mixdog 0.7.11 → 0.7.12

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.
Files changed (41) hide show
  1. package/.claude-plugin/marketplace.json +5 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +193 -249
  4. package/bin/statusline-launcher.mjs +5 -1
  5. package/bin/statusline-lib.mjs +14 -6
  6. package/bin/statusline.mjs +14 -6
  7. package/hooks/lib/settings-loader.cjs +4 -3
  8. package/hooks/pre-tool-subagent.cjs +7 -2
  9. package/hooks/session-start.cjs +52 -24
  10. package/lib/mixdog-debug.cjs +163 -0
  11. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  12. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  13. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  14. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  15. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  16. package/package.json +1 -1
  17. package/scripts/builtin-utils-smoke.mjs +14 -8
  18. package/scripts/bump.mjs +80 -0
  19. package/scripts/doctor.mjs +8 -3
  20. package/scripts/mutation-io-smoke.mjs +17 -1
  21. package/scripts/permission-eval-smoke.mjs +18 -1
  22. package/scripts/statusline-launcher-smoke.mjs +2 -2
  23. package/scripts/webhook-selfheal-smoke.mjs +1 -3
  24. package/server-main.mjs +57 -3
  25. package/setup/install.mjs +574 -574
  26. package/setup/setup-server.mjs +10 -2
  27. package/setup/setup.html +43 -8
  28. package/src/agent/orchestrator/providers/openai-oauth.mjs +9 -2
  29. package/src/agent/orchestrator/providers/openai-ws.mjs +23 -0
  30. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +29 -8
  31. package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
  32. package/src/agent/orchestrator/tools/patch-manifest.json +11 -11
  33. package/src/channels/index.mjs +27 -8
  34. package/src/channels/lib/event-queue.mjs +24 -1
  35. package/src/channels/lib/hook-pipe-server.mjs +21 -8
  36. package/src/channels/lib/webhook.mjs +142 -20
  37. package/src/memory/lib/memory-cycle1.mjs +7 -3
  38. package/src/memory/lib/memory-recall-store.mjs +27 -10
  39. package/src/search/lib/backends/openai-oauth.mjs +6 -2
  40. package/src/search/lib/cache.mjs +55 -7
  41. package/scripts/test-config-rmw-restore.mjs +0 -122
@@ -70,7 +70,23 @@ async function warmRead(path, scope, offset = 0, limit = 20) {
70
70
  }
71
71
 
72
72
  async function rmTree(path) {
73
- await rm(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
73
+ // Windows CI occasionally keeps a just-used temp file locked briefly after the
74
+ // owning JS/native subprocess exits (EACCES/EBUSY/EPERM). Cleanup is not part
75
+ // of the smoke invariant, so retry with backoff and make final teardown
76
+ // best-effort instead of turning a passed mutation suite red.
77
+ const delays = [50, 100, 200, 400, 800];
78
+ let lastErr = null;
79
+ for (let i = 0; i <= delays.length; i++) {
80
+ try {
81
+ await rm(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
82
+ return;
83
+ } catch (err) {
84
+ lastErr = err;
85
+ if (!['EACCES', 'EBUSY', 'EPERM', 'ENOTEMPTY'].includes(err?.code)) break;
86
+ if (i < delays.length) await new Promise((resolve) => setTimeout(resolve, delays[i]));
87
+ }
88
+ }
89
+ console.warn(`mutation-io smoke cleanup warning: failed to remove ${path}: ${lastErr?.code || ''} ${lastErr?.message || lastErr}`);
74
90
  }
75
91
 
76
92
  try {
@@ -10,6 +10,23 @@ import path from 'path';
10
10
  import { createRequire } from 'module';
11
11
  import { fileURLToPath } from 'url';
12
12
 
13
+ // The evaluator intentionally merges user-global Claude settings. Local dev
14
+ // machines commonly have defaultMode=bypassPermissions, which made this smoke
15
+ // pass locally while CI's empty user config exercised stricter defaults. Pin
16
+ // the user tier to a throwaway Claude config dir so the smoke is hermetic and
17
+ // still verifies the intended invariant: hard-deny and explicit list rules win
18
+ // before bypass mode.
19
+ const smokeHome = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'perm-smoke-home-')));
20
+ const smokeConfigDir = path.join(smokeHome, '.claude');
21
+ fs.mkdirSync(smokeConfigDir, { recursive: true });
22
+ fs.writeFileSync(
23
+ path.join(smokeConfigDir, 'settings.json'),
24
+ JSON.stringify({ permissions: { defaultMode: 'bypassPermissions' } }),
25
+ );
26
+ process.env.HOME = smokeHome;
27
+ process.env.USERPROFILE = smokeHome;
28
+ process.env.CLAUDE_CONFIG_DIR = smokeConfigDir;
29
+
13
30
  const _require = createRequire(import.meta.url);
14
31
  const evalPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../hooks/lib/permission-evaluator.cjs');
15
32
  const { evaluatePermission } = _require(evalPath);
@@ -21,7 +38,7 @@ const { loadPermissions, clearSettingsCache } = _require(loaderPath);
21
38
  let pass = 0, fail = 0;
22
39
 
23
40
  function makeProject(settings = {}) {
24
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'perm-smoke-'));
41
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'perm-smoke-')));
25
42
  fs.mkdirSync(path.join(dir, '.claude'), { recursive: true });
26
43
  if (Object.keys(settings).length) {
27
44
  fs.writeFileSync(
@@ -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
- if (!process.env.CLAUDE_PLUGIN_DATA) {
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 = _agentIpcQueue.shift()
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
- _agentIpcQueue.push({ msg, worker, proc })
1129
+ const lane = _classifyAgentIpcJobLane(msg, worker)
1130
+ _agentIpcQueue.push({ msg, worker, proc, lane })
1077
1131
  _drainAgentIpcQueue()
1078
1132
  }
1079
1133