Files
openclaw/test/scripts/plugin-inspect.test-support.ts
Peter SteinbergerandJason 4a478eed2e fix(config): preserve authored values and agents during Doctor repair (#135671)
* fix(config): preserve authored references during Doctor repair

Use canonical authored projection for invalid snapshots so a legacy agent
roster can migrate without restoring the retired list or flattening includes.
Keep roster-format intent separate from explicit value edits and consume it
only after the Doctor write commits. Compare resolved Gateway modes while
retaining intentional parameter removal and ambiguous-include safeguards.

Derive plugin consent failure from its existing typed outcomes, and arm the
transient composer-cue test observer before the user action.

Follow-up to #134490. The exact repair patch and all 26 managed-review
datasets are unchanged after adopting its landed main base. Independent
P0-P2 review, 107 integration cases, and 18 real Doctor cases on this base pass.
The earlier 528 config/Doctor cases pass with those owners unchanged.
Final packaged Crabbox upgrade and autonomous repair proof remains pending.

Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>

* test(update): verify npm artifacts in upgrade recovery

Validate automatic plugin recovery against the declared npm source, its
managed installation, exact version, artifact integrity, and capability
consent. Preserve selected-plugin consent deferral and the explicit
ClawHub companion audit.

Replace the stale ClawHub-only request expectation with a positive artifact
assertion and zero unexpected ClawHub requests. Exercise invalid records
and the real phase order, including macOS Bash 3.2 optional arguments.

The packaged stable-to-candidate reproduction exposed the obsolete oracle.
Focused regressions, all changed checks, and final managed review pass.
Full final-package Crabbox verification remains in progress.

Related: https://github.com/openclaw/openclaw/pull/134490

* test(ui): wait for draft persistence before legacy seeding

Observe the committed nonempty draft and its post-send clear before replacing current metadata with the legacy outbox fixture. This prevents navigation from aborting the clear and restoring the old text and Blob.

The native IndexedDB latency reproduction fails before the correction and passes after it. Both original legacy table cases and the changed checks pass.

* test(ui): wait for menu readiness in continuation proof

Use the shared focus-synchronized submenu helper for both desktop openings
of Continue in terminal. Keep the real action clicks, exact handoff and
clipboard assertions, reconnect retirement, and mobile pointer flow.
Both browser cases and the changed-file gate pass with a clean managed review.

Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>

---------

Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>
2026-09-02 03:32:44 -07:00

88 lines
3.0 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { delimiter, join } from "node:path";
import { pathToFileURL } from "node:url";
import type { PluginInstallRecord } from "../../src/config/types.plugins.js";
import { isTrustedOfficialPluginInstallRecord } from "../../src/plugins/official-external-install-records.js";
export type PluginInspectFixture = {
plugin: {
id: string;
packageName: string;
rootDir: string;
trustedOfficialInstall: boolean;
};
install: PluginInstallRecord;
};
export function writePluginInspectFixture(
binDir: string,
records: Readonly<Record<string, PluginInstallRecord>>,
mutate?: (inspections: Record<string, PluginInspectFixture>) => void,
): NodeJS.ProcessEnv {
const inspections: Record<string, PluginInspectFixture> = {};
for (const [pluginId, record] of Object.entries(records)) {
if (!record.installPath || !existsSync(join(record.installPath, "package.json"))) {
continue;
}
const { name: packageName } = JSON.parse(
readFileSync(join(record.installPath, "package.json"), "utf8"),
) as { name: string };
inspections[pluginId] = {
plugin: {
id: pluginId,
packageName,
rootDir: record.installPath,
trustedOfficialInstall: isTrustedOfficialPluginInstallRecord({
pluginId,
packageName,
record,
}),
},
install: { ...record },
};
}
mutate?.(inspections);
mkdirSync(binDir, { recursive: true });
const inspectionPath = join(binDir, "plugin-inspections.json");
writeFileSync(inspectionPath, JSON.stringify(inspections));
const fixtureScript = join(binDir, "openclaw");
writeFileSync(
fixtureScript,
`#!/usr/bin/env node
const fs = require("node:fs");
const args = process.argv.slice(2);
if (args.join(" ") === "plugins install --help") {
console.log(" --accept-capabilities Accept capabilities");
} else if (args.length === 4 && args[0] === "plugins" && args[1] === "inspect" && args[3] === "--json") {
const inspection = JSON.parse(fs.readFileSync(${JSON.stringify(inspectionPath)}, "utf8"))[args[2]];
if (!inspection) process.exit(98);
console.log(JSON.stringify(inspection));
} else {
process.exit(97);
}
`,
{ mode: 0o755 },
);
// Keep Bash's executable fixture, but let Node callers use an absolute executable on Windows.
const preloadPath = join(binDir, "plugin-inspect-preload.mjs");
writeFileSync(
preloadPath,
`import childProcess from "node:child_process";
import { syncBuiltinESMExports } from "node:module";
const execFileSync = childProcess.execFileSync;
childProcess.execFileSync = (file, args, options) => {
if (file === "openclaw") {
return execFileSync(process.execPath, [${JSON.stringify(fixtureScript)}, ...args], options);
}
return execFileSync(file, args, options);
};
syncBuiltinESMExports();
`,
);
return {
NODE_OPTIONS:
`${process.env.NODE_OPTIONS ?? ""} --import=${pathToFileURL(preloadPath).href}`.trim(),
PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
};
}