vellum 0.0.16 → 0.2.0
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/.dockerignore +27 -0
- package/.env.example +22 -0
- package/Dockerfile +99 -0
- package/Dockerfile.sandbox +5 -0
- package/README.md +150 -3
- package/bun.lock +1768 -0
- package/bunfig.toml +2 -0
- package/docs/skills.md +158 -0
- package/drizzle/0000_dizzy_maggott.sql +301 -0
- package/drizzle/meta/0000_snapshot.json +1999 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/eslint.config.mjs +17 -0
- package/hook-templates/debug-prompt-logger/hook.json +7 -0
- package/hook-templates/debug-prompt-logger/run.sh +68 -0
- package/knip.json +9 -0
- package/package.json +60 -10
- package/scripts/ipc/check-contract-inventory.ts +104 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +163 -0
- package/scripts/ipc/generate-swift.ts +492 -0
- package/scripts/test-filesystem-tools.sh +48 -0
- package/scripts/test.sh +122 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2079 -0
- package/src/__tests__/account-registry.test.ts +244 -0
- package/src/__tests__/active-skill-tools.test.ts +378 -0
- package/src/__tests__/agent-loop-thinking.test.ts +81 -0
- package/src/__tests__/agent-loop.test.ts +1135 -0
- package/src/__tests__/anthropic-provider.test.ts +778 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
- package/src/__tests__/app-bundler.test.ts +313 -0
- package/src/__tests__/app-executors.test.ts +613 -0
- package/src/__tests__/app-open-proxy.test.ts +62 -0
- package/src/__tests__/asset-materialize-tool.test.ts +451 -0
- package/src/__tests__/asset-search-tool.test.ts +476 -0
- package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
- package/src/__tests__/assistant-attachments.test.ts +437 -0
- package/src/__tests__/assistant-event-hub.test.ts +226 -0
- package/src/__tests__/assistant-event.test.ts +123 -0
- package/src/__tests__/attachments-store.test.ts +547 -0
- package/src/__tests__/attachments.test.ts +134 -0
- package/src/__tests__/audit-log-rotation.test.ts +154 -0
- package/src/__tests__/browser-fill-credential.test.ts +309 -0
- package/src/__tests__/browser-manager.test.ts +203 -0
- package/src/__tests__/browser-runtime-check.test.ts +55 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +67 -0
- package/src/__tests__/browser-skill-endstate.test.ts +198 -0
- package/src/__tests__/bundle-scanner.test.ts +313 -0
- package/src/__tests__/checker.test.ts +3856 -0
- package/src/__tests__/clarification-resolver.test.ts +159 -0
- package/src/__tests__/classifier.test.ts +67 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
- package/src/__tests__/cli-discover.test.ts +85 -0
- package/src/__tests__/cli.test.ts +81 -0
- package/src/__tests__/clipboard.test.ts +80 -0
- package/src/__tests__/commit-guarantee.test.ts +335 -0
- package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
- package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
- package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
- package/src/__tests__/config-schema.test.ts +720 -0
- package/src/__tests__/conflict-store.test.ts +329 -0
- package/src/__tests__/connection-policy.test.ts +102 -0
- package/src/__tests__/context-memory-e2e.test.ts +434 -0
- package/src/__tests__/context-token-estimator.test.ts +135 -0
- package/src/__tests__/context-window-manager.test.ts +376 -0
- package/src/__tests__/contradiction-checker.test.ts +216 -0
- package/src/__tests__/conversation-store.test.ts +614 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
- package/src/__tests__/credential-broker-server-use.test.ts +554 -0
- package/src/__tests__/credential-broker.test.ts +167 -0
- package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
- package/src/__tests__/credential-metadata-store.test.ts +779 -0
- package/src/__tests__/credential-policy-validate.test.ts +121 -0
- package/src/__tests__/credential-resolve.test.ts +328 -0
- package/src/__tests__/credential-security-e2e.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +563 -0
- package/src/__tests__/credential-selection.test.ts +354 -0
- package/src/__tests__/credential-vault.test.ts +852 -0
- package/src/__tests__/daemon-assistant-events.test.ts +164 -0
- package/src/__tests__/daemon-server-session-init.test.ts +522 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
- package/src/__tests__/diff.test.ts +121 -0
- package/src/__tests__/domain-normalize.test.ts +112 -0
- package/src/__tests__/domain-policy.test.ts +124 -0
- package/src/__tests__/doordash-client.test.ts +186 -0
- package/src/__tests__/doordash-session.test.ts +143 -0
- package/src/__tests__/dynamic-page-surface.test.ts +91 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
- package/src/__tests__/edit-engine.test.ts +180 -0
- package/src/__tests__/email-cli.test.ts +283 -0
- package/src/__tests__/encrypted-store.test.ts +332 -0
- package/src/__tests__/entity-extractor.test.ts +190 -0
- package/src/__tests__/ephemeral-permissions.test.ts +312 -0
- package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
- package/src/__tests__/event-bus.test.ts +222 -0
- package/src/__tests__/file-edit-tool.test.ts +122 -0
- package/src/__tests__/file-ops-service.test.ts +330 -0
- package/src/__tests__/file-read-tool.test.ts +75 -0
- package/src/__tests__/file-write-tool.test.ts +113 -0
- package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
- package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
- package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
- package/src/__tests__/fuzzy-match-property.test.ts +216 -0
- package/src/__tests__/fuzzy-match.test.ts +138 -0
- package/src/__tests__/gemini-image-service.test.ts +261 -0
- package/src/__tests__/gemini-provider.test.ts +651 -0
- package/src/__tests__/get-weather.test.ts +318 -0
- package/src/__tests__/gmail-integration.test.ts +73 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +351 -0
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +190 -0
- package/src/__tests__/handlers-slack-config.test.ts +199 -0
- package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
- package/src/__tests__/headless-browser-interactions.test.ts +536 -0
- package/src/__tests__/headless-browser-navigate.test.ts +211 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
- package/src/__tests__/history-repair-observability.test.ts +56 -0
- package/src/__tests__/history-repair.test.ts +510 -0
- package/src/__tests__/home-base-bootstrap.test.ts +77 -0
- package/src/__tests__/hooks-blocking.test.ts +128 -0
- package/src/__tests__/hooks-cli.test.ts +144 -0
- package/src/__tests__/hooks-config.test.ts +93 -0
- package/src/__tests__/hooks-discovery.test.ts +199 -0
- package/src/__tests__/hooks-integration.test.ts +189 -0
- package/src/__tests__/hooks-manager.test.ts +187 -0
- package/src/__tests__/hooks-runner.test.ts +178 -0
- package/src/__tests__/hooks-settings.test.ts +154 -0
- package/src/__tests__/hooks-templates.test.ts +137 -0
- package/src/__tests__/hooks-ts-runner.test.ts +125 -0
- package/src/__tests__/hooks-watch.test.ts +100 -0
- package/src/__tests__/host-file-edit-tool.test.ts +104 -0
- package/src/__tests__/host-file-read-tool.test.ts +61 -0
- package/src/__tests__/host-file-write-tool.test.ts +77 -0
- package/src/__tests__/host-shell-tool.test.ts +311 -0
- package/src/__tests__/intent-routing.test.ts +255 -0
- package/src/__tests__/ipc-blob-store.test.ts +315 -0
- package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
- package/src/__tests__/ipc-contract.test.ts +74 -0
- package/src/__tests__/ipc-protocol.test.ts +113 -0
- package/src/__tests__/ipc-snapshot.test.ts +1560 -0
- package/src/__tests__/ipc-validate.test.ts +357 -0
- package/src/__tests__/key-migration.test.ts +183 -0
- package/src/__tests__/keychain.test.ts +258 -0
- package/src/__tests__/llm-usage-store.test.ts +226 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
- package/src/__tests__/managed-store.test.ts +608 -0
- package/src/__tests__/media-generate-image.test.ts +238 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
- package/src/__tests__/media-visibility-policy.test.ts +141 -0
- package/src/__tests__/memory-context-benchmark.test.ts +235 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
- package/src/__tests__/memory-query-builder.test.ts +59 -0
- package/src/__tests__/memory-recall-quality.test.ts +846 -0
- package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
- package/src/__tests__/memory-regressions.test.ts +4238 -0
- package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
- package/src/__tests__/migration-cli-flows.test.ts +169 -0
- package/src/__tests__/migration-ordering.test.ts +249 -0
- package/src/__tests__/mock-signup-server.test.ts +528 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +166 -0
- package/src/__tests__/onboarding-template-contract.test.ts +58 -0
- package/src/__tests__/openai-provider.test.ts +753 -0
- package/src/__tests__/parser.test.ts +472 -0
- package/src/__tests__/path-classifier.test.ts +73 -0
- package/src/__tests__/path-policy.test.ts +435 -0
- package/src/__tests__/platform-move-helper.test.ts +99 -0
- package/src/__tests__/platform-socket-path.test.ts +52 -0
- package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
- package/src/__tests__/platform.test.ts +131 -0
- package/src/__tests__/prebuilt-home-base-seed.test.ts +71 -0
- package/src/__tests__/pricing.test.ts +256 -0
- package/src/__tests__/profile-compiler.test.ts +373 -0
- package/src/__tests__/provider-registry-ollama.test.ts +16 -0
- package/src/__tests__/proxy-approval-callback.test.ts +601 -0
- package/src/__tests__/ratelimit.test.ts +297 -0
- package/src/__tests__/registry.test.ts +487 -0
- package/src/__tests__/reminder-store.test.ts +220 -0
- package/src/__tests__/reminder.test.ts +263 -0
- package/src/__tests__/request-file-tool.test.ts +158 -0
- package/src/__tests__/run-orchestrator.test.ts +200 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +190 -0
- package/src/__tests__/runtime-runs-http.test.ts +451 -0
- package/src/__tests__/runtime-runs.test.ts +273 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
- package/src/__tests__/sandbox-host-parity.test.ts +950 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
- package/src/__tests__/script-proxy-certs.test.ts +90 -0
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
- package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
- package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
- package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
- package/src/__tests__/script-proxy-policy.test.ts +310 -0
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
- package/src/__tests__/script-proxy-router.test.ts +180 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
- package/src/__tests__/secret-allowlist.test.ts +229 -0
- package/src/__tests__/secret-ingress-handler.test.ts +99 -0
- package/src/__tests__/secret-onetime-send.test.ts +130 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
- package/src/__tests__/secret-response-routing.test.ts +93 -0
- package/src/__tests__/secret-scanner-executor.test.ts +348 -0
- package/src/__tests__/secret-scanner.test.ts +857 -0
- package/src/__tests__/secure-keys.test.ts +323 -0
- package/src/__tests__/server-history-render.test.ts +430 -0
- package/src/__tests__/session-abort-tool-results.test.ts +240 -0
- package/src/__tests__/session-conflict-gate.test.ts +697 -0
- package/src/__tests__/session-error.test.ts +341 -0
- package/src/__tests__/session-evictor.test.ts +188 -0
- package/src/__tests__/session-load-history-repair.test.ts +222 -0
- package/src/__tests__/session-pre-run-repair.test.ts +213 -0
- package/src/__tests__/session-profile-injection.test.ts +444 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
- package/src/__tests__/session-queue.test.ts +1462 -0
- package/src/__tests__/session-runtime-assembly.test.ts +315 -0
- package/src/__tests__/session-runtime-workspace.test.ts +183 -0
- package/src/__tests__/session-skill-tools.test.ts +2431 -0
- package/src/__tests__/session-slash-known.test.ts +368 -0
- package/src/__tests__/session-slash-queue.test.ts +288 -0
- package/src/__tests__/session-slash-unknown.test.ts +271 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
- package/src/__tests__/session-undo.test.ts +75 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
- package/src/__tests__/session-workspace-injection.test.ts +327 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
- package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
- package/src/__tests__/shell-credential-ref.test.ts +187 -0
- package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
- package/src/__tests__/shell-parser-property.test.ts +433 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
- package/src/__tests__/signup-e2e.test.ts +352 -0
- package/src/__tests__/size-guard.test.ts +117 -0
- package/src/__tests__/skill-include-graph.test.ts +303 -0
- package/src/__tests__/skill-load-tool.test.ts +409 -0
- package/src/__tests__/skill-script-runner-host.test.ts +489 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
- package/src/__tests__/skill-tool-factory.test.ts +252 -0
- package/src/__tests__/skill-tool-manifest.test.ts +658 -0
- package/src/__tests__/skill-version-hash.test.ts +182 -0
- package/src/__tests__/skills.test.ts +597 -0
- package/src/__tests__/slash-commands-catalog.test.ts +86 -0
- package/src/__tests__/slash-commands-parser.test.ts +119 -0
- package/src/__tests__/slash-commands-resolver.test.ts +193 -0
- package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
- package/src/__tests__/starter-bundle.test.ts +136 -0
- package/src/__tests__/starter-task-flow.test.ts +143 -0
- package/src/__tests__/subagent-manager-notify.test.ts +372 -0
- package/src/__tests__/subagent-tools.test.ts +118 -0
- package/src/__tests__/subagent-types.test.ts +78 -0
- package/src/__tests__/swarm-orchestrator.test.ts +428 -0
- package/src/__tests__/swarm-plan-validator.test.ts +330 -0
- package/src/__tests__/swarm-recursion.test.ts +165 -0
- package/src/__tests__/swarm-router-planner.test.ts +208 -0
- package/src/__tests__/swarm-session-integration.test.ts +274 -0
- package/src/__tests__/swarm-tool.test.ts +145 -0
- package/src/__tests__/swarm-worker-backend.test.ts +129 -0
- package/src/__tests__/swarm-worker-runner.test.ts +272 -0
- package/src/__tests__/system-prompt.test.ts +461 -0
- package/src/__tests__/task-compiler.test.ts +283 -0
- package/src/__tests__/task-runner.test.ts +215 -0
- package/src/__tests__/task-scheduler.test.ts +216 -0
- package/src/__tests__/task-tools.test.ts +602 -0
- package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
- package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
- package/src/__tests__/terminal-sandbox.test.ts +202 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
- package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
- package/src/__tests__/tool-audit-listener.test.ts +112 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +251 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
- package/src/__tests__/tool-executor-redaction.test.ts +289 -0
- package/src/__tests__/tool-executor.test.ts +1971 -0
- package/src/__tests__/tool-metrics-listener.test.ts +225 -0
- package/src/__tests__/tool-notification-listener.test.ts +49 -0
- package/src/__tests__/tool-policy.test.ts +54 -0
- package/src/__tests__/tool-profiling-listener.test.ts +268 -0
- package/src/__tests__/tool-result-truncation.test.ts +217 -0
- package/src/__tests__/tool-trace-listener.test.ts +226 -0
- package/src/__tests__/top-level-renderer.test.ts +121 -0
- package/src/__tests__/top-level-scanner.test.ts +141 -0
- package/src/__tests__/trace-emitter.test.ts +173 -0
- package/src/__tests__/trust-store.test.ts +2030 -0
- package/src/__tests__/turn-commit.test.ts +219 -0
- package/src/__tests__/url-safety.test.ts +418 -0
- package/src/__tests__/weather-skill-regression.test.ts +225 -0
- package/src/__tests__/web-fetch.test.ts +869 -0
- package/src/__tests__/web-search.test.ts +584 -0
- package/src/__tests__/workspace-git-service.test.ts +750 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +347 -0
- package/src/__tests__/workspace-lifecycle.test.ts +292 -0
- package/src/agent/attachments.ts +35 -0
- package/src/agent/loop.ts +500 -0
- package/src/agent/message-types.ts +17 -0
- package/src/autonomy/autonomy-resolver.ts +60 -0
- package/src/autonomy/autonomy-store.ts +122 -0
- package/src/autonomy/disposition-mapper.ts +31 -0
- package/src/autonomy/index.ts +11 -0
- package/src/autonomy/types.ts +39 -0
- package/src/bundler/app-bundler.ts +274 -0
- package/src/bundler/bundle-scanner.ts +535 -0
- package/src/bundler/bundle-signer.ts +124 -0
- package/src/bundler/manifest.ts +21 -0
- package/src/bundler/signature-verifier.ts +184 -0
- package/src/cli/autonomy.ts +188 -0
- package/src/cli/contacts.ts +149 -0
- package/src/cli/doordash.ts +824 -0
- package/src/cli/email-guardrails.ts +200 -0
- package/src/cli/email.ts +405 -0
- package/src/cli/main-screen.tsx +155 -0
- package/src/cli.ts +935 -0
- package/src/config/bundled-skills/.gitkeep +0 -0
- package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
- package/src/config/bundled-skills/agentmail/icon.svg +21 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1348 -0
- package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
- package/src/config/bundled-skills/app-builder/icon.svg +9 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
- package/src/config/bundled-skills/browser/SKILL.md +28 -0
- package/src/config/bundled-skills/browser/TOOLS.json +234 -0
- package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
- package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
- package/src/config/bundled-skills/google-calendar/types.ts +97 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +137 -0
- package/src/config/bundled-skills/messaging/SKILL.md +126 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +124 -0
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +29 -0
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +27 -0
- package/src/config/bundled-skills/messaging/tools/shared.ts +71 -0
- package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
- package/src/config/bundled-skills/self-upgrade/SKILL.md +74 -0
- package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
- package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
- package/src/config/bundled-skills/weather/SKILL.md +37 -0
- package/src/config/bundled-skills/weather/TOOLS.json +32 -0
- package/src/config/bundled-skills/weather/icon.svg +24 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
- package/src/config/computer-use-prompt.ts +97 -0
- package/src/config/defaults.ts +186 -0
- package/src/config/loader.ts +336 -0
- package/src/config/schema.ts +1004 -0
- package/src/config/skill-state.ts +95 -0
- package/src/config/skills.ts +972 -0
- package/src/config/system-prompt.ts +927 -0
- package/src/config/templates/BOOTSTRAP.md +70 -0
- package/src/config/templates/IDENTITY.md +18 -0
- package/src/config/templates/LOOKS.md +25 -0
- package/src/config/templates/SOUL.md +37 -0
- package/src/config/templates/USER.md +19 -0
- package/src/config/types.ts +32 -0
- package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
- package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +194 -0
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +147 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +105 -0
- package/src/contacts/contact-store.ts +410 -0
- package/src/contacts/index.ts +11 -0
- package/src/contacts/types.ts +28 -0
- package/src/context/token-estimator.ts +108 -0
- package/src/context/tool-result-truncation.ts +128 -0
- package/src/context/window-manager.ts +531 -0
- package/src/daemon/assistant-attachments.ts +679 -0
- package/src/daemon/classifier.ts +108 -0
- package/src/daemon/computer-use-session.ts +900 -0
- package/src/daemon/connection-policy.ts +41 -0
- package/src/daemon/handlers/apps.ts +446 -0
- package/src/daemon/handlers/computer-use.ts +181 -0
- package/src/daemon/handlers/config.ts +434 -0
- package/src/daemon/handlers/diagnostics.ts +334 -0
- package/src/daemon/handlers/documents.ts +184 -0
- package/src/daemon/handlers/home-base.ts +73 -0
- package/src/daemon/handlers/index.ts +355 -0
- package/src/daemon/handlers/misc.ts +323 -0
- package/src/daemon/handlers/open-bundle-handler.ts +80 -0
- package/src/daemon/handlers/publish.ts +182 -0
- package/src/daemon/handlers/sessions.ts +486 -0
- package/src/daemon/handlers/shared.ts +533 -0
- package/src/daemon/handlers/skills.ts +487 -0
- package/src/daemon/handlers/subagents.ts +122 -0
- package/src/daemon/handlers/work-items.ts +176 -0
- package/src/daemon/handlers.ts +17 -0
- package/src/daemon/history-repair.ts +214 -0
- package/src/daemon/ipc-blob-store.ts +231 -0
- package/src/daemon/ipc-contract-inventory.json +407 -0
- package/src/daemon/ipc-contract-inventory.ts +126 -0
- package/src/daemon/ipc-contract.ts +2102 -0
- package/src/daemon/ipc-protocol.ts +70 -0
- package/src/daemon/ipc-validate.ts +171 -0
- package/src/daemon/lifecycle.ts +503 -0
- package/src/daemon/main.ts +15 -0
- package/src/daemon/media-visibility-policy.ts +57 -0
- package/src/daemon/ride-shotgun-handler.ts +244 -0
- package/src/daemon/server.ts +1085 -0
- package/src/daemon/session-attachments.ts +173 -0
- package/src/daemon/session-conflict-gate.ts +219 -0
- package/src/daemon/session-dynamic-profile.ts +63 -0
- package/src/daemon/session-error.ts +269 -0
- package/src/daemon/session-evictor.ts +196 -0
- package/src/daemon/session-history.ts +437 -0
- package/src/daemon/session-memory.ts +212 -0
- package/src/daemon/session-process.ts +264 -0
- package/src/daemon/session-queue-manager.ts +81 -0
- package/src/daemon/session-runtime-assembly.ts +395 -0
- package/src/daemon/session-skill-tools.ts +237 -0
- package/src/daemon/session-slash.ts +302 -0
- package/src/daemon/session-surfaces.ts +624 -0
- package/src/daemon/session-tool-setup.ts +286 -0
- package/src/daemon/session-usage.ts +74 -0
- package/src/daemon/session-workspace.ts +19 -0
- package/src/daemon/session.ts +1651 -0
- package/src/daemon/trace-emitter.ts +82 -0
- package/src/daemon/watch-handler.ts +274 -0
- package/src/doordash/client.ts +905 -0
- package/src/doordash/queries.ts +1312 -0
- package/src/doordash/query-extractor.ts +93 -0
- package/src/doordash/session.ts +82 -0
- package/src/email/provider.ts +117 -0
- package/src/email/providers/agentmail.ts +317 -0
- package/src/email/providers/index.ts +58 -0
- package/src/email/service.ts +303 -0
- package/src/email/types.ts +126 -0
- package/src/events/bus.ts +157 -0
- package/src/events/domain-events.ts +83 -0
- package/src/events/index.ts +18 -0
- package/src/events/tool-audit-listener.ts +80 -0
- package/src/events/tool-domain-event-publisher.ts +111 -0
- package/src/events/tool-metrics-listener.ts +159 -0
- package/src/events/tool-notification-listener.ts +17 -0
- package/src/events/tool-profiling-listener.ts +158 -0
- package/src/events/tool-trace-listener.ts +75 -0
- package/src/export/formatter.ts +96 -0
- package/src/followups/followup-store.ts +166 -0
- package/src/followups/index.ts +10 -0
- package/src/followups/types.ts +23 -0
- package/src/gallery/default-gallery.ts +795 -0
- package/src/gallery/gallery-manifest.ts +24 -0
- package/src/home-base/app-link-store.ts +82 -0
- package/src/home-base/bootstrap.ts +66 -0
- package/src/home-base/prebuilt/index.html +662 -0
- package/src/home-base/prebuilt/seed-metadata.json +21 -0
- package/src/home-base/prebuilt/seed.ts +101 -0
- package/src/home-base/prebuilt-home-base-updater.ts +30 -0
- package/src/hooks/cli.ts +163 -0
- package/src/hooks/config.ts +88 -0
- package/src/hooks/discovery.ts +110 -0
- package/src/hooks/manager.ts +128 -0
- package/src/hooks/runner.ts +123 -0
- package/src/hooks/templates.ts +52 -0
- package/src/hooks/types.ts +72 -0
- package/src/index.ts +1194 -0
- package/src/instrument.ts +60 -0
- package/src/logfire.ts +99 -0
- package/src/media/gemini-image-service.ts +136 -0
- package/src/memory/account-store.ts +108 -0
- package/src/memory/admin.ts +211 -0
- package/src/memory/app-store.ts +556 -0
- package/src/memory/attachments-store.ts +453 -0
- package/src/memory/channel-delivery-store.ts +368 -0
- package/src/memory/checkpoints.ts +52 -0
- package/src/memory/clarification-resolver.ts +297 -0
- package/src/memory/conflict-store.ts +342 -0
- package/src/memory/contradiction-checker.ts +329 -0
- package/src/memory/conversation-key-store.ts +127 -0
- package/src/memory/conversation-store.ts +469 -0
- package/src/memory/db.ts +1105 -0
- package/src/memory/embedding-backend.ts +229 -0
- package/src/memory/embedding-gemini.ts +52 -0
- package/src/memory/embedding-local.ts +75 -0
- package/src/memory/embedding-ollama.ts +55 -0
- package/src/memory/embedding-openai.ts +25 -0
- package/src/memory/entity-extractor.ts +471 -0
- package/src/memory/fingerprint.ts +20 -0
- package/src/memory/indexer.ts +156 -0
- package/src/memory/items-extractor.ts +460 -0
- package/src/memory/job-handlers/backfill.ts +139 -0
- package/src/memory/job-handlers/cleanup.ts +58 -0
- package/src/memory/job-handlers/conflict.ts +99 -0
- package/src/memory/job-handlers/embedding.ts +61 -0
- package/src/memory/job-handlers/extraction.ts +123 -0
- package/src/memory/job-handlers/index-maintenance.ts +54 -0
- package/src/memory/job-handlers/summarization.ts +286 -0
- package/src/memory/job-utils.ts +170 -0
- package/src/memory/jobs-store.ts +400 -0
- package/src/memory/jobs-worker.ts +274 -0
- package/src/memory/llm-request-log-store.ts +45 -0
- package/src/memory/llm-usage-store.ts +62 -0
- package/src/memory/message-content.ts +54 -0
- package/src/memory/profile-compiler.ts +160 -0
- package/src/memory/published-pages-store.ts +137 -0
- package/src/memory/qdrant-client.ts +366 -0
- package/src/memory/qdrant-manager.ts +242 -0
- package/src/memory/query-builder.ts +45 -0
- package/src/memory/retrieval-budget.ts +30 -0
- package/src/memory/retriever.ts +653 -0
- package/src/memory/runs-store.ts +211 -0
- package/src/memory/schema.ts +529 -0
- package/src/memory/search/entity.ts +298 -0
- package/src/memory/search/formatting.ts +207 -0
- package/src/memory/search/lexical.ts +227 -0
- package/src/memory/search/ranking.ts +401 -0
- package/src/memory/search/semantic.ts +121 -0
- package/src/memory/search/types.ts +137 -0
- package/src/memory/segmenter.ts +68 -0
- package/src/memory/shared-app-links-store.ts +138 -0
- package/src/memory/tool-usage-store.ts +62 -0
- package/src/messaging/activity-analyzer.ts +76 -0
- package/src/messaging/draft-store.ts +88 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/provider-types.ts +80 -0
- package/src/messaging/provider.ts +43 -0
- package/src/messaging/providers/gmail/adapter.ts +193 -0
- package/src/messaging/providers/gmail/client.ts +204 -0
- package/src/messaging/providers/gmail/types.ts +90 -0
- package/src/messaging/providers/slack/adapter.ts +202 -0
- package/src/messaging/providers/slack/client.ts +198 -0
- package/src/messaging/providers/slack/types.ts +119 -0
- package/src/messaging/registry.ts +34 -0
- package/src/messaging/style-analyzer.ts +158 -0
- package/src/messaging/thread-summarizer.ts +310 -0
- package/src/messaging/triage-engine.ts +321 -0
- package/src/messaging/types.ts +55 -0
- package/src/permissions/checker.ts +636 -0
- package/src/permissions/defaults.ts +243 -0
- package/src/permissions/prompter.ts +102 -0
- package/src/permissions/secret-prompter.ts +114 -0
- package/src/permissions/trust-store.ts +584 -0
- package/src/permissions/types.ts +62 -0
- package/src/playbooks/index.ts +2 -0
- package/src/playbooks/playbook-compiler.ts +90 -0
- package/src/playbooks/types.ts +55 -0
- package/src/providers/anthropic/client.ts +751 -0
- package/src/providers/failover.ts +129 -0
- package/src/providers/fireworks/client.ts +20 -0
- package/src/providers/gemini/client.ts +285 -0
- package/src/providers/ollama/client.ts +30 -0
- package/src/providers/openai/client.ts +337 -0
- package/src/providers/ratelimit.ts +93 -0
- package/src/providers/registry.ts +138 -0
- package/src/providers/retry.ts +106 -0
- package/src/providers/stream-timeout.ts +38 -0
- package/src/providers/types.ts +109 -0
- package/src/runtime/assistant-event-hub.ts +120 -0
- package/src/runtime/assistant-event.ts +82 -0
- package/src/runtime/http-server.ts +478 -0
- package/src/runtime/http-types.ts +68 -0
- package/src/runtime/routes/app-routes.ts +174 -0
- package/src/runtime/routes/attachment-routes.ts +134 -0
- package/src/runtime/routes/channel-routes.ts +342 -0
- package/src/runtime/routes/conversation-routes.ts +349 -0
- package/src/runtime/routes/run-routes.ts +223 -0
- package/src/runtime/routes/secret-routes.ts +76 -0
- package/src/runtime/run-orchestrator.ts +206 -0
- package/src/schedule/schedule-store.ts +452 -0
- package/src/schedule/scheduler.ts +168 -0
- package/src/security/encrypted-store.ts +238 -0
- package/src/security/keychain.ts +252 -0
- package/src/security/oauth2.ts +241 -0
- package/src/security/redaction.ts +89 -0
- package/src/security/secret-allowlist.ts +118 -0
- package/src/security/secret-ingress.ts +57 -0
- package/src/security/secret-scanner.ts +543 -0
- package/src/security/secure-keys.ts +180 -0
- package/src/security/token-manager.ts +141 -0
- package/src/services/published-app-updater.ts +69 -0
- package/src/services/vercel-deploy.ts +73 -0
- package/src/skills/active-skill-tools.ts +81 -0
- package/src/skills/clawhub.ts +414 -0
- package/src/skills/include-graph.ts +146 -0
- package/src/skills/managed-store.ts +233 -0
- package/src/skills/path-classifier.ts +128 -0
- package/src/skills/slash-commands.ts +174 -0
- package/src/skills/tool-manifest.ts +165 -0
- package/src/skills/version-hash.ts +110 -0
- package/src/slack/slack-webhook.ts +61 -0
- package/src/subagent/index.ts +19 -0
- package/src/subagent/manager.ts +477 -0
- package/src/subagent/types.ts +69 -0
- package/src/swarm/backend-claude-code.ts +90 -0
- package/src/swarm/index.ts +44 -0
- package/src/swarm/limits.ts +37 -0
- package/src/swarm/orchestrator.ts +279 -0
- package/src/swarm/plan-validator.ts +151 -0
- package/src/swarm/router-planner.ts +100 -0
- package/src/swarm/router-prompts.ts +36 -0
- package/src/swarm/synthesizer.ts +62 -0
- package/src/swarm/types.ts +62 -0
- package/src/swarm/worker-backend.ts +121 -0
- package/src/swarm/worker-prompts.ts +78 -0
- package/src/swarm/worker-runner.ts +164 -0
- package/src/tasks/SPEC.md +133 -0
- package/src/tasks/candidate-store.ts +86 -0
- package/src/tasks/ephemeral-permissions.ts +41 -0
- package/src/tasks/task-compiler.ts +198 -0
- package/src/tasks/task-runner.ts +85 -0
- package/src/tasks/task-scheduler.ts +20 -0
- package/src/tasks/task-store.ts +127 -0
- package/src/tools/apps/definitions.ts +59 -0
- package/src/tools/apps/executors.ts +313 -0
- package/src/tools/apps/open-proxy.ts +43 -0
- package/src/tools/apps/registry.ts +16 -0
- package/src/tools/assets/materialize.ts +218 -0
- package/src/tools/assets/search.ts +396 -0
- package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
- package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
- package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
- package/src/tools/browser/auth-cache.ts +149 -0
- package/src/tools/browser/auth-detector.ts +347 -0
- package/src/tools/browser/browser-execution.ts +979 -0
- package/src/tools/browser/browser-handoff.ts +79 -0
- package/src/tools/browser/browser-manager.ts +715 -0
- package/src/tools/browser/browser-screencast.ts +217 -0
- package/src/tools/browser/headless-browser.ts +450 -0
- package/src/tools/browser/jit-auth.ts +51 -0
- package/src/tools/browser/network-recorder.ts +348 -0
- package/src/tools/browser/network-recording-types.ts +49 -0
- package/src/tools/browser/recording-store.ts +49 -0
- package/src/tools/browser/runtime-check.ts +43 -0
- package/src/tools/claude-code/claude-code.ts +232 -0
- package/src/tools/computer-use/definitions.ts +443 -0
- package/src/tools/computer-use/registry.ts +22 -0
- package/src/tools/computer-use/request-computer-control.ts +53 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
- package/src/tools/contacts/contact-merge.ts +87 -0
- package/src/tools/contacts/contact-search.ts +102 -0
- package/src/tools/contacts/contact-upsert.ts +137 -0
- package/src/tools/contacts/index.ts +4 -0
- package/src/tools/credentials/account-registry.ts +127 -0
- package/src/tools/credentials/broker-types.ts +107 -0
- package/src/tools/credentials/broker.ts +372 -0
- package/src/tools/credentials/domain-policy.ts +51 -0
- package/src/tools/credentials/host-pattern-match.ts +60 -0
- package/src/tools/credentials/metadata-store.ts +335 -0
- package/src/tools/credentials/policy-types.ts +52 -0
- package/src/tools/credentials/policy-validate.ts +80 -0
- package/src/tools/credentials/resolve.ts +122 -0
- package/src/tools/credentials/selection.ts +159 -0
- package/src/tools/credentials/tool-policy.ts +25 -0
- package/src/tools/credentials/vault.ts +641 -0
- package/src/tools/document/document-tool.ts +165 -0
- package/src/tools/document/editor-template.ts +237 -0
- package/src/tools/document/index.ts +5 -0
- package/src/tools/executor.ts +825 -0
- package/src/tools/filesystem/edit.ts +127 -0
- package/src/tools/filesystem/fuzzy-match.ts +202 -0
- package/src/tools/filesystem/read.ts +71 -0
- package/src/tools/filesystem/view-image.ts +199 -0
- package/src/tools/filesystem/write.ts +79 -0
- package/src/tools/followups/followup_create.ts +118 -0
- package/src/tools/followups/followup_list.ts +100 -0
- package/src/tools/followups/followup_resolve.ts +91 -0
- package/src/tools/followups/index.ts +3 -0
- package/src/tools/host-filesystem/edit.ts +125 -0
- package/src/tools/host-filesystem/read.ts +80 -0
- package/src/tools/host-filesystem/write.ts +76 -0
- package/src/tools/host-terminal/cli-discover.ts +179 -0
- package/src/tools/host-terminal/host-shell.ts +181 -0
- package/src/tools/memory/definitions.ts +69 -0
- package/src/tools/memory/handlers.ts +245 -0
- package/src/tools/memory/register.ts +66 -0
- package/src/tools/network/domain-normalize.ts +85 -0
- package/src/tools/network/script-proxy/certs.ts +237 -0
- package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
- package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
- package/src/tools/network/script-proxy/index.ts +28 -0
- package/src/tools/network/script-proxy/logging.ts +196 -0
- package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
- package/src/tools/network/script-proxy/policy.ts +152 -0
- package/src/tools/network/script-proxy/router.ts +60 -0
- package/src/tools/network/script-proxy/server.ts +136 -0
- package/src/tools/network/script-proxy/session-manager.ts +534 -0
- package/src/tools/network/script-proxy/types.ts +125 -0
- package/src/tools/network/url-safety.ts +227 -0
- package/src/tools/network/web-fetch.ts +701 -0
- package/src/tools/network/web-search.ts +319 -0
- package/src/tools/playbooks/index.ts +5 -0
- package/src/tools/playbooks/playbook-create.ts +140 -0
- package/src/tools/playbooks/playbook-delete.ts +76 -0
- package/src/tools/playbooks/playbook-list.ts +101 -0
- package/src/tools/playbooks/playbook-update.ts +159 -0
- package/src/tools/registry.ts +297 -0
- package/src/tools/reminder/reminder-store.ts +148 -0
- package/src/tools/reminder/reminder.ts +153 -0
- package/src/tools/schedule/create.ts +86 -0
- package/src/tools/schedule/delete.ts +54 -0
- package/src/tools/schedule/list.ts +88 -0
- package/src/tools/schedule/update.ts +97 -0
- package/src/tools/shared/filesystem/edit-engine.ts +56 -0
- package/src/tools/shared/filesystem/errors.ts +85 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
- package/src/tools/shared/filesystem/format-diff.ts +35 -0
- package/src/tools/shared/filesystem/path-policy.ts +125 -0
- package/src/tools/shared/filesystem/size-guard.ts +41 -0
- package/src/tools/shared/filesystem/types.ts +80 -0
- package/src/tools/shared/shell-output.ts +52 -0
- package/src/tools/skills/delete-managed.ts +60 -0
- package/src/tools/skills/load.ts +139 -0
- package/src/tools/skills/sandbox-runner.ts +279 -0
- package/src/tools/skills/scaffold-managed.ts +150 -0
- package/src/tools/skills/script-contract.ts +6 -0
- package/src/tools/skills/skill-script-runner.ts +86 -0
- package/src/tools/skills/skill-tool-factory.ts +64 -0
- package/src/tools/skills/vellum-catalog.ts +217 -0
- package/src/tools/subagent/abort.ts +62 -0
- package/src/tools/subagent/index.ts +5 -0
- package/src/tools/subagent/message.ts +72 -0
- package/src/tools/subagent/read.ts +98 -0
- package/src/tools/subagent/spawn.ts +85 -0
- package/src/tools/subagent/status.ts +74 -0
- package/src/tools/swarm/delegate.ts +182 -0
- package/src/tools/system/request-permission.ts +98 -0
- package/src/tools/tasks/index.ts +25 -0
- package/src/tools/tasks/task-delete.ts +69 -0
- package/src/tools/tasks/task-list.ts +65 -0
- package/src/tools/tasks/task-run.ts +125 -0
- package/src/tools/tasks/task-save.ts +79 -0
- package/src/tools/tasks/work-item-enqueue.ts +176 -0
- package/src/tools/tasks/work-item-list.ts +86 -0
- package/src/tools/terminal/backends/docker.ts +372 -0
- package/src/tools/terminal/backends/native.ts +188 -0
- package/src/tools/terminal/backends/types.ts +26 -0
- package/src/tools/terminal/evaluate-typescript.ts +275 -0
- package/src/tools/terminal/parser.ts +393 -0
- package/src/tools/terminal/safe-env.ts +37 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
- package/src/tools/terminal/sandbox.ts +44 -0
- package/src/tools/terminal/shell.ts +257 -0
- package/src/tools/tool-manifest.ts +250 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/ui-surface/definitions.ts +232 -0
- package/src/tools/ui-surface/registry.ts +14 -0
- package/src/tools/watch/screen-watch.ts +128 -0
- package/src/tools/watch/watch-state.ts +119 -0
- package/src/tools/watcher/create.ts +110 -0
- package/src/tools/watcher/delete.ts +53 -0
- package/src/tools/watcher/digest.ts +84 -0
- package/src/tools/watcher/list.ts +90 -0
- package/src/tools/watcher/update.ts +102 -0
- package/src/tools/weather/service.ts +551 -0
- package/src/usage/actors.ts +24 -0
- package/src/usage/types.ts +38 -0
- package/src/util/clipboard.ts +33 -0
- package/src/util/content-id.ts +16 -0
- package/src/util/diff.ts +181 -0
- package/src/util/errors.ts +129 -0
- package/src/util/logger.ts +243 -0
- package/src/util/platform.ts +607 -0
- package/src/util/pricing.ts +150 -0
- package/src/util/spinner.ts +51 -0
- package/src/util/time.ts +16 -0
- package/src/util/xml.ts +4 -0
- package/src/version.ts +3 -0
- package/src/watcher/constants.ts +11 -0
- package/src/watcher/engine.ts +199 -0
- package/src/watcher/provider-registry.ts +15 -0
- package/src/watcher/provider-types.ts +48 -0
- package/src/watcher/providers/gmail.ts +198 -0
- package/src/watcher/providers/google-calendar.ts +228 -0
- package/src/watcher/providers/slack.ts +128 -0
- package/src/watcher/watcher-store.ts +418 -0
- package/src/work-items/work-item-store.ts +91 -0
- package/src/workspace/git-service.ts +620 -0
- package/src/workspace/heartbeat-service.ts +288 -0
- package/src/workspace/top-level-renderer.ts +19 -0
- package/src/workspace/top-level-scanner.ts +41 -0
- package/src/workspace/turn-commit.ts +122 -0
- package/tsconfig.json +21 -0
- package/LICENSE +0 -674
- package/dist/cli.js +0 -569
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
import type { Message, ContentBlock, ImageContent } from '../providers/types.js';
|
|
3
|
+
import type { ServerMessage, UsageStats, UserMessageAttachment, SurfaceType, SurfaceData, DynamicPageSurfaceData } from './ipc-protocol.js';
|
|
4
|
+
import { repairHistory, deepRepairHistory } from './history-repair.js';
|
|
5
|
+
import { AgentLoop } from '../agent/loop.js';
|
|
6
|
+
import type { CheckpointDecision } from '../agent/loop.js';
|
|
7
|
+
import type { Provider } from '../providers/types.js';
|
|
8
|
+
import { createUserMessage, createAssistantMessage } from '../agent/message-types.js';
|
|
9
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
10
|
+
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
11
|
+
import { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
12
|
+
import { ToolExecutor } from '../tools/executor.js';
|
|
13
|
+
import type { UserDecision } from '../permissions/types.js';
|
|
14
|
+
import { getConfig } from '../config/loader.js';
|
|
15
|
+
import { getLogger } from '../util/logger.js';
|
|
16
|
+
import { TraceEmitter } from './trace-emitter.js';
|
|
17
|
+
import { classifySessionError, isUserCancellation, isContextTooLarge, buildSessionErrorMessage } from './session-error.js';
|
|
18
|
+
import { EventBus } from '../events/bus.js';
|
|
19
|
+
import type { AssistantDomainEvents } from '../events/domain-events.js';
|
|
20
|
+
import {
|
|
21
|
+
registerWatchStartNotifier,
|
|
22
|
+
unregisterWatchStartNotifier,
|
|
23
|
+
registerWatchCommentaryNotifier,
|
|
24
|
+
unregisterWatchCommentaryNotifier,
|
|
25
|
+
registerWatchCompletionNotifier,
|
|
26
|
+
unregisterWatchCompletionNotifier,
|
|
27
|
+
pruneWatchSessions,
|
|
28
|
+
} from '../tools/watch/watch-state.js';
|
|
29
|
+
import type { WatchSession } from '../tools/watch/watch-state.js';
|
|
30
|
+
import { lastCommentaryBySession, lastSummaryBySession } from './watch-handler.js';
|
|
31
|
+
import { createToolDomainEventPublisher } from '../events/tool-domain-event-publisher.js';
|
|
32
|
+
import { registerToolMetricsLoggingListener } from '../events/tool-metrics-listener.js';
|
|
33
|
+
import { registerToolNotificationListener } from '../events/tool-notification-listener.js';
|
|
34
|
+
import { registerToolTraceListener } from '../events/tool-trace-listener.js';
|
|
35
|
+
import { createToolAuditListener } from '../events/tool-audit-listener.js';
|
|
36
|
+
import { ToolProfiler, registerToolProfilingListener } from '../events/tool-profiling-listener.js';
|
|
37
|
+
import {
|
|
38
|
+
ContextWindowManager,
|
|
39
|
+
createContextSummaryMessage,
|
|
40
|
+
getSummaryFromContextMessage,
|
|
41
|
+
} from '../context/window-manager.js';
|
|
42
|
+
import { getHookManager } from '../hooks/manager.js';
|
|
43
|
+
import {
|
|
44
|
+
stripMemoryRecallMessages,
|
|
45
|
+
} from '../memory/retriever.js';
|
|
46
|
+
import { getApp, listAppFiles } from '../memory/app-store.js';
|
|
47
|
+
import { ConflictGate } from './session-conflict-gate.js';
|
|
48
|
+
import { stripDynamicProfileMessages } from './session-dynamic-profile.js';
|
|
49
|
+
import { MessageQueue } from './session-queue-manager.js';
|
|
50
|
+
import type { QueueDrainReason } from './session-queue-manager.js';
|
|
51
|
+
import {
|
|
52
|
+
applyRuntimeInjections,
|
|
53
|
+
stripActiveSurfaceContext,
|
|
54
|
+
stripWorkspaceTopLevelContext,
|
|
55
|
+
stripChannelCapabilityContext,
|
|
56
|
+
} from './session-runtime-assembly.js';
|
|
57
|
+
import type {
|
|
58
|
+
ActiveSurfaceContext,
|
|
59
|
+
ChannelCapabilities,
|
|
60
|
+
} from './session-runtime-assembly.js';
|
|
61
|
+
import {
|
|
62
|
+
cleanAssistantContent,
|
|
63
|
+
drainDirectiveDisplayBuffer,
|
|
64
|
+
type DirectiveRequest,
|
|
65
|
+
type AssistantAttachmentDraft,
|
|
66
|
+
} from './assistant-attachments.js';
|
|
67
|
+
import {
|
|
68
|
+
handleSurfaceAction as handleSurfaceActionImpl,
|
|
69
|
+
handleSurfaceUndo as handleSurfaceUndoImpl,
|
|
70
|
+
} from './session-surfaces.js';
|
|
71
|
+
import { prepareMemoryContext } from './session-memory.js';
|
|
72
|
+
import {
|
|
73
|
+
approveHostAttachmentRead,
|
|
74
|
+
formatAttachmentWarnings,
|
|
75
|
+
resolveAssistantAttachments,
|
|
76
|
+
} from './session-attachments.js';
|
|
77
|
+
import {
|
|
78
|
+
consolidateAssistantMessages,
|
|
79
|
+
undo as undoImpl,
|
|
80
|
+
regenerate as regenerateImpl,
|
|
81
|
+
type HistorySessionContext,
|
|
82
|
+
} from './session-history.js';
|
|
83
|
+
import { recordUsage } from './session-usage.js';
|
|
84
|
+
import { recordRequestLog } from '../memory/llm-request-log-store.js';
|
|
85
|
+
import { isProviderOrderingError } from './session-slash.js';
|
|
86
|
+
import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from './session-workspace.js';
|
|
87
|
+
import type { UsageActor } from '../usage/actors.js';
|
|
88
|
+
import {
|
|
89
|
+
drainQueue as drainQueueImpl,
|
|
90
|
+
processMessage as processMessageImpl,
|
|
91
|
+
type ProcessSessionContext,
|
|
92
|
+
} from './session-process.js';
|
|
93
|
+
import {
|
|
94
|
+
buildToolDefinitions,
|
|
95
|
+
createToolExecutor,
|
|
96
|
+
type ToolSetupContext,
|
|
97
|
+
} from './session-tool-setup.js';
|
|
98
|
+
import { unregisterSessionSender } from '../tools/browser/browser-screencast.js';
|
|
99
|
+
import { projectSkillTools, resetSkillToolProjection } from './session-skill-tools.js';
|
|
100
|
+
import { commitTurnChanges } from '../workspace/turn-commit.js';
|
|
101
|
+
import { getWorkspaceGitService } from '../workspace/git-service.js';
|
|
102
|
+
|
|
103
|
+
export interface SessionMemoryPolicy {
|
|
104
|
+
scopeId: string;
|
|
105
|
+
includeDefaultFallback: boolean;
|
|
106
|
+
strictSideEffects: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const DEFAULT_MEMORY_POLICY: Readonly<SessionMemoryPolicy> = Object.freeze({
|
|
110
|
+
scopeId: 'default',
|
|
111
|
+
includeDefaultFallback: false,
|
|
112
|
+
strictSideEffects: false,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const log = getLogger('session');
|
|
116
|
+
const RETRY_KEEP_LATEST_MEDIA_BLOCKS = 3;
|
|
117
|
+
const MAX_MEDIA_STUB_TEXT = 2_000;
|
|
118
|
+
|
|
119
|
+
export { MAX_QUEUE_DEPTH, type QueueDrainReason, type QueuePolicy } from './session-queue-manager.js';
|
|
120
|
+
export { findLastUndoableUserMessageIndex } from './session-history.js';
|
|
121
|
+
|
|
122
|
+
export class Session {
|
|
123
|
+
public readonly conversationId: string;
|
|
124
|
+
private provider: Provider;
|
|
125
|
+
/** @internal — exposed for session-history.ts module functions. */
|
|
126
|
+
messages: Message[] = [];
|
|
127
|
+
private agentLoop: AgentLoop;
|
|
128
|
+
/** @internal — exposed for session-history.ts module functions. */
|
|
129
|
+
processing = false;
|
|
130
|
+
private stale = false;
|
|
131
|
+
/** @internal — exposed for session-history.ts module functions. */
|
|
132
|
+
abortController: AbortController | null = null;
|
|
133
|
+
private prompter: PermissionPrompter;
|
|
134
|
+
private secretPrompter: SecretPrompter;
|
|
135
|
+
private executor: ToolExecutor;
|
|
136
|
+
private profiler: ToolProfiler;
|
|
137
|
+
/** @internal — exposed for session-surfaces.ts module functions. */
|
|
138
|
+
sendToClient: (msg: ServerMessage) => void;
|
|
139
|
+
/** Broadcast a message to all connected sockets (not just this session's client). */
|
|
140
|
+
private broadcastToAllClients?: (msg: ServerMessage) => void;
|
|
141
|
+
private eventBus = new EventBus<AssistantDomainEvents>();
|
|
142
|
+
/** @internal — exposed for session-workspace.ts module functions. */
|
|
143
|
+
workingDir: string;
|
|
144
|
+
/** @internal — exposed for session-tool-setup.ts module functions. */
|
|
145
|
+
sandboxOverride?: boolean;
|
|
146
|
+
/** @internal — per-turn allowed tool set, read by the tool executor closure. */
|
|
147
|
+
allowedToolNames?: Set<string>;
|
|
148
|
+
/** @internal — request-scoped skill IDs preactivated via slash resolution. */
|
|
149
|
+
preactivatedSkillIds?: string[];
|
|
150
|
+
/** Core tool names (computed once in constructor), always allowed regardless of skill state. */
|
|
151
|
+
private coreToolNames: Set<string>;
|
|
152
|
+
/** Per-session tracking of previously active skill IDs and their version hashes for projection diffing. */
|
|
153
|
+
private readonly skillProjectionState = new Map<string, string>();
|
|
154
|
+
/** @internal — exposed for session-usage.ts module functions. */
|
|
155
|
+
usageStats: UsageStats = { inputTokens: 0, outputTokens: 0, estimatedCost: 0 };
|
|
156
|
+
private readonly systemPrompt: string;
|
|
157
|
+
private contextWindowManager: ContextWindowManager;
|
|
158
|
+
private contextCompactedMessageCount = 0;
|
|
159
|
+
private contextCompactedAt: number | null = null;
|
|
160
|
+
/** @internal — exposed for session-history.ts module functions. */
|
|
161
|
+
currentRequestId?: string;
|
|
162
|
+
/** @internal — exposed for session-usage.ts module functions. */
|
|
163
|
+
assistantId: string | null = null;
|
|
164
|
+
private conflictGate = new ConflictGate();
|
|
165
|
+
/** @internal — exposed for session-tool-setup.ts to propagate into ToolContext. */
|
|
166
|
+
hasNoClient = false;
|
|
167
|
+
/** @internal — exposed for session-process.ts module functions. */
|
|
168
|
+
readonly queue = new MessageQueue();
|
|
169
|
+
/** @internal — exposed for session-process.ts module functions. */
|
|
170
|
+
currentActiveSurfaceId?: string;
|
|
171
|
+
/** @internal — exposed for session-process.ts module functions. */
|
|
172
|
+
currentPage?: string;
|
|
173
|
+
private channelCapabilities?: ChannelCapabilities;
|
|
174
|
+
/** @internal — exposed for session-surfaces.ts module functions. */
|
|
175
|
+
pendingSurfaceActions = new Map<string, {
|
|
176
|
+
surfaceType: SurfaceType;
|
|
177
|
+
}>();
|
|
178
|
+
/** @internal */ lastSurfaceAction = new Map<string, { actionId: string; data?: Record<string, unknown> }>();
|
|
179
|
+
/** @internal */ surfaceState = new Map<string, { surfaceType: SurfaceType; data: SurfaceData }>();
|
|
180
|
+
/** @internal Per-surface undo stack: stores previous HTML strings for workspace refinement undo. */
|
|
181
|
+
surfaceUndoStacks = new Map<string, string[]>();
|
|
182
|
+
/** @internal Surfaces created during the current agent loop turn, to be persisted with the message. */
|
|
183
|
+
currentTurnSurfaces: Array<{ surfaceId: string; surfaceType: SurfaceType; title?: string; data: SurfaceData; actions?: Array<{ id: string; label: string; style?: string }>; display?: string }> = [];
|
|
184
|
+
/** @internal */ onEscalateToComputerUse?: (task: string, sourceSessionId: string) => boolean;
|
|
185
|
+
/** @internal — exposed for session-workspace.ts module functions. */
|
|
186
|
+
workspaceTopLevelContext: string | null = null;
|
|
187
|
+
/** @internal — exposed for session-workspace.ts module functions. */
|
|
188
|
+
workspaceTopLevelDirty = true;
|
|
189
|
+
public readonly traceEmitter: TraceEmitter;
|
|
190
|
+
public memoryPolicy: SessionMemoryPolicy;
|
|
191
|
+
/** Monotonically increasing turn counter for turn-boundary commits. */
|
|
192
|
+
private turnCount = 0;
|
|
193
|
+
|
|
194
|
+
/** Resolved assistant attachment drafts from the most recent exchange. */
|
|
195
|
+
public lastAssistantAttachments: AssistantAttachmentDraft[] = [];
|
|
196
|
+
/** Warnings from directive parsing/resolution for the most recent exchange. */
|
|
197
|
+
public lastAttachmentWarnings: string[] = [];
|
|
198
|
+
|
|
199
|
+
constructor(
|
|
200
|
+
conversationId: string,
|
|
201
|
+
provider: Provider,
|
|
202
|
+
systemPrompt: string,
|
|
203
|
+
maxTokens: number,
|
|
204
|
+
sendToClient: (msg: ServerMessage) => void,
|
|
205
|
+
workingDir: string,
|
|
206
|
+
broadcastToAllClients?: (msg: ServerMessage) => void,
|
|
207
|
+
memoryPolicy?: SessionMemoryPolicy,
|
|
208
|
+
) {
|
|
209
|
+
this.conversationId = conversationId;
|
|
210
|
+
this.systemPrompt = systemPrompt;
|
|
211
|
+
this.provider = provider;
|
|
212
|
+
this.workingDir = workingDir;
|
|
213
|
+
this.sendToClient = sendToClient;
|
|
214
|
+
this.broadcastToAllClients = broadcastToAllClients;
|
|
215
|
+
this.memoryPolicy = memoryPolicy ? { ...memoryPolicy } : { ...DEFAULT_MEMORY_POLICY };
|
|
216
|
+
this.traceEmitter = new TraceEmitter(conversationId, sendToClient);
|
|
217
|
+
this.prompter = new PermissionPrompter(sendToClient);
|
|
218
|
+
this.secretPrompter = new SecretPrompter(sendToClient);
|
|
219
|
+
|
|
220
|
+
registerWatchStartNotifier(conversationId, (session: WatchSession) => {
|
|
221
|
+
this.sendToClient({
|
|
222
|
+
type: 'watch_started',
|
|
223
|
+
sessionId: conversationId,
|
|
224
|
+
watchId: session.watchId,
|
|
225
|
+
durationSeconds: session.durationSeconds,
|
|
226
|
+
intervalSeconds: session.intervalSeconds,
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
registerWatchCommentaryNotifier(conversationId, (_session: WatchSession) => {
|
|
231
|
+
const commentary = lastCommentaryBySession.get(conversationId);
|
|
232
|
+
if (commentary) {
|
|
233
|
+
lastCommentaryBySession.delete(conversationId);
|
|
234
|
+
this.sendToClient({
|
|
235
|
+
type: 'assistant_text_delta',
|
|
236
|
+
text: commentary,
|
|
237
|
+
sessionId: conversationId,
|
|
238
|
+
});
|
|
239
|
+
this.sendToClient({
|
|
240
|
+
type: 'message_complete',
|
|
241
|
+
sessionId: conversationId,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
registerWatchCompletionNotifier(conversationId, (_session: WatchSession) => {
|
|
247
|
+
const summary = lastSummaryBySession.get(conversationId);
|
|
248
|
+
if (summary) {
|
|
249
|
+
lastSummaryBySession.delete(conversationId);
|
|
250
|
+
this.sendToClient({
|
|
251
|
+
type: 'assistant_text_delta',
|
|
252
|
+
text: summary,
|
|
253
|
+
sessionId: conversationId,
|
|
254
|
+
});
|
|
255
|
+
this.sendToClient({
|
|
256
|
+
type: 'message_complete',
|
|
257
|
+
sessionId: conversationId,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this.executor = new ToolExecutor(this.prompter);
|
|
263
|
+
this.profiler = new ToolProfiler();
|
|
264
|
+
registerToolMetricsLoggingListener(this.eventBus);
|
|
265
|
+
registerToolNotificationListener(this.eventBus, (msg) => this.sendToClient(msg));
|
|
266
|
+
registerToolTraceListener(this.eventBus, this.traceEmitter);
|
|
267
|
+
registerToolProfilingListener(this.eventBus, this.profiler);
|
|
268
|
+
const auditToolLifecycleEvent = createToolAuditListener();
|
|
269
|
+
const publishToolDomainEvent = createToolDomainEventPublisher(this.eventBus);
|
|
270
|
+
const handleToolLifecycleEvent = (event: import('../tools/types.js').ToolLifecycleEvent) => {
|
|
271
|
+
auditToolLifecycleEvent(event);
|
|
272
|
+
return publishToolDomainEvent(event);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const toolDefs = buildToolDefinitions();
|
|
276
|
+
this.coreToolNames = new Set(toolDefs.map((d) => d.name));
|
|
277
|
+
const toolExecutor = createToolExecutor(
|
|
278
|
+
this.executor,
|
|
279
|
+
this.prompter,
|
|
280
|
+
this.secretPrompter,
|
|
281
|
+
this as ToolSetupContext,
|
|
282
|
+
handleToolLifecycleEvent,
|
|
283
|
+
broadcastToAllClients,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const config = getConfig();
|
|
287
|
+
// Build a resolveTools callback that merges base tool definitions with
|
|
288
|
+
// dynamically projected skill tools on each agent turn. Also updates
|
|
289
|
+
// allowedToolNames so newly-activated skill tools aren't blocked by
|
|
290
|
+
// the executor's stale gate.
|
|
291
|
+
const resolveTools = toolDefs.length > 0
|
|
292
|
+
? (history: Message[]) => {
|
|
293
|
+
const projection = projectSkillTools(history, {
|
|
294
|
+
preactivatedSkillIds: this.preactivatedSkillIds,
|
|
295
|
+
previouslyActiveSkillIds: this.skillProjectionState,
|
|
296
|
+
});
|
|
297
|
+
const turnAllowed = new Set(this.coreToolNames);
|
|
298
|
+
for (const name of projection.allowedToolNames) {
|
|
299
|
+
turnAllowed.add(name);
|
|
300
|
+
}
|
|
301
|
+
this.allowedToolNames = turnAllowed;
|
|
302
|
+
return [...toolDefs, ...projection.toolDefinitions];
|
|
303
|
+
}
|
|
304
|
+
: undefined;
|
|
305
|
+
|
|
306
|
+
this.agentLoop = new AgentLoop(
|
|
307
|
+
provider,
|
|
308
|
+
systemPrompt,
|
|
309
|
+
{ maxTokens, maxInputTokens: config.contextWindow.maxInputTokens, thinking: config.thinking },
|
|
310
|
+
toolDefs.length > 0 ? toolDefs : undefined,
|
|
311
|
+
toolDefs.length > 0 ? toolExecutor : undefined,
|
|
312
|
+
resolveTools,
|
|
313
|
+
);
|
|
314
|
+
this.contextWindowManager = new ContextWindowManager(
|
|
315
|
+
provider,
|
|
316
|
+
systemPrompt,
|
|
317
|
+
config.contextWindow,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
void getHookManager().trigger('session-start', {
|
|
321
|
+
sessionId: this.conversationId,
|
|
322
|
+
workingDir: this.workingDir,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async loadFromDb(): Promise<void> {
|
|
327
|
+
const dbMessages = conversationStore.getMessages(this.conversationId);
|
|
328
|
+
|
|
329
|
+
const conv = conversationStore.getConversation(this.conversationId);
|
|
330
|
+
const contextSummary = conv?.contextSummary?.trim() || null;
|
|
331
|
+
this.contextCompactedMessageCount = Math.max(
|
|
332
|
+
0,
|
|
333
|
+
Math.min(conv?.contextCompactedMessageCount ?? 0, dbMessages.length),
|
|
334
|
+
);
|
|
335
|
+
this.contextCompactedAt = conv?.contextCompactedAt ?? null;
|
|
336
|
+
|
|
337
|
+
const parsedMessages: Message[] = dbMessages
|
|
338
|
+
.slice(this.contextCompactedMessageCount)
|
|
339
|
+
.map((m) => {
|
|
340
|
+
const role = m.role as 'user' | 'assistant';
|
|
341
|
+
let content: ContentBlock[];
|
|
342
|
+
try {
|
|
343
|
+
const parsed = JSON.parse(m.content);
|
|
344
|
+
content = Array.isArray(parsed) ? parsed : [{ type: 'text', text: m.content }];
|
|
345
|
+
} catch {
|
|
346
|
+
log.warn({ conversationId: this.conversationId, messageId: m.id }, 'Invalid JSON in persisted message content, replacing with safe text block');
|
|
347
|
+
content = [{ type: 'text', text: m.content }];
|
|
348
|
+
}
|
|
349
|
+
return { role, content };
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const { messages: repairedMessages, stats } = repairHistory(parsedMessages);
|
|
353
|
+
if (stats.assistantToolResultsMigrated > 0 || stats.missingToolResultsInserted > 0 || stats.orphanToolResultsDowngraded > 0 || stats.consecutiveSameRoleMerged > 0) {
|
|
354
|
+
log.warn({ conversationId: this.conversationId, phase: 'load', ...stats }, 'Repaired persisted history');
|
|
355
|
+
}
|
|
356
|
+
this.messages = repairedMessages;
|
|
357
|
+
|
|
358
|
+
if (contextSummary) {
|
|
359
|
+
this.messages.unshift(createContextSummaryMessage(contextSummary));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (conv) {
|
|
363
|
+
this.usageStats = {
|
|
364
|
+
inputTokens: conv.totalInputTokens,
|
|
365
|
+
outputTokens: conv.totalOutputTokens,
|
|
366
|
+
estimatedCost: conv.totalEstimatedCost,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
log.info({ conversationId: this.conversationId, count: this.messages.length }, 'Loaded messages from DB');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
updateClient(sendToClient: (msg: ServerMessage) => void, hasNoClient = false): void {
|
|
374
|
+
this.sendToClient = sendToClient;
|
|
375
|
+
this.hasNoClient = hasNoClient;
|
|
376
|
+
this.prompter.updateSender(sendToClient);
|
|
377
|
+
this.secretPrompter.updateSender(sendToClient);
|
|
378
|
+
this.traceEmitter.updateSender(sendToClient);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
setSandboxOverride(enabled: boolean | undefined): void {
|
|
382
|
+
this.sandboxOverride = enabled;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Set a callback for when a text_qa session escalates to computer use
|
|
387
|
+
* via the `computer_use_request_control` tool.
|
|
388
|
+
*/
|
|
389
|
+
setEscalationHandler(handler: (task: string, sourceSessionId: string) => boolean): void {
|
|
390
|
+
this.onEscalateToComputerUse = handler;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
hasEscalationHandler(): boolean {
|
|
394
|
+
return this.onEscalateToComputerUse !== undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Redirect the user to the secure credential prompt after an ingress block.
|
|
399
|
+
* If the user enters a value, it is stored in the vault (or injected as
|
|
400
|
+
* transient) so the credential is available for later tool use.
|
|
401
|
+
*
|
|
402
|
+
* @param onComplete Called after the prompt resolves (success, cancel, or
|
|
403
|
+
* timeout) so the caller can clean up ephemeral resources like placeholder
|
|
404
|
+
* conversations.
|
|
405
|
+
*/
|
|
406
|
+
redirectToSecurePrompt(detectedTypes: string[], onComplete?: () => void): void {
|
|
407
|
+
const service = 'detected';
|
|
408
|
+
const field = detectedTypes.join(',');
|
|
409
|
+
this.secretPrompter.prompt(
|
|
410
|
+
service, field,
|
|
411
|
+
'Secure Credential Entry',
|
|
412
|
+
'Your message contained a secret. Please enter it here instead — it will be stored securely and never sent to the AI.',
|
|
413
|
+
undefined, this.conversationId,
|
|
414
|
+
).then(async (result) => {
|
|
415
|
+
if (!result.value) return; // user cancelled or timed out
|
|
416
|
+
|
|
417
|
+
const { setSecureKey } = await import('../security/secure-keys.js');
|
|
418
|
+
const { upsertCredentialMetadata } = await import('../tools/credentials/metadata-store.js');
|
|
419
|
+
|
|
420
|
+
if (result.delivery === 'transient_send') {
|
|
421
|
+
const { credentialBroker } = await import('../tools/credentials/broker.js');
|
|
422
|
+
credentialBroker.injectTransient(service, field, result.value);
|
|
423
|
+
try { upsertCredentialMetadata(service, field, {}); } catch {}
|
|
424
|
+
log.info({ service, field, delivery: 'transient_send' }, 'Ingress redirect: transient credential injected');
|
|
425
|
+
} else {
|
|
426
|
+
const key = `credential:${service}:${field}`;
|
|
427
|
+
const stored = setSecureKey(key, result.value);
|
|
428
|
+
if (stored) {
|
|
429
|
+
try { upsertCredentialMetadata(service, field, {}); } catch {}
|
|
430
|
+
log.info({ service, field }, 'Ingress redirect: credential stored');
|
|
431
|
+
} else {
|
|
432
|
+
log.warn({ service, field }, 'Ingress redirect: secure storage write failed');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}).catch(() => { /* prompt timeout or cancel is fine */ }).finally(() => {
|
|
436
|
+
onComplete?.();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
isProcessing(): boolean {
|
|
441
|
+
return this.processing;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
markStale(): void {
|
|
445
|
+
this.stale = true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
isStale(): boolean {
|
|
449
|
+
return this.stale;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
abort(): void {
|
|
453
|
+
if (this.processing) {
|
|
454
|
+
log.info({ conversationId: this.conversationId }, 'Aborting in-flight processing');
|
|
455
|
+
this.abortController?.abort();
|
|
456
|
+
this.prompter.dispose();
|
|
457
|
+
this.secretPrompter.dispose();
|
|
458
|
+
this.pendingSurfaceActions.clear();
|
|
459
|
+
this.surfaceState.clear();
|
|
460
|
+
unregisterWatchStartNotifier(this.conversationId);
|
|
461
|
+
unregisterWatchCommentaryNotifier(this.conversationId);
|
|
462
|
+
unregisterWatchCompletionNotifier(this.conversationId);
|
|
463
|
+
pruneWatchSessions(this.conversationId);
|
|
464
|
+
|
|
465
|
+
// Clear queued messages and notify each caller with a session-scoped
|
|
466
|
+
// cancel event so other sessions do not receive cross-thread errors.
|
|
467
|
+
for (const queued of this.queue) {
|
|
468
|
+
queued.onEvent({ type: 'generation_cancelled', sessionId: this.conversationId });
|
|
469
|
+
}
|
|
470
|
+
this.queue.clear();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Abort and permanently tear down this session. Call when removing from the sessions map. */
|
|
475
|
+
dispose(): void {
|
|
476
|
+
void getHookManager().trigger('session-end', {
|
|
477
|
+
sessionId: this.conversationId,
|
|
478
|
+
});
|
|
479
|
+
this.abort();
|
|
480
|
+
unregisterSessionSender(this.conversationId);
|
|
481
|
+
resetSkillToolProjection(this.skillProjectionState);
|
|
482
|
+
this.eventBus.dispose();
|
|
483
|
+
|
|
484
|
+
// Release heavy in-memory data so GC can reclaim it even if stale
|
|
485
|
+
// closure references (e.g. from buildEventHandler / onCheckpoint)
|
|
486
|
+
// keep this Session object reachable.
|
|
487
|
+
this.messages = [];
|
|
488
|
+
this.profiler.clear();
|
|
489
|
+
this.surfaceUndoStacks.clear();
|
|
490
|
+
this.currentTurnSurfaces = [];
|
|
491
|
+
this.pendingSurfaceActions.clear();
|
|
492
|
+
this.surfaceState.clear();
|
|
493
|
+
this.lastSurfaceAction.clear();
|
|
494
|
+
this.workspaceTopLevelContext = null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Enqueue a message if the session is busy, or indicate it should be
|
|
499
|
+
* processed immediately. Returns `{ queued: true }` if the message was
|
|
500
|
+
* added to the queue, `{ queued: false, rejected: true }` if the queue
|
|
501
|
+
* is full, or `{ queued: false }` if the caller should invoke
|
|
502
|
+
* `processMessage` directly.
|
|
503
|
+
*/
|
|
504
|
+
enqueueMessage(
|
|
505
|
+
content: string,
|
|
506
|
+
attachments: UserMessageAttachment[],
|
|
507
|
+
onEvent: (msg: ServerMessage) => void,
|
|
508
|
+
requestId: string,
|
|
509
|
+
activeSurfaceId?: string,
|
|
510
|
+
currentPage?: string,
|
|
511
|
+
): { queued: boolean; rejected?: boolean; requestId: string } {
|
|
512
|
+
if (!this.processing) {
|
|
513
|
+
return { queued: false, requestId };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const pushed = this.queue.push({ content, attachments, requestId, onEvent, activeSurfaceId, currentPage });
|
|
517
|
+
if (!pushed) {
|
|
518
|
+
return { queued: false, rejected: true, requestId };
|
|
519
|
+
}
|
|
520
|
+
return { queued: true, requestId };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
getQueueDepth(): number {
|
|
524
|
+
return this.queue.length;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Returns true if there are messages waiting in the queue.
|
|
529
|
+
*/
|
|
530
|
+
hasQueuedMessages(): boolean {
|
|
531
|
+
return !this.queue.isEmpty;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Remove a queued message by requestId. Returns true if the message was found
|
|
536
|
+
* and removed, false if the requestId was not in the queue.
|
|
537
|
+
*/
|
|
538
|
+
removeQueuedMessage(requestId: string): boolean {
|
|
539
|
+
return this.queue.removeByRequestId(requestId) !== undefined;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Returns true if the session is currently processing and there are queued
|
|
544
|
+
* messages waiting. This is the predicate used to decide whether to yield
|
|
545
|
+
* at a turn boundary (checkpoint handoff).
|
|
546
|
+
*/
|
|
547
|
+
canHandoffAtCheckpoint(): boolean {
|
|
548
|
+
return this.processing && this.hasQueuedMessages();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
hasPendingConfirmation(requestId: string): boolean {
|
|
552
|
+
return this.prompter.hasPendingRequest(requestId);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
hasPendingSecret(requestId: string): boolean {
|
|
556
|
+
return this.secretPrompter.hasPendingRequest(requestId);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
handleConfirmationResponse(
|
|
560
|
+
requestId: string,
|
|
561
|
+
decision: UserDecision,
|
|
562
|
+
selectedPattern?: string,
|
|
563
|
+
selectedScope?: string,
|
|
564
|
+
): void {
|
|
565
|
+
this.prompter.resolveConfirmation(requestId, decision, selectedPattern, selectedScope);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
handleSecretResponse(requestId: string, value?: string, delivery?: 'store' | 'transient_send'): void {
|
|
569
|
+
this.secretPrompter.resolveSecret(requestId, value, delivery);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Bind a runtime assistant ID to this session.
|
|
574
|
+
* IPC-only desktop sessions can leave this unset and use a local scope.
|
|
575
|
+
*/
|
|
576
|
+
setAssistantId(assistantId: string): void {
|
|
577
|
+
this.assistantId = assistantId;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
setChannelCapabilities(caps: ChannelCapabilities): void {
|
|
581
|
+
this.channelCapabilities = caps;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private async approveHostAttachmentReadImpl(filePath: string): Promise<boolean> {
|
|
585
|
+
return approveHostAttachmentRead(filePath, this.workingDir, this.prompter, this.conversationId, this.hasNoClient);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Persist a user message and mark the session as processing.
|
|
590
|
+
* Returns the messageId immediately without running the agent loop.
|
|
591
|
+
* After calling this, call `runAgentLoop` to continue processing.
|
|
592
|
+
*/
|
|
593
|
+
persistUserMessage(
|
|
594
|
+
content: string,
|
|
595
|
+
attachments: UserMessageAttachment[],
|
|
596
|
+
requestId?: string,
|
|
597
|
+
): string {
|
|
598
|
+
if (this.processing) {
|
|
599
|
+
throw new Error('Session is already processing a message');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (!content.trim() && attachments.length === 0) {
|
|
603
|
+
throw new Error('Message content or attachments are required');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const reqId = requestId ?? uuid();
|
|
607
|
+
this.currentRequestId = reqId;
|
|
608
|
+
this.processing = true;
|
|
609
|
+
this.abortController = new AbortController();
|
|
610
|
+
|
|
611
|
+
const userMessage = createUserMessage(content, attachments.map((attachment) => ({
|
|
612
|
+
id: attachment.id,
|
|
613
|
+
filename: attachment.filename,
|
|
614
|
+
mimeType: attachment.mimeType,
|
|
615
|
+
data: attachment.data,
|
|
616
|
+
extractedText: attachment.extractedText,
|
|
617
|
+
})));
|
|
618
|
+
this.messages.push(userMessage);
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const persistedUserMessage = conversationStore.addMessage(
|
|
622
|
+
this.conversationId,
|
|
623
|
+
'user',
|
|
624
|
+
JSON.stringify(userMessage.content),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
if (!persistedUserMessage.id) {
|
|
628
|
+
throw new Error('Failed to persist user message');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return persistedUserMessage.id;
|
|
632
|
+
} catch (err) {
|
|
633
|
+
this.messages.pop();
|
|
634
|
+
this.processing = false;
|
|
635
|
+
this.abortController = null;
|
|
636
|
+
this.currentRequestId = undefined;
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Run the agent loop after a user message has been persisted via
|
|
643
|
+
* `persistUserMessage`. Clears the `processing` flag when done.
|
|
644
|
+
*
|
|
645
|
+
* @param options.skipPreMessageRollback - When true, the pre-message hook
|
|
646
|
+
* blocked path will NOT delete the user message from in-memory history or
|
|
647
|
+
* the DB. Used by `regenerate()` where the user message is the original
|
|
648
|
+
* (not freshly persisted) and must be preserved.
|
|
649
|
+
*/
|
|
650
|
+
async runAgentLoop(
|
|
651
|
+
content: string,
|
|
652
|
+
userMessageId: string,
|
|
653
|
+
onEvent: (msg: ServerMessage) => void,
|
|
654
|
+
options?: { skipPreMessageRollback?: boolean },
|
|
655
|
+
): Promise<void> {
|
|
656
|
+
if (!this.abortController) {
|
|
657
|
+
throw new Error('runAgentLoop called without prior persistUserMessage');
|
|
658
|
+
}
|
|
659
|
+
const abortController = this.abortController;
|
|
660
|
+
const reqId = this.currentRequestId ?? uuid();
|
|
661
|
+
const rlog = log.child({ conversationId: this.conversationId, requestId: reqId });
|
|
662
|
+
let yieldedForHandoff = false;
|
|
663
|
+
|
|
664
|
+
// Reset attachment state so a failed exchange never retains stale data
|
|
665
|
+
// from a prior successful run.
|
|
666
|
+
this.lastAssistantAttachments = [];
|
|
667
|
+
this.lastAttachmentWarnings = [];
|
|
668
|
+
|
|
669
|
+
// Ensure the workspace git repo is initialized before any tools run.
|
|
670
|
+
// This must happen before the first turn so the initial commit captures
|
|
671
|
+
// the pre-turn workspace state; otherwise ensureInitialized() would be
|
|
672
|
+
// triggered lazily by getStatus() inside commitTurnChanges(), absorbing
|
|
673
|
+
// the first turn's file changes into the initial commit.
|
|
674
|
+
try {
|
|
675
|
+
const gitService = getWorkspaceGitService(this.workingDir);
|
|
676
|
+
await gitService.ensureInitialized();
|
|
677
|
+
} catch (err) {
|
|
678
|
+
rlog.warn({ err }, 'Failed to initialize workspace git repo (non-fatal)');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
this.profiler.startRequest();
|
|
682
|
+
|
|
683
|
+
// Tracks whether the agent loop started — once true, we guarantee a
|
|
684
|
+
// turn-boundary commit even if post-processing throws.
|
|
685
|
+
let turnStarted = false;
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
const preMessageResult = await getHookManager().trigger('pre-message', {
|
|
689
|
+
sessionId: this.conversationId,
|
|
690
|
+
messagePreview: content.slice(0, 200),
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
if (preMessageResult.blocked) {
|
|
694
|
+
if (!options?.skipPreMessageRollback) {
|
|
695
|
+
// Roll back the user message from both in-memory history and the DB.
|
|
696
|
+
// We use deleteMessageById (not deleteLastExchange) because it NULLs
|
|
697
|
+
// nullable FK references (message_runs, channel_inbound_events) before
|
|
698
|
+
// deleting the message row, so the run record survives.
|
|
699
|
+
this.messages.pop();
|
|
700
|
+
conversationStore.deleteMessageById(userMessageId);
|
|
701
|
+
}
|
|
702
|
+
onEvent({ type: 'error', message: `Message blocked by hook "${preMessageResult.blockedBy}"` });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const isFirstMessage = this.messages.length === 1;
|
|
707
|
+
|
|
708
|
+
const compacted = await this.contextWindowManager.maybeCompact(
|
|
709
|
+
this.messages,
|
|
710
|
+
abortController.signal,
|
|
711
|
+
{ lastCompactedAt: this.contextCompactedAt ?? undefined },
|
|
712
|
+
);
|
|
713
|
+
if (compacted.compacted) {
|
|
714
|
+
this.messages = compacted.messages;
|
|
715
|
+
this.contextCompactedMessageCount += compacted.compactedPersistedMessages;
|
|
716
|
+
this.contextCompactedAt = Date.now();
|
|
717
|
+
conversationStore.updateConversationContextWindow(
|
|
718
|
+
this.conversationId,
|
|
719
|
+
compacted.summaryText,
|
|
720
|
+
this.contextCompactedMessageCount,
|
|
721
|
+
);
|
|
722
|
+
onEvent({
|
|
723
|
+
type: 'context_compacted',
|
|
724
|
+
previousEstimatedInputTokens: compacted.previousEstimatedInputTokens,
|
|
725
|
+
estimatedInputTokens: compacted.estimatedInputTokens,
|
|
726
|
+
maxInputTokens: compacted.maxInputTokens,
|
|
727
|
+
thresholdTokens: compacted.thresholdTokens,
|
|
728
|
+
compactedMessages: compacted.compactedMessages,
|
|
729
|
+
summaryCalls: compacted.summaryCalls,
|
|
730
|
+
summaryInputTokens: compacted.summaryInputTokens,
|
|
731
|
+
summaryOutputTokens: compacted.summaryOutputTokens,
|
|
732
|
+
summaryModel: compacted.summaryModel,
|
|
733
|
+
});
|
|
734
|
+
this.recordUsage(
|
|
735
|
+
compacted.summaryInputTokens,
|
|
736
|
+
compacted.summaryOutputTokens,
|
|
737
|
+
compacted.summaryModel,
|
|
738
|
+
onEvent,
|
|
739
|
+
'context_compactor',
|
|
740
|
+
reqId,
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Run agent loop
|
|
745
|
+
let firstAssistantText = '';
|
|
746
|
+
let exchangeInputTokens = 0;
|
|
747
|
+
let exchangeOutputTokens = 0;
|
|
748
|
+
let model = '';
|
|
749
|
+
let runMessages = this.messages;
|
|
750
|
+
const pendingToolResults = new Map<string, { content: string; isError: boolean; contentBlocks?: ContentBlock[] }>();
|
|
751
|
+
const persistedToolUseIds = new Set<string>();
|
|
752
|
+
const accumulatedDirectives: DirectiveRequest[] = [];
|
|
753
|
+
const accumulatedToolContentBlocks: ContentBlock[] = [];
|
|
754
|
+
const directiveWarnings: string[] = [];
|
|
755
|
+
let pendingDirectiveDisplayBuffer = '';
|
|
756
|
+
let lastAssistantMessageId: string | undefined;
|
|
757
|
+
let providerErrorUserMessage: string | null = null;
|
|
758
|
+
const memoryResult = await prepareMemoryContext(
|
|
759
|
+
{
|
|
760
|
+
conversationId: this.conversationId,
|
|
761
|
+
messages: this.messages,
|
|
762
|
+
systemPrompt: this.systemPrompt,
|
|
763
|
+
provider: this.provider,
|
|
764
|
+
conflictGate: this.conflictGate,
|
|
765
|
+
scopeId: this.memoryPolicy.scopeId,
|
|
766
|
+
includeDefaultFallback: this.memoryPolicy.includeDefaultFallback,
|
|
767
|
+
},
|
|
768
|
+
content,
|
|
769
|
+
userMessageId,
|
|
770
|
+
abortController.signal,
|
|
771
|
+
onEvent,
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
if (memoryResult.conflictClarification) {
|
|
775
|
+
const assistantMessage = createAssistantMessage(memoryResult.conflictClarification);
|
|
776
|
+
conversationStore.addMessage(
|
|
777
|
+
this.conversationId,
|
|
778
|
+
'assistant',
|
|
779
|
+
JSON.stringify(assistantMessage.content),
|
|
780
|
+
);
|
|
781
|
+
this.messages.push(assistantMessage);
|
|
782
|
+
onEvent({
|
|
783
|
+
type: 'assistant_text_delta',
|
|
784
|
+
text: memoryResult.conflictClarification,
|
|
785
|
+
sessionId: this.conversationId,
|
|
786
|
+
});
|
|
787
|
+
this.traceEmitter.emit('message_complete', 'Conflict clarification requested (relevant)', {
|
|
788
|
+
requestId: reqId,
|
|
789
|
+
status: 'info',
|
|
790
|
+
attributes: { conflictGate: 'relevant' },
|
|
791
|
+
});
|
|
792
|
+
onEvent({ type: 'message_complete', sessionId: this.conversationId });
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const { recall, dynamicProfile, softConflictInstruction, recallInjectionStrategy } = memoryResult;
|
|
797
|
+
runMessages = memoryResult.runMessages;
|
|
798
|
+
|
|
799
|
+
// Inject soft-conflict instruction and active surface context
|
|
800
|
+
let activeSurface: ActiveSurfaceContext | null = null;
|
|
801
|
+
if (this.currentActiveSurfaceId) {
|
|
802
|
+
const stored = this.surfaceState.get(this.currentActiveSurfaceId);
|
|
803
|
+
if (stored && stored.surfaceType === 'dynamic_page') {
|
|
804
|
+
const data = stored.data as DynamicPageSurfaceData;
|
|
805
|
+
activeSurface = {
|
|
806
|
+
surfaceId: this.currentActiveSurfaceId,
|
|
807
|
+
html: data.html,
|
|
808
|
+
currentPage: this.currentPage,
|
|
809
|
+
};
|
|
810
|
+
// Enrich with app context when the surface is backed by a persisted app
|
|
811
|
+
if (data.appId) {
|
|
812
|
+
const app = getApp(data.appId);
|
|
813
|
+
if (app) {
|
|
814
|
+
activeSurface.appId = app.id;
|
|
815
|
+
activeSurface.appName = app.name;
|
|
816
|
+
activeSurface.appSchemaJson = app.schemaJson;
|
|
817
|
+
activeSurface.appFiles = listAppFiles(app.id);
|
|
818
|
+
if (app.pages && Object.keys(app.pages).length > 0) {
|
|
819
|
+
activeSurface.appPages = app.pages;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// Refresh workspace top-level context before injection
|
|
826
|
+
this.refreshWorkspaceTopLevelContextIfNeeded();
|
|
827
|
+
|
|
828
|
+
runMessages = applyRuntimeInjections(runMessages, {
|
|
829
|
+
softConflictInstruction,
|
|
830
|
+
activeSurface,
|
|
831
|
+
workspaceTopLevelContext: this.workspaceTopLevelContext,
|
|
832
|
+
channelCapabilities: this.channelCapabilities ?? null,
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// Pre-run repair: fix any message ordering issues before sending to provider.
|
|
836
|
+
// Keep a reference to the original (un-repaired) messages so we can
|
|
837
|
+
// reconstruct this.messages after the agent loop without leaking synthetic
|
|
838
|
+
// tool_result blocks that repair may inject. Leaking those blocks would
|
|
839
|
+
// break undo semantics (isUndoableUserMessage skips user messages
|
|
840
|
+
// containing only tool_result blocks).
|
|
841
|
+
let preRepairMessages = runMessages;
|
|
842
|
+
const preRunRepair = repairHistory(runMessages);
|
|
843
|
+
if (preRunRepair.stats.assistantToolResultsMigrated > 0 || preRunRepair.stats.missingToolResultsInserted > 0 || preRunRepair.stats.orphanToolResultsDowngraded > 0 || preRunRepair.stats.consecutiveSameRoleMerged > 0) {
|
|
844
|
+
rlog.warn({ phase: 'pre_run', ...preRunRepair.stats }, 'Repaired runtime history before provider call');
|
|
845
|
+
runMessages = preRunRepair.messages;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
let orderingErrorDetected = false;
|
|
849
|
+
let deferredOrderingError: string | null = null;
|
|
850
|
+
let contextTooLargeDetected = false;
|
|
851
|
+
let preRunHistoryLength = runMessages.length;
|
|
852
|
+
|
|
853
|
+
// Track whether llm_call_started has been emitted for the current provider turn.
|
|
854
|
+
// Reset on each usage event (which marks the end of a provider call).
|
|
855
|
+
let llmCallStartedEmitted = false;
|
|
856
|
+
|
|
857
|
+
// Map tool_use_id → toolName so tool_result processing can identify the originating tool.
|
|
858
|
+
const toolUseIdToName = new Map<string, string>();
|
|
859
|
+
|
|
860
|
+
// Track tool names used in the current agent turn for checkpoint decisions.
|
|
861
|
+
let currentTurnToolNames: string[] = [];
|
|
862
|
+
|
|
863
|
+
const buildEventHandler = () => (event: import('../agent/loop.js').AgentEvent) => {
|
|
864
|
+
// Emit llm_call_started once per provider call. Called on first streaming
|
|
865
|
+
// token (text or thinking) or, for tool-only turns, right before the
|
|
866
|
+
// usage event so every llm_call_finished has a matching start.
|
|
867
|
+
const emitLlmCallStartedIfNeeded = () => {
|
|
868
|
+
if (llmCallStartedEmitted) return;
|
|
869
|
+
llmCallStartedEmitted = true;
|
|
870
|
+
this.traceEmitter.emit('llm_call_started', `LLM call to ${this.provider.name}`, {
|
|
871
|
+
requestId: reqId,
|
|
872
|
+
status: 'info',
|
|
873
|
+
attributes: { provider: this.provider.name, model: model || 'unknown' },
|
|
874
|
+
});
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
switch (event.type) {
|
|
878
|
+
case 'text_delta': {
|
|
879
|
+
emitLlmCallStartedIfNeeded();
|
|
880
|
+
pendingDirectiveDisplayBuffer += event.text;
|
|
881
|
+
const drained = drainDirectiveDisplayBuffer(pendingDirectiveDisplayBuffer);
|
|
882
|
+
pendingDirectiveDisplayBuffer = drained.bufferedRemainder;
|
|
883
|
+
if (drained.emitText.length > 0) {
|
|
884
|
+
onEvent({ type: 'assistant_text_delta', text: drained.emitText, sessionId: this.conversationId });
|
|
885
|
+
if (isFirstMessage) firstAssistantText += drained.emitText;
|
|
886
|
+
}
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
case 'thinking_delta':
|
|
890
|
+
// Thinking content itself is NOT included in traces to avoid leaking
|
|
891
|
+
// extended-thinking data.
|
|
892
|
+
emitLlmCallStartedIfNeeded();
|
|
893
|
+
onEvent({ type: 'assistant_thinking_delta', thinking: event.thinking });
|
|
894
|
+
break;
|
|
895
|
+
case 'tool_use':
|
|
896
|
+
toolUseIdToName.set(event.id, event.name);
|
|
897
|
+
currentTurnToolNames.push(event.name);
|
|
898
|
+
onEvent({ type: 'tool_use_start', toolName: event.name, input: event.input, sessionId: this.conversationId });
|
|
899
|
+
break;
|
|
900
|
+
case 'tool_output_chunk':
|
|
901
|
+
onEvent({ type: 'tool_output_chunk', chunk: event.chunk });
|
|
902
|
+
break;
|
|
903
|
+
case 'input_json_delta':
|
|
904
|
+
onEvent({ type: 'tool_input_delta', toolName: event.toolName, content: event.accumulatedJson, sessionId: this.conversationId });
|
|
905
|
+
break;
|
|
906
|
+
case 'tool_result': {
|
|
907
|
+
const imageBlock = event.contentBlocks?.find((b): b is ImageContent => b.type === 'image');
|
|
908
|
+
onEvent({ type: 'tool_result', toolName: '', result: event.content, isError: event.isError, diff: event.diff, status: event.status, sessionId: this.conversationId, imageData: imageBlock?.source.data });
|
|
909
|
+
pendingToolResults.set(event.toolUseId, { content: event.content, isError: event.isError, contentBlocks: event.contentBlocks });
|
|
910
|
+
// Mark workspace context dirty for mutation tools.
|
|
911
|
+
// file_write and bash are always dirty regardless of isError —
|
|
912
|
+
// file_write may physically write before a post-write error, and
|
|
913
|
+
// bash commands can modify the filesystem even when exiting
|
|
914
|
+
// non-zero (e.g. `mkdir foo && false`, `npm install` with audit
|
|
915
|
+
// warnings, compound commands where early parts succeed).
|
|
916
|
+
// file_edit is only dirty on success — a failed edit provably
|
|
917
|
+
// never touches the filesystem.
|
|
918
|
+
{
|
|
919
|
+
const toolName = toolUseIdToName.get(event.toolUseId);
|
|
920
|
+
if (toolName === 'file_write' || toolName === 'bash') {
|
|
921
|
+
this.markWorkspaceTopLevelDirty();
|
|
922
|
+
} else if (toolName === 'file_edit' && !event.isError) {
|
|
923
|
+
this.markWorkspaceTopLevelDirty();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
// Collect image/file content blocks for assistant attachment conversion
|
|
927
|
+
if (event.contentBlocks) {
|
|
928
|
+
for (const cb of event.contentBlocks) {
|
|
929
|
+
if (cb.type === 'image' || cb.type === 'file') {
|
|
930
|
+
accumulatedToolContentBlocks.push(cb);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case 'error':
|
|
937
|
+
if (isProviderOrderingError(event.error.message)) {
|
|
938
|
+
orderingErrorDetected = true;
|
|
939
|
+
// Defer the error event — only forward if retry also fails
|
|
940
|
+
deferredOrderingError = event.error.message;
|
|
941
|
+
} else if (isContextTooLarge(event.error.message)) {
|
|
942
|
+
contextTooLargeDetected = true;
|
|
943
|
+
// Defer — attempt compaction + retry before surfacing to user
|
|
944
|
+
} else {
|
|
945
|
+
const classified = classifySessionError(event.error, { phase: 'agent_loop' });
|
|
946
|
+
onEvent(buildSessionErrorMessage(this.conversationId, classified));
|
|
947
|
+
providerErrorUserMessage = classified.userMessage;
|
|
948
|
+
}
|
|
949
|
+
break;
|
|
950
|
+
case 'message_complete': {
|
|
951
|
+
if (pendingDirectiveDisplayBuffer.length > 0) {
|
|
952
|
+
onEvent({
|
|
953
|
+
type: 'assistant_text_delta',
|
|
954
|
+
text: pendingDirectiveDisplayBuffer,
|
|
955
|
+
sessionId: this.conversationId,
|
|
956
|
+
});
|
|
957
|
+
if (isFirstMessage) firstAssistantText += pendingDirectiveDisplayBuffer;
|
|
958
|
+
pendingDirectiveDisplayBuffer = '';
|
|
959
|
+
}
|
|
960
|
+
// Save pending tool results as a user message before the next assistant message.
|
|
961
|
+
// tool_result blocks belong in user messages per the Anthropic API spec.
|
|
962
|
+
if (pendingToolResults.size > 0) {
|
|
963
|
+
const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
|
|
964
|
+
([toolUseId, result]) => ({
|
|
965
|
+
type: 'tool_result',
|
|
966
|
+
tool_use_id: toolUseId,
|
|
967
|
+
content: result.content,
|
|
968
|
+
is_error: result.isError,
|
|
969
|
+
...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
|
|
970
|
+
}),
|
|
971
|
+
);
|
|
972
|
+
conversationStore.addMessage(
|
|
973
|
+
this.conversationId,
|
|
974
|
+
'user',
|
|
975
|
+
JSON.stringify(toolResultBlocks),
|
|
976
|
+
);
|
|
977
|
+
for (const id of pendingToolResults.keys()) {
|
|
978
|
+
persistedToolUseIds.add(id);
|
|
979
|
+
}
|
|
980
|
+
pendingToolResults.clear();
|
|
981
|
+
}
|
|
982
|
+
// Parse and strip attachment directives from assistant text
|
|
983
|
+
const { cleanedContent, directives: msgDirectives, warnings: msgWarnings } =
|
|
984
|
+
cleanAssistantContent(event.message.content);
|
|
985
|
+
accumulatedDirectives.push(...msgDirectives);
|
|
986
|
+
directiveWarnings.push(...msgWarnings);
|
|
987
|
+
if (msgDirectives.length > 0) {
|
|
988
|
+
rlog.info(
|
|
989
|
+
{ parsedDirectives: msgDirectives.map(d => ({ source: d.source, path: d.path, mimeType: d.mimeType })), totalAccumulated: accumulatedDirectives.length },
|
|
990
|
+
'Parsed attachment directives from assistant message',
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Add surface blocks to content for persistence
|
|
995
|
+
const contentWithSurfaces: ContentBlock[] = [...cleanedContent as ContentBlock[]];
|
|
996
|
+
for (const surface of this.currentTurnSurfaces) {
|
|
997
|
+
contentWithSurfaces.push({
|
|
998
|
+
type: 'ui_surface',
|
|
999
|
+
surfaceId: surface.surfaceId,
|
|
1000
|
+
surfaceType: surface.surfaceType,
|
|
1001
|
+
title: surface.title,
|
|
1002
|
+
data: surface.data,
|
|
1003
|
+
actions: surface.actions,
|
|
1004
|
+
display: surface.display,
|
|
1005
|
+
} as unknown as ContentBlock);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Save assistant message with cleaned content (tags stripped) plus surfaces
|
|
1009
|
+
const assistantMsg = conversationStore.addMessage(
|
|
1010
|
+
this.conversationId,
|
|
1011
|
+
'assistant',
|
|
1012
|
+
JSON.stringify(contentWithSurfaces),
|
|
1013
|
+
);
|
|
1014
|
+
lastAssistantMessageId = assistantMsg.id;
|
|
1015
|
+
|
|
1016
|
+
// Clear surfaces for next turn
|
|
1017
|
+
this.currentTurnSurfaces = [];
|
|
1018
|
+
|
|
1019
|
+
// Emit assistant_message trace with content metrics.
|
|
1020
|
+
// Char count only includes text blocks; thinking blocks are
|
|
1021
|
+
// explicitly excluded from traces.
|
|
1022
|
+
const charCount = cleanedContent
|
|
1023
|
+
.filter((b) => (b as Record<string, unknown>).type === 'text')
|
|
1024
|
+
.reduce((sum: number, b) => sum + ((b as { text?: string }).text?.length ?? 0), 0);
|
|
1025
|
+
const toolUseCount = event.message.content
|
|
1026
|
+
.filter((b) => b.type === 'tool_use')
|
|
1027
|
+
.length;
|
|
1028
|
+
this.traceEmitter.emit('assistant_message', 'Assistant message complete', {
|
|
1029
|
+
requestId: reqId,
|
|
1030
|
+
status: 'success',
|
|
1031
|
+
attributes: { charCount, toolUseCount },
|
|
1032
|
+
});
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
case 'usage':
|
|
1036
|
+
exchangeInputTokens += event.inputTokens;
|
|
1037
|
+
exchangeOutputTokens += event.outputTokens;
|
|
1038
|
+
model = event.model;
|
|
1039
|
+
|
|
1040
|
+
// Persist raw LLM request/response payloads for diagnostics export
|
|
1041
|
+
if (event.rawRequest && event.rawResponse) {
|
|
1042
|
+
try {
|
|
1043
|
+
recordRequestLog(
|
|
1044
|
+
this.conversationId,
|
|
1045
|
+
JSON.stringify(event.rawRequest),
|
|
1046
|
+
JSON.stringify(event.rawResponse),
|
|
1047
|
+
);
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
rlog.warn({ err }, 'Failed to persist LLM request log (non-fatal)');
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Ensure llm_call_started is emitted even for tool-only turns
|
|
1054
|
+
// (where no text_delta or thinking_delta events fire)
|
|
1055
|
+
emitLlmCallStartedIfNeeded();
|
|
1056
|
+
|
|
1057
|
+
// Emit llm_call_finished trace with token and latency metrics
|
|
1058
|
+
this.traceEmitter.emit('llm_call_finished', `LLM call to ${this.provider.name} finished`, {
|
|
1059
|
+
requestId: reqId,
|
|
1060
|
+
status: 'success',
|
|
1061
|
+
attributes: {
|
|
1062
|
+
provider: this.provider.name,
|
|
1063
|
+
model: event.model,
|
|
1064
|
+
inputTokens: event.inputTokens,
|
|
1065
|
+
outputTokens: event.outputTokens,
|
|
1066
|
+
latencyMs: event.providerDurationMs,
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
// Reset flag so the next provider call in this agent loop run
|
|
1070
|
+
// gets its own llm_call_started trace
|
|
1071
|
+
llmCallStartedEmitted = false;
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
const onCheckpoint = (): CheckpointDecision => {
|
|
1077
|
+
// Capture and reset tool names for this turn
|
|
1078
|
+
const turnTools = currentTurnToolNames;
|
|
1079
|
+
currentTurnToolNames = [];
|
|
1080
|
+
|
|
1081
|
+
if (this.canHandoffAtCheckpoint()) {
|
|
1082
|
+
// Don't interrupt active browser interaction flows — the agent
|
|
1083
|
+
// needs multiple consecutive turns (snapshot → click → snapshot)
|
|
1084
|
+
// and yielding mid-flow leaves the task incomplete.
|
|
1085
|
+
const inBrowserFlow = turnTools.length > 0
|
|
1086
|
+
&& turnTools.every(n => n.startsWith('browser_'));
|
|
1087
|
+
if (!inBrowserFlow) {
|
|
1088
|
+
yieldedForHandoff = true;
|
|
1089
|
+
return 'yield';
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return 'continue';
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// Mark that the agent loop is about to run — workspace files may be
|
|
1096
|
+
// modified from this point onward, so we must commit at the turn boundary
|
|
1097
|
+
// even if post-processing (e.g. resolveAssistantAttachments) throws.
|
|
1098
|
+
turnStarted = true;
|
|
1099
|
+
|
|
1100
|
+
let updatedHistory = await this.agentLoop.run(
|
|
1101
|
+
runMessages,
|
|
1102
|
+
buildEventHandler(),
|
|
1103
|
+
abortController.signal,
|
|
1104
|
+
reqId,
|
|
1105
|
+
onCheckpoint,
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
// One-shot self-heal retry: if the provider returned a strict ordering
|
|
1109
|
+
// error and no messages were appended (error on first call), apply a
|
|
1110
|
+
// deep repair (handles additional edge cases like consecutive same-role
|
|
1111
|
+
// messages) and retry exactly once.
|
|
1112
|
+
if (orderingErrorDetected && updatedHistory.length === preRunHistoryLength) {
|
|
1113
|
+
rlog.warn({ phase: 'retry' }, 'Provider ordering error detected, attempting one-shot deep-repair retry');
|
|
1114
|
+
const retryRepair = deepRepairHistory(runMessages);
|
|
1115
|
+
runMessages = retryRepair.messages;
|
|
1116
|
+
// Update preRepairMessages so that structural fixes from deep repair
|
|
1117
|
+
// (e.g., stripping leading assistant messages, merging same-role runs)
|
|
1118
|
+
// persist in this.messages after the run. Without this, the original
|
|
1119
|
+
// malformed prefix would be restored and trigger the same error next turn.
|
|
1120
|
+
preRepairMessages = retryRepair.messages;
|
|
1121
|
+
preRunHistoryLength = runMessages.length;
|
|
1122
|
+
orderingErrorDetected = false;
|
|
1123
|
+
deferredOrderingError = null;
|
|
1124
|
+
|
|
1125
|
+
updatedHistory = await this.agentLoop.run(
|
|
1126
|
+
runMessages,
|
|
1127
|
+
buildEventHandler(),
|
|
1128
|
+
abortController.signal,
|
|
1129
|
+
reqId,
|
|
1130
|
+
onCheckpoint,
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
if (orderingErrorDetected) {
|
|
1134
|
+
rlog.error({ phase: 'retry' }, 'Deep-repair retry also failed with ordering error. Consider starting a new conversation if this persists.');
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// One-shot context-too-large recovery: force compaction and retry once.
|
|
1139
|
+
if (contextTooLargeDetected && updatedHistory.length === preRunHistoryLength) {
|
|
1140
|
+
rlog.warn({ phase: 'retry' }, 'Context too large — attempting forced compaction and retry');
|
|
1141
|
+
const emergencyCompact = await this.contextWindowManager.maybeCompact(
|
|
1142
|
+
this.messages,
|
|
1143
|
+
abortController.signal,
|
|
1144
|
+
{ lastCompactedAt: this.contextCompactedAt ?? undefined, force: true },
|
|
1145
|
+
);
|
|
1146
|
+
if (emergencyCompact.compacted) {
|
|
1147
|
+
this.messages = emergencyCompact.messages;
|
|
1148
|
+
this.contextCompactedMessageCount += emergencyCompact.compactedPersistedMessages;
|
|
1149
|
+
this.contextCompactedAt = Date.now();
|
|
1150
|
+
conversationStore.updateConversationContextWindow(
|
|
1151
|
+
this.conversationId,
|
|
1152
|
+
emergencyCompact.summaryText,
|
|
1153
|
+
this.contextCompactedMessageCount,
|
|
1154
|
+
);
|
|
1155
|
+
onEvent({
|
|
1156
|
+
type: 'context_compacted',
|
|
1157
|
+
previousEstimatedInputTokens: emergencyCompact.previousEstimatedInputTokens,
|
|
1158
|
+
estimatedInputTokens: emergencyCompact.estimatedInputTokens,
|
|
1159
|
+
maxInputTokens: emergencyCompact.maxInputTokens,
|
|
1160
|
+
thresholdTokens: emergencyCompact.thresholdTokens,
|
|
1161
|
+
compactedMessages: emergencyCompact.compactedMessages,
|
|
1162
|
+
summaryCalls: emergencyCompact.summaryCalls,
|
|
1163
|
+
summaryInputTokens: emergencyCompact.summaryInputTokens,
|
|
1164
|
+
summaryOutputTokens: emergencyCompact.summaryOutputTokens,
|
|
1165
|
+
summaryModel: emergencyCompact.summaryModel,
|
|
1166
|
+
});
|
|
1167
|
+
this.recordUsage(
|
|
1168
|
+
emergencyCompact.summaryInputTokens,
|
|
1169
|
+
emergencyCompact.summaryOutputTokens,
|
|
1170
|
+
emergencyCompact.summaryModel,
|
|
1171
|
+
onEvent,
|
|
1172
|
+
'context_compactor',
|
|
1173
|
+
reqId,
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
// Retry with compacted context
|
|
1177
|
+
runMessages = applyRuntimeInjections(this.messages, {
|
|
1178
|
+
softConflictInstruction,
|
|
1179
|
+
activeSurface,
|
|
1180
|
+
workspaceTopLevelContext: this.workspaceTopLevelContext,
|
|
1181
|
+
});
|
|
1182
|
+
preRepairMessages = runMessages;
|
|
1183
|
+
preRunHistoryLength = runMessages.length;
|
|
1184
|
+
contextTooLargeDetected = false;
|
|
1185
|
+
|
|
1186
|
+
updatedHistory = await this.agentLoop.run(
|
|
1187
|
+
runMessages,
|
|
1188
|
+
buildEventHandler(),
|
|
1189
|
+
abortController.signal,
|
|
1190
|
+
reqId,
|
|
1191
|
+
onCheckpoint,
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (contextTooLargeDetected) {
|
|
1196
|
+
const mediaTrimmed = stripMediaPayloadsForRetry(this.messages);
|
|
1197
|
+
if (mediaTrimmed.modified) {
|
|
1198
|
+
rlog.warn(
|
|
1199
|
+
{
|
|
1200
|
+
phase: 'retry',
|
|
1201
|
+
replacedBlocks: mediaTrimmed.replacedBlocks,
|
|
1202
|
+
latestUserIndex: mediaTrimmed.latestUserIndex,
|
|
1203
|
+
},
|
|
1204
|
+
'Context still too large — retrying with older media payloads trimmed',
|
|
1205
|
+
);
|
|
1206
|
+
this.messages = mediaTrimmed.messages;
|
|
1207
|
+
runMessages = applyRuntimeInjections(this.messages, {
|
|
1208
|
+
softConflictInstruction,
|
|
1209
|
+
activeSurface,
|
|
1210
|
+
workspaceTopLevelContext: this.workspaceTopLevelContext,
|
|
1211
|
+
});
|
|
1212
|
+
preRepairMessages = runMessages;
|
|
1213
|
+
preRunHistoryLength = runMessages.length;
|
|
1214
|
+
contextTooLargeDetected = false;
|
|
1215
|
+
|
|
1216
|
+
updatedHistory = await this.agentLoop.run(
|
|
1217
|
+
runMessages,
|
|
1218
|
+
buildEventHandler(),
|
|
1219
|
+
abortController.signal,
|
|
1220
|
+
reqId,
|
|
1221
|
+
onCheckpoint,
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Surface the error if compaction didn't help or wasn't possible
|
|
1227
|
+
if (contextTooLargeDetected) {
|
|
1228
|
+
const classified = classifySessionError(
|
|
1229
|
+
new Error('context_length_exceeded'),
|
|
1230
|
+
{ phase: 'agent_loop' },
|
|
1231
|
+
);
|
|
1232
|
+
onEvent(buildSessionErrorMessage(this.conversationId, classified));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Forward the deferred ordering error to the client if retry failed or was not attempted
|
|
1237
|
+
if (deferredOrderingError) {
|
|
1238
|
+
const classified = classifySessionError(new Error(deferredOrderingError), { phase: 'agent_loop' });
|
|
1239
|
+
onEvent(buildSessionErrorMessage(this.conversationId, classified));
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Reconcile synthesized cancellation tool_results from history tail.
|
|
1243
|
+
// When abort happens, the agent loop synthesizes "Cancelled by user"
|
|
1244
|
+
// results directly into the history without firing tool_result events,
|
|
1245
|
+
// so they're missing from pendingToolResults and would not be persisted.
|
|
1246
|
+
for (let i = preRunHistoryLength; i < updatedHistory.length; i++) {
|
|
1247
|
+
const msg = updatedHistory[i];
|
|
1248
|
+
if (msg.role === 'user') {
|
|
1249
|
+
for (const block of msg.content) {
|
|
1250
|
+
if (block.type === 'tool_result' && !pendingToolResults.has(block.tool_use_id) && !persistedToolUseIds.has(block.tool_use_id)) {
|
|
1251
|
+
pendingToolResults.set(block.tool_use_id, {
|
|
1252
|
+
content: block.content,
|
|
1253
|
+
isError: block.is_error ?? false,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Flush any remaining tool results as a user message
|
|
1261
|
+
if (pendingToolResults.size > 0) {
|
|
1262
|
+
const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
|
|
1263
|
+
([toolUseId, result]) => ({
|
|
1264
|
+
type: 'tool_result',
|
|
1265
|
+
tool_use_id: toolUseId,
|
|
1266
|
+
content: result.content,
|
|
1267
|
+
is_error: result.isError,
|
|
1268
|
+
...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
|
|
1269
|
+
}),
|
|
1270
|
+
);
|
|
1271
|
+
conversationStore.addMessage(
|
|
1272
|
+
this.conversationId,
|
|
1273
|
+
'user',
|
|
1274
|
+
JSON.stringify(toolResultBlocks),
|
|
1275
|
+
);
|
|
1276
|
+
pendingToolResults.clear();
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Reconstruct history: use the original (un-repaired) prefix so that
|
|
1280
|
+
// synthetic tool_result blocks from pre-run repair don't leak into
|
|
1281
|
+
// this.messages. Only the new messages appended by the agent loop
|
|
1282
|
+
// (beyond the repaired prefix) are carried forward.
|
|
1283
|
+
//
|
|
1284
|
+
// Strip directive tags from assistant messages so in-memory history
|
|
1285
|
+
// matches the cleaned content persisted to the DB. Without this,
|
|
1286
|
+
// subsequent turns would send raw <vellum-attachment /> tags to the
|
|
1287
|
+
// LLM, wasting tokens and encouraging hallucinated directives.
|
|
1288
|
+
const newMessages = updatedHistory.slice(preRunHistoryLength).map((msg) => {
|
|
1289
|
+
if (msg.role !== 'assistant') return msg;
|
|
1290
|
+
const { cleanedContent } = cleanAssistantContent(msg.content);
|
|
1291
|
+
return { ...msg, content: cleanedContent as ContentBlock[] };
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
// If no assistant response was produced (e.g. provider 500 error),
|
|
1295
|
+
// synthesize an assistant message so the error is visible in the conversation.
|
|
1296
|
+
const hasAssistantResponse = newMessages.some((msg) => msg.role === 'assistant');
|
|
1297
|
+
if (!hasAssistantResponse && providerErrorUserMessage && !abortController.signal.aborted && !yieldedForHandoff) {
|
|
1298
|
+
const errorAssistantMessage = createAssistantMessage(providerErrorUserMessage);
|
|
1299
|
+
conversationStore.addMessage(
|
|
1300
|
+
this.conversationId,
|
|
1301
|
+
'assistant',
|
|
1302
|
+
JSON.stringify(errorAssistantMessage.content),
|
|
1303
|
+
);
|
|
1304
|
+
newMessages.push(errorAssistantMessage);
|
|
1305
|
+
onEvent({
|
|
1306
|
+
type: 'assistant_text_delta',
|
|
1307
|
+
text: providerErrorUserMessage,
|
|
1308
|
+
sessionId: this.conversationId,
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const restoredHistory = [...preRepairMessages, ...newMessages];
|
|
1313
|
+
const recallStripped = stripMemoryRecallMessages(restoredHistory, recall.injectedText, recallInjectionStrategy);
|
|
1314
|
+
this.messages = stripChannelCapabilityContext(
|
|
1315
|
+
stripWorkspaceTopLevelContext(
|
|
1316
|
+
stripActiveSurfaceContext(
|
|
1317
|
+
stripDynamicProfileMessages(recallStripped, dynamicProfile.text),
|
|
1318
|
+
),
|
|
1319
|
+
),
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
this.recordUsage(exchangeInputTokens, exchangeOutputTokens, model, onEvent, 'main_agent', reqId);
|
|
1323
|
+
|
|
1324
|
+
void getHookManager().trigger('post-message', {
|
|
1325
|
+
sessionId: this.conversationId,
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
// Resolve accumulated attachment directives and tool content blocks
|
|
1329
|
+
// BEFORE emitting the completion event so attachments are included.
|
|
1330
|
+
const attachmentResult = await resolveAssistantAttachments(
|
|
1331
|
+
accumulatedDirectives,
|
|
1332
|
+
accumulatedToolContentBlocks,
|
|
1333
|
+
directiveWarnings,
|
|
1334
|
+
this.workingDir,
|
|
1335
|
+
async (filePath) => this.approveHostAttachmentReadImpl(filePath),
|
|
1336
|
+
lastAssistantMessageId,
|
|
1337
|
+
this.assistantId ?? 'local-assistant',
|
|
1338
|
+
);
|
|
1339
|
+
const { assistantAttachments, emittedAttachments } = attachmentResult;
|
|
1340
|
+
|
|
1341
|
+
this.lastAssistantAttachments = assistantAttachments;
|
|
1342
|
+
this.lastAttachmentWarnings = attachmentResult.directiveWarnings;
|
|
1343
|
+
|
|
1344
|
+
const warningText = formatAttachmentWarnings(attachmentResult.directiveWarnings);
|
|
1345
|
+
if (warningText) {
|
|
1346
|
+
onEvent({ type: 'assistant_text_delta', text: warningText, sessionId: this.conversationId });
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Emit the completion event here in the try block; the turn-boundary
|
|
1350
|
+
// commit runs in `finally` (after this), so the client's
|
|
1351
|
+
// thinking/streaming indicators clear immediately without waiting
|
|
1352
|
+
// for the git commit (which can take 0.5–2 s on large workspaces).
|
|
1353
|
+
if (yieldedForHandoff) {
|
|
1354
|
+
this.traceEmitter.emit('generation_handoff', 'Handing off to next queued message', {
|
|
1355
|
+
requestId: reqId,
|
|
1356
|
+
status: 'info',
|
|
1357
|
+
attributes: { queuedCount: this.getQueueDepth() },
|
|
1358
|
+
});
|
|
1359
|
+
onEvent({
|
|
1360
|
+
type: 'generation_handoff',
|
|
1361
|
+
sessionId: this.conversationId,
|
|
1362
|
+
requestId: reqId,
|
|
1363
|
+
queuedCount: this.getQueueDepth(),
|
|
1364
|
+
...(emittedAttachments.length > 0 ? { attachments: emittedAttachments } : {}),
|
|
1365
|
+
});
|
|
1366
|
+
} else if (abortController.signal.aborted) {
|
|
1367
|
+
this.traceEmitter.emit('generation_cancelled', 'Generation cancelled by user', {
|
|
1368
|
+
requestId: reqId,
|
|
1369
|
+
status: 'warning',
|
|
1370
|
+
});
|
|
1371
|
+
onEvent({ type: 'generation_cancelled', sessionId: this.conversationId });
|
|
1372
|
+
} else {
|
|
1373
|
+
this.traceEmitter.emit('message_complete', 'Message processing complete', {
|
|
1374
|
+
requestId: reqId,
|
|
1375
|
+
status: 'success',
|
|
1376
|
+
});
|
|
1377
|
+
onEvent({
|
|
1378
|
+
type: 'message_complete',
|
|
1379
|
+
sessionId: this.conversationId,
|
|
1380
|
+
...(emittedAttachments.length > 0 ? { attachments: emittedAttachments } : {}),
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Auto-generate conversation title after first exchange
|
|
1385
|
+
if (isFirstMessage) {
|
|
1386
|
+
this.generateTitle(content, firstAssistantText).catch((err) => {
|
|
1387
|
+
log.warn({ err, conversationId: this.conversationId }, 'Failed to generate conversation title (non-fatal, using default title)');
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
const errorCtx = { phase: 'agent_loop' as const, aborted: abortController.signal.aborted };
|
|
1392
|
+
// AbortError is expected when user cancels — don't treat as an error
|
|
1393
|
+
if (isUserCancellation(err, errorCtx)) {
|
|
1394
|
+
rlog.info('Generation cancelled by user');
|
|
1395
|
+
this.traceEmitter.emit('generation_cancelled', 'Generation cancelled by user', {
|
|
1396
|
+
requestId: reqId,
|
|
1397
|
+
status: 'warning',
|
|
1398
|
+
});
|
|
1399
|
+
onEvent({ type: 'generation_cancelled', sessionId: this.conversationId });
|
|
1400
|
+
} else {
|
|
1401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1402
|
+
const errorClass = err instanceof Error ? err.constructor.name : 'Error';
|
|
1403
|
+
rlog.error({ err }, 'Session processing error');
|
|
1404
|
+
this.traceEmitter.emit('request_error', message.slice(0, 200), {
|
|
1405
|
+
requestId: reqId,
|
|
1406
|
+
status: 'error',
|
|
1407
|
+
attributes: { errorClass, message: message.slice(0, 500) },
|
|
1408
|
+
});
|
|
1409
|
+
onEvent({ type: 'error', message: `Failed to process message: ${message}` });
|
|
1410
|
+
const classified = classifySessionError(err, errorCtx);
|
|
1411
|
+
onEvent(buildSessionErrorMessage(this.conversationId, classified));
|
|
1412
|
+
void getHookManager().trigger('on-error', {
|
|
1413
|
+
error: err instanceof Error ? err.name : 'Error',
|
|
1414
|
+
message,
|
|
1415
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
1416
|
+
sessionId: this.conversationId,
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
} finally {
|
|
1420
|
+
// Turn-boundary commit: runs after completion/error events (try or
|
|
1421
|
+
// catch) but before drainQueue. Guarantees a commit attempt whenever
|
|
1422
|
+
// the agent loop started, even if post-processing threw.
|
|
1423
|
+
if (turnStarted) {
|
|
1424
|
+
this.turnCount++;
|
|
1425
|
+
await commitTurnChanges(this.workingDir, this.conversationId, this.turnCount);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
this.profiler.emitSummary(this.traceEmitter, reqId);
|
|
1429
|
+
|
|
1430
|
+
this.abortController = null;
|
|
1431
|
+
this.processing = false;
|
|
1432
|
+
this.currentRequestId = undefined;
|
|
1433
|
+
this.currentActiveSurfaceId = undefined;
|
|
1434
|
+
this.allowedToolNames = undefined;
|
|
1435
|
+
this.preactivatedSkillIds = undefined;
|
|
1436
|
+
|
|
1437
|
+
// Consolidate consecutive assistant messages from this agent loop run
|
|
1438
|
+
if (userMessageId) {
|
|
1439
|
+
this.consolidateAssistantMessages(userMessageId);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Drain the next queued message, if any
|
|
1443
|
+
this.drainQueue(yieldedForHandoff ? 'checkpoint_handoff' : 'loop_complete');
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
private consolidateAssistantMessages(userMessageId: string): void {
|
|
1448
|
+
consolidateAssistantMessages(this.conversationId, userMessageId);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private drainQueue(reason: QueueDrainReason = 'loop_complete'): void {
|
|
1452
|
+
drainQueueImpl(this as ProcessSessionContext, reason);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
async processMessage(
|
|
1456
|
+
content: string,
|
|
1457
|
+
attachments: UserMessageAttachment[],
|
|
1458
|
+
onEvent: (msg: ServerMessage) => void,
|
|
1459
|
+
requestId?: string,
|
|
1460
|
+
activeSurfaceId?: string,
|
|
1461
|
+
currentPage?: string,
|
|
1462
|
+
): Promise<string> {
|
|
1463
|
+
return processMessageImpl(this as ProcessSessionContext, content, attachments, onEvent, requestId, activeSurfaceId, currentPage);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
handleSurfaceAction(surfaceId: string, actionId: string, data?: Record<string, unknown>): void {
|
|
1467
|
+
handleSurfaceActionImpl(this, surfaceId, actionId, data);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
getMessages(): Message[] {
|
|
1471
|
+
return this.messages;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
undo(): number {
|
|
1475
|
+
return undoImpl(this as HistorySessionContext);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
async regenerate(onEvent: (msg: ServerMessage) => void, requestId?: string): Promise<void> {
|
|
1479
|
+
return regenerateImpl(this as HistorySessionContext, onEvent, requestId);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// ── Workspace Top-Level Context ──────────────────────────────────
|
|
1483
|
+
|
|
1484
|
+
refreshWorkspaceTopLevelContextIfNeeded(): void {
|
|
1485
|
+
refreshWorkspaceImpl(this);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
markWorkspaceTopLevelDirty(): void {
|
|
1489
|
+
this.workspaceTopLevelDirty = true;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
getWorkspaceTopLevelContext(): string | null {
|
|
1493
|
+
return this.workspaceTopLevelContext;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
isWorkspaceTopLevelDirty(): boolean {
|
|
1497
|
+
return this.workspaceTopLevelDirty;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* After an app_update, refresh any active surface that displays the updated app.
|
|
1502
|
+
* This makes app_update a single call that both persists AND displays changes.
|
|
1503
|
+
*/
|
|
1504
|
+
handleSurfaceUndo(surfaceId: string): void {
|
|
1505
|
+
handleSurfaceUndoImpl(this, surfaceId);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
private recordUsage(
|
|
1509
|
+
inputTokens: number,
|
|
1510
|
+
outputTokens: number,
|
|
1511
|
+
model: string,
|
|
1512
|
+
onEvent: (msg: ServerMessage) => void,
|
|
1513
|
+
actor: UsageActor,
|
|
1514
|
+
requestId: string | null = null,
|
|
1515
|
+
): void {
|
|
1516
|
+
recordUsage(
|
|
1517
|
+
{ conversationId: this.conversationId, providerName: this.provider.name, assistantId: this.assistantId, usageStats: this.usageStats },
|
|
1518
|
+
inputTokens, outputTokens, model, onEvent, actor, requestId,
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
private async generateTitle(userMessage: string, assistantResponse: string): Promise<void> {
|
|
1523
|
+
const prompt = `Generate a very short title for this conversation. Rules: at most 5 words, at most 40 characters, no quotes.\n\nUser: ${userMessage.slice(0, 200)}\nAssistant: ${assistantResponse.slice(0, 200)}`;
|
|
1524
|
+
const response = await this.provider.sendMessage(
|
|
1525
|
+
[{ role: 'user', content: [{ type: 'text', text: prompt }] }],
|
|
1526
|
+
[], // no tools
|
|
1527
|
+
undefined, // no system prompt
|
|
1528
|
+
{ config: { max_tokens: 30 } },
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
const textBlock = response.content.find((b) => b.type === 'text');
|
|
1532
|
+
if (textBlock && textBlock.type === 'text') {
|
|
1533
|
+
let title = textBlock.text.trim().replace(/^["']|["']$/g, '');
|
|
1534
|
+
const words = title.split(/\s+/);
|
|
1535
|
+
if (words.length > 5) title = words.slice(0, 5).join(' ');
|
|
1536
|
+
if (title.length > 40) title = title.slice(0, 40).trimEnd();
|
|
1537
|
+
conversationStore.updateConversationTitle(this.conversationId, title);
|
|
1538
|
+
log.info({ conversationId: this.conversationId, title }, 'Auto-generated conversation title');
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
function stripMediaPayloadsForRetry(messages: Message[]): { messages: Message[]; modified: boolean; replacedBlocks: number; latestUserIndex: number | null } {
|
|
1545
|
+
let latestUserIndex: number | null = null;
|
|
1546
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1547
|
+
const msg = messages[i];
|
|
1548
|
+
if (msg.role !== 'user') continue;
|
|
1549
|
+
if (getSummaryFromContextMessage(msg) !== null) continue;
|
|
1550
|
+
if (isToolResultOnlyMessage(msg)) continue;
|
|
1551
|
+
latestUserIndex = i;
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
let modified = false;
|
|
1556
|
+
let replacedBlocks = 0;
|
|
1557
|
+
let keptLatestMediaBlocks = 0;
|
|
1558
|
+
|
|
1559
|
+
const nextMessages = messages.map((msg, msgIndex) => {
|
|
1560
|
+
const nextContent: ContentBlock[] = [];
|
|
1561
|
+
for (const block of msg.content) {
|
|
1562
|
+
if (block.type === 'image') {
|
|
1563
|
+
const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
|
|
1564
|
+
if (keep) {
|
|
1565
|
+
keptLatestMediaBlocks += 1;
|
|
1566
|
+
nextContent.push(block);
|
|
1567
|
+
} else {
|
|
1568
|
+
replacedBlocks += 1;
|
|
1569
|
+
modified = true;
|
|
1570
|
+
nextContent.push(imageBlockToStub(block));
|
|
1571
|
+
}
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
if (block.type === 'file') {
|
|
1576
|
+
const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
|
|
1577
|
+
if (keep) {
|
|
1578
|
+
keptLatestMediaBlocks += 1;
|
|
1579
|
+
nextContent.push(block);
|
|
1580
|
+
} else {
|
|
1581
|
+
replacedBlocks += 1;
|
|
1582
|
+
modified = true;
|
|
1583
|
+
nextContent.push(fileBlockToStub(block));
|
|
1584
|
+
}
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if (block.type === 'tool_result' && block.contentBlocks && block.contentBlocks.length > 0) {
|
|
1589
|
+
let toolResultChanged = false;
|
|
1590
|
+
const nextToolContentBlocks: ContentBlock[] = block.contentBlocks.map((cb) => {
|
|
1591
|
+
if (cb.type === 'image') {
|
|
1592
|
+
replacedBlocks += 1;
|
|
1593
|
+
modified = true;
|
|
1594
|
+
toolResultChanged = true;
|
|
1595
|
+
return imageBlockToStub(cb);
|
|
1596
|
+
}
|
|
1597
|
+
if (cb.type === 'file') {
|
|
1598
|
+
replacedBlocks += 1;
|
|
1599
|
+
modified = true;
|
|
1600
|
+
toolResultChanged = true;
|
|
1601
|
+
return fileBlockToStub(cb);
|
|
1602
|
+
}
|
|
1603
|
+
return cb;
|
|
1604
|
+
});
|
|
1605
|
+
if (toolResultChanged) {
|
|
1606
|
+
nextContent.push({ ...block, contentBlocks: nextToolContentBlocks });
|
|
1607
|
+
} else {
|
|
1608
|
+
nextContent.push(block);
|
|
1609
|
+
}
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
nextContent.push(block);
|
|
1614
|
+
}
|
|
1615
|
+
return { ...msg, content: nextContent };
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
return {
|
|
1619
|
+
messages: modified ? nextMessages : messages,
|
|
1620
|
+
modified,
|
|
1621
|
+
replacedBlocks,
|
|
1622
|
+
latestUserIndex,
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function imageBlockToStub(block: Extract<ContentBlock, { type: 'image' }>): Extract<ContentBlock, { type: 'text' }> {
|
|
1627
|
+
const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
|
|
1628
|
+
return {
|
|
1629
|
+
type: 'text',
|
|
1630
|
+
text: `[Image omitted from retry context: ${block.source.media_type}, ${sizeBytes} bytes]`,
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function fileBlockToStub(block: Extract<ContentBlock, { type: 'file' }>): Extract<ContentBlock, { type: 'text' }> {
|
|
1635
|
+
const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
|
|
1636
|
+
const extracted = (block.extracted_text ?? '').trim();
|
|
1637
|
+
const preview = extracted.length > MAX_MEDIA_STUB_TEXT
|
|
1638
|
+
? `${extracted.slice(0, MAX_MEDIA_STUB_TEXT)}...`
|
|
1639
|
+
: extracted;
|
|
1640
|
+
return {
|
|
1641
|
+
type: 'text',
|
|
1642
|
+
text: preview.length > 0
|
|
1643
|
+
? `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]\n${preview}`
|
|
1644
|
+
: `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]`,
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function isToolResultOnlyMessage(message: Message): boolean {
|
|
1649
|
+
return message.content.length > 0
|
|
1650
|
+
&& message.content.every((block) => block.type === 'tool_result');
|
|
1651
|
+
}
|