feat(tui): renamed large-paste local refs to paste-N.md

- Large-paste menu now saves pastes as local://paste-N.md instead of
  local://attachment-N, giving the artifact a markdown extension and a
  clearer name.
This commit is contained in:
can1357
2026-07-24 12:25:40 +02:00
parent c780662881
commit de00e7f136
3 changed files with 20 additions and 16 deletions
+4
View File
@@ -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.
@@ -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 <attachment> 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<void> {
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);
@@ -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 "<dir>/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");
});
});