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