mixdog 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -1,472 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname,
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import {
|
|
5
5
|
cancelBackgroundTask,
|
|
6
6
|
cleanupBackgroundTasks,
|
|
7
7
|
getBackgroundTask,
|
|
8
8
|
listBackgroundTasks,
|
|
9
|
-
renderBackgroundTask,
|
|
10
9
|
reconcileBackgroundTask,
|
|
11
|
-
setBackgroundTaskEnqueueFallback,
|
|
12
10
|
startBackgroundTask,
|
|
13
11
|
sanitizeTaskMeta,
|
|
14
12
|
taskIdFromArgs,
|
|
15
13
|
} from '../runtime/shared/background-tasks.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import { presentErrorText } from '../runtime/shared/err-text.mjs';
|
|
14
|
+
import { presentErrorText, errorLine } from '../runtime/shared/err-text.mjs';
|
|
18
15
|
import { updateJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
|
|
19
|
-
import {
|
|
20
|
-
normalizeAgentPermission,
|
|
21
|
-
normalizeAgentPermissionOrNone,
|
|
22
|
-
parseMarkdownFrontmatter,
|
|
23
|
-
} from '../runtime/shared/markdown-frontmatter.mjs';
|
|
16
|
+
import { normalizeAgentPermission } from '../runtime/shared/markdown-frontmatter.mjs';
|
|
24
17
|
import { prepareAgentSession } from '../runtime/agent/orchestrator/agent-runtime/session-builder.mjs';
|
|
25
18
|
import {
|
|
26
19
|
agentWatchdogPolicyActive,
|
|
27
20
|
evaluateAgentWatchdogAbort,
|
|
28
21
|
resolveAgentWatchdogPolicy,
|
|
29
22
|
} from '../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
30
|
-
import {
|
|
31
|
-
appendAgentProgressKv,
|
|
32
|
-
buildAgentTaskProgressFields,
|
|
33
|
-
} from './agent-task-status.mjs';
|
|
23
|
+
import { buildAgentTaskProgressFields } from './agent-task-status.mjs';
|
|
34
24
|
import { AGENT_OWNER } from '../runtime/agent/orchestrator/agent-owner.mjs';
|
|
35
|
-
import { clearGatewaySessionRoute, writeGatewaySessionRoute } from '../vendor/statusline/src/gateway/session-routes.mjs';
|
|
36
25
|
import { isKnownProvider } from './provider-admin.mjs';
|
|
26
|
+
import {
|
|
27
|
+
ACTIVE_STAGES,
|
|
28
|
+
AGENT_TOOL,
|
|
29
|
+
DEFAULT_AGENT_PRESETS,
|
|
30
|
+
DEFAULT_PROVIDER,
|
|
31
|
+
WORKER_INDEX_FILE,
|
|
32
|
+
} from './agent-tool/tool-def.mjs';
|
|
33
|
+
import {
|
|
34
|
+
agentPresetName,
|
|
35
|
+
agentScope,
|
|
36
|
+
agentTagOf,
|
|
37
|
+
clean,
|
|
38
|
+
clearAgentStatuslineRoute,
|
|
39
|
+
envTimeoutMs,
|
|
40
|
+
findPreset,
|
|
41
|
+
nonNegativeInt,
|
|
42
|
+
normalizeAgentName,
|
|
43
|
+
normalizeAgentRoute,
|
|
44
|
+
positiveInt,
|
|
45
|
+
presetKey,
|
|
46
|
+
readAgentFrontmatterPermission,
|
|
47
|
+
resolvePrompt,
|
|
48
|
+
rowMatchesContext,
|
|
49
|
+
sessionMatchesContext,
|
|
50
|
+
synthesizePreset,
|
|
51
|
+
terminalPidForContext,
|
|
52
|
+
withCwdHeader,
|
|
53
|
+
writeAgentStatuslineRoute,
|
|
54
|
+
} from './agent-tool/helpers.mjs';
|
|
55
|
+
import { abnormalEmptyFinishError, renderResult } from './agent-tool/render.mjs';
|
|
56
|
+
import { createProviderInit } from './agent-tool/provider-init.mjs';
|
|
57
|
+
import { createNotify } from './agent-tool/notify.mjs';
|
|
58
|
+
// Re-export the static tool descriptor so importers of this facade keep the
|
|
59
|
+
// identical public surface (`import { AGENT_TOOL } from './agent-tool.mjs'`).
|
|
60
|
+
export { AGENT_TOOL };
|
|
37
61
|
|
|
38
62
|
const STANDALONE_SOURCE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
39
63
|
|
|
40
|
-
const PRESET_ALIASES = new Map([
|
|
41
|
-
['opus-xhigh', { base: 'opus-high', effort: 'xhigh', id: 'opus-xhigh', name: 'OPUS XHIGH' }],
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
const DEFAULT_AGENT_PRESETS = Object.freeze({
|
|
45
|
-
explore: 'sonnet-high',
|
|
46
|
-
maintainer: 'haiku',
|
|
47
|
-
worker: 'sonnet-high',
|
|
48
|
-
'heavy-worker': 'sonnet-high',
|
|
49
|
-
reviewer: 'opus-xhigh',
|
|
50
|
-
debugger: 'opus-xhigh',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Mirrors DEFAULT_PROVIDER in mixdog-session-runtime.mjs. Used only as the
|
|
54
|
-
// last-resort fallback when a stored agent route omits its provider and the
|
|
55
|
-
// config carries no defaultProvider.
|
|
56
|
-
const DEFAULT_PROVIDER = 'anthropic-oauth';
|
|
57
|
-
|
|
58
|
-
export const AGENT_TOOL = {
|
|
59
|
-
name: 'agent',
|
|
60
|
-
title: 'Agent',
|
|
61
|
-
annotations: {
|
|
62
|
-
title: 'Agent',
|
|
63
|
-
readOnlyHint: false,
|
|
64
|
-
destructiveHint: true,
|
|
65
|
-
idempotentHint: false,
|
|
66
|
-
openWorldHint: true,
|
|
67
|
-
agentHidden: true,
|
|
68
|
-
},
|
|
69
|
-
description: 'Delegate scoped work. Agent handoffs always start background tasks and return task IDs immediately. Spawn independent scopes in parallel with distinct tags; for the same scope/tag, use send or spawn with the same tag to reuse the live session. Wait for the completion notification before dependent work. Do not call status/read after spawn; status/read are manual recovery only.',
|
|
70
|
-
inputSchema: {
|
|
71
|
-
type: 'object',
|
|
72
|
-
properties: {
|
|
73
|
-
type: { type: 'string', enum: ['spawn', 'send', 'list', 'close', 'cancel', 'status', 'read', 'cleanup'], description: 'Action. Default spawn.' },
|
|
74
|
-
task_id: { type: 'string', description: 'Manual recovery task ID.' },
|
|
75
|
-
agent: { type: 'string', description: 'Workflow agent id.' },
|
|
76
|
-
tag: { type: 'string', description: 'Stable scope handle. Reuse the same tag for follow-up on the same scope; use distinct tags only for independent scopes.' },
|
|
77
|
-
sessionId: { type: 'string', description: 'Raw sess_ id.' },
|
|
78
|
-
prompt: { type: 'string', description: 'Scoped task brief.' },
|
|
79
|
-
message: { type: 'string', description: 'Follow-up for send/reuse, or brief.' },
|
|
80
|
-
file: { type: 'string', description: 'Prompt file.' },
|
|
81
|
-
cwd: { type: 'string', description: 'Working directory.' },
|
|
82
|
-
context: { type: 'string', description: 'Extra agent context.' },
|
|
83
|
-
},
|
|
84
|
-
additionalProperties: true,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const WORKER_INDEX_FILE = 'agent-workers.json';
|
|
89
|
-
|
|
90
|
-
// A worker that hits the loop iteration ceiling, gets truncated mid-synthesis,
|
|
91
|
-
// or produces an empty terminal turn returns content:'' but never throws — so
|
|
92
|
-
// the background task would otherwise reconcile as a benign `completed` empty
|
|
93
|
-
// success. That is wrong: an empty final answer is an error. loop.mjs is the
|
|
94
|
-
// single classifier: it tags result.terminationReason for abnormal finishes
|
|
95
|
-
// (carried through manager.mjs terminalResultPreview). We key purely off that
|
|
96
|
-
// here. iteration_cap / truncated are real problems for EVERY agent (hidden
|
|
97
|
-
// too). The plain `empty` case is tagged by the loop ONLY for public agents;
|
|
98
|
-
// hidden agents (explorer/cycle/…) legitimately emit empty terminal turns and
|
|
99
|
-
// are left untagged, so they stay benign.
|
|
100
|
-
function abnormalEmptyFinishError(result, agent) {
|
|
101
|
-
// The loop (loop.mjs) is the single classifier: it tags terminationReason
|
|
102
|
-
// ONLY for abnormal finishes, and gates the `empty` case behind !hidden.
|
|
103
|
-
// So we key purely off terminationReason here — no separate content/hidden
|
|
104
|
-
// check, which previously (a) exempted hidden agents from cap/truncated and
|
|
105
|
-
// (b) let a capped tool-call turn with preamble text slip through as success.
|
|
106
|
-
const reason = result?.terminationReason;
|
|
107
|
-
if (!reason) return null;
|
|
108
|
-
const iterations = result?.iterations ?? 0;
|
|
109
|
-
const toolCallsTotal = result?.toolCallsTotal ?? 0;
|
|
110
|
-
const maxLoopIterations = result?.maxLoopIterations ?? 0;
|
|
111
|
-
const stopReason = result?.stopReason ?? result?.stop_reason ?? null;
|
|
112
|
-
switch (reason) {
|
|
113
|
-
case 'iteration_cap':
|
|
114
|
-
// Real problem for EVERY agent (hidden too): the loop never terminated on
|
|
115
|
-
// its own contract, so any preamble text is not a trustworthy final answer.
|
|
116
|
-
return `agent '${agent}' hit the loop iteration ceiling (${maxLoopIterations} iterations, ${toolCallsTotal} tool calls) without producing a final answer`;
|
|
117
|
-
case 'truncated':
|
|
118
|
-
return `agent '${agent}' response was truncated (stopReason=${stopReason}) before a final answer (${iterations} iterations, ${toolCallsTotal} tool calls)`;
|
|
119
|
-
case 'empty':
|
|
120
|
-
// Only tagged for PUBLIC agents (hidden agents legitimately emit empty
|
|
121
|
-
// terminal turns and are left untagged by the loop).
|
|
122
|
-
return `agent '${agent}' finished without a final answer (stopReason=${stopReason ?? 'none'}, ${iterations} iterations, ${toolCallsTotal} tool calls)`;
|
|
123
|
-
default:
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function envTimeoutMs(name, fallback) {
|
|
129
|
-
const raw = process.env[name];
|
|
130
|
-
if (raw === undefined || raw === '') return fallback;
|
|
131
|
-
const n = Number(raw);
|
|
132
|
-
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
64
|
// Grace window during which a terminated/idle worker row is kept around so the
|
|
136
65
|
// same terminal can re-use (or cleanly re-spawn) the same tag. Cached as a
|
|
137
66
|
// constant like the other timeouts; override with MIXDOG_AGENT_TERMINAL_REAP_MS.
|
|
138
|
-
|
|
67
|
+
// 5m (was 1h): aligned with the BP4 message-tail cache TTL — past 5m the
|
|
68
|
+
// session's prompt cache is cold anyway, so same-tag reuse pays a full prefix
|
|
69
|
+
// rewrite; a fresh respawn (send dead-tag fallback) starts smaller and cheaper.
|
|
70
|
+
// Trace replay (2026-07): agent tail cost 1h 1.77M -> 5m 1.11M (-37%).
|
|
71
|
+
const TERMINAL_REAP_MS = envTimeoutMs('MIXDOG_AGENT_TERMINAL_REAP_MS', 5 * 60_000);
|
|
72
|
+
// Provider-aware reap windows: the 5m default matches Anthropic's 5m agent
|
|
73
|
+
// tail TTL, but providers with long-lived automatic prefix caches (OpenAI 24h
|
|
74
|
+
// retention, DeepSeek hours-days, Groq 2h) keep a warm cache well past 5m, so
|
|
75
|
+
// same-tag reuse stays cheaper than a respawn. Capped at 1h — holding closed
|
|
76
|
+
// worker sessions longer hoards manager memory for a reuse pattern that
|
|
77
|
+
// rarely spans hours. Explicit MIXDOG_AGENT_TERMINAL_REAP_MS overrides all.
|
|
78
|
+
const PROVIDER_REAP_MS = {
|
|
79
|
+
'openai': 60 * 60_000,
|
|
80
|
+
'deepseek': 60 * 60_000,
|
|
81
|
+
'groq': 60 * 60_000,
|
|
82
|
+
'openai-oauth': 10 * 60_000,
|
|
83
|
+
};
|
|
84
|
+
function reapMsForProvider(provider) {
|
|
85
|
+
if (clean(process.env.MIXDOG_AGENT_TERMINAL_REAP_MS)) return TERMINAL_REAP_MS;
|
|
86
|
+
return PROVIDER_REAP_MS[clean(provider)] || TERMINAL_REAP_MS;
|
|
87
|
+
}
|
|
139
88
|
// Independent hard cap for the spawn *prep* phase (ensureProvider /
|
|
140
89
|
// prepareAgentSession / catalog+rules load). Kept separate from the
|
|
141
90
|
// first-response watchdog so prep cannot hang a whole fanout before the model
|
|
142
91
|
// request starts. Set MIXDOG_AGENT_SPAWN_PREP_TIMEOUT_MS=0 to fully disable the
|
|
143
92
|
// cap and restore strictly-unbounded prep.
|
|
144
93
|
const DEFAULT_SPAWN_PREP_TIMEOUT_MS = envTimeoutMs('MIXDOG_AGENT_SPAWN_PREP_TIMEOUT_MS', 120_000);
|
|
145
|
-
const ACTIVE_STAGES = new Set(['connecting', 'requesting', 'streaming', 'tool_running', 'running', 'cancelling']);
|
|
146
|
-
|
|
147
|
-
// Process-wide TTL cache for agent AGENT.md frontmatter permission. The file
|
|
148
|
-
// rarely changes, but readAgentFrontmatterPermission() otherwise pays several
|
|
149
|
-
// existsSync()+readFileSync() calls on EVERY spawn — multiplied across a
|
|
150
|
-
// parallel fanout that is pure redundant synchronous I/O on the event loop,
|
|
151
|
-
// which blocks every other concurrent spawn. Short TTL keeps edits picked up
|
|
152
|
-
// quickly while collapsing burst reads.
|
|
153
|
-
const _frontmatterPermCache = new Map(); // key -> { value, atMs }
|
|
154
|
-
const FRONTMATTER_PERM_CACHE_TTL_MS = envTimeoutMs('MIXDOG_AGENT_FRONTMATTER_TTL_MS', 5_000);
|
|
155
|
-
|
|
156
|
-
function clean(value) {
|
|
157
|
-
return String(value ?? '').trim();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function agentTagOf(session) {
|
|
161
|
-
return clean(session?.agentTag);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function normalizeAgentName(value) {
|
|
165
|
-
const id = clean(value).toLowerCase().replace(/[\s_]+/g, '-');
|
|
166
|
-
if (id === 'explorer') return 'explore';
|
|
167
|
-
if (id === 'maint' || id === 'maintenance' || id === 'memory') return 'maintainer';
|
|
168
|
-
if (id === 'heavy' || id === 'heavyworker') return 'heavy-worker';
|
|
169
|
-
if (id === 'review') return 'reviewer';
|
|
170
|
-
if (id === 'debug') return 'debugger';
|
|
171
|
-
return id;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function readAgentFrontmatterPermission(agent, dataDir) {
|
|
175
|
-
const cleanAgent = clean(agent);
|
|
176
|
-
if (!cleanAgent) return null;
|
|
177
|
-
const cacheKey = `${dataDir || ''}\u0000${cleanAgent}`;
|
|
178
|
-
const cached = _frontmatterPermCache.get(cacheKey);
|
|
179
|
-
if (cached && Date.now() - cached.atMs < FRONTMATTER_PERM_CACHE_TTL_MS) {
|
|
180
|
-
return cached.value;
|
|
181
|
-
}
|
|
182
|
-
const candidates = [];
|
|
183
|
-
if (dataDir) {
|
|
184
|
-
candidates.push(join(dataDir, 'agents', cleanAgent, 'AGENT.md'));
|
|
185
|
-
candidates.push(join(dataDir, 'agents', `${cleanAgent}.md`));
|
|
186
|
-
}
|
|
187
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', cleanAgent, 'AGENT.md'));
|
|
188
|
-
candidates.push(join(STANDALONE_SOURCE_ROOT, 'agents', `${cleanAgent}.md`));
|
|
189
|
-
let resolved = null;
|
|
190
|
-
for (const file of candidates) {
|
|
191
|
-
if (!existsSync(file)) continue;
|
|
192
|
-
const fm = parseMarkdownFrontmatter(readFileSync(file, 'utf8'));
|
|
193
|
-
const permission = normalizeAgentPermissionOrNone(fm.permission);
|
|
194
|
-
if (permission) { resolved = permission; break; }
|
|
195
|
-
}
|
|
196
|
-
_frontmatterPermCache.set(cacheKey, { value: resolved, atMs: Date.now() });
|
|
197
|
-
return resolved;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function positiveInt(value) {
|
|
201
|
-
const n = Number(value);
|
|
202
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function terminalPidForContext(context = {}) {
|
|
206
|
-
return positiveInt(context?.clientHostPid);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function agentScope(args = {}, context = {}) {
|
|
210
|
-
const scope = clean(args.scope || args.terminal || args.term).toLowerCase();
|
|
211
|
-
if (args.allTerminals === true || scope === 'all' || scope === 'global') return {};
|
|
212
|
-
return context || {};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function sessionMatchesContext(session, context = {}) {
|
|
216
|
-
const wantedPid = terminalPidForContext(context);
|
|
217
|
-
if (!wantedPid) return true;
|
|
218
|
-
const sessionPid = positiveInt(session?.clientHostPid);
|
|
219
|
-
return !!sessionPid && sessionPid === wantedPid;
|
|
220
|
-
}
|
|
221
94
|
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function nonNegativeInt(value) {
|
|
230
|
-
if (value === undefined || value === null || value === '') return null;
|
|
231
|
-
const n = Number(value);
|
|
232
|
-
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function presetKey(preset) {
|
|
236
|
-
return clean(preset?.id || preset?.name);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function bridgeRouteForStatusline(preset = {}) {
|
|
240
|
-
const provider = clean(preset.provider);
|
|
241
|
-
const model = clean(preset.model);
|
|
242
|
-
if (!provider || !model) return null;
|
|
243
|
-
const out = {
|
|
244
|
-
mode: 'fixed',
|
|
245
|
-
defaultProvider: provider,
|
|
246
|
-
defaultModel: model,
|
|
247
|
-
};
|
|
248
|
-
const id = clean(preset.id);
|
|
249
|
-
const name = clean(preset.name);
|
|
250
|
-
const modelDisplay = clean(preset.modelDisplay || preset.display || preset.displayName);
|
|
251
|
-
const effort = clean(preset.effort);
|
|
252
|
-
if (id) out.presetId = id;
|
|
253
|
-
if (name) out.presetName = name;
|
|
254
|
-
if (modelDisplay) out.modelDisplay = modelDisplay;
|
|
255
|
-
if (effort) {
|
|
256
|
-
out.effort = effort;
|
|
257
|
-
out.displayEffort = effort;
|
|
95
|
+
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd, onSubagentEvent }) {
|
|
96
|
+
// Optional bridge to the standard hook bus for SubagentStart / SubagentStop.
|
|
97
|
+
// Best-effort: a hook error must never affect worker spawn/finish.
|
|
98
|
+
function emitSubagentEvent(phase, agent, extra = {}) {
|
|
99
|
+
if (typeof onSubagentEvent !== 'function') return;
|
|
100
|
+
try { onSubagentEvent(phase, { agent_type: agent || null, ...extra }); } catch { /* best-effort */ }
|
|
258
101
|
}
|
|
259
|
-
if (preset.fast === true || preset.fast === false) out.fast = preset.fast;
|
|
260
|
-
return out;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function writeAgentStatuslineRoute(sessionId, preset) {
|
|
264
|
-
const route = bridgeRouteForStatusline(preset);
|
|
265
|
-
if (!sessionId || !route) return false;
|
|
266
|
-
try { return writeGatewaySessionRoute(sessionId, route); } catch { return false; }
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function clearAgentStatuslineRoute(sessionId) {
|
|
270
|
-
if (!sessionId) return false;
|
|
271
|
-
try { return clearGatewaySessionRoute(sessionId); } catch { return false; }
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function findPreset(config, key) {
|
|
275
|
-
const wanted = clean(key).toLowerCase();
|
|
276
|
-
if (!wanted) return null;
|
|
277
|
-
const presets = Array.isArray(config?.presets) ? config.presets : [];
|
|
278
|
-
return presets.find((p) => {
|
|
279
|
-
return clean(p?.id).toLowerCase() === wanted || clean(p?.name).toLowerCase() === wanted;
|
|
280
|
-
}) || null;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function synthesizePreset(config, key) {
|
|
284
|
-
const alias = PRESET_ALIASES.get(clean(key).toLowerCase());
|
|
285
|
-
if (!alias) return null;
|
|
286
|
-
const base = findPreset(config, alias.base);
|
|
287
|
-
if (!base) return null;
|
|
288
|
-
return {
|
|
289
|
-
...base,
|
|
290
|
-
id: alias.id,
|
|
291
|
-
name: alias.name,
|
|
292
|
-
effort: alias.effort,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function normalizeAgentRoute(routeLike, fallbackProvider = '') {
|
|
297
|
-
const provider = clean(routeLike?.provider) || clean(fallbackProvider);
|
|
298
|
-
const model = clean(routeLike?.model);
|
|
299
|
-
if (!provider || !model) return null;
|
|
300
|
-
return {
|
|
301
|
-
provider,
|
|
302
|
-
model,
|
|
303
|
-
effort: clean(routeLike?.effort) || undefined,
|
|
304
|
-
fast: routeLike?.fast === true,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function agentPresetName(agent) {
|
|
309
|
-
return `AGENT ${String(agent || '').toUpperCase()}`;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async function resolvePrompt(args, cwd) {
|
|
313
|
-
const prompt = clean(args.prompt || args.message);
|
|
314
|
-
const file = clean(args.file);
|
|
315
|
-
if (prompt && file) throw new Error('agent: provide only one of prompt/message or file');
|
|
316
|
-
if (prompt) return prompt;
|
|
317
|
-
if (file) {
|
|
318
|
-
const target = isAbsolute(file) ? file : resolve(cwd || process.cwd(), file);
|
|
319
|
-
return readFileSync(target, 'utf8');
|
|
320
|
-
}
|
|
321
|
-
throw new Error('agent: prompt/message/file is required');
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function withCwdHeader(prompt, cwd) {
|
|
325
|
-
if (!cwd) return prompt;
|
|
326
|
-
if (String(prompt).startsWith('[effective-cwd]')) return prompt;
|
|
327
|
-
return `[effective-cwd] ${cwd}\n\n${prompt}`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function oneLine(value, max = 180) {
|
|
331
|
-
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
332
|
-
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function compactIso(value) {
|
|
336
|
-
const text = clean(value);
|
|
337
|
-
if (!text) return '';
|
|
338
|
-
return text.replace('T', ' ').replace(/\.\d+Z$/, 'Z');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function formatElapsedMs(ms) {
|
|
342
|
-
const n = Number(ms);
|
|
343
|
-
if (!Number.isFinite(n) || n < 0) return '';
|
|
344
|
-
const totalSec = Math.floor(n / 1000);
|
|
345
|
-
if (totalSec < 60) return `${totalSec}s`;
|
|
346
|
-
const min = Math.floor(totalSec / 60);
|
|
347
|
-
const sec = totalSec % 60;
|
|
348
|
-
if (min < 60) return sec ? `${min}m${sec}s` : `${min}m`;
|
|
349
|
-
const hr = Math.floor(min / 60);
|
|
350
|
-
const remMin = min % 60;
|
|
351
|
-
return remMin ? `${hr}h${remMin}m` : `${hr}h`;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function elapsedFromStamps(startedAt, finishedAt, status) {
|
|
355
|
-
const start = Date.parse(clean(startedAt));
|
|
356
|
-
if (!Number.isFinite(start)) return null;
|
|
357
|
-
const finish = Date.parse(clean(finishedAt));
|
|
358
|
-
const end = Number.isFinite(finish) ? finish : Date.now();
|
|
359
|
-
const label = formatElapsedMs(end - start);
|
|
360
|
-
if (!label) return null;
|
|
361
|
-
return Number.isFinite(finish) ? label : `${label} (running)`;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function stripFinalAnswerWrapper(value) {
|
|
365
|
-
const text = String(value ?? '').trim();
|
|
366
|
-
const match = /^<final-answer\b[^>]*>([\s\S]*?)<\/final-answer>\s*$/i.exec(text);
|
|
367
|
-
return match ? match[1].trim() : text;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function renderResult(value) {
|
|
371
|
-
if (value === undefined || value === null) return '';
|
|
372
|
-
if (typeof value === 'string') return value;
|
|
373
|
-
if (value && typeof value === 'object') {
|
|
374
|
-
const lines = [];
|
|
375
|
-
|
|
376
|
-
if (Array.isArray(value.workers) || Array.isArray(value.jobs)) {
|
|
377
|
-
const workers = Array.isArray(value.workers) ? value.workers : [];
|
|
378
|
-
lines.push(`agents: ${workers.length}`);
|
|
379
|
-
for (const worker of workers) {
|
|
380
|
-
const tokens = worker.windowTokens ? ` ctx=${worker.windowTokens}${worker.windowCap ? `/${worker.windowCap}` : ''}` : '';
|
|
381
|
-
const terminal = worker.clientHostPid ? ` term=${worker.clientHostPid}` : '';
|
|
382
|
-
const base = `- ${worker.tag} ${worker.agent || 'agent'} ${worker.status || 'idle'}/${worker.worker_stage || worker.stage || 'idle'} ${worker.provider}/${worker.model}${terminal}${tokens}`;
|
|
383
|
-
lines.push(appendAgentProgressKv(base, worker));
|
|
384
|
-
}
|
|
385
|
-
const jobs = Array.isArray(value.jobs) ? value.jobs : [];
|
|
386
|
-
lines.push(`tasks: ${jobs.length}`);
|
|
387
|
-
for (const job of jobs) {
|
|
388
|
-
const target = job.tag || job.sessionId || '-';
|
|
389
|
-
const terminal = job.clientHostPid ? ` term=${job.clientHostPid}` : '';
|
|
390
|
-
const base = `- ${job.task_id} ${job.type} ${job.status} target=${target}${terminal}${job.error ? ` error=${presentErrorText(job.error, { surface: 'agent' })}` : ''}`;
|
|
391
|
-
lines.push(appendAgentProgressKv(base, job));
|
|
392
|
-
}
|
|
393
|
-
if (workers.length === 0 && jobs.length === 0) lines.push('(no agents or tasks)');
|
|
394
|
-
return lines.join('\n');
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (value.task_id) {
|
|
398
|
-
lines.push(`agent task: ${value.task_id}`);
|
|
399
|
-
lines.push(`status: ${value.status}`);
|
|
400
|
-
if (value.type) lines.push(`type: ${value.type}`);
|
|
401
|
-
if (value.reused) lines.push('reused: true');
|
|
402
|
-
if (value.tag || value.sessionId) lines.push(`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim());
|
|
403
|
-
if (value.agent) lines.push(`agent: ${value.agent}`);
|
|
404
|
-
if (value.provider && value.model) lines.push(`model: ${value.provider}/${value.model}`);
|
|
405
|
-
if (value.effort) lines.push(`effort: ${value.effort}`);
|
|
406
|
-
if (value.fast === true || value.fast === false) lines.push(`fast: ${value.fast ? 'on' : 'off'}`);
|
|
407
|
-
if (value.maxLoopIterations) {
|
|
408
|
-
const limitParts = [];
|
|
409
|
-
if (value.maxLoopIterations) limitParts.push(`loop=${value.maxLoopIterations}`);
|
|
410
|
-
lines.push(`limits: ${limitParts.join(' ')}`);
|
|
411
|
-
}
|
|
412
|
-
if (value.stage || value.workerStatus) lines.push(`worker: ${value.workerStatus || 'unknown'}/${value.stage || 'unknown'}`);
|
|
413
|
-
if (value.worker_stage) lines.push(`worker_stage: ${value.worker_stage}`);
|
|
414
|
-
if (value.last_progress) lines.push(`last_progress: ${value.last_progress}`);
|
|
415
|
-
if (Number.isFinite(value.silent_for)) lines.push(`silent_for: ${value.silent_for}s`);
|
|
416
|
-
if (value.watchdog) lines.push(`watchdog: ${value.watchdog}`);
|
|
417
|
-
if (Number.isFinite(value.queued_followups)) lines.push(`queued_followups: ${value.queued_followups}`);
|
|
418
|
-
if (value.diagnostic) lines.push(`diagnostic: ${value.diagnostic}`);
|
|
419
|
-
if (value.startedAt) lines.push(`started: ${compactIso(value.startedAt)}`);
|
|
420
|
-
if (value.finishedAt) lines.push(`finished: ${compactIso(value.finishedAt)}`);
|
|
421
|
-
{
|
|
422
|
-
const elapsed = elapsedFromStamps(value.startedAt, value.finishedAt, value.status);
|
|
423
|
-
if (elapsed) lines.push(`elapsed: ${elapsed}`);
|
|
424
|
-
}
|
|
425
|
-
if (value.error) lines.push(`error: ${presentErrorText(value.error, { surface: 'agent' })}`);
|
|
426
|
-
if (value.status === 'running') lines.push('notification: completion will be delivered to the owner session; use read/status only for manual recovery.');
|
|
427
|
-
if (value.result !== undefined) {
|
|
428
|
-
const result = value.result;
|
|
429
|
-
const content = typeof result === 'string' ? result : result?.content;
|
|
430
|
-
if (content) lines.push('', stripFinalAnswerWrapper(content));
|
|
431
|
-
else lines.push('', JSON.stringify(result, null, 2));
|
|
432
|
-
}
|
|
433
|
-
return lines.join('\n');
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (value.queued) {
|
|
437
|
-
return [
|
|
438
|
-
'agent message queued',
|
|
439
|
-
value.reused ? 'reused: true' : null,
|
|
440
|
-
`target: ${value.tag || '-'} ${value.sessionId || ''}`.trim(),
|
|
441
|
-
value.agent ? `agent: ${value.agent}` : null,
|
|
442
|
-
`queueDepth: ${value.queueDepth ?? 1}`,
|
|
443
|
-
].filter(Boolean).join('\n');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (value.closed !== undefined) {
|
|
447
|
-
return [
|
|
448
|
-
`agent close: ${value.closed ? 'ok' : 'not closed'}`,
|
|
449
|
-
value.tag ? `tag: ${value.tag}` : null,
|
|
450
|
-
value.sessionId ? `sessionId: ${value.sessionId}` : null,
|
|
451
|
-
value.task_id ? `task_id: ${value.task_id}` : null,
|
|
452
|
-
value.forgotten ? 'forgotten: true' : null,
|
|
453
|
-
].filter(Boolean).join('\n');
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (value.content !== undefined) {
|
|
457
|
-
const header = [
|
|
458
|
-
value.respawned ? 'agent respawned' : 'agent result',
|
|
459
|
-
value.tag ? `tag=${value.tag}` : null,
|
|
460
|
-
value.agent ? `agent=${value.agent}` : null,
|
|
461
|
-
value.provider && value.model ? `${value.provider}/${value.model}` : null,
|
|
462
|
-
].filter(Boolean).join(' ');
|
|
463
|
-
return `${header}\n${stripFinalAnswerWrapper(value.content)}`;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
return JSON.stringify(value, null, 2);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd }) {
|
|
470
102
|
const tags = new Map();
|
|
471
103
|
const tagAgents = new Map();
|
|
472
104
|
const tagCwds = new Map();
|
|
@@ -494,7 +126,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
494
126
|
const t = workerRowTime(row);
|
|
495
127
|
if (!t) return true;
|
|
496
128
|
if (!isTerminalWorkerStatus(row.status || row.stage)) return true;
|
|
497
|
-
return Date.now() - t <
|
|
129
|
+
return Date.now() - t < reapMsForProvider(row.provider);
|
|
498
130
|
}
|
|
499
131
|
|
|
500
132
|
function normalizeWorkerRows(value) {
|
|
@@ -903,9 +535,10 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
903
535
|
return true;
|
|
904
536
|
}
|
|
905
537
|
|
|
906
|
-
function scheduleReap(sessionId) {
|
|
538
|
+
function scheduleReap(sessionId, provider = null) {
|
|
907
539
|
if (!sessionId) return;
|
|
908
540
|
cancelReap(sessionId);
|
|
541
|
+
const reapProvider = provider || getLiveSession(sessionId)?.provider || null;
|
|
909
542
|
const handle = setTimeout(() => {
|
|
910
543
|
reapTimers.delete(sessionId);
|
|
911
544
|
try { mgr.hideSessionFromList?.(sessionId); } catch {}
|
|
@@ -914,7 +547,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
914
547
|
removeWorkerRow({ tag, sessionId });
|
|
915
548
|
clearAgentStatuslineRoute(sessionId);
|
|
916
549
|
try { mgr.closeSession(sessionId, 'terminal-reap'); } catch {}
|
|
917
|
-
},
|
|
550
|
+
}, reapMsForProvider(reapProvider));
|
|
918
551
|
handle.unref?.();
|
|
919
552
|
reapTimers.set(sessionId, handle);
|
|
920
553
|
}
|
|
@@ -927,140 +560,10 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
927
560
|
return ACTIVE_STAGES.has(session?.status || '');
|
|
928
561
|
}
|
|
929
562
|
|
|
930
|
-
// Provider init de-dup
|
|
931
|
-
//
|
|
932
|
-
//
|
|
933
|
-
|
|
934
|
-
// (b) a provider CONFIG CHANGE still reaches initProviders() so the
|
|
935
|
-
// registry's own signature guard can re-initialize it,
|
|
936
|
-
// (c) two DIFFERENT config signatures for the same provider never init
|
|
937
|
-
// concurrently — otherwise a slow init of the OLD config could land
|
|
938
|
-
// after a fast init of the NEW config and revert the live registry to
|
|
939
|
-
// stale config, and
|
|
940
|
-
// (d) a SUPERSEDED request never resolves before the provider is actually
|
|
941
|
-
// ready: even when its own (stale) init is dropped to satisfy (c), the
|
|
942
|
-
// caller (a spawn about to run prepareSpawn) must still WAIT for the
|
|
943
|
-
// latest init to finish, or it would proceed against an unprepared /
|
|
944
|
-
// stale provider.
|
|
945
|
-
//
|
|
946
|
-
// Skip cache + in-flight collapse are keyed on `provider + signature(effective
|
|
947
|
-
// config)`. To satisfy (c) we SERIALIZE all inits per provider on a chain
|
|
948
|
-
// promise and re-check the latest-requested signature inside the chain: a
|
|
949
|
-
// request superseded by a newer signature drops its own init. To satisfy (d)
|
|
950
|
-
// such a dropped request does not resolve immediately — it awaits the
|
|
951
|
-
// provider's latest settled init (tracked as a rolling "ready" promise) so the
|
|
952
|
-
// caller only proceeds once the newest config is live.
|
|
953
|
-
// Per-provider state. `chain` serializes the ACTUAL initProviders() calls so
|
|
954
|
-
// two different config signatures never run concurrently (goal c). `latestGen`
|
|
955
|
-
// / `latestSig` track the newest requested config. `ready` is a rolling
|
|
956
|
-
// deferred that resolves only when the LATEST requested init has completed —
|
|
957
|
-
// a superseded caller awaits the ready deferred captured at call time, and
|
|
958
|
-
// when a newer request arrives the older deferred ADOPTS the newer one, so a
|
|
959
|
-
// superseded caller transitively waits for the latest init (goal d).
|
|
960
|
-
const _providerState = new Map(); // provider -> state
|
|
961
|
-
const _providerInitPending = new Map(); // provider -> { sigKey, promise } identical-sig collapse
|
|
962
|
-
// Upper bound on how long a queued init waits for the PRIOR chain link before
|
|
963
|
-
// proceeding anyway. A prior init that HANGS (never settles) must not poison
|
|
964
|
-
// the chain and wedge every later request behind it. A hung init can never
|
|
965
|
-
// *complete* against the registry, so it cannot land-after and clobber a
|
|
966
|
-
// newer config (goal c only fears slow-but-completing inits) — so proceeding
|
|
967
|
-
// once the gate expires is safe. Defaults to the spawn-prep cap; 0 disables.
|
|
968
|
-
const PROVIDER_CHAIN_GATE_MS = DEFAULT_SPAWN_PREP_TIMEOUT_MS;
|
|
969
|
-
function providerRegistered(provider) {
|
|
970
|
-
return typeof reg.getProvider !== 'function' || Boolean(reg.getProvider(provider));
|
|
971
|
-
}
|
|
972
|
-
function effectiveProviderConfig(config, provider) {
|
|
973
|
-
const providers = { ...(config.providers || {}) };
|
|
974
|
-
providers[provider] = { ...(providers[provider] || {}), enabled: true };
|
|
975
|
-
return providers;
|
|
976
|
-
}
|
|
977
|
-
function providerStateFor(provider) {
|
|
978
|
-
let s = _providerState.get(provider);
|
|
979
|
-
if (!s) {
|
|
980
|
-
s = { chain: Promise.resolve(), completedSig: null, latestSig: null, latestGen: 0, ready: null };
|
|
981
|
-
_providerState.set(provider, s);
|
|
982
|
-
}
|
|
983
|
-
return s;
|
|
984
|
-
}
|
|
985
|
-
function providerInitSignature(provider, effectiveProviders) {
|
|
986
|
-
let body;
|
|
987
|
-
try { body = JSON.stringify(effectiveProviders); }
|
|
988
|
-
catch { body = String(Date.now()); } // unserializable → force a fresh init
|
|
989
|
-
return `${provider}\u0000${body}`;
|
|
990
|
-
}
|
|
991
|
-
function gateOnPrior(prior) {
|
|
992
|
-
const settled = Promise.resolve(prior).catch(() => {});
|
|
993
|
-
if (!(PROVIDER_CHAIN_GATE_MS > 0)) return settled;
|
|
994
|
-
return new Promise((resolve) => {
|
|
995
|
-
let done = false;
|
|
996
|
-
const finish = () => { if (!done) { done = true; resolve(); } };
|
|
997
|
-
const timer = setTimeout(finish, PROVIDER_CHAIN_GATE_MS);
|
|
998
|
-
timer.unref?.();
|
|
999
|
-
settled.then(() => { clearTimeout(timer); finish(); }, () => { clearTimeout(timer); finish(); });
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
function ensureProvider(config, provider) {
|
|
1003
|
-
const effective = effectiveProviderConfig(config, provider);
|
|
1004
|
-
const sigKey = providerInitSignature(provider, effective);
|
|
1005
|
-
const registered = () => providerRegistered(provider);
|
|
1006
|
-
const s = providerStateFor(provider);
|
|
1007
|
-
// Completed-skip: this exact effective config is already live for this
|
|
1008
|
-
// provider. A config change flips sigKey so we fall through; a torn-down
|
|
1009
|
-
// provider (no longer registered) also does.
|
|
1010
|
-
if (s.completedSig === sigKey && registered()) return Promise.resolve();
|
|
1011
|
-
// Identical-sig collapse: a request with the SAME sigKey is already in
|
|
1012
|
-
// flight — share its caller promise.
|
|
1013
|
-
const pending = _providerInitPending.get(provider);
|
|
1014
|
-
if (pending && pending.sigKey === sigKey) return pending.promise;
|
|
1015
|
-
// New generation. Repoint the rolling `ready` deferred to THIS gen and make
|
|
1016
|
-
// the previous gen's deferred ADOPT the new one, so any superseded caller
|
|
1017
|
-
// awaiting an older deferred transitively waits for the newest init (d).
|
|
1018
|
-
const gen = ++s.latestGen;
|
|
1019
|
-
s.latestSig = sigKey;
|
|
1020
|
-
const prevReady = s.ready;
|
|
1021
|
-
let resolveReady;
|
|
1022
|
-
const readyPromise = new Promise((r) => { resolveReady = r; });
|
|
1023
|
-
s.ready = { gen, promise: readyPromise, resolve: resolveReady };
|
|
1024
|
-
if (prevReady && prevReady.gen < gen) {
|
|
1025
|
-
try { prevReady.resolve(readyPromise); } catch { /* already settled */ }
|
|
1026
|
-
}
|
|
1027
|
-
// Serialize the ACTUAL init behind the prior chain link (gated so a hung
|
|
1028
|
-
// prior cannot wedge the chain). A superseded gen's chain link settles
|
|
1029
|
-
// quickly — it never awaits a later gen — so there is no deadlock.
|
|
1030
|
-
const prior = s.chain;
|
|
1031
|
-
const chainLink = gateOnPrior(prior).then(async () => {
|
|
1032
|
-
if (s.latestGen !== gen) {
|
|
1033
|
-
// Superseded before we ran: drop our (stale) init entirely (goal c).
|
|
1034
|
-
// Our `ready` deferred already adopts the newer gen, so the caller below
|
|
1035
|
-
// still waits for the latest init. Settle now to release the chain.
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
try {
|
|
1039
|
-
if (!(s.completedSig === sigKey && registered())) {
|
|
1040
|
-
await reg.initProviders(effective);
|
|
1041
|
-
s.completedSig = sigKey;
|
|
1042
|
-
}
|
|
1043
|
-
} finally {
|
|
1044
|
-
// ALWAYS release this gen's waiters once we are the latest — even on a
|
|
1045
|
-
// registry init failure. Adopting (superseded) callers chained onto this
|
|
1046
|
-
// deferred would otherwise hang forever; instead they proceed and their
|
|
1047
|
-
// own createSession()/prep-timeout surfaces the unprepared provider.
|
|
1048
|
-
resolveReady();
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
// Next chain link waits on us (settled, never poisoned).
|
|
1052
|
-
s.chain = chainLink.catch(() => {});
|
|
1053
|
-
// The CALLER awaits the ready deferred (resolves only when the LATEST init
|
|
1054
|
-
// for this provider completes), not just the chain link — so a superseded
|
|
1055
|
-
// caller blocks until the newest config is live (goal d). chainLink is
|
|
1056
|
-
// awaited first so a registry init error surfaces to this caller.
|
|
1057
|
-
const callerPromise = chainLink.then(() => readyPromise).finally(() => {
|
|
1058
|
-
const cur = _providerInitPending.get(provider);
|
|
1059
|
-
if (cur && cur.promise === callerPromise) _providerInitPending.delete(provider);
|
|
1060
|
-
});
|
|
1061
|
-
_providerInitPending.set(provider, { sigKey, promise: callerPromise });
|
|
1062
|
-
return callerPromise;
|
|
1063
|
-
}
|
|
563
|
+
// Provider init de-dup lives in ./agent-tool/provider-init.mjs; the factory
|
|
564
|
+
// keeps its per-provider chain/ready state private per agent instance. The
|
|
565
|
+
// chain-gate defaults to the spawn-prep cap (see provider-init.mjs comments).
|
|
566
|
+
const { ensureProvider } = createProviderInit(reg, DEFAULT_SPAWN_PREP_TIMEOUT_MS);
|
|
1064
567
|
|
|
1065
568
|
function resolvePreset(config, args) {
|
|
1066
569
|
if (args.provider && args.model) {
|
|
@@ -1218,9 +721,23 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1218
721
|
|
|
1219
722
|
function getJob(args, context = {}) {
|
|
1220
723
|
const taskId = taskIdFromArgs(args);
|
|
1221
|
-
if (
|
|
1222
|
-
|
|
1223
|
-
|
|
724
|
+
if (taskId) {
|
|
725
|
+
const task = getBackgroundTask(taskId, { surface: 'agent', context });
|
|
726
|
+
if (!task) throw new Error(`agent read/status: task "${taskId}" not found`);
|
|
727
|
+
return task;
|
|
728
|
+
}
|
|
729
|
+
// Fall back to tag/sessionId resolution, same precedence as close()
|
|
730
|
+
// (agent-tool.mjs close()): clean(args.tag || args.sessionId || ...).
|
|
731
|
+
const target = clean(args.tag || args.sessionId);
|
|
732
|
+
if (!target) throw new Error('agent read/status: task_id, tag, or sessionId is required');
|
|
733
|
+
const candidates = listBackgroundTasks({ surface: 'agent', context })
|
|
734
|
+
.filter(Boolean)
|
|
735
|
+
.filter((row) => row.tag === target || row.sessionId === target);
|
|
736
|
+
if (!candidates.length) throw new Error(`agent read/status: no task found for tag/sessionId "${target}"`);
|
|
737
|
+
// Prefer most recent when multiple tasks match the same tag.
|
|
738
|
+
candidates.sort((a, b) => new Date(b.startedAt || 0) - new Date(a.startedAt || 0));
|
|
739
|
+
const task = getBackgroundTask(candidates[0].task_id, { surface: 'agent', context });
|
|
740
|
+
if (!task) throw new Error(`agent read/status: task "${candidates[0].task_id}" not found`);
|
|
1224
741
|
return task;
|
|
1225
742
|
}
|
|
1226
743
|
|
|
@@ -1246,6 +763,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1246
763
|
tag: meta.tag || null,
|
|
1247
764
|
sessionId: meta.sessionId || null,
|
|
1248
765
|
agent: meta.agent || null,
|
|
766
|
+
...(meta.respawned === true ? { respawned: true, note: 'previous session reaped — fresh session, no prior context; re-supply anchors if needed' } : {}),
|
|
1249
767
|
preset: meta.preset || null,
|
|
1250
768
|
provider: meta.provider || null,
|
|
1251
769
|
model: meta.model || null,
|
|
@@ -1323,102 +841,9 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1323
841
|
removeWorkerRow({ tag: prepared.tag, sessionId: prepared.session.id });
|
|
1324
842
|
}
|
|
1325
843
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
try {
|
|
1330
|
-
const visible = modelVisibleToolCompletionMessage(text, meta);
|
|
1331
|
-
return Boolean(visible && mgr.enqueuePendingMessage(target, visible) > 0);
|
|
1332
|
-
} catch {
|
|
1333
|
-
return false;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Wire the canonical completion fallback to this agent surface's owner-session
|
|
1338
|
-
// enqueue so notifyTaskCompletion can deliver via callerSessionId when no
|
|
1339
|
-
// notifyFn is present or it declines. Registered once per agent (the closure
|
|
1340
|
-
// captures mgr); signatures align: (callerSessionId, message, meta).
|
|
1341
|
-
setBackgroundTaskEnqueueFallback((sessionId, text, meta) => enqueueCompletionMessage(sessionId, text, meta));
|
|
1342
|
-
|
|
1343
|
-
function workerNotifyFn(workerSessionId, notifyContext = {}) {
|
|
1344
|
-
const workerId = clean(workerSessionId);
|
|
1345
|
-
const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
|
|
1346
|
-
const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
|
|
1347
|
-
return (text, meta = {}) => {
|
|
1348
|
-
let ownerDelivered = false;
|
|
1349
|
-
if (upstream) {
|
|
1350
|
-
try {
|
|
1351
|
-
const result = upstream(text, meta);
|
|
1352
|
-
ownerDelivered = result !== false;
|
|
1353
|
-
if (ownerDelivered) Promise.resolve(result).catch(() => {});
|
|
1354
|
-
} catch {
|
|
1355
|
-
ownerDelivered = false;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
if (!ownerDelivered && ownerSessionId) {
|
|
1359
|
-
ownerDelivered = enqueueCompletionMessage(ownerSessionId, text, meta);
|
|
1360
|
-
}
|
|
1361
|
-
const workerDelivered = workerId && workerId !== ownerSessionId
|
|
1362
|
-
? enqueueCompletionMessage(workerId, text, meta)
|
|
1363
|
-
: ownerDelivered;
|
|
1364
|
-
return ownerSessionId ? ownerDelivered : workerDelivered;
|
|
1365
|
-
};
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
function notifyOwnerAgentCompletionEarly(job, resultValue, notifyContext = {}) {
|
|
1369
|
-
if (!job || job._earlyCompletionNotified === true) return false;
|
|
1370
|
-
const ownerSessionId = clean(notifyContext?.callerSessionId || notifyContext?.sessionId);
|
|
1371
|
-
const upstream = typeof notifyContext?.notifyFn === 'function' ? notifyContext.notifyFn : null;
|
|
1372
|
-
const finishedAt = new Date().toISOString();
|
|
1373
|
-
// An abnormal-empty finish carries an `error` — the early preview must NOT
|
|
1374
|
-
// present it as a benign `completed` card, or the Lead sees success before
|
|
1375
|
-
// the later `failed` reconcile lands. Mirror the terminal status/instruction.
|
|
1376
|
-
const earlyStatus = resultValue && resultValue.error ? 'failed' : 'completed';
|
|
1377
|
-
const snapshot = {
|
|
1378
|
-
...job,
|
|
1379
|
-
status: earlyStatus,
|
|
1380
|
-
finishedAt,
|
|
1381
|
-
finishedAtMs: Date.now(),
|
|
1382
|
-
result: resultValue,
|
|
1383
|
-
resultType: job.resultType || 'agent_task_result',
|
|
1384
|
-
meta: sanitizeTaskMeta(job.meta || {}),
|
|
1385
|
-
...(resultValue && resultValue.error ? { error: resultValue.error } : {}),
|
|
1386
|
-
};
|
|
1387
|
-
// An early notification is only a header-only *preview*: it fires before
|
|
1388
|
-
// the worker's session is persisted to signal the running→completed
|
|
1389
|
-
// transition. It deliberately carries NO result body — the canonical
|
|
1390
|
-
// notifyTaskCompletion delivers the body exactly once via the
|
|
1391
|
-
// reconcile/finally path, so omitting it here keeps notifications
|
|
1392
|
-
// exact-once with no duplicate body.
|
|
1393
|
-
const text = renderBackgroundTask(snapshot, { includeResult: false });
|
|
1394
|
-
const meta = {
|
|
1395
|
-
type: snapshot.resultType,
|
|
1396
|
-
execution_surface: 'agent',
|
|
1397
|
-
execution_id: job.taskId || null,
|
|
1398
|
-
status: earlyStatus,
|
|
1399
|
-
instruction: `The async agent task ${job.taskId || ''} has finished (${earlyStatus}) - review this result in your next step.`,
|
|
1400
|
-
...(ownerSessionId ? { caller_session_id: ownerSessionId } : {}),
|
|
1401
|
-
};
|
|
1402
|
-
let delivered = false;
|
|
1403
|
-
if (upstream) {
|
|
1404
|
-
try {
|
|
1405
|
-
const result = upstream(text, meta);
|
|
1406
|
-
delivered = result !== false;
|
|
1407
|
-
if (delivered) Promise.resolve(result).catch(() => {});
|
|
1408
|
-
} catch {
|
|
1409
|
-
delivered = false;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
if (!delivered && ownerSessionId) {
|
|
1413
|
-
delivered = enqueueCompletionMessage(ownerSessionId, text, meta);
|
|
1414
|
-
}
|
|
1415
|
-
if (delivered) {
|
|
1416
|
-
// Mark only that a header-only preview fired. The canonical
|
|
1417
|
-
// notifyTaskCompletion still owns the single body-carrying notification.
|
|
1418
|
-
job._earlyCompletionNotified = true;
|
|
1419
|
-
}
|
|
1420
|
-
return delivered;
|
|
1421
|
-
}
|
|
844
|
+
// Owner/worker completion notification lives in ./agent-tool/notify.mjs; the
|
|
845
|
+
// factory captures mgr and registers the canonical completion fallback.
|
|
846
|
+
const { workerNotifyFn, notifyOwnerAgentCompletionEarly } = createNotify(mgr);
|
|
1422
847
|
|
|
1423
848
|
function startJob(type, meta, run, notifyContext = null) {
|
|
1424
849
|
const clientHostPid = terminalPidForContext(notifyContext);
|
|
@@ -1545,7 +970,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1545
970
|
const config = cfgMod.loadConfig();
|
|
1546
971
|
const agent = normalizeAgentName(args.agent);
|
|
1547
972
|
if (!agent) throw new Error('agent spawn: agent is required');
|
|
1548
|
-
const agentPermission = readAgentFrontmatterPermission(agent, dataDir);
|
|
973
|
+
const agentPermission = readAgentFrontmatterPermission(agent, dataDir, STANDALONE_SOURCE_ROOT);
|
|
1549
974
|
const agentPerm = normalizeAgentPermission(agentPermission) || null;
|
|
1550
975
|
const { presetName, preset } = resolvePreset(config, args);
|
|
1551
976
|
await ensureProvider(config, preset.provider);
|
|
@@ -1619,6 +1044,8 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1619
1044
|
const { args, tag, session, agent, preset, presetName, workerCwd, prompt, watchdogPolicy } = prepared;
|
|
1620
1045
|
const watchdog = startProgressIdleWatchdog(session.id, watchdogPolicy);
|
|
1621
1046
|
let finalStatus = 'idle';
|
|
1047
|
+
// SubagentStart: a worker session is about to run its first turn.
|
|
1048
|
+
emitSubagentEvent('start', agent, { session_id: session.id, tag });
|
|
1622
1049
|
upsertWorkerSessionDeferred(session, tag, {
|
|
1623
1050
|
agent,
|
|
1624
1051
|
preset: presetKey(preset) || presetName,
|
|
@@ -1710,7 +1137,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1710
1137
|
try {
|
|
1711
1138
|
reconcileBackgroundTask(job.taskId, {
|
|
1712
1139
|
status: 'failed',
|
|
1713
|
-
error
|
|
1140
|
+
error,
|
|
1714
1141
|
terminalReason: 'agent-stream-stalled',
|
|
1715
1142
|
});
|
|
1716
1143
|
} catch {}
|
|
@@ -1747,6 +1174,8 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1747
1174
|
} catch {}
|
|
1748
1175
|
}
|
|
1749
1176
|
scheduleReap(session.id);
|
|
1177
|
+
// SubagentStop: worker finished (terminal), regardless of outcome.
|
|
1178
|
+
emitSubagentEvent('stop', agent, { session_id: session.id, tag, status: finalStatus });
|
|
1750
1179
|
}
|
|
1751
1180
|
}
|
|
1752
1181
|
|
|
@@ -1836,7 +1265,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1836
1265
|
try {
|
|
1837
1266
|
reconcileBackgroundTask(job.taskId, {
|
|
1838
1267
|
status: 'failed',
|
|
1839
|
-
error
|
|
1268
|
+
error,
|
|
1840
1269
|
terminalReason: 'agent-stream-stalled',
|
|
1841
1270
|
});
|
|
1842
1271
|
} catch {}
|
|
@@ -1928,6 +1357,16 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1928
1357
|
if (tag) forgetTag(tag);
|
|
1929
1358
|
removeWorkerRow({ tag, sessionId });
|
|
1930
1359
|
clearAgentStatuslineRoute(sessionId);
|
|
1360
|
+
// Cancel any running background task bound to this session BEFORE closing
|
|
1361
|
+
// the session. Otherwise closeSession rejects the in-flight runSpawn with
|
|
1362
|
+
// "Session closed: closeSession" and the catch path reconciles the task as
|
|
1363
|
+
// `failed` — a user-initiated close must surface as `cancelled` instead.
|
|
1364
|
+
// (The explicit task_id path below stays as a no-op fallback: cancel is
|
|
1365
|
+
// idempotent once terminal.)
|
|
1366
|
+
for (const row of listBackgroundTasks({ surface: 'agent', context: scopedContext })) {
|
|
1367
|
+
if (row.sessionId !== sessionId && row.tag !== target) continue;
|
|
1368
|
+
cancelBackgroundTask(row.task_id, 'cancelled by agent close');
|
|
1369
|
+
}
|
|
1931
1370
|
const ok = mgr.closeSession(sessionId, 'cli-agent-close');
|
|
1932
1371
|
if (task?.taskId) cancelBackgroundTask(task.taskId, 'cancelled by agent close');
|
|
1933
1372
|
return { closed: ok, tag, sessionId, task_id: task?.taskId || null };
|
|
@@ -1996,8 +1435,47 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
1996
1435
|
if (type === 'cancel') return renderResult(close(args, scopedContext));
|
|
1997
1436
|
if (type === 'close') return renderResult(close(args, scopedContext));
|
|
1998
1437
|
if (type === 'send') {
|
|
1999
|
-
|
|
2000
|
-
|
|
1438
|
+
try {
|
|
1439
|
+
const prepared = await prepareSend(args, scopedContext);
|
|
1440
|
+
return dispatchToExistingSession(prepared, notifyContext);
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
// Reaped/dead-tag fallback: with the 5m terminal-reap window a
|
|
1443
|
+
// same-scope follow-up often lands after the session is gone.
|
|
1444
|
+
// Instead of bouncing an error back to Lead (who would just
|
|
1445
|
+
// re-issue the same content as a spawn), respawn a FRESH session
|
|
1446
|
+
// under the same tag with the message as its brief. `respawned:
|
|
1447
|
+
// true` in the result tells Lead the worker has no prior session
|
|
1448
|
+
// context — re-supply anchors on the next send if needed.
|
|
1449
|
+
// Only tag-addressed sends fall back; explicit sessionId sends
|
|
1450
|
+
// keep erroring (caller pinned a specific session on purpose).
|
|
1451
|
+
const fallbackTag = clean(args.tag);
|
|
1452
|
+
const isDeadTarget = /not found|is closed/i.test(String(err?.message || ''));
|
|
1453
|
+
if (!fallbackTag || fallbackTag.startsWith('sess_') || !isDeadTarget) throw err;
|
|
1454
|
+
const prompt = clean(args.message || args.prompt);
|
|
1455
|
+
if (!prompt) throw err;
|
|
1456
|
+
// Inherit the dead session's agent/cwd from the tag registry so the
|
|
1457
|
+
// auto-respawn doesn't die on "agent spawn: agent is required" when
|
|
1458
|
+
// Lead's send (correctly) omitted them. Read BEFORE forgetTag wipes
|
|
1459
|
+
// the registry entries.
|
|
1460
|
+
const inheritedAgent = clean(args.agent) || clean(tagAgents.get(fallbackTag));
|
|
1461
|
+
const inheritedCwd = clean(args.cwd) || clean(tagCwds.get(fallbackTag));
|
|
1462
|
+
if (!inheritedAgent) throw err;
|
|
1463
|
+
// Clear the terminal trace so the fresh spawn isn't rejected by
|
|
1464
|
+
// the lingering-trace guard, then run the normal deferred spawn.
|
|
1465
|
+
try { forgetTag(fallbackTag); } catch {}
|
|
1466
|
+
try { removeWorkerRow({ tag: fallbackTag }); } catch {}
|
|
1467
|
+
const spawnArgs = {
|
|
1468
|
+
...args,
|
|
1469
|
+
type: 'spawn',
|
|
1470
|
+
tag: fallbackTag,
|
|
1471
|
+
prompt,
|
|
1472
|
+
message: undefined,
|
|
1473
|
+
agent: inheritedAgent,
|
|
1474
|
+
...(inheritedCwd ? { cwd: inheritedCwd } : {}),
|
|
1475
|
+
};
|
|
1476
|
+
const job = startDeferredSpawnJob(spawnArgs, callerCwd, context, notifyContext, { respawned: true });
|
|
1477
|
+
return renderResult(renderJob(job, false));
|
|
1478
|
+
}
|
|
2001
1479
|
}
|
|
2002
1480
|
if (type === 'spawn') {
|
|
2003
1481
|
// Explicit-tag spawn priority (auto nextTag always creates a fresh session):
|
|
@@ -2033,7 +1511,7 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
2033
1511
|
}
|
|
2034
1512
|
throw new Error(`agent: unknown type "${type}"`);
|
|
2035
1513
|
} catch (err) {
|
|
2036
|
-
return
|
|
1514
|
+
return errorLine(err, { surface: 'agent' });
|
|
2037
1515
|
}
|
|
2038
1516
|
}
|
|
2039
1517
|
|