@timurproko/a1 0.1.8-dev.269 → 0.1.8-dev.271

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.
@@ -1,4 +1,5 @@
1
1
  import { CURSOR_MARKER, decodeKittyPrintable, visibleWidth, } from "#pi-tui";
2
+ import { promptPathWordRanges } from "./path-word-ranges.js";
2
3
  export class OwnedEditorUxInterception {
3
4
  interceptors;
4
5
  fallback;
@@ -59,12 +60,13 @@ class PromptSelectionInterceptor {
59
60
  #lastClick;
60
61
  #redoStack = [];
61
62
  #selectionRevision = 0;
63
+ #wordDirection;
62
64
  #geometry;
63
65
  constructor(editor, keybindings, options) {
64
66
  this.editor = editor;
65
67
  this.keybindings = keybindings;
66
68
  this.options = options;
67
- installAtomicSegmentation(editor, options.atomicRanges);
69
+ installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
68
70
  }
69
71
  handleInput(data, next) {
70
72
  if (this.keybindings.matches(data, "owned.editor.selectAll")) {
@@ -167,24 +169,30 @@ class PromptSelectionInterceptor {
167
169
  if (this.keybindings.matches(data, "tui.editor.cursorWordLeft")) {
168
170
  const before = this.#cursor();
169
171
  const startedWithAtomicFocus = this.#atomicFocus() !== undefined;
170
- next();
172
+ this.#delegateWord(-1, next);
171
173
  let after = this.#cursor();
172
174
  const landedAtomic = this.#atomicRangeAt(after);
173
175
  if (!startedWithAtomicFocus && landedAtomic?.start === after.col && after.col > 0) {
174
176
  const line = editorState(this.editor).lines[after.line] ?? "";
175
177
  const previous = [...GRAPHEMES.segment(line.slice(0, after.col))].at(-1);
176
178
  if (previous !== undefined && !/^\s+$/u.test(previous.segment)) {
177
- next();
179
+ this.#delegateWord(-1, next);
178
180
  after = this.#cursor();
179
181
  }
180
182
  }
181
- if (!samePosition(before, after))
183
+ const line = editorState(this.editor).lines[after.line] ?? "";
184
+ const landedPath = promptPathWordRanges(line).some(range => range.start === after.col);
185
+ if (!samePosition(before, after) && !landedPath)
182
186
  this.#moveOntoPreviousSeparator();
183
187
  this.#requestRender();
184
188
  return;
185
189
  }
186
190
  const beforeText = this.editor.getText();
187
- next();
191
+ const wordDirection = this.#wordDirectionFor(data);
192
+ if (wordDirection === undefined)
193
+ next();
194
+ else
195
+ this.#delegateWord(wordDirection, next);
188
196
  if (this.editor.getText() !== beforeText)
189
197
  this.#redoStack = [];
190
198
  }
@@ -584,11 +592,28 @@ class PromptSelectionInterceptor {
584
592
  #snapshot() {
585
593
  return { text: this.editor.getText(), cursor: this.#cursor() };
586
594
  }
595
+ #wordDirectionFor(data) {
596
+ if (this.keybindings.matches(data, "tui.editor.deleteWordBackward"))
597
+ return -1;
598
+ if (this.keybindings.matches(data, "tui.editor.deleteWordForward")
599
+ || this.keybindings.matches(data, "tui.editor.cursorWordRight"))
600
+ return 1;
601
+ return undefined;
602
+ }
603
+ #delegateWord(direction, next) {
604
+ this.#wordDirection = direction;
605
+ try {
606
+ next();
607
+ }
608
+ finally {
609
+ this.#wordDirection = undefined;
610
+ }
611
+ }
587
612
  #requestRender() {
588
613
  this.options.requestRender();
589
614
  }
590
615
  }
591
- function installAtomicSegmentation(editor, rangesForText) {
616
+ function installAtomicSegmentation(editor, rangesForText, wordDirection) {
592
617
  if (Reflect.get(editor, ATOMIC_SEGMENTATION) === true)
593
618
  return;
594
619
  const originalValue = Reflect.get(editor, "segment");
@@ -597,7 +622,14 @@ function installAtomicSegmentation(editor, rangesForText) {
597
622
  const original = originalValue.bind(editor);
598
623
  Reflect.set(editor, "segment", (text, mode) => {
599
624
  const segments = [...original(text, mode)].filter(isEditorSegment);
600
- const ranges = rangesForText(text);
625
+ const ranges = rangesForText(text).map(range => ({ ...range, wordLike: false }));
626
+ if (mode === "word") {
627
+ for (const range of contextualPathRanges(editor, text, wordDirection())) {
628
+ if (!ranges.some(existing => rangesOverlap(existing, range)))
629
+ ranges.push({ ...range, wordLike: true });
630
+ }
631
+ }
632
+ ranges.sort((left, right) => left.start - right.start);
601
633
  if (ranges.length === 0)
602
634
  return segments;
603
635
  const merged = [];
@@ -608,10 +640,12 @@ function installAtomicSegmentation(editor, rangesForText) {
608
640
  const range = ranges[rangeIndex];
609
641
  if (range !== undefined && segment.index >= range.start && segment.index < range.end) {
610
642
  if (segment.index === range.start) {
643
+ const source = text.slice(range.start, range.end);
611
644
  merged.push({
612
- segment: text.slice(range.start, range.end).replaceAll(" ", ATOMIC_SPACE_SENTINEL),
645
+ segment: range.wordLike ? "w".repeat(source.length) : source.replaceAll(" ", ATOMIC_SPACE_SENTINEL),
613
646
  index: range.start,
614
647
  input: text,
648
+ ...(range.wordLike ? { isWordLike: true } : {}),
615
649
  });
616
650
  }
617
651
  continue;
@@ -622,6 +656,25 @@ function installAtomicSegmentation(editor, rangesForText) {
622
656
  });
623
657
  Reflect.set(editor, ATOMIC_SEGMENTATION, true);
624
658
  }
659
+ function contextualPathRanges(editor, text, direction) {
660
+ if (direction === undefined)
661
+ return promptPathWordRanges(text);
662
+ const state = editorState(editor);
663
+ const line = state.lines[state.cursorLine] ?? "";
664
+ const offset = direction < 0 ? 0 : state.cursorCol;
665
+ if (text !== (direction < 0 ? line.slice(0, state.cursorCol) : line.slice(state.cursorCol))) {
666
+ return promptPathWordRanges(text);
667
+ }
668
+ const end = offset + text.length;
669
+ return promptPathWordRanges(line).flatMap(range => {
670
+ const start = Math.max(range.start, offset);
671
+ const finish = Math.min(range.end, end);
672
+ return finish <= start ? [] : [{ start: start - offset, end: finish - offset }];
673
+ });
674
+ }
675
+ function rangesOverlap(left, right) {
676
+ return left.start < right.end && right.start < left.end;
677
+ }
625
678
  function isEditorSegment(value) {
626
679
  if (typeof value !== "object" || value === null)
627
680
  return false;
@@ -0,0 +1,2 @@
1
+ import type { PiShellEditorTextRange } from "./shell-shared-facade.js";
2
+ export declare function promptPathWordRanges(line: string): readonly PiShellEditorTextRange[];
@@ -0,0 +1,37 @@
1
+ const WHITESPACE = /\s/u;
2
+ const DRIVE_ROOT = /^[A-Za-z]:[\\/]/u;
3
+ const UNC_ROOT = /^(?:\\\\|\/\/)[^\\/\s]+[\\/][^\\/\s]+/u;
4
+ const POSIX_ROOT = /^\//u;
5
+ const EXPLICIT_RELATIVE_ROOT = /^(?:\.{1,2}|~)[\\/]/u;
6
+ export function promptPathWordRanges(line) {
7
+ const ranges = [];
8
+ let index = 0;
9
+ while (index < line.length) {
10
+ while (index < line.length && WHITESPACE.test(line[index] ?? ""))
11
+ index += 1;
12
+ if (index >= line.length)
13
+ break;
14
+ const start = index;
15
+ const quote = line[index] === '"' || line[index] === "'" ? line[index] : undefined;
16
+ if (quote !== undefined) {
17
+ const closing = line.indexOf(quote, index + 1);
18
+ if (closing >= 0 && (closing + 1 === line.length || WHITESPACE.test(line[closing + 1] ?? ""))) {
19
+ if (isExplicitPath(line.slice(index + 1, closing)))
20
+ ranges.push({ start, end: closing + 1 });
21
+ index = closing + 1;
22
+ continue;
23
+ }
24
+ }
25
+ while (index < line.length && !WHITESPACE.test(line[index] ?? ""))
26
+ index += 1;
27
+ if (isExplicitPath(line.slice(start, index)))
28
+ ranges.push({ start, end: index });
29
+ }
30
+ return ranges;
31
+ }
32
+ function isExplicitPath(value) {
33
+ return DRIVE_ROOT.test(value)
34
+ || UNC_ROOT.test(value)
35
+ || POSIX_ROOT.test(value)
36
+ || EXPLICIT_RELATIVE_ROOT.test(value);
37
+ }
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T14:17:35.764Z",
8
+ "builtAt": "2026-09-06T15:05:45.656Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "dc03605e5780e2aeebb4ecafd62868dc673e22ddd38b024a369aff704ff136dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T14:17:46.307Z",
8
+ "builtAt": "2026-09-06T15:05:42.977Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-06T14:18:04.630Z",
8
+ "builtAt": "2026-09-06T15:06:12.448Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "d78243dd7f7852c64abc40b2b544039eaa9eba0d1f0bd42317002a97e2e84c7b",
11
+ "sha256": "7cca17597d89ae0dad9cd14114acc0795f688d54f6580b897463fba11af479ca",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.269",
3
+ "version": "0.1.8-dev.271",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",