mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 01:42:59 +00:00
fix: preserve sqlite fallback routing
This commit is contained in:
@@ -57,7 +57,11 @@ public enum DeviceAuthStore {
|
||||
self.removeLegacyToken(deviceId: deviceId, role: normalizedRole)
|
||||
}
|
||||
} catch {
|
||||
self.writeLegacyStore(DeviceAuthStoreFile(version: 1, deviceId: deviceId, tokens: [normalizedRole: entry]))
|
||||
var fallback =
|
||||
self.readLegacyStore().flatMap { $0.deviceId == deviceId ? $0 : nil }
|
||||
?? DeviceAuthStoreFile(version: 1, deviceId: deviceId, tokens: [:])
|
||||
fallback.tokens[normalizedRole] = entry
|
||||
self.writeLegacyStore(fallback)
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
@@ -185,6 +185,33 @@ struct DeviceIdentityStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test("merges legacy device auth sidecar when SQLite write fails")
|
||||
func mergesLegacyDeviceAuthSidecarWhenSQLiteWriteFails() throws {
|
||||
try Self.withTempStateDir { stateDir in
|
||||
let legacyURL = Self.legacyAuthURL(stateDir: stateDir)
|
||||
try Self.writeLegacyAuthSidecar(
|
||||
legacyURL,
|
||||
deviceId: "device-1",
|
||||
token: "gateway-token",
|
||||
scopes: ["read"])
|
||||
try FileManager.default.createDirectory(
|
||||
at: Self.databaseURL(stateDir: stateDir),
|
||||
withIntermediateDirectories: true)
|
||||
|
||||
_ = DeviceAuthStore.storeToken(
|
||||
deviceId: "device-1",
|
||||
role: "operator",
|
||||
token: "operator-token",
|
||||
scopes: ["write"])
|
||||
|
||||
let data = try Data(contentsOf: legacyURL)
|
||||
let root = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
|
||||
let tokens = try #require(root["tokens"] as? [String: Any])
|
||||
#expect((tokens["gateway"] as? [String: Any])?["token"] as? String == "gateway-token")
|
||||
#expect((tokens["operator"] as? [String: Any])?["token"] as? String == "operator-token")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("drops stale legacy device auth sidecar when storing a different device")
|
||||
func dropsStaleLegacyDeviceAuthSidecarWhenReplacingDevice() throws {
|
||||
try Self.withTempStateDir { stateDir in
|
||||
|
||||
@@ -157,6 +157,41 @@ describe("SQLite session row backend", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves channel-only delivery context without a conversation peer", () => {
|
||||
const stateDir = createTempDir();
|
||||
const env = { OPENCLAW_STATE_DIR: stateDir };
|
||||
|
||||
upsertSessionEntry({
|
||||
agentId: "ops",
|
||||
env,
|
||||
sessionKey: "webchat:main",
|
||||
entry: {
|
||||
sessionId: "webchat-session",
|
||||
updatedAt: 200,
|
||||
channel: "webchat",
|
||||
deliveryContext: {
|
||||
channel: "webchat",
|
||||
},
|
||||
lastChannel: "webchat",
|
||||
},
|
||||
});
|
||||
|
||||
const loaded = getSessionEntry({
|
||||
agentId: "ops",
|
||||
env,
|
||||
sessionKey: "webchat:main",
|
||||
});
|
||||
expect(loaded).toMatchObject({
|
||||
sessionId: "webchat-session",
|
||||
channel: "webchat",
|
||||
deliveryContext: {
|
||||
channel: "webchat",
|
||||
},
|
||||
lastChannel: "webchat",
|
||||
});
|
||||
expect(loaded?.lastTo).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores hot session metadata in canonical session roots", () => {
|
||||
const stateDir = createTempDir();
|
||||
const env = { OPENCLAW_STATE_DIR: stateDir };
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
runOpenClawAgentWriteTransaction,
|
||||
} from "../../state/openclaw-agent-db.js";
|
||||
import { type OpenClawStateDatabaseOptions } from "../../state/openclaw-state-db.js";
|
||||
import { normalizeDeliveryContext } from "../../utils/delivery-context.shared.js";
|
||||
import {
|
||||
normalizeDeliveryContext,
|
||||
normalizeSessionDeliveryFields,
|
||||
} from "../../utils/delivery-context.shared.js";
|
||||
import {
|
||||
conversationIdentityFromSessionEntry,
|
||||
type ConversationIdentity,
|
||||
@@ -142,19 +145,23 @@ function clearCompatibilityRoutingShadow(
|
||||
}
|
||||
|
||||
function projectCompatibilityRoutingShadow(entry: SessionEntry): void {
|
||||
const deliveryContext = normalizeDeliveryContext(entry.deliveryContext);
|
||||
if (!deliveryContext?.channel || !deliveryContext.to) {
|
||||
const normalized = normalizeSessionDeliveryFields(entry);
|
||||
const deliveryContext = normalized.deliveryContext;
|
||||
if (!deliveryContext?.channel) {
|
||||
return;
|
||||
}
|
||||
entry.deliveryContext = deliveryContext;
|
||||
entry.lastChannel = deliveryContext.channel;
|
||||
entry.lastTo = deliveryContext.to;
|
||||
entry.lastAccountId = deliveryContext.accountId;
|
||||
entry.lastThreadId = deliveryContext.threadId;
|
||||
entry.lastChannel = normalized.lastChannel;
|
||||
entry.lastTo = normalized.lastTo;
|
||||
entry.lastAccountId = normalized.lastAccountId;
|
||||
entry.lastThreadId = normalized.lastThreadId;
|
||||
}
|
||||
|
||||
function projectTypedSessionColumns(row: SessionEntryRow): SessionEntry | null {
|
||||
const parsed = parseSessionEntry(row);
|
||||
const parsedDeliveryContext = parsed
|
||||
? normalizeSessionDeliveryFields(parsed).deliveryContext
|
||||
: undefined;
|
||||
const sessionId = optionalString(row.typed_session_id) ?? parsed?.sessionId;
|
||||
const updatedAt =
|
||||
typeof row.typed_updated_at === "number" && Number.isFinite(row.typed_updated_at)
|
||||
@@ -222,6 +229,9 @@ function projectTypedSessionColumns(row: SessionEntryRow): SessionEntry | null {
|
||||
if (nativeDirectUserId) {
|
||||
next.nativeDirectUserId = nativeDirectUserId;
|
||||
}
|
||||
if (!next.deliveryContext && parsedDeliveryContext?.channel && !parsedDeliveryContext.to) {
|
||||
next.deliveryContext = parsedDeliveryContext;
|
||||
}
|
||||
const modelProvider = optionalString(row.typed_model_provider);
|
||||
if (modelProvider) {
|
||||
next.modelProvider = modelProvider;
|
||||
|
||||
Reference in New Issue
Block a user