mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -57,15 +57,11 @@ const execFileAsync = promisify(execFile)
|
|
|
57
57
|
const _envLoadCores = Number(process.env.MIXDOG_EMBED_LOAD_CORES)
|
|
58
58
|
const LOAD_AFFINITY_CORES = Number.isInteger(_envLoadCores) && _envLoadCores >= 1 ? _envLoadCores : 1
|
|
59
59
|
const MODEL_CACHE_DIR = join(resolvePluginData(), 'memory-models')
|
|
60
|
-
// Reclaim the model after a short idle window. The
|
|
61
|
-
// so the next recall reloads on demand without keeping hundreds of MB
|
|
62
|
-
// throughout an otherwise idle desktop session.
|
|
60
|
+
// Reclaim the model after a short idle window. The parent retires this worker
|
|
61
|
+
// thread so the next recall reloads on demand without keeping hundreds of MB
|
|
62
|
+
// resident throughout an otherwise idle desktop session.
|
|
63
63
|
const _envIdleMs = Number(process.env.MIXDOG_EMBED_IDLE_TIMEOUT_MS)
|
|
64
|
-
const IDLE_TIMEOUT_MS = Number.isFinite(_envIdleMs) && _envIdleMs >= 0 ? _envIdleMs :
|
|
65
|
-
const _envPressureMb = Number(process.env.MIXDOG_EMBED_PRESSURE_MIN_FREE_MB)
|
|
66
|
-
const PRESSURE_MIN_FREE_BYTES = (
|
|
67
|
-
Number.isFinite(_envPressureMb) && _envPressureMb >= 0 ? _envPressureMb : 1536
|
|
68
|
-
) * 1024 * 1024
|
|
64
|
+
const IDLE_TIMEOUT_MS = Number.isFinite(_envIdleMs) && _envIdleMs >= 0 ? _envIdleMs : 60_000
|
|
69
65
|
|
|
70
66
|
// Defensive belt against giant model inputs. Callers should already bound text
|
|
71
67
|
// (see memory-embed truncateForEmbed), but the worker is the last line before
|
|
@@ -87,6 +83,7 @@ let configuredDtype = DEFAULT_DTYPE
|
|
|
87
83
|
let _device = 'cpu'
|
|
88
84
|
let _idleTimer = null
|
|
89
85
|
let _embedInFlight = false
|
|
86
|
+
let _reclaiming = false
|
|
90
87
|
const _msgQueue = []
|
|
91
88
|
let ortPatched = false
|
|
92
89
|
// Control ops must never be overtaken by a priority embed: crossing a queued
|
|
@@ -155,44 +152,41 @@ function enqueue(msg) {
|
|
|
155
152
|
// reset / ext.dispose() while the prior extractor is still being torn down.
|
|
156
153
|
const GUARDED_ACTIONS = new Set(['embed', 'embed-batch', 'warmup', 'configure', 'dispose'])
|
|
157
154
|
|
|
158
|
-
function disposeLoadedExtractor(reason) {
|
|
159
|
-
if (!extractorPromise) return false
|
|
155
|
+
async function disposeLoadedExtractor(reason) {
|
|
156
|
+
if (!extractorPromise || _reclaiming) return false
|
|
157
|
+
_reclaiming = true
|
|
160
158
|
const loaded = extractorPromise
|
|
161
159
|
extractorPromise = null
|
|
162
160
|
const prevDevice = _device
|
|
163
161
|
_device = 'cpu'
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
162
|
+
try {
|
|
163
|
+
try {
|
|
164
|
+
const ext = await loaded
|
|
165
|
+
try { await Promise.resolve(ext.dispose()) } catch {}
|
|
166
|
+
} catch {}
|
|
167
|
+
__mixdogMemoryLog(`[embed-worker] ${reason} — model disposed\n`)
|
|
168
|
+
parentPort.postMessage({
|
|
169
|
+
type: 'idle-dispose',
|
|
170
|
+
reason,
|
|
171
|
+
device: prevDevice,
|
|
172
|
+
dtype: configuredDtype,
|
|
173
|
+
})
|
|
174
|
+
return true
|
|
175
|
+
} finally {
|
|
176
|
+
_reclaiming = false
|
|
177
|
+
}
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
function resetIdleTimer() {
|
|
176
181
|
if (_idleTimer) clearTimeout(_idleTimer)
|
|
177
182
|
if (IDLE_TIMEOUT_MS <= 0) return
|
|
178
183
|
_idleTimer = setTimeout(() => {
|
|
179
|
-
if (!_embedInFlight) disposeLoadedExtractor('idle timeout')
|
|
184
|
+
if (!_embedInFlight) void disposeLoadedExtractor('idle timeout')
|
|
180
185
|
_idleTimer = null
|
|
181
186
|
}, IDLE_TIMEOUT_MS)
|
|
182
187
|
_idleTimer.unref?.()
|
|
183
188
|
}
|
|
184
189
|
|
|
185
|
-
const _pressureTimer = setInterval(() => {
|
|
186
|
-
if (PRESSURE_MIN_FREE_BYTES <= 0 || _embedInFlight || !extractorPromise) return
|
|
187
|
-
let freeBytes = Number.POSITIVE_INFINITY
|
|
188
|
-
try { freeBytes = Number(os.freemem()) } catch {}
|
|
189
|
-
if (Number.isFinite(freeBytes) && freeBytes < PRESSURE_MIN_FREE_BYTES) {
|
|
190
|
-
if (_idleTimer) { clearTimeout(_idleTimer); _idleTimer = null }
|
|
191
|
-
disposeLoadedExtractor('host memory pressure')
|
|
192
|
-
}
|
|
193
|
-
}, 5_000)
|
|
194
|
-
_pressureTimer.unref?.()
|
|
195
|
-
|
|
196
190
|
// Set this process's CPU affinity to `mask` (bitmask of allowed logical
|
|
197
191
|
// processors). Windows-only and best-effort: there is no Node API, so go
|
|
198
192
|
// through PowerShell; a process may set its own affinity without elevation.
|
|
@@ -243,9 +237,6 @@ function patchOrtThreads() {
|
|
|
243
237
|
async function loadExtractor() {
|
|
244
238
|
if (!extractorPromise) {
|
|
245
239
|
extractorPromise = (async () => {
|
|
246
|
-
if (PRESSURE_MIN_FREE_BYTES > 0 && os.freemem() < PRESSURE_MIN_FREE_BYTES) {
|
|
247
|
-
throw new Error('embedding model load deferred under host memory pressure')
|
|
248
|
-
}
|
|
249
240
|
parentPort.postMessage({ type: 'profile', record: { phase: 'baseline', model: MODEL_ID, device: _device, dtype: configuredDtype, note: 'pre-load' } })
|
|
250
241
|
patchOrtThreads()
|
|
251
242
|
const { pipeline, env } = await import('@huggingface/transformers')
|
|
@@ -441,6 +432,11 @@ async function drainQueue() {
|
|
|
441
432
|
await processMessage(next)
|
|
442
433
|
}
|
|
443
434
|
_embedInFlight = false
|
|
435
|
+
// Idle means time since the LAST completed inference, not time since it
|
|
436
|
+
// started. A cold model load can outlive a short idle window; without this
|
|
437
|
+
// re-arm that timer fires while busy, is discarded, and the loaded ORT
|
|
438
|
+
// session then remains resident indefinitely.
|
|
439
|
+
if (extractorPromise && !_reclaiming) resetIdleTimer()
|
|
444
440
|
}
|
|
445
441
|
|
|
446
442
|
parentPort.on('message', async (msg) => {
|
|
@@ -121,10 +121,7 @@ export function createHttpRouter({
|
|
|
121
121
|
const _ownerInFlightHttpCalls = new Map()
|
|
122
122
|
|
|
123
123
|
const requestHandler = async (req, res) => {
|
|
124
|
-
touchDaemonIdleTimer(`${req.method || 'HTTP'} ${req.url || '/'}`)
|
|
125
|
-
// Connected-client lifecycle. Proxies register on first use and
|
|
126
|
-
// deregister on CLI shutdown; the daemon reaps itself shortly after the
|
|
127
|
-
// last client leaves (see registerClient/deregisterClient in index.mjs).
|
|
124
|
+
touchDaemonIdleTimer?.(`${req.method || 'HTTP'} ${req.url || '/'}`)
|
|
128
125
|
if (req.method === 'POST' && (req.url === '/client/register' || req.url === '/client/deregister')) {
|
|
129
126
|
if (!isLocalOrigin(req)) {
|
|
130
127
|
sendJson(res, { ok: false, error: 'forbidden: cross-origin' }, 403)
|
|
@@ -136,8 +133,6 @@ export function createHttpRouter({
|
|
|
136
133
|
if (req.url === '/client/register') {
|
|
137
134
|
const accepted = registerClient?.(clientPid)
|
|
138
135
|
if (accepted === false) {
|
|
139
|
-
// Daemon is draining — distinct signal so the proxy respawns a fresh
|
|
140
|
-
// daemon and retries its pending RPC instead of binding to this one.
|
|
141
136
|
sendJson(res, { ok: false, draining: true, error: 'memory worker draining' }, 503)
|
|
142
137
|
return
|
|
143
138
|
}
|
|
@@ -171,6 +166,7 @@ export function createHttpRouter({
|
|
|
171
166
|
try {
|
|
172
167
|
const db = getDb()
|
|
173
168
|
const stats = await entryStats()
|
|
169
|
+
const memory = process.memoryUsage()
|
|
174
170
|
sendJson(res, {
|
|
175
171
|
status: 'ok',
|
|
176
172
|
worker_pid: process.pid,
|
|
@@ -192,6 +188,13 @@ export function createHttpRouter({
|
|
|
192
188
|
cycle_running: cycleScheduler.getCycleRunning(),
|
|
193
189
|
cycle_health: cycleScheduler.getCycleHealth(),
|
|
194
190
|
cycle_backlog: cycleScheduler.getCycleBacklogSnapshot(),
|
|
191
|
+
memory: {
|
|
192
|
+
rssBytes: memory.rss,
|
|
193
|
+
heapTotalBytes: memory.heapTotal,
|
|
194
|
+
heapUsedBytes: memory.heapUsed,
|
|
195
|
+
externalBytes: memory.external,
|
|
196
|
+
arrayBufferBytes: memory.arrayBuffers,
|
|
197
|
+
},
|
|
195
198
|
})
|
|
196
199
|
} catch (e) { sendError(res, e.message) }
|
|
197
200
|
return
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
// analyze(text) — sync; returns TokenInfo[] or null when not ready.
|
|
16
16
|
// stems(text) — sync; content-morpheme stems (NNG/NNP/VV/VA/XR/SL) or null.
|
|
17
17
|
import fs from 'node:fs'
|
|
18
|
-
import os from 'node:os'
|
|
19
18
|
import path from 'node:path'
|
|
20
19
|
import zlib from 'node:zlib'
|
|
21
20
|
|
|
@@ -54,11 +53,7 @@ let _idleTimer = null
|
|
|
54
53
|
const _envIdleMs = Number(process.env.MIXDOG_KO_MORPH_IDLE_TIMEOUT_MS)
|
|
55
54
|
const IDLE_TIMEOUT_MS = Number.isFinite(_envIdleMs) && _envIdleMs >= 0
|
|
56
55
|
? _envIdleMs
|
|
57
|
-
:
|
|
58
|
-
const _envPressureMb = Number(process.env.MIXDOG_KO_MORPH_PRESSURE_MIN_FREE_MB)
|
|
59
|
-
const PRESSURE_MIN_FREE_BYTES = (
|
|
60
|
-
Number.isFinite(_envPressureMb) && _envPressureMb >= 0 ? _envPressureMb : 1536
|
|
61
|
-
) * 1024 * 1024
|
|
56
|
+
: 60_000
|
|
62
57
|
|
|
63
58
|
export function isReady() { return _state === 'ready' && _kiwi != null }
|
|
64
59
|
export function state() { return _state }
|
|
@@ -78,14 +73,8 @@ function releaseLoaded(reason = '') {
|
|
|
78
73
|
|
|
79
74
|
function touchIdleTimer() {
|
|
80
75
|
if (_idleTimer) clearTimeout(_idleTimer)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (PRESSURE_MIN_FREE_BYTES > 0 && os.freemem() < PRESSURE_MIN_FREE_BYTES) delay = 1
|
|
84
|
-
} catch {}
|
|
85
|
-
if (delay <= 0) return
|
|
86
|
-
_idleTimer = setTimeout(() => releaseLoaded(
|
|
87
|
-
delay <= 1 ? 'host memory pressure' : 'idle timeout',
|
|
88
|
-
), delay)
|
|
76
|
+
if (IDLE_TIMEOUT_MS <= 0) return
|
|
77
|
+
_idleTimer = setTimeout(() => releaseLoaded('idle timeout'), IDLE_TIMEOUT_MS)
|
|
89
78
|
_idleTimer.unref?.()
|
|
90
79
|
}
|
|
91
80
|
|
|
@@ -166,12 +155,6 @@ export async function init(dataDir, log = () => {}) {
|
|
|
166
155
|
return true
|
|
167
156
|
}
|
|
168
157
|
if (_initPromise) return _initPromise
|
|
169
|
-
try {
|
|
170
|
-
if (PRESSURE_MIN_FREE_BYTES > 0 && os.freemem() < PRESSURE_MIN_FREE_BYTES) {
|
|
171
|
-
_log('[memory-service] kiwi morph load deferred under host memory pressure\n')
|
|
172
|
-
return false
|
|
173
|
-
}
|
|
174
|
-
} catch {}
|
|
175
158
|
_state = 'loading'
|
|
176
159
|
const t0 = Date.now()
|
|
177
160
|
_log(`[memory-service] kiwi morph init start (model ${KIWI_MODEL_VERSION})\n`)
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
promoteCoreCandidate,
|
|
34
34
|
dismissCoreCandidate,
|
|
35
35
|
normalizeCoreInput,
|
|
36
|
+
normalizeCoreOp,
|
|
36
37
|
} from './core-memory-store.mjs'
|
|
37
38
|
import { resolveProjectId, resolveProjectScope } from './project-id-resolver.mjs'
|
|
38
39
|
import { resolvePluginData } from '../../shared/plugin-paths.mjs'
|
|
@@ -602,7 +603,7 @@ export function createMemoryActionHandlers({
|
|
|
602
603
|
}
|
|
603
604
|
|
|
604
605
|
if (action === 'core') {
|
|
605
|
-
const op =
|
|
606
|
+
const op = normalizeCoreOp(args.op)
|
|
606
607
|
if (!['add', 'edit', 'delete', 'list', 'candidates', 'promote', 'dismiss'].includes(op)) {
|
|
607
608
|
return { text: 'core requires op: "add" | "edit" | "delete" | "list" | "candidates" | "promote" | "dismiss"', isError: true }
|
|
608
609
|
}
|
|
@@ -14,7 +14,7 @@ export function readMainConfig() {
|
|
|
14
14
|
|
|
15
15
|
// Recap toggle lives in the `agent` config section (agent.recap.enabled,
|
|
16
16
|
// default true) — the same file the session runtime persists via
|
|
17
|
-
// saveConfigAndAdopt. The memory
|
|
17
|
+
// saveConfigAndAdopt. The memory runtime is hosted by the unified daemon
|
|
18
18
|
// worker, so it re-reads this section from disk each cycle tick (poll-on-use)
|
|
19
19
|
// rather than relying on IPC.
|
|
20
20
|
|
|
@@ -634,6 +634,7 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
634
634
|
timeout,
|
|
635
635
|
// Pin cwd to null so every memory cycle call hits the same agent cache shard.
|
|
636
636
|
cwd: null,
|
|
637
|
+
signal,
|
|
637
638
|
}, userMessage)
|
|
638
639
|
throwIfAborted(signal)
|
|
639
640
|
__mixdogMemoryLog(`[cycle1-time] window=${windowIdx} llmMs=${Date.now() - _tLlm}\n`)
|
|
@@ -32,7 +32,7 @@ const isShiftFollowToken = (tok) => {
|
|
|
32
32
|
return ALL_PRIMARY_VERBS.has(v) || v === 'why' || v === 'core'
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch) {
|
|
35
|
+
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch, signal = null) {
|
|
36
36
|
return await llmCall({
|
|
37
37
|
agent: 'cycle2-agent',
|
|
38
38
|
taskType: 'maintenance',
|
|
@@ -40,6 +40,7 @@ async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispa
|
|
|
40
40
|
preset,
|
|
41
41
|
timeout,
|
|
42
42
|
cwd: null,
|
|
43
|
+
signal,
|
|
43
44
|
}, prompt)
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -357,7 +358,7 @@ export async function runUnifiedGate(db, rows, activeContext, config = {}, optio
|
|
|
357
358
|
const callOnce = async (extraTag) => {
|
|
358
359
|
throwIfAborted(signal)
|
|
359
360
|
const p = extraTag ? `${prompt}\n\n[retry:${extraTag}]` : prompt
|
|
360
|
-
const raw = await invokeLlm(p, mode, preset, timeout, options.callLlm)
|
|
361
|
+
const raw = await invokeLlm(p, mode, preset, timeout, options.callLlm, signal)
|
|
361
362
|
throwIfAborted(signal)
|
|
362
363
|
return raw
|
|
363
364
|
}
|
|
@@ -518,6 +519,7 @@ export async function sonnetCascade(candidates, rulesDigest, options = {}) {
|
|
|
518
519
|
preset,
|
|
519
520
|
timeout: 600000,
|
|
520
521
|
cwd: null,
|
|
522
|
+
signal,
|
|
521
523
|
}, prompt)
|
|
522
524
|
} catch (err) {
|
|
523
525
|
if (signal?.aborted) throw signal.reason ?? err
|
|
@@ -322,6 +322,7 @@ async function _llmJudgePair(summaryA, summaryB, siblingContext = [], options =
|
|
|
322
322
|
preset: options.preset || resolveMaintenancePreset('memory'),
|
|
323
323
|
timeout: 30000,
|
|
324
324
|
cwd: null,
|
|
325
|
+
signal,
|
|
325
326
|
}, prompt)
|
|
326
327
|
throwIfAborted(signal)
|
|
327
328
|
return String(raw ?? '').trim().toLowerCase().startsWith('merge')
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// memory-cycle2.mjs). Logging shim, abort check, and resource-dir resolution.
|
|
3
3
|
// No cycle2 business logic; safe to import from any cycle2 sub-module.
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
|
+
import { join } from 'path'
|
|
5
6
|
|
|
6
7
|
import { __mixdogMemoryLog } from './memory-log.mjs'
|
|
7
8
|
export { __mixdogMemoryLog }
|
|
@@ -11,5 +12,7 @@ export function throwIfAborted(signal) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function resourceDir() {
|
|
14
|
-
return process.env.MIXDOG_ROOT
|
|
15
|
+
return process.env.MIXDOG_ROOT
|
|
16
|
+
? join(process.env.MIXDOG_ROOT, 'src')
|
|
17
|
+
: fileURLToPath(new URL('../../..', import.meta.url))
|
|
15
18
|
}
|
|
@@ -36,7 +36,7 @@ function resourceDir() {
|
|
|
36
36
|
return process.env.MIXDOG_ROOT || fileURLToPath(new URL('../../../..', import.meta.url))
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch) {
|
|
39
|
+
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch, signal = null) {
|
|
40
40
|
return await llmCall({
|
|
41
41
|
agent: 'cycle3-agent',
|
|
42
42
|
taskType: 'maintenance',
|
|
@@ -44,6 +44,7 @@ async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispa
|
|
|
44
44
|
preset,
|
|
45
45
|
timeout,
|
|
46
46
|
cwd: null,
|
|
47
|
+
signal,
|
|
47
48
|
}, prompt)
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -55,6 +56,7 @@ export async function invokeCycle3Maintenance(prompt, options = {}) {
|
|
|
55
56
|
preset,
|
|
56
57
|
Number(options.timeout ?? 600000),
|
|
57
58
|
options.callLlm,
|
|
59
|
+
options.signal,
|
|
58
60
|
)
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -24,6 +24,11 @@ export function createMemoryDaemonLifecycle({
|
|
|
24
24
|
_idleShutdownTimer = null
|
|
25
25
|
}
|
|
26
26
|
_idleShutdownTimer = setTimeout(() => {
|
|
27
|
+
pruneDeadClients()
|
|
28
|
+
if (_connectedClients.size > 0) {
|
|
29
|
+
touchDaemonIdleTimer('live client retained')
|
|
30
|
+
return
|
|
31
|
+
}
|
|
27
32
|
log(`[memory-service] daemon idle TTL elapsed after ${reason}; shutting down\n`)
|
|
28
33
|
stop()
|
|
29
34
|
.then(() => process.exit(0))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Shared memory-
|
|
1
|
+
// Shared memory-runtime stderr logger. The write is captured at import time so
|
|
2
2
|
// memory output stays visible even when a TUI log guard swaps
|
|
3
3
|
// process.stderr.write later; MIXDOG_QUIET_MEMORY_LOG mutes it entirely.
|
|
4
4
|
const __mixdogMemoryStderrWrite = process.stderr.write.bind(process.stderr);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// ownership of DATA_DIR-derived constants and the daemon logger.
|
|
1
|
+
// Process lock for the standalone memory MCP entry. Product mode is hosted
|
|
2
|
+
// A direct entry (including the isolated daemon child) owns this lock.
|
|
4
3
|
import fs from 'node:fs'
|
|
5
4
|
import { execFileSync } from 'node:child_process'
|
|
6
5
|
|
|
@@ -15,55 +14,7 @@ export function isPidAliveLocal(pid) {
|
|
|
15
14
|
catch (e) { return e.code !== 'ESRCH' }
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
|
|
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 = () => {}) {
|
|
17
|
+
function killPreviousServer(pid, log = () => {}) {
|
|
67
18
|
if (pid <= 0 || pid === process.pid) return false
|
|
68
19
|
if (process.platform === 'win32') {
|
|
69
20
|
try {
|
|
@@ -117,14 +68,8 @@ export function killPreviousServer(pid, log = () => {}) {
|
|
|
117
68
|
}
|
|
118
69
|
|
|
119
70
|
export function acquireLock(lockFile, log = () => {}) {
|
|
120
|
-
// Multi-instance
|
|
121
|
-
//
|
|
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.
|
|
71
|
+
// Multi-instance mode is coordinated by the unified daemon, so a direct MCP
|
|
72
|
+
// entry does not evict an already-running peer from another client.
|
|
128
73
|
if (process.env.MIXDOG_MULTI_INSTANCE === '1') return
|
|
129
74
|
try {
|
|
130
75
|
if (fs.existsSync(lockFile)) {
|
|
@@ -417,7 +417,7 @@ async function _migrateRecallIndexesIfStale(db) {
|
|
|
417
417
|
|
|
418
418
|
export async function ensureCurrentSchemaExtensions(db, dims) {
|
|
419
419
|
// One-time cleanup: attachment-only placeholder rows ('(attachment)' user
|
|
420
|
-
// content, e.g. Discord
|
|
420
|
+
// content, e.g. Discord provider discord.mjs:724) predate the
|
|
421
421
|
// shouldExcludeIngestMessage() ingest-time filter (session-ingest.mjs).
|
|
422
422
|
// Delete any already-persisted rows so they stop polluting recall/cycle1.
|
|
423
423
|
// Idempotent (no-op once cleaned); best-effort so a failure never blocks boot.
|
|
@@ -107,6 +107,7 @@ async function findFreePort(preferred) {
|
|
|
107
107
|
// ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
109
|
const MIXDOG_CONF_V2_MARKER = '# mixdog overrides v2 — bgwriter / checkpoint distribution'
|
|
110
|
+
const MIXDOG_CONF_V3_MARKER = '# mixdog overrides v3 — bounded local-app resources'
|
|
110
111
|
|
|
111
112
|
// Lines emitted into postgresql.conf for v2. effective_io_concurrency requires
|
|
112
113
|
// posix_fadvise; PostgreSQL rejects non-zero values on Windows and macOS.
|
|
@@ -124,6 +125,21 @@ function buildV2Block() {
|
|
|
124
125
|
return lines.join('\n') + '\n'
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
function buildV3Block() {
|
|
129
|
+
return [
|
|
130
|
+
'',
|
|
131
|
+
MIXDOG_CONF_V3_MARKER,
|
|
132
|
+
// The only long-lived pools are memory(max 5) + trace(max 5). Keep ample
|
|
133
|
+
// recovery/bootstrap headroom without reserving backend slots for 100
|
|
134
|
+
// connections this single-user local store can never use.
|
|
135
|
+
'max_connections = 32',
|
|
136
|
+
// Mixdog queries are short indexed lookups / bounded batches. LLVM compile
|
|
137
|
+
// setup adds latency and transient memory; parallel query workers remain
|
|
138
|
+
// untouched for genuinely large scans.
|
|
139
|
+
'jit = off',
|
|
140
|
+
].join('\n') + '\n'
|
|
141
|
+
}
|
|
142
|
+
|
|
127
143
|
// Migration: an earlier v2 emitted effective_io_concurrency on every POSIX
|
|
128
144
|
// platform. Strip it anywhere except Linux so old macOS clusters can start.
|
|
129
145
|
function stripPlatformInvalidLines(conf) {
|
|
@@ -136,18 +152,25 @@ function ensureConfV2(pgdataDir) {
|
|
|
136
152
|
if (!existsSync(confPath)) return false
|
|
137
153
|
const original = readFileSync(confPath, 'utf8')
|
|
138
154
|
let conf = stripPlatformInvalidLines(original)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
let changed = conf !== original
|
|
156
|
+
if (!conf.includes(MIXDOG_CONF_V2_MARKER)) {
|
|
157
|
+
conf += buildV2Block()
|
|
158
|
+
changed = true
|
|
159
|
+
}
|
|
160
|
+
if (!conf.includes(MIXDOG_CONF_V3_MARKER)) {
|
|
161
|
+
conf += buildV3Block()
|
|
162
|
+
changed = true
|
|
163
|
+
}
|
|
164
|
+
if (!changed) return false
|
|
143
165
|
writeFileSync(confPath, conf)
|
|
144
166
|
return true
|
|
145
167
|
}
|
|
146
168
|
|
|
147
|
-
// Public reconcile: idempotent
|
|
148
|
-
// instance. Single source of truth
|
|
169
|
+
// Public reconcile: idempotent current override append + pg_ctl reload on a
|
|
170
|
+
// running instance. Single source of truth — supervisor and other
|
|
149
171
|
// callers route through this rather than re-implementing the conf/reload pair.
|
|
150
|
-
//
|
|
172
|
+
// Reload-compatible settings apply immediately; postmaster settings such as
|
|
173
|
+
// max_connections apply on the next normal PG start (never restart live work).
|
|
151
174
|
export function reconcileConfV2(runtimeDir, pgdataDir) {
|
|
152
175
|
const applied = ensureConfV2(pgdataDir)
|
|
153
176
|
if (!applied) return { applied: false, reloaded: false }
|
|
@@ -156,7 +179,7 @@ export function reconcileConfV2(runtimeDir, pgdataDir) {
|
|
|
156
179
|
})
|
|
157
180
|
const reloaded = reload.status === 0
|
|
158
181
|
if (reloaded) {
|
|
159
|
-
__mixdogMemoryLog('[pg-process] postgresql.conf
|
|
182
|
+
__mixdogMemoryLog('[pg-process] postgresql.conf overrides reconciled via pg_ctl reload\n')
|
|
160
183
|
} else {
|
|
161
184
|
__mixdogMemoryLog(`[pg-process] pg_ctl reload after v2 append failed (non-fatal): ${reload.stderr?.toString() || ''}\n`)
|
|
162
185
|
}
|
|
@@ -252,7 +275,7 @@ export async function startPg({
|
|
|
252
275
|
'wal_compression = on', // smaller WAL segments; fewer bytes for Defender to scan per segment
|
|
253
276
|
'wal_init_zero = off', // skips zero-fill on new WAL segment; cuts Defender contact at segment creation
|
|
254
277
|
'wal_recycle = off', // disables WAL rename/recycle loop; eliminates rename-storm EPERM pattern in pgdata
|
|
255
|
-
].join('\n') + '\n' + buildV2Block()
|
|
278
|
+
].join('\n') + '\n' + buildV2Block() + buildV3Block()
|
|
256
279
|
|
|
257
280
|
try {
|
|
258
281
|
const existing = readFileSync(confPath, 'utf8')
|
|
@@ -305,7 +305,7 @@ export function shouldExcludeIngestMessage(m) {
|
|
|
305
305
|
if (role === 'user' && typeof raw === 'string' && /^Reference files:\s*/i.test(raw.trimStart())) {
|
|
306
306
|
return true
|
|
307
307
|
}
|
|
308
|
-
// Attachment-only placeholder rows (e.g. Discord
|
|
308
|
+
// Attachment-only placeholder rows (e.g. Discord provider discord.mjs:724
|
|
309
309
|
// `"(attachment)"` fallback when a message carries no text, only files).
|
|
310
310
|
// Carries zero retrievable content — excluded so recall isn't polluted
|
|
311
311
|
// with bare "(attachment)" memory rows.
|
|
@@ -10,11 +10,7 @@ export const TOOL_DEFS = [
|
|
|
10
10
|
name: 'memory',
|
|
11
11
|
title: 'Memory Cycle',
|
|
12
12
|
annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
13
|
-
description:
|
|
14
|
-
'Core-memory mutation/status; recall retrieves.',
|
|
15
|
-
'Mutations use action=core with op; status uses action=status.',
|
|
16
|
-
'Store only durable compact ENGLISH facts/rules/preferences.',
|
|
17
|
-
].join(' '),
|
|
13
|
+
description: 'Core-memory mutation/status.',
|
|
18
14
|
inputSchema: {
|
|
19
15
|
type: 'object',
|
|
20
16
|
properties: {
|
|
@@ -286,9 +286,9 @@ function normalizeNativeSearchPayload(result, args, startedAt) {
|
|
|
286
286
|
|
|
287
287
|
async function _searchCore(args, { cacheState, nativeSearch, signal }) {
|
|
288
288
|
const cacheArgs = searchArgsForCacheKey(args)
|
|
289
|
-
const
|
|
289
|
+
const provider = 'native-web-search'
|
|
290
290
|
const searchCacheKey = buildCacheKey('search', {
|
|
291
|
-
provider
|
|
291
|
+
provider,
|
|
292
292
|
...cacheArgs,
|
|
293
293
|
})
|
|
294
294
|
const cachedSearch = getCachedEntry(cacheState, searchCacheKey)
|
|
@@ -8,7 +8,7 @@ export const TOOL_DEFS = [
|
|
|
8
8
|
{
|
|
9
9
|
name: 'search',
|
|
10
10
|
title: 'Mixdog Web Search',
|
|
11
|
-
description: `
|
|
11
|
+
description: `Cached web/docs/current-info search. ${TOOL_SYNC_EXECUTION_CONTRACT}`,
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
|
@@ -27,7 +27,7 @@ export const TOOL_DEFS = [
|
|
|
27
27
|
{
|
|
28
28
|
name: 'web_fetch',
|
|
29
29
|
title: 'Mixdog Web Fetch',
|
|
30
|
-
description: '
|
|
30
|
+
description: 'Fetch page/docs body from URL.',
|
|
31
31
|
inputSchema: {
|
|
32
32
|
type: 'object',
|
|
33
33
|
properties: {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function abortReason(signal, fallback = 'Operation aborted') {
|
|
2
|
+
const reason = signal?.reason;
|
|
3
|
+
if (reason instanceof Error) return reason;
|
|
4
|
+
if (reason !== undefined && reason !== null && reason !== '') {
|
|
5
|
+
return new Error(String(reason));
|
|
6
|
+
}
|
|
7
|
+
return new Error(fallback);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function throwIfAborted(signal, fallback) {
|
|
11
|
+
if (signal?.aborted) throw abortReason(signal, fallback);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Await one operation while making the caller's settlement independent from
|
|
16
|
+
* an implementation that ignores AbortSignal. The detached operation remains
|
|
17
|
+
* observed so a later rejection can never become unhandled.
|
|
18
|
+
*/
|
|
19
|
+
export async function runAbortable(signal, task, fallback) {
|
|
20
|
+
if (typeof task !== 'function') throw new TypeError('runAbortable requires a task function');
|
|
21
|
+
if (!signal) return await task();
|
|
22
|
+
throwIfAborted(signal, fallback);
|
|
23
|
+
|
|
24
|
+
const underlying = Promise.resolve().then(task);
|
|
25
|
+
underlying.catch(() => {});
|
|
26
|
+
|
|
27
|
+
return await new Promise((resolve, reject) => {
|
|
28
|
+
let settled = false;
|
|
29
|
+
const finish = (callback, value) => {
|
|
30
|
+
if (settled) return;
|
|
31
|
+
settled = true;
|
|
32
|
+
try { signal.removeEventListener('abort', onAbort); } catch {}
|
|
33
|
+
callback(value);
|
|
34
|
+
};
|
|
35
|
+
const onAbort = () => finish(reject, abortReason(signal, fallback));
|
|
36
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
37
|
+
if (signal.aborted) {
|
|
38
|
+
onAbort();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
underlying.then(
|
|
42
|
+
(value) => finish(resolve, value),
|
|
43
|
+
(error) => finish(reject, error),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Bound best-effort cleanup without cancelling the underlying work. The
|
|
50
|
+
* detached promise remains observed and may still finish in the background.
|
|
51
|
+
*/
|
|
52
|
+
export async function settleWithin(promise, timeoutMs) {
|
|
53
|
+
const budget = Number(timeoutMs);
|
|
54
|
+
if (!(Number.isFinite(budget) && budget > 0)) {
|
|
55
|
+
return { settled: true, value: await promise };
|
|
56
|
+
}
|
|
57
|
+
const underlying = Promise.resolve(promise).then((value) => ({ settled: true, value }));
|
|
58
|
+
underlying.catch(() => {});
|
|
59
|
+
let timer = null;
|
|
60
|
+
try {
|
|
61
|
+
return await Promise.race([
|
|
62
|
+
underlying,
|
|
63
|
+
new Promise((resolve) => {
|
|
64
|
+
timer = setTimeout(() => resolve({ settled: false, value: undefined }), budget);
|
|
65
|
+
}),
|
|
66
|
+
]);
|
|
67
|
+
} finally {
|
|
68
|
+
if (timer) clearTimeout(timer);
|
|
69
|
+
}
|
|
70
|
+
}
|