diff --git a/apps/opencode-plugin/index.ts b/apps/opencode-plugin/index.ts index 74fb02bcc..40395ac54 100644 --- a/apps/opencode-plugin/index.ts +++ b/apps/opencode-plugin/index.ts @@ -185,7 +185,11 @@ export function validateEdits(existingLines: string[], edits: PlanEdit[]): strin if (!Number.isInteger(edit.end) || edit.end < edit.start) { return `end (${edit.end}) must be >= start (${edit.start})`; } - if (edit.end > lineCount) { + // On an empty file (lineCount === 0) every edit is a pure insert; + // end is semantically meaningless and applyEdits handles it via splice + // clamping. Rejecting here breaks first-call payloads where the agent + // or framework includes end (see #742). + if (edit.end > lineCount && lineCount > 0) { return `end (${edit.end}) exceeds file length (${lineCount})`; } } diff --git a/apps/opencode-plugin/submit-plan.test.ts b/apps/opencode-plugin/submit-plan.test.ts index e0eb32e2c..c137d1e19 100644 --- a/apps/opencode-plugin/submit-plan.test.ts +++ b/apps/opencode-plugin/submit-plan.test.ts @@ -53,6 +53,11 @@ describe("applyEdits", () => { expect(result).toEqual(["hello"]); }); + test("edit on empty file (start=1, end=1) — splice clamps gracefully (#742)", () => { + const result = applyEdits([], [{ start: 1, end: 1, content: "# Plan\nGoals" }]); + expect(result).toEqual(["# Plan", "Goals"]); + }); + test("content with trailing newline produces trailing empty string", () => { const result = applyEdits([], [{ start: 1, content: "line1\nline2\n" }]); expect(result).toEqual(["line1", "line2", ""]); @@ -126,6 +131,12 @@ describe("validateEdits", () => { test("passes for empty file with start=1", () => { expect(validateEdits([], [{ start: 1, content: "hello" }])).toBeNull(); }); + + test("passes for empty file with start=1 and end=1 (#742)", () => { + // Agent or framework may include end on first call; validation should + // not reject it since applyEdits handles this via splice clamping. + expect(validateEdits([], [{ start: 1, end: 1, content: "# Plan\nGoals" }])).toBeNull(); + }); }); // ── formatWithLineNumbers ──────────────────────────────────────────────────