mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 02:45:38 -06:00
refactor(types): remove chained assertions in runtime boundaries (#124082)
This commit is contained in:
committed by
GitHub
parent
250b1e68be
commit
f4871eb86b
@@ -17,7 +17,7 @@ export function createTestFollowupRun(overrides: Partial<FollowupRun["run"]> = {
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp",
|
||||
config: {},
|
||||
skillsSnapshot: {},
|
||||
skillsSnapshot: { prompt: "", skills: [] },
|
||||
provider: "anthropic",
|
||||
model: "claude",
|
||||
thinkLevel: "low",
|
||||
@@ -29,7 +29,7 @@ export function createTestFollowupRun(overrides: Partial<FollowupRun["run"]> = {
|
||||
skipProviderRuntimeHints: true,
|
||||
...overrides,
|
||||
},
|
||||
} as unknown as FollowupRun;
|
||||
} satisfies FollowupRun;
|
||||
}
|
||||
|
||||
export async function writeTestSessionStore(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Loads, normalizes, quarantines, and persists cron service store state. */
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeCronJobIdentityFields } from "../normalize-job-identity.js";
|
||||
import { normalizeCronJobInput } from "../normalize.js";
|
||||
import { getInvalidPersistedCronJobReason } from "../persisted-shape.js";
|
||||
@@ -134,6 +135,12 @@ function warnInvalidPersistedCronJob(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function isValidatedCronJob(
|
||||
value: Record<string, unknown>,
|
||||
): value is CronJob & Record<string, unknown> {
|
||||
return getInvalidPersistedCronJobReason(value) === null;
|
||||
}
|
||||
|
||||
/** Loads and normalizes the cron store, quarantining invalid persisted rows before runtime use. */
|
||||
export async function ensureLoaded(
|
||||
state: CronServiceState,
|
||||
@@ -163,7 +170,7 @@ export async function ensureLoaded(
|
||||
const loadNowMs = state.deps.nowMs();
|
||||
// Persisted cron rows are validated lazily, so treat them as raw records at the
|
||||
// store boundary and only trust the CronJob shape after validation below.
|
||||
const loadedJobs = (loaded.store.jobs ?? []) as unknown as Record<string, unknown>[];
|
||||
const loadedJobs = (loaded.store.jobs ?? []).filter(isRecord);
|
||||
const jobs: CronJob[] = [];
|
||||
const durableNextRunAtMsByJobId = new Map<string, number | undefined>();
|
||||
const quarantinedConfigJobs: QuarantinedCronConfigJob[] = [...loaded.invalidConfigRows];
|
||||
@@ -190,11 +197,16 @@ export async function ensureLoaded(
|
||||
}
|
||||
const hydratedRaw = normalized ?? raw;
|
||||
let invalidReason = rawInvalidReason ?? getInvalidPersistedCronJobReason(hydratedRaw);
|
||||
const hydratedSchedule = (hydratedRaw.schedule ?? {}) as Record<string, unknown>;
|
||||
if (!invalidReason && hydratedRaw.enabled !== false && hydratedSchedule.kind === "every") {
|
||||
const hydratedSchedule = isRecord(hydratedRaw.schedule) ? hydratedRaw.schedule : {};
|
||||
if (
|
||||
!invalidReason &&
|
||||
isValidatedCronJob(hydratedRaw) &&
|
||||
hydratedRaw.enabled &&
|
||||
hydratedSchedule.kind === "every"
|
||||
) {
|
||||
try {
|
||||
assertTimeScheduleSatisfiable(
|
||||
{ ...(hydratedRaw as unknown as CronJob), state: {} },
|
||||
{ ...hydratedRaw, state: {} },
|
||||
loadNowMs,
|
||||
computeJobNextRunAtMs,
|
||||
);
|
||||
@@ -226,7 +238,10 @@ export async function ensureLoaded(
|
||||
continue;
|
||||
}
|
||||
// Validated above, so the raw record is now a trusted CronJob.
|
||||
const hydrated = hydratedRaw as unknown as CronJob;
|
||||
if (!isValidatedCronJob(hydratedRaw)) {
|
||||
continue;
|
||||
}
|
||||
const hydrated = hydratedRaw;
|
||||
jobs.push(hydrated);
|
||||
// Capture the value SQLite actually held before schedule-identity repair
|
||||
// mutates the runtime view. A later save can then publish that transition.
|
||||
|
||||
@@ -193,13 +193,13 @@ function bindCronJobRow(storeKey: string, job: CronStoredJob, sortOrder: number)
|
||||
job_json: JSON.stringify(stripJobRuntimeFields(job)),
|
||||
state_json: JSON.stringify(job.state ?? {}),
|
||||
runtime_updated_at_ms: job.updatedAtMs,
|
||||
schedule_identity: tryCronScheduleIdentity(job as unknown as Record<string, unknown>) ?? null,
|
||||
schedule_identity: tryCronScheduleIdentity({ ...job }) ?? null,
|
||||
sort_order: sortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCronJobForSqlite(job: CronStoreFile["jobs"][number]): CronStoredJob | null {
|
||||
const raw = structuredClone(job) as unknown as Record<string, unknown>;
|
||||
const raw: Record<string, unknown> = { ...structuredClone(job) };
|
||||
const hadDeleteAfterRun = Object.hasOwn(raw, "deleteAfterRun");
|
||||
normalizeCronJobIdentityFields(raw);
|
||||
const normalized = normalizeCronJobInput(raw, { applyDefaults: true });
|
||||
@@ -570,7 +570,7 @@ export function updateCronRuntimeRows(
|
||||
...bindStateColumns(job.state ?? {}),
|
||||
state_json: JSON.stringify(job.state ?? {}),
|
||||
runtime_updated_at_ms: job.updatedAtMs,
|
||||
schedule_identity: tryCronScheduleIdentity(job as unknown as Record<string, unknown>),
|
||||
schedule_identity: tryCronScheduleIdentity({ ...job }),
|
||||
})
|
||||
.where("store_key", "=", storeKey)
|
||||
.where("job_id", "=", job.id),
|
||||
|
||||
@@ -161,7 +161,7 @@ type PendingEntry<TPayload = ExecApprovalRequestPayload> = {
|
||||
record: ExecApprovalRecord<TPayload>;
|
||||
resolve: (decision: ExecApprovalDecision | null) => void;
|
||||
reject: (err: Error) => void;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
timer: ReturnType<typeof setTimeout> | null;
|
||||
cleanupTimer: ReturnType<typeof setTimeout> | null;
|
||||
handoffRetainCount: number;
|
||||
handoffReleasedAtMs: number | null;
|
||||
@@ -367,7 +367,7 @@ export class ExecApprovalManager<TPayload = ExecApprovalRequestPayload> {
|
||||
record,
|
||||
resolve: resolvePromise!,
|
||||
reject: rejectPromise!,
|
||||
timer: null as unknown as ReturnType<typeof setTimeout>,
|
||||
timer: null,
|
||||
cleanupTimer: null,
|
||||
handoffRetainCount: 0,
|
||||
handoffReleasedAtMs: null,
|
||||
@@ -757,7 +757,9 @@ export class ExecApprovalManager<TPayload = ExecApprovalRequestPayload> {
|
||||
if (!pending || pending.record.resolvedAtMs !== undefined) {
|
||||
return false;
|
||||
}
|
||||
clearTimeout(pending.timer);
|
||||
if (pending.timer) {
|
||||
clearTimeout(pending.timer);
|
||||
}
|
||||
pending.record.resolvedAtMs = params.resolvedAtMs;
|
||||
if (params.decision === null) {
|
||||
delete pending.record.decision;
|
||||
@@ -875,12 +877,16 @@ export class ExecApprovalManager<TPayload = ExecApprovalRequestPayload> {
|
||||
}
|
||||
|
||||
private scheduleExpiryTimer(entry: PendingEntry<TPayload>): void {
|
||||
const timerDelayMs = resolveApprovalTimeoutMs(entry.record.expiresAtMs - Date.now());
|
||||
entry.timer = setTimeout(() => {
|
||||
entry.timer = this.createExpiryTimer(entry.record);
|
||||
}
|
||||
|
||||
private createExpiryTimer(record: ExecApprovalRecord<TPayload>): ReturnType<typeof setTimeout> {
|
||||
const timerDelayMs = resolveApprovalTimeoutMs(record.expiresAtMs - Date.now());
|
||||
return setTimeout(() => {
|
||||
try {
|
||||
this.expireDue(entry.record.id);
|
||||
this.expireDue(record.id);
|
||||
} catch (error) {
|
||||
this.reportError(error, { approvalId: entry.record.id, operation: "expire" });
|
||||
this.reportError(error, { approvalId: record.id, operation: "expire" });
|
||||
}
|
||||
}, timerDelayMs);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export type McpToolSchemaEntry = {
|
||||
|
||||
function readLoopbackToolField(tool: McpLoopbackTool, key: "name" | "description" | "parameters") {
|
||||
try {
|
||||
return (tool as unknown as Record<typeof key, unknown>)[key];
|
||||
return tool[key];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ function readLoopbackToolDescription(tool: McpLoopbackTool): string | undefined
|
||||
function readLoopbackToolParameters(tool: McpLoopbackTool): Record<string, unknown> | undefined {
|
||||
let value;
|
||||
try {
|
||||
value = (tool as unknown as { parameters?: unknown }).parameters;
|
||||
value = tool.parameters;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -116,15 +116,15 @@ export class QuestionManager {
|
||||
expiresAtMs,
|
||||
status: "pending",
|
||||
};
|
||||
const expiryTimer = setTimeout(() => this.expire(record.id), timeoutMs);
|
||||
const entry: QuestionEntry = {
|
||||
record,
|
||||
expiryTimer: null as unknown as ReturnType<typeof setTimeout>,
|
||||
expiryTimer,
|
||||
cleanupTimer: null,
|
||||
waiters: new Set(),
|
||||
onResolved: params.onResolved,
|
||||
};
|
||||
this.entries.set(record.id, entry);
|
||||
entry.expiryTimer = setTimeout(() => this.expire(record.id), timeoutMs);
|
||||
unrefTimer(entry.expiryTimer);
|
||||
return record;
|
||||
}
|
||||
|
||||
@@ -888,6 +888,10 @@ export const cronHandlers: GatewayRequestHandlers = {
|
||||
if (!assertValidParams(candidate, validateCronUpdateParams, "cron.update", respond)) {
|
||||
return;
|
||||
}
|
||||
if (!normalizedPatch) {
|
||||
respondInvalidCronParams(respond, "cron.update", "patch did not normalize");
|
||||
return;
|
||||
}
|
||||
const p = candidate as {
|
||||
id?: string;
|
||||
jobId?: string;
|
||||
@@ -912,7 +916,7 @@ export const cronHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const patch = p.patch as unknown as CronJobPatch;
|
||||
const patch: CronJobPatch = normalizedPatch;
|
||||
const cfg = context.getRuntimeConfig();
|
||||
const currentJob = await context.cron.readJob(jobId);
|
||||
if (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { resolveAgentWorkspaceDir } from "../../agents/agent-scope.js";
|
||||
import { compactEmbeddedAgentSession } from "../../agents/embedded-agent.js";
|
||||
import { resolveManualCompactionCliTarget } from "../../agents/session-runtime-compat.js";
|
||||
import { preflightManualSessionCompaction } from "../../agents/sessions/manual-compaction-preflight.js";
|
||||
import type { SessionEntry as AgentSessionEntry } from "../../agents/sessions/session-manager.js";
|
||||
import { isIndexedSessionEntry } from "../../agents/sessions/session-manager-codec.js";
|
||||
import { resolveIngressWorkspaceOverrideForSessionRun } from "../../agents/spawned-context.js";
|
||||
import { normalizeReasoningLevel, normalizeThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
resolveSessionTranscriptRuntimeTarget,
|
||||
} from "../../config/sessions/session-accessor.js";
|
||||
import {
|
||||
isCanonicalSessionTranscriptEntry,
|
||||
scanSessionTranscriptTree,
|
||||
selectSessionTranscriptTreePathNodes,
|
||||
} from "../../config/sessions/transcript-tree.js";
|
||||
@@ -71,7 +70,7 @@ export async function preflightGatewaySessionCompaction(
|
||||
const tree = scanSessionTranscriptTree(transcriptEvents);
|
||||
const branch = selectSessionTranscriptTreePathNodes(tree, tree.leafId)
|
||||
.map((node) => node.entry)
|
||||
.filter(isCanonicalSessionTranscriptEntry) as unknown as AgentSessionEntry[];
|
||||
.filter(isIndexedSessionEntry);
|
||||
const preflight = preflightManualSessionCompaction(branch, {
|
||||
enabled: true,
|
||||
reserveTokens: 0,
|
||||
|
||||
@@ -183,14 +183,8 @@ class GatewayRestartTransaction {
|
||||
acceptedConfig &&
|
||||
configDebt.restartOwnedPaths.every((path) =>
|
||||
isDeepStrictEqual(
|
||||
getConfigValueAtPath(
|
||||
configDebt.nextConfig as unknown as Record<string, unknown>,
|
||||
path.split("."),
|
||||
),
|
||||
getConfigValueAtPath(
|
||||
acceptedConfig as unknown as Record<string, unknown>,
|
||||
path.split("."),
|
||||
),
|
||||
getConfigValueAtPath({ ...configDebt.nextConfig }, path.split(".")),
|
||||
getConfigValueAtPath({ ...acceptedConfig }, path.split(".")),
|
||||
),
|
||||
);
|
||||
if (!retainsConfigDebt) {
|
||||
|
||||
@@ -211,9 +211,9 @@ export async function prepareGatewayKernelState(params: {
|
||||
const channelLogs = Object.fromEntries(
|
||||
listGatewayStartupChannelPlugins().map((plugin) => [plugin.id, logChannels.child(plugin.id)]),
|
||||
) as Record<ChannelId, ReturnType<typeof createSubsystemLogger>>;
|
||||
const channelRuntimeEnvs = Object.fromEntries(
|
||||
const channelRuntimeEnvs: Partial<Record<ChannelId, RuntimeEnv>> = Object.fromEntries(
|
||||
Object.entries(channelLogs).map(([id, logger]) => [id, runtimeForLogger(logger)]),
|
||||
) as unknown as Record<ChannelId, RuntimeEnv>;
|
||||
);
|
||||
const listStartupChannelGatewayMethods = () => {
|
||||
const methods: string[] = [];
|
||||
for (const plugin of listGatewayStartupChannelPlugins()) {
|
||||
|
||||
@@ -435,7 +435,7 @@ export async function readSessionLeafStateFromTranscriptAsync(
|
||||
}
|
||||
|
||||
function readSessionLeafStateFromRecords(
|
||||
records: readonly Record<string, unknown>[],
|
||||
records: readonly { type?: unknown; id?: unknown }[],
|
||||
): { entryId: string; leafId: string | null } | null {
|
||||
let latestEntryId: string | undefined;
|
||||
for (const record of records) {
|
||||
@@ -620,7 +620,7 @@ async function captureCompactionCheckpointSnapshotAsync(params: {
|
||||
if (typeof params.sessionManager?.getEntries !== "function") {
|
||||
return null;
|
||||
}
|
||||
const entryRecords = params.sessionManager.getEntries() as unknown as Record<string, unknown>[];
|
||||
const entryRecords = params.sessionManager.getEntries();
|
||||
const transcriptState = readSessionLeafStateFromRecords(entryRecords);
|
||||
const position = resolveCompactionCheckpointTranscriptPosition({
|
||||
preferredLeafId: liveLeafId,
|
||||
|
||||
@@ -239,7 +239,7 @@ export async function projectSessionsPatchEntry(params: {
|
||||
};
|
||||
|
||||
const existing = params.existingEntry
|
||||
? projectCanonicalSessionEntryShape(params.existingEntry as unknown as Record<string, unknown>)
|
||||
? projectCanonicalSessionEntryShape({ ...params.existingEntry })
|
||||
: undefined;
|
||||
// Existing entries without session ids are placeholder aliases; assigning an id makes them real.
|
||||
const next: SessionEntry = existing?.sessionId
|
||||
|
||||
@@ -666,9 +666,8 @@ export function createNodeWorkerTunnelManager(options: NodeWorkerTunnelManagerOp
|
||||
abortController: new AbortController(),
|
||||
launchTasks: new Set<Promise<unknown>>(),
|
||||
};
|
||||
const entry = { ...base, handle: undefined as unknown as WorkerTunnelHandle };
|
||||
const created = createHandle(entry, restoredWorkspace);
|
||||
entry.handle = created.handle;
|
||||
const created = createHandle(base, restoredWorkspace);
|
||||
const entry = Object.assign(base, { handle: created.handle });
|
||||
entries.set(entry.environmentId, entry);
|
||||
try {
|
||||
await created.validateRestoredWorkspace();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
// Gateway WebSocket log formatting.
|
||||
// Redacts and compacts request/response/event metadata for console diagnostics.
|
||||
import { readStringValue } from "@openclaw/normalization-core/string-coerce";
|
||||
@@ -164,7 +165,7 @@ function renderSingleErrorForLog(error: Error): string {
|
||||
if (error.message) {
|
||||
parts.push(error.message);
|
||||
}
|
||||
const codeValue = (error as unknown as { code?: unknown }).code;
|
||||
const codeValue = isRecord(error) ? error.code : undefined;
|
||||
const code =
|
||||
typeof codeValue === "string" || typeof codeValue === "number" ? String(codeValue) : "";
|
||||
if (code) {
|
||||
@@ -175,12 +176,12 @@ function renderSingleErrorForLog(error: Error): string {
|
||||
|
||||
function renderErrorChainForLog(error: Error): string {
|
||||
const segments: string[] = [renderSingleErrorForLog(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(renderSingleErrorForLog(current));
|
||||
current = (current as unknown as { cause?: unknown }).cause;
|
||||
current = current.cause;
|
||||
} else {
|
||||
segments.push(stringifyNonErrorCause(current));
|
||||
current = undefined;
|
||||
|
||||
@@ -8,7 +8,7 @@ type AgentItemEventStatus = "running" | "completed" | "failed" | "blocked";
|
||||
type AgentItemEventKind = "tool" | "command" | "patch" | "search" | "analysis" | (string & {});
|
||||
|
||||
/** Payload for a single item shown in the agent activity stream. */
|
||||
export type AgentItemEventData = {
|
||||
export type AgentItemEventData = Record<string, unknown> & {
|
||||
itemId: string;
|
||||
phase: AgentItemEventPhase;
|
||||
kind: AgentItemEventKind;
|
||||
@@ -32,7 +32,7 @@ export type AgentItemEventData = {
|
||||
};
|
||||
|
||||
/** Incremental command output payload associated with an item/tool call. */
|
||||
export type AgentCommandOutputEventData = {
|
||||
export type AgentCommandOutputEventData = Record<string, unknown> & {
|
||||
itemId: string;
|
||||
phase: "delta" | "end";
|
||||
title: string;
|
||||
@@ -46,7 +46,7 @@ export type AgentCommandOutputEventData = {
|
||||
};
|
||||
|
||||
/** Patch summary payload emitted after an agent applies file changes. */
|
||||
export type AgentPatchSummaryEventData = {
|
||||
export type AgentPatchSummaryEventData = Record<string, unknown> & {
|
||||
itemId: string;
|
||||
phase: "end";
|
||||
title: string;
|
||||
@@ -60,7 +60,7 @@ export type AgentPatchSummaryEventData = {
|
||||
|
||||
type AgentActivityEventDataByStream = {
|
||||
item: AgentItemEventData;
|
||||
approval: AgentApprovalEventData;
|
||||
approval: AgentApprovalEventData & Record<string, unknown>;
|
||||
command_output: AgentCommandOutputEventData;
|
||||
patch: AgentPatchSummaryEventData;
|
||||
};
|
||||
@@ -79,7 +79,7 @@ export function emitAgentActivityEvent(params: AgentActivityEventParams): void {
|
||||
emitAgentEvent({
|
||||
runId: params.runId,
|
||||
stream: params.stream,
|
||||
data: params.data as unknown as Record<string, unknown>,
|
||||
data: params.data,
|
||||
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,10 +7,20 @@ type ExecPolicyLayer = {
|
||||
ask?: ExecAsk;
|
||||
};
|
||||
|
||||
type RequiredExecPolicy = Required<Pick<ExecPolicyLayer, "security" | "ask">>;
|
||||
|
||||
export function applyExecPolicyLayer<TBase extends ExecPolicyLayer & RequiredExecPolicy>(
|
||||
base: TBase,
|
||||
layer?: ExecPolicyLayer,
|
||||
): Omit<TBase, keyof ExecPolicyLayer> & ExecPolicyLayer & RequiredExecPolicy;
|
||||
export function applyExecPolicyLayer<TBase extends ExecPolicyLayer>(
|
||||
base: TBase,
|
||||
layer?: ExecPolicyLayer,
|
||||
): TBase & ExecPolicyLayer {
|
||||
): Omit<TBase, keyof ExecPolicyLayer> & ExecPolicyLayer;
|
||||
export function applyExecPolicyLayer(
|
||||
base: ExecPolicyLayer,
|
||||
layer?: ExecPolicyLayer,
|
||||
): ExecPolicyLayer {
|
||||
if (!layer) {
|
||||
return base;
|
||||
}
|
||||
@@ -19,7 +29,7 @@ export function applyExecPolicyLayer<TBase extends ExecPolicyLayer>(
|
||||
...base,
|
||||
mode: layer.mode,
|
||||
...resolveExecPolicyForMode(layer.mode),
|
||||
} as unknown as TBase & ExecPolicyLayer;
|
||||
};
|
||||
}
|
||||
if (layer.security !== undefined || layer.ask !== undefined) {
|
||||
const { mode: _mode, ...baseWithoutMode } = base;
|
||||
@@ -27,7 +37,7 @@ export function applyExecPolicyLayer<TBase extends ExecPolicyLayer>(
|
||||
...baseWithoutMode,
|
||||
security: layer.security ?? base.security,
|
||||
ask: layer.ask ?? base.ask,
|
||||
} as unknown as TBase & ExecPolicyLayer;
|
||||
};
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
+6
-12
@@ -1,6 +1,6 @@
|
||||
// SSRF policy helpers validate hostnames/IP literals, build pinned DNS lookups,
|
||||
// and create dispatcher policies for guarded network fetches.
|
||||
import { lookup as dnsLookupCb, type LookupAddress } from "node:dns";
|
||||
import { lookup as dnsLookupCb, type LookupAddress, type LookupOptions } from "node:dns";
|
||||
import { lookup as dnsLookup } from "node:dns/promises";
|
||||
import {
|
||||
extractEmbeddedIpv4FromIpv6,
|
||||
@@ -487,15 +487,6 @@ export function createPinnedLookup(params: {
|
||||
throw new Error(`Pinned lookup requires at least one address for ${params.hostname}`);
|
||||
}
|
||||
const fallback = params.fallback ?? dnsLookupCb;
|
||||
const fallbackLookup = fallback as unknown as (
|
||||
hostname: string,
|
||||
callback: LookupCallback,
|
||||
) => void;
|
||||
const fallbackWithOptions = fallback as unknown as (
|
||||
hostname: string,
|
||||
options: unknown,
|
||||
callback: LookupCallback,
|
||||
) => void;
|
||||
const records = params.addresses.map((address) => ({
|
||||
address,
|
||||
family: address.includes(":") ? 6 : 4,
|
||||
@@ -513,9 +504,12 @@ export function createPinnedLookup(params: {
|
||||
const normalized = normalizeHostname(host);
|
||||
if (!normalized || normalized !== normalizedHost) {
|
||||
if (typeof options === "function" || options === undefined) {
|
||||
return fallbackLookup(host, cb);
|
||||
return fallback(host, cb);
|
||||
}
|
||||
return fallbackWithOptions(host, options, cb);
|
||||
if (typeof options === "number") {
|
||||
return fallback(host, options, cb);
|
||||
}
|
||||
return fallback(host, options as LookupOptions, cb);
|
||||
}
|
||||
|
||||
const opts =
|
||||
|
||||
@@ -243,7 +243,8 @@ export function normalizeSessionEntry(
|
||||
entry: SessionEntryLike,
|
||||
sessionKey?: string,
|
||||
): SessionEntry | null {
|
||||
const shaped = normalizePersistedSessionEntryShape(entry, { sessionKey });
|
||||
const { room, ...entryWithoutRoom } = entry;
|
||||
const shaped = normalizePersistedSessionEntryShape(entryWithoutRoom, { sessionKey });
|
||||
if (!shaped) {
|
||||
return null;
|
||||
}
|
||||
@@ -254,11 +255,9 @@ export function normalizeSessionEntry(
|
||||
? normalized.updatedAt
|
||||
: Date.now();
|
||||
}
|
||||
const rec = normalized as unknown as Record<string, unknown>;
|
||||
if (typeof rec.groupChannel !== "string" && typeof rec.room === "string") {
|
||||
rec.groupChannel = rec.room;
|
||||
if (typeof normalized.groupChannel !== "string" && typeof room === "string") {
|
||||
normalized.groupChannel = room;
|
||||
}
|
||||
delete rec.room;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -352,10 +352,7 @@ function logToFile(
|
||||
if (level === "silent") {
|
||||
return;
|
||||
}
|
||||
const safeLevel = level;
|
||||
const method = (fileLogger as unknown as Record<string, unknown>)[safeLevel] as
|
||||
| ((...args: unknown[]) => void)
|
||||
| undefined;
|
||||
const method = fileLogger[level];
|
||||
if (typeof method !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ type CallPluginToolParams = {
|
||||
arguments?: unknown;
|
||||
};
|
||||
|
||||
type ToolWithBeforeToolCallHookContext = AnyAgentTool & {
|
||||
[BEFORE_TOOL_CALL_HOOK_CONTEXT]?: unknown;
|
||||
};
|
||||
|
||||
function toMcpContentBlock(block: unknown): unknown {
|
||||
if (!isRecord(block)) {
|
||||
return { type: "text", text: coerceChatContentText(block) };
|
||||
@@ -56,7 +60,7 @@ function resolveJsonSchemaForTool(tool: AnyAgentTool): Record<string, unknown> {
|
||||
}
|
||||
|
||||
function resolveBeforeToolCallRunId(tool: AnyAgentTool): string | undefined {
|
||||
const context = (tool as unknown as Record<symbol, unknown>)[BEFORE_TOOL_CALL_HOOK_CONTEXT];
|
||||
const context = (tool as ToolWithBeforeToolCallHookContext)[BEFORE_TOOL_CALL_HOOK_CONTEXT];
|
||||
return isRecord(context) && typeof context.runId === "string" ? context.runId : undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1049,7 +1049,7 @@ export async function runCliEntry(params: {
|
||||
});
|
||||
const outputBase = path.join(outputDir, path.parse(mediaPath).name);
|
||||
|
||||
const templCtx: TemplateContext = {
|
||||
const templCtx: TemplateContext & Record<string, unknown> = {
|
||||
...ctx,
|
||||
AttachmentPath: mediaPath,
|
||||
AttachmentUrl: params.attachment.url ?? params.attachment.path ?? mediaPath,
|
||||
@@ -1074,7 +1074,7 @@ export async function runCliEntry(params: {
|
||||
"MediaTranscribedIndexes",
|
||||
"MediaStaged",
|
||||
]) {
|
||||
delete (templCtx as unknown as Record<string, unknown>)[key];
|
||||
delete templCtx[key];
|
||||
}
|
||||
const argv = [command, ...args].map((part, index) =>
|
||||
index === 0 ? part : applyTemplate(part, templCtx),
|
||||
|
||||
@@ -35,13 +35,13 @@ export async function saveRemoteMediaForStore(params: {
|
||||
}): Promise<SavedMedia> {
|
||||
const resolvePinned = params.resolvePinnedHostnameForTest;
|
||||
const lookupFn: LookupFn | undefined = resolvePinned
|
||||
? ((async (hostname: string) => {
|
||||
? async (hostname, _options) => {
|
||||
const pinned = await resolvePinned(hostname);
|
||||
return pinned.addresses.map((address) => ({
|
||||
address,
|
||||
family: address.includes(":") ? 6 : 4,
|
||||
}));
|
||||
}) as unknown as LookupFn)
|
||||
}
|
||||
: undefined;
|
||||
const { id, path, size, contentType } = await saveRemoteMedia({
|
||||
url: params.source,
|
||||
|
||||
@@ -74,8 +74,7 @@ export function createLocalMeetingRealtimeAudioTransport(params: {
|
||||
const input = splitCommand(params.inputCommand);
|
||||
const output = splitCommand(params.outputCommand);
|
||||
const spawnFn: MeetingRealtimeAudioSpawn =
|
||||
params.spawn ??
|
||||
((command, args, options) => spawn(command, args, options) as unknown as BridgeProcess);
|
||||
params.spawn ?? ((command, args, options) => spawn(command, args, options));
|
||||
const spawnOutputProcess = () =>
|
||||
spawnFn(output.command, output.args, { stdio: ["pipe", "ignore", "pipe"] });
|
||||
let outputProcess = spawnOutputProcess();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { sortUniqueStrings } from "@openclaw/normalization-core/string-normalization";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveVoiceModelRefs } from "../tts/voice-models.js";
|
||||
@@ -42,11 +43,8 @@ type CapabilityContractKey =
|
||||
| "videoGenerationProviders"
|
||||
| "musicGenerationProviders";
|
||||
|
||||
type ProviderFor<K extends CapabilityProviderRegistryKey> = PluginRegistry[K][number] extends {
|
||||
provider: infer T;
|
||||
}
|
||||
? T
|
||||
: never;
|
||||
export type CapabilityProviderFor<K extends CapabilityProviderRegistryKey> =
|
||||
PluginRegistry[K][number]["provider"];
|
||||
type CapabilityPluginResolution = {
|
||||
runtimePluginIds: string[];
|
||||
bundledCompatPluginIds: string[];
|
||||
@@ -149,24 +147,29 @@ function createCapabilityProviderLoadOptions(params: {
|
||||
function findProviderById<K extends CapabilityProviderRegistryKey>(
|
||||
entries: PluginRegistry[K],
|
||||
providerId: string,
|
||||
): ProviderFor<K> | undefined {
|
||||
): CapabilityProviderFor<K> | undefined {
|
||||
const normalizedProviderId = normalizeCapabilityProviderId(providerId);
|
||||
if (!normalizedProviderId) {
|
||||
return undefined;
|
||||
}
|
||||
const providerEntries = entries as unknown as Array<{
|
||||
provider: ProviderFor<K> & { id?: unknown; aliases?: unknown };
|
||||
}>;
|
||||
for (const entry of providerEntries) {
|
||||
for (const entry of entries) {
|
||||
const provider: unknown = entry.provider;
|
||||
if (!isRecord(provider)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
typeof entry.provider.id === "string" &&
|
||||
normalizeCapabilityProviderId(entry.provider.id) === normalizedProviderId
|
||||
typeof provider.id === "string" &&
|
||||
normalizeCapabilityProviderId(provider.id) === normalizedProviderId
|
||||
) {
|
||||
return entry.provider;
|
||||
return entry.provider as CapabilityProviderFor<K>;
|
||||
}
|
||||
}
|
||||
for (const entry of providerEntries) {
|
||||
const aliases = Array.isArray(entry.provider.aliases) ? entry.provider.aliases : [];
|
||||
for (const entry of entries) {
|
||||
const provider: unknown = entry.provider;
|
||||
if (!isRecord(provider)) {
|
||||
continue;
|
||||
}
|
||||
const aliases = Array.isArray(provider.aliases) ? provider.aliases : [];
|
||||
if (
|
||||
aliases.some(
|
||||
(alias) =>
|
||||
@@ -174,7 +177,7 @@ function findProviderById<K extends CapabilityProviderRegistryKey>(
|
||||
normalizeCapabilityProviderId(alias) === normalizedProviderId,
|
||||
)
|
||||
) {
|
||||
return entry.provider;
|
||||
return entry.provider as CapabilityProviderFor<K>;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -183,12 +186,12 @@ function findProviderById<K extends CapabilityProviderRegistryKey>(
|
||||
function mergeCapabilityProviders<K extends CapabilityProviderRegistryKey>(
|
||||
left: PluginRegistry[K],
|
||||
right: PluginRegistry[K],
|
||||
): ProviderFor<K>[] {
|
||||
const merged = new Map<string, ProviderFor<K>>();
|
||||
const unnamed: ProviderFor<K>[] = [];
|
||||
): CapabilityProviderFor<K>[] {
|
||||
const merged = new Map<string, CapabilityProviderFor<K>>();
|
||||
const unnamed: CapabilityProviderFor<K>[] = [];
|
||||
const addEntries = (entries: PluginRegistry[K]) => {
|
||||
for (const entry of entries) {
|
||||
const provider = entry.provider as ProviderFor<K> & { id?: string };
|
||||
const provider = entry.provider as CapabilityProviderFor<K> & { id?: string };
|
||||
if (!provider.id) {
|
||||
unnamed.push(provider);
|
||||
continue;
|
||||
@@ -363,10 +366,10 @@ function filterLoadedProvidersForRequestedConfig<K extends CapabilityProviderReg
|
||||
params.key !== "realtimeVoiceProviders" &&
|
||||
params.key !== "mediaUnderstandingProviders"
|
||||
) {
|
||||
return [] as unknown as PluginRegistry[K];
|
||||
return [];
|
||||
}
|
||||
if (params.requested.size === 0) {
|
||||
return [] as unknown as PluginRegistry[K];
|
||||
return [];
|
||||
}
|
||||
return params.entries.filter((entry) => {
|
||||
const provider = entry.provider as { id?: unknown; aliases?: unknown };
|
||||
@@ -495,7 +498,7 @@ export function resolvePluginCapabilityProvider<K extends CapabilityProviderRegi
|
||||
key: K;
|
||||
providerId: string;
|
||||
cfg?: OpenClawConfig;
|
||||
}): ProviderFor<K> | undefined {
|
||||
}): CapabilityProviderFor<K> | undefined {
|
||||
if (shouldSkipCapabilityResolution(params)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -549,7 +552,7 @@ export function resolvePluginCapabilityProvider<K extends CapabilityProviderRegi
|
||||
export function resolvePluginCapabilityProviders<K extends CapabilityProviderRegistryKey>(params: {
|
||||
key: K;
|
||||
cfg?: OpenClawConfig;
|
||||
}): ProviderFor<K>[] {
|
||||
}): CapabilityProviderFor<K>[] {
|
||||
if (shouldSkipCapabilityResolution(params)) {
|
||||
return [];
|
||||
}
|
||||
@@ -573,12 +576,12 @@ export function resolvePluginCapabilityProviders<K extends CapabilityProviderReg
|
||||
: undefined;
|
||||
if (activeProviders.length > 0 && params.key !== "memoryEmbeddingProviders") {
|
||||
if (!missingRequestedProviders && !shouldMergeManifestProvidersWhenActive(params.key)) {
|
||||
return activeProviders.map((entry) => entry.provider) as ProviderFor<K>[];
|
||||
return activeProviders.map((entry) => entry.provider) as CapabilityProviderFor<K>[];
|
||||
}
|
||||
if (missingRequestedProviders) {
|
||||
removeActiveProviderIds(missingRequestedProviders, activeProviders);
|
||||
if (missingRequestedProviders.size === 0) {
|
||||
return activeProviders.map((entry) => entry.provider) as ProviderFor<K>[];
|
||||
return activeProviders.map((entry) => entry.provider) as CapabilityProviderFor<K>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,7 +653,7 @@ export function prepareMediaCapabilityProviders(params: {
|
||||
}) {
|
||||
const providers = <K extends CapabilityProviderRegistryKey>(
|
||||
key: K,
|
||||
): readonly ProviderFor<K>[] | undefined => {
|
||||
): readonly CapabilityProviderFor<K>[] | undefined => {
|
||||
if (shouldSkipCapabilityResolution({ key, cfg: params.cfg })) {
|
||||
return [];
|
||||
}
|
||||
@@ -687,7 +690,7 @@ export function prepareMediaCapabilityProviders(params: {
|
||||
}
|
||||
return Object.freeze(
|
||||
availableEntries.map((entry) => entry.provider),
|
||||
) as readonly ProviderFor<K>[];
|
||||
) as readonly CapabilityProviderFor<K>[];
|
||||
};
|
||||
return Object.freeze({
|
||||
mediaUnderstandingProviders: providers("mediaUnderstandingProviders"),
|
||||
|
||||
@@ -100,7 +100,7 @@ function asLegacyTtsConfig(value: unknown): OpenClawConfig {
|
||||
}
|
||||
|
||||
function asLegacyOpenClawConfig(value: Record<string, unknown>): OpenClawConfig {
|
||||
return value as unknown as OpenClawConfig;
|
||||
return asLegacyTtsConfig(value);
|
||||
}
|
||||
|
||||
function mockCallAt(mock: { mock: { calls: Array<Array<unknown>> } }, index: number): unknown[] {
|
||||
@@ -182,11 +182,8 @@ async function withMockedSpeechFetch(
|
||||
audioLength: number,
|
||||
) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
arrayBuffer: async () => new ArrayBuffer(audioLength),
|
||||
}));
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
const fetchMock = vi.fn(async () => new Response(new Uint8Array(audioLength)));
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
await run(fetchMock);
|
||||
} finally {
|
||||
@@ -559,12 +556,12 @@ export function describeTtsConfigContract() {
|
||||
},
|
||||
{
|
||||
name: "override",
|
||||
cfg: {
|
||||
cfg: asLegacyTtsConfig({
|
||||
...baseCfg,
|
||||
tts: {
|
||||
edge: { outputFormat: "audio-24khz-96kbitrate-mono-mp3" },
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
}),
|
||||
expected: "audio-24khz-96kbitrate-mono-mp3",
|
||||
},
|
||||
] as const)("$name", ({ cfg, expected, name }) => {
|
||||
@@ -796,22 +793,22 @@ export function describeTtsConfigContract() {
|
||||
},
|
||||
{
|
||||
name: "config wins over env",
|
||||
cfg: {
|
||||
cfg: asLegacyTtsConfig({
|
||||
...baseCfg,
|
||||
tts: { ...baseCfg.tts, openai: { baseUrl: "http://my-server:9000/v1" } },
|
||||
} as unknown as OpenClawConfig,
|
||||
}),
|
||||
env: { OPENAI_TTS_BASE_URL: "http://localhost:8880/v1" },
|
||||
expected: "http://my-server:9000/v1",
|
||||
},
|
||||
{
|
||||
name: "config slash trimming",
|
||||
cfg: {
|
||||
cfg: asLegacyTtsConfig({
|
||||
...baseCfg,
|
||||
tts: {
|
||||
...baseCfg.tts,
|
||||
openai: { baseUrl: "http://my-server:9000/v1///" },
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
}),
|
||||
env: { OPENAI_TTS_BASE_URL: undefined },
|
||||
expected: "http://my-server:9000/v1",
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
resolvePluginCapabilityProvider,
|
||||
resolvePluginCapabilityProviders,
|
||||
type CapabilityProviderFor,
|
||||
} from "./capability-provider-runtime.js";
|
||||
|
||||
type EmbeddingProviderCapabilityKey = "embeddingProviders" | "memoryEmbeddingProviders";
|
||||
@@ -29,16 +30,18 @@ export function resolveRuntimeEmbeddingProviderLookupIds(params: {
|
||||
}
|
||||
|
||||
/** Lists registered and plugin-contributed embedding provider adapters for a capability key. */
|
||||
export function listRuntimeEmbeddingProviderAdapters<TAdapter extends { id: string }>(params: {
|
||||
key: EmbeddingProviderCapabilityKey;
|
||||
export function listRuntimeEmbeddingProviderAdapters<
|
||||
K extends EmbeddingProviderCapabilityKey,
|
||||
>(params: {
|
||||
key: K;
|
||||
cfg?: OpenClawConfig;
|
||||
registered: TAdapter[];
|
||||
}): TAdapter[] {
|
||||
registered: CapabilityProviderFor<K>[];
|
||||
}): CapabilityProviderFor<K>[] {
|
||||
const merged = new Map(params.registered.map((adapter) => [adapter.id, adapter]));
|
||||
const capabilityAdapters = resolvePluginCapabilityProviders({
|
||||
key: params.key,
|
||||
cfg: params.cfg,
|
||||
}) as unknown as TAdapter[];
|
||||
});
|
||||
for (const adapter of capabilityAdapters) {
|
||||
if (!merged.has(adapter.id)) {
|
||||
merged.set(adapter.id, adapter);
|
||||
@@ -48,12 +51,16 @@ export function listRuntimeEmbeddingProviderAdapters<TAdapter extends { id: stri
|
||||
}
|
||||
|
||||
/** Resolves one embedding provider adapter from registered providers before plugin capabilities. */
|
||||
export function getRuntimeEmbeddingProviderAdapter<TAdapter extends { id: string }>(params: {
|
||||
key: EmbeddingProviderCapabilityKey;
|
||||
export function getRuntimeEmbeddingProviderAdapter<
|
||||
K extends EmbeddingProviderCapabilityKey,
|
||||
>(params: {
|
||||
key: K;
|
||||
cfg?: OpenClawConfig;
|
||||
lookupIds: string[];
|
||||
getRegisteredProvider: (id: string) => RegisteredAdapterEntry<TAdapter> | undefined;
|
||||
}): TAdapter | undefined {
|
||||
getRegisteredProvider: (
|
||||
id: string,
|
||||
) => RegisteredAdapterEntry<CapabilityProviderFor<K>> | undefined;
|
||||
}): CapabilityProviderFor<K> | undefined {
|
||||
// Resolve each exact id before trying the next configured alias. Otherwise a
|
||||
// registered alias can shadow a plugin-owned adapter for the requested id.
|
||||
for (const candidateId of params.lookupIds) {
|
||||
@@ -65,7 +72,7 @@ export function getRuntimeEmbeddingProviderAdapter<TAdapter extends { id: string
|
||||
key: params.key,
|
||||
providerId: candidateId,
|
||||
cfg: params.cfg,
|
||||
}) as TAdapter | undefined;
|
||||
});
|
||||
if (provider) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,6 @@ import type { PluginHookName } from "./types.js";
|
||||
|
||||
export class HookIsolationError extends Error {}
|
||||
|
||||
type WebAssemblyMemoryConstructor = {
|
||||
new (...args: unknown[]): object;
|
||||
prototype: object;
|
||||
};
|
||||
|
||||
function containsSharedMemory(value: unknown, seen: Set<object>): boolean {
|
||||
if (typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer) {
|
||||
return true;
|
||||
@@ -22,7 +17,7 @@ function containsSharedMemory(value: unknown, seen: Set<object>): boolean {
|
||||
}
|
||||
const webAssemblyMemory = (
|
||||
globalThis as unknown as {
|
||||
WebAssembly?: { Memory?: WebAssemblyMemoryConstructor };
|
||||
WebAssembly?: { Memory?: { new (...args: unknown[]): object; prototype: object } };
|
||||
}
|
||||
).WebAssembly?.Memory;
|
||||
if (
|
||||
|
||||
@@ -29,6 +29,8 @@ const MAX_PLUGIN_NEXT_TURN_INJECTION_TEXT_LENGTH = 32 * 1024;
|
||||
const MAX_PLUGIN_NEXT_TURN_INJECTION_IDEMPOTENCY_KEY_LENGTH = 512;
|
||||
const MAX_PLUGIN_NEXT_TURN_INJECTIONS_PER_SESSION = 32;
|
||||
|
||||
type MutableSessionEntry = SessionEntry & Record<string, unknown>;
|
||||
|
||||
function normalizeNamespace(value: string): string {
|
||||
return value.trim();
|
||||
}
|
||||
@@ -326,7 +328,7 @@ export async function patchPluginSessionExtension(params: {
|
||||
},
|
||||
(entry, context) => {
|
||||
params.assertCurrent?.();
|
||||
const entryRecord = entry as unknown as Record<string, unknown>;
|
||||
const entryRecord = entry as MutableSessionEntry;
|
||||
const pluginExtensions = { ...entry.pluginExtensions };
|
||||
const pluginState = { ...pluginExtensions[pluginId] };
|
||||
if (params.unset === true) {
|
||||
|
||||
@@ -177,9 +177,9 @@ export async function importSessionCatalogHistory(params: {
|
||||
continue;
|
||||
}
|
||||
const message = {
|
||||
...(imported as unknown as Record<string, unknown>),
|
||||
...imported,
|
||||
idempotencyKey: `${params.catalogId}-catalog:${params.threadId}:${item.id ?? index}`,
|
||||
} as unknown as AgentMessage;
|
||||
};
|
||||
await transcript.appendMessage({
|
||||
message,
|
||||
idempotencyLookup: "scan",
|
||||
|
||||
@@ -61,7 +61,7 @@ async function readCapturedResponseBodyBounded(
|
||||
maxBytes: number,
|
||||
): Promise<CapturedResponseBodyResult> {
|
||||
const clone = response.clone();
|
||||
const body = (clone as unknown as { body?: ReadableStream<Uint8Array> | null }).body;
|
||||
const body = clone.body;
|
||||
if (!body || typeof body.getReader !== "function") {
|
||||
// A real null-body Response consumes as empty. Response-like objects without
|
||||
// a stream cannot be read under a byte cap, so never call arrayBuffer().
|
||||
@@ -76,7 +76,7 @@ async function readCapturedResponseBodyBounded(
|
||||
let stalled = false;
|
||||
try {
|
||||
while (true) {
|
||||
let next: Awaited<ReturnType<typeof reader.read>>;
|
||||
let next: Awaited<ReturnType<typeof readChunkWithIdleTimeout>>;
|
||||
try {
|
||||
next = await readChunkWithIdleTimeout(
|
||||
reader,
|
||||
|
||||
@@ -40,7 +40,11 @@ export function collectAgentMemorySearchAssignments(params: {
|
||||
let defaultApiKeyAssignmentCollected = false;
|
||||
const collectedDefaultHeaderKeys = new Set<string>();
|
||||
const collectForAgent = ({ entry: rawAgent, source }: ListedAgentEntry) => {
|
||||
const rawAgentRecord = rawAgent as unknown as Record<string, unknown>;
|
||||
const rawAgentValue: unknown = rawAgent;
|
||||
if (!isRecord(rawAgentValue)) {
|
||||
return;
|
||||
}
|
||||
const rawAgentRecord = rawAgentValue;
|
||||
const agentMemory = isRecord(rawAgentRecord.memory) ? rawAgentRecord.memory : undefined;
|
||||
const memorySearch = isRecord(agentMemory?.search) ? agentMemory.search : undefined;
|
||||
const remote = isRecord(memorySearch?.remote) ? memorySearch.remote : undefined;
|
||||
@@ -48,7 +52,7 @@ export function collectAgentMemorySearchAssignments(params: {
|
||||
const agentPath =
|
||||
source.kind === "entries" ? `agents.entries.${source.key}` : `agents.list.${source.index}`;
|
||||
const active =
|
||||
rawAgentRecord.enabled !== false &&
|
||||
rawAgentRecord["enabled"] !== false &&
|
||||
(memorySearch?.enabled ?? defaultsMemorySearch?.enabled ?? true) !== false;
|
||||
const owner = {
|
||||
ownerKind: "capability",
|
||||
@@ -58,7 +62,7 @@ export function collectAgentMemorySearchAssignments(params: {
|
||||
contract: {
|
||||
defaults: defaultsMemorySearch,
|
||||
override: memorySearch,
|
||||
agentEnabled: rawAgentRecord.enabled,
|
||||
agentEnabled: rawAgentRecord["enabled"],
|
||||
},
|
||||
} satisfies SecretAssignmentOwner;
|
||||
|
||||
|
||||
@@ -78,7 +78,11 @@ export function collectAgentSandboxAssignments(params: {
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const rawAgent = candidate.entry;
|
||||
const rawAgentRecord = rawAgent as unknown as Record<string, unknown>;
|
||||
const rawAgentValue: unknown = rawAgent;
|
||||
if (!isRecord(rawAgentValue)) {
|
||||
continue;
|
||||
}
|
||||
const rawAgentRecord = rawAgentValue;
|
||||
const agentId = normalizeAgentId(candidate.entryId);
|
||||
if (seenAgentIds.has(agentId)) {
|
||||
continue;
|
||||
@@ -99,8 +103,8 @@ export function collectAgentSandboxAssignments(params: {
|
||||
? (defaultsSandbox.scope as "agent" | "session" | "shared")
|
||||
: undefined,
|
||||
perSession:
|
||||
typeof sandbox?.perSession === "boolean"
|
||||
? sandbox.perSession
|
||||
typeof sandbox?.["perSession"] === "boolean"
|
||||
? sandbox["perSession"]
|
||||
: typeof defaultsSandbox?.perSession === "boolean"
|
||||
? defaultsSandbox.perSession
|
||||
: undefined,
|
||||
@@ -112,7 +116,7 @@ export function collectAgentSandboxAssignments(params: {
|
||||
const owner = sandboxSecretOwner(agentId, {
|
||||
defaults: defaultsSandbox,
|
||||
override: sandbox,
|
||||
agentEnabled: rawAgentRecord.enabled,
|
||||
agentEnabled: rawAgentRecord["enabled"],
|
||||
});
|
||||
|
||||
for (const key of SANDBOX_SSH_SECRET_KEYS) {
|
||||
|
||||
@@ -797,7 +797,7 @@ export class EmbeddedTuiBackend implements TuiBackend {
|
||||
ok: true as const,
|
||||
path: target.storePath,
|
||||
key: target.canonicalKey ?? opts.key,
|
||||
entry: applied.entry as unknown as Record<string, unknown>,
|
||||
entry: { ...applied.entry },
|
||||
resolved: {
|
||||
modelProvider: resolved.provider,
|
||||
model: resolved.model,
|
||||
|
||||
+14
-13
@@ -499,10 +499,10 @@ export function beginTuiShutdown(params: {
|
||||
clearTimeoutFn?: (timer: TuiProcessExitTimer) => void;
|
||||
setTimeoutFn?: TuiProcessExitTimeout;
|
||||
}): TuiProcessExitTimer {
|
||||
const setTimeoutFn =
|
||||
params.setTimeoutFn ??
|
||||
((callback, timeoutMs) => setTimeout(callback, timeoutMs) as unknown as TuiProcessExitTimer);
|
||||
const hardExitTimer = setTimeoutFn(params.forceExit, params.hardExitMs);
|
||||
const hardExit = params.setTimeoutFn
|
||||
? { kind: "custom" as const, timer: params.setTimeoutFn(params.forceExit, params.hardExitMs) }
|
||||
: { kind: "native" as const, timer: setTimeout(params.forceExit, params.hardExitMs) };
|
||||
const hardExitTimer = hardExit.timer;
|
||||
hardExitTimer.unref?.();
|
||||
// Stop referenced animations before transport teardown can stall or redraw.
|
||||
params.disposeStatus();
|
||||
@@ -529,10 +529,11 @@ export function beginTuiShutdown(params: {
|
||||
})
|
||||
.finally(() => {
|
||||
if (params.keepHardExitArmed !== true) {
|
||||
const clearTimeoutFn =
|
||||
params.clearTimeoutFn ??
|
||||
((timer) => clearTimeout(timer as unknown as ReturnType<typeof setTimeout>));
|
||||
clearTimeoutFn(hardExitTimer);
|
||||
if (params.clearTimeoutFn) {
|
||||
params.clearTimeoutFn(hardExitTimer);
|
||||
} else if (hardExit.kind === "native") {
|
||||
clearTimeout(hardExit.timer);
|
||||
}
|
||||
}
|
||||
params.disposeStatus();
|
||||
})
|
||||
@@ -607,23 +608,23 @@ export function scheduleProcessExitAfterTuiReturn(
|
||||
} = {},
|
||||
): TuiProcessExitTimer {
|
||||
const delayMs = Math.max(0, Math.floor(params.delayMs ?? TUI_PROCESS_EXIT_AFTER_RETURN_MS));
|
||||
const setTimeoutFn =
|
||||
params.setTimeoutFn ??
|
||||
((callback, timeoutMs) => setTimeout(callback, timeoutMs) as unknown as TuiProcessExitTimer);
|
||||
const exit = params.exit ?? ((code?: number) => process.exit(code));
|
||||
const writeStderr =
|
||||
params.writeStderr ??
|
||||
((text: string) => {
|
||||
process.stderr.write(text);
|
||||
});
|
||||
const timer = setTimeoutFn(() => {
|
||||
const onTimeout = () => {
|
||||
try {
|
||||
writeStderr("openclaw tui forcing process exit after return\n");
|
||||
} catch {
|
||||
// Best effort only; forced exit must not depend on stderr.
|
||||
}
|
||||
exit(0);
|
||||
}, delayMs);
|
||||
};
|
||||
const timer = params.setTimeoutFn
|
||||
? params.setTimeoutFn(onTimeout, delayMs)
|
||||
: setTimeout(onTimeout, delayMs);
|
||||
timer.unref?.();
|
||||
return timer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user