@oh-my-pi/pi-coding-agent 17.3.2 → 17.3.4

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 (43) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{CHANGELOG-fr2awajz.md → CHANGELOG-trcc215s.md} +21 -0
  3. package/dist/cli.js +3558 -3530
  4. package/dist/docs-index.generated.txt +1 -1
  5. package/dist/types/markit/converters/pdf/index.d.ts +2 -1
  6. package/dist/types/session/turn-recovery.d.ts +1 -1
  7. package/dist/types/tools/read-pdf.d.ts +15 -0
  8. package/dist/types/utils/external-editor.d.ts +7 -0
  9. package/dist/types/utils/markit.d.ts +3 -4
  10. package/package.json +13 -16
  11. package/scripts/build-binary.ts +0 -2
  12. package/scripts/bundle-dist.ts +0 -1
  13. package/src/cli/read-cli.ts +2 -0
  14. package/src/markit/NOTICE +8 -8
  15. package/src/markit/converters/pdf/index.ts +14 -126
  16. package/src/mcp/client.ts +8 -9
  17. package/src/modes/controllers/event-controller.ts +57 -11
  18. package/src/modes/rpc/rpc-client.ts +7 -0
  19. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  20. package/src/prompts/system/empty-stop-retry.md +1 -1
  21. package/src/session/agent-session.ts +8 -1
  22. package/src/session/turn-recovery.ts +59 -55
  23. package/src/tools/read-pdf.ts +135 -0
  24. package/src/tools/read.ts +63 -37
  25. package/src/utils/external-editor.ts +24 -5
  26. package/src/utils/markit.ts +6 -44
  27. package/dist/types/markit/converters/pdf/columns.d.ts +0 -35
  28. package/dist/types/markit/converters/pdf/extract.d.ts +0 -10
  29. package/dist/types/markit/converters/pdf/grid.d.ts +0 -25
  30. package/dist/types/markit/converters/pdf/headers.d.ts +0 -24
  31. package/dist/types/markit/converters/pdf/render.d.ts +0 -24
  32. package/dist/types/markit/converters/pdf/types.d.ts +0 -75
  33. package/dist/types/tools/read-pdf-images.d.ts +0 -12
  34. package/dist/types/utils/mupdf-wasm-embed.d.ts +0 -1
  35. package/scripts/embed-mupdf-wasm.ts +0 -67
  36. package/src/markit/converters/pdf/columns.ts +0 -103
  37. package/src/markit/converters/pdf/extract.ts +0 -598
  38. package/src/markit/converters/pdf/grid.ts +0 -780
  39. package/src/markit/converters/pdf/headers.ts +0 -106
  40. package/src/markit/converters/pdf/render.ts +0 -501
  41. package/src/markit/converters/pdf/types.ts +0 -84
  42. package/src/tools/read-pdf-images.ts +0 -250
  43. package/src/utils/mupdf-wasm-embed.ts +0 -12
