pi-hashline-edit-pro 6.1.0 → 6.1.1

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/README.md CHANGED
@@ -346,7 +346,7 @@ The setting lives in `/hashline-config` as Auto-read all and in `config.json` as
346
346
 
347
347
  | Command | Description |
348
348
  | --- | --- |
349
- | `/hashline-config` | Open the settings window: auto-read anchors, auto-read all mode, ignore folders/files, disable on models, diff context lines, `anchor_grep` tool, copy/move tools, replace_match tool, required `path`, and strict input. Persists across sessions. |
349
+ | `/hashline-config` | Open the settings window: auto-read anchors, auto-read all mode, ignore folders/files, disable on models, disable read on models, diff context lines, `anchor_grep` tool, copy/move tools, replace_match tool, required `path`, and strict input. Persists across sessions. |
350
350
  | `/clear-anchors` | Clear the session's anchor claims. Anchors are re-claimed on the next `read`. |
351
351
 
352
352
  Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a setting is first changed in `/hashline-config`:
@@ -362,7 +362,8 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a se
362
362
  "requirePath": false,
363
363
  "strictInput": false,
364
364
  "diffContextLines": 1,
365
- "disableOnModels": []
365
+ "disableOnModels": [],
366
+ "disableReadOnModels": true
366
367
  }
