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,4 +1,4 @@
|
|
|
1
|
-
// Machine-global
|
|
1
|
+
// Machine-global channel front door — HTTP + SSE transport (server side).
|
|
2
2
|
//
|
|
3
3
|
// Replaces the per-TUI fork + node-IPC (`{type:'call'|'notify'}`) plumbing with
|
|
4
4
|
// ONE local HTTP server that many TUIs attach to. Design mirrors the memory
|
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
// (targeted routing, never broadcast — see routeNotify below).
|
|
9
9
|
//
|
|
10
10
|
// This module owns ONLY the transport (sockets, client registry, notify
|
|
11
|
-
// routing
|
|
12
|
-
// Discord
|
|
11
|
+
// routing and lifecycle). The channels runtime (tool dispatch,
|
|
12
|
+
// Discord provider, transcript bind/steal) is injected via `handleCall` so the
|
|
13
13
|
// same transport is exercised by the real daemon entry AND the smoke harness
|
|
14
14
|
// (stub runtime, no Discord token).
|
|
15
15
|
import http from 'node:http';
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import { readFileSync, rmSync } from 'node:fs';
|
|
18
|
-
import { basename
|
|
18
|
+
import { basename } from 'node:path';
|
|
19
19
|
import { writeJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
|
|
20
20
|
import { readBody, sendJson, sendError } from '../runtime/memory/lib/http-wire.mjs';
|
|
21
|
+
import { createFairCallScheduler } from './fair-call-scheduler.mjs';
|
|
21
22
|
|
|
22
23
|
function parsePid(value) {
|
|
23
24
|
const n = Number(value);
|
|
@@ -35,9 +36,8 @@ const ACTIVATE_TOOL = 'activate_channel_bridge';
|
|
|
35
36
|
const REBIND_TOOL = 'rebind_current_transcript';
|
|
36
37
|
const BINDING_TOOLS = new Set([ACTIVATE_TOOL, REBIND_TOOL]);
|
|
37
38
|
|
|
38
|
-
export function
|
|
39
|
+
export function createChannelTransport({
|
|
39
40
|
handleCall,
|
|
40
|
-
discoveryPath,
|
|
41
41
|
serverToken = randomUUID(),
|
|
42
42
|
log = () => {},
|
|
43
43
|
clientGraceMs = 10_000,
|
|
@@ -49,13 +49,12 @@ export function createChannelDaemonTransport({
|
|
|
49
49
|
remoteStatePath = null,
|
|
50
50
|
remoteIntentPath = null,
|
|
51
51
|
onClientRegistered = null,
|
|
52
|
+
agentBroker = null,
|
|
52
53
|
} = {}) {
|
|
53
54
|
if (typeof handleCall !== 'function') throw new Error('handleCall is required');
|
|
54
55
|
|
|
55
|
-
const resolvedRemoteStatePath = remoteStatePath
|
|
56
|
-
|
|
57
|
-
const resolvedRemoteIntentPath = remoteIntentPath
|
|
58
|
-
|| (discoveryPath ? join(dirname(discoveryPath), 'channel-remote-intent.json') : null);
|
|
56
|
+
const resolvedRemoteStatePath = remoteStatePath;
|
|
57
|
+
const resolvedRemoteIntentPath = remoteIntentPath;
|
|
59
58
|
// token -> { token, leadPid, cwd, sse, lastSeen, registeredAt }
|
|
60
59
|
const clients = new Map();
|
|
61
60
|
// The routing pointer is set ONLY by an explicit manual Remote ON. Client
|
|
@@ -81,6 +80,23 @@ export function createChannelDaemonTransport({
|
|
|
81
80
|
// retry never double-runs a non-idempotent tool (e.g. reply). Short TTL.
|
|
82
81
|
const callCache = new Map();
|
|
83
82
|
const CALL_CACHE_TTL_MS = 60_000;
|
|
83
|
+
const configuredLaneLimit = (name) => {
|
|
84
|
+
const parsed = Math.floor(Number(process.env[name]));
|
|
85
|
+
return Number.isFinite(parsed) && parsed >= 1 ? parsed : Infinity;
|
|
86
|
+
};
|
|
87
|
+
const CALL_QUEUE_MAX = Math.max(256, Number(process.env.MIXDOG_CHANNEL_CALL_QUEUE) || 256);
|
|
88
|
+
const channelCalls = createFairCallScheduler({
|
|
89
|
+
name: 'channel call',
|
|
90
|
+
activeMax: configuredLaneLimit('MIXDOG_CHANNEL_ACTIVE_CALLS'),
|
|
91
|
+
queueMax: CALL_QUEUE_MAX,
|
|
92
|
+
minOwnerQueue: Math.max(8, Math.floor(CALL_QUEUE_MAX / 16)),
|
|
93
|
+
});
|
|
94
|
+
const channelControlCalls = createFairCallScheduler({
|
|
95
|
+
name: 'channel control call',
|
|
96
|
+
activeMax: configuredLaneLimit('MIXDOG_CHANNEL_CONTROL_RESERVE'),
|
|
97
|
+
queueMax: Math.max(16, Math.min(64, CALL_QUEUE_MAX)),
|
|
98
|
+
minOwnerQueue: 4,
|
|
99
|
+
});
|
|
84
100
|
// Reconnect register replay: a server may commit replacement just before its
|
|
85
101
|
// HTTP response is lost. The retry supplies this stable id and receives the
|
|
86
102
|
// already-created fresh token instead of creating an orphan replacement.
|
|
@@ -91,6 +107,7 @@ export function createChannelDaemonTransport({
|
|
|
91
107
|
let sweepTimer = null;
|
|
92
108
|
let everHadClient = false;
|
|
93
109
|
let closed = false;
|
|
110
|
+
let drainingReason = '';
|
|
94
111
|
|
|
95
112
|
function nowMs() { return Date.now(); }
|
|
96
113
|
|
|
@@ -251,7 +268,7 @@ export function createChannelDaemonTransport({
|
|
|
251
268
|
for (const [token, c] of clients) {
|
|
252
269
|
// A client is dead when its lead pid is gone OR its SSE stream closed and
|
|
253
270
|
// it has not re-registered within a grace window. pid death is the
|
|
254
|
-
// authoritative signal
|
|
271
|
+
// authoritative signal for client liveness.
|
|
255
272
|
if (!isPidAlive(c.leadPid)) {
|
|
256
273
|
dropClient(token, 'pid dead');
|
|
257
274
|
}
|
|
@@ -275,7 +292,7 @@ export function createChannelDaemonTransport({
|
|
|
275
292
|
if (pointerToken === token) pointerToken = null;
|
|
276
293
|
log(`client ${token} (lead=${c.leadPid}) removed: ${reason}`);
|
|
277
294
|
// Owner removal always means Remote OFF. Never auto-select or rebind a
|
|
278
|
-
// survivor; explicitly deactivate the shared
|
|
295
|
+
// survivor; explicitly deactivate the shared provider if the owner vanished
|
|
279
296
|
// without first sending its normal active:false command.
|
|
280
297
|
if (wasPointer) {
|
|
281
298
|
remoteAcquired = false;
|
|
@@ -298,6 +315,14 @@ export function createChannelDaemonTransport({
|
|
|
298
315
|
maybeArmGrace('client removed');
|
|
299
316
|
}
|
|
300
317
|
|
|
318
|
+
function beginDrain(reason = 'daemon replacement') {
|
|
319
|
+
if (drainingReason) return false;
|
|
320
|
+
drainingReason = String(reason || 'daemon replacement');
|
|
321
|
+
cancelGrace();
|
|
322
|
+
for (const token of [...clients.keys()]) dropClient(token, drainingReason);
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
301
326
|
function removeRegistrationReplay(registrationId, replay = registrationReplays.get(registrationId)) {
|
|
302
327
|
if (!replay || registrationReplays.get(registrationId) !== replay) return;
|
|
303
328
|
registrationReplays.delete(registrationId);
|
|
@@ -406,7 +431,7 @@ export function createChannelDaemonTransport({
|
|
|
406
431
|
}
|
|
407
432
|
|
|
408
433
|
// Manual ON is the only ownership transition. It may temporarily point at
|
|
409
|
-
// the claimant before
|
|
434
|
+
// the claimant before provider activation so a synchronous acquired notify is
|
|
410
435
|
// routed correctly; the displaced owner is notified only after activation
|
|
411
436
|
// succeeds.
|
|
412
437
|
function movePointer(newToken, reason, { notifyDisplaced = true } = {}) {
|
|
@@ -489,10 +514,10 @@ export function createChannelDaemonTransport({
|
|
|
489
514
|
}
|
|
490
515
|
}
|
|
491
516
|
|
|
492
|
-
function registerClient({ leadPid, cwd,
|
|
517
|
+
function registerClient({ leadPid, cwd, passive = false, replaceToken = null, registrationId = null }) {
|
|
493
518
|
const pid = parsePid(leadPid) ?? 0;
|
|
494
519
|
const replacementToken = replaceToken ? String(replaceToken) : null;
|
|
495
|
-
const replayId =
|
|
520
|
+
const replayId = passive && registrationId ? String(registrationId).slice(0, 200) : null;
|
|
496
521
|
const existingReplay = replayId ? registrationReplays.get(replayId) : null;
|
|
497
522
|
if (existingReplay) {
|
|
498
523
|
if (existingReplay.leadPid === pid && existingReplay.cwd === (cwd || null) &&
|
|
@@ -522,9 +547,9 @@ export function createChannelDaemonTransport({
|
|
|
522
547
|
cancelGrace();
|
|
523
548
|
startSweep();
|
|
524
549
|
log(`client registered token=${token} lead=${pid} cwd=${cwd || '-'}`);
|
|
525
|
-
// The unified
|
|
526
|
-
// messaging
|
|
527
|
-
//
|
|
550
|
+
// The unified daemon starts the channels runtime (automation, webhooks,
|
|
551
|
+
// messaging provider) only once a CHANNELS client is actually present — an
|
|
552
|
+
// session runtime-only daemon must not run tunnels nobody asked for.
|
|
528
553
|
if (typeof onClientRegistered === 'function') {
|
|
529
554
|
try { onClientRegistered({ token, leadPid: pid, cwd: cwd || null }); } catch {}
|
|
530
555
|
}
|
|
@@ -543,7 +568,7 @@ export function createChannelDaemonTransport({
|
|
|
543
568
|
// not the pointer: retaining a non-owner old token would let it later call,
|
|
544
569
|
// steal ownership, or accumulate a buffered frame after the fresh client
|
|
545
570
|
// has gone away. Token replacement never crosses leadPid boundaries.
|
|
546
|
-
const replaced =
|
|
571
|
+
const replaced = passive && replacementToken ? clients.get(replacementToken) : null;
|
|
547
572
|
if (replaced && replaced.leadPid === pid) {
|
|
548
573
|
const fresh = clients.get(token);
|
|
549
574
|
const replacedWasPointer = pointerToken === replaced.token;
|
|
@@ -615,19 +640,33 @@ export function createChannelDaemonTransport({
|
|
|
615
640
|
const pathName = url.pathname;
|
|
616
641
|
try {
|
|
617
642
|
if (req.method === 'GET' && pathName === '/health') {
|
|
618
|
-
sendJson(res, {
|
|
643
|
+
sendJson(res, {
|
|
644
|
+
status: 'ok',
|
|
645
|
+
pid: process.pid,
|
|
646
|
+
clients: clients.size,
|
|
647
|
+
activeCalls: channelCalls.active + channelControlCalls.active,
|
|
648
|
+
queuedCalls: channelCalls.queued + channelControlCalls.queued,
|
|
649
|
+
callOwners: channelCalls.snapshot().owners,
|
|
650
|
+
draining: drainingReason || null,
|
|
651
|
+
...(agentBroker?.snapshot ? { agentBroker: agentBroker.snapshot() } : {}),
|
|
652
|
+
...getStatus(),
|
|
653
|
+
});
|
|
619
654
|
return;
|
|
620
655
|
}
|
|
621
656
|
const token = req.headers['x-mixdog-daemon-token'] || url.searchParams.get('server_token');
|
|
622
657
|
if (token !== serverToken) { sendError(res, 'forbidden', 403); return; }
|
|
623
658
|
|
|
624
659
|
if (req.method === 'POST' && pathName === '/client/register') {
|
|
660
|
+
if (drainingReason) {
|
|
661
|
+
sendError(res, `daemon is draining: ${drainingReason}`, 503);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
625
664
|
const body = await readBody(req);
|
|
626
665
|
const clientToken = registerClient({
|
|
627
|
-
leadPid: body.leadPid, cwd: body.cwd,
|
|
666
|
+
leadPid: body.leadPid, cwd: body.cwd, passive: body.passive === true,
|
|
628
667
|
replaceToken: body.replaceToken, registrationId: body.registrationId,
|
|
629
668
|
});
|
|
630
|
-
const replayId = body.
|
|
669
|
+
const replayId = body.passive === true && body.registrationId ? String(body.registrationId).slice(0, 200) : null;
|
|
631
670
|
res.once('finish', () => markRegistrationResponseFinished(replayId, clientToken));
|
|
632
671
|
sendJson(res, { token: clientToken, pid: process.pid });
|
|
633
672
|
return;
|
|
@@ -649,6 +688,45 @@ export function createChannelDaemonTransport({
|
|
|
649
688
|
if (!attachSse(clientToken, res)) { sendError(res, 'unknown client token', 404); return; }
|
|
650
689
|
return; // stream stays open
|
|
651
690
|
}
|
|
691
|
+
// Internal memory -> session LLM bridge. It is authenticated with the
|
|
692
|
+
// daemon discovery token but deliberately does NOT register as a channel
|
|
693
|
+
// client: background memory work must not acquire remote ownership or
|
|
694
|
+
// keep the channels client registry alive. The broker itself owns a
|
|
695
|
+
// parallel fair scheduler and per-call cancellation.
|
|
696
|
+
if (req.method === 'POST' && pathName === '/agent/dispatch') {
|
|
697
|
+
if (!agentBroker?.dispatch) { sendError(res, 'agent broker unavailable', 503); return; }
|
|
698
|
+
const body = await readBody(req);
|
|
699
|
+
const callId = String(body.callId || '').trim();
|
|
700
|
+
if (!callId) { sendError(res, 'callId required', 400); return; }
|
|
701
|
+
res.on('close', () => {
|
|
702
|
+
if (res.writableFinished) return;
|
|
703
|
+
try { agentBroker.cancel(callId, 'agent broker client disconnected'); } catch {}
|
|
704
|
+
});
|
|
705
|
+
try {
|
|
706
|
+
const result = await agentBroker.dispatch(body.params || {}, { callId });
|
|
707
|
+
sendJson(res, { ok: true, result });
|
|
708
|
+
} catch (error) {
|
|
709
|
+
sendJson(res, { ok: false, error: error?.message || String(error) }, 200);
|
|
710
|
+
}
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (req.method === 'POST' && pathName === '/agent/cancel') {
|
|
714
|
+
if (!agentBroker?.cancel) { sendError(res, 'agent broker unavailable', 503); return; }
|
|
715
|
+
const body = await readBody(req);
|
|
716
|
+
const callId = String(body.callId || '').trim();
|
|
717
|
+
if (!callId) { sendError(res, 'callId required', 400); return; }
|
|
718
|
+
const cancelled = agentBroker.cancelAndWait
|
|
719
|
+
? await agentBroker.cancelAndWait(
|
|
720
|
+
callId,
|
|
721
|
+
String(body.reason || 'memory agent dispatch canceled'),
|
|
722
|
+
)
|
|
723
|
+
: agentBroker.cancel(callId, String(body.reason || 'memory agent dispatch canceled'));
|
|
724
|
+
sendJson(res, {
|
|
725
|
+
ok: true,
|
|
726
|
+
cancelled,
|
|
727
|
+
});
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
652
730
|
if (req.method === 'POST' && pathName === '/call') {
|
|
653
731
|
const body = await readBody(req);
|
|
654
732
|
const clientToken = body.token || null;
|
|
@@ -660,13 +738,21 @@ export function createChannelDaemonTransport({
|
|
|
660
738
|
c.lastSeen = nowMs();
|
|
661
739
|
const name = String(body.name || '');
|
|
662
740
|
const callId = body.callId ? String(body.callId) : null;
|
|
741
|
+
const addressedSessionId = String(body.args?.sessionId || '').trim()
|
|
742
|
+
|| remoteSessionIdFromBinding(name, body.args || {});
|
|
743
|
+
const ownerKey = addressedSessionId
|
|
744
|
+
? `session:${addressedSessionId}`
|
|
745
|
+
: c.leadPid
|
|
746
|
+
? `pid:${c.leadPid}`
|
|
747
|
+
: `client:${clientToken}`;
|
|
748
|
+
const cacheKey = callId ? `${ownerKey}\u0000${callId}` : null;
|
|
663
749
|
let dispatch;
|
|
664
|
-
if (
|
|
750
|
+
if (cacheKey && callCache.has(cacheKey)) {
|
|
665
751
|
// Replay of a retried call — dedup to the original run (exactly one
|
|
666
752
|
// side-effect) instead of dispatching handleCall a second time.
|
|
667
|
-
dispatch = callCache.get(
|
|
753
|
+
dispatch = callCache.get(cacheKey).promise;
|
|
668
754
|
} else {
|
|
669
|
-
|
|
755
|
+
const run = async () => {
|
|
670
756
|
const args = body.args || {};
|
|
671
757
|
const activationCall = name === ACTIVATE_TOOL;
|
|
672
758
|
const activating = activationCall && args.active === true;
|
|
@@ -748,15 +834,20 @@ export function createChannelDaemonTransport({
|
|
|
748
834
|
}
|
|
749
835
|
throw err;
|
|
750
836
|
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
837
|
+
};
|
|
838
|
+
const scheduler = BINDING_TOOLS.has(name) ? channelControlCalls : channelCalls;
|
|
839
|
+
dispatch = scheduler.enqueue(ownerKey, run);
|
|
840
|
+
if (cacheKey) {
|
|
841
|
+
const record = { promise: dispatch, at: nowMs() };
|
|
842
|
+
callCache.set(cacheKey, record);
|
|
754
843
|
// Start the TTL only once the call SETTLES: an in-flight call can
|
|
755
844
|
// outlive a fixed-from-dispatch TTL (e.g. a slow reply upload past
|
|
756
845
|
// 60s), and expiring its entry mid-flight would let a transport
|
|
757
846
|
// retry replay-miss and dispatch a second real side-effect.
|
|
758
847
|
dispatch.then(() => {}, () => {}).then(() => {
|
|
759
|
-
const t = setTimeout(() =>
|
|
848
|
+
const t = setTimeout(() => {
|
|
849
|
+
if (callCache.get(cacheKey) === record) callCache.delete(cacheKey);
|
|
850
|
+
}, CALL_CACHE_TTL_MS);
|
|
760
851
|
t.unref?.();
|
|
761
852
|
});
|
|
762
853
|
}
|
|
@@ -780,20 +871,6 @@ export function createChannelDaemonTransport({
|
|
|
780
871
|
}
|
|
781
872
|
}
|
|
782
873
|
|
|
783
|
-
function writeDiscovery() {
|
|
784
|
-
if (!discoveryPath) return;
|
|
785
|
-
try {
|
|
786
|
-
writeJsonAtomicSync(discoveryPath, {
|
|
787
|
-
pid: process.pid,
|
|
788
|
-
port: boundPort,
|
|
789
|
-
token: serverToken,
|
|
790
|
-
startedAt: Date.now(),
|
|
791
|
-
}, { compact: true });
|
|
792
|
-
} catch (err) {
|
|
793
|
-
log(`discovery write failed: ${err?.message || err}`);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
874
|
function start() {
|
|
798
875
|
return new Promise((resolve, reject) => {
|
|
799
876
|
server = http.createServer(handleRequest);
|
|
@@ -803,7 +880,6 @@ export function createChannelDaemonTransport({
|
|
|
803
880
|
server.removeListener('error', reject);
|
|
804
881
|
boundPort = server.address().port;
|
|
805
882
|
server.on('error', (err) => log(`server error: ${err?.message || err}`));
|
|
806
|
-
writeDiscovery();
|
|
807
883
|
publishRemoteOwnerState();
|
|
808
884
|
log(`daemon transport listening on 127.0.0.1:${boundPort} pid=${process.pid}`);
|
|
809
885
|
resolve({ port: boundPort, token: serverToken });
|
|
@@ -813,6 +889,8 @@ export function createChannelDaemonTransport({
|
|
|
813
889
|
|
|
814
890
|
async function stop() {
|
|
815
891
|
closed = true;
|
|
892
|
+
channelCalls.close('channel transport is closed');
|
|
893
|
+
channelControlCalls.close('channel transport is closed');
|
|
816
894
|
cancelGrace();
|
|
817
895
|
if (sweepTimer) { try { clearInterval(sweepTimer); } catch {} sweepTimer = null; }
|
|
818
896
|
for (const [token] of clients) dropClient(token, 'transport stop');
|
|
@@ -820,7 +898,6 @@ export function createChannelDaemonTransport({
|
|
|
820
898
|
pointerToken = null;
|
|
821
899
|
publishRemoteOwnerState();
|
|
822
900
|
clearRegistrationReplays();
|
|
823
|
-
if (discoveryPath) { try { rmSync(discoveryPath, { force: true }); } catch {} }
|
|
824
901
|
if (server) {
|
|
825
902
|
await new Promise((resolve) => { try { server.close(() => resolve()); } catch { resolve(); } });
|
|
826
903
|
server = null;
|
|
@@ -831,11 +908,15 @@ export function createChannelDaemonTransport({
|
|
|
831
908
|
start,
|
|
832
909
|
stop,
|
|
833
910
|
notify,
|
|
911
|
+
beginDrain,
|
|
834
912
|
get port() { return boundPort; },
|
|
835
913
|
get token() { return serverToken; },
|
|
836
|
-
// The unified daemon hosts channels AND
|
|
914
|
+
// The unified daemon hosts channels AND session runtimes: it may only self-shutdown
|
|
837
915
|
// when BOTH sides are empty, so each transport has to expose its liveness.
|
|
838
916
|
get clientCount() { return clients.size; },
|
|
917
|
+
get activeCount() { return channelCalls.active + channelControlCalls.active; },
|
|
918
|
+
get queuedCount() { return channelCalls.queued + channelControlCalls.queued; },
|
|
919
|
+
get draining() { return Boolean(drainingReason); },
|
|
839
920
|
_clientsForTest: clients,
|
|
840
921
|
_registrationReplaysForTest: registrationReplays,
|
|
841
922
|
_resolveTargetForTest: resolveTarget,
|