mixdog 0.9.1 → 0.9.2

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 (214) hide show
  1. package/package.json +8 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/task-bench.mjs +207 -0
  23. package/scripts/tool-failures.mjs +6 -6
  24. package/scripts/tool-smoke.mjs +306 -66
  25. package/scripts/toolcall-args-test.mjs +81 -0
  26. package/src/agents/debugger/AGENT.md +4 -4
  27. package/src/agents/heavy-worker/AGENT.md +4 -2
  28. package/src/agents/reviewer/AGENT.md +4 -4
  29. package/src/agents/worker/AGENT.md +4 -2
  30. package/src/app.mjs +10 -6
  31. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  32. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  33. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  34. package/src/headless-role.mjs +14 -14
  35. package/src/help.mjs +1 -0
  36. package/src/lib/rules-builder.cjs +32 -54
  37. package/src/mixdog-session-runtime.mjs +710 -318
  38. package/src/output-styles/default.md +12 -7
  39. package/src/output-styles/minimal.md +25 -0
  40. package/src/output-styles/oneline.md +21 -0
  41. package/src/output-styles/simple.md +10 -9
  42. package/src/repl.mjs +12 -2
  43. package/src/rules/agent/00-common.md +7 -5
  44. package/src/rules/agent/30-explorer.md +7 -8
  45. package/src/rules/lead/01-general.md +3 -1
  46. package/src/rules/lead/lead-tool.md +7 -0
  47. package/src/rules/shared/01-tool.md +17 -12
  48. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  51. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  52. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  53. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  54. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  56. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  57. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  58. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  59. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  62. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
  64. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  65. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  66. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  67. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  69. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
  70. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
  71. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
  72. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  73. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  74. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
  76. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  77. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  78. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  80. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  81. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  82. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  83. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  84. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  85. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  86. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  87. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  88. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  90. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  91. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  92. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  95. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  96. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  99. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  100. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  101. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  102. package/src/runtime/channels/backends/discord.mjs +99 -9
  103. package/src/runtime/channels/backends/telegram.mjs +501 -0
  104. package/src/runtime/channels/index.mjs +441 -1224
  105. package/src/runtime/channels/lib/config.mjs +54 -2
  106. package/src/runtime/channels/lib/format.mjs +4 -2
  107. package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
  108. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  109. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  110. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  111. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  112. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  113. package/src/runtime/channels/lib/webhook.mjs +59 -31
  114. package/src/runtime/channels/tool-defs.mjs +1 -1
  115. package/src/runtime/memory/index.mjs +184 -19
  116. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  117. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  118. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  119. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  120. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  121. package/src/runtime/memory/lib/memory.mjs +101 -4
  122. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  123. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  124. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  125. package/src/runtime/memory/tool-defs.mjs +6 -3
  126. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  127. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  128. package/src/runtime/shared/config.mjs +9 -0
  129. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  130. package/src/runtime/shared/schedules-store.mjs +21 -19
  131. package/src/runtime/shared/tool-surface.mjs +98 -13
  132. package/src/runtime/shared/transcript-writer.mjs +129 -0
  133. package/src/runtime/shared/update-checker.mjs +214 -0
  134. package/src/standalone/agent-tool.mjs +255 -109
  135. package/src/standalone/channel-admin.mjs +133 -40
  136. package/src/standalone/channel-worker.mjs +8 -291
  137. package/src/standalone/explore-tool.mjs +2 -2
  138. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  139. package/src/standalone/provider-admin.mjs +11 -0
  140. package/src/standalone/usage-dashboard.mjs +1 -1
  141. package/src/tui/App.jsx +2096 -732
  142. package/src/tui/components/ConfirmBar.jsx +47 -0
  143. package/src/tui/components/ContextPanel.jsx +5 -3
  144. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  145. package/src/tui/components/Markdown.jsx +22 -98
  146. package/src/tui/components/Message.jsx +14 -35
  147. package/src/tui/components/Picker.jsx +87 -12
  148. package/src/tui/components/PromptInput.jsx +83 -7
  149. package/src/tui/components/QueuedCommands.jsx +1 -1
  150. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  151. package/src/tui/components/Spinner.jsx +7 -7
  152. package/src/tui/components/StatusLine.jsx +40 -21
  153. package/src/tui/components/TextEntryPanel.jsx +51 -7
  154. package/src/tui/components/ToolExecution.jsx +170 -98
  155. package/src/tui/components/TurnDone.jsx +4 -4
  156. package/src/tui/components/UsagePanel.jsx +1 -1
  157. package/src/tui/components/tool-output-format.mjs +159 -21
  158. package/src/tui/components/tool-output-format.test.mjs +87 -0
  159. package/src/tui/display-width.mjs +69 -0
  160. package/src/tui/display-width.test.mjs +35 -0
  161. package/src/tui/dist/index.mjs +6965 -2391
  162. package/src/tui/engine.mjs +287 -126
  163. package/src/tui/index.jsx +117 -7
  164. package/src/tui/keyboard-protocol.mjs +42 -0
  165. package/src/tui/lib/voice-recorder.mjs +453 -0
  166. package/src/tui/markdown/format-token.mjs +129 -76
  167. package/src/tui/markdown/format-token.test.mjs +61 -19
  168. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  169. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  170. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  171. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  172. package/src/tui/markdown/table-layout.mjs +9 -9
  173. package/src/tui/paste-attachments.mjs +0 -11
  174. package/src/tui/prompt-history-store.mjs +129 -0
  175. package/src/tui/prompt-history-store.test.mjs +52 -0
  176. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  177. package/src/tui/theme.mjs +41 -657
  178. package/src/tui/themes/base.mjs +86 -0
  179. package/src/tui/themes/basic.mjs +85 -0
  180. package/src/tui/themes/catppuccin.mjs +72 -0
  181. package/src/tui/themes/dracula.mjs +70 -0
  182. package/src/tui/themes/everforest.mjs +71 -0
  183. package/src/tui/themes/gruvbox.mjs +71 -0
  184. package/src/tui/themes/index.mjs +71 -0
  185. package/src/tui/themes/indigo.mjs +78 -0
  186. package/src/tui/themes/kanagawa.mjs +80 -0
  187. package/src/tui/themes/light.mjs +81 -0
  188. package/src/tui/themes/nord.mjs +72 -0
  189. package/src/tui/themes/onedark.mjs +16 -0
  190. package/src/tui/themes/rosepine.mjs +70 -0
  191. package/src/tui/themes/teal.mjs +81 -0
  192. package/src/tui/themes/tokyonight.mjs +79 -0
  193. package/src/tui/themes/utils.mjs +106 -0
  194. package/src/tui/themes/warm.mjs +79 -0
  195. package/src/tui/transcript-tool-failures.mjs +13 -2
  196. package/src/ui/markdown.mjs +1 -1
  197. package/src/ui/model-display.mjs +2 -2
  198. package/src/ui/statusline.mjs +26 -27
  199. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  200. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  201. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  202. package/src/workflows/default/WORKFLOW.md +39 -12
  203. package/src/workflows/sequential/WORKFLOW.md +46 -0
  204. package/src/workflows/solo/WORKFLOW.md +7 -0
  205. package/vendor/ink/build/display-width.js +62 -0
  206. package/vendor/ink/build/ink.js +100 -12
  207. package/vendor/ink/build/measure-text.js +4 -1
  208. package/vendor/ink/build/output.js +115 -9
  209. package/vendor/ink/build/render-node-to-output.js +4 -1
  210. package/vendor/ink/build/render.js +4 -0
  211. package/src/output-styles/extreme-simple.md +0 -20
  212. package/src/rules/lead/04-workflow.md +0 -51
  213. package/src/workflows/default/workflow.json +0 -13
  214. package/src/workflows/solo/workflow.json +0 -7
