mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
4273ca9dbd
* refactor(sessions): keep helper transcripts in memory * refactor(sessions): remove file-era transcript storage * test(sessions): use SQLite identity in attempt persistence * test(codex): isolate legacy transcript fixtures * fix(sessions): preserve SQLite transcript identity * fix(sessions): harden transcript lifecycle invariants * fix(sessions): validate transcript identities * fix(sessions): close identity compatibility gaps * fix(sessions): preserve leaf and plugin identities * fix(sessions): retain dispatch transcript targets * fix(sessions): preserve active transcript context * fix(sessions): isolate artifact accounting * fix(sessions): bound SQLite usage accounting * fix(sessions): retain bounded latest usage * fix(sessions): align rebased transcript targets * test(sessions): align accessor scope fixture * fix(telegram): derive SQLite transcript identity * refactor(sessions): remove file-era compaction residue * chore(sessions): lower max-lines baseline * fix(sessions): preserve structured transcript identity * test(sessions): align doctor identity assertions * fix(sessions): isolate default SDK database * refactor(sessions): remove dead file-era exports * fix(sessions): reconcile SQLite transcript identity * fix(sessions): pass checkpoint identity explicitly * test(sessions): make entry field probe explicit * test(sessions): satisfy transcript cleanup lint * test(sessions): align diagnostics identity proof * fix(sessions): finish transcript runtime teardown * fix(sessions): preserve transcript identity invariants * fix(sessions): harden transcript compatibility edges * fix(sessions): preserve checkpoint transcript anchors * fix(sessions): preserve SQLite lifecycle invariants * fix(sessions): retarget compaction successors * test(sessions): preserve transcript fixture semantics * feat(plugin-sdk): add command transcript targets * fix(sessions): serialize transcript rewrites * fix(sessions): validate legacy successor identity * fix(sessions): normalize compaction ownership * fix(sessions): validate successor identity before adoption * fix(sessions): preserve plugin transcript ownership * fix(sessions): carry transcript identity through commands * fix(sessions): import legacy checkpoint artifacts into SQLite * fix(sessions): preserve successor transcript ownership * fix(sessions): align transcript consumers with target identity * fix(sessions): scope transcript token estimates * fix(sessions): retain agent identity across lifecycle hooks * fix(sessions): resolve scoped SQLite targets * fix(sessions): isolate lifecycle transcript targets * fix(sessions): validate compaction agent ownership * fix(sessions): preserve reset and cleanup lifecycle * fix(sessions): serialize prompt cleanup lifecycle * fix(sessions): remove stale lock import * fix(sessions): preserve reset target context * fix(sessions): fence prompt reload takeover * fix(sessions): unblock abort and default lifecycle reads * fix(sessions): validate legacy successor scope * fix(sessions): reject metadata-only runtime rows * fix(sessions): propagate custom transcript stores * fix(sessions): preserve adopted retry targets * fix(sessions): allow unkeyed usage reads * fix(sessions): harden runtime target boundaries * fix(sessions): serialize retry transcript writes * fix(sessions): bound prompt reload disposal * fix(sessions): complete retry marker identity * fix(sessions): keep legacy marker identity minimal * test(sessions): tighten teardown fixture types * fix(sessions): preserve compatibility target identity * test(sessions): persist post-checkpoint boundary turn * test(sessions): align runtime store mock contracts * style(sessions): simplify persisted identity guard * fix(sessions): prefer complete typed targets * fix(sessions): recover legacy marker targets * test(sessions): align marker lookup fixture scope * fix(sessions): validate partial transcript targets * fix(sessions): reconcile partial transcript identities * fix(sessions): canonicalize compatibility identities * test(sessions): cover compatibility aliases * fix(sessions): adopt legacy successor identity * fix(sessions): preserve usage read identity * fix(sessions): preserve partial marker compatibility * fix(sessions): validate legacy successor mappings * fix(sessions): reconcile marker store mappings * fix(sessions): preserve legacy fallback identity * fix(sessions): harden marker alias resolution * fix(sessions): prefer verified successor aliases * fix(sessions): resolve preferred marker aliases * fix(sessions): serialize cleanup admission * fix(sessions): align marker lookup scopes * fix(codex): type marker alias summaries * style(sessions): satisfy changed lint * test(sessions): align structured target assertions * fix(sessions): reconcile latest identity contracts * fix(sessions): validate transcript identity boundaries * docs(sessions): explain stable registry keys * fix(sessions): harden compatibility target round trips * fix(sessions): port usage identity to split modules * test(sessions): align subagent transcript identity * fix(sessions): finish transcript identity migration * fix(agents): route subagent completion capture through transcript targets * fix(agents): settle SQLite prompt handoff during cleanup * chore: shrink max-lines baseline after teardown * fix(sessions): port teardown across split runtime owners * fix(sessions): carry transcript targets through split owners * test(agents): use SQLite compaction target in abort coverage * chore: retain unrelated max-lines suppressions * chore: shrink max-lines baseline after main splits * style(agents): const compaction checkpoint locals * fix(sessions): harden SQLite teardown boundaries * test(sessions): use typed metadata in predicate isolation fixture * test(agents): cover malformed settlement rejections lint-safely * fix(sessions): close remaining SQLite identity races * fix(agents): fail closed on incomplete successor targets * fix(sessions): preserve transcript identity fallbacks * fix(agents): preserve session-key abort admission * fix(trajectory): validate incomplete export targets * test(sessions): drop retired pricing cache imports * fix(sessions): validate partial transcript identities * fix(sessions): close transcript identity edge cases * fix(plugins): reserve retired transcript locator slot * fix(sessions): scope transcript locks by target * style(sessions): simplify SDK initialization error * fix(sessions): preserve initialized transcript state * fix(codex): verify mirrored history session keys * fix(sessions): reject stale transcript ownership * fix(sessions): anchor asynchronous transcript ownership * fix(sessions): measure active transcript state * fix(sessions): preserve scoped transcript compaction * fix(sessions): harden transcript identity and lifecycle * fix(sessions): resolve scoped command transcript stores * fix(sessions): make transcript appends failure-atomic * fix(sessions): enforce scoped transcript ownership * fix(sessions): reject cross-owner transcript handoffs * fix(sessions): fence cleanup transcript ownership * fix(sessions): retire stale write ownership contexts * fix(sessions): preserve pending session migration state * fix(sessions): validate migrated transcript ownership * fix(sessions): validate usage transcript targets * fix(sessions): clear predecessor transcript metadata * fix(sessions): align durable session event targets * fix(sessions): fence late prompt handoffs * fix(sessions): fence lifecycle transcript fallbacks * fix(sessions): bound zero-length memory capture * fix(sessions): preserve transcript teardown ownership * fix(sessions): reject duplicate cleanup ownership * fix(sessions): serialize runtime writes with sqlite leases * fix(sessions): close sqlite teardown concurrency gaps * fix(sessions): preserve nested lifecycle failures * fix(sessions): canonicalize sqlite transcript ownership * fix(sessions): settle disposed prompt handoffs * fix(sessions): resolve canonical attempt lock targets * test(sessions): align canonical target fixtures * test(sessions): retire redundant jsonl parser coverage * refactor(sessions): split active transcript cursors * test(memory): retire legacy marker fixture * fix(sessions): preserve canonical transcript access after rebase * fix(sessions): fence prompt lease and return transcript targets * fix(sessions): colocate transcript leases with target store * fix(sessions): canonicalize transcript lease and worker targets * fix(sessions): preserve plugin and fork identity markers * fix(sessions): complete sqlite transcript target migration * fix(sessions): integrate canonical followup identity * fix(sessions): preserve bounded transcript topology * fix(sessions): validate transcript identity boundaries * fix(context): separate caller and successor targets * test(sessions): split persistence compatibility coverage * test(sessions): preserve fixture topology efficiently * chore(sdk): refresh plugin api baseline * test(agents): align compaction lock target mocks * test(sessions): seed malformed transcript fixtures directly * fix(agents): canonicalize transcript compatibility inputs * fix(agents): type optional tool result ids * test(ci): stabilize loaded process timing * test(tui): wait for collect queue admission
383 lines
14 KiB
TypeScript
383 lines
14 KiB
TypeScript
/**
|
|
* Settles async tools and compaction, then snapshots the completed stream.
|
|
*/
|
|
import { formatErrorMessage } from "../../../infra/errors.js";
|
|
import type { AssistantMessage } from "../../../llm/types.js";
|
|
import type { AgentRunAttemptFailureSource } from "../../agent-run-terminal-outcome.js";
|
|
import type { subscribeEmbeddedAgentSession } from "../../embedded-agent-subscribe.js";
|
|
import type { AgentMessage } from "../../runtime/index.js";
|
|
import type { AgentSession, SessionManager } from "../../sessions/index.js";
|
|
import { projectToolSearchTargetTranscriptMessages } from "../../tool-search.js";
|
|
import { hasNonzeroUsage, normalizeUsage, type NormalizedUsage } from "../../usage.js";
|
|
import { isRunnerAbortError } from "../abort.js";
|
|
import { isCacheTtlEligibleProvider, readLastCacheTtlTimestamp } from "../cache-ttl.js";
|
|
import { log } from "../logger.js";
|
|
import {
|
|
completePromptCacheObservation,
|
|
type PromptCacheBreak,
|
|
type PromptCacheChange,
|
|
} from "../prompt-cache-observability.js";
|
|
import {
|
|
flushSessionManagerTranscript,
|
|
normalizeCompactionRecoveryTranscriptTail,
|
|
} from "./attempt-transcript-helpers.js";
|
|
import {
|
|
shouldWaitForCompletionRequiredAsyncTasks,
|
|
waitForCompletionRequiredAsyncTasks,
|
|
type CompletionRequiredAsyncTaskWaitResult,
|
|
} from "./attempt.async-tasks.js";
|
|
import {
|
|
buildContextEnginePromptCacheInfo,
|
|
findCurrentAttemptAssistantMessage,
|
|
findLatestUncompactedAttemptUsageSnapshot,
|
|
resolvePromptCacheTouchTimestamp,
|
|
} from "./attempt.context-engine-helpers.js";
|
|
import type { createEmbeddedAttemptSessionLockController } from "./attempt.session-lock.js";
|
|
import { appendAttemptCacheTtlIfNeeded } from "./attempt.thread-helpers.js";
|
|
import {
|
|
hasActiveCompactionRetryWork,
|
|
waitForCompactionRetryWithAggregateTimeout,
|
|
} from "./compaction-retry-aggregate-timeout.js";
|
|
import { selectCompactionTimeoutSnapshot } from "./compaction-timeout.js";
|
|
import type { EmbeddedRunAttemptParams, EmbeddedRunAttemptResult } from "./types.js";
|
|
|
|
type EmbeddedAttemptSubscription = ReturnType<typeof subscribeEmbeddedAgentSession>;
|
|
type AttemptSessionLockController = Awaited<
|
|
ReturnType<typeof createEmbeddedAttemptSessionLockController>
|
|
>;
|
|
type PromptCacheRetention = Parameters<typeof buildContextEnginePromptCacheInfo>[0]["retention"];
|
|
type ToolSearchTargetTranscriptProjections = Parameters<
|
|
typeof projectToolSearchTargetTranscriptMessages
|
|
>[1];
|
|
type WithOwnedSessionWriteLock = <T>(operation: () => Promise<T> | T) => Promise<T>;
|
|
|
|
type StreamSettleResult = {
|
|
promptError: unknown;
|
|
promptErrorSource: AgentRunAttemptFailureSource | null;
|
|
timedOutDuringCompaction: boolean;
|
|
compactionOccurredThisAttempt: boolean;
|
|
messagesSnapshot: AgentMessage[];
|
|
sessionIdUsed: string;
|
|
lastAssistant: EmbeddedRunAttemptResult["lastAssistant"];
|
|
currentAttemptAssistant: EmbeddedRunAttemptResult["currentAttemptAssistant"];
|
|
currentAttemptCompletedAssistant: EmbeddedRunAttemptResult["currentAttemptCompletedAssistant"];
|
|
attemptUsage: EmbeddedRunAttemptResult["attemptUsage"];
|
|
cacheBreak: PromptCacheBreak | null;
|
|
lastCallUsage: NormalizedUsage | undefined;
|
|
promptCache: EmbeddedRunAttemptResult["promptCache"];
|
|
};
|
|
|
|
export async function settleEmbeddedAttemptStream(input: {
|
|
attempt: EmbeddedRunAttemptParams;
|
|
activeSession: AgentSession;
|
|
sessionManager: SessionManager;
|
|
sessionLockController: AttemptSessionLockController;
|
|
withOwnedSessionWriteLock: WithOwnedSessionWriteLock;
|
|
subscription: EmbeddedAttemptSubscription;
|
|
state: {
|
|
promptError: unknown;
|
|
promptErrorSource: AgentRunAttemptFailureSource | null;
|
|
yieldAborted: boolean;
|
|
sessionIdUsed: string;
|
|
};
|
|
readLifecycleState: () => {
|
|
aborted: boolean;
|
|
timedOut: boolean;
|
|
timedOutDuringCompaction: boolean;
|
|
};
|
|
markTimedOutDuringCompaction: () => void;
|
|
runAbortDeadlineAtMs: number;
|
|
runAbortSignal: AbortSignal;
|
|
isProbeSession: boolean;
|
|
onBlockReplyFlush?: (payload: {
|
|
reason: "pre_compaction";
|
|
attemptAccepted: boolean;
|
|
}) => Promise<void> | void;
|
|
abortable: <T>(promise: Promise<T>) => Promise<T>;
|
|
prePromptMessageCount: number;
|
|
toolSearchTargetTranscriptProjections: ToolSearchTargetTranscriptProjections;
|
|
cache: {
|
|
observabilityEnabled: boolean;
|
|
changesForTurn: PromptCacheChange[] | null;
|
|
retention: PromptCacheRetention;
|
|
};
|
|
shouldFlushForContextEngine: boolean;
|
|
}): Promise<StreamSettleResult> {
|
|
const { attempt, activeSession, sessionManager, subscription, state } = input;
|
|
let { promptError, promptErrorSource, sessionIdUsed } = state;
|
|
|
|
if (
|
|
shouldWaitForCompletionRequiredAsyncTasks({
|
|
sessionKey: attempt.sessionKey,
|
|
toolMetas: subscription.toolMetas,
|
|
yieldDetected: state.yieldAborted,
|
|
})
|
|
) {
|
|
const getAsyncStartedToolMetas = () =>
|
|
subscription.toolMetas
|
|
.filter(
|
|
(
|
|
entry,
|
|
): entry is {
|
|
toolName: string;
|
|
asyncStarted?: boolean;
|
|
asyncTaskRunId?: string;
|
|
asyncTaskId?: string;
|
|
} => typeof entry.toolName === "string" && entry.toolName.trim().length > 0,
|
|
)
|
|
.map((entry) => ({
|
|
toolName: entry.toolName,
|
|
asyncStarted: entry.asyncStarted,
|
|
asyncTaskRunId: entry.asyncTaskRunId,
|
|
asyncTaskId: entry.asyncTaskId,
|
|
}));
|
|
const completionRequiredAsyncDeadlineAtMs = Math.max(
|
|
Date.now(),
|
|
input.runAbortDeadlineAtMs - 500,
|
|
);
|
|
let asyncTaskWait: CompletionRequiredAsyncTaskWaitResult;
|
|
try {
|
|
asyncTaskWait = await waitForCompletionRequiredAsyncTasks({
|
|
getToolMetas: getAsyncStartedToolMetas,
|
|
sessionKey: attempt.sessionKey,
|
|
deadlineAtMs: completionRequiredAsyncDeadlineAtMs,
|
|
abortSignal: input.runAbortSignal,
|
|
});
|
|
} catch (err) {
|
|
if (!input.readLifecycleState().timedOut || !isRunnerAbortError(err)) {
|
|
throw err;
|
|
}
|
|
asyncTaskWait = await waitForCompletionRequiredAsyncTasks({
|
|
getToolMetas: getAsyncStartedToolMetas,
|
|
sessionKey: attempt.sessionKey,
|
|
deadlineAtMs: Date.now(),
|
|
});
|
|
}
|
|
if (asyncTaskWait.timedOutRunIds.length > 0) {
|
|
promptError = new Error(
|
|
`Timed out waiting for async task completion: ${asyncTaskWait.timedOutRunIds.join(", ")}`,
|
|
);
|
|
promptErrorSource = "prompt";
|
|
state.promptError = promptError;
|
|
state.promptErrorSource = promptErrorSource;
|
|
}
|
|
}
|
|
|
|
// Snapshot only outside compaction. Compaction rewrites history in place and
|
|
// cannot be allowed to leave the timeout result with a half-written view.
|
|
const wasCompactingBefore = activeSession.isCompacting;
|
|
const snapshot = activeSession.messages.slice();
|
|
const wasCompactingAfter = activeSession.isCompacting;
|
|
const preCompactionSnapshot = wasCompactingBefore || wasCompactingAfter ? null : snapshot;
|
|
const preCompactionSessionId = activeSession.sessionId;
|
|
const aggregateTimeoutMs = 60_000;
|
|
|
|
try {
|
|
if (input.onBlockReplyFlush) {
|
|
const currentAssistant = findCurrentAttemptAssistantMessage({
|
|
messagesSnapshot: snapshot,
|
|
prePromptMessageCount: input.prePromptMessageCount,
|
|
});
|
|
const attemptAccepted =
|
|
!promptError &&
|
|
!input.readLifecycleState().aborted &&
|
|
!input.readLifecycleState().timedOut &&
|
|
!state.yieldAborted &&
|
|
currentAssistant?.stopReason === "stop";
|
|
await input.onBlockReplyFlush({ reason: "pre_compaction", attemptAccepted });
|
|
}
|
|
|
|
const compactionRetryWait = state.yieldAborted
|
|
? { timedOut: false }
|
|
: await waitForCompactionRetryWithAggregateTimeout({
|
|
waitForCompactionRetry: subscription.waitForCompactionRetry,
|
|
abortable: input.abortable,
|
|
aggregateTimeoutMs,
|
|
isCompactionRetryStillActive: () =>
|
|
hasActiveCompactionRetryWork({
|
|
isCompactionInFlight: subscription.isCompactionInFlight(),
|
|
isSessionStreaming: activeSession.isStreaming,
|
|
}),
|
|
});
|
|
if (compactionRetryWait.timedOut) {
|
|
input.markTimedOutDuringCompaction();
|
|
if (!input.isProbeSession) {
|
|
log.warn(
|
|
`compaction retry aggregate timeout (${aggregateTimeoutMs}ms): ` +
|
|
`proceeding with pre-compaction state runId=${attempt.runId} sessionId=${attempt.sessionId}`,
|
|
);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (!isRunnerAbortError(err)) {
|
|
throw err;
|
|
}
|
|
if (!promptError) {
|
|
promptError = err;
|
|
promptErrorSource = "compaction";
|
|
state.promptError = promptError;
|
|
state.promptErrorSource = promptErrorSource;
|
|
}
|
|
if (!input.isProbeSession) {
|
|
log.debug(`compaction wait aborted: runId=${attempt.runId} sessionId=${attempt.sessionId}`);
|
|
}
|
|
}
|
|
|
|
let compactionOccurredThisAttempt = false;
|
|
let messagesSnapshot: AgentMessage[] = [];
|
|
let lastAssistant: AssistantMessage | undefined;
|
|
let currentAttemptAssistant: AssistantMessage | undefined;
|
|
let currentAttemptCompletedAssistant: AssistantMessage | undefined;
|
|
let attemptUsage: EmbeddedRunAttemptResult["attemptUsage"];
|
|
let cacheBreak: PromptCacheBreak | null = null;
|
|
let lastCallUsage: NormalizedUsage | undefined;
|
|
let promptCache: EmbeddedRunAttemptResult["promptCache"];
|
|
|
|
await input.withOwnedSessionWriteLock(async () => {
|
|
const { timedOutDuringCompaction } = input.readLifecycleState();
|
|
compactionOccurredThisAttempt = subscription.getCompactionCount() > 0;
|
|
appendAttemptCacheTtlIfNeeded({
|
|
sessionManager,
|
|
timedOutDuringCompaction,
|
|
compactionOccurredThisAttempt,
|
|
config: attempt.config,
|
|
provider: attempt.provider,
|
|
modelId: attempt.modelId,
|
|
modelApi: attempt.model.api,
|
|
isCacheTtlEligibleProvider,
|
|
});
|
|
|
|
if (timedOutDuringCompaction) {
|
|
const removedEntries = normalizeCompactionRecoveryTranscriptTail({
|
|
activeSession,
|
|
sessionManager,
|
|
});
|
|
if (removedEntries > 0 && !input.isProbeSession) {
|
|
log.warn(
|
|
`normalized compaction timeout transcript tail: removedEntries=${removedEntries} ` +
|
|
`runId=${attempt.runId} sessionId=${attempt.sessionId}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const snapshotSelection = selectCompactionTimeoutSnapshot({
|
|
timedOutDuringCompaction,
|
|
preCompactionSnapshot,
|
|
preCompactionSessionId,
|
|
currentSnapshot: activeSession.messages.slice(),
|
|
currentSessionId: activeSession.sessionId,
|
|
});
|
|
if (timedOutDuringCompaction && !input.isProbeSession) {
|
|
log.warn(
|
|
`using ${snapshotSelection.source} snapshot: timed out during compaction ` +
|
|
`runId=${attempt.runId} sessionId=${attempt.sessionId}`,
|
|
);
|
|
}
|
|
messagesSnapshot = projectToolSearchTargetTranscriptMessages(
|
|
snapshotSelection.messagesSnapshot,
|
|
input.toolSearchTargetTranscriptProjections,
|
|
);
|
|
sessionIdUsed = snapshotSelection.sessionIdUsed;
|
|
lastAssistant = messagesSnapshot
|
|
.slice()
|
|
.toReversed()
|
|
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
currentAttemptAssistant = findCurrentAttemptAssistantMessage({
|
|
messagesSnapshot,
|
|
prePromptMessageCount: input.prePromptMessageCount,
|
|
});
|
|
currentAttemptCompletedAssistant = subscription.getCurrentAttemptAssistant();
|
|
attemptUsage = subscription.getUsageTotals();
|
|
cacheBreak = input.cache.observabilityEnabled
|
|
? completePromptCacheObservation({
|
|
sessionId: attempt.sessionId,
|
|
promptCacheKey: attempt.promptCacheKey,
|
|
sessionKey: attempt.sessionKey,
|
|
usage: attemptUsage,
|
|
})
|
|
: null;
|
|
const transcriptUsageSnapshot = findLatestUncompactedAttemptUsageSnapshot({
|
|
messagesSnapshot,
|
|
prePromptMessageCount: input.prePromptMessageCount,
|
|
compactionOccurred: compactionOccurredThisAttempt,
|
|
});
|
|
const completedAssistantUsage = normalizeUsage(currentAttemptCompletedAssistant?.usage);
|
|
lastCallUsage =
|
|
subscription.getLastAssistantUsage() ??
|
|
(hasNonzeroUsage(completedAssistantUsage)
|
|
? completedAssistantUsage
|
|
: transcriptUsageSnapshot?.usage);
|
|
// Keep cache timing bound to the assistant that supplied the exact usage.
|
|
// A terminal zero-usage abort must not advance TTL for the previous call.
|
|
const usageAssistant = hasNonzeroUsage(completedAssistantUsage)
|
|
? currentAttemptCompletedAssistant
|
|
: transcriptUsageSnapshot?.assistant;
|
|
const promptCacheObservation =
|
|
input.cache.observabilityEnabled &&
|
|
(cacheBreak || input.cache.changesForTurn || typeof attemptUsage?.cacheRead === "number")
|
|
? {
|
|
broke: Boolean(cacheBreak),
|
|
...(typeof cacheBreak?.previousCacheRead === "number"
|
|
? { previousCacheRead: cacheBreak.previousCacheRead }
|
|
: {}),
|
|
...(typeof cacheBreak?.cacheRead === "number"
|
|
? { cacheRead: cacheBreak.cacheRead }
|
|
: typeof attemptUsage?.cacheRead === "number"
|
|
? { cacheRead: attemptUsage.cacheRead }
|
|
: {}),
|
|
changes: cacheBreak?.changes ?? input.cache.changesForTurn,
|
|
}
|
|
: undefined;
|
|
const fallbackLastCacheTouchAt = readLastCacheTtlTimestamp(sessionManager, {
|
|
provider: attempt.provider,
|
|
modelId: attempt.modelId,
|
|
});
|
|
promptCache = buildContextEnginePromptCacheInfo({
|
|
retention: input.cache.retention,
|
|
lastCallUsage,
|
|
observation: promptCacheObservation,
|
|
lastCacheTouchAt: resolvePromptCacheTouchTimestamp({
|
|
lastCallUsage,
|
|
assistantTimestamp: usageAssistant?.timestamp,
|
|
fallbackLastCacheTouchAt,
|
|
}),
|
|
});
|
|
|
|
if (promptError && promptErrorSource === "prompt" && !compactionOccurredThisAttempt) {
|
|
try {
|
|
sessionManager.appendCustomEntry("openclaw:prompt-error", {
|
|
timestamp: Date.now(),
|
|
runId: attempt.runId,
|
|
sessionId: attempt.sessionId,
|
|
provider: attempt.provider,
|
|
model: attempt.modelId,
|
|
api: attempt.model.api,
|
|
error: formatErrorMessage(promptError),
|
|
});
|
|
} catch (entryErr) {
|
|
log.warn(`failed to persist prompt error entry: ${String(entryErr)}`);
|
|
}
|
|
}
|
|
|
|
if (input.shouldFlushForContextEngine) {
|
|
flushSessionManagerTranscript(sessionManager);
|
|
}
|
|
});
|
|
|
|
return {
|
|
promptError,
|
|
promptErrorSource,
|
|
timedOutDuringCompaction: input.readLifecycleState().timedOutDuringCompaction,
|
|
compactionOccurredThisAttempt,
|
|
messagesSnapshot,
|
|
sessionIdUsed,
|
|
lastAssistant,
|
|
currentAttemptAssistant,
|
|
currentAttemptCompletedAssistant,
|
|
attemptUsage,
|
|
cacheBreak,
|
|
lastCallUsage,
|
|
promptCache,
|
|
};
|
|
}
|