mixdog 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -23
- package/package.json +33 -27
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +344 -0
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1223 -95
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +118 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +145 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -98
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +367 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +746 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* agent-ipc.mjs — IPC client for
|
|
2
|
+
* agent-ipc.mjs — IPC client for agent dispatch calls from the memory worker.
|
|
3
3
|
*
|
|
4
|
-
* The memory worker runs in its own fork, while the
|
|
4
|
+
* The memory worker runs in its own fork, while the agent runtime / provider
|
|
5
5
|
* registry lives in-process in the parent server. cycle1 / cycle2 can't
|
|
6
|
-
* call
|
|
6
|
+
* call makeAgentDispatch() locally (provider map is empty here), so we route
|
|
7
7
|
* every LLM call over IPC:
|
|
8
8
|
*
|
|
9
9
|
* memory → parent : { type: 'agent_ipc_request', callId, tool, params }
|
|
@@ -36,10 +36,10 @@ function nextCallId() {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Send an agent-
|
|
39
|
+
* Send an agent-agent dispatch request to the parent. Throws if IPC is
|
|
40
40
|
* unavailable (worker not forked) or the parent reports an error.
|
|
41
41
|
*
|
|
42
|
-
* @param {object} opts
|
|
42
|
+
* @param {object} opts agent-dispatch construction options
|
|
43
43
|
* @param {string} [opts.role]
|
|
44
44
|
* @param {string} [opts.taskType]
|
|
45
45
|
* @param {string} [opts.mode]
|
|
@@ -49,9 +49,9 @@ function nextCallId() {
|
|
|
49
49
|
* @param {string} prompt user message
|
|
50
50
|
* @returns {Promise<string>} raw assistant content
|
|
51
51
|
*/
|
|
52
|
-
export function
|
|
53
|
-
if (!process.send) {
|
|
54
|
-
return Promise.reject(new Error('agent-ipc: process.send
|
|
52
|
+
export function callAgentDispatch(opts = {}, prompt) {
|
|
53
|
+
if (!process.send || !process.connected) {
|
|
54
|
+
return Promise.reject(new Error('agent-ipc: IPC channel unavailable (no process.send / not connected)'))
|
|
55
55
|
}
|
|
56
56
|
installListener()
|
|
57
57
|
const callId = nextCallId()
|
|
@@ -60,20 +60,31 @@ export function callBridgeLlm(opts = {}, prompt) {
|
|
|
60
60
|
const timer = setTimeout(() => {
|
|
61
61
|
if (!pending.has(callId)) return
|
|
62
62
|
pending.delete(callId)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if (process.connected) {
|
|
64
|
+
try {
|
|
65
|
+
process.send({ type: 'agent_ipc_cancel', callId }, undefined, {}, () => {})
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
66
68
|
reject(new Error(`agent-ipc: timed out after ${timeoutMs}ms`))
|
|
67
69
|
}, timeoutMs)
|
|
68
70
|
pending.set(callId, {
|
|
69
71
|
resolve: (v) => { clearTimeout(timer); resolve(v) },
|
|
70
72
|
reject: (e) => { clearTimeout(timer); reject(e) },
|
|
71
73
|
})
|
|
74
|
+
if (!process.connected) {
|
|
75
|
+
pending.delete(callId)
|
|
76
|
+
clearTimeout(timer)
|
|
77
|
+
reject(new Error('agent-ipc: IPC channel not connected'))
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
// Pass a write callback so a closed-channel write surfaces as an async
|
|
81
|
+
// error here (rejecting this Promise) instead of bubbling to the process
|
|
82
|
+
// 'error' event and crashing the daemon with ERR_IPC_CHANNEL_CLOSED.
|
|
72
83
|
try {
|
|
73
84
|
process.send({
|
|
74
85
|
type: 'agent_ipc_request',
|
|
75
86
|
callId,
|
|
76
|
-
tool: '
|
|
87
|
+
tool: 'agent_dispatch',
|
|
77
88
|
params: {
|
|
78
89
|
role: opts.role || null,
|
|
79
90
|
taskType: opts.taskType || null,
|
|
@@ -83,6 +94,12 @@ export function callBridgeLlm(opts = {}, prompt) {
|
|
|
83
94
|
prompt: String(prompt ?? ''),
|
|
84
95
|
timeout: timeoutMs,
|
|
85
96
|
},
|
|
97
|
+
}, undefined, {}, (err) => {
|
|
98
|
+
if (!err) return
|
|
99
|
+
if (!pending.has(callId)) return
|
|
100
|
+
pending.delete(callId)
|
|
101
|
+
clearTimeout(timer)
|
|
102
|
+
reject(err)
|
|
86
103
|
})
|
|
87
104
|
} catch (e) {
|
|
88
105
|
pending.delete(callId)
|
|
@@ -16,7 +16,7 @@ function __mixdogMemoryLog(...args) {
|
|
|
16
16
|
|
|
17
17
|
import { getDatabase, embeddingToSql } from './memory.mjs'
|
|
18
18
|
import { cachedEmbedTextBatch } from './memory-embed.mjs'
|
|
19
|
-
import {
|
|
19
|
+
import { callAgentDispatch } from './agent-ipc.mjs'
|
|
20
20
|
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
21
21
|
import { checkedConnect } from './pg/adapter.mjs'
|
|
22
22
|
|
|
@@ -169,7 +169,7 @@ async function _llmJudgeMerge(existing, incoming) {
|
|
|
169
169
|
`EXISTING: ${existing.element} — ${String(existing.summary || '')}\n` +
|
|
170
170
|
`INCOMING: ${incoming.element} — ${String(incoming.summary || '')}`
|
|
171
171
|
try {
|
|
172
|
-
const raw = await
|
|
172
|
+
const raw = await callAgentDispatch({
|
|
173
173
|
role: 'cycle2-agent',
|
|
174
174
|
taskType: 'maintenance',
|
|
175
175
|
mode: 'core-merge-judge',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const DEFAULT_MODEL_ID = 'ibm-granite/granite-embedding-97m-multilingual-r2'
|
|
2
|
+
|
|
3
|
+
const MODEL_PROFILES = Object.freeze({
|
|
4
|
+
[DEFAULT_MODEL_ID]: Object.freeze({
|
|
5
|
+
dims: 384,
|
|
6
|
+
defaultDtype: 'fp32',
|
|
7
|
+
defaultDevice: 'cpu',
|
|
8
|
+
modelFileName: 'model_quint8_avx2',
|
|
9
|
+
supportedDtypes: Object.freeze(['fp32']),
|
|
10
|
+
}),
|
|
11
|
+
'Xenova/bge-m3': Object.freeze({
|
|
12
|
+
dims: 1024,
|
|
13
|
+
defaultDtype: 'q4',
|
|
14
|
+
defaultDevice: 'auto',
|
|
15
|
+
supportedDtypes: Object.freeze(['fp32', 'fp16', 'q8', 'q4']),
|
|
16
|
+
}),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function clean(value) {
|
|
20
|
+
return String(value ?? '').trim()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getConfiguredEmbeddingModelId() {
|
|
24
|
+
return clean(process.env.MIXDOG_EMBED_MODEL) || DEFAULT_MODEL_ID
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getEmbeddingModelProfile(modelId = getConfiguredEmbeddingModelId()) {
|
|
28
|
+
return MODEL_PROFILES[clean(modelId)] || null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getKnownEmbeddingDims(modelId = getConfiguredEmbeddingModelId()) {
|
|
32
|
+
return getEmbeddingModelProfile(modelId)?.dims ?? null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeEmbeddingDtype(modelId, dtype) {
|
|
36
|
+
const profile = getEmbeddingModelProfile(modelId)
|
|
37
|
+
const fallback = profile?.defaultDtype || 'fp32'
|
|
38
|
+
const requested = clean(dtype).toLowerCase()
|
|
39
|
+
if (!requested) return fallback
|
|
40
|
+
const supported = new Set(profile?.supportedDtypes || ['fp32', 'fp16', 'q8', 'q4'])
|
|
41
|
+
return supported.has(requested) ? requested : fallback
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getDefaultEmbeddingDtype(modelId = getConfiguredEmbeddingModelId()) {
|
|
45
|
+
return normalizeEmbeddingDtype(modelId, process.env.MIXDOG_EMBED_DTYPE)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getDefaultEmbeddingDevice(modelId = getConfiguredEmbeddingModelId()) {
|
|
49
|
+
return getEmbeddingModelProfile(modelId)?.defaultDevice || 'auto'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getEmbeddingModelLoadOptions(modelId = getConfiguredEmbeddingModelId()) {
|
|
53
|
+
const profile = getEmbeddingModelProfile(modelId)
|
|
54
|
+
return profile?.modelFileName ? { model_file_name: profile.modelFileName } : {}
|
|
55
|
+
}
|
|
@@ -12,12 +12,18 @@ import { Worker } from 'worker_threads'
|
|
|
12
12
|
import { join } from 'path'
|
|
13
13
|
import { fileURLToPath } from 'url'
|
|
14
14
|
import { writeProfilePoint } from './model-profile.mjs'
|
|
15
|
+
import {
|
|
16
|
+
getConfiguredEmbeddingModelId,
|
|
17
|
+
getDefaultEmbeddingDtype,
|
|
18
|
+
getKnownEmbeddingDims,
|
|
19
|
+
normalizeEmbeddingDtype,
|
|
20
|
+
} from './embedding-model-config.mjs'
|
|
15
21
|
|
|
16
|
-
const MODEL_ID =
|
|
22
|
+
const MODEL_ID = getConfiguredEmbeddingModelId()
|
|
17
23
|
|
|
18
24
|
// Static dims registry — bypasses first-boot measurement for registered models.
|
|
19
25
|
// Validated against measured dims inside warmupEmbeddingProvider; mismatch throws.
|
|
20
|
-
const KNOWN_MODEL_DIMS = {
|
|
26
|
+
const KNOWN_MODEL_DIMS = { [MODEL_ID]: getKnownEmbeddingDims(MODEL_ID) }
|
|
21
27
|
|
|
22
28
|
let worker = null
|
|
23
29
|
let _restartCount = 0
|
|
@@ -26,6 +32,8 @@ const MAX_RESTART_BACKOFF_MS = 30_000
|
|
|
26
32
|
let cachedDims = null
|
|
27
33
|
let _modelReady = false
|
|
28
34
|
let _device = 'cpu'
|
|
35
|
+
let _configuredDtype = getDefaultEmbeddingDtype(MODEL_ID)
|
|
36
|
+
let _warmupPromise = null
|
|
29
37
|
let _embedCallCount = 0
|
|
30
38
|
let _msgId = 0
|
|
31
39
|
const _pending = new Map()
|
|
@@ -145,9 +153,10 @@ export function configureEmbedding(config = {}) {
|
|
|
145
153
|
cachedDims = null
|
|
146
154
|
_modelReady = false
|
|
147
155
|
_device = 'cpu'
|
|
156
|
+
_configuredDtype = normalizeEmbeddingDtype(MODEL_ID, config.dtype ?? process.env.MIXDOG_EMBED_DTYPE)
|
|
148
157
|
queryEmbeddingCache.clear()
|
|
149
158
|
if (worker) {
|
|
150
|
-
sendToWorker('configure', { dtype:
|
|
159
|
+
sendToWorker('configure', { dtype: _configuredDtype }).catch((err) => {
|
|
151
160
|
// Silent .catch hid worker reconfigure failures (dtype mismatch,
|
|
152
161
|
// worker crash, IPC closed). At least one log line so cycle1 /
|
|
153
162
|
// cycle2 root-cause investigation can see the upstream failure
|
|
@@ -166,16 +175,24 @@ export function getEmbeddingModelId() {
|
|
|
166
175
|
return MODEL_ID
|
|
167
176
|
}
|
|
168
177
|
|
|
178
|
+
export function getEmbeddingDtype() {
|
|
179
|
+
return _configuredDtype
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
export function getKnownDimsForCurrentModel() {
|
|
170
183
|
return KNOWN_MODEL_DIMS[MODEL_ID] ?? null
|
|
171
184
|
}
|
|
172
185
|
|
|
186
|
+
export function isEmbeddingModelReady() {
|
|
187
|
+
return _modelReady && Boolean(cachedDims)
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
export function getEmbeddingDims() {
|
|
174
191
|
if (!cachedDims) throw new Error('embedding dims not yet measured — warmup required')
|
|
175
192
|
return cachedDims
|
|
176
193
|
}
|
|
177
194
|
|
|
178
|
-
|
|
195
|
+
async function runEmbeddingWarmup() {
|
|
179
196
|
if (_modelReady && cachedDims) return true
|
|
180
197
|
const result = await sendToWorker('warmup')
|
|
181
198
|
if (!result.dims) throw new Error('warmup returned no dims — model output missing')
|
|
@@ -191,6 +208,16 @@ export async function warmupEmbeddingProvider() {
|
|
|
191
208
|
return true
|
|
192
209
|
}
|
|
193
210
|
|
|
211
|
+
export function warmupEmbeddingProvider() {
|
|
212
|
+
if (_modelReady && cachedDims) return Promise.resolve(true)
|
|
213
|
+
if (!_warmupPromise) {
|
|
214
|
+
_warmupPromise = runEmbeddingWarmup().finally(() => {
|
|
215
|
+
_warmupPromise = null
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
return _warmupPromise
|
|
219
|
+
}
|
|
220
|
+
|
|
194
221
|
export async function embedText(text) {
|
|
195
222
|
const clean = String(text ?? '').trim()
|
|
196
223
|
if (!clean) return []
|
|
@@ -7,14 +7,23 @@ function __mixdogMemoryLog(...args) {
|
|
|
7
7
|
import { parentPort } from 'worker_threads'
|
|
8
8
|
import { createRequire } from 'module'
|
|
9
9
|
import { join } from 'path'
|
|
10
|
-
import { pathToFileURL } from 'url'
|
|
11
10
|
import { mkdirSync } from 'fs'
|
|
12
11
|
import os from 'os'
|
|
13
12
|
import { execFile } from 'child_process'
|
|
14
13
|
import { promisify } from 'util'
|
|
14
|
+
import { resolvePluginData } from '../../shared/plugin-paths.mjs'
|
|
15
|
+
import {
|
|
16
|
+
getConfiguredEmbeddingModelId,
|
|
17
|
+
getDefaultEmbeddingDevice,
|
|
18
|
+
getDefaultEmbeddingDtype,
|
|
19
|
+
getEmbeddingModelLoadOptions,
|
|
20
|
+
normalizeEmbeddingDtype,
|
|
21
|
+
} from './embedding-model-config.mjs'
|
|
15
22
|
|
|
16
|
-
const MODEL_ID =
|
|
17
|
-
const
|
|
23
|
+
const MODEL_ID = getConfiguredEmbeddingModelId()
|
|
24
|
+
const DEFAULT_DEVICE = getDefaultEmbeddingDevice(MODEL_ID)
|
|
25
|
+
const DEFAULT_DTYPE = getDefaultEmbeddingDtype(MODEL_ID)
|
|
26
|
+
const MODEL_LOAD_OPTIONS = getEmbeddingModelLoadOptions(MODEL_ID)
|
|
18
27
|
const INTRA_OP_THREADS = 1
|
|
19
28
|
const INTER_OP_THREADS = 1
|
|
20
29
|
// Session-create graph optimization. ORT defaults to 'all' (full node fusion),
|
|
@@ -31,7 +40,7 @@ const execFileAsync = promisify(execFile)
|
|
|
31
40
|
// MIXDOG_EMBED_LOAD_CORES overrides (default 1 = single core).
|
|
32
41
|
const _envLoadCores = Number(process.env.MIXDOG_EMBED_LOAD_CORES)
|
|
33
42
|
const LOAD_AFFINITY_CORES = Number.isInteger(_envLoadCores) && _envLoadCores >= 1 ? _envLoadCores : 1
|
|
34
|
-
const MODEL_CACHE_DIR = join(
|
|
43
|
+
const MODEL_CACHE_DIR = join(resolvePluginData(), 'memory-models')
|
|
35
44
|
// Idle dispose was previously 15 min. Production profiling showed the model
|
|
36
45
|
// re-load cost (~3 s DirectML cold start) repeating ~880×/4 h because cycle1
|
|
37
46
|
// gaps exceed 15 min in normal use. Default to keep-alive (0) so the model
|
|
@@ -92,8 +101,8 @@ function patchOrtThreads() {
|
|
|
92
101
|
const require = createRequire(import.meta.url)
|
|
93
102
|
let ort = null
|
|
94
103
|
try {
|
|
95
|
-
const
|
|
96
|
-
const transformersRequire = createRequire(
|
|
104
|
+
const transformersEntry = require.resolve('@huggingface/transformers')
|
|
105
|
+
const transformersRequire = createRequire(transformersEntry)
|
|
97
106
|
ort = transformersRequire('onnxruntime-node')
|
|
98
107
|
} catch {
|
|
99
108
|
ort = require('onnxruntime-node')
|
|
@@ -129,12 +138,13 @@ async function loadExtractor() {
|
|
|
129
138
|
env.cacheDir = MODEL_CACHE_DIR
|
|
130
139
|
try { env.backends.onnx.wasm.numThreads = INTRA_OP_THREADS } catch {}
|
|
131
140
|
const opts = {}
|
|
132
|
-
|
|
141
|
+
Object.assign(opts, MODEL_LOAD_OPTIONS)
|
|
142
|
+
if (configuredDtype) {
|
|
133
143
|
opts.dtype = configuredDtype
|
|
134
144
|
}
|
|
135
145
|
const startMs = Date.now()
|
|
136
146
|
let extractor
|
|
137
|
-
const requestedDevice = String(process.env.MIXDOG_MEMORY_EMBED_DEVICE ||
|
|
147
|
+
const requestedDevice = String(process.env.MIXDOG_MEMORY_EMBED_DEVICE || DEFAULT_DEVICE).trim().toLowerCase()
|
|
138
148
|
const preferGpu = requestedDevice === 'dml'
|
|
139
149
|
|| requestedDevice === 'gpu'
|
|
140
150
|
|| (requestedDevice === 'auto' && process.platform === 'win32')
|
|
@@ -268,8 +278,7 @@ async function processMessage(msg) {
|
|
|
268
278
|
_embedInFlight = true
|
|
269
279
|
if (_idleTimer) { clearTimeout(_idleTimer); _idleTimer = null }
|
|
270
280
|
if (msg.dtype != null) {
|
|
271
|
-
|
|
272
|
-
configuredDtype = ['fp32', 'fp16', 'q8', 'q4'].includes(dt) ? dt : DEFAULT_DTYPE
|
|
281
|
+
configuredDtype = normalizeEmbeddingDtype(MODEL_ID, msg.dtype)
|
|
273
282
|
}
|
|
274
283
|
if (extractorPromise) {
|
|
275
284
|
try {
|
|
@@ -6,9 +6,9 @@ function __mixdogMemoryLog(...args) {
|
|
|
6
6
|
|
|
7
7
|
import { cleanMemoryText } from './memory.mjs'
|
|
8
8
|
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
9
|
-
import {
|
|
9
|
+
import { callAgentDispatch } from './agent-ipc.mjs'
|
|
10
10
|
import {
|
|
11
|
-
flushEmbeddingDirty, inferChunkProjectId,
|
|
11
|
+
flushEmbeddingDirty, inferChunkProjectId, syncRootEmbedding,
|
|
12
12
|
} from './memory-embed.mjs'
|
|
13
13
|
import { markCycleRequest, consumeCycleRequests, resolveCoalesceMaxDrains, scheduleCoalescedCycleRetry, makeCycleRequestSignature, resolveCoalesceMaxRetries } from './memory-cycle-requests.mjs'
|
|
14
14
|
|
|
@@ -277,7 +277,7 @@ export async function runCycle1(db, config = {}, options = {}, dataDir = null) {
|
|
|
277
277
|
)
|
|
278
278
|
if (_runCycle1InFlight.has(db)) {
|
|
279
279
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle1', 'in-flight', requestSignature)
|
|
280
|
-
scheduleRetry()
|
|
280
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
281
281
|
logCycle1Throttled('in-flight', '[cycle1] skipped: already in flight for this db\n')
|
|
282
282
|
return {
|
|
283
283
|
processed: 0, chunks: 0, skipped: 0, sessions: 0,
|
|
@@ -301,7 +301,7 @@ export async function runCycle1(db, config = {}, options = {}, dataDir = null) {
|
|
|
301
301
|
if (!gotLock) {
|
|
302
302
|
client.release()
|
|
303
303
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle1', 'advisory-lock', requestSignature)
|
|
304
|
-
scheduleRetry()
|
|
304
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
305
305
|
logCycle1Throttled('advisory-lock', '[cycle1] skipped: advisory lock held by another worker\n')
|
|
306
306
|
return { processed: 0, chunks: 0, skipped: 0, sessions: 0, skippedInFlight: true, pendingRows: await countPendingRows(db) }
|
|
307
307
|
}
|
|
@@ -380,9 +380,21 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
380
380
|
const pendingRowsAtStart = await countPendingRows(db)
|
|
381
381
|
throwIfAborted(signal)
|
|
382
382
|
const batchSize = Math.max(1, Number(config.batch_size ?? 100))
|
|
383
|
+
const windowSize = Math.max(1, Number(config.window_size ?? config.windowSize ?? batchSize))
|
|
384
|
+
const rowsPerSession = Math.max(windowSize, Number(
|
|
385
|
+
config.rows_per_session
|
|
386
|
+
?? config.rowsPerSession
|
|
387
|
+
?? config.max_rows_per_session
|
|
388
|
+
?? config.maxRowsPerSession
|
|
389
|
+
?? batchSize,
|
|
390
|
+
) || batchSize)
|
|
383
391
|
// Fallback chain handles flat config + nested cycle1 wrap shapes.
|
|
384
392
|
const minBatch = Math.max(1, Number(config?.min_batch ?? config?.cycle1?.min_batch ?? CYCLE1_MIN_BATCH))
|
|
385
393
|
const sessionCap = Math.max(1, Number(config?.session_cap ?? config?.cycle1?.session_cap ?? CYCLE1_SESSION_CAP))
|
|
394
|
+
const onlySessionId = String(config.session_id ?? config.sessionId ?? '').trim()
|
|
395
|
+
const sessionFilterSql = onlySessionId ? 'AND session_id = $4' : ''
|
|
396
|
+
const queryParams = [sessionCap, Date.now() - CYCLE1_OMITTED_COOLDOWN_MS, rowsPerSession]
|
|
397
|
+
if (onlySessionId) queryParams.push(onlySessionId)
|
|
386
398
|
const preset = options.preset || resolveMaintenancePreset('memory')
|
|
387
399
|
// Inner LLM timeout aligns to caller deadline -1s so the channel side can ack gracefully.
|
|
388
400
|
const callerDeadlineMs = Number(options.callerDeadlineMs ?? 0)
|
|
@@ -390,23 +402,24 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
390
402
|
const timeout = callerDeadlineMs > 0
|
|
391
403
|
? Math.min(baseTimeout, Math.max(5000, callerDeadlineMs - 1000))
|
|
392
404
|
: baseTimeout
|
|
393
|
-
// Select
|
|
394
|
-
// session.
|
|
395
|
-
//
|
|
405
|
+
// Select closest/recent sessions first, then fetch closest/recent rows per
|
|
406
|
+
// selected session. Memory fill is recency-first; session isolation below
|
|
407
|
+
// keeps unrelated episodes out of the same classifier prompt.
|
|
396
408
|
const fetchResult = await db.query(
|
|
397
409
|
`WITH selected_sessions AS (
|
|
398
|
-
SELECT session_id,
|
|
410
|
+
SELECT session_id, MAX(ts) AS latest_ts, MAX(id) AS latest_id
|
|
399
411
|
FROM entries
|
|
400
412
|
WHERE chunk_root IS NULL
|
|
401
413
|
AND NULLIF(btrim(session_id), '') IS NOT NULL
|
|
402
414
|
AND (reviewed_at IS NULL OR reviewed_at < $2)
|
|
415
|
+
${sessionFilterSql}
|
|
403
416
|
GROUP BY session_id
|
|
404
|
-
ORDER BY
|
|
417
|
+
ORDER BY latest_ts DESC, latest_id DESC
|
|
405
418
|
LIMIT $1
|
|
406
419
|
), ranked AS (
|
|
407
420
|
SELECT e.id, e.ts, e.role, e.content, e.session_id, e.source_ref, e.project_id,
|
|
408
|
-
s.
|
|
409
|
-
ROW_NUMBER() OVER (PARTITION BY e.session_id ORDER BY e.ts
|
|
421
|
+
s.latest_ts, s.latest_id,
|
|
422
|
+
ROW_NUMBER() OVER (PARTITION BY e.session_id ORDER BY e.ts DESC, e.id DESC) AS rn
|
|
410
423
|
FROM entries e
|
|
411
424
|
JOIN selected_sessions s ON s.session_id = e.session_id
|
|
412
425
|
WHERE e.chunk_root IS NULL
|
|
@@ -415,8 +428,8 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
415
428
|
SELECT id, ts, role, content, session_id, source_ref, project_id
|
|
416
429
|
FROM ranked
|
|
417
430
|
WHERE rn <= $3
|
|
418
|
-
ORDER BY
|
|
419
|
-
|
|
431
|
+
ORDER BY latest_ts DESC, latest_id DESC, session_id, ts DESC, id DESC`,
|
|
432
|
+
queryParams,
|
|
420
433
|
)
|
|
421
434
|
throwIfAborted(signal)
|
|
422
435
|
const rowsDesc = fetchResult.rows
|
|
@@ -463,9 +476,9 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
463
476
|
const windows = []
|
|
464
477
|
for (const sessionRowsDesc of rowsBySession.values()) {
|
|
465
478
|
const rowsAsc = sessionRowsDesc.slice().reverse()
|
|
466
|
-
for (let offset = 0; offset < rowsAsc.length; offset +=
|
|
479
|
+
for (let offset = 0; offset < rowsAsc.length; offset += windowSize) {
|
|
467
480
|
throwIfAborted(signal)
|
|
468
|
-
windows.push(rowsAsc.slice(offset, offset +
|
|
481
|
+
windows.push(rowsAsc.slice(offset, offset + windowSize))
|
|
469
482
|
}
|
|
470
483
|
}
|
|
471
484
|
|
|
@@ -501,7 +514,7 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
501
514
|
}
|
|
502
515
|
|
|
503
516
|
const userMessage = buildCycle1ChunkPrompt(rows)
|
|
504
|
-
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm :
|
|
517
|
+
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callAgentDispatch
|
|
505
518
|
|
|
506
519
|
let raw
|
|
507
520
|
const _tLlm = Date.now()
|
|
@@ -512,7 +525,7 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
512
525
|
mode: 'cycle1',
|
|
513
526
|
preset,
|
|
514
527
|
timeout,
|
|
515
|
-
// Pin cwd to null so every memory cycle call hits the same
|
|
528
|
+
// Pin cwd to null so every memory cycle call hits the same agent cache shard.
|
|
516
529
|
cwd: null,
|
|
517
530
|
}, userMessage)
|
|
518
531
|
} catch (err) {
|
|
@@ -646,6 +659,14 @@ async function _runCycle1Impl(db, config = {}, options = {}, _dataDir = null) {
|
|
|
646
659
|
usedIds.add(mid)
|
|
647
660
|
committedRowIds.add(mid)
|
|
648
661
|
}
|
|
662
|
+
// Real-time embedding: embed this episode the moment it is committed so
|
|
663
|
+
// dense recall sees fresh roots without waiting for the end-of-cycle flush.
|
|
664
|
+
// Fire-and-forget on a separate pool connection (independent of the
|
|
665
|
+
// just-finished chunk transaction); never await — the chunk loop must
|
|
666
|
+
// not block. The end-of-cycle flushEmbeddingDirty remains as a safety
|
|
667
|
+
// net that sweeps any NULL embeddings this per-root path raced/missed.
|
|
668
|
+
syncRootEmbedding(db, rootId, { signal })
|
|
669
|
+
.catch((err) => __mixdogMemoryLog(`[cycle1] realtime embed failed (root=${rootId}): ${err.message}\n`))
|
|
649
670
|
} catch (err) {
|
|
650
671
|
__mixdogMemoryLog(`[cycle1] chunk commit failed (root=${rootId}): ${err.message}\n`)
|
|
651
672
|
skippedChunks += 1
|
|
@@ -8,7 +8,7 @@ import { existsSync, readFileSync } from 'fs'
|
|
|
8
8
|
import { join } from 'path'
|
|
9
9
|
import { fileURLToPath } from 'url'
|
|
10
10
|
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
11
|
-
import {
|
|
11
|
+
import { callAgentDispatch } from './agent-ipc.mjs'
|
|
12
12
|
import {
|
|
13
13
|
syncRootEmbedding, deleteRootEmbedding, flushEmbeddingDirty,
|
|
14
14
|
} from './memory-embed.mjs'
|
|
@@ -45,7 +45,7 @@ function resourceDir() {
|
|
|
45
45
|
return process.env.MIXDOG_ROOT || fileURLToPath(new URL('../../../..', import.meta.url))
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async function invokeLlm(prompt, mode, preset, timeout, llmCall =
|
|
48
|
+
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch) {
|
|
49
49
|
return await llmCall({
|
|
50
50
|
role: 'cycle2-agent',
|
|
51
51
|
taskType: 'maintenance',
|
|
@@ -339,7 +339,7 @@ function _pickKeeper(a, b) {
|
|
|
339
339
|
async function _llmJudgePair(summaryA, summaryB, siblingContext = [], options = {}) {
|
|
340
340
|
const signal = options?.signal
|
|
341
341
|
throwIfAborted(signal)
|
|
342
|
-
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm :
|
|
342
|
+
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callAgentDispatch
|
|
343
343
|
const siblings = Array.isArray(siblingContext) && siblingContext.length > 0
|
|
344
344
|
? `\n\nSibling near-matches (recall context only — do not absorb these into the verdict):\n${siblingContext.slice(0, 5).map((p, i) => `${i + 1}. ${String(p.a?.summary ?? '')} ↔ ${String(p.b?.summary ?? '')}`).join('\n')}`
|
|
345
345
|
: ''
|
|
@@ -521,7 +521,6 @@ export function loadCurrentRulesDigest() {
|
|
|
521
521
|
const now = Date.now()
|
|
522
522
|
if (_currentRulesDigest && now - _currentRulesDigestTs < 60_000) return _currentRulesDigest
|
|
523
523
|
const sources = [
|
|
524
|
-
join(resourceDir(), 'rules', 'shared', '00-language.md'),
|
|
525
524
|
join(resourceDir(), 'rules', 'shared', '01-general.md'),
|
|
526
525
|
join(resourceDir(), 'rules', 'shared', '01-tool.md'),
|
|
527
526
|
join(resourceDir(), 'rules', 'shared', '04-memory.md'),
|
|
@@ -805,7 +804,7 @@ async function sonnetCascade(candidates, rulesDigest, options = {}) {
|
|
|
805
804
|
// when no binding exists, which would defeat the cascade. SONNET HIGH
|
|
806
805
|
// matches the worker pool's default preset id from agent-config.
|
|
807
806
|
const preset = options.cascadePreset || 'SONNET HIGH'
|
|
808
|
-
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm :
|
|
807
|
+
const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callAgentDispatch
|
|
809
808
|
let raw
|
|
810
809
|
try {
|
|
811
810
|
raw = await llmCall({
|
|
@@ -901,7 +900,7 @@ export async function runCycle2(db, config = {}, options = {}, dataDir = null) {
|
|
|
901
900
|
}
|
|
902
901
|
if (_runCycle2InFlight.has(db)) {
|
|
903
902
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle2', 'in-flight', requestSignature)
|
|
904
|
-
scheduleRetry()
|
|
903
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
905
904
|
__mixdogMemoryLog('[cycle2] skipped: already in flight for this db\n')
|
|
906
905
|
return { ok: true, ...partial, skippedInFlight: true }
|
|
907
906
|
}
|
|
@@ -921,7 +920,7 @@ export async function runCycle2(db, config = {}, options = {}, dataDir = null) {
|
|
|
921
920
|
if (!gotLock) {
|
|
922
921
|
client.release()
|
|
923
922
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle2', 'advisory-lock', requestSignature)
|
|
924
|
-
scheduleRetry()
|
|
923
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
925
924
|
__mixdogMemoryLog('[cycle2] skipped: advisory lock held by another worker\n')
|
|
926
925
|
return { ok: true, ...partial, skippedInFlight: true }
|
|
927
926
|
}
|
|
@@ -23,7 +23,7 @@ import { existsSync, readFileSync } from 'fs'
|
|
|
23
23
|
import { join } from 'path'
|
|
24
24
|
import { fileURLToPath } from 'url'
|
|
25
25
|
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
26
|
-
import {
|
|
26
|
+
import { callAgentDispatch } from './agent-ipc.mjs'
|
|
27
27
|
import { listCore, editCore, deleteCore, CORE_SUMMARY_MAX } from './core-memory-store.mjs'
|
|
28
28
|
import { loadCurrentRulesDigest } from './memory-cycle2.mjs'
|
|
29
29
|
import { embedText } from './embedding-provider.mjs'
|
|
@@ -34,7 +34,7 @@ function resourceDir() {
|
|
|
34
34
|
return process.env.MIXDOG_ROOT || fileURLToPath(new URL('../../../..', import.meta.url))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async function invokeLlm(prompt, mode, preset, timeout, llmCall =
|
|
37
|
+
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callAgentDispatch) {
|
|
38
38
|
return await llmCall({
|
|
39
39
|
role: 'cycle3-agent',
|
|
40
40
|
taskType: 'maintenance',
|
|
@@ -272,7 +272,7 @@ export async function runCycle3(db, config, dataDir, options = {}) {
|
|
|
272
272
|
}
|
|
273
273
|
if (_runCycle3InFlight.has(db)) {
|
|
274
274
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle3', 'in-flight', requestSignature)
|
|
275
|
-
scheduleRetry()
|
|
275
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
276
276
|
__mixdogMemoryLog('[cycle3] skipped: already in flight for this db\n')
|
|
277
277
|
return { ...partial, skippedInFlight: true }
|
|
278
278
|
}
|
|
@@ -291,7 +291,7 @@ export async function runCycle3(db, config, dataDir, options = {}) {
|
|
|
291
291
|
if (!gotLock) {
|
|
292
292
|
client.release()
|
|
293
293
|
if (!coalescedRetry) await markCycleRequest(db, 'cycle3', 'advisory-lock', requestSignature)
|
|
294
|
-
scheduleRetry()
|
|
294
|
+
if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
|
|
295
295
|
__mixdogMemoryLog('[cycle3] skipped: advisory lock held by another worker\n')
|
|
296
296
|
return { ...partial, skippedInFlight: true }
|
|
297
297
|
}
|
|
@@ -7,6 +7,7 @@ function __mixdogMemoryLog(...args) {
|
|
|
7
7
|
import fs from 'node:fs'
|
|
8
8
|
import os from 'node:os'
|
|
9
9
|
import path from 'node:path'
|
|
10
|
+
import { mixdogHome } from '../../shared/plugin-paths.mjs'
|
|
10
11
|
|
|
11
12
|
function normalizeBackfillWindow(value) {
|
|
12
13
|
const normalized = String(value ?? 'all').trim().toLowerCase()
|
|
@@ -44,7 +45,7 @@ export async function countUnclassified(db) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function selectBackfillTranscripts({ sinceMs = null, limit = null, projectsRoot = null } = {}) {
|
|
47
|
-
const root = projectsRoot || path.join(
|
|
48
|
+
const root = projectsRoot || path.join(mixdogHome(), 'projects')
|
|
48
49
|
if (!fs.existsSync(root)) return []
|
|
49
50
|
const files = []
|
|
50
51
|
for (const d of fs.readdirSync(root)) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Pure, DB-free merge of session-fallback recall rows into the already-ranked
|
|
2
|
+
// global hybrid rows. Extracted into its own module so the starve-prevention +
|
|
3
|
+
// dedupe contract can be unit-tested without booting the memory worker (which
|
|
4
|
+
// has heavy top-level side effects: http server, pg, onnx embedding).
|
|
5
|
+
//
|
|
6
|
+
// Contract:
|
|
7
|
+
// - APPEND session rows, never prepend. Blind prepend lets session rows
|
|
8
|
+
// (limit+) starve the global first page and distorts sort/offset/limit.
|
|
9
|
+
// - Re-apply the SAME sort policy as the global path so global hybrid hits
|
|
10
|
+
// (carrying a real retrievalScore) keep their rank; session rows
|
|
11
|
+
// (retrievalScore 0 under importance, ts-ordered under date) only fill
|
|
12
|
+
// genuinely open slots.
|
|
13
|
+
// - Dedupe drops any session row whose id already appears as a global row OR
|
|
14
|
+
// as a global root's inlined chunk member (prevents member/leaf double
|
|
15
|
+
// output).
|
|
16
|
+
export function mergeSessionRowsIntoGlobal(globalRows, sessionRows, { sort = 'importance' } = {}) {
|
|
17
|
+
const filtered = Array.isArray(globalRows) ? [...globalRows] : []
|
|
18
|
+
if (!Array.isArray(sessionRows) || sessionRows.length === 0) return filtered
|
|
19
|
+
const seen = new Set(filtered.map(r => Number(r.id)))
|
|
20
|
+
for (const r of filtered) {
|
|
21
|
+
if (Array.isArray(r.members)) {
|
|
22
|
+
for (const m of r.members) seen.add(Number(m.id))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const merged = sessionRows.filter(r => !seen.has(Number(r.id)))
|
|
26
|
+
if (merged.length === 0) return filtered
|
|
27
|
+
const out = [...filtered, ...merged]
|
|
28
|
+
const sa = (v) => { const n = Number(v); return Number.isFinite(n) ? n : 0 }
|
|
29
|
+
if (sort === 'date') {
|
|
30
|
+
out.sort((a, b) => (Number(b.ts) || 0) - (Number(a.ts) || 0))
|
|
31
|
+
} else {
|
|
32
|
+
out.sort((a, b) => (sa(b.retrievalScore ?? b.rrf ?? 0) - sa(a.retrievalScore ?? a.rrf ?? 0))
|
|
33
|
+
|| (sa(b.score ?? 0) - sa(a.score ?? 0))
|
|
34
|
+
|| (sa(b.ts ?? 0) - sa(a.ts ?? 0))
|
|
35
|
+
|| (Number(a.id ?? 0) - Number(b.id ?? 0)))
|
|
36
|
+
}
|
|
37
|
+
return out
|
|
38
|
+
}
|