mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-14 10:41:23 +00:00
fix(cycles): remove qa-lab and ui runtime seams
This commit is contained in:
@@ -4,7 +4,8 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { runQaManualLane } from "./manual-lane.runtime.js";
|
||||
import { isQaFastModeModelRef, type QaProviderMode } from "./model-selection.js";
|
||||
import { type QaThinkingLevel } from "./qa-gateway-config.js";
|
||||
import { runQaSuite, type QaSuiteResult } from "./suite.js";
|
||||
import { runQaSuiteFromRuntime } from "./suite-launch.runtime.js";
|
||||
import type { QaSuiteResult } from "./suite.js";
|
||||
|
||||
const DEFAULT_CHARACTER_SCENARIO_ID = "character-vibes-gollum";
|
||||
const DEFAULT_CHARACTER_EVAL_MODELS = Object.freeze([
|
||||
@@ -518,7 +519,7 @@ export async function runQaCharacterEval(params: QaCharacterEvalParams) {
|
||||
const runsDir = path.join(outputDir, "runs");
|
||||
await fs.mkdir(runsDir, { recursive: true });
|
||||
|
||||
const runSuite = params.runSuite ?? runQaSuite;
|
||||
const runSuite = params.runSuite ?? runQaSuiteFromRuntime;
|
||||
const candidateConcurrency = normalizeConcurrency(
|
||||
params.candidateConcurrency,
|
||||
DEFAULT_CHARACTER_EVAL_CONCURRENCY,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const {
|
||||
runQaManualLane,
|
||||
runQaSuite,
|
||||
runQaSuiteFromRuntime,
|
||||
runQaCharacterEval,
|
||||
runQaMultipass,
|
||||
startQaLabServer,
|
||||
@@ -12,7 +12,7 @@ const {
|
||||
runQaDockerUp,
|
||||
} = vi.hoisted(() => ({
|
||||
runQaManualLane: vi.fn(),
|
||||
runQaSuite: vi.fn(),
|
||||
runQaSuiteFromRuntime: vi.fn(),
|
||||
runQaCharacterEval: vi.fn(),
|
||||
runQaMultipass: vi.fn(),
|
||||
startQaLabServer: vi.fn(),
|
||||
@@ -25,8 +25,8 @@ vi.mock("./manual-lane.runtime.js", () => ({
|
||||
runQaManualLane,
|
||||
}));
|
||||
|
||||
vi.mock("./suite.js", () => ({
|
||||
runQaSuite,
|
||||
vi.mock("./suite-launch.runtime.js", () => ({
|
||||
runQaSuiteFromRuntime,
|
||||
}));
|
||||
|
||||
vi.mock("./character-eval.js", () => ({
|
||||
@@ -65,7 +65,7 @@ describe("qa cli runtime", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stdoutWrite = vi.spyOn(process.stdout, "write").mockReturnValue(true);
|
||||
runQaSuite.mockReset();
|
||||
runQaSuiteFromRuntime.mockReset();
|
||||
runQaCharacterEval.mockReset();
|
||||
runQaManualLane.mockReset();
|
||||
runQaMultipass.mockReset();
|
||||
@@ -73,7 +73,7 @@ describe("qa cli runtime", () => {
|
||||
writeQaDockerHarnessFiles.mockReset();
|
||||
buildQaDockerHarnessImage.mockReset();
|
||||
runQaDockerUp.mockReset();
|
||||
runQaSuite.mockResolvedValue({
|
||||
runQaSuiteFromRuntime.mockResolvedValue({
|
||||
watchUrl: "http://127.0.0.1:43124",
|
||||
reportPath: "/tmp/report.md",
|
||||
summaryPath: "/tmp/summary.json",
|
||||
@@ -135,7 +135,7 @@ describe("qa cli runtime", () => {
|
||||
scenarioIds: ["approval-turn-tool-followthrough"],
|
||||
});
|
||||
|
||||
expect(runQaSuite).toHaveBeenCalledWith({
|
||||
expect(runQaSuiteFromRuntime).toHaveBeenCalledWith({
|
||||
repoRoot: path.resolve("/tmp/openclaw-repo"),
|
||||
outputDir: path.resolve("/tmp/openclaw-repo", ".artifacts/qa/frontier"),
|
||||
providerMode: "live-frontier",
|
||||
@@ -153,7 +153,7 @@ describe("qa cli runtime", () => {
|
||||
scenarioIds: ["approval-turn-tool-followthrough"],
|
||||
});
|
||||
|
||||
expect(runQaSuite).toHaveBeenCalledWith(
|
||||
expect(runQaSuiteFromRuntime).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
repoRoot: path.resolve("/tmp/openclaw-repo"),
|
||||
providerMode: "live-frontier",
|
||||
@@ -310,7 +310,7 @@ describe("qa cli runtime", () => {
|
||||
memory: "4G",
|
||||
disk: "24G",
|
||||
});
|
||||
expect(runQaSuite).not.toHaveBeenCalled();
|
||||
expect(runQaSuiteFromRuntime).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes live suite selection through to the multipass runner", async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type QaProviderMode,
|
||||
type QaProviderModeInput,
|
||||
} from "./run-config.js";
|
||||
import { runQaSuite } from "./suite.js";
|
||||
import { runQaSuiteFromRuntime } from "./suite-launch.runtime.js";
|
||||
|
||||
type InterruptibleServer = {
|
||||
baseUrl: string;
|
||||
@@ -241,7 +241,7 @@ export async function runQaSuiteCommand(opts: {
|
||||
process.stdout.write(`QA Multipass bootstrap log: ${result.bootstrapLogPath}\n`);
|
||||
return;
|
||||
}
|
||||
const result = await runQaSuite({
|
||||
const result = await runQaSuiteFromRuntime({
|
||||
repoRoot,
|
||||
outputDir: opts.outputDir ? path.resolve(repoRoot, opts.outputDir) : undefined,
|
||||
providerMode,
|
||||
|
||||
@@ -659,8 +659,8 @@ export async function startQaLabServer(
|
||||
};
|
||||
activeSuiteRun = (async () => {
|
||||
try {
|
||||
const { runQaSuiteFromRuntime } = await import("./suite-launch.runtime.js");
|
||||
const result = await runQaSuiteFromRuntime({
|
||||
const { runQaSuite } = await import("./suite.js");
|
||||
const result = await runQaSuite({
|
||||
lab: labHandle ?? undefined,
|
||||
outputDir: createQaRunOutputDir(repoRoot),
|
||||
providerMode: selection.providerMode,
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
export async function runQaSuiteFromRuntime(
|
||||
...args: Parameters<typeof import("./suite.js").runQaSuite>
|
||||
) {
|
||||
const { runQaSuite } = await import("./suite.js");
|
||||
return await runQaSuite(...args);
|
||||
import type { QaSuiteRunParams } from "./suite.js";
|
||||
|
||||
async function loadQaLabServerRuntime() {
|
||||
const { startQaLabServer } = await import("./lab-server.js");
|
||||
return startQaLabServer;
|
||||
}
|
||||
|
||||
export async function runQaSuiteFromRuntime(...args: [QaSuiteRunParams?]) {
|
||||
const { runQaSuite } = await import("./suite.js");
|
||||
const params = args[0];
|
||||
return await runQaSuite({
|
||||
...params,
|
||||
startLab: params?.startLab ?? (await loadQaLabServerRuntime()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,12 +68,20 @@ type QaSuiteEnvironment = {
|
||||
alternateModel: string;
|
||||
};
|
||||
|
||||
async function startQaLabServerRuntime(
|
||||
params?: QaLabServerStartParams,
|
||||
): Promise<QaLabServerHandle> {
|
||||
const { startQaLabServer } = await import("./lab-server.js");
|
||||
return await startQaLabServer(params);
|
||||
}
|
||||
export type QaSuiteStartLabFn = (params?: QaLabServerStartParams) => Promise<QaLabServerHandle>;
|
||||
|
||||
export type QaSuiteRunParams = {
|
||||
repoRoot?: string;
|
||||
outputDir?: string;
|
||||
providerMode?: QaProviderMode | "live-openai";
|
||||
primaryModel?: string;
|
||||
alternateModel?: string;
|
||||
fastMode?: boolean;
|
||||
thinkingDefault?: QaThinkingLevel;
|
||||
scenarioIds?: string[];
|
||||
lab?: QaLabServerHandle;
|
||||
startLab?: QaSuiteStartLabFn;
|
||||
};
|
||||
|
||||
const _QA_IMAGE_UNDERSTANDING_PNG_BASE64 =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAklEQVR4AewaftIAAAK4SURBVO3BAQEAMAwCIG//znsQgXfJBZjUALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsBpjVALMaYFYDzGqAWQ0wqwFmNcCsl9wFmNQAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwGmNUAsxpgVgPMaoBZDTCrAWY1wKwP4TIF+7ciPkoAAAAASUVORK5CYII=";
|
||||
@@ -1188,17 +1196,7 @@ async function runScenarioDefinition(
|
||||
});
|
||||
}
|
||||
|
||||
export async function runQaSuite(params?: {
|
||||
repoRoot?: string;
|
||||
outputDir?: string;
|
||||
providerMode?: QaProviderMode | "live-openai";
|
||||
primaryModel?: string;
|
||||
alternateModel?: string;
|
||||
fastMode?: boolean;
|
||||
thinkingDefault?: QaThinkingLevel;
|
||||
scenarioIds?: string[];
|
||||
lab?: QaLabServerHandle;
|
||||
}) {
|
||||
export async function runQaSuite(params?: QaSuiteRunParams) {
|
||||
const startedAt = new Date();
|
||||
const repoRoot = path.resolve(params?.repoRoot ?? process.cwd());
|
||||
const providerMode = normalizeQaProviderMode(params?.providerMode ?? "mock-openai");
|
||||
@@ -1217,12 +1215,15 @@ export async function runQaSuite(params?: {
|
||||
const ownsLab = !params?.lab;
|
||||
const lab =
|
||||
params?.lab ??
|
||||
(await startQaLabServerRuntime({
|
||||
(await params?.startLab?.({
|
||||
repoRoot,
|
||||
host: "127.0.0.1",
|
||||
port: 0,
|
||||
embeddedGateway: "disabled",
|
||||
}));
|
||||
if (!lab) {
|
||||
throw new Error("QA suite requires lab or startLab runtime");
|
||||
}
|
||||
const mock =
|
||||
providerMode === "mock-openai"
|
||||
? await startQaMockOpenAiServer({
|
||||
|
||||
@@ -1,39 +1,50 @@
|
||||
import type { OpenClawApp } from "./app.ts";
|
||||
import {
|
||||
loadChannels,
|
||||
logoutWhatsApp,
|
||||
startWhatsAppLogin,
|
||||
waitWhatsAppLogin,
|
||||
type ChannelsState,
|
||||
} from "./controllers/channels.ts";
|
||||
import { loadConfig, saveConfig } from "./controllers/config.ts";
|
||||
import { loadConfig, saveConfig, type ConfigState } from "./controllers/config.ts";
|
||||
import { normalizeOptionalString } from "./string-coerce.ts";
|
||||
import type { NostrProfile } from "./types.ts";
|
||||
import { createNostrProfileFormState } from "./views/channels.nostr-profile-form.ts";
|
||||
|
||||
export async function handleWhatsAppStart(host: OpenClawApp, force: boolean) {
|
||||
await startWhatsAppLogin(host, force);
|
||||
await loadChannels(host, true);
|
||||
type NostrProfileFormState = ReturnType<typeof createNostrProfileFormState> | null;
|
||||
|
||||
type ChannelsActionHost = ChannelsState &
|
||||
ConfigState & {
|
||||
hello?: { auth?: { deviceToken?: string | null } | null } | null;
|
||||
password?: string;
|
||||
settings: { token?: string };
|
||||
nostrProfileFormState: NostrProfileFormState;
|
||||
nostrProfileAccountId: string | null;
|
||||
};
|
||||
|
||||
export async function handleWhatsAppStart(host: ChannelsActionHost, force: boolean) {
|
||||
await startWhatsAppLogin(host as ChannelsState, force);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
}
|
||||
|
||||
export async function handleWhatsAppWait(host: OpenClawApp) {
|
||||
await waitWhatsAppLogin(host);
|
||||
await loadChannels(host, true);
|
||||
export async function handleWhatsAppWait(host: ChannelsActionHost) {
|
||||
await waitWhatsAppLogin(host as ChannelsState);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
}
|
||||
|
||||
export async function handleWhatsAppLogout(host: OpenClawApp) {
|
||||
await logoutWhatsApp(host);
|
||||
await loadChannels(host, true);
|
||||
export async function handleWhatsAppLogout(host: ChannelsActionHost) {
|
||||
await logoutWhatsApp(host as ChannelsState);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
}
|
||||
|
||||
export async function handleChannelConfigSave(host: OpenClawApp) {
|
||||
await saveConfig(host);
|
||||
await loadConfig(host);
|
||||
await loadChannels(host, true);
|
||||
export async function handleChannelConfigSave(host: ChannelsActionHost) {
|
||||
await saveConfig(host as ConfigState);
|
||||
await loadConfig(host as ConfigState);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
}
|
||||
|
||||
export async function handleChannelConfigReload(host: OpenClawApp) {
|
||||
await loadConfig(host);
|
||||
await loadChannels(host, true);
|
||||
export async function handleChannelConfigReload(host: ChannelsActionHost) {
|
||||
await loadConfig(host as ConfigState);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
}
|
||||
|
||||
function parseValidationErrors(details: unknown): Record<string, string> {
|
||||
@@ -58,7 +69,7 @@ function parseValidationErrors(details: unknown): Record<string, string> {
|
||||
return errors;
|
||||
}
|
||||
|
||||
function resolveNostrAccountId(host: OpenClawApp): string {
|
||||
function resolveNostrAccountId(host: ChannelsActionHost): string {
|
||||
const accounts = host.channelsSnapshot?.channelAccounts?.nostr ?? [];
|
||||
return accounts[0]?.accountId ?? host.nostrProfileAccountId ?? "default";
|
||||
}
|
||||
@@ -67,7 +78,7 @@ function buildNostrProfileUrl(accountId: string, suffix = ""): string {
|
||||
return `/api/channels/nostr/${encodeURIComponent(accountId)}/profile${suffix}`;
|
||||
}
|
||||
|
||||
function resolveGatewayHttpAuthHeader(host: OpenClawApp): string | null {
|
||||
function resolveGatewayHttpAuthHeader(host: ChannelsActionHost): string | null {
|
||||
const deviceToken = normalizeOptionalString(host.hello?.auth?.deviceToken);
|
||||
if (deviceToken) {
|
||||
return `Bearer ${deviceToken}`;
|
||||
@@ -83,13 +94,13 @@ function resolveGatewayHttpAuthHeader(host: OpenClawApp): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildGatewayHttpHeaders(host: OpenClawApp): Record<string, string> {
|
||||
function buildGatewayHttpHeaders(host: ChannelsActionHost): Record<string, string> {
|
||||
const authorization = resolveGatewayHttpAuthHeader(host);
|
||||
return authorization ? { Authorization: authorization } : {};
|
||||
}
|
||||
|
||||
export function handleNostrProfileEdit(
|
||||
host: OpenClawApp,
|
||||
host: ChannelsActionHost,
|
||||
accountId: string,
|
||||
profile: NostrProfile | null,
|
||||
) {
|
||||
@@ -97,13 +108,13 @@ export function handleNostrProfileEdit(
|
||||
host.nostrProfileFormState = createNostrProfileFormState(profile ?? undefined);
|
||||
}
|
||||
|
||||
export function handleNostrProfileCancel(host: OpenClawApp) {
|
||||
export function handleNostrProfileCancel(host: ChannelsActionHost) {
|
||||
host.nostrProfileFormState = null;
|
||||
host.nostrProfileAccountId = null;
|
||||
}
|
||||
|
||||
export function handleNostrProfileFieldChange(
|
||||
host: OpenClawApp,
|
||||
host: ChannelsActionHost,
|
||||
field: keyof NostrProfile,
|
||||
value: string,
|
||||
) {
|
||||
@@ -124,7 +135,7 @@ export function handleNostrProfileFieldChange(
|
||||
};
|
||||
}
|
||||
|
||||
export function handleNostrProfileToggleAdvanced(host: OpenClawApp) {
|
||||
export function handleNostrProfileToggleAdvanced(host: ChannelsActionHost) {
|
||||
const state = host.nostrProfileFormState;
|
||||
if (!state) {
|
||||
return;
|
||||
@@ -135,7 +146,7 @@ export function handleNostrProfileToggleAdvanced(host: OpenClawApp) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleNostrProfileSave(host: OpenClawApp) {
|
||||
export async function handleNostrProfileSave(host: ChannelsActionHost) {
|
||||
const state = host.nostrProfileFormState;
|
||||
if (!state || state.saving) {
|
||||
return;
|
||||
@@ -196,7 +207,7 @@ export async function handleNostrProfileSave(host: OpenClawApp) {
|
||||
fieldErrors: {},
|
||||
original: { ...state.values },
|
||||
};
|
||||
await loadChannels(host, true);
|
||||
await loadChannels(host as ChannelsState, true);
|
||||
} catch (err) {
|
||||
host.nostrProfileFormState = {
|
||||
...state,
|
||||
@@ -207,7 +218,7 @@ export async function handleNostrProfileSave(host: OpenClawApp) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleNostrProfileImport(host: OpenClawApp) {
|
||||
export async function handleNostrProfileImport(host: ChannelsActionHost) {
|
||||
const state = host.nostrProfileFormState;
|
||||
if (!state || state.importing) {
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { setLastActiveSessionKey } from "./app-last-active-session.ts";
|
||||
import { scheduleChatScroll, resetChatScroll } from "./app-scroll.ts";
|
||||
import { setLastActiveSessionKey } from "./app-settings.ts";
|
||||
import { resetToolStream } from "./app-tool-stream.ts";
|
||||
import type { OpenClawApp } from "./app.ts";
|
||||
import { executeSlashCommand } from "./chat/slash-command-executor.ts";
|
||||
import { parseSlashCommand } from "./chat/slash-commands.ts";
|
||||
import { abortChatRun, loadChatHistory, sendChatMessage } from "./controllers/chat.ts";
|
||||
import {
|
||||
abortChatRun,
|
||||
loadChatHistory,
|
||||
sendChatMessage,
|
||||
type ChatState,
|
||||
} from "./controllers/chat.ts";
|
||||
import { loadModels } from "./controllers/models.ts";
|
||||
import { loadSessions } from "./controllers/sessions.ts";
|
||||
import { loadSessions, type SessionsState } from "./controllers/sessions.ts";
|
||||
import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway.ts";
|
||||
import { normalizeBasePath } from "./navigation.ts";
|
||||
import { parseAgentSessionKey } from "./session-key.ts";
|
||||
@@ -82,7 +86,7 @@ export async function handleAbortChat(host: ChatHost) {
|
||||
return;
|
||||
}
|
||||
host.chatMessage = "";
|
||||
await abortChatRun(host as unknown as OpenClawApp);
|
||||
await abortChatRun(host as unknown as ChatState);
|
||||
}
|
||||
|
||||
function enqueueChatMessage(
|
||||
@@ -142,7 +146,7 @@ async function sendChatMessageNow(
|
||||
resetToolStream(host as unknown as Parameters<typeof resetToolStream>[0]);
|
||||
// Reset scroll state before sending to ensure auto-scroll works for the response
|
||||
resetChatScroll(host as unknown as Parameters<typeof resetChatScroll>[0]);
|
||||
const runId = await sendChatMessage(host as unknown as OpenClawApp, message, opts?.attachments);
|
||||
const runId = await sendChatMessage(host as unknown as ChatState, message, opts?.attachments);
|
||||
const ok = Boolean(runId);
|
||||
if (!ok && opts?.previousDraft != null) {
|
||||
host.chatMessage = opts.previousDraft;
|
||||
@@ -375,7 +379,7 @@ async function clearChatHistory(host: ChatHost) {
|
||||
host.chatMessages = [];
|
||||
host.chatStream = null;
|
||||
host.chatRunId = null;
|
||||
await loadChatHistory(host as unknown as OpenClawApp);
|
||||
await loadChatHistory(host as unknown as ChatState);
|
||||
} catch (err) {
|
||||
host.lastError = String(err);
|
||||
}
|
||||
@@ -395,8 +399,8 @@ function injectCommandResult(host: ChatHost, content: string) {
|
||||
|
||||
export async function refreshChat(host: ChatHost, opts?: { scheduleScroll?: boolean }) {
|
||||
await Promise.all([
|
||||
loadChatHistory(host as unknown as OpenClawApp),
|
||||
loadSessions(host as unknown as OpenClawApp, {
|
||||
loadChatHistory(host as unknown as ChatState),
|
||||
loadSessions(host as unknown as SessionsState, {
|
||||
activeMinutes: 0,
|
||||
limit: 0,
|
||||
includeGlobal: true,
|
||||
|
||||
@@ -15,14 +15,20 @@ import {
|
||||
setLastActiveSessionKey,
|
||||
} from "./app-settings.ts";
|
||||
import { handleAgentEvent, resetToolStream, type AgentEventPayload } from "./app-tool-stream.ts";
|
||||
import type { OpenClawApp } from "./app.ts";
|
||||
import { shouldReloadHistoryForFinalEvent } from "./chat-event-reload.ts";
|
||||
import { formatConnectError } from "./connect-error.ts";
|
||||
import { loadAgents } from "./controllers/agents.ts";
|
||||
import { loadAssistantIdentity } from "./controllers/assistant-identity.ts";
|
||||
import { loadChatHistory } from "./controllers/chat.ts";
|
||||
import { handleChatEvent, type ChatEventPayload } from "./controllers/chat.ts";
|
||||
import { loadDevices } from "./controllers/devices.ts";
|
||||
import { loadAgents, type AgentsState } from "./controllers/agents.ts";
|
||||
import {
|
||||
loadAssistantIdentity,
|
||||
type AssistantIdentityState,
|
||||
} from "./controllers/assistant-identity.ts";
|
||||
import {
|
||||
loadChatHistory,
|
||||
handleChatEvent,
|
||||
type ChatEventPayload,
|
||||
type ChatState,
|
||||
} from "./controllers/chat.ts";
|
||||
import { loadDevices, type DevicesState } from "./controllers/devices.ts";
|
||||
import type { ExecApprovalRequest } from "./controllers/exec-approval.ts";
|
||||
import {
|
||||
addExecApproval,
|
||||
@@ -32,9 +38,9 @@ import {
|
||||
pruneExecApprovalQueue,
|
||||
removeExecApproval,
|
||||
} from "./controllers/exec-approval.ts";
|
||||
import { loadHealthState } from "./controllers/health.ts";
|
||||
import { loadNodes } from "./controllers/nodes.ts";
|
||||
import { loadSessions, subscribeSessions } from "./controllers/sessions.ts";
|
||||
import { loadHealthState, type HealthState } from "./controllers/health.ts";
|
||||
import { loadNodes, type NodesState } from "./controllers/nodes.ts";
|
||||
import { loadSessions, subscribeSessions, type SessionsState } from "./controllers/sessions.ts";
|
||||
import {
|
||||
resolveGatewayErrorDetailCode,
|
||||
type GatewayEventFrame,
|
||||
@@ -248,12 +254,12 @@ export function connectGateway(host: GatewayHost, options?: ConnectGatewayOption
|
||||
host as unknown as Parameters<typeof flushChatQueueForEvent>[0],
|
||||
);
|
||||
}
|
||||
void subscribeSessions(host as unknown as OpenClawApp);
|
||||
void loadAssistantIdentity(host as unknown as OpenClawApp);
|
||||
void loadAgents(host as unknown as OpenClawApp);
|
||||
void loadHealthState(host as unknown as OpenClawApp);
|
||||
void loadNodes(host as unknown as OpenClawApp, { quiet: true });
|
||||
void loadDevices(host as unknown as OpenClawApp, { quiet: true });
|
||||
void subscribeSessions(host as unknown as SessionsState);
|
||||
void loadAssistantIdentity(host as unknown as AssistantIdentityState);
|
||||
void loadAgents(host as unknown as AgentsState);
|
||||
void loadHealthState(host as unknown as HealthState);
|
||||
void loadNodes(host as unknown as NodesState, { quiet: true });
|
||||
void loadDevices(host as unknown as DevicesState, { quiet: true });
|
||||
void refreshActiveTab(host as unknown as Parameters<typeof refreshActiveTab>[0]);
|
||||
},
|
||||
onClose: ({ code, reason, error }) => {
|
||||
@@ -333,7 +339,7 @@ function handleTerminalChatEvent(
|
||||
if (runId && host.refreshSessionsAfterChat.has(runId)) {
|
||||
host.refreshSessionsAfterChat.delete(runId);
|
||||
if (state === "final") {
|
||||
void loadSessions(host as unknown as OpenClawApp, {
|
||||
void loadSessions(host as unknown as SessionsState, {
|
||||
activeMinutes: CHAT_SESSIONS_ACTIVE_MINUTES,
|
||||
});
|
||||
}
|
||||
@@ -341,7 +347,7 @@ function handleTerminalChatEvent(
|
||||
// Reload history when tools were used so the persisted tool results
|
||||
// replace the now-cleared streaming state.
|
||||
if (hadToolEvents && state === "final") {
|
||||
void loadChatHistory(host as unknown as OpenClawApp);
|
||||
void loadChatHistory(host as unknown as ChatState);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -354,10 +360,10 @@ function handleChatGatewayEvent(host: GatewayHost, payload: ChatEventPayload | u
|
||||
payload.sessionKey,
|
||||
);
|
||||
}
|
||||
const state = handleChatEvent(host as unknown as OpenClawApp, payload);
|
||||
const state = handleChatEvent(host as unknown as ChatState, payload);
|
||||
const historyReloaded = handleTerminalChatEvent(host, payload, state);
|
||||
if (state === "final" && !historyReloaded && shouldReloadHistoryForFinalEvent(payload)) {
|
||||
void loadChatHistory(host as unknown as OpenClawApp);
|
||||
void loadChatHistory(host as unknown as ChatState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +416,7 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) {
|
||||
}
|
||||
|
||||
if (evt.event === "sessions.changed") {
|
||||
void loadSessions(host as unknown as OpenClawApp);
|
||||
void loadSessions(host as unknown as SessionsState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -419,7 +425,7 @@ function handleGatewayEventUnsafe(host: GatewayHost, evt: GatewayEventFrame) {
|
||||
}
|
||||
|
||||
if (evt.event === "device.pair.requested" || evt.event === "device.pair.resolved") {
|
||||
void loadDevices(host as unknown as OpenClawApp, { quiet: true });
|
||||
void loadDevices(host as unknown as DevicesState, { quiet: true });
|
||||
}
|
||||
|
||||
if (evt.event === "exec.approval.requested") {
|
||||
|
||||
14
ui/src/ui/app-last-active-session.ts
Normal file
14
ui/src/ui/app-last-active-session.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { UiSettings } from "./storage.ts";
|
||||
|
||||
type LastActiveSessionHost = {
|
||||
settings: UiSettings;
|
||||
applySettings(next: UiSettings): void;
|
||||
};
|
||||
|
||||
export function setLastActiveSessionKey(host: LastActiveSessionHost, next: string) {
|
||||
const trimmed = next.trim();
|
||||
if (!trimmed || host.settings.lastActiveSessionKey === trimmed) {
|
||||
return;
|
||||
}
|
||||
host.applySettings({ ...host.settings, lastActiveSessionKey: trimmed });
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { OpenClawApp } from "./app.ts";
|
||||
import type { DebugState } from "./controllers/debug.ts";
|
||||
import { loadDebug } from "./controllers/debug.ts";
|
||||
import type { LogsState } from "./controllers/logs.ts";
|
||||
import { loadLogs } from "./controllers/logs.ts";
|
||||
import type { NodesState } from "./controllers/nodes.ts";
|
||||
import { loadNodes } from "./controllers/nodes.ts";
|
||||
|
||||
type PollingHost = {
|
||||
@@ -15,7 +17,7 @@ export function startNodesPolling(host: PollingHost) {
|
||||
return;
|
||||
}
|
||||
host.nodesPollInterval = window.setInterval(
|
||||
() => void loadNodes(host as unknown as OpenClawApp, { quiet: true }),
|
||||
() => void loadNodes(host as unknown as NodesState, { quiet: true }),
|
||||
5000,
|
||||
);
|
||||
}
|
||||
@@ -36,7 +38,7 @@ export function startLogsPolling(host: PollingHost) {
|
||||
if (host.tab !== "logs") {
|
||||
return;
|
||||
}
|
||||
void loadLogs(host as unknown as OpenClawApp, { quiet: true });
|
||||
void loadLogs(host as unknown as LogsState, { quiet: true });
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
@@ -56,7 +58,7 @@ export function startDebugPolling(host: PollingHost) {
|
||||
if (host.tab !== "debug") {
|
||||
return;
|
||||
}
|
||||
void loadDebug(host as unknown as OpenClawApp);
|
||||
void loadDebug(host as unknown as DebugState);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { t } from "../i18n/index.ts";
|
||||
import { refreshChat, refreshChatAvatar } from "./app-chat.ts";
|
||||
import { syncUrlWithSessionKey } from "./app-settings.ts";
|
||||
import type { AppViewState } from "./app-view-state.ts";
|
||||
import { OpenClawApp } from "./app.ts";
|
||||
import { createChatModelOverride } from "./chat-model-ref.ts";
|
||||
import {
|
||||
resolveChatModelOverrideValue,
|
||||
@@ -30,6 +29,20 @@ type SessionDefaultsSnapshot = {
|
||||
mainKey?: string;
|
||||
};
|
||||
|
||||
type SessionSwitchHost = AppViewState & {
|
||||
chatStreamStartedAt: number | null;
|
||||
resetToolStream(): void;
|
||||
resetChatScroll(): void;
|
||||
};
|
||||
|
||||
type ChatRefreshHost = AppViewState & {
|
||||
chatManualRefreshInFlight: boolean;
|
||||
chatNewMessagesBelow: boolean;
|
||||
resetToolStream(): void;
|
||||
scrollToBottom(opts?: { smooth?: boolean }): void;
|
||||
updateComplete?: Promise<unknown>;
|
||||
};
|
||||
|
||||
function resolveSidebarChatSessionKey(state: AppViewState): string {
|
||||
const snapshot = state.hello?.snapshot as
|
||||
| { sessionDefaults?: SessionDefaultsSnapshot }
|
||||
@@ -46,6 +59,7 @@ function resolveSidebarChatSessionKey(state: AppViewState): string {
|
||||
}
|
||||
|
||||
function resetChatStateForSessionSwitch(state: AppViewState, sessionKey: string) {
|
||||
const host = state as unknown as SessionSwitchHost;
|
||||
state.sessionKey = sessionKey;
|
||||
state.chatMessage = "";
|
||||
state.chatAttachments = [];
|
||||
@@ -59,10 +73,10 @@ function resetChatStateForSessionSwitch(state: AppViewState, sessionKey: string)
|
||||
state.fallbackStatus = null;
|
||||
state.chatAvatarUrl = null;
|
||||
state.chatQueue = [];
|
||||
(state as unknown as OpenClawApp).chatStreamStartedAt = null;
|
||||
host.chatStreamStartedAt = null;
|
||||
state.chatRunId = null;
|
||||
(state as unknown as OpenClawApp).resetToolStream();
|
||||
(state as unknown as OpenClawApp).resetChatScroll();
|
||||
host.resetToolStream();
|
||||
host.resetChatScroll();
|
||||
state.applySettings({
|
||||
...state.settings,
|
||||
sessionKey,
|
||||
@@ -252,7 +266,7 @@ export function renderChatControls(state: AppViewState) {
|
||||
class="btn btn--sm btn--icon"
|
||||
?disabled=${state.chatLoading || !state.connected}
|
||||
@click=${async () => {
|
||||
const app = state as unknown as OpenClawApp;
|
||||
const app = state as unknown as ChatRefreshHost;
|
||||
app.chatManualRefreshInFlight = true;
|
||||
app.chatNewMessagesBelow = false;
|
||||
await app.updateComplete;
|
||||
|
||||
@@ -7,24 +7,32 @@ import {
|
||||
stopDebugPolling,
|
||||
} from "./app-polling.ts";
|
||||
import { scheduleChatScroll, scheduleLogsScroll } from "./app-scroll.ts";
|
||||
import type { OpenClawApp } from "./app.ts";
|
||||
import { loadAgentFiles } from "./controllers/agent-files.ts";
|
||||
import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity.ts";
|
||||
import { loadAgentSkills } from "./controllers/agent-skills.ts";
|
||||
import { loadAgents } from "./controllers/agents.ts";
|
||||
import { loadChannels } from "./controllers/channels.ts";
|
||||
import { loadConfig, loadConfigSchema } from "./controllers/config.ts";
|
||||
import { loadCronJobsPage, loadCronRuns, loadCronStatus } from "./controllers/cron.ts";
|
||||
import { loadDebug } from "./controllers/debug.ts";
|
||||
import { loadDevices } from "./controllers/devices.ts";
|
||||
import { loadDreamDiary, loadDreamingStatus } from "./controllers/dreaming.ts";
|
||||
import { loadExecApprovals } from "./controllers/exec-approvals.ts";
|
||||
import { loadLogs } from "./controllers/logs.ts";
|
||||
import { loadNodes } from "./controllers/nodes.ts";
|
||||
import { loadPresence } from "./controllers/presence.ts";
|
||||
import { loadSessions } from "./controllers/sessions.ts";
|
||||
import { loadSkills } from "./controllers/skills.ts";
|
||||
import { loadUsage } from "./controllers/usage.ts";
|
||||
import { loadAgentFiles, type AgentFilesState } from "./controllers/agent-files.ts";
|
||||
import {
|
||||
loadAgentIdentities,
|
||||
loadAgentIdentity,
|
||||
type AgentIdentityState,
|
||||
} from "./controllers/agent-identity.ts";
|
||||
import { loadAgentSkills, type AgentSkillsState } from "./controllers/agent-skills.ts";
|
||||
import { loadAgents, type AgentsState } from "./controllers/agents.ts";
|
||||
import { loadChannels, type ChannelsState } from "./controllers/channels.ts";
|
||||
import { loadConfig, loadConfigSchema, type ConfigState } from "./controllers/config.ts";
|
||||
import {
|
||||
loadCronJobsPage,
|
||||
loadCronRuns,
|
||||
loadCronStatus,
|
||||
type CronState,
|
||||
} from "./controllers/cron.ts";
|
||||
import { loadDebug, type DebugState } from "./controllers/debug.ts";
|
||||
import { loadDevices, type DevicesState } from "./controllers/devices.ts";
|
||||
import { loadDreamDiary, loadDreamingStatus, type DreamingState } from "./controllers/dreaming.ts";
|
||||
import { loadExecApprovals, type ExecApprovalsState } from "./controllers/exec-approvals.ts";
|
||||
import { loadLogs, type LogsState } from "./controllers/logs.ts";
|
||||
import { loadNodes, type NodesState } from "./controllers/nodes.ts";
|
||||
import { loadPresence, type PresenceState } from "./controllers/presence.ts";
|
||||
import { loadSessions, type SessionsState } from "./controllers/sessions.ts";
|
||||
import { loadSkills, type SkillsState } from "./controllers/skills.ts";
|
||||
import { loadUsage, type UsageState } from "./controllers/usage.ts";
|
||||
import {
|
||||
inferBasePathFromPathname,
|
||||
normalizeBasePath,
|
||||
@@ -40,6 +48,8 @@ import { resolveTheme, type ResolvedTheme, type ThemeMode, type ThemeName } from
|
||||
import type { AgentsListResult, AttentionItem } from "./types.ts";
|
||||
import { resetChatViewState } from "./views/chat.ts";
|
||||
|
||||
export { setLastActiveSessionKey } from "./app-last-active-session.ts";
|
||||
|
||||
type SettingsHost = {
|
||||
settings: UiSettings;
|
||||
password?: string;
|
||||
@@ -71,6 +81,30 @@ type SettingsHost = {
|
||||
dreamDiaryContent: string | null;
|
||||
};
|
||||
|
||||
type SettingsAppHost = SettingsHost &
|
||||
AgentFilesState &
|
||||
AgentIdentityState &
|
||||
AgentSkillsState &
|
||||
AgentsState &
|
||||
ChannelsState &
|
||||
ConfigState &
|
||||
CronState &
|
||||
DebugState &
|
||||
DevicesState &
|
||||
DreamingState &
|
||||
ExecApprovalsState &
|
||||
LogsState &
|
||||
NodesState &
|
||||
PresenceState &
|
||||
SessionsState &
|
||||
SkillsState &
|
||||
UsageState & {
|
||||
overviewLogCursor: number | null;
|
||||
overviewLogLines: string[];
|
||||
attentionItems: AttentionItem[];
|
||||
hello: { auth?: { role?: string; scopes?: string[] } } | null;
|
||||
};
|
||||
|
||||
export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||
const normalized = {
|
||||
...next,
|
||||
@@ -90,14 +124,6 @@ export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||
host.applySessionKey = host.settings.lastActiveSessionKey;
|
||||
}
|
||||
|
||||
export function setLastActiveSessionKey(host: SettingsHost, next: string) {
|
||||
const trimmed = next.trim();
|
||||
if (!trimmed || host.settings.lastActiveSessionKey === trimmed) {
|
||||
return;
|
||||
}
|
||||
applySettings(host, { ...host.settings, lastActiveSessionKey: trimmed });
|
||||
}
|
||||
|
||||
function applySessionSelection(host: SettingsHost, session: string) {
|
||||
host.sessionKey = session;
|
||||
applySettings(host, {
|
||||
@@ -231,7 +257,7 @@ export function setThemeMode(
|
||||
);
|
||||
}
|
||||
|
||||
async function refreshAgentsTab(host: SettingsHost, app: OpenClawApp) {
|
||||
async function refreshAgentsTab(host: SettingsHost, app: SettingsAppHost) {
|
||||
await loadAgents(app);
|
||||
await loadConfig(app);
|
||||
const agentIds = host.agentsList?.agents?.map((entry) => entry.id) ?? [];
|
||||
@@ -257,7 +283,7 @@ async function refreshAgentsTab(host: SettingsHost, app: OpenClawApp) {
|
||||
}
|
||||
|
||||
export async function refreshActiveTab(host: SettingsHost) {
|
||||
const app = host as unknown as OpenClawApp;
|
||||
const app = host as unknown as SettingsAppHost;
|
||||
switch (host.tab) {
|
||||
case "config":
|
||||
case "communications":
|
||||
@@ -547,7 +573,7 @@ export function hasMissingSkillDependencies(
|
||||
return Object.values(missing).some((value) => Array.isArray(value) && value.length > 0);
|
||||
}
|
||||
|
||||
async function loadOverviewLogs(host: OpenClawApp) {
|
||||
async function loadOverviewLogs(host: SettingsAppHost) {
|
||||
if (!host.client || !host.connected) {
|
||||
return;
|
||||
}
|
||||
@@ -573,7 +599,7 @@ async function loadOverviewLogs(host: OpenClawApp) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildAttentionItems(host: OpenClawApp) {
|
||||
function buildAttentionItems(host: SettingsAppHost) {
|
||||
const items: AttentionItem[] = [];
|
||||
|
||||
if (host.lastError) {
|
||||
@@ -650,12 +676,12 @@ function buildAttentionItems(host: OpenClawApp) {
|
||||
}
|
||||
|
||||
export async function loadChannelsTab(host: SettingsHost) {
|
||||
const app = host as unknown as OpenClawApp;
|
||||
const app = host as unknown as SettingsAppHost;
|
||||
await Promise.all([loadChannels(app, true), loadConfigSchema(app), loadConfig(app)]);
|
||||
}
|
||||
|
||||
export async function loadCron(host: SettingsHost) {
|
||||
const app = host as unknown as OpenClawApp;
|
||||
const app = host as unknown as SettingsAppHost;
|
||||
const activeCronJobId = app.cronRunsScope === "job" ? app.cronRunsJobId : null;
|
||||
await Promise.all([
|
||||
loadChannels(app, false),
|
||||
|
||||
Reference in New Issue
Block a user