mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
refactor(types): remove chained type assertions in core and ui (#124073)
* refactor(types): remove chained assertions in core and ui * fix(types): preserve legacy cron migration identity * fix(types): preserve settings patch key types
This commit is contained in:
committed by
GitHub
parent
26ff3c9607
commit
aaa509b26e
@@ -128,12 +128,12 @@ export function formatAcpErrorChain(error: unknown): string {
|
||||
return redactSensitiveText(String(error));
|
||||
}
|
||||
const segments: string[] = [renderSingleError(error)];
|
||||
let current: unknown = (error as unknown as { cause?: unknown }).cause;
|
||||
let current: unknown = error.cause;
|
||||
let depth = 0;
|
||||
while (current !== undefined && current !== null && depth < 8) {
|
||||
if (current instanceof Error) {
|
||||
segments.push(renderSingleError(current));
|
||||
current = (current as unknown as { cause?: unknown }).cause;
|
||||
current = current.cause;
|
||||
} else {
|
||||
segments.push(stringifyNonErrorCause(current));
|
||||
current = undefined;
|
||||
@@ -144,7 +144,7 @@ export function formatAcpErrorChain(error: unknown): string {
|
||||
}
|
||||
|
||||
function renderSingleError(error: Error): string {
|
||||
const codeValue = (error as unknown as { code?: unknown }).code;
|
||||
const codeValue = "code" in error ? error.code : undefined;
|
||||
const codeSuffix =
|
||||
typeof codeValue === "string" || typeof codeValue === "number" ? ` [${codeValue}]` : "";
|
||||
return `${error.name}${codeSuffix}: ${error.message}`;
|
||||
|
||||
@@ -1873,8 +1873,17 @@ type TurnTaintMetadata = {
|
||||
};
|
||||
|
||||
function readTurnTaintMetadata(message: AgentMessage): TurnTaintMetadata | undefined {
|
||||
const metadata = (message as unknown as Record<string, unknown>)["__openclaw"];
|
||||
return asOptionalRecord(metadata) as TurnTaintMetadata | undefined;
|
||||
const metadata = Reflect.get(message, "__openclaw");
|
||||
const record = asOptionalRecord(metadata);
|
||||
if (!record) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...(record.resultContentSource === "network"
|
||||
? { resultContentSource: record.resultContentSource }
|
||||
: {}),
|
||||
...(record.turnTainted === true ? { turnTainted: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function toolResultTaintsTurn(message: ToolResultMessage): boolean {
|
||||
@@ -1898,10 +1907,11 @@ function withAssistantTurnTaint(message: AssistantMessage, tainted: boolean): As
|
||||
if (!tainted) {
|
||||
return message;
|
||||
}
|
||||
return {
|
||||
const taintedMessage = {
|
||||
...message,
|
||||
__openclaw: { ...readTurnTaintMetadata(message), turnTainted: true },
|
||||
} as unknown as AssistantMessage;
|
||||
} satisfies AssistantMessage & { __openclaw: TurnTaintMetadata };
|
||||
return taintedMessage;
|
||||
}
|
||||
|
||||
function withToolResultContentSource(
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
RawMessageStreamEvent,
|
||||
TextBlockParam,
|
||||
} from "@anthropic-ai/sdk/resources/messages.js";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { getEnvApiKey } from "../env-api-keys.js";
|
||||
import { getAiTransportHost, resolveAiTransportHeaderSentinels } from "../host.js";
|
||||
import {
|
||||
@@ -448,7 +449,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicComp
|
||||
// and allowing the thinking-block recovery retry to fire.
|
||||
eventSink.push({ type: "start", partial: output });
|
||||
} else if (event.type === "content_block_start") {
|
||||
const rawContentBlock = event.content_block as unknown as Record<string, unknown>;
|
||||
const rawContentBlock = isRecord(event.content_block) ? event.content_block : undefined;
|
||||
if (
|
||||
requestOptions?.anthropicServerCompaction === true &&
|
||||
compactionCapture.begin(event.index, rawContentBlock, output.content.length)
|
||||
@@ -566,7 +567,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicComp
|
||||
});
|
||||
}
|
||||
} else if (event.type === "content_block_delta") {
|
||||
const rawDelta = event.delta as unknown as Record<string, unknown>;
|
||||
const rawDelta = isRecord(event.delta) ? event.delta : undefined;
|
||||
if (compactionCapture.delta(event.index, rawDelta)) {
|
||||
continue;
|
||||
} else if (event.delta.type === "text_delta") {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AssistantMessage, Context, Model, ProviderReplayState } from "@openclaw/llm-core";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { shortHash } from "../utils/hash.js";
|
||||
|
||||
const ANTHROPIC_COMPACTION_REPLAY_TYPE = "anthropic-compaction";
|
||||
@@ -85,40 +86,39 @@ function buildAnthropicReplayContext(model: Model, options?: ReplayOpts): Anthro
|
||||
};
|
||||
}
|
||||
|
||||
function isAnthropicCompactionState(
|
||||
state: Record<string, unknown>,
|
||||
): state is Record<string, unknown> &
|
||||
(AnthropicCompactionReplayState | AnthropicCompactionSuppressionState) {
|
||||
if (
|
||||
state.v !== 1 ||
|
||||
typeof state.data !== "string" ||
|
||||
typeof state.provider !== "string" ||
|
||||
typeof state.api !== "string" ||
|
||||
typeof state.model !== "string" ||
|
||||
typeof state.baseUrlHash !== "string" ||
|
||||
(state.sessionHash !== undefined && typeof state.sessionHash !== "string") ||
|
||||
(state.authProfileHash !== undefined && typeof state.authProfileHash !== "string")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (state.type === ANTHROPIC_COMPACTION_SUPPRESSION_TYPE) {
|
||||
return state.data === ANTHROPIC_COMPACTION_SUPPRESSION_DATA;
|
||||
}
|
||||
return (
|
||||
state.type === ANTHROPIC_COMPACTION_REPLAY_TYPE &&
|
||||
state.data.length > 0 &&
|
||||
(state.replayIndex === undefined ||
|
||||
(typeof state.replayIndex === "number" &&
|
||||
Number.isSafeInteger(state.replayIndex) &&
|
||||
state.replayIndex >= 0))
|
||||
);
|
||||
}
|
||||
|
||||
function readAnthropicCompactionState(
|
||||
value: unknown,
|
||||
): AnthropicCompactionReplayState | AnthropicCompactionSuppressionState | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const state = value as Record<string, unknown>;
|
||||
const validEnvelope =
|
||||
state.v === 1 &&
|
||||
typeof state.data === "string" &&
|
||||
typeof state.provider === "string" &&
|
||||
typeof state.api === "string" &&
|
||||
typeof state.model === "string" &&
|
||||
typeof state.baseUrlHash === "string" &&
|
||||
(state.sessionHash === undefined || typeof state.sessionHash === "string") &&
|
||||
(state.authProfileHash === undefined || typeof state.authProfileHash === "string");
|
||||
if (!validEnvelope) {
|
||||
return undefined;
|
||||
}
|
||||
const replayState = state as unknown as
|
||||
| AnthropicCompactionReplayState
|
||||
| AnthropicCompactionSuppressionState;
|
||||
if (replayState.type === ANTHROPIC_COMPACTION_SUPPRESSION_TYPE) {
|
||||
return replayState.data === ANTHROPIC_COMPACTION_SUPPRESSION_DATA ? replayState : undefined;
|
||||
}
|
||||
if (
|
||||
replayState.type !== ANTHROPIC_COMPACTION_REPLAY_TYPE ||
|
||||
replayState.data.length === 0 ||
|
||||
(replayState.replayIndex !== undefined &&
|
||||
(!Number.isSafeInteger(replayState.replayIndex) || replayState.replayIndex < 0))
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return replayState;
|
||||
return isRecord(value) && isAnthropicCompactionState(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function replayContextMatches(
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import {
|
||||
failTransportStream,
|
||||
finalizeTransportStream,
|
||||
type WritableTransportStream,
|
||||
withProviderResponseHook,
|
||||
} from "./transport-stream-shared.js";
|
||||
|
||||
@@ -173,7 +174,7 @@ function buildOpenAICompletionsClientConfig(
|
||||
export function createOpenAICompletionsTransportStreamFn(): StreamFn {
|
||||
return (model, context, options) => {
|
||||
const eventStream = createAssistantMessageEventStream();
|
||||
const stream = eventStream as unknown as { push(event: unknown): void; end(): void };
|
||||
const stream = eventStream as unknown as WritableTransportStream;
|
||||
void (async () => {
|
||||
const output: MutableAssistantOutput = {
|
||||
role: "assistant" as const,
|
||||
@@ -293,6 +294,6 @@ export function createOpenAICompletionsTransportStreamFn(): StreamFn {
|
||||
firstEventAbort?.dispose();
|
||||
}
|
||||
})();
|
||||
return eventStream as unknown as ReturnType<StreamFn>;
|
||||
return eventStream;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ import { sanitizeResponsesImagePayload } from "./responses-image-payload-sanitiz
|
||||
import {
|
||||
mergeTransportMetadata,
|
||||
transportAbortError,
|
||||
type WritableTransportStream,
|
||||
withProviderResponseHook,
|
||||
} from "./transport-stream-shared.js";
|
||||
import { redactIdentifier } from "./transport-utils.js";
|
||||
@@ -240,7 +241,7 @@ function createResponsesTransportExecutor(config: ResponsesTransportExecutorOpti
|
||||
const responsesOptions = options as OpenAIResponsesOptions | undefined;
|
||||
const compactRequest = claimResponsesCompactRequest(responsesOptions);
|
||||
const eventStream = createAssistantMessageEventStream();
|
||||
const stream = eventStream as unknown as { push(event: unknown): void; end(): void };
|
||||
const stream = eventStream as unknown as WritableTransportStream;
|
||||
void (async () => {
|
||||
const output = createOpenAIResponsesAssistantOutput(model, config.outputApi);
|
||||
let firstEventAbort: ReturnType<typeof createFirstStreamEventAbortController> | undefined;
|
||||
@@ -590,7 +591,7 @@ function createResponsesTransportExecutor(config: ResponsesTransportExecutorOpti
|
||||
firstEventAbort?.dispose();
|
||||
}
|
||||
})();
|
||||
return eventStream as unknown as ReturnType<StreamFn>;
|
||||
return eventStream;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,29 +62,35 @@ export function isOpenAIResponsesReplayContext(
|
||||
);
|
||||
}
|
||||
|
||||
function readOpenAIResponsesCompactionReplayState(
|
||||
value: unknown,
|
||||
): OpenAIResponsesCompactionReplayState | OpenAIResponsesCompactionSuppressionState | undefined {
|
||||
if (
|
||||
!isOpenAIResponsesReplayContext(value) ||
|
||||
typeof value.baseUrlHash !== "string" ||
|
||||
(value as Record<string, unknown>).v !== 1
|
||||
) {
|
||||
return undefined;
|
||||
function isOpenAIResponsesCompactionState(
|
||||
state: OpenAIResponsesReplayContext & Record<string, unknown>,
|
||||
): state is Record<string, unknown> &
|
||||
(OpenAIResponsesCompactionReplayState | OpenAIResponsesCompactionSuppressionState) {
|
||||
if (typeof state.baseUrlHash !== "string" || state.v !== 1) {
|
||||
return false;
|
||||
}
|
||||
const state = value as OpenAIResponsesReplayContext & Record<string, unknown>;
|
||||
if (state.type === OPENAI_RESPONSES_COMPACTION_SUPPRESSION_TYPE) {
|
||||
return state.data === OPENAI_RESPONSES_COMPACTION_SUPPRESSION_DATA
|
||||
? (state as unknown as OpenAIResponsesCompactionSuppressionState)
|
||||
: undefined;
|
||||
return state.data === OPENAI_RESPONSES_COMPACTION_SUPPRESSION_DATA;
|
||||
}
|
||||
return state.type === OPENAI_RESPONSES_COMPACTION_REPLAY_TYPE &&
|
||||
return (
|
||||
state.type === OPENAI_RESPONSES_COMPACTION_REPLAY_TYPE &&
|
||||
typeof state.data === "string" &&
|
||||
state.data.length > 0 &&
|
||||
(state.id === undefined || typeof state.id === "string") &&
|
||||
(state.replayIndex === undefined ||
|
||||
(Number.isSafeInteger(state.replayIndex) && (state.replayIndex as number) >= 0))
|
||||
? (state as unknown as OpenAIResponsesCompactionReplayState)
|
||||
(typeof state.replayIndex === "number" &&
|
||||
Number.isSafeInteger(state.replayIndex) &&
|
||||
state.replayIndex >= 0))
|
||||
);
|
||||
}
|
||||
|
||||
function readOpenAIResponsesCompactionReplayState(
|
||||
value: unknown,
|
||||
): OpenAIResponsesCompactionReplayState | OpenAIResponsesCompactionSuppressionState | undefined {
|
||||
return isRecord(value) &&
|
||||
isOpenAIResponsesReplayContext(value) &&
|
||||
isOpenAIResponsesCompactionState(value)
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Api, AssistantMessage, Context, Model } from "@openclaw/llm-core";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type {
|
||||
ResponseFunctionCallOutputItemList,
|
||||
ResponseInput,
|
||||
@@ -369,9 +370,7 @@ function convertResponsesMessagesWithStyle(
|
||||
const replayableReasoningItem = prepareOpenAIResponsesReasoningItemForReplay(
|
||||
reasoningItem,
|
||||
replayContext,
|
||||
readOpenAIResponsesReasoningReplayBlockMetadata(
|
||||
block as unknown as Record<string, unknown>,
|
||||
),
|
||||
readOpenAIResponsesReasoningReplayBlockMetadata(isRecord(block) ? block : {}),
|
||||
providerStyle ? { preserveUnattributedEncryptedContent: true } : undefined,
|
||||
);
|
||||
if (!shouldReplayResponsesItemIds) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type {
|
||||
ResponseCreateParamsStreaming,
|
||||
ResponseOutputItem,
|
||||
@@ -705,10 +706,7 @@ export async function processResponsesStream<TApi extends Api>(
|
||||
event.message ? `Error Code ${event.code}: ${event.message}` : "Unknown error",
|
||||
);
|
||||
} else if (event.type === "response.failed") {
|
||||
const failure = normalizeResponsesFailedEvent(
|
||||
event as unknown as Record<string, unknown>,
|
||||
model,
|
||||
);
|
||||
const failure = normalizeResponsesFailedEvent(isRecord(event) ? event : {}, model);
|
||||
finalizeFailedResponse(event.response, failure.responseId);
|
||||
throw new ResponsesStreamFailure(failure, event.response);
|
||||
}
|
||||
|
||||
@@ -1578,7 +1578,7 @@ export function markdownToIRWithMeta(
|
||||
assistantTranscriptRolePreserveLinks: options.assistantTranscriptRoleHeaders === true,
|
||||
};
|
||||
const md = createMarkdownIt(options);
|
||||
const tokens = md.parse(source, env as unknown as object);
|
||||
const tokens = md.parse(source, env);
|
||||
|
||||
const tableMode = options.tableMode ?? "off";
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
timestampMsToIsoString,
|
||||
} from "@openclaw/normalization-core/number-coercion";
|
||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { truncateToVisibleWidth, visibleWidth } from "../../../packages/terminal-core/src/ansi.js";
|
||||
import { sanitizeTerminalText } from "../../../packages/terminal-core/src/safe-text.js";
|
||||
@@ -148,7 +149,7 @@ export function enrichCronJsonWithStatus(value: unknown): unknown {
|
||||
|
||||
// Single job object (has 'state' and 'enabled')
|
||||
if ("state" in obj && "enabled" in obj) {
|
||||
return { ...obj, status: computeStatus(obj as unknown as CronJob) };
|
||||
return { ...obj, status: computeStatus(obj) };
|
||||
}
|
||||
|
||||
// List response (has 'jobs' array)
|
||||
@@ -163,15 +164,19 @@ export function enrichCronJsonWithStatus(value: unknown): unknown {
|
||||
return value;
|
||||
}
|
||||
|
||||
function computeStatus(job: CronJob): string {
|
||||
function computeStatus(job: { enabled?: unknown; state?: unknown }): string {
|
||||
if (!job.enabled) {
|
||||
return "disabled";
|
||||
}
|
||||
const state = job.state ?? {};
|
||||
const state = asOptionalRecord(job.state) ?? {};
|
||||
if (state.runningAtMs) {
|
||||
return "running";
|
||||
}
|
||||
return state.lastRunStatus ?? state.lastStatus ?? "idle";
|
||||
return typeof state.lastRunStatus === "string"
|
||||
? state.lastRunStatus
|
||||
: typeof state.lastStatus === "string"
|
||||
? state.lastStatus
|
||||
: "idle";
|
||||
}
|
||||
|
||||
// Human-facing decoration only: enrichCronJsonWithStatus() emits computeStatus()
|
||||
|
||||
@@ -361,8 +361,8 @@ function assertLocalFallbackMatchesGatewayRequest(
|
||||
function redactLocalPairedDevice(device: InfraPairedDevice): PairedDevice {
|
||||
const { tokens, ...rest } = device;
|
||||
return {
|
||||
...(rest as unknown as PairedDevice),
|
||||
tokens: summarizeDeviceTokens(tokens) as DeviceTokenSummary[] | undefined,
|
||||
...rest,
|
||||
tokens: summarizeDeviceTokens(tokens),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,9 @@ function readTranscriptEventsForImport(
|
||||
id: sessionId,
|
||||
type: "session",
|
||||
version: plan.sourceVersion,
|
||||
} as unknown as FileEntry;
|
||||
timestamp: "",
|
||||
cwd: "",
|
||||
} satisfies FileEntry;
|
||||
// V1 compactions refer to original row indexes. Stable index-derived IDs let
|
||||
// the second pass resolve those links without retaining the transcript.
|
||||
const idPrefix = createHash("sha256")
|
||||
@@ -144,14 +146,15 @@ function readTranscriptEventsForImport(
|
||||
let event = loadedEvent;
|
||||
let recognizedEvent: FileEntry | undefined;
|
||||
if (originalIndex === plan.headerIndex) {
|
||||
const legacyHeader = event as unknown as Record<string, unknown>;
|
||||
const canonicalHeader: Record<string, unknown> = {
|
||||
...legacyHeader,
|
||||
const canonicalHeader = {
|
||||
...event,
|
||||
id: sessionId,
|
||||
type: "session",
|
||||
type: "session" as const,
|
||||
timestamp: typeof event.timestamp === "string" ? event.timestamp : "",
|
||||
cwd: "cwd" in event && typeof event.cwd === "string" ? event.cwd : "",
|
||||
};
|
||||
delete canonicalHeader.sessionId;
|
||||
event = canonicalHeader as unknown as FileEntry;
|
||||
Reflect.deleteProperty(canonicalHeader, "sessionId");
|
||||
event = canonicalHeader;
|
||||
recognizedEvent = event;
|
||||
} else {
|
||||
// Reuse the runtime partition contract one row at a time. The
|
||||
@@ -482,7 +485,14 @@ function parseSqliteSessionEntry(entryJson: string): SessionEntry | undefined {
|
||||
try {
|
||||
const parsed = JSON.parse(entryJson) as unknown;
|
||||
return isRecord(parsed) && typeof parsed.sessionId === "string"
|
||||
? (parsed as unknown as SessionEntry)
|
||||
? {
|
||||
...parsed,
|
||||
sessionId: parsed.sessionId,
|
||||
updatedAt:
|
||||
typeof parsed.updatedAt === "number" && Number.isFinite(parsed.updatedAt)
|
||||
? parsed.updatedAt
|
||||
: 0,
|
||||
}
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
/** Doctor repair for stale plugin-owned routing state persisted in session entries. */
|
||||
import { normalizeOptionalString as normalizeString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { normalizeStringEntriesLower } from "@openclaw/normalization-core/string-normalization";
|
||||
@@ -113,7 +114,10 @@ function entryMayContainPluginSessionRouteState(sessionKey: string, entry: Sessi
|
||||
if (isValidAgentHarnessSessionStoreEntry(sessionKey, entry)) {
|
||||
return false;
|
||||
}
|
||||
const record = entry as unknown as Record<string, unknown>;
|
||||
if (!isRecord(entry)) {
|
||||
return false;
|
||||
}
|
||||
const record = entry;
|
||||
return (
|
||||
normalizeString(record.providerOverride) !== undefined ||
|
||||
normalizeString(record.modelOverride) !== undefined ||
|
||||
@@ -511,7 +515,9 @@ export async function runPluginSessionStateDoctorRepairs(params: {
|
||||
}
|
||||
routeByAgentId.set(agentId, route);
|
||||
}
|
||||
scanStore[sessionKey] = entry as unknown as Record<string, unknown>;
|
||||
if (isRecord(entry)) {
|
||||
scanStore[sessionKey] = entry;
|
||||
}
|
||||
routes[sessionKey] = route;
|
||||
}
|
||||
if (Object.keys(scanStore).length === 0) {
|
||||
@@ -537,14 +543,10 @@ export async function runPluginSessionStateDoctorRepairs(params: {
|
||||
const repairedAt = Date.now();
|
||||
const repairsByKey = new Map(repairs.map((repair) => [repair.key, repair]));
|
||||
await updateLegacySessionStore(params.absoluteStorePath, (currentStore) => {
|
||||
const currentMutableStore = currentStore as unknown as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
for (const [key, repair] of repairsByKey) {
|
||||
const current = currentMutableStore[key];
|
||||
const current = currentStore[key];
|
||||
if (
|
||||
current &&
|
||||
isRecord(current) &&
|
||||
applySessionRouteStateRepair({
|
||||
sessionKey: key,
|
||||
entry: current,
|
||||
|
||||
@@ -434,20 +434,20 @@ function hasInlineState(jobs: Array<Record<string, unknown> | null | undefined>)
|
||||
);
|
||||
}
|
||||
|
||||
function ensureJobStateObject(job: CronStoreFile["jobs"][number]): void {
|
||||
function ensureJobStateObject(job: Record<string, unknown>): void {
|
||||
if (!isRecord(job.state)) {
|
||||
job.state = {} as never;
|
||||
job.state = {};
|
||||
}
|
||||
}
|
||||
|
||||
function backfillMissingRuntimeFields(job: CronStoreFile["jobs"][number]): void {
|
||||
function backfillMissingRuntimeFields(job: Record<string, unknown>): void {
|
||||
ensureJobStateObject(job);
|
||||
if (typeof job.updatedAtMs !== "number") {
|
||||
job.updatedAtMs = typeof job.createdAtMs === "number" ? job.createdAtMs : Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
function resolveUpdatedAtMs(job: CronStoreFile["jobs"][number], updatedAtMs: unknown): number {
|
||||
function resolveUpdatedAtMs(job: Record<string, unknown>, updatedAtMs: unknown): number {
|
||||
if (typeof updatedAtMs === "number" && Number.isFinite(updatedAtMs)) {
|
||||
return updatedAtMs;
|
||||
}
|
||||
@@ -459,20 +459,21 @@ function resolveUpdatedAtMs(job: CronStoreFile["jobs"][number], updatedAtMs: unk
|
||||
: Date.now();
|
||||
}
|
||||
|
||||
function mergeStateFileEntry(job: CronStoreFile["jobs"][number], entry: unknown): void {
|
||||
function mergeStateFileEntry(job: Record<string, unknown>, entry: unknown): void {
|
||||
if (!isRecord(entry)) {
|
||||
backfillMissingRuntimeFields(job);
|
||||
return;
|
||||
}
|
||||
job.updatedAtMs = resolveUpdatedAtMs(job, entry.updatedAtMs);
|
||||
job.state = isRecord(entry.state) ? (entry.state as never) : ({} as never);
|
||||
job.state = isRecord(entry.state) ? entry.state : {};
|
||||
if (
|
||||
typeof entry.scheduleIdentity === "string" &&
|
||||
entry.scheduleIdentity !==
|
||||
tryLegacyCronScheduleIdentity(job as unknown as Record<string, unknown>)
|
||||
entry.scheduleIdentity !== tryLegacyCronScheduleIdentity(job)
|
||||
) {
|
||||
ensureJobStateObject(job);
|
||||
job.state.nextRunAtMs = undefined;
|
||||
if (isRecord(job.state)) {
|
||||
job.state.nextRunAtMs = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +596,7 @@ export async function loadLegacyCronStoreForMigration(
|
||||
version: 1,
|
||||
jobs: configRows as never as CronStoreFile["jobs"],
|
||||
};
|
||||
const jobs = store.jobs as unknown as Array<Record<string, unknown>>;
|
||||
const jobs = configRows;
|
||||
const configJobs = cloneConfigJobs(configRows);
|
||||
|
||||
const statePath = resolveLegacyCronStatePath(resolvedStorePath);
|
||||
@@ -604,8 +605,8 @@ export async function loadLegacyCronStoreForMigration(
|
||||
const hasLegacyInlineState = !stateFile && hasInlineState(jobs);
|
||||
|
||||
if (stateFile) {
|
||||
for (const job of store.jobs) {
|
||||
const stateId = resolveCronStateId(job as unknown as Record<string, unknown>);
|
||||
for (const job of jobs) {
|
||||
const stateId = resolveCronStateId(job);
|
||||
const entry = stateId ? stateFile.jobs[stateId] : undefined;
|
||||
configJobRuntimeEntries.push(isRecord(entry) ? structuredClone(entry) : {});
|
||||
if (entry) {
|
||||
@@ -615,12 +616,12 @@ export async function loadLegacyCronStoreForMigration(
|
||||
}
|
||||
}
|
||||
} else if (!hasLegacyInlineState) {
|
||||
for (const job of store.jobs) {
|
||||
for (const job of jobs) {
|
||||
backfillMissingRuntimeFields(job);
|
||||
}
|
||||
}
|
||||
|
||||
for (const job of store.jobs) {
|
||||
for (const job of jobs) {
|
||||
ensureJobStateObject(job);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Cron doctor repair planning helpers for previewing and merging legacy rows.
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalStringifiedId } from "../../../../packages/normalization-core/src/string-coerce.js";
|
||||
import { normalizeCronJobInput } from "../../../cron/normalize.js";
|
||||
import type { CronJob } from "../../../cron/types.js";
|
||||
@@ -231,7 +232,10 @@ export function needsSqliteProjectionBackfill(params: {
|
||||
if (!normalizedConfig) {
|
||||
return true;
|
||||
}
|
||||
const projected = params.projectedJob as unknown as Record<string, unknown>;
|
||||
if (!isRecord(params.projectedJob)) {
|
||||
return true;
|
||||
}
|
||||
const projected = params.projectedJob;
|
||||
for (const field of [
|
||||
"agentId",
|
||||
"deleteAfterRun",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Doctor-only runtime policy repair for migrated cron Codex model refs.
|
||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { asOptionalRecord, isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { tryResolveDefaultAgentId } from "../../../agents/agent-scope-config.js";
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
import { normalizeAgentId } from "../../../routing/session-key.js";
|
||||
@@ -26,7 +26,7 @@ function resolvePolicyOwner(params: {
|
||||
cfg: OpenClawConfig;
|
||||
target: CronCodexRuntimePolicyTarget;
|
||||
}): { owner: MutableRecord; path: string } | undefined {
|
||||
const root = params.cfg as unknown as MutableRecord;
|
||||
const root = isRecord(params.cfg) ? params.cfg : {};
|
||||
const agents = ensureRecord(root, "agents");
|
||||
const requestedAgentId = params.target.agentId
|
||||
? normalizeAgentId(params.target.agentId)
|
||||
|
||||
@@ -71,9 +71,8 @@ function clearPromotedSessionEntrySlots(
|
||||
options.includeStoredSlotKeys === false && sessionEntrySlotKeys
|
||||
? new Set(sessionEntrySlotKeys)
|
||||
: collectPromotedSessionEntrySlotKeys(entry, pluginId, sessionEntrySlotKeys);
|
||||
const entryRecord = entry as unknown as Record<string, unknown>;
|
||||
for (const slotKey of slotKeys) {
|
||||
delete entryRecord[slotKey];
|
||||
Reflect.deleteProperty(entry, slotKey);
|
||||
}
|
||||
if (!options.pruneSlotOwnership || !entry.pluginExtensionSlotKeys) {
|
||||
return;
|
||||
@@ -152,9 +151,8 @@ function hasPromotedSessionEntrySlot(
|
||||
if (slotKeys.size === 0) {
|
||||
return false;
|
||||
}
|
||||
const entryRecord = entry as unknown as Record<string, unknown>;
|
||||
for (const slotKey of slotKeys) {
|
||||
if (Object.hasOwn(entryRecord, slotKey)) {
|
||||
if (Object.hasOwn(entry, slotKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,13 +328,13 @@ export async function markSessionAbortTarget(params: {
|
||||
scope: SessionAccessScope;
|
||||
now?: () => number;
|
||||
}): Promise<SessionAbortTargetResult | null> {
|
||||
let resolvedTarget: SessionAbortTargetResult | null = null;
|
||||
const resolution: { target: SessionAbortTargetResult | null } = { target: null };
|
||||
try {
|
||||
const sessionKey = normalizeStoreSessionKey(params.scope.sessionKey);
|
||||
const updated = await patchSessionEntryCore(
|
||||
params.scope,
|
||||
(currentEntry) => {
|
||||
resolvedTarget = {
|
||||
resolution.target = {
|
||||
entry: { ...currentEntry },
|
||||
persisted: false,
|
||||
sessionId: currentEntry.sessionId,
|
||||
@@ -368,7 +368,7 @@ export async function markSessionAbortTarget(params: {
|
||||
}
|
||||
: null;
|
||||
} catch (error) {
|
||||
const fallbackTarget = resolvedTarget as unknown as SessionAbortTargetResult | null;
|
||||
const fallbackTarget = resolution.target;
|
||||
if (fallbackTarget) {
|
||||
return {
|
||||
entry: fallbackTarget.entry,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { projectCanonicalSessionEntryShape } from "./store-entry-shape.js";
|
||||
import type { SessionEntry } from "./types.js";
|
||||
|
||||
export function normalizeSessionEntryTimestamp(entry: SessionEntry): SessionEntry {
|
||||
const raw = entry as unknown as Record<string, unknown>;
|
||||
const hasLegacyDeliveryFields = [
|
||||
"route",
|
||||
"deliveryContext",
|
||||
@@ -20,7 +19,7 @@ export function normalizeSessionEntryTimestamp(entry: SessionEntry): SessionEntr
|
||||
"lastTo",
|
||||
"lastAccountId",
|
||||
"lastThreadId",
|
||||
].some((key) => key in raw);
|
||||
].some((key) => key in entry);
|
||||
const delivery =
|
||||
entry.delivery ?? (hasLegacyDeliveryFields ? undefined : { kind: "none" as const });
|
||||
if (typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt)) {
|
||||
@@ -84,9 +83,7 @@ export function bindSessionNode(params: {
|
||||
sessionKey: string;
|
||||
updatedAt: number;
|
||||
}) {
|
||||
const canonicalEntry = projectCanonicalSessionEntryShape(
|
||||
params.entry as unknown as Record<string, unknown>,
|
||||
);
|
||||
const canonicalEntry = projectCanonicalSessionEntryShape({ ...params.entry });
|
||||
const actor = params.entry.createdActor;
|
||||
const legacyActorId = normalizeText(
|
||||
(params.entry as SessionEntry & { createdBy?: { id?: unknown } }).createdBy?.id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Session store migrations repair legacy field names during load/save normalization.
|
||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import type { SessionEntry } from "./types.js";
|
||||
|
||||
/** Applies best-effort in-place migrations for legacy session store entry fields. */
|
||||
@@ -9,25 +10,28 @@ export function applySessionStoreMigrations(store: Record<string, SessionEntry>)
|
||||
if (!entry || typeof entry !== "object") {
|
||||
continue;
|
||||
}
|
||||
const rec = entry as unknown as Record<string, unknown>;
|
||||
if (typeof rec.channel !== "string" && typeof rec.provider === "string") {
|
||||
rec.channel = rec.provider;
|
||||
delete rec.provider;
|
||||
const rec = asOptionalRecord(entry);
|
||||
if (!rec) {
|
||||
continue;
|
||||
}
|
||||
if (typeof rec["channel"] !== "string" && typeof rec["provider"] === "string") {
|
||||
rec["channel"] = rec["provider"];
|
||||
delete rec["provider"];
|
||||
changed = true;
|
||||
}
|
||||
if (typeof rec.lastChannel !== "string" && typeof rec.lastProvider === "string") {
|
||||
rec.lastChannel = rec.lastProvider;
|
||||
delete rec.lastProvider;
|
||||
if (typeof rec["lastChannel"] !== "string" && typeof rec["lastProvider"] === "string") {
|
||||
rec["lastChannel"] = rec["lastProvider"];
|
||||
delete rec["lastProvider"];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Best-effort migration: legacy `room` field → `groupChannel` (keep value, prune old key).
|
||||
if (typeof rec.groupChannel !== "string" && typeof rec.room === "string") {
|
||||
rec.groupChannel = rec.room;
|
||||
delete rec.room;
|
||||
if (typeof rec.groupChannel !== "string" && typeof rec["room"] === "string") {
|
||||
rec.groupChannel = rec["room"];
|
||||
delete rec["room"];
|
||||
changed = true;
|
||||
} else if ("room" in rec) {
|
||||
delete rec.room;
|
||||
delete rec["room"];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,8 +740,8 @@ function isIdentifiedDeliveryMirror(message: SessionTranscriptAssistantMessage):
|
||||
);
|
||||
}
|
||||
|
||||
function extractAssistantMessageText(message: SessionTranscriptAssistantMessage): string | null {
|
||||
if (!Array.isArray(message.content)) {
|
||||
function extractAssistantMessageText(message: AgentMessage): string | null {
|
||||
if (message.role !== "assistant" || !Array.isArray(message.content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -764,9 +764,7 @@ async function findLatestEquivalentAssistantMessageId(
|
||||
message: SessionTranscriptAssistantMessage,
|
||||
config?: OpenClawConfig,
|
||||
): Promise<string | undefined> {
|
||||
const expectedText = extractAssistantMessageText(
|
||||
redactTranscriptMessage(message, config) as unknown as SessionTranscriptAssistantMessage,
|
||||
);
|
||||
const expectedText = extractAssistantMessageText(redactTranscriptMessage(message, config));
|
||||
if (!expectedText) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -783,12 +781,7 @@ async function findLatestEquivalentAssistantMessageId(
|
||||
return undefined;
|
||||
}
|
||||
const candidateText = latest
|
||||
? extractAssistantMessageText(
|
||||
redactTranscriptMessage(
|
||||
latest.message as AgentMessage,
|
||||
config,
|
||||
) as unknown as SessionTranscriptAssistantMessage,
|
||||
)
|
||||
? extractAssistantMessageText(redactTranscriptMessage(latest.message as AgentMessage, config))
|
||||
: undefined;
|
||||
return candidateText === expectedText ? latest?.id : undefined;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import { completeSimple, streamSimple } from "./llm.js";
|
||||
|
||||
/** Runtime adapter that lets the package agent-core use OpenClaw LLM helpers. */
|
||||
export const openClawAgentCoreRuntime = {
|
||||
completeSimple: completeSimple as unknown as CompleteSimpleFn,
|
||||
streamSimple: streamSimple as unknown as StreamFn,
|
||||
completeSimple: ((model, context, options) =>
|
||||
completeSimple(model, context, options)) satisfies CompleteSimpleFn,
|
||||
streamSimple: ((model, context, options) =>
|
||||
streamSimple(model, context, options)) satisfies StreamFn,
|
||||
} satisfies AgentCoreRuntimeDeps;
|
||||
|
||||
/** Agent-core class preconfigured with OpenClaw runtime dependencies. */
|
||||
|
||||
@@ -97,10 +97,10 @@ export function registerWebhookTargetWithPluginRoute<T extends { path: string }>
|
||||
});
|
||||
}
|
||||
|
||||
const pathTeardownByTargetMap = new WeakMap<Map<string, unknown[]>, Map<string, () => void>>();
|
||||
const pathTeardownByTargetMap = new WeakMap<object, Map<string, () => void>>();
|
||||
|
||||
function getPathTeardownMap<T>(targetsByPath: Map<string, T[]>): Map<string, () => void> {
|
||||
const mapKey = targetsByPath as unknown as Map<string, unknown[]>;
|
||||
const mapKey = targetsByPath;
|
||||
const existing = pathTeardownByTargetMap.get(mapKey);
|
||||
if (existing) {
|
||||
return existing;
|
||||
|
||||
@@ -118,6 +118,21 @@ export function prefValuesEqual(left: unknown, right: unknown): boolean {
|
||||
return left === right;
|
||||
}
|
||||
|
||||
function applyChangedSettingsPatch(
|
||||
target: Partial<UiSettings>,
|
||||
settings: UiSettings,
|
||||
source: Partial<UiSettings>,
|
||||
): void {
|
||||
const applyKey = <K extends keyof UiSettings>(key: K, value: UiSettings[K] | undefined) => {
|
||||
if (!prefValuesEqual(settings[key], value)) {
|
||||
target[key] = value;
|
||||
}
|
||||
};
|
||||
for (const key of Object.keys(source) as Array<keyof UiSettings>) {
|
||||
applyKey(key, source[key]);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractServerUiPrefs(configObject: unknown): ServerUiPrefs {
|
||||
const prefs = asRecord(asRecord(asRecord(configObject)?.ui)?.prefs);
|
||||
if (!prefs) {
|
||||
@@ -232,13 +247,7 @@ export function serverPrefsLocalPatch(
|
||||
if (serverValue === null) {
|
||||
const resetPatch = specification.clearable ? specification.reset?.(settings) : undefined;
|
||||
if (resetPatch) {
|
||||
for (const [resetKey, resetValue] of Object.entries(resetPatch)) {
|
||||
if (
|
||||
!prefValuesEqual((settings as unknown as Record<string, unknown>)[resetKey], resetValue)
|
||||
) {
|
||||
(patch as Record<string, unknown>)[resetKey] = resetValue;
|
||||
}
|
||||
}
|
||||
applyChangedSettingsPatch(patch, settings, resetPatch);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { gatewayOriginScope } from "@openclaw/gateway-client/browser";
|
||||
import { safeParseJson } from "@openclaw/normalization-core";
|
||||
import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { normalizeUniqueTrimmedStringList } from "@openclaw/normalization-core/string-normalization";
|
||||
import {
|
||||
@@ -468,7 +469,7 @@ export function loadSettings(): UiSettings {
|
||||
(parsed as { theme?: unknown }).theme,
|
||||
(parsed as { themeMode?: unknown }).themeMode,
|
||||
);
|
||||
const parsedRecord = parsed as unknown as Record<string, unknown>;
|
||||
const parsedRecord = asOptionalRecord(parsed) ?? {};
|
||||
const hasSidebarEntries = Object.hasOwn(parsedRecord, "sidebarEntries");
|
||||
// One-time read of the retired route-only shape; all writes use sidebarEntries.
|
||||
const migratedSidebarEntries = hasSidebarEntries
|
||||
|
||||
@@ -6,7 +6,7 @@ import { isSessionRunActive } from "../../lib/session-run-state.ts";
|
||||
import { areUiSessionKeysEquivalent } from "../../lib/sessions/session-key.ts";
|
||||
import { refreshChatAvatar, resolveAgentIdForSession } from "./chat-avatar.ts";
|
||||
import { applyRemoteSlashCommandsResult, refreshSlashCommands } from "./chat-commands.ts";
|
||||
import { loadChatHistory, type ChatMetadataResult, type ChatState } from "./chat-history.ts";
|
||||
import { loadChatHistory, type ChatMetadataResult } from "./chat-history.ts";
|
||||
import { flushChatQueueForEvent } from "./chat-send-actions.ts";
|
||||
import { flushChatQueueAfterIdleSessionReconciliation } from "./chat-session.ts";
|
||||
import type { ChatPageHost } from "./chat-state-host.ts";
|
||||
@@ -241,7 +241,7 @@ export async function refreshChatMetadata(
|
||||
const request = { host, client, agentId, version: requestVersion };
|
||||
host.chatModelsLoading = true;
|
||||
try {
|
||||
if (isGatewayMethodAdvertised(host as unknown as ChatState, "chat.metadata") === false) {
|
||||
if (isGatewayMethodAdvertised(host, "chat.metadata") === false) {
|
||||
await refreshMissingChatMetadata(request, EMPTY_CHAT_METADATA_APPLY_RESULT, opts);
|
||||
return EMPTY_CHAT_METADATA_APPLY_RESULT;
|
||||
}
|
||||
@@ -341,7 +341,7 @@ async function refreshChat(
|
||||
const refreshedAgentId = resolveAgentIdForSession(host);
|
||||
const requestUpdate = () => host.requestUpdate?.();
|
||||
const previousSessionsResult = host.sessionsResult;
|
||||
const historyLoad = loadChatHistory(host as unknown as ChatState, {
|
||||
const historyLoad = loadChatHistory(host, {
|
||||
deferBranches: opts?.deferBranches === true,
|
||||
startup: opts?.startup === true,
|
||||
});
|
||||
@@ -442,7 +442,7 @@ export function refreshPageChat(host: ChatPageHost, opts?: ChatRefreshOptions) {
|
||||
opts?.startup &&
|
||||
host.client &&
|
||||
host.connected &&
|
||||
isGatewayMethodAdvertised(host as unknown as ChatState, "chat.startup") !== false,
|
||||
isGatewayMethodAdvertised(host, "chat.startup") !== false,
|
||||
);
|
||||
const startupMetadataRequestVersion = ownsStartupMetadata
|
||||
? ++host.chatMetadataRequestVersion
|
||||
|
||||
@@ -200,7 +200,7 @@ const adoptedWidgetPromptFrames = new WeakSet<HTMLIFrameElement>();
|
||||
const widgetPromptOfferListenerWindows = new WeakSet<Window>();
|
||||
|
||||
function tryAdoptWidgetPromptPort(frame: HTMLIFrameElement) {
|
||||
const source = frame.contentWindow as unknown as object | null;
|
||||
const source = frame.contentWindow;
|
||||
if (adoptedWidgetPromptFrames.has(frame) || !promptEligibleFrames.has(frame) || !source) {
|
||||
return;
|
||||
}
|
||||
@@ -236,14 +236,14 @@ function installWidgetPromptOfferListener() {
|
||||
if (!source || !port || event.origin !== "null") {
|
||||
return;
|
||||
}
|
||||
if (offeredWidgetPromptSources.has(source as unknown as object)) {
|
||||
if (offeredWidgetPromptSources.has(source)) {
|
||||
// Only the first offer per content window can win; a replacement
|
||||
// document's offer must never displace the genuine bridge's.
|
||||
port.close();
|
||||
return;
|
||||
}
|
||||
offeredWidgetPromptSources.add(source as unknown as object);
|
||||
pendingWidgetPromptPorts.set(source as unknown as object, port);
|
||||
offeredWidgetPromptSources.add(source);
|
||||
pendingWidgetPromptPorts.set(source, port);
|
||||
// Posted-message and iframe-load tasks have no guaranteed cross-source
|
||||
// ordering, so the offer may arrive after the eligible frame's load;
|
||||
// adopt for it now instead of stranding the widget without a channel.
|
||||
|
||||
@@ -194,7 +194,7 @@ async function loadSvgImage(url: string): Promise<HTMLImageElement> {
|
||||
return image;
|
||||
}
|
||||
|
||||
function parseSvgDimensions(root: SVGSVGElement): { width: number; height: number } | null {
|
||||
function parseSvgDimensions(root: Element): { width: number; height: number } | null {
|
||||
const viewBox = root.getAttribute("viewBox");
|
||||
if (viewBox) {
|
||||
const values = viewBox
|
||||
@@ -278,7 +278,7 @@ async function sanitizeSvgForRasterization(
|
||||
if (pathCommands > PLUGIN_ICON_SVG_MAX_PATH_COMMANDS) {
|
||||
return null;
|
||||
}
|
||||
const dimensions = parseSvgDimensions(root as unknown as SVGSVGElement);
|
||||
const dimensions = parseSvgDimensions(root);
|
||||
if (!dimensions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user