@iowarp/clio-coder 0.3.2 → 0.3.3
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/CHANGELOG.md +229 -457
- package/README.md +2 -2
- package/dist/{acp-BIYHVZIM.js → acp-P2AQILE2.js} +2 -2
- package/dist/{agents-YT6SSRIT.js → agents-72W3BI7I.js} +8 -8
- package/dist/assets/codewiki.json +1 -1
- package/dist/{chunk-WMSVI4G2.js → chunk-2DJ2KNFG.js} +3 -3
- package/dist/{chunk-2EHAIA3X.js → chunk-2TLUCQVG.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-4XUGQOHA.js} +2 -2
- package/dist/{chunk-IGLFWIYI.js → chunk-5UFT4SUX.js} +2 -2
- package/dist/{chunk-AO4RKG4M.js → chunk-6SGHMWE3.js} +3 -3
- package/dist/{chunk-X75S7HFS.js → chunk-COU2UHX6.js} +45 -19
- package/dist/{chunk-KJ5LWLOE.js → chunk-DSELYM6W.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-DUYJ5IO6.js} +2 -2
- package/dist/{chunk-EPVUXGXG.js → chunk-FNTMWMX5.js} +9 -9
- package/dist/{chunk-MNA4JGU4.js → chunk-J7CWMCQD.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-KZWTDYJF.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-LM5TQCJZ.js} +3 -3
- package/dist/{chunk-OHHN2SO4.js → chunk-LW6DSM3M.js} +7 -7
- package/dist/{chunk-MQSRRFWA.js → chunk-LWLEKMDQ.js} +56 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-OC7FIQPC.js} +2 -2
- package/dist/{chunk-3ZXDFGR5.js → chunk-PAJK6MAQ.js} +2 -2
- package/dist/{chunk-7EYHLWU7.js → chunk-PIWWS5BL.js} +3 -3
- package/dist/{chunk-6EJV5X2W.js → chunk-SRDMMSEP.js} +3 -3
- package/dist/{chunk-ARBGF5F7.js → chunk-TZK7PACC.js} +2 -2
- package/dist/{chunk-QTYWRVRA.js → chunk-UFIIWP2H.js} +2 -2
- package/dist/{chunk-J5HN4RYU.js → chunk-V6RTAOC2.js} +2 -2
- package/dist/{chunk-SRF2PJNW.js → chunk-VPAYEGVX.js} +2 -2
- package/dist/{chunk-77VKQEHF.js → chunk-X6IAEBZR.js} +2 -2
- package/dist/{chunk-4KLWL3UC.js → chunk-XBXAASKX.js} +2 -2
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/cli/index.js +17 -17
- package/dist/{clio-4LY5K2AC.js → clio-JOU4FXVA.js} +2 -2
- package/dist/{config-GTLUW2PR.js → config-XCDVKR23.js} +7 -7
- package/dist/{configure-R6A64DHX.js → configure-4GAP54ZW.js} +5 -5
- package/dist/{context-RW5HC47S.js → context-4UOGGLQ5.js} +6 -6
- package/dist/{context-JFZEJ7W5.js → context-77FM5DV5.js} +9 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-XXJRLCJJ.js} +6 -6
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-QPRDDBDX.js} +7 -7
- package/dist/{doctor-KI767GSN.js → doctor-HR46URBJ.js} +5 -5
- package/dist/{evidence-UA6AWDQQ.js → evidence-6HG2PY2B.js} +4 -4
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7YU3NCY.js} +4 -4
- package/dist/{fleet-Q7UOMUSG.js → fleet-VY3HHKN6.js} +15 -15
- package/dist/{init-WBB65ZHQ.js → init-JYGXI3FK.js} +12 -12
- package/dist/{memory-MD3O64RI.js → memory-WFZMGYHX.js} +5 -5
- package/dist/{models-BZU34YWD.js → models-I5QWSEOM.js} +7 -7
- package/dist/{monitor-MEQA5C3I.js → monitor-GE4ID3IA.js} +5 -5
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-EM5MC3HM.js} +1482 -1055
- package/dist/{run-IV4Q6RLN.js → run-ZU3QMZPZ.js} +18 -18
- package/dist/{skills-LQEKRDTN.js → skills-X5VXCRNQ.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WKIHWTHR.js} +5 -5
- package/dist/{targets-C4SSGQOB.js → targets-SNCPI2NR.js} +8 -8
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-BNAHVHBS.js} +2 -2
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-JQHHPQ4K.js} +7 -7
- package/dist/{usage-GV4PKT3M.js → usage-OR4O5SMZ.js} +5 -5
- package/dist/{verify-G6V4D2G7.js → verify-375KUB3Y.js} +4 -4
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-UEXP2ARI.js} +11 -11
- package/dist/worker/entry.js +8 -8
- package/docs/README.md +3 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +1 -1
- package/docs/configuration-and-targets.md +1 -1
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +2 -2
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +2 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +1 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +3 -3
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +30 -30
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +2 -2
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +2 -2
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +1 -1
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/domains/config/keybindings.ts +3 -3
- package/src/interactive/chat-panel.ts +555 -244
- package/src/interactive/chat-renderer.ts +50 -19
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +12 -7
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +151 -56
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/tools/builtin-tool-catalog.ts +7 -1
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +6 -0
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* `isTransparentAssistantWrapper`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
16
17
|
import type {
|
|
17
18
|
BashExecutionEntry,
|
|
18
19
|
BranchSummaryEntry,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
} from "../engine/messages.js";
|
|
39
40
|
import { wrapTextWithAnsi } from "../engine/tui.js";
|
|
40
41
|
import type { AgentMessage } from "../engine/types.js";
|
|
42
|
+
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
41
43
|
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
42
44
|
import { isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
43
45
|
import type { ChatPanel } from "./chat-panel.js";
|
|
@@ -53,6 +55,7 @@ import {
|
|
|
53
55
|
type StreamPacer,
|
|
54
56
|
type StreamPacerSlice,
|
|
55
57
|
} from "./stream-pacer.js";
|
|
58
|
+
import { type FoldOverride, policyToolFold, resolveFold, type TranscriptDetailPolicy } from "./transcript-detail.js";
|
|
56
59
|
import { readWorkerReceiptFactsForReplay } from "./worker-receipts.js";
|
|
57
60
|
import { workerEntriesFromRunEntries } from "./worker-replay.js";
|
|
58
61
|
import type { WorkerReceiptReader } from "./worker-stream.js";
|
|
@@ -468,18 +471,18 @@ function toolCallMessageFromEntry(entry: MessageEntry): AgentMessage {
|
|
|
468
471
|
} as unknown as AgentMessage;
|
|
469
472
|
}
|
|
470
473
|
|
|
471
|
-
function toolResultContent(result: unknown): unknown[] {
|
|
474
|
+
function toolResultContent(result: unknown, unbounded = false): unknown[] {
|
|
472
475
|
const obj = payloadObject(result);
|
|
473
476
|
if (Array.isArray(obj?.content)) {
|
|
474
|
-
return cloneContentBlocks(obj.content, MAX_REPLAY_TEXT_CHARS) ?? [];
|
|
477
|
+
return cloneContentBlocks(obj.content, unbounded ? undefined : MAX_REPLAY_TEXT_CHARS) ?? [];
|
|
475
478
|
}
|
|
476
|
-
if (isTextResult(result)) return [{ type: "text", text: truncateReplayText(result.text) }];
|
|
477
|
-
if (typeof result === "string") return [{ type: "text", text: truncateReplayText(result) }];
|
|
478
|
-
return [{ type: "text", text: stringifyPreview(result, 10_000) }];
|
|
479
|
+
if (isTextResult(result)) return [{ type: "text", text: unbounded ? result.text : truncateReplayText(result.text) }];
|
|
480
|
+
if (typeof result === "string") return [{ type: "text", text: unbounded ? result : truncateReplayText(result) }];
|
|
481
|
+
return [{ type: "text", text: stringifyPreview(result, unbounded ? Number.POSITIVE_INFINITY : 10_000) }];
|
|
479
482
|
}
|
|
480
483
|
|
|
481
|
-
function displayReplayToolResult(result: unknown): unknown {
|
|
482
|
-
const content = toolResultContent(result);
|
|
484
|
+
function displayReplayToolResult(result: unknown, unbounded = false): unknown {
|
|
485
|
+
const content = toolResultContent(result, unbounded);
|
|
483
486
|
// Preserve the details record (observation envelope, exec records) so the
|
|
484
487
|
// replayed ledger line carries the same outcome facts as the live one.
|
|
485
488
|
const details = payloadObject(result)?.details;
|
|
@@ -704,19 +707,28 @@ function appendReplayLine(chatPanel: ChatPanel, text: string): void {
|
|
|
704
707
|
chatPanel.appendReplayBlock((width) => renderReplayLine(truncateReplayText(text), width));
|
|
705
708
|
}
|
|
706
709
|
|
|
707
|
-
function renderBashExecutionEntry(
|
|
710
|
+
function renderBashExecutionEntry(
|
|
711
|
+
entry: BashExecutionEntry,
|
|
712
|
+
width: number,
|
|
713
|
+
folded: boolean,
|
|
714
|
+
unbounded: boolean,
|
|
715
|
+
): string[] {
|
|
716
|
+
const normalizedOutput = entry.output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\s+$/g, "");
|
|
708
717
|
return renderBashTranscriptExecution(
|
|
709
718
|
{
|
|
710
719
|
command: entry.command,
|
|
711
|
-
output:
|
|
720
|
+
output: unbounded ? normalizedOutput : truncateReplayText(normalizedOutput),
|
|
712
721
|
running: false,
|
|
713
722
|
exitCode: entry.exitCode,
|
|
714
723
|
cancelled: entry.cancelled,
|
|
715
724
|
truncated: entry.truncated,
|
|
716
725
|
fullOutputPath: entry.fullOutputPath,
|
|
717
726
|
excludeFromContext: entry.excludeFromContext,
|
|
727
|
+
folded,
|
|
718
728
|
},
|
|
719
729
|
width,
|
|
730
|
+
undefined,
|
|
731
|
+
{ unbounded, diffStyle: "plain" },
|
|
720
732
|
);
|
|
721
733
|
}
|
|
722
734
|
|
|
@@ -1066,6 +1078,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1066
1078
|
options: RehydrateChatPanelOptions = {},
|
|
1067
1079
|
): void {
|
|
1068
1080
|
const pendingToolIds: string[] = [];
|
|
1081
|
+
let runAssistantMessages: AgentMessage[] = [];
|
|
1069
1082
|
const selected = selectReplayEntries(turns, options);
|
|
1070
1083
|
// One block per assignment, drawn where its first attempt started. Later
|
|
1071
1084
|
// attempts of the same assignment fold into that block as `↻` rail lines, so
|
|
@@ -1079,6 +1092,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1079
1092
|
switch (entry.kind) {
|
|
1080
1093
|
case "message": {
|
|
1081
1094
|
if (entry.role === "user") {
|
|
1095
|
+
runAssistantMessages = [];
|
|
1082
1096
|
const text = truncateReplayText(replayedUserText(entry));
|
|
1083
1097
|
if (text.length > 0) chatPanel.appendUser(text);
|
|
1084
1098
|
break;
|
|
@@ -1094,7 +1108,8 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1094
1108
|
(message as { stopReason?: string; errorMessage?: string }).errorMessage = failure.errorMessage;
|
|
1095
1109
|
}
|
|
1096
1110
|
chatPanel.applyEvent({ type: "message_end", message });
|
|
1097
|
-
|
|
1111
|
+
runAssistantMessages.push(message);
|
|
1112
|
+
chatPanel.applyEvent({ type: "agent_end", messages: runAssistantMessages });
|
|
1098
1113
|
}
|
|
1099
1114
|
break;
|
|
1100
1115
|
}
|
|
@@ -1120,7 +1135,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1120
1135
|
type: "tool_execution_end",
|
|
1121
1136
|
toolCallId: fallbackId,
|
|
1122
1137
|
toolName: result.name,
|
|
1123
|
-
result: displayReplayToolResult(result.result),
|
|
1138
|
+
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1124
1139
|
isError: result.isError,
|
|
1125
1140
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1126
1141
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
@@ -1133,7 +1148,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1133
1148
|
{
|
|
1134
1149
|
toolCallId: result.id ?? "",
|
|
1135
1150
|
toolName: result.name,
|
|
1136
|
-
result: displayReplayToolResult(result.result),
|
|
1151
|
+
result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
|
|
1137
1152
|
isError: result.isError,
|
|
1138
1153
|
...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
|
|
1139
1154
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
@@ -1159,9 +1174,25 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1159
1174
|
}
|
|
1160
1175
|
break;
|
|
1161
1176
|
}
|
|
1162
|
-
case "bashExecution":
|
|
1163
|
-
|
|
1177
|
+
case "bashExecution": {
|
|
1178
|
+
let fold: FoldOverride;
|
|
1179
|
+
const presentation = toolPresentationPolicy(ToolNames.Bash, undefined);
|
|
1180
|
+
const policyFold = (detail: TranscriptDetailPolicy) => policyToolFold(detail, presentation);
|
|
1181
|
+
const unbounded = options.unboundedToolBodies === true;
|
|
1182
|
+
chatPanel.appendReplayBlock(
|
|
1183
|
+
(width, detail) =>
|
|
1184
|
+
renderBashExecutionEntry(entry, width, resolveFold(fold, policyFold(detail)) === "folded", unbounded),
|
|
1185
|
+
undefined,
|
|
1186
|
+
{
|
|
1187
|
+
policyFold,
|
|
1188
|
+
fold: () => fold,
|
|
1189
|
+
setFold: (next: FoldOverride) => {
|
|
1190
|
+
fold = next;
|
|
1191
|
+
},
|
|
1192
|
+
},
|
|
1193
|
+
);
|
|
1164
1194
|
break;
|
|
1195
|
+
}
|
|
1165
1196
|
case "custom":
|
|
1166
1197
|
if (rendersCustomEntry(entry)) chatPanel.appendReplayBlock((width) => renderCustomEntry(entry, width));
|
|
1167
1198
|
break;
|
|
@@ -1220,9 +1251,9 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1220
1251
|
isError: true,
|
|
1221
1252
|
});
|
|
1222
1253
|
}
|
|
1223
|
-
//
|
|
1224
|
-
//
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1227
|
-
chatPanel.
|
|
1254
|
+
// A rehydrated transcript starts from the transcript detail policy alone:
|
|
1255
|
+
// whatever the operator had opened or folded before the switch belonged to
|
|
1256
|
+
// the transcript they left. /export reaches the same policy with a verbose
|
|
1257
|
+
// panel, so nothing here is terminal-only.
|
|
1258
|
+
chatPanel.clearFoldOverrides();
|
|
1228
1259
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { combineBashOutput, runBashCommand } from "../core/bash-exec.js";
|
|
2
2
|
import type { PendingSkillRequest } from "../core/skill-activation.js";
|
|
3
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
3
4
|
import type { DispatchContract } from "../domains/dispatch/contract.js";
|
|
4
5
|
import type { SessionContract, SessionEntry } from "../domains/session/index.js";
|
|
6
|
+
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
5
7
|
import type { ChatLoop } from "./chat-loop.js";
|
|
6
8
|
import type { ChatPanel } from "./chat-panel.js";
|
|
7
9
|
import { bashExecutionEntryInput, parseEditorBashCommand } from "./editor-bash.js";
|
|
@@ -14,6 +16,13 @@ import {
|
|
|
14
16
|
import { type ExternalEditResult, editTextExternally, resolveExternalEditor } from "./external-editor.js";
|
|
15
17
|
import { type BashTranscriptExecution, renderBashTranscriptExecution } from "./renderers/tool-execution.js";
|
|
16
18
|
import { parseSlashCommand, type RunIo, type SlashCommand, type SlashCommandDispatchResult } from "./slash-commands.js";
|
|
19
|
+
import {
|
|
20
|
+
type FoldOverride,
|
|
21
|
+
policyRunningToolFold,
|
|
22
|
+
policyToolFold,
|
|
23
|
+
resolveFold,
|
|
24
|
+
type TranscriptDetailPolicy,
|
|
25
|
+
} from "./transcript-detail.js";
|
|
17
26
|
|
|
18
27
|
const EDITOR_BASH_TIMEOUT_MS = 300_000;
|
|
19
28
|
|
|
@@ -154,16 +163,32 @@ export function createEditorSubmitController(deps: EditorSubmitDeps): EditorSubm
|
|
|
154
163
|
totalBytes: 0,
|
|
155
164
|
excludeFromContext: parsed.excludeFromContext,
|
|
156
165
|
};
|
|
166
|
+
// The operator's bash row follows the same rule as a model bash call: the
|
|
167
|
+
// transcript detail policy (through bash's presentation once settled)
|
|
168
|
+
// unless the operator overrode this one block with the expand key.
|
|
169
|
+
let fold: FoldOverride;
|
|
170
|
+
const bashPresentation = toolPresentationPolicy(ToolNames.Bash, undefined);
|
|
171
|
+
const policyFold = (detail: TranscriptDetailPolicy) =>
|
|
172
|
+
execution.running ? policyRunningToolFold(detail) : policyToolFold(detail, bashPresentation);
|
|
157
173
|
deps.chatPanel.appendReplayBlock(
|
|
158
|
-
(width) =>
|
|
174
|
+
(width, detail) =>
|
|
159
175
|
renderBashTranscriptExecution(
|
|
160
176
|
{
|
|
161
177
|
...execution,
|
|
178
|
+
folded: resolveFold(fold, policyFold(detail)) === "folded",
|
|
162
179
|
...(execution.running ? { elapsedMs: Math.max(0, performance.now() - startedAt) } : {}),
|
|
163
180
|
},
|
|
164
181
|
width,
|
|
165
182
|
),
|
|
166
183
|
() => execution.running,
|
|
184
|
+
{
|
|
185
|
+
policyFold,
|
|
186
|
+
fold: () => fold,
|
|
187
|
+
setFold: (next: FoldOverride) => {
|
|
188
|
+
fold = next;
|
|
189
|
+
deps.ui.requestRender();
|
|
190
|
+
},
|
|
191
|
+
},
|
|
167
192
|
);
|
|
168
193
|
deps.ui.requestRender();
|
|
169
194
|
|
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
dispatchStatusPresentation,
|
|
20
20
|
} from "../dispatch-board.js";
|
|
21
21
|
import { buildSegmentedContextBar, CONTEXT_BAR_LABEL_WIDTH, formatFooterTokens } from "../footer-panel.js";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
type AgentStatus,
|
|
24
|
+
formatReasoningChip,
|
|
25
|
+
reasoningFromSummary,
|
|
26
|
+
spinnerFrame,
|
|
27
|
+
type TurnSummary,
|
|
28
|
+
} from "../status/index.js";
|
|
23
29
|
import {
|
|
24
30
|
type ClioTheme,
|
|
25
31
|
type ClioToken,
|
|
@@ -706,11 +712,8 @@ export function formatLastTurn(theme: ClioTheme, summary: TurnSummary): string {
|
|
|
706
712
|
];
|
|
707
713
|
// Zero suppresses the chip, the same rule the chat panel's turn line follows:
|
|
708
714
|
// a turn that spent no reasoning tokens states nothing by printing `r0`.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
summary.reasoningTokenProvenance === "estimated" || summary.reasoningTokenProvenance === "mixed" ? "≈" : "";
|
|
712
|
-
parts.push(theme.fg("reason", `r${marker}${summary.reasoningTokens}`));
|
|
713
|
-
}
|
|
715
|
+
const chip = formatReasoningChip(reasoningFromSummary(summary), String);
|
|
716
|
+
if (chip !== null) parts.push(theme.fg("reason", chip));
|
|
714
717
|
if (summary.cacheReadTokens > 0 || summary.cacheWriteTokens > 0) {
|
|
715
718
|
parts.push(theme.fg("dim", `cache ${summary.cacheReadTokens}/${summary.cacheWriteTokens}`));
|
|
716
719
|
}
|
|
@@ -802,6 +805,17 @@ function liveTokenValue(
|
|
|
802
805
|
return joined.length > 0 ? joined : null;
|
|
803
806
|
}
|
|
804
807
|
|
|
808
|
+
/**
|
|
809
|
+
* The last turn's reasoning chip, or null when the turn spent none. Every
|
|
810
|
+
* surface that shows reasoning reads the same projection, so the transcript,
|
|
811
|
+
* this footer, and the receipt cannot disagree about the count or whether it
|
|
812
|
+
* is provider-attested.
|
|
813
|
+
*/
|
|
814
|
+
function reasoningChip(theme: ClioTheme, lastTurn: TurnSummary): string | null {
|
|
815
|
+
const chip = formatReasoningChip(reasoningFromSummary(lastTurn), formatFooterTokens);
|
|
816
|
+
return chip === null ? null : theme.fg("reason", chip);
|
|
817
|
+
}
|
|
818
|
+
|
|
805
819
|
function lastTurnOutcome(theme: ClioTheme, lastTurn: TurnSummary): string {
|
|
806
820
|
const stop = stopReasonStyle(lastTurn.stopReason);
|
|
807
821
|
return theme.fg(stop.token, `${stop.glyph} ${formatCompactMs(lastTurn.elapsedMs)}`);
|
|
@@ -813,12 +827,7 @@ function lastTurnDetails(theme: ClioTheme, lastTurn: TurnSummary): string {
|
|
|
813
827
|
"muted",
|
|
814
828
|
`${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`,
|
|
815
829
|
),
|
|
816
|
-
|
|
817
|
-
? theme.fg(
|
|
818
|
-
"reason",
|
|
819
|
-
`r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "≈" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`,
|
|
820
|
-
)
|
|
821
|
-
: null,
|
|
830
|
+
reasoningChip(theme, lastTurn),
|
|
822
831
|
lastTurn.cacheReadTokens > 0 || lastTurn.cacheWriteTokens > 0
|
|
823
832
|
? theme.fg(
|
|
824
833
|
"dim",
|
|
@@ -1202,14 +1211,7 @@ export function buildMetricStrip(
|
|
|
1202
1211
|
`${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`,
|
|
1203
1212
|
),
|
|
1204
1213
|
);
|
|
1205
|
-
candidates.push(
|
|
1206
|
-
lastTurn.reasoningTokens !== undefined && lastTurn.reasoningTokens > 0
|
|
1207
|
-
? theme.fg(
|
|
1208
|
-
"reason",
|
|
1209
|
-
`r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "≈" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`,
|
|
1210
|
-
)
|
|
1211
|
-
: null,
|
|
1212
|
-
);
|
|
1214
|
+
candidates.push(reasoningChip(theme, lastTurn));
|
|
1213
1215
|
// Held back so the session totals below outrank them. The chip list is cut
|
|
1214
1216
|
// to `maxChipsCount` before it is measured, and with cache and tools ahead
|
|
1215
1217
|
// of the totals an 80-column terminal spent the whole budget on per-turn
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TokenThroughputSnapshot, UsageBreakdown } from "../domains/observability/index.js";
|
|
2
2
|
import { type Text, truncateToWidth, visibleWidth } from "../engine/tui.js";
|
|
3
3
|
import type { DispatchBoardRow, DispatchBoardStatus } from "./dispatch-board.js";
|
|
4
|
+
import { formatReasoningChip } from "./status/reasoning.js";
|
|
4
5
|
import { type ClioTheme, formatCompactMs, formatContextPercent, GLYPH } from "./theme/index.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -37,7 +38,11 @@ export function tokensSegment(usage: UsageBreakdown | null | undefined): string
|
|
|
37
38
|
const reasoning = Math.max(0, usage.reasoningTokens ?? 0);
|
|
38
39
|
const total = Math.max(0, usage.totalTokens ?? input + output);
|
|
39
40
|
if (input + output + reasoning + total === 0) return null;
|
|
40
|
-
|
|
41
|
+
// Session totals are summed from provider usage payloads, so the chip carries
|
|
42
|
+
// no `≈`. It is built by the same formatter every other reasoning surface
|
|
43
|
+
// uses rather than by a fourth copy of the marker rule.
|
|
44
|
+
const reasoningChip = formatReasoningChip({ tokens: reasoning, provenance: "provider" }, formatFooterTokens);
|
|
45
|
+
const reasoningPart = reasoningChip === null ? "" : ` ${reasoningChip}`;
|
|
41
46
|
const totalPart = total > 0 ? ` Σ${formatFooterTokens(total)}` : "";
|
|
42
47
|
return `${GLYPH.up}${formatFooterTokens(input)} ${GLYPH.down}${formatFooterTokens(output)}${reasoningPart}${totalPart}`;
|
|
43
48
|
}
|
|
@@ -560,6 +560,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
|
|
|
560
560
|
recordToolEnd: (_toolName, toolCallId, isError, truncated) =>
|
|
561
561
|
presentation.recordToolEnd({ toolCallId, isError, truncated }),
|
|
562
562
|
setStatusLine: (line) => chatPanel.setStatusLine(line),
|
|
563
|
+
setLiveReasoning: (view) => chatPanel.setLiveReasoning(view),
|
|
563
564
|
setLastTurnSummary: (summary) => presentation.setLastTurnSummary(summary),
|
|
564
565
|
startTerminalProgress: () => agentProgress.start(),
|
|
565
566
|
stopTerminalProgress: () => agentProgress.stop(),
|
|
@@ -591,6 +592,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
|
|
|
591
592
|
chatPanel: {
|
|
592
593
|
appendReplayBlock: (...args) => chatRenderer.mutate(() => chatPanel.appendReplayBlock(...args), "slash-output"),
|
|
593
594
|
appendUser: (text) => chatRenderer.mutate(() => chatPanel.appendUser(text), "user-submit"),
|
|
595
|
+
clearFoldOverrides: () => chatRenderer.mutate(() => chatPanel.clearFoldOverrides(), "output-detail"),
|
|
594
596
|
},
|
|
595
597
|
beforeSemanticSubmit: () => chatRenderer.flush(),
|
|
596
598
|
settleVisibleFrame: (reason) => chatRenderer.flushAndCommit(reason),
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
type AgentStatus,
|
|
30
30
|
INLINE_STATUS_INDENT_COLS,
|
|
31
|
+
type ReasoningUsageView,
|
|
32
|
+
reasoningFromTally,
|
|
31
33
|
resolveInlineVerb,
|
|
32
34
|
type StatusPhase,
|
|
33
35
|
spinnerFrame,
|
|
@@ -67,6 +69,11 @@ export interface InteractiveEventProjectionDeps {
|
|
|
67
69
|
recordToolStart: (toolName: string, toolCallId: string) => void;
|
|
68
70
|
recordToolEnd: (toolName: string, toolCallId: string, isError: boolean, truncated: boolean) => void;
|
|
69
71
|
setStatusLine: (line: InteractiveStatusLine | null) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Publish the live turn's reasoning projection to the transcript. Optional so
|
|
74
|
+
* a host without a chat panel still gets every other projection.
|
|
75
|
+
*/
|
|
76
|
+
setLiveReasoning?: (view: ReasoningUsageView | null) => void;
|
|
70
77
|
setLastTurnSummary: (summary: TurnSummary) => void;
|
|
71
78
|
startTerminalProgress: () => void;
|
|
72
79
|
stopTerminalProgress: () => void;
|
|
@@ -177,6 +184,11 @@ export function createInteractiveEventProjection(deps: InteractiveEventProjectio
|
|
|
177
184
|
let statusInlineFrame = 0;
|
|
178
185
|
primaryUnsubscribers.push(
|
|
179
186
|
deps.status.subscribe((status) => {
|
|
187
|
+
// One projection of the run tally reaches the transcript, so the live
|
|
188
|
+
// line, the turn receipt, and the footer state the same number.
|
|
189
|
+
deps.setLiveReasoning?.(
|
|
190
|
+
status.phase === "idle" || status.phase === "ended" ? null : reasoningFromTally(status.runTally),
|
|
191
|
+
);
|
|
180
192
|
if (status.phase === "idle") {
|
|
181
193
|
deps.setStatusLine(null);
|
|
182
194
|
} else if (status.phase === "ended") {
|
|
@@ -54,7 +54,7 @@ export interface InteractiveSlashSubmitExpansion {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
type SlashChat = Pick<ChatLoop, "getSessionId" | "isStreaming" | "submit">;
|
|
57
|
-
type SlashChatPanel = Pick<ChatPanel, "appendReplayBlock" | "appendUser">;
|
|
57
|
+
type SlashChatPanel = Pick<ChatPanel, "appendReplayBlock" | "appendUser" | "clearFoldOverrides">;
|
|
58
58
|
type SlashResources = Pick<ResourcesContract, "prompts" | "expandPromptTemplate" | "reload">;
|
|
59
59
|
type SlashExtensions = Pick<ExtensionsContract, "list">;
|
|
60
60
|
type SlashAgents = Pick<AgentsContract, "getSpec" | "listSpecs">;
|
|
@@ -398,6 +398,11 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
398
398
|
// alone. Saving it as the default is what Settings → Terminal is for.
|
|
399
399
|
if (deps.commitSetting) deps.commitSetting("terminal.outputVerbosity", next, "session");
|
|
400
400
|
else deps.writeSettings?.(next);
|
|
401
|
+
// `/output` is also the explicit reset action for transcript folds. Do
|
|
402
|
+
// this even when the requested level already matches the current one;
|
|
403
|
+
// the render-time change detector cannot observe a same-value command.
|
|
404
|
+
deps.chatPanel.clearFoldOverrides();
|
|
405
|
+
deps.refreshFooter();
|
|
401
406
|
return { status: "applied", verbosity: match };
|
|
402
407
|
},
|
|
403
408
|
openModel: deps.openModel,
|
|
@@ -434,11 +439,12 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
434
439
|
try {
|
|
435
440
|
const turns = deps.readStructuredEntries(sessionId);
|
|
436
441
|
// Same pure render pipeline as the live panel and /resume replay: a
|
|
437
|
-
// throwaway panel is rehydrated from the ledger
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
+
// throwaway panel is rehydrated from the ledger under the verbose
|
|
443
|
+
// transcript detail policy (every body open, full receipts) with no
|
|
444
|
+
// operator overrides, and rendered at a stable width. HTML converts
|
|
445
|
+
// the resulting ANSI presentation to inline styles; Markdown keeps the
|
|
446
|
+
// prior plain-text fenced transcript.
|
|
447
|
+
const exportPanel = createChatPanel({ unboundedToolBodies: true, getOutputVerbosity: () => "verbose" });
|
|
442
448
|
// Scoped to the leaf the session is on, the same way /resume replays
|
|
443
449
|
// (issue #107): with a /tree pin persisted and not yet extended, the
|
|
444
450
|
// file still holds the abandoned branch after the pin, and an unscoped
|
|
@@ -449,7 +455,6 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
449
455
|
unboundedToolBodies: true,
|
|
450
456
|
...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
|
|
451
457
|
});
|
|
452
|
-
exportPanel.toggleAllToolsExpanded();
|
|
453
458
|
const ansiLines = exportPanel.render(EXPORT_RENDER_WIDTH);
|
|
454
459
|
// The operator names this file by the day they ran the export, so the
|
|
455
460
|
// date in it is their calendar date. The header below keeps the ISO
|