mixdog 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -38
- package/package.json +10 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/r5-orchestrated-task.json +7 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r13-clientmeta.json +464 -0
- package/scripts/bench/round-r14-betafeatures.json +466 -0
- package/scripts/bench/round-r15-fulldefault.json +462 -0
- package/scripts/bench/round-r16-sessionid.json +466 -0
- package/scripts/bench/round-r17-wirebytes.json +456 -0
- package/scripts/bench/round-r18-prewarm.json +468 -0
- package/scripts/bench/round-r19-clean.json +472 -0
- package/scripts/bench/round-r20-prewarm-clean.json +475 -0
- package/scripts/bench/round-r21-delta-retry.json +473 -0
- package/scripts/bench/round-r22-full-probe.json +693 -0
- package/scripts/bench/round-r23-itemprobe.json +701 -0
- package/scripts/bench/round-r24-shapefix.json +677 -0
- package/scripts/bench/round-r25-serial.json +464 -0
- package/scripts/bench/round-r26-parallel3.json +671 -0
- package/scripts/bench/round-r27-parallel10.json +894 -0
- package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
- package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
- package/scripts/bench/round-r30-instid.json +253 -0
- package/scripts/bench/round-r31-upgradeprobe.json +256 -0
- package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
- package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
- package/scripts/bench/round-r34-orchestrated.json +120 -0
- package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
- package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +251 -32
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +3 -4
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/model-catalog-audit.mjs +209 -0
- package/scripts/model-list-sanitize-test.mjs +37 -0
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +76 -13
- package/scripts/recall-quality-cases.json +12 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +25 -65
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +9 -8
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/scheduler-task/AGENT.md +2 -3
- package/src/agents/webhook-handler/AGENT.md +2 -3
- package/src/agents/worker/AGENT.md +10 -7
- package/src/app.mjs +12 -1
- package/src/headless-role.mjs +7 -1
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +647 -2056
- package/src/output-styles/default.md +30 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +8 -6
- package/src/output-styles/simple.md +21 -7
- package/src/rules/agent/00-common.md +6 -3
- package/src/rules/agent/30-explorer.md +16 -5
- package/src/rules/lead/01-general.md +5 -5
- package/src/rules/lead/lead-brief.md +15 -0
- package/src/rules/lead/lead-tool.md +6 -15
- package/src/rules/shared/01-tool.md +17 -21
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +27 -318
- package/src/runtime/channels/backends/telegram.mjs +8 -12
- package/src/runtime/channels/index.mjs +247 -701
- package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
- package/src/runtime/channels/lib/config.mjs +37 -149
- package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
- package/src/runtime/channels/lib/event-queue.mjs +78 -13
- package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
- package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/scheduler.mjs +27 -113
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +43 -616
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +210 -1948
- package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/ko-morph.mjs +195 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
- package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +802 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/config.mjs +14 -4
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
- package/src/runtime/shared/schedules-store.mjs +13 -3
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +119 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +205 -0
- package/src/session-runtime/quick-search-models.mjs +47 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +352 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +1 -1
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +138 -669
- package/src/standalone/channel-admin.mjs +102 -90
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +64 -14
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1059 -8110
- package/src/tui/app/app-format.mjs +213 -0
- package/src/tui/app/channel-pickers.mjs +508 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +356 -0
- package/src/tui/app/model-options.mjs +334 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +489 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +677 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +512 -0
- package/src/tui/app/use-transcript-window.mjs +607 -0
- package/src/tui/components/ConfirmBar.jsx +10 -7
- package/src/tui/components/Picker.jsx +64 -15
- package/src/tui/components/PromptInput.jsx +33 -102
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +69 -15
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +13553 -12384
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +170 -574
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +65 -1
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -18
- package/src/workflows/sequential/WORKFLOW.md +16 -18
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +112 -7
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/channels/lib/holidays.mjs +0 -138
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -22,28 +22,15 @@ class PendingInteractionStore {
|
|
|
22
22
|
this.patch(userId, channelId, { _msgId: messageId });
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
function getPendingSelectUpdate(customId, values) {
|
|
26
|
-
const value = values?.[0];
|
|
27
|
-
if (!value) return null;
|
|
28
|
-
const scheduleMatch = customId.match(/^sched_(add|edit)_(period|exec|mode)$/);
|
|
29
|
-
if (scheduleMatch) {
|
|
30
|
-
return { [scheduleMatch[2]]: value };
|
|
31
|
-
}
|
|
32
|
-
if (customId === "quiet_holidays_select") {
|
|
33
|
-
return { holidays: value };
|
|
34
|
-
}
|
|
35
|
-
if (customId === "activity_mode_select") {
|
|
36
|
-
return { activityMode: value };
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
25
|
function buildModalRequestSpec(customId, pending, profile) {
|
|
41
26
|
switch (customId) {
|
|
42
27
|
case "sched_add_next": {
|
|
43
28
|
const fields = [
|
|
44
29
|
{ id: "name", label: "Name", required: true },
|
|
45
30
|
{ id: "time", label: "Cron expression (e.g. 0 9 * * 1-5)", required: true },
|
|
46
|
-
|
|
31
|
+
// Single main channel: `channel` is a boolean-ish flag (true → post
|
|
32
|
+
// to the main channel, empty/false → inject into the Lead session).
|
|
33
|
+
{ id: "channel", label: "Post to channel? (true / false)", required: false, value: "true" }
|
|
47
34
|
];
|
|
48
35
|
if (pending.exec?.includes("script")) {
|
|
49
36
|
fields.push({ id: "script", label: "Script filename", required: true });
|
|
@@ -54,19 +41,10 @@ function buildModalRequestSpec(customId, pending, profile) {
|
|
|
54
41
|
fields
|
|
55
42
|
};
|
|
56
43
|
}
|
|
57
|
-
case "quiet_set_next":
|
|
58
|
-
return {
|
|
59
|
-
customId: "modal_quiet",
|
|
60
|
-
title: "Quiet Hours",
|
|
61
|
-
fields: [
|
|
62
|
-
{ id: "schedule", label: "Schedule quiet hours (e.g. 23:00-07:00)", required: false }
|
|
63
|
-
]
|
|
64
|
-
};
|
|
65
44
|
case "sched_edit_next": {
|
|
66
45
|
const fields = [
|
|
67
46
|
{ id: "time", label: "Cron expression (e.g. 0 9 * * 1-5)", required: false },
|
|
68
|
-
{ id: "channel", label: "
|
|
69
|
-
{ id: "dnd", label: "Quiet hours (e.g. 23:00-07:00, leave empty to disable)", required: false }
|
|
47
|
+
{ id: "channel", label: "Post to channel? (true / false)", required: false }
|
|
70
48
|
];
|
|
71
49
|
if (pending.exec?.includes("script")) {
|
|
72
50
|
fields.push({ id: "script", label: "Script filename", required: false });
|
|
@@ -77,15 +55,6 @@ function buildModalRequestSpec(customId, pending, profile) {
|
|
|
77
55
|
fields
|
|
78
56
|
};
|
|
79
57
|
}
|
|
80
|
-
case "activity_add_next":
|
|
81
|
-
return {
|
|
82
|
-
customId: "modal_activity_add",
|
|
83
|
-
title: "Add Activity Channel",
|
|
84
|
-
fields: [
|
|
85
|
-
{ id: "name", label: "Channel Name", required: true },
|
|
86
|
-
{ id: "id", label: "Channel ID", required: true }
|
|
87
|
-
]
|
|
88
|
-
};
|
|
89
58
|
case "profile_edit":
|
|
90
59
|
return {
|
|
91
60
|
customId: "modal_profile_edit",
|
|
@@ -101,84 +70,7 @@ function buildModalRequestSpec(customId, pending, profile) {
|
|
|
101
70
|
return null;
|
|
102
71
|
}
|
|
103
72
|
}
|
|
104
|
-
function buildModalExecutionPlan(customId, pending, fields) {
|
|
105
|
-
switch (customId) {
|
|
106
|
-
case "modal_sched_add": {
|
|
107
|
-
const params = {
|
|
108
|
-
time: fields.time,
|
|
109
|
-
channel: fields.channel || "general",
|
|
110
|
-
mode: pending.mode || "non-interactive",
|
|
111
|
-
period: pending.period || "daily",
|
|
112
|
-
exec: pending.exec || "prompt"
|
|
113
|
-
};
|
|
114
|
-
if (fields.script) params.script = fields.script;
|
|
115
|
-
return {
|
|
116
|
-
commands: [{ target: "bot", args: ["schedule", "add", fields.name], params }],
|
|
117
|
-
followup: { target: "bot", args: ["schedule", "list"], params: {} }
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
case "modal_quiet": {
|
|
121
|
-
const commands = [];
|
|
122
|
-
if (fields.schedule) commands.push({ target: "bot", args: ["quiet", "schedule", fields.schedule], params: {} });
|
|
123
|
-
if (pending.holidays && pending.holidays !== "none") {
|
|
124
|
-
commands.push({ target: "bot", args: ["quiet", "holidays", pending.holidays], params: {} });
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
commands,
|
|
128
|
-
followup: { target: "bot", args: ["quiet", "list"], params: {} }
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
case "modal_sched_edit": {
|
|
132
|
-
const name = pending.editName;
|
|
133
|
-
if (!name) return null;
|
|
134
|
-
const params = {};
|
|
135
|
-
if (fields.time) params.time = fields.time;
|
|
136
|
-
if (fields.channel) params.channel = fields.channel;
|
|
137
|
-
if (pending.period) params.period = pending.period;
|
|
138
|
-
if (pending.exec) params.exec = pending.exec;
|
|
139
|
-
if (pending.mode) params.mode = pending.mode;
|
|
140
|
-
if (fields.script) params.script = fields.script;
|
|
141
|
-
const commands = [
|
|
142
|
-
{ target: "bot", args: ["schedule", "edit", name], params }
|
|
143
|
-
];
|
|
144
|
-
if (fields.dnd) {
|
|
145
|
-
commands.push({ target: "bot", args: ["quiet", "schedule", fields.dnd], params: {} });
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
commands,
|
|
149
|
-
followup: { target: "bot", args: ["schedule", "detail", name], params: {} }
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
case "modal_activity_add":
|
|
153
|
-
return {
|
|
154
|
-
commands: [{
|
|
155
|
-
target: "bot",
|
|
156
|
-
args: ["activity", "add", fields.name],
|
|
157
|
-
params: {
|
|
158
|
-
id: fields.id,
|
|
159
|
-
mode: pending.activityMode || "interactive"
|
|
160
|
-
}
|
|
161
|
-
}],
|
|
162
|
-
followup: { target: "bot", args: ["activity", "list"], params: {} }
|
|
163
|
-
};
|
|
164
|
-
case "modal_profile_edit": {
|
|
165
|
-
const params = {};
|
|
166
|
-
if (fields.name) params.name = fields.name;
|
|
167
|
-
if (fields.role) params.role = fields.role;
|
|
168
|
-
if (fields.lang) params.lang = fields.lang;
|
|
169
|
-
if (fields.tone) params.tone = fields.tone;
|
|
170
|
-
return {
|
|
171
|
-
commands: Object.keys(params).length > 0 ? [{ target: "profile", args: ["set"], params }] : [],
|
|
172
|
-
followup: { target: "bot", args: ["profile", "list"], params: {} }
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
default:
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
73
|
export {
|
|
180
74
|
PendingInteractionStore,
|
|
181
|
-
|
|
182
|
-
buildModalRequestSpec,
|
|
183
|
-
getPendingSelectUpdate
|
|
75
|
+
buildModalRequestSpec
|
|
184
76
|
};
|
|
@@ -588,7 +588,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
588
588
|
// isHidden, because that helper would re-consult the module-local
|
|
589
589
|
// HIDDEN_TOOLS Set and ignore the OutputForwarder static.
|
|
590
590
|
if (OutputForwarder.HIDDEN_TOOLS.has(name)) return true;
|
|
591
|
-
if (name === "reply" || name === "
|
|
591
|
+
if (name === "reply" || name === "fetch") return true;
|
|
592
592
|
return false;
|
|
593
593
|
};
|
|
594
594
|
/** Concatenate text blocks from a transcript entry (user or assistant). */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Bridge ownership snapshot + owner heartbeat cluster. Extracted verbatim from
|
|
2
|
+
// channels/index.mjs (behavior-preserving). Self-contained: owns its own
|
|
3
|
+
// heartbeat timer + last-note dedup state, and depends only on the
|
|
4
|
+
// active-instance read/refresh primitives + identity, threaded as injected
|
|
5
|
+
// deps so the module reads live file-level references at call time.
|
|
6
|
+
function createOwnerHeartbeat({
|
|
7
|
+
getInstanceId,
|
|
8
|
+
readActiveInstance,
|
|
9
|
+
refreshActiveInstance,
|
|
10
|
+
OWNER_HEARTBEAT_INTERVAL_MS = 5e3,
|
|
11
|
+
}) {
|
|
12
|
+
let lastOwnershipNote = "";
|
|
13
|
+
let ownerHeartbeatTimer = null;
|
|
14
|
+
|
|
15
|
+
function logOwnership(note) {
|
|
16
|
+
if (lastOwnershipNote === note) return;
|
|
17
|
+
lastOwnershipNote = note;
|
|
18
|
+
process.stderr.write(`[ownership] ${note}
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
function currentOwnerState() {
|
|
22
|
+
const active = readActiveInstance();
|
|
23
|
+
return {
|
|
24
|
+
active,
|
|
25
|
+
// Strict last-wins: this process owns the bridge ONLY when active-instance
|
|
26
|
+
// names exactly this INSTANCE_ID. A newer remote session that claims the
|
|
27
|
+
// seat overwrites instanceId, so the old owner immediately reads owned=false
|
|
28
|
+
// and disconnects on its next refresh tick. No PID/terminal fallback —
|
|
29
|
+
// that used to let a co-terminal worker wrongly self-claim.
|
|
30
|
+
owned: active?.instanceId === getInstanceId()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function getBridgeOwnershipSnapshot() {
|
|
34
|
+
return currentOwnerState();
|
|
35
|
+
}
|
|
36
|
+
function claimBridgeOwnership(reason) {
|
|
37
|
+
refreshActiveInstance(getInstanceId());
|
|
38
|
+
logOwnership(`claimed owner (${reason})`);
|
|
39
|
+
}
|
|
40
|
+
function startOwnerHeartbeat() {
|
|
41
|
+
if (ownerHeartbeatTimer) return;
|
|
42
|
+
ownerHeartbeatTimer = setInterval(() => {
|
|
43
|
+
try {
|
|
44
|
+
// Last-wins guard: only refresh the seat if we STILL own it. If a newer
|
|
45
|
+
// remote session claimed active-instance.json since our last tick, do
|
|
46
|
+
// NOT overwrite it back — that would re-steal ownership and cause
|
|
47
|
+
// ping-pong / double backend connections. The bridgeOwnershipTimer's
|
|
48
|
+
// refreshBridgeOwnership() will observe owned=false and disconnect us.
|
|
49
|
+
// onlyIfOwned: re-checks ownership INSIDE the file lock so a newer
|
|
50
|
+
// owner claiming the seat between currentOwnerState() and the locked
|
|
51
|
+
// write is never overwritten (see refreshActiveInstance CAS guard).
|
|
52
|
+
if (currentOwnerState().owned) refreshActiveInstance(getInstanceId(), undefined, { onlyIfOwned: true });
|
|
53
|
+
} catch (e) {
|
|
54
|
+
process.stderr.write(`[ownership] heartbeat refresh failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
55
|
+
}
|
|
56
|
+
}, OWNER_HEARTBEAT_INTERVAL_MS);
|
|
57
|
+
ownerHeartbeatTimer.unref?.();
|
|
58
|
+
}
|
|
59
|
+
function stopOwnerHeartbeat() {
|
|
60
|
+
if (!ownerHeartbeatTimer) return;
|
|
61
|
+
clearInterval(ownerHeartbeatTimer);
|
|
62
|
+
ownerHeartbeatTimer = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
logOwnership,
|
|
67
|
+
currentOwnerState,
|
|
68
|
+
getBridgeOwnershipSnapshot,
|
|
69
|
+
claimBridgeOwnership,
|
|
70
|
+
startOwnerHeartbeat,
|
|
71
|
+
stopOwnerHeartbeat,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { createOwnerHeartbeat };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Worker → parent IPC bridge. Extracted verbatim from channels/index.mjs
|
|
2
|
+
// (behavior-preserving). Groups the notify-to-parent path and the
|
|
3
|
+
// worker → parent → memory call bridge. Bound to live getters
|
|
4
|
+
// (getInstanceId) so runtime identity stays consistent.
|
|
5
|
+
function normalizeChannelNotifyParams(method, params) {
|
|
6
|
+
if (method === 'notifications/claude/channel' && params && params.meta) {
|
|
7
|
+
const m = {};
|
|
8
|
+
for (const [k, v] of Object.entries(params.meta)) {
|
|
9
|
+
if (v === undefined || v === null) continue;
|
|
10
|
+
m[k] = k === 'silent_to_agent' ? (v === true || v === 'true') : String(v);
|
|
11
|
+
}
|
|
12
|
+
return { ...params, meta: m };
|
|
13
|
+
}
|
|
14
|
+
return params;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createParentBridge({ getInstanceId }) {
|
|
18
|
+
function sendNotifyToParent(method, params) {
|
|
19
|
+
// CC channel schema requires meta: Record<string,string> (channelNotification.ts).
|
|
20
|
+
// Coerce every meta value to string so a non-string (e.g. a Discord
|
|
21
|
+
// interaction.type number) can't fail zod and silently drop the notify.
|
|
22
|
+
// silent_to_agent stays boolean — an internal routing flag the daemon
|
|
23
|
+
// router / agentNotify consume (=== true) before the CC zod boundary.
|
|
24
|
+
const outParams = normalizeChannelNotifyParams(method, params);
|
|
25
|
+
if (!process.send) {
|
|
26
|
+
try { process.stderr.write(`mixdog channels: notify dropped (no IPC channel): ${method}\n`); } catch {}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
process.send({ type: 'notify', method, params: outParams });
|
|
31
|
+
} catch (err) {
|
|
32
|
+
try { process.stderr.write(`mixdog channels: notify IPC send failed: ${err && err.message || err}\n`); } catch {}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Memory worker bridge (worker → parent → memory) ─────────────────
|
|
37
|
+
// The channels worker does not own the memory worker handle. To trigger
|
|
38
|
+
// memory tool actions (e.g. cycle1) we send `memory_call_request` to the
|
|
39
|
+
// parent, which routes through callWorker('memory', ...) and ships the
|
|
40
|
+
// result back as `memory_call_response`. The response listener is
|
|
41
|
+
// integrated into the main IPC handler below (not a second listener).
|
|
42
|
+
const _memoryCallPending = new Map();
|
|
43
|
+
let _memoryCallSeq = 0;
|
|
44
|
+
|
|
45
|
+
function callMemoryAction(action, args, timeoutMs) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
if (!process.send) return reject(new Error('not a worker process'));
|
|
48
|
+
const callId = `mc_${getInstanceId()}_${++_memoryCallSeq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
_memoryCallPending.delete(callId);
|
|
51
|
+
reject(new Error(`memory_call ${action} timed out after ${timeoutMs}ms`));
|
|
52
|
+
}, timeoutMs);
|
|
53
|
+
_memoryCallPending.set(callId, {
|
|
54
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
55
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
process.send({ type: 'memory_call_request', callId, action, args: args || {} });
|
|
59
|
+
} catch (e) {
|
|
60
|
+
_memoryCallPending.delete(callId);
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(e);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Response side of the worker → parent → memory bridge. The caller routes
|
|
68
|
+
// `memory_call_response` messages here from its single process IPC handler
|
|
69
|
+
// (keeping IPC dispatch in one place). Returns true when the message was a
|
|
70
|
+
// recognised memory_call_response (handled), false otherwise.
|
|
71
|
+
function handleMemoryCallResponse(msg) {
|
|
72
|
+
if (!(msg && msg.type === 'memory_call_response' && msg.callId)) return false;
|
|
73
|
+
const pending = _memoryCallPending.get(msg.callId);
|
|
74
|
+
if (!pending) return true;
|
|
75
|
+
_memoryCallPending.delete(msg.callId);
|
|
76
|
+
if (msg.ok) pending.resolve(msg.result);
|
|
77
|
+
else pending.reject(new Error(msg.error || 'memory_call failed'));
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
sendNotifyToParent,
|
|
83
|
+
callMemoryAction,
|
|
84
|
+
handleMemoryCallResponse,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { createParentBridge, normalizeChannelNotifyParams };
|
|
@@ -104,7 +104,7 @@ function touchUiHeartbeat(instanceId) {
|
|
|
104
104
|
if (!curRaw) return undefined;
|
|
105
105
|
if (instanceId && curRaw.instanceId !== instanceId) return undefined;
|
|
106
106
|
return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
|
|
107
|
-
}, { compact: true, fsync: false, fsyncDir: false });
|
|
107
|
+
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
|
|
108
108
|
} catch { /* best-effort; a missed tick just relies on the next one */ }
|
|
109
109
|
}
|
|
110
110
|
function buildRuntimeIdentity() {
|
|
@@ -209,7 +209,7 @@ function readActiveInstance() {
|
|
|
209
209
|
&& _staleSup2 === undefined;
|
|
210
210
|
if (ownerEmpty) return undefined;
|
|
211
211
|
return { ...stableRestLocked, updatedAt: Date.now() };
|
|
212
|
-
}, { compact: true, fsync: false, fsyncDir: false });
|
|
212
|
+
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
|
|
213
213
|
} catch {}
|
|
214
214
|
}
|
|
215
215
|
return null;
|
|
@@ -240,11 +240,21 @@ function buildActiveInstanceState(instanceId, meta) {
|
|
|
240
240
|
...typeof meta?.backendReady === "boolean" ? { backendReady: meta.backendReady } : {}
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
|
-
function refreshActiveInstance(instanceId, meta) {
|
|
243
|
+
function refreshActiveInstance(instanceId, meta, options) {
|
|
244
244
|
ensureRuntimeDirs();
|
|
245
245
|
return updateJsonAtomicSync(ACTIVE_INSTANCE_FILE, (curRaw) => {
|
|
246
246
|
const prevForPreserve = curRaw;
|
|
247
247
|
const prev = activeInstanceStaleReason(curRaw) ? null : curRaw;
|
|
248
|
+
// CAS guard (opt-in via options.onlyIfOwned): heartbeat/refresh ticks
|
|
249
|
+
// check ownership OUTSIDE this lock, so a newer session can claim the
|
|
250
|
+
// seat between that check and this locked update. Without this re-read,
|
|
251
|
+
// the tick would overwrite the newer owner's instanceId (TOCTOU
|
|
252
|
+
// re-steal -> ownership ping-pong / double backend connections).
|
|
253
|
+
// Returning undefined aborts the update with no write. Explicit claims
|
|
254
|
+
// (boot, claimBridgeOwnership) omit the option and stay last-wins.
|
|
255
|
+
if (options?.onlyIfOwned && (!prev?.instanceId || prev.instanceId !== instanceId)) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
248
258
|
// Drop stale fields (pid/startedAt) written by older server versions.
|
|
249
259
|
const { pid: _legacyPid, startedAt: _legacyStartedAt, ...prevRest } = prev ?? {};
|
|
250
260
|
const identity = buildRuntimeIdentity();
|
|
@@ -362,7 +372,7 @@ function refreshActiveInstance(instanceId, meta) {
|
|
|
362
372
|
}
|
|
363
373
|
}
|
|
364
374
|
return { ...preservedExtra, ...next };
|
|
365
|
-
}, { compact: true, fsync: false, fsyncDir: false });
|
|
375
|
+
}, { compact: true, fsync: false, fsyncDir: false, renameFallback: 'truncate' });
|
|
366
376
|
}
|
|
367
377
|
const SERVER_PID_FILE = join(
|
|
368
378
|
RUNTIME_ROOT,
|
|
@@ -3,7 +3,7 @@ import { appendFile as _appendFile } from "fs";
|
|
|
3
3
|
import { join, isAbsolute } from "path";
|
|
4
4
|
import { tmpdir } from "os";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
|
-
import { DATA_DIR
|
|
6
|
+
import { DATA_DIR } from "./config.mjs";
|
|
7
7
|
import { runScript as execScript, ensureNopluginDir } from "./executor.mjs";
|
|
8
8
|
import { withFileLockSync } from "../../shared/atomic-file.mjs";
|
|
9
9
|
import { makeAgentDispatch } from '../../agent/orchestrator/agent-runtime/agent-dispatch.mjs';
|
|
@@ -39,7 +39,6 @@ function logSchedule(msg) {
|
|
|
39
39
|
if (!_schedLogTimer) _schedLogTimer = setTimeout(_flushScheduleLog, 2000);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
import { isHoliday } from "./holidays.mjs";
|
|
43
42
|
import { tryRead } from "./settings.mjs";
|
|
44
43
|
// node-cron is an optional runtime dep. If the module isn't installed
|
|
45
44
|
// (e.g. a fresh v0.6.190 where node_modules predates the package.json
|
|
@@ -104,7 +103,7 @@ function tzSnapshot(now, tz) {
|
|
|
104
103
|
class Scheduler {
|
|
105
104
|
nonInteractive;
|
|
106
105
|
interactive;
|
|
107
|
-
|
|
106
|
+
channelId;
|
|
108
107
|
promptsDir;
|
|
109
108
|
tickTimer = null;
|
|
110
109
|
lastFired = /* @__PURE__ */ new Map();
|
|
@@ -122,53 +121,16 @@ class Scheduler {
|
|
|
122
121
|
// names skipped for today
|
|
123
122
|
skippedTodayDate = "";
|
|
124
123
|
// "YYYY-MM-DD" local date the skippedToday set belongs to
|
|
125
|
-
holidayCountry = null;
|
|
126
|
-
// ISO country code for holiday check
|
|
127
|
-
holidayChecked = "";
|
|
128
|
-
// "YYYY-MM-DD" last checked date
|
|
129
|
-
todayIsHoliday = false;
|
|
130
|
-
// cached result for today
|
|
131
|
-
quietSchedule = null;
|
|
132
|
-
// global quiet hours "HH:MM-HH:MM"
|
|
133
124
|
cronJobs = /* @__PURE__ */ new Map();
|
|
134
125
|
// name -> node-cron ScheduledTask for cron-expression entries
|
|
135
126
|
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
// at the top level. `channelsConfig` is still accepted separately
|
|
140
|
-
// (channel metadata only, not quiet config) because resolveChannel()
|
|
141
|
-
// needs the channel-label → platform-id map.
|
|
142
|
-
constructor(nonInteractive, interactive, channelsConfig, topConfig) {
|
|
127
|
+
// `channelId` is the single resolved main-channel id used when a schedule's
|
|
128
|
+
// `channel` flag is set (post-to-channel); absent flag → inject into session.
|
|
129
|
+
constructor(nonInteractive, interactive, channelId) {
|
|
143
130
|
this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
|
|
144
131
|
this.interactive = interactive.filter((s) => s.enabled !== false);
|
|
145
|
-
this.
|
|
132
|
+
this.channelId = channelId ?? "";
|
|
146
133
|
this.promptsDir = join(DATA_DIR, "prompts");
|
|
147
|
-
this._applyQuietConfig(topConfig);
|
|
148
|
-
}
|
|
149
|
-
/** Resolve quiet/schedules flags from the top-level config
|
|
150
|
-
* (0.1.62 shape: `topConfig.quiet`, `topConfig.schedules`). Falls
|
|
151
|
-
* through silently to defaults (empty schedule, holidays off,
|
|
152
|
-
* respect flag default true) when topConfig is missing or malformed
|
|
153
|
-
* — defensive, not legacy. */
|
|
154
|
-
_applyQuietConfig(topConfig) {
|
|
155
|
-
const cfg = (topConfig && typeof topConfig === "object") ? topConfig : null;
|
|
156
|
-
const quietSrc = cfg?.quiet ?? null;
|
|
157
|
-
const schedulesSrc = cfg?.schedules ?? null;
|
|
158
|
-
// Holidays: prefer an explicit ISO country code. Legacy boolean `true`
|
|
159
|
-
// from older setup UI builds is normalized to the local default country.
|
|
160
|
-
const hol = quietSrc?.holidays;
|
|
161
|
-
if (hol === true) {
|
|
162
|
-
this.holidayCountry = DEFAULT_HOLIDAY_COUNTRY;
|
|
163
|
-
} else if (typeof hol === "string" && hol) {
|
|
164
|
-
this.holidayCountry = hol.trim().toUpperCase();
|
|
165
|
-
} else {
|
|
166
|
-
this.holidayCountry = null;
|
|
167
|
-
}
|
|
168
|
-
// Quiet window string "HH:MM-HH:MM" from topConfig.quiet.schedule.
|
|
169
|
-
this.quietSchedule = quietSrc?.schedule ?? null;
|
|
170
|
-
// Opt-in flag: default true when unspecified, matching applyDefaults.
|
|
171
|
-
this.respectQuietSchedules = schedulesSrc?.respectQuiet !== false;
|
|
172
134
|
}
|
|
173
135
|
setInjectHandler(fn) {
|
|
174
136
|
this.injectFn = fn;
|
|
@@ -372,8 +334,8 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
372
334
|
}
|
|
373
335
|
}
|
|
374
336
|
}
|
|
375
|
-
/** Fire path for a cron-triggered entry. Applies day
|
|
376
|
-
*
|
|
337
|
+
/** Fire path for a cron-triggered entry. Applies day guards against
|
|
338
|
+
* the schedule's TZ (or local when absent). */
|
|
377
339
|
async onCronFire(schedule, type) {
|
|
378
340
|
const now = /* @__PURE__ */ new Date();
|
|
379
341
|
const tz = schedule.timezone || null;
|
|
@@ -381,16 +343,6 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
381
343
|
const isWeekend = snap.dow === 0 || snap.dow === 6;
|
|
382
344
|
const days = schedule.days ?? "daily";
|
|
383
345
|
if (!this.matchesDays(days, snap.dow, isWeekend)) return;
|
|
384
|
-
if (this.holidayCountry) {
|
|
385
|
-
try {
|
|
386
|
-
const holiday = await isHoliday(this.tzDate(now, tz), this.holidayCountry);
|
|
387
|
-
if (holiday && (schedule.skipHolidays || days === "weekday")) {
|
|
388
|
-
logSchedule(`skipping "${schedule.name}" \u2014 public holiday\n`);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
} catch {}
|
|
392
|
-
}
|
|
393
|
-
if ((schedule.dnd || this.respectQuietSchedules) && this.isQuietHours(now, tz)) return;
|
|
394
346
|
if (this.shouldSkip(schedule.name)) return;
|
|
395
347
|
// Record lastFired only when the fire actually proceeds past
|
|
396
348
|
// fireTimed's running/precondition guards (it resolves truthy on a
|
|
@@ -401,13 +353,6 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
401
353
|
(err) => process.stderr.write(`mixdog scheduler: ${schedule.name} failed: ${err}\n`)
|
|
402
354
|
);
|
|
403
355
|
}
|
|
404
|
-
/** Produce a Date whose calendar day matches the TZ-adjusted dateStr,
|
|
405
|
-
* so holiday lookups by country work against the right day. */
|
|
406
|
-
tzDate(now, tz) {
|
|
407
|
-
if (!tz) return now;
|
|
408
|
-
const snap = tzSnapshot(now, tz);
|
|
409
|
-
return new Date(`${snap.dateStr}T12:00:00Z`);
|
|
410
|
-
}
|
|
411
356
|
stop() {
|
|
412
357
|
if (this.tickTimer) {
|
|
413
358
|
clearInterval(this.tickTimer);
|
|
@@ -447,14 +392,11 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
447
392
|
} catch {}
|
|
448
393
|
this.start();
|
|
449
394
|
}
|
|
450
|
-
reloadConfig(nonInteractive, interactive,
|
|
395
|
+
reloadConfig(nonInteractive, interactive, channelId, options = {}) {
|
|
451
396
|
this.nonInteractive = nonInteractive.filter((s) => s.enabled !== false);
|
|
452
397
|
this.interactive = interactive.filter((s) => s.enabled !== false);
|
|
453
|
-
this.
|
|
398
|
+
this.channelId = channelId ?? "";
|
|
454
399
|
this.promptsDir = join(DATA_DIR, "prompts");
|
|
455
|
-
this._applyQuietConfig(topConfig);
|
|
456
|
-
this.holidayChecked = "";
|
|
457
|
-
this.todayIsHoliday = false;
|
|
458
400
|
if (this.deferred.size > 0 || this.skippedToday.size > 0) {
|
|
459
401
|
process.stderr.write(`mixdog scheduler: reload clearing ${this.deferred.size} deferred, ${this.skippedToday.size} skipped
|
|
460
402
|
`);
|
|
@@ -520,24 +462,9 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
520
462
|
);
|
|
521
463
|
}
|
|
522
464
|
async tickAsync() {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
-
// tick() only drives the holiday cache refresh.
|
|
527
|
-
if (this.holidayCountry && this.holidayChecked !== dateStr) {
|
|
528
|
-
this.holidayChecked = dateStr;
|
|
529
|
-
try {
|
|
530
|
-
this.todayIsHoliday = await isHoliday(now, this.holidayCountry);
|
|
531
|
-
if (this.todayIsHoliday) {
|
|
532
|
-
process.stderr.write(`mixdog scheduler: today (${dateStr}) is a holiday \u2014 weekday schedules will be skipped
|
|
533
|
-
`);
|
|
534
|
-
}
|
|
535
|
-
} catch (err) {
|
|
536
|
-
process.stderr.write(`mixdog scheduler: holiday check failed: ${err}
|
|
537
|
-
`);
|
|
538
|
-
this.todayIsHoliday = false;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
465
|
+
// All timed schedules are handled exclusively by node-cron
|
|
466
|
+
// (registerCronJobs). tick() no longer does any per-day work; the
|
|
467
|
+
// interval is kept for future cadence-driven work and lifecycle parity.
|
|
541
468
|
}
|
|
542
469
|
/** Day abbreviation → JS day number (0=Sun...6=Sat) */
|
|
543
470
|
static DAY_ABBRS = {
|
|
@@ -557,18 +484,6 @@ ${Scheduler.INSTANCE_UUID}`;
|
|
|
557
484
|
const dayList = days.split(",").map((d) => d.trim().toLowerCase());
|
|
558
485
|
return dayList.some((d) => Scheduler.DAY_ABBRS[d] === dow);
|
|
559
486
|
}
|
|
560
|
-
/** Check if current time is within global quiet hours (quiet.schedule).
|
|
561
|
-
* tz optional — when set, HH:MM is evaluated in the given IANA zone.
|
|
562
|
-
*
|
|
563
|
-
* Delegates to the shared, now TZ-aware isInQuietWindow(cfg, now, tz)
|
|
564
|
-
* helper in lib/config.mjs. Holidays are passed as `false` here on
|
|
565
|
-
* purpose: public-holiday skips for the scheduler are handled by the
|
|
566
|
-
* separate holidayCountry / skipHolidays / days==="weekday" path in
|
|
567
|
-
* onCronFire, so this quiet-window check stays schedule-window only —
|
|
568
|
-
* identical to the prior local implementation. */
|
|
569
|
-
isQuietHours(now, tz) {
|
|
570
|
-
return isInQuietWindow({ schedule: this.quietSchedule, holidays: false }, now, tz);
|
|
571
|
-
}
|
|
572
487
|
// ── Fire timed schedule ─────────────────────────────────────────────
|
|
573
488
|
async fireTimed(schedule, type) {
|
|
574
489
|
const execMode = schedule.exec ?? "prompt";
|
|
@@ -680,21 +595,20 @@ ${scriptResult}
|
|
|
680
595
|
});
|
|
681
596
|
}
|
|
682
597
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
683
|
-
/**
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
return label;
|
|
598
|
+
/**
|
|
599
|
+
* Map a schedule's `channel` flag to a target channel id. The flag is
|
|
600
|
+
* boolean-ish: any non-empty value except "false" (including legacy labels
|
|
601
|
+
* like "main") means "post to the main channel"; absent/false → "" (inject
|
|
602
|
+
* into the Lead session). No label resolution. A pure-digit / snowflake
|
|
603
|
+
* value is honored verbatim as an explicit id override (matches
|
|
604
|
+
* resolveWebhookChannelId in index.mjs).
|
|
605
|
+
*/
|
|
606
|
+
resolveChannel(flag) {
|
|
607
|
+
if (flag == null) return "";
|
|
608
|
+
const v = String(flag).trim();
|
|
609
|
+
if (v === "" || v.toLowerCase() === "false") return "";
|
|
610
|
+
if (/^-?\d+$/.test(v)) return v;
|
|
611
|
+
return this.channelId ?? "";
|
|
698
612
|
}
|
|
699
613
|
/** Resolve prompt: try file first, fall back to inline text */
|
|
700
614
|
resolvePrompt(schedule) {
|