refactor(deadcode): enforce repository hard zero (#108641)

This commit is contained in:
Peter Steinberger
2026-07-15 22:40:00 -07:00
committed by GitHub
parent cf8cfa8322
commit bae9752c5a
187 changed files with 1413 additions and 2284 deletions

View File

@@ -53,7 +53,7 @@ exec "$@"
const DEPENDENCY_INPUT_RE =
/^(?:\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|patches\/)|(?:^|\/)package\.json$/u;
export class UpdateInvariantError extends Error {
class UpdateInvariantError extends Error {
constructor(code, message) {
super(message);
this.name = "UpdateInvariantError";
@@ -282,7 +282,7 @@ export function inspectBuildState(checkout, expectedSha) {
};
}
export function verifyCheckout(checkout, { remote }) {
function verifyCheckout(checkout, { remote }) {
let resolvedCheckout;
try {
resolvedCheckout = realpathSync(checkout);
@@ -369,7 +369,7 @@ export function verifyCheckout(checkout, { remote }) {
};
}
export function updateMain({ checkout, remote }, dependencies = {}) {
function updateMain({ checkout, remote }, dependencies = {}) {
const before = verifyCheckout(checkout, { remote });
const fetchMain =
dependencies.fetchMain ??
@@ -2164,7 +2164,7 @@ function parseArgs(argv) {
return options;
}
export function main(argv = process.argv.slice(2)) {
function main(argv = process.argv.slice(2)) {
try {
console.log(JSON.stringify(maintainMain(parseArgs(argv))));
} catch (error) {

View File

@@ -60,7 +60,7 @@ function isBodyLocationType(locationType) {
}
/** Decides whether redacting an issue/PR body requires notifying the reporter. */
export function decideBodyRedaction(currentBody, redactedBody) {
function decideBodyRedaction(currentBody, redactedBody) {
const bodyChanged = String(currentBody) !== String(redactedBody);
return {
body_changed: bodyChanged,
@@ -69,7 +69,7 @@ export function decideBodyRedaction(currentBody, redactedBody) {
}
/** Loads redaction-result metadata for issue/PR body secret locations. */
export function loadBodyRedactionResult(locationType, resultFile) {
function loadBodyRedactionResult(locationType, resultFile) {
if (!isBodyLocationType(locationType)) {
return { notify_required: true };
}
@@ -887,7 +887,7 @@ function cmdSummary(jsonFile) {
const args = [];
export const commands = {
const commands = {
"fetch-alert": () => cmdFetchAlert(args[0]),
"fetch-content": () => cmdFetchContent(args[0]),
"redact-body": () => cmdRedactBody(args[0], args[1], args[2]),

View File

@@ -0,0 +1,132 @@
/**
* Full-repository unused-export audit.
*
* Production Knip intentionally ignores test-support exports. This companion
* config makes every test/spec file an entry and audits those support modules
* too, so a helper used by a src/ or plugin test cannot be mistaken for dead
* code while genuinely unused test and script exports still fail the gate.
*/
import fs from "node:fs";
import path from "node:path";
import YAML from "yaml";
import productionConfig from "./knip.config.ts";
const TEST_ENTRY_GLOB = "**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!";
function listQaScenarioExecutionEntries(dir = "qa/scenarios"): string[] {
const entries = fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
return listQaScenarioExecutionEntries(entryPath);
}
if (!entry.isFile() || (!entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml"))) {
return [];
}
const document = YAML.parse(fs.readFileSync(entryPath, "utf8")) as {
scenario?: { execution?: { kind?: unknown; path?: unknown } };
};
const execution = document.scenario?.execution;
return execution?.kind !== "flow" && typeof execution?.path === "string"
? [`${execution.path}!`]
: [];
});
return [...new Set(entries)].toSorted((left, right) => left.localeCompare(right));
}
const QA_SCENARIO_EXECUTION_ENTRIES = listQaScenarioExecutionEntries();
const ROOT_TEST_ENTRY_GLOBS = [
"*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
"src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
"scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
"test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
// Vitest loads these by configuration or module alias rather than imports.
"test/setup*.ts!",
"test/non-isolated-runner.ts!",
"test/vitest/*-runtime.ts!",
"test/vitest/vitest*.config.ts!",
// Test drivers and Docker fixtures are executed by path from package scripts
// and the test-project registry.
"test/e2e/qa-lab/runtime/agent-bundle-mcp-tools-docker-client.ts!",
"test/e2e/qa-lab/runtime/docker-e2e-lane.ts!",
"test/e2e/qa-lab/runtime/mcp-channels-docker-client.ts!",
// Invoked directly by the Docker image-auth scenario.
"test/e2e/qa-lab/runtime/openai-image-auth-docker-client.ts!",
"test/e2e/qa-lab/runtime/system-agent-first-run-docker-client.ts!",
// QA scenario YAML dispatches these scripts/tests by path rather than import.
...QA_SCENARIO_EXECUTION_ENTRIES,
// The Voice Call QA scenario loads this fixture through a generated plugin directory.
"test/e2e/qa-lab/runtime/fixtures/voice-call-runtime-plugin/index.js!",
"test/scripts/fixtures/secret-provider-integrations-harness.mjs!",
// Loaded with cache-busting query strings so configuration fallback tests
// get independent module initialization.
"test/helpers/config/bundled-channel-config-runtime.ts!",
// The topology analyzer owns these as an intentionally self-contained graph.
"test/fixtures/ts-topology/basic/**/*.{js,mjs,cjs,ts,mts,cts}!",
] as const;
const workspaces = Object.fromEntries(
Object.entries(productionConfig.workspaces).map(([workspace, settings]) => [
workspace,
{
...settings,
...(workspace === "."
? {
// Root test aliases and mocks load these from their owning bundled
// plugin workspaces; do not duplicate plugin runtime dependencies.
ignoreDependencies: [
...("ignoreDependencies" in settings ? settings.ignoreDependencies : []),
"baileys",
"discord-api-types",
],
}
: {}),
entry: [
...settings.entry,
...(workspace === "."
? [".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!", ...ROOT_TEST_ENTRY_GLOBS]
: [TEST_ENTRY_GLOB]),
],
project:
workspace === "."
? [...settings.project, ".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!"]
: settings.project,
},
]),
);
const config = {
...productionConfig,
ignoreFiles: [
// The production pass omits scripts, live probes, and generated runtime
// surfaces. This test-aware pass audits all source files; only build output
// and ambient declarations stay excluded.
"dist/**",
"packages/*/dist/**",
"scripts/**/*.d.{mts,ts}",
// Ambient declarations and handwritten declaration companions are type
// inputs, not executable roots.
"test/external-script-modules.d.ts",
"test/vitest/**/*.d.{mts,ts}",
],
// Keep only build artifacts out of the full-tree export audit. In
// particular, do not inherit production's test-support exclusions.
ignore: ["dist/**", "packages/*/dist/**", "**/.boundary-stubs/**"],
// This fixture deliberately mixes used, aliased, and unused exports so the
// topology analyzer can prove each classification.
ignoreIssues: {
// Cache-busting dynamic imports are real consumers, but Knip cannot map
// their query-suffixed module ids back to these named test-support exports.
"test/helpers/config/bundled-channel-config-runtime.ts": ["exports"],
"test/fixtures/ts-topology/basic/**": [
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
],
},
workspaces,
};
export default config;

View File

@@ -7,7 +7,77 @@ function bundledPluginFile(pluginId: string, relativePath: string, suffix = ""):
return `${BUNDLED_PLUGIN_ROOT_DIR}/${pluginId}/${relativePath}${suffix}`;
}
// Package scripts, workflows, Docker scenarios, and documented maintainer commands invoke these
// files by path. They are executable roots rather than importable library modules.
const repositoryScriptEntries = [
"scripts/build-discord-activity-sdk.mjs!",
"scripts/check-live-cache.ts!",
"scripts/check-package-dist-imports.mjs!",
"scripts/dev/ios-node-e2e.ts!",
"scripts/diffs-shiki-curated.ts!",
"scripts/e2e/lib/browser-cdp-snapshot/assert-snapshot.mjs!",
"scripts/e2e/lib/browser-cdp-snapshot/fixture-server.mjs!",
"scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs!",
"scripts/e2e/lib/clawhub-fixture-server.cjs!",
"scripts/e2e/lib/codex-media-path/client.mjs!",
"scripts/e2e/lib/codex-media-path/fake-codex-app-server.mjs!",
"scripts/e2e/lib/codex-media-path/write-config.mjs!",
"scripts/e2e/lib/config-reload/assert-log.mjs!",
"scripts/e2e/lib/config-reload/mutate-metadata.mjs!",
"scripts/e2e/lib/docker-artifact-proof/write-identities.ts!",
"scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs!",
"scripts/e2e/lib/doctor-install-switch/write-wrapper.mjs!",
"scripts/e2e/lib/fixture.mjs!",
"scripts/e2e/lib/fixtures/config.mjs!",
"scripts/e2e/lib/fixtures/plugins.mjs!",
"scripts/e2e/lib/fixtures/workspace.mjs!",
"scripts/e2e/lib/npm-telegram-live/prepare-package.mjs!",
"scripts/e2e/lib/onboard/assert-config.mjs!",
"scripts/e2e/lib/onboard/write-config.mjs!",
"scripts/e2e/lib/openai-chat-tools/client.mjs!",
"scripts/e2e/lib/openai-chat-tools/write-config.mjs!",
"scripts/e2e/lib/package-git-fixture.mjs!",
"scripts/e2e/lib/parallels-package/build-info-commit.mjs!",
"scripts/e2e/lib/parallels-package/log-progress-extract.mjs!",
"scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs!",
"scripts/e2e/lib/plugin-update/registry-server.mjs!",
"scripts/e2e/lib/plugins/npm-registry-server.mjs!",
"scripts/e2e/lib/release-scenarios/write-cli-plugin.mjs!",
"scripts/e2e/lib/release-scenarios/write-marketplace.mjs!",
"scripts/e2e/lib/release-user-journey/clickclack-fixture.mjs!",
"scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs!",
"scripts/e2e/lib/run-with-pty.mjs!",
"scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs!",
"scripts/embedded-run-abort-leak.ts!",
"scripts/fixtures/packed-plugin-sdk-type-smoke.ts!",
"scripts/ios-release-signing.mjs!",
"scripts/lib/docker-plugin-selection.mjs!",
"scripts/lib/openclaw-test-state.mjs!",
"scripts/list-prod-store-packages.mjs!",
// Invoked by scripts/lib/live-docker-stage.sh during container validation.
"scripts/live-docker-normalize-config.ts!",
"scripts/mcp-code-mode-gateway-e2e.ts!",
"scripts/openclaw-release-clawhub-plan.ts!",
"scripts/openclaw-release-clawhub-runtime-state.ts!",
"scripts/plugin-prerelease-liveish-matrix.mjs!",
"scripts/pr-gates-lock.mjs!",
"scripts/pr-lib/process-group-runner.mjs!",
"scripts/pre-commit/filter-staged-files.mjs!",
"scripts/qa-coverage-report.ts!",
"scripts/qa-parity-report.ts!",
"scripts/repro/tsx-name-repro.ts!",
"scripts/resolve-frozen-codex-live-suite.mjs!",
"scripts/secrets/openclaw-bws-resolver.mjs!",
"scripts/sync-labels.ts!",
"scripts/test-built-bundled-channel-entry-smoke.mjs!",
"scripts/update-clawtributors.ts!",
"scripts/verify-stable-main-closeout.mjs!",
"scripts/write-package-dist-inventory.ts!",
"scripts/write-plugin-sdk-entry-dts.ts!",
] as const;
const rootEntries = [
...repositoryScriptEntries,
"openclaw.mjs!",
"src/index.ts!",
"src/entry.ts!",
@@ -17,8 +87,16 @@ const rootEntries = [
"src/agents/compaction-planning.worker.ts!",
"scripts/print-cli-backend-live-metadata.ts!",
"scripts/repro/code-mode-namespace-live.ts!",
// Workflow/package-script entrypoints are not imported from production modules.
"scripts/openclaw-cross-os-release-checks.ts!",
"scripts/bench-sqlite-reliability.ts!",
// Docker/manual E2E executables and their nested assertion/probe entrypoints.
"scripts/e2e/*.{js,mjs,ts}!",
"scripts/e2e/lib/**/{assertions,probe,mock-server}.{js,mjs,ts}!",
"src/audit/audit-event-writer.worker.ts!",
"src/agents/model-provider-auth.worker.ts!",
// Split runtime loaded through a path assembled in subagent-registry.ts.
"src/agents/subagent-registry.runtime.ts!",
// Loaded lazily by the registry; its callbacks form the orphan-recovery runtime contract.
"src/agents/subagent-orphan-recovery.ts!",
// Task cancellation loads this control facade by string path to avoid a registry cycle.
@@ -38,6 +116,19 @@ const rootEntries = [
"src/mcp/codex-supervision-tools-serve.ts!",
// Spawned by generated system-agent MCP configs; this stdio entry is not statically imported.
"src/mcp/openclaw-tools-serve.ts!",
// Spawned by ACPX and QA Lab from a generated plugin-tool MCP command line.
"src/mcp/plugin-tools-serve.ts!",
// Dedicated tsdown entry exercised against built plugin singletons.
"src/plugins/build-smoke-entry.ts!",
// Package-script owners invoke these generated-artifact modules directly.
"src/config/doc-baseline.ts!",
"src/plugins/runtime-sidecar-paths-baseline.ts!",
// Imported by scripts/tsdown-build.mjs as the AI package build configuration.
"tsdown.ai.config.ts!",
// Maintainer-owned compatibility data referenced by release/docs workflows.
"src/commands/doctor/shared/deprecation-compat.ts!",
// Compiled as the package-boundary failure canary by the extension checker.
"src/plugins/contracts/rootdir-boundary-canary.ts!",
"scripts/qa/render-maturity-docs.ts!",
bundledPluginFile("telegram", "src/audit.ts", "!"),
bundledPluginFile("telegram", "src/token.ts", "!"),
@@ -49,7 +140,6 @@ const rootEntries = [
] as const;
const bundledPluginEntries = [
"*.ts!",
"index.ts!",
"setup-entry.ts!",
// Core resolves these public plugin artifacts by basename rather than by a
@@ -57,6 +147,15 @@ const bundledPluginEntries = [
"*-api.ts!",
"cli-metadata.ts!",
"channel-entry.ts!",
// Manifest and SDK loaders resolve these public artifacts by basename.
"configured-state.ts!",
"auth-presence.ts!",
"thread-bindings-runtime.ts!",
"document-extractor.ts!",
"web-content-extractor.ts!",
"timeouts.ts!",
"action-runtime.runtime.ts!",
"allow-from.ts!",
// Provider catalogs and web tools resolve these manifest/convention-owned
// modules from the plugin root at runtime.
"provider-discovery.ts!",
@@ -67,8 +166,6 @@ const bundledPluginEntries = [
"src/subagent-hooks-api.ts!",
] as const;
const strictBundledPluginEntries = bundledPluginEntries.filter((entry) => entry !== "*.ts!");
const bundledPluginIgnoredRuntimeDependencies = [
"@agentclientprotocol/claude-agent-acp",
"@a2ui/lit",
@@ -113,10 +210,27 @@ const rootBundledPluginRuntimeDependencies = [
"tokenjuice",
] as const;
function strictBundledPluginWorkspace(extraEntries: readonly string[] = []) {
// Root installation and build workflows deliberately mirror these dependencies from their
// owning workspace, or invoke their package binaries/loaders without a static module import.
const rootToolingAndWorkspaceDependencies = [
"@a2ui/lit",
"@copilotkit/aimock",
"@lit-labs/signals",
"@lit/context",
// scripts/ui.js anchors these lookups at ui/package.json before invoking the UI workspace.
"@vitest/browser-playwright",
"dompurify",
"jscpd",
"lit",
"oxlint",
"oxlint-tsgolint",
"signal-utils",
] as const;
function bundledPluginWorkspace(extraEntries: readonly string[] = []) {
return {
entry: [...strictBundledPluginEntries, ...extraEntries],
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
entry: [...bundledPluginEntries, ...extraEntries],
project: ["**/*.{js,mjs,ts}!"],
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
} as const;
}
@@ -125,6 +239,7 @@ function strictBundledPluginWorkspace(extraEntries: readonly string[] = []) {
// available to tests without becoming part of the production dead-code scan.
const ignoredTestSupportFiles = [
"**/__tests__/**",
"**/test/**",
"src/test-utils/**",
"**/test-helpers/**",
"**/test-fixtures/**",
@@ -136,6 +251,9 @@ const ignoredTestSupportFiles = [
"**/*test-harness.ts",
"**/*test-utils.ts",
"**/*test-support.ts",
"**/*.test-loader.ts",
"**/*.live-helpers.ts",
"**/*.live-probe-helpers.ts",
"**/*test-shared.ts",
"**/*mocks.ts",
"**/*.e2e-mocks.ts",
@@ -169,13 +287,20 @@ const ignoredTestSupportFiles = [
"**/*.test-mocks.ts",
"**/*.test-utils.ts",
"test/helpers/live-image-probe.ts",
// Legacy test-only owners whose filenames predate the test-support convention.
"src/plugins/contracts/host-hook-fixture.ts",
"src/plugins/contracts/tts-contract-suites.ts",
] as const;
const config = {
ignoreFiles: [
// Production mode excludes dev/maintainer executables. The full-tree
// companion config removes this exclusion and audits them as script roots.
"scripts/**",
"dist/**",
"packages/*/dist/**",
// Declaration companions describe executable JavaScript modules; they are not standalone roots.
"scripts/**/*.d.{mts,ts}",
"**/live-*.ts",
"src/secrets/credential-matrix.ts",
"src/shared/text/assistant-visible-text.ts",
@@ -184,10 +309,14 @@ const config = {
],
// Knip's `ignoreFiles` only suppresses unused-file findings. Test helpers
// belong in `ignore` so they do not inflate unused-export/type findings.
ignore: ["dist/**", "packages/*/dist/**", ...ignoredTestSupportFiles],
ignore: ["dist/**", "packages/*/dist/**", "**/.boundary-stubs/**", ...ignoredTestSupportFiles],
// Script exports are checked with every script as an entry and entry-export
// reporting enabled. Suppress them only in this application-production scan.
ignoreIssues: {
"scripts/**": ["exports", "nsExports", "types", "nsTypes", "enumMembers", "namespaceMembers"],
},
workspaces: {
".": {
entry: rootEntries,
ignoreDependencies: [
"@openclaw/*",
// Docker packaging stages @openclaw/ai without nested dependencies after
@@ -202,14 +331,19 @@ const config = {
"partial-json",
"sqlite-vec",
"tree-sitter-bash",
...rootToolingAndWorkspaceDependencies,
...rootBundledPluginRuntimeDependencies,
],
// Platform tools and shell builtins used by package scripts and process-boundary tests.
ignoreBinaries: ["mint", "open", "sleep", "xcrun"],
project: [
"src/**/*.ts!",
"scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
"test/**/*.{js,mjs,cjs,ts,mts,cts}!",
"*.config.{js,mjs,cjs,ts,mts,cts}!",
"*.mjs!",
],
entry: [...rootEntries, "test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!"],
},
ui: {
entry: [
@@ -372,13 +506,17 @@ const config = {
entry: ["index.js!", "scripts/postinstall.js!"],
project: ["index.js!", "scripts/**/*.js!"],
},
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock-mantle`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/anthropic`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/anthropic-vertex`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/acpx`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/azure-speech`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/browser`]: strictBundledPluginWorkspace([
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock-mantle`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/anthropic`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/anthropic-vertex`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/acpx`]: bundledPluginWorkspace([
// Copied as executable runtime internals by the package artifact manifest.
"src/runtime-internals/mcp-command-line.mjs!",
"src/runtime-internals/mcp-proxy.mjs!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/azure-speech`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/browser`]: bundledPluginWorkspace([
// Core and plugin-SDK facades resolve these shipped Browser surfaces by basename.
"browser-control-auth.ts!",
"browser-config.ts!",
@@ -386,53 +524,77 @@ const config = {
"browser-host-inspection.ts!",
"browser-maintenance.ts!",
"browser-profiles.ts!",
// Chrome manifest/package scripts load these without TypeScript imports.
"chrome-extension/background.js!",
"chrome-extension/popup.js!",
"scripts/copy-chrome-extension.mjs!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/cloudflare-ai-gateway`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/chutes`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/clawrouter`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/cohere`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/comfy`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/copilot`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/copilot-proxy`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/codex`]: strictBundledPluginWorkspace([
// Codex provider runtime and harness surfaces are reached through plugin
[`${BUNDLED_PLUGIN_ROOT_DIR}/canvas`]: bundledPluginWorkspace([
// Package build/copy scripts are invoked from package.json.
"scripts/bundle-a2ui.mjs!",
"scripts/copy-a2ui.mjs!",
"scripts/pnpm-runner.mjs!",
// Rolldown consumes this config and its browser bootstrap entry.
"src/host/a2ui-app/rolldown.config.mjs!",
"src/host/a2ui-app/bootstrap.js!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/cloudflare-ai-gateway`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/chutes`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/clawrouter`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/cohere`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/comfy`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/copilot`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/copilot-proxy`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/codex`]: bundledPluginWorkspace([
// Provider runtime and harness surfaces are reached through plugin
// registration contracts rather than static imports from the entrypoint.
"harness.ts!",
"media-understanding-provider.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepgram`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepinfra`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/discord`]: strictBundledPluginWorkspace([
// Channel package-state probes resolve this module from package metadata.
"configured-state.ts!",
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepgram`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepinfra`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/discord`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/diffs`]: bundledPluginWorkspace([
// scripts/build-diffs-viewer-runtime.mjs bundles this browser entry.
"src/viewer-client.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/elevenlabs`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/featherless`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/fal`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/fireworks`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/google`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/huggingface`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/github-copilot`]: strictBundledPluginWorkspace([
[`${BUNDLED_PLUGIN_ROOT_DIR}/elevenlabs`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/featherless`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/fal`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/fireworks`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/google`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/huggingface`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/github-copilot`]: bundledPluginWorkspace([
// Auth, replay, token, and stream helpers are runtime-owned provider
// surfaces that are consumed through plugin hooks and dynamic imports.
// surfaces consumed through plugin hooks and dynamic imports.
"connection-bound-ids.ts!",
"login.ts!",
"stream.ts!",
"token.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/kilocode`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/kimi-coding`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-core`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-lancedb`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft-foundry`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-claude`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-hermes`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/minimax`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/mistral`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/moonshot`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/nvidia`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/openai`]: strictBundledPluginWorkspace([
[`${BUNDLED_PLUGIN_ROOT_DIR}/kilocode`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/kimi-coding`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/matrix`]: bundledPluginWorkspace([
// Native import wrapper shipped alongside the Matrix runtime bundle.
"src/plugin-entry.runtime.js!",
// The monitor lazy-loads outbound behavior on inbound-only processes.
"src/matrix/send.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-core`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-lancedb`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft-foundry`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-claude`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-hermes`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/minimax`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/mistral`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/moonshot`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/mxc`]: bundledPluginWorkspace([
// Copied to dist and spawned by the MXC backend.
"src/mxc-spawn-launcher.mjs!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/nvidia`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/openai`]: bundledPluginWorkspace([
// OpenAI exposes provider, OAuth, overlay, media, usage, and realtime
// contracts to runtime/plugin integration paths that Knip cannot trace.
"embedding-batch.ts!",
@@ -451,39 +613,47 @@ const config = {
"tts.ts!",
"usage.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/opencode`]: strictBundledPluginWorkspace([
[`${BUNDLED_PLUGIN_ROOT_DIR}/opencode`]: bundledPluginWorkspace([
// Session catalog and provider helpers are plugin-owned runtime surfaces.
"media-understanding-provider.ts!",
"provider-catalog.ts!",
"session-catalog-plugin.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/opencode-go`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/openrouter`]: strictBundledPluginWorkspace([
[`${BUNDLED_PLUGIN_ROOT_DIR}/opencode-go`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/openrouter`]: bundledPluginWorkspace([
// OAuth, model, and media provider helpers are runtime/plugin surfaces.
"image-generation-provider.ts!",
"media-understanding-provider.ts!",
"models.ts!",
"oauth.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/pixverse`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qianfan`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qwen`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qa-lab`]: strictBundledPluginWorkspace([
// The plugin-SDK QA Lab facade resolves this CLI surface by basename.
[`${BUNDLED_PLUGIN_ROOT_DIR}/pixverse`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qianfan`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qwen`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/qa-lab`]: bundledPluginWorkspace([
// Core loads the CLI facade by basename; QA Lab also owns a nested Vite app.
"cli.ts!",
// The debugger UI is a separate browser entrypoint outside src/.
"web/index.html!",
"web/src/app.ts!",
"web/src/main.ts!",
"web/vite.config.ts!",
// Imported directly from the GitHub Actions smoke-plan script.
"src/ci-smoke-plan.ts!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/senseaudio`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/tavily`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/tencent`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/vllm`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/voyage`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/xiaomi`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/xai`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/senseaudio`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/tavily`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/tencent`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/vllm`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/vault`]: bundledPluginWorkspace([
// Shipped resolver child process declared as a static plugin artifact.
"vault-secret-ref-resolver.js!",
]),
[`${BUNDLED_PLUGIN_ROOT_DIR}/voyage`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/xiaomi`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/xai`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/llama-cpp`]: {
entry: bundledPluginEntries,
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
project: ["**/*.{js,mjs,ts}!"],
ignoreDependencies: [
// The provider resolves node-llama-cpp from its own package at runtime
// so local embeddings use the plugin-owned native dependency.
@@ -491,21 +661,21 @@ const config = {
...bundledPluginIgnoredRuntimeDependencies,
],
},
[`${BUNDLED_PLUGIN_ROOT_DIR}/lmstudio`]: strictBundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/lmstudio`]: bundledPluginWorkspace(),
[`${BUNDLED_PLUGIN_ROOT_DIR}/reef`]: {
// Reef vendors its wire protocol under protocol/, which owns the noble
// crypto dependencies. The protocol barrel is the vendored library's
// public surface, so its exports are intentional even where the channel
// consumes only a subset.
entry: [...bundledPluginEntries, "protocol/index.ts!", "protocol/node.ts!"],
project: ["*.ts!", "src/**/*.{js,mjs,ts}!", "protocol/**/*.ts!"],
project: ["**/*.{js,mjs,ts}!"],
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
},
[`${BUNDLED_PLUGIN_ROOT_DIR}/*`]: {
// Bundled plugins often load their public surface via string specifiers in
// `index.ts` contracts, so Knip needs these convention-based entry files.
entry: bundledPluginEntries,
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
project: ["**/*.{js,mjs,ts}!"],
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
},
},

View File

@@ -0,0 +1,76 @@
/**
* Entry-export audit for repository scripts.
*
* Production configuration already owns the executable script roots. This
* companion pass keeps the rest of scripts/** as library project files and
* makes repository tests real consumers of deliberately testable helpers.
*/
import productionConfig from "./knip.config.ts";
const scriptEntries = productionConfig.workspaces["."].entry.filter((entry) =>
entry.startsWith("scripts/"),
);
const config = {
ignoreWorkspaces: ["apps/**", "extensions/**", "packages/**", "ui"],
ignore: ["scripts/**/*.d.{mts,cts,ts}", "scripts/**/*.test-support.{js,mjs,cjs,ts,mts,cts}"],
// Script entrypoints import core and Plugin SDK APIs. Those owners are
// checked by the application scans; this pass owns only scripts/** exports.
ignoreIssues: {
// These executable modules are also loaded through variable/file-URL imports
// by build or subprocess test harnesses, which Knip cannot resolve statically.
"scripts/diffs-shiki-curated.ts": [
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
],
"scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs": [
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
],
"scripts/e2e/secret-provider-integrations.mjs": [
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
],
"scripts/repro/code-mode-namespace-live.ts": [
"exports",
"nsExports",
"types",
"nsTypes",
"enumMembers",
"namespaceMembers",
],
"src/**": ["exports", "nsExports", "types", "nsTypes", "enumMembers", "namespaceMembers"],
"test/**": ["exports", "nsExports", "types", "nsTypes", "enumMembers", "namespaceMembers"],
},
workspaces: {
".": {
entry: [
...scriptEntries,
".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
"scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
"test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
"src/plugin-sdk/api-baseline.ts!",
],
project: [
".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
"scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
"test/**/*.{js,mjs,cjs,ts,mts,cts}!",
"src/plugin-sdk/api-baseline.ts!",
],
},
},
};
export default config;

View File

@@ -4,7 +4,7 @@
/** Tab group shown to the user; membership == what the agent may touch. */
export const OPENCLAW_TAB_GROUP_TITLE = "OpenClaw";
export const EXTENSION_RELAY_PROTOCOL = "openclaw-extension-relay";
const EXTENSION_RELAY_PROTOCOL = "openclaw-extension-relay";
const EXTENSION_RELAY_TOKEN_PROTOCOL_PREFIX = "openclaw-extension-token.";
const CHROME_GROUP_COLORS = {

View File

@@ -8,17 +8,10 @@ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
export {
createCliRuntimeCapture,
expectGeneratedTokenPersistedToGatewayAuth,
type CliMockOutputRuntime,
type CliRuntimeCapture,
} from "openclaw/plugin-sdk/test-fixtures";
export {
createTempHomeEnv,
withEnv,
withEnvAsync,
withFetchPreconnect,
isLiveTestEnabled,
} from "openclaw/plugin-sdk/test-env";
export type { FetchMock, TempHomeEnv } from "openclaw/plugin-sdk/test-env";
export { createTempHomeEnv, isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
export type { TempHomeEnv } from "openclaw/plugin-sdk/test-env";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
export function useAutoCleanupTempDirTracker(registerCleanup: (cleanup: () => void) => unknown) {

View File

@@ -268,7 +268,7 @@ export function mockCall(mock: unknown, label: string, index = 0): unknown[] {
return call;
}
export function getMockServerVersion() {
function getMockServerVersion() {
return "0.132.0";
}

View File

@@ -127,10 +127,6 @@ export async function writeCodexAppServerBinding(
await testCodexAppServerBindingStore.mutate(testIdentity(sessionId), { kind: "set", binding });
}
export async function clearCodexAppServerBinding(sessionId: string): Promise<void> {
await testCodexAppServerBindingStore.mutate(testIdentity(sessionId), { kind: "clear" });
}
export async function clearCodexAppServerBindingForThread(
sessionId: string,
threadId: string,

View File

@@ -78,20 +78,23 @@ vi.mock("openclaw/plugin-sdk/agent-runtime", () => ({
resolveDefaultAgentDir: mocks.resolveDefaultAgentDir,
}));
import {
assertCodexAppServerClientStartSelectionCurrent,
detachSharedCodexAppServerClientIfCurrent,
getSharedCodexAppServerClient,
readCodexAppServerClientProcessIdentity,
} from "./shared-client.js";
let listCodexAppServerModels: typeof import("./models.js").listCodexAppServerModels;
let clearSharedCodexAppServerClient: typeof import("./shared-client.js").clearSharedCodexAppServerClient;
let assertCodexAppServerClientStartSelectionCurrent: typeof import("./shared-client.js").assertCodexAppServerClientStartSelectionCurrent;
let clearSharedCodexAppServerClientIfCurrent: typeof import("./shared-client.js").clearSharedCodexAppServerClientIfCurrent;
let clearSharedCodexAppServerClientIfCurrentAndWait: typeof import("./shared-client.js").clearSharedCodexAppServerClientIfCurrentAndWait;
let createIsolatedCodexAppServerClient: typeof import("./shared-client.js").createIsolatedCodexAppServerClient;
let detachSharedCodexAppServerClientIfCurrent: typeof import("./shared-client.js").detachSharedCodexAppServerClientIfCurrent;
let getLeasedSharedCodexAppServerClient: typeof import("./shared-client.js").getLeasedSharedCodexAppServerClient;
let getSharedCodexAppServerClient: typeof import("./shared-client.js").getSharedCodexAppServerClient;
let isCodexAppServerStartSelectionChangedError: typeof import("./shared-client.js").isCodexAppServerStartSelectionChangedError;
let retainSharedCodexAppServerClientIfCurrent: typeof import("./shared-client.js").retainSharedCodexAppServerClientIfCurrent;
let releaseLeasedSharedCodexAppServerClient: typeof import("./shared-client.js").releaseLeasedSharedCodexAppServerClient;
let releaseCodexAppServerClientLease: typeof import("./shared-client.js").releaseCodexAppServerClientLease;
let readCodexAppServerClientProcessIdentity: typeof import("./shared-client.js").readCodexAppServerClientProcessIdentity;
let resolveCodexNativeConfigFenceKey: typeof import("./shared-client.js").resolveCodexNativeConfigFenceKey;
let resolveCodexAppServerSpawnIdentity: typeof import("./shared-client.js").resolveCodexAppServerSpawnIdentity;
let retireSharedCodexAppServerClientIfCurrent: typeof import("./shared-client.js").retireSharedCodexAppServerClientIfCurrent;
@@ -183,19 +186,15 @@ describe("shared Codex app-server client", () => {
beforeAll(async () => {
({ listCodexAppServerModels } = await import("./models.js"));
({
assertCodexAppServerClientStartSelectionCurrent,
clearSharedCodexAppServerClient,
clearSharedCodexAppServerClientIfCurrent,
clearSharedCodexAppServerClientIfCurrentAndWait,
createIsolatedCodexAppServerClient,
detachSharedCodexAppServerClientIfCurrent,
getLeasedSharedCodexAppServerClient,
getSharedCodexAppServerClient,
isCodexAppServerStartSelectionChangedError,
retainSharedCodexAppServerClientIfCurrent,
releaseLeasedSharedCodexAppServerClient,
releaseCodexAppServerClientLease,
readCodexAppServerClientProcessIdentity,
resolveCodexNativeConfigFenceKey,
resolveCodexAppServerSpawnIdentity,
retireSharedCodexAppServerClientIfCurrent,

View File

@@ -68,7 +68,7 @@ type CodexAppServerClientStartMetadata = {
};
/** Successful physical process identity, excluding environment and credentials. */
export type CodexAppServerClientProcessIdentity = {
type CodexAppServerClientProcessIdentity = {
clientId: string;
command: string;
argsFingerprint: string;
@@ -79,7 +79,7 @@ export type CodexAppServerClientProcessIdentity = {
userAgent?: string;
};
export type CodexAppServerSpawnIdentity = Omit<
type CodexAppServerSpawnIdentity = Omit<
CodexAppServerClientProcessIdentity,
"clientId" | "serverVersion" | "userAgent"
>;
@@ -160,7 +160,7 @@ export function resolveCodexAppServerSpawnIdentity(
};
}
export class CodexAppServerStartSelectionChangedError extends Error {
class CodexAppServerStartSelectionChangedError extends Error {
readonly code = "CODEX_APP_SERVER_START_SELECTION_CHANGED";
constructor() {

View File

@@ -11,7 +11,6 @@
"@github/copilot-sdk": "1.0.5"
},
"devDependencies": {
"@github/copilot": "1.0.68",
"@openclaw/plugin-sdk": "workspace:*"
},
"openclaw": {

View File

@@ -1,43 +0,0 @@
// Matrix helper module supports test helpers behavior.
import fs from "node:fs";
import path from "node:path";
export const MATRIX_TEST_HOMESERVER = "https://matrix.example.org";
export const MATRIX_DEFAULT_USER_ID = "@bot:example.org";
export const MATRIX_DEFAULT_ACCESS_TOKEN = "tok-123";
export const MATRIX_DEFAULT_DEVICE_ID = "DEVICE123";
export const MATRIX_OPS_ACCOUNT_ID = "ops";
export const MATRIX_OPS_USER_ID = "@ops-bot:example.org";
export const MATRIX_OPS_ACCESS_TOKEN = "tok-ops";
export const MATRIX_OPS_DEVICE_ID = "DEVICEOPS";
export function writeFile(filePath: string, value: string) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, value, "utf8");
}
export function writeMatrixCredentials(
stateDir: string,
params?: {
accountId?: string;
homeserver?: string;
userId?: string;
accessToken?: string;
deviceId?: string;
},
) {
const accountId = params?.accountId ?? MATRIX_OPS_ACCOUNT_ID;
writeFile(
path.join(stateDir, "credentials", "matrix", `credentials-${accountId}.json`),
JSON.stringify(
{
homeserver: params?.homeserver ?? MATRIX_TEST_HOMESERVER,
userId: params?.userId ?? MATRIX_OPS_USER_ID,
accessToken: params?.accessToken ?? MATRIX_OPS_ACCESS_TOKEN,
deviceId: params?.deviceId ?? MATRIX_OPS_DEVICE_ID,
},
null,
2,
),
);
}

View File

@@ -15,8 +15,10 @@
"typebox": "1.3.3"
},
"devDependencies": {
"@microsoft/teams.common": "2.0.13",
"@openclaw/plugin-sdk": "workspace:*",
"jose": "6.2.3",
"jwks-rsa": "3.2.2",
"openclaw": "workspace:*"
},
"peerDependencies": {

View File

@@ -7,8 +7,6 @@ export const TEST_HEX_PRIVATE_KEY =
export const TEST_HEX_PUBLIC_KEY =
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
export const TEST_NSEC = "nsec1qypqxpq9qtpqscx7peytzfwtdjmcv0mrz5rjpej8vjppfkqfqy8skqfv3l";
export const TEST_RELAY_URL = "wss://relay.example.com";
export const TEST_SETUP_RELAY_URLS = ["wss://relay.damus.io", "wss://relay.primal.net"];
export const TEST_RESOLVED_PRIVATE_KEY = "resolved-nostr-private-key";

View File

@@ -1,5 +0,0 @@
// Test setup file for nostr extension
import { vi } from "vitest";
// Mock console.error to suppress noise in tests
vi.spyOn(console, "error").mockImplementation(() => {});

View File

@@ -15,9 +15,9 @@ import { registerPolicyDoctorChecks } from "./register.js";
export let workspaceDir: string;
export let originalOpenClawHome: string | undefined;
let originalOpenClawHome: string | undefined;
export let originalOpenClawStateDir: string | undefined;
let originalOpenClawStateDir: string | undefined;
export function cfgWithPolicy(settings: Record<string, unknown> = {}): OpenClawConfig {
return {

View File

@@ -22,7 +22,8 @@
"@openclaw/plugin-sdk": "workspace:*",
"@openclaw/slack": "workspace:*",
"@openclaw/whatsapp": "workspace:*",
"openclaw": "workspace:*"
"openclaw": "workspace:*",
"vite": "8.1.3"
},
"peerDependencies": {
"openclaw": ">=2026.7.2"

View File

@@ -13,7 +13,7 @@ const QA_SMOKE_CI_SCENARIO_IDS = new Set([
"system-agent-ring-zero-setup",
"dreaming-shadow-trial-report",
"gateway-smoke",
"luna-thinking-visibility-switch",
"model-switch-follow-up",
"group-visible-reply-tool",
"long-running-release-audit",
"matrix-restart-resume",

View File

@@ -283,5 +283,3 @@ export function buildCaptureTimelineModel(params: {
visibleTimelineLanes,
};
}
export type CaptureTimelineModel = ReturnType<typeof buildCaptureTimelineModel>;

View File

@@ -55,7 +55,7 @@ export type Message = {
reactions: Array<{ emoji: string; senderId: string }>;
};
export type BusEvent =
type BusEvent =
| { cursor: number; kind: "thread-created"; thread: Thread }
| { cursor: number; kind: string; message?: Message; emoji?: string };
@@ -104,7 +104,7 @@ export type Bootstrap = {
};
};
export type ScenarioStep = {
type ScenarioStep = {
name: string;
status: "pass" | "fail" | "skip";
details?: string;
@@ -120,7 +120,7 @@ export type ScenarioOutcome = {
finishedAt?: string;
};
export type ScenarioRun = {
type ScenarioRun = {
kind: "suite" | "self-check";
status: "idle" | "running" | "completed";
startedAt?: string;
@@ -144,7 +144,7 @@ export type RunnerSelection = {
scenarioIds: string[];
};
export type RunnerSnapshot = {
type RunnerSnapshot = {
status: "idle" | "running" | "completed" | "failed";
selection: RunnerSelection;
startedAt?: string;
@@ -171,7 +171,7 @@ export type OutcomesEnvelope = {
run: ScenarioRun | null;
};
export type CaptureSessionSummary = {
type CaptureSessionSummary = {
id: string;
startedAt: number;
endedAt?: number;
@@ -226,12 +226,12 @@ export type CaptureQueryEnvelope = {
rows: Array<Record<string, string | number | null>>;
};
export type CaptureObservedDimension = {
type CaptureObservedDimension = {
value: string;
count: number;
};
export type CaptureCoverageSummary = {
type CaptureCoverageSummary = {
sessionId: string;
totalEvents: number;
unlabeledEventCount: number;
@@ -263,13 +263,13 @@ export type CaptureStartupStatusEnvelope = {
status: CaptureStartupStatus;
};
export type EvidenceStatus = QaEvidenceGalleryEntryView["status"];
type EvidenceStatus = QaEvidenceGalleryEntryView["status"];
export type EvidenceArtifactView = QaEvidenceArtifactView;
export type EvidenceEntryView = QaEvidenceGalleryEntryView;
export type EvidenceProducerContextFile = QaEvidenceProducerContextFile;
export type EvidenceMatrixCell = QaEvidenceMatrixCellView;
export type EvidenceProducerContext = QaEvidenceProducerContext;
export type EvidenceGalleryModel = QaEvidenceGalleryModel;
type EvidenceGalleryModel = QaEvidenceGalleryModel;
export type EvidenceEnvelope = {
evidence: EvidenceGalleryModel | null;

View File

@@ -7,7 +7,7 @@ type RuntimeConfigApi = ReturnType<NonNullable<CommandsPort["approveRuntimeGette
type ReplaceConfigFile = RuntimeConfigApi["replaceConfigFile"];
type ReplaceConfigFileResult = Awaited<ReturnType<ReplaceConfigFile>>;
export type WrittenQQBotConfig = {
type WrittenQQBotConfig = {
streaming?: unknown;
accounts?: { default?: { streaming?: unknown } };
};

View File

@@ -11,7 +11,7 @@ import {
import pMap, { pMapSkip } from "p-map";
import { formatSlackFileReference } from "../file-reference.js";
import type { SlackAttachment, SlackFile } from "../types.js";
export { MAX_SLACK_MEDIA_FILES, type SlackMediaResult } from "./media-types.js";
export type { SlackMediaResult } from "./media-types.js";
import { MAX_SLACK_MEDIA_FILES, type SlackMediaResult } from "./media-types.js";
import { type FetchLike, fetchWithRuntimeDispatcher, saveRemoteMedia } from "./media.runtime.js";
import { logVerbose } from "./thread.runtime.js";
@@ -19,8 +19,6 @@ export {
resetSlackThreadStarterCacheForTest,
resolveSlackThreadHistory,
resolveSlackThreadStarter,
type SlackThreadMessage,
type SlackThreadStarter,
} from "./thread.js";
function isSlackHostname(hostname: string): boolean {

View File

@@ -173,7 +173,7 @@ export function resetSlackThreadStarterCacheForTest(): void {
THREAD_STARTER_CACHE.clear();
}
export type SlackThreadMessage = {
type SlackThreadMessage = {
text: string;
userId?: string;
ts?: string;

View File

@@ -121,36 +121,36 @@ export const deliverInboundReplyWithMessageSendContext =
deliverInboundReplyWithMessageSendContextHoisted;
export const emitInternalMessageSentHook = emitInternalMessageSentHookHoisted;
export const recordOutboundMessageForPromptContext = recordOutboundMessageForPromptContextHoisted;
export const createForumTopicTelegram = createForumTopicTelegramHoisted;
export const deleteMessageTelegram = deleteMessageTelegramHoisted;
export const editForumTopicTelegram = editForumTopicTelegramHoisted;
const createForumTopicTelegram = createForumTopicTelegramHoisted;
const deleteMessageTelegram = deleteMessageTelegramHoisted;
const editForumTopicTelegram = editForumTopicTelegramHoisted;
export const editMessageTelegram = editMessageTelegramHoisted;
export const reactMessageTelegram = reactMessageTelegramHoisted;
const reactMessageTelegram = reactMessageTelegramHoisted;
export const sendMessageTelegram = sendMessageTelegramHoisted;
export const sendPollTelegram = sendPollTelegramHoisted;
export const sendStickerTelegram = sendStickerTelegramHoisted;
export const loadConfig = loadConfigHoisted;
export const readChannelAllowFromStore = readChannelAllowFromStoreHoisted;
export const upsertChannelPairingRequest = upsertChannelPairingRequestHoisted;
export const enqueueSystemEvent = enqueueSystemEventHoisted;
export const buildModelsProviderData = buildModelsProviderDataHoisted;
export const listSkillCommandsForAgents = listSkillCommandsForAgentsHoisted;
const sendPollTelegram = sendPollTelegramHoisted;
const sendStickerTelegram = sendStickerTelegramHoisted;
const loadConfig = loadConfigHoisted;
const readChannelAllowFromStore = readChannelAllowFromStoreHoisted;
const upsertChannelPairingRequest = upsertChannelPairingRequestHoisted;
const enqueueSystemEvent = enqueueSystemEventHoisted;
const buildModelsProviderData = buildModelsProviderDataHoisted;
const listSkillCommandsForAgents = listSkillCommandsForAgentsHoisted;
export const createChannelMessageReplyPipeline = createChannelMessageReplyPipelineHoisted;
export const wasSentByBot = wasSentByBotHoisted;
const wasSentByBot = wasSentByBotHoisted;
export const appendAssistantMirrorMessageByIdentity = appendAssistantMirrorMessageByIdentityHoisted;
export const getSessionEntry = getSessionEntryHoisted;
const getSessionEntry = getSessionEntryHoisted;
export const loadSessionStore = loadSessionStoreHoisted;
export const readLatestAssistantTextByIdentity = readLatestAssistantTextByIdentityHoisted;
export const resolveStorePath = resolveStorePathHoisted;
const resolveStorePath = resolveStorePathHoisted;
export const generateTopicLabel = generateTopicLabelHoisted;
export const describeStickerImage = describeStickerImageHoisted;
export const loadModelCatalog = loadModelCatalogHoisted;
export const findModelInCatalog = findModelInCatalogHoisted;
export const modelSupportsVision = modelSupportsVisionHoisted;
export const resolveAgentDir = resolveAgentDirHoisted;
export const resolveDefaultModelForAgent = resolveDefaultModelForAgentHoisted;
export const getAgentScopedMediaLocalRoots = getAgentScopedMediaLocalRootsHoisted;
export const resolveChunkMode = resolveChunkModeHoisted;
const loadModelCatalog = loadModelCatalogHoisted;
const findModelInCatalog = findModelInCatalogHoisted;
const modelSupportsVision = modelSupportsVisionHoisted;
const resolveAgentDir = resolveAgentDirHoisted;
const resolveDefaultModelForAgent = resolveDefaultModelForAgentHoisted;
const getAgentScopedMediaLocalRoots = getAgentScopedMediaLocalRootsHoisted;
const resolveChunkMode = resolveChunkModeHoisted;
export const resolveMarkdownTableMode = resolveMarkdownTableModeHoisted;
vi.mock("./draft-stream.js", () => ({
@@ -675,4 +675,4 @@ export function describeTelegramDispatch(name: string, registerTests: () => void
});
}
export type { Bot, TelegramBotDeps };
export type { TelegramBotDeps };

View File

@@ -218,7 +218,7 @@ const menuSyncHoisted = vi.hoisted(() => ({
await bot.api.setMyCommands(commandsToRegister);
}),
}));
export const syncTelegramMenuCommands = menuSyncHoisted.syncTelegramMenuCommands;
const syncTelegramMenuCommands = menuSyncHoisted.syncTelegramMenuCommands;
function parseModelRef(raw: string): { provider?: string; model: string } {
const trimmed = raw.trim();
@@ -357,7 +357,7 @@ const grammySpies = vi.hoisted(() => ({
export const useSpy: MockFn<(arg: unknown) => void> = grammySpies.useSpy;
export const middlewareUseSpy: AnyMock = grammySpies.middlewareUseSpy;
export const onSpy: AnyMock = grammySpies.onSpy;
export const stopSpy: AnyMock = grammySpies.stopSpy;
const stopSpy: AnyMock = grammySpies.stopSpy;
export const commandSpy: AnyMock = grammySpies.commandSpy;
export const botCtorSpy: MockFn<
(token: string, options?: { client?: { fetch?: typeof fetch }; botInfo?: unknown }) => void
@@ -433,7 +433,7 @@ const runnerHoisted = vi.hoisted(() => ({
export const sequentializeSpy: AnyMock = runnerHoisted.sequentializeSpy;
export let sequentializeKey: ((ctx: unknown) => string) | undefined;
export const throttlerSpy: AnyMock = runnerHoisted.throttlerSpy;
export const telegramBotRuntimeForTest = {
const telegramBotRuntimeForTest = {
Bot: class {
api = {
config: { use: grammySpies.useSpy },

View File

@@ -165,7 +165,7 @@ function installTopicNameRuntimeForTest(): void {
} as TelegramRuntime);
}
export const telegramBotRuntimeForTest: TelegramBotRuntimeForTest = {
const telegramBotRuntimeForTest: TelegramBotRuntimeForTest = {
Bot: class {
api = apiStub;
use = middlewareUseSpy;

View File

@@ -1,7 +0,0 @@
/**
* Vitest setup file for Twitch plugin tests.
*
* Re-exports the root test setup to avoid duplication.
*/
export * from "../../../test/setup.js";

View File

@@ -92,7 +92,7 @@ function createVoiceCallStateRuntimeForTests(): VoiceCallStateRuntime["state"] {
};
}
export function installVoiceCallStateRuntimeForTests(): void {
function installVoiceCallStateRuntimeForTests(): void {
setVoiceCallStateRuntime({ state: createVoiceCallStateRuntimeForTests() });
}

View File

@@ -1,2 +0,0 @@
// Whatsapp plugin module implements constants behavior.
export { DEFAULT_WEB_MEDIA_BYTES } from "./src/auto-reply/constants.js";

View File

@@ -20,7 +20,6 @@ import {
resetLoadConfigMock as _resetLoadConfigMock,
} from "./test-helpers.js";
export { createAcceptedWhatsAppSendResult } from "./inbound/send-result.test-helper.js";
export {
resetLoadConfigMock,
setLoadConfigMock,

View File

@@ -7,7 +7,6 @@ import { resetLogger, setLoggerOverride } from "openclaw/plugin-sdk/runtime-env"
import { afterEach, beforeEach, expect, vi } from "vitest";
import {
loadConfigMock,
readAllowFromStoreMock as pairingReadAllowFromStoreMock,
resetPairingSecurityMocks,
upsertPairingRequestMock as pairingUpsertPairingRequestMock,
} from "./pairing-security.test-harness.js";
@@ -30,10 +29,9 @@ export const DEFAULT_WEB_INBOX_CONFIG = {
},
} as const;
export const mockLoadConfig: typeof loadConfigMock = loadConfigMock;
export const readAllowFromStoreMock = pairingReadAllowFromStoreMock;
export const upsertPairingRequestMock = pairingUpsertPairingRequestMock;
export type MockSock = {
type MockSock = {
ev: EventEmitter;
end: AnyMockFn;
ws: { close: AnyMockFn };

View File

@@ -1,6 +0,0 @@
// Whatsapp plugin module implements targets behavior.
export {
isWhatsAppGroupJid,
isWhatsAppUserTarget,
normalizeWhatsAppTarget,
} from "./src/normalize-target.js";

View File

@@ -1584,10 +1584,11 @@
"crabbox:run": "node scripts/crabbox-wrapper.mjs run",
"crabbox:stop": "node scripts/crabbox-wrapper.mjs stop",
"crabbox:warmup": "node scripts/crabbox-wrapper.mjs warmup",
"deadcode:dependencies": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --dependencies --no-config-hints",
"deadcode:dependencies": "pnpm deadcode:full",
"deadcode:exports": "node scripts/check-deadcode-exports.mjs",
"deadcode:full": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --no-config-hints --exclude duplicates && pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.all-exports.config.ts --no-progress --reporter compact --no-config-hints --exclude duplicates",
"deadcode:knip": "pnpm --config.minimum-release-age=0 dlx --package knip@6.8.0 knip --config config/knip.config.ts --production --no-progress --reporter compact --files --dependencies",
"deadcode:report": "pnpm deadcode:knip; pnpm deadcode:exports",
"deadcode:report": "pnpm deadcode:full; pnpm deadcode:exports",
"deadcode:unused-files": "node scripts/check-deadcode-unused-files.mjs",
"deps:root-ownership": "node scripts/root-dependency-ownership-audit.mjs",
"deps:root-ownership:check": "node scripts/root-dependency-ownership-audit.mjs --check",
@@ -2068,7 +2069,6 @@
"devDependencies": {
"@a2ui/lit": "0.10.1",
"@copilotkit/aimock": "1.35.0",
"@grammyjs/types": "3.28.0",
"@lit-labs/signals": "0.3.0",
"@lit/context": "1.1.6",
"@mdx-js/mdx": "3.1.1",
@@ -2086,19 +2086,23 @@
"@types/ws": "8.18.1",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@vitest/coverage-v8": "4.1.9",
"acorn": "8.17.0",
"esbuild": "0.28.1",
"fast-glob": "3.3.3",
"jscpd": "4.2.4",
"jsdom": "29.1.1",
"lit": "3.3.3",
"oxfmt": "0.58.0",
"oxlint": "1.73.0",
"oxlint-tsgolint": "0.24.0",
"playwright": "1.61.1",
"shiki": "4.3.0",
"signal-utils": "0.21.1",
"sigstore": "4.1.1",
"tsdown": "0.22.1",
"tsx": "4.22.4",
"unrun": "0.3.1",
"vite": "8.1.3",
"vitest": "4.1.9"
},
"optionalDependencies": {

View File

@@ -92,7 +92,7 @@ function isThinkingPart(part: Pick<Part, "thought" | "thoughtSignature">): boole
* a signature from being overwritten with `undefined` within the same streamed block.
* @internal Directly tested provider implementation detail.
*/
export function retainThoughtSignature(
function retainThoughtSignature(
existing: string | undefined,
incoming: string | undefined,
): string | undefined {
@@ -129,7 +129,7 @@ function resolveThoughtSignature(
* Models via Google APIs that require explicit tool call IDs in function calls/responses.
* @internal Directly tested provider implementation detail.
*/
export function requiresToolCallId(modelId: string): boolean {
function requiresToolCallId(modelId: string): boolean {
return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-");
}
@@ -396,7 +396,7 @@ export function convertTools(
* Map tool choice string to Gemini FunctionCallingConfigMode.
* @internal Directly tested provider implementation detail.
*/
export function mapToolChoice(choice: string): FunctionCallingConfigMode {
function mapToolChoice(choice: string): FunctionCallingConfigMode {
switch (choice) {
case "auto":
return FunctionCallingConfigMode.AUTO;
@@ -604,7 +604,7 @@ export function getDisabledGoogleThinkingConfig<T extends GoogleApiType>(
}
/** @internal Directly tested provider implementation detail. */
export function isGemma4Model<T extends GoogleApiType>(model: Model<T>): boolean {
function isGemma4Model<T extends GoogleApiType>(model: Model<T>): boolean {
return /gemma-?4/.test(model.id.toLowerCase());
}
@@ -701,7 +701,7 @@ function getGoogleBudget<T extends GoogleApiType>(
* Map Gemini FinishReason to our StopReason.
* @internal Directly tested provider implementation detail.
*/
export function mapStopReason(reason: FinishReason): StopReason {
function mapStopReason(reason: FinishReason): StopReason {
switch (reason) {
case FinishReason.STOP:
return "stop";

View File

@@ -18,10 +18,10 @@ const ArtifactQueryParamsProperties = {
};
/** Shared artifact filter payload used by list-style requests. */
export const ArtifactQueryParamsSchema = closedObject(ArtifactQueryParamsProperties);
const ArtifactQueryParamsSchema = closedObject(ArtifactQueryParamsProperties);
/** Artifact lookup payload with a required artifact id plus optional scope filters. */
export const ArtifactGetParamsSchema = closedObject({
const ArtifactGetParamsSchema = closedObject({
...ArtifactQueryParamsProperties,
artifactId: NonEmptyString,
});

View File

@@ -8,6 +8,9 @@
"@openclaw/retry": "workspace:*",
"p-map": "7.0.5"
},
"devDependencies": {
"openclaw": "workspace:*"
},
"exports": {
"./runtime-core": "./src/runtime-core.ts",
"./runtime-cli": "./src/runtime-cli.ts",

49
pnpm-lock.yaml generated
View File

@@ -236,9 +236,6 @@ importers:
'@copilotkit/aimock':
specifier: 1.35.0
version: 1.35.0(vitest@4.1.9)
'@grammyjs/types':
specifier: 3.28.0
version: 3.28.0
'@lit-labs/signals':
specifier: 0.3.0
version: 0.3.0
@@ -290,9 +287,15 @@ importers:
'@vitest/coverage-v8':
specifier: 4.1.9
version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)
acorn:
specifier: 8.17.0
version: 8.17.0
esbuild:
specifier: 0.28.1
version: 0.28.1
fast-glob:
specifier: 3.3.3
version: 3.3.3
jscpd:
specifier: 4.2.4
version: 4.2.4
@@ -311,6 +314,9 @@ importers:
oxlint-tsgolint:
specifier: 0.24.0
version: 0.24.0
playwright:
specifier: 1.61.1
version: 1.61.1
shiki:
specifier: 4.3.0
version: 4.3.0
@@ -329,6 +335,9 @@ importers:
unrun:
specifier: 0.3.1
version: 0.3.1
vite:
specifier: 8.1.3
version: 8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
vitest:
specifier: 4.1.9
version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
@@ -595,9 +604,6 @@ importers:
specifier: 1.0.5
version: 1.0.5
devDependencies:
'@github/copilot':
specifier: 1.0.68
version: 1.0.68
'@openclaw/plugin-sdk':
specifier: workspace:*
version: link:../../packages/plugin-sdk
@@ -1260,12 +1266,18 @@ importers:
specifier: 1.3.3
version: 1.3.3
devDependencies:
'@microsoft/teams.common':
specifier: 2.0.13
version: 2.0.13
'@openclaw/plugin-sdk':
specifier: workspace:*
version: link:../../packages/plugin-sdk
jose:
specifier: 6.2.3
version: 6.2.3
jwks-rsa:
specifier: 3.2.2
version: 3.2.2
openclaw:
specifier: workspace:*
version: link:../..
@@ -1458,18 +1470,18 @@ importers:
extensions/qa-lab:
dependencies:
'@openclaw/crabline':
specifier: 0.1.11
version: 0.1.11
'@openclaw/matrix':
specifier: workspace:*
version: link:../matrix
'@copilotkit/aimock':
specifier: 1.35.0
version: 1.35.0(vitest@4.1.9)
'@modelcontextprotocol/sdk':
specifier: 1.29.0
version: 1.29.0(zod@4.4.3)
'@openclaw/crabline':
specifier: 0.1.11
version: 0.1.11
'@openclaw/matrix':
specifier: workspace:*
version: link:../matrix
p-limit:
specifier: 7.3.0
version: 7.3.0
@@ -1507,6 +1519,9 @@ importers:
openclaw:
specifier: workspace:*
version: link:../..
vite:
specifier: 8.1.3
version: 8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
extensions/qianfan:
devDependencies:
@@ -2094,6 +2109,10 @@ importers:
p-map:
specifier: 7.0.5
version: 7.0.5
devDependencies:
openclaw:
specifier: workspace:*
version: link:../..
packages/model-catalog-core:
dependencies:
@@ -2242,12 +2261,18 @@ importers:
'@types/markdown-it':
specifier: 14.1.2
version: 14.1.2
'@vitest/browser':
specifier: 4.1.9
version: 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
'@vitest/browser-playwright':
specifier: 4.1.9
version: 4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
jsdom:
specifier: 29.1.1
version: 29.1.1(@noble/hashes@2.2.0)
openclaw:
specifier: workspace:*
version: link:..
playwright:
specifier: 1.61.1
version: 1.61.1

View File

@@ -3,15 +3,29 @@
import { fileURLToPath } from "node:url";
import { isLikelyRepoFilePath, runKnip, uniqueSorted } from "./deadcode-knip-runner.mjs";
const KNIP_ARGS = [
"--config",
"config/knip.config.ts",
"--production",
const KNIP_ISSUES = "exports,nsExports,types,nsTypes,enumMembers,namespaceMembers";
const KNIP_SCANS = [
{
name: "production unused-export scan",
args: ["--config", "config/knip.config.ts", "--production"],
},
{
name: "full-tree unused-export scan",
args: ["--config", "config/knip.all-exports.config.ts"],
},
{
name: "script unused-export scan",
args: ["--config", "config/knip.scripts-exports.config.ts", "--include-entry-exports"],
},
];
const KNIP_COMMON_ARGS = [
"--no-progress",
"--reporter",
"compact",
"--include",
"exports,nsExports,types,nsTypes,enumMembers,namespaceMembers",
KNIP_ISSUES,
"--no-config-hints",
];
@@ -82,18 +96,30 @@ export function checkUnusedExports(output) {
}
async function main() {
const result = await runKnip(KNIP_ARGS, { scanName: "unused-export scan" });
for (const scan of KNIP_SCANS) {
const ok = await runUnusedExportScan(scan);
if (!ok) {
process.exitCode = 1;
return;
}
}
console.log(
"[deadcode] Knip production and full-tree unused-export checks passed with 0 entries.",
);
}
async function runUnusedExportScan(scan) {
const result = await runKnip([...scan.args, ...KNIP_COMMON_ARGS], { scanName: scan.name });
if (result.errorCode || result.status === null) {
console.error(
`deadcode unused-export scan failed: ${result.errorCode ?? result.signal ?? "unknown"}${
`deadcode ${scan.name} failed: ${result.errorCode ?? result.signal ?? "unknown"}${
result.errorMessage ? `: ${result.errorMessage}` : ""
}`,
);
if (result.output) {
console.error(result.output);
}
process.exitCode = 1;
return;
return false;
}
const parsed = parseKnipCompactUnusedExportsResult(result.output);
@@ -101,21 +127,27 @@ async function main() {
// legitimately prints no export sections; sectionless output is only a
// failure signal when Knip also exited nonzero (crash/config error).
if (!parsed.sawExportSection && result.status !== 0) {
console.error("deadcode unused-export scan produced no export sections.");
console.error(`deadcode ${scan.name} produced no export sections.`);
if (result.output) {
console.error(result.output);
}
process.exitCode = 1;
return;
return false;
}
const check = checkUnusedExports(result.output);
if (!check.ok) {
console.error(check.message);
process.exitCode = 1;
return;
console.error(`${scan.name}:\n${check.message}`);
return false;
}
console.log("[deadcode] Knip unused-export check passed with 0 entries.");
if (result.status !== 0) {
console.error(`deadcode ${scan.name} exited with status ${result.status}.`);
if (result.output) {
console.error(result.output);
}
return false;
}
console.log(`[deadcode] Knip ${scan.name} passed with 0 entries.`);
return true;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {

View File

@@ -1,47 +1,34 @@
#!/usr/bin/env node
/**
* Parses compact knip output into unused file paths.
*/
export function parseKnipCompactUnusedFiles(output: unknown): unknown[];
/**
* Compares detected unused files against the checked-in allowlist.
*/
export function compareUnusedFilesToAllowlist(
actualFiles: unknown,
allowlistFiles: unknown,
optionalAllowlistFiles?: unknown[],
): {
actual: unknown[];
allowed: unknown[];
unexpected: unknown[];
stale: unknown[];
duplicateAllowedCount: number;
allowlistIsSorted: boolean;
export type KnipUnusedFileScanResult = {
errorCode?: string;
errorMessage?: string;
output: string;
signal?: string | null;
status: number | null;
};
/**
* Runs knip and returns parsed unused-file results.
*/
export function runKnipUnusedFiles(params?: Record<string, unknown>): Promise<unknown>;
/**
* Checks detected unused files against the current allowlist.
*/
export function checkUnusedFiles(
output: unknown,
allowlistFiles?: string[],
optionalAllowlistFiles?: string[],
): {
ok: boolean;
comparison: {
actual: unknown[];
allowed: unknown[];
unexpected: unknown[];
stale: unknown[];
duplicateAllowedCount: number;
allowlistIsSorted: boolean;
};
/** Parses compact Knip output into unused file paths. */
export function parseKnipCompactUnusedFiles(output: string): string[];
/** Runs the production Knip unused-file scan. */
export function runKnipUnusedFiles(
params?: Record<string, unknown>,
): Promise<KnipUnusedFileScanResult>;
/** Rejects every unused file reported by Knip. */
export function checkUnusedFiles(output: string): {
files: string[];
message: string;
ok: boolean;
};
/**
* Maximum buffered knip output retained for diagnostics.
*/
/** Validates both Knip process completion and the unused-file report. */
export function checkKnipUnusedFileScanResult(result: KnipUnusedFileScanResult): {
failureReason: string;
message: string;
ok: boolean;
};
/** Maximum buffered Knip output retained for diagnostics. */
export const KNIP_MAX_BUFFER_BYTES: number;

View File

@@ -1,29 +1,26 @@
#!/usr/bin/env node
// Runs Knip unused-file detection and compares results to the allowlist.
// Enforces a hard-zero policy for Knip's unused files.
import { fileURLToPath } from "node:url";
import {
compareStringListToAllowlist,
isLikelyRepoFilePath,
KNIP_MAX_BUFFER_BYTES,
runKnip,
uniqueSorted,
} from "./deadcode-knip-runner.mjs";
import {
KNIP_OPTIONAL_UNUSED_FILE_ALLOWLIST,
KNIP_UNUSED_FILE_ALLOWLIST,
} from "./deadcode-unused-files.allowlist.mjs";
export { KNIP_MAX_BUFFER_BYTES };
const KNIP_ARGS = [
"--config",
"config/knip.config.ts",
"--production",
"--no-progress",
"--reporter",
"compact",
"--files",
"--no-config-hints",
const KNIP_COMMON_ARGS = ["--no-progress", "--reporter", "compact", "--files", "--no-config-hints"];
const KNIP_SCANS = [
{
name: "production unused-file scan",
args: ["--config", "config/knip.config.ts", "--production"],
},
{
name: "full-tree unused-file scan",
args: ["--config", "config/knip.all-exports.config.ts"],
},
];
/** Parses compact Knip output into unused file paths. */
@@ -55,88 +52,77 @@ export function parseKnipCompactUnusedFiles(output) {
return uniqueSorted(files);
}
/** Compares detected unused files against the checked-in allowlist. */
export function compareUnusedFilesToAllowlist(
actualFiles,
allowlistFiles,
optionalAllowlistFiles = [],
) {
return compareStringListToAllowlist(actualFiles, allowlistFiles, optionalAllowlistFiles);
}
function formatUnusedFileComparison(comparison) {
const lines = [];
if (!comparison.allowlistIsSorted) {
lines.push("deadcode unused-file allowlist is not sorted.");
}
if (comparison.duplicateAllowedCount > 0) {
lines.push(
`deadcode unused-file allowlist contains ${comparison.duplicateAllowedCount} duplicate entr${
comparison.duplicateAllowedCount === 1 ? "y" : "ies"
}.`,
);
}
if (comparison.unexpected.length > 0) {
lines.push("Unexpected unused files:");
lines.push(...comparison.unexpected.map((file) => ` ${file}`));
}
if (comparison.stale.length > 0) {
lines.push("Stale allowlist entries:");
lines.push(...comparison.stale.map((file) => ` ${file}`));
}
return lines.join("\n");
}
/** Runs Knip and returns parsed unused-file results. */
export async function runKnipUnusedFiles(params = {}) {
return await runKnip(KNIP_ARGS, { ...params, scanName: "unused-file scan" });
return await runKnip([...KNIP_SCANS[0].args, ...KNIP_COMMON_ARGS], {
...params,
scanName: KNIP_SCANS[0].name,
});
}
/** Checks detected unused files against the current allowlist. */
export function checkUnusedFiles(
output,
allowlistFiles = KNIP_UNUSED_FILE_ALLOWLIST,
optionalAllowlistFiles = KNIP_OPTIONAL_UNUSED_FILE_ALLOWLIST,
) {
const actual = parseKnipCompactUnusedFiles(output);
const comparison = compareUnusedFilesToAllowlist(actual, allowlistFiles, optionalAllowlistFiles);
/** Rejects every unused file reported by Knip. */
export function checkUnusedFiles(output) {
const files = parseKnipCompactUnusedFiles(output);
return {
ok:
comparison.allowlistIsSorted &&
comparison.duplicateAllowedCount === 0 &&
comparison.unexpected.length === 0 &&
comparison.stale.length === 0,
comparison,
message: formatUnusedFileComparison(comparison),
ok: files.length === 0,
files,
message:
files.length === 0
? ""
: [
"Unused files are not allowed:",
...files.map((file) => ` ${file}`),
"Delete the files or model their real entrypoints in Knip.",
].join("\n"),
};
}
/** Validates both Knip process completion and the unused-file report. */
export function checkKnipUnusedFileScanResult(result) {
if (result.errorCode || result.status === null || result.status !== 0) {
return {
ok: false,
failureReason: result.errorCode ?? result.signal ?? `exit status ${String(result.status)}`,
message: "",
};
}
const check = checkUnusedFiles(result.output);
return { ok: check.ok, failureReason: "", message: check.message };
}
async function main() {
const result = await runKnipUnusedFiles();
if (result.errorCode || result.status === null) {
for (const scan of KNIP_SCANS) {
const ok = await runUnusedFileScan(scan);
if (!ok) {
process.exitCode = 1;
return;
}
}
console.log("[deadcode] Knip production and full-tree unused-file checks passed with 0 entries.");
}
async function runUnusedFileScan(scan) {
const result = await runKnip([...scan.args, ...KNIP_COMMON_ARGS], { scanName: scan.name });
const validation = checkKnipUnusedFileScanResult(result);
if (validation.failureReason) {
console.error(
`deadcode unused-file scan failed: ${result.errorCode ?? result.signal ?? "unknown"}${
`deadcode ${scan.name} failed: ${validation.failureReason}${
result.errorMessage ? `: ${result.errorMessage}` : ""
}`,
);
if (result.output) {
console.error(result.output);
}
process.exitCode = 1;
return;
return false;
}
const check = checkUnusedFiles(result.output);
if (!check.ok) {
if (check.message) {
console.error(check.message);
if (!validation.ok) {
if (validation.message) {
console.error(`${scan.name}:\n${validation.message}`);
}
process.exitCode = 1;
return;
return false;
}
console.log(
`[deadcode] Knip unused-file allowlist matched ${check.comparison.actual.length} intentional entries.`,
);
console.log(`[deadcode] Knip ${scan.name} passed with 0 entries.`);
return true;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {

View File

@@ -21,8 +21,8 @@ const targets = [
"security",
"test",
"skills",
"config",
"openclaw.mjs",
"config/knip.config.ts",
"tsdown.ai.config.ts",
"tsdown.config.ts",
"vitest.config.ts",

View File

@@ -11,7 +11,7 @@ const KNIP_HEARTBEAT_MS = 60_000;
/** Maximum buffered Knip output retained for diagnostics. */
export const KNIP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
export function normalizeRepoPath(value) {
function normalizeRepoPath(value) {
return value.replaceAll("\\", "/").replace(/^\.\//u, "");
}
@@ -22,26 +22,9 @@ export function uniqueSorted(values) {
}
export function isLikelyRepoFilePath(value) {
return /^(apps|docs|extensions|packages|scripts|src|test|ui)\//u.test(normalizeRepoPath(value));
}
/** Compares a detected string list with required and optional baseline entries. */
export function compareStringListToAllowlist(actualValues, allowlistValues, optionalValues = []) {
const actual = uniqueSorted(actualValues);
const allowed = uniqueSorted(allowlistValues);
const optionalAllowed = uniqueSorted(optionalValues);
const allowedOrOptionalSet = new Set([...allowed, ...optionalAllowed]);
const actualSet = new Set(actual);
return {
actual,
allowed,
unexpected: actual.filter((value) => !allowedOrOptionalSet.has(value)),
stale: allowed.filter((value) => !actualSet.has(value)),
duplicateAllowedCount: allowlistValues.length - new Set(allowlistValues).size,
allowlistIsSorted:
JSON.stringify(allowlistValues.map(normalizeRepoPath)) === JSON.stringify(allowed),
};
return /^(\.agents|apps|config|docs|extensions|packages|scripts|src|test|ui)\//u.test(
normalizeRepoPath(value),
);
}
function spawnErrorCode(error) {

View File

@@ -1,40 +0,0 @@
// Intentional Knip unused-file findings. These are dynamic entrypoints,
// generated/build inputs, manifest-discovered plugin surfaces, live-test
// helpers, or package bridge files that static production scanning cannot see.
export const KNIP_UNUSED_FILE_ALLOWLIST = ["extensions/qa-lab/src/ci-smoke-plan.ts"];
// Knip can disagree across supported local/CI platforms for files that are
// only reachable through test-only import graphs, sparse-checkout proof
// workspaces, dynamic entrypoints, manifest-discovered plugin surfaces, or
// package bridge files. Ignore these when reported, but do not require them
// to be reported.
export const KNIP_OPTIONAL_UNUSED_FILE_ALLOWLIST = [
"extensions/acpx/src/runtime-internals/mcp-command-line.mjs",
"extensions/acpx/src/runtime-internals/mcp-proxy.mjs",
"extensions/canvas/src/host/a2ui-app/bootstrap.js",
"extensions/canvas/src/host/a2ui-app/rolldown.config.mjs",
"extensions/diffs/src/viewer-client.ts",
"extensions/diffs/src/viewer-payload.ts",
"extensions/matrix/src/plugin-entry.runtime.js",
"extensions/mxc/src/mxc-spawn-launcher.mjs",
"src/agents/subagent-registry.runtime.ts",
"src/auto-reply/reply/get-reply.test-loader.ts",
"src/commands/doctor/shared/deprecation-compat.ts",
"src/config/doc-baseline.runtime.ts",
"src/config/doc-baseline.ts",
"src/gateway/gateway-cli-backend.live-helpers.ts",
"src/gateway/gateway-cli-backend.live-probe-helpers.ts",
"src/gateway/gateway-codex-harness.live-helpers.ts",
"src/mcp/plugin-tools-handlers.ts",
"src/mcp/plugin-tools-serve.ts",
"src/mcp/tools-stdio-server.ts",
"src/plugins/build-smoke-entry.ts",
"src/plugins/contracts/host-hook-fixture.ts",
"src/plugins/contracts/rootdir-boundary-canary.ts",
"src/plugins/contracts/tts-contract-suites.ts",
"src/plugins/runtime-sidecar-paths-baseline.ts",
"src/tasks/task-registry-control.runtime.ts",
"extensions/qa-lab/src/auth-profile.fixture.ts",
"extensions/qa-lab/src/codex-plugin.fixture.ts",
"extensions/qa-lab/src/mantis-phase-timer.runtime.ts",
];

View File

@@ -234,8 +234,3 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
process.exitCode = await runGatewaySmoke({ token: cli.token, urlRaw: cli.urlRaw });
}
}
export const testing = {
parseGatewaySmokeCli,
usage,
};

View File

@@ -1524,7 +1524,7 @@ function tailText(text) {
return text.split(/\r?\n/u).slice(-120).join("\n");
}
export async function main(argv = process.argv.slice(2)) {
async function main(argv = process.argv.slice(2)) {
const [command, pluginId, pluginDir, requiresConfigRaw, pluginIndexRaw, pluginRoot, provider] =
argv;
const pluginIndex = readNonNegativeInt(pluginIndexRaw, 0, "bundled plugin runtime index");

View File

@@ -293,7 +293,7 @@ async function runGatewaySuspensionPostRestartClient({ statePath, token, url })
emitPhase("post-restart", startedAt);
}
export function responseError(method, response) {
function responseError(method, response) {
const message = response.error?.message ?? "unknown";
return new Error(`${method} failed: ${message}`);
}

View File

@@ -9,7 +9,7 @@ function assert(condition: unknown, message: string): asserts condition {
}
}
export function outputText(response: unknown): string {
function outputText(response: unknown): string {
const output = (response as { output?: Array<{ type?: unknown; content?: unknown }> }).output;
if (!Array.isArray(output)) {
return "";

View File

@@ -9,8 +9,6 @@ export type PluginInstallRecord = Record<string, unknown> & {
spec?: string;
};
export function stateDir(): string;
export function configPath(): string;
export function readPluginInstallIndex(options?: Record<string, unknown>): unknown;
export function readPluginInstallRecords(options?: {
configPath?: string;

View File

@@ -12,11 +12,11 @@ const JSON_ARTIFACT_MAX_BYTES = readPositiveIntEnv(
1024 * 1024,
);
export function stateDir() {
function stateDir() {
return process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME, ".openclaw");
}
export function configPath() {
function configPath() {
return process.env.OPENCLAW_CONFIG_PATH || path.join(stateDir(), "openclaw.json");
}

View File

@@ -2,14 +2,21 @@
export * from "./filesystem.ts";
export * from "./env-limits.ts";
export * from "./host-command.ts";
export * from "./host-server.ts";
export {
resolveHostIp,
resolveHostPort,
startHostServer,
startNpmRegistryServer,
} from "./host-server.ts";
export * from "./lane-runner.ts";
export * from "./macos-users.ts";
export * from "./package-artifact.ts";
// host-server.ts and package-artifact.ts both export a module-private `testing` hook, which
// star re-exports silently drop as ambiguous. Re-export one explicitly so the barrel
// typechecks (TS2308); import either module's `testing` directly, never through this barrel.
export { testing } from "./host-server.ts";
export {
extractPackageJsonFromTgz,
packOpenClaw,
packageBuildCommitFromTgz,
packageVersionFromTgz,
resolveOpenClawRegistryVersion,
} from "./package-artifact.ts";
export * from "./parallels-vm.ts";
export * from "./plugin-isolation.ts";
export * from "./provider-auth.ts";

View File

@@ -1,5 +1,5 @@
// Filesystem script supports OpenClaw repository automation.
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync } from "node:fs";
import { access, mkdir, open, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { repoRoot } from "./host-command.ts";
@@ -20,7 +20,7 @@ export async function readJson<T>(filePath: string): Promise<T> {
return JSON.parse(await readFile(filePath, "utf8")) as T;
}
export async function readTextFileTail(
async function readTextFileTail(
filePath: string,
maxBytes = DEFAULT_TEXT_FILE_TAIL_BYTES,
): Promise<string> {
@@ -86,7 +86,3 @@ export async function writeSummaryMarkdown(input: {
);
return markdownPath;
}
export function writeExecutable(filePath: string, content: string): void {
writeFileSync(filePath, content, { encoding: "utf8", mode: 0o755 });
}

View File

@@ -2164,13 +2164,10 @@ async function main() {
export {
assertAllowedFailureCommandSucceeded,
clampSecretProofTimerTimeoutMs,
collectBlockingProofResults,
cleanupEnv,
expectGatewayStartupFails,
gatewayCall,
parseJsonOutput,
readPositiveTimerMs,
runPtySecretsConfigurePreset,
runWithProof,
runCommand,

View File

@@ -156,7 +156,7 @@ const COMMAND_STDOUT_MAX_CHARS = 1024 * 1024;
const COMMAND_STDERR_TAIL_CHARS = 256 * 1024;
const COMMAND_FAILURE_STDOUT_TAIL_CHARS = 64 * 1024;
export const COMMAND_TIMEOUT_MS = 30 * 60 * 1000;
export const COMMAND_TIMEOUT_KILL_GRACE_MS = 5_000;
const COMMAND_TIMEOUT_KILL_GRACE_MS = 5_000;
const COMMAND_PROCESS_TREE_EXIT_POLL_MS = 25;
export const REMOTE_SETUP_COMMAND_TIMEOUT_MS = 90 * 60 * 1000;
const REMOTE_ROOT = "/tmp/openclaw-telegram-user-crabbox";

View File

@@ -1,15 +0,0 @@
// Generate Secretref Credential Matrix script supports OpenClaw repository automation.
import fs from "node:fs";
import path from "node:path";
import { buildSecretRefCredentialMatrix } from "../src/secrets/credential-matrix.js";
const outputPath = path.join(
process.cwd(),
"docs",
"reference",
"secretref-user-supplied-credentials-matrix.json",
);
const matrix = buildSecretRefCredentialMatrix();
fs.writeFileSync(outputPath, `${JSON.stringify(matrix, null, 2)}\n`, "utf8");
console.log(`Wrote ${outputPath}`);

View File

@@ -7,7 +7,7 @@ export const PROOF_OVERRIDE_LABEL = "proof: override";
export const PROOF_SUFFICIENT_LABEL = "proof: sufficient";
export const NEEDS_PR_CONTEXT_LABEL = "triage: needs-pr-context";
const MAINTAINER_TEAM_SLUG = "maintainer";
export const DEFAULT_GITHUB_API_TIMEOUT_MS = 30_000;
const DEFAULT_GITHUB_API_TIMEOUT_MS = 30_000;
const GITHUB_API_RESPONSE_BODY_MAX_BYTES = 1024 * 1024;
const CLAWSWEEPER_PROOF_VERDICT_STATUS = "clawsweeper_exact_head_pass";

View File

@@ -3,9 +3,9 @@ import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
import { basename } from "node:path";
import { inflateRawSync } from "node:zlib";
export const ACTIONS_ARTIFACT_API_VERSION = "2026-03-10";
export const DEFAULT_MAX_ACTIONS_ARTIFACT_BYTES = 256 * 1024 * 1024;
export const DEFAULT_MAX_ACTIONS_ARTIFACT_EXPANDED_BYTES = 512 * 1024 * 1024;
const ACTIONS_ARTIFACT_API_VERSION = "2026-03-10";
const DEFAULT_MAX_ACTIONS_ARTIFACT_BYTES = 256 * 1024 * 1024;
const DEFAULT_MAX_ACTIONS_ARTIFACT_EXPANDED_BYTES = 512 * 1024 * 1024;
const DEFAULT_MAX_JSON_BYTES = 2 * 1024 * 1024;
const DEFAULT_TIMEOUT_MS = 60_000;

View File

@@ -20,7 +20,7 @@ export const selectedCodexAppServerJsonSchemas = [
"v2/TurnStartResponse.json",
] as const;
export type GeneratedCodexAppServerProtocolSource = {
type GeneratedCodexAppServerProtocolSource = {
root: string;
codexRepo: string;
typescriptRoot: string;

View File

@@ -2,10 +2,10 @@ import type { ChildProcess } from "node:child_process";
import { basename, dirname, resolve, win32 as pathWin32 } from "node:path";
import { trimForSummary } from "./shared.ts";
export type CrossOsSuite = "packaged-fresh" | "installer-fresh" | "packaged-upgrade" | "dev-update";
export type CrossOsMode = "fresh" | "upgrade" | "both";
export type CrossOsOsId = "ubuntu" | "windows" | "macos";
export type ProviderId = "openai" | "anthropic" | "minimax";
type CrossOsSuite = "packaged-fresh" | "installer-fresh" | "packaged-upgrade" | "dev-update";
type CrossOsMode = "fresh" | "upgrade" | "both";
type CrossOsOsId = "ubuntu" | "windows" | "macos";
type ProviderId = "openai" | "anthropic" | "minimax";
export type ProviderConfig = {
extensionId: string;
secretEnv: string;
@@ -670,7 +670,7 @@ export function updateTimeoutMs() {
: 20 * 60 * 1000;
}
export function updateStepTimeoutSeconds() {
function updateStepTimeoutSeconds() {
return process.platform === "win32"
? CROSS_OS_WINDOWS_PACKAGED_UPGRADE_STEP_TIMEOUT_SECONDS
: 1200;

View File

@@ -342,11 +342,11 @@ export function readProvidedCandidate(params: {
};
}
export function readPackageJson(packageRoot: string): PackageJson {
function readPackageJson(packageRoot: string): PackageJson {
return JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as PackageJson;
}
export function packageJsonHasScript(packageJson: PackageJson, scriptName: string) {
function packageJsonHasScript(packageJson: PackageJson, scriptName: string) {
return typeof packageJson?.scripts?.[scriptName] === "string";
}
@@ -740,7 +740,7 @@ export function readInstalledMetadata(prefixDir: string) {
return readInstalledMetadataFromManifest(packageJson, packageRoot);
}
export function readInstalledMetadataFromPackageRoot(packageRoot: string) {
function readInstalledMetadataFromPackageRoot(packageRoot: string) {
const { packageJson } = readInstalledPackageManifestFromPackageRoot(packageRoot);
return readInstalledMetadataFromManifest(packageJson, packageRoot);
}
@@ -822,7 +822,7 @@ export function resolveInstalledPackageRootFromCliPath(
throw new Error(`Installed package manifest missing. Checked: ${checked.join(", ")}`);
}
export function installedPackageRoot(prefixDir: string, platform = process.platform) {
function installedPackageRoot(prefixDir: string, platform = process.platform) {
return platform === "win32"
? join(prefixDir, "node_modules", "openclaw")
: join(prefixDir, "lib", "node_modules", "openclaw");
@@ -832,7 +832,7 @@ export function installedEntryPath(prefixDir: string) {
return join(installedPackageRoot(prefixDir), "openclaw.mjs");
}
export function npmShimPath(prefixDir: string) {
function npmShimPath(prefixDir: string) {
return process.platform === "win32" ? join(prefixDir, "npm.cmd") : join(prefixDir, "bin", "npm");
}
@@ -840,7 +840,7 @@ export function binDirForPrefix(prefixDir: string) {
return process.platform === "win32" ? prefixDir : join(prefixDir, "bin");
}
export function pnpmCommand() {
function pnpmCommand() {
return process.platform === "win32" ? "pnpm.cmd" : "pnpm";
}
@@ -848,7 +848,7 @@ export function npmCommand() {
return process.platform === "win32" ? "npm.cmd" : "npm";
}
export function gitCommand() {
function gitCommand() {
return process.platform === "win32" ? "git.exe" : "git";
}

View File

@@ -59,7 +59,6 @@ export type DockerE2ePlan = {
export const DEFAULT_LIVE_RETRIES: number;
export const DEFAULT_E2E_BARE_IMAGE: string;
export const DEFAULT_E2E_FUNCTIONAL_IMAGE: string;
export const DEFAULT_E2E_IMAGE: string;
export const DEFAULT_PARALLELISM: number;
export const DEFAULT_PROFILE: string;
export const DEFAULT_RESOURCE_LIMITS: Record<string, number>;

View File

@@ -20,7 +20,6 @@ export { normalizeReleaseProfile };
export const DEFAULT_E2E_BARE_IMAGE = "openclaw-docker-e2e-bare:local";
export const DEFAULT_E2E_FUNCTIONAL_IMAGE = "openclaw-docker-e2e-functional:local";
export const DEFAULT_E2E_IMAGE = DEFAULT_E2E_FUNCTIONAL_IMAGE;
export const DEFAULT_PARALLELISM = 10;
export const DEFAULT_PROFILE = "all";
export const DEFAULT_RESOURCE_LIMITS = {

View File

@@ -32,7 +32,7 @@ function collectPluginIdentities(extensionsRoot) {
}
/** Resolve public Docker selections to the source directories used by build and prune steps. */
export function resolveDockerPluginSelection(params) {
function resolveDockerPluginSelection(params) {
const selection = typeof params.selection === "string" ? params.selection : "";
const selectedIds = new Set(
selection

View File

@@ -9,7 +9,7 @@ const TEARDOWN_GRACE_MS = 2_000;
const TEARDOWN_KILL_GRACE_MS = 1_000;
const EXIT_POLL_MS = 10;
export type ChildExit = {
type ChildExit = {
exitCode: number | null;
signal: string | null;
};

View File

@@ -20,8 +20,8 @@ type GatewayResFrame = {
payload?: unknown;
error?: unknown;
};
export type GatewayEventFrame = { type: "event"; event: string; seq?: number; payload?: unknown };
export type GatewayFrame =
type GatewayEventFrame = { type: "event"; event: string; seq?: number; payload?: unknown };
type GatewayFrame =
| GatewayReqFrame
| GatewayResFrame
| GatewayEventFrame

View File

@@ -323,7 +323,7 @@ function buildCreatePlan(options = {}) {
}
/** Create an isolated OpenClaw test state directory and optional scenario config. */
export async function createState(options = {}) {
async function createState(options = {}) {
const label = normalizeLabel(options.label);
const root = await fs.mkdtemp(path.join(os.tmpdir(), `openclaw-${label}-`));
const plan = buildCreatePlan({ ...options, root });

View File

@@ -2,10 +2,6 @@ import type {
ExecFileSyncOptions,
ExecFileSyncOptionsWithBufferEncoding,
ExecFileSyncOptionsWithStringEncoding,
SpawnSyncOptions,
SpawnSyncOptionsWithBufferEncoding,
SpawnSyncOptionsWithStringEncoding,
SpawnSyncReturns,
} from "node:child_process";
export function plainGhEnv(env?: NodeJS.ProcessEnv): {
@@ -36,16 +32,4 @@ export function execGhApiRead(
endpoint: string,
options?: ExecFileSyncOptions,
): string | Uint8Array<ArrayBuffer>;
export function spawnPlainGh(
args: readonly string[],
options: SpawnSyncOptionsWithStringEncoding,
): SpawnSyncReturns<string>;
export function spawnPlainGh(
args: readonly string[],
options?: SpawnSyncOptionsWithBufferEncoding,
): SpawnSyncReturns<Uint8Array<ArrayBuffer>>;
export function spawnPlainGh(
args: readonly string[],
options?: SpawnSyncOptions,
): SpawnSyncReturns<string | Uint8Array<ArrayBuffer>>;
export const PLAIN_GH_SYSTEM_CANDIDATES: string[];

View File

@@ -126,7 +126,6 @@ export function readQaSuiteSummary(summaryPath: unknown):
diagnosticDetail: null;
summary: unknown;
};
export function schemaHasRequiredFields(schema: unknown, seen?: Set<unknown>): boolean;
export function selectPluginEntries<T extends PluginGatewayEntry>(
entries: T[],
options?: { ids?: string[]; shardIndex?: number; shardTotal?: number },

View File

@@ -633,6 +633,5 @@ export {
detectCommandDiagnosticFailure,
discoverBundledPluginManifests,
readQaSuiteSummary,
schemaHasRequiredFields,
selectPluginEntries,
};

View File

@@ -12,7 +12,7 @@ import {
resolveNpmPublishPlan,
} from "./npm-publish-plan.mjs";
export type PluginPackageJson = {
type PluginPackageJson = {
name?: string;
version?: string;
type?: string;
@@ -64,11 +64,11 @@ export type PublishablePluginPackage = {
requiredLatestDependencies?: RequiredLatestDependency[];
};
export type PluginReleasePlanItem = PublishablePluginPackage & {
type PluginReleasePlanItem = PublishablePluginPackage & {
alreadyPublished: boolean;
};
export type PluginReleasePlan = {
type PluginReleasePlan = {
all: PluginReleasePlanItem[];
candidates: PluginReleasePlanItem[];
skippedPublished: PluginReleasePlanItem[];

View File

@@ -1,6 +1,6 @@
export type ProfileId = "smoke" | "default" | "large";
export type ProfileConfig = {
type ProfileConfig = {
iterations: number;
maxWalBytes: number;
payloadBytes: number;
@@ -18,7 +18,7 @@ export type CliOptions = {
stateDir: string | null;
};
export type ReliabilityStateProof = {
type ReliabilityStateProof = {
batches: number;
rows: number;
sha256: string;

View File

@@ -12,7 +12,7 @@ type WriterReadyMessage = {
kind: "ready";
};
export type WriterPartialMessage = {
type WriterPartialMessage = {
batch: number;
kind: "partial";
rows: number;
@@ -23,7 +23,7 @@ type WriterReleasedMessage = {
kind: "released";
};
export type WriterResultMessage = {
type WriterResultMessage = {
batchesCommitted: number;
kind: "result";
rowsCommitted: number;

View File

@@ -5,13 +5,13 @@ import path from "node:path";
import { expectDefined } from "../../packages/normalization-core/src/expect.js";
/** Rendered baseline artifact for the sessions/transcripts SQLite schema. */
export type SqliteSessionSchemaBaselineRender = {
type SqliteSessionSchemaBaselineRender = {
/** Normalized SQL for the session, conversation, and transcript schema objects. */
sql: string;
};
/** Result returned after writing or checking SQLite schema baseline artifacts. */
export type SqliteSessionSchemaBaselineWriteResult = {
type SqliteSessionSchemaBaselineWriteResult = {
/** True when generated artifact content differs from disk. */
changed: boolean;
/** True when changed artifacts were actually written. */

View File

@@ -163,7 +163,7 @@ function laneArtifactEntries() {
/**
* Builds the manifest for paired baseline/candidate Telegram Desktop proof artifacts.
*/
export function buildTelegramDesktopProofManifest({
function buildTelegramDesktopProofManifest({
baseline,
baselineRef,
baselineSha,

View File

@@ -181,7 +181,7 @@ async function writeConfig(params: {
await fs.writeFile(params.configPath, `${JSON.stringify(cfg, null, 2)}\n`, "utf8");
}
export async function main() {
async function main() {
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mcp-code-mode-"));
const keep = process.env.OPENCLAW_MCP_CODE_MODE_GATEWAY_E2E_KEEP === "1";
const previousEnv = {

View File

@@ -17,7 +17,7 @@ const DEFAULT_METHODS = ["health", "config.get"];
const DEFAULT_ITERATIONS = 10;
const SINGLE_VALUE_FLAGS = new Set(["--iterations", "--methods", "--output-dir", "--repo-root"]);
/** Maximum time to wait for a spawned gateway to become reachable. */
export const READY_TIMEOUT_MS = 120_000;
const READY_TIMEOUT_MS = 120_000;
/** Per-probe timeout used while polling gateway readiness endpoints. */
const READY_PROBE_TIMEOUT_MS = 1_000;
const READY_PROBE_RESPONSE_BODY_MAX_BYTES = 64 * 1024;

View File

@@ -47,7 +47,7 @@ function isMainModule() {
return resolve(invokedPath) === SCRIPT_PATH;
}
export async function main(argv: string[]) {
async function main(argv: string[]) {
const args = parseArgs(argv);
if (args["resolve-matrix"] === "true") {

View File

@@ -155,7 +155,7 @@ function parseArgs(argv) {
return options;
}
export function main(argv = process.argv.slice(2)) {
function main(argv = process.argv.slice(2)) {
const result = resolveOpenClawNpmResumeRun(parseArgs(argv));
process.stdout.write(`${JSON.stringify(result)}\n`);
}

View File

@@ -8,7 +8,7 @@ import { parsePositiveInt } from "../lib/numeric-options.mjs";
const DEFAULT_LIMIT = 30;
export function usage() {
function usage() {
return "Usage: scripts/perf/summarize-cpuprofile.mjs [--limit N] <profile...>";
}
@@ -120,7 +120,7 @@ function validateProfile(profile, file) {
}
}
export function summarizeProfile(file, limit) {
function summarizeProfile(file, limit) {
const profile = JSON.parse(fs.readFileSync(file, "utf8"));
validateProfile(profile, file);
const nodes = new Map(profile.nodes.map((node) => [node.id, node]));

View File

@@ -40,7 +40,7 @@ type MatrixCell = {
type ChromiumLauncher = Awaited<typeof import("playwright")>["chromium"];
type ChromiumBrowser = Awaited<ReturnType<ChromiumLauncher["launch"]>>;
export type ProducerOptions = {
type ProducerOptions = {
artifactBase: string;
repoRoot: string;
skipVisualProof: boolean;

View File

@@ -40,14 +40,14 @@ function validateTag(tag) {
}
}
export function githubReleaseBodySize(body) {
function githubReleaseBodySize(body) {
return {
characters: [...body].length,
bytes: Buffer.byteLength(body, "utf8"),
};
}
export function fitsGithubReleaseBody(body) {
function fitsGithubReleaseBody(body) {
const size = githubReleaseBodySize(body);
return (
size.characters <= GITHUB_RELEASE_BODY_MAX_CHARACTERS &&
@@ -185,7 +185,7 @@ export function parseShippedBaselineExclusions(section) {
return baselines;
}
export function tagPinnedContributionRecordUrl(repository, tag) {
function tagPinnedContributionRecordUrl(repository, tag) {
validateRepository(repository);
validateTag(tag);
return `https://github.com/${repository}/blob/${tag}/CHANGELOG.md#complete-contribution-record`;

View File

@@ -435,7 +435,7 @@ export function collectTempCreationFindingsFromDiff(diffText, options = {}) {
return findings;
}
export async function main(argv, io) {
async function main(argv, io) {
const args = parseArgs(argv ?? process.argv.slice(2));
const stdout = io?.stdout ?? process.stdout;
const stderr = io?.stderr ?? process.stderr;

View File

@@ -1,41 +0,0 @@
// Verifies sqlite-vec can load and execute a simple vector query in Node's
// built-in SQLite runtime.
import { DatabaseSync } from "node:sqlite";
import { load, getLoadablePath } from "sqlite-vec";
import { formatErrorMessage } from "./lib/error-format.mjs";
function vec(values) {
return Buffer.from(new Float32Array(values).buffer);
}
const db = new DatabaseSync(":memory:", { allowExtension: true });
try {
load(db);
} catch (err) {
const message = formatErrorMessage(err);
console.error("sqlite-vec load failed:");
console.error(message);
console.error("expected extension path:", getLoadablePath());
process.exit(1);
}
db.exec(`
CREATE VIRTUAL TABLE v USING vec0(
id TEXT PRIMARY KEY,
embedding FLOAT[4]
);
`);
const insert = db.prepare("INSERT INTO v (id, embedding) VALUES (?, ?)");
insert.run("a", vec([1, 0, 0, 0]));
insert.run("b", vec([0, 1, 0, 0]));
insert.run("c", vec([0.2, 0.2, 0, 0]));
const query = vec([1, 0, 0, 0]);
const rows = db
.prepare("SELECT id, vec_distance_cosine(embedding, ?) AS dist FROM v ORDER BY dist ASC")
.all(query);
console.log("sqlite-vec ok");
console.log(rows);

View File

@@ -1,302 +0,0 @@
#!/usr/bin/env node
// Synchronizes GitHub label colors to the OpenClaw taxonomy policy.
import { execFileSync } from "node:child_process";
const REPO = "openclaw/openclaw";
const APPLY = process.argv.includes("--apply");
const COLORS = {
saturatedRed: "B60205",
saturatedBugRed: "D73A4A",
saturatedOrangeRed: "D93F0B",
saturatedAmber: "FBCA04",
softerAmber: "F9D65C",
paleYellow: "F7E7A1",
saturatedGreen: "0E8A16",
mutedGreen: "8C959F",
paleGreen: "D6E3DA",
proofGreen: "2DA44E",
mutedProofGreen: "1A7F37",
overrideGreen: "2DA44E",
saturatedBlue: "0F2CCE",
paleBlue: "0A3069",
channelBlue: "0969DA",
dedupeBlue: "57606A",
triageBlue: "0969DA",
saturatedPurple: "7057FF",
mutedPurple: "57606A",
taxonomyGray: "6E7781",
taxonomySteel: "57606A",
appPurple: "6E7781",
neutralGray: "E5E7EB",
duplicateGray: "D1D5DB",
darkGray: "8C8C8C",
mutedRose: "8C959F",
mutedRed: "E99695",
black: "000000",
white: "FFFFFF",
clawsweeperOrange: "F97316",
helpGreen: "008672",
maintainerYellow: "FFD700",
};
const EXACT_COLORS = new Map(
Object.entries({
P0: COLORS.saturatedRed,
P1: COLORS.saturatedOrangeRed,
P2: COLORS.saturatedAmber,
P3: COLORS.mutedGreen,
"rating: 🦀 challenger crab": "1F883D",
"rating: 🦞 diamond lobster": "0969DA",
"rating: 🐚 platinum hermit": "0F766E",
"rating: 🦐 gold shrimp": "B7791F",
"rating: 🦪 silver shellfish": "7A828E",
"rating: 🧂 unranked krab": "8C2F39",
"rating: 🌊 off-meta tidepool": "6E7781",
"impact:data-loss": COLORS.saturatedRed,
"impact:security": COLORS.saturatedRed,
"impact:crash-loop": COLORS.saturatedOrangeRed,
"impact:message-loss": COLORS.saturatedOrangeRed,
"impact:auth-provider": COLORS.softerAmber,
"impact:session-state": COLORS.softerAmber,
bug: COLORS.saturatedBugRed,
"bug:crash": COLORS.saturatedRed,
"bug:behavior": COLORS.saturatedBugRed,
regression: COLORS.saturatedOrangeRed,
security: COLORS.saturatedRed,
"beta-blocker": COLORS.saturatedOrangeRed,
"proof: supplied": COLORS.proofGreen,
"proof: sufficient": COLORS.mutedProofGreen,
"proof: override": COLORS.overrideGreen,
"triage:blocked": COLORS.saturatedAmber,
"triage:bug": COLORS.saturatedBugRed,
"triage:done": COLORS.mutedGreen,
"triage:needs-review": COLORS.paleBlue,
"triage:started": COLORS.mutedPurple,
agents: COLORS.taxonomySteel,
docs: COLORS.paleBlue,
cli: COLORS.paleBlue,
commands: COLORS.paleBlue,
scripts: COLORS.mutedPurple,
gateway: COLORS.mutedPurple,
codex: COLORS.taxonomySteel,
docker: COLORS.paleGreen,
tui: COLORS.paleGreen,
"extensions: NEW": COLORS.channelBlue,
clawsweeper: COLORS.clawsweeperOrange,
"clawsweeper:human-review": COLORS.saturatedRed,
"clawsweeper:needs-security-review": COLORS.saturatedRed,
"clawsweeper:needs-live-repro": COLORS.saturatedAmber,
"clawsweeper:needs-maintainer-review": COLORS.saturatedAmber,
"clawsweeper:needs-product-decision": COLORS.saturatedAmber,
"clawsweeper:automerge": COLORS.saturatedGreen,
"clawsweeper:queueable-fix": COLORS.saturatedGreen,
"clawsweeper:fix-shape-clear": COLORS.mutedProofGreen,
"clawsweeper:autofix": COLORS.paleBlue,
"clawsweeper:commit-finding": COLORS.paleBlue,
"clawsweeper:autogenerated": COLORS.mutedPurple,
"clawsweeper:linked-pr-open": COLORS.mutedPurple,
"clawsweeper:merge-ready": COLORS.mutedPurple,
"clawsweeper:current-main-repro": COLORS.paleBlue,
"clawsweeper:source-repro": COLORS.paleBlue,
"clawsweeper:not-repro-on-main": COLORS.proofGreen,
"clawsweeper:no-new-fix-pr": COLORS.neutralGray,
"clawsweeper:needs-info": COLORS.appPurple,
"close:spam": COLORS.black,
"close:duplicate": COLORS.mutedRed,
"close:already-fixed": COLORS.paleBlue,
"close:cannot-repro": COLORS.paleYellow,
"close:invalid": COLORS.neutralGray,
"close:not-planned": COLORS.darkGray,
"close:superseded": COLORS.mutedPurple,
duplicate: COLORS.duplicateGray,
invalid: COLORS.paleYellow,
wontfix: COLORS.white,
"r: spam": COLORS.saturatedRed,
"r: moltbook": COLORS.saturatedRed,
"r: too-many-prs": COLORS.saturatedOrangeRed,
"r: no-ci-pr": COLORS.saturatedOrangeRed,
"r: bluebubbles": COLORS.saturatedOrangeRed,
"r: testflight": COLORS.saturatedOrangeRed,
"r: false-positive": COLORS.saturatedOrangeRed,
"r: skill": COLORS.mutedPurple,
"r: third-party-extension": COLORS.mutedPurple,
"r: support": COLORS.mutedGreen,
"r: too-many-prs-override": COLORS.overrideGreen,
maintainer: COLORS.maintainerYellow,
"trusted-contributor": COLORS.neutralGray,
"help wanted": COLORS.helpGreen,
"good first issue": COLORS.saturatedPurple,
"In Progress": COLORS.saturatedBlue,
"no-stale": COLORS.mutedGreen,
stale: COLORS.paleYellow,
"trigger-response": COLORS.saturatedAmber,
"bad-barnacle": COLORS.mutedRed,
clownfish: COLORS.neutralGray,
"mantis: telegram-visible-proof": COLORS.mutedPurple,
"dedupe:parent": COLORS.dedupeBlue,
"dedupe:child": COLORS.paleBlue,
dependencies: COLORS.neutralGray,
"dependencies-changed": COLORS.paleYellow,
"security-sensitive-changed": COLORS.paleYellow,
github_actions: COLORS.neutralGray,
go: COLORS.neutralGray,
java: COLORS.neutralGray,
swift_package_manager: COLORS.neutralGray,
"cannot-reproduce": COLORS.dedupeBlue,
question: COLORS.appPurple,
enhancement: COLORS.channelBlue,
"pi-issue": COLORS.saturatedOrangeRed,
aardvark: COLORS.neutralGray,
"cohort-4": COLORS.mutedPurple,
dirty: COLORS.saturatedRed,
}),
);
const FAMILY_RULES = [
{
family: "channel",
match: (name) => name.startsWith("channel: "),
color: COLORS.channelBlue,
reason: "routine channel taxonomy stays visually quiet",
},
{
family: "app",
match: (name) => name.startsWith("app: "),
color: COLORS.appPurple,
reason: "app platform taxonomy stays muted",
},
{
family: "extension",
match: (name) => name.startsWith("extensions: "),
color: COLORS.taxonomyGray,
reason: "plugin implementation taxonomy should not compete with priority",
},
{
family: "plugin",
match: (name) => name.startsWith("plugin: "),
color: COLORS.taxonomyGray,
reason: "plugin taxonomy stays neutral unless it becomes an action gate",
},
{
family: "size",
match: (name) => name.startsWith("size: "),
color: COLORS.mutedRose,
reason: "size text carries the value, so one muted family color is enough",
},
{
family: "triage-candidate",
match: (name) => name.startsWith("triage:"),
color: COLORS.triageBlue,
reason: "routine triage candidates stay softer than blockers and priorities",
},
];
function wantedPolicy(name) {
if (EXACT_COLORS.has(name)) {
return { color: EXACT_COLORS.get(name), family: exactFamily(name), source: "exact" };
}
const rule = FAMILY_RULES.find((candidate) => candidate.match(name));
if (rule) {
return { color: rule.color, family: rule.family, source: "family" };
}
return null;
}
function exactFamily(name) {
if (/^P[0-3]$/.test(name)) {
return "priority";
}
if (name.startsWith("rating:")) {
return "rating";
}
if (name.startsWith("impact:")) {
return "impact";
}
if (name.startsWith("clawsweeper")) {
return "clawsweeper";
}
if (name.startsWith("close:") || name.startsWith("r:")) {
return "resolution";
}
if (name.startsWith("proof:")) {
return "proof";
}
if (name.startsWith("triage:")) {
return "triage-status";
}
if (name.startsWith("bug") || name === "security" || name === "regression") {
return "risk";
}
return "specific";
}
function ghJson(args) {
return JSON.parse(execFileSync("gh", args, { encoding: "utf8" }));
}
function gh(args) {
execFileSync("gh", args, { encoding: "utf8", stdio: APPLY ? "inherit" : "pipe" });
}
function fetchLabels() {
const labels = [];
let cursor = null;
for (;;) {
const query = `query($cursor:String) {
repository(owner:"openclaw", name:"openclaw") {
labels(first:100, after:$cursor, orderBy:{field:NAME,direction:ASC}) {
nodes { name color description }
pageInfo { hasNextPage endCursor }
}
}
}`;
const response = ghJson([
"api",
"graphql",
"-f",
`query=${query}`,
"-f",
`cursor=${cursor ?? ""}`,
]);
const page = response.data.repository.labels;
labels.push(...page.nodes);
if (!page.pageInfo.hasNextPage) {
return labels;
}
cursor = page.pageInfo.endCursor;
}
}
const changes = fetchLabels()
.map((label) => ({
name: label.name,
current: label.color.toUpperCase(),
policy: wantedPolicy(label.name),
}))
.filter((label) => label.policy && label.current !== label.policy.color)
.toSorted((a, b) => a.name.localeCompare(b.name));
if (changes.length === 0) {
console.log("No label color changes needed.");
process.exit(0);
}
const familyCounts = new Map();
for (const change of changes) {
familyCounts.set(change.policy.family, (familyCounts.get(change.policy.family) ?? 0) + 1);
console.log(
`${APPLY ? "update" : "would update"} [${change.policy.family}] ${change.name}: ${change.current} -> ${change.policy.color}`,
);
if (APPLY) {
gh(["label", "edit", change.name, "--repo", REPO, "--color", change.policy.color]);
}
}
console.log("Family summary:");
for (const [family, count] of [...familyCounts.entries()].toSorted(([a], [b]) =>
a.localeCompare(b),
)) {
console.log(`- ${family}: ${count}`);
}
console.log(`${APPLY ? "Updated" : "Would update"} ${changes.length} labels.`);

View File

@@ -9,7 +9,6 @@ import { fileURLToPath } from "node:url";
import {
DEFAULT_E2E_BARE_IMAGE,
DEFAULT_E2E_FUNCTIONAL_IMAGE,
DEFAULT_E2E_IMAGE,
DEFAULT_LIVE_RETRIES,
DEFAULT_PARALLELISM,
DEFAULT_PROFILE,
@@ -340,7 +339,7 @@ function buildLaneRerunCommand(name, baseEnv) {
["OPENCLAW_DOCKER_ALL_BUILD", build],
["OPENCLAW_DOCKER_ALL_PREFLIGHT", "0"],
["OPENCLAW_SKIP_DOCKER_BUILD", "1"],
["OPENCLAW_DOCKER_E2E_IMAGE", image || DEFAULT_E2E_IMAGE],
["OPENCLAW_DOCKER_E2E_IMAGE", image || DEFAULT_E2E_FUNCTIONAL_IMAGE],
["OPENCLAW_DOCKER_E2E_BARE_IMAGE", baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE],
["OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE", baseEnv.OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE],
["OPENCLAW_CURRENT_PACKAGE_TGZ", baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ],

View File

@@ -4422,7 +4422,7 @@ export function buildFullSuiteVitestRunPlans(args, cwd = process.cwd()) {
});
}
export function shouldUseLocalFullSuiteParallelByDefault(env = process.env) {
function shouldUseLocalFullSuiteParallelByDefault(env = process.env) {
if (hasConservativeVitestWorkerBudget(env)) {
return false;
}
@@ -4431,7 +4431,7 @@ export function shouldUseLocalFullSuiteParallelByDefault(env = process.env) {
);
}
export function shouldExpandLocalFullSuiteShardsByDefault(env = process.env) {
function shouldExpandLocalFullSuiteShardsByDefault(env = process.env) {
return env.CI !== "true" && env.GITHUB_ACTIONS !== "true";
}

View File

@@ -66,7 +66,7 @@ function digestInputs(repoRoot, inputs) {
return hash.digest("hex");
}
export function buildTestboxLeaseFingerprint(repoRoot, args) {
function buildTestboxLeaseFingerprint(repoRoot, args) {
let baseSha;
try {
baseSha = git(repoRoot, ["merge-base", "HEAD", "refs/remotes/origin/main"]);

View File

@@ -476,7 +476,7 @@ function loadPullRequestCommitShas(repo, { baseSha, headSha }) {
return shas;
}
export function main(argv = process.argv.slice(2)) {
function main(argv = process.argv.slice(2)) {
const args = parseArgs(argv);
const pullRequest = JSON.parse(
execGhApiRead(`repos/${args.repo}/pulls/${args.pr}`, {

View File

@@ -1,137 +0,0 @@
/**
* Real-process verification: bounded template file cache.
*
* Imports the production loadUsageBarTemplate and exercises it with 65
* template files to prove:
* - 64 files load successfully and are cached
* - 65th file triggers eviction of the oldest entry
* - Evicted watcher is closed (proved by disk re-read on next access)
* - Non-evicted watcher remains alive (proved by watcher callback update)
*
* Usage: npx tsx scripts/verify-template-cache-bound.mjs
*/
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const startTime = new Date().toISOString();
const { loadUsageBarTemplate, clearUsageBarTemplateCacheForTest } =
await import("../src/auto-reply/usage-bar/template.js");
const dir = mkdtempSync(join(tmpdir(), "usage-template-proof-"));
const paths = [];
const CAP = 64;
const TOTAL = 65;
console.log("=".repeat(72));
console.log("OpenClaw Template Cache Bound — Real-Process Verification");
console.log("=".repeat(72));
console.log(`PID: ${process.pid}`);
console.log(`Node: ${process.version}`);
console.log(`Platform: ${process.platform} ${process.arch}`);
console.log(`Started: ${startTime}`);
console.log(`Temp dir: ${dir}`);
console.log(`Cache cap: ${CAP}`);
console.log(`Files: ${TOTAL}`);
console.log();
for (let i = 0; i < TOTAL; i++) {
const p = join(dir, `tpl-${String(i).padStart(3, "0")}.json`);
writeFileSync(p, JSON.stringify({ segments: [{ text: `v1-${i}` }] }));
paths.push(p);
}
// Phase 1: Fill cache
console.log("── Phase 1: Fill cache (files 063) ──");
const t1 = Date.now();
for (let i = 0; i < CAP; i++) {
const tpl = loadUsageBarTemplate(paths[i]);
if (!tpl || tpl.segments?.[0]?.text !== `v1-${i}`) {
console.log(` FAIL at file ${i}`);
process.exit(1);
}
}
console.log(` Duration: ${Date.now() - t1}ms`);
console.log(` Result: ${CAP} files loaded and cached`);
console.log();
// Phase 2: 65th file triggers eviction
console.log("── Phase 2: Load 65th file (triggers eviction of oldest entry) ──");
const t2 = Date.now();
const tpl64 = loadUsageBarTemplate(paths[64]);
console.log(` Duration: ${Date.now() - t2}ms`);
console.log(` File 64: ${JSON.stringify(tpl64)}`);
console.log();
// Phase 3: Non-evicted watcher still alive (MUST run before re-inserting
// the evicted path, otherwise the re-insert would evict file 1.)
console.log("── Phase 3: Non-evicted file 1 — prove watcher still alive ──");
writeFileSync(paths[1], JSON.stringify({ segments: [{ text: "CHANGED-VIA-WATCHER" }] }));
// fs.watch uses a polling fallback on Linux; give the watcher time to fire.
await new Promise((r) => {
setTimeout(r, 500);
});
const tpl1 = loadUsageBarTemplate(paths[1]);
const alive = tpl1?.segments?.[0]?.text === "CHANGED-VIA-WATCHER";
console.log(` File 1 reloaded: "${tpl1?.segments?.[0]?.text}"`);
console.log(
` Result: ${alive ? "PASS (live watcher updated cache)" : "NOTE (cache hit — watcher may need more time)"}`,
);
console.log();
// Phase 4: Cache integrity (MUST run before reloading the evicted path.)
console.log("── Phase 4: Verify files 263 still cached ──");
let cachedOk = 0;
for (let i = 2; i < CAP; i++) {
const tpl = loadUsageBarTemplate(paths[i]);
if (tpl?.segments?.[0]?.text === `v1-${i}`) {
cachedOk++;
}
}
console.log(` Cached: ${cachedOk}/${CAP - 2}`);
console.log(` Result: ${cachedOk === CAP - 2 ? "PASS" : "FAIL"}`);
console.log();
// Phase 5: Prove eviction — reloading the evicted path re-reads from disk
// because its watcher was closed. This may also evict another entry, but all
// earlier checks have already completed.
console.log("── Phase 5: Prove eviction (modify file 0 on disk, re-read) ──");
writeFileSync(paths[0], JSON.stringify({ segments: [{ text: "V2-EVICTED-RELOADED" }] }));
const tpl0 = loadUsageBarTemplate(paths[0]);
const evicted = tpl0?.segments?.[0]?.text === "V2-EVICTED-RELOADED";
console.log(` File 0 reloaded: "${tpl0?.segments?.[0]?.text}"`);
console.log(
` Result: ${evicted ? "PASS (disk re-read — evicted watcher was closed)" : "FAIL"}`,
);
console.log();
// Phase 6: Cleanup
console.log("── Phase 6: Cleanup ──");
clearUsageBarTemplateCacheForTest();
await new Promise((r) => {
setTimeout(r, 100);
});
console.log(` Result: clearUsageBarTemplateCacheForTest called`);
console.log();
rmSync(dir, { recursive: true, force: true });
console.log("=".repeat(72));
console.log("VERDICT");
console.log("=".repeat(72));
console.log(` ${CAP} files loaded → all cached PASS`);
console.log(
` 65th file → eviction + watcher close ${evicted ? "PASS" : "FAIL"}`,
);
console.log(
` Non-evicted watcher still alive ${alive ? "PASS" : "WARN"}`,
);
console.log(
` ${CAP - 2} files remain cached ${cachedOk === CAP - 2 ? "PASS" : "FAIL"}`,
);
console.log(` cleanup → all watchers closed PASS`);
console.log();
console.log(` End time: ${new Date().toISOString()}`);
process.exit(evicted && cachedOk === CAP - 2 ? 0 : 1);

View File

@@ -1,122 +0,0 @@
/** Shared behavioral contract testkit for ACP runtime adapter implementations. */
import { randomUUID } from "node:crypto";
import type { AcpRuntime, AcpRuntimeEvent } from "@openclaw/acp-core/runtime/types";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { expect } from "vitest";
import { toAcpRuntimeError } from "./errors.js";
/** Inputs and optional assertions for the shared ACP runtime adapter contract. */
type AcpRuntimeAdapterContractParams = {
createRuntime: () => Promise<AcpRuntime> | AcpRuntime;
agentId?: string;
successPrompt?: string;
errorPrompt?: string;
includeControlChecks?: boolean;
assertSuccessEvents?: (events: AcpRuntimeEvent[]) => void | Promise<void>;
assertErrorOutcome?: (params: {
events: AcpRuntimeEvent[];
thrown: unknown;
}) => void | Promise<void>;
};
/** Runs the shared behavioral contract for ACP runtime adapters. */
export async function runAcpRuntimeAdapterContract(
params: AcpRuntimeAdapterContractParams,
): Promise<void> {
const runtime = await params.createRuntime();
const sessionKey = `agent:${params.agentId ?? "codex"}:acp:contract-${randomUUID()}`;
const agent = params.agentId ?? "codex";
const handle = await runtime.ensureSession({
sessionKey,
agent,
mode: "persistent",
});
expect(handle.sessionKey).toBe(sessionKey);
expect(handle.backend.trim()).not.toHaveLength(0);
expect(handle.runtimeSessionName.trim()).not.toHaveLength(0);
const successEvents: AcpRuntimeEvent[] = [];
for await (const event of runtime.runTurn({
handle,
text: params.successPrompt ?? "contract-success",
mode: "prompt",
requestId: `contract-success-${randomUUID()}`,
})) {
successEvents.push(event);
}
expect(
successEvents.some(
(event) =>
event.type === "done" ||
event.type === "text_delta" ||
event.type === "status" ||
event.type === "tool_call",
),
).toBe(true);
expect(successEvents.some((event) => event.type === "done")).toBe(true);
await params.assertSuccessEvents?.(successEvents);
if (params.includeControlChecks ?? true) {
if (runtime.getStatus) {
const status = await runtime.getStatus({ handle });
expect(status).toBeDefined();
expect(typeof status).toBe("object");
}
if (runtime.setMode) {
await runtime.setMode({
handle,
mode: "contract",
});
}
if (runtime.setConfigOption) {
await runtime.setConfigOption({
handle,
key: "contract_key",
value: "contract_value",
});
}
}
let errorThrown: unknown = null;
const errorEvents: AcpRuntimeEvent[] = [];
const errorPrompt = normalizeOptionalString(params.errorPrompt);
if (errorPrompt) {
try {
for await (const event of runtime.runTurn({
handle,
text: errorPrompt,
mode: "prompt",
requestId: `contract-error-${randomUUID()}`,
})) {
errorEvents.push(event);
}
} catch (error) {
errorThrown = error;
}
const sawErrorEvent = errorEvents.some((event) => event.type === "error");
expect(Boolean(errorThrown) || sawErrorEvent).toBe(true);
if (errorThrown) {
const acpError = toAcpRuntimeError({
error: errorThrown,
fallbackCode: "ACP_TURN_FAILED",
fallbackMessage: "ACP runtime contract expected an error turn failure.",
});
expect(acpError.code.length).toBeGreaterThan(0);
expect(acpError.message.length).toBeGreaterThan(0);
}
}
await params.assertErrorOutcome?.({
events: errorEvents,
thrown: errorThrown,
});
await runtime.cancel({
handle,
reason: "contract-cancel",
});
await runtime.close({
handle,
reason: "contract-close",
});
}

View File

@@ -9,7 +9,7 @@ import type { Model } from "openclaw/plugin-sdk/llm";
import { describe, expect, it } from "vitest";
import { createAnthropicMessagesTransportStreamFn } from "./anthropic-transport-stream.js";
import { isLiveTestEnabled } from "./live-test-helpers.js";
import { isLiveBillingDrift } from "./live-test-provider-drift.js";
import { isLiveBillingDrift } from "./live-test-provider-drift.test-support.js";
const LIVE = isLiveTestEnabled(["ANTHROPIC_TRANSPORT_LIVE_TEST"]);
const describeLive = LIVE ? describe : describe.skip;

View File

@@ -3,103 +3,10 @@
* fixture files.
*/
import fs from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
const require = createRequire(import.meta.url);
const SDK_SERVER_MCP_PATH = require.resolve("@modelcontextprotocol/sdk/server/mcp.js");
const SDK_SERVER_STDIO_PATH = require.resolve("@modelcontextprotocol/sdk/server/stdio.js");
/** Writes an executable fixture script with parent directories created. */
export async function writeExecutable(filePath: string, content: string): Promise<void> {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, content, { encoding: "utf-8", mode: 0o755 });
}
/** Writes a stdio MCP server fixture exposing a `bundle_probe` tool. */
export async function writeBundleProbeMcpServer(
filePath: string,
params: {
startupCounterPath?: string;
startupDelayMs?: number;
pidPath?: string;
exitMarkerPath?: string;
} = {},
): Promise<void> {
await writeExecutable(
filePath,
`#!/usr/bin/env node
import fs from "node:fs";
import fsp from "node:fs/promises";
import { setTimeout as delay } from "node:timers/promises";
import { McpServer } from ${JSON.stringify(SDK_SERVER_MCP_PATH)};
import { StdioServerTransport } from ${JSON.stringify(SDK_SERVER_STDIO_PATH)};
const startupCounterPath = ${JSON.stringify(params.startupCounterPath ?? "")};
if (startupCounterPath) {
let current = 0;
try {
current = Number.parseInt((await fsp.readFile(startupCounterPath, "utf8")).trim(), 10) || 0;
} catch {}
await fsp.writeFile(startupCounterPath, String(current + 1), "utf8");
}
const pidPath = ${JSON.stringify(params.pidPath ?? "")};
if (pidPath) {
await fsp.writeFile(pidPath, String(process.pid), "utf8");
}
const exitMarkerPath = ${JSON.stringify(params.exitMarkerPath ?? "")};
if (exitMarkerPath) {
process.once("exit", () => {
try {
fs.writeFileSync(exitMarkerPath, "exited", "utf8");
} catch {}
});
}
const startupDelayMs = ${JSON.stringify(params.startupDelayMs ?? 0)};
if (startupDelayMs > 0) {
await delay(startupDelayMs);
}
const server = new McpServer({ name: "bundle-probe", version: "1.0.0" });
server.tool("bundle_probe", "Bundle MCP probe", async () => {
return {
content: [{ type: "text", text: process.env.BUNDLE_PROBE_TEXT ?? "missing-probe-text" }],
};
});
await server.connect(new StdioServerTransport());
`,
);
}
/** Writes a minimal Claude plugin bundle fixture with a bundled MCP config. */
export async function writeClaudeBundle(params: {
pluginRoot: string;
serverScriptPath: string;
}): Promise<void> {
await fs.mkdir(path.join(params.pluginRoot, ".claude-plugin"), { recursive: true });
await fs.writeFile(
path.join(params.pluginRoot, ".claude-plugin", "plugin.json"),
`${JSON.stringify({ name: "bundle-probe" }, null, 2)}\n`,
"utf-8",
);
await fs.writeFile(
path.join(params.pluginRoot, ".mcp.json"),
`${JSON.stringify(
{
mcpServers: {
bundleProbe: {
command: "node",
args: [path.relative(params.pluginRoot, params.serverScriptPath)],
env: {
BUNDLE_PROBE_TEXT: "FROM-BUNDLE",
},
},
},
},
null,
2,
)}\n`,
"utf-8",
);
}

View File

@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { applyExtraParamsToAgent } from "./embedded-agent-runner/extra-params.js";
import { isLiveTestEnabled } from "./live-test-helpers.js";
import { isLiveAuthDrift, isLiveBillingDrift } from "./live-test-provider-drift.js";
import { isLiveAuthDrift, isLiveBillingDrift } from "./live-test-provider-drift.test-support.js";
const OPENAI_KEY = process.env.OPENAI_API_KEY ?? "";
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY ?? "";

View File

@@ -77,12 +77,10 @@ export const sessionCompactImpl = vi.fn(async () => ({
details: { ok: true },
}));
export const triggerInternalHook: Mock<(event?: unknown) => void> = vi.fn();
export const sanitizeSessionHistoryMock = vi.fn(
const sanitizeSessionHistoryMock = vi.fn(
async (params: { messages: unknown[] }) => params.messages,
);
export const validateReplayTurnsMock = vi.fn(
async ({ messages }: { messages: unknown[] }) => messages,
);
const validateReplayTurnsMock = vi.fn(async ({ messages }: { messages: unknown[] }) => messages);
export const getMemorySearchManagerMock: Mock<
(params?: unknown) => Promise<MockMemorySearchManager>
> = vi.fn(async () => ({
@@ -188,7 +186,7 @@ function createMockToolDefinitions(tools: unknown[] = []) {
});
}
export const createOpenClawCodingToolsMock = vi.fn(() => []);
export const buildEmbeddedExtensionFactoriesMock = vi.fn(() => []);
const buildEmbeddedExtensionFactoriesMock = vi.fn(() => []);
export const guardSessionManagerMock = vi.fn(() => ({
flushPendingToolResults: vi.fn(),
}));
@@ -243,8 +241,9 @@ function createDefaultCompactionAuthStore(): AuthProfileStore {
export const ensureAuthProfileStoreMock: Mock<() => AuthProfileStore> = vi.fn(
createDefaultCompactionAuthStore,
);
export const ensureAuthProfileStoreWithoutExternalProfilesMock: Mock<() => AuthProfileStore> =
vi.fn(createDefaultCompactionAuthStore);
const ensureAuthProfileStoreWithoutExternalProfilesMock: Mock<() => AuthProfileStore> = vi.fn(
createDefaultCompactionAuthStore,
);
const resolveAgentTransportOverrideMock: Mock<(params?: unknown) => string | undefined> = vi.fn(
() => undefined,
);

View File

@@ -140,8 +140,8 @@ function makeMockRuntimePlan(): MockRuntimePlan {
}
export const mockedCompactDirect = mockedContextEngine.compact;
export const mockedResolveContextEngine = vi.fn(async () => mockedContextEngine);
export const mockedResolveContextEngineOwnerPluginId = vi.fn(() => undefined);
const mockedResolveContextEngine = vi.fn(async () => mockedContextEngine);
const mockedResolveContextEngineOwnerPluginId = vi.fn(() => undefined);
export const mockedBuildAgentRuntimePlan = vi.fn<() => AgentRuntimePlan>(
() => makeMockRuntimePlan() as AgentRuntimePlan,
);
@@ -159,7 +159,7 @@ function createMockAgentDiscoveryStores(): MockAgentDiscoveryStores {
};
}
export const mockedCreateEmptyAgentDiscoveryStores = vi.fn(createMockAgentDiscoveryStores);
const mockedCreateEmptyAgentDiscoveryStores = vi.fn(createMockAgentDiscoveryStores);
function createMockResolvedModel(
provider = "anthropic",
modelId = "test-model",
@@ -191,7 +191,7 @@ export const mockedResolveModelAsync = vi.fn(
async (provider?: string, modelId?: string, _agentDir?: string, cfg?: unknown) =>
createMockResolvedModel(provider, modelId, cfg),
);
export const mockedPrepareProviderRuntimeAuth = vi.fn(async () => undefined);
const mockedPrepareProviderRuntimeAuth = vi.fn(async () => undefined);
export const mockedRunEmbeddedAttempt =
vi.fn<(params: unknown) => Promise<EmbeddedRunAttemptResult>>();
export const mockedBuildEmbeddedRunPayloads = vi.fn<
@@ -204,7 +204,7 @@ export const mockedWaitForDeferredTurnMaintenanceForSession = vi.fn(
async (_sessionKey?: string) => undefined,
);
export const mockedSessionLikelyHasOversizedToolResults = vi.fn(() => false);
export const mockedResolveLiveToolResultMaxChars = vi.fn(() => 32_000);
const mockedResolveLiveToolResultMaxChars = vi.fn(() => 32_000);
type MockTruncateOversizedToolResultsResult = {
truncated: boolean;
truncatedCount: number;
@@ -264,7 +264,7 @@ export const mockedLog: {
isEnabled: vi.fn(() => false),
};
export const mockedFormatBillingErrorMessage = vi.fn(() => "");
const mockedFormatBillingErrorMessage = vi.fn(() => "");
export const mockedClassifyFailoverReason = vi.fn<(raw: string) => FailoverReason | null>(
() => null,
);
@@ -277,12 +277,12 @@ export const mockedExtractObservedOverflowTokenCount = vi.fn((msg?: string) => {
return match?.[1] ? Number(match[1].replaceAll(",", "")) : undefined;
});
export const mockedFormatAssistantErrorText = vi.fn(() => "");
export const mockedIsAuthAssistantError = vi.fn(() => false);
export const mockedIsBillingAssistantError = vi.fn(() => false);
const mockedIsAuthAssistantError = vi.fn(() => false);
const mockedIsBillingAssistantError = vi.fn(() => false);
export const mockedIsCompactionFailureError = vi.fn(() => false);
export const mockedIsFailoverAssistantError = vi.fn<MockAssistantErrorProbe>(() => false);
export const mockedIsFailoverErrorMessage = vi.fn(() => false);
export const mockedIsGenericUnknownStreamErrorMessage = vi.fn((raw: string) =>
const mockedIsFailoverErrorMessage = vi.fn(() => false);
const mockedIsGenericUnknownStreamErrorMessage = vi.fn((raw: string) =>
/^\s*an unknown error occurred\.?\s*$/i.test(raw),
);
export const mockedIsLikelyContextOverflowError = vi.fn((msg?: string) => {
@@ -294,10 +294,10 @@ export const mockedIsLikelyContextOverflowError = vi.fn((msg?: string) => {
lower.includes("prompt is too long")
);
});
export const mockedParseImageSizeError = vi.fn(() => null);
export const mockedParseImageDimensionError = vi.fn(() => null);
const mockedParseImageSizeError = vi.fn(() => null);
const mockedParseImageDimensionError = vi.fn(() => null);
export const mockedIsRateLimitAssistantError = vi.fn<MockAssistantErrorProbe>(() => false);
export const mockedIsTimeoutErrorMessage = vi.fn(() => false);
const mockedIsTimeoutErrorMessage = vi.fn(() => false);
export const mockedPickFallbackThinkingLevel = vi.fn<(params?: unknown) => ThinkLevel | null>(
() => null,
);
@@ -313,11 +313,11 @@ export const mockedResolveContextWindowInfo = vi.fn(() => ({
tokens: 200000,
source: "model",
}));
export const mockedFormatContextWindowWarningMessage = vi.fn(
const mockedFormatContextWindowWarningMessage = vi.fn(
(params: { provider: string; modelId: string; guard: { tokens: number; source: string } }) =>
`low context window: ${params.provider}/${params.modelId} ctx=${params.guard.tokens} source=${params.guard.source}`,
);
export const mockedFormatContextWindowBlockMessage = vi.fn(
const mockedFormatContextWindowBlockMessage = vi.fn(
(params: { guard: { tokens: number; source: string } }) =>
`Model context window too small (${params.guard.tokens} tokens; source=${params.guard.source}). Minimum is 1000.`,
);
@@ -369,7 +369,7 @@ export const mockedResolveAuthProfileOrder = vi.fn<(_params?: unknown) => string
type AuthProfileOrderResolution = ReturnType<
typeof import("../model-auth.js").resolveAuthProfileOrderWithMetadata
>;
export const mockedResolveAuthProfileOrderWithMetadata = vi.fn<
const mockedResolveAuthProfileOrderWithMetadata = vi.fn<
(_params?: unknown) => AuthProfileOrderResolution
>((params?: unknown) => ({
profileIds: mockedResolveAuthProfileOrder(params),
@@ -378,9 +378,9 @@ export const mockedResolveAuthProfileOrderWithMetadata = vi.fn<
export const mockedResolveProviderEntryApiKeyProfileReference = vi.fn<
(_params?: unknown) => unknown
>(() => ({ kind: "none" }));
export const mockedHasUsableCustomProviderApiKey = vi.fn(() => false);
const mockedHasUsableCustomProviderApiKey = vi.fn(() => false);
export const mockedMarkAuthProfileSuccess = vi.fn(async () => {});
export const mockedShouldPreferExplicitConfigApiKeyAuth = vi.fn(() => false);
const mockedShouldPreferExplicitConfigApiKeyAuth = vi.fn(() => false);
export const overflowBaseRunParams = {
sessionId: "test-session",

View File

@@ -0,0 +1,237 @@
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import {
LIVE_CACHE_REGRESSION_BASELINE,
type LiveCacheFloor,
} from "./live-cache-regression-baseline.js";
import {
isLiveCachePrerequisiteSkip,
type LiveResolvedModelPool,
logLiveCache,
resolveLiveDirectModelPool,
} from "./live-cache-test-support.js";
const LIVE_CACHE_LANE_RETRIES = 1;
export const LIVE_CACHE_RESPONSE_RETRIES = 2;
const OPENAI_CACHE_PROBE_MIN_MAX_TOKENS = 1024;
const ANTHROPIC_CACHE_PROBE_MIN_MAX_TOKENS = 1024;
type LiveCacheProviderConfig = Parameters<typeof resolveLiveDirectModelPool>[0];
type ProviderKey = keyof typeof LIVE_CACHE_REGRESSION_BASELINE;
export type CacheLane = "image" | "mcp" | "stable" | "tool";
export type CacheUsage = {
input?: number;
output?: number;
cacheRead?: number;
cacheWrite?: number;
};
type BaselineLane = CacheLane | "disabled";
export type CacheRun = {
hitRate: number;
suffix: string;
text: string;
usage: CacheUsage;
};
export type LaneResult = {
best?: CacheRun;
disabled?: CacheRun;
warmup?: CacheRun;
};
export type BaselineFindings = {
regressions: string[];
warnings: string[];
};
type LiveCacheRegressionSummary = Record<string, Record<string, unknown>>;
type LiveCacheProviderResolver = (
params: LiveCacheProviderConfig,
) => Promise<LiveResolvedModelPool>;
export async function resolveLiveCacheProviderPool(params: {
config: LiveCacheProviderConfig;
regressions: string[];
resolver?: LiveCacheProviderResolver;
summary: LiveCacheRegressionSummary;
warnings: string[];
}): Promise<LiveResolvedModelPool | undefined> {
try {
return await (params.resolver ?? resolveLiveDirectModelPool)(params.config);
} catch (error) {
if (!isLiveCachePrerequisiteSkip(error)) {
throw error;
}
const warning = `${error.provider} skipped: ${error.message}`;
if (error.provider === "openai") {
params.warnings.push(warning);
} else {
params.regressions.push(warning);
}
const providerSummary = params.summary[error.provider];
if (providerSummary) {
providerSummary.skipped = true;
}
logLiveCache(warning);
return undefined;
}
}
export function shouldRetryCacheProbeText(params: {
attempt: number;
suffix: string;
text: string;
}): boolean {
const responseTextLower = normalizeLowercaseStringOrEmpty(params.text);
const suffixLower = normalizeLowercaseStringOrEmpty(params.suffix);
const markerLower = `cache-ok ${suffixLower}`;
// Live providers sometimes return near-miss text on the first attempt.
return (
(!responseTextLower.includes(markerLower) || !responseTextLower.includes(suffixLower)) &&
params.attempt <= LIVE_CACHE_RESPONSE_RETRIES
);
}
export function resolveCacheProbeMaxTokens(params: {
maxTokens: number | undefined;
providerTag: "anthropic" | "openai";
}): number {
const requested = params.maxTokens ?? 64;
const floor =
params.providerTag === "anthropic"
? ANTHROPIC_CACHE_PROBE_MIN_MAX_TOKENS
: OPENAI_CACHE_PROBE_MIN_MAX_TOKENS;
return Math.max(requested, floor);
}
export function shouldAcceptEmptyCacheProbe(params: {
providerTag: "anthropic" | "openai";
text: string;
usage: CacheUsage;
}): boolean {
if (params.text.trim().length > 0) {
return false;
}
// Empty text is acceptable only when provider usage proves the cache lane ran.
return (
(params.usage.input ?? 0) > 0 ||
(params.usage.cacheRead ?? 0) > 0 ||
(params.usage.cacheWrite ?? 0) > 0
);
}
function resolveBaselineFloor(provider: ProviderKey, lane: string): LiveCacheFloor | undefined {
return LIVE_CACHE_REGRESSION_BASELINE[provider][
lane as keyof (typeof LIVE_CACHE_REGRESSION_BASELINE)[typeof provider]
] as LiveCacheFloor | undefined;
}
function warmupHasCacheEvidence(params: { floor: LiveCacheFloor; warmup: CacheRun }): boolean {
const cacheRead = params.warmup.usage.cacheRead ?? 0;
const cacheWrite = params.warmup.usage.cacheWrite ?? 0;
if (params.floor.minCacheReadOrWrite !== undefined) {
return Math.max(cacheRead, cacheWrite) >= params.floor.minCacheReadOrWrite;
}
if (params.floor.minCacheRead !== undefined && cacheRead < params.floor.minCacheRead) {
return false;
}
if (params.floor.minHitRate !== undefined && params.warmup.hitRate < params.floor.minHitRate) {
return false;
}
return params.floor.minCacheRead !== undefined || params.floor.minHitRate !== undefined;
}
export function assertAgainstBaseline(params: {
lane: BaselineLane;
provider: ProviderKey;
result: LaneResult;
regressions: string[];
warnings: string[];
}): void {
const floor = resolveBaselineFloor(params.provider, params.lane);
const recordRegression = (message: string) => {
// OpenAI cache floors are currently watch-only; Anthropic misses fail.
if (floor?.warnOnly) {
params.warnings.push(message);
} else {
params.regressions.push(message);
}
};
if (!floor) {
params.regressions.push(`${params.provider}:${params.lane} missing baseline entry`);
return;
}
if (params.result.best) {
const usage = params.result.best.usage;
if (floor.minCacheReadOrWrite !== undefined) {
const cacheReadOrWrite = Math.max(usage.cacheRead ?? 0, usage.cacheWrite ?? 0);
if (cacheReadOrWrite < floor.minCacheReadOrWrite) {
recordRegression(
`${params.provider}:${params.lane} cacheReadOrWrite=${cacheReadOrWrite} < min=${floor.minCacheReadOrWrite}`,
);
}
} else if ((usage.cacheRead ?? 0) < (floor.minCacheRead ?? 0)) {
recordRegression(
`${params.provider}:${params.lane} cacheRead=${usage.cacheRead ?? 0} < min=${floor.minCacheRead}`,
);
}
if (params.result.best.hitRate < (floor.minHitRate ?? 0)) {
recordRegression(
`${params.provider}:${params.lane} hitRate=${params.result.best.hitRate.toFixed(3)} < min=${floor.minHitRate?.toFixed(3)}`,
);
}
}
if (params.result.warmup) {
const warmup = params.result.warmup;
const warmupUsage = warmup.usage;
if (
(warmupUsage.cacheWrite ?? 0) < (floor.minCacheWrite ?? 0) &&
!warmupHasCacheEvidence({ floor, warmup })
) {
recordRegression(
`${params.provider}:${params.lane} warmup cacheWrite=${warmupUsage.cacheWrite ?? 0} < min=${floor.minCacheWrite}`,
);
}
}
if (params.result.disabled) {
const usage = params.result.disabled.usage;
if ((usage.cacheRead ?? 0) > (floor.maxCacheRead ?? Number.POSITIVE_INFINITY)) {
recordRegression(
`${params.provider}:${params.lane} cacheRead=${usage.cacheRead ?? 0} > max=${floor.maxCacheRead}`,
);
}
if ((usage.cacheWrite ?? 0) > (floor.maxCacheWrite ?? Number.POSITIVE_INFINITY)) {
recordRegression(
`${params.provider}:${params.lane} cacheWrite=${usage.cacheWrite ?? 0} > max=${floor.maxCacheWrite}`,
);
}
}
}
export function evaluateAgainstBaseline(params: {
lane: BaselineLane;
provider: ProviderKey;
result: LaneResult;
}): BaselineFindings {
const regressions: string[] = [];
const warnings: string[] = [];
assertAgainstBaseline({
...params,
regressions,
warnings,
});
return { regressions, warnings };
}
export function shouldRetryBaselineFindings(findings: BaselineFindings, attempt: number): boolean {
return findings.regressions.length > 0 && attempt <= LIVE_CACHE_LANE_RETRIES;
}
export function isAnthropicToolProbeDrift(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
return (
error.message.startsWith("expected tool call for ") ||
error.message.startsWith("expected tool-only response for ")
);
}

View File

@@ -1,6 +1,15 @@
// Verifies live cache regression baseline classification without live providers.
import { describe, expect, it } from "vitest";
import { testing } from "./live-cache-regression-runner.js";
import {
assertAgainstBaseline,
evaluateAgainstBaseline,
isAnthropicToolProbeDrift,
resolveCacheProbeMaxTokens,
resolveLiveCacheProviderPool,
shouldAcceptEmptyCacheProbe,
shouldRetryBaselineFindings,
shouldRetryCacheProbeText,
} from "./live-cache-regression-policy.js";
import {
LiveCachePrerequisiteSkip,
toLiveCachePrerequisiteSkip,
@@ -14,7 +23,7 @@ describe("live cache regression runner", () => {
const regressions: string[] = [];
const warnings: string[] = [];
testing.assertAgainstBaseline({
assertAgainstBaseline({
lane: "image",
provider: "openai",
result: {
@@ -40,7 +49,7 @@ describe("live cache regression runner", () => {
const regressions: string[] = [];
const warnings: string[] = [];
testing.assertAgainstBaseline({
assertAgainstBaseline({
lane: "stable",
provider: "openai",
result: {
@@ -66,7 +75,7 @@ describe("live cache regression runner", () => {
// Hard regressions get one rerun to absorb provider cache warmup jitter;
// advisory warnings should not trigger reruns.
expect(
testing.shouldRetryBaselineFindings(
shouldRetryBaselineFindings(
{
regressions: ["anthropic:image cacheRead=0 < min=4500"],
warnings: [],
@@ -75,7 +84,7 @@ describe("live cache regression runner", () => {
),
).toBe(true);
expect(
testing.shouldRetryBaselineFindings(
shouldRetryBaselineFindings(
{
regressions: ["anthropic:image cacheRead=0 < min=4500"],
warnings: [],
@@ -84,7 +93,7 @@ describe("live cache regression runner", () => {
),
).toBe(false);
expect(
testing.shouldRetryBaselineFindings(
shouldRetryBaselineFindings(
{
regressions: [],
warnings: ["openai:image cacheRead=0 < min=3840"],
@@ -102,7 +111,7 @@ describe("live cache regression runner", () => {
openai: {},
};
const resolved = await testing.resolveLiveCacheProviderPool({
const resolved = await resolveLiveCacheProviderPool({
config: {
provider: "openai",
api: "openai-responses",
@@ -138,7 +147,7 @@ describe("live cache regression runner", () => {
openai: {},
};
const resolved = await testing.resolveLiveCacheProviderPool({
const resolved = await resolveLiveCacheProviderPool({
config: {
provider: "anthropic",
api: "anthropic-messages",
@@ -177,35 +186,35 @@ describe("live cache regression runner", () => {
it("retries a cache probe twice when provider text misses the sentinel", () => {
expect(
testing.shouldRetryCacheProbeText({
shouldRetryCacheProbeText({
attempt: 1,
suffix: "openai-stable-hit-a",
text: "",
}),
).toBe(true);
expect(
testing.shouldRetryCacheProbeText({
shouldRetryCacheProbeText({
attempt: 2,
suffix: "openai-stable-hit-a",
text: "",
}),
).toBe(true);
expect(
testing.shouldRetryCacheProbeText({
shouldRetryCacheProbeText({
attempt: 3,
suffix: "openai-stable-hit-a",
text: "",
}),
).toBe(false);
expect(
testing.shouldRetryCacheProbeText({
shouldRetryCacheProbeText({
attempt: 1,
suffix: "openai-stable-hit-a",
text: "I saw openai-stable-hit-a.",
}),
).toBe(true);
expect(
testing.shouldRetryCacheProbeText({
shouldRetryCacheProbeText({
attempt: 1,
suffix: "openai-stable-hit-a",
text: "CACHE-OK openai-stable-hit-a",
@@ -215,25 +224,25 @@ describe("live cache regression runner", () => {
it("keeps cache probes above the provider empty-output floor", () => {
expect(
testing.resolveCacheProbeMaxTokens({
resolveCacheProbeMaxTokens({
maxTokens: 32,
providerTag: "openai",
}),
).toBe(1024);
expect(
testing.resolveCacheProbeMaxTokens({
resolveCacheProbeMaxTokens({
maxTokens: 512,
providerTag: "openai",
}),
).toBe(1024);
expect(
testing.resolveCacheProbeMaxTokens({
resolveCacheProbeMaxTokens({
maxTokens: 2048,
providerTag: "openai",
}),
).toBe(2048);
expect(
testing.resolveCacheProbeMaxTokens({
resolveCacheProbeMaxTokens({
maxTokens: 32,
providerTag: "anthropic",
}),
@@ -241,46 +250,44 @@ describe("live cache regression runner", () => {
});
it("classifies Anthropic tool-only probe misses as provider drift", () => {
expect(testing.isAnthropicToolProbeDrift(new Error("expected tool call for noop"))).toBe(true);
expect(isAnthropicToolProbeDrift(new Error("expected tool call for noop"))).toBe(true);
expect(
testing.isAnthropicToolProbeDrift(
new Error('expected tool-only response for noop, got "ok"'),
),
isAnthropicToolProbeDrift(new Error('expected tool-only response for noop, got "ok"')),
).toBe(true);
expect(testing.isAnthropicToolProbeDrift(new Error("other failure"))).toBe(false);
expect(isAnthropicToolProbeDrift(new Error("other failure"))).toBe(false);
});
it("accepts empty cache probe text only when usage is observable", () => {
expect(
testing.shouldAcceptEmptyCacheProbe({
shouldAcceptEmptyCacheProbe({
providerTag: "openai",
text: "",
usage: { input: 5_000 },
}),
).toBe(true);
expect(
testing.shouldAcceptEmptyCacheProbe({
shouldAcceptEmptyCacheProbe({
providerTag: "openai",
text: "",
usage: { cacheRead: 4_608 },
}),
).toBe(true);
expect(
testing.shouldAcceptEmptyCacheProbe({
shouldAcceptEmptyCacheProbe({
providerTag: "openai",
text: "wrong",
usage: { input: 5_000 },
}),
).toBe(false);
expect(
testing.shouldAcceptEmptyCacheProbe({
shouldAcceptEmptyCacheProbe({
providerTag: "anthropic",
text: "",
usage: { input: 5_000 },
}),
).toBe(true);
expect(
testing.shouldAcceptEmptyCacheProbe({
shouldAcceptEmptyCacheProbe({
providerTag: "openai",
text: "",
usage: {},
@@ -289,7 +296,7 @@ describe("live cache regression runner", () => {
});
it("accepts a warmup that already hits the provider cache", () => {
const findings = testing.evaluateAgainstBaseline({
const findings = evaluateAgainstBaseline({
lane: "image",
provider: "anthropic",
result: {
@@ -314,7 +321,7 @@ describe("live cache regression runner", () => {
it("still rejects warmups with no cache write or cache hit evidence", () => {
// A successful best probe is not enough: warmup must prove either cache
// write or read evidence so the measured hit is meaningful.
const findings = testing.evaluateAgainstBaseline({
const findings = evaluateAgainstBaseline({
lane: "image",
provider: "anthropic",
result: {

View File

@@ -11,19 +11,28 @@ import { Type } from "typebox";
import type { AssistantMessage, Message, Tool } from "../llm/types.js";
import { extractAssistantText } from "./embedded-agent-utils.js";
import {
LIVE_CACHE_REGRESSION_BASELINE,
type LiveCacheFloor,
} from "./live-cache-regression-baseline.js";
assertAgainstBaseline,
type BaselineFindings,
type CacheLane,
type CacheRun,
type CacheUsage,
evaluateAgainstBaseline,
isAnthropicToolProbeDrift,
type LaneResult,
LIVE_CACHE_RESPONSE_RETRIES,
resolveCacheProbeMaxTokens,
resolveLiveCacheProviderPool,
shouldAcceptEmptyCacheProbe,
shouldRetryBaselineFindings,
shouldRetryCacheProbeText,
} from "./live-cache-regression-policy.js";
import {
buildAssistantHistoryTurn,
buildStableCachePrefix,
completeSimpleWithLiveTimeout,
computeCacheHitRate,
isLiveCachePrerequisiteSkip,
type LiveResolvedModel,
type LiveResolvedModelPool,
logLiveCache,
resolveLiveDirectModelPool,
withLiveDirectModelApiKey,
} from "./live-cache-test-support.js";
import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
@@ -31,10 +40,7 @@ import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
const OPENAI_TIMEOUT_MS = 120_000;
const ANTHROPIC_TIMEOUT_MS = 120_000;
const LIVE_CACHE_LANE_RETRIES = 1;
const LIVE_CACHE_RESPONSE_RETRIES = 2;
const OPENAI_CACHE_REASONING = "none" as unknown as never;
const OPENAI_CACHE_PROBE_MIN_MAX_TOKENS = 1024;
const ANTHROPIC_CACHE_PROBE_MIN_MAX_TOKENS = 1024;
const OPENAI_PREFIX = buildStableCachePrefix("openai");
const OPENAI_MCP_PREFIX = buildStableCachePrefix("openai-mcp-style");
const ANTHROPIC_PREFIX = buildStableCachePrefix("anthropic");
@@ -43,42 +49,11 @@ const LIVE_TEST_PNG_URL = new URL(
import.meta.url,
);
type LiveCacheProviderConfig = Parameters<typeof resolveLiveDirectModelPool>[0];
type ProviderKey = keyof typeof LIVE_CACHE_REGRESSION_BASELINE;
type CacheLane = "image" | "mcp" | "stable" | "tool";
type CacheUsage = {
input?: number;
output?: number;
cacheRead?: number;
cacheWrite?: number;
};
type BaselineLane = CacheLane | "disabled";
type CacheRun = {
hitRate: number;
suffix: string;
text: string;
usage: CacheUsage;
};
type LaneResult = {
best?: CacheRun;
disabled?: CacheRun;
warmup?: CacheRun;
};
type BaselineFindings = {
regressions: string[];
warnings: string[];
};
type LiveCacheRegressionResult = {
regressions: string[];
summary: Record<string, Record<string, unknown>>;
warnings: string[];
};
type LiveCacheRegressionSummary = LiveCacheRegressionResult["summary"];
type LiveCacheProviderResolver = (
params: LiveCacheProviderConfig,
) => Promise<LiveResolvedModelPool>;
class CacheProbeTextMismatchError extends Error {
constructor(
readonly suffix: string,
@@ -108,34 +83,6 @@ function makeUserTurn(content: Extract<Message, { role: "user" }>["content"]): M
};
}
async function resolveLiveCacheProviderPool(params: {
config: LiveCacheProviderConfig;
regressions: string[];
resolver?: LiveCacheProviderResolver;
summary: LiveCacheRegressionSummary;
warnings: string[];
}): Promise<LiveResolvedModelPool | undefined> {
try {
return await (params.resolver ?? resolveLiveDirectModelPool)(params.config);
} catch (error) {
if (!isLiveCachePrerequisiteSkip(error)) {
throw error;
}
const warning = `${error.provider} skipped: ${error.message}`;
if (error.provider === "openai") {
params.warnings.push(warning);
} else {
params.regressions.push(warning);
}
const providerSummary = params.summary[error.provider];
if (providerSummary) {
providerSummary.skipped = true;
}
logLiveCache(warning);
return undefined;
}
}
function makeImageUserTurn(text: string, pngBase64: string): Message {
return makeUserTurn([
{ type: "text", text },
@@ -174,61 +121,12 @@ function normalizeCacheUsage(usage: AssistantMessage["usage"] | undefined): Cach
};
}
function resolveBaselineFloor(provider: ProviderKey, lane: string): LiveCacheFloor | undefined {
return LIVE_CACHE_REGRESSION_BASELINE[provider][
lane as keyof (typeof LIVE_CACHE_REGRESSION_BASELINE)[typeof provider]
] as LiveCacheFloor | undefined;
}
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
function shouldRetryCacheProbeText(params: {
attempt: number;
suffix: string;
text: string;
}): boolean {
const responseTextLower = normalizeLowercaseStringOrEmpty(params.text);
const suffixLower = normalizeLowercaseStringOrEmpty(params.suffix);
const markerLower = `cache-ok ${suffixLower}`;
// Live providers sometimes return near-miss text on the first attempt.
return (
(!responseTextLower.includes(markerLower) || !responseTextLower.includes(suffixLower)) &&
params.attempt <= LIVE_CACHE_RESPONSE_RETRIES
);
}
function resolveCacheProbeMaxTokens(params: {
maxTokens: number | undefined;
providerTag: "anthropic" | "openai";
}): number {
const requested = params.maxTokens ?? 64;
const floor =
params.providerTag === "anthropic"
? ANTHROPIC_CACHE_PROBE_MIN_MAX_TOKENS
: OPENAI_CACHE_PROBE_MIN_MAX_TOKENS;
return Math.max(requested, floor);
}
function shouldAcceptEmptyCacheProbe(params: {
providerTag: "anthropic" | "openai";
text: string;
usage: CacheUsage;
}): boolean {
if (params.text.trim().length > 0) {
return false;
}
// Empty text is acceptable only when provider usage proves the cache lane ran.
return (
(params.usage.input ?? 0) > 0 ||
(params.usage.cacheRead ?? 0) > 0 ||
(params.usage.cacheWrite ?? 0) > 0
);
}
async function runToolOnlyTurn(params: {
apiKey: string;
cacheRetention: "none" | "short" | "long";
@@ -490,110 +388,6 @@ function formatUsage(usage: CacheUsage | undefined) {
return `cacheRead=${usage?.cacheRead ?? 0} cacheWrite=${usage?.cacheWrite ?? 0} input=${usage?.input ?? 0} output=${usage?.output ?? 0}`;
}
function warmupHasCacheEvidence(params: { floor: LiveCacheFloor; warmup: CacheRun }): boolean {
const cacheRead = params.warmup.usage.cacheRead ?? 0;
const cacheWrite = params.warmup.usage.cacheWrite ?? 0;
if (params.floor.minCacheReadOrWrite !== undefined) {
return Math.max(cacheRead, cacheWrite) >= params.floor.minCacheReadOrWrite;
}
if (params.floor.minCacheRead !== undefined && cacheRead < params.floor.minCacheRead) {
return false;
}
if (params.floor.minHitRate !== undefined && params.warmup.hitRate < params.floor.minHitRate) {
return false;
}
return params.floor.minCacheRead !== undefined || params.floor.minHitRate !== undefined;
}
function assertAgainstBaseline(params: {
lane: BaselineLane;
provider: ProviderKey;
result: LaneResult;
regressions: string[];
warnings: string[];
}) {
const floor = resolveBaselineFloor(params.provider, params.lane);
const recordRegression = (message: string) => {
// OpenAI cache floors are currently watch-only; Anthropic misses fail.
if (floor?.warnOnly) {
params.warnings.push(message);
} else {
params.regressions.push(message);
}
};
if (!floor) {
params.regressions.push(`${params.provider}:${params.lane} missing baseline entry`);
return;
}
if (params.result.best) {
const usage = params.result.best.usage;
if (floor.minCacheReadOrWrite !== undefined) {
const cacheReadOrWrite = Math.max(usage.cacheRead ?? 0, usage.cacheWrite ?? 0);
if (cacheReadOrWrite < floor.minCacheReadOrWrite) {
recordRegression(
`${params.provider}:${params.lane} cacheReadOrWrite=${cacheReadOrWrite} < min=${floor.minCacheReadOrWrite}`,
);
}
} else if ((usage.cacheRead ?? 0) < (floor.minCacheRead ?? 0)) {
recordRegression(
`${params.provider}:${params.lane} cacheRead=${usage.cacheRead ?? 0} < min=${floor.minCacheRead}`,
);
}
if (params.result.best.hitRate < (floor.minHitRate ?? 0)) {
recordRegression(
`${params.provider}:${params.lane} hitRate=${params.result.best.hitRate.toFixed(3)} < min=${floor.minHitRate?.toFixed(3)}`,
);
}
}
if (params.result.warmup) {
const warmup = params.result.warmup;
const warmupUsage = warmup.usage;
if (
(warmupUsage.cacheWrite ?? 0) < (floor.minCacheWrite ?? 0) &&
!warmupHasCacheEvidence({ floor, warmup })
) {
recordRegression(
`${params.provider}:${params.lane} warmup cacheWrite=${warmupUsage.cacheWrite ?? 0} < min=${floor.minCacheWrite}`,
);
}
}
if (params.result.disabled) {
const usage = params.result.disabled.usage;
if ((usage.cacheRead ?? 0) > (floor.maxCacheRead ?? Number.POSITIVE_INFINITY)) {
recordRegression(
`${params.provider}:${params.lane} cacheRead=${usage.cacheRead ?? 0} > max=${floor.maxCacheRead}`,
);
}
if ((usage.cacheWrite ?? 0) > (floor.maxCacheWrite ?? Number.POSITIVE_INFINITY)) {
recordRegression(
`${params.provider}:${params.lane} cacheWrite=${usage.cacheWrite ?? 0} > max=${floor.maxCacheWrite}`,
);
}
}
}
function evaluateAgainstBaseline(params: {
lane: BaselineLane;
provider: ProviderKey;
result: LaneResult;
}): BaselineFindings {
const regressions: string[] = [];
const warnings: string[] = [];
assertAgainstBaseline({
...params,
regressions,
warnings,
});
return { regressions, warnings };
}
function shouldRetryBaselineFindings(findings: BaselineFindings, attempt: number): boolean {
return findings.regressions.length > 0 && attempt <= LIVE_CACHE_LANE_RETRIES;
}
async function runRepeatedLaneWithBaselineRetry(params: {
lane: CacheLane;
providerTag: "anthropic" | "openai";
@@ -651,16 +445,6 @@ function isAnthropicEmptyCacheProbe(error: unknown): boolean {
return error instanceof CacheProbeTextMismatchError && error.text.trim().length === 0;
}
function isAnthropicToolProbeDrift(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
return (
error.message.startsWith("expected tool call for ") ||
error.message.startsWith("expected tool-only response for ")
);
}
function shouldSkipAnthropicCacheProviderDrift(error: unknown): boolean {
return Boolean(
shouldSkipLiveProviderDrift({
@@ -743,18 +527,6 @@ async function runAnthropicDisabledCacheLane(params: {
}
}
/** Internal seams used by unit tests for baseline and retry decisions. */
export const testing = {
assertAgainstBaseline,
evaluateAgainstBaseline,
resolveLiveCacheProviderPool,
resolveCacheProbeMaxTokens,
isAnthropicToolProbeDrift,
shouldAcceptEmptyCacheProbe,
shouldRetryCacheProbeText,
shouldRetryBaselineFindings,
};
/** Runs all live prompt-cache lanes and returns hard regressions plus warn-only drift. */
export async function runLiveCacheRegression(): Promise<LiveCacheRegressionResult> {
const pngBase64 = (await fs.readFile(LIVE_TEST_PNG_URL)).toString("base64");
@@ -881,4 +653,3 @@ export async function runLiveCacheRegression(): Promise<LiveCacheRegressionResul
}
return { regressions, summary, warnings };
}
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */

View File

@@ -0,0 +1,20 @@
import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
export function isLiveAuthDrift(error: unknown): boolean {
return shouldSkipLiveProviderDrift({ allowAuth: true, error })?.reason === "auth";
}
export function isLiveBillingDrift(error: unknown): boolean {
return shouldSkipLiveProviderDrift({ allowBilling: true, error })?.reason === "billing";
}
export function isLiveRateLimitDrift(error: unknown): boolean {
return shouldSkipLiveProviderDrift({ allowRateLimit: true, error })?.reason === "rate-limit";
}
export function isLiveProviderUnavailableDrift(error: unknown): boolean {
return (
shouldSkipLiveProviderDrift({ allowProviderUnavailable: true, error })?.reason ===
"provider-unavailable"
);
}

View File

@@ -1,12 +1,12 @@
// Classifies acceptable live-provider drift for optional validation lanes.
import { describe, expect, it } from "vitest";
import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
import {
isLiveAuthDrift,
isLiveBillingDrift,
isLiveProviderUnavailableDrift,
isLiveRateLimitDrift,
shouldSkipLiveProviderDrift,
} from "./live-test-provider-drift.js";
} from "./live-test-provider-drift.test-support.js";
describe("live test provider drift", () => {
it("classifies provider account drift", () => {

Some files were not shown because too many files have changed in this diff Show More