chore(tooling): typecheck scripts/** with a dedicated tsgo lane (#104348)

* feat(tooling): add tsgo typecheck lane for scripts/**

* fix(scripts): burn down scripts type debt surfaced by the new lane

Typing-only except bugs the lane surfaced: gh-read timeout race,
Discord Headers spread dropping entries, undefined allowedHeadBranches
match, plugin-boundary matchAll crash. Deletes retired config keys from
fixtures/benches (prompt snapshots regenerated, config dump only) and
the orphaned non-runnable sync-moonshot-docs script. Adds full-surface
.d.mts declarations for existing .mjs boundaries.
This commit is contained in:
Peter Steinberger
2026-07-11 02:31:17 -07:00
committed by GitHub
parent 6e81c548fc
commit 2e2366b6d3
52 changed files with 1162 additions and 536 deletions
+1
View File
@@ -1348,6 +1348,7 @@ jobs:
;;
test-types)
pnpm check:test-types
pnpm tsgo:scripts
;;
*)
echo "Unsupported check task: $TASK" >&2
+1
View File
@@ -1962,6 +1962,7 @@
"tsgo:extensions:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo",
"tsgo:prod": "pnpm tsgo:core && pnpm tsgo:extensions",
"tsgo:profile": "node scripts/profile-tsgo.mjs",
"tsgo:scripts": "node scripts/run-tsgo.mjs -p tsconfig.scripts.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/scripts.tsbuildinfo",
"tsgo:test": "pnpm tsgo:core:test && pnpm tsgo:extensions:test",
"tsgo:test:extensions": "pnpm tsgo:extensions:test",
"tsgo:test:packages": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.packages.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-packages.tsbuildinfo",
+1 -1
View File
@@ -65,7 +65,7 @@ export async function checkAndroidAppI18n() {
]);
const [base, ...translations] = localeStrings;
const baseKeys = new Set(base.keys());
const problems = translations.flatMap((strings, index) => {
const problems: Array<readonly [string, string[]]> = translations.flatMap((strings, index) => {
const locale = NATIVE_I18N_LOCALES[index];
const keys = new Set(strings.keys());
const placeholderMismatches = [...base].flatMap(([key, sourceValue]) => {
+21 -19
View File
@@ -212,17 +212,16 @@ function listSetupTokenProfiles(
store: { profiles: Record<string, AuthProfileCredential> },
normalizeProviderId: (provider: string) => string,
): Array<{ id: string; token: string }> {
return Object.entries(store.profiles)
.filter(([, cred]) => {
if (cred.type !== "token") {
return false;
}
if (normalizeProviderId(cred.provider) !== "anthropic") {
return false;
}
return isSetupToken(cred.token ?? "");
})
.map(([id, cred]) => ({ id, token: cred.token ?? "" }));
return Object.entries(store.profiles).flatMap(([id, cred]) => {
if (
cred.type !== "token" ||
normalizeProviderId(cred.provider) !== "anthropic" ||
!isSetupToken(cred.token ?? "")
) {
return [];
}
return [{ id, token: cred.token ?? "" }];
});
}
function pickSetupTokenProfile(candidates: Array<{ id: string; token: string }>): {
@@ -390,15 +389,16 @@ async function startAnthropicProxy(params: { port: number; upstreamBaseUrl: stri
}
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
}
const upstreamRes = await fetch(upstreamUrl, {
const upstreamInit = {
method,
headers,
body:
method === "GET" || method === "HEAD" || requestBody.byteLength === 0
? undefined
: requestBody,
: Uint8Array.from(requestBody),
duplex: "half",
});
} as RequestInit & { duplex: "half" };
const upstreamRes = await fetch(upstreamUrl, upstreamInit);
const responseHeaders: Record<string, string> = {};
for (const [key, value] of upstreamRes.headers.entries()) {
const lower = key.toLowerCase();
@@ -926,19 +926,21 @@ async function runGatewayPrompt(prompt: string): Promise<PromptResult> {
mode: "cli",
});
const text = extractPayloadText(waitRes);
const waitStatus = typeof waitRes.status === "string" ? waitRes.status : undefined;
const waitError = typeof waitRes.error === "string" ? waitRes.error : undefined;
const logTail = await readLogTail(logPath);
const matched400 = matchesExtraUsage400(waitRes.error, logTail, JSON.stringify(waitRes));
const matched400 = matchesExtraUsage400(waitError, logTail, JSON.stringify(waitRes));
return {
prompt,
ok: waitRes.status === "ok" && !matched400,
ok: waitStatus === "ok" && !matched400,
transport: "gateway",
promptMode: GATEWAY_PROMPT_MODE,
status: waitRes.status,
status: waitStatus,
text: text || undefined,
error:
waitRes.status === "ok"
waitStatus === "ok"
? undefined
: redactForDevToolLog(waitRes.error || logTail || "agent.wait failed"),
: redactForDevToolLog(waitError || logTail || "agent.wait failed"),
matchedExtraUsage400: matched400,
capture: summarizeCapture(proxy?.getLastCapture(), prompt),
...promptProbeTmpResult(tmpDir),
+7 -2
View File
@@ -24,7 +24,12 @@ const LOCALIZED_WRAPPER_CONTRACTS: Record<string, string[]> = {
],
};
const CATALOGS = [
type AppleCatalogSpec = {
path: string;
coverage: Record<string, readonly string[]>;
};
const CATALOGS: readonly AppleCatalogSpec[] = [
{
path: "apps/ios/Resources/Localizable.xcstrings",
coverage: {
@@ -99,7 +104,7 @@ const CATALOGS = [
"apps/macos/Sources/OpenClaw/CronSettings+Rows.swift": ["Run now"],
},
},
] as const;
];
type Catalog = {
sourceLanguage?: string;
+1
View File
@@ -182,6 +182,7 @@ async function main(argv = process.argv.slice(2)): Promise<void> {
name: "Claude Opus 4.6",
api: "anthropic-messages",
provider: "anthropic",
baseUrl: "https://api.anthropic.com",
reasoning: true,
input: ["text", "image"],
cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
+2 -2
View File
@@ -2,7 +2,7 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import { pathToFileURL } from "node:url";
import {
openOpenClawAgentDatabase,
@@ -465,7 +465,7 @@ function percentile(values: number[], pct: number): number {
function runTimedQuery(
db: DatabaseSync,
query: string,
params: unknown[],
params: SQLInputValue[],
runs: number,
): TimedQuery {
const statement = db.prepare(query);
+2 -3
View File
@@ -114,13 +114,12 @@ const TEXT_BODY = "OpenClaw web_fetch direct text benchmark body.".repeat(160);
const MARKDOWN_BODY = "# Web Fetch Benchmark\n\n" + "- markdown list item\n".repeat(220);
const OFFLINE_PROVIDER_ENV_VARS = ["FIRECRAWL_API_KEY"] as const;
const lookupFn: LookupFn = async () => [{ address: "93.184.216.34", family: 4 }];
const lookupFn = (async () => [{ address: "93.184.216.34", family: 4 }]) as unknown as LookupFn;
const toolConfig: OpenClawConfig = {
tools: {
web: {
fetch: {
cacheTtlMinutes: 0,
firecrawl: { enabled: false },
},
},
},
@@ -270,7 +269,7 @@ function installMockFetch(params: { body: string; contentType: string }) {
headers: {
"content-type": params.contentType,
},
})) as typeof globalThis.fetch & { mock: object };
})) as unknown as typeof globalThis.fetch & { mock: object };
// fetchWithSsrFGuard preserves dispatcher support unless global fetch is a
// test double. The marker keeps this benchmark offline and deterministic.
fetchImpl.mock = {};
+8 -1
View File
@@ -13,6 +13,8 @@ const DOCS_PATH_RE = /^(?:docs\/|README\.md$|AGENTS\.md$|.*\.mdx?$)/u;
const APP_PATH_RE = /^(?:apps\/|Swabble\/|appcast\.xml$)/u;
const EXTENSION_PATH_RE = /^extensions\/[^/]+(?:\/|$)/u;
const CORE_PATH_RE = /^(?:src\/|ui\/|packages\/)/u;
const SCRIPTS_TYPECHECK_PATH_RE =
/^(?:scripts\/.*\.(?:[cm]?ts|[cm]?tsx)|tsconfig\.scripts\.json)$/u;
const TOOLING_PATH_RE =
/^(?:scripts\/|test\/vitest\/|\.github\/|\.vscode\/|config\/|deploy\/|git-hooks\/|Dockerfile\.sandbox(?:-(?:browser|common))?$|Makefile$|docker-setup\.sh$|setup-podman\.sh$|openclaw\.podman\.env$|skills\/pyproject\.toml$|vitest(?:\..+)?\.config\.ts$|tsconfig.*\.json$|\.dockerignore$|\.gitignore$|\.jscpd\.json$|\.npmignore$|\.pre-commit-config\.yaml$|\.swiftformat$|\.swiftlint\.yml$|\.oxlint.*|\.oxfmt.*)/u;
const ROOT_GLOBAL_PATH_RE =
@@ -54,7 +56,7 @@ export const RELEASE_METADATA_PATHS = new Set([
"package.json",
]);
/** @typedef {"core" | "coreTests" | "extensions" | "extensionTests" | "apps" | "docs" | "tooling" | "liveDockerTooling" | "releaseMetadata" | "all"} ChangedLane */
/** @typedef {"core" | "coreTests" | "extensions" | "extensionTests" | "scripts" | "apps" | "docs" | "tooling" | "liveDockerTooling" | "releaseMetadata" | "all"} ChangedLane */
/**
* @typedef {{
@@ -85,6 +87,7 @@ export function createEmptyChangedLanes() {
coreTests: false,
extensions: false,
extensionTests: false,
scripts: false,
apps: false,
docs: false,
tooling: false,
@@ -139,6 +142,10 @@ export function detectChangedLanes(changedPaths, options = {}) {
}
for (const changedPath of paths) {
if (SCRIPTS_TYPECHECK_PATH_RE.test(changedPath)) {
lanes.scripts = true;
}
if (DOCS_PATH_RE.test(changedPath)) {
lanes.docs = true;
continue;
+3
View File
@@ -391,6 +391,9 @@ export function createChangedCheckPlan(result, options = {}) {
if (lanes.extensionTests) {
addTypecheck("typecheck extension tests", ["tsgo:extensions:test"]);
}
if (lanes.scripts) {
addTypecheck("typecheck scripts", ["tsgo:scripts"]);
}
if (lanes.core || lanes.coreTests) {
const coreLintCommand = createTargetedCoreLintCommand(result.paths, baseEnv);
+15
View File
@@ -0,0 +1,15 @@
import type fs from "node:fs";
type CliBootstrapCheckParams = {
rootDir?: string;
entrypoints?: string[];
distDir?: string;
gatewayRunChunkMaxBytes?: number;
fs?: typeof fs;
logger?: { error(message: string): void };
};
export function listStaticImportSpecifiers(source: string): string[];
export function collectCliBootstrapExternalImportErrors(params?: CliBootstrapCheckParams): string[];
export function collectGatewayRunChunkBudgetErrors(params?: CliBootstrapCheckParams): string[];
export function checkCliBootstrapExternalImports(params?: CliBootstrapCheckParams): void;
+6 -6
View File
@@ -114,12 +114,12 @@ export async function main(argv = process.argv.slice(2)) {
{
name: "typecheck",
parallel: false,
commands: [
{
name: args.includeTestTypes ? "typecheck all" : "typecheck prod",
args: [args.includeTestTypes ? "tsgo:all" : "tsgo:prod"],
},
],
commands: args.includeTestTypes
? [{ name: "typecheck all", args: ["tsgo:all"] }]
: [
{ name: "typecheck prod", args: ["tsgo:prod"] },
{ name: "typecheck scripts", args: ["tsgo:scripts"] },
],
},
{
name: "lint",
+2 -2
View File
@@ -324,8 +324,8 @@ async function main(): Promise<void> {
},
);
let childStdout = Buffer.alloc(0);
let childStderr = Buffer.alloc(0);
let childStdout: Buffer = Buffer.alloc(0);
let childStderr: Buffer = Buffer.alloc(0);
let restored = false;
let mirrorOffset = 0;
let mirrorFilterPending = "";
+2
View File
@@ -0,0 +1,2 @@
export function readPositiveIntEnv(name: string, fallback: number, env?: NodeJS.ProcessEnv): number;
export function readTcpPortEnv(name: string, fallback: number, env?: NodeJS.ProcessEnv): number;
+52
View File
@@ -0,0 +1,52 @@
import type { spawnSync, SpawnSyncOptions } from "node:child_process";
import type { existsSync } from "node:fs";
import type { resolvePnpmRunner } from "./pnpm-runner.mjs";
type Getuid = typeof process.getuid;
type ChromiumInstallOptions = {
comSpec?: string;
cwd?: string;
env?: NodeJS.ProcessEnv;
executablePath?: string;
existsSync?: typeof existsSync;
getuid?: Getuid;
log?: (message: string) => void;
platform?: NodeJS.Platform;
spawnSync?: typeof spawnSync;
stdio?: SpawnSyncOptions["stdio"];
};
export const systemChromiumExecutableCandidates: readonly string[];
export function canRunChromiumExecutable(
executablePath: string,
spawnSync?: typeof spawnSync,
): boolean;
export function resolveSystemChromiumExecutablePath(
existsSync?: typeof existsSync,
spawnSync?: typeof spawnSync,
): string;
export function resolvePlaywrightInstallRunner(options?: {
comSpec?: string;
env?: NodeJS.ProcessEnv;
platform?: NodeJS.Platform;
targets?: string[];
withDeps?: boolean;
}): ReturnType<typeof resolvePnpmRunner>;
export function shouldInstallPlaywrightSystemDependencies(options?: {
env?: NodeJS.ProcessEnv;
getuid?: Getuid;
platform?: NodeJS.Platform;
}): boolean;
export function installLinuxSystemChromiumPackage(options?: ChromiumInstallOptions): number;
export function isDirectScriptExecution(
argvEntry?: string,
modulePath?: string,
realpath?: (path: string) => string,
): boolean;
export function ensurePlaywrightChromium(
options?: ChromiumInstallOptions & {
ensureFfmpeg?: boolean;
systemExecutablePath?: string;
},
): number;
export function shouldEnsureFfmpegFromArgv(argv?: readonly string[]): boolean;
+1 -1
View File
@@ -191,7 +191,7 @@ async function withGitHubFetchTimeout<T>(
}, timeoutMs);
});
try {
return await Promise.race([run(controller.signal), timeoutPromise]);
return await Promise.race([run(controller.signal, timeoutPromise), timeoutPromise]);
} finally {
if (timeout) {
clearTimeout(timeout);
@@ -0,0 +1,94 @@
export const ACTIONS_ARTIFACT_API_VERSION: "2026-03-10";
export const DEFAULT_MAX_ACTIONS_ARTIFACT_BYTES: number;
export const DEFAULT_MAX_ACTIONS_ARTIFACT_EXPANDED_BYTES: number;
export type ArtifactArchivePolicy = {
expectedEntries?: readonly string[];
minEntries?: number;
maxEntries?: number;
maxArchiveBytes?: number;
maxExpandedBytes?: number;
rejectCaseFoldAliases?: boolean;
allowPath?: (name: string) => boolean;
maxCompressedEntryBytes?: (name: string) => number;
maxEntryBytes: (name: string) => number;
};
export type ArtifactBinding = {
artifactDigest: string;
artifactId: number;
artifactName: string;
artifactSizeBytes: number;
repository: string;
runStatePolicy: "completed-success" | "same-run-producer-success";
runAttempt: number;
runId: number;
workflowEvent: string;
workflowHeadBranch: string;
workflowPath: string;
workflowSha: string;
consumerRunAttempt?: number;
producerJobName?: string;
};
export type ArtifactFileDescription = {
path: string;
sha256: string;
sizeBytes: number;
};
type ArtifactDownloadParams = {
expected: ArtifactBinding;
fetchImpl?: typeof fetch;
maxArchiveBytes?: number;
retryAttempts?: number;
retryDelayMs?: number;
timeoutMs?: number;
token: string;
};
type ArtifactDownloadResult = {
archiveBytes: Uint8Array;
artifactMetadata: Record<string, unknown>;
binding: ArtifactBinding;
workflowJobs: Record<string, unknown> | undefined;
workflowRun: Record<string, unknown>;
};
export function sha256Digest(bytes: Uint8Array): string;
export function describeActionsArtifactFiles(
files: Map<string, Uint8Array>,
): ArtifactFileDescription[];
export function readBoundedRegularFile(
path: string,
params: { label: string; maxBytes: number },
): Buffer;
export function inspectActionsArtifactZipWithPolicy(
inputBytes: Uint8Array,
inputPolicy: ArtifactArchivePolicy,
): Map<string, Buffer>;
export function inspectActionsArtifactZip(
bytes: Uint8Array,
expectedEntries?: number | readonly string[],
limits?: {
maxArchiveBytes?: number;
maxExpandedBytes?: number;
maxCompressedEntryBytes?: number;
maxEntryBytes?: number;
},
): Map<string, Buffer>;
export function validateActionsArtifactBinding(params: {
artifactMetadata: unknown;
expected: ArtifactBinding;
workflowRun: unknown;
}): ArtifactBinding;
export function validateActionsArtifactProducerJob(params: {
expected: ArtifactBinding;
workflowJobs: unknown;
}): ArtifactBinding;
export function downloadActionsArtifactArchive(
params: ArtifactDownloadParams,
): Promise<ArtifactDownloadResult>;
export function readPublicationArtifactArchive(
params: ArtifactDownloadParams & { archivePolicy: ArtifactArchivePolicy },
): Promise<ArtifactDownloadResult & { files: Map<string, Buffer> }>;
+51
View File
@@ -0,0 +1,51 @@
type FlagArgs = Record<string, unknown>;
type FlagSpec<T extends FlagArgs> = {
consume(
argv: readonly string[],
index: number,
args: T,
): {
flag: string;
nextIndex: number;
repeatable: boolean;
apply(target: T): void;
} | null;
};
export function readFlagValue(args: readonly string[], name: string): string | undefined;
export function stripLeadingPackageManagerSeparator(argv: string[]): string[];
export function stringFlag<T extends FlagArgs>(
flag: string,
key: string,
options?: { rejectShortOptions?: boolean },
): FlagSpec<T>;
export function stringListFlag<T extends FlagArgs>(
flag: string,
key: string,
options?: { rejectShortOptions?: boolean },
): FlagSpec<T>;
export function intFlag<T extends FlagArgs>(
flag: string,
key: string,
options?: { min?: number },
): FlagSpec<T>;
export function floatFlag<T extends FlagArgs>(
flag: string,
key: string,
options?: { includeMin?: boolean; min?: number },
): FlagSpec<T>;
export function booleanFlag<T extends FlagArgs>(
flag: string,
key: string,
value?: unknown,
): FlagSpec<T>;
export function parseFlagArgs<T extends FlagArgs>(
argv: readonly string[],
args: T,
specs: readonly FlagSpec<T>[],
options?: {
allowUnknownOptions?: boolean;
ignoreDoubleDash?: boolean;
onUnhandledArg?: (arg: string, args: T) => "handled" | void;
},
): T;
+11
View File
@@ -0,0 +1,11 @@
export const BUNDLED_PLUGIN_ROOT_DIR: "extensions";
export const BUNDLED_PLUGIN_PATH_PREFIX: "extensions/";
export const BUNDLED_PLUGIN_TEST_GLOB: "extensions/**/*.test.ts";
export const BUNDLED_PLUGIN_E2E_TEST_GLOB: "extensions/**/*.e2e.test.ts";
export const BUNDLED_PLUGIN_LIVE_TEST_GLOB: "extensions/**/*.live.test.ts";
export function bundledPluginRoot(pluginId: string): string;
export function bundledPluginFile(pluginId: string, relativePath: string): string;
export function bundledDistPluginRoot(pluginId: string): string;
export function bundledDistPluginFile(pluginId: string, relativePath: string): string;
export function bundledPluginCallsite(pluginId: string, relativePath: string, line: number): string;
+55
View File
@@ -0,0 +1,55 @@
export type ParsedReleaseVersion = {
version: string;
baseVersion: string;
channel: "stable" | "alpha" | "beta";
year: number;
month: number;
patch: number;
alphaNumber?: number;
betaNumber?: number;
correctionNumber?: number;
};
export type NpmPublishPlan = {
channel: "stable" | "alpha" | "beta";
publishTag: "latest" | "alpha" | "beta" | "extended-stable";
mirrorDistTags: Array<"latest" | "alpha" | "beta">;
};
export type PublishedNpmVersionRoute = "npm-readback" | "npm-mirror" | "npm-tag-repair";
export type NpmRegistryPackumentResult = {
status: number;
ok: boolean;
packument: unknown;
};
export function fetchNpmRegistryPackumentWithRetry(params: {
packageName: string;
packageUrl: string;
attempts?: number;
timeoutMs?: number;
fetchImpl?: (input: string, init: RequestInit) => Promise<Response>;
sleep?: (delayMs: number) => Promise<void>;
createSignal?: (timeoutMs: number) => AbortSignal;
}): Promise<NpmRegistryPackumentResult>;
export function parseReleaseVersion(version: string): ParsedReleaseVersion | null;
export function collectReleaseVersionFloorErrors(
version: string | ParsedReleaseVersion | null,
): string[];
export function compareReleaseVersions(left: string, right: string): number | null;
export function resolveNpmPublishPlan(
version: string,
currentBetaVersion?: string | null,
publishTagOverride?: string | null,
): NpmPublishPlan;
export function resolvePublishedNpmVersionRoute(params: {
packageVersion: string;
publishPlan: NpmPublishPlan;
distTags: Record<string, unknown>;
}): PublishedNpmVersionRoute;
export function resolveNpmDistTagMirrorAuth(params?: {
nodeAuthToken?: string | null;
npmToken?: string | null;
}): { hasAuth: boolean; source: "node-auth-token" | "npm-token" | "none" };
export function shouldRequireNpmDistTagMirrorAuth(params: {
mode: "--dry-run" | "--publish";
mirrorDistTags: readonly string[];
hasAuth: boolean;
}): boolean;
+9 -4
View File
@@ -1,12 +1,16 @@
// Npm Verify Exec script supports OpenClaw repository automation.
import { execFileSync } from "node:child_process";
import { execFileSync, type ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
type NpmVerifyCommandInvocation = {
export type NpmVerifyCommandInvocation = {
command: string;
args: string[];
windowsVerbatimArguments?: boolean;
};
type NpmVerifyExecOptions = ExecFileSyncOptionsWithStringEncoding & {
windowsVerbatimArguments?: boolean;
};
const DEFAULT_NPM_VERIFY_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
const DEFAULT_NPM_VERIFY_COMMAND_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
@@ -40,7 +44,7 @@ export function runNpmVerifyCommand(
DEFAULT_NPM_VERIFY_COMMAND_MAX_BUFFER_BYTES,
);
return execFileSync(invocation.command, invocation.args, {
const execOptions: NpmVerifyExecOptions = {
cwd,
encoding: "utf8",
killSignal: "SIGKILL",
@@ -48,5 +52,6 @@ export function runNpmVerifyCommand(
stdio: ["ignore", "pipe", "pipe"],
timeout: timeoutMs,
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
}).trim();
};
return execFileSync(invocation.command, invocation.args, execOptions).trim();
}
+3
View File
@@ -0,0 +1,3 @@
export function parsePositiveInt(raw: string, label: string): number;
export function parseNonNegativeInt(raw: string, label: string): number;
export function parsePositiveNumber(raw: string, label: string): number;
+1 -1
View File
@@ -58,7 +58,7 @@ export type PublishablePluginPackage = {
packageName: string;
version: string;
channel: "stable" | "alpha" | "beta";
publishTag: "latest" | "alpha" | "beta";
publishTag: "latest" | "alpha" | "beta" | "extended-stable";
requiredLatestDependencies?: RequiredLatestDependency[];
};
+1
View File
@@ -40,6 +40,7 @@ export type PluginPackageJson = {
pluginSdkVersion?: string;
};
release?: {
publishToClawHub?: boolean;
publishToNpm?: boolean;
requireLatestDependencies?: unknown;
};
@@ -0,0 +1,14 @@
export type RuntimeDependencyPackageJson = {
dependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
};
export function collectRuntimeDependencySpecs(
packageJson?: RuntimeDependencyPackageJson,
): Map<string, string>;
export function packageNameFromSpecifier(specifier: string): string | null;
export function collectBundledPluginPackageDependencySpecs(
bundledPluginsDir: string,
): Map<
string,
{ conflicts: Array<{ pluginId: string; spec: string }>; pluginIds: string[]; spec: string }
>;
+11 -3
View File
@@ -843,7 +843,12 @@ function validateBootstrapPackageEvidence(
}
const expectedSize = value.expectedSize;
const registrySize = value.registrySize;
if (!Number.isSafeInteger(expectedSize) || expectedSize <= 0 || registrySize !== expectedSize) {
if (
typeof expectedSize !== "number" ||
!Number.isSafeInteger(expectedSize) ||
expectedSize <= 0 ||
registrySize !== expectedSize
) {
throw new Error(
`${params.packageName} registry artifact size differs from the packed artifact.`,
);
@@ -1316,6 +1321,9 @@ export async function verifyBetaRelease(
}
const workflowRuns: WorkflowRunSummary[] = [];
const allowedReleaseWorkflowHeadBranches = args.workflowRef
? ["main", args.workflowRef]
: ["main"];
if (args.workflowRuns.fullReleaseValidation !== undefined) {
workflowRuns.push(
verifyWorkflowRun({
@@ -1323,7 +1331,7 @@ export async function verifyBetaRelease(
label: "Full Release Validation",
repo: args.repo,
expectedWorkflowName: "Full Release Validation",
allowedHeadBranches: ["main", args.workflowRef],
allowedHeadBranches: allowedReleaseWorkflowHeadBranches,
rerunFailed: false,
}),
);
@@ -1383,7 +1391,7 @@ export async function verifyBetaRelease(
label: "NPM Telegram Beta E2E",
repo: args.repo,
expectedWorkflowName: "NPM Telegram Beta E2E",
allowedHeadBranches: ["main", args.workflowRef],
allowedHeadBranches: allowedReleaseWorkflowHeadBranches,
rerunFailed: false,
}),
);
@@ -0,0 +1,7 @@
export const WORKSPACE_TEMPLATE_PACK_PATHS: readonly string[];
export function createWorkspaceBootstrapSmokeEnv(
env: NodeJS.ProcessEnv,
homeDir: string,
overrides?: NodeJS.ProcessEnv,
): NodeJS.ProcessEnv;
export function runInstalledWorkspaceBootstrapSmoke(params: { packageRoot: string }): void;
+1 -1
View File
@@ -659,7 +659,7 @@ function extractCandidates(
uiCallNames: ReadonlySet<string>,
): Candidate[] {
const entries: Candidate[] = [];
const patterns =
const patterns: Array<readonly [RegExp, string]> =
surface === "apple"
? [
[APPLE_UI_MULTILINE_CALLS, "ui-call-multiline"],
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -328,7 +328,7 @@ export function runNpmReleaseCheckCommand(
},
): string {
const env = options.env ?? process.env;
const output = execFileSync(invocation.command, invocation.args, {
const execOptions = {
cwd: options.cwd,
encoding: options.encoding,
env,
@@ -337,7 +337,11 @@ export function runNpmReleaseCheckCommand(
stdio: options.stdio,
timeout: options.timeoutMs ?? resolveNpmReleaseCheckCommandTimeoutMs(env),
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
}) as Buffer | string | null;
} as Parameters<typeof execFileSync>[2] & { windowsVerbatimArguments?: boolean };
const output = execFileSync(invocation.command, invocation.args, execOptions) as
| Buffer
| string
| null;
if (output == null) {
return "";
}
+16
View File
@@ -0,0 +1,16 @@
type PackageChangelogOptions = { allowUnreleased?: boolean };
export function resolvePackageChangelogVersions(
packageVersion: string,
options?: PackageChangelogOptions,
): string[];
export function extractCurrentPackageChangelog(
content: string,
packageVersion: string,
options?: PackageChangelogOptions,
): string;
export function restorePackageChangelog(cwd?: string): Promise<boolean>;
export function preparePackageChangelog(
cwd?: string,
options?: PackageChangelogOptions,
): Promise<boolean>;
@@ -151,18 +151,6 @@ function buildConfig(options: Options, workspaceDir: string): OpenClawConfig {
controlUi: { enabled: false },
mode: "local",
},
memory: {
active: {
allowedChatTypes: ["direct"],
agents: ["main"],
logging: false,
maxSummaryChars: 220,
persistTranscripts: false,
promptStyle: "balanced",
queryMode: "recent",
timeoutMs: 15_000,
},
},
models: {
mode: "replace",
providers,
+4 -1
View File
@@ -322,6 +322,9 @@ function extractCompatTokens(record: PluginCompatRecord): string[] {
const tokens = new Set<string>();
const values = [record.code, record.replacement, ...record.surfaces, ...record.diagnostics];
for (const value of values) {
if (value === undefined) {
continue;
}
for (const match of value.matchAll(/`([^`]+)`/g)) {
const token = match[1]?.trim();
if (token && !token.includes(" ")) {
@@ -523,7 +526,7 @@ function buildSummary(report: BoundaryReport, owner?: string): BoundaryReportSum
};
}
function buildReport(options: Pick<CliOptions, "owner" | "summary"> = {}): BoundaryReport {
function buildReport(options: Partial<Pick<CliOptions, "owner" | "summary">> = {}): BoundaryReport {
const files = options.summary
? collectSummaryWorkspaceTextFileSources()
: collectWorkspaceTextFileSources();
+3 -5
View File
@@ -521,11 +521,9 @@ function emitDiscriminatedUnion(name: string, schema: JsonSchema): string | unde
if (cases.some((entry) => !entry)) {
continue;
}
const resolvedCases: Array<{
branchName: string;
caseName: string;
literal: boolean | number | string | null;
}> = cases;
const resolvedCases = cases.filter(
(entry): entry is NonNullable<typeof entry> => entry !== undefined,
);
const [firstCase] = resolvedCases;
if (!firstCase) {
continue;
+6 -2
View File
@@ -703,7 +703,10 @@ async function runUxMatrixEvidenceProducer(options: ProducerOptions) {
: []),
],
coverageIds: ["ui.control", "gateway.control-ui-hosting"],
failureReason: matrixScreenshotResult.failureReason,
failureReason:
"failureReason" in matrixScreenshotResult
? matrixScreenshotResult.failureReason
: undefined,
stage: "screenshot-artifact",
status: matrixScreenshotResult.status,
surface: "control-ui",
@@ -781,7 +784,8 @@ async function runUxMatrixEvidenceProducer(options: ProducerOptions) {
: []),
],
coverageIds: ["qa.artifact-safety", "tools.evidence", "workspace.artifacts"],
failureReason: fixtureProofResult.failureReason,
failureReason:
"failureReason" in fixtureProofResult ? fixtureProofResult.failureReason : undefined,
stage: "producer-artifact-fixture",
status: fixtureProofResult.status,
surface: "qa-lab",
+12 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env -S node --import tsx
// Release Check script supports OpenClaw repository automation.
import { execFileSync } from "node:child_process";
import { execFileSync, type ExecFileSyncOptions } from "node:child_process";
import {
copyFileSync,
existsSync,
@@ -56,6 +56,10 @@ import { listStaticExtensionAssetOutputs } from "./runtime-postbuild.mjs";
import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts";
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
type ReleaseCheckExecOptions = ExecFileSyncOptions & {
windowsVerbatimArguments?: boolean;
};
export { collectBundledExtensionManifestErrors } from "./lib/bundled-extension-manifest.ts";
export { packageNameFromSpecifier } from "./lib/plugin-package-dependencies.mjs";
@@ -211,7 +215,7 @@ export function runReleaseCheckCommand(
timeoutMs?: number;
},
): string {
const output = execFileSync(invocation.command, invocation.args, {
const execOptions: ReleaseCheckExecOptions = {
cwd: options.cwd,
encoding: options.encoding,
env: invocation.env ?? options.env,
@@ -231,7 +235,12 @@ export function runReleaseCheckCommand(
DEFAULT_RELEASE_CHECK_COMMAND_TIMEOUT_MS,
),
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
}) as Buffer | string | null;
};
const output: Buffer | string | null = execFileSync(
invocation.command,
invocation.args,
execOptions,
);
if (output == null) {
return "";
}
+1 -1
View File
@@ -200,7 +200,7 @@ function stringParam(params: Record<string, unknown>, key: string): string {
function makeTool(
name: string,
description: string,
properties: Record<string, unknown>,
properties: Parameters<typeof Type.Object>[0],
execute: (params: Record<string, unknown>) => unknown,
): AnyAgentTool {
const tool = {
+47
View File
@@ -0,0 +1,47 @@
import type fs from "node:fs";
export type StaticExtensionAsset = {
pluginDir?: string;
src: string;
dest: string;
};
export type RuntimePostBuildParams = {
rootDir?: string;
repoRoot?: string;
cwd?: string;
env?: NodeJS.ProcessEnv;
fs?: typeof fs;
timings?: boolean | "verbose";
warn?: (message: string) => void;
};
type StaticExtensionAssetParams = Pick<RuntimePostBuildParams, "rootDir" | "fs" | "warn"> & {
assets?: StaticExtensionAsset[];
};
type LegacyCliExitCompatChunk = { dest: string; contents: string };
export function copyStaticExtensionAssets(params?: StaticExtensionAssetParams): void;
export function listStaticExtensionAssetOutputs(params?: StaticExtensionAssetParams): string[];
export const LEGACY_CLI_EXIT_COMPAT_CHUNKS: LegacyCliExitCompatChunk[];
export function listCoreRuntimePostBuildOutputs(
params?: Pick<RuntimePostBuildParams, "rootDir" | "fs"> & {
chunks?: LegacyCliExitCompatChunk[];
},
): string[];
export function writeStableRootRuntimeAliases(
params?: Pick<RuntimePostBuildParams, "rootDir" | "fs">,
): void;
export function rewriteRootRuntimeImportsToStableAliases(
params?: Pick<RuntimePostBuildParams, "rootDir" | "fs">,
): void;
export function writeLegacyRootRuntimeCompatAliases(
params?: Pick<RuntimePostBuildParams, "rootDir" | "fs">,
): void;
export function writeLegacyCliExitCompatChunks(params?: {
rootDir?: string;
chunks?: LegacyCliExitCompatChunk[];
}): void;
export function runRuntimePostBuild(params?: RuntimePostBuildParams): void;
-126
View File
@@ -1,126 +0,0 @@
// Sync Moonshot Docs script supports OpenClaw repository automation.
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
MOONSHOT_KIMI_K2_CONTEXT_WINDOW,
MOONSHOT_KIMI_K2_COST,
MOONSHOT_KIMI_K2_INPUT,
MOONSHOT_KIMI_K2_MAX_TOKENS,
MOONSHOT_KIMI_K2_MODELS,
} from "../ui/src/ui/data/moonshot-kimi-k2";
const here = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(here, "..");
function replaceBlockLines(
text: string,
startMarker: string,
endMarker: string,
lines: string[],
): string {
const startIndex = text.indexOf(startMarker);
if (startIndex === -1) {
throw new Error(`Missing start marker: ${startMarker}`);
}
const endIndex = text.indexOf(endMarker, startIndex);
if (endIndex === -1) {
throw new Error(`Missing end marker: ${endMarker}`);
}
const startLineStart = text.lastIndexOf("\n", startIndex);
const startLineStartIndex = startLineStart === -1 ? 0 : startLineStart + 1;
const indent = text.slice(startLineStartIndex, startIndex);
const endLineEnd = text.indexOf("\n", endIndex);
const endLineEndIndex = endLineEnd === -1 ? text.length : endLineEnd + 1;
const before = text.slice(0, startLineStartIndex);
const after = text.slice(endLineEndIndex);
const replacementLines = [
`${indent}${startMarker}`,
...lines.map((line) => `${indent}${line}`),
`${indent}${endMarker}`,
];
const replacement = replacementLines.join("\n");
if (!after) {
return `${before}${replacement}`;
}
return `${before}${replacement}\n${after}`;
}
function renderKimiK2Ids(prefix: string) {
return [...MOONSHOT_KIMI_K2_MODELS.map((model) => `- \`${prefix}${model.id}\``), ""];
}
function renderMoonshotAliases() {
return MOONSHOT_KIMI_K2_MODELS.map((model, index) => {
const isLast = index === MOONSHOT_KIMI_K2_MODELS.length - 1;
const suffix = isLast ? "" : ",";
return `"moonshot/${model.id}": { alias: "${model.alias}" }${suffix}`;
});
}
function renderMoonshotModels() {
const input = JSON.stringify([...MOONSHOT_KIMI_K2_INPUT]);
const cost = `input: ${MOONSHOT_KIMI_K2_COST.input}, output: ${MOONSHOT_KIMI_K2_COST.output}, cacheRead: ${MOONSHOT_KIMI_K2_COST.cacheRead}, cacheWrite: ${MOONSHOT_KIMI_K2_COST.cacheWrite}`;
return MOONSHOT_KIMI_K2_MODELS.flatMap((model, index) => {
const isLast = index === MOONSHOT_KIMI_K2_MODELS.length - 1;
const closing = isLast ? "}" : "},";
return [
"{",
` id: "${model.id}",`,
` name: "${model.name}",`,
` reasoning: ${model.reasoning},`,
` input: ${input},`,
` cost: { ${cost} },`,
` contextWindow: ${MOONSHOT_KIMI_K2_CONTEXT_WINDOW},`,
` maxTokens: ${MOONSHOT_KIMI_K2_MAX_TOKENS}`,
closing,
];
});
}
async function syncMoonshotDocs() {
const moonshotDoc = path.join(repoRoot, "docs/providers/moonshot.md");
const conceptsDoc = path.join(repoRoot, "docs/concepts/model-providers.md");
let moonshotText = await readFile(moonshotDoc, "utf8");
moonshotText = replaceBlockLines(
moonshotText,
'[//]: # "moonshot-kimi-k2-ids:start"',
'[//]: # "moonshot-kimi-k2-ids:end"',
renderKimiK2Ids(""),
);
moonshotText = replaceBlockLines(
moonshotText,
"// moonshot-kimi-k2-aliases:start",
"// moonshot-kimi-k2-aliases:end",
renderMoonshotAliases(),
);
moonshotText = replaceBlockLines(
moonshotText,
"// moonshot-kimi-k2-models:start",
"// moonshot-kimi-k2-models:end",
renderMoonshotModels(),
);
let conceptsText = await readFile(conceptsDoc, "utf8");
conceptsText = replaceBlockLines(
conceptsText,
'[//]: # "moonshot-kimi-k2-model-refs:start"',
'[//]: # "moonshot-kimi-k2-model-refs:end"',
renderKimiK2Ids("moonshot/"),
);
await writeFile(moonshotDoc, moonshotText);
await writeFile(conceptsDoc, conceptsText);
}
syncMoonshotDocs().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
+1 -1
View File
@@ -165,7 +165,7 @@ function envKeysFromObjectLiteral(node: ts.Expression): string[] {
}
return node.properties
.map((property) => (ts.isPropertyAssignment(property) ? propertyNameText(property.name) : null))
.filter((key): key is string => Boolean(key) && TRACKED_ENV_KEYS.has(key));
.filter((key): key is string => key !== null && TRACKED_ENV_KEYS.has(key));
}
function isAssignmentOperator(kind: ts.SyntaxKind): boolean {
+4
View File
@@ -708,6 +708,10 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
],
["scripts/clawtributors-map.json", ["test/scripts/update-clawtributors.test.ts"]],
["scripts/tsconfig.json", ["test/scripts/oxlint-config.test.ts"]],
[
"tsconfig.scripts.json",
["test/scripts/changed-lanes.test.ts", "test/scripts/test-projects.test.ts"],
],
["scripts/build-all.mjs", ["test/scripts/build-all.test.ts"]],
["scripts/build-stamp.mjs", ["src/infra/build-stamp.test.ts"]],
["scripts/crabbox-wrapper-providers.mjs", ["test/scripts/crabbox-wrapper.test.ts"]],
+3 -1
View File
@@ -2,7 +2,9 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { TOOL_DISPLAY_CONFIG, type ToolDisplayConfig } from "../src/agents/tool-display-config.js";
import { TOOL_DISPLAY_CONFIG } from "../src/agents/tool-display-config.js";
type ToolDisplayConfig = typeof TOOL_DISPLAY_CONFIG;
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, "..");
-3
View File
@@ -343,9 +343,6 @@ function createIsolatedRootHelpRenderContext(
workspace: workspaceDir,
},
},
plugins: {
loadPaths: [],
},
};
return { config, env };
}
@@ -41,7 +41,6 @@
"agents": {
"defaults": {
"heartbeat": {
"enabled": true,
"every": "30m"
}
}
@@ -50,23 +49,6 @@
"groupChat": {
"visibleReplies": "message_tool"
}
},
"tools": {
"profiles": {
"coding": {
"allow": [
"message",
"heartbeat_respond",
"sessions_spawn",
"sessions_list",
"sessions_yield",
"cron",
"memory_search",
"memory_get",
"session_status"
]
}
}
}
}
```
@@ -41,7 +41,6 @@
"agents": {
"defaults": {
"heartbeat": {
"enabled": true,
"every": "30m"
}
}
@@ -50,23 +49,6 @@
"groupChat": {
"visibleReplies": "message_tool"
}
},
"tools": {
"profiles": {
"coding": {
"allow": [
"message",
"heartbeat_respond",
"sessions_spawn",
"sessions_list",
"sessions_yield",
"cron",
"memory_search",
"memory_get",
"session_status"
]
}
}
}
}
```
@@ -41,7 +41,6 @@
"agents": {
"defaults": {
"heartbeat": {
"enabled": true,
"every": "30m"
}
}
@@ -50,23 +49,6 @@
"groupChat": {
"visibleReplies": "message_tool"
}
},
"tools": {
"profiles": {
"coding": {
"allow": [
"message",
"heartbeat_respond",
"sessions_spawn",
"sessions_list",
"sessions_yield",
"cron",
"memory_search",
"memory_get",
"session_status"
]
}
}
}
}
```
@@ -107,7 +107,10 @@ type CodexPromptSnapshotApi = {
developerInstructions: string;
threadStartParams: Record<string, unknown>;
threadResumeParams: Record<string, unknown>;
turnStartParams: Record<string, unknown>;
turnStartParams: Record<string, unknown> & {
input?: unknown;
collaborationMode?: { settings?: { developer_instructions?: string } };
};
};
createCodexDynamicToolSpecsForPromptSnapshot: (params: {
tools: AnyAgentTool[];
@@ -249,28 +252,10 @@ const baseConfig: OpenClawConfig = {
agents: {
defaults: {
heartbeat: {
enabled: true,
every: "30m",
},
},
},
tools: {
profiles: {
coding: {
allow: [
"message",
"heartbeat_respond",
"sessions_spawn",
"sessions_list",
"sessions_yield",
"cron",
"memory_search",
"memory_get",
"session_status",
],
},
},
},
};
const dynamicToolsConfig: OpenClawConfig = {
+14
View File
@@ -598,6 +598,19 @@ describe("scripts/changed-lanes", () => {
});
});
it.each([
"scripts/control-ui-i18n.ts",
"scripts/lib/example.ts",
"scripts/lib/example.d.mts",
"tsconfig.scripts.json",
])("routes %s to the scripts typecheck lane", (changedPath) => {
const result = detectChangedLanes([changedPath]);
const plan = createChangedCheckPlan(result);
expect(result.lanes.scripts).toBe(true);
expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:scripts");
});
it("falls back to full core lint for broad core diffs", () => {
const targets = Array.from({ length: 9 }, (_, index) => `src/shared/file-${index}.ts`);
const command = createTargetedCoreLintCommand(targets, { PATH: "/usr/bin" });
@@ -1818,6 +1831,7 @@ describe("scripts/changed-lanes", () => {
coreTests: false,
extensions: false,
extensionTests: false,
scripts: false,
apps: false,
docs: false,
tooling: false,
@@ -869,7 +869,9 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
const topLevelImports = source.slice(0, source.indexOf("const SCRIPT_PATH"));
expect(topLevelImports).not.toContain("package-dist-inventory");
expect(source).toContain("function assertNoLegacyPluginDependencyStagingDebris(packageRoot)");
expect(source).toMatch(
/function assertNoLegacyPluginDependencyStagingDebris\(packageRoot: string\)/u,
);
});
it("filters the cross-OS runner matrix to a focused OS suite", () => {
@@ -1724,11 +1726,10 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
expect(init).toMatchObject({
method: "POST",
body: "{}",
headers: {
Authorization: "Bot discord-token",
"Content-Type": "application/json",
},
});
const headers = new Headers(init.headers);
expect(headers.get("Authorization")).toBe("Bot discord-token");
expect(headers.get("Content-Type")).toBe("application/json");
expect(init.signal).toBeInstanceOf(AbortSignal);
});
+7
View File
@@ -1197,6 +1197,13 @@ describe("scripts/test-projects changed-target routing", () => {
});
});
it("keeps the scripts typecheck project on its routing tests", () => {
expect(resolveChangedTestTargetPlan(["tsconfig.scripts.json"])).toEqual({
mode: "targets",
targets: ["test/scripts/changed-lanes.test.ts", "test/scripts/test-projects.test.ts"],
});
});
it("keeps docs i18n behavior fixture edits on behavior baseline tests", () => {
for (const fixturePath of [
"scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/case.json",
+2 -1
View File
@@ -2,6 +2,7 @@
"files": [],
"references": [
{ "path": "./tsconfig.core.projects.json" },
{ "path": "./tsconfig.extensions.projects.json" }
{ "path": "./tsconfig.extensions.projects.json" },
{ "path": "./tsconfig.scripts.json" }
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"tsBuildInfoFile": ".artifacts/tsgo-cache/scripts.tsbuildinfo"
},
"include": ["scripts/**/*", "src/**/*.d.ts", "packages/**/*.d.ts"],
"exclude": [
"node_modules",
"dist",
"**/dist/**",
// E2E clients import built dist artifacts and are validated by Docker runs,
// so they cannot join this source-only program.
"scripts/e2e/**"
]
}