mixdog 0.9.39 → 0.9.41
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.
- package/package.json +2 -1
- package/scripts/agent-terminal-reap-test.mjs +6 -6
- package/scripts/anthropic-oauth-refresh-race-test.mjs +338 -0
- package/scripts/compact-pressure-test.mjs +128 -0
- package/scripts/compact-trigger-migration-smoke.mjs +8 -8
- package/scripts/execution-resume-esc-integration-test.mjs +4 -2
- package/scripts/internal-comms-smoke.mjs +66 -25
- package/scripts/max-output-recovery-persist-test.mjs +81 -0
- package/scripts/max-output-recovery-test.mjs +222 -0
- package/scripts/provider-toolcall-test.mjs +32 -0
- package/scripts/steering-drain-buckets-test.mjs +140 -5
- package/scripts/tui-transcript-perf-test.mjs +279 -0
- package/src/agents/reviewer/AGENT.md +4 -0
- package/src/rules/lead/lead-brief.md +11 -3
- package/src/rules/lead/lead-tool.md +0 -2
- package/src/rules/shared/01-tool.md +4 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +20 -2
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +7 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +144 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -2
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +57 -25
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +54 -12
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +106 -8
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +22 -7
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +26 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +43 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +49 -34
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -10
- package/src/runtime/shared/buffered-appender.mjs +13 -2
- package/src/session-runtime/config-helpers.mjs +6 -6
- package/src/session-runtime/lifecycle-api.mjs +4 -0
- package/src/session-runtime/runtime-core.mjs +1 -0
- package/src/session-runtime/session-turn-api.mjs +12 -0
- package/src/standalone/agent-tool.mjs +8 -1
- package/src/tui/App.jsx +2 -1
- package/src/tui/app/transcript-window.mjs +9 -10
- package/src/tui/app/use-transcript-window.mjs +38 -56
- package/src/tui/dist/index.mjs +354 -163
- package/src/tui/engine/agent-job-feed.mjs +76 -6
- package/src/tui/engine/session-api.mjs +7 -1
- package/src/tui/engine/session-flow.mjs +3 -1
- package/src/tui/engine/tool-card-results.mjs +10 -5
- package/src/tui/engine/turn.mjs +96 -36
- package/src/tui/engine.mjs +136 -37
- package/src/tui/index.jsx +2 -2
- package/src/workflows/bench/WORKFLOW.md +51 -27
- package/src/workflows/default/WORKFLOW.md +29 -24
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
hasStreamingRowStateToPrune,
|
|
19
19
|
transcriptItemVariantKey,
|
|
20
20
|
buildTranscriptRowIndexIncremental,
|
|
21
|
-
transcriptStructureSignature,
|
|
22
21
|
estimateTranscriptItemRowsCached,
|
|
23
22
|
setStreamingBottomPinned,
|
|
24
23
|
transcriptRenderWindow,
|
|
@@ -32,7 +31,9 @@ import {
|
|
|
32
31
|
import { shouldSuppressFullyFailedToolItem } from '../transcript-tool-failures.mjs';
|
|
33
32
|
|
|
34
33
|
export function useTranscriptWindow({
|
|
35
|
-
items,
|
|
34
|
+
items: settledItems,
|
|
35
|
+
structureRevision,
|
|
36
|
+
streamingTail,
|
|
36
37
|
themeEpoch,
|
|
37
38
|
frameColumns,
|
|
38
39
|
toolOutputExpanded,
|
|
@@ -129,42 +130,12 @@ export function useTranscriptWindow({
|
|
|
129
130
|
return fn;
|
|
130
131
|
}, []);
|
|
131
132
|
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// changes, so steady per-character growth keeps both memos warm.
|
|
139
|
-
//
|
|
140
|
-
// The signature itself is memoized on `items` identity (+columns/
|
|
141
|
-
// expanded) so re-renders that DO NOT touch items — drag motion, scroll,
|
|
142
|
-
// input typing — skip the O(n) signature walk entirely. During streaming the
|
|
143
|
-
// engine hands us a fresh `items` array each flush, so this memo
|
|
144
|
-
// recomputes and still tracks the streaming item's height correctly.
|
|
145
|
-
// Split the structure signature into prefix (all items except the trailing
|
|
146
|
-
// streaming item) + tail. On a streaming flush only the tail item object is
|
|
147
|
-
// replaced; every prefix item keeps identity, so the prefix sig is O(1)-
|
|
148
|
-
const streamingTailItem = useMemo(() => {
|
|
149
|
-
const last = (items || []).length > 0 ? items[items.length - 1] : null;
|
|
150
|
-
return last && last.kind === 'assistant' && last.streaming ? last : null;
|
|
151
|
-
}, [items]);
|
|
152
|
-
const prefixLen = streamingTailItem ? (items || []).length - 1 : (items || []).length;
|
|
153
|
-
// Key on `items` identity (engine swaps the array every flush — INCLUDING a
|
|
154
|
-
// tool-card patch that replaces a MIDDLE item object), so a same-length
|
|
155
|
-
// middle-item replacement is caught (new object → fresh WeakMap fragment →
|
|
156
|
-
// different prefixSig string). The walk itself is cheap: transcript-
|
|
157
|
-
// StructureSignature reads a WeakMap sigPart per item (no re-estimation for
|
|
158
|
-
// unchanged objects) and joins — O(n) map lookups, not O(n) measurement. The
|
|
159
|
-
// row-index memo stays keyed on the prefixSig STRING, so a tail-only flush
|
|
160
|
-
// (identical prefix objects → identical string) still skips the row rebuild.
|
|
161
|
-
const prefixSig = useMemo(
|
|
162
|
-
() => transcriptStructureSignature(
|
|
163
|
-
streamingTailItem ? (items || []).slice(0, prefixLen) : (items || []),
|
|
164
|
-
frameColumns, toolOutputExpanded,
|
|
165
|
-
),
|
|
166
|
-
[items, streamingTailItem, prefixLen, frameColumns, toolOutputExpanded],
|
|
167
|
-
);
|
|
133
|
+
// The settled array no longer changes during streaming. Geometry is keyed by
|
|
134
|
+
// the engine revision plus the live tail's resolved height, so same-height
|
|
135
|
+
// text flushes do not copy/walk the settled prefix or rerun heavy memos.
|
|
136
|
+
const streamingTailItem = streamingTail?.kind === 'assistant' && streamingTail.streaming
|
|
137
|
+
? streamingTail
|
|
138
|
+
: null;
|
|
168
139
|
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
169
140
|
const transcriptPinnedForStreaming = followingRef.current
|
|
170
141
|
|| scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
@@ -173,13 +144,18 @@ export function useTranscriptWindow({
|
|
|
173
144
|
// applies while bottom-pinned (right geometry on first commit, no judder) and
|
|
174
145
|
// every tail-resolving path reads the same height (no sig/geometry divergence).
|
|
175
146
|
setStreamingBottomPinned(transcriptPinnedForStreaming);
|
|
176
|
-
const
|
|
177
|
-
?
|
|
178
|
-
:
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
147
|
+
const tailRows = streamingTailItem
|
|
148
|
+
? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)
|
|
149
|
+
: 0;
|
|
150
|
+
const tailSig = streamingTailItem ? `${streamingTailItem.id}:${tailRows}` : '_';
|
|
151
|
+
const revision = Math.max(0, Number(structureRevision) || 0);
|
|
152
|
+
const transcriptItems = useMemo(
|
|
153
|
+
() => streamingTailItem ? [...(settledItems || []), streamingTailItem] : (settledItems || []),
|
|
154
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- tail text at the same resolved height is injected into the bounded visible slice below
|
|
155
|
+
[settledItems, revision, streamingTailItem?.id, tailRows],
|
|
182
156
|
);
|
|
157
|
+
const transcriptStructureSig = `${revision}#${tailSig}`;
|
|
158
|
+
const transcriptStreamingActive = !!streamingTailItem;
|
|
183
159
|
const scrolledUpForStreamingMeasure = scrolledUpRowsForPin > transcriptBottomSlackRows;
|
|
184
160
|
const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
|
|
185
161
|
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current
|
|
@@ -199,15 +175,15 @@ export function useTranscriptWindow({
|
|
|
199
175
|
// rebuild for the settled prefix. All those invalidators are folded into the
|
|
200
176
|
// memo deps below (sig captures item/column/expanded structure; the rest are
|
|
201
177
|
// listed explicitly), so the memo only recomputes when one of them changes.
|
|
202
|
-
const transcriptRowIndex = useMemo(() => buildTranscriptRowIndexIncremental(
|
|
178
|
+
const transcriptRowIndex = useMemo(() => buildTranscriptRowIndexIncremental(transcriptItems, {
|
|
203
179
|
columns: frameColumns,
|
|
204
180
|
toolOutputExpanded,
|
|
205
181
|
suppressMeasuredRowHeights,
|
|
206
182
|
measuredRowsVersion,
|
|
207
183
|
cacheRef: incrementalRowIndexCacheRef,
|
|
208
|
-
|
|
209
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps --
|
|
210
|
-
}), [
|
|
184
|
+
prefixRevision: revision,
|
|
185
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- revision/tail height capture structural geometry; measuredRowsVersion folds in measured corrections
|
|
186
|
+
}), [revision, tailSig, frameColumns, toolOutputExpanded, measuredRowsVersion, suppressMeasuredRowHeights]);
|
|
211
187
|
// ── Same-frame anchor lock ───────────────────────────────────────────────
|
|
212
188
|
// While the user reads older transcript (anchor captured, not dirty), resolve
|
|
213
189
|
// the scroll offset that keeps the anchored viewport-top row fixed for THIS
|
|
@@ -243,7 +219,7 @@ export function useTranscriptWindow({
|
|
|
243
219
|
if (anchorLockActive) {
|
|
244
220
|
const locked = resolveAnchorScrollOffset({
|
|
245
221
|
anchor: transcriptAnchorRef.current,
|
|
246
|
-
items:
|
|
222
|
+
items: transcriptItems,
|
|
247
223
|
curPrefix: curPrefixForLock,
|
|
248
224
|
totalRows: lockTotalRows,
|
|
249
225
|
viewRows: lockViewRows,
|
|
@@ -288,7 +264,7 @@ export function useTranscriptWindow({
|
|
|
288
264
|
const captured = { id: anchorItem.id, offset: Math.max(0, prevTopRow - (prevPrefix[idx] || 0)) };
|
|
289
265
|
const locked = resolveAnchorScrollOffset({
|
|
290
266
|
anchor: captured,
|
|
291
|
-
items:
|
|
267
|
+
items: transcriptItems,
|
|
292
268
|
curPrefix: curPrefixForLock,
|
|
293
269
|
totalRows: lockTotalRows,
|
|
294
270
|
viewRows: lockViewRows,
|
|
@@ -343,7 +319,7 @@ export function useTranscriptWindow({
|
|
|
343
319
|
const captured = { id: anchorItem.id, offset: Math.max(0, prevTopRow - (prevPrefix[idx] || 0)) };
|
|
344
320
|
const locked = resolveAnchorScrollOffset({
|
|
345
321
|
anchor: captured,
|
|
346
|
-
items:
|
|
322
|
+
items: transcriptItems,
|
|
347
323
|
curPrefix: curPrefixForLock,
|
|
348
324
|
totalRows: lockTotalRows,
|
|
349
325
|
viewRows: lockViewRows,
|
|
@@ -376,7 +352,7 @@ export function useTranscriptWindow({
|
|
|
376
352
|
// measuredRowsVersion bumps, the row index absorbs the growth (idEntry.rows ==
|
|
377
353
|
// the new measured height) and the live estimate matches it, so delta → 0.
|
|
378
354
|
if (scrolledUp && !followingRef.current) {
|
|
379
|
-
const growth = streamingTailMountedGrowth(
|
|
355
|
+
const growth = streamingTailMountedGrowth(transcriptItems, frameColumns, toolOutputExpanded);
|
|
380
356
|
// Only compensate when the tail is actually mounted in the rendered slice
|
|
381
357
|
// (viewport + overscan). Off-slice it is represented by a row-index-sized
|
|
382
358
|
// bottom spacer that does NOT physically grow this frame, so shifting the
|
|
@@ -394,7 +370,7 @@ export function useTranscriptWindow({
|
|
|
394
370
|
}
|
|
395
371
|
}
|
|
396
372
|
}
|
|
397
|
-
const transcriptWindow = useMemo(() => transcriptRenderWindow(
|
|
373
|
+
const transcriptWindow = useMemo(() => transcriptRenderWindow(transcriptItems, {
|
|
398
374
|
scrollOffset: renderScrollOffset,
|
|
399
375
|
viewportHeight: transcriptContentHeight,
|
|
400
376
|
columns: frameColumns,
|
|
@@ -451,7 +427,7 @@ export function useTranscriptWindow({
|
|
|
451
427
|
prefixRows: transcriptRowIndex?.prefixRows || null,
|
|
452
428
|
totalRows: Math.max(0, Number(transcriptWindow.totalRows) || 0),
|
|
453
429
|
viewRows: Math.max(1, Number(transcriptContentHeight) || 1),
|
|
454
|
-
items:
|
|
430
|
+
items: transcriptItems || null,
|
|
455
431
|
// The offset THIS frame actually rendered with. The same-frame anchor
|
|
456
432
|
// CAPTURE for a missing/dirty anchor (above) reads this from the PREVIOUS
|
|
457
433
|
// frame to reconstruct the exact top-edge row that was on screen, so the
|
|
@@ -469,10 +445,16 @@ export function useTranscriptWindow({
|
|
|
469
445
|
// height changes. Re-slice the live `items` over the memo's stable
|
|
470
446
|
// [startIndex, endIndex) bounds so the on-screen text is always current
|
|
471
447
|
// while the expensive indexing/windowing stays warm.
|
|
472
|
-
const transcriptVisibleItems = (
|
|
448
|
+
const transcriptVisibleItems = (transcriptItems || []).slice(
|
|
473
449
|
transcriptWindow.startIndex,
|
|
474
450
|
transcriptWindow.endIndex,
|
|
475
451
|
);
|
|
452
|
+
if (streamingTailItem && transcriptVisibleItems.length > 0) {
|
|
453
|
+
const last = transcriptVisibleItems.length - 1;
|
|
454
|
+
if (transcriptVisibleItems[last]?.id === streamingTailItem.id) {
|
|
455
|
+
transcriptVisibleItems[last] = streamingTailItem;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
476
458
|
// The bottom meta band is spinner-only, so nothing is pulled out of the
|
|
477
459
|
// transcript for it. A finished turn's done row (turndone/statusdone) renders
|
|
478
460
|
// inline in scrollback like any other item — no filtering, no double-paint.
|
|
@@ -703,7 +685,7 @@ export function useTranscriptWindow({
|
|
|
703
685
|
// move the item's prefix start and are absorbed the same way. No deltas, no
|
|
704
686
|
// fallback, no drift.
|
|
705
687
|
const viewRows = Math.max(1, Number(transcriptContentHeight) || 1);
|
|
706
|
-
const itemList =
|
|
688
|
+
const itemList = transcriptItems || [];
|
|
707
689
|
let anchor = transcriptAnchorRef.current;
|
|
708
690
|
// (Re)capture the anchor from the current viewport-top edge when missing or
|
|
709
691
|
// invalidated by a manual scroll. anchorRow = absolute row at the top edge.
|