From 4fa961d4f1494ef29fb19dfd683a9f12f8f62d76 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 19:53:02 +0100 Subject: [PATCH] refactor(lint): enable map spread rule --- .oxlintrc.json | 5 +- extensions/arcee/provider-catalog.ts | 7 +- extensions/discord/src/directory-config.ts | 9 ++- extensions/feishu/src/docx.ts | 2 +- extensions/google/provider-policy.ts | 2 +- .../src/gemini-web-search-provider.runtime.ts | 7 +- extensions/irc/src/channel.ts | 2 +- extensions/lmstudio/src/setup.ts | 13 ++-- extensions/matrix/src/channel.ts | 4 +- extensions/memory-wiki/src/compile.ts | 68 ++++++++++------- extensions/mistral/model-definitions.ts | 4 +- extensions/moonshot/provider-catalog.ts | 4 +- extensions/ollama/src/provider-models.ts | 5 +- extensions/qa-lab/src/bus-queries.ts | 8 +- extensions/qa-lab/src/character-eval.ts | 8 +- extensions/qwen/provider-catalog.ts | 2 +- extensions/slack/src/action-runtime.ts | 2 +- .../telegram/src/bot-native-command-menu.ts | 2 +- extensions/vydra/speech-provider.ts | 2 +- ...compresses-common-formats-jpeg-cap.test.ts | 7 +- scripts/cron_usage_report.ts | 9 ++- scripts/lib/extension-test-plan.mjs | 12 +-- scripts/lib/plugin-clawhub-release.ts | 20 +++-- scripts/lib/plugin-npm-release.ts | 9 ++- scripts/openclaw-cross-os-release-checks.ts | 13 ++-- scripts/postinstall-bundled-plugins.mjs | 9 ++- src/agents/models-config.merge.ts | 19 +++-- .../pi-embedded-runner.bundle-mcp.e2e.test.ts | 2 +- .../effective-tool-policy.ts | 2 +- .../tool-result-truncation.ts | 5 +- src/agents/pi-embedded-subscribe.tools.ts | 4 +- src/agents/pi-tools.read.ts | 12 +-- .../test-helpers/fast-openclaw-tools.ts | 2 +- src/agents/tool-call-id.ts | 2 +- src/agents/tools/common.ts | 21 ++--- .../reply/dispatch-acp-attachments.ts | 4 +- src/auto-reply/reply/reply-payloads-dedupe.ts | 5 +- src/commands/auth-choice-options.ts | 8 +- src/commands/backup-shared.ts | 5 +- src/commands/channel-setup/discovery.ts | 20 ++--- .../doctor-bundled-plugin-runtime-deps.ts | 15 ++-- .../shared/legacy-config-core-normalizers.ts | 5 +- src/commands/sessions-cleanup.ts | 19 ++--- src/commands/sessions.ts | 11 +-- src/commands/status-all/report-sections.ts | 2 +- src/commands/status.test.ts | 10 +-- src/config/defaults.ts | 5 +- src/config/sessions/targets.ts | 4 +- src/cron/isolated-agent/delivery-dispatch.ts | 5 +- src/gateway/config-reload-plan.ts | 76 ++++++++++--------- .../gateway-models.profiles.live.test.ts | 7 +- .../server-methods/agents-mutate.test.ts | 4 +- src/gateway/server-methods/tools-catalog.ts | 7 +- src/gateway/server-methods/usage.ts | 2 +- src/infra/bonjour-discovery.ts | 5 +- src/infra/exec-approvals.ts | 5 +- src/markdown/ir.ts | 9 ++- src/pairing/pairing-store.test.ts | 8 +- src/plugins/cli-backends.runtime.ts | 7 +- src/plugins/loader.test.ts | 9 ++- src/plugins/provider-discovery.runtime.ts | 7 +- src/plugins/providers.runtime.ts | 14 ++-- .../runtime-live-state-guardrails.test.ts | 49 +++++++----- src/plugins/status.ts | 11 +-- .../web-provider-public-artifacts.explicit.ts | 6 +- src/plugins/web-provider-resolution-shared.ts | 5 +- src/process/supervisor/registry.ts | 4 +- src/tasks/task-registry.ts | 4 +- .../agents/prompt-composition-scenarios.ts | 2 +- ui/src/ui/chat/message-normalizer.ts | 8 +- ui/src/ui/chat/session-controls.ts | 7 +- ui/src/ui/views/config.ts | 13 ++-- ui/src/ui/views/dreaming.ts | 5 +- 73 files changed, 352 insertions(+), 344 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 82006eecb10..c99082b2a73 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -17,7 +17,7 @@ "eslint-plugin-unicorn/prefer-set-size": "error", "oxc/no-accumulating-spread": "error", "oxc/no-async-endpoint-handlers": "error", - "oxc/no-map-spread": "off", + "oxc/no-map-spread": "error", "typescript/consistent-return": "error", "typescript/no-explicit-any": "error", "typescript/no-extraneous-class": "error", @@ -55,7 +55,8 @@ { "files": ["src/security/**"], "rules": { - "eslint/no-warning-comments": "off" + "eslint/no-warning-comments": "off", + "oxc/no-map-spread": "off" } }, { diff --git a/extensions/arcee/provider-catalog.ts b/extensions/arcee/provider-catalog.ts index e8a95566a05..a0f6c9d325e 100644 --- a/extensions/arcee/provider-catalog.ts +++ b/extensions/arcee/provider-catalog.ts @@ -36,10 +36,9 @@ export function buildArceeCatalogModels(): NonNullable { - return buildArceeCatalogModels().map((model) => ({ - ...model, - id: toArceeOpenRouterModelId(model.id), - })); + return buildArceeCatalogModels().map((model) => + Object.assign({}, model, { id: toArceeOpenRouterModelId(model.id) }), + ); } export function buildArceeProvider(): ModelProviderConfig { diff --git a/extensions/discord/src/directory-config.ts b/extensions/discord/src/directory-config.ts index 1158b3bc1e4..f833efe0d48 100644 --- a/extensions/discord/src/directory-config.ts +++ b/extensions/discord/src/directory-config.ts @@ -25,10 +25,11 @@ export const listDiscordDirectoryPeersFromConfig = createResolvedDirectoryEntrie resolveAccount: (cfg, accountId) => resolveDiscordDirectoryConfigAccount(cfg, accountId), resolveSources: (account) => { const allowFrom = account.config.allowFrom ?? account.config.dm?.allowFrom ?? []; - const guildUsers = Object.values(account.config.guilds ?? {}).flatMap((guild) => [ - ...(guild.users ?? []), - ...Object.values(guild.channels ?? {}).flatMap((channel) => channel.users ?? []), - ]); + const guildUsers = Object.values(account.config.guilds ?? {}).flatMap((guild) => + (guild.users ?? []).concat( + Object.values(guild.channels ?? {}).flatMap((channel) => channel.users ?? []), + ), + ); return [allowFrom, Object.keys(account.config.dms ?? {}), guildUsers]; }, normalizeId: (raw) => { diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 7faf53bedfd..f12667a818b 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -110,7 +110,7 @@ function cleanBlocksForInsert(blocks: FeishuDocxBlock[]): { .map((block) => { if (block.block_type === 31 && block.table?.merge_info) { const { merge_info: _merge_info, ...tableRest } = block.table; - return { ...block, table: tableRest }; + return Object.assign({}, block, { table: tableRest }); } return block; }); diff --git a/extensions/google/provider-policy.ts b/extensions/google/provider-policy.ts index 4b1766b0a38..d3127c1c4a4 100644 --- a/extensions/google/provider-policy.ts +++ b/extensions/google/provider-policy.ts @@ -141,7 +141,7 @@ function normalizeProviderModels( return model; } mutated = true; - return { ...model, id: nextId }; + return Object.assign({}, model, { id: nextId }); }); return mutated ? { ...provider, models: nextModels } : provider; diff --git a/extensions/google/src/gemini-web-search-provider.runtime.ts b/extensions/google/src/gemini-web-search-provider.runtime.ts index 2f3252930ec..c26c9d896f0 100644 --- a/extensions/google/src/gemini-web-search-provider.runtime.ts +++ b/extensions/google/src/gemini-web-search-provider.runtime.ts @@ -120,10 +120,9 @@ async function runGeminiSearch(params: { for (let index = 0; index < rawCitations.length; index += 10) { const batch = rawCitations.slice(index, index + 10); const resolved = await Promise.all( - batch.map(async (citation) => ({ - ...citation, - url: await resolveCitationRedirectUrl(citation.url), - })), + batch.map(async (citation) => + Object.assign({}, citation, { url: await resolveCitationRedirectUrl(citation.url) }), + ), ); citations.push(...resolved); } diff --git a/extensions/irc/src/channel.ts b/extensions/irc/src/channel.ts index eef0cca8df1..e0e8a5e2bee 100644 --- a/extensions/irc/src/channel.ts +++ b/extensions/irc/src/channel.ts @@ -279,7 +279,7 @@ export const ircPlugin: ChannelPlugin = createChat listPeers: async (params) => listIrcDirectoryPeersFromConfig(params), listGroups: async (params) => { const entries = await listIrcDirectoryGroupsFromConfig(params); - return entries.map((entry) => ({ ...entry, name: entry.id })); + return entries.map((entry) => Object.assign({}, entry, { name: entry.id })); }, }), status: createComputedAccountStatusAdapter({ diff --git a/extensions/lmstudio/src/setup.ts b/extensions/lmstudio/src/setup.ts index 1ab902dff71..31526f1eda7 100644 --- a/extensions/lmstudio/src/setup.ts +++ b/extensions/lmstudio/src/setup.ts @@ -817,10 +817,11 @@ export async function prepareLmstudioDynamicModels( headers, quiet: true, }); - return discoveredModels.map((model) => ({ - ...model, - provider: PROVIDER_ID, - api: ctx.providerConfig?.api ?? "openai-completions", - baseUrl, - })); + return discoveredModels.map((model) => + Object.assign({}, model, { + provider: PROVIDER_ID, + api: ctx.providerConfig?.api ?? `openai-completions`, + baseUrl, + }), + ); } diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index f8d6d6fe441..8c7aa5a5a6d 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -364,7 +364,9 @@ export const matrixPlugin: ChannelPlugin = return entries.map((entry) => { const raw = entry.id.startsWith("user:") ? entry.id.slice("user:".length) : entry.id; const incomplete = !raw.startsWith("@") || !raw.includes(":"); - return incomplete ? { ...entry, name: "incomplete id; expected @user:server" } : entry; + return incomplete + ? Object.assign({}, entry, { name: `incomplete id; expected @user:server` }) + : entry; }); }, listGroups: async (params) => await listMatrixDirectoryGroupsFromConfig(params), diff --git a/extensions/memory-wiki/src/compile.ts b/extensions/memory-wiki/src/compile.ts index cae5d5bba46..2cc483a0fb3 100644 --- a/extensions/memory-wiki/src/compile.ts +++ b/extensions/memory-wiki/src/compile.ts @@ -840,35 +840,45 @@ function buildAgentDigest(params: { .toSorted((left, right) => left.relativePath.localeCompare(right.relativePath)) .map((page) => { const pageFreshness = assessPageFreshness(page); - return { - ...(page.id ? { id: page.id } : {}), - title: page.title, - kind: page.kind, - path: page.relativePath, - sourceIds: [...page.sourceIds], - questions: [...page.questions], - contradictions: [...page.contradictions], - ...(typeof page.confidence === "number" ? { confidence: page.confidence } : {}), - freshnessLevel: pageFreshness.level, - ...(pageFreshness.lastTouchedAt ? { lastTouchedAt: pageFreshness.lastTouchedAt } : {}), - claimCount: page.claims.length, - topClaims: sortClaims(page) - .slice(0, 5) - .map((claim) => { - const freshness = assessClaimFreshness({ page, claim }); - return { - ...(claim.id ? { id: claim.id } : {}), - text: claim.text, - status: normalizeClaimStatus(claim.status), - ...(typeof claim.confidence === "number" ? { confidence: claim.confidence } : {}), - evidenceCount: claim.evidence.length, - missingEvidence: claim.evidence.length === 0, - evidence: [...claim.evidence], - freshnessLevel: freshness.level, - ...(freshness.lastTouchedAt ? { lastTouchedAt: freshness.lastTouchedAt } : {}), - }; - }), - }; + return Object.assign( + {}, + page.id ? { id: page.id } : {}, + { + title: page.title, + kind: page.kind, + path: page.relativePath, + sourceIds: [...page.sourceIds], + questions: [...page.questions], + contradictions: [...page.contradictions], + }, + typeof page.confidence === "number" ? { confidence: page.confidence } : {}, + { freshnessLevel: pageFreshness.level }, + pageFreshness.lastTouchedAt ? { lastTouchedAt: pageFreshness.lastTouchedAt } : {}, + { + claimCount: page.claims.length, + topClaims: sortClaims(page) + .slice(0, 5) + .map((claim) => { + const freshness = assessClaimFreshness({ page, claim }); + return Object.assign( + {}, + claim.id ? { id: claim.id } : {}, + { + text: claim.text, + status: normalizeClaimStatus(claim.status), + }, + typeof claim.confidence === "number" ? { confidence: claim.confidence } : {}, + { + evidenceCount: claim.evidence.length, + missingEvidence: claim.evidence.length === 0, + evidence: [...claim.evidence], + freshnessLevel: freshness.level, + }, + freshness.lastTouchedAt ? { lastTouchedAt: freshness.lastTouchedAt } : {}, + ); + }), + }, + ); }); return { pageCounts: params.pageCounts, diff --git a/extensions/mistral/model-definitions.ts b/extensions/mistral/model-definitions.ts index 6ee5483d86b..34012e48171 100644 --- a/extensions/mistral/model-definitions.ts +++ b/extensions/mistral/model-definitions.ts @@ -93,5 +93,7 @@ export function buildMistralModelDefinition(): ModelDefinitionConfig { } export function buildMistralCatalogModels(): ModelDefinitionConfig[] { - return MISTRAL_MODEL_CATALOG.map((model) => ({ ...model, input: [...model.input] })); + return MISTRAL_MODEL_CATALOG.map((model) => + Object.assign({}, model, { input: [...model.input] }), + ); } diff --git a/extensions/moonshot/provider-catalog.ts b/extensions/moonshot/provider-catalog.ts index bc63e729235..86b4b52a6ad 100644 --- a/extensions/moonshot/provider-catalog.ts +++ b/extensions/moonshot/provider-catalog.ts @@ -75,6 +75,8 @@ export function buildMoonshotProvider(): ModelProviderConfig { return { baseUrl: MOONSHOT_BASE_URL, api: "openai-completions", - models: MOONSHOT_MODEL_CATALOG.map((model) => ({ ...model, input: [...model.input] })), + models: MOONSHOT_MODEL_CATALOG.map((model) => + Object.assign({}, model, { input: [...model.input] }), + ), }; } diff --git a/extensions/ollama/src/provider-models.ts b/extensions/ollama/src/provider-models.ts index 6d86e62108d..818bad79804 100644 --- a/extensions/ollama/src/provider-models.ts +++ b/extensions/ollama/src/provider-models.ts @@ -194,11 +194,10 @@ export async function enrichOllamaModelsWithContext( const batchResults = await Promise.all( batch.map(async (model) => { const showInfo = await queryOllamaModelShowInfoCached(apiBase, model); - return { - ...model, + return Object.assign({}, model, { contextWindow: showInfo.contextWindow, capabilities: showInfo.capabilities, - }; + }); }), ); enriched.push(...batchResults); diff --git a/extensions/qa-lab/src/bus-queries.ts b/extensions/qa-lab/src/bus-queries.ts index b0f95639fb8..be478257be9 100644 --- a/extensions/qa-lab/src/bus-queries.ts +++ b/extensions/qa-lab/src/bus-queries.ts @@ -85,10 +85,10 @@ export function buildQaBusSnapshot(params: { }): QaBusStateSnapshot { return { cursor: params.cursor, - conversations: Array.from(params.conversations.values()).map((conversation) => ({ - ...conversation, - })), - threads: Array.from(params.threads.values()).map((thread) => ({ ...thread })), + conversations: Array.from(params.conversations.values()).map((conversation) => + Object.assign({}, conversation), + ), + threads: Array.from(params.threads.values()).map((thread) => Object.assign({}, thread)), messages: Array.from(params.messages.values()).map((message) => cloneMessage(message)), events: params.events.map((event) => cloneEvent(event)), }; diff --git a/extensions/qa-lab/src/character-eval.ts b/extensions/qa-lab/src/character-eval.ts index 6309d874d29..2e62d4f40f4 100644 --- a/extensions/qa-lab/src/character-eval.ts +++ b/extensions/qa-lab/src/character-eval.ts @@ -651,10 +651,10 @@ export async function runQaCharacterEval(params: QaCharacterEvalParams) { timeoutMs: judgeTimeoutMs, }); rankings = parseJudgeReply(rawReply, new Set(judgePrompt.labelToModel.keys())).map( - (ranking) => ({ - ...ranking, - model: judgePrompt.labelToModel.get(ranking.model) ?? ranking.model, - }), + (ranking) => + Object.assign({}, ranking, { + model: judgePrompt.labelToModel.get(ranking.model) ?? ranking.model, + }), ); } catch (error) { judgeError = formatErrorMessage(error); diff --git a/extensions/qwen/provider-catalog.ts b/extensions/qwen/provider-catalog.ts index 53d44f3f490..01b19974cb3 100644 --- a/extensions/qwen/provider-catalog.ts +++ b/extensions/qwen/provider-catalog.ts @@ -6,7 +6,7 @@ export function buildQwenProvider(params?: { baseUrl?: string }): ModelProviderC return { baseUrl, api: "openai-completions", - models: buildQwenModelCatalogForBaseUrl(baseUrl).map((model) => ({ ...model })), + models: buildQwenModelCatalogForBaseUrl(baseUrl).map((model) => Object.assign({}, model)), }; } diff --git a/extensions/slack/src/action-runtime.ts b/extensions/slack/src/action-runtime.ts index 4688c348b70..d5189eeb046 100644 --- a/extensions/slack/src/action-runtime.ts +++ b/extensions/slack/src/action-runtime.ts @@ -446,7 +446,7 @@ export async function handleSlackAction( (pin.message as { ts?: unknown }).ts, ) : pin.message; - return message ? { ...pin, message } : pin; + return message ? Object.assign({}, pin, { message }) : pin; }); return jsonResult({ ok: true, pins: normalizedPins }); } diff --git a/extensions/telegram/src/bot-native-command-menu.ts b/extensions/telegram/src/bot-native-command-menu.ts index e9f914d2435..176407826b7 100644 --- a/extensions/telegram/src/bot-native-command-menu.ts +++ b/extensions/telegram/src/bot-native-command-menu.ts @@ -70,7 +70,7 @@ function fitTelegramCommandsWithinTextBudget( const description = truncateTelegramCommandText(command.description, descriptionCap); if (description !== command.description) { descriptionTrimmed = true; - return { ...command, description }; + return Object.assign({}, command, { description }); } return command; }); diff --git a/extensions/vydra/speech-provider.ts b/extensions/vydra/speech-provider.ts index a45f0ec68a3..43e8630cdff 100644 --- a/extensions/vydra/speech-provider.ts +++ b/extensions/vydra/speech-provider.ts @@ -86,7 +86,7 @@ export function buildVydraSpeechProvider(): SpeechProviderPlugin { models: [DEFAULT_VYDRA_SPEECH_MODEL], voices: VYDRA_SPEECH_VOICES.map((voice) => voice.id), resolveConfig: ({ rawConfig }) => normalizeVydraSpeechConfig(rawConfig), - listVoices: async () => VYDRA_SPEECH_VOICES.map((voice) => ({ ...voice })), + listVoices: async () => VYDRA_SPEECH_VOICES.map((voice) => Object.assign({}, voice)), isConfigured: ({ providerConfig }) => Boolean(readVydraSpeechConfig(providerConfig).apiKey || process.env.VYDRA_API_KEY), synthesize: async (req) => { diff --git a/extensions/whatsapp/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts b/extensions/whatsapp/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts index 71674cf3474..ffc80772560 100644 --- a/extensions/whatsapp/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts +++ b/extensions/whatsapp/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts @@ -169,10 +169,9 @@ describe("web auto-reply", () => { const sharedRaw = crypto.randomBytes(width * height * 3); const renderedFormats = await Promise.all( - formats.map(async (fmt) => ({ - ...fmt, - image: await fmt.make(sharedRaw, { width, height }), - })), + formats.map(async (fmt) => + Object.assign({}, fmt, { image: await fmt.make(sharedRaw, { width, height }) }), + ), ); await withMediaCap(SMALL_MEDIA_CAP_MB, async () => { diff --git a/scripts/cron_usage_report.ts b/scripts/cron_usage_report.ts index 827106d3ceb..0e7fb3ca727 100644 --- a/scripts/cron_usage_report.ts +++ b/scripts/cron_usage_report.ts @@ -215,10 +215,11 @@ export async function main() { } const rows = Object.values(totalsByJob) - .map((r) => ({ - ...r, - models: Object.values(r.models).toSorted((a, b) => b.total_tokens - a.total_tokens), - })) + .map((r) => + Object.assign({}, r, { + models: Object.values(r.models).toSorted((a, b) => b.total_tokens - a.total_tokens), + }), + ) .toSorted((a, b) => b.total_tokens - a.total_tokens); if (asJson) { diff --git a/scripts/lib/extension-test-plan.mjs b/scripts/lib/extension-test-plan.mjs index b03c65bbaa8..1e9498e76b2 100644 --- a/scripts/lib/extension-test-plan.mjs +++ b/scripts/lib/extension-test-plan.mjs @@ -197,11 +197,12 @@ function mergeTestPlans(plans) { } const planGroups = [...groupsByConfig.values()] - .map((group) => ({ - ...group, - extensionIds: group.extensionIds.toSorted((left, right) => left.localeCompare(right)), - roots: [...new Set(group.roots)], - })) + .map((group) => + Object.assign({}, group, { + extensionIds: group.extensionIds.toSorted((left, right) => left.localeCompare(right)), + roots: [...new Set(group.roots)], + }), + ) .toSorted((left, right) => left.config.localeCompare(right.config)); return { @@ -279,6 +280,7 @@ export function createExtensionTestShards(params = {}) { return shards .map((shard, index) => Object.assign( + {}, { index, checkName: `checks-node-extensions-shard-${index + 1}` }, mergeTestPlans(shard.plans), ), diff --git a/scripts/lib/plugin-clawhub-release.ts b/scripts/lib/plugin-clawhub-release.ts index e9a122a25f7..9f0369cfd5c 100644 --- a/scripts/lib/plugin-clawhub-release.ts +++ b/scripts/lib/plugin-clawhub-release.ts @@ -352,17 +352,15 @@ export async function collectPluginClawHubReleasePlan(params?: { }); const all = await Promise.all( - selectedPublishable.map(async (plugin) => ({ - ...plugin, - alreadyPublished: await isPluginVersionPublishedOnClawHub( - plugin.packageName, - plugin.version, - { - registryBaseUrl: params?.registryBaseUrl, - fetchImpl: params?.fetchImpl, - }, - ), - })), + selectedPublishable.map(async (plugin) => + Object.assign({}, plugin, { + alreadyPublished: await isPluginVersionPublishedOnClawHub( + plugin.packageName, + plugin.version, + { registryBaseUrl: params?.registryBaseUrl, fetchImpl: params?.fetchImpl }, + ), + }), + ), ); return { diff --git a/scripts/lib/plugin-npm-release.ts b/scripts/lib/plugin-npm-release.ts index c4f16c6504d..d375f39a15f 100644 --- a/scripts/lib/plugin-npm-release.ts +++ b/scripts/lib/plugin-npm-release.ts @@ -462,10 +462,11 @@ export function collectPluginReleasePlan(params?: { }) : allPublishable; - const all = selectedPublishable.map((plugin) => ({ - ...plugin, - alreadyPublished: isPluginVersionPublished(plugin.packageName, plugin.version), - })); + const all = selectedPublishable.map((plugin) => + Object.assign({}, plugin, { + alreadyPublished: isPluginVersionPublished(plugin.packageName, plugin.version), + }), + ); return { all, diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index ae8925caba2..ea9b7a31ece 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -167,12 +167,13 @@ export function resolveRunnerMatrix(params) { ]; return { include: runners.flatMap((runner) => - suites.map((suite) => ({ - ...runner, - suite, - suite_label: formatSuiteLabel(suite), - lane: suite.includes("upgrade") || suite === "dev-update" ? "upgrade" : "fresh", - })), + suites.map((suite) => + Object.assign({}, runner, { + suite, + suite_label: formatSuiteLabel(suite), + lane: suite.includes(`upgrade`) || suite === `dev-update` ? `upgrade` : `fresh`, + }), + ), ), }; } diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index 288af7cc042..86db9144cd3 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -449,10 +449,11 @@ export function discoverBundledPluginRuntimeDeps(params = {}) { } return [...deps.values()] - .map((dep) => ({ - ...dep, - pluginIds: [...dep.pluginIds].toSorted((a, b) => a.localeCompare(b)), - })) + .map((dep) => + Object.assign({}, dep, { + pluginIds: [...dep.pluginIds].toSorted((a, b) => a.localeCompare(b)), + }), + ) .toSorted((a, b) => a.name.localeCompare(b.name)); } diff --git a/src/agents/models-config.merge.ts b/src/agents/models-config.merge.ts index 25fcef8547c..fac281b5c26 100644 --- a/src/agents/models-config.merge.ts +++ b/src/agents/models-config.merge.ts @@ -97,14 +97,17 @@ export function mergeProviderModels( implicitValue: implicitModel.maxTokens, }); - return { - ...explicitModel, - input: implicitModel.input, - reasoning: "reasoning" in explicitModel ? explicitModel.reasoning : implicitModel.reasoning, - ...(contextWindow === undefined ? {} : { contextWindow }), - ...(contextTokens === undefined ? {} : { contextTokens }), - ...(maxTokens === undefined ? {} : { maxTokens }), - }; + return Object.assign( + {}, + explicitModel, + { + input: implicitModel.input, + reasoning: `reasoning` in explicitModel ? explicitModel.reasoning : implicitModel.reasoning, + }, + contextWindow === undefined ? {} : { contextWindow }, + contextTokens === undefined ? {} : { contextTokens }, + maxTokens === undefined ? {} : { maxTokens }, + ); }); for (const implicitModel of implicitModels) { diff --git a/src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts b/src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts index 779cf224c0a..209d850a79c 100644 --- a/src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts +++ b/src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts @@ -126,7 +126,7 @@ vi.mock("@mariozechner/pi-ai", async () => { context: { messages?: Array<{ role?: string; content?: unknown }> }, ) => { streamCallCount += 1; - const messages = (context.messages ?? []).map((message) => ({ ...message })); + const messages = (context.messages ?? []).map((message) => Object.assign({}, message)); observedContexts.push(messages); const stream = actual.createAssistantMessageEventStream(); queueMicrotask(() => { diff --git a/src/agents/pi-embedded-runner/effective-tool-policy.ts b/src/agents/pi-embedded-runner/effective-tool-policy.ts index e6598ec6cfd..a355a7f49a8 100644 --- a/src/agents/pi-embedded-runner/effective-tool-policy.ts +++ b/src/agents/pi-embedded-runner/effective-tool-policy.ts @@ -168,7 +168,7 @@ export function applyFinalEffectiveToolPolicy( }), { policy: params.sandboxToolPolicy, label: "sandbox tools.allow" }, { policy: subagentPolicy, label: "subagent tools.allow" }, - ].map((step) => ({ ...step, suppressUnavailableCoreToolWarning: true })); + ].map((step) => Object.assign({}, step, { suppressUnavailableCoreToolWarning: true })); return applyToolPolicyPipeline({ tools: ownerFiltered, toolMeta: (tool) => getPluginToolMeta(tool), diff --git a/src/agents/pi-embedded-runner/tool-result-truncation.ts b/src/agents/pi-embedded-runner/tool-result-truncation.ts index 73c004e36f4..38625993d2e 100644 --- a/src/agents/pi-embedded-runner/tool-result-truncation.ts +++ b/src/agents/pi-embedded-runner/tool-result-truncation.ts @@ -288,13 +288,12 @@ export function truncateToolResultMessage( 1, Math.min(maxChars, Math.max(minKeepChars + defaultSuffix.length, proportionalBudget)), ); - return { - ...textBlock, + return Object.assign({}, textBlock, { text: truncateToolResultText(textBlock.text, blockBudget, { suffix: suffixFactory, minKeepChars, }), - }; + }); }); return { ...msg, content: newContent } as AgentMessage; diff --git a/src/agents/pi-embedded-subscribe.tools.ts b/src/agents/pi-embedded-subscribe.tools.ts index 3ce04c5427c..7021bcb3b72 100644 --- a/src/agents/pi-embedded-subscribe.tools.ts +++ b/src/agents/pi-embedded-subscribe.tools.ts @@ -105,14 +105,14 @@ export function sanitizeToolResult(result: unknown): unknown { const entry = item as Record; const type = readStringValue(entry.type); if (type === "text" && typeof entry.text === "string") { - return { ...entry, text: truncateToolText(entry.text) }; + return Object.assign({}, entry, { text: truncateToolText(entry.text) }); } if (type === "image") { const data = readStringValue(entry.data); const bytes = data ? data.length : undefined; const cleaned = { ...entry }; delete cleaned.data; - return { ...cleaned, bytes, omitted: true }; + return Object.assign({}, cleaned, { bytes, omitted: true }); } return entry; }); diff --git a/src/agents/pi-tools.read.ts b/src/agents/pi-tools.read.ts index ade0c7e37e0..9278c9f3843 100644 --- a/src/agents/pi-tools.read.ts +++ b/src/agents/pi-tools.read.ts @@ -126,10 +126,7 @@ function withToolResultText( (block as { type?: unknown }).type === "text" ) { replaced = true; - return { - ...(block as TextContentBlock), - text, - }; + return Object.assign({}, block as TextContentBlock, { text }); } return block; }); @@ -332,7 +329,7 @@ async function normalizeReadImageResult( const nextContent = content.map((block) => { if (block && typeof block === "object" && (block as { type?: unknown }).type === "image") { const b = block as ImageContentBlock & { mimeType: string }; - return { ...b, mimeType: sniffed } satisfies ImageContentBlock; + return Object.assign({}, b, { mimeType: sniffed }) satisfies ImageContentBlock; } if ( block && @@ -341,10 +338,9 @@ async function normalizeReadImageResult( typeof (block as { text?: unknown }).text === "string" ) { const b = block as TextContentBlock & { text: string }; - return { - ...b, + return Object.assign({}, b, { text: rewriteReadImageHeader(b.text, sniffed), - } satisfies TextContentBlock; + }) satisfies TextContentBlock; } return block; }); diff --git a/src/agents/test-helpers/fast-openclaw-tools.ts b/src/agents/test-helpers/fast-openclaw-tools.ts index 34f26eaa887..3ff7b3d7200 100644 --- a/src/agents/test-helpers/fast-openclaw-tools.ts +++ b/src/agents/test-helpers/fast-openclaw-tools.ts @@ -39,7 +39,7 @@ const coreTools = [ ]; vi.mock("../openclaw-tools.js", () => ({ - createOpenClawTools: () => coreTools.map((tool) => ({ ...tool })), + createOpenClawTools: () => coreTools.map((tool) => Object.assign({}, tool)), __testing: { setDepsForTest: () => {}, }, diff --git a/src/agents/tool-call-id.ts b/src/agents/tool-call-id.ts index 0d2d5b2a9a0..bcaf5007980 100644 --- a/src/agents/tool-call-id.ts +++ b/src/agents/tool-call-id.ts @@ -446,7 +446,7 @@ function rewriteAssistantToolCallIds(params: { return block; } changed = true; - return { ...(block as unknown as Record), id: nextId }; + return Object.assign({}, block as unknown as Record, { id: nextId }); }); if (!changed) { diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index 24d2ed62bb6..b26be343539 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -364,15 +364,18 @@ export function parseAvailableTags(raw: unknown): AvailableTag[] | undefined { (t): t is Record => typeof t === "object" && t !== null && typeof t.name === "string", ) - .map((t) => ({ - ...(t.id !== undefined && typeof t.id === "string" ? { id: t.id } : {}), - name: t.name as string, - ...(typeof t.moderated === "boolean" ? { moderated: t.moderated } : {}), - ...(t.emoji_id === null || typeof t.emoji_id === "string" ? { emoji_id: t.emoji_id } : {}), - ...(t.emoji_name === null || typeof t.emoji_name === "string" - ? { emoji_name: t.emoji_name } - : {}), - })); + .map((t) => + Object.assign( + {}, + t.id !== undefined && typeof t.id === `string` ? { id: t.id } : {}, + { name: t.name as string }, + typeof t.moderated === `boolean` ? { moderated: t.moderated } : {}, + t.emoji_id === null || typeof t.emoji_id === `string` ? { emoji_id: t.emoji_id } : {}, + t.emoji_name === null || typeof t.emoji_name === `string` + ? { emoji_name: t.emoji_name } + : {}, + ), + ); // Return undefined instead of empty array to avoid accidentally clearing all tags return result.length ? result : undefined; } diff --git a/src/auto-reply/reply/dispatch-acp-attachments.ts b/src/auto-reply/reply/dispatch-acp-attachments.ts index 8ec8f382673..564f43a3757 100644 --- a/src/auto-reply/reply/dispatch-acp-attachments.ts +++ b/src/auto-reply/reply/dispatch-acp-attachments.ts @@ -33,7 +33,9 @@ export async function resolveAcpAttachments(params: { const mediaAttachments = runtime .normalizeAttachments(params.ctx) .map((attachment) => - normalizeOptionalString(attachment.path) ? { ...attachment, url: undefined } : attachment, + normalizeOptionalString(attachment.path) + ? Object.assign({}, attachment, { url: undefined }) + : attachment, ); const cache = new runtime.MediaAttachmentCache(mediaAttachments, { localPathRoots: runtime.resolveMediaAttachmentLocalRoots({ diff --git a/src/auto-reply/reply/reply-payloads-dedupe.ts b/src/auto-reply/reply/reply-payloads-dedupe.ts index e77e798c7ab..7ad80888bc4 100644 --- a/src/auto-reply/reply/reply-payloads-dedupe.ts +++ b/src/auto-reply/reply/reply-payloads-dedupe.ts @@ -57,11 +57,10 @@ export function filterMessagingToolMediaDuplicates(params: { if (!stripSingle && (!mediaUrls || filteredUrls?.length === mediaUrls.length)) { return payload; } - return { - ...payload, + return Object.assign({}, payload, { mediaUrl: stripSingle ? undefined : mediaUrl, mediaUrls: filteredUrls?.length ? filteredUrls : undefined, - }; + }); }); } diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 75959235dc1..ea97f171d27 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -33,6 +33,7 @@ function resolveProviderChoiceOptions(params?: { scope: "text-inference", }).map((contribution) => Object.assign( + {}, { value: contribution.option.value as AuthChoice, label: contribution.option.label }, contribution.option.hint ? { hint: contribution.option.hint } : {}, contribution.option.assistantPriority !== undefined @@ -140,10 +141,9 @@ export function buildAuthChoiceGroups(params: { }); } const groups = Array.from(groupsById.values()) - .map((group) => ({ - ...group, - options: [...group.options].toSorted(compareAssistantOptions), - })) + .map((group) => + Object.assign({}, group, { options: [...group.options].toSorted(compareAssistantOptions) }), + ) .toSorted(compareGroupLabels); const skipOption = params.includeSkip diff --git a/src/commands/backup-shared.ts b/src/commands/backup-shared.ts index 242c669920d..da3f5911dab 100644 --- a/src/commands/backup-shared.ts +++ b/src/commands/backup-shared.ts @@ -160,13 +160,12 @@ export async function resolveBackupPlanFromPaths(params: { const candidates: BackupAssetCandidate[] = await Promise.all( rawCandidates.map(async (candidate) => { const exists = await pathExists(candidate.sourcePath); - return { - ...candidate, + return Object.assign({}, candidate, { exists, canonicalPath: exists ? await canonicalizeExistingPath(candidate.sourcePath) : path.resolve(candidate.sourcePath), - }; + }); }), ); diff --git a/src/commands/channel-setup/discovery.ts b/src/commands/channel-setup/discovery.ts index 13fec3c7301..c9324dc7efa 100644 --- a/src/commands/channel-setup/discovery.ts +++ b/src/commands/channel-setup/discovery.ts @@ -97,13 +97,11 @@ export function resolveChannelSetupEntries(params: { manifestInstalledIds.has(entry.id as ChannelChoice) && shouldShowChannelInSetup(entry.meta), ) - .map((entry) => ({ - ...entry, - meta: normalizeChannelMeta({ - id: entry.id as ChannelChoice, - meta: entry.meta, + .map((entry) => + Object.assign({}, entry, { + meta: normalizeChannelMeta({ id: entry.id as ChannelChoice, meta: entry.meta }), }), - })); + ); const installableCatalogEntries = installableCatalogEntriesSource .filter( (entry) => @@ -111,13 +109,11 @@ export function resolveChannelSetupEntries(params: { !manifestInstalledIds.has(entry.id as ChannelChoice) && shouldShowChannelInSetup(entry.meta), ) - .map((entry) => ({ - ...entry, - meta: normalizeChannelMeta({ - id: entry.id as ChannelChoice, - meta: entry.meta, + .map((entry) => + Object.assign({}, entry, { + meta: normalizeChannelMeta({ id: entry.id as ChannelChoice, meta: entry.meta }), }), - })); + ); const metaById = new Map(); for (const meta of listChatChannels()) { diff --git a/src/commands/doctor-bundled-plugin-runtime-deps.ts b/src/commands/doctor-bundled-plugin-runtime-deps.ts index f4b9a127c33..d99b4426fd5 100644 --- a/src/commands/doctor-bundled-plugin-runtime-deps.ts +++ b/src/commands/doctor-bundled-plugin-runtime-deps.ts @@ -185,13 +185,14 @@ export async function maybeRepairBundledPluginRuntimeDeps(params: { const { missing, conflicts } = scanBundledPluginRuntimeDeps({ packageRoot }); if (conflicts.length > 0) { - const conflictLines = conflicts.flatMap((conflict) => [ - `- ${conflict.name}: ${conflict.versions.join(", ")}`, - ...conflict.versions.flatMap((version) => { - const pluginIds = conflict.pluginIdsByVersion.get(version) ?? []; - return pluginIds.length > 0 ? [` - ${version}: ${pluginIds.join(", ")}`] : []; - }), - ]); + const conflictLines = conflicts.flatMap((conflict) => + [`- ${conflict.name}: ${conflict.versions.join(", ")}`].concat( + conflict.versions.flatMap((version) => { + const pluginIds = conflict.pluginIdsByVersion.get(version) ?? []; + return pluginIds.length > 0 ? [` - ${version}: ${pluginIds.join(", ")}`] : []; + }), + ), + ); note( [ "Bundled plugin runtime deps use conflicting versions.", diff --git a/src/commands/doctor/shared/legacy-config-core-normalizers.ts b/src/commands/doctor/shared/legacy-config-core-normalizers.ts index 304b32af5bf..d1ac8e96e74 100644 --- a/src/commands/doctor/shared/legacy-config-core-normalizers.ts +++ b/src/commands/doctor/shared/legacy-config-core-normalizers.ts @@ -531,10 +531,7 @@ export function normalizeLegacyMistralModelMaxTokens( changes.push( `Normalized models.providers.${providerId}.models[${index}].maxTokens (${maxTokens} → ${normalizedMaxTokens}) to avoid Mistral context-window rejects.`, ); - return { - ...model, - maxTokens: normalizedMaxTokens, - }; + return Object.assign({}, model, { maxTokens: normalizedMaxTokens }); }); if (!modelsChanged) { diff --git a/src/commands/sessions-cleanup.ts b/src/commands/sessions-cleanup.ts index 1e55904caa5..a3dad54a889 100644 --- a/src/commands/sessions-cleanup.ts +++ b/src/commands/sessions-cleanup.ts @@ -123,16 +123,17 @@ function buildActionRows(params: { cappedKeys: Set; budgetEvictedKeys: Set; }): SessionCleanupActionRow[] { - return toSessionDisplayRows(params.beforeStore).map((row) => ({ - ...row, - action: resolveSessionCleanupAction({ - key: row.key, - missingKeys: params.missingKeys, - staleKeys: params.staleKeys, - cappedKeys: params.cappedKeys, - budgetEvictedKeys: params.budgetEvictedKeys, + return toSessionDisplayRows(params.beforeStore).map((row) => + Object.assign({}, row, { + action: resolveSessionCleanupAction({ + key: row.key, + missingKeys: params.missingKeys, + staleKeys: params.staleKeys, + cappedKeys: params.cappedKeys, + budgetEvictedKeys: params.budgetEvictedKeys, + }), }), - })); + ); } function pruneMissingTranscriptEntries(params: { diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index 2ca8db3e96f..a42547d755f 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -146,11 +146,12 @@ export async function sessionsCommand( const rows = targets .flatMap((target) => { const store = loadSessionStore(target.storePath); - return toSessionDisplayRows(store).map((row) => ({ - ...row, - agentId: parseAgentSessionKey(row.key)?.agentId ?? target.agentId, - kind: classifySessionKey(row.key, store[row.key]), - })); + return toSessionDisplayRows(store).map((row) => + Object.assign({}, row, { + agentId: parseAgentSessionKey(row.key)?.agentId ?? target.agentId, + kind: classifySessionKey(row.key, store[row.key]), + }), + ); }) .filter((row) => { if (activeMinutes === undefined) { diff --git a/src/commands/status-all/report-sections.ts b/src/commands/status-all/report-sections.ts index 8c4c8114dd7..c632ae22dcd 100644 --- a/src/commands/status-all/report-sections.ts +++ b/src/commands/status-all/report-sections.ts @@ -51,7 +51,7 @@ export function buildStatusChannelsSection(params: { width: params.width, renderTable: params.renderTable, columns: statusChannelsTableColumns.map((column) => - column.key === "Detail" ? { ...column, minWidth: 28 } : column, + column.key === "Detail" ? Object.assign({}, column, { minWidth: 28 }) : column, ), rows: buildStatusChannelsTableRows({ rows: params.rows, diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index 1dd5eb9c30f..d1ceaa12514 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -264,11 +264,11 @@ async function createMockStatusScanResult(params: { includePluginCompatibility?: ...mocks.listGatewayAgentsBasic(), bootstrapPendingCount: 0, totalSessions: 1, - agents: mocks.listGatewayAgentsBasic().agents.map((agent: { id: string; name?: string }) => ({ - ...agent, - bootstrapPending: false, - activeSessions: 1, - })), + agents: mocks + .listGatewayAgentsBasic() + .agents.map((agent: { id: string; name?: string }) => + Object.assign({}, agent, { bootstrapPending: false, activeSessions: 1 }), + ), }; const sessions = createSessionStatusRows(); const channelIssues = gatewayReachable diff --git a/src/config/defaults.ts b/src/config/defaults.ts index e4908db5b5c..7d7936e37b7 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -211,15 +211,14 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { return model; } providerMutated = true; - return { - ...raw, + return Object.assign({}, raw, { reasoning, input, cost, contextWindow, maxTokens, api, - } as ModelDefinitionConfig; + }) as ModelDefinitionConfig; }); if (!providerMutated) { diff --git a/src/config/sessions/targets.ts b/src/config/sessions/targets.ts index 7f5e222070b..1c41f574a28 100644 --- a/src/config/sessions/targets.ts +++ b/src/config/sessions/targets.ts @@ -248,7 +248,9 @@ export async function resolveAllAgentSessionStoreTargets( agentsRoot, realAgentsRoot, }); - return validatedStorePath ? { ...target, storePath: validatedStorePath } : undefined; + return validatedStorePath + ? Object.assign({}, target, { storePath: validatedStorePath }) + : undefined; }), ) ).filter((target): target is SessionStoreTarget => Boolean(target)); diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index 25d098a37cb..2a4290aca56 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -514,10 +514,9 @@ export async function dispatchCronDelivery( return p; } const normalized = normalizeSilentReplyText(p.text); - return { - ...p, + return Object.assign({}, p, { text: normalized.strippedTrailingSilentToken ? undefined : normalized.text, - }; + }); }) .filter((p) => hasReplyPayloadContent(p, { trimText: true })); if (payloadsForDelivery.length === 0) { diff --git a/src/gateway/config-reload-plan.ts b/src/gateway/config-reload-plan.ts index 1ab4dac66a7..10353432768 100644 --- a/src/gateway/config-reload-plan.ts +++ b/src/gateway/config-reload-plan.ts @@ -118,41 +118,47 @@ function listReloadRules(): ReloadRule[] { return cachedReloadRules; } // Channel docking: plugins contribute hot reload/no-op prefixes here. - const channelReloadRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => [ - ...(plugin.reload?.configPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "hot", - actions: [`restart-channel:${plugin.id}` as ReloadAction], - }), - ), - ...(plugin.reload?.noopPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "none", - }), - ), - ]); - const pluginReloadRules: ReloadRule[] = (registry?.reloads ?? []).flatMap((entry) => [ - ...(entry.registration.restartPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "restart", - }), - ), - ...(entry.registration.hotPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "hot", - }), - ), - ...(entry.registration.noopPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "none", - }), - ), - ]); + const channelReloadRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => + (plugin.reload?.configPrefixes ?? []) + .map( + (prefix): ReloadRule => ({ + prefix, + kind: "hot", + actions: [`restart-channel:${plugin.id}` as ReloadAction], + }), + ) + .concat( + (plugin.reload?.noopPrefixes ?? []).map( + (prefix): ReloadRule => ({ + prefix, + kind: "none", + }), + ), + ), + ); + const pluginReloadRules: ReloadRule[] = (registry?.reloads ?? []).flatMap((entry) => + (entry.registration.restartPrefixes ?? []) + .map( + (prefix): ReloadRule => ({ + prefix, + kind: "restart", + }), + ) + .concat( + (entry.registration.hotPrefixes ?? []).map( + (prefix): ReloadRule => ({ + prefix, + kind: "hot", + }), + ), + (entry.registration.noopPrefixes ?? []).map( + (prefix): ReloadRule => ({ + prefix, + kind: "none", + }), + ), + ), + ); const rules = [ ...BASE_RELOAD_RULES, ...pluginReloadRules, diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 5ed3a6e338d..3170d840701 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -1213,10 +1213,9 @@ function buildLiveGatewayConfig(params: { ...params.cfg, agents: { ...params.cfg.agents, - list: (params.cfg.agents?.list ?? []).map((entry) => ({ - ...entry, - sandbox: { mode: "off" }, - })), + list: (params.cfg.agents?.list ?? []).map((entry) => + Object.assign({}, entry, { sandbox: { mode: `off` } }), + ), defaults: { ...params.cfg.agents?.defaults, // Live tests should avoid Docker sandboxing so tool probes can diff --git a/src/gateway/server-methods/agents-mutate.test.ts b/src/gateway/server-methods/agents-mutate.test.ts index c46da5a3f8e..8abedfe301e 100644 --- a/src/gateway/server-methods/agents-mutate.test.ts +++ b/src/gateway/server-methods/agents-mutate.test.ts @@ -221,7 +221,9 @@ type MockConfig = { }; function getAgentList(cfg: unknown): MockAgentEntry[] { - return ((cfg as MockConfig | undefined)?.agents?.list ?? []).map((entry) => ({ ...entry })); + return ((cfg as MockConfig | undefined)?.agents?.list ?? []).map((entry) => + Object.assign({}, entry), + ); } function mergeAgentConfig(cfg: unknown, opts: unknown): MockConfig { diff --git a/src/gateway/server-methods/tools-catalog.ts b/src/gateway/server-methods/tools-catalog.ts index 6d32dadc082..49d9d595c1b 100644 --- a/src/gateway/server-methods/tools-catalog.ts +++ b/src/gateway/server-methods/tools-catalog.ts @@ -120,10 +120,9 @@ function buildPluginGroups(params: { groups.set(groupId, existing); } return [...groups.values()] - .map((group) => ({ - ...group, - tools: group.tools.toSorted((a, b) => a.id.localeCompare(b.id)), - })) + .map((group) => + Object.assign({}, group, { tools: group.tools.toSorted((a, b) => a.id.localeCompare(b.id)) }), + ) .toSorted((a, b) => a.label.localeCompare(b.label)); } diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index 50e5cbbd4d2..c985b0bef13 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -293,7 +293,7 @@ async function discoverAllSessionsForUsage(params: { startMs: params.startMs, endMs: params.endMs, }); - return sessions.map((session) => ({ ...session, agentId: agent.id })); + return sessions.map((session) => Object.assign({}, session, { agentId: agent.id })); }), ); return results.flat().toSorted((a, b) => b.mtime - a.mtime); diff --git a/src/infra/bonjour-discovery.ts b/src/infra/bonjour-discovery.ts index 032e4d9a498..b97a9b53a22 100644 --- a/src/infra/bonjour-discovery.ts +++ b/src/infra/bonjour-discovery.ts @@ -574,10 +574,7 @@ async function discoverViaAvahi( args.push("-d", domain.replace(/\.$/, "")); } const browse = await run(args, { timeoutMs }); - return parseAvahiBrowse(browse.stdout).map((beacon) => ({ - ...beacon, - domain, - })); + return parseAvahiBrowse(browse.stdout).map((beacon) => Object.assign({}, beacon, { domain })); } export async function discoverGatewayBeacons( diff --git a/src/infra/exec-approvals.ts b/src/infra/exec-approvals.ts index f06601adb93..0eff54f557b 100644 --- a/src/infra/exec-approvals.ts +++ b/src/infra/exec-approvals.ts @@ -854,13 +854,12 @@ export function recordAllowlistUse( const nextAllowlist = allowlist.map((item) => item.pattern === entry.pattern && (item.argPattern ?? undefined) === (entry.argPattern ?? undefined) - ? { - ...item, + ? Object.assign({}, item, { id: item.id ?? crypto.randomUUID(), lastUsedAt: Date.now(), lastUsedCommand: command, lastResolvedPath: resolvedPath, - } + }) : item, ); agents[target] = { ...existing, allowlist: nextAllowlist }; diff --git a/src/markdown/ir.ts b/src/markdown/ir.ts index c626d32a02c..3a2454defbb 100644 --- a/src/markdown/ir.ts +++ b/src/markdown/ir.ts @@ -967,10 +967,11 @@ export function markdownToIRWithMeta( links: clampLinkSpans(state.links, finalLength), }, hasTables: state.hasTables, - tables: state.collectedTables.map((table) => ({ - ...table, - placeholderOffset: Math.min(table.placeholderOffset, finalLength), - })), + tables: state.collectedTables.map((table) => + Object.assign({}, table, { + placeholderOffset: Math.min(table.placeholderOffset, finalLength), + }), + ), }; } diff --git a/src/pairing/pairing-store.test.ts b/src/pairing/pairing-store.test.ts index bd3ab378c97..c7a3400e860 100644 --- a/src/pairing/pairing-store.test.ts +++ b/src/pairing/pairing-store.test.ts @@ -341,11 +341,9 @@ describe("pairing store", () => { requests?: Array>; }; const expiredAt = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(); - const requests = (parsed.requests ?? []).map((entry) => ({ - ...entry, - createdAt: expiredAt, - lastSeenAt: expiredAt, - })); + const requests = (parsed.requests ?? []).map((entry) => + Object.assign({}, entry, { createdAt: expiredAt, lastSeenAt: expiredAt }), + ); await writeJsonFixture(filePath, { version: 1, requests }); expect(await listChannelPairingRequests("demo-pairing-b")).toHaveLength(0); const next = await upsertChannelPairingRequest({ diff --git a/src/plugins/cli-backends.runtime.ts b/src/plugins/cli-backends.runtime.ts index c03a48ba39a..63d2eda60c7 100644 --- a/src/plugins/cli-backends.runtime.ts +++ b/src/plugins/cli-backends.runtime.ts @@ -28,8 +28,7 @@ function loadPluginRuntime(): PluginRuntimeModule | null { } export function resolveRuntimeCliBackends(): PluginCliBackendEntry[] { - return (loadPluginRuntime()?.getActivePluginRegistry()?.cliBackends ?? []).map((entry) => ({ - ...entry.backend, - pluginId: entry.pluginId, - })); + return (loadPluginRuntime()?.getActivePluginRegistry()?.cliBackends ?? []).map((entry) => + Object.assign({}, entry.backend, { pluginId: entry.pluginId }), + ); } diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index d46e6f590b8..bc3f9ac412d 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -2822,12 +2822,13 @@ module.exports = { id: "throws-after-import", register() {} };`, ] as const; runSinglePluginRegistryScenarios( - scenarios.map((scenario) => ({ - ...scenario, - body: `module.exports = { id: "${scenario.pluginId}", register(api) { + scenarios.map((scenario) => + Object.assign({}, scenario, { + body: `module.exports = { id: "${scenario.pluginId}", register(api) { api.registerHttpRoute(${scenario.routeOptions}); } };`, - })), + }), + ), ); }); diff --git a/src/plugins/provider-discovery.runtime.ts b/src/plugins/provider-discovery.runtime.ts index 38684066e64..972a2e1ff83 100644 --- a/src/plugins/provider-discovery.runtime.ts +++ b/src/plugins/provider-discovery.runtime.ts @@ -57,10 +57,9 @@ function resolveProviderDiscoveryEntryPlugins(params: { try { const moduleExport = loadSource(manifest.providerDiscoverySource!) as ProviderDiscoveryModule; providers.push( - ...normalizeDiscoveryModule(moduleExport).map((provider) => ({ - ...provider, - pluginId: manifest.id, - })), + ...normalizeDiscoveryModule(moduleExport).map((provider) => + Object.assign({}, provider, { pluginId: manifest.id }), + ), ); } catch { // Discovery fast path is optional. Fall back to the full plugin loader diff --git a/src/plugins/providers.runtime.ts b/src/plugins/providers.runtime.ts index fbbdbc4b794..e14658e5e0d 100644 --- a/src/plugins/providers.runtime.ts +++ b/src/plugins/providers.runtime.ts @@ -275,10 +275,9 @@ export function resolvePluginProviders(params: { return []; } const registry = loadOpenClawPlugins(loadState.loadOptions); - return registry.providers.map((entry) => ({ - ...entry.provider, - pluginId: entry.pluginId, - })); + return registry.providers.map((entry) => + Object.assign({}, entry.provider, { pluginId: entry.pluginId }), + ); } const loadState = resolveRuntimeProviderPluginLoadState(params, base); const registry = resolveRuntimePluginRegistry(loadState.loadOptions); @@ -286,8 +285,7 @@ export function resolvePluginProviders(params: { return []; } - return registry.providers.map((entry) => ({ - ...entry.provider, - pluginId: entry.pluginId, - })); + return registry.providers.map((entry) => + Object.assign({}, entry.provider, { pluginId: entry.pluginId }), + ); } diff --git a/src/plugins/runtime-live-state-guardrails.test.ts b/src/plugins/runtime-live-state-guardrails.test.ts index 9c75d8240b6..1fab5321316 100644 --- a/src/plugins/runtime-live-state-guardrails.test.ts +++ b/src/plugins/runtime-live-state-guardrails.test.ts @@ -19,29 +19,38 @@ const LIVE_RUNTIME_STATE_GUARDS: Record< }, }; -function guardAssertions() { - return Object.entries(LIVE_RUNTIME_STATE_GUARDS).flatMap(([relativePath, guard]) => [ - ...guard.required.map((needle) => ({ - relativePath, - type: "required" as const, - needle, - message: `${relativePath} missing ${needle}`, - })), - ...guard.forbidden.map((needle) => ({ - relativePath, - type: "forbidden" as const, - needle, - message: `${relativePath} must not contain ${needle}`, - })), - ]); -} - -function expectGuardState(params: { - source: string; +type GuardAssertion = { + relativePath: string; type: "required" | "forbidden"; needle: string; message: string; -}) { +}; + +function guardAssertions(): GuardAssertion[] { + return Object.entries(LIVE_RUNTIME_STATE_GUARDS).flatMap(([relativePath, guard]) => + guard.required + .map((needle) => ({ + relativePath, + type: "required", + needle, + message: `${relativePath} missing ${needle}`, + })) + .concat( + guard.forbidden.map((needle) => ({ + relativePath, + type: "forbidden", + needle, + message: `${relativePath} must not contain ${needle}`, + })), + ), + ); +} + +function expectGuardState( + params: { + source: string; + } & Pick, +) { if (params.type === "required") { expect(params.source, params.message).toContain(params.needle); return; diff --git a/src/plugins/status.ts b/src/plugins/status.ts index 0b7c4efe511..0c5aead25c3 100644 --- a/src/plugins/status.ts +++ b/src/plugins/status.ts @@ -208,11 +208,12 @@ function buildPluginReport( return { workspaceDir, ...registry, - plugins: registry.plugins.map((plugin) => ({ - ...plugin, - imported: plugin.format !== "bundle" && importedPluginIds.has(plugin.id), - version: resolveReportedPluginVersion(plugin, params?.env), - })), + plugins: registry.plugins.map((plugin) => + Object.assign({}, plugin, { + imported: plugin.format !== `bundle` && importedPluginIds.has(plugin.id), + version: resolveReportedPluginVersion(plugin, params?.env), + }), + ), }; } diff --git a/src/plugins/web-provider-public-artifacts.explicit.ts b/src/plugins/web-provider-public-artifacts.explicit.ts index c801b87b5d7..db8c87ee13f 100644 --- a/src/plugins/web-provider-public-artifacts.explicit.ts +++ b/src/plugins/web-provider-public-artifacts.explicit.ts @@ -126,7 +126,7 @@ export function loadBundledWebSearchProviderEntriesFromDir(params: { if (providers.length === 0) { return null; } - return providers.map((provider) => ({ ...provider, pluginId: params.pluginId })); + return providers.map((provider) => Object.assign({}, provider, { pluginId: params.pluginId })); } export function loadBundledRuntimeWebSearchProviderEntriesFromDir(params: { @@ -148,7 +148,7 @@ export function loadBundledRuntimeWebSearchProviderEntriesFromDir(params: { if (providers.length === 0) { return null; } - return providers.map((provider) => ({ ...provider, pluginId: params.pluginId })); + return providers.map((provider) => Object.assign({}, provider, { pluginId: params.pluginId })); } export function loadBundledWebFetchProviderEntriesFromDir(params: { @@ -170,7 +170,7 @@ export function loadBundledWebFetchProviderEntriesFromDir(params: { if (providers.length === 0) { return null; } - return providers.map((provider) => ({ ...provider, pluginId: params.pluginId })); + return providers.map((provider) => Object.assign({}, provider, { pluginId: params.pluginId })); } export function resolveBundledExplicitWebSearchProvidersFromPublicArtifacts(params: { diff --git a/src/plugins/web-provider-resolution-shared.ts b/src/plugins/web-provider-resolution-shared.ts index e570403c43b..a1ee8831f94 100644 --- a/src/plugins/web-provider-resolution-shared.ts +++ b/src/plugins/web-provider-resolution-shared.ts @@ -175,9 +175,6 @@ export function mapRegistryProviders(params: { return params.sortProviders( params.entries .filter((entry) => !onlyPluginIdSet || onlyPluginIdSet.has(entry.pluginId)) - .map((entry) => ({ - ...entry.provider, - pluginId: entry.pluginId, - })), + .map((entry) => Object.assign({}, entry.provider, { pluginId: entry.pluginId })), ); } diff --git a/src/process/supervisor/registry.ts b/src/process/supervisor/registry.ts index 02432af7c0b..c5a212d7eb2 100644 --- a/src/process/supervisor/registry.ts +++ b/src/process/supervisor/registry.ts @@ -75,7 +75,7 @@ export function createRunRegistry(options?: { maxExitedRecords?: number }): RunR }; const list: RunRegistry["list"] = () => { - return Array.from(records.values()).map((record) => ({ ...record })); + return Array.from(records.values()).map((record) => Object.assign({}, record)); }; const listByScope: RunRegistry["listByScope"] = (scopeKey) => { @@ -84,7 +84,7 @@ export function createRunRegistry(options?: { maxExitedRecords?: number }): RunR } return Array.from(records.values()) .filter((record) => record.scopeKey === scopeKey) - .map((record) => ({ ...record })); + .map((record) => Object.assign({}, record)); }; const updateState: RunRegistry["updateState"] = (runId, state, patch) => { diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index d643a755bad..770eb1601e2 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -1814,7 +1814,7 @@ export async function cancelTaskById(params: { export function listTaskRecords(): TaskRecord[] { ensureTaskRegistryReady(); return [...tasks.values()] - .map((task, insertionIndex) => ({ ...cloneTaskRecord(task), insertionIndex })) + .map((task, insertionIndex) => Object.assign({}, cloneTaskRecord(task), { insertionIndex })) .toSorted(compareTasksNewestFirst) .map(({ insertionIndex: _, ...task }) => task); } @@ -1851,7 +1851,7 @@ function listTasksFromIndex(index: Map>, key: string): TaskR return [...ids] .map((taskId, insertionIndex) => { const task = tasks.get(taskId); - return task ? { ...cloneTaskRecord(task), insertionIndex } : null; + return task ? Object.assign({}, cloneTaskRecord(task), { insertionIndex }) : null; }) .filter( ( diff --git a/test/helpers/agents/prompt-composition-scenarios.ts b/test/helpers/agents/prompt-composition-scenarios.ts index 6f6a3190283..cbcd4b95da1 100644 --- a/test/helpers/agents/prompt-composition-scenarios.ts +++ b/test/helpers/agents/prompt-composition-scenarios.ts @@ -155,7 +155,7 @@ function buildToolRichSystemPrompt(params: { "web_search", "x_search", "web_fetch", - ].map((name) => ({ ...createStubTool(name), description: `${name} tool` })); + ].map((name) => Object.assign({}, createStubTool(name), { description: `${name} tool` })); return buildEmbeddedSystemPrompt({ workspaceDir: params.workspaceDir, reasoningTagHint: false, diff --git a/ui/src/ui/chat/message-normalizer.ts b/ui/src/ui/chat/message-normalizer.ts index eb712ddfcea..697cd39377a 100644 --- a/ui/src/ui/chat/message-normalizer.ts +++ b/ui/src/ui/chat/message-normalizer.ts @@ -211,13 +211,7 @@ function expandTextContent(text: string): { const content = mergeAdjacentTextItems( parts.map((item) => { if (item.type === "attachment" && item.attachment.kind === "audio" && audioAsVoice) { - return { - ...item, - attachment: { - ...item.attachment, - isVoiceNote: true, - }, - }; + return Object.assign({}, item, { attachment: { ...item.attachment, isVoiceNote: true } }); } return item; }), diff --git a/ui/src/ui/chat/session-controls.ts b/ui/src/ui/chat/session-controls.ts index 8afda9f3aef..976ae65ff60 100644 --- a/ui/src/ui/chat/session-controls.ts +++ b/ui/src/ui/chat/session-controls.ts @@ -276,12 +276,7 @@ function patchSessionThinkingLevel( state.sessionsResult = { ...current, sessions: current.sessions.map((row) => - row.key === sessionKey - ? { - ...row, - thinkingLevel, - } - : row, + row.key === sessionKey ? Object.assign({}, row, { thinkingLevel }) : row, ), }; } diff --git a/ui/src/ui/views/config.ts b/ui/src/ui/views/config.ts index b1063d287e7..72d2981f126 100644 --- a/ui/src/ui/views/config.ts +++ b/ui/src/ui/views/config.ts @@ -724,12 +724,13 @@ export function renderConfig(props: ConfigProps) { const schemaProps = analysis.schema?.properties ?? {}; const VIRTUAL_SECTIONS = new Set(["__appearance__"]); - const visibleCategories = SECTION_CATEGORIES.map((cat) => ({ - ...cat, - sections: cat.sections.filter( - (s) => (includeVirtualSections && VIRTUAL_SECTIONS.has(s.key)) || s.key in schemaProps, - ), - })).filter((cat) => cat.sections.length > 0); + const visibleCategories = SECTION_CATEGORIES.map((cat) => + Object.assign({}, cat, { + sections: cat.sections.filter( + (s) => (includeVirtualSections && VIRTUAL_SECTIONS.has(s.key)) || s.key in schemaProps, + ), + }), + ).filter((cat) => cat.sections.length > 0); // Catch any schema keys not in our categories const extraSections = Object.keys(schemaProps) diff --git a/ui/src/ui/views/dreaming.ts b/ui/src/ui/views/dreaming.ts index 8bd1f03e979..d654674c847 100644 --- a/ui/src/ui/views/dreaming.ts +++ b/ui/src/ui/views/dreaming.ts @@ -80,10 +80,7 @@ function formatDiaryChipLabel(date: string): string { function buildDiaryNavigation(entries: DiaryEntry[]): DiaryEntryNav[] { const reversed = [...entries].toReversed(); - return reversed.map((entry, page) => ({ - ...entry, - page, - })); + return reversed.map((entry, page) => Object.assign({}, entry, { page })); } type DreamingPhaseInfo = {