Preserve remote process identity in legacy adapter dispatch
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
311f71954e
commit
bad9d3f9d2
|
|
@ -3,7 +3,10 @@ import { eq } from "drizzle-orm";
|
|||
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { agents, companies, createDb, heartbeatRuns, startEmbeddedPostgresTestDatabase, type Db } from "@paperclipai/db";
|
||||
import * as processes from "../services/hot-restart.js";
|
||||
import { persistHeartbeatRunProcessMetadata } from "../services/heartbeat.js";
|
||||
import * as adapters from "../adapters/index.js";
|
||||
import * as orchestration from "../services/environment-run-orchestrator.js";
|
||||
import * as executionTargets from "@paperclipai/adapter-utils/execution-target";
|
||||
import { heartbeatService, persistHeartbeatRunProcessMetadata } from "../services/heartbeat.js";
|
||||
|
||||
describe("heartbeat process identity persistence", () => {
|
||||
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
|
||||
|
|
@ -12,7 +15,7 @@ describe("heartbeat process identity persistence", () => {
|
|||
beforeAll(async () => {
|
||||
database = await startEmbeddedPostgresTestDatabase("paperclip-process-metadata-");
|
||||
db = createDb(database.connectionString);
|
||||
await db.insert(companies).values({ id: companyId, name: "Process identity" });
|
||||
await db.insert(companies).values({ id: companyId, name: "Process identity", defaultResponsibleUserId: "responsible-user" });
|
||||
await db.insert(agents).values({ id: agentId, companyId, name: "Runner", role: "engineer", status: "idle", adapterType: "codex_local" });
|
||||
}, 60_000);
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
|
@ -23,6 +26,46 @@ describe("heartbeat process identity persistence", () => {
|
|||
return run!;
|
||||
}
|
||||
|
||||
it("passes remote process identity through the legacy adapter spawn callback", async () => {
|
||||
const remoteStart = "2026-09-09T23:19:43.123Z";
|
||||
const originalOrchestrator = orchestration.environmentRunOrchestrator;
|
||||
vi.spyOn(orchestration, "environmentRunOrchestrator").mockImplementation((...args) => {
|
||||
const actual = originalOrchestrator(...args);
|
||||
return { ...actual, realizeForRun: async (input) => ({
|
||||
...await actual.realizeForRun(input),
|
||||
executionTarget: { kind: "remote", transport: "ssh", remoteCwd: "/remote/task", shellCommand: "sh" } as never,
|
||||
}) };
|
||||
});
|
||||
vi.spyOn(executionTargets, "prepareGitHubOperationLaunchers").mockImplementation(async (input) => input.env);
|
||||
vi.spyOn(executionTargets, "cleanupGitHubOperationLaunchers").mockResolvedValue(undefined);
|
||||
let spawned!: () => void, release!: () => void;
|
||||
const observed = new Promise<void>((resolve) => { spawned = resolve; });
|
||||
const finish = new Promise<void>((resolve) => { release = resolve; });
|
||||
vi.spyOn(adapters, "getServerAdapter").mockReturnValue({
|
||||
supportsLocalAgentJwt: false,
|
||||
execute: async (input) => {
|
||||
expect(input.executionTarget?.kind).toBe("remote");
|
||||
await input.onSpawn?.({ pid: process.pid, processGroupId: null, startedAt: remoteStart });
|
||||
spawned();
|
||||
await finish;
|
||||
return { exitCode: 0, signal: null, timedOut: false };
|
||||
},
|
||||
} as ReturnType<typeof adapters.getServerAdapter>);
|
||||
const heartbeat = heartbeatService(db);
|
||||
try {
|
||||
const queued = await heartbeat.invoke(agentId, "on_demand", {}, "manual");
|
||||
expect(queued).not.toBeNull();
|
||||
await observed;
|
||||
const actual = await heartbeat.getRun(queued!.id);
|
||||
expect(actual?.runtimeMode).toBe("legacy");
|
||||
expect(actual?.processPid).toBe(process.pid);
|
||||
expect(actual?.processStartedAt?.toISOString()).toBe(remoteStart);
|
||||
} finally {
|
||||
release();
|
||||
await heartbeat.drainActiveRunExecutions();
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
it("uses the remote marker even when its PID exists on the host", async () => {
|
||||
const run = await running();
|
||||
const host = vi.spyOn(processes, "readProcessStartedAt");
|
||||
|
|
|
|||
|
|
@ -21703,7 +21703,7 @@ export function heartbeatService(
|
|||
? meta.processGroupId
|
||||
: null,
|
||||
startedAt: meta.startedAt,
|
||||
})));
|
||||
}, executionTarget?.kind === "remote" ? "remote" : "local")));
|
||||
},
|
||||
authToken: authToken ?? undefined,
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue