@oh-my-pi/pi-coding-agent 15.3.0 → 15.3.2
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/dist/types/cli/auth-gateway-cli.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +26 -0
- package/dist/types/modes/components/status-line.d.ts +6 -0
- package/dist/types/session/session-manager.d.ts +10 -0
- package/dist/types/task/types.d.ts +8 -0
- package/package.json +7 -7
- package/src/cli/auth-gateway-cli.ts +71 -2
- package/src/commands/auth-gateway.ts +2 -0
- package/src/config/model-registry.ts +46 -21
- package/src/extensibility/plugins/marketplace/manager.ts +20 -1
- package/src/hashline/parser.ts +44 -5
- package/src/internal-urls/docs-index.generated.ts +3 -1
- package/src/lsp/config.ts +87 -22
- package/src/modes/components/status-line.ts +124 -31
- package/src/modes/utils/context-usage.ts +18 -7
- package/src/sdk.ts +4 -1
- package/src/session/agent-session.ts +14 -2
- package/src/session/session-manager.ts +68 -3
- package/src/slash-commands/builtin-registry.ts +9 -4
- package/src/task/executor.ts +29 -0
- package/src/task/render.ts +53 -1
- package/src/task/types.ts +8 -0
- package/src/tools/jtd-to-json-schema.ts +5 -1
- package/src/tools/read.ts +3 -35
- package/src/utils/clipboard.ts +14 -3
- package/src/utils/image-resize.ts +28 -5
package/src/tools/read.ts
CHANGED
|
@@ -118,22 +118,8 @@ function formatTextWithMode(
|
|
|
118
118
|
startNum: number,
|
|
119
119
|
shouldAddHashLines: boolean,
|
|
120
120
|
shouldAddLineNumbers: boolean,
|
|
121
|
-
truncatedLines?: ReadonlySet<number>,
|
|
122
121
|
): string {
|
|
123
|
-
if (shouldAddHashLines)
|
|
124
|
-
if (!truncatedLines || truncatedLines.size === 0) return formatHashLines(text, startNum);
|
|
125
|
-
// Column-truncated lines hash differently from the on-disk line that the
|
|
126
|
-
// edit verifier reads back. Drop the anchor (`LINE|TEXT` instead of
|
|
127
|
-
// `LINE+HASH|TEXT`) so the model treats the line as un-anchorable rather
|
|
128
|
-
// than copying a hash that will be rejected as stale.
|
|
129
|
-
const lines = text.split("\n");
|
|
130
|
-
return lines
|
|
131
|
-
.map((line, i) => {
|
|
132
|
-
const ln = startNum + i;
|
|
133
|
-
return truncatedLines.has(ln) ? `${ln}${HL_BODY_SEP}${line}` : formatHashLine(ln, line);
|
|
134
|
-
})
|
|
135
|
-
.join("\n");
|
|
136
|
-
}
|
|
122
|
+
if (shouldAddHashLines) return formatHashLines(text, startNum);
|
|
137
123
|
if (shouldAddLineNumbers) return prependLineNumbers(text, startNum);
|
|
138
124
|
return text;
|
|
139
125
|
}
|
|
@@ -1045,14 +1031,12 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1045
1031
|
}
|
|
1046
1032
|
|
|
1047
1033
|
const collectedLines = streamResult.lines;
|
|
1048
|
-
const truncatedLineNumbers = new Set<number>();
|
|
1049
1034
|
if (!rawSelector && maxColumns > 0) {
|
|
1050
1035
|
for (let i = 0; i < collectedLines.length; i++) {
|
|
1051
1036
|
const { text, wasTruncated } = truncateLine(collectedLines[i], maxColumns);
|
|
1052
1037
|
if (wasTruncated) {
|
|
1053
1038
|
collectedLines[i] = text;
|
|
1054
1039
|
columnTruncated = maxColumns;
|
|
1055
|
-
truncatedLineNumbers.add(range.startLine + i);
|
|
1056
1040
|
}
|
|
1057
1041
|
}
|
|
1058
1042
|
}
|
|
@@ -1062,15 +1046,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1062
1046
|
}
|
|
1063
1047
|
|
|
1064
1048
|
const blockText = collectedLines.join("\n");
|
|
1065
|
-
blocks.push(
|
|
1066
|
-
formatTextWithMode(
|
|
1067
|
-
blockText,
|
|
1068
|
-
range.startLine,
|
|
1069
|
-
shouldAddHashLines,
|
|
1070
|
-
shouldAddLineNumbers,
|
|
1071
|
-
truncatedLineNumbers,
|
|
1072
|
-
),
|
|
1073
|
-
);
|
|
1049
|
+
blocks.push(formatTextWithMode(blockText, range.startLine, shouldAddHashLines, shouldAddLineNumbers));
|
|
1074
1050
|
}
|
|
1075
1051
|
|
|
1076
1052
|
let outputText = blocks.join("\n\n…\n\n");
|
|
@@ -1814,14 +1790,12 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1814
1790
|
// view — column truncation surfaces separately via `.limits()`.
|
|
1815
1791
|
const rawSelector = isRawSelector(parsed);
|
|
1816
1792
|
const maxColumns = resolveOutputMaxColumns(this.session.settings);
|
|
1817
|
-
const truncatedLineNumbers = new Set<number>();
|
|
1818
1793
|
if (!rawSelector && maxColumns > 0) {
|
|
1819
1794
|
for (let i = 0; i < collectedLines.length; i++) {
|
|
1820
1795
|
const { text, wasTruncated } = truncateLine(collectedLines[i], maxColumns);
|
|
1821
1796
|
if (wasTruncated) {
|
|
1822
1797
|
collectedLines[i] = text;
|
|
1823
1798
|
columnTruncated = maxColumns;
|
|
1824
|
-
truncatedLineNumbers.add(startLineDisplay + i);
|
|
1825
1799
|
}
|
|
1826
1800
|
}
|
|
1827
1801
|
}
|
|
@@ -1855,13 +1829,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1855
1829
|
let capturedDisplayContent: { text: string; startLine: number } | undefined;
|
|
1856
1830
|
const formatText = (text: string, startNum: number): string => {
|
|
1857
1831
|
capturedDisplayContent = { text, startLine: startNum };
|
|
1858
|
-
return formatTextWithMode(
|
|
1859
|
-
text,
|
|
1860
|
-
startNum,
|
|
1861
|
-
shouldAddHashLines,
|
|
1862
|
-
shouldAddLineNumbers,
|
|
1863
|
-
truncatedLineNumbers,
|
|
1864
|
-
);
|
|
1832
|
+
return formatTextWithMode(text, startNum, shouldAddHashLines, shouldAddLineNumbers);
|
|
1865
1833
|
};
|
|
1866
1834
|
|
|
1867
1835
|
let outputText: string;
|
package/src/utils/clipboard.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import type { ClipboardImage } from "@oh-my-pi/pi-natives";
|
|
3
3
|
import * as native from "@oh-my-pi/pi-natives";
|
|
4
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
5
|
|
|
5
6
|
function hasDisplay(): boolean {
|
|
6
7
|
return process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
@@ -80,7 +81,7 @@ if ($img -ne $null) {
|
|
|
80
81
|
}
|
|
81
82
|
`;
|
|
82
83
|
|
|
83
|
-
const POWERSHELL_TIMEOUT_MS =
|
|
84
|
+
const POWERSHELL_TIMEOUT_MS = 8000;
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
87
|
* Read a clipboard image through the Windows host's PowerShell.
|
|
@@ -104,6 +105,12 @@ async function readImageViaPowerShell(): Promise<ClipboardImage | null> {
|
|
|
104
105
|
try {
|
|
105
106
|
stdout = await new Response(proc.stdout).text();
|
|
106
107
|
await proc.exited;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// powershell.exe is a Windows process reached over WSL interop; if it
|
|
110
|
+
// doesn't reap cleanly, swallow the error so the dispatcher can fall
|
|
111
|
+
// through to the native bridge instead of throwing.
|
|
112
|
+
logger.warn("clipboard: powershell read failed", { error: String(err) });
|
|
113
|
+
return null;
|
|
107
114
|
} finally {
|
|
108
115
|
clearTimeout(timer);
|
|
109
116
|
}
|
|
@@ -136,8 +143,12 @@ export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
|
|
136
143
|
if (isWsl()) {
|
|
137
144
|
const image = await readImageViaPowerShell();
|
|
138
145
|
if (image) return image;
|
|
139
|
-
// Fall through: arboard may still succeed on a future WSLg release
|
|
140
|
-
|
|
146
|
+
// Fall through: arboard may still succeed on a future WSLg release —
|
|
147
|
+
// but only when we actually have a display server. Headless WSL has
|
|
148
|
+
// no display, so arboard would reject anyway.
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!hasDisplay()) {
|
|
141
152
|
return null;
|
|
142
153
|
}
|
|
143
154
|
|
|
@@ -23,16 +23,27 @@ export interface ResizedImage {
|
|
|
23
23
|
// binding constraint once images are downsized to 1568px (Anthropic's internal threshold).
|
|
24
24
|
const DEFAULT_MAX_BYTES = 500 * 1024;
|
|
25
25
|
|
|
26
|
-
const DEFAULT_OPTIONS: Required<ImageResizeOptions
|
|
26
|
+
const DEFAULT_OPTIONS: Required<Omit<ImageResizeOptions, "excludeWebP">> = {
|
|
27
27
|
// Anthropic's "internal recommended size" — Claude internally caps images at
|
|
28
28
|
// 1568px on the longest edge before vision processing.
|
|
29
29
|
maxWidth: 1568,
|
|
30
30
|
maxHeight: 1568,
|
|
31
31
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
32
32
|
jpegQuality: 80,
|
|
33
|
-
excludeWebP: Bun.env.OMP_NO_WEBP !== undefined,
|
|
34
33
|
};
|
|
35
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Read `OMP_NO_WEBP` per-call so runtime toggles take effect.
|
|
37
|
+
* Only `"1"` and `"true"` (case-insensitive) enable exclusion — an empty string
|
|
38
|
+
* or `"0"` MUST be treated as disabled.
|
|
39
|
+
*/
|
|
40
|
+
function isWebPExcluded(): boolean {
|
|
41
|
+
const raw = Bun.env.OMP_NO_WEBP;
|
|
42
|
+
if (raw === undefined) return false;
|
|
43
|
+
const v = raw.toLowerCase();
|
|
44
|
+
return v === "1" || v === "true";
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
/** Pick the smallest of N encoded buffers. */
|
|
37
48
|
function pickSmallest(...candidates: Array<{ buffer: Uint8Array; mimeType: string }>): {
|
|
38
49
|
buffer: Uint8Array;
|
|
@@ -62,7 +73,8 @@ Buffer.prototype.toBase64 = function (this: Buffer) {
|
|
|
62
73
|
* off the JS thread when the terminal (`.bytes()`) is awaited.
|
|
63
74
|
*/
|
|
64
75
|
export async function resizeImage(img: ImageContent, options?: ImageResizeOptions): Promise<ResizedImage> {
|
|
65
|
-
const
|
|
76
|
+
const excludeWebP = options?.excludeWebP ?? isWebPExcluded();
|
|
77
|
+
const opts = { ...DEFAULT_OPTIONS, ...options, excludeWebP };
|
|
66
78
|
const inputBuffer = Buffer.from(img.data, "base64");
|
|
67
79
|
|
|
68
80
|
try {
|
|
@@ -75,7 +87,12 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
|
|
75
87
|
// still get JPEG-compressed.
|
|
76
88
|
const originalSize = inputBuffer.length;
|
|
77
89
|
const comfortableSize = opts.maxBytes / 4;
|
|
78
|
-
if (
|
|
90
|
+
if (
|
|
91
|
+
originalWidth <= opts.maxWidth &&
|
|
92
|
+
originalHeight <= opts.maxHeight &&
|
|
93
|
+
originalSize <= comfortableSize &&
|
|
94
|
+
!(opts.excludeWebP && sourceMime === "image/webp")
|
|
95
|
+
) {
|
|
79
96
|
return {
|
|
80
97
|
buffer: inputBuffer,
|
|
81
98
|
mimeType: sourceMime,
|
|
@@ -251,7 +268,13 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
|
|
251
268
|
},
|
|
252
269
|
};
|
|
253
270
|
} catch {
|
|
254
|
-
//
|
|
271
|
+
// Bun.Image rejected the input — we cannot decode/re-encode it.
|
|
272
|
+
// When the caller demanded WebP exclusion AND the original is WebP,
|
|
273
|
+
// returning the original buffer would silently violate that contract,
|
|
274
|
+
// so surface an explicit error instead.
|
|
275
|
+
if (excludeWebP && (img.mimeType === "image/webp" || !img.mimeType)) {
|
|
276
|
+
throw new Error("resizeImage: failed to decode image and cannot honor excludeWebP for a WebP source");
|
|
277
|
+
}
|
|
255
278
|
return {
|
|
256
279
|
buffer: inputBuffer,
|
|
257
280
|
mimeType: img.mimeType,
|