mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
chore(lint): clear 172 lint:all baseline violations in five batches (#118098)
* chore(lint): clean Android-Linux app assets batch * chore(lint): clean setup-launcher-plugin batch * chore(lint): clean changelog-updater batch * chore(lint): clean QA-runtime-helper batch * chore(lint): clean script-tests batch * fix(mxc): await sandbox spawn before bridge selection * fix(test): make fake plutil metacharacter escaping survive the template hop
This commit is contained in:
committed by
GitHub
parent
70876c9790
commit
7c70571683
@@ -44,7 +44,7 @@ const nonEditorialTypes = new Set([
|
||||
"test",
|
||||
]);
|
||||
const nonEditorialTitlePattern =
|
||||
/(?:^|[\s:([{\-])(docs?|documentation|tests?|testing|qa|quality assurance|refactor(?:ing)?|ci|continuous integration|build|chore|style|lint|format)(?:$|[\s:)\]}\-])/i;
|
||||
/(?:^|[\s:([{-])(docs?|documentation|tests?|testing|qa|quality assurance|refactor(?:ing)?|ci|continuous integration|build|chore|style|lint|format)(?:$|[\s:)\]}-])/i;
|
||||
const editorialTitlePattern =
|
||||
/^\s*(?:\[[^\]]+\]\s*)?(?:#\d+:\s*)?(?:add|allow|block|enable|expose|fail|fix|harden|honor|improve|keep|migrate|move|persist|polish|preserve|prevent|propagate|rate[- ]?limit|restore|revert|ship|support|treat|validate)\b|^\s*#\d+:/i;
|
||||
const genericDirectCommitTerms = new Set([
|
||||
@@ -111,7 +111,7 @@ function parseArgs(argv) {
|
||||
mainRef: undefined,
|
||||
noGithubSnapshot: false,
|
||||
refreshGithubSnapshot: false,
|
||||
seedRef: undefined,
|
||||
seedRef: /** @type {string | undefined} */ (undefined),
|
||||
shippedRefs: [],
|
||||
writeLedger: false,
|
||||
};
|
||||
@@ -233,7 +233,7 @@ function gitIsAncestor(base, target) {
|
||||
if (result.status === 1) {
|
||||
return false;
|
||||
}
|
||||
fail(
|
||||
return fail(
|
||||
`could not validate release range ancestry for ${base}..${target}: ${
|
||||
result.stderr?.trim() || result.signal || result.status
|
||||
}`,
|
||||
@@ -252,7 +252,9 @@ function gitCommit(ref, required = false) {
|
||||
if (!required) {
|
||||
return undefined;
|
||||
}
|
||||
fail(`could not resolve canonical main ref ${ref}: ${result.stderr?.trim() || result.status}`);
|
||||
return fail(
|
||||
`could not resolve canonical main ref ${ref}: ${result.stderr?.trim() || result.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
function fetchGithubApi(args) {
|
||||
@@ -942,8 +944,8 @@ function authorsMatch(left, right) {
|
||||
}
|
||||
|
||||
function pathsOverlap(left, right) {
|
||||
for (const path of left) {
|
||||
if (right.has(path)) {
|
||||
for (const filePath of left) {
|
||||
if (right.has(filePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -998,7 +1000,7 @@ function canonicalMainCommits(base, mainRef) {
|
||||
if (!mainRef) {
|
||||
return [];
|
||||
}
|
||||
const mainCommit = gitCommit(mainRef, true);
|
||||
const mainCommit = /** @type {string} */ (gitCommit(mainRef, true));
|
||||
const mainBase = git(["merge-base", base, mainCommit]);
|
||||
const output = git([
|
||||
"log",
|
||||
@@ -1103,8 +1105,8 @@ function sourceCommits(base, target, mainRef) {
|
||||
fail(`cyclic revert history at ${hash}`);
|
||||
}
|
||||
seen.add(hash);
|
||||
const output = git(["show", "-s", "--format=%s%x1f%B", hash]);
|
||||
const [subject, ...bodyParts] = output.split("\x1f");
|
||||
const commitOutput = git(["show", "-s", "--format=%s%x1f%B", hash]);
|
||||
const [subject, ...bodyParts] = commitOutput.split("\x1f");
|
||||
const body = bodyParts.join("\x1f");
|
||||
const message = `${subject}\n${body}`;
|
||||
const revertedHash = standardRevertedHash(body);
|
||||
@@ -1345,11 +1347,11 @@ function sourceCommits(base, target, mainRef) {
|
||||
}
|
||||
}
|
||||
const revertedPullRequests = new Set();
|
||||
for (const pullRequests of resolveAssociatedPullRequests(
|
||||
for (const revertedPullRequestNumbers of resolveAssociatedPullRequests(
|
||||
[...revertedCommitHashes],
|
||||
targetTimestamp,
|
||||
).values()) {
|
||||
for (const number of pullRequests) {
|
||||
for (const number of revertedPullRequestNumbers) {
|
||||
revertedPullRequests.add(number);
|
||||
}
|
||||
}
|
||||
@@ -2544,7 +2546,6 @@ function main() {
|
||||
];
|
||||
const resolvedHandles = resolveGitHubHandles(contributorHandles);
|
||||
const relationships = contributionRelationships(source, nodes, resolvedHandles);
|
||||
const unlinkedCommits = source.activeCommits.filter((commit) => commit.references.length === 0);
|
||||
const resolvedCommitAuthors = resolveDirectCommitAuthors(relationships.directCommits);
|
||||
relationships.directCommits = withDirectCommitAuthors(
|
||||
relationships.directCommits,
|
||||
|
||||
@@ -61,7 +61,7 @@ const DEPENDENCY_INPUT_RE =
|
||||
/^(?:\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|patches\/)|(?:^|\/)package\.json$/u;
|
||||
|
||||
class UpdateInvariantError extends Error {
|
||||
constructor(code, message, details = undefined) {
|
||||
constructor(code, message, details) {
|
||||
super(message);
|
||||
this.name = "UpdateInvariantError";
|
||||
this.code = code;
|
||||
@@ -69,6 +69,11 @@ class UpdateInvariantError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-throw the original runtime value while exposing the Error contract to type-aware lint. */
|
||||
function throwPreservingValue(value) {
|
||||
throw /** @type {Error} */ (value);
|
||||
}
|
||||
|
||||
function git(checkout, args, options = {}) {
|
||||
return execFileSync("git", ["-C", checkout, ...args], {
|
||||
encoding: options.encoding ?? "utf8",
|
||||
@@ -149,7 +154,7 @@ export function classifyActions(
|
||||
) {
|
||||
// CI skips generated protocol-only macOS jobs, but the live app embeds these Swift sources.
|
||||
const generatedMacProtocolChanged = changedPaths.some((changedPath) =>
|
||||
/^apps\/shared\/OpenClawKit\/Sources\/OpenClawProtocol\//u.test(changedPath),
|
||||
changedPath.startsWith("apps/shared/OpenClawKit/Sources/OpenClawProtocol/"),
|
||||
);
|
||||
const runMacos =
|
||||
changedPaths.length > 0 &&
|
||||
@@ -246,7 +251,9 @@ function missingControlUiAssets(checkout) {
|
||||
if (!hasAssetPayload) {
|
||||
missing.push("assets/*");
|
||||
}
|
||||
return [...new Set(missing)].toSorted();
|
||||
return [...new Set(missing)].toSorted((left, right) =>
|
||||
left < right ? -1 : left > right ? 1 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
export function inspectBuildState(checkout, expectedSha) {
|
||||
@@ -867,7 +874,7 @@ function readManagedGatewayLaunchAgent(checkout) {
|
||||
if (plistResult.status !== 0) {
|
||||
throw new UpdateInvariantError(
|
||||
"gateway_launchagent_failed",
|
||||
`could not read the managed Gateway LaunchAgent: ${String(plistResult.stderr).trim()}`,
|
||||
`could not read the managed Gateway LaunchAgent: ${plistResult.stderr.trim()}`,
|
||||
);
|
||||
}
|
||||
const plist = JSON.parse(plistResult.stdout);
|
||||
@@ -999,7 +1006,7 @@ function prepareLaunchAgentEntrypointReplacement(deployment, entrypoint, options
|
||||
if (plistResult.status !== 0) {
|
||||
throw new UpdateInvariantError(
|
||||
"gateway_repoint_failed",
|
||||
`could not read the managed Gateway LaunchAgent: ${String(plistResult.stderr).trim()}`,
|
||||
`could not read the managed Gateway LaunchAgent: ${plistResult.stderr.trim()}`,
|
||||
);
|
||||
}
|
||||
const programArguments = replaceLaunchAgentProgramArgument(
|
||||
@@ -1131,10 +1138,9 @@ function verifyManagedGatewayRuntime(checkout, expectedSha) {
|
||||
["print", `gui/${process.getuid()}/${deployment.label}`],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
const pidMatch =
|
||||
launchctl.status === 0 ? String(launchctl.stdout).match(/\bpid = (\d+)\b/u) : null;
|
||||
const pidMatch = launchctl.status === 0 ? launchctl.stdout.match(/\bpid = (\d+)\b/u) : null;
|
||||
const pid = Number(pidMatch?.[1] ?? Number.NaN);
|
||||
const loadedArguments = parseLaunchctlArguments(String(launchctl.stdout));
|
||||
const loadedArguments = parseLaunchctlArguments(launchctl.stdout);
|
||||
const loadedCommand = resolveManagedGatewayCommand(
|
||||
loadedArguments,
|
||||
process.env.HOME,
|
||||
@@ -1167,7 +1173,7 @@ function verifyManagedGatewayRuntime(checkout, expectedSha) {
|
||||
["-nP", `-iTCP:${deployment.port}`, "-sTCP:LISTEN", "-t"],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
const listenerPids = String(listeners.stdout).trim().split(/\s+/u).filter(Boolean).map(Number);
|
||||
const listenerPids = listeners.stdout.trim().split(/\s+/u).filter(Boolean).map(Number);
|
||||
// The Gateway overwrites process.title, so ps cannot prove argv. The owned
|
||||
// LaunchAgent arguments plus its exact listener PID remain stable evidence.
|
||||
if (listeners.status !== 0 || !listenerPids.includes(pid)) {
|
||||
@@ -1481,7 +1487,7 @@ function proveMacLaunchdGatewayStopped(checkout) {
|
||||
encoding: "utf8",
|
||||
});
|
||||
const listenerClosed =
|
||||
listeners.status === 1 && !String(listeners.stdout).trim() && !String(listeners.stderr).trim();
|
||||
listeners.status === 1 && !listeners.stdout.trim() && !listeners.stderr.trim();
|
||||
const details = { listenerClosed, processExited, serviceBootedOut };
|
||||
if (!serviceBootedOut) {
|
||||
throw new UpdateInvariantError(
|
||||
@@ -1597,62 +1603,68 @@ function runBuildWithPreservedMacApp(runCommand, checkout, sleep = defaultSleep)
|
||||
`.openclaw-live-mac-${process.pid}-${randomUUID()}.app`,
|
||||
);
|
||||
renameSync(appBundle, preservedBundle);
|
||||
let buildFailed = false;
|
||||
let buildError;
|
||||
try {
|
||||
runCommand("pnpm", ["build"], checkout);
|
||||
} finally {
|
||||
// A running app or external file coordinator can temporarily relocate and
|
||||
// restore the exact bundle while the JS build runs. Allow that move to settle, but
|
||||
// require the original inode so an unrelated replacement still fails closed.
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
if (existsSync(preservedBundle) || existsSync(appBundle)) {
|
||||
break;
|
||||
}
|
||||
sleep(100);
|
||||
} catch (error) {
|
||||
buildFailed = true;
|
||||
buildError = error;
|
||||
}
|
||||
// Restore outside `finally` so restoration failures retain precedence over build failures.
|
||||
// Accept an external restore only when the original inode returns; replacements still fail closed.
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
if (existsSync(preservedBundle) || existsSync(appBundle)) {
|
||||
break;
|
||||
}
|
||||
const alreadyRestored = isOriginalMacBundle(appBundle, appStat);
|
||||
if (!alreadyRestored && existsSync(appBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_conflict",
|
||||
`build unexpectedly created ${appBundle}; preserved bundle remains at ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
if (!alreadyRestored) {
|
||||
mkdirSync(path.dirname(appBundle), { recursive: true });
|
||||
try {
|
||||
renameSync(preservedBundle, appBundle);
|
||||
} catch (error) {
|
||||
if (!isOriginalMacBundle(appBundle, appStat)) {
|
||||
if (existsSync(appBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_conflict",
|
||||
`build unexpectedly created ${appBundle}; preserved bundle remains at ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
if (existsSync(preservedBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_failed",
|
||||
`failed to restore Mac app bundle: ${String(error)}`,
|
||||
);
|
||||
}
|
||||
sleep(100);
|
||||
}
|
||||
const alreadyRestored = isOriginalMacBundle(appBundle, appStat);
|
||||
if (!alreadyRestored && existsSync(appBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_conflict",
|
||||
`build unexpectedly created ${appBundle}; preserved bundle remains at ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
if (!alreadyRestored) {
|
||||
mkdirSync(path.dirname(appBundle), { recursive: true });
|
||||
try {
|
||||
renameSync(preservedBundle, appBundle);
|
||||
} catch (error) {
|
||||
if (!isOriginalMacBundle(appBundle, appStat)) {
|
||||
if (existsSync(appBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"missing_preserved_mac_bundle",
|
||||
`preserved Mac app bundle disappeared: ${preservedBundle}`,
|
||||
"mac_bundle_restore_conflict",
|
||||
`build unexpectedly created ${appBundle}; preserved bundle remains at ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
if (existsSync(preservedBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_failed",
|
||||
`failed to restore Mac app bundle: ${String(error)}`,
|
||||
);
|
||||
}
|
||||
throw new UpdateInvariantError(
|
||||
"missing_preserved_mac_bundle",
|
||||
`preserved Mac app bundle disappeared: ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isOriginalMacBundle(appBundle, appStat)) {
|
||||
throw new UpdateInvariantError(
|
||||
"missing_preserved_mac_bundle",
|
||||
`original Mac app bundle was not restored to ${appBundle}`,
|
||||
);
|
||||
}
|
||||
if (existsSync(preservedBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_conflict",
|
||||
`original Mac app bundle exists at both ${appBundle} and ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isOriginalMacBundle(appBundle, appStat)) {
|
||||
throw new UpdateInvariantError(
|
||||
"missing_preserved_mac_bundle",
|
||||
`original Mac app bundle was not restored to ${appBundle}`,
|
||||
);
|
||||
}
|
||||
if (existsSync(preservedBundle)) {
|
||||
throw new UpdateInvariantError(
|
||||
"mac_bundle_restore_conflict",
|
||||
`original Mac app bundle exists at both ${appBundle} and ${preservedBundle}`,
|
||||
);
|
||||
}
|
||||
if (buildFailed) {
|
||||
throwPreservingValue(buildError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1666,7 +1678,6 @@ function restartGateway(
|
||||
options = {},
|
||||
) {
|
||||
assertExactBuild(checkout, expectedSha);
|
||||
const now = options.now ?? Date.now;
|
||||
if (!deployment) {
|
||||
runCommand("pnpm", ["openclaw", "gateway", "restart"], checkout);
|
||||
return { processStartedAt: null, restartStartedAtMs: startedAtMs };
|
||||
@@ -1749,7 +1760,7 @@ function bootstrapManagedGateway(runCommand, checkout, deployment, options = {})
|
||||
}
|
||||
environmentRestore.disarm();
|
||||
if (restartError) {
|
||||
throw restartError;
|
||||
throwPreservingValue(restartError);
|
||||
}
|
||||
return { processStartedAt };
|
||||
}
|
||||
@@ -1811,7 +1822,7 @@ function readLaunchdEnvironmentVariable(name) {
|
||||
}
|
||||
// launchd normalizes `setenv NAME ""` to the same absent manager state as
|
||||
// `unsetenv NAME`; both `getenv` and `print gui/$UID` omit the value.
|
||||
const value = String(result.stdout).replace(/\r?\n$/u, "");
|
||||
const value = result.stdout.replace(/\r?\n$/u, "");
|
||||
return value || null;
|
||||
}
|
||||
|
||||
@@ -1821,7 +1832,7 @@ function waitForManagedGatewayProcess(deployment, sleep = defaultSleep) {
|
||||
Math.ceil(GATEWAY_PROCESS_START_TIMEOUT_MS / GATEWAY_PROCESS_START_RETRY_DELAY_MS) + 1;
|
||||
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
||||
const result = spawnSync("/bin/launchctl", ["print", target], { encoding: "utf8" });
|
||||
if (result.status === 0 && /\bpid\s*=\s*\d+\b/iu.test(String(result.stdout))) {
|
||||
if (result.status === 0 && /\bpid\s*=\s*\d+\b/iu.test(result.stdout)) {
|
||||
return;
|
||||
}
|
||||
if (attempt + 1 < attempts) {
|
||||
@@ -1849,7 +1860,7 @@ function waitForManagedGatewayReadiness(
|
||||
sleep = defaultSleep,
|
||||
) {
|
||||
for (let attempt = 1; attempt <= GATEWAY_READINESS_ATTEMPTS; attempt += 1) {
|
||||
if (probeMilestones(deployment)?.readyzReady === true) {
|
||||
if (probeMilestones(deployment)?.readyzReady) {
|
||||
return;
|
||||
}
|
||||
if (attempt < GATEWAY_READINESS_ATTEMPTS) {
|
||||
@@ -1904,7 +1915,7 @@ function probeGatewayMilestones(deployment) {
|
||||
["-nP", `-iTCP:${deployment.port}`, "-sTCP:LISTEN", "-t"],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
const listenerReady = listeners.status === 0 && Boolean(String(listeners.stdout).trim());
|
||||
const listenerReady = listeners.status === 0 && Boolean(listeners.stdout.trim());
|
||||
if (!listenerReady) {
|
||||
return { listenerReady: false, healthzReady: false, readyzReady: false };
|
||||
}
|
||||
@@ -2342,7 +2353,7 @@ function verifyAndAuditGateway({
|
||||
}
|
||||
const audit = auditGatewayLogs(checkout, sinceMs, deployment);
|
||||
if (verificationError) {
|
||||
throw verificationError;
|
||||
throwPreservingValue(verificationError);
|
||||
}
|
||||
return { audit, timing: gatewayTiming };
|
||||
}
|
||||
@@ -2815,7 +2826,7 @@ export function maintainMain(options, dependencies = {}) {
|
||||
if (actions.macAppRebuild) {
|
||||
const pendingState = {
|
||||
...queuedMacState,
|
||||
attempts: Number(queuedMacState?.attempts ?? 0) + 1,
|
||||
attempts: (queuedMacState?.attempts ?? 0) + 1,
|
||||
lastAttemptAt: new Date().toISOString(),
|
||||
};
|
||||
writeMaintenanceState(statePath, pendingState);
|
||||
|
||||
@@ -55,7 +55,7 @@ function canonicalize(value) {
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(value)
|
||||
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
||||
.toSorted(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
||||
.map(([key, child]) => [key, canonicalize(child)]),
|
||||
);
|
||||
}
|
||||
@@ -128,7 +128,7 @@ function trackedPackageManifests(workspace) {
|
||||
return result.stdout
|
||||
.split("\0")
|
||||
.filter((entry) => entry === "package.json" || entry.endsWith("/package.json"))
|
||||
.sort();
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function computeDependencyFingerprint({ workspace, frozenLockfile }) {
|
||||
@@ -145,7 +145,7 @@ function computeDependencyFingerprint({ workspace, frozenLockfile }) {
|
||||
try {
|
||||
manifest = JSON.parse(source);
|
||||
} catch (error) {
|
||||
throw new Error(`invalid JSON in ${relativePath}: ${error.message}`);
|
||||
throw new Error(`invalid JSON in ${relativePath}: ${error.message}`, { cause: error });
|
||||
}
|
||||
return { manifest, relativePath };
|
||||
});
|
||||
|
||||
@@ -91,7 +91,7 @@ function parseManifest(manifestPath) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
} catch (error) {
|
||||
throw new Error(`could not read ${manifestPath}: ${error.message}`);
|
||||
throw new Error(`could not read ${manifestPath}: ${error.message}`, { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +201,13 @@ function verifyImporters(workspace, manifestPath) {
|
||||
if (!expectedResolution) {
|
||||
continue;
|
||||
}
|
||||
const manifestPath = findInstalledManifest({ dependencyName, projectPath, workspace });
|
||||
const resolvedManifestPath = findInstalledManifest({
|
||||
dependencyName,
|
||||
projectPath,
|
||||
workspace,
|
||||
});
|
||||
const importerDisplay = relativeDisplayPath(workspace, projectPath);
|
||||
if (!manifestPath) {
|
||||
if (!resolvedManifestPath) {
|
||||
if (field.optional) {
|
||||
continue;
|
||||
}
|
||||
@@ -214,7 +218,7 @@ function verifyImporters(workspace, manifestPath) {
|
||||
}
|
||||
checked += 1;
|
||||
const installedLocation = normalizeLocation(
|
||||
relativeDisplayPath(workspace, path.dirname(manifestPath)),
|
||||
relativeDisplayPath(workspace, path.dirname(resolvedManifestPath)),
|
||||
);
|
||||
const expectedLocation = normalizeLocation(
|
||||
path.join(
|
||||
@@ -232,20 +236,22 @@ function verifyImporters(workspace, manifestPath) {
|
||||
// says this importer owns the lockfile snapshot in its local slot.
|
||||
if (exactImporterSlot && !installedSnapshotKeys?.has(expectedResolution.snapshotKey)) {
|
||||
const actualKeys = installedSnapshotKeys
|
||||
? [...installedSnapshotKeys].toSorted().join(", ")
|
||||
? [...installedSnapshotKeys]
|
||||
.toSorted((left, right) => (left < right ? -1 : left > right ? 1 : 0))
|
||||
.join(", ")
|
||||
: "<missing metadata>";
|
||||
mismatches.push(
|
||||
`${importerDisplay}: ${dependencyName} expected pnpm snapshot ${expectedResolution.snapshotKey}, resolved ${actualKeys} from ${installedLocation}`,
|
||||
);
|
||||
}
|
||||
const actual = parseManifest(manifestPath);
|
||||
const actual = parseManifest(resolvedManifestPath);
|
||||
if (
|
||||
actual.name !== expectedResolution.packageName ||
|
||||
actual.version !== expectedResolution.version
|
||||
) {
|
||||
const resolvedFrom = relativeDisplayPath(
|
||||
workspace,
|
||||
realpathSync(path.dirname(manifestPath)),
|
||||
realpathSync(path.dirname(resolvedManifestPath)),
|
||||
);
|
||||
mismatches.push(
|
||||
`${importerDisplay}: ${dependencyName} expected ${expectedResolution.packageName}@${expectedResolution.version}, resolved ${actual.name ?? "<missing>"}@${actual.version ?? "<missing>"} from ${resolvedFrom}`,
|
||||
|
||||
@@ -16,17 +16,21 @@ window.renderMath = async (job) => {
|
||||
trust: false,
|
||||
});
|
||||
await document.fonts.ready;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
const initialBounds = container.getBoundingClientRect();
|
||||
const width = Math.ceil(Math.max(initialBounds.width, container.scrollWidth));
|
||||
document.body.style.width = `${width}px`;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
const finalBounds = container.getBoundingClientRect();
|
||||
const height = Math.ceil(Math.max(finalBounds.height, container.scrollHeight));
|
||||
window.ChatMathBridge.postMessage(
|
||||
JSON.stringify({ id: job.id, widthCssPx: width, heightCssPx: height, success: true }),
|
||||
);
|
||||
} catch (_) {
|
||||
} catch {
|
||||
window.ChatMathBridge.postMessage(
|
||||
JSON.stringify({ id: job.id, widthCssPx: 0, heightCssPx: 0, success: false }),
|
||||
);
|
||||
|
||||
@@ -303,10 +303,9 @@ function resolveApkSignerFromSdk(sdkRoot: string | undefined): string | null {
|
||||
|
||||
const candidates = readdirSync(buildToolsDir)
|
||||
.toSorted((left, right) => right.localeCompare(left))
|
||||
.map((version) => join(buildToolsDir, version, "apksigner"))
|
||||
.filter((candidate) => existsSync(candidate));
|
||||
.map((version) => join(buildToolsDir, version, "apksigner"));
|
||||
|
||||
return candidates[0] ?? null;
|
||||
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
||||
}
|
||||
|
||||
function resolveApkSigner(): string {
|
||||
|
||||
+47
-37
@@ -169,7 +169,7 @@ function chatMessageWidgets(message) {
|
||||
suffix += 1;
|
||||
}
|
||||
emitted.add(key);
|
||||
return key === widget.key ? widget : { ...widget, key };
|
||||
return key === widget.key ? widget : Object.assign({}, widget, { key });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@ function scheduleWidgetSync() {
|
||||
generation,
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
.catch(/** @param {unknown} error */ (error) => {
|
||||
sendError = friendlyError(error, "Could not render the widget.");
|
||||
renderStatus();
|
||||
});
|
||||
@@ -903,7 +903,7 @@ async function openNamedPopover(kind) {
|
||||
setPopoverVisibility(kind);
|
||||
if (kind === "agents") {
|
||||
const selectedIndex = agents.findIndex((agent) => agent.id === activeIdentity.id);
|
||||
menuIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
||||
menuIndex = Math.max(selectedIndex, 0);
|
||||
renderAgentList();
|
||||
elements.agentList.querySelectorAll(".agent-option")[menuIndex]?.focus();
|
||||
} else {
|
||||
@@ -967,10 +967,18 @@ function acceleratorFromEvent(event) {
|
||||
return null;
|
||||
}
|
||||
const parts = [];
|
||||
if (event.ctrlKey) parts.push("Ctrl");
|
||||
if (event.altKey) parts.push("Alt");
|
||||
if (event.shiftKey) parts.push("Shift");
|
||||
if (event.metaKey) parts.push("Super");
|
||||
if (event.ctrlKey) {
|
||||
parts.push("Ctrl");
|
||||
}
|
||||
if (event.altKey) {
|
||||
parts.push("Alt");
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
parts.push("Shift");
|
||||
}
|
||||
if (event.metaKey) {
|
||||
parts.push("Super");
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -1005,34 +1013,36 @@ async function requestHide() {
|
||||
document.body.classList.remove("shown");
|
||||
window.clearTimeout(hideTimer);
|
||||
hideTimer = window.setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
const hidden = await invoke("quickchat_hide", {
|
||||
sessionId: rendererSessionId,
|
||||
rendererEpoch,
|
||||
generation: operationGeneration,
|
||||
});
|
||||
if (visibilitySequence !== operationGeneration) {
|
||||
return;
|
||||
() => {
|
||||
void (async () => {
|
||||
try {
|
||||
const hidden = await invoke("quickchat_hide", {
|
||||
sessionId: rendererSessionId,
|
||||
rendererEpoch,
|
||||
generation: operationGeneration,
|
||||
});
|
||||
if (visibilitySequence !== operationGeneration) {
|
||||
return;
|
||||
}
|
||||
if (hidden !== true) {
|
||||
document.body.classList.add("shown");
|
||||
return;
|
||||
}
|
||||
resetAccepted();
|
||||
clearReply();
|
||||
} catch (error) {
|
||||
if (visibilitySequence === operationGeneration) {
|
||||
sendError = friendlyError(error);
|
||||
renderStatus();
|
||||
document.body.classList.add("shown");
|
||||
elements.input.focus();
|
||||
}
|
||||
} finally {
|
||||
if (visibilitySequence === operationGeneration) {
|
||||
hiding = false;
|
||||
}
|
||||
}
|
||||
if (hidden !== true) {
|
||||
document.body.classList.add("shown");
|
||||
return;
|
||||
}
|
||||
resetAccepted();
|
||||
clearReply();
|
||||
} catch (error) {
|
||||
if (visibilitySequence === operationGeneration) {
|
||||
sendError = friendlyError(error);
|
||||
renderStatus();
|
||||
document.body.classList.add("shown");
|
||||
elements.input.focus();
|
||||
}
|
||||
} finally {
|
||||
if (visibilitySequence === operationGeneration) {
|
||||
hiding = false;
|
||||
}
|
||||
}
|
||||
})();
|
||||
},
|
||||
reducedMotion.matches ? 45 : 120,
|
||||
);
|
||||
@@ -1045,13 +1055,13 @@ function reveal() {
|
||||
rendererEpoch,
|
||||
generation: operationGeneration,
|
||||
})
|
||||
.then((accepted) => {
|
||||
if (accepted !== true && visibilitySequence === operationGeneration) {
|
||||
.then((activated) => {
|
||||
if (activated !== true && visibilitySequence === operationGeneration) {
|
||||
document.body.classList.remove("shown");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
accepted === true &&
|
||||
activated === true &&
|
||||
visibilitySequence === operationGeneration &&
|
||||
activeReply?.widgets.length
|
||||
) {
|
||||
|
||||
@@ -35,9 +35,9 @@ export function forwardSignals(spawned, options = {}) {
|
||||
if (exitTimer) {
|
||||
return;
|
||||
}
|
||||
const setTimeoutFn = options.setTimeout ?? setTimeout;
|
||||
const setTimeoutFn = options.setTimeout?.bind(undefined) ?? setTimeout;
|
||||
exitTimer = setTimeoutFn(() => {
|
||||
const exit = options.exit ?? process.exit;
|
||||
const exit = options.exit?.bind(undefined) ?? ((code) => process.exit(code));
|
||||
exit(signalExitCode(signal));
|
||||
}, exitGraceMs);
|
||||
exitTimer?.unref?.();
|
||||
@@ -86,11 +86,37 @@ function bridgeChildProcess(child) {
|
||||
|
||||
export function exitOnChildProcessClose(child, options = {}) {
|
||||
child.on("close", (exitCode, signal) => {
|
||||
const exit = options.exit ?? process.exit;
|
||||
const exit = options.exit?.bind(undefined) ?? ((code) => process.exit(code));
|
||||
exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
||||
});
|
||||
}
|
||||
|
||||
function attachPtyProcess(spawned) {
|
||||
bridgeStdio(spawned);
|
||||
forwardSignals(spawned);
|
||||
spawned.onExit(({ exitCode, signal }) => {
|
||||
process.exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
||||
});
|
||||
}
|
||||
|
||||
function attachChildProcess(spawned) {
|
||||
bridgeChildProcess(spawned);
|
||||
forwardSignals(spawned);
|
||||
exitOnChildProcessClose(spawned);
|
||||
}
|
||||
|
||||
export async function launchSandbox(spawnSandboxFromConfig, config, options, bridges = {}) {
|
||||
// Normalize sync and Promise-returning SDK implementations before selecting an I/O bridge.
|
||||
const spawned = await spawnSandboxFromConfig(config, options ?? {});
|
||||
|
||||
if (typeof spawned.onData === "function") {
|
||||
(bridges.pty ?? attachPtyProcess)(spawned);
|
||||
return;
|
||||
}
|
||||
|
||||
(bridges.child ?? attachChildProcess)(spawned);
|
||||
}
|
||||
|
||||
const SIGNAL_NUMBERS = new Map([
|
||||
["SIGHUP", 1],
|
||||
["SIGINT", 2],
|
||||
@@ -123,20 +149,7 @@ export async function main() {
|
||||
try {
|
||||
const { config, options } = decodePayload(process.argv.slice(2));
|
||||
const { spawnSandboxFromConfig } = await import("@microsoft/mxc-sdk");
|
||||
const spawned = await spawnSandboxFromConfig(config, options ?? {});
|
||||
|
||||
if (typeof spawned.onData === "function") {
|
||||
bridgeStdio(spawned);
|
||||
forwardSignals(spawned);
|
||||
spawned.onExit(({ exitCode, signal }) => {
|
||||
process.exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
bridgeChildProcess(spawned);
|
||||
forwardSignals(spawned);
|
||||
exitOnChildProcessClose(spawned);
|
||||
await launchSandbox(spawnSandboxFromConfig, config, options);
|
||||
} catch (error) {
|
||||
process.stderr.write(`${formatErrorStack(error)}\n`);
|
||||
process.exit(127);
|
||||
|
||||
@@ -28,6 +28,15 @@ const loadLauncher = () =>
|
||||
setTimeout?: (callback: () => void, ms: number) => { unref?: () => void };
|
||||
},
|
||||
) => void;
|
||||
launchSandbox: (
|
||||
spawnSandboxFromConfig: (config: unknown, options: unknown) => unknown,
|
||||
config: unknown,
|
||||
options: unknown,
|
||||
bridges?: {
|
||||
child?: (spawned: unknown) => void;
|
||||
pty?: (spawned: unknown) => void;
|
||||
},
|
||||
) => Promise<void>;
|
||||
signalExitCode: (signal: number | string | undefined) => number;
|
||||
};
|
||||
|
||||
@@ -64,6 +73,45 @@ describe("mxc-spawn-launcher", () => {
|
||||
expect(signalExitCode(undefined)).toBe(1);
|
||||
});
|
||||
|
||||
it("resolves a promised PTY before selecting its bridge", async () => {
|
||||
const { launchSandbox } = loadLauncher();
|
||||
const pty = { onData: vi.fn() };
|
||||
const spawnResult = Promise.resolve(pty);
|
||||
const ptyBridge = vi.fn();
|
||||
const childBridge = vi.fn();
|
||||
|
||||
await launchSandbox(() => spawnResult, { process: {} }, undefined, {
|
||||
pty: ptyBridge,
|
||||
child: childBridge,
|
||||
});
|
||||
|
||||
expect(ptyBridge).toHaveBeenCalledWith(pty);
|
||||
expect(ptyBridge).not.toHaveBeenCalledWith(spawnResult);
|
||||
expect(childBridge).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves a promised child process before selecting its bridge", async () => {
|
||||
const { launchSandbox } = loadLauncher();
|
||||
const child = { on: vi.fn(), stdout: undefined, stderr: undefined };
|
||||
const spawnResult = Promise.resolve(child);
|
||||
const ptyBridge = vi.fn();
|
||||
const childBridge = vi.fn();
|
||||
|
||||
await launchSandbox(
|
||||
() => spawnResult,
|
||||
{ process: {} },
|
||||
{ usePty: false },
|
||||
{
|
||||
pty: ptyBridge,
|
||||
child: childBridge,
|
||||
},
|
||||
);
|
||||
|
||||
expect(childBridge).toHaveBeenCalledWith(child);
|
||||
expect(childBridge).not.toHaveBeenCalledWith(spawnResult);
|
||||
expect(ptyBridge).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("forwards process termination signals to spawned sandbox children", () => {
|
||||
const { forwardSignals } = loadLauncher();
|
||||
const listeners = new Map<string, () => void>();
|
||||
|
||||
@@ -18,7 +18,7 @@ function readStdin() {
|
||||
let input = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => {
|
||||
input += chunk;
|
||||
input += String(chunk);
|
||||
});
|
||||
process.stdin.on("error", reject);
|
||||
process.stdin.on("end", () => resolve(input));
|
||||
|
||||
+2
-3
@@ -51,7 +51,7 @@ const ensureSupportedRuntimeVersion = () => {
|
||||
if (process.versions.bun) {
|
||||
// Bun >=1.4 (Rust rewrite) ships node:sqlite; feature-probe instead of
|
||||
// rejecting Bun outright so capable Bun builds can run OpenClaw.
|
||||
let hasNodeSqlite = false;
|
||||
let hasNodeSqlite;
|
||||
try {
|
||||
hasNodeSqlite = Boolean(process.getBuiltinModule?.("node:sqlite"));
|
||||
} catch {
|
||||
@@ -463,8 +463,7 @@ const hasLauncherContainerTarget = (argv) => {
|
||||
return true;
|
||||
}
|
||||
const args = argv.slice(2);
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
for (const arg of args) {
|
||||
if (!arg || arg === "--") {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ function requireBuzzPrivateKey(
|
||||
try {
|
||||
return { value, publicKey: getPublicKey(decodeBuzzPrivateKey(value)) };
|
||||
} catch {
|
||||
throwPayloadError(
|
||||
return throwPayloadError(
|
||||
createFailure,
|
||||
`Credential payload for kind "buzz" must include "${key}" as an nsec or 64-character hex private key.`,
|
||||
);
|
||||
|
||||
@@ -245,16 +245,20 @@ async function waitForGatewayReadiness(
|
||||
async function startFakeClawRouter(): Promise<FakeClawRouter> {
|
||||
const requests: CapturedRequest[] = [];
|
||||
const server = createServer((req, res) => {
|
||||
void handleClawRouterRequest(req, res, requests).catch((error) => {
|
||||
void handleClawRouterRequest(req, res, requests).catch((error: unknown) => {
|
||||
res.writeHead(500, { "content-type": "application/json" });
|
||||
res.end(JSON.stringify({ error: { message: String(error) } }));
|
||||
});
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") {
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
throw new Error("fake ClawRouter did not bind a TCP port");
|
||||
}
|
||||
return {
|
||||
@@ -262,7 +266,9 @@ async function startFakeClawRouter(): Promise<FakeClawRouter> {
|
||||
requests,
|
||||
close: async () => {
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -273,13 +279,19 @@ async function handleClawRouterRequest(
|
||||
requests: CapturedRequest[],
|
||||
): Promise<void> {
|
||||
const method = req.method ?? "GET";
|
||||
const path = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
|
||||
const requestPath = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
|
||||
const bodyText = await readRequestBody(req);
|
||||
const body = bodyText ? (JSON.parse(bodyText) as Record<string, unknown>) : undefined;
|
||||
const authorization = req.headers.authorization;
|
||||
requests.push({ method, path, authorization, headers: { ...req.headers }, body });
|
||||
requests.push({
|
||||
method,
|
||||
path: requestPath,
|
||||
authorization,
|
||||
headers: { ...req.headers },
|
||||
body,
|
||||
});
|
||||
|
||||
if (method === "GET" && path === "/v1/health") {
|
||||
if (method === "GET" && requestPath === "/v1/health") {
|
||||
writeJson(res, 200, {
|
||||
ok: true,
|
||||
environment: "fakeco",
|
||||
@@ -296,7 +308,7 @@ async function handleClawRouterRequest(
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "GET" && path === "/v1/catalog") {
|
||||
if (method === "GET" && requestPath === "/v1/catalog") {
|
||||
writeJson(res, 200, {
|
||||
providers: [
|
||||
{
|
||||
@@ -318,7 +330,7 @@ async function handleClawRouterRequest(
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "GET" && path === "/v1/usage") {
|
||||
if (method === "GET" && requestPath === "/v1/usage") {
|
||||
writeJson(res, 200, {
|
||||
budget: { configured: false, ledger: "unmetered" },
|
||||
usage: { summary: { requestCount: 0, totalTokens: 0, actualCostMicros: 0 } },
|
||||
@@ -326,12 +338,12 @@ async function handleClawRouterRequest(
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "POST" && path === "/v1/responses") {
|
||||
if (method === "POST" && requestPath === "/v1/responses") {
|
||||
writeResponsesStream(res, resolveResponseText(body));
|
||||
return;
|
||||
}
|
||||
|
||||
writeJson(res, 404, { error: { message: `unexpected ${method} ${path}` } });
|
||||
writeJson(res, 404, { error: { message: `unexpected ${method} ${requestPath}` } });
|
||||
}
|
||||
|
||||
function resolveResponseText(body: Record<string, unknown> | undefined): string {
|
||||
|
||||
@@ -166,7 +166,10 @@ async function runRealPicker(options: ProducerOptions, openclawHome: string) {
|
||||
await sendAndWait("\r", /Configure DM access policies now\?/u);
|
||||
await sendAndWait("\r", /Configuration updated\./u);
|
||||
|
||||
while (!exit) {
|
||||
for (;;) {
|
||||
if (exit) {
|
||||
break;
|
||||
}
|
||||
if (remainingMs() === 0) {
|
||||
throw new Error(`picker timed out after ${options.timeoutMs}ms`);
|
||||
}
|
||||
@@ -182,7 +185,10 @@ async function runRealPicker(options: ProducerOptions, openclawHome: string) {
|
||||
if (!exit) {
|
||||
child.kill("SIGTERM");
|
||||
const cleanupDeadline = Date.now() + 5_000;
|
||||
while (!exit && Date.now() < cleanupDeadline) {
|
||||
while (Date.now() < cleanupDeadline) {
|
||||
if (exit) {
|
||||
break;
|
||||
}
|
||||
await delay(25);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ async function selectProviders(params: {
|
||||
const candidates = explicit
|
||||
? params.suite.providers
|
||||
: (params.suite.defaultProviders ?? params.suite.providers);
|
||||
let providers = candidates.filter((provider) => (explicit ? explicit.has(provider) : true));
|
||||
const providers = candidates.filter((provider) => (explicit ? explicit.has(provider) : true));
|
||||
if (!params.requireAuth) {
|
||||
return providers;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class FakeCommandChild extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(tempDirs.cleanup);
|
||||
afterEach(() => tempDirs.cleanup());
|
||||
|
||||
describe("plugin lifecycle matrix probe", () => {
|
||||
it("accepts inspect JSON for an enabled loaded plugin", async () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ describe("QA Docker E2E lane fixture", () => {
|
||||
"update-restart-auth",
|
||||
]),
|
||||
);
|
||||
expect(listQaDockerE2eLaneNames()).toEqual([...listQaDockerE2eLaneNames()].sort());
|
||||
expect(listQaDockerE2eLaneNames()).toEqual([...listQaDockerE2eLaneNames()].toSorted());
|
||||
});
|
||||
|
||||
it("parses help, list, and lane arguments", () => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createServer, type Server } from "node:http";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { WebSocket, WebSocketServer, type RawData } from "ws";
|
||||
import { runGatewaySmoke } from "../../../../scripts/dev/gateway-smoke.js";
|
||||
|
||||
let server: Server | undefined;
|
||||
@@ -73,8 +73,13 @@ describe("gateway-smoke", () => {
|
||||
server = createServer();
|
||||
wss = new WebSocketServer({ server });
|
||||
wss.on("connection", (ws: WebSocket) => {
|
||||
ws.on("message", (data) => {
|
||||
const frame = JSON.parse(data.toString()) as {
|
||||
ws.on("message", (data: RawData) => {
|
||||
const text = Array.isArray(data)
|
||||
? Buffer.concat(data.map((chunk) => Buffer.from(chunk))).toString("utf8")
|
||||
: Buffer.isBuffer(data)
|
||||
? data.toString("utf8")
|
||||
: Buffer.from(data).toString("utf8");
|
||||
const frame = JSON.parse(text) as {
|
||||
id: string;
|
||||
method: string;
|
||||
params?: unknown;
|
||||
|
||||
@@ -280,7 +280,9 @@ async function waitForChatFinal(
|
||||
if (finalEvent) {
|
||||
return finalEvent.payload;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
throw new Error(`timed out waiting for WebChat final event for run ${runId}`);
|
||||
}
|
||||
@@ -301,7 +303,9 @@ async function waitForWebchatAudio(params: {
|
||||
if (attachment) {
|
||||
return { attachment, history };
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
return { attachment: undefined, history };
|
||||
}
|
||||
@@ -434,7 +438,9 @@ async function waitForActiveTalkStatus(client: GatewayClient, sessionKey: string
|
||||
return status;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error("timed out waiting for active Talk run");
|
||||
@@ -462,7 +468,9 @@ async function waitForQueuedTalkSteer(client: GatewayClient, sessionKey: string)
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
if (lastError instanceof Error) {
|
||||
throw lastError;
|
||||
@@ -513,7 +521,7 @@ async function runActiveTalkAgentRunProof(options: ProducerOptions): Promise<str
|
||||
url: gateway.wsUrl,
|
||||
});
|
||||
const sessionKey = "agent:qa:main";
|
||||
const created = await client.request<Record<string, unknown>>("talk.client.create", {
|
||||
const created = await client.request("talk.client.create", {
|
||||
sessionKey,
|
||||
provider: FIXTURE_REALTIME_PROVIDER_ID,
|
||||
});
|
||||
@@ -643,7 +651,7 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
.then((exitCode) => {
|
||||
process.exitCode = exitCode;
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: unknown) => {
|
||||
console.error(formatErrorMessage(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
@@ -221,7 +221,7 @@ describe("scripts/e2e/openwebui-probe.mjs", () => {
|
||||
});
|
||||
|
||||
it("redacts admin credentials from sign-in error bodies", async () => {
|
||||
const adminEmail = "openwebui-e2e" + "@example.com";
|
||||
const adminEmail = "openwebui-e2e@example.com";
|
||||
const server = createServer((request, response) => {
|
||||
if (request.url === "/api/v1/auths/signin") {
|
||||
response.writeHead(401, { "content-type": "application/json" });
|
||||
@@ -400,33 +400,35 @@ describe("scripts/e2e/openwebui-probe.mjs", () => {
|
||||
|
||||
it("runs chat mode through Open WebUI chat completions and validates the nonce", async () => {
|
||||
const chatRequests: unknown[] = [];
|
||||
const server = createServer(async (request, response) => {
|
||||
if (request.url === "/api/v1/auths/signin") {
|
||||
response.writeHead(200, {
|
||||
"content-type": "application/json",
|
||||
"set-cookie": "openwebui-session=test; Path=/",
|
||||
});
|
||||
response.end(JSON.stringify({ token: "test-token" }));
|
||||
return;
|
||||
}
|
||||
if (request.url === "/api/models") {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ data: [{ id: "openclaw/default" }] }));
|
||||
return;
|
||||
}
|
||||
if (request.url === "/api/chat/completions") {
|
||||
expect(request.headers.authorization).toBe("Bearer test-token");
|
||||
expect(request.headers.cookie).toContain("openwebui-session=test");
|
||||
chatRequests.push(JSON.parse(await readRequestBody(request)));
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
choices: [{ message: { content: "OpenClaw replied with nonce-123" } }],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
const server = createServer((request, response) => {
|
||||
void (async () => {
|
||||
if (request.url === "/api/v1/auths/signin") {
|
||||
response.writeHead(200, {
|
||||
"content-type": "application/json",
|
||||
"set-cookie": "openwebui-session=test; Path=/",
|
||||
});
|
||||
response.end(JSON.stringify({ token: "test-token" }));
|
||||
return;
|
||||
}
|
||||
if (request.url === "/api/models") {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ data: [{ id: "openclaw/default" }] }));
|
||||
return;
|
||||
}
|
||||
if (request.url === "/api/chat/completions") {
|
||||
expect(request.headers.authorization).toBe("Bearer test-token");
|
||||
expect(request.headers.cookie).toContain("openwebui-session=test");
|
||||
chatRequests.push(JSON.parse(await readRequestBody(request)));
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
choices: [{ message: { content: "OpenClaw replied with nonce-123" } }],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
})();
|
||||
});
|
||||
const baseUrl = await listen(server);
|
||||
try {
|
||||
|
||||
@@ -569,7 +569,8 @@ describe("qa-otel-smoke receiver bounds", () => {
|
||||
stopDockerContainer,
|
||||
waitForLocalPort: async () => {},
|
||||
writeFile: async (_path, config) => {
|
||||
writtenConfig = String(config);
|
||||
writtenConfig =
|
||||
typeof config === "string" ? config : Buffer.from(config as Uint8Array).toString("utf8");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -141,7 +141,9 @@ async function waitForFinalToolResult(filePath: string) {
|
||||
if (final) {
|
||||
return { entries, final };
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
throw new Error("timed out waiting for final Voice Call consult tool result");
|
||||
}
|
||||
@@ -359,7 +361,7 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
.then((exitCode) => {
|
||||
process.exitCode = exitCode;
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: unknown) => {
|
||||
console.error(formatErrorMessage(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
@@ -230,7 +230,7 @@ async function startMockModelServer(): Promise<MockModelServer> {
|
||||
await drainRequest(request);
|
||||
responseCount += 1;
|
||||
writeModelResponse(response, responseCount);
|
||||
})().catch((error) => {
|
||||
})().catch((error: unknown) => {
|
||||
response.writeHead(500, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ error: { message: String(error) } }));
|
||||
});
|
||||
|
||||
Vendored
-8
@@ -85,11 +85,6 @@ declare module "*openclaw-changelog-update/scripts/verify-release-notes.mjs" {
|
||||
legacyIssues: Map<number, unknown>;
|
||||
pullRequests: Map<number, ContributionRecord>;
|
||||
};
|
||||
export function contributionRecordTarget(section: { source: string }): string | undefined;
|
||||
export function pullRequestTitleFromCommitSubject(
|
||||
subject: string,
|
||||
number: number,
|
||||
): string | undefined;
|
||||
export function recoverUnavailablePullRequests(params: {
|
||||
numbers: Iterable<number>;
|
||||
nodes: Map<number, unknown>;
|
||||
@@ -161,9 +156,6 @@ declare module "*openclaw-changelog-update/scripts/verify-release-notes.mjs" {
|
||||
associatedPullRequests: number[],
|
||||
hasProvenanceOverride: boolean,
|
||||
): number[];
|
||||
export function recoverUnavailablePullRequests(
|
||||
params: Record<string, unknown>,
|
||||
): Map<number, Record<string, unknown>>;
|
||||
export function validateReleaseProvenanceOverrides(
|
||||
provenanceOverrides: Map<string, number[]>,
|
||||
nodes: Map<number, unknown>,
|
||||
|
||||
@@ -295,7 +295,7 @@ describe("Gateway queued session rotation", () => {
|
||||
expect(JSON.stringify(modelServer.requests[1]?.body)).toContain("OPENCLAW_E2E_AFTER_RESET");
|
||||
} finally {
|
||||
await client.abortChat({ sessionKey }).catch(() => undefined);
|
||||
client.stop();
|
||||
void client.stop();
|
||||
modelServer.releaseHeldResponse();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,7 +11,9 @@ export function isProcessAlive(pid: number): boolean {
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, ms));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export async function waitForFile(filePath: string, timeoutMs: number): Promise<void> {
|
||||
|
||||
@@ -165,13 +165,18 @@ async function startFakeOpenAiServer(params: { modelId: string }): Promise<FakeO
|
||||
});
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
throw new Error("fake OpenAI server did not bind a TCP port");
|
||||
}
|
||||
return {
|
||||
baseUrl: `http://127.0.0.1:${(address as AddressInfo).port}/v1`,
|
||||
requests,
|
||||
close: () => new Promise<void>((resolve) => server.close(() => resolve())),
|
||||
close: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -900,7 +900,7 @@ test("adding a widget preserves the existing native webview identity", async ()
|
||||
message: { role: "assistant", content: [widgetBlock("first"), widgetBlock("second")] },
|
||||
});
|
||||
await harness.flushWidgets();
|
||||
const layouts = harness.syncedWidgets().map((layout: object) => ({ ...layout }));
|
||||
const layouts = harness.syncedWidgets().map((layout: object) => Object.assign({}, layout));
|
||||
|
||||
assert.deepEqual(layouts[0], firstLayout);
|
||||
assert.equal(layouts[0].visible, true);
|
||||
|
||||
@@ -58,7 +58,9 @@ async function waitForChatFinal(
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
}
|
||||
throw new Error(
|
||||
`timed out waiting for chat final runId=${runId}; events=${JSON.stringify(
|
||||
|
||||
@@ -35,7 +35,7 @@ async function runOpenClaw(args: string[], env: NodeJS.ProcessEnv): Promise<stri
|
||||
return result.stdout;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(message.replaceAll(openAiApiKey, "[REDACTED]"));
|
||||
throw new Error(message.replaceAll(openAiApiKey, "[REDACTED]"), { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -475,7 +475,9 @@ describe("collectPluginClawHubReleasePlan", () => {
|
||||
activeRequests += 1;
|
||||
maxActiveRequests = Math.max(maxActiveRequests, activeRequests);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 5);
|
||||
});
|
||||
return await baseFetch(...args);
|
||||
} finally {
|
||||
activeRequests -= 1;
|
||||
|
||||
@@ -321,7 +321,7 @@ describe("plugin cron registry ownership e2e", () => {
|
||||
providers: {
|
||||
"cron-owner": {
|
||||
baseUrl: `${modelServer.baseUrl}/v1`,
|
||||
["api" + "Key"]: TEST_API_KEY,
|
||||
apiKey: TEST_API_KEY,
|
||||
api: "openai-responses",
|
||||
request: { allowPrivateNetwork: true },
|
||||
models: [
|
||||
@@ -360,7 +360,7 @@ describe("plugin cron registry ownership e2e", () => {
|
||||
|
||||
const client = await connectGatewayClient({
|
||||
url: instance.url,
|
||||
["to" + "ken"]: instance.gatewayToken,
|
||||
token: instance.gatewayToken,
|
||||
role: "operator",
|
||||
scopes: ["operator.admin", "operator.read", "operator.write"],
|
||||
requestTimeoutMs: 30_000,
|
||||
|
||||
@@ -190,7 +190,7 @@ beforeAll(() => {
|
||||
status: "fulfilled",
|
||||
value,
|
||||
}),
|
||||
(reason): PromiseRejectedResult => ({ status: "rejected", reason }),
|
||||
(reason: unknown): PromiseRejectedResult => ({ status: "rejected", reason }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ describe("Android app i18n resources", () => {
|
||||
);
|
||||
const base = await readFile("apps/android/wear/src/main/res/values/strings.xml", "utf8");
|
||||
const baseKeys = [...base.matchAll(/<string name="([^"]+)"/gu)]
|
||||
.map((match) => match[1])
|
||||
.toSorted();
|
||||
.map((match) => match[1] as string)
|
||||
.toSorted((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
||||
const basePlaceholders = [...base.matchAll(/%\d+\$[a-z]/giu)]
|
||||
.map((match) => match[0])
|
||||
.toSorted();
|
||||
.toSorted((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
||||
|
||||
expect(wearResources).toHaveLength(NATIVE_I18N_LOCALES.length);
|
||||
for (const [, content] of wearResources) {
|
||||
|
||||
@@ -20,8 +20,12 @@ function runScript(
|
||||
return { ok: true, stdout, stderr: "" };
|
||||
} catch (error) {
|
||||
const e = error as { stdout?: unknown; stderr?: unknown };
|
||||
const stdout = Buffer.isBuffer(e.stdout) ? e.stdout.toString("utf8") : String(e.stdout ?? "");
|
||||
const stderr = Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf8") : String(e.stderr ?? "");
|
||||
const stdout = Buffer.isBuffer(e.stdout)
|
||||
? e.stdout.toString("utf8")
|
||||
: ((e.stdout ?? "") as string);
|
||||
const stderr = Buffer.isBuffer(e.stderr)
|
||||
? e.stderr.toString("utf8")
|
||||
: ((e.stderr ?? "") as string);
|
||||
return { ok: false, stdout, stderr };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,9 @@ describe("scripts/bench-sqlite-reliability", () => {
|
||||
},
|
||||
operation: async () => {
|
||||
fs.writeFileSync(walPath, Buffer.alloc(2048));
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 25);
|
||||
});
|
||||
fs.truncateSync(walPath, 0);
|
||||
return "complete";
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ function run(cwd: string, command: string, args: string[], env?: NodeJS.ProcessE
|
||||
|
||||
function commandOutput(error: unknown): string {
|
||||
const result = error as { stderr?: unknown; stdout?: unknown };
|
||||
return `${String(result.stdout ?? "")}${String(result.stderr ?? "")}`;
|
||||
return `${(result.stdout ?? "") as string}${(result.stderr ?? "") as string}`;
|
||||
}
|
||||
|
||||
function createRepoWithPrChangelogDiff(entry: string): string {
|
||||
|
||||
@@ -88,13 +88,10 @@ describe("extractSwiftHandledEvents", () => {
|
||||
if evt.event == "connect.challenge" { return }
|
||||
`;
|
||||
const handled = extractSwiftHandledEvents(source, constants);
|
||||
expect([...handled].toSorted()).toEqual([
|
||||
"chat",
|
||||
"connect.challenge",
|
||||
"exec.approval.requested",
|
||||
"session.message",
|
||||
"tick",
|
||||
]);
|
||||
const handledEvents = [...handled] as string[];
|
||||
expect(
|
||||
handledEvents.toSorted((left, right) => (left < right ? -1 : left > right ? 1 : 0)),
|
||||
).toEqual(["chat", "connect.challenge", "exec.approval.requested", "session.message", "tick"]);
|
||||
});
|
||||
|
||||
it("extracts only type-scoped static string constants", () => {
|
||||
|
||||
@@ -203,7 +203,9 @@ describe("scripts/ci-run-node-test-shard.mjs", () => {
|
||||
}
|
||||
activeCaches.add(cache);
|
||||
seenCaches.add(cache);
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
activeCaches.delete(cache);
|
||||
return 0;
|
||||
},
|
||||
|
||||
@@ -449,8 +449,8 @@ function readCriticalQualityWorkflow() {
|
||||
return readFileSync(".github/workflows/codeql-critical-quality.yml", "utf8");
|
||||
}
|
||||
|
||||
function readWorkflow(path: string) {
|
||||
return parse(readFileSync(path, "utf8"));
|
||||
function readWorkflow(filePath: string) {
|
||||
return parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
const PULL_REQUEST_EDIT_FIELDS = ["title", "body", "base"] as const;
|
||||
@@ -3176,7 +3176,11 @@ describe("ci workflow guards", () => {
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
new Set(retiredDisks.map((disk) => `${disk.key}:${disk.architecture}:${disk.region}`)).size,
|
||||
new Set(
|
||||
retiredDisks.map(
|
||||
(disk) => `${disk.key as string}:${disk.architecture as string}:${disk.region as string}`,
|
||||
),
|
||||
).size,
|
||||
).toBe(retiredDisks.length);
|
||||
expect(cleanup.on).toHaveProperty("workflow_dispatch");
|
||||
expect(cleanup.permissions).toEqual({ contents: "read" });
|
||||
@@ -3263,7 +3267,7 @@ describe("ci workflow guards", () => {
|
||||
for (const retiredDisk of retiredDisks) {
|
||||
expect(
|
||||
activeKeyPatterns.some((pattern) => pattern.test(retiredDisk.key as string)),
|
||||
`${retiredDisk.key} is still an active sticky-disk key`,
|
||||
`${retiredDisk.key as string} is still an active sticky-disk key`,
|
||||
).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -194,7 +194,9 @@ describe("codesign-mac-app temp file hygiene", () => {
|
||||
expect(signLines[1]).toContain(`${path.join(app, "Contents", "MacOS", "OpenClaw")}\t`);
|
||||
expect(signLines[2]).toContain(`${app}\t`);
|
||||
for (const line of signLines) {
|
||||
const [, , entitlementPath, copiedEntitlementsPath] = line.split("\t");
|
||||
const columns = line.split("\t");
|
||||
const entitlementPath = columns[2];
|
||||
const copiedEntitlementsPath = columns[3];
|
||||
const entitlementSource = expectDefined(entitlementPath, "codesign entitlement source path");
|
||||
const copiedEntitlementSource = expectDefined(
|
||||
copiedEntitlementsPath,
|
||||
|
||||
@@ -126,7 +126,10 @@ describe("dependency-vulnerability-gate", () => {
|
||||
const report = await runDependencyVulnerabilityGate({
|
||||
rootDir,
|
||||
fetchImpl: async (_url, init) => {
|
||||
const payload = JSON.parse(String(init?.body));
|
||||
if (typeof init?.body !== "string") {
|
||||
throw new Error("expected a JSON request body");
|
||||
}
|
||||
const payload = JSON.parse(init.body);
|
||||
payloads.push(payload);
|
||||
const packages = Object.keys(payload);
|
||||
const body: Record<string, unknown[]> = {};
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("Docker E2E observability", () => {
|
||||
const script = readFileSync("scripts/e2e/cron-cli-docker.sh", "utf8");
|
||||
|
||||
expect(script).toMatch(
|
||||
/docker_e2e_run_with_harness[\s\S]*\n -i \\\n "\$IMAGE_NAME" \\\n bash -s >"\$CLIENT_LOG" 2>&1 <<'INNER'/u,
|
||||
/docker_e2e_run_with_harness[\s\S]*\n {2}-i \\\n {2}"\$IMAGE_NAME" \\\n {2}bash -s >"\$CLIENT_LOG" 2>&1 <<'INNER'/u,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ describe("e2e helper numeric env limits", () => {
|
||||
headersSentResolve?.();
|
||||
});
|
||||
const baseUrl = await listen(server);
|
||||
const realTimeout = AbortSignal.timeout;
|
||||
const realTimeout = AbortSignal.timeout.bind(AbortSignal);
|
||||
const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockImplementation(() => realTimeout(200));
|
||||
try {
|
||||
const result = runScript(clickclackPluginWritePath, [tempDir]);
|
||||
@@ -328,7 +328,9 @@ describe("e2e helper numeric env limits", () => {
|
||||
} finally {
|
||||
timeoutSpy.mockRestore();
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
fs.rmSync(tempDir, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -316,24 +316,27 @@ function normalizedEvidence(options: {
|
||||
["productPerformance", "204", 3, 2, "OpenClaw Performance", "openclaw-performance.yml", ""],
|
||||
] as const;
|
||||
const children = roles.map(
|
||||
([role, childRunId, runAttempt, sourceParentAttempt, name, workflow, suffix]) => ({
|
||||
conclusion: "success",
|
||||
dispatchNonce: `full-release-validation-${runId}-${sourceParentAttempt}${suffix}`,
|
||||
displayTitle: `${name} full-release-validation-${runId}-${sourceParentAttempt}${suffix}`,
|
||||
event: "workflow_dispatch",
|
||||
headBranch: workflowRef,
|
||||
parentJobId: `job-${role}`,
|
||||
path: `.github/workflows/${workflow}`,
|
||||
role,
|
||||
runAttempt,
|
||||
runId: childRunId,
|
||||
sourceParentAttempt,
|
||||
sourceParentRunId: runId,
|
||||
status: "completed",
|
||||
url: `https://example.test/runs/${childRunId}`,
|
||||
workflowSha: producerSha,
|
||||
...(role === "productPerformance" ? { reportPublication: "artifact-only" } : {}),
|
||||
}),
|
||||
([role, childRunId, runAttempt, sourceParentAttempt, name, workflow, suffix]) =>
|
||||
Object.assign(
|
||||
{
|
||||
conclusion: "success",
|
||||
dispatchNonce: `full-release-validation-${runId}-${sourceParentAttempt}${suffix}`,
|
||||
displayTitle: `${name} full-release-validation-${runId}-${sourceParentAttempt}${suffix}`,
|
||||
event: "workflow_dispatch",
|
||||
headBranch: workflowRef,
|
||||
parentJobId: `job-${role}`,
|
||||
path: `.github/workflows/${workflow}`,
|
||||
role,
|
||||
runAttempt,
|
||||
runId: childRunId,
|
||||
sourceParentAttempt,
|
||||
sourceParentRunId: runId,
|
||||
status: "completed",
|
||||
url: `https://example.test/runs/${childRunId}`,
|
||||
workflowSha: producerSha,
|
||||
},
|
||||
role === "productPerformance" ? { reportPublication: "artifact-only" } : {},
|
||||
),
|
||||
);
|
||||
return {
|
||||
children,
|
||||
@@ -485,11 +488,15 @@ function runResolver(args: {
|
||||
`repos/${REPOSITORY}/compare/${args.compareBaseSha}...${args.targetSha}`,
|
||||
),
|
||||
JSON.stringify({
|
||||
files: (args.compareFiles ?? ["CHANGELOG.md"]).map((filename, index) => ({
|
||||
filename,
|
||||
status: args.compareRenamed && index === 0 ? "renamed" : "modified",
|
||||
...(args.compareRenamed && index === 0 ? { previous_filename: "src/index.ts" } : {}),
|
||||
})),
|
||||
files: (args.compareFiles ?? ["CHANGELOG.md"]).map((filename, index) =>
|
||||
Object.assign(
|
||||
{
|
||||
filename,
|
||||
status: args.compareRenamed && index === 0 ? "renamed" : "modified",
|
||||
},
|
||||
args.compareRenamed && index === 0 ? { previous_filename: "src/index.ts" } : {},
|
||||
),
|
||||
),
|
||||
merge_base_commit: { sha: args.compareBaseSha },
|
||||
status: args.compareStatus ?? "ahead",
|
||||
}),
|
||||
@@ -819,10 +826,10 @@ describe("scripts/github/find-reusable-release-validation.sh", () => {
|
||||
record.current.artifact.digest = "sha256:not-a-digest";
|
||||
},
|
||||
},
|
||||
])("rejects normalized evidence that is not reusable: $label", ({ mutate }) => {
|
||||
])("rejects normalized evidence that is not reusable: $label", (testCase) => {
|
||||
const { clone, priorSha } = getSharedRepo();
|
||||
const record = normalizedEvidence({ targetSha: priorSha });
|
||||
mutate(record);
|
||||
testCase.mutate(record);
|
||||
const { binDir, fixtures, validatorPath } = setUpFixtures([{ record, runId: "111" }]);
|
||||
|
||||
const result = runResolver({
|
||||
|
||||
@@ -129,7 +129,7 @@ describe("format-docs", () => {
|
||||
writeDocsFixture(root);
|
||||
const oxfmtFileArgs: string[][] = [];
|
||||
|
||||
const spawnSync = (command: string, args: string[]) => {
|
||||
const runCommandSync = (command: string, args: string[]) => {
|
||||
if (command === "git") {
|
||||
return {
|
||||
status: 0,
|
||||
@@ -150,7 +150,7 @@ describe("format-docs", () => {
|
||||
},
|
||||
{
|
||||
existsSync: fs.existsSync,
|
||||
spawnSync,
|
||||
spawnSync: runCommandSync,
|
||||
},
|
||||
),
|
||||
).toEqual({ changed: [], fileCount: 2 });
|
||||
@@ -164,7 +164,7 @@ describe("format-docs", () => {
|
||||
},
|
||||
{
|
||||
existsSync: fs.existsSync,
|
||||
spawnSync,
|
||||
spawnSync: runCommandSync,
|
||||
},
|
||||
),
|
||||
).toEqual({ changed: [], fileCount: 2 });
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("gateway network client", () => {
|
||||
expect(signal).toBeInstanceOf(AbortSignal);
|
||||
const requestSignal = signal as AbortSignal;
|
||||
return new Promise((_, reject) => {
|
||||
const rejectWithReason = () => reject(requestSignal.reason);
|
||||
const rejectWithReason = () => reject(requestSignal.reason as Error);
|
||||
if (requestSignal.aborted) {
|
||||
rejectWithReason();
|
||||
return;
|
||||
@@ -207,7 +207,14 @@ describe("gateway network client", () => {
|
||||
expect(Date.now() - startedAt).toBeLessThan(500);
|
||||
expect(bodySignal?.aborted).toBe(true);
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
||||
expect(String(fetchImpl.mock.calls[1]?.[0])).toContain("/healthz");
|
||||
const request = fetchImpl.mock.calls[1]?.[0];
|
||||
const requestUrl =
|
||||
request instanceof Request
|
||||
? request.url
|
||||
: request instanceof URL
|
||||
? request.href
|
||||
: (request ?? "");
|
||||
expect(requestUrl).toContain("/healthz");
|
||||
});
|
||||
|
||||
it("bounds a stalled post-restart admin request by the client deadline", async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { createServer, type Server } from "node:http";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { WebSocket, WebSocketServer, type RawData } from "ws";
|
||||
import { createBoundedChildOutput } from "../helpers/bounded-child-output.js";
|
||||
|
||||
type ScriptResult = {
|
||||
@@ -92,8 +92,13 @@ async function listenGateway(params: {
|
||||
server = createServer();
|
||||
wss = new WebSocketServer({ server });
|
||||
wss.on("connection", (ws: WebSocket) => {
|
||||
ws.on("message", (data) => {
|
||||
const frame = JSON.parse(String(data)) as GatewayFrame;
|
||||
ws.on("message", (data: RawData) => {
|
||||
const text = Array.isArray(data)
|
||||
? Buffer.concat(data.map((chunk) => Buffer.from(chunk))).toString("utf8")
|
||||
: Buffer.isBuffer(data)
|
||||
? data.toString("utf8")
|
||||
: Buffer.from(data).toString("utf8");
|
||||
const frame = JSON.parse(text) as GatewayFrame;
|
||||
if (frame.type !== "req") {
|
||||
return;
|
||||
}
|
||||
@@ -138,7 +143,7 @@ async function listenGateway(params: {
|
||||
ok: true,
|
||||
nodeId: "ios-node",
|
||||
command: frame.params?.command,
|
||||
payload: invokePayload(String(frame.params?.command ?? ""), params.mode),
|
||||
payload: invokePayload(frame.params?.command ?? "", params.mode),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -290,7 +295,7 @@ describe("ios-node-e2e", () => {
|
||||
payload: {},
|
||||
},
|
||||
});
|
||||
expect(report.results.every((entry) => entry.ok === false)).toBe(true);
|
||||
expect(report.results.every((entry) => !entry.ok)).toBe(true);
|
||||
expect(invokeParams.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ function swiftFunctionBody(source: string, name: string): string {
|
||||
}
|
||||
|
||||
const rest = source.slice(start + startMarker.length);
|
||||
const nextFunction = rest.search(/\n (?:private )?func /);
|
||||
const nextFunction = rest.search(/\n {4}(?:private )?func /);
|
||||
return nextFunction < 0 ? rest : rest.slice(0, nextFunction);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,12 @@ function runScript(
|
||||
return { ok: true, stdout, stderr: "" };
|
||||
} catch (error) {
|
||||
const e = error as { stdout?: unknown; stderr?: unknown };
|
||||
const stdout = Buffer.isBuffer(e.stdout) ? e.stdout.toString("utf8") : String(e.stdout ?? "");
|
||||
const stderr = Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf8") : String(e.stderr ?? "");
|
||||
const stdout = Buffer.isBuffer(e.stdout)
|
||||
? e.stdout.toString("utf8")
|
||||
: ((e.stdout ?? "") as string);
|
||||
const stderr = Buffer.isBuffer(e.stderr)
|
||||
? e.stderr.toString("utf8")
|
||||
: ((e.stderr ?? "") as string);
|
||||
return { ok: false, stdout, stderr };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// iOS IPA validation tests cover the App Store upload gate without real signing assets.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import {
|
||||
chmodSync,
|
||||
mkdirSync,
|
||||
@@ -138,8 +138,11 @@ if (extractIndex < 0 || expectIndex < 0 || process.argv[expectIndex + 1] !== "st
|
||||
const key = process.argv[extractIndex + 1];
|
||||
const file = process.argv[process.argv.length - 1];
|
||||
const xml = readFileSync(file, "utf8");
|
||||
const escapedKey = key.replace(/[.*+?^\${}()|[\]\\]/g, "\\$&");
|
||||
const match = xml.match(new RegExp("<key>" + escapedKey + "<\\/key>\\s*<string>([^<]*)<\\/string>"));
|
||||
// Escapes are doubled for the template-literal -> emitted-file hop: the emitted script
|
||||
// must contain \\] in the class and \\$& in the replacement, or keys with regex
|
||||
// metacharacters interpolate unescaped into the RegExp below and stop emulating plutil.
|
||||
const escapedKey = key.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
|
||||
const match = xml.match(new RegExp("<key>" + escapedKey + "</key>\\\\s*<string>([^<]*)</string>"));
|
||||
if (!match) process.exit(1);
|
||||
process.stdout.write(match[1]);
|
||||
`,
|
||||
@@ -378,8 +381,12 @@ function runValidator(
|
||||
return { ok: true, stdout, stderr: "" };
|
||||
} catch (error) {
|
||||
const e = error as { stdout?: unknown; stderr?: unknown };
|
||||
const stdout = Buffer.isBuffer(e.stdout) ? e.stdout.toString("utf8") : String(e.stdout ?? "");
|
||||
const stderr = Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf8") : String(e.stderr ?? "");
|
||||
const stdout = Buffer.isBuffer(e.stdout)
|
||||
? e.stdout.toString("utf8")
|
||||
: ((e.stdout ?? "") as string);
|
||||
const stderr = Buffer.isBuffer(e.stderr)
|
||||
? e.stderr.toString("utf8")
|
||||
: ((e.stderr ?? "") as string);
|
||||
return { ok: false, stdout, stderr };
|
||||
}
|
||||
}
|
||||
@@ -391,6 +398,37 @@ describe("scripts/ios-validate-app-store-ipa.sh", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("fake plutil escapes regex-metacharacter keys before matching", () => {
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-ios-ipa-"));
|
||||
tempDirs.push(root);
|
||||
const plutil = path.join(root, "plutil");
|
||||
writeFakePlutil(plutil);
|
||||
const plistPath = path.join(root, "meta.plist");
|
||||
writeFileSync(
|
||||
plistPath,
|
||||
"<plist><dict>\n<key>Weird[Key]*</key>\n<string>metavalue</string>\n</dict></plist>",
|
||||
"utf8",
|
||||
);
|
||||
const escaped = spawnSync(
|
||||
process.execPath,
|
||||
[plutil, "-extract", "Weird[Key]*", "-expect", "string", plistPath],
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
expect(escaped.status).toBe(0);
|
||||
expect(escaped.stdout).toBe("metavalue");
|
||||
// An unescaped interpolation would let this key match as a regex; it must miss instead.
|
||||
const missing = spawnSync(
|
||||
process.execPath,
|
||||
[plutil, "-extract", "Weird.Key.*", "-expect", "string", plistPath],
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
expect(missing.status).toBe(1);
|
||||
});
|
||||
|
||||
it("accepts an App Store IPA with appStore mode and production entitlements", async () => {
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-ios-ipa-"));
|
||||
tempDirs.push(root);
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("k8s manifests", () => {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1",
|
||||
kind: "Kustomization",
|
||||
});
|
||||
expect(asStrings(kustomization.resources, "kustomization resources").sort()).toEqual([
|
||||
expect(asStrings(kustomization.resources, "kustomization resources").toSorted()).toEqual([
|
||||
"configmap.yaml",
|
||||
"deployment.yaml",
|
||||
"pvc.yaml",
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("Kova report publish files", () => {
|
||||
"bundle.json",
|
||||
"bundle.tar.gz.sha256",
|
||||
]);
|
||||
expect(readdirSync(destinationDir).sort()).toEqual(["bundle.json", "bundle.tar.gz.sha256"]);
|
||||
expect(readdirSync(destinationDir).toSorted()).toEqual(["bundle.json", "bundle.tar.gz.sha256"]);
|
||||
expect(readFileSync(join(destinationDir, "bundle.tar.gz.sha256"), "utf8")).toBe(
|
||||
"abc bundle.tar.gz\n",
|
||||
);
|
||||
|
||||
@@ -178,7 +178,9 @@ describe("scripts/mantis/publish-pr-evidence", () => {
|
||||
fetchImpl: (_url, init) => {
|
||||
observedSignal = init.signal;
|
||||
return new Promise<Response>((_resolve, reject) => {
|
||||
init.signal.addEventListener("abort", () => reject(init.signal.reason), { once: true });
|
||||
init.signal.addEventListener("abort", () => reject(init.signal.reason as Error), {
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
manifest,
|
||||
@@ -276,7 +278,9 @@ describe("scripts/mantis/publish-pr-evidence", () => {
|
||||
cause: { name: "TimeoutError" },
|
||||
message: "Timed out uploading Mantis artifact baseline.png after 50ms.",
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
expect(cancelled).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -45,7 +45,12 @@ function candidateOverridePattern(): RegExp {
|
||||
if (!match) {
|
||||
throw new Error("Missing candidate override regex");
|
||||
}
|
||||
return Function(`"use strict"; return ${match[1]};`)() as RegExp;
|
||||
const literal = match[1];
|
||||
if (!literal) {
|
||||
throw new Error("Missing candidate override regex literal");
|
||||
}
|
||||
const flagsStart = literal.lastIndexOf("/");
|
||||
return new RegExp(literal.slice(1, flagsStart), literal.slice(flagsStart + 1));
|
||||
}
|
||||
|
||||
describe("Mantis Web UI chat proof workflow", () => {
|
||||
|
||||
@@ -135,9 +135,7 @@ describe("scripts/e2e/lib/fixtures/mock-openai-config.mjs", () => {
|
||||
agents: {
|
||||
defaults: { models: {} },
|
||||
entries: {
|
||||
main: {
|
||||
...(model === undefined ? {} : { model }),
|
||||
},
|
||||
main: model === undefined ? {} : { model },
|
||||
},
|
||||
},
|
||||
models: { providers: {} },
|
||||
|
||||
@@ -194,7 +194,7 @@ function fakeCommands(mirror: string) {
|
||||
const calls: string[] = [];
|
||||
return {
|
||||
calls,
|
||||
runCommand(command: string, args: string[]) {
|
||||
runCommand: (command: string, args: string[]) => {
|
||||
calls.push([command, ...args].join(" "));
|
||||
if (command === "pnpm" && args[0] === "install") {
|
||||
mkdirSync(path.join(mirror, "node_modules"), { recursive: true });
|
||||
|
||||
@@ -433,7 +433,7 @@ describe("release Telegram QA workflow", () => {
|
||||
},
|
||||
);
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout.toString().split("\0").filter(Boolean)).toEqual(paths.slice(4).reverse());
|
||||
expect(result.stdout.split("\0").filter(Boolean)).toEqual(paths.slice(4).toReversed());
|
||||
});
|
||||
|
||||
it("keeps generated SUT programs syntactically valid", () => {
|
||||
|
||||
@@ -131,37 +131,47 @@ function writeFixture(repoRoot: string, relativePath: string, contents: string):
|
||||
}
|
||||
|
||||
describe("Periphery scope workflows", () => {
|
||||
it.each(WORKFLOW_CASES)("uses the synthetic merge parent for $name scope", ({ path }) => {
|
||||
const workflow = readWorkflow(path);
|
||||
const steps = workflow.jobs?.scope?.steps ?? [];
|
||||
const checkout = steps.find((step) => step.name === "Checkout");
|
||||
const script = scopeScript(path);
|
||||
it.each(WORKFLOW_CASES)(
|
||||
"uses the synthetic merge parent for $name scope",
|
||||
({ path: workflowPath }) => {
|
||||
const workflow = readWorkflow(workflowPath);
|
||||
const steps = workflow.jobs?.scope?.steps ?? [];
|
||||
const checkout = steps.find((step) => step.name === "Checkout");
|
||||
const script = scopeScript(workflowPath);
|
||||
|
||||
expect(workflow.on?.pull_request?.types).toContain("converted_to_draft");
|
||||
expect(workflow.on?.pull_request?.paths).toBeUndefined();
|
||||
expect(checkout?.with?.["fetch-depth"]).toBe(2);
|
||||
expect(steps.some((step) => step.name === "Ensure base commit")).toBe(false);
|
||||
expect(script).toContain('"HEAD^1"');
|
||||
expect(script).not.toContain("pulls.listFiles");
|
||||
expect(() =>
|
||||
compileFunction(`return (async () => {\n${script}\n})();`, ["context", "core", "exec"]),
|
||||
).not.toThrow();
|
||||
});
|
||||
expect(workflow.on?.pull_request?.types).toContain("converted_to_draft");
|
||||
expect(workflow.on?.pull_request?.paths).toBeUndefined();
|
||||
expect(checkout?.with?.["fetch-depth"]).toBe(2);
|
||||
expect(steps.some((step) => step.name === "Ensure base commit")).toBe(false);
|
||||
expect(script).toContain('"HEAD^1"');
|
||||
expect(script).not.toContain("pulls.listFiles");
|
||||
expect(() =>
|
||||
compileFunction(`return (async () => {\n${script}\n})();`, ["context", "core", "exec"]),
|
||||
).not.toThrow();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(WORKFLOW_CASES)("selects only $name scope changes", async ({ path, scopedPath }) => {
|
||||
await expect(runScope(path, { files: [scopedPath] })).resolves.toBe("true");
|
||||
await expect(runScope(path, { files: ["docs/index.md"] })).resolves.toBe("false");
|
||||
await expect(runScope(path, { draft: true, files: [scopedPath] })).resolves.toBe("false");
|
||||
await expect(runScope(path, { eventName: "workflow_dispatch" })).resolves.toBe("true");
|
||||
await expect(
|
||||
runScope(path, {
|
||||
files: [{ filename: "docs/Moved.swift", previous_filename: scopedPath }],
|
||||
}),
|
||||
).resolves.toBe("true");
|
||||
await expect(runScope(path, { diffExitCode: 128 })).rejects.toThrow(
|
||||
"git diff failed with exit code 128",
|
||||
);
|
||||
});
|
||||
it.each(WORKFLOW_CASES)(
|
||||
"selects only $name scope changes",
|
||||
async ({ path: workflowPath, scopedPath }) => {
|
||||
await expect(runScope(workflowPath, { files: [scopedPath] })).resolves.toBe("true");
|
||||
await expect(runScope(workflowPath, { files: ["docs/index.md"] })).resolves.toBe("false");
|
||||
await expect(runScope(workflowPath, { draft: true, files: [scopedPath] })).resolves.toBe(
|
||||
"false",
|
||||
);
|
||||
await expect(runScope(workflowPath, { eventName: "workflow_dispatch" })).resolves.toBe(
|
||||
"true",
|
||||
);
|
||||
await expect(
|
||||
runScope(workflowPath, {
|
||||
files: [{ filename: "docs/Moved.swift", previous_filename: scopedPath }],
|
||||
}),
|
||||
).resolves.toBe("true");
|
||||
await expect(runScope(workflowPath, { diffExitCode: 128 })).rejects.toThrow(
|
||||
"git diff failed with exit code 128",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("ignores scoped files added only by base-branch drift", async () => {
|
||||
const repoRoot = makeTempRepoRoot(tempDirs, "openclaw-periphery-scope-");
|
||||
|
||||
@@ -257,7 +257,9 @@ async function waitFor(predicate: () => boolean, timeoutMs: number): Promise<boo
|
||||
if (predicate()) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
}
|
||||
return predicate();
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ describePosix("scripts/pr review artifact validation", () => {
|
||||
const archives = readdirSync(join(localDir, "superseded"));
|
||||
expect(archives).toHaveLength(1);
|
||||
const archive = join(localDir, "superseded", archives[0]!);
|
||||
expect(readdirSync(archive).sort()).toEqual(["review.json", "review.md"]);
|
||||
expect(readdirSync(archive).toSorted()).toEqual(["review.json", "review.md"]);
|
||||
expect(JSON.parse(readFileSync(join(archive, "review.json"), "utf8")).pr.number).toBe(113928);
|
||||
expect(readFileSync(join(archive, "review.md"), "utf8")).toBe("A) Ship another PR\n");
|
||||
|
||||
|
||||
@@ -216,7 +216,9 @@ describe("scripts/pr wrappers", () => {
|
||||
const classifications = parseSubcommandClassifications(script);
|
||||
const dispatched = parseDispatchedSubcommands(script);
|
||||
|
||||
expect([...classifications.keys()].sort()).toEqual([...dispatched, "lock-recover"].sort());
|
||||
expect([...classifications.keys()].toSorted()).toEqual(
|
||||
[...dispatched, "lock-recover"].toSorted(),
|
||||
);
|
||||
expect(classifications.get("ls")).toBe("advisory");
|
||||
expect(classifications.get("ci-dispatch")).toBe("advisory");
|
||||
for (const command of dispatched.filter((value) => !["ls", "ci-dispatch"].includes(value))) {
|
||||
|
||||
@@ -438,7 +438,6 @@ describe("prepare-extension-package-boundary-artifacts", () => {
|
||||
tempRoots.add(rootDir);
|
||||
const descendantPidPath = path.join(rootDir, "descendant.pid");
|
||||
let descendantPid = 0;
|
||||
let runnerPid = 0;
|
||||
const moduleHref = pathToFileURL(
|
||||
path.resolve("scripts/prepare-extension-package-boundary-artifacts.mjs"),
|
||||
).href;
|
||||
@@ -461,7 +460,7 @@ describe("prepare-extension-package-boundary-artifacts", () => {
|
||||
const runner = spawn(process.execPath, ["--input-type=module", "--eval", runnerScript], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
runnerPid = runner.pid ?? 0;
|
||||
const runnerPid = runner.pid ?? 0;
|
||||
|
||||
try {
|
||||
descendantPid = Number.parseInt(await waitForFile(descendantPidPath, 10_000), 10);
|
||||
|
||||
@@ -79,14 +79,12 @@ describe("release candidate checklist", () => {
|
||||
toolingSha: "b".repeat(40),
|
||||
});
|
||||
const resumed = reconcileReleaseCandidateState(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
...expected,
|
||||
phase: "waiting",
|
||||
fullReleaseRunId: "111",
|
||||
npmPreflightRunId: "222",
|
||||
}),
|
||||
),
|
||||
structuredClone({
|
||||
...expected,
|
||||
phase: "waiting",
|
||||
fullReleaseRunId: "111",
|
||||
npmPreflightRunId: "222",
|
||||
}),
|
||||
expected,
|
||||
);
|
||||
|
||||
|
||||
@@ -199,8 +199,8 @@ describe("release validation no-push transport", () => {
|
||||
"validate_docker_lanes",
|
||||
"validate_docker_openwebui",
|
||||
]) {
|
||||
const job = workflow.jobs?.[jobName];
|
||||
const runStep = job?.steps?.find((candidate) =>
|
||||
const workflowJob = workflow.jobs?.[jobName];
|
||||
const runStep = workflowJob?.steps?.find((candidate) =>
|
||||
candidate.run?.includes("test-live-build-docker.sh"),
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
const SCRIPT = path.resolve("scripts/release-telegram-candidate-archive.py");
|
||||
const tempDirs: string[] = [];
|
||||
const tarVersion = spawnSync("tar", ["--version"], { encoding: "utf8" });
|
||||
const hasGnuTar = tarVersion.status === 0 && tarVersion.stdout?.includes("GNU tar") === true;
|
||||
const hasGnuTar = tarVersion.status === 0 && tarVersion.stdout?.includes("GNU tar");
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of tempDirs.splice(0)) {
|
||||
@@ -472,7 +472,9 @@ describe("release Telegram candidate archive guard", () => {
|
||||
try {
|
||||
expectFailure(["validate-tree", root], "unsupported special entry");
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ async function waitUntil(matches: () => boolean, label: string): Promise<void> {
|
||||
if (matches()) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 20);
|
||||
});
|
||||
}
|
||||
throw new Error(`timed out waiting for ${label}`);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ describe("sync-labels", () => {
|
||||
|
||||
const ghCalls = execFileSyncMock.mock.calls.filter(([command]) => command === "gh");
|
||||
expect(ghCalls.length).toBeGreaterThan(1);
|
||||
for (const [, , options] of ghCalls) {
|
||||
for (const call of ghCalls) {
|
||||
const options = call[2];
|
||||
expect(options).toMatchObject({
|
||||
timeout: 120_000,
|
||||
killSignal: "SIGKILL",
|
||||
|
||||
@@ -882,9 +882,7 @@ describe("scripts/test-group-report arg parsing", () => {
|
||||
flag,
|
||||
expectDefined(values[1], `second ${flag} value`),
|
||||
];
|
||||
expect(() => parseTestGroupReportArgs(args)).toThrow(
|
||||
`${String(flag)} was provided more than once`,
|
||||
);
|
||||
expect(() => parseTestGroupReportArgs(args)).toThrow(`${flag} was provided more than once`);
|
||||
}
|
||||
expect(parseTestGroupReportArgs(["--config", "a.ts", "--config", "b.ts"]).configs).toEqual([
|
||||
"a.ts",
|
||||
|
||||
@@ -296,7 +296,7 @@ function normalizeInstallE2eAgentOutput(output: string) {
|
||||
function extractInstallSmokeUpdateJsonParser(): string {
|
||||
const script = readFileSync(SMOKE_RUNNER_PATH, "utf8");
|
||||
const match = script.match(
|
||||
/UPDATE_JSON="\$UPDATE_JSON" \\\n[\s\S]*?node - <<'NODE'\n([\s\S]*?)\nNODE\n\n echo "==> Verify updated version"/u,
|
||||
/UPDATE_JSON="\$UPDATE_JSON" \\\n[\s\S]*?node - <<'NODE'\n([\s\S]*?)\nNODE\n\n {2}echo "==> Verify updated version"/u,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error("install smoke update JSON parser was not found");
|
||||
@@ -463,7 +463,9 @@ async function waitForCondition(
|
||||
if (predicate()) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 5);
|
||||
});
|
||||
}
|
||||
throw new Error(`timed out waiting for ${label}`);
|
||||
}
|
||||
|
||||
@@ -208,8 +208,9 @@ describe("transitive-manifest-risk-report", () => {
|
||||
version: "1.0.0",
|
||||
registryBaseUrl: "https://registry.example.test",
|
||||
fetchImpl: async (url, init) => {
|
||||
const requestUrl = url instanceof Request ? url.url : url instanceof URL ? url.href : url;
|
||||
fetchCalls.push({
|
||||
url: String(url),
|
||||
url: requestUrl,
|
||||
accept: new Headers(init?.headers).get("accept"),
|
||||
signal: init?.signal instanceof AbortSignal ? init.signal : null,
|
||||
});
|
||||
|
||||
@@ -1077,7 +1077,9 @@ describe("verify-pr-hosted-gates", () => {
|
||||
["queued artifact run", 5],
|
||||
])("does not cover queued artifacts with a stale %s", (_kind, staleRunIndex) => {
|
||||
const workflowRuns = queuedBuildArtifactFallbackRuns().map((run, index) =>
|
||||
index === staleRunIndex ? { ...run, updated_at: "2026-06-16T10:54:59Z" } : run,
|
||||
index === staleRunIndex
|
||||
? Object.assign({}, run, { updated_at: "2026-06-16T10:54:59Z" })
|
||||
: run,
|
||||
);
|
||||
expect(() => collectHostedGateEvidence({ sha, workflowRuns })).toThrow(
|
||||
"Missing successful recent Blacksmith Build Artifacts Testbox workflow",
|
||||
|
||||
@@ -590,7 +590,9 @@ describe("write-cli-startup-metadata", () => {
|
||||
return "Usage: openclaw browser\n";
|
||||
},
|
||||
renderSourceSecretsHelpText: async () => {
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
await new Promise((resolve) => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
statePresentDuringSiblingRender = existsSync(stateDir);
|
||||
return "Usage: openclaw secrets\n";
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user