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,25 +1,22 @@
|
|
|
1
|
-
// Machine-global
|
|
1
|
+
// Machine-global Mixdog daemon entry — one process for sessions, channels, and memory.
|
|
2
2
|
//
|
|
3
|
-
// One process per machine hosts the whole
|
|
4
|
-
// (worker-main), the memory runtime, and the session
|
|
5
|
-
// two local front doors — channel
|
|
6
|
-
//
|
|
3
|
+
// One process per machine hosts the whole service: the channels runtime
|
|
4
|
+
// (worker-main), the memory runtime, and the session runtime pool. It exposes
|
|
5
|
+
// two local front doors — channel transport (pointer-routed calls) and session
|
|
6
|
+
// transport (broadcast session frames) — so the
|
|
7
7
|
// terminal TUI and the desktop app are views over ONE writer instead of each
|
|
8
|
-
// booting
|
|
8
|
+
// booting a separate service and arbitrating ownership on disk.
|
|
9
9
|
// Spawned (or attached-to) by createStandaloneChannelWorker; ownership is a
|
|
10
10
|
// pid-verified singleton lock (singleton-owner.mjs) — NOT the try-once
|
|
11
11
|
// active-instance lock that starved under 6 contending workers. A stale daemon
|
|
12
12
|
// (dead owner pid) is reclaimed by the next claim; a live peer that wins the
|
|
13
13
|
// race makes this process exit(0) so the spawner attaches to the winner.
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// lets this entry own start()/stop() + the transport.
|
|
18
|
-
process.env.MIXDOG_CHANNEL_DAEMON = '1';
|
|
15
|
+
// The unified host identity is set before the channels runtime is loaded so
|
|
16
|
+
// worker-main skips its parent-IPC loop and this entry owns start()/stop().
|
|
19
17
|
process.env.MIXDOG_WORKER_MODE = process.env.MIXDOG_WORKER_MODE || '1';
|
|
20
|
-
// This process
|
|
21
|
-
|
|
22
|
-
process.env.MIXDOG_ENGINE_DAEMON_HOST = '1';
|
|
18
|
+
// This process owns session runtimes and must never proxy back into itself.
|
|
19
|
+
process.env.MIXDOG_DAEMON_HOST = '1';
|
|
23
20
|
|
|
24
21
|
// V8 compile cache: the daemon is a standalone child entry (not via cli.mjs);
|
|
25
22
|
// caching compiled bytecode across restarts removes the channels+memory
|
|
@@ -31,21 +28,46 @@ try {
|
|
|
31
28
|
|
|
32
29
|
import os from 'node:os';
|
|
33
30
|
import path from 'node:path';
|
|
34
|
-
import { mkdirSync } from 'node:fs';
|
|
31
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
35
32
|
import { appendFile, mkdir, open, stat, writeFile } from 'node:fs/promises';
|
|
36
33
|
import { fileURLToPath } from 'node:url';
|
|
37
34
|
import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
38
35
|
import { inspect } from 'node:util';
|
|
36
|
+
import { writeJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
|
|
37
|
+
import { ensureProcessListenerHeadroom } from '../runtime/shared/process-listener-headroom.mjs';
|
|
39
38
|
import { claimSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
|
|
40
39
|
import { PLUGIN_LOG_MAX_BYTES, PLUGIN_LOG_KEEP_BYTES } from '../lib/mixdog-debug.cjs';
|
|
41
40
|
import { setChannelNotifySink } from '../runtime/channels/lib/parent-bridge.mjs';
|
|
42
41
|
import { setOwnerContext } from '../runtime/channels/lib/runtime-paths.mjs';
|
|
43
42
|
import { safeIpcSend } from '../runtime/shared/safe-ipc-send.mjs';
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
43
|
+
import { resourceAdmission } from '../runtime/shared/resource-admission.mjs';
|
|
44
|
+
import { snapshot as childSpawnSnapshot } from '../runtime/shared/child-spawn-gate.mjs';
|
|
45
|
+
import { toolWorkloadSnapshot } from '../runtime/shared/tool-workload-gates.mjs';
|
|
46
|
+
import { providerAdmissionScheduler } from '../runtime/agent/orchestrator/providers/admission-scheduler.mjs';
|
|
47
|
+
import { getMcpAdmissionSnapshot } from '../runtime/agent/orchestrator/mcp/client.mjs';
|
|
48
|
+
import { loadConfig as loadAgentConfig } from '../runtime/agent/orchestrator/config.mjs';
|
|
49
|
+
import { initProviders } from '../runtime/agent/orchestrator/providers/registry.mjs';
|
|
50
|
+
import { makeAgentDispatch } from '../runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs';
|
|
51
|
+
import {
|
|
52
|
+
closeProviderStreamJsonPool,
|
|
53
|
+
providerStreamJsonSnapshot,
|
|
54
|
+
} from '../runtime/agent/orchestrator/providers/stream-json-pool.mjs';
|
|
55
|
+
import { createAgentDispatchBroker } from './agent-dispatch-broker.mjs';
|
|
56
|
+
import { createChannelTransport } from './channel-transport.mjs';
|
|
57
|
+
import { createSessionTransport } from './session-transport.mjs';
|
|
58
|
+
import { createSessionService } from './session-service.mjs';
|
|
59
|
+
import { createSessionProtocolClient } from './session-protocol.mjs';
|
|
60
|
+
import { createSessionRuntimePool } from './session-runtime-pool.mjs';
|
|
61
|
+
import { getStandaloneMemoryRuntime } from './memory-runtime-proxy.mjs';
|
|
62
|
+
import {
|
|
63
|
+
compareRuntimeVersions,
|
|
64
|
+
SESSION_CAPABILITY_FINGERPRINT,
|
|
65
|
+
SESSION_PROTOCOL,
|
|
66
|
+
SESSION_REVISION,
|
|
67
|
+
runtimeVersion,
|
|
68
|
+
} from './session-wire.mjs';
|
|
69
|
+
|
|
70
|
+
ensureProcessListenerHeadroom(64);
|
|
49
71
|
|
|
50
72
|
function runtimeRoot() {
|
|
51
73
|
return process.env.MIXDOG_RUNTIME_ROOT
|
|
@@ -54,28 +76,21 @@ function runtimeRoot() {
|
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
const RUNTIME_ROOT = runtimeRoot();
|
|
57
|
-
const DATA_DIR = process.env.MIXDOG_DATA_DIR
|
|
79
|
+
const DATA_DIR = process.env.MIXDOG_DATA_DIR
|
|
80
|
+
? path.resolve(process.env.MIXDOG_DATA_DIR)
|
|
81
|
+
: path.join(process.env.MIXDOG_HOME || path.join(os.homedir(), '.mixdog'), 'data');
|
|
82
|
+
process.env.MIXDOG_DATA_DIR = DATA_DIR;
|
|
83
|
+
process.env.MIXDOG_SERVER_PID = String(process.pid);
|
|
58
84
|
const CWD = process.cwd();
|
|
59
|
-
const
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
// are broadcast — they must not share a router).
|
|
63
|
-
const ENGINE_DISCOVERY_PATH = path.join(RUNTIME_ROOT, 'engine-daemon.json');
|
|
64
|
-
// Owner-election lock, separate from the channels seat/bridge state. Reused
|
|
65
|
-
// pid-verified claim primitive with real claim-lock retry inside.
|
|
66
|
-
const OWNER_PATH = path.join(DATA_DIR, 'channel-daemon-owner.json');
|
|
67
|
-
// Memory runtime is folded into the daemon: the ONE machine-global process is
|
|
68
|
-
// responsible for starting BOTH the channels runtime and the memory runtime.
|
|
69
|
-
// The memory proxy is spawn-or-attach + singleton (memory-runtime-owner lock)
|
|
70
|
-
// and advertises memory_port to active-instance.json exactly as before, so
|
|
71
|
-
// external readers / memory-client.mjs discovery is UNCHANGED.
|
|
85
|
+
const DAEMON_DISCOVERY_PATH = path.join(RUNTIME_ROOT, 'daemon.json');
|
|
86
|
+
// Owner-election lock, separate from the channels seat/bridge state.
|
|
87
|
+
const OWNER_PATH = path.join(DATA_DIR, 'daemon-owner.json');
|
|
72
88
|
const MEMORY_ENTRY = fileURLToPath(new URL('../runtime/memory/index.mjs', import.meta.url));
|
|
73
|
-
|
|
74
89
|
// The spawning TUI mirrors our stderr into this file ONLY until it sees our
|
|
75
90
|
// 'ready' message; after that its pipe consumer dies on parent exit and later
|
|
76
91
|
// lines would be lost. So once ready we append here ourselves (fileLogging on),
|
|
77
92
|
// keyed to the SAME ready event the spawner detaches on — no loss, no dup.
|
|
78
|
-
const LOG_PATH = path.join(DATA_DIR, '
|
|
93
|
+
const LOG_PATH = path.join(DATA_DIR, 'daemon.log');
|
|
79
94
|
let fileLogging = false;
|
|
80
95
|
// This process outlives every spawner, so it has to bound its OWN log: the
|
|
81
96
|
// spawner's boot-time rotate never runs again while the daemon lives, and the
|
|
@@ -87,6 +102,7 @@ let logQueueBytes = 0;
|
|
|
87
102
|
let logDropped = 0;
|
|
88
103
|
let logFlushTimer = null;
|
|
89
104
|
let logWriter = Promise.resolve();
|
|
105
|
+
let logFileBytes = null;
|
|
90
106
|
|
|
91
107
|
function boundedLogText(value) {
|
|
92
108
|
const text = String(value ?? '');
|
|
@@ -95,15 +111,18 @@ function boundedLogText(value) {
|
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
async function rotateDaemonLogIfNeeded(incomingBytes) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
if (logFileBytes === null) {
|
|
115
|
+
try { logFileBytes = (await stat(LOG_PATH)).size; }
|
|
116
|
+
catch { logFileBytes = 0; }
|
|
117
|
+
}
|
|
118
|
+
if (logFileBytes + incomingBytes <= PLUGIN_LOG_MAX_BYTES) return;
|
|
119
|
+
const keep = Math.min(logFileBytes, PLUGIN_LOG_KEEP_BYTES);
|
|
102
120
|
const tail = Buffer.allocUnsafe(keep);
|
|
103
121
|
const handle = await open(LOG_PATH, 'r');
|
|
104
122
|
try {
|
|
105
|
-
const { bytesRead } = await handle.read(tail, 0, keep, Math.max(0,
|
|
123
|
+
const { bytesRead } = await handle.read(tail, 0, keep, Math.max(0, logFileBytes - keep));
|
|
106
124
|
await writeFile(LOG_PATH, tail.subarray(0, bytesRead));
|
|
125
|
+
logFileBytes = bytesRead;
|
|
107
126
|
} finally {
|
|
108
127
|
await handle.close().catch(() => {});
|
|
109
128
|
}
|
|
@@ -117,7 +136,7 @@ function takeLogBatch() {
|
|
|
117
136
|
logQueueBytes = 0;
|
|
118
137
|
logDropped = 0;
|
|
119
138
|
if (dropped > 0) {
|
|
120
|
-
rows.unshift(`[${new Date().toISOString()}] [
|
|
139
|
+
rows.unshift(`[${new Date().toISOString()}] [daemon] dropped ${dropped} log line(s) under backpressure\n`);
|
|
121
140
|
}
|
|
122
141
|
return rows.join('');
|
|
123
142
|
}
|
|
@@ -132,6 +151,7 @@ function queueLogFlush(delayMs = 10) {
|
|
|
132
151
|
await mkdir(path.dirname(LOG_PATH), { recursive: true });
|
|
133
152
|
await rotateDaemonLogIfNeeded(Buffer.byteLength(batch));
|
|
134
153
|
await appendFile(LOG_PATH, batch, 'utf8');
|
|
154
|
+
logFileBytes = (logFileBytes || 0) + Buffer.byteLength(batch);
|
|
135
155
|
}).catch(() => {});
|
|
136
156
|
if (logQueue.length > 0 || logDropped > 0) queueLogFlush();
|
|
137
157
|
}, delayMs);
|
|
@@ -162,12 +182,13 @@ async function flushDaemonLogs() {
|
|
|
162
182
|
await mkdir(path.dirname(LOG_PATH), { recursive: true });
|
|
163
183
|
await rotateDaemonLogIfNeeded(Buffer.byteLength(batch));
|
|
164
184
|
await appendFile(LOG_PATH, batch, 'utf8');
|
|
185
|
+
logFileBytes = (logFileBytes || 0) + Buffer.byteLength(batch);
|
|
165
186
|
}).catch(() => {});
|
|
166
187
|
}
|
|
167
188
|
await logWriter;
|
|
168
189
|
}
|
|
169
190
|
function log(line) {
|
|
170
|
-
const text = `[
|
|
191
|
+
const text = `[daemon] ${line}`;
|
|
171
192
|
// Exactly ONE sink per line: before ready the spawner mirrors our stderr into
|
|
172
193
|
// the log, so write stderr only; after ready we own the file, so write the
|
|
173
194
|
// file only — never both (no duplicate around the ready handoff).
|
|
@@ -213,13 +234,15 @@ function installDaemonLogRedirect() {
|
|
|
213
234
|
|
|
214
235
|
let channels = null;
|
|
215
236
|
let transport = null;
|
|
216
|
-
let
|
|
217
|
-
let
|
|
218
|
-
let
|
|
219
|
-
let
|
|
237
|
+
let sessionTransport = null;
|
|
238
|
+
let sessionService = null;
|
|
239
|
+
let sessionRuntimePool = null;
|
|
240
|
+
let localSessionBridge = null;
|
|
220
241
|
let memoryRuntime = null;
|
|
242
|
+
let agentDispatchBroker = null;
|
|
221
243
|
let shuttingDown = false;
|
|
222
244
|
let shutdownRecheckTimer = null;
|
|
245
|
+
let replacementRequested = null;
|
|
223
246
|
const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
|
|
224
247
|
eventLoopDelay.enable();
|
|
225
248
|
let eventLoopLagTimer = null;
|
|
@@ -242,45 +265,68 @@ async function shutdown(reason, code = 0) {
|
|
|
242
265
|
}
|
|
243
266
|
log(`shutting down (${reason})`);
|
|
244
267
|
try { setChannelNotifySink(null); } catch {}
|
|
245
|
-
try { await
|
|
246
|
-
try { await
|
|
247
|
-
try {
|
|
248
|
-
|
|
249
|
-
try { await engineTransport?.stop?.(); } catch (e) { log(`engine transport.stop failed: ${e?.message || e}`); }
|
|
268
|
+
try { await sessionService?.stop?.(reason); } catch (e) { log(`session service stop failed: ${e?.message || e}`); }
|
|
269
|
+
try { await sessionRuntimePool?.close?.(reason); } catch (e) { log(`session shard pool stop failed: ${e?.message || e}`); }
|
|
270
|
+
try { await localSessionBridge?.close?.(reason); } catch (e) { log(`local session bridge.close failed: ${e?.message || e}`); }
|
|
271
|
+
try { await sessionTransport?.stop?.(); } catch (e) { log(`session transport stop failed: ${e?.message || e}`); }
|
|
250
272
|
try { await channels?.stop?.(); } catch (e) { log(`channels.stop failed: ${e?.message || e}`); }
|
|
251
|
-
//
|
|
273
|
+
// The daemon only releases its client registration. The isolated memory
|
|
274
|
+
// process shuts down after its last live client disappears.
|
|
252
275
|
try { await memoryRuntime?.stop?.(); } catch (e) { log(`memory.stop failed: ${e?.message || e}`); }
|
|
276
|
+
try { agentDispatchBroker?.close?.(reason); } catch (e) { log(`agent broker stop failed: ${e?.message || e}`); }
|
|
277
|
+
try { await closeProviderStreamJsonPool(reason); } catch (e) { log(`stream parser stop failed: ${e?.message || e}`); }
|
|
253
278
|
try { await transport?.stop?.(); } catch (e) { log(`transport.stop failed: ${e?.message || e}`); }
|
|
254
279
|
if (eventLoopLagTimer) { clearInterval(eventLoopLagTimer); eventLoopLagTimer = null; }
|
|
255
280
|
eventLoopDelay.disable();
|
|
281
|
+
for (const discoveryPath of [DAEMON_DISCOVERY_PATH]) {
|
|
282
|
+
try { rmSync(discoveryPath, { force: true }); } catch {}
|
|
283
|
+
}
|
|
256
284
|
try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {}
|
|
257
285
|
await flushDaemonLogs();
|
|
258
286
|
process.exit(code);
|
|
259
287
|
}
|
|
260
288
|
|
|
261
|
-
/** One process, two front doors (channels +
|
|
289
|
+
/** One process, two front doors (channels + sessions): an idle side must never
|
|
262
290
|
* evict a busy one, so every self-shutdown trigger checks BOTH registries. */
|
|
263
291
|
function maybeSelfShutdown(reason) {
|
|
264
292
|
const channelClients = transport?.clientCount ?? 0;
|
|
265
|
-
const
|
|
266
|
-
const remoteClients =
|
|
267
|
-
|
|
293
|
+
const sessionClients = sessionTransport?.clientCount ?? 0;
|
|
294
|
+
const remoteClients = sessionService?.externalClientCount ?? 0;
|
|
295
|
+
const replacing = Boolean(replacementRequested);
|
|
296
|
+
if (channelClients > 0 || sessionClients > 0 || (!replacing && remoteClients > 0)) {
|
|
268
297
|
if (shutdownRecheckTimer) {
|
|
269
298
|
clearTimeout(shutdownRecheckTimer);
|
|
270
299
|
shutdownRecheckTimer = null;
|
|
271
300
|
}
|
|
272
|
-
log(`shutdown deferred (${reason}): channels=${channelClients}
|
|
301
|
+
log(`shutdown deferred (${reason}): channels=${channelClients} sessionClients=${sessionClients} remote=${remoteClients}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const activeCalls = (transport?.activeCount ?? 0) + (sessionTransport?.activeCount ?? 0);
|
|
305
|
+
const queuedCalls = (transport?.queuedCount ?? 0) + (sessionTransport?.queuedCount ?? 0);
|
|
306
|
+
if (activeCalls > 0 || queuedCalls > 0) {
|
|
307
|
+
log(`shutdown deferred (${reason}): activeCalls=${activeCalls} queuedCalls=${queuedCalls}`);
|
|
308
|
+
if (!shutdownRecheckTimer) {
|
|
309
|
+
shutdownRecheckTimer = setTimeout(() => {
|
|
310
|
+
shutdownRecheckTimer = null;
|
|
311
|
+
maybeSelfShutdown('service calls settled');
|
|
312
|
+
}, 250);
|
|
313
|
+
shutdownRecheckTimer.unref?.();
|
|
314
|
+
}
|
|
273
315
|
return;
|
|
274
316
|
}
|
|
275
317
|
// A turn in flight outlives every view — closing the app or the terminal is
|
|
276
318
|
// not a reason to abandon work the daemon is still running.
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
319
|
+
const busySessions = sessionService?.busyCount ?? 0;
|
|
320
|
+
const busyMemoryAgents = agentDispatchBroker?.snapshot?.().inFlight ?? 0;
|
|
321
|
+
if (busySessions > 0 || busyMemoryAgents > 0) {
|
|
322
|
+
log(
|
|
323
|
+
`shutdown deferred (${reason}): busySessions=${busySessions}`
|
|
324
|
+
+ ` memoryAgents=${busyMemoryAgents}`,
|
|
325
|
+
);
|
|
280
326
|
if (!shutdownRecheckTimer) {
|
|
281
327
|
shutdownRecheckTimer = setTimeout(() => {
|
|
282
328
|
shutdownRecheckTimer = null;
|
|
283
|
-
maybeSelfShutdown('busy
|
|
329
|
+
maybeSelfShutdown('busy service work settled');
|
|
284
330
|
}, 1_000);
|
|
285
331
|
shutdownRecheckTimer.unref?.();
|
|
286
332
|
}
|
|
@@ -289,6 +335,30 @@ function maybeSelfShutdown(reason) {
|
|
|
289
335
|
void shutdown(reason);
|
|
290
336
|
}
|
|
291
337
|
|
|
338
|
+
function requestDaemonReplacement({ protocol, revision, version } = {}) {
|
|
339
|
+
const requested = {
|
|
340
|
+
protocol: Number(protocol),
|
|
341
|
+
revision: Math.max(0, Number(revision) || 0),
|
|
342
|
+
version: String(version || '0.0.0'),
|
|
343
|
+
};
|
|
344
|
+
if (requested.protocol !== SESSION_PROTOCOL) return false;
|
|
345
|
+
const revisionOrder = requested.revision - SESSION_REVISION;
|
|
346
|
+
const versionOrder = compareRuntimeVersions(requested.version, runtimeVersion());
|
|
347
|
+
if (revisionOrder < 0 || (revisionOrder === 0 && versionOrder <= 0)) return false;
|
|
348
|
+
if (
|
|
349
|
+
replacementRequested
|
|
350
|
+
&& requested.revision === replacementRequested.revision
|
|
351
|
+
&& requested.version === replacementRequested.version
|
|
352
|
+
) return true;
|
|
353
|
+
replacementRequested = requested;
|
|
354
|
+
const reason = `daemon replacement by revision/build ${requested.revision}/${requested.version}`;
|
|
355
|
+
log(`${reason} requested — draining clients without interrupting live work`);
|
|
356
|
+
sessionTransport?.beginDrain?.(reason);
|
|
357
|
+
transport?.beginDrain?.(reason);
|
|
358
|
+
maybeSelfShutdown(reason);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
292
362
|
async function main() {
|
|
293
363
|
const startedAt = performance.now();
|
|
294
364
|
// 0o700 matters where the runtime root is a SHARED temp dir: the discovery
|
|
@@ -299,12 +369,28 @@ async function main() {
|
|
|
299
369
|
// Pid-verified singleton claim (claimSingletonOwner reclaims a dead-pid owner
|
|
300
370
|
// file and refuses only a LIVE peer). Loser exits so the spawner attaches to
|
|
301
371
|
// the winner instead of running a second daemon.
|
|
302
|
-
const claim = claimSingletonOwner(OWNER_PATH, {
|
|
372
|
+
const claim = claimSingletonOwner(OWNER_PATH, {
|
|
373
|
+
kind: 'mixdog-daemon',
|
|
374
|
+
pid: process.pid,
|
|
375
|
+
meta: {
|
|
376
|
+
cwd: CWD,
|
|
377
|
+
protocol: SESSION_PROTOCOL,
|
|
378
|
+
revision: SESSION_REVISION,
|
|
379
|
+
version: runtimeVersion(),
|
|
380
|
+
},
|
|
381
|
+
});
|
|
303
382
|
if (!claim.owned) {
|
|
304
383
|
log(`live peer holds owner lock (pid=${claim.owner?.pid}) — exiting for attach`);
|
|
305
384
|
process.exit(0);
|
|
306
385
|
}
|
|
307
386
|
process.on('exit', () => { try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {} });
|
|
387
|
+
agentDispatchBroker = createAgentDispatchBroker({
|
|
388
|
+
loadConfig: loadAgentConfig,
|
|
389
|
+
initProviders,
|
|
390
|
+
makeAgentDispatch,
|
|
391
|
+
log,
|
|
392
|
+
onActivityChanged: () => { maybeSelfShutdown('memory agent activity changed'); },
|
|
393
|
+
});
|
|
308
394
|
process.on('mixdog:turn-timing', (row = {}) => {
|
|
309
395
|
const ms = (value) => Number.isFinite(value) ? Math.round(value) : -1;
|
|
310
396
|
log(
|
|
@@ -318,7 +404,7 @@ async function main() {
|
|
|
318
404
|
|
|
319
405
|
// The channels runtime is imported AFTER the daemon env is set so worker-main
|
|
320
406
|
// skips runWorkerIpc; that import also triggers its boot side effects
|
|
321
|
-
// (config/
|
|
407
|
+
// (config/service). It is now deferred past the ready handshake: a session
|
|
322
408
|
// view attaching to this same process must not wait out the channels graph,
|
|
323
409
|
// and every channels call awaits this promise anyway.
|
|
324
410
|
let channelsReady = null;
|
|
@@ -333,9 +419,9 @@ async function main() {
|
|
|
333
419
|
}
|
|
334
420
|
|
|
335
421
|
// Channels bring automation with them (schedules, webhook listener + tunnel,
|
|
336
|
-
// optional messaging
|
|
337
|
-
// as before; a daemon spawned for
|
|
338
|
-
// client actually registers, so an app-only
|
|
422
|
+
// optional messaging service). A daemon spawned by a TUI starts them exactly
|
|
423
|
+
// as before; a daemon spawned for session views stays dormant until a channels
|
|
424
|
+
// client actually registers, so an app-only service runs no tunnels.
|
|
339
425
|
let channelsStarted = false;
|
|
340
426
|
function startChannels() {
|
|
341
427
|
if (channelsStarted) return;
|
|
@@ -355,79 +441,77 @@ async function main() {
|
|
|
355
441
|
}
|
|
356
442
|
return module.handleToolCallWithBridgeRetry(name, args || {});
|
|
357
443
|
};
|
|
358
|
-
transport =
|
|
444
|
+
transport = createChannelTransport({
|
|
359
445
|
handleCall,
|
|
360
|
-
|
|
446
|
+
agentBroker: agentDispatchBroker,
|
|
447
|
+
remoteStatePath: path.join(RUNTIME_ROOT, 'channel-remote-state.json'),
|
|
448
|
+
remoteIntentPath: path.join(RUNTIME_ROOT, 'channel-remote-intent.json'),
|
|
449
|
+
// If a manual owner disappears without sending OFF, deactivate the service.
|
|
361
450
|
// No survivor is selected or rebound.
|
|
362
451
|
dispatchControl: (name, args, ctx) => handleCall(name, args, ctx),
|
|
363
|
-
discoveryPath: DISCOVERY_PATH,
|
|
364
452
|
log,
|
|
365
453
|
// Self-shutdown when the last attached TUI leaves (reuses the SSE/client
|
|
366
|
-
// registry as the liveness signal
|
|
454
|
+
// registry as the liveness signal).
|
|
367
455
|
onClientsEmpty: () => { maybeSelfShutdown('no live channel clients'); },
|
|
368
456
|
// First channels client in: bring the channels runtime up (see startChannels).
|
|
369
457
|
onClientRegistered: () => { startChannels(); },
|
|
370
458
|
});
|
|
371
459
|
setChannelNotifySink((method, params) => transport.notify(method, params));
|
|
372
460
|
const { port, token } = await transport.start();
|
|
461
|
+
// Memory-cycle agent dispatch is rare and initializes on first use. Eagerly
|
|
462
|
+
// loading its provider graph here consumed the control loop before any
|
|
463
|
+
// memory cycle requested it.
|
|
373
464
|
|
|
374
|
-
// ──
|
|
375
|
-
// Session
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
let nextLocalEngineClient = 0;
|
|
382
|
-
localEngineBridge = {
|
|
465
|
+
// ── Session front door ──────────────────────────────────────────────────────
|
|
466
|
+
// Session runtimes live in child-process shards. The daemon owns routing and
|
|
467
|
+
// fan-out only, so one provider/parser turn cannot stall health or input for
|
|
468
|
+
// every other session.
|
|
469
|
+
const localSessionClients = new Map();
|
|
470
|
+
let nextLocalSessionClient = 0;
|
|
471
|
+
localSessionBridge = {
|
|
383
472
|
attach({ onFrame = () => {}, onFatal = () => {} } = {}) {
|
|
384
|
-
const clientToken = `daemon_local_${process.pid}_${++
|
|
473
|
+
const clientToken = `daemon_local_${process.pid}_${++nextLocalSessionClient}`;
|
|
385
474
|
let closed = false;
|
|
386
|
-
|
|
387
|
-
return {
|
|
475
|
+
localSessionClients.set(clientToken, { onFrame, onFatal });
|
|
476
|
+
return createSessionProtocolClient({
|
|
388
477
|
call(name, args = {}, options = {}) {
|
|
389
|
-
if (closed) throw new Error('
|
|
390
|
-
return
|
|
478
|
+
if (closed) throw new Error('daemon-local session client is closed');
|
|
479
|
+
return sessionService.handleCall(name, args, {
|
|
391
480
|
clientToken,
|
|
392
481
|
...(options?.callId ? { callId: String(options.callId) } : {}),
|
|
393
482
|
});
|
|
394
483
|
},
|
|
395
|
-
async close(reason = 'local
|
|
484
|
+
async close(reason = 'local session view closed') {
|
|
396
485
|
if (closed) return;
|
|
397
486
|
closed = true;
|
|
398
|
-
|
|
399
|
-
try {
|
|
487
|
+
localSessionClients.delete(clientToken);
|
|
488
|
+
try { sessionService.releaseClient(clientToken); } catch {}
|
|
400
489
|
log(`${reason} (${clientToken})`);
|
|
401
490
|
},
|
|
402
|
-
};
|
|
491
|
+
});
|
|
403
492
|
},
|
|
404
493
|
publish(frame, targetTokens = null) {
|
|
405
494
|
const targets = targetTokens ? new Set(targetTokens) : null;
|
|
406
|
-
for (const [clientToken, client] of
|
|
495
|
+
for (const [clientToken, client] of localSessionClients) {
|
|
407
496
|
if (targets && !targets.has(clientToken)) continue;
|
|
408
497
|
try { client.onFrame(frame); } catch {}
|
|
409
498
|
}
|
|
410
499
|
},
|
|
411
500
|
async close(reason = 'daemon shutdown') {
|
|
412
|
-
for (const [clientToken, client] of
|
|
501
|
+
for (const [clientToken, client] of localSessionClients) {
|
|
413
502
|
try { client.onFatal(reason); } catch {}
|
|
414
|
-
try {
|
|
503
|
+
try { sessionService.releaseClient(clientToken); } catch {}
|
|
415
504
|
}
|
|
416
|
-
|
|
505
|
+
localSessionClients.clear();
|
|
417
506
|
},
|
|
418
507
|
};
|
|
419
|
-
uninstallLocalEngineBridge = installEngineDaemonLocalBridge(localEngineBridge);
|
|
420
|
-
const engineDaemonClientModule = () => import('./engine-daemon-client.mjs');
|
|
421
508
|
const desktopRuntime = {
|
|
422
|
-
async
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
async callDaemonSession(options) {
|
|
426
|
-
return (await engineDaemonClientModule()).callDaemonSession(options);
|
|
509
|
+
async attachSessionClient(options = {}) {
|
|
510
|
+
if (!localSessionBridge) throw new Error('daemon-local session client is unavailable');
|
|
511
|
+
return localSessionBridge.attach(options);
|
|
427
512
|
},
|
|
428
513
|
loadProjects: () => import('./projects.mjs'),
|
|
429
514
|
loadSessionStore: () => import('../runtime/agent/orchestrator/session/store-summary-reader.mjs'),
|
|
430
|
-
loadStatuslineSegments: () => import('../ui/statusline-segments.mjs'),
|
|
431
515
|
loadConfig: () => import('../runtime/shared/config.mjs'),
|
|
432
516
|
loadCommitCompletion: () => import(
|
|
433
517
|
'../runtime/agent/orchestrator/agent-runtime/commit-message-completion.mjs'
|
|
@@ -437,68 +521,96 @@ async function main() {
|
|
|
437
521
|
return graph.executeCodeGraphTool(name, args, cwd);
|
|
438
522
|
},
|
|
439
523
|
};
|
|
440
|
-
let
|
|
441
|
-
|
|
442
|
-
function
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
return engineModulePromise;
|
|
450
|
-
}
|
|
451
|
-
function prewarmEngineRuntime() {
|
|
452
|
-
if (engineRuntimePrewarmStarted) return;
|
|
453
|
-
engineRuntimePrewarmStarted = true;
|
|
454
|
-
void loadEngineModule()
|
|
455
|
-
.then((engineModule) => {
|
|
456
|
-
engineModule.preloadSessionRuntimeModule?.();
|
|
457
|
-
engineModule.preloadKeychainSecrets?.();
|
|
458
|
-
log('engine runtime/keychain prewarm started');
|
|
459
|
-
})
|
|
524
|
+
let sessionRuntimePrewarmStarted = false;
|
|
525
|
+
sessionRuntimePool = createSessionRuntimePool({ cwd: CWD, log });
|
|
526
|
+
function prewarmSessionRuntime() {
|
|
527
|
+
if (sessionRuntimePrewarmStarted) return;
|
|
528
|
+
sessionRuntimePrewarmStarted = true;
|
|
529
|
+
void sessionRuntimePool.prewarm()
|
|
530
|
+
.then(() => log('session shard runtime/agent-loop/keychain prewarm started'))
|
|
460
531
|
.catch((error) => {
|
|
461
|
-
|
|
462
|
-
log(`
|
|
532
|
+
sessionRuntimePrewarmStarted = false;
|
|
533
|
+
log(`session runtime/keychain prewarm failed (non-fatal): ${error?.message || error}`);
|
|
463
534
|
});
|
|
464
535
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
536
|
+
sessionService = createSessionService({
|
|
537
|
+
createSessionRuntime: (options) => sessionRuntimePool.create(options),
|
|
538
|
+
sessionExists: async (sessionId) => {
|
|
539
|
+
const store = await desktopRuntime.loadSessionStore();
|
|
540
|
+
return store.storedSessionExists?.(sessionId) === true;
|
|
541
|
+
},
|
|
542
|
+
listSessions: async (options = {}) => {
|
|
543
|
+
const store = await desktopRuntime.loadSessionStore();
|
|
544
|
+
return store.listStoredSessionSummaries({
|
|
545
|
+
refreshFromStorage: options.refreshFromStorage === true,
|
|
546
|
+
});
|
|
469
547
|
},
|
|
470
548
|
desktopRuntime,
|
|
471
549
|
onFrame: (frame, targetTokens) => {
|
|
472
|
-
|
|
473
|
-
|
|
550
|
+
localSessionBridge?.publish(frame, targetTokens);
|
|
551
|
+
sessionTransport?.broadcast(frame, targetTokens);
|
|
474
552
|
},
|
|
475
553
|
onExternalClientsChanged: () => { maybeSelfShutdown('remote clients changed'); },
|
|
476
554
|
log,
|
|
477
555
|
});
|
|
478
|
-
|
|
479
|
-
// ctx carries the CLIENT token: the
|
|
480
|
-
// processes with it, so a terminal exiting cannot destroy the
|
|
556
|
+
sessionTransport = createSessionTransport({
|
|
557
|
+
// ctx carries the CLIENT token: the session pool refcounts views across
|
|
558
|
+
// processes with it, so a terminal exiting cannot destroy the session a
|
|
481
559
|
// desktop window is still streaming.
|
|
482
|
-
handleCall: (name, args, ctx) =>
|
|
483
|
-
discoveryPath: ENGINE_DISCOVERY_PATH,
|
|
560
|
+
handleCall: (name, args, ctx) => sessionService.handleCall(name, args, ctx),
|
|
484
561
|
log,
|
|
485
562
|
// `busy` is what stops a newer install from draining a daemon that is
|
|
486
563
|
// mid-turn: work outlives views AND installs.
|
|
487
564
|
getStatus: () => ({
|
|
488
|
-
|
|
489
|
-
busy:
|
|
565
|
+
sessions: sessionService.size,
|
|
566
|
+
busy: sessionService.busyCount,
|
|
567
|
+
sessionPool: sessionService.status,
|
|
568
|
+
sessionShards: sessionRuntimePool.status,
|
|
569
|
+
workload: {
|
|
570
|
+
resources: resourceAdmission.snapshot(),
|
|
571
|
+
childSpawns: childSpawnSnapshot(),
|
|
572
|
+
toolIo: toolWorkloadSnapshot(),
|
|
573
|
+
mcp: getMcpAdmissionSnapshot(),
|
|
574
|
+
providers: providerAdmissionScheduler.snapshot(),
|
|
575
|
+
streamParsing: providerStreamJsonSnapshot(),
|
|
576
|
+
},
|
|
577
|
+
memory: {
|
|
578
|
+
...(() => {
|
|
579
|
+
const usage = process.memoryUsage();
|
|
580
|
+
return {
|
|
581
|
+
rssBytes: usage.rss,
|
|
582
|
+
heapTotalBytes: usage.heapTotal,
|
|
583
|
+
heapUsedBytes: usage.heapUsed,
|
|
584
|
+
externalBytes: usage.external,
|
|
585
|
+
arrayBufferBytes: usage.arrayBuffers,
|
|
586
|
+
};
|
|
587
|
+
})(),
|
|
588
|
+
},
|
|
490
589
|
...eventLoopStatus(),
|
|
491
590
|
}),
|
|
492
|
-
onClientsEmpty: () => { maybeSelfShutdown('no live
|
|
493
|
-
onClientRegistered: () => {
|
|
494
|
-
onClientDropped: (token) => { try {
|
|
591
|
+
onClientsEmpty: () => { maybeSelfShutdown('no live session clients'); },
|
|
592
|
+
onClientRegistered: () => { prewarmSessionRuntime(); },
|
|
593
|
+
onClientDropped: (token) => { try { sessionService.releaseClient(token); } catch {} },
|
|
594
|
+
onUpgradeRequested: requestDaemonReplacement,
|
|
495
595
|
});
|
|
496
|
-
const
|
|
497
|
-
|
|
596
|
+
const sessionEndpoint = await sessionTransport.start();
|
|
597
|
+
writeJsonAtomicSync(DAEMON_DISCOVERY_PATH, {
|
|
598
|
+
protocol: SESSION_PROTOCOL,
|
|
599
|
+
revision: SESSION_REVISION,
|
|
600
|
+
version: runtimeVersion(),
|
|
601
|
+
capabilityFingerprint: SESSION_CAPABILITY_FINGERPRINT,
|
|
602
|
+
pid: process.pid,
|
|
603
|
+
startedAt: Date.now(),
|
|
604
|
+
endpoints: {
|
|
605
|
+
channel: { port, token },
|
|
606
|
+
session: { port: sessionEndpoint.port, token: sessionEndpoint.token },
|
|
607
|
+
},
|
|
608
|
+
}, { compact: true });
|
|
609
|
+
log(`session front door on 127.0.0.1:${sessionEndpoint.port}`);
|
|
498
610
|
|
|
499
|
-
// Ready handshake for the spawner
|
|
500
|
-
//
|
|
501
|
-
// Discord connect so the spawner's ready wait never blocks on
|
|
611
|
+
// Ready handshake for the spawner first. Transport is already listening;
|
|
612
|
+
// signal ready before the heavy
|
|
613
|
+
// Discord connect so the spawner's ready wait never blocks on service I/O.
|
|
502
614
|
// Take over file logging from the spawner at the ready boundary. No rotate
|
|
503
615
|
// here: the spawner already bounds the file at its own boot (channel-worker
|
|
504
616
|
// rotateBoundedLog), and rotating now would race other processes' buffered
|
|
@@ -533,18 +645,16 @@ async function main() {
|
|
|
533
645
|
|
|
534
646
|
// Boot messaging only for an explicit manual remote request. Automation may
|
|
535
647
|
// spawn the shared daemon with no remote intent; keep schedules/webhooks live
|
|
536
|
-
// without connecting the channel
|
|
537
|
-
// Legacy `auto` intent is deliberately ignored.
|
|
648
|
+
// without connecting the channel service until a manual Remote ON arrives.
|
|
538
649
|
// A TUI-spawned daemon keeps the historical eager start (its channels client
|
|
539
|
-
// is already on the way).
|
|
650
|
+
// is already on the way). A session-spawned one waits for a real channels
|
|
540
651
|
// client — see the transport's onClientRegistered hook.
|
|
541
|
-
if (process.env.MIXDOG_DAEMON_SPAWNED_FOR !== '
|
|
652
|
+
if (process.env.MIXDOG_DAEMON_SPAWNED_FOR !== 'session') startChannels();
|
|
542
653
|
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
// to active-instance.json when ready and memory-client buffers until then.
|
|
654
|
+
// Memory owns a separate process/event loop. Initialization stays
|
|
655
|
+
// asynchronous because DB/embedding startup must not delay the front-door
|
|
656
|
+
// ready handshake. The proxy retains it for this daemon pid and its
|
|
657
|
+
// client-grace lifecycle reaps it only after the daemon is gone.
|
|
548
658
|
// Isolated test roots opt out (MIXDOG_DAEMON_SKIP_MEMORY=1): spinning a
|
|
549
659
|
// throwaway Postgres cluster per test run is pure cost, and a hard-killed
|
|
550
660
|
// daemon would orphan it.
|
|
@@ -553,8 +663,14 @@ async function main() {
|
|
|
553
663
|
return;
|
|
554
664
|
}
|
|
555
665
|
try {
|
|
556
|
-
memoryRuntime =
|
|
557
|
-
|
|
666
|
+
memoryRuntime = getStandaloneMemoryRuntime({
|
|
667
|
+
entry: MEMORY_ENTRY,
|
|
668
|
+
dataDir: DATA_DIR,
|
|
669
|
+
cwd: CWD,
|
|
670
|
+
});
|
|
671
|
+
void memoryRuntime.init()
|
|
672
|
+
.then(() => log('memory runtime ready in isolated process'))
|
|
673
|
+
.catch((e) => log(`memory.start failed (non-fatal): ${e?.message || e}`));
|
|
558
674
|
} catch (e) { log(`memory.start setup failed (non-fatal): ${e?.message || e}`); }
|
|
559
675
|
}
|
|
560
676
|
|