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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session summary index: the compact `session-summaries.json` sidecar that
|
|
3
|
+
* lists every session's lightweight metadata (preview, counts, lifecycle) so
|
|
4
|
+
* the status aggregator and session listers avoid parsing every full session
|
|
5
|
+
* file. Extracted from store.mjs verbatim; store.mjs re-exports these so
|
|
6
|
+
* importers stay unchanged.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { getPluginData } from '../config.mjs';
|
|
11
|
+
import { updateJsonAtomicSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
|
|
12
|
+
|
|
13
|
+
export const SESSION_SUMMARY_INDEX_VERSION = 1;
|
|
14
|
+
|
|
15
|
+
export function summaryIndexPath() {
|
|
16
|
+
const dir = getPluginData();
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
return join(dir, 'session-summaries.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _messageText(content) {
|
|
22
|
+
if (typeof content === 'string') return content;
|
|
23
|
+
if (Array.isArray(content)) {
|
|
24
|
+
return content.map((part) => {
|
|
25
|
+
if (typeof part === 'string') return part;
|
|
26
|
+
if (part?.type === 'text') return part.text || '';
|
|
27
|
+
if (typeof part?.text === 'string') return part.text;
|
|
28
|
+
try { return JSON.stringify(part); } catch { return ''; }
|
|
29
|
+
}).filter(Boolean).join('\n');
|
|
30
|
+
}
|
|
31
|
+
if (content && typeof content === 'object') {
|
|
32
|
+
if (typeof content.text === 'string') return content.text;
|
|
33
|
+
try { return JSON.stringify(content); } catch { return ''; }
|
|
34
|
+
}
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function _cleanPreview(text, max = 240) {
|
|
39
|
+
const value = String(text || '').replace(/\s+/g, ' ').trim();
|
|
40
|
+
return value.length > max ? value.slice(0, max).replace(/\s+\S*$/, '').trim() : value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _isPreviewNoise(text) {
|
|
44
|
+
const value = String(text || '').trim();
|
|
45
|
+
if (!value) return true;
|
|
46
|
+
if (value.startsWith('<system-reminder>')) return true;
|
|
47
|
+
if (/^Reference files:/i.test(value)) return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _sessionPreview(session) {
|
|
52
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
53
|
+
let first = '';
|
|
54
|
+
for (const m of messages) {
|
|
55
|
+
if (m?.role !== 'user') continue;
|
|
56
|
+
const text = _cleanPreview(_messageText(m.content));
|
|
57
|
+
if (_isPreviewNoise(text)) continue;
|
|
58
|
+
if (!first) first = text;
|
|
59
|
+
}
|
|
60
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
61
|
+
const m = messages[i];
|
|
62
|
+
if (m?.role !== 'user') continue;
|
|
63
|
+
const text = _cleanPreview(_messageText(m.content));
|
|
64
|
+
if (_isPreviewNoise(text)) continue;
|
|
65
|
+
return text || first;
|
|
66
|
+
}
|
|
67
|
+
return first;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function _sessionMessageCount(session) {
|
|
71
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
72
|
+
let count = 0;
|
|
73
|
+
for (const m of messages) {
|
|
74
|
+
if (m && (m.role === 'user' || m.role === 'assistant')) count++;
|
|
75
|
+
}
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _positiveNumber(value, fallback = 0) {
|
|
80
|
+
const n = Number(value);
|
|
81
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function _sessionSummary(session) {
|
|
85
|
+
if (!session?.id) return null;
|
|
86
|
+
return {
|
|
87
|
+
id: String(session.id),
|
|
88
|
+
updatedAt: _positiveNumber(session.updatedAt, Date.now()),
|
|
89
|
+
createdAt: _positiveNumber(session.createdAt, 0),
|
|
90
|
+
lastHeartbeatAt: _positiveNumber(session.lastHeartbeatAt, 0),
|
|
91
|
+
closed: session.closed === true,
|
|
92
|
+
status: String(session.status || (session.closed === true ? 'closed' : 'idle')),
|
|
93
|
+
owner: session.owner || 'user',
|
|
94
|
+
agent: session.agent || null,
|
|
95
|
+
sourceType: session.sourceType || null,
|
|
96
|
+
sourceName: session.sourceName || null,
|
|
97
|
+
scopeKey: session.scopeKey || null,
|
|
98
|
+
ownerSessionId: session.ownerSessionId || null,
|
|
99
|
+
clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
|
|
100
|
+
cwd: session.cwd || '',
|
|
101
|
+
provider: session.provider || null,
|
|
102
|
+
model: session.model || null,
|
|
103
|
+
agentTag: session.agentTag || null,
|
|
104
|
+
task_id: session.task_id || session.taskId || null,
|
|
105
|
+
permission: session.permission || null,
|
|
106
|
+
toolPermission: session.toolPermission || null,
|
|
107
|
+
messageCount: _sessionMessageCount(session),
|
|
108
|
+
preview: _sessionPreview(session),
|
|
109
|
+
generation: typeof session.generation === 'number' ? session.generation : 0,
|
|
110
|
+
implicitBashSessionId: session.implicitBashSessionId || null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _normalizeSummaryRow(row) {
|
|
115
|
+
if (!row?.id || typeof row.id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(row.id)) return null;
|
|
116
|
+
return {
|
|
117
|
+
id: row.id,
|
|
118
|
+
updatedAt: _positiveNumber(row.updatedAt, 0),
|
|
119
|
+
createdAt: _positiveNumber(row.createdAt, 0),
|
|
120
|
+
lastHeartbeatAt: _positiveNumber(row.lastHeartbeatAt, 0),
|
|
121
|
+
closed: row.closed === true,
|
|
122
|
+
status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
|
|
123
|
+
owner: row.owner || 'user',
|
|
124
|
+
agent: row.agent || null,
|
|
125
|
+
sourceType: row.sourceType || null,
|
|
126
|
+
sourceName: row.sourceName || null,
|
|
127
|
+
scopeKey: row.scopeKey || null,
|
|
128
|
+
ownerSessionId: row.ownerSessionId || null,
|
|
129
|
+
clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
|
|
130
|
+
cwd: row.cwd || '',
|
|
131
|
+
provider: row.provider || null,
|
|
132
|
+
model: row.model || null,
|
|
133
|
+
agentTag: row.agentTag || null,
|
|
134
|
+
task_id: row.task_id || null,
|
|
135
|
+
permission: row.permission || null,
|
|
136
|
+
toolPermission: row.toolPermission || null,
|
|
137
|
+
messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
|
|
138
|
+
preview: _cleanPreview(row.preview || ''),
|
|
139
|
+
generation: typeof row.generation === 'number' ? row.generation : 0,
|
|
140
|
+
implicitBashSessionId: row.implicitBashSessionId || null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function _normalizeSummaryIndex(raw) {
|
|
145
|
+
const rows = Array.isArray(raw?.rows) ? raw.rows.map(_normalizeSummaryRow).filter(Boolean) : [];
|
|
146
|
+
rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
147
|
+
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: _positiveNumber(raw?.updatedAt, 0), rows };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function _writeSummaryIndex(rows) {
|
|
151
|
+
const cleanRows = (rows || []).map(_normalizeSummaryRow).filter(Boolean)
|
|
152
|
+
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
153
|
+
writeJsonAtomicSync(summaryIndexPath(), {
|
|
154
|
+
version: SESSION_SUMMARY_INDEX_VERSION,
|
|
155
|
+
updatedAt: Date.now(),
|
|
156
|
+
rows: cleanRows,
|
|
157
|
+
}, { compact: true, lock: true });
|
|
158
|
+
return cleanRows;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function _upsertSessionSummary(session) {
|
|
162
|
+
const row = _sessionSummary(session);
|
|
163
|
+
if (!row) return;
|
|
164
|
+
try {
|
|
165
|
+
updateJsonAtomicSync(summaryIndexPath(), (cur) => {
|
|
166
|
+
const index = _normalizeSummaryIndex(cur);
|
|
167
|
+
const rows = index.rows.filter((r) => r.id !== row.id);
|
|
168
|
+
const cleanRow = _normalizeSummaryRow(row);
|
|
169
|
+
if (!cleanRow) return undefined;
|
|
170
|
+
const existing = index.rows.find((r) => r.id === row.id) || null;
|
|
171
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(cleanRow)) return undefined;
|
|
172
|
+
rows.push(cleanRow);
|
|
173
|
+
rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
174
|
+
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
|
|
175
|
+
}, { compact: true, lock: true });
|
|
176
|
+
} catch { /* summary index is best-effort */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function _removeSessionSummary(id) {
|
|
180
|
+
if (!id) return;
|
|
181
|
+
try {
|
|
182
|
+
updateJsonAtomicSync(summaryIndexPath(), (cur) => {
|
|
183
|
+
const index = _normalizeSummaryIndex(cur);
|
|
184
|
+
const rows = index.rows.filter((r) => r.id !== id);
|
|
185
|
+
if (rows.length === index.rows.length) return undefined;
|
|
186
|
+
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
|
|
187
|
+
}, { compact: true, lock: true });
|
|
188
|
+
} catch { /* summary index is best-effort */ }
|
|
189
|
+
}
|
|
@@ -10,11 +10,29 @@ import { join } from 'path';
|
|
|
10
10
|
import { Worker } from 'worker_threads';
|
|
11
11
|
import { getPluginData } from '../config.mjs';
|
|
12
12
|
import { isAgentOwner } from '../agent-owner.mjs';
|
|
13
|
-
import { renameWithRetrySync
|
|
13
|
+
import { renameWithRetrySync } from '../../../shared/atomic-file.mjs';
|
|
14
14
|
import { sanitizeContentForStoredHistory } from '../providers/media-normalization.mjs';
|
|
15
|
+
import {
|
|
16
|
+
summaryIndexPath,
|
|
17
|
+
_sessionSummary,
|
|
18
|
+
_normalizeSummaryIndex,
|
|
19
|
+
_writeSummaryIndex,
|
|
20
|
+
_upsertSessionSummary,
|
|
21
|
+
_removeSessionSummary,
|
|
22
|
+
} from './store-summary-index.mjs';
|
|
23
|
+
// Facade re-export: summary-index API moved to store-summary-index.mjs; keep
|
|
24
|
+
// prior importers of store.mjs unchanged.
|
|
25
|
+
export {
|
|
26
|
+
SESSION_SUMMARY_INDEX_VERSION,
|
|
27
|
+
summaryIndexPath,
|
|
28
|
+
_sessionSummary,
|
|
29
|
+
_normalizeSummaryIndex,
|
|
30
|
+
_writeSummaryIndex,
|
|
31
|
+
_upsertSessionSummary,
|
|
32
|
+
_removeSessionSummary,
|
|
33
|
+
} from './store-summary-index.mjs';
|
|
15
34
|
|
|
16
35
|
const _lastSaveError = new Map(); // id -> { message, at }
|
|
17
|
-
const SESSION_SUMMARY_INDEX_VERSION = 1;
|
|
18
36
|
|
|
19
37
|
// The live in-memory session (and every model request)
|
|
20
38
|
// retains attached image bytes across turns so multi-turn recognition works.
|
|
@@ -71,11 +89,6 @@ function sessionPath(id) {
|
|
|
71
89
|
}
|
|
72
90
|
return join(getStoreDir(), `${id}.json`);
|
|
73
91
|
}
|
|
74
|
-
function summaryIndexPath() {
|
|
75
|
-
const dir = getPluginData();
|
|
76
|
-
mkdirSync(dir, { recursive: true });
|
|
77
|
-
return join(dir, 'session-summaries.json');
|
|
78
|
-
}
|
|
79
92
|
/**
|
|
80
93
|
* Ensure generation/closed defaults on every session object.
|
|
81
94
|
* Older persisted sessions predate these fields; we normalise at load and save.
|
|
@@ -88,176 +101,6 @@ function _ensureLifecycleFields(session) {
|
|
|
88
101
|
return session;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
function _messageText(content) {
|
|
92
|
-
if (typeof content === 'string') return content;
|
|
93
|
-
if (Array.isArray(content)) {
|
|
94
|
-
return content.map((part) => {
|
|
95
|
-
if (typeof part === 'string') return part;
|
|
96
|
-
if (part?.type === 'text') return part.text || '';
|
|
97
|
-
if (typeof part?.text === 'string') return part.text;
|
|
98
|
-
try { return JSON.stringify(part); } catch { return ''; }
|
|
99
|
-
}).filter(Boolean).join('\n');
|
|
100
|
-
}
|
|
101
|
-
if (content && typeof content === 'object') {
|
|
102
|
-
if (typeof content.text === 'string') return content.text;
|
|
103
|
-
try { return JSON.stringify(content); } catch { return ''; }
|
|
104
|
-
}
|
|
105
|
-
return '';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function _cleanPreview(text, max = 240) {
|
|
109
|
-
const value = String(text || '').replace(/\s+/g, ' ').trim();
|
|
110
|
-
return value.length > max ? value.slice(0, max).replace(/\s+\S*$/, '').trim() : value;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function _isPreviewNoise(text) {
|
|
114
|
-
const value = String(text || '').trim();
|
|
115
|
-
if (!value) return true;
|
|
116
|
-
if (value.startsWith('<system-reminder>')) return true;
|
|
117
|
-
if (/^Reference files:/i.test(value)) return true;
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function _sessionPreview(session) {
|
|
122
|
-
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
123
|
-
let first = '';
|
|
124
|
-
for (const m of messages) {
|
|
125
|
-
if (m?.role !== 'user') continue;
|
|
126
|
-
const text = _cleanPreview(_messageText(m.content));
|
|
127
|
-
if (_isPreviewNoise(text)) continue;
|
|
128
|
-
if (!first) first = text;
|
|
129
|
-
}
|
|
130
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
131
|
-
const m = messages[i];
|
|
132
|
-
if (m?.role !== 'user') continue;
|
|
133
|
-
const text = _cleanPreview(_messageText(m.content));
|
|
134
|
-
if (_isPreviewNoise(text)) continue;
|
|
135
|
-
return text || first;
|
|
136
|
-
}
|
|
137
|
-
return first;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function _sessionMessageCount(session) {
|
|
141
|
-
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
142
|
-
let count = 0;
|
|
143
|
-
for (const m of messages) {
|
|
144
|
-
if (m && (m.role === 'user' || m.role === 'assistant')) count++;
|
|
145
|
-
}
|
|
146
|
-
return count;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function _positiveNumber(value, fallback = 0) {
|
|
150
|
-
const n = Number(value);
|
|
151
|
-
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function _sessionSummary(session) {
|
|
155
|
-
if (!session?.id) return null;
|
|
156
|
-
return {
|
|
157
|
-
id: String(session.id),
|
|
158
|
-
updatedAt: _positiveNumber(session.updatedAt, Date.now()),
|
|
159
|
-
createdAt: _positiveNumber(session.createdAt, 0),
|
|
160
|
-
lastHeartbeatAt: _positiveNumber(session.lastHeartbeatAt, 0),
|
|
161
|
-
closed: session.closed === true,
|
|
162
|
-
status: String(session.status || (session.closed === true ? 'closed' : 'idle')),
|
|
163
|
-
owner: session.owner || 'user',
|
|
164
|
-
agent: session.agent || null,
|
|
165
|
-
sourceType: session.sourceType || null,
|
|
166
|
-
sourceName: session.sourceName || null,
|
|
167
|
-
scopeKey: session.scopeKey || null,
|
|
168
|
-
ownerSessionId: session.ownerSessionId || null,
|
|
169
|
-
clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
|
|
170
|
-
cwd: session.cwd || '',
|
|
171
|
-
provider: session.provider || null,
|
|
172
|
-
model: session.model || null,
|
|
173
|
-
agentTag: session.agentTag || null,
|
|
174
|
-
task_id: session.task_id || session.taskId || null,
|
|
175
|
-
permission: session.permission || null,
|
|
176
|
-
toolPermission: session.toolPermission || null,
|
|
177
|
-
messageCount: _sessionMessageCount(session),
|
|
178
|
-
preview: _sessionPreview(session),
|
|
179
|
-
generation: typeof session.generation === 'number' ? session.generation : 0,
|
|
180
|
-
implicitBashSessionId: session.implicitBashSessionId || null,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function _normalizeSummaryRow(row) {
|
|
185
|
-
if (!row?.id || typeof row.id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(row.id)) return null;
|
|
186
|
-
return {
|
|
187
|
-
id: row.id,
|
|
188
|
-
updatedAt: _positiveNumber(row.updatedAt, 0),
|
|
189
|
-
createdAt: _positiveNumber(row.createdAt, 0),
|
|
190
|
-
lastHeartbeatAt: _positiveNumber(row.lastHeartbeatAt, 0),
|
|
191
|
-
closed: row.closed === true,
|
|
192
|
-
status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
|
|
193
|
-
owner: row.owner || 'user',
|
|
194
|
-
agent: row.agent || null,
|
|
195
|
-
sourceType: row.sourceType || null,
|
|
196
|
-
sourceName: row.sourceName || null,
|
|
197
|
-
scopeKey: row.scopeKey || null,
|
|
198
|
-
ownerSessionId: row.ownerSessionId || null,
|
|
199
|
-
clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
|
|
200
|
-
cwd: row.cwd || '',
|
|
201
|
-
provider: row.provider || null,
|
|
202
|
-
model: row.model || null,
|
|
203
|
-
agentTag: row.agentTag || null,
|
|
204
|
-
task_id: row.task_id || null,
|
|
205
|
-
permission: row.permission || null,
|
|
206
|
-
toolPermission: row.toolPermission || null,
|
|
207
|
-
messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
|
|
208
|
-
preview: _cleanPreview(row.preview || ''),
|
|
209
|
-
generation: typeof row.generation === 'number' ? row.generation : 0,
|
|
210
|
-
implicitBashSessionId: row.implicitBashSessionId || null,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function _normalizeSummaryIndex(raw) {
|
|
215
|
-
const rows = Array.isArray(raw?.rows) ? raw.rows.map(_normalizeSummaryRow).filter(Boolean) : [];
|
|
216
|
-
rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
217
|
-
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: _positiveNumber(raw?.updatedAt, 0), rows };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function _writeSummaryIndex(rows) {
|
|
221
|
-
const cleanRows = (rows || []).map(_normalizeSummaryRow).filter(Boolean)
|
|
222
|
-
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
223
|
-
writeJsonAtomicSync(summaryIndexPath(), {
|
|
224
|
-
version: SESSION_SUMMARY_INDEX_VERSION,
|
|
225
|
-
updatedAt: Date.now(),
|
|
226
|
-
rows: cleanRows,
|
|
227
|
-
}, { compact: true, lock: true });
|
|
228
|
-
return cleanRows;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function _upsertSessionSummary(session) {
|
|
232
|
-
const row = _sessionSummary(session);
|
|
233
|
-
if (!row) return;
|
|
234
|
-
try {
|
|
235
|
-
updateJsonAtomicSync(summaryIndexPath(), (cur) => {
|
|
236
|
-
const index = _normalizeSummaryIndex(cur);
|
|
237
|
-
const rows = index.rows.filter((r) => r.id !== row.id);
|
|
238
|
-
const cleanRow = _normalizeSummaryRow(row);
|
|
239
|
-
if (!cleanRow) return undefined;
|
|
240
|
-
const existing = index.rows.find((r) => r.id === row.id) || null;
|
|
241
|
-
if (existing && JSON.stringify(existing) === JSON.stringify(cleanRow)) return undefined;
|
|
242
|
-
rows.push(cleanRow);
|
|
243
|
-
rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
244
|
-
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
|
|
245
|
-
}, { compact: true, lock: true });
|
|
246
|
-
} catch { /* summary index is best-effort */ }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function _removeSessionSummary(id) {
|
|
250
|
-
if (!id) return;
|
|
251
|
-
try {
|
|
252
|
-
updateJsonAtomicSync(summaryIndexPath(), (cur) => {
|
|
253
|
-
const index = _normalizeSummaryIndex(cur);
|
|
254
|
-
const rows = index.rows.filter((r) => r.id !== id);
|
|
255
|
-
if (rows.length === index.rows.length) return undefined;
|
|
256
|
-
return { version: SESSION_SUMMARY_INDEX_VERSION, updatedAt: Date.now(), rows };
|
|
257
|
-
}, { compact: true, lock: true });
|
|
258
|
-
} catch { /* summary index is best-effort */ }
|
|
259
|
-
}
|
|
260
|
-
|
|
261
104
|
/** Module-level map tracking in-flight saves per session ID to prevent concurrent write corruption. */
|
|
262
105
|
const _savePending = new Map();
|
|
263
106
|
|
|
@@ -520,17 +363,34 @@ function _shouldDrop(id, opts) {
|
|
|
520
363
|
// ran on every save). Fast-path a regex scan; only fall back to a full
|
|
521
364
|
// parse if the lightweight scan is inconclusive.
|
|
522
365
|
const closedMatch = /"closed"\s*:\s*(true|false)/.exec(raw);
|
|
523
|
-
if (closedMatch && closedMatch[1] === 'false') return false;
|
|
524
366
|
if (closedMatch && closedMatch[1] === 'true') {
|
|
525
367
|
const genMatch = /"generation"\s*:\s*(-?\d+)/.exec(raw);
|
|
526
368
|
const diskGen = genMatch ? Number(genMatch[1]) : 0;
|
|
527
369
|
return diskGen >= expected;
|
|
528
370
|
}
|
|
371
|
+
if (closedMatch && closedMatch[1] === 'false') {
|
|
372
|
+
// Not closed — still check the generation-as-ownership-counter
|
|
373
|
+
// rule (see full-parse branch below) via the same fast regex
|
|
374
|
+
// scan, so bumpSessionGeneration()'s detach bump is honoured
|
|
375
|
+
// without paying for a full JSON.parse on every save.
|
|
376
|
+
const genMatch = /"generation"\s*:\s*(-?\d+)/.exec(raw);
|
|
377
|
+
const diskGen = genMatch ? Number(genMatch[1]) : 0;
|
|
378
|
+
return diskGen > expected;
|
|
379
|
+
}
|
|
529
380
|
// No `closed` key found by scan — not a tombstone, so nothing to drop.
|
|
530
381
|
if (!closedMatch && !/"closed"/.test(raw)) return false;
|
|
531
382
|
const onDisk = JSON.parse(raw);
|
|
532
383
|
const diskGen = typeof onDisk.generation === 'number' ? onDisk.generation : 0;
|
|
533
|
-
|
|
384
|
+
if (onDisk.closed === true) return diskGen >= expected;
|
|
385
|
+
// Not closed, but `generation` also doubles as an ownership counter:
|
|
386
|
+
// normal in-place saves (updateSession/appendMessage/etc.) never bump
|
|
387
|
+
// it, only closeSession()-family calls do (markSessionClosed and its
|
|
388
|
+
// non-tombstoning sibling bumpSessionGeneration). So if disk
|
|
389
|
+
// generation is strictly greater than what this write expected,
|
|
390
|
+
// ownership moved on (session was detached-closed and possibly
|
|
391
|
+
// resumed) after our turn started — drop the stale write rather than
|
|
392
|
+
// let it clobber whatever happened after the handoff.
|
|
393
|
+
return diskGen > expected;
|
|
534
394
|
} catch {
|
|
535
395
|
return false;
|
|
536
396
|
}
|
|
@@ -666,6 +526,41 @@ export function markSessionClosed(id, reason = 'manual') {
|
|
|
666
526
|
return newGen;
|
|
667
527
|
}
|
|
668
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Bump a session's generation WITHOUT planting a closed:true tombstone.
|
|
531
|
+
* Used by closeSession(id, reason, { tombstone: false }) — the runtime side
|
|
532
|
+
* (heartbeat, bash shells, controller, in-memory entry) is detached, but the
|
|
533
|
+
* session file itself stays valid/resumable. The generation bump alone is
|
|
534
|
+
* what protects it from a late save race: any saveSession() still in flight
|
|
535
|
+
* from the detached turn was issued with the OLD generation as its
|
|
536
|
+
* `expectedGeneration`, so once we bump the on-disk generation here, that
|
|
537
|
+
* late write's own _shouldDrop() check (generation-as-ownership-counter
|
|
538
|
+
* rule, see below) sees disk generation > expected and drops itself instead
|
|
539
|
+
* of clobbering whatever the resumed session writes next.
|
|
540
|
+
* Returns the new generation, or null if the session file doesn't exist.
|
|
541
|
+
*/
|
|
542
|
+
export function bumpSessionGeneration(id, reason = 'detach') {
|
|
543
|
+
_clearDebounce(id);
|
|
544
|
+
const existing = loadSession(id);
|
|
545
|
+
if (!existing) return null;
|
|
546
|
+
const newGen = (typeof existing.generation === 'number' ? existing.generation : 0) + 1;
|
|
547
|
+
const detached = { ...existing, generation: newGen, updatedAt: Date.now(), detachedReason: reason };
|
|
548
|
+
const target = sessionPath(id);
|
|
549
|
+
const tmp = target + '.' + randomBytes(6).toString('hex') + '.tmp';
|
|
550
|
+
try {
|
|
551
|
+
writeFileSync(tmp, JSON.stringify(_sessionForDisk(detached)), 'utf-8');
|
|
552
|
+
_renameWithRetrySync(tmp, target);
|
|
553
|
+
} catch {
|
|
554
|
+
try { unlinkSync(tmp); } catch { /* ignore */ }
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
_savePending.delete(id);
|
|
558
|
+
_clearLiveSession(id);
|
|
559
|
+
_deleteHeartbeat(id);
|
|
560
|
+
_upsertSessionSummary(detached);
|
|
561
|
+
return newGen;
|
|
562
|
+
}
|
|
563
|
+
|
|
669
564
|
export function loadSession(id) {
|
|
670
565
|
const path = sessionPath(id);
|
|
671
566
|
// Read-your-writes: if a save is pending (debouncing, scheduled, or queued
|
|
@@ -206,9 +206,28 @@ export const PROVIDER_RETRY_JITTER_RATIO = (() => {
|
|
|
206
206
|
return 0.2;
|
|
207
207
|
})();
|
|
208
208
|
|
|
209
|
+
// Public workflow agents are not in agents.json (that file only defines hidden
|
|
210
|
+
// roles), so they historically always fell back to the 600s default. A wedged
|
|
211
|
+
// anthropic SSE stream (HTTP 200 then zero deltas — observed 2026-07-03 on a
|
|
212
|
+
// sonnet-5 worker, twice in a row) then holds the owner for the full 10 min.
|
|
213
|
+
// Scoped workers/reviewers should be reaped faster; lead keeps the generous
|
|
214
|
+
// default because interactive xhigh turns legitimately think for minutes.
|
|
215
|
+
const WORKFLOW_AGENT_STALL_ABORT_S = {
|
|
216
|
+
'worker': 300,
|
|
217
|
+
'reviewer': 300,
|
|
218
|
+
'debugger': 420,
|
|
219
|
+
'heavy-worker': 420,
|
|
220
|
+
'maintainer': 300,
|
|
221
|
+
'explore': 240,
|
|
222
|
+
'web-researcher': 300,
|
|
223
|
+
};
|
|
224
|
+
|
|
209
225
|
export function resolveAgentStallThresholds(agent, env = process.env) {
|
|
210
226
|
const cfg = agent ? getHiddenAgent(agent) : null;
|
|
211
|
-
const
|
|
227
|
+
const roleAbort = WORKFLOW_AGENT_STALL_ABORT_S[String(agent || '')] || null;
|
|
228
|
+
const cfgAbort = cfg?.stallCap?.idleSeconds > 0
|
|
229
|
+
? cfg.stallCap.idleSeconds
|
|
230
|
+
: (roleAbort || STALL_ABORT_S);
|
|
212
231
|
const envOverride = envThresholdSeconds(env);
|
|
213
232
|
const abort = envOverride != null ? envOverride : cfgAbort;
|
|
214
233
|
// Mid-stream "slow" warning disabled — an agent stall now notifies ONLY at
|