@oh-my-pi/pi-coding-agent 15.9.3 → 15.9.67
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 +74 -1
- package/dist/types/cli/classify-install-target.d.ts +5 -1
- package/dist/types/config/keybindings.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +24 -5
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/modes/components/assistant-message.d.ts +16 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/components/error-banner.d.ts +11 -0
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/transcript-container.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/image-references.d.ts +17 -0
- package/dist/types/modes/interactive-mode.d.ts +8 -1
- package/dist/types/modes/types.d.ts +8 -1
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/session/blob-store.d.ts +12 -11
- package/dist/types/session/session-manager.d.ts +5 -3
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +16 -1
- package/dist/types/tool-discovery/mode.d.ts +8 -0
- package/dist/types/tools/archive-reader.d.ts +5 -1
- package/dist/types/tools/eval-render.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +25 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/hyperlink.d.ts +12 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/search/render.d.ts +1 -2
- package/package.json +9 -9
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/classify-install-target.ts +31 -5
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/plugin-cli.ts +45 -0
- package/src/cli/web-search-cli.ts +0 -1
- package/src/config/keybindings.ts +58 -1
- package/src/config/model-registry.ts +54 -4
- package/src/config/settings-schema.ts +25 -5
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +7 -7
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +100 -27
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/__tests__/shared-executors.test.ts +2 -2
- package/src/eval/agent-bridge.ts +4 -5
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/eval/py/tool-bridge.ts +43 -5
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +31 -2
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +9 -8
- package/src/lsp/client.ts +80 -2
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/main.ts +8 -2
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +44 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +14 -2
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/tool-execution.ts +71 -13
- package/src/modes/components/transcript-container.ts +93 -32
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/components/user-message.ts +9 -2
- package/src/modes/controllers/command-controller.ts +0 -116
- package/src/modes/controllers/event-controller.ts +67 -12
- package/src/modes/controllers/input-controller.ts +33 -1
- package/src/modes/controllers/selector-controller.ts +38 -1
- package/src/modes/image-references.ts +111 -0
- package/src/modes/interactive-mode.ts +52 -17
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +11 -2
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/ui-helpers.ts +23 -2
- package/src/prompts/ci-green-request.md +5 -3
- package/src/prompts/system/project-prompt.md +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +17 -9
- package/src/session/agent-session.ts +43 -14
- package/src/session/blob-store.ts +96 -9
- package/src/session/session-manager.ts +19 -10
- package/src/slash-commands/builtin-registry.ts +3 -11
- package/src/system-prompt.ts +4 -0
- package/src/task/render.ts +38 -11
- package/src/tiny/title-client.ts +7 -1
- package/src/tool-discovery/mode.ts +24 -0
- package/src/tools/archive-reader.ts +339 -31
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-render.ts +24 -9
- package/src/tools/eval.ts +14 -19
- package/src/tools/fetch.ts +34 -14
- package/src/tools/gh.ts +65 -11
- package/src/tools/index.ts +6 -8
- package/src/tools/read.ts +65 -19
- package/src/tools/render-utils.ts +46 -0
- package/src/tools/search-tool-bm25.ts +4 -6
- package/src/tools/search.ts +60 -11
- package/src/tools/ssh.ts +21 -8
- package/src/tools/write.ts +17 -8
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/hyperlink.ts +42 -7
- package/src/tui/output-block.ts +14 -0
- package/src/web/search/index.ts +2 -2
- package/src/web/search/render.ts +23 -55
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Settings } from "../config/settings";
|
|
2
|
+
import type { SettingValue } from "../config/settings-schema";
|
|
3
|
+
|
|
4
|
+
export const TOOL_DISCOVERY_AUTO_THRESHOLD = 40;
|
|
5
|
+
export const TOOL_DISCOVERY_SEARCH_TOOL_NAME = "search_tool_bm25";
|
|
6
|
+
|
|
7
|
+
export type ToolDiscoveryModeSetting = SettingValue<"tools.discoveryMode">;
|
|
8
|
+
export type EffectiveToolDiscoveryMode = Exclude<ToolDiscoveryModeSetting, "auto">;
|
|
9
|
+
|
|
10
|
+
export function countToolsForAutoDiscovery(toolNames: Iterable<string>): number {
|
|
11
|
+
let count = 0;
|
|
12
|
+
for (const name of toolNames) {
|
|
13
|
+
if (name !== TOOL_DISCOVERY_SEARCH_TOOL_NAME) count++;
|
|
14
|
+
}
|
|
15
|
+
return count;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveEffectiveToolDiscoveryMode(settings: Settings, toolCount: number): EffectiveToolDiscoveryMode {
|
|
19
|
+
const configuredMode = settings.get("tools.discoveryMode");
|
|
20
|
+
if (configuredMode === "all" || configuredMode === "mcp-only") return configuredMode;
|
|
21
|
+
if (settings.get("mcp.discoveryMode")) return "mcp-only";
|
|
22
|
+
if (configuredMode === "auto" && toolCount > TOOL_DISCOVERY_AUTO_THRESHOLD) return "mcp-only";
|
|
23
|
+
return "off";
|
|
24
|
+
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inflateSync, strFromU8 } from "fflate";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
async function loadFflate(): Promise<typeof import("fflate")> {
|
|
5
|
-
if (!fflateModulePromise) fflateModulePromise = import("fflate");
|
|
6
|
-
return fflateModulePromise;
|
|
7
|
-
}
|
|
3
|
+
import { ToolError } from "./tool-errors";
|
|
8
4
|
|
|
9
5
|
export type ArchiveFormat = "zip" | "tar" | "tar.gz";
|
|
10
6
|
|
|
@@ -35,7 +31,11 @@ interface TarStorage {
|
|
|
35
31
|
|
|
36
32
|
interface ZipStorage {
|
|
37
33
|
type: "zip";
|
|
38
|
-
|
|
34
|
+
archivePath: string;
|
|
35
|
+
compressedSize: number;
|
|
36
|
+
compression: number;
|
|
37
|
+
flags: number;
|
|
38
|
+
localHeaderOffset: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
type EntryStorage = TarStorage | ZipStorage;
|
|
@@ -123,6 +123,321 @@ function getArchiveFormatFromPath(filePath: string): ArchiveFormat | undefined {
|
|
|
123
123
|
return undefined;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
const ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
127
|
+
const ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014b50;
|
|
128
|
+
const ZIP64_EOCD_SIGNATURE = 0x06064b50;
|
|
129
|
+
const ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50;
|
|
130
|
+
const ZIP_EOCD_SIGNATURE = 0x06054b50;
|
|
131
|
+
const ZIP_EOCD_MIN_LENGTH = 22;
|
|
132
|
+
const ZIP_EOCD_MAX_COMMENT_LENGTH = 0xffff;
|
|
133
|
+
const ZIP64_EOCD_LOCATOR_LENGTH = 20;
|
|
134
|
+
const ZIP_STORED_COMPRESSION = 0;
|
|
135
|
+
const ZIP_DEFLATE_COMPRESSION = 8;
|
|
136
|
+
const ZIP_UTF8_FLAG = 0x0800;
|
|
137
|
+
const ZIP_ENCRYPTED_FLAG = 0x0001;
|
|
138
|
+
const ZIP_UINT16_MAX = 0xffff;
|
|
139
|
+
const ZIP_UINT32_MAX = 0xffffffff;
|
|
140
|
+
const ZIP_UINT32_RANGE = 0x100000000;
|
|
141
|
+
|
|
142
|
+
interface ZipCentralDirectoryInfo {
|
|
143
|
+
entries: number;
|
|
144
|
+
offset: number;
|
|
145
|
+
size: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface Zip64EntryValues {
|
|
149
|
+
compressedSize: number;
|
|
150
|
+
uncompressedSize: number;
|
|
151
|
+
localHeaderOffset: number;
|
|
152
|
+
diskStart: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface Zip64EntryPlaceholders {
|
|
156
|
+
compressedSize: boolean;
|
|
157
|
+
uncompressedSize: boolean;
|
|
158
|
+
localHeaderOffset: boolean;
|
|
159
|
+
diskStart: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function readUInt16LE(bytes: Uint8Array, offset: number): number {
|
|
163
|
+
return bytes[offset]! | (bytes[offset + 1]! << 8);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readUInt32LE(bytes: Uint8Array, offset: number): number {
|
|
167
|
+
return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readUInt64LEAsNumber(bytes: Uint8Array, offset: number): number {
|
|
171
|
+
const value = readUInt32LE(bytes, offset) + readUInt32LE(bytes, offset + 4) * ZIP_UINT32_RANGE;
|
|
172
|
+
if (!Number.isSafeInteger(value)) {
|
|
173
|
+
throw new ToolError("ZIP archive uses offsets or sizes too large to read safely");
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function readZipRange(filePath: string, start: number, end: number): Promise<Uint8Array> {
|
|
179
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {
|
|
180
|
+
throw new ToolError("Invalid ZIP archive range");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const bytes = await Bun.file(filePath).slice(start, end).bytes();
|
|
184
|
+
if (bytes.byteLength !== end - start) {
|
|
185
|
+
throw new ToolError("Invalid ZIP archive: truncated data");
|
|
186
|
+
}
|
|
187
|
+
return bytes;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function findEndOfCentralDirectory(tail: Uint8Array): number {
|
|
191
|
+
for (let offset = tail.byteLength - ZIP_EOCD_MIN_LENGTH; offset >= 0; offset--) {
|
|
192
|
+
if (readUInt32LE(tail, offset) !== ZIP_EOCD_SIGNATURE) continue;
|
|
193
|
+
const commentLength = readUInt16LE(tail, offset + 20);
|
|
194
|
+
if (offset + ZIP_EOCD_MIN_LENGTH + commentLength === tail.byteLength) return offset;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
throw new ToolError("Invalid ZIP archive: missing end of central directory");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function readZip64CentralDirectoryInfo(
|
|
201
|
+
filePath: string,
|
|
202
|
+
tail: Uint8Array,
|
|
203
|
+
tailStart: number,
|
|
204
|
+
eocdOffset: number,
|
|
205
|
+
): Promise<ZipCentralDirectoryInfo | undefined> {
|
|
206
|
+
const locatorOffset = eocdOffset - ZIP64_EOCD_LOCATOR_LENGTH;
|
|
207
|
+
if (locatorOffset < 0) return undefined;
|
|
208
|
+
|
|
209
|
+
const locator =
|
|
210
|
+
locatorOffset >= tailStart
|
|
211
|
+
? tail.subarray(locatorOffset - tailStart, locatorOffset - tailStart + ZIP64_EOCD_LOCATOR_LENGTH)
|
|
212
|
+
: await readZipRange(filePath, locatorOffset, eocdOffset);
|
|
213
|
+
if (readUInt32LE(locator, 0) !== ZIP64_EOCD_LOCATOR_SIGNATURE) return undefined;
|
|
214
|
+
|
|
215
|
+
const zip64EocdDisk = readUInt32LE(locator, 4);
|
|
216
|
+
const zip64EocdOffset = readUInt64LEAsNumber(locator, 8);
|
|
217
|
+
const totalDisks = readUInt32LE(locator, 16);
|
|
218
|
+
if (zip64EocdDisk !== 0 || totalDisks > 1) {
|
|
219
|
+
throw new ToolError("Multi-disk ZIP archives are not supported");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const record = await readZipRange(filePath, zip64EocdOffset, zip64EocdOffset + 56);
|
|
223
|
+
if (readUInt32LE(record, 0) !== ZIP64_EOCD_SIGNATURE) {
|
|
224
|
+
throw new ToolError("Invalid ZIP archive: missing ZIP64 end of central directory");
|
|
225
|
+
}
|
|
226
|
+
if (readUInt32LE(record, 16) !== 0 || readUInt32LE(record, 20) !== 0) {
|
|
227
|
+
throw new ToolError("Multi-disk ZIP archives are not supported");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
entries: readUInt64LEAsNumber(record, 32),
|
|
232
|
+
size: readUInt64LEAsNumber(record, 40),
|
|
233
|
+
offset: readUInt64LEAsNumber(record, 48),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function readZipCentralDirectoryInfo(filePath: string, fileSize: number): Promise<ZipCentralDirectoryInfo> {
|
|
238
|
+
if (fileSize < ZIP_EOCD_MIN_LENGTH) {
|
|
239
|
+
throw new ToolError("Invalid ZIP archive: missing end of central directory");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const tailLength = Math.min(fileSize, ZIP_EOCD_MIN_LENGTH + ZIP_EOCD_MAX_COMMENT_LENGTH);
|
|
243
|
+
const tailStart = fileSize - tailLength;
|
|
244
|
+
const tail = await readZipRange(filePath, tailStart, fileSize);
|
|
245
|
+
const eocdIndex = findEndOfCentralDirectory(tail);
|
|
246
|
+
const eocdOffset = tailStart + eocdIndex;
|
|
247
|
+
|
|
248
|
+
if (readUInt16LE(tail, eocdIndex + 4) !== 0 || readUInt16LE(tail, eocdIndex + 6) !== 0) {
|
|
249
|
+
throw new ToolError("Multi-disk ZIP archives are not supported");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let entries = readUInt16LE(tail, eocdIndex + 10);
|
|
253
|
+
let size = readUInt32LE(tail, eocdIndex + 12);
|
|
254
|
+
let offset = readUInt32LE(tail, eocdIndex + 16);
|
|
255
|
+
const needsZip64 = entries === ZIP_UINT16_MAX || size === ZIP_UINT32_MAX || offset === ZIP_UINT32_MAX;
|
|
256
|
+
const zip64Info = await readZip64CentralDirectoryInfo(filePath, tail, tailStart, eocdOffset);
|
|
257
|
+
if (zip64Info) {
|
|
258
|
+
({ entries, size, offset } = zip64Info);
|
|
259
|
+
} else if (needsZip64) {
|
|
260
|
+
throw new ToolError("Invalid ZIP archive: missing ZIP64 central directory metadata");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (offset + size > fileSize) {
|
|
264
|
+
throw new ToolError("Invalid ZIP archive: central directory exceeds file size");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { entries, offset, size };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function readZip64EntryValues(
|
|
271
|
+
extra: Uint8Array,
|
|
272
|
+
placeholders: Zip64EntryPlaceholders,
|
|
273
|
+
current: Zip64EntryValues,
|
|
274
|
+
): Zip64EntryValues {
|
|
275
|
+
if (
|
|
276
|
+
!placeholders.compressedSize &&
|
|
277
|
+
!placeholders.uncompressedSize &&
|
|
278
|
+
!placeholders.localHeaderOffset &&
|
|
279
|
+
!placeholders.diskStart
|
|
280
|
+
) {
|
|
281
|
+
return current;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let offset = 0;
|
|
285
|
+
while (offset + 4 <= extra.byteLength) {
|
|
286
|
+
const headerId = readUInt16LE(extra, offset);
|
|
287
|
+
const dataSize = readUInt16LE(extra, offset + 2);
|
|
288
|
+
const dataStart = offset + 4;
|
|
289
|
+
const dataEnd = dataStart + dataSize;
|
|
290
|
+
if (dataEnd > extra.byteLength) {
|
|
291
|
+
throw new ToolError("Invalid ZIP archive: malformed extra field");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (headerId === 0x0001) {
|
|
295
|
+
let cursor = dataStart;
|
|
296
|
+
let uncompressedSize = current.uncompressedSize;
|
|
297
|
+
let compressedSize = current.compressedSize;
|
|
298
|
+
let localHeaderOffset = current.localHeaderOffset;
|
|
299
|
+
let diskStart = current.diskStart;
|
|
300
|
+
|
|
301
|
+
if (placeholders.uncompressedSize) {
|
|
302
|
+
if (cursor + 8 > dataEnd) throw new ToolError("Invalid ZIP archive: malformed ZIP64 extra field");
|
|
303
|
+
uncompressedSize = readUInt64LEAsNumber(extra, cursor);
|
|
304
|
+
cursor += 8;
|
|
305
|
+
}
|
|
306
|
+
if (placeholders.compressedSize) {
|
|
307
|
+
if (cursor + 8 > dataEnd) throw new ToolError("Invalid ZIP archive: malformed ZIP64 extra field");
|
|
308
|
+
compressedSize = readUInt64LEAsNumber(extra, cursor);
|
|
309
|
+
cursor += 8;
|
|
310
|
+
}
|
|
311
|
+
if (placeholders.localHeaderOffset) {
|
|
312
|
+
if (cursor + 8 > dataEnd) throw new ToolError("Invalid ZIP archive: malformed ZIP64 extra field");
|
|
313
|
+
localHeaderOffset = readUInt64LEAsNumber(extra, cursor);
|
|
314
|
+
cursor += 8;
|
|
315
|
+
}
|
|
316
|
+
if (placeholders.diskStart) {
|
|
317
|
+
if (cursor + 4 > dataEnd) throw new ToolError("Invalid ZIP archive: malformed ZIP64 extra field");
|
|
318
|
+
diskStart = readUInt32LE(extra, cursor);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { compressedSize, uncompressedSize, localHeaderOffset, diskStart };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
offset = dataEnd;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
throw new ToolError("Invalid ZIP archive: missing ZIP64 extra field");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parseZipCentralDirectory(
|
|
331
|
+
filePath: string,
|
|
332
|
+
centralDirectory: Uint8Array,
|
|
333
|
+
expectedEntries: number,
|
|
334
|
+
): ArchiveIndexEntry[] {
|
|
335
|
+
const entries: ArchiveIndexEntry[] = [];
|
|
336
|
+
let offset = 0;
|
|
337
|
+
|
|
338
|
+
for (let index = 0; index < expectedEntries; index++) {
|
|
339
|
+
if (offset + 46 > centralDirectory.byteLength) {
|
|
340
|
+
throw new ToolError("Invalid ZIP archive: truncated central directory");
|
|
341
|
+
}
|
|
342
|
+
if (readUInt32LE(centralDirectory, offset) !== ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE) {
|
|
343
|
+
throw new ToolError("Invalid ZIP archive: malformed central directory");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const flags = readUInt16LE(centralDirectory, offset + 8);
|
|
347
|
+
const compression = readUInt16LE(centralDirectory, offset + 10);
|
|
348
|
+
const compressedSizeRaw = readUInt32LE(centralDirectory, offset + 20);
|
|
349
|
+
const uncompressedSizeRaw = readUInt32LE(centralDirectory, offset + 24);
|
|
350
|
+
const fileNameLength = readUInt16LE(centralDirectory, offset + 28);
|
|
351
|
+
const extraLength = readUInt16LE(centralDirectory, offset + 30);
|
|
352
|
+
const commentLength = readUInt16LE(centralDirectory, offset + 32);
|
|
353
|
+
const diskStartRaw = readUInt16LE(centralDirectory, offset + 34);
|
|
354
|
+
const localHeaderOffsetRaw = readUInt32LE(centralDirectory, offset + 42);
|
|
355
|
+
const nameStart = offset + 46;
|
|
356
|
+
const extraStart = nameStart + fileNameLength;
|
|
357
|
+
const entryEnd = extraStart + extraLength + commentLength;
|
|
358
|
+
if (entryEnd > centralDirectory.byteLength) {
|
|
359
|
+
throw new ToolError("Invalid ZIP archive: truncated central directory entry");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const rawPath = strFromU8(centralDirectory.subarray(nameStart, extraStart), (flags & ZIP_UTF8_FLAG) === 0);
|
|
363
|
+
const normalizedPath = normalizeArchiveEntryPath(rawPath);
|
|
364
|
+
if (normalizedPath) {
|
|
365
|
+
const values = readZip64EntryValues(
|
|
366
|
+
centralDirectory.subarray(extraStart, extraStart + extraLength),
|
|
367
|
+
{
|
|
368
|
+
compressedSize: compressedSizeRaw === ZIP_UINT32_MAX,
|
|
369
|
+
uncompressedSize: uncompressedSizeRaw === ZIP_UINT32_MAX,
|
|
370
|
+
localHeaderOffset: localHeaderOffsetRaw === ZIP_UINT32_MAX,
|
|
371
|
+
diskStart: diskStartRaw === ZIP_UINT16_MAX,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
compressedSize: compressedSizeRaw,
|
|
375
|
+
uncompressedSize: uncompressedSizeRaw,
|
|
376
|
+
localHeaderOffset: localHeaderOffsetRaw,
|
|
377
|
+
diskStart: diskStartRaw,
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
if (values.diskStart !== 0) {
|
|
381
|
+
throw new ToolError("Multi-disk ZIP archives are not supported");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const isDirectory = isArchiveDirectoryName(rawPath);
|
|
385
|
+
entries.push({
|
|
386
|
+
path: normalizedPath,
|
|
387
|
+
isDirectory,
|
|
388
|
+
size: isDirectory ? 0 : values.uncompressedSize,
|
|
389
|
+
storage: isDirectory
|
|
390
|
+
? undefined
|
|
391
|
+
: {
|
|
392
|
+
type: "zip",
|
|
393
|
+
archivePath: filePath,
|
|
394
|
+
compressedSize: values.compressedSize,
|
|
395
|
+
compression,
|
|
396
|
+
flags,
|
|
397
|
+
localHeaderOffset: values.localHeaderOffset,
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
offset = entryEnd;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return entries;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function readZipFileBytes(storage: ZipStorage, uncompressedSize: number): Promise<Uint8Array> {
|
|
409
|
+
if ((storage.flags & ZIP_ENCRYPTED_FLAG) !== 0) {
|
|
410
|
+
throw new ToolError("Encrypted ZIP entries are not supported");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const localHeader = await readZipRange(
|
|
414
|
+
storage.archivePath,
|
|
415
|
+
storage.localHeaderOffset,
|
|
416
|
+
storage.localHeaderOffset + 30,
|
|
417
|
+
);
|
|
418
|
+
if (readUInt32LE(localHeader, 0) !== ZIP_LOCAL_FILE_HEADER_SIGNATURE) {
|
|
419
|
+
throw new ToolError("Invalid ZIP archive: malformed local file header");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const fileNameLength = readUInt16LE(localHeader, 26);
|
|
423
|
+
const extraLength = readUInt16LE(localHeader, 28);
|
|
424
|
+
const dataStart = storage.localHeaderOffset + 30 + fileNameLength + extraLength;
|
|
425
|
+
const compressedBytes = await readZipRange(storage.archivePath, dataStart, dataStart + storage.compressedSize);
|
|
426
|
+
|
|
427
|
+
if (storage.compression === ZIP_STORED_COMPRESSION) {
|
|
428
|
+
return compressedBytes;
|
|
429
|
+
}
|
|
430
|
+
if (storage.compression !== ZIP_DEFLATE_COMPRESSION) {
|
|
431
|
+
throw new ToolError(`Unsupported ZIP compression method: ${storage.compression}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
return inflateSync(compressedBytes, { out: new Uint8Array(uncompressedSize) });
|
|
436
|
+
} catch (error) {
|
|
437
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
126
441
|
async function readTarEntries(bytes: Uint8Array): Promise<ArchiveIndexEntry[]> {
|
|
127
442
|
let archive: Bun.Archive;
|
|
128
443
|
try {
|
|
@@ -155,29 +470,19 @@ async function readTarEntries(bytes: Uint8Array): Promise<ArchiveIndexEntry[]> {
|
|
|
155
470
|
return entries;
|
|
156
471
|
}
|
|
157
472
|
|
|
158
|
-
async function readZipEntries(
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
files = unzipSync(bytes);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
473
|
+
async function readZipEntries(filePath: string): Promise<ArchiveIndexEntry[]> {
|
|
474
|
+
const fileSize = Bun.file(filePath).size;
|
|
475
|
+
if (!Number.isSafeInteger(fileSize)) {
|
|
476
|
+
throw new ToolError("ZIP archive is too large to read safely");
|
|
165
477
|
}
|
|
166
478
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
isDirectory,
|
|
175
|
-
size: isDirectory ? 0 : fileBytes.byteLength,
|
|
176
|
-
storage: isDirectory ? undefined : { type: "zip", bytes: fileBytes },
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return entries;
|
|
479
|
+
const directoryInfo = await readZipCentralDirectoryInfo(filePath, fileSize);
|
|
480
|
+
const centralDirectory = await readZipRange(
|
|
481
|
+
filePath,
|
|
482
|
+
directoryInfo.offset,
|
|
483
|
+
directoryInfo.offset + directoryInfo.size,
|
|
484
|
+
);
|
|
485
|
+
return parseZipCentralDirectory(filePath, centralDirectory, directoryInfo.entries);
|
|
181
486
|
}
|
|
182
487
|
|
|
183
488
|
export function parseArchivePathCandidates(filePath: string): ArchivePathCandidate[] {
|
|
@@ -297,7 +602,10 @@ export class ArchiveReader {
|
|
|
297
602
|
throw new ToolError(`Archive file '${normalizedPath}' has no readable storage`);
|
|
298
603
|
}
|
|
299
604
|
|
|
300
|
-
const bytes =
|
|
605
|
+
const bytes =
|
|
606
|
+
entry.storage.type === "tar"
|
|
607
|
+
? await entry.storage.file.bytes()
|
|
608
|
+
: await readZipFileBytes(entry.storage, entry.size);
|
|
301
609
|
|
|
302
610
|
return {
|
|
303
611
|
path: entry.path,
|
|
@@ -315,7 +623,7 @@ export async function openArchive(filePath: string): Promise<ArchiveReader> {
|
|
|
315
623
|
throw new ToolError(`Unsupported archive format: ${filePath}`);
|
|
316
624
|
}
|
|
317
625
|
|
|
318
|
-
const
|
|
319
|
-
|
|
626
|
+
const entries =
|
|
627
|
+
format === "zip" ? await readZipEntries(filePath) : await readTarEntries(await Bun.file(filePath).bytes());
|
|
320
628
|
return new ArchiveReader(format, entries);
|
|
321
629
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -20,7 +20,7 @@ import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
|
20
20
|
import type { ClientBridgeTerminalExitStatus, ClientBridgeTerminalOutput } from "../session/client-bridge";
|
|
21
21
|
import { DEFAULT_MAX_BYTES, streamTailUpdates, TailBuffer } from "../session/streaming-output";
|
|
22
22
|
import { renderStatusLine } from "../tui";
|
|
23
|
-
import { CachedOutputBlock } from "../tui/output-block";
|
|
23
|
+
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
24
24
|
import { getSixelLineMask } from "../utils/sixel";
|
|
25
25
|
import type { ToolSession } from ".";
|
|
26
26
|
import { truncateForPrompt } from "./approval";
|
|
@@ -31,7 +31,7 @@ import { canUseInteractiveBashPty } from "./bash-pty-selection";
|
|
|
31
31
|
import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-skill-urls";
|
|
32
32
|
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
33
33
|
import { resolveToCwd } from "./path-utils";
|
|
34
|
-
import { formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
34
|
+
import { capPreviewLines, formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
35
35
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
36
36
|
import { toolResult } from "./tool-result";
|
|
37
37
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -1083,16 +1083,22 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1083
1083
|
const cmdLines = formatBashCommandLines(renderArgs, uiTheme);
|
|
1084
1084
|
const header = renderStatusLine({ icon: "pending", title }, uiTheme);
|
|
1085
1085
|
const outputBlock = new CachedOutputBlock();
|
|
1086
|
-
return {
|
|
1086
|
+
return markFramedBlockComponent({
|
|
1087
1087
|
render: (width: number): string[] =>
|
|
1088
1088
|
outputBlock.render(
|
|
1089
|
-
{
|
|
1089
|
+
{
|
|
1090
|
+
header,
|
|
1091
|
+
state: "pending",
|
|
1092
|
+
sections: [{ lines: capPreviewLines(cmdLines, uiTheme, { expanded: options.expanded }) }],
|
|
1093
|
+
width,
|
|
1094
|
+
animate: true,
|
|
1095
|
+
},
|
|
1090
1096
|
uiTheme,
|
|
1091
1097
|
),
|
|
1092
1098
|
invalidate: () => {
|
|
1093
1099
|
outputBlock.invalidate();
|
|
1094
1100
|
},
|
|
1095
|
-
};
|
|
1101
|
+
});
|
|
1096
1102
|
},
|
|
1097
1103
|
|
|
1098
1104
|
renderResult(
|
|
@@ -1114,7 +1120,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1114
1120
|
const details = result.details;
|
|
1115
1121
|
const outputBlock = new CachedOutputBlock();
|
|
1116
1122
|
|
|
1117
|
-
return {
|
|
1123
|
+
return markFramedBlockComponent({
|
|
1118
1124
|
render: (width: number): string[] => {
|
|
1119
1125
|
// REACTIVE: read mutable options at render time
|
|
1120
1126
|
const { renderContext } = options;
|
|
@@ -1201,7 +1207,11 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1201
1207
|
header,
|
|
1202
1208
|
state: options.isPartial ? "pending" : isError ? "error" : "success",
|
|
1203
1209
|
sections: [
|
|
1204
|
-
{
|
|
1210
|
+
{
|
|
1211
|
+
lines: options.isPartial
|
|
1212
|
+
? capPreviewLines(cmdLines ?? [], uiTheme, { expanded })
|
|
1213
|
+
: (cmdLines ?? []),
|
|
1214
|
+
},
|
|
1205
1215
|
{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
|
|
1206
1216
|
],
|
|
1207
1217
|
width,
|
|
@@ -1213,7 +1223,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1213
1223
|
invalidate: () => {
|
|
1214
1224
|
outputBlock.invalidate();
|
|
1215
1225
|
},
|
|
1216
|
-
};
|
|
1226
|
+
});
|
|
1217
1227
|
},
|
|
1218
1228
|
mergeCallAndResult: true,
|
|
1219
1229
|
inline: true,
|
|
@@ -9,7 +9,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
9
9
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
11
11
|
import type { Theme } from "../../modes/theme/theme";
|
|
12
|
-
import { Hasher, renderCodeCell, renderStatusLine } from "../../tui";
|
|
12
|
+
import { Hasher, isFramedBlockComponent, markFramedBlockComponent, renderCodeCell, renderStatusLine } from "../../tui";
|
|
13
13
|
import type { BrowserToolDetails } from "../browser";
|
|
14
14
|
import { formatStyledTruncationWarning, stripOutputNotice } from "../output-meta";
|
|
15
15
|
import { replaceTabs, shortenPath } from "../render-utils";
|
|
@@ -65,13 +65,14 @@ function dropTrailingBlankLines(text: string): string {
|
|
|
65
65
|
|
|
66
66
|
function appendLine(component: Component, line: string | undefined): Component {
|
|
67
67
|
if (!line) return component;
|
|
68
|
-
|
|
68
|
+
const wrapped = {
|
|
69
69
|
render: (width: number): string[] => {
|
|
70
70
|
const base = component.render(width);
|
|
71
71
|
return [...base, line];
|
|
72
72
|
},
|
|
73
73
|
invalidate: () => component.invalidate?.(),
|
|
74
74
|
};
|
|
75
|
+
return isFramedBlockComponent(component) ? markFramedBlockComponent(wrapped) : wrapped;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
function renderRunCell(
|
|
@@ -93,7 +94,7 @@ function renderRunCell(
|
|
|
93
94
|
const title = titleParts.join(" · ");
|
|
94
95
|
|
|
95
96
|
let cached: { key: bigint; width: number; lines: string[] } | undefined;
|
|
96
|
-
return {
|
|
97
|
+
return markFramedBlockComponent({
|
|
97
98
|
render: (width: number): string[] => {
|
|
98
99
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
99
100
|
const previewLines = options.renderContext?.previewLines ?? BROWSER_DEFAULT_PREVIEW_LINES;
|
|
@@ -131,7 +132,7 @@ function renderRunCell(
|
|
|
131
132
|
invalidate: () => {
|
|
132
133
|
cached = undefined;
|
|
133
134
|
},
|
|
134
|
-
};
|
|
135
|
+
});
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
function renderOpenOrCloseLine(
|
package/src/tools/debug.ts
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import type { Theme } from "../modes/theme/theme";
|
|
37
37
|
import debugDescription from "../prompts/tools/debug.md" with { type: "text" };
|
|
38
38
|
import { renderStatusLine } from "../tui";
|
|
39
|
-
import { CachedOutputBlock } from "../tui/output-block";
|
|
39
|
+
import { CachedOutputBlock, markFramedBlockComponent } from "../tui/output-block";
|
|
40
40
|
import type { ToolSession } from ".";
|
|
41
41
|
import { truncateForPrompt } from "./approval";
|
|
42
42
|
import type { OutputMeta } from "./output-meta";
|
|
@@ -581,7 +581,7 @@ export const debugToolRenderer = {
|
|
|
581
581
|
args?: DebugRenderArgs,
|
|
582
582
|
): Component {
|
|
583
583
|
const outputBlock = new CachedOutputBlock();
|
|
584
|
-
return {
|
|
584
|
+
return markFramedBlockComponent({
|
|
585
585
|
render(width: number): string[] {
|
|
586
586
|
const action = (args?.action ?? result.details?.action ?? "debug").replaceAll("_", " ");
|
|
587
587
|
const status = options.isPartial ? "running" : result.isError ? "error" : "success";
|
|
@@ -620,7 +620,7 @@ export const debugToolRenderer = {
|
|
|
620
620
|
invalidate() {
|
|
621
621
|
outputBlock.invalidate();
|
|
622
622
|
},
|
|
623
|
-
};
|
|
623
|
+
});
|
|
624
624
|
},
|
|
625
625
|
mergeCallAndResult: true,
|
|
626
626
|
inline: true,
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { formatContextUsage } from "../modes/components/status-line/context-thre
|
|
|
18
18
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
19
19
|
import { shimmerEnabled } from "../modes/theme/shimmer";
|
|
20
20
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
21
|
-
import { borderShimmerTick, renderCodeCell } from "../tui";
|
|
21
|
+
import { borderShimmerTick, markFramedBlockComponent, renderCodeCell } from "../tui";
|
|
22
22
|
import {
|
|
23
23
|
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
24
24
|
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
@@ -39,8 +39,15 @@ import {
|
|
|
39
39
|
truncateToWidth,
|
|
40
40
|
wrapBrackets,
|
|
41
41
|
} from "./render-utils";
|
|
42
|
-
|
|
43
42
|
export const EVAL_DEFAULT_PREVIEW_LINES = 10;
|
|
43
|
+
/**
|
|
44
|
+
* Rows of source kept in the *pending* eval preview. The window follows the
|
|
45
|
+
* streaming edge (newest lines pinned to the bottom) so you can watch the code
|
|
46
|
+
* being written, while staying bounded — a volatile tool block taller than the
|
|
47
|
+
* viewport would otherwise strand its scrolled-off head out of native scrollback
|
|
48
|
+
* on ED3-risk terminals. Matches the streaming windows used by edit/write.
|
|
49
|
+
*/
|
|
50
|
+
export const EVAL_STREAMING_PREVIEW_LINES = 12;
|
|
44
51
|
|
|
45
52
|
function languageForHighlighter(language: EvalLanguage | undefined): "python" | "javascript" {
|
|
46
53
|
return language === "js" ? "javascript" : "python";
|
|
@@ -490,10 +497,10 @@ export const evalToolRenderer = {
|
|
|
490
497
|
|
|
491
498
|
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
492
499
|
|
|
493
|
-
return {
|
|
500
|
+
return markFramedBlockComponent({
|
|
494
501
|
render: (width: number): string[] => {
|
|
495
502
|
const animate = options.isPartial && shimmerEnabled();
|
|
496
|
-
const key = `${animate ? borderShimmerTick() : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
503
|
+
const key = `${animate ? borderShimmerTick() : 0}|${options.expanded ? 1 : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
497
504
|
if (cached && cached.key === key && cached.width === width) {
|
|
498
505
|
return cached.result;
|
|
499
506
|
}
|
|
@@ -510,8 +517,16 @@ export const evalToolRenderer = {
|
|
|
510
517
|
title: cell.title,
|
|
511
518
|
status: "pending",
|
|
512
519
|
width,
|
|
513
|
-
codeMaxLines:
|
|
514
|
-
|
|
520
|
+
codeMaxLines: EVAL_STREAMING_PREVIEW_LINES,
|
|
521
|
+
// Follow the streaming edge with a bounded tail window so the
|
|
522
|
+
// newest source stays visible as it is written, instead of
|
|
523
|
+
// rendering every line of a >100-line `code` — which would
|
|
524
|
+
// overflow the viewport and, because a tool block is volatile
|
|
525
|
+
// (it collapses to a capped result), strand its scrolled-off head
|
|
526
|
+
// out of native scrollback, cutting the box top. `Ctrl+O` lifts
|
|
527
|
+
// the window via `expanded` for a deliberate full view.
|
|
528
|
+
codeTail: true,
|
|
529
|
+
expanded: options.expanded,
|
|
515
530
|
animate,
|
|
516
531
|
},
|
|
517
532
|
uiTheme,
|
|
@@ -527,7 +542,7 @@ export const evalToolRenderer = {
|
|
|
527
542
|
invalidate: () => {
|
|
528
543
|
cached = undefined;
|
|
529
544
|
},
|
|
530
|
-
};
|
|
545
|
+
});
|
|
531
546
|
},
|
|
532
547
|
|
|
533
548
|
renderResult(
|
|
@@ -571,7 +586,7 @@ export const evalToolRenderer = {
|
|
|
571
586
|
if (cellResults && cellResults.length > 0) {
|
|
572
587
|
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
573
588
|
|
|
574
|
-
return {
|
|
589
|
+
return markFramedBlockComponent({
|
|
575
590
|
render: (width: number): string[] => {
|
|
576
591
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
577
592
|
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
@@ -649,7 +664,7 @@ export const evalToolRenderer = {
|
|
|
649
664
|
invalidate: () => {
|
|
650
665
|
cached = undefined;
|
|
651
666
|
},
|
|
652
|
-
};
|
|
667
|
+
});
|
|
653
668
|
}
|
|
654
669
|
|
|
655
670
|
const displayOutput = output;
|