mixdog 0.9.11 → 0.9.13

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 (48) hide show
  1. package/package.json +1 -1
  2. package/scripts/internal-comms-bench.mjs +1 -0
  3. package/scripts/internal-comms-smoke.mjs +11 -7
  4. package/src/lib/rules-builder.cjs +15 -11
  5. package/src/mixdog-session-runtime.mjs +48 -0
  6. package/src/rules/agent/00-common.md +5 -17
  7. package/src/rules/agent/00-core.md +21 -0
  8. package/src/rules/lead/lead-brief.md +7 -0
  9. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +39 -2
  10. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +0 -25
  11. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +120 -3
  12. package/src/runtime/agent/orchestrator/agent-trace.mjs +43 -1
  13. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -1
  14. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +48 -3
  15. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +42 -4
  16. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +24 -3
  18. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +6 -0
  19. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +61 -6
  20. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +31 -0
  21. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +57 -2
  22. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +40 -4
  23. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +0 -62
  24. package/src/runtime/agent/orchestrator/session/compact.mjs +0 -2
  25. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +12 -9
  26. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +12 -0
  27. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +48 -157
  28. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +6 -23
  29. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +0 -13
  30. package/src/runtime/agent/orchestrator/session/loop.mjs +32 -77
  31. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +33 -45
  32. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -6
  33. package/src/runtime/agent/orchestrator/session/manager.mjs +27 -14
  34. package/src/runtime/agent/orchestrator/stall-policy.mjs +16 -0
  35. package/src/runtime/channels/index.mjs +28 -1
  36. package/src/runtime/channels/lib/memory-client.mjs +200 -15
  37. package/src/runtime/memory/index.mjs +11 -11
  38. package/src/runtime/memory/lib/cycle-scheduler.mjs +6 -4
  39. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +22 -17
  40. package/src/runtime/memory/lib/memory-embed.mjs +35 -1
  41. package/src/session-runtime/tool-catalog.mjs +1 -0
  42. package/src/session-runtime/tool-defs.mjs +28 -0
  43. package/src/standalone/agent-tool.mjs +89 -7
  44. package/src/tui/dist/index.mjs +35 -12
  45. package/src/tui/engine.mjs +55 -12
  46. package/src/workflows/default/WORKFLOW.md +2 -0
  47. package/src/workflows/sequential/WORKFLOW.md +2 -0
  48. package/src/workflows/solo/WORKFLOW.md +2 -0
@@ -23097,6 +23097,16 @@ async function createEngineSession({
23097
23097
  restorable: nonEditable.length === 0,
23098
23098
  requeueOnAbort: nonEditable
23099
23099
  });
23100
+ const scheduledReset = runtime.consumePendingSessionReset?.();
23101
+ if ((scheduledReset === "clear" || scheduledReset === "compact_clear") && turnStatus !== "cancelled" && !state.commandBusy) {
23102
+ await performSessionClear({
23103
+ verb: scheduledReset === "clear" ? "Clearing conversation" : "Compacting and clearing conversation",
23104
+ doneLabel: scheduledReset === "clear" ? "Session cleared" : "Session compacted and cleared",
23105
+ skipLabel: "Session reset skipped",
23106
+ surface: "session-manage",
23107
+ useCompaction: scheduledReset === "compact_clear"
23108
+ });
23109
+ }
23100
23110
  if (turnStatus === "cancelled" && pending.length === 0) break;
23101
23111
  }
