pi-harness-runtime 1.1.134 → 1.1.136

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.
Files changed (106) hide show
  1. package/index.ts +100 -47
  2. package/package.json +1 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/config-capture/package.json +1 -1
  18. package/packages/context-compiler/package.json +1 -1
  19. package/packages/context-discovery/package.json +1 -1
  20. package/packages/context-manager/package.json +1 -1
  21. package/packages/cookie-sanitizer/package.json +1 -1
  22. package/packages/cost-optimizer/package.json +1 -1
  23. package/packages/dependency-analyzer/package.json +1 -1
  24. package/packages/django-plugin/package.json +1 -1
  25. package/packages/doc-generator/package.json +1 -1
  26. package/packages/evaluation-engine/package.json +1 -1
  27. package/packages/evaluation-runner/package.json +1 -1
  28. package/packages/event-bus/package.json +1 -1
  29. package/packages/event-store/package.json +1 -1
  30. package/packages/experience-replay/package.json +1 -1
  31. package/packages/feedback-collector/package.json +1 -1
  32. package/packages/file-copy-helper/package.json +1 -1
  33. package/packages/framework-detector/package.json +1 -1
  34. package/packages/framework-plugin-sdk/package.json +1 -1
  35. package/packages/frappe-plugin/package.json +1 -1
  36. package/packages/generic-web-plugin/package.json +1 -1
  37. package/packages/health-monitor/package.json +1 -1
  38. package/packages/intent-analyzer/package.json +1 -1
  39. package/packages/jev-judge/package.json +1 -1
  40. package/packages/knowledge-graph/package.json +1 -1
  41. package/packages/knowledge-retrieval/package.json +1 -1
  42. package/packages/laravel-plugin/package.json +1 -1
  43. package/packages/learning-engine/package.json +1 -1
  44. package/packages/mcp-adapter/package.json +1 -1
  45. package/packages/memory-engine/package.json +1 -1
  46. package/packages/milestone-manager/package.json +1 -1
  47. package/packages/model-registry/package.json +1 -1
  48. package/packages/nextjs-plugin/package.json +1 -1
  49. package/packages/notification/package.json +1 -1
  50. package/packages/observability/package.json +1 -1
  51. package/packages/okf-indexer/package.json +1 -1
  52. package/packages/performance-optimizer/package.json +1 -1
  53. package/packages/privilege-broker/package.json +1 -1
  54. package/packages/project-analyzer/package.json +1 -1
  55. package/packages/project-bootstrap/package.json +1 -1
  56. package/packages/projection-engine/package.json +1 -1
  57. package/packages/prompt-compiler/package.json +1 -1
  58. package/packages/prompt-versioning/package.json +1 -1
  59. package/packages/provider-adapter-sdk/package.json +1 -1
  60. package/packages/provider-router/package.json +1 -1
  61. package/packages/provider-selector/package.json +1 -1
  62. package/packages/providers/package.json +1 -1
  63. package/packages/qdrant-skills/package.json +1 -1
  64. package/packages/quota-manager/package.json +1 -1
  65. package/packages/rate-limiter/package.json +1 -1
  66. package/packages/react-vite-plugin/package.json +1 -1
  67. package/packages/release-manager/package.json +1 -1
  68. package/packages/requirement-compiler/package.json +1 -1
  69. package/packages/runtime/package.json +1 -1
  70. package/packages/scheduler/package.json +1 -1
  71. package/packages/scheduler-adapter/package.json +1 -1
  72. package/packages/session/package.json +1 -1
  73. package/packages/session-api/package.json +1 -1
  74. package/packages/session-export/package.json +1 -1
  75. package/packages/shared-context/package.json +1 -1
  76. package/packages/skill-mcp-client/package.json +1 -1
  77. package/packages/skill-registry/package.json +1 -1
  78. package/packages/skills/package.json +1 -1
  79. package/packages/sprint-planner/package.json +1 -1
  80. package/packages/subscription-engine/package.json +1 -1
  81. package/packages/task-compiler/package.json +1 -1
  82. package/packages/tencentdb-memory/package.json +1 -1
  83. package/packages/tencentdb-sync/package.json +1 -1
  84. package/packages/test-data-generator/package.json +1 -1
  85. package/packages/test-generator/package.json +1 -1
  86. package/packages/todo-bd-sync/package.json +1 -1
  87. package/packages/token-estimation/package.json +1 -1
  88. package/packages/token-optimizer/package.json +1 -1
  89. package/packages/trajectory/package.json +1 -1
  90. package/packages/tui/dist/harness-header.d.ts +41 -0
  91. package/packages/tui/dist/harness-header.d.ts.map +1 -0
  92. package/packages/tui/dist/harness-header.js +184 -0
  93. package/packages/tui/dist/harness-header.js.map +1 -0
  94. package/packages/tui/dist/index.d.ts +9 -0
  95. package/packages/tui/dist/index.d.ts.map +1 -0
  96. package/packages/tui/dist/index.js +9 -0
  97. package/packages/tui/dist/index.js.map +1 -0
  98. package/packages/tui/package.json +1 -1
  99. package/packages/tui/src/harness-header.ts +255 -0
  100. package/packages/tui/src/index.ts +9 -0
  101. package/packages/tui/test/harness-header.test.ts +310 -0
  102. package/packages/types/package.json +1 -1
  103. package/packages/workflow-events/package.json +1 -1
  104. package/packages/workspace-scanner/package.json +1 -1
  105. package/packages/worktree/package.json +1 -1
  106. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,310 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Unit tests for harness-header.ts
4
+ *
5
+ * Tests cover:
6
+ * - ANSI reset after every colored logo segment
7
+ * - Visible logo width (4 cells)
8
+ * - Compact/expanded expansion
9
+ * - Narrow-width rendering (no escape corruption)
10
+ * - No emoji in output
11
+ *
12
+ * Run with: bun test packages/tui/test/harness-header.test.ts
13
+ */
14
+
15
+ import { describe, it, expect, beforeAll } from "bun:test";
16
+ import { createHarnessHeader, stripAnsi, visibleWidth, type HarnessHeaderOptions } from "../src/harness-header.js";
17
+ import { initTheme, type Theme } from "@earendil-works/pi-coding-agent";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Initialize pi-coding-agent theme (required for keyText/keyHint)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ let testTheme: Theme;
24
+
25
+ beforeAll(() => {
26
+ // Initialize with dark theme so the global theme proxy works for keyText/keyHint
27
+ initTheme("dark");
28
+
29
+ // Create a minimal mock Theme for the harness header's logo rendering.
30
+ // This satisfies Theme.fg() without needing a real terminal.
31
+ // The global theme from initTheme() is used by keyText/keyHint internally.
32
+ testTheme = {
33
+ fg(color, text) {
34
+ // Return text with ANSI codes matching dark theme colors
35
+ const codes: Record<string, string> = {
36
+ accent: "\x1b[36m", // cyan for dark theme accent
37
+ success: "\x1b[32m", // green
38
+ muted: "\x1b[90m", // bright black (gray)
39
+ dim: "\x1b[90m",
40
+ text: "\x1b[37m", // white
41
+ };
42
+ return `${codes[color] ?? ""}${text}\x1b[39m`;
43
+ },
44
+ bg() { return ""; },
45
+ style() { return ""; },
46
+ bold() { return ""; },
47
+ italic() { return ""; },
48
+ underline() { return ""; },
49
+ inverse() { return ""; },
50
+ strikethrough() { return ""; },
51
+ getFgAnsi() { return ""; },
52
+ getBgAnsi() { return ""; },
53
+ getColorMode() { return "truecolor"; },
54
+ getThinkingBorderColor() { return () => ""; },
55
+ getBashModeBorderColor() { return () => ""; },
56
+ get appearance() { return "dark"; },
57
+ get colors() { return {}; },
58
+ } as unknown as Theme;
59
+ });
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Test data
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const TEST_OPTIONS: HarnessHeaderOptions = {
66
+ version: "1.0.0",
67
+ productName: "Harness",
68
+ onboardingText: "Test onboarding.",
69
+ };
70
+
71
+ function createMockTui() {
72
+ return {} as object;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Tests
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe("visibleWidth", () => {
80
+ it("returns 0 for empty string", () => {
81
+ expect(visibleWidth("")).toBe(0);
82
+ });
83
+
84
+ it("returns correct length for plain text", () => {
85
+ expect(visibleWidth("hello")).toBe(5);
86
+ });
87
+
88
+ it("subtracts ANSI escape sequences from length", () => {
89
+ const colored = "\x1b[31mhello\x1b[39m";
90
+ expect(visibleWidth(colored)).toBe(5);
91
+ });
92
+
93
+ it("handles multiple escape sequences", () => {
94
+ const colored = "\x1b[1m\x1b[31mbold red\x1b[39m\x1b[22m";
95
+ // "bold red" = 9 chars visible, minus 1 = 8 (the space in "bold red")
96
+ expect(visibleWidth(colored)).toBe(8);
97
+ });
98
+ });
99
+
100
+ describe("stripAnsi", () => {
101
+ it("returns empty string for empty input", () => {
102
+ expect(stripAnsi("")).toBe("");
103
+ });
104
+
105
+ it("returns original string if no escapes", () => {
106
+ expect(stripAnsi("hello world")).toBe("hello world");
107
+ });
108
+
109
+ it("strips all ANSI escapes", () => {
110
+ const colored = "\x1b[1m\x1b[31mbold red\x1b[39m\x1b[22m";
111
+ expect(stripAnsi(colored)).toBe("bold red");
112
+ });
113
+ });
114
+
115
+ describe("logo segment ANSI resets", () => {
116
+ it("all ANSI sequences in output are complete (balanced opens/closes)", () => {
117
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
118
+ const lines = header.render(80);
119
+
120
+ for (const line of lines) {
121
+ const csiStarts = (line.match(/\x1b\[/g) || []).length;
122
+ const completeSeqs = (line.match(/\x1b\[[0-9;]*[a-zA-Z]/g) || []).length;
123
+ expect(completeSeqs).toBe(csiStarts);
124
+ }
125
+ });
126
+ });
127
+
128
+ describe("logo visible width", () => {
129
+ it("logo occupies exactly 4 visible cells per row", () => {
130
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
131
+ const lines = header.render(80);
132
+
133
+ const logoLine = lines.find((l) => stripAnsi(l).includes("▀") || stripAnsi(l).includes("█"));
134
+ expect(logoLine).toBeDefined();
135
+
136
+ const plain = stripAnsi(logoLine!);
137
+ const logoMatch = plain.match(/^([▀▄█]+)/);
138
+ expect(logoMatch).not.toBeNull();
139
+ expect(logoMatch[1].length).toBe(4);
140
+ });
141
+
142
+ it("logo renders on exactly 2 rows", () => {
143
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
144
+ const lines = header.render(80);
145
+
146
+ const logoLines = lines.filter((l) => stripAnsi(l).includes("▀") || stripAnsi(l).includes("█"));
147
+ expect(logoLines.length).toBe(2);
148
+ });
149
+ });
150
+
151
+ describe("setExpanded", () => {
152
+ it("starts in compact mode", () => {
153
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
154
+ const compactLines = header.render(80);
155
+
156
+ const compactText = stripAnsi(compactLines.join("\n"));
157
+ // keyHint returns key name + description; check for the interrupt key (escape)
158
+ expect(compactText.toLowerCase()).toContain("interrupt");
159
+ });
160
+
161
+ it("switches to expanded mode when setExpanded(true)", () => {
162
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
163
+
164
+ header.setExpanded(true);
165
+ const expandedLines = header.render(80);
166
+ const expandedText = stripAnsi(expandedLines.join("\n"));
167
+
168
+ expect(expandedText.toLowerCase()).toContain("abort");
169
+ expect(expandedText.toLowerCase()).toContain("clear");
170
+ expect(expandedText.toLowerCase()).toContain("slash");
171
+ });
172
+
173
+ it("switches back to compact when setExpanded(false)", () => {
174
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
175
+
176
+ header.setExpanded(true);
177
+ header.setExpanded(false);
178
+ const collapsedLines = header.render(80);
179
+
180
+ const hasDotSeparator = collapsedLines.some((l) => stripAnsi(l).includes(" · "));
181
+ expect(hasDotSeparator).toBe(true);
182
+ });
183
+
184
+ it("idempotent when setting same state twice", () => {
185
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
186
+
187
+ header.setExpanded(true);
188
+ const lines1 = header.render(80);
189
+ header.setExpanded(true);
190
+ const lines2 = header.render(80);
191
+
192
+ expect(lines1).toEqual(lines2);
193
+ });
194
+ });
195
+
196
+ describe("narrow width rendering", () => {
197
+ it("does not split ANSI escape sequences", () => {
198
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
199
+
200
+ const widths = [20, 40, 60, 80, 120];
201
+
202
+ for (const width of widths) {
203
+ const lines = header.render(width);
204
+ for (const line of lines) {
205
+ const incompletePattern = /\x1b\[[0-9;]*[^\x1b\x5bm]?$/;
206
+ expect(incompletePattern.test(line)).toBe(false);
207
+ }
208
+ }
209
+ });
210
+
211
+ it("renders without error at width 1", () => {
212
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
213
+
214
+ expect(() => header.render(1)).not.toThrow();
215
+ });
216
+ });
217
+
218
+ describe("no emoji", () => {
219
+ it("output contains no emoji code points", () => {
220
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
221
+ const lines = header.render(80);
222
+
223
+ const allText = lines.join("\n");
224
+
225
+ const emojiRanges = [
226
+ /[\u{1F300}-\u{1F9FF}]/u,
227
+ /[\u{2600}-\u{26FF}]/u,
228
+ /[\u{2700}-\u{27BF}]/u,
229
+ /[\u{1F600}-\u{1F64F}]/u,
230
+ /[\u{1F680}-\u{1F6FF}]/u,
231
+ ];
232
+
233
+ for (const range of emojiRanges) {
234
+ const matches = allText.match(range);
235
+ expect(matches).toBeNull();
236
+ }
237
+ });
238
+
239
+ it("uses only allowed Unicode block characters", () => {
240
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
241
+ const lines = header.render(80);
242
+
243
+ for (const line of lines) {
244
+ const plain = stripAnsi(line);
245
+
246
+ for (const char of plain) {
247
+ const code = char.charCodeAt(0);
248
+
249
+ if (code >= 0x20 && code <= 0x7e) continue;
250
+ if (code === 0x20) continue;
251
+ if (char === "▀" || char === "▄" || char === "█") continue;
252
+ if (char === "\n") continue;
253
+ if (char === "·") continue; // middle dot U+00B7 used in shortcuts
254
+
255
+ expect(`Unexpected character: ${char} (U+${code.toString(16).padStart(4, "0")})`).toBe("");
256
+ }
257
+ }
258
+ });
259
+ });
260
+
261
+ describe("version display", () => {
262
+ it("displays correct version from options", () => {
263
+ const customOptions: HarnessHeaderOptions = {
264
+ ...TEST_OPTIONS,
265
+ version: "2.5.10-test",
266
+ };
267
+ const header = createHarnessHeader(createMockTui(), testTheme, customOptions);
268
+ const lines = header.render(80);
269
+
270
+ const text = stripAnsi(lines.join("\n"));
271
+ expect(text).toContain("v2.5.10-test");
272
+ });
273
+
274
+ it("defaults product name to 'Harness'", () => {
275
+ const optionsWithoutName: HarnessHeaderOptions = {
276
+ version: "1.0.0",
277
+ };
278
+ const header = createHarnessHeader(createMockTui(), testTheme, optionsWithoutName);
279
+ const lines = header.render(80);
280
+
281
+ const text = stripAnsi(lines.join("\n"));
282
+ expect(text).toContain("Harness");
283
+ });
284
+ });
285
+
286
+ describe("dynamic key hints", () => {
287
+ it("renders hint text for shortcuts", () => {
288
+ const header = createHarnessHeader(createMockTui(), testTheme, TEST_OPTIONS);
289
+ const lines = header.render(80);
290
+
291
+ const text = stripAnsi(lines.join("\n"));
292
+
293
+ expect(text.toLowerCase()).toContain("interrupt");
294
+ expect(text.toLowerCase()).toContain("command");
295
+ });
296
+ });
297
+
298
+ describe("onboarding text", () => {
299
+ it("displays custom onboarding text", () => {
300
+ const customOptions: HarnessHeaderOptions = {
301
+ ...TEST_OPTIONS,
302
+ onboardingText: "Custom onboarding message.",
303
+ };
304
+ const header = createHarnessHeader(createMockTui(), testTheme, customOptions);
305
+ const lines = header.render(80);
306
+
307
+ const text = stripAnsi(lines.join("\n"));
308
+ expect(text).toContain("Custom onboarding message.");
309
+ });
310
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/types",
3
- "version": "1.1.134",
3
+ "version": "1.1.136",
4
4
  "description": "Shared TypeScript types for pi-harness-runtime packages",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workflow-events",
3
- "version": "1.1.134",
3
+ "version": "1.1.136",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workspace-scanner",
3
- "version": "1.1.134",
3
+ "version": "1.1.136",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/worktree",
3
- "version": "1.1.134",
3
+ "version": "1.1.136",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/write-review",
3
- "version": "1.1.134",
3
+ "version": "1.1.136",
4
4
  "description": "Two-agent write-review loop with blackboard coordination",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",