Skip to content

Commit dbe2a97

Browse files
committed
fix(cycles): remove qa-lab and ui runtime seams
1 parent 10b26ed commit dbe2a97

13 files changed

Lines changed: 224 additions & 136 deletions

extensions/qa-lab/src/character-eval.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
44
import { runQaManualLane } from "./manual-lane.runtime.js";
55
import { isQaFastModeModelRef, type QaProviderMode } from "./model-selection.js";
66
import { type QaThinkingLevel } from "./qa-gateway-config.js";
7-
import { runQaSuite, type QaSuiteResult } from "./suite.js";
7+
import { runQaSuiteFromRuntime } from "./suite-launch.runtime.js";
8+
import type { QaSuiteResult } from "./suite.js";
89

910
const DEFAULT_CHARACTER_SCENARIO_ID = "character-vibes-gollum";
1011
const DEFAULT_CHARACTER_EVAL_MODELS = Object.freeze([
@@ -518,7 +519,7 @@ export async function runQaCharacterEval(params: QaCharacterEvalParams) {
518519
const runsDir = path.join(outputDir, "runs");
519520
await fs.mkdir(runsDir, { recursive: true });
520521

521-
const runSuite = params.runSuite ?? runQaSuite;
522+
const runSuite = params.runSuite ?? runQaSuiteFromRuntime;
522523
const candidateConcurrency = normalizeConcurrency(
523524
params.candidateConcurrency,
524525
DEFAULT_CHARACTER_EVAL_CONCURRENCY,

extensions/qa-lab/src/cli.runtime.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33

44
const {
55
runQaManualLane,
6-
runQaSuite,
6+
runQaSuiteFromRuntime,
77
runQaCharacterEval,
88
runQaMultipass,
99
startQaLabServer,
@@ -12,7 +12,7 @@ const {
1212
runQaDockerUp,
1313
} = vi.hoisted(() => ({
1414
runQaManualLane: vi.fn(),
15-
runQaSuite: vi.fn(),
15+
runQaSuiteFromRuntime: vi.fn(),
1616
runQaCharacterEval: vi.fn(),
1717
runQaMultipass: vi.fn(),
1818
startQaLabServer: vi.fn(),
@@ -25,8 +25,8 @@ vi.mock("./manual-lane.runtime.js", () => ({
2525
runQaManualLane,
2626
}));
2727

28-
vi.mock("./suite.js", () => ({
29-
runQaSuite,
28+
vi.mock("./suite-launch.runtime.js", () => ({
29+
runQaSuiteFromRuntime,
3030
}));
3131

3232
vi.mock("./character-eval.js", () => ({
@@ -65,15 +65,15 @@ describe("qa cli runtime", () => {
6565

6666
beforeEach(() => {
6767
stdoutWrite = vi.spyOn(process.stdout, "write").mockReturnValue(true);
68-
runQaSuite.mockReset();
68+
runQaSuiteFromRuntime.mockReset();
6969
runQaCharacterEval.mockReset();
7070
runQaManualLane.mockReset();
7171
runQaMultipass.mockReset();
7272
startQaLabServer.mockReset();
7373
writeQaDockerHarnessFiles.mockReset();
7474
buildQaDockerHarnessImage.mockReset();
7575
runQaDockerUp.mockReset();
76-
runQaSuite.mockResolvedValue({
76+
runQaSuiteFromRuntime.mockResolvedValue({
7777
watchUrl: "http://127.0.0.1:43124",
7878
reportPath: "/tmp/report.md",
7979
summaryPath: "/tmp/summary.json",
@@ -135,7 +135,7 @@ describe("qa cli runtime", () => {
135135
scenarioIds: ["approval-turn-tool-followthrough"],
136136
});
137137

138-
expect(runQaSuite).toHaveBeenCalledWith({
138+
expect(runQaSuiteFromRuntime).toHaveBeenCalledWith({
139139
repoRoot: path.resolve("/tmp/openclaw-repo"),
140140
outputDir: path.resolve("/tmp/openclaw-repo", ".artifacts/qa/frontier"),
141141
providerMode: "live-frontier",
@@ -153,7 +153,7 @@ describe("qa cli runtime", () => {
153153
scenarioIds: ["approval-turn-tool-followthrough"],
154154
});
155155

156-
expect(runQaSuite).toHaveBeenCalledWith(
156+
expect(runQaSuiteFromRuntime).toHaveBeenCalledWith(
157157
expect.objectContaining({
158158
repoRoot: path.resolve("/tmp/openclaw-repo"),
159159
providerMode: "live-frontier",
@@ -310,7 +310,7 @@ describe("qa cli runtime", () => {
310310
memory: "4G",
311311
disk: "24G",
312312
});
313-
expect(runQaSuite).not.toHaveBeenCalled();
313+
expect(runQaSuiteFromRuntime).not.toHaveBeenCalled();
314314
});
315315

316316
it("passes live suite selection through to the multipass runner", async () => {

extensions/qa-lab/src/cli.runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
type QaProviderMode,
1414
type QaProviderModeInput,
1515
} from "./run-config.js";
16-
import { runQaSuite } from "./suite.js";
16+
import { runQaSuiteFromRuntime } from "./suite-launch.runtime.js";
1717

1818
type InterruptibleServer = {
1919
baseUrl: string;
@@ -241,7 +241,7 @@ export async function runQaSuiteCommand(opts: {
241241
process.stdout.write(`QA Multipass bootstrap log: ${result.bootstrapLogPath}\n`);
242242
return;
243243
}
244-
const result = await runQaSuite({
244+
const result = await runQaSuiteFromRuntime({
245245
repoRoot,
246246
outputDir: opts.outputDir ? path.resolve(repoRoot, opts.outputDir) : undefined,
247247
providerMode,

extensions/qa-lab/src/lab-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,8 +659,8 @@ export async function startQaLabServer(
659659
};
660660
activeSuiteRun = (async () => {
661661
try {
662-
const { runQaSuiteFromRuntime } = await import("./suite-launch.runtime.js");
663-
const result = await runQaSuiteFromRuntime({
662+
const { runQaSuite } = await import("./suite.js");
663+
const result = await runQaSuite({
664664
lab: labHandle ?? undefined,
665665
outputDir: createQaRunOutputDir(repoRoot),
666666
providerMode: selection.providerMode,
Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
export async function runQaSuiteFromRuntime(
2-
...args: Parameters<typeof import("./suite.js").runQaSuite>
3-
) {
1+
import type { QaSuiteRunParams } from "./suite.js";
2+
3+
async function loadQaLabServerRuntime() {
4+
const { startQaLabServer } = await import("./lab-server.js");
5+
return startQaLabServer;
6+
}
7+
8+
export async function runQaSuiteFromRuntime(...args: [QaSuiteRunParams?]) {
49
const { runQaSuite } = await import("./suite.js");
5-
return await runQaSuite(...args);
10+
const params = args[0];
11+
return await runQaSuite({
12+
...params,
13+
startLab: params?.startLab ?? (await loadQaLabServerRuntime()),
14+
});
615
}

extensions/qa-lab/src/suite.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,20 @@ type QaSuiteEnvironment = {
6868
alternateModel: string;
6969
};
7070

71-
async function startQaLabServerRuntime(
72-
params?: QaLabServerStartParams,
73-
): Promise<QaLabServerHandle> {
74-
const { startQaLabServer } = await import("./lab-server.js");
75-
return await startQaLabServer(params);
76-
}
71+
export type QaSuiteStartLabFn = (params?: QaLabServerStartParams) => Promise<QaLabServerHandle>;
72+
73+
export type QaSuiteRunParams = {
74+
repoRoot?: string;
75+
outputDir?: string;
76+
providerMode?: QaProviderMode | "live-openai";
77+
primaryModel?: string;
78+
alternateModel?: string;
79+
fastMode?: boolean;
80+
thinkingDefault?: QaThinkingLevel;
81+
scenarioIds?: string[];
82+
lab?: QaLabServerHandle;
83+
startLab?: QaSuiteStartLabFn;
84+
};
7785

7886
const _QA_IMAGE_UNDERSTANDING_PNG_BASE64 =
7987
"iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAklEQVR4AewaftIAAAK4SURBVO3BAQEAMAwCIG//znsQgXfJBZjUALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsl9wFmNQAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwP4TIF+7ciPkoAAAAASUVORK5CYII=";
@@ -1188,17 +1196,7 @@ async function runScenarioDefinition(
11881196
});
11891197
}
11901198

1191-
export async function runQaSuite(params?: {
1192-
repoRoot?: string;
1193-
outputDir?: string;
1194-
providerMode?: QaProviderMode | "live-openai";
1195-
primaryModel?: string;
1196-
alternateModel?: string;
1197-
fastMode?: boolean;
1198-
thinkingDefault?: QaThinkingLevel;
1199-
scenarioIds?: string[];
1200-
lab?: QaLabServerHandle;
1201-
}) {
1199+
export async function runQaSuite(params?: QaSuiteRunParams) {
12021200
const startedAt = new Date();
12031201
const repoRoot = path.resolve(params?.repoRoot ?? process.cwd());
12041202
const providerMode = normalizeQaProviderMode(params?.providerMode ?? "mock-openai");
@@ -1217,12 +1215,15 @@ export async function runQaSuite(params?: {
12171215
const ownsLab = !params?.lab;
12181216
const lab =
12191217
params?.lab ??
1220-
(await startQaLabServerRuntime({
1218+
(await params?.startLab?.({
12211219
repoRoot,
12221220
host: "127.0.0.1",
12231221
port: 0,
12241222
embeddedGateway: "disabled",
12251223
}));
1224+
if (!lab) {
1225+
throw new Error("QA suite requires lab or startLab runtime");
1226+
}
12261227
const mock =
12271228
providerMode === "mock-openai"
12281229
? await startQaMockOpenAiServer({

ui/src/ui/app-channels.ts

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,50 @@
1-
import type { OpenClawApp } from "./app.ts";
21
import {
32
loadChannels,
43
logoutWhatsApp,
54
startWhatsAppLogin,
65
waitWhatsAppLogin,
6+
type ChannelsState,
77
} from "./controllers/channels.ts";
8-
import { loadConfig, saveConfig } from "./controllers/config.ts";
8+
import { loadConfig, saveConfig, type ConfigState } from "./controllers/config.ts";
99
import { normalizeOptionalString } from "./string-coerce.ts";
1010
import type { NostrProfile } from "./types.ts";
1111
import { createNostrProfileFormState } from "./views/channels.nostr-profile-form.ts";
1212

13-
export async function handleWhatsAppStart(host: OpenClawApp, force: boolean) {
14-
await startWhatsAppLogin(host, force);
15-
await loadChannels(host, true);
13+
type NostrProfileFormState = ReturnType<typeof createNostrProfileFormState> | null;
14+
15+
type ChannelsActionHost = ChannelsState &
16+
ConfigState & {
17+
hello?: { auth?: { deviceToken?: string | null } | null } | null;
18+
password?: string;
19+
settings: { token?: string };
20+
nostrProfileFormState: NostrProfileFormState;
21+
nostrProfileAccountId: string | null;
22+
};
23+
24+
export async function handleWhatsAppStart(host: ChannelsActionHost, force: boolean) {
25+
await startWhatsAppLogin(host as ChannelsState, force);
26+
await loadChannels(host as ChannelsState, true);
1627
}
1728

18-
export async function handleWhatsAppWait(host: OpenClawApp) {
19-
await waitWhatsAppLogin(host);
20-
await loadChannels(host, true);
29+
export async function handleWhatsAppWait(host: ChannelsActionHost) {
30+
await waitWhatsAppLogin(host as ChannelsState);
31+
await loadChannels(host as ChannelsState, true);
2132
}
2233

23-
export async function handleWhatsAppLogout(host: OpenClawApp) {
24-
await logoutWhatsApp(host);
25-
await loadChannels(host, true);
34+
export async function handleWhatsAppLogout(host: ChannelsActionHost) {
35+
await logoutWhatsApp(host as ChannelsState);
36+
await loadChannels(host as ChannelsState, true);
2637
}
2738

28-
export async function handleChannelConfigSave(host: OpenClawApp) {
29-
await saveConfig(host);
30-
await loadConfig(host);
31-
await loadChannels(host, true);
39+
export async function handleChannelConfigSave(host: ChannelsActionHost) {
40+
await saveConfig(host as ConfigState);
41+
await loadConfig(host as ConfigState);
42+
await loadChannels(host as ChannelsState, true);
3243
}
3344

34-
export async function handleChannelConfigReload(host: OpenClawApp) {
35-
await loadConfig(host);
36-
await loadChannels(host, true);
45+
export async function handleChannelConfigReload(host: ChannelsActionHost) {
46+
await loadConfig(host as ConfigState);
47+
await loadChannels(host as ChannelsState, true);
3748
}
3849

3950
function parseValidationErrors(details: unknown): Record<string, string> {
@@ -58,7 +69,7 @@ function parseValidationErrors(details: unknown): Record<string, string> {
5869
return errors;
5970
}
6071

61-
function resolveNostrAccountId(host: OpenClawApp): string {
72+
function resolveNostrAccountId(host: ChannelsActionHost): string {
6273
const accounts = host.channelsSnapshot?.channelAccounts?.nostr ?? [];
6374
return accounts[0]?.accountId ?? host.nostrProfileAccountId ?? "default";
6475
}
@@ -67,7 +78,7 @@ function buildNostrProfileUrl(accountId: string, suffix = ""): string {
6778
return `/api/channels/nostr/${encodeURIComponent(accountId)}/profile${suffix}`;
6879
}
6980

70-
function resolveGatewayHttpAuthHeader(host: OpenClawApp): string | null {
81+
function resolveGatewayHttpAuthHeader(host: ChannelsActionHost): string | null {
7182
const deviceToken = normalizeOptionalString(host.hello?.auth?.deviceToken);
7283
if (deviceToken) {
7384
return `Bearer ${deviceToken}`;
@@ -83,27 +94,27 @@ function resolveGatewayHttpAuthHeader(host: OpenClawApp): string | null {
8394
return null;
8495
}
8596

86-
function buildGatewayHttpHeaders(host: OpenClawApp): Record<string, string> {
97+
function buildGatewayHttpHeaders(host: ChannelsActionHost): Record<string, string> {
8798
const authorization = resolveGatewayHttpAuthHeader(host);
8899
return authorization ? { Authorization: authorization } : {};
89100
}
90101

91102
export function handleNostrProfileEdit(
92-
host: OpenClawApp,
103+
host: ChannelsActionHost,
93104
accountId: string,
94105
profile: NostrProfile | null,
95106
) {
96107
host.nostrProfileAccountId = accountId;
97108
host.nostrProfileFormState = createNostrProfileFormState(profile ?? undefined);
98109
}
99110

100-
export function handleNostrProfileCancel(host: OpenClawApp) {
111+
export function handleNostrProfileCancel(host: ChannelsActionHost) {
101112
host.nostrProfileFormState = null;
102113
host.nostrProfileAccountId = null;
103114
}
104115

105116
export function handleNostrProfileFieldChange(
106-
host: OpenClawApp,
117+
host: ChannelsActionHost,
107118
field: keyof NostrProfile,
108119
value: string,
109120
) {
@@ -124,7 +135,7 @@ export function handleNostrProfileFieldChange(
124135
};
125136
}
126137

127-
export function handleNostrProfileToggleAdvanced(host: OpenClawApp) {
138+
export function handleNostrProfileToggleAdvanced(host: ChannelsActionHost) {
128139
const state = host.nostrProfileFormState;
129140
if (!state) {
130141
return;
@@ -135,7 +146,7 @@ export function handleNostrProfileToggleAdvanced(host: OpenClawApp) {
135146
};
136147
}
137148

138-
export async function handleNostrProfileSave(host: OpenClawApp) {
149+
export async function handleNostrProfileSave(host: ChannelsActionHost) {
139150
const state = host.nostrProfileFormState;
140151
if (!state || state.saving) {
141152
return;
@@ -196,7 +207,7 @@ export async function handleNostrProfileSave(host: OpenClawApp) {
196207
fieldErrors: {},
197208
original: { ...state.values },
198209
};
199-
await loadChannels(host, true);
210+
await loadChannels(host as ChannelsState, true);
200211
} catch (err) {
201212
host.nostrProfileFormState = {
202213
...state,
@@ -207,7 +218,7 @@ export async function handleNostrProfileSave(host: OpenClawApp) {
207218
}
208219
}
209220

210-
export async function handleNostrProfileImport(host: OpenClawApp) {
221+
export async function handleNostrProfileImport(host: ChannelsActionHost) {
211222
const state = host.nostrProfileFormState;
212223
if (!state || state.importing) {
213224
return;

0 commit comments

Comments
 (0)