mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 02:31:39 +00:00
Merge branch 'main' into fix/custodian-cancel-hint
This commit is contained in:
@@ -1986,6 +1986,7 @@ const EXACT_TOOLING_TARGETS = new Map([
|
||||
],
|
||||
],
|
||||
["scripts/run-vitest.mjs", ["run-vitest", "test-projects", "vitest-local-scheduling"]],
|
||||
["scripts/run-oxlint-shards.mjs", ["run-oxlint"]],
|
||||
["scripts/docker-e2e-rerun.mjs", ["docker-e2e-helper-cli"]],
|
||||
["scripts/openclaw-postpack.mjs", [TOOLING_VITEST_CONFIG]],
|
||||
["scripts/openclaw-npm-prepublish-verify.ts", ["test/openclaw-npm-prepublish-verify.test.ts"]],
|
||||
|
||||
@@ -148,6 +148,51 @@ describe("pristine startup state", () => {
|
||||
expect(canSkipPristineStartupStateMigrations(env)).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts canonical internal hook configuration", () => {
|
||||
const env = createFixture({
|
||||
hooks: {
|
||||
internal: {
|
||||
enabled: true,
|
||||
entries: {
|
||||
"session-memory": {
|
||||
enabled: true,
|
||||
env: { OPENCLAW_HOOK_TEST: "enabled" },
|
||||
customOption: "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(planPristineStartupStateMigrations(env)).toEqual({
|
||||
skipAllStateMigrations: true,
|
||||
skipCoreStateMigrations: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("retains migrations for legacy, external, and malformed hook configuration", () => {
|
||||
const unsafeHooks = [
|
||||
{ gmail: { account: "operator@example.com" } },
|
||||
{ internal: { installs: { "session-memory": { source: "bundled" } } } },
|
||||
{ internal: { handlers: [] } },
|
||||
{ internal: { load: { extraDirs: ["/tmp/hooks"] } } },
|
||||
{ internal: { enabled: "yes" } },
|
||||
{ internal: { entries: [] } },
|
||||
{ internal: { entries: { "session-memory": { enabled: "yes" } } } },
|
||||
{ internal: { entries: { "session-memory": { env: { INVALID: true } } } } },
|
||||
];
|
||||
|
||||
for (const hooks of unsafeHooks) {
|
||||
expect(
|
||||
planPristineStartupStateMigrations(createFixture({ hooks })),
|
||||
JSON.stringify(hooks),
|
||||
).toEqual({
|
||||
skipAllStateMigrations: false,
|
||||
skipCoreStateMigrations: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("retains migrations for bundled plugins with doctor state surfaces", () => {
|
||||
const env = addBundledPlugin(
|
||||
createFixture({ plugins: { entries: { example: { enabled: true } } } }),
|
||||
|
||||
@@ -31,7 +31,6 @@ const STATEFUL_CONFIG_KEYS = new Set([
|
||||
"cron",
|
||||
"discovery",
|
||||
"env",
|
||||
"hooks",
|
||||
"marketplaces",
|
||||
"mcp",
|
||||
"media",
|
||||
@@ -48,6 +47,51 @@ const STATEFUL_CONFIG_KEYS = new Set([
|
||||
"web",
|
||||
]);
|
||||
|
||||
// Canonical internal entries have no legacy machine state to import. Keep every
|
||||
// older or external hook shape on Doctor's full migration path.
|
||||
function hasOnlyMigrationSafeInternalHooks(config: Record<string, unknown>): boolean {
|
||||
const hooks = config.hooks;
|
||||
if (hooks === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (!isRecord(hooks) || Object.keys(hooks).some((key) => key !== "internal")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const internal = hooks.internal;
|
||||
if (internal === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
!isRecord(internal) ||
|
||||
Object.keys(internal).some((key) => !["enabled", "entries"].includes(key)) ||
|
||||
(internal.enabled !== undefined && typeof internal.enabled !== "boolean")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (internal.entries === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (!isRecord(internal.entries)) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(internal.entries).every((entry) => {
|
||||
if (!isRecord(entry)) {
|
||||
return false;
|
||||
}
|
||||
if (entry.enabled !== undefined && typeof entry.enabled !== "boolean") {
|
||||
return false;
|
||||
}
|
||||
if (entry.env === undefined) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
isRecord(entry.env) && Object.values(entry.env).every((value) => typeof value === "string")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function containsObjectKey(value: unknown, targetKey: string): boolean {
|
||||
if (Array.isArray(value)) {
|
||||
return value.some((entry) => containsObjectKey(entry, targetKey));
|
||||
@@ -142,6 +186,9 @@ function configIsPristineCoreStateSafe(config: Record<string, unknown>): boolean
|
||||
if ([...STATEFUL_CONFIG_KEYS].some((key) => Object.hasOwn(config, key))) {
|
||||
return false;
|
||||
}
|
||||
if (!hasOnlyMigrationSafeInternalHooks(config)) {
|
||||
return false;
|
||||
}
|
||||
if (containsObjectKey(config.agents, "memorySearch")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user