mixdog 0.7.11 → 0.7.13
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/.claude-plugin/marketplace.json +5 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +28 -74
- package/README.md +193 -249
- package/bin/statusline-launcher.mjs +5 -1
- package/bin/statusline-lib.mjs +14 -6
- package/bin/statusline.mjs +14 -6
- package/bun.lock +128 -3
- package/defaults/hidden-roles.json +3 -0
- package/defaults/user-workflow.json +1 -2
- package/defaults/user-workflow.md +5 -1
- package/hooks/lib/settings-loader.cjs +4 -3
- package/hooks/pre-tool-subagent.cjs +7 -2
- package/hooks/session-start.cjs +52 -24
- package/lib/mixdog-debug.cjs +163 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +9 -2
- package/scripts/builtin-utils-smoke.mjs +14 -8
- package/scripts/bump.mjs +80 -0
- package/scripts/doctor.mjs +8 -3
- package/scripts/ensure-deps.mjs +2 -2
- package/scripts/mutation-io-smoke.mjs +17 -1
- package/scripts/permission-eval-smoke.mjs +18 -1
- package/scripts/run-mcp.mjs +65 -9
- package/scripts/statusline-launcher-smoke.mjs +2 -2
- package/scripts/webhook-selfheal-smoke.mjs +1 -3
- package/server-main.mjs +57 -3
- package/setup/install.mjs +574 -574
- package/setup/launch-core.mjs +0 -1
- package/setup/setup-server.mjs +90 -35
- package/setup/setup.html +44 -11
- package/skills/setup/SKILL.md +12 -2
- package/src/agent/index.mjs +1 -1
- package/src/agent/orchestrator/config.mjs +58 -6
- package/src/agent/orchestrator/providers/model-catalog.mjs +1 -1
- package/src/agent/orchestrator/providers/openai-oauth.mjs +9 -2
- package/src/agent/orchestrator/providers/openai-ws.mjs +23 -0
- package/src/agent/orchestrator/session/loop.mjs +3 -3
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +6 -2
- package/src/agent/orchestrator/tools/bash-session.mjs +1 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +29 -6
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +8 -4
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +29 -8
- package/src/agent/orchestrator/tools/builtin.mjs +5 -2
- package/src/agent/orchestrator/tools/cwd-tool.mjs +17 -17
- package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/agent/tool-defs.mjs +1 -1
- package/src/channels/index.mjs +39 -9
- package/src/channels/lib/event-queue.mjs +24 -1
- package/src/channels/lib/hook-pipe-server.mjs +21 -8
- package/src/channels/lib/webhook.mjs +159 -20
- package/src/memory/index.mjs +5 -1
- package/src/memory/lib/core-memory-store.mjs +1 -1
- package/src/memory/lib/memory-cycle1.mjs +8 -4
- package/src/memory/lib/memory-cycle2.mjs +1 -1
- package/src/memory/lib/memory-cycle3.mjs +1 -1
- package/src/memory/lib/memory-recall-store.mjs +27 -10
- package/src/search/lib/backends/openai-oauth.mjs +6 -2
- package/src/search/lib/cache.mjs +55 -7
- package/tools.json +2 -2
- package/scripts/test-config-rmw-restore.mjs +0 -122
package/scripts/run-mcp.mjs
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
ensureRuntimeDeps,
|
|
27
27
|
hasRequiredDeps,
|
|
28
28
|
renameWithRetrySync,
|
|
29
|
+
RENAME_RETRY_CODES,
|
|
30
|
+
sleepSync,
|
|
29
31
|
} from './ensure-deps.mjs';
|
|
30
32
|
|
|
31
33
|
// Stable per-terminal session id for this proxy supervisor's lifetime. The
|
|
@@ -453,6 +455,12 @@ let childHasResponded = false;
|
|
|
453
455
|
let announceListChangedOnReady = false;
|
|
454
456
|
let cachedInitRequest = null; // { id, params } from client's first initialize
|
|
455
457
|
let cachedInitDone = false; // initialized notification observed from client
|
|
458
|
+
// One-shot latch: flips true the instant the client's initialize response is
|
|
459
|
+
// forwarded and never resets. It selects replayInitToChild's mode (real-id vs
|
|
460
|
+
// swallow) and tells handleChildGone whether the pending initialize may be
|
|
461
|
+
// flushed. Until it is true the client has NOT seen its initialize result, so
|
|
462
|
+
// that request must survive a child death and be re-driven, not errored.
|
|
463
|
+
let clientInitAnswered = false;
|
|
456
464
|
let internalIdSeq = -1; // negative ids reserved for supervisor-internal requests
|
|
457
465
|
const pendingFromClient = new Map(); // request id (from client) → { method }
|
|
458
466
|
const pendingInternal = new Set(); // internal ids (init replay) — drop responses
|
|
@@ -670,14 +678,33 @@ function flushPendingClientErrors(tag) {
|
|
|
670
678
|
|
|
671
679
|
function replayInitToChild() {
|
|
672
680
|
if (!cachedInitRequest) return;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
+
// Mode select by invariant — has the client's initialize been answered yet?
|
|
682
|
+
// • Already answered (steady-state respawn): the client is fully
|
|
683
|
+
// initialized and must NOT see a second result. Replay under an internal
|
|
684
|
+
// negative id and swallow the response.
|
|
685
|
+
// • Not yet (first-boot / handshake-time crash): replay under the client's
|
|
686
|
+
// OWN id. Its request is still pending (preserved across the child-gone
|
|
687
|
+
// flush in handleChildGone), so the new child's initialize response flows
|
|
688
|
+
// back through the normal forward path — the client sees one clean result
|
|
689
|
+
// instead of the -32603 that previously killed the connection and forced
|
|
690
|
+
// a manual /mcp reconnect.
|
|
691
|
+
if (clientInitAnswered) {
|
|
692
|
+
const internalId = internalIdSeq--;
|
|
693
|
+
pendingInternal.add(internalId);
|
|
694
|
+
writeToChild(JSON.stringify({
|
|
695
|
+
jsonrpc: '2.0',
|
|
696
|
+
id: internalId,
|
|
697
|
+
method: 'initialize',
|
|
698
|
+
params: cachedInitRequest.params,
|
|
699
|
+
}));
|
|
700
|
+
} else {
|
|
701
|
+
writeToChild(JSON.stringify({
|
|
702
|
+
jsonrpc: '2.0',
|
|
703
|
+
id: cachedInitRequest.id,
|
|
704
|
+
method: 'initialize',
|
|
705
|
+
params: cachedInitRequest.params,
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
681
708
|
if (cachedInitDone) {
|
|
682
709
|
// Notification — no id, no response expected.
|
|
683
710
|
writeToChild(JSON.stringify({
|
|
@@ -816,7 +843,13 @@ function handleChildLine(line) {
|
|
|
816
843
|
for (const item of scanned) {
|
|
817
844
|
if (item && item.id !== undefined) {
|
|
818
845
|
if (pendingInternal.has(item.id)) { internalIds.add(item.id); pendingInternal.delete(item.id); _maybeResolveLivenessPong(item.id); }
|
|
819
|
-
else {
|
|
846
|
+
else {
|
|
847
|
+
pendingFromClient.delete(item.id);
|
|
848
|
+
// Same latch as the scalar path below — keep the invariant consistent
|
|
849
|
+
// even if the client's initialize ever returns inside a batch, so a
|
|
850
|
+
// later respawn swallows its replay instead of re-driving the real id.
|
|
851
|
+
if (cachedInitRequest && item.id === cachedInitRequest.id) clientInitAnswered = true;
|
|
852
|
+
}
|
|
820
853
|
}
|
|
821
854
|
}
|
|
822
855
|
if (internalIds.size) {
|
|
@@ -846,6 +879,10 @@ function handleChildLine(line) {
|
|
|
846
879
|
return;
|
|
847
880
|
}
|
|
848
881
|
pendingFromClient.delete(scanned.id);
|
|
882
|
+
// The client's initialize is satisfied the instant its response is
|
|
883
|
+
// forwarded. Latch it so the next respawn swallows its replay (steady
|
|
884
|
+
// state) and so handleChildGone is free to error this id on a later death.
|
|
885
|
+
if (cachedInitRequest && scanned.id === cachedInitRequest.id) clientInitAnswered = true;
|
|
849
886
|
}
|
|
850
887
|
writeToClient(line);
|
|
851
888
|
}
|
|
@@ -896,10 +933,29 @@ function handleChildGone(why) {
|
|
|
896
933
|
}
|
|
897
934
|
const _pendingClientAtGone = pendingFromClient.size;
|
|
898
935
|
const _pendingInternalAtGone = pendingInternal.size;
|
|
936
|
+
// First-boot recovery invariant: the client's initialize must receive exactly
|
|
937
|
+
// one success response, from whichever child completes the handshake. If it
|
|
938
|
+
// has not been answered yet (clientInitAnswered=false), erroring it here makes
|
|
939
|
+
// the client mark the MCP server failed — it never re-issues initialize on its
|
|
940
|
+
// own, and the replay (internal id) never reaches it. That is the "startup
|
|
941
|
+
// fails, /mcp fixes it" symptom. Keep that single id pending across the flush;
|
|
942
|
+
// replayInitToChild re-drives it under the client's own id against the fresh
|
|
943
|
+
// child so the success response flows straight back. shuttingDown never
|
|
944
|
+
// preserves (the supervisor is exiting; nothing will replay).
|
|
945
|
+
const _preserveInitId = (!shuttingDown && !clientInitAnswered && cachedInitRequest)
|
|
946
|
+
? cachedInitRequest.id
|
|
947
|
+
: undefined;
|
|
948
|
+
const _preservedInit = _preserveInitId !== undefined
|
|
949
|
+
? pendingFromClient.get(_preserveInitId)
|
|
950
|
+
: undefined;
|
|
899
951
|
for (const [id] of pendingFromClient) {
|
|
952
|
+
if (id === _preserveInitId) continue;
|
|
900
953
|
sendErrorToClient(id, -32603, `[run-mcp] mcp child ${why.tag}; retry`);
|
|
901
954
|
}
|
|
902
955
|
pendingFromClient.clear();
|
|
956
|
+
if (_preserveInitId !== undefined && _preservedInit !== undefined) {
|
|
957
|
+
pendingFromClient.set(_preserveInitId, _preservedInit);
|
|
958
|
+
}
|
|
903
959
|
pendingInternal.clear();
|
|
904
960
|
// Fresh child = fresh response path; discard any in-flight liveness probe.
|
|
905
961
|
_livenessPingId = null;
|
|
@@ -27,7 +27,7 @@ const tmp = mkdtempSync(join(tmpdir(), 'mixdog-sl-launcher-'));
|
|
|
27
27
|
function run(homeDir) {
|
|
28
28
|
return spawnSync(process.execPath, [LAUNCHER], {
|
|
29
29
|
input: SAMPLE,
|
|
30
|
-
env: { ...process.env, HOME: homeDir, USERPROFILE: homeDir },
|
|
30
|
+
env: { ...process.env, HOME: homeDir, USERPROFILE: homeDir, CLAUDE_CONFIG_DIR: join(homeDir, '.claude') },
|
|
31
31
|
encoding: 'utf8',
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -60,7 +60,7 @@ try {
|
|
|
60
60
|
+ "process.stdout.write(await renderStatusLine(i));\n"
|
|
61
61
|
);
|
|
62
62
|
const directRun = spawnSync(process.execPath, [driver],
|
|
63
|
-
{ input: SAMPLE, env: { ...process.env, HOME: tmp, USERPROFILE: tmp }, encoding: 'utf8' });
|
|
63
|
+
{ input: SAMPLE, env: { ...process.env, HOME: tmp, USERPROFILE: tmp, CLAUDE_CONFIG_DIR: join(tmp, '.claude') }, encoding: 'utf8' });
|
|
64
64
|
const direct = directRun.stdout || '';
|
|
65
65
|
|
|
66
66
|
const ok = run(tmp);
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
process.env.CLAUDE_PLUGIN_DATA = join(tmpdir(), `mixdog-webhook-selfheal-smoke-${process.pid}`);
|
|
10
|
-
}
|
|
8
|
+
process.env.CLAUDE_PLUGIN_DATA = join(tmpdir(), `mixdog-webhook-selfheal-smoke-${process.pid}`);
|
|
11
9
|
|
|
12
10
|
const { decidePortReclaimAction } = await import('../src/channels/lib/webhook.mjs');
|
|
13
11
|
|
package/server-main.mjs
CHANGED
|
@@ -1040,10 +1040,62 @@ async function _getBridgeLlmFactory() {
|
|
|
1040
1040
|
// completion after the worker stopped waiting for the result.
|
|
1041
1041
|
const AGENT_IPC_MAX_CONCURRENT = 2
|
|
1042
1042
|
const _agentIpcInflight = new Map()
|
|
1043
|
-
/** @type {Array<{ msg: object, worker: string, proc: import('child_process').ChildProcess }>} */
|
|
1043
|
+
/** @type {Array<{ msg: object, worker: string, proc: import('child_process').ChildProcess, lane?: string }>} */
|
|
1044
1044
|
const _agentIpcQueue = []
|
|
1045
1045
|
let _agentIpcRunning = 0
|
|
1046
1046
|
|
|
1047
|
+
const AGENT_IPC_LANE_USER = 'user'
|
|
1048
|
+
const AGENT_IPC_LANE_MAINTENANCE = 'maintenance'
|
|
1049
|
+
/** @type {Set<string>} */
|
|
1050
|
+
const AGENT_IPC_MAINTENANCE_WORKERS = new Set(['memory'])
|
|
1051
|
+
|
|
1052
|
+
function _normalizeAgentIpcLaneToken(v) {
|
|
1053
|
+
return typeof v === 'string' ? v.trim().toLowerCase() : ''
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function _isMaintenanceBridgeRole(role) {
|
|
1057
|
+
const r = _normalizeAgentIpcLaneToken(role)
|
|
1058
|
+
if (!r) return false
|
|
1059
|
+
if (r === 'maintenance' || r === 'scheduler-task' || r === 'webhook-handler' || r === 'memory-classification') {
|
|
1060
|
+
return true
|
|
1061
|
+
}
|
|
1062
|
+
if (r.startsWith('cycle')) return true
|
|
1063
|
+
return false
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** @returns {'user'|'maintenance'} */
|
|
1067
|
+
function _classifyAgentIpcJobLane(msg, worker) {
|
|
1068
|
+
const lane = _normalizeAgentIpcLaneToken(msg?.lane)
|
|
1069
|
+
if (lane === AGENT_IPC_LANE_MAINTENANCE || lane === 'maint') return AGENT_IPC_LANE_MAINTENANCE
|
|
1070
|
+
if (lane === AGENT_IPC_LANE_USER) return AGENT_IPC_LANE_USER
|
|
1071
|
+
|
|
1072
|
+
const priority = _normalizeAgentIpcLaneToken(msg?.priority)
|
|
1073
|
+
if (priority === AGENT_IPC_LANE_MAINTENANCE || priority === 'maint') return AGENT_IPC_LANE_MAINTENANCE
|
|
1074
|
+
if (priority === AGENT_IPC_LANE_USER) return AGENT_IPC_LANE_USER
|
|
1075
|
+
|
|
1076
|
+
if (msg?.maintenance === true || msg?.isMaintenance === true) return AGENT_IPC_LANE_MAINTENANCE
|
|
1077
|
+
|
|
1078
|
+
const params = msg?.params && typeof msg.params === 'object' ? msg.params : {}
|
|
1079
|
+
if (_isMaintenanceBridgeRole(params.role)) return AGENT_IPC_LANE_MAINTENANCE
|
|
1080
|
+
|
|
1081
|
+
const w = _normalizeAgentIpcLaneToken(worker)
|
|
1082
|
+
if (AGENT_IPC_MAINTENANCE_WORKERS.has(w)) return AGENT_IPC_LANE_MAINTENANCE
|
|
1083
|
+
|
|
1084
|
+
return AGENT_IPC_LANE_USER
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function _dequeueHighestPriorityAgentIpcJob() {
|
|
1088
|
+
if (_agentIpcQueue.length === 0) return null
|
|
1089
|
+
for (let i = 0; i < _agentIpcQueue.length; i++) {
|
|
1090
|
+
const job = _agentIpcQueue[i]
|
|
1091
|
+
const lane = job.lane || _classifyAgentIpcJobLane(job.msg, job.worker)
|
|
1092
|
+
if (lane !== AGENT_IPC_LANE_MAINTENANCE) {
|
|
1093
|
+
return _agentIpcQueue.splice(i, 1)[0]
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return _agentIpcQueue.shift()
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1047
1099
|
function _sendAgentIpcResponse(proc, callId, body) {
|
|
1048
1100
|
try { proc.send({ type: 'agent_ipc_response', callId, ...body }) } catch {}
|
|
1049
1101
|
}
|
|
@@ -1066,14 +1118,16 @@ function _startAgentIpcJob(job) {
|
|
|
1066
1118
|
|
|
1067
1119
|
function _drainAgentIpcQueue() {
|
|
1068
1120
|
while (_agentIpcRunning < AGENT_IPC_MAX_CONCURRENT && _agentIpcQueue.length > 0) {
|
|
1069
|
-
const job =
|
|
1121
|
+
const job = _dequeueHighestPriorityAgentIpcJob()
|
|
1122
|
+
if (!job) break
|
|
1070
1123
|
_agentIpcRunning++
|
|
1071
1124
|
_startAgentIpcJob(job)
|
|
1072
1125
|
}
|
|
1073
1126
|
}
|
|
1074
1127
|
|
|
1075
1128
|
function _enqueueAgentIpcRequest(msg, worker, proc) {
|
|
1076
|
-
|
|
1129
|
+
const lane = _classifyAgentIpcJobLane(msg, worker)
|
|
1130
|
+
_agentIpcQueue.push({ msg, worker, proc, lane })
|
|
1077
1131
|
_drainAgentIpcQueue()
|
|
1078
1132
|
}
|
|
1079
1133
|
|