23102
23112
  } finally {
@@ -23140,14 +23150,26 @@ async function createEngineSession({
23140
23150
  if (!activityAt) lastUserActivityAt = now;
23141
23151
  return false;
23142
23152
  }
23153
+ return performSessionClear({
23154
+ verb: "Auto-clearing idle conversation",
23155
+ doneLabel: "Auto-clear complete",
23156
+ skipLabel: "Auto-clear skipped",
23157
+ surface: "auto-clear",
23158
+ useCompaction: true
23159
+ });
23160
+ }
23161
+ async function performSessionClear({ verb, doneLabel, skipLabel, surface, useCompaction }) {
23143
23162
  autoClearRunning = true;
23144
23163
  const startedAt2 = Date.now();
23145
- set({ commandStatus: { active: true, verb: "Auto-clearing idle conversation", startedAt: startedAt2, mode: "auto-clear" } });
23164
+ set({ commandBusy: true, commandStatus: { active: true, verb, startedAt: startedAt2, mode: "auto-clear" } });
23146
23165
  try {
23147
23166
  await new Promise((resolve5) => setTimeout(resolve5, 0));
23148
- const compaction = runtime.getCompactionSettings();
23149
- const compactType = compaction.compactType || compaction.type;
23150
- await runtime.clear({ compactType, requireCompactSuccess: !!compactType });
23167
+ let compactType = null;
23168
+ if (useCompaction) {
23169
+ const compaction = runtime.getCompactionSettings();
23170
+ compactType = compaction.compactType || compaction.type || null;
23171
+ }
23172
+ await runtime.clear(compactType ? { compactType, requireCompactSuccess: true } : {});
23151
23173
  resetStats();
23152
23174
  clearUiActivityBeforeContextSync();
23153
23175
  syncContextStats({ allowEstimated: true });
@@ -23164,24 +23186,24 @@ async function createEngineSession({
23164
23186
  pushItem({
23165
23187
  kind: "statusdone",
23166
23188
  id: nextId(),
23167
- label: "Auto-clear complete",
23189
+ label: doneLabel,
23168
23190
  detail: formatElapsedSeconds(Date.now() - startedAt2)
23169
23191
  });
23170
23192
  return true;
23171
23193
  } catch (error) {
23172
- const message = presentErrorText(error, { surface: "auto-clear" });
23194
+ const message = presentErrorText(error, { surface });
23173
23195
  pushItem({
23174
23196
  kind: "statusdone",
23175
23197
  id: nextId(),
23176
- label: "Auto-clear skipped",
23198
+ label: skipLabel,
23177
23199
  detail: `conversation kept \xB7 ${message}`
23178
23200
  });
23179
- pushNotice(`auto-clear skipped: ${message}`, "error");
23201
+ pushNotice(`${surface} skipped: ${message}`, "error");
23180
23202
  return false;
23181
23203
  } finally {
23182
23204
  lastUserActivityAt = Date.now();
23183
23205
  autoClearRunning = false;
23184
- set({ commandStatus: null });
23206
+ set({ commandBusy: false, commandStatus: null });
23185
23207
  void drain();
23186
23208
  }
23187
23209
  }
@@ -23278,7 +23300,7 @@ async function createEngineSession({
23278
23300
  },
23279
23301
  submit: (text, options = {}) => {
23280
23302
  const t = promptDisplayText(text, options).trim();
23281
- if (!t || state.commandBusy) return false;
23303
+ if (!t) return false;
23282
23304
  const mode = options.mode || "prompt";
23283
23305
  const priority = options.priority;
23284
23306
  const queueOptions = {
@@ -23287,11 +23309,12 @@ async function createEngineSession({
23287
23309
  displayText: promptDisplayText(text, options),
23288
23310
  priority
23289
23311
  };
23290
- if (state.busy) {
23312
+ if (autoClearRunning) {
23291
23313
  enqueue(text, queueOptions);
23292
23314
  return true;
23293
23315
  }
23294
- if (autoClearRunning) {
23316
+ if (state.commandBusy) return false;
23317
+ if (state.busy) {
23295
23318
  enqueue(text, queueOptions);
23296
23319
  return true;
23297
23320
  }
@@ -1624,6 +1624,24 @@ export async function createEngineSession({
1624
1624
  restorable: nonEditable.length === 0,
1625
1625
  requeueOnAbort: nonEditable,
1626
1626
  });
1627
+ // session_manage tool: the model scheduled a reset during this turn.
1628
+ // Run it now, at the turn boundary — same clear body as auto-clear.
1629
+ // Cancelled/interrupted turns drop the reset (consume + discard): the
1630
+ // user aborted the turn that asked for it, so the destructive clear
1631
+ // must not fire. commandBusy guards against a concurrent session
1632
+ // command (resume/new) racing the async clear.
1633
+ const scheduledReset = runtime.consumePendingSessionReset?.();
1634
+ if ((scheduledReset === 'clear' || scheduledReset === 'compact_clear')
1635
+ && turnStatus !== 'cancelled'
1636
+ && !state.commandBusy) {
1637
+ await performSessionClear({
1638
+ verb: scheduledReset === 'clear' ? 'Clearing conversation' : 'Compacting and clearing conversation',
1639
+ doneLabel: scheduledReset === 'clear' ? 'Session cleared' : 'Session compacted and cleared',
1640
+ skipLabel: 'Session reset skipped',
1641
+ surface: 'session-manage',
1642
+ useCompaction: scheduledReset === 'compact_clear',
1643
+ });
1644
+ }
1627
1645
  // If the user re-submits the reclaimed prompt while the cancelled turn
1628
1646
  // is still unwinding, enqueue() cannot start another drain because this
1629
1647
  // drain loop is still active. Continue when pending work appeared during
@@ -1682,18 +1700,40 @@ export async function createEngineSession({
1682
1700
  if (!activityAt) lastUserActivityAt = now;
1683
1701
  return false;
1684
1702
  }
1703
+ return performSessionClear({
1704
+ verb: 'Auto-clearing idle conversation',
1705
+ doneLabel: 'Auto-clear complete',
1706
+ skipLabel: 'Auto-clear skipped',
1707
+ surface: 'auto-clear',
1708
+ useCompaction: true,
1709
+ });
1710
+ }
1711
+
1712
+ // Shared clear body for idle auto-clear and the session_manage tool.
1713
+ // useCompaction=true mirrors auto-clear (summarize via configured
1714
+ // compactType, context carries forward); false is a plain /clear wipe.
1715
+ async function performSessionClear({ verb, doneLabel, skipLabel, surface, useCompaction }) {
1685
1716
  autoClearRunning = true;
1686
1717
  const startedAt = Date.now();
1687
- set({ commandStatus: { active: true, verb: 'Auto-clearing idle conversation', startedAt, mode: 'auto-clear' } });
1718
+ // commandBusy blocks concurrent session commands (resume/newSession/
1719
+ // setModel) AND new submits for the duration of the async clear — the
1720
+ // clear swaps the live session object, so racing commands could act on
1721
+ // the wrong session.
1722
+ set({ commandBusy: true, commandStatus: { active: true, verb, startedAt, mode: 'auto-clear' } });
1688
1723
  try {
1689
1724
  // Give Ink one event-loop turn to paint the auto-clear status before the
1690
1725
  // clear/compact path starts doing synchronous session/transcript work.
1691
1726
  // Without this, long idle clears can look like a frozen prompt followed by
1692
1727
  // an already-complete status row.
1693
1728
  await new Promise((resolve) => setTimeout(resolve, 0));
1694
- const compaction = runtime.getCompactionSettings();
1695
- const compactType = compaction.compactType || compaction.type;
1696
- await runtime.clear({ compactType, requireCompactSuccess: !!compactType });
1729
+ let compactType = null;
1730
+ if (useCompaction) {
1731
+ const compaction = runtime.getCompactionSettings();
1732
+ compactType = compaction.compactType || compaction.type || null;
1733
+ }
1734
+ await runtime.clear(compactType
1735
+ ? { compactType, requireCompactSuccess: true }
1736
+ : {});
1697
1737
  resetStats();
1698
1738
  clearUiActivityBeforeContextSync();
1699
1739
  syncContextStats({ allowEstimated: true });
@@ -1710,24 +1750,24 @@ export async function createEngineSession({
1710
1750
  pushItem({
1711
1751
  kind: 'statusdone',
1712
1752
  id: nextId(),
1713
- label: 'Auto-clear complete',
1753
+ label: doneLabel,
1714
1754
  detail: formatElapsedSeconds(Date.now() - startedAt),
1715
1755
  });
1716
1756
  return true;
1717
1757
  } catch (error) {
1718
- const message = presentErrorText(error, { surface: 'auto-clear' });
1758
+ const message = presentErrorText(error, { surface });
1719
1759
  pushItem({
1720
1760
  kind: 'statusdone',
1721
1761
  id: nextId(),
1722
- label: 'Auto-clear skipped',
1762
+ label: skipLabel,
1723
1763
  detail: `conversation kept · ${message}`,
1724
1764
  });
1725
- pushNotice(`auto-clear skipped: ${message}`, 'error');
1765
+ pushNotice(`${surface} skipped: ${message}`, 'error');
1726
1766
  return false;
1727
1767
  } finally {
1728
1768
  lastUserActivityAt = Date.now();
1729
1769
  autoClearRunning = false;
1730
- set({ commandStatus: null });
1770
+ set({ commandBusy: false, commandStatus: null });
1731
1771
  void drain();
1732
1772
  }
1733
1773
  }
@@ -1827,7 +1867,7 @@ export async function createEngineSession({
1827
1867
  },
1828
1868
  submit: (text, options = {}) => {
1829
1869
  const t = promptDisplayText(text, options).trim();
1830
- if (!t || state.commandBusy) return false;
1870
+ if (!t) return false;
1831
1871
  const mode = options.mode || 'prompt';
1832
1872
  // Prompt input queued while a turn is active keeps the
1833
1873
  // default `next` priority, so it is injected at the next tool/model
@@ -1839,11 +1879,14 @@ export async function createEngineSession({
1839
1879
  displayText: promptDisplayText(text, options),
1840
1880
  priority,
1841
1881
  };
1842
- if (state.busy) {
1882
+ // A running clear (idle auto-clear or session_manage) sets commandBusy;
1883
+ // queue the prompt instead of dropping it — it drains after the clear.
1884
+ if (autoClearRunning) {
1843
1885
  enqueue(text, queueOptions);
1844
1886
  return true;
1845
1887
  }
1846
- if (autoClearRunning) {
1888
+ if (state.commandBusy) return false;
1889
+ if (state.busy) {
1847
1890
  enqueue(text, queueOptions);
1848
1891
  return true;
1849
1892
  }
@@ -17,6 +17,8 @@ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
17
17
  failed at least once.
18
18
 
19
19
  1. Plan — discuss the request with the user, form a plan, wait for approval.
20
+ Only an explicit go-ahead ("do it", "proceed") is approval. Agreement with
21
+ a diagnosis, or pointing out a problem, is NOT execution approval.
20
22
  2. Delegate — split into the maximum number of independent scopes.
21
23
  - PARALLEL across independent scopes by default (implementation, analysis,
22
24
  review, debugging alike). Spawn every scope in the SAME turn. Shared or
@@ -21,6 +21,8 @@ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
21
21
  failed at least once.
22
22
 
23
23
  1. Plan — discuss the request with the user, form a plan, wait for approval.
24
+ Only an explicit go-ahead is approval; diagnosis agreement is not. When
25
+ ambiguous, restate the plan and ask before executing.
24
26
  2. Delegate — split into ordered scopes and hand off ONE AT A TIME.
25
27
  - NO PARALLEL, NO FAN-OUT. Even when scopes are independent, spawn exactly
26
28
  one agent per turn; never multiple in the same turn. Wait for the current
@@ -9,6 +9,8 @@ agents:
9
9
 
10
10
  1. Plan — Lead discusses the request with the user, forms a plan, and waits for
11
11
  approval before execution.
12
+ Only an explicit go-ahead is approval; diagnosis agreement is not. When
13
+ ambiguous, restate the plan and ask before executing.
12
14
  2. Execute — Lead performs all implementation, research, debugging, review, and
13
15
  verification work directly. Delegation to any agent is forbidden.
14
16
  3. Verify — Lead checks the result directly, fixes issues directly, and repeats