mixdog 0.9.0 → 0.9.1

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 (57) hide show
  1. package/package.json +3 -3
  2. package/scripts/session-ingest-smoke.mjs +2 -2
  3. package/src/headless-role.mjs +1 -1
  4. package/src/lib/mixdog-debug.cjs +0 -22
  5. package/src/lib/plugin-paths.cjs +1 -7
  6. package/src/lib/rules-builder.cjs +2 -2
  7. package/src/mixdog-session-runtime.mjs +0 -1
  8. package/src/repl.mjs +0 -2
  9. package/src/runtime/agent/orchestrator/internal-roles.mjs +1 -1
  10. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +27 -49
  11. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +5 -20
  12. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +8 -27
  13. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +52 -182
  14. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +8 -30
  15. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +258 -0
  16. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -8
  18. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
  19. package/src/runtime/channels/index.mjs +0 -30
  20. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  21. package/src/runtime/channels/lib/config.mjs +0 -1
  22. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  23. package/src/runtime/channels/lib/executor.mjs +0 -3
  24. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  25. package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
  26. package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
  27. package/src/runtime/channels/lib/session-discovery.mjs +0 -4
  28. package/src/runtime/channels/lib/tool-format.mjs +0 -1
  29. package/src/runtime/channels/lib/transcript-discovery.mjs +1 -10
  30. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  31. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  32. package/src/runtime/memory/index.mjs +3 -24
  33. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  34. package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
  35. package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
  36. package/src/runtime/memory/lib/session-ingest.mjs +9 -7
  37. package/src/runtime/search/index.mjs +2 -7
  38. package/src/runtime/search/lib/config.mjs +0 -4
  39. package/src/runtime/search/lib/state.mjs +1 -15
  40. package/src/runtime/search/lib/web-tools.mjs +0 -1
  41. package/src/runtime/shared/child-spawn-gate.mjs +0 -6
  42. package/src/standalone/seeds.mjs +1 -11
  43. package/src/tui/App.jsx +35 -12
  44. package/src/tui/components/PromptInput.jsx +63 -2
  45. package/src/tui/components/ToolExecution.jsx +7 -2
  46. package/src/tui/components/tool-output-format.mjs +156 -22
  47. package/src/tui/components/tool-output-format.test.mjs +93 -1
  48. package/src/tui/dist/index.mjs +473 -116
  49. package/src/tui/markdown/format-token.mjs +267 -108
  50. package/src/tui/markdown/format-token.test.mjs +105 -9
  51. package/src/tui/theme.mjs +10 -0
  52. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
  53. package/vendor/ink/build/ink.js +54 -8
  54. package/src/hooks/lib/permission-rules.cjs +0 -170
  55. package/src/hooks/lib/settings-loader.cjs +0 -112
  56. package/src/lib/hook-pipe-path.cjs +0 -10
  57. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
@@ -33,7 +33,7 @@ import {
33
33
  PROVIDER_HTTP_RESPONSE_TIMEOUT_MS,
34
34
  createTimeoutSignal,
35
35
  } from '../stall-policy.mjs';
36
- import { populateHttpStatusFromMessage } from './retry-classifier.mjs';
36
+ import { populateHttpStatusFromMessage, shouldFallbackTransport } from './retry-classifier.mjs';
37
37
  import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
38
38
  import { makeInvalidToolArgsMarker } from './openai-compat-stream.mjs';
39
39
  import {
@@ -742,36 +742,14 @@ function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey }) {
742
742
  return headers;
743
743
  }
744
744
 
