mixdog 0.9.54 → 0.9.55

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 (49) hide show
  1. package/package.json +2 -1
  2. package/scripts/desktop-session-bridge-test.mjs +47 -0
  3. package/scripts/interrupted-turn-history-test.mjs +28 -0
  4. package/scripts/pending-completion-drop-test.mjs +160 -4
  5. package/scripts/pending-messages-lock-nonblocking-test.mjs +62 -0
  6. package/scripts/process-lifecycle-test.mjs +5 -2
  7. package/scripts/prompt-input-parity-test.mjs +145 -0
  8. package/scripts/streaming-tail-window-test.mjs +146 -0
  9. package/scripts/tui-runtime-load-bench-entry.jsx +608 -0
  10. package/scripts/tui-runtime-load-bench.mjs +45 -0
  11. package/scripts/tui-store-frame-batch-test.mjs +99 -0
  12. package/scripts/tui-transcript-perf-test.mjs +367 -2
  13. package/scripts/write-backpressure-test.mjs +147 -0
  14. package/src/cli.mjs +5 -5
  15. package/src/lib/keychain-cjs.cjs +114 -25
  16. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +94 -18
  17. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +20 -4
  18. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +316 -63
  19. package/src/runtime/agent/orchestrator/session/store.mjs +46 -1
  20. package/src/runtime/memory/lib/trace-store.mjs +66 -4
  21. package/src/runtime/shared/buffered-appender.mjs +83 -4
  22. package/src/runtime/shared/process-lifecycle.mjs +56 -12
  23. package/src/runtime/shared/process-shutdown.mjs +2 -1
  24. package/src/session-runtime/model-route-api.mjs +4 -1
  25. package/src/session-runtime/native-search.mjs +4 -2
  26. package/src/session-runtime/provider-auth-api.mjs +8 -1
  27. package/src/session-runtime/provider-models.mjs +8 -3
  28. package/src/session-runtime/resource-api.mjs +2 -1
  29. package/src/session-runtime/runtime-core.mjs +32 -0
  30. package/src/session-runtime/warmup-schedulers.mjs +5 -1
  31. package/src/standalone/agent-tool.mjs +19 -1
  32. package/src/tui/App.jsx +10 -4
  33. package/src/tui/app/text-layout.mjs +9 -1
  34. package/src/tui/app/transcript-window.mjs +146 -60
  35. package/src/tui/app/use-mouse-input.mjs +16 -8
  36. package/src/tui/app/use-transcript-scroll.mjs +71 -19
  37. package/src/tui/app/use-transcript-window.mjs +21 -10
  38. package/src/tui/components/PromptInput.jsx +13 -6
  39. package/src/tui/dist/index.mjs +1063 -232
  40. package/src/tui/engine/context-state.mjs +31 -31
  41. package/src/tui/engine/frame-batched-store.mjs +75 -0
  42. package/src/tui/engine/session-api-ext.mjs +6 -1
  43. package/src/tui/engine/session-api.mjs +9 -3
  44. package/src/tui/engine/session-flow.mjs +54 -27
  45. package/src/tui/engine/turn.mjs +13 -3
  46. package/src/tui/engine.mjs +515 -36
  47. package/src/tui/input-editing.mjs +33 -13
  48. package/src/tui/markdown/measure-rendered-rows.mjs +117 -5
  49. package/vendor/ink/build/ink.js +8 -16
@@ -6,7 +6,21 @@ import {
6
6
  } from '../src/tui/markdown/streaming-markdown.mjs';
7
7
  import {
8
8
  measureStreamingMarkdownRenderedRows,
9
+ measureStreamingMarkdownRenderedRowsUncached,
9
10
  } from '../src/tui/markdown/measure-rendered-rows.mjs';
11
+ import {
12
+ buildTranscriptRowIndex,
13
+ streamingRowEstimateStateForId,
14
+ streamingEstimateRows,
15
+ transcriptRenderWindow,
16
+ } from '../src/tui/app/transcript-window.mjs';
17
+
18
+ function assertCachedEqualsDirect(text, columns, key) {
19
+ const cached = measureStreamingMarkdownRenderedRows(text, columns, key);
20
+ const direct = measureStreamingMarkdownRenderedRowsUncached(text, columns, key);
21
+ assert.equal(cached, direct);
22
+ return cached;
23
+ }
10
24
 
