Keep timeline history duplicate-free across daemon upgrades (#2789)

* fix(app): keep timeline pagination duplicate-free

Older daemons page projected history by canonical sequence ranges, so one projected block can appear in adjacent responses. Accept each block on the page containing its projection anchor while still advancing across empty projected pages.\n\nProjected siblings can share a provider message ID, so render identity also includes the timeline cursor instead of collapsing distinct blocks onto one layout row.

* fix(e2e): preserve isolated daemon launch contract

Keep the source-daemon spawn path recognizable to the repository launch-contract check while selecting the built supervisor only for published package fixtures.

* fix(app): preserve stable timeline item identity
This commit is contained in:
Mohamed Boudra
2026-08-02 12:21:47 +02:00
committed by GitHub
parent 0a106dd35f
commit 26bdb1d171
9 changed files with 407 additions and 44 deletions
@@ -1,5 +1,11 @@
import { once } from "node:events";
import { spawn, execSync, type ChildProcess } from "node:child_process";
import {
spawn,
execFileSync,
execSync,
type ChildProcess,
type SpawnOptions,
} from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import net from "node:net";
import { tmpdir } from "node:os";
@@ -23,6 +29,7 @@ export interface IsolatedHostDaemonOptions {
};
paseoHome?: string;
preserveHome?: boolean;
publishedVersion?: string;
}
async function getAvailablePort(): Promise<number> {
@@ -100,6 +107,31 @@ export async function startIsolatedHostDaemon(
const paseoHome =
options.paseoHome ?? (await mkdtemp(path.join(tmpdir(), "paseo-e2e-secondary-host-")));
let publishedPackageRoot: string | null = null;
if (options.publishedVersion) {
publishedPackageRoot = await mkdtemp(path.join(tmpdir(), "paseo-e2e-published-server-"));
await writeFile(
path.join(publishedPackageRoot, "package.json"),
`${JSON.stringify({ private: true })}\n`,
);
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
try {
execFileSync(
npmCommand,
[
"install",
"--no-audit",
"--no-fund",
"--no-package-lock",
`@getpaseo/server@${options.publishedVersion}`,
],
{ cwd: publishedPackageRoot, stdio: "ignore" },
);
} catch (error) {
await rm(publishedPackageRoot, { recursive: true, force: true });
throw error;
}
}
if (options.mutableRelay) {
const endpoint =
options.mutableRelay.endpoint ??
@@ -120,10 +152,12 @@ export async function startIsolatedHostDaemon(
})}\n`,
);
}
const serverDir = path.resolve(__dirname, "../../../../server");
const serverDir = publishedPackageRoot
? path.join(publishedPackageRoot, "node_modules", "@getpaseo", "server")
: path.resolve(__dirname, "../../../../server");
const tsxBin = execSync("which tsx").toString().trim();
const spawnDaemon = async (): Promise<ChildProcess> => {
const child = spawn(tsxBin, ["scripts/supervisor-entrypoint.ts", "--dev"], {
const spawnOptions: SpawnOptions = {
cwd: serverDir,
env: withDisabledE2ESpeechEnv({
...process.env,
@@ -138,7 +172,10 @@ export async function startIsolatedHostDaemon(
}),
stdio: ["ignore", "ignore", "pipe"],
detached: false,
});
};
const child = publishedPackageRoot
? spawn(process.execPath, ["dist/scripts/supervisor-entrypoint.js"], spawnOptions)
: spawn(tsxBin, ["scripts/supervisor-entrypoint.ts", "--dev"], spawnOptions);
let stderr = "";
child.stderr?.on("data", (chunk: Buffer) => {
@@ -165,6 +202,9 @@ export async function startIsolatedHostDaemon(
if (!options.preserveHome) {
await rm(paseoHome, { recursive: true, force: true });
}
if (publishedPackageRoot) {
await rm(publishedPackageRoot, { recursive: true, force: true });
}
throw error;
}
let closed = false;
@@ -186,6 +226,9 @@ export async function startIsolatedHostDaemon(
if (!options.preserveHome) {
await rm(paseoHome, { recursive: true, force: true });
}
if (publishedPackageRoot) {
await rm(publishedPackageRoot, { recursive: true, force: true });
}
},
};
}