mixdog 0.9.95 → 0.9.96
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/package.json +12 -9
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/shared/01-tool.md +20 -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/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +26 -11
- 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/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 +28 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -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 +1 -27
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- 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/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-reader.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -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 +118 -18
- 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/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +10 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -23
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +23 -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 +87 -15
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -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/orchestrator.mjs +29 -1
- 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 +10 -10
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +56 -8
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +2 -2
- package/src/runtime/channels/lib/inbound-handler.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/memory/index.mjs +5 -38
- package/src/runtime/memory/lib/agent-ipc.mjs +145 -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 +12 -33
- 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 +8 -0
- 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-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-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/tool-defs.mjs +2 -2
- 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 +60 -31
- package/src/runtime/shared/config.mjs +130 -44
- package/src/runtime/shared/resource-admission.mjs +146 -97
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-surface.mjs +0 -5
- package/src/runtime/shared/turn-snapshot.mjs +31 -5
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/runtime-core.mjs +11 -11
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/session-lifecycle.mjs +9 -2
- package/src/session-runtime/session-turn-api.mjs +41 -9
- package/src/session-runtime/settings-api.mjs +5 -17
- 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/backend-daemon.mjs +97 -36
- package/src/standalone/channel-admin.mjs +12 -12
- package/src/standalone/channel-daemon-client.mjs +17 -1
- package/src/standalone/channel-daemon-transport.mjs +90 -8
- package/src/standalone/engine-daemon-client.mjs +380 -205
- package/src/standalone/engine-daemon-protocol.mjs +3 -4
- package/src/standalone/engine-daemon-service.mjs +378 -134
- package/src/standalone/engine-daemon-transport.mjs +268 -77
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/session-protocol.mjs +188 -0
- package/src/tui/App.jsx +30 -17
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +1 -2
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +46 -41
- 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 +6 -8
- 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 +9 -2
- 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/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/dist/index.mjs +920 -1592
- package/src/tui/engine/live-share.mjs +34 -0
- package/src/tui/engine/session-api-ext.mjs +1 -4
- package/src/tui/engine/session-api.mjs +103 -33
- package/src/tui/engine/session-flow.mjs +7 -1
- package/src/tui/engine/tool-card-results.mjs +4 -2
- package/src/tui/engine/tool-result-text.mjs +10 -0
- package/src/tui/engine/turn.mjs +9 -69
- package/src/tui/engine-local-session.mjs +47 -31
- package/src/tui/index.jsx +1 -2
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
|
@@ -171,6 +171,7 @@ export function createHttpRouter({
|
|
|
171
171
|
try {
|
|
172
172
|
const db = getDb()
|
|
173
173
|
const stats = await entryStats()
|
|
174
|
+
const memory = process.memoryUsage()
|
|
174
175
|
sendJson(res, {
|
|
175
176
|
status: 'ok',
|
|
176
177
|
worker_pid: process.pid,
|
|
@@ -192,6 +193,13 @@ export function createHttpRouter({
|
|
|
192
193
|
cycle_running: cycleScheduler.getCycleRunning(),
|
|
193
194
|
cycle_health: cycleScheduler.getCycleHealth(),
|
|
194
195
|
cycle_backlog: cycleScheduler.getCycleBacklogSnapshot(),
|
|
196
|
+
memory: {
|
|
197
|
+
rssBytes: memory.rss,
|
|
198
|
+
heapTotalBytes: memory.heapTotal,
|
|
199
|
+
heapUsedBytes: memory.heapUsed,
|
|
200
|
+
externalBytes: memory.external,
|
|
201
|
+
arrayBufferBytes: memory.arrayBuffers,
|
|
202
|
+
},
|
|
195
203
|
})
|
|
196
204
|
} catch (e) { sendError(res, e.message) }
|
|
197
205
|
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
|
}
|
|
@@ -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')
|
|
@@ -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
|
|
|
@@ -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')
|
|
@@ -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: {
|
|
@@ -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,119 @@
|
|
|
1
|
+
const LEGACY_AGENT_ROUTE_SLOTS = Object.freeze({
|
|
2
|
+
explore: Object.freeze({ workflow: 'explorer', maintenance: 'explore' }),
|
|
3
|
+
maintainer: Object.freeze({ alias: 'maintenance', workflow: 'memory', maintenance: 'memory' }),
|
|
4
|
+
worker: Object.freeze({ workflow: 'agent' }),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const REDUNDANT_WORKFLOW_PRESET_IDS = new Set([
|
|
8
|
+
'workflow-agent',
|
|
9
|
+
'workflow-explorer',
|
|
10
|
+
'workflow-memory',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function record(value) {
|
|
14
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isCompleteAgentRoute(value) {
|
|
18
|
+
return !!value
|
|
19
|
+
&& typeof value === 'object'
|
|
20
|
+
&& !Array.isArray(value)
|
|
21
|
+
&& !!String(value.provider || '').trim()
|
|
22
|
+
&& !!String(value.model || '').trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function generatedPresetAgentId(preset) {
|
|
26
|
+
const id = String(preset?.id || '').trim().toLowerCase();
|
|
27
|
+
if (id === 'workflow-agent') return 'worker';
|
|
28
|
+
if (id === 'workflow-explorer') return 'explore';
|
|
29
|
+
if (id === 'workflow-memory') return 'maintainer';
|
|
30
|
+
if (!id.startsWith('workflow-agent-')) return '';
|
|
31
|
+
const suffix = id.slice('workflow-agent-'.length);
|
|
32
|
+
if (suffix === 'explorer' || suffix === 'explore') return 'explore';
|
|
33
|
+
if (suffix === 'maintenance' || suffix === 'memory' || suffix === 'maintainer') return 'maintainer';
|
|
34
|
+
return suffix;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function routeFromGeneratedPreset(preset) {
|
|
38
|
+
const provider = String(preset?.provider || '').trim();
|
|
39
|
+
const model = String(preset?.model || '').trim();
|
|
40
|
+
if (!provider || !model) return null;
|
|
41
|
+
const route = { provider, model };
|
|
42
|
+
const effort = String(preset?.effort || '').trim();
|
|
43
|
+
if (effort) route.effort = effort;
|
|
44
|
+
if (preset?.fast === true) route.fast = true;
|
|
45
|
+
return route;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function configuredAgentRouteCandidates(config, agentId) {
|
|
49
|
+
const id = String(agentId || '').trim();
|
|
50
|
+
if (!id) return [];
|
|
51
|
+
const agents = record(config?.agents);
|
|
52
|
+
const legacy = LEGACY_AGENT_ROUTE_SLOTS[id] || null;
|
|
53
|
+
const candidates = [agents[id]];
|
|
54
|
+
if (legacy?.alias) candidates.push(agents[legacy.alias]);
|
|
55
|
+
if (legacy?.workflow) candidates.push(record(config?.workflowRoutes)[legacy.workflow]);
|
|
56
|
+
if (legacy?.maintenance) candidates.push(record(config?.maintenance)[legacy.maintenance]);
|
|
57
|
+
return candidates.filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function canonicalizeAgentRoutes(config = {}) {
|
|
61
|
+
const sourceAgents = record(config.agents);
|
|
62
|
+
const agents = Object.fromEntries(
|
|
63
|
+
Object.entries(sourceAgents).filter(([, route]) => isCompleteAgentRoute(route)),
|
|
64
|
+
);
|
|
65
|
+
for (const id of Object.keys(LEGACY_AGENT_ROUTE_SLOTS)) {
|
|
66
|
+
if (agents[id]) continue;
|
|
67
|
+
const candidate = configuredAgentRouteCandidates({ ...config, agents: sourceAgents }, id)
|
|
68
|
+
.find((route) => isCompleteAgentRoute(route));
|
|
69
|
+
if (candidate) agents[id] = candidate;
|
|
70
|
+
}
|
|
71
|
+
delete agents.maintenance;
|
|
72
|
+
// Older settings saves generated one preset per fixed/custom agent. Promote
|
|
73
|
+
// a preset only when no higher-priority canonical/legacy route exists, then
|
|
74
|
+
// the redundant preset can be removed without discarding user assignments.
|
|
75
|
+
for (const preset of Array.isArray(config?.presets) ? config.presets : []) {
|
|
76
|
+
const id = generatedPresetAgentId(preset);
|
|
77
|
+
if (!id || agents[id]) continue;
|
|
78
|
+
const route = routeFromGeneratedPreset(preset);
|
|
79
|
+
if (route) agents[id] = route;
|
|
80
|
+
}
|
|
81
|
+
return agents;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isRedundantGeneratedRoutePreset(preset, defaultPreset = null) {
|
|
85
|
+
const id = String(preset?.id || '').trim();
|
|
86
|
+
if (!id || id === String(defaultPreset || '').trim()) return false;
|
|
87
|
+
return REDUNDANT_WORKFLOW_PRESET_IDS.has(id) || id.startsWith('workflow-agent-');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function canonicalizeAgentRouteStorage(config = {}) {
|
|
91
|
+
const {
|
|
92
|
+
workflowRoutes: _legacyWorkflowRoutes,
|
|
93
|
+
...rest
|
|
94
|
+
} = config || {};
|
|
95
|
+
const maintenance = { ...record(config?.maintenance) };
|
|
96
|
+
delete maintenance.explore;
|
|
97
|
+
delete maintenance.memory;
|
|
98
|
+
const presets = Array.isArray(config?.presets)
|
|
99
|
+
? config.presets.filter((preset) => !isRedundantGeneratedRoutePreset(preset, config?.default))
|
|
100
|
+
: [];
|
|
101
|
+
return {
|
|
102
|
+
...rest,
|
|
103
|
+
agents: canonicalizeAgentRoutes(config),
|
|
104
|
+
maintenance,
|
|
105
|
+
presets,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function agentRouteStorageNeedsMigration(config = {}) {
|
|
110
|
+
const agents = record(config?.agents);
|
|
111
|
+
const maintenance = record(config?.maintenance);
|
|
112
|
+
return Object.prototype.hasOwnProperty.call(config || {}, 'workflowRoutes')
|
|
113
|
+
|| Object.prototype.hasOwnProperty.call(agents, 'maintenance')
|
|
114
|
+
|| Object.prototype.hasOwnProperty.call(maintenance, 'explore')
|
|
115
|
+
|| Object.prototype.hasOwnProperty.call(maintenance, 'memory')
|
|
116
|
+
|| Object.values(agents).some((route) => !isCompleteAgentRoute(route))
|
|
117
|
+
|| (Array.isArray(config?.presets)
|
|
118
|
+
&& config.presets.some((preset) => isRedundantGeneratedRoutePreset(preset, config?.default)));
|
|
119
|
+
}
|
|
@@ -28,6 +28,55 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
28
28
|
|
|
29
29
|
const _execFileAsync = promisify(execFile);
|
|
30
30
|
|
|
31
|
+
// Async atomic writes from many daemon sessions share one disk. Keep enough
|
|
32
|
+
// parallelism for SSD throughput, then use additive-increase/multiplicative-
|
|
33
|
+
// decrease when latency shows the filesystem is saturated. This gate never
|
|
34
|
+
// touches synchronous compatibility paths and never revokes in-flight writes.
|
|
35
|
+
const _ioCeiling = Math.max(1, Math.floor(Number(process.env.MIXDOG_FILE_IO_MAX_CONCURRENCY) || 16));
|
|
36
|
+
let _ioLimit = Math.min(4, _ioCeiling);
|
|
37
|
+
let _ioActive = 0;
|
|
38
|
+
let _ioFastStreak = 0;
|
|
39
|
+
const _ioQueue = [];
|
|
40
|
+
|
|
41
|
+
function _drainAdaptiveFileIo() {
|
|
42
|
+
while (_ioActive < _ioLimit && _ioQueue.length > 0) {
|
|
43
|
+
const item = _ioQueue.shift();
|
|
44
|
+
_ioActive += 1;
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
Promise.resolve()
|
|
47
|
+
.then(item.run)
|
|
48
|
+
.then(item.resolve, item.reject)
|
|
49
|
+
.finally(() => {
|
|
50
|
+
const elapsed = Date.now() - startedAt;
|
|
51
|
+
_ioActive = Math.max(0, _ioActive - 1);
|
|
52
|
+
if (elapsed >= 250) {
|
|
53
|
+
_ioLimit = Math.max(1, Math.ceil(_ioLimit / 2));
|
|
54
|
+
_ioFastStreak = 0;
|
|
55
|
+
} else if (elapsed <= 40 && _ioLimit < _ioCeiling) {
|
|
56
|
+
_ioFastStreak += 1;
|
|
57
|
+
if (_ioFastStreak >= _ioLimit * 4) {
|
|
58
|
+
_ioLimit += 1;
|
|
59
|
+
_ioFastStreak = 0;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
_ioFastStreak = 0;
|
|
63
|
+
}
|
|
64
|
+
_drainAdaptiveFileIo();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function _runAdaptiveFileIo(run) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
_ioQueue.push({ run, resolve, reject });
|
|
72
|
+
_drainAdaptiveFileIo();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function adaptiveFileIoSnapshot() {
|
|
77
|
+
return { active: _ioActive, queued: _ioQueue.length, limit: _ioLimit, ceiling: _ioCeiling };
|
|
78
|
+
}
|
|
79
|
+
|
|
31
80
|
const RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY', 'EEXIST']);
|
|
32
81
|
const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
|
|
33
82
|
const DEFAULT_BACKOFFS_MS = Object.freeze([25, 50, 100, 200, 400, 800, 1200, 1600]);
|
|
@@ -79,6 +128,28 @@ function _describeLockHolder(lockPath) {
|
|
|
79
128
|
}
|
|
80
129
|
}
|
|
81
130
|
|
|
131
|
+
// Lock-wait watchdog. A cross-process file lock is invisible in a profile: the
|
|
132
|
+
// only symptom is "everything stopped". One bounded line per path names the
|
|
133
|
+
// path and the wait, so the next stall is diagnosed from the log instead of a
|
|
134
|
+
// live repro.
|
|
135
|
+
const LOCK_WAIT_WARN_MS = 500;
|
|
136
|
+
const LOCK_WAIT_WARN_INTERVAL_MS = 10_000;
|
|
137
|
+
const _lockWaitWarnedAt = new Map();
|
|
138
|
+
function _reportLockWait(lockPath, waitedMs, mode) {
|
|
139
|
+
if (waitedMs < LOCK_WAIT_WARN_MS) return;
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
if (now - (_lockWaitWarnedAt.get(lockPath) || 0) < LOCK_WAIT_WARN_INTERVAL_MS) return;
|
|
142
|
+
_lockWaitWarnedAt.set(lockPath, now);
|
|
143
|
+
if (_lockWaitWarnedAt.size > 64) {
|
|
144
|
+
for (const [path, at] of _lockWaitWarnedAt) {
|
|
145
|
+
if (now - at >= LOCK_WAIT_WARN_INTERVAL_MS * 6) _lockWaitWarnedAt.delete(path);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
process.stderr.write(`[atomic-file] ${mode} lock wait ${waitedMs}ms: ${lockPath}\n`);
|
|
150
|
+
} catch { /* diagnostics only */ }
|
|
151
|
+
}
|
|
152
|
+
|
|
82
153
|
export function renameWithRetrySync(src, dst, opts = {}) {
|
|
83
154
|
const backoffs = Array.isArray(opts.backoffs) && opts.backoffs.length > 0
|
|
84
155
|
? opts.backoffs
|
|
@@ -129,6 +200,7 @@ export function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
129
200
|
}
|
|
130
201
|
const deadline = Date.now() + timeoutMs;
|
|
131
202
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
203
|
+
const waitStartedAt = Date.now();
|
|
132
204
|
let attempt = 0;
|
|
133
205
|
let lastErr = null;
|
|
134
206
|
while (true) {
|
|
@@ -168,6 +240,7 @@ export function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
168
240
|
// `<pid> <ts> <token>` is a superset of the old `<pid> <ts>`; a
|
|
169
241
|
// tokenless (old-format) lock is still read as pid-authoritative.
|
|
170
242
|
try { writeFileSync(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}\n`, 'utf8'); } catch {}
|
|
243
|
+
_reportLockWait(lockPath, Date.now() - waitStartedAt, 'sync');
|
|
171
244
|
// For secret-bearing critical sections, the lock file sits beside
|
|
172
245
|
// the secret in the same (shared-home) directory; clamp it owner-only
|
|
173
246
|
// too. Fail-closed: an unenforceable ACL aborts before fn() runs.
|
|
@@ -362,6 +435,7 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
|
|
|
362
435
|
const staleMs = Number.isFinite(opts.staleMs) ? opts.staleMs : 30000;
|
|
363
436
|
const deadline = Date.now() + timeoutMs;
|
|
364
437
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
438
|
+
const waitStartedAt = Date.now();
|
|
365
439
|
let attempt = 0;
|
|
366
440
|
let lastErr = null;
|
|
367
441
|
while (true) {
|
|
@@ -393,6 +467,7 @@ async function _withOsFileLock(lockPath, fn, opts = {}) {
|
|
|
393
467
|
continue;
|
|
394
468
|
}
|
|
395
469
|
try { writeFileSync(fd, `${process.pid} ${Date.now()} ${_OWNER_TOKEN}\n`, 'utf8'); } catch {}
|
|
470
|
+
_reportLockWait(lockPath, Date.now() - waitStartedAt, 'async');
|
|
396
471
|
_osHeldPaths.add(lockPath);
|
|
397
472
|
try {
|
|
398
473
|
// Async ACL enforcement (promisified execFile) so a secret-bearing
|
|
@@ -738,10 +813,11 @@ export async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
|
738
813
|
throw err;
|
|
739
814
|
}
|
|
740
815
|
};
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
816
|
+
return _runAdaptiveFileIo(() => (
|
|
817
|
+
opts.lock === true
|
|
818
|
+
? withFileLock(`${filePath}.lock`, run, opts)
|
|
819
|
+
: run()
|
|
820
|
+
));
|
|
745
821
|
}
|
|
746
822
|
|
|
747
823
|
export function writeJsonAtomicAsync(filePath, value, opts = {}) {
|
|
@@ -7,8 +7,8 @@ export function channelNotificationModelContent(params = {}) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function shouldMirrorChannelNotificationToPending(meta = {}) {
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Every inbound channel/schedule notification flows through the single
|
|
11
|
+
// queue — when no live
|
|
12
12
|
// listener handled it (engine not subscribed yet, pane disposed, headless),
|
|
13
13
|
// it is mirrored into the session pending queue for the next turn instead
|
|
14
14
|
// of being dropped. Silent-to-agent stays excluded; the upstream content
|