11
25
  test('bottom-pinned plain streaming text keeps only the visible suffix', () => {
12
26
  const lines = Array.from({ length: 100 }, (_, index) => `plain line ${index}`);
@@ -27,3 +41,135 @@ test('wrapped plain lines consume the streaming row budget', () => {
27
41
  assert.equal(windowPlainStreamingText(lines.join('\n'), 80, 2), lines.slice(-1).join('\n'));
28
42
  assert.equal(windowPlainStreamingText(lines.join('\n'), 80, 3), lines.slice(-2).join('\n'));
29
43
  });
44
+
45
+ test('streaming row memo preserves plain append and resize measurements', () => {
46
+ const key = 'plain-row-memo';
47
+ const before = 'alpha\nbeta '.concat('x'.repeat(80));
48
+ const after = `${before}\ngamma ${'y'.repeat(120)}`;
49
+
50
+ assertCachedEqualsDirect(before, 40, key);
51
+ assertCachedEqualsDirect(after, 40, key);
52
+ assertCachedEqualsDirect(after, 24, key);
53
+ });
54
+
55
+ test('streaming row memo preserves markdown stable-prefix measurements', () => {
56
+ const key = 'markdown-row-memo';
57
+ const before = '# Heading\n\nSettled paragraph.\n\n**live';
58
+ const after = `${before} suffix**`;
59
+
60
+ assertCachedEqualsDirect(before, 48, key);
61
+ assertCachedEqualsDirect(after, 48, key);
62
+ assert.equal(
63
+ measureStreamingMarkdownRenderedRows(after, 48, key),
64
+ measureStreamingMarkdownRenderedRowsUncached(after, 48, key),
65
+ );
66
+ });
67
+
68
+ test('streaming row memo preserves fences, lists, and tables', () => {
69
+ const cases = [
70
+ ['fence', ['# Intro\n\n```js\nconst value = 1;', '# Intro\n\n```js\nconst value = 1;\nconsole.log(value);']],
71
+ ['list', ['- first item\n- second', '- first item\n- second item with wrapped detail '.repeat(3)]],
72
+ ['table', ['| A | B |\n| - | - |\n| one | two |', '| A | B |\n| - | - |\n| one | two |\n| three | four |']],
73
+ ];
74
+ for (const [name, values] of cases) {
75
+ for (const value of values) assertCachedEqualsDirect(value, 36, `shape-${name}`);
76
+ }
77
+ });
78
+
79
+ test('render-mode changes reset the streaming estimate high-water', () => {
80
+ const id = 'render-mode-flip';
81
+ streamingEstimateRows({
82
+ id,
83
+ kind: 'assistant',
84
+ streaming: false,
85
+ text: 'large settled markdown paragraph '.repeat(80),
86
+ }, 24, false);
87
+ const flipped = streamingEstimateRows({
88
+ id,
89
+ kind: 'assistant',
90
+ streaming: true,
91
+ text: 'short',
92
+ }, 24, false);
93
+ const fresh = streamingEstimateRows({
94
+ id: 'render-mode-fresh',
95
+ kind: 'assistant',
96
+ streaming: true,
97
+ text: 'short',
98
+ }, 24, false);
99
+ assert.equal(flipped, fresh);
100
+ });
101
+
102
+ test('stream estimate LRU evicts tail and high-water state in lockstep', () => {
103
+ const id = 'lru-evicted-stream';
104
+ streamingEstimateRows({
105
+ id,
106
+ kind: 'assistant',
107
+ streaming: true,
108
+ text: 'long streaming response '.repeat(80),
109
+ }, 24, false);
110
+ assert.deepEqual(streamingRowEstimateStateForId(id), {
111
+ tailEstimate: true,
112
+ highWater: true,
113
+ });
114
+
115
+ // Production cleanup when no measured-height layout effect runs: admitting
116
+ // eight newer stream ids evicts this oldest id from the bounded estimate LRU.
117
+ for (let index = 0; index < 8; index++) {
118
+ streamingEstimateRows({
119
+ id: `lru-newer-${index}`,
120
+ kind: 'assistant',
121
+ streaming: true,
122
+ text: `newer ${index}`,
123
+ }, 24, false);
124
+ }
125
+ assert.deepEqual(streamingRowEstimateStateForId(id), {
126
+ tailEstimate: false,
127
+ highWater: false,
128
+ });
129
+
130
+ // Reusing the evicted id must start from its new response, not retain the
131
+ // much taller high-water geometry from the aborted stream above.
132
+ const reused = streamingEstimateRows({
133
+ id,
134
+ kind: 'assistant',
135
+ streaming: true,
136
+ text: 'short',
137
+ }, 24, false);
138
+ const fresh = streamingEstimateRows({
139
+ id: 'lru-evicted-stream-fresh',
140
+ kind: 'assistant',
141
+ streaming: true,
142
+ text: 'short',
143
+ }, 24, false);
144
+ assert.equal(reused, fresh);
145
+ });
146
+
147
+ test('cached tail rows preserve rendered-window bytes', () => {
148
+ const items = Array.from({ length: 20 }, (_, index) => ({
149
+ id: `notice-${index}`,
150
+ kind: 'notice',
151
+ text: `notice ${index}`,
152
+ }));
153
+ const tail = {
154
+ id: 'window-byte-tail',
155
+ kind: 'assistant',
156
+ streaming: true,
157
+ text: '# Result\n\n- first\n- second\n\n| A | B |\n| - | - |\n| one | two |',
158
+ };
159
+ const allItems = [...items, tail];
160
+ const cachedIndex = buildTranscriptRowIndex(allItems, { columns: 36 });
161
+ const directTailRows = 1 + measureStreamingMarkdownRenderedRowsUncached(tail.text, 36, tail.id);
162
+ const directRows = [...cachedIndex.rows.slice(0, -1), directTailRows];
163
+ const directPrefix = [0];
164
+ for (const rows of directRows) directPrefix.push(directPrefix.at(-1) + rows);
165
+ const directIndex = {
166
+ rows: directRows,
167
+ prefixRows: directPrefix,
168
+ totalRows: directPrefix.at(-1),
169
+ };
170
+ const options = { scrollOffset: 4, viewportHeight: 8, columns: 36 };
171
+ assert.equal(
172
+ JSON.stringify(transcriptRenderWindow(allItems, { ...options, rowIndex: cachedIndex })),
173
+ JSON.stringify(transcriptRenderWindow(allItems, { ...options, rowIndex: directIndex })),
174
+ );
175
+ });