pi-harness-runtime 1.1.136 → 1.1.138
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/harness/langchain/daemon.ts +128 -25
- package/harness/langchain/run.ts +1 -1
- package/index.ts +41 -2
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/dist/src/index.d.ts +42 -0
- package/packages/codex-watcher/dist/src/index.d.ts.map +1 -0
- package/packages/codex-watcher/dist/src/index.js +198 -0
- package/packages/codex-watcher/dist/src/index.js.map +1 -0
- package/packages/codex-watcher/dist/src/parser.d.ts +36 -0
- package/packages/codex-watcher/dist/src/parser.d.ts.map +1 -0
- package/packages/codex-watcher/dist/src/parser.js +151 -0
- package/packages/codex-watcher/dist/src/parser.js.map +1 -0
- package/packages/codex-watcher/dist/src/session.d.ts +9 -0
- package/packages/codex-watcher/dist/src/session.d.ts.map +1 -0
- package/packages/codex-watcher/dist/src/session.js +131 -0
- package/packages/codex-watcher/dist/src/session.js.map +1 -0
- package/packages/codex-watcher/dist/src/types.d.ts +63 -0
- package/packages/codex-watcher/dist/src/types.d.ts.map +1 -0
- package/packages/codex-watcher/dist/src/types.js +17 -0
- package/packages/codex-watcher/dist/src/types.js.map +1 -0
- package/packages/codex-watcher/package.json +26 -0
- package/packages/codex-watcher/src/index.ts +244 -0
- package/packages/codex-watcher/src/parser.ts +210 -0
- package/packages/codex-watcher/src/session.ts +151 -0
- package/packages/codex-watcher/src/types.ts +67 -0
- package/packages/codex-watcher/test/watch.test.ts +150 -0
- package/packages/config-capture/package.json +1 -1
- package/packages/config-capture/src/index.ts +5 -16
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/dist/harness-header.d.ts +18 -14
- package/packages/tui/dist/harness-header.d.ts.map +1 -1
- package/packages/tui/dist/harness-header.js +110 -64
- package/packages/tui/dist/harness-header.js.map +1 -1
- package/packages/tui/dist/index.d.ts +1 -2
- package/packages/tui/dist/index.d.ts.map +1 -1
- package/packages/tui/dist/index.js +1 -2
- package/packages/tui/dist/index.js.map +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/tui/src/harness-header.ts +156 -77
- package/packages/tui/src/index.ts +6 -2
- package/packages/tui/test/harness-header.test.ts +380 -100
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
* Exports the Harness custom header component that replaces Pi's built-in
|
|
5
5
|
* startup banner with a harness-branded version using Unicode half-block art.
|
|
6
6
|
*/
|
|
7
|
-
export { createHarnessHeader } from "./harness-header.js";
|
|
8
|
-
export { visibleWidth, stripAnsi } from "./harness-header.js";
|
|
7
|
+
export { createHarnessHeader, stripAnsi, visibleWidth, } from "./harness-header.js";
|
|
9
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,mBAAmB,EAEnB,SAAS,EACT,YAAY,GACZ,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,59 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Harness ASCII Banner — a Pi-compatible custom header
|
|
2
|
+
* Harness ASCII Banner — a Pi-compatible custom header.
|
|
3
3
|
*
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses Unicode block characters to create a compact 4×2 logo:
|
|
5
5
|
* - `▀` (U+2580) fills the upper half of a terminal cell
|
|
6
6
|
* - `▄` (U+2584) fills the lower half of a terminal cell
|
|
7
|
-
* -
|
|
7
|
+
* - Each cell uses a distinct foreground color to suggest depth
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* Compact/expanded states respond to Ctrl+O via Pi's global setExpanded() mechanism.
|
|
11
|
-
*
|
|
12
|
-
* MIT — derived from general technique documented in upstream pi-coding-agent.
|
|
13
|
-
* Harness branding is original work of the harness project.
|
|
9
|
+
* The logo is NOT Pi's logo — it uses the same rendering primitives only.
|
|
14
10
|
*/
|
|
15
11
|
|
|
16
|
-
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
17
|
-
|
|
18
12
|
// Re-export Theme from pi-coding-agent since @pi-harness/tui has no theme dependency.
|
|
19
13
|
// Extensions already depend on @earendil-works/pi-coding-agent.
|
|
20
14
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import {
|
|
15
|
+
import { keyHint, keyText, rawKeyHint } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
22
17
|
|
|
23
18
|
// ---------------------------------------------------------------------------
|
|
24
19
|
// Logo design
|
|
25
20
|
// ---------------------------------------------------------------------------
|
|
26
21
|
//
|
|
27
|
-
// Logical 4×4 pixel grid (each cell = foreground
|
|
22
|
+
// Logical 4×4 pixel grid (each cell = one foreground color):
|
|
28
23
|
//
|
|
29
|
-
// Row 0: ░ ░ ▓ ▒ (top half-blocks: accent, accent,
|
|
30
|
-
// Row 1: ▓ ░ ░ ▒ (bottom half-blocks:
|
|
24
|
+
// Row 0: ░ ░ ▓ ▒ (top half-blocks: accent, accent, muted, muted)
|
|
25
|
+
// Row 1: ▓ ░ ░ ▒ (bottom half-blocks: muted, success, success, muted)
|
|
31
26
|
//
|
|
32
27
|
// Rendered as 2 text rows with 4 terminal cells per row:
|
|
33
|
-
// Line 0: ▀▀█▄ (▀ = upper-half, █ = full
|
|
34
|
-
// Line 1: █▀▀▄ (
|
|
28
|
+
// Line 0: ▀▀█▄ (▀ = upper-half block, █ = full block, ▄ = lower-half block)
|
|
29
|
+
// Line 1: █▀▀▄ (alternating foreground colors per cell)
|
|
35
30
|
//
|
|
36
31
|
// Colors:
|
|
37
32
|
// - accent = harness primary (teal in harness theme, adapts to user's theme)
|
|
38
33
|
// - success = harness secondary (green in harness theme)
|
|
39
34
|
// - muted = dim teal (for visual depth)
|
|
40
|
-
// - dim = dim blue (for visual depth)
|
|
41
35
|
//
|
|
42
|
-
// The
|
|
36
|
+
// The fallback for non-color/headless modes uses an empty logo; renderCompact/Expanded
|
|
37
|
+
// show the product name and version normally.
|
|
43
38
|
|
|
44
|
-
// Logo glyphs
|
|
39
|
+
// Logo glyphs defined inline in logoLines() — kept here for reference only.
|
|
45
40
|
|
|
46
|
-
/** Harness primary
|
|
41
|
+
/** Harness primary — accent color from active theme */
|
|
47
42
|
function logoAccent(text: string, theme: Theme): string {
|
|
48
43
|
return theme.fg("accent", text);
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
/** Harness secondary
|
|
52
|
-
function
|
|
46
|
+
/** Harness secondary — success color from active theme */
|
|
47
|
+
function logoSuccess(text: string, theme: Theme): string {
|
|
53
48
|
return theme.fg("success", text);
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
/** Dim foreground —
|
|
51
|
+
/** Dim foreground — muted color for visual depth */
|
|
57
52
|
function logoDim(text: string, theme: Theme): string {
|
|
58
53
|
return theme.fg("muted", text);
|
|
59
54
|
}
|
|
@@ -61,29 +56,29 @@ function logoDim(text: string, theme: Theme): string {
|
|
|
61
56
|
/**
|
|
62
57
|
* Build the two colored logo lines for the Harness mark.
|
|
63
58
|
*
|
|
64
|
-
*
|
|
59
|
+
* Each character has its own foreground color:
|
|
65
60
|
* Line 0: ▀▀█▄ → ▀(accent) ▀(accent) █(muted) ▄(muted)
|
|
66
61
|
* Line 1: █▀▀▄ → █(muted) ▀(success) ▀(success) ▄(muted)
|
|
67
62
|
*/
|
|
68
63
|
function logoLines(theme: Theme): [string, string] {
|
|
69
64
|
return [
|
|
70
65
|
logoAccent("▀", theme) +
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
logoAccent("▀", theme) +
|
|
67
|
+
logoDim("█", theme) +
|
|
68
|
+
logoDim("▄", theme),
|
|
74
69
|
logoDim("█", theme) +
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
logoSuccess("▀", theme) +
|
|
71
|
+
logoSuccess("▀", theme) +
|
|
72
|
+
logoDim("▄", theme),
|
|
78
73
|
];
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
// ---------------------------------------------------------------------------
|
|
82
|
-
// Fallback (Apple Terminal, no
|
|
77
|
+
// Fallback (Apple Terminal, no true-color, or non-TUI modes)
|
|
83
78
|
// ---------------------------------------------------------------------------
|
|
84
79
|
|
|
85
|
-
const
|
|
86
|
-
const
|
|
80
|
+
const FALLBACK_LINE0 = "";
|
|
81
|
+
const FALLBACK_LINE1 = "";
|
|
87
82
|
|
|
88
83
|
// ---------------------------------------------------------------------------
|
|
89
84
|
// HarnessHeader component
|
|
@@ -97,17 +92,16 @@ export interface HarnessHeaderOptions {
|
|
|
97
92
|
|
|
98
93
|
export function createHarnessHeader(
|
|
99
94
|
_tui: TUI,
|
|
100
|
-
|
|
95
|
+
theme: Theme,
|
|
101
96
|
options: HarnessHeaderOptions,
|
|
102
97
|
): HarnessHeaderComponent {
|
|
103
|
-
return new HarnessHeaderComponent(
|
|
98
|
+
return new HarnessHeaderComponent(theme, options);
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
class HarnessHeaderComponent implements Component {
|
|
107
102
|
private readonly theme: Theme;
|
|
108
103
|
private readonly options: Required<HarnessHeaderOptions>;
|
|
109
104
|
private expanded = false;
|
|
110
|
-
private supportsHalfBlocks = true;
|
|
111
105
|
|
|
112
106
|
constructor(theme: Theme, options: HarnessHeaderOptions) {
|
|
113
107
|
this.theme = theme;
|
|
@@ -121,25 +115,43 @@ class HarnessHeaderComponent implements Component {
|
|
|
121
115
|
|
|
122
116
|
// --- Component interface ---
|
|
123
117
|
|
|
124
|
-
render(
|
|
118
|
+
render(width: number): string[] {
|
|
125
119
|
const { version, onboardingText } = this.options;
|
|
126
120
|
|
|
127
|
-
//
|
|
128
|
-
|
|
121
|
+
// Detect true-color support to enable/disable half-block rendering.
|
|
122
|
+
// Falls back to plain text when color mode is unavailable or mono.
|
|
123
|
+
const supportsHalfBlocks = this.theme.getColorMode() === "truecolor";
|
|
124
|
+
|
|
125
|
+
const [logoLine0, logoLine1] = supportsHalfBlocks
|
|
129
126
|
? logoLines(this.theme)
|
|
130
|
-
: [
|
|
127
|
+
: [FALLBACK_LINE0, FALLBACK_LINE1];
|
|
131
128
|
|
|
132
129
|
const versionStr = this.theme.fg("dim", `v${version}`);
|
|
133
130
|
const expandKey = keyText("app.tools.expand");
|
|
134
131
|
const onboarding = this.theme.fg("dim", onboardingText);
|
|
135
132
|
|
|
136
133
|
if (this.expanded) {
|
|
137
|
-
return this.renderExpanded(
|
|
134
|
+
return this.renderExpanded(
|
|
135
|
+
width,
|
|
136
|
+
logoLine0,
|
|
137
|
+
logoLine1,
|
|
138
|
+
versionStr,
|
|
139
|
+
expandKey,
|
|
140
|
+
onboarding,
|
|
141
|
+
);
|
|
138
142
|
}
|
|
139
|
-
return this.renderCompact(
|
|
143
|
+
return this.renderCompact(
|
|
144
|
+
width,
|
|
145
|
+
logoLine0,
|
|
146
|
+
logoLine1,
|
|
147
|
+
versionStr,
|
|
148
|
+
expandKey,
|
|
149
|
+
onboarding,
|
|
150
|
+
);
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
private renderCompact(
|
|
154
|
+
width: number,
|
|
143
155
|
logoLine0: string,
|
|
144
156
|
logoLine1: string,
|
|
145
157
|
versionStr: string,
|
|
@@ -151,74 +163,137 @@ class HarnessHeaderComponent implements Component {
|
|
|
151
163
|
// Build compact one-line shortcuts
|
|
152
164
|
const hints = [
|
|
153
165
|
keyHint("app.interrupt", "interrupt"),
|
|
154
|
-
rawKeyHint(
|
|
166
|
+
rawKeyHint(
|
|
167
|
+
`${keyText("app.clear")}/${keyText("app.exit")}`,
|
|
168
|
+
"clear/exit",
|
|
169
|
+
),
|
|
155
170
|
rawKeyHint("/", "commands"),
|
|
156
171
|
rawKeyHint("!", "bash"),
|
|
157
172
|
keyHint("app.tools.expand", "more"),
|
|
158
173
|
].join(this.theme.fg("muted", " · "));
|
|
159
174
|
|
|
160
|
-
//
|
|
161
|
-
const line0 =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const line1 = `${logoLine1} ${hints}`;
|
|
165
|
-
|
|
166
|
-
// Line 2: expand prompt
|
|
167
|
-
const line2 = this.theme.fg(
|
|
168
|
-
"dim",
|
|
169
|
-
`Press ${expandKey} to show full startup help and loaded resources.`,
|
|
175
|
+
// Build each line and truncate to width
|
|
176
|
+
const line0 = this.truncate(
|
|
177
|
+
`${logoLine0} ${this.theme.fg("text", productName)} ${versionStr}`,
|
|
178
|
+
width,
|
|
170
179
|
);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
const line1 = this.truncate(`${logoLine1} ${hints}`, width);
|
|
181
|
+
const line2 = this.truncate(
|
|
182
|
+
this.theme.fg(
|
|
183
|
+
"dim",
|
|
184
|
+
`Press ${expandKey} to show full startup help and loaded resources.`,
|
|
185
|
+
),
|
|
186
|
+
width,
|
|
187
|
+
);
|
|
188
|
+
const line4 = this.truncate(onboarding, width);
|
|
174
189
|
|
|
175
190
|
return ["", line0, line1, line2, "", line4, ""];
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
private renderExpanded(
|
|
194
|
+
width: number,
|
|
179
195
|
logoLine0: string,
|
|
196
|
+
logoLine1: string,
|
|
180
197
|
versionStr: string,
|
|
181
198
|
expandKey: string,
|
|
182
199
|
onboarding: string,
|
|
183
200
|
): string[] {
|
|
184
201
|
const { productName } = this.options;
|
|
185
202
|
|
|
186
|
-
//
|
|
187
|
-
const line0 =
|
|
203
|
+
// Build lines — both logo rows are preserved
|
|
204
|
+
const line0 = this.truncate(
|
|
205
|
+
`${logoLine0} ${this.theme.fg("text", productName)} ${versionStr}`,
|
|
206
|
+
width,
|
|
207
|
+
);
|
|
208
|
+
const line1 = this.truncate(`${logoLine1} `, width);
|
|
209
|
+
const line3 = this.truncate(
|
|
210
|
+
this.theme.fg("dim", `Press ${expandKey} to show compact header.`),
|
|
211
|
+
width,
|
|
212
|
+
);
|
|
213
|
+
const line5 = this.truncate(onboarding, width);
|
|
188
214
|
|
|
189
215
|
// Expanded: one shortcut per line
|
|
190
|
-
const
|
|
216
|
+
const expandedHints = [
|
|
191
217
|
keyHint("app.interrupt", "abort the current agent turn"),
|
|
192
218
|
keyHint("app.clear", "clear the current session"),
|
|
193
219
|
keyHint("app.exit", "exit Pi"),
|
|
194
220
|
rawKeyHint("/", "show available slash commands"),
|
|
195
221
|
rawKeyHint("!", "open a bash shell"),
|
|
196
|
-
keyHint(
|
|
222
|
+
keyHint(
|
|
223
|
+
"app.tools.expand",
|
|
224
|
+
"expand/collapse tool output and this header",
|
|
225
|
+
),
|
|
197
226
|
this.theme.fg("muted", `${expandKey} to collapse`),
|
|
198
227
|
];
|
|
199
228
|
|
|
200
|
-
//
|
|
201
|
-
const line2 = this.theme.fg("dim", `Press ${expandKey} to show compact header.`);
|
|
202
|
-
|
|
203
|
-
// Line 4: onboarding
|
|
204
|
-
const line4 = onboarding;
|
|
205
|
-
|
|
206
|
-
// Compose: blank, logo, blank, expanded hints (indented), blank, expand prompt, blank, onboarding, blank
|
|
229
|
+
// Compose: blank, logo row 1, logo row 2, blank, hints (indented), blank, expand prompt, blank, onboarding, blank
|
|
207
230
|
const result: string[] = [""];
|
|
208
231
|
result.push(line0);
|
|
232
|
+
result.push(line1);
|
|
209
233
|
result.push(""); // blank after logo
|
|
210
|
-
for (const hint of
|
|
211
|
-
result.push(` ${hint}
|
|
234
|
+
for (const hint of expandedHints) {
|
|
235
|
+
result.push(this.truncate(` ${hint}`, width));
|
|
212
236
|
}
|
|
213
237
|
result.push("");
|
|
214
|
-
result.push(
|
|
238
|
+
result.push(line3);
|
|
215
239
|
result.push("");
|
|
216
|
-
result.push(
|
|
240
|
+
result.push(line5);
|
|
217
241
|
result.push("");
|
|
218
242
|
|
|
219
243
|
return result;
|
|
220
244
|
}
|
|
221
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Truncate a string to at most `maxWidth` visible characters.
|
|
248
|
+
* ANSI escape sequences are excluded from the width count and preserved in the output.
|
|
249
|
+
* If truncation occurs, a "…" (U+2026) is appended.
|
|
250
|
+
*/
|
|
251
|
+
private truncate(str: string, maxWidth: number): string {
|
|
252
|
+
if (maxWidth <= 0) return "";
|
|
253
|
+
const w = visibleWidth(str);
|
|
254
|
+
if (w <= maxWidth) return str;
|
|
255
|
+
|
|
256
|
+
// Collect characters up to maxWidth and append ellipsis
|
|
257
|
+
const ellipsis = "…";
|
|
258
|
+
const ellipsisWidth = visibleWidth(ellipsis);
|
|
259
|
+
const targetWidth = Math.max(1, maxWidth - ellipsisWidth);
|
|
260
|
+
|
|
261
|
+
let currentWidth = 0;
|
|
262
|
+
let inEscape = false;
|
|
263
|
+
let escapeBuffer = "";
|
|
264
|
+
const result: string[] = [];
|
|
265
|
+
|
|
266
|
+
for (const char of str) {
|
|
267
|
+
if (char === "\x1b") {
|
|
268
|
+
inEscape = true;
|
|
269
|
+
escapeBuffer = char;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (inEscape) {
|
|
273
|
+
escapeBuffer += char;
|
|
274
|
+
if (/[a-zA-Z]/.test(char)) {
|
|
275
|
+
// End of CSI sequence
|
|
276
|
+
result.push(escapeBuffer);
|
|
277
|
+
inEscape = false;
|
|
278
|
+
escapeBuffer = "";
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
// Visible character
|
|
283
|
+
const charWidth = visibleWidth(char);
|
|
284
|
+
if (currentWidth + charWidth > targetWidth) {
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
currentWidth += charWidth;
|
|
288
|
+
result.push(char);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Always append a foreground reset so any open color is closed.
|
|
292
|
+
// If we broke out mid-escape, the partial escape buffer is discarded
|
|
293
|
+
// (it would be an incomplete sequence). The reset restores default.
|
|
294
|
+
return `${result.join("")}${ellipsis}\x1b[39m`;
|
|
295
|
+
}
|
|
296
|
+
|
|
222
297
|
invalidate(): void {
|
|
223
298
|
// Theme changes or expansion state changes call this to request re-render.
|
|
224
299
|
// The TUI calls render() again automatically.
|
|
@@ -240,16 +315,20 @@ class HarnessHeaderComponent implements Component {
|
|
|
240
315
|
}
|
|
241
316
|
|
|
242
317
|
// ---------------------------------------------------------------------------
|
|
243
|
-
// Visible width
|
|
318
|
+
// Visible width helpers (ANSI-aware)
|
|
244
319
|
// ---------------------------------------------------------------------------
|
|
245
320
|
|
|
246
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* Compute the visible character count of a string, excluding ANSI escape sequences.
|
|
323
|
+
* Uses `new RegExp()` to avoid Biome's no-control-characters-in-regex rule.
|
|
324
|
+
*/
|
|
247
325
|
export function visibleWidth(str: string): number {
|
|
248
|
-
//
|
|
249
|
-
return str.replace(
|
|
326
|
+
// biome-ignore lint/complexity/useRegexLiterals: must use string constructor to avoid control-char-in-regex error
|
|
327
|
+
return str.replace(new RegExp("\x1b\\[[0-9;]*[a-zA-Z]", "g"), "").length;
|
|
250
328
|
}
|
|
251
329
|
|
|
252
|
-
/** Strip ANSI
|
|
330
|
+
/** Strip all ANSI escape sequences from a string. */
|
|
253
331
|
export function stripAnsi(str: string): string {
|
|
254
|
-
|
|
332
|
+
// biome-ignore lint/complexity/useRegexLiterals: must use string constructor to avoid control-char-in-regex error
|
|
333
|
+
return str.replace(new RegExp("\x1b\\[[0-9;]*[a-zA-Z]", "g"), "");
|
|
255
334
|
}
|
|
@@ -5,5 +5,9 @@
|
|
|
5
5
|
* startup banner with a harness-branded version using Unicode half-block art.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export {
|
|
9
|
-
|
|
8
|
+
export {
|
|
9
|
+
createHarnessHeader,
|
|
10
|
+
type HarnessHeaderOptions,
|
|
11
|
+
stripAnsi,
|
|
12
|
+
visibleWidth,
|
|
13
|
+
} from "./harness-header.js";
|