mixdog 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/build-tui.mjs +13 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +30 -67
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +5 -2
- package/src/agents/heavy-worker/AGENT.md +21 -11
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +3 -2
- package/src/agents/worker/AGENT.md +21 -11
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +933 -3731
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/repl.mjs +5 -5
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/agent/30-explorer.md +8 -11
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +2 -9
- package/src/rules/shared/01-tool.md +11 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +241 -894
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/telegram-format.mjs +19 -22
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +258 -2050
- package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +20 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +238 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +6 -14
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +52 -2
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +291 -0
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +126 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +155 -677
- package/src/standalone/channel-worker.mjs +7 -9
- package/src/standalone/explore-tool.mjs +40 -12
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +110 -746
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +125 -0
- package/src/standalone/provider-admin.mjs +15 -19
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +1163 -7571
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +259 -80
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +56 -588
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/components/tool-output-format.mjs +2 -2
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +18034 -17188
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +405 -1385
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +60 -10
- package/src/tui/keyboard-protocol.mjs +2 -2
- package/src/tui/lib/voice-recorder.mjs +35 -19
- package/src/tui/markdown/format-token.mjs +11 -9
- package/src/tui/paste-attachments.mjs +38 -0
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/tui/themes/base.mjs +2 -2
- package/src/tui/themes/kanagawa.mjs +4 -4
- package/src/tui/themes/teal.mjs +4 -5
- package/src/tui/themes/utils.mjs +1 -1
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +77 -462
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +16 -9
- package/src/workflows/sequential/WORKFLOW.md +16 -11
- package/src/workflows/solo/WORKFLOW.md +5 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -213,6 +213,155 @@ export async function flushEmbeddingDirty(db, options = {}) {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// Embeds raw transcript rows (is_root = 0, non-chunk-member) so the dense
|
|
217
|
+
// recall leg can rank them alongside root/summary entries. Mirrors
|
|
218
|
+
// flushEmbeddingDirty's SKIP LOCKED batch-claim structure but sources the
|
|
219
|
+
// embed text from `content` (raw rows have no element/summary) and honors
|
|
220
|
+
// `limit` as a total cap across the whole call rather than an unbounded loop.
|
|
221
|
+
//
|
|
222
|
+
// Stale-embedding safety when recap toggles back ON: cycle1's promotion
|
|
223
|
+
// UPDATE (memory-cycle1.mjs chunk commit) SETs summary/element, which fires
|
|
224
|
+
// trg_entries_embedding_invalidate (memory.mjs — BEFORE UPDATE OF content,
|
|
225
|
+
// summary, element; NEW.is_root=1 AND summary IS DISTINCT FROM OLD.summary)
|
|
226
|
+
// and NULLs the raw-content embedding written here; cycle1's end-of-cycle
|
|
227
|
+
// flushEmbeddingDirty (is_root=1 AND embedding IS NULL) then re-embeds the
|
|
228
|
+
// root from its fresh element/summary. Member leaves keep their content
|
|
229
|
+
// embedding — the dense recall leg resolves members to their chunk root
|
|
230
|
+
// (memory-recall-store.mjs member→root resolution), so that is leaf-level
|
|
231
|
+
// dense matching, not staleness.
|
|
232
|
+
export async function flushRawEmbeddings(db, options = {}) {
|
|
233
|
+
const { limit = 200, signal } = options ?? {}
|
|
234
|
+
throwIfAborted(signal)
|
|
235
|
+
await ensureEmbCacheTable(db)
|
|
236
|
+
throwIfAborted(signal)
|
|
237
|
+
|
|
238
|
+
let attempted = 0
|
|
239
|
+
let embedded = 0
|
|
240
|
+
let cursor = 0
|
|
241
|
+
|
|
242
|
+
while (attempted < limit) {
|
|
243
|
+
throwIfAborted(signal)
|
|
244
|
+
const batchCap = Math.min(BATCH_SIZE, limit - attempted)
|
|
245
|
+
const client = await db._pool.connect()
|
|
246
|
+
let ids
|
|
247
|
+
try {
|
|
248
|
+
throwIfAborted(signal)
|
|
249
|
+
await client.query('BEGIN')
|
|
250
|
+
const res = await client.query(
|
|
251
|
+
`SELECT id FROM memory.entries
|
|
252
|
+
WHERE is_root = 0 AND chunk_root IS NULL AND embedding IS NULL
|
|
253
|
+
AND NULLIF(btrim(session_id), '') IS NOT NULL
|
|
254
|
+
AND content IS NOT NULL
|
|
255
|
+
AND id > $2
|
|
256
|
+
ORDER BY id
|
|
257
|
+
LIMIT $1
|
|
258
|
+
FOR UPDATE SKIP LOCKED`,
|
|
259
|
+
[batchCap, cursor],
|
|
260
|
+
)
|
|
261
|
+
ids = res.rows.map(r => Number(r.id))
|
|
262
|
+
throwIfAborted(signal)
|
|
263
|
+
} catch (err) {
|
|
264
|
+
try { await client.query('ROLLBACK') } catch {}
|
|
265
|
+
client.release()
|
|
266
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
267
|
+
__mixdogMemoryLog(`[embed] raw flush SKIP LOCKED claim failed: ${err.message}\n`)
|
|
268
|
+
break
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (ids.length === 0) {
|
|
272
|
+
try { await client.query('COMMIT') } catch {}
|
|
273
|
+
client.release()
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
cursor = ids[ids.length - 1]
|
|
278
|
+
attempted += ids.length
|
|
279
|
+
let batchDone = false
|
|
280
|
+
try {
|
|
281
|
+
throwIfAborted(signal)
|
|
282
|
+
const writtenIds = await syncRawBatchEmbeddings(client, ids, { signal })
|
|
283
|
+
embedded += writtenIds.length
|
|
284
|
+
batchDone = true
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
287
|
+
__mixdogMemoryLog(`[embed] raw batch failed (ids=${ids[0]}..${ids[ids.length-1]}): ${err.message}\n`)
|
|
288
|
+
} finally {
|
|
289
|
+
try {
|
|
290
|
+
if (batchDone) await client.query('COMMIT')
|
|
291
|
+
else await client.query('ROLLBACK')
|
|
292
|
+
} catch {}
|
|
293
|
+
client.release()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (ids.length < batchCap) break
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { attempted, embedded }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Batch-embed raw transcript rows: content is the embed source text (raw
|
|
303
|
+
// rows have no element/summary). Mirrors syncBatchEmbeddings' shape.
|
|
304
|
+
async function syncRawBatchEmbeddings(db, ids, options = {}) {
|
|
305
|
+
const signal = options?.signal
|
|
306
|
+
throwIfAborted(signal)
|
|
307
|
+
const rows = (await db.query(
|
|
308
|
+
`SELECT id, content FROM memory.entries
|
|
309
|
+
WHERE id = ANY($1::bigint[]) AND is_root = 0 AND chunk_root IS NULL`,
|
|
310
|
+
[ids],
|
|
311
|
+
)).rows
|
|
312
|
+
throwIfAborted(signal)
|
|
313
|
+
if (rows.length === 0) return []
|
|
314
|
+
|
|
315
|
+
const dimsRow = (await db.query(`SELECT value FROM memory.meta WHERE key = 'embedding.current_dims'`)).rows[0]
|
|
316
|
+
throwIfAborted(signal)
|
|
317
|
+
const expected = Number(dimsRow?.value ?? 0)
|
|
318
|
+
|
|
319
|
+
const texts = rows.map(row => (typeof row.content === 'string' ? row.content.trim() : ''))
|
|
320
|
+
const vectors = await cachedEmbedTextBatch(db, texts.filter(Boolean), { signal })
|
|
321
|
+
throwIfAborted(signal)
|
|
322
|
+
let vecIdx = 0
|
|
323
|
+
const updates = [] // { id, content, vector }
|
|
324
|
+
for (let i = 0; i < rows.length; i++) {
|
|
325
|
+
throwIfAborted(signal)
|
|
326
|
+
const row = rows[i]
|
|
327
|
+
if (!texts[i]) continue
|
|
328
|
+
const vector = vectors[vecIdx++]
|
|
329
|
+
if (!Array.isArray(vector) || vector.length === 0) continue
|
|
330
|
+
if (Number.isFinite(expected) && expected > 0 && vector.length !== expected) {
|
|
331
|
+
__mixdogMemoryLog(`[embed] raw dim mismatch (id=${row.id} got=${vector.length} expected=${expected})\n`)
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
updates.push({ id: Number(row.id), content: row.content, vector })
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (updates.length === 0) return []
|
|
338
|
+
|
|
339
|
+
const valClauses = updates.map((u, i) => {
|
|
340
|
+
const base = i * 3
|
|
341
|
+
return `($${base+1}::bigint, $${base+2}::halfvec, $${base+3}::text)`
|
|
342
|
+
})
|
|
343
|
+
const params = []
|
|
344
|
+
for (const u of updates) {
|
|
345
|
+
params.push(u.id, embeddingToSql(u.vector), u.content)
|
|
346
|
+
}
|
|
347
|
+
const res = await db.query(
|
|
348
|
+
`UPDATE memory.entries AS e
|
|
349
|
+
SET embedding = t.vector
|
|
350
|
+
FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, content)
|
|
351
|
+
WHERE e.id = t.id AND e.is_root = 0 AND e.chunk_root IS NULL
|
|
352
|
+
AND e.embedding IS NULL
|
|
353
|
+
AND e.content IS NOT DISTINCT FROM t.content
|
|
354
|
+
RETURNING e.id`,
|
|
355
|
+
params,
|
|
356
|
+
)
|
|
357
|
+
throwIfAborted(signal)
|
|
358
|
+
const writtenIds = (res.rows || []).map(r => Number(r.id))
|
|
359
|
+
if (writtenIds.length < updates.length) {
|
|
360
|
+
__mixdogMemoryLog(`[embed-sync] ${updates.length - writtenIds.length} raw stale-write(s) skipped — content changed since read\n`)
|
|
361
|
+
}
|
|
362
|
+
return writtenIds
|
|
363
|
+
}
|
|
364
|
+
|
|
216
365
|
// Batch-embed all ids in one DB round-trip for fetching, one API call per unique text,
|
|
217
366
|
// then one VALUES UPDATE for all results.
|
|
218
367
|
// Returns the list of ids that were actually UPDATEd with a fresh embedding.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Extracted from index.mjs — single/multi-instance process-lock + owner-election
|
|
2
|
+
// helpers. Pure functions parameterized on lock-file paths so index.mjs retains
|
|
3
|
+
// ownership of DATA_DIR-derived constants and the daemon logger.
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import { execFileSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
export function parsePositivePid(value) {
|
|
8
|
+
const pid = Number(value)
|
|
9
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isPidAliveLocal(pid) {
|
|
13
|
+
if (!Number.isFinite(pid) || pid <= 0) return false
|
|
14
|
+
try { process.kill(pid, 0); return true }
|
|
15
|
+
catch (e) { return e.code !== 'ESRCH' }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function tryAcquireMemoryOwnerLock(ownerLockFile, log = () => {}) {
|
|
19
|
+
// Returns true on success (this process now owns memory worker for the data
|
|
20
|
+
// dir), false when a live peer holds the lock. Stale locks (dead PID) are
|
|
21
|
+
// unlinked and retried atomically. Throws on unexpected fs errors so callers
|
|
22
|
+
// surface lock-system corruption rather than silently downgrading.
|
|
23
|
+
//
|
|
24
|
+
// EPERM/EBUSY/EACCES at openSync are transient — AV scanners (SignKorea /
|
|
25
|
+
// SKCert / ezPDFWS etc) briefly lock newly-created files during inspection.
|
|
26
|
+
// The 0.1.x baseline threw immediately and the worker promoted to
|
|
27
|
+
// permanentlyDegraded, killing memory tools for the rest of the session.
|
|
28
|
+
// Treat the AV error codes as retryable with bounded backoff (~750ms total)
|
|
29
|
+
// before giving up and rethrowing.
|
|
30
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
31
|
+
try {
|
|
32
|
+
const fd = fs.openSync(ownerLockFile, 'wx')
|
|
33
|
+
fs.writeSync(fd, String(process.pid))
|
|
34
|
+
fs.closeSync(fd)
|
|
35
|
+
return true
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e.code === 'EEXIST') {
|
|
38
|
+
let ownerPid = NaN
|
|
39
|
+
try { ownerPid = Number(fs.readFileSync(ownerLockFile, 'utf8').trim()) } catch {}
|
|
40
|
+
if (isPidAliveLocal(ownerPid)) return false
|
|
41
|
+
// Stale lock: dead owner — unlink and retry exclusive create.
|
|
42
|
+
try { fs.unlinkSync(ownerLockFile) } catch {}
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
const transient = e.code === 'EPERM' || e.code === 'EBUSY' || e.code === 'EACCES'
|
|
46
|
+
if (transient && attempt < 4) {
|
|
47
|
+
// Sync busy-wait acceptable here: this runs on memory worker boot
|
|
48
|
+
// path, once per process; the parent handler is not blocked.
|
|
49
|
+
const end = Date.now() + 50 * (attempt + 1)
|
|
50
|
+
while (Date.now() < end) {}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
throw e
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function releaseMemoryOwnerLock(ownerLockFile) {
|
|
60
|
+
try {
|
|
61
|
+
const ownerPid = Number(fs.readFileSync(ownerLockFile, 'utf8').trim())
|
|
62
|
+
if (ownerPid === process.pid) fs.unlinkSync(ownerLockFile)
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function killPreviousServer(pid, log = () => {}) {
|
|
67
|
+
if (pid <= 0 || pid === process.pid) return false
|
|
68
|
+
if (process.platform === 'win32') {
|
|
69
|
+
try {
|
|
70
|
+
execFileSync('taskkill', ['/F', '/T', '/PID', String(pid)], { encoding: 'utf8', timeout: 5000, windowsHide: true })
|
|
71
|
+
log(`[memory-service] Killed previous server PID ${pid}\n`)
|
|
72
|
+
return true
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Exit code 128 = process not found; treat stale lock as already-dead = success.
|
|
75
|
+
// Status 128 reliably means "process not found" regardless of locale; no text match needed.
|
|
76
|
+
// Status 1 with English text match handles edge cases on some Windows versions.
|
|
77
|
+
const notFoundText = /not found|no running instance/i.test(e.stdout || '')
|
|
78
|
+
|| /not found|no running instance/i.test(e.stderr || '')
|
|
79
|
+
|| /not found|no running instance/i.test(e.message || '')
|
|
80
|
+
const alreadyDead = e.status === 128 || (e.status === 1 && notFoundText)
|
|
81
|
+
if (alreadyDead) {
|
|
82
|
+
log(`[memory-service] PID ${pid} already dead (stale lock), proceeding\n`)
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
log(`[memory-service] taskkill failed for PID ${pid}: ${e.message}\n`)
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Pre-flight: if the process is already gone, treat stale lock as success.
|
|
90
|
+
try {
|
|
91
|
+
process.kill(pid, 0)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (e.code === 'ESRCH') {
|
|
94
|
+
log(`[memory-service] PID ${pid} already dead (stale lock), proceeding\n`)
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try { process.kill(pid, 'SIGTERM') } catch {}
|
|
99
|
+
try { process.kill(pid, 'SIGKILL') } catch {}
|
|
100
|
+
// Poll for death up to 2s
|
|
101
|
+
const deadline = Date.now() + 2000
|
|
102
|
+
while (Date.now() < deadline) {
|
|
103
|
+
try {
|
|
104
|
+
process.kill(pid, 0)
|
|
105
|
+
} catch (e) {
|
|
106
|
+
if (e.code === 'ESRCH') {
|
|
107
|
+
log(`[memory-service] Killed previous server PID ${pid}\n`)
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Synchronous 50ms sleep via shared buffer spin
|
|
112
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50)
|
|
113
|
+
}
|
|
114
|
+
log(`[memory-service] PID ${pid} still alive after SIGKILL\n`)
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function acquireLock(lockFile, log = () => {}) {
|
|
120
|
+
// Multi-instance guard. In multi-terminal mode the lock owner is a *peer*
|
|
121
|
+
// memory worker serving recall for another CC session. killPreviousServer
|
|
122
|
+
// would taskkill /F that healthy peer mid-flight, then this fork-proxy
|
|
123
|
+
// mode wouldn't even need a lock anyway. Skip the entire kill-the-previous
|
|
124
|
+
// protocol; fork-proxy detection in init() takes priority. If neither
|
|
125
|
+
// proxy nor lock-owner path applies (race window during simultaneous
|
|
126
|
+
// boot), the worker simply continues without the lock — server-main /
|
|
127
|
+
// PG / port-listen handle the actual conflict cases.
|
|
128
|
+
if (process.env.MIXDOG_MULTI_INSTANCE === '1') return
|
|
129
|
+
try {
|
|
130
|
+
if (fs.existsSync(lockFile)) {
|
|
131
|
+
const lockedPid = Number(fs.readFileSync(lockFile, 'utf8').trim())
|
|
132
|
+
if (lockedPid > 0 && lockedPid !== process.pid) {
|
|
133
|
+
const killed = killPreviousServer(lockedPid, log)
|
|
134
|
+
if (!killed) {
|
|
135
|
+
log(`[memory-service] Could not kill previous server PID ${lockedPid}, aborting\n`)
|
|
136
|
+
process.exit(1)
|
|
137
|
+
}
|
|
138
|
+
try { fs.unlinkSync(lockFile) } catch {}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const fd = fs.openSync(lockFile, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600)
|
|
142
|
+
try {
|
|
143
|
+
fs.writeSync(fd, String(process.pid))
|
|
144
|
+
} finally {
|
|
145
|
+
fs.closeSync(fd)
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (e.code === 'EEXIST') {
|
|
149
|
+
log(`[memory-service] Lock file exists (EEXIST) — another instance is already running, exiting\n`)
|
|
150
|
+
process.exit(0)
|
|
151
|
+
}
|
|
152
|
+
log(`[memory-service] Lock acquisition failed: ${e.message}\n`)
|
|
153
|
+
process.exit(1)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function releaseLock(lockFile) {
|
|
158
|
+
try {
|
|
159
|
+
const content = fs.readFileSync(lockFile, 'utf8').trim()
|
|
160
|
+
if (Number(content) === process.pid) fs.unlinkSync(lockFile)
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
@@ -18,11 +18,35 @@ export function buildCategoryFilterClause(offset, categories, { tableAlias = ''
|
|
|
18
18
|
return { clause: `AND (${inner})`, params: [...cats] }
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Shared, param-less predicate excluding promoted/promoting core-candidate
|
|
22
|
+
// roots AND member rows under such a root. Single source of truth so the hybrid
|
|
23
|
+
// recall path (buildRecallScopeFilter) and the query-less browse path
|
|
24
|
+
// (retrieveEntries) can't diverge. tableAlias='' → bare `entries` column refs.
|
|
25
|
+
export function buildPromotedExclusionClauses(tableAlias = '') {
|
|
26
|
+
const p = `${tableAlias || 'entries'}.`
|
|
27
|
+
return [
|
|
28
|
+
// Promoted core-candidate roots have been absorbed into user-curated
|
|
29
|
+
// core_entries and archived by promoteCoreCandidate. Their content now
|
|
30
|
+
// lives in the {{USER_CORE}} slot, so surfacing the stale generated root
|
|
31
|
+
// would double-serve the same fact. 'promoting' (mid-flight or crashed
|
|
32
|
+
// promote awaiting recovery) is excluded too — its root is already archived
|
|
33
|
+
// and finalizes to 'promoted'. Member rows whose chunk_root points at a
|
|
34
|
+
// promoted/promoting root are excluded via EXISTS-on-root (the flag lives
|
|
35
|
+
// only on the root; members keep NULL). Constant predicates — no bind param.
|
|
36
|
+
`(${p}core_candidate_status IS NULL OR ${p}core_candidate_status NOT IN ('promoted', 'promoting'))`,
|
|
37
|
+
`NOT (${p}is_root = 0 AND ${p}chunk_root IS NOT NULL AND ${p}chunk_root <> ${p}id AND EXISTS (
|
|
38
|
+
SELECT 1 FROM entries r WHERE r.id = ${p}chunk_root AND r.is_root = 1 AND r.core_candidate_status IN ('promoted', 'promoting')
|
|
39
|
+
))`,
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
export function buildRecallScopeFilter(offset, options = {}, tableAlias = '') {
|
|
22
44
|
const outerRef = tableAlias || 'entries'
|
|
23
45
|
const p = `${outerRef}.`
|
|
24
46
|
const clauses = [
|
|
25
47
|
`NOT (${p}is_root = 0 AND ${p}chunk_root IS NOT DISTINCT FROM ${p}id AND ${p}status IS NOT DISTINCT FROM 'archived')`,
|
|
48
|
+
// Exclude promoted/promoting roots + their members (shared predicate).
|
|
49
|
+
...buildPromotedExclusionClauses(tableAlias),
|
|
26
50
|
]
|
|
27
51
|
const params = []
|
|
28
52
|
let next = offset
|
|
@@ -15,6 +15,18 @@ import { recallReadQuery } from './memory-recall-read-query.mjs'
|
|
|
15
15
|
const _MV_HOT_ACTIVE_TTL_MS = 60_000
|
|
16
16
|
const _mvHotActiveCache = new WeakMap() // db → { populated: boolean, ts: number }
|
|
17
17
|
const SEMANTIC_ONLY_MIN_SIM = 0.72
|
|
18
|
+
// Cross-language semantic rescue: a query in one language against rows in
|
|
19
|
+
// another rarely clears SEMANTIC_ONLY_MIN_SIM on a small (384-dim) embedding
|
|
20
|
+
// model, yet the TOP dense ranks are still overwhelmingly relevant. Accept
|
|
21
|
+
// semantic-only rows that are both top-ranked in the dense leg AND above a
|
|
22
|
+
// softer floor, instead of dropping everything below 0.72.
|
|
23
|
+
const SEMANTIC_TOP_RANK_MAX = 3
|
|
24
|
+
// Floor for the top-rank rescue. 384-dim MiniLM-class embeddings put UNRELATED
|
|
25
|
+
// text at ~0.65-0.75 sim (measured: gibberish query "zzqqxx ... 999" scored
|
|
26
|
+
// 0.749 against real rows), so anything below ~0.70 is noise regardless of
|
|
27
|
+
// rank; cross-language relevant pairs measured 0.67-0.74. 0.70 keeps most of
|
|
28
|
+
// the cross-language rescue while rejecting pure-noise top ranks.
|
|
29
|
+
const SEMANTIC_TOP_RANK_MIN_SIM = 0.70
|
|
18
30
|
const SHORT_QUERY_TOKEN_MAX = 2
|
|
19
31
|
|
|
20
32
|
function buildExactTerms(query) {
|
|
@@ -62,7 +74,12 @@ function hasFullQueryTextMatch(query, row) {
|
|
|
62
74
|
function hasQueryTokenCoverage(row, queryTokenCount) {
|
|
63
75
|
if (queryTokenCount <= SHORT_QUERY_TOKEN_MAX) return true
|
|
64
76
|
const hits = Number(row?.exact_hits)
|
|
65
|
-
|
|
77
|
+
// Majority coverage, not full coverage. Requiring EVERY query token to hit
|
|
78
|
+
// made mixed-language queries ("usage spike 원인") return nothing when one
|
|
79
|
+
// token has no lexical counterpart in the row — ~60% of tokens matching is
|
|
80
|
+
// strong enough lexical evidence alongside the semantic leg.
|
|
81
|
+
const required = Math.max(2, Math.ceil(queryTokenCount * 0.6))
|
|
82
|
+
return Number.isFinite(hits) && hits >= required
|
|
66
83
|
}
|
|
67
84
|
|
|
68
85
|
function exactTextBoost(query, row, exactHits) {
|
|
@@ -479,7 +496,10 @@ LEFT JOIN exact x ON x.id = c.id`
|
|
|
479
496
|
const scored = scoredAll.filter(({ row }) => {
|
|
480
497
|
const hasTextSupport = row.sparse_rank != null || row.trgm_rank != null || row.exact_rank != null
|
|
481
498
|
const sim = Number(row.dense_sim)
|
|
482
|
-
const
|
|
499
|
+
const denseRankNum = row.dense_rank != null ? Number(row.dense_rank) : null
|
|
500
|
+
const hasSemanticSupport = (Number.isFinite(sim) && sim >= SEMANTIC_ONLY_MIN_SIM)
|
|
501
|
+
|| (Number.isFinite(sim) && sim >= SEMANTIC_TOP_RANK_MIN_SIM
|
|
502
|
+
&& denseRankNum != null && denseRankNum <= SEMANTIC_TOP_RANK_MAX)
|
|
483
503
|
if (!hasTextSupport) {
|
|
484
504
|
if (hasSemanticSupport) return true
|
|
485
505
|
semanticOnlyDropped += 1
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { recallReadQuery } from './memory-recall-read-query.mjs'
|
|
2
2
|
|
|
3
|
+
import { buildPromotedExclusionClauses } from './memory-recall-scope-filter.mjs'
|
|
4
|
+
|
|
3
5
|
const VALID_CATEGORIES_SET = new Set([
|
|
4
6
|
'rule', 'constraint', 'decision', 'fact', 'goal', 'preference', 'task', 'issue',
|
|
5
7
|
])
|
|
@@ -72,6 +74,12 @@ export async function retrieveEntries(db, filters = {}) {
|
|
|
72
74
|
where.push(`chunk_root IS NULL`)
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
// Exclude promoted/promoting core-candidate roots AND members under such a
|
|
78
|
+
// root — the query-less browse path (includeArchived) would otherwise surface
|
|
79
|
+
// rows the hybrid recall path already filters. Shared predicate (param-less)
|
|
80
|
+
// keeps this in lock-step with buildRecallScopeFilter.
|
|
81
|
+
where.push(...buildPromotedExclusionClauses())
|
|
82
|
+
|
|
75
83
|
const limit = Math.max(1, Math.min(500, Number(filters.limit ?? 50)))
|
|
76
84
|
const offset = Math.max(0, Number(filters.offset ?? 0))
|
|
77
85
|
const sort = String(filters.sort ?? 'importance').trim().toLowerCase()
|
|
@@ -443,6 +443,26 @@ export async function ensureCurrentSchemaExtensions(db, dims) {
|
|
|
443
443
|
|
|
444
444
|
|
|
445
445
|
|
|
446
|
+
// Core-candidate promotion pipeline (proposal mode): active entries the
|
|
447
|
+
// cycle2 nomination pass flags as strong core-memory candidates. Never
|
|
448
|
+
// auto-inserted into core_entries — a user approves each via the
|
|
449
|
+
// action:'core' op:'promote' handler. Columns are nullable and the ALTERs
|
|
450
|
+
// are idempotent (ADD COLUMN IF NOT EXISTS), safe to re-run every boot.
|
|
451
|
+
// core_candidate_status: NULL (not a candidate) | 'candidate' | 'promoting'
|
|
452
|
+
// (mid-flight promote, recoverable) | 'promoted' | 'dismissed'
|
|
453
|
+
// core_candidate_at: ms timestamp of last nomination/state change
|
|
454
|
+
// 'dismissed'/'promoted' are terminal for a given root so the pass never
|
|
455
|
+
// re-nominates the same entry.
|
|
456
|
+
await db.exec(`ALTER TABLE entries ADD COLUMN IF NOT EXISTS core_candidate_status text`)
|
|
457
|
+
await db.exec(`ALTER TABLE entries ADD COLUMN IF NOT EXISTS core_candidate_at bigint`)
|
|
458
|
+
// No index on core_candidate_status by design (round-2 finding #4): the only
|
|
459
|
+
// readers are listCoreCandidates (user picker, on-demand) and
|
|
460
|
+
// nominateCoreCandidates (once per cycle2, hourly). Both are rare and the
|
|
461
|
+
// entries table is small enough that a seq scan is fine — an index isn't
|
|
462
|
+
// worth the boot-time AccessExclusive build lock on the hot entries table.
|
|
463
|
+
// Drop it if a previous deploy created it (idempotent no-op otherwise).
|
|
464
|
+
await db.exec(`DROP INDEX IF EXISTS idx_entries_core_candidate`)
|
|
465
|
+
|
|
446
466
|
// Dedupe core_entries before creating the unique index — keeps the row with
|
|
447
467
|
// the most recent updated_at (id breaks ties), drops the rest.
|
|
448
468
|
const dedupe = await db.query(`
|
|
@@ -194,7 +194,7 @@ function patchActiveInstance(fields) {
|
|
|
194
194
|
else merged[k] = v;
|
|
195
195
|
}
|
|
196
196
|
return merged;
|
|
197
|
-
}, { compact: true, fsyncDir: true });
|
|
197
|
+
}, { compact: true, fsyncDir: true, renameFallback: 'truncate' });
|
|
198
198
|
} catch (e) {
|
|
199
199
|
__mixdogMemoryLog(`[supervisor-pg] patchActiveInstance failed: ${e?.message}\n`);
|
|
200
200
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
// Plugin version + promotion code fingerprint. Extracted from index.mjs
|
|
6
|
+
// (behavior-preserving). Callers pass PLUGIN_ROOT so this stays free of any
|
|
7
|
+
// entry-point path resolution.
|
|
8
|
+
|
|
9
|
+
export function readPluginVersion(pluginRoot) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(fs.readFileSync(path.join(pluginRoot, 'package.json'), 'utf8')).version || '0.0.1'
|
|
12
|
+
} catch { return '0.0.1' }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PROMOTION_FINGERPRINT_ROOTS = ['src/memory']
|
|
16
|
+
|
|
17
|
+
function collectPromotionFingerprintFiles(pluginRoot) {
|
|
18
|
+
const out = []
|
|
19
|
+
const walk = (relDir) => {
|
|
20
|
+
let entries = []
|
|
21
|
+
try { entries = fs.readdirSync(path.join(pluginRoot, relDir), { withFileTypes: true }) }
|
|
22
|
+
catch { return }
|
|
23
|
+
for (const ent of entries) {
|
|
24
|
+
const rel = `${relDir}/${ent.name}`.replace(/\\/g, '/')
|
|
25
|
+
if (ent.isDirectory()) {
|
|
26
|
+
walk(rel)
|
|
27
|
+
} else if (ent.isFile() && rel.endsWith('.mjs')) {
|
|
28
|
+
out.push(rel)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const root of PROMOTION_FINGERPRINT_ROOTS) walk(root)
|
|
33
|
+
return out.sort()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readPromotionCodeFingerprint(pluginRoot) {
|
|
37
|
+
const hash = crypto.createHash('sha256')
|
|
38
|
+
const files = collectPromotionFingerprintFiles(pluginRoot)
|
|
39
|
+
for (const rel of files) {
|
|
40
|
+
hash.update(rel)
|
|
41
|
+
hash.update('\0')
|
|
42
|
+
try {
|
|
43
|
+
hash.update(fs.readFileSync(path.join(pluginRoot, rel)))
|
|
44
|
+
} catch {
|
|
45
|
+
hash.update('missing')
|
|
46
|
+
}
|
|
47
|
+
hash.update('\0')
|
|
48
|
+
}
|
|
49
|
+
return `src/memory:${files.length}:${hash.digest('hex').slice(0, 16)}`
|
|
50
|
+
}
|