mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
fix(agents): preserve survivor-owned databases offline (#129085)
Prepare surviving agent-directory ownership before filtering journaled SQLite paths, addressing the post-merge review of #129017. Refs #129014
This commit is contained in:
committed by
GitHub
parent
876f2de46f
commit
203024eb33
@@ -5,12 +5,16 @@ import {
|
||||
formatSharedAuthStoreOwnerDeleteError,
|
||||
isSharedAuthStoreOwner,
|
||||
} from "../agents/agent-delete-safety.js";
|
||||
import { normalizeAgentDirRegistryPath } from "../agents/agent-dir-registry.js";
|
||||
import {
|
||||
isPathOwnedByAnotherRegisteredAgent,
|
||||
normalizeAgentDirRegistryPath,
|
||||
} from "../agents/agent-dir-registry.js";
|
||||
import {
|
||||
beginAgentDeletion,
|
||||
claimCompletedAgentDeletion,
|
||||
} from "../agents/agent-lifecycle-registry.js";
|
||||
import {
|
||||
listAgentIds,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
tryResolveSoleAgentId,
|
||||
@@ -358,6 +362,11 @@ export async function agentsDeleteCommand(
|
||||
}
|
||||
}
|
||||
if (deleteFiles) {
|
||||
// Directory ownership is process-local; prepare every survivor before filtering journal paths,
|
||||
// or a deleted agent's database nested beneath a survivor's agentDir could be trashed.
|
||||
for (const survivingAgentId of listAgentIds(result.config)) {
|
||||
resolveAgentDir(result.config, survivingAgentId);
|
||||
}
|
||||
const canonicalAgentDir = normalizeAgentDirRegistryPath(agentDir);
|
||||
const survivingDatabasePaths = new Set(
|
||||
listOpenClawRegisteredAgentDatabases()
|
||||
@@ -370,6 +379,7 @@ export async function agentsDeleteCommand(
|
||||
return (
|
||||
!isPathInside(canonicalAgentDir, canonicalPath) &&
|
||||
!survivingDatabasePaths.has(canonicalPath) &&
|
||||
!isPathOwnedByAnotherRegisteredAgent({ agentId, pathname }) &&
|
||||
findOverlappingWorkspaceAgentIds(result.config, agentId, canonicalPath).length === 0
|
||||
);
|
||||
});
|
||||
|
||||
@@ -675,10 +675,15 @@ describe("agents delete command", () => {
|
||||
|
||||
it("deregisters the agent database after offline deletion", async () => {
|
||||
await withStateDirEnv("openclaw-agents-delete-registry-", async ({ tempRoot, stateDir }) => {
|
||||
const mainAgentDir = path.join(tempRoot, "main-agent");
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", workspace: path.join(stateDir, "workspace-main") },
|
||||
{
|
||||
id: "main",
|
||||
agentDir: mainAgentDir,
|
||||
workspace: path.join(stateDir, "workspace-main"),
|
||||
},
|
||||
{ id: "ops", workspace: path.join(stateDir, "workspace-ops") },
|
||||
],
|
||||
},
|
||||
@@ -687,8 +692,10 @@ describe("agents delete command", () => {
|
||||
const databasePath = path.join(stateDir, "agents", "ops", "agent", "openclaw-agent.sqlite");
|
||||
const externalDatabaseDir = path.join(tempRoot, "external-databases");
|
||||
await fs.mkdir(externalDatabaseDir);
|
||||
await fs.mkdir(mainAgentDir);
|
||||
const externalDatabasePath = path.join(externalDatabaseDir, "ops.sqlite");
|
||||
const sharedDatabasePath = path.join(externalDatabaseDir, "shared.sqlite");
|
||||
const survivorOwnedDatabasePath = path.join(mainAgentDir, "ops.sqlite");
|
||||
const externalDatabasePaths = [
|
||||
externalDatabasePath,
|
||||
`${externalDatabasePath}-wal`,
|
||||
@@ -696,14 +703,16 @@ describe("agents delete command", () => {
|
||||
`${externalDatabasePath}-journal`,
|
||||
];
|
||||
await Promise.all(
|
||||
[...externalDatabasePaths, sharedDatabasePath].map((sqlitePath) =>
|
||||
fs.writeFile(sqlitePath, ""),
|
||||
[...externalDatabasePaths, sharedDatabasePath, survivorOwnedDatabasePath].map(
|
||||
(sqlitePath) => fs.writeFile(sqlitePath, ""),
|
||||
),
|
||||
);
|
||||
const canonicalExternalDatabaseDir = await fs.realpath(externalDatabaseDir);
|
||||
const canonicalMainAgentDir = await fs.realpath(mainAgentDir);
|
||||
registerOpenClawAgentDatabase({ agentId: "ops", path: databasePath });
|
||||
registerOpenClawAgentDatabase({ agentId: "ops", path: externalDatabasePath });
|
||||
registerOpenClawAgentDatabase({ agentId: "ops", path: sharedDatabasePath });
|
||||
registerOpenClawAgentDatabase({ agentId: "ops", path: survivorOwnedDatabasePath });
|
||||
registerOpenClawAgentDatabase({ agentId: "main", path: sharedDatabasePath });
|
||||
recordAgentProvenance("ops", { createdVia: "operator" });
|
||||
recordAgentProvenance("child", { createdVia: "agent", creatorAgentId: "ops" });
|
||||
@@ -726,6 +735,11 @@ describe("agents delete command", () => {
|
||||
path.join(canonicalExternalDatabaseDir, path.basename(sharedDatabasePath)),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(fsSafeMocks.movePathToTrash).not.toHaveBeenCalledWith(
|
||||
path.join(canonicalMainAgentDir, path.basename(survivorOwnedDatabasePath)),
|
||||
expect.anything(),
|
||||
);
|
||||
expect((await fs.stat(survivorOwnedDatabasePath)).isFile()).toBe(true);
|
||||
const registeredDatabases = listOpenClawRegisteredAgentDatabases();
|
||||
expect(registeredDatabases.map((entry) => entry.agentId)).not.toContain("ops");
|
||||
expect(registeredDatabases).toEqual(
|
||||
|
||||
Reference in New Issue
Block a user