@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.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 +316 -1
- package/package.json +86 -24
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +116 -30
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +123 -178
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -8
- package/src/commit/agentic/index.ts +22 -26
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/tools/git-file-diff.ts +3 -6
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +6 -9
- package/src/commit/agentic/tools/index.ts +6 -8
- package/src/commit/agentic/tools/propose-commit.ts +4 -7
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/split-commit.ts +4 -4
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/changelog/index.ts +5 -9
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +13 -16
- package/src/config/keybindings.ts +7 -6
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +98 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
- package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
- package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +125 -47
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/footer.ts +9 -29
- package/src/modes/components/hook-editor.ts +3 -3
- package/src/modes/components/hook-selector.ts +6 -1
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +472 -0
- package/src/modes/components/settings-defs.ts +24 -0
- package/src/modes/components/status-line.ts +15 -61
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +4 -2
- package/src/modes/controllers/event-controller.ts +59 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +15 -8
- package/src/modes/controllers/selector-controller.ts +26 -0
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +278 -69
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +0 -42
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +4 -2
- package/src/modes/utils/keybinding-matchers.ts +9 -0
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +8 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +216 -165
- package/src/secrets/index.ts +1 -1
- package/src/secrets/obfuscator.ts +25 -17
- package/src/session/agent-session.ts +381 -286
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +15 -5
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +25 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +32 -4
- package/src/task/index.ts +91 -82
- package/src/task/template.ts +2 -2
- package/src/task/types.ts +25 -0
- package/src/task/worktree.ts +131 -149
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +3 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +130 -308
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +211 -146
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/external-editor.ts +11 -5
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +1400 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/web/search/render.ts +6 -4
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -210
- package/src/commit/git/operations.ts +0 -54
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/gh-cli.ts +0 -125
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
package/src/tools/read.ts
CHANGED
|
@@ -5,15 +5,22 @@ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
|
5
5
|
import { glob } from "@oh-my-pi/pi-natives";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import { getRemoteDir, untilAborted } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import { type Static, Type } from "@sinclair/typebox";
|
|
10
|
-
import {
|
|
10
|
+
import { computeLineHash } from "../edit/line-hash";
|
|
11
|
+
import {
|
|
12
|
+
type ChunkReadTarget,
|
|
13
|
+
formatChunkedRead,
|
|
14
|
+
parseChunkReadPath,
|
|
15
|
+
parseChunkSelector,
|
|
16
|
+
resolveAnchorStyle,
|
|
17
|
+
} from "../edit/modes/chunk";
|
|
11
18
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
19
|
import { parseInternalUrl } from "../internal-urls/parse";
|
|
13
20
|
import type { InternalUrl } from "../internal-urls/types";
|
|
14
21
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
15
|
-
import { computeLineHash } from "../patch/hashline";
|
|
16
22
|
import readDescription from "../prompts/tools/read.md" with { type: "text" };
|
|
23
|
+
import readChunkDescription from "../prompts/tools/read-chunk.md" with { type: "text" };
|
|
17
24
|
import type { ToolSession } from "../sdk";
|
|
18
25
|
import {
|
|
19
26
|
DEFAULT_MAX_BYTES,
|
|
@@ -25,20 +32,17 @@ import {
|
|
|
25
32
|
} from "../session/streaming-output";
|
|
26
33
|
import { renderCodeCell, renderStatusLine } from "../tui";
|
|
27
34
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
35
|
+
import { resolveEditMode } from "../utils/edit-mode";
|
|
28
36
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
29
|
-
import {
|
|
30
|
-
ImageInputTooLargeError,
|
|
31
|
-
loadImageInput,
|
|
32
|
-
MAX_IMAGE_INPUT_BYTES,
|
|
33
|
-
readImageMetadata,
|
|
34
|
-
} from "../utils/image-input";
|
|
37
|
+
import { ImageInputTooLargeError, loadImageInput, MAX_IMAGE_INPUT_BYTES } from "../utils/image-loading";
|
|
35
38
|
import { convertFileWithMarkit } from "../utils/markit";
|
|
36
|
-
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
37
39
|
import { type ArchiveReader, openArchive, parseArchivePathCandidates } from "./archive-reader";
|
|
40
|
+
|
|
38
41
|
import {
|
|
39
42
|
executeReadUrl,
|
|
40
43
|
isReadableUrlPath,
|
|
41
44
|
loadReadUrlCacheEntry,
|
|
45
|
+
parseReadUrlTarget,
|
|
42
46
|
type ReadUrlToolDetails,
|
|
43
47
|
renderReadUrlCall,
|
|
44
48
|
renderReadUrlResult,
|
|
@@ -50,19 +54,14 @@ import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-util
|
|
|
50
54
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
51
55
|
import { toolResult } from "./tool-result";
|
|
52
56
|
|
|
57
|
+
const PROSE_LANGUAGES = new Set(["markdown", "text", "log", "asciidoc", "restructuredtext"]);
|
|
58
|
+
|
|
59
|
+
function isProseLanguage(language: string | undefined): boolean {
|
|
60
|
+
return language !== undefined && PROSE_LANGUAGES.has(language);
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
// Document types converted to markdown via markit.
|
|
54
|
-
const CONVERTIBLE_EXTENSIONS = new Set([
|
|
55
|
-
".pdf",
|
|
56
|
-
".doc",
|
|
57
|
-
".docx",
|
|
58
|
-
".ppt",
|
|
59
|
-
".pptx",
|
|
60
|
-
".xls",
|
|
61
|
-
".xlsx",
|
|
62
|
-
".rtf",
|
|
63
|
-
".epub",
|
|
64
|
-
".ipynb",
|
|
65
|
-
]);
|
|
64
|
+
const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
|
|
66
65
|
|
|
67
66
|
// Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs
|
|
68
67
|
const REMOTE_MOUNT_PREFIX = getRemoteDir() + path.sep;
|
|
@@ -354,10 +353,8 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
|
|
|
354
353
|
|
|
355
354
|
const readSchema = Type.Object({
|
|
356
355
|
path: Type.String({ description: "Path or URL to read" }),
|
|
357
|
-
|
|
358
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of lines" })),
|
|
356
|
+
sel: Type.Optional(Type.String({ description: "Selector: chunk path, L10-L50, or raw" })),
|
|
359
357
|
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
|
|
360
|
-
raw: Type.Optional(Type.Boolean({ description: "If set, returns raw content without transformations" })),
|
|
361
358
|
});
|
|
362
359
|
|
|
363
360
|
export type ReadToolInput = Static<typeof readSchema>;
|
|
@@ -368,6 +365,7 @@ export interface ReadToolDetails {
|
|
|
368
365
|
isDirectory?: boolean;
|
|
369
366
|
resolvedPath?: string;
|
|
370
367
|
suffixResolution?: { from: string; to: string };
|
|
368
|
+
chunk?: ChunkReadTarget;
|
|
371
369
|
url?: string;
|
|
372
370
|
finalUrl?: string;
|
|
373
371
|
contentType?: string;
|
|
@@ -378,6 +376,43 @@ export interface ReadToolDetails {
|
|
|
378
376
|
|
|
379
377
|
type ReadParams = ReadToolInput;
|
|
380
378
|
|
|
379
|
+
/** Parsed representation of the `sel` parameter. */
|
|
380
|
+
type ParsedSelector =
|
|
381
|
+
| { kind: "none" }
|
|
382
|
+
| { kind: "raw" }
|
|
383
|
+
| { kind: "lines"; startLine: number; endLine: number | undefined }
|
|
384
|
+
| { kind: "chunk"; selector: string };
|
|
385
|
+
|
|
386
|
+
const LINE_RANGE_RE = /^L(\d+)(?:-L?(\d+))?$/i;
|
|
387
|
+
|
|
388
|
+
function parseSel(sel: string | undefined): ParsedSelector {
|
|
389
|
+
if (!sel || sel.length === 0) return { kind: "none" };
|
|
390
|
+
const normalizedSelector = parseChunkSelector(sel).selector ?? sel;
|
|
391
|
+
if (normalizedSelector === "raw") return { kind: "raw" };
|
|
392
|
+
const lineMatch = LINE_RANGE_RE.exec(normalizedSelector);
|
|
393
|
+
if (lineMatch) {
|
|
394
|
+
const rawStart = Number.parseInt(lineMatch[1]!, 10);
|
|
395
|
+
if (rawStart < 1) {
|
|
396
|
+
throw new ToolError("L0 is invalid; lines are 1-indexed. Use sel=L1.");
|
|
397
|
+
}
|
|
398
|
+
const rawEnd = lineMatch[2] ? Number.parseInt(lineMatch[2], 10) : undefined;
|
|
399
|
+
if (rawEnd !== undefined && rawEnd < rawStart) {
|
|
400
|
+
throw new ToolError(`Invalid range L${rawStart}-L${rawEnd}: end must be >= start.`);
|
|
401
|
+
}
|
|
402
|
+
return { kind: "lines", startLine: rawStart, endLine: rawEnd };
|
|
403
|
+
}
|
|
404
|
+
return { kind: "chunk", selector: normalizedSelector };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Convert a line-range selector to the offset/limit pair used by internal pagination. */
|
|
408
|
+
function selToOffsetLimit(parsed: ParsedSelector): { offset?: number; limit?: number } {
|
|
409
|
+
if (parsed.kind === "lines") {
|
|
410
|
+
const limit = parsed.endLine !== undefined ? parsed.endLine - parsed.startLine + 1 : undefined;
|
|
411
|
+
return { offset: parsed.startLine, limit };
|
|
412
|
+
}
|
|
413
|
+
return {};
|
|
414
|
+
}
|
|
415
|
+
|
|
381
416
|
interface ResolvedArchiveReadPath {
|
|
382
417
|
absolutePath: string;
|
|
383
418
|
archiveSubPath: string;
|
|
@@ -410,12 +445,17 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
410
445
|
Math.min(session.settings.get("read.defaultLimit") ?? DEFAULT_MAX_LINES, DEFAULT_MAX_LINES),
|
|
411
446
|
);
|
|
412
447
|
this.#inspectImageEnabled = session.settings.get("inspect_image.enabled");
|
|
413
|
-
this.description =
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
448
|
+
this.description =
|
|
449
|
+
resolveEditMode(session) === "chunk"
|
|
450
|
+
? prompt.render(readChunkDescription, {
|
|
451
|
+
anchorStyle: resolveAnchorStyle(session.settings),
|
|
452
|
+
})
|
|
453
|
+
: prompt.render(readDescription, {
|
|
454
|
+
DEFAULT_LIMIT: String(this.#defaultLimit),
|
|
455
|
+
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
|
456
|
+
IS_HASHLINE_MODE: displayMode.hashLines,
|
|
457
|
+
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
458
|
+
});
|
|
419
459
|
}
|
|
420
460
|
|
|
421
461
|
async #resolveArchiveReadPath(readPath: string, signal?: AbortSignal): Promise<ResolvedArchiveReadPath | null> {
|
|
@@ -470,6 +510,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
470
510
|
sourceUrl?: string;
|
|
471
511
|
sourceInternal?: string;
|
|
472
512
|
entityLabel: string;
|
|
513
|
+
ignoreResultLimits?: boolean;
|
|
473
514
|
},
|
|
474
515
|
): AgentToolResult<ReadToolDetails> {
|
|
475
516
|
const displayMode = resolveFileDisplayMode(this.session);
|
|
@@ -478,6 +519,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
478
519
|
const totalLines = allLines.length;
|
|
479
520
|
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
480
521
|
const startLineDisplay = startLine + 1;
|
|
522
|
+
const ignoreResultLimits = options.ignoreResultLimits ?? false;
|
|
481
523
|
|
|
482
524
|
const resultBuilder = toolResult(details);
|
|
483
525
|
if (options.sourcePath) {
|
|
@@ -494,18 +536,19 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
494
536
|
const suggestion =
|
|
495
537
|
allLines.length === 0
|
|
496
538
|
? `The ${options.entityLabel} is empty.`
|
|
497
|
-
: `Use
|
|
539
|
+
: `Use sel=L1 to read from the start, or sel=L${allLines.length} to read the last line.`;
|
|
498
540
|
return resultBuilder
|
|
499
541
|
.text(
|
|
500
|
-
`
|
|
542
|
+
`Line ${startLineDisplay} is beyond end of ${options.entityLabel} (${allLines.length} lines total). ${suggestion}`,
|
|
501
543
|
)
|
|
502
544
|
.done();
|
|
503
545
|
}
|
|
504
546
|
|
|
505
|
-
const endLine =
|
|
547
|
+
const endLine =
|
|
548
|
+
limit !== undefined && !ignoreResultLimits ? Math.min(startLine + limit, allLines.length) : allLines.length;
|
|
506
549
|
const selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
507
|
-
const userLimitedLines = limit !== undefined ? endLine - startLine : undefined;
|
|
508
|
-
const truncation = truncateHead(selectedContent);
|
|
550
|
+
const userLimitedLines = limit !== undefined && !ignoreResultLimits ? endLine - startLine : undefined;
|
|
551
|
+
const truncation = ignoreResultLimits ? noTruncResult(selectedContent) : truncateHead(selectedContent);
|
|
509
552
|
|
|
510
553
|
const shouldAddHashLines = displayMode.hashLines;
|
|
511
554
|
const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
|
|
@@ -553,7 +596,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
553
596
|
const nextOffset = startLine + userLimitedLines + 1;
|
|
554
597
|
|
|
555
598
|
outputText = formatText(selectedContent, startLineDisplay);
|
|
556
|
-
outputText += `\n\n[${remaining} more lines in ${options.entityLabel}. Use
|
|
599
|
+
outputText += `\n\n[${remaining} more lines in ${options.entityLabel}. Use sel=L${nextOffset} to continue]`;
|
|
557
600
|
} else {
|
|
558
601
|
outputText = formatText(truncation.content, startLineDisplay);
|
|
559
602
|
}
|
|
@@ -671,42 +714,60 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
671
714
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
672
715
|
_toolContext?: AgentToolContext,
|
|
673
716
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
674
|
-
let { path: readPath,
|
|
675
|
-
const displayMode = resolveFileDisplayMode(this.session);
|
|
717
|
+
let { path: readPath, sel, timeout } = params;
|
|
676
718
|
if (readPath.startsWith("file://")) {
|
|
677
719
|
readPath = expandPath(readPath);
|
|
678
720
|
}
|
|
721
|
+
const displayMode = resolveFileDisplayMode(this.session);
|
|
722
|
+
const chunkMode = resolveEditMode(this.session) === "chunk";
|
|
679
723
|
|
|
680
724
|
// Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
|
|
681
725
|
const internalRouter = this.session.internalRouter;
|
|
682
726
|
if (internalRouter?.canHandle(readPath)) {
|
|
727
|
+
const parsed = parseSel(sel);
|
|
728
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
683
729
|
return this.#handleInternalUrl(readPath, offset, limit);
|
|
684
730
|
}
|
|
685
731
|
|
|
686
|
-
|
|
732
|
+
const parsedUrlTarget = parseReadUrlTarget(readPath, sel);
|
|
733
|
+
if (parsedUrlTarget) {
|
|
687
734
|
if (!this.session.settings.get("fetch.enabled")) {
|
|
688
735
|
throw new ToolError("URL reads are disabled by settings.");
|
|
689
736
|
}
|
|
690
|
-
if (offset !== undefined || limit !== undefined) {
|
|
691
|
-
const cached = await loadReadUrlCacheEntry(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
737
|
+
if (parsedUrlTarget.offset !== undefined || parsedUrlTarget.limit !== undefined) {
|
|
738
|
+
const cached = await loadReadUrlCacheEntry(
|
|
739
|
+
this.session,
|
|
740
|
+
{ path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw },
|
|
741
|
+
signal,
|
|
742
|
+
{
|
|
743
|
+
ensureArtifact: true,
|
|
744
|
+
preferCached: true,
|
|
745
|
+
},
|
|
746
|
+
);
|
|
747
|
+
return this.#buildInMemoryTextResult(cached.output, parsedUrlTarget.offset, parsedUrlTarget.limit, {
|
|
696
748
|
details: { ...cached.details },
|
|
697
749
|
sourceUrl: cached.details.finalUrl,
|
|
698
750
|
entityLabel: "URL output",
|
|
699
751
|
});
|
|
700
752
|
}
|
|
701
|
-
return executeReadUrl(this.session, { path:
|
|
753
|
+
return executeReadUrl(this.session, { path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw }, signal);
|
|
702
754
|
}
|
|
703
755
|
|
|
704
|
-
const
|
|
756
|
+
const parsedReadPath = chunkMode ? parseChunkReadPath(readPath) : { filePath: readPath };
|
|
757
|
+
const localReadPath = parsedReadPath.filePath;
|
|
758
|
+
const pathSelectorParsed = chunkMode ? parseSel(parsedReadPath.selector) : { kind: "none" as const };
|
|
759
|
+
const pathChunkSelector = pathSelectorParsed.kind === "chunk" ? pathSelectorParsed.selector : undefined;
|
|
760
|
+
const selectorInput = sel ?? parsedReadPath.selector;
|
|
761
|
+
const rawSelectorInput = sel ?? parsedReadPath.selector;
|
|
762
|
+
const parsed = parseSel(selectorInput);
|
|
763
|
+
|
|
764
|
+
const archivePath = await this.#resolveArchiveReadPath(localReadPath, signal);
|
|
705
765
|
if (archivePath) {
|
|
766
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
706
767
|
return this.#readArchive(readPath, offset, limit, archivePath, signal);
|
|
707
768
|
}
|
|
708
769
|
|
|
709
|
-
let absolutePath = resolveReadPath(
|
|
770
|
+
let absolutePath = resolveReadPath(localReadPath, this.session.cwd);
|
|
710
771
|
let suffixResolution: { from: string; to: string } | undefined;
|
|
711
772
|
|
|
712
773
|
let isDirectory = false;
|
|
@@ -719,14 +780,14 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
719
780
|
if (isNotFoundError(error)) {
|
|
720
781
|
// Attempt unique suffix resolution before falling back to fuzzy suggestions
|
|
721
782
|
if (!isRemoteMountPath(absolutePath)) {
|
|
722
|
-
const suffixMatch = await findUniqueSuffixMatch(
|
|
783
|
+
const suffixMatch = await findUniqueSuffixMatch(localReadPath, this.session.cwd, signal);
|
|
723
784
|
if (suffixMatch) {
|
|
724
785
|
try {
|
|
725
786
|
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
726
787
|
absolutePath = suffixMatch.absolutePath;
|
|
727
788
|
fileSize = retryStat.size;
|
|
728
789
|
isDirectory = retryStat.isDirectory();
|
|
729
|
-
suffixResolution = { from:
|
|
790
|
+
suffixResolution = { from: localReadPath, to: suffixMatch.displayPath };
|
|
730
791
|
} catch {
|
|
731
792
|
// Suffix match candidate no longer stats — fall through to error path
|
|
732
793
|
}
|
|
@@ -734,7 +795,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
734
795
|
}
|
|
735
796
|
|
|
736
797
|
if (!suffixResolution) {
|
|
737
|
-
throw new ToolError(`Path '${
|
|
798
|
+
throw new ToolError(`Path '${localReadPath}' not found`);
|
|
738
799
|
}
|
|
739
800
|
} else {
|
|
740
801
|
throw error;
|
|
@@ -742,7 +803,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
742
803
|
}
|
|
743
804
|
|
|
744
805
|
if (isDirectory) {
|
|
745
|
-
const dirResult = await this.#readDirectory(absolutePath, limit, signal);
|
|
806
|
+
const dirResult = await this.#readDirectory(absolutePath, selToOffsetLimit(parsed).limit, signal);
|
|
746
807
|
if (suffixResolution) {
|
|
747
808
|
dirResult.details ??= {};
|
|
748
809
|
dirResult.details.suffixResolution = suffixResolution;
|
|
@@ -750,11 +811,59 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
750
811
|
return dirResult;
|
|
751
812
|
}
|
|
752
813
|
|
|
753
|
-
const
|
|
814
|
+
const imageMetadata = await readImageMetadata(absolutePath);
|
|
815
|
+
const mimeType = imageMetadata?.mimeType;
|
|
754
816
|
const ext = path.extname(absolutePath).toLowerCase();
|
|
817
|
+
const hasEditTool = this.session.hasEditTool ?? true;
|
|
818
|
+
const language = getLanguageFromPath(absolutePath);
|
|
819
|
+
const skipChunksForExplore = !hasEditTool && !this.session.settings.get("read.explorechunks");
|
|
820
|
+
const skipChunksForProse = isProseLanguage(language) && !this.session.settings.get("read.prosechunks");
|
|
821
|
+
const shouldConvertWithMarkit =
|
|
822
|
+
CONVERTIBLE_EXTENSIONS.has(ext) || (ext === ".ipynb" && (parsed.kind === "raw" || !chunkMode));
|
|
823
|
+
|
|
824
|
+
if (chunkMode && parsed.kind !== "raw" && !skipChunksForExplore && !skipChunksForProse) {
|
|
825
|
+
const absoluteLineRange =
|
|
826
|
+
pathChunkSelector && parsed.kind === "lines"
|
|
827
|
+
? { startLine: parsed.startLine, endLine: parsed.endLine }
|
|
828
|
+
: undefined;
|
|
829
|
+
// sel= wins over path:chunk when both are provided (explicit param > embedded path).
|
|
830
|
+
const effectiveSelector = sel ? selectorInput : (pathChunkSelector ?? selectorInput);
|
|
831
|
+
const rawEffectiveSelector = sel ? selectorInput : (rawSelectorInput ?? effectiveSelector);
|
|
832
|
+
const chunkReadPath =
|
|
833
|
+
parsed.kind === "chunk" || (pathChunkSelector && !sel)
|
|
834
|
+
? rawEffectiveSelector
|
|
835
|
+
? `${localReadPath}:${rawEffectiveSelector}`
|
|
836
|
+
: localReadPath
|
|
837
|
+
: parsed.kind === "lines"
|
|
838
|
+
? parsed.endLine !== undefined
|
|
839
|
+
? `${localReadPath}:L${parsed.startLine}-L${parsed.endLine}`
|
|
840
|
+
: `${localReadPath}:L${parsed.startLine}`
|
|
841
|
+
: localReadPath;
|
|
842
|
+
const chunkResult = await formatChunkedRead({
|
|
843
|
+
filePath: absolutePath,
|
|
844
|
+
readPath: chunkReadPath,
|
|
845
|
+
cwd: this.session.cwd,
|
|
846
|
+
language,
|
|
847
|
+
omitChecksum: !hasEditTool,
|
|
848
|
+
anchorStyle: resolveAnchorStyle(this.session.settings),
|
|
849
|
+
absoluteLineRange,
|
|
850
|
+
});
|
|
851
|
+
let text = chunkResult.text;
|
|
852
|
+
if (suffixResolution) {
|
|
853
|
+
text = prependSuffixResolutionNotice(text, suffixResolution);
|
|
854
|
+
}
|
|
855
|
+
return toolResult<ReadToolDetails>({
|
|
856
|
+
resolvedPath: absolutePath,
|
|
857
|
+
suffixResolution,
|
|
858
|
+
chunk: chunkResult.chunk,
|
|
859
|
+
})
|
|
860
|
+
.text(text)
|
|
861
|
+
.sourcePath(absolutePath)
|
|
862
|
+
.done();
|
|
863
|
+
}
|
|
755
864
|
|
|
756
865
|
// Read the file based on type
|
|
757
|
-
let content:
|
|
866
|
+
let content: Array<TextContent | ImageContent>;
|
|
758
867
|
let details: ReadToolDetails = {};
|
|
759
868
|
let sourcePath: string | undefined;
|
|
760
869
|
let truncationInfo:
|
|
@@ -763,14 +872,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
763
872
|
|
|
764
873
|
if (mimeType) {
|
|
765
874
|
if (this.#inspectImageEnabled) {
|
|
766
|
-
const metadata =
|
|
767
|
-
path: readPath,
|
|
768
|
-
cwd: this.session.cwd,
|
|
769
|
-
resolvedPath: absolutePath,
|
|
770
|
-
detectedMimeType: mimeType,
|
|
771
|
-
});
|
|
875
|
+
const metadata = imageMetadata;
|
|
772
876
|
const outputMime = metadata?.mimeType ?? mimeType;
|
|
773
|
-
const outputBytes =
|
|
877
|
+
const outputBytes = fileSize;
|
|
774
878
|
const metadataLines = [
|
|
775
879
|
"Image metadata:",
|
|
776
880
|
`- MIME: ${outputMime}`,
|
|
@@ -821,7 +925,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
821
925
|
throw error;
|
|
822
926
|
}
|
|
823
927
|
}
|
|
824
|
-
} else if (
|
|
928
|
+
} else if (shouldConvertWithMarkit) {
|
|
825
929
|
// Convert document or notebook via markit.
|
|
826
930
|
const result = await convertFileWithMarkit(absolutePath, signal);
|
|
827
931
|
if (result.ok) {
|
|
@@ -840,13 +944,41 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
840
944
|
content = [{ type: "text", text: `[Cannot read ${ext} file: conversion failed]` }];
|
|
841
945
|
}
|
|
842
946
|
} else {
|
|
843
|
-
//
|
|
947
|
+
// Chunk mode: dispatch to chunk tree unless raw or line range requested
|
|
948
|
+
if (chunkMode && parsed.kind !== "raw" && parsed.kind !== "lines") {
|
|
949
|
+
const chunkSel = parsed.kind === "chunk" ? parsed.selector : undefined;
|
|
950
|
+
const chunkResult = await formatChunkedRead({
|
|
951
|
+
filePath: absolutePath,
|
|
952
|
+
readPath: chunkSel ? `${localReadPath}:${chunkSel}` : localReadPath,
|
|
953
|
+
cwd: this.session.cwd,
|
|
954
|
+
language: getLanguageFromPath(absolutePath),
|
|
955
|
+
omitChecksum: !(this.session.hasEditTool ?? true),
|
|
956
|
+
anchorStyle: resolveAnchorStyle(this.session.settings),
|
|
957
|
+
});
|
|
958
|
+
let text = chunkResult.text;
|
|
959
|
+
if (suffixResolution) {
|
|
960
|
+
text = prependSuffixResolutionNotice(text, suffixResolution);
|
|
961
|
+
}
|
|
962
|
+
return toolResult<ReadToolDetails>({
|
|
963
|
+
resolvedPath: absolutePath,
|
|
964
|
+
suffixResolution,
|
|
965
|
+
chunk: chunkResult.chunk,
|
|
966
|
+
})
|
|
967
|
+
.text(text)
|
|
968
|
+
.sourcePath(absolutePath)
|
|
969
|
+
.done();
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Raw text or line-range mode
|
|
973
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
844
974
|
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
845
|
-
const startLineDisplay = startLine + 1;
|
|
975
|
+
const startLineDisplay = startLine + 1;
|
|
846
976
|
|
|
847
|
-
const
|
|
977
|
+
const DEFAULT_LIMIT = this.#defaultLimit;
|
|
978
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
848
979
|
const maxLinesToCollect = Math.min(effectiveLimit, DEFAULT_MAX_LINES);
|
|
849
980
|
const selectedLineLimit = effectiveLimit;
|
|
981
|
+
|
|
850
982
|
const streamResult = await streamLinesFromFile(
|
|
851
983
|
absolutePath,
|
|
852
984
|
startLine,
|
|
@@ -870,9 +1002,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
870
1002
|
const suggestion =
|
|
871
1003
|
totalFileLines === 0
|
|
872
1004
|
? "The file is empty."
|
|
873
|
-
: `Use
|
|
1005
|
+
: `Use sel=L1 to read from the start, or sel=L${totalFileLines} to read the last line.`;
|
|
874
1006
|
return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
|
|
875
|
-
.text(`
|
|
1007
|
+
.text(`Line ${startLineDisplay} is beyond end of file (${totalFileLines} lines total). ${suggestion}`)
|
|
876
1008
|
.done();
|
|
877
1009
|
}
|
|
878
1010
|
|
|
@@ -939,7 +1071,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
939
1071
|
const nextOffset = startLine + userLimitedLines + 1;
|
|
940
1072
|
|
|
941
1073
|
outputText = formatText(truncation.content, startLineDisplay);
|
|
942
|
-
outputText += `\n\n[${remaining} more lines in file. Use
|
|
1074
|
+
outputText += `\n\n[${remaining} more lines in file. Use sel=L${nextOffset} to continue]`;
|
|
943
1075
|
details = {};
|
|
944
1076
|
sourcePath = absolutePath;
|
|
945
1077
|
} else {
|
|
@@ -1011,82 +1143,13 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1011
1143
|
return toolResult(details).text(resource.content).sourceInternal(url).done();
|
|
1012
1144
|
}
|
|
1013
1145
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
if (startLine >= allLines.length) {
|
|
1022
|
-
const suggestion =
|
|
1023
|
-
allLines.length === 0
|
|
1024
|
-
? "The resource is empty."
|
|
1025
|
-
: `Use offset=1 to read from the start, or offset=${allLines.length} to read the last line.`;
|
|
1026
|
-
return toolResult<ReadToolDetails>(details)
|
|
1027
|
-
.text(`Offset ${offset} is beyond end of resource (${allLines.length} lines total). ${suggestion}`)
|
|
1028
|
-
.done();
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
const ignoreLimits = scheme === "skill";
|
|
1032
|
-
let selectedContent: string;
|
|
1033
|
-
let userLimitedLines: number | undefined;
|
|
1034
|
-
if (limit !== undefined && !ignoreLimits) {
|
|
1035
|
-
const endLine = Math.min(startLine + limit, allLines.length);
|
|
1036
|
-
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
1037
|
-
userLimitedLines = endLine - startLine;
|
|
1038
|
-
} else {
|
|
1039
|
-
selectedContent = allLines.slice(startLine).join("\n");
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const truncation: TruncationResult = ignoreLimits
|
|
1043
|
-
? noTruncResult(selectedContent)
|
|
1044
|
-
: truncateHead(selectedContent);
|
|
1045
|
-
|
|
1046
|
-
let outputText: string;
|
|
1047
|
-
let truncationInfo:
|
|
1048
|
-
| { result: TruncationResult; options: { direction: "head"; startLine?: number; totalFileLines?: number } }
|
|
1049
|
-
| undefined;
|
|
1050
|
-
|
|
1051
|
-
if (truncation.firstLineExceedsLimit) {
|
|
1052
|
-
const firstLine = allLines[startLine] ?? "";
|
|
1053
|
-
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
1054
|
-
const snippet = truncateHeadBytes(firstLine, DEFAULT_MAX_BYTES);
|
|
1055
|
-
|
|
1056
|
-
outputText = snippet.text;
|
|
1057
|
-
if (snippet.text.length === 0) {
|
|
1058
|
-
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
1059
|
-
firstLineBytes,
|
|
1060
|
-
)}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
1061
|
-
}
|
|
1062
|
-
details.truncation = truncation;
|
|
1063
|
-
truncationInfo = {
|
|
1064
|
-
result: truncation,
|
|
1065
|
-
options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
|
|
1066
|
-
};
|
|
1067
|
-
} else if (truncation.truncated) {
|
|
1068
|
-
outputText = truncation.content;
|
|
1069
|
-
details.truncation = truncation;
|
|
1070
|
-
truncationInfo = {
|
|
1071
|
-
result: truncation,
|
|
1072
|
-
options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
|
|
1073
|
-
};
|
|
1074
|
-
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
1075
|
-
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
1076
|
-
const nextOffset = startLine + userLimitedLines + 1;
|
|
1077
|
-
|
|
1078
|
-
outputText = truncation.content;
|
|
1079
|
-
outputText += `\n\n[${remaining} more lines in resource. Use offset=${nextOffset} to continue]`;
|
|
1080
|
-
details.truncation = truncation;
|
|
1081
|
-
} else {
|
|
1082
|
-
outputText = truncation.content;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
const resultBuilder = toolResult(details).text(outputText).sourceInternal(url);
|
|
1086
|
-
if (truncationInfo) {
|
|
1087
|
-
resultBuilder.truncation(truncationInfo.result, truncationInfo.options);
|
|
1088
|
-
}
|
|
1089
|
-
return resultBuilder.done();
|
|
1146
|
+
return this.#buildInMemoryTextResult(resource.content, offset, limit, {
|
|
1147
|
+
details,
|
|
1148
|
+
sourcePath: resource.sourcePath,
|
|
1149
|
+
sourceInternal: url,
|
|
1150
|
+
entityLabel: "resource",
|
|
1151
|
+
ignoreResultLimits: scheme === "skill",
|
|
1152
|
+
});
|
|
1090
1153
|
}
|
|
1091
1154
|
|
|
1092
1155
|
/** Read directory contents as a formatted listing */
|
|
@@ -1166,9 +1229,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1166
1229
|
interface ReadRenderArgs {
|
|
1167
1230
|
path?: string;
|
|
1168
1231
|
file_path?: string;
|
|
1232
|
+
sel?: string;
|
|
1233
|
+
timeout?: number;
|
|
1234
|
+
// Legacy fields from old schema — tolerated for in-flight tool calls during transition
|
|
1169
1235
|
offset?: number;
|
|
1170
1236
|
limit?: number;
|
|
1171
|
-
timeout?: number;
|
|
1172
1237
|
raw?: boolean;
|
|
1173
1238
|
}
|
|
1174
1239
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { type MermaidAsciiRenderOptions, renderMermaidAscii } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { type MermaidAsciiRenderOptions, prompt, renderMermaidAscii } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { type Static, Type } from "@sinclair/typebox";
|
|
4
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
5
4
|
import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with { type: "text" };
|
|
6
5
|
import type { ToolSession } from "./index";
|
|
7
6
|
|
|
@@ -41,7 +40,7 @@ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema,
|
|
|
41
40
|
readonly strict = true;
|
|
42
41
|
|
|
43
42
|
constructor(private readonly session: ToolSession) {
|
|
44
|
-
this.description =
|
|
43
|
+
this.description = prompt.render(renderMermaidDescription);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
async execute(
|
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
* tool renderers to ensure a unified TUI experience.
|
|
6
6
|
*/
|
|
7
7
|
import * as os from "node:os";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import type { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
10
|
+
import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
11
|
+
import { pluralize } from "@oh-my-pi/pi-utils";
|
|
10
12
|
import { settings } from "../config/settings";
|
|
11
13
|
import type { Theme } from "../modes/theme/theme";
|
|
12
14
|
import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
|
|
13
15
|
|
|
14
|
-
export { Ellipsis
|
|
16
|
+
export { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
17
|
+
export { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
15
18
|
|
|
16
|
-
export function replaceTabs(text: string, file?: string): string {
|
|
17
|
-
return text.replaceAll("\t", getIndentation(file));
|
|
18
|
-
}
|
|
19
19
|
// =============================================================================
|
|
20
20
|
// Standardized Display Constants
|
|
21
21
|
// =============================================================================
|
|
@@ -548,6 +548,20 @@ export function shortenPath(filePath: string, homeDir?: string): string {
|
|
|
548
548
|
return filePath;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
+
export function formatToolWorkingDirectory(workdir: string | undefined, projectDir: string): string | undefined {
|
|
552
|
+
if (!workdir) return undefined;
|
|
553
|
+
const resolvedProjectDir = path.resolve(projectDir);
|
|
554
|
+
const resolvedWorkdir = path.resolve(projectDir, workdir);
|
|
555
|
+
if (resolvedWorkdir === resolvedProjectDir) {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
const relativePath = path.relative(resolvedProjectDir, resolvedWorkdir);
|
|
559
|
+
const isWithinProject =
|
|
560
|
+
relativePath.length > 0 && !relativePath.startsWith("..") && !relativePath.startsWith(`..${path.sep}`);
|
|
561
|
+
const displayWorkdir = isWithinProject ? relativePath : shortenPath(resolvedWorkdir);
|
|
562
|
+
return replaceTabs(displayWorkdir);
|
|
563
|
+
}
|
|
564
|
+
|
|
551
565
|
export function formatScreenshot(opts: {
|
|
552
566
|
saveFullRes: boolean;
|
|
553
567
|
savedMimeType: string;
|
package/src/tools/renderers.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* These provide rich visualization for tool calls and results in the TUI.
|
|
5
5
|
*/
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
|
+
import { editToolRenderer } from "../edit/renderer";
|
|
7
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
9
|
import { lspToolRenderer } from "../lsp/render";
|
|
9
10
|
import type { Theme } from "../modes/theme/theme";
|
|
10
|
-
import { editToolRenderer } from "../patch";
|
|
11
11
|
import { taskToolRenderer } from "../task/render";
|
|
12
12
|
import { webSearchToolRenderer } from "../web/search/render";
|
|
13
13
|
import { askToolRenderer } from "./ask";
|
|
@@ -15,6 +15,7 @@ import { astEditToolRenderer } from "./ast-edit";
|
|
|
15
15
|
import { astGrepToolRenderer } from "./ast-grep";
|
|
16
16
|
import { bashToolRenderer } from "./bash";
|
|
17
17
|
import { calculatorToolRenderer } from "./calculator";
|
|
18
|
+
import { debugToolRenderer } from "./debug";
|
|
18
19
|
import { findToolRenderer } from "./find";
|
|
19
20
|
import { ghRunWatchToolRenderer } from "./gh-renderer";
|
|
20
21
|
import { grepToolRenderer } from "./grep";
|
|
@@ -46,6 +47,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
46
47
|
ast_grep: astGrepToolRenderer as ToolRenderer,
|
|
47
48
|
ast_edit: astEditToolRenderer as ToolRenderer,
|
|
48
49
|
bash: bashToolRenderer as ToolRenderer,
|
|
50
|
+
debug: debugToolRenderer as ToolRenderer,
|
|
49
51
|
python: pythonToolRenderer as ToolRenderer,
|
|
50
52
|
calc: calculatorToolRenderer as ToolRenderer,
|
|
51
53
|
edit: editToolRenderer as ToolRenderer,
|