@herbertgao/pi-extensions 2026.9.3 → 2026.9.5
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 +2 -1
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/README.es.md +33 -2
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +33 -2
- package/node_modules/@dietrichgebert/ponytail/README.md +49 -5
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +4 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +4 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +0 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/cursor-hooks.json +17 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +22 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +27 -2
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +61 -2
- package/node_modules/@dietrichgebert/ponytail/package.json +2 -1
- package/node_modules/@dietrichgebert/ponytail/scripts/cursor-hooks.js +135 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +33 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +23 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +88 -19
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +4 -4
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +41 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/events.ts +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/normalize-params.ts +56 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/option-list-view.ts +1 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/tab-bar.ts +1 -2
- package/node_modules/@pi-plugins/fast-mode/README.md +31 -44
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20914 -30
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +5 -7
- package/node_modules/@tifan/pi-copy-response/README.md +2 -2
- package/node_modules/@tifan/pi-copy-response/package.json +3 -2
- package/node_modules/@tifan/pi-inline-skills/README.md +2 -2
- package/node_modules/@tifan/pi-inline-skills/package.json +3 -2
- package/node_modules/@tifan/pi-mermaid-open/README.md +1 -1
- package/node_modules/@tifan/pi-mermaid-open/package.json +3 -2
- package/node_modules/@tifan/pi-preferred-thinking/package.json +3 -2
- package/node_modules/@tifan/pi-recap/package.json +3 -2
- package/node_modules/pi-antigravity/LICENSE +21 -0
- package/node_modules/pi-antigravity/README.md +194 -0
- package/node_modules/pi-antigravity/package.json +67 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
- package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
- package/node_modules/pi-antigravity/src/client/client.ts +561 -0
- package/node_modules/pi-antigravity/src/client/index.ts +1 -0
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/node_modules/pi-antigravity/src/image/image.ts +336 -0
- package/node_modules/pi-antigravity/src/image/index.ts +1 -0
- package/node_modules/pi-antigravity/src/index.ts +280 -0
- package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
- package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
- package/node_modules/pi-antigravity/src/models/index.ts +3 -0
- package/node_modules/pi-antigravity/src/models/models.ts +500 -0
- package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
- package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
- package/node_modules/pi-antigravity/src/types/index.ts +2 -0
- package/node_modules/pi-antigravity/src/types/types.ts +292 -0
- package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
- package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
- package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
- package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
- package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
- package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
- package/node_modules/pi-antigravity/tsconfig.json +21 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +78 -0
- package/node_modules/pi-mcp-adapter/README.md +125 -19
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +69 -30
- package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +36 -0
- package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +365 -0
- package/node_modules/pi-mcp-adapter/commands.ts +13 -15
- package/node_modules/pi-mcp-adapter/config.ts +198 -30
- package/node_modules/pi-mcp-adapter/consent-manager.ts +40 -9
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +250 -0
- package/node_modules/pi-mcp-adapter/direct-tools.ts +9 -228
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +74 -32
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +31 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +9 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +362 -0
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +189 -27
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +7 -16
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +5 -17
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +30 -4
- package/node_modules/pi-mcp-adapter/dist/types.js +26 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +65 -6
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/host-html-template.ts +20 -19
- package/node_modules/pi-mcp-adapter/http-ca.ts +84 -0
- package/node_modules/pi-mcp-adapter/index.ts +827 -164
- package/node_modules/pi-mcp-adapter/init.ts +48 -10
- package/node_modules/pi-mcp-adapter/lazy-loader.ts +7 -0
- package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +113 -0
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +256 -77
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +316 -54
- package/node_modules/pi-mcp-adapter/mcp-code.ts +45 -18
- package/node_modules/pi-mcp-adapter/mcp-install.ts +60 -0
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +70 -6
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +53 -42
- package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +104 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +366 -332
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -14
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +3 -1
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +383 -341
- package/node_modules/pi-mcp-adapter/mcp-status.ts +5 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +7 -16
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +61 -3
- package/node_modules/pi-mcp-adapter/oauth.ts +6 -2
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +5 -17
- package/node_modules/pi-mcp-adapter/package.json +13 -3
- package/node_modules/pi-mcp-adapter/prompts.ts +35 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -7
- package/node_modules/pi-mcp-adapter/runtime-owner.ts +16 -1
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +9 -32
- package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +20 -93
- package/node_modules/pi-mcp-adapter/server-manager.ts +227 -40
- package/node_modules/pi-mcp-adapter/session-approvals.ts +187 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +9 -1
- package/node_modules/pi-mcp-adapter/state.ts +10 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +10 -22
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +14 -0
- package/node_modules/pi-mcp-adapter/types.ts +57 -4
- package/node_modules/pi-mcp-adapter/ui-server.ts +36 -28
- package/node_modules/pi-mcp-adapter/utils.ts +64 -6
- package/package.json +15 -15
package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts
CHANGED
|
@@ -50,9 +50,9 @@ interface DiffTheme {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
type DiffLineKind = "add" | "remove" | "context";
|
|
53
|
-
type DiffEntryKind = "line" | "meta" | "hunk" | "file";
|
|
53
|
+
type DiffEntryKind = "line" | "omission" | "meta" | "hunk" | "file";
|
|
54
54
|
|
|
55
|
-
interface DiffLineEntry {
|
|
55
|
+
export interface DiffLineEntry {
|
|
56
56
|
kind: "line";
|
|
57
57
|
lineKind: DiffLineKind;
|
|
58
58
|
oldLineNumber: number | null;
|
|
@@ -64,15 +64,21 @@ interface DiffLineEntry {
|
|
|
64
64
|
hunkIndex: number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export interface DiffOmissionEntry {
|
|
68
|
+
kind: "omission";
|
|
69
|
+
raw: string;
|
|
70
|
+
hunkIndex: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
interface DiffMetaEntry {
|
|
68
|
-
kind: Exclude<DiffEntryKind, "line">;
|
|
74
|
+
kind: Exclude<DiffEntryKind, "line" | "omission">;
|
|
69
75
|
raw: string;
|
|
70
76
|
hunkIndex: number;
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
type ParsedDiffEntry = DiffLineEntry | DiffMetaEntry;
|
|
79
|
+
type ParsedDiffEntry = DiffLineEntry | DiffOmissionEntry | DiffMetaEntry;
|
|
74
80
|
|
|
75
|
-
interface ParsedDiff {
|
|
81
|
+
export interface ParsedDiff {
|
|
76
82
|
entries: ParsedDiffEntry[];
|
|
77
83
|
stats: DiffStats;
|
|
78
84
|
}
|
|
@@ -94,6 +100,7 @@ interface RenderedRow {
|
|
|
94
100
|
interface SplitDiffRow {
|
|
95
101
|
left?: DiffLineEntry;
|
|
96
102
|
right?: DiffLineEntry;
|
|
103
|
+
omission?: DiffOmissionEntry;
|
|
97
104
|
meta?: DiffMetaEntry;
|
|
98
105
|
hunkIndex: number | null;
|
|
99
106
|
}
|
|
@@ -131,7 +138,9 @@ type CodeLineHighlighter = (line: string, entry: DiffLineEntry) => string;
|
|
|
131
138
|
|
|
132
139
|
const CANONICAL_LINE_PATTERN = /^([+\- ])(\s*\d+)\|(.*)$/;
|
|
133
140
|
const HASHLINE_ANCHOR_LINE_PATTERN = /^([+\- ])(\s*\d+)#([A-Za-z0-9]+| {2}):(.*)$/;
|
|
141
|
+
// Pi 仍使用空格分隔的行号格式;仅在无 hunk 头时可安全识别。
|
|
134
142
|
const LEGACY_LINE_PATTERN = /^([+\- ])(\s*\d+)\s(.*)$/;
|
|
143
|
+
const PI_OMISSION_LINE_PATTERN = /^ {3,}\.\.\.$/;
|
|
135
144
|
const HUNK_HEADER_PATTERN = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(.*)$/;
|
|
136
145
|
const SPLIT_SEPARATOR = " │ ";
|
|
137
146
|
const MIN_LINE_NUMBER_WIDTH = 2;
|
|
@@ -374,7 +383,10 @@ function toParsedDiffLine(
|
|
|
374
383
|
return { lineKind: "context", lineNumber: normalizedLineNumber, content };
|
|
375
384
|
}
|
|
376
385
|
|
|
377
|
-
function parseCanonicalDiffLine(
|
|
386
|
+
function parseCanonicalDiffLine(
|
|
387
|
+
line: string,
|
|
388
|
+
allowPiFormat: boolean,
|
|
389
|
+
): {
|
|
378
390
|
lineKind: DiffLineKind;
|
|
379
391
|
lineNumber: string;
|
|
380
392
|
content: string;
|
|
@@ -393,7 +405,7 @@ function parseCanonicalDiffLine(line: string): {
|
|
|
393
405
|
}
|
|
394
406
|
|
|
395
407
|
const canonicalMatch = line.match(CANONICAL_LINE_PATTERN);
|
|
396
|
-
const legacyMatch = canonicalMatch ? null : line.match(LEGACY_LINE_PATTERN);
|
|
408
|
+
const legacyMatch = canonicalMatch || !allowPiFormat ? null : line.match(LEGACY_LINE_PATTERN);
|
|
397
409
|
const matched = canonicalMatch ?? legacyMatch;
|
|
398
410
|
if (!matched) {
|
|
399
411
|
return null;
|
|
@@ -486,7 +498,7 @@ function ensureImplicitHunk(currentHunk: number): number {
|
|
|
486
498
|
return currentHunk > 0 ? currentHunk : 1;
|
|
487
499
|
}
|
|
488
500
|
|
|
489
|
-
function parseDiff(diffText: string): ParsedDiff {
|
|
501
|
+
export function parseDiff(diffText: string): ParsedDiff {
|
|
490
502
|
const stats: DiffStats = {
|
|
491
503
|
added: 0,
|
|
492
504
|
removed: 0,
|
|
@@ -505,12 +517,14 @@ function parseDiff(diffText: string): ParsedDiff {
|
|
|
505
517
|
let oldLineCursor: number | null = null;
|
|
506
518
|
let newLineCursor: number | null = null;
|
|
507
519
|
let lineNumberDelta = 0;
|
|
520
|
+
let hasHunkHeader = false;
|
|
508
521
|
|
|
509
522
|
for (const rawLine of diffText.replace(/\r/g, "").split("\n")) {
|
|
510
523
|
stats.lines++;
|
|
511
524
|
|
|
512
525
|
const hunkMatch = rawLine.match(HUNK_HEADER_PATTERN);
|
|
513
526
|
if (hunkMatch) {
|
|
527
|
+
hasHunkHeader = true;
|
|
514
528
|
hunkIndex++;
|
|
515
529
|
stats.hunks = Math.max(stats.hunks, hunkIndex);
|
|
516
530
|
oldLineCursor = toNumber(hunkMatch[1]);
|
|
@@ -535,7 +549,14 @@ function parseDiff(diffText: string): ParsedDiff {
|
|
|
535
549
|
lineNumberDelta = 0;
|
|
536
550
|
}
|
|
537
551
|
|
|
538
|
-
|
|
552
|
+
// Pi 用空白行号填充省略上下文;它不是源码行,也不推进行号游标。
|
|
553
|
+
if (!hasHunkHeader && PI_OMISSION_LINE_PATTERN.test(rawLine)) {
|
|
554
|
+
entries.push({ kind: "omission", raw: rawLine, hunkIndex });
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Pi 无 hunk 头时的数字行号与 unified hunk 中的数字源码文本存在歧义。
|
|
559
|
+
const canonical = parseCanonicalDiffLine(rawLine, !hasHunkHeader);
|
|
539
560
|
if (canonical) {
|
|
540
561
|
hunkIndex = ensureImplicitHunk(hunkIndex);
|
|
541
562
|
stats.hunks = Math.max(stats.hunks, hunkIndex);
|
|
@@ -802,6 +823,12 @@ function buildSplitRows(entries: ParsedDiffEntry[]): SplitDiffRow[] {
|
|
|
802
823
|
break;
|
|
803
824
|
}
|
|
804
825
|
|
|
826
|
+
if (entry.kind === "omission") {
|
|
827
|
+
rows.push({ omission: entry, hunkIndex: entry.hunkIndex || null });
|
|
828
|
+
index++;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
|
|
805
832
|
if (entry.kind !== "line") {
|
|
806
833
|
rows.push({ meta: entry, hunkIndex: entry.hunkIndex || null });
|
|
807
834
|
index++;
|
|
@@ -1785,8 +1812,44 @@ interface DiffRenderContext {
|
|
|
1785
1812
|
showHashlineAnchors: boolean;
|
|
1786
1813
|
}
|
|
1787
1814
|
|
|
1788
|
-
|
|
1815
|
+
const OMISSION_GLYPH = "⋮";
|
|
1816
|
+
|
|
1817
|
+
function createOmissionRenderLine(entry: DiffOmissionEntry, compact = false): DiffLineEntry {
|
|
1818
|
+
return {
|
|
1819
|
+
kind: "line",
|
|
1820
|
+
lineKind: "context",
|
|
1821
|
+
oldLineNumber: null,
|
|
1822
|
+
newLineNumber: null,
|
|
1823
|
+
fallbackLineNumber: compact ? "" : OMISSION_GLYPH,
|
|
1824
|
+
content: compact ? OMISSION_GLYPH : "",
|
|
1825
|
+
raw: entry.raw,
|
|
1826
|
+
hunkIndex: entry.hunkIndex,
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function materializeOmissionEntries(
|
|
1789
1831
|
entries: ParsedDiffEntry[],
|
|
1832
|
+
compact = false,
|
|
1833
|
+
): (DiffLineEntry | DiffMetaEntry)[] {
|
|
1834
|
+
return entries.map((entry) =>
|
|
1835
|
+
entry.kind === "omission" ? createOmissionRenderLine(entry, compact) : entry,
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function omitTerminalOmissions(entries: ParsedDiffEntry[]): ParsedDiffEntry[] {
|
|
1840
|
+
let lastLineIndex = -1;
|
|
1841
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
1842
|
+
if (entries[index]?.kind === "line") {
|
|
1843
|
+
lastLineIndex = index;
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
return entries.filter((entry, index) => entry.kind !== "omission" || index < lastLineIndex);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function processDiffEntries(
|
|
1852
|
+
entries: (DiffLineEntry | DiffMetaEntry)[],
|
|
1790
1853
|
ctx: DiffRenderContext,
|
|
1791
1854
|
processLine: (entry: DiffLineEntry) => string[],
|
|
1792
1855
|
): RenderedRow[] {
|
|
@@ -1807,7 +1870,7 @@ function renderUnified(
|
|
|
1807
1870
|
ctx: DiffRenderContext,
|
|
1808
1871
|
lineNumberWidth: number,
|
|
1809
1872
|
): RenderedRow[] {
|
|
1810
|
-
return processDiffEntries(entries, ctx, (entry) => {
|
|
1873
|
+
return processDiffEntries(materializeOmissionEntries(entries), ctx, (entry) => {
|
|
1811
1874
|
const lineNumber =
|
|
1812
1875
|
entry.lineKind === "add"
|
|
1813
1876
|
? formatLineNumberLabel(
|
|
@@ -1857,6 +1920,10 @@ function toUnifiedFallbackRows(
|
|
|
1857
1920
|
): RenderedRow[] {
|
|
1858
1921
|
const flattened: ParsedDiffEntry[] = [];
|
|
1859
1922
|
for (const row of rows) {
|
|
1923
|
+
if (row.omission) {
|
|
1924
|
+
flattened.push(row.omission);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1860
1927
|
if (row.meta) {
|
|
1861
1928
|
flattened.push(row.meta);
|
|
1862
1929
|
continue;
|
|
@@ -1872,7 +1939,7 @@ function toUnifiedFallbackRows(
|
|
|
1872
1939
|
}
|
|
1873
1940
|
|
|
1874
1941
|
function renderCompact(entries: ParsedDiffEntry[], ctx: DiffRenderContext): RenderedRow[] {
|
|
1875
|
-
return processDiffEntries(entries, ctx, (entry) => {
|
|
1942
|
+
return processDiffEntries(materializeOmissionEntries(entries, true), ctx, (entry) => {
|
|
1876
1943
|
const codeText = normalizeCodeWhitespace(
|
|
1877
1944
|
getCompactLineRenderContent(entry, ctx.showHashlineAnchors),
|
|
1878
1945
|
);
|
|
@@ -2091,8 +2158,9 @@ function renderSplit(
|
|
|
2091
2158
|
continue;
|
|
2092
2159
|
}
|
|
2093
2160
|
|
|
2161
|
+
const omissionLine = row.omission ? createOmissionRenderLine(row.omission) : undefined;
|
|
2094
2162
|
const leftCells = renderSplitCell(
|
|
2095
|
-
row.left,
|
|
2163
|
+
omissionLine ?? row.left,
|
|
2096
2164
|
"left",
|
|
2097
2165
|
leftWidth,
|
|
2098
2166
|
splitLineNumberWidth,
|
|
@@ -2106,7 +2174,7 @@ function renderSplit(
|
|
|
2106
2174
|
showHashlineAnchors,
|
|
2107
2175
|
);
|
|
2108
2176
|
const rightCells = renderSplitCell(
|
|
2109
|
-
row.right,
|
|
2177
|
+
omissionLine ?? row.right,
|
|
2110
2178
|
"right",
|
|
2111
2179
|
rightWidth,
|
|
2112
2180
|
splitLineNumberWidth,
|
|
@@ -2561,18 +2629,19 @@ export function renderEditDiffResult(
|
|
|
2561
2629
|
return new Text(theme.fg("muted", "↳ no diff data"), 0, 0);
|
|
2562
2630
|
}
|
|
2563
2631
|
|
|
2564
|
-
const
|
|
2632
|
+
const renderEntries = omitTerminalOmissions(parsed.entries);
|
|
2633
|
+
const splitRows = buildSplitRows(renderEntries);
|
|
2565
2634
|
const showHashlineAnchors =
|
|
2566
2635
|
options.expanded === true &&
|
|
2567
|
-
|
|
2568
|
-
const lineNumberWidth = getLineNumberWidth(
|
|
2636
|
+
renderEntries.some((entry) => entry.kind === "line" && !!entry.hashlineAnchorContent);
|
|
2637
|
+
const lineNumberWidth = getLineNumberWidth(renderEntries, showHashlineAnchors);
|
|
2569
2638
|
const palette = resolveDiffPalette(theme);
|
|
2570
2639
|
// Rich diffs use ccstyle's self shell. Keep the panel transparent so the
|
|
2571
2640
|
// separator cannot leak toolSuccessBg across the entire new column.
|
|
2572
2641
|
const containerBgAnsi = undefined;
|
|
2573
2642
|
const language = resolveLanguageFromPath(options.filePath);
|
|
2574
2643
|
const cache = createDiffRenderCache();
|
|
2575
|
-
const highlightLine = createCodeLineHighlighter(language, theme,
|
|
2644
|
+
const highlightLine = createCodeLineHighlighter(language, theme, renderEntries, () => {
|
|
2576
2645
|
cache.invalidate();
|
|
2577
2646
|
options.invalidate?.();
|
|
2578
2647
|
});
|
|
@@ -2621,7 +2690,7 @@ export function renderEditDiffResult(
|
|
|
2621
2690
|
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
2622
2691
|
// Only highlight/render a prefix that can fill the display limit; full-diff
|
|
2623
2692
|
// LCS + syntax highlight on thousands of hidden lines is pure waste when collapsed.
|
|
2624
|
-
const entryBudget = takeEntriesForLineBudget(
|
|
2693
|
+
const entryBudget = takeEntriesForLineBudget(renderEntries, processBudget);
|
|
2625
2694
|
const splitBudget = takeSplitRowsForBudget(splitRows, processBudget);
|
|
2626
2695
|
const inlineHighlights = buildInlineHighlightMap(splitBudget.rows);
|
|
2627
2696
|
const renderCtx: DiffRenderContext = {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
10
10
|
import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
|
|
11
|
+
import { fitToolCallSummary, pathSummary, type ToolCallSummary } from "./result.ts";
|
|
11
12
|
import { isToolTuiFullscreen, showMoreHintText } from "../show-more-hint.ts";
|
|
12
13
|
|
|
13
14
|
const PATCH_KEY = Symbol.for("pi.ccstyle.tool-grouping-patch");
|
|
@@ -172,7 +173,7 @@ function humanizeToolName(name: string): string {
|
|
|
172
173
|
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
function toolSummary(tool: any):
|
|
176
|
+
function toolSummary(tool: any): ToolCallSummary {
|
|
176
177
|
const name = toolName(tool);
|
|
177
178
|
const lowerName = name.toLowerCase();
|
|
178
179
|
const args = tool?.args ?? {};
|
|
@@ -232,10 +233,11 @@ function toolSummary(tool: any): { main: string; detail: string } {
|
|
|
232
233
|
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
233
234
|
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
234
235
|
].filter(Boolean);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
236
|
+
const detail = details.length ? ` (${details.join(", ")})` : "";
|
|
237
|
+
if (typeof args.path === "string" && args.path) {
|
|
238
|
+
return pathSummary("Read", args.path, tool?.cwd, detail);
|
|
239
|
+
}
|
|
240
|
+
return { main: "Read ...", detail };
|
|
239
241
|
}
|
|
240
242
|
if (name === "bash") return { main: `Bash ${oneLine(args.command || "...")}`, detail: "" };
|
|
241
243
|
if (name === "grep") {
|
|
@@ -456,11 +458,14 @@ export class ToolGroupComponent extends Container {
|
|
|
456
458
|
const continuation = index === total - 1 ? " " : "│ ";
|
|
457
459
|
if (!this._expanded) {
|
|
458
460
|
const summary = toolSummary(tool);
|
|
461
|
+
const prefix = ` ${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `;
|
|
462
|
+
const detail = fg("dim", summary.detail);
|
|
463
|
+
const mainWidth = Math.max(0, width - visibleWidth(prefix) - visibleWidth(detail));
|
|
459
464
|
lines.push(
|
|
460
465
|
truncateToWidth(
|
|
461
|
-
|
|
466
|
+
`${prefix}${fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${detail}`,
|
|
462
467
|
width,
|
|
463
|
-
"
|
|
468
|
+
"",
|
|
464
469
|
),
|
|
465
470
|
);
|
|
466
471
|
continue;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
2
3
|
import { inspect } from "node:util";
|
|
3
4
|
import { config } from "../../config/config.ts";
|
|
4
5
|
import { showMoreHintText } from "../show-more-hint.ts";
|
|
@@ -557,6 +558,103 @@ export function middleTruncateToWidth(text: string, width: number): string {
|
|
|
557
558
|
return `${left}…${right}`;
|
|
558
559
|
}
|
|
559
560
|
|
|
561
|
+
export type ToolCallSummary = {
|
|
562
|
+
main: string;
|
|
563
|
+
detail: string;
|
|
564
|
+
/** 路径摘要保留结构,供最终渲染按实际宽度优先保留文件名。 */
|
|
565
|
+
path?: { prefix: string; value: string };
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
function pathApi(value: string) {
|
|
569
|
+
if (win32.isAbsolute(value)) return win32;
|
|
570
|
+
if (posix.isAbsolute(value)) return posix;
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** cwd 内绝对路径转相对路径;cwd 外路径保持不变。 */
|
|
575
|
+
export function displayPath(value: unknown, cwd?: string): string {
|
|
576
|
+
const text = oneLine(value, 4096);
|
|
577
|
+
const base = cwd ? oneLine(cwd, 4096) : "";
|
|
578
|
+
const api = pathApi(text);
|
|
579
|
+
if (!api || !base || !api.isAbsolute(base)) return text;
|
|
580
|
+
const relative = api.relative(base, text);
|
|
581
|
+
if (!relative) return api.basename(text) || ".";
|
|
582
|
+
if (relative === ".." || relative.startsWith(`..${api.sep}`) || api.isAbsolute(relative)) {
|
|
583
|
+
return text;
|
|
584
|
+
}
|
|
585
|
+
return relative;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function headToWidth(text: string, width: number, ellipsis = ""): string {
|
|
589
|
+
if (visibleWidth(text) <= width) return text;
|
|
590
|
+
if (width <= 0) return "";
|
|
591
|
+
const suffix = visibleWidth(ellipsis) <= width ? ellipsis : "";
|
|
592
|
+
const contentWidth = width - visibleWidth(suffix);
|
|
593
|
+
let head = "";
|
|
594
|
+
for (const char of Array.from(text)) {
|
|
595
|
+
if (visibleWidth(head + char) > contentWidth) break;
|
|
596
|
+
head += char;
|
|
597
|
+
}
|
|
598
|
+
return head + suffix;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function tailToWidth(text: string, width: number): string {
|
|
602
|
+
if (width <= 0) return "";
|
|
603
|
+
let tail = "";
|
|
604
|
+
for (const char of Array.from(text).reverse()) {
|
|
605
|
+
if (visibleWidth(char + tail) > width) break;
|
|
606
|
+
tail = char + tail;
|
|
607
|
+
}
|
|
608
|
+
return tail;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** 中间截断路径:目录保留开头,末尾优先完整保留文件名。 */
|
|
612
|
+
export function truncatePathToWidth(path: string, width: number): string {
|
|
613
|
+
if (visibleWidth(path) <= width) return path;
|
|
614
|
+
if (width <= 1) return width === 1 ? "…" : "";
|
|
615
|
+
const separatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
616
|
+
const filename = separatorIndex >= 0 ? path.slice(separatorIndex + 1) : path;
|
|
617
|
+
const filenameWidth = visibleWidth(filename);
|
|
618
|
+
if (separatorIndex >= 0 && filenameWidth + 1 <= width) {
|
|
619
|
+
const separator = path[separatorIndex]!;
|
|
620
|
+
const suffix = `${separator}${filename}`;
|
|
621
|
+
const prefixWidth = width - visibleWidth(suffix) - 1;
|
|
622
|
+
const prefix = headToWidth(path.slice(0, separatorIndex), Math.max(0, prefixWidth));
|
|
623
|
+
return `${prefix}…${prefix ? suffix : filename}`;
|
|
624
|
+
}
|
|
625
|
+
const leftWidth = Math.max(1, Math.floor((width - 1) * 0.45));
|
|
626
|
+
const rightWidth = Math.max(0, width - leftWidth - 1);
|
|
627
|
+
return `${headToWidth(filename, leftWidth)}…${tailToWidth(filename, rightWidth)}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** 路径展示的统一入口:相对化后按当前可用宽度中间截断。 */
|
|
631
|
+
export function formatDisplayPath(value: unknown, cwd: string | undefined, width: number): string {
|
|
632
|
+
return truncatePathToWidth(displayPath(value, cwd), Math.max(0, Math.floor(width)));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** 按最终终端宽度渲染摘要;路径摘要不会再被整行头部截断。 */
|
|
636
|
+
export function fitToolCallSummary(summary: ToolCallSummary, width: number): string {
|
|
637
|
+
if (!summary.path) return headTruncateToWidth(summary.main, width);
|
|
638
|
+
const prefix = summary.path.prefix;
|
|
639
|
+
const pathWidth = Math.max(0, width - visibleWidth(prefix) - 1);
|
|
640
|
+
if (pathWidth <= 0) return headTruncateToWidth(prefix, width);
|
|
641
|
+
return `${prefix} ${truncatePathToWidth(summary.path.value, pathWidth)}`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function pathSummary(
|
|
645
|
+
prefix: string,
|
|
646
|
+
value: unknown,
|
|
647
|
+
cwd: string | undefined,
|
|
648
|
+
detail = "",
|
|
649
|
+
): ToolCallSummary {
|
|
650
|
+
const path = displayPath(value, cwd);
|
|
651
|
+
return {
|
|
652
|
+
main: `${prefix} ${path}`,
|
|
653
|
+
detail,
|
|
654
|
+
path: { prefix, value: path },
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
560
658
|
/** Pretty-print tool call args for the expanded Input section. */
|
|
561
659
|
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
562
660
|
if (args === undefined || args === null) return "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herbertgao/pi-cc-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
|
|
6
6
|
"type": "module",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
},
|
|
70
70
|
"x-upstream": {
|
|
71
71
|
"package": "pi-cc-extensions",
|
|
72
|
-
"version": "0.8.
|
|
72
|
+
"version": "0.8.71",
|
|
73
73
|
"repository": "https://github.com/minuque/pi-cc-extensions",
|
|
74
|
-
"commit": "
|
|
74
|
+
"commit": "e43e0041b59f5d7f03be9b9d103a5f9e954e4c11"
|
|
75
75
|
}
|
|
76
76
|
}
|
package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts
CHANGED
|
@@ -218,9 +218,8 @@ export function decideCompaction(input: {
|
|
|
218
218
|
carriedDebtTokens: input.carriedDebtTokens,
|
|
219
219
|
cacheDebtRepaymentTokens: input.cacheDebtRepaymentTokens,
|
|
220
220
|
compact,
|
|
221
|
-
reason:
|
|
222
|
-
?
|
|
223
|
-
: windowProtection
|
|
221
|
+
reason: compressible
|
|
222
|
+
? windowProtection
|
|
224
223
|
? "window_protection"
|
|
225
224
|
: economic
|
|
226
225
|
? "economic"
|
|
@@ -232,6 +231,7 @@ export function decideCompaction(input: {
|
|
|
232
231
|
? "deferred_subsequent_margin"
|
|
233
232
|
: !firstCompaction && baseEconomic && !carriedDebtGateOpen
|
|
234
233
|
? "deferred_carried_debt"
|
|
235
|
-
: "deferred_economic"
|
|
234
|
+
: "deferred_economic"
|
|
235
|
+
: "non_positive_saving",
|
|
236
236
|
};
|
|
237
237
|
}
|
package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
|
-
import type { AgentMessage, AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
|
+
import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import {
|
|
7
7
|
buildSessionContext,
|
|
8
8
|
estimateTokens,
|
|
9
9
|
findCutPoint,
|
|
10
10
|
sessionEntryToContextMessages,
|
|
11
|
+
type ExtensionAPI,
|
|
11
12
|
type ExtensionContext,
|
|
12
13
|
type ExtensionFactory,
|
|
13
14
|
type SessionEntry,
|
|
@@ -165,6 +166,37 @@ function validPositiveInteger(value: unknown): value is number {
|
|
|
165
166
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
type CompactionThinkingOverride = {
|
|
170
|
+
readonly previous: ThinkingLevel;
|
|
171
|
+
readonly applied: ThinkingLevel;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Native summaries share their output cap with reasoning; reserve the cap for summary text.
|
|
175
|
+
function lowerCompactionThinking(
|
|
176
|
+
pi: ExtensionAPI,
|
|
177
|
+
reasoning: boolean,
|
|
178
|
+
): CompactionThinkingOverride | undefined {
|
|
179
|
+
if (!reasoning) return;
|
|
180
|
+
try {
|
|
181
|
+
const previous = pi.getThinkingLevel();
|
|
182
|
+
if (previous !== "high" && previous !== "xhigh" && previous !== "max") return;
|
|
183
|
+
pi.setThinkingLevel("medium");
|
|
184
|
+
const applied = pi.getThinkingLevel();
|
|
185
|
+
return applied === previous ? undefined : { previous, applied };
|
|
186
|
+
} catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function restoreCompactionThinking(pi: ExtensionAPI, override: CompactionThinkingOverride | undefined): void {
|
|
192
|
+
if (!override) return;
|
|
193
|
+
try {
|
|
194
|
+
if (pi.getThinkingLevel() === override.applied) pi.setThinkingLevel(override.previous);
|
|
195
|
+
} catch {
|
|
196
|
+
// A compaction result must not be masked by a best-effort state restore.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
168
200
|
function releaseParentContinuation(continuation: PendingContinuation | undefined): void {
|
|
169
201
|
if (continuation) setTimeout(continuation.resolve, 0);
|
|
170
202
|
}
|
|
@@ -182,6 +214,7 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
|
|
|
182
214
|
let activeDebt: CacheDebt | undefined;
|
|
183
215
|
let nextContinuation: PendingContinuation | undefined;
|
|
184
216
|
let compactionInFlight = false;
|
|
217
|
+
let compactionThinkingOverride: CompactionThinkingOverride | undefined;
|
|
185
218
|
|
|
186
219
|
const releaseContinuation = (): void => {
|
|
187
220
|
const continuation = nextContinuation;
|
|
@@ -349,6 +382,7 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
|
|
|
349
382
|
let compactionError: Error | undefined;
|
|
350
383
|
try {
|
|
351
384
|
compactionInFlight = true;
|
|
385
|
+
compactionThinkingOverride = lowerCompactionThinking(pi, context.model?.reasoning === true);
|
|
352
386
|
await new Promise<void>((resolve) => {
|
|
353
387
|
let finished = false;
|
|
354
388
|
const finish = (): void => {
|
|
@@ -382,6 +416,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
|
|
|
382
416
|
},
|
|
383
417
|
});
|
|
384
418
|
});
|
|
419
|
+
restoreCompactionThinking(pi, compactionThinkingOverride);
|
|
420
|
+
compactionThinkingOverride = undefined;
|
|
385
421
|
compactionInFlight = false;
|
|
386
422
|
if (
|
|
387
423
|
compactionError &&
|
|
@@ -422,6 +458,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
|
|
|
422
458
|
await continuation.promise;
|
|
423
459
|
}
|
|
424
460
|
} finally {
|
|
461
|
+
restoreCompactionThinking(pi, compactionThinkingOverride);
|
|
462
|
+
compactionThinkingOverride = undefined;
|
|
425
463
|
compactionInFlight = false;
|
|
426
464
|
activeDebt = undefined;
|
|
427
465
|
releaseParentContinuation(parentContinuation);
|
|
@@ -445,6 +483,8 @@ export function createOnlineContextCompactExtension(options: OnlineContextCompac
|
|
|
445
483
|
});
|
|
446
484
|
|
|
447
485
|
pi.on("session_shutdown", () => {
|
|
486
|
+
restoreCompactionThinking(pi, compactionThinkingOverride);
|
|
487
|
+
compactionThinkingOverride = undefined;
|
|
448
488
|
releaseContinuation();
|
|
449
489
|
pendingBoundary = undefined;
|
|
450
490
|
selected = undefined;
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { type DialogUI, hasDialogUI, runRpcQuestionnaire } from "./rpc-fallback.js";
|
|
19
19
|
import { displayLabel, t } from "./state/i18n-bridge.js";
|
|
20
20
|
import { sentinelsToAppend } from "./state/row-intent.js";
|
|
21
|
+
import { normalizeQuestionParams } from "./tool/normalize-params.js";
|
|
21
22
|
import { buildQuestionnaireResponse, buildToolResult } from "./tool/response-envelope.js";
|
|
22
23
|
import {
|
|
23
24
|
MAX_OPTIONS,
|
|
@@ -310,7 +311,10 @@ export function registerAskUserQuestionTool(pi: ExtensionAPI): void {
|
|
|
310
311
|
parameters: QuestionParamsSchema,
|
|
311
312
|
|
|
312
313
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
313
|
-
|
|
314
|
+
// Line-terminator normalization runs once here, ahead of validation, so
|
|
315
|
+
// every downstream consumer — validator, TUI, RPC walker, envelope, prompt
|
|
316
|
+
// event — sees the same clean text (#192).
|
|
317
|
+
const typed = normalizeQuestionParams(params as unknown as QuestionParams);
|
|
314
318
|
if (!ctx.hasUI) return rejectWithoutUi();
|
|
315
319
|
|
|
316
320
|
const validation = validateQuestionnaire(typed);
|
|
@@ -38,7 +38,11 @@ export interface AskUserBlockedEventPayload {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface AskUserPromptQuestion {
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The full question text as the agent authored it, with line terminators
|
|
43
|
+
* normalized at tool entry (`\r\n` → `\n`, lone `\r` removed — #192). The
|
|
44
|
+
* same normalization applies to `header` and every option field below.
|
|
45
|
+
*/
|
|
42
46
|
question: string;
|
|
43
47
|
/** The short chip/tag shown next to the question. */
|
|
44
48
|
header: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-ask-user-question",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "Pi extension. A structured questionnaire the model can put to you when it would otherwise guess, with typed options instead of free-form replies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"state/state-reducer.ts",
|
|
57
57
|
"state/state.ts",
|
|
58
58
|
"tool/format-answer.ts",
|
|
59
|
+
"tool/normalize-params.ts",
|
|
59
60
|
"tool/response-envelope.ts",
|
|
60
61
|
"tool/types.ts",
|
|
61
62
|
"tool/validate-questionnaire.ts",
|
|
@@ -94,7 +95,7 @@
|
|
|
94
95
|
}
|
|
95
96
|
},
|
|
96
97
|
"dependencies": {
|
|
97
|
-
"@juicesharp/rpiv-config": "^2.
|
|
98
|
+
"@juicesharp/rpiv-config": "^2.10.1",
|
|
98
99
|
"typebox": "^1.1.24"
|
|
99
100
|
}
|
|
100
101
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { QuestionParams } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize line terminators in one model-supplied text field (#192).
|
|
5
|
+
*
|
|
6
|
+
* Some models serialize a bare carriage return inside tool-call string
|
|
7
|
+
* arguments at token boundaries where the text was meant to be contiguous
|
|
8
|
+
* (`GEMBA\r_LOG\r_FILE` for `GEMBA_LOG_FILE`). A raw CR is a cursor-control
|
|
9
|
+
* byte, not text: pi-tui ≤0.80 writes it straight into the row, so the
|
|
10
|
+
* terminal returns to column 0 and later text overwrites the pointer and
|
|
11
|
+
* number; pi-tui ≥0.84 splits `wrapTextWithAnsi` on `\r`, so one option
|
|
12
|
+
* fragments into stacked rows. Both symptoms have the same fix at our
|
|
13
|
+
* boundary:
|
|
14
|
+
*
|
|
15
|
+
* - `\r\n` → `\n` keeps genuine multi-line content (preview markdown) intact.
|
|
16
|
+
* - a lone `\r` is deleted — never a space (phantom gaps inside words) and
|
|
17
|
+
* never `\n` (reintroduces the vertical fragmentation). This matches
|
|
18
|
+
* pi-coding-agent's own display normalization (`normalizeDisplayText`).
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeLineTerminators(text: string): string {
|
|
21
|
+
return text.replace(/\r\n/g, "\n").replace(/\r/g, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Copy `obj`, normalizing the listed keys that hold strings. Keys absent from
|
|
26
|
+
* `obj` (e.g. an omitted `preview`) stay absent so `"preview" in option`
|
|
27
|
+
* checks and `hasPreview` derivations are unchanged.
|
|
28
|
+
*/
|
|
29
|
+
function normalizeStringFields<T extends object>(obj: T, keys: readonly (keyof T)[]): T {
|
|
30
|
+
const out = { ...obj };
|
|
31
|
+
for (const key of keys) {
|
|
32
|
+
const value = obj[key];
|
|
33
|
+
if (typeof value === "string") out[key] = normalizeLineTerminators(value) as T[typeof key];
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return a copy of the tool params with every user-facing string field
|
|
40
|
+
* (`question`, `header`, `options[].label`, `options[].description`,
|
|
41
|
+
* `options[].preview`) line-terminator-normalized. Runs once at tool entry,
|
|
42
|
+
* BEFORE `validateQuestionnaire`, so the reserved-label and duplicate-label
|
|
43
|
+
* guards compare the text the user will actually see (`"Other\r"` must not
|
|
44
|
+
* slip past `reserved_label`), and so the TUI, the RPC dialog walker, the
|
|
45
|
+
* envelope echo, and the `rpiv:ask-user:prompt` payload all carry the same
|
|
46
|
+
* clean text. Pure: the input object is never mutated.
|
|
47
|
+
*/
|
|
48
|
+
export function normalizeQuestionParams(params: QuestionParams): QuestionParams {
|
|
49
|
+
return {
|
|
50
|
+
...params,
|
|
51
|
+
questions: params.questions.map((q) => ({
|
|
52
|
+
...normalizeStringFields(q, ["question", "header"]),
|
|
53
|
+
options: q.options.map((o) => normalizeStringFields(o, ["label", "description", "preview"])),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -13,7 +13,7 @@ export interface OptionListViewConfig {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Per-tick projection of OptionListView state.
|
|
16
|
+
* Per-tick projection of OptionListView state. `inputBuffer`
|
|
17
17
|
* is part of the props bag — the session-owned `inlineInput` (a headless
|
|
18
18
|
* `pi-tui` Editor instance) supplies its current `getText()` here per tick.
|
|
19
19
|
* `OptionListView` is purely props-driven; the imperative buffer surface and
|