mixdog 0.9.24 → 0.9.26

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 (84) hide show
  1. package/package.json +2 -1
  2. package/scripts/build-runtime-windows.ps1 +242 -242
  3. package/scripts/explore-bench-tmp.mjs +1 -1
  4. package/scripts/recall-usecase-cases.json +1 -1
  5. package/scripts/smoke-runtime-negative.ps1 +106 -106
  6. package/scripts/steering-fold-provenance-test.mjs +71 -0
  7. package/scripts/tool-efficiency-diag.mjs +1 -1
  8. package/scripts/webhook-smoke.mjs +46 -53
  9. package/src/defaults/cycle3-review-prompt.md +11 -4
  10. package/src/defaults/memory-promote-prompt.md +9 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/rules/lead/02-channels.md +3 -3
  13. package/src/rules/shared/01-tool.md +8 -4
  14. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +17 -0
  15. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +2 -2
  16. package/src/runtime/agent/orchestrator/mcp/client.mjs +28 -10
  17. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +8 -1
  18. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  19. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +51 -15
  20. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +5 -1
  21. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +164 -9
  22. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +45 -0
  23. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +4 -0
  24. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +12 -1
  25. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +2 -2
  26. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +7 -3
  27. package/src/runtime/channels/backends/discord.mjs +10 -3
  28. package/src/runtime/channels/lib/config.mjs +13 -11
  29. package/src/runtime/channels/lib/inbound-handler.mjs +45 -1
  30. package/src/runtime/channels/lib/memory-client.mjs +22 -2
  31. package/src/runtime/channels/lib/owned-runtime.mjs +1 -1
  32. package/src/runtime/channels/lib/scheduler.mjs +226 -208
  33. package/src/runtime/channels/lib/status-snapshot.mjs +16 -0
  34. package/src/runtime/channels/lib/webhook/deliveries.mjs +7 -318
  35. package/src/runtime/channels/lib/webhook.mjs +98 -150
  36. package/src/runtime/channels/lib/worker-main.mjs +1 -1
  37. package/src/runtime/memory/index.mjs +50 -25
  38. package/src/runtime/memory/lib/cycle-scheduler.mjs +20 -2
  39. package/src/runtime/memory/lib/memory-config-flags.mjs +13 -1
  40. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +59 -0
  41. package/src/runtime/memory/lib/memory-cycle2.mjs +10 -3
  42. package/src/runtime/memory/lib/memory-cycle3.mjs +79 -9
  43. package/src/runtime/memory/lib/pg/adapter.mjs +6 -1
  44. package/src/runtime/memory/lib/pg/process.mjs +19 -1
  45. package/src/runtime/memory/lib/pg/supervisor.mjs +125 -14
  46. package/src/runtime/memory/lib/query-handlers.mjs +102 -0
  47. package/src/runtime/memory/lib/recall-format.mjs +60 -22
  48. package/src/runtime/memory/lib/session-ingest-runtime.mjs +20 -6
  49. package/src/runtime/memory/tool-defs.mjs +1 -1
  50. package/src/runtime/shared/child-guardian.mjs +2 -2
  51. package/src/runtime/shared/open-url.mjs +2 -1
  52. package/src/runtime/shared/schedules-db.mjs +350 -0
  53. package/src/runtime/shared/service-discovery.mjs +169 -0
  54. package/src/runtime/shared/spawn-flags.mjs +27 -0
  55. package/src/runtime/shared/tool-primitives.mjs +3 -1
  56. package/src/runtime/shared/tool-surface.mjs +19 -3
  57. package/src/runtime/shared/update-checker.mjs +54 -10
  58. package/src/runtime/shared/webhooks-db.mjs +405 -0
  59. package/src/session-runtime/channel-config-api.mjs +13 -13
  60. package/src/session-runtime/lifecycle-api.mjs +14 -0
  61. package/src/session-runtime/runtime-core.mjs +39 -20
  62. package/src/standalone/agent-tool.mjs +42 -11
  63. package/src/standalone/channel-admin.mjs +173 -121
  64. package/src/standalone/channel-worker.mjs +2 -2
  65. package/src/standalone/memory-runtime-proxy.mjs +10 -5
  66. package/src/tui/App.jsx +32 -10
  67. package/src/tui/app/channel-pickers.mjs +9 -9
  68. package/src/tui/app/clipboard.mjs +14 -0
  69. package/src/tui/app/doctor.mjs +1 -1
  70. package/src/tui/app/maintenance-pickers.mjs +17 -7
  71. package/src/tui/app/settings-picker.mjs +2 -2
  72. package/src/tui/app/transcript-window.mjs +37 -1
  73. package/src/tui/app/use-mouse-input.mjs +19 -6
  74. package/src/tui/components/ToolExecution.jsx +12 -4
  75. package/src/tui/display-width.mjs +10 -4
  76. package/src/tui/dist/index.mjs +166 -58
  77. package/src/tui/engine/agent-job-feed.mjs +42 -3
  78. package/src/tui/engine/session-api-ext.mjs +12 -12
  79. package/src/tui/index.jsx +12 -2
  80. package/src/ui/statusline-segments.mjs +12 -1
  81. package/src/workflows/default/WORKFLOW.md +29 -38
  82. package/src/workflows/solo/WORKFLOW.md +15 -20
  83. package/vendor/ink/build/display-width.js +5 -4
  84. package/src/runtime/shared/schedules-store.mjs +0 -82
