mixdog 0.9.38 → 0.9.40
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 +9 -4
- package/scripts/abort-recovery-test.mjs +43 -2
- package/scripts/agent-tag-reuse-smoke.mjs +146 -6
- package/scripts/agent-terminal-reap-test.mjs +127 -0
- package/scripts/agent-trace-io-test.mjs +69 -0
- package/scripts/code-graph-disk-hit-test.mjs +224 -0
- package/scripts/execution-completion-dedup-test.mjs +157 -0
- package/scripts/execution-pending-resume-kick-test.mjs +57 -2
- package/scripts/execution-resume-esc-integration-test.mjs +176 -0
- package/scripts/explore-bench.mjs +38 -2
- package/scripts/explore-prompt-policy-test.mjs +152 -11
- package/scripts/find-fuzzy-hidden-test.mjs +122 -0
- package/scripts/internal-comms-bench-test.mjs +226 -0
- package/scripts/internal-comms-bench.mjs +185 -58
- package/scripts/internal-comms-smoke.mjs +171 -23
- package/scripts/live-worker-smoke.mjs +38 -2
- package/scripts/memory-cycle-routing-test.mjs +111 -0
- package/scripts/memory-rule-contract-test.mjs +93 -0
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/rg-runner-test.mjs +240 -0
- package/scripts/routing-corpus-test.mjs +349 -0
- package/scripts/routing-corpus.mjs +211 -32
- package/scripts/session-orphan-sweep-test.mjs +83 -0
- package/scripts/steering-drain-buckets-test.mjs +316 -2
- package/scripts/tool-smoke.mjs +21 -13
- package/scripts/tool-tui-presentation-test.mjs +202 -0
- package/scripts/tui-transcript-perf-test.mjs +279 -0
- package/src/agents/heavy-worker/AGENT.md +10 -7
- package/src/agents/reviewer/AGENT.md +6 -4
- package/src/agents/worker/AGENT.md +7 -5
- package/src/rules/agent/00-common.md +4 -4
- package/src/rules/agent/00-core.md +11 -14
- package/src/rules/agent/20-skip-protocol.md +3 -3
- package/src/rules/agent/30-explorer.md +50 -60
- package/src/rules/agent/40-cycle1-agent.md +15 -24
- package/src/rules/agent/41-cycle2-agent.md +33 -57
- package/src/rules/agent/42-cycle3-agent.md +28 -42
- package/src/rules/lead/01-general.md +7 -10
- package/src/rules/lead/lead-brief.md +11 -14
- package/src/rules/lead/lead-tool.md +6 -5
- package/src/rules/shared/01-tool.md +44 -45
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +20 -5
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +21 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +17 -38
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +17 -8
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +270 -78
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +67 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +232 -43
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +43 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +27 -13
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -20
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +118 -53
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +106 -29
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +151 -64
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +37 -13
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +84 -49
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +172 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +68 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +17 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +24 -10
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -7
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -4
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +4 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +12 -2
- package/src/runtime/shared/buffered-appender.mjs +13 -2
- package/src/runtime/shared/tool-primitives.mjs +4 -1
- package/src/runtime/shared/tool-status.mjs +27 -0
- package/src/runtime/shared/tool-surface.mjs +6 -3
- package/src/session-runtime/config-helpers.mjs +14 -0
- package/src/session-runtime/context-status.mjs +1 -0
- package/src/session-runtime/effort.mjs +6 -2
- package/src/session-runtime/lifecycle-api.mjs +4 -0
- package/src/session-runtime/model-recency.mjs +5 -2
- package/src/session-runtime/runtime-core.mjs +36 -2
- package/src/session-runtime/session-turn-api.mjs +12 -0
- package/src/session-runtime/tool-catalog.mjs +34 -0
- package/src/standalone/agent-tool/notify.mjs +13 -0
- package/src/standalone/agent-tool.mjs +53 -70
- package/src/standalone/explore-tool.mjs +6 -7
- package/src/tui/App.jsx +33 -1
- package/src/tui/app/model-options.mjs +5 -3
- package/src/tui/app/model-picker.mjs +12 -24
- package/src/tui/app/transcript-window.mjs +10 -10
- package/src/tui/app/use-transcript-window.mjs +38 -56
- package/src/tui/components/ToolExecution.jsx +11 -6
- package/src/tui/components/TranscriptItem.jsx +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +24 -10
- package/src/tui/components/tool-execution/text-format.mjs +10 -19
- package/src/tui/dist/index.mjs +866 -300
- package/src/tui/engine/agent-job-feed.mjs +216 -19
- package/src/tui/engine/agent-response-tail.mjs +68 -0
- package/src/tui/engine/notification-plan.mjs +16 -0
- package/src/tui/engine/session-api.mjs +14 -2
- package/src/tui/engine/session-flow.mjs +22 -2
- package/src/tui/engine/tool-card-results.mjs +64 -37
- package/src/tui/engine/tool-result-status.mjs +75 -21
- package/src/tui/engine/turn.mjs +172 -77
- package/src/tui/engine.mjs +199 -39
- package/src/tui/index.jsx +2 -2
- package/src/workflows/bench/WORKFLOW.md +25 -35
- package/src/workflows/default/WORKFLOW.md +38 -32
- package/src/workflows/solo/WORKFLOW.md +19 -22
- package/scripts/_jitter-fuzz.mjs +0 -44410
- package/scripts/_jitter-fuzz2.mjs +0 -44400
- package/scripts/_jitter-probe.mjs +0 -44397
- package/scripts/_jp2.mjs +0 -45614
|
@@ -245,6 +245,7 @@ function toolHasDisplayResultForRows(item) {
|
|
|
245
245
|
|
|
246
246
|
function toolExpandedRawTextForRows(item, rawRt) {
|
|
247
247
|
if (item?.aggregate) return rawRt;
|
|
248
|
+
if (item?.agentResponseAggregate) return rawRt;
|
|
248
249
|
const hasDisplayResult = toolHasDisplayResultForRows(item);
|
|
249
250
|
if (hasDisplayResult) return toolDisplayedResultTextForRows(item);
|
|
250
251
|
return stripLeadingStatusMarkerFromTextForRows(rawRt || '');
|
|
@@ -831,7 +832,7 @@ export function buildTranscriptRowIndexIncremental(items, {
|
|
|
831
832
|
suppressMeasuredRowHeights = false,
|
|
832
833
|
measuredRowsVersion = 0,
|
|
833
834
|
cacheRef = null,
|
|
834
|
-
|
|
835
|
+
prefixRevision = null,
|
|
835
836
|
} = {}) {
|
|
836
837
|
const allItems = Array.isArray(items) ? items : [];
|
|
837
838
|
const tail = trailingStreamingItem(allItems);
|
|
@@ -849,11 +850,10 @@ export function buildTranscriptRowIndexIncremental(items, {
|
|
|
849
850
|
}
|
|
850
851
|
const prefixLen = allItems.length - 1;
|
|
851
852
|
const cache = holder.current;
|
|
852
|
-
// Prefix identity is carried by
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
//
|
|
856
|
-
// Fast path: same prefix length + tail id + prefixSig, and columns/expanded/
|
|
853
|
+
// Prefix identity is carried by the engine's monotonic structure revision.
|
|
854
|
+
// Every settled-item mutation, including same-length middle tool-card patches,
|
|
855
|
+
// bumps it without requiring a render-time walk over the transcript.
|
|
856
|
+
// Fast path: same prefix length + tail id + revision, and columns/expanded/
|
|
857
857
|
// suppress/version all match. Only the tail row can differ → recompute + append.
|
|
858
858
|
if (cache
|
|
859
859
|
&& cache.columns === columns
|
|
@@ -861,10 +861,10 @@ export function buildTranscriptRowIndexIncremental(items, {
|
|
|
861
861
|
&& cache.suppress === suppressMeasuredRowHeights
|
|
862
862
|
&& cache.version === measuredRowsVersion
|
|
863
863
|
&& cache.prefixLen === prefixLen
|
|
864
|
-
&&
|
|
865
|
-
&& cache.
|
|
864
|
+
&& prefixRevision != null
|
|
865
|
+
&& cache.prefixRevision === prefixRevision
|
|
866
866
|
&& cache.tailId === tail.id) {
|
|
867
|
-
//
|
|
867
|
+
// revision matches → prefix rows provably unchanged; NO prefix walk / no
|
|
868
868
|
// re-estimation. Only the tail row can differ, recompute it.
|
|
869
869
|
{
|
|
870
870
|
const tailMeasured = suppressMeasuredRowHeights
|
|
@@ -893,7 +893,7 @@ export function buildTranscriptRowIndexIncremental(items, {
|
|
|
893
893
|
suppress: suppressMeasuredRowHeights,
|
|
894
894
|
version: measuredRowsVersion,
|
|
895
895
|
prefixLen,
|
|
896
|
-
|
|
896
|
+
prefixRevision,
|
|
897
897
|
tailId: tail.id,
|
|
898
898
|
prefixRowsArr: full.rows.slice(0, prefixLen),
|
|
899
899
|
prefixPrefixRows: full.prefixRows.slice(0, prefixLen + 1),
|
|
@@ -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.
|
|
@@ -86,7 +86,7 @@ function statusCopy(name, label, count, doneCount, pending, isError, args = {})
|
|
|
86
86
|
// dropping the pad just normalizes the spacing.
|
|
87
87
|
return formatToolActionHeader(name, args, { pending, count });
|
|
88
88
|
}
|
|
89
|
-
export function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false }) {
|
|
89
|
+
export function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, exitErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false, agentResponseAggregate = false }) {
|
|
90
90
|
const rowWidth = Math.max(1, Number(columns || 80));
|
|
91
91
|
const groupCount = Math.max(1, Number(count || 1));
|
|
92
92
|
const doneCount = Math.max(0, Math.min(groupCount, Number(completedCount || (result == null ? 0 : groupCount))));
|
|
@@ -138,6 +138,9 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
138
138
|
// paint the dot red. Fall back to 0 (never `isError`) when the engine did not
|
|
139
139
|
// supply a call-error count so a result failure can't leak into the dot.
|
|
140
140
|
const callFailedCount = clampFailureCount(callErrorCount, groupCount, false);
|
|
141
|
+
// Shell command-exits (ran, non-zero exit). Counted separately so the dot
|
|
142
|
+
// paints the neutral warning "Exit" color instead of red or green success.
|
|
143
|
+
const exitFailedCount = clampFailureCount(exitErrorCount, groupCount, false);
|
|
141
144
|
const displayGroupCount = groupCount;
|
|
142
145
|
const displayCategories = normalizeCountMap(categories || {});
|
|
143
146
|
// In the DONE state, count only successful calls: error-terminated calls are
|
|
@@ -212,7 +215,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
212
215
|
const aggregateTerminalStatus = pending
|
|
213
216
|
? 'running'
|
|
214
217
|
: (resultTerminalStatus(rt) || (isError || failedCount > 0 ? 'failed' : 'completed'));
|
|
215
|
-
const dotColor = toolStatusColor({ pending, groupCount, callFailedCount, terminalStatus: aggregateTerminalStatus });
|
|
218
|
+
const dotColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus: aggregateTerminalStatus });
|
|
216
219
|
const dotText = pending && !blinkOn ? ' ' : TURN_MARKER;
|
|
217
220
|
const gutter = 2;
|
|
218
221
|
const showHeaderExpandHint = hasRawResult;
|
|
@@ -310,7 +313,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
310
313
|
const firstResultLine = hasDisplayResult ? String(lines[0] ?? '') : '';
|
|
311
314
|
const firstResultLineClipped = hasDisplayBody && stringWidth(firstResultLine) > maxResultChars;
|
|
312
315
|
const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
|
|
313
|
-
const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, isError, result: displayedResultText }) : '';
|
|
316
|
+
const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : '';
|
|
314
317
|
const shellElapsed = isShellSurface ? (shellResultElapsed(displayedResultText) || elapsed) : '';
|
|
315
318
|
const backgroundElapsed = backgroundMeta
|
|
316
319
|
? backgroundTaskElapsed(backgroundMeta, elapsed)
|
|
@@ -384,7 +387,9 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
384
387
|
const showRawResult = expanded && (hasDisplayBody || hasRawResult)
|
|
385
388
|
&& (!isBackgroundMetadataResult || hasRawResult);
|
|
386
389
|
const detailLines = showRawResult
|
|
387
|
-
? (
|
|
390
|
+
? (agentResponseAggregate && hasRawResult
|
|
391
|
+
? stripLeadingStatusMarkerLines(rawRt.split('\n'))
|
|
392
|
+
: (hasDisplayBody ? lines : (rawRt ? stripLeadingStatusMarkerLines(rawRt.split('\n')) : [])))
|
|
388
393
|
: (collapsedDetail ? [collapsedDetail] : []);
|
|
389
394
|
const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
|
|
390
395
|
const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
|
|
@@ -426,7 +431,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
426
431
|
const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
|
|
427
432
|
visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
|
|
428
433
|
}
|
|
429
|
-
const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, terminalStatus });
|
|
434
|
+
const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus });
|
|
430
435
|
const dotColor = finalStatusColor;
|
|
431
436
|
// Agent surface cards use directional markers: `←` for requests going OUT
|
|
432
437
|
// (spawn/send/etc.) and `→` for the response coming back IN. Background
|
|
@@ -446,7 +451,7 @@ export function ToolExecution({ name, args, result, rawResult, isError, errorCou
|
|
|
446
451
|
const markerText = isDirectionalMarker ? `${markerGlyph} ` : markerGlyph;
|
|
447
452
|
const dotText = pending && !blinkOn ? ' ' : markerText;
|
|
448
453
|
let labelText;
|
|
449
|
-
if (isAgentResponse) labelText = agentResponseTitle(parsedArgs);
|
|
454
|
+
if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, displayGroupCount);
|
|
450
455
|
else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
|
|
451
456
|
else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
|
|
452
457
|
else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
|
|
@@ -77,7 +77,7 @@ export const Item = React.memo(function Item({ item, prevKind, columns, toolOutp
|
|
|
77
77
|
node = <ToolHookDenialCard item={item} columns={columns} />;
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
|
-
node = <ToolExecution name={item.name} args={item.args} result={item.result} rawResult={item.rawResult} isError={item.isError} errorCount={item.errorCount} callErrorCount={item.callErrorCount} expanded={toolOutputExpanded || item.expanded} columns={columns} attached={false} count={item.count} completedCount={item.completedCount} startedAt={item.startedAt} completedAt={item.completedAt} aggregate={item.aggregate} categories={item.categories} doneCategories={item.doneCategories} headerFinalized={item.headerFinalized} deferredDisplayReady={item.deferredDisplayReady} />;
|
|
80
|
+
node = <ToolExecution name={item.name} args={item.args} result={item.result} rawResult={item.rawResult} isError={item.isError} errorCount={item.errorCount} callErrorCount={item.callErrorCount} exitErrorCount={item.exitErrorCount} expanded={toolOutputExpanded || item.expanded} columns={columns} attached={false} count={item.count} completedCount={item.completedCount} startedAt={item.startedAt} completedAt={item.completedAt} aggregate={item.aggregate} categories={item.categories} doneCategories={item.doneCategories} headerFinalized={item.headerFinalized} deferredDisplayReady={item.deferredDisplayReady} agentResponseAggregate={item.agentResponseAggregate} />;
|
|
81
81
|
break;
|
|
82
82
|
}
|
|
83
83
|
case 'notice':
|
|
@@ -21,11 +21,18 @@ export function isShellTool(normalizedName, label = '') {
|
|
|
21
21
|
return n === 'shell' || n === 'bash' || n === 'bash_session' || n === 'shell_command' || n === 'job_wait' || l === 'run';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function shellDisplayStatus({ pending = false, failedCount = 0, isError = false, result = '' } = {}) {
|
|
24
|
+
export function shellDisplayStatus({ pending = false, failedCount = 0, exitFailedCount = 0, isError = false, result = '' } = {}) {
|
|
25
25
|
const status = shellResultStatus(result);
|
|
26
26
|
if (pending || /^(running|pending|queued)$/.test(status)) return 'running';
|
|
27
27
|
if (/^cancel/.test(status)) return 'cancelled';
|
|
28
|
-
if (/^(failed|error|killed|timeout)$/.test(status)
|
|
28
|
+
if (/^(failed|error|killed|timeout)$/.test(status)) return 'failed';
|
|
29
|
+
// A command that RAN but exited non-zero is a command-exit, not a real
|
|
30
|
+
// failure: render the neutral "Exit" state unless there is ALSO a real
|
|
31
|
+
// tool-call/result failure in the group.
|
|
32
|
+
const realFailed = Math.max(0, Number(failedCount) - Number(exitFailedCount));
|
|
33
|
+
if (realFailed > 0) return 'failed';
|
|
34
|
+
if (Number(exitFailedCount) > 0) return 'exit';
|
|
35
|
+
if (isError || failedCount > 0) return 'failed';
|
|
29
36
|
return 'completed';
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -98,7 +105,9 @@ export function joinActionAgent(action, agent) {
|
|
|
98
105
|
return agent ? `${action} ${agent}` : action;
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
export function agentResponseTitle(args) {
|
|
108
|
+
export function agentResponseTitle(args, count = 1) {
|
|
109
|
+
const total = Math.max(1, Number(count) || 1);
|
|
110
|
+
if (total > 1) return `Responses ${total} agents`;
|
|
102
111
|
const name = titleizeAgentName(args?.agent || args?.subagent_type || args?.name || '');
|
|
103
112
|
// The agent + model identify the responder; the response summary itself
|
|
104
113
|
// is hidden in the collapsed card (ctrl+o expand still shows the full body).
|
|
@@ -392,14 +401,19 @@ export function clampFailureCount(errorCount, groupCount, isError) {
|
|
|
392
401
|
// cancelled -> theme.warning
|
|
393
402
|
// The RED/orange failure color is driven ONLY by real tool-call errors
|
|
394
403
|
// (`callFailedCount` — backend isError / error toolKind), NOT by command/result
|
|
395
|
-
// failures like a
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
export function toolStatusColor({ pending, groupCount, callFailedCount = 0, terminalStatus = '' }) {
|
|
404
|
+
// failures like a `[status: failed]` result. A shell command-exit
|
|
405
|
+
// (`exitFailedCount`) is its own distinct neutral state: warning color, never
|
|
406
|
+
// red. `terminalStatus` is still consulted so a cancelled card stays warning.
|
|
407
|
+
export function toolStatusColor({ pending, groupCount, callFailedCount = 0, exitFailedCount = 0, terminalStatus = '' }) {
|
|
399
408
|
if (pending) return theme.text;
|
|
400
409
|
const status = normalizeTerminalStatus(terminalStatus);
|
|
401
410
|
if (status === 'cancelled') return theme.warning;
|
|
402
|
-
if (
|
|
403
|
-
if (
|
|
404
|
-
|
|
411
|
+
if (status === 'denied') return theme.warning;
|
|
412
|
+
if (callFailedCount > 0) {
|
|
413
|
+
if (groupCount > 1 && callFailedCount < groupCount) return theme.mixdogOrange || theme.warning;
|
|
414
|
+
return theme.error;
|
|
415
|
+
}
|
|
416
|
+
// Command-exit(s) with no real tool-call failure: distinct warning state.
|
|
417
|
+
if (exitFailedCount > 0) return theme.warning;
|
|
418
|
+
return theme.success;
|
|
405
419
|
}
|
|
@@ -8,6 +8,10 @@ import { displayWidth } from '../../display-width.mjs';
|
|
|
8
8
|
import { theme } from '../../theme.mjs';
|
|
9
9
|
import { formatElapsed } from '../../time-format.mjs';
|
|
10
10
|
import stripAnsi from 'strip-ansi';
|
|
11
|
+
import {
|
|
12
|
+
normalizeToolTerminalStatus,
|
|
13
|
+
toolResultTerminalStatus,
|
|
14
|
+
} from '../../../runtime/shared/tool-status.mjs';
|
|
11
15
|
|
|
12
16
|
export const MIN_RESULT_LINE_CHARS = 24;
|
|
13
17
|
// Hard cap for the collapsed result detail row (the second line under the ⎿
|
|
@@ -108,37 +112,24 @@ export function shellResultStatus(value) {
|
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
export function normalizeTerminalStatus(value) {
|
|
111
|
-
|
|
112
|
-
if (!raw) return '';
|
|
113
|
-
if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return 'running';
|
|
114
|
-
if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return 'completed';
|
|
115
|
-
if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return 'failed';
|
|
116
|
-
if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
|
|
117
|
-
return '';
|
|
115
|
+
return normalizeToolTerminalStatus(value);
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
export function displayTerminalStatus(value) {
|
|
119
|
+
// 'exit' is a shell-only pseudo-status (command RAN but exited non-zero); it
|
|
120
|
+
// is intentionally NOT a normalized terminal status so it never colors red.
|
|
121
|
+
if (String(value || '').trim().toLowerCase() === 'exit') return 'Exit';
|
|
121
122
|
const status = normalizeTerminalStatus(value);
|
|
122
123
|
if (status === 'running') return 'Running';
|
|
123
124
|
if (status === 'completed') return 'Finished';
|
|
124
125
|
if (status === 'failed') return 'Failed';
|
|
125
126
|
if (status === 'cancelled') return 'Cancelled';
|
|
127
|
+
if (status === 'denied') return 'Denied';
|
|
126
128
|
return '';
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
export function resultTerminalStatus(value) {
|
|
130
|
-
|
|
131
|
-
const tagged = text.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
|
|
132
|
-
if (tagged) return normalizeTerminalStatus(tagged);
|
|
133
|
-
const bracketed = text.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
134
|
-
if (bracketed) return normalizeTerminalStatus(bracketed);
|
|
135
|
-
// Loose inline `status: x` / `state: x` matches are a last-resort fallback —
|
|
136
|
-
// prefer the engine-controlled `<status>` tag or `[status: …]` marker above.
|
|
137
|
-
// A loose match can false-positive on prose that happens to start with
|
|
138
|
-
// "status:" (rare, but shellResultStatus below already owns real shell
|
|
139
|
-
// output parsing; this fallback stays narrow and unchanged in behavior).
|
|
140
|
-
const inline = text.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
141
|
-
return normalizeTerminalStatus(inline);
|
|
132
|
+
return toolResultTerminalStatus(value);
|
|
142
133
|
}
|
|
143
134
|
|
|
144
135
|
const LEADING_STATUS_MARKER_LINE_RE = /^\[status:\s*[^\]]*\]\s*$/i;
|