745
+ // WS→HTTP/SSE fallback predicate → shared shouldFallbackTransport
746
+ // (retry-classifier.mjs). The per-provider env flag is computed here and passed
747
+ // as `enabled`; the deny-order + allow-list are identical to the former copy.
745
748
  function _shouldUseOpenAIHttpFallback(err, externalSignal) {
746
- if (!_envFlag('MIXDOG_OPENAI_OAUTH_HTTP_FALLBACK', true)) return false;
747
- if (externalSignal?.aborted) return false;
748
- // Live-text invariant: if the WS attempt already relayed a non-empty text
749
- // chunk to the client, the HTTP fallback would re-run the request and
750
- // concatenate a second attempt onto rendered output. Never fall back.
751
- if (err?.liveTextEmitted === true) return false;
752
- if (err?.emittedToolCall === true || err?.unsafeToRetry === true) return false;
753
- const status = Number(err?.httpStatus || err?.status || 0);
754
- if (status === 401 || status === 403 || status === 404 || status === 429) return false;
755
- if (status >= 500 && status < 600) return true;
756
- const code = String(err?.code || '');
757
- if (['EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE'].includes(code)) {
758
- return true;
759
- }
760
- const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '');
761
- if ([
762
- 'timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx',
763
- 'first_byte_timeout', 'first_meaningful_timeout',
764
- 'ws_1006', 'ws_1011', 'ws_1012', 'ws_1000', 'ws_4000', 'agent_stall', 'stream_stalled',
765
- 'response_failed_disconnected', 'response_failed_network', 'response_failed_auth_expired',
766
- 'ws_send_failed',
767
- ].includes(classifier)) {
768
- return true;
769
- }
770
- if (/^http_5\d\d$/.test(classifier)) return true;
771
- if (err?.firstByteTimeout) return true;
772
- if (err?.firstMeaningfulTimeout) return true;
773
- const msg = String(err?.message || '');
774
- return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg);
749
+ return shouldFallbackTransport(err, {
750
+ signal: externalSignal,
751
+ enabled: _envFlag('MIXDOG_OPENAI_OAUTH_HTTP_FALLBACK', true),
752
+ });
775
753
  }
776
754
 
777
755
  // Exported for the single-emit regression smoke (scripts/openai-oauth-
@@ -231,6 +231,264 @@ export function jitterDelayMs(ms, ratio = PROVIDER_RETRY_JITTER_RATIO, mode = 's
231
231
  return Math.max(0, Math.round(base + offset))
232
232
  }
233
233
 
