mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-02 21:01:51 +00:00
Core: harden matrix migration and runner wiring
This commit is contained in:
19
src/agents/pi-embedded-runner/run/attempt.imports.test.ts
Normal file
19
src/agents/pi-embedded-runner/run/attempt.imports.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const compactionFailuresImported = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../compaction-failures.js", () => {
|
||||
compactionFailuresImported();
|
||||
return {};
|
||||
});
|
||||
|
||||
describe("run attempt module wiring", () => {
|
||||
it("loads the compaction failure bridge during runner init", async () => {
|
||||
vi.resetModules();
|
||||
compactionFailuresImported.mockClear();
|
||||
|
||||
await import("./attempt.js");
|
||||
|
||||
expect(compactionFailuresImported).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -142,6 +142,7 @@ import {
|
||||
selectCompactionTimeoutSnapshot,
|
||||
shouldFlagCompactionTimeout,
|
||||
} from "./compaction-timeout.js";
|
||||
import "../compaction-failures.js";
|
||||
import { pruneProcessedHistoryImages } from "./history-image-prune.js";
|
||||
import { detectAndLoadPromptImages } from "./images.js";
|
||||
import type { EmbeddedRunAttemptParams, EmbeddedRunAttemptResult } from "./types.js";
|
||||
|
||||
33
src/auto-reply/reply/session-target-resolution.test.ts
Normal file
33
src/auto-reply/reply/session-target-resolution.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
const listAcpSessionEntriesMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../acp/runtime/session-meta.js", () => ({
|
||||
listAcpSessionEntries: listAcpSessionEntriesMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../gateway/call.js", () => ({
|
||||
callGateway: vi.fn(async () => {
|
||||
throw new Error("gateway unavailable");
|
||||
}),
|
||||
}));
|
||||
|
||||
import { resolveSessionKeyByReference } from "./session-target-resolution.js";
|
||||
|
||||
describe("resolveSessionKeyByReference", () => {
|
||||
it("matches ACP fallback session references case-insensitively", async () => {
|
||||
listAcpSessionEntriesMock.mockResolvedValueOnce([
|
||||
{
|
||||
sessionKey: "user:alice:acp:982649c1-1234-4abc-8123-0123456789ab",
|
||||
},
|
||||
]);
|
||||
|
||||
const resolved = await resolveSessionKeyByReference({
|
||||
cfg: {} as OpenClawConfig,
|
||||
token: "acp:982649C1-1234-4ABC-8123-0123456789AB",
|
||||
});
|
||||
|
||||
expect(resolved).toBe("user:alice:acp:982649c1-1234-4abc-8123-0123456789ab");
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,23 @@
|
||||
import { listAcpSessionEntries } from "../../acp/runtime/session-meta.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { SESSION_ID_RE } from "../../sessions/session-id.js";
|
||||
import { normalizeSessionId, SESSION_ID_RE } from "../../sessions/session-id.js";
|
||||
|
||||
function resolveAcpSessionKeySuffixToken(token: string): string | null {
|
||||
const trimmed = token.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (SESSION_ID_RE.test(trimmed)) {
|
||||
return trimmed;
|
||||
const normalizedSessionId = normalizeSessionId(trimmed);
|
||||
if (normalizedSessionId) {
|
||||
return normalizedSessionId;
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
if (!lower.startsWith("acp:")) {
|
||||
return null;
|
||||
}
|
||||
const suffix = trimmed.slice("acp:".length).trim();
|
||||
return SESSION_ID_RE.test(suffix) ? suffix : null;
|
||||
return normalizeSessionId(suffix);
|
||||
}
|
||||
|
||||
async function resolveSessionKeyViaGateway(token: string): Promise<string | null> {
|
||||
|
||||
30
src/infra/matrix-env-vars.ts
Normal file
30
src/infra/matrix-env-vars.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { normalizeAccountId } from "../routing/session-key.js";
|
||||
|
||||
export function resolveMatrixEnvAccountToken(accountId: string): string {
|
||||
return Array.from(normalizeAccountId(accountId))
|
||||
.map((char) =>
|
||||
/[a-z0-9]/.test(char)
|
||||
? char.toUpperCase()
|
||||
: `_X${char.codePointAt(0)?.toString(16).toUpperCase() ?? "00"}_`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
export function getMatrixScopedEnvVarNames(accountId: string): {
|
||||
homeserver: string;
|
||||
userId: string;
|
||||
accessToken: string;
|
||||
password: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
} {
|
||||
const token = resolveMatrixEnvAccountToken(accountId);
|
||||
return {
|
||||
homeserver: `MATRIX_${token}_HOMESERVER`,
|
||||
userId: `MATRIX_${token}_USER_ID`,
|
||||
accessToken: `MATRIX_${token}_ACCESS_TOKEN`,
|
||||
password: `MATRIX_${token}_PASSWORD`,
|
||||
deviceId: `MATRIX_${token}_DEVICE_ID`,
|
||||
deviceName: `MATRIX_${token}_DEVICE_NAME`,
|
||||
};
|
||||
}
|
||||
@@ -136,4 +136,34 @@ describe("resolveMatrixMigrationAccountTarget", () => {
|
||||
expect(target).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the same scoped env token encoding as runtime account auth", async () => {
|
||||
await withTempHome(async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
matrix: {
|
||||
accounts: {
|
||||
"ops-prod": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const env = {
|
||||
MATRIX_OPS_X2D_PROD_HOMESERVER: "https://matrix.example.org",
|
||||
MATRIX_OPS_X2D_PROD_USER_ID: "@ops-prod:example.org",
|
||||
MATRIX_OPS_X2D_PROD_ACCESS_TOKEN: "tok-ops-prod",
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
const target = resolveMatrixMigrationAccountTarget({
|
||||
cfg,
|
||||
env,
|
||||
accountId: "ops-prod",
|
||||
});
|
||||
|
||||
expect(target).not.toBeNull();
|
||||
expect(target?.homeserver).toBe("https://matrix.example.org");
|
||||
expect(target?.userId).toBe("@ops-prod:example.org");
|
||||
expect(target?.accessToken).toBe("tok-ops-prod");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
resolveMatrixChannelConfig,
|
||||
resolveMatrixDefaultOrOnlyAccountId,
|
||||
} from "./matrix-account-selection.js";
|
||||
import { getMatrixScopedEnvVarNames } from "./matrix-env-vars.js";
|
||||
import {
|
||||
resolveMatrixAccountStorageRoot,
|
||||
resolveMatrixCredentialsPath,
|
||||
@@ -41,13 +42,6 @@ function clean(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function resolveMatrixEnvAccountToken(accountId: string): string {
|
||||
return normalizeAccountId(accountId)
|
||||
.replace(/[^a-z0-9]+/gi, "_")
|
||||
.replace(/^_+|_+$/g, "")
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
function resolveScopedMatrixEnvConfig(
|
||||
accountId: string,
|
||||
env: NodeJS.ProcessEnv,
|
||||
@@ -56,11 +50,11 @@ function resolveScopedMatrixEnvConfig(
|
||||
userId: string;
|
||||
accessToken: string;
|
||||
} {
|
||||
const token = resolveMatrixEnvAccountToken(accountId);
|
||||
const keys = getMatrixScopedEnvVarNames(accountId);
|
||||
return {
|
||||
homeserver: clean(env[`MATRIX_${token}_HOMESERVER`]),
|
||||
userId: clean(env[`MATRIX_${token}_USER_ID`]),
|
||||
accessToken: clean(env[`MATRIX_${token}_ACCESS_TOKEN`]),
|
||||
homeserver: clean(env[keys.homeserver]),
|
||||
userId: clean(env[keys.userId]),
|
||||
accessToken: clean(env[keys.accessToken]),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,8 @@ export const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[
|
||||
export function looksLikeSessionId(value: string): boolean {
|
||||
return SESSION_ID_RE.test(value.trim());
|
||||
}
|
||||
|
||||
export function normalizeSessionId(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
return SESSION_ID_RE.test(trimmed) ? trimmed.toLowerCase() : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user