mixdog 0.9.17 → 0.9.18
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 +1 -1
- package/scripts/bench/cache-probe-tasks.json +1 -1
- package/scripts/bench/lead-review-tasks-r3.json +1 -1
- package/scripts/bench/r4-mixed-tasks.json +1 -1
- package/scripts/bench/review-tasks.json +1 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/src/rules/lead/02-channels.md +3 -3
- package/src/runtime/channels/backends/discord-attachments.mjs +2 -2
- package/src/runtime/channels/backends/discord-gateway.mjs +12 -2
- package/src/runtime/channels/backends/discord.mjs +97 -7
- package/src/runtime/channels/index.mjs +150 -23
- package/src/runtime/channels/lib/crash-log.mjs +21 -3
- package/src/runtime/channels/lib/output-forwarder.mjs +118 -7
- package/src/runtime/channels/lib/runtime-paths.mjs +13 -0
- package/src/runtime/channels/lib/voice-transcription.mjs +4 -2
- package/src/standalone/channel-worker.mjs +14 -2
- package/src/tui/app/transcript-window.mjs +22 -0
- package/src/tui/app/use-transcript-window.mjs +9 -3
- package/src/tui/dist/index.mjs +63 -12
- package/src/tui/engine.mjs +60 -10
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
buildTranscriptRowIndexIncremental,
|
|
20
20
|
transcriptStructureSignature,
|
|
21
21
|
estimateTranscriptItemRowsCached,
|
|
22
|
+
setStreamingBottomPinned,
|
|
22
23
|
transcriptRenderWindow,
|
|
23
24
|
resolveAnchorScrollOffset,
|
|
24
25
|
upperBound,
|
|
@@ -148,6 +149,14 @@ export function useTranscriptWindow({
|
|
|
148
149
|
),
|
|
149
150
|
[items, streamingTailItem, prefixLen, frameColumns, toolOutputExpanded],
|
|
150
151
|
);
|
|
152
|
+
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
153
|
+
const transcriptPinnedForStreaming = followingRef.current
|
|
154
|
+
|| scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
155
|
+
// Publish the pinned state to the streaming-tail estimator BEFORE resolving
|
|
156
|
+
// tailSig / the row-index memo this render, so max(measuredFloor, liveEstimate)
|
|
157
|
+
// applies while bottom-pinned (right geometry on first commit, no judder) and
|
|
158
|
+
// every tail-resolving path reads the same height (no sig/geometry divergence).
|
|
159
|
+
setStreamingBottomPinned(transcriptPinnedForStreaming);
|
|
151
160
|
const tailSig = streamingTailItem
|
|
152
161
|
? `a${streamingTailItem.id}:${estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)}`
|
|
153
162
|
: '_';
|
|
@@ -155,9 +164,6 @@ export function useTranscriptWindow({
|
|
|
155
164
|
const transcriptStreamingActive = (items || []).some(
|
|
156
165
|
(item) => item?.kind === 'assistant' && item?.streaming,
|
|
157
166
|
);
|
|
158
|
-
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
159
|
-
const transcriptPinnedForStreaming = followingRef.current
|
|
160
|
-
|| scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
161
167
|
const scrolledUpForStreamingMeasure = scrolledUpRowsForPin > transcriptBottomSlackRows;
|
|
162
168
|
const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
|
|
163
169
|
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -11100,6 +11100,10 @@ function measuredTranscriptRows(item, columns, toolOutputExpanded) {
|
|
|
11100
11100
|
return entry.rows;
|
|
11101
11101
|
}
|
|
11102
11102
|
var STREAMING_ROW_QUANTUM = 1;
|
|
11103
|
+
var streamingBottomPinned = false;
|
|
11104
|
+
function setStreamingBottomPinned(pinned) {
|
|
11105
|
+
streamingBottomPinned = !!pinned;
|
|
11106
|
+
}
|
|
11103
11107
|
function assistantTextForStreamingRowEstimate(text) {
|
|
11104
11108
|
return streamingLayoutText(text);
|
|
11105
11109
|
}
|
|
@@ -11116,6 +11120,9 @@ function estimateTranscriptItemRowsCached(item, columns, toolOutputExpanded) {
|
|
|
11116
11120
|
const toolExpanded2 = toolOutputExpanded ? 1 : 0;
|
|
11117
11121
|
const idEntry = streamingMeasuredRowsById.get(item.id);
|
|
11118
11122
|
if (idEntry && idEntry.rows > 0 && idEntry.columns === columns && idEntry.toolExpanded === toolExpanded2) {
|
|
11123
|
+
if (streamingBottomPinned) {
|
|
11124
|
+
return Math.max(idEntry.rows, streamingEstimateRows(item, columns, toolOutputExpanded));
|
|
11125
|
+
}
|
|
11119
11126
|
return idEntry.rows;
|
|
11120
11127
|
}
|
|
11121
11128
|
if (idEntry) streamingMeasuredRowsById.delete(item.id);
|
|
@@ -12081,13 +12088,14 @@ function useTranscriptWindow({
|
|
|
12081
12088
|
),
|
|
12082
12089
|
[items, streamingTailItem, prefixLen, frameColumns, toolOutputExpanded]
|
|
12083
12090
|
);
|
|
12091
|
+
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
12092
|
+
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
12093
|
+
setStreamingBottomPinned(transcriptPinnedForStreaming);
|
|
12084
12094
|
const tailSig = streamingTailItem ? `a${streamingTailItem.id}:${estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)}` : "_";
|
|
12085
12095
|
const transcriptStructureSig = `${prefixSig}#${tailSig}`;
|
|
12086
12096
|
const transcriptStreamingActive = (items || []).some(
|
|
12087
12097
|
(item) => item?.kind === "assistant" && item?.streaming
|
|
12088
12098
|
);
|
|
12089
|
-
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
12090
|
-
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin <= transcriptBottomSlackRows;
|
|
12091
12099
|
const scrolledUpForStreamingMeasure = scrolledUpRowsForPin > transcriptBottomSlackRows;
|
|
12092
12100
|
const prevEstimateGeometry = transcriptGeomRef.current?.suppressMeasuredRowHeights === true;
|
|
12093
12101
|
const hasStreamingReadingAnchor = !!transcriptAnchorRef.current || transcriptAnchorDirtyRef.current;
|
|
@@ -23054,13 +23062,20 @@ async function createEngineSession({
|
|
|
23054
23062
|
seq: deferredSeqCounter++,
|
|
23055
23063
|
pushed: false,
|
|
23056
23064
|
timer: null,
|
|
23057
|
-
|
|
23065
|
+
// Mark the card visible and return its spec WITHOUT emitting, so a
|
|
23066
|
+
// batched turn-close flush can commit many specs in one set().
|
|
23067
|
+
materialize: () => {
|
|
23058
23068
|
card.pushed = true;
|
|
23059
|
-
if (!card.spec) return;
|
|
23069
|
+
if (!card.spec) return null;
|
|
23060
23070
|
card.spec.deferredDisplayReady = true;
|
|
23071
|
+
return card.spec;
|
|
23072
|
+
},
|
|
23073
|
+
push: () => {
|
|
23074
|
+
const spec = entry.materialize();
|
|
23075
|
+
if (!spec) return;
|
|
23061
23076
|
pushingFromDeferredEntry = true;
|
|
23062
23077
|
try {
|
|
23063
|
-
pushItem(
|
|
23078
|
+
pushItem(spec);
|
|
23064
23079
|
} finally {
|
|
23065
23080
|
pushingFromDeferredEntry = false;
|
|
23066
23081
|
}
|
|
@@ -23081,13 +23096,18 @@ async function createEngineSession({
|
|
|
23081
23096
|
seq: deferredSeqCounter++,
|
|
23082
23097
|
pushed: false,
|
|
23083
23098
|
timer: null,
|
|
23084
|
-
|
|
23099
|
+
materialize: () => {
|
|
23085
23100
|
aggregate.pushed = true;
|
|
23086
|
-
if (!aggregate.pendingSpec) return;
|
|
23101
|
+
if (!aggregate.pendingSpec) return null;
|
|
23087
23102
|
aggregate.pendingSpec.deferredDisplayReady = true;
|
|
23103
|
+
return aggregate.pendingSpec;
|
|
23104
|
+
},
|
|
23105
|
+
push: () => {
|
|
23106
|
+
const spec = entry.materialize();
|
|
23107
|
+
if (!spec) return;
|
|
23088
23108
|
pushingFromDeferredEntry = true;
|
|
23089
23109
|
try {
|
|
23090
|
-
pushItem(
|
|
23110
|
+
pushItem(spec);
|
|
23091
23111
|
} finally {
|
|
23092
23112
|
pushingFromDeferredEntry = false;
|
|
23093
23113
|
}
|
|
@@ -23111,6 +23131,35 @@ async function createEngineSession({
|
|
|
23111
23131
|
}
|
|
23112
23132
|
}
|
|
23113
23133
|
};
|
|
23134
|
+
const collectDeferredUpTo = (entry) => {
|
|
23135
|
+
const specs = [];
|
|
23136
|
+
if (!entry) return specs;
|
|
23137
|
+
for (const e of deferredEntries) {
|
|
23138
|
+
if (e.seq > entry.seq) break;
|
|
23139
|
+
if (e.pushed) continue;
|
|
23140
|
+
e.pushed = true;
|
|
23141
|
+
if (e.timer) {
|
|
23142
|
+
clearTimeout(e.timer);
|
|
23143
|
+
e.timer = null;
|
|
23144
|
+
}
|
|
23145
|
+
const spec = e.materialize?.();
|
|
23146
|
+
if (spec) specs.push(spec);
|
|
23147
|
+
}
|
|
23148
|
+
return specs;
|
|
23149
|
+
};
|
|
23150
|
+
const appendItemsBatch = (newItems, extra = {}) => {
|
|
23151
|
+
if (!newItems || !newItems.length) {
|
|
23152
|
+
set(extra);
|
|
23153
|
+
return;
|
|
23154
|
+
}
|
|
23155
|
+
const base = state.items.length;
|
|
23156
|
+
const items = [...state.items, ...newItems];
|
|
23157
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
23158
|
+
const it = newItems[i];
|
|
23159
|
+
if (it?.id != null) itemIndexById.set(it.id, base + i);
|
|
23160
|
+
}
|
|
23161
|
+
set({ items, ...extra });
|
|
23162
|
+
};
|
|
23114
23163
|
const markPromptCommitted = () => {
|
|
23115
23164
|
if (activePromptRestore) {
|
|
23116
23165
|
if (!promptCommittedCallbackCalled && typeof activePromptRestore.onCommitted === "function") {
|
|
@@ -23613,13 +23662,14 @@ async function createEngineSession({
|
|
|
23613
23662
|
denyAllToolApprovals(cancelled ? "turn cancelled" : "turn finished");
|
|
23614
23663
|
clearWatchdog();
|
|
23615
23664
|
const isStaleUnwind = leadTurnEpoch !== turnEpoch;
|
|
23665
|
+
let closingItems = [];
|
|
23616
23666
|
if (deferredEntries.length) {
|
|
23617
23667
|
const last = deferredEntries[deferredEntries.length - 1];
|
|
23618
|
-
|
|
23668
|
+
closingItems = collectDeferredUpTo(last);
|
|
23619
23669
|
clearDeferredTimers();
|
|
23620
23670
|
}
|
|
23621
23671
|
flushDeferredBeforeImmediatePush = null;
|
|
23622
|
-
const producedTranscriptItem = state.items.length > itemsAtTurnStart;
|
|
23672
|
+
const producedTranscriptItem = state.items.length + closingItems.length > itemsAtTurnStart;
|
|
23623
23673
|
const reclaimed = cancelled && activePromptRestore?.reclaimed === true;
|
|
23624
23674
|
if (!isStaleUnwind) activePromptRestore = null;
|
|
23625
23675
|
closeThinkingSegment();
|
|
@@ -23633,15 +23683,16 @@ async function createEngineSession({
|
|
|
23633
23683
|
const assistantOutput = (currentAssistantText || assistantText || "").trim();
|
|
23634
23684
|
const isNoOpTurn = turnFinishedNormally && !cancelled && toolCards.length === 0 && !resultContent && !assistantOutput && !producedTranscriptItem;
|
|
23635
23685
|
if (isStaleUnwind) {
|
|
23686
|
+
if (closingItems.length) appendItemsBatch(closingItems);
|
|
23636
23687
|
tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} \u2014 force-released; skipping shared-state writes`);
|
|
23637
23688
|
} else {
|
|
23638
23689
|
if (!isNoOpTurn) {
|
|
23639
23690
|
state.stats.turns = (state.stats.turns || 0) + 1;
|
|
23640
23691
|
}
|
|
23641
23692
|
if (!reclaimed && !isNoOpTurn) {
|
|
23642
|
-
|
|
23693
|
+
closingItems.push({ kind: "turndone", id: nextId(), elapsedMs, status: turnStatus, outputTokens: finalOutputTokens, thinkingElapsedMs, verb: pickDoneVerb(turnIndex) });
|
|
23643
23694
|
}
|
|
23644
|
-
|
|
23695
|
+
appendItemsBatch(closingItems, {
|
|
23645
23696
|
busy: false,
|
|
23646
23697
|
spinner: null,
|
|
23647
23698
|
thinking: null,
|
package/src/tui/engine.mjs
CHANGED
|
@@ -775,12 +775,19 @@ export async function createEngineSession({
|
|
|
775
775
|
seq: deferredSeqCounter++,
|
|
776
776
|
pushed: false,
|
|
777
777
|
timer: null,
|
|
778
|
-
|
|
778
|
+
// Mark the card visible and return its spec WITHOUT emitting, so a
|
|
779
|
+
// batched turn-close flush can commit many specs in one set().
|
|
780
|
+
materialize: () => {
|
|
779
781
|
card.pushed = true;
|
|
780
|
-
if (!card.spec) return;
|
|
782
|
+
if (!card.spec) return null;
|
|
781
783
|
card.spec.deferredDisplayReady = true;
|
|
784
|
+
return card.spec;
|
|
785
|
+
},
|
|
786
|
+
push: () => {
|
|
787
|
+
const spec = entry.materialize();
|
|
788
|
+
if (!spec) return;
|
|
782
789
|
pushingFromDeferredEntry = true;
|
|
783
|
-
try { pushItem(
|
|
790
|
+
try { pushItem(spec); } finally { pushingFromDeferredEntry = false; }
|
|
784
791
|
},
|
|
785
792
|
};
|
|
786
793
|
card.deferred = entry;
|
|
@@ -798,12 +805,17 @@ export async function createEngineSession({
|
|
|
798
805
|
seq: deferredSeqCounter++,
|
|
799
806
|
pushed: false,
|
|
800
807
|
timer: null,
|
|
801
|
-
|
|
808
|
+
materialize: () => {
|
|
802
809
|
aggregate.pushed = true;
|
|
803
|
-
if (!aggregate.pendingSpec) return;
|
|
810
|
+
if (!aggregate.pendingSpec) return null;
|
|
804
811
|
aggregate.pendingSpec.deferredDisplayReady = true;
|
|
812
|
+
return aggregate.pendingSpec;
|
|
813
|
+
},
|
|
814
|
+
push: () => {
|
|
815
|
+
const spec = entry.materialize();
|
|
816
|
+
if (!spec) return;
|
|
805
817
|
pushingFromDeferredEntry = true;
|
|
806
|
-
try { pushItem(
|
|
818
|
+
try { pushItem(spec); } finally { pushingFromDeferredEntry = false; }
|
|
807
819
|
},
|
|
808
820
|
};
|
|
809
821
|
aggregate.deferred = entry;
|
|
@@ -821,6 +833,35 @@ export async function createEngineSession({
|
|
|
821
833
|
if (e.timer) { clearTimeout(e.timer); e.timer = null; }
|
|
822
834
|
}
|
|
823
835
|
};
|
|
836
|
+
// Collect (mark pushed + cancel timers) every still-deferred entry up to
|
|
837
|
+
// `entry` in creation order, returning their specs WITHOUT emitting — the
|
|
838
|
+
// caller commits them (optionally alongside a trailing turndone item) in a
|
|
839
|
+
// single set() so turn-close writes land as ONE visible commit.
|
|
840
|
+
const collectDeferredUpTo = (entry) => {
|
|
841
|
+
const specs = [];
|
|
842
|
+
if (!entry) return specs;
|
|
843
|
+
for (const e of deferredEntries) {
|
|
844
|
+
if (e.seq > entry.seq) break;
|
|
845
|
+
if (e.pushed) continue;
|
|
846
|
+
e.pushed = true;
|
|
847
|
+
if (e.timer) { clearTimeout(e.timer); e.timer = null; }
|
|
848
|
+
const spec = e.materialize?.();
|
|
849
|
+
if (spec) specs.push(spec);
|
|
850
|
+
}
|
|
851
|
+
return specs;
|
|
852
|
+
};
|
|
853
|
+
// Append pre-built items (deferred cards + turndone) in ONE set(). None are
|
|
854
|
+
// 'user' kind, so no promptHistory rebuild is needed.
|
|
855
|
+
const appendItemsBatch = (newItems, extra = {}) => {
|
|
856
|
+
if (!newItems || !newItems.length) { set(extra); return; }
|
|
857
|
+
const base = state.items.length;
|
|
858
|
+
const items = [...state.items, ...newItems];
|
|
859
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
860
|
+
const it = newItems[i];
|
|
861
|
+
if (it?.id != null) itemIndexById.set(it.id, base + i);
|
|
862
|
+
}
|
|
863
|
+
set({ items, ...extra });
|
|
864
|
+
};
|
|
824
865
|
|
|
825
866
|
const markPromptCommitted = () => {
|
|
826
867
|
if (activePromptRestore) {
|
|
@@ -1542,13 +1583,18 @@ export async function createEngineSession({
|
|
|
1542
1583
|
// pending push timers so nothing fires (or leaks) after the turn ends. The
|
|
1543
1584
|
// finalize path above already patches results onto visible cards; this just
|
|
1544
1585
|
// guarantees every registered card is materialized before the turn closes.
|
|
1586
|
+
// Collect (don't emit) the still-deferred cards so the turn-close flush and
|
|
1587
|
+
// the turndone item append in ONE set() below instead of one render bounce
|
|
1588
|
+
// per row. Order/ids are preserved (creation order, then turndone last).
|
|
1589
|
+
let closingItems = [];
|
|
1545
1590
|
if (deferredEntries.length) {
|
|
1546
1591
|
const last = deferredEntries[deferredEntries.length - 1];
|
|
1547
|
-
|
|
1592
|
+
closingItems = collectDeferredUpTo(last);
|
|
1548
1593
|
clearDeferredTimers();
|
|
1549
1594
|
}
|
|
1550
1595
|
flushDeferredBeforeImmediatePush = null;
|
|
1551
|
-
const producedTranscriptItem =
|
|
1596
|
+
const producedTranscriptItem =
|
|
1597
|
+
state.items.length + closingItems.length > itemsAtTurnStart;
|
|
1552
1598
|
const reclaimed = cancelled && activePromptRestore?.reclaimed === true;
|
|
1553
1599
|
if (!isStaleUnwind) activePromptRestore = null;
|
|
1554
1600
|
closeThinkingSegment();
|
|
@@ -1574,6 +1620,9 @@ export async function createEngineSession({
|
|
|
1574
1620
|
&& !assistantOutput
|
|
1575
1621
|
&& !producedTranscriptItem;
|
|
1576
1622
|
if (isStaleUnwind) {
|
|
1623
|
+
// Preserve prior behavior: a stale unwind still materializes its own
|
|
1624
|
+
// deferred cards (turn-local teardown) but writes no other shared state.
|
|
1625
|
+
if (closingItems.length) appendItemsBatch(closingItems);
|
|
1577
1626
|
tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} — force-released; skipping shared-state writes`);
|
|
1578
1627
|
} else {
|
|
1579
1628
|
if (!isNoOpTurn) {
|
|
@@ -1584,9 +1633,10 @@ export async function createEngineSession({
|
|
|
1584
1633
|
// in scrollback. (Previously TurnDone rendered only in the
|
|
1585
1634
|
// bottom-fixed live-status slot and vanished on the next turn.)
|
|
1586
1635
|
if (!reclaimed && !isNoOpTurn) {
|
|
1587
|
-
|
|
1636
|
+
closingItems.push({ kind: 'turndone', id: nextId(), elapsedMs, status: turnStatus, outputTokens: finalOutputTokens, thinkingElapsedMs, verb: pickDoneVerb(turnIndex) });
|
|
1588
1637
|
}
|
|
1589
|
-
set(
|
|
1638
|
+
// Deferred cards + turndone + status all land in ONE set() (one commit).
|
|
1639
|
+
appendItemsBatch(closingItems, {
|
|
1590
1640
|
busy: false,
|
|
1591
1641
|
spinner: null,
|
|
1592
1642
|
thinking: null,
|