mixdog 0.9.3 → 0.9.5
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/README.md +112 -38
- package/package.json +10 -3
- 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/r5-orchestrated-task.json +7 -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-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -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 +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- 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/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- 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/providers/anthropic-effort.mjs +62 -20
- 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 +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- 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 +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- 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 +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -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/provider-catalog-cache.mjs +80 -0
- 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-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -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/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- 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 +20 -1
- 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 +35 -44
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- 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 -92
- 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 +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- 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 +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- 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 +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -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/scheduler.mjs +27 -113
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- 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 +313 -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 +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -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/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/ko-morph.mjs +195 -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 +24 -842
- 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-store.mjs +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -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 +5 -13
- 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/atomic-file.mjs +26 -1
- package/src/runtime/shared/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- 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 +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +119 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -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 +11 -9
- 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 +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +1 -1
- 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 +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- 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 +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -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 +356 -0
- package/src/tui/app/model-options.mjs +334 -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 +489 -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 +677 -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 +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- 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/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +13553 -12384
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -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.mjs +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- 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 -501
- 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 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- 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/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/channels/lib/holidays.mjs +0 -138
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- 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/input-editing.selection.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/paste-fix.test.mjs +0 -119
- 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
|
@@ -13,11 +13,17 @@ import {
|
|
|
13
13
|
normalizeCompactType,
|
|
14
14
|
DEFAULT_COMPACT_TYPE,
|
|
15
15
|
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
16
|
+
CONTEXT_SHARE_RATIO,
|
|
16
17
|
} from '../compact.mjs';
|
|
17
18
|
import { positiveTokenInt, envFlag, envTokenInt } from './env.mjs';
|
|
19
|
+
import { isAgentOwner } from '../../agent-owner.mjs';
|
|
18
20
|
|
|
19
21
|
const COMPACT_SAFETY_PERCENT = 1.00;
|
|
20
|
-
|
|
22
|
+
// Unified context-share rule (compact/constants.mjs CONTEXT_SHARE_RATIO): the
|
|
23
|
+
// post-compaction target is 5% of the boundary/context window — the same 5%
|
|
24
|
+
// the recall-fasttrack injection cap uses (loop.mjs recallTokenCap). One
|
|
25
|
+
// number governs every "share of model context" budget.
|
|
26
|
+
const COMPACT_TARGET_RATIO = CONTEXT_SHARE_RATIO;
|
|
21
27
|
const COMPACT_TARGET_MIN_TOKENS = 4_000;
|
|
22
28
|
const COMPACT_TARGET_MAX_TOKENS = 16_000;
|
|
23
29
|
|
|
@@ -30,7 +36,13 @@ function resolveSemanticCompactSetting(sessionRef, cfg = {}) {
|
|
|
30
36
|
return true;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
function resolveCompactTypeSetting(
|
|
39
|
+
function resolveCompactTypeSetting(sessionRef, cfg = {}) {
|
|
40
|
+
// Agent-owned sessions are ALWAYS semantic. recall-fasttrack rebuilds
|
|
41
|
+
// context from Memory recall, which is scoped to the user's main-session
|
|
42
|
+
// history — an agent's tool-loop history is not in the recall pool, so a
|
|
43
|
+
// fasttrack compact would inject unrelated main-session memories and drop
|
|
44
|
+
// the agent's own working context. Env/config overrides do not apply.
|
|
45
|
+
if (isAgentOwner(sessionRef)) return DEFAULT_COMPACT_TYPE;
|
|
34
46
|
const configured = process.env.MIXDOG_AGENT_COMPACT_TYPE
|
|
35
47
|
?? process.env.MIXDOG_COMPACT_TYPE
|
|
36
48
|
?? cfg.type
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Completion-first loop guards: escalation ladder (level-2 steering),
|
|
2
|
+
// cross-turn identical read-only call dedup, and worker soft-cap wrap-up.
|
|
3
|
+
// Pure string/signature helpers extracted from loop.mjs so the loop body only
|
|
4
|
+
// wires state + messages. No provider/manager coupling.
|
|
5
|
+
|
|
6
|
+
// Deterministic, key-sorted stringify for cross-turn call signatures. Mirrors
|
|
7
|
+
// _canonicalArgs but exposed by name for the dedup signature contract.
|
|
8
|
+
export function stableStringify(value) {
|
|
9
|
+
if (value == null || typeof value !== 'object') {
|
|
10
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
try { return `[${value.map(stableStringify).join(',')}]`; } catch { return String(value); }
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const keys = Object.keys(value).sort();
|
|
17
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
|
|
18
|
+
} catch { return String(value); }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function crossTurnSignature(name, args) {
|
|
22
|
+
return `${name}:${stableStringify(args)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Tool names that are non-eager (no readOnlyHint) but are NOT edits/progress —
|
|
26
|
+
// they must not reset the escalation ladder's "zero edit" condition. Skill /
|
|
27
|
+
// recall / agent / task / cwd / tool_search are exploration/meta plumbing.
|
|
28
|
+
const NON_PROGRESS_TOOLS = new Set(['Skill', 'recall', 'agent', 'task', 'cwd', 'tool_search']);
|
|
29
|
+
|
|
30
|
+
// True when a successfully-executed tool represents real edit/progress. A tool
|
|
31
|
+
// counts as progress only if its def lacks readOnlyHint (not eager) AND it is
|
|
32
|
+
// not in the meta/non-progress set. apply_patch and shell/bash always count.
|
|
33
|
+
export function isEditProgressTool(name, isEager) {
|
|
34
|
+
if (isEager) return false;
|
|
35
|
+
const bare = name && name.startsWith('mcp__') ? name.split('__').pop() : name;
|
|
36
|
+
if (bare === 'apply_patch' || bare === 'shell' || bare === 'bash' || bare === 'bash_session') return true;
|
|
37
|
+
return !NON_PROGRESS_TOOLS.has(bare);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Step 1 — level-2 escalation steering. N = cumulative level-1 fires.
|
|
41
|
+
// `readOnlyRole` swaps the edit-oriented directive for a report-oriented one:
|
|
42
|
+
// read-permission sessions (reviewer-style) cannot apply_patch, so telling
|
|
43
|
+
// them to edit is self-contradictory and pushes premature termination.
|
|
44
|
+
export function level2SteerMessage(n, readOnlyRole = false) {
|
|
45
|
+
if (readOnlyRole) {
|
|
46
|
+
return `<system-reminder>\nYou have received this batching reminder ${n} times. Converge now: report your findings from what you have already read, or state exactly what information is missing for a verdict. A partial report with named gaps is a valid, successful completion — continued exploration is not.\n</system-reminder>`;
|
|
47
|
+
}
|
|
48
|
+
return `<system-reminder>\nYou have received this batching reminder ${n} times without making any edit. Stop exploring now: either apply_patch with what you already know, or return a blocked report stating exactly what is missing. A blocked report is a valid, successful completion — continued exploration is not.\n</system-reminder>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Step 2 — cross-turn dedup stub. `stuck` appends the escalation tail at the
|
|
52
|
+
// 5th+ dedup stub in the session.
|
|
53
|
+
export function crossTurnDedupStub(name, firstIteration, stuck) {
|
|
54
|
+
let s = `[cross-turn-dedup] identical read-only \`${name}\` call already executed in iteration ${firstIteration}; its result is unchanged and already in context. Use it, or change path/offset/pattern for new information.`;
|
|
55
|
+
if (stuck) s += ` Repeated identical calls indicate you are stuck — apply what you know or return blocked.`;
|
|
56
|
+
return s;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 3 — worker soft-cap wrap-up assistant-visible directive + refusal stub.
|
|
60
|
+
export const SOFT_CAP_WRAPUP_MESSAGE = `MAXIMUM EXPLORATION BUDGET REACHED. Tools are now disabled. Respond with text only: summarize work completed so far, list remaining/incomplete items, and state what is blocking or what should be done next. This overrides all other instructions.`;
|
|
61
|
+
export const SOFT_CAP_REFUSAL_STUB = `Tools are disabled: exploration budget reached. Provide your final text summary.`;
|
|
@@ -20,9 +20,7 @@ const WORKER_DENIED_TOOLS = new Set([
|
|
|
20
20
|
// session control.
|
|
21
21
|
'agent',
|
|
22
22
|
// channels module (owner/Discord-facing)
|
|
23
|
-
'reply', '
|
|
24
|
-
'schedule_status', 'trigger_schedule', 'schedule_control',
|
|
25
|
-
'activate_channel_bridge', 'reload_config', 'inject_command',
|
|
23
|
+
'reply', 'fetch',
|
|
26
24
|
// host input injection
|
|
27
25
|
'inject_input',
|
|
28
26
|
]);
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Recall-fasttrack compaction pipeline, extracted from loop.mjs.
|
|
2
|
+
// Hydrates the session transcript into the memory pipeline (ingest_session),
|
|
3
|
+
// dumps chunked/raw roots, drains cycle1 until no raw rows remain, and folds
|
|
4
|
+
// the combined recall text back into a compacted message array capped at
|
|
5
|
+
// CONTEXT_SHARE_RATIO of the model context window. No behavior change: this is
|
|
6
|
+
// the same body that lived inline in loop.mjs, re-exported via the facade so
|
|
7
|
+
// existing importers keep working.
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
import { executeInternalTool } from '../../internal-tools.mjs';
|
|
10
|
+
import { loadConfig as loadOrchestratorConfig } from '../../config.mjs';
|
|
11
|
+
import {
|
|
12
|
+
recallFastTrackCompactMessages,
|
|
13
|
+
CONTEXT_SHARE_RATIO,
|
|
14
|
+
RECALL_TOKEN_CAP_FLOOR_TOKENS,
|
|
15
|
+
drainSessionCycle1,
|
|
16
|
+
countRawPendingRows,
|
|
17
|
+
} from '../compact.mjs';
|
|
18
|
+
import {
|
|
19
|
+
compactDiagnosticError,
|
|
20
|
+
compactByteLength,
|
|
21
|
+
compactDebugLog,
|
|
22
|
+
} from './compact-debug.mjs';
|
|
23
|
+
import { positiveTokenInt } from './env.mjs';
|
|
24
|
+
import { TOOL_OUTPUT_MAX_BYTES } from '../../tools/builtin/tool-output-limit.mjs';
|
|
25
|
+
|
|
26
|
+
// ── Digest mode (compaction.recallDigest=true) ─────────────────────────────
|
|
27
|
+
// Instead of folding the FULL chunked session dump into the compacted
|
|
28
|
+
// messages (heavy: cycle1 drain + up to CONTEXT_SHARE_RATIO of the context
|
|
29
|
+
// window), inject a small newest-first digest plus an instruction telling the
|
|
30
|
+
// model to pull details lazily via recall(sessionId/query/period). The memory
|
|
31
|
+
// DB already holds the full session (ingest_session below runs in both
|
|
32
|
+
// modes), and raw rows are embedded synchronously at ingest, so recall serves
|
|
33
|
+
// everything the big injection used to carry.
|
|
34
|
+
// Default digest cap = the SHARED tool-output limit (TOOL_OUTPUT_MAX_BYTES,
|
|
35
|
+
// 50KB default, env MIXDOG_TOOL_OUTPUT_MAX_BYTES) — the digest injection is
|
|
36
|
+
// budgeted like any other tool result, not a special context share.
|
|
37
|
+
// compaction.recallDigestMaxKb still overrides per-session.
|
|
38
|
+
const DIGEST_DEFAULT_MAX_KB = Math.max(1, Math.floor(TOOL_OUTPUT_MAX_BYTES / 1024));
|
|
39
|
+
|
|
40
|
+
// Byte-capped line-boundary truncation. Digest source is newest-first, so
|
|
41
|
+
// keeping the HEAD keeps the newest turns.
|
|
42
|
+
function truncateToKb(text, maxKb) {
|
|
43
|
+
const maxBytes = Math.max(1, maxKb) * 1024;
|
|
44
|
+
const s = String(text || '');
|
|
45
|
+
if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;
|
|
46
|
+
const lines = s.split('\n');
|
|
47
|
+
const out = [];
|
|
48
|
+
let used = 0;
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const cost = Buffer.byteLength(line, 'utf8') + 1;
|
|
51
|
+
if (used + cost > maxBytes) break;
|
|
52
|
+
out.push(line);
|
|
53
|
+
used += cost;
|
|
54
|
+
}
|
|
55
|
+
return out.join('\n') + '\n[digest truncated at ' + maxKb + 'KB — pull the rest via recall]';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildRecallDigestText(sessionId, digestBody, maxKb) {
|
|
59
|
+
// No recall-usage instruction block here: the recall tool description
|
|
60
|
+
// already carries the usage-pattern cheatsheet (tool-defs.mjs), so
|
|
61
|
+
// repeating it per-compaction would be redundant injected tokens. The
|
|
62
|
+
// one-line header marks the compaction boundary and names the session id
|
|
63
|
+
// the model needs for a scoped recall.
|
|
64
|
+
return [
|
|
65
|
+
`[context compacted — session ${sessionId}]`,
|
|
66
|
+
`Full history is in memory — use the recall tool for details beyond this digest.`,
|
|
67
|
+
`Recent digest (newest first):`,
|
|
68
|
+
truncateToKb(digestBody, maxKb),
|
|
69
|
+
].join('\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runRecallFastTrackCompact({ sessionRef, messages, compactBudgetTokens, compactPolicy, sessionId, signal }) {
|
|
73
|
+
if (!sessionId) throw new Error('recall-fasttrack requires a session id');
|
|
74
|
+
const startedAt = Date.now();
|
|
75
|
+
const diagnostics = {
|
|
76
|
+
hydrateLimit: null,
|
|
77
|
+
ingestMs: null,
|
|
78
|
+
ingestSkipped: false,
|
|
79
|
+
ingestError: null,
|
|
80
|
+
initialDumpMs: null,
|
|
81
|
+
initialDumpBytes: null,
|
|
82
|
+
initialDumpChars: null,
|
|
83
|
+
initialRawPending: null,
|
|
84
|
+
cycle1Ms: null,
|
|
85
|
+
cycle1Skipped: false,
|
|
86
|
+
cycle1SkipReason: null,
|
|
87
|
+
cycle1Passes: null,
|
|
88
|
+
cycle1RawRemaining: null,
|
|
89
|
+
cycle1TextBytes: null,
|
|
90
|
+
cycle1Error: null,
|
|
91
|
+
finalRecallBytes: null,
|
|
92
|
+
finalRecallChars: null,
|
|
93
|
+
totalMs: null,
|
|
94
|
+
};
|
|
95
|
+
const query = `session:${sessionId}:all-chunks`;
|
|
96
|
+
const querySha = createHash('sha256').update(query).digest('hex').slice(0, 16);
|
|
97
|
+
const callerCtx = {
|
|
98
|
+
callerSessionId: sessionId || null,
|
|
99
|
+
callerCwd: sessionRef?.cwd || undefined,
|
|
100
|
+
routingSessionId: sessionId || null,
|
|
101
|
+
clientHostPid: sessionRef?.clientHostPid,
|
|
102
|
+
signal: signal || null,
|
|
103
|
+
};
|
|
104
|
+
const hydrateLimit = positiveTokenInt(sessionRef?.compaction?.recallIngestLimit)
|
|
105
|
+
|| Math.max(500, Math.min(5000, messages.length || 0));
|
|
106
|
+
diagnostics.hydrateLimit = hydrateLimit;
|
|
107
|
+
let t0 = Date.now();
|
|
108
|
+
try {
|
|
109
|
+
await executeInternalTool('memory', {
|
|
110
|
+
action: 'ingest_session',
|
|
111
|
+
sessionId,
|
|
112
|
+
messages,
|
|
113
|
+
cwd: sessionRef?.cwd,
|
|
114
|
+
limit: hydrateLimit,
|
|
115
|
+
}, callerCtx);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
diagnostics.ingestSkipped = true;
|
|
118
|
+
diagnostics.ingestError = compactDiagnosticError(err);
|
|
119
|
+
try { process.stderr.write(`[loop] recall-fasttrack ingest skipped (sess=${sessionId || 'unknown'}): ${err?.message || err}\n`); } catch {}
|
|
120
|
+
} finally {
|
|
121
|
+
diagnostics.ingestMs = Date.now() - t0;
|
|
122
|
+
}
|
|
123
|
+
// ── Digest mode: skip the dump + cycle1 drain entirely. Pull a small
|
|
124
|
+
// newest-first session browse (recall path: roots + raw fallback merged
|
|
125
|
+
// chronologically), cap it at recallDigestMaxKb, and inject it with a
|
|
126
|
+
// recall-usage instruction. The model pulls older/topical detail lazily.
|
|
127
|
+
const digestMode = sessionRef?.compaction?.recallDigest === true;
|
|
128
|
+
if (digestMode) {
|
|
129
|
+
const digestMaxKb = positiveTokenInt(sessionRef?.compaction?.recallDigestMaxKb) || DIGEST_DEFAULT_MAX_KB;
|
|
130
|
+
let digestBody = '';
|
|
131
|
+
t0 = Date.now();
|
|
132
|
+
try {
|
|
133
|
+
const browsed = await executeInternalTool('memory', {
|
|
134
|
+
action: 'search',
|
|
135
|
+
sessionId,
|
|
136
|
+
limit: positiveTokenInt(sessionRef?.compaction?.recallDigestLimit) || 30,
|
|
137
|
+
includeMembers: true,
|
|
138
|
+
}, callerCtx);
|
|
139
|
+
digestBody = typeof browsed === 'string' ? browsed : String(browsed?.text ?? browsed ?? '');
|
|
140
|
+
} catch (err) {
|
|
141
|
+
diagnostics.cycle1Error = compactDiagnosticError(err);
|
|
142
|
+
try { process.stderr.write(`[loop] recall-digest browse failed (sess=${sessionId || 'unknown'}): ${err?.message || err}\n`); } catch {}
|
|
143
|
+
}
|
|
144
|
+
diagnostics.initialDumpMs = Date.now() - t0;
|
|
145
|
+
diagnostics.cycle1Skipped = true;
|
|
146
|
+
diagnostics.cycle1SkipReason = 'digest mode';
|
|
147
|
+
diagnostics.cycle1Passes = 0;
|
|
148
|
+
const digestText = buildRecallDigestText(sessionId, digestBody, digestMaxKb);
|
|
149
|
+
diagnostics.finalRecallChars = digestText.length;
|
|
150
|
+
diagnostics.finalRecallBytes = compactByteLength(digestText);
|
|
151
|
+
const result = recallFastTrackCompactMessages(messages, compactBudgetTokens, {
|
|
152
|
+
reserveTokens: compactPolicy.reserveTokens,
|
|
153
|
+
force: true,
|
|
154
|
+
recallText: digestText,
|
|
155
|
+
query,
|
|
156
|
+
querySha,
|
|
157
|
+
allowEmptyRecall: true,
|
|
158
|
+
tailTurns: compactPolicy.tailTurns,
|
|
159
|
+
keepTokens: compactPolicy.keepTokens,
|
|
160
|
+
preserveRecentTokens: compactPolicy.preserveRecentTokens,
|
|
161
|
+
});
|
|
162
|
+
diagnostics.totalMs = Date.now() - startedAt;
|
|
163
|
+
if (result && typeof result === 'object') {
|
|
164
|
+
result.diagnostics = { ...(result.diagnostics || {}), pipeline: { ...diagnostics, digestMode: true } };
|
|
165
|
+
}
|
|
166
|
+
compactDebugLog('recall-digest pipeline', diagnostics);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
const dumpArgs = {
|
|
170
|
+
action: 'dump_session_roots',
|
|
171
|
+
sessionId,
|
|
172
|
+
includeRaw: true,
|
|
173
|
+
limit: positiveTokenInt(sessionRef?.compaction?.recallChunkLimit ?? sessionRef?.compaction?.recallLimit) || hydrateLimit,
|
|
174
|
+
};
|
|
175
|
+
const runTool = (name, args) => executeInternalTool(name, args, callerCtx);
|
|
176
|
+
t0 = Date.now();
|
|
177
|
+
let recallText = await executeInternalTool('memory', dumpArgs, callerCtx);
|
|
178
|
+
diagnostics.initialDumpMs = Date.now() - t0;
|
|
179
|
+
diagnostics.initialDumpChars = String(recallText || '').length;
|
|
180
|
+
diagnostics.initialDumpBytes = compactByteLength(recallText);
|
|
181
|
+
diagnostics.initialRawPending = countRawPendingRows(recallText);
|
|
182
|
+
let cycle1Text = '';
|
|
183
|
+
const hasRawRows = /(?:^|\n)# raw_pending\s+\d+\s+id=/i.test(String(recallText || ''));
|
|
184
|
+
// Recap off = NO memory-pipeline LLM calls: skip the cycle1 drain entirely
|
|
185
|
+
// and let the dump's raw transcript lines (includeRaw:true above) ride into
|
|
186
|
+
// the injected summary as-is. Poll-on-use: re-read the flag per compact so
|
|
187
|
+
// a runtime toggle applies without restart. Default on if config read fails.
|
|
188
|
+
let recapOn = true;
|
|
189
|
+
try { recapOn = loadOrchestratorConfig({ secrets: false })?.recap?.enabled !== false; } catch { /* default on */ }
|
|
190
|
+
if (hasRawRows && !recapOn) {
|
|
191
|
+
diagnostics.cycle1Skipped = true;
|
|
192
|
+
diagnostics.cycle1SkipReason = 'recap disabled';
|
|
193
|
+
diagnostics.cycle1Passes = 0;
|
|
194
|
+
diagnostics.cycle1RawRemaining = countRawPendingRows(recallText);
|
|
195
|
+
cycle1Text = 'cycle1: skipped (recap disabled — raw transcript lines kept as-is)';
|
|
196
|
+
} else if (hasRawRows) {
|
|
197
|
+
t0 = Date.now();
|
|
198
|
+
try {
|
|
199
|
+
// Drain this session's cycle1 in window×concurrency units until no
|
|
200
|
+
// raw rows remain, so the injected root is fully chunked rather than
|
|
201
|
+
// carrying the unprocessed transcript tail (single-pass left raw in).
|
|
202
|
+
const drained = await drainSessionCycle1(runTool, {
|
|
203
|
+
sessionId,
|
|
204
|
+
dumpArgs,
|
|
205
|
+
deadlineMs: positiveTokenInt(sessionRef?.compaction?.recallCycle1DeadlineMs) || 120_000,
|
|
206
|
+
maxPasses: positiveTokenInt(sessionRef?.compaction?.recallCycle1MaxPasses) || 0,
|
|
207
|
+
cycleArgs: {
|
|
208
|
+
min_batch: 1,
|
|
209
|
+
session_cap: 1,
|
|
210
|
+
batch_size: positiveTokenInt(sessionRef?.compaction?.recallCycle1BatchSize) || 100,
|
|
211
|
+
rows_per_session: positiveTokenInt(sessionRef?.compaction?.recallRowsPerSession) || 100,
|
|
212
|
+
window_size: positiveTokenInt(sessionRef?.compaction?.recallWindowSize) || 20,
|
|
213
|
+
concurrency: positiveTokenInt(sessionRef?.compaction?.recallConcurrency) || 5,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
recallText = drained.recallText;
|
|
217
|
+
cycle1Text = drained.cycle1Text;
|
|
218
|
+
diagnostics.cycle1Passes = drained.passes;
|
|
219
|
+
diagnostics.cycle1RawRemaining = drained.rawRemaining;
|
|
220
|
+
diagnostics.cycle1TextBytes = compactByteLength(cycle1Text);
|
|
221
|
+
if (drained.error) {
|
|
222
|
+
diagnostics.cycle1Error = drained.error;
|
|
223
|
+
try { process.stderr.write(`[loop] recall-fasttrack cycle1 error (sess=${sessionId || 'unknown'}): ${drained.error}\n`); } catch {}
|
|
224
|
+
}
|
|
225
|
+
if (drained.rawRemaining > 0) {
|
|
226
|
+
try { process.stderr.write(`[loop] recall-fasttrack drained passes=${drained.passes} rawRemaining=${drained.rawRemaining} (sess=${sessionId || 'unknown'})\n`); } catch {}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
diagnostics.cycle1Error = compactDiagnosticError(err);
|
|
230
|
+
try { process.stderr.write(`[loop] recall-fasttrack cycle1 skipped (sess=${sessionId || 'unknown'}): ${err?.message || err}\n`); } catch {}
|
|
231
|
+
} finally {
|
|
232
|
+
diagnostics.cycle1Ms = Date.now() - t0;
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
diagnostics.cycle1Skipped = true;
|
|
236
|
+
diagnostics.cycle1SkipReason = 'session chunks already hydrated';
|
|
237
|
+
diagnostics.cycle1Passes = 0;
|
|
238
|
+
diagnostics.cycle1RawRemaining = 0;
|
|
239
|
+
cycle1Text = 'cycle1: skipped (session chunks already hydrated)';
|
|
240
|
+
}
|
|
241
|
+
const combinedRecallText = [`session_id=${sessionId}`, cycle1Text, recallText].map(v => String(v || '').trim()).filter(Boolean).join('\n\n');
|
|
242
|
+
diagnostics.finalRecallChars = combinedRecallText.length;
|
|
243
|
+
diagnostics.finalRecallBytes = compactByteLength(combinedRecallText);
|
|
244
|
+
// Recall injection (chunked-summary + raw-fallback text, combined above)
|
|
245
|
+
// never exceeds CONTEXT_SHARE_RATIO (5%) of the model context window
|
|
246
|
+
// (floor RECALL_TOKEN_CAP_FLOOR_TOKENS); the rest of the budget belongs to
|
|
247
|
+
// live conversation. Same unified 5% as the compact target ratio
|
|
248
|
+
// (compact-policy.mjs COMPACT_TARGET_RATIO). Omitted when contextWindow is
|
|
249
|
+
// unknown/0 so current no-cap behavior is preserved.
|
|
250
|
+
const _recallCapWindow = Number(compactPolicy.contextWindow) || 0;
|
|
251
|
+
const recallTokenCap = _recallCapWindow > 0
|
|
252
|
+
? Math.max(RECALL_TOKEN_CAP_FLOOR_TOKENS, Math.floor(_recallCapWindow * CONTEXT_SHARE_RATIO))
|
|
253
|
+
: undefined;
|
|
254
|
+
const result = recallFastTrackCompactMessages(messages, compactBudgetTokens, {
|
|
255
|
+
reserveTokens: compactPolicy.reserveTokens,
|
|
256
|
+
force: true,
|
|
257
|
+
recallText: combinedRecallText,
|
|
258
|
+
query,
|
|
259
|
+
querySha,
|
|
260
|
+
allowEmptyRecall: true,
|
|
261
|
+
tailTurns: compactPolicy.tailTurns,
|
|
262
|
+
keepTokens: compactPolicy.keepTokens,
|
|
263
|
+
preserveRecentTokens: compactPolicy.preserveRecentTokens,
|
|
264
|
+
recallTokenCap,
|
|
265
|
+
});
|
|
266
|
+
diagnostics.totalMs = Date.now() - startedAt;
|
|
267
|
+
if (result && typeof result === 'object') {
|
|
268
|
+
result.diagnostics = {
|
|
269
|
+
...(result.diagnostics || {}),
|
|
270
|
+
pipeline: diagnostics,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
compactDebugLog('recall-fasttrack pipeline', diagnostics);
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Completion-first steering ladder (worker runaway prevention), extracted from
|
|
2
|
+
// loop.mjs. Owns the mutable ladder counters and the post-batch steering-hint
|
|
3
|
+
// emitters. State is threaded live via a context object of getters/setters so
|
|
4
|
+
// no snapshot goes stale — the loop mutates `messages`/`iterations` in place and
|
|
5
|
+
// this module reads them through the accessors on each call. No behavior change:
|
|
6
|
+
// the counters, thresholds, and emitted messages are verbatim from agentLoop.
|
|
7
|
+
import { appendAgentTrace } from '../../agent-trace.mjs';
|
|
8
|
+
import { level2SteerMessage } from './completion-guards.mjs';
|
|
9
|
+
import { isEagerDispatchable } from './tool-helpers.mjs';
|
|
10
|
+
|
|
11
|
+
// Consecutive ignored level-2 steers (zero edits) that force the wrap-up early.
|
|
12
|
+
export const EARLY_SOFT_CAP_LEVEL2_FIRES = 3;
|
|
13
|
+
|
|
14
|
+
// Build the completion-first steering-ladder controller. `ctx` supplies live
|
|
15
|
+
// accessors so every read reflects the loop's current mutable state:
|
|
16
|
+
// - messages, sessionId, sessionAgent, tools (stable refs/values)
|
|
17
|
+
// - getIterations() (current iteration)
|
|
18
|
+
// - softCapEnabled (constant per loop)
|
|
19
|
+
// - getEditCount() (mutated by the loop)
|
|
20
|
+
// - pushSystemReminder(text) → push a meta:'hook' user message
|
|
21
|
+
// - pushUserMessage(msg) → push a raw user message (level-2 latch text)
|
|
22
|
+
export function createSteeringLadder(ctx) {
|
|
23
|
+
const {
|
|
24
|
+
sessionId,
|
|
25
|
+
sessionAgent,
|
|
26
|
+
tools,
|
|
27
|
+
getIterations,
|
|
28
|
+
softCapEnabled,
|
|
29
|
+
getEditCount,
|
|
30
|
+
} = ctx;
|
|
31
|
+
const pushSystemReminder = ctx.pushSystemReminder;
|
|
32
|
+
const pushUserMessage = ctx.pushUserMessage;
|
|
33
|
+
// Permission-based role detection (agent names are user-definable):
|
|
34
|
+
// read-permission sessions legitimately never edit, so they get the
|
|
35
|
+
// report-oriented level-2 text and never arm the early soft-cap.
|
|
36
|
+
const readOnlyRole = ctx.readOnlyRole === true;
|
|
37
|
+
|
|
38
|
+
// Step 1: escalation ladder. _level1FireCount is CUMULATIVE (never reset)
|
|
39
|
+
// so repeated batching reminders accumulate across the whole session.
|
|
40
|
+
// _level2LatchAtIteration latches level-2 steering to at most once / 5 turns.
|
|
41
|
+
let _level1FireCount = 0;
|
|
42
|
+
let _level2LatchAtIteration = -Infinity;
|
|
43
|
+
// Independent ladder counter: consecutive turns where EVERY call is
|
|
44
|
+
// read-only (any count) with zero edits. Catches multi-call read-only
|
|
45
|
+
// turns that the single-call level-1 streak misses. Reset on any edit.
|
|
46
|
+
let _allReadOnlyStreak = 0;
|
|
47
|
+
// Tracks consecutive assistant turns that ran exactly one read-only tool
|
|
48
|
+
// call (missed parallelism). Not reset per-iteration — only by the
|
|
49
|
+
// steering-hint fire below or by a turn that batches/edits.
|
|
50
|
+
let _serialReadOnlyStreak = 0;
|
|
51
|
+
// Tracks consecutive grep calls scoped to the SAME path (any patterns) —
|
|
52
|
+
// the "serial rewording" spiral: re-grepping one file with reworded
|
|
53
|
+
// patterns instead of reading it. Only counts turns whose every call is a
|
|
54
|
+
// grep on that path; any other tool/path resets it.
|
|
55
|
+
let _sameFileGrepStreak = 0;
|
|
56
|
+
let _sameFileGrepPath = null;
|
|
57
|
+
// Early soft-cap: N ignored level-2 steers with zero edits forces the
|
|
58
|
+
// wrap-up early; any edit disarms it (level-2 requires editCount === 0).
|
|
59
|
+
let _level2FireCount = 0;
|
|
60
|
+
|
|
61
|
+
// Level-2 steering emitter shared by both ladder paths (single-call
|
|
62
|
+
// level-1 streak and the independent all-read-only streak). Sets the latch
|
|
63
|
+
// so it fires at most once per 5 turns regardless of which path triggered.
|
|
64
|
+
const _emitLevel2Steer = () => {
|
|
65
|
+
const iterations = getIterations();
|
|
66
|
+
_level2LatchAtIteration = iterations;
|
|
67
|
+
_level2FireCount += 1;
|
|
68
|
+
// When this fire arms the early soft-cap, the wrap-up injected at the
|
|
69
|
+
// next loop head supersedes the level-2 text — skip the redundant hint.
|
|
70
|
+
const _armsEarlyCap = !readOnlyRole && softCapEnabled && _level2FireCount >= EARLY_SOFT_CAP_LEVEL2_FIRES && getEditCount() === 0;
|
|
71
|
+
if (!_armsEarlyCap) pushUserMessage({ role: 'user', content: level2SteerMessage(_level1FireCount, readOnlyRole), meta: 'hook' });
|
|
72
|
+
try {
|
|
73
|
+
appendAgentTrace({
|
|
74
|
+
sessionId,
|
|
75
|
+
iteration: iterations,
|
|
76
|
+
kind: 'steer',
|
|
77
|
+
payload: { tag: 'level2_steer', level1_fires: _level1FireCount, level2_fires: _level2FireCount, edit_count: getEditCount(), all_read_only_streak: _allReadOnlyStreak },
|
|
78
|
+
agent: sessionAgent || null,
|
|
79
|
+
});
|
|
80
|
+
} catch { /* best-effort */ }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
// Loop head reads: is the early soft-cap armed?
|
|
85
|
+
get level2FireCount() { return _level2FireCount; },
|
|
86
|
+
earlySoftCapArmed() {
|
|
87
|
+
return !readOnlyRole && _level2FireCount >= EARLY_SOFT_CAP_LEVEL2_FIRES && getEditCount() === 0;
|
|
88
|
+
},
|
|
89
|
+
// Post-batch steering hint gate. `hintAlreadyFired` seeds the once-per-
|
|
90
|
+
// turn latch (soft-cap active suppresses all hints). Returns nothing;
|
|
91
|
+
// pushes at most one steering message via the ctx push callbacks.
|
|
92
|
+
emitPostBatchSteering(calls, hintAlreadyFired) {
|
|
93
|
+
const iterations = getIterations();
|
|
94
|
+
const editCount = getEditCount();
|
|
95
|
+
// Steering hint gate: at most ONE hint per turn (priority: soft-cap >
|
|
96
|
+
// level-2 > same-file grep > level-1), and none once the soft-cap
|
|
97
|
+
// wrap-up is active — its "text only" directive must not share a send
|
|
98
|
+
// with a "keep exploring" hint.
|
|
99
|
+
let _hintFiredThisTurn = hintAlreadyFired;
|
|
100
|
+
// Missed-parallelism steering: 3+ consecutive turns of a single
|
|
101
|
+
// read-only tool call suggest the model isn't batching independent
|
|
102
|
+
// lookups. Nudge once, then reset (fires again after 3 more).
|
|
103
|
+
if (calls.length === 1 && isEagerDispatchable(calls[0].name, tools)) {
|
|
104
|
+
_serialReadOnlyStreak += 1;
|
|
105
|
+
if (_serialReadOnlyStreak >= 3 && !_hintFiredThisTurn) {
|
|
106
|
+
_serialReadOnlyStreak = 0;
|
|
107
|
+
// Escalation ladder (Step 1). Cumulative level-1 fires are
|
|
108
|
+
// tracked and NEVER reset. Once level-1 has fired >=3 times with
|
|
109
|
+
// ZERO edits, escalate to level-2 steering (blocked-report is a
|
|
110
|
+
// valid completion) instead of the batching nudge — latched to at
|
|
111
|
+
// most once per 5 turns.
|
|
112
|
+
_level1FireCount += 1;
|
|
113
|
+
if (_level1FireCount >= 3 && editCount === 0 && (iterations - _level2LatchAtIteration) >= 5) {
|
|
114
|
+
_emitLevel2Steer();
|
|
115
|
+
} else {
|
|
116
|
+
pushSystemReminder('Last 3 turns each ran a single read-only tool. Batch independent lookups (read/grep/glob/code_graph) into ONE turn, or start editing if you have enough context.');
|
|
117
|
+
}
|
|
118
|
+
_hintFiredThisTurn = true;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
_serialReadOnlyStreak = 0;
|
|
122
|
+
}
|
|
123
|
+
// Independent all-read-only escalation (audit finding): the level-1
|
|
124
|
+
// streak above only counts single-call turns, so a worker that runs
|
|
125
|
+
// 2+ read-only calls per turn escapes the ladder entirely. Track a
|
|
126
|
+
// cumulative count of consecutive turns where EVERY call is read-only
|
|
127
|
+
// (any count) and no edit has been made; at 12 such turns fire level-2
|
|
128
|
+
// directly (same once-per-5-turn latch), reset on any edit.
|
|
129
|
+
{
|
|
130
|
+
const _allReadOnly = calls.length > 0 && calls.every((c) => isEagerDispatchable(c.name, tools));
|
|
131
|
+
if (_allReadOnly && editCount === 0) {
|
|
132
|
+
_allReadOnlyStreak += 1;
|
|
133
|
+
if (_allReadOnlyStreak >= 12 && (iterations - _level2LatchAtIteration) >= 5 && !_hintFiredThisTurn) {
|
|
134
|
+
_emitLevel2Steer();
|
|
135
|
+
_hintFiredThisTurn = true;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
_allReadOnlyStreak = 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Serial-rewording steering: 4+ consecutive turns grepping the SAME
|
|
142
|
+
// path with reworded patterns = a search spiral that single-call
|
|
143
|
+
// batching cannot catch. Crisis-only: fires once per spiral, then
|
|
144
|
+
// resets. Read-the-file is almost always the answer at that point.
|
|
145
|
+
{
|
|
146
|
+
const _grepPathOf = (c) => {
|
|
147
|
+
if (c?.name !== 'grep') return null;
|
|
148
|
+
const p = c?.arguments?.path;
|
|
149
|
+
return typeof p === 'string' && p ? p : null;
|
|
150
|
+
};
|
|
151
|
+
const _turnPaths = calls.map(_grepPathOf);
|
|
152
|
+
const _uniq = [...new Set(_turnPaths)];
|
|
153
|
+
if (_uniq.length === 1 && _uniq[0] !== null) {
|
|
154
|
+
if (_uniq[0] === _sameFileGrepPath) _sameFileGrepStreak += 1;
|
|
155
|
+
else { _sameFileGrepPath = _uniq[0]; _sameFileGrepStreak = 1; }
|
|
156
|
+
if (_sameFileGrepStreak >= 4 && !_hintFiredThisTurn) {
|
|
157
|
+
pushSystemReminder(`4+ consecutive grep turns on the same path (${_sameFileGrepPath}). Rewording patterns is not converging — read the relevant span directly (read with offset/limit) or act on what you have.`);
|
|
158
|
+
_sameFileGrepStreak = 0;
|
|
159
|
+
_sameFileGrepPath = null;
|
|
160
|
+
_hintFiredThisTurn = true;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
_sameFileGrepStreak = 0;
|
|
164
|
+
_sameFileGrepPath = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
// Reviewer fix: a zero-tool turn must not bridge the all-read-only
|
|
169
|
+
// streak across non-tool turns — that would fire level-2 early on a
|
|
170
|
+
// worker that paused to synthesize text mid-run.
|
|
171
|
+
resetAllReadOnlyStreak() { _allReadOnlyStreak = 0; },
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Loop termination-reason classification, extracted from loop.mjs.
|
|
2
|
+
// Pure function over the final response + loop-end flags. No behavior change:
|
|
3
|
+
// the classification ladder is verbatim from the tail of agentLoop.
|
|
4
|
+
import { HIDDEN_AGENT_NAMES } from './hidden-agents.mjs';
|
|
5
|
+
|
|
6
|
+
// Stop reasons that signal the turn was cut short mid-synthesis (token cap,
|
|
7
|
+
// provider pause). Empty content + one of these reasons means the worker
|
|
8
|
+
// was not done. Covers Anthropic (pause_turn, max_tokens), OpenAI (length),
|
|
9
|
+
// Gemini (MAX_TOKENS, OTHER), and case variants.
|
|
10
|
+
export const INCOMPLETE_STOP_REASONS = new Set([
|
|
11
|
+
'pause_turn', 'max_tokens', 'length', 'MAX_TOKENS', 'OTHER',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
// Classify WHY the loop ended so agent-tool can promote an empty/abnormal
|
|
15
|
+
// finish to an explicit Lead-facing error instead of a silent empty
|
|
16
|
+
// "completed". Determine "has content" exactly the way the no-tool-call
|
|
17
|
+
// branch in agentLoop does (trimmed string content, or any reasoning content).
|
|
18
|
+
export function classifyTerminationReason(response, {
|
|
19
|
+
terminatedByCap,
|
|
20
|
+
terminatedBySoftCap,
|
|
21
|
+
softCapActive,
|
|
22
|
+
sessionAgent,
|
|
23
|
+
} = {}) {
|
|
24
|
+
const _finalHasContent = (typeof response?.content === 'string' && response.content.trim().length > 0)
|
|
25
|
+
|| (typeof response?.reasoningContent === 'string' && response.reasoningContent.trim().length > 0);
|
|
26
|
+
const _finalStopReason = response?.stopReason ?? response?.stop_reason ?? null;
|
|
27
|
+
const _finalIncompleteStop = _finalStopReason && INCOMPLETE_STOP_REASONS.has(_finalStopReason);
|
|
28
|
+
const _finalIsHidden = HIDDEN_AGENT_NAMES.has(sessionAgent);
|
|
29
|
+
if (terminatedByCap) {
|
|
30
|
+
// Real problem regardless of hidden/public: the loop never terminated
|
|
31
|
+
// on its own contract.
|
|
32
|
+
return 'iteration_cap';
|
|
33
|
+
}
|
|
34
|
+
if (terminatedBySoftCap || softCapActive) {
|
|
35
|
+
// Worker soft-cap wrap-up path: non-lead session hit the exploration
|
|
36
|
+
// budget and was steered to a text-only finish. Distinct from the hard
|
|
37
|
+
// cap (iteration_cap) — this is an expected, budget-driven termination,
|
|
38
|
+
// not a runaway. Covers BOTH the grace-exhausted path
|
|
39
|
+
// (terminatedBySoftCap) AND the compliant path where the model emitted
|
|
40
|
+
// the final text with no more tool calls while the soft cap was active
|
|
41
|
+
// (loop ends via the no-tool-call break, so terminatedBySoftCap stays
|
|
42
|
+
// false but softCapActive is still true).
|
|
43
|
+
return 'soft_cap_wrapup';
|
|
44
|
+
}
|
|
45
|
+
if (!_finalHasContent && _finalIncompleteStop) {
|
|
46
|
+
// Cut short mid-synthesis (token cap / provider pause). Real problem
|
|
47
|
+
// for hidden agents too.
|
|
48
|
+
return 'truncated';
|
|
49
|
+
}
|
|
50
|
+
if (!_finalHasContent && !_finalIsHidden) {
|
|
51
|
+
// Empty terminal turn. Only public agents violate their contract by
|
|
52
|
+
// finishing empty — hidden agents (explorer/cycle/…) legitimately emit
|
|
53
|
+
// text-only/empty terminal turns per their own role contract, so leave
|
|
54
|
+
// terminationReason undefined for them.
|
|
55
|
+
return 'empty';
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|