@oh-my-pi/pi-coding-agent 14.9.1 → 14.9.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.9.2] - 2026-05-10
6
+ ### Added
7
+
8
+ - Added `agentsMdFiles` to `WorkspaceTree` so AGENTS.md discovery results are returned with the workspace scan output
9
+
10
+ ### Changed
11
+
12
+ - Changed startup workspace discovery to use one native `listWorkspace` walk for both the rendered tree and AGENTS.md directory-context candidates, removing the layered `git ls-files` orchestration and secondary AGENTS.md glob.
13
+
14
+ ### Fixed
15
+
16
+ - Fixed AGENTS.md context discovery to include AGENTS.md files that are explicitly gitignored while still excluding AGENTS.md files under ignored directories
17
+ - Fixed task tool renderer spamming `Tool renderer failed: undefined is not an object (evaluating 'args.tasks.length')` warnings while a `task` call was streaming in (the `tasks` array is undefined until the partial JSON parser closes it); the renderer now tolerates an absent `tasks` field and shows `0 agents` until the array arrives ([#985](https://github.com/can1357/oh-my-pi/issues/985)).
18
+ - Fixed MCP HTTP streamable transport spamming `HTTP SSE stream error: ReadableStream already has a controller` after every JSON-RPC request whose response was returned as `text/event-stream`. The transport used to break out of the SSE iterator once the matching response was captured and then re-open `response.body` for a background drain, but the body had already been piped through a `TransformStream` and could not be re-read. The drain now runs from a single iterator that resolves the response promise inline and continues to dispatch piggybacked notifications on the same stream.
19
+
5
20
  ## [14.9.0] - 2026-05-10
6
21
  ### Breaking Changes
7
22
 
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": "14.9.1",
4
+ "version": "14.9.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -47,12 +47,12 @@
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": "14.9.1",
51
- "@oh-my-pi/pi-agent-core": "14.9.1",
52
- "@oh-my-pi/pi-ai": "14.9.1",
53
- "@oh-my-pi/pi-natives": "14.9.1",
54
- "@oh-my-pi/pi-tui": "14.9.1",
55
- "@oh-my-pi/pi-utils": "14.9.1",
50
+ "@oh-my-pi/omp-stats": "14.9.2",
51
+ "@oh-my-pi/pi-agent-core": "14.9.2",
52
+ "@oh-my-pi/pi-ai": "14.9.2",
53
+ "@oh-my-pi/pi-natives": "14.9.2",
54
+ "@oh-my-pi/pi-tui": "14.9.2",
55
+ "@oh-my-pi/pi-utils": "14.9.2",
56
56
  "@puppeteer/browsers": "^2.13.0",
57
57
  "@sinclair/typebox": "^0.34.49",
58
58
  "@types/turndown": "5.0.6",
@@ -60,6 +60,15 @@ interface BeforeAgentStartCombinedResult {
60
60
 
61
61
  export type ExtensionErrorListener = (error: ExtensionError) => void;
62
62
 
63
+ export const EXTENSION_HANDLER_TIMEOUT_MS = 30_000;
64
+ let extensionHandlerTimeoutMs = EXTENSION_HANDLER_TIMEOUT_MS;
65
+
66
+ export function __test_setExtensionHandlerTimeoutMs(timeoutMs: number): void {
67
+ extensionHandlerTimeoutMs = timeoutMs;
68
+ }
69
+
70
+ const EXTENSION_HANDLER_TIMEOUT = Symbol("extensionHandlerTimeout");
71
+
63
72
  /**
64
73
  * Events handled by the generic emit() method.
65
74
  * Events with dedicated emitXxx() methods are excluded for stronger type safety.
@@ -434,6 +443,46 @@ export class ExtensionRunner {
434
443
  );
435
444
  }
436
445
 
446
+ async #runHandlerWithTimeout<TEvent extends { type: string }, TResult>(
447
+ handler: (event: TEvent, ctx: ExtensionContext) => Promise<TResult | undefined> | TResult | undefined,
448
+ event: TEvent,
449
+ ctx: ExtensionContext,
450
+ ext: Extension,
451
+ timeoutMs: number,
452
+ ): Promise<TResult | undefined> {
453
+ try {
454
+ const handlerResult = await Promise.race([
455
+ Promise.resolve(handler(event, ctx)),
456
+ Bun.sleep(timeoutMs).then(() => EXTENSION_HANDLER_TIMEOUT),
457
+ ]);
458
+ if (handlerResult === EXTENSION_HANDLER_TIMEOUT) {
459
+ const error = `handler timed out after ${timeoutMs}ms`;
460
+ logger.warn("Extension handler timed out", {
461
+ extensionPath: ext.path,
462
+ event: event.type,
463
+ timeoutMs,
464
+ });
465
+ this.emitError({
466
+ extensionPath: ext.path,
467
+ event: event.type,
468
+ error,
469
+ });
470
+ return undefined;
471
+ }
472
+ return handlerResult as TResult | undefined;
473
+ } catch (err) {
474
+ const message = err instanceof Error ? err.message : String(err);
475
+ const stack = err instanceof Error ? err.stack : undefined;
476
+ this.emitError({
477
+ extensionPath: ext.path,
478
+ event: event.type,
479
+ error: message,
480
+ stack,
481
+ });
482
+ return undefined;
483
+ }
484
+ }
485
+
437
486
  async emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>> {
438
487
  const ctx = this.createContext();
439
488
  let result: SessionBeforeEventResult | SessionCompactingResult | undefined;
@@ -443,28 +492,23 @@ export class ExtensionRunner {
443
492
  if (!handlers || handlers.length === 0) continue;
444
493
 
445
494
  for (const handler of handlers) {
446
- try {
447
- const handlerResult = await handler(event, ctx);
448
-
449
- if (this.#isSessionBeforeEvent(event) && handlerResult) {
450
- result = handlerResult as SessionBeforeEventResult;
451
- if (result.cancel) {
452
- return result as RunnerEmitResult<TEvent>;
453
- }
495
+ const handlerResult = await this.#runHandlerWithTimeout(
496
+ handler,
497
+ event,
498
+ ctx,
499
+ ext,
500
+ extensionHandlerTimeoutMs,
501
+ );
502
+
503
+ if (this.#isSessionBeforeEvent(event) && handlerResult) {
504
+ result = handlerResult as SessionBeforeEventResult;
505
+ if (result.cancel) {
506
+ return result as RunnerEmitResult<TEvent>;
454
507
  }
508
+ }
455
509
 
456
- if (event.type === "session.compacting" && handlerResult) {
457
- result = handlerResult as SessionCompactingResult;
458
- }
459
- } catch (err) {
460
- const message = err instanceof Error ? err.message : String(err);
461
- const stack = err instanceof Error ? err.stack : undefined;
462
- this.emitError({
463
- extensionPath: ext.path,
464
- event: event.type,
465
- error: message,
466
- stack,
467
- });
510
+ if (event.type === "session.compacting" && handlerResult) {
511
+ result = handlerResult as SessionCompactingResult;
468
512
  }
469
513
  }
470
514
  }
@@ -482,31 +526,26 @@ export class ExtensionRunner {
482
526
  if (!handlers || handlers.length === 0) continue;
483
527
 
484
528
  for (const handler of handlers) {
485
- try {
486
- const handlerResult = (await handler(currentEvent, ctx)) as ToolResultEventResult | undefined;
487
- if (!handlerResult) continue;
488
-
489
- if (handlerResult.content !== undefined) {
490
- currentEvent.content = handlerResult.content;
491
- modified = true;
492
- }
493
- if (handlerResult.details !== undefined) {
494
- currentEvent.details = handlerResult.details;
495
- modified = true;
496
- }
497
- if (handlerResult.isError !== undefined) {
498
- currentEvent.isError = handlerResult.isError;
499
- modified = true;
500
- }
501
- } catch (err) {
502
- const message = err instanceof Error ? err.message : String(err);
503
- const stack = err instanceof Error ? err.stack : undefined;
504
- this.emitError({
505
- extensionPath: ext.path,
506
- event: "tool_result",
507
- error: message,
508
- stack,
509
- });
529
+ const handlerResult = (await this.#runHandlerWithTimeout(
530
+ handler,
531
+ currentEvent,
532
+ ctx,
533
+ ext,
534
+ extensionHandlerTimeoutMs,
535
+ )) as ToolResultEventResult | undefined;
536
+ if (!handlerResult) continue;
537
+
538
+ if (handlerResult.content !== undefined) {
539
+ currentEvent.content = handlerResult.content;
540
+ modified = true;
541
+ }
542
+ if (handlerResult.details !== undefined) {
543
+ currentEvent.details = handlerResult.details;
544
+ modified = true;
545
+ }
546
+ if (handlerResult.isError !== undefined) {
547
+ currentEvent.isError = handlerResult.isError;
548
+ modified = true;
510
549
  }
511
550
  }
512
551
  }
@@ -574,20 +613,15 @@ export class ExtensionRunner {
574
613
  if (!handlers || handlers.length === 0) continue;
575
614
 
576
615
  for (const handler of handlers) {
577
- try {
578
- const handlerResult = await handler(event, ctx);
579
- if (handlerResult) {
580
- return handlerResult as R;
581
- }
582
- } catch (err) {
583
- const message = err instanceof Error ? err.message : String(err);
584
- const stack = err instanceof Error ? err.stack : undefined;
585
- this.emitError({
586
- extensionPath: ext.path,
587
- event: eventName,
588
- error: message,
589
- stack,
590
- });
616
+ const handlerResult = await this.#runHandlerWithTimeout(
617
+ handler,
618
+ event,
619
+ ctx,
620
+ ext,
621
+ extensionHandlerTimeoutMs,
622
+ );
623
+ if (handlerResult) {
624
+ return handlerResult as R;
591
625
  }
592
626
  }
593
627
  }
@@ -613,29 +647,24 @@ export class ExtensionRunner {
613
647
  if (!handlers || handlers.length === 0) continue;
614
648
 
615
649
  for (const handler of handlers) {
616
- try {
617
- const event: ResourcesDiscoverEvent = { type: "resources_discover", cwd, reason };
618
- const handlerResult = await handler(event, ctx);
619
- const result = handlerResult as ResourcesDiscoverResult | undefined;
620
-
621
- if (result?.skillPaths?.length) {
622
- skillPaths.push(...result.skillPaths.map(path => ({ path, extensionPath: ext.path })));
623
- }
624
- if (result?.promptPaths?.length) {
625
- promptPaths.push(...result.promptPaths.map(path => ({ path, extensionPath: ext.path })));
626
- }
627
- if (result?.themePaths?.length) {
628
- themePaths.push(...result.themePaths.map(path => ({ path, extensionPath: ext.path })));
629
- }
630
- } catch (err) {
631
- const message = err instanceof Error ? err.message : String(err);
632
- const stack = err instanceof Error ? err.stack : undefined;
633
- this.emitError({
634
- extensionPath: ext.path,
635
- event: "resources_discover",
636
- error: message,
637
- stack,
638
- });
650
+ const event: ResourcesDiscoverEvent = { type: "resources_discover", cwd, reason };
651
+ const handlerResult = await this.#runHandlerWithTimeout(
652
+ handler,
653
+ event,
654
+ ctx,
655
+ ext,
656
+ extensionHandlerTimeoutMs,
657
+ );
658
+ const result = handlerResult as ResourcesDiscoverResult | undefined;
659
+
660
+ if (result?.skillPaths?.length) {
661
+ skillPaths.push(...result.skillPaths.map(path => ({ path, extensionPath: ext.path })));
662
+ }
663
+ if (result?.promptPaths?.length) {
664
+ promptPaths.push(...result.promptPaths.map(path => ({ path, extensionPath: ext.path })));
665
+ }
666
+ if (result?.themePaths?.length) {
667
+ themePaths.push(...result.themePaths.map(path => ({ path, extensionPath: ext.path })));
639
668
  }
640
669
  }
641
670
  }
@@ -655,21 +684,14 @@ export class ExtensionRunner {
655
684
 
656
685
  for (const ext of this.extensions) {
657
686
  for (const handler of ext.handlers.get("input") ?? []) {
658
- try {
659
- const event: InputEvent = { type: "input", text: currentText, images: currentImages, source };
660
- const result = (await handler(event, ctx)) as InputEventResult | undefined;
661
- if (result?.handled) return result;
662
- if (result?.text !== undefined) {
663
- currentText = result.text;
664
- currentImages = result.images ?? currentImages;
665
- }
666
- } catch (err) {
667
- this.emitError({
668
- extensionPath: ext.path,
669
- event: "input",
670
- error: err instanceof Error ? err.message : String(err),
671
- stack: err instanceof Error ? err.stack : undefined,
672
- });
687
+ const event: InputEvent = { type: "input", text: currentText, images: currentImages, source };
688
+ const result = (await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs)) as
689
+ | InputEventResult
690
+ | undefined;
691
+ if (result?.handled) return result;
692
+ if (result?.text !== undefined) {
693
+ currentText = result.text;
694
+ currentImages = result.images ?? currentImages;
673
695
  }
674
696
  }
675
697
  }
@@ -704,22 +726,17 @@ export class ExtensionRunner {
704
726
  if (!handlers || handlers.length === 0) continue;
705
727
 
706
728
  for (const handler of handlers) {
707
- try {
708
- const event: ContextEvent = { type: "context", messages: currentMessages };
709
- const handlerResult = await handler(event, ctx);
710
-
711
- if (handlerResult && (handlerResult as ContextEventResult).messages) {
712
- currentMessages = (handlerResult as ContextEventResult).messages!;
713
- }
714
- } catch (err) {
715
- const message = err instanceof Error ? err.message : String(err);
716
- const stack = err instanceof Error ? err.stack : undefined;
717
- this.emitError({
718
- extensionPath: ext.path,
719
- event: "context",
720
- error: message,
721
- stack,
722
- });
729
+ const event: ContextEvent = { type: "context", messages: currentMessages };
730
+ const handlerResult = await this.#runHandlerWithTimeout(
731
+ handler,
732
+ event,
733
+ ctx,
734
+ ext,
735
+ extensionHandlerTimeoutMs,
736
+ );
737
+
738
+ if (handlerResult && (handlerResult as ContextEventResult).messages) {
739
+ currentMessages = (handlerResult as ContextEventResult).messages!;
723
740
  }
724
741
  }
725
742
  }
@@ -736,24 +753,19 @@ export class ExtensionRunner {
736
753
  if (!handlers || handlers.length === 0) continue;
737
754
 
738
755
  for (const handler of handlers) {
739
- try {
740
- const event: BeforeProviderRequestEvent = {
741
- type: "before_provider_request",
742
- payload: currentPayload,
743
- };
744
- const handlerResult = await handler(event, ctx);
745
- if (handlerResult !== undefined) {
746
- currentPayload = handlerResult;
747
- }
748
- } catch (err) {
749
- const message = err instanceof Error ? err.message : String(err);
750
- const stack = err instanceof Error ? err.stack : undefined;
751
- this.emitError({
752
- extensionPath: ext.path,
753
- event: "before_provider_request",
754
- error: message,
755
- stack,
756
- });
756
+ const event: BeforeProviderRequestEvent = {
757
+ type: "before_provider_request",
758
+ payload: currentPayload,
759
+ };
760
+ const handlerResult = await this.#runHandlerWithTimeout(
761
+ handler,
762
+ event,
763
+ ctx,
764
+ ext,
765
+ extensionHandlerTimeoutMs,
766
+ );
767
+ if (handlerResult !== undefined) {
768
+ currentPayload = handlerResult;
757
769
  }
758
770
  }
759
771
  }
@@ -769,25 +781,14 @@ export class ExtensionRunner {
769
781
  if (!handlers || handlers.length === 0) continue;
770
782
 
771
783
  for (const handler of handlers) {
772
- try {
773
- const event: AfterProviderResponseEvent = {
774
- type: "after_provider_response",
775
- status: response.status,
776
- headers: response.headers,
777
- requestId: response.requestId,
778
- metadata: response.metadata,
779
- };
780
- await handler(event, ctx);
781
- } catch (err) {
782
- const message = err instanceof Error ? err.message : String(err);
783
- const stack = err instanceof Error ? err.stack : undefined;
784
- this.emitError({
785
- extensionPath: ext.path,
786
- event: "after_provider_response",
787
- error: message,
788
- stack,
789
- });
790
- }
784
+ const event: AfterProviderResponseEvent = {
785
+ type: "after_provider_response",
786
+ status: response.status,
787
+ headers: response.headers,
788
+ requestId: response.requestId,
789
+ metadata: response.metadata,
790
+ };
791
+ await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
791
792
  }
792
793
  }
793
794
  }
@@ -807,34 +808,29 @@ export class ExtensionRunner {
807
808
  if (!handlers || handlers.length === 0) continue;
808
809
 
809
810
  for (const handler of handlers) {
810
- try {
811
- const event: BeforeAgentStartEvent = {
812
- type: "before_agent_start",
813
- prompt,
814
- images,
815
- systemPrompt: currentSystemPrompt,
816
- };
817
- const handlerResult = await handler(event, ctx);
818
-
819
- if (handlerResult) {
820
- const result = handlerResult as BeforeAgentStartEventResult;
821
- if (result.message) {
822
- messages.push(result.message);
823
- }
824
- if (result.systemPrompt !== undefined) {
825
- currentSystemPrompt = result.systemPrompt;
826
- systemPromptModified = true;
827
- }
811
+ const event: BeforeAgentStartEvent = {
812
+ type: "before_agent_start",
813
+ prompt,
814
+ images,
815
+ systemPrompt: currentSystemPrompt,
816
+ };
817
+ const handlerResult = await this.#runHandlerWithTimeout(
818
+ handler,
819
+ event,
820
+ ctx,
821
+ ext,
822
+ extensionHandlerTimeoutMs,
823
+ );
824
+
825
+ if (handlerResult) {
826
+ const result = handlerResult as BeforeAgentStartEventResult;
827
+ if (result.message) {
828
+ messages.push(result.message);
829
+ }
830
+ if (result.systemPrompt !== undefined) {
831
+ currentSystemPrompt = result.systemPrompt;
832
+ systemPromptModified = true;
828
833
  }
829
- } catch (err) {
830
- const message = err instanceof Error ? err.message : String(err);
831
- const stack = err instanceof Error ? err.stack : undefined;
832
- this.emitError({
833
- extensionPath: ext.path,
834
- event: "before_agent_start",
835
- error: message,
836
- stack,
837
- });
838
834
  }
839
835
  }
840
836
  }
@@ -1,6 +1,6 @@
1
1
  import { HashlineMismatchError } from "./anchors";
2
2
  import { RANGE_INTERIOR_HASH } from "./constants";
3
- import { computeLineHash, HL_EDIT_SEP } from "./hash";
3
+ import { computeLineHash } from "./hash";
4
4
  import { cloneCursor } from "./parser";
5
5
  import type { Anchor, HashlineApplyOptions, HashlineCursor, HashlineEdit, HashMismatch } from "./types";
6
6
 
@@ -37,7 +37,6 @@ interface HashlineReplacementGroup {
37
37
 
38
38
  function getHashlineEditAnchors(edit: HashlineEdit): Anchor[] {
39
39
  if (edit.kind === "delete") return [edit.anchor];
40
- if (edit.kind === "modify") return [edit.anchor];
41
40
  if (edit.cursor.kind === "before_anchor") return [edit.cursor.anchor];
42
41
  if (edit.cursor.kind === "after_anchor") return [edit.cursor.anchor];
43
42
  return [];
@@ -96,7 +95,7 @@ function insertAtEnd(fileLines: string[], lineOrigins: HashlineLineOrigin[], lin
96
95
  /** Bucket edits by the line they target so we can apply each line's group in one splice. */
97
96
 
98
97
  function getAnchorTargetLine(edit: HashlineEdit): number | undefined {
99
- if (edit.kind === "delete" || edit.kind === "modify") return edit.anchor.line;
98
+ if (edit.kind === "delete") return edit.anchor.line;
100
99
  if (edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor") return edit.cursor.anchor.line;
101
100
  return undefined;
102
101
  }
@@ -607,11 +606,9 @@ function bucketAnchorEditsByLine(edits: IndexedEdit[]): Map<number, IndexedEdit[
607
606
  const line =
608
607
  entry.edit.kind === "delete"
609
608
  ? entry.edit.anchor.line
610
- : entry.edit.kind === "modify"
611
- ? entry.edit.anchor.line
612
- : entry.edit.cursor.kind === "before_anchor"
613
- ? entry.edit.cursor.anchor.line
614
- : 0;
609
+ : entry.edit.cursor.kind === "before_anchor"
610
+ ? entry.edit.cursor.anchor.line
611
+ : 0;
615
612
  const bucket = byLine.get(line);
616
613
  if (bucket) bucket.push(entry);
617
614
  else byLine.set(line, [entry]);
@@ -683,33 +680,20 @@ export function applyHashlineEdits(
683
680
  const currentLine = fileLines[idx] ?? "";
684
681
  const beforeLines: string[] = [];
685
682
  let deleteLine = false;
686
- let prefix = "";
687
- let suffix = "";
688
- let modified = false;
689
683
 
690
684
  for (const { edit } of bucket) {
691
685
  if (edit.kind === "insert") {
692
686
  beforeLines.push(edit.text);
693
687
  } else if (edit.kind === "delete") {
694
688
  deleteLine = true;
695
- } else if (edit.kind === "modify") {
696
- prefix = edit.prefix + prefix;
697
- suffix = suffix + edit.suffix;
698
- modified = true;
699
689
  }
700
690
  }
701
- if (beforeLines.length === 0 && !deleteLine && !modified) continue;
702
- if (deleteLine && modified) {
703
- throw new Error(
704
- `line ${line}: cannot combine inline modify ("< ${line}${HL_EDIT_SEP}…" or "+ ${line}${HL_EDIT_SEP}…") with a delete or replace targeting the same line.`,
705
- );
706
- }
691
+ if (beforeLines.length === 0 && !deleteLine) continue;
707
692
 
708
- const effectiveLine = modified ? prefix + currentLine + suffix : currentLine;
709
- const replacement = deleteLine ? beforeLines : [...beforeLines, effectiveLine];
693
+ const replacement = deleteLine ? beforeLines : [...beforeLines, currentLine];
710
694
  const origins = replacement.map((): HashlineLineOrigin => (deleteLine ? "replacement" : "insert"));
711
695
  if (!deleteLine) {
712
- origins[origins.length - 1] = modified ? "replacement" : (lineOrigins[idx] ?? "original");
696
+ origins[origins.length - 1] = lineOrigins[idx] ?? "original";
713
697
  }
714
698
 
715
699
  fileLines.splice(idx, 1, ...replacement);
@@ -42,7 +42,6 @@ async function readHashlineFile(absolutePath: string, pathText: string): Promise
42
42
  function hasAnchorScopedEdit(edits: HashlineEdit[]): boolean {
43
43
  return edits.some(edit => {
44
44
  if (edit.kind === "delete") return true;
45
- if (edit.kind === "modify") return true;
46
45
  return edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor";
47
46
  });
48
47
  }
@@ -7,16 +7,12 @@ section: file_header line_op*
7
7
 
8
8
  file_header: "@" path LF
9
9
 
10
- line_op: inline_before_op payload*
11
- | inline_after_op payload*
12
- | insert_before_op payload+
10
+ line_op: insert_before_op payload+
13
11
  | insert_after_op payload+
14
12
  | replace_op payload*
15
13
  | delete_op
16
14
  | blank
17
15
 
18
- inline_before_op: "<" LID $HSEP$ line_text? LF
19
- inline_after_op: "+" LID $HSEP$ line_text? LF
20
16
  insert_before_op: "<" insert_target LF
21
17
  insert_after_op: "+" insert_target LF
22
18
  replace_op: "=" range LF
@@ -1,9 +1,8 @@
1
1
  import { RANGE_INTERIOR_HASH } from "./constants";
2
- import { describeAnchorExamples, HL_EDIT_SEP, HL_EDIT_SEP_RE_RAW, HL_HASH_CAPTURE_RE_RAW } from "./hash";
2
+ import { describeAnchorExamples, HL_EDIT_SEP, HL_HASH_CAPTURE_RE_RAW } from "./hash";
3
3
  import type { Anchor, HashlineCursor, HashlineEdit } from "./types";
4
4
  import { stripTrailingCarriageReturn } from "./utils";
5
5
 
6
- const HL_EDIT_SEPARATOR_RE = HL_EDIT_SEP_RE_RAW;
7
6
  const LID_CAPTURE_RE = new RegExp(`^${HL_HASH_CAPTURE_RE_RAW}$`);
8
7
 
9
8
  function parseLid(raw: string, lineNum: number): Anchor {
@@ -70,8 +69,6 @@ const INSERT_BEFORE_OP_RE = /^<\s*(\S+)$/;
70
69
  const INSERT_AFTER_OP_RE = /^\+\s*(\S+)$/;
71
70
  const DELETE_OP_RE = /^-\s*(\S+)$/;
72
71
  const REPLACE_OP_RE = /^=\s*(\S+)$/;
73
- const INLINE_BEFORE_OP_RE = new RegExp(`^<\\s*${HL_HASH_CAPTURE_RE_RAW}${HL_EDIT_SEPARATOR_RE}(.*)$`);
74
- const INLINE_AFTER_OP_RE = new RegExp(`^\\+\\s*${HL_HASH_CAPTURE_RE_RAW}${HL_EDIT_SEPARATOR_RE}(.*)$`);
75
72
 
76
73
  export function cloneCursor(cursor: HashlineCursor): HashlineCursor {
77
74
  if (cursor.kind === "before_anchor") return { kind: "before_anchor", anchor: { ...cursor.anchor } };
@@ -125,42 +122,6 @@ export function parseHashlineWithWarnings(diff: string): { edits: HashlineEdit[]
125
122
  throw new Error(`line ${lineNum}: payload line has no preceding +, <, or = operation.`);
126
123
  }
127
124
 
128
- const inlineBeforeMatch = INLINE_BEFORE_OP_RE.exec(line);
129
- if (inlineBeforeMatch) {
130
- const anchor = parseLid(`${inlineBeforeMatch[1]}${inlineBeforeMatch[2]}`, lineNum);
131
- edits.push({
132
- kind: "modify",
133
- anchor,
134
- prefix: inlineBeforeMatch[3],
135
- suffix: "",
136
- lineNum,
137
- index: editIndex++,
138
- });
139
- const cursor: HashlineCursor = { kind: "before_anchor", anchor };
140
- const { payload, nextIndex } = collectPayload(lines, i + 1, lineNum, false);
141
- for (const text of payload) pushInsert(cursor, text, lineNum);
142
- i = nextIndex;
143
- continue;
144
- }
145
-
146
- const inlineAfterMatch = INLINE_AFTER_OP_RE.exec(line);
147
- if (inlineAfterMatch) {
148
- const anchor = parseLid(`${inlineAfterMatch[1]}${inlineAfterMatch[2]}`, lineNum);
149
- edits.push({
150
- kind: "modify",
151
- anchor,
152
- prefix: "",
153
- suffix: inlineAfterMatch[3],
154
- lineNum,
155
- index: editIndex++,
156
- });
157
- const cursor: HashlineCursor = { kind: "after_anchor", anchor };
158
- const { payload, nextIndex } = collectPayload(lines, i + 1, lineNum, false);
159
- for (const text of payload) pushInsert(cursor, text, lineNum);
160
- i = nextIndex;
161
- continue;
162
- }
163
-
164
125
  const insertBeforeMatch = INSERT_BEFORE_OP_RE.exec(line);
165
126
  if (insertBeforeMatch) {
166
127
  const cursor = parseInsertTarget(insertBeforeMatch[1], lineNum, "before");
@@ -24,8 +24,7 @@ export type HashlineCursor =
24
24
 
25
25
  export type HashlineEdit =
26
26
  | { kind: "insert"; cursor: HashlineCursor; text: string; lineNum: number; index: number }
27
- | { kind: "delete"; anchor: Anchor; lineNum: number; index: number; oldAssertion?: string }
28
- | { kind: "modify"; anchor: Anchor; prefix: string; suffix: string; lineNum: number; index: number };
27
+ | { kind: "delete"; anchor: Anchor; lineNum: number; index: number; oldAssertion?: string };
29
28
 
30
29
  export const hashlineEditParamsSchema = Type.Object({ input: Type.String() });
31
30
  export type HashlineParams = Static<typeof hashlineEditParamsSchema>;