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:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user