234
+ // ── Shared network-resilience interface ──────────────────────────────────────
235
+ // One home for the logic that used to be copied per provider: mid-stream
236
+ // classifier (WS + SSE), transport fallback predicate, stream-safety stamp
237
+ // latches, abort-aware sleep, handshake classifier, and the retry-budget table.
238
+ // Provider differences are passed as ARGUMENTS (policy objects), never branched
239
+ // on a hardcoded provider name. Behavior is preserved EXACTLY — each path below
240
+ // is the relocated original, selected by policy.mode.
241
+
242
+ // F) Retry-budget profiles as DATA. The numbers live ONLY here now.
243
+ // ws.transientCloseRetries (4) — ws_1006 / ws_1011 connection-loss buckets.
244
+ // ws.defaultRetries (2) — every other WS mid-stream bucket.
245
+ // sse.defaultRetries (3) — anthropic single-shot SSE mid-stream budget.
246
+ export const MIDSTREAM_RETRY_POLICY = {
247
+ ws: { transientCloseRetries: 4, defaultRetries: 2, backoff: [250, 1000, 2000, 4000] },
248
+ sse: { defaultRetries: 3, backoff: [250, 1000, 2000, 4000] },
249
+ }
250
+
251
+ // WS buckets that earn the larger transient-close retry budget.
252
+ const WS_TRANSIENT_CLOSE_CLASSIFIERS = new Set(['ws_1006', 'ws_1011'])
253
+
254
+ function _midstreamLimitFor(classifier, policy) {
255
+ if (policy.mode === 'ws') {
256
+ return WS_TRANSIENT_CLOSE_CLASSIFIERS.has(classifier)
257
+ ? policy.transientCloseRetries
258
+ : policy.defaultRetries
259
+ }
260
+ return policy.defaultRetries
261
+ }
262
+
263
+ // WS gates each classifier against its own budget (mirrors the old
264
+ // _allowMidstreamRetry). SSE applies a single top-of-function budget gate and
265
+ // then returns raw classifier strings, so perClassifierGate:false returns the
266
+ // classifier unconditionally here.
267
+ function _allowMidstream(classifier, attemptIndex, policy) {
268
+ if (policy.perClassifierGate === false) return classifier
269
+ return attemptIndex < _midstreamLimitFor(classifier, policy) ? classifier : null
270
+ }
271
+
272
+ // A) Unified mid-stream classifier. Returns a classifier string or null.
273
+ // `signals` is the provider's mid-stream state object (field names unchanged
274
+ // from each provider's midState). `policy.mode` selects the WS or SSE path so
275
+ // both providers reproduce their exact current branch order and gating.
276
+ export function classifyMidstreamError(err, signals, policy = {}) {
277
+ if (!signals) return null
278
+ const attemptIndex = signals.attemptIndex | 0
279
+ if (policy.mode === 'sse') return _classifyMidstreamSse(err, signals, attemptIndex, policy)
280
+ return _classifyMidstreamWs(err, signals, attemptIndex, policy)
281
+ }
282
+
283
+ // Verbatim relocation of openai-oauth-ws's _classifyMidstreamError. `signals`
284
+ // carries sawResponseCreated / sawCompleted / emittedText / emittedToolCall /
285
+ // wsCloseCode / firstByteTimeout / firstMeaningfulTimeout / wsSendFailed /
286
+ // userAbort / watchdogAbort / responseFailedPayload exactly as before.
287
+ function _classifyMidstreamWs(err, state, attemptIndex, policy) {
288
+ if (state.sawCompleted) return null
289
+ if (state.emittedToolCall) {
290
+ const _cc = Number(err?.wsCloseCode || state.wsCloseCode || 0)
291
+ if (!(_cc === 1000 && state.sawResponseCreated && !state.sawCompleted)) return null
292
+ }
293
+ if (state.emittedText || err?.liveTextEmitted) return null
294
+ if (state.firstByteTimeout || err?.firstByteTimeout) {
295
+ return _allowMidstream('first_byte_timeout', attemptIndex, policy)
296
+ }
297
+ if (state.firstMeaningfulTimeout || err?.firstMeaningfulTimeout) {
298
+ return _allowMidstream('first_meaningful_timeout', attemptIndex, policy)
299
+ }
300
+ if (err?.wsSendFailed || state.wsSendFailed) {
301
+ return _allowMidstream('ws_send_failed', attemptIndex, policy)
302
+ }
303
+ if (!state.sawResponseCreated) {
304
+ const closeCode = Number(err?.wsCloseCode || state.wsCloseCode || 0)
305
+ if (closeCode !== 1011 && closeCode !== 1012) return null
306
+ }
307
+ if (state.userAbort) return null
308
+
309
+ if (!err) return null
310
+ const status = Number(err?.httpStatus || 0)
311
+ if (status === 401 || status === 403 || status === 429) return null
312
+ if (status >= 500 && status < 600) {
313
+ return _allowMidstream(`http_${status}`, attemptIndex, policy)
314
+ }
315
+
316
+ const name = err?.name || ''
317
+ if (name === 'AgentStallAbortError') return _allowMidstream('agent_stall', attemptIndex, policy)
318
+ if (name === 'StreamStalledAbortError') return _allowMidstream('stream_stalled', attemptIndex, policy)
319
+
320
+ if (state.watchdogAbort === 'AgentStallAbortError') return _allowMidstream('agent_stall', attemptIndex, policy)
321
+ if (state.watchdogAbort === 'StreamStalledAbortError') return _allowMidstream('stream_stalled', attemptIndex, policy)
322
+
323
+ const closeCode = Number(err?.wsCloseCode || state.wsCloseCode || 0)
324
+ if (closeCode === 1006) return _allowMidstream('ws_1006', attemptIndex, policy)
325
+ if (closeCode === 1011) return _allowMidstream('ws_1011', attemptIndex, policy)
326
+ if (closeCode === 1012) return _allowMidstream('ws_1012', attemptIndex, policy)
327
+ if (closeCode >= 4000 && closeCode < 5000 && closeCode !== 4000) return null
328
+ if (closeCode === 4000) return _allowMidstream('ws_4000', attemptIndex, policy)
329
+ if (closeCode === 1000 && state.sawResponseCreated && !state.sawCompleted) return _allowMidstream('ws_1000', attemptIndex, policy)
330
+
331
+ const failed = err?.responseFailed || state.responseFailedPayload
332
+ if (failed) {
333
+ try {
334
+ const blob = JSON.stringify(failed).toLowerCase()
335
+ if (blob.includes('stream_disconnected')) return _allowMidstream('response_failed_disconnected', attemptIndex, policy)
336
+ if (blob.includes('network_error')) return _allowMidstream('response_failed_network', attemptIndex, policy)
337
+ if (blob.includes('auth context expired')) return _allowMidstream('response_failed_auth_expired', attemptIndex, policy)
338
+ } catch {}
339
+ }
340
+
341
+ return null
342
+ }
343
+
344
+ // Verbatim relocation of anthropic-oauth's _classifyMidstreamError. `signals`
345
+ // carries sawMessageStart / sawCompleted / emittedToolCall / userAbort /
346
+ // watchdogAbort exactly as before.
347
+ function _classifyMidstreamSse(err, state, attemptIndex, policy) {
348
+ if (attemptIndex >= policy.defaultRetries) return null
349
+ if (state.sawCompleted) return null
350
+ if (!state.sawMessageStart) return null
351
+ if (state.userAbort) return null
352
+ if (state.emittedToolCall) return null
353
+
354
+ if (!err) return null
355
+ const status = Number(err?.httpStatus || 0)
356
+ if (status === 401 || status === 403 || status === 429) return null
357
+
358
+ const name = err?.name || ''
359
+ if (name === 'AgentStallAbortError') return 'agent_stall'
360
+ if (name === 'StreamStalledAbortError') return 'stream_stalled'
361
+ if (state.watchdogAbort === 'AgentStallAbortError') return 'agent_stall'
362
+ if (state.watchdogAbort === 'StreamStalledAbortError') return 'stream_stalled'
363
+
364
+ const code = err?.code || err?.cause?.code || ''
365
+ if (code === 'ECONNRESET') return 'reset'
366
+ if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT') return 'timeout'
367
+ if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'EAI_NODATA') return 'dns'
368
+
369
+ const msg = String(err?.message || '').toLowerCase()
370
+ if (msg.includes('stream timed out after') && msg.includes('of inactivity')) return 'sse_idle_timeout'
371
+ if (msg.includes('body stream') && msg.includes('terminated')) return 'stream_terminated'
372
+ if (msg.includes('fetch failed')) return 'fetch_failed'
373
+
374
+ return null
375
+ }
376
+
377
+ // B) Unified transport (WS→HTTP) fallback predicate. Identical deny-order +
378
+ // allow-list to the two former copies; `enabled` replaces the per-provider
379
+ // env-flag check (caller computes the flag and passes it).
380
+ const TRANSPORT_FALLBACK_CLASSIFIERS = new Set([
381
+ 'timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx',
382
+ 'first_byte_timeout', 'first_meaningful_timeout',
383
+ 'ws_1006', 'ws_1011', 'ws_1012', 'ws_1000', 'ws_4000', 'agent_stall', 'stream_stalled',
384
+ 'response_failed_disconnected', 'response_failed_network', 'response_failed_auth_expired',
385
+ 'ws_send_failed',
386
+ ])
387
+ const TRANSPORT_FALLBACK_ERRNO = new Set([
388
+ 'EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN',
389
+ 'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE',
390
+ ])
391
+
392
+ export function shouldFallbackTransport(err, { signal, enabled = true } = {}) {
393
+ if (!enabled) return false
394
+ if (signal?.aborted) return false
395
+ if (err?.liveTextEmitted === true) return false
396
+ if (err?.emittedToolCall === true || err?.unsafeToRetry === true) return false
397
+ const status = Number(err?.httpStatus || err?.status || 0)
398
+ if (status === 401 || status === 403 || status === 404 || status === 429) return false
399
+ if (status >= 500 && status < 600) return true
400
+ const code = String(err?.code || '')
401
+ if (TRANSPORT_FALLBACK_ERRNO.has(code)) return true
402
+ const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '')
403
+ if (TRANSPORT_FALLBACK_CLASSIFIERS.has(classifier)) return true
404
+ if (/^http_5\d\d$/.test(classifier)) return true
405
+ if (err?.firstByteTimeout) return true
406
+ if (err?.firstMeaningfulTimeout) return true
407
+ const msg = String(err?.message || '')
408
+ return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg)
409
+ }
410
+
411
+ // C) Stream-safety stamp latches. Mirrors openai-oauth-ws's _stampLiveText /
412
+ // _stampTool: once text/tool has been marked, every subsequent throw path
413
+ // re-applies the liveTextEmitted/emittedToolCall + unsafeToRetry markers so
414
+ // no upstream gate can reissue the turn and concatenate attempts.
415
+ export function createStreamSafetyStamps() {
416
+ let textLatched = false
417
+ let toolLatched = false
418
+ const stampText = (e) => {
419
+ if (textLatched && e) { try { e.liveTextEmitted = true; e.unsafeToRetry = true } catch {} }
420
+ return e
421
+ }
422
+ const stampTool = (e) => {
423
+ if (toolLatched && e) { try { e.emittedToolCall = true; e.unsafeToRetry = true } catch {} }
424
+ return e
425
+ }
426
+ return {
427
+ markText() { textLatched = true },
428
+ markTool() { toolLatched = true },
429
+ stampText,
430
+ stampTool,
431
+ stampAll: (e) => stampTool(stampText(e)),
432
+ }
433
+ }
434
+
435
+ const _defaultAbortSleep = (ms) => new Promise((r) => setTimeout(r, ms))
436
+
437
+ // D) Abort-aware sleep (single copy). Resolves after `ms`, or rejects with the
438
+ // signal's reason (or `abortMessage`) the moment the signal aborts. `sleepFn`
439
+ // is the injectable no-signal sleep (test seam); `abortMessage` preserves
440
+ // each caller's prior fallback text when the abort reason is not an Error.
441
+ export async function sleepWithAbort(ms, signal, sleepFn = _defaultAbortSleep, abortMessage = 'sleep aborted') {
442
+ if (!ms) return
443
+ if (!signal) {
444
+ await (sleepFn || _defaultAbortSleep)(ms)
445
+ return
446
+ }
447
+ await new Promise((resolve, reject) => {
448
+ const t = setTimeout(() => {
449
+ try { signal.removeEventListener('abort', onAbort) } catch {}
450
+ resolve()
451
+ }, ms)
452
+ const onAbort = () => {
453
+ clearTimeout(t)
454
+ const reason = signal.reason
455
+ reject(reason instanceof Error ? reason : new Error(abortMessage))
456
+ }
457
+ if (signal.aborted) { onAbort(); return }
458
+ signal.addEventListener('abort', onAbort, { once: true })
459
+ })
460
+ }
461
+
462
+ // E) Handshake classifier (moved here from openai-oauth-ws). Default-deny:
463
+ // anything not recognized as transient returns null. 401/403/404/429 are
464
+ // permanent. Returns 'timeout' | 'reset' | 'dns' | 'refused' | 'network' |
465
+ // 'acquire_timeout' | `http_5xx` | null.
466
+ export function classifyHandshakeError(err) {
467
+ if (!err) return null
468
+ const code = err.code || ''
469
+ const msg = String(err.message || '')
470
+ const status = Number(err.httpStatus || 0)
471
+
472
+ if (status === 401 || status === 403 || status === 404 || status === 429) {
473
+ return null
474
+ }
475
+ if (status >= 500 && status < 600) {
476
+ return `http_${status}`
477
+ }
478
+
479
+ if (code === 'ECONNRESET') return 'reset'
480
+ if (code === 'EAI_AGAIN' || code === 'ENOTFOUND' || code === 'EAI_NODATA') return 'dns'
481
+ if (code === 'ECONNREFUSED') return 'refused'
482
+ if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT') return 'timeout'
483
+ if (code === 'EWSACQUIRETIMEOUT') return 'acquire_timeout'
484
+ if (code === 'ENETUNREACH' || code === 'EHOSTUNREACH' || code === 'EPIPE') return 'network'
485
+
486
+ if (/opening handshake has timed out/i.test(msg)) return 'timeout'
487
+ if (/socket hang up/i.test(msg)) return 'reset'
488
+
489
+ return null
490
+ }
491
+
234
492
  /**
235
493
  * Run an async function with exponential-backoff retry on transient errors.
236
494
  *
@@ -44,7 +44,7 @@ import { invalidateBuiltinResultCache, analyzeShellCommandEffects, preflightShel
44
44
  import { markCodeGraphDirtyPaths, drainCodeGraphCache } from './code-graph-state.mjs';
45
45
  import { maybeRewriteWmicProcessCommand } from './shell-policy.mjs';
46
46
  import { _maybeEncodePowerShellCommand } from './shell-command.mjs';
47
- import { _captureTrackedMtimes, _trackedDriftNoteAfter, _injectionBlockTargets, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
47
+ import { _captureTrackedMtimes, _trackedDriftNoteAfter, getDedupedDestructiveWarnings } from './builtin/bash-tool.mjs';
48
48
  import { scrubLoaderVars, scrubProviderSecrets } from './env-scrub.mjs';
49
49
  import { checkExecPolicyMessage } from './bash-policy-scan.mjs';
50
50
  import { startChildGuardian } from '../../../shared/child-guardian.mjs';
@@ -76,11 +76,6 @@ function isStringOrStringArray(v) {
76
76
  return true;
77
77
  }
78
78
 
79
- function hasBackslashPipe(value) {
80
- if (typeof value === 'string') return false;
81
- return false;
82
- }
83
-
84
79
  function hasUnsupportedRipgrepRegex(value) {
85
80
  const values = Array.isArray(value) ? value : [value];
86
81
  return values.some((item) => {
@@ -144,9 +139,6 @@ function guardGrep(a) {
144
139
  if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
145
140
  return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
146
141
  }
147
- if (hasOwn(a, k) && hasBackslashPipe(a[k])) {
148
- return `Error: grep arg "${k}" contains \\|. Use pattern:["a","b"] for OR terms, or unescaped | for regex alternation.`;
149
- }
150
142
  if (hasOwn(a, k) && hasUnsupportedRipgrepRegex(a[k])) {
151
143
  return `Error: grep arg "${k}" uses regex syntax ripgrep does not support here (lookaround/backrefs). Use plain pattern arrays or simpler regex.`;
152
144
  }
@@ -3,7 +3,7 @@ import { execShellCommand, stripAnsi } from '../shell-command.mjs';
3
3
  import { wrapCommandWithSnapshot } from '../shell-snapshot.mjs';
4
4
  import { getDestructiveCommandWarning } from '../destructive-warning.mjs';
5
5
  import { maybeRewriteWmicProcessCommand } from '../shell-policy.mjs';
6
- import { buildBashPolicyScanTargets, checkExecPolicyMessage, injectionBlockTargets } from '../bash-policy-scan.mjs';
6
+ import { buildBashPolicyScanTargets, checkExecPolicyMessage } from '../bash-policy-scan.mjs';
7
7
  import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph-state.mjs';
8
8
  import {
9
9
  buildJobNotFoundMessage,
@@ -27,7 +27,6 @@ import {
27
27
  cancelBackgroundTask,
28
28
  completeBackgroundTask,
29
29
  getBackgroundTask,
30
- listBackgroundTasks,
31
30
  registerBackgroundTask,
32
31
  renderBackgroundTask,
33
32
  renderBackgroundTaskList,
@@ -48,7 +47,6 @@ import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
48
47
  // closing the "external write -> stale old_string -> code 8" gap when shell is
49
48
  // routed through this tool. Bounded to the tracked-read set (capped) so cost
50
49
  // stays off the whole-cwd path; emits nothing when no read file changed.
51
- const _DRIFT_SCAN_CAP = 256;
52
50
  export function _captureTrackedMtimes(_scope) {
53
51
  return new Map();
54
52
  }
@@ -86,53 +84,12 @@ function _combineAbortSignals(sessionSignal, externalSignal) {
86
84
  return ctl.signal;
87
85
  }
88
86
 
89
- // Decode ANSI-C $'…' and locale $"…" escapes so the blocklist scan sees the
90
- // literal command (e.g. $'\x72m' → "rm"). Defensive against quoting bypass.
91
- function _decodeAnsiCQuotes(s) {
92
- if (typeof s !== 'string') return '';
93
- if (s.indexOf('$') === -1) return s;
94
- return s.replace(/\$(['"])((?:\\.|[^\\])*?)\1/g, (_full, _q, body) =>
95
- body
96
- .replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
97
- .replace(/\\u([0-9a-fA-F]{1,4})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
98
- .replace(/\\0([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
99
- .replace(/\\([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
100
- .replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
101
- .replace(/\\\\/g, '\\').replace(/\\(['"])/g, '$1'),
102
- );
103
- }
104
-
105
- // Extract $(…) and `…` command-substitution bodies so each is re-scanned by
106
- // isBlockedCommand (e.g. eval $(printf 'rm -rf ~')).
107
- function _extractSubstitutionBodies(s) {
108
- if (typeof s !== 'string') return [];
109
- const out = [];
110
- const re = /\$\(([^()]*(?:\([^()]*\)[^()]*)*)\)|`([^`]*)`/g;
111
- let m;
112
- while ((m = re.exec(s)) !== null) {
113
- const body = m[1] != null ? m[1] : m[2];
114
- if (body && body.trim()) out.push(body);
115
- }
116
- return out;
117
- }
118
-
119
- // Combined injection-aware block targets: decoded form + substitution bodies
120
- // (and their decoded forms). Used on BOTH the persistent and stateless paths.
121
- export function _injectionBlockTargets(cmd) {
122
- return injectionBlockTargets(cmd);
123
- }
124
-
125
87
  function _prefixPowerShellUtf8(command) {
126
88
  const prefix = '[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=[System.Text.Encoding]::UTF8;';
127
89
  const text = String(command || '');
128
90
  return text.trimStart().startsWith(prefix) ? text : `${prefix}\n${text}`;
129
91
  }
130
92
 
131
- const _unquoteSpansForPolicy = (s) => s.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, (m) => m.slice(1, -1));
132
-
133
- // Same normalized + decoded target set as hard-block (strip/unquote/shell -c/PS).
134
- export { buildBashPolicyScanTargets } from '../bash-policy-scan.mjs';
135
-
136
93
  export function getDedupedDestructiveWarnings(command) {
137
94
  const seenMsg = new Set();
138
95
  const warnings = [];
@@ -387,16 +387,6 @@ function sendNotifyToParent(method, params) {
387
387
  }
388
388
  }
389
389
 
390
- const recapState = { state: 'idle', running: false, startedAt: null, lastCompletedAt: null, updatedAt: null, errorMessage: null };
391
- function sendRecapStateToParent() {
392
- if (!process.send) return;
393
- try {
394
- process.send({ type: 'recap_status', recap: { ...recapState } });
395
- } catch (err) {
396
- try { process.stderr.write(`mixdog channels: recap status IPC send failed: ${err && err.message || err}\n`); } catch {}
397
- }
398
- }
399
-
400
390
  // ── Memory worker bridge (worker → parent → memory) ─────────────────
401
391
  // The channels worker does not own the memory worker handle. To trigger
402
392
  // memory tool actions (e.g. cycle1) we send `memory_call_request` to the
@@ -1141,26 +1131,6 @@ const OWNER_ROUTES = {
1141
1131
  res.end(JSON.stringify({ error: "Method not allowed" }));
1142
1132
  }
1143
1133
  },
1144
- "/recap/reset": async (req, res /*, body*/) => {
1145
- if (req.method !== "POST") { res.writeHead(405); res.end(JSON.stringify({ error: "POST required" })); return; }
1146
- if (!requireOwnerToken(req, res)) return;
1147
- // Called by hooks/session-start.cjs on `/clear` (matcher startup|clear).
1148
- // The session-start hook runs in a separate cjs process with no IPC
1149
- // handle to this forked channels child, so it can't drop recap
1150
- // status directly. Reset to an `empty` baseline so the statusline
1151
- // doesn't carry the prior session's `injected`/`error` recap badge
1152
- // into the cleared session.
1153
- const now = Date.now();
1154
- recapState.state = 'empty';
1155
- recapState.running = false;
1156
- recapState.startedAt = null;
1157
- recapState.lastCompletedAt = now;
1158
- recapState.updatedAt = now;
1159
- recapState.errorMessage = null;
1160
- sendRecapStateToParent();
1161
- res.writeHead(200);
1162
- res.end(JSON.stringify({ ok: true }));
1163
- },
1164
1134
  "/cycle1": async (req, res, body) => {
1165
1135
  if (req.method !== "POST") { res.writeHead(405); res.end(JSON.stringify({ ok: false, reason: "method-not-allowed", error: "POST required" })); return; }
1166
1136
  if (!requireOwnerToken(req, res)) return;
@@ -1,12 +1,5 @@
1
- function hasCliWorker() {
2
- return true;
3
- }
4
1
  function startCliWorker(_options) {
5
2
  }
6
- async function stopCliWorker() {
7
- }
8
3
  export {
9
- hasCliWorker,
10
- startCliWorker,
11
- stopCliWorker
4
+ startCliWorker
12
5
  };
@@ -276,7 +276,6 @@ function loadProfileConfig() {
276
276
  export {
277
277
  DATA_DIR,
278
278
  DEFAULT_HOLIDAY_COUNTRY,
279
- applyDefaults,
280
279
  createBackend,
281
280
  getDiscordToken,
282
281
  isInQuietWindow,
@@ -68,4 +68,4 @@ function dropTrace(event, fields) {
68
68
  } catch {}
69
69
  }
70
70
 
71
- export { dropTrace, _dtPreview, DROP_TRACE_ENABLED };
71
+ export { dropTrace, _dtPreview };
@@ -196,8 +196,5 @@ export {
196
196
  ensureNopluginDir,
197
197
  evaluateFilter,
198
198
  logEvent,
199
- parseGeneric,
200
- parseGithub,
201
- parseSentry,
202
199
  runScript
203
200
  };
@@ -109,41 +109,3 @@ export async function ingestTranscript(filePath, { cwd } = {}) {
109
109
  }
110
110
  }
111
111
 
112
- export async function listCoreMemories(args = {}) {
113
- const rawProjectId = args && Object.prototype.hasOwnProperty.call(args, 'project_id')
114
- ? args.project_id
115
- : args?.projectScope
116
- const projectId = rawProjectId == null || rawProjectId === 'all' ? '*' : rawProjectId
117
- const result = await memoryFetch('POST', '/api/tool', {
118
- name: 'memory',
119
- arguments: {
120
- action: 'core',
121
- op: 'list',
122
- project_id: projectId,
123
- },
124
- }, 30_000)
125
- if (!result || result.error) {
126
- throw new Error(result?.error || 'core memory list: empty response')
127
- }
128
- return result
129
- }
130
-
131
- export async function searchMemories(args = {}) {
132
- const result = await memoryFetch('POST', '/api/tool', {
133
- name: 'search_memories',
134
- arguments: args && typeof args === 'object' ? args : {},
135
- }, 30_000)
136
- if (!result || result.error) {
137
- throw new Error(result?.error || 'memory_search: empty response')
138
- }
139
- return result
140
- }
141
-
142
- export async function isHealthy() {
143
- try {
144
- const result = await memoryFetch('GET', '/health')
145
- return result.status === 'ok'
146
- } catch {
147
- return false
148
- }
149
- }
@@ -754,12 +754,5 @@ ${_bt.trim()}` : _bt.trim();
754
754
  }
755
755
  }
756
756
  export {
757
- OutputForwarder,
758
- cwdToProjectSlug,
759
- detectCurrentSessionTranscript,
760
- discoverCurrentClaudeSession,
761
- discoverSessionBoundTranscript,
762
- findLatestTranscriptByMtime,
763
- getLatestInteractiveClaudeSession,
764
- listInteractiveClaudeSessions
757
+ OutputForwarder
765
758
  };
@@ -468,11 +468,7 @@ function clearActiveInstance(instanceId) {
468
468
  });
469
469
  }
470
470
  export {
471
- ACTIVE_INSTANCE_FILE,
472
- OWNER_DIR,
473
471
  RUNTIME_ROOT,
474
- RUNTIME_STALE_TTL,
475
- buildActiveInstanceState,
476
472
  cleanupInstanceRuntimeFiles,
477
473
  cleanupStaleRuntimeFiles,
478
474
  clearActiveInstance,
@@ -485,13 +481,11 @@ export {
485
481
  getPermissionResultPath,
486
482
  getTerminalLeadPid,
487
483
  getStatusPath,
488
- getStopFlagPath,
489
484
  getTurnEndPath,
490
485
  notePreviousServerIfAny,
491
486
  makeInstanceId,
492
487
  readActiveInstance,
493
488
  refreshActiveInstance,
494
489
  releaseOwnedChannelLocks,
495
- writeActiveInstance,
496
490
  writeServerPid
497
491
  };
@@ -1,7 +1,6 @@
1
1
  import { readFileSync, readdirSync, statSync } from "fs";
2
2
  import { execFileSync } from "child_process";
3
3
  import { basename, join, resolve } from "path";
4
- import { homedir } from "os";
5
4
  import { mixdogHome } from "../../shared/plugin-paths.mjs";
6
5
 
7
6
  function cwdToProjectSlug(cwd) {
@@ -94,9 +93,6 @@ function getLatestInteractiveClaudeSession() {
94
93
 
95
94
  export {
96
95
  cwdToProjectSlug,
97
- getParentPid,
98
- readSessionRecord,
99
- isInteractiveSession,
100
96
  discoverCurrentClaudeSession,
101
97
  listInteractiveClaudeSessions,
102
98
  getLatestInteractiveClaudeSession
@@ -116,7 +116,6 @@ export {
116
116
  HIDDEN_TOOLS,
117
117
  isRecallMemory,
118
118
  isMemoryFile,
119
- isHidden,
120
119
  buildDedupKey,
121
120
  buildToolLine
122
121
  };
@@ -1,6 +1,5 @@
1
1
  import { readdirSync, existsSync, statSync } from "fs";
2
2
  import { basename, join, resolve } from "path";
3
- import { homedir } from "os";
4
3
  import {
5
4
  cwdToProjectSlug,
6
5
  discoverCurrentClaudeSession,
@@ -181,16 +180,8 @@ function discoverSessionBoundTranscript() {
181
180
  }
182
181
 
183
182
  export {
184
- resolveTranscriptForSession,
185
183
  findLatestTranscriptByMtime,
186
- isPidAlive,
187
184
  sameResolvedPath,
188
- transcriptStat,
189
- sessionTranscriptCandidate,
190
- latestMtimeTranscriptCandidate,
191
- candidateAffinity,
192
- compareTranscriptCandidates,
193
185
  detectCurrentSessionTranscript,
194
- discoverSessionBoundTranscript,
195
- TRANSCRIPT_MTIME_DECISIVE_MS
186
+ discoverSessionBoundTranscript
196
187
  };
@@ -148,7 +148,6 @@ function loadKeytar() {
148
148
  // Avoids Atomics.wait on main thread (which hangs when SAB is not forwarded to worker).
149
149
  function keytarSync(method, ...args) {
150
150
  loadKeytar(); // throws if not installed — before spawning child
151
- const { spawnSync } = require('child_process');
152
151
  // Pass service/account/value via env to avoid shell injection entirely.
153
152
  const env = {
154
153
  ...process.env,