@@ -31,6 +31,10 @@ export function createMaintenancePickers({
31
31
  const upd = readSettings();
32
32
  const status = readStatus();
33
33
  const current = upd.currentVersion || 'unknown';
34
+ // After a successful in-place install the running process is still the
35
+ // old version; surface the pending version so "Current" doesn't look
36
+ // stale/broken until restart.
37
+ const installedVersion = status.phase === 'installed' ? (status.version || upd.latestVersion || null) : null;
34
38
  const latestMeta = checking || status.phase === 'checking'
35
39
  ? 'checking…'
36
40
  : (upd.latestVersion || 'unknown');
@@ -38,8 +42,10 @@ export function createMaintenancePickers({
38
42
  {
39
43
  value: 'current',
40
44
  label: 'Current version',
41
- meta: current,
42
- description: 'Installed mixdog version.',
45
+ meta: installedVersion ? `${current} → ${installedVersion}` : current,
46
+ description: installedVersion
47
+ ? `v${installedVersion} installed — restart mixdog to apply.`
48
+ : 'Installed mixdog version.',
43
49
  _action: 'current',
44
50
  },
45
51
  {
@@ -73,13 +79,17 @@ export function createMaintenancePickers({
73
79
  buttons: [
74
80
  {
75
81
  value: 'update-now',
76
- label: upd.updateAvailable
77
- ? `Update to v${upd.latestVersion || 'latest'}`
78
- : 'Update now',
82
+ label: installedVersion
83
+ ? `v${installedVersion} installed — restart to apply`
84
+ : (status.phase === 'installing'
85
+ ? 'Installing…'
86
+ : (upd.updateAvailable
87
+ ? `Update to v${upd.latestVersion || 'latest'}`
88
+ : 'Update now')),
79
89
  },
80
90
  ],
81
91
  onConfirm: (button) => {
82
- if (button?.value === 'update-now') runUpdate();
92
+ if (button?.value === 'update-now' && !installedVersion && status.phase !== 'installing') runUpdate();
83
93
  },
84
94
  },
85
95
  onSelect: (_value, item) => {
@@ -118,7 +128,7 @@ export function createMaintenancePickers({
118
128
  void Promise.resolve(store.runUpdateNow?.())
119
129
  .then((result) => {
120
130
  if (result?.ok) {
121
- store.pushNotice(`v${result.version} installed — restart to apply`, 'info');
131
+ store.pushNotice(`v${result.version} installed — restart to apply`, 'warn');
122
132
  } else {
123
133
  store.pushNotice(`Update failed: ${result?.error || 'unknown error'}`, 'error');
124
134
  }
@@ -43,7 +43,7 @@ export function createSettingsPicker({
43
43
  openUpdatePicker,
44
44
  openChannelSettingTypePicker,
45
45
  }) {
46
- const openSettingsPicker = (opts = {}) => {
46
+ const openSettingsPicker = async (opts = {}) => {
47
47
  const light = opts.light === true;
48
48
  const overrides = opts.overrides || null;
49
49
  const heavyCache = light ? settingsHeavyCacheRef.current : null;
@@ -63,7 +63,7 @@ export function createSettingsPicker({
63
63
  channelBackend = heavyCache.channelBackend || 'discord';
64
64
  } else {
65
65
  try {
66
- channelBackend = store.getChannelSetup?.()?.backend || 'discord';
66
+ channelBackend = (await store.getChannelSetup?.())?.backend || 'discord';
67
67
  } catch {
68
68
  channelBackend = 'discord';
69
69
  }
@@ -272,6 +272,33 @@ function toolArgPathForRows(item) {
272
272
  return a?.path ?? a?.file_path ?? a?.file ?? '';
273
273
  }
274
274
 
275
+ // Mirror ToolExecution's collapsed agent-card rule: an agent surface collapses
276
+ // to a SINGLE header row unless the detail row carries failure info (the call
277
+ // errored, or the brief/status reads as a failure/cancel). Pending agent cards
278
+ // never carry failure info yet, so they collapse to one row. A header-failure-
279
+ // only card is handled earlier by toolHeaderFailureOnlyForRows (also one row).
280
+ function agentCardKeepsCollapsedDetailForRows(item, normalizedName) {
281
+ if (normalizedName !== 'agent') return false;
282
+ if (toolItemPendingForRows(item)) return false;
283
+ const bgArgs = backgroundArgsForRows(item.args);
284
+ const isError = Boolean(item.isError);
285
+ const hasDisplayResult = toolHasDisplayResultForRows(item);
286
+ const displayedResultText = toolDisplayedResultTextForRows(item);
287
+ const rt = item.result == null ? '' : String(item.result).replace(/\s+$/, '');
288
+ const isAgentResponse = hasDisplayResult && isAgentResponseResultText(rt);
289
+ const briefRaw = summarizeAgentSurfaceBrief(item.name, bgArgs, displayedResultText, {
290
+ isError,
291
+ isResponse: isAgentResponse,
292
+ });
293
+ const brief = String(briefRaw || '').trim();
294
+ const status = String(bgArgs.status || '').toLowerCase();
295
+ const failureText = /\b(cancelled|canceled|failed)\b/i.test(brief)
296
+ || /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status);
297
+ const agentHeaderFailure = isError && String(bgArgs.error || '').trim() && !hasDisplayResult;
298
+ if (agentHeaderFailure && !brief) return false;
299
+ return isError || failureText;
300
+ }
301
+
275
302
  function isShellSurfaceForRows(normalizedName, label = '') {
276
303
  const n = String(normalizedName || '').toLowerCase();
277
304
  const l = String(label || '').toLowerCase();
@@ -332,6 +359,11 @@ function estimateTranscriptItemRows(item, columns, toolOutputExpanded) {
332
359
  if (toolHeaderFailureOnlyForRows(item, normalizedName, hasDisplayResult)) {
333
360
  return TOOL_MARGIN_TOP + 1;
334
361
  }
362
+ if (isAgentSurface) {
363
+ return agentCardKeepsCollapsedDetailForRows(item, normalizedName)
364
+ ? TOOL_MARGIN_TOP + 1 + 1
365
+ : TOOL_MARGIN_TOP + 1;
366
+ }
335
367
  return TOOL_MARGIN_TOP + 1 + 1;
336
368
  }
337
369
  if (hasRawResult) {
@@ -341,7 +373,11 @@ function estimateTranscriptItemRows(item, columns, toolOutputExpanded) {
341
373
  : { pathArg: toolArgPathForRows(item), isShell: isShellSurfaceForToolItem(item, normalizedName) };
342
374
  return TOOL_MARGIN_TOP + 1 + estimateToolRenderedResultRows(estimateText, { ...rawOpts, columns });
343
375
  }
344
- if (isAgentSurface && !hasResult) return TOOL_MARGIN_TOP + 1 + 1;
376
+ if (isAgentSurface && !hasResult) {
377
+ return agentCardKeepsCollapsedDetailForRows(item, normalizedName)
378
+ ? TOOL_MARGIN_TOP + 1 + 1
379
+ : TOOL_MARGIN_TOP + 1;
380
+ }
345
381
  if (isSkillSurface && !hasResult) return TOOL_MARGIN_TOP + 1;
346
382
  if (item.aggregate) {
347
383
  if (hasRawResult) {
@@ -18,8 +18,11 @@ const MOUSE_TRACKING_OFF = '\x1b[?1006l\x1b[?1002l\x1b[?1000l';
18
18
  // conversion WINS over WT's native ctrl+wheel font zoom. During the zoom
19
19
  // passthrough window we must turn BOTH mouse tracking and alternate scroll
20
20
  // off, or ctrl+wheel lands as Up/Down (prompt history) instead of zooming.
21
+ // 1007 is kept OFF for the entire session (index.jsx boots with ?1007l and
22
+ // the restore below re-asserts it): if mouse tracking ever drops while 1007
23
+ // is on, every wheel notch turns into prompt-history Up/Down. Off means a
24
+ // degraded wheel is a no-op, never history navigation.
21
25
  const ALT_SCROLL_OFF = '\x1b[?1007l';
22
- const ALT_SCROLL_ON = '\x1b[?1007h';
23
26
  const MOUSE_MODIFIER_MASK = 4 | 8 | 16;
24
27
  const MOUSE_CTRL_MASK = 16;
25
28
  // Bit 2 (4) of the SGR button byte = shift held during the click. Wheel/ctrl
@@ -76,15 +79,25 @@ export function useMouseInput({
76
79
  } catch {
77
80
  return;
78
81
  }
79
- if (zoomTimerRef.current) clearTimeout(zoomTimerRef.current);
80
- zoomTimerRef.current = setTimeout(() => {
82
+ // Re-enable with RETRIES: a single swallowed write failure (EAGAIN
83
+ // backpressure is real on Windows console streams) used to leave mouse
84
+ // tracking off for the rest of the session — wheel scroll dead, and with
85
+ // alternate scroll enabled the wheel then typed into prompt history.
86
+ const restore = (attempt) => {
81
87
  zoomTimerRef.current = null;
82
88
  try {
83
- stdout.write(ALT_SCROLL_ON + MOUSE_TRACKING_ON);
89
+ stdout.write(MOUSE_TRACKING_ON + ALT_SCROLL_OFF);
84
90
  } catch {
85
- // The terminal may already be closing.
91
+ // The terminal may be closing — but it may also be transient
92
+ // backpressure; retry a few times before giving up.
93
+ if (attempt < 5) {
94
+ zoomTimerRef.current = setTimeout(() => restore(attempt + 1), 200);
95
+ zoomTimerRef.current.unref?.();
96
+ }
86
97
  }
87
- }, 700);
98
+ };
99
+ if (zoomTimerRef.current) clearTimeout(zoomTimerRef.current);
100
+ zoomTimerRef.current = setTimeout(() => restore(0), 700);
88
101
  zoomTimerRef.current.unref?.();
89
102
  }, [stdout, zoomTimerRef]);
90
103
 
@@ -198,9 +198,10 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
198
198
  // Mirror estimateTranscriptItemRows: a non-aggregate skill surface collapses
199
199
  // to a single header row; everything else reserves header + one detail row.
200
200
  const placeholderNormalizedName = String(formatToolSurface(name, args)?.normalizedName || '').toLowerCase();
201
- // Skill surfaces collapse to a single header row; agent surfaces reserve
202
- // header + one brief detail row (see estimateTranscriptItemRows).
203
- const placeholderSingleRow = !aggregate && SKILL_SURFACE_NAMES.has(placeholderNormalizedName);
201
+ // Skill AND agent surfaces collapse to a single header row when collapsed
202
+ // (see estimateTranscriptItemRows); reserve one row for both.
203
+ const placeholderSingleRow = !aggregate
204
+ && (SKILL_SURFACE_NAMES.has(placeholderNormalizedName) || isAgentTool(placeholderNormalizedName));
204
205
  return (
205
206
  <Box flexDirection="column" marginTop={attached ? 0 : 1} width={rowWidth} overflow="hidden">
206
207
  <Text> </Text>
@@ -443,11 +444,18 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
443
444
  } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure && !showRawResult) {
444
445
  visibleDetailLines = [];
445
446
  } else if (isAgentSurfaceCard && !showRawResult) {
447
+ // Agent cards collapse to a SINGLE header row like skill surfaces. Keep the
448
+ // ⎿ detail row ONLY when it carries failure info: the call errored, or the
449
+ // brief/summary reads as a failure/cancel. A header-failure-only card (no
450
+ // brief) shows the cause in the header, so it stays a single row too.
451
+ // Expanded (ctrl+o raw) still flows through the showRawResult path above.
446
452
  const agentDetailFallback = collapsedDetail
447
453
  || (pending ? (pendingDetailPlaceholder || 'Running') : 'Finished');
448
454
  const agentDetailLine = agentSurfaceBrief
449
455
  || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
450
- visibleDetailLines = agentHeaderFailure && !agentSurfaceBrief ? [] : [agentDetailLine];
456
+ const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || '');
457
+ const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
458
+ visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
451
459
  }
452
460
  const finalStatusColor = toolStatusColor({ pending, groupCount, failedCount, terminalStatus });
453
461
  const dotColor = finalStatusColor;
@@ -12,7 +12,9 @@
12
12
  * - Widen ONLY the two problem ranges below. NEVER widen box-drawing
13
13
  * (U+2500–U+257F └ │ ⎿ ─), block elements, or the figures.mjs glyphs —
14
14
  * those are also EAW-ambiguous but must stay 1 cell.
15
- * - Enabled by default ONLY under Windows Terminal (WT_SESSION set).
15
+ * - Enabled by default on Windows (process.platform === 'win32') or under
16
+ * Windows Terminal (WT_SESSION set — covers WT reached via e.g. SSH from
17
+ * a non-Windows host).
16
18
  * - MIXDOG_TUI_AMBIGUOUS_WIDE overrides the default: '1' forces on, '0'
17
19
  * forces off; the override always wins.
18
20
  * - When OFF, behaviour is byte-for-byte identical to plain string-width.
@@ -43,12 +45,16 @@ export function isProblemCodePoint(cp) {
43
45
  // vendor/ink/build/display-width.js.
44
46
  const PROBLEM_RE = /[\u2190-\u21ff\u2460-\u24ff]/;
45
47
 
46
- /** Resolve the wide policy from env. Override wins over the WT_SESSION default. */
47
- export function resolveAmbiguousWidePolicy(env = process.env) {
48
+ /**
49
+ * Resolve the wide policy from env. Override wins over the default; the
50
+ * default is ON on Windows (WT_SESSION does not reliably propagate to child
51
+ * processes, so gate on the OS itself) or when WT_SESSION is present.
52
+ */
53
+ export function resolveAmbiguousWidePolicy(env = process.env, platform = process.platform) {
48
54
  const override = env?.MIXDOG_TUI_AMBIGUOUS_WIDE;
49
55
  if (override === '1') return true;
50
56
  if (override === '0') return false;
51
- return Boolean(env?.WT_SESSION);
57
+ return platform === 'win32' || Boolean(env?.WT_SESSION);
52
58
  }
53
59
 
54
60
  /** Resolved once at module load (matches the "computed once" requirement). */