fix(ci): restore current-main quality gates (#106751)

* fix(ci): restore Control UI quality gates

* fix(ci): repair dead export cleanup fallout

* fix(ci): repair latest main quality gates

* fix(ci): satisfy current-main gate contracts

* docs: format Cloud Workers guide

* test: isolate context lookup module
This commit is contained in:
Peter Steinberger
2026-07-13 13:54:03 -07:00
committed by GitHub
parent b086cb3110
commit 995c75baa0
19 changed files with 95 additions and 74 deletions
+13 -13
View File
@@ -16,13 +16,13 @@ Cloud workers are opt-in and invisible until you configure a profile. Unconfigur
## What runs where
| Concern | Location |
| --- | --- |
| Agent loop + tools (`exec`, `read`, `write`, `edit`, …) | Cloud worker box |
| Model inference and provider credentials | Gateway (proxied by `{provider, model}` reference) |
| Transcript (durable, session store) | Gateway |
| Live streaming into the sidebar | Gateway fanout, fed by the worker's replayable event stream |
| Workspace git history | Authored on the box credential-free; the Gateway adopts commits and owns push/PR |
| Concern | Location |
| ------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Agent loop + tools (`exec`, `read`, `write`, `edit`, …) | Cloud worker box |
| Model inference and provider credentials | Gateway (proxied by `{provider, model}` reference) |
| Transcript (durable, session store) | Gateway |
| Live streaming into the sidebar | Gateway fanout, fed by the worker's replayable event stream |
| Workspace git history | Authored on the box credential-free; the Gateway adopts commits and owns push/PR |
The box needs no inbound ports except `sshd` and no egress beyond what your setup command uses: the Gateway connects out via SSH and a reverse tunnel carries the worker's WebSocket back. No Tailscale or VPN required.
@@ -58,12 +58,12 @@ Add a profile under `cloudWorkers.profiles` in `openclaw.json`:
Profile fields:
| Key | Meaning |
| --- | --- |
| `provider` | Worker provider id registered by a plugin (`crabbox` for the bundled plugin). |
| `install` | `bundle` (default) ships the running Gateway's build; `npm` installs the exact released Gateway version with pinned integrity. `npm` requires the Gateway to run from a packaged release. |
| `settings` | Provider-owned JSON. For crabbox: `provider` (backend), `class` (machine class), `ttl`, `idleTimeout` (Go durations), optional `setup` and absolute `binary` path. |
| `lifetime` | Optional stored policy (`idleTimeoutMinutes`, `maxLifetimeMinutes`). |
| Key | Meaning |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider` | Worker provider id registered by a plugin (`crabbox` for the bundled plugin). |
| `install` | `bundle` (default) ships the running Gateway's build; `npm` installs the exact released Gateway version with pinned integrity. `npm` requires the Gateway to run from a packaged release. |
| `settings` | Provider-owned JSON. For crabbox: `provider` (backend), `class` (machine class), `ttl`, `idleTimeout` (Go durations), optional `setup` and absolute `binary` path. |
| `lifetime` | Optional stored policy (`idleTimeoutMinutes`, `maxLifetimeMinutes`). |
### The setup command
+4 -4
View File
@@ -466,10 +466,10 @@ export async function startGatewayBonjourAdvertiser(
`bonjour: ${label} name conflict resolved; newName=${JSON.stringify(name)}`,
);
});
svc.on("hostname-change", (hostname) => {
svc.on("hostname-change", (nextHostname) => {
markConflictObserved(label, svc);
logger.warn(
`bonjour: ${label} hostname conflict resolved; newHostname=${JSON.stringify(hostname)}`,
`bonjour: ${label} hostname conflict resolved; newHostname=${JSON.stringify(nextHostname)}`,
);
});
} catch (err) {
@@ -542,7 +542,7 @@ export async function startGatewayBonjourAdvertiser(
const updateStateTrackers = (services: BonjourCycle) => {
const now = Date.now();
for (const { label, svc } of services) {
const nextState = svc.serviceState;
const nextState: string = svc.serviceState;
const current = stateTracker.get(label);
const nextEnteredAt =
current && current.state !== "announced" && nextState !== "announced"
@@ -629,7 +629,7 @@ export async function startGatewayBonjourAdvertiser(
updateStateTrackers(cycle);
for (const { label, svc } of cycle) {
const now = Date.now();
const state = svc.serviceState;
const state: string = svc.serviceState;
if (state === "announced") {
consecutiveRestarts = 0;
consecutiveStuckStateRestarts = 0;
@@ -493,7 +493,7 @@ async function readGoogleAuthResponseBytes(response: Response): Promise<Uint8Arr
}
export async function loadGoogleAuthRuntime(): Promise<GoogleAuthRuntime> {
googleAuthRuntimePromise ??= import("google-auth-library").catch((error) => {
googleAuthRuntimePromise ??= import("google-auth-library").catch((error: unknown) => {
googleAuthRuntimePromise = null;
throw error;
});
+2
View File
@@ -39,6 +39,7 @@ const rawSqliteAllowPathGroups = {
"src/infra/sqlite-wal.ts",
"src/state/openclaw-agent-db-session-migrations.ts",
"src/state/openclaw-agent-db.ts",
"src/state/openclaw-state-db-schema-helpers.ts",
"src/state/openclaw-state-db.ts",
"src/state/sqlite-schema-shape.test-support.ts",
],
@@ -64,6 +65,7 @@ const rawSqliteAllowPathGroups = {
"src/infra/state-migrations.storage.ts",
"src/infra/state-migrations.cron-run-logs.ts",
"src/infra/state-migrations.debug-proxy.ts",
"src/infra/state-migrations.task-sidecar-rows.ts",
],
"shared database stores with direct DatabaseSync access": ["src/proxy-capture/store.sqlite.ts"],
"Kysely-backed stores that own a DatabaseSync boundary": [
+1 -2
View File
@@ -48,8 +48,7 @@ beforeAll(async () => {
prepareGatewaySuspend,
resetGatewaySuspendCoordinatorForLifecycleRestart,
resumeGatewaySuspend,
} =
await import("../infra/gateway-suspend-coordinator.js"));
} = await import("../infra/gateway-suspend-coordinator.js"));
});
beforeEach(() => {
+5 -3
View File
@@ -2,7 +2,6 @@
// model resolution.
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { lookupCachedContextWindow, providerContextTokenCacheKey } from "./context-cache.js";
import { CONTEXT_WINDOW_RUNTIME_STATE } from "./context-runtime-state.js";
type DiscoveredModel = {
@@ -122,7 +121,7 @@ async function importResolveContextTokensForModel() {
describe("lookupContextTokens", () => {
beforeAll(async () => {
contextModule = await import("./context.js");
contextModule = await importFreshContextModule();
});
beforeEach(() => {
@@ -381,7 +380,10 @@ describe("lookupContextTokens", () => {
await contextModule.ensureContextWindowCacheLoaded();
expect(
lookupCachedContextWindow(providerContextTokenCacheKey("fresh-provider", "fresh-model")),
contextModule.lookupContextTokens("fresh-model", {
allowAsyncLoad: false,
skipRuntimeConfigLoad: true,
}),
).toBe(123_456);
expect(CONTEXT_WINDOW_RUNTIME_STATE.loadPromise).not.toBe(legacyLoadPromise);
expect(CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration).toBe(
+1 -5
View File
@@ -26,11 +26,7 @@ import { parseSqliteSessionFileMarker } from "../config/sessions/sqlite-marker.j
import { clearSessionStoreCacheForTest } from "../config/sessions/store.js";
import type { SessionEntry } from "../config/sessions/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
emitAgentEvent,
onAgentEvent,
resetAgentEventsForTest,
} from "../infra/agent-events.js";
import { emitAgentEvent, onAgentEvent, resetAgentEventsForTest } from "../infra/agent-events.js";
import type { PluginProviderRegistration } from "../plugins/registry.test-fixtures.js";
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";
import type { RuntimeEnv } from "../runtime.js";
@@ -3,23 +3,34 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js";
type RuntimePluginInstallResult = {
cfg: OpenClawConfig;
required: boolean;
installed: boolean;
status?: "installed" | "skipped" | "failed" | "timed_out";
};
const ensureCodexRuntimePluginForModelSelection = vi.hoisted(() =>
vi.fn(async ({ cfg }: { cfg: OpenClawConfig }) => ({
cfg,
required: false,
installed: false,
})),
vi.fn(
async ({ cfg }: { cfg: OpenClawConfig }): Promise<RuntimePluginInstallResult> => ({
cfg,
required: false,
installed: false,
}),
),
);
vi.mock("../../codex-runtime-plugin-install.js", () => ({
CODEX_RUNTIME_PLUGIN_ID: "codex",
ensureCodexRuntimePluginForModelSelection,
}));
const ensureCopilotRuntimePluginForModelSelection = vi.hoisted(() =>
vi.fn(async ({ cfg }: { cfg: OpenClawConfig }) => ({
cfg,
required: false,
installed: false,
})),
vi.fn(
async ({ cfg }: { cfg: OpenClawConfig }): Promise<RuntimePluginInstallResult> => ({
cfg,
required: false,
installed: false,
}),
),
);
vi.mock("../../copilot-runtime-plugin-install.js", () => ({
ensureCopilotRuntimePluginForModelSelection,
+5 -5
View File
@@ -754,7 +754,10 @@ describe("security: path traversal protection (CWE-22)", () => {
});
it("rejects include paths at or over the platform-safe maximum", () => {
expectResolveIncludeError(() => resolve({ $include: "a".repeat(4096) }, {}), /maximum length/);
expectResolveIncludeError(
() => resolve({ $include: "a".repeat(4096) }, {}),
/maximum length/,
);
expectResolveIncludeError(
() => resolve({ $include: "b".repeat(4097) }, {}),
/maximum length/,
@@ -863,10 +866,7 @@ describe("security: path traversal protection (CWE-22)", () => {
);
expect(() =>
resolveConfigIncludes(
{ $include: "./big.json5" },
path.join(configDir, "openclaw.json"),
),
resolveConfigIncludes({ $include: "./big.json5" }, path.join(configDir, "openclaw.json")),
).toThrow(/security checks|max/i);
});
});
@@ -380,7 +380,7 @@ describe("models.authStatus", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
openrouter: { ...Object.fromEntries([["apiKey", profileId]]) },
openrouter: Object.fromEntries([["apiKey", profileId]]),
},
},
});
@@ -458,11 +458,9 @@ describe("models.authStatus", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
openai: {
...Object.fromEntries([
["apiKey", { source: "file", provider: "mounted-json", id: "model-provider-key" }],
]),
},
openai: Object.fromEntries([
["apiKey", { source: "file", provider: "mounted-json", id: "model-provider-key" }],
]),
},
},
});
@@ -484,7 +482,7 @@ describe("models.authStatus", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
anthropic: { ...Object.fromEntries([["apiKey", "ANTHROPIC_API_KEY"]]) },
anthropic: Object.fromEntries([["apiKey", "ANTHROPIC_API_KEY"]]),
},
},
});
@@ -506,7 +504,7 @@ describe("models.authStatus", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
anthropic: { ...Object.fromEntries([["apiKey", "ANTHROPIC_API_KEY"]]) },
anthropic: Object.fromEntries([["apiKey", "ANTHROPIC_API_KEY"]]),
},
},
});
@@ -527,7 +525,7 @@ describe("models.authStatus", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
ollama: { ...Object.fromEntries([["apiKey", "ollama-local"]]) },
ollama: Object.fromEntries([["apiKey", "ollama-local"]]),
},
},
});
@@ -546,7 +544,7 @@ describe("models.authStatus", () => {
const profileId = "anthropic:saved";
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: { anthropic: { ...Object.fromEntries([["apiKey", profileId]]) } },
providers: { anthropic: Object.fromEntries([["apiKey", profileId]]) },
},
});
mocks.ensureAuthProfileStore.mockReturnValue({
@@ -1140,7 +1138,7 @@ describe("models.authLogout", () => {
mocks.getRuntimeConfig.mockReturnValue({
models: {
providers: {
openrouter: { ...Object.fromEntries([["apiKey", profileId]]) },
openrouter: Object.fromEntries([["apiKey", profileId]]),
},
},
});
@@ -354,10 +354,10 @@ describe("session transcript reader facade", () => {
expect(messages).toMatchObject([{ content: "branch prompt" }, { content: "active branch" }]);
expect(
messages.map((message) => (message as { __openclaw?: { id?: string } }).__openclaw?.id),
messages.map((message) => (message as { __openclaw?: { id?: string } })["__openclaw"]?.id),
).toEqual(["root", "active"]);
expect(
messages.map((message) => (message as { __openclaw?: { seq?: number } }).__openclaw?.seq),
messages.map((message) => (message as { __openclaw?: { seq?: number } })["__openclaw"]?.seq),
).toEqual([2, 4]);
await expect(readSessionMessageCountAsync(scope)).resolves.toBe(2);
});
@@ -109,6 +109,7 @@ describe("runHeartbeatOnce ack handling", () => {
cfg: params.cfg,
accountId: undefined,
audioAsVoice: undefined,
deliveryPartIndex: 0,
deliveryQueueId: undefined,
forceDocument: undefined,
formatting: undefined,
+1 -3
View File
@@ -8,9 +8,7 @@ export {
runLegacyStateMigrations,
} from "./state-migrations.doctor.js";
export { migrateLegacyAgentDir } from "./state-migrations.legacy-sessions.js";
export {
migrateOrphanedSessionKeys,
} from "./state-migrations.session-store.js";
export { migrateOrphanedSessionKeys } from "./state-migrations.session-store.js";
export {
autoMigrateLegacyStateDir,
autoMigrateLegacyTaskStateSidecars,
+11
View File
@@ -12,6 +12,17 @@ function moduleIdIncludesPackage(id: string, packageName: string): boolean {
}
export function controlUiManualChunk(id: string): string | undefined {
const normalized = normalizeModuleId(id);
// These entry-and-route helpers must stay together; separate shared chunks
// turn small route-graph changes into extra startup preload requests.
if (
normalized.endsWith("/ui/src/components/config-form.shared.ts") ||
normalized.endsWith("/ui/src/lib/clipboard.ts")
) {
return "control-ui-shared";
}
if (
moduleIdIncludesPackage(id, "lit") ||
moduleIdIncludesPackage(id, "lit-html") ||
+8 -2
View File
@@ -1404,7 +1404,13 @@ describe("GatewayBrowserClient", () => {
vi.useRealTimers();
});
it("does not auto-reconnect on AUTH_TOKEN_MISSING", async () => {
it.each([
ConnectErrorDetailCodes.AUTH_TOKEN_MISSING,
ConnectErrorDetailCodes.AUTH_BOOTSTRAP_TOKEN_INVALID,
ConnectErrorDetailCodes.AUTH_PASSWORD_MISSING,
ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
ConnectErrorDetailCodes.PAIRING_REQUIRED,
])("does not auto-reconnect on %s", async (detailCode) => {
useNodeFakeTimers();
localStorage.clear();
@@ -1421,7 +1427,7 @@ describe("GatewayBrowserClient", () => {
error: {
code: "INVALID_REQUEST",
message: "unauthorized",
details: { code: "AUTH_TOKEN_MISSING" },
details: { code: detailCode },
},
});
await expectSocketClosed(ws1);
+4
View File
@@ -19,6 +19,10 @@ describe("Control UI build chunking", () => {
expect(controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/json5/dist/index.js")).toBe(
"config-runtime",
);
expect(controlUiManualChunk("/repo/ui/src/components/config-form.shared.ts")).toBe(
"control-ui-shared",
);
expect(controlUiManualChunk("/repo/ui/src/lib/clipboard.ts")).toBe("control-ui-shared");
expect(controlUiManualChunk("/tmp/openclaw-pnpm-node-modules/@noble/ed25519/index.js")).toBe(
"gateway-runtime",
);
+1 -4
View File
@@ -221,10 +221,7 @@ async function startWhatsAppLogin(
return true;
}
async function waitWhatsAppLogin(
state: ChannelsState,
accountId?: string,
): Promise<boolean> {
async function waitWhatsAppLogin(state: ChannelsState, accountId?: string): Promise<boolean> {
const operation = beginWhatsAppOperation(state);
if (!operation) {
return false;
+4 -4
View File
@@ -328,10 +328,10 @@ export function buildModelProviderCards(input: ModelProviderCardsInput): ModelPr
)
.map((draft) => {
const apiKeySupported = apiKeyCapabilities.get(draft.card.id);
return {
...draft.card,
...(apiKeySupported === undefined ? {} : { apiKeySupported }),
};
if (apiKeySupported !== undefined) {
draft.card.apiKeySupported = apiKeySupported;
}
return draft.card;
})
.toSorted((a, b) => a.displayName.localeCompare(b.displayName));
}
+1 -5
View File
@@ -528,11 +528,7 @@ describe("renderPlugins", () => {
expect(normalizedText(row.querySelector(".settings-row__title"))).toBe("Calendar Plus");
expect(row.querySelector(".plugins-install")).toBeNull();
actionButton(row, "Disable")?.click();
expect(onSetEnabled).toHaveBeenCalledWith(
"calendar-runtime",
false,
clawHubKey(packageName),
);
expect(onSetEnabled).toHaveBeenCalledWith("calendar-runtime", false, clawHubKey(packageName));
});
it("does not present an empty catalog alongside an initial list failure", () => {