diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 11d319708..cde118b9b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Large pastes saved via the large-paste menu now insert `local://paste-N.md` references (previously `local://attachment-N`), so the saved paste carries a markdown extension and a clearer name. + ### Fixed - Fixed `todo` calls that omit `op` hard-failing validation ("op must be operation to apply (was missing)"): the tool now validates leniently and infers the op for unambiguous payloads (`list` → `init`, `phase`+`items` → `append`, bare `items` on an empty list → `init`); `op` stays required in the schema, and ambiguous op-less calls surface the schema error as a retryable tool error. diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index 484128434..ab7881fa2 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -179,9 +179,9 @@ export class InputController { // (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See // #detectLeftDoubleTap. #leftTapCount = 0; - // Sequential index for `local://attachment-N` references created by large-paste and - // pasted-file attachments. Seeded from 0 and bumped past existing attachment files. - #attachmentCounter = 0; + // Sequential index for `local://paste-N.md` references created by the large-paste + // flow. Seeded from 0 and bumped past existing paste files. + #pasteCounter = 0; #showTinyTitleDownloadProgress(modelKey: string): void { if (!isTinyTitleLocalModelKey(modelKey)) return; @@ -1711,7 +1711,7 @@ export class InputController { `Pasted ${lineCount} lines`, [ { label: WRAPPED_BLOCK, description: "Wrap the text in tags, collapsed to a marker" }, - { label: LOCAL_FILE, description: "Save the text to a local://attachment file" }, + { label: LOCAL_FILE, description: "Save the text to a local://paste file" }, { label: INLINE, description: "Collapse the text to an inline paste marker" }, ], { helpText: "Esc to paste inline" }, @@ -1740,7 +1740,7 @@ export class InputController { } /** - * Save a large paste to the session's `local://` store and insert a clean `local://attachment-N` + * Save a large paste to the session's `local://` store and insert a clean `local://paste-N.md` * reference into the editor so the agent can `read` it on demand — instead of inlining the text or * leaking a raw temp path. Falls back to an inline paste marker when the write fails, so the * content is never lost. @@ -1748,7 +1748,7 @@ export class InputController { async #attachPasteAsFile(text: string, lineCount: number): Promise { try { // Mirror the exact mapping the read tool's local:// resolver uses so a later - // `read local://attachment-N` lands on the file written here. + // `read local://paste-N.md` lands on the file written here. const localRoot = resolveLocalRoot({ getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(), getSessionId: () => this.ctx.sessionManager.getSessionId(), @@ -1756,8 +1756,8 @@ export class InputController { let name: string; let filePath: string; do { - this.#attachmentCounter++; - name = `attachment-${this.#attachmentCounter}`; + this.#pasteCounter++; + name = `paste-${this.#pasteCounter}.md`; filePath = path.join(localRoot, name); } while (await Bun.file(filePath).exists()); await Bun.write(filePath, text); diff --git a/packages/coding-agent/test/input-controller-large-paste.test.ts b/packages/coding-agent/test/input-controller-large-paste.test.ts index 683a612ce..77b89e5b8 100644 --- a/packages/coding-agent/test/input-controller-large-paste.test.ts +++ b/packages/coding-agent/test/input-controller-large-paste.test.ts @@ -126,28 +126,28 @@ describe("InputController.presentLargePasteMenu file attachment", () => { dir = undefined; }); - it("saves the paste to local:// and inserts a clean local://attachment reference", async () => { + it("saves the paste to local:// and inserts a clean local://paste reference", async () => { dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-paste-test-")); const { controller, spies } = createContext({ choice: "Attach as local file", artifactsDir: dir }); await controller.presentLargePasteMenu("line one\nline two", 2); - expect(spies.insertText).toHaveBeenCalledWith("local://attachment-1 "); + expect(spies.insertText).toHaveBeenCalledWith("local://paste-1.md "); expect(spies.insertPaste).not.toHaveBeenCalled(); // resolveLocalRoot maps an artifacts dir to "/local"; the reference resolves there. - const saved = await Bun.file(path.join(dir, "local", "attachment-1")).text(); + const saved = await Bun.file(path.join(dir, "local", "paste-1.md")).text(); expect(saved).toBe("line one\nline two"); }); - it("does not overwrite an existing attachment file", async () => { + it("does not overwrite an existing paste file", async () => { dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-paste-test-")); - await Bun.write(path.join(dir, "local", "attachment-1"), "previous"); + await Bun.write(path.join(dir, "local", "paste-1.md"), "previous"); const { controller, spies } = createContext({ choice: "Attach as local file", artifactsDir: dir }); await controller.presentLargePasteMenu("fresh", 1); - expect(spies.insertText).toHaveBeenCalledWith("local://attachment-2 "); - expect(await Bun.file(path.join(dir, "local", "attachment-1")).text()).toBe("previous"); - expect(await Bun.file(path.join(dir, "local", "attachment-2")).text()).toBe("fresh"); + expect(spies.insertText).toHaveBeenCalledWith("local://paste-2.md "); + expect(await Bun.file(path.join(dir, "local", "paste-1.md")).text()).toBe("previous"); + expect(await Bun.file(path.join(dir, "local", "paste-2.md")).text()).toBe("fresh"); }); });