@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea
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/Dockerfile +20 -4
- package/bun.lock +2 -2
- package/docker-entrypoint.sh +4 -2
- 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/examples/plugins/echo/README.md +61 -66
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +3378 -335
- package/package.json +2 -2
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- 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 +95 -2
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
- package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
- package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
- package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- 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__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/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 +1 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- 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 +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-api-shim.test.ts +3 -6
- package/src/__tests__/plugin-bootstrap.test.ts +14 -40
- package/src/__tests__/plugin-registry.test.ts +3 -76
- package/src/__tests__/plugin-types.test.ts +0 -193
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- 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__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- 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 +468 -5
- 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 +0 -2
- 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 +54 -286
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- 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 +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -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 +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +362 -485
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +374 -0
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/acp/SKILL.md +83 -31
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
- 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/messaging/SKILL.md +0 -7
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +48 -7
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +128 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +5 -7
- 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__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
- package/src/daemon/conversation-agent-loop.ts +301 -997
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +218 -398
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +32 -20
- package/src/daemon/external-plugins-bootstrap.ts +17 -18
- 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 +3 -1
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +42 -10
- package/src/daemon/lifecycle.ts +25 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- 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 +1 -3
- package/src/daemon/server.ts +2 -0
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- 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/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- 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__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +32 -5
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- 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/index.ts +3 -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 +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
- package/src/memory/v2/consolidation-job.ts +14 -5
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +7 -5
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +8 -19
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +2 -18
- 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/register.ts → memory-retrieval/injectors.ts} +288 -81
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
- package/src/{memory/v3 → 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/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
- package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
- package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -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 +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +6 -305
- package/src/plugins/registry.ts +10 -55
- package/src/plugins/types.ts +62 -797
- package/src/plugins/user-loader.ts +30 -127
- package/src/proactive-artifact/aux-message-injector.ts +4 -4
- package/src/proactive-artifact/job.test.ts +8 -13
- package/src/prompts/__tests__/system-prompt.test.ts +42 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +10 -6
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +2 -5
- package/src/runtime/assistant-event-hub.ts +37 -7
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -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 +6 -2
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +3 -1
- package/src/runtime/routes/conversation-routes.ts +372 -185
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +224 -18
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +66 -34
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- 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/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/signals/cancel.ts +2 -4
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/subagent/manager.ts +17 -5
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -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 +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/executor.ts +1 -53
- package/src/tools/filesystem/write.ts +34 -0
- 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-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +34 -5
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- 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/registry.ts +6 -0
- package/docs/plugins.md +0 -836
- package/examples/plugins/echo/register.ts +0 -184
- 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 -251
- package/src/__tests__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/pipeline-runner.test.ts +0 -564
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- 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 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/memory/v3/provider-blocks.ts +0 -16
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
package/src/agent/loop.ts
CHANGED
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node";
|
|
2
2
|
|
|
3
3
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
4
|
+
import { stripInjectionsForCompaction } from "../context/strip-injections.js";
|
|
4
5
|
import {
|
|
5
6
|
estimatePromptTokensRaw,
|
|
7
|
+
estimatePromptTokensWithTools,
|
|
6
8
|
estimateToolsTokens,
|
|
7
9
|
getCalibrationProviderKey,
|
|
8
10
|
} from "../context/token-estimator.js";
|
|
9
|
-
import { calculateMaxToolResultChars } from "../context/tool-result-truncation.js";
|
|
10
11
|
import type { ContextWindowResult } from "../context/window-manager.js";
|
|
11
12
|
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "../plugins/
|
|
21
|
-
import {
|
|
22
|
-
import { getMiddlewaresFor } from "../plugins/registry.js";
|
|
23
|
-
import type {
|
|
24
|
-
CompactionArgs,
|
|
25
|
-
CompactionCircuitEvent,
|
|
26
|
-
CompactionResult,
|
|
27
|
-
EmptyResponseArgs,
|
|
28
|
-
EmptyResponseDecision,
|
|
29
|
-
EstimateArgs,
|
|
30
|
-
EstimateResult,
|
|
31
|
-
LLMCallArgs,
|
|
32
|
-
LLMCallResult,
|
|
33
|
-
ToolErrorArgs,
|
|
34
|
-
ToolErrorDecision,
|
|
35
|
-
TurnContext,
|
|
36
|
-
} from "../plugins/types.js";
|
|
37
|
-
import { PluginTimeoutError } from "../plugins/types.js";
|
|
13
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
14
|
+
import type { PostToolUseContext, StopContext } from "../plugin-api/types.js";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_COMPACTION_PLUGIN_NAME,
|
|
17
|
+
defaultCompact,
|
|
18
|
+
} from "../plugins/defaults/compaction/compact.js";
|
|
19
|
+
import type { PostCompactionHookInput } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
20
|
+
import { runHook } from "../plugins/pipeline.js";
|
|
21
|
+
import type { CompactionCircuitEvent, TurnContext } from "../plugins/types.js";
|
|
22
|
+
import { PluginExecutionError } from "../plugins/types.js";
|
|
38
23
|
import { normalizeThinkingConfigForWire } from "../providers/thinking-config.js";
|
|
39
24
|
import type {
|
|
40
25
|
ContentBlock,
|
|
41
26
|
Message,
|
|
42
27
|
Provider,
|
|
28
|
+
ProviderResponse,
|
|
29
|
+
SendMessageOptions,
|
|
43
30
|
ToolDefinition,
|
|
44
31
|
ToolResultContent,
|
|
45
32
|
} from "../providers/types.js";
|
|
@@ -48,7 +35,7 @@ import {
|
|
|
48
35
|
applyStreamingSubstitution,
|
|
49
36
|
applySubstitutions,
|
|
50
37
|
} from "../tools/sensitive-output-placeholders.js";
|
|
51
|
-
import {
|
|
38
|
+
import { ProviderError } from "../util/errors.js";
|
|
52
39
|
import { getLogger } from "../util/logger.js";
|
|
53
40
|
import { isRetryableNetworkError } from "../util/retry.js";
|
|
54
41
|
import { CompactionCircuit } from "./compaction-circuit.js";
|
|
@@ -95,17 +82,28 @@ export type ExitReason = "handoff" | "budget";
|
|
|
95
82
|
|
|
96
83
|
export type CheckpointDecision = "continue" | ExitReason;
|
|
97
84
|
|
|
98
|
-
/**
|
|
99
|
-
* Result of {@link AgentLoop.run}.
|
|
100
|
-
*
|
|
101
|
-
* `exitReason` carries the reason the loop paused at a checkpoint so the
|
|
102
|
-
* orchestrator reads the loop's own signal instead of inferring it from
|
|
103
|
-
* callback side-effects. It is `null` whenever the loop reached a terminal
|
|
104
|
-
* stop (completion, error, abort, or a tool-requested yield-to-user).
|
|
105
|
-
*/
|
|
85
|
+
/** Result of {@link AgentLoop.run}. */
|
|
106
86
|
export interface AgentLoopRunResult {
|
|
87
|
+
/** Full conversation history after the run, including everything appended this run. */
|
|
107
88
|
history: Message[];
|
|
89
|
+
/**
|
|
90
|
+
* Reason the loop paused at a checkpoint, or `null` on a terminal stop
|
|
91
|
+
* (completion, error, abort, or a tool-requested yield-to-user).
|
|
92
|
+
*/
|
|
108
93
|
exitReason: ExitReason | null;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the loop produced at least one new assistant message this run —
|
|
96
|
+
* the forward-progress signal for the ordering-error retry gate and the
|
|
97
|
+
* overflow convergence fold (immune to in-loop compaction shrinking history
|
|
98
|
+
* below a pre-run length).
|
|
99
|
+
*/
|
|
100
|
+
appendedNewMessages: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Slice of `history` appended this run, measured from the loop's input or
|
|
103
|
+
* from the compacted base when it compacts in place. The loop owns this
|
|
104
|
+
* boundary, so it cannot desync the way an externally-held index can.
|
|
105
|
+
*/
|
|
106
|
+
newMessages: Message[];
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
/**
|
|
@@ -129,8 +127,6 @@ export interface AgentLoopRunResult {
|
|
|
129
127
|
export type AgentLoopExitReason =
|
|
130
128
|
/** `if (signal?.aborted) break;` at the top of the loop. */
|
|
131
129
|
| "aborted_pre_call"
|
|
132
|
-
/** Empty assistant response after the configured retry budget. */
|
|
133
|
-
| "empty_response_exhausted"
|
|
134
130
|
/** Assistant message has no tool-use blocks (or no tool executor). */
|
|
135
131
|
| "no_tool_calls"
|
|
136
132
|
/** Signal aborted while building the user-side tool-results message. */
|
|
@@ -209,6 +205,14 @@ export type AgentEvent =
|
|
|
209
205
|
approvalReason?: string;
|
|
210
206
|
riskThreshold?: string;
|
|
211
207
|
activityMetadata?: ToolActivityMetadata;
|
|
208
|
+
/**
|
|
209
|
+
* Set when the loop synthesizes this result for a tool_use that never
|
|
210
|
+
* executed (a "Cancelled by user" block on abort). The daemon still
|
|
211
|
+
* captures it into `pendingToolResults` and forwards it to the client,
|
|
212
|
+
* but skips the side effects that assume the tool ran — marking the
|
|
213
|
+
* workspace dirty and emitting a post-tool "thinking" activity state.
|
|
214
|
+
*/
|
|
215
|
+
cancelled?: boolean;
|
|
212
216
|
}
|
|
213
217
|
| { type: "tool_use_preview_start"; toolUseId: string; toolName: string }
|
|
214
218
|
| {
|
|
@@ -243,7 +247,7 @@ export type AgentEvent =
|
|
|
243
247
|
| { type: "error"; error: Error }
|
|
244
248
|
| {
|
|
245
249
|
/**
|
|
246
|
-
* Emitted when the
|
|
250
|
+
* Emitted when the provider call throws — i.e. the provider
|
|
247
251
|
* rejected the request before returning a usable response. Carries
|
|
248
252
|
* the loop-level raw request we attempted to send (messages, tools,
|
|
249
253
|
* system prompt, provider-agnostic config) plus the thrown error.
|
|
@@ -295,6 +299,42 @@ export type AgentEvent =
|
|
|
295
299
|
*/
|
|
296
300
|
type: "context_compacting";
|
|
297
301
|
}
|
|
302
|
+
| {
|
|
303
|
+
/**
|
|
304
|
+
* Emitted after the loop's inline mid-loop compaction pipeline runs,
|
|
305
|
+
* immediately before re-injection — whether or not the pipeline actually
|
|
306
|
+
* compacted. The daemon's event dispatcher always commits `basis` (the
|
|
307
|
+
* stripped pre-compaction history) as the conversation's durable message
|
|
308
|
+
* state, so re-injection ({@link MidLoopCompaction.reinject}) re-applies
|
|
309
|
+
* injections onto the stripped base rather than stacking on top of the
|
|
310
|
+
* still-injected messages. When `result.compacted` is set it
|
|
311
|
+
* additionally commits the durable compaction result (DB-record fields,
|
|
312
|
+
* graph-memory side effects, SSE) and flips the per-turn re-injection
|
|
313
|
+
* guards on the handler state.
|
|
314
|
+
*
|
|
315
|
+
* Treated as a critical event: a failed durable commit re-throws so the
|
|
316
|
+
* turn aborts rather than re-injecting against half-applied state.
|
|
317
|
+
*
|
|
318
|
+
* `basis` is the stripped pre-compaction history the summary was built
|
|
319
|
+
* from; the dispatcher uses it to project Slack provenance onto the
|
|
320
|
+
* compacted result.
|
|
321
|
+
*/
|
|
322
|
+
type: "compaction_completed";
|
|
323
|
+
result: ContextWindowResult;
|
|
324
|
+
basis: Message[];
|
|
325
|
+
}
|
|
326
|
+
| {
|
|
327
|
+
/**
|
|
328
|
+
* Emitted right after the loop strips runtime injections from the
|
|
329
|
+
* running history, before the compaction pipeline runs. The daemon's
|
|
330
|
+
* event dispatcher records the history-stripped marker — a Conversation
|
|
331
|
+
* DB-record field read back at load time to strip embedded injection
|
|
332
|
+
* prefixes from pre-strip messages. Best-effort: a transient marker
|
|
333
|
+
* write must not abort the turn, so unlike `compaction_completed` this
|
|
334
|
+
* event is not treated as critical.
|
|
335
|
+
*/
|
|
336
|
+
type: "history_stripped";
|
|
337
|
+
}
|
|
298
338
|
/**
|
|
299
339
|
* Circuit-breaker transitions emitted when auto-compaction is paused
|
|
300
340
|
* (`compaction_circuit_open`, after three consecutive summary-LLM
|
|
@@ -324,8 +364,7 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
|
|
|
324
364
|
minTurnIntervalMs: 150,
|
|
325
365
|
};
|
|
326
366
|
|
|
327
|
-
const
|
|
328
|
-
const MAX_EMPTY_RESPONSE_RETRIES = 1;
|
|
367
|
+
const MAX_STOP_CONTINUE_RETRIES = 1;
|
|
329
368
|
const MAX_TOKENS_STOP_REASONS = new Set([
|
|
330
369
|
"length",
|
|
331
370
|
"max_output_tokens",
|
|
@@ -346,12 +385,11 @@ export function isMaxTokensStopReason(
|
|
|
346
385
|
* {@link AgentLoop.run}); this helper is the fallback used only by unit
|
|
347
386
|
* tests that construct `AgentLoop` directly without an orchestrator.
|
|
348
387
|
*
|
|
349
|
-
* When the orchestrator-supplied context is present
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* current tool-use iteration.
|
|
388
|
+
* When the orchestrator-supplied context is present it is used directly so the
|
|
389
|
+
* pipeline sees the real `conversationId`, trust, and `contextWindowManager`.
|
|
390
|
+
* In the fallback path the returned context is still useful for pipeline
|
|
391
|
+
* logging: `requestId` surfaces in every structured record, and `turnIndex`
|
|
392
|
+
* reflects the current tool-use iteration.
|
|
355
393
|
*/
|
|
356
394
|
function buildLoopTurnContext(
|
|
357
395
|
requestId: string | undefined,
|
|
@@ -371,29 +409,6 @@ function buildLoopTurnContext(
|
|
|
371
409
|
};
|
|
372
410
|
}
|
|
373
411
|
|
|
374
|
-
/**
|
|
375
|
-
* Produce a `TurnContext` for a pipeline call inside {@link AgentLoop.run}.
|
|
376
|
-
*
|
|
377
|
-
* When the orchestrator supplied a `turnContext`, clone it and overwrite
|
|
378
|
-
* `requestId` + `turnIndex` with the loop-scoped values so plugin log
|
|
379
|
-
* records correctly attribute the call to the current tool-use iteration
|
|
380
|
-
* while preserving the real `conversationId`, trust context, and
|
|
381
|
-
* `contextWindowManager` the orchestrator assembled for the turn. Without
|
|
382
|
-
* an orchestrator context (unit tests that instantiate `AgentLoop` with no
|
|
383
|
-
* `turnContext`), fall back to {@link buildLoopTurnContext}'s synthesized
|
|
384
|
-
* placeholder.
|
|
385
|
-
*/
|
|
386
|
-
function resolveLoopTurnContext(
|
|
387
|
-
base: TurnContext | undefined,
|
|
388
|
-
requestId: string | undefined,
|
|
389
|
-
turnIndex: number,
|
|
390
|
-
): TurnContext {
|
|
391
|
-
if (base) {
|
|
392
|
-
return { ...base, requestId: requestId ?? base.requestId, turnIndex };
|
|
393
|
-
}
|
|
394
|
-
return buildLoopTurnContext(requestId, turnIndex);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
412
|
/**
|
|
398
413
|
* User-config HTTP status codes that should never page the on-call: billing
|
|
399
414
|
* exhaustion (402), invalid credentials (401), and forbidden/plan-gated (403).
|
|
@@ -437,28 +452,25 @@ export interface ResolvedSystemPrompt {
|
|
|
437
452
|
}
|
|
438
453
|
|
|
439
454
|
/**
|
|
440
|
-
* Orchestrator-supplied
|
|
455
|
+
* Orchestrator-supplied hook the loop invokes when the mid-loop budget gate
|
|
441
456
|
* trips and inline compaction runs. The loop owns the trigger, the
|
|
442
|
-
*
|
|
443
|
-
* bookkeeping + the exhaustion decision), and the inline continue;
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
457
|
+
* compaction call, the result interpretation (circuit-breaker
|
|
458
|
+
* bookkeeping + the exhaustion decision), and the inline continue; this hook
|
|
459
|
+
* bridges the injection state the loop is intentionally blind to. Durable
|
|
460
|
+
* persistence is signalled out-of-band via the `history_stripped` (marker)
|
|
461
|
+
* and `compaction_completed` (basis commit + successful summary) {@link
|
|
462
|
+
* AgentEvent}s; the {@link MidLoopCompaction.postCompactionHook} is
|
|
463
|
+
* orchestrator-supplied, and its inputs migrate loop-ward as the loop
|
|
464
|
+
* subsumes the re-injection ceremony.
|
|
448
465
|
*/
|
|
449
466
|
export interface MidLoopCompaction {
|
|
450
|
-
/**
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
result: ContextWindowResult,
|
|
458
|
-
rawHistory: Message[],
|
|
459
|
-
) => Promise<void>;
|
|
460
|
-
/** Re-apply runtime injections and return the history to continue from. */
|
|
461
|
-
reinject: () => Promise<Message[]>;
|
|
467
|
+
/**
|
|
468
|
+
* Re-apply runtime injections onto the post-compaction history and return
|
|
469
|
+
* the history to continue from. The loop supplies its own working state via
|
|
470
|
+
* {@link PostCompactionHookInput} so the hook re-injects from that rather
|
|
471
|
+
* than reading it back from orchestrator state.
|
|
472
|
+
*/
|
|
473
|
+
postCompactionHook: (input: PostCompactionHookInput) => Promise<Message[]>;
|
|
462
474
|
}
|
|
463
475
|
|
|
464
476
|
export interface AgentLoopRunOptions {
|
|
@@ -518,21 +530,12 @@ export interface AgentLoopRunOptions {
|
|
|
518
530
|
|
|
519
531
|
/**
|
|
520
532
|
* Callback shape the loop uses to execute a tool invocation.
|
|
521
|
-
*
|
|
522
|
-
* The trailing `turnContext` is optional so in-process tests that wire the
|
|
523
|
-
* callback without an orchestrator keep working. Production sites (the
|
|
524
|
-
* `Conversation`'s `createToolExecutor`) forward the supplied context into
|
|
525
|
-
* `ToolExecutor.execute` so the `toolExecute` pipeline sees the orchestrator's
|
|
526
|
-
* real conversation identity/trust/contextWindowManager instead of the
|
|
527
|
-
* synthesized placeholder `ToolExecutor` would otherwise build from the
|
|
528
|
-
* `ToolContext` alone.
|
|
529
533
|
*/
|
|
530
534
|
export type LoopToolExecutor = (
|
|
531
535
|
name: string,
|
|
532
536
|
input: Record<string, unknown>,
|
|
533
537
|
onOutput?: (chunk: string) => void,
|
|
534
538
|
toolUseId?: string,
|
|
535
|
-
turnContext?: TurnContext,
|
|
536
539
|
) => Promise<{
|
|
537
540
|
content: string;
|
|
538
541
|
isError: boolean;
|
|
@@ -624,10 +627,9 @@ export class AgentLoop {
|
|
|
624
627
|
* Resolve the tool definitions sent to the provider for the given turn.
|
|
625
628
|
*
|
|
626
629
|
* Mirrors the logic of {@link getToolTokenBudget} but returns the tool
|
|
627
|
-
* array itself — callers that need to thread the tool set into
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
* resolver fork.
|
|
630
|
+
* array itself — callers that need to thread the tool set into the token
|
|
631
|
+
* estimate (`estimatePromptTokensWithTools`, whose args include `tools`)
|
|
632
|
+
* use this rather than re-implementing the dynamic-vs-static resolver fork.
|
|
631
633
|
*/
|
|
632
634
|
getResolvedTools(history?: Message[]): ToolDefinition[] {
|
|
633
635
|
return history && this.resolveTools
|
|
@@ -648,28 +650,15 @@ export class AgentLoop {
|
|
|
648
650
|
}
|
|
649
651
|
|
|
650
652
|
/**
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
* context from the loop's live `history`.
|
|
653
|
+
* Calibrated prompt-token estimate for `history`, including the
|
|
654
|
+
* resolved-tool budget for the turn.
|
|
654
655
|
*/
|
|
655
|
-
private estimateTokens(
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
getMiddlewaresFor("tokenEstimate"),
|
|
662
|
-
defaultTokenEstimateTerminal,
|
|
663
|
-
{
|
|
664
|
-
history: Object.freeze([...history]) as Message[],
|
|
665
|
-
systemPrompt: this.systemPrompt,
|
|
666
|
-
tools: Object.freeze([
|
|
667
|
-
...this.getResolvedTools(history),
|
|
668
|
-
]) as ToolDefinition[],
|
|
669
|
-
providerName: getCalibrationProviderKey(this.provider),
|
|
670
|
-
},
|
|
671
|
-
turnContext,
|
|
672
|
-
DEFAULT_TIMEOUTS.tokenEstimate,
|
|
656
|
+
private estimateTokens(history: Message[]): number {
|
|
657
|
+
return estimatePromptTokensWithTools(
|
|
658
|
+
history,
|
|
659
|
+
this.systemPrompt,
|
|
660
|
+
this.getResolvedTools(history),
|
|
661
|
+
getCalibrationProviderKey(this.provider),
|
|
673
662
|
);
|
|
674
663
|
}
|
|
675
664
|
|
|
@@ -688,15 +677,7 @@ export class AgentLoop {
|
|
|
688
677
|
onEvent: (event: AgentEvent) => void | Promise<void>,
|
|
689
678
|
): Promise<void> {
|
|
690
679
|
try {
|
|
691
|
-
await this.compactionCircuit.recordOutcome(
|
|
692
|
-
{
|
|
693
|
-
currentRequestId: turnContext.requestId,
|
|
694
|
-
currentTurnTrustContext: turnContext.trust,
|
|
695
|
-
turnCount: turnContext.turnIndex,
|
|
696
|
-
},
|
|
697
|
-
summaryFailed,
|
|
698
|
-
onEvent,
|
|
699
|
-
);
|
|
680
|
+
await this.compactionCircuit.recordOutcome(summaryFailed, onEvent);
|
|
700
681
|
} catch (recordError) {
|
|
701
682
|
log.error(
|
|
702
683
|
{ err: recordError, requestId: turnContext.requestId },
|
|
@@ -708,11 +689,10 @@ export class AgentLoop {
|
|
|
708
689
|
/**
|
|
709
690
|
* Compact the running history in place when the mid-loop budget gate trips.
|
|
710
691
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
* `exitReason = "budget"` and the orchestrator escalates.
|
|
692
|
+
* Calls the default compaction plugin on the stripped history, then
|
|
693
|
+
* re-applies injections via the supplied hooks. Returns the history to
|
|
694
|
+
* continue from, or `null` when the compactor exhausted its retry budget so
|
|
695
|
+
* the caller yields `exitReason = "budget"` and the orchestrator escalates.
|
|
716
696
|
*/
|
|
717
697
|
private async compact(
|
|
718
698
|
history: Message[],
|
|
@@ -720,32 +700,37 @@ export class AgentLoop {
|
|
|
720
700
|
compaction: MidLoopCompaction,
|
|
721
701
|
signal: AbortSignal | undefined,
|
|
722
702
|
onEvent: (event: AgentEvent) => void | Promise<void>,
|
|
703
|
+
overrideProfile: string | null,
|
|
723
704
|
): Promise<Message[] | null> {
|
|
724
705
|
await onEvent({ type: "context_compacting" });
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
706
|
+
// Strip runtime injections so the compactor summarizes the raw persistent
|
|
707
|
+
// messages.
|
|
708
|
+
const rawHistory = stripInjectionsForCompaction(history);
|
|
709
|
+
// Record the history-stripped marker right after stripping, before the
|
|
710
|
+
// pipeline runs.
|
|
711
|
+
await onEvent({ type: "history_stripped" });
|
|
712
|
+
const manager = turnContext.contextWindowManager;
|
|
713
|
+
if (manager == null) {
|
|
714
|
+
throw new PluginExecutionError(
|
|
715
|
+
"default-compaction: turnContext.contextWindowManager is missing — orchestrator must attach it before invoking compaction",
|
|
716
|
+
DEFAULT_COMPACTION_PLUGIN_NAME,
|
|
735
717
|
);
|
|
736
|
-
} catch (error) {
|
|
737
|
-
if (error instanceof PluginTimeoutError) {
|
|
738
|
-
// A timeout counts as a compaction failure against the circuit breaker.
|
|
739
|
-
await this.recordCompactionOutcome(turnContext, true, onEvent);
|
|
740
|
-
return null;
|
|
741
|
-
}
|
|
742
|
-
throw error;
|
|
743
718
|
}
|
|
744
|
-
//
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
//
|
|
719
|
+
// The mid-loop budget gate is reached only when this turn decides to
|
|
720
|
+
// compact in place, so `force` past the auto-threshold check.
|
|
721
|
+
// `actorTrustClass` comes from the turn context (the actor whose turn
|
|
722
|
+
// triggered compaction) so the compactor's image manifest excludes
|
|
723
|
+
// guardian-only attachments for untrusted actors. `overrideProfile` is the
|
|
724
|
+
// turn's resolved inference-profile override for the summary call.
|
|
725
|
+
const compactResult = await defaultCompact({
|
|
726
|
+
manager,
|
|
727
|
+
messages: rawHistory,
|
|
728
|
+
signal,
|
|
729
|
+
force: true,
|
|
730
|
+
actorTrustClass: turnContext.trust.trustClass,
|
|
731
|
+
overrideProfile,
|
|
732
|
+
});
|
|
733
|
+
// `force: true` bypasses the auto-threshold gate, but early returns
|
|
749
734
|
// for "no eligible messages" / "insufficient messages" still leave
|
|
750
735
|
// `summaryFailed` undefined. Only record an outcome when the summary LLM
|
|
751
736
|
// actually ran.
|
|
@@ -756,13 +741,25 @@ export class AgentLoop {
|
|
|
756
741
|
onEvent,
|
|
757
742
|
);
|
|
758
743
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
744
|
+
// Emit unconditionally: the dispatcher commits the stripped `basis` as the
|
|
745
|
+
// durable message base whether or not the pipeline compacted (re-injection
|
|
746
|
+
// reads it), and runs the durable compaction commit only when
|
|
747
|
+
// `result.compacted`.
|
|
748
|
+
await onEvent({
|
|
749
|
+
type: "compaction_completed",
|
|
750
|
+
result: compactResult,
|
|
751
|
+
basis: rawHistory,
|
|
752
|
+
});
|
|
762
753
|
if (compactResult.exhausted ?? false) {
|
|
763
754
|
return null;
|
|
764
755
|
}
|
|
765
|
-
|
|
756
|
+
// Re-inject onto the same base the `compaction_completed` dispatch commits:
|
|
757
|
+
// the compacted messages when the pipeline compacted, the stripped
|
|
758
|
+
// pre-compaction history otherwise.
|
|
759
|
+
return compaction.postCompactionHook({
|
|
760
|
+
history: compactResult.compacted ? compactResult.messages : rawHistory,
|
|
761
|
+
turnContext,
|
|
762
|
+
});
|
|
766
763
|
}
|
|
767
764
|
|
|
768
765
|
async run(
|
|
@@ -783,26 +780,36 @@ export class AgentLoop {
|
|
|
783
780
|
mutableLatestUserMessage,
|
|
784
781
|
} = options ?? {};
|
|
785
782
|
let history = [...messages];
|
|
783
|
+
// Index into `history` where this run's appended output begins. It starts
|
|
784
|
+
// after the input and resets to the compacted base whenever the loop
|
|
785
|
+
// compacts in place, so `history.slice(newMessagesStart)` is always exactly
|
|
786
|
+
// what the loop produced since the last (re-injected) base.
|
|
787
|
+
let newMessagesStart = history.length;
|
|
786
788
|
let producedVisibleTextThisRun = false;
|
|
787
789
|
let toolUseTurns = 0;
|
|
788
|
-
let
|
|
789
|
-
let emptyResponseRetries = 0;
|
|
790
|
+
let stopContinueRetries = 0;
|
|
790
791
|
let lastLlmCallTime = 0;
|
|
791
792
|
let exitReason: ExitReason | null = null;
|
|
793
|
+
let appendedNewMessages = false;
|
|
792
794
|
const rlog = requestId ? log.child({ requestId }) : log;
|
|
793
795
|
|
|
796
|
+
// Resolve the inference-profile override that applies right now. The
|
|
797
|
+
// optional resolver lets a turn observe a confirmed mid-turn profile switch
|
|
798
|
+
// before the next model call; absent a resolver the turn-start value holds.
|
|
799
|
+
const resolveEffectiveOverrideProfile = (): string | undefined =>
|
|
800
|
+
resolveOverrideProfile ? resolveOverrideProfile() : overrideProfile;
|
|
801
|
+
|
|
794
802
|
// Per-run substitution map for sensitive output placeholders.
|
|
795
803
|
// Bindings are accumulated from tool results; placeholders are
|
|
796
804
|
// resolved in streamed deltas and final assistant message text.
|
|
797
805
|
const substitutionMap = new Map<string, string>();
|
|
798
806
|
let streamingPending = "";
|
|
799
807
|
|
|
800
|
-
// Idempotency guard for `emitExit
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
// double-emits if a new break site is added without checking this.
|
|
808
|
+
// Idempotency guard for `emitExit`: the first reason stamped wins. A break
|
|
809
|
+
// site that stamps a specific reason before unwinding into the catch
|
|
810
|
+
// handler keeps that reason instead of the generic "error", and the guard
|
|
811
|
+
// also defends against accidental double-emits if a new break site is
|
|
812
|
+
// added without checking this.
|
|
806
813
|
let exitReasonEmitted = false;
|
|
807
814
|
const emitExit = async (reason: AgentLoopExitReason): Promise<void> => {
|
|
808
815
|
if (exitReasonEmitted) return;
|
|
@@ -923,12 +930,8 @@ export class AgentLoop {
|
|
|
923
930
|
// `activeProfile` and any call-site named profile. Threading it on
|
|
924
931
|
// every send (rather than once at construction) keeps subagents that
|
|
925
932
|
// share an `AgentLoop` instance but ought to inherit a different
|
|
926
|
-
// profile correct — and matches how `callSite` is plumbed.
|
|
927
|
-
|
|
928
|
-
// profile-session switch before the next model call.
|
|
929
|
-
const effectiveOverrideProfile = resolveOverrideProfile
|
|
930
|
-
? resolveOverrideProfile()
|
|
931
|
-
: overrideProfile;
|
|
933
|
+
// profile correct — and matches how `callSite` is plumbed.
|
|
934
|
+
const effectiveOverrideProfile = resolveEffectiveOverrideProfile();
|
|
932
935
|
if (effectiveOverrideProfile) {
|
|
933
936
|
providerConfig.overrideProfile = effectiveOverrideProfile;
|
|
934
937
|
}
|
|
@@ -974,95 +977,76 @@ export class AgentLoop {
|
|
|
974
977
|
stripOldMediaBlocks(history),
|
|
975
978
|
);
|
|
976
979
|
|
|
977
|
-
//
|
|
978
|
-
//
|
|
979
|
-
//
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
config: providerConfig,
|
|
997
|
-
onEvent: (event) => {
|
|
998
|
-
if (event.type === "text_delta") {
|
|
999
|
-
// Apply sensitive-output placeholder substitution (chunk-safe)
|
|
1000
|
-
if (substitutionMap.size > 0) {
|
|
1001
|
-
const combined = streamingPending + event.text;
|
|
1002
|
-
const { emit, pending } = applyStreamingSubstitution(
|
|
1003
|
-
combined,
|
|
1004
|
-
substitutionMap,
|
|
1005
|
-
);
|
|
1006
|
-
streamingPending = pending;
|
|
1007
|
-
if (emit.length > 0) {
|
|
1008
|
-
onEvent({ type: "text_delta", text: emit });
|
|
1009
|
-
}
|
|
1010
|
-
} else {
|
|
1011
|
-
onEvent({ type: "text_delta", text: event.text });
|
|
980
|
+
// The `onEvent` wrapping below applies sensitive-output placeholder
|
|
981
|
+
// substitution to streamed text while forwarding every other event
|
|
982
|
+
// type through unchanged.
|
|
983
|
+
const providerOptions: SendMessageOptions = {
|
|
984
|
+
tools: currentTools.length > 0 ? currentTools : undefined,
|
|
985
|
+
systemPrompt: turnSystemPrompt,
|
|
986
|
+
config: providerConfig,
|
|
987
|
+
onEvent: (event) => {
|
|
988
|
+
if (event.type === "text_delta") {
|
|
989
|
+
// Apply sensitive-output placeholder substitution (chunk-safe)
|
|
990
|
+
if (substitutionMap.size > 0) {
|
|
991
|
+
const combined = streamingPending + event.text;
|
|
992
|
+
const { emit, pending } = applyStreamingSubstitution(
|
|
993
|
+
combined,
|
|
994
|
+
substitutionMap,
|
|
995
|
+
);
|
|
996
|
+
streamingPending = pending;
|
|
997
|
+
if (emit.length > 0) {
|
|
998
|
+
onEvent({ type: "text_delta", text: emit });
|
|
1012
999
|
}
|
|
1013
|
-
} else
|
|
1014
|
-
onEvent({ type: "
|
|
1015
|
-
} else if (event.type === "tool_use_preview_start") {
|
|
1016
|
-
onEvent({
|
|
1017
|
-
type: "tool_use_preview_start",
|
|
1018
|
-
toolUseId: event.toolUseId,
|
|
1019
|
-
toolName: event.toolName,
|
|
1020
|
-
});
|
|
1021
|
-
} else if (event.type === "input_json_delta") {
|
|
1022
|
-
onEvent({
|
|
1023
|
-
type: "input_json_delta",
|
|
1024
|
-
toolName: event.toolName,
|
|
1025
|
-
toolUseId: event.toolUseId,
|
|
1026
|
-
accumulatedJson: event.accumulatedJson,
|
|
1027
|
-
});
|
|
1028
|
-
} else if (event.type === "server_tool_start") {
|
|
1029
|
-
onEvent({
|
|
1030
|
-
type: "server_tool_start",
|
|
1031
|
-
name: event.name,
|
|
1032
|
-
toolUseId: event.toolUseId,
|
|
1033
|
-
input: event.input,
|
|
1034
|
-
});
|
|
1035
|
-
} else if (event.type === "server_tool_complete") {
|
|
1036
|
-
onEvent({
|
|
1037
|
-
type: "server_tool_complete",
|
|
1038
|
-
toolUseId: event.toolUseId,
|
|
1039
|
-
isError: event.isError,
|
|
1040
|
-
...(event.content ? { content: event.content } : {}),
|
|
1041
|
-
...(event.resolvedInput
|
|
1042
|
-
? { resolvedInput: event.resolvedInput }
|
|
1043
|
-
: {}),
|
|
1044
|
-
...(event.errorCode ? { errorCode: event.errorCode } : {}),
|
|
1045
|
-
...(event.errorMessage
|
|
1046
|
-
? { errorMessage: event.errorMessage }
|
|
1047
|
-
: {}),
|
|
1048
|
-
});
|
|
1000
|
+
} else {
|
|
1001
|
+
onEvent({ type: "text_delta", text: event.text });
|
|
1049
1002
|
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1003
|
+
} else if (event.type === "thinking_delta") {
|
|
1004
|
+
onEvent({ type: "thinking_delta", thinking: event.thinking });
|
|
1005
|
+
} else if (event.type === "tool_use_preview_start") {
|
|
1006
|
+
onEvent({
|
|
1007
|
+
type: "tool_use_preview_start",
|
|
1008
|
+
toolUseId: event.toolUseId,
|
|
1009
|
+
toolName: event.toolName,
|
|
1010
|
+
});
|
|
1011
|
+
} else if (event.type === "input_json_delta") {
|
|
1012
|
+
onEvent({
|
|
1013
|
+
type: "input_json_delta",
|
|
1014
|
+
toolName: event.toolName,
|
|
1015
|
+
toolUseId: event.toolUseId,
|
|
1016
|
+
accumulatedJson: event.accumulatedJson,
|
|
1017
|
+
});
|
|
1018
|
+
} else if (event.type === "server_tool_start") {
|
|
1019
|
+
onEvent({
|
|
1020
|
+
type: "server_tool_start",
|
|
1021
|
+
name: event.name,
|
|
1022
|
+
toolUseId: event.toolUseId,
|
|
1023
|
+
input: event.input,
|
|
1024
|
+
});
|
|
1025
|
+
} else if (event.type === "server_tool_complete") {
|
|
1026
|
+
onEvent({
|
|
1027
|
+
type: "server_tool_complete",
|
|
1028
|
+
toolUseId: event.toolUseId,
|
|
1029
|
+
isError: event.isError,
|
|
1030
|
+
...(event.content ? { content: event.content } : {}),
|
|
1031
|
+
...(event.resolvedInput
|
|
1032
|
+
? { resolvedInput: event.resolvedInput }
|
|
1033
|
+
: {}),
|
|
1034
|
+
...(event.errorCode ? { errorCode: event.errorCode } : {}),
|
|
1035
|
+
...(event.errorMessage
|
|
1036
|
+
? { errorMessage: event.errorMessage }
|
|
1037
|
+
: {}),
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1052
1040
|
},
|
|
1041
|
+
signal,
|
|
1053
1042
|
};
|
|
1054
1043
|
|
|
1055
|
-
// Per-turn pipeline context.
|
|
1056
|
-
// `turnContext` into `run()
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const turnCtx = resolveLoopTurnContext(
|
|
1062
|
-
turnContext,
|
|
1063
|
-
requestId,
|
|
1064
|
-
toolUseTurns,
|
|
1065
|
-
);
|
|
1044
|
+
// Per-turn pipeline context. Real call sites thread a full
|
|
1045
|
+
// `turnContext` into `run()` and it is used directly; standalone
|
|
1046
|
+
// unit-test instantiations that never plumb a context through fall
|
|
1047
|
+
// back to a synthesized placeholder scoped to the tool-use iteration.
|
|
1048
|
+
const turnCtx =
|
|
1049
|
+
turnContext ?? buildLoopTurnContext(requestId, toolUseTurns);
|
|
1066
1050
|
|
|
1067
1051
|
// Announce the LLM-call boundary so downstream handlers (the
|
|
1068
1052
|
// daemon's persistence pipeline) can reserve an empty assistant row
|
|
@@ -1085,15 +1069,11 @@ export class AgentLoop {
|
|
|
1085
1069
|
// `llm_request_logs` row, then re-throw so the existing outer catch
|
|
1086
1070
|
// continues to handle abort sync, Sentry capture, the `error` event,
|
|
1087
1071
|
// and the loop break unchanged.
|
|
1088
|
-
let response:
|
|
1072
|
+
let response: ProviderResponse;
|
|
1089
1073
|
try {
|
|
1090
|
-
response = await
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
(args) => args.provider.sendMessage(args.messages, args.options),
|
|
1094
|
-
llmCallArgs,
|
|
1095
|
-
turnCtx,
|
|
1096
|
-
DEFAULT_TIMEOUTS.llmCall,
|
|
1074
|
+
response = await this.provider.sendMessage(
|
|
1075
|
+
providerHistory,
|
|
1076
|
+
providerOptions,
|
|
1097
1077
|
);
|
|
1098
1078
|
} catch (llmCallError) {
|
|
1099
1079
|
// Skip recording on abort — the user cancelled the request and
|
|
@@ -1111,10 +1091,10 @@ export class AgentLoop {
|
|
|
1111
1091
|
// misrepresent both.
|
|
1112
1092
|
const rawRequest = {
|
|
1113
1093
|
provider: this.provider.name,
|
|
1114
|
-
messages:
|
|
1115
|
-
tools:
|
|
1116
|
-
systemPrompt:
|
|
1117
|
-
config:
|
|
1094
|
+
messages: providerHistory,
|
|
1095
|
+
tools: providerOptions.tools,
|
|
1096
|
+
systemPrompt: providerOptions.systemPrompt,
|
|
1097
|
+
config: providerOptions.config,
|
|
1118
1098
|
};
|
|
1119
1099
|
onEvent({
|
|
1120
1100
|
type: "provider_error",
|
|
@@ -1203,6 +1183,7 @@ export class AgentLoop {
|
|
|
1203
1183
|
"LLM response reached output token limit",
|
|
1204
1184
|
);
|
|
1205
1185
|
history.push(safeAssistantMessage);
|
|
1186
|
+
appendedNewMessages = true;
|
|
1206
1187
|
await onEvent({
|
|
1207
1188
|
type: "max_tokens_reached",
|
|
1208
1189
|
stopReason: response.stopReason,
|
|
@@ -1215,126 +1196,65 @@ export class AgentLoop {
|
|
|
1215
1196
|
break;
|
|
1216
1197
|
}
|
|
1217
1198
|
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
// the
|
|
1222
|
-
//
|
|
1223
|
-
// Only nudge when the model hasn't already delivered text to the user
|
|
1224
|
-
// earlier in this tool-use chain. If a prior assistant turn in history
|
|
1225
|
-
// contained visible text (e.g. the model said its piece before calling
|
|
1226
|
-
// a side-effect tool like `remember`), an empty follow-up is the model
|
|
1227
|
-
// correctly ending its turn — nudging would mislead it into thinking
|
|
1228
|
-
// its earlier text didn't land and cause a verbatim re-send.
|
|
1229
|
-
//
|
|
1230
|
-
// Note: we check ANY prior assistant turn from this run()
|
|
1231
|
-
// invocation, not just the most recent one. In multi-step tool-use
|
|
1232
|
-
// chains (say-something → call-tool → call-another-tool → end),
|
|
1233
|
-
// the "say-something" text lives on an earlier assistant turn while
|
|
1234
|
-
// the most recent assistant turn is a pure tool_use with no text.
|
|
1235
|
-
// Restricting the check to the most recent assistant turn would
|
|
1236
|
-
// falsely nudge in that case and trigger a duplicate re-send of
|
|
1237
|
-
// text the user already saw.
|
|
1238
|
-
//
|
|
1239
|
-
// Scope the scan to messages appended during this run() call only.
|
|
1240
|
-
// Assistant text from prior conversation turns (earlier run()
|
|
1241
|
-
// invocations passed in via `messages`) must NOT suppress the
|
|
1242
|
-
// nudge — those turns completed long ago and have no bearing on
|
|
1243
|
-
// whether the current tool-use chain has delivered text yet.
|
|
1244
|
-
//
|
|
1245
|
-
// The actual decision (nudge vs. accept vs. error) is delegated to
|
|
1246
|
-
// the `emptyResponse` plugin pipeline. The pipeline returns a
|
|
1247
|
-
// decision; the loop carries out the side-effect (pushing the nudge
|
|
1248
|
-
// or surfacing the error). See `plugins/defaults/empty-response/register.ts`
|
|
1249
|
-
// for the default decision logic.
|
|
1199
|
+
// The model's "stop" moment: a response with no tool calls is about to
|
|
1200
|
+
// yield to the user. The `stop` hook (below) decides whether to accept
|
|
1201
|
+
// the turn or re-query with a follow-up; `priorAssistantHadVisibleText`
|
|
1202
|
+
// gates the ops log for the post-tool empty case.
|
|
1250
1203
|
const hasVisibleText = response.content.some(
|
|
1251
1204
|
(block) => block.type === "text" && block.text.trim().length > 0,
|
|
1252
1205
|
);
|
|
1253
|
-
// Track whether the model produced visible text earlier in this
|
|
1254
|
-
// run() invocation. Run-scoped rather than derived from `history` so
|
|
1255
|
-
// it survives inline compaction rewriting the message array: an empty
|
|
1256
|
-
// completion after a compaction must not be nudged into re-sending
|
|
1257
|
-
// text the user already saw.
|
|
1258
1206
|
const priorAssistantHadVisibleText = producedVisibleTextThisRun;
|
|
1259
1207
|
if (hasVisibleText) {
|
|
1260
1208
|
producedVisibleTextThisRun = true;
|
|
1261
1209
|
}
|
|
1262
1210
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
"
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const nudgeText =
|
|
1292
|
-
emptyResponseDecision.nudgeText ??
|
|
1293
|
-
"<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
|
|
1294
|
-
emptyResponseRetries++;
|
|
1295
|
-
rlog.warn(
|
|
1296
|
-
{ turn: toolUseTurns, retry: emptyResponseRetries },
|
|
1297
|
-
"Model returned empty response after tool results — retrying",
|
|
1298
|
-
);
|
|
1299
|
-
history.push({
|
|
1300
|
-
role: "user",
|
|
1301
|
-
content: [{ type: "text", text: nudgeText }],
|
|
1302
|
-
});
|
|
1303
|
-
continue;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
if (emptyResponseDecision.action === "error") {
|
|
1307
|
-
rlog.error(
|
|
1308
|
-
{ turn: toolUseTurns, retries: emptyResponseRetries },
|
|
1309
|
-
"emptyResponse pipeline requested error surface",
|
|
1310
|
-
);
|
|
1311
|
-
// Stamp the specific exit reason *before* throwing. The catch
|
|
1312
|
-
// handler below will see the rethrown error and attempt to stamp
|
|
1313
|
-
// "error" — guarded by `exitReasonEmitted`, that becomes a no-op
|
|
1314
|
-
// and the more specific reason wins.
|
|
1315
|
-
await emitExit("empty_response_exhausted");
|
|
1316
|
-
throw new AssistantError(
|
|
1317
|
-
"Model returned empty response after tool results",
|
|
1318
|
-
ErrorCode.INTERNAL_ERROR,
|
|
1319
|
-
);
|
|
1320
|
-
}
|
|
1211
|
+
if (toolUseBlocks.length === 0) {
|
|
1212
|
+
// The model stopped requesting tools — the run's stop boundary. The
|
|
1213
|
+
// `stop` hook decides whether to let the turn end or re-query with a
|
|
1214
|
+
// follow-up turn. It receives the full history and, when it asks to
|
|
1215
|
+
// continue, appends the follow-up turn itself.
|
|
1216
|
+
const stopCtx: StopContext = {
|
|
1217
|
+
conversationId: turnCtx.conversationId,
|
|
1218
|
+
messages: [...history],
|
|
1219
|
+
responseContent: response.content,
|
|
1220
|
+
stopReason: response.stopReason,
|
|
1221
|
+
decision: "stop",
|
|
1222
|
+
logger: rlog,
|
|
1223
|
+
};
|
|
1224
|
+
const finalStopCtx = await runHook(HOOKS.STOP, stopCtx);
|
|
1225
|
+
|
|
1226
|
+
if (finalStopCtx.decision === "continue") {
|
|
1227
|
+
// The loop owns the retry budget: a hook always asks to continue
|
|
1228
|
+
// when a nudge is warranted, and the loop stops anyway once the
|
|
1229
|
+
// budget is spent. This bounds the hook-driven re-query loop.
|
|
1230
|
+
if (stopContinueRetries < MAX_STOP_CONTINUE_RETRIES) {
|
|
1231
|
+
stopContinueRetries++;
|
|
1232
|
+
rlog.warn(
|
|
1233
|
+
{ turn: toolUseTurns, retry: stopContinueRetries },
|
|
1234
|
+
"Model returned empty response after tool results — retrying",
|
|
1235
|
+
);
|
|
1236
|
+
history = finalStopCtx.messages;
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1321
1239
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1240
|
+
// Budget spent — accept the empty turn. Emit a dedicated log line
|
|
1241
|
+
// for the post-tool empty case so ops dashboards that grep on it
|
|
1242
|
+
// keep working.
|
|
1243
|
+
if (
|
|
1244
|
+
!hasVisibleText &&
|
|
1245
|
+
toolUseTurns > 0 &&
|
|
1246
|
+
!priorAssistantHadVisibleText
|
|
1247
|
+
) {
|
|
1248
|
+
rlog.error(
|
|
1249
|
+
{ turn: toolUseTurns, retries: stopContinueRetries },
|
|
1250
|
+
"Model returned empty response after tool results — retries exhausted",
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1335
1254
|
}
|
|
1336
1255
|
|
|
1337
1256
|
history.push(assistantMessage);
|
|
1257
|
+
appendedNewMessages = true;
|
|
1338
1258
|
|
|
1339
1259
|
await onEvent({ type: "message_complete", message: assistantMessage });
|
|
1340
1260
|
|
|
@@ -1364,6 +1284,15 @@ export class AgentLoop {
|
|
|
1364
1284
|
}),
|
|
1365
1285
|
);
|
|
1366
1286
|
history.push({ role: "user", content: cancelledBlocks });
|
|
1287
|
+
for (const toolUse of toolUseBlocks) {
|
|
1288
|
+
await onEvent({
|
|
1289
|
+
type: "tool_result",
|
|
1290
|
+
toolUseId: toolUse.id,
|
|
1291
|
+
content: "Cancelled by user",
|
|
1292
|
+
isError: true,
|
|
1293
|
+
cancelled: true,
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1367
1296
|
await emitExit("aborted_post_response");
|
|
1368
1297
|
break;
|
|
1369
1298
|
}
|
|
@@ -1393,14 +1322,6 @@ export class AgentLoop {
|
|
|
1393
1322
|
});
|
|
1394
1323
|
},
|
|
1395
1324
|
toolUse.id,
|
|
1396
|
-
// Forward the loop's resolved `TurnContext` through the
|
|
1397
|
-
// executor callback so `ToolExecutor.execute` can thread the
|
|
1398
|
-
// real orchestrator context into the `toolExecute` pipeline.
|
|
1399
|
-
// Standalone tests that don't wire a `turnContext` into
|
|
1400
|
-
// `AgentLoop.run()` pass `undefined` here and the executor
|
|
1401
|
-
// falls back to the synthesized placeholder — preserving the
|
|
1402
|
-
// existing unit-test behavior.
|
|
1403
|
-
turnCtx,
|
|
1404
1325
|
);
|
|
1405
1326
|
|
|
1406
1327
|
return { toolUse, result };
|
|
@@ -1464,60 +1385,39 @@ export class AgentLoop {
|
|
|
1464
1385
|
}),
|
|
1465
1386
|
);
|
|
1466
1387
|
|
|
1467
|
-
//
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
-
//
|
|
1471
|
-
//
|
|
1388
|
+
// Run the `post-tool-use` hook once per tool result, after the tool
|
|
1389
|
+
// returns and before the result joins the provider-bound history.
|
|
1390
|
+
// The default tool-result-truncate plugin tail-drops oversized output
|
|
1391
|
+
// to fit the context window; user hooks can swap in a smarter strategy
|
|
1392
|
+
// (e.g. a summariser) or observe results for side effects.
|
|
1472
1393
|
const contextWindowTokens =
|
|
1473
1394
|
resolveContextWindow?.().maxInputTokens ??
|
|
1474
1395
|
this.config.maxInputTokens ??
|
|
1475
1396
|
180_000;
|
|
1476
|
-
const maxChars = calculateMaxToolResultChars(contextWindowTokens);
|
|
1477
|
-
const truncateMiddlewares = getMiddlewaresFor("toolResultTruncate");
|
|
1478
1397
|
|
|
1479
|
-
|
|
1480
|
-
const
|
|
1398
|
+
const resultBlocks: ContentBlock[] = [];
|
|
1399
|
+
const additionalContextBlocks: ContentBlock[] = [];
|
|
1481
1400
|
for (const block of rawResultBlocks) {
|
|
1482
1401
|
if (block.type !== "tool_result") {
|
|
1483
|
-
|
|
1402
|
+
resultBlocks.push(block);
|
|
1484
1403
|
continue;
|
|
1485
1404
|
}
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
truncateMiddlewares,
|
|
1500
|
-
async (args) => defaultToolResultTruncateTerminal(args),
|
|
1501
|
-
{ content: toolBlock.content, maxChars },
|
|
1502
|
-
turnCtx,
|
|
1503
|
-
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
1504
|
-
);
|
|
1505
|
-
if (pipelineResult.truncated) {
|
|
1506
|
-
truncatedCount++;
|
|
1507
|
-
truncatedBlocks.push({
|
|
1508
|
-
...toolBlock,
|
|
1509
|
-
content: pipelineResult.content,
|
|
1405
|
+
const postToolUseCtx: PostToolUseContext = {
|
|
1406
|
+
conversationId: turnCtx.conversationId,
|
|
1407
|
+
toolResponse: block as ToolResultContent,
|
|
1408
|
+
messages: history,
|
|
1409
|
+
maxInputTokens: contextWindowTokens,
|
|
1410
|
+
logger: rlog,
|
|
1411
|
+
};
|
|
1412
|
+
const finalCtx = await runHook(HOOKS.POST_TOOL_USE, postToolUseCtx);
|
|
1413
|
+
resultBlocks.push(finalCtx.toolResponse);
|
|
1414
|
+
if (finalCtx.additionalContext !== undefined) {
|
|
1415
|
+
additionalContextBlocks.push({
|
|
1416
|
+
type: "text",
|
|
1417
|
+
text: finalCtx.additionalContext,
|
|
1510
1418
|
});
|
|
1511
|
-
} else {
|
|
1512
|
-
truncatedBlocks.push(block);
|
|
1513
1419
|
}
|
|
1514
1420
|
}
|
|
1515
|
-
const resultBlocks = truncatedBlocks;
|
|
1516
|
-
if (truncatedCount > 0) {
|
|
1517
|
-
log.warn(
|
|
1518
|
-
`Truncated ${truncatedCount} oversized tool result(s) to prevent context overflow`,
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
1421
|
|
|
1522
1422
|
// Emit tool_result events AFTER truncation so downstream consumers
|
|
1523
1423
|
// (e.g. session persistence) receive the truncated content.
|
|
@@ -1569,54 +1469,15 @@ export class AgentLoop {
|
|
|
1569
1469
|
|
|
1570
1470
|
toolUseTurns++;
|
|
1571
1471
|
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
//
|
|
1578
|
-
|
|
1579
|
-
if (hasToolError) {
|
|
1580
|
-
consecutiveErrorTurns++;
|
|
1581
|
-
} else {
|
|
1582
|
-
consecutiveErrorTurns = 0;
|
|
1583
|
-
}
|
|
1584
|
-
const toolErrorArgs: ToolErrorArgs = {
|
|
1585
|
-
hasToolError,
|
|
1586
|
-
consecutiveErrorTurns,
|
|
1587
|
-
maxConsecutiveErrorNudges: MAX_CONSECUTIVE_ERROR_NUDGES,
|
|
1588
|
-
};
|
|
1589
|
-
const toolErrorCtx: TurnContext = resolveLoopTurnContext(
|
|
1590
|
-
turnContext,
|
|
1591
|
-
requestId,
|
|
1592
|
-
toolUseTurns - 1,
|
|
1593
|
-
);
|
|
1594
|
-
const toolErrorDecision = await runPipeline<
|
|
1595
|
-
ToolErrorArgs,
|
|
1596
|
-
ToolErrorDecision
|
|
1597
|
-
>(
|
|
1598
|
-
"toolError",
|
|
1599
|
-
getMiddlewaresFor("toolError"),
|
|
1600
|
-
// Terminal: the canonical nudge decision. The default plugin's
|
|
1601
|
-
// middleware is a passthrough (so later-registered user plugins
|
|
1602
|
-
// aren't shadowed), so this terminal is what actually produces
|
|
1603
|
-
// the decision when no user plugin overrides it. Wiring the
|
|
1604
|
-
// decision here also ensures the nudge fires for direct
|
|
1605
|
-
// AgentLoop callers (tests, benchmarks) that skip
|
|
1606
|
-
// `bootstrapPlugins()` and therefore never register the default.
|
|
1607
|
-
async (args) => defaultToolErrorTerminal(args),
|
|
1608
|
-
toolErrorArgs,
|
|
1609
|
-
toolErrorCtx,
|
|
1610
|
-
DEFAULT_TIMEOUTS.toolError,
|
|
1611
|
-
);
|
|
1612
|
-
if (toolErrorDecision.action === "nudge") {
|
|
1613
|
-
resultBlocks.push({
|
|
1614
|
-
type: "text",
|
|
1615
|
-
text: toolErrorDecision.nudgeText,
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1472
|
+
// Append any guidance a post-tool-use hook surfaced via
|
|
1473
|
+
// `additionalContext` (e.g. tool-error retry coaching) as separate
|
|
1474
|
+
// blocks. They join the provider-bound history below but were not part
|
|
1475
|
+
// of the tool_result events emitted above, so the model sees the
|
|
1476
|
+
// guidance while the client-facing and persisted tool output stay the
|
|
1477
|
+
// tool's actual result.
|
|
1478
|
+
resultBlocks.push(...additionalContextBlocks);
|
|
1618
1479
|
|
|
1619
|
-
// Add tool results as a user message and continue the loop
|
|
1480
|
+
// Add tool results as a user message and continue the loop.
|
|
1620
1481
|
history.push({ role: "user", content: resultBlocks });
|
|
1621
1482
|
|
|
1622
1483
|
// Invoke checkpoint callback after tool results are in history.
|
|
@@ -1659,7 +1520,7 @@ export class AgentLoop {
|
|
|
1659
1520
|
);
|
|
1660
1521
|
const midLoopThreshold =
|
|
1661
1522
|
preflightBudget * MID_LOOP_YIELD_THRESHOLD_RATIO;
|
|
1662
|
-
const estimated =
|
|
1523
|
+
const estimated = this.estimateTokens(history);
|
|
1663
1524
|
if (estimated > midLoopThreshold) {
|
|
1664
1525
|
if (compaction) {
|
|
1665
1526
|
rlog.info(
|
|
@@ -1672,9 +1533,13 @@ export class AgentLoop {
|
|
|
1672
1533
|
compaction,
|
|
1673
1534
|
signal,
|
|
1674
1535
|
onEvent,
|
|
1536
|
+
resolveEffectiveOverrideProfile() ?? null,
|
|
1675
1537
|
);
|
|
1676
1538
|
if (compacted) {
|
|
1677
1539
|
history = compacted;
|
|
1540
|
+
// The compacted, re-injected array is the new base; output
|
|
1541
|
+
// produced after this point is what the orchestrator persists.
|
|
1542
|
+
newMessagesStart = history.length;
|
|
1678
1543
|
continue;
|
|
1679
1544
|
}
|
|
1680
1545
|
}
|
|
@@ -1701,6 +1566,15 @@ export class AgentLoop {
|
|
|
1701
1566
|
}),
|
|
1702
1567
|
);
|
|
1703
1568
|
history.push({ role: "user", content: cancelledBlocks });
|
|
1569
|
+
for (const toolUse of toolUseBlocks) {
|
|
1570
|
+
await onEvent({
|
|
1571
|
+
type: "tool_result",
|
|
1572
|
+
toolUseId: toolUse.id,
|
|
1573
|
+
content: "Cancelled by user",
|
|
1574
|
+
isError: true,
|
|
1575
|
+
cancelled: true,
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1704
1578
|
}
|
|
1705
1579
|
await emitExit("aborted_via_error");
|
|
1706
1580
|
break;
|
|
@@ -1714,11 +1588,9 @@ export class AgentLoop {
|
|
|
1714
1588
|
Sentry.captureException(err);
|
|
1715
1589
|
}
|
|
1716
1590
|
onEvent({ type: "error", error: err });
|
|
1717
|
-
// Catch-block fallback.
|
|
1718
|
-
//
|
|
1719
|
-
//
|
|
1720
|
-
// before the throw. Otherwise, this is the genuine
|
|
1721
|
-
// unhandled-error exit.
|
|
1591
|
+
// Catch-block fallback. A break site that stamped a more specific
|
|
1592
|
+
// reason before unwinding here keeps it; the guard makes this a no-op.
|
|
1593
|
+
// Otherwise this is the genuine unhandled-error exit.
|
|
1722
1594
|
await emitExit("error");
|
|
1723
1595
|
break;
|
|
1724
1596
|
}
|
|
@@ -1733,7 +1605,12 @@ export class AgentLoop {
|
|
|
1733
1605
|
"Agent loop exited",
|
|
1734
1606
|
);
|
|
1735
1607
|
|
|
1736
|
-
return {
|
|
1608
|
+
return {
|
|
1609
|
+
history,
|
|
1610
|
+
exitReason,
|
|
1611
|
+
appendedNewMessages,
|
|
1612
|
+
newMessages: history.slice(newMessagesStart),
|
|
1613
|
+
};
|
|
1737
1614
|
}
|
|
1738
1615
|
}
|
|
1739
1616
|
|