367
368
  ```
368
369
 
@@ -377,9 +378,10 @@ Settings live in `~/.config/pi-hashline-edit-pro/config.json`, created when a se
377
378
  | `requirePath` | Require path | `false` | `replace`, `replace_match`, `insert`, `copy`, and `move` require a `path` argument that must match anchor ownership. |
378
379
  | `strictInput` | Strict input | `false` | Reject auto-fixable slips (`[W_BAD_SHAPE]`, `[W_BAD_REF]`, `[W_INVALID_PATCH]`, `[W_BARE_HASH_PREFIX]`) with `[E_BAD_SHAPE]` instead of applying them with a warning. |
379
380
  | `diffContextLines` | Diff context | `1` | Surrounding lines in post-edit diffs, 0-10 (needs Auto-read). |
380
- | `disableOnModels` | Disable on models | `[]` | Model globs matched case-insensitively against `provider/id`, the bare `id`, and `api`, with `*` and `?` wildcards. A matching model gets no hashline tools, no auto-read, and no write-echo refusal. |
381
+ | `disableOnModels` | Disable on models | `[]` | Model globs matched case-insensitively against `provider/id`, the bare `id`, and `api`, with `*` and `?` wildcards. A matching model gets no anchored edit tools, no auto-read, and no write-echo refusal; `read` is only removed while Disable read on models is on. |
382
+ | `disableReadOnModels` | Disable read on models | `true` | Also remove `read` from the active tools for a model matched by `disableOnModels`. Turn it off to keep `read` active on those models. |
381
383
 
382
- `disableOnModels` turns off the whole anchored surface for the models you name, so another edit tool (`apply_patch`, a shell read) can own the session without competing instructions. A match removes `read`, `replace`, `replace_match`, `insert`, `copy`, `move`, `anchor_grep`, and `undo_last_change` from the active tools, restoring the built-in `grep` when it was active, and it also skips the auto-read-all injection, the auto-read block after `write`, the post-edit diff substitution, and the `write` hook that refuses a `write` echoing a served anchor. The list is checked on `session_start`, on `model_select`, and again on `before_agent_start`, so a model change inside a session switches the surface immediately and another extension cannot re-add the tools mid-session. The default `[]` leaves every model unchanged.
384
+ `disableOnModels` turns off the whole anchored surface for the models you name, so another edit tool (`apply_patch`, a shell read) can own the session without competing instructions. A match removes `replace`, `replace_match`, `insert`, `copy`, `move`, `anchor_grep`, and `undo_last_change` from the active tools, and also `read` while Disable read on models is on (the default), restoring the built-in `grep` when it was active, and it also skips the auto-read-all injection, the auto-read block after `write`, the post-edit diff substitution, and the `write` hook that refuses a `write` echoing a served anchor. Because the extension's `read` replaces the built-in one, a matched model has no `read` at all unless another extension provides one, so turn Disable read on models off to keep the anchored `read` active on those models. The list is checked on `session_start`, on `model_select`, and again on `before_agent_start`, so a model change inside a session switches the surface immediately and another extension cannot re-add the tools mid-session. The default `[]` leaves every model unchanged.
383
385
 
384
386
  When `PI_HASHLINE_DIR` is unset or empty, non-Windows platforms honor `XDG_CONFIG_HOME` when set (falling back to `~/.config`); on Windows the directory always uses `~/.config`, where `~` is `%USERPROFILE%`. To move the directory explicitly, see [Isolated state](#isolated-state).
385
387
 
package/index.ts CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  toggleAnchorGrep,
26
26
  toggleCopyMove,
27
27
  toggleReplaceMatch,
28
+ toggleDisableReadOnModels,
28
29
  toggleRequirePath,
29
30
  toggleStrictInput,
30
31
  adjustDiffContextLines,
@@ -61,6 +62,7 @@ export default function (pi: ExtensionAPI): void {
61
62
  let autoReadAll: AutoReadAllMode = "off";
62
63
  let autoReadAllIgnore: string[] = [];
63
64
  let disableOnModels: string[] = [];
65
+ let disableReadOnModels = true;
64
66
  let autoReadAllInjected = false;
65
67
  let grepWasActive = false;
66
68
  const baseAnchorTools = new Set<string>();
@@ -85,7 +87,8 @@ export default function (pi: ExtensionAPI): void {
85
87
  async function syncModelGate(model: ModelLike | undefined): Promise<void> {
86
88
  if (disableOnModels.length > 0 && modelDisabled(model, disableOnModels)) {
87
89
  const active = pi.getActiveTools();
88
- const next = active.filter((tool) => !ANCHOR_TOOL_NAMES.includes(tool));
90
+ const gated = disableReadOnModels ? ANCHOR_TOOL_NAMES : ANCHOR_TOOL_NAMES.filter((tool) => tool !== "read");
91
+ const next = active.filter((tool) => !gated.includes(tool));
89
92
  if (grepWasActive && !next.includes("grep")) next.push("grep");
90
93
  pi.setActiveTools(next);
91
94
  gateApplied = true;
@@ -133,6 +136,7 @@ export default function (pi: ExtensionAPI): void {
133
136
  autoReadAll = config.autoReadAll ?? "off";
134
137
  autoReadAllIgnore = config.autoReadAllIgnore ?? [];
135
138
  disableOnModels = config.disableOnModels ?? [];
139
+ disableReadOnModels = config.disableReadOnModels !== false;
136
140
  const sessionBranch = (ctx as { sessionManager?: { getBranch?: () => Array<{ type?: string; customType?: string }> } }).sessionManager?.getBranch?.() ?? [];
137
141
  autoReadAllInjected = sessionBranch.some((entry) => entry.type === "custom_message" && entry.customType === AUTO_READ_ALL_CUSTOM_TYPE);
138
142
  await refreshEditTools();
@@ -197,6 +201,7 @@ export default function (pi: ExtensionAPI): void {
197
201
  else if (key === "autoReadAll") { autoReadAll = await cycleAutoReadAllMode(); autoReadAllInjected = false; }
198
202
  else if (key === "autoReadAllIgnore") autoReadAllIgnore = await setAutoReadAllIgnoreFromText(value ?? "");
199
203
  else if (key === "disableOnModels") disableOnModels = await setDisableOnModelsFromText(value ?? "");
204
+ else if (key === "disableReadOnModels") disableReadOnModels = await toggleDisableReadOnModels();
200
205
  else if (key === "diffContextLines") await adjustDiffContextLines(delta ?? 1);
201
206
  else if (key === "anchorGrepEnabled") {
202
207
  const enabled = await toggleAnchorGrep();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "6.1.0",
3
+ "version": "6.1.1",
4
4
  "type": "module",
5
5
  "description": "Hash-anchored read/replace/insert/grep tools for pi-coding-agent. Every line gets a unique 4-char tokenizer-friendly anchor that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
6
6
  "main": "index.ts",
package/src/config-ui.ts CHANGED
@@ -2,7 +2,7 @@ import { Key, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
2
2
  import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { readConfig, type Config } from "./config";
4
4
 
5
- export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllIgnore" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput" | "diffContextLines" | "disableOnModels";
5
+ export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllIgnore" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput" | "diffContextLines" | "disableOnModels" | "disableReadOnModels";
6
6
 
7
7
  export interface ConfigRow {
8
8
  key: ConfigToggleKey;
@@ -27,7 +27,8 @@ export function configRows(config: Config): ConfigRow[] {
27
27
  { key: "requirePath", label: "Require path", hint: "replace, insert, copy, move need path (RPC visibility)", enabled: config.requirePath === true },
28
28
  { key: "strictInput", label: "Strict input", hint: "Reject auto-fixable slips instead of warnings", enabled: config.strictInput === true },
29
29
  { key: "replaceMatchEnabled", label: "Replace match", hint: "replace_match tool (off while disabled)", enabled: config.replaceMatchEnabled !== false },
30
- { key: "disableOnModels", label: "Disable on models", hint: "Model globs (provider/id, model id, or api) that turn off read and the hashline tools (comma-separated)", enabled: (config.disableOnModels ?? []).length > 0, entries: config.disableOnModels ?? [] },
30
+ { key: "disableOnModels", label: "Disable on models", hint: "Model globs (provider/id, model id, or api) that turn off the anchored edit tools (comma-separated)", enabled: (config.disableOnModels ?? []).length > 0, entries: config.disableOnModels ?? [] },
31
+ { key: "disableReadOnModels", label: "Disable read on models", hint: "Also turn off read for models matched by Disable on models (off keeps read active)", enabled: config.disableReadOnModels !== false },
31
32
  ];
32
33
  }
33
34
 
package/src/config.ts CHANGED
@@ -21,6 +21,7 @@ export interface Config {
21
21
  strictInput?: boolean;
22
22
  diffContextLines?: number;
23
23
  disableOnModels?: string[];
24
+ disableReadOnModels?: boolean;
24
25
  }
25
26
 
26
27
  const DEFAULT_CONFIG: Config = {
@@ -33,7 +34,8 @@ const DEFAULT_CONFIG: Config = {
33
34
  requirePath: false,
34
35
  strictInput: false,
35
36
  diffContextLines: DEFAULT_DIFF_CONTEXT_LINES,
36
- disableOnModels: []
37
+ disableOnModels: [],
38
+ disableReadOnModels: true,
37
39
  };
38
40
 
39
41
  function parseAutoReadAllMode(value: unknown): AutoReadAllMode {
@@ -94,6 +96,7 @@ function parseConfig(content: string): Config {
94
96
  const diffContextLines = parsed.diffContextLines;
95
97
  const autoReadAllIgnore = parsed.autoReadAllIgnore;
96
98
  const disableOnModels = parsed.disableOnModels;
99
+ const disableReadOnModels = parsed.disableReadOnModels;
97
100
  return {
98
101
  autoRead: typeof autoRead === "boolean" ? autoRead : DEFAULT_CONFIG.autoRead,
99
102
  anchorGrepEnabled: typeof anchorGrepEnabled === "boolean" ? anchorGrepEnabled : DEFAULT_CONFIG.anchorGrepEnabled,
@@ -105,6 +108,7 @@ function parseConfig(content: string): Config {
105
108
  diffContextLines: normalizeDiffContextLines(diffContextLines),
106
109
  autoReadAllIgnore: parseAutoReadAllIgnore(autoReadAllIgnore),
107
110
  disableOnModels: parseDisableOnModels(disableOnModels),
111
+ disableReadOnModels: typeof disableReadOnModels === "boolean" ? disableReadOnModels : DEFAULT_CONFIG.disableReadOnModels,
108
112
  };
109
113
  }
110
114
 
@@ -212,7 +216,7 @@ export async function writeConfig(config: Config): Promise<void> {
212
216
  }
213
217
 
214
218
 
215
- type ToggleKey = "autoRead" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput";
219
+ type ToggleKey = "autoRead" | "anchorGrepEnabled" | "copyMoveEnabled" | "replaceMatchEnabled" | "requirePath" | "strictInput" | "disableReadOnModels";
216
220
 
217
221
  async function toggleFlag(key: ToggleKey): Promise<boolean> {
218
222
  const config = await updateConfig((c) => { c[key] = !(c[key] === true); });
@@ -222,6 +226,7 @@ export const toggleAutoRead = (): Promise<boolean> => toggleFlag("autoRead");
222
226
  export const toggleAnchorGrep = (): Promise<boolean> => toggleFlag("anchorGrepEnabled");
223
227
  export const toggleCopyMove = (): Promise<boolean> => toggleFlag("copyMoveEnabled");
224
228
  export const toggleReplaceMatch = (): Promise<boolean> => toggleFlag("replaceMatchEnabled");
229
+ export const toggleDisableReadOnModels = (): Promise<boolean> => toggleFlag("disableReadOnModels");
225
230
  export async function cycleAutoReadAllMode(): Promise<AutoReadAllMode> {
226
231
  let next: AutoReadAllMode = "off";
227
232
  await updateConfig((c) => {