@zhijiewang/openharness 2.24.0 → 2.25.0
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/dist/renderer/index.js +1 -3
- package/dist/renderer/layout-sections.js +28 -13
- package/dist/renderer/spinner-label.d.ts +6 -0
- package/dist/renderer/spinner-label.js +24 -0
- package/dist/renderer/tool-color.d.ts +8 -0
- package/dist/renderer/tool-color.js +20 -0
- package/dist/repl.js +0 -1
- package/package.json +1 -1
package/dist/renderer/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* right after the scrollback content each frame (no absolute positioning gap).
|
|
5
5
|
*/
|
|
6
6
|
import { recordApproval } from "../harness/approvals.js";
|
|
7
|
+
import { appendToolPermission } from "../harness/config.js";
|
|
7
8
|
import { getTheme } from "../utils/theme-data.js";
|
|
8
9
|
import { summarizeToolArgs } from "../utils/tool-summary.js";
|
|
9
10
|
import { CellGrid } from "./cells.js";
|
|
@@ -396,9 +397,6 @@ export class TerminalRenderer {
|
|
|
396
397
|
// exists (we don't auto-create on first interaction).
|
|
397
398
|
if (k === "a" && toolName) {
|
|
398
399
|
try {
|
|
399
|
-
// Lazy import to avoid pulling config into the renderer bundle
|
|
400
|
-
// for callers that don't trip the permission path.
|
|
401
|
-
const { appendToolPermission } = require("../harness/config.js");
|
|
402
400
|
appendToolPermission(toolName);
|
|
403
401
|
}
|
|
404
402
|
catch {
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { getTheme } from "../utils/theme-data.js";
|
|
6
6
|
import { renderDiff } from "./diff.js";
|
|
7
7
|
import { isImageOutput, renderImageInline } from "./image.js";
|
|
8
|
+
import { deriveSpinnerLabel } from "./spinner-label.js";
|
|
9
|
+
import { toolColor } from "./tool-color.js";
|
|
8
10
|
// ── Style constants ──
|
|
9
11
|
const s = (fg, bold = false, dim = false) => ({ fg, bg: null, bold, dim, underline: false });
|
|
10
12
|
export const S_TEXT = s(null);
|
|
@@ -93,14 +95,14 @@ export function renderThinkingSummarySection(state, grid, r, limit) {
|
|
|
93
95
|
export function renderSpinnerSection(state, grid, r, limit) {
|
|
94
96
|
if (!state.loading || state.streamingText || state.thinkingText || r >= limit)
|
|
95
97
|
return r;
|
|
96
|
-
const thinkText =
|
|
98
|
+
const thinkText = deriveSpinnerLabel(state.toolCalls);
|
|
97
99
|
const elapsed = state.thinkingStartedAt ? Math.floor((Date.now() - state.thinkingStartedAt) / 1000) : 0;
|
|
98
100
|
const t = getTheme();
|
|
99
101
|
const baseColor = elapsed > 60 ? t.error : elapsed > 30 ? t.stall : t.primary;
|
|
100
102
|
const shimmerColor = elapsed > 60 ? t.stallShimmer : elapsed > 30 ? t.warning : t.primaryShimmer;
|
|
101
103
|
const baseStyle = { fg: baseColor, bg: null, bold: false, dim: false, underline: false };
|
|
102
104
|
grid.writeText(r, 0, "◆ ", { ...baseStyle, bold: true });
|
|
103
|
-
const shimmerPos = state.spinnerFrame %
|
|
105
|
+
const shimmerPos = state.spinnerFrame % 20;
|
|
104
106
|
const shimmerStyle = { fg: shimmerColor, bg: null, bold: true, dim: false, underline: false };
|
|
105
107
|
for (let ci = 0; ci < thinkText.length; ci++) {
|
|
106
108
|
grid.setCell(r, 2 + ci, thinkText[ci], Math.abs(ci - shimmerPos) <= 1 ? shimmerStyle : baseStyle);
|
|
@@ -141,8 +143,9 @@ export function renderToolCallsSection(state, grid, r, limit, opts) {
|
|
|
141
143
|
: tc.status === "done"
|
|
142
144
|
? "✓"
|
|
143
145
|
: "✗";
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
+
const toolStyle = { fg: toolColor(tc.toolName), bg: null, bold: false, dim: false, underline: false };
|
|
147
|
+
const statusStyle = tc.status === "error" ? S_ERROR : tc.status === "done" ? S_GREEN : isAgent ? S_AGENT : toolStyle;
|
|
148
|
+
const nameStyle = isAgent ? S_AGENT : { ...toolStyle, bold: true };
|
|
146
149
|
const isExpanded = state.expandedToolCalls.has(callId);
|
|
147
150
|
const canExpand = tc.status !== "running" && tc.output;
|
|
148
151
|
if (canExpand) {
|
|
@@ -427,21 +430,33 @@ export function renderInputSection(state, grid, inputRow, limit, promptText, pro
|
|
|
427
430
|
const inputStart = promptWidth;
|
|
428
431
|
const inputLines = state.inputText.split("\n");
|
|
429
432
|
const maxInputLines = Math.min(inputLines.length, 5);
|
|
433
|
+
// Pre-compute the [N lines] suffix column on row 0 (if multi-line) so the
|
|
434
|
+
// wrap glyph for line 0 can yield to the suffix when they would collide.
|
|
435
|
+
// The +1 reserves the glyph column so the suffix starts one column after
|
|
436
|
+
// it (produces "text↵ [N lines]"); a +0 would let the suffix overwrite the
|
|
437
|
+
// glyph on narrow terminals.
|
|
438
|
+
let lineCountCol = -1;
|
|
439
|
+
if (inputLines.length > 1) {
|
|
440
|
+
const lineCountStr = ` [${inputLines.length} lines]`;
|
|
441
|
+
lineCountCol = Math.min(inputStart + (inputLines[0]?.length ?? 0) + 1, grid.width - lineCountStr.length - 1);
|
|
442
|
+
}
|
|
430
443
|
for (let li = 0; li < maxInputLines; li++) {
|
|
431
444
|
if (inputRow + li >= limit)
|
|
432
445
|
break;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
446
|
+
const lineText = inputLines[li];
|
|
447
|
+
grid.writeText(inputRow + li, inputStart, lineText, S_TEXT);
|
|
448
|
+
// Audit U-C2: append a dim ↵ continuation glyph to every non-last line.
|
|
449
|
+
if (li < inputLines.length - 1) {
|
|
450
|
+
const glyphCol = inputStart + lineText.length;
|
|
451
|
+
const wouldCollideWithLineCount = li === 0 && lineCountCol > inputStart && glyphCol >= lineCountCol;
|
|
452
|
+
if (glyphCol < grid.width && !wouldCollideWithLineCount) {
|
|
453
|
+
grid.writeText(inputRow + li, glyphCol, "↵", S_DIM);
|
|
454
|
+
}
|
|
438
455
|
}
|
|
439
456
|
}
|
|
440
|
-
if (inputLines.length > 1) {
|
|
457
|
+
if (inputLines.length > 1 && lineCountCol > inputStart) {
|
|
441
458
|
const lineCountStr = ` [${inputLines.length} lines]`;
|
|
442
|
-
|
|
443
|
-
if (lineCountCol > inputStart)
|
|
444
|
-
grid.writeText(inputRow, lineCountCol, lineCountStr, S_DIM);
|
|
459
|
+
grid.writeText(inputRow, lineCountCol, lineCountStr, S_DIM);
|
|
445
460
|
}
|
|
446
461
|
const hintsRow = inputRow + maxInputLines;
|
|
447
462
|
if (hintsRow < limit) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the spinner section label from the live tool-call map.
|
|
3
|
+
*/
|
|
4
|
+
export function deriveSpinnerLabel(toolCalls) {
|
|
5
|
+
const running = [];
|
|
6
|
+
for (const tc of toolCalls.values()) {
|
|
7
|
+
if (tc.status === "running")
|
|
8
|
+
running.push(tc);
|
|
9
|
+
}
|
|
10
|
+
if (running.length === 0)
|
|
11
|
+
return "Thinking";
|
|
12
|
+
if (running.length === 1) {
|
|
13
|
+
const name = running[0].toolName;
|
|
14
|
+
if (name.startsWith("mcp__")) {
|
|
15
|
+
const rest = name.slice("mcp__".length);
|
|
16
|
+
const idx = rest.indexOf("__");
|
|
17
|
+
if (idx > 0)
|
|
18
|
+
return `Calling ${rest.slice(0, idx)}:${rest.slice(idx + 2)}`;
|
|
19
|
+
}
|
|
20
|
+
return `Running ${name}`;
|
|
21
|
+
}
|
|
22
|
+
return `Running ${running.length} tools`;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=spinner-label.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a tool name to a display color for the tool-call section.
|
|
3
|
+
* Read-class tools → cyan, Mutate-class → yellow, Exec-class → magenta,
|
|
4
|
+
* MCP tools (mcp__ prefix) → green, everything else → yellow fallback.
|
|
5
|
+
*/
|
|
6
|
+
export type ToolColor = "cyan" | "yellow" | "magenta" | "green";
|
|
7
|
+
export declare function toolColor(toolName: string): ToolColor;
|
|
8
|
+
//# sourceMappingURL=tool-color.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a tool name to a display color for the tool-call section.
|
|
3
|
+
* Read-class tools → cyan, Mutate-class → yellow, Exec-class → magenta,
|
|
4
|
+
* MCP tools (mcp__ prefix) → green, everything else → yellow fallback.
|
|
5
|
+
*/
|
|
6
|
+
const READ = new Set(["Read", "Glob", "Grep", "WebFetch", "WebSearch", "ExaSearch"]);
|
|
7
|
+
const MUTATE = new Set(["Edit", "Write", "NotebookEdit"]);
|
|
8
|
+
const EXEC = new Set(["Bash", "PowerShell"]);
|
|
9
|
+
export function toolColor(toolName) {
|
|
10
|
+
if (READ.has(toolName))
|
|
11
|
+
return "cyan";
|
|
12
|
+
if (MUTATE.has(toolName))
|
|
13
|
+
return "yellow";
|
|
14
|
+
if (EXEC.has(toolName))
|
|
15
|
+
return "magenta";
|
|
16
|
+
if (toolName.startsWith("mcp__"))
|
|
17
|
+
return "green";
|
|
18
|
+
return "yellow";
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=tool-color.js.map
|
package/dist/repl.js
CHANGED
|
@@ -977,7 +977,6 @@ export async function startREPL(config) {
|
|
|
977
977
|
case "tool_call_end": {
|
|
978
978
|
const toolName = callIdToToolName.get(event.callId) ?? event.callId;
|
|
979
979
|
const prevTc = renderer.getToolCall(event.callId);
|
|
980
|
-
const _elapsed = prevTc?.startedAt ? Math.floor((Date.now() - prevTc.startedAt) / 1000) : 0;
|
|
981
980
|
renderer.setToolCall(event.callId, {
|
|
982
981
|
toolName,
|
|
983
982
|
status: event.isError ? "error" : "done",
|