pi-cc-extensions 0.8.9 → 0.8.11
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/README.md +3 -2
- package/extensions/agent-autocomplete.ts +11 -16
- package/extensions/ask-user-question/ask-user-question.ts +29 -7
- package/extensions/ask-user-question/config.ts +6 -2
- package/extensions/ask-user-question/index.ts +8 -8
- package/extensions/ask-user-question/rpc-fallback.ts +16 -4
- package/extensions/ask-user-question/state/build-questionnaire.ts +16 -5
- package/extensions/ask-user-question/state/key-router.ts +29 -7
- package/extensions/ask-user-question/state/questionnaire-session.ts +1 -4
- package/extensions/ask-user-question/state/selectors/projections.ts +4 -1
- package/extensions/ask-user-question/state/state-reducer.ts +24 -7
- package/extensions/ask-user-question/tool/response-envelope.ts +4 -1
- package/extensions/ask-user-question/tool/types.ts +5 -1
- package/extensions/ask-user-question/tool/validate-questionnaire.ts +10 -2
- package/extensions/ask-user-question/view/components/inline-input.ts +15 -2
- package/extensions/ask-user-question/view/components/multi-select-view.ts +11 -4
- package/extensions/ask-user-question/view/components/option-list-view.ts +14 -5
- package/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +13 -7
- package/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +16 -4
- package/extensions/ask-user-question/view/components/preview/preview-pane.ts +5 -1
- package/extensions/ask-user-question/view/components/submit-picker.ts +3 -1
- package/extensions/ask-user-question/view/components/wrapping-select.ts +20 -4
- package/extensions/ask-user-question/view/dialog-builder.ts +27 -7
- package/extensions/ask-user-question/view/tab-content-strategy.ts +23 -8
- package/extensions/claude-code-style.ts +1722 -1485
- package/extensions/compact-style.ts +120 -43
- package/extensions/context.ts +502 -394
- package/extensions/session-reference-core.ts +8 -3
- package/extensions/session-reference.ts +475 -448
- package/extensions/tool-diff/ATTRIBUTION.md +28 -0
- package/extensions/tool-diff/ansi-utils.ts +113 -0
- package/extensions/tool-diff/diff-presentation.ts +70 -0
- package/extensions/tool-diff/diff-renderer.ts +2827 -0
- package/extensions/tool-diff/index.ts +106 -0
- package/extensions/tool-diff/line-width-safety.ts +91 -0
- package/extensions/tool-diff/render-utils.ts +5 -0
- package/extensions/tool-diff/types.ts +20 -0
- package/extensions/tool-diff/write-display-utils.ts +29 -0
- package/extensions/tool-diff/write-execution.ts +113 -0
- package/extensions/tool-result-sanitize.ts +4 -4
- package/package.json +64 -61
- package/themes/github-dark-default.json +91 -91
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { QuestionData } from "../../../tool/types.js";
|
|
3
3
|
import type { WrappingSelectItem } from "../wrapping-select.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BORDER_HORIZONTAL_OVERHEAD,
|
|
6
|
+
BORDER_INNER_PADDING_HORIZONTAL,
|
|
7
|
+
} from "./preview-box-renderer.js";
|
|
5
8
|
|
|
6
9
|
/** Min terminal/pane width for the side-by-side layout to engage. */
|
|
7
10
|
export const PREVIEW_MIN_WIDTH = 100;
|
|
@@ -34,7 +37,9 @@ export type PreviewLayoutMode = "side-by-side" | "stacked";
|
|
|
34
37
|
* capping height too short.
|
|
35
38
|
*/
|
|
36
39
|
export function decideLayout(terminalWidth: number, paneWidth: number): PreviewLayoutMode {
|
|
37
|
-
return terminalWidth >= PREVIEW_MIN_WIDTH && paneWidth >= PREVIEW_MIN_WIDTH
|
|
40
|
+
return terminalWidth >= PREVIEW_MIN_WIDTH && paneWidth >= PREVIEW_MIN_WIDTH
|
|
41
|
+
? "side-by-side"
|
|
42
|
+
: "stacked";
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
@@ -120,12 +125,19 @@ export function previewSourceWidth(question: QuestionData): number {
|
|
|
120
125
|
*
|
|
121
126
|
* Pure function — same cross-tab max pattern as `crossTabMaxLeftWidth`.
|
|
122
127
|
*/
|
|
123
|
-
export function crossTabPreviewBudget(
|
|
128
|
+
export function crossTabPreviewBudget(
|
|
129
|
+
questions: readonly QuestionData[],
|
|
130
|
+
paneWidth: number,
|
|
131
|
+
): number {
|
|
124
132
|
let max = MIN_PREVIEW_WIDTH;
|
|
125
133
|
for (const question of questions) {
|
|
126
134
|
const rawWidth = previewSourceWidth(question);
|
|
127
135
|
const capped = Math.min(rawWidth, paneWidth - PREVIEW_COLUMN_GAP - MIN_LEFT);
|
|
128
|
-
const budget =
|
|
136
|
+
const budget =
|
|
137
|
+
capped +
|
|
138
|
+
BORDER_HORIZONTAL_OVERHEAD +
|
|
139
|
+
2 * BORDER_INNER_PADDING_HORIZONTAL +
|
|
140
|
+
PREVIEW_PADDING_LEFT;
|
|
129
141
|
if (budget > max) max = budget;
|
|
130
142
|
}
|
|
131
143
|
return max;
|
|
@@ -168,7 +168,11 @@ export class PreviewPane implements StatefulView<PreviewPaneProps>, Component {
|
|
|
168
168
|
const adaptiveLeft = this.getAdaptiveLeft(width);
|
|
169
169
|
const { optionsWidth, previewWidth } = bodyWidths(width, mode, adaptiveLeft);
|
|
170
170
|
const optionsHeight = this.optionListView.render(optionsWidth).length;
|
|
171
|
-
const previewBlockHeight = this.previewBlock.blockHeight(
|
|
171
|
+
const previewBlockHeight = this.previewBlock.blockHeight(
|
|
172
|
+
previewWidth,
|
|
173
|
+
this.props.selectedIndex,
|
|
174
|
+
mode,
|
|
175
|
+
);
|
|
172
176
|
if (mode === "side-by-side") return Math.max(optionsHeight, previewBlockHeight);
|
|
173
177
|
return optionsHeight + STACKED_GAP_ROWS + previewBlockHeight;
|
|
174
178
|
}
|
|
@@ -56,7 +56,9 @@ export class SubmitPicker implements StatefulView<SubmitPickerProps> {
|
|
|
56
56
|
const active = this.props.rows[i]?.active ?? false;
|
|
57
57
|
const pointer = active ? ACTIVE_POINTER : INACTIVE_POINTER;
|
|
58
58
|
const number = `${i + 1}${NUMBER_SEPARATOR}`;
|
|
59
|
-
const label = active
|
|
59
|
+
const label = active
|
|
60
|
+
? this.theme.fg("accent", this.theme.bold(text))
|
|
61
|
+
: this.theme.fg("text", text);
|
|
60
62
|
lines.push(truncateToWidth(`${pointer}${number}${label}`, width, ""));
|
|
61
63
|
}
|
|
62
64
|
return lines;
|
|
@@ -192,7 +192,10 @@ export class WrappingSelect implements Component {
|
|
|
192
192
|
|
|
193
193
|
private computeVisibleWindow(): { startIndex: number; endIndex: number } {
|
|
194
194
|
const half = Math.floor(this.maxVisible / 2);
|
|
195
|
-
const startIndex = Math.max(
|
|
195
|
+
const startIndex = Math.max(
|
|
196
|
+
0,
|
|
197
|
+
Math.min(this.selectedIndex - half, this.items.length - this.maxVisible),
|
|
198
|
+
);
|
|
196
199
|
const endIndex = Math.min(startIndex + this.maxVisible, this.items.length);
|
|
197
200
|
return { startIndex, endIndex };
|
|
198
201
|
}
|
|
@@ -210,7 +213,10 @@ export class WrappingSelect implements Component {
|
|
|
210
213
|
): string[] {
|
|
211
214
|
const rowPrefix = this.buildRowPrefix(index, isActive, numberWidth);
|
|
212
215
|
const continuationPrefix = " ".repeat(visibleWidth(rowPrefix));
|
|
213
|
-
const contentWidth = Math.max(
|
|
216
|
+
const contentWidth = Math.max(
|
|
217
|
+
WrappingSelect.MIN_CONTENT_WIDTH,
|
|
218
|
+
width - visibleWidth(rowPrefix),
|
|
219
|
+
);
|
|
214
220
|
|
|
215
221
|
if (this.shouldRenderAsInlineInput(item, isActive)) {
|
|
216
222
|
return this.renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth);
|
|
@@ -229,7 +235,13 @@ export class WrappingSelect implements Component {
|
|
|
229
235
|
const applySelectedStyle = isActive || isConfirmed;
|
|
230
236
|
|
|
231
237
|
return [
|
|
232
|
-
...this.renderLabelBlock(
|
|
238
|
+
...this.renderLabelBlock(
|
|
239
|
+
label,
|
|
240
|
+
rowPrefix,
|
|
241
|
+
continuationPrefix,
|
|
242
|
+
contentWidth,
|
|
243
|
+
applySelectedStyle,
|
|
244
|
+
),
|
|
233
245
|
...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth),
|
|
234
246
|
];
|
|
235
247
|
}
|
|
@@ -252,7 +264,11 @@ export class WrappingSelect implements Component {
|
|
|
252
264
|
* building core. The `multiline: true` path is byte-identical to the pre-extraction
|
|
253
265
|
* output (cursor rationale lives in `inline-input.ts`).
|
|
254
266
|
*/
|
|
255
|
-
private renderInlineInputRow(
|
|
267
|
+
private renderInlineInputRow(
|
|
268
|
+
rowPrefix: string,
|
|
269
|
+
continuationPrefix: string,
|
|
270
|
+
contentWidth: number,
|
|
271
|
+
): string[] {
|
|
256
272
|
return renderInlineInputRow({
|
|
257
273
|
buffer: this.inputBuffer,
|
|
258
274
|
cursorOffset: this.inputCursorOffset,
|
|
@@ -6,7 +6,11 @@ import type { PreviewPaneProps } from "./components/preview/preview-pane.js";
|
|
|
6
6
|
import type { TabBar } from "./components/tab-bar.js";
|
|
7
7
|
import type { StatefulView } from "./stateful-view.js";
|
|
8
8
|
import type { TabComponents } from "./tab-components.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
QuestionTabStrategy,
|
|
11
|
+
SubmitTabStrategy,
|
|
12
|
+
type TabContentStrategy,
|
|
13
|
+
} from "./tab-content-strategy.js";
|
|
10
14
|
|
|
11
15
|
export const HINT_PART_ENTER = "Enter to select";
|
|
12
16
|
export const HINT_PART_NAV = "↑/↓ to navigate";
|
|
@@ -27,10 +31,16 @@ export const HINT_PART_EXPAND = "Ctrl+] to expand";
|
|
|
27
31
|
* prefix substring of the rendered line. On narrow terminals the collapse tail
|
|
28
32
|
* clips with `…` (`OneLineClippedText`); the core is preserved.
|
|
29
33
|
*/
|
|
30
|
-
export const HINT_SINGLE = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_CANCEL].join(
|
|
31
|
-
export const HINT_MULTI = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_TAB, HINT_PART_CANCEL].join(
|
|
34
|
+
export const HINT_SINGLE = [HINT_PART_ENTER, HINT_PART_NAV, HINT_PART_NOTES, HINT_PART_CANCEL].join(
|
|
32
35
|
" · ",
|
|
33
36
|
);
|
|
37
|
+
export const HINT_MULTI = [
|
|
38
|
+
HINT_PART_ENTER,
|
|
39
|
+
HINT_PART_NAV,
|
|
40
|
+
HINT_PART_NOTES,
|
|
41
|
+
HINT_PART_TAB,
|
|
42
|
+
HINT_PART_CANCEL,
|
|
43
|
+
].join(" · ");
|
|
34
44
|
/** Single-line footer shown by `QuestionnaireSession` when `state.collapsed === true`. Bypasses `buildHintText`. */
|
|
35
45
|
export const COLLAPSED_HINT = [HINT_PART_EXPAND, HINT_PART_CANCEL].join(" · ");
|
|
36
46
|
export const REVIEW_HEADING = "Review your answers";
|
|
@@ -137,7 +147,10 @@ export class DialogView implements StatefulView<DialogProps> {
|
|
|
137
147
|
const availableMiddle = Math.max(0, termRows - topFixed - bottomFixed);
|
|
138
148
|
if (availableMiddle === 0) {
|
|
139
149
|
// Terminal too small for any middle content — show just chrome.
|
|
140
|
-
const chromeOnly = [
|
|
150
|
+
const chromeOnly = [
|
|
151
|
+
...natural.slice(0, topFixed),
|
|
152
|
+
...natural.slice(natural.length - bottomFixed),
|
|
153
|
+
];
|
|
141
154
|
return chromeOnly.length > termRows ? chromeOnly.slice(0, termRows) : chromeOnly;
|
|
142
155
|
}
|
|
143
156
|
|
|
@@ -149,14 +162,18 @@ export class DialogView implements StatefulView<DialogProps> {
|
|
|
149
162
|
const focusedRowInMiddle = headingCount + bodyRange[0];
|
|
150
163
|
const focusedHeight = bodyRange[1] - bodyRange[0];
|
|
151
164
|
// Center the focused item vertically in the available middle space.
|
|
152
|
-
const idealStart =
|
|
165
|
+
const idealStart =
|
|
166
|
+
focusedRowInMiddle - Math.floor(Math.max(0, availableMiddle - focusedHeight) / 2);
|
|
153
167
|
scrollStart = Math.max(0, Math.min(idealStart, middleRows - availableMiddle));
|
|
154
168
|
} else {
|
|
155
169
|
// No interactive focus (submit tab) — top-anchor the middle.
|
|
156
170
|
scrollStart = 0;
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
const scrollableMiddle = natural.slice(
|
|
173
|
+
const scrollableMiddle = natural.slice(
|
|
174
|
+
topFixed + scrollStart,
|
|
175
|
+
topFixed + scrollStart + availableMiddle,
|
|
176
|
+
);
|
|
160
177
|
|
|
161
178
|
const hasUp = scrollStart > 0;
|
|
162
179
|
const hasDown = scrollStart + availableMiddle < middleRows;
|
|
@@ -182,7 +199,10 @@ export class DialogView implements StatefulView<DialogProps> {
|
|
|
182
199
|
return result.length > termRows ? result.slice(0, termRows) : result;
|
|
183
200
|
}
|
|
184
201
|
|
|
185
|
-
private buildContainerFromStrategy(
|
|
202
|
+
private buildContainerFromStrategy(
|
|
203
|
+
strategy: TabContentStrategy,
|
|
204
|
+
headingRowCache: Component[],
|
|
205
|
+
): Container {
|
|
186
206
|
const { theme, isMulti, tabBar } = this.config;
|
|
187
207
|
const state = this.liveProps.state;
|
|
188
208
|
const container = new Container();
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type Component,
|
|
4
|
+
Container,
|
|
5
|
+
type Input,
|
|
6
|
+
Spacer,
|
|
7
|
+
Text,
|
|
8
|
+
truncateToWidth,
|
|
9
|
+
} from "@earendil-works/pi-tui";
|
|
3
10
|
import { formatAnswerScalar } from "../tool/format-answer.js";
|
|
4
11
|
import type { QuestionData } from "../tool/types.js";
|
|
5
12
|
import type { PreviewPane, PreviewPaneProps } from "./components/preview/preview-pane.js";
|
|
@@ -134,7 +141,10 @@ export class QuestionTabStrategy implements TabContentStrategy {
|
|
|
134
141
|
// drops the trailing parts (collapse hint first, then cancel) with `…`.
|
|
135
142
|
return [
|
|
136
143
|
new Spacer(1),
|
|
137
|
-
new OneLineClippedText(
|
|
144
|
+
new OneLineClippedText(
|
|
145
|
+
this.config.theme.fg("dim", buildHintText(question, this.config.isMulti, state)),
|
|
146
|
+
1,
|
|
147
|
+
),
|
|
138
148
|
];
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -175,7 +185,11 @@ export class SubmitTabStrategy implements TabContentStrategy {
|
|
|
175
185
|
const answerText = formatAnswerScalar(a, "summary");
|
|
176
186
|
c.addChild(new Text(this.config.theme.fg("muted", ` ● ${label}`), 1, 0));
|
|
177
187
|
c.addChild(
|
|
178
|
-
new Text(
|
|
188
|
+
new Text(
|
|
189
|
+
` ${this.config.theme.fg("muted", "→")} ${this.config.theme.fg("text", answerText)}`,
|
|
190
|
+
1,
|
|
191
|
+
0,
|
|
192
|
+
),
|
|
179
193
|
);
|
|
180
194
|
if (a.notes && a.notes.length > 0) {
|
|
181
195
|
c.addChild(new Text(this.config.theme.fg("dim", ` notes: ${a.notes}`), 1, 0));
|
|
@@ -203,10 +217,7 @@ export class SubmitTabStrategy implements TabContentStrategy {
|
|
|
203
217
|
const promptText =
|
|
204
218
|
missing.length === 0
|
|
205
219
|
? this.config.theme.fg("muted", READY_PROMPT)
|
|
206
|
-
: this.config.theme.fg(
|
|
207
|
-
"warning",
|
|
208
|
-
`${INCOMPLETE_WARNING_PREFIX} ${missing.join(", ")}`,
|
|
209
|
-
);
|
|
220
|
+
: this.config.theme.fg("warning", `${INCOMPLETE_WARNING_PREFIX} ${missing.join(", ")}`);
|
|
210
221
|
const out: Component[] = [new Spacer(1), new Text(promptText, 1, 0), new Spacer(1)];
|
|
211
222
|
if (this.config.submitPicker) {
|
|
212
223
|
out.push(this.config.submitPicker);
|
|
@@ -236,7 +247,11 @@ export class SubmitTabStrategy implements TabContentStrategy {
|
|
|
236
247
|
* is the trade we picked over wrapping (which would inflate `footerRowCount` and
|
|
237
248
|
* desync the height math in `DialogView.render`).
|
|
238
249
|
*/
|
|
239
|
-
export function buildHintText(
|
|
250
|
+
export function buildHintText(
|
|
251
|
+
question: QuestionData | undefined,
|
|
252
|
+
isMulti: boolean,
|
|
253
|
+
state: DialogState,
|
|
254
|
+
): string {
|
|
240
255
|
const parts: string[] = [HINT_PART_ENTER, HINT_PART_NAV];
|
|
241
256
|
if (question?.multiSelect === true) parts.push(HINT_PART_TOGGLE);
|
|
242
257
|
if (question && !state.notesVisible && !state.inputMode) {
|