opencode-readseek 0.9.5 → 0.9.7

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.
Files changed (2) hide show
  1. package/dist/index.js +824 -933
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // index.ts
2
- import { lstat, mkdir, open, readFile, readlink, realpath, stat } from "node:fs/promises";
2
+ import path7 from "node:path";
3
+
4
+ // src/client.ts
3
5
  import { createRequire as createRequire2 } from "node:module";
4
6
  import { homedir } from "node:os";
5
- import path2 from "node:path";
6
- import { tool } from "@opencode-ai/plugin";
7
+ import path3 from "node:path";
8
+ import { stat } from "node:fs/promises";
7
9
 
8
10
  // ../readseek-api/src/contract.ts
9
11
  var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
@@ -22,11 +24,10 @@ var DOCUMENT_NODE_KINDS = [
22
24
  "structural_section"
23
25
  ];
24
26
  var TOOL_NAMES = [
25
- "read",
27
+ "digest",
26
28
  "edit",
27
29
  "write",
28
30
  "grep",
29
- "digest",
30
31
  "view",
31
32
  "search",
32
33
  "def",
@@ -35,6 +36,8 @@ var TOOL_NAMES = [
35
36
  ];
36
37
  var PARAM_DESCRIPTIONS = {
37
38
  path: "Absolute or relative file path",
39
+ projectPath: "Path relative to the project directory",
40
+ projectSearchPath: "File or directory to search; defaults to the project directory",
38
41
  searchPath: "File or directory to search; defaults to the current directory",
39
42
  language: "Language override when auto-detection is ambiguous",
40
43
  languageOverride: "Language override",
@@ -88,6 +91,7 @@ var PARAM_DESCRIPTIONS = {
88
91
  symbol: "Mapped symbol name",
89
92
  newBody: "Complete replacement body for the symbol",
90
93
  apply: "When supported by the host, apply the verified plan instead of previewing only",
94
+ applyDefaultTrue: "Apply the verified edits; defaults to true",
91
95
  planHash: "Require the apply plan to match this dry-run plan hash",
92
96
  workspace: "Rename across the project",
93
97
  to: "New symbol name",
@@ -103,11 +107,10 @@ var PARAM_DESCRIPTIONS = {
103
107
  scopeContext: "Context lines within each symbol"
104
108
  };
105
109
  var TOOL_DESCRIPTIONS = {
106
- read: "Read anchored text by range or symbol. Standalone images require an explicit mode. Use it before edits to obtain fresh `LINE:HASH` anchors; use `view` for documents and `digest` with the map facet for structural outlines.",
110
+ digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`. Use it before edits to obtain fresh `LINE:HASH` anchors; use for structural maps, diagnostics, and cursor identity.",
107
111
  edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
108
112
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
109
113
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
110
- digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`; use for structural maps, diagnostics, and cursor identity.",
111
114
  view: "Digest a document's structure or selected content. Start with the overview, then narrow by page, node, kind, or depth instead of reading the whole document. PDF is the first supported document format.",
112
115
  search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
113
116
  def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
@@ -115,7 +118,10 @@ var TOOL_DESCRIPTIONS = {
115
118
  rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
116
119
  };
117
120
  function renderToolDescription(name, aliases = {}) {
118
- return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), TOOL_DESCRIPTIONS[name]);
121
+ const desc = TOOL_DESCRIPTIONS[name];
122
+ if (!desc)
123
+ throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
124
+ return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
119
125
  }
120
126
  var gitFlagsSchema = {
121
127
  cached: {
@@ -194,25 +200,27 @@ var editItemSchema = {
194
200
  };
195
201
  var TOOL_CONTRACTS = [
196
202
  {
197
- name: "read",
198
- title: "Read anchored text",
199
- description: TOOL_DESCRIPTIONS.read,
203
+ name: "digest",
204
+ title: "Digest source",
205
+ description: TOOL_DESCRIPTIONS.digest,
200
206
  inputSchema: {
201
207
  type: "object",
202
208
  properties: {
203
209
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
204
- offset: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.offset },
205
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.readLimit },
206
- symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbolRead },
207
- bundle: {
210
+ select: { type: "string", description: PARAM_DESCRIPTIONS.select },
211
+ at: { type: "string", description: PARAM_DESCRIPTIONS.at },
212
+ end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
213
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
214
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
215
+ visionMode: {
208
216
  type: "string",
209
- enum: ["local"],
210
- description: PARAM_DESCRIPTIONS.bundle
217
+ enum: [...VISION_MODES],
218
+ description: PARAM_DESCRIPTIONS.visionModeDigest
211
219
  },
212
- image: {
220
+ visionLevel: {
213
221
  type: "string",
214
- enum: [...VISION_MODES],
215
- description: PARAM_DESCRIPTIONS.image
222
+ enum: [...VISION_LEVELS],
223
+ description: PARAM_DESCRIPTIONS.visionLevel
216
224
  }
217
225
  },
218
226
  required: ["path"],
@@ -286,34 +294,6 @@ var TOOL_CONTRACTS = [
286
294
  additionalProperties: false
287
295
  }
288
296
  },
289
- {
290
- name: "digest",
291
- title: "Digest source",
292
- description: TOOL_DESCRIPTIONS.digest,
293
- inputSchema: {
294
- type: "object",
295
- properties: {
296
- path: { type: "string", description: PARAM_DESCRIPTIONS.path },
297
- select: { type: "string", description: PARAM_DESCRIPTIONS.select },
298
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
299
- end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
300
- limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
301
- language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
302
- visionMode: {
303
- type: "string",
304
- enum: [...VISION_MODES],
305
- description: PARAM_DESCRIPTIONS.visionModeDigest
306
- },
307
- visionLevel: {
308
- type: "string",
309
- enum: [...VISION_LEVELS],
310
- description: PARAM_DESCRIPTIONS.visionLevel
311
- }
312
- },
313
- required: ["path"],
314
- additionalProperties: false
315
- }
316
- },
317
297
  {
318
298
  name: "view",
319
299
  title: "View document",
@@ -416,6 +396,88 @@ var TOOL_CONTRACTS = [
416
396
  }
417
397
  }
418
398
  ];
399
+ // ../readseek-api/src/policy.ts
400
+ var BUILTIN_FALLBACKS = {
401
+ read: "digest",
402
+ edit: "edit",
403
+ write: "write",
404
+ grep: "grep",
405
+ apply_patch: "edit"
406
+ };
407
+ var PREFERENCE_LEADS = {
408
+ digest: "Preferred for reading source with anchors and facets.",
409
+ edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
410
+ write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
411
+ grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
412
+ search: "Preferred for AST or structural code search with ast-grep-style patterns.",
413
+ def: "Preferred for go-to-definition or declaration lookup by name.",
414
+ refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
415
+ rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
416
+ view: "Preferred for digesting document structure or selected content."
417
+ };
418
+ var FALLBACK_TEMPLATES = {
419
+ read: "Fallback only. Prefer `digest` with fresh LINE:HASH anchors.",
420
+ edit: "Fallback only. Prefer `edit` with fresh LINE:HASH anchors.",
421
+ write: "Fallback only. Prefer `write` so the result returns LINE:HASH anchors.",
422
+ grep: "Fallback only. Prefer `grep` for text/regex search with edit-ready anchors.",
423
+ apply_patch: "Fallback only. Prefer `edit` for existing text files."
424
+ };
425
+ function resolveToolName(name, aliases = {}) {
426
+ return aliases[name] ?? name;
427
+ }
428
+ function rewriteBacktickedToolNames(text, aliases = {}) {
429
+ return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${resolveToolName(toolName, aliases)}\``), text);
430
+ }
431
+ function renderPreferredToolDescription(name, aliases = {}) {
432
+ return rewriteBacktickedToolNames(PREFERENCE_LEADS[name], aliases);
433
+ }
434
+ function renderFallbackToolDescription(builtIn, aliases = {}) {
435
+ const template = FALLBACK_TEMPLATES[builtIn];
436
+ if (!template)
437
+ return;
438
+ return rewriteBacktickedToolNames(template, aliases);
439
+ }
440
+ function renderToolRoutingPolicy(options = {}) {
441
+ const aliases = options.aliases ?? {};
442
+ const digest = resolveToolName("digest", aliases);
443
+ const edit = resolveToolName("edit", aliases);
444
+ const write = resolveToolName("write", aliases);
445
+ const grep = resolveToolName("grep", aliases);
446
+ const search = resolveToolName("search", aliases);
447
+ const def = resolveToolName("def", aliases);
448
+ const refs = resolveToolName("refs", aliases);
449
+ const rename = resolveToolName("rename", aliases);
450
+ const view = resolveToolName("view", aliases);
451
+ if (options.style === "server") {
452
+ return [
453
+ "Structural code navigation, document viewing, and hash-verified edits with readseek.",
454
+ "For structural questions, prefer these tools to text Grep/Bash: definitions, references, AST search, multi-facet digest (content/map/diagnostics/identity), and document views.",
455
+ `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
456
+ "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
457
+ "call MCP tools for active navigation, document questions, or anchored edits."
458
+ ].join(" ");
459
+ }
460
+ const title = options.title ?? "ReadSeek tool policy:";
461
+ const bullets = [];
462
+ if (options.preferOverBuiltIns) {
463
+ bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
464
+ }
465
+ bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, and ${digest}/${def}/${refs} for symbol navigation.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${view} for document structure; run ${digest} with select diagnostics after source edits for a quick syntax check.`);
466
+ if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
467
+ bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
468
+ }
469
+ return [title, ...bullets].join(`
470
+ `);
471
+ }
472
+ function preferredToolDescriptionMap(aliases = {}) {
473
+ return Object.fromEntries(TOOL_NAMES.map((name) => [resolveToolName(name, aliases), renderPreferredToolDescription(name, aliases)]));
474
+ }
475
+ function fallbackToolDescriptionMap(builtIns = Object.keys(BUILTIN_FALLBACKS), aliases = {}) {
476
+ return Object.fromEntries(builtIns.flatMap((builtIn) => {
477
+ const description = renderFallbackToolDescription(builtIn, aliases);
478
+ return description ? [[builtIn, description]] : [];
479
+ }));
480
+ }
419
481
  // ../readseek-api/src/validation.ts
420
482
  function validateGitSelection(input) {
421
483
  if (input.ignored && !input.others) {
@@ -764,50 +826,103 @@ async function runReadSeekBunText(binaryPath, args, options = {}) {
764
826
  async function runReadSeekBunJson(binaryPath, args, options = {}) {
765
827
  return parseSuccessfulJson(await runReadSeekBun(binaryPath, args, options));
766
828
  }
767
- // index.ts
768
- var resolveFromHost = createRequire2(import.meta.url).resolve;
829
+ // src/constants.ts
769
830
  var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
770
831
  var DEFAULT_READ_LIMIT = 2000;
771
832
  var EDIT_RESULT_READ_LIMIT = 40;
772
833
  var MAX_DOCUMENT_OUTPUT_BYTES = 256 * 1024;
773
834
  var MAX_DOCUMENT_OUTPUT_LINES = 2000;
774
- var TOOL_POLICY = [
775
- "ReadSeek tool policy:",
776
- "- Prefer readseek_* tools over built-ins when they can do the job.",
777
- "- Read with readseek_read first; readseek_edit needs fresh LINE:HASH anchors.",
778
- "- Use readseek_grep for text/regex, readseek_search for AST patterns, and readseek_digest/def/refs for symbol navigation.",
779
- "- Use readseek_edit for existing files, readseek_write for whole-file creation or replacement, and readseek_rename for symbol renames.",
780
- "- Do not use built-in read, edit, write, grep, or apply_patch when a ReadSeek tool can do the job.",
781
- "- Run readseek_digest with select=diagnostics after source edits for a quick syntax check."
782
- ].join(`
783
- `);
784
- var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
785
- function openCodeToolDescription(name) {
786
- return renderToolDescription(name, OPENCODE_TOOL_ALIASES);
787
- }
788
- var PREFERRED_TOOL_DESCRIPTIONS = {
789
- readseek_read: "Preferred file reader; returns LINE:HASH anchors for durable references.",
790
- readseek_edit: "Preferred tool for editing existing text files with fresh LINE:HASH anchors.",
791
- readseek_write: "Preferred tool for creating or replacing a complete text file; returns LINE:HASH anchors.",
792
- readseek_rename: "Use to rename a symbol with binding-aware, verified edits.",
793
- readseek_grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
794
- readseek_search: "Use for AST or structural code search with ast-grep-style patterns.",
795
- readseek_digest: "Use to digest native facets (metadata, content, map, diagnostics, identity) and return the full envelope.",
796
- readseek_def: "Use for go-to-definition or declaration lookup by name.",
797
- readseek_refs: "Use to find identifier-name matches or binding-specific references before rename or delete.",
798
- readseek_view: "Use to digest document structure or selected content."
799
- };
800
- var FALLBACK_TOOL_DESCRIPTIONS = {
801
- read: "Fallback only. Prefer readseek_read so edits can use LINE:HASH anchors.",
802
- edit: "Fallback only. Prefer readseek_edit with fresh LINE:HASH anchors.",
803
- write: "Fallback only. Prefer readseek_write so the result returns LINE:HASH anchors.",
804
- grep: "Fallback only. Prefer readseek_grep for text/regex search with edit-ready anchors.",
805
- apply_patch: "Fallback only. Prefer readseek_edit for existing text files."
806
- };
835
+ var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
807
836
  var IMAGE_MODES = [...VISION_MODES];
808
837
  var VISION_ANALYSIS_MODE_VALUES = [...VISION_ANALYSIS_MODES];
809
838
  var VISION_LEVEL_VALUES = [...VISION_LEVELS];
810
839
  var DOCUMENT_NODE_KIND_VALUES = [...DOCUMENT_NODE_KINDS];
840
+
841
+ // src/util.ts
842
+ import path2 from "node:path";
843
+ function render(value) {
844
+ return JSON.stringify(value, null, 2);
845
+ }
846
+ function record(value) {
847
+ return value && typeof value === "object" ? value : {};
848
+ }
849
+ function items(value) {
850
+ return Array.isArray(value) ? value : [];
851
+ }
852
+ function identifiedName(value) {
853
+ if (!value || typeof value !== "object")
854
+ return;
855
+ const identifier = value.identifier;
856
+ if (!identifier || typeof identifier !== "object")
857
+ return;
858
+ const text = identifier.text;
859
+ return typeof text === "string" ? text : undefined;
860
+ }
861
+ function collectAnchoredFiles(value, files) {
862
+ if (Array.isArray(value)) {
863
+ for (const item of value)
864
+ collectAnchoredFiles(item, files);
865
+ return;
866
+ }
867
+ if (!value || typeof value !== "object")
868
+ return;
869
+ const output = value;
870
+ const hashlines = output.hashlines;
871
+ if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record(line).line === "number" && typeof record(line).hash === "string"))
872
+ files.add(output.file);
873
+ for (const item of Object.values(output))
874
+ collectAnchoredFiles(item, files);
875
+ }
876
+ function renameFiles(value) {
877
+ const output = record(value);
878
+ const files = [];
879
+ if (typeof output.file === "string" && items(output.edits).length > 0)
880
+ files.push(path2.resolve(output.file));
881
+ for (const other of items(output.others)) {
882
+ const item = record(other);
883
+ if (typeof item.file === "string" && items(item.edits).length > 0)
884
+ files.push(path2.resolve(item.file));
885
+ }
886
+ return files;
887
+ }
888
+ function normalizeToLf(content) {
889
+ return content.replace(/\r\n/g, `
890
+ `).replace(/\r/g, `
891
+ `);
892
+ }
893
+ function diffLines(content) {
894
+ if (content === "")
895
+ return [];
896
+ const lines = content.split(`
897
+ `);
898
+ if (lines.at(-1) === "")
899
+ lines.pop();
900
+ return lines.map((line) => line.replace(/\r/g, "\\r"));
901
+ }
902
+ function hasFinalNewline(content) {
903
+ return content === "" || /[\r\n]$/.test(content);
904
+ }
905
+ function diffRange(start, count) {
906
+ return count === 0 ? `${start},0` : `${start + 1},${count}`;
907
+ }
908
+ function utf8Prefix(value, maxBytes) {
909
+ const bytes = Buffer.from(value, "utf8");
910
+ if (bytes.length <= maxBytes)
911
+ return value;
912
+ let end = maxBytes;
913
+ const decoder = new TextDecoder("utf-8", { fatal: true });
914
+ while (end > 0) {
915
+ try {
916
+ return decoder.decode(bytes.subarray(0, end));
917
+ } catch {
918
+ end--;
919
+ }
920
+ }
921
+ return "";
922
+ }
923
+
924
+ // src/client.ts
925
+ var resolveFromHost = createRequire2(import.meta.url).resolve;
811
926
  function resolveImagePolicy(options) {
812
927
  const value = options?.imageMode;
813
928
  if (value === undefined)
@@ -816,46 +931,6 @@ function resolveImagePolicy(options) {
816
931
  return value;
817
932
  throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
818
933
  }
819
- function isVisualFile(value) {
820
- const output = record(value);
821
- const type = output.type;
822
- return typeof output.width === "number" || typeof type === "string" && type.startsWith("image/");
823
- }
824
- function isVisualBoundsError(error) {
825
- if (!(error instanceof Error))
826
- return false;
827
- const message = error.message.replace(/^error:\s*/, "");
828
- return message.startsWith("--end") && /(?:does|do) not apply to images/.test(message);
829
- }
830
- function isDocumentBoundsError(error) {
831
- if (!(error instanceof Error))
832
- return false;
833
- const message = error.message.replace(/^error:\s*/, "");
834
- return /(?:does|do) not apply to (?:documents|PDFs)/.test(message) || /document content belongs to view/.test(message) || /source facets do not apply to documents/.test(message);
835
- }
836
- function isPdfFile(value) {
837
- const output = record(value);
838
- return output.format === "pdf" || output.type === "application/pdf";
839
- }
840
- function documentReceipt(filePath, envelope) {
841
- const recordEnvelope = record(envelope);
842
- return {
843
- file: filePath,
844
- use: "readseek_view",
845
- reason: "document content belongs to readseek_view",
846
- metadata: digestMetadata(envelope),
847
- ...recordEnvelope.cache !== undefined ? { cache: recordEnvelope.cache } : {}
848
- };
849
- }
850
- async function digestDocumentReceipt(context, filePath) {
851
- const cacheDir = await ensureReadSeekCache(context);
852
- const envelope = await runReadSeek(context, [
853
- "--readseek-dir",
854
- cacheDir,
855
- ...digestArgs(filePath, "metadata")
856
- ]);
857
- return documentReceipt(filePath, envelope);
858
- }
859
934
  function digestArgs(filePath, select, extra = []) {
860
935
  return [...buildDigestArgs({ path: filePath, select }), ...extra];
861
936
  }
@@ -866,232 +941,30 @@ function requireDigestFacet(envelope, facet) {
866
941
  }
867
942
  return value;
868
943
  }
869
- function digestMetadata(envelope) {
870
- return record(record(envelope).metadata);
944
+ function withAt(args, line, column) {
945
+ args.push("--at", buildLineLocation(line, column));
871
946
  }
872
-
873
- class SessionAnchors {
874
- #pathsBySession = new Map;
875
- #renamePlans = new Map;
876
- mark(sessionID, filePath) {
877
- let paths = this.#pathsBySession.get(sessionID);
878
- if (!paths) {
879
- paths = new Set;
880
- this.#pathsBySession.set(sessionID, paths);
881
- }
882
- paths.add(filePath);
883
- }
884
- forget(filePath) {
885
- const absolutePath = path2.resolve(filePath);
886
- for (const paths of this.#pathsBySession.values())
887
- paths.delete(absolutePath);
888
- for (const [sessionID, plan] of this.#renamePlans) {
889
- if (plan.files.has(absolutePath))
890
- this.#renamePlans.delete(sessionID);
891
- }
892
- }
893
- deleteSession(sessionID) {
894
- this.#pathsBySession.delete(sessionID);
895
- this.#renamePlans.delete(sessionID);
896
- }
897
- planRename(sessionID, output) {
898
- if (!output || typeof output !== "object")
899
- return;
900
- const record = output;
901
- if (record.applied === true) {
902
- this.#renamePlans.delete(sessionID);
903
- return;
904
- }
905
- const { file, old_name: oldName, new_name: newName, others } = record;
906
- if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
907
- return;
908
- const files = new Set([path2.resolve(file)]);
909
- if (Array.isArray(others)) {
910
- for (const item of others) {
911
- if (!item || typeof item !== "object")
912
- continue;
913
- const otherFile = item.file;
914
- if (typeof otherFile === "string")
915
- files.add(path2.resolve(otherFile));
916
- }
917
- }
918
- this.#renamePlans.set(sessionID, { summary: `${oldName} -> ${newName}`, files });
919
- }
920
- render(sessionID) {
921
- const paths = this.#pathsBySession.get(sessionID);
922
- const sections = [];
923
- if (paths?.size) {
924
- sections.push(`## ReadSeek Anchors
925
- The following files have fresh LINE:HASH anchors:
926
- ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
927
- `)}`);
928
- }
929
- const renamePlan = this.#renamePlans.get(sessionID);
930
- if (renamePlan)
931
- sections.push(`## Pending ReadSeek Rename Plan
932
- - ${renamePlan.summary}`);
933
- return sections.length === 0 ? undefined : sections.join(`
934
-
935
- `);
936
- }
947
+ function readRangeArgs(filePath, start, end) {
948
+ const extra = [];
949
+ if (start > 1)
950
+ withAt(extra, start);
951
+ extra.push("--end", String(end));
952
+ return digestArgs(filePath, "content", extra);
937
953
  }
938
- function resolvePath(directory, filePath) {
939
- return path2.resolve(directory, filePath);
954
+ function readSeekBinaryPath() {
955
+ return resolveReadSeekBinaryFromPackages(resolveFromHost);
940
956
  }
941
- function containsPath(directory, filePath) {
942
- const relative = path2.relative(directory, filePath);
943
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
944
- }
945
- async function resolveSymlinks(filePath) {
946
- let unresolved = path2.resolve(filePath);
947
- let symlinks = 0;
948
- while (true) {
949
- const parsed = path2.parse(unresolved);
950
- const parts = unresolved.slice(parsed.root.length).split(path2.sep).filter(Boolean);
951
- let resolved = parsed.root;
952
- let restart = false;
953
- for (let index = 0;index < parts.length; index++) {
954
- const candidate = path2.join(resolved, parts[index]);
955
- const info = await lstat(candidate);
956
- if (!info.isSymbolicLink()) {
957
- resolved = candidate;
958
- continue;
959
- }
960
- symlinks++;
961
- if (symlinks > 40) {
962
- const error = new Error(`too many symbolic links: ${filePath}`);
963
- error.code = "ELOOP";
964
- throw error;
965
- }
966
- const target = await readlink(candidate);
967
- unresolved = path2.resolve(path2.dirname(candidate), target, ...parts.slice(index + 1));
968
- restart = true;
969
- break;
970
- }
971
- if (!restart)
972
- return resolved;
973
- }
974
- }
975
- async function portableRealpath(filePath) {
976
- try {
977
- return await realpath(filePath);
978
- } catch (error) {
979
- if (error.code !== "EACCES")
980
- throw error;
981
- return resolveSymlinks(filePath);
982
- }
983
- }
984
- async function canonicalPath(filePath) {
985
- const missing = [];
986
- let existing = filePath;
987
- while (true) {
988
- try {
989
- return path2.join(await portableRealpath(existing), ...missing.reverse());
990
- } catch (error) {
991
- if (error.code !== "ENOENT")
992
- throw error;
993
- const parent = path2.dirname(existing);
994
- if (parent === existing)
995
- throw error;
996
- missing.push(path2.basename(existing));
997
- existing = parent;
998
- }
999
- }
1000
- }
1001
- async function authorizeExternal(context, filePath) {
1002
- const [accessPath, directory, worktree] = await Promise.all([
1003
- canonicalPath(filePath),
1004
- canonicalPath(context.directory),
1005
- context.worktree === "/" ? Promise.resolve("/") : canonicalPath(context.worktree)
1006
- ]);
1007
- if (containsPath(directory, accessPath) || worktree !== "/" && containsPath(worktree, accessPath)) {
1008
- return;
1009
- }
1010
- const info = await stat(accessPath).catch(() => {
1011
- return;
1012
- });
1013
- const parentDir = info?.isDirectory() ? accessPath : path2.dirname(accessPath);
1014
- const pattern = path2.join(parentDir, "*").replaceAll("\\", "/");
1015
- await context.ask({
1016
- permission: "external_directory",
1017
- patterns: [pattern],
1018
- always: [pattern],
1019
- metadata: { filepath: filePath, parentDir }
1020
- });
1021
- }
1022
- async function rejectSymlinkMutation(filePath) {
1023
- const info = await lstat(filePath).catch((error) => {
1024
- if (error.code === "ENOENT")
1025
- return;
1026
- throw error;
1027
- });
1028
- if (info?.isSymbolicLink())
1029
- throw new Error(`refusing to modify symbolic link: ${filePath}`);
1030
- }
1031
- async function authorizeRead(context, filePath) {
1032
- await authorizeExternal(context, filePath);
1033
- await context.ask({
1034
- permission: "read",
1035
- patterns: [path2.relative(context.worktree, filePath).replaceAll("\\", "/")],
1036
- always: ["*"],
1037
- metadata: {}
1038
- });
1039
- }
1040
- async function authorizeSearch(context, filePath, pattern) {
1041
- await context.ask({
1042
- permission: "grep",
1043
- patterns: [pattern],
1044
- always: ["*"],
1045
- metadata: { pattern, path: filePath }
1046
- });
1047
- await authorizeExternal(context, filePath);
1048
- }
1049
- async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPaths = true) {
1050
- if (authorizeExternalPaths) {
1051
- for (const filePath of filePaths)
1052
- await authorizeExternal(context, filePath);
1053
- }
1054
- const patterns = filePaths.map((filePath) => path2.relative(context.worktree, filePath).replaceAll("\\", "/"));
1055
- await context.ask({
1056
- permission: "edit",
1057
- patterns,
1058
- always: ["*"],
1059
- metadata: { filepath: filePaths.join(", "), diff }
1060
- });
1061
- }
1062
- function withAt(args, line, column) {
1063
- args.push("--at", buildLineLocation(line, column));
1064
- }
1065
- function isLikelyImagePath(filePath) {
1066
- return /\.(avif|bmp|gif|heic|heif|ico|jpe?g|png|svg|tiff?|webp)$/i.test(filePath);
1067
- }
1068
- function isLikelyDocumentPath(filePath) {
1069
- return /\.pdf$/i.test(filePath);
1070
- }
1071
- function readRangeArgs(filePath, start, end) {
1072
- const extra = [];
1073
- if (start > 1)
1074
- withAt(extra, start);
1075
- extra.push("--end", String(end));
1076
- return digestArgs(filePath, "content", extra);
1077
- }
1078
- async function runDigestContent(context, args, options = {}) {
1079
- return requireDigestFacet(await runReadSeek(context, args, options), "content");
1080
- }
1081
- function readSeekBinaryPath() {
1082
- return resolveReadSeekBinaryFromPackages(resolveFromHost);
1083
- }
1084
- async function preserveAbortReason(signal, cancelable, invoke) {
1085
- try {
1086
- const value = await invoke();
1087
- if (cancelable)
1088
- signal.throwIfAborted();
1089
- return value;
1090
- } catch (error) {
1091
- if (cancelable)
1092
- signal.throwIfAborted();
1093
- throw error;
1094
- }
957
+ async function preserveAbortReason(signal, cancelable, invoke) {
958
+ try {
959
+ const value = await invoke();
960
+ if (cancelable)
961
+ signal.throwIfAborted();
962
+ return value;
963
+ } catch (error) {
964
+ if (cancelable)
965
+ signal.throwIfAborted();
966
+ throw error;
967
+ }
1095
968
  }
1096
969
  async function runReadSeekRaw(context, args, options = {}) {
1097
970
  context.abort.throwIfAborted();
@@ -1113,6 +986,9 @@ async function runReadSeek(context, args, options = {}) {
1113
986
  timeoutMs: options.timeoutMs
1114
987
  }));
1115
988
  }
989
+ async function runDigestContent(context, args, options = {}) {
990
+ return requireDigestFacet(await runReadSeek(context, args, options), "content");
991
+ }
1116
992
  async function postMutationPreview(context, args, filePath, changed) {
1117
993
  try {
1118
994
  return await runDigestContent(context, args, {
@@ -1132,8 +1008,8 @@ async function postMutationPreview(context, args, filePath, changed) {
1132
1008
  var readSeekCacheInit;
1133
1009
  function readSeekCacheDir() {
1134
1010
  const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
1135
- const cacheHome = xdgCacheHome || path2.join(homedir(), ".cache");
1136
- return path2.join(cacheHome, "opencode", "readseek");
1011
+ const cacheHome = xdgCacheHome || path3.join(homedir(), ".cache");
1012
+ return path3.join(cacheHome, "opencode", "readseek");
1137
1013
  }
1138
1014
  async function ensureReadSeekCache(context) {
1139
1015
  const cacheDir = readSeekCacheDir();
@@ -1151,28 +1027,109 @@ async function ensureReadSeekCache(context) {
1151
1027
  });
1152
1028
  return readSeekCacheInit;
1153
1029
  }
1154
- function render(value) {
1155
- return JSON.stringify(value, null, 2);
1030
+ function boundDocumentOutput(value) {
1031
+ const lines = value.split(`
1032
+ `);
1033
+ let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1034
+ `) : value;
1035
+ const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1036
+ output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1037
+ return truncated ? `${output}
1038
+ [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1156
1039
  }
1157
- function collectAnchoredFiles(value, files) {
1158
- if (Array.isArray(value)) {
1159
- for (const item of value)
1160
- collectAnchoredFiles(item, files);
1161
- return;
1040
+
1041
+ // src/policy.ts
1042
+ var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
1043
+ var TOOL_POLICY = renderToolRoutingPolicy({
1044
+ aliases: OPENCODE_TOOL_ALIASES,
1045
+ preferOverBuiltIns: true,
1046
+ demoteBuiltIns: ["read", "edit", "write", "grep", "apply_patch"]
1047
+ });
1048
+ var PREFERRED_TOOL_DESCRIPTIONS = preferredToolDescriptionMap(OPENCODE_TOOL_ALIASES);
1049
+ var FALLBACK_TOOL_DESCRIPTIONS = fallbackToolDescriptionMap(["edit", "write", "grep", "apply_patch"], OPENCODE_TOOL_ALIASES);
1050
+ function openCodeToolDescription(name) {
1051
+ return renderToolDescription(name, OPENCODE_TOOL_ALIASES);
1052
+ }
1053
+
1054
+ // src/session-anchors.ts
1055
+ import path4 from "node:path";
1056
+
1057
+ class SessionAnchors {
1058
+ #pathsBySession = new Map;
1059
+ #renamePlans = new Map;
1060
+ mark(sessionID, filePath) {
1061
+ let paths = this.#pathsBySession.get(sessionID);
1062
+ if (!paths) {
1063
+ paths = new Set;
1064
+ this.#pathsBySession.set(sessionID, paths);
1065
+ }
1066
+ paths.add(filePath);
1067
+ }
1068
+ forget(filePath) {
1069
+ const absolutePath = path4.resolve(filePath);
1070
+ for (const paths of this.#pathsBySession.values())
1071
+ paths.delete(absolutePath);
1072
+ for (const [sessionID, plan] of this.#renamePlans) {
1073
+ if (plan.files.has(absolutePath))
1074
+ this.#renamePlans.delete(sessionID);
1075
+ }
1076
+ }
1077
+ deleteSession(sessionID) {
1078
+ this.#pathsBySession.delete(sessionID);
1079
+ this.#renamePlans.delete(sessionID);
1080
+ }
1081
+ planRename(sessionID, output) {
1082
+ if (!output || typeof output !== "object")
1083
+ return;
1084
+ const record2 = output;
1085
+ if (record2.applied === true) {
1086
+ this.#renamePlans.delete(sessionID);
1087
+ return;
1088
+ }
1089
+ const { file, old_name: oldName, new_name: newName, others } = record2;
1090
+ if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
1091
+ return;
1092
+ const files = new Set([path4.resolve(file)]);
1093
+ if (Array.isArray(others)) {
1094
+ for (const item of others) {
1095
+ if (!item || typeof item !== "object")
1096
+ continue;
1097
+ const otherFile = item.file;
1098
+ if (typeof otherFile === "string")
1099
+ files.add(path4.resolve(otherFile));
1100
+ }
1101
+ }
1102
+ this.#renamePlans.set(sessionID, { summary: `${oldName} -> ${newName}`, files });
1103
+ }
1104
+ render(sessionID) {
1105
+ const paths = this.#pathsBySession.get(sessionID);
1106
+ const sections = [];
1107
+ if (paths?.size) {
1108
+ sections.push(`## ReadSeek Anchors
1109
+ The following files have fresh LINE:HASH anchors:
1110
+ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
1111
+ `)}`);
1112
+ }
1113
+ const renamePlan = this.#renamePlans.get(sessionID);
1114
+ if (renamePlan)
1115
+ sections.push(`## Pending ReadSeek Rename Plan
1116
+ - ${renamePlan.summary}`);
1117
+ return sections.length === 0 ? undefined : sections.join(`
1118
+
1119
+ `);
1162
1120
  }
1163
- if (!value || typeof value !== "object")
1164
- return;
1165
- const output = value;
1166
- const hashlines = output.hashlines;
1167
- if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record(line).line === "number" && typeof record(line).hash === "string"))
1168
- files.add(output.file);
1169
- for (const item of Object.values(output))
1170
- collectAnchoredFiles(item, files);
1171
1121
  }
1122
+
1123
+ // src/tools.ts
1124
+ import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1125
+ import { tool as tool2 } from "@opencode-ai/plugin";
1126
+
1127
+ // src/edits.ts
1128
+ import { lstat, mkdir, open, readFile } from "node:fs/promises";
1129
+ import path5 from "node:path";
1172
1130
  var fileMutationTails = new Map;
1173
- var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1174
1131
  async function withFileMutationQueue(filePath, operation) {
1175
- const key = path2.resolve(filePath);
1132
+ const key = path5.resolve(filePath);
1176
1133
  const previous = fileMutationTails.get(key);
1177
1134
  let release = () => {};
1178
1135
  const current = new Promise((resolve) => {
@@ -1189,6 +1146,15 @@ async function withFileMutationQueue(filePath, operation) {
1189
1146
  fileMutationTails.delete(key);
1190
1147
  }
1191
1148
  }
1149
+ function parseAnchor(anchor) {
1150
+ const match = /^(\d+):([0-9a-fA-F]{3})$/.exec(anchor.trim());
1151
+ if (!match)
1152
+ throw new Error(`invalid LINE:HASH anchor: ${anchor}`);
1153
+ const line = Number(match[1]);
1154
+ if (line === 0)
1155
+ throw new Error(`anchor line must be greater than zero: ${anchor}`);
1156
+ return { line, hash: match[2].toLowerCase() };
1157
+ }
1192
1158
  function validateEdits(value) {
1193
1159
  if (!Array.isArray(value) || value.length === 0)
1194
1160
  throw new Error("edits must contain at least one edit");
@@ -1230,15 +1196,6 @@ function validateEdits(value) {
1230
1196
  }
1231
1197
  }
1232
1198
  }
1233
- function parseAnchor(anchor) {
1234
- const match = /^(\d+):([0-9a-fA-F]{3})$/.exec(anchor.trim());
1235
- if (!match)
1236
- throw new Error(`invalid LINE:HASH anchor: ${anchor}`);
1237
- const line = Number(match[1]);
1238
- if (line === 0)
1239
- throw new Error(`anchor line must be greater than zero: ${anchor}`);
1240
- return { line, hash: match[2].toLowerCase() };
1241
- }
1242
1199
  function firstChangedLine(before, after) {
1243
1200
  const beforeLines = normalizeToLf(before).split(`
1244
1201
  `);
@@ -1272,7 +1229,7 @@ async function writeHandle(handle, content) {
1272
1229
  }
1273
1230
  }
1274
1231
  async function writeText(filePath, expected, content) {
1275
- await mkdir(path2.dirname(filePath), { recursive: true });
1232
+ await mkdir(path5.dirname(filePath), { recursive: true });
1276
1233
  if (!expected.exists) {
1277
1234
  let handle2;
1278
1235
  try {
@@ -1309,100 +1266,6 @@ async function writeText(filePath, expected, content) {
1309
1266
  await handle.close();
1310
1267
  }
1311
1268
  }
1312
- function renameFiles(value) {
1313
- const output = record(value);
1314
- const files = [];
1315
- if (typeof output.file === "string" && items(output.edits).length > 0)
1316
- files.push(path2.resolve(output.file));
1317
- for (const other of items(output.others)) {
1318
- const item = record(other);
1319
- if (typeof item.file === "string" && items(item.edits).length > 0)
1320
- files.push(path2.resolve(item.file));
1321
- }
1322
- return files;
1323
- }
1324
- function normalizeToLf(content) {
1325
- return content.replace(/\r\n/g, `
1326
- `).replace(/\r/g, `
1327
- `);
1328
- }
1329
- function diffLines(content) {
1330
- if (content === "")
1331
- return [];
1332
- const lines = content.split(`
1333
- `);
1334
- if (lines.at(-1) === "")
1335
- lines.pop();
1336
- return lines.map((line) => line.replace(/\r/g, "\\r"));
1337
- }
1338
- function hasFinalNewline(content) {
1339
- return content === "" || /[\r\n]$/.test(content);
1340
- }
1341
- function diffRange(start, count) {
1342
- return count === 0 ? `${start},0` : `${start + 1},${count}`;
1343
- }
1344
- function utf8Prefix(value, maxBytes) {
1345
- const bytes = Buffer.from(value, "utf8");
1346
- if (bytes.length <= maxBytes)
1347
- return value;
1348
- let end = maxBytes;
1349
- const decoder = new TextDecoder("utf-8", { fatal: true });
1350
- while (end > 0) {
1351
- try {
1352
- return decoder.decode(bytes.subarray(0, end));
1353
- } catch {
1354
- end--;
1355
- }
1356
- }
1357
- return "";
1358
- }
1359
- function boundDocumentOutput(value) {
1360
- const lines = value.split(`
1361
- `);
1362
- let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1363
- `) : value;
1364
- const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1365
- output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1366
- return truncated ? `${output}
1367
- [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1368
- }
1369
- function attachmentExtension(mime) {
1370
- switch (mime) {
1371
- case "image/gif":
1372
- return "gif";
1373
- case "image/jpeg":
1374
- return "jpg";
1375
- case "image/png":
1376
- return "png";
1377
- case "image/webp":
1378
- return "webp";
1379
- default:
1380
- return "bin";
1381
- }
1382
- }
1383
- function imageAttachment(image, filename) {
1384
- if (image.encoding !== "base64" || typeof image.data !== "string" || typeof image.mime !== "string") {
1385
- return;
1386
- }
1387
- return {
1388
- type: "file",
1389
- mime: image.mime,
1390
- url: `data:${image.mime};base64,${image.data}`,
1391
- filename: `${filename}.${attachmentExtension(image.mime)}`
1392
- };
1393
- }
1394
- function formatReadResult(value) {
1395
- const output = record(value);
1396
- if (output.use === "readseek_view")
1397
- return { output: render(value) };
1398
- const attachment = imageAttachment(output, "image");
1399
- if (!attachment)
1400
- return { output: render(value) };
1401
- const sanitized = { ...output };
1402
- delete sanitized.data;
1403
- delete sanitized.encoding;
1404
- return { output: render(sanitized), attachments: [attachment] };
1405
- }
1406
1269
  function previewDiffLine(prefix, line) {
1407
1270
  const value = `${prefix}${line}`;
1408
1271
  const maxBytes = 256;
@@ -1495,21 +1358,137 @@ function simpleDiff(filePath, before, after) {
1495
1358
  ].join(`
1496
1359
  `);
1497
1360
  }
1498
- function identifiedName(value) {
1499
- if (!value || typeof value !== "object")
1361
+
1362
+ // src/paths.ts
1363
+ import { lstat as lstat2, readlink, realpath, stat as stat2 } from "node:fs/promises";
1364
+ import path6 from "node:path";
1365
+ function resolvePath(directory, filePath) {
1366
+ return path6.resolve(directory, filePath);
1367
+ }
1368
+ function containsPath(directory, filePath) {
1369
+ const relative = path6.relative(directory, filePath);
1370
+ return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
1371
+ }
1372
+ async function resolveSymlinks(filePath) {
1373
+ let unresolved = path6.resolve(filePath);
1374
+ let symlinks = 0;
1375
+ while (true) {
1376
+ const parsed = path6.parse(unresolved);
1377
+ const parts = unresolved.slice(parsed.root.length).split(path6.sep).filter(Boolean);
1378
+ let resolved = parsed.root;
1379
+ let restart = false;
1380
+ for (let index = 0;index < parts.length; index++) {
1381
+ const candidate = path6.join(resolved, parts[index]);
1382
+ const info = await lstat2(candidate);
1383
+ if (!info.isSymbolicLink()) {
1384
+ resolved = candidate;
1385
+ continue;
1386
+ }
1387
+ symlinks++;
1388
+ if (symlinks > 40) {
1389
+ const error = new Error(`too many symbolic links: ${filePath}`);
1390
+ error.code = "ELOOP";
1391
+ throw error;
1392
+ }
1393
+ const target = await readlink(candidate);
1394
+ unresolved = path6.resolve(path6.dirname(candidate), target, ...parts.slice(index + 1));
1395
+ restart = true;
1396
+ break;
1397
+ }
1398
+ if (!restart)
1399
+ return resolved;
1400
+ }
1401
+ }
1402
+ async function portableRealpath(filePath) {
1403
+ try {
1404
+ return await realpath(filePath);
1405
+ } catch (error) {
1406
+ if (error.code !== "EACCES")
1407
+ throw error;
1408
+ return resolveSymlinks(filePath);
1409
+ }
1410
+ }
1411
+ async function canonicalPath(filePath) {
1412
+ const missing = [];
1413
+ let existing = filePath;
1414
+ while (true) {
1415
+ try {
1416
+ return path6.join(await portableRealpath(existing), ...missing.reverse());
1417
+ } catch (error) {
1418
+ if (error.code !== "ENOENT")
1419
+ throw error;
1420
+ const parent = path6.dirname(existing);
1421
+ if (parent === existing)
1422
+ throw error;
1423
+ missing.push(path6.basename(existing));
1424
+ existing = parent;
1425
+ }
1426
+ }
1427
+ }
1428
+ async function authorizeExternal(context, filePath) {
1429
+ const [accessPath, directory, worktree] = await Promise.all([
1430
+ canonicalPath(filePath),
1431
+ canonicalPath(context.directory),
1432
+ context.worktree === "/" ? Promise.resolve("/") : canonicalPath(context.worktree)
1433
+ ]);
1434
+ if (containsPath(directory, accessPath) || worktree !== "/" && containsPath(worktree, accessPath)) {
1500
1435
  return;
1501
- const identifier = value.identifier;
1502
- if (!identifier || typeof identifier !== "object")
1436
+ }
1437
+ const info = await stat2(accessPath).catch(() => {
1503
1438
  return;
1504
- const text = identifier.text;
1505
- return typeof text === "string" ? text : undefined;
1439
+ });
1440
+ const parentDir = info?.isDirectory() ? accessPath : path6.dirname(accessPath);
1441
+ const pattern = path6.join(parentDir, "*").replaceAll("\\", "/");
1442
+ await context.ask({
1443
+ permission: "external_directory",
1444
+ patterns: [pattern],
1445
+ always: [pattern],
1446
+ metadata: { filepath: filePath, parentDir }
1447
+ });
1506
1448
  }
1507
- function record(value) {
1508
- return value && typeof value === "object" ? value : {};
1449
+ async function rejectSymlinkMutation(filePath) {
1450
+ const info = await lstat2(filePath).catch((error) => {
1451
+ if (error.code === "ENOENT")
1452
+ return;
1453
+ throw error;
1454
+ });
1455
+ if (info?.isSymbolicLink())
1456
+ throw new Error(`refusing to modify symbolic link: ${filePath}`);
1509
1457
  }
1510
- function items(value) {
1511
- return Array.isArray(value) ? value : [];
1458
+ async function authorizeRead(context, filePath) {
1459
+ await authorizeExternal(context, filePath);
1460
+ await context.ask({
1461
+ permission: "read",
1462
+ patterns: [path6.relative(context.worktree, filePath).replaceAll("\\", "/")],
1463
+ always: ["*"],
1464
+ metadata: {}
1465
+ });
1512
1466
  }
1467
+ async function authorizeSearch(context, filePath, pattern) {
1468
+ await context.ask({
1469
+ permission: "grep",
1470
+ patterns: [pattern],
1471
+ always: ["*"],
1472
+ metadata: { pattern, path: filePath }
1473
+ });
1474
+ await authorizeExternal(context, filePath);
1475
+ }
1476
+ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPaths = true) {
1477
+ if (authorizeExternalPaths) {
1478
+ for (const filePath of filePaths)
1479
+ await authorizeExternal(context, filePath);
1480
+ }
1481
+ const patterns = filePaths.map((filePath) => path6.relative(context.worktree, filePath).replaceAll("\\", "/"));
1482
+ await context.ask({
1483
+ permission: "edit",
1484
+ patterns,
1485
+ always: ["*"],
1486
+ metadata: { filepath: filePaths.join(", "), diff }
1487
+ });
1488
+ }
1489
+
1490
+ // src/presentation.ts
1491
+ import { tool } from "@opencode-ai/plugin";
1513
1492
  function initialTitle(kind, args) {
1514
1493
  switch (kind) {
1515
1494
  case "read":
@@ -1645,478 +1624,390 @@ function readseekTool(description, args, kind, execute, formatResult = (result)
1645
1624
  }
1646
1625
  });
1647
1626
  }
1648
- var ReadSeekPlugin = async (_input, options) => {
1649
- const anchors = new SessionAnchors;
1650
- const imagePolicy = resolveImagePolicy(options);
1651
- const imageModes = imagePolicy === "auto" ? ["none", "all", "ocr", "caption", "objects"] : ["all", "ocr", "caption", "objects"];
1627
+
1628
+ // src/tools.ts
1629
+ function createReadSeekTools(imagePolicy) {
1652
1630
  return {
1653
- tool: {
1654
- readseek_read: readseekTool(imagePolicy === "off" ? `${openCodeToolDescription("read")} Image reads are disabled.` : `${openCodeToolDescription("read")} Available image modes: ${imageModes.join(", ")}.`, {
1655
- path: tool.schema.string().describe("Path relative to the project directory"),
1656
- offset: tool.schema.number().int().positive().optional().describe("One-based starting line"),
1657
- limit: tool.schema.number().int().positive().optional().describe(`Maximum number of lines; defaults to ${DEFAULT_READ_LIMIT}`),
1658
- language: tool.schema.string().optional().describe("Language override when auto-detection is ambiguous"),
1659
- ...imagePolicy === "off" ? {} : {
1660
- image: tool.schema.enum(imageModes).optional().describe("Standalone image processing mode")
1661
- }
1662
- }, "read", async (input, context) => {
1663
- const filePath = resolvePath(context.directory, input.path);
1664
- await authorizeRead(context, filePath);
1665
- const image = input.image;
1666
- if (imagePolicy === "off" && image !== undefined)
1667
- throw new Error("image reads are disabled");
1668
- if (imagePolicy === "on" && image === "none")
1669
- throw new Error('image="none" requires imageMode="auto"');
1670
- if (isLikelyDocumentPath(filePath)) {
1671
- if (image !== undefined)
1672
- throw new Error("image does not apply to documents; use readseek_view");
1673
- if (input.offset !== undefined || input.limit !== undefined) {
1674
- throw new Error("offset and limit do not apply to documents; use readseek_view");
1675
- }
1676
- if (input.language !== undefined) {
1677
- throw new Error("language does not apply to documents; use readseek_view");
1678
- }
1679
- return digestDocumentReceipt(context, filePath);
1631
+ readseek_view: readseekTool(openCodeToolDescription("view"), {
1632
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1633
+ node: tool2.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1634
+ page: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1635
+ kind: tool2.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1636
+ depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1637
+ outline: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1638
+ visionMode: tool2.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1639
+ visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1640
+ }, "view", async (input, context) => {
1641
+ const filePath = resolvePath(context.directory, input.path);
1642
+ await authorizeRead(context, filePath);
1643
+ const visionMode = input.visionMode;
1644
+ const visionLevel = input.visionLevel;
1645
+ const vision = validateVisionLevelVsMode({
1646
+ visionMode,
1647
+ visionLevel,
1648
+ outline: input.outline
1649
+ });
1650
+ if (!vision.ok)
1651
+ throw new Error(vision.error);
1652
+ const cacheDir = await ensureReadSeekCache(context);
1653
+ const args = buildViewArgs({
1654
+ path: filePath,
1655
+ node: input.node,
1656
+ page: input.page,
1657
+ kind: input.kind,
1658
+ depth: input.depth,
1659
+ outline: input.outline,
1660
+ visionMode,
1661
+ visionLevel,
1662
+ readseekDir: cacheDir
1663
+ });
1664
+ return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1665
+ }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1666
+ readseek_digest: readseekTool(openCodeToolDescription("digest"), {
1667
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1668
+ select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
1669
+ at: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
1670
+ end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
1671
+ limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
1672
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
1673
+ visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeDigest),
1674
+ visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1675
+ }, "digest", async (input, context) => {
1676
+ const filePath = resolvePath(context.directory, input.path);
1677
+ await authorizeRead(context, filePath);
1678
+ const select = (input.select ?? "content").trim() || "content";
1679
+ const visionMode = input.visionMode;
1680
+ const visionLevel = input.visionLevel;
1681
+ const vision = validateVisionLevelVsMode({ visionMode, visionLevel });
1682
+ if (!vision.ok)
1683
+ throw new Error(vision.error);
1684
+ const cacheDir = await ensureReadSeekCache(context);
1685
+ const args = buildDigestArgs({
1686
+ path: filePath,
1687
+ select,
1688
+ at: input.at,
1689
+ end: input.end,
1690
+ limit: input.limit,
1691
+ language: input.language,
1692
+ visionMode,
1693
+ visionLevel,
1694
+ readseekDir: cacheDir
1695
+ });
1696
+ return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1697
+ }),
1698
+ readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1699
+ pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
1700
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1701
+ glob: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
1702
+ literal: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
1703
+ ignoreCase: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
1704
+ context: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
1705
+ limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
1706
+ }, "grep", async (input, context) => {
1707
+ const target = resolvePath(context.directory, input.path ?? ".");
1708
+ const pattern = input.pattern;
1709
+ await authorizeSearch(context, target, pattern);
1710
+ let expression;
1711
+ try {
1712
+ const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
1713
+ expression = new RegExp(source, input.ignoreCase ? "i" : "");
1714
+ } catch (error) {
1715
+ throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
1716
+ }
1717
+ const info = await stat3(target);
1718
+ const files = [];
1719
+ if (info.isFile())
1720
+ files.push(target);
1721
+ else if (info.isDirectory()) {
1722
+ const glob = new Bun.Glob(input.glob ?? "**/*");
1723
+ for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
1724
+ files.push(filePath);
1680
1725
  }
1681
- let detectedText = false;
1682
- if (image === undefined && isLikelyImagePath(filePath)) {
1683
- const envelope2 = await runReadSeek(context, digestArgs(filePath, "metadata"));
1684
- const metadata2 = digestMetadata(envelope2);
1685
- if (isPdfFile(metadata2))
1686
- return documentReceipt(filePath, envelope2);
1687
- if (isVisualFile(metadata2)) {
1688
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1726
+ } else
1727
+ throw new Error(`grep target is not a file or directory: ${target}`);
1728
+ const maxMatches = input.limit ?? 100;
1729
+ const contextLines = input.context ?? 0;
1730
+ const results = [];
1731
+ let totalMatches = 0;
1732
+ let truncated = false;
1733
+ for (const filePath of files.sort()) {
1734
+ context.abort.throwIfAborted();
1735
+ if (truncated)
1736
+ break;
1737
+ const buffer = await readFile2(filePath);
1738
+ if (buffer.includes(0))
1739
+ continue;
1740
+ const lines = buffer.toString("utf8").replace(/\r\n/g, `
1741
+ `).split(`
1742
+ `);
1743
+ const matchLines = new Set;
1744
+ const ranges = [];
1745
+ for (let index = 0;index < lines.length; index++) {
1746
+ expression.lastIndex = 0;
1747
+ if (!expression.test(lines[index] ?? ""))
1748
+ continue;
1749
+ if (totalMatches === maxMatches) {
1750
+ truncated = true;
1751
+ break;
1689
1752
  }
1690
- detectedText = true;
1753
+ const line = index + 1;
1754
+ matchLines.add(line);
1755
+ ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
1756
+ totalMatches++;
1691
1757
  }
1692
- const extra = [];
1693
- const wantsImage = !detectedText && (image !== undefined || isLikelyImagePath(filePath));
1694
- if (!wantsImage) {
1695
- const start = input.offset ?? 1;
1696
- const end = start + (input.limit ?? DEFAULT_READ_LIMIT) - 1;
1697
- if (start > 1)
1698
- withAt(extra, start);
1699
- extra.push("--end", String(end));
1700
- if (input.language)
1701
- extra.push("--language", input.language);
1702
- } else {
1703
- if (input.offset !== undefined || input.limit !== undefined) {
1704
- throw new Error("offset and limit do not apply to image reads");
1705
- }
1706
- if (input.language !== undefined) {
1707
- throw new Error("language does not apply to image reads");
1708
- }
1709
- if (image !== undefined)
1710
- extra.push("--vision-mode", image);
1758
+ if (ranges.length === 0)
1759
+ continue;
1760
+ const mergedRanges = [];
1761
+ for (const [start, end] of ranges) {
1762
+ const previous = mergedRanges.at(-1);
1763
+ if (previous && start <= previous[1] + 1)
1764
+ previous[1] = Math.max(previous[1], end);
1765
+ else
1766
+ mergedRanges.push([start, end]);
1711
1767
  }
1712
- let envelope;
1713
- try {
1714
- envelope = await runReadSeek(context, digestArgs(filePath, "content", extra), image !== undefined && image !== "none" ? { timeoutMs: DEFAULT_VISION_TIMEOUT_MS } : {});
1715
- } catch (error) {
1716
- if (image !== undefined)
1717
- throw error;
1718
- if (isDocumentBoundsError(error)) {
1719
- return digestDocumentReceipt(context, filePath);
1720
- }
1721
- if (isVisualBoundsError(error)) {
1722
- const metadataEnvelope = await runReadSeek(context, digestArgs(filePath, "metadata"));
1723
- if (isPdfFile(digestMetadata(metadataEnvelope))) {
1724
- return documentReceipt(filePath, metadataEnvelope);
1725
- }
1726
- if (isVisualFile(digestMetadata(metadataEnvelope))) {
1727
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1728
- }
1768
+ const linesByNumber = new Map;
1769
+ for (const [start, end] of mergedRanges) {
1770
+ const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
1771
+ for (const hashline of items(output.hashlines)) {
1772
+ const line = record(hashline).line;
1773
+ if (typeof line === "number" && !linesByNumber.has(line))
1774
+ linesByNumber.set(line, hashline);
1729
1775
  }
1730
- throw error;
1731
- }
1732
- const metadata = digestMetadata(envelope);
1733
- if (isPdfFile(metadata) || record(envelope).cache !== undefined) {
1734
- return documentReceipt(filePath, envelope);
1735
- }
1736
- const visual = isVisualFile(metadata);
1737
- if (visual && image === undefined) {
1738
- return { file: filePath, skipped: true, reason: "image mode not selected" };
1739
1776
  }
1740
- if (visual && (input.offset !== undefined || input.limit !== undefined)) {
1741
- throw new Error("offset and limit do not apply to image reads");
1742
- }
1743
- if (visual && input.language !== undefined) {
1744
- throw new Error("language does not apply to image reads");
1745
- }
1746
- return requireDigestFacet(envelope, "content");
1747
- }, formatReadResult),
1748
- readseek_view: readseekTool(openCodeToolDescription("view"), {
1749
- path: tool.schema.string().describe("Document path relative to the project directory"),
1750
- node: tool.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1751
- page: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1752
- kind: tool.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1753
- depth: tool.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1754
- outline: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1755
- visionMode: tool.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1756
- visionLevel: tool.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1757
- }, "view", async (input, context) => {
1758
- const filePath = resolvePath(context.directory, input.path);
1759
- await authorizeRead(context, filePath);
1760
- const visionMode = input.visionMode;
1761
- const visionLevel = input.visionLevel;
1762
- const vision = validateVisionLevelVsMode({
1763
- visionMode,
1764
- visionLevel,
1765
- outline: input.outline
1766
- });
1767
- if (!vision.ok)
1768
- throw new Error(vision.error);
1769
- const cacheDir = await ensureReadSeekCache(context);
1770
- const args = buildViewArgs({
1771
- path: filePath,
1772
- node: input.node,
1773
- page: input.page,
1774
- kind: input.kind,
1775
- depth: input.depth,
1776
- outline: input.outline,
1777
- visionMode,
1778
- visionLevel,
1779
- readseekDir: cacheDir
1777
+ const hashlines = [...linesByNumber.values()];
1778
+ const matches = hashlines.filter((hashline) => {
1779
+ const line = record(hashline).line;
1780
+ return typeof line === "number" && matchLines.has(line);
1780
1781
  });
1781
- return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1782
- }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1783
- readseek_digest: readseekTool(openCodeToolDescription("digest"), {
1784
- path: tool.schema.string().describe("Path relative to the project directory"),
1785
- select: tool.schema.string().optional().describe("Comma-separated facets: metadata, content, map, diagnostics, identity; defaults to content"),
1786
- at: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
1787
- end: tool.schema.number().int().positive().optional().describe("Last line to include; content facet only"),
1788
- limit: tool.schema.number().int().positive().optional().describe("Maximum number of lines; content facet only"),
1789
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1790
- visionMode: tool.schema.enum(IMAGE_MODES).optional().describe("Standalone image mode for the content facet: none, all, ocr, caption, or objects"),
1791
- visionLevel: tool.schema.enum(VISION_LEVEL_VALUES).optional().describe("Vision inference level: low (default), medium, or high; content facet only")
1792
- }, "digest", async (input, context) => {
1793
- const filePath = resolvePath(context.directory, input.path);
1782
+ results.push({ file: filePath, matches, hashlines });
1783
+ }
1784
+ return { results, truncated };
1785
+ }),
1786
+ readseek_search: readseekTool(openCodeToolDescription("search"), {
1787
+ pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1788
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1789
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1790
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1791
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1792
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1793
+ }, "search", async (input, context) => {
1794
+ const target = resolvePath(context.directory, input.path ?? ".");
1795
+ const args = buildSearchArgs({
1796
+ path: target,
1797
+ pattern: input.pattern,
1798
+ language: input.language,
1799
+ cached: input.cached,
1800
+ others: input.others,
1801
+ ignored: input.ignored
1802
+ });
1803
+ await authorizeSearch(context, target, input.pattern);
1804
+ return runReadSeek(context, args);
1805
+ }),
1806
+ readseek_def: readseekTool(openCodeToolDescription("def"), {
1807
+ name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
1808
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1809
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1810
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1811
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1812
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1813
+ }, "def", async (input, context) => {
1814
+ const target = resolvePath(context.directory, input.path ?? ".");
1815
+ const args = buildDefArgs({
1816
+ path: target,
1817
+ name: input.name,
1818
+ language: input.language,
1819
+ format: "plain",
1820
+ cached: input.cached,
1821
+ others: input.others,
1822
+ ignored: input.ignored
1823
+ });
1824
+ await authorizeSearch(context, target, input.name);
1825
+ return runReadSeek(context, args);
1826
+ }),
1827
+ readseek_refs: readseekTool(openCodeToolDescription("refs"), {
1828
+ name: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
1829
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1830
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1831
+ scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1832
+ line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1833
+ column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1834
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1835
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1836
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1837
+ }, "refs", async (input, context) => {
1838
+ const target = resolvePath(context.directory, input.path ?? ".");
1839
+ const name = input.name;
1840
+ const args = buildRefsArgs({
1841
+ path: target,
1842
+ name,
1843
+ scope: input.scope,
1844
+ line: input.line,
1845
+ column: input.column,
1846
+ language: input.language,
1847
+ cached: input.cached,
1848
+ others: input.others,
1849
+ ignored: input.ignored
1850
+ });
1851
+ await authorizeSearch(context, target, name ?? "*");
1852
+ return runReadSeek(context, args);
1853
+ }),
1854
+ readseek_rename: readseekTool(openCodeToolDescription("rename"), {
1855
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1856
+ line: tool2.schema.number().int().positive().describe(PARAM_DESCRIPTIONS.renameLine),
1857
+ column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1858
+ to: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
1859
+ workspace: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
1860
+ apply: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue)
1861
+ }, "rename", async (input, context) => {
1862
+ const filePath = resolvePath(context.directory, input.path);
1863
+ await authorizeRead(context, filePath);
1864
+ if (input.workspace) {
1865
+ const identifyArgs = digestArgs(filePath, "identity");
1866
+ withAt(identifyArgs, input.line, input.column);
1867
+ const envelope = await runReadSeek(context, identifyArgs);
1868
+ const name = identifiedName(requireDigestFacet(envelope, "identity"));
1869
+ if (!name)
1870
+ throw new Error("readseek could not identify a binding at the rename cursor");
1871
+ await authorizeSearch(context, context.directory, name);
1872
+ }
1873
+ const args = buildRenameArgs({
1874
+ path: filePath,
1875
+ to: input.to,
1876
+ line: input.line,
1877
+ column: input.column,
1878
+ workspace: input.workspace ? context.directory : undefined
1879
+ });
1880
+ const plan = await runReadSeek(context, args);
1881
+ if (input.apply === false)
1882
+ return plan;
1883
+ const files = renameFiles(plan);
1884
+ const planHash = record(plan).plan_hash;
1885
+ if (typeof planHash !== "string" || planHash.length === 0) {
1886
+ throw new Error("readseek rename plan did not include a plan hash");
1887
+ }
1888
+ await authorizeEdit(context, files);
1889
+ return runReadSeek(context, buildRenameArgs({
1890
+ path: filePath,
1891
+ to: input.to,
1892
+ line: input.line,
1893
+ column: input.column,
1894
+ workspace: input.workspace ? context.directory : undefined,
1895
+ apply: true,
1896
+ planHash
1897
+ }), {
1898
+ cancelable: false,
1899
+ timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1900
+ });
1901
+ }),
1902
+ readseek_edit: readseekTool(openCodeToolDescription("edit"), {
1903
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1904
+ edits: tool2.schema.array(tool2.schema.union([
1905
+ tool2.schema.object({
1906
+ set_line: tool2.schema.object({
1907
+ anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
1908
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
1909
+ }).strict()
1910
+ }).strict(),
1911
+ tool2.schema.object({
1912
+ replace_lines: tool2.schema.object({
1913
+ start_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
1914
+ end_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
1915
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
1916
+ }).strict()
1917
+ }).strict(),
1918
+ tool2.schema.object({
1919
+ insert_after: tool2.schema.object({
1920
+ anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
1921
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
1922
+ }).strict()
1923
+ }).strict(),
1924
+ tool2.schema.object({
1925
+ replace: tool2.schema.object({
1926
+ old_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
1927
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newText),
1928
+ all: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
1929
+ }).strict()
1930
+ }).strict(),
1931
+ tool2.schema.object({
1932
+ replace_symbol: tool2.schema.object({
1933
+ symbol: tool2.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
1934
+ new_body: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
1935
+ }).strict()
1936
+ }).strict()
1937
+ ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
1938
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
1939
+ }, "edit", async (input, context) => {
1940
+ const filePath = resolvePath(context.directory, input.path);
1941
+ validateEdits(input.edits);
1942
+ return withFileMutationQueue(filePath, async () => {
1943
+ context.abort.throwIfAborted();
1794
1944
  await authorizeRead(context, filePath);
1795
- const select = (input.select ?? "content").trim() || "content";
1796
- const visionMode = input.visionMode;
1797
- const visionLevel = input.visionLevel;
1798
- const vision = validateVisionLevelVsMode({ visionMode, visionLevel });
1799
- if (!vision.ok)
1800
- throw new Error(vision.error);
1801
- const cacheDir = await ensureReadSeekCache(context);
1802
- const args = buildDigestArgs({
1945
+ await rejectSymlinkMutation(filePath);
1946
+ const edits = input.edits;
1947
+ const request = JSON.stringify({ edits });
1948
+ const baseArgs = buildEditArgs({
1803
1949
  path: filePath,
1804
- select,
1805
- at: input.at,
1806
- end: input.end,
1807
- limit: input.limit,
1808
1950
  language: input.language,
1809
- visionMode,
1810
- visionLevel,
1811
- readseekDir: cacheDir
1951
+ requestFromStdin: true
1812
1952
  });
1813
- return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1814
- }),
1815
- readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1816
- pattern: tool.schema.string().describe("Text or regular expression to search for; set literal for exact text"),
1817
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1818
- glob: tool.schema.string().optional().describe("File-name glob, such as **/*.ts"),
1819
- literal: tool.schema.boolean().optional().describe("Treat pattern as literal text"),
1820
- ignoreCase: tool.schema.boolean().optional().describe("Ignore case"),
1821
- context: tool.schema.number().int().nonnegative().optional().describe("Surrounding lines for each match"),
1822
- limit: tool.schema.number().int().positive().optional().describe("Maximum matching lines; defaults to 100")
1823
- }, "grep", async (input, context) => {
1824
- const target = resolvePath(context.directory, input.path ?? ".");
1825
- const pattern = input.pattern;
1826
- await authorizeSearch(context, target, pattern);
1827
- let expression;
1828
- try {
1829
- const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
1830
- expression = new RegExp(source, input.ignoreCase ? "i" : "");
1831
- } catch (error) {
1832
- throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
1953
+ const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
1954
+ if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
1955
+ throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
1833
1956
  }
1834
- const info = await stat(target);
1835
- const files = [];
1836
- if (info.isFile())
1837
- files.push(target);
1838
- else if (info.isDirectory()) {
1839
- const glob = new Bun.Glob(input.glob ?? "**/*");
1840
- for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
1841
- files.push(filePath);
1842
- }
1843
- } else
1844
- throw new Error(`grep target is not a file or directory: ${target}`);
1845
- const maxMatches = input.limit ?? 100;
1846
- const contextLines = input.context ?? 0;
1847
- const results = [];
1848
- let totalMatches = 0;
1849
- let truncated = false;
1850
- for (const filePath of files.sort()) {
1851
- context.abort.throwIfAborted();
1852
- if (truncated)
1853
- break;
1854
- const buffer = await readFile(filePath);
1855
- if (buffer.includes(0))
1856
- continue;
1857
- const lines = buffer.toString("utf8").replace(/\r\n/g, `
1858
- `).split(`
1859
- `);
1860
- const matchLines = new Set;
1861
- const ranges = [];
1862
- for (let index = 0;index < lines.length; index++) {
1863
- expression.lastIndex = 0;
1864
- if (!expression.test(lines[index] ?? ""))
1865
- continue;
1866
- if (totalMatches === maxMatches) {
1867
- truncated = true;
1868
- break;
1869
- }
1870
- const line = index + 1;
1871
- matchLines.add(line);
1872
- ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
1873
- totalMatches++;
1874
- }
1875
- if (ranges.length === 0)
1876
- continue;
1877
- const mergedRanges = [];
1878
- for (const [start, end] of ranges) {
1879
- const previous = mergedRanges.at(-1);
1880
- if (previous && start <= previous[1] + 1)
1881
- previous[1] = Math.max(previous[1], end);
1882
- else
1883
- mergedRanges.push([start, end]);
1884
- }
1885
- const linesByNumber = new Map;
1886
- for (const [start, end] of mergedRanges) {
1887
- const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
1888
- for (const hashline of items(output.hashlines)) {
1889
- const line = record(hashline).line;
1890
- if (typeof line === "number" && !linesByNumber.has(line))
1891
- linesByNumber.set(line, hashline);
1892
- }
1893
- }
1894
- const hashlines = [...linesByNumber.values()];
1895
- const matches = hashlines.filter((hashline) => {
1896
- const line = record(hashline).line;
1897
- return typeof line === "number" && matchLines.has(line);
1898
- });
1899
- results.push({ file: filePath, matches, hashlines });
1957
+ const before = plan.before_content;
1958
+ const after = plan.content;
1959
+ const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
1960
+ const previewArgs = readRangeArgs(filePath, previewStart, previewStart + EDIT_RESULT_READ_LIMIT - 1);
1961
+ if (before === after) {
1962
+ return postMutationPreview(context, previewArgs, filePath, false);
1900
1963
  }
1901
- return { results, truncated };
1902
- }),
1903
- readseek_search: readseekTool(openCodeToolDescription("search"), {
1904
- pattern: tool.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1905
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1906
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1907
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1908
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1909
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1910
- }, "search", async (input, context) => {
1911
- const target = resolvePath(context.directory, input.path ?? ".");
1912
- const args = buildSearchArgs({
1913
- path: target,
1914
- pattern: input.pattern,
1915
- language: input.language,
1916
- cached: input.cached,
1917
- others: input.others,
1918
- ignored: input.ignored
1919
- });
1920
- await authorizeSearch(context, target, input.pattern);
1921
- return runReadSeek(context, args);
1922
- }),
1923
- readseek_def: readseekTool(openCodeToolDescription("def"), {
1924
- name: tool.schema.string().describe(PARAM_DESCRIPTIONS.name),
1925
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1926
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1927
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1928
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1929
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1930
- }, "def", async (input, context) => {
1931
- const target = resolvePath(context.directory, input.path ?? ".");
1932
- const args = buildDefArgs({
1933
- path: target,
1934
- name: input.name,
1935
- language: input.language,
1936
- format: "plain",
1937
- cached: input.cached,
1938
- others: input.others,
1939
- ignored: input.ignored
1940
- });
1941
- await authorizeSearch(context, target, input.name);
1942
- return runReadSeek(context, args);
1943
- }),
1944
- readseek_refs: readseekTool(openCodeToolDescription("refs"), {
1945
- name: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
1946
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1947
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1948
- scope: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1949
- line: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1950
- column: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1951
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1952
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1953
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1954
- }, "refs", async (input, context) => {
1955
- const target = resolvePath(context.directory, input.path ?? ".");
1956
- const name = input.name;
1957
- const args = buildRefsArgs({
1958
- path: target,
1959
- name,
1960
- scope: input.scope,
1961
- line: input.line,
1962
- column: input.column,
1963
- language: input.language,
1964
- cached: input.cached,
1965
- others: input.others,
1966
- ignored: input.ignored
1967
- });
1968
- await authorizeSearch(context, target, name ?? "*");
1969
- return runReadSeek(context, args);
1970
- }),
1971
- readseek_rename: readseekTool(openCodeToolDescription("rename"), {
1972
- path: tool.schema.string().describe("Path relative to the project directory"),
1973
- line: tool.schema.number().int().positive().describe(PARAM_DESCRIPTIONS.renameLine),
1974
- column: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1975
- to: tool.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
1976
- workspace: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
1977
- apply: tool.schema.boolean().optional().describe("Apply the verified edits; defaults to true")
1978
- }, "rename", async (input, context) => {
1979
- const filePath = resolvePath(context.directory, input.path);
1980
- await authorizeRead(context, filePath);
1981
- if (input.workspace) {
1982
- const identifyArgs = digestArgs(filePath, "identity");
1983
- withAt(identifyArgs, input.line, input.column);
1984
- const envelope = await runReadSeek(context, identifyArgs);
1985
- const name = identifiedName(requireDigestFacet(envelope, "identity"));
1986
- if (!name)
1987
- throw new Error("readseek could not identify a binding at the rename cursor");
1988
- await authorizeSearch(context, context.directory, name);
1989
- }
1990
- const args = buildRenameArgs({
1964
+ await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
1965
+ context.abort.throwIfAborted();
1966
+ const applied = record(await runReadSeek(context, buildEditArgs({
1991
1967
  path: filePath,
1992
- to: input.to,
1993
- line: input.line,
1994
- column: input.column,
1995
- workspace: input.workspace ? context.directory : undefined
1996
- });
1997
- const plan = await runReadSeek(context, args);
1998
- if (input.apply === false)
1999
- return plan;
2000
- const files = renameFiles(plan);
2001
- const planHash = record(plan).plan_hash;
2002
- if (typeof planHash !== "string" || planHash.length === 0) {
2003
- throw new Error("readseek rename plan did not include a plan hash");
2004
- }
2005
- await authorizeEdit(context, files);
2006
- return runReadSeek(context, buildRenameArgs({
2007
- path: filePath,
2008
- to: input.to,
2009
- line: input.line,
2010
- column: input.column,
2011
- workspace: input.workspace ? context.directory : undefined,
1968
+ language: input.language,
1969
+ requestFromStdin: true,
2012
1970
  apply: true,
2013
- planHash
1971
+ planHash: plan.plan_hash
2014
1972
  }), {
2015
1973
  cancelable: false,
1974
+ stdin: request,
2016
1975
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
2017
- });
2018
- }),
2019
- readseek_edit: readseekTool(openCodeToolDescription("edit"), {
2020
- path: tool.schema.string().describe("Path relative to the project directory"),
2021
- edits: tool.schema.array(tool.schema.union([
2022
- tool.schema.object({
2023
- set_line: tool.schema.object({
2024
- anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
2025
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
2026
- }).strict()
2027
- }).strict(),
2028
- tool.schema.object({
2029
- replace_lines: tool.schema.object({
2030
- start_anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
2031
- end_anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
2032
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
2033
- }).strict()
2034
- }).strict(),
2035
- tool.schema.object({
2036
- insert_after: tool.schema.object({
2037
- anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
2038
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
2039
- }).strict()
2040
- }).strict(),
2041
- tool.schema.object({
2042
- replace: tool.schema.object({
2043
- old_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
2044
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newText),
2045
- all: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
2046
- }).strict()
2047
- }).strict(),
2048
- tool.schema.object({
2049
- replace_symbol: tool.schema.object({
2050
- symbol: tool.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
2051
- new_body: tool.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
2052
- }).strict()
2053
- }).strict()
2054
- ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
2055
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
2056
- }, "edit", async (input, context) => {
2057
- const filePath = resolvePath(context.directory, input.path);
2058
- validateEdits(input.edits);
2059
- return withFileMutationQueue(filePath, async () => {
2060
- context.abort.throwIfAborted();
2061
- await authorizeRead(context, filePath);
2062
- await rejectSymlinkMutation(filePath);
2063
- const edits = input.edits;
2064
- const request = JSON.stringify({ edits });
2065
- const baseArgs = buildEditArgs({
2066
- path: filePath,
2067
- language: input.language,
2068
- requestFromStdin: true
2069
- });
2070
- const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
2071
- if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
2072
- throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
2073
- }
2074
- const before = plan.before_content;
2075
- const after = plan.content;
2076
- const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
2077
- const previewArgs = readRangeArgs(filePath, previewStart, previewStart + EDIT_RESULT_READ_LIMIT - 1);
2078
- if (before === after) {
2079
- return postMutationPreview(context, previewArgs, filePath, false);
2080
- }
2081
- await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
2082
- context.abort.throwIfAborted();
2083
- const applied = record(await runReadSeek(context, buildEditArgs({
2084
- path: filePath,
2085
- language: input.language,
2086
- requestFromStdin: true,
2087
- apply: true,
2088
- planHash: plan.plan_hash
2089
- }), {
2090
- cancelable: false,
2091
- stdin: request,
2092
- timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
2093
- }));
2094
- if (applied.applied !== true) {
2095
- throw new Error("readseek did not confirm that the edit was applied");
2096
- }
2097
- return postMutationPreview(context, previewArgs, filePath, true);
2098
- });
2099
- }),
2100
- readseek_write: readseekTool(openCodeToolDescription("write"), {
2101
- path: tool.schema.string().describe("Path relative to the project directory"),
2102
- content: tool.schema.string().describe("Complete text file content")
2103
- }, "write", async (input, context) => {
2104
- const filePath = resolvePath(context.directory, input.path);
2105
- await authorizeExternal(context, filePath);
2106
- return withFileMutationQueue(filePath, async () => {
2107
- context.abort.throwIfAborted();
2108
- await rejectSymlinkMutation(filePath);
2109
- const before = await readTextSnapshot(filePath);
2110
- const content = input.content;
2111
- if (content.includes("\x00"))
2112
- throw new Error("write content must be text");
2113
- await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
2114
- context.abort.throwIfAborted();
2115
- await writeText(filePath, before, content);
2116
- return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
2117
- });
2118
- })
2119
- },
1976
+ }));
1977
+ if (applied.applied !== true) {
1978
+ throw new Error("readseek did not confirm that the edit was applied");
1979
+ }
1980
+ return postMutationPreview(context, previewArgs, filePath, true);
1981
+ });
1982
+ }),
1983
+ readseek_write: readseekTool(openCodeToolDescription("write"), {
1984
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1985
+ content: tool2.schema.string().describe(PARAM_DESCRIPTIONS.content)
1986
+ }, "write", async (input, context) => {
1987
+ const filePath = resolvePath(context.directory, input.path);
1988
+ await authorizeExternal(context, filePath);
1989
+ return withFileMutationQueue(filePath, async () => {
1990
+ context.abort.throwIfAborted();
1991
+ await rejectSymlinkMutation(filePath);
1992
+ const before = await readTextSnapshot(filePath);
1993
+ const content = input.content;
1994
+ if (content.includes("\x00"))
1995
+ throw new Error("write content must be text");
1996
+ await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
1997
+ context.abort.throwIfAborted();
1998
+ await writeText(filePath, before, content);
1999
+ return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
2000
+ });
2001
+ })
2002
+ };
2003
+ }
2004
+
2005
+ // index.ts
2006
+ var ReadSeekPlugin = async (_input, options) => {
2007
+ const anchors = new SessionAnchors;
2008
+ const imagePolicy = resolveImagePolicy(options);
2009
+ return {
2010
+ tool: createReadSeekTools(imagePolicy),
2120
2011
  "experimental.chat.system.transform": async (_input2, output) => {
2121
2012
  output.system.push(TOOL_POLICY);
2122
2013
  },
@@ -2153,7 +2044,7 @@ var ReadSeekPlugin = async (_input, options) => {
2153
2044
  const files = new Set;
2154
2045
  collectAnchoredFiles(result, files);
2155
2046
  for (const filePath of files)
2156
- anchors.mark(input.sessionID, path2.resolve(filePath));
2047
+ anchors.mark(input.sessionID, path7.resolve(filePath));
2157
2048
  } catch {}
2158
2049
  },
2159
2050
  "experimental.session.compacting": async (input, output) => {