




















@@ -2,6 +2,7 @@
22import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
33import { randomUUID } from "node:crypto";
44import path from "node:path";
5+import { setTimeout as sleep } from "node:timers/promises";
56import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
67import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
78import {
@@ -14,6 +15,7 @@ import {
1415readQaChildOutput,
1516} from "./child-output.js";
1617import { QaSuiteInfraError } from "./errors.js";
18+import { extractGatewayMessageText } from "./gateway-log-sentinel.js";
1719import { resolveQaNodeExecPath } from "./node-exec.js";
1820import { liveTurnTimeoutMs } from "./suite-runtime-agent-common.js";
1921import { waitForGatewayHealthy, waitForTransportReady } from "./suite-runtime-gateway.js";
@@ -35,11 +37,19 @@ type QaCronJob = {
3537state?: { nextRunAtMs?: number };
3638};
373940+type QaChatHistoryResponse = {
41+messages?: unknown[];
42+};
43+3844const ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\x1B\[[0-?]*[ -/]*[@-~]`, "g");
3945const MANAGED_DREAMING_CRON_MARKER = "[managed-by=memory-core.short-term-promotion]";
4046const MANAGED_DREAMING_CRON_NAME = "Memory Dreaming Promotion";
4147const MANAGED_DREAMING_PROMPT = "__openclaw_memory_core_short_term_promotion_dream__";
424849+function isRecord(value: unknown): value is Record<string, unknown> {
50+return typeof value === "object" && value !== null && !Array.isArray(value);
51+}
52+4353function stripAnsiCodes(text: string) {
4454return text.replace(ANSI_ESCAPE_PATTERN, "");
4555}
@@ -368,6 +378,58 @@ async function waitForAgentRun(
368378}
369379}
370380381+function readLatestAssistantTextFromHistory(history: QaChatHistoryResponse | undefined) {
382+for (const message of [...(history?.messages ?? [])].reverse()) {
383+if (!isRecord(message) || message.role !== "assistant") {
384+continue;
385+}
386+const text = extractGatewayMessageText(message);
387+if (text) {
388+return text;
389+}
390+}
391+return undefined;
392+}
393+394+async function readLatestAgentHistoryReply(
395+env: Pick<QaSuiteRuntimeEnv, "gateway">,
396+sessionKey: string,
397+) {
398+const history = (await env.gateway.call(
399+"chat.history",
400+{
401+ sessionKey,
402+limit: 12,
403+},
404+{
405+timeoutMs: 10_000,
406+},
407+)) as QaChatHistoryResponse | undefined;
408+return readLatestAssistantTextFromHistory(history);
409+}
410+411+async function waitForAgentHistoryReply(
412+env: Pick<QaSuiteRuntimeEnv, "gateway">,
413+sessionKey: string,
414+predicate: (text: string) => boolean | Promise<boolean>,
415+timeoutMs = 30_000,
416+intervalMs = 250,
417+) {
418+const startedAt = Date.now();
419+while (Date.now() - startedAt < timeoutMs) {
420+const text = await readLatestAgentHistoryReply(env, sessionKey);
421+if (text && (await predicate(text))) {
422+return { text };
423+}
424+const remainingMs = timeoutMs - (Date.now() - startedAt);
425+if (remainingMs <= 0) {
426+break;
427+}
428+await sleep(Math.min(intervalMs, remainingMs));
429+}
430+throw new Error(`timed out after ${timeoutMs}ms`);
431+}
432+371433async function listCronJobs(env: Pick<QaSuiteRuntimeEnv, "gateway">) {
372434const payload = (await env.gateway.call(
373435"cron.list",
@@ -501,6 +563,7 @@ export {
501563runAgentPrompt,
502564runQaCli,
503565startAgentRun,
566+waitForAgentHistoryReply,
504567waitForMemorySearchMatch,
505568waitForAgentRun,
506569};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。