

























@@ -1,18 +1,55 @@
11import fs from "node:fs/promises";
22import path from "node:path";
3+import { setTimeout as sleep } from "node:timers/promises";
4+import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
35import { liveTurnTimeoutMs } from "./suite-runtime-agent-common.js";
46import type {
57QaRawSessionStoreEntry,
68QaSkillStatusEntry,
79QaSuiteRuntimeEnv,
810} from "./suite-runtime-types.js";
91110-async function createSession(
11-env: Pick<QaSuiteRuntimeEnv, "gateway" | "primaryModel" | "alternateModel" | "providerMode">,
12-label: string,
13-key?: string,
12+type QaGatewayCallEnv = Pick<
13+QaSuiteRuntimeEnv,
14+"gateway" | "primaryModel" | "alternateModel" | "providerMode"
15+>;
16+17+const SESSION_STORE_LOCK_RETRY_DELAYS_MS = [1_000, 3_000, 5_000] as const;
18+19+function isSessionStoreLockTimeout(error: unknown) {
20+const text = formatErrorMessage(error);
21+return (
22+text.includes("OPENCLAW_SESSION_WRITE_LOCK_TIMEOUT") ||
23+text.includes("SessionWriteLockTimeoutError") ||
24+text.includes("session file locked")
25+);
26+}
27+28+async function callGatewayWithSessionStoreLockRetry<T>(
29+env: QaGatewayCallEnv,
30+method: string,
31+params: Record<string, unknown>,
32+options: { timeoutMs: number },
1433) {
15-const created = (await env.gateway.call(
34+for (let attempt = 0; attempt <= SESSION_STORE_LOCK_RETRY_DELAYS_MS.length; attempt += 1) {
35+try {
36+return (await env.gateway.call(method, params, options)) as T;
37+} catch (error) {
38+if (
39+!isSessionStoreLockTimeout(error) ||
40+attempt === SESSION_STORE_LOCK_RETRY_DELAYS_MS.length
41+) {
42+throw error;
43+}
44+await sleep(SESSION_STORE_LOCK_RETRY_DELAYS_MS[attempt]);
45+}
46+}
47+throw new Error(`${method} failed after session store lock retries`);
48+}
49+50+async function createSession(env: QaGatewayCallEnv, label: string, key?: string) {
51+const created = await callGatewayWithSessionStoreLockRetry<{ key?: string }>(
52+env,
1653"sessions.create",
1754{
1855 label,
@@ -21,29 +58,27 @@ async function createSession(
2158{
2259timeoutMs: liveTurnTimeoutMs(env, 60_000),
2360},
24-)) as { key?: string };
61+);
2562const sessionKey = created.key?.trim();
2663if (!sessionKey) {
2764throw new Error("sessions.create returned no key");
2865}
2966return sessionKey;
3067}
316832-async function readEffectiveTools(
33-env: Pick<QaSuiteRuntimeEnv, "gateway" | "primaryModel" | "alternateModel" | "providerMode">,
34-sessionKey: string,
35-) {
36-const payload = (await env.gateway.call(
69+async function readEffectiveTools(env: QaGatewayCallEnv, sessionKey: string) {
70+const payload = await callGatewayWithSessionStoreLockRetry<{
71+ groups?: Array<{ tools?: Array<{ id?: string }> }>;
72+ }>(
73+ env,
3774"tools.effective",
3875{
3976 sessionKey,
4077},
4178{
4279timeoutMs: liveTurnTimeoutMs(env, 90_000),
4380},
44-)) as {
45-groups?: Array<{ tools?: Array<{ id?: string }> }>;
46-};
81+);
4782const ids = new Set<string>();
4883for (const group of payload.groups ?? []) {
4984for (const tool of group.tools ?? []) {
@@ -55,21 +90,19 @@ async function readEffectiveTools(
5590return ids;
5691}
579258-async function readSkillStatus(
59-env: Pick<QaSuiteRuntimeEnv, "gateway" | "primaryModel" | "alternateModel" | "providerMode">,
60-agentId = "qa",
61-) {
62-const payload = (await env.gateway.call(
93+async function readSkillStatus(env: QaGatewayCallEnv, agentId = "qa") {
94+const payload = await callGatewayWithSessionStoreLockRetry<{
95+ skills?: QaSkillStatusEntry[];
96+ }>(
97+ env,
6398"skills.status",
6499{
65100 agentId,
66101},
67102{
68103timeoutMs: liveTurnTimeoutMs(env, 45_000),
69104},
70-)) as {
71-skills?: QaSkillStatusEntry[];
72-};
105+);
73106return payload.skills ?? [];
74107}
75108此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。