mixdog 0.9.52 → 0.9.53

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 (111) hide show
  1. package/package.json +1 -1
  2. package/scripts/bench-run.mjs +2 -2
  3. package/scripts/compact-pressure-test.mjs +104 -0
  4. package/scripts/desktop-session-bridge-test.mjs +704 -0
  5. package/scripts/freevar-smoke.mjs +7 -4
  6. package/scripts/lifecycle-api-test.mjs +65 -4
  7. package/scripts/max-output-recovery-test.mjs +31 -0
  8. package/scripts/memory-core-input-test.mjs +10 -0
  9. package/scripts/openai-oauth-ws-1006-retry-test.mjs +63 -3
  10. package/scripts/openai-ws-early-settle-test.mjs +40 -0
  11. package/scripts/parent-abort-link-test.mjs +24 -0
  12. package/scripts/process-lifecycle-test.mjs +80 -22
  13. package/scripts/provider-contract-test.mjs +257 -0
  14. package/scripts/provider-toolcall-test.mjs +172 -10
  15. package/scripts/session-orphan-sweep-test.mjs +27 -1
  16. package/src/lib/keychain-cjs.cjs +36 -23
  17. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -13
  18. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
  19. package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
  20. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
  21. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -300
  22. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -4
  23. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +18 -266
  24. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +18 -1
  25. package/src/runtime/agent/orchestrator/providers/gemini.mjs +15 -130
  26. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +5 -115
  27. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
  28. package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
  29. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
  30. package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
  31. package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
  32. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -71
  33. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +47 -3
  34. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -7
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +72 -77
  36. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -20
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
  39. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +37 -28
  40. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -7
  41. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -0
  42. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
  43. package/src/runtime/agent/orchestrator/session/context-utils.mjs +20 -223
  44. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
  45. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -5
  46. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
  47. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
  48. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +24 -0
  49. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
  50. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
  51. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -1
  52. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
  53. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
  54. package/src/runtime/agent/orchestrator/session/store.mjs +305 -127
  55. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
  56. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
  57. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
  58. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
  59. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
  60. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
  61. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
  62. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +22 -298
  63. package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
  64. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -71
  65. package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
  66. package/src/runtime/channels/backends/discord.mjs +6 -6
  67. package/src/runtime/channels/lib/config.mjs +15 -2
  68. package/src/runtime/channels/lib/memory-client.mjs +20 -3
  69. package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
  70. package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
  71. package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
  72. package/src/runtime/channels/lib/worker-main.mjs +16 -5
  73. package/src/runtime/memory/index.mjs +24 -198
  74. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
  75. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
  76. package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
  77. package/src/runtime/memory/lib/pg/supervisor.mjs +0 -4
  78. package/src/runtime/memory/lib/query-handlers.mjs +2 -122
  79. package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
  80. package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
  81. package/src/runtime/search/lib/http-fetch.mjs +1 -1
  82. package/src/runtime/shared/config.mjs +58 -13
  83. package/src/runtime/shared/llm/cost.mjs +14 -4
  84. package/src/runtime/shared/memory-snapshot.mjs +236 -0
  85. package/src/runtime/shared/process-lifecycle.mjs +92 -19
  86. package/src/runtime/shared/process-shutdown.mjs +6 -0
  87. package/src/runtime/shared/resource-admission.mjs +7 -2
  88. package/src/session-runtime/channel-config-api.mjs +7 -7
  89. package/src/session-runtime/config-lifecycle.mjs +20 -17
  90. package/src/session-runtime/cwd-plugins.mjs +9 -7
  91. package/src/session-runtime/env.mjs +1 -2
  92. package/src/session-runtime/hitch-profile.mjs +45 -0
  93. package/src/session-runtime/lifecycle-api.mjs +36 -9
  94. package/src/session-runtime/mcp-glue.mjs +6 -11
  95. package/src/session-runtime/provider-init-key.mjs +17 -0
  96. package/src/session-runtime/runtime-core.mjs +44 -103
  97. package/src/session-runtime/runtime-paths.mjs +20 -0
  98. package/src/session-runtime/runtime-tool-routing.mjs +55 -0
  99. package/src/session-runtime/tool-catalog-data.mjs +51 -0
  100. package/src/session-runtime/tool-catalog.mjs +15 -89
  101. package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
  102. package/src/standalone/agent-tool/worker-rows.mjs +93 -0
  103. package/src/standalone/agent-tool.mjs +12 -162
  104. package/src/standalone/channel-admin.mjs +29 -0
  105. package/src/tui/App.jsx +11 -5
  106. package/src/tui/dist/index.mjs +202 -65
  107. package/src/tui/engine/session-api-ext.mjs +37 -4
  108. package/src/tui/index.jsx +1 -0
  109. package/src/tui/lib/voice-setup.mjs +5 -5
  110. package/scripts/_devtools-stub.mjs +0 -1
  111. package/src/runtime/lib/keychain-cjs.cjs +0 -288
