mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:50:43 +00:00
Merge branch 'main' into meow/control-chat-responsive
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.
|
||||||
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.
|
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.
|
||||||
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
|
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
|
||||||
- Control UI/Talk: stop and clear failed realtime Talk sessions when dismissing runtime error banners, so the next Talk click starts a fresh session instead of only stopping the stale one. Thanks @vincentkoc.
|
- Control UI/Talk: stop and clear failed realtime Talk sessions when dismissing runtime error banners, so the next Talk click starts a fresh session instead of only stopping the stale one. Thanks @vincentkoc.
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ function createManagedNpmPlugin(params: {
|
|||||||
id: string;
|
id: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
packageLock?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const npmRoot = path.join(params.stateDir, "npm");
|
const npmRoot = path.join(params.stateDir, "npm");
|
||||||
const packageDir = path.join(npmRoot, "node_modules", params.packageName);
|
const packageDir = path.join(npmRoot, "node_modules", params.packageName);
|
||||||
@@ -117,6 +118,37 @@ function createManagedNpmPlugin(params: {
|
|||||||
}),
|
}),
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
if (params.packageLock) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(npmRoot, "package-lock.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
lockfileVersion: 3,
|
||||||
|
packages: {
|
||||||
|
"": {
|
||||||
|
dependencies: {
|
||||||
|
[params.packageName]: params.version,
|
||||||
|
"other-plugin": "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[`node_modules/${params.packageName}`]: {
|
||||||
|
version: params.version,
|
||||||
|
},
|
||||||
|
"node_modules/other-plugin": {
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
[params.packageName]: {
|
||||||
|
version: params.version,
|
||||||
|
},
|
||||||
|
"other-plugin": {
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(packageDir, "package.json"),
|
path.join(packageDir, "package.json"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -301,4 +333,51 @@ describe("maybeRepairPluginRegistryState", () => {
|
|||||||
"Removed stale managed npm plugin package",
|
"Removed stale managed npm plugin package",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("removes stale managed npm packages from the package lock during repair", async () => {
|
||||||
|
const stateDir = makeTempDir();
|
||||||
|
const bundledDir = path.join(stateDir, "bundled", "google-meet");
|
||||||
|
fs.mkdirSync(bundledDir, { recursive: true });
|
||||||
|
createManagedNpmPlugin({
|
||||||
|
stateDir,
|
||||||
|
id: "google-meet",
|
||||||
|
packageName: "@openclaw/google-meet",
|
||||||
|
version: "2026.5.2",
|
||||||
|
packageLock: true,
|
||||||
|
});
|
||||||
|
await writePersistedInstalledPluginIndex(createCurrentIndex(), { stateDir });
|
||||||
|
|
||||||
|
await maybeRepairPluginRegistryState({
|
||||||
|
stateDir,
|
||||||
|
candidates: [
|
||||||
|
createBundledCandidate({
|
||||||
|
rootDir: bundledDir,
|
||||||
|
id: "google-meet",
|
||||||
|
packageName: "@openclaw/google-meet",
|
||||||
|
version: "2026.5.3",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
env: hermeticEnv(),
|
||||||
|
config: {
|
||||||
|
plugins: {
|
||||||
|
allow: ["google-meet"],
|
||||||
|
entries: {
|
||||||
|
"google-meet": {
|
||||||
|
enabled: true,
|
||||||
|
config: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prompter: { shouldRepair: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageLock = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(stateDir, "npm", "package-lock.json"), "utf8"),
|
||||||
|
);
|
||||||
|
expect(packageLock.packages[""].dependencies).toEqual({ "other-plugin": "1.0.0" });
|
||||||
|
expect(packageLock.packages).not.toHaveProperty("node_modules/@openclaw/google-meet");
|
||||||
|
expect(packageLock.dependencies).not.toHaveProperty("@openclaw/google-meet");
|
||||||
|
expect(packageLock.dependencies).toHaveProperty("other-plugin");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ function readStringMap(value: unknown): Record<string, string> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteObjectKey(record: Record<string, unknown>, key: string): boolean {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(record, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete record[key];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function readPackageVersion(packageDir: string): string | undefined {
|
function readPackageVersion(packageDir: string): string | undefined {
|
||||||
const packageJson = readJsonObject(path.join(packageDir, "package.json"));
|
const packageJson = readJsonObject(path.join(packageDir, "package.json"));
|
||||||
const version = packageJson?.version;
|
const version = packageJson?.version;
|
||||||
@@ -137,6 +145,7 @@ function removeManagedNpmDependency(params: {
|
|||||||
dependencies,
|
dependencies,
|
||||||
};
|
};
|
||||||
saveJsonFile(npmPackageJsonPath, nextPackageJson);
|
saveJsonFile(npmPackageJsonPath, nextPackageJson);
|
||||||
|
removeManagedNpmPackageLockDependency(params);
|
||||||
fs.rmSync(params.packageDir, { recursive: true, force: true });
|
fs.rmSync(params.packageDir, { recursive: true, force: true });
|
||||||
const scopeDir = path.dirname(params.packageDir);
|
const scopeDir = path.dirname(params.packageDir);
|
||||||
if (path.basename(path.dirname(scopeDir)) === "node_modules") {
|
if (path.basename(path.dirname(scopeDir)) === "node_modules") {
|
||||||
@@ -148,6 +157,44 @@ function removeManagedNpmDependency(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeManagedNpmPackageLockDependency(params: {
|
||||||
|
npmRoot: string;
|
||||||
|
packageName: string;
|
||||||
|
}): void {
|
||||||
|
const packageLockPath = path.join(params.npmRoot, "package-lock.json");
|
||||||
|
const packageLock = readJsonObject(packageLockPath);
|
||||||
|
if (!packageLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
const packages = packageLock.packages;
|
||||||
|
if (isRecord(packages)) {
|
||||||
|
const rootPackage = packages[""];
|
||||||
|
if (isRecord(rootPackage)) {
|
||||||
|
const rootDependencies = readStringMap(rootPackage.dependencies);
|
||||||
|
if (deleteObjectKey(rootDependencies, params.packageName)) {
|
||||||
|
changed = true;
|
||||||
|
if (Object.keys(rootDependencies).length === 0) {
|
||||||
|
delete rootPackage.dependencies;
|
||||||
|
} else {
|
||||||
|
rootPackage.dependencies = rootDependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = deleteObjectKey(packages, `node_modules/${params.packageName}`) || changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = packageLock.dependencies;
|
||||||
|
if (isRecord(dependencies)) {
|
||||||
|
changed = deleteObjectKey(dependencies, params.packageName) || changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
saveJsonFile(packageLockPath, packageLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function maybeRepairStaleManagedNpmBundledPlugins(
|
function maybeRepairStaleManagedNpmBundledPlugins(
|
||||||
params: PluginRegistryDoctorRepairParams,
|
params: PluginRegistryDoctorRepairParams,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user