merge: refresh Daytona hydration branch from main

* origin/main: (57 commits)
  fix(media): fall back when providers return empty output (#118660)
  fix(nextcloud-talk): allow bounded parallel room deliveries (#118692)
  test(state): reuse current agent database fixture (#118677)
  fix(scripts): bound check-file-utils git lookup (#111582)
  fix(slack): scope interactive conversation bindings safely (#118662)
  fix(agents): avoid false compaction after mid-turn precheck (#117963)
  fix(ui): show original filename on chat history attachment cards instead of managed UUID suffix (#118628)
  fix(whatsapp): preserve terminal retry exhaustion lifecycle (#118659)
  fix(doctor): audit every agent workspace (#111840)
  fix(release): skip unavailable Buzz on frozen candidates (#118670)
  fix(ui): surface onboarding and pairing clipboard failures (#118651)
  fix(config): stop plugin schemas rejecting the channel key core writes (#117992)
  test(memory): isolate wiki plugin fixtures (#118654)
  fix(line): clear default access token when removing account (#118055)
  feat: enable rich setup controls in custodian chat (#114631)
  test(ui): reuse responsive browser fixtures (#118655)
  chore(tui): stabilize queued-turn admission in Gateway PTY test (#118638)
  fix(xai): classify exhausted credits as billing (#118615)
  fix(otel): fail closed when configured TLS material is invalid (#118648)
  fix(cli): preserve gateway request errors in health JSON (#118645)
  ...
This commit is contained in:
Vincent Koc
2026-08-03 07:17:56 -07:00
417 changed files with 15844 additions and 8003 deletions
+25
View File
@@ -38,6 +38,31 @@ describe("scripts/lib/arg-utils parseFlagArgs", () => {
expect(parsed.match).toEqual(["alpha", "beta"]);
});
it("supports split-only, empty, transformed, and last-value-wins string contracts", () => {
expect(() =>
parseFlagArgs(["--value=inline"], { value: "" }, [
stringFlag("--value", "value", { allowInline: false }),
]),
).toThrow("Unknown option: --value=inline");
expect(
parseFlagArgs(["--value", "", "--value", "SECOND"], { value: "" }, [
stringFlag("--value", "value", {
allowEmpty: true,
repeatable: true,
transform: (value) => value.toLowerCase(),
}),
]).value,
).toBe("second");
});
it("supports idempotent boolean flags", () => {
expect(
parseFlagArgs(["--verbose", "--verbose"], { verbose: false }, [
booleanFlag("--verbose", "verbose", true, { repeatable: true }),
]).verbose,
).toBe(true);
});
it("rejects duplicate single-value flags", () => {
expect(() =>
parseFlagArgs(["--label", "first", "--label=second"], { label: "" }, [
+51 -1
View File
@@ -1,15 +1,23 @@
// Check File Utils tests cover check file utils script behavior.
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
collectFilesSync,
isCodeFile,
listRepoFilesSync,
relativeToCwd,
toPosixPath,
} from "../../scripts/check-file-utils.js";
import { createScriptTestHarness } from "./test-helpers.js";
const execFileSyncMock = vi.hoisted(() => vi.fn(() => ""));
vi.mock("node:child_process", async (importOriginal) => {
const original = (await importOriginal()) as typeof import("node:child_process");
return { ...original, execFileSync: execFileSyncMock };
});
const { createTempDir } = createScriptTestHarness();
describe("scripts/check-file-utils isCodeFile", () => {
@@ -64,3 +72,45 @@ describe("scripts/check-file-utils relativeToCwd", () => {
);
});
});
describe("scripts/check-file-utils listRepoFilesSync", () => {
afterEach(() => {
execFileSyncMock.mockReset();
});
it("bounds git ls-files with a timeout and kill signal", () => {
execFileSyncMock.mockReturnValue("src/keep.ts\nsrc/skip.d.ts\n");
expect(
listRepoFilesSync("/fake/repo", {
includeFile: (filePath) => isCodeFile(filePath),
}),
).toEqual(["src/keep.ts"]);
expect(execFileSyncMock).toHaveBeenCalledWith(
"git",
expect.arrayContaining(["-C", "/fake/repo", "ls-files", "--"]),
expect.objectContaining({
timeout: 30_000,
killSignal: "SIGKILL",
}),
);
});
it("falls back to filesystem traversal when git ls-files times out", () => {
const error: NodeJS.ErrnoException & { signal?: string } = new Error("Command timed out");
error.code = "ETIMEDOUT";
error.signal = "SIGKILL";
execFileSyncMock.mockImplementation(() => {
throw error;
});
const rootDir = createTempDir("openclaw-check-file-utils-fallback-");
fs.mkdirSync(path.join(rootDir, "src"), { recursive: true });
fs.writeFileSync(path.join(rootDir, "src", "keep.ts"), "");
expect(
listRepoFilesSync(rootDir, {
includeFile: (filePath) => filePath.endsWith(".ts"),
}),
).toEqual(["src/keep.ts"]);
});
});
+8 -6
View File
@@ -21,13 +21,15 @@ describe("scripts/check", () => {
});
it("rejects unknown args before running check stages", () => {
const result = runCheck("--bogus");
for (const args of [["--bogus"], ["bogus", "--help"]]) {
const result = runCheck(...args);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("unknown argument: --bogus");
expect(result.stderr).toContain("Usage: node scripts/check.mjs");
expect(result.stderr).not.toContain("[check]");
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(`unknown argument: ${args[0]}`);
expect(result.stderr).toContain("Usage: node scripts/check.mjs");
expect(result.stderr).not.toContain("[check]");
}
});
it("runs pnpm commands through the managed child runner", async () => {
@@ -9,7 +9,6 @@ import {
releaseEvidenceVerificationArgs,
releaseEvidenceVerifierPath,
resolveRemoteTargetRefSha,
runGhRead,
shouldDeleteTemporaryWorkflowRef,
} from "../../scripts/full-release-validation-at-sha.mjs";
@@ -163,30 +162,9 @@ describe("full-release-validation-at-sha", () => {
});
it("bounds GitHub reads without applying a timeout to workflow dispatch", () => {
const calls: unknown[][] = [];
expect(
runGhRead(["api", "repos/openclaw/openclaw/actions/runs/123"], {
execFileSyncImpl: (...args: unknown[]) => {
calls.push(args);
return " result ";
},
}),
).toBe("result");
expect(calls).toEqual([
[
"gh",
["api", "repos/openclaw/openclaw/actions/runs/123"],
expect.objectContaining({
killSignal: "SIGKILL",
timeout: 60_000,
}),
],
]);
const source = readFileSync("scripts/full-release-validation-at-sha.mjs", "utf8");
expect(source).toContain(
'runGhRead(["api", `repos/openclaw/openclaw/actions/runs/${parentRunId}`])',
);
expect(source).toContain("timeout: GH_READ_TIMEOUT_MS");
expect(source.match(/GH_READ_OPTIONS/gu)).toHaveLength(3);
expect(source).toContain('const dispatchOutput = run("gh", dispatchArgs');
});
@@ -206,12 +206,14 @@ describe("generate-dependency-release-evidence", () => {
});
it("reports CLI argument errors without a Node stack trace", () => {
const result = runCli("--wat");
for (const args of [["--wat"], ["wat", "--help"]]) {
const result = runCli(...args);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe("Unsupported argument: --wat");
expectNoNodeStack(result.stderr);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe(`Unsupported argument: ${args[0]}`);
expectNoNodeStack(result.stderr);
}
});
it("falls back to fetching tags when local previous-release resolution misses", () => {
@@ -454,4 +454,19 @@ describe("release Telegram QA workflow", () => {
.status,
).not.toBe(0);
});
it("shares only the isolated workspace with the trusted scenario host", () => {
const createSut = requireRun(
"run_telegram",
"Create isolated Telegram SUT identity and launcher",
);
expect(createSut).toContain('workspace="${temp_root}/workspace"');
expect(createSut).toContain('chown -R "$RUNNER_UID:$SUT_GID" "$workspace"');
expect(createSut).toContain('chmod -R u=rwX,g=rwX,o= "$workspace"');
expect(createSut).toContain('find "$workspace" -type d -exec chmod g+s {} +');
expect(createSut).not.toContain(
'for path in \\\n "$temp_root/workspace" \\\n "${OPENCLAW_HOME:?}"',
);
});
});
@@ -3183,7 +3183,7 @@ describe("package artifact reuse", () => {
);
const requireBuzz = workflowStep(buzzJob, "Require requested Buzz QA runner");
expect(requireBuzz.if).toBe(
"always() && steps.resolve_buzz.outcome == 'success' && steps.resolve_buzz.outputs.available != 'true'",
"always() && inputs.expected_sha == '' && steps.resolve_buzz.outcome == 'success' && steps.resolve_buzz.outputs.available != 'true'",
);
expect(requireBuzz.run).toContain(
"The selected ref does not declare the requested Buzz QA runner.",
+55
View File
@@ -6,6 +6,8 @@ import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
execGhApiRead,
execGhJson,
execGhRead,
execPlainGh,
plainGhEnv,
PLAIN_GH_SYSTEM_CANDIDATES,
@@ -114,6 +116,59 @@ describe("plain gh helpers", () => {
expect(output).toContain("OPENCLAW_GH_BIN_SET=");
});
it("shares bounded PATH-shim reads and JSON parsing", () => {
const calls: unknown[][] = [];
const execFileSyncImpl = (...args: unknown[]) => {
calls.push(args);
return '{"ok":true}';
};
expect(
execGhJson(
["api", "repos/openclaw/openclaw"],
{
killSignal: "SIGKILL",
stdio: ["ignore", "pipe", "inherit"],
timeout: 60_000,
},
{ execFileSyncImpl },
),
).toEqual({ ok: true });
expect(calls).toEqual([
[
"gh",
["api", "repos/openclaw/openclaw"],
expect.objectContaining({
encoding: "utf8",
killSignal: "SIGKILL",
maxBuffer: 32 * 1024 * 1024,
stdio: ["ignore", "pipe", "inherit"],
timeout: 60_000,
}),
],
]);
expect(
execGhRead(
["api", "rate_limit"],
{ encoding: "utf8" },
{ execFileSyncImpl: () => " result " },
),
).toBe(" result ");
const failure = new Error("gh read failed");
expect(() =>
execGhRead(
["api", "rate_limit"],
{},
{
execFileSyncImpl: () => {
throw failure;
},
},
),
).toThrow(failure);
});
it("runs the shell helper with color disabled", () => {
const ghPath = makeFakeGh();
const outputPath = path.join(path.dirname(path.dirname(ghPath)), "output.txt");
@@ -107,6 +107,11 @@ describe("plugin npm extended-stable workflow", () => {
it("overlays the complete trusted packaging helper dependency set", () => {
const parsed = workflow();
const lockGenerator = readFileSync("scripts/generate-npm-package-lock.mjs", "utf8");
expect(lockGenerator).toContain(
'path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")',
);
expect(lockGenerator).not.toContain("./lib/repo-root.mjs");
const preflightCheckout = step(
parsed.jobs?.preview_plugin_pack,
"Checkout trusted packaging helper",
+14 -12
View File
@@ -59,19 +59,21 @@ describe("plugin SDK surface report", () => {
});
it("rejects unknown CLI options before collecting SDK stats", () => {
const result = spawnSync(
process.execPath,
["scripts/plugin-sdk-surface-report.mjs", "--chekc"],
{
cwd: process.cwd(),
encoding: "utf8",
},
);
for (const args of [["--chekc"], ["chekc", "--help"]]) {
const result = spawnSync(
process.execPath,
["scripts/plugin-sdk-surface-report.mjs", ...args],
{
cwd: process.cwd(),
encoding: "utf8",
},
);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe("Unknown plugin SDK surface report option: --chekc");
expect(result.stderr).not.toContain("at ");
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe(`Unknown plugin SDK surface report option: ${args[0]}`);
expect(result.stderr).not.toContain("at ");
}
});
it("prints help before collecting SDK stats", () => {
@@ -68,6 +68,50 @@ async function writeBaileysMediaFile(packageRoot: string, text: string) {
}
describe("bundled plugin postinstall", () => {
it("resolves TypeScript from NODE_PATH during external modules-dir installs", async () => {
const packageRoot = await createTempDirAsync("openclaw-postinstall-node-path-");
const scriptRoot = path.join(packageRoot, "scripts");
const externalModulesDir = path.join(packageRoot, "external-node-modules");
await fs.mkdir(path.join(scriptRoot, "lib"), { recursive: true });
await fs.mkdir(externalModulesDir, { recursive: true });
await fs.writeFile(
path.join(packageRoot, "package.json"),
'{"name":"openclaw","type":"module","version":"2026.7.2"}\n',
);
for (const relativePath of [
"scripts/postinstall-bundled-plugins.mjs",
"scripts/lib/package-dist-imports.mjs",
"scripts/lib/guard-inventory-utils.mjs",
]) {
await fs.copyFile(
fileURLToPath(new URL(`../../${relativePath}`, import.meta.url)),
path.join(packageRoot, relativePath),
);
}
await fs.symlink(
fileURLToPath(new URL("../../node_modules/typescript", import.meta.url)),
path.join(externalModulesDir, "typescript"),
process.platform === "win32" ? "junction" : "dir",
);
const result = spawnSync(
process.execPath,
[path.join(scriptRoot, "postinstall-bundled-plugins.mjs")],
{
cwd: packageRoot,
encoding: "utf8",
env: {
...process.env,
NODE_PATH: [externalModulesDir, process.env.NODE_PATH]
.filter(Boolean)
.join(path.delimiter),
},
},
);
expect(result.status, result.stderr).toBe(0);
});
it("recognizes direct invocation through symlinked temp prefixes", () => {
const realpathSync = vi.fn((value: string) =>
value.replace(/^\/var\/folders\//u, "/private/var/folders/"),
+7 -1
View File
@@ -4,7 +4,7 @@ import { testForceTesting } from "../../scripts/test-force.js";
describe("scripts/test-force.ts", () => {
it("prints help without clearing ports or running tests", () => {
const args = testForceTesting.parseArgs(["--help"]);
const args = testForceTesting.parseArgs(["--help", "--bogus"]);
expect(args).toEqual({ help: true });
expect(testForceTesting.usage()).toContain("Usage: node --import tsx scripts/test-force.ts");
@@ -16,5 +16,11 @@ describe("scripts/test-force.ts", () => {
expect(() => testForceTesting.parseArgs(["--bogus"])).toThrow(
/unknown argument: --bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
);
expect(() => testForceTesting.parseArgs(["bogus"])).toThrow(
/unknown argument: bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
);
expect(() => testForceTesting.parseArgs(["bogus", "--help"])).toThrow(
/unknown argument: bogus[\s\S]*Usage: node --import tsx scripts\/test-force\.ts/u,
);
});
});
+1
View File
@@ -1931,6 +1931,7 @@ describe("scripts/test-projects changed-target routing", () => {
"scripts/lib/ts-topology/analyze.ts": ["test/scripts/ts-topology.test.ts"],
"scripts/lib/ts-topology/reports.ts": ["test/scripts/ts-topology.test.ts"],
"scripts/lib/ts-topology/scope.ts": ["test/scripts/ts-topology.test.ts"],
"scripts/lib/repo-root.mjs": ["test/scripts/ts-guard-utils.test.ts"],
"scripts/lib/ts-guard-utils.mjs": ["test/scripts/ts-guard-utils.test.ts"],
"scripts/lib/tsgo-sparse-guard.mjs": [
"test/scripts/run-tsgo.test.ts",
+18 -2
View File
@@ -1,9 +1,10 @@
// Ts Guard Utils tests cover ts guard utils script behavior.
import { existsSync } from "node:fs";
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { resolveRepoRoot } from "../../scripts/lib/ts-guard-utils.mjs";
import { resolveRepoRoot } from "../../scripts/lib/repo-root.mjs";
/**
* Regression tests for resolveRepoRoot().
@@ -49,4 +50,19 @@ describe("resolveRepoRoot", () => {
expect(fromLib).toBe(fromScripts);
expect(fromScripts).toBe(fromExtension);
});
it("resolves an unpacked workspace without git metadata", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-repo-root-"));
try {
mkdirSync(path.join(root, "scripts", "nested"), { recursive: true });
writeFileSync(path.join(root, "package.json"), '{"name":"openclaw"}\n');
writeFileSync(path.join(root, "pnpm-workspace.yaml"), "packages: []\n");
expect(
resolveRepoRoot(pathToFileURL(path.join(root, "scripts", "nested", "tool.mjs")).href),
).toBe(root);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});
+9 -7
View File
@@ -21,13 +21,15 @@ describe("scripts/verify", () => {
});
it("rejects unknown args before running verify stages", () => {
const result = runVerify("--bogus");
for (const args of [["--bogus"], ["bogus", "--help"]]) {
const result = runVerify(...args);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("unknown argument: --bogus");
expect(result.stderr).toContain("Usage: node scripts/verify.mjs");
expect(result.stderr).not.toContain("CRABBOX_PHASE:");
expect(result.stderr).not.toContain("[verify]");
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(`unknown argument: ${args[0]}`);
expect(result.stderr).toContain("Usage: node scripts/verify.mjs");
expect(result.stderr).not.toContain("CRABBOX_PHASE:");
expect(result.stderr).not.toContain("[verify]");
}
});
});