@gotgenes/pi-subagents 6.16.3 → 6.17.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/CHANGELOG.md +15 -0
- package/docs/architecture/architecture.md +13 -13
- package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +166 -0
- package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +50 -0
- package/package.json +1 -1
- package/src/ui/agent-menu.ts +2 -0
- package/src/ui/conversation-viewer.ts +27 -15
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [6.17.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.16.3...pi-subagents-v6.17.0) (2026-05-23)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* inject wrapText into ConversationViewer (Phase 9, Step O) ([#147](https://github.com/gotgenes/pi-packages/issues/147)) ([2522d5b](https://github.com/gotgenes/pi-packages/commit/2522d5b82f2a29b9011f31d49f82b5f914f12f99))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* mark Step O complete in architecture doc ([#147](https://github.com/gotgenes/pi-packages/issues/147)) ([c4a6ee5](https://github.com/gotgenes/pi-packages/commit/c4a6ee5217692fc67b9a9e5b6c8586376e9e7e02))
|
|
19
|
+
* plan inject wrapText into ConversationViewer ([#147](https://github.com/gotgenes/pi-packages/issues/147)) ([fe4dceb](https://github.com/gotgenes/pi-packages/commit/fe4dcebda894b0d641820e55d41e48e4a0c67c3d))
|
|
20
|
+
* **retro:** add planning stage notes for issue [#147](https://github.com/gotgenes/pi-packages/issues/147) ([dce5db2](https://github.com/gotgenes/pi-packages/commit/dce5db2fc2ce2fd530bbfacd61ec40e771005768))
|
|
21
|
+
* **retro:** add TDD stage notes for issue [#147](https://github.com/gotgenes/pi-packages/issues/147) ([2db1238](https://github.com/gotgenes/pi-packages/commit/2db123874d1730ac8811d44731a8f7cb052ee043))
|
|
22
|
+
|
|
8
23
|
## [6.16.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.16.2...pi-subagents-v6.16.3) (2026-05-23)
|
|
9
24
|
|
|
10
25
|
|
|
@@ -616,13 +616,13 @@ Phase 9 targets the next layer: observation model consolidation, `ExtensionConte
|
|
|
616
616
|
|
|
617
617
|
### Current smells
|
|
618
618
|
|
|
619
|
-
| Smell
|
|
620
|
-
|
|
|
621
|
-
| `execute` does config resolution for its callees
|
|
622
|
-
| ~~Wide `ctx` in menu handlers~~
|
|
623
|
-
| Direct SDK import in `conversation-viewer.ts
|
|
624
|
-
| ~~Widget mixes rendering, lifecycle, and state~~
|
|
625
|
-
| `deps.` prefix noise in function bodies
|
|
619
|
+
| Smell | Location | Evidence | Severity |
|
|
620
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------- |
|
|
621
|
+
| `execute` does config resolution for its callees | `agent-tool.ts` (145-line `execute`) | ~60 lines unpack config, resolve model, compute metadata, repack into 16-field bags for spawners; `ctx` threaded 4 layers deep | Medium |
|
|
622
|
+
| ~~Wide `ctx` in menu handlers~~ | ~~`agent-menu.ts`, `agent-config-editor.ts`, `agent-creation-wizard.ts`~~ | Resolved by #146: `MenuUI` interface introduced; 42 `ctx as any` casts eliminated across 5 test files | Done |
|
|
623
|
+
| ~~Direct SDK import in `conversation-viewer.ts`~~ | ~~`conversation-viewer.test.ts`~~ | Resolved by #147: `wrapText` injected via `ConversationViewerOptions`; `vi.mock("@earendil-works/pi-tui")` eliminated | Done |
|
|
624
|
+
| ~~Widget mixes rendering, lifecycle, and state~~ | ~~`agent-widget.ts` (370 lines)~~ | Resolved by #148: rendering extracted to `widget-renderer.ts`; widget is now 198 lines | Done |
|
|
625
|
+
| `deps.` prefix noise in function bodies | remaining modules across tools, UI, service-adapter | Functions accept a `deps` bag and access every field as `deps.foo`; hides real dependencies and lengthens every call line | Low |
|
|
626
626
|
|
|
627
627
|
### Dependency bag convention
|
|
628
628
|
|
|
@@ -683,15 +683,15 @@ After Steps M and N, `ExtensionContext` appears only at true boundaries: `index.
|
|
|
683
683
|
|
|
684
684
|
Impact: eliminated 42 `ctx as any` casts across 5 test files (`agent-menu.test.ts`: 8, `agent-config-editor.test.ts`: 20, `agent-creation-wizard.test.ts`: 14); tests construct plain `MenuUI`-shaped objects with no cast.
|
|
685
685
|
|
|
686
|
-
### Step O: Inject text wrapping into ConversationViewer (#147)
|
|
686
|
+
### Step O: Inject text wrapping into ConversationViewer (#147) ✓
|
|
687
687
|
|
|
688
|
-
|
|
689
|
-
`
|
|
690
|
-
Tests inject a stub or the real function directly
|
|
688
|
+
Accepted `wrapText: (text: string, width: number) => string[]` via `ConversationViewerOptions`.
|
|
689
|
+
`agent-menu.ts` passes the real `wrapTextWithAnsi` import at the `ConversationViewer` construction site.
|
|
690
|
+
Tests inject a stub or the real function directly via options — no module-level mock needed.
|
|
691
691
|
|
|
692
|
-
|
|
692
|
+
Applied the dependency bag convention: `ConversationViewerOptions` is now destructured in the constructor signature.
|
|
693
693
|
|
|
694
|
-
Impact:
|
|
694
|
+
Impact: eliminated the hoisted `vi.mock("@earendil-works/pi-tui")` from `conversation-viewer.test.ts`; 1 test deleted (mock-mechanism sentinel); net −1 test.
|
|
695
695
|
|
|
696
696
|
### Step P: Split AgentWidget rendering (#148) ✓
|
|
697
697
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 147
|
|
3
|
+
issue_title: "Inject text wrapping into ConversationViewer (Phase 9, Step O)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inject text wrapping into ConversationViewer
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
`ConversationViewer` calls `wrapTextWithAnsi` directly from `@earendil-works/pi-tui` in four places inside `buildContentLines`.
|
|
11
|
+
Because the function is a module-level binding, tests must intercept it via a hoisted `vi.mock("@earendil-works/pi-tui")` factory that replaces the entire TUI module.
|
|
12
|
+
This is the last `vi.mock` on an SDK module in the test suite, added specifically to exercise the overwidth-clamping safety net.
|
|
13
|
+
|
|
14
|
+
## Goals
|
|
15
|
+
|
|
16
|
+
- Accept `wrapText: (text: string, width: number) => string[]` via `ConversationViewerOptions`.
|
|
17
|
+
- Destructure `ConversationViewerOptions` in the constructor signature (dependency bag convention).
|
|
18
|
+
- Replace all four `wrapTextWithAnsi` calls in `buildContentLines` with `this.wrapText`.
|
|
19
|
+
- Remove `wrapTextWithAnsi` from `conversation-viewer.ts`'s `@earendil-works/pi-tui` import.
|
|
20
|
+
- Pass `wrapTextWithAnsi` at the production call site in `agent-menu.ts`.
|
|
21
|
+
- Eliminate the hoisted `vi.mock("@earendil-works/pi-tui")` from `conversation-viewer.test.ts`.
|
|
22
|
+
|
|
23
|
+
## Non-Goals
|
|
24
|
+
|
|
25
|
+
- Injecting `truncateToWidth` or any other TUI function (only `wrapTextWithAnsi` is relevant here).
|
|
26
|
+
- Changing the overwidth-clamping behavior of `buildContentLines`.
|
|
27
|
+
- Touching `agent-widget.ts` (tracked separately as Issue #148, Step P — already closed).
|
|
28
|
+
|
|
29
|
+
## Background
|
|
30
|
+
|
|
31
|
+
`ui/conversation-viewer.ts` is the live conversation overlay rendered when a user selects an agent in the `/agents` menu.
|
|
32
|
+
Its `buildContentLines` method formats messages from the agent session into displayable lines, calling `wrapTextWithAnsi` to soft-wrap text to the available terminal width.
|
|
33
|
+
|
|
34
|
+
The overwidth-clamping safety net (`truncateToWidth` applied after `wrapTextWithAnsi`) exists because a prior upstream bug returned lines wider than the requested width.
|
|
35
|
+
The `vi.mock` in the test is the mechanism for simulating that bug by returning overwidth strings from `wrapTextWithAnsi`.
|
|
36
|
+
|
|
37
|
+
The only production call site for `new ConversationViewer(...)` is in the `viewAgentConversation` closure inside `createAgentsMenuHandler` in `ui/agent-menu.ts`.
|
|
38
|
+
|
|
39
|
+
Architecture reference: `docs/architecture/architecture.md` § Phase 9, Step O.
|
|
40
|
+
|
|
41
|
+
## Design Overview
|
|
42
|
+
|
|
43
|
+
### `ConversationViewerOptions` — add `wrapText`
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
export interface ConversationViewerOptions {
|
|
47
|
+
tui: TUI;
|
|
48
|
+
session: AgentSession;
|
|
49
|
+
record: AgentRecord;
|
|
50
|
+
activity: AgentActivityTracker | undefined;
|
|
51
|
+
theme: Theme;
|
|
52
|
+
done: (result: undefined) => void;
|
|
53
|
+
registry: AgentConfigLookup;
|
|
54
|
+
wrapText: (text: string, width: number) => string[];
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The field is **required** — it must be supplied at every construction site.
|
|
59
|
+
No default is provided; the default would be an invisible SDK dependency hidden inside the class.
|
|
60
|
+
|
|
61
|
+
### Constructor destructuring
|
|
62
|
+
|
|
63
|
+
The constructor adopts the dependency bag convention — destructure the options object rather than accessing via `options.*`:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
constructor({
|
|
67
|
+
tui, session, record, activity, theme, done, registry, wrapText,
|
|
68
|
+
}: ConversationViewerOptions) {
|
|
69
|
+
this.tui = tui;
|
|
70
|
+
this.session = session;
|
|
71
|
+
// … etc.
|
|
72
|
+
this.wrapText = wrapText;
|
|
73
|
+
this.unsubscribe = session.subscribe(() => { … });
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Production wiring — `agent-menu.ts`
|
|
78
|
+
|
|
79
|
+
`agent-menu.ts` statically imports `wrapTextWithAnsi` from `@earendil-works/pi-tui` and passes it when constructing `ConversationViewer`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
83
|
+
// …
|
|
84
|
+
return new ConversationViewer({
|
|
85
|
+
tui, session, record, activity, theme, done, registry,
|
|
86
|
+
wrapText: wrapTextWithAnsi,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Adding `wrapText` to `AgentMenuDeps` and threading it through the closure would violate the Law of Demeter — `AgentMenuDeps` has no conceptual ownership of a text-wrapping function.
|
|
91
|
+
The `viewAgentConversation` closure is the direct consumer; the import belongs there.
|
|
92
|
+
|
|
93
|
+
### Test strategy
|
|
94
|
+
|
|
95
|
+
After DI, tests pass `wrapText` directly in `ConversationViewerOptions`:
|
|
96
|
+
|
|
97
|
+
- **"render width safety" tests**: pass `wrapText: wrapTextWithAnsi` (real function, statically imported — no mock).
|
|
98
|
+
- **"safety net" tests**: pass a stub `wrapText: () => ["X".repeat(width + 50)]` inline in options — no `vi.mock` needed.
|
|
99
|
+
- The "mock is intercepting wrapTextWithAnsi" test is removed (it verified the mock mechanism, not viewer behavior).
|
|
100
|
+
- The module-level `wrapOverride` variable and the `vi.mock` block are removed entirely.
|
|
101
|
+
- The `await import("@earendil-works/pi-tui")` and `await import("../src/ui/conversation-viewer.js")` dynamic imports are converted to ordinary top-level `import` statements.
|
|
102
|
+
|
|
103
|
+
## Module-Level Changes
|
|
104
|
+
|
|
105
|
+
| File | Change |
|
|
106
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
107
|
+
| `src/ui/conversation-viewer.ts` | Add `wrapText` field to `ConversationViewerOptions`; add `private wrapText` field; destructure options in constructor; replace 4× `wrapTextWithAnsi(...)` with `this.wrapText(...)`; remove `wrapTextWithAnsi` from the `@earendil-works/pi-tui` import line |
|
|
108
|
+
| `src/ui/agent-menu.ts` | Add `import { wrapTextWithAnsi } from "@earendil-works/pi-tui"`; pass `wrapText: wrapTextWithAnsi` in `viewAgentConversation` |
|
|
109
|
+
| `test/conversation-viewer.test.ts` | Convert top-level dynamic `await import()` to static imports; remove `vi.mock`, `wrapOverride`, and "mock is intercepting" test; add `wrapText` to every `new ConversationViewer({…})` call; update safety-net tests to pass inline stub `wrapText` |
|
|
110
|
+
|
|
111
|
+
No symbols are removed from exported API (`ConversationViewerOptions`, `ConversationViewer`, `VIEWPORT_HEIGHT_PCT` all remain).
|
|
112
|
+
The `wrapText` addition to `ConversationViewerOptions` is a breaking change for any external consumers that construct `ConversationViewer` — check for usages outside this package.
|
|
113
|
+
|
|
114
|
+
Grep check: `ConversationViewer` appears only in `src/ui/conversation-viewer.ts`, `src/ui/agent-menu.ts`, and `test/conversation-viewer.test.ts` — no other files construct it.
|
|
115
|
+
|
|
116
|
+
## Test Impact Analysis
|
|
117
|
+
|
|
118
|
+
1. **New unit-test capability**: The safety-net tests can now inject exactly the stub they need without patching a module.
|
|
119
|
+
Each test is self-contained and immediately legible — the stub is declared inline at the call site.
|
|
120
|
+
|
|
121
|
+
2. **Tests that become redundant**: The "mock is intercepting wrapTextWithAnsi" test verified the test mechanism, not production behavior.
|
|
122
|
+
It is deleted.
|
|
123
|
+
The `wrapOverride` reset in `beforeEach` is no longer needed.
|
|
124
|
+
|
|
125
|
+
3. **Tests that stay**: All "render width safety" tests and all overwidth-clamping tests remain; they exercise real viewer behavior with real or controlled inputs.
|
|
126
|
+
|
|
127
|
+
## TDD Order
|
|
128
|
+
|
|
129
|
+
### Cycle 1 — Add `wrapText` field and update production wiring
|
|
130
|
+
|
|
131
|
+
1. **Red**: Update one `new ConversationViewer({…})` in the test to include `wrapText: vi.fn()` — TypeScript rejects the unknown field.
|
|
132
|
+
2. **Green**:
|
|
133
|
+
- Add `wrapText: (text: string, width: number) => string[]` to `ConversationViewerOptions`.
|
|
134
|
+
- Add `private wrapText: (text: string, width: number) => string[]` field to the class.
|
|
135
|
+
- Destructure options in the constructor; assign `this.wrapText = wrapText`.
|
|
136
|
+
- Replace all four `wrapTextWithAnsi(...)` calls with `this.wrapText(...)` in `buildContentLines`.
|
|
137
|
+
- Remove `wrapTextWithAnsi` from the `@earendil-works/pi-tui` import in `conversation-viewer.ts`.
|
|
138
|
+
- In `agent-menu.ts`: add static import of `wrapTextWithAnsi` from `@earendil-works/pi-tui`; pass `wrapText: wrapTextWithAnsi`.
|
|
139
|
+
- Update **all** `new ConversationViewer({…})` calls in `conversation-viewer.test.ts` to include `wrapText`.
|
|
140
|
+
"Render width safety" tests pass `wrapText: wrapTextWithAnsi` (real function, still imported via the existing `vi.mock` shim for now).
|
|
141
|
+
Safety-net tests pass a stub inline: `wrapText: () => ["X".repeat(w + 50)]`.
|
|
142
|
+
- Delete the "mock is intercepting wrapTextWithAnsi" test.
|
|
143
|
+
3. **Verify**: `pnpm vitest run test/conversation-viewer.test.ts` passes.
|
|
144
|
+
4. **Commit**: `feat: inject wrapText into ConversationViewer (Phase 9, Step O) (#147)`
|
|
145
|
+
|
|
146
|
+
### Cycle 2 — Remove the module mock
|
|
147
|
+
|
|
148
|
+
1. **Red**: Remove the `vi.mock("@earendil-works/pi-tui", …)` block, the `wrapOverride` variable, and the `beforeEach(() => { wrapOverride = null; })` reset.
|
|
149
|
+
Convert `const { visibleWidth } = await import("@earendil-works/pi-tui")` to `import { visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui"`.
|
|
150
|
+
Convert `const { ConversationViewer } = await import("../src/ui/conversation-viewer.js")` to `import { ConversationViewer } from "../src/ui/conversation-viewer.js"`.
|
|
151
|
+
Run tests — they should still pass (the mock was no longer needed after Cycle 1).
|
|
152
|
+
2. **Green**: Tests pass without any module mock.
|
|
153
|
+
3. **Verify**: `pnpm vitest run test/conversation-viewer.test.ts` and `pnpm run check` both pass.
|
|
154
|
+
4. **Commit**: `refactor: remove vi.mock from conversation-viewer tests (#147)`
|
|
155
|
+
|
|
156
|
+
## Risks and Mitigations
|
|
157
|
+
|
|
158
|
+
| Risk | Mitigation |
|
|
159
|
+
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
160
|
+
| Missing a `new ConversationViewer({…})` call site in tests | Grep confirms only `test/conversation-viewer.test.ts` constructs the viewer; all instances are in-file |
|
|
161
|
+
| TypeScript missing the `private wrapText` field type | Use `pnpm run check` after Cycle 1 to verify no type errors |
|
|
162
|
+
| The dynamic `await import()` pattern in tests was necessary for some other reason | The only purpose was to run after `vi.mock` hoisting; once the mock is gone, static imports work fine |
|
|
163
|
+
|
|
164
|
+
## Open Questions
|
|
165
|
+
|
|
166
|
+
None — the issue's "Changes" section is unambiguous and the call-site inventory is small.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 147
|
|
3
|
+
issue_title: "Inject text wrapping into ConversationViewer (Phase 9, Step O)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #147 — Inject text wrapping into ConversationViewer (Phase 9, Step O)
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-23T00:00:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Read the issue, loaded package-pi-subagents, code-design, testing, and markdown-conventions skills.
|
|
13
|
+
Explored `src/ui/conversation-viewer.ts`, `src/ui/agent-menu.ts`, `test/conversation-viewer.test.ts`, and the Phase 9 architecture roadmap.
|
|
14
|
+
Wrote and committed the plan at `packages/pi-subagents/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md`.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- The change is tightly scoped: two source files (`conversation-viewer.ts`, `agent-menu.ts`) and one test file.
|
|
19
|
+
- `wrapTextWithAnsi` is called in exactly four places inside `buildContentLines` — all in the same private method, making the replacement straightforward.
|
|
20
|
+
- The only production call site for `new ConversationViewer({…})` is `viewAgentConversation` in `agent-menu.ts`.
|
|
21
|
+
`wrapTextWithAnsi` is added as a static import there and passed as `wrapText` — no threading through `AgentMenuDeps` needed.
|
|
22
|
+
- All `new ConversationViewer({…})` calls in the test file are inline (no shared factory helper), so every call site needs the new `wrapText` field added.
|
|
23
|
+
Grep confirms the count: 11+ calls, all in `test/conversation-viewer.test.ts`.
|
|
24
|
+
- The plan uses 2 TDD cycles: Cycle 1 adds the field and updates all call sites (with the `vi.mock` still present for safety); Cycle 2 removes the mock and converts dynamic `await import()` to static imports.
|
|
25
|
+
This ordering avoids a large simultaneous change and gives the suite a stable intermediate state.
|
|
26
|
+
- The "mock is intercepting wrapTextWithAnsi" test is deleted in Cycle 1 (it verified the mock mechanism, not production behavior).
|
|
27
|
+
- No exported API symbols are removed; `wrapText` is a new required field on `ConversationViewerOptions`, which is a breaking change only for external constructors of `ConversationViewer` — confirmed none exist outside this package.
|
|
28
|
+
|
|
29
|
+
## Stage: Implementation — TDD (2026-05-23T11:36:00Z)
|
|
30
|
+
|
|
31
|
+
### Session summary
|
|
32
|
+
|
|
33
|
+
Completed both TDD cycles from the plan.
|
|
34
|
+
Cycle 1 added `wrapText` to `ConversationViewerOptions`, destructured options in the constructor, replaced all four `wrapTextWithAnsi` calls with `this.wrapText`, updated `agent-menu.ts` to import and pass `wrapTextWithAnsi`, and updated all 16 test constructor call sites (11 render-width-safety + 5 safety-net) while keeping the `vi.mock` shim in place.
|
|
35
|
+
Cycle 2 removed the `vi.mock` block, `wrapOverride`, and `beforeEach` reset, then converted the dynamic `await import()` calls to ordinary static imports.
|
|
36
|
+
Test count: 806 → 805 (deleted the mock-mechanism sentinel test).
|
|
37
|
+
Full suite 50 files, 805 tests, all green.
|
|
38
|
+
|
|
39
|
+
### Observations
|
|
40
|
+
|
|
41
|
+
- The plan said the render-width-safety constructor calls numbered "11+" — the actual count was 16 total (11 render-width-safety + 5 safety-net), all in `test/conversation-viewer.test.ts`.
|
|
42
|
+
No external call sites existed.
|
|
43
|
+
- `wrapTextWithAnsi` needed to be added to the dynamic import in Cycle 1 (`const { visibleWidth, wrapTextWithAnsi } = await import(...)`) because the render-width-safety tests reference it by name.
|
|
44
|
+
The plan didn't call this out explicitly — minor omission.
|
|
45
|
+
- Used a Python script (inline via bash) to make the 17 constructor-call edits rather than 17 separate Edit-tool calls.
|
|
46
|
+
The safety-net tests each had a different stub character (`X`, `Y`, `Z`, `B`, `W`) which required a regex capture group to preserve.
|
|
47
|
+
The script worked on the first attempt.
|
|
48
|
+
- Cycle 2 was a single Edit call replacing the entire mock block + the two dynamic imports + `beforeEach`.
|
|
49
|
+
The autoformatter then cleaned up import ordering automatically.
|
|
50
|
+
- Architecture doc updated: smells table row struck-through and Step O marked ✓.
|
package/package.json
CHANGED
package/src/ui/agent-menu.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
1
2
|
import { AgentTypeRegistry } from "../agent-types.js";
|
|
2
3
|
import type { ModelRegistry } from "../model-resolver.js";
|
|
3
4
|
import type { ParentSnapshot } from "../parent-snapshot.js";
|
|
@@ -260,6 +261,7 @@ export function createAgentsMenuHandler({
|
|
|
260
261
|
theme,
|
|
261
262
|
done,
|
|
262
263
|
registry,
|
|
264
|
+
wrapText: wrapTextWithAnsi,
|
|
263
265
|
});
|
|
264
266
|
},
|
|
265
267
|
{
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth
|
|
9
|
+
import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
10
10
|
import type { AgentConfigLookup } from "../agent-types.js";
|
|
11
11
|
import { extractText } from "../context.js";
|
|
12
12
|
import type { AgentRecord } from "../types.js";
|
|
@@ -59,6 +59,7 @@ export interface ConversationViewerOptions {
|
|
|
59
59
|
theme: Theme;
|
|
60
60
|
done: (result: undefined) => void;
|
|
61
61
|
registry: AgentConfigLookup;
|
|
62
|
+
wrapText: (text: string, width: number) => string[];
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
export class ConversationViewer implements Component {
|
|
@@ -75,16 +76,27 @@ export class ConversationViewer implements Component {
|
|
|
75
76
|
private theme: Theme;
|
|
76
77
|
private done: (result: undefined) => void;
|
|
77
78
|
private registry: AgentConfigLookup;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
private wrapText: (text: string, width: number) => string[];
|
|
80
|
+
|
|
81
|
+
constructor({
|
|
82
|
+
tui,
|
|
83
|
+
session,
|
|
84
|
+
record,
|
|
85
|
+
activity,
|
|
86
|
+
theme,
|
|
87
|
+
done,
|
|
88
|
+
registry,
|
|
89
|
+
wrapText,
|
|
90
|
+
}: ConversationViewerOptions) {
|
|
91
|
+
this.tui = tui;
|
|
92
|
+
this.session = session;
|
|
93
|
+
this.record = record;
|
|
94
|
+
this.activity = activity;
|
|
95
|
+
this.theme = theme;
|
|
96
|
+
this.done = done;
|
|
97
|
+
this.registry = registry;
|
|
98
|
+
this.wrapText = wrapText;
|
|
99
|
+
this.unsubscribe = session.subscribe(() => {
|
|
88
100
|
if (this.closed) return;
|
|
89
101
|
this.tui.requestRender();
|
|
90
102
|
});
|
|
@@ -253,7 +265,7 @@ export class ConversationViewer implements Component {
|
|
|
253
265
|
if (!text.trim()) continue;
|
|
254
266
|
if (needsSeparator) lines.push(th.fg("dim", "───"));
|
|
255
267
|
lines.push(th.fg("accent", "[User]"));
|
|
256
|
-
for (const line of
|
|
268
|
+
for (const line of this.wrapText(text.trim(), width)) {
|
|
257
269
|
lines.push(line);
|
|
258
270
|
}
|
|
259
271
|
} else if (msg.role === "assistant") {
|
|
@@ -268,7 +280,7 @@ export class ConversationViewer implements Component {
|
|
|
268
280
|
if (needsSeparator) lines.push(th.fg("dim", "───"));
|
|
269
281
|
lines.push(th.bold("[Assistant]"));
|
|
270
282
|
if (textParts.length > 0) {
|
|
271
|
-
for (const line of
|
|
283
|
+
for (const line of this.wrapText(textParts.join("\n").trim(), width)) {
|
|
272
284
|
lines.push(line);
|
|
273
285
|
}
|
|
274
286
|
}
|
|
@@ -281,7 +293,7 @@ export class ConversationViewer implements Component {
|
|
|
281
293
|
if (!truncated.trim()) continue;
|
|
282
294
|
if (needsSeparator) lines.push(th.fg("dim", "───"));
|
|
283
295
|
lines.push(th.fg("dim", "[Result]"));
|
|
284
|
-
for (const line of
|
|
296
|
+
for (const line of this.wrapText(truncated.trim(), width)) {
|
|
285
297
|
lines.push(th.fg("dim", line));
|
|
286
298
|
}
|
|
287
299
|
} else if (isBashExecution(msg)) {
|
|
@@ -291,7 +303,7 @@ export class ConversationViewer implements Component {
|
|
|
291
303
|
const out = msg.output.length > 500
|
|
292
304
|
? msg.output.slice(0, 500) + "... (truncated)"
|
|
293
305
|
: msg.output;
|
|
294
|
-
for (const line of
|
|
306
|
+
for (const line of this.wrapText(out.trim(), width)) {
|
|
295
307
|
lines.push(th.fg("dim", line));
|
|
296
308
|
}
|
|
297
309
|
}
|