mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-16 23:52:40 -06:00
fix(sessions): honor explicit legacy owner (#123887)
This commit is contained in:
@@ -589,7 +589,7 @@ Selects the agent whose model and credentials own ambient OpenClaw system-agent
|
||||
}
|
||||
```
|
||||
|
||||
Delegated consults with a requesting agent keep that requester as their owner. When `agentId` is absent, a sole configured agent resolves implicitly; ambient consults in a multi-agent fleet fail with an actionable error. Upgrade-only ownership lives at `agents.defaults.authInheritance.agentId` for inherited credentials and `agents.defaults.sessionStore.agentId` for unscoped rows in a fixed `session.store`.
|
||||
Delegated consults with a requesting agent keep that requester as their owner. When `agentId` is absent, a sole configured agent resolves implicitly; ambient consults in a multi-agent fleet fail with an actionable error. Upgrade-only ownership lives at `agents.defaults.authInheritance.agentId` for inherited credentials and `agents.defaults.sessionStore.agentId` for retired `main` session rows or unscoped rows in a fixed `session.store`.
|
||||
|
||||
### `agents.defaults.compaction`
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
|
||||
These migrations are best-effort and idempotent; doctor emits warnings when it leaves any legacy folders behind as backups. The Gateway/CLI also auto-migrates the legacy sessions + agent dir on startup so history/auth/models land in the per-agent path without a manual doctor run. WhatsApp auth is intentionally only migrated via `openclaw doctor`. Talk provider/provider-map normalization compares by structural equality, so key-order-only diffs no longer trigger repeat no-op `doctor --fix` changes.
|
||||
|
||||
When an explicit roster no longer contains `main`, OpenClaw migrates durable `agent:main:*` SQLite rows only if the replacement owner is unambiguous: the sole roster member or the configured fixed-store owner in `agents.defaults.sessionStore.agentId`. Conflicting canonical or alias rows are preserved during startup and reported with a Doctor hint. `openclaw doctor --fix` first imports any legacy JSON session store, then keeps the winning canonical claim and renames each losing claim to `agent:<owner>:legacy-main-conflict-<n>` in its original database. Quarantine changes only the key; the entry and full transcript remain available for inspection or archival.
|
||||
When an explicit roster no longer contains `main`, OpenClaw migrates durable `agent:main:*` SQLite rows only if the replacement owner is unambiguous: the sole roster member or the configured upgrade owner in `agents.defaults.sessionStore.agentId`. The explicit owner works for both per-agent and fixed session stores; fixed-store runtime ownership remains scoped to that physical store. Conflicting canonical or alias rows are preserved during startup and reported with a Doctor hint. `openclaw doctor --fix` first imports any legacy JSON session store, then keeps the winning canonical claim and renames each losing claim to `agent:<owner>:legacy-main-conflict-<n>` in its original database. Quarantine changes only the key; the entry and full transcript remain available for inspection or archival.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="3a. Legacy plugin manifest migrations">
|
||||
|
||||
@@ -338,9 +338,9 @@ export const CORE_FIELD_HELP: Record<string, string> = {
|
||||
"agents.defaults.authInheritance.agentId":
|
||||
"Agent whose legacy credential store remains the inheritance source after default-marker retirement. Written automatically during upgrade when the former owner was not main.",
|
||||
"agents.defaults.sessionStore":
|
||||
"Upgrade compatibility owner for a fixed legacy session store until its SQLite database records ownership.",
|
||||
"Upgrade compatibility owner for retired main-agent rows and fixed legacy session stores.",
|
||||
"agents.defaults.sessionStore.agentId":
|
||||
"Agent that owns unscoped rows in a fixed legacy session store after default-marker retirement. Written automatically during upgrade when the former owner was not main or the sole agent.",
|
||||
"Agent that owns retired main-agent rows or unscoped rows in a fixed legacy session store after default-marker retirement. Written automatically during upgrade when the former owner was not main or the sole agent.",
|
||||
"talk.agentId":
|
||||
"Agent that owns Talk sessions created without an explicit agent-scoped session key.",
|
||||
};
|
||||
|
||||
@@ -547,7 +547,7 @@ describe("legacy main session migration", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses an explicit fixed-store owner only when the multi-agent roster is unambiguous", async () => {
|
||||
it("uses an explicit migration owner when the multi-agent roster is unambiguous", async () => {
|
||||
const resolved = createFixture({
|
||||
agents: {
|
||||
ownership: "explicit",
|
||||
@@ -577,7 +577,7 @@ describe("legacy main session migration", () => {
|
||||
env: unresolved.env,
|
||||
mode: "detect",
|
||||
});
|
||||
const perAgentNotArmed = await migrateLegacyMainSessionKeys({
|
||||
const perAgentArmed = await migrateLegacyMainSessionKeys({
|
||||
cfg: perAgentPinned.cfg,
|
||||
env: perAgentPinned.env,
|
||||
mode: "detect",
|
||||
@@ -588,13 +588,40 @@ describe("legacy main session migration", () => {
|
||||
armed: false,
|
||||
outcomes: [{ kind: "not-armed", detail: "owner-unresolved" }],
|
||||
});
|
||||
expect(perAgentNotArmed).toMatchObject({
|
||||
armed: false,
|
||||
outcomes: [{ kind: "not-armed", detail: "owner-unresolved" }],
|
||||
});
|
||||
expect(perAgentArmed).toMatchObject({ armed: true, ownerAgentId: "ops" });
|
||||
expect(notArmed.warnings[0]).toContain("agents.defaults.sessionStore.agentId");
|
||||
});
|
||||
|
||||
it("uses an explicit migration owner for retired main rows in per-agent stores", async () => {
|
||||
const fixture = createFixture({
|
||||
agents: {
|
||||
ownership: "explicit",
|
||||
defaults: { sessionStore: { agentId: "ops" } },
|
||||
entries: { ops: {}, research: {} },
|
||||
},
|
||||
});
|
||||
const mainPath = databasePath(fixture.stateDir, "main");
|
||||
seedClaim({ databaseAgentId: "main", databasePath: mainPath, key: "agent:main:chat" });
|
||||
|
||||
const result = await migrateLegacyMainSessionKeys({
|
||||
cfg: fixture.cfg,
|
||||
env: fixture.env,
|
||||
mode: "automatic",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ armed: true, complete: true, ownerAgentId: "ops" });
|
||||
expect(
|
||||
readClaim({ databaseAgentId: "main", databasePath: mainPath, key: "agent:main:chat" }),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
readClaim({
|
||||
databaseAgentId: "ops",
|
||||
databasePath: databasePath(fixture.stateDir, "ops"),
|
||||
key: "agent:ops:chat",
|
||||
}),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it("keeps automatic detection non-throwing for unreadable stores and treats ENOENT as absence", async () => {
|
||||
const absent = createFixture();
|
||||
const unreadablePath = path.join(tempDirs.make("automatic-unreadable-"), "sessions.sqlite");
|
||||
|
||||
@@ -38,7 +38,6 @@ import type {
|
||||
import { resolveSessionStorePathCore } from "./paths.js";
|
||||
import { getSessionKysely } from "./session-accessor.sqlite-scope.js";
|
||||
import { resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target.js";
|
||||
import { isPerAgentSessionStoreConfig } from "./session-store-config.js";
|
||||
import {
|
||||
resolveAllAgentSessionStoreCandidateTargetsSync,
|
||||
resolveAgentSessionStoreTargetsSync,
|
||||
@@ -73,9 +72,7 @@ function resolveArmingDecision(cfg: OpenClawConfig, legacyAgentId: string): Armi
|
||||
if (sole && roster.has(normalizeAgentId(sole))) {
|
||||
return { armed: true, ownerAgentId: normalizeAgentId(sole) };
|
||||
}
|
||||
const sessionStoreOwner = !isPerAgentSessionStoreConfig(cfg.session?.store)
|
||||
? cfg.agents?.defaults?.sessionStore?.agentId?.trim()
|
||||
: undefined;
|
||||
const sessionStoreOwner = cfg.agents?.defaults?.sessionStore?.agentId?.trim();
|
||||
if (sessionStoreOwner) {
|
||||
const normalized = normalizeAgentId(sessionStoreOwner);
|
||||
if (roster.has(normalized)) {
|
||||
|
||||
@@ -328,7 +328,7 @@ export type AgentDefaultsConfig = {
|
||||
authInheritance?: {
|
||||
agentId?: string;
|
||||
};
|
||||
/** Upgrade-only owner for legacy fixed session stores until SQLite records ownership. */
|
||||
/** Upgrade-only owner for retired main-agent rows and legacy fixed session stores. */
|
||||
sessionStore?: {
|
||||
agentId?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user