Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/desktop/e2e/harness-wrapper-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,10 @@ async function main() {
UUID_V4.test(first ?? "") &&
UUID_V4.test(second ?? "") &&
first !== second &&
// The newest launch is what a resume of this session must find.
seed === second;
// Neither launch fired a SessionStart (the fake binary has no hooks), so
// neither proved itself, and neither id may become the seed (VC-807).
seed !== first &&
seed !== second;
return { ok, detail: `first=${first} second=${second} seed=${seed}` };
});

Expand Down
11 changes: 7 additions & 4 deletions apps/desktop/e2e/interrupt-resume-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
* `sessionId: { kind: "reported" | "none" }` (opencode, codex), since
* an `argv`-tier one always has a minted seed;
* – the harness's own wrapper minted one for THAT LAUNCH (`volli session
* harness <slug> --mint`, `sessionId: { kind: "argv" }`) → check 5,
* which asserts the resume comes back with that exact id.
* harness <slug> --mint`, `sessionId: { kind: "argv" }`) and the
* launch's own SessionStart confirmed it (the fake `claude` fires one,
* as a real one does at boot) → check 5, which asserts the resume comes
* back with that exact id.
* Resume opens a new attachment on the same durable Volli Session; it does
* not mint a second Session. Ticket history names the successful reattachment
* and its user origin, while process lifecycle evidence stays in the ledger.
Expand Down Expand Up @@ -719,8 +721,9 @@ async function main() {
//
// The other half of what check 4 stopped covering. An `argv`-tier harness
// gets a fresh UUIDv4 from its own wrapper's synchronous `volli session
// harness --mint` call on every launch, written to `harness_session_id`,
// and a resume must come back with THAT id — not merely some uuid, and
// harness --mint` call on every launch, written to `harness_session_id`
// once the launch's SessionStart names it (VC-807: the fake fires one, as
// a real claude does at boot), and a resume must come back with THAT id — not merely some uuid, and
// emphatically not `VOLLI_SESSION` (reusing the PTY's own id per launch is
// the collision this path replaced). Check 3 proves the `volli session
// link` seed; this proves the minted one, which nothing else here reaches.
Expand Down
20 changes: 19 additions & 1 deletion apps/desktop/e2e/lib/fake-harness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ export async function buildFakeHarness(scratchDir, probePath, opts = {}) {
// only — see the module header), the fake then puts the pty in raw mode and
// blocks forever copying stdin verbatim into a per-session log instead of
// exiting; otherwise (every other smoke) it exits 0 right after recording.
// In interactive mode the fake `claude` also does what a real one does at
// boot: fire SessionStart naming the id it was launched with. The app
// holds a wrapper's minted id as pending until that event proves the
// launch (VC-807), so a fake that stayed silent would never be resumable
// by id. Synchronous, so it has landed before the capture loop opens.
const startupHook =
name === "claude"
? ` fake_sid=''
fake_prev=''
for arg in "$@"; do
if [ "$fake_prev" = "--session-id" ]; then fake_sid=$arg; fi
fake_prev=$arg
done
if [ -n "$fake_sid" ]; then
volli hook claude-code session.started "{\\"session_id\\":\\"$fake_sid\\"}" </dev/null >/dev/null 2>&1 || true
fi
`
: "";
const script = `#!/bin/sh
# Fake "${name}" harness for e2e kickoff / interrupt-resume probes. Records
# argv, then either exits 0 (default) or blocks copying raw stdin (interactive
Expand All @@ -110,7 +128,7 @@ probe="\${VOLLI_FAKE_HARNESS_PROBE:-${probe}}"
done
} >> "$probe"
if [ -n "$VOLLI_FAKE_HARNESS_INTERACTIVE_DIR" ] && [ -n "$VOLLI_SESSION" ]; then
stty raw -echo 2>/dev/null
${startupHook} stty raw -echo 2>/dev/null
exec cat >> "$VOLLI_FAKE_HARNESS_INTERACTIVE_DIR/$VOLLI_SESSION.stdin"
fi
exit 0
Expand Down
109 changes: 109 additions & 0 deletions apps/desktop/src/main/harness-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@ describe("ensureHarnessRuntime", () => {
* live PTY's real environment; this runs it in milliseconds, in CI.
*/
/** Drops a stand-in `volli` shim where the wrapper will look for the real one. */
function pause(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function installFakeVolli(shimPath: string, script: string): Promise<void> {
await mkdir(join(shimPath, ".."), { recursive: true });
await writeFile(shimPath, script, { mode: 0o755 });
Expand Down Expand Up @@ -881,6 +885,111 @@ describe("the generated wrapper, run", () => {
});
expect(elsewhere.config).toBe("unset");
});

/** A built-in harness's real wrapper over a fake binary, and a `volli` that logs every call. */
async function builtInRuntime(id: string): Promise<{
wrapperPath: string;
session: Record<string, string>;
}> {
const paths = await scratch();
const adapter = adapterFor(id);
const binaryPath = await fakeBinary(adapter.command);
const runtime = await ensureHarnessRuntime({
...paths,
socketPath: "/tmp/volli.sock",
adapters: [adapter],
adapterCensus: "complete",
resolveCommand: () => Promise.resolve(binaryPath),
});
const wrapperPath = runtime.wrapperPaths.get(id as HarnessId);
if (wrapperPath === undefined) throw new Error("no wrapper was written");
await installFakeVolli(paths.shimPath, FAKE_VOLLI);
return { wrapperPath, session: mintingSession({ ...runtime.env, PATH: "/nonexistent" }) };
}

/** Every call the wrappers made to `volli`, in order. */
async function volliCalls(): Promise<string[]> {
const log = await readFile(join(scratchRoot, "mint-log"), "utf8").catch(() => "");
return log.split("\n").filter(Boolean);
}

/**
* The calls once `expected` of them have landed, and a moment more for any
* straggler. The announce is detached by design, so a launch that makes one
* is waited for rather than read once.
*/
async function volliCallsSettled(expected: number): Promise<string[]> {
for (let attempt = 0; attempt < 100; attempt += 1) {
if ((await volliCalls()).length >= expected) break;
await pause(20);
}
await pause(250);
return volliCalls();
}

// VC-807. Each of these went through the wrapper and was minted for as a
// launch: the phantom id overwrote the seed of the conversation the user
// really had, and Resume session ran `claude --resume <the phantom>`.
it.each([
[["--version"]],
[["-v"]],
[["--help"]],
[["mcp", "list"]],
[["doctor"]],
[["update"]],
[["install"]],
])("asks the app for nothing when claude is run as `claude %j`", async (words) => {
const { wrapperPath, session } = await builtInRuntime("claude-code");

const run = await runWrapper(wrapperPath, words, session);
const launch = await runWrapper(wrapperPath, ["hello"], session);

expect(run.code).toBe(0);
expect(run.args).not.toContain("--session-id");
expect(run.args.slice(-words.length)).toEqual(words);
// The real launch after it still mints, and is the only call made.
expect(launch.args).toContain("--session-id");
expect(await volliCalls()).toEqual(["session harness claude-code --mint"]);
});

// VC-807. The announce for `codex --help` repointed the running harness at
// codex while keeping claude's id, so resume built `codex resume <claude id>`.
it.each([
["codex", ["--help"]],
["codex", ["login"]],
["opencode", ["--version"]],
])("announces nothing when %s is run with %j", async (id, words) => {
const { wrapperPath, session } = await builtInRuntime(id);

const run = await runWrapper(wrapperPath, words, session);
await runWrapper(wrapperPath, ["hello"], session);

expect(run.code).toBe(0);
expect(run.args.slice(-words.length)).toEqual(words);
expect(await volliCallsSettled(1)).toEqual([`session harness ${id}`]);
});

// VC-807. A `claude -p` fired by a live claude's own Bash tool inherits PATH
// and `VOLLI_SESSION`. It must not mint over the parent's seed, nor carry the
// injected hooks that would report its turns as the parent's.
it.each([
["CLAUDECODE", "1"],
["CLAUDE_CODE_ENTRYPOINT", "cli"],
])(
"runs a claude nested inside a running claude (%s set) exactly as outside Volli",
async (marker, value) => {
const { wrapperPath, session } = await builtInRuntime("claude-code");

const run = await runWrapper(wrapperPath, ["-p", "hello"], {
...session,
[marker]: value,
});

expect(run.code).toBe(0);
expect(run.args).toEqual(["-p", "hello"]);
expect(await volliCalls()).toEqual([]);
},
);
});

describe("harnessLaunchArgv", () => {
Expand Down
Loading