






















@@ -1,6 +1,7 @@
11import { randomUUID } from "node:crypto";
22import path from "node:path";
33import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
4+import { resolveDefaultModelForAgent } from "../agents/model-selection.js";
45import { runEmbeddedPiAgent, type EmbeddedPiRunResult } from "../agents/pi-embedded.js";
56import type { OpenClawConfig } from "../config/config.js";
67import { resolveStateDir } from "../config/paths.js";
@@ -41,12 +42,14 @@ export type CommitmentExtractionRuntime = {
4142};
42434344const log = createSubsystemLogger("commitments");
45+const TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS = 15 * 60_000;
44464547let runtime: CommitmentExtractionRuntime = {};
4648let queue: Array<Omit<CommitmentExtractionItem, "existingPending"> & { cfg?: OpenClawConfig }> = [];
4749let timer: TimerHandle | null = null;
4850let draining = false;
4951let queueOverflowWarned = false;
52+let terminalFailureCooldownUntilByAgent = new Map<string, number>();
50535154function shouldDisableBackgroundExtractionForTests(): boolean {
5255if (runtime.forceInTests) {
@@ -82,6 +85,7 @@ export function resetCommitmentExtractionRuntimeForTests(): void {
8285timer = null;
8386draining = false;
8487queueOverflowWarned = false;
88+terminalFailureCooldownUntilByAgent = new Map();
8589}
86908791function buildItemId(params: CommitmentExtractionEnqueueInput, nowMs: number): string {
@@ -95,14 +99,19 @@ function isUsefulText(value: string | undefined): boolean {
959996100export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueInput): boolean {
97101const resolved = resolveCommitmentsConfig(input.cfg);
102+const nowMs = input.nowMs ?? Date.now();
103+const agentId = normalizeOptionalString(input.agentId) ?? "";
104+const sessionKey = normalizeOptionalString(input.sessionKey) ?? "";
105+const channel = normalizeOptionalString(input.channel) ?? "";
98106if (
99107!resolved.enabled ||
100108shouldDisableBackgroundExtractionForTests() ||
109+(agentId ? nowMs < (terminalFailureCooldownUntilByAgent.get(agentId) ?? 0) : false) ||
101110!isUsefulText(input.userText) ||
102111!isUsefulText(input.assistantText) ||
103-!input.agentId.trim() ||
104-!input.sessionKey.trim() ||
105-!input.channel.trim()
112+!agentId ||
113+!sessionKey ||
114+!channel
106115) {
107116return false;
108117}
@@ -116,14 +125,13 @@ export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueIn
116125}
117126return false;
118127}
119-const nowMs = input.nowMs ?? Date.now();
120128queue.push({
121129itemId: buildItemId(input, nowMs),
122130 nowMs,
123131timezone: resolveCommitmentTimezone(input.cfg),
124-agentId: input.agentId.trim(),
125-sessionKey: input.sessionKey.trim(),
126-channel: input.channel.trim(),
132+ agentId,
133+ sessionKey,
134+ channel,
127135 ...(input.accountId?.trim() ? { accountId: input.accountId.trim() } : {}),
128136 ...(input.to?.trim() ? { to: input.to.trim() } : {}),
129137 ...(input.threadId?.trim() ? { threadId: input.threadId.trim() } : {}),
@@ -145,6 +153,33 @@ export function enqueueCommitmentExtraction(input: CommitmentExtractionEnqueueIn
145153return true;
146154}
147155156+function isTerminalExtractionError(error: unknown): boolean {
157+const message = error instanceof Error ? error.message : String(error);
158+return (
159+/\bNo API key found\b/i.test(message) ||
160+/\bUnknown model\b/i.test(message) ||
161+/\bAuth profile credentials are missing or expired\b/i.test(message) ||
162+/\bOAuth token refresh failed\b/i.test(message) ||
163+/\bmissing credential\b/i.test(message) ||
164+/\bmissing credentials\b/i.test(message) ||
165+/\bmissing_api_key\b/i.test(message) ||
166+/\binvalid_grant\b/i.test(message)
167+);
168+}
169+170+function openTerminalFailureCooldown(agentId: string, error: unknown): void {
171+terminalFailureCooldownUntilByAgent.set(
172+agentId,
173+Date.now() + TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS,
174+);
175+queue = queue.filter((item) => item.agentId !== agentId);
176+log.warn("commitment extraction disabled temporarily after terminal model/auth failure", {
177+ agentId,
178+cooldownMs: TERMINAL_EXTRACTION_FAILURE_COOLDOWN_MS,
179+error: String(error),
180+});
181+}
182+148183function resolveExtractionSessionFile(agentId: string, runId: string): string {
149184return path.join(
150185resolveStateDir(),
@@ -176,6 +211,7 @@ async function defaultExtractBatch(params: {
176211}
177212const resolved = resolveCommitmentsConfig(cfg);
178213const runId = `commitments-${randomUUID()}`;
214+const modelRef = resolveDefaultModelForAgent({ cfg, agentId: first.agentId });
179215const result = await runEmbeddedPiAgent({
180216sessionId: runId,
181217sessionKey: `agent:${first.agentId}:commitments:${runId}`,
@@ -184,6 +220,8 @@ async function defaultExtractBatch(params: {
184220sessionFile: resolveExtractionSessionFile(first.agentId, runId),
185221workspaceDir: resolveAgentWorkspaceDir(cfg, first.agentId),
186222config: cfg,
223+provider: modelRef.provider,
224+model: modelRef.model,
187225prompt: buildCommitmentExtractionPrompt({ cfg, items: params.items }),
188226disableTools: true,
189227thinkLevel: "off",
@@ -225,7 +263,15 @@ export async function drainCommitmentExtractionQueue(): Promise<number> {
225263const batch = queue.splice(0, resolved.extraction.batchMaxItems);
226264const items = await hydrateBatch(batch);
227265const extractor = runtime.extractBatch ?? defaultExtractBatch;
228-const result = await extractor({ cfg: firstCfg, items });
266+let result: CommitmentExtractionBatchResult;
267+try {
268+result = await extractor({ cfg: firstCfg, items });
269+} catch (error) {
270+if (isTerminalExtractionError(error)) {
271+openTerminalFailureCooldown(items[0]?.agentId ?? "", error);
272+}
273+throw error;
274+}
229275await persistCommitmentExtractionResult({
230276cfg: firstCfg,
231277 items,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。