@oh-my-pi/pi-coding-agent 15.5.2 → 15.5.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 (77) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/types/config/settings-schema.d.ts +27 -0
  3. package/dist/types/config.d.ts +31 -5
  4. package/dist/types/edit/file-snapshot-store.d.ts +18 -0
  5. package/dist/types/edit/hashline/diff.d.ts +30 -0
  6. package/dist/types/edit/hashline/execute.d.ts +29 -0
  7. package/dist/types/edit/hashline/filesystem.d.ts +57 -0
  8. package/dist/types/edit/hashline/index.d.ts +4 -0
  9. package/dist/types/edit/hashline/params.d.ts +12 -0
  10. package/dist/types/edit/index.d.ts +4 -3
  11. package/dist/types/edit/normalize.d.ts +4 -16
  12. package/dist/types/index.d.ts +0 -1
  13. package/dist/types/tools/bash.d.ts +1 -0
  14. package/dist/types/tools/index.d.ts +6 -5
  15. package/dist/types/tools/path-utils.d.ts +18 -0
  16. package/dist/types/utils/changelog.d.ts +8 -3
  17. package/package.json +8 -15
  18. package/src/config/settings-schema.ts +32 -0
  19. package/src/config.ts +42 -15
  20. package/src/edit/file-snapshot-store.ts +22 -0
  21. package/src/edit/hashline/diff.ts +88 -0
  22. package/src/edit/hashline/execute.ts +188 -0
  23. package/src/edit/hashline/filesystem.ts +129 -0
  24. package/src/edit/hashline/index.ts +4 -0
  25. package/src/edit/hashline/params.ts +11 -0
  26. package/src/edit/index.ts +7 -15
  27. package/src/edit/normalize.ts +11 -41
  28. package/src/edit/renderer.ts +1 -1
  29. package/src/edit/streaming.ts +8 -9
  30. package/src/index.ts +0 -1
  31. package/src/internal-urls/docs-index.generated.ts +1 -1
  32. package/src/sdk.ts +8 -1
  33. package/src/tools/ast-edit.ts +1 -1
  34. package/src/tools/ast-grep.ts +3 -3
  35. package/src/tools/bash.ts +74 -10
  36. package/src/tools/index.ts +6 -5
  37. package/src/tools/path-utils.ts +81 -0
  38. package/src/tools/read.ts +14 -72
  39. package/src/tools/search.ts +136 -17
  40. package/src/tools/write.ts +3 -3
  41. package/src/utils/changelog.ts +11 -3
  42. package/src/utils/file-mentions.ts +1 -1
  43. package/dist/types/edit/file-read-cache.d.ts +0 -36
  44. package/dist/types/hashline/anchors.d.ts +0 -26
  45. package/dist/types/hashline/apply.d.ts +0 -14
  46. package/dist/types/hashline/constants.d.ts +0 -40
  47. package/dist/types/hashline/diff-preview.d.ts +0 -2
  48. package/dist/types/hashline/diff.d.ts +0 -16
  49. package/dist/types/hashline/execute.d.ts +0 -4
  50. package/dist/types/hashline/executor.d.ts +0 -56
  51. package/dist/types/hashline/hash.d.ts +0 -76
  52. package/dist/types/hashline/index.d.ts +0 -14
  53. package/dist/types/hashline/input.d.ts +0 -4
  54. package/dist/types/hashline/prefixes.d.ts +0 -7
  55. package/dist/types/hashline/recovery.d.ts +0 -21
  56. package/dist/types/hashline/stream.d.ts +0 -2
  57. package/dist/types/hashline/tokenizer.d.ts +0 -94
  58. package/dist/types/hashline/types.d.ts +0 -75
  59. package/src/edit/file-read-cache.ts +0 -138
  60. package/src/hashline/anchors.ts +0 -104
  61. package/src/hashline/apply.ts +0 -790
  62. package/src/hashline/bigrams.json +0 -649
  63. package/src/hashline/constants.ts +0 -51
  64. package/src/hashline/diff-preview.ts +0 -42
  65. package/src/hashline/diff.ts +0 -82
  66. package/src/hashline/execute.ts +0 -334
  67. package/src/hashline/executor.ts +0 -334
  68. package/src/hashline/grammar.lark +0 -23
  69. package/src/hashline/hash.ts +0 -131
  70. package/src/hashline/index.ts +0 -14
  71. package/src/hashline/input.ts +0 -137
  72. package/src/hashline/prefixes.ts +0 -111
  73. package/src/hashline/recovery.ts +0 -139
  74. package/src/hashline/stream.ts +0 -123
  75. package/src/hashline/tokenizer.ts +0 -473
  76. package/src/hashline/types.ts +0 -66
  77. package/src/prompts/tools/hashline.md +0 -63
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.5.4] - 2026-05-27
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed the package root `hashline` export so imports from the top-level entrypoint can no longer access `hashline` helpers directly
10
+
11
+ ### Added
12
+
13
+ - Added `read.summarize.minTotalLines` setting (default 100) to set the minimum file length that triggers read summarization
14
+ - Added `<file>:<lines>` support to `search` `paths`, allowing file-scoped constraints such as `:N-M`, `:N+K`, and comma-separated ranges
15
+
16
+ ### Changed
17
+
18
+ - Changed multi-section hashline `edit` execution to defer LSP diagnostics flushing until the final section is written
19
+ - Changed read to return verbatim contents for files shorter than `read.summarize.minTotalLines` instead of summarizing them
20
+ - Changed `search` path line-range filtering to include only matches and context lines that fall inside the requested ranges
21
+
22
+ ### Fixed
23
+
24
+ - Fixed multi-section hashline edits to reject duplicate canonical targets and preflight write guards before any section is committed
25
+
26
+ ### Fixed
27
+
28
+ - Fixed `createAgentSession()` dropping the hidden `resolve` tool from the registry when no active tool sets `deferrable: true`, even though plan mode dispatches the plan-approval `resolve { action: "apply", ... }` call through a standing handler. Read-only plan-mode toolsets (e.g. `read`, `search`, `find`, `web_search`) silently activated plan mode without `resolve`, leaving the agent unable to submit the finalized plan and forcing the user to exit plan mode manually. `resolve` is now kept whenever `plan.enabled` is true, so the standing handler always has a callable tool ([#1428](https://github.com/can1357/oh-my-pi/issues/1428))
29
+
30
+ ### Fixed
31
+
32
+ - Fixed `omp` startup and `/changelog` reading the host project's `CHANGELOG.md` as omp's — `getPackageDir()` no longer falls back to the user's `cwd` when no owning `package.json` is locatable, preventing spurious `lastChangelogVersion` writes ([#1423](https://github.com/can1357/oh-my-pi/issues/1423))
33
+
34
+ ## [15.5.3] - 2026-05-27
35
+ ### Breaking Changes
36
+
37
+ - Disallowed inline payload on hashline `↑`, `↓`, and `:` operations (including BOF/EOF inserts), requiring payload text to be supplied on standalone `+` continuation rows
38
+
39
+ ### Changed
40
+
41
+ - Warned when legacy inline `LINE:TEXT` lines are accepted as payload continuations only when inside a pending multi-line `A-B:` replacement
42
+
5
43
  ## [15.5.2] - 2026-05-26
6
44
  ### Breaking Changes
7
45
 
@@ -2015,6 +2015,33 @@ export declare const SETTINGS_SCHEMA: {
2015
2015
  readonly description: "Minimum multiline block comment length before read summaries collapse it";
2016
2016
  };
2017
2017
  };
2018
+ readonly "read.summarize.minTotalLines": {
2019
+ readonly type: "number";
2020
+ readonly default: 100;
2021
+ readonly ui: {
2022
+ readonly tab: "editing";
2023
+ readonly label: "Read Summary Minimum File Length";
2024
+ readonly description: "Files with fewer total lines are read verbatim instead of structurally summarized";
2025
+ };
2026
+ };
2027
+ readonly "read.summarize.unfoldUntil": {
2028
+ readonly type: "number";
2029
+ readonly default: 50;
2030
+ readonly ui: {
2031
+ readonly tab: "editing";
2032
+ readonly label: "Read Summary Unfold Target";
2033
+ readonly description: "BFS-unfold elidable spans until the summary is at least this many visible lines. 0 keeps only the outermost elisions.";
2034
+ };
2035
+ };
2036
+ readonly "read.summarize.unfoldLimit": {
2037
+ readonly type: "number";
2038
+ readonly default: 100;
2039
+ readonly ui: {
2040
+ readonly tab: "editing";
2041
+ readonly label: "Read Summary Unfold Ceiling";
2042
+ readonly description: "Hard ceiling on summary size while BFS-unfolding. An unfold that would exceed this is reverted and unfolding stops.";
2043
+ };
2044
+ };
2018
2045
  readonly "read.toolResultPreview": {
2019
2046
  readonly type: "boolean";
2020
2047
  readonly default: false;
@@ -1,11 +1,37 @@
1
1
  export * from "./config/config-file";
2
2
  /**
3
- * Get the base directory for resolving optional package assets (docs, examples).
4
- * Walk up from import.meta.dir until we find package.json, or fall back to cwd.
3
+ * Walk up from `startDir` looking for a `package.json`. Returns the directory
4
+ * containing the marker, or `undefined` when the walk hits the filesystem root
5
+ * without finding one.
6
+ *
7
+ * Exported for unit-testing the resolution contract from arbitrary start
8
+ * directories (notably the `bun --compile` case where `import.meta.dir`
9
+ * resolves to `/$bunfs/root` and no owning package is locatable — issue
10
+ * #1423). Production callers should use {@link getPackageDir} instead.
11
+ */
12
+ export declare function walkUpForPackageDir(startDir: string): string | undefined;
13
+ /**
14
+ * Get the base directory for resolving optional package assets (docs, examples, CHANGELOG.md).
15
+ *
16
+ * Honors the `PI_PACKAGE_DIR` override (useful for Nix/Guix store paths);
17
+ * otherwise walks up from `import.meta.dir` looking for a `package.json`.
18
+ * Returns `undefined` when no owning package is locatable — notably inside
19
+ * `bun --compile` binaries where `import.meta.dir` resolves to `/$bunfs/root`
20
+ * and the walk hits the filesystem root with nothing found.
21
+ *
22
+ * Callers MUST treat `undefined` as "no package assets available" and skip the
23
+ * lookup. NEVER fall back to the user's `cwd` here: that conflates the host
24
+ * project with omp's own assets and was the source of issue #1423 (the host
25
+ * project's `CHANGELOG.md` rendered as omp's startup changelog).
26
+ */
27
+ export declare function getPackageDir(): string | undefined;
28
+ /**
29
+ * Path to omp's own `CHANGELOG.md`, or `undefined` when the package directory
30
+ * cannot be resolved (e.g. inside `bun --compile` binaries that don't bundle
31
+ * package assets). Callers MUST skip changelog parsing when this is undefined;
32
+ * see issue #1423.
5
33
  */
6
- export declare function getPackageDir(): string;
7
- /** Get path to CHANGELOG.md (optional, may not exist in binary) */
8
- export declare function getChangelogPath(): string;
34
+ export declare function getChangelogPath(): string | undefined;
9
35
  export interface ConfigDirEntry {
10
36
  path: string;
11
37
  source: string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Session-bound file snapshot store.
3
+ *
4
+ * Used by `read` and `search` to record exactly what the model saw, and by
5
+ * the hashline patcher to recover from stale section hashes (file changed
6
+ * externally between read and edit, or a prior in-session edit advanced
7
+ * the hash). The store is the {@link InMemorySnapshotStore} implementation
8
+ * from `@oh-my-pi/hashline`; the only coding-agent-specific concern here
9
+ * is wiring it onto the per-session {@link ToolSession} object.
10
+ */
11
+ import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
12
+ import type { ToolSession } from "../tools";
13
+ /**
14
+ * Look up (or lazily create) the file snapshot store attached to a session.
15
+ * Storage lives on `session.fileSnapshotStore` so it ages out exactly with
16
+ * the session itself.
17
+ */
18
+ export declare function getFileSnapshotStore(session: ToolSession): InMemorySnapshotStore;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Read-only hashline diff preview helpers used by the streaming edit
3
+ * renderer. Reads the target file, parses + applies the section's edits in
4
+ * memory (no FS write, no LSP writethrough), then hands the before/after
5
+ * pair to {@link generateDiffString} so the renderer can show the diff
6
+ * while the tool call is still streaming.
7
+ *
8
+ * Validation is intentionally light: only the section file hash is checked
9
+ * (so the preview goes red when anchors are stale), no plan-mode guards
10
+ * and no auto-generated-file refusal — those belong on the write path.
11
+ */
12
+ import { type PatchSection } from "@oh-my-pi/hashline";
13
+ export interface HashlineDiffOptions {
14
+ autoDropPureInsertDuplicates?: boolean;
15
+ }
16
+ export declare function computeHashlineSectionDiff(section: PatchSection, cwd: string, options?: HashlineDiffOptions): Promise<{
17
+ diff: string;
18
+ firstChangedLine: number | undefined;
19
+ } | {
20
+ error: string;
21
+ }>;
22
+ export declare function computeHashlineDiff(input: {
23
+ input: string;
24
+ path?: string;
25
+ }, cwd: string, options?: HashlineDiffOptions): Promise<{
26
+ diff: string;
27
+ firstChangedLine: number | undefined;
28
+ } | {
29
+ error: string;
30
+ }>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Coding-agent runner that drives the hashline {@link Patcher} on behalf of
3
+ * the `edit` tool. Converts a `{input, path?}` tool-call payload into a
4
+ * fully-applied patch, wraps the result in the agent's
5
+ * {@link AgentToolResult} shape, and attaches LSP diagnostics + `outputMeta`
6
+ * for the renderer.
7
+ *
8
+ * Multi-section patches are preflighted up front via {@link Patcher.prepare}
9
+ * so a partial batch never lands; the commit loop then narrows the LSP
10
+ * batch's `flush` flag to true only for the final write so diagnostics
11
+ * round-trip once.
12
+ */
13
+ import { MismatchError as HashlineMismatchError } from "@oh-my-pi/hashline";
14
+ import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
15
+ import type { WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
16
+ import type { ToolSession } from "../../tools";
17
+ import type { EditToolDetails, LspBatchRequest } from "../renderer";
18
+ import { type HashlineParams, hashlineEditParamsSchema } from "./params";
19
+ export interface ExecuteHashlineSingleOptions {
20
+ session: ToolSession;
21
+ input: string;
22
+ path?: string;
23
+ signal?: AbortSignal;
24
+ batchRequest?: LspBatchRequest;
25
+ writethrough: WritethroughCallback;
26
+ beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
27
+ }
28
+ export declare function executeHashlineSingle(options: ExecuteHashlineSingleOptions): Promise<AgentToolResult<EditToolDetails, typeof hashlineEditParamsSchema>>;
29
+ export { HashlineMismatchError, type HashlineParams, hashlineEditParamsSchema };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Coding-agent specific {@link Filesystem} adapter for the hashline patcher.
3
+ *
4
+ * Wires hashline's storage abstraction to the agent runtime:
5
+ *
6
+ * - Section paths are resolved through the plan-mode redirect so a bare
7
+ * `PLAN.md` lands at the canonical session artifact location.
8
+ * - Reads go through `readEditFileText` (notebook-aware) and the
9
+ * auto-generated-file guard.
10
+ * - Writes go through `serializeEditFileText` (notebook-aware) and the
11
+ * LSP writethrough, with FS-scan cache invalidation on success. The
12
+ * resulting `FileDiagnosticsResult` is captured per-path so the
13
+ * orchestrator can attach it to the tool result.
14
+ *
15
+ * Construct one per `executeHashlineSingle` call: per-section state
16
+ * (batch request, diagnostics) lives on the instance and isn't safe to
17
+ * share across concurrent edit tools.
18
+ */
19
+ import { Filesystem, type WriteResult } from "@oh-my-pi/hashline";
20
+ import type { FileDiagnosticsResult, WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
21
+ import type { ToolSession } from "../../tools";
22
+ import type { LspBatchRequest } from "../renderer";
23
+ export interface HashlineFilesystemOptions {
24
+ session: ToolSession;
25
+ writethrough: WritethroughCallback;
26
+ beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
27
+ signal?: AbortSignal;
28
+ /**
29
+ * Outer LSP batch request inherited from the tool-call context. The
30
+ * orchestrator narrows this per-section (flush only on the final write)
31
+ * via {@link HashlineFilesystem.setBatchRequest}.
32
+ */
33
+ batchRequest?: LspBatchRequest;
34
+ }
35
+ export declare class HashlineFilesystem extends Filesystem {
36
+ #private;
37
+ readonly session: ToolSession;
38
+ constructor(options: HashlineFilesystemOptions);
39
+ /**
40
+ * Set the LSP batch request used for the next {@link writeText} call.
41
+ * Multi-section orchestrators flip the `flush` flag to true before the
42
+ * final section so LSP diagnostics flush in one round-trip.
43
+ */
44
+ setBatchRequest(batchRequest: LspBatchRequest | undefined): void;
45
+ /**
46
+ * Look up (and clear) the diagnostics captured by the most-recent
47
+ * {@link writeText} call for `path`. Returns `undefined` if no write
48
+ * has happened or the writethrough returned no diagnostics.
49
+ */
50
+ consumeDiagnostics(path: string): FileDiagnosticsResult | undefined;
51
+ resolveAbsolute(relativePath: string): string;
52
+ canonicalPath(relativePath: string): string;
53
+ readText(relativePath: string): Promise<string>;
54
+ preflightWrite(relativePath: string): Promise<void>;
55
+ writeText(relativePath: string, content: string): Promise<WriteResult>;
56
+ exists(relativePath: string): Promise<boolean>;
57
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./diff";
2
+ export * from "./execute";
3
+ export * from "./filesystem";
4
+ export * from "./params";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Zod schema for the `edit` tool's hashline mode payload. The schema is
3
+ * deliberately permissive (`.passthrough()`) so providers can attach extra
4
+ * keys without rejection; only `input` is required and `path` is an
5
+ * optional fallback used when the input lacks a `¶PATH#HASH` header.
6
+ */
7
+ import * as z from "zod/v4";
8
+ export declare const hashlineEditParamsSchema: z.ZodObject<{
9
+ input: z.ZodString;
10
+ path: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$loose>;
12
+ export type HashlineParams = z.infer<typeof hashlineEditParamsSchema>;
@@ -1,16 +1,17 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import { type HashlineParams, hashlineEditParamsSchema } from "../hashline";
3
2
  import type { ToolSession } from "../tools";
4
3
  import { type EditMode } from "../utils/edit-mode";
4
+ import { type HashlineParams, hashlineEditParamsSchema } from "./hashline";
5
5
  import { type ApplyPatchParams, applyPatchSchema } from "./modes/apply-patch";
6
6
  import { type PatchParams, patchEditSchema } from "./modes/patch";
7
7
  import { type ReplaceParams, replaceEditSchema } from "./modes/replace";
8
8
  import { type EditToolDetails } from "./renderer";
9
+ export * from "@oh-my-pi/hashline";
9
10
  export { DEFAULT_EDIT_MODE, type EditMode, normalizeEditMode } from "../utils/edit-mode";
10
11
  export * from "./apply-patch";
11
12
  export * from "./diff";
12
- export * from "./file-read-cache";
13
- export * from "../hashline";
13
+ export * from "./file-snapshot-store";
14
+ export * from "./hashline";
14
15
  export * from "./modes/apply-patch";
15
16
  export * from "./modes/patch";
16
17
  export * from "./modes/replace";
@@ -1,23 +1,11 @@
1
1
  /**
2
2
  * Text normalization utilities for the edit tool.
3
3
  *
4
- * Handles line endings, BOM, whitespace, and Unicode normalization.
4
+ * Whitespace, Unicode, and indentation helpers. Line-ending and BOM
5
+ * primitives live in `@oh-my-pi/hashline` and are re-exported here so
6
+ * existing consumers see one stable surface.
5
7
  */
6
- export type LineEnding = "\r\n" | "\n";
7
- /** Detect the predominant line ending in content */
8
- export declare function detectLineEnding(content: string): LineEnding;
9
- /** Normalize all line endings to LF */
10
- export declare function normalizeToLF(text: string): string;
11
- /** Restore line endings to the specified type */
12
- export declare function restoreLineEndings(text: string, ending: LineEnding): string;
13
- export interface BomResult {
14
- /** The BOM character if present, empty string otherwise */
15
- bom: string;
16
- /** The text without the BOM */
17
- text: string;
18
- }
19
- /** Strip UTF-8 BOM if present */
20
- export declare function stripBom(content: string): BomResult;
8
+ export { type BomResult, detectLineEnding, type LineEnding, normalizeToLF, restoreLineEndings, stripBom, } from "@oh-my-pi/hashline";
21
9
  /** Count leading whitespace characters in a line */
22
10
  export declare function countLeadingWhitespace(line: string): number;
23
11
  /** Get the leading whitespace string from a line */
@@ -14,7 +14,6 @@ export type * from "./extensibility/extensions";
14
14
  export * from "./extensibility/extensions";
15
15
  export * from "./extensibility/skills";
16
16
  export { type FileSlashCommand, loadSlashCommands as discoverSlashCommands } from "./extensibility/slash-commands";
17
- export * from "./hashline";
18
17
  export type * from "./lsp";
19
18
  export * from "./main";
20
19
  export * from "./modes";
@@ -42,6 +42,7 @@ export interface BashToolDetails {
42
42
  meta?: OutputMeta;
43
43
  timeoutSeconds?: number;
44
44
  requestedTimeoutSeconds?: number;
45
+ wallTimeMs?: number;
45
46
  terminalId?: string;
46
47
  async?: {
47
48
  state: "running" | "completed" | "failed";
@@ -1,3 +1,4 @@
1
+ import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
1
2
  import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
2
3
  import type { ToolChoice } from "@oh-my-pi/pi-ai";
3
4
  import type { PromptTemplate } from "../config/prompt-templates";
@@ -188,11 +189,11 @@ export interface ToolSession {
188
189
  getCheckpointState?: () => CheckpointState | undefined;
189
190
  /** Set or clear active checkpoint state. */
190
191
  setCheckpointState?: (state: CheckpointState | null) => void;
191
- /** Per-session cache of file contents as last shown to the model by
192
- * `read`/`search`. Used by hashline anchor-stale recovery to reconstruct
193
- * the version the model authored anchors against when the file changed
194
- * out-of-band. Lazily initialized by `getFileReadCache`. */
195
- fileReadCache?: import("../edit/file-read-cache").FileReadCache;
192
+ /** Per-session snapshot store of file contents as last shown to the model
193
+ * by `read`/`search`. Used by hashline anchor-stale recovery to
194
+ * reconstruct the version the model authored anchors against when the
195
+ * file changed out-of-band. Lazily initialized by `getFileSnapshotStore`. */
196
+ fileSnapshotStore?: InMemorySnapshotStore;
196
197
  /** Per-session log of unresolved git merge conflict regions surfaced by
197
198
  * `read`. Each entry gets a stable id N referenced by `write conflict://N`
198
199
  * to splice the recorded region with replacement content. Lazily initialized
@@ -1,5 +1,23 @@
1
1
  export declare function expandTilde(filePath: string, home?: string): string;
2
2
  export declare function expandPath(filePath: string): string;
3
+ /**
4
+ * Inclusive line range describing one selector segment (e.g. `50-100`,
5
+ * `301-`, or `50+10`). `endLine` is `undefined` for open-ended ranges.
6
+ */
7
+ export interface LineRange {
8
+ startLine: number;
9
+ endLine: number | undefined;
10
+ }
11
+ /** Parse a single `N`, `N-M`, `N-`, or `N+K` chunk. Throws via {@link ToolError} on invalid bounds. */
12
+ export declare function parseLineRangeChunk(sel: string): LineRange | null;
13
+ /**
14
+ * Parse a comma-separated list of line ranges (e.g. `5-16,960-973`). Returns
15
+ * the ranges in ascending order with overlapping/adjacent ranges merged so
16
+ * downstream consumers can stream the file in a single forward pass per range.
17
+ */
18
+ export declare function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null;
19
+ /** Return `true` when `lineNumber` (1-indexed) falls in any of the supplied ranges. */
20
+ export declare function isLineInRanges(lineNumber: number, ranges: readonly LineRange[]): boolean;
3
21
  export declare function splitPathAndSel(rawPath: string): {
4
22
  path: string;
5
23
  sel?: string;
@@ -5,10 +5,15 @@ export interface ChangelogEntry {
5
5
  content: string;
6
6
  }
7
7
  /**
8
- * Parse changelog entries from CHANGELOG.md
9
- * Scans for ## lines and collects content until next ## or EOF
8
+ * Parse changelog entries from the file at `changelogPath`. Scans for `## [x.y.z]`
9
+ * headings and collects each block until the next heading or EOF.
10
+ *
11
+ * Returns `[]` when `changelogPath` is `undefined` (package directory not
12
+ * resolvable — see `getChangelogPath`) or the file is missing. Callers MUST NOT
13
+ * synthesize a fallback path from the host project's cwd; doing so caused issue
14
+ * #1423 (the host project's `CHANGELOG.md` was rendered as omp's).
10
15
  */
11
- export declare function parseChangelog(changelogPath: string): Promise<ChangelogEntry[]>;
16
+ export declare function parseChangelog(changelogPath: string | undefined): Promise<ChangelogEntry[]>;
12
17
  /**
13
18
  * Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
14
19
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.5.2",
4
+ "version": "15.5.4",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,12 +47,13 @@
47
47
  "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@babel/parser": "^7.29.3",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/omp-stats": "15.5.2",
51
- "@oh-my-pi/pi-agent-core": "15.5.2",
52
- "@oh-my-pi/pi-ai": "15.5.2",
53
- "@oh-my-pi/pi-natives": "15.5.2",
54
- "@oh-my-pi/pi-tui": "15.5.2",
55
- "@oh-my-pi/pi-utils": "15.5.2",
50
+ "@oh-my-pi/hashline": "15.5.4",
51
+ "@oh-my-pi/omp-stats": "15.5.4",
52
+ "@oh-my-pi/pi-agent-core": "15.5.4",
53
+ "@oh-my-pi/pi-ai": "15.5.4",
54
+ "@oh-my-pi/pi-natives": "15.5.4",
55
+ "@oh-my-pi/pi-tui": "15.5.4",
56
+ "@oh-my-pi/pi-utils": "15.5.4",
56
57
  "@puppeteer/browsers": "^2.13.0",
57
58
  "@types/turndown": "5.0.6",
58
59
  "@xterm/headless": "^6.0.0",
@@ -227,14 +228,6 @@
227
228
  "types": "./dist/types/edit/modes/*.d.ts",
228
229
  "import": "./src/edit/modes/*.ts"
229
230
  },
230
- "./hashline": {
231
- "types": "./dist/types/hashline/index.d.ts",
232
- "import": "./src/hashline/index.ts"
233
- },
234
- "./hashline/*": {
235
- "types": "./dist/types/hashline/*.d.ts",
236
- "import": "./src/hashline/*.ts"
237
- },
238
231
  "./exa": {
239
232
  "types": "./dist/types/exa/index.d.ts",
240
233
  "import": "./src/exa/index.ts"
@@ -1666,6 +1666,38 @@ export const SETTINGS_SCHEMA = {
1666
1666
  },
1667
1667
  },
1668
1668
 
1669
+ "read.summarize.minTotalLines": {
1670
+ type: "number",
1671
+ default: 100,
1672
+ ui: {
1673
+ tab: "editing",
1674
+ label: "Read Summary Minimum File Length",
1675
+ description: "Files with fewer total lines are read verbatim instead of structurally summarized",
1676
+ },
1677
+ },
1678
+
1679
+ "read.summarize.unfoldUntil": {
1680
+ type: "number",
1681
+ default: 50,
1682
+ ui: {
1683
+ tab: "editing",
1684
+ label: "Read Summary Unfold Target",
1685
+ description:
1686
+ "BFS-unfold elidable spans until the summary is at least this many visible lines. 0 keeps only the outermost elisions.",
1687
+ },
1688
+ },
1689
+
1690
+ "read.summarize.unfoldLimit": {
1691
+ type: "number",
1692
+ default: 100,
1693
+ ui: {
1694
+ tab: "editing",
1695
+ label: "Read Summary Unfold Ceiling",
1696
+ description:
1697
+ "Hard ceiling on summary size while BFS-unfolding. An unfold that would exceed this is reverted and unfolding stops.",
1698
+ },
1699
+ },
1700
+
1669
1701
  "read.toolResultPreview": {
1670
1702
  type: "boolean",
1671
1703
  default: false,
package/src/config.ts CHANGED
@@ -18,30 +18,57 @@ const priorityList = [
18
18
  // =============================================================================
19
19
 
20
20
  /**
21
- * Get the base directory for resolving optional package assets (docs, examples).
22
- * Walk up from import.meta.dir until we find package.json, or fall back to cwd.
21
+ * Walk up from `startDir` looking for a `package.json`. Returns the directory
22
+ * containing the marker, or `undefined` when the walk hits the filesystem root
23
+ * without finding one.
24
+ *
25
+ * Exported for unit-testing the resolution contract from arbitrary start
26
+ * directories (notably the `bun --compile` case where `import.meta.dir`
27
+ * resolves to `/$bunfs/root` and no owning package is locatable — issue
28
+ * #1423). Production callers should use {@link getPackageDir} instead.
23
29
  */
24
- export function getPackageDir(): string {
25
- // Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
26
- const envDir = process.env.PI_PACKAGE_DIR;
27
- if (envDir) {
28
- return expandTilde(envDir);
29
- }
30
-
31
- let dir = import.meta.dir;
30
+ export function walkUpForPackageDir(startDir: string): string | undefined {
31
+ let dir = startDir;
32
32
  while (dir !== path.dirname(dir)) {
33
33
  if (fs.existsSync(path.join(dir, "package.json"))) {
34
34
  return dir;
35
35
  }
36
36
  dir = path.dirname(dir);
37
37
  }
38
- // Fallback to project dir (docs/examples won't be found, but that's fine)
39
- return getProjectDir();
38
+ return undefined;
40
39
  }
41
40
 
42
- /** Get path to CHANGELOG.md (optional, may not exist in binary) */
43
- export function getChangelogPath(): string {
44
- return path.resolve(path.join(getPackageDir(), "CHANGELOG.md"));
41
+ /**
42
+ * Get the base directory for resolving optional package assets (docs, examples, CHANGELOG.md).
43
+ *
44
+ * Honors the `PI_PACKAGE_DIR` override (useful for Nix/Guix store paths);
45
+ * otherwise walks up from `import.meta.dir` looking for a `package.json`.
46
+ * Returns `undefined` when no owning package is locatable — notably inside
47
+ * `bun --compile` binaries where `import.meta.dir` resolves to `/$bunfs/root`
48
+ * and the walk hits the filesystem root with nothing found.
49
+ *
50
+ * Callers MUST treat `undefined` as "no package assets available" and skip the
51
+ * lookup. NEVER fall back to the user's `cwd` here: that conflates the host
52
+ * project with omp's own assets and was the source of issue #1423 (the host
53
+ * project's `CHANGELOG.md` rendered as omp's startup changelog).
54
+ */
55
+ export function getPackageDir(): string | undefined {
56
+ const envDir = process.env.PI_PACKAGE_DIR;
57
+ if (envDir) {
58
+ return expandTilde(envDir);
59
+ }
60
+ return walkUpForPackageDir(import.meta.dir);
61
+ }
62
+
63
+ /**
64
+ * Path to omp's own `CHANGELOG.md`, or `undefined` when the package directory
65
+ * cannot be resolved (e.g. inside `bun --compile` binaries that don't bundle
66
+ * package assets). Callers MUST skip changelog parsing when this is undefined;
67
+ * see issue #1423.
68
+ */
69
+ export function getChangelogPath(): string | undefined {
70
+ const packageDir = getPackageDir();
71
+ return packageDir ? path.resolve(packageDir, "CHANGELOG.md") : undefined;
45
72
  }
46
73
 
47
74
  // =============================================================================
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Session-bound file snapshot store.
3
+ *
4
+ * Used by `read` and `search` to record exactly what the model saw, and by
5
+ * the hashline patcher to recover from stale section hashes (file changed
6
+ * externally between read and edit, or a prior in-session edit advanced
7
+ * the hash). The store is the {@link InMemorySnapshotStore} implementation
8
+ * from `@oh-my-pi/hashline`; the only coding-agent-specific concern here
9
+ * is wiring it onto the per-session {@link ToolSession} object.
10
+ */
11
+ import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
12
+ import type { ToolSession } from "../tools";
13
+
14
+ /**
15
+ * Look up (or lazily create) the file snapshot store attached to a session.
16
+ * Storage lives on `session.fileSnapshotStore` so it ages out exactly with
17
+ * the session itself.
18
+ */
19
+ export function getFileSnapshotStore(session: ToolSession): InMemorySnapshotStore {
20
+ if (!session.fileSnapshotStore) session.fileSnapshotStore = new InMemorySnapshotStore();
21
+ return session.fileSnapshotStore;
22
+ }