fix(cli): preserve update lifecycle config path

This commit is contained in:
i-trytoohard 2026-05-21 07:49:41 +05:30
parent bc7f23a0de
commit 968f0c388f
2 changed files with 39 additions and 12 deletions

View File

@ -600,7 +600,10 @@ describe("update command", () => {
expect(mockSpawn.mock.calls[0]).toEqual([
"ao",
["stop", "--yes"],
expect.objectContaining({ stdio: "inherit" }),
expect.objectContaining({
stdio: "inherit",
env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }),
}),
]);
expect(mockSpawn.mock.calls[1][0]).toBe("pnpm");
expect(mockSpawn.mock.calls[1][1]).toEqual(["add", "-g", "@aoagents/ao@latest"]);
@ -609,7 +612,10 @@ describe("update command", () => {
expect(mockSpawn.mock.calls[3]).toEqual([
"ao",
["start", "my-app", "--restore"],
expect.objectContaining({ stdio: "inherit" }),
expect.objectContaining({
stdio: "inherit",
env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }),
}),
]);
const stopOrder = mockSpawn.mock.invocationCallOrder[0];
@ -662,6 +668,11 @@ describe("update command", () => {
expect(mockSpawn.mock.calls[0][0]).toBe("ao");
expect(mockSpawn.mock.calls[0][1]).toEqual(["stop", "--yes"]);
expect(mockSpawn.mock.calls[0][2]).toEqual(
expect.objectContaining({
env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }),
}),
);
expect(mockSpawn.mock.calls[1][0]).toBe("pnpm");
expect(mockSpawn.mock.calls[2][0]).toBe("ao");
expect(mockSpawn.mock.calls[2][1]).toEqual(["--version"]);
@ -683,7 +694,10 @@ describe("update command", () => {
expect(mockSpawn.mock.calls.at(-1)).toEqual([
"ao",
["start", "my-app", "--no-restore"],
expect.objectContaining({ stdio: "inherit" }),
expect.objectContaining({
stdio: "inherit",
env: expect.objectContaining({ AO_CONFIG_PATH: "/tmp/test-global-config.yaml" }),
}),
]);
});
@ -705,7 +719,10 @@ describe("update command", () => {
expect(mockSpawn.mock.calls[0][0]).toBe("ao");
expect(mockSpawn.mock.calls[0][1]).toEqual(["stop", "--yes"]);
expect(mockSpawn.mock.calls.some((call) => call[0] === "pnpm")).toBe(false);
const stderr = vi.mocked(console.error).mock.calls.map((c) => String(c[0])).join("\n");
const stderr = vi
.mocked(console.error)
.mock.calls.map((c) => String(c[0]))
.join("\n");
expect(stderr).toContain("AO still appears to be running after `ao stop --yes`");
});

View File

@ -147,12 +147,14 @@ async function handleCheck(): Promise<void> {
*/
interface UpdateLifecyclePlan {
runningBeforeUpdate: boolean;
configPath?: string;
primaryProjectId?: string;
activeSessions: Session[];
}
async function getUpdateLifecyclePlan(): Promise<UpdateLifecyclePlan> {
let sessions: Session[];
let configPath: string | undefined;
let primaryProjectId: string | undefined;
let runningBeforeUpdate = false;
try {
@ -168,6 +170,7 @@ async function getUpdateLifecyclePlan(): Promise<UpdateLifecyclePlan> {
const running = await getRunning();
if (running && running.projects.length > 0) {
runningBeforeUpdate = true;
configPath = running.configPath;
primaryProjectId = running.projects[0];
// running.configPath could be local-wrapped (a project's
// agent-orchestrator.yaml) OR the canonical global path. loadConfig
@ -184,15 +187,16 @@ async function getUpdateLifecyclePlan(): Promise<UpdateLifecyclePlan> {
// sessions to `killed`, so terminal statuses don't block the update.
const globalPath = getGlobalConfigPath();
if (!existsSync(globalPath)) {
return { runningBeforeUpdate, primaryProjectId, activeSessions: [] };
return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
}
const globalConfig = loadGlobalConfig(globalPath);
if (!globalConfig || Object.keys(globalConfig.projects).length === 0) {
return { runningBeforeUpdate, primaryProjectId, activeSessions: [] };
return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
}
if (!isCanonicalGlobalConfigPath(globalPath)) {
return { runningBeforeUpdate, primaryProjectId, activeSessions: [] };
return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
}
configPath = globalPath;
const config = loadConfig(globalPath);
primaryProjectId = Object.keys(config.projects)[0];
const sm = await getSessionManager(config);
@ -204,11 +208,11 @@ async function getUpdateLifecyclePlan(): Promise<UpdateLifecyclePlan> {
console.error(
chalk.yellow("⚠ Could not check for active sessions before updating. Proceeding anyway."),
);
return { runningBeforeUpdate, primaryProjectId, activeSessions: [] };
return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
}
const active = sessions.filter((s) => ACTIVE_SESSION_STATUSES.has(s.status));
return { runningBeforeUpdate, primaryProjectId, activeSessions: active };
return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: active };
}
async function pauseAoForUpdate(plan: UpdateLifecyclePlan): Promise<boolean> {
@ -232,7 +236,9 @@ async function pauseAoForUpdate(plan: UpdateLifecyclePlan): Promise<boolean> {
console.log(chalk.dim("\nAO is running; it will be restarted after the update."));
}
const stopExit = await runAoLifecycleCommand(["stop", "--yes"]);
const stopExit = await runAoLifecycleCommand(["stop", "--yes"], {
configPath: plan.configPath,
});
if (stopExit !== 0) {
recordActivityEvent({
source: "cli",
@ -288,7 +294,7 @@ async function restartAoAfterUpdate(
args.push(opts.restore ? "--restore" : "--no-restore");
console.log(chalk.dim(`\nRestarting AO: ao ${args.join(" ")}`));
const exitCode = await runAoLifecycleCommand(args);
const exitCode = await runAoLifecycleCommand(args, { configPath: plan.configPath });
if (exitCode !== 0) {
recordActivityEvent({
source: "cli",
@ -307,12 +313,16 @@ async function restartAoAfterUpdate(
}
}
function runAoLifecycleCommand(args: string[]): Promise<number> {
function runAoLifecycleCommand(
args: string[],
opts: { configPath?: string } = {},
): Promise<number> {
return new Promise<number>((resolveExit) => {
const child = spawn("ao", args, {
stdio: "inherit",
shell: isWindows(),
windowsHide: true,
env: opts.configPath ? { ...process.env, AO_CONFIG_PATH: opts.configPath } : process.env,
});
child.on("error", (error) => {
console.error(chalk.yellow(`Could not run ao ${args.join(" ")}: ${error.message}`));