pi-readseek 0.4.30 → 0.5.0

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/src/edit.ts CHANGED
@@ -29,8 +29,6 @@ import { upsertDiffComponent, upsertTextComponent } from "./tui-diff-component.j
29
29
  import type { FreshAnchorsPredicate } from "./tool-types.js";
30
30
  import { filePathParam, registerReadSeekTool } from "./register-tool.js";
31
31
 
32
- import { resolveEditDiffDisplay } from "./readseek-settings.js";
33
-
34
32
  const EDIT_PENDING_PREVIEW_STATE_KEY = "hashline-edit-pending-preview";
35
33
 
36
34
  function pendingPreviewLines(summary: string, preview: PendingDiffPreviewResult | undefined, expanded: boolean): { lines: string[]; diffData?: ReturnType<typeof buildDiffData>; headerLabel?: string } {
@@ -73,7 +71,7 @@ type HashlineParams = Static<typeof hashlineEditSchema>;
73
71
 
74
72
  const EDIT_PROMPT_METADATA = defineToolPromptMetadata({
75
73
  promptUrl: new URL("../prompts/edit.md", import.meta.url),
76
- promptSnippet: "Edit files using hash-verified anchors from read/grep/search/write",
74
+ promptSnippet: "Edit files using hash-verified anchors from readSeek_read/readSeek_grep/readSeek_search/readSeek_write",
77
75
  });
78
76
 
79
77
  function buildEditError(
@@ -85,7 +83,7 @@ function buildEditError(
85
83
  ): {
86
84
  content: [{ type: "text"; text: string }];
87
85
  isError: true;
88
- details: EditToolDetails & { readseekValue: any };
86
+ details: EditToolDetails & { readSeekValue: any };
89
87
  } {
90
88
  return {
91
89
  content: [{ type: "text", text: message }],
@@ -94,13 +92,13 @@ function buildEditError(
94
92
  diff: "",
95
93
  patch: "",
96
94
  firstChangedLine: undefined,
97
- readseekValue: {
95
+ readSeekValue: {
98
96
  tool: "edit",
99
97
  ok: false,
100
98
  path,
101
99
  error: buildReadSeekError(code, message, hint, errorDetails),
102
100
  },
103
- } as EditToolDetails & { readseekValue: any },
101
+ } as EditToolDetails & { readSeekValue: any },
104
102
  };
105
103
  }
106
104
 
@@ -137,14 +135,14 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
137
135
  if (wasReadInSession && !wasReadInSession(absolutePath)) {
138
136
  const message = [
139
137
  `You must get fresh anchors for ${absolutePath} before editing it.`,
140
- `Call read(${JSON.stringify(rawPath)}) first, or use grep, search, or write to produce fresh anchors for this file.`,
141
- "edit requires fresh LINE:HASH anchors from read, grep, search, or write so the hashes match the current file contents.",
138
+ `Call readSeek_read(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`,
139
+ "readSeek_edit requires fresh LINE:HASH anchors from readSeek_read, readSeek_grep, readSeek_search, or readSeek_write so the hashes match the current file contents.",
142
140
  ].join(" ");
143
141
  return buildEditError(
144
142
  absolutePath,
145
143
  "file-not-read",
146
144
  message,
147
- `Call read(${JSON.stringify(rawPath)}) first, or use grep, search, or write to produce fresh anchors for this file.`,
145
+ `Call readSeek_read(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`,
148
146
  );
149
147
  }
150
148
  const hasTopLevelReplaceInput =
@@ -253,7 +251,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
253
251
  // Error-precedence order: replace_symbol resolution > anchor-overlap > anchored-edit.
254
252
  //
255
253
  // store successful probe results and reuse them in the apply loop so
256
- // readseekMapContent is invoked at most once per replace_symbol edit.
254
+ // readSeekMapContent is invoked at most once per replace_symbol edit.
257
255
  const replaceSymbolRanges: { start: number; end: number }[] = [];
258
256
  const rsProbeResults: { type: "ok"; content: string; replacement: string; warnings: string[]; range: { start: number; end: number } }[] = [];
259
257
  try {
@@ -365,7 +363,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
365
363
  // Apply pass: reuse all probe results. The probe pass resolved every
366
364
  // replace_symbol against originalNormalized; apply those replacements in
367
365
  // reverse source order so original line ranges stay valid and no second
368
- // replaceSymbol/readseekMapContent call is needed.
366
+ // replaceSymbol/readSeekMapContent call is needed.
369
367
  const replaceSymbolWarnings: string[] = [];
370
368
  if (rsProbeResults.length > 0) {
371
369
  const lines = originalNormalized.split("\n");
@@ -539,10 +537,10 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
539
537
  patch: builtOutput.patch,
540
538
  diffData,
541
539
  firstChangedLine: anchorResult.firstChangedLine ?? diffResult.firstChangedLine,
542
- readseekValue: builtOutput.readseekValue,
540
+ readSeekValue: builtOutput.readSeekValue,
543
541
  } as EditToolDetails & {
544
542
  diffData: typeof diffData;
545
- readseekValue: {
543
+ readSeekValue: {
546
544
  tool: string;
547
545
  ok: boolean;
548
546
  path: string;
@@ -567,11 +565,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
567
565
 
568
566
  export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}) {
569
567
  const tool = registerReadSeekTool(pi, {
570
- policy: "mutating",
571
- pythonName: "edit",
572
- defaultExposure: "not-safe-by-default",
573
- }, {
574
- name: "edit",
568
+ name: "readSeek_edit",
575
569
  label: "Edit",
576
570
  description: EDIT_PROMPT_METADATA.description,
577
571
  promptSnippet: EDIT_PROMPT_METADATA.promptSnippet,
@@ -617,7 +611,7 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
617
611
  previewKey,
618
612
  () => buildPendingEditPreviewData(args ?? {}, context.cwd ?? process.cwd()),
619
613
  );
620
- const expanded = !!context.expanded || resolveEditDiffDisplay() === "expanded";
614
+ const expanded = !!context.expanded;
621
615
  const preview2 = pendingPreviewLines(text, preview, expanded);
622
616
  if (preview2.diffData) {
623
617
  return upsertDiffComponent(context.lastComponent, { prefixLines: preview2.lines, diffData: preview2.diffData, theme, expanded: true });
@@ -625,7 +619,7 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
625
619
  return upsertTextComponent(context.lastComponent, clampLinesToWidth(preview2.lines, context.width).join("\n"));
626
620
  },
627
621
  renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
628
- const { isPartial, isError, expanded: baseExpanded, width, context } = resolveRenderResultContext(options, rest);
622
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options, rest);
629
623
 
630
624
  if (isPartial) {
631
625
  return renderPendingResult("pending edit", width);
@@ -638,13 +632,13 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
638
632
  .join("\n") ?? "";
639
633
  const details = result.details ?? {};
640
634
  const diff: string = details.diff ?? "";
641
- const readseekValue = details.readseekValue as {
635
+ const readSeekValue = details.readSeekValue as {
642
636
  warnings?: string[];
643
637
  noopEdits?: unknown[];
644
638
  } | undefined;
645
- const warnings = readseekValue?.warnings ?? [];
646
- const noopEdits = readseekValue?.noopEdits ?? [];
647
- const semanticClassification = (readseekValue as any)?.semanticSummary?.classification as string | undefined;
639
+ const warnings = readSeekValue?.warnings ?? [];
640
+ const noopEdits = readSeekValue?.noopEdits ?? [];
641
+ const semanticClassification = (readSeekValue as any)?.semanticSummary?.classification as string | undefined;
648
642
 
649
643
  const info = formatEditResultText({
650
644
  isError: isError || !!result.isError,
@@ -655,7 +649,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
655
649
  semanticClassification: semanticClassification as any,
656
650
  });
657
651
 
658
- const expanded = baseExpanded || resolveEditDiffDisplay() === "expanded";
659
652
  const diffData = (details as any).diffData;
660
653
  const stats = diffData?.stats ?? { added: 0, removed: 0 };
661
654
  let text = "";
package/src/file-map.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { stat } from "node:fs/promises";
2
2
 
3
3
  import type { FileMap } from "./readseek/types.js";
4
- import { readseekMap } from "./readseek-client.js";
4
+ import { readSeekMap } from "./readseek-client.js";
5
5
 
6
6
  /**
7
7
  * Fetch a structural file map from readseek, which maintains its own on-disk
@@ -10,7 +10,7 @@ import { readseekMap } from "./readseek-client.js";
10
10
  export async function getOrGenerateMap(absPath: string): Promise<FileMap | null> {
11
11
  try {
12
12
  const { size } = await stat(absPath);
13
- return await readseekMap(absPath, size);
13
+ return await readSeekMap(absPath, size);
14
14
  } catch {
15
15
  return null;
16
16
  }
@@ -1,29 +1,6 @@
1
1
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
2
2
  import { resolveReadSeekJsonSettings } from "./readseek-settings.js";
3
3
 
4
- const POSITIVE_BASE10_INT = /^[1-9][0-9]*$/;
5
-
6
- /**
7
- * Strict positive base-10 integer parser used by readseek env knobs.
8
- *
9
- * Accepts: trimmed strings matching /^[1-9][0-9]*$/ that parse to a finite
10
- * positive integer.
11
- *
12
- * Rejects: undefined, empty, whitespace-only, "0", negative, signed, hex
13
- * ("0x10"), exponent ("1e3"), decimal ("3.14"), separators ("1,000" /
14
- * "1_000"), embedded whitespace ("5 5").
15
- *
16
- * Returns `undefined` on rejection; never throws.
17
- */
18
- function parsePositiveBase10Int(raw: string | undefined | null): number | undefined {
19
- if (raw === undefined || raw === null) return undefined;
20
- const trimmed = String(raw).trim();
21
- if (!POSITIVE_BASE10_INT.test(trimmed)) return undefined;
22
- const parsed = Number.parseInt(trimmed, 10);
23
- if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
24
- return parsed;
25
- }
26
-
27
4
  export interface GrepOutputBudget {
28
5
  maxLines: number;
29
6
  maxBytes: number;
@@ -31,7 +8,7 @@ export interface GrepOutputBudget {
31
8
 
32
9
  /**
33
10
  * Effective grep-output ceilings used as clamp upper bounds and as the
34
- * fallback defaults when env vars are unset/invalid.
11
+ * fallback defaults when the settings are unset.
35
12
  *
36
13
  * The bytes ceiling is the already-tightened 50 KiB used by `buildGrepOutput`
37
14
  * today, NOT the unclamped `DEFAULT_MAX_BYTES`.
@@ -39,41 +16,20 @@ export interface GrepOutputBudget {
39
16
  export const GREP_OUTPUT_DEFAULT_MAX_LINES = DEFAULT_MAX_LINES;
40
17
  export const GREP_OUTPUT_DEFAULT_MAX_BYTES = Math.min(DEFAULT_MAX_BYTES, 50 * 1024);
41
18
 
42
- function resolveEnvDimension(rawEnvValue: string | undefined, ceiling: number): number | undefined {
43
- const parsed = parsePositiveBase10Int(rawEnvValue);
44
- return parsed === undefined ? undefined : Math.min(parsed, ceiling);
45
- }
46
-
47
- function resolveDimension(rawEnvValue: string | undefined, jsonValue: number | undefined, ceiling: number): number {
48
- if (rawEnvValue !== undefined) {
49
- const envValue = resolveEnvDimension(rawEnvValue, ceiling);
50
- if (envValue !== undefined) return envValue;
51
- }
19
+ function resolveDimension(jsonValue: number | undefined, ceiling: number): number {
52
20
  if (jsonValue !== undefined) return Math.min(jsonValue, ceiling);
53
21
  return ceiling;
54
22
  }
55
23
 
56
24
  /**
57
- * Resolve the effective grep visible-output budget. Re-reads `process.env`
58
- * on every call (no memoization) so tests and long-lived agent sessions can
59
- * change the env vars dynamically.
60
- *
61
- * Invalid / zero / negative env values fall back to the current defaults.
62
- * Above-default values clamp to the current defaults. Below-default values
63
- * are used as-is.
25
+ * Resolve the effective grep visible-output budget from the readseek
26
+ * settings. Below-default values are used as-is; above-default values clamp
27
+ * to the current defaults.
64
28
  */
65
29
  export function resolveGrepOutputBudget(): GrepOutputBudget {
66
30
  const settings = resolveReadSeekJsonSettings().settings.grep;
67
31
  return {
68
- maxLines: resolveDimension(
69
- process.env.READSEEK_GREP_MAX_LINES,
70
- settings?.maxLines,
71
- GREP_OUTPUT_DEFAULT_MAX_LINES,
72
- ),
73
- maxBytes: resolveDimension(
74
- process.env.READSEEK_GREP_MAX_BYTES,
75
- settings?.maxBytes,
76
- GREP_OUTPUT_DEFAULT_MAX_BYTES,
77
- ),
32
+ maxLines: resolveDimension(settings?.maxLines, GREP_OUTPUT_DEFAULT_MAX_LINES),
33
+ maxBytes: resolveDimension(settings?.maxBytes, GREP_OUTPUT_DEFAULT_MAX_BYTES),
78
34
  };
79
35
  }
@@ -68,7 +68,7 @@ interface BuildGrepOutputInput {
68
68
 
69
69
  interface GrepOutputResult {
70
70
  text: string;
71
- readseekValue: {
71
+ readSeekValue: {
72
72
  tool: "grep";
73
73
  summary: boolean;
74
74
  totalMatches: number;
@@ -159,7 +159,7 @@ export function buildGrepOutput(input: BuildGrepOutputInput): GrepOutputResult {
159
159
  if (truncated.truncated) {
160
160
  text = `${truncated.content}\n\n[Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Refine pattern or increase limit.]`;
161
161
  }
162
- const readseekValue: GrepOutputResult["readseekValue"] = {
162
+ const readSeekValue: GrepOutputResult["readSeekValue"] = {
163
163
  tool: "grep",
164
164
  summary: input.summary,
165
165
  totalMatches: input.totalMatches,
@@ -171,11 +171,11 @@ export function buildGrepOutput(input: BuildGrepOutputInput): GrepOutputResult {
171
171
  })),
172
172
  };
173
173
  if (!input.summary && input.scopeMode === "symbol") {
174
- readseekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
174
+ readSeekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
175
175
  }
176
176
 
177
177
  return {
178
178
  text,
179
- readseekValue,
179
+ readSeekValue,
180
180
  };
181
181
  }
package/src/grep.ts CHANGED
@@ -168,7 +168,6 @@ function escapeForRegex(s: string): string {
168
168
  }
169
169
 
170
170
  interface GrepToolOptions {
171
- searchGuideline?: string;
172
171
  onFileAnchored?: FileAnchoredCallback;
173
172
  }
174
173
 
@@ -253,7 +252,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
253
252
  ),
254
253
  details: {
255
254
  ...(typeof result.details === "object" && result.details !== null ? result.details : {}),
256
- readseekValue: {
255
+ readSeekValue: {
257
256
  tool: "grep",
258
257
  summary: !!p.summary,
259
258
  totalMatches: 0,
@@ -410,7 +409,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
410
409
  ...result,
411
410
  details: {
412
411
  ...passthroughDetails,
413
- readseekValue: {
412
+ readSeekValue: {
414
413
  tool: "grep",
415
414
  summary: true,
416
415
  totalMatches: 0,
@@ -436,7 +435,7 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
436
435
  ...passthroughDetails,
437
436
  hashlinePassthrough: true,
438
437
  hashlineWarning: warning,
439
- readseekValue: {
438
+ readSeekValue: {
440
439
  tool: "grep",
441
440
  summary: !!p.summary,
442
441
  totalMatches: 0,
@@ -477,20 +476,20 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
477
476
  renderedGroups = scoped.groups;
478
477
  scopeWarnings = scoped.warnings;
479
478
  }
480
- const readseekRecords = recordsFromGroups(renderedGroups);
479
+ const readSeekRecords = recordsFromGroups(renderedGroups);
481
480
  const builtOutput = buildGrepOutput({
482
481
  summary: !!summary,
483
482
  totalMatches,
484
483
  groups: renderedGroups,
485
484
  limit: effectiveLimit,
486
- records: readseekRecords,
485
+ records: readSeekRecords,
487
486
  scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
488
487
  scopeWarnings,
489
488
  passthroughLines,
490
489
  });
491
490
 
492
- if (!summary && readseekRecords.length > 0) {
493
- const anchoredPaths = new Set(readseekRecords.map((record) => record.path));
491
+ if (!summary && readSeekRecords.length > 0) {
492
+ const anchoredPaths = new Set(readSeekRecords.map((record) => record.path));
494
493
  for (const absolutePath of anchoredPaths) {
495
494
  onFileAnchored?.(absolutePath);
496
495
  }
@@ -508,25 +507,19 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
508
507
  ),
509
508
  details: {
510
509
  ...compactDetails,
511
- readseekValue: builtOutput.readseekValue,
510
+ readSeekValue: builtOutput.readSeekValue,
512
511
  },
513
512
  };
514
513
  }
515
514
 
516
515
  export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}) {
517
516
  const tool = registerReadSeekTool(pi, {
518
- policy: "read-only",
519
- pythonName: "grep",
520
- defaultExposure: "safe-by-default",
521
- }, {
522
- name: "grep",
517
+ name: "readSeek_grep",
523
518
  label: "grep",
524
519
  description: GREP_PROMPT_METADATA.description,
525
520
  parameters: grepSchema,
526
521
  promptSnippet: GREP_PROMPT_METADATA.promptSnippet,
527
- promptGuidelines: options.searchGuideline
528
- ? [GREP_PROMPT_METADATA.promptGuidelines[0], options.searchGuideline]
529
- : GREP_PROMPT_METADATA.promptGuidelines,
522
+ promptGuidelines: GREP_PROMPT_METADATA.promptGuidelines,
530
523
  async execute(toolCallId, params, signal, onUpdate, ctx) {
531
524
  return executeGrep({
532
525
  toolCallId,
@@ -558,14 +551,14 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
558
551
  renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
559
552
  const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
560
553
 
561
- if (isPartial) return renderPendingResult("pending search", width);
554
+ if (isPartial) return renderPendingResult("pending grep", width);
562
555
 
563
556
  const content = result.content?.[0];
564
557
  const textContent = content?.type === "text" ? content.text : "";
565
558
 
566
559
  if (isError || result.isError) return renderErrorResult(textContent, { expanded, width });
567
560
 
568
- const readseekValue = (result.details as any)?.readseekValue as {
561
+ const readSeekValue = (result.details as any)?.readSeekValue as {
569
562
  tool: "grep";
570
563
  summary: boolean;
571
564
  totalMatches: number;
@@ -575,26 +568,26 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
575
568
  const hasBinaryWarning = textContent.includes("appears to be a binary file");
576
569
 
577
570
  const fileSet = new Set<string>();
578
- for (const r of readseekValue?.records ?? []) {
571
+ for (const r of readSeekValue?.records ?? []) {
579
572
  if (r.path) fileSet.add(r.path);
580
573
  }
581
574
 
582
575
  const info = formatGrepResultText({
583
- totalMatches: readseekValue?.totalMatches ?? 0,
584
- summary: readseekValue?.summary ?? false,
585
- records: readseekValue?.records ?? [],
576
+ totalMatches: readSeekValue?.totalMatches ?? 0,
577
+ summary: readSeekValue?.summary ?? false,
578
+ records: readSeekValue?.records ?? [],
586
579
  fileCount: fileSet.size,
587
580
  hasBinaryWarning,
588
581
  });
589
582
 
590
583
  if (info.noMatches && !hasBinaryWarning) return new Text(summaryLine("no matches"), 0, 0);
591
- const matchCount = readseekValue?.totalMatches ?? 0;
584
+ const matchCount = readSeekValue?.totalMatches ?? 0;
592
585
  const matchWord = matchCount === 1 ? "match" : "matches";
593
586
  let text = summaryLine(`${matchCount} ${matchWord} returned`, { hidden: !!textContent && !expanded });
594
587
  for (const badge of info.badges) text += theme.fg(badge.startsWith("⚠") ? "warning" : "dim", ` ${badge}`);
595
- if (expanded && readseekValue?.records) {
588
+ if (expanded && readSeekValue?.records) {
596
589
  const fileCounts = new Map<string, number>();
597
- for (const r of readseekValue.records) if (r.path && r.kind === "match") fileCounts.set(r.path, (fileCounts.get(r.path) ?? 0) + 1);
590
+ for (const r of readSeekValue.records) if (r.path && r.kind === "match") fileCounts.set(r.path, (fileCounts.get(r.path) ?? 0) + 1);
598
591
  for (const [filePath, count] of [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20)) {
599
592
  const display = path.relative(cwd, filePath) || filePath;
600
593
  text += "\n" + theme.fg("dim", ` ${display} (${count})`);
package/src/hover.ts CHANGED
@@ -8,7 +8,7 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
8
8
  import { buildToolErrorResult } from "./readseek-value.js";
9
9
  import { resolveToCwd } from "./path-utils.js";
10
10
  import { formatFsError } from "./fs-error.js";
11
- import { classifyReadSeekFailure, readseekIdentify } from "./readseek-client.js";
11
+ import { classifyReadSeekFailure, readSeekIdentify } from "./readseek-client.js";
12
12
  import { filePathParam, registerReadSeekTool } from "./register-tool.js";
13
13
 
14
14
  import { clampLinesToWidth, linkToolPath, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
@@ -55,7 +55,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
55
55
  }
56
56
 
57
57
  try {
58
- const output = await readseekIdentify(filePath, content, {
58
+ const output = await readSeekIdentify(filePath, content, {
59
59
  line: p.line,
60
60
  column: p.column,
61
61
  signal,
@@ -77,7 +77,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
77
77
  return {
78
78
  content: [{ type: "text", text: lines.join("\n") }],
79
79
  details: {
80
- readseekValue: {
80
+ readSeekValue: {
81
81
  tool: "hover",
82
82
  ok: true,
83
83
  path: filePath,
@@ -93,11 +93,7 @@ export async function executeHover(opts: ExecuteHoverOptions): Promise<any> {
93
93
 
94
94
  export function registerHoverTool(pi: ExtensionAPI) {
95
95
  registerReadSeekTool(pi, {
96
- policy: "read-only",
97
- pythonName: "hover",
98
- defaultExposure: "opt-in",
99
- }, {
100
- name: "hover",
96
+ name: "readSeek_hover",
101
97
  label: "Hover",
102
98
  description: HOVER_PROMPT_METADATA.description,
103
99
  promptSnippet: HOVER_PROMPT_METADATA.promptSnippet,
@@ -63,7 +63,7 @@ interface ReadOutputInput {
63
63
  interface ReadOutputResult {
64
64
  text: string;
65
65
  lines: ReadSeekLine[];
66
- readseekValue: {
66
+ readSeekValue: {
67
67
  tool: "read";
68
68
  path: string;
69
69
  range: {
@@ -141,7 +141,7 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
141
141
  text = `${warnings.map((warning) => warning.message).join("\n\n")}\n\n${text}`;
142
142
  }
143
143
 
144
- const readseekValue: ReadOutputResult["readseekValue"] = {
144
+ const readSeekValue: ReadOutputResult["readSeekValue"] = {
145
145
  tool: "read",
146
146
  path: input.path,
147
147
  range: {
@@ -160,7 +160,7 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
160
160
  };
161
161
 
162
162
  if (input.bundle) {
163
- readseekValue.bundle = {
163
+ readSeekValue.bundle = {
164
164
  mode: input.bundle.mode,
165
165
  applied: input.bundle.applied,
166
166
  localSupport: input.bundle.localSupport.map((item) => {
@@ -181,6 +181,6 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
181
181
  return {
182
182
  text,
183
183
  lines,
184
- readseekValue,
184
+ readSeekValue,
185
185
  };
186
186
  }
package/src/read.ts CHANGED
@@ -26,7 +26,7 @@ import { buildReadOutput } from "./read-output.js";
26
26
 
27
27
  import { buildLocalBundle } from "./read-local-bundle.js";
28
28
  import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
29
- import { readseekRead, readseekDetect, type ReadSeekDetection } from "./readseek-client.js";
29
+ import { readSeekRead, readSeekDetect, readSeekImage, type ReadSeekDetection } from "./readseek-client.js";
30
30
  import { formatReadCallText, formatReadResultText } from "./read-render-helpers.js";
31
31
  import { resolveReadSeekOcrMode } from "./readseek-settings.js";
32
32
  import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine, wrapReadHashlinesForWidthCached } from "./tui-render-utils.js";
@@ -65,9 +65,9 @@ export interface ExecuteReadOptions {
65
65
  function hasReadAnchors(result: AgentToolResult<any>): boolean {
66
66
  const details = (result as { details?: unknown }).details;
67
67
  if (!details || typeof details !== "object") return false;
68
- const readseekValue = (details as { readseekValue?: unknown }).readseekValue;
69
- if (!readseekValue || typeof readseekValue !== "object") return false;
70
- const lines = (readseekValue as { lines?: unknown }).lines;
68
+ const readSeekValue = (details as { readSeekValue?: unknown }).readSeekValue;
69
+ if (!readSeekValue || typeof readSeekValue !== "object") return false;
70
+ const lines = (readSeekValue as { lines?: unknown }).lines;
71
71
  return Array.isArray(lines) && lines.length > 0;
72
72
  }
73
73
 
@@ -171,24 +171,19 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
171
171
  if (hasBinaryContent) {
172
172
  let detection: ReadSeekDetection | undefined;
173
173
  try {
174
- detection = await readseekDetect(absolutePath, { signal });
174
+ detection = await readSeekDetect(absolutePath, { signal });
175
175
  } catch {
176
176
  }
177
177
  if (detection?.kind === "image") {
178
178
  const builtinRead = createReadTool(cwd);
179
179
  const builtinResult = await builtinRead.execute(toolCallId, p, signal, onUpdate);
180
180
  const ocrMode = resolveReadSeekOcrMode();
181
- const shouldRunVision = ocrMode === "on" || (ocrMode === "auto" && !opts.modelSupportsImages);
181
+ const shouldRunVision = ocrMode === "force" || (ocrMode === "auto" && !opts.modelSupportsImages);
182
182
  if (!shouldRunVision) return succeed(builtinResult);
183
183
 
184
184
  let ocrFailed = false;
185
185
  try {
186
- const ocrDetection = await readseekDetect(absolutePath, {
187
- ocr: true,
188
- caption: true,
189
- objects: true,
190
- signal,
191
- });
186
+ const ocrDetection = await readSeekImage(absolutePath, ["ocr", "caption", "objects"], { signal });
192
187
  const imageAnalysis = formatImageAnalysis(ocrDetection);
193
188
  if (imageAnalysis) {
194
189
  return succeed({
@@ -356,33 +351,33 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
356
351
  }
357
352
 
358
353
  throwIfAborted(signal);
359
- let readseekOutput: Awaited<ReturnType<typeof readseekRead>>;
354
+ let readSeekOutput: Awaited<ReturnType<typeof readSeekRead>>;
360
355
  try {
361
- readseekOutput = total === 0
362
- ? await readseekRead(absolutePath, undefined, undefined, { signal })
363
- : await readseekRead(absolutePath, startLine, endIdx, { signal });
356
+ readSeekOutput = total === 0
357
+ ? await readSeekRead(absolutePath, undefined, undefined, { signal })
358
+ : await readSeekRead(absolutePath, startLine, endIdx, { signal });
364
359
  } catch (err: any) {
365
360
  const detail = err?.message ? ` — ${err.message}` : "";
366
361
  const message = `readseek failed while reading ${rawPath}${detail}`;
367
362
  return buildToolErrorResult("read", "readseek-error", message, { path: rawParams.path, hint: "Ensure @jarkkojs/readseek and its npm platform package are installed.", details: { message: err?.message } });
368
363
  }
369
364
  const expectedLineCount = Math.max(0, endIdx - startLine + 1);
370
- const invalidLine = readseekOutput.hashlines.find((line, index) => line.line !== startLine + index);
371
- if (readseekOutput.hashlines.length !== expectedLineCount || invalidLine) {
365
+ const invalidLine = readSeekOutput.hashlines.find((line, index) => line.line !== startLine + index);
366
+ if (readSeekOutput.hashlines.length !== expectedLineCount || invalidLine) {
372
367
  const message = invalidLine
373
368
  ? `readseek returned non-sequential line ${invalidLine.line} for requested range ${startLine}-${endIdx}`
374
- : `readseek returned ${readseekOutput.hashlines.length} lines for requested range ${startLine}-${endIdx} (${expectedLineCount} expected)`;
369
+ : `readseek returned ${readSeekOutput.hashlines.length} lines for requested range ${startLine}-${endIdx} (${expectedLineCount} expected)`;
375
370
  return buildToolErrorResult("read", "readseek-output-mismatch", message, { path: rawParams.path });
376
371
  }
377
- const readseekLines: ReadSeekLine[] = readseekOutput.hashlines.map((line) => ({
372
+ const readSeekLines: ReadSeekLine[] = readSeekOutput.hashlines.map((line) => ({
378
373
  line: line.line,
379
374
  hash: line.hash,
380
375
  anchor: `${line.line}:${line.hash}`,
381
376
  raw: line.text,
382
377
  display: escapeControlCharsForDisplay(line.text),
383
378
  }));
384
- const selected = readseekLines.map((line) => line.raw);
385
- const formatted = renderReadSeekLines(readseekLines);
379
+ const selected = readSeekLines.map((line) => line.raw);
380
+ const formatted = renderReadSeekLines(readSeekLines);
386
381
 
387
382
  const truncation = truncateHead(formatted, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
388
383
 
@@ -423,7 +418,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
423
418
  endLine: endIdx,
424
419
  totalLines: total,
425
420
  selectedLines: selected,
426
- lines: readseekLines,
421
+ lines: readSeekLines,
427
422
  warnings: structuredWarnings,
428
423
  truncation: truncation.truncated
429
424
  ? {
@@ -456,7 +451,7 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
456
451
  content: [{ type: "text", text: readOutput.text }],
457
452
  details: {
458
453
  truncation: truncation.truncated ? truncation : undefined,
459
- readseekValue: readOutput.readseekValue,
454
+ readSeekValue: readOutput.readSeekValue,
460
455
  },
461
456
  });
462
457
  }
@@ -469,11 +464,7 @@ function splitReadSeekLines(text: string): string[] {
469
464
 
470
465
  export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}) {
471
466
  const tool = registerReadSeekTool(pi, {
472
- policy: "read-only",
473
- pythonName: "read",
474
- defaultExposure: "safe-by-default",
475
- }, {
476
- name: "read",
467
+ name: "readSeek_read",
477
468
  label: "Read",
478
469
  description: READ_PROMPT_METADATA.description,
479
470
  promptSnippet: READ_PROMPT_METADATA.promptSnippet,
@@ -529,16 +520,16 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
529
520
  return new Text(clampLinesToWidth([summaryLine(errorText)], width).join("\n"), 0, 0);
530
521
  }
531
522
 
532
- const readseekValue = (result.details as any)?.readseekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings: ReadSeekWarning[] } | undefined;
533
- if (!readseekValue) {
523
+ const readSeekValue = (result.details as any)?.readSeekValue as { range: { startLine: number; endLine: number; totalLines: number }; truncation: any; symbol: any; map: any; warnings: ReadSeekWarning[] } | undefined;
524
+ if (!readSeekValue) {
534
525
  const lines = textContent.split("\n").filter(Boolean).length || textContent.split("\n").length;
535
526
  return new Text(summaryLine(`loaded ${lines} ${lines === 1 ? "line" : "lines"}`, { hidden: !!textContent && !expanded }), 0, 0);
536
527
  }
537
528
 
538
- const info = formatReadResultText({ range: readseekValue.range, truncation: readseekValue.truncation, symbol: readseekValue.symbol, map: readseekValue.map, warnings: readseekValue.warnings });
539
- const visibleLines = info.truncated && readseekValue.truncation ? readseekValue.truncation.outputLines : readseekValue.range.endLine - readseekValue.range.startLine + 1;
529
+ const info = formatReadResultText({ range: readSeekValue.range, truncation: readSeekValue.truncation, symbol: readSeekValue.symbol, map: readSeekValue.map, warnings: readSeekValue.warnings });
530
+ const visibleLines = info.truncated && readSeekValue.truncation ? readSeekValue.truncation.outputLines : readSeekValue.range.endLine - readSeekValue.range.startLine + 1;
540
531
  const loadedWord = visibleLines === 1 ? "line" : "lines";
541
- const summaryParts: string[] = [info.truncated ? `loaded ${visibleLines} of ${readseekValue.truncation?.totalLines ?? readseekValue.range.totalLines} ${loadedWord} (truncated)` : `loaded ${visibleLines} ${loadedWord}`];
532
+ const summaryParts: string[] = [info.truncated ? `loaded ${visibleLines} of ${readSeekValue.truncation?.totalLines ?? readSeekValue.range.totalLines} ${loadedWord} (truncated)` : `loaded ${visibleLines} ${loadedWord}`];
542
533
  if (info.symbolBadge) summaryParts.push(info.symbolBadge);
543
534
  for (const badge of info.badges) summaryParts.push(badge);
544
535
  const summary = summaryParts.join(" • ");