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
|
@@ -1,49 +1,136 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Agent-dispatch client for the memory process.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* memory → parent : { type: 'agent_ipc_request', callId, tool, params }
|
|
10
|
-
* parent → memory : { type: 'agent_ipc_response', callId, ok, result | error }
|
|
11
|
-
*
|
|
12
|
-
* Uses a module-level pending map keyed by callId. Parent-side handler
|
|
13
|
-
* lives in server.mjs spawnWorker's message listener.
|
|
4
|
+
* PG/embedding/recall remain isolated in the memory process, while the heavy
|
|
5
|
+
* provider/session graph lives exactly once in the machine-global daemon.
|
|
6
|
+
* daemon. Calls use its authenticated loopback broker, so the memory process
|
|
7
|
+
* can outlive its original fork parent and reconnect after a daemon restart.
|
|
14
8
|
*/
|
|
15
|
-
import
|
|
9
|
+
import http from 'node:http'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { tmpdir } from 'node:os'
|
|
12
|
+
import { join, resolve } from 'node:path'
|
|
16
13
|
|
|
17
|
-
const
|
|
18
|
-
|
|
14
|
+
const brokerAgent = new http.Agent({
|
|
15
|
+
keepAlive: true,
|
|
16
|
+
maxSockets: 64,
|
|
17
|
+
maxFreeSockets: 8,
|
|
18
|
+
})
|
|
19
19
|
let _idSeq = 0
|
|
20
20
|
|
|
21
|
-
function installListener() {
|
|
22
|
-
if (listenerInstalled) return
|
|
23
|
-
listenerInstalled = true
|
|
24
|
-
process.on('message', (msg) => {
|
|
25
|
-
if (!msg || msg.type !== 'agent_ipc_response' || !msg.callId) return
|
|
26
|
-
const entry = pending.get(msg.callId)
|
|
27
|
-
if (!entry) return
|
|
28
|
-
pending.delete(msg.callId)
|
|
29
|
-
if (msg.ok) entry.resolve(msg.result)
|
|
30
|
-
else entry.reject(new Error(msg.error || 'agent_ipc_response error'))
|
|
31
|
-
})
|
|
32
|
-
process.once('disconnect', () => {
|
|
33
|
-
const error = new Error('agent-ipc: parent IPC disconnected')
|
|
34
|
-
for (const [, entry] of pending) entry.reject(error)
|
|
35
|
-
pending.clear()
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
21
|
function nextCallId() {
|
|
40
22
|
_idSeq += 1
|
|
41
23
|
return `mem-${process.pid}-${Date.now()}-${_idSeq}`
|
|
42
24
|
}
|
|
43
25
|
|
|
26
|
+
function runtimeRoot() {
|
|
27
|
+
return process.env.MIXDOG_RUNTIME_ROOT
|
|
28
|
+
? resolve(process.env.MIXDOG_RUNTIME_ROOT)
|
|
29
|
+
: join(tmpdir(), 'mixdog')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isPidAlive(value) {
|
|
33
|
+
const pid = Number(value)
|
|
34
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
35
|
+
try { process.kill(pid, 0); return true } catch (error) { return error?.code === 'EPERM' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readBrokerDiscovery() {
|
|
39
|
+
try {
|
|
40
|
+
const raw = JSON.parse(readFileSync(join(runtimeRoot(), 'daemon.json'), 'utf8'))
|
|
41
|
+
const endpoint = raw?.endpoints?.channel
|
|
42
|
+
const port = Number(endpoint?.port)
|
|
43
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536 || !endpoint?.token || !isPidAlive(raw?.pid)) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
return { port, token: String(endpoint.token) }
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function abortError(signal) {
|
|
53
|
+
return signal?.reason instanceof Error
|
|
54
|
+
? signal.reason
|
|
55
|
+
: new Error(String(signal?.reason || 'agent broker request canceled'))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function requestBroker(discovery, path, body, { timeoutMs, signal = null } = {}) {
|
|
59
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
60
|
+
const payload = JSON.stringify(body || {})
|
|
61
|
+
let settled = false
|
|
62
|
+
let req = null
|
|
63
|
+
const finish = (fn, value) => {
|
|
64
|
+
if (settled) return
|
|
65
|
+
settled = true
|
|
66
|
+
clearTimeout(deadline)
|
|
67
|
+
try { signal?.removeEventListener?.('abort', onAbort) } catch {}
|
|
68
|
+
fn(value)
|
|
69
|
+
}
|
|
70
|
+
const reject = (error) => finish(
|
|
71
|
+
rejectPromise,
|
|
72
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
73
|
+
)
|
|
74
|
+
const onAbort = () => {
|
|
75
|
+
const error = abortError(signal)
|
|
76
|
+
try { req?.destroy?.(error) } catch {}
|
|
77
|
+
reject(error)
|
|
78
|
+
}
|
|
79
|
+
const deadline = setTimeout(() => {
|
|
80
|
+
const error = new Error(`agent broker request timed out after ${timeoutMs}ms`)
|
|
81
|
+
try { req?.destroy?.(error) } catch {}
|
|
82
|
+
reject(error)
|
|
83
|
+
}, timeoutMs)
|
|
84
|
+
deadline.unref?.()
|
|
85
|
+
if (signal?.aborted) {
|
|
86
|
+
onAbort()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
try { signal?.addEventListener?.('abort', onAbort, { once: true }) } catch {}
|
|
90
|
+
req = http.request({
|
|
91
|
+
hostname: '127.0.0.1',
|
|
92
|
+
port: discovery.port,
|
|
93
|
+
path,
|
|
94
|
+
method: 'POST',
|
|
95
|
+
agent: brokerAgent,
|
|
96
|
+
headers: {
|
|
97
|
+
'X-Mixdog-Daemon-Token': discovery.token,
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
100
|
+
},
|
|
101
|
+
}, (res) => {
|
|
102
|
+
let data = ''
|
|
103
|
+
res.setEncoding('utf8')
|
|
104
|
+
res.on('data', (chunk) => { data += chunk })
|
|
105
|
+
res.on('error', reject)
|
|
106
|
+
res.on('end', () => {
|
|
107
|
+
let parsed = null
|
|
108
|
+
try { parsed = data ? JSON.parse(data) : null } catch {}
|
|
109
|
+
if ((res.statusCode || 0) >= 400) {
|
|
110
|
+
reject(new Error(parsed?.error || data || `HTTP ${res.statusCode}`))
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
if (parsed?.ok === false) {
|
|
114
|
+
reject(new Error(parsed.error || 'agent broker dispatch failed'))
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
finish(resolvePromise, parsed)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
req.on('error', reject)
|
|
121
|
+
req.write(payload)
|
|
122
|
+
req.end()
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function cancelBrokerCall(discovery, callId, reason) {
|
|
127
|
+
return requestBroker(discovery, '/agent/cancel', { callId, reason }, {
|
|
128
|
+
timeoutMs: 1500,
|
|
129
|
+
}).catch(() => {})
|
|
130
|
+
}
|
|
131
|
+
|
|
44
132
|
/**
|
|
45
|
-
*
|
|
46
|
-
* unavailable (worker not forked) or the parent reports an error.
|
|
133
|
+
* Dispatch through the singleton daemon provider/session graph.
|
|
47
134
|
*
|
|
48
135
|
* @param {object} opts agent-dispatch construction options
|
|
49
136
|
* @param {string} [opts.agent]
|
|
@@ -55,55 +142,33 @@ function nextCallId() {
|
|
|
55
142
|
* @param {string} prompt user message
|
|
56
143
|
* @returns {Promise<string>} raw assistant content
|
|
57
144
|
*/
|
|
58
|
-
export function callAgentDispatch(opts = {}, prompt) {
|
|
59
|
-
|
|
60
|
-
|
|
145
|
+
export async function callAgentDispatch(opts = {}, prompt) {
|
|
146
|
+
const discovery = readBrokerDiscovery()
|
|
147
|
+
if (!discovery) {
|
|
148
|
+
throw new Error('agent-broker: daemon unavailable')
|
|
61
149
|
}
|
|
62
|
-
installListener()
|
|
63
150
|
const callId = nextCallId()
|
|
64
151
|
const timeoutMs = Math.max(1000, Number(opts.timeout ?? 600000))
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
152
|
+
try {
|
|
153
|
+
const response = await requestBroker(discovery, '/agent/dispatch', {
|
|
154
|
+
callId,
|
|
155
|
+
params: {
|
|
156
|
+
agent: opts.agent || null,
|
|
157
|
+
taskType: opts.taskType || null,
|
|
158
|
+
mode: opts.mode || null,
|
|
159
|
+
preset: opts.preset || null,
|
|
160
|
+
cwd: opts.cwd || null,
|
|
161
|
+
prompt: String(prompt ?? ''),
|
|
162
|
+
timeout: timeoutMs,
|
|
163
|
+
},
|
|
164
|
+
}, {
|
|
165
|
+
// Small transport grace after the dispatch's own timeout/watchdog.
|
|
166
|
+
timeoutMs: timeoutMs + 5000,
|
|
167
|
+
signal: opts.signal || null,
|
|
77
168
|
})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
// Pass a write callback so a closed-channel write surfaces as an async
|
|
85
|
-
// error here (rejecting this Promise) instead of bubbling to the process
|
|
86
|
-
// 'error' event and crashing the daemon with ERR_IPC_CHANNEL_CLOSED.
|
|
87
|
-
safeIpcSend(process, {
|
|
88
|
-
type: 'agent_ipc_request',
|
|
89
|
-
callId,
|
|
90
|
-
tool: 'agent_dispatch',
|
|
91
|
-
params: {
|
|
92
|
-
agent: opts.agent || null,
|
|
93
|
-
taskType: opts.taskType || null,
|
|
94
|
-
mode: opts.mode || null,
|
|
95
|
-
preset: opts.preset || null,
|
|
96
|
-
cwd: opts.cwd || null,
|
|
97
|
-
prompt: String(prompt ?? ''),
|
|
98
|
-
timeout: timeoutMs,
|
|
99
|
-
},
|
|
100
|
-
}, {
|
|
101
|
-
onError: (err) => {
|
|
102
|
-
if (!pending.has(callId)) return
|
|
103
|
-
pending.delete(callId)
|
|
104
|
-
clearTimeout(timer)
|
|
105
|
-
reject(err)
|
|
106
|
-
},
|
|
107
|
-
})
|
|
108
|
-
})
|
|
169
|
+
return String(response?.result ?? '')
|
|
170
|
+
} catch (error) {
|
|
171
|
+
await cancelBrokerCall(discovery, callId, error?.message || 'agent broker request failed')
|
|
172
|
+
throw error
|
|
173
|
+
}
|
|
109
174
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const DEFAULT_MAX_ENTRIES = 1000
|
|
2
|
+
const DEFAULT_MAX_BYTES = 8 * 1024 * 1024
|
|
3
|
+
|
|
4
|
+
function entryBytes(key, vector) {
|
|
5
|
+
// Vector storage is exact (Float32Array.byteLength). UTF-16 key bytes plus a
|
|
6
|
+
// small fixed allowance bound Map/string/object overhead conservatively.
|
|
7
|
+
return vector.byteLength + String(key).length * 2 + 64
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LRU cache specialized for embedding vectors.
|
|
12
|
+
*
|
|
13
|
+
* Worker results are semantically float32 but arrive over structured clone as
|
|
14
|
+
* JS number[]. Keeping 1,000 such arrays stores every component as a wider JS
|
|
15
|
+
* Number. Compact once on insertion; callers still receive normal arrays, so
|
|
16
|
+
* the embedding provider's public contract and PG serialization stay intact.
|
|
17
|
+
*/
|
|
18
|
+
export function createCompactVectorCache({
|
|
19
|
+
maxEntries = DEFAULT_MAX_ENTRIES,
|
|
20
|
+
maxBytes = DEFAULT_MAX_BYTES,
|
|
21
|
+
} = {}) {
|
|
22
|
+
const entryLimit = Math.max(1, Math.floor(Number(maxEntries) || DEFAULT_MAX_ENTRIES))
|
|
23
|
+
const byteLimit = Math.max(1024, Math.floor(Number(maxBytes) || DEFAULT_MAX_BYTES))
|
|
24
|
+
const entries = new Map()
|
|
25
|
+
let bytes = 0
|
|
26
|
+
|
|
27
|
+
function remove(key) {
|
|
28
|
+
const current = entries.get(key)
|
|
29
|
+
if (!current) return false
|
|
30
|
+
entries.delete(key)
|
|
31
|
+
bytes = Math.max(0, bytes - current.bytes)
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function trim() {
|
|
36
|
+
while (entries.size > entryLimit || bytes > byteLimit) {
|
|
37
|
+
const oldest = entries.keys().next().value
|
|
38
|
+
if (oldest === undefined) break
|
|
39
|
+
remove(oldest)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function set(key, input) {
|
|
44
|
+
if (!Array.isArray(input) && !ArrayBuffer.isView(input)) {
|
|
45
|
+
throw new TypeError('embedding cache vector must be an array or typed array')
|
|
46
|
+
}
|
|
47
|
+
const vector = input instanceof Float32Array ? input.slice() : Float32Array.from(input)
|
|
48
|
+
remove(key)
|
|
49
|
+
const size = entryBytes(key, vector)
|
|
50
|
+
// A single vector larger than the whole budget is useful to its caller but
|
|
51
|
+
// must not permanently violate the cache bound.
|
|
52
|
+
if (size > byteLimit) return false
|
|
53
|
+
entries.set(key, { vector, bytes: size })
|
|
54
|
+
bytes += size
|
|
55
|
+
trim()
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function get(key) {
|
|
60
|
+
const current = entries.get(key)
|
|
61
|
+
if (!current) return null
|
|
62
|
+
entries.delete(key)
|
|
63
|
+
entries.set(key, current)
|
|
64
|
+
return current.vector
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function clear() {
|
|
68
|
+
entries.clear()
|
|
69
|
+
bytes = 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function snapshot() {
|
|
73
|
+
return {
|
|
74
|
+
entries: entries.size,
|
|
75
|
+
bytes,
|
|
76
|
+
maxEntries: entryLimit,
|
|
77
|
+
maxBytes: byteLimit,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
get,
|
|
83
|
+
set,
|
|
84
|
+
has: (key) => entries.has(key),
|
|
85
|
+
clear,
|
|
86
|
+
snapshot,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -29,6 +29,20 @@ export const CORE_DEDUP_TOP_K = 5
|
|
|
29
29
|
|
|
30
30
|
const CORE_ELEMENT_DERIVE_LENGTH = 40
|
|
31
31
|
|
|
32
|
+
// Op aliases: the surface verbs are add/edit/delete/list/…, and a caller
|
|
33
|
+
// naming the same intent differently ("update", "remove") used to get a hard
|
|
34
|
+
// rejection. Only unambiguous synonyms map through.
|
|
35
|
+
const CORE_OP_ALIASES = {
|
|
36
|
+
update: 'edit', modify: 'edit', change: 'edit', set: 'edit',
|
|
37
|
+
create: 'add', new: 'add', insert: 'add', store: 'add', save: 'add',
|
|
38
|
+
remove: 'delete', rm: 'delete', del: 'delete', forget: 'delete',
|
|
39
|
+
show: 'list', get: 'list', read: 'list',
|
|
40
|
+
}
|
|
41
|
+
export function normalizeCoreOp(op) {
|
|
42
|
+
const raw = String(op ?? '').trim().toLowerCase()
|
|
43
|
+
return CORE_OP_ALIASES[raw] ?? raw
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
function trimOrNull(v) {
|
|
33
47
|
if (v == null) return null
|
|
34
48
|
const s = String(v).trim()
|
|
@@ -36,7 +50,10 @@ function trimOrNull(v) {
|
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
export function normalizeCoreInput(input = {}, options = {}) {
|
|
39
|
-
|
|
53
|
+
// Summary aliases: the field is `summary`, but callers routinely send the
|
|
54
|
+
// synonym they'd use in prose. Accepting them costs nothing and removes a
|
|
55
|
+
// measured class of bounced calls.
|
|
56
|
+
const summary = trimOrNull(input.summary ?? input.content ?? input.text ?? input.value ?? input.note)
|
|
40
57
|
const element = trimOrNull(input.element) ?? (summary ? summary.slice(0, CORE_ELEMENT_DERIVE_LENGTH) : null)
|
|
41
58
|
const suppliedCategory = trimOrNull(input.category)
|
|
42
59
|
const category = (suppliedCategory ?? 'fact').toLowerCase()
|
|
@@ -1,43 +1,22 @@
|
|
|
1
1
|
// Cycle LLM dispatch adapters, extracted from index.mjs (pass 3).
|
|
2
2
|
//
|
|
3
|
-
// Each cycleN scheduler/handler routes its LLM work through
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// cycle
|
|
7
|
-
// and reshaped to the cycle call signature:
|
|
3
|
+
// Each cycleN scheduler/handler routes its LLM work through the provider's
|
|
4
|
+
// process-singleton agent broker. The memory process owns PG/embeddings only;
|
|
5
|
+
// provider/session modules are not duplicated here. The factory fixes the
|
|
6
|
+
// cycle role/taskType and reshapes to the cycle call signature:
|
|
8
7
|
// callLlm({ role, taskType, mode, preset, timeout, cwd }, userMessage) -> string
|
|
9
|
-
// while
|
|
10
|
-
// adapter maps the two — preset/cwd resolution is handled inside
|
|
11
|
-
// makeAgentDispatch via role.
|
|
8
|
+
// while callAgentDispatch takes (opts, prompt).
|
|
12
9
|
//
|
|
13
|
-
// Pure factory: index.mjs injects
|
|
10
|
+
// Pure factory: index.mjs injects callAgentDispatch. No import-time side
|
|
14
11
|
// effects, no db/config coupling.
|
|
15
|
-
export function createCycleLlmAdapters({
|
|
12
|
+
export function createCycleLlmAdapters({ callAgentDispatch }) {
|
|
16
13
|
function buildAdapter(agent) {
|
|
17
|
-
let dispatch = null
|
|
18
14
|
return async (opts = {}, userMessage) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// The cycle agents parse the full raw line-format response; the
|
|
25
|
-
// agent brief cap (12KB) would truncate a large valid response and
|
|
26
|
-
// append prose, causing partial parsing / omitted / invalid chunks.
|
|
27
|
-
// Opt out so the no-truncation contract is preserved.
|
|
28
|
-
brief: false,
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
// Preserve the cycle timeout contract: the caller derives opts.timeout
|
|
32
|
-
// from config / caller deadline and expects it to bound the call.
|
|
33
|
-
// makeAgentDispatch takes it as a per-call idleTimeoutMs (stale
|
|
34
|
-
// watchdog). Map it through; omit when absent/0 so agent defaults apply.
|
|
35
|
-
const callTimeout = Number(opts?.timeout)
|
|
36
|
-
return dispatch({
|
|
37
|
-
prompt: String(userMessage ?? ''),
|
|
38
|
-
preset: opts?.preset || undefined,
|
|
39
|
-
...(Number.isFinite(callTimeout) && callTimeout > 0 ? { idleTimeoutMs: callTimeout } : {}),
|
|
40
|
-
})
|
|
15
|
+
return callAgentDispatch({
|
|
16
|
+
...opts,
|
|
17
|
+
agent,
|
|
18
|
+
taskType: 'maintenance',
|
|
19
|
+
}, String(userMessage ?? ''))
|
|
41
20
|
}
|
|
42
21
|
}
|
|
43
22
|
|
|
@@ -45,7 +24,7 @@ export function createCycleLlmAdapters({ makeAgentDispatch }) {
|
|
|
45
24
|
// treat these as FACTORIES: call once to obtain the callLlm adapter.
|
|
46
25
|
// Returning the adapter directly here made `getCycle1CallLlm()` execute an
|
|
47
26
|
// LLM dispatch with no arguments — empty prompt → agent-dispatch throw →
|
|
48
|
-
// unhandled rejection that killed the memory
|
|
27
|
+
// unhandled rejection that killed the memory runtime. Keep the factory
|
|
49
28
|
// contract: each getter returns the (memoized) adapter function.
|
|
50
29
|
const cycle1 = buildAdapter('cycle1-agent')
|
|
51
30
|
const cycle2 = buildAdapter('cycle2-agent')
|
|
@@ -8,6 +8,7 @@ import { Worker } from 'worker_threads'
|
|
|
8
8
|
import { join } from 'path'
|
|
9
9
|
import { fileURLToPath } from 'url'
|
|
10
10
|
import { writeProfilePoint } from './model-profile.mjs'
|
|
11
|
+
import { createCompactVectorCache } from './compact-vector-cache.mjs'
|
|
11
12
|
import {
|
|
12
13
|
getConfiguredEmbeddingModelId,
|
|
13
14
|
getDefaultEmbeddingDtype,
|
|
@@ -33,27 +34,21 @@ let _warmupPromise = null
|
|
|
33
34
|
let _embedCallCount = 0
|
|
34
35
|
let _msgId = 0
|
|
35
36
|
const _pending = new Map()
|
|
37
|
+
const retiringWorkers = new WeakSet()
|
|
36
38
|
const EMBED_STEADY_SAMPLE_EVERY = 20
|
|
37
|
-
const queryEmbeddingCache =
|
|
38
|
-
|
|
39
|
+
const queryEmbeddingCache = createCompactVectorCache({
|
|
40
|
+
maxEntries: 1000,
|
|
41
|
+
maxBytes: 8 * 1024 * 1024,
|
|
42
|
+
})
|
|
39
43
|
|
|
40
44
|
const WORKER_PATH = join(fileURLToPath(import.meta.url), '..', 'embedding-worker.mjs')
|
|
41
45
|
|
|
42
46
|
function cacheEmbedding(key, vector) {
|
|
43
|
-
if (queryEmbeddingCache.has(key)) queryEmbeddingCache.delete(key)
|
|
44
47
|
queryEmbeddingCache.set(key, vector)
|
|
45
|
-
if (queryEmbeddingCache.size > QUERY_EMBEDDING_CACHE_LIMIT) {
|
|
46
|
-
const oldestKey = queryEmbeddingCache.keys().next().value
|
|
47
|
-
if (oldestKey) queryEmbeddingCache.delete(oldestKey)
|
|
48
|
-
}
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
function getCachedEmbedding(key) {
|
|
52
|
-
|
|
53
|
-
const value = queryEmbeddingCache.get(key)
|
|
54
|
-
queryEmbeddingCache.delete(key)
|
|
55
|
-
queryEmbeddingCache.set(key, value)
|
|
56
|
-
return value
|
|
51
|
+
return queryEmbeddingCache.get(key)
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
function ensureWorker() {
|
|
@@ -68,8 +63,16 @@ function ensureWorker() {
|
|
|
68
63
|
}
|
|
69
64
|
_lastRestartMs = now
|
|
70
65
|
const execArgv = process.execArgv.filter((arg) => !String(arg).startsWith('--input-type'))
|
|
71
|
-
|
|
72
|
-
worker
|
|
66
|
+
const created = new Worker(WORKER_PATH, { env: { ...process.env }, execArgv })
|
|
67
|
+
worker = created
|
|
68
|
+
const rejectWorkerPending = (error) => {
|
|
69
|
+
for (const [id, pending] of _pending) {
|
|
70
|
+
if (pending.worker !== created) continue
|
|
71
|
+
_pending.delete(id)
|
|
72
|
+
pending.reject(error)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
created.on('message', (msg) => {
|
|
73
76
|
if (msg.type === 'log') {
|
|
74
77
|
// Worker-thread stdio forwarded via IPC (see embedding-worker.mjs
|
|
75
78
|
// header): route through the parent's guardable stderr so TUI runs
|
|
@@ -85,8 +88,25 @@ function ensureWorker() {
|
|
|
85
88
|
_modelReady = false
|
|
86
89
|
_device = 'cpu'
|
|
87
90
|
const reason = String(msg.reason || 'idle timeout')
|
|
88
|
-
__mixdogMemoryLog(`[embed] ${reason} — model disposed\n`)
|
|
91
|
+
__mixdogMemoryLog(`[embed] ${reason} — model disposed; retiring worker thread\n`)
|
|
89
92
|
writeProfilePoint({ phase: 'post-idle', model: MODEL_ID, device: msg.device, dtype: msg.dtype, note: reason })
|
|
93
|
+
// A request can be posted after the worker's idle timer starts disposal
|
|
94
|
+
// but before this notification reaches the parent. Do not terminate that
|
|
95
|
+
// worker out from under the accepted request; its next idle cycle will
|
|
96
|
+
// retire it after the request finishes.
|
|
97
|
+
const hasAcceptedRequest = [..._pending.values()]
|
|
98
|
+
.some((pending) => pending.worker === created)
|
|
99
|
+
if (hasAcceptedRequest) {
|
|
100
|
+
__mixdogMemoryLog(`[embed] ${reason} — worker retirement deferred for accepted request\n`)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
// ORT's native allocator does not reliably return model pages when only
|
|
104
|
+
// InferenceSession.dispose() runs on Windows. Ending the worker thread is
|
|
105
|
+
// the actual native-memory boundary; the next recall lazily creates a
|
|
106
|
+
// fresh worker while cached dimensions remain available.
|
|
107
|
+
if (worker === created) worker = null
|
|
108
|
+
retiringWorkers.add(created)
|
|
109
|
+
void created.terminate().catch(() => {})
|
|
90
110
|
return
|
|
91
111
|
}
|
|
92
112
|
const pending = _pending.get(msg.id)
|
|
@@ -98,28 +118,28 @@ function ensureWorker() {
|
|
|
98
118
|
pending.resolve(msg)
|
|
99
119
|
}
|
|
100
120
|
})
|
|
101
|
-
|
|
121
|
+
created.on('error', (err) => {
|
|
102
122
|
__mixdogMemoryLog(`[embed] worker error: ${err?.message || err}\n`)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
worker = null
|
|
123
|
+
rejectWorkerPending(err)
|
|
124
|
+
if (worker === created) worker = null
|
|
106
125
|
_modelReady = false
|
|
107
126
|
_restartCount++
|
|
108
127
|
})
|
|
109
|
-
|
|
128
|
+
created.on('exit', (code) => {
|
|
129
|
+
const retired = retiringWorkers.has(created)
|
|
130
|
+
retiringWorkers.delete(created)
|
|
110
131
|
const exitError = new Error(`Worker exited with code ${code}`)
|
|
111
|
-
if (code !== 0) {
|
|
132
|
+
if (!retired && code !== 0) {
|
|
112
133
|
__mixdogMemoryLog(`[embed] worker exited with code ${code}\n`)
|
|
113
134
|
_restartCount++
|
|
114
|
-
} else {
|
|
135
|
+
} else if (!retired) {
|
|
115
136
|
_restartCount = 0
|
|
116
137
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
worker = null
|
|
138
|
+
rejectWorkerPending(exitError)
|
|
139
|
+
if (worker === created) worker = null
|
|
120
140
|
_modelReady = false
|
|
121
141
|
})
|
|
122
|
-
return
|
|
142
|
+
return created
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
const EMBED_WORKER_TIMEOUT_MS = 60_000
|
|
@@ -140,6 +160,7 @@ function sendToWorker(action, extra = {}, timeoutMs = EMBED_WORKER_TIMEOUT_MS) {
|
|
|
140
160
|
reject(new Error(`embed worker ${action} timed out after ${timeoutMs}ms`))
|
|
141
161
|
}, timeoutMs)
|
|
142
162
|
_pending.set(id, {
|
|
163
|
+
worker: w,
|
|
143
164
|
resolve: (v) => { clearTimeout(timer); resolve(v) },
|
|
144
165
|
reject: (e) => { clearTimeout(timer); reject(e) },
|
|
145
166
|
})
|