@@ -1,288 +0,0 @@
1
- 'use strict';
2
-
3
- const { spawnSync } = require('child_process');
4
- const path = require('path');
5
- const fs = require('fs');
6
- const { resolvePluginData } = require('./plugin-paths.cjs');
7
-
8
- const SERVICE = 'mixdog';
9
- // Shared bound for every synchronous keychain backend (DPAPI/PowerShell on
10
- // Windows, security(1) on macOS). A single env override keeps them consistent.
11
- const KEYCHAIN_TIMEOUT_MS = Number(process.env.MIXDOG_KEYCHAIN_TIMEOUT_MS || 15000);
12
- const POWERSHELL_TIMEOUT_MS = KEYCHAIN_TIMEOUT_MS;
13
-
14
- // CommonJS module: cannot import the ESM src/shared/wsl.mjs, so inline an
15
- // equivalent WSL check (process.platform reports 'linux' inside WSL).
16
- function isWSL() {
17
- if (process.platform !== 'linux') return false;
18
- if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
19
- try { return /microsoft|wsl/i.test(fs.readFileSync('/proc/version', 'utf8')); }
20
- catch { return false; }
21
- }
22
-
23
- // ---------------------------------------------------------------------------
24
- // Helpers
25
- // ---------------------------------------------------------------------------
26
-
27
- function platform() {
28
- return process.platform;
29
- }
30
-
31
- function run(cmd, args, opts) {
32
- // windowsHide + stdio:['ignore','pipe','pipe'] keeps powershell.exe
33
- // consoleless during DPAPI ops. Without these flags every keychain
34
- // read/write flashes a conhost window: setup-html's loaders call
35
- // /agent/config and /memory/auth, and each credential probe can trigger
36
- // one powershell.exe spawn — users saw 8-15 console flashes during
37
- // config-UI page load.
38
- // Default opts go BEFORE the spread so callers can still override.
39
- const result = spawnSync(cmd, args, {
40
- encoding: 'utf8',
41
- shell: false,
42
- windowsHide: true,
43
- stdio: ['ignore', 'pipe', 'pipe'],
44
- ...opts,
45
- });
46
- return result;
47
- }
48
-
49
- // PS 5.1(powershell.exe) first: pwsh(PS 7) does not auto-load ProtectedData assembly,
50
- // so DPAPI calls silently fail there. Windows always ships powershell.exe.
51
- function powershell(script) {
52
- // Bound DPAPI PowerShell calls with a timeout: a hung powershell.exe
53
- // (AV scan stall, profile loader, transient cert chain lookup) would
54
- // otherwise block hook/server callers synchronously. The default is long
55
- // enough for cold PowerShell startup on Windows without silently dropping
56
- // setup UI credential saves.
57
- // Resolve powershell.exe to its deterministic System32 location rather than
58
- // a PATH lookup, so a shadow `powershell.exe` earlier in PATH cannot hijack
59
- // secret encryption/decryption. (pwsh/PS7 is intentionally not used: it does
60
- // not auto-load the ProtectedData assembly, so DPAPI silently fails there.)
61
- const _sysRoot = process.env.SystemRoot || process.env.windir;
62
- if (!_sysRoot) {
63
- throw new Error('[keychain] cannot resolve SystemRoot to locate powershell.exe for DPAPI');
64
- }
65
- const _psHosts = [path.join(_sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')];
66
- let timedOut = null;
67
- for (const exe of _psHosts) {
68
- const r = run(exe, ['-NonInteractive', '-NoProfile', '-Command', script], { timeout: POWERSHELL_TIMEOUT_MS });
69
- if (r.error && r.error.code === 'ENOENT') continue;
70
- if (r.error && r.error.code === 'ETIMEDOUT') {
71
- timedOut = { exe, timeoutMs: POWERSHELL_TIMEOUT_MS };
72
- continue;
73
- }
74
- return r;
75
- }
76
- if (timedOut) {
77
- throw new Error(`[keychain] PowerShell DPAPI command timed out after ${timedOut.timeoutMs}ms (last host: ${timedOut.exe})`);
78
- }
79
- const err = new Error('[keychain] PowerShell not found (tried powershell, pwsh)');
80
- throw err;
81
- }
82
-
83
- function secretsDir() {
84
- return path.join(resolvePluginData(), 'secrets');
85
- }
86
-
87
- function dpApiFile(account) {
88
- return path.join(secretsDir(), account + '.dpapi');
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // darwin — security(1)
93
- // ---------------------------------------------------------------------------
94
-
95
- // Bound security(1) calls so a stuck Keychain prompt (locked keychain, GUI
96
- // unlock dialog with no display) cannot block hook/server callers forever —
97
- // matching the Windows DPAPI and Linux keytar timeouts.
98
- function darwinRun(args) {
99
- const r = run('security', args, { timeout: KEYCHAIN_TIMEOUT_MS });
100
- if (r.error && r.error.code === 'ETIMEDOUT') {
101
- throw new Error(`[keychain] security command timed out after ${KEYCHAIN_TIMEOUT_MS}ms`);
102
- }
103
- return r;
104
- }
105
-
106
- function darwinGet(account) {
107
- const r = darwinRun(['find-generic-password', '-a', account, '-s', SERVICE, '-w']);
108
- if (r.status !== 0) return null;
109
- return r.stdout.trimEnd();
110
- }
111
-
112
- function darwinSet(account, value) {
113
- const r = darwinRun(['add-generic-password', '-a', account, '-s', SERVICE, '-w', value, '-U']);
114
- if (r.status !== 0) throw new Error(`[keychain] security set failed: ${r.stderr || r.stdout}`);
115
- }
116
-
117
- function darwinDelete(account) {
118
- const r = darwinRun(['delete-generic-password', '-a', account, '-s', SERVICE]);
119
- if (r.status !== 0) throw new Error(`[keychain] security delete failed: ${r.stderr || r.stdout}`);
120
- }
121
-
122
- // ---------------------------------------------------------------------------
123
- // linux/WSL — keytar (libsecret binding, optionalDependency)
124
- // ---------------------------------------------------------------------------
125
- // keytar must be installed: npm install keytar (requires libsecret-dev on
126
- // Debian/Ubuntu, or libsecret on other distros). If not installed, every
127
- // call throws immediately — silent credential loss is not permitted.
128
-
129
- let _keytarMod = null;
130
- function loadKeytar() {
131
- if (_keytarMod !== null) return _keytarMod;
132
- try { _keytarMod = require('keytar'); }
133
- catch {
134
- throw new Error(
135
- '[keychain] keytar is not installed — run: npm install keytar\n' +
136
- ' Requires libsecret-dev (Debian/Ubuntu) or libsecret (other distros).\n' +
137
- ' Cannot access credentials on this platform without it.' +
138
- (isWSL()
139
- ? '\n Detected WSL: there is usually no Secret Service here; ' +
140
- 'set the relevant MIXDOG_*/PROVIDER_API_KEY env var instead.'
141
- : '')
142
- );
143
- }
144
- return _keytarMod;
145
- }
146
-
147
- // keytar is Promise-based; bridge to sync via spawnSync of a child Node process.
148
- // Avoids Atomics.wait on main thread (which hangs when SAB is not forwarded to worker).
149
- function keytarSync(method, ...args) {
150
- loadKeytar(); // throws if not installed — before spawning child
151
- // Pass service/account/value via env to avoid shell injection entirely.
152
- const env = {
153
- ...process.env,
154
- _KEYTAR_METHOD: method,
155
- _KEYTAR_ARGS: JSON.stringify(args),
156
- };
157
- const script = [
158
- 'const kt = require("keytar");',
159
- 'const method = process.env._KEYTAR_METHOD;',
160
- 'const args = JSON.parse(process.env._KEYTAR_ARGS);',
161
- 'kt[method](...args)',
162
- ' .then(v => { process.stdout.write(JSON.stringify({ ok: true, value: v })); })',
163
- ' .catch(e => { process.stdout.write(JSON.stringify({ ok: false, error: e.message })); });',
164
- ].join(' ');
165
- const r = spawnSync(process.execPath, ['-e', script], {
166
- env,
167
- stdio: ['ignore', 'pipe', 'pipe'],
168
- timeout: 5000,
169
- encoding: 'utf8',
170
- windowsHide: true,
171
- });
172
- if (r.error) throw new Error(`[keychain] keytar.${method} spawnSync failed: ${r.error.message}`);
173
- if (r.status !== 0) {
174
- const detail = (r.stderr || '').trim() || `exit ${r.status}`;
175
- throw new Error(`[keychain] keytar.${method} child exited with error: ${detail}`);
176
- }
177
- let parsed;
178
- try { parsed = JSON.parse(r.stdout); } catch {
179
- throw new Error(`[keychain] keytar.${method} child returned unparseable output: ${String(r.stdout).slice(0, 200)}`);
180
- }
181
- if (!parsed.ok) throw new Error(`[keychain] keytar.${method} failed: ${parsed.error}`);
182
- return parsed.value ?? null;
183
- }
184
-
185
- function linuxGet(account) {
186
- return keytarSync('getPassword', SERVICE, account);
187
- }
188
-
189
- function linuxSet(account, value) {
190
- keytarSync('setPassword', SERVICE, account, value);
191
- }
192
-
193
- function linuxDelete(account) {
194
- keytarSync('deletePassword', SERVICE, account);
195
- }
196
-
197
- // ---------------------------------------------------------------------------
198
- // win32 — DPAPI via PowerShell
199
- // ---------------------------------------------------------------------------
200
-
201
- // Add-Type loads System.Security on PS 5.1 hosts where the assembly is not
202
- // auto-loaded — ProtectedData/DataProtectionScope resolve to TypeNotFound
203
- // otherwise, and the host returns exit 0 with empty stdout, which then falls
204
- // through win32Set's empty-output guard. PS 7+ already has the type loaded;
205
- // SilentlyContinue covers the benign "already loaded" case on repeat calls.
206
- const PS_PROTECT = [
207
- 'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;',
208
- '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;',
209
- '$value = $args[0];',
210
- '$bytes = [System.Text.Encoding]::UTF8.GetBytes($value);',
211
- '$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser;',
212
- '$enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, $scope);',
213
- '[Convert]::ToBase64String($enc)',
214
- ].join(' ');
215
-
216
- const PS_UNPROTECT = [
217
- 'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;',
218
- '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;',
219
- '$b64 = $args[0];',
220
- '$enc = [Convert]::FromBase64String($b64);',
221
- '$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser;',
222
- '$dec = [System.Security.Cryptography.ProtectedData]::Unprotect($enc, $null, $scope);',
223
- '[System.Text.Encoding]::UTF8.GetString($dec)',
224
- ].join(' ');
225
-
226
- function win32Get(account) {
227
- const file = dpApiFile(account);
228
- if (!fs.existsSync(file)) return null;
229
- const b64 = fs.readFileSync(file, 'utf8').trim();
230
- if (!b64) throw new Error(`[keychain] DPAPI ciphertext file is empty: ${file}`);
231
- const r = powershell(`& { ${PS_UNPROTECT} } '${b64}'`);
232
- if (r.status !== 0) throw new Error(`[keychain] DPAPI decrypt failed (exit ${r.status}): ${r.stderr || r.stdout}`);
233
- const out = (r.stdout || '').trim();
234
- if (!out) throw new Error(`[keychain] DPAPI decrypt returned empty output (stderr: ${(r.stderr || '').trim()})`);
235
- return out;
236
- }
237
-
238
- function win32Set(account, value) {
239
- const dir = secretsDir();
240
- fs.mkdirSync(dir, { recursive: true });
241
- // Pass value as a PS argument via -Command inline to avoid shell injection;
242
- // value is embedded as a PS single-quoted string literal with ' escaped.
243
- const escaped = value.replace(/'/g, "''");
244
- const r = powershell(`& { ${PS_PROTECT} } '${escaped}'`);
245
- if (r.status !== 0) throw new Error(`[keychain] DPAPI encrypt failed (exit ${r.status}): ${r.stderr || r.stdout}`);
246
- const out = (r.stdout || '').trim();
247
- if (!out) throw new Error(`[keychain] DPAPI encrypt returned empty output (stderr: ${(r.stderr || '').trim()})`);
248
- fs.writeFileSync(dpApiFile(account), out, 'utf8');
249
- }
250
-
251
- function win32Delete(account) {
252
- const file = dpApiFile(account);
253
- if (!fs.existsSync(file)) throw new Error(`[keychain] secret not found: ${account}`);
254
- fs.unlinkSync(file);
255
- }
256
-
257
- // ---------------------------------------------------------------------------
258
- // Public API
259
- // ---------------------------------------------------------------------------
260
-
261
- function getSecret(account) {
262
- switch (platform()) {
263
- case 'darwin': return darwinGet(account);
264
- case 'linux': return linuxGet(account);
265
- case 'win32': return win32Get(account);
266
- default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
267
- }
268
- }
269
-
270
- function setSecret(account, value) {
271
- switch (platform()) {
272
- case 'darwin': return darwinSet(account, value);
273
- case 'linux': return linuxSet(account, value);
274
- case 'win32': return win32Set(account, value);
275
- default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
276
- }
277
- }
278
-
279
- function deleteSecret(account) {
280
- switch (platform()) {
281
- case 'darwin': return darwinDelete(account);
282
- case 'linux': return linuxDelete(account);
283
- case 'win32': return win32Delete(account);
284
- default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
285
- }
286
- }
287
-
288
- module.exports = { getSecret, setSecret, deleteSecret, SERVICE };