@@ -1,11 +1,9 @@
1
1
  import { execFile, fork, spawnSync } from 'node:child_process';
2
- import { appendFileSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
- import http from 'node:http';
2
+ import { appendFileSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
3
  import { tmpdir } from 'node:os';
5
4
  import { dirname, join, resolve } from 'node:path';
6
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
7
6
  import { startChildGuardian } from '../runtime/shared/child-guardian.mjs';
8
- import { claimSingletonOwner, readSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
9
7
 
10
8
  const CHANNEL_TOOLS = new Set([
11
9
  'reply',
@@ -56,67 +54,6 @@ function runtimeRoot() {
56
54
  return process.env.MIXDOG_RUNTIME_ROOT ? resolve(process.env.MIXDOG_RUNTIME_ROOT) : join(tmpdir(), 'mixdog');
57
55
  }
58
56
 
59
- function readJson(path) {
60
- try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
61
- }
62
-
63
- function parsePort(value) {
64
- const port = Number(value);
65
- return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
66
- }
67
-
68
- function readActiveInstance() {
69
- return readJson(join(runtimeRoot(), 'active-instance.json'));
70
- }
71
-
72
- function readOwnerSecret(instanceId) {
73
- const parsed = readJson(join(runtimeRoot(), `owner-secret-${String(instanceId)}.json`));
74
- return typeof parsed?.secret === 'string' ? parsed.secret : '';
75
- }
76
-
77
- function requestJson({ port, method = 'GET', path = '/', body = null, headers = {}, timeoutMs = 120_000 }) {
78
- return new Promise((resolvePromise, reject) => {
79
- const payload = body == null ? null : JSON.stringify(body);
80
- const req = http.request({
81
- hostname: '127.0.0.1',
82
- port,
83
- path,
84
- method,
85
- headers: {
86
- ...headers,
87
- ...(payload
88
- ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
89
- : {}),
90
- },
91
- timeout: timeoutMs,
92
- }, (res) => {
93
- let data = '';
94
- res.setEncoding('utf8');
95
- res.on('data', chunk => { data += chunk; });
96
- res.on('end', () => {
97
- let parsed = null;
98
- try { parsed = data ? JSON.parse(data) : null; } catch {}
99
- if (res.statusCode && res.statusCode >= 400) {
100
- reject(new Error(parsed?.error || data || `HTTP ${res.statusCode}`));
101
- return;
102
- }
103
- resolvePromise(parsed ?? { raw: data });
104
- });
105
- });
106
- req.on('error', reject);
107
- req.on('timeout', () => {
108
- req.destroy();
109
- reject(new Error(`channel owner request timed out: ${method} ${path}`));
110
- });
111
- if (payload) req.write(payload);
112
- req.end();
113
- });
114
- }
115
-
116
- function textResult(text, isError = false) {
117
- return { content: [{ type: 'text', text: String(text ?? '') }], ...(isError ? { isError: true } : {}) };
118
- }
119
-
120
57
  export function createStandaloneChannelWorker({
121
58
  entry,
122
59
  rootDir,
@@ -141,16 +78,8 @@ export function createStandaloneChannelWorker({
141
78
  const ownedChildPids = new Set();
142
79
  const logPath = join(dataDir, 'channels-worker-standalone.log');
143
80
  const useProcessWorker = process.env.MIXDOG_CHANNEL_WORKER_PROCESS !== '0';
144
- const singletonEnabled = process.env.MIXDOG_CHANNEL_SINGLETON !== '0';
145
- const daemonEnabled = process.env.MIXDOG_CHANNEL_DAEMON !== '0';
146
- const ownerPath = join(dataDir, 'channels-worker-owner.json');
147
- const notifyBusPath = join(runtimeRoot(), 'channel-notifications.jsonl');
148
81
  const clientDir = join(runtimeRoot(), 'channel-clients');
149
82
  const clientPath = join(clientDir, `${process.pid}.json`);
150
- let ownerClaim = null;
151
- let ownerPid = process.pid;
152
- let notifyBusTimer = null;
153
- let notifyBusOffset = 0;
154
83
  let clientHeartbeatTimer = null;
155
84
  let clientHeartbeatExitCleanup = null;
156
85
 
@@ -186,56 +115,9 @@ export function createStandaloneChannelWorker({
186
115
  try { rmSync(clientPath, { force: true }); } catch {}
187
116
  }
188
117
 
189
- function startNotifyBus() {
190
- if (!onNotify || notifyBusTimer) return;
191
- try { notifyBusOffset = statSync(notifyBusPath).size; } catch { notifyBusOffset = 0; }
192
- notifyBusTimer = setInterval(pollNotifyBus, 500);
193
- notifyBusTimer.unref?.();
194
- }
195
-
196
- function stopNotifyBus() {
197
- if (!notifyBusTimer) return;
198
- clearInterval(notifyBusTimer);
199
- notifyBusTimer = null;
200
- }
201
-
202
- function pollNotifyBus() {
203
- let size = 0;
204
- try { size = statSync(notifyBusPath).size; } catch { return; }
205
- if (size < notifyBusOffset) notifyBusOffset = 0;
206
- if (size === notifyBusOffset) return;
207
- let chunk;
208
- try {
209
- const data = readFileSync(notifyBusPath);
210
- chunk = data.subarray(notifyBusOffset, size).toString('utf8');
211
- notifyBusOffset = size;
212
- } catch {
213
- return;
214
- }
215
- for (const line of chunk.split(/\r?\n/)) {
216
- if (!line.trim()) continue;
217
- try {
218
- const msg = JSON.parse(line);
219
- if (!msg || typeof msg !== 'object') continue;
220
- onNotify({ type: 'notify', method: msg.method, params: msg.params, busId: msg.id, busPid: msg.pid });
221
- } catch {}
222
- }
223
- }
224
-
225
- startNotifyBus();
226
118
  startClientHeartbeat();
227
119
 
228
120
  function status() {
229
- const external = externalOwner();
230
- if (external) {
231
- return {
232
- running: false,
233
- pid: null,
234
- pending: pending.size,
235
- mode: 'external-owner',
236
- ownerPid: Number(external.pid) || null,
237
- };
238
- }
239
121
  if (!useProcessWorker) {
240
122
  return {
241
123
  running: Boolean(inProcessMod),
@@ -252,36 +134,6 @@ export function createStandaloneChannelWorker({
252
134
  };
253
135
  }
254
136
 
255
- function externalOwner() {
256
- if (!singletonEnabled || ownerClaim?.owned) return null;
257
- const current = readSingletonOwner(ownerPath);
258
- if (current.alive && current.owner?.pid && Number(current.owner.pid) !== process.pid) return current.owner;
259
- return null;
260
- }
261
-
262
- function ensureOwner() {
263
- if (!singletonEnabled || ownerClaim?.owned) return true;
264
- ownerClaim = claimSingletonOwner(ownerPath, {
265
- kind: 'channel-worker-host',
266
- pid: process.pid,
267
- meta: { cwd },
268
- });
269
- if (!ownerClaim.owned) {
270
- logLine(logPath, `runtime owned by pid ${ownerClaim.owner?.pid || 'unknown'}; skipping local channel worker`);
271
- return false;
272
- }
273
- installParentExitHook();
274
- return true;
275
- }
276
-
277
- function releaseOwner() {
278
- if (!singletonEnabled || !ownerClaim?.owned) return;
279
- releaseSingletonOwner(ownerPath, ownerPid);
280
- ownerClaim = null;
281
- ownerPid = process.pid;
282
- uninstallParentExitHook();
283
- }
284
-
285
137
  function rejectPending(error) {
286
138
  for (const [, item] of pending) {
287
139
  clearTimeout(item.timer);
@@ -292,7 +144,6 @@ export function createStandaloneChannelWorker({
292
144
 
293
145
  function start() {
294
146
  if (!useProcessWorker) return startInProcess();
295
- if (!ensureOwner()) return Promise.resolve(status());
296
147
  if (stopPromise) {
297
148
  return stopPromise.then(() => start());
298
149
  }
@@ -306,35 +157,21 @@ export function createStandaloneChannelWorker({
306
157
  cwd,
307
158
  execArgv: ['--require', WORKER_PRELOAD],
308
159
  stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
309
- detached: daemonEnabled,
160
+ detached: false,
310
161
  env: {
311
162
  ...process.env,
312
163
  MIXDOG_ROOT: rootDir,
313
164
  MIXDOG_DATA_DIR: dataDir,
314
165
  MIXDOG_STANDALONE: '1',
315
166
  MIXDOG_WORKER_MODE: '1',
316
- MIXDOG_CLI_OWNED: daemonEnabled ? '0' : '1',
317
- MIXDOG_CHANNEL_DAEMON: daemonEnabled ? '1' : '0',
318
- MIXDOG_CHANNELS_AUTO_BOOT: '0',
319
- MIXDOG_CHANNEL_FLAG: '1',
167
+ MIXDOG_CLI_OWNED: '0',
320
168
  MIXDOG_QUIET_SESSION_LOG: process.env.MIXDOG_QUIET_SESSION_LOG ?? '1',
321
169
  },
322
170
  windowsHide: true,
323
171
  });
324
172
  const spawnedPid = child.pid;
325
- if (daemonEnabled && spawnedPid && singletonEnabled) {
326
- releaseSingletonOwner(ownerPath, process.pid);
327
- ownerClaim = claimSingletonOwner(ownerPath, {
328
- kind: 'channel-worker-daemon',
329
- pid: spawnedPid,
330
- meta: { cwd, launcherPid: process.pid },
331
- });
332
- ownerPid = spawnedPid;
333
- }
334
- if (!daemonEnabled) {
335
- startChildGuardian({ childPid: spawnedPid, label: 'channel-worker', orphanGraceMs: 8000, forceGraceMs: 3000 });
336
- if (spawnedPid) ownedChildPids.add(spawnedPid);
337
- }
173
+ startChildGuardian({ childPid: spawnedPid, label: 'channel-worker', orphanGraceMs: 8000, forceGraceMs: 3000 });
174
+ if (spawnedPid) ownedChildPids.add(spawnedPid);
338
175
  installParentExitHook();
339
176
 
340
177
  child.stderr?.on('data', (chunk) => {
@@ -360,19 +197,12 @@ export function createStandaloneChannelWorker({
360
197
  return;
361
198
  }
362
199
  if (msg.type === 'notify') {
363
- if (!daemonEnabled) {
364
- try { onNotify?.(msg); } catch {}
365
- }
200
+ try { onNotify?.(msg); } catch {}
366
201
  }
367
202
  });
368
203
 
369
204
  child.on('exit', (code, signal) => {
370
205
  if (spawnedPid) ownedChildPids.delete(spawnedPid);
371
- if (daemonEnabled && spawnedPid) releaseSingletonOwner(ownerPath, spawnedPid);
372
- if (daemonEnabled && ownerPid === spawnedPid) {
373
- ownerClaim = null;
374
- ownerPid = process.pid;
375
- }
376
206
  const error = new Error(`channels runtime exited (${signal || (code ?? 'unknown')})`);
377
207
  if (readyReject) readyReject(error);
378
208
  readyResolve = null;
@@ -394,16 +224,12 @@ export function createStandaloneChannelWorker({
394
224
  }
395
225
 
396
226
  async function startInProcess() {
397
- if (!ensureOwner()) return status();
398
227
  if (inProcessMod) return status();
399
228
  if (inProcessStartPromise) return inProcessStartPromise;
400
229
  inProcessStartPromise = (async () => {
401
230
  process.env.MIXDOG_ROOT = rootDir;
402
231
  process.env.MIXDOG_DATA_DIR = dataDir;
403
232
  process.env.MIXDOG_STANDALONE ??= '1';
404
- process.env.MIXDOG_CHANNEL_FLAG ??= '1';
405
- process.env.MIXDOG_CHANNELS_AUTO_BOOT = '0';
406
- process.env.MIXDOG_CLI_OWNED = '1';
407
233
  const mod = await import(pathToFileURL(entry).href);
408
234
  if (typeof mod?.start !== 'function') throw new Error('channels runtime does not export start()');
409
235
  await mod.start();
@@ -417,9 +243,6 @@ export function createStandaloneChannelWorker({
417
243
 
418
244
  async function execute(name, args = {}, { timeoutMs = 120_000 } = {}) {
419
245
  if (!CHANNEL_TOOLS.has(name)) throw new Error(`unknown channel tool: ${name}`);
420
- if (!ensureOwner()) {
421
- return await executeViaOwnerHttp(name, args || {}, timeoutMs);
422
- }
423
246
  await start();
424
247
  if (!useProcessWorker) {
425
248
  const call = inProcessMod?.handleToolCallWithBridgeRetry || inProcessMod?.handleToolCall;
@@ -453,91 +276,6 @@ export function createStandaloneChannelWorker({
453
276
  });
454
277
  }
455
278
 
456
- async function executeViaOwnerHttp(name, args, timeoutMs) {
457
- const active = readActiveInstance();
458
- const port = parsePort(active?.httpPort);
459
- const instanceId = active?.instanceId;
460
- const token = readOwnerSecret(instanceId);
461
- if (!port || !token) {
462
- const owner = externalOwner();
463
- throw new Error(`channels runtime is owned by pid ${owner?.pid || 'unknown'} but owner HTTP is unavailable`);
464
- }
465
- const headers = {
466
- 'x-owner-token': token,
467
- 'x-owner-instance': String(instanceId || ''),
468
- };
469
- const post = (path, body) => requestJson({ port, method: 'POST', path, body, headers, timeoutMs });
470
- const get = (path) => requestJson({ port, method: 'GET', path, headers, timeoutMs });
471
-
472
- switch (name) {
473
- case 'reply': {
474
- const result = await post('/send', {
475
- chatId: args.chat_id,
476
- text: args.text,
477
- opts: {
478
- replyTo: args.reply_to,
479
- files: args.files ?? [],
480
- embeds: args.embeds ?? [],
481
- components: args.components ?? [],
482
- },
483
- });
484
- const ids = Array.isArray(result?.sentIds) ? result.sentIds : [];
485
- return textResult(ids.length === 1 ? `sent (id: ${ids[0]})` : `sent ${ids.length} parts (ids: ${ids.join(', ')})`);
486
- }
487
- case 'fetch': {
488
- const channel = encodeURIComponent(String(args.channel || ''));
489
- const limit = Math.max(1, Number(args.limit) || 20);
490
- const result = await get(`/fetch?channel=${channel}&limit=${limit}`);
491
- const msgs = Array.isArray(result?.messages) ? result.messages : [];
492
- const text = msgs.length === 0 ? '(no messages)' : msgs.map((m) => {
493
- const atts = Number(m.attachmentCount) > 0 ? ` +${m.attachmentCount}att` : '';
494
- return `[${m.ts}] ${m.user}: ${m.text} (id: ${m.id}${atts})`;
495
- }).join('\n');
496
- return textResult(text);
497
- }
498
- case 'react':
499
- await post('/react', { chatId: args.chat_id, messageId: args.message_id, emoji: args.emoji });
500
- return textResult('reacted');
501
- case 'edit_message': {
502
- const result = await post('/edit', {
503
- chatId: args.chat_id,
504
- messageId: args.message_id,
505
- text: args.text,
506
- opts: { embeds: args.embeds ?? [], components: args.components ?? [] },
507
- });
508
- return textResult(`edited (id: ${result?.id ?? ''})`);
509
- }
510
- case 'download_attachment': {
511
- const result = await post('/download', { chatId: args.chat_id, messageId: args.message_id });
512
- const files = Array.isArray(result?.files) ? result.files : [];
513
- if (files.length === 0) return textResult('message has no attachments');
514
- return textResult(`downloaded ${files.length} attachment(s):\n${files.map((f) => ` ${f.path} (${f.name}, ${f.contentType}, ${(Number(f.size || 0) / 1024).toFixed(0)}KB)`).join('\n')}`);
515
- }
516
- case 'schedule_status': {
517
- const result = await get('/schedule-status');
518
- return result?.result ?? textResult('no schedules configured');
519
- }
520
- case 'trigger_schedule': {
521
- const result = await post('/trigger-schedule', { name: args.name });
522
- return textResult(result?.result == null ? '' : String(result.result));
523
- }
524
- case 'schedule_control': {
525
- const result = await post('/schedule-control', { name: args.name, action: args.action, minutes: args.minutes });
526
- return result?.result ?? textResult(`unknown action: ${args.action}`, true);
527
- }
528
- case 'activate_channel_bridge':
529
- await post('/bridge/activate', { active: args.active === true });
530
- return textResult(`channel bridge ${args.active ? 'activated' : 'deactivated'}`);
531
- case 'inject_command':
532
- await post('/inject', { content: `/${String(args.command || '').trim()}`, source: 'mixdog-agent', type: 'command' });
533
- return textResult(`queued /${String(args.command || '').trim()} via channel owner`);
534
- case 'reload_config':
535
- return textResult('reload_config must run in the channel owner process', true);
536
- default:
537
- return textResult(`unknown channel tool: ${name}`, true);
538
- }
539
- }
540
-
541
279
  function forceKillTree(pid) {
542
280
  if (!pid) return;
543
281
  if (process.platform === 'win32') {
@@ -565,11 +303,8 @@ export function createStandaloneChannelWorker({
565
303
  if (parentExitCleanup) return;
566
304
  parentExitCleanup = registerChannelWorkerExitCleanup(() => {
567
305
  parentExitCleanup = null;
568
- if (!daemonEnabled) {
569
- for (const pid of Array.from(ownedChildPids)) {
570
- forceKillTreeSync(pid);
571
- }
572
- releaseOwner();
306
+ for (const pid of Array.from(ownedChildPids)) {
307
+ forceKillTreeSync(pid);
573
308
  }
574
309
  ownedChildPids.clear();
575
310
  });
@@ -592,12 +327,10 @@ export function createStandaloneChannelWorker({
592
327
 
593
328
  function stop(reason = 'standalone shutdown', options = {}) {
594
329
  const waitForExit = options?.waitForExit !== false;
595
- stopNotifyBus();
596
330
  stopClientHeartbeat();
597
331
  if (stopPromise) return stopPromise;
598
332
  if (!useProcessWorker) {
599
333
  if (!inProcessMod && !inProcessStartPromise) {
600
- releaseOwner();
601
334
  return Promise.resolve(false);
602
335
  }
603
336
  stopPromise = Promise.resolve(inProcessStartPromise)
@@ -605,7 +338,6 @@ export function createStandaloneChannelWorker({
605
338
  .then(async () => {
606
339
  try { await inProcessMod?.stop?.(reason); } catch {}
607
340
  inProcessMod = null;
608
- releaseOwner();
609
341
  return true;
610
342
  })
611
343
  .finally(() => {
@@ -614,23 +346,11 @@ export function createStandaloneChannelWorker({
614
346
  return stopPromise;
615
347
  }
616
348
  if (!child) {
617
- stopNotifyBus();
618
- releaseOwner();
619
349
  return Promise.resolve(false);
620
350
  }
621
351
  const target = child;
622
352
  const targetPid = target.pid;
623
353
  child = null;
624
- if (daemonEnabled && options?.force !== true) {
625
- rejectPending(new Error(`channels runtime detached (${reason})`));
626
- if (targetPid) ownedChildPids.delete(targetPid);
627
- try { target.disconnect?.(); } catch {}
628
- unrefChildHandles(target);
629
- ownerClaim = null;
630
- ownerPid = process.pid;
631
- uninstallParentExitHook();
632
- return Promise.resolve(true);
633
- }
634
354
  if (!waitForExit) {
635
355
  rejectPending(new Error(`channels runtime shutdown requested (${reason})`));
636
356
  if (targetPid) ownedChildPids.delete(targetPid);
@@ -642,7 +362,6 @@ export function createStandaloneChannelWorker({
642
362
  clearTimeout(sendTimer);
643
363
  stopPromise = null;
644
364
  unrefChildHandles(target);
645
- releaseOwner();
646
365
  uninstallParentExitHook();
647
366
  resolve(ok);
648
367
  };
@@ -667,8 +386,6 @@ export function createStandaloneChannelWorker({
667
386
  clearTimeout(termTimer);
668
387
  clearTimeout(killTimer);
669
388
  stopPromise = null;
670
- stopNotifyBus();
671
- releaseOwner();
672
389
  uninstallParentExitHook();
673
390
  resolve(ok);
674
391
  };
@@ -14,7 +14,7 @@ export const EXPLORE_TOOL = {
14
14
  name: 'explore',
15
15
  title: 'Explore',
16
16
  annotations: { title: 'Explore', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
17
- description: 'Repo anchor locator. Use for broad/uncertain paths. Array only for independent targets.',
17
+ description: 'Repo anchor locator. LLM-backed; broad/uncertain only. Array only for independent targets.',
18
18
  inputSchema: {
19
19
  type: 'object',
20
20
  properties: {
@@ -305,7 +305,7 @@ async function runExploreSync(args = {}, ctx = {}) {
305
305
  await ensureExploreProviderReady(config);
306
306
  const presetName = config?.maintenance?.explore || '';
307
307
  const llm = makeAgentDispatch({
308
- role: 'explorer',
308
+ agent: 'explorer',
309
309
  cwd: resolvedCwd,
310
310
  brief: true,
311
311
  parentSessionId: ctx.callerSessionId || null,
@@ -188,12 +188,14 @@ export function createStandaloneMemoryRuntime({
188
188
  releaseOwnerIfSelf();
189
189
  }
190
190
 
191
+ const daemonEnv = { ...process.env };
192
+ delete daemonEnv.MIXDOG_QUIET_MEMORY_LOG;
191
193
  child = fork(entry, [], {
192
194
  cwd,
193
195
  stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
194
196
  detached: true,
195
197
  env: {
196
- ...process.env,
198
+ ...daemonEnv,
197
199
  MIXDOG_DATA_DIR: dataDir,
198
200
  MIXDOG_WORKER_MODE: '1',
199
201
  MIXDOG_STANDALONE: '1',
@@ -52,6 +52,17 @@ const API_PROVIDER_IDS = new Set(API_PROVIDERS.map((p) => p.id));
52
52
  const OAUTH_BY_ID = new Map(OAUTH_PROVIDERS.map((p) => [p.id, p]));
53
53
  const LOCAL_BY_ID = new Map(LOCAL_PROVIDERS.map((p) => [p.id, p]));
54
54
 
55
+ const ALL_PROVIDER_IDS = new Set([
56
+ ...API_PROVIDERS.map((p) => p.id),
57
+ ...OAUTH_PROVIDERS.map((p) => p.id),
58
+ ...LOCAL_PROVIDERS.map((p) => p.id),
59
+ ]);
60
+
61
+ export function isKnownProvider(provider) {
62
+ const id = String(provider || '').trim();
63
+ return id !== '' && ALL_PROVIDER_IDS.has(id);
64
+ }
65
+
55
66
  async function detectLocalProvider(baseURL) {
56
67
  const url = String(baseURL || '').replace(/\/+$/, '') + '/models';
57
68
  const controller = new AbortController();
@@ -29,7 +29,7 @@ function clean(value) {
29
29
 
30
30
  function money(value) {
31
31
  const n = Number(value);
32
- if (!Number.isFinite(n)) return 'n/a';
32
+ if (!Number.isFinite(n)) return 'N/A';
33
33
  if (n === 0) return '$0';
34
34
  if (n >= 10) return `$${n.toFixed(0)}`;
35
35
  if (n >= 1) return `$${n.toFixed(2)}`;