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
|
@@ -22,20 +22,15 @@ import {
|
|
|
22
22
|
updateSection,
|
|
23
23
|
} from '../runtime/shared/config.mjs';
|
|
24
24
|
import { resolvePluginData } from '../runtime/shared/plugin-paths.mjs';
|
|
25
|
-
import { readMarkdownDocument } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
25
|
+
import { readMarkdownDocument, serializeFrontmatterDoc } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
26
26
|
|
|
27
27
|
const NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
28
28
|
const DEFAULT_CHANNELS = Object.freeze({
|
|
29
29
|
backend: 'discord',
|
|
30
30
|
discord: {},
|
|
31
31
|
access: { dmPolicy: 'allowlist', allowFrom: [], channels: {} },
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
main: { channelId: '', mode: 'interactive' },
|
|
35
|
-
},
|
|
36
|
-
quiet: { schedule: '23:00-09:00', holidays: false },
|
|
37
|
-
schedules: { respectQuiet: true },
|
|
38
|
-
webhook: { enabled: true, respectQuiet: false, port: 3333 },
|
|
32
|
+
channel: { channelId: '', discordChannelId: '', telegramChatId: '' },
|
|
33
|
+
webhook: { enabled: true, port: 3333 },
|
|
39
34
|
});
|
|
40
35
|
|
|
41
36
|
function dataDir() {
|
|
@@ -61,24 +56,6 @@ function writeTextAtomic(path, text) {
|
|
|
61
56
|
renameSync(tmp, path);
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
// Serialize a single-file frontmatter markdown document:
|
|
65
|
-
// ---
|
|
66
|
-
// key: value
|
|
67
|
-
// ---
|
|
68
|
-
//
|
|
69
|
-
// <body>
|
|
70
|
-
// Values are stringified as-is (enabled:false -> "false"); the reader casts
|
|
71
|
-
// types back. Mirrors the WORKFLOW.md / SKILL.md single-file convention.
|
|
72
|
-
function serializeFrontmatterDoc(meta = {}, body = '') {
|
|
73
|
-
const lines = ['---'];
|
|
74
|
-
for (const [key, value] of Object.entries(meta)) {
|
|
75
|
-
if (value === undefined || value === null) continue;
|
|
76
|
-
lines.push(`${key}: ${value}`);
|
|
77
|
-
}
|
|
78
|
-
lines.push('---', '');
|
|
79
|
-
return `${lines.join('\n')}\n${String(body || '').trim()}\n`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
59
|
function assertName(name, kind = 'name') {
|
|
83
60
|
const value = String(name || '').trim();
|
|
84
61
|
if (!NAME_RE.test(value) || value !== basename(value)) {
|
|
@@ -93,9 +70,7 @@ function normalizeChannelsConfig(raw = {}) {
|
|
|
93
70
|
...(raw && typeof raw === 'object' ? raw : {}),
|
|
94
71
|
discord: { ...DEFAULT_CHANNELS.discord, ...(raw?.discord || {}) },
|
|
95
72
|
access: { ...DEFAULT_CHANNELS.access, ...(raw?.access || {}) },
|
|
96
|
-
|
|
97
|
-
quiet: { ...DEFAULT_CHANNELS.quiet, ...(raw?.quiet || {}) },
|
|
98
|
-
schedules: { ...DEFAULT_CHANNELS.schedules, ...(raw?.schedules || {}) },
|
|
73
|
+
channel: { ...DEFAULT_CHANNELS.channel, ...(raw?.channel || {}) },
|
|
99
74
|
webhook: { ...DEFAULT_CHANNELS.webhook, ...(raw?.webhook || {}) },
|
|
100
75
|
};
|
|
101
76
|
}
|
|
@@ -116,11 +91,42 @@ function seedBackendChannelIds(entry = {}, backend = 'discord') {
|
|
|
116
91
|
return next;
|
|
117
92
|
}
|
|
118
93
|
|
|
94
|
+
// Resolve the single-channel entry from the config: prefer the new `channel`
|
|
95
|
+
// object; fall back (read-side only, no on-disk migration) to the legacy
|
|
96
|
+
// channelsConfig[mainChannel ?? 'main'] entry, then the first entry with an id.
|
|
97
|
+
function resolveChannelEntry(cfg = {}) {
|
|
98
|
+
if (cfg.channel && typeof cfg.channel === 'object'
|
|
99
|
+
&& (cfg.channel.channelId || cfg.channel.discordChannelId || cfg.channel.telegramChatId)) {
|
|
100
|
+
return cfg.channel;
|
|
101
|
+
}
|
|
102
|
+
const legacy = cfg.channelsConfig && typeof cfg.channelsConfig === 'object' ? cfg.channelsConfig : null;
|
|
103
|
+
if (legacy) {
|
|
104
|
+
const mainName = cfg.mainChannel ?? 'main';
|
|
105
|
+
const preferred = legacy[mainName];
|
|
106
|
+
if (preferred && typeof preferred === 'object'
|
|
107
|
+
&& (preferred.channelId || preferred.discordChannelId || preferred.telegramChatId)) {
|
|
108
|
+
return preferred;
|
|
109
|
+
}
|
|
110
|
+
for (const entry of Object.values(legacy)) {
|
|
111
|
+
if (entry && typeof entry === 'object'
|
|
112
|
+
&& (entry.channelId || entry.discordChannelId || entry.telegramChatId)) {
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return cfg.channel && typeof cfg.channel === 'object' ? cfg.channel : {};
|
|
118
|
+
}
|
|
119
|
+
|
|
119
120
|
function updateChannelsSection(build) {
|
|
120
121
|
let next;
|
|
121
122
|
updateSection('channels', (current) => {
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
// Preserve any legacy channelsConfig/mainChannel that already live on disk
|
|
124
|
+
// for read-side compat, but never re-emit them from our writers: strip them
|
|
125
|
+
// out of the returned shape so writes converge on the single `channel`.
|
|
126
|
+
const normalized = normalizeChannelsConfig(current);
|
|
127
|
+
next = build(normalized);
|
|
128
|
+
const { channelsConfig: _lc, mainChannel: _lm, ...clean } = normalizeChannelsConfig(next);
|
|
129
|
+
return clean;
|
|
124
130
|
});
|
|
125
131
|
return next;
|
|
126
132
|
}
|
|
@@ -136,19 +142,17 @@ function listEntryDirs(dir) {
|
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
|
|
145
|
+
// Single-channel read: resolves `cfg.channel` (legacy channelsConfig fallback)
|
|
146
|
+
// into the flat shape the settings/TUI layer consumes.
|
|
147
|
+
export function getChannel(config = {}) {
|
|
140
148
|
const cfg = normalizeChannelsConfig(config);
|
|
141
149
|
const backend = cfg.backend === 'telegram' ? 'telegram' : 'discord';
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
telegramChatId: String(value?.telegramChatId || ''),
|
|
149
|
-
mode: value?.mode || 'interactive',
|
|
150
|
-
main: cfg.mainChannel === name,
|
|
151
|
-
}));
|
|
150
|
+
const entry = resolveChannelEntry(cfg);
|
|
151
|
+
return {
|
|
152
|
+
channelId: channelIdForBackend(entry, backend),
|
|
153
|
+
discordChannelId: String(entry?.discordChannelId || ''),
|
|
154
|
+
telegramChatId: String(entry?.telegramChatId || ''),
|
|
155
|
+
};
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
export function saveDiscordToken(token) {
|
|
@@ -187,40 +191,24 @@ export function forgetWebhookAuthtoken() {
|
|
|
187
191
|
return { ok: true };
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
// Single-channel write: persists `cfg.channel`, keeping the per-backend id
|
|
195
|
+
// fields (discordChannelId/telegramChatId) so a backend switch retains both
|
|
196
|
+
// ids. `backend` selects which per-backend field the id updates; when omitted
|
|
197
|
+
// the id lands on the active backend's field.
|
|
198
|
+
export function setChannel({ channelId, backend = null } = {}) {
|
|
192
199
|
const id = String(channelId || '').trim();
|
|
193
200
|
if (!id) throw new Error('channelId is required');
|
|
194
|
-
const nextMode = mode === 'broadcast' ? 'broadcast' : 'interactive';
|
|
195
201
|
const targetBackend = backend === 'telegram' || backend === 'discord' ? backend : null;
|
|
196
202
|
return updateChannelsSection((cfg) => {
|
|
197
203
|
const activeBackend = cfg.backend === 'telegram' ? 'telegram' : 'discord';
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
else nextEntry.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
...cfg,
|
|
208
|
-
mainChannel: main || !cfg.mainChannel ? label : cfg.mainChannel,
|
|
209
|
-
channelsConfig: {
|
|
210
|
-
...(cfg.channelsConfig || {}),
|
|
211
|
-
[label]: nextEntry,
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function deleteChannel(name) {
|
|
218
|
-
const label = assertName(name, 'channel name');
|
|
219
|
-
return updateChannelsSection((cfg) => {
|
|
220
|
-
const channelsConfig = { ...(cfg.channelsConfig || {}) };
|
|
221
|
-
delete channelsConfig[label];
|
|
222
|
-
const nextMain = cfg.mainChannel === label ? Object.keys(channelsConfig)[0] || 'main' : cfg.mainChannel;
|
|
223
|
-
return { ...cfg, channelsConfig, mainChannel: nextMain };
|
|
204
|
+
const writeBackend = targetBackend || activeBackend;
|
|
205
|
+
const current = seedBackendChannelIds(resolveChannelEntry(cfg), activeBackend);
|
|
206
|
+
const nextEntry = { ...current };
|
|
207
|
+
if (writeBackend === 'telegram') nextEntry.telegramChatId = id;
|
|
208
|
+
else nextEntry.discordChannelId = id;
|
|
209
|
+
// `channelId` mirrors the active backend's id for legacy readers.
|
|
210
|
+
nextEntry.channelId = channelIdForBackend(nextEntry, activeBackend);
|
|
211
|
+
return { ...cfg, channel: nextEntry };
|
|
224
212
|
});
|
|
225
213
|
}
|
|
226
214
|
|
|
@@ -230,7 +218,6 @@ export function setWebhookConfig(patch = {}) {
|
|
|
230
218
|
webhook: {
|
|
231
219
|
...(cfg.webhook || {}),
|
|
232
220
|
...(Object.prototype.hasOwnProperty.call(patch, 'enabled') ? { enabled: patch.enabled === true } : {}),
|
|
233
|
-
...(Object.prototype.hasOwnProperty.call(patch, 'respectQuiet') ? { respectQuiet: patch.respectQuiet === true } : {}),
|
|
234
221
|
...(patch.port ? { port: Number(patch.port) || 3333 } : {}),
|
|
235
222
|
...(patch.domain ? { domain: String(patch.domain).trim() } : {}),
|
|
236
223
|
...(patch.ngrokDomain ? { ngrokDomain: String(patch.ngrokDomain).trim() } : {}),
|
|
@@ -245,14 +232,11 @@ export function setBackend(name) {
|
|
|
245
232
|
}
|
|
246
233
|
updateChannelsSection((cfg) => {
|
|
247
234
|
const activeBackend = cfg.backend === 'telegram' ? 'telegram' : 'discord';
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
})(),
|
|
254
|
-
]));
|
|
255
|
-
return { ...cfg, channelsConfig, backend: value };
|
|
235
|
+
const seeded = seedBackendChannelIds(resolveChannelEntry(cfg), activeBackend);
|
|
236
|
+
// `channelId` now mirrors the newly-selected backend's id; both per-backend
|
|
237
|
+
// id fields are retained so switching back keeps the other id.
|
|
238
|
+
const channel = { ...seeded, channelId: channelIdForBackend(seeded, value) };
|
|
239
|
+
return { ...cfg, channel, backend: value };
|
|
256
240
|
});
|
|
257
241
|
return { ok: true, backend: value };
|
|
258
242
|
}
|
|
@@ -274,8 +258,15 @@ export function listSchedules() {
|
|
|
274
258
|
if (Object.prototype.hasOwnProperty.call(config, 'enabled')) {
|
|
275
259
|
config.enabled = config.enabled !== 'false' && config.enabled !== false;
|
|
276
260
|
}
|
|
261
|
+
// Dir name is canonical identity; frontmatter `name` is display-only and
|
|
262
|
+
// must never override the slug used for routing/deletion. `description`
|
|
263
|
+
// defaults to '' for old files with no such key (backward compat).
|
|
264
|
+
const description = String(config.description || '');
|
|
265
|
+
delete config.name;
|
|
266
|
+
delete config.description;
|
|
277
267
|
return {
|
|
278
268
|
name,
|
|
269
|
+
description,
|
|
279
270
|
...config,
|
|
280
271
|
instructions: body,
|
|
281
272
|
route: config.channel ? `channel:${config.channel}` : 'session',
|
|
@@ -285,6 +276,7 @@ export function listSchedules() {
|
|
|
285
276
|
|
|
286
277
|
export function saveSchedule({
|
|
287
278
|
name,
|
|
279
|
+
description = '',
|
|
288
280
|
time,
|
|
289
281
|
timezone,
|
|
290
282
|
days,
|
|
@@ -302,7 +294,9 @@ export function saveSchedule({
|
|
|
302
294
|
const dir = join(schedulesDir(), id);
|
|
303
295
|
if (existsSync(dir) && overwrite !== true) throw new Error(`schedule "${id}" already exists`);
|
|
304
296
|
mkdirSync(dir, { recursive: true });
|
|
305
|
-
|
|
297
|
+
// name (== dir slug) and description lead the frontmatter per the SKILL.md
|
|
298
|
+
// convention; operational keys follow.
|
|
299
|
+
const config = { name: id, description: String(description || '').trim(), time: cron };
|
|
306
300
|
if (timezone) config.timezone = String(timezone).trim();
|
|
307
301
|
if (days && days !== 'daily') config.days = String(days).trim();
|
|
308
302
|
if (channel) config.channel = String(channel).trim();
|
|
@@ -324,7 +318,13 @@ export function setScheduleEnabled(name, enabled) {
|
|
|
324
318
|
const mdPath = join(dir, 'SCHEDULE.md');
|
|
325
319
|
if (!existsSync(mdPath)) throw new Error(`schedule "${id}" does not exist`);
|
|
326
320
|
const { frontmatter, body } = readMarkdownDocument(readText(mdPath, ''));
|
|
327
|
-
|
|
321
|
+
// Re-spread with name/description first so the round-trip keeps SKILL.md
|
|
322
|
+
// key order; name stays the dir slug regardless of stale frontmatter.
|
|
323
|
+
const { name: _staleName, description = '', ...rest } = frontmatter;
|
|
324
|
+
writeTextAtomic(mdPath, serializeFrontmatterDoc(
|
|
325
|
+
{ name: id, description, ...rest, enabled: enabled !== false },
|
|
326
|
+
body,
|
|
327
|
+
));
|
|
328
328
|
return { name: id, enabled: enabled !== false };
|
|
329
329
|
}
|
|
330
330
|
|
|
@@ -336,12 +336,19 @@ export function listWebhooks() {
|
|
|
336
336
|
if (Object.prototype.hasOwnProperty.call(config, 'enabled')) {
|
|
337
337
|
config.enabled = config.enabled !== 'false' && config.enabled !== false;
|
|
338
338
|
}
|
|
339
|
+
// Dir name is canonical identity/URL routing key; frontmatter `name` is
|
|
340
|
+
// display-only and must not override the slug. `description` defaults to
|
|
341
|
+
// '' for old files with no such key (backward compat).
|
|
342
|
+
const description = String(config.description || '');
|
|
343
|
+
delete config.name;
|
|
344
|
+
delete config.description;
|
|
339
345
|
// Secret lives in a side file (<name>/secret), never in frontmatter, so
|
|
340
346
|
// an arbitrary user-supplied secret (quotes/colon/newline) round-trips
|
|
341
347
|
// losslessly and setWebhookEnabled cannot corrupt it.
|
|
342
348
|
const hasSecret = Boolean(readText(join(dir, 'secret'), '').trim());
|
|
343
349
|
return {
|
|
344
350
|
name,
|
|
351
|
+
description,
|
|
345
352
|
...config,
|
|
346
353
|
secretSet: hasSecret,
|
|
347
354
|
secret: undefined,
|
|
@@ -353,6 +360,7 @@ export function listWebhooks() {
|
|
|
353
360
|
|
|
354
361
|
export function saveWebhook({
|
|
355
362
|
name,
|
|
363
|
+
description = '',
|
|
356
364
|
parser = 'github',
|
|
357
365
|
secret,
|
|
358
366
|
channel,
|
|
@@ -373,7 +381,9 @@ export function saveWebhook({
|
|
|
373
381
|
if (existsSync(dir) && overwrite !== true) throw new Error(`webhook "${id}" already exists`);
|
|
374
382
|
mkdirSync(dir, { recursive: true });
|
|
375
383
|
const secretValue = String(secret || randomBytes(24).toString('hex')).trim();
|
|
376
|
-
|
|
384
|
+
// name (== dir slug) and description lead the frontmatter per the SKILL.md
|
|
385
|
+
// convention; operational keys (parser/channel/model/enabled) follow.
|
|
386
|
+
const config = { name: id, description: String(description || '').trim(), parser: nextParser };
|
|
377
387
|
if (channel) config.channel = String(channel).trim();
|
|
378
388
|
if (model) config.model = String(model).trim();
|
|
379
389
|
if (enabled === false) config.enabled = false;
|
|
@@ -397,7 +407,13 @@ export function setWebhookEnabled(name, enabled) {
|
|
|
397
407
|
const mdPath = join(dir, 'WEBHOOK.md');
|
|
398
408
|
if (!existsSync(mdPath)) throw new Error(`webhook "${id}" does not exist`);
|
|
399
409
|
const { frontmatter, body } = readMarkdownDocument(readText(mdPath, ''));
|
|
400
|
-
|
|
410
|
+
// Re-spread with name/description first so the round-trip keeps SKILL.md
|
|
411
|
+
// key order; name stays the dir slug regardless of stale frontmatter.
|
|
412
|
+
const { name: _staleName, description = '', ...rest } = frontmatter;
|
|
413
|
+
writeTextAtomic(mdPath, serializeFrontmatterDoc(
|
|
414
|
+
{ name: id, description, ...rest, enabled: enabled !== false },
|
|
415
|
+
body,
|
|
416
|
+
));
|
|
401
417
|
return { name: id, enabled: enabled !== false };
|
|
402
418
|
}
|
|
403
419
|
|
|
@@ -429,7 +445,7 @@ export function channelSetup(config = null) {
|
|
|
429
445
|
stored: hasStoredSecret(SECRET_ACCOUNTS.webhookAuth),
|
|
430
446
|
status: webhookAuth ? 'Set' : 'Off',
|
|
431
447
|
},
|
|
432
|
-
|
|
448
|
+
channel: getChannel(cfg),
|
|
433
449
|
schedules: listSchedules(),
|
|
434
450
|
webhooks: listWebhooks(),
|
|
435
451
|
};
|
|
@@ -440,11 +456,7 @@ export function renderChannelStatus(config = null) {
|
|
|
440
456
|
const lines = [];
|
|
441
457
|
lines.push(`discord ${setup.discord.status}${setup.discord.problem ? ` (${setup.discord.problem})` : ''}`);
|
|
442
458
|
lines.push(`webhook ${setup.webhook.enabled === false ? 'disabled' : 'enabled'} · auth ${setup.webhook.status} · port ${setup.webhook.port || 3333}`);
|
|
443
|
-
lines.push('
|
|
444
|
-
if (setup.channels.length === 0) lines.push(' (none)');
|
|
445
|
-
for (const ch of setup.channels) {
|
|
446
|
-
lines.push(` ${ch.name}${ch.main ? ' *' : ''} ${ch.channelId || '(unset)'} ${ch.mode}`);
|
|
447
|
-
}
|
|
459
|
+
lines.push(`channel ${setup.channel.channelId || '(unset)'}`);
|
|
448
460
|
lines.push('schedules');
|
|
449
461
|
if (setup.schedules.length === 0) lines.push(' (none)');
|
|
450
462
|
for (const item of setup.schedules) {
|
|
@@ -9,15 +9,12 @@ import { appendBuffered } from '../runtime/shared/buffered-appender.mjs';
|
|
|
9
9
|
const CHANNEL_TOOLS = new Set([
|
|
10
10
|
'reply',
|
|
11
11
|
'fetch',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'trigger_schedule',
|
|
17
|
-
'schedule_control',
|
|
12
|
+
// activate_channel_bridge/reload_config are NOT model-facing tools (no
|
|
13
|
+
// TOOL_DEFS entry) but stay in the allow-set: mixdog-session-runtime.mjs
|
|
14
|
+
// calls channels.execute() with these names directly as internal
|
|
15
|
+
// Lead-only runtime plumbing (bridge-claim on start, config hot-reload).
|
|
18
16
|
'activate_channel_bridge',
|
|
19
17
|
'reload_config',
|
|
20
|
-
'inject_command',
|
|
21
18
|
]);
|
|
22
19
|
|
|
23
20
|
const WORKER_PRELOAD = fileURLToPath(new URL('./channel-worker-preload.cjs', import.meta.url));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { makeAgentDispatch } from '../runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs';
|
|
4
5
|
import { loadConfig } from '../runtime/agent/orchestrator/config.mjs';
|
|
@@ -38,8 +39,28 @@ const EXPLORE_RESULT_CACHE_TTL_MS = (() => {
|
|
|
38
39
|
const raw = Number(process.env.MIXDOG_EXPLORE_RESULT_CACHE_TTL_MS);
|
|
39
40
|
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 5 * 60_000;
|
|
40
41
|
})();
|
|
42
|
+
// Hard ceiling for one explorer compute. The dispatch-level watchdog is the
|
|
43
|
+
// primary abort; this race is the last line of defence so a wedged compute can
|
|
44
|
+
// never hang the awaiting tool call (and its cache key) forever.
|
|
45
|
+
export const EXPLORE_COMPUTE_HARD_TIMEOUT_MS = (() => {
|
|
46
|
+
const raw = Number(process.env.MIXDOG_EXPLORE_HARD_TIMEOUT_MS);
|
|
47
|
+
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 10 * 60_000;
|
|
48
|
+
})();
|
|
41
49
|
const EXPLORE_RESULT_CACHE = new Map(); // key -> { ts, value?, promise? }
|
|
42
50
|
|
|
51
|
+
function withExploreHardTimeout(promise) {
|
|
52
|
+
if (!(EXPLORE_COMPUTE_HARD_TIMEOUT_MS > 0)) return promise;
|
|
53
|
+
let timer = null;
|
|
54
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
55
|
+
timer = setTimeout(
|
|
56
|
+
() => reject(new Error(`explorer timed out after ${EXPLORE_COMPUTE_HARD_TIMEOUT_MS}ms`)),
|
|
57
|
+
EXPLORE_COMPUTE_HARD_TIMEOUT_MS,
|
|
58
|
+
);
|
|
59
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
60
|
+
});
|
|
61
|
+
return Promise.race([promise, timeout]).finally(() => { if (timer) clearTimeout(timer); });
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
function escapeXml(str) {
|
|
44
65
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
45
66
|
}
|
|
@@ -49,7 +70,7 @@ function escapeXml(str) {
|
|
|
49
70
|
// reminder. The full no-verdict contract lives at system level
|
|
50
71
|
// (rules/agent/30-explorer.md).
|
|
51
72
|
export function buildExplorerPrompt(query) {
|
|
52
|
-
return `<query>${escapeXml(query)}</query>\nReminder:
|
|
73
|
+
return `<query>${escapeXml(query)}</query>\nReminder: budget 4 tool turns — batch every candidate lookup per turn, never one tool at a time. The moment you have credible anchors, STOP and answer; spend remaining turns only while you have zero anchors. Output only anchor lines formatted as path:line — symbol/name — short reason, or EXPLORATION_FAILED. No preamble, bullets, numbering, headings, summary, code quotes, analysis, verdicts, ratings, or recommendations.`;
|
|
53
74
|
}
|
|
54
75
|
|
|
55
76
|
export function normalizeExploreQueries(rawQuery) {
|
|
@@ -80,11 +101,11 @@ function resolveExploreCwd(input, callerCwd) {
|
|
|
80
101
|
return base;
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
function settledExplorerResult(result) {
|
|
104
|
+
function settledExplorerResult(result, cwd = null) {
|
|
84
105
|
if (result?.status !== 'fulfilled') {
|
|
85
106
|
return { ok: false, text: `[explorer error] ${result?.reason?.message || String(result?.reason)}` };
|
|
86
107
|
}
|
|
87
|
-
const text = cleanExplorerText(typeof result.value === 'string' ? result.value : responseText(result.value));
|
|
108
|
+
const text = cleanExplorerText(typeof result.value === 'string' ? result.value : responseText(result.value), cwd);
|
|
88
109
|
if (!text) return { ok: false, text: '[explorer error] empty response' };
|
|
89
110
|
return { ok: true, text };
|
|
90
111
|
}
|
|
@@ -106,10 +127,10 @@ function fatalExploreError(settled) {
|
|
|
106
127
|
return (settled || []).find((r) => r?.status === 'rejected' && isFatalExploreError(r.reason))?.reason || null;
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
function mergeSettled(settled, queries) {
|
|
130
|
+
function mergeSettled(settled, queries, cwd = null) {
|
|
110
131
|
const single = queries.length === 1;
|
|
111
132
|
if (single) {
|
|
112
|
-
const { text: body } = settledExplorerResult(settled[0]);
|
|
133
|
+
const { text: body } = settledExplorerResult(settled[0], cwd);
|
|
113
134
|
return body.length > EXPLORE_OUTPUT_CHAR_CAP
|
|
114
135
|
? body.slice(0, EXPLORE_OUTPUT_CHAR_CAP) + EXPLORE_TRUNCATION_MARKER
|
|
115
136
|
: body;
|
|
@@ -120,7 +141,7 @@ function mergeSettled(settled, queries) {
|
|
|
120
141
|
let truncated = false;
|
|
121
142
|
for (let i = 0; i < settled.length; i++) {
|
|
122
143
|
const header = `Q${i + 1}: ${String(queries[i] ?? '').replace(/\s+/g, ' ').slice(0, 60)}`;
|
|
123
|
-
const { text: body } = settledExplorerResult(settled[i]);
|
|
144
|
+
const { text: body } = settledExplorerResult(settled[i], cwd);
|
|
124
145
|
const piece = `${header}\n${body}`;
|
|
125
146
|
const addLen = (parts.length === 0 ? 0 : sep.length) + piece.length;
|
|
126
147
|
if (total + addLen > EXPLORE_OUTPUT_CHAR_CAP) {
|
|
@@ -208,11 +229,19 @@ function getCachedExploreResult(key) {
|
|
|
208
229
|
async function runExploreCached(key, compute) {
|
|
209
230
|
const cached = getCachedExploreResult(key);
|
|
210
231
|
if (cached !== null) return cached;
|
|
211
|
-
if (!exploreResultCacheEnabled()) return await compute();
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
232
|
+
if (!exploreResultCacheEnabled()) return await withExploreHardTimeout(compute());
|
|
233
|
+
const entry = EXPLORE_RESULT_CACHE.get(key);
|
|
234
|
+
if (entry?.promise) {
|
|
235
|
+
// Pending in-flight dedup — but never trust a pending entry forever: a
|
|
236
|
+
// compute that outlived the hard timeout is wedged (its own race should
|
|
237
|
+
// have rejected); drop the poisoned key and recompute fresh.
|
|
238
|
+
if (!(EXPLORE_COMPUTE_HARD_TIMEOUT_MS > 0) || Date.now() - entry.ts <= EXPLORE_COMPUTE_HARD_TIMEOUT_MS) {
|
|
239
|
+
return await entry.promise;
|
|
240
|
+
}
|
|
241
|
+
EXPLORE_RESULT_CACHE.delete(key);
|
|
242
|
+
}
|
|
214
243
|
const promise = Promise.resolve()
|
|
215
|
-
.then(() => compute())
|
|
244
|
+
.then(() => withExploreHardTimeout(compute()))
|
|
216
245
|
.then((value) => {
|
|
217
246
|
const text = typeof value === 'string' ? value : responseText(value);
|
|
218
247
|
const cleaned = cleanExplorerText(text);
|
|
@@ -247,6 +276,7 @@ function responseText(response) {
|
|
|
247
276
|
}
|
|
248
277
|
|
|
249
278
|
const ANCHOR_LINE_RE = /(?:^|\s)(?:[A-Za-z]:[\\/][^:\n]+|\.{0,2}[\\/][^:\n]+|src[\\/][^:\n]+|[\w.-]+[\\/][^:\n]+):\d+\b/;
|
|
279
|
+
const ANCHOR_PATH_CAPTURE_RE = /(?:^|\s)((?:[A-Za-z]:[\\/][^:\n]+|\.{0,2}[\\/][^:\n]+|src[\\/][^:\n]+|[\w.-]+[\\/][^:\n]+)):\d+\b/;
|
|
250
280
|
const FAILED_RE = /\b(?:EXPLORATION_FAILED|exploration failed|no credible (?:anchor|location)|no relevant (?:anchor|location)|not found|could(?: not|n't) find)\b/i;
|
|
251
281
|
|
|
252
282
|
function normalizeExplorerLine(line) {
|
|
@@ -260,7 +290,16 @@ function normalizeExplorerLine(line) {
|
|
|
260
290
|
.trim();
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
|
|
293
|
+
// Anchor existence filter: a locator answer citing a file that does not exist
|
|
294
|
+
// is a hallucination — drop the line. Only enforced when cwd is known.
|
|
295
|
+
function anchorLineExists(line, cwd) {
|
|
296
|
+
const m = line.match(ANCHOR_PATH_CAPTURE_RE);
|
|
297
|
+
if (!m) return true;
|
|
298
|
+
const p = m[1].trim();
|
|
299
|
+
try { return existsSync(isAbsolute(p) ? p : resolve(cwd, p)); } catch { return true; }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function cleanExplorerText(text, cwd = null) {
|
|
264
303
|
const raw = String(text || '').trim();
|
|
265
304
|
if (!raw) return '';
|
|
266
305
|
const anchors = [];
|
|
@@ -270,7 +309,12 @@ function cleanExplorerText(text) {
|
|
|
270
309
|
if (/^EXPLORATION_FAILED\b/i.test(line)) return 'EXPLORATION_FAILED';
|
|
271
310
|
if (ANCHOR_LINE_RE.test(line)) anchors.push(line);
|
|
272
311
|
}
|
|
273
|
-
if (anchors.length)
|
|
312
|
+
if (anchors.length) {
|
|
313
|
+
const real = cwd ? anchors.filter((l) => anchorLineExists(l, cwd)) : anchors;
|
|
314
|
+
// All anchors pointed at nonexistent paths: fail harmlessly instead of
|
|
315
|
+
// forwarding hallucinated locations.
|
|
316
|
+
return real.length ? real.join('\n') : 'EXPLORATION_FAILED';
|
|
317
|
+
}
|
|
274
318
|
if (FAILED_RE.test(raw)) return 'EXPLORATION_FAILED';
|
|
275
319
|
return raw;
|
|
276
320
|
}
|
|
@@ -311,10 +355,16 @@ async function runExploreSync(args = {}, ctx = {}) {
|
|
|
311
355
|
const presetName = (route && typeof route === 'object')
|
|
312
356
|
? `${clean(route.provider)}/${clean(route.model)}`
|
|
313
357
|
: (route || '');
|
|
358
|
+
// Explorer self-manages its turn budget: the 4-turn budget lives ONLY in
|
|
359
|
+
// explorer-scoped surfaces (rules/agent/30-explorer.md + per-query prompt),
|
|
360
|
+
// never in shared loop policy. The cap here is a runaway guard, unchanged
|
|
361
|
+
// from the original value; B-round live tests showed hard targets
|
|
362
|
+
// legitimately need ~10.
|
|
314
363
|
const llm = makeAgentDispatch({
|
|
315
364
|
agent: 'explorer',
|
|
316
365
|
cwd: resolvedCwd,
|
|
317
366
|
brief: true,
|
|
367
|
+
maxLoopIterations: 12,
|
|
318
368
|
parentSessionId: ctx.callerSessionId || null,
|
|
319
369
|
clientHostPid: ctx.clientHostPid || null,
|
|
320
370
|
config,
|
|
@@ -328,8 +378,8 @@ async function runExploreSync(args = {}, ctx = {}) {
|
|
|
328
378
|
const fatal = fatalExploreError(settled);
|
|
329
379
|
if (fatal) return fail(presentErrorText(fatal, { surface: 'explore' }));
|
|
330
380
|
|
|
331
|
-
const merged = mergeSettled(settled, working);
|
|
332
|
-
const allFailed = settled.every((r) => !settledExplorerResult(r).ok);
|
|
381
|
+
const merged = mergeSettled(settled, working, resolvedCwd);
|
|
382
|
+
const allFailed = settled.every((r) => !settledExplorerResult(r, resolvedCwd).ok);
|
|
333
383
|
const out = capNotice + merged;
|
|
334
384
|
return allFailed ? fail(out) : ok(out);
|
|
335
385
|
}
|