mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
refactor(scripts): deslop shared tooling flows (#162824)
Consolidate script lifecycle and helper ownership while preserving CLI, generated-output, cleanup, and guard contracts. Reject inherited object keys in model-matrix option admission with the existing unknown-argument diagnostic. Validation: 90 full sibling suites; original-fails parser regression; byte-identical native outputs; changed checks; SDK surface and API comparison; both zero-cycle checks; independent isolated review.
This commit is contained in:
parent
6f56df6b5a
commit
7cbaa6b36f
71 changed files with 464 additions and 1003 deletions
|
|
@ -146,9 +146,11 @@ describe("Code Mode model matrix options", () => {
|
|||
it("rejects ambiguous selectors and output paths", () => {
|
||||
expect(() => parseCodeModeMatrixOptions([])).toThrow("At least one --model");
|
||||
expect(() => parseCodeModeMatrixOptions(["--model", "qwen3.5:9b"])).toThrow("provider/model");
|
||||
expect(() =>
|
||||
parseCodeModeMatrixOptions(["--model", "ollama/qwen3.5:9b", "--skip-build"]),
|
||||
).toThrow("Unknown argument");
|
||||
for (const arg of ["--skip-build", "constructor", "toString", "__proto__"]) {
|
||||
expect(() => parseCodeModeMatrixOptions(["--model", "ollama/qwen3.5:9b", arg, "1"])).toThrow(
|
||||
`Unknown argument: ${arg}`,
|
||||
);
|
||||
}
|
||||
expect(() =>
|
||||
parseCodeModeMatrixOptions([
|
||||
"--model",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { expectDefined } from "../packages/normalization-core/src/expect.ts";
|
||||
import { compareAscii as compareText } from "./lib/canonical-json.mjs";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
import { NATIVE_I18N_LOCALES } from "./native-i18n-locales.ts";
|
||||
|
||||
|
|
@ -126,10 +127,6 @@ export type AndroidUiLiteralFinding = {
|
|||
source: string;
|
||||
};
|
||||
|
||||
function compareText(left: string, right: string): number {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
function decodeXml(value: string): string {
|
||||
return value
|
||||
.replaceAll("<", "<")
|
||||
|
|
@ -378,7 +375,7 @@ export function findUnusedAndroidResourceKeys(
|
|||
return [...keys].filter((key) => !references.has(key));
|
||||
}
|
||||
|
||||
function lineNumber(source: string, offset: number): number {
|
||||
export function lineNumber(source: string, offset: number): number {
|
||||
return source.slice(0, offset).split("\n").length;
|
||||
}
|
||||
|
||||
|
|
@ -602,7 +599,7 @@ function createDoubleQuoteScanner() {
|
|||
};
|
||||
}
|
||||
|
||||
function findClosingDelimiter(
|
||||
export function findClosingDelimiter(
|
||||
source: string,
|
||||
openingOffset: number,
|
||||
opening: string,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { decodeXml } from "../src/shared/xml.ts";
|
||||
import { selectDeterministicTranslation } from "./android-app-i18n.ts";
|
||||
import { compareAscii as compareCodeUnits } from "./lib/canonical-json.mjs";
|
||||
import { NATIVE_I18N_LOCALES } from "./native-i18n-locales.ts";
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -345,10 +347,6 @@ function formatTokens(value: string): string[] {
|
|||
return [...value.matchAll(FORMAT_RE)].map((match) => match[0]).toSorted();
|
||||
}
|
||||
|
||||
function compareCodeUnits(left: string, right: string): number {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
export function serializeAppleCatalog(catalog: Catalog): string {
|
||||
const topLevelEntries = Object.entries(catalog);
|
||||
const lines = ["{"];
|
||||
|
|
@ -412,7 +410,7 @@ export function selectInfoPlistTranslation(
|
|||
(candidate) => candidate.trim() && candidate.trim() !== source.trim(),
|
||||
);
|
||||
if (translatedCandidates.length > 0) {
|
||||
return chooseTranslation(source, translatedCandidates);
|
||||
return selectDeterministicTranslation(source, translatedCandidates);
|
||||
}
|
||||
return existing?.source === source && existing.value.trim() ? existing.value : source;
|
||||
}
|
||||
|
|
@ -517,21 +515,6 @@ function appleCatalogValue(value: string): string {
|
|||
);
|
||||
}
|
||||
|
||||
function chooseTranslation(source: string, translations: readonly string[]): string {
|
||||
// Apple catalogs key by source, so duplicate native contexts must converge.
|
||||
// Preserve shipped values first; otherwise choose deterministically and report every conflict.
|
||||
const counts = new Map<string, number>();
|
||||
for (const translation of translations) {
|
||||
counts.set(translation, (counts.get(translation) ?? 0) + 1);
|
||||
}
|
||||
return (
|
||||
[...counts].toSorted(([leftValue, leftCount], [rightValue, rightCount]) => {
|
||||
const sourcePenalty = Number(leftValue === source) - Number(rightValue === source);
|
||||
return sourcePenalty || rightCount - leftCount || compareCodeUnits(leftValue, rightValue);
|
||||
})[0]?.[0] ?? source
|
||||
);
|
||||
}
|
||||
|
||||
function buildAppleCatalog(
|
||||
existingCatalog: Catalog,
|
||||
nativeSource: NativeSourceArtifact,
|
||||
|
|
@ -589,7 +572,8 @@ function buildAppleCatalog(
|
|||
};
|
||||
continue;
|
||||
}
|
||||
const value = chooseTranslation(source, candidates);
|
||||
const value =
|
||||
candidates.length === 0 ? source : selectDeterministicTranslation(source, candidates);
|
||||
localizations[locale] = {
|
||||
stringUnit: {
|
||||
state: value === source ? "new" : "translated",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { isRecord } from "../packages/normalization-core/src/record-coerce.ts";
|
|||
import { sliceUtf16Safe } from "../packages/normalization-core/src/utf16-slice.ts";
|
||||
import type { createAgentTurnService } from "../src/gateway/agent-turn/agent-turn-service.js";
|
||||
import type { SessionsListResult } from "../src/gateway/session-utils.types.js";
|
||||
import { createDeferredCore } from "../src/shared/deferred.ts";
|
||||
import { applyMockOpenAiModelConfig } from "./e2e/lib/fixtures/mock-openai-config.mjs";
|
||||
import {
|
||||
summarizeMockInferenceRequest,
|
||||
|
|
@ -2161,34 +2162,21 @@ async function runGatewaySample(
|
|||
}
|
||||
}
|
||||
const historyClients = await concurrently(
|
||||
Array.from({ length: options.historyClients }, async () => {
|
||||
const historyClient = await connectGateway(port, setupDeadlineAt, protocolVersion, false);
|
||||
ownClient(historyClient);
|
||||
return historyClient;
|
||||
}),
|
||||
Array.from({ length: options.historyClients }, async () =>
|
||||
ownClient(await connectGateway(port, setupDeadlineAt, protocolVersion, false)),
|
||||
),
|
||||
);
|
||||
const sessionUpdateClients = await concurrently(
|
||||
Array.from(
|
||||
{ length: options.sessionUpdates > 0 ? options.sessionUpdateClients : 0 },
|
||||
async () => {
|
||||
const updateClient = await connectGateway(
|
||||
port,
|
||||
setupDeadlineAt,
|
||||
protocolVersion,
|
||||
false,
|
||||
);
|
||||
ownClient(updateClient);
|
||||
return updateClient;
|
||||
},
|
||||
async () =>
|
||||
ownClient(await connectGateway(port, setupDeadlineAt, protocolVersion, false)),
|
||||
),
|
||||
);
|
||||
const subscriptionProbeClient =
|
||||
options.subscribers > 0
|
||||
? await connectGateway(port, setupDeadlineAt, protocolVersion, false)
|
||||
? ownClient(await connectGateway(port, setupDeadlineAt, protocolVersion, false))
|
||||
: undefined;
|
||||
if (subscriptionProbeClient) {
|
||||
ownClient(subscriptionProbeClient);
|
||||
}
|
||||
if (!live) {
|
||||
mockCheckpoints.push(await readMockRequests(mockPort, setupDeadlineAt));
|
||||
}
|
||||
|
|
@ -2272,10 +2260,7 @@ async function runGatewaySample(
|
|||
let browserDone = !browserProbe;
|
||||
const workloadDone = () => probesStopped || (turnsDone && updatesDone && browserDone);
|
||||
let startedTurnCount = 0;
|
||||
let resolveAllTurnsStarted!: () => void;
|
||||
const allTurnsStarted = new Promise<void>((resolve) => {
|
||||
resolveAllTurnsStarted = resolve;
|
||||
});
|
||||
const { promise: allTurnsStarted, resolve: resolveAllTurnsStarted } = createDeferredCore();
|
||||
if (!live) {
|
||||
providerBeforeLoad = readProviderRequestLog(requestLogPath).length;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Web fetch benchmark covers direct response loading, HTML extraction, and fallback cleanup.
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import type { OpenClawConfig } from "../src/config/types.openclaw.js";
|
||||
import type { LookupFn } from "../src/infra/net/ssrf.js";
|
||||
import * as cliArgs from "./lib/arg-utils.mts";
|
||||
import { writeReportArtifact } from "./lib/report-cli-helpers.mts";
|
||||
|
||||
type BenchmarkCaseId = (typeof ALL_CASE_IDS)[number];
|
||||
|
||||
|
|
@ -285,74 +284,72 @@ async function loadCaseFactory(): Promise<() => Record<BenchmarkCaseId, Benchmar
|
|||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
return {
|
||||
"tool-create": {
|
||||
label: "create web_fetch tool",
|
||||
run: () => {
|
||||
createTool();
|
||||
},
|
||||
return () => ({
|
||||
"tool-create": {
|
||||
label: "create web_fetch tool",
|
||||
run: () => {
|
||||
createTool();
|
||||
},
|
||||
"tool-text": fetchCase("execute text/plain fetch", TEXT_BODY, "text/plain; charset=utf-8", {
|
||||
url: "https://example.com/plain",
|
||||
}),
|
||||
"tool-markdown": fetchCase(
|
||||
"execute text/markdown fetch",
|
||||
MARKDOWN_BODY,
|
||||
"text/markdown; charset=utf-8",
|
||||
{ url: "https://example.com/markdown" },
|
||||
),
|
||||
"tool-html-article": fetchCase(
|
||||
"execute article HTML fetch",
|
||||
ARTICLE_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/article" },
|
||||
),
|
||||
"tool-html-article-text": fetchCase(
|
||||
"execute article HTML fetch as text",
|
||||
ARTICLE_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/article-text", extractMode: "text" },
|
||||
),
|
||||
"tool-html-shell": fetchCase(
|
||||
"execute shell HTML fallback fetch",
|
||||
SHELL_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/shell" },
|
||||
),
|
||||
"extract-readable-article": {
|
||||
label: "extract readable article HTML",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article",
|
||||
extractMode: "markdown",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
},
|
||||
"tool-text": fetchCase("execute text/plain fetch", TEXT_BODY, "text/plain; charset=utf-8", {
|
||||
url: "https://example.com/plain",
|
||||
}),
|
||||
"tool-markdown": fetchCase(
|
||||
"execute text/markdown fetch",
|
||||
MARKDOWN_BODY,
|
||||
"text/markdown; charset=utf-8",
|
||||
{ url: "https://example.com/markdown" },
|
||||
),
|
||||
"tool-html-article": fetchCase(
|
||||
"execute article HTML fetch",
|
||||
ARTICLE_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/article" },
|
||||
),
|
||||
"tool-html-article-text": fetchCase(
|
||||
"execute article HTML fetch as text",
|
||||
ARTICLE_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/article-text", extractMode: "text" },
|
||||
),
|
||||
"tool-html-shell": fetchCase(
|
||||
"execute shell HTML fallback fetch",
|
||||
SHELL_HTML,
|
||||
"text/html; charset=utf-8",
|
||||
{ url: "https://example.com/shell" },
|
||||
),
|
||||
"extract-readable-article": {
|
||||
label: "extract readable article HTML",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article",
|
||||
extractMode: "markdown",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
"extract-readable-article-text": {
|
||||
label: "extract readable article HTML as text",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article-text",
|
||||
extractMode: "text",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
},
|
||||
"extract-readable-article-text": {
|
||||
label: "extract readable article HTML as text",
|
||||
run: async () => {
|
||||
await extractReadableContent({
|
||||
html: ARTICLE_HTML,
|
||||
url: "https://example.com/article-text",
|
||||
extractMode: "text",
|
||||
config: toolConfig,
|
||||
});
|
||||
},
|
||||
"extract-basic-shell": {
|
||||
label: "extract basic shell HTML",
|
||||
run: async () => {
|
||||
await extractBasicHtmlContent({
|
||||
html: SHELL_HTML,
|
||||
extractMode: "markdown",
|
||||
});
|
||||
},
|
||||
},
|
||||
"extract-basic-shell": {
|
||||
label: "extract basic shell HTML",
|
||||
run: async () => {
|
||||
await extractBasicHtmlContent({
|
||||
html: SHELL_HTML,
|
||||
extractMode: "markdown",
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function measureCase(
|
||||
|
|
@ -414,10 +411,7 @@ async function main(): Promise<void> {
|
|||
rssMb: Math.round((process.memoryUsage().rss / 1024 / 1024) * 10) / 10,
|
||||
};
|
||||
});
|
||||
if (options.output) {
|
||||
await mkdir(path.dirname(options.output), { recursive: true });
|
||||
await writeFile(options.output, `${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
await writeReportArtifact(options.output ?? null, `${JSON.stringify(report, null, 2)}\n`);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ async function createFixture(params: {
|
|||
await fs.mkdir(workspace);
|
||||
if (scenario === "inventory" || scenario === "delta" || scenario === "unchanged") {
|
||||
await exec("git", ["-c", `core.hooksPath=${os.devNull}`, "init", "--quiet", workspace], {
|
||||
env: { ...isolatedEnv(root), GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: os.devNull },
|
||||
env: isolatedEnv(root),
|
||||
});
|
||||
}
|
||||
if (scenario === "inventory" || scenario === "manifest") {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ import {
|
|||
} from "../src/gateway/control-ui-route-preloads.ts";
|
||||
import { reportLimitViolations } from "./lib/check-limits.mts";
|
||||
import { CONTROL_UI_LOCALE_ENTRIES } from "./lib/control-ui-i18n-config.ts";
|
||||
|
||||
function isMetricsRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
import { isRecord } from "./lib/record-shared.mjs";
|
||||
import { escapeRegExp } from "./lib/regexp.mjs";
|
||||
|
||||
const KIB = 1024;
|
||||
const STARTUP_JS_BASELINE_RATCHET_BYTES = 4096;
|
||||
|
|
@ -139,10 +137,6 @@ function largestAsset(assets: Array<ReturnType<typeof readAssetMetrics>>) {
|
|||
)[0]!;
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
||||
}
|
||||
|
||||
function controlUiLocaleAssetIdentity(
|
||||
file: string,
|
||||
): { locale: string; kind: "base" | "configHints" } | null {
|
||||
|
|
@ -541,7 +535,7 @@ function isIsoDate(value: string): boolean {
|
|||
function readControlUiStartupBudgetBaseline(baselinePath: string): ControlUiStartupBudgetBaseline {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
|
||||
const record: Record<string, unknown> = isMetricsRecord(parsed) ? parsed : {};
|
||||
const record: Record<string, unknown> = isRecord(parsed) ? parsed : {};
|
||||
const { startupJsGzipBytes, reason, updatedAt } = record;
|
||||
if (
|
||||
typeof startupJsGzipBytes !== "number" ||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
compareRatchetCounts,
|
||||
parseRatchetArgs,
|
||||
reportRatchetFailures,
|
||||
reportRatchetSuccess,
|
||||
resolveRatchetBase,
|
||||
} from "./lib/shrink-ratchet.mts";
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ export function main(root = process.cwd(), argv = process.argv.slice(2)) {
|
|||
) {
|
||||
return 1;
|
||||
}
|
||||
reportRatchetSuccess("SQLite worker ratchet OK: no T1 call-count growth against " + base + ".");
|
||||
console.log("SQLite worker ratchet OK: no T1 call-count growth against " + base + ".");
|
||||
return 0;
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
loadRatchetSnapshot,
|
||||
loadRatchetSources,
|
||||
parseRatchetScalar,
|
||||
reportRatchetSuccess,
|
||||
} from "./lib/shrink-ratchet.mts";
|
||||
|
||||
const BUDGET_PATH = "config/env-var-count-budget.txt";
|
||||
|
|
@ -161,7 +160,7 @@ export function main(
|
|||
throw new Error(messages.join("\n"));
|
||||
}
|
||||
if (messages.length === 0 && growth.length === 0) {
|
||||
reportRatchetSuccess(`OPENCLAW_* count ${names.length}/${budget}`);
|
||||
console.log(`OPENCLAW_* count ${names.length}/${budget}`);
|
||||
}
|
||||
return names.length;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
resolveTimerTimeoutMs,
|
||||
} from "../packages/normalization-core/src/number-coercion.ts";
|
||||
import { collectFilesSync } from "./check-file-utils.ts";
|
||||
import { appendBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import { appendBoundedTail, formatBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import {
|
||||
portableRelativePath,
|
||||
readArtifactRecord,
|
||||
|
|
@ -159,13 +159,6 @@ function createStepOutputCapture(): StepOutputCapture {
|
|||
return { text: "", truncatedChars: 0 };
|
||||
}
|
||||
|
||||
function formatCapturedStepOutput(buffer: StepOutputCapture) {
|
||||
if (buffer.truncatedChars === 0) {
|
||||
return buffer.text;
|
||||
}
|
||||
return `[output truncated ${buffer.truncatedChars} chars; showing tail]\n${buffer.text}`;
|
||||
}
|
||||
|
||||
function isPositiveFinite(value: number | undefined): value is number {
|
||||
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
||||
}
|
||||
|
|
@ -174,9 +167,6 @@ function isPositiveInteger(value: number | undefined): value is number {
|
|||
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the successful boundary compile summary.
|
||||
*/
|
||||
export function formatBoundaryCheckSuccessSummary(params: BoundarySummaryParams = {}) {
|
||||
const lines = ["extension package boundary check passed"];
|
||||
if (params.mode) {
|
||||
|
|
@ -206,9 +196,6 @@ export function formatBoundaryCheckSuccessSummary(params: BoundarySummaryParams
|
|||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats skipped compile progress for fresh extension canaries.
|
||||
*/
|
||||
export function formatSkippedCompileProgress(params: SkippedCompileParams = {}) {
|
||||
const skippedCount = params.skippedCount ?? 0;
|
||||
const totalCount = params.totalCount ?? 0;
|
||||
|
|
@ -223,9 +210,6 @@ export function formatSkippedCompileProgress(params: SkippedCompileParams = {})
|
|||
return `skipped ${skippedCount} fresh plugin compiles\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats slow extension compile diagnostics.
|
||||
*/
|
||||
export function formatSlowCompileSummary(params: SlowCompileParams = {}) {
|
||||
const compileTimings = Array.isArray(params.compileTimings) ? params.compileTimings : [];
|
||||
if (compileTimings.length === 0) {
|
||||
|
|
@ -242,9 +226,6 @@ export function formatSlowCompileSummary(params: SlowCompileParams = {}) {
|
|||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a failed boundary-check child process step.
|
||||
*/
|
||||
export function formatStepFailure(label: string, params: StepFailureParams = {}) {
|
||||
const stdoutSection = summarizeOutputSection("stdout", params.stdout ?? "");
|
||||
const stderrSection = summarizeOutputSection("stderr", params.stderr ?? "");
|
||||
|
|
@ -363,8 +344,8 @@ export async function runNodeStepAsync(
|
|||
});
|
||||
}
|
||||
return {
|
||||
stdout: formatCapturedStepOutput(stdout),
|
||||
stderr: formatCapturedStepOutput(stderr),
|
||||
stdout: formatBoundedTail(stdout),
|
||||
stderr: formatBoundedTail(stderr),
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
@ -383,8 +364,8 @@ export async function runNodeStepAsync(
|
|||
? "signal"
|
||||
: "spawn-error";
|
||||
const detail = {
|
||||
stdout: formatCapturedStepOutput(stdout),
|
||||
stderr: formatCapturedStepOutput(stderr),
|
||||
stdout: formatBoundedTail(stdout),
|
||||
stderr: formatBoundedTail(stderr),
|
||||
kind,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
note:
|
||||
|
|
@ -403,9 +384,6 @@ export async function runNodeStepAsync(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs boundary check steps with bounded concurrency.
|
||||
*/
|
||||
export async function runNodeStepsWithConcurrency(steps: BoundaryStep[], concurrency: number) {
|
||||
const abortController = new AbortController();
|
||||
let firstFailure: unknown = null;
|
||||
|
|
@ -445,9 +423,6 @@ export async function runNodeStepsWithConcurrency(steps: BoundaryStep[], concurr
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves canary artifact paths for an extension boundary compile.
|
||||
*/
|
||||
export function resolveCanaryArtifactPaths(extensionId: string, rootDir = repoRoot) {
|
||||
const extensionRoot = resolve(rootDir, "extensions", extensionId);
|
||||
return {
|
||||
|
|
@ -457,9 +432,6 @@ export function resolveCanaryArtifactPaths(extensionId: string, rootDir = repoRo
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes canary artifacts for one extension.
|
||||
*/
|
||||
function cleanupCanaryArtifacts(extensionId: string, rootDir = repoRoot) {
|
||||
const { canaryPath, tsconfigPath } = resolveCanaryArtifactPaths(extensionId, rootDir);
|
||||
rmSync(canaryPath, { force: true });
|
||||
|
|
@ -467,18 +439,12 @@ function cleanupCanaryArtifacts(extensionId: string, rootDir = repoRoot) {
|
|||
rmSync(resolveBoundaryInputReceiptPath(`${extensionId}-canary`, rootDir), { force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes canary artifacts for multiple extensions.
|
||||
*/
|
||||
function cleanupCanaryArtifactsForExtensions(extensionIds: string[], rootDir = repoRoot) {
|
||||
for (const extensionId of extensionIds) {
|
||||
cleanupCanaryArtifacts(extensionId, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs signal/exit cleanup for extension canary artifacts.
|
||||
*/
|
||||
export function installCanaryArtifactCleanup(
|
||||
extensionIds: string[],
|
||||
params: BoundaryCheckParams = {},
|
||||
|
|
@ -730,9 +696,6 @@ async function runCanaryCheck(extensionIds: string[]) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the extension package TypeScript boundary check.
|
||||
*/
|
||||
async function runBoundaryCheck(argv: string[]) {
|
||||
const startedAt = Date.now();
|
||||
const mode = parseMode(argv);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
loadRatchetSnapshot,
|
||||
loadRatchetSources,
|
||||
parseRatchetArgs,
|
||||
reportRatchetSuccess,
|
||||
resolveRatchetBase,
|
||||
} from "./lib/shrink-ratchet.mts";
|
||||
|
||||
|
|
@ -204,7 +203,7 @@ export function main(root = process.cwd(), argv = process.argv.slice(2)) {
|
|||
return 1;
|
||||
}
|
||||
if (increased.length === 0) {
|
||||
reportRatchetSuccess(
|
||||
console.log(
|
||||
`Line-cap ratchet OK: ${paths.length} changed source files; no new violations or over-cap growth.`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import {
|
|||
parseRatchetArgs,
|
||||
parseRatchetPaths,
|
||||
reportRatchetFailures,
|
||||
reportRatchetSuccess,
|
||||
resolveRatchetBase,
|
||||
} from "./lib/shrink-ratchet.mts";
|
||||
import { collectTypeScriptCommentRanges } from "./lib/ts-guard-utils.mts";
|
||||
|
|
@ -254,9 +253,7 @@ export function main(
|
|||
.filter((entry) => current.includes(entry))
|
||||
.toSorted(compareStrings);
|
||||
writeBaseline(root, kept);
|
||||
reportRatchetSuccess(
|
||||
"Pruned " + BASELINE_PATH + ": " + baseline.size + " -> " + kept.length + ".",
|
||||
);
|
||||
console.log("Pruned " + BASELINE_PATH + ": " + baseline.size + " -> " + kept.length + ".");
|
||||
return 0;
|
||||
}
|
||||
if (
|
||||
|
|
@ -272,9 +269,7 @@ export function main(
|
|||
}
|
||||
|
||||
if (added.length + expanded.length + stale.length === 0) {
|
||||
reportRatchetSuccess(
|
||||
"max-lines ratchet OK: " + current.length + " grandfathered suppressions.",
|
||||
);
|
||||
console.log("max-lines ratchet OK: " + current.length + " grandfathered suppressions.");
|
||||
}
|
||||
return 0;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -120,8 +120,6 @@ async function runPluginExtensionImportBoundaryCheck(): Promise<0 | 1> {
|
|||
return 1;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
runAsScript(import.meta.url, async () => {
|
||||
process.exitCode = await runPluginExtensionImportBoundaryCheck();
|
||||
}
|
||||
|
||||
runAsScript(import.meta.url, main);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
resolveTimerTimeoutMs,
|
||||
} from "../packages/normalization-core/src/number-coercion.ts";
|
||||
import { normalizeCsvOrLooseStringList } from "../packages/normalization-core/src/string-normalization.ts";
|
||||
import { chunkItems } from "../src/utils/chunk-items.ts";
|
||||
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mts";
|
||||
import { hasUnjoinedWork, runManagedCommand } from "./lib/managed-child-process.mts";
|
||||
import {
|
||||
|
|
@ -371,14 +372,6 @@ function sourceOpenclawCommand(repoRoot: string, args: string[]) {
|
|||
};
|
||||
}
|
||||
|
||||
function chunkArray<Value>(values: Value[], chunkSize: number) {
|
||||
const chunks: Value[][] = [];
|
||||
for (let index = 0; index < values.length; index += chunkSize) {
|
||||
chunks.push(values.slice(index, index + chunkSize));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
export function toRepoRelativePath(repoRoot: string, absolutePath: string) {
|
||||
const relativePath = path.relative(repoRoot, absolutePath);
|
||||
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
||||
|
|
@ -798,7 +791,7 @@ async function runSlashHelpProbes(params: GauntletContext) {
|
|||
async function runQaChunks(params: GauntletContext) {
|
||||
const chunks = [
|
||||
...(params.qaBaseline ? [{ label: "baseline", plugins: [] }] : []),
|
||||
...chunkArray(params.plugins, params.qaPluginChunkSize).map((plugins, index) => ({
|
||||
...chunkItems(params.plugins, params.qaPluginChunkSize).map((plugins, index) => ({
|
||||
label: `chunk-${String(index).padStart(2, "0")}`,
|
||||
plugins,
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function parsePrNumberList(value) {
|
|||
...new Set(
|
||||
text
|
||||
.split(/[\s,]+/u)
|
||||
.map((part) => part.trim().replace(/^#/u, ""))
|
||||
.map((part) => part.replace(/^#/u, ""))
|
||||
.filter(Boolean)
|
||||
.map((part) => {
|
||||
if (!/^\d+$/u.test(part)) {
|
||||
|
|
@ -62,7 +62,6 @@ export function parseArgs(argv, env = process.env) {
|
|||
} else if (arg === "--labels") {
|
||||
args.labels = next()
|
||||
.split(/[\s,]+/u)
|
||||
.map((label) => label.trim())
|
||||
.filter(Boolean);
|
||||
} else if (arg === "--help" || arg === "-h") {
|
||||
help = true;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import {
|
|||
} from "./lib/code-mode-matrix-provider.ts";
|
||||
import type { MatrixUsageAccounting } from "./lib/code-mode-matrix-usage.ts";
|
||||
import { previewForDevToolLog, redactJsonValueForDevToolLog } from "./lib/dev-tooling-safety.ts";
|
||||
import { groupBy } from "./lib/group-by.mts";
|
||||
|
||||
export { validateQaEvidenceSummaryJson };
|
||||
|
||||
|
|
@ -333,7 +334,7 @@ export function parseCodeModeMatrixOptions(
|
|||
"--max-known-cost-usd": "maxKnownCostUsd",
|
||||
"--max-wall-seconds": "maxWallSeconds",
|
||||
};
|
||||
const admissionKey = admissionKeys[arg];
|
||||
const admissionKey = Object.hasOwn(admissionKeys, arg) ? admissionKeys[arg] : undefined;
|
||||
if (admissionKey) {
|
||||
recordOnce(arg);
|
||||
const raw = requireOptionArgument(argv, index, arg);
|
||||
|
|
@ -1377,13 +1378,7 @@ function summarizeMetric(values: (number | undefined)[]) {
|
|||
}
|
||||
|
||||
function summarizeResults(results: CodeModeMatrixCellResult[]) {
|
||||
const groups = new Map<string, CodeModeMatrixCellResult[]>();
|
||||
for (const result of results) {
|
||||
const key = `${result.model}\0${result.mode}\0${result.task}`;
|
||||
const group = groups.get(key) ?? [];
|
||||
group.push(result);
|
||||
groups.set(key, group);
|
||||
}
|
||||
const groups = groupBy(results, (result) => `${result.model}\0${result.mode}\0${result.task}`);
|
||||
return [...groups.entries()].map(([key, group]) => {
|
||||
const [model, mode, task] = key.split("\0");
|
||||
const passed = group.filter((result) => result.passed);
|
||||
|
|
@ -1953,11 +1948,7 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function isCliEntrypoint(): boolean {
|
||||
const entrypoint = process.argv[1];
|
||||
return Boolean(entrypoint && import.meta.url === pathToFileURL(path.resolve(entrypoint)).href);
|
||||
}
|
||||
|
||||
if (isCliEntrypoint()) {
|
||||
const entrypoint = process.argv[1];
|
||||
if (entrypoint && import.meta.url === pathToFileURL(path.resolve(entrypoint)).href) {
|
||||
await main();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ TRUSTED_HARNESS_DIR="$(cd "$TRUSTED_HARNESS_DIR" && pwd)"
|
|||
CANDIDATE_ROOT="$(cd "$CANDIDATE_ROOT" && pwd)"
|
||||
ROOT_DIR="$TRUSTED_HARNESS_DIR"
|
||||
source "$TRUSTED_HARNESS_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$TRUSTED_HARNESS_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-codex-npm-plugin-live-e2e" OPENCLAW_CODEX_NPM_PLUGIN_E2E_IMAGE)"
|
||||
DOCKER_TARGET="${OPENCLAW_CODEX_NPM_PLUGIN_DOCKER_TARGET:-bare}"
|
||||
|
|
@ -101,22 +100,18 @@ trap cleanup EXIT
|
|||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" codex-npm-plugin-live "$CANDIDATE_ROOT/scripts/e2e/Dockerfile" "$CANDIDATE_ROOT" "$DOCKER_TARGET"
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-npm-plugin-live "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-npm-plugin-live "$PACKAGE_TGZ")"
|
||||
else
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_CODEX_NPM_PLUGIN_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
local harness_root="$ROOT_DIR"
|
||||
ROOT_DIR="$CANDIDATE_ROOT"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-npm-plugin-live)"
|
||||
ROOT_DIR="$harness_root"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
PACKAGE_TGZ="$(
|
||||
ROOT_DIR="$CANDIDATE_ROOT"
|
||||
docker_e2e_prepare_package_tgz codex-npm-plugin-live
|
||||
)"
|
||||
fi
|
||||
|
||||
configure_codex_plugin_registry_candidate() {
|
||||
local source_path="$1"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
|
||||
source "$ROOT_DIR/scripts/lib/frozen-target-compat.sh"
|
||||
|
||||
|
|
@ -53,19 +52,11 @@ trap cleanup EXIT
|
|||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" codex-on-demand "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-on-demand "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_CODEX_ON_DEMAND_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-on-demand)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
if [ -z "$PACKAGE_TGZ" ] && [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_CODEX_ON_DEMAND_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-on-demand "$PACKAGE_TGZ")"
|
||||
|
||||
if [ -z "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] &&
|
||||
[ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ] &&
|
||||
|
|
|
|||
|
|
@ -11,14 +11,10 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|||
TARGET_ROOT_DIR="$(cd "${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$ROOT_DIR}" && pwd)"
|
||||
TARGET_CONTRACT_DIR="$TARGET_ROOT_DIR/scripts/e2e/lib/doctor-install-switch"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-doctor-install-switch-e2e" OPENCLAW_DOCTOR_INSTALL_SWITCH_E2E_IMAGE)"
|
||||
NPM_INSTALL_TIMEOUT="${OPENCLAW_E2E_NPM_INSTALL_TIMEOUT:-600s}"
|
||||
COMMAND_TIMEOUT="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}"
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz doctor-switch "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
|
|
|
|||
|
|
@ -141,61 +141,37 @@ start_kitchen_sink_clawhub_fixture_server() {
|
|||
return 1
|
||||
}
|
||||
|
||||
scan_logs_for_unexpected_errors() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs scan-logs
|
||||
}
|
||||
|
||||
configure_kitchen_sink_runtime() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs configure-runtime
|
||||
}
|
||||
|
||||
remove_kitchen_sink_channel_config() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs remove-channel-config
|
||||
}
|
||||
|
||||
assert_kitchen_sink_installed() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-installed
|
||||
}
|
||||
|
||||
assert_kitchen_sink_removed() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-removed
|
||||
}
|
||||
|
||||
assert_kitchen_sink_cutover_preinstalled() {
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-cutover-preinstalled
|
||||
}
|
||||
|
||||
run_success_scenario() {
|
||||
echo "Testing ${KITCHEN_SINK_LABEL} install from ${KITCHEN_SINK_SPEC}..."
|
||||
local install_args=("$KITCHEN_SINK_SPEC")
|
||||
if [ -n "${KITCHEN_SINK_PREINSTALL_SPEC:-}" ]; then
|
||||
run_kitchen_sink_fixture_logged "kitchen-sink-preinstall-${KITCHEN_SINK_LABEL}" plugins install "$KITCHEN_SINK_PREINSTALL_SPEC" --force
|
||||
assert_kitchen_sink_cutover_preinstalled
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-cutover-preinstalled
|
||||
install_args+=("--force")
|
||||
fi
|
||||
run_kitchen_sink_fixture_logged "kitchen-sink-install-${KITCHEN_SINK_LABEL}" plugins install "${install_args[@]}" --force
|
||||
configure_kitchen_sink_runtime
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs configure-runtime
|
||||
run_kitchen_sink_openclaw_logged "kitchen-sink-enable-${KITCHEN_SINK_LABEL}" plugins enable "$KITCHEN_SINK_ID"
|
||||
run_kitchen_sink_openclaw_capture "${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-plugins.json" plugins list --json
|
||||
run_kitchen_sink_openclaw_capture "${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect.json" plugins inspect "$KITCHEN_SINK_ID" --runtime --json
|
||||
run_kitchen_sink_openclaw_capture "${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect-all.json" plugins inspect --all --runtime --json
|
||||
assert_kitchen_sink_installed
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-installed
|
||||
if [ "$KITCHEN_SINK_SOURCE" = "clawhub" ]; then
|
||||
run_kitchen_sink_openclaw_logged "kitchen-sink-uninstall-${KITCHEN_SINK_LABEL}" plugins uninstall "$KITCHEN_SINK_SPEC" --force
|
||||
else
|
||||
run_kitchen_sink_openclaw_logged "kitchen-sink-uninstall-${KITCHEN_SINK_LABEL}" plugins uninstall "$KITCHEN_SINK_ID" --force
|
||||
fi
|
||||
remove_kitchen_sink_channel_config
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs remove-channel-config
|
||||
run_kitchen_sink_openclaw_capture "${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" plugins list --json
|
||||
assert_kitchen_sink_removed
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-removed
|
||||
}
|
||||
|
||||
run_failure_scenario() {
|
||||
echo "Testing expected ${KITCHEN_SINK_LABEL} install failure from ${KITCHEN_SINK_SPEC}..."
|
||||
run_expect_failure "install-${KITCHEN_SINK_LABEL}" openclaw_e2e_maybe_timeout "$KITCHEN_SINK_CLI_TIMEOUT" node "$OPENCLAW_ENTRY" plugins install "$KITCHEN_SINK_SPEC" --force
|
||||
remove_kitchen_sink_channel_config
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs remove-channel-config
|
||||
run_kitchen_sink_openclaw_capture "${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" plugins list --json
|
||||
assert_kitchen_sink_removed
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-removed
|
||||
}
|
||||
|
||||
run_kitchen_sink_sweep_main() {
|
||||
|
|
@ -246,7 +222,7 @@ run_kitchen_sink_sweep_main() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
scan_logs_for_unexpected_errors
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs scan-logs
|
||||
echo "kitchen-sink plugin Docker E2E passed (${scenario_count} scenario(s))"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,40 +141,6 @@ openclaw_plugins_fixture_exit_trap() {
|
|||
exit "$status"
|
||||
}
|
||||
|
||||
record_fixture_plugin_trust() {
|
||||
local plugin_id="$1"
|
||||
local plugin_root="$2"
|
||||
local enabled="$3"
|
||||
node scripts/e2e/lib/plugins/assertions.mjs record-fixture-plugin-trust "$plugin_id" "$plugin_root" "$enabled"
|
||||
}
|
||||
|
||||
write_demo_fixture_plugin() {
|
||||
local dir="$1"
|
||||
node scripts/e2e/lib/fixture.mjs plugin-demo "$dir"
|
||||
}
|
||||
|
||||
write_fixture_plugin() {
|
||||
local dir="$1"
|
||||
local id="$2"
|
||||
local version="$3"
|
||||
local method="$4"
|
||||
local name="$5"
|
||||
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$dir" "$id" "$version" "$method" "$name"
|
||||
}
|
||||
|
||||
write_fixture_plugin_with_cli() {
|
||||
local dir="$1"
|
||||
local id="$2"
|
||||
local version="$3"
|
||||
local method="$4"
|
||||
local name="$5"
|
||||
local cli_root="$6"
|
||||
local cli_output="$7"
|
||||
|
||||
node scripts/e2e/lib/fixture.mjs plugin-cli "$dir" "$id" "$version" "$method" "$name" "$cli_root" "$cli_output"
|
||||
}
|
||||
|
||||
pack_fixture_plugin_with_cli_registry_dependency() {
|
||||
local pack_dir="$1"
|
||||
local output_tgz="$2"
|
||||
|
|
@ -199,16 +165,6 @@ pack_fake_is_number_package() {
|
|||
tar -czf "$output_tgz" -C "$pack_dir" package
|
||||
}
|
||||
|
||||
write_fixture_plugin_with_vendored_dependency() {
|
||||
local dir="$1"
|
||||
local id="$2"
|
||||
local version="$3"
|
||||
local method="$4"
|
||||
local name="$5"
|
||||
|
||||
node scripts/e2e/lib/fixture.mjs plugin-vendored-dep "$dir" "$id" "$version" "$method" "$name"
|
||||
}
|
||||
|
||||
pack_fixture_plugin() {
|
||||
local pack_dir="$1"
|
||||
local output_tgz="$2"
|
||||
|
|
@ -218,7 +174,7 @@ pack_fixture_plugin() {
|
|||
local name="$6"
|
||||
|
||||
mkdir -p "$pack_dir/package"
|
||||
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
|
||||
tar -czf "$output_tgz" -C "$pack_dir" package
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +187,7 @@ pack_fixture_plugin_with_invalid_extension_entry() {
|
|||
local name="$6"
|
||||
|
||||
mkdir -p "$pack_dir/package"
|
||||
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
|
||||
node --input-type=module - "$pack_dir/package/package.json" <<'NODE'
|
||||
import fs from "node:fs";
|
||||
|
||||
|
|
@ -279,9 +235,3 @@ start_npm_fixture_registry() {
|
|||
echo "Timed out waiting for npm fixture registry." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
write_claude_bundle_fixture() {
|
||||
local bundle_root="$1"
|
||||
|
||||
node scripts/e2e/lib/fixture.mjs claude-bundle "$bundle_root"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ run_plugins_marketplace_scenario() {
|
|||
echo "Testing marketplace install and update flows..."
|
||||
marketplace_root="$HOME/.claude/plugins/marketplaces/fixture-marketplace"
|
||||
mkdir -p "$HOME/.claude/plugins" "$marketplace_root/.claude-plugin"
|
||||
write_fixture_plugin \
|
||||
node scripts/e2e/lib/fixture.mjs plugin \
|
||||
"$marketplace_root/plugins/marketplace-shortcut" \
|
||||
"marketplace-shortcut" \
|
||||
"0.0.1" \
|
||||
"demo.marketplace.shortcut.v1" \
|
||||
"Marketplace Shortcut"
|
||||
write_fixture_plugin \
|
||||
node scripts/e2e/lib/fixture.mjs plugin \
|
||||
"$marketplace_root/plugins/marketplace-direct" \
|
||||
"marketplace-direct" \
|
||||
"0.0.1" \
|
||||
|
|
@ -30,7 +30,7 @@ run_plugins_marketplace_scenario() {
|
|||
|
||||
node scripts/e2e/lib/plugins/assertions.mjs marketplace-records
|
||||
|
||||
write_fixture_plugin \
|
||||
node scripts/e2e/lib/fixture.mjs plugin \
|
||||
"$marketplace_root/plugins/marketplace-shortcut" \
|
||||
"marketplace-shortcut" \
|
||||
"0.0.2" \
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ OPENCLAW_PLUGIN_HOME="$HOME/.openclaw/$BUNDLED_PLUGIN_ROOT_DIR"
|
|||
|
||||
demo_plugin_id="demo-plugin"
|
||||
demo_plugin_root="$OPENCLAW_PLUGIN_HOME/$demo_plugin_id"
|
||||
write_demo_fixture_plugin "$demo_plugin_root"
|
||||
record_fixture_plugin_trust "$demo_plugin_id" "$demo_plugin_root" 1
|
||||
node scripts/e2e/lib/fixture.mjs plugin-demo "$demo_plugin_root"
|
||||
node scripts/e2e/lib/plugins/assertions.mjs record-fixture-plugin-trust "$demo_plugin_id" "$demo_plugin_root" 1
|
||||
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins.json" plugins list --json
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-inspect.json" plugins inspect demo-plugin --runtime --json
|
||||
|
|
@ -158,7 +158,7 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-tgz-removed
|
|||
|
||||
echo "Testing install from local folder (plugins.load.paths)..."
|
||||
dir_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-dir.XXXXXX")"
|
||||
write_fixture_plugin "$dir_plugin" demo-plugin-dir 0.0.1 demo.dir "Demo Plugin DIR"
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$dir_plugin" demo-plugin-dir 0.0.1 demo.dir "Demo Plugin DIR"
|
||||
|
||||
run_plugins_fixture_logged install-dir plugins install "$dir_plugin" --force
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins3.json" plugins list --json
|
||||
|
|
@ -175,7 +175,7 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-removed
|
|||
|
||||
echo "Testing install from local folder with preinstalled dependencies..."
|
||||
dir_deps_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-dir-deps.XXXXXX")"
|
||||
write_fixture_plugin_with_vendored_dependency "$dir_deps_plugin" demo-plugin-dir-deps 0.0.1 demo.dir.deps "Demo Plugin DIR Deps"
|
||||
node scripts/e2e/lib/fixture.mjs plugin-vendored-dep "$dir_deps_plugin" demo-plugin-dir-deps 0.0.1 demo.dir.deps "Demo Plugin DIR Deps"
|
||||
|
||||
run_plugins_fixture_logged install-dir-deps plugins install "$dir_deps_plugin" --force
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps.json" plugins list --json
|
||||
|
|
@ -189,7 +189,7 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps-removed
|
|||
|
||||
echo "Testing install from npm spec (file:)..."
|
||||
file_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-filepack.XXXXXX")"
|
||||
write_fixture_plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE"
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE"
|
||||
|
||||
run_plugins_fixture_logged install-file plugins install "file:$file_pack_dir/package" --force
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins4.json" plugins list --json
|
||||
|
|
@ -252,7 +252,7 @@ echo "Testing install from git repo and plugin CLI execution..."
|
|||
git_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git.XXXXXX")"
|
||||
git_repo="$git_fixture_root/repo"
|
||||
git_repo_url="file://$git_repo"
|
||||
write_fixture_plugin_with_cli "$git_repo" demo-plugin-git 0.0.1 demo.git "Demo Plugin Git" demo-git "demo-plugin-git:pong"
|
||||
node scripts/e2e/lib/fixture.mjs plugin-cli "$git_repo" demo-plugin-git 0.0.1 demo.git "Demo Plugin Git" demo-git "demo-plugin-git:pong"
|
||||
git -C "$git_repo" init -q
|
||||
git -C "$git_repo" config user.email "docker-e2e@openclaw.local"
|
||||
git -C "$git_repo" config user.name "OpenClaw Docker E2E"
|
||||
|
|
@ -275,7 +275,7 @@ echo "Testing git plugin update from moving ref..."
|
|||
git_update_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git-update.XXXXXX")"
|
||||
git_update_repo="$git_update_fixture_root/repo"
|
||||
git_update_repo_url="file://$git_update_repo"
|
||||
write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.1 demo.git.update.v1 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v1"
|
||||
node scripts/e2e/lib/fixture.mjs plugin-cli "$git_update_repo" demo-plugin-git-update 0.0.1 demo.git.update.v1 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v1"
|
||||
git -C "$git_update_repo" init -q
|
||||
git -C "$git_update_repo" config user.email "docker-e2e@openclaw.local"
|
||||
git -C "$git_update_repo" config user.name "OpenClaw Docker E2E"
|
||||
|
|
@ -285,7 +285,7 @@ git -C "$git_update_repo" commit -qm "test fixture v1"
|
|||
git_update_ref_v1="$(git -C "$git_update_repo" rev-parse HEAD)"
|
||||
|
||||
run_plugins_fixture_logged install-git-update plugins install "git:$git_update_repo_url@main" --force
|
||||
write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.2 demo.git.update.v2 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v2"
|
||||
node scripts/e2e/lib/fixture.mjs plugin-cli "$git_update_repo" demo-plugin-git-update 0.0.2 demo.git.update.v2 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v2"
|
||||
git -C "$git_update_repo" add -A
|
||||
git -C "$git_update_repo" commit -qm "test fixture v2"
|
||||
|
||||
|
|
@ -299,8 +299,8 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-git-updated "$git_update_ref_
|
|||
echo "Testing Claude bundle enable and inspect flow..."
|
||||
bundle_plugin_id="claude-bundle-e2e"
|
||||
bundle_root="$OPENCLAW_PLUGIN_HOME/$bundle_plugin_id"
|
||||
write_claude_bundle_fixture "$bundle_root"
|
||||
record_fixture_plugin_trust "$bundle_plugin_id" "$bundle_root" 0
|
||||
node scripts/e2e/lib/fixture.mjs claude-bundle "$bundle_root"
|
||||
node scripts/e2e/lib/plugins/assertions.mjs record-fixture-plugin-trust "$bundle_plugin_id" "$bundle_root" 0
|
||||
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugins-bundle-disabled.json" plugins list --json
|
||||
node scripts/e2e/lib/plugins/assertions.mjs bundle-disabled
|
||||
|
|
@ -311,7 +311,7 @@ node scripts/e2e/lib/plugins/assertions.mjs bundle-inspect
|
|||
|
||||
echo "Testing plugin install visible after explicit restart..."
|
||||
slash_install_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-slash-install.XXXXXX")"
|
||||
write_fixture_plugin "$slash_install_dir" slash-install-plugin 0.0.1 demo.slash.install "Slash Install Plugin"
|
||||
node scripts/e2e/lib/fixture.mjs plugin "$slash_install_dir" slash-install-plugin 0.0.1 demo.slash.install "Slash Install Plugin"
|
||||
|
||||
run_plugins_fixture_logged install-slash-plugin plugins install "$slash_install_dir" --force
|
||||
run_plugins_openclaw_capture "$OPENCLAW_PLUGINS_TMP_DIR/plugin-command-install-show.json" plugins inspect slash-install-plugin --runtime --json
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ set -euo pipefail
|
|||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/openclaw-e2e-instance.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-live-plugin-tool-e2e" OPENCLAW_LIVE_PLUGIN_TOOL_E2E_IMAGE)"
|
||||
DOCKER_TARGET="${OPENCLAW_LIVE_PLUGIN_TOOL_DOCKER_TARGET:-bare}"
|
||||
|
|
@ -23,15 +22,7 @@ AGENT_OUTPUT_DUMP_BYTES="$(openclaw_e2e_read_nonnegative_int_env OPENCLAW_LIVE_P
|
|||
SESSION_SCAN_MAX_ENTRIES="$(openclaw_e2e_read_positive_int_env OPENCLAW_LIVE_PLUGIN_TOOL_SESSION_SCAN_MAX_ENTRIES 50000)"
|
||||
run_log=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${PACKAGE_TGZ:-}" ]; then
|
||||
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
|
||||
fi
|
||||
if [ -n "${run_log:-}" ]; then
|
||||
rm -f "$run_log"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_run "${PACKAGE_TGZ:-}" "${run_log:-}"' EXIT
|
||||
|
||||
if [ ! -f "$PROFILE_FILE" ] && [ -f "$HOME/.profile" ]; then
|
||||
PROFILE_FILE="$HOME/.profile"
|
||||
|
|
@ -39,19 +30,11 @@ fi
|
|||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" live-plugin-tool "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz live-plugin-tool "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_LIVE_PLUGIN_TOOL_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz live-plugin-tool)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
if [ -z "$PACKAGE_TGZ" ] && [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_LIVE_PLUGIN_TOOL_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz live-plugin-tool "$PACKAGE_TGZ")"
|
||||
|
||||
PROFILE_MOUNT=()
|
||||
PROFILE_STATUS="none"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-multi-node-update-e2e" OPENCLAW_MULTI_NODE_UPDATE_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_MULTI_NODE_UPDATE_E2E_SKIP_BUILD:-0}"
|
||||
|
|
@ -31,10 +30,7 @@ ARTIFACT_DIR="${OPENCLAW_MULTI_NODE_ARTIFACT_DIR:-$ROOT_DIR/.artifacts/multi-nod
|
|||
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
chmod -R a+rwX "$ARTIFACT_DIR" || true
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
# Build the bare e2e image and prepare the package tarball.
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" multi-node-update "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
|
||||
source "$ROOT_DIR/scripts/lib/frozen-target-compat.sh"
|
||||
|
||||
|
|
@ -40,15 +39,7 @@ STATUS_TEXT_MAX_BYTES="$(
|
|||
)"
|
||||
run_log=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${PACKAGE_TGZ:-}" ]; then
|
||||
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
|
||||
fi
|
||||
if [ -n "${run_log:-}" ]; then
|
||||
rm -f "$run_log"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_run "${PACKAGE_TGZ:-}" "${run_log:-}"' EXIT
|
||||
|
||||
case "$CHANNEL" in
|
||||
telegram | discord | slack) ;;
|
||||
|
|
@ -60,19 +51,11 @@ esac
|
|||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" npm-onboard-channel-agent "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz npm-onboard-channel-agent "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_NPM_ONBOARD_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz npm-onboard-channel-agent)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
if [ -z "$PACKAGE_TGZ" ] && [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_NPM_ONBOARD_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz npm-onboard-channel-agent "$PACKAGE_TGZ")"
|
||||
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
run_log="$(docker_e2e_run_log npm-onboard-channel-agent)"
|
||||
|
|
|
|||
|
|
@ -362,26 +362,7 @@ class MacosSmoke extends SmokeRunController<MacosOptions> {
|
|||
}),
|
||||
);
|
||||
await this.phases.phase("fresh.onboard-ref", 420, () => this.runRefOnboard());
|
||||
await this.phases.phase("fresh.gateway-start", 180, () => this.startManualGatewayIfNeeded());
|
||||
await this.phases.phase("fresh.gateway-status", 180, () => this.verifyGateway());
|
||||
this.status.freshGateway = "pass";
|
||||
await this.phases.phase("fresh.dashboard-load", 180, () => this.verifyDashboardLoad());
|
||||
this.status.freshDashboard = "pass";
|
||||
await this.phases.phase("fresh.first-agent-turn", this.agentTimeoutSeconds, () =>
|
||||
this.verifyTurn(),
|
||||
);
|
||||
this.status.freshAgent = "pass";
|
||||
if (this.discordEnabled()) {
|
||||
this.status.freshDiscord = "fail";
|
||||
await this.phases.phase("fresh.discord-config", 600, () => this.discord?.configure());
|
||||
await this.phases.phase("fresh.discord-gateway-ready", 180, () =>
|
||||
this.ensureDiscordGatewayReady(),
|
||||
);
|
||||
await this.phases.phase("fresh.discord-roundtrip", 180, () =>
|
||||
this.runDiscordRoundtrip("fresh"),
|
||||
);
|
||||
this.status.freshDiscord = "pass";
|
||||
}
|
||||
await this.runGatewaySmoke("fresh");
|
||||
}
|
||||
|
||||
protected async runUpgradeLane(): Promise<void> {
|
||||
|
|
@ -422,25 +403,29 @@ class MacosSmoke extends SmokeRunController<MacosOptions> {
|
|||
);
|
||||
}
|
||||
await this.phases.phase("upgrade.onboard-ref", 420, () => this.runRefOnboard());
|
||||
await this.phases.phase("upgrade.gateway-start", 180, () => this.startManualGatewayIfNeeded());
|
||||
await this.phases.phase("upgrade.gateway-status", 180, () => this.verifyGateway());
|
||||
this.status.upgradeGateway = "pass";
|
||||
await this.phases.phase("upgrade.dashboard-load", 180, () => this.verifyDashboardLoad());
|
||||
this.status.upgradeDashboard = "pass";
|
||||
await this.phases.phase("upgrade.first-agent-turn", this.agentTimeoutSeconds, () =>
|
||||
await this.runGatewaySmoke("upgrade");
|
||||
}
|
||||
|
||||
private async runGatewaySmoke(lane: "fresh" | "upgrade"): Promise<void> {
|
||||
await this.phases.phase(`${lane}.gateway-start`, 180, () => this.startManualGatewayIfNeeded());
|
||||
await this.phases.phase(`${lane}.gateway-status`, 180, () => this.verifyGateway());
|
||||
this.status[`${lane}Gateway`] = "pass";
|
||||
await this.phases.phase(`${lane}.dashboard-load`, 180, () => this.verifyDashboardLoad());
|
||||
this.status[`${lane}Dashboard`] = "pass";
|
||||
await this.phases.phase(`${lane}.first-agent-turn`, this.agentTimeoutSeconds, () =>
|
||||
this.verifyTurn(),
|
||||
);
|
||||
this.status.upgradeAgent = "pass";
|
||||
this.status[`${lane}Agent`] = "pass";
|
||||
if (this.discordEnabled()) {
|
||||
this.status.upgradeDiscord = "fail";
|
||||
await this.phases.phase("upgrade.discord-config", 600, () => this.discord?.configure());
|
||||
await this.phases.phase("upgrade.discord-gateway-ready", 180, () =>
|
||||
this.status[`${lane}Discord`] = "fail";
|
||||
await this.phases.phase(`${lane}.discord-config`, 600, () => this.discord?.configure());
|
||||
await this.phases.phase(`${lane}.discord-gateway-ready`, 180, () =>
|
||||
this.ensureDiscordGatewayReady(),
|
||||
);
|
||||
await this.phases.phase("upgrade.discord-roundtrip", 180, () =>
|
||||
this.runDiscordRoundtrip("upgrade"),
|
||||
await this.phases.phase(`${lane}.discord-roundtrip`, 180, () =>
|
||||
this.runDiscordRoundtrip(lane),
|
||||
);
|
||||
this.status.upgradeDiscord = "pass";
|
||||
this.status[`${lane}Discord`] = "pass";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -461,15 +446,7 @@ exec node "$entry" ${argv}`,
|
|||
const prlctlDeadline = Date.now() + 45_000;
|
||||
const deadline = Date.now() + timeoutSeconds * 1000;
|
||||
while (Date.now() < prlctlDeadline && Date.now() < deadline) {
|
||||
const result = run("prlctl", ["exec", this.options.vmName, "--current-user", "whoami"], {
|
||||
check: false,
|
||||
quiet: true,
|
||||
timeoutMs: this.phases.remainingTimeoutMs(),
|
||||
});
|
||||
const user = result.stdout.trim().replaceAll("\r", "").split("\n").at(-1) ?? "";
|
||||
if (result.status === 0 && /^[A-Za-z0-9._-]+$/.test(user)) {
|
||||
this.guestUser = user;
|
||||
this.guestTransport = "current-user";
|
||||
if (this.tryCurrentUser()) {
|
||||
return;
|
||||
}
|
||||
run("sleep", ["2"], { quiet: true });
|
||||
|
|
@ -484,15 +461,7 @@ exec node "$entry" ${argv}`,
|
|||
return;
|
||||
}
|
||||
while (Date.now() < deadline) {
|
||||
const result = run("prlctl", ["exec", this.options.vmName, "--current-user", "whoami"], {
|
||||
check: false,
|
||||
quiet: true,
|
||||
timeoutMs: this.phases.remainingTimeoutMs(),
|
||||
});
|
||||
const user = result.stdout.trim().replaceAll("\r", "").split("\n").at(-1) ?? "";
|
||||
if (result.status === 0 && /^[A-Za-z0-9._-]+$/.test(user)) {
|
||||
this.guestUser = user;
|
||||
this.guestTransport = "current-user";
|
||||
if (this.tryCurrentUser()) {
|
||||
return;
|
||||
}
|
||||
run("sleep", ["2"], { quiet: true });
|
||||
|
|
@ -500,6 +469,21 @@ exec node "$entry" ${argv}`,
|
|||
throw new Error("guest current user did not become available");
|
||||
}
|
||||
|
||||
private tryCurrentUser(): boolean {
|
||||
const result = run("prlctl", ["exec", this.options.vmName, "--current-user", "whoami"], {
|
||||
check: false,
|
||||
quiet: true,
|
||||
timeoutMs: this.phases.remainingTimeoutMs(),
|
||||
});
|
||||
const user = result.stdout.trim().replaceAll("\r", "").split("\n").at(-1) ?? "";
|
||||
if (result.status !== 0 || !/^[A-Za-z0-9._-]+$/.test(user)) {
|
||||
return false;
|
||||
}
|
||||
this.guestUser = user;
|
||||
this.guestTransport = "current-user";
|
||||
return true;
|
||||
}
|
||||
|
||||
private resolveDesktopUser(): string {
|
||||
return resolveMacosDesktopUser((args) => this.readDesktopUserOutput(args));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,17 +280,7 @@ class WindowsSmoke extends SmokeRunController<WindowsOptions> {
|
|||
),
|
||||
}),
|
||||
);
|
||||
await this.phases.phase("fresh.onboard-ref", 720, () => this.runRefOnboard());
|
||||
await this.phases.phase("fresh.gateway-restart", 420, () => this.gatewayAction("restart"));
|
||||
await this.phases.phase("fresh.gateway-status", 420, () => this.verifyGatewayReachable());
|
||||
this.status.freshGateway = "pass";
|
||||
await this.phases.phase("fresh.gateway-stop-before-local-agent", 420, () =>
|
||||
this.gatewayAction("stop"),
|
||||
);
|
||||
await this.phases.phase("fresh.first-agent-turn", this.agentTimeoutSeconds, () =>
|
||||
this.verifyTurn(),
|
||||
);
|
||||
this.status.freshAgent = "pass";
|
||||
await this.runGatewaySmoke("fresh");
|
||||
}
|
||||
|
||||
protected async runUpgradeLane(): Promise<void> {
|
||||
|
|
@ -345,17 +335,21 @@ class WindowsSmoke extends SmokeRunController<WindowsOptions> {
|
|||
this.status.upgradeVersion = await this.extractLastVersion("upgrade.update-dev");
|
||||
await this.phases.phase("upgrade.verify-dev-channel", 120, () => this.verifyDevChannelUpdate());
|
||||
await this.phases.phase("upgrade.gateway-stop", 420, () => this.gatewayAction("stop"));
|
||||
await this.phases.phase("upgrade.onboard-ref", 720, () => this.runRefOnboard());
|
||||
await this.phases.phase("upgrade.gateway-restart", 420, () => this.gatewayAction("restart"));
|
||||
await this.phases.phase("upgrade.gateway-status", 420, () => this.verifyGatewayReachable());
|
||||
this.status.upgradeGateway = "pass";
|
||||
await this.phases.phase("upgrade.gateway-stop-before-local-agent", 420, () =>
|
||||
await this.runGatewaySmoke("upgrade");
|
||||
}
|
||||
|
||||
private async runGatewaySmoke(lane: "fresh" | "upgrade"): Promise<void> {
|
||||
await this.phases.phase(`${lane}.onboard-ref`, 720, () => this.runRefOnboard());
|
||||
await this.phases.phase(`${lane}.gateway-restart`, 420, () => this.gatewayAction("restart"));
|
||||
await this.phases.phase(`${lane}.gateway-status`, 420, () => this.verifyGatewayReachable());
|
||||
this.status[`${lane}Gateway`] = "pass";
|
||||
await this.phases.phase(`${lane}.gateway-stop-before-local-agent`, 420, () =>
|
||||
this.gatewayAction("stop"),
|
||||
);
|
||||
await this.phases.phase("upgrade.first-agent-turn", this.agentTimeoutSeconds, () =>
|
||||
await this.phases.phase(`${lane}.first-agent-turn`, this.agentTimeoutSeconds, () =>
|
||||
this.verifyTurn(),
|
||||
);
|
||||
this.status.upgradeAgent = "pass";
|
||||
this.status[`${lane}Agent`] = "pass";
|
||||
}
|
||||
|
||||
private guestPowerShell(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-plugin-lifecycle-matrix-e2e" OPENCLAW_PLUGIN_LIFECYCLE_MATRIX_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_PLUGIN_LIFECYCLE_MATRIX_E2E_SKIP_BUILD:-0}"
|
||||
|
|
|
|||
|
|
@ -9,14 +9,10 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-plugin-update-e2e" OPENCLAW_PLUGIN_UPDATE_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_PLUGIN_UPDATE_E2E_SKIP_BUILD:-0}"
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz plugin-update "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ export CELL_DEADLINE_EPOCH_SECONDS
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz published-driver-update "${1:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}")"
|
||||
RUNTIME_VOLUME=""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-release-media-memory-e2e" OPENCLAW_RELEASE_MEDIA_MEMORY_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_RELEASE_MEDIA_MEMORY_E2E_SKIP_BUILD:-0}"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-release-plugin-marketplace-e2e" OPENCLAW_RELEASE_PLUGIN_MARKETPLACE_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_RELEASE_PLUGIN_MARKETPLACE_E2E_SKIP_BUILD:-0}"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ set -euo pipefail
|
|||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
TARGET_ROOT_DIR="$(cd "${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$ROOT_DIR}" && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
|
||||
source "$ROOT_DIR/scripts/lib/frozen-target-compat.sh"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-release-upgrade-user-journey-e2e" OPENCLAW_RELEASE_UPGRADE_USER_JOURNEY_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_RELEASE_UPGRADE_USER_JOURNEY_E2E_SKIP_BUILD:-0}"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-release-user-journey-e2e" OPENCLAW_RELEASE_USER_JOURNEY_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_RELEASE_USER_JOURNEY_E2E_SKIP_BUILD:-0}"
|
||||
|
|
|
|||
|
|
@ -9,13 +9,9 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-skill-install-e2e" OPENCLAW_SKILL_INSTALL_E2E_IMAGE)"
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz skill-install "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 skill-install empty)"
|
||||
|
|
|
|||
|
|
@ -9,14 +9,10 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-update-channel-switch-e2e" OPENCLAW_UPDATE_CHANNEL_SWITCH_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_UPDATE_CHANNEL_SWITCH_E2E_SKIP_BUILD:-0}"
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz update-channel-switch "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$ROOT_DIR/scripts/lib/frozen-target-compat.sh"
|
||||
|
||||
TARGET_ROOT_DIR="$(cd "${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$ROOT_DIR}" && pwd)"
|
||||
|
|
@ -20,10 +19,7 @@ CORRUPT_UPDATE_SCENARIO="$(openclaw_resolve_frozen_target_file "$TARGET_ROOT_DIR
|
|||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-update-corrupt-plugin-e2e" OPENCLAW_UPDATE_CORRUPT_PLUGIN_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_UPDATE_CORRUPT_PLUGIN_E2E_SKIP_BUILD:-0}"
|
||||
cleanup() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"' EXIT
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz update-corrupt-plugin "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ set -euo pipefail
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
if [ "${OPENCLAW_QA_ALLOW_UPDATE_FIRST_HOP:-0}" != "1" ]; then
|
||||
echo "blocked destructive package self-update; set OPENCLAW_QA_ALLOW_UPDATE_FIRST_HOP=1 to run" >&2
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { setImmediate, setTimeout } from "node:timers/promises";
|
||||
import * as v8 from "node:v8";
|
||||
import { expectDefined } from "../packages/normalization-core/src/expect.js";
|
||||
import { toErrorObject as toLintErrorObject } from "./lib/error-format.mts";
|
||||
import { coerceErrorMessage, toErrorObject as toLintErrorObject } from "./lib/error-format.mts";
|
||||
import { parseNonNegativeInt, parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
|
||||
type Mode = "production" | "closure-extracted" | "closure-inline" | "synthetic-leak";
|
||||
|
|
@ -235,14 +236,10 @@ function runOnce(mode: Mode, scopeBytes: number, iter: number): void {
|
|||
|
||||
async function settleAndGc(): Promise<void> {
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
await new Promise<void>((r) => {
|
||||
setImmediate(r);
|
||||
});
|
||||
await setImmediate();
|
||||
globalThis.gc?.();
|
||||
}
|
||||
await new Promise<void>((r) => {
|
||||
setTimeout(r, 100);
|
||||
});
|
||||
await setTimeout(100);
|
||||
globalThis.gc?.();
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +273,7 @@ async function main(): Promise<void> {
|
|||
try {
|
||||
opts = parseArgs(process.argv.slice(2));
|
||||
} catch (error) {
|
||||
fail(error instanceof Error ? error.message : String(error));
|
||||
fail(coerceErrorMessage(error));
|
||||
}
|
||||
if (opts.mode === "production") {
|
||||
productionAbortable = (await import("../src/agents/embedded-agent-runner/run/abortable.js"))
|
||||
|
|
|
|||
|
|
@ -3,19 +3,15 @@
|
|||
import { pathToFileURL } from "node:url";
|
||||
import { computeBaseConfigSchemaResponse } from "../src/config/schema-base.js";
|
||||
|
||||
function checkBaseConfigSchema(): void {
|
||||
computeBaseConfigSchemaResponse({
|
||||
generatedAt: "2026-05-05T00:00:00.000Z",
|
||||
});
|
||||
}
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
if (args.has("--check") && args.has("--write")) {
|
||||
throw new Error("Use either --check or --write, not both.");
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
checkBaseConfigSchema();
|
||||
computeBaseConfigSchemaResponse({
|
||||
generatedAt: "2026-05-05T00:00:00.000Z",
|
||||
});
|
||||
if (args.has("--write")) {
|
||||
console.log("[base-config-schema] runtime-computed; no generated file to write");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
// Shared PR context and evidence policy for GitHub checks and label decisions.
|
||||
import { readBoundedResponseText } from "../lib/bounded-response.mjs";
|
||||
import {
|
||||
createBoundedResponseTooLargeError,
|
||||
readBoundedResponseText,
|
||||
} from "../lib/bounded-response.mjs";
|
||||
import { escapeRegExp } from "../lib/regexp.mjs";
|
||||
import { createTimeoutError } from "../lib/timeout-error.mjs";
|
||||
|
||||
|
|
@ -62,12 +65,6 @@ const legacyProofFieldNames = [
|
|||
const missingValueRegex =
|
||||
/^(?:n\/?a|none|not applicable|tbd|todo|unknown|unsure|none provided|no evidence|not tested|untested|did not test|didn't test|could not test|couldn't test|-|(?:-{3,}|\*{3,}|_{3,})|\[[^\]]*\])\.?$/i;
|
||||
|
||||
function createTooLargeGitHubApiBodyError(label, maxBytes) {
|
||||
const error = new Error(`${label} response body exceeded ${maxBytes} bytes`);
|
||||
error.code = "ETOOBIG";
|
||||
return error;
|
||||
}
|
||||
|
||||
async function withGitHubApiTimeout(label, timeoutMs, run) {
|
||||
const boundedTimeoutMs = Math.max(1, timeoutMs);
|
||||
const controller = new AbortController();
|
||||
|
|
@ -105,7 +102,8 @@ export async function readBoundedGitHubApiJson(
|
|||
) {
|
||||
const text = await readBoundedResponseText(response, label, maxBytes, {
|
||||
...options,
|
||||
createTooLargeError: () => createTooLargeGitHubApiBodyError(label, maxBytes),
|
||||
createTooLargeError: () =>
|
||||
createBoundedResponseTooLargeError(`${label} response body exceeded ${maxBytes} bytes`),
|
||||
});
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,18 +149,6 @@ assert_plist_nonempty_string() {
|
|||
fi
|
||||
}
|
||||
|
||||
assert_plist_true() {
|
||||
local plist="$1"
|
||||
local key_path="$2"
|
||||
local label="$3"
|
||||
local actual
|
||||
actual="$(plist_value "${plist}" "${key_path}")"
|
||||
if [[ "${actual}" != "true" ]]; then
|
||||
echo "Invalid IPA: ${label}; expected true, got ${actual:-missing}." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_plist_key_absent() {
|
||||
local plist="$1"
|
||||
local key_path="$2"
|
||||
|
|
@ -258,7 +246,7 @@ assert_plist_string "${entitlements_plist}" "application-identifier" "${EXPECTED
|
|||
assert_plist_string "${entitlements_plist}" "com.apple.developer.team-identifier" "${EXPECTED_TEAM_ID}" "signed team identifier mismatch"
|
||||
assert_plist_string "${entitlements_plist}" "aps-environment" "production" "signed APNs entitlement mismatch"
|
||||
assert_plist_string "${entitlements_plist}" "com.apple.developer.devicecheck.appattest-environment" "production" "signed App Attest entitlement mismatch"
|
||||
assert_plist_true "${entitlements_plist}" "com.apple.developer.healthkit" "signed HealthKit entitlement mismatch"
|
||||
assert_plist_string "${entitlements_plist}" "com.apple.developer.healthkit" "true" "signed HealthKit entitlement mismatch"
|
||||
assert_plist_array_contains "${entitlements_plist}" "com.apple.security.application-groups" "${EXPECTED_APP_GROUP}" "signed App Group entitlement mismatch"
|
||||
|
||||
if ! "${SECURITY_BIN}" cms -D -i "${embedded_profile}" >"${profile_plist}" 2>"${tmp_dir}/security.err"; then
|
||||
|
|
@ -272,7 +260,7 @@ assert_plist_array_contains "${profile_plist}" "TeamIdentifier" "${EXPECTED_TEAM
|
|||
assert_plist_string "${profile_plist}" "Entitlements:application-identifier" "${EXPECTED_TEAM_ID}.${EXPECTED_BUNDLE_ID}" "embedded profile application identifier mismatch"
|
||||
assert_plist_string "${profile_plist}" "Entitlements:aps-environment" "production" "embedded profile APNs entitlement mismatch"
|
||||
assert_plist_array_contains "${profile_plist}" "Entitlements:com.apple.developer.devicecheck.appattest-environment" "production" "embedded profile App Attest entitlement mismatch"
|
||||
assert_plist_true "${profile_plist}" "Entitlements:com.apple.developer.healthkit" "embedded profile HealthKit entitlement mismatch"
|
||||
assert_plist_string "${profile_plist}" "Entitlements:com.apple.developer.healthkit" "true" "embedded profile HealthKit entitlement mismatch"
|
||||
assert_plist_array_contains "${profile_plist}" "Entitlements:com.apple.security.application-groups" "${EXPECTED_APP_GROUP}" "embedded profile App Group entitlement mismatch"
|
||||
|
||||
echo "Validated iOS App Store IPA: ${IPA_PATH}"
|
||||
|
|
|
|||
|
|
@ -16,3 +16,11 @@ export function appendBoundedTail(state, chunk, maxChars) {
|
|||
truncatedChars: state.truncatedChars + droppedChars,
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {BoundedTail} state */
|
||||
export function formatBoundedTail(state) {
|
||||
if (state.truncatedChars === 0) {
|
||||
return state.text;
|
||||
}
|
||||
return `[output truncated ${state.truncatedChars} chars; showing tail]\n${state.text}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|||
import { isDeepStrictEqual } from "node:util";
|
||||
import { z } from "zod";
|
||||
import type { CodeModeMatrixCellResult } from "../code-mode-model-matrix.ts";
|
||||
import { groupBy } from "./group-by.mts";
|
||||
|
||||
const comparableResult = z
|
||||
.object({
|
||||
|
|
@ -422,10 +423,9 @@ export function compareCodeModeMatrixModes(values: readonly unknown[]) {
|
|||
}
|
||||
}
|
||||
const grouped = (includeTask: boolean) => {
|
||||
const subsets = new Map<string, ModeResult[]>();
|
||||
for (const row of rows) {
|
||||
const subsets = groupBy(rows, (row) => {
|
||||
const settings = row.workload!.settings;
|
||||
const identity = canonicalJson({
|
||||
return canonicalJson({
|
||||
model: row.model,
|
||||
...(includeTask
|
||||
? { task: row.task, settings }
|
||||
|
|
@ -434,10 +434,7 @@ export function compareCodeModeMatrixModes(values: readonly unknown[]) {
|
|||
buildSha256: row.buildSha256,
|
||||
sourcePatchSha256: row.sourcePatchSha256,
|
||||
});
|
||||
const subset = subsets.get(identity) ?? [];
|
||||
subset.push(row);
|
||||
subsets.set(identity, subset);
|
||||
}
|
||||
});
|
||||
return [...subsets]
|
||||
.toSorted(([a], [b]) => a.localeCompare(b))
|
||||
.map(([identity, subset]) => {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ function isProductionExtensionFile(relPath: string) {
|
|||
if (
|
||||
relPath.includes("/test-support/") ||
|
||||
relPath.includes(".test.") ||
|
||||
relPath.includes(".live.test.") ||
|
||||
relPath.includes(".test-d.") ||
|
||||
relPath.includes(".test-harness.") ||
|
||||
relPath.includes(".test-shared.") ||
|
||||
|
|
@ -105,17 +104,13 @@ function isProductionExtensionFile(relPath: string) {
|
|||
function isTestOrHarnessFile(relPath: string) {
|
||||
return (
|
||||
relPath.includes("test-support") ||
|
||||
relPath.includes("/test-support/") ||
|
||||
relPath.includes("/test-helpers/") ||
|
||||
relPath.includes(".test.") ||
|
||||
relPath.includes(".live.test.") ||
|
||||
relPath.includes(".test-d.") ||
|
||||
relPath.includes(".test-harness.") ||
|
||||
relPath.includes(".test-shared.") ||
|
||||
relPath.endsWith(".test-helpers.ts") ||
|
||||
relPath.endsWith(".test-support.ts") ||
|
||||
relPath.endsWith("-test-helpers.ts") ||
|
||||
relPath.endsWith("-test-support.ts")
|
||||
relPath.endsWith("-test-helpers.ts")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -511,8 +511,7 @@ export function looksLikeCommitSha(ref: string) {
|
|||
}
|
||||
|
||||
export function resolveExpectedDevUpdateRef(ref?: string) {
|
||||
const trimmed = normalizeRequestedRef(ref) || "main";
|
||||
return trimmed || "main";
|
||||
return normalizeRequestedRef(ref) || "main";
|
||||
}
|
||||
|
||||
export function resolveDevUpdateVerificationRef(ref: string, sourceSha?: string) {
|
||||
|
|
@ -540,13 +539,8 @@ export function buildRealUpdateEnv(env: NodeJS.ProcessEnv) {
|
|||
return updateEnv;
|
||||
}
|
||||
|
||||
function isExtendedStableBaselineVersion(baselineVersion: string | undefined) {
|
||||
const parsed = baselineVersion ? parseReleaseVersion(baselineVersion) : null;
|
||||
return parsed !== null && classifyReleaseTrain(parsed) === "extended-stable";
|
||||
}
|
||||
|
||||
function isExtendedStableCandidateVersion(candidateVersion: string | undefined) {
|
||||
const parsed = candidateVersion ? parseReleaseVersion(candidateVersion) : null;
|
||||
function isExtendedStableVersion(version: string | undefined) {
|
||||
const parsed = version ? parseReleaseVersion(version) : null;
|
||||
return parsed !== null && classifyReleaseTrain(parsed) === "extended-stable";
|
||||
}
|
||||
|
||||
|
|
@ -554,10 +548,7 @@ function usesExtendedStableRegistryRoute(
|
|||
baselineVersion: string | undefined,
|
||||
candidateVersion: string | undefined,
|
||||
) {
|
||||
return (
|
||||
isExtendedStableBaselineVersion(baselineVersion) &&
|
||||
isExtendedStableCandidateVersion(candidateVersion)
|
||||
);
|
||||
return isExtendedStableVersion(baselineVersion) && isExtendedStableVersion(candidateVersion);
|
||||
}
|
||||
|
||||
function buildPackagedUpgradeUpdateEnv(
|
||||
|
|
@ -576,10 +567,7 @@ function buildPackagedUpgradeUpdateEnv(
|
|||
return updateEnv;
|
||||
}
|
||||
|
||||
export function verifyPackagedUpgradeUpdateResult(
|
||||
result: CommandResult,
|
||||
_options?: { candidateVersion?: string },
|
||||
) {
|
||||
export function verifyPackagedUpgradeUpdateResult(result: CommandResult) {
|
||||
if (result.exitCode === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ import { formatError, trimForSummary } from "./shared.ts";
|
|||
export async function runFreshLane(params: LaneBaseParams & { build: CandidateBuild }) {
|
||||
const lane = createLaneState("fresh");
|
||||
const cleanup: Cleanup[] = [];
|
||||
const gatewayHolder: { current: GatewayHandle | null } = { current: null };
|
||||
try {
|
||||
const env = buildLaneEnv(lane, params.providerConfig, params.providerSecretValue);
|
||||
await runTimedLanePhase(lane, "install-candidate", async () => {
|
||||
|
|
@ -134,85 +133,34 @@ export async function runFreshLane(params: LaneBaseParams & { build: CandidateBu
|
|||
|
||||
await installLaneCompanions({ ...params, lane, env });
|
||||
|
||||
// Own the configured port through setup; release only when the gateway can claim it.
|
||||
const gatewayPortReservation = await reserveGatewayPortForLane(lane);
|
||||
cleanup.push(() => gatewayPortReservation.release());
|
||||
await runTimedLanePhase(lane, "onboard", async () => {
|
||||
await runOnboard({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: join(params.logsDir, "fresh-onboard.log"),
|
||||
});
|
||||
});
|
||||
|
||||
await runTimedLanePhase(lane, "models-set", async () => {
|
||||
await runModelsSet({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: join(params.logsDir, "fresh-models-set.log"),
|
||||
});
|
||||
});
|
||||
|
||||
const authoredConfigPath = join(lane.stateDir, "openclaw.json");
|
||||
const nestedPluginPath = "~/.openclaw/wiki";
|
||||
await runTimedLanePhase(lane, "seed-nested-plugin-path", async () => {
|
||||
const config = JSON.parse(readFileSync(authoredConfigPath, "utf8"));
|
||||
config.plugins ??= {};
|
||||
config.plugins.entries ??= {};
|
||||
// A disabled entry exercises generic path expansion without changing provider setup.
|
||||
config.plugins.entries.wiki = {
|
||||
enabled: false,
|
||||
config: { store: { path: nestedPluginPath } },
|
||||
};
|
||||
writeFileSync(authoredConfigPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
||||
const agent = await runPackagedGatewaySmoke({
|
||||
...params,
|
||||
lane,
|
||||
env,
|
||||
cleanup,
|
||||
beforeGatewayStart: () =>
|
||||
runTimedLanePhase(lane, "seed-nested-plugin-path", async () => {
|
||||
const config = JSON.parse(readFileSync(authoredConfigPath, "utf8"));
|
||||
config.plugins ??= {};
|
||||
config.plugins.entries ??= {};
|
||||
// A disabled entry exercises generic path expansion without changing provider setup.
|
||||
config.plugins.entries.wiki = {
|
||||
enabled: false,
|
||||
config: { store: { path: nestedPluginPath } },
|
||||
};
|
||||
writeFileSync(authoredConfigPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
||||
}),
|
||||
afterGatewayReady: () =>
|
||||
runTimedLanePhase(lane, "verify-nested-plugin-path", async () => {
|
||||
const config = JSON.parse(readFileSync(authoredConfigPath, "utf8"));
|
||||
if (config.plugins?.entries?.wiki?.config?.store?.path !== nestedPluginPath) {
|
||||
throw new Error("Fresh Gateway startup changed the authored nested plugin path.");
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
const gateway = await runTimedLanePhase(lane, "start-gateway", async () => {
|
||||
await gatewayPortReservation.release();
|
||||
return startGateway({
|
||||
lane,
|
||||
env,
|
||||
logPath: join(params.logsDir, "fresh-gateway.log"),
|
||||
});
|
||||
});
|
||||
gatewayHolder.current = gateway;
|
||||
cleanup.push(() => stopGateway(gatewayHolder.current));
|
||||
|
||||
await runTimedLanePhase(lane, "wait-gateway", async () => {
|
||||
await waitForGateway({
|
||||
lane,
|
||||
env,
|
||||
gatewayHolder,
|
||||
gatewayLogPath: join(params.logsDir, "fresh-gateway.log"),
|
||||
logPath: join(params.logsDir, "fresh-gateway-status.log"),
|
||||
});
|
||||
});
|
||||
|
||||
await runTimedLanePhase(lane, "verify-nested-plugin-path", async () => {
|
||||
const config = JSON.parse(readFileSync(authoredConfigPath, "utf8"));
|
||||
if (config.plugins?.entries?.wiki?.config?.store?.path !== nestedPluginPath) {
|
||||
throw new Error("Fresh Gateway startup changed the authored nested plugin path.");
|
||||
}
|
||||
});
|
||||
|
||||
await runTimedLanePhase(lane, "dashboard", async () => {
|
||||
await runDashboardSmoke({
|
||||
lane,
|
||||
logPath: join(params.logsDir, "fresh-dashboard.log"),
|
||||
});
|
||||
});
|
||||
|
||||
const agent = await runTimedLanePhase(lane, "agent-turn", async () =>
|
||||
runAgentTurn({
|
||||
lane,
|
||||
env,
|
||||
label: "fresh",
|
||||
logPath: join(params.logsDir, "fresh-agent.log"),
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
status: "pass",
|
||||
installedVersion: installed.version,
|
||||
|
|
@ -244,7 +192,6 @@ export async function runUpgradeLane(
|
|||
}
|
||||
const lane = createLaneState("upgrade");
|
||||
const cleanup: Cleanup[] = [];
|
||||
const gatewayHolder: { current: GatewayHandle | null } = { current: null };
|
||||
const result: LaneResult = {
|
||||
status: "pending",
|
||||
phaseTimings: lane.phaseTimings,
|
||||
|
|
@ -409,9 +356,7 @@ export async function runUpgradeLane(
|
|||
);
|
||||
});
|
||||
} else {
|
||||
verifyPackagedUpgradeUpdateResult(updateResult, {
|
||||
candidateVersion: params.build.candidateVersion,
|
||||
});
|
||||
verifyPackagedUpgradeUpdateResult(updateResult);
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -442,64 +387,7 @@ export async function runUpgradeLane(
|
|||
verifyInstalledCandidate(installed, params.build);
|
||||
|
||||
await installLaneCompanions({ ...params, lane, env });
|
||||
|
||||
// Own the configured port through setup; release only when the gateway can claim it.
|
||||
const gatewayPortReservation = await reserveGatewayPortForLane(lane);
|
||||
cleanup.push(() => gatewayPortReservation.release());
|
||||
await runTimedLanePhase(lane, "onboard", async () => {
|
||||
await runOnboard({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: join(params.logsDir, "upgrade-onboard.log"),
|
||||
});
|
||||
});
|
||||
|
||||
await runTimedLanePhase(lane, "models-set", async () => {
|
||||
await runModelsSet({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: join(params.logsDir, "upgrade-models-set.log"),
|
||||
});
|
||||
});
|
||||
|
||||
const gateway = await runTimedLanePhase(lane, "start-gateway", async () => {
|
||||
await gatewayPortReservation.release();
|
||||
return startGateway({
|
||||
lane,
|
||||
env,
|
||||
logPath: join(params.logsDir, "upgrade-gateway.log"),
|
||||
});
|
||||
});
|
||||
gatewayHolder.current = gateway;
|
||||
cleanup.push(() => stopGateway(gatewayHolder.current));
|
||||
|
||||
await runTimedLanePhase(lane, "wait-gateway", async () => {
|
||||
await waitForGateway({
|
||||
lane,
|
||||
env,
|
||||
gatewayHolder,
|
||||
gatewayLogPath: join(params.logsDir, "upgrade-gateway.log"),
|
||||
logPath: join(params.logsDir, "upgrade-gateway-status.log"),
|
||||
});
|
||||
});
|
||||
|
||||
await runTimedLanePhase(lane, "dashboard", async () => {
|
||||
await runDashboardSmoke({
|
||||
lane,
|
||||
logPath: join(params.logsDir, "upgrade-dashboard.log"),
|
||||
});
|
||||
});
|
||||
|
||||
const agent = await runTimedLanePhase(lane, "agent-turn", async () =>
|
||||
runAgentTurn({
|
||||
lane,
|
||||
env,
|
||||
label: "upgrade",
|
||||
logPath: join(params.logsDir, "upgrade-agent.log"),
|
||||
}),
|
||||
);
|
||||
const agent = await runPackagedGatewaySmoke({ ...params, lane, env, cleanup });
|
||||
|
||||
return {
|
||||
...result,
|
||||
|
|
@ -522,6 +410,65 @@ export async function runUpgradeLane(
|
|||
}
|
||||
}
|
||||
|
||||
async function runPackagedGatewaySmoke(
|
||||
params: Pick<LaneBaseParams, "logsDir" | "providerConfig"> & {
|
||||
lane: LaneState;
|
||||
env: NodeJS.ProcessEnv;
|
||||
cleanup: Cleanup[];
|
||||
beforeGatewayStart?: () => Promise<void>;
|
||||
afterGatewayReady?: () => Promise<void>;
|
||||
},
|
||||
) {
|
||||
const { lane, env, cleanup } = params;
|
||||
const gatewayHolder: { current: GatewayHandle | null } = { current: null };
|
||||
const logPath = (phase: string) => join(params.logsDir, `${lane.name}-${phase}.log`);
|
||||
// Own the configured port through setup; release only when the gateway can claim it.
|
||||
const gatewayPortReservation = await reserveGatewayPortForLane(lane);
|
||||
cleanup.push(() => gatewayPortReservation.release());
|
||||
await runTimedLanePhase(lane, "onboard", async () => {
|
||||
await runOnboard({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: logPath("onboard"),
|
||||
});
|
||||
});
|
||||
await runTimedLanePhase(lane, "models-set", async () => {
|
||||
await runModelsSet({
|
||||
lane,
|
||||
env,
|
||||
providerConfig: params.providerConfig,
|
||||
logPath: logPath("models-set"),
|
||||
});
|
||||
});
|
||||
if (params.beforeGatewayStart) {
|
||||
await params.beforeGatewayStart();
|
||||
}
|
||||
gatewayHolder.current = await runTimedLanePhase(lane, "start-gateway", async () => {
|
||||
await gatewayPortReservation.release();
|
||||
return startGateway({ lane, env, logPath: logPath("gateway") });
|
||||
});
|
||||
cleanup.push(() => stopGateway(gatewayHolder.current));
|
||||
await runTimedLanePhase(lane, "wait-gateway", async () => {
|
||||
await waitForGateway({
|
||||
lane,
|
||||
env,
|
||||
gatewayHolder,
|
||||
gatewayLogPath: logPath("gateway"),
|
||||
logPath: logPath("gateway-status"),
|
||||
});
|
||||
});
|
||||
if (params.afterGatewayReady) {
|
||||
await params.afterGatewayReady();
|
||||
}
|
||||
await runTimedLanePhase(lane, "dashboard", async () => {
|
||||
await runDashboardSmoke({ lane, logPath: logPath("dashboard") });
|
||||
});
|
||||
return runTimedLanePhase(lane, "agent-turn", async () =>
|
||||
runAgentTurn({ lane, env, label: lane.name, logPath: logPath("agent") }),
|
||||
);
|
||||
}
|
||||
|
||||
export async function runInstallerFreshSuite(
|
||||
params: LaneBaseParams & { build: CandidateBuild; runDiscordRoundtrip: boolean },
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { writeFileSync } from "node:fs";
|
|||
import type { Session } from "node:inspector/promises";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import type { Worker } from "node:worker_threads";
|
||||
import { coerceErrorMessage as errorMessage } from "./error-format.mts";
|
||||
import {
|
||||
GATEWAY_CPU_SAMPLE_INTERVAL_MICROS,
|
||||
GATEWAY_HEAP_SAMPLE_INTERVAL,
|
||||
|
|
@ -44,10 +45,6 @@ type WorkerReply = {
|
|||
error?: { message: string };
|
||||
};
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
/** Uses the private inspector channel; no worker factory changes or debug listener. */
|
||||
export class GatewayBenchWorkerProfiler {
|
||||
private workers = new Set<Worker>();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
export function validatePackedTarballOutputName(value: string) {
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.t(?:ar\.)?gz$/u.test(value)) {
|
||||
throw new Error(`--output-name must be a tarball filename, not a path: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanPackedOpenClawTarballs(outputDir: string) {
|
||||
let entries: string[];
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import JSON5 from "json5";
|
||||
import { normalizeTrimmedStringList } from "../../packages/normalization-core/src/string-normalization.ts";
|
||||
import { NON_PACKAGED_BUNDLED_PLUGIN_DIRS } from "../../src/shared/non-packaged-plugin-dirs.ts";
|
||||
import { collectBundledPluginBuildEntries } from "./bundled-plugin-build-entries.mjs";
|
||||
import { groupBy } from "./group-by.mts";
|
||||
import { parsePositiveInt } from "./numeric-options.mjs";
|
||||
import { isRecord } from "./record-shared.mjs";
|
||||
import { isRecord, trimString } from "./record-shared.mjs";
|
||||
|
||||
type PluginGatewayEntry = {
|
||||
id: string;
|
||||
|
|
@ -29,16 +31,6 @@ const ANSI_PATTERN = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
|
|||
const QA_SUMMARY_MAX_BYTES_ENV = "OPENCLAW_PLUGIN_GATEWAY_GAUNTLET_QA_SUMMARY_MAX_BYTES";
|
||||
const DEFAULT_QA_SUMMARY_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function normalizeStringOrEmpty(value: unknown) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown) {
|
||||
return Array.isArray(value)
|
||||
? value.map((entry) => normalizeStringOrEmpty(entry)).filter((entry) => entry.length > 0)
|
||||
: [];
|
||||
}
|
||||
|
||||
function readPluginManifest(manifestPath: string) {
|
||||
const raw = fs.readFileSync(manifestPath, "utf8");
|
||||
const parsed: unknown = manifestPath.endsWith(".json5") ? JSON5.parse(raw) : JSON.parse(raw);
|
||||
|
|
@ -95,20 +87,20 @@ function collectCommandAliasRecords(manifest: PluginManifest) {
|
|||
return aliases
|
||||
.map((alias) => {
|
||||
if (typeof alias === "string") {
|
||||
const name = normalizeStringOrEmpty(alias);
|
||||
const name = trimString(alias);
|
||||
return name ? { name, kind: "runtime-slash", cliCommand: null } : null;
|
||||
}
|
||||
if (!isRecord(alias)) {
|
||||
return null;
|
||||
}
|
||||
const name = normalizeStringOrEmpty(alias.name);
|
||||
const name = trimString(alias.name);
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name,
|
||||
kind: normalizeStringOrEmpty(alias.kind) || "runtime-slash",
|
||||
cliCommand: normalizeStringOrEmpty(alias.cliCommand) || null,
|
||||
kind: trimString(alias.kind) || "runtime-slash",
|
||||
cliCommand: trimString(alias.cliCommand) || null,
|
||||
};
|
||||
})
|
||||
.filter((alias) => alias !== null);
|
||||
|
|
@ -117,14 +109,14 @@ function collectCommandAliasRecords(manifest: PluginManifest) {
|
|||
function collectAuthMethods(manifest: PluginManifest) {
|
||||
const auth = Array.isArray(manifest.auth) ? manifest.auth : [];
|
||||
return auth
|
||||
.map((entry) => (isRecord(entry) ? normalizeStringOrEmpty(entry.method) : ""))
|
||||
.map((entry) => (isRecord(entry) ? trimString(entry.method) : ""))
|
||||
.filter((method) => method.length > 0);
|
||||
}
|
||||
|
||||
function collectOnboardingScopes(manifest: PluginManifest) {
|
||||
const scopes = new Set<string>();
|
||||
const addScopes = (value: unknown) => {
|
||||
for (const scope of normalizeStringArray(value)) {
|
||||
for (const scope of normalizeTrimmedStringList(value)) {
|
||||
scopes.add(scope);
|
||||
}
|
||||
};
|
||||
|
|
@ -146,17 +138,17 @@ function buildPluginMatrixEntry(repoRoot: string, manifestPath: string, manifest
|
|||
return {
|
||||
id: manifest.id,
|
||||
buildId: path.basename(pluginDir),
|
||||
name: normalizeStringOrEmpty(manifest.name) || manifest.id,
|
||||
name: trimString(manifest.name) || manifest.id,
|
||||
dir: path.relative(repoRoot, pluginDir),
|
||||
manifestPath: relativeManifestPath,
|
||||
enabledByDefault: manifest.enabledByDefault === true,
|
||||
activation: isRecord(manifest.activation) ? manifest.activation : {},
|
||||
providers: normalizeStringArray(manifest.providers),
|
||||
channels: normalizeStringArray(manifest.channels),
|
||||
skills: normalizeStringArray(manifest.skills),
|
||||
providers: normalizeTrimmedStringList(manifest.providers),
|
||||
channels: normalizeTrimmedStringList(manifest.channels),
|
||||
skills: normalizeTrimmedStringList(manifest.skills),
|
||||
authMethods: collectAuthMethods(manifest),
|
||||
onboardingScopes: collectOnboardingScopes(manifest),
|
||||
requiredPlugins: normalizeStringArray(manifest.requiresPlugins),
|
||||
requiredPlugins: normalizeTrimmedStringList(manifest.requiresPlugins),
|
||||
hasConfigSchema: isRecord(manifest.configSchema),
|
||||
hasRequiredConfigFields: schemaHasRequiredFields(manifest.configSchema),
|
||||
commandAliases,
|
||||
|
|
@ -194,7 +186,7 @@ function selectPluginEntries<T extends PluginGatewayEntry>(
|
|||
entries: T[],
|
||||
options: { ids?: string[]; shardIndex?: number; shardTotal?: number; limit?: number } = {},
|
||||
) {
|
||||
const ids = new Set(normalizeStringArray(options.ids));
|
||||
const ids = new Set(normalizeTrimmedStringList(options.ids));
|
||||
let selected = ids.size > 0 ? entries.filter((entry) => ids.has(entry.id)) : [...entries];
|
||||
const missingIds = [...ids].filter((id) => !entries.some((entry) => entry.id === id));
|
||||
if (missingIds.length > 0) {
|
||||
|
|
@ -281,17 +273,6 @@ function median(values: unknown[]) {
|
|||
: (lower + upper) / 2;
|
||||
}
|
||||
|
||||
function groupByPhase(rows: JsonRecord[]) {
|
||||
const phases = new Map<string, JsonRecord[]>();
|
||||
for (const row of rows) {
|
||||
const phase = normalizeStringOrEmpty(row.phase) || "unknown";
|
||||
const current = phases.get(phase) ?? [];
|
||||
current.push(row);
|
||||
phases.set(phase, current);
|
||||
}
|
||||
return phases;
|
||||
}
|
||||
|
||||
function collectMetricObservations(rows: JsonRecord[], thresholds: MetricThresholds = {}) {
|
||||
const cpuCoreWarn = thresholds.cpuCoreWarn ?? 0.9;
|
||||
const hotWallWarnMs = thresholds.hotWallWarnMs ?? 30_000;
|
||||
|
|
@ -300,7 +281,7 @@ function collectMetricObservations(rows: JsonRecord[], thresholds: MetricThresho
|
|||
const rssAnomalyMultiplier = thresholds.rssAnomalyMultiplier ?? 2.5;
|
||||
const firstWorkRow = rows.find((row) => row.phase !== "prebuild");
|
||||
const observations = [];
|
||||
for (const [phase, phaseRows] of groupByPhase(rows)) {
|
||||
for (const [phase, phaseRows] of groupBy(rows, (row) => trimString(row.phase) || "unknown")) {
|
||||
const wallMedianMs = median(phaseRows.map((row) => row.wallMs));
|
||||
const rssMedianMb = median(phaseRows.map((row) => row.maxRssMb));
|
||||
for (const row of phaseRows) {
|
||||
|
|
@ -437,7 +418,7 @@ function buildGauntletPrebuildEnv(
|
|||
includePrivateQa?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
const buildIds = new Set(normalizeStringArray(options.buildIds));
|
||||
const buildIds = new Set(normalizeTrimmedStringList(options.buildIds));
|
||||
if (options.includePrivateQa) {
|
||||
for (const pluginId of NON_PACKAGED_BUNDLED_PLUGIN_DIRS) {
|
||||
buildIds.add(pluginId);
|
||||
|
|
@ -492,7 +473,7 @@ function collectGatewayCpuObservations(params: {
|
|||
if (!isRecord(result)) {
|
||||
continue;
|
||||
}
|
||||
const id = normalizeStringOrEmpty(result.id) || "unknown";
|
||||
const id = trimString(result.id) || "unknown";
|
||||
const cpuCoreMax = metricMax(result.summary, "cpuCoreRatio");
|
||||
const wallMax = metricMax(result.summary, "readyzMs") ?? metricMax(result.summary, "healthzMs");
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -255,10 +255,6 @@ export function reportRatchetFailures(
|
|||
return active.length > 0;
|
||||
}
|
||||
|
||||
export function reportRatchetSuccess(message: string) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
type PerFileCountRatchetOptions = {
|
||||
baselinePath: string;
|
||||
baselineHeader: string;
|
||||
|
|
@ -366,7 +362,7 @@ export function runPerFileCountRatchet(
|
|||
} catch {
|
||||
if (args.prune && !args.staged && baseBaseline === null) {
|
||||
writeBaseline(current);
|
||||
reportRatchetSuccess(
|
||||
console.log(
|
||||
`Initialized ${baselinePath}: ${current.size} files, ${totalCount(current)} ${messages.countNoun}.`,
|
||||
);
|
||||
return 0;
|
||||
|
|
@ -376,7 +372,7 @@ export function runPerFileCountRatchet(
|
|||
|
||||
if (args.prune && !args.staged && baseBaseline === null) {
|
||||
writeBaseline(current);
|
||||
reportRatchetSuccess(
|
||||
console.log(
|
||||
`Refreshed initial ${baselinePath}: ${current.size} files, ${totalCount(current)} ${messages.countNoun}.`,
|
||||
);
|
||||
return 0;
|
||||
|
|
@ -427,7 +423,7 @@ export function runPerFileCountRatchet(
|
|||
const oldFiles = baseline.size;
|
||||
const oldCount = totalCount(baseline);
|
||||
writeBaseline(current);
|
||||
reportRatchetSuccess(
|
||||
console.log(
|
||||
`Pruned ${baselinePath}: ${oldFiles} -> ${current.size} files; ${oldCount} -> ${totalCount(current)} ${messages.countNoun}.`,
|
||||
);
|
||||
return 0;
|
||||
|
|
@ -445,7 +441,7 @@ export function runPerFileCountRatchet(
|
|||
return 1;
|
||||
}
|
||||
|
||||
reportRatchetSuccess(
|
||||
console.log(
|
||||
`${messages.successTitle}: ${current.size} files, ${totalCount(current)} grandfathered ${messages.countNoun}.`,
|
||||
);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import path from "node:path";
|
|||
import { Script } from "node:vm";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { chromium, type WebSocketRoute } from "playwright";
|
||||
import { createDeferredCore } from "../../src/shared/deferred.ts";
|
||||
import {
|
||||
createControlUiMockBootstrapConfig,
|
||||
createControlUiMockGatewayInitScript,
|
||||
|
|
@ -37,10 +38,7 @@ new Script(createControlUiMockGatewayInitScript(scenario)).runInContext(
|
|||
const sockets = new Set<WebSocketRoute>();
|
||||
let sendRequest: { id: string; params: unknown } | undefined;
|
||||
let sendRequestCount = 0;
|
||||
let resolveSend: (() => void) | undefined;
|
||||
const sent = new Promise<void>((resolve) => {
|
||||
resolveSend = resolve;
|
||||
});
|
||||
const { promise: sent, resolve: resolveSend } = createDeferredCore();
|
||||
let trafficBytes = 0;
|
||||
let protocolError = false;
|
||||
let captureArmed = false;
|
||||
|
|
@ -142,7 +140,7 @@ try {
|
|||
if (frame.ok !== true) {
|
||||
protocolError = true;
|
||||
}
|
||||
resolveSend?.();
|
||||
resolveSend();
|
||||
}
|
||||
});
|
||||
route.onMessage((message) => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:f
|
|||
import path from "node:path";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { promisify } from "node:util";
|
||||
import { z } from "zod";
|
||||
import { requestIdentitySchema } from "./request-proof.ts";
|
||||
import { proofImageInspectionSchema } from "./telegram-proof-storage.mts";
|
||||
import { removeTelegramQaNetwork } from "./telegram-qa-cleanup.ts";
|
||||
import {
|
||||
telegramQaExecutionSchema,
|
||||
|
|
@ -43,15 +43,9 @@ const identity = requestIdentitySchema.parse({
|
|||
const execute = promisify(execFile);
|
||||
const podman = async (args: string[]) =>
|
||||
(await execute("podman", args, { timeout: 300_000, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
||||
const info = z
|
||||
.array(
|
||||
z.object({
|
||||
Id: z.string().regex(/^(?:sha256:)?[a-f0-9]{64}$/),
|
||||
Config: z.object({ Labels: z.record(z.string(), z.string()) }),
|
||||
}),
|
||||
)
|
||||
.length(1)
|
||||
.parse(JSON.parse(await podman(["image", "inspect", image])))[0]!;
|
||||
const info = proofImageInspectionSchema.parse(
|
||||
JSON.parse(await podman(["image", "inspect", image])),
|
||||
)[0]!;
|
||||
if (info.Config.Labels["org.openclaw.mantis.candidate-sha"] !== candidate) {
|
||||
throw new Error("Candidate runtime identity mismatch");
|
||||
}
|
||||
|
|
@ -204,23 +198,18 @@ try {
|
|||
} catch (error) {
|
||||
errors.push(error);
|
||||
} finally {
|
||||
for (const owned of [sut, observer]) {
|
||||
for (const cleanup of [
|
||||
() => podman(["rm", "--force", "--ignore", sut]),
|
||||
() => podman(["rm", "--force", "--ignore", observer]),
|
||||
() => removeTelegramQaNetwork(podman, createdNetwork),
|
||||
() => rm(scratch, { recursive: true, force: true }),
|
||||
]) {
|
||||
try {
|
||||
await podman(["rm", "--force", "--ignore", owned]);
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await removeTelegramQaNetwork(podman, createdNetwork);
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
try {
|
||||
await rm(scratch, { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
throw new AggregateError(errors, "Requested QA execution or owned environment cleanup failed");
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { parseTelegramProofPlan, type TelegramProofPlan } from "./telegram-proof
|
|||
import {
|
||||
assertPodmanProofStorage,
|
||||
assertProofImage,
|
||||
proofImageInspectionSchema,
|
||||
proofImageTag,
|
||||
} from "./telegram-proof-storage.mts";
|
||||
import { telegramProofIdentitySchema } from "./telegram-request-proof.ts";
|
||||
|
|
@ -47,14 +48,6 @@ class TelegramProofStageError extends Error {
|
|||
const execute = promisify(execFile);
|
||||
const podman = async (args: string[]) =>
|
||||
(await execute("podman", args, { maxBuffer: 2 * 1024 * 1024, timeout: 60_000 })).stdout;
|
||||
const imageInfo = z
|
||||
.array(
|
||||
z.object({
|
||||
Id: z.string().regex(/^(?:sha256:)?[a-f0-9]{64}$/),
|
||||
Config: z.object({ Labels: z.record(z.string(), z.string()) }),
|
||||
}),
|
||||
)
|
||||
.length(1);
|
||||
const skill = path.resolve(".agents/skills/telegram-e2e-userbot/scripts");
|
||||
type QaLease = Awaited<ReturnType<typeof acquireQaLease>>;
|
||||
|
||||
|
|
@ -112,7 +105,9 @@ async function preflight(candidate: string, image: string) {
|
|||
if (!/^[a-f0-9]{40}$/.test(candidate) || !/^[a-z0-9][a-z0-9/.:@-]*$/.test(image)) {
|
||||
throw new Error("Invalid candidate/image selection");
|
||||
}
|
||||
const info = imageInfo.parse(JSON.parse(await podman(["image", "inspect", image])))[0];
|
||||
const info = proofImageInspectionSchema.parse(
|
||||
JSON.parse(await podman(["image", "inspect", image])),
|
||||
)[0];
|
||||
if (!info || info.Config.Labels["org.openclaw.mantis.candidate-sha"] !== candidate) {
|
||||
throw new Error("Prepared runtime does not match exact candidate");
|
||||
}
|
||||
|
|
@ -546,7 +541,7 @@ async function run() {
|
|||
// field literally. Bind a private tag to the immutable image before creation,
|
||||
// then verify the created container's immutable image before Gateway startup.
|
||||
assertPodmanProofStorage();
|
||||
const tagged = imageInfo.parse(
|
||||
const tagged = proofImageInspectionSchema.parse(
|
||||
JSON.parse(await podman(["image", "inspect", ready.imageTag])),
|
||||
)[0];
|
||||
assertProofImage(ready.imageId, tagged?.Id ?? "");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { appendFile } from "node:fs/promises";
|
|||
import http from "node:http";
|
||||
import { z } from "zod";
|
||||
import { startTelegramTestApiProxy } from "../../.agents/skills/telegram-e2e-userbot/scripts/telegram-test-api-proxy.mjs";
|
||||
import { createDeferredCore } from "../../src/shared/deferred.ts";
|
||||
import type { TelegramFailureDiagnostic } from "./request-proof.ts";
|
||||
import type { TelegramProofPlan } from "./telegram-proof-plan.ts";
|
||||
|
||||
|
|
@ -95,10 +96,7 @@ export async function startTelegramProofIngress(options: {
|
|||
diagnostics.push({ sequence: diagnostics.length + 1, category });
|
||||
}
|
||||
};
|
||||
let stopForwarding!: (error: Error) => void;
|
||||
const stopped = new Promise<Error>((resolve) => {
|
||||
stopForwarding = resolve;
|
||||
});
|
||||
const { promise: stopped, resolve: stopForwarding } = createDeferredCore<Error>();
|
||||
const cancel = () => {
|
||||
stopForwarding(new Error("Telegram proof forwarding stopped"));
|
||||
for (const controller of readers) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ import { fileURLToPath } from "node:url";
|
|||
import { z } from "zod";
|
||||
|
||||
export const proofStorageBytes = 32 * 1024 ** 3;
|
||||
export const proofImageInspectionSchema = z
|
||||
.array(
|
||||
z.object({
|
||||
Id: z.string().regex(/^(?:sha256:)?[a-f0-9]{64}$/),
|
||||
Config: z.object({ Labels: z.record(z.string(), z.string()) }),
|
||||
}),
|
||||
)
|
||||
.length(1);
|
||||
const containerConfiguration =
|
||||
'[containers]\nlog_driver = "k8s-file"\nlog_size_max = 1048576\n' +
|
||||
'[network]\ndefault_rootless_network_cmd = "slirp4netns"\n';
|
||||
|
|
|
|||
|
|
@ -7,8 +7,14 @@ import { expectDefined } from "../packages/normalization-core/src/expect.js";
|
|||
import { isRecord } from "../packages/normalization-core/src/record-coerce.js";
|
||||
import { sliceUtf16Safe } from "../packages/normalization-core/src/utf16-slice.ts";
|
||||
import { decodeXml } from "../src/shared/xml.ts";
|
||||
import { collectToolDisplaySources, selectDeterministicTranslation } from "./android-app-i18n.ts";
|
||||
import {
|
||||
collectToolDisplaySources,
|
||||
findClosingDelimiter,
|
||||
lineNumber,
|
||||
selectDeterministicTranslation,
|
||||
} from "./android-app-i18n.ts";
|
||||
import { translateNativeEntries } from "./control-ui-i18n.ts";
|
||||
import { compareAscii as compareCodePoints } from "./lib/canonical-json.mjs";
|
||||
import { NATIVE_I18N_LOCALES } from "./native-i18n-locales.ts";
|
||||
|
||||
type NativeI18nSurface = "android" | "apple";
|
||||
|
|
@ -396,52 +402,6 @@ function extractKotlinInterpolations(source: string): NativeInterpolation[] | nu
|
|||
return values;
|
||||
}
|
||||
|
||||
function compareCodePoints(left: string, right: string): number {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
function lineNumber(source: string, offset: number): number {
|
||||
return source.slice(0, offset).split("\n").length;
|
||||
}
|
||||
|
||||
function findClosingDelimiter(
|
||||
source: string,
|
||||
openingIndex: number,
|
||||
opening: string,
|
||||
closing: string,
|
||||
): number | null {
|
||||
let depth = 0;
|
||||
let quoted = false;
|
||||
let escaped = false;
|
||||
for (let index = openingIndex; index < source.length; index += 1) {
|
||||
const character = source[index];
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (quoted && character === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (character === '"') {
|
||||
quoted = !quoted;
|
||||
continue;
|
||||
}
|
||||
if (quoted) {
|
||||
continue;
|
||||
}
|
||||
if (character === opening) {
|
||||
depth += 1;
|
||||
} else if (character === closing) {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readMultilineStringLiteral(
|
||||
source: string,
|
||||
openingQuote: number,
|
||||
|
|
|
|||
|
|
@ -266,22 +266,14 @@ function runPnpm(args: string[], env: NodeJS.ProcessEnv): void {
|
|||
run(command.command, command.args, { ...command.options, env });
|
||||
}
|
||||
|
||||
function runBuildSmoke(): void {
|
||||
run(process.execPath, ["--import", "tsx", "scripts/test-built-bundled-channel-entry-smoke.mts"]);
|
||||
}
|
||||
|
||||
async function writeDistInventory(): Promise<void> {
|
||||
await writePackageDistInventoryForPublish(process.cwd());
|
||||
}
|
||||
|
||||
export async function preparePrepackArtifacts(env: NodeJS.ProcessEnv = process.env): Promise<void> {
|
||||
ensurePreparedArtifacts();
|
||||
runBuildSmoke();
|
||||
run(process.execPath, ["--import", "tsx", "scripts/test-built-bundled-channel-entry-smoke.mts"]);
|
||||
// The docs-map receipt serializes source-mutating pack lifecycles before the
|
||||
// changelog is touched, so concurrent packs cannot restore each other's files.
|
||||
await preparePackageDocsMap(process.cwd());
|
||||
try {
|
||||
await writeDistInventory();
|
||||
await writePackageDistInventoryForPublish(process.cwd());
|
||||
await preparePackageManifest(process.cwd());
|
||||
await preparePackageChangelog(process.cwd(), {
|
||||
allowUnreleased: resolvePrepackAllowUnreleasedChangelog(env),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { createRequire } from "node:module";
|
|||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { resolveTimerTimeoutMs } from "../packages/normalization-core/src/number-coercion.ts";
|
||||
import {
|
||||
booleanFlag,
|
||||
parseFlagArgs,
|
||||
|
|
@ -22,7 +23,10 @@ import {
|
|||
LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH,
|
||||
PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH,
|
||||
} from "./lib/package-lifecycle-marker.mjs";
|
||||
import { cleanPackedOpenClawTarballs } from "./lib/packed-openclaw-tarballs.mts";
|
||||
import {
|
||||
cleanPackedOpenClawTarballs,
|
||||
validatePackedTarballOutputName,
|
||||
} from "./lib/packed-openclaw-tarballs.mts";
|
||||
import { isRecord } from "./lib/record-shared.mjs";
|
||||
import { resolveNpmRunner } from "./npm-runner.mts";
|
||||
import { preparePackageChangelog, restorePackageChangelog } from "./package-changelog.mjs";
|
||||
|
|
@ -38,7 +42,6 @@ const DEFAULT_TIMEOUT_KILL_AFTER_MS = 5_000;
|
|||
const PROCESS_GROUP_EXIT_POLL_MS = 25;
|
||||
const POST_FORCE_KILL_WAIT_MS = 1_000;
|
||||
const DEFAULT_CAPTURED_STDOUT_MAX_BYTES = 1024 * 1024;
|
||||
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
const AI_RUNTIME_PACKAGE = "@openclaw/ai";
|
||||
const AI_RUNTIME_BACKUP_DIR = ".openclaw-ai-package-backup";
|
||||
|
||||
|
|
@ -174,30 +177,8 @@ function resolveTimeoutMs(envName: string, defaultValue: number) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
function numericTimerValueMs(valueMs: unknown) {
|
||||
const value = Number(valueMs);
|
||||
return Number.isFinite(value) ? Math.floor(value) : undefined;
|
||||
}
|
||||
|
||||
function resolvePackageBuildTimeoutMs(
|
||||
valueMs: unknown,
|
||||
fallbackMs: unknown = MAX_TIMER_TIMEOUT_MS,
|
||||
) {
|
||||
const value = numericTimerValueMs(valueMs) ?? numericTimerValueMs(fallbackMs);
|
||||
return Math.min(Math.max(value ?? MAX_TIMER_TIMEOUT_MS, 1), MAX_TIMER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveOptionalTimerTimeoutMs(valueMs: unknown) {
|
||||
if (valueMs === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return resolvePackageBuildTimeoutMs(valueMs, 1);
|
||||
}
|
||||
|
||||
function validateOutputName(value: string) {
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.t(?:ar\.)?gz$/u.test(value)) {
|
||||
throw new Error(`--output-name must be a tarball filename, not a path: ${value}`);
|
||||
}
|
||||
return valueMs === undefined ? undefined : resolveTimerTimeoutMs(Number(valueMs), 1);
|
||||
}
|
||||
|
||||
function resolvePackedOpenClawFileName(value: string) {
|
||||
|
|
@ -253,7 +234,7 @@ export function parseArgs(argv: string[]) {
|
|||
},
|
||||
);
|
||||
if (options.outputName) {
|
||||
validateOutputName(options.outputName);
|
||||
validatePackedTarballOutputName(options.outputName);
|
||||
}
|
||||
if (options.packJson && options.pnpmPack) {
|
||||
throw new Error("--pack-json cannot be combined with --pnpm-pack");
|
||||
|
|
@ -271,8 +252,8 @@ function run(command: string, args: string[], cwd: string, options: RunOptions =
|
|||
}
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const resolvedTimeoutMs = resolveOptionalTimerTimeoutMs(options.timeoutMs);
|
||||
const resolvedKillAfterMs = resolvePackageBuildTimeoutMs(
|
||||
options.killAfterMs,
|
||||
const resolvedKillAfterMs = resolveTimerTimeoutMs(
|
||||
Number(options.killAfterMs),
|
||||
DEFAULT_TIMEOUT_KILL_AFTER_MS,
|
||||
);
|
||||
const useProcessGroup = process.platform !== "win32";
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import {
|
|||
ensureExtensionMemoryBuild,
|
||||
findBuiltExtensionMemoryEntries,
|
||||
} from "./ensure-extension-memory-build.mts";
|
||||
import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mts";
|
||||
import { appendBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import { requireOptionArgument, stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mts";
|
||||
import { appendBoundedTail, formatBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import { formatErrorMessage } from "./lib/error-format.mts";
|
||||
import {
|
||||
captureImportIdentity,
|
||||
|
|
@ -131,10 +131,7 @@ export function parseArgs(argv: string[]): {
|
|||
break parseArgv;
|
||||
case "--extension":
|
||||
case "-e": {
|
||||
const next = args[index + 1];
|
||||
if (!next || next.startsWith("-")) {
|
||||
throw new Error(`${arg} requires a value`);
|
||||
}
|
||||
const next = requireOptionArgument(args, index, arg);
|
||||
options.extensions.push(next);
|
||||
index += 1;
|
||||
break;
|
||||
|
|
@ -154,10 +151,7 @@ export function parseArgs(argv: string[]): {
|
|||
break;
|
||||
}
|
||||
case "--json": {
|
||||
const next = args[index + 1];
|
||||
if (!next || next.startsWith("-")) {
|
||||
throw new Error(`${arg} requires a value`);
|
||||
}
|
||||
const next = requireOptionArgument(args, index, arg);
|
||||
options.jsonPath = path.resolve(next);
|
||||
index += 1;
|
||||
break;
|
||||
|
|
@ -181,13 +175,6 @@ function createOutputCapture(): OutputCapture {
|
|||
return { text: "", truncatedChars: 0 };
|
||||
}
|
||||
|
||||
function formatCapturedOutput(capture: OutputCapture): string {
|
||||
if (capture.truncatedChars === 0) {
|
||||
return capture.text;
|
||||
}
|
||||
return `[output truncated ${capture.truncatedChars} chars; showing tail]\n${capture.text}`;
|
||||
}
|
||||
|
||||
function summarizeStderr(stderr: string, lines = 8, maxChars = STDERR_PREVIEW_MAX_CHARS): string {
|
||||
const text = stderr.trim().split("\n").filter(Boolean).slice(0, lines).join("\n");
|
||||
if (text.length <= maxChars) {
|
||||
|
|
@ -438,14 +425,14 @@ export function runCase({
|
|||
}
|
||||
}
|
||||
}
|
||||
const stderrText = formatCapturedOutput(stderr);
|
||||
const stderrText = formatBoundedTail(stderr);
|
||||
const result: RunCaseResult = {
|
||||
name,
|
||||
code,
|
||||
signal,
|
||||
timedOut,
|
||||
error: null,
|
||||
stdout: formatCapturedOutput(stdout),
|
||||
stdout: formatBoundedTail(stdout),
|
||||
stderr: stderrText,
|
||||
maxRssMb: observation.resources ? observation.resources.maxRssKb / 1024 : null,
|
||||
resources: observation.resources,
|
||||
|
|
|
|||
|
|
@ -17,13 +17,17 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveTimerTimeoutMs } from "../packages/normalization-core/src/number-coercion.ts";
|
||||
import { isRecord as isJsonRecord } from "../packages/normalization-core/src/record-coerce.ts";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mts";
|
||||
import { appendBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import { appendBoundedTail, formatBoundedTail } from "./lib/bounded-output-tail.mjs";
|
||||
import { toErrorObject } from "./lib/error-format.mts";
|
||||
import { terminateManagedChild } from "./lib/managed-child-process.mts";
|
||||
import { resolveNpmJsonEntries } from "./lib/npm-json-output.mts";
|
||||
import { cleanPackedOpenClawTarballs } from "./lib/packed-openclaw-tarballs.mts";
|
||||
import {
|
||||
cleanPackedOpenClawTarballs,
|
||||
validatePackedTarballOutputName,
|
||||
} from "./lib/packed-openclaw-tarballs.mts";
|
||||
import { resolveRepoRoot } from "./lib/repo-root.mjs";
|
||||
import { resolveNpmRunner } from "./npm-runner.mts";
|
||||
import { validatePackageSourceDir } from "./package-source-preflight.mjs";
|
||||
|
|
@ -40,15 +44,10 @@ const COMMAND_STDERR_CAPTURE_MAX_CHARS = 128 * 1024;
|
|||
const COMMAND_TIMEOUT_KILL_AFTER_MS = 5_000;
|
||||
const FORWARDED_SIGNAL_KILL_AFTER_MS = 250;
|
||||
const COMMAND_PROCESS_TREE_EXIT_POLL_MS = 50;
|
||||
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
type ChildSignal = ChildProcess["signalCode"];
|
||||
type TimerHandle = ReturnType<typeof setTimeout>;
|
||||
type ChildKiller = (signal: NodeJS.Signals) => void;
|
||||
type ProcessTreeChild = Pick<ChildProcess, "exitCode" | "kill" | "pid" | "signalCode">;
|
||||
type CommandOutputBuffer = {
|
||||
text: string;
|
||||
truncatedChars: number;
|
||||
};
|
||||
|
||||
type RunOptions = {
|
||||
capture?: boolean;
|
||||
|
|
@ -266,16 +265,10 @@ export function parseArgs(argv: readonly string[]) {
|
|||
},
|
||||
},
|
||||
);
|
||||
validateOutputName(options.outputName);
|
||||
validatePackedTarballOutputName(options.outputName);
|
||||
return options;
|
||||
}
|
||||
|
||||
function validateOutputName(value: string) {
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.t(?:ar\.)?gz$/u.test(value)) {
|
||||
throw new Error(`--output-name must be a tarball filename, not a path: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePackedOpenClawTarballFilename(value: unknown) {
|
||||
const filename = typeof value === "string" ? value.trim() : "";
|
||||
if (
|
||||
|
|
@ -324,31 +317,15 @@ export function resolveNpmPackageCandidatePackRunner(
|
|||
});
|
||||
}
|
||||
|
||||
function numericTimerValueMs(valueMs: unknown) {
|
||||
const value = Number(valueMs);
|
||||
return Number.isFinite(value) ? Math.floor(value) : undefined;
|
||||
}
|
||||
|
||||
function resolvePackageCandidateTimeoutMs(
|
||||
valueMs: unknown,
|
||||
fallbackMs: unknown = MAX_TIMER_TIMEOUT_MS,
|
||||
) {
|
||||
const value = numericTimerValueMs(valueMs) ?? numericTimerValueMs(fallbackMs);
|
||||
return Math.min(Math.max(value ?? MAX_TIMER_TIMEOUT_MS, 1), MAX_TIMER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveOptionalTimerTimeoutMs(valueMs: unknown) {
|
||||
if (valueMs === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return resolvePackageCandidateTimeoutMs(valueMs, 1);
|
||||
return valueMs === undefined ? undefined : resolveTimerTimeoutMs(Number(valueMs), 1);
|
||||
}
|
||||
|
||||
function run(command: string, args: readonly string[], options: RunOptions = {}) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const resolvedTimeoutMs = resolveOptionalTimerTimeoutMs(options.timeoutMs);
|
||||
const resolvedKillAfterMs = resolvePackageCandidateTimeoutMs(
|
||||
options.killAfterMs,
|
||||
const resolvedKillAfterMs = resolveTimerTimeoutMs(
|
||||
Number(options.killAfterMs),
|
||||
COMMAND_TIMEOUT_KILL_AFTER_MS,
|
||||
);
|
||||
const useProcessGroup = process.platform !== "win32";
|
||||
|
|
@ -448,7 +425,7 @@ function run(command: string, args: readonly string[], options: RunOptions = {})
|
|||
resolve(stdout.text);
|
||||
return;
|
||||
}
|
||||
const stderrText = formatCapturedCommandOutput(stderr).trim();
|
||||
const stderrText = formatBoundedTail(stderr).trim();
|
||||
const detail = stderrText ? `\n${stderrText}` : "";
|
||||
reject(new Error(`${command} ${args.join(" ")} failed with ${status ?? signal}${detail}`));
|
||||
});
|
||||
|
|
@ -519,13 +496,6 @@ async function waitForProcessTreeExit(
|
|||
return !processTreeIsAlive(child, useProcessGroup);
|
||||
}
|
||||
|
||||
function formatCapturedCommandOutput(buffer: CommandOutputBuffer) {
|
||||
if (buffer.truncatedChars === 0) {
|
||||
return buffer.text;
|
||||
}
|
||||
return `[output truncated ${buffer.truncatedChars} chars; showing tail]\n${buffer.text}`;
|
||||
}
|
||||
|
||||
export const runCommandForTest = run;
|
||||
|
||||
async function sha256(file: string) {
|
||||
|
|
@ -1426,8 +1396,8 @@ async function openHttpsPackageDownloadResponse(
|
|||
|
||||
async function openPackageDownloadResponse(url: string, options: PackageDownloadOptions) {
|
||||
const lookupHost = options.lookupHost ?? defaultLookupHost;
|
||||
const timeoutMs = resolvePackageCandidateTimeoutMs(
|
||||
options.timeoutMs,
|
||||
const timeoutMs = resolveTimerTimeoutMs(
|
||||
Number(options.timeoutMs),
|
||||
PACKAGE_URL_DOWNLOAD_TIMEOUT_MS,
|
||||
);
|
||||
const maxRedirects = options.maxRedirects ?? PACKAGE_URL_MAX_REDIRECTS;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ import { Readable } from "node:stream";
|
|||
import { finished } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { coerceErrorMessage } from "@openclaw/normalization-core/error-coercion";
|
||||
import {
|
||||
clampPositiveTimerTimeoutMs,
|
||||
resolveTimerTimeoutMs,
|
||||
} from "@openclaw/normalization-core/number-coercion";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import {
|
||||
DEFAULT_E2E_BARE_IMAGE,
|
||||
|
|
@ -65,7 +69,6 @@ const SHELL_POST_FORCE_KILL_WAIT_MS = 1_000;
|
|||
// Private QA subprocess contract. Ordinary lane/CLI failures remain 1; 130/143
|
||||
// acknowledge joined signal cleanup. Only failed owner cleanup uses 2.
|
||||
const CLEANUP_FAILURE_EXIT_CODE = 2;
|
||||
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
const DEFAULT_TIMINGS_FILE = path.join(ROOT_DIR, ".artifacts/docker-tests/lane-timings.json");
|
||||
const DEFAULT_GITHUB_WORKFLOW = "openclaw-live-and-e2e-checks-reusable.yml";
|
||||
const CANDIDATE_ENV_KEYS =
|
||||
|
|
@ -267,25 +270,8 @@ function normalizeReleaseProfileEnv(raw: string | undefined) {
|
|||
);
|
||||
}
|
||||
|
||||
function numericTimerValueMs(valueMs: unknown) {
|
||||
const value = Number(valueMs);
|
||||
return Number.isFinite(value) ? Math.floor(value) : undefined;
|
||||
}
|
||||
|
||||
function resolveDockerSchedulerTimeoutMs(
|
||||
valueMs: unknown,
|
||||
fallbackMs: unknown = MAX_TIMER_TIMEOUT_MS,
|
||||
) {
|
||||
const value = numericTimerValueMs(valueMs) ?? numericTimerValueMs(fallbackMs);
|
||||
return Math.min(Math.max(value ?? MAX_TIMER_TIMEOUT_MS, 1), MAX_TIMER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveOptionalTimerTimeoutMs(valueMs: unknown) {
|
||||
const value = numericTimerValueMs(valueMs);
|
||||
if (value === undefined || value <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveDockerSchedulerTimeoutMs(value);
|
||||
return clampPositiveTimerTimeoutMs(Math.floor(Number(valueMs)));
|
||||
}
|
||||
|
||||
function resourceLimitsSummary(resourceLimits: Record<string, number>) {
|
||||
|
|
@ -933,7 +919,7 @@ export function runShellCommand({
|
|||
return new Promise<ShellCommandResult>((resolve, reject) => {
|
||||
const resolvedTimeoutMs = resolveOptionalTimerTimeoutMs(timeoutMs);
|
||||
const resolvedNoOutputTimeoutMs = resolveOptionalTimerTimeoutMs(noOutputTimeoutMs);
|
||||
const resolvedTimeoutKillGraceMs = resolveDockerSchedulerTimeoutMs(
|
||||
const resolvedTimeoutKillGraceMs = resolveTimerTimeoutMs(
|
||||
timeoutKillGraceMs,
|
||||
SHELL_TIMEOUT_KILL_GRACE_MS,
|
||||
);
|
||||
|
|
@ -1144,7 +1130,7 @@ export function runShellCaptureCommand({
|
|||
}
|
||||
return new Promise<ShellCaptureResult>((resolve, reject) => {
|
||||
const resolvedTimeoutMs = resolveOptionalTimerTimeoutMs(timeoutMs);
|
||||
const resolvedTimeoutKillGraceMs = resolveDockerSchedulerTimeoutMs(
|
||||
const resolvedTimeoutKillGraceMs = resolveTimerTimeoutMs(
|
||||
timeoutKillGraceMs,
|
||||
SHELL_TIMEOUT_KILL_GRACE_MS,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -812,6 +812,8 @@ describe("package-openclaw-for-docker", () => {
|
|||
it("loads from a trusted harness checkout without installed dependencies", async () => {
|
||||
const tempRoot = tempDirs.make("openclaw-package-harness-");
|
||||
const copiedFiles = [
|
||||
"packages/normalization-core/src/number-coercion.ts",
|
||||
"packages/normalization-core/src/string-coerce.ts",
|
||||
"scripts/package-openclaw-for-docker.mts",
|
||||
"scripts/package-changelog.mjs",
|
||||
"scripts/package-docs-map.mjs",
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ it("compares real UI builds with canonical compression and keeps artifacts after
|
|||
"lib/repo-root.mjs",
|
||||
"lib/output-root-guard.mjs",
|
||||
"lib/record-shared.mjs",
|
||||
"lib/regexp.mjs",
|
||||
]) {
|
||||
fs.copyFileSync(path.join(repoRoot, "scripts", script), path.join(root, "scripts", script));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,18 +89,15 @@ function createCliFixture(startupCssGzipBytes = 15, deferredCssGzipBytes = 15) {
|
|||
path.resolve("src/gateway/control-ui-route-preloads.ts"),
|
||||
path.join(gatewayDir, "control-ui-route-preloads.ts"),
|
||||
);
|
||||
fs.copyFileSync(
|
||||
path.resolve("scripts/lib/check-limits.mts"),
|
||||
path.join(scriptLibDir, "check-limits.mts"),
|
||||
);
|
||||
fs.copyFileSync(
|
||||
path.resolve("scripts/lib/control-ui-i18n-config.ts"),
|
||||
path.join(scriptLibDir, "control-ui-i18n-config.ts"),
|
||||
);
|
||||
fs.copyFileSync(
|
||||
path.resolve("scripts/lib/control-ui-i18n-config.json"),
|
||||
path.join(scriptLibDir, "control-ui-i18n-config.json"),
|
||||
);
|
||||
for (const file of [
|
||||
"check-limits.mts",
|
||||
"control-ui-i18n-config.ts",
|
||||
"control-ui-i18n-config.json",
|
||||
"record-shared.mjs",
|
||||
"regexp.mjs",
|
||||
]) {
|
||||
fs.copyFileSync(path.resolve("scripts/lib", file), path.join(scriptLibDir, file));
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(scriptsDir, "tsx.mjs"),
|
||||
`await import(${JSON.stringify(tsxImport)});\n`,
|
||||
|
|
|
|||
|
|
@ -1040,7 +1040,7 @@ export KITCHEN_SINK_SPEC=npm:@openclaw/kitchen-sink@0.0.0
|
|||
source scripts/e2e/lib/kitchen-sink-plugin/sweep.sh
|
||||
run_expect_failure "install/failure" bash -c 'printf "%s\\n" "npm ERR! No matching version @openclaw/kitchen-sink@0.0.0"; exit 1'
|
||||
test -f "$SCRATCH_ROOT/kitchen-sink-expected-failure-install_failure.log"
|
||||
scan_logs_for_unexpected_errors
|
||||
node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs scan-logs
|
||||
`,
|
||||
{
|
||||
HOME_DIR: home,
|
||||
|
|
|
|||
|
|
@ -1803,19 +1803,16 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
|||
|
||||
it("rejects a successful packaged update followed by an old self-swapped process import miss", () => {
|
||||
expect(() =>
|
||||
verifyPackagedUpgradeUpdateResult(
|
||||
{
|
||||
exitCode: 1,
|
||||
stdout: JSON.stringify({
|
||||
status: "ok",
|
||||
after: { version: "2026.4.27" },
|
||||
steps: [{ name: "global update", exitCode: 0 }],
|
||||
}),
|
||||
stderr:
|
||||
"[openclaw] Failed to start CLI: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/prefix/lib/node_modules/openclaw/dist/memory-state-old.js'",
|
||||
},
|
||||
{ candidateVersion: "2026.4.27" },
|
||||
),
|
||||
verifyPackagedUpgradeUpdateResult({
|
||||
exitCode: 1,
|
||||
stdout: JSON.stringify({
|
||||
status: "ok",
|
||||
after: { version: "2026.4.27" },
|
||||
steps: [{ name: "global update", exitCode: 0 }],
|
||||
}),
|
||||
stderr:
|
||||
"[openclaw] Failed to start CLI: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/prefix/lib/node_modules/openclaw/dist/memory-state-old.js'",
|
||||
}),
|
||||
).toThrow(/Packaged upgrade failed/u);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -102,17 +102,11 @@ openclaw_e2e_fixture_plugin_command openclaw_e2e_maybe_timeout 5s node "$OPENCLA
|
|||
const sweepFixtureLoader = `
|
||||
source() {
|
||||
if [[ "$1" == scripts/e2e/lib/plugins/fixtures.sh ]]; then
|
||||
write_fixture_plugin() { mkdir -p "$1"; }
|
||||
write_demo_fixture_plugin() { write_fixture_plugin "$@"; }
|
||||
write_fixture_plugin_with_cli() { write_fixture_plugin "$@"; }
|
||||
write_fixture_plugin_with_vendored_dependency() { write_fixture_plugin "$@"; }
|
||||
write_claude_bundle_fixture() { write_fixture_plugin "$@"; }
|
||||
pack_fixture_plugin() { :; }
|
||||
pack_fixture_plugin_with_cli_registry_dependency() { :; }
|
||||
pack_fake_is_number_package() { :; }
|
||||
pack_fixture_plugin_with_invalid_extension_entry() { :; }
|
||||
start_npm_fixture_registry() { :; }
|
||||
record_fixture_plugin_trust() { :; }
|
||||
openclaw_plugins_cleanup_fixture_servers() { :; }
|
||||
else
|
||||
builtin source "$@"
|
||||
|
|
@ -120,7 +114,8 @@ source() {
|
|||
}
|
||||
node() {
|
||||
case "$1" in
|
||||
scripts/e2e/lib/plugins/assertions.mjs|scripts/e2e/lib/fixture.mjs) return 0 ;;
|
||||
scripts/e2e/lib/fixture.mjs) mkdir -p "$3" ;;
|
||||
scripts/e2e/lib/plugins/assertions.mjs) return 0 ;;
|
||||
*) command node "$@" ;;
|
||||
esac
|
||||
}
|
||||
|
|
@ -131,11 +126,6 @@ source scripts/e2e/lib/plugins/sweep.sh
|
|||
const kitchenFixtureLoader = `
|
||||
export KITCHEN_SINK_SWEEP_SOURCE_ONLY=1
|
||||
source scripts/e2e/lib/kitchen-sink-plugin/sweep.sh
|
||||
assert_kitchen_sink_cutover_preinstalled() { :; }
|
||||
configure_kitchen_sink_runtime() { :; }
|
||||
assert_kitchen_sink_installed() { :; }
|
||||
assert_kitchen_sink_removed() { :; }
|
||||
remove_kitchen_sink_channel_config() { :; }
|
||||
node() {
|
||||
case "$1" in
|
||||
scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs) return 0 ;;
|
||||
|
|
|
|||
|
|
@ -328,12 +328,12 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
|||
sweepScript.indexOf("run_success_scenario()"),
|
||||
sweepScript.indexOf("run_failure_scenario()"),
|
||||
);
|
||||
expect(successScenario.indexOf('plugins install "${install_args[@]}" --force')).toBeLessThan(
|
||||
successScenario.indexOf("configure_kitchen_sink_runtime"),
|
||||
);
|
||||
expect(successScenario.indexOf("configure_kitchen_sink_runtime")).toBeLessThan(
|
||||
successScenario.indexOf('plugins enable "$KITCHEN_SINK_ID"'),
|
||||
);
|
||||
const installIndex = successScenario.indexOf('plugins install "${install_args[@]}" --force');
|
||||
const configureIndex = successScenario.indexOf("assertions.mjs configure-runtime");
|
||||
const enableIndex = successScenario.indexOf('plugins enable "$KITCHEN_SINK_ID"');
|
||||
expect(installIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(configureIndex).toBeGreaterThan(installIndex);
|
||||
expect(enableIndex).toBeGreaterThan(configureIndex);
|
||||
expect(successScenario).toContain('plugins inspect "$KITCHEN_SINK_ID" --runtime --json');
|
||||
expect(successScenario).toContain("plugins inspect --all --runtime --json");
|
||||
expect(sweepScript).toContain("run_failure_scenario");
|
||||
|
|
@ -355,7 +355,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
|||
);
|
||||
expect(script).toContain("docker_e2e_sample_stats_until_exit");
|
||||
expect(script).toContain("scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs");
|
||||
expect(sweepScript).toContain("scan_logs_for_unexpected_errors");
|
||||
expect(sweepScript).toContain("assertions.mjs scan-logs");
|
||||
});
|
||||
|
||||
it("keeps kitchen-sink RPC coverage package-backed and resource-guarded", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue