mixdog 0.9.87 → 0.9.89
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 +13 -7
- package/scripts/tool-failures.mjs +60 -24
- package/src/agents/heavy-worker/AGENT.md +3 -4
- package/src/agents/worker/AGENT.md +1 -2
- package/src/cli.mjs +8 -0
- package/src/defaults/cycle3-review-prompt.md +4 -4
- package/src/rules/agent/00-core.md +3 -0
- package/src/rules/agent/42-cycle3-agent.md +5 -6
- package/src/rules/lead/01-general.md +2 -0
- package/src/rules/shared/01-tool.md +29 -35
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -51
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +59 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +67 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +57 -4
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +2 -3
- package/src/runtime/agent/orchestrator/config.mjs +29 -11
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +0 -24
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +25 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +321 -22
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -22
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +28 -23
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -5
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +27 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +329 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +23 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +107 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +124 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +110 -75
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +96 -113
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +160 -11
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +147 -117
- package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +88 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +50 -8
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +25 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +47 -6
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +485 -24
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +100 -2
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +80 -14
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +142 -2
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +128 -32
- package/src/runtime/agent/orchestrator/session/manager.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +77 -4
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +70 -39
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +91 -0
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +141 -64
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +211 -28
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +313 -0
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +622 -53
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +43 -10
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +53 -8
- package/src/runtime/agent/orchestrator/session/store/summary-rebuild-worker.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +113 -9
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +476 -52
- package/src/runtime/agent/orchestrator/session/store.mjs +721 -196
- package/src/runtime/agent/orchestrator/session/token-native.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +4 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +146 -90
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +93 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +85 -5
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +52 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +54 -51
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +14 -15
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +132 -8
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +86 -3
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +210 -38
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +87 -19
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -8
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -2
- package/src/runtime/media/lanes.mjs +2 -2
- package/src/runtime/media/renditions.mjs +101 -17
- package/src/runtime/media/renditions.test.mjs +54 -0
- package/src/runtime/media/store.mjs +111 -43
- package/src/runtime/media/store.test.mjs +53 -11
- package/src/runtime/memory/index.mjs +26 -50
- package/src/runtime/memory/lib/core-memory-candidates.mjs +2 -7
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -9
- package/src/runtime/memory/lib/cycle-scheduler.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +3 -3
- package/src/runtime/memory/lib/embedding-worker.mjs +60 -15
- package/src/runtime/memory/lib/http-router.mjs +4 -0
- package/src/runtime/memory/lib/ko-morph.mjs +49 -6
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -18
- package/src/runtime/memory/lib/memory-cycle2.mjs +3 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -3
- package/src/runtime/memory/lib/memory-recall-store.mjs +24 -0
- package/src/runtime/memory/lib/query-handlers.mjs +26 -28
- package/src/runtime/memory/tool-defs.mjs +3 -3
- package/src/runtime/shared/atomic-file.mjs +5 -1
- package/src/runtime/shared/child-guardian.mjs +73 -2
- package/src/runtime/shared/provider-api-key.mjs +22 -5
- package/src/runtime/shared/tool-execution-contract.mjs +53 -0
- package/src/session-runtime/config-helpers.mjs +6 -5
- package/src/session-runtime/lifecycle-api.mjs +16 -4
- package/src/session-runtime/media-api.mjs +31 -16
- package/src/session-runtime/prewarm.mjs +2 -24
- package/src/session-runtime/runtime-core.mjs +33 -15
- package/src/session-runtime/runtime-tunables.mjs +0 -3
- package/src/session-runtime/session-lifecycle.mjs +0 -6
- package/src/session-runtime/session-title.mjs +228 -0
- package/src/session-runtime/session-turn-api.mjs +11 -7
- package/src/session-runtime/workflow-agents-api.mjs +16 -0
- package/src/session-runtime/workflow.mjs +7 -5
- package/src/standalone/agent-tool/job-views.mjs +90 -36
- package/src/standalone/agent-tool/spawn-flow.mjs +25 -216
- package/src/standalone/agent-tool/worker-index.mjs +8 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +17 -17
- package/src/standalone/agent-watchdog-registry.mjs +19 -2
- package/src/standalone/channel-daemon.mjs +8 -0
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/standalone/memory-runtime-proxy.mjs +4 -7
- package/src/standalone/projects.mjs +4 -1
- package/src/standalone/usage-dashboard.mjs +25 -3
- package/src/tui/App.jsx +5 -3
- package/src/tui/app/resume-picker.mjs +7 -2
- package/src/tui/app/route-pickers.mjs +11 -117
- package/src/tui/app/slash-dispatch.mjs +13 -3
- package/src/tui/app/transcript-row-estimate.mjs +1 -1
- package/src/tui/app/transcript-window.mjs +17 -93
- package/src/tui/app/use-transcript-scroll.mjs +32 -2
- package/src/tui/app/use-transcript-window.mjs +64 -114
- package/src/tui/components/Markdown.jsx +19 -2
- package/src/tui/components/Message.jsx +8 -3
- package/src/tui/components/ToolExecution.jsx +7 -3
- package/src/tui/dist/index.mjs +243 -223
- package/src/tui/engine/live-share.mjs +15 -5
- package/src/tui/engine/render-timing.mjs +2 -2
- package/src/tui/engine/session-api-ext.mjs +74 -4
- package/src/tui/engine/session-flow.mjs +3 -1
- package/src/tui/engine.mjs +2 -2
- package/src/tui/hooks/useSharedTick.mjs +0 -2
- package/src/tui/index.jsx +5 -5
- package/src/ui/statusline-agents.mjs +44 -11
- package/src/vendor/statusline/bin/statusline-route.mjs +26 -3
- package/src/workflows/default/WORKFLOW.md +3 -3
- package/src/workflows/solo/WORKFLOW.md +5 -3
- package/src/runtime/media/index.mjs +0 -18
- package/src/runtime/memory/lib/embedding-warmup.mjs +0 -68
- package/src/standalone/agent-shard/shard-child.mjs +0 -300
- package/src/standalone/agent-shard/shard-pool.mjs +0 -443
|
@@ -12,7 +12,6 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
|
|
|
12
12
|
import {
|
|
13
13
|
TRANSCRIPT_MEASURED_ROWS,
|
|
14
14
|
transcriptMeasuredRowsCache,
|
|
15
|
-
streamingMeasuredRowsById,
|
|
16
15
|
pruneStreamingMeasuredRowsById,
|
|
17
16
|
hasStreamingRowStateToPrune,
|
|
18
17
|
transcriptItemVariantKey,
|
|
@@ -21,12 +20,8 @@ import {
|
|
|
21
20
|
transcriptRowAt,
|
|
22
21
|
buildTranscriptRowIndexIncremental,
|
|
23
22
|
estimateTranscriptItemRowsCached,
|
|
24
|
-
setStreamingBottomPinned,
|
|
25
23
|
transcriptRenderWindow,
|
|
26
24
|
resolveAnchorScrollOffset,
|
|
27
|
-
streamingTailMountedGrowth,
|
|
28
|
-
streamingEstimateRows,
|
|
29
|
-
TRANSCRIPT_WINDOW_OVERSCAN_ROWS,
|
|
30
25
|
upperBound,
|
|
31
26
|
shiftSelectionRectY,
|
|
32
27
|
transcriptHarvestInputsEqual,
|
|
@@ -59,6 +54,7 @@ function traceHarvestBreaker(changedKeys) {
|
|
|
59
54
|
export function useTranscriptWindow({
|
|
60
55
|
items: settledItems,
|
|
61
56
|
structureRevision,
|
|
57
|
+
sessionKey = '',
|
|
62
58
|
streamingTail,
|
|
63
59
|
themeEpoch,
|
|
64
60
|
frameColumns,
|
|
@@ -141,6 +137,46 @@ export function useTranscriptWindow({
|
|
|
141
137
|
// item by id at call time. The ref(null) path drops the element; the harvest
|
|
142
138
|
// reads getComputedHeight from whatever is mounted, so an unmount simply stops
|
|
143
139
|
// contributing (its last measurement stays cached on the item object).
|
|
140
|
+
const normalizedSessionKey = String(sessionKey ?? '');
|
|
141
|
+
const transcriptSessionKeyRef = useRef(normalizedSessionKey);
|
|
142
|
+
if (transcriptSessionKeyRef.current !== normalizedSessionKey) {
|
|
143
|
+
// A resumed session replaces the transcript atomically, but this hook
|
|
144
|
+
// remains mounted. Never let the outgoing session's row index, reading
|
|
145
|
+
// anchor, measured-element map, or bottom-relative offset participate in
|
|
146
|
+
// the incoming session's first frame. Reset synchronously during render so
|
|
147
|
+
// the new transcript paints at its final bottom-pinned position; the layout
|
|
148
|
+
// effect below only reconciles the App-owned scroll state afterward.
|
|
149
|
+
transcriptSessionKeyRef.current = normalizedSessionKey;
|
|
150
|
+
transcriptTotalRowsRef.current = 0;
|
|
151
|
+
harvestBumpStreakRef.current = { count: 0, timer: null };
|
|
152
|
+
committedMaxScrollRowsRef.current = 0;
|
|
153
|
+
incrementalRowIndexCacheRef.current = null;
|
|
154
|
+
transcriptItemsCacheRef.current = null;
|
|
155
|
+
prevViewportGeomRef.current = { contentHeight: 0, floatingPanelRows: 0 };
|
|
156
|
+
transcriptItemElsRef.current.clear();
|
|
157
|
+
transcriptMeasureRefCache.current.clear();
|
|
158
|
+
transcriptMeasureItemsRef.current.clear();
|
|
159
|
+
harvestGateRef.current = {
|
|
160
|
+
inputs: null,
|
|
161
|
+
skippedForDrag: false,
|
|
162
|
+
forceNext: false,
|
|
163
|
+
};
|
|
164
|
+
transcriptAnchorRef.current = null;
|
|
165
|
+
transcriptAnchorDirtyRef.current = false;
|
|
166
|
+
scrollTargetRef.current = 0;
|
|
167
|
+
scrollPositionRef.current = 0;
|
|
168
|
+
maxScrollRowsRef.current = 0;
|
|
169
|
+
transcriptGeomRef.current = {};
|
|
170
|
+
followingRef.current = true;
|
|
171
|
+
selectionLayoutRef.current = null;
|
|
172
|
+
if (hasStreamingRowStateToPrune()) {
|
|
173
|
+
pruneStreamingMeasuredRowsById(new Set());
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
useLayoutEffect(() => {
|
|
177
|
+
if (scrollOffset !== 0) setScrollOffset(0);
|
|
178
|
+
}, [normalizedSessionKey, setScrollOffset]);
|
|
179
|
+
|
|
144
180
|
const transcriptMeasureRef = useCallback((item) => {
|
|
145
181
|
if (!TRANSCRIPT_MEASURED_ROWS || !item || item.id == null) return undefined;
|
|
146
182
|
if (shouldSuppressFullyFailedToolItem(item)) {
|
|
@@ -172,13 +208,7 @@ export function useTranscriptWindow({
|
|
|
172
208
|
? streamingTail
|
|
173
209
|
: null;
|
|
174
210
|
const scrolledUpRowsForPin = Math.max(0, Number(scrollTargetRef.current) || 0);
|
|
175
|
-
const transcriptPinnedForStreaming = followingRef.current
|
|
176
|
-
|| scrolledUpRowsForPin === 0;
|
|
177
|
-
// Publish the pinned state to the streaming-tail estimator BEFORE resolving
|
|
178
|
-
// tailSig / the row-index memo this render, so max(measuredFloor, liveEstimate)
|
|
179
|
-
// applies while bottom-pinned (right geometry on first commit, no judder) and
|
|
180
|
-
// every tail-resolving path reads the same height (no sig/geometry divergence).
|
|
181
|
-
setStreamingBottomPinned(transcriptPinnedForStreaming);
|
|
211
|
+
const transcriptPinnedForStreaming = followingRef.current || scrolledUpRowsForPin === 0;
|
|
182
212
|
const tailRows = streamingTailItem
|
|
183
213
|
? estimateTranscriptItemRowsCached(streamingTailItem, frameColumns, toolOutputExpanded)
|
|
184
214
|
: 0;
|
|
@@ -381,41 +411,6 @@ export function useTranscriptWindow({
|
|
|
381
411
|
contentHeight: lockViewRows,
|
|
382
412
|
floatingPanelRows: Number(floatingPanelRows) || 0,
|
|
383
413
|
};
|
|
384
|
-
// ── Same-frame streaming-tail growth compensation (scrolled-up only) ───────
|
|
385
|
-
// When the user reads older transcript, the row index freezes the trailing
|
|
386
|
-
// streaming item at its last Yoga-confirmed height (defer-growth), but the
|
|
387
|
-
// mounted Box lays out at the live-grown height THIS frame. renderScrollOffset
|
|
388
|
-
// was just resolved against that frozen row index, so the extra physical rows
|
|
389
|
-
// are unaccounted-for and the visible window jumps up one frame until the
|
|
390
|
-
// harvest bumps measuredRowsVersion. Fold the mounted growth delta into the
|
|
391
|
-
// offset NOW so marginBottom slides those extra rows off the bottom and the
|
|
392
|
-
// window holds still. Bottom-pinned/following is stable (flex-end owns it) and
|
|
393
|
-
// is excluded by the scrolledUp gate. No double-compensation next frame: once
|
|
394
|
-
// measuredRowsVersion bumps, the row index absorbs the growth (idEntry.rows ==
|
|
395
|
-
// the new measured height) and the live estimate matches it, so delta → 0.
|
|
396
|
-
if (scrolledUp && !followingRef.current) {
|
|
397
|
-
const growth = streamingTailMountedGrowth(
|
|
398
|
-
streamingTailItem ? [streamingTailItem] : transcriptItems,
|
|
399
|
-
frameColumns,
|
|
400
|
-
toolOutputExpanded,
|
|
401
|
-
);
|
|
402
|
-
// Only compensate when the tail is actually mounted in the rendered slice
|
|
403
|
-
// (viewport + overscan). Off-slice it is represented by a row-index-sized
|
|
404
|
-
// bottom spacer that does NOT physically grow this frame, so shifting the
|
|
405
|
-
// offset there would itself introduce a jump. In-slice its Box grows now.
|
|
406
|
-
// Gate/clamp on the POST-compensation offset: transcriptRenderWindow drops
|
|
407
|
-
// the tail once effectiveScrollOffset >= tailRows + overscan, so adding the
|
|
408
|
-
// full delta could push the offset past that edge and unmount the very row
|
|
409
|
-
// we are compensating for (a reverse jump at the boundary). Cap the applied
|
|
410
|
-
// delta so the final offset keeps the tail mounted; any residual growth left
|
|
411
|
-
// uncompensated sits in the overscan below the viewport (not visible).
|
|
412
|
-
if (growth && growth.delta > 0) {
|
|
413
|
-
const maxOffsetKeepingTailMounted = growth.tailRows + TRANSCRIPT_WINDOW_OVERSCAN_ROWS - 1;
|
|
414
|
-
if (renderScrollOffset <= maxOffsetKeepingTailMounted) {
|
|
415
|
-
renderScrollOffset += Math.min(growth.delta, maxOffsetKeepingTailMounted - renderScrollOffset);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
414
|
const transcriptWindow = useMemo(() => transcriptRenderWindow(transcriptItems, {
|
|
420
415
|
scrollOffset: renderScrollOffset,
|
|
421
416
|
viewportHeight: transcriptContentHeight,
|
|
@@ -447,12 +442,10 @@ export function useTranscriptWindow({
|
|
|
447
442
|
for (let i = 0; i < mountedSlice.length; i++) {
|
|
448
443
|
const it = mountedSlice[i];
|
|
449
444
|
if (!it || shouldSuppressFullyFailedToolItem(it)) continue;
|
|
450
|
-
if (it.kind === 'assistant'
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
445
|
+
if (it.kind === 'assistant') {
|
|
446
|
+
// Assistant rows are exact-model owned and never wait for a Yoga
|
|
447
|
+
// commit before their max scroll range becomes authoritative.
|
|
448
|
+
continue;
|
|
456
449
|
} else {
|
|
457
450
|
const prev = transcriptMeasuredRowsCache.get(it);
|
|
458
451
|
if (!prev
|
|
@@ -580,14 +573,9 @@ export function useTranscriptWindow({
|
|
|
580
573
|
if (transcriptMeasuredRowsCache.delete(item)) { changed = true; changedKeys.push(`${key}=del`); }
|
|
581
574
|
continue;
|
|
582
575
|
}
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
// (transcriptStructureSignature) could not react to it. Since streaming
|
|
587
|
-
// items are replaced per-token, their WeakMap entry is orphaned every
|
|
588
|
-
// frame; the id-keyed streamingMeasuredRowsById store (transcript-
|
|
589
|
-
// window.mjs) is written below to survive that swap. The drag guard
|
|
590
|
-
// above keeps this from fighting drag scroll.
|
|
576
|
+
// Active stream geometry is owned exclusively by the deterministic row
|
|
577
|
+
// model. Harvesting it here would create a second, post-commit geometry
|
|
578
|
+
// authority and reintroduce estimate→measure correction jitter.
|
|
591
579
|
// Width 0 = Yoga has not laid this node out yet this frame; skip so a
|
|
592
580
|
// transient 0 never poisons the cache (guard on a real positive width).
|
|
593
581
|
if (typeof yoga.getComputedWidth === 'function' && yoga.getComputedWidth() <= 0) continue;
|
|
@@ -596,48 +584,14 @@ export function useTranscriptWindow({
|
|
|
596
584
|
if (transcriptMeasuredRowsCache.delete(item)) { changed = true; changedKeys.push(`${key}=del`); }
|
|
597
585
|
continue;
|
|
598
586
|
}
|
|
599
|
-
|
|
587
|
+
if (item.kind === 'assistant') {
|
|
588
|
+
transcriptMeasuredRowsCache.delete(item);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
600
591
|
const measured = Math.max(1, rawMeasured);
|
|
601
592
|
const variantKey = transcriptItemVariantKey(item);
|
|
602
|
-
if (isStreamingAssistant) {
|
|
603
|
-
const idPrev = streamingMeasuredRowsById.get(item.id);
|
|
604
|
-
// Baseline = the estimate for the text at THIS commit's measured state.
|
|
605
|
-
// Refresh it on EVERY harvest pass (not only on an id-store change): if
|
|
606
|
-
// the estimator's row count drifts while the Yoga height is unchanged
|
|
607
|
-
// (or a same-id text reset), a stale baseline would make
|
|
608
|
-
// streamingTailMountedGrowth compensate with no physical growth.
|
|
609
|
-
const estimateAtMeasure = streamingEstimateRows(item, frameColumns, toolOutputExpanded);
|
|
610
|
-
if (!idPrev || idPrev.rows !== measured || idPrev.columns !== frameColumns || idPrev.toolExpanded !== toolExpandedFlag) {
|
|
611
|
-
// Record the estimate for the EXACT text just measured alongside the
|
|
612
|
-
// real Yoga height, so streamingTailMountedGrowth can measure later
|
|
613
|
-
// growth in the estimate metric (live_now − estimateAtMeasure) and the
|
|
614
|
-
// estimator's steady bias cancels instead of leaking into a permanent
|
|
615
|
-
// over-compensation once measuredRowsVersion has absorbed this height.
|
|
616
|
-
streamingMeasuredRowsById.set(item.id, {
|
|
617
|
-
rows: measured,
|
|
618
|
-
columns: frameColumns,
|
|
619
|
-
toolExpanded: toolExpandedFlag,
|
|
620
|
-
estimateRows: estimateAtMeasure,
|
|
621
|
-
});
|
|
622
|
-
// ALWAYS bump on a real id-store change, bottom-pinned or not.
|
|
623
|
-
// estimateTranscriptItemRowsCached (transcript-window.mjs) now
|
|
624
|
-
// returns ONLY this id-store floor for a streaming item (defer-
|
|
625
|
-
// growth: no live-estimate blend), so it is the SOLE path that ever
|
|
626
|
-
// feeds this item's grown height into transcriptStructureSig /
|
|
627
|
-
// buildTranscriptRowIndex. Skipping the bump while bottom-pinned
|
|
628
|
-
// used to be safe because the blended estimate kept the signature
|
|
629
|
-
// moving on its own each flush; with the blend gone, skipping it
|
|
630
|
-
// here would freeze totalRows/prefixRows for this item until the
|
|
631
|
-
// stream settles — invisible growth for anything reading rowIndex
|
|
632
|
-
// (windowing, maxScrollRows, an anchor captured mid-stream).
|
|
633
|
-
changed = true;
|
|
634
|
-
changedKeys.push(`${key}=${measured}s`);
|
|
635
|
-
} else {
|
|
636
|
-
idPrev.estimateRows = estimateAtMeasure;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
593
|
const prev = transcriptMeasuredRowsCache.get(item);
|
|
640
|
-
if (
|
|
594
|
+
if (prev
|
|
641
595
|
&& prev.rows === measured
|
|
642
596
|
&& prev.columns === frameColumns
|
|
643
597
|
&& prev.toolExpanded === toolExpandedFlag
|
|
@@ -650,19 +604,12 @@ export function useTranscriptWindow({
|
|
|
650
604
|
toolExpanded: toolExpandedFlag,
|
|
651
605
|
variantKey,
|
|
652
606
|
});
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// jump). Only bump when the measurement actually corrects the estimate.
|
|
660
|
-
// A CHANGED height (prev existed but differs) always bumps.
|
|
661
|
-
const estimateRows = prev
|
|
662
|
-
? -1
|
|
663
|
-
: estimateTranscriptItemRowsCached(item, frameColumns, toolOutputExpanded);
|
|
664
|
-
if (prev || measured !== estimateRows) { changed = true; changedKeys.push(`${key}=${measured}`); }
|
|
665
|
-
}
|
|
607
|
+
// First mount (no prior entry): this frame's row index already used the
|
|
608
|
+
// estimate. Only bump when Yoga actually corrects it.
|
|
609
|
+
const estimateRows = prev
|
|
610
|
+
? -1
|
|
611
|
+
: estimateTranscriptItemRowsCached(item, frameColumns, toolOutputExpanded);
|
|
612
|
+
if (prev || measured !== estimateRows) { changed = true; changedKeys.push(`${key}=${measured}`); }
|
|
666
613
|
}
|
|
667
614
|
if (changed) {
|
|
668
615
|
// `changed` only flips true when a height actually differs from the
|
|
@@ -723,7 +670,10 @@ export function useTranscriptWindow({
|
|
|
723
670
|
const currentPosition = Math.max(0, Number(scrollPositionRef.current) || 0);
|
|
724
671
|
const currentOffset = Math.max(0, Number(scrollOffset) || 0);
|
|
725
672
|
const maxRows = Math.max(0, Number(transcriptWindow.maxScrollRows) || 0);
|
|
726
|
-
|
|
673
|
+
// Follow ownership is explicit. target=0 can also mean the first manual
|
|
674
|
+
// read-back gesture is waiting for a newly mounted row measurement; never
|
|
675
|
+
// infer permission to chase the tail from the numeric offset alone.
|
|
676
|
+
const nearBottom = followingRef.current;
|
|
727
677
|
const pinnedToBottom = nearBottom;
|
|
728
678
|
// A genuine reading anchor must win over ordinary stream growth, but an
|
|
729
679
|
// explicit follow arm (prompt submit / pinned bottom) must win over stale
|
|
@@ -88,14 +88,31 @@ const StableMarkdownChunk = React.memo(function StableMarkdownChunk({
|
|
|
88
88
|
return <Markdown themeEpoch={themeEpoch} columns={columns}>{text}</Markdown>;
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
export function StreamingMarkdown({
|
|
91
|
+
export function StreamingMarkdown({
|
|
92
|
+
children,
|
|
93
|
+
themeEpoch = 0,
|
|
94
|
+
columns,
|
|
95
|
+
streamKey,
|
|
96
|
+
streaming = true,
|
|
97
|
+
}) {
|
|
98
|
+
if (!streaming) {
|
|
99
|
+
return (
|
|
100
|
+
<Box flexDirection="column">
|
|
101
|
+
<Markdown themeEpoch={themeEpoch} columns={columns} trimPartialFences>{children}</Markdown>
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
92
105
|
const parts = resolveStreamingMarkdownParts(children, streamKey);
|
|
93
106
|
if (parts.plain) {
|
|
94
107
|
// Plain streaming text has no markdown tokens to style. Sending a growing
|
|
95
108
|
// multi-line tail through renderTokenAnsiSegments/marked on every delta
|
|
96
109
|
// reparses the whole response and dominates frame time; Ink can wrap the
|
|
97
110
|
// identical visible text directly.
|
|
98
|
-
return
|
|
111
|
+
return (
|
|
112
|
+
<Box flexDirection="column">
|
|
113
|
+
<Text color={theme.text} wrap="wrap">{parts.unstableForRender}</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
);
|
|
99
116
|
}
|
|
100
117
|
const stableChunks = parts.stableChunks?.length
|
|
101
118
|
? parts.stableChunks
|
|
@@ -58,9 +58,14 @@ export const AssistantMessage = React.memo(function AssistantMessage({
|
|
|
58
58
|
<Text color={theme.text}>{TURN_MARKER}</Text>
|
|
59
59
|
</Box>
|
|
60
60
|
<Box flexDirection="column" flexShrink={0} width={bodyWidth}>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
<StreamingMarkdown
|
|
62
|
+
themeEpoch={themeEpoch}
|
|
63
|
+
columns={bodyWidth}
|
|
64
|
+
streamKey={assistantId}
|
|
65
|
+
streaming={streaming}
|
|
66
|
+
>
|
|
67
|
+
{streaming ? renderText : text}
|
|
68
|
+
</StreamingMarkdown>
|
|
64
69
|
</Box>
|
|
65
70
|
</Box>
|
|
66
71
|
);
|
|
@@ -368,17 +368,21 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
368
368
|
const trailingColor = expandHintColor;
|
|
369
369
|
let labelOut;
|
|
370
370
|
let summaryOut;
|
|
371
|
+
// Shell headers stay label-only ("Ran 1 command") on every surface —
|
|
372
|
+
// opencode parity and the desktop card contract: the raw command line never
|
|
373
|
+
// rides inline in the header; it stays in the expanded detail body.
|
|
374
|
+
const headerSummaryText = isShellSurface ? '' : summaryText;
|
|
371
375
|
if (stringWidth(labelText) >= avail) {
|
|
372
376
|
labelOut = truncateToWidth(labelText, avail);
|
|
373
377
|
summaryOut = '';
|
|
374
378
|
} else {
|
|
375
379
|
labelOut = labelText;
|
|
376
|
-
const summaryBudget = avail - stringWidth(labelText) - (
|
|
380
|
+
const summaryBudget = avail - stringWidth(labelText) - (headerSummaryText ? stringWidth(' ()') : 0);
|
|
377
381
|
// Cap by both the remaining header width and a fixed max so long
|
|
378
382
|
// paths/queries get an ellipsis instead of dominating the line.
|
|
379
383
|
const summaryWidth = Math.max(0, Math.min(summaryBudget, SUMMARY_MAX_CHARS));
|
|
380
|
-
const truncatedSummary =
|
|
381
|
-
? truncateToWidth(
|
|
384
|
+
const truncatedSummary = headerSummaryText && summaryWidth > 0
|
|
385
|
+
? truncateToWidth(headerSummaryText, summaryWidth)
|
|
382
386
|
: '';
|
|
383
387
|
summaryOut = truncatedSummary ? ` (${truncatedSummary})` : '';
|
|
384
388
|
}
|