mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 12:41:23 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
export function isAbortError(err: unknown): boolean {
|
||||
if (!err || typeof err !== "object") return false;
|
||||
if (!err || typeof err !== "object") {
|
||||
return false;
|
||||
}
|
||||
const name = "name" in err ? String(err.name) : "";
|
||||
if (name === "AbortError") return true;
|
||||
if (name === "AbortError") {
|
||||
return true;
|
||||
}
|
||||
const message =
|
||||
"message" in err && typeof err.message === "string" ? err.message.toLowerCase() : "";
|
||||
return message.includes("aborted");
|
||||
|
||||
@@ -11,21 +11,28 @@ export type CacheTtlEntryData = {
|
||||
export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean {
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
const normalizedModelId = modelId.toLowerCase();
|
||||
if (normalizedProvider === "anthropic") return true;
|
||||
if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/"))
|
||||
if (normalizedProvider === "anthropic") {
|
||||
return true;
|
||||
}
|
||||
if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function readLastCacheTtlTimestamp(sessionManager: unknown): number | null {
|
||||
const sm = sessionManager as { getEntries?: () => CustomEntryLike[] };
|
||||
if (!sm?.getEntries) return null;
|
||||
if (!sm?.getEntries) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const entries = sm.getEntries();
|
||||
let last: number | null = null;
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const entry = entries[i];
|
||||
if (entry?.type !== "custom" || entry?.customType !== CACHE_TTL_CUSTOM_TYPE) continue;
|
||||
if (entry?.type !== "custom" || entry?.customType !== CACHE_TTL_CUSTOM_TYPE) {
|
||||
continue;
|
||||
}
|
||||
const data = entry?.data as Partial<CacheTtlEntryData> | undefined;
|
||||
const ts = typeof data?.timestamp === "number" ? data.timestamp : null;
|
||||
if (ts && Number.isFinite(ts)) {
|
||||
@@ -43,7 +50,9 @@ export function appendCacheTtlTimestamp(sessionManager: unknown, data: CacheTtlE
|
||||
const sm = sessionManager as {
|
||||
appendCustomEntry?: (customType: string, data: unknown) => void;
|
||||
};
|
||||
if (!sm?.appendCustomEntry) return;
|
||||
if (!sm?.appendCustomEntry) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sm.appendCustomEntry(CACHE_TTL_CUSTOM_TYPE, data);
|
||||
} catch {
|
||||
|
||||
@@ -250,7 +250,9 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
accountId: params.agentAccountId ?? undefined,
|
||||
});
|
||||
if (inlineButtonsScope !== "off") {
|
||||
if (!runtimeCapabilities) runtimeCapabilities = [];
|
||||
if (!runtimeCapabilities) {
|
||||
runtimeCapabilities = [];
|
||||
}
|
||||
if (
|
||||
!runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons")
|
||||
) {
|
||||
|
||||
@@ -45,11 +45,17 @@ function buildContextPruningExtension(params: {
|
||||
model: Model<Api> | undefined;
|
||||
}): { additionalExtensionPaths?: string[] } {
|
||||
const raw = params.cfg?.agents?.defaults?.contextPruning;
|
||||
if (raw?.mode !== "cache-ttl") return {};
|
||||
if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) return {};
|
||||
if (raw?.mode !== "cache-ttl") {
|
||||
return {};
|
||||
}
|
||||
if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const settings = computeEffectiveSettings(raw);
|
||||
if (!settings) return {};
|
||||
if (!settings) {
|
||||
return {};
|
||||
}
|
||||
|
||||
setContextPruningRuntime(params.sessionManager, {
|
||||
settings,
|
||||
|
||||
@@ -29,9 +29,15 @@ function resolveCacheControlTtl(
|
||||
modelId: string,
|
||||
): CacheControlTtl | undefined {
|
||||
const raw = extraParams?.cacheControlTtl;
|
||||
if (raw !== "5m" && raw !== "1h") return undefined;
|
||||
if (provider === "anthropic") return raw;
|
||||
if (provider === "openrouter" && modelId.startsWith("anthropic/")) return raw;
|
||||
if (raw !== "5m" && raw !== "1h") {
|
||||
return undefined;
|
||||
}
|
||||
if (provider === "anthropic") {
|
||||
return raw;
|
||||
}
|
||||
if (provider === "openrouter" && modelId.startsWith("anthropic/")) {
|
||||
return raw;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,16 @@ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([
|
||||
const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/;
|
||||
|
||||
function isValidAntigravitySignature(value: unknown): value is string {
|
||||
if (typeof value !== "string") return false;
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return false;
|
||||
if (trimmed.length % 4 !== 0) return false;
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
if (trimmed.length % 4 !== 0) {
|
||||
return false;
|
||||
}
|
||||
return ANTIGRAVITY_SIGNATURE_RE.test(trimmed);
|
||||
}
|
||||
|
||||
@@ -113,7 +119,9 @@ function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessa
|
||||
}
|
||||
|
||||
function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[] {
|
||||
if (!schema || typeof schema !== "object") return [];
|
||||
if (!schema || typeof schema !== "object") {
|
||||
return [];
|
||||
}
|
||||
if (Array.isArray(schema)) {
|
||||
return schema.flatMap((item, index) =>
|
||||
findUnsupportedSchemaKeywords(item, `${path}[${index}]`),
|
||||
@@ -131,7 +139,9 @@ function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[]
|
||||
}
|
||||
}
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
if (key === "properties") continue;
|
||||
if (key === "properties") {
|
||||
continue;
|
||||
}
|
||||
if (GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS.has(key)) {
|
||||
violations.push(`${path}.${key}`);
|
||||
}
|
||||
@@ -153,7 +163,9 @@ export function sanitizeToolsForGoogle<
|
||||
return params.tools;
|
||||
}
|
||||
return params.tools.map((tool) => {
|
||||
if (!tool.parameters || typeof tool.parameters !== "object") return tool;
|
||||
if (!tool.parameters || typeof tool.parameters !== "object") {
|
||||
return tool;
|
||||
}
|
||||
return {
|
||||
...tool,
|
||||
parameters: cleanToolSchemaForGemini(
|
||||
@@ -206,7 +218,9 @@ export function onUnhandledCompactionFailure(cb: CompactionFailureListener): ()
|
||||
|
||||
registerUnhandledRejectionHandler((reason) => {
|
||||
const message = describeUnknownError(reason);
|
||||
if (!isCompactionFailureError(message)) return false;
|
||||
if (!isCompactionFailureError(message)) {
|
||||
return false;
|
||||
}
|
||||
log.error(`Auto-compaction failed (unhandled): ${message}`);
|
||||
compactionFailureEmitter.emit("failure", message);
|
||||
return true;
|
||||
@@ -228,7 +242,9 @@ function readLastModelSnapshot(sessionManager: SessionManager): ModelSnapshotEnt
|
||||
const entries = sessionManager.getEntries();
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const entry = entries[i] as CustomEntryLike;
|
||||
if (entry?.type !== "custom" || entry?.customType !== MODEL_SNAPSHOT_CUSTOM_TYPE) continue;
|
||||
if (entry?.type !== "custom" || entry?.customType !== MODEL_SNAPSHOT_CUSTOM_TYPE) {
|
||||
continue;
|
||||
}
|
||||
const data = entry?.data as ModelSnapshotEntry | undefined;
|
||||
if (data && typeof data === "object") {
|
||||
return data;
|
||||
|
||||
@@ -17,7 +17,9 @@ export function limitHistoryTurns(
|
||||
messages: AgentMessage[],
|
||||
limit: number | undefined,
|
||||
): AgentMessage[] {
|
||||
if (!limit || limit <= 0 || messages.length === 0) return messages;
|
||||
if (!limit || limit <= 0 || messages.length === 0) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
let userCount = 0;
|
||||
let lastUserIndex = messages.length;
|
||||
@@ -42,18 +44,24 @@ export function getDmHistoryLimitFromSessionKey(
|
||||
sessionKey: string | undefined,
|
||||
config: OpenClawConfig | undefined,
|
||||
): number | undefined {
|
||||
if (!sessionKey || !config) return undefined;
|
||||
if (!sessionKey || !config) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parts = sessionKey.split(":").filter(Boolean);
|
||||
const providerParts = parts.length >= 3 && parts[0] === "agent" ? parts.slice(2) : parts;
|
||||
|
||||
const provider = providerParts[0]?.toLowerCase();
|
||||
if (!provider) return undefined;
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const kind = providerParts[1]?.toLowerCase();
|
||||
const userIdRaw = providerParts.slice(2).join(":");
|
||||
const userId = stripThreadSuffix(userIdRaw);
|
||||
if (kind !== "dm") return undefined;
|
||||
if (kind !== "dm") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const getLimit = (
|
||||
providerConfig:
|
||||
@@ -63,7 +71,9 @@ export function getDmHistoryLimitFromSessionKey(
|
||||
}
|
||||
| undefined,
|
||||
): number | undefined => {
|
||||
if (!providerConfig) return undefined;
|
||||
if (!providerConfig) {
|
||||
return undefined;
|
||||
}
|
||||
if (userId && providerConfig.dms?.[userId]?.historyLimit !== undefined) {
|
||||
return providerConfig.dms[userId].historyLimit;
|
||||
}
|
||||
@@ -75,9 +85,13 @@ export function getDmHistoryLimitFromSessionKey(
|
||||
providerId: string,
|
||||
): { dmHistoryLimit?: number; dms?: Record<string, { historyLimit?: number }> } | undefined => {
|
||||
const channels = cfg?.channels;
|
||||
if (!channels || typeof channels !== "object") return undefined;
|
||||
if (!channels || typeof channels !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const entry = (channels as Record<string, unknown>)[providerId];
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return undefined;
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
return undefined;
|
||||
}
|
||||
return entry as { dmHistoryLimit?: number; dms?: Record<string, { historyLimit?: number }> };
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ export function buildInlineProviderModels(
|
||||
): InlineModelEntry[] {
|
||||
return Object.entries(providers).flatMap(([providerId, entry]) => {
|
||||
const trimmed = providerId.trim();
|
||||
if (!trimmed) return [];
|
||||
if (!trimmed) {
|
||||
return [];
|
||||
}
|
||||
return (entry?.models ?? []).map((model) => ({
|
||||
...model,
|
||||
provider: trimmed,
|
||||
@@ -40,9 +42,13 @@ export function buildModelAliasLines(cfg?: OpenClawConfig) {
|
||||
const entries: Array<{ alias: string; model: string }> = [];
|
||||
for (const [keyRaw, entryRaw] of Object.entries(models)) {
|
||||
const model = String(keyRaw ?? "").trim();
|
||||
if (!model) continue;
|
||||
if (!model) {
|
||||
continue;
|
||||
}
|
||||
const alias = String((entryRaw as { alias?: string } | undefined)?.alias ?? "").trim();
|
||||
if (!alias) continue;
|
||||
if (!alias) {
|
||||
continue;
|
||||
}
|
||||
entries.push({ alias, model });
|
||||
}
|
||||
return entries
|
||||
|
||||
@@ -110,7 +110,9 @@ vi.mock("./run/payloads.js", () => ({
|
||||
|
||||
vi.mock("./utils.js", () => ({
|
||||
describeUnknownError: vi.fn((err: unknown) => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (err instanceof Error) {
|
||||
return err.message;
|
||||
}
|
||||
return String(err);
|
||||
}),
|
||||
}));
|
||||
@@ -118,12 +120,16 @@ vi.mock("./utils.js", () => ({
|
||||
vi.mock("../pi-embedded-helpers.js", async () => {
|
||||
return {
|
||||
isCompactionFailureError: (msg?: string) => {
|
||||
if (!msg) return false;
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
const lower = msg.toLowerCase();
|
||||
return lower.includes("request_too_large") && lower.includes("summarization failed");
|
||||
},
|
||||
isContextOverflowError: (msg?: string) => {
|
||||
if (!msg) return false;
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
const lower = msg.toLowerCase();
|
||||
return lower.includes("request_too_large") || lower.includes("request size exceeds");
|
||||
},
|
||||
|
||||
@@ -60,7 +60,9 @@ const ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL = "ANTHROPIC_MAGIC_STRING_TRIGGER_R
|
||||
const ANTHROPIC_MAGIC_STRING_REPLACEMENT = "ANTHROPIC MAGIC STRING TRIGGER REFUSAL (redacted)";
|
||||
|
||||
function scrubAnthropicRefusalMagic(prompt: string): string {
|
||||
if (!prompt.includes(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL)) return prompt;
|
||||
if (!prompt.includes(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL)) {
|
||||
return prompt;
|
||||
}
|
||||
return prompt.replaceAll(
|
||||
ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL,
|
||||
ANTHROPIC_MAGIC_STRING_REPLACEMENT,
|
||||
@@ -174,7 +176,9 @@ export async function runEmbeddedPiAgent(
|
||||
allInCooldown: boolean;
|
||||
message: string;
|
||||
}): FailoverReason => {
|
||||
if (params.allInCooldown) return "rate_limit";
|
||||
if (params.allInCooldown) {
|
||||
return "rate_limit";
|
||||
}
|
||||
const classified = classifyFailoverReason(params.message);
|
||||
return classified ?? "auth";
|
||||
};
|
||||
@@ -202,7 +206,9 @@ export async function runEmbeddedPiAgent(
|
||||
cause: params.error,
|
||||
});
|
||||
}
|
||||
if (params.error instanceof Error) throw params.error;
|
||||
if (params.error instanceof Error) {
|
||||
throw params.error;
|
||||
}
|
||||
throw new Error(message);
|
||||
};
|
||||
|
||||
@@ -242,7 +248,9 @@ export async function runEmbeddedPiAgent(
|
||||
};
|
||||
|
||||
const advanceAuthProfile = async (): Promise<boolean> => {
|
||||
if (lockedProfileId) return false;
|
||||
if (lockedProfileId) {
|
||||
return false;
|
||||
}
|
||||
let nextIndex = profileIndex + 1;
|
||||
while (nextIndex < profileCandidates.length) {
|
||||
const candidate = profileCandidates[nextIndex];
|
||||
@@ -257,7 +265,9 @@ export async function runEmbeddedPiAgent(
|
||||
attemptedThinking.clear();
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (candidate && candidate === lockedProfileId) throw err;
|
||||
if (candidate && candidate === lockedProfileId) {
|
||||
throw err;
|
||||
}
|
||||
nextIndex += 1;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +292,9 @@ export async function runEmbeddedPiAgent(
|
||||
throwAuthProfileFailover({ allInCooldown: true });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof FailoverError) throw err;
|
||||
if (err instanceof FailoverError) {
|
||||
throw err;
|
||||
}
|
||||
if (profileCandidates[profileIndex] === lockedProfileId) {
|
||||
throwAuthProfileFailover({ allInCooldown: false, error: err });
|
||||
}
|
||||
@@ -578,7 +590,9 @@ export async function runEmbeddedPiAgent(
|
||||
}
|
||||
|
||||
const rotated = await advanceAuthProfile();
|
||||
if (rotated) continue;
|
||||
if (rotated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fallbackConfigured) {
|
||||
// Prefer formatted error message (user-friendly) over raw errorMessage
|
||||
|
||||
@@ -95,12 +95,16 @@ export function injectHistoryImagesIntoMessages(
|
||||
messages: AgentMessage[],
|
||||
historyImagesByIndex: Map<number, ImageContent[]>,
|
||||
): boolean {
|
||||
if (historyImagesByIndex.size === 0) return false;
|
||||
if (historyImagesByIndex.size === 0) {
|
||||
return false;
|
||||
}
|
||||
let didMutate = false;
|
||||
|
||||
for (const [msgIndex, images] of historyImagesByIndex) {
|
||||
// Bounds check: ensure index is valid before accessing
|
||||
if (msgIndex < 0 || msgIndex >= messages.length) continue;
|
||||
if (msgIndex < 0 || msgIndex >= messages.length) {
|
||||
continue;
|
||||
}
|
||||
const msg = messages[msgIndex];
|
||||
if (msg && msg.role === "user") {
|
||||
// Convert string content to array format if needed
|
||||
@@ -256,7 +260,9 @@ export async function runEmbeddedAttempt(
|
||||
accountId: params.agentAccountId ?? undefined,
|
||||
});
|
||||
if (inlineButtonsScope !== "off") {
|
||||
if (!runtimeCapabilities) runtimeCapabilities = [];
|
||||
if (!runtimeCapabilities) {
|
||||
runtimeCapabilities = [];
|
||||
}
|
||||
if (
|
||||
!runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons")
|
||||
) {
|
||||
@@ -575,7 +581,9 @@ export async function runEmbeddedAttempt(
|
||||
};
|
||||
const abortRun = (isTimeout = false, reason?: unknown) => {
|
||||
aborted = true;
|
||||
if (isTimeout) timedOut = true;
|
||||
if (isTimeout) {
|
||||
timedOut = true;
|
||||
}
|
||||
if (isTimeout) {
|
||||
runAbortController.abort(reason ?? makeTimeoutAbortReason());
|
||||
} else {
|
||||
@@ -660,7 +668,9 @@ export async function runEmbeddedAttempt(
|
||||
abortRun(true);
|
||||
if (!abortWarnTimer) {
|
||||
abortWarnTimer = setTimeout(() => {
|
||||
if (!activeSession.isStreaming) return;
|
||||
if (!activeSession.isStreaming) {
|
||||
return;
|
||||
}
|
||||
if (!isProbeSession) {
|
||||
log.warn(
|
||||
`embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`,
|
||||
@@ -808,7 +818,9 @@ export async function runEmbeddedAttempt(
|
||||
await waitForCompactionRetry();
|
||||
} catch (err) {
|
||||
if (isAbortError(err)) {
|
||||
if (!promptError) promptError = err;
|
||||
if (!promptError) {
|
||||
promptError = err;
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@@ -846,7 +858,9 @@ export async function runEmbeddedAttempt(
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(abortTimer);
|
||||
if (abortWarnTimer) clearTimeout(abortWarnTimer);
|
||||
if (abortWarnTimer) {
|
||||
clearTimeout(abortWarnTimer);
|
||||
}
|
||||
unsubscribe();
|
||||
clearActiveEmbeddedRun(params.sessionId, queueHandle);
|
||||
params.abortSignal?.removeEventListener?.("abort", onAbort);
|
||||
|
||||
@@ -80,9 +80,15 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// Helper to add a path ref
|
||||
const addPathRef = (raw: string) => {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed || seen.has(trimmed.toLowerCase())) return;
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return;
|
||||
if (!isImageExtension(trimmed)) return;
|
||||
if (!trimmed || seen.has(trimmed.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
||||
return;
|
||||
}
|
||||
if (!isImageExtension(trimmed)) {
|
||||
return;
|
||||
}
|
||||
seen.add(trimmed.toLowerCase());
|
||||
const resolved = trimmed.startsWith("~") ? resolveUserPath(trimmed) : trimmed;
|
||||
refs.push({ raw: trimmed, type: "path", resolved });
|
||||
@@ -118,7 +124,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
/\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
|
||||
while ((match = messageImagePattern.exec(prompt)) !== null) {
|
||||
const raw = match[1]?.trim();
|
||||
if (raw) addPathRef(raw);
|
||||
if (raw) {
|
||||
addPathRef(raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
|
||||
@@ -127,7 +135,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
const fileUrlPattern = /file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
|
||||
while ((match = fileUrlPattern.exec(prompt)) !== null) {
|
||||
const raw = match[0];
|
||||
if (seen.has(raw.toLowerCase())) continue;
|
||||
if (seen.has(raw.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
seen.add(raw.toLowerCase());
|
||||
// Use fileURLToPath for proper handling (e.g., file://localhost/path)
|
||||
try {
|
||||
@@ -148,7 +158,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
/(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
|
||||
while ((match = pathPattern.exec(prompt)) !== null) {
|
||||
// Use capture group 1 (the path without delimiter prefix); skip if undefined
|
||||
if (match[1]) addPathRef(match[1]);
|
||||
if (match[1]) {
|
||||
addPathRef(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return refs;
|
||||
@@ -267,9 +279,13 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] {
|
||||
const seen = new Set<string>();
|
||||
|
||||
const messageHasImageContent = (msg: unknown): boolean => {
|
||||
if (!msg || typeof msg !== "object") return false;
|
||||
if (!msg || typeof msg !== "object") {
|
||||
return false;
|
||||
}
|
||||
const content = (msg as { content?: unknown }).content;
|
||||
if (!Array.isArray(content)) return false;
|
||||
if (!Array.isArray(content)) {
|
||||
return false;
|
||||
}
|
||||
return content.some(
|
||||
(part) =>
|
||||
part != null && typeof part === "object" && (part as { type?: string }).type === "image",
|
||||
@@ -278,20 +294,30 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] {
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i];
|
||||
if (!msg || typeof msg !== "object") continue;
|
||||
if (!msg || typeof msg !== "object") {
|
||||
continue;
|
||||
}
|
||||
const message = msg as { role?: string };
|
||||
// Only scan user messages for image references
|
||||
if (message.role !== "user") continue;
|
||||
if (message.role !== "user") {
|
||||
continue;
|
||||
}
|
||||
// Skip if message already has image content (prevents reloading each turn)
|
||||
if (messageHasImageContent(msg)) continue;
|
||||
if (messageHasImageContent(msg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const text = extractTextFromMessage(msg);
|
||||
if (!text) continue;
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const refs = detectImageReferences(text);
|
||||
for (const ref of refs) {
|
||||
const key = ref.resolved.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
allRefs.push({ ...ref, messageIndex: i });
|
||||
}
|
||||
|
||||
@@ -76,7 +76,9 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
: null;
|
||||
const normalizedErrorText = errorText ? normalizeTextForComparison(errorText) : null;
|
||||
const genericErrorText = "The AI service returned an error. Please try again.";
|
||||
if (errorText) replyItems.push({ text: errorText, isError: true });
|
||||
if (errorText) {
|
||||
replyItems.push({ text: errorText, isError: true });
|
||||
}
|
||||
|
||||
const inlineToolResults =
|
||||
params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
|
||||
@@ -110,31 +112,51 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
params.lastAssistant && params.reasoningLevel === "on"
|
||||
? formatReasoningMessage(extractAssistantThinking(params.lastAssistant))
|
||||
: "";
|
||||
if (reasoningText) replyItems.push({ text: reasoningText });
|
||||
if (reasoningText) {
|
||||
replyItems.push({ text: reasoningText });
|
||||
}
|
||||
|
||||
const fallbackAnswerText = params.lastAssistant ? extractAssistantText(params.lastAssistant) : "";
|
||||
const shouldSuppressRawErrorText = (text: string) => {
|
||||
if (!lastAssistantErrored) return false;
|
||||
if (!lastAssistantErrored) {
|
||||
return false;
|
||||
}
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return false;
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
if (errorText) {
|
||||
const normalized = normalizeTextForComparison(trimmed);
|
||||
if (normalized && normalizedErrorText && normalized === normalizedErrorText) return true;
|
||||
if (trimmed === genericErrorText) return true;
|
||||
if (normalized && normalizedErrorText && normalized === normalizedErrorText) {
|
||||
return true;
|
||||
}
|
||||
if (trimmed === genericErrorText) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (rawErrorMessage && trimmed === rawErrorMessage) {
|
||||
return true;
|
||||
}
|
||||
if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage) {
|
||||
return true;
|
||||
}
|
||||
if (rawErrorMessage && trimmed === rawErrorMessage) return true;
|
||||
if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage) return true;
|
||||
if (normalizedRawErrorText) {
|
||||
const normalized = normalizeTextForComparison(trimmed);
|
||||
if (normalized && normalized === normalizedRawErrorText) return true;
|
||||
if (normalized && normalized === normalizedRawErrorText) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (normalizedFormattedRawErrorMessage) {
|
||||
const normalized = normalizeTextForComparison(trimmed);
|
||||
if (normalized && normalized === normalizedFormattedRawErrorMessage) return true;
|
||||
if (normalized && normalized === normalizedFormattedRawErrorMessage) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (rawErrorFingerprint) {
|
||||
const fingerprint = getApiErrorPayloadFingerprint(trimmed);
|
||||
if (fingerprint && fingerprint === rawErrorFingerprint) return true;
|
||||
if (fingerprint && fingerprint === rawErrorFingerprint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return isRawApiErrorPayload(trimmed);
|
||||
};
|
||||
@@ -222,8 +244,12 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
audioAsVoice: item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length),
|
||||
}))
|
||||
.filter((p) => {
|
||||
if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) return false;
|
||||
if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) return false;
|
||||
if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) {
|
||||
return false;
|
||||
}
|
||||
if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,12 +58,16 @@ export function isEmbeddedPiRunActive(sessionId: string): boolean {
|
||||
|
||||
export function isEmbeddedPiRunStreaming(sessionId: string): boolean {
|
||||
const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId);
|
||||
if (!handle) return false;
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
return handle.isStreaming();
|
||||
}
|
||||
|
||||
export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): Promise<boolean> {
|
||||
if (!sessionId || !ACTIVE_EMBEDDED_RUNS.has(sessionId)) return Promise.resolve(true);
|
||||
if (!sessionId || !ACTIVE_EMBEDDED_RUNS.has(sessionId)) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
diag.debug(`waiting for run end: sessionId=${sessionId} timeoutMs=${timeoutMs}`);
|
||||
return new Promise((resolve) => {
|
||||
const waiters = EMBEDDED_RUN_WAITERS.get(sessionId) ?? new Set();
|
||||
@@ -72,7 +76,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000):
|
||||
timer: setTimeout(
|
||||
() => {
|
||||
waiters.delete(waiter);
|
||||
if (waiters.size === 0) EMBEDDED_RUN_WAITERS.delete(sessionId);
|
||||
if (waiters.size === 0) {
|
||||
EMBEDDED_RUN_WAITERS.delete(sessionId);
|
||||
}
|
||||
diag.warn(`wait timeout: sessionId=${sessionId} timeoutMs=${timeoutMs}`);
|
||||
resolve(false);
|
||||
},
|
||||
@@ -83,7 +89,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000):
|
||||
EMBEDDED_RUN_WAITERS.set(sessionId, waiters);
|
||||
if (!ACTIVE_EMBEDDED_RUNS.has(sessionId)) {
|
||||
waiters.delete(waiter);
|
||||
if (waiters.size === 0) EMBEDDED_RUN_WAITERS.delete(sessionId);
|
||||
if (waiters.size === 0) {
|
||||
EMBEDDED_RUN_WAITERS.delete(sessionId);
|
||||
}
|
||||
clearTimeout(waiter.timer);
|
||||
resolve(true);
|
||||
}
|
||||
@@ -92,7 +100,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000):
|
||||
|
||||
function notifyEmbeddedRunEnded(sessionId: string) {
|
||||
const waiters = EMBEDDED_RUN_WAITERS.get(sessionId);
|
||||
if (!waiters || waiters.size === 0) return;
|
||||
if (!waiters || waiters.size === 0) {
|
||||
return;
|
||||
}
|
||||
EMBEDDED_RUN_WAITERS.delete(sessionId);
|
||||
diag.debug(`notifying waiters: sessionId=${sessionId} waiterCount=${waiters.size}`);
|
||||
for (const waiter of waiters) {
|
||||
|
||||
@@ -6,7 +6,9 @@ export function buildEmbeddedSandboxInfo(
|
||||
sandbox?: Awaited<ReturnType<typeof resolveSandboxContext>>,
|
||||
execElevated?: ExecElevatedDefaults,
|
||||
): EmbeddedSandboxInfo | undefined {
|
||||
if (!sandbox?.enabled) return undefined;
|
||||
if (!sandbox?.enabled) {
|
||||
return undefined;
|
||||
}
|
||||
const elevatedAllowed = Boolean(execElevated?.enabled && execElevated.allowed);
|
||||
return {
|
||||
enabled: true,
|
||||
|
||||
@@ -23,7 +23,9 @@ function isSessionManagerCacheEnabled(): boolean {
|
||||
}
|
||||
|
||||
export function trackSessionManagerAccess(sessionFile: string): void {
|
||||
if (!isSessionManagerCacheEnabled()) return;
|
||||
if (!isSessionManagerCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
SESSION_MANAGER_CACHE.set(sessionFile, {
|
||||
sessionFile,
|
||||
@@ -32,17 +34,25 @@ export function trackSessionManagerAccess(sessionFile: string): void {
|
||||
}
|
||||
|
||||
function isSessionManagerCached(sessionFile: string): boolean {
|
||||
if (!isSessionManagerCacheEnabled()) return false;
|
||||
if (!isSessionManagerCacheEnabled()) {
|
||||
return false;
|
||||
}
|
||||
const entry = SESSION_MANAGER_CACHE.get(sessionFile);
|
||||
if (!entry) return false;
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
const now = Date.now();
|
||||
const ttl = getSessionManagerTtl();
|
||||
return now - entry.loadedAt <= ttl;
|
||||
}
|
||||
|
||||
export async function prewarmSessionFile(sessionFile: string): Promise<void> {
|
||||
if (!isSessionManagerCacheEnabled()) return;
|
||||
if (isSessionManagerCached(sessionFile)) return;
|
||||
if (!isSessionManagerCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (isSessionManagerCached(sessionFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read a small chunk to encourage OS page cache warmup.
|
||||
|
||||
@@ -5,19 +5,27 @@ import type { ExecToolDefaults } from "../bash-tools.js";
|
||||
|
||||
export function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel {
|
||||
// pi-agent-core supports "xhigh"; OpenClaw enables it for specific models.
|
||||
if (!level) return "off";
|
||||
if (!level) {
|
||||
return "off";
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
export function resolveExecToolDefaults(config?: OpenClawConfig): ExecToolDefaults | undefined {
|
||||
const tools = config?.tools;
|
||||
if (!tools?.exec) return undefined;
|
||||
if (!tools?.exec) {
|
||||
return undefined;
|
||||
}
|
||||
return tools.exec;
|
||||
}
|
||||
|
||||
export function describeUnknownError(error: unknown): string {
|
||||
if (error instanceof Error) return error.message;
|
||||
if (typeof error === "string") return error;
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
try {
|
||||
const serialized = JSON.stringify(error);
|
||||
return serialized ?? "Unknown error";
|
||||
|
||||
Reference in New Issue
Block a user