From 08520edc3537239d4ee35ef693d6a7a232b272e0 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Wed, 23 Sep 2026 14:59:24 -0700 Subject: [PATCH] aesel: ac history hands the launcher its directory as the bare word it expects --- easel/package.json | 2 +- easel/src/history-cli.mjs | 5 ++++- easel/test/history-cli.test.mjs | 31 +++++++++++++++++++++++++++++++ easel/test/layout.test.mjs | 5 +++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 easel/test/history-cli.test.mjs diff --git a/easel/package.json b/easel/package.json index 688be02d13..290e4938bf 100644 --- a/easel/package.json +++ b/easel/package.json @@ -1,6 +1,6 @@ { "name": "aesel", - "version": "0.7.19", + "version": "0.7.20", "private": true, "type": "module", "scripts": { diff --git a/easel/src/history-cli.mjs b/easel/src/history-cli.mjs index fb9419ab09..ec6727a9ca 100644 --- a/easel/src/history-cli.mjs +++ b/easel/src/history-cli.mjs @@ -50,12 +50,15 @@ export function sessions(root = defaultRoot()) { .slice(0, LIMIT); } +// The launcher takes the directory as its one bare word, so it goes last, +// after the options; `--cwd` is the launcher's own word to the interface. export function launchArguments(meta) { const args = []; if (meta.pro) args.push("pro"); if (meta.private) args.push("--private"); - args.push("--cwd", meta.cwd, "--backend", meta.engineEvent.engine, "--resume", meta.engineEvent.thread); + args.push("--backend", meta.engineEvent.engine, "--resume", meta.engineEvent.thread); if (meta.engineEvent.model) args.push("--model", meta.engineEvent.model); + args.push(meta.cwd); return args; } diff --git a/easel/test/history-cli.test.mjs b/easel/test/history-cli.test.mjs new file mode 100644 index 0000000000..6ec1a6e66d --- /dev/null +++ b/easel/test/history-cli.test.mjs @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync, realpathSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import test from "node:test"; +import { launchArguments, sessions } from "../src/history-cli.mjs"; +import { Transcript } from "../src/transcript.mjs"; + +const EASEL = join(dirname(fileURLToPath(import.meta.url)), "..", "bin", "easel"); + +test("a resumed session is launched the way the launcher reads: options, then the directory", (context) => { + const root = mkdtempSync(join(tmpdir(), "history-")); + context.after(() => rmSync(root, { recursive: true, force: true })); + const transcript = new Transcript({ sessionId: "s1", root }); + transcript.meta({ cwd: root, pro: true, subject: "the diff" }); + transcript.event("engine", { status: "started", engine: "claude", model: "claude-sonnet-5", thread: "11111111-2222-4333-8444-555555555555" }); + transcript.event("engine", { status: "restarted", engine: "codex", model: "gpt-6-astra", thread: "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee" }); + transcript.close(); + const [meta] = sessions(root); + assert.equal(meta.session_id, "s1"); + const args = launchArguments(meta); + assert.deepEqual(args, ["pro", "--backend", "codex", "--resume", "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee", "--model", "gpt-6-astra", root], "the engine it ended on, the directory last"); + // The launcher itself accepts exactly this shape. + const dry = execFileSync(EASEL, args, { encoding: "utf8", env: { ...process.env, EASEL_DRY_RUN: "1" } }); + assert.match(dry, /pro=on/); + assert.match(dry, /backend=codex/); + assert.match(dry, /resume=yes/); + assert.ok(dry.includes(`directory=${realpathSync(root)}`), "the directory reaches the launcher"); +}); diff --git a/easel/test/layout.test.mjs b/easel/test/layout.test.mjs index 6c57af3e98..ccd555d657 100644 --- a/easel/test/layout.test.mjs +++ b/easel/test/layout.test.mjs @@ -57,6 +57,11 @@ test("set writes one key to the override and reset takes it back", (context) => test("bake writes the shape into the package and commits that one file", async (context) => { const { root, file } = scratch(context); + // The scratch repo commits unsigned: a signing key, or a gpg agent another + // process is holding, is not what this test is about. + const unsigned = { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "commit.gpgsign", GIT_CONFIG_VALUE_0: "false" }; + for (const [key, value] of Object.entries(unsigned)) process.env[key] = value; + context.after(() => { for (const key of Object.keys(unsigned)) delete process.env[key]; }); execFileSync("git", ["-C", root, "init", "-q"]); execFileSync("git", ["-C", root, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-q", "--allow-empty", "-m", "start"]); writeFileSync(join(root, "stray.txt"), "not mine to commit"); -- 2.51.2