@@ -1,25 +0,0 @@
1
- /**
2
- * Table grid detection from vector segments and text boxes.
3
- *
4
- * Ported from @oharato/pdf2md-ts with TypeScript types and without
5
- * CJK-specific borderless table heuristics. The core algorithm:
6
- *
7
- * 1. Classify segments as horizontal or vertical lines
8
- * 2. Group horizontal Y-lines into table groups (split by vertical gaps)
9
- * 3. For each group:
10
- * a. Full grid (H+V lines): build cells from grid intersections,
11
- * place text via raycasting
12
- * b. H-line only (no V lines): infer columns from text X positions
13
- * 4. Prune empty rows/cols
14
- *
15
- * Coordinate system: PDF native (bottom-left origin, Y increases upward).
16
- */
17
- import type { Segment, TableGrid, TextBox } from "./types.js";
18
- export interface GridResult {
19
- grids: TableGrid[];
20
- consumedIds: string[];
21
- }
22
- /**
23
- * Detect all table grids on a single page from its text boxes and segments.
24
- */
25
- export declare function resolveTableGrids(pageNumber: number, textBoxes: TextBox[], segments: Segment[]): GridResult;
@@ -1,24 +0,0 @@
1
- /**
2
- * Running header/footer detection and removal.
3
- *
4
- * Many PDFs have repeated text at the top or bottom of every page:
5
- * document titles, chapter names, page numbers, copyright notices.
6
- * These pollute the markdown output as false headings or noise.
7
- *
8
- * Algorithm:
9
- * 1. For each page, bucket text boxes by Y position (top/bottom zones)
10
- * 2. Collect the text content at each zone across all pages
11
- * 3. Text appearing on >20% of pages OR 8+ consecutive pages is a
12
- * running header/footer
13
- * 4. Remove matching text boxes before further processing
14
- */
15
- import type { PageContent } from "./types.js";
16
- /**
17
- * Detect and remove running headers and footers from all pages.
18
- * Mutates the pages array in place, removing header/footer text boxes.
19
- *
20
- * Uses two strategies:
21
- * 1. Global frequency: text appearing on > 20% of all pages
22
- * 2. Consecutive runs: text appearing on 8+ consecutive pages
23
- */
24
- export declare function stripHeadersFooters(pages: PageContent[]): void;
@@ -1,24 +0,0 @@
1
- /**
2
- * Markdown rendering for PDF pages.
3
- *
4
- * Converts table grids and free text boxes into markdown, handling:
5
- * - Table grid → markdown table (`| col | col |`)
6
- * - Free text → paragraphs with heading detection (by font size)
7
- * - Content ordering (top-to-bottom via Y coordinate)
8
- * - Paragraph wrap merging (lines broken across PDF line boundaries)
9
- * - Page number removal
10
- *
11
- * Ported from @oharato/pdf2md-ts, stripped of CJK/TDnet-specific logic.
12
- */
13
- import type { TableGrid, TextBox } from "./types.js";
14
- /**
15
- * Render a TableGrid as a markdown table.
16
- */
17
- export declare function renderTableToMarkdown(table: TableGrid): string;
18
- /**
19
- * Render one page's content: free text and tables interleaved top-to-bottom.
20
- */
21
- export declare function renderPageContent(freeTextBoxes: TextBox[], tables: TableGrid[], imageBlocks?: Array<{
22
- topY: number;
23
- markdown: string;
24
- }>, allTextBoxes?: TextBox[]): string;
@@ -1,75 +0,0 @@
1
- /** Bounding box in PDF coordinate space (origin = bottom-left). */
2
- export type Bounds = {
3
- left: number;
4
- right: number;
5
- /** Higher value = higher on the page. */
6
- top: number;
7
- bottom: number;
8
- };
9
- /** A text fragment with position and font metadata. */
10
- export type TextBox = {
11
- id: string;
12
- text: string;
13
- bounds: Bounds;
14
- pageNumber: number;
15
- /** Dominant font size in points. */
16
- fontSize: number;
17
- /** True if rendered bold (font name or rendering mode). */
18
- isBold: boolean;
19
- };
20
- /** A horizontal or vertical line segment extracted from vector graphics. */
21
- export type Segment = {
22
- id: string;
23
- x1: number;
24
- y1: number;
25
- x2: number;
26
- y2: number;
27
- };
28
- /** A single cell in a resolved table grid. */
29
- export type TableCell = {
30
- row: number;
31
- col: number;
32
- text: string;
33
- rowSpan: number;
34
- colSpan: number;
35
- };
36
- /** A resolved table grid ready for markdown rendering. */
37
- export type TableGrid = {
38
- pageNumber: number;
39
- rows: number;
40
- cols: number;
41
- cells: TableCell[];
42
- warnings: string[];
43
- /** Top Y coordinate (PDF space: larger = higher on page). */
44
- topY: number;
45
- /** True for tables detected without vector borders. */
46
- isBorderless: boolean;
47
- };
48
- /** An image/diagram region detected on a page. */
49
- export type ImageRegion = {
50
- id: string;
51
- pageNumber: number;
52
- /** Bounding box in mupdf coordinates (top-left origin). */
53
- bbox: {
54
- x: number;
55
- y: number;
56
- w: number;
57
- h: number;
58
- };
59
- /** Y position in PDF coordinates (bottom-left) for ordering. */
60
- topY: number;
61
- };
62
- /** Result of extracting content from a single PDF page. */
63
- export type PageContent = {
64
- pageNumber: number;
65
- textBoxes: TextBox[];
66
- segments: Segment[];
67
- images: ImageRegion[];
68
- };
69
- /** A block of rendered content (text paragraph or table). */
70
- export type ContentBlock = {
71
- topY: number;
72
- content: string;
73
- /** True if this line has wide gaps between text boxes (column headers). */
74
- isTabular?: boolean;
75
- };
@@ -1,12 +0,0 @@
1
- import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
- import type { ToolSession } from "../sdk.js";
3
- import type { ReadToolDetails } from "./read.js";
4
- export declare function rewritePdfImagePlaceholders(markdown: string, pdfPath: string): string;
5
- export declare function splitPdfImageMemberReadPath(readPath: string): {
6
- pdfPath: string;
7
- member: string;
8
- } | null;
9
- export declare function readPdfImageMember(session: ToolSession, autoResizeImages: boolean, absolutePdfPath: string, pdfDisplayPath: string, member: string, suffixResolution: {
10
- from: string;
11
- to: string;
12
- } | undefined, signal?: AbortSignal): Promise<AgentToolResult<ReadToolDetails>>;
@@ -1 +0,0 @@
1
- export declare function loadEmbeddedMupdfWasm(): Uint8Array | undefined;
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- // Embeds mupdf's `mupdf-wasm.wasm` into the compiled single-file binary.
4
- //
5
- // mupdf loads its wasm by reading the `mupdf-wasm.wasm` sibling of its own
6
- // module via `new URL(..., import.meta.url)` + `readFileSync`. A `bun --compile`
7
- // binary has no node_modules, so that read fails (`ENOENT .../mupdf-wasm.wasm`),
8
- // and marking mupdf `--external` instead makes `bun --compile` eagerly fail to
9
- // resolve the package at startup (the static `import * as mupdf` lives in a lazy
10
- // chunk but is hoisted). So the binary build bundles mupdf and embeds the wasm
11
- // bytes here, handing them to the WASM module as `$libmupdf_wasm_Module.wasmBinary`
12
- // (see src/utils/markit.ts).
13
- //
14
- // `--generate` copies the wasm next to src/utils/mupdf-wasm-embed.ts and rewrites
15
- // that module to import it via `with { type: "file" }`; `--reset` restores the
16
- // checked-in placeholder and removes the copy. The npm `dist/cli.js` bundle never
17
- // runs this — it keeps mupdf external and loads the wasm from node_modules.
18
-
19
- import * as fs from "node:fs/promises";
20
- import { createRequire } from "node:module";
21
- import * as path from "node:path";
22
-
23
- const utilsDir = path.join(import.meta.dir, "..", "src", "utils");
24
- const helperPath = path.join(utilsDir, "mupdf-wasm-embed.ts");
25
- const wasmCopyPath = path.join(utilsDir, "mupdf-wasm.wasm");
26
-
27
- const placeholder = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit by hand.
28
- //
29
- // Compiled single-file binaries cannot let mupdf resolve its \`mupdf-wasm.wasm\`
30
- // sibling from the read-only bunfs, so the binary build (scripts/build-binary.ts
31
- // and scripts/ci-release-build-binaries.ts) regenerates this module to embed the
32
- // wasm bytes via \`with { type: "file" }\` and copies the wasm next to it. Source
33
- // checkouts, \`bun test\`, and the npm \`dist/cli.js\` bundle keep mupdf external and
34
- // load the wasm from node_modules, so this placeholder returns undefined and the
35
- // build resets back to it afterward.
36
- export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
37
- \treturn undefined;
38
- }
39
- `;
40
-
41
- const generated = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit or commit.
42
- import { readFileSync } from "node:fs";
43
- import wasmPath from "./mupdf-wasm.wasm" with { type: "file" };
44
-
45
- export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
46
- \treturn readFileSync(wasmPath);
47
- }
48
- `;
49
-
50
- if (process.argv.includes("--reset")) {
51
- await Bun.write(helperPath, placeholder);
52
- try {
53
- await fs.unlink(wasmCopyPath);
54
- } catch (err) {
55
- if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
56
- }
57
- process.exit(0);
58
- }
59
-
60
- const wasmSource = path.join(path.dirname(createRequire(import.meta.url).resolve("mupdf")), "mupdf-wasm.wasm");
61
- const wasmFile = Bun.file(wasmSource);
62
- if (!(await wasmFile.exists())) {
63
- throw new Error(`mupdf wasm not found at ${wasmSource}; run \`bun install\` first.`);
64
- }
65
- await Bun.write(wasmCopyPath, wasmFile);
66
- await Bun.write(helperPath, generated);
67
- console.log(`Embedded mupdf wasm (${wasmFile.size} bytes) into ${path.relative(process.cwd(), wasmCopyPath)}`);
@@ -1,103 +0,0 @@
1
- // Adapted from markit-ai (MIT). See ../../NOTICE.
2
-
3
- /**
4
- * Multi-column layout detection and text box reordering.
5
- *
6
- * Many PDFs (legal documents, datasheets, academic papers) use two-column
7
- * layouts. Without column detection, text boxes are ordered by Y position
8
- * only, interleaving left and right column content.
9
- *
10
- * Algorithm:
11
- * 1. Collect left edges of all text boxes on the page
12
- * 2. Find the largest horizontal gap between consecutive left edges
13
- * 3. If gap > MIN_GAP_RATIO of the text width and both sides have
14
- * enough boxes → multi-column detected
15
- * 4. Assign each text box to a column based on its center X
16
- * 5. Return columns in reading order (left-to-right, top-to-bottom)
17
- *
18
- * This only detects the column structure. The caller is responsible for
19
- * processing each column's text boxes independently (table detection,
20
- * rendering, etc.).
21
- */
22
- import type { TextBox } from "./types";
23
-
24
- export interface ColumnLayout {
25
- /** Number of columns detected (1 = single column, 2+ = multi-column). */
26
- columnCount: number;
27
- /** Text boxes grouped by column, in reading order (left to right). */
28
- columns: TextBox[][];
29
- /** X positions of column boundaries (between columns). */
30
- boundaries: number[];
31
- }
32
-
33
- /**
34
- * Minimum gap as a fraction of the total text width to consider a column
35
- * boundary. A two-column layout typically has ~50% gap; we use a lower
36
- * threshold to catch asymmetric columns.
37
- */
38
- const MIN_GAP_RATIO = 0.15;
39
- /** Minimum number of text boxes on each side of the gap. */
40
- const MIN_BOXES_PER_COLUMN = 4;
41
- /** Minimum gap in absolute points to avoid splitting on small whitespace. */
42
- const MIN_GAP_PTS = 40;
43
-
44
- /**
45
- * Detect column layout and return text boxes grouped by column.
46
- *
47
- * For single-column pages, returns all boxes in one group.
48
- * For multi-column pages, returns boxes split by column in reading order.
49
- */
50
- export function detectColumns(textBoxes: TextBox[]): ColumnLayout {
51
- if (textBoxes.length < MIN_BOXES_PER_COLUMN * 2) {
52
- return { columnCount: 1, columns: [textBoxes], boundaries: [] };
53
- }
54
- // Collect unique left edges (rounded to avoid float noise)
55
- const lefts = [...new Set(textBoxes.map(tb => Math.round(tb.bounds.left)))].sort((a, b) => a - b);
56
- if (lefts.length < 2) {
57
- return { columnCount: 1, columns: [textBoxes], boundaries: [] };
58
- }
59
- const textXMin = lefts[0];
60
- const textXMax = Math.max(...textBoxes.map(tb => Math.round(tb.bounds.right)));
61
- const textWidth = textXMax - textXMin;
62
- if (textWidth <= 0) {
63
- return { columnCount: 1, columns: [textBoxes], boundaries: [] };
64
- }
65
- // Find the largest gap between consecutive left-edge positions
66
- let maxGap = 0;
67
- let gapLeft = 0;
68
- let gapRight = 0;
69
- for (let i = 1; i < lefts.length; i++) {
70
- const gap = lefts[i] - lefts[i - 1];
71
- if (gap > maxGap) {
72
- maxGap = gap;
73
- gapLeft = lefts[i - 1];
74
- gapRight = lefts[i];
75
- }
76
- }
77
- const gapRatio = maxGap / textWidth;
78
- if (gapRatio < MIN_GAP_RATIO || maxGap < MIN_GAP_PTS) {
79
- return { columnCount: 1, columns: [textBoxes], boundaries: [] };
80
- }
81
- // Split point is the midpoint of the gap
82
- const splitX = (gapLeft + gapRight) / 2;
83
- // Assign boxes to columns based on center X
84
- const leftCol: TextBox[] = [];
85
- const rightCol: TextBox[] = [];
86
- for (const tb of textBoxes) {
87
- const cx = (tb.bounds.left + tb.bounds.right) / 2;
88
- if (cx < splitX) {
89
- leftCol.push(tb);
90
- } else {
91
- rightCol.push(tb);
92
- }
93
- }
94
- // Validate both columns have enough content
95
- if (leftCol.length < MIN_BOXES_PER_COLUMN || rightCol.length < MIN_BOXES_PER_COLUMN) {
96
- return { columnCount: 1, columns: [textBoxes], boundaries: [] };
97
- }
98
- return {
99
- columnCount: 2,
100
- columns: [leftCol, rightCol],
101
- boundaries: [splitX],
102
- };
103
- }