mixdog 0.9.2 → 0.9.4
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/package.json +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-sse.mjs — Anthropic SSE stream parser + mid-stream retry policy.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from anthropic-oauth.mjs. Shared by BOTH Anthropic providers:
|
|
5
|
+
* anthropic-oauth.mjs (OAuth) and anthropic.mjs (API key) import
|
|
6
|
+
* parseSSEStream / _classifyMidstreamError / ANTHROPIC_MAX_MIDSTREAM_RETRIES
|
|
7
|
+
* from here (via the anthropic-oauth.mjs re-export for legacy paths).
|
|
8
|
+
*/
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
10
|
+
import {
|
|
11
|
+
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
12
|
+
PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
|
|
13
|
+
PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
|
|
14
|
+
streamStalledError,
|
|
15
|
+
} from '../stall-policy.mjs';
|
|
16
|
+
import {
|
|
17
|
+
classifyMidstreamError,
|
|
18
|
+
MIDSTREAM_RETRY_POLICY,
|
|
19
|
+
sleepWithAbort,
|
|
20
|
+
} from './retry-classifier.mjs';
|
|
21
|
+
import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
|
|
22
|
+
import { scanLeakedToolCalls, createToolCallDedupe } from './anthropic-leaked-toolcall.mjs';
|
|
23
|
+
|
|
24
|
+
/** Bounded mid-stream SSE retries (transient stream loss); shared with anthropic.mjs.
|
|
25
|
+
* Sourced from the single shared retry-budget table (MIDSTREAM_RETRY_POLICY.sse). */
|
|
26
|
+
export const ANTHROPIC_MAX_MIDSTREAM_RETRIES = MIDSTREAM_RETRY_POLICY.sse.defaultRetries;
|
|
27
|
+
|
|
28
|
+
// Policy passed to the shared classifyMidstreamError for the SSE path. The
|
|
29
|
+
// top-of-function attempt-budget gate uses defaultRetries (3); perClassifierGate
|
|
30
|
+
// is false so the classifier returns raw bucket strings (the loop owns the
|
|
31
|
+
// MAX_MIDSTREAM_RETRIES bound), matching the former _classifyMidstreamError.
|
|
32
|
+
const SSE_MIDSTREAM_POLICY = {
|
|
33
|
+
mode: 'sse',
|
|
34
|
+
defaultRetries: MIDSTREAM_RETRY_POLICY.sse.defaultRetries,
|
|
35
|
+
perClassifierGate: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// --- SSE parser ---
|
|
39
|
+
|
|
40
|
+
function _captureMidstreamAbort(state, reason) {
|
|
41
|
+
if (!state) return;
|
|
42
|
+
const reasonName = reason?.name || '';
|
|
43
|
+
if (reasonName === 'AgentStallAbortError' || reasonName === 'StreamStalledAbortError') {
|
|
44
|
+
state.watchdogAbort = reasonName;
|
|
45
|
+
} else {
|
|
46
|
+
state.userAbort = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Abort-aware mid-stream backoff sleep → shared sleepWithAbort
|
|
51
|
+
// (retry-classifier.mjs). abortMessage preserves the prior fallback text.
|
|
52
|
+
export function _midstreamSleepWithAbort(ms, signal) {
|
|
53
|
+
return sleepWithAbort(ms, signal, undefined, 'Anthropic OAuth mid-stream retry backoff aborted');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _statusForAnthropicSseError(type, message) {
|
|
57
|
+
const kind = String(type || '').toLowerCase();
|
|
58
|
+
const text = String(message || '').toLowerCase();
|
|
59
|
+
if (kind.includes('overload') || text.includes('overload')) return 503;
|
|
60
|
+
if (kind.includes('rate_limit') || text.includes('rate limit') || text.includes('quota')) return 429;
|
|
61
|
+
if (kind.includes('authentication') || text.includes('authentication') || text.includes('unauthorized')) return 401;
|
|
62
|
+
if (kind.includes('permission') || text.includes('forbidden')) return 403;
|
|
63
|
+
if (kind.includes('not_found') || text.includes('not found')) return 404;
|
|
64
|
+
if (kind.includes('invalid_request')) return 400;
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function _anthropicSseError(event) {
|
|
69
|
+
const payload = event?.error && typeof event.error === 'object' ? event.error : event;
|
|
70
|
+
const type = payload?.type || event?.type || 'error';
|
|
71
|
+
const message = payload?.message || 'Anthropic SSE error';
|
|
72
|
+
const err = new Error(`Anthropic OAuth SSE error ${type}: ${message}`);
|
|
73
|
+
err.name = 'AnthropicSseError';
|
|
74
|
+
err.code = 'EANTHROPIC_SSE_ERROR';
|
|
75
|
+
err.providerErrorType = type;
|
|
76
|
+
err.requestId = event?.request_id || event?.requestId || null;
|
|
77
|
+
const status = _statusForAnthropicSseError(type, message);
|
|
78
|
+
if (status) {
|
|
79
|
+
err.httpStatus = status;
|
|
80
|
+
err.status = status;
|
|
81
|
+
}
|
|
82
|
+
return err;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function parseSSEStream(response, signal, abortStream, onStreamDelta, onToolCall, state, onTextDelta, knownToolNames) {
|
|
86
|
+
const reader = response.body.getReader();
|
|
87
|
+
const decoder = new TextDecoder();
|
|
88
|
+
// SEMANTIC idle window: reset only by real model events (message/content/
|
|
89
|
+
// tool deltas), NOT by raw keepalive bytes. A ping-only wedge therefore
|
|
90
|
+
// trips this within the window instead of hanging until the 30-min agent
|
|
91
|
+
// watchdog. See resetIdleTimer + the per-event reset in the loop below.
|
|
92
|
+
// state.semanticIdleTimeoutMs is a test/override seam (same shape as
|
|
93
|
+
// firstMessageTimeoutMs); production uses the shared env-backed default.
|
|
94
|
+
const SSE_IDLE_TIMEOUT_MS = Number.isFinite(Number(state?.semanticIdleTimeoutMs))
|
|
95
|
+
&& Number(state.semanticIdleTimeoutMs) > 0
|
|
96
|
+
? Number(state.semanticIdleTimeoutMs)
|
|
97
|
+
: PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
|
|
98
|
+
const SSE_FIRST_MESSAGE_TIMEOUT_MS = Number.isFinite(Number(state?.firstMessageTimeoutMs))
|
|
99
|
+
&& Number(state.firstMessageTimeoutMs) > 0
|
|
100
|
+
? Number(state.firstMessageTimeoutMs)
|
|
101
|
+
: PROVIDER_FIRST_BYTE_TIMEOUT_MS;
|
|
102
|
+
let content = '';
|
|
103
|
+
let hasThinkingContent = false;
|
|
104
|
+
const contentBlockTypes = new Set();
|
|
105
|
+
let model = '';
|
|
106
|
+
let toolCalls = [];
|
|
107
|
+
let usage = { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, raw: null };
|
|
108
|
+
let stopReason = null;
|
|
109
|
+
let buffer = '';
|
|
110
|
+
let idleTimedOut = false;
|
|
111
|
+
let firstMessageTimedOut = false;
|
|
112
|
+
let idleTimer = null;
|
|
113
|
+
let firstMessageTimer = null;
|
|
114
|
+
let currentEvent = '';
|
|
115
|
+
|
|
116
|
+
const pendingToolInputs = new Map();
|
|
117
|
+
|
|
118
|
+
// Leaked tool-call guard. The model (esp. Opus via OAuth) occasionally
|
|
119
|
+
// emits a tool call as plain text tags inside `text_delta` instead of a
|
|
120
|
+
// native `tool_use` block. `leakBuffer` is a minimal rolling window that
|
|
121
|
+
// only holds back text when a partial sentinel prefix is present, so a
|
|
122
|
+
// tag split across chunk boundaries is still detected while ordinary text
|
|
123
|
+
// still streams promptly. The guard is additive: the native tool_use path
|
|
124
|
+
// (content_block_start/input_json_delta/content_block_stop) is untouched.
|
|
125
|
+
const _knownTools = knownToolNames instanceof Set
|
|
126
|
+
? knownToolNames
|
|
127
|
+
: new Set(Array.isArray(knownToolNames) ? knownToolNames : []);
|
|
128
|
+
const _leakGuardEnabled = _knownTools.size > 0;
|
|
129
|
+
const _isKnownTool = (name) => _knownTools.has(name);
|
|
130
|
+
let leakBuffer = '';
|
|
131
|
+
// Running markdown fence/inline-code state threaded across text_delta
|
|
132
|
+
// chunks (Fix 1): a tool-call tag inside a ```code fence``` or inline span
|
|
133
|
+
// is a doc example, not a real call — the guard emits it as text.
|
|
134
|
+
let leakFenceState = undefined;
|
|
135
|
+
// Cross-path fingerprint dedupe (Fix 2): a synthesized text-leaked call and
|
|
136
|
+
// an identical native tool_use block must dispatch onToolCall exactly once.
|
|
137
|
+
const _toolDedupe = createToolCallDedupe();
|
|
138
|
+
|
|
139
|
+
// Synthesize + dispatch a recovered leaked call exactly like the native
|
|
140
|
+
// content_block_stop path (push into toolCalls, flag state, eager
|
|
141
|
+
// onToolCall). A generated id uses the same `toolu_`-prefixed shape as
|
|
142
|
+
// Anthropic's native tool-call ids.
|
|
143
|
+
const dispatchLeakedCall = (recovered) => {
|
|
144
|
+
let args = recovered?.arguments;
|
|
145
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
146
|
+
// Skip if an identical native (or prior synthetic) call already fired.
|
|
147
|
+
if (!_toolDedupe.shouldDispatch(recovered.name, args)) return;
|
|
148
|
+
const call = {
|
|
149
|
+
id: `toolu_leaked_${randomBytes(8).toString('hex')}`,
|
|
150
|
+
name: recovered.name,
|
|
151
|
+
arguments: args,
|
|
152
|
+
};
|
|
153
|
+
toolCalls.push(call);
|
|
154
|
+
if (state) state.emittedToolCall = true;
|
|
155
|
+
try { onToolCall?.(call); } catch {}
|
|
156
|
+
try { onStreamDelta?.(); } catch {}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Feed accumulated text through the scanner. On `final` nothing is held
|
|
160
|
+
// back so legitimate text is never lost at stream end.
|
|
161
|
+
const pumpLeakBuffer = (final) => {
|
|
162
|
+
if (!_leakGuardEnabled) return;
|
|
163
|
+
if (!leakBuffer && !final) return;
|
|
164
|
+
const { emit, calls, rest, fenceState } = scanLeakedToolCalls(leakBuffer, { isKnownTool: _isKnownTool, final, fenceState: leakFenceState });
|
|
165
|
+
leakBuffer = rest;
|
|
166
|
+
leakFenceState = fenceState;
|
|
167
|
+
if (emit) {
|
|
168
|
+
content += emit;
|
|
169
|
+
if (onTextDelta) {
|
|
170
|
+
if (state) state.emittedText = true;
|
|
171
|
+
try { onTextDelta(emit); } catch {}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const c of calls) dispatchLeakedCall(c);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Holds the in-flight reader.read() race rejector so the idle timer can
|
|
178
|
+
// force-unblock the loop even when reader.cancel() fails to settle the
|
|
179
|
+
// pending read (undici half-open socket). See resetIdleTimer below.
|
|
180
|
+
let idleReject = null;
|
|
181
|
+
|
|
182
|
+
const firstMessageTimeoutError = () => {
|
|
183
|
+
const err = new Error(`Anthropic OAuth SSE stream produced no message_start within ${SSE_FIRST_MESSAGE_TIMEOUT_MS}ms`);
|
|
184
|
+
err.code = 'EEMPTYSTREAM';
|
|
185
|
+
err.isEmptyStream = true;
|
|
186
|
+
err.firstByteTimeout = true;
|
|
187
|
+
return err;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const clearFirstMessageTimer = () => {
|
|
191
|
+
if (firstMessageTimer) {
|
|
192
|
+
clearTimeout(firstMessageTimer);
|
|
193
|
+
firstMessageTimer = null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const armFirstMessageTimer = () => {
|
|
198
|
+
if (!(SSE_FIRST_MESSAGE_TIMEOUT_MS > 0)) return;
|
|
199
|
+
clearFirstMessageTimer();
|
|
200
|
+
firstMessageTimer = setTimeout(() => {
|
|
201
|
+
if (state?.sawMessageStart) return;
|
|
202
|
+
firstMessageTimedOut = true;
|
|
203
|
+
const err = firstMessageTimeoutError();
|
|
204
|
+
try { abortStream?.(err); } catch (abortErr) {
|
|
205
|
+
try { process.stderr.write(`[anthropic-oauth] sse first-message abortStream failed: ${abortErr?.message ?? String(abortErr)}\n`); } catch {}
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const _c = reader.cancel('SSE first message timeout');
|
|
209
|
+
if (_c && typeof _c.catch === 'function') _c.catch(() => {});
|
|
210
|
+
} catch (cancelErr) {
|
|
211
|
+
try { process.stderr.write(`[anthropic-oauth] sse first-message cancel failed: ${cancelErr?.message ?? String(cancelErr)}\n`); } catch {}
|
|
212
|
+
}
|
|
213
|
+
if (idleReject) {
|
|
214
|
+
const r = idleReject; idleReject = null; r(err);
|
|
215
|
+
}
|
|
216
|
+
}, SSE_FIRST_MESSAGE_TIMEOUT_MS);
|
|
217
|
+
try { firstMessageTimer.unref?.(); } catch {}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Attach the partial stream state to a mid-stream stall error so the agent
|
|
221
|
+
// loop can decide SUCCESS vs FAILURE. The recurring "worker finished but
|
|
222
|
+
// owner never notified" case is a FINAL no-tool summary stream that wedges
|
|
223
|
+
// ping-only after the real work (tool calls) already completed in earlier
|
|
224
|
+
// iterations: there is streamed `content`, no pending tool_use, and no
|
|
225
|
+
// emitted tool call this iteration. The loop treats that as a successful
|
|
226
|
+
// partial-final (deliver the summary we have) instead of dropping it. A
|
|
227
|
+
// stall WITH a pending/emitted tool call stays a hard failure (a tool whose
|
|
228
|
+
// input never completed must never be reported as done).
|
|
229
|
+
const _attachStallPartial = (err) => {
|
|
230
|
+
try {
|
|
231
|
+
err.partialContent = content;
|
|
232
|
+
err.partialToolCalls = toolCalls.length ? toolCalls.slice() : undefined;
|
|
233
|
+
err.pendingToolUse = pendingToolInputs.size > 0;
|
|
234
|
+
err.partialModel = model || undefined;
|
|
235
|
+
err.partialUsage = usage;
|
|
236
|
+
err.partialStopReason = stopReason || undefined;
|
|
237
|
+
err.partialHasThinking = hasThinkingContent;
|
|
238
|
+
} catch { /* best-effort enrichment */ }
|
|
239
|
+
return err;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const resetIdleTimer = () => {
|
|
243
|
+
// OFF by default. When disabled the
|
|
244
|
+
// idle timer never arms, so the stream is never killed on inactivity;
|
|
245
|
+
// the agent stall watchdog (600s) remains the dead-stream backstop.
|
|
246
|
+
if (!PROVIDER_SSE_IDLE_WATCHDOG_ENABLED) return;
|
|
247
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
248
|
+
idleTimer = setTimeout(() => {
|
|
249
|
+
idleTimedOut = true;
|
|
250
|
+
try { abortStream?.(); } catch (err) {
|
|
251
|
+
try { process.stderr.write(`[anthropic-oauth] sse idle abortStream failed: ${err?.message ?? String(err)}\n`); } catch {}
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const _c = reader.cancel('SSE idle timeout');
|
|
255
|
+
if (_c && typeof _c.catch === 'function') _c.catch(() => {});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
try { process.stderr.write(`[anthropic-oauth] sse idle cancel failed: ${err?.message ?? String(err)}\n`); } catch {}
|
|
258
|
+
}
|
|
259
|
+
// Force-reject the in-flight reader.read() race even when reader.cancel()
|
|
260
|
+
// fails to settle the pending read: without this the await below stays
|
|
261
|
+
// pending forever and the SSE idle timeout never unblocks the loop —
|
|
262
|
+
// the 391s-hang root cause.
|
|
263
|
+
if (idleReject) {
|
|
264
|
+
const e = _attachStallPartial(streamStalledError('Anthropic OAuth SSE', SSE_IDLE_TIMEOUT_MS, { emittedToolCall: !!state?.emittedToolCall }));
|
|
265
|
+
const r = idleReject; idleReject = null; r(e);
|
|
266
|
+
}
|
|
267
|
+
// Shared provider policy: short SEMANTIC-event inactivity catches the
|
|
268
|
+
// ping-only wedge where SSE starts, emits some deltas, then goes silent
|
|
269
|
+
// while `:ping` keepalives keep the transport socket warm.
|
|
270
|
+
}, SSE_IDLE_TIMEOUT_MS);
|
|
271
|
+
try { idleTimer.unref?.(); } catch {}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const onAbort = () => {
|
|
275
|
+
try {
|
|
276
|
+
const _c = reader.cancel('SSE aborted');
|
|
277
|
+
if (_c && typeof _c.catch === 'function') _c.catch(() => {});
|
|
278
|
+
} catch {}
|
|
279
|
+
};
|
|
280
|
+
if (signal) {
|
|
281
|
+
if (signal.aborted) {
|
|
282
|
+
_captureMidstreamAbort(state, signal.reason);
|
|
283
|
+
throw signal.reason instanceof Error
|
|
284
|
+
? signal.reason
|
|
285
|
+
: new Error('Anthropic OAuth SSE stream aborted');
|
|
286
|
+
}
|
|
287
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
// Part A / reviewer fix: do NOT arm the SEMANTIC idle timer before the
|
|
292
|
+
// stream has produced its first event. A slow first response is governed
|
|
293
|
+
// by armFirstMessageTimer() (first-byte window) alone; arming the
|
|
294
|
+
// semantic idle here could let it win and mis-abort a legitimately slow
|
|
295
|
+
// first response as a stall. The semantic idle is first armed at
|
|
296
|
+
// `message_start` (see below), so it only ever guards MID-stream silence.
|
|
297
|
+
armFirstMessageTimer();
|
|
298
|
+
streamLoop: while (true) {
|
|
299
|
+
let chunk;
|
|
300
|
+
try {
|
|
301
|
+
// Race the read against the idle timer's rejector so a stuck
|
|
302
|
+
// reader.read() (cancel did not settle it) still unblocks here.
|
|
303
|
+
chunk = await new Promise((resolve, reject) => {
|
|
304
|
+
idleReject = reject;
|
|
305
|
+
reader.read().then(resolve, reject);
|
|
306
|
+
});
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if (idleTimedOut) {
|
|
309
|
+
throw _attachStallPartial(streamStalledError('Anthropic OAuth SSE', SSE_IDLE_TIMEOUT_MS, { emittedToolCall: !!state?.emittedToolCall }));
|
|
310
|
+
}
|
|
311
|
+
if (firstMessageTimedOut) {
|
|
312
|
+
throw firstMessageTimeoutError();
|
|
313
|
+
}
|
|
314
|
+
if (signal?.aborted) {
|
|
315
|
+
_captureMidstreamAbort(state, signal.reason);
|
|
316
|
+
throw signal.reason instanceof Error
|
|
317
|
+
? signal.reason
|
|
318
|
+
: new Error('Anthropic OAuth SSE stream aborted');
|
|
319
|
+
}
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
322
|
+
const { done, value } = chunk;
|
|
323
|
+
if (done) break;
|
|
324
|
+
|
|
325
|
+
buffer += decoder.decode(value, { stream: true });
|
|
326
|
+
const lines = buffer.split('\n');
|
|
327
|
+
buffer = lines.pop() || '';
|
|
328
|
+
|
|
329
|
+
for (const line of lines) {
|
|
330
|
+
if (line.startsWith(':')) {
|
|
331
|
+
// SSE comment frame (Anthropic `:ping` keepalive). Keep it
|
|
332
|
+
// at transport level only: comments must not refresh the
|
|
333
|
+
// agent's semantic progress timestamp, or a ping-only 200
|
|
334
|
+
// can look alive forever without message_start/content.
|
|
335
|
+
// Crucially it also does NOT reset the SEMANTIC idle timer
|
|
336
|
+
// below — a ping-only wedge must trip the idle abort.
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
// Blank lines are SSE record separators — emitted after EVERY
|
|
340
|
+
// frame, including `:ping` keepalives — so they are NOT semantic
|
|
341
|
+
// progress and must not reset the idle timer (else a ping frame's
|
|
342
|
+
// trailing blank would keep a wedge alive forever).
|
|
343
|
+
if (line === '') continue;
|
|
344
|
+
if (line.startsWith('event: ')) {
|
|
345
|
+
currentEvent = line.slice(7).trim();
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (!line.startsWith('data: ')) continue;
|
|
349
|
+
const data = line.slice(6).trim();
|
|
350
|
+
if (!data) continue;
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const event = JSON.parse(data);
|
|
354
|
+
|
|
355
|
+
// SEMANTIC idle reset (Part A): reset the idle timer ONLY for
|
|
356
|
+
// real progress events, NOT for Anthropic keepalives. Anthropic
|
|
357
|
+
// sends pings as a NAMED event (`event: ping` /
|
|
358
|
+
// `data: {"type":"ping"}`), not just `:` comment frames, so a
|
|
359
|
+
// named ping must be excluded here or a ping-only wedge keeps
|
|
360
|
+
// the timer alive forever. Everything that is not a ping is a
|
|
361
|
+
// genuine server event (message_start/content/tool/thinking
|
|
362
|
+
// deltas, message_delta/stop, errors) and counts as progress.
|
|
363
|
+
if (currentEvent !== 'ping' && event?.type !== 'ping') {
|
|
364
|
+
resetIdleTimer();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (currentEvent === 'error' || event?.type === 'error' || event?.error) {
|
|
368
|
+
throw _anthropicSseError(event);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (event.type === 'message_start' && event.message) {
|
|
372
|
+
clearFirstMessageTimer();
|
|
373
|
+
if (state) state.sawMessageStart = true;
|
|
374
|
+
if (event.message.model) model = event.message.model;
|
|
375
|
+
if (event.message.usage) {
|
|
376
|
+
usage.inputTokens = event.message.usage.input_tokens || 0;
|
|
377
|
+
usage.cachedTokens = event.message.usage.cache_read_input_tokens || 0;
|
|
378
|
+
usage.cacheWriteTokens = event.message.usage.cache_creation_input_tokens || 0;
|
|
379
|
+
usage.raw = { ...event.message.usage };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (event.type === 'content_block_start') {
|
|
384
|
+
const block = event.content_block;
|
|
385
|
+
if (block?.type === 'tool_use') {
|
|
386
|
+
pendingToolInputs.set(event.index, {
|
|
387
|
+
id: block.id || '',
|
|
388
|
+
name: block.name || '',
|
|
389
|
+
inputJson: '',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (event.type === 'content_block_delta') {
|
|
395
|
+
const delta = event.delta;
|
|
396
|
+
if (delta?.type) contentBlockTypes.add(delta.type);
|
|
397
|
+
// Time-to-first-token: stamp the first content delta
|
|
398
|
+
// (text / thinking / tool input_json) exactly once so
|
|
399
|
+
// the SSE trace can separate first-byte latency from
|
|
400
|
+
// total stream/generation time. Without this stamp
|
|
401
|
+
// ttftMs was always null and reported as 0ms.
|
|
402
|
+
if (state && !state.ttftAt) state.ttftAt = Date.now();
|
|
403
|
+
if (delta?.type === 'text_delta') {
|
|
404
|
+
try { onStreamDelta?.(); } catch {}
|
|
405
|
+
// Live text relay (gateway): forward the explicit
|
|
406
|
+
// text chunk. thinking/signature/input_json deltas
|
|
407
|
+
// intentionally stay off this path.
|
|
408
|
+
// Invariant: once a non-empty chunk has been relayed
|
|
409
|
+
// live it cannot be withdrawn, so flag the attempt so
|
|
410
|
+
// the mid-stream retry loop treats any later failure
|
|
411
|
+
// as final (a retry would concatenate attempts).
|
|
412
|
+
if (_leakGuardEnabled) {
|
|
413
|
+
// Route text through the leaked-tool-call guard.
|
|
414
|
+
// It appends to `content`, forwards visible text
|
|
415
|
+
// via onTextDelta, and synthesizes/dispatches any
|
|
416
|
+
// recovered known-tool call — suppressing the
|
|
417
|
+
// tags from the visible stream. A partial sentinel
|
|
418
|
+
// is held in leakBuffer until the next chunk.
|
|
419
|
+
leakBuffer += delta.text || '';
|
|
420
|
+
pumpLeakBuffer(false);
|
|
421
|
+
} else {
|
|
422
|
+
content += delta.text || '';
|
|
423
|
+
if (delta.text && onTextDelta) {
|
|
424
|
+
if (state) state.emittedText = true;
|
|
425
|
+
try { onTextDelta(delta.text); } catch {}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') {
|
|
430
|
+
// Extended-thinking block: provider reasoning without
|
|
431
|
+
// user-visible text. Track presence so a final turn
|
|
432
|
+
// that emitted ONLY thinking (no text_delta, no
|
|
433
|
+
// tool_use) can be classified by the loop as
|
|
434
|
+
// synthesis-stalled rather than silent empty.
|
|
435
|
+
hasThinkingContent = true;
|
|
436
|
+
try { onStreamDelta?.(); } catch {}
|
|
437
|
+
}
|
|
438
|
+
if (delta?.type === 'input_json_delta') {
|
|
439
|
+
const pending = pendingToolInputs.get(event.index);
|
|
440
|
+
if (pending) {
|
|
441
|
+
pending.inputJson += delta.partial_json || '';
|
|
442
|
+
}
|
|
443
|
+
try { onStreamDelta?.(); } catch {}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (event.type === 'content_block_stop') {
|
|
448
|
+
const pending = pendingToolInputs.get(event.index);
|
|
449
|
+
if (pending) {
|
|
450
|
+
// Bare JSON.parse threw straight up into the
|
|
451
|
+
// surrounding broad catch, which swallowed the
|
|
452
|
+
// whole tool_call — the loop never saw it and
|
|
453
|
+
// the assistant turn ended with an unmatched
|
|
454
|
+
// tool_use id. Wrap the parse so a malformed
|
|
455
|
+
// input still produces a tool_call (with an
|
|
456
|
+
// invalid-args marker and a logged error) instead
|
|
457
|
+
// of a silent drop or accidental `{}` dispatch.
|
|
458
|
+
let parsedArgs = {};
|
|
459
|
+
if (pending.inputJson) {
|
|
460
|
+
try { parsedArgs = JSON.parse(pending.inputJson); }
|
|
461
|
+
catch (parseErr) {
|
|
462
|
+
process.stderr.write(`[anthropic-oauth] tool args JSON.parse failed (id=${pending.id}, name=${pending.name}): ${parseErr?.message || parseErr}\n`);
|
|
463
|
+
parsedArgs = makeInvalidToolArgsMarker(pending.inputJson, parseErr instanceof Error ? parseErr.message : String(parseErr));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Tool arguments must be a plain object. Anthropic's
|
|
467
|
+
// tool_use input is always a JSON object, but a
|
|
468
|
+
// malformed stream could parse to an array/string/
|
|
469
|
+
// number — wrap those as {} to keep the contract
|
|
470
|
+
// (invariant-based, no heuristic coercion).
|
|
471
|
+
if (parsedArgs === null
|
|
472
|
+
|| typeof parsedArgs !== 'object'
|
|
473
|
+
|| Array.isArray(parsedArgs)) {
|
|
474
|
+
process.stderr.write(`[anthropic-oauth] tool args not a plain object (id=${pending.id}, name=${pending.name}, type=${Array.isArray(parsedArgs) ? 'array' : typeof parsedArgs}); using {}\n`);
|
|
475
|
+
parsedArgs = {};
|
|
476
|
+
}
|
|
477
|
+
const call = {
|
|
478
|
+
id: pending.id,
|
|
479
|
+
name: pending.name,
|
|
480
|
+
arguments: parsedArgs,
|
|
481
|
+
};
|
|
482
|
+
pendingToolInputs.delete(event.index);
|
|
483
|
+
// Eager dispatch: let the loop start this tool
|
|
484
|
+
// before message_stop arrives. The loop keys
|
|
485
|
+
// pending promises by call.id so order is safe.
|
|
486
|
+
// Fix 2: skip the ENTIRE call (push + dispatch) when a
|
|
487
|
+
// text-leaked synthetic of the same (name,args) already
|
|
488
|
+
// fired — otherwise the duplicate stays in `toolCalls`
|
|
489
|
+
// and the loop executes the side-effecting tool twice.
|
|
490
|
+
// An invalid-args marker never fingerprint-collides with
|
|
491
|
+
// a real recovered call, so malformed native calls still
|
|
492
|
+
// dispatch (the marker path is unaffected).
|
|
493
|
+
if (_toolDedupe.shouldDispatch(call.name, call.arguments)) {
|
|
494
|
+
toolCalls.push(call);
|
|
495
|
+
if (state) state.emittedToolCall = true;
|
|
496
|
+
// Eager dispatch: let the loop start this tool
|
|
497
|
+
// before message_stop arrives. The loop keys
|
|
498
|
+
// pending promises by call.id so order is safe.
|
|
499
|
+
try { onToolCall?.(call); } catch {}
|
|
500
|
+
}
|
|
501
|
+
try { onStreamDelta?.(); } catch {}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (event.type === 'message_delta') {
|
|
506
|
+
if (event.delta?.stop_reason) {
|
|
507
|
+
stopReason = event.delta.stop_reason;
|
|
508
|
+
}
|
|
509
|
+
if (event.usage) {
|
|
510
|
+
usage.outputTokens = event.usage.output_tokens || 0;
|
|
511
|
+
usage.raw = { ...(usage.raw || {}), ...event.usage };
|
|
512
|
+
}
|
|
513
|
+
if (stopReason === 'tool_use' && toolCalls.length > 0 && pendingToolInputs.size === 0) {
|
|
514
|
+
if (state) state.sawCompleted = true;
|
|
515
|
+
break streamLoop;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (event.type === 'message_stop') {
|
|
519
|
+
if (state) state.sawCompleted = true;
|
|
520
|
+
// Anthropic streams can keep emitting `:ping` keepalive
|
|
521
|
+
// frames after `message_stop`; if we wait for EOF the
|
|
522
|
+
// outer reader.read() loop hangs indefinitely. Break
|
|
523
|
+
// out of streamLoop the moment the message ends.
|
|
524
|
+
break streamLoop;
|
|
525
|
+
}
|
|
526
|
+
// Unified prompt volume — what the model actually ingested.
|
|
527
|
+
// Anthropic splits input into three billable slots (uncached
|
|
528
|
+
// input + cache_read + cache_create); keep them separate for
|
|
529
|
+
// cost math but also expose the sum so cross-provider logs
|
|
530
|
+
// have a consistent `promptTokens` meaning.
|
|
531
|
+
usage.promptTokens = (usage.inputTokens || 0)
|
|
532
|
+
+ (usage.cachedTokens || 0)
|
|
533
|
+
+ (usage.cacheWriteTokens || 0);
|
|
534
|
+
} catch (err) {
|
|
535
|
+
if (err?.code === 'EANTHROPIC_SSE_ERROR') throw err;
|
|
536
|
+
/* skip malformed events */
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Stream ended: flush any held-back leaked-tool-call buffer. `final`
|
|
542
|
+
// holds nothing back, so a trailing partial sentinel that never
|
|
543
|
+
// resolved into a real call is surfaced as ordinary text — legitimate
|
|
544
|
+
// user-visible content is never lost on the failure path.
|
|
545
|
+
pumpLeakBuffer(true);
|
|
546
|
+
|
|
547
|
+
// Truncated-stream guard: if the reader loop exited (EOF or break)
|
|
548
|
+
// after message_start but without seeing message_stop / a tool_use
|
|
549
|
+
// stop_reason, the assistant turn was cut off mid-flight. Returning
|
|
550
|
+
// success here would silently surface partial content (or a partially
|
|
551
|
+
// streamed tool_use whose input_json never completed) as final.
|
|
552
|
+
// Throw a typed truncated-stream error so the loop can decide whether
|
|
553
|
+
// to retry, surface, or escalate instead of accepting the partial.
|
|
554
|
+
if (state?.sawMessageStart && !state?.sawCompleted) {
|
|
555
|
+
const pendingToolUse = pendingToolInputs.size > 0;
|
|
556
|
+
const err = Object.assign(
|
|
557
|
+
new Error(
|
|
558
|
+
`Anthropic OAuth SSE stream truncated: message_start without message_stop`
|
|
559
|
+
+ (pendingToolUse ? ` (pending tool_use input)` : ''),
|
|
560
|
+
),
|
|
561
|
+
{
|
|
562
|
+
name: 'TruncatedStreamError',
|
|
563
|
+
code: 'TRUNCATED_STREAM',
|
|
564
|
+
truncatedStream: true,
|
|
565
|
+
pendingToolUse,
|
|
566
|
+
stopReason,
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
content,
|
|
574
|
+
model,
|
|
575
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
576
|
+
usage,
|
|
577
|
+
stopReason,
|
|
578
|
+
hasThinkingContent,
|
|
579
|
+
contentBlockTypes: Array.from(contentBlockTypes),
|
|
580
|
+
};
|
|
581
|
+
} finally {
|
|
582
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
583
|
+
clearFirstMessageTimer();
|
|
584
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
585
|
+
try { reader.releaseLock(); } catch (err) {
|
|
586
|
+
try { process.stderr.write(`[anthropic-oauth] reader releaseLock failed: ${err?.message ?? String(err)}\n`); } catch {}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Classify an Anthropic SSE failure for single-shot mid-stream retry.
|
|
593
|
+
*
|
|
594
|
+
* Retry is allowed only after `message_start` and before `message_stop`,
|
|
595
|
+
* and only when no tool call has already been surfaced to the loop.
|
|
596
|
+
* That keeps recovery limited to transport/stream stalls without risking
|
|
597
|
+
* duplicate eager tool execution.
|
|
598
|
+
*/
|
|
599
|
+
// Thin wrapper: the SSE mid-stream decision tree now lives in the shared
|
|
600
|
+
// classifyMidstreamError (retry-classifier.mjs, policy.mode='sse'). Kept as a
|
|
601
|
+
// named export so internal call sites AND anthropic.mjs (which imports this
|
|
602
|
+
// symbol) keep resolving it. Behavior is byte-identical — the shared function
|
|
603
|
+
// is the relocated original, gated by SSE_MIDSTREAM_POLICY (defaultRetries=3,
|
|
604
|
+
// perClassifierGate:false).
|
|
605
|
+
export function _classifyMidstreamError(err, state) {
|
|
606
|
+
return classifyMidstreamError(err, state, SSE_MIDSTREAM_POLICY);
|
|
607
|
+
}
|