@xiaoso/dsh-tool-plus 0.1.7 → 0.1.8-beta.3
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/lib/{abort-Caqff3M6.mjs → abort-BtBy5Svf.mjs} +1 -1
- package/lib/{alibaba-coding-plan-DTupMiAi.mjs → alibaba-coding-plan-oxHCHJtG.mjs} +2 -2
- package/lib/{alibaba-token-plan-Dj3OZhEE.mjs → alibaba-token-plan-B0WzvYnB.mjs} +2 -2
- package/lib/{amazon-bedrock-DbFSYXfJ.mjs → amazon-bedrock-BvfYGhrD.mjs} +6 -6
- package/lib/{anthropic-CXfWlp-x.mjs → anthropic-C6-vdduI.mjs} +1 -1
- package/lib/{anthropic-CTJ3IQVa.mjs → anthropic-Id561lwb.mjs} +13 -13
- package/lib/{anthropic-wire-CKuhDwZ1.mjs → anthropic-wire-Cb8AQ1tS.mjs} +13 -13
- package/lib/{api-key-validation-CGCJFbFr.mjs → api-key-validation-BXJKlsJg.mjs} +1 -1
- package/lib/{azure-openai-responses-ChFbWaGj.mjs → azure-openai-responses-CGws_ijz.mjs} +8 -8
- package/lib/{browser-render-9o0Rknop.mjs → browser-render-i7uYOl4f.mjs} +2 -2
- package/lib/client.js +2205 -19
- package/lib/{cloudflare-ai-gateway-BupNXyKK.mjs → cloudflare-ai-gateway-BK6eadm9.mjs} +2 -2
- package/lib/{cloudflare-ai-gateway-EWo0VkP5.mjs → cloudflare-ai-gateway-B_kWnF30.mjs} +1 -1
- package/lib/{collapse-DOR8sOGf.mjs → collapse-DU3g7d3B.mjs} +1 -1
- package/lib/{coreweave-Ct8cF-da.mjs → coreweave-CkR_FlQi.mjs} +2 -2
- package/lib/{cursor-LKGjucn2.mjs → cursor-BXx_5h4a.mjs} +2 -2
- package/lib/{cursor-DJV_dcgB.mjs → cursor-BcHwv8qq.mjs} +10 -10
- package/lib/{cursor-B95LzVWV.mjs → cursor-BjWbJJLe.mjs} +1 -1
- package/lib/{cursor-proto-B8Wj8bv9.mjs → cursor-proto-DPXXYx8S.mjs} +1 -1
- package/lib/{devin-DSp0Rw_E.mjs → devin-BZBfzyIz.mjs} +6 -6
- package/lib/{devin-BMTdx_hx.mjs → devin-Ce3Wz8_E.mjs} +5 -5
- package/lib/{devin-proto-Br8fdyNl.mjs → devin-proto-B6HwDwHK.mjs} +1 -1
- package/lib/{error-CORgGg5a.mjs → error-DdCn4m1q.mjs} +3 -3
- package/lib/{event-stream-ulcDenZD.mjs → event-stream-ldcUvOA0.mjs} +1 -1
- package/lib/{github-copilot-Dstq1D3s.mjs → github-copilot-BT5dkIJl.mjs} +2 -2
- package/lib/{github-copilot-headers-DOh1gt_w.mjs → github-copilot-headers-BGL8JO5c.mjs} +2 -2
- package/lib/{gitlab-duo-DSCMrU0m.mjs → gitlab-duo-B0qSYCa3.mjs} +1 -1
- package/lib/{google-CtsKkOQK.mjs → google-6cRgRmh0.mjs} +3 -3
- package/lib/{google-antigravity-z-5KsEci.mjs → google-antigravity-D1llOVad.mjs} +3 -3
- package/lib/{google-auth-ieLBaneO.mjs → google-auth-DYhqX1rJ.mjs} +5 -5
- package/lib/{google-gemini-cli-CAIPSl7e.mjs → google-gemini-cli-ByQUuKp9.mjs} +7 -7
- package/lib/{google-gemini-cli-C6NE5mWz.mjs → google-gemini-cli-CjHy6mFG.mjs} +3 -3
- package/lib/{google-oauth-shared-DhMllAkt.mjs → google-oauth-shared-CSwCT0Yl.mjs} +1 -1
- package/lib/{google-shared-B0aNCYEH.mjs → google-shared-DYjJp0mQ.mjs} +5 -5
- package/lib/{google-vertex-Bbd1tYNc.mjs → google-vertex-CbOvlE5j.mjs} +4 -4
- package/lib/{idle-iterator-DlUkEfzW.mjs → idle-iterator-DTBaEIbM.mjs} +2 -2
- package/lib/index.mjs +1278 -184
- package/lib/{kilo-ChJL54wq.mjs → kilo-cVG1aNZM.mjs} +1 -1
- package/lib/{kimi-C2c_Zq8A.mjs → kimi-BFfnFDhu.mjs} +4 -4
- package/lib/{launch-DKDH71En.mjs → launch-Uq7920ng.mjs} +7 -7
- package/lib/{markit-CmRfm4E2.mjs → markit-BmsfOFVh.mjs} +18 -18
- package/lib/{muse-code-Di1VZmB1.mjs → muse-code-CtcevJab.mjs} +1 -1
- package/lib/{ollama-B8ic3sEp.mjs → ollama-B5hR1P1p.mjs} +8 -8
- package/lib/{openai-codex-DuJNC99x.mjs → openai-codex-CxkPyvyt.mjs} +2 -2
- package/lib/{openai-codex-responses-BL29nh9K.mjs → openai-codex-responses-BbM3Q7nJ.mjs} +9 -9
- package/lib/{openai-completions-BK2f2dBv.mjs → openai-completions-CffXVOCL.mjs} +13 -13
- package/lib/{openai-reasoning-fallback-BdB3RCbj.mjs → openai-reasoning-fallback-DFdD1ok5.mjs} +2 -2
- package/lib/{openai-responses-CGQPlIIy.mjs → openai-responses-DgqOvSn_.mjs} +9 -9
- package/lib/{perplexity-TBjz3VMB.mjs → perplexity-JFp3knZN.mjs} +2 -2
- package/lib/{protobuf-aab1UGVn.mjs → protobuf-BJHNU9-E.mjs} +1 -1
- package/lib/{proxy-5nWqRL1f.mjs → proxy-B51IWEMt.mjs} +2 -2
- package/lib/{scrapers-CtQrQ5kG.mjs → scrapers-DU4u4-Co.mjs} +7 -7
- package/lib/{src-BwW51vA5.mjs → src-DUUqyo9y.mjs} +42 -42
- package/lib/{stream-B_7qN-bA.mjs → stream-DHdFrAG4.mjs} +54 -54
- package/lib/{stream-markup-healing-DKT1lnkW.mjs → stream-markup-healing-DlJ9abTw.mjs} +2 -2
- package/lib/{thinking-CiLa2LFB.mjs → thinking-D6LJxkz_.mjs} +1 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/config/settings.d.ts +8 -0
- package/lib/types/tools/ast-grep/adapter/index.d.ts +23 -0
- package/lib/types/tools/edit/adapter/index.d.ts +3 -0
- package/lib/types/tools/glob/adapter/index.d.ts +2 -0
- package/lib/types/tools/grep/adapter/index.d.ts +4 -0
- package/lib/types/tools/read/adapter/index.d.ts +18 -0
- package/lib/types/tools/write/adapter/index.d.ts +2 -0
- package/lib/types/web/client/ToolCardShell.d.ts +111 -0
- package/lib/types/web/client/labels.d.ts +36 -0
- package/lib/types/web/client/registerToolCards.d.ts +35 -0
- package/lib/types/web/client/row-utils.d.ts +177 -0
- package/lib/types/web/client/rows/AstEditRow.d.ts +22 -0
- package/lib/types/web/client/rows/FileMutationRow.d.ts +23 -0
- package/lib/types/web/client/rows/ReadRow.d.ts +31 -0
- package/lib/types/web/client/rows/SearchRow.d.ts +21 -0
- package/lib/types/web/client/rows/TerminalRow.d.ts +24 -0
- package/lib/types/web/client/rows/WriteRow.d.ts +22 -0
- package/lib/types/web/client/rows/command-card-facts.d.ts +45 -0
- package/lib/types/web/client/rows/image-card.d.ts +60 -0
- package/lib/types/web/client/rows/read-card-facts.d.ts +33 -0
- package/lib/types/web/client/rows/search-card-facts.d.ts +37 -0
- package/lib/types/web/contract.d.ts +249 -0
- package/lib/types/web/host/ast-edit.d.ts +10 -0
- package/lib/types/web/host/bash.d.ts +30 -0
- package/lib/types/web/host/edit.d.ts +33 -0
- package/lib/types/web/host/read.d.ts +48 -0
- package/lib/types/web/host/search.d.ts +41 -0
- package/lib/types/web/host/write.d.ts +13 -0
- package/lib/{types-PVueGi9S.mjs → types-DT9vgZ3S.mjs} +1 -1
- package/lib/{utils-CUPhpw9A.mjs → utils-BMmwLB0f.mjs} +2 -2
- package/lib/{utils-ECAwvaPQ.mjs → utils-C-Lo_JC6.mjs} +41 -41
- package/lib/{utils-DeWqNWV8.mjs → utils-D31VFael.mjs} +1 -1
- package/lib/{vision-guard-_XwsL1DF.mjs → vision-guard-BlAAqGUC.mjs} +1 -1
- package/lib/{xai-oauth-CXhnz0gO.mjs → xai-oauth-QPyScm4X.mjs} +1 -1
- package/lib/{xiaomi-psoUItal.mjs → xiaomi-CLzjxHAi.mjs} +1 -1
- package/lib/{zai-ApJ8HBgT.mjs → zai-wDp0g7VC.mjs} +1 -1
- package/lib/{zip-Kd602XNe.mjs → zip-CNtIFfyV.mjs} +7 -7
- package/package.json +3 -1
- package/presets/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
2
|
__installBunShim();
|
|
3
3
|
import { n as fs_promises_shim_exports, t as exists } from "./fs-promises-shim-Bx0fpxuk.mjs";
|
|
4
|
-
import { i as setRuntimeLogger, n as systemChromiumCandidatesForTest, r as runtimeLogger } from "./launch-
|
|
5
|
-
import { $ as isEnoent, A as formatCount, C as warn, D as installBunShim, F as $flag, N as $env, O as formatAge, P as $envpos, Q as hasFsCode, U as getAutoQaDbPath, V as VERSION, X as getRemoteDir, Y as getPuppeteerDir, at as findOnPath, b as debug, c as combineSignals, d as $which, et as isEnotdir, it as untilAborted, k as formatBytes$1, l as exec, m as readImageMetadata, nt as peekFile, q as getInstallId, tt as isProbablyBinary, u as render, x as error } from "./src-
|
|
6
|
-
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-
|
|
4
|
+
import { i as setRuntimeLogger, n as systemChromiumCandidatesForTest, r as runtimeLogger } from "./launch-Uq7920ng.mjs";
|
|
5
|
+
import { $ as isEnoent, A as formatCount, C as warn, D as installBunShim, F as $flag, N as $env, O as formatAge, P as $envpos, Q as hasFsCode, U as getAutoQaDbPath, V as VERSION, X as getRemoteDir, Y as getPuppeteerDir, at as findOnPath, b as debug, c as combineSignals, d as $which, et as isEnotdir, it as untilAborted, k as formatBytes$1, l as exec, m as readImageMetadata, nt as peekFile, q as getInstallId, tt as isProbablyBinary, u as render, x as error } from "./src-DUUqyo9y.mjs";
|
|
6
|
+
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-C-Lo_JC6.mjs";
|
|
7
7
|
import { t as sanitizeText } from "./sanitize-text-BCf5KmCf.mjs";
|
|
8
|
-
import { n as isRecord$
|
|
8
|
+
import { n as isRecord$5 } from "./type-guards-CttAANEx.mjs";
|
|
9
9
|
import { n as ToolError, r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
|
|
10
|
-
import { a as openArchive, c as sniffArchiveFormat, d as writeArchive, i as listArchiveRoot, o as parseArchivePathCandidates, r as formatArchiveEntryLines, s as readArchiveEntries, t as archiveFormatFromPath } from "./zip-
|
|
11
|
-
import { a as finalizeOutput, d as looksLikeHtml, n as MAX_OUTPUT_CHARS, t as MAX_BYTES, u as loadPage } from "./types-
|
|
10
|
+
import { a as openArchive, c as sniffArchiveFormat, d as writeArchive, i as listArchiveRoot, o as parseArchivePathCandidates, r as formatArchiveEntryLines, s as readArchiveEntries, t as archiveFormatFromPath } from "./zip-CNtIFfyV.mjs";
|
|
11
|
+
import { a as finalizeOutput, d as looksLikeHtml, n as MAX_OUTPUT_CHARS, t as MAX_BYTES, u as loadPage } from "./types-DT9vgZ3S.mjs";
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { TOOL_ABORTED, defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -16,7 +16,8 @@ import { HarnessError } from "@deepseek-ai/dsh-llm";
|
|
|
16
16
|
import { DSH_ENV_PREFIX, parseExitStatus } from "@deepseek-ai/dsh-shell";
|
|
17
17
|
import * as natives from "@oh-my-pi/pi-natives";
|
|
18
18
|
import { EditSession, EditStore, FileType, GrepOutputMode, Shell, astEdit, astGrep, diffLines, editDescription, enclosingBlockBoundaries, glob, grep, hashlineFormatHeader, hashlineFormatNumberedLines, hashlineStripPrefixes, htmlToMarkdown, invalidateFsScanCache, listWorkspace, notebookToEditableText, structuredPatchHunks, summarizeCode } from "@oh-my-pi/pi-natives";
|
|
19
|
-
import * as path from "node:path";
|
|
19
|
+
import * as path$1 from "node:path";
|
|
20
|
+
import path from "node:path";
|
|
20
21
|
import { spawn } from "node:child_process";
|
|
21
22
|
import * as os from "node:os";
|
|
22
23
|
import { tmpdir } from "node:os";
|
|
@@ -482,7 +483,7 @@ function buildNonInteractiveEnv(overrides, baseEnv = process.env, platform = pro
|
|
|
482
483
|
* shell experience.
|
|
483
484
|
*/
|
|
484
485
|
function loadFnEnvHelper() {
|
|
485
|
-
const candidates = [path.join(fileURLToPath(new URL(".", import.meta.url)), "shell-snapshot-fn-env.sh"), path.join(path.dirname(process.execPath), "shell-snapshot-fn-env.sh")];
|
|
486
|
+
const candidates = [path$1.join(fileURLToPath(new URL(".", import.meta.url)), "shell-snapshot-fn-env.sh"), path$1.join(path$1.dirname(process.execPath), "shell-snapshot-fn-env.sh")];
|
|
486
487
|
for (const candidate of candidates) try {
|
|
487
488
|
return fs.readFileSync(candidate, "utf-8");
|
|
488
489
|
} catch {}
|
|
@@ -564,9 +565,9 @@ function sanitizeSnapshotEnv(env) {
|
|
|
564
565
|
*/
|
|
565
566
|
function getShellConfigFile(shell, env) {
|
|
566
567
|
const home = env.HOME || os.homedir();
|
|
567
|
-
if (shell.includes("zsh")) return path.join(home, ".zshrc");
|
|
568
|
-
if (shell.includes("bash")) return path.join(home, ".bashrc");
|
|
569
|
-
return path.join(home, ".profile");
|
|
568
|
+
if (shell.includes("zsh")) return path$1.join(home, ".zshrc");
|
|
569
|
+
if (shell.includes("bash")) return path$1.join(home, ".bashrc");
|
|
570
|
+
return path$1.join(home, ".profile");
|
|
570
571
|
}
|
|
571
572
|
/**
|
|
572
573
|
* Generate the snapshot creation script.
|
|
@@ -676,8 +677,8 @@ async function getOrCreateSnapshot(shell, env, timeoutMs = SNAPSHOT_TIMEOUT_MS)
|
|
|
676
677
|
if (cached) cachedSnapshotPaths.delete(cacheKey);
|
|
677
678
|
if (process.platform === "win32") {
|
|
678
679
|
const uid = process.getuid?.();
|
|
679
|
-
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
680
|
-
const snapshotPath = path.join(snapshotDir, `snapshot-bash-${crypto.randomUUID()}.sh`);
|
|
680
|
+
const snapshotDir = path$1.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
681
|
+
const snapshotPath = path$1.join(snapshotDir, `snapshot-bash-${crypto.randomUUID()}.sh`);
|
|
681
682
|
try {
|
|
682
683
|
fs.mkdirSync(snapshotDir, {
|
|
683
684
|
recursive: true,
|
|
@@ -696,9 +697,9 @@ async function getOrCreateSnapshot(shell, env, timeoutMs = SNAPSHOT_TIMEOUT_MS)
|
|
|
696
697
|
}
|
|
697
698
|
const rcFile = getShellConfigFile(shell, env);
|
|
698
699
|
const uid = process.getuid?.();
|
|
699
|
-
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
700
|
+
const snapshotDir = path$1.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
700
701
|
const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : "sh";
|
|
701
|
-
const snapshotPath = path.join(snapshotDir, `snapshot-${shellName}-${crypto.randomUUID()}.sh`);
|
|
702
|
+
const snapshotPath = path$1.join(snapshotDir, `snapshot-${shellName}-${crypto.randomUUID()}.sh`);
|
|
702
703
|
try {
|
|
703
704
|
fs.mkdirSync(snapshotDir, {
|
|
704
705
|
recursive: true,
|
|
@@ -2023,23 +2024,23 @@ var TailBuffer = class {
|
|
|
2023
2024
|
*/
|
|
2024
2025
|
function resolveWindowsShell(env = process.env) {
|
|
2025
2026
|
const gitRoots = [
|
|
2026
|
-
env.ProgramFiles && path.join(env.ProgramFiles, "Git"),
|
|
2027
|
-
env["ProgramFiles(x86)"] && path.join(env["ProgramFiles(x86)"], "Git"),
|
|
2028
|
-
env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, "Programs", "Git"),
|
|
2027
|
+
env.ProgramFiles && path$1.join(env.ProgramFiles, "Git"),
|
|
2028
|
+
env["ProgramFiles(x86)"] && path$1.join(env["ProgramFiles(x86)"], "Git"),
|
|
2029
|
+
env.LOCALAPPDATA && path$1.join(env.LOCALAPPDATA, "Programs", "Git"),
|
|
2029
2030
|
env.GIT_INSTALL_ROOT,
|
|
2030
|
-
env.SCOOP && path.join(env.SCOOP, "apps", "git", "current"),
|
|
2031
|
-
env.USERPROFILE && path.join(env.USERPROFILE, "scoop", "apps", "git", "current")
|
|
2031
|
+
env.SCOOP && path$1.join(env.SCOOP, "apps", "git", "current"),
|
|
2032
|
+
env.USERPROFILE && path$1.join(env.USERPROFILE, "scoop", "apps", "git", "current")
|
|
2032
2033
|
];
|
|
2033
2034
|
for (const root of gitRoots) {
|
|
2034
2035
|
if (!root) continue;
|
|
2035
|
-
const candidate = path.join(root, "bin", "bash.exe");
|
|
2036
|
+
const candidate = path$1.join(root, "bin", "bash.exe");
|
|
2036
2037
|
if (fs.existsSync(candidate)) return candidate;
|
|
2037
2038
|
}
|
|
2038
2039
|
const bashOnPath = findOnPath("bash.exe", env);
|
|
2039
2040
|
if (bashOnPath) return bashOnPath;
|
|
2040
2041
|
const shOnPath = findOnPath("sh.exe", env);
|
|
2041
2042
|
if (shOnPath) {
|
|
2042
|
-
const siblingBash = path.join(path.dirname(shOnPath), "bash.exe");
|
|
2043
|
+
const siblingBash = path$1.join(path$1.dirname(shOnPath), "bash.exe");
|
|
2043
2044
|
return fs.existsSync(siblingBash) ? siblingBash : shOnPath;
|
|
2044
2045
|
}
|
|
2045
2046
|
return env.ComSpec || env.COMSPEC || "C:\\Windows\\System32\\cmd.exe";
|
|
@@ -2574,7 +2575,7 @@ function buildSessionKey(shell, prefix, snapshotPath, env, agentSessionKey, mini
|
|
|
2574
2575
|
*/
|
|
2575
2576
|
/** Directory holding all bash spill files. */
|
|
2576
2577
|
function spillDir() {
|
|
2577
|
-
return path.join(os.tmpdir(), "dsh-bash-spill");
|
|
2578
|
+
return path$1.join(os.tmpdir(), "dsh-bash-spill");
|
|
2578
2579
|
}
|
|
2579
2580
|
/** Age after which a spill file is considered orphaned and swept. */
|
|
2580
2581
|
const STALE_MS = 864e5;
|
|
@@ -2596,7 +2597,7 @@ function allocateOriginalFile() {
|
|
|
2596
2597
|
function allocate(kind) {
|
|
2597
2598
|
try {
|
|
2598
2599
|
fs.mkdirSync(spillDir(), { recursive: true });
|
|
2599
|
-
return path.join(spillDir(), `${kind}-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}.log`);
|
|
2600
|
+
return path$1.join(spillDir(), `${kind}-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}.log`);
|
|
2600
2601
|
} catch {
|
|
2601
2602
|
return;
|
|
2602
2603
|
}
|
|
@@ -2636,11 +2637,11 @@ function sweepStaleSpillFiles(now = Date.now()) {
|
|
|
2636
2637
|
} catch {}
|
|
2637
2638
|
};
|
|
2638
2639
|
try {
|
|
2639
|
-
for (const entry of fs.readdirSync(spillDir())) sweepOne(path.join(spillDir(), entry));
|
|
2640
|
+
for (const entry of fs.readdirSync(spillDir())) sweepOne(path$1.join(spillDir(), entry));
|
|
2640
2641
|
} catch {}
|
|
2641
2642
|
try {
|
|
2642
2643
|
const tmp = os.tmpdir();
|
|
2643
|
-
for (const entry of fs.readdirSync(tmp)) if (/^dsh-bash-[0-9a-f]{8}\.log$/.test(entry)) sweepOne(path.join(tmp, entry));
|
|
2644
|
+
for (const entry of fs.readdirSync(tmp)) if (/^dsh-bash-[0-9a-f]{8}\.log$/.test(entry)) sweepOne(path$1.join(tmp, entry));
|
|
2644
2645
|
} catch {}
|
|
2645
2646
|
return removed;
|
|
2646
2647
|
}
|
|
@@ -2788,7 +2789,7 @@ function startBashJob(options) {
|
|
|
2788
2789
|
function expandTilde$1(filePath, home = os.homedir()) {
|
|
2789
2790
|
if (filePath === "~") return home;
|
|
2790
2791
|
if (filePath.startsWith("~/") || filePath.startsWith("~\\")) return home + filePath.slice(1);
|
|
2791
|
-
if (filePath.startsWith("~")) return path.join(home, filePath.slice(1));
|
|
2792
|
+
if (filePath.startsWith("~")) return path$1.join(home, filePath.slice(1));
|
|
2792
2793
|
return filePath;
|
|
2793
2794
|
}
|
|
2794
2795
|
/**
|
|
@@ -2941,7 +2942,7 @@ function fileExists(filePath) {
|
|
|
2941
2942
|
function normalizeAtPrefix(filePath) {
|
|
2942
2943
|
if (!filePath.startsWith("@")) return filePath;
|
|
2943
2944
|
const withoutAt = filePath.slice(1);
|
|
2944
|
-
if (withoutAt.startsWith("/") || withoutAt === "~" || withoutAt.startsWith("~/") || path.win32.isAbsolute(withoutAt) || withoutAt.startsWith("agent://") || withoutAt.startsWith("artifact://") || withoutAt.startsWith("skill://") || withoutAt.startsWith("rule://") || withoutAt.startsWith("security://") || withoutAt.startsWith("local:") || withoutAt.startsWith("mcp://")) return withoutAt;
|
|
2945
|
+
if (withoutAt.startsWith("/") || withoutAt === "~" || withoutAt.startsWith("~/") || path$1.win32.isAbsolute(withoutAt) || withoutAt.startsWith("agent://") || withoutAt.startsWith("artifact://") || withoutAt.startsWith("skill://") || withoutAt.startsWith("rule://") || withoutAt.startsWith("security://") || withoutAt.startsWith("local:") || withoutAt.startsWith("mcp://")) return withoutAt;
|
|
2945
2946
|
return filePath;
|
|
2946
2947
|
}
|
|
2947
2948
|
function stripFileUrl(filePath) {
|
|
@@ -2956,7 +2957,7 @@ function expandTilde(filePath, home) {
|
|
|
2956
2957
|
const h = home ?? os.homedir();
|
|
2957
2958
|
if (filePath === "~") return h;
|
|
2958
2959
|
if (filePath.startsWith("~/") || filePath.startsWith("~\\")) return h + filePath.slice(1);
|
|
2959
|
-
if (filePath.startsWith("~")) return path.join(h, filePath.slice(1));
|
|
2960
|
+
if (filePath.startsWith("~")) return path$1.join(h, filePath.slice(1));
|
|
2960
2961
|
return filePath;
|
|
2961
2962
|
}
|
|
2962
2963
|
function expandPath(filePath) {
|
|
@@ -3231,17 +3232,17 @@ function resolveToCwd(filePath, cwd) {
|
|
|
3231
3232
|
const expanded = normalizeWindowsDriveAliasPath(expandPath(normalized));
|
|
3232
3233
|
assertNotInternalUrl(normalizeLocalScheme(expanded), normalized);
|
|
3233
3234
|
if (/^\/+$/.test(expanded)) return cwd;
|
|
3234
|
-
if (path.isAbsolute(expanded)) return expanded;
|
|
3235
|
-
return path.resolve(cwd, expanded);
|
|
3235
|
+
if (path$1.isAbsolute(expanded)) return expanded;
|
|
3236
|
+
return path$1.resolve(cwd, expanded);
|
|
3236
3237
|
}
|
|
3237
3238
|
function formatPathRelativeToCwd(filePath, cwd, options = {}) {
|
|
3238
|
-
const resolvedCwd = path.resolve(cwd);
|
|
3239
|
+
const resolvedCwd = path$1.resolve(cwd);
|
|
3239
3240
|
const normalized = normalizeLocalScheme(filePath);
|
|
3240
3241
|
if (isInternalUrlPath(normalized)) return normalized;
|
|
3241
3242
|
const expanded = expandPath(normalized);
|
|
3242
|
-
const resolvedPath = path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(cwd, expanded);
|
|
3243
|
-
const relative = path.relative(resolvedCwd, resolvedPath);
|
|
3244
|
-
let displayPath = normalizePosixPath(relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative) ? relative || "." : resolvedPath);
|
|
3243
|
+
const resolvedPath = path$1.isAbsolute(expanded) ? path$1.resolve(expanded) : path$1.resolve(cwd, expanded);
|
|
3244
|
+
const relative = path$1.relative(resolvedCwd, resolvedPath);
|
|
3245
|
+
let displayPath = normalizePosixPath(relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative) ? relative || "." : resolvedPath);
|
|
3245
3246
|
if (options.trailingSlash && displayPath !== "." && !displayPath.endsWith("/")) displayPath += "/";
|
|
3246
3247
|
return displayPath;
|
|
3247
3248
|
}
|
|
@@ -3484,16 +3485,16 @@ function buildBraceUnion(patterns) {
|
|
|
3484
3485
|
}
|
|
3485
3486
|
function findCommonBasePath(paths) {
|
|
3486
3487
|
if (paths.length === 0) return ".";
|
|
3487
|
-
let commonParts = path.resolve(paths[0]).split(path.sep);
|
|
3488
|
+
let commonParts = path$1.resolve(paths[0]).split(path$1.sep);
|
|
3488
3489
|
for (const candidatePath of paths.slice(1)) {
|
|
3489
|
-
const candidateParts = path.resolve(candidatePath).split(path.sep);
|
|
3490
|
+
const candidateParts = path$1.resolve(candidatePath).split(path$1.sep);
|
|
3490
3491
|
let sharedCount = 0;
|
|
3491
3492
|
const maxShared = Math.min(commonParts.length, candidateParts.length);
|
|
3492
3493
|
while (sharedCount < maxShared && commonParts[sharedCount] === candidateParts[sharedCount]) sharedCount += 1;
|
|
3493
3494
|
commonParts = commonParts.slice(0, sharedCount);
|
|
3494
3495
|
}
|
|
3495
|
-
if (commonParts.length === 0) return path.parse(path.resolve(paths[0])).root;
|
|
3496
|
-
return commonParts.join(path.sep) || path.parse(path.resolve(paths[0])).root;
|
|
3496
|
+
if (commonParts.length === 0) return path$1.parse(path$1.resolve(paths[0])).root;
|
|
3497
|
+
return commonParts.join(path$1.sep) || path$1.parse(path$1.resolve(paths[0])).root;
|
|
3497
3498
|
}
|
|
3498
3499
|
function toScopeDisplay(items, cwd) {
|
|
3499
3500
|
return items.map((item) => formatPathRelativeToCwd(item, cwd, { trailingSlash: item.endsWith("/") || item.endsWith("\\") })).join(", ");
|
|
@@ -3513,14 +3514,14 @@ async function resolveSearchPathItems(pathItems, cwd, suffixGlob, fanOutFileItem
|
|
|
3513
3514
|
const allExactFiles = !suffixGlob && parsedItems.every((item) => !item.parsedPath.glob && item.stat.isFile());
|
|
3514
3515
|
const commonBasePath = findCommonBasePath(parsedItems.map((item) => item.absoluteBasePath));
|
|
3515
3516
|
const combinedPatterns = parsedItems.map((item) => {
|
|
3516
|
-
const relativeBasePath = normalizePosixPath(path.relative(commonBasePath, item.absoluteBasePath)) || ".";
|
|
3517
|
+
const relativeBasePath = normalizePosixPath(path$1.relative(commonBasePath, item.absoluteBasePath)) || ".";
|
|
3517
3518
|
if (item.parsedPath.glob) {
|
|
3518
3519
|
const pathGlob = joinRelativeGlob(relativeBasePath, item.parsedPath.glob);
|
|
3519
3520
|
return combineSearchGlobs(pathGlob, suffixGlob) ?? pathGlob;
|
|
3520
3521
|
}
|
|
3521
3522
|
if (suffixGlob) return combineSearchGlobs(relativeBasePath === "." ? void 0 : relativeBasePath, suffixGlob) ?? suffixGlob;
|
|
3522
3523
|
if (item.stat.isDirectory()) return joinRelativeGlob(relativeBasePath, "**/*");
|
|
3523
|
-
return relativeBasePath === "." ? path.basename(item.absoluteBasePath) : relativeBasePath;
|
|
3524
|
+
return relativeBasePath === "." ? path$1.basename(item.absoluteBasePath) : relativeBasePath;
|
|
3524
3525
|
});
|
|
3525
3526
|
const commonIsRequestedScope = parsedItems.some((item) => item.absoluteBasePath === commonBasePath);
|
|
3526
3527
|
const demotesFileItem = fanOutFileItems && !allExactFiles && parsedItems.some((item) => !item.parsedPath.glob && item.stat.isFile());
|
|
@@ -3641,7 +3642,7 @@ async function findUniqueWorkspaceSuffix(rawPath, cwd, signal) {
|
|
|
3641
3642
|
}
|
|
3642
3643
|
if (matches.length !== 1) return null;
|
|
3643
3644
|
return {
|
|
3644
|
-
absolutePath: path.resolve(cwd, matches[0]),
|
|
3645
|
+
absolutePath: path$1.resolve(cwd, matches[0]),
|
|
3645
3646
|
displayPath: matches[0]
|
|
3646
3647
|
};
|
|
3647
3648
|
}
|
|
@@ -3674,7 +3675,7 @@ async function resolveToolSearchScope(opts) {
|
|
|
3674
3675
|
const resolved = opts.resolveExternalUrl ? await opts.resolveExternalUrl(rawPath) : void 0;
|
|
3675
3676
|
if (resolved) {
|
|
3676
3677
|
resolvedPathInputs.push(resolved.sourcePath);
|
|
3677
|
-
if (opts.trackImmutableSources && resolved.immutable) immutableSourcePaths.add(path.resolve(resolved.sourcePath));
|
|
3678
|
+
if (opts.trackImmutableSources && resolved.immutable) immutableSourcePaths.add(path$1.resolve(resolved.sourcePath));
|
|
3678
3679
|
continue;
|
|
3679
3680
|
}
|
|
3680
3681
|
throw new ToolError(`Cannot ${internalUrlAction} external URL: ${rawPath}. Use \`read\` to fetch web content, then search the returned text.`);
|
|
@@ -3694,7 +3695,7 @@ async function resolveToolSearchScope(opts) {
|
|
|
3694
3695
|
pathOnly: true
|
|
3695
3696
|
});
|
|
3696
3697
|
if (!resource.sourcePath) throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);
|
|
3697
|
-
if (opts.trackImmutableSources && resource.immutable) immutableSourcePaths.add(path.resolve(resource.sourcePath));
|
|
3698
|
+
if (opts.trackImmutableSources && resource.immutable) immutableSourcePaths.add(path$1.resolve(resource.sourcePath));
|
|
3698
3699
|
resolvedPathInputs.push(resource.sourcePath);
|
|
3699
3700
|
}
|
|
3700
3701
|
let missingPaths = [];
|
|
@@ -4235,6 +4236,15 @@ const TOOL_PLUS_FIELDS = [
|
|
|
4235
4236
|
group: "behavior",
|
|
4236
4237
|
tool: "bash"
|
|
4237
4238
|
},
|
|
4239
|
+
{
|
|
4240
|
+
name: "webCards",
|
|
4241
|
+
kind: "boolean",
|
|
4242
|
+
default: true,
|
|
4243
|
+
labelKey: "webCards",
|
|
4244
|
+
hintKey: "webCardsHint",
|
|
4245
|
+
group: "behavior",
|
|
4246
|
+
tool: "bash"
|
|
4247
|
+
},
|
|
4238
4248
|
{
|
|
4239
4249
|
name: "autoBackgroundMs",
|
|
4240
4250
|
kind: "number",
|
|
@@ -4930,6 +4940,7 @@ const Config = z.object({
|
|
|
4930
4940
|
snapshotEnabled: z.boolean().default(fieldDefault("snapshotEnabled", true)),
|
|
4931
4941
|
rmSafe: z.boolean().default(fieldDefault("rmSafe", true)),
|
|
4932
4942
|
useShellCommandWrapper: z.boolean().default(fieldDefault("useShellCommandWrapper", false)),
|
|
4943
|
+
webCards: z.boolean().default(fieldDefault("webCards", true)),
|
|
4933
4944
|
maxBackgroundJobs: z.number().default(fieldDefault("maxBackgroundJobs", 15)),
|
|
4934
4945
|
outputTruncateStrategy: z.union(["bytes", "lines"]).default(fieldDefault("outputTruncateStrategy", "bytes")),
|
|
4935
4946
|
outputTruncateTriggerBytes: z.number().default(fieldDefault("outputTruncateTriggerBytes", 10240)),
|
|
@@ -5027,6 +5038,7 @@ function resolveConfig(config) {
|
|
|
5027
5038
|
snapshotEnabled: config.snapshotEnabled ?? fieldDefault("snapshotEnabled", true),
|
|
5028
5039
|
rmSafe: config.rmSafe ?? fieldDefault("rmSafe", true),
|
|
5029
5040
|
useShellCommandWrapper: config.useShellCommandWrapper ?? fieldDefault("useShellCommandWrapper", false),
|
|
5041
|
+
webCards: config.webCards ?? fieldDefault("webCards", true),
|
|
5030
5042
|
maxBackgroundJobs: config.maxBackgroundJobs ?? fieldDefault("maxBackgroundJobs", 15),
|
|
5031
5043
|
outputTruncate: {
|
|
5032
5044
|
strategy: config.outputTruncateStrategy ?? fieldDefault("outputTruncateStrategy", DEFAULT_OUTPUT_TRUNCATE.strategy),
|
|
@@ -5435,6 +5447,47 @@ function installBrowserProbeRpc(ctx, deps) {
|
|
|
5435
5447
|
return () => disposer?.();
|
|
5436
5448
|
}
|
|
5437
5449
|
//#endregion
|
|
5450
|
+
//#region src/web/host/bash.ts
|
|
5451
|
+
/** Whether `value` is a plain object (never `null` or an array). */
|
|
5452
|
+
function isRecord$4(value) {
|
|
5453
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5454
|
+
}
|
|
5455
|
+
/**
|
|
5456
|
+
* Project a bash result value into terminal-card metadata.
|
|
5457
|
+
*
|
|
5458
|
+
* Every emitted field is a plain JSON scalar and no key is ever set to
|
|
5459
|
+
* `undefined`: the metadata rides `presentationMeta`, where a non-lossless
|
|
5460
|
+
* payload turns a successful call into a failed one. An unrecognized value
|
|
5461
|
+
* yields `null`, which simply leaves the row on the generic shell.
|
|
5462
|
+
* @param value - the tool's own output value (foreground or background arm).
|
|
5463
|
+
* @returns the card metadata, or `null` when the value is not a usable bash result.
|
|
5464
|
+
*/
|
|
5465
|
+
function bashCardMeta(value) {
|
|
5466
|
+
if (!isRecord$4(value)) return null;
|
|
5467
|
+
if (value.kind === "background") {
|
|
5468
|
+
const jobId = value.jobId;
|
|
5469
|
+
if (typeof jobId !== "string" || jobId.trim() === "") return null;
|
|
5470
|
+
return {
|
|
5471
|
+
kind: "terminal",
|
|
5472
|
+
mode: "background",
|
|
5473
|
+
jobId
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
if (value.kind !== "foreground") return null;
|
|
5477
|
+
const { exitCode, timedOut, aborted } = value;
|
|
5478
|
+
if (exitCode !== null && !(typeof exitCode === "number" && Number.isInteger(exitCode))) return null;
|
|
5479
|
+
if (typeof timedOut !== "boolean" || typeof aborted !== "boolean") return null;
|
|
5480
|
+
const workingDir = value.workingDir;
|
|
5481
|
+
return {
|
|
5482
|
+
kind: "terminal",
|
|
5483
|
+
mode: "foreground",
|
|
5484
|
+
exitCode,
|
|
5485
|
+
timedOut,
|
|
5486
|
+
aborted,
|
|
5487
|
+
...typeof workingDir === "string" && workingDir !== "" ? { workingDir } : {}
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
//#endregion
|
|
5438
5491
|
//#region src/config/truncate.ts
|
|
5439
5492
|
/**
|
|
5440
5493
|
* Config-driven output truncation for background→foreground completion text.
|
|
@@ -5811,8 +5864,8 @@ function canonicalSnapshotKey(absolutePath) {
|
|
|
5811
5864
|
return fs.realpathSync.native(absolutePath);
|
|
5812
5865
|
} catch {
|
|
5813
5866
|
try {
|
|
5814
|
-
const parent = fs.realpathSync.native(path.dirname(absolutePath));
|
|
5815
|
-
return path.join(parent, path.basename(absolutePath));
|
|
5867
|
+
const parent = fs.realpathSync.native(path$1.dirname(absolutePath));
|
|
5868
|
+
return path$1.join(parent, path$1.basename(absolutePath));
|
|
5816
5869
|
} catch {
|
|
5817
5870
|
return absolutePath;
|
|
5818
5871
|
}
|
|
@@ -6071,9 +6124,9 @@ function unescapeMarkerLikeLine(line) {
|
|
|
6071
6124
|
return ESCAPED_MARKER_RE.test(line) ? line.replace("# %%", "# %") : line;
|
|
6072
6125
|
}
|
|
6073
6126
|
function isNotebookPath(filePath) {
|
|
6074
|
-
return path.extname(filePath).toLowerCase() === ".ipynb";
|
|
6127
|
+
return path$1.extname(filePath).toLowerCase() === ".ipynb";
|
|
6075
6128
|
}
|
|
6076
|
-
function isRecord$
|
|
6129
|
+
function isRecord$3(value) {
|
|
6077
6130
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6078
6131
|
}
|
|
6079
6132
|
function isCellType(value) {
|
|
@@ -6107,11 +6160,11 @@ function createEmptyNotebook() {
|
|
|
6107
6160
|
};
|
|
6108
6161
|
}
|
|
6109
6162
|
function validateNotebook(value, displayPath) {
|
|
6110
|
-
if (!isRecord$
|
|
6163
|
+
if (!isRecord$3(value)) throw new Error(`Invalid notebook structure (expected object): ${displayPath}`);
|
|
6111
6164
|
if (!Array.isArray(value.cells)) throw new Error(`Invalid notebook structure (missing cells array): ${displayPath}`);
|
|
6112
6165
|
for (let index = 0; index < value.cells.length; index++) {
|
|
6113
6166
|
const cell = value.cells[index];
|
|
6114
|
-
if (!isRecord$
|
|
6167
|
+
if (!isRecord$3(cell) || !isCellType(cell.cell_type)) throw new Error(`Invalid notebook cell ${index} in ${displayPath}`);
|
|
6115
6168
|
}
|
|
6116
6169
|
return value;
|
|
6117
6170
|
}
|
|
@@ -6325,7 +6378,7 @@ async function resolveArtifactFile(url, context) {
|
|
|
6325
6378
|
}
|
|
6326
6379
|
const match = files.find((f) => f.startsWith(`${id}.`));
|
|
6327
6380
|
if (match) {
|
|
6328
|
-
foundPath = path.join(dir, match);
|
|
6381
|
+
foundPath = path$1.join(dir, match);
|
|
6329
6382
|
break;
|
|
6330
6383
|
}
|
|
6331
6384
|
for (const f of files) {
|
|
@@ -6737,7 +6790,7 @@ function renderProfileNode(node, indent, ctx) {
|
|
|
6737
6790
|
function isCpuProfilePath(filePath) {
|
|
6738
6791
|
return /\.cpuprofile$/i.test(filePath);
|
|
6739
6792
|
}
|
|
6740
|
-
function isRecord(value) {
|
|
6793
|
+
function isRecord$2(value) {
|
|
6741
6794
|
return typeof value === "object" && value !== null;
|
|
6742
6795
|
}
|
|
6743
6796
|
/**
|
|
@@ -6752,14 +6805,14 @@ function parseCpuProfile(text) {
|
|
|
6752
6805
|
} catch {
|
|
6753
6806
|
return null;
|
|
6754
6807
|
}
|
|
6755
|
-
if (isRecord(data) && !("nodes" in data) && isRecord(data.profile)) data = data.profile;
|
|
6756
|
-
if (!isRecord(data)) return null;
|
|
6808
|
+
if (isRecord$2(data) && !("nodes" in data) && isRecord$2(data.profile)) data = data.profile;
|
|
6809
|
+
if (!isRecord$2(data)) return null;
|
|
6757
6810
|
const { nodes, startTime, endTime, samples, timeDeltas } = data;
|
|
6758
6811
|
if (!Array.isArray(nodes) || nodes.length === 0) return null;
|
|
6759
6812
|
if (typeof startTime !== "number" || typeof endTime !== "number") return null;
|
|
6760
6813
|
for (const node of nodes) {
|
|
6761
|
-
if (!isRecord(node) || typeof node.id !== "number") return null;
|
|
6762
|
-
if (!isRecord(node.callFrame) || typeof node.callFrame.functionName !== "string") return null;
|
|
6814
|
+
if (!isRecord$2(node) || typeof node.id !== "number") return null;
|
|
6815
|
+
if (!isRecord$2(node.callFrame) || typeof node.callFrame.functionName !== "string") return null;
|
|
6763
6816
|
}
|
|
6764
6817
|
return {
|
|
6765
6818
|
nodes,
|
|
@@ -7361,7 +7414,7 @@ function renderSampleProfile(text) {
|
|
|
7361
7414
|
* walker. Used by the read tool's directory-listing path.
|
|
7362
7415
|
*/
|
|
7363
7416
|
async function buildDirectoryTree(cwd, options = {}) {
|
|
7364
|
-
const rootPath = path.resolve(cwd);
|
|
7417
|
+
const rootPath = path$1.resolve(cwd);
|
|
7365
7418
|
const maxDepth = options.maxDepth ?? 1;
|
|
7366
7419
|
const perDirLimit = options.perDirLimit === void 0 ? null : options.perDirLimit;
|
|
7367
7420
|
const rootLimit = options.rootLimit === void 0 ? perDirLimit : options.rootLimit;
|
|
@@ -9708,7 +9761,7 @@ function normalizeMime(contentType) {
|
|
|
9708
9761
|
}
|
|
9709
9762
|
function getFilenameExtensionHint(filename) {
|
|
9710
9763
|
if (filename.toLowerCase().endsWith(".tar.gz")) return ".tar.gz";
|
|
9711
|
-
return path.extname(filename).toLowerCase();
|
|
9764
|
+
return path$1.extname(filename).toLowerCase();
|
|
9712
9765
|
}
|
|
9713
9766
|
/**
|
|
9714
9767
|
* Get extension from URL or Content-Disposition
|
|
@@ -9860,7 +9913,7 @@ function extractDocumentLinks(html, baseUrl) {
|
|
|
9860
9913
|
for (const tag of anchorTags) {
|
|
9861
9914
|
const href = getHtmlAttribute(tag, "href");
|
|
9862
9915
|
if (!href) continue;
|
|
9863
|
-
const ext = path.extname(href).toLowerCase();
|
|
9916
|
+
const ext = path$1.extname(href).toLowerCase();
|
|
9864
9917
|
if (!CONVERTIBLE_EXTENSIONS.has(ext)) continue;
|
|
9865
9918
|
const resolved = href.startsWith("http") ? href : new URL(href, baseUrl).href;
|
|
9866
9919
|
if (seen.has(resolved)) continue;
|
|
@@ -10043,7 +10096,7 @@ async function renderHtmlToText(url, html, timeout, settings, userSignal, storag
|
|
|
10043
10096
|
},
|
|
10044
10097
|
browser: async () => {
|
|
10045
10098
|
if (settings.get("browser.readerEnabled") === false) return null;
|
|
10046
|
-
const { renderUrlWithBrowser } = await import("./browser-render-
|
|
10099
|
+
const { renderUrlWithBrowser } = await import("./browser-render-i7uYOl4f.mjs");
|
|
10047
10100
|
return renderUrlWithBrowser(url, {
|
|
10048
10101
|
timeoutMs: remoteBudgetMs,
|
|
10049
10102
|
signal: userSignal
|
|
@@ -10190,8 +10243,8 @@ function buildBinaryPayloadResult(url, finalUrl, mime, method, content, fetchedA
|
|
|
10190
10243
|
};
|
|
10191
10244
|
}
|
|
10192
10245
|
async function withTempBinaryFile(prefix, extension, bytes, readTempFile) {
|
|
10193
|
-
const tempDir = await fs_promises_shim_exports.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
10194
|
-
const tempPath = path.join(tempDir, `payload${extension}`);
|
|
10246
|
+
const tempDir = await fs_promises_shim_exports.mkdtemp(path$1.join(os.tmpdir(), prefix));
|
|
10247
|
+
const tempPath = path$1.join(tempDir, `payload${extension}`);
|
|
10195
10248
|
try {
|
|
10196
10249
|
await Bun.write(tempPath, bytes);
|
|
10197
10250
|
return await readTempFile(tempPath);
|
|
@@ -10264,7 +10317,7 @@ let specialHandlersPromise;
|
|
|
10264
10317
|
* requires a special handler, so we keep them out of the cold-startup graph.
|
|
10265
10318
|
*/
|
|
10266
10319
|
function loadSpecialHandlers() {
|
|
10267
|
-
specialHandlersPromise ??= import("./scrapers-
|
|
10320
|
+
specialHandlersPromise ??= import("./scrapers-DU4u4-Co.mjs").then((m) => m.specialHandlers);
|
|
10268
10321
|
return specialHandlersPromise;
|
|
10269
10322
|
}
|
|
10270
10323
|
/**
|
|
@@ -10409,7 +10462,7 @@ async function renderUrl(url, timeout, raw, settings, signal, storage, fetchOver
|
|
|
10409
10462
|
};
|
|
10410
10463
|
}
|
|
10411
10464
|
const imageBridge = session?.imageBridge?.();
|
|
10412
|
-
const displayName = path.basename(new URL(finalUrl).pathname) || "image";
|
|
10465
|
+
const displayName = path$1.basename(new URL(finalUrl).pathname) || "image";
|
|
10413
10466
|
let bridgeNote;
|
|
10414
10467
|
let committed;
|
|
10415
10468
|
let savedRef;
|
|
@@ -10728,7 +10781,7 @@ async function findArtifactPath(session, artifactId) {
|
|
|
10728
10781
|
if (!artifactsDir) return null;
|
|
10729
10782
|
try {
|
|
10730
10783
|
const match = (await fs_promises_shim_exports.readdir(artifactsDir)).find((file) => file.startsWith(`${artifactId}.`));
|
|
10731
|
-
return match ? path.join(artifactsDir, match) : null;
|
|
10784
|
+
return match ? path$1.join(artifactsDir, match) : null;
|
|
10732
10785
|
} catch {
|
|
10733
10786
|
return null;
|
|
10734
10787
|
}
|
|
@@ -10766,10 +10819,10 @@ function readUrlContentExtension(finalUrl) {
|
|
|
10766
10819
|
async function materializeReadUrlContent(session, entry, raw) {
|
|
10767
10820
|
const root = session.getArtifactsDir?.();
|
|
10768
10821
|
if (!root) throw new ToolError("Cannot search URL output because this session cannot materialize read artifacts.");
|
|
10769
|
-
const dir = path.join(root, "url-search");
|
|
10822
|
+
const dir = path$1.join(root, "url-search");
|
|
10770
10823
|
await fs_promises_shim_exports.mkdir(dir, { recursive: true });
|
|
10771
10824
|
const hash = Bun.hash(`${raw ? "raw" : "rendered"}:${entry.details.finalUrl}`).toString(36);
|
|
10772
|
-
const contentPath = path.join(dir, `${hash}${readUrlContentExtension(entry.details.finalUrl)}`);
|
|
10825
|
+
const contentPath = path$1.join(dir, `${hash}${readUrlContentExtension(entry.details.finalUrl)}`);
|
|
10773
10826
|
await Bun.write(contentPath, entry.content);
|
|
10774
10827
|
return contentPath;
|
|
10775
10828
|
}
|
|
@@ -11097,7 +11150,7 @@ function themeExtensionKey(filePath) {
|
|
|
11097
11150
|
* Language id for syntax highlighting and UI (icons, read tool), or undefined if unknown.
|
|
11098
11151
|
*/
|
|
11099
11152
|
function getLanguageFromPath(filePath) {
|
|
11100
|
-
const baseName = path.basename(filePath).toLowerCase();
|
|
11153
|
+
const baseName = path$1.basename(filePath).toLowerCase();
|
|
11101
11154
|
if (baseName.startsWith(".env.")) return "env";
|
|
11102
11155
|
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") return "dockerfile";
|
|
11103
11156
|
if (baseName === ".emacs") return "emacs-lisp";
|
|
@@ -11115,10 +11168,10 @@ function prependLineNumbers(text, startNum) {
|
|
|
11115
11168
|
return text.split("\n").map((line, i) => `${startNum + i}|${line}`).join("\n");
|
|
11116
11169
|
}
|
|
11117
11170
|
function formatReadHashlineHeader(displayPath, tag) {
|
|
11118
|
-
return formatHashlineHeader(path.isAbsolute(displayPath) ? shortenPath(displayPath) : displayPath, tag);
|
|
11171
|
+
return formatHashlineHeader(path$1.isAbsolute(displayPath) ? shortenPath(displayPath) : displayPath, tag);
|
|
11119
11172
|
}
|
|
11120
11173
|
function recordFullHashlineContext(session, absolutePath, displayPath, fullText) {
|
|
11121
|
-
if (!absolutePath || !path.isAbsolute(absolutePath)) return void 0;
|
|
11174
|
+
if (!absolutePath || !path$1.isAbsolute(absolutePath)) return void 0;
|
|
11122
11175
|
const normalized = normalizeToLF(fullText);
|
|
11123
11176
|
const tag = getFileSnapshotStore(session).recordSnapshot(canonicalSnapshotKey(absolutePath), normalized);
|
|
11124
11177
|
return {
|
|
@@ -11213,7 +11266,7 @@ function lineNumbersFromSpans(spans) {
|
|
|
11213
11266
|
return lines;
|
|
11214
11267
|
}
|
|
11215
11268
|
function recordInMemorySeenLines(session, absolutePath, fullText, seenLines) {
|
|
11216
|
-
if (!absolutePath || !path.isAbsolute(absolutePath) || !seenLines || seenLines.length === 0) return;
|
|
11269
|
+
if (!absolutePath || !path$1.isAbsolute(absolutePath) || !seenLines || seenLines.length === 0) return;
|
|
11217
11270
|
getFileSnapshotStore(session).recordSnapshot(canonicalSnapshotKey(absolutePath), normalizeToLF(fullText), [...seenLines]);
|
|
11218
11271
|
}
|
|
11219
11272
|
function lineNumbersFromEntries(entries) {
|
|
@@ -11459,7 +11512,7 @@ function markMarkdownContentType(session, details, filePath) {
|
|
|
11459
11512
|
}
|
|
11460
11513
|
//#endregion
|
|
11461
11514
|
//#region src/tools/omp/tools/read-path-resolution.ts
|
|
11462
|
-
const REMOTE_MOUNT_PREFIX = getRemoteDir() + path.sep;
|
|
11515
|
+
const REMOTE_MOUNT_PREFIX = getRemoteDir() + path$1.sep;
|
|
11463
11516
|
function isRemoteMountPath(absolutePath) {
|
|
11464
11517
|
return absolutePath.startsWith(REMOTE_MOUNT_PREFIX);
|
|
11465
11518
|
}
|
|
@@ -11789,7 +11842,7 @@ const MAX_SUMMARY_LINES = 2e4;
|
|
|
11789
11842
|
* unless `read.summarize.prose` opts them in.
|
|
11790
11843
|
*/
|
|
11791
11844
|
function isProseSummaryPath(filePath) {
|
|
11792
|
-
return isMarkdownPath(filePath) || path.extname(filePath).toLowerCase() === ".txt";
|
|
11845
|
+
return isMarkdownPath(filePath) || path$1.extname(filePath).toLowerCase() === ".txt";
|
|
11793
11846
|
}
|
|
11794
11847
|
async function trySummarize(session, absolutePath, fileSize, signal) {
|
|
11795
11848
|
if (fileSize > MAX_SUMMARY_BYTES) return null;
|
|
@@ -12029,7 +12082,7 @@ function openAutoQaDb() {
|
|
|
12029
12082
|
const dbPath = getAutoQaDbPath();
|
|
12030
12083
|
if (!dbPath) return null;
|
|
12031
12084
|
try {
|
|
12032
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
12085
|
+
fs.mkdirSync(path$1.dirname(dbPath), { recursive: true });
|
|
12033
12086
|
const db = new Database(dbPath, { create: true });
|
|
12034
12087
|
db.run("PRAGMA busy_timeout = 5000");
|
|
12035
12088
|
db.exec(`
|
|
@@ -12741,7 +12794,7 @@ var ReadTool = class {
|
|
|
12741
12794
|
"image/jpeg",
|
|
12742
12795
|
"image/webp",
|
|
12743
12796
|
"image/gif"
|
|
12744
|
-
])).has(loaded.mimeType) ? loaded.mimeType : void 0) ?? imageMediaTypeForExtension(path.extname(absolutePath));
|
|
12797
|
+
])).has(loaded.mimeType) ? loaded.mimeType : void 0) ?? imageMediaTypeForExtension(path$1.extname(absolutePath));
|
|
12745
12798
|
if (!commitMediaType) return finish([{
|
|
12746
12799
|
type: "text",
|
|
12747
12800
|
text: formatImageUnsupportedNote(displayPath, {
|
|
@@ -12754,7 +12807,7 @@ var ReadTool = class {
|
|
|
12754
12807
|
const saved = await saveImageViaBridge(bridge, {
|
|
12755
12808
|
data: new Uint8Array(Buffer.from(loaded.data, "base64")),
|
|
12756
12809
|
mediaType: commitMediaType,
|
|
12757
|
-
name: path.basename(absolutePath),
|
|
12810
|
+
name: path$1.basename(absolutePath),
|
|
12758
12811
|
displayPath
|
|
12759
12812
|
});
|
|
12760
12813
|
const blocks = imageContentBlocks(displayPath, saved);
|
|
@@ -12774,15 +12827,15 @@ var ReadTool = class {
|
|
|
12774
12827
|
#approvedPlanAlias(missingAbsolutePath) {
|
|
12775
12828
|
const planReferencePath = this.session.getPlanReferencePath?.();
|
|
12776
12829
|
if (!planReferencePath?.startsWith("local:")) return void 0;
|
|
12777
|
-
const requestedPath = path.resolve(missingAbsolutePath);
|
|
12778
|
-
if (path.dirname(requestedPath) !== path.resolve(this.session.cwd)) return void 0;
|
|
12830
|
+
const requestedPath = path$1.resolve(missingAbsolutePath);
|
|
12831
|
+
if (path$1.dirname(requestedPath) !== path$1.resolve(this.session.cwd)) return void 0;
|
|
12779
12832
|
const localProtocolOptions = this.session.localProtocolOptions ?? {
|
|
12780
12833
|
getArtifactsDir: () => this.session.getArtifactsDir?.() ?? null,
|
|
12781
12834
|
getSessionId: () => this.session.getSessionId?.() ?? null
|
|
12782
12835
|
};
|
|
12783
12836
|
try {
|
|
12784
12837
|
const approvedPlanPath = resolveLocalUrlToPath(planReferencePath, localProtocolOptions);
|
|
12785
|
-
return path.basename(requestedPath) === path.basename(approvedPlanPath) ? approvedPlanPath : void 0;
|
|
12838
|
+
return path$1.basename(requestedPath) === path$1.basename(approvedPlanPath) ? approvedPlanPath : void 0;
|
|
12786
12839
|
} catch {
|
|
12787
12840
|
return;
|
|
12788
12841
|
}
|
|
@@ -13078,7 +13131,7 @@ var ReadTool = class {
|
|
|
13078
13131
|
if (parsed.kind === "conflicts") return this.#readFileConflicts(absolutePath, suffixResolution, signal);
|
|
13079
13132
|
const imageMetadata = await readImageMetadata(absolutePath);
|
|
13080
13133
|
const mimeType = imageMetadata?.mimeType;
|
|
13081
|
-
const ext = path.extname(absolutePath).toLowerCase();
|
|
13134
|
+
const ext = path$1.extname(absolutePath).toLowerCase();
|
|
13082
13135
|
const resolvedDisplayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
13083
13136
|
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext);
|
|
13084
13137
|
if (!mimeType && !isRawSelector(parsed) && fileSize <= MAX_PROFILE_SUMMARY_BYTES) {
|
|
@@ -13718,6 +13771,253 @@ var ReadTool = class {
|
|
|
13718
13771
|
}
|
|
13719
13772
|
};
|
|
13720
13773
|
//#endregion
|
|
13774
|
+
//#region src/web/contract.ts
|
|
13775
|
+
/**
|
|
13776
|
+
* Cap on the serialized `edit` metadata. The engine's unified diff is already
|
|
13777
|
+
* bounded per file but unbounded across files; past this budget whole trailing
|
|
13778
|
+
* files are dropped, and once nothing is left the call falls back to the
|
|
13779
|
+
* generic shell rather than writing an oversized session entry.
|
|
13780
|
+
*/
|
|
13781
|
+
const EDIT_META_MAX_BYTES = 131072;
|
|
13782
|
+
/** Cap on the `ast_edit` change preview (the engine's own display text). */
|
|
13783
|
+
const AST_EDIT_META_MAX_BYTES = 65536;
|
|
13784
|
+
/** Cap on the serialized `read` metadata (the returned window's lines). */
|
|
13785
|
+
const READ_META_MAX_BYTES = 262144;
|
|
13786
|
+
/**
|
|
13787
|
+
* Cap on the serialized search metadata. Same value and meaning as the shipped
|
|
13788
|
+
* search card's own `searchMetaMaxBytes`.
|
|
13789
|
+
*/
|
|
13790
|
+
const SEARCH_META_MAX_BYTES = 65536;
|
|
13791
|
+
/** UTF-8 byte length of `text`, without allocating an encoder. */
|
|
13792
|
+
function utf8ByteLength(text) {
|
|
13793
|
+
let bytes = 0;
|
|
13794
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
13795
|
+
const code = text.charCodeAt(index);
|
|
13796
|
+
if (code < 128) bytes += 1;
|
|
13797
|
+
else if (code < 2048) bytes += 2;
|
|
13798
|
+
else if (code >= 55296 && code <= 56319) {
|
|
13799
|
+
bytes += 4;
|
|
13800
|
+
index += 1;
|
|
13801
|
+
} else bytes += 3;
|
|
13802
|
+
}
|
|
13803
|
+
return bytes;
|
|
13804
|
+
}
|
|
13805
|
+
/** UTF-8 byte length of a value's JSON form; the unit every cap above uses. */
|
|
13806
|
+
function jsonByteLength(value) {
|
|
13807
|
+
const json = JSON.stringify(value);
|
|
13808
|
+
return json === void 0 ? 0 : utf8ByteLength(json);
|
|
13809
|
+
}
|
|
13810
|
+
/**
|
|
13811
|
+
* Longest prefix of `text` that fits in `cap` UTF-8 bytes, never splitting a
|
|
13812
|
+
* surrogate pair.
|
|
13813
|
+
* @param text - the text to bound.
|
|
13814
|
+
* @param cap - the byte budget.
|
|
13815
|
+
* @returns `text` when it already fits, otherwise its longest fitting prefix.
|
|
13816
|
+
*/
|
|
13817
|
+
function capText(text, cap) {
|
|
13818
|
+
if (utf8ByteLength(text) <= cap) return text;
|
|
13819
|
+
let low = 0;
|
|
13820
|
+
let high = text.length;
|
|
13821
|
+
while (low < high) {
|
|
13822
|
+
const mid = Math.ceil((low + high) / 2);
|
|
13823
|
+
if (utf8ByteLength(text.slice(0, mid)) <= cap) low = mid;
|
|
13824
|
+
else high = mid - 1;
|
|
13825
|
+
}
|
|
13826
|
+
if (low > 0 && low < text.length) {
|
|
13827
|
+
const code = text.charCodeAt(low - 1);
|
|
13828
|
+
if (code >= 55296 && code <= 56319) low -= 1;
|
|
13829
|
+
}
|
|
13830
|
+
return text.slice(0, low);
|
|
13831
|
+
}
|
|
13832
|
+
/**
|
|
13833
|
+
* Drop entries from the tail until the payload `wrap(retained)` serializes
|
|
13834
|
+
* within `cap`.
|
|
13835
|
+
*
|
|
13836
|
+
* `wrap` receives the retained slice only, so the caller measures the real
|
|
13837
|
+
* payload — including its fixed fields — and a single oversized entry ends as
|
|
13838
|
+
* an empty list rather than an oversized one.
|
|
13839
|
+
* @param items - the ordered entries to bound.
|
|
13840
|
+
* @param cap - byte budget for `wrap`'s result.
|
|
13841
|
+
* @param wrap - builds the payload that would be emitted for a retained slice.
|
|
13842
|
+
* @returns the retained head, in input order.
|
|
13843
|
+
*/
|
|
13844
|
+
function capTail(items, cap, wrap) {
|
|
13845
|
+
const retained = [...items];
|
|
13846
|
+
while (retained.length > 0 && jsonByteLength(wrap(retained)) > cap) retained.pop();
|
|
13847
|
+
return retained;
|
|
13848
|
+
}
|
|
13849
|
+
//#endregion
|
|
13850
|
+
//#region src/web/host/read.ts
|
|
13851
|
+
/**
|
|
13852
|
+
* Host projection for the `read` card: the OMP engine already hands the renderer
|
|
13853
|
+
* a prefix-free window (`details.displayContent` —
|
|
13854
|
+
* `{text, startLine, lineNumbers?}`) exactly so the UI can draw its own gutter,
|
|
13855
|
+
* and that is what the shipped read card consumes
|
|
13856
|
+
* (`{kind:'read', path, offset, lines, totalLines, lang?}`).
|
|
13857
|
+
*
|
|
13858
|
+
* Three details drive the projection:
|
|
13859
|
+
* - the card path must be the file, never the model-facing argument: the OMP
|
|
13860
|
+
* argument carries an inline selector (`src/foo.ts:5-16,40-80`, `:raw`), so
|
|
13861
|
+
* the selector is stripped from whatever path source wins;
|
|
13862
|
+
* - `lineNumbers[i] === null` marks an elided span (the body renders `…`); such
|
|
13863
|
+
* entries carry no file line number and must be dropped, or the card's
|
|
13864
|
+
* "strictly increasing" rule breaks;
|
|
13865
|
+
* - only local, text-like reads take the card. Images, directories, internal
|
|
13866
|
+
* URLs (`skill://`, `conflict://N`, …), web URLs, SQLite, archives and
|
|
13867
|
+
* converted documents all fall back to the generic shell.
|
|
13868
|
+
*
|
|
13869
|
+
* The projection is bounded: past {@link READ_META_MAX_BYTES} whole trailing
|
|
13870
|
+
* lines are dropped, and an empty window projects to `null` rather than to an
|
|
13871
|
+
* empty card.
|
|
13872
|
+
*
|
|
13873
|
+
* @module @xiaoso/dsh-tool-plus/web/host/read
|
|
13874
|
+
*/
|
|
13875
|
+
/** `scheme://` — `file://` is still a local read; every other scheme is not. */
|
|
13876
|
+
const SCHEME_RE = /^([a-z][a-z0-9+.-]*):\/\//i;
|
|
13877
|
+
/**
|
|
13878
|
+
* Extensions whose reads are never a plain line window: SQLite and archive
|
|
13879
|
+
* readers (`omp/tools/sqlite-reader.ts`, `read/adapter/omp/tools/read-archive.ts`)
|
|
13880
|
+
* and markit document conversions (`omp/utils/markit.ts` CONVERTIBLE_EXTENSIONS)
|
|
13881
|
+
* all render their own body, so a line-numbered card would misrepresent them.
|
|
13882
|
+
*/
|
|
13883
|
+
const NON_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13884
|
+
".sqlite",
|
|
13885
|
+
".sqlite3",
|
|
13886
|
+
".db",
|
|
13887
|
+
".db3",
|
|
13888
|
+
".zip",
|
|
13889
|
+
".jar",
|
|
13890
|
+
".war",
|
|
13891
|
+
".ear",
|
|
13892
|
+
".apk",
|
|
13893
|
+
".tar",
|
|
13894
|
+
".tgz",
|
|
13895
|
+
".pdf",
|
|
13896
|
+
".docx",
|
|
13897
|
+
".pptx",
|
|
13898
|
+
".xlsx",
|
|
13899
|
+
".epub"
|
|
13900
|
+
]);
|
|
13901
|
+
/** Whether `value` is a plain object (never `null` or an array). */
|
|
13902
|
+
function isRecord$1(value) {
|
|
13903
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13904
|
+
}
|
|
13905
|
+
/** A positive integer, or `undefined`. */
|
|
13906
|
+
function positiveInteger(value) {
|
|
13907
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : void 0;
|
|
13908
|
+
}
|
|
13909
|
+
/** Non-blank string, or `undefined`. */
|
|
13910
|
+
function nonBlankString$2(value) {
|
|
13911
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
13912
|
+
}
|
|
13913
|
+
/** Whether the colon at `index` separates a Windows drive letter from its path. */
|
|
13914
|
+
function isDriveColon(target, index) {
|
|
13915
|
+
if (index < 1 || !/[a-zA-Z]/.test(target[index - 1] ?? "")) return false;
|
|
13916
|
+
const next = target[index + 1];
|
|
13917
|
+
if (next !== "/" && next !== "\\") return false;
|
|
13918
|
+
const before = index >= 2 ? target[index - 2] : void 0;
|
|
13919
|
+
return before === void 0 || before === "/" || before === "\\";
|
|
13920
|
+
}
|
|
13921
|
+
/**
|
|
13922
|
+
* Strip the model-facing inline selector from a read target, leaving the file
|
|
13923
|
+
* path the card should show.
|
|
13924
|
+
*
|
|
13925
|
+
* Handles `:5-16,40-80`, `:raw`, `:50+150`, `:conflicts` and the SQLite/archive
|
|
13926
|
+
* sub-selectors (`db.sqlite:table:key`), plus the `file://` form the engine
|
|
13927
|
+
* itself expands. Windows drive letters survive.
|
|
13928
|
+
* @param target - the raw read target (`args.path`, or a resolved path).
|
|
13929
|
+
* @returns the path without its selector.
|
|
13930
|
+
*/
|
|
13931
|
+
function stripReadSelector(target) {
|
|
13932
|
+
let value = target.trim();
|
|
13933
|
+
if (/^file:\/\//i.test(value)) value = value.slice(7);
|
|
13934
|
+
let end = value.length;
|
|
13935
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
13936
|
+
if (value[index] !== ":") continue;
|
|
13937
|
+
if (value[index + 1] === "/" && value[index + 2] === "/") continue;
|
|
13938
|
+
if (isDriveColon(value, index)) continue;
|
|
13939
|
+
end = index;
|
|
13940
|
+
break;
|
|
13941
|
+
}
|
|
13942
|
+
let base = value.slice(0, end);
|
|
13943
|
+
if (/^\/[a-zA-Z]:[\\/]/.test(base)) base = base.slice(1);
|
|
13944
|
+
return base;
|
|
13945
|
+
}
|
|
13946
|
+
/** Whether the target names something other than a local file path. */
|
|
13947
|
+
function isNonLocalTarget(target) {
|
|
13948
|
+
const scheme = SCHEME_RE.exec(target.trim());
|
|
13949
|
+
return scheme !== null && scheme[1].toLowerCase() !== "file";
|
|
13950
|
+
}
|
|
13951
|
+
/** Whether the path is one of the non-text extension families above. */
|
|
13952
|
+
function hasNonTextExtension(target) {
|
|
13953
|
+
const lower = target.toLowerCase();
|
|
13954
|
+
if (/\.tar\.gz$/.test(lower)) return true;
|
|
13955
|
+
return NON_TEXT_EXTENSIONS.has(path$1.extname(lower));
|
|
13956
|
+
}
|
|
13957
|
+
/** Read the display window into strictly increasing, numbered lines. */
|
|
13958
|
+
function readWindowLines(text, startLine, rawNumbers) {
|
|
13959
|
+
if (text === "") return [];
|
|
13960
|
+
const textLines = text.split("\n");
|
|
13961
|
+
const numbers = Array.isArray(rawNumbers) ? rawNumbers : void 0;
|
|
13962
|
+
const lines = [];
|
|
13963
|
+
for (let index = 0; index < textLines.length; index += 1) {
|
|
13964
|
+
const number = numbers === void 0 ? startLine + index : positiveInteger(numbers[index]);
|
|
13965
|
+
if (number === void 0) continue;
|
|
13966
|
+
if (lines.length > 0 && number <= lines[lines.length - 1].number) continue;
|
|
13967
|
+
lines.push({
|
|
13968
|
+
number,
|
|
13969
|
+
text: textLines[index] ?? ""
|
|
13970
|
+
});
|
|
13971
|
+
}
|
|
13972
|
+
return lines;
|
|
13973
|
+
}
|
|
13974
|
+
/**
|
|
13975
|
+
* Project a read result value into read-card metadata.
|
|
13976
|
+
*
|
|
13977
|
+
* Every emitted field is plain JSON and no key is ever `undefined`: the metadata
|
|
13978
|
+
* rides `presentationMeta`, where a non-lossless payload turns a successful call
|
|
13979
|
+
* into a failed one. A non-text read, or a window without a single numbered
|
|
13980
|
+
* line, yields `null` and leaves the row on the generic shell.
|
|
13981
|
+
* @param args - the tool's arguments (only `path` is read).
|
|
13982
|
+
* @param value - the tool's output value, as the adapter projects it.
|
|
13983
|
+
* @returns the card metadata, or `null` when this read does not take the card.
|
|
13984
|
+
*/
|
|
13985
|
+
function readCardMeta(args, value) {
|
|
13986
|
+
if (!isRecord$1(value)) return null;
|
|
13987
|
+
if (value.image !== void 0 && value.image !== null) return null;
|
|
13988
|
+
if (value.isDirectory === true) return null;
|
|
13989
|
+
if (value.textBlocks !== void 0 && value.textBlocks !== 1) return null;
|
|
13990
|
+
const source = isRecord$1(value.source) ? value.source : void 0;
|
|
13991
|
+
if (source !== void 0 && (source.type === "url" || source.type === "internal")) return null;
|
|
13992
|
+
const requested = isRecord$1(args) ? nonBlankString$2(args.path) : void 0;
|
|
13993
|
+
if (requested !== void 0 && isNonLocalTarget(requested)) return null;
|
|
13994
|
+
const sourcePath = source !== void 0 && source.type === "path" ? nonBlankString$2(source.value) : void 0;
|
|
13995
|
+
const resolved = nonBlankString$2(value.path);
|
|
13996
|
+
const raw = sourcePath ?? resolved ?? requested;
|
|
13997
|
+
if (raw === void 0) return null;
|
|
13998
|
+
const target = stripReadSelector(raw);
|
|
13999
|
+
if (target === "" || isNonLocalTarget(target) || hasNonTextExtension(target)) return null;
|
|
14000
|
+
const display = isRecord$1(value.display) ? value.display : void 0;
|
|
14001
|
+
const startLine = display === void 0 ? void 0 : positiveInteger(display.startLine);
|
|
14002
|
+
if (display === void 0 || typeof display.text !== "string" || startLine === void 0) return null;
|
|
14003
|
+
const lines = readWindowLines(display.text, startLine, display.lineNumbers);
|
|
14004
|
+
if (lines.length === 0) return null;
|
|
14005
|
+
const declaredTotal = positiveInteger(value.totalLines);
|
|
14006
|
+
const lastLine = lines[lines.length - 1].number;
|
|
14007
|
+
const totalLines = declaredTotal !== void 0 && declaredTotal >= lastLine ? declaredTotal : lastLine;
|
|
14008
|
+
const language = getLanguageFromPath(target);
|
|
14009
|
+
const build = (kept) => ({
|
|
14010
|
+
kind: "read",
|
|
14011
|
+
path: target,
|
|
14012
|
+
offset: kept[0]?.number ?? lines[0].number,
|
|
14013
|
+
lines: [...kept],
|
|
14014
|
+
totalLines,
|
|
14015
|
+
...language === void 0 || language === "" ? {} : { lang: language }
|
|
14016
|
+
});
|
|
14017
|
+
const retained = capTail(lines, READ_META_MAX_BYTES, build);
|
|
14018
|
+
return retained.length === 0 ? null : build(retained);
|
|
14019
|
+
}
|
|
14020
|
+
//#endregion
|
|
13721
14021
|
//#region src/tools/read/adapter/index.ts
|
|
13722
14022
|
/** Build the OMP ToolSession facade over a DSH exec context + tool-plus config. */
|
|
13723
14023
|
function createToolSession$6(exec, cfg, ctx) {
|
|
@@ -13773,13 +14073,34 @@ function toReadToolResult(result, args) {
|
|
|
13773
14073
|
const text = toText$6(result);
|
|
13774
14074
|
const details = result.details ?? {};
|
|
13775
14075
|
const notice = formatOutputNotice(details.meta);
|
|
14076
|
+
const source = details.meta?.source;
|
|
14077
|
+
const display = asDisplayContent(details.displayContent);
|
|
13776
14078
|
return {
|
|
13777
14079
|
path: String(details.resolvedPath ?? args.path ?? ""),
|
|
13778
14080
|
text,
|
|
14081
|
+
textBlocks: result.content.filter((block) => block.type === "text").length,
|
|
13779
14082
|
...notice ? { notice } : {},
|
|
13780
14083
|
...typeof details.totalLines === "number" ? { totalLines: details.totalLines } : {},
|
|
13781
14084
|
...typeof details.truncation !== "undefined" ? { truncated: true } : {},
|
|
13782
|
-
...details.image !== void 0 && typeof details.image === "object" ? { image: details.image } : {}
|
|
14085
|
+
...details.image !== void 0 && typeof details.image === "object" ? { image: details.image } : {},
|
|
14086
|
+
...display === void 0 ? {} : { display },
|
|
14087
|
+
...source === void 0 ? {} : { source: {
|
|
14088
|
+
type: String(source.type),
|
|
14089
|
+
value: String(source.value)
|
|
14090
|
+
} },
|
|
14091
|
+
...details.isDirectory === true ? { isDirectory: true } : {}
|
|
14092
|
+
};
|
|
14093
|
+
}
|
|
14094
|
+
/** 校验并逐字带出引擎的 displayContent(形状不对就当没有,降级走通用行)。 */
|
|
14095
|
+
function asDisplayContent(raw) {
|
|
14096
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
14097
|
+
const display = raw;
|
|
14098
|
+
if (typeof display.text !== "string" || typeof display.startLine !== "number") return void 0;
|
|
14099
|
+
const lineNumbers = Array.isArray(display.lineNumbers) ? display.lineNumbers : void 0;
|
|
14100
|
+
return {
|
|
14101
|
+
text: display.text,
|
|
14102
|
+
startLine: display.startLine,
|
|
14103
|
+
...lineNumbers === void 0 ? {} : { lineNumbers }
|
|
13783
14104
|
};
|
|
13784
14105
|
}
|
|
13785
14106
|
/** render:模型可见文本 = 工具文本 + session 层提示(OMP messages.ts 语义)。 */
|
|
@@ -13826,6 +14147,40 @@ function registerRead(ctx, getConfig) {
|
|
|
13826
14147
|
truncated: { type: "boolean" },
|
|
13827
14148
|
totalLines: { type: "number" },
|
|
13828
14149
|
offset: { type: "number" },
|
|
14150
|
+
display: {
|
|
14151
|
+
type: "object",
|
|
14152
|
+
additionalProperties: false,
|
|
14153
|
+
properties: {
|
|
14154
|
+
text: {
|
|
14155
|
+
type: "string",
|
|
14156
|
+
required: true
|
|
14157
|
+
},
|
|
14158
|
+
startLine: {
|
|
14159
|
+
type: "integer",
|
|
14160
|
+
required: true
|
|
14161
|
+
},
|
|
14162
|
+
lineNumbers: {
|
|
14163
|
+
type: "array",
|
|
14164
|
+
items: { oneOf: [{ type: "integer" }, { type: "null" }] }
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
14167
|
+
},
|
|
14168
|
+
source: {
|
|
14169
|
+
type: "object",
|
|
14170
|
+
additionalProperties: false,
|
|
14171
|
+
properties: {
|
|
14172
|
+
type: {
|
|
14173
|
+
type: "string",
|
|
14174
|
+
required: true
|
|
14175
|
+
},
|
|
14176
|
+
value: {
|
|
14177
|
+
type: "string",
|
|
14178
|
+
required: true
|
|
14179
|
+
}
|
|
14180
|
+
}
|
|
14181
|
+
},
|
|
14182
|
+
isDirectory: { type: "boolean" },
|
|
14183
|
+
textBlocks: { type: "integer" },
|
|
13829
14184
|
image: {
|
|
13830
14185
|
type: "object",
|
|
13831
14186
|
additionalProperties: false,
|
|
@@ -13889,11 +14244,7 @@ function registerRead(ctx, getConfig) {
|
|
|
13889
14244
|
});
|
|
13890
14245
|
return blocks;
|
|
13891
14246
|
},
|
|
13892
|
-
presentationMeta: (
|
|
13893
|
-
...value.path !== void 0 ? { path: value.path } : {},
|
|
13894
|
-
...value.offset !== void 0 ? { offset: value.offset } : {},
|
|
13895
|
-
...value.totalLines !== void 0 ? { lines: value.totalLines } : {}
|
|
13896
|
-
})
|
|
14247
|
+
presentationMeta: (args, value) => readCardMeta(args, value) ?? null
|
|
13897
14248
|
},
|
|
13898
14249
|
async execute(args, exec) {
|
|
13899
14250
|
return executeReadTool(exec, getConfig(), args, ctx);
|
|
@@ -13911,6 +14262,61 @@ function registerRead(ctx, getConfig) {
|
|
|
13911
14262
|
}));
|
|
13912
14263
|
}
|
|
13913
14264
|
//#endregion
|
|
14265
|
+
//#region src/web/host/write.ts
|
|
14266
|
+
/**
|
|
14267
|
+
* `write` tool card: project the engine's result into the `{kind:'write'}` meta
|
|
14268
|
+
* this plugin's browser half consumes (plan §4.1).
|
|
14269
|
+
*
|
|
14270
|
+
* The card is a **content preview** (matching the OMP card the model already
|
|
14271
|
+
* sees), so metadata carries only the target path plus an optional grammar
|
|
14272
|
+
* hint — never a diff, never a `before` read, never `±` counts. Both fields are
|
|
14273
|
+
* pure computation over data the adapter already holds: `lang` comes from the
|
|
14274
|
+
* shared `getLanguageFromPath` table, and no I/O happens here.
|
|
14275
|
+
*
|
|
14276
|
+
* Zero-dependency by design, like the contract it fills in: the Node half
|
|
14277
|
+
* imports it from `presentationMeta`, the browser half never does.
|
|
14278
|
+
*
|
|
14279
|
+
* Defensive: a card never throws and never emits `undefined` values (the host
|
|
14280
|
+
* rejects non-lossless metadata and turns a successful call into an error), so
|
|
14281
|
+
* an unusable path projects to `null` and the row falls back to the generic
|
|
14282
|
+
* shell.
|
|
14283
|
+
*
|
|
14284
|
+
* @module @xiaoso/dsh-tool-plus/web/host/write
|
|
14285
|
+
*/
|
|
14286
|
+
/** `value` as a plain record, or `null` (arrays, `null` and primitives drop). */
|
|
14287
|
+
function asRecord$2(value) {
|
|
14288
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
14289
|
+
}
|
|
14290
|
+
/** First non-blank string among `values`, or `null`. */
|
|
14291
|
+
function firstNonBlank(...values) {
|
|
14292
|
+
for (const value of values) if (typeof value === "string" && value.trim() !== "") return value;
|
|
14293
|
+
return null;
|
|
14294
|
+
}
|
|
14295
|
+
/**
|
|
14296
|
+
* Project the write tool's result (and its call arguments) into card metadata.
|
|
14297
|
+
*
|
|
14298
|
+
* The path is the engine's resolved target when it has one, falling back to the
|
|
14299
|
+
* authored `args.path` (matching the value the adapter already emits). A blank
|
|
14300
|
+
* or missing path projects to `null`; an unrecognized extension omits `lang`
|
|
14301
|
+
* instead of writing `undefined`.
|
|
14302
|
+
* @param details - the engine's `resolvedPath` / the adapter's value (`path`).
|
|
14303
|
+
* @param args - the tool call arguments (`path`), used only as a fallback.
|
|
14304
|
+
* @returns the write card metadata, or `null` when no usable path is present.
|
|
14305
|
+
*/
|
|
14306
|
+
function projectWriteCardMeta(details, args) {
|
|
14307
|
+
const result = asRecord$2(details);
|
|
14308
|
+
const parameters = asRecord$2(args);
|
|
14309
|
+
const path = firstNonBlank(result?.resolvedPath, result?.path, parameters?.path);
|
|
14310
|
+
if (path === null) return null;
|
|
14311
|
+
const language = getLanguageFromPath(path);
|
|
14312
|
+
return {
|
|
14313
|
+
kind: "write",
|
|
14314
|
+
path,
|
|
14315
|
+
...language !== void 0 && language !== "" ? { lang: language } : {},
|
|
14316
|
+
...result?.madeExecutable === true ? { madeExecutable: true } : {}
|
|
14317
|
+
};
|
|
14318
|
+
}
|
|
14319
|
+
//#endregion
|
|
13914
14320
|
//#region src/tools/omp/tools/writethrough.ts
|
|
13915
14321
|
/** Write `content` to `dst` (or the passed file handle) — no LSP pass. */
|
|
13916
14322
|
async function writethroughNoop(dst, content, _signal, file) {
|
|
@@ -14045,10 +14451,10 @@ function stripBom(content) {
|
|
|
14045
14451
|
}
|
|
14046
14452
|
function getCommentStylesForPath(filePath) {
|
|
14047
14453
|
const normalizedPath = filePath.toLowerCase();
|
|
14048
|
-
const fileName = path.basename(normalizedPath);
|
|
14454
|
+
const fileName = path$1.basename(normalizedPath);
|
|
14049
14455
|
const stylesByName = COMMENT_STYLES_BY_BASENAME.get(fileName);
|
|
14050
14456
|
if (stylesByName) return stylesByName;
|
|
14051
|
-
const ext = path.extname(fileName);
|
|
14457
|
+
const ext = path$1.extname(fileName);
|
|
14052
14458
|
return COMMENT_STYLES_BY_EXTENSION.get(ext) ?? [];
|
|
14053
14459
|
}
|
|
14054
14460
|
function extractLeadingHeaderCommentText(content, commentStyles) {
|
|
@@ -14232,7 +14638,7 @@ function planLocalProtocolOptions(session) {
|
|
|
14232
14638
|
* Returns `null` when the session has no artifact wiring (e.g. tests). */
|
|
14233
14639
|
function localSandboxRoot(session) {
|
|
14234
14640
|
try {
|
|
14235
|
-
return path.resolve(resolveLocalRoot(planLocalProtocolOptions(session)));
|
|
14641
|
+
return path$1.resolve(resolveLocalRoot(planLocalProtocolOptions(session)));
|
|
14236
14642
|
} catch {
|
|
14237
14643
|
return null;
|
|
14238
14644
|
}
|
|
@@ -14240,7 +14646,7 @@ function localSandboxRoot(session) {
|
|
|
14240
14646
|
/** True when `absolutePath` resolves inside `root` (== root or under it). */
|
|
14241
14647
|
function isWithinRoot(absolutePath, root) {
|
|
14242
14648
|
if (absolutePath === root) return true;
|
|
14243
|
-
const sep = `${root}${path.sep}`;
|
|
14649
|
+
const sep = `${root}${path$1.sep}`;
|
|
14244
14650
|
return absolutePath.startsWith(sep);
|
|
14245
14651
|
}
|
|
14246
14652
|
/** Strip the hashline `[path#TAG]` wrapper from a write/edit target so the inner
|
|
@@ -14273,14 +14679,14 @@ function targetsLocalSandbox(session, targetPath) {
|
|
|
14273
14679
|
} catch {
|
|
14274
14680
|
return false;
|
|
14275
14681
|
}
|
|
14276
|
-
if (!path.isAbsolute(resolved)) return false;
|
|
14277
|
-
const absolute = path.resolve(resolved);
|
|
14682
|
+
if (!path$1.isAbsolute(resolved)) return false;
|
|
14683
|
+
const absolute = path$1.resolve(resolved);
|
|
14278
14684
|
if (isWithinRoot(absolute, root)) return true;
|
|
14279
14685
|
try {
|
|
14280
14686
|
const realRoot = fs.realpathSync.native(root);
|
|
14281
14687
|
if (isWithinRoot(absolute, realRoot)) return true;
|
|
14282
|
-
const realParent = fs.realpathSync.native(path.dirname(absolute));
|
|
14283
|
-
return isWithinRoot(path.join(realParent, path.basename(absolute)), realRoot);
|
|
14688
|
+
const realParent = fs.realpathSync.native(path$1.dirname(absolute));
|
|
14689
|
+
return isWithinRoot(path$1.join(realParent, path$1.basename(absolute)), realRoot);
|
|
14284
14690
|
} catch {
|
|
14285
14691
|
return false;
|
|
14286
14692
|
}
|
|
@@ -14324,7 +14730,7 @@ const XD_SCHEME_NEAR_MISSES = {
|
|
|
14324
14730
|
};
|
|
14325
14731
|
function assertWriteTargetAddressable(target, router) {
|
|
14326
14732
|
const trimmed = target.trim();
|
|
14327
|
-
if (path.win32.isAbsolute(trimmed) || router.canHandle(trimmed)) return;
|
|
14733
|
+
if (path$1.win32.isAbsolute(trimmed) || router.canHandle(trimmed)) return;
|
|
14328
14734
|
const missingDelimiter = trimmed.match(XD_MISSING_DELIMITER_RE);
|
|
14329
14735
|
if (missingDelimiter) throw new ToolError(`Unknown URI-like write target '${trimmed}'. Did you mean 'xd://${missingDelimiter[1]}'? Prefix the path with './' to write it as a filesystem path.`);
|
|
14330
14736
|
const uriLike = trimmed.match(URI_LIKE_WRITE_PATH_RE);
|
|
@@ -14683,7 +15089,7 @@ var WriteTool = class {
|
|
|
14683
15089
|
const finalPath = resolvedArchivePath.exists ? await fs_promises_shim_exports.realpath(resolvedArchivePath.absolutePath).catch(() => resolvedArchivePath.absolutePath) : resolvedArchivePath.absolutePath;
|
|
14684
15090
|
const format = archiveFormatFromPath(finalPath) ?? "tar";
|
|
14685
15091
|
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
14686
|
-
const parentDir = path.dirname(resolvedArchivePath.absolutePath);
|
|
15092
|
+
const parentDir = path$1.dirname(resolvedArchivePath.absolutePath);
|
|
14687
15093
|
if (parentDir && parentDir !== ".") await fs_promises_shim_exports.mkdir(parentDir, { recursive: true });
|
|
14688
15094
|
const entries = /* @__PURE__ */ new Map();
|
|
14689
15095
|
if (resolvedArchivePath.exists) try {
|
|
@@ -14774,7 +15180,7 @@ var WriteTool = class {
|
|
|
14774
15180
|
} catch (error) {
|
|
14775
15181
|
throw new ToolError(`SQLite write content must be valid JSON5: ${error instanceof Error ? error.message : String(error)}`);
|
|
14776
15182
|
}
|
|
14777
|
-
if (!isRecord$
|
|
15183
|
+
if (!isRecord$5(parsedContent)) throw new ToolError("SQLite write content must be a JSON object");
|
|
14778
15184
|
if (resolvedSqlitePath.key) {
|
|
14779
15185
|
const lookup = resolveTableRowLookup(db, resolvedSqlitePath.table);
|
|
14780
15186
|
resultText = (lookup.kind === "pk" ? updateRowByKey(db, resolvedSqlitePath.table, lookup, resolvedSqlitePath.key, parsedContent) : updateRowByRowId(db, resolvedSqlitePath.table, resolvedSqlitePath.key, parsedContent)) > 0 ? `Updated row '${resolvedSqlitePath.key}' in ${resolvedSqlitePath.table}` : `No row updated in ${resolvedSqlitePath.table} for key '${resolvedSqlitePath.key}'`;
|
|
@@ -15112,9 +15518,11 @@ async function executeWriteTool(exec, cfg, args) {
|
|
|
15112
15518
|
persistOmpSessionState(exec?.agent?.session, session);
|
|
15113
15519
|
const text = toText$5(result);
|
|
15114
15520
|
const details = result.details ?? {};
|
|
15521
|
+
const card = projectWriteCardMeta({ resolvedPath: details.resolvedPath }, args);
|
|
15115
15522
|
return {
|
|
15116
15523
|
path: String(details.resolvedPath ?? args.path ?? ""),
|
|
15117
|
-
text
|
|
15524
|
+
text,
|
|
15525
|
+
...card !== null && card.lang !== void 0 ? { lang: card.lang } : {}
|
|
15118
15526
|
};
|
|
15119
15527
|
}
|
|
15120
15528
|
/**
|
|
@@ -15148,14 +15556,15 @@ function registerWrite(ctx, getConfig) {
|
|
|
15148
15556
|
text: {
|
|
15149
15557
|
type: "string",
|
|
15150
15558
|
required: true
|
|
15151
|
-
}
|
|
15559
|
+
},
|
|
15560
|
+
lang: { type: "string" }
|
|
15152
15561
|
}
|
|
15153
15562
|
},
|
|
15154
15563
|
render: (_args, value) => [{
|
|
15155
15564
|
type: "text",
|
|
15156
15565
|
text: String(value.text ?? "")
|
|
15157
15566
|
}],
|
|
15158
|
-
presentationMeta: (_args, value) => (
|
|
15567
|
+
presentationMeta: (_args, value) => projectWriteCardMeta(value) ?? null
|
|
15159
15568
|
},
|
|
15160
15569
|
async execute(args, exec) {
|
|
15161
15570
|
return executeWriteTool(exec, getConfig(), args);
|
|
@@ -17552,7 +17961,7 @@ async function applyNormalizedPatch(input, options) {
|
|
|
17552
17961
|
const normalizedContent = normalizeCreateContent(input.diff);
|
|
17553
17962
|
const content = normalizedContent.endsWith("\n") ? normalizedContent : `${normalizedContent}\n`;
|
|
17554
17963
|
if (!dryRun) {
|
|
17555
|
-
const parentDir = path.dirname(absolutePath);
|
|
17964
|
+
const parentDir = path$1.dirname(absolutePath);
|
|
17556
17965
|
if (parentDir && parentDir !== ".") await fs.mkdir(parentDir);
|
|
17557
17966
|
await fs.write(absolutePath, content);
|
|
17558
17967
|
}
|
|
@@ -17589,7 +17998,7 @@ async function applyNormalizedPatch(input, options) {
|
|
|
17589
17998
|
const isMove = Boolean(input.rename) && destPath !== absolutePath;
|
|
17590
17999
|
if (!dryRun) {
|
|
17591
18000
|
if (isMove) {
|
|
17592
|
-
const parentDir = path.dirname(destPath);
|
|
18001
|
+
const parentDir = path$1.dirname(destPath);
|
|
17593
18002
|
if (parentDir && parentDir !== ".") await fs.mkdir(parentDir);
|
|
17594
18003
|
await fs.write(destPath, finalContent);
|
|
17595
18004
|
await fs.delete(absolutePath);
|
|
@@ -18401,6 +18810,169 @@ async function executeSinglePathEntries(path, runs, outerBatchRequest, onUpdate,
|
|
|
18401
18810
|
};
|
|
18402
18811
|
}
|
|
18403
18812
|
//#endregion
|
|
18813
|
+
//#region src/web/host/edit.ts
|
|
18814
|
+
/**
|
|
18815
|
+
* `edit` tool card: project the engine's already-computed diff into the
|
|
18816
|
+
* `{kind:'edit'}` meta this plugin's browser half consumes (plan §4.2).
|
|
18817
|
+
*
|
|
18818
|
+
* The engine puts the diff in `details.diff` (single file) and in
|
|
18819
|
+
* `details.perFileResults[].diff` (multi file), so the card costs no extra I/O
|
|
18820
|
+
* and no extra diff computation. Its rows are `<sign><line>|<content>`
|
|
18821
|
+
* (`omp/edit/diff.ts:54` `formatNumberedDiffLine`):
|
|
18822
|
+
* - replace / hashline emit numbered rows only (`generateDiffString`);
|
|
18823
|
+
* - patch / apply_patch emit a `@@ -x,y +a,b @@` header plus the same rows
|
|
18824
|
+
* (`generateUnifiedDiffString`).
|
|
18825
|
+
*
|
|
18826
|
+
* The repo's `parseDiffHunks` parses the *authored* patch format and only
|
|
18827
|
+
* strips a `12 content` line-number prefix (space-separated), not the engine's
|
|
18828
|
+
* `12|content` (pipe-separated) — reusing it would leave the gutter inside the
|
|
18829
|
+
* card's content. `parseNumberedDiffHunks` below is the display-side minimum:
|
|
18830
|
+
* it drops the gutter, splits hunks on `@@` headers, blank gap rows and line
|
|
18831
|
+
* number breaks, and returns the line content byte-for-byte.
|
|
18832
|
+
*
|
|
18833
|
+
* Zero-dependency by design: the Node half imports it from `presentationMeta`,
|
|
18834
|
+
* the browser half never does.
|
|
18835
|
+
*
|
|
18836
|
+
* Defensive: a card never throws and never emits `undefined` values (the host
|
|
18837
|
+
* rejects non-lossless metadata and turns a successful call into an error), so
|
|
18838
|
+
* an empty or unparseable diff projects to `null`.
|
|
18839
|
+
*
|
|
18840
|
+
* @module @xiaoso/dsh-tool-plus/web/host/edit
|
|
18841
|
+
*/
|
|
18842
|
+
/** One engine diff row: `<sign><line>|<content>` (`-2|beta`, ` 3|gamma`). */
|
|
18843
|
+
const NUMBERED_ROW = /^([+\- ])(\d+)\|([\s\S]*)$/;
|
|
18844
|
+
/** One authored unified row without a gutter: `-beta`, `+BETA`, ` gamma`. */
|
|
18845
|
+
const PLAIN_ROW = /^([+\- ])([\s\S]*)$/;
|
|
18846
|
+
/** Diff metadata that is never line content (file headers, envelope markers). */
|
|
18847
|
+
const METADATA_PREFIXES = [
|
|
18848
|
+
"*** ",
|
|
18849
|
+
"diff --git ",
|
|
18850
|
+
"index ",
|
|
18851
|
+
"--- ",
|
|
18852
|
+
"+++ "
|
|
18853
|
+
];
|
|
18854
|
+
/** Whether `line` continues the current hunk (a line-number break starts a new one). */
|
|
18855
|
+
function breaksContinuity(prefix, oldNumber, newNumber, nextOld, nextNew) {
|
|
18856
|
+
if (prefix === "-") return oldNumber !== null && nextOld !== null && oldNumber !== nextOld;
|
|
18857
|
+
if (prefix === "+") return newNumber !== null && nextNew !== null && newNumber !== nextNew;
|
|
18858
|
+
return oldNumber !== null && nextOld !== null && oldNumber !== nextOld || newNumber !== null && nextNew !== null && newNumber !== nextNew;
|
|
18859
|
+
}
|
|
18860
|
+
/**
|
|
18861
|
+
* Parse an engine diff body into display hunks.
|
|
18862
|
+
*
|
|
18863
|
+
* Boundaries: a `@@ … @@` header, a blank gap row (the engine's own
|
|
18864
|
+
* non-contiguity marker), or a line-number break after a change (how joined
|
|
18865
|
+
* per-entry diffs and skipped context regions read). Hunks without a change are
|
|
18866
|
+
* dropped — they would render as an empty diff.
|
|
18867
|
+
* @param diff - `details.diff` / `details.perFileResults[].diff`.
|
|
18868
|
+
* @returns the hunks, in diff order; empty when nothing parses.
|
|
18869
|
+
*/
|
|
18870
|
+
function parseNumberedDiffHunks(diff) {
|
|
18871
|
+
const hunks = [];
|
|
18872
|
+
let current = null;
|
|
18873
|
+
let nextOld = null;
|
|
18874
|
+
let nextNew = null;
|
|
18875
|
+
const flush = () => {
|
|
18876
|
+
if (current !== null && current.changed) hunks.push(current);
|
|
18877
|
+
current = null;
|
|
18878
|
+
};
|
|
18879
|
+
for (const rawLine of diff.split("\n")) {
|
|
18880
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
18881
|
+
if (line === "" || line.startsWith("@@")) {
|
|
18882
|
+
flush();
|
|
18883
|
+
continue;
|
|
18884
|
+
}
|
|
18885
|
+
if (METADATA_PREFIXES.some((prefix) => line.startsWith(prefix))) continue;
|
|
18886
|
+
const numbered = NUMBERED_ROW.exec(line);
|
|
18887
|
+
const plain = numbered === null ? PLAIN_ROW.exec(line) : null;
|
|
18888
|
+
if (numbered === null && plain === null) continue;
|
|
18889
|
+
const prefix = numbered?.[1] ?? plain?.[1];
|
|
18890
|
+
const content = numbered?.[3] ?? plain?.[2] ?? "";
|
|
18891
|
+
const digits = numbered?.[2];
|
|
18892
|
+
const oldNumber = digits === void 0 || prefix === "+" ? null : Number.parseInt(digits, 10);
|
|
18893
|
+
const newNumber = digits === void 0 || prefix === "-" ? null : Number.parseInt(digits, 10);
|
|
18894
|
+
if (current !== null && current.changed && breaksContinuity(prefix, oldNumber, newNumber, nextOld, nextNew)) flush();
|
|
18895
|
+
if (current === null) {
|
|
18896
|
+
current = {
|
|
18897
|
+
oldLines: [],
|
|
18898
|
+
newLines: [],
|
|
18899
|
+
changed: false
|
|
18900
|
+
};
|
|
18901
|
+
nextOld = null;
|
|
18902
|
+
nextNew = null;
|
|
18903
|
+
}
|
|
18904
|
+
if (prefix === "-") {
|
|
18905
|
+
current.oldLines.push(content);
|
|
18906
|
+
current.changed = true;
|
|
18907
|
+
nextOld = oldNumber === null ? null : oldNumber + 1;
|
|
18908
|
+
} else if (prefix === "+") {
|
|
18909
|
+
current.newLines.push(content);
|
|
18910
|
+
current.changed = true;
|
|
18911
|
+
nextNew = newNumber === null ? null : newNumber + 1;
|
|
18912
|
+
} else {
|
|
18913
|
+
current.oldLines.push(content);
|
|
18914
|
+
current.newLines.push(content);
|
|
18915
|
+
nextOld = oldNumber === null ? null : oldNumber + 1;
|
|
18916
|
+
nextNew = newNumber === null ? null : newNumber + 1;
|
|
18917
|
+
}
|
|
18918
|
+
}
|
|
18919
|
+
flush();
|
|
18920
|
+
return hunks;
|
|
18921
|
+
}
|
|
18922
|
+
/** `value` as a plain record, or `null` (arrays, `null` and primitives drop). */
|
|
18923
|
+
function asRecord$1(value) {
|
|
18924
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
18925
|
+
}
|
|
18926
|
+
/**
|
|
18927
|
+
* Append one file's hunks to `out`.
|
|
18928
|
+
*
|
|
18929
|
+
* A pure addition (no removed and no context rows) carries `oldText: null`, the
|
|
18930
|
+
* shape the shipped diff card reads as "new file". A move/rename has no
|
|
18931
|
+
* pre-move side to show in a two-column diff, so its target is recorded the
|
|
18932
|
+
* same way (the source side is deliberately not recorded).
|
|
18933
|
+
*/
|
|
18934
|
+
function pushFileHunks(out, pathValue, diffValue, isMove) {
|
|
18935
|
+
if (typeof pathValue !== "string" || pathValue.trim() === "") return;
|
|
18936
|
+
if (typeof diffValue !== "string" || diffValue.trim() === "") return;
|
|
18937
|
+
for (const hunk of parseNumberedDiffHunks(diffValue)) out.push({
|
|
18938
|
+
path: pathValue,
|
|
18939
|
+
oldText: isMove || hunk.oldLines.length === 0 ? null : hunk.oldLines.join("\n"),
|
|
18940
|
+
newText: hunk.newLines.join("\n")
|
|
18941
|
+
});
|
|
18942
|
+
}
|
|
18943
|
+
/**
|
|
18944
|
+
* Project the edit engine's result details into card metadata.
|
|
18945
|
+
*
|
|
18946
|
+
* Multi-file results (`perFileResults`) are read file by file; single-file
|
|
18947
|
+
* results use `path` + `diff`. The payload is capped at
|
|
18948
|
+
* {@link EDIT_META_MAX_BYTES} by dropping whole trailing files; when nothing is
|
|
18949
|
+
* left the call projects to `null` and falls back to the generic shell rather
|
|
18950
|
+
* than writing an oversized session entry.
|
|
18951
|
+
* @param details - the engine's `EditToolDetails` (or the adapter's value).
|
|
18952
|
+
* @returns the edit card metadata, or `null` when there is no usable diff.
|
|
18953
|
+
*/
|
|
18954
|
+
function projectEditCardMeta(details) {
|
|
18955
|
+
const record = asRecord$1(details);
|
|
18956
|
+
if (record === null) return null;
|
|
18957
|
+
const hunks = [];
|
|
18958
|
+
const perFile = Array.isArray(record.perFileResults) ? record.perFileResults : null;
|
|
18959
|
+
if (perFile !== null) for (const entry of perFile) {
|
|
18960
|
+
const file = asRecord$1(entry);
|
|
18961
|
+
if (file === null) continue;
|
|
18962
|
+
pushFileHunks(hunks, file.path, file.diff, file.move !== void 0);
|
|
18963
|
+
}
|
|
18964
|
+
else pushFileHunks(hunks, record.path, record.diff, record.move !== void 0);
|
|
18965
|
+
if (hunks.length === 0) return null;
|
|
18966
|
+
const retained = capTail(hunks, EDIT_META_MAX_BYTES, (kept) => ({
|
|
18967
|
+
kind: "edit",
|
|
18968
|
+
diffs: kept
|
|
18969
|
+
}));
|
|
18970
|
+
return retained.length === 0 ? null : {
|
|
18971
|
+
kind: "edit",
|
|
18972
|
+
diffs: retained
|
|
18973
|
+
};
|
|
18974
|
+
}
|
|
18975
|
+
//#endregion
|
|
18404
18976
|
//#region src/tools/shared/sandbox-policy.ts
|
|
18405
18977
|
/**
|
|
18406
18978
|
* Resolve the per-call mutation policy for the given exec context, or
|
|
@@ -18501,6 +19073,17 @@ function runPatchEntry(options) {
|
|
|
18501
19073
|
});
|
|
18502
19074
|
}
|
|
18503
19075
|
/**
|
|
19076
|
+
* 把成功结果带上卡片投影(纯展示用;投影不抛异常,null → 不产 meta)。
|
|
19077
|
+
* 错误路径不经此函数,保证报错文案一字不变。
|
|
19078
|
+
*/
|
|
19079
|
+
function withEditCard(result, text) {
|
|
19080
|
+
const diffs = projectEditCardMeta(result.details)?.diffs;
|
|
19081
|
+
return diffs === void 0 ? { text } : {
|
|
19082
|
+
text,
|
|
19083
|
+
diffs
|
|
19084
|
+
};
|
|
19085
|
+
}
|
|
19086
|
+
/**
|
|
18504
19087
|
* 把 dispatch 聚合结果转成 DSH 输出。成功:保留引擎 per-entry 摘要原文;
|
|
18505
19088
|
* 失败:抛出完整聚合文案,并在“失败即末条目”的空档补上游同款未应用指引。
|
|
18506
19089
|
*
|
|
@@ -18515,7 +19098,7 @@ function toEditToolResult(result, entryCount, failedEntry, failedPath) {
|
|
|
18515
19098
|
const supplement = entryCount > 1 && failedEntry !== void 0 && failedEntry === entryCount ? failedPath !== void 0 ? `\nFiles NOT applied: ${failedPath}; re-read the affected files and re-issue only the failed and unapplied files.` : `\nEntry ${failedEntry} was NOT applied; re-read the file and re-issue only the failed and unapplied entries.` : "";
|
|
18516
19099
|
throw new Error(`${text || "edit failed"}${supplement}`);
|
|
18517
19100
|
}
|
|
18518
|
-
return
|
|
19101
|
+
return withEditCard(result, text);
|
|
18519
19102
|
}
|
|
18520
19103
|
/**
|
|
18521
19104
|
* S-13:edit.fuzzyThreshold 解析——上游 settings 面原样放行显式 0/负值
|
|
@@ -18575,31 +19158,35 @@ async function executeEditTool(exec, cfg, args, ctx) {
|
|
|
18575
19158
|
};
|
|
18576
19159
|
}), void 0, cwd, signal), entries.length, failedFile, failedFile !== void 0 ? entries[failedFile - 1]?.path : void 0);
|
|
18577
19160
|
}
|
|
18578
|
-
|
|
19161
|
+
const result = await executeHashlineSingle({
|
|
18579
19162
|
session,
|
|
18580
19163
|
input: args.input,
|
|
18581
19164
|
signal,
|
|
18582
19165
|
writethrough
|
|
18583
|
-
})
|
|
19166
|
+
});
|
|
19167
|
+
return withEditCard(result, toText$4(result));
|
|
18584
19168
|
}
|
|
18585
19169
|
const filePath = args.file_path ?? args.path ?? "";
|
|
18586
19170
|
if (!filePath || typeof filePath !== "string" || filePath.trim().length === 0) throw new Error("file_path must be a non-empty string");
|
|
18587
|
-
if (typeof args.patch === "string" && args.patch.trim().length > 0)
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
|
|
18596
|
-
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
19171
|
+
if (typeof args.patch === "string" && args.patch.trim().length > 0) {
|
|
19172
|
+
const result = await runPatchEntry({
|
|
19173
|
+
session,
|
|
19174
|
+
path: await resolveEditPath(session, filePath, {
|
|
19175
|
+
mustExist: true,
|
|
19176
|
+
signal
|
|
19177
|
+
}),
|
|
19178
|
+
entry: {
|
|
19179
|
+
op: "update",
|
|
19180
|
+
diff: args.patch
|
|
19181
|
+
},
|
|
19182
|
+
signal,
|
|
19183
|
+
writethrough,
|
|
19184
|
+
allowFuzzy,
|
|
19185
|
+
fuzzyThreshold,
|
|
19186
|
+
allowCreateOverwrite: true
|
|
19187
|
+
});
|
|
19188
|
+
return withEditCard(result, toText$4(result));
|
|
19189
|
+
}
|
|
18603
19190
|
const editsInput = Array.isArray(args.edits) && args.edits.length > 0 ? args.edits : typeof args.old_string === "string" ? [{
|
|
18604
19191
|
oldText: args.old_string,
|
|
18605
19192
|
newText: String(args.new_string ?? "")
|
|
@@ -18693,15 +19280,42 @@ function registerEdit(ctx, getConfig) {
|
|
|
18693
19280
|
schema: {
|
|
18694
19281
|
type: "object",
|
|
18695
19282
|
additionalProperties: false,
|
|
18696
|
-
properties: {
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
|
|
19283
|
+
properties: {
|
|
19284
|
+
text: {
|
|
19285
|
+
type: "string",
|
|
19286
|
+
required: true
|
|
19287
|
+
},
|
|
19288
|
+
diffs: {
|
|
19289
|
+
type: "array",
|
|
19290
|
+
items: {
|
|
19291
|
+
type: "object",
|
|
19292
|
+
additionalProperties: false,
|
|
19293
|
+
properties: {
|
|
19294
|
+
path: {
|
|
19295
|
+
type: "string",
|
|
19296
|
+
required: true
|
|
19297
|
+
},
|
|
19298
|
+
oldText: {
|
|
19299
|
+
required: true,
|
|
19300
|
+
oneOf: [{ type: "string" }, { type: "null" }]
|
|
19301
|
+
},
|
|
19302
|
+
newText: {
|
|
19303
|
+
type: "string",
|
|
19304
|
+
required: true
|
|
19305
|
+
}
|
|
19306
|
+
}
|
|
19307
|
+
}
|
|
19308
|
+
}
|
|
19309
|
+
}
|
|
18700
19310
|
},
|
|
18701
19311
|
render: (_args, value) => [{
|
|
18702
19312
|
type: "text",
|
|
18703
19313
|
text: String(value.text ?? "")
|
|
18704
|
-
}]
|
|
19314
|
+
}],
|
|
19315
|
+
presentationMeta: (_args, value) => Array.isArray(value?.diffs) && value.diffs.length > 0 ? {
|
|
19316
|
+
kind: "edit",
|
|
19317
|
+
diffs: value.diffs
|
|
19318
|
+
} : null
|
|
18705
19319
|
},
|
|
18706
19320
|
async execute(args, exec) {
|
|
18707
19321
|
return executeEditTool(exec, getConfig(), args, ctx);
|
|
@@ -18777,7 +19391,7 @@ function createFileRecorder() {
|
|
|
18777
19391
|
*/
|
|
18778
19392
|
function formatResultPath(filePath, isDirectory, basePath, cwd) {
|
|
18779
19393
|
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
18780
|
-
if (isDirectory) return formatPathRelativeToCwd(path.resolve(basePath, cleanPath), cwd);
|
|
19394
|
+
if (isDirectory) return formatPathRelativeToCwd(path$1.resolve(basePath, cleanPath), cwd);
|
|
18781
19395
|
return formatPathRelativeToCwd(basePath, cwd);
|
|
18782
19396
|
}
|
|
18783
19397
|
//#endregion
|
|
@@ -18839,6 +19453,80 @@ function formatGroupedFiles(files, renderFile) {
|
|
|
18839
19453
|
display
|
|
18840
19454
|
};
|
|
18841
19455
|
}
|
|
19456
|
+
const GROUPED_HEADER_RE = /^(#+)\s+(.*)$/;
|
|
19457
|
+
const HEADER_SUFFIX_RE = /\s+\([^)]*\)\s*$/;
|
|
19458
|
+
const HEADER_HASH_TAG_RE = /#[0-9a-f]+$/i;
|
|
19459
|
+
function resolveGroupedPath(parent, name) {
|
|
19460
|
+
if (parent === void 0) return void 0;
|
|
19461
|
+
if (name === "" || name === ".") return parent;
|
|
19462
|
+
return path.resolve(parent, name);
|
|
19463
|
+
}
|
|
19464
|
+
/**
|
|
19465
|
+
* Walk grouped output lines, tracking a directory stack keyed by header depth, so
|
|
19466
|
+
* each header and body line can be linked back to its absolute filesystem path.
|
|
19467
|
+
* Reconstruction is stack-based (not per-blank-group) so nested directory headers
|
|
19468
|
+
* resolve correctly across the whole output.
|
|
19469
|
+
*
|
|
19470
|
+
* `headerBase` is the directory the displayed (folded) header paths are relative
|
|
19471
|
+
* to — for grep/ast tools that is the session cwd, since display paths are
|
|
19472
|
+
* formatted relative to cwd regardless of the (sub)directory the search was
|
|
19473
|
+
* scoped to. `fileScope` is the initial owning file for body lines that appear
|
|
19474
|
+
* before any header (single-file scopes have no `#` headers); it defaults to
|
|
19475
|
+
* `headerBase` and should be passed the scoped file's absolute path.
|
|
19476
|
+
*/
|
|
19477
|
+
function classifyGroupedLines(lines, headerBase, fileScope = headerBase) {
|
|
19478
|
+
const result = [];
|
|
19479
|
+
const dirAtDepth = /* @__PURE__ */ new Map();
|
|
19480
|
+
let currentFile = fileScope;
|
|
19481
|
+
const clearDeeper = (depth) => {
|
|
19482
|
+
for (const key of dirAtDepth.keys()) if (key >= depth) dirAtDepth.delete(key);
|
|
19483
|
+
};
|
|
19484
|
+
for (const line of lines) {
|
|
19485
|
+
const match = GROUPED_HEADER_RE.exec(line);
|
|
19486
|
+
if (!match) {
|
|
19487
|
+
result.push({
|
|
19488
|
+
kind: "content",
|
|
19489
|
+
depth: 0,
|
|
19490
|
+
filePath: currentFile
|
|
19491
|
+
});
|
|
19492
|
+
continue;
|
|
19493
|
+
}
|
|
19494
|
+
const depth = match[1].length;
|
|
19495
|
+
const rest = match[2].trimEnd();
|
|
19496
|
+
if (isUrlLikePath(rest)) {
|
|
19497
|
+
clearDeeper(depth);
|
|
19498
|
+
currentFile = void 0;
|
|
19499
|
+
result.push({
|
|
19500
|
+
kind: "file",
|
|
19501
|
+
depth,
|
|
19502
|
+
isUrl: true
|
|
19503
|
+
});
|
|
19504
|
+
continue;
|
|
19505
|
+
}
|
|
19506
|
+
const parent = depth > 1 ? dirAtDepth.get(depth - 1) : headerBase;
|
|
19507
|
+
if (rest.endsWith("/")) {
|
|
19508
|
+
const abs = resolveGroupedPath(parent, rest.slice(0, -1).replace(HEADER_SUFFIX_RE, ""));
|
|
19509
|
+
clearDeeper(depth);
|
|
19510
|
+
if (abs !== void 0) dirAtDepth.set(depth, abs);
|
|
19511
|
+
currentFile = void 0;
|
|
19512
|
+
result.push({
|
|
19513
|
+
kind: "dir",
|
|
19514
|
+
depth,
|
|
19515
|
+
headerPath: abs
|
|
19516
|
+
});
|
|
19517
|
+
continue;
|
|
19518
|
+
}
|
|
19519
|
+
const name = rest.replace(HEADER_SUFFIX_RE, "").replace(HEADER_HASH_TAG_RE, "");
|
|
19520
|
+
const abs = name ? resolveGroupedPath(parent, name) : void 0;
|
|
19521
|
+
currentFile = abs;
|
|
19522
|
+
result.push({
|
|
19523
|
+
kind: "file",
|
|
19524
|
+
depth,
|
|
19525
|
+
headerPath: abs
|
|
19526
|
+
});
|
|
19527
|
+
}
|
|
19528
|
+
return result;
|
|
19529
|
+
}
|
|
18842
19530
|
//#endregion
|
|
18843
19531
|
//#region src/tools/omp/tools/match-line-format.ts
|
|
18844
19532
|
/**
|
|
@@ -18869,8 +19557,8 @@ function formatMatchLine(lineNumber, line, isMatch, options) {
|
|
|
18869
19557
|
*/
|
|
18870
19558
|
function matchAbsolutePath(matchPath, searchPath) {
|
|
18871
19559
|
if (matchPath === "") return searchPath;
|
|
18872
|
-
if (path.isAbsolute(matchPath)) return path.resolve(matchPath);
|
|
18873
|
-
return path.resolve(searchPath, matchPath);
|
|
19560
|
+
if (path$1.isAbsolute(matchPath)) return path$1.resolve(matchPath);
|
|
19561
|
+
return path$1.resolve(searchPath, matchPath);
|
|
18874
19562
|
}
|
|
18875
19563
|
//#endregion
|
|
18876
19564
|
//#region src/tools/omp/tools/grep.ts
|
|
@@ -19015,9 +19703,9 @@ async function resolveArchiveSearchPaths(pathSpecs, cwd) {
|
|
|
19015
19703
|
unreadable.push(`${entry} (non-UTF-8 archive entry)`);
|
|
19016
19704
|
continue;
|
|
19017
19705
|
}
|
|
19018
|
-
if (!tempDir) tempDir = await (0, fs_promises_shim_exports.mkdtemp)(path.join(tmpdir(), "omp-search-archive-"));
|
|
19019
|
-
const safeBase = path.basename(member.subPath).replace(/[^\w.-]+/g, "_") || "entry";
|
|
19020
|
-
const tempPath = path.join(tempDir, `${idx}-${safeBase}`);
|
|
19706
|
+
if (!tempDir) tempDir = await (0, fs_promises_shim_exports.mkdtemp)(path$1.join(tmpdir(), "omp-search-archive-"));
|
|
19707
|
+
const safeBase = path$1.basename(member.subPath).replace(/[^\w.-]+/g, "_") || "entry";
|
|
19708
|
+
const tempPath = path$1.join(tempDir, `${idx}-${safeBase}`);
|
|
19021
19709
|
await (0, fs_promises_shim_exports.writeFile)(tempPath, text);
|
|
19022
19710
|
resolvedPaths[idx] = tempPath;
|
|
19023
19711
|
displayMap.set(tempPath, entry);
|
|
@@ -19038,7 +19726,7 @@ async function resolveArchiveSearchPaths(pathSpecs, cwd) {
|
|
|
19038
19726
|
};
|
|
19039
19727
|
}
|
|
19040
19728
|
function isImmutableSourcePath(filePath, immutableSourcePaths) {
|
|
19041
|
-
for (const immutablePath of immutableSourcePaths) if (filePath === immutablePath || filePath.startsWith(`${immutablePath}${path.sep}`)) return true;
|
|
19729
|
+
for (const immutablePath of immutableSourcePaths) if (filePath === immutablePath || filePath.startsWith(`${immutablePath}${path$1.sep}`)) return true;
|
|
19042
19730
|
return false;
|
|
19043
19731
|
}
|
|
19044
19732
|
const OMP_ROOT_URL_RE = /^omp:\/\/(?:\/?|docs\/?)$/i;
|
|
@@ -19156,7 +19844,7 @@ async function nativeChunkedLineIndexes(dir, resourceIdx, content, pattern, igno
|
|
|
19156
19844
|
let chunkSeq = 0;
|
|
19157
19845
|
const flush = async () => {
|
|
19158
19846
|
if (chunkLines.length === 0) return;
|
|
19159
|
-
const scratch = path.resolve(dir, `${resourceIdx}-chunk-${chunkSeq++}`);
|
|
19847
|
+
const scratch = path$1.resolve(dir, `${resourceIdx}-chunk-${chunkSeq++}`);
|
|
19160
19848
|
await (0, fs_promises_shim_exports.writeFile)(scratch, chunkLines.join("\n"));
|
|
19161
19849
|
const probe = await grep({
|
|
19162
19850
|
pattern,
|
|
@@ -19264,7 +19952,7 @@ async function searchVirtualResources(resources, pattern, ignoreCase, multiline,
|
|
|
19264
19952
|
const filesWithMatches = /* @__PURE__ */ new Set();
|
|
19265
19953
|
let totalMatches = 0;
|
|
19266
19954
|
let limitReached = false;
|
|
19267
|
-
const dir = await (0, fs_promises_shim_exports.mkdtemp)(path.join(tmpdir(), "omp-search-virtual-"));
|
|
19955
|
+
const dir = await (0, fs_promises_shim_exports.mkdtemp)(path$1.join(tmpdir(), "omp-search-virtual-"));
|
|
19268
19956
|
try {
|
|
19269
19957
|
for (let idx = 0; idx < resources.length; idx++) {
|
|
19270
19958
|
const resource = resources[idx];
|
|
@@ -19277,7 +19965,7 @@ async function searchVirtualResources(resources, pattern, ignoreCase, multiline,
|
|
|
19277
19965
|
let matchedIndexes;
|
|
19278
19966
|
if (Buffer.byteLength(resource.content, "utf8") > NATIVE_GREP_MAX_FILE_BYTES) matchedIndexes = (multiline ? jsMatchedLineIndexes(resource.content, lines, pattern, ignoreCase, true) : await nativeChunkedLineIndexes(dir, idx, resource.content, pattern, ignoreCase, signal)).filter((lineIndex) => lineAllowed(lineIndex + 1, resource.ranges));
|
|
19279
19967
|
else {
|
|
19280
|
-
const scratch = path.resolve(dir, `${idx}`);
|
|
19968
|
+
const scratch = path$1.resolve(dir, `${idx}`);
|
|
19281
19969
|
await (0, fs_promises_shim_exports.writeFile)(scratch, resource.content);
|
|
19282
19970
|
const probe = await grep({
|
|
19283
19971
|
pattern,
|
|
@@ -19380,7 +20068,7 @@ async function resolveInternalSearchInputs(opts) {
|
|
|
19380
20068
|
if (resource.isDirectory && !resource.sourcePath) throw new ToolError(`search cannot recurse the directory listing at ${rawPath}; search a specific file under it (e.g. ${rawPath.replace(/\/+$/, "")}/<file>) or read ${rawPath} to list its entries`);
|
|
19381
20069
|
if (resource.sourcePath) {
|
|
19382
20070
|
paths[idx] = resource.sourcePath;
|
|
19383
|
-
if (resource.immutable) immutableSourcePaths.add(path.resolve(resource.sourcePath));
|
|
20071
|
+
if (resource.immutable) immutableSourcePaths.add(path$1.resolve(resource.sourcePath));
|
|
19384
20072
|
continue;
|
|
19385
20073
|
}
|
|
19386
20074
|
if (context.pathOnly) resource = await internalRouter.resolve(rawPath, {
|
|
@@ -19520,16 +20208,16 @@ var GrepTool = class {
|
|
|
19520
20208
|
if (!resolved) continue;
|
|
19521
20209
|
const materializedExternalPath = materializedExternalPaths.get(spec.clean);
|
|
19522
20210
|
if (materializedExternalPath) {
|
|
19523
|
-
mergeRangesInto(rangesByAbsPath, path.resolve(materializedExternalPath), spec.ranges);
|
|
20211
|
+
mergeRangesInto(rangesByAbsPath, path$1.resolve(materializedExternalPath), spec.ranges);
|
|
19524
20212
|
continue;
|
|
19525
20213
|
}
|
|
19526
20214
|
if (resolved === spec.clean && !archiveDisplayMap.has(resolved)) {
|
|
19527
|
-
const absKey = path.resolve(resolveReadPath(resolved, this.session.cwd));
|
|
20215
|
+
const absKey = path$1.resolve(resolveReadPath(resolved, this.session.cwd));
|
|
19528
20216
|
const stats = await (0, fs_promises_shim_exports.stat)(absKey).catch(() => null);
|
|
19529
20217
|
if (!stats) throw new ToolError(`Path not found for line-range selector: ${spec.original}`);
|
|
19530
20218
|
if (!stats.isFile()) throw new ToolError(`Line-range selector requires a single file: ${spec.original} is a directory`);
|
|
19531
20219
|
mergeRangesInto(rangesByAbsPath, absKey, spec.ranges);
|
|
19532
|
-
} else mergeRangesInto(rangesByAbsPath, path.resolve(resolved), spec.ranges);
|
|
20220
|
+
} else mergeRangesInto(rangesByAbsPath, path$1.resolve(resolved), spec.ranges);
|
|
19533
20221
|
}
|
|
19534
20222
|
const physicalScopePath = searchablePaths.length === 1 && archiveDisplayMap.get(searchPath) ? archiveDisplayMap.get(searchPath) : scope.scopePath;
|
|
19535
20223
|
scopePath = internalResolution.virtualScopePath ? `${physicalScopePath}, ${internalResolution.virtualScopePath}` : physicalScopePath;
|
|
@@ -19596,14 +20284,14 @@ var GrepTool = class {
|
|
|
19596
20284
|
totalMatches += targetResult.totalMatches;
|
|
19597
20285
|
filesSearched += targetResult.filesSearched;
|
|
19598
20286
|
for (const match of targetResult.matches) {
|
|
19599
|
-
const absolute = path.resolve(target.basePath, match.path);
|
|
20287
|
+
const absolute = path$1.resolve(target.basePath, match.path);
|
|
19600
20288
|
const matchKey = `${absolute}\0${match.lineNumber}`;
|
|
19601
20289
|
if (seenMatchKeys.has(matchKey)) {
|
|
19602
20290
|
totalMatches = Math.max(0, totalMatches - 1);
|
|
19603
20291
|
continue;
|
|
19604
20292
|
}
|
|
19605
20293
|
seenMatchKeys.add(matchKey);
|
|
19606
|
-
const rebased = path.relative(searchPath, absolute).replace(/\\/g, "/");
|
|
20294
|
+
const rebased = path$1.relative(searchPath, absolute).replace(/\\/g, "/");
|
|
19607
20295
|
matches.push({
|
|
19608
20296
|
...match,
|
|
19609
20297
|
path: rebased
|
|
@@ -19740,7 +20428,7 @@ var GrepTool = class {
|
|
|
19740
20428
|
await Promise.all(explicitFileTargets.map(async (target) => {
|
|
19741
20429
|
try {
|
|
19742
20430
|
const st = await (0, fs_promises_shim_exports.stat)(target);
|
|
19743
|
-
if (st.isFile() && st.size > NATIVE_GREP_MAX_FILE_BYTES) oversized.push(path.relative(this.session.cwd, target) || target);
|
|
20431
|
+
if (st.isFile() && st.size > NATIVE_GREP_MAX_FILE_BYTES) oversized.push(path$1.relative(this.session.cwd, target) || target);
|
|
19744
20432
|
} catch {}
|
|
19745
20433
|
}));
|
|
19746
20434
|
if (oversized.length === 0) return void 0;
|
|
@@ -19785,7 +20473,7 @@ var GrepTool = class {
|
|
|
19785
20473
|
const hashContexts = /* @__PURE__ */ new Map();
|
|
19786
20474
|
if (baseDisplayMode.hashLines) for (const relativePath of fileList) {
|
|
19787
20475
|
if (archiveDisplaySet.has(relativePath) || virtualPathSet.has(relativePath)) continue;
|
|
19788
|
-
const absoluteFilePath = path.resolve(this.session.cwd, relativePath);
|
|
20476
|
+
const absoluteFilePath = path$1.resolve(this.session.cwd, relativePath);
|
|
19789
20477
|
if (isImmutableSourcePath(absoluteFilePath, immutableSourcePaths)) continue;
|
|
19790
20478
|
const tag = await recordFileSnapshot(this.session, absoluteFilePath);
|
|
19791
20479
|
if (tag) hashContexts.set(relativePath, { tag });
|
|
@@ -19821,7 +20509,7 @@ var GrepTool = class {
|
|
|
19821
20509
|
fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1);
|
|
19822
20510
|
}
|
|
19823
20511
|
if (hashContext?.tag) {
|
|
19824
|
-
const absoluteFilePath = path.resolve(this.session.cwd, relativePath);
|
|
20512
|
+
const absoluteFilePath = path$1.resolve(this.session.cwd, relativePath);
|
|
19825
20513
|
recordSeenLinesFromBody(this.session, absoluteFilePath, hashContext.tag, modelOut.join("\n"));
|
|
19826
20514
|
}
|
|
19827
20515
|
return {
|
|
@@ -19889,6 +20577,204 @@ var GrepTool = class {
|
|
|
19889
20577
|
}
|
|
19890
20578
|
};
|
|
19891
20579
|
//#endregion
|
|
20580
|
+
//#region src/web/host/search.ts
|
|
20581
|
+
/**
|
|
20582
|
+
* Host projection for the `grep` / `ast_grep` / `glob` search cards.
|
|
20583
|
+
*
|
|
20584
|
+
* The engines already render the display text the TUI used to read: the grouped
|
|
20585
|
+
* file output (`# dir/` / `## file.ts#<tag>` headers) followed by code-frame
|
|
20586
|
+
* body lines (`*12│text` for a match, ` 12│text` for context, `…` for a gap).
|
|
20587
|
+
* That text is the only place the matched *line numbers* live — `details` only
|
|
20588
|
+
* carries per-file counts — so the card is rebuilt by parsing it with the same
|
|
20589
|
+
* `classifyGroupedLines` helper the TUI renderers used, then reading each
|
|
20590
|
+
* `formatCodeFrameLine` gutter back.
|
|
20591
|
+
*
|
|
20592
|
+
* Everything is defensive and bounded:
|
|
20593
|
+
* - an engine that explicitly reports zero (a `matchCount` of `0`, or an empty
|
|
20594
|
+
* `paths` array backed by a zero count) is a *known empty result*: it projects
|
|
20595
|
+
* to the empty card the client already renders, so the same tool never shows
|
|
20596
|
+
* two different shells;
|
|
20597
|
+
* - a display that yields no file group at all projects to `null` unless that
|
|
20598
|
+
* zero was reported (the row then falls back to the generic shell — the result
|
|
20599
|
+
* text stays fully visible);
|
|
20600
|
+
* - the payload is capped at {@link SEARCH_META_MAX_BYTES} by dropping trailing
|
|
20601
|
+
* groups/paths, with `truncated` raised and `total` left at its pre-cap value.
|
|
20602
|
+
*
|
|
20603
|
+
* @module @xiaoso/dsh-tool-plus/web/host/search
|
|
20604
|
+
*/
|
|
20605
|
+
/**
|
|
20606
|
+
* One rendered body line's gutter: optional `formatCodeFrameLine` match marker,
|
|
20607
|
+
* the 1-based line number, and the display separator. Both shipped modes end up
|
|
20608
|
+
* in `displayContent`, so the parser also accepts the model-facing `|` / `:`
|
|
20609
|
+
* separators an older engine version could emit.
|
|
20610
|
+
*/
|
|
20611
|
+
const CODE_FRAME_RE = /^\s*(\*?)(\d+)[│|:]([\s\S]*)$/;
|
|
20612
|
+
/** Whether `value` is a plain object (never `null` or an array). */
|
|
20613
|
+
function isRecord(value) {
|
|
20614
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20615
|
+
}
|
|
20616
|
+
/** Non-negative integer, or `undefined`. */
|
|
20617
|
+
function nonNegativeInteger$1(value) {
|
|
20618
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
20619
|
+
}
|
|
20620
|
+
/** The non-blank string `value` holds, or `undefined`. */
|
|
20621
|
+
function nonBlankString$1(value) {
|
|
20622
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
20623
|
+
}
|
|
20624
|
+
/**
|
|
20625
|
+
* The pre-cap total to report: the engine's own count when it is present and at
|
|
20626
|
+
* least what the display actually holds, otherwise the parsed count. The
|
|
20627
|
+
* metadata contract requires `total >= files.length`, and an engine count below
|
|
20628
|
+
* the parsed one only means the display and the count disagree.
|
|
20629
|
+
*/
|
|
20630
|
+
function reportedTotal(declared, parsed) {
|
|
20631
|
+
const count = nonNegativeInteger$1(declared);
|
|
20632
|
+
return count !== void 0 && count >= parsed ? count : parsed;
|
|
20633
|
+
}
|
|
20634
|
+
/** One parsed code-frame body line, or `null` for headers/gaps/unparsable text. */
|
|
20635
|
+
function parseFrameLine(line) {
|
|
20636
|
+
const match = CODE_FRAME_RE.exec(line);
|
|
20637
|
+
if (match === null) return null;
|
|
20638
|
+
const lineNumber = Number(match[2]);
|
|
20639
|
+
if (!Number.isInteger(lineNumber) || lineNumber < 1) return null;
|
|
20640
|
+
return {
|
|
20641
|
+
lineNumber,
|
|
20642
|
+
line: match[3] ?? "",
|
|
20643
|
+
isMatch: match[1] === "*"
|
|
20644
|
+
};
|
|
20645
|
+
}
|
|
20646
|
+
/**
|
|
20647
|
+
* Parse one grouped display into per-file match groups, in first-seen order.
|
|
20648
|
+
*
|
|
20649
|
+
* Only `*`-marked body lines become matches; context lines and `…` gap lines are
|
|
20650
|
+
* dropped. A body line whose owning file cannot be resolved (a URL group, or a
|
|
20651
|
+
* display read without a session base) is skipped rather than attributed to the
|
|
20652
|
+
* wrong file.
|
|
20653
|
+
* @param display - the engine's `displayContent`.
|
|
20654
|
+
* @param base - directory the display-relative header paths are relative to (the session cwd).
|
|
20655
|
+
* @param fileScope - absolute path of the only file, for a display without headers.
|
|
20656
|
+
* @returns the match groups; empty when nothing could be attributed.
|
|
20657
|
+
*/
|
|
20658
|
+
function parseMatchGroups(display, base, fileScope) {
|
|
20659
|
+
const lines = display.split("\n");
|
|
20660
|
+
const contexts = classifyGroupedLines(lines, base, fileScope);
|
|
20661
|
+
const groups = [];
|
|
20662
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
20663
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
20664
|
+
const context = contexts[index];
|
|
20665
|
+
if (context === void 0 || context.kind !== "content") continue;
|
|
20666
|
+
const parsed = parseFrameLine(lines[index] ?? "");
|
|
20667
|
+
if (parsed === null || !parsed.isMatch) continue;
|
|
20668
|
+
const filePath = context.filePath;
|
|
20669
|
+
if (typeof filePath !== "string" || filePath === "") continue;
|
|
20670
|
+
let group = byPath.get(filePath);
|
|
20671
|
+
if (group === void 0) {
|
|
20672
|
+
group = {
|
|
20673
|
+
path: filePath,
|
|
20674
|
+
matches: []
|
|
20675
|
+
};
|
|
20676
|
+
byPath.set(filePath, group);
|
|
20677
|
+
groups.push(group);
|
|
20678
|
+
}
|
|
20679
|
+
const matchLine = {
|
|
20680
|
+
lineNumber: parsed.lineNumber,
|
|
20681
|
+
line: parsed.line
|
|
20682
|
+
};
|
|
20683
|
+
group.matches.push(matchLine);
|
|
20684
|
+
}
|
|
20685
|
+
return groups;
|
|
20686
|
+
}
|
|
20687
|
+
/**
|
|
20688
|
+
* The known-empty matches card: the engine explicitly reported `matchCount: 0`,
|
|
20689
|
+
* so there is no grouping to parse but the empty result itself is definitive.
|
|
20690
|
+
*/
|
|
20691
|
+
function emptyMatchesMeta() {
|
|
20692
|
+
return {
|
|
20693
|
+
kind: "search",
|
|
20694
|
+
shape: "matches",
|
|
20695
|
+
files: [],
|
|
20696
|
+
truncated: false,
|
|
20697
|
+
total: 0
|
|
20698
|
+
};
|
|
20699
|
+
}
|
|
20700
|
+
/**
|
|
20701
|
+
* Project a `grep` / `ast_grep` result value into grouped-matches card metadata.
|
|
20702
|
+
*
|
|
20703
|
+
* @param input - the tool's output value: `displayContent`, `cwd`, `files`,
|
|
20704
|
+
* `matchCount` and `truncated` as the adapters project them.
|
|
20705
|
+
* @returns the card metadata, or `null` when no file group could be parsed.
|
|
20706
|
+
*/
|
|
20707
|
+
function searchMatchesCardMeta(input) {
|
|
20708
|
+
if (!isRecord(input)) return null;
|
|
20709
|
+
const zeroMatches = input.matchCount === 0;
|
|
20710
|
+
const display = typeof input.displayContent === "string" ? input.displayContent : void 0;
|
|
20711
|
+
if (display === void 0 || display === "") return zeroMatches ? emptyMatchesMeta() : null;
|
|
20712
|
+
const base = nonBlankString$1(input.cwd);
|
|
20713
|
+
const files = Array.isArray(input.files) ? input.files : [];
|
|
20714
|
+
let fileScope = files.length === 1 ? nonBlankString$1(files[0]) : void 0;
|
|
20715
|
+
if (fileScope !== void 0 && base !== void 0 && !path$1.isAbsolute(fileScope)) fileScope = path$1.resolve(base, fileScope);
|
|
20716
|
+
const groups = parseMatchGroups(display, base, fileScope);
|
|
20717
|
+
if (groups.length === 0) return zeroMatches ? emptyMatchesMeta() : null;
|
|
20718
|
+
const parsedMatches = groups.reduce((total, group) => total + group.matches.length, 0);
|
|
20719
|
+
const total = reportedTotal(input.matchCount, parsedMatches);
|
|
20720
|
+
const retained = capTail(groups, SEARCH_META_MAX_BYTES, (kept) => ({
|
|
20721
|
+
kind: "search",
|
|
20722
|
+
shape: "matches",
|
|
20723
|
+
files: kept,
|
|
20724
|
+
truncated: true,
|
|
20725
|
+
total
|
|
20726
|
+
}));
|
|
20727
|
+
if (retained.length === 0) return null;
|
|
20728
|
+
const retainedMatches = retained.reduce((count, group) => count + group.matches.length, 0);
|
|
20729
|
+
return {
|
|
20730
|
+
kind: "search",
|
|
20731
|
+
shape: "matches",
|
|
20732
|
+
files: retained,
|
|
20733
|
+
truncated: input.truncated === true || retainedMatches < total,
|
|
20734
|
+
total
|
|
20735
|
+
};
|
|
20736
|
+
}
|
|
20737
|
+
/**
|
|
20738
|
+
* Project a `glob` result value into path-list card metadata.
|
|
20739
|
+
*
|
|
20740
|
+
* @param input - the tool's output value: `paths`, `fileCount`, `total` and `truncated`.
|
|
20741
|
+
* @returns the card metadata, or `null` when the value is not a glob result (no
|
|
20742
|
+
* `paths` key, or an empty one the engine never backed with a zero count).
|
|
20743
|
+
*/
|
|
20744
|
+
function searchPathsCardMeta(input) {
|
|
20745
|
+
if (!isRecord(input)) return null;
|
|
20746
|
+
const listed = input.paths;
|
|
20747
|
+
if (!Array.isArray(listed)) return null;
|
|
20748
|
+
if (listed.length === 0) {
|
|
20749
|
+
if (!(nonNegativeInteger$1(input.fileCount) === 0 || nonNegativeInteger$1(input.total) === 0)) return null;
|
|
20750
|
+
return {
|
|
20751
|
+
kind: "search",
|
|
20752
|
+
shape: "paths",
|
|
20753
|
+
paths: [],
|
|
20754
|
+
truncated: false,
|
|
20755
|
+
total: reportedTotal(input.total, 0)
|
|
20756
|
+
};
|
|
20757
|
+
}
|
|
20758
|
+
const raw = listed.filter((entry) => typeof entry === "string");
|
|
20759
|
+
if (raw.length === 0) return null;
|
|
20760
|
+
const total = reportedTotal(input.total, raw.length);
|
|
20761
|
+
const retained = capTail(raw, SEARCH_META_MAX_BYTES, (kept) => ({
|
|
20762
|
+
kind: "search",
|
|
20763
|
+
shape: "paths",
|
|
20764
|
+
paths: kept,
|
|
20765
|
+
truncated: true,
|
|
20766
|
+
total
|
|
20767
|
+
}));
|
|
20768
|
+
if (retained.length === 0) return null;
|
|
20769
|
+
return {
|
|
20770
|
+
kind: "search",
|
|
20771
|
+
shape: "paths",
|
|
20772
|
+
paths: retained,
|
|
20773
|
+
truncated: input.truncated === true || retained.length < total,
|
|
20774
|
+
total
|
|
20775
|
+
};
|
|
20776
|
+
}
|
|
20777
|
+
//#endregion
|
|
19892
20778
|
//#region src/tools/grep/adapter/index.ts
|
|
19893
20779
|
/** Build the OMP ToolSession facade over a DSH exec context. */
|
|
19894
20780
|
function createToolSession$3(exec, cfg) {
|
|
@@ -19921,7 +20807,9 @@ function toGrepToolResult(result) {
|
|
|
19921
20807
|
...typeof details.fileCount === "number" ? { fileCount: details.fileCount } : {},
|
|
19922
20808
|
...Array.isArray(details.files) ? { files: details.files } : {},
|
|
19923
20809
|
...typeof details.scopePath === "string" ? { scopePath: details.scopePath } : {},
|
|
19924
|
-
...details.truncated === true ? { truncated: true } : {}
|
|
20810
|
+
...details.truncated === true ? { truncated: true } : {},
|
|
20811
|
+
...typeof details.displayContent === "string" ? { displayContent: details.displayContent } : {},
|
|
20812
|
+
...typeof details.cwd === "string" ? { cwd: details.cwd } : {}
|
|
19925
20813
|
};
|
|
19926
20814
|
}
|
|
19927
20815
|
/**
|
|
@@ -19988,18 +20876,16 @@ function registerGrep(ctx, getConfig) {
|
|
|
19988
20876
|
items: { type: "string" }
|
|
19989
20877
|
},
|
|
19990
20878
|
scopePath: { type: "string" },
|
|
19991
|
-
truncated: { type: "boolean" }
|
|
20879
|
+
truncated: { type: "boolean" },
|
|
20880
|
+
displayContent: { type: "string" },
|
|
20881
|
+
cwd: { type: "string" }
|
|
19992
20882
|
}
|
|
19993
20883
|
},
|
|
19994
20884
|
render: (_args, value) => [{
|
|
19995
20885
|
type: "text",
|
|
19996
20886
|
text: String(value.text ?? "")
|
|
19997
20887
|
}],
|
|
19998
|
-
presentationMeta: (_args, value) => (
|
|
19999
|
-
...typeof value.matchCount === "number" ? { matches: value.matchCount } : {},
|
|
20000
|
-
...typeof value.fileCount === "number" ? { files: value.fileCount } : {},
|
|
20001
|
-
...value.scopePath !== void 0 ? { path: value.scopePath } : {}
|
|
20002
|
-
})
|
|
20888
|
+
presentationMeta: (_args, value) => searchMatchesCardMeta(value) ?? null
|
|
20003
20889
|
},
|
|
20004
20890
|
async execute(args, exec) {
|
|
20005
20891
|
return executeGrepTool(exec, getConfig(), args, ctx);
|
|
@@ -20132,7 +21018,7 @@ var GlobTool = class {
|
|
|
20132
21018
|
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
20133
21019
|
const formatMatchPath = (matchPath, base, fileType) => {
|
|
20134
21020
|
const hadTrailingSlash = matchPath.endsWith("/") || matchPath.endsWith("\\");
|
|
20135
|
-
return formatPathRelativeToCwd(path.isAbsolute(matchPath) ? matchPath : path.resolve(base, matchPath), this.session.cwd, { trailingSlash: fileType === natives.FileType.Dir || hadTrailingSlash });
|
|
21021
|
+
return formatPathRelativeToCwd(path$1.isAbsolute(matchPath) ? matchPath : path$1.resolve(base, matchPath), this.session.cwd, { trailingSlash: fileType === natives.FileType.Dir || hadTrailingSlash });
|
|
20136
21022
|
};
|
|
20137
21023
|
const missingPathsNote = missingPaths.length > 0 ? `Skipped missing paths: ${missingPaths.join(", ")}` : void 0;
|
|
20138
21024
|
const buildResult = (files, opts) => {
|
|
@@ -20342,7 +21228,8 @@ function toGlobToolResult(result, args) {
|
|
|
20342
21228
|
path: String(details.scopePath ?? args.path ?? ""),
|
|
20343
21229
|
text: text + notice,
|
|
20344
21230
|
...typeof details.fileCount === "number" ? { fileCount: details.fileCount } : {},
|
|
20345
|
-
...typeof details.truncated === "boolean" ? { truncated: details.truncated } : {}
|
|
21231
|
+
...typeof details.truncated === "boolean" ? { truncated: details.truncated } : {},
|
|
21232
|
+
...Array.isArray(details.files) ? { paths: details.files.filter((entry) => typeof entry === "string") } : {}
|
|
20346
21233
|
};
|
|
20347
21234
|
}
|
|
20348
21235
|
/**
|
|
@@ -20399,17 +21286,18 @@ function registerGlob(ctx, getConfig) {
|
|
|
20399
21286
|
required: true
|
|
20400
21287
|
},
|
|
20401
21288
|
fileCount: { type: "number" },
|
|
20402
|
-
truncated: { type: "boolean" }
|
|
21289
|
+
truncated: { type: "boolean" },
|
|
21290
|
+
paths: {
|
|
21291
|
+
type: "array",
|
|
21292
|
+
items: { type: "string" }
|
|
21293
|
+
}
|
|
20403
21294
|
}
|
|
20404
21295
|
},
|
|
20405
21296
|
render: (_args, value) => [{
|
|
20406
21297
|
type: "text",
|
|
20407
21298
|
text: String(value.text ?? "")
|
|
20408
21299
|
}],
|
|
20409
|
-
presentationMeta: (_args, value) => (
|
|
20410
|
-
...value.fileCount !== void 0 ? { files: value.fileCount } : {},
|
|
20411
|
-
...value.truncated !== void 0 ? { truncated: value.truncated } : {}
|
|
20412
|
-
})
|
|
21300
|
+
presentationMeta: (_args, value) => searchPathsCardMeta(value) ?? null
|
|
20413
21301
|
},
|
|
20414
21302
|
async execute(args, exec) {
|
|
20415
21303
|
return executeGlobTool(exec, getConfig(), args, ctx);
|
|
@@ -20462,16 +21350,16 @@ async function runAstEditTargets(targets, commonBasePath, options) {
|
|
|
20462
21350
|
applied = applied && targetResult.applied;
|
|
20463
21351
|
if (targetResult.parseErrors) parseErrors.push(...targetResult.parseErrors);
|
|
20464
21352
|
for (const change of targetResult.changes) {
|
|
20465
|
-
const absolute = path.resolve(target.basePath, change.path);
|
|
20466
|
-
const rebased = path.relative(commonBasePath, absolute).replace(/\\/g, "/");
|
|
21353
|
+
const absolute = path$1.resolve(target.basePath, change.path);
|
|
21354
|
+
const rebased = path$1.relative(commonBasePath, absolute).replace(/\\/g, "/");
|
|
20467
21355
|
aggregatedChanges.push({
|
|
20468
21356
|
...change,
|
|
20469
21357
|
path: rebased
|
|
20470
21358
|
});
|
|
20471
21359
|
}
|
|
20472
21360
|
for (const fileChange of targetResult.fileChanges) {
|
|
20473
|
-
const absolute = path.resolve(target.basePath, fileChange.path);
|
|
20474
|
-
const rebased = path.relative(commonBasePath, absolute).replace(/\\/g, "/");
|
|
21361
|
+
const absolute = path$1.resolve(target.basePath, fileChange.path);
|
|
21362
|
+
const rebased = path$1.relative(commonBasePath, absolute).replace(/\\/g, "/");
|
|
20475
21363
|
fileCounts.set(rebased, (fileCounts.get(rebased) ?? 0) + fileChange.count);
|
|
20476
21364
|
}
|
|
20477
21365
|
}
|
|
@@ -20670,7 +21558,7 @@ var AstEditTool = class {
|
|
|
20670
21558
|
if (useHashLines) {
|
|
20671
21559
|
const snapshotStore = getFileSnapshotStore(this.session);
|
|
20672
21560
|
for (const relativePath of fileList) {
|
|
20673
|
-
const absolutePath = path.resolve(this.session.cwd, relativePath);
|
|
21561
|
+
const absolutePath = path$1.resolve(this.session.cwd, relativePath);
|
|
20674
21562
|
try {
|
|
20675
21563
|
const fullText = normalizeToLF(await Bun.file(absolutePath).text());
|
|
20676
21564
|
const tag = snapshotStore.recordSnapshot(canonicalSnapshotKey(absolutePath), fullText);
|
|
@@ -20762,7 +21650,7 @@ var AstEditTool = class {
|
|
|
20762
21650
|
if (useHashLines) {
|
|
20763
21651
|
const snapshotStore = getFileSnapshotStore(this.session);
|
|
20764
21652
|
for (const relativePath of appliedFileList) {
|
|
20765
|
-
const appliedAbsolutePath = path.resolve(this.session.cwd, relativePath);
|
|
21653
|
+
const appliedAbsolutePath = path$1.resolve(this.session.cwd, relativePath);
|
|
20766
21654
|
try {
|
|
20767
21655
|
const fullText = normalizeToLF(await Bun.file(appliedAbsolutePath).text());
|
|
20768
21656
|
const freshTag = snapshotStore.recordSnapshot(canonicalSnapshotKey(appliedAbsolutePath), fullText);
|
|
@@ -20814,6 +21702,116 @@ var AstEditTool = class {
|
|
|
20814
21702
|
}
|
|
20815
21703
|
};
|
|
20816
21704
|
//#endregion
|
|
21705
|
+
//#region src/web/host/ast-edit.ts
|
|
21706
|
+
/**
|
|
21707
|
+
* `ast_edit` tool card: project the engine's own change preview plus its
|
|
21708
|
+
* counters into the `{kind:'ast_edit'}` meta this plugin's browser half
|
|
21709
|
+
* consumes (plan §4.3).
|
|
21710
|
+
*
|
|
21711
|
+
* The engine renders a preview (`displayContent`: grouped hits with a `│` line
|
|
21712
|
+
* gutter) for exactly the replacements it found, so the card reuses that text
|
|
21713
|
+
* verbatim instead of sampling `before`/`after`. The preview is bounded by
|
|
21714
|
+
* {@link AST_EDIT_META_MAX_BYTES}: past that budget the text is truncated (the
|
|
21715
|
+
* file list and counters survive), and if even an empty preview cannot fit the
|
|
21716
|
+
* payload, trailing files are dropped.
|
|
21717
|
+
*
|
|
21718
|
+
* Zero-dependency by design (no React, no Node built-ins beyond the contract's
|
|
21719
|
+
* own helpers), so the Node half can import it from `presentationMeta` without
|
|
21720
|
+
* dragging anything into the browser bundle.
|
|
21721
|
+
*
|
|
21722
|
+
* Defensive: a card never throws and never emits `undefined` values (the host
|
|
21723
|
+
* rejects non-lossless metadata and turns a successful call into an error); no
|
|
21724
|
+
* replacement, or no preview text, projects to `null` so the row falls back to
|
|
21725
|
+
* the generic shell.
|
|
21726
|
+
*
|
|
21727
|
+
* @module @xiaoso/dsh-tool-plus/web/host/ast-edit
|
|
21728
|
+
*/
|
|
21729
|
+
/** `value` as a plain record, or `null` (arrays, `null` and primitives drop). */
|
|
21730
|
+
function asRecord(value) {
|
|
21731
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
21732
|
+
}
|
|
21733
|
+
/** Non-negative integer, or `null`. */
|
|
21734
|
+
function nonNegativeInteger(value) {
|
|
21735
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
|
|
21736
|
+
}
|
|
21737
|
+
/** Non-blank string, or `null`. */
|
|
21738
|
+
function nonBlankString(value) {
|
|
21739
|
+
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
21740
|
+
}
|
|
21741
|
+
/** The engine's `fileReplacements[{path,count}]`, malformed entries dropped. */
|
|
21742
|
+
function projectFiles(value) {
|
|
21743
|
+
if (!Array.isArray(value)) return [];
|
|
21744
|
+
const files = [];
|
|
21745
|
+
for (const entry of value) {
|
|
21746
|
+
const file = asRecord(entry);
|
|
21747
|
+
if (file === null) continue;
|
|
21748
|
+
const path = nonBlankString(file.path);
|
|
21749
|
+
const count = nonNegativeInteger(file.count);
|
|
21750
|
+
if (path === null || count === null) continue;
|
|
21751
|
+
files.push({
|
|
21752
|
+
path,
|
|
21753
|
+
count
|
|
21754
|
+
});
|
|
21755
|
+
}
|
|
21756
|
+
return files;
|
|
21757
|
+
}
|
|
21758
|
+
/**
|
|
21759
|
+
* Total replacements: the engine's own count when present, else the file
|
|
21760
|
+
* counts' sum (a missing array then counts as zero).
|
|
21761
|
+
*/
|
|
21762
|
+
function replacementsOf(record, files) {
|
|
21763
|
+
const reported = nonNegativeInteger(record.totalReplacements);
|
|
21764
|
+
if (reported !== null) return reported;
|
|
21765
|
+
return files.reduce((total, file) => total + file.count, 0);
|
|
21766
|
+
}
|
|
21767
|
+
/**
|
|
21768
|
+
* Build the payload within {@link AST_EDIT_META_MAX_BYTES}.
|
|
21769
|
+
*
|
|
21770
|
+
* Trailing files are dropped first (the counters survive), then the preview
|
|
21771
|
+
* gets every remaining byte. The preview budget is found by binary search over
|
|
21772
|
+
* `capText` — the raw text is measured in UTF-8 bytes while the payload is
|
|
21773
|
+
* measured as JSON, and escaping can inflate the latter, so spending the exact
|
|
21774
|
+
* cap on text is not enough.
|
|
21775
|
+
*/
|
|
21776
|
+
function fitAstEditMeta(raw, files, replacements, applied) {
|
|
21777
|
+
const build = (preview, retained) => ({
|
|
21778
|
+
kind: "ast_edit",
|
|
21779
|
+
preview,
|
|
21780
|
+
files: [...retained],
|
|
21781
|
+
replacements,
|
|
21782
|
+
applied
|
|
21783
|
+
});
|
|
21784
|
+
const retained = capTail(files, AST_EDIT_META_MAX_BYTES, (kept) => build("", kept));
|
|
21785
|
+
const fits = (budget) => jsonByteLength(build(capText(raw, budget), retained)) <= AST_EDIT_META_MAX_BYTES;
|
|
21786
|
+
if (!fits(0)) return null;
|
|
21787
|
+
let low = 0;
|
|
21788
|
+
let high = AST_EDIT_META_MAX_BYTES;
|
|
21789
|
+
while (low < high) {
|
|
21790
|
+
const mid = Math.ceil((low + high) / 2);
|
|
21791
|
+
if (fits(mid)) low = mid;
|
|
21792
|
+
else high = mid - 1;
|
|
21793
|
+
}
|
|
21794
|
+
return build(capText(raw, low), retained);
|
|
21795
|
+
}
|
|
21796
|
+
/**
|
|
21797
|
+
* Project the ast_edit engine's result into card metadata.
|
|
21798
|
+
* @param details - the engine's `displayContent` / `fileReplacements` /
|
|
21799
|
+
* `totalReplacements` / `applied` fields (the adapter's value carries the
|
|
21800
|
+
* same names).
|
|
21801
|
+
* @returns the ast_edit card metadata, or `null` when the call previews no
|
|
21802
|
+
* replacement or produced no preview text.
|
|
21803
|
+
*/
|
|
21804
|
+
function projectAstEditCardMeta(details) {
|
|
21805
|
+
const record = asRecord(details);
|
|
21806
|
+
if (record === null) return null;
|
|
21807
|
+
const preview = nonBlankString(record.displayContent);
|
|
21808
|
+
if (preview === null) return null;
|
|
21809
|
+
const files = projectFiles(record.fileReplacements);
|
|
21810
|
+
const replacements = replacementsOf(record, files);
|
|
21811
|
+
if (replacements <= 0) return null;
|
|
21812
|
+
return fitAstEditMeta(preview, files, replacements, record.applied === true);
|
|
21813
|
+
}
|
|
21814
|
+
//#endregion
|
|
20817
21815
|
//#region src/tools/ast-edit/adapter/index.ts
|
|
20818
21816
|
/**
|
|
20819
21817
|
* Build the OMP ToolSession facade over a DSH exec context.
|
|
@@ -20850,6 +21848,18 @@ function toText$1(result) {
|
|
|
20850
21848
|
return text;
|
|
20851
21849
|
}
|
|
20852
21850
|
/**
|
|
21851
|
+
* ast_edit 卡片 value 字段(output.schema 镜像)。预览已按 AST_EDIT_META_MAX_BYTES
|
|
21852
|
+
* 在投影里截断,这里只做搬运,保证 meta 里不出现 undefined。
|
|
21853
|
+
*/
|
|
21854
|
+
function astEditCardFields(card) {
|
|
21855
|
+
return {
|
|
21856
|
+
preview: card.preview,
|
|
21857
|
+
files: card.files,
|
|
21858
|
+
replacements: card.replacements,
|
|
21859
|
+
applied: card.applied
|
|
21860
|
+
};
|
|
21861
|
+
}
|
|
21862
|
+
/**
|
|
20853
21863
|
* Register the ast_edit tool. Argument shape matches OMP verbatim:
|
|
20854
21864
|
* `ops` is a non-empty array of `{ pat, out }` rewrite rules, and `paths` is a
|
|
20855
21865
|
* non-empty array of files, directories, globs, or internal URLs.
|
|
@@ -20891,15 +21901,44 @@ function registerAstEdit(ctx, getConfig) {
|
|
|
20891
21901
|
schema: {
|
|
20892
21902
|
type: "object",
|
|
20893
21903
|
additionalProperties: false,
|
|
20894
|
-
properties: {
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
21904
|
+
properties: {
|
|
21905
|
+
text: {
|
|
21906
|
+
type: "string",
|
|
21907
|
+
required: true
|
|
21908
|
+
},
|
|
21909
|
+
preview: { type: "string" },
|
|
21910
|
+
files: {
|
|
21911
|
+
type: "array",
|
|
21912
|
+
items: {
|
|
21913
|
+
type: "object",
|
|
21914
|
+
additionalProperties: false,
|
|
21915
|
+
properties: {
|
|
21916
|
+
path: {
|
|
21917
|
+
type: "string",
|
|
21918
|
+
required: true
|
|
21919
|
+
},
|
|
21920
|
+
count: {
|
|
21921
|
+
type: "integer",
|
|
21922
|
+
required: true
|
|
21923
|
+
}
|
|
21924
|
+
}
|
|
21925
|
+
}
|
|
21926
|
+
},
|
|
21927
|
+
replacements: { type: "integer" },
|
|
21928
|
+
applied: { type: "boolean" }
|
|
21929
|
+
}
|
|
20898
21930
|
},
|
|
20899
21931
|
render: (_args, value) => [{
|
|
20900
21932
|
type: "text",
|
|
20901
21933
|
text: String(value.text ?? "")
|
|
20902
|
-
}]
|
|
21934
|
+
}],
|
|
21935
|
+
presentationMeta: (_args, value) => typeof value?.preview === "string" && value.preview !== "" ? {
|
|
21936
|
+
kind: "ast_edit",
|
|
21937
|
+
preview: value.preview,
|
|
21938
|
+
files: Array.isArray(value.files) ? value.files : [],
|
|
21939
|
+
replacements: typeof value.replacements === "number" ? value.replacements : 0,
|
|
21940
|
+
applied: value.applied === true
|
|
21941
|
+
} : null
|
|
20903
21942
|
},
|
|
20904
21943
|
async execute(args, exec) {
|
|
20905
21944
|
const session = createToolSession$1(exec, getConfig());
|
|
@@ -20907,8 +21946,27 @@ function registerAstEdit(ctx, getConfig) {
|
|
|
20907
21946
|
ops: Array.isArray(args.ops) ? args.ops : [],
|
|
20908
21947
|
paths: Array.isArray(args.paths) ? args.paths : []
|
|
20909
21948
|
}, exec.signal);
|
|
20910
|
-
|
|
20911
|
-
|
|
21949
|
+
const previewDetails = result.details ?? {};
|
|
21950
|
+
if (session.pendingInvoker) {
|
|
21951
|
+
const applied = await session.pendingInvoker();
|
|
21952
|
+
const resolveDetails = applied.details ?? {};
|
|
21953
|
+
const appliedDetails = typeof resolveDetails.sourceResultDetails === "object" && resolveDetails.sourceResultDetails !== null ? resolveDetails.sourceResultDetails : resolveDetails;
|
|
21954
|
+
const card = projectAstEditCardMeta({
|
|
21955
|
+
...appliedDetails,
|
|
21956
|
+
displayContent: appliedDetails.displayContent ?? previewDetails.displayContent
|
|
21957
|
+
});
|
|
21958
|
+
const text = toText$1(applied);
|
|
21959
|
+
return card === null ? { text } : {
|
|
21960
|
+
text,
|
|
21961
|
+
...astEditCardFields(card)
|
|
21962
|
+
};
|
|
21963
|
+
}
|
|
21964
|
+
const card = projectAstEditCardMeta(previewDetails);
|
|
21965
|
+
const text = toText$1(result);
|
|
21966
|
+
return card === null ? { text } : {
|
|
21967
|
+
text,
|
|
21968
|
+
...astEditCardFields(card)
|
|
21969
|
+
};
|
|
20912
21970
|
},
|
|
20913
21971
|
presentCall: (args) => ({
|
|
20914
21972
|
card: "generic",
|
|
@@ -20977,8 +22035,8 @@ async function runMultiTargetAstGrep(targets, options) {
|
|
|
20977
22035
|
limitReached = limitReached || targetResult.limitReached;
|
|
20978
22036
|
if (targetResult.parseErrors) parseErrors.push(...targetResult.parseErrors);
|
|
20979
22037
|
for (const match of targetResult.matches) {
|
|
20980
|
-
const absolute = path.resolve(target.basePath, match.path);
|
|
20981
|
-
const rebased = path.relative(options.commonBasePath, absolute).replace(/\\/g, "/");
|
|
22038
|
+
const absolute = path$1.resolve(target.basePath, match.path);
|
|
22039
|
+
const rebased = path$1.relative(options.commonBasePath, absolute).replace(/\\/g, "/");
|
|
20982
22040
|
retainAstFindMatch(retainedMatches, retainedCapacity, {
|
|
20983
22041
|
...match,
|
|
20984
22042
|
path: rebased
|
|
@@ -21127,7 +22185,7 @@ var AstGrepTool = class {
|
|
|
21127
22185
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
|
21128
22186
|
const hashContexts = /* @__PURE__ */ new Map();
|
|
21129
22187
|
if (useHashLines) for (const relativePath of fileList) {
|
|
21130
|
-
const absolutePath = path.resolve(this.session.cwd, relativePath);
|
|
22188
|
+
const absolutePath = path$1.resolve(this.session.cwd, relativePath);
|
|
21131
22189
|
const tag = await recordFileSnapshot(this.session, absolutePath);
|
|
21132
22190
|
if (tag) hashContexts.set(relativePath, { tag });
|
|
21133
22191
|
}
|
|
@@ -21160,7 +22218,7 @@ var AstGrepTool = class {
|
|
|
21160
22218
|
fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1);
|
|
21161
22219
|
}
|
|
21162
22220
|
if (hashContext?.tag) {
|
|
21163
|
-
const absoluteFilePath = path.resolve(this.session.cwd, relativePath);
|
|
22221
|
+
const absoluteFilePath = path$1.resolve(this.session.cwd, relativePath);
|
|
21164
22222
|
recordSeenLinesFromBody(this.session, absoluteFilePath, hashContext.tag, modelOut.join("\n"));
|
|
21165
22223
|
}
|
|
21166
22224
|
return {
|
|
@@ -21224,6 +22282,28 @@ function toText(result) {
|
|
|
21224
22282
|
return text;
|
|
21225
22283
|
}
|
|
21226
22284
|
/**
|
|
22285
|
+
* 把 OMP AgentToolResult 转成 DSH ast_grep 工具输出。
|
|
22286
|
+
*
|
|
22287
|
+
* 此前适配层只回 `{text}`,卡片拿不到任何结构化数据;现在把引擎的
|
|
22288
|
+
* `displayContent`(分组显示文本)+ `files` + `matchCount` + `limitReached`
|
|
22289
|
+
* 一并带出,供 `presentationMeta` 投影成 search 卡(§4.6)。isError 仍抛错,
|
|
22290
|
+
* 报错文案不变。
|
|
22291
|
+
*/
|
|
22292
|
+
function toAstGrepToolResult(result) {
|
|
22293
|
+
const text = toText(result);
|
|
22294
|
+
const details = result.details ?? {};
|
|
22295
|
+
return {
|
|
22296
|
+
text,
|
|
22297
|
+
...typeof details.matchCount === "number" ? { matchCount: details.matchCount } : {},
|
|
22298
|
+
...typeof details.fileCount === "number" ? { fileCount: details.fileCount } : {},
|
|
22299
|
+
...Array.isArray(details.files) ? { files: details.files.filter((entry) => typeof entry === "string") } : {},
|
|
22300
|
+
...typeof details.scopePath === "string" ? { scopePath: details.scopePath } : {},
|
|
22301
|
+
...details.limitReached === true || details.truncated === true ? { truncated: true } : {},
|
|
22302
|
+
...typeof details.displayContent === "string" ? { displayContent: details.displayContent } : {},
|
|
22303
|
+
...typeof details.cwd === "string" ? { cwd: details.cwd } : {}
|
|
22304
|
+
};
|
|
22305
|
+
}
|
|
22306
|
+
/**
|
|
21227
22307
|
* Register the ast_grep tool. Argument shape matches OMP verbatim:
|
|
21228
22308
|
* `pat` is a required AST pattern; `path` optionally selects files,
|
|
21229
22309
|
* directories, globs, or internal URLs (semicolon-delimited list); `skip`
|
|
@@ -21252,22 +22332,35 @@ function registerAstGrep(ctx, getConfig) {
|
|
|
21252
22332
|
schema: {
|
|
21253
22333
|
type: "object",
|
|
21254
22334
|
additionalProperties: false,
|
|
21255
|
-
properties: {
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
22335
|
+
properties: {
|
|
22336
|
+
text: {
|
|
22337
|
+
type: "string",
|
|
22338
|
+
required: true
|
|
22339
|
+
},
|
|
22340
|
+
matchCount: { type: "number" },
|
|
22341
|
+
fileCount: { type: "number" },
|
|
22342
|
+
files: {
|
|
22343
|
+
type: "array",
|
|
22344
|
+
items: { type: "string" }
|
|
22345
|
+
},
|
|
22346
|
+
scopePath: { type: "string" },
|
|
22347
|
+
truncated: { type: "boolean" },
|
|
22348
|
+
displayContent: { type: "string" },
|
|
22349
|
+
cwd: { type: "string" }
|
|
22350
|
+
}
|
|
21259
22351
|
},
|
|
21260
22352
|
render: (_args, value) => [{
|
|
21261
22353
|
type: "text",
|
|
21262
22354
|
text: String(value.text ?? "")
|
|
21263
|
-
}]
|
|
22355
|
+
}],
|
|
22356
|
+
presentationMeta: (_args, value) => searchMatchesCardMeta(value) ?? null
|
|
21264
22357
|
},
|
|
21265
22358
|
async execute(args, exec) {
|
|
21266
|
-
return
|
|
22359
|
+
return toAstGrepToolResult(await new AstGrepTool(createToolSession(exec, getConfig())).execute("ast_grep", {
|
|
21267
22360
|
pat: String(args.pat ?? ""),
|
|
21268
22361
|
...args.path !== void 0 ? { path: String(args.path) } : {},
|
|
21269
22362
|
...args.skip !== void 0 ? { skip: Number(args.skip) } : {}
|
|
21270
|
-
}, exec.signal))
|
|
22363
|
+
}, exec.signal));
|
|
21271
22364
|
},
|
|
21272
22365
|
presentCall: (args) => ({
|
|
21273
22366
|
card: "generic",
|
|
@@ -21540,7 +22633,8 @@ function apply(ctx, config = {}) {
|
|
|
21540
22633
|
render: (_args, value) => [{
|
|
21541
22634
|
type: "text",
|
|
21542
22635
|
text: value.kind === "background" ? `Backgrounded as job ${value.jobId}; result will be delivered automatically when it finishes. Continue with other work — do not poll for it.` : renderBashResult(value)
|
|
21543
|
-
}]
|
|
22636
|
+
}],
|
|
22637
|
+
presentationMeta: (_args, value) => bashCardMeta(value)
|
|
21544
22638
|
},
|
|
21545
22639
|
async execute(args, exec) {
|
|
21546
22640
|
validateBashArgs(args);
|