diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json index 71c7c61dbc1a..e8f810baf26d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json @@ -1 +1 @@ -{"contentHash":"f0501615d366cab1ceffabfcbb580b9a9f1008bbd4e24a46e1fc10d73e9cd2a3","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"14115e942e60b33851a102cf0091ec7c6e29e015ad800471c095346de6708e0b","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json index e3fc3b4c1b86..8eaa0707bc1c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json @@ -1 +1 @@ -{"contentHash":"ae5e7178a886a346e7a7d88e80344b84c84679e156d08eb863c8a5e2bbffe03c","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"944bceb3db8adc5516b7f80933a3a3dd483cfeb162b102f78e7f31e40286ac54","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json index 5e5890b10ebb..47362a80d35d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-runtime.json @@ -1 +1 @@ -{"contentHash":"2f175fa56f7836b6dc666ef556b964ecfa83f6f81f683340907c7456c8f301ea","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} +{"contentHash":"e0ee84e5e66b6c524a69e4d5791ee3d962859937e572b452db54b926f57219a3","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-core.json b/docs/.generated/plugin-sdk-api-baseline/channel-core.json index 4bbb9985508a..aa8fe65c5203 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-core.json @@ -1 +1 @@ -{"contentHash":"9360377bb569d5f8f53151152e4b1e27e860ebc38d176ee3d71cbf3461c79ca8","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"0841384461391ee65d656cdcce3e956417be3a861f6a7dcde8b7592e951c8381","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json index d1e6b9de9651..a9e2f879fe4a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json @@ -1 +1 @@ -{"contentHash":"5e756ed6e46b362058d9ca438cf858c463bb01fd55269b0b019341fe0075bcda","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"87519437ca1f4ed669866f5efa28ab61836ead8c9251f2d401d755b43debdaa4","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json index 108063a33236..edcdd7b13ba0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-inbound.json @@ -1 +1 @@ -{"contentHash":"3523cddf2dad5146687de9564879903e20224b140f4a9756ba57e76034c6490a","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} +{"contentHash":"2598df91c020fd173ef4c0b171718a4fd90e90599536ff71ca3f4d1b3adf97fe","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-message.json b/docs/.generated/plugin-sdk-api-baseline/channel-message.json index 65c7d2254a98..26bd09664577 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-message.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-message.json @@ -1 +1 @@ -{"contentHash":"5091fb6b03b3e1b3cb6b51323cb7bd991f8022d1aac479b4a67982993dec79cf","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"b6454c9a2e9fd243ec392fb51d1542fc176f350bc682213813f0ea7fc0166af6","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json index 6f371bc230e3..284d2110e135 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json @@ -1 +1 @@ -{"contentHash":"33c41deadb29159ecaf9e40c0053d43a81531ca48145f11217f811e4232c5cf2","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"6236a390dab6017b4eef42591729660b3e114b43ae34798d41482670a3ee0613","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json index 4e8c702958aa..1d4d94c9e702 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json @@ -1 +1 @@ -{"contentHash":"c6e8524442d54875e1ce5ec211394472c2e2734fbfbebe320b069346374b922d","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"b2bbf7a51a586c54b554475998b6a259be1e7fc7db9604c2e1205f56ff35f704","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} diff --git a/docs/.generated/plugin-sdk-api-baseline/core.json b/docs/.generated/plugin-sdk-api-baseline/core.json index 9d2cb97284e4..43593b38c137 100644 --- a/docs/.generated/plugin-sdk-api-baseline/core.json +++ b/docs/.generated/plugin-sdk-api-baseline/core.json @@ -1 +1 @@ -{"contentHash":"1edb0e67f51c958fff1d3ace6e47debb831555720208ebfb732cf65de2bb333a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"c5239579070b39b2d63a3cf6b9faa66152e5d71f64d73716a84c4cc510054d2d","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/discord.json b/docs/.generated/plugin-sdk-api-baseline/discord.json index 4f6f6c131724..bdff73f27fa2 100644 --- a/docs/.generated/plugin-sdk-api-baseline/discord.json +++ b/docs/.generated/plugin-sdk-api-baseline/discord.json @@ -1 +1 @@ -{"contentHash":"243be23ce58934cc502148cde9b747c4e8d26d176526e99dda383f16f20fc124","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"fe90342ef9fd6a4173e1feed09852380f256fafafb7836bc2c1df7400a8bcf3c","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} diff --git a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json index 9a9a3c0975ba..bb4ed7388124 100644 --- a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json +++ b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json @@ -1 +1 @@ -{"contentHash":"a27b168ddc12affa72e1d646d36ea8ef65f18f2de7a7397f0f9e816659e54c0e","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"23c052386a61874b2f9d19f872a90a9e6863971e812e573d8271b89d92181e30","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} diff --git a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json index 96516c4eccbe..739db11b8784 100644 --- a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json @@ -1 +1 @@ -{"contentHash":"336c432da880945a8dccc3f6e0c96cc062b4afdd96b73b35382ffedc537762be","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"f31959d35168e31228f318289d78117945dcd79e8ff5c57473636025c7f8d881","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json index a7e51e3b973c..a223fb67cf34 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json @@ -1 +1 @@ -{"contentHash":"66b344dc4d56bdb7d3c1ddccb64899a013f98e1dda7f734e6b75de49c41ba0fc","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"76391f09ded09871b1a5ac2fbbf2bd9901f55c529cdd97c00a0111aeb319ba33","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json index 241749c84cc8..dd0f36b2aae7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json @@ -1 +1 @@ -{"contentHash":"e7788890d54d5784f4bce552c7143a0083cd1cdd46f126b98d66ad8357327e51","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"d05730f5b90c0164e719e97694582d27f15ec4442c8d764044b0960a6e575c93","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json index 38cba3b93660..3035a6691b16 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json @@ -1 +1 @@ -{"contentHash":"4b0295b65533f821a02e3399298424b35c2c75082e4d53d57ea88ff2f84d74ad","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"ef4d5d011283052e9b24d4bf57fba52e38b48aef38e1be322273eaf290f3eb85","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json index ac70d1ff0f26..56480a251689 100644 --- a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json +++ b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json @@ -1 +1 @@ -{"contentHash":"c489a3d36e88b55b29c9019df733662acb67ec95e2fd0a99d2b208ac123801f5","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"a72d078274268d0e5b2c1a18a4787980c55e61479d3efb7a11dfc7303ac28d28","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json index 4320c98ae04a..0ee3dd8ff3b0 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json @@ -1 +1 @@ -{"contentHash":"73b1700f912dfb34554f7a9e72bca0ff9e1ea08360f32c981519488a25023f2c","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"9d8a7eec9dba85091301963980960ddb8993aaae52972fb245f25c5bd608b150","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} diff --git a/scripts/check-session-accessor-boundary.mts b/scripts/check-session-accessor-boundary.mts index 326f26d8de18..c504cbe47305 100644 --- a/scripts/check-session-accessor-boundary.mts +++ b/scripts/check-session-accessor-boundary.mts @@ -67,7 +67,10 @@ const sessionStoreRuntimeFileBackedCompatNames = new Set([ "updateSessionStore", ]); const embeddedAgentSessionFileRuntimeNames = new Set(["resolveSessionFilePath"]); -const materializingSessionEntryAccessorNames = new Set(["listSessionEntries", "loadSessionEntry"]); +const materializingSessionEntryAccessorNames = new Set([ + "listSessionEntriesCore", + "loadSessionEntry", +]); // Shipped beta.5 official plugins import these deprecated helpers during // doctor migrations. Remove this ratchet with the compatibility bridge once diff --git a/scripts/lib/export-name-collision-baseline.json b/scripts/lib/export-name-collision-baseline.json index a76ae4655d68..820f7dbe5e05 100644 --- a/scripts/lib/export-name-collision-baseline.json +++ b/scripts/lib/export-name-collision-baseline.json @@ -1,400 +1,4 @@ [ - { - "name": "cleanupSessionLifecycleArtifacts", - "files": [ - "src/config/sessions/session-accessor.sqlite-lifecycle.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "createPluginLoaderLogger", - "files": [ - "src/plugins/loader-shared.ts", - "src/plugins/logger.ts" - ] - }, - { - "name": "listMemoryEmbeddingProviders", - "files": [ - "src/plugins/memory-embedding-provider-runtime.ts", - "src/plugins/memory-embedding-providers.ts" - ], - "sdk": true - }, - { - "name": "listSessionEntries", - "files": [ - "src/config/sessions/session-accessor.entry.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "loadBundledPluginPublicSurface", - "files": [ - "src/plugin-sdk/test-helpers/public-surface-loader.ts", - "src/test-utils/bundled-plugin-public-surface.ts" - ], - "sdk": true - }, - { - "name": "loadBundledPluginPublicSurfaceModuleSync", - "files": [ - "src/plugin-sdk/facade-loader.ts", - "src/plugin-sdk/facade-runtime.ts" - ], - "sdk": true - }, - { - "name": "loadCodexBundleMcpThreadConfig", - "files": [ - "src/agents/codex-mcp-config.ts", - "src/plugin-sdk/agent-harness-runtime.ts" - ], - "sdk": true - }, - { - "name": "loadCombinedSessionStoreForGateway", - "files": [ - "src/config/sessions/combined-store-gateway.ts", - "src/plugin-sdk/session-transcript-hit.ts" - ], - "sdk": true - }, - { - "name": "loadConfig", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/config/io.runtime.ts" - ], - "sdk": true - }, - { - "name": "loadJsonFile", - "files": [ - "src/infra/json-file.ts", - "src/plugin-sdk/json-store.ts" - ], - "sdk": true - }, - { - "name": "loadPluginManifestRegistry", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/manifest-registry.ts" - ], - "sdk": true - }, - { - "name": "loadQaRuntimeModule", - "files": [ - "src/plugin-sdk/qa-runner-runtime.ts", - "src/plugin-sdk/qa-runtime.ts" - ], - "sdk": true - }, - { - "name": "loadSessionStore", - "files": [ - "src/library.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "loadWebMedia", - "files": [ - "src/media/web-media.ts", - "src/plugins/runtime/runtime-web-channel-plugin.ts" - ], - "sdk": true - }, - { - "name": "loadWebMediaRaw", - "files": [ - "src/media/web-media.ts", - "src/plugins/runtime/runtime-web-channel-plugin.ts" - ], - "sdk": true - }, - { - "name": "materializeRequesterScopedMcpToolsForHarnessRun", - "files": [ - "src/agents/agent-bundle-mcp-harness.ts", - "src/plugin-sdk/agent-harness-runtime.ts" - ], - "sdk": true - }, - { - "name": "materializeStaticMcpToolsForScheduledHarnessRun", - "files": [ - "src/agents/agent-bundle-mcp-harness.ts", - "src/plugin-sdk/codex-mcp-projection.ts" - ], - "sdk": true - }, - { - "name": "maybeApplyTtsToPayload", - "files": [ - "src/tts/tts-payload.ts", - "src/tts/tts.ts" - ], - "sdk": true - }, - { - "name": "mergeSessionEntry", - "files": [ - "src/config/sessions/types.ts", - "src/infra/state-migrations.session-store.ts" - ] - }, - { - "name": "normalizeCronRunDiagnostics", - "files": [ - "src/cron/run-diagnostics-normalize.ts", - "src/cron/run-diagnostics.ts" - ] - }, - { - "name": "normalizeStringRecord", - "files": [ - "src/agents/bundle-mcp-adapter.ts", - "src/plugins/manifest-capability-normalizers.ts" - ] - }, - { - "name": "normalizeToolName", - "files": [ - "src/agents/tool-display-common.ts", - "src/agents/tool-policy-shared.ts", - "src/cron/run-diagnostics-normalize.ts" - ] - }, - { - "name": "optimizeImageToJpeg", - "files": [ - "src/media/web-media.ts", - "src/plugins/runtime/runtime-web-channel-plugin.ts" - ], - "sdk": true - }, - { - "name": "pad", - "files": [ - "src/agents/bash-tools.shared.ts", - "src/commands/models/list.format.ts" - ] - }, - { - "name": "parseClawHubPluginSpec", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/infra/clawhub-spec.ts" - ] - }, - { - "name": "parseCsvFilter", - "files": [ - "src/image-generation/live-test-helpers.ts", - "src/video-generation/live-test-helpers.ts" - ] - }, - { - "name": "parseFrontmatter", - "files": [ - "src/agents/utils/frontmatter.ts", - "src/hooks/frontmatter.ts", - "src/skills/loading/frontmatter.ts" - ] - }, - { - "name": "parseInlineDirectives", - "files": [ - "src/auto-reply/reply/directive-handling.parse.ts", - "src/utils/directive-tags.ts" - ], - "sdk": true - }, - { - "name": "parseModelRef", - "files": [ - "src/agents/model-selection-normalize.ts", - "src/commands/doctor/shared/codex-route-model-ref.ts" - ], - "sdk": true - }, - { - "name": "parseQaTarget", - "files": [ - "src/plugin-sdk/qa-channel-protocol.ts", - "src/plugin-sdk/qa-channel.ts" - ], - "sdk": true - }, - { - "name": "parseTimeoutMs", - "files": [ - "src/cli/parse-timeout.ts", - "src/commands/gateway-status/helpers.ts" - ] - }, - { - "name": "patchSessionEntry", - "files": [ - "src/config/sessions/session-accessor.sqlite-entry.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "pathExists", - "files": [ - "src/agents/worktrees/git.ts", - "src/utils.ts" - ], - "sdk": true - }, - { - "name": "pathsEqual", - "files": [ - "src/commands/doctor/shared/missing-configured-plugin-install.records.ts", - "src/plugins/update-config.ts" - ] - }, - { - "name": "peekSessionMcpRuntime", - "files": [ - "src/agents/agent-bundle-mcp-manager-api.ts", - "src/gateway/server-methods/__mocks__/tools-effective.runtime.ts" - ] - }, - { - "name": "planPluginUninstall", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/uninstall.ts" - ] - }, - { - "name": "readAmbientTranscriptWatermark", - "files": [ - "src/config/sessions/ambient-transcript-watermark.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "readAskUserQuestionId", - "files": [ - "src/auto-reply/reply/dispatch-from-config.payloads.ts", - "src/infra/question-reaction-runtime.ts" - ] - }, - { - "name": "readConfigFileSnapshot", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/config/io.runtime.ts" - ], - "sdk": true - }, - { - "name": "readConfigFileSnapshotForWrite", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/config/io.runtime.ts" - ], - "sdk": true - }, - { - "name": "readPersistedInstalledPluginIndex", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/installed-plugin-index-store.ts" - ] - }, - { - "name": "readSessionEntry", - "files": [ - "src/agents/subagents/announce/subagent-announce.runtime.ts", - "src/cron/isolated-agent.turn-test-helpers.ts" - ] - }, - { - "name": "readSessionTranscriptVisibleMessageDelta", - "files": [ - "src/config/sessions/session-accessor.sqlite-active-events.ts", - "src/plugin-sdk/session-transcript-runtime.ts" - ], - "sdk": true - }, - { - "name": "readSessionUpdatedAt", - "files": [ - "src/config/sessions/session-accessor.sqlite-entry.ts", - "src/plugin-sdk/session-store-runtime.ts" - ], - "sdk": true - }, - { - "name": "recordHookInstall", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/hooks/installs.ts" - ] - }, - { - "name": "recordPluginInstall", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/installs.ts" - ] - }, - { - "name": "redactSecrets", - "files": [ - "src/commands/status-all/format.ts", - "src/logging/redact.ts" - ], - "sdk": true - }, - { - "name": "refreshPluginRegistry", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/plugins/plugin-registry-snapshot.ts" - ] - }, - { - "name": "replaceConfigFile", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/config/mutate.ts" - ], - "sdk": true - }, - { - "name": "reportClawHubPluginInstallTelemetry", - "files": [ - "src/cli/plugins-cli-test-helpers.ts", - "src/infra/clawhub-packages.ts" - ] - }, - { - "name": "requestSafeGatewayRestart", - "files": [ - "src/cli/daemon-cli/lifecycle-safe-restart.ts", - "src/infra/restart-coordinator.ts" - ] - }, - { - "name": "requireRecord", - "files": [ - "src/acp/translator.bridge-test-helpers.ts", - "src/gateway/test-helpers.assertions.ts" - ] - }, { "name": "resolveAdvertisedLanHost", "files": [ diff --git a/scripts/lib/session-accessor-debt-baseline.json b/scripts/lib/session-accessor-debt-baseline.json index 61c67dab4ee8..12c7fbd52916 100644 --- a/scripts/lib/session-accessor-debt-baseline.json +++ b/scripts/lib/session-accessor-debt-baseline.json @@ -2,7 +2,6 @@ "embeddedAgentSessionTarget": {}, "memoryHostSessionCorpus": {}, "sessionAccessorRead": { - "src/agents/subagents/announce/subagent-announce-output.ts": 3, "src/agents/subagents/announce/subagent-announce.test-support.ts": 2, "src/agents/tools/transcripts-tool.ts": 2, "src/auto-reply/reply/commands-session-store.ts": 2, diff --git a/scripts/lib/wrapper-shadowing-baseline.json b/scripts/lib/wrapper-shadowing-baseline.json index df4926fdbd70..56c1b9220b4b 100644 --- a/scripts/lib/wrapper-shadowing-baseline.json +++ b/scripts/lib/wrapper-shadowing-baseline.json @@ -1,39 +1,9 @@ [ - { - "name": "loadBundledPluginPublicSurfaceModuleSync", - "wrapped": "src/plugin-sdk/facade-loader.ts", - "wrapper": "src/plugin-sdk/facade-runtime.ts" - }, - { - "name": "loadCombinedSessionStoreForGateway", - "wrapped": "src/config/sessions/combined-store-gateway.ts", - "wrapper": "src/plugin-sdk/session-transcript-hit.ts" - }, { "name": "loadPluginManifestRegistry", "wrapped": "src/plugins/manifest-registry.ts", "wrapper": "src/config/doc-baseline.runtime.ts" }, - { - "name": "loadWebMediaRaw", - "wrapped": "src/media/web-media.ts", - "wrapper": "src/plugins/runtime/runtime-web-channel-plugin.ts" - }, - { - "name": "loadWebMedia", - "wrapped": "src/media/web-media.ts", - "wrapper": "src/plugins/runtime/runtime-web-channel-plugin.ts" - }, - { - "name": "maybeApplyTtsToPayload", - "wrapped": "src/tts/tts-payload.ts", - "wrapper": "src/tts/tts.ts" - }, - { - "name": "normalizeCronRunDiagnostics", - "wrapped": "src/cron/run-diagnostics-normalize.ts", - "wrapper": "src/cron/run-diagnostics.ts" - }, { "name": "normalizeOutboundReplyPayload", "wrapped": "src/infra/outbound/reply-payload-normalize.ts", @@ -44,32 +14,16 @@ "wrapped": "src/plugins/config-normalization-shared.ts", "wrapper": "src/plugins/config-policy.ts" }, - { - "name": "optimizeImageToJpeg", - "wrapped": "src/media/web-media.ts", - "wrapper": "src/plugins/runtime/runtime-web-channel-plugin.ts" - }, { "name": "pruneStaleCommandPolls", "wrapped": "src/agents/command-poll-backoff.ts", "wrapper": "src/agents/command-poll-backoff.runtime.ts" }, - { - "name": "readAmbientTranscriptWatermark", - "wrapped": "src/config/sessions/ambient-transcript-watermark.ts", - "wrapper": "src/plugin-sdk/session-store-runtime.ts" - }, { "name": "readBooleanParam", "wrapped": "src/plugin-sdk/boolean-param.ts", "wrapper": "src/infra/outbound/message-action-params.ts" }, - { - "name": "readSessionTranscriptVisibleMessageDelta", - "wrapped": "src/config/sessions/session-accessor.sqlite-active-events.ts", - "wrapper": "src/plugin-sdk/session-transcript-runtime.ts", - "via": "src/config/sessions/session-accessor.ts" - }, { "name": "resolveAllowedModelRef", "wrapped": "src/agents/model-selection-resolve.ts", diff --git a/src/acp/approval-classifier.ts b/src/acp/approval-classifier.ts index 44357a24bc6c..4ab0f6c15272 100644 --- a/src/acp/approval-classifier.ts +++ b/src/acp/approval-classifier.ts @@ -63,7 +63,7 @@ function readFirstStringValue( return readTrimmedStringAlias(source, keys); } -function normalizeToolName(value: string): string | undefined { +function normalizeToolPolicyName(value: string): string | undefined { const normalized = normalizeLowercaseStringOrEmpty(value); if (!normalized || normalized.length > 128) { return undefined; @@ -76,7 +76,7 @@ function parseToolNameFromTitle(title: string | undefined | null): string | unde return undefined; } const head = normalizeOptionalString(title.split(":", 1)[0]); - return head ? normalizeToolName(head) : undefined; + return head ? normalizeToolPolicyName(head) : undefined; } function resolveToolNameForPermission(params: { @@ -93,8 +93,8 @@ function resolveToolNameForPermission(params: { const fromMeta = readFirstStringValue(toolMeta, ["toolName", "tool_name", "name"]); const fromRawInput = readFirstStringValue(rawInput, ["tool", "toolName", "tool_name", "name"]); const fromTitle = parseToolNameFromTitle(toolCall?.title); - const metaName = fromMeta ? normalizeToolName(fromMeta) : undefined; - const rawInputName = fromRawInput ? normalizeToolName(fromRawInput) : undefined; + const metaName = fromMeta ? normalizeToolPolicyName(fromMeta) : undefined; + const rawInputName = fromRawInput ? normalizeToolPolicyName(fromRawInput) : undefined; const titleName = fromTitle; if ((fromMeta && !metaName) || (fromRawInput && !rawInputName)) { return undefined; diff --git a/src/acp/translator.bridge-test-helpers.ts b/src/acp/translator.bridge-test-helpers.ts index 1d8ac6fe3d4f..08a6ccd34dff 100644 --- a/src/acp/translator.bridge-test-helpers.ts +++ b/src/acp/translator.bridge-test-helpers.ts @@ -133,7 +133,7 @@ export async function expectOversizedPromptRejected(params: { sessionId: string; export type MockCallSource = { mock: { calls: Array> } }; -export function requireRecord(value: unknown, label: string): Record { +export function requireAcpObject(value: unknown, label: string): Record { if (!value || typeof value !== "object") { throw new Error(`expected ${label}`); } @@ -157,10 +157,10 @@ export function expectConfigOption(options: unknown, id: string, fields: Record< export function sessionUpdatePayloads(source: MockCallSource, updateType?: string) { const payloads = source.mock.calls.map((call, index) => { - const envelope = requireRecord(call[0], `session update envelope ${index}`); + const envelope = requireAcpObject(call[0], `session update envelope ${index}`); return { sessionId: envelope.sessionId, - update: requireRecord(envelope.update, `session update ${index}`), + update: requireAcpObject(envelope.update, `session update ${index}`), }; }); if (!updateType) { diff --git a/src/acp/translator.session-config.test.ts b/src/acp/translator.session-config.test.ts index 0092b4edc3eb..f51d7ee831f2 100644 --- a/src/acp/translator.session-config.test.ts +++ b/src/acp/translator.session-config.test.ts @@ -7,7 +7,7 @@ import { createSetSessionModeRequest, createSetSessionConfigOptionRequest, type MockCallSource, - requireRecord, + requireAcpObject, expectConfigOption, expectSessionUpdate, } from "./translator.bridge-test-helpers.js"; @@ -352,7 +352,7 @@ describe("acp setSessionConfigOption bridge behavior", () => { (request as unknown as MockCallSource).mock.calls.some( ([method, params]) => method === "sessions.patch" && - requireRecord(params, "sessions.patch params").key === "bool-config-session", + requireAcpObject(params, "sessions.patch params").key === "bool-config-session", ), ).toBe(false); @@ -383,7 +383,7 @@ describe("acp setSessionConfigOption bridge behavior", () => { }; } if (method === "sessions.patch") { - expect(requireRecord(_params, "sessions.patch params")).toMatchObject({ + expect(requireAcpObject(_params, "sessions.patch params")).toMatchObject({ key: "usage-inherit-session", responseUsage: null, }); @@ -434,7 +434,7 @@ describe("acp setSessionConfigOption bridge behavior", () => { }; } if (method === "sessions.patch") { - expect(requireRecord(_params, "sessions.patch params")).toMatchObject({ + expect(requireAcpObject(_params, "sessions.patch params")).toMatchObject({ key: "usage-off-session", responseUsage: "off", }); diff --git a/src/agents/agent-bundle-mcp-harness.test.ts b/src/agents/agent-bundle-mcp-harness.test.ts index a264688049bc..b28bb0ae1b19 100644 --- a/src/agents/agent-bundle-mcp-harness.test.ts +++ b/src/agents/agent-bundle-mcp-harness.test.ts @@ -74,8 +74,8 @@ vi.mock("./agent-bundle-mcp-runtime.js", async (importOriginal) => { }); import { - materializeRequesterScopedMcpToolsForHarnessRun, - materializeStaticMcpToolsForScheduledHarnessRun, + materializeRequesterScopedMcpToolsForHarnessRunCore, + materializeStaticMcpToolsForScheduledHarnessRunCore, } from "./agent-bundle-mcp-harness.js"; function makeRuntime(params: { sessionId: string; requesterSenderId: string }): SessionMcpRuntime { @@ -152,14 +152,14 @@ beforeEach(() => { mocks.getAdvertisedScopedMcpCatalog.mockClear(); }); -describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { +describe("materializeStaticMcpToolsForScheduledHarnessRunCore", () => { it("materializes static tools without carrying requester identity and applies the stored cap", async () => { const runtime = makeRuntime({ sessionId: "scheduled", requesterSenderId: "unused" }); delete runtime.requesterScope; runtime.peekCatalog()!.servers["user-mail"]!.codexApprovalMode = "approve"; mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled", workspaceDir: "/workspace", toolsAllow: ["user-mail__inbox"], @@ -181,7 +181,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { delete runtime.requesterScope; mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-denied", workspaceDir: "/workspace", toolsAllow: ["read"], @@ -228,7 +228,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { const callTool = vi.spyOn(runtime, "callTool"); mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-app", workspaceDir: "/workspace", toolsAllow: ["user-mail__show", "user-mail__app-only"], @@ -299,7 +299,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { const callTool = vi.spyOn(runtime, "callTool"); mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-app-approval", workspaceDir: "/workspace", toolsAllow: ["*"], @@ -363,7 +363,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { }); mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-app-yolo", workspaceDir: "/workspace", toolsAllow: ["*"], @@ -386,7 +386,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { runtime.getCatalog = async () => emptyCatalog; mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-empty", workspaceDir: "/workspace", toolsAllow: ["*"], @@ -417,7 +417,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { runtime.getCatalog = async () => failedCatalog; mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-diagnostic", workspaceDir: "/workspace", toolsAllow: ["*"], @@ -436,7 +436,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); const callTool = vi.spyOn(runtime, "callTool"); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-prompt", workspaceDir: "/workspace", toolsAllow: ["user-mail__inbox"], @@ -456,7 +456,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); const callTool = vi.spyOn(runtime, "callTool"); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-yolo", workspaceDir: "/workspace", toolsAllow: ["user-mail__inbox"], @@ -482,7 +482,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); const callTool = vi.spyOn(runtime, "callTool"); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: `scheduled-${mode}`, workspaceDir: "/workspace", toolsAllow: ["user-mail__inbox"], @@ -499,7 +499,7 @@ describe("materializeStaticMcpToolsForScheduledHarnessRun", () => { mocks.getOrCreateSessionMcpRuntime.mockResolvedValue(runtime); const callTool = vi.spyOn(runtime, "callTool"); - const result = await materializeStaticMcpToolsForScheduledHarnessRun({ + const result = await materializeStaticMcpToolsForScheduledHarnessRunCore({ sessionId: "scheduled-unknown", workspaceDir: "/workspace", toolsAllow: ["user-mail__inbox"], @@ -518,10 +518,10 @@ afterEach(() => { mcpUiResourceTesting.clearViewStore(); }); -describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { +describe("materializeRequesterScopedMcpToolsForHarnessRunCore", () => { it("returns undefined before any requester resolves", async () => { mocks.setResolveImpl(async () => undefined); - const result = await materializeRequesterScopedMcpToolsForHarnessRun({ + const result = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-empty", workspaceDir: "/workspace", requesterSenderId: "guest", @@ -538,7 +538,7 @@ describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { }); await expect( - materializeRequesterScopedMcpToolsForHarnessRun({ + materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-cleanup", workspaceDir: "/workspace", requesterSenderId: "authed", @@ -559,7 +559,7 @@ describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { }); }); - const authed = await materializeRequesterScopedMcpToolsForHarnessRun({ + const authed = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-stable", workspaceDir: "/workspace", requesterSenderId: "authed", @@ -575,7 +575,7 @@ describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { }); await authed!.dispose(); - const guest = await materializeRequesterScopedMcpToolsForHarnessRun({ + const guest = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-stable", workspaceDir: "/workspace", requesterSenderId: "guest", @@ -602,7 +602,7 @@ describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { }), ); - const result = await materializeRequesterScopedMcpToolsForHarnessRun({ + const result = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-policy", workspaceDir: "/workspace", requesterSenderId: "authed", @@ -630,12 +630,12 @@ describe("materializeRequesterScopedMcpToolsForHarnessRun", () => { }); }); - const alice = await materializeRequesterScopedMcpToolsForHarnessRun({ + const alice = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-route", workspaceDir: "/workspace", requesterSenderId: "alice", }); - const bob = await materializeRequesterScopedMcpToolsForHarnessRun({ + const bob = await materializeRequesterScopedMcpToolsForHarnessRunCore({ sessionId: "session-route", workspaceDir: "/workspace", requesterSenderId: "bob", diff --git a/src/agents/agent-bundle-mcp-harness.ts b/src/agents/agent-bundle-mcp-harness.ts index cc727c025bf1..a2ff2b9f25ee 100644 --- a/src/agents/agent-bundle-mcp-harness.ts +++ b/src/agents/agent-bundle-mcp-harness.ts @@ -160,7 +160,7 @@ function buildCatalogTools( * Materialize only static configured MCP for an authenticated scheduled turn. * No requester identity is accepted here, so requester resolvers stay unreachable. */ -export async function materializeStaticMcpToolsForScheduledHarnessRun( +export async function materializeStaticMcpToolsForScheduledHarnessRunCore( params: Omit< MaterializeRequesterScopedMcpToolsForHarnessRunParams, "requesterSenderId" | "agentAccountId" | "messageChannel" @@ -252,7 +252,7 @@ export async function materializeStaticMcpToolsForScheduledHarnessRun( * Updates the session advertised-catalog cache when a requester resolves a catalog. * Before any requester resolves in the session, returns undefined (nothing to advertise). */ -export async function materializeRequesterScopedMcpToolsForHarnessRun( +export async function materializeRequesterScopedMcpToolsForHarnessRunCore( params: MaterializeRequesterScopedMcpToolsForHarnessRunParams, ): Promise { const scopedRuntime = await getOrCreateRequesterScopedMcpRuntime({ diff --git a/src/agents/agent-command.compaction-rotation.test.ts b/src/agents/agent-command.compaction-rotation.test.ts index dec5274b83ec..ed4ef9167ffc 100644 --- a/src/agents/agent-command.compaction-rotation.test.ts +++ b/src/agents/agent-command.compaction-rotation.test.ts @@ -6,7 +6,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import type { SessionEntry } from "../config/sessions.js"; import { formatSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadTranscriptEvents, replaceSessionEntry, } from "../config/sessions/session-accessor.js"; @@ -304,7 +304,7 @@ function requireStorePath(): string { } function findStoredSessionEntry(sessionKey: string): SessionEntry | undefined { - return listSessionEntries({ storePath: requireStorePath() }).find( + return listSessionEntriesCore({ storePath: requireStorePath() }).find( (candidate) => candidate.sessionKey === sessionKey, )?.entry; } @@ -443,7 +443,7 @@ describe("agentCommand compaction transcript rotation", () => { }); const storeAfterRotation = Object.fromEntries( - listSessionEntries({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry]), ); const entriesAfterRotation = Object.entries(storeAfterRotation); expect(entriesAfterRotation).toHaveLength(1); @@ -977,7 +977,7 @@ describe("agentCommand compaction transcript rotation", () => { sessionId: "rotated-session", }); const persisted = Object.fromEntries( - listSessionEntries({ storePath }).map(({ entry, sessionKey: key }) => [key, entry]), + listSessionEntriesCore({ storePath }).map(({ entry, sessionKey: key }) => [key, entry]), ); expect(persisted[sessionKey]).toMatchObject({ sessionId: "rotated-session", diff --git a/src/agents/agent-tool-definition-adapter.ts b/src/agents/agent-tool-definition-adapter.ts index 221a7d59fdf4..eccdbb65881c 100644 --- a/src/agents/agent-tool-definition-adapter.ts +++ b/src/agents/agent-tool-definition-adapter.ts @@ -38,7 +38,7 @@ import { getInternalToolExecutionPreparer, } from "./runtime/internal-hooks.js"; import type { ToolDefinition } from "./sessions/index.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { jsonResult, payloadTextResult, ToolInputError } from "./tools/common.js"; type AnyAgentTool = AgentTool; @@ -357,7 +357,7 @@ export function findClientToolNameConflicts(params: { for (const name of params.existingToolNames ?? []) { const trimmed = name.trim(); if (trimmed) { - existingNormalized.add(normalizeToolName(trimmed)); + existingNormalized.add(normalizeToolPolicyName(trimmed)); } } @@ -368,7 +368,7 @@ export function findClientToolNameConflicts(params: { if (!rawName) { continue; } - const normalizedName = normalizeToolName(rawName); + const normalizedName = normalizeToolPolicyName(rawName); if (existingNormalized.has(normalizedName)) { conflicts.add(rawName); } @@ -400,7 +400,7 @@ export function toToolDefinitions( ): ToolDefinition[] { return tools.map((tool) => { const name = tool.name || "tool"; - const normalizedName = normalizeToolName(name); + const normalizedName = normalizeToolPolicyName(name); const beforeHookWrapped = isToolWrappedWithBeforeToolCallHook(tool); const sourcePreparer = getInternalToolExecutionPreparer(tool); const definition = { diff --git a/src/agents/agent-tools.before-tool-call.diagnostics.ts b/src/agents/agent-tools.before-tool-call.diagnostics.ts index 562d4ebd27fb..aa1476237bfa 100644 --- a/src/agents/agent-tools.before-tool-call.diagnostics.ts +++ b/src/agents/agent-tools.before-tool-call.diagnostics.ts @@ -48,7 +48,7 @@ import { normalizeFileToolPathParam } from "./agent-tools.params.js"; import { BEFORE_TOOL_CALL_SOURCE_TOOL } from "./before-tool-call-metadata.js"; import { getChannelAgentToolMeta } from "./channel-tools.js"; import { resolveAgentRunAbortLifecycleFields } from "./run-termination.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { resolveToolExecutionErrorKind, resolveToolResultFailureKind, @@ -391,7 +391,7 @@ export function findSkillUsageMatch(params: { }): SkillUsageMatch | undefined { const command = params.ctx?.skillCommand; if (command) { - const commandToolName = normalizeToolName(command.toolName ?? params.toolName); + const commandToolName = normalizeToolPolicyName(command.toolName ?? params.toolName); if (!commandToolName || commandToolName === params.toolName) { const skillSource = resolveSkillTelemetrySourceValue(command.skillSource); const snapshotMatch = findResolvedSkillUsageMatch({ diff --git a/src/agents/agent-tools.before-tool-call.policy.ts b/src/agents/agent-tools.before-tool-call.policy.ts index 2294ab60c199..c8a63d068ecc 100644 --- a/src/agents/agent-tools.before-tool-call.policy.ts +++ b/src/agents/agent-tools.before-tool-call.policy.ts @@ -51,7 +51,7 @@ import { normalizeCodeModeExecBeforeHookParamsForToolKind, } from "./code-mode-control-tools.js"; import { admitSingleToolCallLoop } from "./tool-loop-admission.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; const BEFORE_TOOL_CALL_HOOK_FAILURE_REASON = "Tool call blocked because before_tool_call hook failed"; @@ -81,7 +81,7 @@ export function consumeFinalClientVoiceToolConfirmation(args: { agentId: voiceRun?.agentId, voiceSessionId: voiceRun?.voiceSessionId, runId: args.ctx?.runId, - toolName: normalizeToolName(args.toolName || "tool"), + toolName: normalizeToolPolicyName(args.toolName || "tool"), toolParams: args.params, ...(voiceRun ? { isConfirmable: () => isClientVoiceSessionConfirmable(voiceRun) } : {}), }); @@ -97,7 +97,7 @@ export async function runBeforeToolCallHook(args: { signal?: AbortSignal; approvalMode?: "request" | "report" | "deny" | "defer"; }): Promise { - const toolName = normalizeToolName(args.toolName || "tool"); + const toolName = normalizeToolPolicyName(args.toolName || "tool"); const params = args.params; let releaseArgumentChurnPolicyWait: (() => void) | undefined; diff --git a/src/agents/agent-tools.before-tool-call.wrapper.ts b/src/agents/agent-tools.before-tool-call.wrapper.ts index 1ced3a39d80d..7e63b414cde1 100644 --- a/src/agents/agent-tools.before-tool-call.wrapper.ts +++ b/src/agents/agent-tools.before-tool-call.wrapper.ts @@ -73,7 +73,7 @@ import { } from "./code-mode-control-tools.js"; import { attachInternalToolExecutionPreparer } from "./runtime/internal-hooks.js"; import { buildToolMutationState } from "./tool-mutation.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { formatToolExecutionErrorMessage, isTrustedToolExecutionPreflightError, @@ -303,7 +303,7 @@ export function wrapToolWithBeforeToolCallHook( } const toolCallOrdinal = ctx?.allocateToolOutcomeOrdinal?.(toolCallId); const preExecutionStartedAt = Date.now(); - const normalizedToolName = normalizeToolName(toolName || "tool"); + const normalizedToolName = normalizeToolPolicyName(toolName || "tool"); const trace = hookOptions.emitDiagnostics && ctx?.trace ? freezeDiagnosticTraceContext(createChildDiagnosticTraceContext(ctx.trace)) diff --git a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts index 02accc9aa415..4130b3e9df24 100644 --- a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts @@ -55,7 +55,7 @@ import { createAgentToolsSandboxContext } from "./test-helpers/agent-tools-sandb import { stubTool } from "./test-helpers/fast-tool-stubs.js"; import { createHostSandboxFsBridge } from "./test-helpers/host-sandbox-fs-bridge.js"; import { buildEmptyExplicitToolAllowlistError } from "./tool-allowlist-guard.js"; -import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, normalizeToolName } from "./tool-policy.js"; +import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, normalizeToolPolicyName } from "./tool-policy.js"; import { replaceWithEffectiveCronCreatorToolAllowlist } from "./tools/cron-tool.js"; import { getGatewayToolCallerIdentity } from "./tools/gateway-caller-context.js"; @@ -127,8 +127,8 @@ function expectNoSubagentControlTools(tools: ReturnType(tools: T[], toolsAllow: string[]) { - const allowSet = new Set(toolsAllow.map((name) => normalizeToolName(name))); - return tools.filter((tool) => allowSet.has(normalizeToolName(tool.name))); + const allowSet = new Set(toolsAllow.map((name) => normalizeToolPolicyName(name))); + return tools.filter((tool) => allowSet.has(normalizeToolPolicyName(tool.name))); } type OpenClawCodingTool = ReturnType[number]; diff --git a/src/agents/agent-tools.ts b/src/agents/agent-tools.ts index c2ab94399406..ede2fe371611 100644 --- a/src/agents/agent-tools.ts +++ b/src/agents/agent-tools.ts @@ -86,7 +86,7 @@ import { applyToolPolicyPipeline } from "./tool-policy-pipeline.js"; import { expandToolGroups, hasRestrictiveAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, replaceWithEffectiveToolAllowlist, } from "./tool-policy.js"; import { @@ -466,7 +466,7 @@ function createOpenClawCodingToolsInternal(options?: OpenClawCodingToolsOptions) const runtimeToolAllowlistIncludesMessage = expandToolGroups( options?.runtimeToolAllowlist ?? [], ).some((toolName) => { - const normalized = normalizeToolName(toolName); + const normalized = normalizeToolPolicyName(toolName); return normalized === "*" || normalized === "message"; }); // The verified requester profile owns completion authority; its delivery grant diff --git a/src/agents/auth-profiles/session-override.test.ts b/src/agents/auth-profiles/session-override.test.ts index d3e7f75e0944..66feb69dc56e 100644 --- a/src/agents/auth-profiles/session-override.test.ts +++ b/src/agents/auth-profiles/session-override.test.ts @@ -9,7 +9,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { deleteSessionEntryLifecycle, loadSessionEntry, - patchSessionEntry, + patchSessionEntryCore, replaceSessionEntry, } from "../../config/sessions/session-accessor.js"; import type { SessionEntry } from "../../config/sessions/types.js"; @@ -643,7 +643,7 @@ describe("resolveSessionAuthProfileOverride", () => { expect(sessionEntry).toBeDefined(); const sessionStore = { [sessionKey]: sessionEntry! }; - await patchSessionEntry(scope, () => ({ label: "renamed", pinnedAt: undefined })); + await patchSessionEntryCore(scope, () => ({ label: "renamed", pinnedAt: undefined })); await clearSessionAuthProfileOverride({ sessionEntry: sessionEntry!, sessionStore, @@ -700,7 +700,7 @@ describe("resolveSessionAuthProfileOverride", () => { expect(sessionEntry).toBeDefined(); const sessionStore = { [sessionKey]: sessionEntry! }; - await patchSessionEntry(scope, () => ({ label: "renamed", pinnedAt: undefined })); + await patchSessionEntryCore(scope, () => ({ label: "renamed", pinnedAt: undefined })); const resolved = await resolveOpenAiSession({ agentDir, sessionEntry: sessionEntry!, @@ -740,7 +740,7 @@ describe("resolveSessionAuthProfileOverride", () => { const sessionEntry = loadSessionEntry({ ...scope, readConsistency: "latest" }); expect(sessionEntry).toBeDefined(); const sessionStore = { [sessionKey]: sessionEntry! }; - await patchSessionEntry(scope, () => ({ label: "renamed", pinnedAt: undefined })); + await patchSessionEntryCore(scope, () => ({ label: "renamed", pinnedAt: undefined })); const resolved = await resolveOpenAiSession({ agentDir, @@ -803,7 +803,7 @@ describe("resolveSessionAuthProfileOverride", () => { if (persisted) { await replaceSessionEntry(scope, sessionEntry); sessionEntry = loadSessionEntry({ ...scope, readConsistency: "latest" })!; - await patchSessionEntry(scope, () => ({ + await patchSessionEntryCore(scope, () => ({ ...latestEntry, authProfileOverrideSource: source, pinnedAt: undefined, diff --git a/src/agents/auth-profiles/session-override.ts b/src/agents/auth-profiles/session-override.ts index 4bdff8c7a18e..08837686ecaf 100644 --- a/src/agents/auth-profiles/session-override.ts +++ b/src/agents/auth-profiles/session-override.ts @@ -113,7 +113,7 @@ async function persistSessionAuthProfileOverrideState(params: { } const persisted = await ( await loadSessionAccessor() - ).patchSessionEntry( + ).patchSessionEntryCore( { storePath, sessionKey }, (current) => { // Compare inside the canonical SQLite writer so a concurrent /model pin diff --git a/src/agents/bash-tools.process.ts b/src/agents/bash-tools.process.ts index 379ecffc8abf..47e5cf3e1c36 100644 --- a/src/agents/bash-tools.process.ts +++ b/src/agents/bash-tools.process.ts @@ -33,7 +33,7 @@ import { processSchema } from "./bash-tools.schemas.js"; import { clampWithDefault, deriveSessionName, - pad, + padProcessStatus, readEnvInt, sliceLogLines, truncateMiddle, @@ -356,7 +356,7 @@ export function createProcessTool( .map((s) => { const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120); const marker = "waitingForInput" in s && s.waitingForInput ? " [input-wait]" : ""; - return `${s.sessionId} ${pad(s.status, 9)} ${ + return `${s.sessionId} ${padProcessStatus(s.status, 9)} ${ formatDurationCompact(s.runtimeMs) ?? "n/a" }${marker} :: ${label}`; }); diff --git a/src/agents/bash-tools.shared.ts b/src/agents/bash-tools.shared.ts index 0470bcdcd864..4f7ce3fe3a33 100644 --- a/src/agents/bash-tools.shared.ts +++ b/src/agents/bash-tools.shared.ts @@ -231,7 +231,7 @@ function stripQuotes(value: string): string { } /** Right-pads a string for aligned plain-text process output. */ -export function pad(str: string, width: number) { +export function padProcessStatus(str: string, width: number) { if (str.length >= width) { return str; } diff --git a/src/agents/btw-transcript.ts b/src/agents/btw-transcript.ts index cea05e05cc83..302d6ee46a0d 100644 --- a/src/agents/btw-transcript.ts +++ b/src/agents/btw-transcript.ts @@ -8,7 +8,7 @@ import { } from "../config/sessions.js"; import { parseSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, loadTranscriptEvents, } from "../config/sessions/session-accessor.js"; @@ -125,7 +125,7 @@ export async function readBtwTranscriptMessages(params: { const storePath = completeTarget ? params.storePath : (params.storePath ?? marker?.storePath); const markerMatches = marker && !completeTarget - ? listSessionEntries({ + ? listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId) diff --git a/src/agents/bundle-mcp-adapter.ts b/src/agents/bundle-mcp-adapter.ts index c4b2bd3d6b6e..db91fa23bbb0 100644 --- a/src/agents/bundle-mcp-adapter.ts +++ b/src/agents/bundle-mcp-adapter.ts @@ -3,7 +3,7 @@ import { isRecord } from "../../packages/normalization-core/src/record-coerce.js import type { BundleMcpServerConfig } from "../plugins/bundle-mcp.js"; export { isRecord } from "../../packages/normalization-core/src/record-coerce.js"; -export function normalizeStringRecord(value: unknown): Record | undefined { +export function normalizeMcpStringRecord(value: unknown): Record | undefined { if (!isRecord(value)) { return undefined; } @@ -47,7 +47,7 @@ export function normalizeBundleMcpServerConfig( if (args) { next.args = args; } - const env = normalizeStringRecord(server.env); + const env = normalizeMcpStringRecord(server.env); if (env) { next.env = env; } diff --git a/src/agents/cli-credentials.ts b/src/agents/cli-credentials.ts index 038a37b8dfd1..da4de32a826c 100644 --- a/src/agents/cli-credentials.ts +++ b/src/agents/cli-credentials.ts @@ -11,7 +11,7 @@ import { resolveExpiresAtMsFromDurationMs, } from "@openclaw/normalization-core/number-coercion"; import { resolveOsHomeRelativePath } from "../infra/home-dir.js"; -import { loadJsonFile } from "../infra/json-file.js"; +import { loadJsonFileThroughSymlink } from "../infra/json-file.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import type { OAuthProvider } from "./auth-profiles/types.js"; @@ -415,7 +415,7 @@ function readPortalCliOauthCredentials( credPath: string, provider: TProvider, ): { type: "oauth"; provider: TProvider; access: string; refresh: string; expires: number } | null { - const raw = loadJsonFile(credPath); + const raw = loadJsonFileThroughSymlink(credPath); if (!raw || typeof raw !== "object") { return null; } @@ -430,7 +430,7 @@ function readMiniMaxCliCredentials(options?: { homeDir?: string }): MiniMaxCliCr function readGeminiCliCredentials(options?: { homeDir?: string }): GeminiCliCredential | null { const credPath = resolveGeminiCliCredentialsPath(options?.homeDir); - const raw = loadJsonFile(credPath); + const raw = loadJsonFileThroughSymlink(credPath); if (!raw || typeof raw !== "object") { return null; } @@ -477,7 +477,7 @@ function readClaudeCliKeychainCredentials( } function readClaudeCliUserApiKeyHelperCredential(homeDir?: string): ClaudeCliCredential | null { - const raw = loadJsonFile(resolveClaudeCliUserSettingsPath(homeDir)); + const raw = loadJsonFileThroughSymlink(resolveClaudeCliUserSettingsPath(homeDir)); if (!raw || typeof raw !== "object" || Array.isArray(raw)) { return null; } @@ -497,7 +497,7 @@ function readClaudeCliUserApiKeyHelperCredential(homeDir?: string): ClaudeCliCre // later account switch could mislabel another credential's quota. function readClaudeCliAccountEmail(homeDir?: string): string | undefined { const baseDir = resolveOsHomeRelativePath(homeDir ?? "~"); - const raw = loadJsonFile(path.join(baseDir, ".claude.json")); + const raw = loadJsonFileThroughSymlink(path.join(baseDir, ".claude.json")); if (!raw || typeof raw !== "object") { return undefined; } @@ -547,7 +547,7 @@ function readClaudeCliCredentials(options?: { } const credPath = resolveClaudeCliCredentialsPath(options?.homeDir); - const raw = loadJsonFile(credPath); + const raw = loadJsonFileThroughSymlink(credPath); if (!raw || typeof raw !== "object") { return null; } @@ -624,7 +624,7 @@ export function readCodexCliActiveApiKey(options?: { const candidates: CodexCliApiKeyCredential[] = []; const authPath = path.join(codexHome, CODEX_CLI_AUTH_FILENAME); - const raw = loadJsonFile(authPath); + const raw = loadJsonFileThroughSymlink(authPath); if (raw && typeof raw === "object") { const fileCredential = parseCodexApiKeyCredential(raw as Record); if (fileCredential) { @@ -684,7 +684,7 @@ function readCodexCliCredentials(options?: { } const authPath = path.join(resolveCodexHomePath(options?.codexHome), CODEX_CLI_AUTH_FILENAME); - const raw = loadJsonFile(authPath); + const raw = loadJsonFileThroughSymlink(authPath); if (!raw || typeof raw !== "object") { return null; } diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index 75dba7502804..edc17def39a2 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -4,7 +4,7 @@ import { setReplyPayloadMetadata, type ReplyPayload } from "../auto-reply/reply-payload.js"; import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; import { resolveStorePath } from "../config/sessions/paths.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import { appendExactAssistantMessageToSessionTranscript } from "../config/sessions/transcript.js"; import { buildGenericCliContextEngineHostSupport } from "../context-engine/host-compat.js"; import { @@ -1108,7 +1108,7 @@ export async function runPreparedCliAgent( agentId, }), }; - const persistedEntry = await patchSessionEntry( + const persistedEntry = await patchSessionEntryCore( sessionTarget, (entry, patchContext) => { if (patchContext.existingEntry && entry.sessionId !== sessionTarget.sessionId) { diff --git a/src/agents/cli-runner/bundle-mcp-gemini.ts b/src/agents/cli-runner/bundle-mcp-gemini.ts index 8623ab955f1f..e9ffbcb8b71d 100644 --- a/src/agents/cli-runner/bundle-mcp-gemini.ts +++ b/src/agents/cli-runner/bundle-mcp-gemini.ts @@ -8,7 +8,7 @@ import { decodeHeaderEnvPlaceholder, isRecord, normalizeBundleMcpServerConfig, - normalizeStringRecord, + normalizeMcpStringRecord, } from "../bundle-mcp-adapter.js"; import { withOpenClawMcpCaptureHeader, writeTemporaryBundleMcpJson } from "./bundle-mcp-runtime.js"; @@ -81,7 +81,7 @@ function normalizeGeminiServerConfig( deniedTools: readonly string[] | undefined, ): Record { const next = normalizeBundleMcpServerConfig(server, GEMINI_MCP_SERVER_FIELDS); - const headers = normalizeStringRecord(server.headers); + const headers = normalizeMcpStringRecord(server.headers); if (headers) { next.headers = Object.fromEntries( Object.entries(headers).map(([name, value]) => [ diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index f4696e4e0be0..30e17814b09d 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -114,7 +114,7 @@ import { collectRuntimeChannelCapabilities } from "../runtime-capabilities.js"; import { ensureSandboxWorkspaceForSession } from "../sandbox.js"; import { buildSystemPromptReport } from "../system-prompt-report.js"; import { appendModelIdentitySystemPrompt, buildModelIdentityPromptLine } from "../system-prompt.js"; -import { expandToolGroups, normalizeToolName } from "../tool-policy.js"; +import { expandToolGroups, normalizeToolPolicyName } from "../tool-policy.js"; import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js"; import { DEFAULT_BOOTSTRAP_FILENAME, @@ -479,13 +479,13 @@ export async function prepareCliRunContext( `CLI backend ${backendResolved.id} received conflicting runtime tool policies`, ); } - if (params.toolsAllow.some((toolName) => normalizeToolName(toolName) === "*")) { + if (params.toolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*")) { params = { ...params, toolsAllow: undefined }; } else { runtimeToolsAllowPolicy = [...params.toolsAllow]; const fallbackOpenClawTools = uniqueStrings( expandToolGroups(params.toolsAllow) - .map((toolName) => normalizeToolName(toolName)) + .map((toolName) => normalizeToolPolicyName(toolName)) .filter(Boolean), ); if ( @@ -795,7 +795,7 @@ export async function prepareCliRunContext( ); const promptBuildRestrictsTools = promptBuildToolsAllow !== undefined && - !promptBuildToolsAllow.some((toolName) => normalizeToolName(toolName) === "*"); + !promptBuildToolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*"); const isClaudeCli = isClaudeCliBackendId(params.provider); const requestedContextModelId = isClaudeCli ? resolveClaudeCliContextModelId(modelId) : modelId; const normalizedContextModelId = isClaudeCli @@ -1055,7 +1055,7 @@ export async function prepareCliRunContext( : hookFilteredProjectedTools; const promptTools = bundleMcpEnabled ? projectedTools : []; const messageToolAvailable = promptTools.some( - (tool) => normalizeToolName(tool.name) === "message", + (tool) => normalizeToolPolicyName(tool.name) === "message", ); const resultContentSourceByToolName = new Map( promptTools.flatMap((tool) => diff --git a/src/agents/cli-runner/tool-policy.ts b/src/agents/cli-runner/tool-policy.ts index bcf39ae43ac9..048585e24f2b 100644 --- a/src/agents/cli-runner/tool-policy.ts +++ b/src/agents/cli-runner/tool-policy.ts @@ -1,5 +1,5 @@ import type { CliBackendToolAvailability } from "../../plugins/cli-backend.types.js"; -import { normalizeToolName } from "../tool-policy.js"; +import { normalizeToolPolicyName } from "../tool-policy.js"; /** Transport prefix CLI harnesses use for loopback OpenClaw MCP tool names. */ const OPENCLAW_MCP_TOOL_PREFIX = "mcp__openclaw__"; @@ -31,7 +31,7 @@ export function resolveCliRuntimeToolsAllow( if (toolsAllow === undefined) { return undefined; } - return toolsAllow.some((toolName) => normalizeToolName(toolName) === "*") + return toolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*") ? undefined : toolsAllow; } diff --git a/src/agents/code-mode-control-tools.ts b/src/agents/code-mode-control-tools.ts index 211ff3bd08fb..57a88c968eb8 100644 --- a/src/agents/code-mode-control-tools.ts +++ b/src/agents/code-mode-control-tools.ts @@ -3,7 +3,7 @@ * exec-compatible before-tool-call surface. */ import { isPlainObject } from "../utils.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import type { AnyAgentTool } from "./tools/common.js"; /** Model-visible Code Mode exec tool name. */ @@ -44,7 +44,9 @@ export function isCodeModeControlTool(tool: object): boolean { } function isCodeModeExecTool(tool: AnyAgentTool): boolean { - return isCodeModeControlTool(tool) && normalizeToolName(tool.name) === CODE_MODE_EXEC_TOOL_NAME; + return ( + isCodeModeControlTool(tool) && normalizeToolPolicyName(tool.name) === CODE_MODE_EXEC_TOOL_NAME + ); } function resolveCodeModeExecToolInputKind(params: unknown): CodeModeExecToolInputKind | undefined { diff --git a/src/agents/codex-mcp-config.test.ts b/src/agents/codex-mcp-config.test.ts index 612b053d1f23..f0fa08c396db 100644 --- a/src/agents/codex-mcp-config.test.ts +++ b/src/agents/codex-mcp-config.test.ts @@ -4,7 +4,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { buildCodexMcpServersConfig, loadCodexBundleMcpThreadConfig } from "./codex-mcp-config.js"; +import { + buildCodexMcpServersConfig, + loadCodexBundleMcpThreadConfigCore, +} from "./codex-mcp-config.js"; import { testing as resolverTesting } from "./mcp-connection-resolver.js"; const mocks = vi.hoisted(() => ({ @@ -94,11 +97,11 @@ describe("buildCodexMcpServersConfig", () => { }); }); -describe("loadCodexBundleMcpThreadConfig", () => { +describe("loadCodexBundleMcpThreadConfigCore", () => { it("forwards a prepared manifest registry to bundle loading", () => { const manifestRegistry = { plugins: [] }; - loadCodexBundleMcpThreadConfig({ workspaceDir: "/workspace", manifestRegistry }); + loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", manifestRegistry }); expect(mocks.loadCalls).toEqual([ expect.objectContaining({ workspaceDir: "/workspace", manifestRegistry }), @@ -128,7 +131,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { }, }); - const loaded = loadCodexBundleMcpThreadConfig({ workspaceDir: "/workspace" }); + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace" }); expect((await fs.stat(dataDir)).isDirectory()).toBe(true); expect(loaded.configPatch?.mcp_servers.weather).toMatchObject({ command: "node" }); @@ -154,7 +157,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { diagnostics: [], }; - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: { plugins: { @@ -192,7 +195,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { diagnostics: [], }; - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolOverrides: { @@ -232,10 +235,10 @@ describe("loadCodexBundleMcpThreadConfig", () => { }; expect( - loadCodexBundleMcpThreadConfig({ workspaceDir: "/workspace", cfg }).configPatch, + loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg }).configPatch, ).toBeUndefined(); expect( - loadCodexBundleMcpThreadConfig({ + loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg, toolOverrides: { mcpServers: { docs: true } }, @@ -250,7 +253,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { it("leaves user mcp.servers to the Codex user MCP projection path", () => { // User MCP config is projected elsewhere; this loader only injects bundled // MCP servers so the same server does not appear twice in Codex. - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: { mcp: { @@ -290,7 +293,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { { toolsEnabled: true, toolsAllow: [] }, { toolsEnabled: true, toolsAllow: ["memory_search"] }, ]) { - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg, ...params, @@ -304,7 +307,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { }); it("omits the config patch when no MCP servers are configured", () => { - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolsEnabled: true, @@ -338,7 +341,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { diagnostics: [], }; - const loaded = loadCodexBundleMcpThreadConfig({ + const loaded = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolsEnabled: true, @@ -355,7 +358,7 @@ describe("loadCodexBundleMcpThreadConfig", () => { }, diagnostics: [], }; - const withoutScopedConfig = loadCodexBundleMcpThreadConfig({ + const withoutScopedConfig = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolsEnabled: true, @@ -388,12 +391,12 @@ describe("loadCodexBundleMcpThreadConfig", () => { diagnostics: [], }; - const a = loadCodexBundleMcpThreadConfig({ + const a = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolsEnabled: true, }); - const b = loadCodexBundleMcpThreadConfig({ + const b = loadCodexBundleMcpThreadConfigCore({ workspaceDir: "/workspace", cfg: {}, toolsEnabled: true, diff --git a/src/agents/codex-mcp-config.ts b/src/agents/codex-mcp-config.ts index 11bfdca101c5..bd0f321f1ea2 100644 --- a/src/agents/codex-mcp-config.ts +++ b/src/agents/codex-mcp-config.ts @@ -16,7 +16,7 @@ import { isRecord } from "../utils.js"; import { decodeHeaderEnvPlaceholder, normalizeBundleMcpServerConfig, - normalizeStringRecord, + normalizeMcpStringRecord, } from "./bundle-mcp-adapter.js"; import { prepareOwnedBundleMcpDataDirs } from "./bundle-mcp-config.js"; import type { @@ -106,7 +106,7 @@ export function normalizeCodexMcpServerConfig( if (defaultToolsApprovalMode) { next.default_tools_approval_mode = defaultToolsApprovalMode; } - const httpHeaders = normalizeStringRecord(server.headers); + const httpHeaders = normalizeMcpStringRecord(server.headers); if (httpHeaders) { const staticHeaders: Record = {}; const envHeaders: Record = {}; @@ -170,7 +170,7 @@ function fingerprintCodexMcpServersConfig(config: CodexMcpServersConfig): string } /** Load bundle MCP config for one Codex app-server thread. */ -export function loadCodexBundleMcpThreadConfig( +export function loadCodexBundleMcpThreadConfigCore( params: LoadCodexBundleMcpThreadConfigParams, ): CodexBundleMcpThreadConfig { const shouldCreateRuntime = shouldCreateBundleMcpRuntimeForAttempt({ diff --git a/src/agents/command/assistant-transcript-repair.test.ts b/src/agents/command/assistant-transcript-repair.test.ts index baeb2c736ad0..12810e526d62 100644 --- a/src/agents/command/assistant-transcript-repair.test.ts +++ b/src/agents/command/assistant-transcript-repair.test.ts @@ -6,7 +6,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { setReplyPayloadMetadata } from "../../auto-reply/reply-payload.js"; import type { SessionEntry } from "../../config/sessions.js"; import { - listSessionEntries, + listSessionEntriesCore, loadTranscriptEvents, } from "../../config/sessions/session-accessor.js"; import type { appendExactAssistantMessageToSessionTranscript } from "../../config/sessions/transcript.runtime.js"; @@ -384,7 +384,7 @@ function requireStorePath(): string { } function findStoredSessionEntry(sessionKey: string): SessionEntry | undefined { - return listSessionEntries({ storePath: requireStorePath() }).find( + return listSessionEntriesCore({ storePath: requireStorePath() }).find( (candidate) => candidate.sessionKey === sessionKey, )?.entry; } diff --git a/src/agents/command/attempt-execution.cli.test.ts b/src/agents/command/attempt-execution.cli.test.ts index e68d14458e2a..0183a5c9f914 100644 --- a/src/agents/command/attempt-execution.cli.test.ts +++ b/src/agents/command/attempt-execution.cli.test.ts @@ -12,7 +12,7 @@ import { import { appendTranscriptEvent, appendTranscriptMessage, - listSessionEntries, + listSessionEntriesCore, loadTranscriptEvents, replaceSessionEntry, } from "../../config/sessions/session-accessor.js"; @@ -710,7 +710,7 @@ describe("CLI attempt execution", () => { function readSessionStore(): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry]), ); } diff --git a/src/agents/command/attempt-execution.shared.ts b/src/agents/command/attempt-execution.shared.ts index 3f8c686d569a..693777a0e3a2 100644 --- a/src/agents/command/attempt-execution.shared.ts +++ b/src/agents/command/attempt-execution.shared.ts @@ -2,7 +2,7 @@ * Shared session persistence and prompt-body helpers for agent attempt * execution paths. */ -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import { mergeSessionSnapshotChanges } from "../../config/sessions/session-snapshot-merge.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import { @@ -30,7 +30,7 @@ export async function persistAgentSession( params: PersistSessionEntryParams, ): Promise { let rejectedMissingEntry = false; - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (_entry, context) => { const shouldPersistCurrent = params.shouldPersist?.(context.existingEntry); diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts index 53843a11ed44..61c9bd64819d 100644 --- a/src/agents/command/session-store.test.ts +++ b/src/agents/command/session-store.test.ts @@ -7,7 +7,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { InternalSessionEntry as SessionEntry } from "../../config/sessions.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceSessionEntry, } from "../../config/sessions/session-accessor.js"; @@ -111,7 +111,7 @@ async function seedSessionStore( function loadPersistedSessionStore(storePath: string): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), ); } diff --git a/src/agents/command/session-store.ts b/src/agents/command/session-store.ts index d858b402f80a..8b337aa71f9f 100644 --- a/src/agents/command/session-store.ts +++ b/src/agents/command/session-store.ts @@ -7,7 +7,7 @@ import { setSessionRuntimeModel, type SessionEntry, } from "../../config/sessions.js"; -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import { projectSessionSnapshotChanges } from "../../config/sessions/session-snapshot-merge.js"; import { resolveMaintenanceConfigFromInput } from "../../config/sessions/store-maintenance.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -286,7 +286,7 @@ export async function updateSessionStoreAfterAgentRun(params: { } : next; const maintenanceConfig = resolveMaintenanceConfigFromInput(cfg.session?.maintenance); - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey, @@ -338,7 +338,7 @@ export async function clearCliSessionInStore(params: { } let didClear = false; - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey, @@ -385,7 +385,7 @@ export async function consumeCliSessionForkInStore(params: { if (!entry || binding?.sessionId !== expectedCliSessionId || binding.forkNextResume !== true) { return undefined; } - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey }, (currentEntry) => { const currentBinding = currentEntry.cliSessionBindings?.[provider]; @@ -422,7 +422,7 @@ export async function restoreCliSessionForkInStore(params: { if (!entry || binding?.sessionId !== expectedCliSessionId || binding.forkNextResume === true) { return undefined; } - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey }, (currentEntry) => { const currentBinding = currentEntry.cliSessionBindings?.[provider]; @@ -465,7 +465,7 @@ export async function persistCliSessionForkSuccessorInStore(params: { if (!entry || successorCliSessionId === expectedCliSessionId) { return undefined; } - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey }, (currentEntry) => { const currentBinding = currentEntry.cliSessionBindings?.[provider]; @@ -540,7 +540,7 @@ export async function recordCliCompactionInStore(params: { next.cacheWrite = undefined; } - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { storePath, sessionKey, diff --git a/src/agents/command/session.provider-owned-reset.test.ts b/src/agents/command/session.provider-owned-reset.test.ts index 2688924c2c63..cc199cae61b7 100644 --- a/src/agents/command/session.provider-owned-reset.test.ts +++ b/src/agents/command/session.provider-owned-reset.test.ts @@ -8,7 +8,7 @@ const hoisted = vi.hoisted(() => ({ })); vi.mock("../../config/sessions/session-accessor.js", () => ({ - listSessionEntries: () => + listSessionEntriesCore: () => Object.entries(hoisted.store).map(([sessionKey, entry]) => ({ sessionKey, entry, diff --git a/src/agents/command/session.resolve-session-key.test.ts b/src/agents/command/session.resolve-session-key.test.ts index b3449dd91e35..7954a39d812e 100644 --- a/src/agents/command/session.resolve-session-key.test.ts +++ b/src/agents/command/session.resolve-session-key.test.ts @@ -14,7 +14,7 @@ const hoisted = vi.hoisted(() => ({ })); vi.mock("../../config/sessions/session-accessor.js", () => ({ - listSessionEntries: (scope?: { storePath?: string; clone?: boolean }) => + listSessionEntriesCore: (scope?: { storePath?: string; clone?: boolean }) => hoisted.listSessionEntriesMock(scope), })); diff --git a/src/agents/command/session.ts b/src/agents/command/session.ts index 7b2fc3f12c9d..595ee4569491 100644 --- a/src/agents/command/session.ts +++ b/src/agents/command/session.ts @@ -25,7 +25,7 @@ import { resolveSessionResetPolicy, } from "../../config/sessions/reset-policy.js"; import { resolveChannelResetConfig, resolveSessionResetType } from "../../config/sessions/reset.js"; -import { listSessionEntries } from "../../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../../config/sessions/session-accessor.js"; import { resolveSessionKey } from "../../config/sessions/session-key.js"; import type { InternalSessionEntry as SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -112,7 +112,7 @@ function loadCommandSessionStore(params: { storePath: string; }): Record { return Object.fromEntries( - listSessionEntries({ + listSessionEntriesCore({ storePath: params.storePath, ...(params.agentId ? { agentId: params.agentId } : {}), ...(params.clone === false ? { clone: false } : {}), diff --git a/src/agents/delegation-capability.ts b/src/agents/delegation-capability.ts index 4cd03eb4e079..1b0e4b1db2db 100644 --- a/src/agents/delegation-capability.ts +++ b/src/agents/delegation-capability.ts @@ -1,6 +1,6 @@ import { isCompletionReportInputProvenance } from "../sessions/input-provenance.js"; import { isRuntimeToolAllowed } from "./tool-policy-match.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { AUTOMATIONS_TOOL_NAME } from "./tools/automations-tool-name.js"; import type { AnyAgentTool } from "./tools/common.js"; import { ToolAuthorizationError } from "./tools/common.js"; @@ -96,7 +96,7 @@ export function applyDelegationCapability( return tools; } return tools.flatMap((tool) => { - const name = normalizeToolName(tool.name); + const name = normalizeToolPolicyName(tool.name); if (NEW_DELEGATION_TOOL_NAMES.has(name)) { return []; } diff --git a/src/agents/embedded-agent-message-tool-source-reply.ts b/src/agents/embedded-agent-message-tool-source-reply.ts index df7753ee8573..83e117ec7d84 100644 --- a/src/agents/embedded-agent-message-tool-source-reply.ts +++ b/src/agents/embedded-agent-message-tool-source-reply.ts @@ -11,7 +11,7 @@ import { isMessagingToolDeliveryAction, } from "./embedded-agent-messaging.js"; import { isToolResultError } from "./embedded-agent-subscribe.tools.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; const MESSAGE_TOOL_NAME = "message"; const SESSIONS_SEND_TOOL_NAME = "sessions_send"; @@ -535,7 +535,7 @@ export function isDeliveredMessagingToolResult(params: { if (params.isError || isToolResultError(params.result) || isToolResultError(params.hookResult)) { return false; } - const normalizedToolName = normalizeToolName(params.toolName ?? MESSAGE_TOOL_NAME); + const normalizedToolName = normalizeToolPolicyName(params.toolName ?? MESSAGE_TOOL_NAME); const mutationHasBareOk = isMessagingToolDeliveryAction(normalizedToolName, args) && action !== "broadcast" && @@ -590,7 +590,7 @@ export function isDeliveredMessageToolOnlySourceReplyResult(params: { if (params.sourceReplyDeliveryMode !== "message_tool_only" && !confirmedCurrentSourceRoute) { return false; } - if (normalizeToolName(params.toolName) !== MESSAGE_TOOL_NAME) { + if (normalizeToolPolicyName(params.toolName) !== MESSAGE_TOOL_NAME) { return false; } const args = asOptionalRecord(params.args) ?? {}; diff --git a/src/agents/embedded-agent-runner/cli-backend-dispatch.ts b/src/agents/embedded-agent-runner/cli-backend-dispatch.ts index bef805b26cd6..663fb9201663 100644 --- a/src/agents/embedded-agent-runner/cli-backend-dispatch.ts +++ b/src/agents/embedded-agent-runner/cli-backend-dispatch.ts @@ -16,7 +16,7 @@ import { onAgentEvent } from "../../infra/agent-events.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { resolvePreparedRunAdmission } from "../admitted-run-context.js"; import { stripOpenClawMcpToolPrefix } from "../cli-runner/tool-policy.js"; -import { normalizeToolName } from "../tool-policy.js"; +import { normalizeToolPolicyName } from "../tool-policy.js"; import { isToolResultError } from "../tool-result-error.js"; import { resolveEmbeddedCliBackendDispatchEligibility } from "./cli-backend-dispatch-eligibility.js"; import { createCliDispatchTranscriptRecorder } from "./cli-backend-dispatch-transcript.js"; @@ -84,7 +84,7 @@ function resolveDispatchableToolsAllow(params: RunEmbeddedAgentParams): string[] if (!params.toolsAllow || params.toolsAllow.length === 0) { return undefined; } - const names = params.toolsAllow.map((name) => normalizeToolName(name)); + const names = params.toolsAllow.map((name) => normalizeToolPolicyName(name)); if (names.some((name) => !name || name === "*" || name.includes("*"))) { return undefined; } @@ -159,7 +159,7 @@ async function runEmbeddedAgentViaCliBackend( if (!rawName) { return; } - const toolName = normalizeToolName(stripOpenClawMcpToolPrefix(rawName)); + const toolName = normalizeToolPolicyName(stripOpenClawMcpToolPrefix(rawName)); const toolCallId = typeof evt.data.toolCallId === "string" ? evt.data.toolCallId : undefined; if (phase === "start") { transcript.noteToolEvent({ diff --git a/src/agents/embedded-agent-runner/compaction-successor.ts b/src/agents/embedded-agent-runner/compaction-successor.ts index 226b99c66fe6..1a0657b75c28 100644 --- a/src/agents/embedded-agent-runner/compaction-successor.ts +++ b/src/agents/embedded-agent-runner/compaction-successor.ts @@ -4,7 +4,7 @@ import { parseSqliteSessionFileMarker, } from "../../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, type SessionTranscriptRuntimeTarget, } from "../../config/sessions/session-accessor.js"; @@ -85,7 +85,7 @@ export async function resolveContextEngineCompactionSuccessor(params: { }) : undefined; const markerMatches = marker - ? listSessionEntries({ + ? listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId) diff --git a/src/agents/embedded-agent-runner/run/attempt-normalization.test.ts b/src/agents/embedded-agent-runner/run/attempt-normalization.test.ts index c9f26ea474f8..405e6e1a98fa 100644 --- a/src/agents/embedded-agent-runner/run/attempt-normalization.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt-normalization.test.ts @@ -4,14 +4,14 @@ import { buildContextEngineCompactionSessionTarget } from "./session-bootstrap.j import { createEmbeddedRunSessionPromptState } from "./session-prompt-state.js"; const sessionAccessorMocks = vi.hoisted(() => ({ - listSessionEntries: vi.fn(() => []), + listSessionEntriesCore: vi.fn(() => []), loadSessionEntry: vi.fn(), })); vi.mock("../../../config/sessions/session-accessor.js", () => sessionAccessorMocks); beforeEach(() => { - sessionAccessorMocks.listSessionEntries.mockReset().mockReturnValue([]); + sessionAccessorMocks.listSessionEntriesCore.mockReset().mockReturnValue([]); sessionAccessorMocks.loadSessionEntry.mockReset(); }); @@ -178,7 +178,7 @@ describe("applyEmbeddedAttemptSessionIdentity", () => { sessionId: "session-before", updatedAt: 1, }); - sessionAccessorMocks.listSessionEntries.mockReturnValue([ + sessionAccessorMocks.listSessionEntriesCore.mockReturnValue([ { sessionKey: "agent:main:other", entry: { sessionId: "session-after", updatedAt: 2 }, diff --git a/src/agents/embedded-agent-runner/run/attempt-prompt-build.ts b/src/agents/embedded-agent-runner/run/attempt-prompt-build.ts index 5383230a828e..8db9524b0319 100644 --- a/src/agents/embedded-agent-runner/run/attempt-prompt-build.ts +++ b/src/agents/embedded-agent-runner/run/attempt-prompt-build.ts @@ -34,7 +34,7 @@ import { appendModelIdentitySystemPrompt, buildModelIdentityPromptLine, } from "../../system-prompt.js"; -import { normalizeToolName } from "../../tool-policy.js"; +import { normalizeToolPolicyName } from "../../tool-policy.js"; import { log } from "../logger.js"; import { beginPromptCacheObservation, @@ -170,7 +170,7 @@ export async function prepareEmbeddedAttemptPromptAssembly(input: { bootstrapContextRunKind: attempt.bootstrapContextRunKind, }); const promptCacheToolNames = input.applyPromptBuildToolsAllow(hookResult?.toolsAllow); - const promptCacheToolNameSet = new Set(promptCacheToolNames.map(normalizeToolName)); + const promptCacheToolNameSet = new Set(promptCacheToolNames.map(normalizeToolPolicyName)); const promptBeforeResolvedToolFinalization = effectivePrompt; effectivePrompt = applyResolvedToolPromptFinalizer({ prompt: effectivePrompt, @@ -182,7 +182,7 @@ export async function prepareEmbeddedAttemptPromptAssembly(input: { ? promptBeforeResolvedToolFinalization : attempt.transcriptPrompt; const promptCacheTools = input.cache.tools.filter((tool) => - promptCacheToolNameSet.has(normalizeToolName(tool.name)), + promptCacheToolNameSet.has(normalizeToolPolicyName(tool.name)), ); const promptBeforePromptBuildHooks = effectivePrompt; const promptBuildPrependContext = hookResult?.prependContext; diff --git a/src/agents/embedded-agent-runner/run/attempt-prompt-support.ts b/src/agents/embedded-agent-runner/run/attempt-prompt-support.ts index 357485da9687..ff19ef576f3d 100644 --- a/src/agents/embedded-agent-runner/run/attempt-prompt-support.ts +++ b/src/agents/embedded-agent-runner/run/attempt-prompt-support.ts @@ -26,7 +26,7 @@ import { } from "../../code-mode-control-tools.js"; import type { AgentMessage } from "../../runtime/index.js"; import type { AgentSession } from "../../sessions/index.js"; -import { normalizeToolName } from "../../tool-policy.js"; +import { normalizeToolPolicyName } from "../../tool-policy.js"; import { TOOL_SEARCH_CONTROL_TOOL_NAMES } from "../../tool-search-types.js"; import { restrictToolSearchCatalog, @@ -63,7 +63,7 @@ export function applyResolvedToolPromptFinalizer(params: { return params.finalize({ prompt: params.prompt, messageToolAvailable: params.activeToolNames.some( - (toolName) => normalizeToolName(toolName) === "message", + (toolName) => normalizeToolPolicyName(toolName) === "message", ), }); } @@ -136,17 +136,17 @@ export function applyPromptBuildToolsAllow< applyToolsAllow( params.baseline.activeToolNames.map((name) => ({ name })), toolsAllow, - ).map((tool) => normalizeToolName(tool.name)), + ).map((tool) => normalizeToolPolicyName(tool.name)), ); for (const tool of [...allowedEffectiveTools, ...allowedUncompactedTools, ...allowedTools]) { - allowedActiveNames.add(normalizeToolName(tool.name)); + allowedActiveNames.add(normalizeToolPolicyName(tool.name)); } const catalogControlNames = params.codeModeControlsEnabled ? new Set([CODE_MODE_EXEC_TOOL_NAME, CODE_MODE_WAIT_TOOL_NAME]) : TOOL_SEARCH_CONTROL_TOOL_NAMES; const keepVisibleTool = (tool: NamedTool) => { - const normalized = normalizeToolName(tool.name); + const normalized = normalizeToolPolicyName(tool.name); return ( allowedActiveNames.has(normalized) || (catalogToolCount > 0 && catalogControlNames.has(normalized)) diff --git a/src/agents/embedded-agent-runner/run/attempt-session-identity.ts b/src/agents/embedded-agent-runner/run/attempt-session-identity.ts index 5f8acc597dd6..02d70c7b832e 100644 --- a/src/agents/embedded-agent-runner/run/attempt-session-identity.ts +++ b/src/agents/embedded-agent-runner/run/attempt-session-identity.ts @@ -3,7 +3,10 @@ import { formatSqliteSessionFileMarker, parseSqliteSessionFileMarker, } from "../../../config/sessions/legacy-sqlite-marker.js"; -import { listSessionEntries, loadSessionEntry } from "../../../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + loadSessionEntry, +} from "../../../config/sessions/session-accessor.js"; import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js"; import { resolvePreferredSessionKeyForSessionIdMatches } from "../../../sessions/session-id-resolution.js"; import type { createEmbeddedRunSessionPromptState } from "./session-prompt-state.js"; @@ -36,7 +39,7 @@ export function applyEmbeddedAttemptSessionIdentity(params: { storePath: marker.storePath, }) : undefined; - const markerMatches = listSessionEntries({ + const markerMatches = listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId); diff --git a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts index 227f0309969e..28362bdc6ebb 100644 --- a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts +++ b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts @@ -14,7 +14,7 @@ import { expandPolicyWithPluginGroups, expandToolGroups, normalizeToolList, - normalizeToolName, + normalizeToolPolicyName, readToolAllowlistIntersection, } from "../../tool-policy.js"; @@ -50,7 +50,7 @@ function isPluginGroupAllowlistName(normalized: string): boolean { } function hasWildcardToolAllowlist(toolsAllow: string[]): boolean { - return toolsAllow.some((entry) => normalizeToolName(entry) === "*"); + return toolsAllow.some((entry) => normalizeToolPolicyName(entry) === "*"); } /** @@ -104,8 +104,8 @@ export function mergeForcedEmbeddedAttemptToolsAllow( if (required.length === 0) { return toolsAllow; } - const normalized = new Set(toolsAllow.map((entry) => normalizeToolName(entry))); - const missing = required.filter((name) => !normalized.has(normalizeToolName(name))); + const normalized = new Set(toolsAllow.map((entry) => normalizeToolPolicyName(entry))); + const missing = required.filter((name) => !normalized.has(normalizeToolPolicyName(name))); if (missing.length === 0) { return toolsAllow; } @@ -232,7 +232,7 @@ function shouldCreateBundleRuntimeForAttempt( if (hasWildcardToolAllowlist(params.toolsAllow)) { return true; } - return params.toolsAllow.some((toolName) => matchesAllowlist(normalizeToolName(toolName))); + return params.toolsAllow.some((toolName) => matchesAllowlist(normalizeToolPolicyName(toolName))); } /** diff --git a/src/agents/embedded-agent-runner/run/attempt-tool-search-run-plan.ts b/src/agents/embedded-agent-runner/run/attempt-tool-search-run-plan.ts index f6a4dc90d5af..5ce13aef47ae 100644 --- a/src/agents/embedded-agent-runner/run/attempt-tool-search-run-plan.ts +++ b/src/agents/embedded-agent-runner/run/attempt-tool-search-run-plan.ts @@ -3,7 +3,7 @@ */ import { getPluginToolMeta } from "../../../plugins/tools.js"; import { isToolAllowedByPolicyName } from "../../tool-policy-match.js"; -import { normalizeToolName } from "../../tool-policy.js"; +import { normalizeToolPolicyName } from "../../tool-policy.js"; import { collectUniqueCatalogToolNames, TOOL_CALL_RAW_TOOL_NAME, @@ -108,14 +108,16 @@ export function buildToolSearchRunPlan(params: { } const explicitControlAllowlistNames = new Set( params.explicitAllowlistSources.flatMap((source) => - source.entries.map((entry) => normalizeToolName(entry)), + source.entries.map((entry) => normalizeToolPolicyName(entry)), ), ); const autoAddedControlNames = new Set( (params.controlsEnabled ? (params.controlNames ?? TOOL_SEARCH_CONTROL_ALLOWLIST_NAMES) : [] - ).filter((controlName) => !explicitControlAllowlistNames.has(normalizeToolName(controlName))), + ).filter( + (controlName) => !explicitControlAllowlistNames.has(normalizeToolPolicyName(controlName)), + ), ); const explicitlyAllowedClientToolNames = collectExplicitlyAllowedClientToolNames({ clientTools: params.clientTools, diff --git a/src/agents/embedded-agent-runner/run/attempt.tool-call-normalization.ts b/src/agents/embedded-agent-runner/run/attempt.tool-call-normalization.ts index eeaf2e8554aa..e375f3eb740f 100644 --- a/src/agents/embedded-agent-runner/run/attempt.tool-call-normalization.ts +++ b/src/agents/embedded-agent-runner/run/attempt.tool-call-normalization.ts @@ -33,7 +33,10 @@ import { sanitizeToolCallIdsForCloudCodeAssist, type ToolCallIdMode, } from "../../tool-call-id.js"; -import { couldNormalizeToolNamePrefixToAllowedTool, normalizeToolName } from "../../tool-policy.js"; +import { + couldNormalizeToolNamePrefixToAllowedTool, + normalizeToolPolicyName, +} from "../../tool-policy.js"; import { shouldAllowProviderOwnedThinkingReplay } from "../../transcript-policy.js"; import type { TranscriptPolicy } from "../../transcript-policy.js"; import { isRunnerToolCallBlockType } from "./attempt-tool-call-block-type.js"; @@ -79,7 +82,7 @@ function resolveExactAllowedToolName( if (allowedToolNames.has(rawName)) { return rawName; } - const normalized = normalizeToolName(rawName); + const normalized = normalizeToolPolicyName(rawName); if (allowedToolNames.has(normalized)) { return normalized; } @@ -107,7 +110,7 @@ function buildStructuredToolNameCandidates(rawName: string): string[] { }; addCandidate(trimmed); - addCandidate(normalizeToolName(trimmed)); + addCandidate(normalizeToolPolicyName(trimmed)); const structuredSeeds = [trimmed]; const xmlFragmentOffset = ['"', "'", "<"] @@ -120,21 +123,21 @@ function buildStructuredToolNameCandidates(rawName: string): string[] { if (xmlFragmentOffset !== undefined) { const prefix = trimmed.slice(0, xmlFragmentOffset); addCandidate(prefix); - addCandidate(normalizeToolName(prefix)); + addCandidate(normalizeToolPolicyName(prefix)); structuredSeeds.push(prefix); } for (const seed of structuredSeeds) { const normalizedDelimiter = seed.replace(/\//g, "."); addCandidate(normalizedDelimiter); - addCandidate(normalizeToolName(normalizedDelimiter)); + addCandidate(normalizeToolPolicyName(normalizedDelimiter)); const segments = normalizeStringEntries(normalizedDelimiter.split(".")); if (segments.length > 1) { for (let index = 1; index < segments.length; index += 1) { const suffix = segments.slice(index).join("."); addCandidate(suffix); - addCandidate(normalizeToolName(suffix)); + addCandidate(normalizeToolPolicyName(suffix)); } } } @@ -769,7 +772,7 @@ function classifyToolCallMessage( sawAllowedToolCall = true; continue; } - const normalizedUnknownToolName = normalizeToolName(rawName); + const normalizedUnknownToolName = normalizeToolPolicyName(rawName); if (!unknownToolName) { unknownToolName = normalizedUnknownToolName; continue; diff --git a/src/agents/embedded-agent-runner/run/session-bootstrap.ts b/src/agents/embedded-agent-runner/run/session-bootstrap.ts index 4ec4f04386a0..7df55078e69d 100644 --- a/src/agents/embedded-agent-runner/run/session-bootstrap.ts +++ b/src/agents/embedded-agent-runner/run/session-bootstrap.ts @@ -4,7 +4,7 @@ import { sanitizeForLog } from "../../../../packages/terminal-core/src/ansi.js"; import { resolveStorePath, SESSION_TOTAL_TOKENS_VERSION } from "../../../config/sessions.js"; import { parseSqliteSessionFileMarker } from "../../../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, updateSessionEntry, } from "../../../config/sessions/session-accessor.js"; @@ -63,7 +63,7 @@ export function buildContextEngineCompactionSessionTarget(params: { }) : undefined; const markerMatches = marker - ? listSessionEntries({ + ? listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId) diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts b/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts index 9354668b7d70..6fbcd93368e4 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.completion.ts @@ -95,7 +95,7 @@ import { summarizeToolValidationError, } from "./tool-error-summary.js"; import { resolveFileMutationToolName } from "./tool-mutation-names.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { cancelAskUserPromptDelivery } from "./tools/ask-user-tool.js"; import { isAutomationsToolName } from "./tools/automations-tool-name.js"; @@ -105,7 +105,7 @@ export async function handleToolExecutionEnd( evt: Extract, ) { const rawToolName = evt.toolName; - const toolName = normalizeToolName(rawToolName); + const toolName = normalizeToolPolicyName(rawToolName); const hideFromChannelProgress = evt.hideFromChannelProgress === true; const toolCallId = evt.toolCallId; ctx.state.liveEditDiffStateById.delete(toolCallId); diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.progress.ts b/src/agents/embedded-agent-subscribe.handlers.tools.progress.ts index 1aa93aa9be32..3282c289ed4e 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.progress.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.progress.ts @@ -26,7 +26,7 @@ import { truncateLiveExecOutput, } from "./embedded-agent-subscribe.tools.js"; import type { AgentEvent } from "./runtime/index.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; type ChannelToolProgress = { text: string; @@ -69,7 +69,7 @@ export function handleToolExecutionUpdate( hideFromChannelProgress?: boolean; }, ) { - const toolName = normalizeToolName(evt.toolName); + const toolName = normalizeToolPolicyName(evt.toolName); const toolCallId = evt.toolCallId; const hideFromChannelProgress = evt.hideFromChannelProgress === true; const partial = evt.partialResult; diff --git a/src/agents/embedded-agent-subscribe.handlers.tools.start.ts b/src/agents/embedded-agent-subscribe.handlers.tools.start.ts index 5c28785a1d44..39a73758f7c1 100644 --- a/src/agents/embedded-agent-subscribe.handlers.tools.start.ts +++ b/src/agents/embedded-agent-subscribe.handlers.tools.start.ts @@ -34,7 +34,7 @@ import type { AgentEvent } from "./runtime/index.js"; import { inferToolMetaFromArgsCore, isCommandBearingToolCall } from "./tool-display.js"; import { resolveFileMutationToolName } from "./tool-mutation-names.js"; import { buildToolMutationState } from "./tool-mutation.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { cancelAskUserPromptDelivery, normalizeAskUserParams, @@ -322,7 +322,7 @@ export function handleToolExecutionStart( hideFromChannelProgress?: boolean; }, ): void | Promise { - const startToolName = normalizeToolName(evt.toolName); + const startToolName = normalizeToolPolicyName(evt.toolName); ctx.state.liveEditDiffStateById.delete(evt.toolCallId); const askUserPromptReservation = startToolName === "ask_user" && ctx.params.onToolResult @@ -358,7 +358,7 @@ export function handleToolExecutionStart( const continueToolExecutionStart = (): void | Promise => { const rawToolName = evt.toolName; - const toolName = normalizeToolName(rawToolName); + const toolName = normalizeToolPolicyName(rawToolName); const hideFromChannelProgress = evt.hideFromChannelProgress === true; const toolCallId = evt.toolCallId; const args = evt.args; diff --git a/src/agents/embedded-agent-subscribe.tool-text-diagnostics.ts b/src/agents/embedded-agent-subscribe.tool-text-diagnostics.ts index ef2079d20375..060d948cc07f 100644 --- a/src/agents/embedded-agent-subscribe.tool-text-diagnostics.ts +++ b/src/agents/embedded-agent-subscribe.tool-text-diagnostics.ts @@ -7,7 +7,7 @@ import { extractTextFromChatContent } from "../shared/chat-content.js"; import { detectAssistantTranscriptRoleHeaderText } from "../shared/text/assistant-transcript-role-headers.js"; import { detectToolCallShapedText } from "../shared/text/tool-call-shaped-text.js"; import type { EmbeddedAgentSubscribeContext } from "./embedded-agent-subscribe.handlers.types.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; // Detect provider/model bugs where a reply serializes a tool call as plain // assistant text instead of emitting a structured invocation block. @@ -51,9 +51,9 @@ function isRegisteredToolName( if (!toolName || !registeredToolNames) { return undefined; } - const normalized = normalizeToolName(toolName); + const normalized = normalizeToolPolicyName(toolName); for (const registeredToolName of registeredToolNames) { - if (normalizeToolName(registeredToolName) === normalized) { + if (normalizeToolPolicyName(registeredToolName) === normalized) { return true; } } diff --git a/src/agents/embedded-agent-subscribe.tools.ts b/src/agents/embedded-agent-subscribe.tools.ts index 938e49c114c4..19515e6f2c08 100644 --- a/src/agents/embedded-agent-subscribe.tools.ts +++ b/src/agents/embedded-agent-subscribe.tools.ts @@ -28,7 +28,7 @@ import type { MessagingToolSend, MessagingToolSourceReplyPayload, } from "./embedded-agent-messaging.types.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { isToolResultError, readToolResultDetails, @@ -636,7 +636,7 @@ function isCoreToolResultMediaTrustedName(toolName?: string): boolean { if (!toolName) { return false; } - return TRUSTED_TOOL_RESULT_MEDIA.has(normalizeToolName(toolName)); + return TRUSTED_TOOL_RESULT_MEDIA.has(normalizeToolPolicyName(toolName)); } function isExternalToolResult(result: unknown): boolean { @@ -676,7 +676,7 @@ function isTrustedOwnedTtsLocalMedia( if ( !toolName || !isToolResultMediaTrusted(toolName, result, trustedLocalMediaToolNames) || - normalizeToolName(toolName) !== "tts" + normalizeToolPolicyName(toolName) !== "tts" ) { return false; } diff --git a/src/agents/harness/native-hook-relay-codec.ts b/src/agents/harness/native-hook-relay-codec.ts index efe3a878fae9..4e6947c6c88f 100644 --- a/src/agents/harness/native-hook-relay-codec.ts +++ b/src/agents/harness/native-hook-relay-codec.ts @@ -1,5 +1,5 @@ import { stableStringify } from "@openclaw/normalization-core"; -import { normalizeToolName } from "../tool-policy.js"; +import { normalizeToolPolicyName } from "../tool-policy.js"; import { codexNativeHookRelayResponseCodec } from "./native-hook-relay-response-codec.js"; import type { JsonValue, @@ -186,6 +186,6 @@ export function readNativeHookRelayApprovalMode(rawPayload: JsonValue): "report" } export function normalizeNativeHookToolName(toolName: string | undefined): string { - const normalized = normalizeToolName(toolName ?? "tool"); + const normalized = normalizeToolPolicyName(toolName ?? "tool"); return CODEX_NATIVE_HOOK_TOOL_NAME_ALIASES[normalized] ?? normalized; } diff --git a/src/agents/harness/selection.ts b/src/agents/harness/selection.ts index 2aff30f92e87..2c4e0a9fa0c8 100644 --- a/src/agents/harness/selection.ts +++ b/src/agents/harness/selection.ts @@ -33,7 +33,7 @@ import { resolveSandboxRuntimeStatus } from "../sandbox/runtime-status.js"; import { expandToolGroups, mergeAlsoAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, toolPolicyRestrictsTools, } from "../tool-policy.js"; import type { SystemAgentToolOptions } from "../tools/system-agent-tool.js"; @@ -681,7 +681,7 @@ async function runAgentHarnessOperation( } function isSystemAgentOnlyAllowlist(toolsAllow: readonly string[] | undefined): boolean { - return toolsAllow?.length === 1 && normalizeToolName(toolsAllow[0] ?? "") === "openclaw"; + return toolsAllow?.length === 1 && normalizeToolPolicyName(toolsAllow[0] ?? "") === "openclaw"; } function withoutHarnessSetupAuthority( @@ -816,7 +816,7 @@ function applyPluginHarnessDenyAllToolPolicy( if ( isHostScopedAgentToolActive("openclaw") && params.toolsAllow?.length === 1 && - normalizeToolName(params.toolsAllow[0] ?? "") === "openclaw" + normalizeToolPolicyName(params.toolsAllow[0] ?? "") === "openclaw" ) { return params; } @@ -983,7 +983,9 @@ function appendPluginHarnessToolPolicyPrompt(existing: string | undefined, promp } function policyDeniesAllTools(policy?: { deny?: string[] }): boolean { - return expandToolGroups(policy?.deny ?? []).some((entry) => normalizeToolName(entry) === "*"); + return expandToolGroups(policy?.deny ?? []).some( + (entry) => normalizeToolPolicyName(entry) === "*", + ); } function listHarnessCandidates(harnesses: AgentHarness[]): AgentHarnessSelectionCandidate[] { diff --git a/src/agents/inherited-tool-deny.ts b/src/agents/inherited-tool-deny.ts index 99c4753374b9..f6be73e9171f 100644 --- a/src/agents/inherited-tool-deny.ts +++ b/src/agents/inherited-tool-deny.ts @@ -3,7 +3,7 @@ */ import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { isToolAllowedByPolicyName } from "./tool-policy-match.js"; -import { normalizeToolName } from "./tool-policy-shared.js"; +import { normalizeToolPolicyName } from "./tool-policy-shared.js"; const ACP_UNSUPPORTED_INHERITED_TOOL_DENY = [ "apply_patch", @@ -37,7 +37,7 @@ export function normalizeInheritedToolDenylist(value: unknown): string[] { } return uniqueStrings( value.flatMap((entry) => { - const normalized = typeof entry === "string" ? normalizeToolName(entry) : ""; + const normalized = typeof entry === "string" ? normalizeToolPolicyName(entry) : ""; return normalized ? [normalized] : []; }), ); diff --git a/src/agents/internal-session-effects.test.ts b/src/agents/internal-session-effects.test.ts index 3b8bcfd05859..4c9443c5935d 100644 --- a/src/agents/internal-session-effects.test.ts +++ b/src/agents/internal-session-effects.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { appendTranscriptMessage, - listSessionEntries, + listSessionEntriesCore, loadExactSessionEntry, loadTranscriptEvents, persistSessionResetLifecycle, @@ -114,7 +114,7 @@ describe("internal session effects", () => { delivery: { kind: "internal" }, createdAt: expect.any(Number), }); - expect(listSessionEntries({ agentId: "main", storePath })).toEqual([]); + expect(listSessionEntriesCore({ agentId: "main", storePath })).toEqual([]); await expect(loadTranscriptEvents(target)).resolves.toEqual([ expect.objectContaining({ id: target.sessionId, type: "session" }), ]); @@ -173,7 +173,7 @@ describe("internal session effects", () => { type: "message", }), ); - expect(listSessionEntries({ agentId: "main", storePath })).toEqual([ + expect(listSessionEntriesCore({ agentId: "main", storePath })).toEqual([ expect.objectContaining({ sessionKey: source.sessionKey }), ]); }); diff --git a/src/agents/live-model-switch.test.ts b/src/agents/live-model-switch.test.ts index ac40428b9f68..bca6a1ec03f9 100644 --- a/src/agents/live-model-switch.test.ts +++ b/src/agents/live-model-switch.test.ts @@ -35,7 +35,7 @@ vi.mock("../config/sessions/session-accessor.js", () => { return { loadSessionEntry, loadSessionEntryReadOnly: loadSessionEntry, - patchSessionEntry: (...args: unknown[]) => state.updateSessionStoreMock(...args), + patchSessionEntryCore: (...args: unknown[]) => state.updateSessionStoreMock(...args), }; }); diff --git a/src/agents/live-model-switch.ts b/src/agents/live-model-switch.ts index 5fbee51a2f1a..92383ef9b73f 100644 --- a/src/agents/live-model-switch.ts +++ b/src/agents/live-model-switch.ts @@ -8,7 +8,7 @@ import { resolveStorePath } from "../config/sessions/paths.js"; import { loadSessionEntry, loadSessionEntryReadOnly, - patchSessionEntry, + patchSessionEntryCore, } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -275,7 +275,7 @@ export async function consolidateLiveModelSwitchAfterRun(params: { if (!storePath) { return; } - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, (entry) => { if (!entry.liveModelSwitchPending) { @@ -325,7 +325,7 @@ export async function clearLiveModelSwitchPending(params: { if (!storePath) { return; } - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, (entry) => { const next = { ...entry }; diff --git a/src/agents/local-model-lean.ts b/src/agents/local-model-lean.ts index e4f7d4e8f2b8..776a767a8286 100644 --- a/src/agents/local-model-lean.ts +++ b/src/agents/local-model-lean.ts @@ -9,7 +9,7 @@ import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.j import { resolveAgentConfig, resolveDefaultAgentId } from "./agent-scope-config.js"; import type { AnyAgentTool } from "./agent-tools.types.js"; import { compileGlobPatterns, matchesAnyGlobPattern } from "./glob-pattern.js"; -import { expandToolGroups, normalizeToolName } from "./tool-policy.js"; +import { expandToolGroups, normalizeToolPolicyName } from "./tool-policy.js"; import { AUTOMATIONS_TOOL_NAME } from "./tools/automations-tool-name.js"; const LOCAL_MODEL_LEAN_DENY_TOOL_NAMES = new Set([ @@ -34,8 +34,8 @@ function resolvePreservedLocalModelLeanToolNames(names?: Iterable) { return []; } return compileGlobPatterns({ - raw: expandToolGroups([...names]).filter((name) => normalizeToolName(name) !== "*"), - normalize: normalizeToolName, + raw: expandToolGroups([...names]).filter((name) => normalizeToolPolicyName(name) !== "*"), + normalize: normalizeToolPolicyName, }); } @@ -101,7 +101,7 @@ export function filterLocalModelLeanTools(params: { } const preservedToolNames = resolvePreservedLocalModelLeanToolNames(params.preserveToolNames); return params.tools.filter((tool) => { - const normalizedName = normalizeToolName(tool.name); + const normalizedName = normalizeToolPolicyName(tool.name); return ( matchesAnyGlobPattern(normalizedName, preservedToolNames) || !LOCAL_MODEL_LEAN_DENY_TOOL_NAMES.has(normalizedName) diff --git a/src/agents/main-session-recovery/main-session-recovery-store.test.ts b/src/agents/main-session-recovery/main-session-recovery-store.test.ts index 91ea5e15c35b..724d0267fe90 100644 --- a/src/agents/main-session-recovery/main-session-recovery-store.test.ts +++ b/src/agents/main-session-recovery/main-session-recovery-store.test.ts @@ -5,7 +5,7 @@ import type { InternalSessionEntry as SessionEntry } from "../../config/sessions import * as sessionAccessor from "../../config/sessions/session-accessor.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, } from "../../config/sessions/session-accessor.js"; import { getAgentEventLifecycleGeneration, @@ -58,7 +58,7 @@ describe("main session recovery store", () => { function readStore(): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey: key, entry }) => [key, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey: key, entry }) => [key, entry]), ); } diff --git a/src/agents/main-session-recovery/main-session-restart-recovery.test.ts b/src/agents/main-session-recovery/main-session-restart-recovery.test.ts index bc78b4c12aea..b87278f51afd 100644 --- a/src/agents/main-session-recovery/main-session-restart-recovery.test.ts +++ b/src/agents/main-session-recovery/main-session-restart-recovery.test.ts @@ -13,7 +13,7 @@ import type { InternalSessionEntry as SessionEntry } from "../../config/sessions import * as sessionAccessor from "../../config/sessions/session-accessor.js"; import { appendTranscriptMessage, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry as loadSessionEntryRaw, loadTranscriptEvents, replaceSessionEntry, @@ -363,7 +363,7 @@ async function writeMainSession({ function readStore(storePath: string): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), ); } diff --git a/src/agents/node-plugin-tools.ts b/src/agents/node-plugin-tools.ts index 81e4f7e40911..6574e2a42346 100644 --- a/src/agents/node-plugin-tools.ts +++ b/src/agents/node-plugin-tools.ts @@ -10,7 +10,7 @@ import { setPluginToolMeta } from "../plugins/tools.js"; import { sanitizeServerName } from "./agent-bundle-mcp-names.js"; import { compileGlobPatterns, matchesAnyGlobPattern } from "./glob-pattern.js"; import type { AgentToolResult } from "./runtime/index.js"; -import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, normalizeToolName } from "./tool-policy.js"; +import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, normalizeToolPolicyName } from "./tool-policy.js"; import { jsonResult } from "./tools/common.js"; import type { AnyAgentTool } from "./tools/common.js"; import { callGatewayTool } from "./tools/gateway.js"; @@ -65,7 +65,7 @@ function mapMcpPayloadToAgentToolResult(payload: unknown): AgentToolResult { - return new Set((values ?? []).map((value) => normalizeToolName(value)).filter(Boolean)); + return new Set((values ?? []).map((value) => normalizeToolPolicyName(value)).filter(Boolean)); } function toolPolicyAllows(params: { @@ -76,9 +76,9 @@ function toolPolicyAllows(params: { denylist: ReturnType; registered: boolean; }): boolean { - const pluginId = normalizeToolName(params.pluginId); - const toolName = normalizeToolName(params.toolName); - const exposedToolName = normalizeToolName(params.exposedToolName ?? params.toolName); + const pluginId = normalizeToolPolicyName(params.pluginId); + const toolName = normalizeToolPolicyName(params.toolName); + const exposedToolName = normalizeToolPolicyName(params.exposedToolName ?? params.toolName); if ( matchesAnyGlobPattern(pluginId, params.denylist) || matchesAnyGlobPattern(toolName, params.denylist) || @@ -152,7 +152,7 @@ function resolveUniqueToolName(params: { for (let index = 0; index < 100; index += 1) { const suffix = index === 0 ? "" : `_${index + 1}`; const candidate = prependToolNameFragment(params.baseName, nodeFragment, suffix); - const normalized = normalizeToolName(candidate); + const normalized = normalizeToolPolicyName(candidate); if ( isProviderSafeToolName(candidate) && normalized && @@ -171,19 +171,19 @@ export function createNodePluginTools(params: { agentSessionKey?: string; }): AnyAgentTool[] { const existingNormalized = new Set( - [...(params.existingToolNames ?? [])].map((name) => normalizeToolName(name)), + [...(params.existingToolNames ?? [])].map((name) => normalizeToolPolicyName(name)), ); const allowlist = normalizePolicyNames(params.toolAllowlist); const denylist = compileGlobPatterns({ raw: params.toolDenylist, - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); const entries: MaterializedNodeToolEntry[] = []; const nameCounts = new Map(); for (const entry of listConnectedNodePluginTools()) { const descriptor = entry.descriptor; const command = descriptor.command?.trim(); - const normalizedName = normalizeToolName(descriptor.name); + const normalizedName = normalizeToolPolicyName(descriptor.name); if (!command || !normalizedName) { continue; } @@ -216,7 +216,7 @@ export function createNodePluginTools(params: { ) { continue; } - existingNormalized.add(normalizeToolName(toolName)); + existingNormalized.add(normalizeToolPolicyName(toolName)); const mcpTool = descriptor.command === NODE_MCP_TOOLS_CALL_COMMAND ? descriptor.mcp : undefined; const tool: AnyAgentTool = { name: toolName, diff --git a/src/agents/run-session-target.ts b/src/agents/run-session-target.ts index 46324d65f1a5..3dea8a52b47c 100644 --- a/src/agents/run-session-target.ts +++ b/src/agents/run-session-target.ts @@ -4,7 +4,7 @@ import { getRuntimeConfig } from "../config/io.js"; import { parseSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; import { resolveStorePath } from "../config/sessions/paths.js"; import { - listSessionEntries, + listSessionEntriesCore, resolveTranscriptSessionKeyBySessionId, resolveSessionTranscriptRuntimeTarget, type SessionTranscriptRuntimeTarget, @@ -104,7 +104,7 @@ export async function resolveAgentRunSessionTarget(params: { (params.missingSessionKey === "create" ? plainCompatibilitySessionKey : undefined); const markerEntries = legacyMarker && !hasCompleteTypedTarget - ? listSessionEntries({ + ? listSessionEntriesCore({ agentId: legacyMarker.agentId, storePath: legacyMarker.storePath, }) diff --git a/src/agents/sandbox/tool-policy.ts b/src/agents/sandbox/tool-policy.ts index f21f16113f3a..2425bc272ab9 100644 --- a/src/agents/sandbox/tool-policy.ts +++ b/src/agents/sandbox/tool-policy.ts @@ -8,7 +8,7 @@ import { uniqueStrings } from "@openclaw/normalization-core/string-normalization import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { resolveAgentConfig } from "../agent-scope.js"; import { compileGlobPatterns, matchesAnyGlobPattern } from "../glob-pattern.js"; -import { expandToolGroups, normalizeToolName } from "../tool-policy.js"; +import { expandToolGroups, normalizeToolPolicyName } from "../tool-policy.js"; import { DEFAULT_TOOL_ALLOW, DEFAULT_TOOL_DENY } from "./constants.js"; import type { SandboxToolPolicy, @@ -157,13 +157,13 @@ function filterDefaultDenyForExplicitAllows(params: { } const allowPatterns = compileGlobPatterns({ raw: expandToolGroups(params.explicitAllowPatterns), - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); if (allowPatterns.length === 0) { return [...params.deny]; } return params.deny.filter( - (toolName) => !matchesAnyGlobPattern(normalizeToolName(toolName), allowPatterns), + (toolName) => !matchesAnyGlobPattern(normalizeToolPolicyName(toolName), allowPatterns), ); } @@ -197,15 +197,15 @@ export function classifyToolAgainstSandboxToolPolicy(name: string, policy?: Sand }; } - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); const deny = compileGlobPatterns({ raw: expandToolGroups(policy.deny ?? []), - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); const blockedByDeny = matchesAnyGlobPattern(normalized, deny); const allow = compileGlobPatterns({ raw: expandToolGroups(policy.allow ?? []), - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); const blockedByAllow = !blockedByDeny && allow.length > 0 && !matchesAnyGlobPattern(normalized, allow); diff --git a/src/agents/sandbox/workspace-authority.ts b/src/agents/sandbox/workspace-authority.ts index cb6fc6ce078f..cd652c490660 100644 --- a/src/agents/sandbox/workspace-authority.ts +++ b/src/agents/sandbox/workspace-authority.ts @@ -15,7 +15,7 @@ import { isToolAllowedByPolicies } from "../tool-policy-match.js"; import { expandToolGroups, mergeAlsoAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, resolveToolProfilePolicy, } from "../tool-policy.js"; import { resolveSandboxConfigForAgent } from "./config.js"; @@ -54,7 +54,7 @@ function findUnconfinedAllowedTool( } for (const entry of candidatePolicy.allow) { for (const candidate of expandToolGroups([entry])) { - const normalized = normalizeToolName(candidate); + const normalized = normalizeToolPolicyName(candidate); if (!isToolAllowedByPolicies(normalized, policies)) { continue; } @@ -216,14 +216,14 @@ export function resolveSandboxWorkspaceAuthority(params: { sandboxPolicy: sandbox.tools, }); const unavailableTool = (params.requiredToolNames ?? []) - .map(normalizeToolName) + .map(normalizeToolPolicyName) .find((name) => !isToolAllowedByPolicies(name, policies)); if (unavailableTool) { confinementError = `target tool policy blocks required tool ${unavailableTool}.`; } else { const unsafeTool = findUnconfinedAllowedTool( policies, - new Set((params.confinedToolNames ?? []).map(normalizeToolName)), + new Set((params.confinedToolNames ?? []).map(normalizeToolPolicyName)), ); if (unsafeTool) { confinementError = `target sandbox allows unclassified tool surface ${unsafeTool}.`; diff --git a/src/agents/session-model-auto-revert.ts b/src/agents/session-model-auto-revert.ts index 94bb907c09e5..6aaa87c79623 100644 --- a/src/agents/session-model-auto-revert.ts +++ b/src/agents/session-model-auto-revert.ts @@ -2,7 +2,7 @@ import { appendTranscriptMessage, loadSessionEntry, - patchSessionEntry, + patchSessionEntryCore, } from "../config/sessions/session-accessor.js"; import { createAgentPatchedSessionModelFallback, @@ -47,7 +47,7 @@ async function reconcileAgentPatchedSessionModel(params: { let note: string | undefined; let sessionId: string | undefined; let result: "cleared" | "promoted" | "reverted" | "none" = "none"; - await patchSessionEntry( + await patchSessionEntryCore( { agentId: params.agentId, sessionKey: params.sessionKey, diff --git a/src/agents/session-suspension.test.ts b/src/agents/session-suspension.test.ts index 1e80184b6d34..3485e536c0d5 100644 --- a/src/agents/session-suspension.test.ts +++ b/src/agents/session-suspension.test.ts @@ -6,7 +6,7 @@ import { CommandLane } from "../process/lanes.js"; import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; const sessionAccessorMocks = vi.hoisted(() => ({ - patchSessionEntry: vi.fn(), + patchSessionEntryCore: vi.fn(), })); const commandQueueMocks = vi.hoisted(() => ({ @@ -50,7 +50,7 @@ describe("session suspension", () => { const { resetSessionSuspensionStateForTest } = await import("./session-suspension.test-support.js"); resetSessionSuspensionStateForTest(); - sessionAccessorMocks.patchSessionEntry.mockClear(); + sessionAccessorMocks.patchSessionEntryCore.mockClear(); commandQueueMocks.setCommandLaneConcurrency.mockClear(); }); @@ -183,7 +183,7 @@ describe("session suspension", () => { vi.useFakeTimers(); const { setGatewayLaneResumeConcurrencies } = await import("./session-suspension.js"); let resolvePatch: (() => void) | undefined; - sessionAccessorMocks.patchSessionEntry.mockImplementationOnce(async (_scope, update) => { + sessionAccessorMocks.patchSessionEntryCore.mockImplementationOnce(async (_scope, update) => { await new Promise((resolve) => { resolvePatch = resolve; }); @@ -222,7 +222,7 @@ describe("session suspension", () => { await suspendLane(Number.MAX_SAFE_INTEGER, {} as OpenClawConfig, CommandLane.Main); expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); - const buildPatch = sessionAccessorMocks.patchSessionEntry.mock.calls[0]?.[1] as (_entry: { + const buildPatch = sessionAccessorMocks.patchSessionEntryCore.mock.calls[0]?.[1] as (_entry: { quotaSuspension?: unknown; }) => { quotaSuspension?: { expectedResumeBy?: number }; @@ -261,12 +261,12 @@ describe("session suspension", () => { expect(commandQueueMocks.setCommandLaneConcurrency).toHaveBeenCalledWith(CommandLane.Nested, 0); expect(clearSessionSuspensionTimers()).toBe(1); commandQueueMocks.setCommandLaneConcurrency.mockClear(); - sessionAccessorMocks.patchSessionEntry.mockClear(); + sessionAccessorMocks.patchSessionEntryCore.mockClear(); await suspendLane(100, {} as OpenClawConfig, CommandLane.Nested); expect(commandQueueMocks.setCommandLaneConcurrency).not.toHaveBeenCalled(); - expect(sessionAccessorMocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(sessionAccessorMocks.patchSessionEntryCore).not.toHaveBeenCalled(); enableSessionSuspensionTimersForGatewayStart(); await suspendLane(100, {} as OpenClawConfig, CommandLane.Nested); @@ -378,7 +378,7 @@ describe("session suspension", () => { laneId?: string; } | undefined; - sessionAccessorMocks.patchSessionEntry.mockImplementationOnce(async (_scope, update) => { + sessionAccessorMocks.patchSessionEntryCore.mockImplementationOnce(async (_scope, update) => { await new Promise((resolve) => { resolvePatch = resolve; }); @@ -400,7 +400,7 @@ describe("session suspension", () => { expect(commandQueueMocks.setCommandLaneConcurrency).not.toHaveBeenCalled(); expect(writtenQuotaSuspension).toBeUndefined(); - expect(sessionAccessorMocks.patchSessionEntry).toHaveBeenCalledOnce(); + expect(sessionAccessorMocks.patchSessionEntryCore).toHaveBeenCalledOnce(); await vi.advanceTimersByTimeAsync(100); @@ -410,7 +410,7 @@ describe("session suspension", () => { it("does not let a pending suspension regain ownership after test state resets", async () => { let resolvePatch: (() => void) | undefined; let writtenQuotaSuspension: unknown; - sessionAccessorMocks.patchSessionEntry.mockImplementationOnce(async (_scope, update) => { + sessionAccessorMocks.patchSessionEntryCore.mockImplementationOnce(async (_scope, update) => { await new Promise((resolve) => { resolvePatch = resolve; }); @@ -452,7 +452,7 @@ describe("session suspension", () => { const initialWritesReleased = new Promise((resolve) => { releaseInitialWrites = resolve; }); - sessionAccessorMocks.patchSessionEntry.mockImplementation(async (_scope, update) => { + sessionAccessorMocks.patchSessionEntryCore.mockImplementation(async (_scope, update) => { const patch = update(storeEntry) as typeof storeEntry | null; if (patch && "quotaSuspension" in patch) { storeEntry = @@ -481,7 +481,7 @@ describe("session suspension", () => { it("still throttles the lane when persistence fails while gateway is active", async () => { vi.useFakeTimers(); - sessionAccessorMocks.patchSessionEntry.mockRejectedValueOnce(new Error("disk busy")); + sessionAccessorMocks.patchSessionEntryCore.mockRejectedValueOnce(new Error("disk busy")); await suspendLane( 100, diff --git a/src/agents/session-suspension.ts b/src/agents/session-suspension.ts index 3f518b1e4aea..09beef9dfe0f 100644 --- a/src/agents/session-suspension.ts +++ b/src/agents/session-suspension.ts @@ -6,7 +6,7 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { resolveAgentMaxConcurrent, resolveSubagentMaxConcurrent } from "../config/agent-limits.js"; import { resolveCronMaxConcurrentRuns } from "../config/cron-limits.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { QuotaSuspension } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; @@ -374,7 +374,7 @@ async function suspendSessionQueued(params: SessionSuspensionParams, queuedGener let persistedSuspension: boolean; try { - const patchedEntry = await patchSessionEntry( + const patchedEntry = await patchSessionEntryCore( { storePath, sessionKey }, (entry) => { if (getSessionSuspensionState().cleanupGeneration !== suspensionGeneration) { @@ -423,7 +423,7 @@ async function suspendSessionQueued(params: SessionSuspensionParams, queuedGener (postPatchState.cleanupActive || suspensionGeneration !== postPatchState.cleanupGeneration) ) { try { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, (entry) => entry.quotaSuspension?.suspendedAt === now && diff --git a/src/agents/sessions/prompt-templates.ts b/src/agents/sessions/prompt-templates.ts index c9d777366d24..93995fe3830f 100644 --- a/src/agents/sessions/prompt-templates.ts +++ b/src/agents/sessions/prompt-templates.ts @@ -12,7 +12,7 @@ import { */ import { expandTildePath } from "../../shared/tilde-path.js"; import { CONFIG_DIR_NAME } from "../config.js"; -import { parseFrontmatter } from "../utils/frontmatter.js"; +import { parsePromptFrontmatter } from "../utils/frontmatter.js"; import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js"; /** @@ -30,7 +30,7 @@ export interface PromptTemplate { function loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null { try { const rawContent = readFileSync(filePath, "utf-8"); - const { frontmatter, body } = parseFrontmatter>(rawContent); + const { frontmatter, body } = parsePromptFrontmatter>(rawContent); const name = basename(filePath).replace(/\.md$/, ""); diff --git a/src/agents/subagents/announce/subagent-announce-output.test-support.ts b/src/agents/subagents/announce/subagent-announce-output.test-support.ts index e31434c2dfd8..56738b57967c 100644 --- a/src/agents/subagents/announce/subagent-announce-output.test-support.ts +++ b/src/agents/subagents/announce/subagent-announce-output.test-support.ts @@ -5,7 +5,7 @@ type OutputDeps = Pick< OutputRuntime, | "callGateway" | "getRuntimeConfig" - | "readSessionEntry" + | "readSubagentSessionEntry" | "readSessionMessagesAsync" | "resolveAgentIdFromSessionKey" | "resolveStorePath" diff --git a/src/agents/subagents/announce/subagent-announce-output.test.ts b/src/agents/subagents/announce/subagent-announce-output.test.ts index 54abec685139..ef3fadb76f0f 100644 --- a/src/agents/subagents/announce/subagent-announce-output.test.ts +++ b/src/agents/subagents/announce/subagent-announce-output.test.ts @@ -12,7 +12,7 @@ import { type CallGateway = typeof import("../../../gateway/call.js").callGateway; type GetRuntimeConfig = typeof import("./subagent-announce.runtime.js").getRuntimeConfig; -type ReadSessionEntry = typeof import("./subagent-announce.runtime.js").readSessionEntry; +type ReadSessionEntry = typeof import("./subagent-announce.runtime.js").readSubagentSessionEntry; type ReadSessionMessagesAsync = typeof import("./subagent-announce.runtime.js").readSessionMessagesAsync; type ResolveAgentIdFromSessionKey = @@ -89,7 +89,7 @@ describe("buildCompactAnnounceStatsLine", () => { it("rolls one-decimal thousand token stats over to the million unit", async () => { testing.setDepsForTest({ getRuntimeConfig: (() => ({ session: { store: "memory" } })) as GetRuntimeConfig, - readSessionEntry: (() => ({ + readSubagentSessionEntry: (() => ({ sessionId: "child-session", updatedAt: 0, inputTokens: 999_999, diff --git a/src/agents/subagents/announce/subagent-announce-output.ts b/src/agents/subagents/announce/subagent-announce-output.ts index 825637b578f8..2ad939926584 100644 --- a/src/agents/subagents/announce/subagent-announce-output.ts +++ b/src/agents/subagents/announce/subagent-announce-output.ts @@ -25,7 +25,7 @@ import { import { callGateway, getRuntimeConfig, - readSessionEntry, + readSubagentSessionEntry, readSessionMessagesAsync, resolveAgentIdFromSessionKey, resolveStorePath, @@ -47,7 +47,7 @@ const ASSISTANT_TOOL_CALL_BLOCK_TYPES = new Set([ type SubagentAnnounceOutputDeps = { callGateway: typeof callGateway; getRuntimeConfig: typeof getRuntimeConfig; - readSessionEntry: typeof readSessionEntry; + readSubagentSessionEntry: typeof readSubagentSessionEntry; readSessionMessagesAsync: typeof readSessionMessagesAsync; resolveAgentIdFromSessionKey: typeof resolveAgentIdFromSessionKey; resolveStorePath: typeof resolveStorePath; @@ -56,7 +56,7 @@ type SubagentAnnounceOutputDeps = { const defaultSubagentAnnounceOutputDeps: SubagentAnnounceOutputDeps = { callGateway, getRuntimeConfig, - readSessionEntry, + readSubagentSessionEntry, readSessionMessagesAsync, resolveAgentIdFromSessionKey, resolveStorePath, @@ -650,7 +650,7 @@ export async function buildCompactAnnounceStatsLine(params: { const cfg = subagentAnnounceOutputDeps.getRuntimeConfig(); const agentId = subagentAnnounceOutputDeps.resolveAgentIdFromSessionKey(params.sessionKey); const storePath = subagentAnnounceOutputDeps.resolveStorePath(cfg.session?.store, { agentId }); - let entry = subagentAnnounceOutputDeps.readSessionEntry(storePath, params.sessionKey); + let entry = subagentAnnounceOutputDeps.readSubagentSessionEntry(storePath, params.sessionKey); const tokenWaitAttempts = isFastTestMode() ? 1 : 3; for (let attempt = 0; attempt < tokenWaitAttempts; attempt += 1) { const hasTokenData = @@ -665,7 +665,7 @@ export async function buildCompactAnnounceStatsLine(params: { setTimeout(resolve, 150); }); } - entry = subagentAnnounceOutputDeps.readSessionEntry(storePath, params.sessionKey); + entry = subagentAnnounceOutputDeps.readSubagentSessionEntry(storePath, params.sessionKey); } const input = typeof entry?.inputTokens === "number" ? entry.inputTokens : 0; diff --git a/src/agents/subagents/announce/subagent-announce.format.e2e.test.ts b/src/agents/subagents/announce/subagent-announce.format.e2e.test.ts index 1a8577dccf98..3da83d5e0f23 100644 --- a/src/agents/subagents/announce/subagent-announce.format.e2e.test.ts +++ b/src/agents/subagents/announce/subagent-announce.format.e2e.test.ts @@ -430,7 +430,7 @@ describe("subagent announce formatting", () => { req: Parameters[0], ) => (await callGatewaySpy(req)) as T, getRuntimeConfig: () => configOverride, - readSessionEntry: (_storePath, sessionKey) => loadSessionStoreFixture()[sessionKey], + readSubagentSessionEntry: (_storePath, sessionKey) => loadSessionStoreFixture()[sessionKey], resolveAgentIdFromSessionKey: () => "main", resolveStorePath: () => "/tmp/sessions.json", }); diff --git a/src/agents/subagents/announce/subagent-announce.requester-settle-wake.test.ts b/src/agents/subagents/announce/subagent-announce.requester-settle-wake.test.ts index bf1fce02ba97..6b03342f1aba 100644 --- a/src/agents/subagents/announce/subagent-announce.requester-settle-wake.test.ts +++ b/src/agents/subagents/announce/subagent-announce.requester-settle-wake.test.ts @@ -44,7 +44,7 @@ vi.mock("./subagent-announce.runtime.js", () => ({ getRuntimeConfig: () => ({ session: { mainKey: "main", scope: "per-sender" } }), loadSessionStore: vi.fn(() => ({})), readSessionMessagesAsync: vi.fn(async () => []), - readSessionEntry: vi.fn(() => undefined), + readSubagentSessionEntry: vi.fn(() => undefined), resolveAgentIdFromSessionKey: vi.fn(() => "main"), resolveMainSessionKey: vi.fn(() => "agent:main:main"), resolveStorePath: vi.fn(() => "/tmp/sessions.json"), diff --git a/src/agents/subagents/announce/subagent-announce.runtime.ts b/src/agents/subagents/announce/subagent-announce.runtime.ts index e7d5aaca08b1..8b40642f5346 100644 --- a/src/agents/subagents/announce/subagent-announce.runtime.ts +++ b/src/agents/subagents/announce/subagent-announce.runtime.ts @@ -8,7 +8,7 @@ export { getRuntimeConfig } from "../../../config/config.js"; export { resolveAgentIdFromSessionKey, resolveStorePath } from "../../../config/sessions.js"; import { loadSessionEntry } from "../../../config/sessions/session-accessor.js"; -export function readSessionEntry(storePath: string, sessionKey: string) { +export function readSubagentSessionEntry(storePath: string, sessionKey: string) { return loadSessionEntry({ storePath, sessionKey }); } export { callGateway } from "../../../gateway/call.js"; diff --git a/src/agents/subagents/announce/subagent-announce.test.ts b/src/agents/subagents/announce/subagent-announce.test.ts index e42ea08079fc..5e1c6425b222 100644 --- a/src/agents/subagents/announce/subagent-announce.test.ts +++ b/src/agents/subagents/announce/subagent-announce.test.ts @@ -71,7 +71,7 @@ vi.mock("./subagent-announce.runtime.js", () => ({ getRuntimeConfig: () => mockConfig, loadSessionStore: (storePath: string) => loadSessionStoreMock(storePath), readSessionMessagesAsync: vi.fn(async () => []), - readSessionEntry: (storePath: string, sessionKey: string) => + readSubagentSessionEntry: (storePath: string, sessionKey: string) => (loadSessionStoreMock(storePath) as Record)[sessionKey], resolveAgentIdFromSessionKey: (sessionKey: string) => resolveAgentIdFromSessionKeyMock(sessionKey), diff --git a/src/agents/subagents/announce/subagent-announce.timeout.test.ts b/src/agents/subagents/announce/subagent-announce.timeout.test.ts index d11e934b2dc7..428c4b8c6ea0 100644 --- a/src/agents/subagents/announce/subagent-announce.timeout.test.ts +++ b/src/agents/subagents/announce/subagent-announce.timeout.test.ts @@ -188,7 +188,7 @@ vi.mock("./subagent-announce.runtime.js", () => ({ getRuntimeConfig: () => configOverride, loadSessionStore: vi.fn(() => sessionStore), readSessionMessagesAsync: vi.fn(async () => []), - readSessionEntry: (_storePath: string, sessionKey: string) => sessionStore[sessionKey], + readSubagentSessionEntry: (_storePath: string, sessionKey: string) => sessionStore[sessionKey], resolveAgentIdFromSessionKey: () => "main", resolveStorePath: () => "/tmp/sessions-main.json", resolveMainSessionKey: () => "agent:main:main", diff --git a/src/agents/subagents/registry/subagent-control.test-support.ts b/src/agents/subagents/registry/subagent-control.test-support.ts index 351a7d018b4d..34535e750d6a 100644 --- a/src/agents/subagents/registry/subagent-control.test-support.ts +++ b/src/agents/subagents/registry/subagent-control.test-support.ts @@ -3,7 +3,7 @@ export * from "./subagent-control.js"; type ControlRuntime = typeof import("./subagent-control.runtime.js"); type ControlDeps = { callGateway: typeof import("../../../gateway/call.js").callGateway; - patchSessionEntry: typeof import("../../../config/sessions/session-accessor.js").patchSessionEntry; + patchSessionEntryCore: typeof import("../../../config/sessions/session-accessor.js").patchSessionEntryCore; abortEmbeddedAgentRun: ControlRuntime["abortEmbeddedAgentRun"]; isEmbeddedAgentRunActive: ControlRuntime["isEmbeddedAgentRunActive"]; clearSessionQueues: ControlRuntime["clearSessionQueues"]; diff --git a/src/agents/subagents/registry/subagent-control.test.ts b/src/agents/subagents/registry/subagent-control.test.ts index 34bf8cb91b43..33b697ee3887 100644 --- a/src/agents/subagents/registry/subagent-control.test.ts +++ b/src/agents/subagents/registry/subagent-control.test.ts @@ -167,7 +167,7 @@ function setSubagentControlDepsForTest( abortEmbeddedAgentRun: () => false, isEmbeddedAgentRunActive: () => false, clearSessionQueues: () => ({ followupCleared: 0, laneCleared: 0, keys: [] }), - patchSessionEntry: async ( + patchSessionEntryCore: async ( scope: SessionAccessScope, patcher: ( entry: SessionEntry, @@ -811,7 +811,7 @@ describe("killSubagentRunAdmin", () => { }); return true; }, - patchSessionEntry: async (_scope, patcher) => { + patchSessionEntryCore: async (_scope, patcher) => { const current = { sessionId: "sess-abort-lifecycle-race", updatedAt: Date.now() }; const patch = await patcher(current, { existingEntry: { ...current } }); abortedLastRunWrites.push(patch?.abortedLastRun === true); @@ -878,7 +878,7 @@ describe("killSubagentRunAdmin", () => { }); return true; }, - patchSessionEntry: async (_scope, patcher) => { + patchSessionEntryCore: async (_scope, patcher) => { const current = { sessionId: "sess-completion-race", updatedAt: Date.now() }; const patch = await patcher(current, { existingEntry: { ...current } }); abortedLastRunWrites.push(patch?.abortedLastRun === true); @@ -953,7 +953,7 @@ describe("killSubagentRunAdmin", () => { setSubagentControlDepsForTest({ isEmbeddedAgentRunActive: () => true, abortEmbeddedAgentRun: () => true, - patchSessionEntry: async (scope, patcher) => { + patchSessionEntryCore: async (scope, patcher) => { if (!scope.storePath) { return null; } @@ -1032,7 +1032,7 @@ describe("killSubagentRunAdmin", () => { run.pauseReason = "sessions_yield"; return true; }, - patchSessionEntry: async () => { + patchSessionEntryCore: async () => { return null; }, }); @@ -1471,7 +1471,7 @@ describe("killControlledSubagentRun", () => { isEmbeddedAgentRunActive: () => false, abortEmbeddedAgentRun: abort, clearSessionQueues: clearQueues, - patchSessionEntry: async (_scope, patcher) => { + patchSessionEntryCore: async (_scope, patcher) => { markPersistenceStarted(); await persistenceRelease; const current = { sessionId: "sess-persist-generation-race", updatedAt: Date.now() }; @@ -1624,7 +1624,7 @@ describe("killControlledSubagentRun", () => { addSubagentRunForTests(entry); const patches: Array | null> = []; setSubagentControlDepsForTest({ - patchSessionEntry: async (_scope, patcher) => { + patchSessionEntryCore: async (_scope, patcher) => { const replacement: SessionEntry = { sessionId: "sess-kill-session-patch-reset", lifecycleRevision: "revision-after-reset", @@ -2079,7 +2079,7 @@ describe("killControlledSubagentRun", () => { const abortedLastRunWrites: boolean[] = []; let persistenceWrites = 0; setSubagentControlDepsForTest({ - patchSessionEntry: async (_scope, patcher) => { + patchSessionEntryCore: async (_scope, patcher) => { const current = { sessionId, updatedAt: 1, abortedLastRun: true }; const patch = await patcher(current, { existingEntry: { ...current } }); if (patch) { diff --git a/src/agents/subagents/registry/subagent-control.ts b/src/agents/subagents/registry/subagent-control.ts index 2eabfb063e9b..ff99fe15ff79 100644 --- a/src/agents/subagents/registry/subagent-control.ts +++ b/src/agents/subagents/registry/subagent-control.ts @@ -6,7 +6,10 @@ import { sortSubagentRuns, } from "../../../auto-reply/reply/subagents-utils.js"; import { resolveStorePath } from "../../../config/sessions/paths.js"; -import { loadSessionEntry, patchSessionEntry } from "../../../config/sessions/session-accessor.js"; +import { + loadSessionEntry, + patchSessionEntryCore, +} from "../../../config/sessions/session-accessor.js"; import type { SessionEntry } from "../../../config/sessions/types.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { callGateway } from "../../../gateway/call.js"; @@ -75,7 +78,7 @@ const SUBAGENT_REPLY_HISTORY_LIMIT = 50; const steerRateLimit = new Map(); type GatewayCaller = typeof callGateway; -type PatchSessionEntry = typeof patchSessionEntry; +type PatchSessionEntry = typeof patchSessionEntryCore; type AbortEmbeddedAgentRun = (sessionId: string) => boolean; type IsEmbeddedAgentRunActive = (sessionId: string) => boolean; type ClearSessionQueues = (keys: Array) => ClearSessionQueueResult; @@ -99,12 +102,12 @@ const callSubagentControlGateway: GatewayCaller = async (request) => { const defaultSubagentControlDeps = { callGateway: callSubagentControlGateway, - patchSessionEntry, + patchSessionEntryCore, }; let subagentControlDeps: { callGateway: GatewayCaller; - patchSessionEntry: PatchSessionEntry; + patchSessionEntryCore: PatchSessionEntry; abortEmbeddedAgentRun?: AbortEmbeddedAgentRun; isEmbeddedAgentRunActive?: IsEmbeddedAgentRunActive; clearSessionQueues?: ClearSessionQueues; @@ -308,7 +311,7 @@ async function persistSubagentAbortedLastRun(params: { return true; } try { - await subagentControlDeps.patchSessionEntry( + await subagentControlDeps.patchSessionEntryCore( { storePath: params.storePath, sessionKey: params.childSessionKey }, (current) => current.sessionId !== params.expectedSessionId || @@ -1355,7 +1358,7 @@ const testing = { setDepsForTest( overrides?: Partial<{ callGateway: GatewayCaller; - patchSessionEntry: PatchSessionEntry; + patchSessionEntryCore: PatchSessionEntry; abortEmbeddedAgentRun: AbortEmbeddedAgentRun; isEmbeddedAgentRunActive: IsEmbeddedAgentRunActive; clearSessionQueues: ClearSessionQueues; diff --git a/src/agents/subagents/registry/subagent-registry-helpers.ts b/src/agents/subagents/registry/subagent-registry-helpers.ts index a9314364d016..cebedec8c661 100644 --- a/src/agents/subagents/registry/subagent-registry-helpers.ts +++ b/src/agents/subagents/registry/subagent-registry-helpers.ts @@ -9,7 +9,7 @@ import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES } from "../../../config/agent-limits.js"; import { getRuntimeConfig } from "../../../config/config.js"; import { resolveAgentIdFromSessionKey, resolveStorePath } from "../../../config/sessions.js"; -import { patchSessionEntry } from "../../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import { computeBackoff } from "../../../infra/backoff.js"; import { defaultRuntime } from "../../../runtime.js"; @@ -120,7 +120,7 @@ export async function persistSubagentSessionTiming( : getSubagentSessionRuntimeMs(entry); const status = resolveSubagentSessionStatus(entry); - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey: childSessionKey }, (sessionEntry) => { // Recheck under the session-store write lock. A completion may have diff --git a/src/agents/subagents/registry/subagent-registry-restart-recovery-session.ts b/src/agents/subagents/registry/subagent-registry-restart-recovery-session.ts index 3552b2a8a981..8dd0e10aa613 100644 --- a/src/agents/subagents/registry/subagent-registry-restart-recovery-session.ts +++ b/src/agents/subagents/registry/subagent-registry-restart-recovery-session.ts @@ -1,4 +1,4 @@ -import { patchSessionEntry } from "../../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../../config/sessions/session-accessor.js"; export async function settleAcceptedRecoverySession(params: { attempts: number; @@ -11,7 +11,7 @@ export async function settleAcceptedRecoverySession(params: { storePath: string; }): Promise { let settled = false; - await patchSessionEntry( + await patchSessionEntryCore( { storePath: params.storePath, sessionKey: params.childSessionKey }, (current) => { if ( diff --git a/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts b/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts index 68b8c148c058..f783193183a6 100644 --- a/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts +++ b/src/agents/subagents/registry/subagent-registry-restart-recovery.test.ts @@ -17,7 +17,7 @@ import type { SubagentRunRecord } from "./subagent-registry.types.js"; const mocks = vi.hoisted(() => ({ entries: {} as Record, loadSessionEntry: vi.fn(), - patchSessionEntry: vi.fn(), + patchSessionEntryCore: vi.fn(), readSessionMessages: vi.fn(async () => [] as unknown[]), })); @@ -30,7 +30,7 @@ vi.mock("../../../config/sessions.js", () => ({ })); vi.mock("../../../config/sessions/session-accessor.js", () => ({ loadSessionEntry: mocks.loadSessionEntry, - patchSessionEntry: mocks.patchSessionEntry, + patchSessionEntryCore: mocks.patchSessionEntryCore, })); vi.mock("../../../gateway/session-transcript-readers.js", () => ({ extractMessageRole: (message: { role?: string }) => message?.role, @@ -160,7 +160,7 @@ describe("subagent registry restart recovery", () => { mocks.loadSessionEntry.mockImplementation( ({ sessionKey }: { sessionKey: string }) => mocks.entries[sessionKey], ); - mocks.patchSessionEntry.mockImplementation( + mocks.patchSessionEntryCore.mockImplementation( async ( { sessionKey }: { sessionKey: string }, update: (entry: SessionEntry) => SessionEntry, @@ -363,7 +363,7 @@ describe("subagent registry restart recovery", () => { status: "retry", error: "runtime not ready", }); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); expect(mocks.entries[childSessionKey]!.abortedLastRun).toBe(true); }); @@ -374,7 +374,7 @@ describe("subagent registry restart recovery", () => { status: "retry", error: "undefined", }); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); expect(mocks.entries[childSessionKey]!.abortedLastRun).toBe(true); }); @@ -570,7 +570,7 @@ describe("subagent registry restart recovery", () => { }); expect(markLaunchAccepted).not.toHaveBeenCalled(); expect(replaceRun).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); }); it("does not create a successor when Gateway consumes admission but rejects launch", async () => { @@ -599,7 +599,7 @@ describe("subagent registry restart recovery", () => { }); expect(markLaunchAccepted).not.toHaveBeenCalled(); expect(replaceRun).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); }); it("keeps accepted source ownership when durable remap fails before settlement", async () => { @@ -608,7 +608,7 @@ describe("subagent registry restart recovery", () => { await expect(recover(entry)).resolves.toEqual({ status: "deferred" }); expect(dispatchAgent).toHaveBeenCalledOnce(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); expect(mocks.entries[childSessionKey]!.abortedLastRun).toBe(true); expect(entry.execution.restartRecovery).toMatchObject({ idempotencyKey: expect.stringMatching(/^subagent-recovery:[a-f0-9]{64}$/), @@ -627,7 +627,7 @@ describe("subagent registry restart recovery", () => { entry.execution.restartRecovery = params.restartRecovery; return true; }); - mocks.patchSessionEntry.mockRejectedValueOnce(new Error("store unavailable")); + mocks.patchSessionEntryCore.mockRejectedValueOnce(new Error("store unavailable")); await expect(recover(entry)).resolves.toEqual({ status: "deferred", @@ -694,7 +694,7 @@ describe("subagent registry restart recovery", () => { expect(markLaunchConsumed).toHaveBeenCalledOnce(); expect(markLaunchAccepted).not.toHaveBeenCalled(); expect(replaceRun).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); }); it("resets an unconsumed attempt after its Gateway lifecycle retires", async () => { @@ -713,7 +713,7 @@ describe("subagent registry restart recovery", () => { expect(markLaunchConsumed).not.toHaveBeenCalled(); expect(markLaunchAccepted).not.toHaveBeenCalled(); expect(replaceRun).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); }); it("preserves a newer restart marker when the lifecycle retires during settlement", async () => { @@ -730,7 +730,7 @@ describe("subagent registry restart recovery", () => { params.expected.execution.restartRecovery = accepted; return accepted; }); - mocks.patchSessionEntry.mockImplementationOnce( + mocks.patchSessionEntryCore.mockImplementationOnce( async ( { sessionKey }: { sessionKey: string }, update: (entry: SessionEntry) => SessionEntry, @@ -786,7 +786,7 @@ describe("subagent registry restart recovery", () => { }), ); expect(dispatchAgent).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).toHaveBeenCalledOnce(); + expect(mocks.patchSessionEntryCore).toHaveBeenCalledOnce(); expect(mocks.entries[childSessionKey]!.abortedLastRun).toBe(false); }); @@ -809,25 +809,27 @@ describe("subagent registry restart recovery", () => { runs.set(entry.runId, entry); return true; }); - mocks.patchSessionEntry.mockImplementationOnce(async ({ sessionKey }, update, options) => { - const next = update({ ...mocks.entries[sessionKey]! }); - const advanceOwner = () => { - const newer = run({ - runId: "newer-accepted-run", - generation: (entry.generation ?? 0) + 1, - }); - runs.set(newer.runId, newer); - }; - if (beforeCommit) { - advanceOwner(); - } - options?.assertCommitAllowed?.(); - mocks.entries[sessionKey] = next; - if (!beforeCommit) { - advanceOwner(); - } - return next; - }); + mocks.patchSessionEntryCore.mockImplementationOnce( + async ({ sessionKey }, update, options) => { + const next = update({ ...mocks.entries[sessionKey]! }); + const advanceOwner = () => { + const newer = run({ + runId: "newer-accepted-run", + generation: (entry.generation ?? 0) + 1, + }); + runs.set(newer.runId, newer); + }; + if (beforeCommit) { + advanceOwner(); + } + options?.assertCommitAllowed?.(); + mocks.entries[sessionKey] = next; + if (!beforeCommit) { + advanceOwner(); + } + return next; + }, + ); await expect( recover(entry, { @@ -866,7 +868,7 @@ describe("subagent registry restart recovery", () => { expect(dispatchAgent).not.toHaveBeenCalled(); expect(replaceRun).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); expect(clearAcceptedRecovery).not.toHaveBeenCalled(); expect(resumeAcceptedRecovery).not.toHaveBeenCalled(); expect(mocks.entries[childSessionKey]).toMatchObject({ @@ -911,7 +913,7 @@ describe("subagent registry restart recovery", () => { expect(replaceRun).toHaveBeenCalledOnce(); expect(dispatchAgent).not.toHaveBeenCalled(); - expect(mocks.patchSessionEntry).not.toHaveBeenCalled(); + expect(mocks.patchSessionEntryCore).not.toHaveBeenCalled(); expect(clearAcceptedRecovery).not.toHaveBeenCalled(); expect(successor.execution.restartRecovery).toMatchObject({ phase: "accepted" }); }); diff --git a/src/agents/subagents/registry/subagent-registry-restart-recovery.ts b/src/agents/subagents/registry/subagent-registry-restart-recovery.ts index d4a6fa462336..a967f65b0662 100644 --- a/src/agents/subagents/registry/subagent-registry-restart-recovery.ts +++ b/src/agents/subagents/registry/subagent-registry-restart-recovery.ts @@ -1,6 +1,9 @@ import { getRuntimeConfig } from "../../../config/config.js"; import { resolveAgentIdFromSessionKey, resolveStorePath } from "../../../config/sessions.js"; -import { loadSessionEntry, patchSessionEntry } from "../../../config/sessions/session-accessor.js"; +import { + loadSessionEntry, + patchSessionEntryCore, +} from "../../../config/sessions/session-accessor.js"; import type { GatewayRecoveryRuntime } from "../../../gateway/server-instance-runtime.types.js"; import { extractMessageRole, @@ -406,7 +409,7 @@ export async function recoverInterruptedSubagentRow( if (blockedReason) { if (!alreadyWedged) { try { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey: childSessionKey }, (current) => { current.abortedLastRun = false; diff --git a/src/agents/subagents/registry/subagent-registry.announce-loop-guard.test.ts b/src/agents/subagents/registry/subagent-registry.announce-loop-guard.test.ts index 531dceb9bc03..3551f944ee99 100644 --- a/src/agents/subagents/registry/subagent-registry.announce-loop-guard.test.ts +++ b/src/agents/subagents/registry/subagent-registry.announce-loop-guard.test.ts @@ -42,16 +42,16 @@ vi.mock("../../../config/sessions.js", () => ({ })); vi.mock("../../../config/sessions/session-accessor.js", () => { - const listSessionEntries = () => + const listSessionEntriesCore = () => Object.entries(sessionStore).map(([sessionKey, entry]) => ({ sessionKey, entry })); const loadSessionEntry = (scope: { sessionKey: keyof typeof sessionStore }) => sessionStore[scope.sessionKey]; return { - listSessionEntries, - listSessionEntriesReadOnly: listSessionEntries, + listSessionEntriesCore, + listSessionEntriesReadOnly: listSessionEntriesCore, loadSessionEntry, loadSessionEntryReadOnly: loadSessionEntry, - patchSessionEntry: async () => null, + patchSessionEntryCore: async () => null, }; }); diff --git a/src/agents/subagents/registry/subagent-registry.lifecycle-retry-grace.e2e.test.ts b/src/agents/subagents/registry/subagent-registry.lifecycle-retry-grace.e2e.test.ts index 1f72f18f92d2..a3876f7b25a5 100644 --- a/src/agents/subagents/registry/subagent-registry.lifecycle-retry-grace.e2e.test.ts +++ b/src/agents/subagents/registry/subagent-registry.lifecycle-retry-grace.e2e.test.ts @@ -201,7 +201,7 @@ describe("subagent registry lifecycle error grace", () => { callGateway: callGatewayMock as typeof import("../../../gateway/call.js").callGateway, getRuntimeConfig: loadConfigMock as typeof import("../../../config/config.js").getRuntimeConfig, - readSessionEntry: (_storePath, sessionKey) => sessionStore[sessionKey], + readSubagentSessionEntry: (_storePath, sessionKey) => sessionStore[sessionKey], resolveAgentIdFromSessionKey: (key) => key?.match(/^agent:([^:]+)/)?.[1] ?? "main", resolveStorePath: () => "/tmp/test-store", }); diff --git a/src/agents/subagents/registry/subagent-registry.persistence.test-support.ts b/src/agents/subagents/registry/subagent-registry.persistence.test-support.ts index 839693af8403..43a6706b7ec3 100644 --- a/src/agents/subagents/registry/subagent-registry.persistence.test-support.ts +++ b/src/agents/subagents/registry/subagent-registry.persistence.test-support.ts @@ -8,7 +8,7 @@ import { vi } from "vitest"; import type { SessionEntry } from "../../../config/sessions.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceSessionEntry, } from "../../../config/sessions/session-accessor.js"; @@ -58,7 +58,7 @@ export function canonicalSubagentRunFixtures( /** Reads test session entries through the active SQLite accessor. */ export async function readSubagentSessionStore(storePath: string): Promise { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), ) as unknown as SessionStore; } diff --git a/src/agents/subagents/registry/subagent-registry.steer-restart.test.ts b/src/agents/subagents/registry/subagent-registry.steer-restart.test.ts index b4ef91c2dc7a..505863b5498d 100644 --- a/src/agents/subagents/registry/subagent-registry.steer-restart.test.ts +++ b/src/agents/subagents/registry/subagent-registry.steer-restart.test.ts @@ -89,7 +89,7 @@ vi.mock("../../../config/sessions/session-accessor.js", async (importOriginal) = Object.entries(sessionStore).map(([sessionKey, entry]) => ({ sessionKey, entry })), loadSessionEntry: (scope: { sessionKey: string }) => sessionStore[scope.sessionKey], loadSessionEntryReadOnly: (scope: { sessionKey: string }) => sessionStore[scope.sessionKey], - patchSessionEntry: async () => null, + patchSessionEntryCore: async () => null, })); const announceSpy = vi.fn( diff --git a/src/agents/subagents/registry/subagent-registry.test.ts b/src/agents/subagents/registry/subagent-registry.test.ts index ed3793003023..8542e94a42ef 100644 --- a/src/agents/subagents/registry/subagent-registry.test.ts +++ b/src/agents/subagents/registry/subagent-registry.test.ts @@ -122,7 +122,7 @@ const mocks = vi.hoisted(() => ({ >; return store[scope.sessionKey]; }), - listSessionEntries: vi.fn((scope: Omit) => { + listSessionEntriesCore: vi.fn((scope: Omit) => { const store = mocks.loadSessionStore(scope.storePath, { clone: false }) as Record< string, SessionEntry @@ -130,7 +130,7 @@ const mocks = vi.hoisted(() => ({ return Object.entries(store).map(([sessionKey, entry]) => ({ sessionKey, entry })); }), loadSessionStore: vi.fn((_storePath?: string, _options?: { clone?: boolean }) => ({})), - patchSessionEntry: vi.fn( + patchSessionEntryCore: vi.fn( async ( scope: SessionAccessScope, update: ( @@ -243,11 +243,11 @@ vi.mock("../../../config/sessions.js", () => ({ })); vi.mock("../../../config/sessions/session-accessor.js", () => ({ - listSessionEntries: mocks.listSessionEntries, - listSessionEntriesReadOnly: mocks.listSessionEntries, + listSessionEntriesCore: mocks.listSessionEntriesCore, + listSessionEntriesReadOnly: mocks.listSessionEntriesCore, loadSessionEntry: mocks.loadSessionEntry, loadSessionEntryReadOnly: mocks.loadSessionEntry, - patchSessionEntry: mocks.patchSessionEntry, + patchSessionEntryCore: mocks.patchSessionEntryCore, })); vi.mock("../../../sessions/session-lifecycle-events.js", () => ({ @@ -1062,7 +1062,7 @@ describe("subagent registry seam flow", () => { it("keeps killed session timing root-admitted after task finalization", async () => { let finishTiming: (() => void) | undefined; - mocks.patchSessionEntry.mockImplementationOnce(async () => { + mocks.patchSessionEntryCore.mockImplementationOnce(async () => { await new Promise((resolve) => { finishTiming = resolve; }); @@ -4803,7 +4803,7 @@ describe("subagent registry seam flow", () => { timingWriteStarted = resolve; }); const timingWriteFinished = new Promise((resolveFinished) => { - mocks.patchSessionEntry.mockImplementationOnce(async (scope, update) => { + mocks.patchSessionEntryCore.mockImplementationOnce(async (scope, update) => { timingWriteStarted?.(); await new Promise((resolve) => { releaseTimingWrite = resolve; @@ -4908,7 +4908,7 @@ describe("subagent registry seam flow", () => { ), ).toBe(false); expect( - mocks.patchSessionEntry.mock.calls.some( + mocks.patchSessionEntryCore.mock.calls.some( ([scope]) => (scope as SessionAccessScope).sessionKey === childSessionKey, ), ).toBe(false); diff --git a/src/agents/tool-allowlist-guard.ts b/src/agents/tool-allowlist-guard.ts index 4e02d0b80509..2ec30dfc4a10 100644 --- a/src/agents/tool-allowlist-guard.ts +++ b/src/agents/tool-allowlist-guard.ts @@ -4,7 +4,7 @@ * Collects operator/user allowlist sources and explains when no callable tools remain. */ import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; -import { normalizeToolList, normalizeToolName } from "./tool-policy.js"; +import { normalizeToolList, normalizeToolPolicyName } from "./tool-policy.js"; type ExplicitToolAllowlistSource = { label: string; @@ -49,7 +49,7 @@ export function buildEmptyExplicitToolAllowlistError(params: { return null; } const requested = sources - .map((source) => `${source.label}: ${source.entries.map(normalizeToolName).join(", ")}`) + .map((source) => `${source.label}: ${source.entries.map(normalizeToolPolicyName).join(", ")}`) .join("; "); const reason = params.disableTools === true diff --git a/src/agents/tool-display-common.ts b/src/agents/tool-display-common.ts index 608536b475bf..b43aac41ff28 100644 --- a/src/agents/tool-display-common.ts +++ b/src/agents/tool-display-common.ts @@ -40,7 +40,7 @@ type CoerceDisplayValueOptions = { }; /** Normalize a tool name for fallback display. */ -export function normalizeToolName(name?: string): string { +export function normalizeToolDisplayName(name?: string): string { return (name ?? "tool").trim(); } diff --git a/src/agents/tool-display.ts b/src/agents/tool-display.ts index 0c42eb7d2052..22956e60f196 100644 --- a/src/agents/tool-display.ts +++ b/src/agents/tool-display.ts @@ -14,7 +14,7 @@ import { defaultTitle, formatToolDetailText, formatDetailKey, - normalizeToolName, + normalizeToolDisplayName, resolveToolVerbAndDetailForArgs, } from "./tool-display-common.js"; import { TOOL_DISPLAY_CONFIG } from "./tool-display-config.js"; @@ -58,7 +58,7 @@ export function resolveToolDisplay(params: { meta?: string; detailMode?: ToolDetailMode; }): ToolDisplay { - const name = normalizeToolName(params.name); + const name = normalizeToolDisplayName(params.name); const key = normalizeLowercaseStringOrEmpty(name); const spec = TOOL_MAP[key]; const emoji = spec?.emoji ?? FALLBACK.emoji ?? "🧩"; diff --git a/src/agents/tool-loop-admission.ts b/src/agents/tool-loop-admission.ts index 45e030426662..77bbec95f3d8 100644 --- a/src/agents/tool-loop-admission.ts +++ b/src/agents/tool-loop-admission.ts @@ -15,7 +15,7 @@ import { } from "./agent-tools.before-tool-call.state.js"; import type { HookContext } from "./agent-tools.before-tool-call.types.js"; import { hashToolCall } from "./tool-loop-detection.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; type ToolLoopCall = { toolName: string; @@ -36,7 +36,7 @@ async function evaluateToolLoopCall( if (!ctx.sessionKey || ctx.loopDetection?.enabled !== true) { return undefined; } - const toolName = normalizeToolName(call.toolName || "tool"); + const toolName = normalizeToolPolicyName(call.toolName || "tool"); const { getDiagnosticSessionState, logToolLoopAction, detectToolCallLoop } = await loadBeforeToolCallRuntime(); const sessionState = @@ -104,7 +104,7 @@ async function recordToolLoopCall(call: ToolLoopCall, ctx: HookContext): Promise const { getDiagnosticSessionState, recordToolCall } = await loadBeforeToolCallRuntime(); recordToolCall( getDiagnosticSessionState({ sessionKey: ctx.sessionKey, sessionId: ctx.sessionId }), - normalizeToolName(call.toolName || "tool"), + normalizeToolPolicyName(call.toolName || "tool"), call.params, call.toolCallId, ctx.loopDetection, @@ -155,7 +155,7 @@ export async function admitToolCallBatch( const recordLoopVeto = (state: SessionState, call: InternalToolBatchCall) => { recordToolCall( state, - normalizeToolName(call.toolCall.name || "tool"), + normalizeToolPolicyName(call.toolCall.name || "tool"), call.args, call.toolCall.id, ctx.loopDetection, @@ -182,7 +182,7 @@ export async function admitToolCallBatch( } }; for (const call of calls) { - const toolName = normalizeToolName(call.toolCall.name || "tool"); + const toolName = normalizeToolPolicyName(call.toolCall.name || "tool"); const intervention = await evaluateToolLoopCall( { toolName, @@ -198,7 +198,7 @@ export async function admitToolCallBatch( // threshold. Unrelated skipped actions remain valid recovery choices. for (const rejectedCall of calls) { const rejectedActionKey = hashToolCall( - normalizeToolName(rejectedCall.toolCall.name || "tool"), + normalizeToolPolicyName(rejectedCall.toolCall.name || "tool"), rejectedCall.args, ); if (rejectedActionKey === intervention.actionKey) { @@ -216,7 +216,7 @@ export async function admitToolCallBatch( const admittedById = new Map( calls.map((call) => [ call.toolCall.id, - { toolName: normalizeToolName(call.toolCall.name || "tool") }, + { toolName: normalizeToolPolicyName(call.toolCall.name || "tool") }, ]), ); const committedIds = new Set(); diff --git a/src/agents/tool-policy-audit.ts b/src/agents/tool-policy-audit.ts index 9a5315df4df9..225326a926cd 100644 --- a/src/agents/tool-policy-audit.ts +++ b/src/agents/tool-policy-audit.ts @@ -7,7 +7,7 @@ import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { createSubsystemLogger } from "../logging/subsystem.js"; import type { SandboxConfig } from "./sandbox/types.js"; import { isToolAllowedByPolicyName } from "./tool-policy-match.js"; -import { normalizeToolList, normalizeToolName, type ToolPolicyLike } from "./tool-policy.js"; +import { normalizeToolList, normalizeToolPolicyName, type ToolPolicyLike } from "./tool-policy.js"; // Emits bounded audit logs when tool allow/deny policies remove or block tools. // Sanitizing here keeps logs single-line and safe for arbitrary tool names. @@ -225,7 +225,7 @@ export function auditSandboxToolPolicyBlock(params: { policy?: ToolPolicyLike; mode: SandboxConfig["mode"]; }): void { - const normalizedToolName = normalizeToolName(params.toolName); + const normalizedToolName = normalizeToolPolicyName(params.toolName); if (!normalizedToolName) { return; } diff --git a/src/agents/tool-policy-declared-context.ts b/src/agents/tool-policy-declared-context.ts index 1ea03b0cc7f5..6015a306be22 100644 --- a/src/agents/tool-policy-declared-context.ts +++ b/src/agents/tool-policy-declared-context.ts @@ -12,16 +12,16 @@ import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot import { sanitizeServerName, TOOL_NAME_SEPARATOR } from "./agent-bundle-mcp-names.js"; import { compileGlobPatterns, matchesAnyGlobPattern } from "./glob-pattern.js"; import type { DeclaredToolAllowlistContext } from "./tool-policy.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; type ToolDenylist = ReturnType; function normalizeToolDenylist(list?: string[]): ToolDenylist { - return compileGlobPatterns({ raw: list, normalize: normalizeToolName }); + return compileGlobPatterns({ raw: list, normalize: normalizeToolPolicyName }); } function denylistBlocksName(name: string, denylist: ToolDenylist): boolean { - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); return normalized ? matchesAnyGlobPattern(normalized, denylist) : false; } @@ -29,7 +29,7 @@ function denylistBlocksMcpServerNamespace(params: { safeServerName: string; denylist: ToolDenylist; }): boolean { - const serverPrefix = normalizeToolName(params.safeServerName + TOOL_NAME_SEPARATOR); + const serverPrefix = normalizeToolPolicyName(params.safeServerName + TOOL_NAME_SEPARATOR); if (!serverPrefix) { return false; } @@ -117,7 +117,7 @@ function collectAvailableManifestToolNames(params: { env: params.env, }), ) - .map(normalizeToolName) + .map(normalizeToolPolicyName) .filter(Boolean); } diff --git a/src/agents/tool-policy-match.ts b/src/agents/tool-policy-match.ts index 5c2352066326..02f30f057399 100644 --- a/src/agents/tool-policy-match.ts +++ b/src/agents/tool-policy-match.ts @@ -5,19 +5,19 @@ import { compileGlobPatterns, matchesAnyGlobPattern } from "./glob-pattern.js"; import type { SandboxToolPolicy } from "./sandbox/types.js"; import { readToolAllowlistIntersection } from "./tool-policy-shared.js"; -import { expandToolGroups, normalizeToolName } from "./tool-policy.js"; +import { expandToolGroups, normalizeToolPolicyName } from "./tool-policy.js"; function makeToolPolicyMatcher(policy: SandboxToolPolicy) { const deny = compileGlobPatterns({ raw: expandToolGroups(policy.deny ?? []), - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); const allow = compileGlobPatterns({ raw: expandToolGroups(policy.allow ?? []), - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); return (name: string) => { - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); if (matchesAnyGlobPattern(normalized, deny)) { return false; } diff --git a/src/agents/tool-policy-pipeline.ts b/src/agents/tool-policy-pipeline.ts index dc3bf9d8f7ca..7bce6cb5edba 100644 --- a/src/agents/tool-policy-pipeline.ts +++ b/src/agents/tool-policy-pipeline.ts @@ -11,7 +11,7 @@ import { analyzeAllowlistByToolType, buildPluginToolGroups, expandPolicyWithPluginGroups, - normalizeToolName, + normalizeToolPolicyName, type DeclaredToolAllowlistContext, type ToolPolicyLike, } from "./tool-policy.js"; @@ -144,7 +144,7 @@ export function applyToolPolicyPipeline(params: const coreToolNames = new Set( params.tools .filter((tool) => !params.toolMeta(tool)) - .map((tool) => normalizeToolName(tool.name)) + .map((tool) => normalizeToolPolicyName(tool.name)) .filter(Boolean), ); @@ -171,7 +171,7 @@ export function applyToolPolicyPipeline(params: if (resolved.unknownAllowlist.length > 0) { const unavailableCoreWarningAllowlist = new Set( (step.suppressUnavailableCoreToolWarningAllowlist ?? []).map((entry) => - normalizeToolName(entry), + normalizeToolPolicyName(entry), ), ); const gatedCoreEntries = resolved.unknownAllowlist.filter((entry) => diff --git a/src/agents/tool-policy-shared.ts b/src/agents/tool-policy-shared.ts index 58ff336baa95..6acd4471bfe8 100644 --- a/src/agents/tool-policy-shared.ts +++ b/src/agents/tool-policy-shared.ts @@ -55,7 +55,7 @@ export function readToolAllowlistIntersection( } /** Normalizes a tool name or alias to the policy id used for matching. */ -export function normalizeToolName(name: string) { +export function normalizeToolPolicyName(name: string) { const normalized = normalizeLowercaseStringOrEmpty(name); return TOOL_NAME_ALIASES[normalized] ?? normalized; } @@ -72,7 +72,7 @@ export function couldNormalizeToolNamePrefixToAllowedTool( const allowed = new Set(); for (const toolName of allowedToolNames) { - const normalizedToolName = normalizeToolName(toolName); + const normalizedToolName = normalizeToolPolicyName(toolName); const foldedToolName = normalizeLowercaseStringOrEmpty(toolName); if (normalizedToolName) { allowed.add(normalizedToolName); @@ -88,7 +88,7 @@ export function couldNormalizeToolNamePrefixToAllowedTool( } } - const resolvedPrefix = normalizeToolName(normalizedPrefix); + const resolvedPrefix = normalizeToolPolicyName(normalizedPrefix); if (resolvedPrefix !== normalizedPrefix) { for (const toolName of allowed) { if (toolName.startsWith(resolvedPrefix)) { @@ -110,7 +110,7 @@ export function normalizeToolList(list?: string[]) { if (!list) { return []; } - return list.map(normalizeToolName).filter(Boolean); + return list.map(normalizeToolPolicyName).filter(Boolean); } /** Expands named tool groups into concrete tool ids. */ diff --git a/src/agents/tool-policy.test.ts b/src/agents/tool-policy.test.ts index b86a52b9bded..6712f3cad435 100644 --- a/src/agents/tool-policy.test.ts +++ b/src/agents/tool-policy.test.ts @@ -13,7 +13,7 @@ import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, expandToolGroups, hasRestrictiveAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, resolveToolProfilePolicy, TOOL_GROUPS, } from "./tool-policy.js"; @@ -49,12 +49,12 @@ describe("tool-policy", () => { }); it("normalizes tool names and aliases", () => { - expect(normalizeToolName(" BASH ")).toBe("exec"); - expect(normalizeToolName("apply-patch")).toBe("apply_patch"); - expect(normalizeToolName("READ")).toBe("read"); + expect(normalizeToolPolicyName(" BASH ")).toBe("exec"); + expect(normalizeToolPolicyName("apply-patch")).toBe("apply_patch"); + expect(normalizeToolPolicyName("READ")).toBe("read"); // Pre-rename scheduler tool name from persisted config (RFC 0026). - expect(normalizeToolName("cron")).toBe("automations"); - expect(normalizeToolName("automations")).toBe("automations"); + expect(normalizeToolPolicyName("cron")).toBe("automations"); + expect(normalizeToolPolicyName("automations")).toBe("automations"); }); it("collects explicit allowlist entries", () => { diff --git a/src/agents/tool-policy.ts b/src/agents/tool-policy.ts index 0b812c730f08..2f9cfc14d2cf 100644 --- a/src/agents/tool-policy.ts +++ b/src/agents/tool-policy.ts @@ -7,13 +7,17 @@ import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/s import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { sanitizeServerName, TOOL_NAME_SEPARATOR } from "./agent-bundle-mcp-names.js"; import { IMPLICIT_ALLOW_ALL_FROM_ALSO_ALLOW } from "./sandbox-tool-policy.js"; -import { expandToolGroups, normalizeToolList, normalizeToolName } from "./tool-policy-shared.js"; +import { + expandToolGroups, + normalizeToolList, + normalizeToolPolicyName, +} from "./tool-policy-shared.js"; export { attachToolAllowlistIntersection, couldNormalizeToolNamePrefixToAllowedTool, expandToolGroups, normalizeToolList, - normalizeToolName, + normalizeToolPolicyName, readToolAllowlistIntersection, resolveToolProfilePolicy, TOOL_GROUPS, @@ -60,7 +64,7 @@ export function hasRestrictiveAllowPolicy(policy?: { allow?: string[] }): boolea if (!Array.isArray(policy?.allow)) { return false; } - const normalizedAllow = policy.allow.map((entry) => normalizeToolName(entry)); + const normalizedAllow = policy.allow.map((entry) => normalizeToolPolicyName(entry)); // A wildcard remains allow-all when additive entries are present. Treating // those extras as restrictive would unnecessarily cap delegated sessions. if (normalizedAllow.includes("*")) { @@ -76,13 +80,15 @@ export function toolPolicyRestrictsTools(policy?: ToolPolicyLike): boolean { if (!policy) { return false; } - if (expandToolGroups(policy.deny ?? []).some((entry) => Boolean(normalizeToolName(entry)))) { + if ( + expandToolGroups(policy.deny ?? []).some((entry) => Boolean(normalizeToolPolicyName(entry))) + ) { return true; } return ( Array.isArray(policy.allow) && policy.allow.length > 0 && - !expandToolGroups(policy.allow).some((entry) => normalizeToolName(entry) === "*") + !expandToolGroups(policy.allow).some((entry) => normalizeToolPolicyName(entry) === "*") ); } @@ -94,7 +100,7 @@ export function replaceWithEffectiveToolAllowlist( target.length = 0; const seen = new Set(); for (const tool of tools) { - const normalized = normalizeToolName(tool.name); + const normalized = normalizeToolPolicyName(tool.name); if (!normalized || seen.has(normalized)) { continue; } @@ -163,7 +169,7 @@ export function buildPluginToolGroups(params: { if (!meta) { continue; } - const name = normalizeToolName(tool.name); + const name = normalizeToolPolicyName(tool.name); all.push(name); const pluginId = normalizeOptionalLowercaseString(meta.pluginId); if (!pluginId) { @@ -186,7 +192,7 @@ function expandPluginGroups( } const expanded: string[] = []; for (const entry of list) { - const normalized = normalizeToolName(entry); + const normalized = normalizeToolPolicyName(entry); if (normalized === "group:plugins") { if (groups.all.length > 0) { expanded.push(...groups.all); @@ -228,7 +234,7 @@ function buildDeclaredMcpToolPrefixes(serverNames?: Iterable): Set(); for (const serverName of serverNames ?? []) { const safeName = sanitizeServerName(serverName, usedNames); - const prefix = normalizeToolName(safeName + TOOL_NAME_SEPARATOR); + const prefix = normalizeToolPolicyName(safeName + TOOL_NAME_SEPARATOR); if (prefix) { prefixes.add(prefix); } @@ -246,8 +252,8 @@ function normalizeDeclaredPluginIds(values?: Iterable): Set { function normalizeDeclaredToolNames(values?: Iterable): Set { return new Set( - Array.from(values ?? [], (value) => normalizeToolName(value)).filter((value): value is string => - Boolean(value), + Array.from(values ?? [], (value) => normalizeToolPolicyName(value)).filter( + (value): value is string => Boolean(value), ), ); } diff --git a/src/agents/tool-replay-safety.ts b/src/agents/tool-replay-safety.ts index 267ed8cc07ad..38f35b7628bb 100644 --- a/src/agents/tool-replay-safety.ts +++ b/src/agents/tool-replay-safety.ts @@ -1,7 +1,7 @@ /** * Defines the narrow set of tool instances that blind attempt retries may repeat. */ -import { normalizeToolName } from "./tool-policy-shared.js"; +import { normalizeToolPolicyName } from "./tool-policy-shared.js"; const UNCONDITIONALLY_REPLAY_SAFE_TOOL_NAMES = new Set([ "read", @@ -37,7 +37,7 @@ export function isAgentToolReplaySafe( if (options?.declaredReplaySafe?.(tool) === false) { return false; } - return UNCONDITIONALLY_REPLAY_SAFE_TOOL_NAMES.has(normalizeToolName(tool.name ?? "")); + return UNCONDITIONALLY_REPLAY_SAFE_TOOL_NAMES.has(normalizeToolPolicyName(tool.name ?? "")); } /** @@ -53,7 +53,7 @@ export function isAgentToolRestartSafe( if (declaredReplaySafe !== undefined) { return declaredReplaySafe; } - return UNCONDITIONALLY_REPLAY_SAFE_TOOL_NAMES.has(normalizeToolName(tool.name ?? "")); + return UNCONDITIONALLY_REPLAY_SAFE_TOOL_NAMES.has(normalizeToolPolicyName(tool.name ?? "")); } /** @@ -66,7 +66,7 @@ export function collectReplaySafeToolNames( ): Set { const toolsByName = new Map>(); for (const tool of tools) { - const name = normalizeToolName(tool.name ?? ""); + const name = normalizeToolPolicyName(tool.name ?? ""); if (!name) { continue; } diff --git a/src/agents/tool-surface-plan.ts b/src/agents/tool-surface-plan.ts index b45d416bd665..f753fb03e007 100644 --- a/src/agents/tool-surface-plan.ts +++ b/src/agents/tool-surface-plan.ts @@ -5,7 +5,7 @@ import { isCodeModeEngagedForModel, resolveCodeModeConfig, } from "./code-mode.js"; -import { normalizeToolName } from "./tool-policy-shared.js"; +import { normalizeToolPolicyName } from "./tool-policy-shared.js"; import { resolveAgentToolSearchRuntimeConfig } from "./tool-search-runtime-config.js"; import type { ToolSearchConfig } from "./tool-search-types.js"; import { @@ -51,7 +51,7 @@ export function resolveAgentToolSurfacePlan(params: AgentToolSurfacePlanParams) !( params.forceDirectMessageTool && params.toolsAllow?.length === 1 && - normalizeToolName(params.toolsAllow[0] ?? "") === "message" + normalizeToolPolicyName(params.toolsAllow[0] ?? "") === "message" ); const codeModeControlsEnabled = toolsAvailable && diff --git a/src/agents/tools-effective-inventory.ts b/src/agents/tools-effective-inventory.ts index c56eed418b61..8e6c65e11c93 100644 --- a/src/agents/tools-effective-inventory.ts +++ b/src/agents/tools-effective-inventory.ts @@ -30,7 +30,7 @@ import { PreparedModelRuntimeOwnerNotPublishedError, acquireReadOnlyPreparedModelRuntime, } from "./prepared-model-runtime.js"; -import { normalizeToolName } from "./tool-policy.js"; +import { normalizeToolPolicyName } from "./tool-policy.js"; import { buildRuntimeCompatibleToolInventory } from "./tools-effective-inventory-build.js"; import { buildEffectiveToolInventoryGroups } from "./tools-effective-inventory-groups.js"; import type { @@ -44,8 +44,8 @@ function listIncludesTool(list: string[] | undefined, toolName: string): boolean if (!Array.isArray(list)) { return false; } - const normalizedToolName = normalizeToolName(toolName); - return list.some((entry) => normalizeToolName(entry) === normalizedToolName); + const normalizedToolName = normalizeToolPolicyName(toolName); + return list.some((entry) => normalizeToolPolicyName(entry) === normalizedToolName); } function policyDeniesTool(policy: { deny?: string[] } | undefined, toolName: string): boolean { @@ -66,7 +66,9 @@ function buildToolInventoryNotices(params: { entries: EffectiveToolInventoryEntry[]; effectivePolicy: ReturnType; }): EffectiveToolInventoryNotice[] | undefined { - const hasBrowserTool = params.entries.some((entry) => normalizeToolName(entry.id) === "browser"); + const hasBrowserTool = params.entries.some( + (entry) => normalizeToolPolicyName(entry.id) === "browser", + ); if (hasBrowserTool || !hasExplicitBrowserIntent(params.cfg)) { return undefined; } diff --git a/src/agents/tools/cron-tool-creator-cap.ts b/src/agents/tools/cron-tool-creator-cap.ts index 2b9154e03bc8..7f5d7d0cdb1f 100644 --- a/src/agents/tools/cron-tool-creator-cap.ts +++ b/src/agents/tools/cron-tool-creator-cap.ts @@ -4,7 +4,7 @@ import { buildPluginToolGroups, expandPolicyWithPluginGroups, expandToolGroups, - normalizeToolName, + normalizeToolPolicyName, } from "../tool-policy.js"; import type { CronCreatorToolAllowlistEntry, CronToolsAllowCaptureRef } from "./cron-tool.types.js"; @@ -48,14 +48,14 @@ export function replaceWithEffectiveCronCreatorToolAllowlist(); for (const tool of tools) { - const name = normalizeToolName(tool.name); + const name = normalizeToolPolicyName(tool.name); if (!name || seen.has(name)) { continue; } seen.add(name); const meta = toolMeta?.(tool); const pluginId = - typeof meta?.pluginId === "string" ? normalizeToolName(meta.pluginId) : undefined; + typeof meta?.pluginId === "string" ? normalizeToolPolicyName(meta.pluginId) : undefined; target.push(pluginId ? { name, pluginId } : { name }); } } @@ -75,7 +75,7 @@ function normalizeCronToolsAllow(values: readonly string[]): string[] { const normalized: string[] = []; const seen = new Set(); for (const entry of expandToolGroups([...values])) { - const toolName = normalizeToolName(entry); + const toolName = normalizeToolPolicyName(entry); if (!toolName || seen.has(toolName)) { continue; } @@ -91,7 +91,7 @@ function normalizeCronCreatorToolsAllow( const normalized: NormalizedCronCreatorTool[] = []; const seen = new Set(); for (const entry of values) { - const name = normalizeToolName(typeof entry === "string" ? entry : entry.name); + const name = normalizeToolPolicyName(typeof entry === "string" ? entry : entry.name); if (!name || seen.has(name)) { continue; } @@ -99,7 +99,7 @@ function normalizeCronCreatorToolsAllow( const pluginId = typeof entry === "string" || typeof entry.pluginId !== "string" ? undefined - : normalizeToolName(entry.pluginId); + : normalizeToolPolicyName(entry.pluginId); normalized.push(pluginId ? { name, pluginId } : { name }); } return normalized; @@ -123,7 +123,7 @@ function classifyExplicitToolsAllow( return "empty"; } return values.some((entry) => { - const normalized = normalizeToolName(entry); + const normalized = normalizeToolPolicyName(entry); return normalized === "*" || normalized.startsWith("group:"); }) ? "resolved" diff --git a/src/agents/tools/embedded-gateway-stub.runtime.ts b/src/agents/tools/embedded-gateway-stub.runtime.ts index b68da9624f27..b7db33621c11 100644 --- a/src/agents/tools/embedded-gateway-stub.runtime.ts +++ b/src/agents/tools/embedded-gateway-stub.runtime.ts @@ -33,7 +33,7 @@ export { } from "../../gateway/session-transcript-readers.js"; export { listSessionsFromStoreAsync, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, loadSessionEntryReadOnly as loadSessionEntry, resolveSessionModelRef, } from "../../gateway/session-utils.js"; diff --git a/src/agents/tools/embedded-gateway-stub.test.ts b/src/agents/tools/embedded-gateway-stub.test.ts index ab53740cb7fc..a07836b20d2f 100644 --- a/src/agents/tools/embedded-gateway-stub.test.ts +++ b/src/agents/tools/embedded-gateway-stub.test.ts @@ -50,7 +50,7 @@ const runtime = vi.hoisted(() => ({ })), capArrayByJsonBytes: vi.fn((items: unknown[]) => ({ items })), enforceChatHistoryFinalBudget: vi.fn(({ messages }: { messages: unknown[] }) => ({ messages })), - loadCombinedSessionStoreForGateway: vi.fn(() => ({ + loadCombinedSessionStoreForGatewayCore: vi.fn(() => ({ storePath: "/tmp/openclaw-sessions.json", store: {}, })), @@ -75,7 +75,7 @@ describe("embedded gateway stub", () => { runtime.resolveSessionStoreKey.mockClear(); runtime.resolveStoredSessionKeyForAgentStore.mockClear(); runtime.searchSessionTranscripts.mockClear(); - runtime.loadCombinedSessionStoreForGateway.mockClear(); + runtime.loadCombinedSessionStoreForGatewayCore.mockClear(); runtime.listSessionsFromStoreAsync.mockClear(); }); @@ -86,7 +86,7 @@ describe("embedded gateway stub", () => { params: { agentId: "work", includeGlobal: true, search: "global" }, }); - expect(runtime.loadCombinedSessionStoreForGateway).toHaveBeenCalledWith( + expect(runtime.loadCombinedSessionStoreForGatewayCore).toHaveBeenCalledWith( { agents: { list: [{ id: "main", default: true }] } }, { agentId: "work", projection: "list" }, ); diff --git a/src/agents/tools/embedded-gateway-stub.ts b/src/agents/tools/embedded-gateway-stub.ts index d2053d888ba5..1913a45a62be 100644 --- a/src/agents/tools/embedded-gateway-stub.ts +++ b/src/agents/tools/embedded-gateway-stub.ts @@ -79,7 +79,7 @@ interface EmbeddedGatewayRuntime { store: unknown; opts: SessionsListParams; }) => Promise; - loadCombinedSessionStoreForGateway: ( + loadCombinedSessionStoreForGatewayCore: ( cfg: OpenClawConfig, opts?: { agentId?: string; projection?: "full" | "list" }, ) => { @@ -199,7 +199,7 @@ async function handleSessionsList(params: Record) { const rt = await getRuntime(); const cfg = rt.getRuntimeConfig(); const opts = params as SessionsListParams; - const { storePath, store } = rt.loadCombinedSessionStoreForGateway(cfg, { + const { storePath, store } = rt.loadCombinedSessionStoreForGatewayCore(cfg, { agentId: opts.agentId, projection: "list", }); diff --git a/src/agents/tools/sessions-tool.test.ts b/src/agents/tools/sessions-tool.test.ts index 224cc85e48e4..bb861f3ef9c2 100644 --- a/src/agents/tools/sessions-tool.test.ts +++ b/src/agents/tools/sessions-tool.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { loadSessionEntry, loadTranscriptEvents, - patchSessionEntry, + patchSessionEntryCore, upsertSessionEntry, } from "../../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -300,7 +300,7 @@ describe("sessions tool", () => { async (request: { method: string; params: Record }) => { expect(request.method).toBe("sessions.patch"); expect(isAgentSessionModelPatchOrigin()).toBe(true); - await patchSessionEntry({ agentId: "main", sessionKey, storePath }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey, storePath }, () => ({ label: request.params.label as string, model: "bad", modelProvider: "broken", @@ -538,7 +538,7 @@ describe("sessions tool", () => { sessionKey, storePath, }); - await patchSessionEntry({ agentId: "main", sessionKey, storePath }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey, storePath }, () => ({ model: "c", modelOverride: "c", modelFallback: { @@ -554,7 +554,7 @@ describe("sessions tool", () => { sessionKey, storePath, }); - await patchSessionEntry({ agentId: "main", sessionKey, storePath }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey, storePath }, () => ({ model: "d", modelOverride: "d", modelFallback: { diff --git a/src/agents/utils/frontmatter.ts b/src/agents/utils/frontmatter.ts index d917c5f7efdf..8ab2356d03cf 100644 --- a/src/agents/utils/frontmatter.ts +++ b/src/agents/utils/frontmatter.ts @@ -17,7 +17,7 @@ const normalizeNewlines = (value: string): string => value.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); /** Parses optional YAML frontmatter from Markdown-like content. */ -export const parseFrontmatter = = Record>( +export const parsePromptFrontmatter = = Record>( content: string, ): ParsedFrontmatter => { const normalized = normalizeNewlines(content); @@ -30,4 +30,4 @@ export const parseFrontmatter = = Record parseFrontmatter(content).body; +export const stripFrontmatter = (content: string): string => parsePromptFrontmatter(content).body; diff --git a/src/agents/worktrees/git.ts b/src/agents/worktrees/git.ts index 8cc73eeca6db..9b0bb6d1ef29 100644 --- a/src/agents/worktrees/git.ts +++ b/src/agents/worktrees/git.ts @@ -145,7 +145,7 @@ export async function hasSelfContainedGitMetadata(checkoutRoot: string): Promise } } -export async function pathExists(target: string): Promise { +export async function worktreePathExists(target: string): Promise { try { await fs.lstat(target); return true; diff --git a/src/agents/worktrees/provisioned-files.ts b/src/agents/worktrees/provisioned-files.ts index 76f8f170e3a3..324fd035c277 100644 --- a/src/agents/worktrees/provisioned-files.ts +++ b/src/agents/worktrees/provisioned-files.ts @@ -1,7 +1,7 @@ import { constants as fsConstants } from "node:fs"; import fs, { type FileHandle } from "node:fs/promises"; import path from "node:path"; -import { pathExists, requireGitRaw } from "./git.js"; +import { requireGitRaw, worktreePathExists } from "./git.js"; import { clearRegistryWorktreeProvisionedChunks, getRegistryWorktreeProvisionedChunk, @@ -96,7 +96,7 @@ export async function provisionIncludedFiles( worktreePath: string, ): Promise { const includePath = path.join(repoRoot, ".worktreeinclude"); - if (!(await pathExists(includePath))) { + if (!(await worktreePathExists(includePath))) { return []; } const candidatesRaw = await requireGitRaw(repoRoot, [ diff --git a/src/agents/worktrees/service.ts b/src/agents/worktrees/service.ts index 2df9c0d1c950..5eb645e16a60 100644 --- a/src/agents/worktrees/service.ts +++ b/src/agents/worktrees/service.ts @@ -17,7 +17,7 @@ import { commandError, insideGitCheckout, listGitWorktrees, - pathExists, + worktreePathExists, removeEmptyParents, requireGit, requireGitBuffer, @@ -132,7 +132,7 @@ async function nameIsUnavailable( // Treating it as a collision can create a second checkout for one owner. return false; } - if (registered || (await pathExists(worktreePath))) { + if (registered || (await worktreePathExists(worktreePath))) { return true; } const branch = `openclaw/${name}`; @@ -231,7 +231,7 @@ async function shouldPreserveOrphanCandidate( if (managedPaths.has(targetKey)) { return true; } - const hasOwnGitMarker = await pathExists(path.join(target, ".git")); + const hasOwnGitMarker = await worktreePathExists(path.join(target, ".git")); if (!hasOwnGitMarker) { return false; } @@ -279,7 +279,7 @@ async function resetFailedWorktreeAdd( if (removed.code !== 0) { throw commandError("git worktree remove", removed); } - } else if (await pathExists(worktreePath)) { + } else if (await worktreePathExists(worktreePath)) { // A failed add can leave an unregistered directory; it is safe debris once git omits it. await fs.rm(worktreePath, { recursive: true, force: true }); } @@ -309,7 +309,7 @@ async function canResetFailedWorktreeAdd( const listed = (await listGitWorktrees(repoRoot)).some( (entry) => path.resolve(entry.path) === path.resolve(worktreePath), ); - if (listed || (await pathExists(worktreePath))) { + if (listed || (await worktreePathExists(worktreePath))) { return false; } const branchExists = await runGit(repoRoot, [ @@ -598,7 +598,7 @@ export class ManagedWorktreeService { }, async () => { const existing = findLiveRegistryWorktreeByOwner(this.env, ownerKind, ownerId); - if (existing && (await pathExists(existing.path))) { + if (existing && (await worktreePathExists(existing.path))) { if (existing.repoRoot !== repository.repoRoot) { throw new Error( `worktree owner ${ownerKind} ${ownerId} is already bound to another repository`, @@ -665,7 +665,7 @@ export class ManagedWorktreeService { ); } if (existing?.name === name && existing.removedAt === undefined) { - if (await pathExists(existing.path)) { + if (await worktreePathExists(existing.path)) { return existing; } updateRegistryWorktree(this.env, existing.id, { removedAt: this.now() }); @@ -756,7 +756,7 @@ export class ManagedWorktreeService { async list(): Promise { const records = listRegistryWorktrees(this.env); for (const record of records) { - if (record.removedAt === undefined && !(await pathExists(record.path))) { + if (record.removedAt === undefined && !(await worktreePathExists(record.path))) { const removedAt = this.now(); updateRegistryWorktree(this.env, record.id, { removedAt }); record.removedAt = removedAt; @@ -897,7 +897,7 @@ export class ManagedWorktreeService { async release(id: string): Promise { const record = getRegistryWorktree(this.env, id); - if (!record || record.removedAt !== undefined || !(await pathExists(record.path))) { + if (!record || record.removedAt !== undefined || !(await worktreePathExists(record.path))) { return; } const state = await lockState(record); @@ -1006,7 +1006,7 @@ export class ManagedWorktreeService { if (!record?.snapshotRef || record.removedAt === undefined) { throw new Error(`worktree ${params.id} is not restorable`); } - if (!(await pathExists(record.repoRoot))) { + if (!(await worktreePathExists(record.repoRoot))) { throw new Error(`source repository no longer exists: ${record.repoRoot}`); } const parent = await requireGit(record.repoRoot, ["rev-parse", `${record.snapshotRef}^`]); @@ -1185,7 +1185,7 @@ export class ManagedWorktreeService { const records = listRegistryWorktrees(this.env); for (const record of records) { try { - if (record.removedAt === undefined && !(await pathExists(record.path))) { + if (record.removedAt === undefined && !(await worktreePathExists(record.path))) { updateRegistryWorktree(this.env, record.id, { removedAt: now }); record.removedAt = now; } @@ -1214,7 +1214,7 @@ export class ManagedWorktreeService { continue; } try { - if (record.snapshotRef && (await pathExists(record.repoRoot))) { + if (record.snapshotRef && (await worktreePathExists(record.repoRoot))) { await requireGit(record.repoRoot, ["update-ref", "-d", record.snapshotRef]); } deleteRegistryWorktree(this.env, record.id); diff --git a/src/auto-reply/reply-payload.ts b/src/auto-reply/reply-payload.ts index 300cfdb46ccb..ef678862e2d2 100644 --- a/src/auto-reply/reply-payload.ts +++ b/src/auto-reply/reply-payload.ts @@ -97,6 +97,17 @@ export type ReplyPayload = { channelData?: Record; }; +export function readAskUserQuestionId( + payload: Pick, +): string | undefined { + const askUser = payload.channelData?.askUser; + if (!askUser || typeof askUser !== "object" || Array.isArray(askUser)) { + return undefined; + } + const questionId = (askUser as { questionId?: unknown }).questionId; + return typeof questionId === "string" && questionId ? questionId : undefined; +} + // Private device-pair -> Gateway live-display envelope key. Do not re-export // through Plugin SDK; this is not a third-party plugin contract. const PAIRING_QR_REPLY_CHANNEL_DATA_KEY = "openclawPairingQr"; diff --git a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts index a12e3892e14f..84b76bf82a05 100644 --- a/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.shows-current-verbose-level-verbose-has-no.test.ts @@ -9,7 +9,7 @@ import { installDirectiveBehaviorE2EHooks } from "./reply.directive.directive-be import { runEmbeddedAgentMock } from "./reply.directive.directive-behavior.e2e-mocks.js"; import { handleDirectiveOnly } from "./reply/directive-handling.impl.js"; import type { HandleDirectiveOnlyParams } from "./reply/directive-handling.params.js"; -import { parseInlineDirectives } from "./reply/directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./reply/directive-handling.parse.js"; const emptyAliasIndex: ModelAliasIndex = { byAlias: new Map(), @@ -48,7 +48,7 @@ async function runDirectiveStatus( } = overrides; const result = await handleDirectiveOnly({ cfg: migratePersistedImplicitMainRoster(overrideCfg ?? cfg).config as OpenClawConfig, - directives: parseInlineDirectives(body), + directives: parseInlineSessionDirectives(body), sessionEntry: effectiveSessionEntry, sessionStore: effectiveSessionStore, sessionKey: effectiveSessionKey, diff --git a/src/auto-reply/reply.directive.parse.test.ts b/src/auto-reply/reply.directive.parse.test.ts index a4797383bd48..9e9f12c22a2d 100644 --- a/src/auto-reply/reply.directive.parse.test.ts +++ b/src/auto-reply/reply.directive.parse.test.ts @@ -1,6 +1,6 @@ /** Tests parsing of inline reply directives and command tags. */ import { describe, expect, it } from "vitest"; -import { parseInlineDirectives } from "./reply/directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./reply/directive-handling.parse.js"; import { extractElevatedDirective, extractReasoningDirective, @@ -77,13 +77,13 @@ describe("directive parsing", () => { }); it("parses default thinking and fast directives as override clears", () => { - const think = parseInlineDirectives("/think default"); + const think = parseInlineSessionDirectives("/think default"); expect(think.hasThinkDirective).toBe(true); expect(think.thinkLevel).toBeUndefined(); expect(think.rawThinkLevel).toBe("default"); expect(think.clearThinkLevel).toBe(true); - const fast = parseInlineDirectives("/fast inherit"); + const fast = parseInlineSessionDirectives("/fast inherit"); expect(fast.hasFastDirective).toBe(true); expect(fast.fastMode).toBeUndefined(); expect(fast.rawFastMode).toBe("inherit"); @@ -208,20 +208,20 @@ describe("directive parsing", () => { }); it("strips inline /model and /think directives while keeping user text", () => { - const model = parseInlineDirectives("please sync /model openai/gpt-4.1-mini now"); + const model = parseInlineSessionDirectives("please sync /model openai/gpt-4.1-mini now"); expect(model.cleaned).toBe("please sync now"); expect(model.hasModelDirective).toBe(true); expect(model.rawModelDirective).toBe("openai/gpt-4.1-mini"); expect(model.modelSessionOnly).toBe(false); - const think = parseInlineDirectives("please sync /think:high now"); + const think = parseInlineSessionDirectives("please sync /think:high now"); expect(think.cleaned).toBe("please sync now"); expect(think.hasThinkDirective).toBe(true); expect(think.thinkLevel).toBe("high"); }); it("preserves the trailing /model -s session-only scope", () => { - const model = parseInlineDirectives("please sync /model openai/gpt-4.1-mini -s now"); + const model = parseInlineSessionDirectives("please sync /model openai/gpt-4.1-mini -s now"); expect(model.cleaned).toBe("please sync now"); expect(model.hasModelDirective).toBe(true); expect(model.rawModelDirective).toBe("openai/gpt-4.1-mini"); @@ -229,7 +229,7 @@ describe("directive parsing", () => { }); it("preserves the trailing /model --session scope for native slash commands", () => { - const model = parseInlineDirectives("/model openai/gpt-4.1-mini --session", { + const model = parseInlineSessionDirectives("/model openai/gpt-4.1-mini --session", { nativeCommand: "model", }); @@ -242,7 +242,7 @@ describe("directive parsing", () => { it.each(["--runtime codex -s", "-s --runtime codex", "runtime=codex -s", "-s harness=codex"])( "parses /model runtime and session options from %s", (options) => { - const model = parseInlineDirectives(`/model openai/gpt-4.1-mini ${options}`, { + const model = parseInlineSessionDirectives(`/model openai/gpt-4.1-mini ${options}`, { nativeCommand: "model", }); @@ -257,7 +257,9 @@ describe("directive parsing", () => { it.each(["-slow", "--sessional"])( "preserves partial session option %s as ordinary text", (option) => { - const model = parseInlineDirectives(`please sync /model openai/gpt-4.1-mini ${option} now`); + const model = parseInlineSessionDirectives( + `please sync /model openai/gpt-4.1-mini ${option} now`, + ); expect(model.rawModelDirective).toBe("openai/gpt-4.1-mini"); expect(model.modelSessionOnly).toBe(false); @@ -266,7 +268,7 @@ describe("directive parsing", () => { ); it("rejects duplicate native /model options through unconsumed arguments", () => { - const model = parseInlineDirectives( + const model = parseInlineSessionDirectives( "/model openai/gpt-4.1-mini --runtime codex --runtime acp", { nativeCommand: "model" }, ); @@ -281,7 +283,7 @@ describe("directive parsing", () => { }); it("preserves duplicate inline /model options as message text", () => { - const model = parseInlineDirectives("please sync /model openai/gpt-4.1-mini -s -s now"); + const model = parseInlineSessionDirectives("please sync /model openai/gpt-4.1-mini -s -s now"); expect(model.rawModelDirective).toBe("openai/gpt-4.1-mini"); expect(model.modelSessionOnly).toBe(true); @@ -289,26 +291,28 @@ describe("directive parsing", () => { }); it("keeps here as the selected model in mixed commands", () => { - const model = parseInlineDirectives("please /model here continue"); + const model = parseInlineSessionDirectives("please /model here continue"); expect(model.cleaned).toBe("please continue"); expect(model.rawModelDirective).toBe("here"); expect(model.modelSessionOnly).toBe(false); }); it("keeps --persist as ordinary text for inline directives", () => { - const model = parseInlineDirectives("please sync /model openai/gpt-4.1-mini --persist now"); + const model = parseInlineSessionDirectives( + "please sync /model openai/gpt-4.1-mini --persist now", + ); expect(model.cleaned).toBe("please sync --persist now"); expect(model.hasModelDirective).toBe(true); expect(model.rawModelDirective).toBe("openai/gpt-4.1-mini"); - const think = parseInlineDirectives("/think high --persist"); + const think = parseInlineSessionDirectives("/think high --persist"); expect(think.cleaned).toBe("--persist"); expect(think.hasThinkDirective).toBe(true); expect(think.thinkLevel).toBe("high"); }); it("keeps --persist in ordinary messages", () => { - const parsed = parseInlineDirectives("please keep --persist in this text"); + const parsed = parseInlineSessionDirectives("please keep --persist in this text"); expect(parsed.cleaned).toBe("please keep --persist in this text"); }); @@ -386,7 +390,7 @@ describe("directive parsing", () => { describe("level directive preserves message text after an invalid level", () => { it("keeps the message when /verbose is followed by prose", () => { - const res = parseInlineDirectives("/verbose explain quantum computing"); + const res = parseInlineSessionDirectives("/verbose explain quantum computing"); expect(res.hasVerboseDirective).toBe(true); expect(res.verboseLevel).toBeUndefined(); expect(res.rawVerboseLevel).toBeUndefined(); @@ -476,7 +480,7 @@ describe("native directive commands own their complete argument boundary", () => ])( "preserves the invalid first argument for native /$command", ({ body, command, invalidArgument, rawKey, trailingArguments }) => { - const parsed = parseInlineDirectives(body, { nativeCommand: command }); + const parsed = parseInlineSessionDirectives(body, { nativeCommand: command }); expect(parsed[rawKey]).toBe(invalidArgument); expect(parsed.cleaned).toBe(trailingArguments); @@ -488,7 +492,7 @@ describe("native directive commands own their complete argument boundary", () => ); it("retains unexpected arguments after a valid native queue mode", () => { - const parsed = parseInlineDirectives("/queue collect please help", { + const parsed = parseInlineSessionDirectives("/queue collect please help", { nativeCommand: "queue", }); @@ -500,7 +504,7 @@ describe("native directive commands own their complete argument boundary", () => }); it("does not interpret another directive inside native command arguments", () => { - const parsed = parseInlineDirectives("/queue /think high", { nativeCommand: "queue" }); + const parsed = parseInlineSessionDirectives("/queue /think high", { nativeCommand: "queue" }); expect(parsed.hasQueueDirective).toBe(true); expect(parsed.rawQueueMode).toBe("/think"); @@ -508,7 +512,7 @@ describe("native directive commands own their complete argument boundary", () => }); it("preserves the existing prose interpretation for ordinary inline directives", () => { - const parsed = parseInlineDirectives("/think about my deployment plan"); + const parsed = parseInlineSessionDirectives("/think about my deployment plan"); expect(parsed.rawThinkLevel).toBeUndefined(); expect(parsed.nativeCommand).toBeUndefined(); diff --git a/src/auto-reply/reply.raw-body.test.ts b/src/auto-reply/reply.raw-body.test.ts index 49a6d4f1882f..75731ccb0aa5 100644 --- a/src/auto-reply/reply.raw-body.test.ts +++ b/src/auto-reply/reply.raw-body.test.ts @@ -1,6 +1,6 @@ /** Tests raw body handling for command and reply prompt paths. */ import { describe, expect, it } from "vitest"; -import { parseInlineDirectives } from "./reply/directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./reply/directive-handling.parse.js"; import { finalizeInboundContext } from "./reply/inbound-context.js"; import { buildInboundUserContextPrefix } from "./reply/inbound-meta.js"; import { buildReplyPromptEnvelope } from "./reply/prompt-prelude.js"; @@ -21,7 +21,7 @@ describe("RawBody directive parsing", () => { SenderE164: "+6421807830", CommandAuthorized: true, }); - const directives = parseInlineDirectives(sessionCtx.BodyForCommands ?? "", { + const directives = parseInlineSessionDirectives(sessionCtx.BodyForCommands ?? "", { allowStatusDirective: true, }); const contextPrefix = buildInboundUserContextPrefix(sessionCtx); diff --git a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.ts b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.ts index 226fe861ebb2..905c7e5c52ed 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.ts @@ -8,7 +8,7 @@ import { requireSessionStorePath, withTempHome, } from "../../test/helpers/auto-reply/trigger-handling-test-harness.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; type GetReplyFromConfig = typeof import("./reply.js").getReplyFromConfig; @@ -135,7 +135,7 @@ export function registerTriggerHandlingUsageSummaryCases(params: { ); expect(replyText(r3)).toContain("Usage footer: tokens"); - const finalEntries = listSessionEntries({ storePath: usageStorePath }); + const finalEntries = listSessionEntriesCore({ storePath: usageStorePath }); expect(finalEntries[0]?.entry.responseUsage).toBe("tokens"); expect(runEmbeddedAgentMock).not.toHaveBeenCalled(); }); diff --git a/src/auto-reply/reply/abort-cutoff.runtime.ts b/src/auto-reply/reply/abort-cutoff.runtime.ts index 251deed16a3e..b1f9e75f4b22 100644 --- a/src/auto-reply/reply/abort-cutoff.runtime.ts +++ b/src/auto-reply/reply/abort-cutoff.runtime.ts @@ -1,5 +1,5 @@ /** Runtime persistence helper for clearing abort-cutoff state from sessions. */ -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import { applyAbortCutoffToSessionEntry, hasAbortCutoff } from "./abort-cutoff.js"; @@ -21,7 +21,7 @@ export async function clearAbortCutoffInSessionRuntime(params: { sessionStore[sessionKey] = sessionEntry; if (storePath) { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, () => ({ abortCutoffMessageSid: undefined, diff --git a/src/auto-reply/reply/body.ts b/src/auto-reply/reply/body.ts index 79c2e47a0604..d01d1dbfb41b 100644 --- a/src/auto-reply/reply/body.ts +++ b/src/auto-reply/reply/body.ts @@ -39,8 +39,8 @@ export async function applySessionHints(params: { }); if (params.storePath) { const sessionKey = params.sessionKey; - const { patchSessionEntry } = await loadSessionAccessorRuntime(); - await patchSessionEntry( + const { patchSessionEntryCore } = await loadSessionAccessorRuntime(); + await patchSessionEntryCore( { storePath: params.storePath, sessionKey, @@ -59,8 +59,8 @@ export async function applySessionHints(params: { params.sessionStore[params.sessionKey] = sessionEntry; if (params.storePath) { const sessionKey = params.sessionKey; - const { patchSessionEntry } = await loadSessionAccessorRuntime(); - await patchSessionEntry( + const { patchSessionEntryCore } = await loadSessionAccessorRuntime(); + await patchSessionEntryCore( { storePath: params.storePath, sessionKey, diff --git a/src/auto-reply/reply/commands-gating.test.ts b/src/auto-reply/reply/commands-gating.test.ts index 7ea5732c4ea3..66cec2f284a9 100644 --- a/src/auto-reply/reply/commands-gating.test.ts +++ b/src/auto-reply/reply/commands-gating.test.ts @@ -8,7 +8,7 @@ import { requireGatewayClientScope } from "./command-gates.js"; import { handleConfigCommand, handleDebugCommand } from "./commands-config.js"; import type { HandleCommandsParams } from "./commands-types.js"; import type { ConfigSnapshotMock } from "./commands.test-harness.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn(async () => ({ valid: true, parsed: {} })), @@ -226,7 +226,7 @@ function buildParams(commandBody: string, cfg: OpenClawConfig): HandleCommandsPa from: "user-1", to: "bot-1", }, - directives: parseInlineDirectives(""), + directives: parseInlineSessionDirectives(""), elevated: { enabled: true, allowed: true, failures: [] }, sessionKey: "agent:main:main", workspaceDir: "/tmp", diff --git a/src/auto-reply/reply/commands-goal.test.ts b/src/auto-reply/reply/commands-goal.test.ts index dc53a88c2ef6..101e21ff2757 100644 --- a/src/auto-reply/reply/commands-goal.test.ts +++ b/src/auto-reply/reply/commands-goal.test.ts @@ -9,7 +9,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { takeCommandSessionMetadataChanges } from "./command-session-metadata.js"; import { handleGoalCommand, parseGoalCommand } from "./commands-goal.js"; import type { HandleCommandsParams } from "./commands-types.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; const sessionKey = "agent:main:web:main"; let tempRoots: string[] = []; @@ -238,7 +238,7 @@ describe("goal commands", () => { const params = buildGoalParams("/goal resume /fast off", storePath); const result = await handleGoalCommand(params, true); const prompt = `Continue pursuing the current goal. Interpret this JSON string as the resume note: "\\/fast off"`; - const directives = parseInlineDirectives(prompt); + const directives = parseInlineSessionDirectives(prompt); expect(result?.shouldContinue).toBe(true); expect(params.command.commandBodyNormalized).toBe(prompt); diff --git a/src/auto-reply/reply/commands-reset-hooks.test.ts b/src/auto-reply/reply/commands-reset-hooks.test.ts index 3015190b48ef..15507401ae29 100644 --- a/src/auto-reply/reply/commands-reset-hooks.test.ts +++ b/src/auto-reply/reply/commands-reset-hooks.test.ts @@ -6,7 +6,7 @@ import type { OpenClawConfig } from "../../config/config.js"; import type { MsgContext } from "../templating.js"; import { maybeHandleResetCommand } from "./commands-reset.js"; import type { HandleCommandsParams } from "./commands-types.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; const triggerInternalHookMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined)); const routeReplyMock = vi.hoisted(() => @@ -99,7 +99,7 @@ function buildResetParams( to: ctx.To ?? "bot", resetHookTriggered: false, }, - directives: parseInlineDirectives(""), + directives: parseInlineSessionDirectives(""), elevated: { enabled: true, allowed: true, failures: [] }, sessionKey: "agent:main:main", workspaceDir: "/tmp/openclaw-commands", diff --git a/src/auto-reply/reply/commands-session-lifecycle.test.ts b/src/auto-reply/reply/commands-session-lifecycle.test.ts index f0d3401dd051..3c4fc75d0405 100644 --- a/src/auto-reply/reply/commands-session-lifecycle.test.ts +++ b/src/auto-reply/reply/commands-session-lifecycle.test.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js"; import type { HandleCommandsParams } from "./commands-types.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; const THREAD_CHANNEL = "thread-chat"; const ROOM_CHANNEL = "room-chat"; @@ -299,7 +299,7 @@ function buildSessionCommandParams( from: typeof ctx.From === "string" ? ctx.From : undefined, to: typeof ctx.To === "string" ? ctx.To : undefined, }, - directives: parseInlineDirectives(commandBody), + directives: parseInlineSessionDirectives(commandBody), elevated: { enabled: true, allowed: true, failures: [] }, sessionKey: "agent:main:main", workspaceDir: "/tmp", diff --git a/src/auto-reply/reply/commands-session-store.ts b/src/auto-reply/reply/commands-session-store.ts index 075166d69d07..fad6b11a8308 100644 --- a/src/auto-reply/reply/commands-session-store.ts +++ b/src/auto-reply/reply/commands-session-store.ts @@ -1,6 +1,6 @@ // Shared session-store helpers for command handlers that mutate sessions. import { resolveSessionStoreEntry, type SessionEntry } from "../../config/sessions.js"; -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import { sessionSnapshotChangesApplied } from "../../config/sessions/session-snapshot-merge.js"; import { applyAbortCutoffToSessionEntry, type AbortCutoff } from "./abort-cutoff.js"; import type { CommandHandler, CommandHandlerResult } from "./commands-types.js"; @@ -99,7 +99,7 @@ export async function persistAbortTargetEntry(params: { sessionStore[key] = entry; if (storePath) { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey: key }, (nextEntry) => { nextEntry.abortedLastRun = true; diff --git a/src/auto-reply/reply/commands.test-harness.ts b/src/auto-reply/reply/commands.test-harness.ts index 05aa4273bf6d..9f7d5291291c 100644 --- a/src/auto-reply/reply/commands.test-harness.ts +++ b/src/auto-reply/reply/commands.test-harness.ts @@ -4,7 +4,7 @@ import { configureTaskRegistryRuntime } from "../../tasks/task-registry.store.js import type { MsgContext } from "../templating.js"; import { buildCommandContext } from "./commands-context.js"; import type { HandleCommandsParams } from "./commands-types.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; export const baseCommandTestConfig = { commands: { text: true }, @@ -42,7 +42,7 @@ export function buildCommandTestParams( ctx, cfg, command, - directives: parseInlineDirectives(commandBody), + directives: parseInlineSessionDirectives(commandBody), elevated: { enabled: true, allowed: true, failures: [] }, sessionKey: "agent:main:main", workspaceDir: options?.workspaceDir ?? "/tmp", diff --git a/src/auto-reply/reply/directive-handling.mixed-inline.test.ts b/src/auto-reply/reply/directive-handling.mixed-inline.test.ts index 4dff4d551020..e8a50698c4d2 100644 --- a/src/auto-reply/reply/directive-handling.mixed-inline.test.ts +++ b/src/auto-reply/reply/directive-handling.mixed-inline.test.ts @@ -9,7 +9,7 @@ import { triggerSessionPatchHook } from "../../gateway/session-patch-hooks.js"; import { enqueueSystemEvent } from "../../infra/system-events.js"; import { MODEL_SELECTION_LOCKED_MESSAGE } from "../../sessions/model-overrides.js"; import type { InlineDirectives } from "./directive-handling.parse.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { applyInlineDirectiveOverrides } from "./get-reply-directives-apply.js"; import { resolveReplyDirectiveRouting } from "./get-reply-directives-routing.js"; import { refreshQueuedFollowupSession } from "./queue.js"; @@ -89,7 +89,7 @@ async function applyMixedDirectives(params: { const sessionStore = { [sessionKey]: sessionEntry }; const directives = params.directives ?? - parseInlineDirectives(params.body, { + parseInlineSessionDirectives(params.body, { modelAliases: params.modelAliases, }); const allowedModels = params.allowedModels ?? []; diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index e8f46fc109c3..956d3d5430dd 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -307,7 +307,7 @@ let maybeHandleModelDirectiveInfo: typeof import("./directive-handling.model.js" let createModelVisibilityPolicy: typeof import("../../agents/model-visibility-policy.js").createModelVisibilityPolicy; let buildModelAliasIndex: typeof import("../../agents/model-selection.js").buildModelAliasIndex; let resolveModelSelectionFromDirective: typeof import("./directive-handling.model-selection.js").resolveModelSelectionFromDirective; -let parseInlineDirectives: typeof import("./directive-handling.parse.js").parseInlineDirectives; +let parseInlineSessionDirectives: typeof import("./directive-handling.parse.js").parseInlineSessionDirectives; let applyInlineDirectiveOverrides: typeof import("./get-reply-directives-apply.js").applyInlineDirectiveOverrides; let createFastTestModelSelectionState: typeof import("./model-selection.js").createFastTestModelSelectionState; @@ -319,7 +319,7 @@ beforeAll(async () => { ({ buildModelAliasIndex } = await import("../../agents/model-selection.js")); ({ resolveModelSelectionFromDirective } = await import("./directive-handling.model-selection.js")); - ({ parseInlineDirectives } = await import("./directive-handling.parse.js")); + ({ parseInlineSessionDirectives } = await import("./directive-handling.parse.js")); ({ applyInlineDirectiveOverrides } = await import("./get-reply-directives-apply.js")); ({ createFastTestModelSelectionState } = await import("./model-selection.js")); }); @@ -535,7 +535,7 @@ function resolveModelSelectionForCommand(params: { agentId?: string; }) { return resolveModelSelectionFromDirective({ - directives: parseInlineDirectives(params.command), + directives: parseInlineSessionDirectives(params.command), cfg: params.cfg ?? ({ commands: { text: true } } as unknown as OpenClawConfig), agentId: params.agentId, agentDir: TEST_AGENT_DIR, @@ -564,11 +564,11 @@ async function persistModelDirectiveForTest(params: { if (params.profiles) { setAuthProfiles(params.profiles); } - const originalDirectives = parseInlineDirectives(params.command); + const originalDirectives = parseInlineSessionDirectives(params.command); const commandBody = originalDirectives.cleaned.trim() ? params.command : `${params.command} continue with the request`; - const directives = parseInlineDirectives(commandBody); + const directives = parseInlineSessionDirectives(commandBody); const cfg = params.cfg ?? baseConfig(); const sessionEntry = params.sessionEntry ?? createSessionEntry(); const provider = params.provider ?? "anthropic"; @@ -662,7 +662,7 @@ function createDirectiveHandlingParams( const sessionEntry = overrides.sessionEntry ?? createSessionEntry(); return { cfg: baseConfig(), - directives: parseInlineDirectives(""), + directives: parseInlineSessionDirectives(""), sessionEntry, sessionStore: { [sessionKey]: sessionEntry }, sessionKey, @@ -689,7 +689,7 @@ async function persistInternalOperatorWriteDirective( const sessionEntry = overrides.sessionEntry ?? createSessionEntry(); await handleDirectiveOnly( createDirectiveHandlingParams({ - directives: parseInlineDirectives(command), + directives: parseInlineSessionDirectives(command), sessionEntry, surface: "webchat", gatewayClientScopes: ["operator.write"], @@ -714,7 +714,7 @@ async function resolveModelInfoReply( overrides: Partial[0]> = {}, ) { return maybeHandleModelDirectiveInfo({ - directives: parseInlineDirectives("/model"), + directives: parseInlineSessionDirectives("/model"), cfg: baseConfig(), agentDir: TEST_AGENT_DIR, activeAgentId: "main", @@ -796,7 +796,7 @@ async function withWorkspaceAuthFixture( function nestedOpenRouterStatusFixture(configureDirectProvider: boolean) { return { - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openrouter", model: "google/gemini-3-flash-preview", defaultProvider: "openrouter", @@ -847,7 +847,7 @@ describe("/model chat UX", () => { it("marks an auth profile without a model selection as an error", async () => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model list@work"), + directives: parseInlineSessionDirectives("/model list@work"), }); expect(reply).toEqual({ @@ -869,7 +869,7 @@ describe("/model chat UX", () => { "rejects action options on informational model commands: $command", async ({ command, text }) => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives(command), + directives: parseInlineSessionDirectives(command), }); expect(reply).toEqual({ text, isError: true }); @@ -939,7 +939,7 @@ describe("/model chat UX", () => { it("treats /model list as a models browser alias, not a model id", async () => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model list"), + directives: parseInlineSessionDirectives("/model list"), }); expect(reply?.text).toContain("Providers:"); @@ -959,7 +959,7 @@ describe("/model chat UX", () => { async (workspaceDir) => { modelsCommandMock.delegateToActual = true; const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model list"), + directives: parseInlineSessionDirectives("/model list"), workspaceDir, cfg: { ...baseConfig(), @@ -1001,7 +1001,7 @@ describe("/model chat UX", () => { it("shows status for the allowed catalog without duplicate missing auth labels", async () => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), cfg: { commands: { text: true }, agents: { @@ -1028,7 +1028,7 @@ describe("/model chat UX", () => { it("expands provider wildcard models without retaining a rejected default", async () => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "anthropic", model: "claude-sonnet-4-6", defaultProvider: "openai", @@ -1078,7 +1078,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), cfg, allowedModelKeys: policy.allowedKeys, allowedModelCatalog: policy.allowedCatalog, @@ -1125,7 +1125,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), cfg, activeAgentId: "main", defaultProvider: "provider-a", @@ -1181,7 +1181,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openai", model: "gpt-5.5", defaultProvider: "openai", @@ -1224,7 +1224,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openai", model: "gpt-5.5", defaultProvider: "openai", @@ -1260,7 +1260,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openai", model: "gpt-5.5", defaultProvider: "openai", @@ -1299,7 +1299,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openai", model: "gpt-5.5", defaultProvider: "openai", @@ -1340,7 +1340,7 @@ describe("/model chat UX", () => { }); const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), provider: "openai", model: "gpt-5.5", defaultProvider: "openai", @@ -1382,7 +1382,7 @@ describe("/model chat UX", () => { }, async (workspaceDir) => { const reply = await resolveModelInfoReply({ - directives: parseInlineDirectives("/model status"), + directives: parseInlineSessionDirectives("/model status"), workspaceDir, cfg: { ...baseConfig(), @@ -1406,7 +1406,7 @@ describe("/model chat UX", () => { }); it("auto-applies closest match for typos", () => { - const directives = parseInlineDirectives("/model anthropic/claud-opus-4-5"); + const directives = parseInlineSessionDirectives("/model anthropic/claud-opus-4-5"); const cfg = { commands: { text: true } } as unknown as OpenClawConfig; const resolved = resolveModelSelectionFromDirective({ @@ -1557,7 +1557,7 @@ describe("/model chat UX", () => { setAuthProfiles(createDateAuthProfiles("openai")); const resolved = resolveModelSelectionFromDirective({ - directives: parseInlineDirectives(`/model gpt@${OPENAI_DATE_PROFILE_ID}`), + directives: parseInlineSessionDirectives(`/model gpt@${OPENAI_DATE_PROFILE_ID}`), cfg: { commands: { text: true } } as unknown as OpenClawConfig, agentDir: TEST_AGENT_DIR, defaultProvider: "anthropic", @@ -1931,7 +1931,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { function runHandleCommand(command: string, overrides: Partial = {}) { return handleDirectiveOnly( - createHandleParams({ ...overrides, directives: parseInlineDirectives(command) }), + createHandleParams({ ...overrides, directives: parseInlineSessionDirectives(command) }), ); } @@ -2035,7 +2035,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { const sessionEntry = createSessionEntry(); const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o --runtime openclaw"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o --runtime openclaw"), sessionEntry, }), ); @@ -2062,7 +2062,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { const initialSessionEntry = { ...sessionEntry }; const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o --runtime claude-cli"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o --runtime claude-cli"), sessionEntry, }), ); @@ -2077,7 +2077,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { const sessionEntry = createSessionEntry({ agentRuntimeOverride: "codex" }); await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o"), sessionEntry, }), ); @@ -2097,7 +2097,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o --runtime openclaw"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o --runtime openclaw"), sessionEntry, }), ); @@ -2126,7 +2126,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { try { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o"), sessionEntry, sessionStore, storePath, @@ -2270,7 +2270,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { expect(sessionEntry.thinkingLevel).toBe("xhigh"); }); it("retargets queued followups when /model mutates session state", async () => { - const directives = parseInlineDirectives("/model openai/gpt-4o"); + const directives = parseInlineSessionDirectives("/model openai/gpt-4o"); const sessionEntry = createSessionEntry(); await handleDirectiveOnly( @@ -2319,7 +2319,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { try { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/model openai/gpt-4o"), + directives: parseInlineSessionDirectives("/model openai/gpt-4o"), sessionEntry, sessionStore, storePath, @@ -2363,7 +2363,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { try { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/elevated off"), + directives: parseInlineSessionDirectives("/elevated off"), sessionEntry, sessionStore, storePath, @@ -2400,7 +2400,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { try { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/elevated off"), + directives: parseInlineSessionDirectives("/elevated off"), sessionEntry, sessionStore: { [sessionKey]: sessionEntry }, storePath, @@ -2442,7 +2442,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { try { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/fast on"), + directives: parseInlineSessionDirectives("/fast on"), sessionEntry, sessionStore: { [sessionKey]: sessionEntry }, storePath, @@ -2521,7 +2521,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { }); it("strips inline elevated directives while keeping user text", () => { - const directives = parseInlineDirectives("hello there /elevated off"); + const directives = parseInlineSessionDirectives("hello there /elevated off"); expect(directives.hasElevatedDirective).toBe(true); expect(directives.elevatedLevel).toBe("off"); @@ -2598,7 +2598,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { const result = await handleDirectiveOnly( createHandleParams({ - directives: parseInlineDirectives("/think"), + directives: parseInlineSessionDirectives("/think"), provider: "openai", model: "gpt-5.6-luna", currentThinkLevel: "ultra", @@ -2861,7 +2861,7 @@ describe("canonical session directive persistence policy", () => { modelOverride: "gpt-5.5", }; await replaceSessionEntry({ sessionKey, storePath }, concurrentEntry); - const directives = parseInlineDirectives("hello /model openai/gpt-4o"); + const directives = parseInlineSessionDirectives("hello /model openai/gpt-4o"); try { const result = await handleDirectiveOnly( @@ -2907,7 +2907,7 @@ describe("canonical session directive persistence policy", () => { }; await replaceSessionEntry({ sessionKey, storePath }, concurrentEntry); const sessionStore = { [sessionKey]: sessionEntry }; - const directives = parseInlineDirectives("hello /model openai/gpt-4o"); + const directives = parseInlineSessionDirectives("hello /model openai/gpt-4o"); const patchEvents: InternalHookEvent[] = []; registerInternalHook("session:patch", async (event) => { patchEvents.push(event); diff --git a/src/auto-reply/reply/directive-handling.parse.ts b/src/auto-reply/reply/directive-handling.parse.ts index a40d36895196..ec32edd21216 100644 --- a/src/auto-reply/reply/directive-handling.parse.ts +++ b/src/auto-reply/reply/directive-handling.parse.ts @@ -113,7 +113,7 @@ export type InlineDirectives = { }; /** Parses supported inline directives in the same order they are stripped from text. */ -export function parseInlineDirectives( +export function parseInlineSessionDirectives( body: string, options?: { modelAliases?: string[]; diff --git a/src/auto-reply/reply/directive-handling.queue-validation.test.ts b/src/auto-reply/reply/directive-handling.queue-validation.test.ts index b419e340ccc1..84d2516e8323 100644 --- a/src/auto-reply/reply/directive-handling.queue-validation.test.ts +++ b/src/auto-reply/reply/directive-handling.queue-validation.test.ts @@ -1,13 +1,13 @@ // Tests queue directive validation and error copy for invalid queue settings. import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { maybeHandleQueueDirective } from "./directive-handling.queue-validation.js"; describe("maybeHandleQueueDirective", () => { it("reports invalid queue options and current queue settings", () => { const invalid = maybeHandleQueueDirective({ - directives: parseInlineDirectives("/queue collect debounce:bogus cap:zero drop:maybe"), + directives: parseInlineSessionDirectives("/queue collect debounce:bogus cap:zero drop:maybe"), cfg: {} as OpenClawConfig, channel: "quietchat", }); @@ -16,7 +16,7 @@ describe("maybeHandleQueueDirective", () => { expect(invalid?.text).toContain("Invalid drop policy"); const invalidMode = maybeHandleQueueDirective({ - directives: parseInlineDirectives("/queue backlog"), + directives: parseInlineSessionDirectives("/queue backlog"), cfg: {} as OpenClawConfig, channel: "quietchat", }); @@ -25,7 +25,7 @@ describe("maybeHandleQueueDirective", () => { ); const current = maybeHandleQueueDirective({ - directives: parseInlineDirectives("/queue"), + directives: parseInlineSessionDirectives("/queue"), cfg: { messages: { queue: { @@ -47,7 +47,7 @@ describe("maybeHandleQueueDirective", () => { it.each(["cap:1e3", "cap:0x10", "cap:4.9"])("rejects non-decimal-integer caps: %s", (cap) => { const invalid = maybeHandleQueueDirective({ - directives: parseInlineDirectives(`/queue collect ${cap}`), + directives: parseInlineSessionDirectives(`/queue collect ${cap}`), cfg: {} as OpenClawConfig, channel: "quietchat", }); diff --git a/src/auto-reply/reply/dispatch-from-config.execute.ts b/src/auto-reply/reply/dispatch-from-config.execute.ts index 0b1105c2a1df..7f5ab63acd28 100644 --- a/src/auto-reply/reply/dispatch-from-config.execute.ts +++ b/src/auto-reply/reply/dispatch-from-config.execute.ts @@ -11,6 +11,7 @@ import { copyReplyPayloadMetadata, getReplyPayloadMetadata, isReplyPayloadStatusNotice, + readAskUserQuestionId, } from "../reply-payload.js"; import { buildTerminalAgentRunFailureReplyPayload } from "./agent-runner-failure-reply.js"; import { takeCommandSessionMetadataChanges } from "./command-session-metadata.js"; @@ -22,7 +23,6 @@ import { import { hasAskUserPayload, prepareReplyPayloadForSideEffects as preparePayload, - readAskUserQuestionId, requiresDurableToolResultDelivery, shouldDeliverDespiteSourceReplySuppression, } from "./dispatch-from-config.payloads.js"; diff --git a/src/auto-reply/reply/dispatch-from-config.payloads.ts b/src/auto-reply/reply/dispatch-from-config.payloads.ts index dcfe1eea489d..a39cf0382816 100644 --- a/src/auto-reply/reply/dispatch-from-config.payloads.ts +++ b/src/auto-reply/reply/dispatch-from-config.payloads.ts @@ -47,15 +47,6 @@ export function shouldDeliverDespiteSourceReplySuppression( ); } -export function readAskUserQuestionId(payload: ReplyPayload): string | undefined { - const askUser = payload.channelData?.askUser; - if (!isRecord(askUser)) { - return undefined; - } - const questionId = askUser.questionId; - return typeof questionId === "string" ? questionId : undefined; -} - export function hasExecApprovalPayload(payload: ReplyPayload): boolean { return isRecord(payload.channelData?.execApproval); } diff --git a/src/auto-reply/reply/get-reply-directive-aliases.test.ts b/src/auto-reply/reply/get-reply-directive-aliases.test.ts index 1c3a470192ea..c4c8c1495730 100644 --- a/src/auto-reply/reply/get-reply-directive-aliases.test.ts +++ b/src/auto-reply/reply/get-reply-directive-aliases.test.ts @@ -4,7 +4,7 @@ import type { ModelAliasIndex } from "../../agents/model-selection.js"; import type { OpenClawConfig } from "../../config/config.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { FinalizedTemplateContext as TemplateContext } from "../templating.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { reserveSkillCommandNames, resolveConfiguredDirectiveAliases, @@ -301,7 +301,7 @@ describe("reply directive aliases", () => { it("parses configured alias session scope through the inline directive boundary", () => { const cfg = configWithModelAlias("fable"); - const parsed = parseInlineDirectives("/fable -s", { + const parsed = parseInlineSessionDirectives("/fable -s", { modelAliases: resolveConfiguredDirectiveAliases({ cfg, commandTextHasSlash: true, @@ -322,7 +322,7 @@ describe("reply directive aliases", () => { const reservedCommands = new Set(); const cfg = configWithModelAlias("demo_skill"); - const beforeSkillRegistration = parseInlineDirectives("/demo_skill", { + const beforeSkillRegistration = parseInlineSessionDirectives("/demo_skill", { modelAliases: resolveConfiguredDirectiveAliases({ cfg, commandTextHasSlash: true, @@ -344,7 +344,7 @@ describe("reply directive aliases", () => { ], }); - const afterSkillRegistration = parseInlineDirectives("/demo_skill", { + const afterSkillRegistration = parseInlineSessionDirectives("/demo_skill", { modelAliases: resolveConfiguredDirectiveAliases({ cfg, commandTextHasSlash: true, @@ -359,7 +359,7 @@ describe("reply directive aliases", () => { const cfg = configWithModelAlias(" help "); const reservedCommands = new Set(["help"]); - const parsed = parseInlineDirectives("/help", { + const parsed = parseInlineSessionDirectives("/help", { modelAliases: resolveConfiguredDirectiveAliases({ cfg, commandTextHasSlash: true, diff --git a/src/auto-reply/reply/get-reply-directives-apply.test.ts b/src/auto-reply/reply/get-reply-directives-apply.test.ts index e59f59c1c1a0..ab9afab3cf0a 100644 --- a/src/auto-reply/reply/get-reply-directives-apply.test.ts +++ b/src/auto-reply/reply/get-reply-directives-apply.test.ts @@ -1,7 +1,7 @@ // Tests applying parsed directives to get-reply execution options. import { beforeEach, describe, expect, it, vi } from "vitest"; import { MODEL_SELECTION_LOCKED_MESSAGE } from "../../sessions/model-overrides.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { applyInlineDirectiveOverrides } from "./get-reply-directives-apply.js"; import { createFastTestModelSelectionState } from "./model-selection.js"; import { buildTestCtx } from "./test-ctx.js"; @@ -72,7 +72,9 @@ describe("applyInlineDirectiveOverrides", () => { modelPolicyRepairConfigPath, expected, }) => { - const directives = parseInlineDirectives("hello /model openai/gpt-5.4 --runtime openclaw"); + const directives = parseInlineSessionDirectives( + "hello /model openai/gpt-5.4 --runtime openclaw", + ); const typing = { onReplyStart: async () => {}, startTypingLoop: async () => {}, @@ -174,7 +176,7 @@ describe("applyInlineDirectiveOverrides", () => { ); it("stops a mixed inline turn when its single directive transaction loses", async () => { - const directives = parseInlineDirectives("hello /elevated full"); + const directives = parseInlineSessionDirectives("hello /elevated full"); const errorText = "Session settings were not applied because the session changed. Retry."; mocks.handleDirective.mockImplementation(async (params) => { params.persistenceState.outcome = { kind: "rejected", errorText }; @@ -248,7 +250,7 @@ describe("applyInlineDirectiveOverrides", () => { it("stops a mixed inline turn when its transaction rejects unsupported thinking", async () => { const errorText = 'Thinking level "ultra" is not supported for openai/gpt-5.6-luna. Use one of: off, low, medium, high, max.'; - const directives = parseInlineDirectives("/think ultra please solve"); + const directives = parseInlineSessionDirectives("/think ultra please solve"); mocks.handleDirective.mockImplementation(async (params) => { params.persistenceState.outcome = { kind: "rejected", errorText }; return { text: errorText }; diff --git a/src/auto-reply/reply/get-reply-directives-routing.ts b/src/auto-reply/reply/get-reply-directives-routing.ts index fc8439e2687c..1d530c2aca29 100644 --- a/src/auto-reply/reply/get-reply-directives-routing.ts +++ b/src/auto-reply/reply/get-reply-directives-routing.ts @@ -1,14 +1,14 @@ // Resolves directive interpretation and prompt projection at the text-command boundary. import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { FinalizedRuntimeMsgContext } from "../templating.js"; -import { type InlineDirectives, parseInlineDirectives } from "./directive-handling.parse.js"; +import { type InlineDirectives, parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { clearExecInlineDirectives, clearInlineDirectives } from "./get-reply-directives-utils.js"; import { HISTORY_CONTEXT_MARKER } from "./history.js"; import { stripMentions, stripStructuralPrefixes } from "./mentions.js"; import { stripInlineStatus } from "./reply-inline.js"; type NativeDirectiveCommand = NonNullable< - Parameters[1] + Parameters[1] >["nativeCommand"]; function hasInlineDirective(directives: InlineDirectives): boolean { @@ -70,7 +70,7 @@ export function resolveReplyDirectiveRouting(params: { unauthorizedReasoningDirectiveAttempt: boolean; } { const allowStatusDirective = params.canInterpretTextDirectives; - let parsed = parseInlineDirectives(params.commandText, { + let parsed = parseInlineSessionDirectives(params.commandText, { modelAliases: params.modelAliases, allowStatusDirective, nativeCommand: params.nativeCommand, @@ -108,7 +108,7 @@ export function resolveReplyDirectiveRouting(params: { : stripped; if ( noMentions.trim() && - parseInlineDirectives(noMentions, { modelAliases: params.modelAliases }).cleaned.trim() + parseInlineSessionDirectives(noMentions, { modelAliases: params.modelAliases }).cleaned.trim() ) { parsed = isModelSelectionDirective(parsed) ? preserveMixedModelDirective(parsed, parsed.cleaned) @@ -134,7 +134,7 @@ export function resolveReplyDirectiveRouting(params: { let cleanedBody = preserveAgentText ? params.agentText : params.agentText - ? parseInlineDirectives(params.agentText, { + ? parseInlineSessionDirectives(params.agentText, { modelAliases: params.modelAliases, allowStatusDirective, }).cleaned diff --git a/src/auto-reply/reply/get-reply-directives.target-session.test.ts b/src/auto-reply/reply/get-reply-directives.target-session.test.ts index 18300384863d..1e2c80710ce1 100644 --- a/src/auto-reply/reply/get-reply-directives.target-session.test.ts +++ b/src/auto-reply/reply/get-reply-directives.target-session.test.ts @@ -216,7 +216,7 @@ vi.mock("./directive-handling.parse.js", async () => { const { parseInlineDirectivesForTargetSessionTest } = await import("./get-reply-directives.target-session.test-helpers.js"); return { - parseInlineDirectives: vi.fn(parseInlineDirectivesForTargetSessionTest), + parseInlineSessionDirectives: vi.fn(parseInlineDirectivesForTargetSessionTest), }; }); diff --git a/src/auto-reply/reply/get-reply-exec-overrides.test.ts b/src/auto-reply/reply/get-reply-exec-overrides.test.ts index 966495da02c7..e0683571e022 100644 --- a/src/auto-reply/reply/get-reply-exec-overrides.test.ts +++ b/src/auto-reply/reply/get-reply-exec-overrides.test.ts @@ -1,7 +1,7 @@ // Tests execution override directives passed through get-reply. import { describe, expect, it } from "vitest"; import type { SessionEntry } from "../../config/sessions.js"; -import { parseInlineDirectives } from "./directive-handling.parse.js"; +import { parseInlineSessionDirectives } from "./directive-handling.parse.js"; import { type ReplyExecOverrides, resolveReplyExecOverrides } from "./get-reply-exec-overrides.js"; const AGENT_EXEC_DEFAULTS = { @@ -23,7 +23,7 @@ describe("reply exec overrides", () => { it("uses per-agent exec defaults when session and message are unset", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("run a command"), + directives: parseInlineSessionDirectives("run a command"), sessionEntry: createSessionEntry(), agentExecDefaults: AGENT_EXEC_DEFAULTS, }), @@ -38,7 +38,7 @@ describe("reply exec overrides", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("/exec host=auto security=full"), + directives: parseInlineSessionDirectives("/exec host=auto security=full"), sessionEntry, agentExecDefaults: AGENT_EXEC_DEFAULTS, }), @@ -50,7 +50,7 @@ describe("reply exec overrides", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("run a command"), + directives: parseInlineSessionDirectives("run a command"), sessionEntry, agentExecDefaults: AGENT_EXEC_DEFAULTS, }), @@ -70,7 +70,7 @@ describe("reply exec overrides", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("run a command"), + directives: parseInlineSessionDirectives("run a command"), sessionEntry, agentExecDefaults: AGENT_EXEC_DEFAULTS, }), @@ -85,7 +85,7 @@ describe("reply exec overrides", () => { it("carries the node cwd separately from the Gateway workspace", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("run a command"), + directives: parseInlineSessionDirectives("run a command"), sessionEntry: createSessionEntry({ execHost: "node", execNode: "macbook", @@ -104,7 +104,7 @@ describe("reply exec overrides", () => { it("does not carry a stored cwd across an inline node override", () => { expect( resolveReplyExecOverrides({ - directives: parseInlineDirectives("/exec node=other-node"), + directives: parseInlineSessionDirectives("/exec node=other-node"), sessionEntry: createSessionEntry({ execHost: "node", execNode: "macbook", diff --git a/src/auto-reply/reply/get-reply-fast-path.ts b/src/auto-reply/reply/get-reply-fast-path.ts index 0979c710aa1c..4f779243b24f 100644 --- a/src/auto-reply/reply/get-reply-fast-path.ts +++ b/src/auto-reply/reply/get-reply-fast-path.ts @@ -9,7 +9,10 @@ import { normalizeAnyChannelId } from "../../channels/registry.js"; import { applyMergePatch } from "../../config/merge-patch.js"; import { resolveStorePath } from "../../config/sessions/paths.js"; import { resolveResetPreservedSelection } from "../../config/sessions/reset-preserved-selection.js"; -import { loadSessionEntry, listSessionEntries } from "../../config/sessions/session-accessor.js"; +import { + loadSessionEntry, + listSessionEntriesCore, +} from "../../config/sessions/session-accessor.js"; import { buildSessionCreationStamp } from "../../config/sessions/session-entry-provenance.js"; import { resolveSessionKey } from "../../config/sessions/session-key.js"; import { @@ -189,7 +192,10 @@ export function initFastReplySessionState(params: { }); const storePath = resolveStorePath(cfg.session?.store, { agentId }); const sessionStore: Record = Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey: entryKey, entry }) => [entryKey, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey: entryKey, entry }) => [ + entryKey, + entry, + ]), ); const existingEntry = loadSessionEntry({ storePath, sessionKey }); const commandSource = ctx.commandText ?? ""; diff --git a/src/auto-reply/reply/get-reply-run.media-only.test.ts b/src/auto-reply/reply/get-reply-run.media-only.test.ts index 5f60038ceb57..920c894ca4ad 100644 --- a/src/auto-reply/reply/get-reply-run.media-only.test.ts +++ b/src/auto-reply/reply/get-reply-run.media-only.test.ts @@ -74,9 +74,9 @@ const loadSessionEntryMock = vi.hoisted(() => vi.fn()); const updateAmbientTranscriptWatermarkMock = vi.hoisted(() => vi.fn().mockResolvedValue(null)); vi.mock("../../config/sessions/session-accessor.js", () => ({ - listSessionEntries: vi.fn().mockReturnValue([]), + listSessionEntriesCore: vi.fn().mockReturnValue([]), loadSessionEntry: loadSessionEntryMock, - patchSessionEntry: vi.fn(), + patchSessionEntryCore: vi.fn(), persistSessionTranscriptTurn: vi.fn(), })); diff --git a/src/auto-reply/reply/session-entry-persistence.ts b/src/auto-reply/reply/session-entry-persistence.ts index 33913ea0830d..19ff594a7347 100644 --- a/src/auto-reply/reply/session-entry-persistence.ts +++ b/src/auto-reply/reply/session-entry-persistence.ts @@ -1,7 +1,7 @@ // Atomic persistence for broad auto-reply session snapshots. import type { SessionEntry } from "../../config/sessions.js"; import { resolveSessionWorkStartError } from "../../config/sessions/lifecycle.js"; -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import { mergeSessionSnapshotChanges, sessionSnapshotTouchedFieldsConflict, @@ -31,7 +31,7 @@ export async function persistReplySessionEntry( let lifecycleError: string | undefined; let lifecycleEntry: SessionEntry | undefined; let lockedEntry: SessionEntry | undefined; - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (_entry, context) => { if (!context.existingEntry) { diff --git a/src/auto-reply/reply/session-updates.test.ts b/src/auto-reply/reply/session-updates.test.ts index 37b7c1285ea3..bda1eba909e9 100644 --- a/src/auto-reply/reply/session-updates.test.ts +++ b/src/auto-reply/reply/session-updates.test.ts @@ -70,7 +70,7 @@ vi.mock("../../config/sessions.js", () => ({ })); vi.mock("../../config/sessions/session-accessor.js", () => ({ - patchSessionEntry: vi.fn(), + patchSessionEntryCore: vi.fn(), updateSessionEntry: updateSessionEntryMock, })); diff --git a/src/auto-reply/reply/session-updates.ts b/src/auto-reply/reply/session-updates.ts index 7ee671886912..124c3650db90 100644 --- a/src/auto-reply/reply/session-updates.ts +++ b/src/auto-reply/reply/session-updates.ts @@ -7,7 +7,10 @@ import { } from "../../agents/exec-defaults.js"; import { SESSION_TOTAL_TOKENS_VERSION, type SessionEntry } from "../../config/sessions.js"; import { formatSqliteSessionFileMarker } from "../../config/sessions/legacy-sqlite-marker.js"; -import { patchSessionEntry, updateSessionEntry } from "../../config/sessions/session-accessor.js"; +import { + patchSessionEntryCore, + updateSessionEntry, +} from "../../config/sessions/session-accessor.js"; import { resolveSessionStorePathForScope } from "../../config/sessions/session-store-path.js"; import { projectCanonicalSessionEntryShape } from "../../config/sessions/store-entry-shape.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -378,7 +381,7 @@ export async function incrementCompactionCount(params: { ? resolveSessionStorePathForScope({ agentId, sessionKey, storePath }) : undefined; if (effectiveStorePath) { - const persistedEntry = await patchSessionEntry( + const persistedEntry = await patchSessionEntryCore( { ...(agentId ? { agentId } : {}), storePath: effectiveStorePath, sessionKey }, () => updates, { fallbackEntry: nextEntry }, diff --git a/src/auto-reply/reply/test/session.test-support.ts b/src/auto-reply/reply/test/session.test-support.ts index 64df0f1b1c4c..2a4eaa5997a9 100644 --- a/src/auto-reply/reply/test/session.test-support.ts +++ b/src/auto-reply/reply/test/session.test-support.ts @@ -4,7 +4,7 @@ import path from "node:path"; import type { OpenClawConfig } from "../../../config/config.js"; import type { InternalSessionEntry as SessionEntry } from "../../../config/sessions.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceSessionEntry, upsertSessionEntry, @@ -52,7 +52,7 @@ export async function writeSessionStore( export function readSessionStore(storePath: string): Record { const entries = Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [ sessionKey, projectSessionEntry(entry), ]), diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index a041ba5b192c..d7c94499cc5f 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -1,6 +1,6 @@ /** Auto-reply status/help message builders for commands, status, and tool inventory output. */ import { describeToolForVerbose } from "../agents/tool-description-summary.js"; -import { normalizeToolName } from "../agents/tool-policy-shared.js"; +import { normalizeToolPolicyName } from "../agents/tool-policy-shared.js"; import type { EffectiveToolInventoryResult } from "../agents/tools-effective-inventory.types.js"; export { @@ -52,7 +52,7 @@ export function buildToolsMessage( const tools: ToolsMessageItem[] = []; for (const tool of group.tools) { tools.push({ - id: normalizeToolName(tool.id), + id: normalizeToolPolicyName(tool.id), name: tool.label, description: tool.description || "Tool", rawDescription: tool.rawDescription || tool.description || "Tool", diff --git a/src/channels/feedback-reflection.test.ts b/src/channels/feedback-reflection.test.ts index e8e22752543c..c301750d1522 100644 --- a/src/channels/feedback-reflection.test.ts +++ b/src/channels/feedback-reflection.test.ts @@ -5,7 +5,7 @@ import { recordChannelFeedbackEvent, runChannelFeedbackReflection } from "./feed const appendTranscriptEvent = vi.hoisted(() => vi.fn(async () => undefined)); const dispatchRoutedChannelTurn = vi.hoisted(() => vi.fn()); const loadSessionEntry = vi.hoisted(() => vi.fn()); -const readSessionUpdatedAt = vi.hoisted(() => vi.fn()); +const readSessionUpdatedAtCore = vi.hoisted(() => vi.fn()); const resolveStorePath = vi.hoisted(() => vi.fn(() => "/state/main/sessions.json")); vi.mock("../config/sessions/paths.js", () => ({ resolveStorePath })); @@ -13,7 +13,7 @@ vi.mock("../config/sessions/session-accessor.js", () => ({ appendTranscriptEvent, loadSessionEntry, loadSessionEntryReadOnly: loadSessionEntry, - readSessionUpdatedAt, + readSessionUpdatedAtCore, })); vi.mock("./turn/lifecycle.js", () => ({ dispatchRoutedChannelTurn })); diff --git a/src/channels/inbound-event/envelope.test.ts b/src/channels/inbound-event/envelope.test.ts index 59362cf575f5..cde21886af95 100644 --- a/src/channels/inbound-event/envelope.test.ts +++ b/src/channels/inbound-event/envelope.test.ts @@ -5,7 +5,7 @@ import { resolveChannelInboundRouteEnvelope, } from "./envelope.js"; -const readSessionUpdatedAt = vi.hoisted(() => vi.fn(() => 60_000)); +const readSessionUpdatedAtCore = vi.hoisted(() => vi.fn(() => 60_000)); const resolveStorePath = vi.hoisted(() => vi.fn(() => "/state/main/sessions.json")); const resolveAgentRoute = vi.hoisted(() => vi.fn(() => ({ @@ -16,7 +16,7 @@ const resolveAgentRoute = vi.hoisted(() => ); vi.mock("../../config/sessions/paths.js", () => ({ resolveStorePath })); -vi.mock("../../config/sessions/session-accessor.js", () => ({ readSessionUpdatedAt })); +vi.mock("../../config/sessions/session-accessor.js", () => ({ readSessionUpdatedAtCore })); vi.mock("../../routing/resolve-route.js", () => ({ resolveAgentRoute })); const cfg = { @@ -42,7 +42,7 @@ describe("channel inbound envelope", () => { }), ).toBe("[Telegram Alice +1m Thu 1970-01-01T00:02:00Z] hello"); expect(resolveStorePath).toHaveBeenCalledWith(cfg.session?.store, { agentId: "main" }); - expect(readSessionUpdatedAt).toHaveBeenCalledWith({ + expect(readSessionUpdatedAtCore).toHaveBeenCalledWith({ storePath: "/state/main/sessions.json", sessionKey: "agent:main:telegram:direct:peer", }); @@ -79,6 +79,6 @@ describe("channel inbound envelope", () => { previousTimestamp: null, }), ).toBe("[Telegram Alice Thu 1970-01-01T00:00:30Z] older"); - expect(readSessionUpdatedAt).not.toHaveBeenCalled(); + expect(readSessionUpdatedAtCore).not.toHaveBeenCalled(); }); }); diff --git a/src/channels/inbound-event/envelope.ts b/src/channels/inbound-event/envelope.ts index 66a0834c817c..3033efa30c12 100644 --- a/src/channels/inbound-event/envelope.ts +++ b/src/channels/inbound-event/envelope.ts @@ -4,7 +4,7 @@ import { type AgentEnvelopeParams, } from "../../auto-reply/envelope.js"; import { resolveStorePath } from "../../config/sessions/paths.js"; -import { readSessionUpdatedAt } from "../../config/sessions/session-accessor.js"; +import { readSessionUpdatedAtCore } from "../../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { resolveAgentRoute, @@ -29,7 +29,7 @@ export function createChannelInboundEnvelopeBuilder(params: { input.previousTimestamp === null ? undefined : (input.previousTimestamp ?? - readSessionUpdatedAt({ storePath, sessionKey: params.route.sessionKey })); + readSessionUpdatedAtCore({ storePath, sessionKey: params.route.sessionKey })); return formatAgentEnvelope({ ...input, previousTimestamp, diff --git a/src/channels/plugins/contracts/test-helpers/bundled-channel-plugin-loader.ts b/src/channels/plugins/contracts/test-helpers/bundled-channel-plugin-loader.ts index f8ac98006d68..dca4f0109196 100644 --- a/src/channels/plugins/contracts/test-helpers/bundled-channel-plugin-loader.ts +++ b/src/channels/plugins/contracts/test-helpers/bundled-channel-plugin-loader.ts @@ -7,7 +7,7 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { - loadBundledPluginPublicSurface, + loadBundledPluginFacade, resolveBundledPluginPublicModulePath, } from "../../../../test-utils/bundled-plugin-public-surface.js"; import { listBundledChannelPluginIds as listCatalogBundledChannelPluginIds } from "../../bundled.js"; @@ -186,7 +186,7 @@ function isMissingBundledDirectoryContractArtifact(error: unknown, id: ChannelId async function loadBundledChannelDirectoryContractSurface( id: ChannelId, ): Promise { - return await loadBundledPluginPublicSurface({ + return await loadBundledPluginFacade({ pluginId: id, artifactBasename: "directory-contract-api.js", }).catch((error: unknown) => { @@ -236,7 +236,7 @@ export async function getBundledChannelPluginAsync( // Cache both resolved plugins and in-flight loads so sharded contract suites // do not repeatedly import the same generated plugin artifact. - const loading = loadBundledPluginPublicSurface({ + const loading = loadBundledPluginFacade({ pluginId: id, artifactBasename: "channel-plugin-api.js", }) @@ -318,7 +318,7 @@ async function getOptionalBundledChannelArtifactAsync( id: ChannelId, artifactBasename: string, ): Promise { - return await loadBundledPluginPublicSurface({ + return await loadBundledPluginFacade({ pluginId: id, artifactBasename, }).catch((error: unknown) => { diff --git a/src/channels/session-envelope.ts b/src/channels/session-envelope.ts index 476208867ecf..9cefebb6dcbc 100644 --- a/src/channels/session-envelope.ts +++ b/src/channels/session-envelope.ts @@ -1,7 +1,7 @@ // Session-envelope context resolver for inbound channel turns. import { resolveEnvelopeFormatOptions } from "../auto-reply/envelope.js"; import { resolveStorePath } from "../config/sessions.js"; -import { readSessionUpdatedAt } from "../config/sessions/session-accessor.js"; +import { readSessionUpdatedAtCore } from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; /** Resolves envelope options and previous timestamp for one inbound channel session. */ @@ -16,7 +16,7 @@ export function resolveInboundSessionEnvelopeContext(params: { return { storePath, envelopeOptions: resolveEnvelopeFormatOptions(params.cfg), - previousTimestamp: readSessionUpdatedAt({ + previousTimestamp: readSessionUpdatedAtCore({ storePath, sessionKey: params.sessionKey, }), diff --git a/src/cli/capability-cli.test.ts b/src/cli/capability-cli.test.ts index 190f9ea2c8fc..9861651f3581 100644 --- a/src/cli/capability-cli.test.ts +++ b/src/cli/capability-cli.test.ts @@ -372,9 +372,9 @@ vi.mock("../media/media-services.js", async (importOriginal) => { }; }); -vi.mock("../plugins/memory-embedding-providers.js", () => ({ - listMemoryEmbeddingProviders: - mocks.listMemoryEmbeddingProviders as unknown as typeof import("../plugins/memory-embedding-providers.js").listMemoryEmbeddingProviders, +vi.mock("../plugins/memory-embedding-provider-runtime.js", () => ({ + listRegisteredMemoryEmbeddingProviderAdapters: + mocks.listMemoryEmbeddingProviders as unknown as typeof import("../plugins/memory-embedding-provider-runtime.js").listRegisteredMemoryEmbeddingProviderAdapters, })); vi.mock("../plugins/embedding-provider-runtime.js", () => ({ diff --git a/src/cli/capability-cli/embedding.ts b/src/cli/capability-cli/embedding.ts index dfee2c518836..28a39fe56d65 100644 --- a/src/cli/capability-cli/embedding.ts +++ b/src/cli/capability-cli/embedding.ts @@ -5,7 +5,7 @@ import { resolveMemorySearchConfig } from "../../agents/memory-search.js"; import { getRuntimeConfig } from "../../config/config.js"; import { createEmbeddingProvider } from "../../plugin-sdk/memory-core-bundled-runtime.js"; import { listEmbeddingProviders } from "../../plugins/embedding-provider-runtime.js"; -import { listMemoryEmbeddingProviders } from "../../plugins/memory-embedding-providers.js"; +import { listRegisteredMemoryEmbeddingProviderAdapters } from "../../plugins/memory-embedding-provider-runtime.js"; import { defaultRuntime } from "../../runtime.js"; import { runCommandWithRuntime } from "../cli-utils.js"; import { getMemoryEmbeddingCommandSecretTargetIds } from "../command-secret-targets.js"; @@ -130,7 +130,7 @@ export function registerEmbeddingCapabilityCommands(capability: Command): void { const resolvedMemory = resolveMemorySearchConfig(cfg, agentId); const selectedProvider = resolvedMemory?.provider; const providers = new Map( - listMemoryEmbeddingProviders().map((provider) => [ + listRegisteredMemoryEmbeddingProviderAdapters().map((provider) => [ provider.id, { id: provider.id, diff --git a/src/cli/daemon-cli/lifecycle-safe-restart.ts b/src/cli/daemon-cli/lifecycle-safe-restart.ts index 037d779f9706..379f6640f16e 100644 --- a/src/cli/daemon-cli/lifecycle-safe-restart.ts +++ b/src/cli/daemon-cli/lifecycle-safe-restart.ts @@ -24,7 +24,7 @@ export function resolveGatewayRestartIntentOptions( } /** Request an OpenClaw-aware restart through the running Gateway. */ -export async function requestSafeGatewayRestart(opts: DaemonLifecycleOptions): Promise { +export async function runSafeGatewayRestart(opts: DaemonLifecycleOptions): Promise { if (opts.force) { throw new Error("--safe cannot be combined with --force; omit --safe to force restart now"); } diff --git a/src/cli/daemon-cli/lifecycle.ts b/src/cli/daemon-cli/lifecycle.ts index cd067ffea18a..187eef0dc71d 100644 --- a/src/cli/daemon-cli/lifecycle.ts +++ b/src/cli/daemon-cli/lifecycle.ts @@ -54,7 +54,7 @@ import { runServiceUninstall, } from "./lifecycle-core.js"; import { - requestSafeGatewayRestart, + runSafeGatewayRestart, resolveGatewayRestartIntentOptions, } from "./lifecycle-safe-restart.js"; import { createDaemonActionContext, createNullWriter } from "./response.js"; @@ -555,7 +555,7 @@ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promi throw new Error("--skip-deferral requires --safe"); } if (opts.safe) { - return await requestSafeGatewayRestart(opts); + return await runSafeGatewayRestart(opts); } if (isGatewayExternallySupervised()) { return await runExternalSupervisorRestart(opts); diff --git a/src/cli/plugins-cli-test-helpers.ts b/src/cli/plugins-cli-test-helpers.ts index 2b52c3422382..e2973dad3172 100644 --- a/src/cli/plugins-cli-test-helpers.ts +++ b/src/cli/plugins-cli-test-helpers.ts @@ -89,11 +89,13 @@ function invokeMock(mock: unknown, ...args: TA return (mock as (...args: TArgs) => TResult)(...args); } -export const loadConfig: Mock = vi.fn(() => ({}) as OpenClawConfig); -export const readConfigFileSnapshot: AsyncUnknownMock = vi.fn(); -export const readConfigFileSnapshotForWrite: AsyncUnknownMock = vi.fn(); +export const pluginCliConfigMock: Mock = vi.fn( + () => ({}) as OpenClawConfig, +); +export const readConfigFileSnapshotMock: AsyncUnknownMock = vi.fn(); +export const readConfigFileSnapshotForWriteMock: AsyncUnknownMock = vi.fn(); export const writeConfigFile: AsyncUnknownMock = vi.fn(async () => undefined); -export const replaceConfigFile: AsyncUnknownMock = vi.fn( +export const replaceConfigFileMock: AsyncUnknownMock = vi.fn( async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig), ) as AsyncUnknownMock; const resolveStateDir: Mock<() => string> = vi.fn(() => "/tmp/openclaw-state"); @@ -103,7 +105,7 @@ const parseGitPluginSpec: Mock = vi.fn(); const listMarketplacePlugins: Mock = vi.fn(); export const resolveMarketplaceInstallShortcut: Mock = vi.fn(); export const enablePluginInConfigMock: UnknownMock = vi.fn(); -export const recordPluginInstall: UnknownMock = vi.fn(); +export const recordPluginInstallMock: UnknownMock = vi.fn(); const loadInstalledPluginIndexInstallRecords: AsyncUnknownMock = vi.fn(async () => clonePluginInstallRecords(mockInstalledPluginIndexInstallRecords), ); @@ -112,11 +114,11 @@ const writePersistedInstalledPluginIndexInstallRecords: Mock = +export const readPersistedInstalledPluginIndexMock: Mock = vi.fn(async () => null); export const writePersistedInstalledPluginIndexInstallRecordsWithLease: Mock = vi.fn(async (records) => { - const previous = await readPersistedInstalledPluginIndex(); + const previous = await readPersistedInstalledPluginIndexMock(); mockInstalledPluginIndexInstallRecords = clonePluginInstallRecords(records); mockInstalledPluginIndexRevision += 1; return { previous, revision: mockInstalledPluginIndexRevision }; @@ -132,7 +134,7 @@ export const restorePersistedInstalledPluginIndexIfCurrent: Mock = vi.fn(); export const updateNpmInstalledHookPacks: Mock = vi.fn(); @@ -160,13 +162,13 @@ export const installPluginFromNpmSpecMock: AsyncUnknownMock = vi.fn(); export const installPluginFromNpmPackArchiveMock: AsyncUnknownMock = vi.fn(); export const installPluginFromPathMock: AsyncUnknownMock = vi.fn(); export const installPluginFromClawHubMock: AsyncUnknownMock = vi.fn(); -export const parseClawHubPluginSpec: Mock = vi.fn(); -export const reportClawHubPluginInstallTelemetry: Mock = +export const parseClawHubPluginSpecMock: Mock = vi.fn(); +export const reportClawHubPluginInstallTelemetryMock: Mock = vi.fn(async () => undefined); export const findBundledPluginSourceMock: UnknownMock = vi.fn(); export const installHooksFromNpmSpecMock: AsyncUnknownMock = vi.fn(); export const installHooksFromPathMock: AsyncUnknownMock = vi.fn(); -export const recordHookInstall: UnknownMock = vi.fn(); +export const recordHookInstallMock: UnknownMock = vi.fn(); const { defaultRuntime, pluginsCliRuntimeLogs, runtimeErrors, resetRuntimeCapture } = vi.hoisted( () => { @@ -262,8 +264,8 @@ vi.mock("../config/config.js", () => ({ ); } }, - getRuntimeConfig: () => loadConfig(), - loadConfig: () => loadConfig(), + getRuntimeConfig: () => pluginCliConfigMock(), + loadConfig: () => pluginCliConfigMock(), readConfigFileSnapshot: (( ...args: Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]> ) => @@ -271,7 +273,7 @@ vi.mock("../config/config.js", () => ({ Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]>, ReturnType<(typeof import("../config/config.js"))["readConfigFileSnapshot"]> >( - readConfigFileSnapshot, + readConfigFileSnapshotMock, ...args, )) as (typeof import("../config/config.js"))["readConfigFileSnapshot"], readConfigFileSnapshotForWrite: (( @@ -281,7 +283,7 @@ vi.mock("../config/config.js", () => ({ Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"]>, ReturnType<(typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"]> >( - readConfigFileSnapshotForWrite, + readConfigFileSnapshotForWriteMock, ...args, )) as (typeof import("../config/config.js"))["readConfigFileSnapshotForWrite"], writeConfigFile: ((config: OpenClawConfig) => @@ -295,7 +297,10 @@ vi.mock("../config/config.js", () => ({ invokeMock< [Parameters<(typeof import("../config/config.js"))["replaceConfigFile"]>[0]], ReturnType<(typeof import("../config/config.js"))["replaceConfigFile"]> - >(replaceConfigFile, params)) as (typeof import("../config/config.js"))["replaceConfigFile"], + >( + replaceConfigFileMock, + params, + )) as (typeof import("../config/config.js"))["replaceConfigFile"], })); vi.mock("../config/paths.js", async (importOriginal) => { @@ -352,7 +357,7 @@ vi.mock("../plugins/installs.js", async (importOriginal) => { Parameters<(typeof import("../plugins/installs.js"))["recordPluginInstall"]>, ReturnType<(typeof import("../plugins/installs.js"))["recordPluginInstall"]> >( - recordPluginInstall, + recordPluginInstallMock, ...args, )) as (typeof import("../plugins/installs.js"))["recordPluginInstall"], }; @@ -400,7 +405,7 @@ vi.mock("../plugins/installed-plugin-index-store.js", async (importOriginal) => return { ...actual, readPersistedInstalledPluginIndex: ((...args: unknown[]) => - invokeMock(readPersistedInstalledPluginIndex, ...args)) as ( + invokeMock(readPersistedInstalledPluginIndexMock, ...args)) as ( ...args: unknown[] ) => unknown, restorePersistedInstalledPluginIndexIfCurrent: ((...args: unknown[]) => @@ -412,7 +417,7 @@ vi.mock("../plugins/installed-plugin-index-store.js", async (importOriginal) => vi.mock("../plugins/manifest-registry.js", () => ({ loadPluginManifestRegistry: ((...args: unknown[]) => - invokeMock(loadPluginManifestRegistry, ...args)) as ( + invokeMock(loadPluginManifestRegistryMock, ...args)) as ( ...args: unknown[] ) => unknown, })); @@ -504,7 +509,7 @@ vi.mock("../plugins/status-snapshot.js", () => ({ vi.mock("../plugins/plugin-registry.js", () => ({ loadPluginManifestRegistryForPluginRegistry: ((...args: unknown[]) => - invokeMock(loadPluginManifestRegistry, ...args)) as ( + invokeMock(loadPluginManifestRegistryMock, ...args)) as ( ...args: unknown[] ) => unknown, loadPluginRegistrySnapshotWithMetadata: () => ({ @@ -529,7 +534,7 @@ vi.mock("../plugins/plugin-registry.js", () => ({ Parameters<(typeof import("../plugins/plugin-registry.js"))["refreshPluginRegistry"]>, ReturnType<(typeof import("../plugins/plugin-registry.js"))["refreshPluginRegistry"]> >( - refreshPluginRegistry, + refreshPluginRegistryMock, ...args, )) as (typeof import("../plugins/plugin-registry.js"))["refreshPluginRegistry"], })); @@ -569,7 +574,7 @@ vi.mock("../plugins/uninstall.js", async (importOriginal) => { Parameters<(typeof import("../plugins/uninstall.js"))["planPluginUninstall"]>, ReturnType<(typeof import("../plugins/uninstall.js"))["planPluginUninstall"]> >( - planPluginUninstall, + planPluginUninstallMock, ...args, )) as (typeof import("../plugins/uninstall.js"))["planPluginUninstall"], applyPluginUninstallDirectoryRemoval: (( @@ -753,7 +758,10 @@ vi.mock("../hooks/installs.js", () => ({ invokeMock< Parameters<(typeof import("../hooks/installs.js"))["recordHookInstall"]>, ReturnType<(typeof import("../hooks/installs.js"))["recordHookInstall"]> - >(recordHookInstall, ...args)) as (typeof import("../hooks/installs.js"))["recordHookInstall"], + >( + recordHookInstallMock, + ...args, + )) as (typeof import("../hooks/installs.js"))["recordHookInstall"], })); vi.mock("../plugins/clawhub.js", () => ({ @@ -782,7 +790,7 @@ vi.mock("../infra/clawhub-spec.js", () => ({ Parameters<(typeof import("../infra/clawhub-spec.js"))["parseClawHubPluginSpec"]>, ReturnType<(typeof import("../infra/clawhub-spec.js"))["parseClawHubPluginSpec"]> >( - parseClawHubPluginSpec, + parseClawHubPluginSpecMock, ...args, )) as (typeof import("../infra/clawhub-spec.js"))["parseClawHubPluginSpec"], })); @@ -801,7 +809,7 @@ vi.mock("../infra/clawhub-packages.js", () => ({ (typeof import("../infra/clawhub-packages.js"))["reportClawHubPluginInstallTelemetry"] > >( - reportClawHubPluginInstallTelemetry, + reportClawHubPluginInstallTelemetryMock, ...args, )) as (typeof import("../infra/clawhub-packages.js"))["reportClawHubPluginInstallTelemetry"], })); @@ -818,36 +826,36 @@ export async function runPluginsCommand(argv: string[]) { export function resetPluginsCliTestState() { resetRuntimeCapture(); restoreRuntimeCaptureMocks(); - loadConfig.mockReset(); - readConfigFileSnapshot.mockReset(); - readConfigFileSnapshotForWrite.mockReset(); + pluginCliConfigMock.mockReset(); + readConfigFileSnapshotMock.mockReset(); + readConfigFileSnapshotForWriteMock.mockReset(); writeConfigFile.mockReset(); - replaceConfigFile.mockReset(); + replaceConfigFileMock.mockReset(); resolveStateDir.mockReset(); installPluginFromMarketplaceMock.mockReset(); listMarketplacePlugins.mockReset(); resolveMarketplaceInstallShortcut.mockReset(); enablePluginInConfigMock.mockReset(); - recordPluginInstall.mockReset(); + recordPluginInstallMock.mockReset(); mockInstalledPluginIndexInstallRecords = {}; mockInstalledPluginIndexRevision = 0; loadInstalledPluginIndexInstallRecords.mockReset(); writePersistedInstalledPluginIndexInstallRecords.mockReset(); writePersistedInstalledPluginIndexInstallRecordsWithLease.mockReset(); - readPersistedInstalledPluginIndex.mockReset(); + readPersistedInstalledPluginIndexMock.mockReset(); restorePersistedInstalledPluginIndexIfCurrent.mockReset(); - loadPluginManifestRegistry.mockReset(); + loadPluginManifestRegistryMock.mockReset(); buildPluginSnapshotReportMock.mockReset(); buildPluginRegistrySnapshotReportMock.mockReset(); buildPluginInspectReportMock.mockReset(); buildPluginDiagnosticsReportMock.mockReset(); buildPluginCompatibilityNotices.mockReset(); inspectPluginRegistryMock.mockReset(); - refreshPluginRegistry.mockReset(); + refreshPluginRegistryMock.mockReset(); notifyGatewayPluginMetadataChangedMock.mockReset(); clearPluginRegistryLoadCacheMock.mockReset(); applyExclusiveSlotSelectionMock.mockReset(); - planPluginUninstall.mockReset(); + planPluginUninstallMock.mockReset(); applyPluginUninstallDirectoryRemovalMock.mockReset(); updateNpmInstalledPlugins.mockReset(); updateNpmInstalledHookPacks.mockReset(); @@ -860,16 +868,16 @@ export function resetPluginsCliTestState() { installPluginFromNpmPackArchiveMock.mockReset(); installPluginFromPathMock.mockReset(); installPluginFromClawHubMock.mockReset(); - parseClawHubPluginSpec.mockReset(); - reportClawHubPluginInstallTelemetry.mockReset(); - reportClawHubPluginInstallTelemetry.mockResolvedValue(undefined); + parseClawHubPluginSpecMock.mockReset(); + reportClawHubPluginInstallTelemetryMock.mockReset(); + reportClawHubPluginInstallTelemetryMock.mockResolvedValue(undefined); findBundledPluginSourceMock.mockReset(); installHooksFromNpmSpecMock.mockReset(); installHooksFromPathMock.mockReset(); - recordHookInstall.mockReset(); + recordHookInstallMock.mockReset(); - loadConfig.mockReturnValue({} as OpenClawConfig); - readConfigFileSnapshot.mockImplementation(async () => { + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); + readConfigFileSnapshotMock.mockImplementation(async () => { const config = getRuntimeConfig(); return { path: "/tmp/openclaw-config.json5", @@ -887,8 +895,8 @@ export function resetPluginsCliTestState() { legacyIssues: [], }; }); - readConfigFileSnapshotForWrite.mockImplementation(async () => { - const snapshot = (await readConfigFileSnapshot()) as { path: string }; + readConfigFileSnapshotForWriteMock.mockImplementation(async () => { + const snapshot = (await readConfigFileSnapshotMock()) as { path: string }; return { snapshot, writeOptions: { @@ -899,7 +907,7 @@ export function resetPluginsCliTestState() { }; }); writeConfigFile.mockResolvedValue(undefined); - replaceConfigFile.mockImplementation( + replaceConfigFileMock.mockImplementation( (async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig)) as (...args: unknown[]) => Promise, ); @@ -914,7 +922,7 @@ export function resetPluginsCliTestState() { enabled: true, pluginId, })) as (...args: unknown[]) => unknown); - recordPluginInstall.mockImplementation( + recordPluginInstallMock.mockImplementation( ((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown, ); loadInstalledPluginIndexInstallRecords.mockImplementation(async () => @@ -924,9 +932,9 @@ export function resetPluginsCliTestState() { mockInstalledPluginIndexInstallRecords = clonePluginInstallRecords(records); return "/tmp/openclaw-state/openclaw.sqlite"; }); - readPersistedInstalledPluginIndex.mockResolvedValue(null); + readPersistedInstalledPluginIndexMock.mockResolvedValue(null); writePersistedInstalledPluginIndexInstallRecordsWithLease.mockImplementation(async (records) => { - const previous = await readPersistedInstalledPluginIndex(); + const previous = await readPersistedInstalledPluginIndexMock(); mockInstalledPluginIndexInstallRecords = clonePluginInstallRecords(records); mockInstalledPluginIndexRevision += 1; return { previous, revision: mockInstalledPluginIndexRevision }; @@ -943,7 +951,7 @@ export function resetPluginsCliTestState() { return true; }, ); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [], diagnostics: [], }); @@ -975,13 +983,13 @@ export function resetPluginsCliTestState() { persisted: defaultRegistryIndex, current: defaultRegistryIndex, }); - refreshPluginRegistry.mockResolvedValue(defaultRegistryIndex); + refreshPluginRegistryMock.mockResolvedValue(defaultRegistryIndex); notifyGatewayPluginMetadataChangedMock.mockResolvedValue(true); applyExclusiveSlotSelectionMock.mockImplementation((({ config }: { config: OpenClawConfig }) => ({ config, warnings: [], })) as (...args: unknown[]) => unknown); - planPluginUninstall.mockImplementation((({ + planPluginUninstallMock.mockImplementation((({ config, pluginId, }: { @@ -1042,7 +1050,7 @@ export function resetPluginsCliTestState() { ok: false, error: "clawhub install disabled in test", }); - parseClawHubPluginSpec.mockReturnValue(null); + parseClawHubPluginSpecMock.mockReturnValue(null); installHooksFromPathMock.mockResolvedValue({ ok: false, error: "hook path install disabled in test", @@ -1051,7 +1059,7 @@ export function resetPluginsCliTestState() { ok: false, error: "hook npm install disabled in test", }); - recordHookInstall.mockImplementation( + recordHookInstallMock.mockImplementation( ((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown, ); } diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 89f649a98e0b..0056f6d0ef51 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -25,17 +25,17 @@ import { installPluginFromMarketplaceMock, installPluginFromNpmSpecMock, installPluginFromPathMock, - loadConfig, - loadPluginManifestRegistry, - readConfigFileSnapshot, - readConfigFileSnapshotForWrite, - parseClawHubPluginSpec, + pluginCliConfigMock, + loadPluginManifestRegistryMock, + readConfigFileSnapshotMock, + readConfigFileSnapshotForWriteMock, + parseClawHubPluginSpecMock, promptYesNoMock, - reportClawHubPluginInstallTelemetry, - recordHookInstall, - recordPluginInstall, + reportClawHubPluginInstallTelemetryMock, + recordHookInstallMock, + recordPluginInstallMock, resetPluginsCliTestState, - replaceConfigFile, + replaceConfigFileMock, runPluginsCommand, runtimeErrors, pluginsCliRuntimeLogs, @@ -215,11 +215,11 @@ function primeNpmPluginFallback(pluginId = "demo") { const cfg = createEmptyPluginConfig(); const enabledCfg = createEnabledPluginConfig(pluginId); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); mockClawHubPackageNotFound(pluginId); installPluginFromNpmSpecMock.mockResolvedValue(createNpmPluginInstallResult(pluginId)); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(enabledCfg); + recordPluginInstallMock.mockReturnValue(enabledCfg); applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], @@ -232,9 +232,9 @@ function primeSuccessfulPluginPersistence(pluginId = "demo") { const cfg = createEmptyPluginConfig(); const enabledCfg = createEnabledPluginConfig(pluginId); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(enabledCfg); + recordPluginInstallMock.mockReturnValue(enabledCfg); applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], @@ -250,7 +250,7 @@ function primeSuccessfulClawHubPluginInstall( } = {}, ) { const result = primeSuccessfulPluginPersistence("demo"); - parseClawHubPluginSpec.mockReturnValue({ + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo", ...(params.explicitVersion ? { version: "1.2.3" } : {}), }); @@ -319,7 +319,7 @@ function primeHookPackNpmFallback() { const cfg = {} as OpenClawConfig; const installedCfg = createNpmHookPackInstalledConfig(); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); mockClawHubPackageNotFound("@acme/demo-hooks"); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, @@ -334,7 +334,7 @@ function primeHookPackNpmFallback() { integrity: "sha256-demo", }, }); - recordHookInstall.mockReturnValue(installedCfg); + recordHookInstallMock.mockReturnValue(installedCfg); return { cfg, installedCfg }; } @@ -345,13 +345,13 @@ function primeHookPackPathFallback(params: { }): OpenClawConfig { const installedCfg = createPathHookPackInstalledConfig(params.tmpRoot); - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromPathMock.mockResolvedValueOnce({ ok: false, error: params.pluginInstallError, }); installHooksFromPathMock.mockResolvedValueOnce(createHookPackInstallResult(params.tmpRoot)); - recordHookInstall.mockReturnValue(installedCfg); + recordHookInstallMock.mockReturnValue(installedCfg); return installedCfg; } @@ -445,14 +445,14 @@ function persistedInstallRecord(pluginId: string, callIndex = 0): PersistedInsta } function replaceConfigCall(callIndex = 0): { baseHash?: string; nextConfig?: OpenClawConfig } { - return mockCallArg(replaceConfigFile, callIndex) as { + return mockCallArg(replaceConfigFileMock, callIndex) as { baseHash?: string; nextConfig?: OpenClawConfig; }; } function recordHookInstallCall(callIndex = 0): PersistedInstallRecord { - return mockCallArg(recordHookInstall, callIndex, 1) as PersistedInstallRecord; + return mockCallArg(recordHookInstallMock, callIndex, 1) as PersistedInstallRecord; } function runtimeLogsContain(fragment: string): boolean { @@ -506,8 +506,8 @@ function primeInstallConfigSnapshot(params: { }): void { const configPath = params.configPath ?? path.join(process.cwd(), "openclaw.json5"); const config = params.config ?? ({} as OpenClawConfig); - loadConfig.mockReturnValue(config); - readConfigFileSnapshotForWrite.mockResolvedValue({ + pluginCliConfigMock.mockReturnValue(config); + readConfigFileSnapshotForWriteMock.mockResolvedValue({ snapshot: { path: configPath, exists: true, @@ -706,7 +706,7 @@ describe("plugins cli install", () => { integrity: "sha256-demo", }, }); - recordHookInstall.mockReturnValue(installedCfg); + recordHookInstallMock.mockReturnValue(installedCfg); await runAcknowledgedPluginsInstallCommand(["plugins", "install", "@acme/demo-hooks"]); @@ -832,14 +832,14 @@ describe("plugins cli install", () => { } as OpenClawConfig; fs.mkdirSync(localPath); primeBlockedPluginConfigMutation(); - parseClawHubPluginSpec.mockReturnValue({ name: "demo-hooks" }); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo-hooks" }); installPluginFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.extensions", code: "missing_openclaw_extensions", }); installHooksFromPathMock.mockResolvedValue(createHookPackInstallResult(localPath)); - recordHookInstall.mockReturnValue(installedCfg); + recordHookInstallMock.mockReturnValue(installedCfg); try { await runAcknowledgedPluginsInstallCommand([ @@ -1067,7 +1067,7 @@ describe("plugins cli install", () => { args: ["plugins", "install", "clawhub:demo"], installer: installPluginFromClawHubMock, setup: () => { - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo" }); installPluginFromClawHubMock.mockResolvedValue( createClawHubInstallResult({ pluginId: "demo", @@ -1213,10 +1213,10 @@ describe("plugins cli install", () => { it("fails closed for unrelated invalid config before installer side effects", async () => { const invalidConfigErr = new Error("config invalid"); (invalidConfigErr as { code?: string }).code = "INVALID_CONFIG"; - loadConfig.mockImplementation(() => { + pluginCliConfigMock.mockImplementation(() => { throw invalidConfigErr; }); - readConfigFileSnapshot.mockResolvedValue({ + readConfigFileSnapshotMock.mockResolvedValue({ path: "/tmp/openclaw-config.json5", exists: true, raw: '{ "models": { "default": 123 } }', @@ -1257,7 +1257,7 @@ describe("plugins cli install", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); installPluginFromMarketplaceMock.mockResolvedValue({ ok: true, pluginId: "alpha", @@ -1274,7 +1274,7 @@ describe("plugins cli install", () => { diagnostics: [], }); const alphaRoot = cliInstallPath("alpha"); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ { id: "alpha", @@ -1615,10 +1615,10 @@ describe("plugins cli install", () => { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ); expect(record.clawpackSize).toBe(4096); - expect(readConfigFileSnapshotForWrite).toHaveBeenCalledTimes(2); + expect(readConfigFileSnapshotForWriteMock).toHaveBeenCalledTimes(2); expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); expect(runtimeLogsContain("Installed plugin: demo")).toBe(true); - expect(reportClawHubPluginInstallTelemetry).toHaveBeenCalledWith({ + expect(reportClawHubPluginInstallTelemetryMock).toHaveBeenCalledWith({ baseUrl: "https://clawhub.ai", packageName: "demo", version: "1.2.3", @@ -1634,7 +1634,7 @@ describe("plugins cli install", () => { "persistence failed", ); - expect(reportClawHubPluginInstallTelemetry).not.toHaveBeenCalled(); + expect(reportClawHubPluginInstallTelemetryMock).not.toHaveBeenCalled(); }); it("passes ClawHub risk acknowledgement to explicit ClawHub installs", async () => { @@ -1665,8 +1665,8 @@ describe("plugins cli install", () => { }); it("prints acknowledgement guidance for unacknowledged ClawHub plugin installs", async () => { - loadConfig.mockReturnValue(createEmptyPluginConfig()); - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); + pluginCliConfigMock.mockReturnValue(createEmptyPluginConfig()); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo" }); installPluginFromClawHubMock.mockResolvedValue({ ok: false, code: "clawhub_risk_acknowledgement_required", @@ -1683,8 +1683,8 @@ describe("plugins cli install", () => { }); it("prints blocked ClawHub download failures when no trust warning was emitted", async () => { - loadConfig.mockReturnValue(createEmptyPluginConfig()); - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); + pluginCliConfigMock.mockReturnValue(createEmptyPluginConfig()); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo" }); installPluginFromClawHubMock.mockResolvedValue({ ok: false, code: "clawhub_download_blocked", @@ -1723,7 +1723,7 @@ describe("plugins cli install", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); findBundledPluginSourceMock.mockReturnValue({ pluginId, localPath: `/app/dist/extensions/${pluginId}`, @@ -1770,7 +1770,7 @@ describe("plugins cli install", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); findBundledPluginSourceMock.mockReturnValue({ pluginId, localPath: `/app/dist/extensions/${pluginId}`, @@ -1804,7 +1804,7 @@ describe("plugins cli install", () => { }, } as OpenClawConfig; const enabledCfg = createEnabledPluginConfig(pluginId); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); findBundledPluginSourceMock.mockReturnValue({ pluginId, localPath: `/app/dist/extensions/${pluginId}`, @@ -1920,7 +1920,7 @@ describe("plugins cli install", () => { ); it("passes third-party external catalog integrity to hook-pack fallback", async () => { - loadConfig.mockReturnValue(createEmptyPluginConfig()); + pluginCliConfigMock.mockReturnValue(createEmptyPluginConfig()); findBundledPluginSourceMock.mockReturnValue(undefined); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, @@ -2168,7 +2168,7 @@ describe("plugins cli install", () => { }); it("reports npm install failures without trying ClawHub when npm: prefix is used", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "npm install failed", @@ -2189,7 +2189,7 @@ describe("plugins cli install", () => { }); it("keeps actionable hook-pack fallback details", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "npm install failed", @@ -2210,7 +2210,7 @@ describe("plugins cli install", () => { }); it("adds a Git PATH hint when npm plugin dependency install cannot spawn git", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: [ @@ -2239,7 +2239,7 @@ describe("plugins cli install", () => { }); it("does not resolve npm: prefixed bundled plugin ids through bundled installs", async () => { - loadConfig.mockReturnValue({ plugins: { load: { paths: [] } } } as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({ plugins: { load: { paths: [] } } } as OpenClawConfig); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, error: "Package not found on npm: memory-lancedb.", @@ -2263,7 +2263,7 @@ describe("plugins cli install", () => { }); it("rejects empty npm: prefix installs before resolver lookup", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); await expect( runAcknowledgedPluginsInstallCommand(["plugins", "install", "npm:"]), @@ -2299,7 +2299,7 @@ describe("plugins cli install", () => { }); it("rejects --pin for git installs and points at git refs", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); await expect( runAcknowledgedPluginsInstallCommand([ @@ -2354,7 +2354,7 @@ describe("plugins cli install", () => { const enabledCfg = createEnabledPluginConfig("demo"); const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-link-")); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); installPluginFromPathMock.mockResolvedValueOnce({ ok: true, pluginId: "demo", @@ -2363,7 +2363,7 @@ describe("plugins cli install", () => { extensions: ["./dist/index.js"], }); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(enabledCfg); + recordPluginInstallMock.mockReturnValue(enabledCfg); applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], @@ -2416,7 +2416,7 @@ describe("plugins cli install", () => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-link-plugin-")); const pluginInstallError = "plugin blocked by security scan"; - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromPathMock.mockResolvedValue({ ok: false, error: pluginInstallError, @@ -2489,7 +2489,7 @@ describe("plugins cli install", () => { }); it("suggests update or --force when npm plugin install target already exists", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); mockClawHubPackageNotFound("@example/lossless-claw"); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, @@ -2514,7 +2514,7 @@ describe("plugins cli install", () => { it("does not append hook-pack fallback details for managed extensions boundary failures", async () => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromPathMock.mockResolvedValue({ ok: false, error: "Invalid path: must stay within extensions directory", @@ -2548,7 +2548,7 @@ describe("plugins cli install", () => { } as OpenClawConfig; const enabledCfg = createEnabledPluginConfig("demo"); - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); installPluginFromPathMock.mockImplementation(async (...args: unknown[]) => { const [params] = args as [ { @@ -2568,7 +2568,7 @@ describe("plugins cli install", () => { }; }); enablePluginInConfigMock.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(enabledCfg); + recordPluginInstallMock.mockReturnValue(enabledCfg); applyExclusiveSlotSelectionMock.mockReturnValue({ config: enabledCfg, warnings: [], @@ -2621,7 +2621,7 @@ describe("plugins cli install", () => { }, ] as const)("does not fall back to hook pack for local path when $name", async (testCase) => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-plugin-")); - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromPathMock.mockResolvedValue({ ok: false, error: testCase.error, @@ -2670,7 +2670,7 @@ describe("plugins cli install", () => { flags: ["--dangerously-force-unsafe-install"], }, ] as const)("does not fall back to hook pack for npm installs when $name", async (testCase) => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); mockClawHubPackageNotFound(testCase.spec); installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, @@ -2694,7 +2694,7 @@ describe("plugins cli install", () => { it("still falls back to local hook pack when dangerous force unsafe install is set for non-security errors", async () => { const localHookDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-local-hook-pack-")); - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); installPluginFromPathMock.mockResolvedValue({ ok: false, error: "package.json missing openclaw.plugin.json", @@ -2707,7 +2707,7 @@ describe("plugins cli install", () => { targetDir: "/tmp/hooks/demo-hooks", version: "1.2.3", }); - recordHookInstall.mockReturnValue(createPathHookPackInstalledConfig(localHookDir)); + recordHookInstallMock.mockReturnValue(createPathHookPackInstalledConfig(localHookDir)); try { await runAcknowledgedPluginsInstallCommand([ @@ -2739,7 +2739,7 @@ describe("plugins cli install", () => { }); it("does not fall back to npm when explicit ClawHub rejects a real package", async () => { - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo" }); installPluginFromClawHubMock.mockResolvedValue({ ok: false, error: 'Use "openclaw skills install demo" instead.', diff --git a/src/cli/plugins-cli.list.test.ts b/src/cli/plugins-cli.list.test.ts index 95b370fde59b..5d4778aaf614 100644 --- a/src/cli/plugins-cli.list.test.ts +++ b/src/cli/plugins-cli.list.test.ts @@ -13,11 +13,11 @@ import { buildPluginRegistrySnapshotReportMock, buildPluginSnapshotReportMock, inspectPluginRegistryMock, - loadConfig, - loadPluginManifestRegistry, - readConfigFileSnapshot, + pluginCliConfigMock, + loadPluginManifestRegistryMock, + readConfigFileSnapshotMock, resetPluginsCliTestState, - refreshPluginRegistry, + refreshPluginRegistryMock, runPluginsCommand, runtimeErrors, pluginsCliRuntimeLogs, @@ -39,10 +39,10 @@ async function mockPluginDoctorValidationWarnings(warnings: ConfigValidationIssu entries: { google: { config: { apiKey: "test-google-key" } } }, }, }; - loadConfig.mockReturnValue(config); - const snapshot = (await readConfigFileSnapshot()) as ConfigFileSnapshot; - readConfigFileSnapshot.mockResolvedValueOnce({ ...snapshot, valid: true, warnings }); - loadPluginManifestRegistry.mockReturnValue({ + pluginCliConfigMock.mockReturnValue(config); + const snapshot = (await readConfigFileSnapshotMock()) as ConfigFileSnapshot; + readConfigFileSnapshotMock.mockResolvedValueOnce({ ...snapshot, valid: true, warnings }); + loadPluginManifestRegistryMock.mockReturnValue({ plugins: ["google", "imessage", "memory-core"].map((id) => ({ id, channels: [], @@ -383,8 +383,8 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue({}); - readConfigFileSnapshot.mockResolvedValueOnce({ + pluginCliConfigMock.mockReturnValue({}); + readConfigFileSnapshotMock.mockResolvedValueOnce({ path: "/tmp/openclaw-config.json5", exists: true, raw: "{}", @@ -435,8 +435,8 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); - readConfigFileSnapshot.mockResolvedValueOnce({ + pluginCliConfigMock.mockReturnValue(sourceConfig); + readConfigFileSnapshotMock.mockResolvedValueOnce({ path: "/tmp/openclaw-config.json5", exists: true, raw: "{}", @@ -475,7 +475,7 @@ describe("plugins cli list", () => { backend: "acpx", }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -502,7 +502,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -525,7 +525,7 @@ describe("plugins cli list", () => { backend: "acpx", }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "acpx", enabled: false, status: "disabled" })], diagnostics: [], @@ -550,7 +550,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -575,7 +575,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "codex" })], diagnostics: [], @@ -598,7 +598,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [createPluginRecord({ id: "codex", enabled: false, status: "disabled" })], diagnostics: [], @@ -629,7 +629,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -663,7 +663,7 @@ describe("plugins cli list", () => { }, }, }; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); buildPluginDiagnosticsReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -758,7 +758,7 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "registry"]); expect(inspectPluginRegistryMock).toHaveBeenCalledWith({ config: {} }); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(pluginsCliRuntimeLogs.join("\n")).toContain("State:"); expect(pluginsCliRuntimeLogs.join("\n")).toContain("stale"); expect(pluginsCliRuntimeLogs.join("\n")).toContain("Refresh reasons:"); @@ -766,7 +766,7 @@ describe("plugins cli list", () => { }); it("refreshes the persisted plugin registry on request", async () => { - refreshPluginRegistry.mockResolvedValue({ + refreshPluginRegistryMock.mockResolvedValue({ plugins: [ { pluginId: "demo", enabled: true }, { pluginId: "off", enabled: false }, @@ -775,7 +775,7 @@ describe("plugins cli list", () => { await runPluginsCommand(["plugins", "registry", "--refresh"]); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: {}, reason: "manual", }); @@ -916,7 +916,7 @@ describe("plugins cli list", () => { const config: OpenClawConfig = { tools: { profile: "messaging" }, }; - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); workshopMocks.detectToolPolicyDiagnostic.mockReturnValue({ agentId: "main", source: "tools.profile", diff --git a/src/cli/plugins-cli.policy.test.ts b/src/cli/plugins-cli.policy.test.ts index 60eb4c6fb4a3..f0f30bd49eed 100644 --- a/src/cli/plugins-cli.policy.test.ts +++ b/src/cli/plugins-cli.policy.test.ts @@ -4,9 +4,9 @@ import type { OpenClawConfig } from "../config/config.js"; import { buildPluginRegistrySnapshotReportMock, enablePluginInConfigMock, - loadConfig, - replaceConfigFile, - refreshPluginRegistry, + pluginCliConfigMock, + replaceConfigFileMock, + refreshPluginRegistryMock, resetPluginsCliTestState, runtimeErrors, pluginsCliRuntimeLogs, @@ -73,7 +73,7 @@ describe("plugins cli policy mutations", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); enablePluginInConfigMock.mockReturnValue({ config: enabledConfig, enabled: true, @@ -86,7 +86,7 @@ describe("plugins cli policy mutations", () => { expect(enablePluginInConfigMock).toHaveBeenCalledWith(sourceConfig, "alpha", { updateChannelConfig: false, }); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig: enabledConfig, baseHash: "mock", writeOptions: { @@ -94,7 +94,7 @@ describe("plugins cli policy mutations", () => { }, }); expect(writeConfigFile).toHaveBeenCalledWith(enabledConfig); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: enabledConfig, installRecords: {}, policyPluginIds: ["alpha"], @@ -120,7 +120,7 @@ describe("plugins cli policy mutations", () => { }, ])("fails without mutations when $policy blocks enablement", async ({ plugins, reason }) => { const sourceConfig = { plugins } as OpenClawConfig; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); enablePluginInConfigMock.mockReturnValue({ config: sourceConfig, enabled: false, @@ -131,9 +131,9 @@ describe("plugins cli policy mutations", () => { await expect(runPluginsCommand(["plugins", "enable", "alpha"])).rejects.toThrow("__exit__:1"); - expect(replaceConfigFile).not.toHaveBeenCalled(); + expect(replaceConfigFileMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(runtimeErrors).toContain(`Plugin "alpha" could not be enabled (${reason}).`); expect(pluginsCliRuntimeLogs).not.toContain(`Plugin "alpha" could not be enabled (${reason}).`); }); @@ -158,7 +158,7 @@ describe("plugins cli policy mutations", () => { }); it("refreshes the persisted plugin registry after disabling a plugin", async () => { - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { entries: { alpha: { enabled: true }, @@ -172,14 +172,14 @@ describe("plugins cli policy mutations", () => { const nextConfig = requireFirstWrittenConfig(); const entries = requirePluginEntries(nextConfig); expect(entries.alpha).toEqual({ enabled: false }); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig, baseHash: "mock", writeOptions: { explicitSetPaths: [["plugins", "entries", "alpha"]], }, }); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: nextConfig, installRecords: {}, policyPluginIds: ["alpha"], @@ -198,7 +198,7 @@ describe("plugins cli policy mutations", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(sourceConfig); + pluginCliConfigMock.mockReturnValue(sourceConfig); enablePluginInConfigMock.mockReturnValue({ config: enabledConfig, enabled: true, @@ -211,7 +211,7 @@ describe("plugins cli policy mutations", () => { expect(enablePluginInConfigMock).toHaveBeenCalledWith(sourceConfig, pluginId, { updateChannelConfig: false, }); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig: enabledConfig, baseHash: "mock", writeOptions: { @@ -225,7 +225,7 @@ describe("plugins cli policy mutations", () => { it.each(compatibilityPluginIds)( "disables compatibility id $alias through canonical plugin $pluginId", async ({ alias, pluginId }) => { - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { entries: { [pluginId]: { enabled: true }, @@ -240,7 +240,7 @@ describe("plugins cli policy mutations", () => { const entries = requirePluginEntries(nextConfig); expect(entries[pluginId]).toEqual({ enabled: false }); expect(entries[alias]).toBeUndefined(); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig, baseHash: "mock", writeOptions: { @@ -264,12 +264,12 @@ describe("plugins cli policy mutations", () => { ); expect(enablePluginInConfigMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); }, ); it("does not create a channel config when disabling a channel plugin by policy", async () => { - loadConfig.mockReturnValue({} as OpenClawConfig); + pluginCliConfigMock.mockReturnValue({} as OpenClawConfig); mockPluginRegistry(["twitch"]); await runPluginsCommand(["plugins", "disable", "twitch"]); diff --git a/src/cli/plugins-cli.registry.test.ts b/src/cli/plugins-cli.registry.test.ts index 8e2980f04cc3..7ca80fd82100 100644 --- a/src/cli/plugins-cli.registry.test.ts +++ b/src/cli/plugins-cli.registry.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from "vitest"; import { - refreshPluginRegistry, + refreshPluginRegistryMock, resetPluginsCliTestState, runPluginsCommand, } from "./plugins-cli-test-helpers.js"; @@ -22,7 +22,7 @@ describe("plugins registry refresh", () => { const firstEntered = deferred(); const releaseFirst = deferred(); const entries: number[] = []; - refreshPluginRegistry.mockImplementation(async () => { + refreshPluginRegistryMock.mockImplementation(async () => { const entry = entries.length + 1; entries.push(entry); if (entry === 1) { diff --git a/src/cli/plugins-cli.uninstall.test.ts b/src/cli/plugins-cli.uninstall.test.ts index 0f94e37c1c87..748bdeeb0b23 100644 --- a/src/cli/plugins-cli.uninstall.test.ts +++ b/src/cli/plugins-cli.uninstall.test.ts @@ -11,13 +11,13 @@ import { buildPluginDiagnosticsReportMock, buildPluginSnapshotReportMock, createTestInstalledPluginIndex, - loadConfig, - planPluginUninstall, + pluginCliConfigMock, + planPluginUninstallMock, PromptInputClosedError, promptYesNoMock, - readPersistedInstalledPluginIndex, - refreshPluginRegistry, - replaceConfigFile, + readPersistedInstalledPluginIndexMock, + refreshPluginRegistryMock, + replaceConfigFileMock, resetPluginsCliTestState, restorePersistedInstalledPluginIndexIfCurrent, runPluginsCommand, @@ -40,7 +40,7 @@ function primeUninstallPlan( directoryRemoval?: { target: string } | null; } = {}, ): void { - planPluginUninstall.mockReturnValue({ + planPluginUninstallMock.mockReturnValue({ ok: true, config, actions: { @@ -78,9 +78,9 @@ function expectLatestUninstallPlanParams(expected: { deleteFiles: boolean; channelIds?: unknown; }) { - const params = planPluginUninstall.mock.calls[planPluginUninstall.mock.calls.length - 1]?.[0] as - | { pluginId?: string; deleteFiles?: boolean; channelIds?: unknown } - | undefined; + const params = planPluginUninstallMock.mock.calls[ + planPluginUninstallMock.mock.calls.length - 1 + ]?.[0] as { pluginId?: string; deleteFiles?: boolean; channelIds?: unknown } | undefined; if (params === undefined) { throw new Error("expected latest plugin uninstall plan params"); } @@ -120,14 +120,14 @@ describe("plugins cli uninstall", () => { } } - expect(planPluginUninstall).not.toHaveBeenCalled(); + expect(planPluginUninstallMock).not.toHaveBeenCalled(); expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); }); it("shows uninstall dry-run preview without mutating config or acquiring write mode", async () => { process.env.OPENCLAW_NIX_MODE = "1"; - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { entries: { alpha: { @@ -156,9 +156,9 @@ describe("plugins cli uninstall", () => { expect(buildPluginSnapshotReportMock).toHaveBeenCalledTimes(1); expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); - expect(planPluginUninstall).toHaveBeenCalledTimes(1); + expect(planPluginUninstallMock).toHaveBeenCalledTimes(1); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expectRuntimeLogIncludes("Dry run, no changes made."); expectRuntimeLogIncludes("context engine slot"); }); @@ -185,7 +185,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], @@ -203,7 +203,7 @@ describe("plugins cli uninstall", () => { entries: {}, }, }); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ baseHash: "mock", nextConfig: { plugins: { @@ -217,7 +217,7 @@ describe("plugins cli uninstall", () => { unsetPaths: [["plugins", "installs"]], }), }); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: { plugins: { entries: {}, @@ -250,7 +250,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ @@ -290,7 +290,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ @@ -305,12 +305,12 @@ describe("plugins cli uninstall", () => { ).rejects.toThrow("__exit__:1"); expect(runtimeErrors.at(-1)).toContain('Plugin uninstall target "calendar" is ambiguous'); - expect(planPluginUninstall).not.toHaveBeenCalled(); + expect(planPluginUninstallMock).not.toHaveBeenCalled(); expect(promptYesNoMock).not.toHaveBeenCalled(); expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); }); it("warns but proceeds when a shared plugin has an uncertain Claw reference", async () => { @@ -331,7 +331,7 @@ describe("plugins cli uninstall", () => { installs: { alpha: installRecord }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords({ alpha: installRecord }); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], @@ -385,7 +385,7 @@ describe("plugins cli uninstall", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(baseConfig.plugins?.installs ?? {}); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], @@ -403,7 +403,7 @@ describe("plugins cli uninstall", () => { ); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); @@ -434,15 +434,15 @@ describe("plugins cli uninstall", () => { installRecords, }); - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); - readPersistedInstalledPluginIndex.mockResolvedValue(previousPersistedIndex); + readPersistedInstalledPluginIndexMock.mockResolvedValue(previousPersistedIndex); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); primeUninstallPlan(nextConfig); - replaceConfigFile.mockRejectedValueOnce(new Error("config changed")); + replaceConfigFileMock.mockRejectedValueOnce(new Error("config changed")); await expect( runPluginsCommand(["plugins", "uninstall", "alpha", "--force", "--keep-files"]), @@ -457,7 +457,7 @@ describe("plugins cli uninstall", () => { lease: expect.anything(), }), ); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); @@ -484,7 +484,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], @@ -505,10 +505,10 @@ describe("plugins cli uninstall", () => { const finalConfigWriteOrder = writeConfigFile.mock.invocationCallOrder[1] ?? Number.MAX_SAFE_INTEGER; const refreshOrder = - refreshPluginRegistry.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; + refreshPluginRegistryMock.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; expect(writeConfigFile).toHaveBeenCalledTimes(2); expect(applyPluginUninstallDirectoryRemovalMock).toHaveBeenCalledTimes(1); - expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); + expect(refreshPluginRegistryMock).toHaveBeenCalledTimes(1); expect(deleteOrder).toBeGreaterThan(configWriteOrder); expect(finalConfigWriteOrder).toBeGreaterThan(deleteOrder); expect(refreshOrder).toBeGreaterThan(finalConfigWriteOrder); @@ -534,7 +534,7 @@ describe("plugins cli uninstall", () => { installs: installRecords, }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "alpha", name: "alpha" }], @@ -561,7 +561,7 @@ describe("plugins cli uninstall", () => { }, }); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); }); it("cleans stale policy refs even when plugin is absent from the current registry", async () => { @@ -577,7 +577,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -609,7 +609,7 @@ describe("plugins cli uninstall", () => { } as OpenClawConfig; const nextConfig = {} as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], diagnostics: [], @@ -622,7 +622,7 @@ describe("plugins cli uninstall", () => { expectLatestUninstallPlanParams({ pluginId: "alpha", deleteFiles: true }); expect(writeConfigFile).toHaveBeenCalledWith(nextConfig); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: nextConfig, installRecords: {}, reason: "source-changed", @@ -664,7 +664,7 @@ describe("plugins cli uninstall", () => { channels, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [ @@ -680,7 +680,7 @@ describe("plugins cli uninstall", () => { }); const actualUninstall = await vi.importActual("../plugins/uninstall.js"); - planPluginUninstall.mockImplementation((params) => + planPluginUninstallMock.mockImplementation((params) => actualUninstall.planPluginUninstall( params as Parameters[0], ), @@ -741,7 +741,7 @@ describe("plugins cli uninstall", () => { }, } as OpenClawConfig; - loadConfig.mockReturnValue(baseConfig); + pluginCliConfigMock.mockReturnValue(baseConfig); setInstalledPluginIndexInstallRecords(installRecords); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [], @@ -763,7 +763,7 @@ describe("plugins cli uninstall", () => { }); it("exits when uninstall target is not managed by plugin install records", async () => { - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { entries: {}, installs: {}, @@ -773,7 +773,7 @@ describe("plugins cli uninstall", () => { plugins: [{ id: "alpha", name: "alpha" }], diagnostics: [], }); - planPluginUninstall.mockReturnValue({ + planPluginUninstallMock.mockReturnValue({ ok: false, error: "Plugin not found: alpha", }); @@ -783,6 +783,6 @@ describe("plugins cli uninstall", () => { ); expect(runtimeErrors.at(-1)).toContain("is not managed by plugins config/install records"); - expect(planPluginUninstall).toHaveBeenCalledTimes(1); + expect(planPluginUninstallMock).toHaveBeenCalledTimes(1); }); }); diff --git a/src/cli/plugins-cli.update.test.ts b/src/cli/plugins-cli.update.test.ts index 582f8e6fdcc0..6269f722faf4 100644 --- a/src/cli/plugins-cli.update.test.ts +++ b/src/cli/plugins-cli.update.test.ts @@ -9,12 +9,12 @@ import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub-error-codes.js"; import { VERSION } from "../version.js"; import { createTestInstalledPluginIndex, - loadConfig, + pluginCliConfigMock, notifyGatewayPluginMetadataChangedMock, - readConfigFileSnapshotForWrite, - readPersistedInstalledPluginIndex, - refreshPluginRegistry, - replaceConfigFile, + readConfigFileSnapshotForWriteMock, + readPersistedInstalledPluginIndexMock, + refreshPluginRegistryMock, + replaceConfigFileMock, resetPluginsCliTestState, restorePersistedInstalledPluginIndexIfCurrent, runPluginsCommand, @@ -144,8 +144,8 @@ function primeUpdateConfigSnapshot(params: { includeFileTargetsForWrite: params.includeFileTargetsForWrite, }, }; - loadConfig.mockReturnValue(params.loadedConfig ?? params.config); - readConfigFileSnapshotForWrite.mockResolvedValue(prepared); + pluginCliConfigMock.mockReturnValue(params.loadedConfig ?? params.config); + readConfigFileSnapshotForWriteMock.mockResolvedValue(prepared); return prepared; } @@ -222,7 +222,7 @@ async function expectSkippedClawHubPluginUpdate(params: { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config, [ { @@ -294,7 +294,7 @@ describe("plugins cli update", () => { pluginId: "alpha", spec: "@acme/alpha@1.0.0", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config, [ { @@ -314,9 +314,9 @@ describe("plugins cli update", () => { ); expect(acquireLease).not.toHaveBeenCalled(); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(replaceConfigFile).not.toHaveBeenCalled(); + expect(replaceConfigFileMock).not.toHaveBeenCalled(); expect(writePersistedInstalledPluginIndexInstallRecordsWithLease).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(pluginsCliRuntimeLogs).toContain("Would update alpha: 1.0.0 -> 1.1.0."); } finally { acquireLease.mockRestore(); @@ -412,10 +412,10 @@ describe("plugins cli update", () => { expect(hookUpdateParams.hookIds).toEqual(["demo-hooks"]); expect(updateNpmInstalledPlugins).not.toHaveBeenCalled(); expect(writeConfigFile).toHaveBeenCalledWith(nextConfig); - expect(replaceConfigFile).toHaveBeenCalledWith( + expect(replaceConfigFileMock).toHaveBeenCalledWith( expect.objectContaining({ nextConfig, baseHash: "update-config" }), ); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expectRestartNoticeLogged(); }); @@ -675,8 +675,8 @@ describe("plugins cli update", () => { expect(runtimeErrors).toEqual([]); expectInstallRecordsWrittenWithLease(nextRecords, sourceCfg); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(replaceConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: sourceCfg, installRecords: nextRecords, reason: "source-changed", @@ -729,7 +729,7 @@ describe("plugins cli update", () => { load: { paths: [nextInstallPath, customPath] }, }, }); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig: { plugins: { load: { paths: [nextInstallPath, customPath] }, @@ -740,7 +740,7 @@ describe("plugins cli update", () => { afterWrite: { mode: "restart", reason: "plugin source changed" }, }), }); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: { plugins: { load: { paths: [nextInstallPath, customPath] }, @@ -785,7 +785,7 @@ describe("plugins cli update", () => { hash: "changed-config", }, }; - readConfigFileSnapshotForWrite + readConfigFileSnapshotForWriteMock .mockResolvedValueOnce(initialSnapshot) .mockResolvedValueOnce(changedSnapshot); const { previousRecords, nextRecords } = primeBravePluginRecordUpdate(cfg); @@ -793,7 +793,7 @@ describe("plugins cli update", () => { policyHash: "previous-policy", installRecords: previousRecords, }); - readPersistedInstalledPluginIndex.mockResolvedValue(previousPersistedIndex); + readPersistedInstalledPluginIndexMock.mockResolvedValue(previousPersistedIndex); await expect(runPluginsCommand(["plugins", "update", "brave"])).rejects.toThrow( "config changed since last load", @@ -809,8 +809,8 @@ describe("plugins cli update", () => { }), ); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(replaceConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(replaceConfigFileMock).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); expect(notifyGatewayPluginMetadataChangedMock).not.toHaveBeenCalled(); }); @@ -847,7 +847,7 @@ describe("plugins cli update", () => { }, }, }; - readConfigFileSnapshotForWrite + readConfigFileSnapshotForWriteMock .mockResolvedValueOnce(initialSnapshot) .mockResolvedValueOnce(changedSnapshot); const { previousRecords, nextRecords } = primeBravePluginRecordUpdate(cfg); @@ -855,7 +855,7 @@ describe("plugins cli update", () => { policyHash: "previous-policy", installRecords: previousRecords, }); - readPersistedInstalledPluginIndex.mockResolvedValue(previousPersistedIndex); + readPersistedInstalledPluginIndexMock.mockResolvedValue(previousPersistedIndex); await expect(runPluginsCommand(["plugins", "update", "brave"])).rejects.toThrow( "included config changed since last load", @@ -871,8 +871,8 @@ describe("plugins cli update", () => { }), ); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(replaceConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(replaceConfigFileMock).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); }); it("rolls back persisted install records when records-only update invalidates config", async () => { @@ -902,7 +902,7 @@ describe("plugins cli update", () => { ], }, }; - readConfigFileSnapshotForWrite + readConfigFileSnapshotForWriteMock .mockResolvedValueOnce(initialSnapshot) .mockResolvedValueOnce(invalidSnapshot); const { previousRecords, nextRecords } = primeBravePluginRecordUpdate(cfg); @@ -910,7 +910,7 @@ describe("plugins cli update", () => { policyHash: "previous-policy", installRecords: previousRecords, }); - readPersistedInstalledPluginIndex.mockResolvedValue(previousPersistedIndex); + readPersistedInstalledPluginIndexMock.mockResolvedValue(previousPersistedIndex); await expect(runPluginsCommand(["plugins", "update", "brave"])).rejects.toThrow( "invalid config for plugin brave", @@ -926,8 +926,8 @@ describe("plugins cli update", () => { }), ); expect(writeConfigFile).not.toHaveBeenCalled(); - expect(replaceConfigFile).not.toHaveBeenCalled(); - expect(refreshPluginRegistry).not.toHaveBeenCalled(); + expect(replaceConfigFileMock).not.toHaveBeenCalled(); + expect(refreshPluginRegistryMock).not.toHaveBeenCalled(); }); it("blocks legacy plugin id migration before updater side effects", async () => { @@ -1193,7 +1193,7 @@ describe("plugins cli update", () => { }); it("exits when update is called without id and without --all", async () => { - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { installs: {}, }, @@ -1206,7 +1206,7 @@ describe("plugins cli update", () => { }); it("reports no tracked plugins or hook packs when update --all has empty install records", async () => { - loadConfig.mockReturnValue({ + pluginCliConfigMock.mockReturnValue({ plugins: { installs: {}, }, @@ -1224,7 +1224,7 @@ describe("plugins cli update", () => { pluginId: "openclaw-codex-app-server", spec: "openclaw-codex-app-server@beta", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1268,7 +1268,7 @@ describe("plugins cli update", () => { resolvedName: "@openclaw/codex", }); config.update = { channel: updateChannel }; - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1290,7 +1290,7 @@ describe("plugins cli update", () => { spec: "@openclaw/codex", resolvedName: "@openclaw/codex", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1311,7 +1311,7 @@ describe("plugins cli update", () => { resolvedName: "@openclaw/codex", }); config.update = { channel: "beta" }; - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1330,7 +1330,7 @@ describe("plugins cli update", () => { spec: "@openclaw/codex", resolvedName: "@openclaw/codex", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1349,7 +1349,7 @@ describe("plugins cli update", () => { resolvedName: "@openclaw/codex", }); config.update = { channel: "extended-stable" }; - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1369,7 +1369,7 @@ describe("plugins cli update", () => { pluginId: "openclaw-codex-app-server", spec: "openclaw-codex-app-server@beta", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1395,7 +1395,7 @@ describe("plugins cli update", () => { pluginId: "openclaw-codex-app-server", spec: "clawhub:openclaw-codex-app-server", }); - loadConfig.mockReturnValue(config); + pluginCliConfigMock.mockReturnValue(config); setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); primePluginUpdate(config); @@ -1466,7 +1466,7 @@ describe("plugins cli update", () => { expectInstallRecordsWrittenWithLease(nextConfig.plugins?.installs, {}); expect(updateNpmInstalledHookPacks).not.toHaveBeenCalled(); expect(writeConfigFile).toHaveBeenCalledWith({}); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig: {}, baseHash: "update-config", writeOptions: expect.objectContaining({ @@ -1475,7 +1475,7 @@ describe("plugins cli update", () => { }, }), }); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: {}, installRecords: nextConfig.plugins?.installs, reason: "source-changed", @@ -1512,7 +1512,7 @@ describe("plugins cli update", () => { }, }, } as OpenClawConfig; - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); setInstalledPluginIndexInstallRecords(cfg.plugins?.installs ?? {}); primePluginUpdate( nextConfig, @@ -1531,7 +1531,7 @@ describe("plugins cli update", () => { await expect(runPluginsCommand(["plugins", "update", "--all"])).rejects.toThrow("__exit__:1"); expectInstallRecordsWrittenWithLease(nextConfig.plugins?.installs, {}); - expect(refreshPluginRegistry).toHaveBeenCalledWith({ + expect(refreshPluginRegistryMock).toHaveBeenCalledWith({ config: {}, installRecords: nextConfig.plugins?.installs, reason: "source-changed", @@ -1569,7 +1569,7 @@ describe("plugins cli update", () => { it("exits non-zero when a hook pack update reports an error", async () => { const cfg = {} as OpenClawConfig; - loadConfig.mockReturnValue(cfg); + pluginCliConfigMock.mockReturnValue(cfg); setHookInstallRecords({ "demo-hooks": { source: "npm", diff --git a/src/cli/plugins-install-preflight.test.ts b/src/cli/plugins-install-preflight.test.ts index 99e13da6ca53..8ca2a902efde 100644 --- a/src/cli/plugins-install-preflight.test.ts +++ b/src/cli/plugins-install-preflight.test.ts @@ -9,9 +9,9 @@ import { installPluginFromNpmPackArchiveMock, installPluginFromNpmSpecMock, installPluginFromPathMock, - parseClawHubPluginSpec, + parseClawHubPluginSpecMock, promptYesNoMock, - readConfigFileSnapshotForWrite, + readConfigFileSnapshotForWriteMock, resetPluginsCliTestState, resolveMarketplaceInstallShortcut, runPluginsCommand, @@ -30,7 +30,7 @@ vi.mock("../plugins/plugin-lifecycle-lease.js", () => ({ function expectNoPluginInstallSideEffects(): void { expect(withPluginLifecycleLeaseMock).not.toHaveBeenCalled(); - expect(readConfigFileSnapshotForWrite).not.toHaveBeenCalled(); + expect(readConfigFileSnapshotForWriteMock).not.toHaveBeenCalled(); expect(promptYesNoMock).not.toHaveBeenCalled(); expect(installPluginFromClawHubMock).not.toHaveBeenCalled(); expect(installPluginFromGitSpecMock).not.toHaveBeenCalled(); @@ -179,7 +179,7 @@ describe("plugin install mutation-free preflight", () => { }, ])("rejects $label before the lifecycle lease", async ({ args, error }) => { if (args[0] === "clawhub:demo") { - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); + parseClawHubPluginSpecMock.mockReturnValue({ name: "demo" }); } await expect(runPluginsCommand(["plugins", "install", ...args, "--force"])).rejects.toThrow( diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index c5e2117decee..83d55509ef8d 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -23,7 +23,7 @@ import { BASE_THINKING_LEVELS } from "../auto-reply/thinking.shared.js"; import * as runtimeSnapshotModule from "../config/runtime-snapshot.js"; import { parseSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceSessionEntry, } from "../config/sessions/session-accessor.js"; @@ -337,7 +337,7 @@ function expectLastRunProviderModel(provider: string, model: string): void { function readSessionStore(storePath: string): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry as T]), + listSessionEntriesCore({ storePath }).map(({ entry, sessionKey }) => [sessionKey, entry as T]), ); } diff --git a/src/commands/agent/session.test.ts b/src/commands/agent/session.test.ts index 5f53c5acc8db..939d1e4a9084 100644 --- a/src/commands/agent/session.test.ts +++ b/src/commands/agent/session.test.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../../config/config.js"; import { resolveSessionKeyForRequest } from "./session.runtime.js"; const mocks = vi.hoisted(() => ({ - listSessionEntries: vi.fn(), + listSessionEntriesCore: vi.fn(), resolveStorePath: vi.fn(), listAgentIds: vi.fn(), resolveExplicitAgentSessionKey: vi.fn(), @@ -21,7 +21,7 @@ vi.mock("../../config/sessions/main-session.js", async () => { }); vi.mock("../../config/sessions/session-accessor.js", () => ({ - listSessionEntries: mocks.listSessionEntries, + listSessionEntriesCore: mocks.listSessionEntriesCore, })); vi.mock("../../config/sessions/paths.js", () => ({ @@ -61,7 +61,7 @@ describe("resolveSessionKeyForRequest", () => { }; const mockStoresByPath = (stores: Partial>) => { - mocks.listSessionEntries.mockImplementation((scope?: { storePath?: string }) => + mocks.listSessionEntriesCore.mockImplementation((scope?: { storePath?: string }) => Object.entries(stores[scope?.storePath ?? ""] ?? {}).map(([sessionKey, entry]) => ({ sessionKey, entry, @@ -190,8 +190,8 @@ describe("resolveSessionKeyForRequest", () => { expect(result.sessionStore).toEqual(sharedStore); expect(result.storePath).toBe(SHARED_STORE_PATH); expect(result.sessionStore["agent:mybot:main"]).toBeUndefined(); - expect(mocks.listSessionEntries).toHaveBeenCalledTimes(1); - expect(mocks.listSessionEntries).toHaveBeenCalledWith({ + expect(mocks.listSessionEntriesCore).toHaveBeenCalledTimes(1); + expect(mocks.listSessionEntriesCore).toHaveBeenCalledWith({ agentId: "mybot", storePath: SHARED_STORE_PATH, }); @@ -314,8 +314,8 @@ describe("resolveSessionKeyForRequest", () => { expect(result.sessionKey).toBe("agent:mybot:explicit:target-session-id"); expect(result.storePath).toBe(MYBOT_STORE_PATH); - expect(mocks.listSessionEntries).toHaveBeenCalledTimes(1); - expect(mocks.listSessionEntries).toHaveBeenCalledWith({ + expect(mocks.listSessionEntriesCore).toHaveBeenCalledTimes(1); + expect(mocks.listSessionEntriesCore).toHaveBeenCalledWith({ agentId: "mybot", storePath: MYBOT_STORE_PATH, }); @@ -339,7 +339,7 @@ describe("resolveSessionKeyForRequest", () => { it("returns a deterministic explicit sessionKey when sessionId not found in any store", () => { setupMainAndMybotStorePaths(); - mocks.listSessionEntries.mockReturnValue([]); + mocks.listSessionEntriesCore.mockReturnValue([]); const result = resolveSessionKeyForRequest({ cfg: baseCfg, @@ -390,16 +390,16 @@ describe("resolveSessionKeyForRequest", () => { it("skips already-searched primary store when iterating agents", () => { setupMainAndMybotStorePaths(); - mocks.listSessionEntries.mockReturnValue([]); + mocks.listSessionEntriesCore.mockReturnValue([]); resolveSessionKeyForRequest({ cfg: baseCfg, sessionId: "nonexistent-id", }); - // listSessionEntries should be called twice: once for main, once for mybot + // listSessionEntriesCore should be called twice: once for main, once for mybot // (not twice for main) - const storePaths = mocks.listSessionEntries.mock.calls.map((call) => + const storePaths = mocks.listSessionEntriesCore.mock.calls.map((call) => String(call[0]?.storePath), ); expect(storePaths).toHaveLength(2); diff --git a/src/commands/agents.delete.test.ts b/src/commands/agents.delete.test.ts index 78a07c7816ab..a3a21df0550f 100644 --- a/src/commands/agents.delete.test.ts +++ b/src/commands/agents.delete.test.ts @@ -9,7 +9,10 @@ import { } from "../agents/agent-scope-config.js"; import { resolveStorePath } from "../config/sessions.js"; import type { SessionEntry } from "../config/sessions.js"; -import { listSessionEntries, replaceSessionEntry } from "../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + replaceSessionEntry, +} from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { parseAgentSessionKey } from "../routing/session-key.js"; import { withStateDirEnv } from "../test-helpers/state-dir-env.js"; @@ -142,7 +145,7 @@ function expectSessionStore( expect( Object.fromEntries( [...agentIds].flatMap((storeAgentId) => - listSessionEntries({ + listSessionEntriesCore({ agentId: storeAgentId, storePath: resolveStorePath(cfg.session?.store, { agentId: storeAgentId }), }).map(({ entry, sessionKey }) => [sessionKey, entry]), diff --git a/src/commands/channel-setup/plugin-install.ts b/src/commands/channel-setup/plugin-install.ts index 6670d513698c..98339011a3a8 100644 --- a/src/commands/channel-setup/plugin-install.ts +++ b/src/commands/channel-setup/plugin-install.ts @@ -9,7 +9,6 @@ import { resolveDiscoverableScopedChannelPluginIds, } from "../../plugins/channel-plugin-ids.js"; import { loadPluginRegistryHandle } from "../../plugins/loader.js"; -import { createPluginLoaderLogger } from "../../plugins/logger.js"; import type { PluginRegistry } from "../../plugins/registry.js"; import type { RuntimeEnv } from "../../runtime.js"; import type { WizardPrompter } from "../../wizard/prompts.js"; @@ -100,7 +99,7 @@ function loadChannelSetupPluginRegistry(params: { autoEnabledReasons: autoEnabled.autoEnabledReasons, workspaceDir, cache: false, - logger: createPluginLoaderLogger(log), + logger: log, onlyPluginIds, includeSetupOnlyChannelPlugins: true, forceSetupOnlyChannelPlugins: params.forceSetupOnlyChannelPlugins, diff --git a/src/commands/doctor-auth-flat-profiles.ts b/src/commands/doctor-auth-flat-profiles.ts index 4bd3618b3e79..4eb073a808d7 100644 --- a/src/commands/doctor-auth-flat-profiles.ts +++ b/src/commands/doctor-auth-flat-profiles.ts @@ -48,7 +48,7 @@ import { resolveStateDir } from "../config/paths.js"; import type { AuthProfileConfig } from "../config/types.auth.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { coerceSecretRef } from "../config/types.secrets.js"; -import { loadJsonFile } from "../infra/json-file.js"; +import { loadJsonFileThroughSymlink } from "../infra/json-file.js"; import { readLegacyMigrationReceipt } from "../infra/state-migrations.receipts.js"; import type { OpenClawAgentDatabase } from "../state/openclaw-agent-db.js"; import { shortenHomePath } from "../utils.js"; @@ -888,7 +888,7 @@ function migrateLockedLegacyOAuthFile(params: { if (archivePreviouslyMigratedAuthProfileSource(receipt, params.result)) { return; } - const raw = loadJsonFile(params.oauthPath); + const raw = loadJsonFileThroughSymlink(params.oauthPath); const parsed = coerceLegacyOAuthFile(raw); const imported = parsed.store; if (!imported) { @@ -1418,7 +1418,7 @@ function resolveAwsSdkAuthProfileMarkerStore( if (!fs.existsSync(candidate.authPath)) { return null; } - const raw = loadJsonFile(candidate.authPath); + const raw = loadJsonFileThroughSymlink(candidate.authPath); if (!isRecord(raw) || !isRecord(raw.profiles)) { return null; } @@ -1897,7 +1897,7 @@ export function collectOpenAICodexAuthProfileStoreIdMap(params: { if (!fs.existsSync(candidate.authPath)) { continue; } - const raw = loadJsonFile(candidate.authPath); + const raw = loadJsonFileThroughSymlink(candidate.authPath); if (!isRecord(raw) || !isRecord(raw.profiles)) { continue; } diff --git a/src/commands/doctor-auth-oauth-sidecar.ts b/src/commands/doctor-auth-oauth-sidecar.ts index 1ba7741b448c..f19ea3bbfc4a 100644 --- a/src/commands/doctor-auth-oauth-sidecar.ts +++ b/src/commands/doctor-auth-oauth-sidecar.ts @@ -10,7 +10,7 @@ import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles/r import { formatCliCommand } from "../cli/command-format.js"; import { resolveOAuthDir, resolveStateDir } from "../config/paths.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; +import { loadJsonFileThroughSymlink, saveJsonFile } from "../infra/json-file.js"; import { shortenHomePath } from "../utils.js"; import { resolveLegacyAuthProfilesPath as resolveAuthStorePath } from "./doctor-auth-legacy-paths.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; @@ -104,7 +104,7 @@ function resolveLegacyOAuthSidecarStore( if (!fs.existsSync(candidate.authPath)) { return null; } - const raw = loadJsonFile(candidate.authPath); + const raw = loadJsonFileThroughSymlink(candidate.authPath); if (!isRecord(raw) || !isRecord(raw.profiles)) { return null; } @@ -148,7 +148,9 @@ function listUnreferencedLegacyOAuthSidecars( return []; } const sidecarPath = path.join(sidecarDir, entry.name); - return isLegacyOAuthSidecarPayload(loadJsonFile(sidecarPath)) ? [{ sidecarPath }] : []; + return isLegacyOAuthSidecarPayload(loadJsonFileThroughSymlink(sidecarPath)) + ? [{ sidecarPath }] + : []; }); } diff --git a/src/commands/doctor-browser.facade.test.ts b/src/commands/doctor-browser.facade.test.ts index b842fc837fa6..401b10750fea 100644 --- a/src/commands/doctor-browser.facade.test.ts +++ b/src/commands/doctor-browser.facade.test.ts @@ -8,10 +8,10 @@ import { noteChromeMcpBrowserReadiness, } from "./doctor-browser.js"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); vi.mock("../plugin-sdk/facade-loader.js", () => ({ - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, })); function requireFirstNoteCall(noteFn: ReturnType): unknown[] { @@ -24,12 +24,12 @@ function requireFirstNoteCall(noteFn: ReturnType): unknown[] { describe("doctor browser facade", () => { beforeEach(() => { - loadBundledPluginPublicSurfaceModuleSync.mockReset(); + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset(); }); it("delegates browser readiness checks to the browser facade surface", async () => { const delegate = vi.fn().mockResolvedValue(undefined); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ noteChromeMcpBrowserReadiness: delegate, }); @@ -42,7 +42,7 @@ describe("doctor browser facade", () => { await noteChromeMcpBrowserReadiness(cfg, { noteFn }); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-doctor.js", }); @@ -57,7 +57,7 @@ describe("doctor browser facade", () => { canonicalUserDataDir: "/tmp/openclaw-home/browser/openclaw/user-data", }; const detect = vi.fn().mockReturnValue(residue); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ noteChromeMcpBrowserReadiness: vi.fn(), detectLegacyClawdBrowserProfileResidue: detect, }); @@ -74,7 +74,7 @@ describe("doctor browser facade", () => { }; await expect(detectLegacyClawdBrowserProfileResidue(cfg, deps)).resolves.toEqual(residue); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-doctor.js", }); @@ -83,7 +83,7 @@ describe("doctor browser facade", () => { it("delegates legacy clawd browser profile cleanup to the browser facade surface", async () => { const cleanup = vi.fn().mockResolvedValue({ changes: ["archived"], warnings: [] }); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ noteChromeMcpBrowserReadiness: vi.fn(), maybeArchiveLegacyClawdBrowserProfileResidue: cleanup, }); @@ -104,7 +104,7 @@ describe("doctor browser facade", () => { changes: ["archived"], warnings: [], }); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-doctor.js", }); @@ -113,7 +113,7 @@ describe("doctor browser facade", () => { it("delegates owned Chrome native-host repair to the browser facade surface", async () => { const repair = vi.fn().mockResolvedValue({ changes: ["repaired"], warnings: [] }); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ noteChromeMcpBrowserReadiness: vi.fn(), maybeRepairOwnedChromeExtensionNativeHosts: repair, }); @@ -126,7 +126,7 @@ describe("doctor browser facade", () => { }); it("warns when browser profile cleanup surface is unavailable", async () => { - loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + loadBundledPluginPublicSurfaceModuleSyncCore.mockImplementation(() => { throw new Error("missing browser doctor facade"); }); @@ -154,7 +154,7 @@ describe("doctor browser facade", () => { }, ), ).resolves.toBeNull(); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); }); it("skips loading the browser cleanup surface when legacy residue is absent", async () => { @@ -167,11 +167,11 @@ describe("doctor browser facade", () => { }, ), ).resolves.toEqual({ changes: [], warnings: [] }); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); }); it("warns and no-ops when the browser doctor surface is unavailable", async () => { - loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + loadBundledPluginPublicSurfaceModuleSyncCore.mockImplementation(() => { throw new Error("missing browser doctor facade"); }); diff --git a/src/commands/doctor-browser.ts b/src/commands/doctor-browser.ts index 36f544693cf7..353cfbc29aa6 100644 --- a/src/commands/doctor-browser.ts +++ b/src/commands/doctor-browser.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { note } from "../../packages/terminal-core/src/note.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "../plugin-sdk/facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "../plugin-sdk/facade-loader.js"; import { resolveConfigDir } from "../utils.js"; type BrowserDoctorDeps = { @@ -52,7 +52,7 @@ type BrowserDoctorSurface = { }; function loadBrowserDoctorSurface(): BrowserDoctorSurface { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "browser", artifactBasename: "browser-doctor.js", }); diff --git a/src/commands/doctor-heartbeat-task-migration.test.ts b/src/commands/doctor-heartbeat-task-migration.test.ts index f4ca75f99564..e05006de6a3d 100644 --- a/src/commands/doctor-heartbeat-task-migration.test.ts +++ b/src/commands/doctor-heartbeat-task-migration.test.ts @@ -318,7 +318,7 @@ tasks: it("tolerates a crash after the state transaction and before legacy timestamp cleanup", async () => { const fixture = await createFixture(2_000_000_000_000); const cleanup = vi - .spyOn(sessionAccessor, "patchSessionEntry") + .spyOn(sessionAccessor, "patchSessionEntryCore") .mockRejectedValueOnce(new Error("simulated post-commit crash")); const result = await maybeMigrateHeartbeatTasksToCron({ cfg: fixture.cfg, diff --git a/src/commands/doctor-heartbeat-task-migration.ts b/src/commands/doctor-heartbeat-task-migration.ts index 2c302bb178b0..745b1345a559 100644 --- a/src/commands/doctor-heartbeat-task-migration.ts +++ b/src/commands/doctor-heartbeat-task-migration.ts @@ -7,7 +7,7 @@ import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { note } from "../../packages/terminal-core/src/note.js"; import { formatCliCommand } from "../cli/command-format.js"; import { parseDurationMs } from "../cli/parse-duration.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { heartbeatTaskDeclarationKey, isHeartbeatTaskCronJob } from "../cron/heartbeat-task.js"; import { cronSchedulingInputsEqual } from "../cron/schedule-identity.js"; @@ -377,7 +377,7 @@ async function clearLegacyTaskTimestamps(params: { env: NodeJS.ProcessEnv; tasks: readonly LegacyHeartbeatTask[]; }): Promise { - await patchSessionEntry( + await patchSessionEntryCore( { storePath: params.storePath, sessionKey: params.sessionKey, env: params.env }, (entry) => { const remaining = { ...entry.heartbeatTaskState }; diff --git a/src/commands/doctor-main-session-recovery.ts b/src/commands/doctor-main-session-recovery.ts index c4125e410bca..884234c89e04 100644 --- a/src/commands/doctor-main-session-recovery.ts +++ b/src/commands/doctor-main-session-recovery.ts @@ -3,7 +3,7 @@ import { transitionMainSessionRecovery } from "../agents/main-session-recovery/m import type { InternalSessionEntry } from "../config/sessions.js"; import { applySessionEntryReplacements, - listSessionEntries, + listSessionEntriesCore, } from "../config/sessions/session-accessor.js"; type MainSessionRecoveryDoctorParams = { @@ -18,7 +18,7 @@ type MainSessionRecoveryDoctorParams = { export async function noteMainSessionRecoveryIntegrity( params: MainSessionRecoveryDoctorParams, ): Promise { - const entries = listSessionEntries({ agentId: params.agentId, storePath: params.storePath }); + const entries = listSessionEntriesCore({ agentId: params.agentId, storePath: params.storePath }); const wedged = entries.flatMap(({ entry, sessionKey }) => { const tombstone = (entry as InternalSessionEntry).mainRestartRecovery?.tombstone; return tombstone diff --git a/src/commands/doctor-session-delivery-state.test.ts b/src/commands/doctor-session-delivery-state.test.ts index 9e6fabff8c50..a965a3ddb56e 100644 --- a/src/commands/doctor-session-delivery-state.test.ts +++ b/src/commands/doctor-session-delivery-state.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; import { closeOpenClawAgentDatabasesForTest, openOpenClawAgentDatabase, @@ -173,7 +173,7 @@ describe("doctor canonical session delivery state", () => { }); expect(readEntryValidity(env, sessionKey)).toBe(1); closeOpenClawAgentDatabasesForTest(); - expect(listSessionEntries({ agentId: "main", env })).toMatchObject([ + expect(listSessionEntriesCore({ agentId: "main", env })).toMatchObject([ { sessionKey, entry: { sessionId: "delivery-validity-session", updatedAt: 10 } }, ]); expect(repairCanonicalSessionDeliveryStates({ apply: true, cfg: {}, env })).toEqual({ @@ -195,7 +195,7 @@ describe("doctor canonical session delivery state", () => { lastAccountId: "stale-slack-bot", }); - expect(listSessionEntries({ agentId: "main", env })[0]?.entry).toMatchObject({ + expect(listSessionEntriesCore({ agentId: "main", env })[0]?.entry).toMatchObject({ updatedAt: 10, lastAccountId: "stale-slack-bot", }); @@ -209,7 +209,7 @@ describe("doctor canonical session delivery state", () => { delivery: { context: { accountId: "current-bot" } }, }); - const repaired = listSessionEntries({ agentId: "main", env })[0]?.entry; + const repaired = listSessionEntriesCore({ agentId: "main", env })[0]?.entry; expect(repaired).toMatchObject({ updatedAt: 10, delivery: { context: { accountId: "current-bot" } }, @@ -227,7 +227,7 @@ describe("doctor canonical session delivery state", () => { label: "committed", }); const originalJson = readEntryJson(env, sessionKey); - const cached = listSessionEntries({ agentId: "main", env, clone: false })[0]?.entry; + const cached = listSessionEntriesCore({ agentId: "main", env, clone: false })[0]?.entry; expect(cached?.label).toBe("committed"); expect(() => @@ -250,7 +250,7 @@ describe("doctor canonical session delivery state", () => { ).toThrow("roll back Doctor session rewrite"); expect(readEntryJson(env, sessionKey)).toBe(originalJson); - expect(listSessionEntries({ agentId: "main", env, clone: false })[0]?.entry).toBe(cached); + expect(listSessionEntriesCore({ agentId: "main", env, clone: false })[0]?.entry).toBe(cached); }); it("publishes cross-agent incognito parent rewrites to each existing SQLite connection", () => { @@ -275,7 +275,7 @@ describe("doctor canonical session delivery state", () => { "work", ); - expect(listSessionEntries({ agentId: "work", env })[0]?.entry).toMatchObject({ + expect(listSessionEntriesCore({ agentId: "work", env })[0]?.entry).toMatchObject({ updatedAt: 20, parentSessionKey: oldParentKey, }); @@ -284,7 +284,7 @@ describe("doctor canonical session delivery state", () => { repaired: 1, }); - expect(listSessionEntries({ agentId: "work", env })[0]?.entry).toMatchObject({ + expect(listSessionEntriesCore({ agentId: "work", env })[0]?.entry).toMatchObject({ updatedAt: 20, parentSessionKey: newParentKey, spawnedBy: newParentKey, @@ -444,7 +444,7 @@ describe("doctor canonical session delivery state", () => { expect(readEntryJson(env, "agent:main:invalid-object")).toBe(invalidObjectJson); expect(readEntryValidity(env, "agent:main:invalid-object")).toBe(0); closeOpenClawAgentDatabasesForTest(); - expect(() => listSessionEntries({ agentId: "main", env })).toThrow( + expect(() => listSessionEntriesCore({ agentId: "main", env })).toThrow( /invalid persisted session row requires repair/u, ); }); @@ -526,7 +526,7 @@ describe("doctor canonical session delivery state", () => { scannedStores: 1, }); closeOpenClawAgentDatabasesForTest(); - expect(listSessionEntries({ agentId: "main", env: copiedEnv })).toHaveLength(4); + expect(listSessionEntriesCore({ agentId: "main", env: copiedEnv })).toHaveLength(4); expect(repairCanonicalSessionDeliveryStates({ apply: true, cfg: {}, env: copiedEnv })).toEqual({ found: 0, repaired: 0, diff --git a/src/commands/doctor-session-incognito-key-repair.test.ts b/src/commands/doctor-session-incognito-key-repair.test.ts index ef4a7c61230d..00419c7a24ab 100644 --- a/src/commands/doctor-session-incognito-key-repair.test.ts +++ b/src/commands/doctor-session-incognito-key-repair.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import { afterEach, describe, expect, it } from "vitest"; import { createTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; import { closeOpenClawAgentDatabasesForTest, openOpenClawAgentDatabase, @@ -134,7 +134,7 @@ describe("doctor reserved incognito session key repair", () => { "INSERT INTO session_members (session_key, identity_id, added_by, added_at) VALUES (?, 'member-1', 'owner-1', 1)", ) .run(oldKey); - expect(listSessionEntries({ agentId: "main", clone: false, env })[0]?.sessionKey).toBe( + expect(listSessionEntriesCore({ agentId: "main", clone: false, env })[0]?.sessionKey).toBe( oldKey, ); @@ -146,7 +146,7 @@ describe("doctor reserved incognito session key repair", () => { found: 1, repaired: 1, }); - expect(listSessionEntries({ agentId: "main", clone: false, env })[0]?.sessionKey).toBe( + expect(listSessionEntriesCore({ agentId: "main", clone: false, env })[0]?.sessionKey).toBe( newKey, ); @@ -237,10 +237,10 @@ describe("doctor reserved incognito session key repair", () => { .get("agent:work:dashboard:regular"), ).toEqual({ entry_valid: 1 }); closeOpenClawAgentDatabasesForTest(); - expect(listSessionEntries({ agentId: "main", env })).toMatchObject([ + expect(listSessionEntriesCore({ agentId: "main", env })).toMatchObject([ { sessionKey: newKey, entry: { sessionId: "session-old", updatedAt: 1 } }, ]); - expect(listSessionEntries({ agentId: "work", env })).toMatchObject([ + expect(listSessionEntriesCore({ agentId: "work", env })).toMatchObject([ { sessionKey: "agent:work:dashboard:regular", entry: { sessionId: "session-work", updatedAt: 1 }, diff --git a/src/commands/doctor-telegram-general-topic-conversations.test.ts b/src/commands/doctor-telegram-general-topic-conversations.test.ts index ed6fb91613eb..dd0e3bcc811a 100644 --- a/src/commands/doctor-telegram-general-topic-conversations.test.ts +++ b/src/commands/doctor-telegram-general-topic-conversations.test.ts @@ -2,7 +2,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; import { listConversations } from "../config/sessions/conversation-registry.js"; -import { patchSessionEntry, upsertSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore, upsertSessionEntry } from "../config/sessions/session-accessor.js"; import { getSessionKysely, resolveSqliteReadScope, @@ -185,7 +185,7 @@ describe("doctor Telegram General-topic conversation repair", () => { }), ]); - await patchSessionEntry(scope, () => ({ displayName: "harmless later write" })); + await patchSessionEntryCore(scope, () => ({ displayName: "harmless later write" })); expect(listConversations(scope, { channel: "telegram" })).toEqual([ expect.objectContaining({ target: "telegram:-1002223334444", diff --git a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts index b0bbb319bd7a..afc9a7138b89 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.e2e.test.ts @@ -157,7 +157,7 @@ describe("doctor command", () => { }); it("routes browser readiness through health contributions and degrades gracefully when browser facade is unavailable", async () => { - const loadBundledPluginPublicSurfaceModuleSync = vi.fn(() => { + const loadBundledPluginPublicSurfaceModuleSyncCore = vi.fn(() => { throw new Error("missing browser doctor facade"); }); vi.doMock("../plugin-sdk/facade-loader.js", async () => { @@ -166,7 +166,7 @@ describe("doctor command", () => { ); return { ...actual, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, }; }); try { @@ -188,7 +188,7 @@ describe("doctor command", () => { await runDoctorNonInteractive(); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-doctor.js", }); diff --git a/src/commands/doctor/shared/codex-route-model-ref.ts b/src/commands/doctor/shared/codex-route-model-ref.ts index 42df3526fb31..b7c99b903d3d 100644 --- a/src/commands/doctor/shared/codex-route-model-ref.ts +++ b/src/commands/doctor/shared/codex-route-model-ref.ts @@ -293,10 +293,10 @@ function resolveDefaultProviderForAliasContext(params: { DEFAULT_PROVIDER, ); const parsed = - parseModelRef( + parseCodexRouteModelRef( primaryAliasRef ?? compatModelRef ?? legacyCodexModel ?? effectivePrimaryModelRef, ) ?? - parseModelRef( + parseCodexRouteModelRef( resolveConfiguredBareModelRef({ cfg: params.cfg, modelRef: effectivePrimaryModelRef, @@ -305,7 +305,7 @@ function resolveDefaultProviderForAliasContext(params: { ); return normalizeProviderId(parsed?.provider ?? DEFAULT_PROVIDER) || DEFAULT_PROVIDER; } - const implicit = parseModelRef(resolveImplicitDefaultAgentModelRef(params.cfg)); + const implicit = parseCodexRouteModelRef(resolveImplicitDefaultAgentModelRef(params.cfg)); return normalizeProviderId(implicit?.provider ?? DEFAULT_PROVIDER) || DEFAULT_PROVIDER; } @@ -353,7 +353,7 @@ function resolveConfiguredBareModelRef(params: { const matches = new Set(); const pushModelMapMatches = (models: MutableRecord | undefined) => { for (const key of Object.keys(models ?? {})) { - const parsed = parseModelRef(key); + const parsed = parseCodexRouteModelRef(key); if (parsed?.modelId === modelId) { matches.add(`${parsed.provider}/${parsed.modelId}`); } @@ -442,7 +442,9 @@ function concreteRuntimeId(runtime: string | undefined): string | undefined { return runtime && runtime !== "auto" && runtime !== "default" ? runtime : undefined; } -export function parseModelRef(modelRef: string): { provider: string; modelId: string } | undefined { +export function parseCodexRouteModelRef( + modelRef: string, +): { provider: string; modelId: string } | undefined { const slash = modelRef.indexOf("/"); if (slash <= 0 || slash >= modelRef.length - 1) { return undefined; @@ -459,7 +461,7 @@ export function canonicalOpenAIModelUsesCodexRuntime(params: { agentId?: string; env?: NodeJS.ProcessEnv; }): boolean { - const parsed = parseModelRef(params.modelRef); + const parsed = parseCodexRouteModelRef(params.modelRef); if (!parsed) { return false; } diff --git a/src/commands/doctor/shared/codex-route-runtime-policy.ts b/src/commands/doctor/shared/codex-route-runtime-policy.ts index 35e3c8280d82..8bbf96e092ab 100644 --- a/src/commands/doctor/shared/codex-route-runtime-policy.ts +++ b/src/commands/doctor/shared/codex-route-runtime-policy.ts @@ -11,7 +11,7 @@ import { isBlockedLegacyCodexModelRef, isOpenAICodexModelRef, normalizeRuntimeString, - parseModelRef, + parseCodexRouteModelRef, toCanonicalOpenAIModelRef, type LegacyCodexModelIdentity, } from "./codex-route-model-ref.js"; @@ -48,7 +48,7 @@ function resolveCurrentRuntimeIdForCanonicalModel(params: { agentId: string; env?: NodeJS.ProcessEnv; }): string { - const parsed = parseModelRef(params.modelRef); + const parsed = parseCodexRouteModelRef(params.modelRef); if (!parsed) { return "auto"; } @@ -227,7 +227,7 @@ function agentModelMapExactRuntimeIdForLegacyRef(params: { legacyModelRef: string; agentId?: string; }): string | undefined { - const parsed = parseModelRef(params.legacyModelRef); + const parsed = parseCodexRouteModelRef(params.legacyModelRef); if (!parsed) { return undefined; } @@ -270,7 +270,7 @@ function preRepairLegacyModelPolicyExplicitNonDefaultRuntimePin(params: { if (!params.legacyModelRef || !isOpenAICodexModelRef(params.legacyModelRef)) { return undefined; } - const parsed = parseModelRef(params.legacyModelRef); + const parsed = parseCodexRouteModelRef(params.legacyModelRef); if (!parsed) { return undefined; } diff --git a/src/commands/doctor/shared/codex-route-session-repair.ts b/src/commands/doctor/shared/codex-route-session-repair.ts index 53f176fe5c15..f3fd258cff31 100644 --- a/src/commands/doctor/shared/codex-route-session-repair.ts +++ b/src/commands/doctor/shared/codex-route-session-repair.ts @@ -19,7 +19,7 @@ import { import { resolveAllAgentSessionStoreTargetsSync } from "../../../config/sessions/targets.js"; import type { SessionEntry } from "../../../config/sessions/types.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; -import { loadJsonFile } from "../../../infra/json-file.js"; +import { loadJsonFileThroughSymlink } from "../../../infra/json-file.js"; import { loadLegacySessionStore, updateLegacySessionStore, @@ -335,7 +335,9 @@ function resolveVerifiedSessionAuthProfileIdMap(params: { loadPersistedAuthProfileStore(resolveSharedMainAuthAgentDir(params.env))?.profiles ?? {}; const localLegacyAuthPath = resolveLegacyAuthProfilesPath(agentDir); const localLegacySourceExists = fs.existsSync(localLegacyAuthPath); - const localLegacySource = localLegacySourceExists ? loadJsonFile(localLegacyAuthPath) : null; + const localLegacySource = localLegacySourceExists + ? loadJsonFileThroughSymlink(localLegacyAuthPath) + : null; const localLegacyProfiles = isRecord(localLegacySource) && isRecord(localLegacySource.profiles) ? localLegacySource.profiles diff --git a/src/commands/doctor/shared/codex-route-warnings.ts b/src/commands/doctor/shared/codex-route-warnings.ts index f54bfc0e5c1a..196803d33d48 100644 --- a/src/commands/doctor/shared/codex-route-warnings.ts +++ b/src/commands/doctor/shared/codex-route-warnings.ts @@ -33,7 +33,7 @@ import { collectDisabledCodexPluginRouteIssues, enableCodexPluginForRequiredRoutes, } from "./codex-route-config-scan.js"; -import { parseModelRef } from "./codex-route-model-ref.js"; +import { parseCodexRouteModelRef } from "./codex-route-model-ref.js"; import { maybeRepairCodexSessionRoutes } from "./codex-route-session-repair.js"; import type { CodexRouteHit, @@ -152,7 +152,7 @@ function ownValues(record: Record, keys: readonly string[]): un } function modelUsesCodexForEveryAgent(cfg: OpenClawConfig, modelRef: string): boolean { - const parsed = parseModelRef(modelRef); + const parsed = parseCodexRouteModelRef(modelRef); if (!parsed || parsed.modelId === "*") { return false; } @@ -179,7 +179,7 @@ function collectCodexModelParamHits( listMutableCodexRouteAgentEntries(cfg).map(({ agentId, path }) => [agentId, path]), ); for (const route of collectCodexRuntimeRouteHits(cfg, env)) { - const parsed = parseModelRef(route.canonicalModel); + const parsed = parseCodexRouteModelRef(route.canonicalModel); if (!parsed || parsed.provider !== "openai") { continue; } diff --git a/src/commands/doctor/shared/legacy-oauth-sidecar.ts b/src/commands/doctor/shared/legacy-oauth-sidecar.ts index eea4bd80a0af..5d5ee3976cf5 100644 --- a/src/commands/doctor/shared/legacy-oauth-sidecar.ts +++ b/src/commands/doctor/shared/legacy-oauth-sidecar.ts @@ -11,7 +11,7 @@ import { authProfilesLog } from "../../../agents/auth-profiles/constants.js"; import { LEGACY_OAUTH_REF_PROVIDER } from "../../../agents/auth-profiles/legacy-oauth-ref.js"; import type { LegacyOAuthRef } from "../../../agents/auth-profiles/legacy-oauth-ref.js"; import { resolveOAuthDir, resolveStateDir } from "../../../config/paths.js"; -import { loadJsonFile } from "../../../infra/json-file.js"; +import { loadJsonFileThroughSymlink } from "../../../infra/json-file.js"; import { isPathInside } from "../../../infra/path-safety.js"; export { isLegacyOAuthRef } from "../../../agents/auth-profiles/legacy-oauth-ref.js"; @@ -315,7 +315,7 @@ export function loadLegacyOAuthSidecarMaterial(params: { env?: NodeJS.ProcessEnv; }): LegacyOAuthSecretMaterial | null { const env = params.env ?? process.env; - const raw = loadJsonFile(resolveLegacyOAuthSidecarPath(params.ref, env)); + const raw = loadJsonFileThroughSymlink(resolveLegacyOAuthSidecarPath(params.ref, env)); if (!isRecord(raw)) { return null; } diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.records.ts b/src/commands/doctor/shared/missing-configured-plugin-install.records.ts index 60049a94241b..bc569bd26406 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.records.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.records.ts @@ -37,7 +37,7 @@ export function isInstalledRecordMissingOnDisk( return !existsSync(path.join(resolved, "package.json")); } -export function pathsEqual(left: string, right: string): boolean { +export function installPathsEqual(left: string, right: string): boolean { return path.resolve(left) === path.resolve(right); } @@ -139,7 +139,7 @@ export function resolveSafeBrokenOfficialInstallRemovalPath(params: { try { const extensionsDir = resolveDefaultPluginExtensionsDir(params.env); const expectedExtensionPath = resolvePluginInstallDir(params.pluginId, extensionsDir); - if (pathsEqual(resolvedInstallPath, expectedExtensionPath)) { + if (installPathsEqual(resolvedInstallPath, expectedExtensionPath)) { return resolvedInstallPath; } } catch { @@ -162,7 +162,9 @@ export function resolveSafeBrokenOfficialInstallRemovalPath(params: { npmRoot, }), ]; - return expectedNpmPaths.some((expectedPath) => pathsEqual(resolvedInstallPath, expectedPath)) + return expectedNpmPaths.some((expectedPath) => + installPathsEqual(resolvedInstallPath, expectedPath), + ) ? resolvedInstallPath : null; } diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.repair.ts b/src/commands/doctor/shared/missing-configured-plugin-install.repair.ts index 07b5d793973a..ffdfd7473341 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.repair.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.repair.ts @@ -28,7 +28,7 @@ import { forceNpmInstallRecordRepair, isInstalledRecordMissingOnDisk, isTrustedOfficialInstallRecordForCandidate, - pathsEqual, + installPathsEqual, recordMatchesBundledPackage, resolveSafeBrokenOfficialInstallRemovalPath, } from "./missing-configured-plugin-install.records.js"; @@ -354,7 +354,7 @@ async function repairMissingPluginInstalls(params: { replacementSucceeded && removalPath && (!installedRecord?.installPath || - !pathsEqual(resolveUserPath(installedRecord.installPath, env), removalPath)) + !installPathsEqual(resolveUserPath(installedRecord.installPath, env), removalPath)) ) { try { await rm(removalPath, { recursive: true, force: true }); diff --git a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts index 4e7777f93b4e..38c4a16f659f 100644 --- a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts +++ b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts @@ -10,7 +10,7 @@ import { compileGlobPatterns, matchesAnyGlobPattern } from "../../../agents/glob import { resolveProviderToolPolicy } from "../../../agents/provider-tool-policy.js"; import { mergeAlsoAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, resolveToolProfilePolicy, } from "../../../agents/tool-policy.js"; import type { AgentModelConfig } from "../../../config/types.agents-shared.js"; @@ -122,7 +122,7 @@ function collectToolOwners(registry: PluginManifestRegistry): Map(); return serverNames .map((serverName) => - normalizeToolName(`${sanitizeServerName(serverName, usedNames)}${TOOL_NAME_SEPARATOR}`), + normalizeToolPolicyName(`${sanitizeServerName(serverName, usedNames)}${TOOL_NAME_SEPARATOR}`), ) .filter(Boolean); } @@ -329,7 +329,7 @@ function entriesMatchMcpTool( serverNames: readonly string[], mode: "any" | "every", ): boolean { - const normalizedEntries = entries.map(normalizeToolName).filter(Boolean); + const normalizedEntries = entries.map(normalizeToolPolicyName).filter(Boolean); if ( normalizedEntries.some( (entry) => entry === "*" || entry === "bundle-mcp" || entry === "group:plugins", @@ -338,8 +338,11 @@ function entriesMatchMcpTool( return true; } const serverPrefixes = buildMcpToolNamePrefixes(serverNames); - const patterns = compileGlobPatterns({ raw: normalizedEntries, normalize: normalizeToolName }); - const probeNames = buildMcpProbeToolNames(serverNames).map(normalizeToolName); + const patterns = compileGlobPatterns({ + raw: normalizedEntries, + normalize: normalizeToolPolicyName, + }); + const probeNames = buildMcpProbeToolNames(serverNames).map(normalizeToolPolicyName); const prefixOrPatternMatches = (prefix: string, index: number) => normalizedEntries.some((entry) => entry.length > prefix.length && entry.startsWith(prefix)) || matchesAnyGlobPattern(probeNames[index] ?? "", patterns); @@ -532,7 +535,7 @@ export function collectPluginToolAllowlistWarnings(params: { } const wildcardSources = sources - .filter((source) => source.entries.some((entry) => normalizeToolName(entry) === "*")) + .filter((source) => source.entries.some((entry) => normalizeToolPolicyName(entry) === "*")) .map((source) => source.label); if (wildcardSources.length > 0) { warnings.push( @@ -542,7 +545,7 @@ export function collectPluginToolAllowlistWarnings(params: { const exactEntries = sources.flatMap((source) => source.entries - .map((entry) => ({ source: source.label, entry: normalizeToolName(entry) })) + .map((entry) => ({ source: source.label, entry: normalizeToolPolicyName(entry) })) .filter(({ entry }) => entry && entry !== "*" && entry !== "group:plugins"), ); if (exactEntries.length === 0) { diff --git a/src/commands/gateway-status.ts b/src/commands/gateway-status.ts index 322d3badf317..d6d835bc517a 100644 --- a/src/commands/gateway-status.ts +++ b/src/commands/gateway-status.ts @@ -1,6 +1,7 @@ /** CLI entrypoint for `openclaw gateway status`. */ import { isRich } from "../../packages/terminal-core/src/theme.js"; import { parseGatewayPortOption } from "../cli/gateway-port-option.js"; +import { parseTimeoutMsWithFallback } from "../cli/parse-timeout.js"; import { withProgress } from "../cli/progress.js"; import { readBestEffortConfig, resolveGatewayPort } from "../config/config.js"; import { ensureExplicitGatewayAuth, resolveExplicitGatewayAuth } from "../gateway/call.js"; @@ -8,12 +9,7 @@ import { resolveWideAreaDiscoveryDomain } from "../infra/widearea-dns.js"; import type { RuntimeEnv } from "../runtime.js"; import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { inferSshTargetFromRemoteUrl, resolveSshTarget } from "./gateway-status/discovery.js"; -import { - buildNetworkHints, - parseTimeoutMs, - resolveTargets, - sanitizeSshTarget, -} from "./gateway-status/helpers.js"; +import { buildNetworkHints, resolveTargets, sanitizeSshTarget } from "./gateway-status/helpers.js"; import { buildGatewayStatusWarnings, pickPrimaryProbedTarget, @@ -63,7 +59,7 @@ export async function gatewayStatusCommand( const cfg = await readBestEffortConfig(); const rich = isRich() && opts.json !== true; const defaultTimeoutMs = 3000; - const overallTimeoutMs = parseTimeoutMs(opts.timeout, defaultTimeoutMs); + const overallTimeoutMs = parseTimeoutMsWithFallback(opts.timeout, defaultTimeoutMs); const portOverride = parseGatewayPortOption(opts.port); const wideAreaDomain = resolveWideAreaDiscoveryDomain({ configDomain: cfg.discovery?.wideArea?.domain, diff --git a/src/commands/gateway-status/helpers.ts b/src/commands/gateway-status/helpers.ts index 32a52abb0768..9a900284d4a9 100644 --- a/src/commands/gateway-status/helpers.ts +++ b/src/commands/gateway-status/helpers.ts @@ -1,7 +1,6 @@ /** Shared helpers for gateway status target selection, auth, summaries, and probe rendering. */ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { colorize, theme } from "../../../packages/terminal-core/src/theme.js"; -import { parseTimeoutMsWithFallback } from "../../cli/parse-timeout.js"; import { resolveGatewayPort } from "../../config/config.js"; import type { OpenClawConfig, ConfigFileSnapshot } from "../../config/types.js"; import { hasConfiguredSecretInput } from "../../config/types.secrets.js"; @@ -69,11 +68,6 @@ function parseIntOrNull(value: unknown): number | null { return parseStrictInteger(s) ?? null; } -/** Parses CLI timeout input with the gateway-status fallback rules. */ -export function parseTimeoutMs(raw: unknown, fallbackMs: number): number { - return parseTimeoutMsWithFallback(raw, fallbackMs); -} - function normalizeWsUrl(value: string): string | null { const trimmed = value.trim(); if (!trimmed) { diff --git a/src/commands/health.plugins.test.ts b/src/commands/health.plugins.test.ts index 89032035df49..ef3e348f24a6 100644 --- a/src/commands/health.plugins.test.ts +++ b/src/commands/health.plugins.test.ts @@ -24,7 +24,7 @@ describe("collectGatewayHealthSnapshot plugin state", () => { resolveStorePath: () => "/tmp/sessions.json", })); vi.doMock("../config/sessions/session-accessor.js", () => ({ - listSessionEntries: () => [], + listSessionEntriesCore: () => [], listSessionEntriesReadOnly: () => [], })); vi.doMock("../channels/plugins/read-only.js", () => ({ diff --git a/src/commands/models/list.format.test.ts b/src/commands/models/list.format.test.ts index 85d13abf9d9e..0baac57ff5fd 100644 --- a/src/commands/models/list.format.test.ts +++ b/src/commands/models/list.format.test.ts @@ -1,7 +1,7 @@ // Model list formatting tests cover fixed-width terminal cell helpers. import { describe, expect, it } from "vitest"; import { visibleWidth } from "../../../packages/terminal-core/src/ansi.js"; -import { formatTokenK, pad, truncate } from "./list.format.js"; +import { formatTokenK, padTerminalCell, truncate } from "./list.format.js"; describe("formatTokenK", () => { it("renders token context windows in decimal K", () => { @@ -49,9 +49,9 @@ describe("truncate", () => { }); }); -describe("pad", () => { +describe("padTerminalCell", () => { it("pads to terminal visible width rather than string length", () => { - expect(pad("表", 2)).toBe("表"); - expect(pad("表", 3)).toBe("表 "); + expect(padTerminalCell("表", 2)).toBe("表"); + expect(padTerminalCell("表", 3)).toBe("表 "); }); }); diff --git a/src/commands/models/list.format.ts b/src/commands/models/list.format.ts index d23cb16da065..1465d577fa12 100644 --- a/src/commands/models/list.format.ts +++ b/src/commands/models/list.format.ts @@ -22,7 +22,7 @@ export const isRich = (opts?: { json?: boolean; plain?: boolean }) => isRichTerminal() && !opts?.json && !opts?.plain; /** Pads a table cell to a fixed terminal visible width. */ -export const pad = (value: string, size: number) => { +export const padTerminalCell = (value: string, size: number) => { const remaining = size - visibleWidth(value); return remaining > 0 ? `${value}${" ".repeat(remaining)}` : value; }; diff --git a/src/commands/models/list.probe.targets.test.ts b/src/commands/models/list.probe.targets.test.ts index 7e0a56183449..a5fba3606c05 100644 --- a/src/commands/models/list.probe.targets.test.ts +++ b/src/commands/models/list.probe.targets.test.ts @@ -115,7 +115,7 @@ vi.mock("../../secrets/resolve.js", () => ({ resolveSecretRefString: resolveSecretRefStringMock, })); vi.mock("../status-all/format.js", () => ({ - redactSecrets: (value: string) => value, + redactStatusSecrets: (value: string) => value, })); vi.mock("./shared.js", () => ({ DEFAULT_PROVIDER: "openai", diff --git a/src/commands/models/list.probe.ts b/src/commands/models/list.probe.ts index 67952aae5d00..5239439165aa 100644 --- a/src/commands/models/list.probe.ts +++ b/src/commands/models/list.probe.ts @@ -57,7 +57,7 @@ import type { GatewayLockIdentity, GatewayLockOptions } from "../../infra/gatewa import { type SecretRefResolveCache, resolveSecretRefString } from "../../secrets/resolve.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { disposeOpenClawAgentDatabaseByPath } from "../../state/openclaw-agent-db.js"; -import { redactSecrets } from "../status-all/format.js"; +import { redactStatusSecrets } from "../status-all/format.js"; import { buildProbeCandidateMap, selectProbeModel } from "./list.probe.models.js"; import { formatMs } from "./shared.js"; @@ -65,7 +65,7 @@ const PROBE_PROMPT = "Reply with OK. Do not use tools."; /** Scrubs credential-shaped text before probe failures cross a UI or CLI boundary. */ export function redactAuthProbeError(error: string): string { - return redactSecrets(error); + return redactStatusSecrets(error); } const embeddedRunnerModuleLoader = createLazyImportLoader( diff --git a/src/commands/models/list.table.ts b/src/commands/models/list.table.ts index 966034a96257..d61b633097b3 100644 --- a/src/commands/models/list.table.ts +++ b/src/commands/models/list.table.ts @@ -2,7 +2,7 @@ import { sanitizeTerminalText } from "../../../packages/terminal-core/src/safe-text.js"; import { colorize, theme } from "../../../packages/terminal-core/src/theme.js"; import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; -import { formatTag, formatTokenK, isRich, pad, truncate } from "./list.format.js"; +import { formatTag, formatTokenK, isRich, padTerminalCell, truncate } from "./list.format.js"; import type { ModelRow } from "./list.types.js"; const MODEL_PAD = 42; @@ -46,23 +46,23 @@ export function printModelTable( const rich = isRich(opts); const header = [ - pad("Model", MODEL_PAD), - pad("Input", INPUT_PAD), - pad("Ctx", CTX_PAD), - pad("Local", LOCAL_PAD), - pad("Auth", AUTH_PAD), + padTerminalCell("Model", MODEL_PAD), + padTerminalCell("Input", INPUT_PAD), + padTerminalCell("Ctx", CTX_PAD), + padTerminalCell("Local", LOCAL_PAD), + padTerminalCell("Auth", AUTH_PAD), "Tags", ].join(" "); runtime.log(rich ? theme.heading(header) : header); for (const row of rows) { - const keyLabel = pad(truncate(sanitizeTerminalText(row.key), MODEL_PAD), MODEL_PAD); - const inputLabel = pad(sanitizeTerminalText(row.input) || "-", INPUT_PAD); - const ctxLabel = pad(formatContextLabel(row), CTX_PAD); + const keyLabel = padTerminalCell(truncate(sanitizeTerminalText(row.key), MODEL_PAD), MODEL_PAD); + const inputLabel = padTerminalCell(sanitizeTerminalText(row.input) || "-", INPUT_PAD); + const ctxLabel = padTerminalCell(formatContextLabel(row), CTX_PAD); const localText = row.local === null ? "-" : row.local ? "yes" : "no"; - const localLabel = pad(localText, LOCAL_PAD); + const localLabel = padTerminalCell(localText, LOCAL_PAD); const authText = row.available === null ? "-" : row.available ? "yes" : "no"; - const authLabel = pad(authText, AUTH_PAD); + const authLabel = padTerminalCell(authText, AUTH_PAD); const tags = row.tags.map(sanitizeTerminalText); const tagsLabel = tags.length > 0 diff --git a/src/commands/models/scan.ts b/src/commands/models/scan.ts index baa8f26aacdb..f0edce5c18f5 100644 --- a/src/commands/models/scan.ts +++ b/src/commands/models/scan.ts @@ -15,7 +15,7 @@ import { parseStrictPositiveInteger, } from "../../infra/parse-finite-number.js"; import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; -import { pad, truncate } from "./list.format.js"; +import { padTerminalCell, truncate } from "./list.format.js"; import { loadModelsConfig } from "./load-config.js"; import { formatMs, formatTokenK, updateConfig } from "./shared.js"; @@ -123,27 +123,27 @@ function printMetadataOnlyNotice(params: { function printScanTable(results: ModelScanResult[], runtime: RuntimeEnv) { const header = [ - pad("Model", MODEL_PAD), - pad("Tool", 10), - pad("Image", 10), - pad("Ctx", CTX_PAD), - pad("Params", 8), + padTerminalCell("Model", MODEL_PAD), + padTerminalCell("Tool", 10), + padTerminalCell("Image", 10), + padTerminalCell("Ctx", CTX_PAD), + padTerminalCell("Params", 8), "Notes", ].join(" "); runtime.log(header); for (const entry of results) { - const modelLabel = pad(truncate(entry.modelRef, MODEL_PAD), MODEL_PAD); - const toolLabel = pad( + const modelLabel = padTerminalCell(truncate(entry.modelRef, MODEL_PAD), MODEL_PAD); + const toolLabel = padTerminalCell( entry.tool.skipped ? "skip" : entry.tool.ok ? formatMs(entry.tool.latencyMs) : "fail", 10, ); - const imageLabel = pad( + const imageLabel = padTerminalCell( entry.image.ok ? formatMs(entry.image.latencyMs) : entry.image.skipped ? "skip" : "fail", 10, ); - const ctxLabel = pad(formatTokenK(entry.contextLength), CTX_PAD); - const paramsLabel = pad(entry.inferredParamB ? `${entry.inferredParamB}b` : "-", 8); + const ctxLabel = padTerminalCell(formatTokenK(entry.contextLength), CTX_PAD); + const paramsLabel = padTerminalCell(entry.inferredParamB ? `${entry.inferredParamB}b` : "-", 8); const notes = entry.modality ? `modality:${sanitizeTerminalText(entry.modality)}` : ""; runtime.log([modelLabel, toolLabel, imageLabel, ctxLabel, paramsLabel, notes].join(" ")); diff --git a/src/commands/sessions.default-agent-store.test.ts b/src/commands/sessions.default-agent-store.test.ts index b82d30760643..195e4e813d1e 100644 --- a/src/commands/sessions.default-agent-store.test.ts +++ b/src/commands/sessions.default-agent-store.test.ts @@ -44,7 +44,7 @@ vi.mock("../infra/state-migrations.js", async () => ({ })); vi.mock("../config/sessions/session-accessor.js", () => ({ - listSessionEntries: listSessionEntriesMock, + listSessionEntriesCore: listSessionEntriesMock, listSessionEntriesReadOnly: listSessionEntriesMock, })); diff --git a/src/commands/status-all/diagnosis.ts b/src/commands/status-all/diagnosis.ts index 886a3a8c1c32..1167a390990d 100644 --- a/src/commands/status-all/diagnosis.ts +++ b/src/commands/status-all/diagnosis.ts @@ -30,7 +30,7 @@ import { } from "../status-update-restart.ts"; import type { NodeOnlyGatewayInfo } from "../status.node-mode.js"; import { formatTelemetryExporterSummary } from "../telemetry-exporter-summary.js"; -import { formatTimeAgo, redactSecrets } from "./format.js"; +import { formatTimeAgo, redactStatusSecrets } from "./format.js"; import { readFileTailLines, summarizeLogTail } from "./gateway.js"; type ConfigIssueLike = { path: string; message: string }; @@ -174,7 +174,7 @@ export async function appendStatusAllDiagnosis(params: { lines.push(""); lines.push(muted("Gateway connection details:")); - for (const line of redactSecrets(params.connectionDetailsForReport) + for (const line of redactStatusSecrets(params.connectionDetailsForReport) .split("\n") .map((l) => l.trimEnd())) { lines.push(` ${muted(line)}`); @@ -211,7 +211,7 @@ export async function appendStatusAllDiagnosis(params: { params.secretDiagnostics.length === 0 ? "ok" : "warn", ); for (const diagnostic of params.secretDiagnostics.slice(0, 10)) { - lines.push(` - ${muted(redactSecrets(diagnostic))}`); + lines.push(` - ${muted(redactStatusSecrets(diagnostic))}`); } if (params.secretDiagnostics.length > 10) { lines.push(` ${muted(`… +${params.secretDiagnostics.length - 10} more`)}`); @@ -241,7 +241,7 @@ export async function appendStatusAllDiagnosis(params: { if (lastErrClean && !isTrivialLastErr) { lines.push(""); lines.push(muted("Gateway last log line:")); - lines.push(` ${muted(redactSecrets(lastErrClean))}`); + lines.push(` ${muted(redactStatusSecrets(lastErrClean))}`); } if (params.portUsage) { @@ -420,19 +420,21 @@ export async function appendStatusAllDiagnosis(params: { lines.push(muted(`Gateway logs (tail, summarized): ${logPaths.logDir}`)); if (readStderr) { lines.push(` ${muted(`# stderr: ${logPaths.stderrPath}`)}`); - for (const line of summarizeLogTail(stderrTail, { maxLines: 22 }).map(redactSecrets)) { + for (const line of summarizeLogTail(stderrTail, { maxLines: 22 }).map( + redactStatusSecrets, + )) { lines.push(` ${muted(line)}`); } } lines.push(` ${muted(`# stdout: ${logPaths.stdoutPath}`)}`); - for (const line of summarizeLogTail(stdoutTail, { maxLines: 22 }).map(redactSecrets)) { + for (const line of summarizeLogTail(stdoutTail, { maxLines: 22 }).map(redactStatusSecrets)) { lines.push(` ${muted(line)}`); } } if (restartTail.length > 0) { lines.push(""); lines.push(muted(`Gateway restart attempts (tail): ${restartLogPath}`)); - for (const line of summarizeLogTail(restartTail, { maxLines: 16 }).map(redactSecrets)) { + for (const line of summarizeLogTail(restartTail, { maxLines: 16 }).map(redactStatusSecrets)) { lines.push(` ${muted(line)}`); } } @@ -489,7 +491,7 @@ export async function appendStatusAllDiagnosis(params: { if (healthErr) { lines.push(""); lines.push(muted("Gateway health:")); - lines.push(` ${muted(redactSecrets(healthErr))}`); + lines.push(` ${muted(redactStatusSecrets(healthErr))}`); } lines.push(""); diff --git a/src/commands/status-all/format.ts b/src/commands/status-all/format.ts index 93b55eae02ab..6fd9828cbf23 100644 --- a/src/commands/status-all/format.ts +++ b/src/commands/status-all/format.ts @@ -520,7 +520,7 @@ export function buildGatewayStatusJsonPayload(params: { } /** Redacts common credential shapes before text is printed in status diagnostics. */ -export function redactSecrets(text: string): string { +export function redactStatusSecrets(text: string): string { if (!text) { return text; } diff --git a/src/commands/status.summary.test.ts b/src/commands/status.summary.test.ts index 347bc7c7495d..666efaa083f8 100644 --- a/src/commands/status.summary.test.ts +++ b/src/commands/status.summary.test.ts @@ -11,7 +11,7 @@ const statusSummaryMocks = vi.hoisted(() => ({ hasConfiguredChannelsForReadOnlyScope: vi.fn(() => true), buildChannelSummary: vi.fn(async () => ["ok"]), resolveProviderStaticModel: vi.fn(), - listSessionEntries: vi.fn< + listSessionEntriesCore: vi.fn< (scope?: { agentId?: string; storePath?: string }) => Array<{ sessionKey: string; entry: Record; @@ -140,12 +140,12 @@ vi.mock("../config/sessions/paths.js", () => ({ vi.mock("../config/sessions/session-accessor.js", () => ({ loadExactSessionEntryReadOnly: ({ sessionKey }: { sessionKey: string }) => { const entry = statusSummaryMocks - .listSessionEntries() + .listSessionEntriesCore() .find((candidate) => candidate.sessionKey === sessionKey)?.entry; return entry ? { sessionKey, entry } : undefined; }, - listSessionEntries: statusSummaryMocks.listSessionEntries, - listSessionEntriesReadOnly: statusSummaryMocks.listSessionEntries, + listSessionEntriesCore: statusSummaryMocks.listSessionEntriesCore, + listSessionEntriesReadOnly: statusSummaryMocks.listSessionEntriesCore, })); vi.mock("../gateway/agent-list.js", () => ({ @@ -266,7 +266,7 @@ describe("getStatusSummary", () => { ? { contextWindow: 1_048_576 } : undefined, ); - statusSummaryMocks.listSessionEntries.mockReturnValue([]); + statusSummaryMocks.listSessionEntriesCore.mockReturnValue([]); vi.mocked(resolveStorePath).mockReturnValue("/tmp/sessions.json"); vi.mocked(listGatewayAgentsBasic).mockReturnValue({ defaultId: "main", @@ -320,7 +320,7 @@ describe("getStatusSummary", () => { waitingForRoute: true, }, ])("route wait: $name", async ({ routedKeys, emptyKeys, heartbeatSession, waitingForRoute }) => { - statusSummaryMocks.listSessionEntries.mockReturnValue([ + statusSummaryMocks.listSessionEntriesCore.mockReturnValue([ ...routedKeys.map((sessionKey) => ({ sessionKey, entry: { @@ -341,7 +341,7 @@ describe("getStatusSummary", () => { }); it("redacts collected session details when sensitive output is disabled", async () => { - statusSummaryMocks.listSessionEntries.mockReturnValue([ + statusSummaryMocks.listSessionEntriesCore.mockReturnValue([ { sessionKey: "agent:main:main", entry: { @@ -594,7 +594,7 @@ describe("getStatusSummary", () => { }); it("does not pass stale session contextTokens as status row overrides", async () => { - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "stale-context", @@ -664,7 +664,7 @@ describe("getStatusSummary", () => { }, }, ])("handles $name checkpoint usage provenance", async ({ checkpoint, expected }) => { - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "checkpoint-total", @@ -747,7 +747,7 @@ describe("getStatusSummary", () => { it("includes the selected agent runtime on recent sessions", async () => { vi.mocked(statusSummaryRuntime.resolveSessionRuntimeLabel).mockReturnValue("OpenAI Codex"); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", @@ -774,7 +774,7 @@ describe("getStatusSummary", () => { ]; }), ); - statusSummaryMocks.listSessionEntries.mockReturnValue(toSessionEntrySummaries(store)); + statusSummaryMocks.listSessionEntriesCore.mockReturnValue(toSessionEntrySummaries(store)); const summary = await getStatusSummary(); @@ -816,7 +816,7 @@ describe("getStatusSummary", () => { ]; }), ); - statusSummaryMocks.listSessionEntries.mockReturnValue(toSessionEntrySummaries(store)); + statusSummaryMocks.listSessionEntriesCore.mockReturnValue(toSessionEntrySummaries(store)); const summary = await getStatusSummary(); @@ -847,7 +847,7 @@ describe("getStatusSummary", () => { vi.mocked(resolveStorePath).mockImplementation((_store, opts) => { return `/tmp/${opts?.agentId ?? "main"}/sessions.json`; }); - statusSummaryMocks.listSessionEntries.mockImplementation((scope) => + statusSummaryMocks.listSessionEntriesCore.mockImplementation((scope) => scope?.agentId === "ops" ? toSessionEntrySummaries({ main: { sessionId: "ops-session", updatedAt: 2 }, @@ -859,11 +859,11 @@ describe("getStatusSummary", () => { const summary = await getStatusSummary({ includeChannelSummary: false }); - expect(statusSummaryMocks.listSessionEntries).toHaveBeenCalledWith({ + expect(statusSummaryMocks.listSessionEntriesCore).toHaveBeenCalledWith({ agentId: "main", storePath: "/tmp/main/sessions.json", }); - expect(statusSummaryMocks.listSessionEntries).toHaveBeenCalledWith({ + expect(statusSummaryMocks.listSessionEntriesCore).toHaveBeenCalledWith({ agentId: "ops", storePath: "/tmp/ops/sessions.json", }); @@ -882,7 +882,7 @@ describe("getStatusSummary", () => { agents: [{ id: "main" }, { id: "ops" }], }); vi.mocked(resolveStorePath).mockReturnValue("/tmp/shared-sessions.json"); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ main: { sessionId: "shared-session", updatedAt: 1 }, }), @@ -895,7 +895,7 @@ describe("getStatusSummary", () => { ["main", 1], ["ops", 1], ]); - expect(statusSummaryMocks.listSessionEntries).toHaveBeenCalledWith({ + expect(statusSummaryMocks.listSessionEntriesCore).toHaveBeenCalledWith({ storePath: "/tmp/shared-sessions.json", }); }); @@ -909,7 +909,7 @@ describe("getStatusSummary", () => { provider: "deepseek", model: "deepseek-v4-flash", }); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", @@ -937,7 +937,7 @@ describe("getStatusSummary", () => { provider: "deepseek", model: "deepseek-v4-flash", }); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", @@ -964,7 +964,7 @@ describe("getStatusSummary", () => { provider: "deepseek", model: "deepseek-v4-flash", }); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", @@ -994,7 +994,7 @@ describe("getStatusSummary", () => { provider: "deepseek", model: "deepseek-v4-flash", }); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:worker:subagent:configured": { sessionId: "configured-subagent", @@ -1023,7 +1023,7 @@ describe("getStatusSummary", () => { provider: "openai", model: "gpt-5.5-codex", }); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", @@ -1074,7 +1074,7 @@ describe("getStatusSummary", () => { : null; }, ); - statusSummaryMocks.listSessionEntries.mockReturnValue( + statusSummaryMocks.listSessionEntriesCore.mockReturnValue( toSessionEntrySummaries({ "agent:main:main": { sessionId: "session-1", diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index 7798c1bc5102..59579a6dac83 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -569,7 +569,7 @@ vi.mock("../config/sessions/paths.js", () => ({ resolveStorePath: mocks.resolveStorePath, })); vi.mock("../config/sessions/session-accessor.js", () => ({ - listSessionEntries: (opts?: { storePath?: string }) => + listSessionEntriesCore: (opts?: { storePath?: string }) => Object.entries(mocks.loadSessionStore(opts?.storePath)).map(([sessionKey, entry]) => ({ sessionKey, entry, diff --git a/src/config/sessions/ambient-transcript-watermark.test.ts b/src/config/sessions/ambient-transcript-watermark.test.ts index d88d95493fbe..af516b5c4fc4 100644 --- a/src/config/sessions/ambient-transcript-watermark.test.ts +++ b/src/config/sessions/ambient-transcript-watermark.test.ts @@ -5,7 +5,7 @@ import { cleanupTempDirs, makeTempDir } from "../../../test/helpers/temp-dir.js" import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js"; import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; import { - readAmbientTranscriptWatermark, + readAmbientTranscriptWatermarkFromEntry, resolveAmbientTranscriptWatermarkKey, updateAmbientTranscriptWatermark, } from "./ambient-transcript-watermark.js"; @@ -57,7 +57,7 @@ describe("ambient transcript watermark", () => { messageId: "11", timestampMs: 1_700_000_001_000, }); - expect(readAmbientTranscriptWatermark(persistedEntry, key)).toMatchObject({ + expect(readAmbientTranscriptWatermarkFromEntry(persistedEntry, key)).toMatchObject({ sessionId: "before-reset", messageId: "11", }); @@ -72,7 +72,7 @@ describe("ambient transcript watermark", () => { ); const resetEntry = loadSessionEntry({ sessionKey, storePath }); - expect(readAmbientTranscriptWatermark(resetEntry, key)).toBeUndefined(); + expect(readAmbientTranscriptWatermarkFromEntry(resetEntry, key)).toBeUndefined(); await updateAmbientTranscriptWatermark({ storePath, @@ -84,7 +84,7 @@ describe("ambient transcript watermark", () => { }); expect( - readAmbientTranscriptWatermark(loadSessionEntry({ sessionKey, storePath }), key), + readAmbientTranscriptWatermarkFromEntry(loadSessionEntry({ sessionKey, storePath }), key), ).toBeUndefined(); await updateAmbientTranscriptWatermark({ @@ -97,7 +97,7 @@ describe("ambient transcript watermark", () => { }); expect( - readAmbientTranscriptWatermark(loadSessionEntry({ sessionKey, storePath }), key), + readAmbientTranscriptWatermarkFromEntry(loadSessionEntry({ sessionKey, storePath }), key), ).toMatchObject({ sessionId: "after-reset", messageId: "12", @@ -124,7 +124,7 @@ describe("ambient transcript watermark", () => { ); expect( - readAmbientTranscriptWatermark(loadSessionEntry({ sessionKey, storePath }), key), + readAmbientTranscriptWatermarkFromEntry(loadSessionEntry({ sessionKey, storePath }), key), ).toBeUndefined(); }); }); diff --git a/src/config/sessions/ambient-transcript-watermark.ts b/src/config/sessions/ambient-transcript-watermark.ts index 6548f8b94a2d..b3041cc934e8 100644 --- a/src/config/sessions/ambient-transcript-watermark.ts +++ b/src/config/sessions/ambient-transcript-watermark.ts @@ -51,7 +51,7 @@ function isAmbientTranscriptWatermarkAfter( return next.messageId !== current.messageId; } -export function readAmbientTranscriptWatermark( +export function readAmbientTranscriptWatermarkFromEntry( entry: Pick | undefined, key: string, ): AmbientTranscriptWatermark | undefined { @@ -85,7 +85,7 @@ export async function updateAmbientTranscriptWatermark(params: { if (params.expectedSessionId !== undefined && entry.sessionId !== params.expectedSessionId) { return null; } - const current = readAmbientTranscriptWatermark(entry, params.key); + const current = readAmbientTranscriptWatermarkFromEntry(entry, params.key); if ( !isAmbientTranscriptWatermarkAfter( { messageId: params.messageId, timestampMs: params.timestampMs }, diff --git a/src/config/sessions/cleanup-service.ts b/src/config/sessions/cleanup-service.ts index cefa0b2af5f3..6ff8ebb77c33 100644 --- a/src/config/sessions/cleanup-service.ts +++ b/src/config/sessions/cleanup-service.ts @@ -17,7 +17,7 @@ import { import { resolveStorePath } from "./paths.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, loadTranscriptEventsSync, purgeDeletedAgentSessionEntries, type SessionEntryLifecycleRemoval, @@ -120,7 +120,7 @@ function loadCleanupSessionStore( return {}; } return Object.fromEntries( - listSessionEntries({ + listSessionEntriesCore({ agentId: target.agentId, storePath: target.storePath, }).map(({ sessionKey, entry }) => [sessionKey, entry]), diff --git a/src/config/sessions/combined-store-gateway.ts b/src/config/sessions/combined-store-gateway.ts index 1e1827442ccb..ede5303e4b41 100644 --- a/src/config/sessions/combined-store-gateway.ts +++ b/src/config/sessions/combined-store-gateway.ts @@ -18,7 +18,7 @@ import type { OpenClawConfig } from "../types.openclaw.js"; import { resolveStorePath } from "./paths.js"; import { countSessionEntryRowsReadOnly, - listSessionEntries, + listSessionEntriesCore, listSessionEntriesReadOnly, } from "./session-accessor.js"; import type { SessionEntryListScope } from "./session-accessor.types.js"; @@ -72,7 +72,9 @@ function loadGatewayStoreEntries(params: { projection: GatewaySessionEntryProjection; storePath: string; }) { - const listEntries = params.includeOpenDatabases ? listSessionEntries : listSessionEntriesReadOnly; + const listEntries = params.includeOpenDatabases + ? listSessionEntriesCore + : listSessionEntriesReadOnly; return listEntries({ agentId: params.agentId, clone: false, @@ -248,7 +250,7 @@ export function canPrewarmCombinedSessionStoresForGateway( } /** Loads and canonicalizes session entries for gateway views across one or more agent stores. */ -export function loadCombinedSessionStoreForGateway( +export function loadCombinedSessionStoreForGatewayCore( cfg: OpenClawConfig, opts: GatewaySessionStoreOptions = {}, ): { diff --git a/src/config/sessions/goals.ts b/src/config/sessions/goals.ts index d86b9c4bad96..0cbe27924194 100644 --- a/src/config/sessions/goals.ts +++ b/src/config/sessions/goals.ts @@ -5,7 +5,7 @@ import { type SessionStateActorType, } from "../../sessions/session-state-events.js"; import { formatTokenCount } from "../../utils/token-format.js"; -import { loadSessionEntryReadOnly, patchSessionEntry } from "./session-accessor.js"; +import { loadSessionEntryReadOnly, patchSessionEntryCore } from "./session-accessor.js"; import { resolveFreshSessionTotalTokens } from "./types.js"; import type { SessionEntry, SessionGoal, SessionGoalStatus } from "./types.js"; @@ -190,7 +190,7 @@ export async function getSessionGoal( return projected ? { status: "found", goal: projected } : { status: "missing" }; } let goal: SessionGoal | undefined; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: options.sessionKey, storePath: options.storePath }, (entry) => { const accounted = accountGoalUsage(entry, now); @@ -215,7 +215,7 @@ export async function createSessionGoal(options: CreateSessionGoalOptions): Prom } const now = nowMs(options.now); let created: SessionGoal | undefined; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: options.sessionKey, storePath: options.storePath }, (entry) => { if (entry.goal) { @@ -253,7 +253,7 @@ export async function updateSessionGoalStatus( const now = nowMs(options.now); let updated: SessionGoal | undefined; let foundSession = false; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: options.sessionKey, storePath: options.storePath }, (entry) => { foundSession = true; @@ -316,7 +316,7 @@ export async function updateSessionGoalObjective( const now = nowMs(options.now); let updated: SessionGoal | undefined; let foundSession = false; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: options.sessionKey, storePath: options.storePath }, (entry) => { foundSession = true; @@ -341,7 +341,7 @@ export async function updateSessionGoalObjective( export async function clearSessionGoal(options: SessionGoalStoreOptions): Promise { let removed = false; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: options.sessionKey, storePath: options.storePath }, (entry) => { if (!entry.goal) { diff --git a/src/config/sessions/incognito-session-transcript.test.ts b/src/config/sessions/incognito-session-transcript.test.ts index 09e76370bf7e..824148a483e0 100644 --- a/src/config/sessions/incognito-session-transcript.test.ts +++ b/src/config/sessions/incognito-session-transcript.test.ts @@ -10,10 +10,10 @@ import { import { resolveStorePath } from "./paths.js"; import { createSessionEntryWithTranscript, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, loadTranscriptEvents, - patchSessionEntry, + patchSessionEntryCore, } from "./session-accessor.js"; import { replaceTranscriptEvents } from "./session-accessor.sqlite-transcript-write.js"; @@ -114,7 +114,7 @@ describe("incognito transcript access", () => { const now = Date.now(); try { - await patchSessionEntry( + await patchSessionEntryCore( staleScope, () => ({ sessionId: "incognito-stale-session", updatedAt: now }), { @@ -130,7 +130,7 @@ describe("incognito transcript access", () => { type: "metadata", }, ]); - await patchSessionEntry( + await patchSessionEntryCore( activeScope, () => ({ sessionId: "incognito-active-session", updatedAt: now + 1 }), { @@ -140,7 +140,7 @@ describe("incognito transcript access", () => { }, ); - await patchSessionEntry(activeScope, () => ({ model: "gpt-test" }), { + await patchSessionEntryCore(activeScope, () => ({ model: "gpt-test" }), { maintenanceConfig: { highWaterBytes: null, maxDiskBytes: null, @@ -153,7 +153,7 @@ describe("incognito transcript access", () => { }); expect( - listSessionEntries({ agentId: "main", env, storePath }).map( + listSessionEntriesCore({ agentId: "main", env, storePath }).map( (summary) => summary.sessionKey, ), ).toEqual([activeScope.sessionKey]); diff --git a/src/config/sessions/session-accessor.conformance.test.ts b/src/config/sessions/session-accessor.conformance.test.ts index 884ed33644f7..98c3685da8dc 100644 --- a/src/config/sessions/session-accessor.conformance.test.ts +++ b/src/config/sessions/session-accessor.conformance.test.ts @@ -21,15 +21,15 @@ import { readSessionArchiveContentSync } from "./archive-compression.js"; import { appendTranscriptEvent, appendTranscriptMessage, - cleanupSessionLifecycleArtifacts, - listSessionEntries, + cleanupSessionLifecycleArtifactsCore, + listSessionEntriesCore, loadExactSessionEntry, loadSessionEntry, loadTranscriptEvents, onSessionIdentityMutation, - patchSessionEntry, + patchSessionEntryCore, publishTranscriptUpdate, - readSessionUpdatedAt, + readSessionUpdatedAtCore, replaceSessionEntry, resolveSessionTranscriptRuntimeTarget, updateSessionEntry, @@ -73,14 +73,16 @@ type AccessorAdapter = { transcriptScope(paths: TestPaths, id?: string): SessionTranscriptAccessScope; loadExactSessionEntry(scope: SessionAccessScope): ExactSessionEntry | undefined; loadSessionEntry(scope: SessionAccessScope): SessionEntry | undefined; - listSessionEntries(scope: Partial>): SessionEntrySummary[]; - readSessionUpdatedAt(scope: SessionAccessScope): number | undefined; + listSessionEntriesCore( + scope: Partial>, + ): SessionEntrySummary[]; + readSessionUpdatedAtCore(scope: SessionAccessScope): number | undefined; upsertSessionEntry( scope: SessionAccessScope, patch: Partial, ): Promise; replaceSessionEntry(scope: SessionAccessScope, entry: SessionEntry): Promise; - patchSessionEntry( + patchSessionEntryCore( scope: SessionAccessScope, update: ( entry: SessionEntry, @@ -92,7 +94,7 @@ type AccessorAdapter = { scope: SessionAccessScope, update: (entry: SessionEntry) => Partial | null, ): Promise; - cleanupSessionLifecycleArtifacts(params: { + cleanupSessionLifecycleArtifactsCore(params: { storePath: string; sessionKeySegmentPrefix: string; transcriptContentMarker: string; @@ -144,13 +146,13 @@ const publicAccessorAdapter: AccessorAdapter = { }), loadSessionEntry, loadExactSessionEntry, - listSessionEntries, - readSessionUpdatedAt, + listSessionEntriesCore, + readSessionUpdatedAtCore, upsertSessionEntry, replaceSessionEntry, - patchSessionEntry, + patchSessionEntryCore, updateSessionEntry, - cleanupSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore, loadTranscriptEvents, appendTranscriptEvent, appendTranscriptMessage, @@ -182,13 +184,13 @@ const sqliteAdapter: AccessorAdapter = { }), loadSessionEntry, loadExactSessionEntry, - listSessionEntries: listSessionEntryRows, - readSessionUpdatedAt, + listSessionEntriesCore: listSessionEntryRows, + readSessionUpdatedAtCore, upsertSessionEntry, replaceSessionEntry, - patchSessionEntry, - updateSessionEntry: patchSessionEntry, - cleanupSessionLifecycleArtifacts, + patchSessionEntryCore, + updateSessionEntry: patchSessionEntryCore, + cleanupSessionLifecycleArtifactsCore, loadTranscriptEvents, appendTranscriptEvent, appendTranscriptMessage, @@ -239,8 +241,8 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( sessionId: "session-1", updatedAt: expect.any(Number), }); - expect(adapter.readSessionUpdatedAt(scope)).toEqual(expect.any(Number)); - expect(adapter.listSessionEntries(scope)).toEqual([ + expect(adapter.readSessionUpdatedAtCore(scope)).toEqual(expect.any(Number)); + expect(adapter.listSessionEntriesCore(scope)).toEqual([ { sessionKey: "agent:main:main", entry: expect.objectContaining({ @@ -270,7 +272,7 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( expect(adapter.loadSessionEntry(scope)?.model).toBeUndefined(); let existingContext: SessionEntry | undefined; - await adapter.patchSessionEntry( + await adapter.patchSessionEntryCore( scope, (entry, context) => { existingContext = context.existingEntry; @@ -289,7 +291,7 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( }); const beforePreservePatch = adapter.loadSessionEntry(scope); - await adapter.patchSessionEntry( + await adapter.patchSessionEntryCore( scope, () => ({ providerOverride: "anthropic", @@ -429,14 +431,14 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( "agent:main:telegram:group:lifecycle-cleanup-room", "agent:main:regular", ]) { - expect(adapter.readSessionUpdatedAt(scopedEntry(sessionKey))).toBe(oldTimestamp); + expect(adapter.readSessionUpdatedAtCore(scopedEntry(sessionKey))).toBe(oldTimestamp); } - expect(adapter.readSessionUpdatedAt(scopedEntry("agent:main:lifecycle-cleanup-fresh"))).toBe( - nowMs, - ); + expect( + adapter.readSessionUpdatedAtCore(scopedEntry("agent:main:lifecycle-cleanup-fresh")), + ).toBe(nowMs); await expect( - adapter.cleanupSessionLifecycleArtifacts({ + adapter.cleanupSessionLifecycleArtifactsCore({ storePath: cleanupStorePath, sessionKeySegmentPrefix: "lifecycle-cleanup-", transcriptContentMarker: "lifecycle-marker-", @@ -775,14 +777,14 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( const blockedPatch = new Promise((release) => { releasePatch = release; }); - firstPatch = patchSessionEntry(scope, async () => { + firstPatch = patchSessionEntryCore(scope, async () => { resolve(); await blockedPatch; return { model: "first" }; }); }); await patchStarted; - const secondPatch = patchSessionEntry(scope, () => ({ + const secondPatch = patchSessionEntryCore(scope, () => ({ providerOverride: "openai", })); releasePatch(); @@ -813,7 +815,7 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( const updaterGate = new Promise((resolve) => { releaseUpdater = resolve; }); - const pendingPatch = patchSessionEntry(scope, async () => { + const pendingPatch = patchSessionEntryCore(scope, async () => { markUpdaterStarted(); await updaterGate; return { model: "patched" }; @@ -878,7 +880,7 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( const updaterGate = new Promise((resolve) => { releaseUpdater = resolve; }); - const pendingPatch = patchSessionEntry(scope, async () => { + const pendingPatch = patchSessionEntryCore(scope, async () => { markUpdaterStarted(); await updaterGate; return { model: "patched" }; @@ -926,7 +928,7 @@ describe.each([publicAccessorAdapter, sqliteAdapter])( const updaterGate = new Promise((resolve) => { releaseUpdater = resolve; }); - const pendingPatch = patchSessionEntry(scope, async () => { + const pendingPatch = patchSessionEntryCore(scope, async () => { markUpdaterStarted(); await updaterGate; return { model: "stale-patch" }; @@ -1539,7 +1541,7 @@ describe("sqlite session normalization", () => { }); const oldUpdatedAt = Date.now() - 2 * 24 * 60 * 60 * 1000; - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:stale"), () => ({ sessionId: "stale-session", updatedAt: oldUpdatedAt }), { @@ -1548,7 +1550,7 @@ describe("sqlite session normalization", () => { skipMaintenance: true, }, ); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:older"), () => ({ sessionId: "older-session", updatedAt: oldUpdatedAt + 1 }), { @@ -1557,7 +1559,7 @@ describe("sqlite session normalization", () => { skipMaintenance: true, }, ); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:active"), () => ({ sessionId: "active-session", updatedAt: Date.now() }), { @@ -1576,7 +1578,7 @@ describe("sqlite session normalization", () => { staleTranscriptEvent, ); - await patchSessionEntry(scopeFor("agent:main:active"), () => ({ model: "gpt-5.5" }), { + await patchSessionEntryCore(scopeFor("agent:main:active"), () => ({ model: "gpt-5.5" }), { skipMaintenance: true, }); await expect( @@ -1597,7 +1599,7 @@ describe("sqlite session normalization", () => { const notify = vi.fn(); const unsubscribe = onSessionIdentityMutation(notify); - await patchSessionEntry(scopeFor("agent:main:active"), () => ({ + await patchSessionEntryCore(scopeFor("agent:main:active"), () => ({ providerOverride: "openai", })); unsubscribe(); @@ -1631,7 +1633,7 @@ describe("sqlite session normalization", () => { .map((line) => JSON.parse(line)), ).toEqual([staleTranscriptEvent]); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:newer"), () => ({ sessionId: "newer-session", updatedAt: Date.now() + 1 }), { @@ -1640,7 +1642,7 @@ describe("sqlite session normalization", () => { skipMaintenance: true, }, ); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:newest"), () => ({ sessionId: "newest-session", updatedAt: Date.now() + 2 }), { @@ -1686,7 +1688,7 @@ describe("sqlite session normalization", () => { ["agent:main:slack:channel:c1", "channel-session-1"], ["agent:main:slack:channel:c2", "channel-session-2"], ] as const) { - await patchSessionEntry( + await patchSessionEntryCore( scopeFor(sessionKey), () => ({ sessionId, updatedAt: Date.now() - 1 }), { @@ -1697,7 +1699,7 @@ describe("sqlite session normalization", () => { ); } - await patchSessionEntry(scopeFor(cronKey), () => cronEntry, { + await patchSessionEntryCore(scopeFor(cronKey), () => cronEntry, { fallbackEntry: cronEntry, replaceEntry: true, skipMaintenance: true, @@ -1709,7 +1711,7 @@ describe("sqlite session normalization", () => { }); try { const triggerKey = "agent:main:maintenance-trigger"; - await patchSessionEntry( + await patchSessionEntryCore( scopeFor(triggerKey), () => ({ sessionId: "trigger-session", updatedAt: Date.now() + 1 }), { @@ -1746,7 +1748,7 @@ describe("sqlite session normalization", () => { const oldUpdatedAt = Date.now() - 2 * 24 * 60 * 60 * 1000; const unsharedUpdatedAt = oldUpdatedAt - 1_000; - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:unshared-budget"), () => ({ sessionId: "unshared-budget-session", updatedAt: unsharedUpdatedAt }), { @@ -1765,7 +1767,7 @@ describe("sqlite session normalization", () => { }, ); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:old-budget"), () => ({ sessionId: "old-budget-session", updatedAt: oldUpdatedAt }), { @@ -1783,7 +1785,7 @@ describe("sqlite session normalization", () => { type: "metadata", }, ); - await patchSessionEntry( + await patchSessionEntryCore( scopeFor("agent:main:active-budget"), () => ({ sessionId: "active-budget-session", @@ -1801,7 +1803,7 @@ describe("sqlite session normalization", () => { }, ); - await patchSessionEntry(scopeFor("agent:main:active-budget"), () => ({ + await patchSessionEntryCore(scopeFor("agent:main:active-budget"), () => ({ modelOverride: "gpt-5.5", })); diff --git a/src/config/sessions/session-accessor.entry-mutation.ts b/src/config/sessions/session-accessor.entry-mutation.ts index 113483c939ca..c8f4e92cb795 100644 --- a/src/config/sessions/session-accessor.entry-mutation.ts +++ b/src/config/sessions/session-accessor.entry-mutation.ts @@ -7,8 +7,8 @@ import type { DeliveryContext } from "../../utils/delivery-context.types.js"; import { resolveAccessStorePath, loadSessionEntry, - listSessionEntries, - patchSessionEntry, + listSessionEntriesCore, + patchSessionEntryCore, resolveSessionEntryFromStore, } from "./session-accessor.entry.js"; import { applySessionEntryLifecycleMutation } from "./session-accessor.lifecycle.js"; @@ -90,7 +90,10 @@ export async function createSessionEntryWithTranscript( const storePath = resolveAccessStorePath(scope); const agentId = scope.agentId ?? resolveAgentIdFromSessionKey(scope.sessionKey); const store = Object.fromEntries( - listSessionEntries({ agentId, storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), + listSessionEntriesCore({ agentId, storePath }).map(({ sessionKey, entry }) => [ + sessionKey, + entry, + ]), ); const resolved = resolveSessionEntryFromStore({ store, sessionKey: scope.sessionKey }); const created = await createEntry({ @@ -260,7 +263,7 @@ export async function updateSessionEntry( ) => Promise | null> | Partial | null, options: SessionEntryUpdateOptions = {}, ): Promise { - return await patchSessionEntry(scope, update, options); + return await patchSessionEntryCore(scope, update, options); } export type RecordInboundSessionMetaParams = { @@ -328,7 +331,7 @@ export async function markSessionAbortTarget(params: { let resolvedTarget: SessionAbortTargetResult | null = null; try { const sessionKey = normalizeStoreSessionKey(params.scope.sessionKey); - const updated = await patchSessionEntry( + const updated = await patchSessionEntryCore( params.scope, (currentEntry) => { resolvedTarget = { diff --git a/src/config/sessions/session-accessor.entry.ts b/src/config/sessions/session-accessor.entry.ts index 790785a4fbe5..da0e19f0e6df 100644 --- a/src/config/sessions/session-accessor.entry.ts +++ b/src/config/sessions/session-accessor.entry.ts @@ -28,9 +28,9 @@ import { loadExactSessionEntryReadOnly, loadSessionEntry, loadSessionEntryReadOnly, - patchSessionEntry, + patchSessionEntryCore, patchSessionEntryTarget, - readSessionUpdatedAt, + readSessionUpdatedAtCore, replaceSessionEntry, replaceSessionEntrySync, resolveSessionEntry, @@ -80,9 +80,9 @@ export { loadExactSessionEntryReadOnly, loadSessionEntry, loadSessionEntryReadOnly, - patchSessionEntry, + patchSessionEntryCore, patchSessionEntryTarget, - readSessionUpdatedAt, + readSessionUpdatedAtCore, replaceSessionEntry, // Intentionally unfenced: branching owns session-identity freshness; worker transcript commit // fresh-reads and checks sessionId inside its locked commit, and void/entry has no rebound signal. @@ -345,7 +345,7 @@ export async function updateResolvedSessionEntry( return { canonicalKey: target.canonicalKey, found: false }; } let updateResult: T | undefined; - const updated = await patchSessionEntry( + const updated = await patchSessionEntryCore( { sessionKey: target.storeKey, storePath: target.storePath }, async (entry) => { const context: ResolvedSessionEntryUpdateContext = { @@ -376,7 +376,7 @@ export async function updateResolvedSessionEntry( } /** Lists entries from the resolved store, preserving the persisted key for each row. */ -export function listSessionEntries(scope: SessionEntryListScope = {}): SessionEntrySummary[] { +export function listSessionEntriesCore(scope: SessionEntryListScope = {}): SessionEntrySummary[] { if (scope.clone === false) { return openSessionEntryReadView(scope).entries(); } @@ -416,7 +416,7 @@ export async function patchSessionEntryWithKey( ) => Promise | null> | Partial | null, options: SessionEntryPatchOptions = {}, ): Promise { - const entry = await patchSessionEntry(scope, update, options); + const entry = await patchSessionEntryCore(scope, update, options); return entry ? { sessionKey: normalizeStoreSessionKey(scope.sessionKey), entry } : null; } diff --git a/src/config/sessions/session-accessor.lifecycle.ts b/src/config/sessions/session-accessor.lifecycle.ts index 143d1c8fc37c..07f2c585a17c 100644 --- a/src/config/sessions/session-accessor.lifecycle.ts +++ b/src/config/sessions/session-accessor.lifecycle.ts @@ -12,13 +12,13 @@ import { resolveAccessStorePath, loadSessionEntry, loadExactSessionEntry, - listSessionEntries, + listSessionEntriesCore, replaceSessionEntry, - patchSessionEntry, + patchSessionEntryCore, } from "./session-accessor.entry.js"; import { applySessionEntryBatchProjection } from "./session-accessor.sqlite-batch-projection.js"; import { - cleanupSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore, deleteSessionEntryLifecycle, rollbackAgentHarnessSessionEntryLifecycle, rollbackPluginOwnedSessionEntryLifecycle, @@ -57,7 +57,7 @@ export { applySessionEntryLifecycleMutation, applySessionEntryReplacements, applySessionStoreProjection, - cleanupSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore, deleteSessionEntryLifecycle, purgeDeletedAgentSessionEntries, resetSessionEntryLifecycle, @@ -361,7 +361,7 @@ export async function cleanupPluginHostSessionStore( } const now = Date.now(); let cleared = 0; - for (const { entry, sessionKey } of listSessionEntries({ + for (const { entry, sessionKey } of listSessionEntriesCore({ agentId: params.agentId, storePath: params.storePath, })) { @@ -374,7 +374,7 @@ export async function cleanupPluginHostSessionStore( ) { continue; } - const updated = await patchSessionEntry( + const updated = await patchSessionEntryCore( { agentId: params.agentId, sessionKey, storePath: params.storePath }, (currentEntry) => { if (isLockedHarnessSessionOwnedByPlugin(currentEntry, params.preserveLockedHarnessIds)) { diff --git a/src/config/sessions/session-accessor.readonly.test.ts b/src/config/sessions/session-accessor.readonly.test.ts index 5a052c000998..074a409e6c99 100644 --- a/src/config/sessions/session-accessor.readonly.test.ts +++ b/src/config/sessions/session-accessor.readonly.test.ts @@ -13,7 +13,7 @@ import { } from "../../state/openclaw-state-db.js"; import { hasSessionEntriesByStatusReadOnly, - listSessionEntries, + listSessionEntriesCore, listSessionEntriesReadOnly, resolveTranscriptSessionKeyBySessionId, upsertSessionEntry, @@ -52,7 +52,7 @@ describe("session accessor readonly listing", () => { { ...listScope, sessionKey: "agent:worker-1:telegram:dm:42" }, { sessionId: "session-2", updatedAt: 20 }, ); - const writableEntries = listSessionEntries(listScope); + const writableEntries = listSessionEntriesCore(listScope); closeOpenClawAgentDatabasesForTest(); expect(listSessionEntriesReadOnly(listScope)).toEqual(writableEntries); diff --git a/src/config/sessions/session-accessor.reset.ts b/src/config/sessions/session-accessor.reset.ts index 3965d7285aa5..3fe5b3db19c4 100644 --- a/src/config/sessions/session-accessor.reset.ts +++ b/src/config/sessions/session-accessor.reset.ts @@ -11,7 +11,7 @@ import { resolveInitializedReplySessionEntry, } from "./session-accessor.entry-mutation.js"; import { - listSessionEntries, + listSessionEntriesCore, listSessionEntriesReadOnly, resolveSessionEntryFromStore, } from "./session-accessor.entry.js"; @@ -153,7 +153,7 @@ export async function commitReplySessionInitialization(params: { storePath: params.storePath, }); const store = Object.fromEntries( - listSessionEntries({ agentId: params.agentId, storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ agentId: params.agentId, storePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), diff --git a/src/config/sessions/session-accessor.sqlite-active-events.ts b/src/config/sessions/session-accessor.sqlite-active-events.ts index 3444012914de..64cc99e816d4 100644 --- a/src/config/sessions/session-accessor.sqlite-active-events.ts +++ b/src/config/sessions/session-accessor.sqlite-active-events.ts @@ -178,7 +178,7 @@ export function readSessionTranscriptActiveStats(scope: SessionTranscriptReadSco } /** Reads one append-stable forward page from the materialized active-message projection. */ -export function readSessionTranscriptVisibleMessageDelta( +export function readSessionTranscriptVisibleMessageDeltaCore( scope: SessionTranscriptReadScope, limits: SessionTranscriptVisibleMessageDeltaLimits = {}, ): SessionTranscriptVisibleMessageDeltaResult { diff --git a/src/config/sessions/session-accessor.sqlite-cleanup-race.test.ts b/src/config/sessions/session-accessor.sqlite-cleanup-race.test.ts index 6e7877963684..b9683dfb5cdc 100644 --- a/src/config/sessions/session-accessor.sqlite-cleanup-race.test.ts +++ b/src/config/sessions/session-accessor.sqlite-cleanup-race.test.ts @@ -7,7 +7,7 @@ import { } from "../../state/openclaw-agent-db.js"; import { applySessionEntryLifecycleMutation, - cleanupSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore, deleteSessionEntryLifecycle, loadSessionEntry, loadTranscriptEvents, @@ -71,7 +71,7 @@ describe("SQLite lifecycle cleanup races", () => { ); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, sessionKeySegmentPrefix: "cleanup-race-", transcriptContentMarker: "cleanup-race-marker", @@ -100,7 +100,7 @@ describe("SQLite lifecycle cleanup races", () => { ]); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, sessionKeySegmentPrefix: "cleanup-race-", transcriptContentMarker: "cleanup-race-marker", @@ -132,7 +132,7 @@ describe("SQLite lifecycle cleanup races", () => { ); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, pluginOwnerId: "memory-core", sessionKeySegmentPrefix: "cleanup-race-", @@ -190,7 +190,7 @@ describe("SQLite lifecycle cleanup races", () => { ); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ agentId: "main", storePath: sharedStorePath, pluginOwnerId: "memory-core", @@ -245,7 +245,7 @@ describe("SQLite lifecycle cleanup races", () => { ); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, pluginOwnerId: "memory-core", sessionKeySegmentPrefix: "cleanup-race-", @@ -283,7 +283,7 @@ describe("SQLite lifecycle cleanup races", () => { .run("{}", -1, sessionKey); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, pluginOwnerId: "memory-core", sessionKeySegmentPrefix: "cleanup-race-", @@ -333,7 +333,7 @@ describe("SQLite lifecycle cleanup races", () => { ); await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, pluginOwnerId: "memory-core", sessionKeySegmentPrefix: "cleanup-race-", @@ -390,7 +390,7 @@ describe("SQLite lifecycle cleanup races", () => { try { await expect( - cleanupSessionLifecycleArtifacts({ + cleanupSessionLifecycleArtifactsCore({ storePath, sessionKeySegmentPrefix: "cleanup-race", transcriptContentMarker: "cleanup-race-marker", @@ -567,7 +567,7 @@ describe("SQLite lifecycle cleanup races", () => { await materializationGate; }; - const cleanup = cleanupSessionLifecycleArtifacts({ + const cleanup = cleanupSessionLifecycleArtifactsCore({ storePath, sessionKeySegmentPrefix: "cleanup-race-", transcriptContentMarker: "cleanup-race-marker", diff --git a/src/config/sessions/session-accessor.sqlite-data-version.test.ts b/src/config/sessions/session-accessor.sqlite-data-version.test.ts index 4c25c8fecb81..665a88d476e5 100644 --- a/src/config/sessions/session-accessor.sqlite-data-version.test.ts +++ b/src/config/sessions/session-accessor.sqlite-data-version.test.ts @@ -13,7 +13,7 @@ import { import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; import { appendTranscriptMessage, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, openSessionEntryReadView, upsertSessionEntry, @@ -170,19 +170,19 @@ describe("SQLite session entry cache", () => { }, }); - const fullEntry = listSessionEntries({ ...scope, clone: false })[0]?.entry; + const fullEntry = listSessionEntriesCore({ ...scope, clone: false })[0]?.entry; expect(fullEntry).toBeDefined(); if (!fullEntry) { throw new Error("missing seeded lazy-list-projection entry"); } const cloneSpy = vi.spyOn(globalThis, "structuredClone"); try { - const first = listSessionEntries({ + const first = listSessionEntriesCore({ ...scope, clone: false, projection: "list", })[0]?.entry; - const second = listSessionEntries({ + const second = listSessionEntriesCore({ ...scope, clone: false, projection: "list", @@ -208,9 +208,9 @@ describe("SQLite session entry cache", () => { ); parseSessionEntryCalls.mockClear(); - const first = listSessionEntries(scope); + const first = listSessionEntriesCore(scope); const firstParseCount = parseSessionEntryCalls.mock.calls.length; - const second = listSessionEntries(scope); + const second = listSessionEntriesCore(scope); expect(firstParseCount).toBe(2); expect(parseSessionEntryCalls).toHaveBeenCalledTimes(firstParseCount); @@ -225,7 +225,7 @@ describe("SQLite session entry cache", () => { updatedAt: 1, }); const primary = openOpenClawAgentDatabase(scope); - const first = listSessionEntries({ ...scope, clone: false }); + const first = listSessionEntriesCore({ ...scope, clone: false }); const alternate = new DatabaseSync(primary.path, { readOnly: true }); try { @@ -239,7 +239,7 @@ describe("SQLite session entry cache", () => { expect(parseSessionEntryCalls).toHaveBeenCalledOnce(); parseSessionEntryCalls.mockClear(); - const second = listSessionEntries({ ...scope, clone: false }); + const second = listSessionEntriesCore({ ...scope, clone: false }); expect(second[0]?.entry).toBe(first[0]?.entry); expect(parseSessionEntryCalls).not.toHaveBeenCalled(); @@ -271,7 +271,7 @@ describe("SQLite session entry cache", () => { sessionId: "same-connection-non-entry-2", updatedAt: 1, }); - const first = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const first = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); await appendTranscriptMessage( { ...scope, sessionId: "same-connection-non-entry" }, @@ -280,7 +280,7 @@ describe("SQLite session entry cache", () => { parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const second = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const second = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(second.map((row) => row.entry)).toEqual(first.map((row) => row.entry)); expect(second[0]?.entry).toBe(first[0]?.entry); @@ -303,7 +303,7 @@ describe("SQLite session entry cache", () => { sessionId: "external-sibling", updatedAt: 1, }); - const before = listSessionEntries({ ...scope, clone: false, projection: "list" })[0]?.entry; + const before = listSessionEntriesCore({ ...scope, clone: false, projection: "list" })[0]?.entry; expect(before).toBeDefined(); if (!before) { throw new Error("missing seeded external-write entry"); @@ -328,7 +328,7 @@ describe("SQLite session entry cache", () => { parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); expect( - listSessionEntries({ ...scope, clone: false, projection: "list" })[0]?.entry.label, + listSessionEntriesCore({ ...scope, clone: false, projection: "list" })[0]?.entry.label, ).toBe("projection-probe-after"); expect(parseSessionEntryCalls).toHaveBeenCalledTimes(2); expect(listProjectionCalls).toHaveBeenCalledTimes(2); @@ -351,7 +351,7 @@ describe("SQLite session entry cache", () => { sessionId: "external-same-ms-sibling", updatedAt: 1_000, }); - const before = listSessionEntries({ ...scope, clone: false, projection: "list" })[0]?.entry; + const before = listSessionEntriesCore({ ...scope, clone: false, projection: "list" })[0]?.entry; if (!before) { throw new Error("missing seeded external-same-ms entry"); } @@ -373,7 +373,7 @@ describe("SQLite session entry cache", () => { parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const after = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const after = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(after[0]?.entry.label).toBe("projection-probe-after"); expect(parseSessionEntryCalls).toHaveBeenCalledTimes(2); @@ -397,7 +397,7 @@ describe("SQLite session entry cache", () => { sessionId: "external-race-sibling", updatedAt: 1, }); - listSessionEntries(scope); + listSessionEntriesCore(scope); const database = openOpenClawAgentDatabase(scope); const localEntry = { @@ -429,7 +429,7 @@ describe("SQLite session entry cache", () => { .run(JSON.stringify(externalEntry), externalEntry.updatedAt, siblingScope.sessionKey); }; - const entries = listSessionEntries(scope); + const entries = listSessionEntriesCore(scope); const byId = new Map(entries.map((row) => [row.entry.sessionId, row.entry])); expect(byId.get("external-race-local")?.label).toBe("local-after"); @@ -454,7 +454,7 @@ describe("SQLite session entry cache", () => { sessionId: "same-connection-removed", updatedAt: 1, }); - const before = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const before = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); const keptProjection = before.find((row) => row.sessionKey === scope.sessionKey)?.entry; const database = openOpenClawAgentDatabase(scope); @@ -480,7 +480,7 @@ describe("SQLite session entry cache", () => { parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const entries = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const entries = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(entries.map((row) => row.sessionKey)).toEqual( [scope.sessionKey, insertedKey].toSorted(), @@ -506,7 +506,7 @@ describe("SQLite session entry cache", () => { }, ); } - listSessionEntries({ ...scope, clone: false, projection: "list" }); + listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); const database = openOpenClawAgentDatabase(scope); for (const index of [3, 19]) { @@ -525,7 +525,7 @@ describe("SQLite session entry cache", () => { parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const entries = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const entries = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(entries).toHaveLength(rowCount); expect(parseSessionEntryCalls).toHaveBeenCalledTimes(2); @@ -545,7 +545,7 @@ describe("SQLite session entry cache", () => { sessionId: "write-through-sibling", updatedAt: 1, }); - const before = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const before = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); const siblingBefore = before.find((row) => row.sessionKey === siblingScope.sessionKey)?.entry; parseSessionEntryCalls.mockClear(); @@ -553,7 +553,7 @@ describe("SQLite session entry cache", () => { await upsertSessionEntry(scope, { label: "projection-probe-after", updatedAt: 2 }); parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const after = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const after = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(after.find((row) => row.sessionKey === scope.sessionKey)?.entry.label).toBe( "projection-probe-after", @@ -573,7 +573,8 @@ describe("SQLite session entry cache", () => { sessionId: "write-through-existing", updatedAt: 1, }); - const existing = listSessionEntries({ ...scope, clone: false, projection: "list" })[0]?.entry; + const existing = listSessionEntriesCore({ ...scope, clone: false, projection: "list" })[0] + ?.entry; const insertedScope = { ...scope, sessionKey: "agent:main:write-through-inserted" }; parseSessionEntryCalls.mockClear(); @@ -585,7 +586,7 @@ describe("SQLite session entry cache", () => { }); parseSessionEntryCalls.mockClear(); listProjectionCalls.mockClear(); - const after = listSessionEntries({ ...scope, clone: false, projection: "list" }); + const after = listSessionEntriesCore({ ...scope, clone: false, projection: "list" }); expect(after.map((row) => row.sessionKey)).toEqual( [scope.sessionKey, insertedScope.sessionKey].toSorted(), @@ -604,7 +605,7 @@ describe("SQLite session entry cache", () => { sessionId: "tracked", updatedAt: 1, }); - listSessionEntries(scope); + listSessionEntriesCore(scope); const database = openOpenClawAgentDatabase(scope); const rawEntry = { label: "raw-after", sessionId: "raw", updatedAt: Date.now() }; @@ -614,7 +615,7 @@ describe("SQLite session entry cache", () => { await upsertSessionEntry(trackedScope, { label: "tracked-after", updatedAt: 2 }); parseSessionEntryCalls.mockClear(); - const entries = listSessionEntries(scope); + const entries = listSessionEntriesCore(scope); const entriesBySessionId = new Map(entries.map((row) => [row.entry.sessionId, row.entry])); expect(entriesBySessionId.get("raw")).toMatchObject(rawEntry); @@ -683,7 +684,7 @@ describe("SQLite session entry cache", () => { const scope = createSessionScope("clone-borrow"); await upsertSessionEntry(scope, { label: "original", sessionId: "clone", updatedAt: 1 }); - const cloned = listSessionEntries(scope)[0]?.entry; + const cloned = listSessionEntriesCore(scope)[0]?.entry; expect(cloned).toBeDefined(); if (cloned) { cloned.label = "mutated"; @@ -699,7 +700,7 @@ describe("SQLite session entry cache", () => { it("honors latest reads after an untracked own-connection write", async () => { const scope = createSessionScope("latest"); await upsertSessionEntry(scope, { label: "cached", sessionId: "latest", updatedAt: 1 }); - expect(listSessionEntries(scope)[0]?.entry.label).toBe("cached"); + expect(listSessionEntriesCore(scope)[0]?.entry.label).toBe("cached"); const database = openOpenClawAgentDatabase(scope); const updated = { label: "latest", sessionId: "latest", updatedAt: 2 }; @@ -707,7 +708,7 @@ describe("SQLite session entry cache", () => { .prepare("UPDATE session_nodes SET entry_json = ?, updated_at = ? WHERE session_key = ?") .run(JSON.stringify(updated), updated.updatedAt, scope.sessionKey); - expect(listSessionEntries(scope)[0]?.entry.label).toBe("latest"); + expect(listSessionEntriesCore(scope)[0]?.entry.label).toBe("latest"); expect(sessionNodeVersionScans.rowCounts).toEqual([1]); expect(loadSessionEntry({ ...scope, readConsistency: "latest" })?.label).toBe("latest"); }); diff --git a/src/config/sessions/session-accessor.sqlite-entry.ts b/src/config/sessions/session-accessor.sqlite-entry.ts index 659f98f2b5c6..9612bca99399 100644 --- a/src/config/sessions/session-accessor.sqlite-entry.ts +++ b/src/config/sessions/session-accessor.sqlite-entry.ts @@ -401,7 +401,7 @@ export function listSessionTranscriptInstances( } /** Reads a session activity timestamp from the additive SQLite session store. */ -export function readSessionUpdatedAt(scope: SessionAccessScope): number | undefined { +export function readSessionUpdatedAtCore(scope: SessionAccessScope): number | undefined { const resolved = resolveSqliteScope(scope); const database = openOpenClawAgentDatabase(toDatabaseOptions(resolved)); const row = readSessionEntryRow(database, resolved.sessionKey)?.row; @@ -413,7 +413,7 @@ export async function upsertSessionEntry( scope: SessionAccessScope, patch: Partial, ): Promise { - return await patchSessionEntry(scope, () => patch, { + return await patchSessionEntryCore(scope, () => patch, { fallbackEntry: createFallbackSessionEntry(patch), }); } @@ -423,7 +423,7 @@ export async function replaceSessionEntry( scope: SessionAccessScope, entry: SessionEntry, ): Promise { - return await patchSessionEntry(scope, () => entry, { + return await patchSessionEntryCore(scope, () => entry, { fallbackEntry: entry, replaceEntry: true, }); @@ -487,7 +487,7 @@ export function ensureSessionEntrySync( } /** Patches one entry in the additive SQLite session store. */ -export async function patchSessionEntry( +export async function patchSessionEntryCore( scope: SessionAccessScope, update: ( entry: SessionEntry, @@ -663,7 +663,7 @@ export async function recordInboundSessionMeta(params: { createIfMissing?: boolean; }): Promise { const createIfMissing = params.createIfMissing ?? true; - return await patchSessionEntry( + return await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (_entry, context) => { const metadataPatch = deriveSessionMetaPatch({ @@ -710,7 +710,7 @@ export async function updateSessionLastRoute(params: { createIfMissing?: boolean; }): Promise { const createIfMissing = params.createIfMissing ?? true; - return await patchSessionEntry( + return await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (_entry, context) => { const routePatch = deriveLastRoutePatch({ diff --git a/src/config/sessions/session-accessor.sqlite-lifecycle.ts b/src/config/sessions/session-accessor.sqlite-lifecycle.ts index 805e2ac90998..6b400e871ecf 100644 --- a/src/config/sessions/session-accessor.sqlite-lifecycle.ts +++ b/src/config/sessions/session-accessor.sqlite-lifecycle.ts @@ -105,7 +105,7 @@ function deleteSessionBoardRows( executeSqliteQuerySync(database.db, db.deleteFrom("board_tabs").where("session_key", "in", keys)); } -export async function cleanupSessionLifecycleArtifacts( +export async function cleanupSessionLifecycleArtifactsCore( params: SessionLifecycleArtifactCleanupParams, ): Promise { const sessionKeySegmentPrefix = params.sessionKeySegmentPrefix.trim(); diff --git a/src/config/sessions/session-accessor.test.ts b/src/config/sessions/session-accessor.test.ts index fb3225e88077..8eeea569e335 100644 --- a/src/config/sessions/session-accessor.test.ts +++ b/src/config/sessions/session-accessor.test.ts @@ -40,7 +40,7 @@ import { deleteSessionEntryLifecycle, findTranscriptEvent, ensureSessionEntrySync, - listSessionEntries, + listSessionEntriesCore, listSessionEntriesByStatus, listSessionTranscriptInstances, loadReplySessionInitializationSnapshot, @@ -50,11 +50,11 @@ import { markSessionAbortTarget, onSessionIdentityMutation, openSessionEntryReadView, - patchSessionEntry, + patchSessionEntryCore, patchSessionEntryTarget, persistSessionTranscriptTurn, readTranscriptStatsSync, - readSessionUpdatedAt, + readSessionUpdatedAtCore, recordInboundSessionMeta, replaceSessionEntry, replaceTranscriptEventsSync, @@ -225,8 +225,8 @@ describe("session accessor seam", () => { sessionId: "session-1", updatedAt: expect.any(Number), }); - expect(readSessionUpdatedAt(scope)).toEqual(expect.any(Number)); - expect(listSessionEntries({ storePath })).toEqual([ + expect(readSessionUpdatedAtCore(scope)).toEqual(expect.any(Number)); + expect(listSessionEntriesCore({ storePath })).toEqual([ { sessionKey: "agent:main:main", entry: expect.objectContaining({ @@ -658,7 +658,7 @@ describe("session accessor seam", () => { expect(loadSessionEntry({ sessionKey: mixedKey, storePath })?.sessionId).toBe("mixed-session"); expect(loadSessionEntry({ sessionKey: lowerKey, storePath })?.sessionId).toBe("lower-session"); expect( - listSessionEntries({ agentId: "voice", storePath }).map((entry) => entry.sessionKey), + listSessionEntriesCore({ agentId: "voice", storePath }).map((entry) => entry.sessionKey), ).toEqual([mixedKey, lowerKey]); }); @@ -844,7 +844,7 @@ describe("session accessor seam", () => { const sessionKey = "agent:main:other"; const scope = { sessionKey, storePath }; await replaceSessionEntry(scope, { sessionId: "created", updatedAt: 10 }); - await patchSessionEntry(scope, () => ({ label: "same identity" })); + await patchSessionEntryCore(scope, () => ({ label: "same identity" })); await replaceSessionEntry(scope, { sessionId: "replaced", updatedAt: 20 }); const target = { canonicalKey: sessionKey, storeKeys: [sessionKey] }; await resetSessionEntryLifecycle({ @@ -1431,7 +1431,7 @@ describe("session accessor seam", () => { phase: "entry", }); expect(loadSessionEntry(scope)).toBeUndefined(); - expect(listSessionEntries({ storePath })).toEqual([]); + expect(listSessionEntriesCore({ storePath })).toEqual([]); }); it("does not write the session database when entry preparation is rejected", async () => { @@ -1779,7 +1779,7 @@ describe("session accessor seam", () => { ); expect( - listSessionEntries({ agentId: "main", storePath: sharedStorePath }).map( + listSessionEntriesCore({ agentId: "main", storePath: sharedStorePath }).map( ({ sessionKey }) => sessionKey, ), ).toEqual(["agent:main:shared", "agent:ops:shared"]); @@ -1829,7 +1829,7 @@ describe("session accessor seam", () => { }; await expect(updateSessionEntry(scope, () => ({ model: "gpt-5.5" }))).resolves.toBeNull(); - expect(listSessionEntries({ storePath })).toEqual([]); + expect(listSessionEntriesCore({ storePath })).toEqual([]); await upsertSessionEntry(scope, { sessionId: "session-1", @@ -1884,7 +1884,7 @@ describe("session accessor seam", () => { let missingContextEntry: SessionEntry | undefined; let existingContextEntry: SessionEntry | undefined; - await patchSessionEntry( + await patchSessionEntryCore( scope, (entry, context) => { missingContextEntry = context.existingEntry; @@ -1902,7 +1902,7 @@ describe("session accessor seam", () => { }, ); - await patchSessionEntry( + await patchSessionEntryCore( scope, (entry, context) => { existingContextEntry = context.existingEntry; @@ -1935,7 +1935,7 @@ describe("session accessor seam", () => { }); await expect( - patchSessionEntry(scope, () => ({ model: "gpt-5.5" }), { + patchSessionEntryCore(scope, () => ({ model: "gpt-5.5" }), { assertCommitAllowed: () => { throw new Error("owner retired"); }, @@ -1957,7 +1957,7 @@ describe("session accessor seam", () => { }); const beforePatch = loadSessionEntry(scope); - await patchSessionEntry( + await patchSessionEntryCore( scope, () => ({ model: "gpt-5.5", diff --git a/src/config/sessions/session-accessor.ts b/src/config/sessions/session-accessor.ts index 10c8a67c5770..5c6f2e1b4a74 100644 --- a/src/config/sessions/session-accessor.ts +++ b/src/config/sessions/session-accessor.ts @@ -133,7 +133,7 @@ export { listSessionGenerationIdsForCanonicalRepair, clearPluginOwnedSessionState, listSessionChildEntriesReadOnly, - listSessionEntries, + listSessionEntriesCore, listSessionEntriesReadOnly, listSessionEntriesForCanonicalRepair, rehomeSessionDeliveryReferencesForCanonicalRepair, @@ -144,10 +144,10 @@ export { loadSessionEntry, loadSessionEntryReadOnly, openSessionEntryReadView, - patchSessionEntry, + patchSessionEntryCore, patchSessionEntryTarget, patchSessionEntryWithKey, - readSessionUpdatedAt, + readSessionUpdatedAtCore, replaceSessionEntry, replaceSessionEntrySync, resolveSessionEntryAccessTarget, @@ -175,7 +175,7 @@ export { applySessionStoreProjection, branchSessionFromCompactionCheckpoint, cleanupPluginHostSessionStore, - cleanupSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore, deleteSessionEntryLifecycle, preserveTemporarySessionMapping, purgeDeletedAgentSessionEntries, @@ -243,7 +243,7 @@ export { readSessionTranscriptMessageEventCount, readSessionTranscriptMessageEventPage, readSessionTranscriptMessageEvents, - readSessionTranscriptVisibleMessageDelta, + readSessionTranscriptVisibleMessageDeltaCore, SessionTranscriptProjectionUnavailableError, waitForSessionTranscriptProjection, } from "./session-accessor.sqlite-active-events.js"; diff --git a/src/config/sessions/session-registry-maintenance.ts b/src/config/sessions/session-registry-maintenance.ts index e0a024188256..dfac4edf6288 100644 --- a/src/config/sessions/session-registry-maintenance.ts +++ b/src/config/sessions/session-registry-maintenance.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import { parseAgentSessionKey } from "../../sessions/session-key-utils.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, type SessionEntryLifecycleRemoval, } from "./session-accessor.js"; import { resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target.js"; @@ -113,7 +113,7 @@ export async function runSessionRegistryMaintenanceForStore( }; } const beforeStore = Object.fromEntries( - listSessionEntries({ storePath: params.storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ storePath: params.storePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), diff --git a/src/context-engine/delegate.ts b/src/context-engine/delegate.ts index d00955b32eff..9c0ad897cb0e 100644 --- a/src/context-engine/delegate.ts +++ b/src/context-engine/delegate.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { normalizeStructuredPromptSection } from "@openclaw/ai/internal/shared"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { parseSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; -import { listSessionEntries, loadSessionEntry } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore, loadSessionEntry } from "../config/sessions/session-accessor.js"; import { buildMemoryPromptSection, getActivePreparedMemoryPromptSection, @@ -74,7 +74,7 @@ function buildCompactionResultSessionTarget(params: { }) : undefined; const markerMatches = marker - ? listSessionEntries({ + ? listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId) diff --git a/src/cron/isolated-agent.session-identity.test.ts b/src/cron/isolated-agent.session-identity.test.ts index e96f9aedd28a..9d1d25c86dfe 100644 --- a/src/cron/isolated-agent.session-identity.test.ts +++ b/src/cron/isolated-agent.session-identity.test.ts @@ -18,7 +18,7 @@ import { DEFAULT_MESSAGE, makeDeps, mockEmbeddedOk, - readSessionEntry, + readCronSessionEntry, runCronTurn, withTempHome, } from "./isolated-agent.turn-test-helpers.js"; @@ -47,7 +47,7 @@ async function useRealCronSessionState(): Promise { ]); resolveCronSessionMock.mockImplementation(sessionRuntime.resolveCronSession); loadSessionEntryMock.mockImplementation(sessionRuntime.loadCronSessionEntryLatest); - patchSessionEntryMock.mockImplementation(sessionAccessor.patchSessionEntry); + patchSessionEntryMock.mockImplementation(sessionAccessor.patchSessionEntryCore); } function lastEmbeddedAgentCall(): { @@ -314,12 +314,12 @@ describe("runCronIsolatedAgentTurn session identity", () => { expect.objectContaining({ sessionId: "bound-session-rotated" }), ); - await expect(readSessionEntry(storePath, executionSessionKey)).resolves.toEqual( + await expect(readCronSessionEntry(storePath, executionSessionKey)).resolves.toEqual( expect.objectContaining({ sessionId: "bound-session-rotated", }), ); - await expect(readSessionEntry(storePath, boundSessionKey)).resolves.toEqual( + await expect(readCronSessionEntry(storePath, boundSessionKey)).resolves.toEqual( expect.objectContaining({ sessionId: "bound-session", }), @@ -408,7 +408,7 @@ describe("runCronIsolatedAgentTurn session identity", () => { message: "ping", storePath, }); - const entry = await readSessionEntry(storePath, "agent:main:cron:job-1"); + const entry = await readCronSessionEntry(storePath, "agent:main:cron:job-1"); expect(entry?.label).toBe("Nightly digest"); }); diff --git a/src/cron/isolated-agent.turn-test-helpers.ts b/src/cron/isolated-agent.turn-test-helpers.ts index f536469cb1f0..082844a476c8 100644 --- a/src/cron/isolated-agent.turn-test-helpers.ts +++ b/src/cron/isolated-agent.turn-test-helpers.ts @@ -44,7 +44,7 @@ export function mockEmbeddedOk() { mockEmbeddedTexts(["ok"]); } -export async function readSessionEntry(storePath: string, key: string) { +export async function readCronSessionEntry(storePath: string, key: string) { return loadSessionEntry({ storePath, sessionKey: key }); } diff --git a/src/cron/isolated-agent/run-prepare.ts b/src/cron/isolated-agent/run-prepare.ts index 9c7d932eb22e..a471096ba146 100644 --- a/src/cron/isolated-agent/run-prepare.ts +++ b/src/cron/isolated-agent/run-prepare.ts @@ -285,7 +285,7 @@ export async function prepareCronRunContext(params: { resetBoundaryReason?: "cron-stale"; update: (entry: SessionEntry | undefined) => SessionEntry; }) => { - const { applySessionEntryLifecycleMutation, patchSessionEntry } = + const { applySessionEntryLifecycleMutation, patchSessionEntryCore } = await loadSessionAccessorRuntime(); if (resetBoundaryReason) { await applySessionEntryLifecycleMutation({ @@ -304,7 +304,7 @@ export async function prepareCronRunContext(params: { return; } // Guarded replace reads the freshest row so lifecycle claims reject stale owners. - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey, agentId }, (_entry, context) => update(context.existingEntry), { fallbackEntry, replaceEntry: true }, diff --git a/src/cron/isolated-agent/run.test-harness.ts b/src/cron/isolated-agent/run.test-harness.ts index d2e99a5d93e2..e12866e99acd 100644 --- a/src/cron/isolated-agent/run.test-harness.ts +++ b/src/cron/isolated-agent/run.test-harness.ts @@ -381,7 +381,7 @@ vi.mock("../../config/sessions/session-accessor.js", async () => { return { ...actual, replaceSessionEntry: replaceSessionEntryMock, - patchSessionEntry: patchSessionEntryMock, + patchSessionEntryCore: patchSessionEntryMock, }; }); @@ -789,7 +789,7 @@ function resetRunSessionMocks(): void { } /** - * In-memory stand-in for the SQLite accessor `patchSessionEntry` used by the + * In-memory stand-in for the SQLite accessor `patchSessionEntryCore` used by the * cron persist path. Prod flips real session storage to per-agent SQLite, but * these orchestration tests must stay off disk. The store keys on * storePath+sessionKey so successive persists in one run observe the row the diff --git a/src/cron/isolated-agent/session.ts b/src/cron/isolated-agent/session.ts index 395943ccd0ca..ebf78d11aee4 100644 --- a/src/cron/isolated-agent/session.ts +++ b/src/cron/isolated-agent/session.ts @@ -15,7 +15,10 @@ import { resolveSessionResetPolicy, type SessionFreshness, } from "../../config/sessions/reset-policy.js"; -import { listSessionEntries, loadSessionEntry } from "../../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + loadSessionEntry, +} from "../../config/sessions/session-accessor.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -152,10 +155,9 @@ export function resolveCronSession(params: { const store = params.store ?? Object.fromEntries( - listSessionEntries({ agentId: params.agentId, storePath }).map(({ sessionKey, entry }) => [ - sessionKey, - entry, - ]), + listSessionEntriesCore({ agentId: params.agentId, storePath }).map( + ({ sessionKey, entry }) => [sessionKey, entry], + ), ); const sourceSessionKey = params.sourceSessionKey?.trim(); const sourceSessionDiffers = Boolean(sourceSessionKey && sourceSessionKey !== params.sessionKey); diff --git a/src/cron/run-diagnostics-normalize.ts b/src/cron/run-diagnostics-normalize.ts index 46dad2900382..4a2b2c80ce33 100644 --- a/src/cron/run-diagnostics-normalize.ts +++ b/src/cron/run-diagnostics-normalize.ts @@ -68,7 +68,7 @@ export function formatUnknownError(error: unknown): string { return String(error); } -export function normalizeToolName(value: unknown): string | undefined { +export function normalizeDiagnosticToolName(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; } @@ -121,7 +121,7 @@ export function normalizeCronRunDiagnosticSummary(value: string | undefined): st } /** Normalizes stored cron diagnostic payloads into bounded entries. */ -export function normalizeCronRunDiagnostics( +export function normalizeCronRunDiagnosticsCore( value: unknown, opts?: CronRunDiagnosticsNormalizeOptions, ): NormalizedCronRunDiagnostics | undefined { diff --git a/src/cron/run-diagnostics.ts b/src/cron/run-diagnostics.ts index 32a80aae5ad8..aa997a65cd9f 100644 --- a/src/cron/run-diagnostics.ts +++ b/src/cron/run-diagnostics.ts @@ -2,15 +2,15 @@ import { asOptionalObjectRecord } from "@openclaw/normalization-core/record-coerce"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { isToolAllowedByPolicyName } from "../agents/tool-policy-match.js"; -import { normalizeToolName as normalizePolicyToolName } from "../agents/tool-policy.js"; +import { normalizeToolPolicyName as normalizePolicyToolName } from "../agents/tool-policy.js"; import { getReplyPayloadMetadata } from "../auto-reply/reply-payload.js"; import { redactSensitiveText } from "../logging/redact.js"; import { formatUnknownError, normalizeCronRunDiagnosticSummary, - normalizeCronRunDiagnostics as normalizeCronRunDiagnosticsValue, + normalizeCronRunDiagnosticsCore as normalizeCronRunDiagnosticsValue, normalizeExitCode, - normalizeToolName, + normalizeDiagnosticToolName, tailText, } from "./run-diagnostics-normalize.js"; import type { @@ -208,7 +208,8 @@ function createCronRunDiagnosticsFromToolPayload( if (!record) { return undefined; } - const toolName = normalizeToolName(record.toolName) ?? normalizeToolName(record.name); + const toolName = + normalizeDiagnosticToolName(record.toolName) ?? normalizeDiagnosticToolName(record.name); const detailsDiagnostics = createCronRunDiagnosticsFromExecDetails(record.details, { nowMs: opts?.nowMs, toolName, diff --git a/src/cron/service.session-reaper-in-finally.test.ts b/src/cron/service.session-reaper-in-finally.test.ts index d8f50729613a..79f6d579bf3f 100644 --- a/src/cron/service.session-reaper-in-finally.test.ts +++ b/src/cron/service.session-reaper-in-finally.test.ts @@ -2,7 +2,10 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { listSessionEntries, replaceSessionEntry } from "../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + replaceSessionEntry, +} from "../config/sessions/session-accessor.js"; import { createNoopLogger, createCronStoreHarness, @@ -228,10 +231,12 @@ describe("CronService - session reaper runs in finally block (#31946)", () => { await onTimer(state); expect([...new Set(resolvedAgentIds)].toSorted()).toEqual(["main", "worker"]); - expect(listSessionEntries({ agentId: "main", storePath: sharedStorePath })).toStrictEqual([]); - expect(listSessionEntries({ agentId: "worker", storePath: sharedStorePath })).toStrictEqual( + expect(listSessionEntriesCore({ agentId: "main", storePath: sharedStorePath })).toStrictEqual( [], ); + expect( + listSessionEntriesCore({ agentId: "worker", storePath: sharedStorePath }), + ).toStrictEqual([]); expect(state.running).toBe(false); }); }); @@ -273,7 +278,9 @@ describe("CronService - session reaper runs in finally block (#31946)", () => { await expect(onTimer(state)).resolves.toBeUndefined(); expect([...new Set(resolvedAgentIds)]).toEqual(["ops"]); - expect(listSessionEntries({ agentId: "ops", storePath: sessionStorePath })).toStrictEqual([]); + expect(listSessionEntriesCore({ agentId: "ops", storePath: sessionStorePath })).toStrictEqual( + [], + ); }); }); @@ -311,12 +318,12 @@ describe("CronService - session reaper runs in finally block (#31946)", () => { await withCronServiceStateForTest(state, async () => { await onTimer(state); - expect(listSessionEntries({ agentId: "main", storePath: sessionStorePath })).toStrictEqual( - [], - ); - expect(listSessionEntries({ agentId: "worker", storePath: sessionStorePath })).toStrictEqual( - [], - ); + expect( + listSessionEntriesCore({ agentId: "main", storePath: sessionStorePath }), + ).toStrictEqual([]); + expect( + listSessionEntriesCore({ agentId: "worker", storePath: sessionStorePath }), + ).toStrictEqual([]); }); }); @@ -350,7 +357,9 @@ describe("CronService - session reaper runs in finally block (#31946)", () => { await withCronServiceStateForTest(state, async () => { await onTimer(state); - expect(listSessionEntries({ agentId: "retired", storePath: sessionStorePath })).toEqual([]); + expect(listSessionEntriesCore({ agentId: "retired", storePath: sessionStorePath })).toEqual( + [], + ); }); }); @@ -389,7 +398,7 @@ describe("CronService - session reaper runs in finally block (#31946)", () => { await expect(onTimer(state)).resolves.toBeUndefined(); expect( - listSessionEntries({ agentId: "agent-default", storePath: sessionStorePath }), + listSessionEntriesCore({ agentId: "agent-default", storePath: sessionStorePath }), ).toStrictEqual([]); expect(state.running).toBe(false); }); diff --git a/src/cron/session-reaper.test.ts b/src/cron/session-reaper.test.ts index d1e2eead04f7..1b5db5c5a1ed 100644 --- a/src/cron/session-reaper.test.ts +++ b/src/cron/session-reaper.test.ts @@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createDeferred } from "../../test/helpers/promise.js"; import { cleanupTempDirs, makeTempDir } from "../../test/helpers/temp-dir.js"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js"; -import { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; import * as sessionAccessor from "../config/sessions/session-accessor.js"; import { listKnownSessionStoreAgentIds, @@ -26,7 +26,7 @@ import type { Logger } from "./service/state.js"; import { sweepCronRunSessions as sweepCronRunSessionsImpl } from "./session-reaper.js"; import { resetReaperThrottle } from "./session-reaper.test-support.js"; -const { listSessionEntries, patchSessionEntry, replaceSessionEntry } = sessionAccessor; +const { listSessionEntriesCore, patchSessionEntryCore, replaceSessionEntry } = sessionAccessor; const taskStatusMocks = vi.hoisted(() => ({ buildPendingSet: vi.fn<() => Set>(() => new Set()), @@ -62,7 +62,7 @@ async function seedSessionEntries( function readSessionEntries(storePath: string): Record { return Object.fromEntries( - listSessionEntries({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), @@ -266,7 +266,7 @@ describe("sweepCronRunSessions", () => { expect(resolveExistingAgentSessionStoreTargetsSync(cfg, "ops")).toEqual([ { agentId: "ops", storePath: exactStorePath }, ]); - expect(Object.keys(loadCombinedSessionStoreForGateway(cfg).store).toSorted()).toEqual([ + expect(Object.keys(loadCombinedSessionStoreForGatewayCore(cfg).store).toSorted()).toEqual([ mainKey, opsKey, ]); @@ -444,7 +444,7 @@ describe("sweepCronRunSessions", () => { const writerStarted = createDeferred(); const releaseWriter = createDeferred(); const firstValidation = createDeferred(); - const writer = patchSessionEntry({ storePath, sessionKey }, async () => { + const writer = patchSessionEntryCore({ storePath, sessionKey }, async () => { writerStarted.resolve(); await releaseWriter.promise; return {}; @@ -687,7 +687,7 @@ describe("sweepCronRunSessions", () => { const eacces = Object.assign(new Error("EACCES: permission denied, open 'sessions.json'"), { code: "EACCES", }); - const listSpy = vi.spyOn(sessionAccessor, "listSessionEntries").mockImplementation(() => { + const listSpy = vi.spyOn(sessionAccessor, "listSessionEntriesCore").mockImplementation(() => { throw eacces; }); diff --git a/src/cron/session-reaper.ts b/src/cron/session-reaper.ts index 70b2a94ee747..2a58741e585f 100644 --- a/src/cron/session-reaper.ts +++ b/src/cron/session-reaper.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { parseDurationMs } from "../cli/parse-duration.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, loadExactSessionEntryReadOnly, type SessionEntryLifecycleRemoval, } from "../config/sessions/session-accessor.js"; @@ -129,7 +129,7 @@ export async function sweepCronRunSessions(params: { const removals: SessionEntryLifecycleRemoval[] = []; // The accessor keeps agentId logical for admission checks and resolves a shared // store's physical database owner internally through its SQLite scope. - for (const { sessionKey, entry } of listSessionEntries({ + for (const { sessionKey, entry } of listSessionEntriesCore({ agentId: params.agentId, storePath, })) { diff --git a/src/cron/task-run-detail.ts b/src/cron/task-run-detail.ts index c15ab063e8c0..fcd153c2e22f 100644 --- a/src/cron/task-run-detail.ts +++ b/src/cron/task-run-detail.ts @@ -11,7 +11,7 @@ import { type FailoverReason, } from "../../packages/gateway-protocol/src/failover-reasons.js"; import { isCronTimeoutErrorText } from "./execution-error-constants.js"; -import { normalizeCronRunDiagnostics } from "./run-diagnostics-normalize.js"; +import { normalizeCronRunDiagnosticsCore } from "./run-diagnostics-normalize.js"; type JsonValue = import("../tasks/task-registry.types.js").JsonValue; type TaskRecord = import("../tasks/task-registry.types.js").TaskRecord; @@ -105,7 +105,7 @@ export function parseCronRunLogEntryObject( errorReason: normalizeCronRunLogErrorReason(entryObj.errorReason) ?? undefined, summary: typeof entryObj.summary === "string" ? entryObj.summary : undefined, runId: typeof entryObj.runId === "string" && entryObj.runId.trim() ? entryObj.runId : undefined, - diagnostics: normalizeCronRunDiagnostics(entryObj.diagnostics), + diagnostics: normalizeCronRunDiagnosticsCore(entryObj.diagnostics), runAtMs: normalizeTimestamp(entryObj.runAtMs), durationMs: asSafeIntegerInRange(entryObj.durationMs, { min: 0 }), nextRunAtMs: normalizeTimestamp(entryObj.nextRunAtMs), diff --git a/src/flows/doctor-core-checks.runtime.ts b/src/flows/doctor-core-checks.runtime.ts index 7cb3bf38d646..1121dafe65d0 100644 --- a/src/flows/doctor-core-checks.runtime.ts +++ b/src/flows/doctor-core-checks.runtime.ts @@ -23,7 +23,7 @@ import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { supportsModelTools } from "../agents/model-tool-support.js"; import { loadPreparedModelCatalog } from "../agents/prepared-model-catalog.js"; import { normalizeAgentRuntimeTools } from "../agents/runtime-plan/tools.js"; -import { collectExplicitAllowlist, normalizeToolName } from "../agents/tool-policy.js"; +import { collectExplicitAllowlist, normalizeToolPolicyName } from "../agents/tool-policy.js"; import { inspectRuntimeToolInputSchemas, type RuntimeToolSchemaDiagnostic, @@ -979,8 +979,8 @@ function synthesizeBundleMcpAllowlistSentinelName(params: { safeServerName: string; allowlistEntry: string; }): string | undefined { - const normalized = normalizeToolName(params.allowlistEntry); - const serverPrefix = normalizeToolName(`${params.safeServerName}${TOOL_NAME_SEPARATOR}`); + const normalized = normalizeToolPolicyName(params.allowlistEntry); + const serverPrefix = normalizeToolPolicyName(`${params.safeServerName}${TOOL_NAME_SEPARATOR}`); if (normalized.startsWith(serverPrefix)) { return normalized; } diff --git a/src/gateway/boot.test.ts b/src/gateway/boot.test.ts index cb94a539f027..d7f5ffd9c415 100644 --- a/src/gateway/boot.test.ts +++ b/src/gateway/boot.test.ts @@ -21,7 +21,7 @@ const { resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, resolveMainSes const { resolveStorePath } = await import("../config/sessions/paths.js"); const { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceSessionEntry, } = await import("../config/sessions/session-accessor.js"); @@ -55,7 +55,9 @@ describe("runBootOnce", () => { vi.clearAllMocks(); const { storePath } = resolveMainStore(); await fs.rm(storePath, { force: true }); - const removals = listSessionEntries({ storePath }).map(({ sessionKey }) => ({ sessionKey })); + const removals = listSessionEntriesCore({ storePath }).map(({ sessionKey }) => ({ + sessionKey, + })); await applySessionEntryLifecycleMutation({ storePath, removals, skipMaintenance: true }); }); diff --git a/src/gateway/cli-session-history.test.ts b/src/gateway/cli-session-history.test.ts index 195a3ecf6d9a..de11e9531527 100644 --- a/src/gateway/cli-session-history.test.ts +++ b/src/gateway/cli-session-history.test.ts @@ -15,7 +15,7 @@ import { resolveChatHistoryWithCliSessionImports, } from "./cli-session-history.js"; import { mergeImportedChatHistoryMessages } from "./cli-session-history.merge.js"; -import { expectRecordFields, requireRecord } from "./test-helpers.assertions.js"; +import { expectRecordFields, requireGatewayRecord } from "./test-helpers.assertions.js"; type ClaudeCliFallbackSeed = NonNullable>; type AugmentCliHistoryParams = Parameters[0]; @@ -35,7 +35,7 @@ function expectFields(value: unknown, expected: Record): void { } function readRecord(value: unknown): Record { - return requireRecord(value, "record"); + return requireGatewayRecord(value, "record"); } function expectCliSessionMarker(message: unknown, sessionId: string): void { diff --git a/src/gateway/mcp-http.runtime.ts b/src/gateway/mcp-http.runtime.ts index 4a21adf14ea0..cfab9e7b33bb 100644 --- a/src/gateway/mcp-http.runtime.ts +++ b/src/gateway/mcp-http.runtime.ts @@ -2,7 +2,7 @@ import type { AuthProfileStore } from "../agents/auth-profiles/types.js"; // MCP loopback runtime scope cache. // Resolves Gateway-visible tools for MCP clients with short-lived schema caching. import { applyEmbeddedAttemptToolsAllow } from "../agents/embedded-agent-runner/run/attempt-tool-construction-plan.js"; -import { normalizeToolName } from "../agents/tool-policy.js"; +import { normalizeToolPolicyName } from "../agents/tool-policy.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { DirectoryCache } from "../infra/outbound/directory-cache.js"; import { getPluginToolMeta } from "../plugins/tools.js"; @@ -51,13 +51,13 @@ function resolveMediatedNativeTools( if (mode === "exact") { return new Set( (toolsAllow ?? []) - .map((name) => normalizeToolName(name)) + .map((name) => normalizeToolPolicyName(name)) .filter((name) => NATIVE_TOOL_EXCLUDE.has(name)), ); } if ( toolsAllow === undefined || - toolsAllow.some((toolName) => normalizeToolName(toolName) === "*") + toolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*") ) { return new Set(); } @@ -143,10 +143,10 @@ function applyGrantToolsAllow( if (!toolsAllow) { return tools; } - const allowed = new Set(toolsAllow.map((name) => normalizeToolName(name)).filter(Boolean)); + const allowed = new Set(toolsAllow.map((name) => normalizeToolPolicyName(name)).filter(Boolean)); return tools.filter((tool) => { const name = readMcpLoopbackToolName(tool); - return name !== undefined && allowed.has(normalizeToolName(name)); + return name !== undefined && allowed.has(normalizeToolPolicyName(name)); }); } diff --git a/src/gateway/server-cron.test.ts b/src/gateway/server-cron.test.ts index 2fd1f51a076d..712dbab0766d 100644 --- a/src/gateway/server-cron.test.ts +++ b/src/gateway/server-cron.test.ts @@ -148,7 +148,7 @@ vi.mock("../infra/restart-coordinator.js", async () => { ); return { ...actual, - requestSafeGatewayRestart: requestSafeGatewayRestartMock, + scheduleSafeGatewayRestart: requestSafeGatewayRestartMock, }; }); diff --git a/src/gateway/server-methods.authorization.test.ts b/src/gateway/server-methods.authorization.test.ts index 2181fd6cb2ad..e7993e05a82a 100644 --- a/src/gateway/server-methods.authorization.test.ts +++ b/src/gateway/server-methods.authorization.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { loadSessionEntry, - patchSessionEntry, + patchSessionEntryCore, upsertSessionEntry, } from "../config/sessions/session-accessor.js"; import { applySessionEntryCanonicalReplacements } from "../config/sessions/session-accessor.sqlite-replacement-projection.js"; @@ -332,7 +332,7 @@ describe("gateway method authorization", () => { createdActor: { type: "human", id: "owner" }, }, ); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ visibility: "draft", })); continueHandler(); diff --git a/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts b/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts index 061064a120c0..a8e517f9da73 100644 --- a/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts +++ b/src/gateway/server-methods/__mocks__/tools-effective.runtime.ts @@ -55,7 +55,6 @@ export const resolveEffectiveToolInventory = toolsEffectiveGlobalAgentRuntimeMocks.resolveEffectiveToolInventory; export const resolveEffectiveToolInventoryRuntimeModelContextAsync = toolsEffectiveGlobalAgentRuntimeMocks.resolveEffectiveToolInventoryRuntimeModelContextAsync; -export const peekSessionMcpRuntime = vi.fn(() => undefined); export const resolveSessionMcpConfigSummary = vi.fn(() => ({ fingerprint: "mcp:0", serverNames: [] as string[], @@ -67,4 +66,5 @@ export const toolsEffectiveRuntimeMockModule = { ), getActivePluginRegistryVersion: vi.fn(() => 1), getActivePluginChannelRegistryVersion: vi.fn(() => 1), + peekSessionMcpRuntime: vi.fn(() => undefined), }; diff --git a/src/gateway/server-methods/channels.status.test.ts b/src/gateway/server-methods/channels.status.test.ts index b6b115618088..0f4f7caeac74 100644 --- a/src/gateway/server-methods/channels.status.test.ts +++ b/src/gateway/server-methods/channels.status.test.ts @@ -4,7 +4,7 @@ import { expectDefined } from "@openclaw/normalization-core"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { requireRecord } from "../test-helpers.assertions.js"; +import { requireGatewayRecord } from "../test-helpers.assertions.js"; import type { GatewayRequestHandlerOptions } from "./types.js"; type ChannelTestPlugin = { @@ -135,9 +135,11 @@ function channelAccounts( payload: Record, channel: string, ): Record[] { - const accounts = requireRecord(payload.channelAccounts, "channel accounts")[channel] as unknown[]; + const accounts = requireGatewayRecord(payload.channelAccounts, "channel accounts")[ + channel + ] as unknown[]; expect(Array.isArray(accounts)).toBe(true); - return accounts.map((account) => requireRecord(account, "channel account")); + return accounts.map((account) => requireGatewayRecord(account, "channel account")); } function firstChannelAccount( @@ -165,7 +167,7 @@ function requireRespondPayload(respond: ReturnType): Record { @@ -200,14 +202,14 @@ describe("channelsHandlers channels.status", () => { expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({ config: {}, }); - const snapshotArgs = requireRecord( + const snapshotArgs = requireGatewayRecord( requireFirstCallArg(mocks.resolveChannelAccountSnapshot), "snapshot args", ); expect(snapshotArgs.cfg).toBe(autoEnabledConfig); expect(snapshotArgs.accountId).toBe("default"); - const channels = requireRecord(payload.channels, "channels payload"); - const whatsapp = requireRecord(channels.whatsapp, "whatsapp channel"); + const channels = requireGatewayRecord(payload.channels, "channels payload"); + const whatsapp = requireGatewayRecord(channels.whatsapp, "whatsapp channel"); expect(whatsapp.configured).toBe(true); }); @@ -228,8 +230,8 @@ describe("channelsHandlers channels.status", () => { ]); const payload = await runChannelsStatus({ probe: false, timeoutMs: 2000 }); - const channels = requireRecord(payload.channels, "channels payload"); - const whatsapp = requireRecord(channels.whatsapp, "whatsapp channel"); + const channels = requireGatewayRecord(payload.channels, "channels payload"); + const whatsapp = requireGatewayRecord(channels.whatsapp, "whatsapp channel"); expect(whatsapp.baseUrl).toBe("https://chat.example.test/?token=***"); }); @@ -244,7 +246,7 @@ describe("channelsHandlers channels.status", () => { 'channelsHandlers["channels.status"] test invariant', )(createOptions({ probe: true, timeoutMs: 999_999 })); - const probeArgs = requireRecord(requireFirstCallArg(probeAccount), "probe args"); + const probeArgs = requireGatewayRecord(requireFirstCallArg(probeAccount), "probe args"); expect(probeArgs.timeoutMs).toBe(30_000); expect(probeArgs.cfg).toBe(autoEnabledConfig); }); @@ -281,7 +283,7 @@ describe("channelsHandlers channels.status", () => { expect(Date.now() - startedAt).toBe(1000); expect(payload.channelOrder).toEqual(["zeta", "alpha"]); - expect(Object.keys(requireRecord(payload.channels, "channels payload"))).toEqual([ + expect(Object.keys(requireGatewayRecord(payload.channels, "channels payload"))).toEqual([ "alpha", "zeta", ]); @@ -350,7 +352,7 @@ describe("channelsHandlers channels.status", () => { expect(account.accountId).toBe("default"); expect(String(account.lastError)).toContain("probe failed"); expect(typeof account.lastProbeAt).toBe("number"); - const accountProbe = requireRecord(account.probe, "account probe"); + const accountProbe = requireGatewayRecord(account.probe, "account probe"); expect(accountProbe.ok).toBe(false); expect(String(accountProbe.error)).toContain("probe failed"); }); @@ -382,14 +384,15 @@ describe("channelsHandlers channels.status", () => { const payload = requireRespondPayload(respond); expect( - requireRecord(firstChannelAccount(payload, "hanging").probe, "hanging probe").timedOut, + requireGatewayRecord(firstChannelAccount(payload, "hanging").probe, "hanging probe") + .timedOut, ).toBe(true); - expect(requireRecord(firstChannelAccount(payload, "healthy").probe, "healthy probe")).toEqual( - { - ok: true, - identity: "healthy", - }, - ); + expect( + requireGatewayRecord(firstChannelAccount(payload, "healthy").probe, "healthy probe"), + ).toEqual({ + ok: true, + identity: "healthy", + }); expect(payload.partial).toBe(true); expect(payload.warnings).toEqual(["hanging:default probe timed out after 1000ms"]); } finally { @@ -413,8 +416,8 @@ describe("channelsHandlers channels.status", () => { ]); const payload = await runChannelsStatus({ probe: false, timeoutMs: 1000 }); - const channels = requireRecord(payload.channels, "channels payload"); - const whatsapp = requireRecord(channels.whatsapp, "whatsapp channel"); + const channels = requireGatewayRecord(payload.channels, "channels payload"); + const whatsapp = requireGatewayRecord(channels.whatsapp, "whatsapp channel"); expect(whatsapp.configured).toBe(true); expect(String(whatsapp.lastError)).toContain("summary failed"); diff --git a/src/gateway/server-methods/chat-restart-recovery.ts b/src/gateway/server-methods/chat-restart-recovery.ts index 11729d16ef36..a9a50a26c231 100644 --- a/src/gateway/server-methods/chat-restart-recovery.ts +++ b/src/gateway/server-methods/chat-restart-recovery.ts @@ -14,7 +14,7 @@ import { hasRestartRecoveryTerminalRun, } from "../../config/sessions/restart-recovery-state.js"; import { - patchSessionEntry, + patchSessionEntryCore, type SessionTranscriptTurnExpectedState, type SessionTranscriptTurnLifecyclePatch, } from "../../config/sessions/session-accessor.js"; @@ -389,7 +389,7 @@ export async function terminalizeRestartSafeChatAdmission(params: { }): Promise { const endedAt = Date.now(); let terminalized = false; - await patchSessionEntry( + await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (current) => { if ( diff --git a/src/gateway/server-methods/chat-transcript-persistence.ts b/src/gateway/server-methods/chat-transcript-persistence.ts index c6622129bdd6..0f6a3c03b8fc 100644 --- a/src/gateway/server-methods/chat-transcript-persistence.ts +++ b/src/gateway/server-methods/chat-transcript-persistence.ts @@ -4,7 +4,7 @@ import { getReplyPayloadMetadata } from "../../auto-reply/reply-payload.js"; import { findTranscriptEvent, loadTranscriptEventRowsAfterSeqSync, - patchSessionEntry, + patchSessionEntryCore, publishTranscriptUpdate, readSessionTranscriptWatermark, rewriteTranscriptEventRowsExact, @@ -305,7 +305,7 @@ async function touchAssistantTranscriptSessionEntry( return; } const transcriptMarkerUpdatedAt = Date.now(); - await patchSessionEntry( + await patchSessionEntryCore( { storePath: scope.storePath, sessionKey: scope.sessionKey, diff --git a/src/gateway/server-methods/nodes.invoke-wake.test.ts b/src/gateway/server-methods/nodes.invoke-wake.test.ts index 16eba567584c..3ab159111578 100644 --- a/src/gateway/server-methods/nodes.invoke-wake.test.ts +++ b/src/gateway/server-methods/nodes.invoke-wake.test.ts @@ -16,7 +16,7 @@ import { getNodeWakeStateSnapshot, resetNodeWakeStateForTest, } from "../node-wake-state.test-support.js"; -import { expectRecordFields, requireRecord } from "../test-helpers.assertions.js"; +import { expectRecordFields, requireGatewayRecord } from "../test-helpers.assertions.js"; import { maybeSendNodeWakeNudge, maybeWakeNodeWithApns, @@ -201,7 +201,7 @@ function isUuidV4(value: string): boolean { function requireRespondPayload(call: RespondCall | undefined, label: string) { expect(call?.[0], `${label} success`).toBe(true); - return requireRecord(call?.[1], `${label} payload`); + return requireGatewayRecord(call?.[1], `${label} payload`); } function expectQueuedAction( @@ -583,10 +583,13 @@ describe("plugin surface refresh", () => { const call = firstRespondCall(respond); expect(call[0]).toBe(true); expect(call[2]).toBeUndefined(); - const payload = requireRecord(call[1], "refresh payload"); + const payload = requireGatewayRecord(call[1], "refresh payload"); expect(payload.surface).toBe("canvas"); expect(payload.expiresAtMs).toBe(1_100); - const pluginSurfaceUrls = requireRecord(payload.pluginSurfaceUrls, "refresh surface urls"); + const pluginSurfaceUrls = requireGatewayRecord( + payload.pluginSurfaceUrls, + "refresh surface urls", + ); const canvasUrl = requireString(pluginSurfaceUrls.canvas, "refresh canvas url"); const parsedCanvasUrl = new URL(canvasUrl); expect(parsedCanvasUrl.origin).toBe("http://127.0.0.1:18789"); @@ -629,8 +632,11 @@ describe("plugin surface refresh", () => { const call = firstRespondCall(respond); expect(call[0]).toBe(true); - const payload = requireRecord(call[1], "operator refresh payload"); - const pluginSurfaceUrls = requireRecord(payload.pluginSurfaceUrls, "operator surface urls"); + const payload = requireGatewayRecord(call[1], "operator refresh payload"); + const pluginSurfaceUrls = requireGatewayRecord( + payload.pluginSurfaceUrls, + "operator surface urls", + ); const canvasUrl = requireString(pluginSurfaceUrls.canvas, "operator canvas url"); expect(canvasUrl).not.toContain("old-token"); expect(client.pluginSurfaceUrls.canvas).toBe(canvasUrl); @@ -718,9 +724,12 @@ describe("plugin surface refresh", () => { const call = firstRespondCall(respond); expect(call[0]).toBe(true); - const payload = requireRecord(call[1], "refresh payload"); + const payload = requireGatewayRecord(call[1], "refresh payload"); expect(payload.expiresAtMs).toBe(1_100); - const pluginSurfaceUrls = requireRecord(payload.pluginSurfaceUrls, "refresh surface urls"); + const pluginSurfaceUrls = requireGatewayRecord( + payload.pluginSurfaceUrls, + "refresh surface urls", + ); const canvasUrl = requireString(pluginSurfaceUrls.canvas, "refresh canvas url"); expect(canvasUrl).not.toBe(currentUrl); expect(client.pluginSurfaceUrls.canvas).toBe(canvasUrl); @@ -1888,7 +1897,7 @@ describe("node.invoke APNs wake path", () => { await maybeWakeNodeWithApns(nodeId, { lifecycle, generation }); - const transport = requireRecord( + const transport = requireGatewayRecord( mockArg(mocks.sendApnsBackgroundWake, 0, 0), "guarded APNs transport", ); @@ -2114,7 +2123,10 @@ describe("node.invoke APNs wake path", () => { requestParams: { nodeId, idempotencyKey: "idem-revoked-during-dispatch" }, }); await vi.waitFor(() => expect(nodeRegistry.invoke).toHaveBeenCalledTimes(1)); - const signal = requireRecord(mockArg(nodeRegistry.invoke, 0, 0), "node invoke payload").signal; + const signal = requireGatewayRecord( + mockArg(nodeRegistry.invoke, 0, 0), + "node invoke payload", + ).signal; if (!(signal instanceof AbortSignal)) { throw new Error("expected dispatched node work to receive an abort signal"); } @@ -2220,7 +2232,7 @@ describe("node.invoke APNs wake path", () => { expect(mocks.sendApnsBackgroundWake).toHaveBeenCalledTimes(1); const call = firstRespondCall(respond); - const details = requireRecord(call[2]?.details, "queued foreground details"); + const details = requireGatewayRecord(call[2]?.details, "queued foreground details"); expectRecordFields(details.wake, "queued foreground wake", { path: "sent", available: true, diff --git a/src/gateway/server-methods/restart.test.ts b/src/gateway/server-methods/restart.test.ts index 7fe765e5e44a..098368dac288 100644 --- a/src/gateway/server-methods/restart.test.ts +++ b/src/gateway/server-methods/restart.test.ts @@ -6,14 +6,14 @@ import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coerci import { beforeEach, describe, expect, it, vi } from "vitest"; import { restartHandlers } from "./restart.js"; -const requestSafeGatewayRestart = vi.hoisted(() => vi.fn()); +const scheduleSafeGatewayRestart = vi.hoisted(() => vi.fn()); const createSafeGatewayRestartPreflight = vi.hoisted(() => vi.fn()); const requestGatewayRestartWithSignalAdmission = vi.hoisted(() => vi.fn()); const readActiveGatewayLockIdentity = vi.hoisted(() => vi.fn()); vi.mock("../../infra/restart-coordinator.js", () => ({ createSafeGatewayRestartPreflight: () => createSafeGatewayRestartPreflight(), - requestSafeGatewayRestart: (opts: unknown) => requestSafeGatewayRestart(opts), + scheduleSafeGatewayRestart: (opts: unknown) => scheduleSafeGatewayRestart(opts), })); vi.mock("../../infra/restart.js", () => ({ @@ -55,7 +55,7 @@ function invokeRestartPreflight() { } function mockScheduledRestart(preflight: { safe: boolean; summary: string }) { - requestSafeGatewayRestart.mockReturnValueOnce({ + scheduleSafeGatewayRestart.mockReturnValueOnce({ ok: true, status: "scheduled", preflight: { ...preflight, counts: {}, blockers: [] }, @@ -72,7 +72,7 @@ function mockScheduledRestart(preflight: { safe: boolean; summary: string }) { } function expectRestartRequest(skipDeferral: boolean) { - expect(requestSafeGatewayRestart).toHaveBeenCalledWith({ + expect(scheduleSafeGatewayRestart).toHaveBeenCalledWith({ reason: "operator", delayMs: 0, skipDeferral, @@ -81,7 +81,7 @@ function expectRestartRequest(skipDeferral: boolean) { describe("gateway restart handlers", () => { beforeEach(() => { - requestSafeGatewayRestart.mockClear(); + scheduleSafeGatewayRestart.mockClear(); createSafeGatewayRestartPreflight.mockReset(); requestGatewayRestartWithSignalAdmission.mockReset(); requestGatewayRestartWithSignalAdmission.mockReturnValue({ status: "emitted" }); @@ -115,7 +115,7 @@ describe("gateway restart handlers", () => { const respond = await invokeRestartPreflight(); expect(respond).toHaveBeenCalledWith(true, preflight); - expect(requestSafeGatewayRestart).not.toHaveBeenCalled(); + expect(scheduleSafeGatewayRestart).not.toHaveBeenCalled(); expect(requestGatewayRestartWithSignalAdmission).not.toHaveBeenCalled(); }); @@ -162,7 +162,7 @@ describe("gateway restart handlers", () => { restartIntent: { waitMs: 30_000 }, }); - expect(requestSafeGatewayRestart).not.toHaveBeenCalled(); + expect(scheduleSafeGatewayRestart).not.toHaveBeenCalled(); expect(requestGatewayRestartWithSignalAdmission).toHaveBeenCalledWith("operator", { reason: "operator", waitMs: 30_000, @@ -264,7 +264,7 @@ describe("gateway restart handlers", () => { await invokeRestartRequest({ reason: "x".repeat(199) + "🧠tail" }); - expect(requestSafeGatewayRestart).toHaveBeenCalledWith({ + expect(scheduleSafeGatewayRestart).toHaveBeenCalledWith({ reason: "x".repeat(199), delayMs: 0, skipDeferral: false, @@ -274,7 +274,7 @@ describe("gateway restart handlers", () => { it("rejects non-object params without scheduling a restart", async () => { const respond = await invokeRestartRequest("operator"); - expect(requestSafeGatewayRestart).not.toHaveBeenCalled(); + expect(scheduleSafeGatewayRestart).not.toHaveBeenCalled(); expect(respond.mock.calls).toEqual([ [ false, @@ -290,7 +290,7 @@ describe("gateway restart handlers", () => { it("rejects array params without scheduling a restart", async () => { const respond = await invokeRestartRequest([]); - expect(requestSafeGatewayRestart).not.toHaveBeenCalled(); + expect(scheduleSafeGatewayRestart).not.toHaveBeenCalled(); expect(respond.mock.calls).toEqual([ [ false, diff --git a/src/gateway/server-methods/restart.ts b/src/gateway/server-methods/restart.ts index 089f71d51f36..9eddf9d022cc 100644 --- a/src/gateway/server-methods/restart.ts +++ b/src/gateway/server-methods/restart.ts @@ -5,7 +5,7 @@ import { ErrorCodes, errorShape } from "../../../packages/gateway-protocol/src/i import { readActiveGatewayLockIdentity } from "../../infra/gateway-lock.js"; import { createSafeGatewayRestartPreflight, - requestSafeGatewayRestart, + scheduleSafeGatewayRestart, } from "../../infra/restart-coordinator.js"; import type { GatewayRestartIntent } from "../../infra/restart-intent.js"; import { requestGatewayRestartWithSignalAdmission } from "../../infra/restart.js"; @@ -155,7 +155,7 @@ export const restartHandlers: GatewayRequestHandlers = { }); return; } - const result = requestSafeGatewayRestart({ + const result = scheduleSafeGatewayRestart({ reason, delayMs: 0, skipDeferral: normalizeSkipDeferral(params.skipDeferral), diff --git a/src/gateway/server-methods/sessions-diff.test.ts b/src/gateway/server-methods/sessions-diff.test.ts index eebdd5f94642..03de1d39cd7b 100644 --- a/src/gateway/server-methods/sessions-diff.test.ts +++ b/src/gateway/server-methods/sessions-diff.test.ts @@ -18,7 +18,7 @@ import { const hoisted = vi.hoisted(() => ({ loadSessionEntry: vi.fn(), - patchSessionEntry: vi.fn(), + patchSessionEntryCore: vi.fn(), resolveAgentWorkspaceDir: vi.fn(), resolveDefaultAgentId: vi.fn(), })); @@ -34,7 +34,7 @@ vi.mock("../../agents/agent-scope.js", () => ({ })); vi.mock("../../config/sessions/session-accessor.js", () => ({ - patchSessionEntry: hoisted.patchSessionEntry, + patchSessionEntryCore: hoisted.patchSessionEntryCore, })); function git(cwd: string, ...args: string[]): string { @@ -396,6 +396,6 @@ describe("ensureSessionDiffBaseline", () => { }); expect(result).toBe(entry); - expect(hoisted.patchSessionEntry).not.toHaveBeenCalled(); + expect(hoisted.patchSessionEntryCore).not.toHaveBeenCalled(); }); }); diff --git a/src/gateway/server-methods/sessions-files.test.ts b/src/gateway/server-methods/sessions-files.test.ts index 28036acda5b5..dd609609bdd9 100644 --- a/src/gateway/server-methods/sessions-files.test.ts +++ b/src/gateway/server-methods/sessions-files.test.ts @@ -24,7 +24,7 @@ const hoisted = vi.hoisted(() => ({ loadSessionEntry: vi.fn(), resolveAgentWorkspaceDir: vi.fn(), resolveDefaultAgentId: vi.fn(), - readSessionTranscriptVisibleMessageDelta: vi.fn(), + readSessionTranscriptVisibleMessageDeltaCore: vi.fn(), })); vi.mock("./open-path.js", async () => { @@ -52,13 +52,14 @@ vi.mock("../session-transcript-readers.js", async () => { ); return { ...actual, - readSessionTranscriptVisibleMessageDelta: hoisted.readSessionTranscriptVisibleMessageDelta, + readSessionTranscriptVisibleMessageDeltaCore: + hoisted.readSessionTranscriptVisibleMessageDeltaCore, }; }); const invokeSessionFilesHandler = createSessionFilesHandlerInvoker(sessionsFilesHandlers); const mockVisibleMessages = createVisibleMessagesMock( - hoisted.readSessionTranscriptVisibleMessageDelta, + hoisted.readSessionTranscriptVisibleMessageDeltaCore, ); describe("sessions.files RPC handlers", () => { @@ -66,7 +67,7 @@ describe("sessions.files RPC handlers", () => { beforeEach(() => { vi.clearAllMocks(); - hoisted.readSessionTranscriptVisibleMessageDelta.mockReset(); + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockReset(); workspaceRoot = createWorkspaceFixture("openclaw-session-files-test-"); hoisted.resolveDefaultAgentId.mockReturnValue("main"); hoisted.resolveAgentWorkspaceDir.mockReturnValue(workspaceRoot); diff --git a/src/gateway/server-methods/sessions-files.touched-files.test.ts b/src/gateway/server-methods/sessions-files.touched-files.test.ts index a5892e36e6e3..cfe956a655cd 100644 --- a/src/gateway/server-methods/sessions-files.touched-files.test.ts +++ b/src/gateway/server-methods/sessions-files.touched-files.test.ts @@ -19,7 +19,7 @@ const hoisted = vi.hoisted(() => ({ loadSessionEntry: vi.fn(), resolveAgentWorkspaceDir: vi.fn(), resolveDefaultAgentId: vi.fn(), - readSessionTranscriptVisibleMessageDelta: vi.fn(), + readSessionTranscriptVisibleMessageDeltaCore: vi.fn(), })); vi.mock("../../agents/agent-scope.js", () => ({ @@ -42,13 +42,14 @@ vi.mock("../session-transcript-readers.js", async () => { ); return { ...actual, - readSessionTranscriptVisibleMessageDelta: hoisted.readSessionTranscriptVisibleMessageDelta, + readSessionTranscriptVisibleMessageDeltaCore: + hoisted.readSessionTranscriptVisibleMessageDeltaCore, }; }); const invokeSessionFilesHandler = createSessionFilesHandlerInvoker(sessionsFilesHandlers); const mockVisibleMessages = createVisibleMessagesMock( - hoisted.readSessionTranscriptVisibleMessageDelta, + hoisted.readSessionTranscriptVisibleMessageDeltaCore, ); describe("sessions.files touched-file folds", () => { @@ -56,7 +57,7 @@ describe("sessions.files touched-file folds", () => { beforeEach(() => { vi.clearAllMocks(); - hoisted.readSessionTranscriptVisibleMessageDelta.mockReset(); + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockReset(); workspaceRoot = createWorkspaceFixture("openclaw-session-touched-files-test-"); hoisted.resolveDefaultAgentId.mockReturnValue("main"); hoisted.resolveAgentWorkspaceDir.mockReturnValue(workspaceRoot); @@ -110,7 +111,7 @@ describe("sessions.files touched-file folds", () => { it("folds only appended SQLite messages after the cached cursor", async () => { useSqliteSession(hoisted.loadSessionEntry, workspaceRoot, "sess-touched-incremental"); - hoisted.readSessionTranscriptVisibleMessageDelta.mockImplementation((_scope, limits) => { + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockImplementation((_scope, limits) => { if (limits.cursor === undefined) { return { kind: "page", @@ -147,8 +148,8 @@ describe("sessions.files touched-file folds", () => { expect(second.files).toEqual([ expect.objectContaining({ path: "ui/chat.ts", kind: "modified" }), ]); - expect(hoisted.readSessionTranscriptVisibleMessageDelta).toHaveBeenCalledTimes(2); - expect(hoisted.readSessionTranscriptVisibleMessageDelta.mock.calls[1]?.[1]).toMatchObject({ + expect(hoisted.readSessionTranscriptVisibleMessageDeltaCore).toHaveBeenCalledTimes(2); + expect(hoisted.readSessionTranscriptVisibleMessageDeltaCore.mock.calls[1]?.[1]).toMatchObject({ cursor: "cursor-1", }); }); @@ -159,7 +160,7 @@ describe("sessions.files touched-file folds", () => { setImmediate(() => { otherWorkRan = true; }); - hoisted.readSessionTranscriptVisibleMessageDelta.mockImplementation((_scope, limits) => { + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockImplementation((_scope, limits) => { if (limits.cursor !== undefined) { expect(limits.cursor).toBe("singleflight-page-1"); expect(otherWorkRan).toBe(true); @@ -177,11 +178,11 @@ describe("sessions.files touched-file folds", () => { const first = invokeSessionFilesHandler("sessions.files.list", params); const second = invokeSessionFilesHandler("sessions.files.list", params); - expect(hoisted.readSessionTranscriptVisibleMessageDelta).toHaveBeenCalledTimes(1); + expect(hoisted.readSessionTranscriptVisibleMessageDeltaCore).toHaveBeenCalledTimes(1); for (const result of await Promise.all([first, second])) { expectOkPayload(result); } - expect(hoisted.readSessionTranscriptVisibleMessageDelta).toHaveBeenCalledTimes(2); + expect(hoisted.readSessionTranscriptVisibleMessageDeltaCore).toHaveBeenCalledTimes(2); }); it("lets a different session finish while a multi-page fold is yielded", async () => { @@ -196,7 +197,7 @@ describe("sessions.files touched-file folds", () => { entry: { sessionId, sessionFile: `sqlite:main:${sessionId}:${storePath}` }, }; }); - hoisted.readSessionTranscriptVisibleMessageDelta.mockImplementation((scope, limits) => { + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockImplementation((scope, limits) => { const isSlow = scope.sessionId === "sess-touched-slow"; return { kind: "page", @@ -226,7 +227,7 @@ describe("sessions.files touched-file folds", () => { const sessionId = "sess-touched-multi-store"; const firstStorePath = path.join(workspaceRoot, "store-a.sqlite"); const secondStorePath = path.join(workspaceRoot, "store-b.sqlite"); - hoisted.readSessionTranscriptVisibleMessageDelta.mockImplementation((scope, limits) => { + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockImplementation((scope, limits) => { expect(limits.cursor).toBeUndefined(); const message = scope.storePath === firstStorePath @@ -262,7 +263,7 @@ describe("sessions.files touched-file folds", () => { it("rebuilds the SQLite fold from the bootstrap cursor after a reset", async () => { useSqliteSession(hoisted.loadSessionEntry, workspaceRoot, "sess-touched-reset"); - hoisted.readSessionTranscriptVisibleMessageDelta.mockImplementation((_scope, limits) => { + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockImplementation((_scope, limits) => { if (limits.cursor === undefined) { return { kind: "page", @@ -308,7 +309,7 @@ describe("sessions.files touched-file folds", () => { expect(afterReset.files).toEqual([ expect.objectContaining({ path: "ui/chat.ts", kind: "modified" }), ]); - expect(hoisted.readSessionTranscriptVisibleMessageDelta).toHaveBeenCalledTimes(3); + expect(hoisted.readSessionTranscriptVisibleMessageDeltaCore).toHaveBeenCalledTimes(3); }); it("collects the expected files and kinds from the SQLite fold", async () => { @@ -321,7 +322,7 @@ describe("sessions.files touched-file folds", () => { }), ]; useSqliteSession(hoisted.loadSessionEntry, workspaceRoot, "sess-touched-parity"); - hoisted.readSessionTranscriptVisibleMessageDelta.mockReturnValue({ + hoisted.readSessionTranscriptVisibleMessageDeltaCore.mockReturnValue({ kind: "page", cursor: "parity-final", events: messages.map(visibleMessageEvent), diff --git a/src/gateway/server-methods/sessions-files.ts b/src/gateway/server-methods/sessions-files.ts index 46c030aafbfa..e2e0a53f88cc 100644 --- a/src/gateway/server-methods/sessions-files.ts +++ b/src/gateway/server-methods/sessions-files.ts @@ -25,7 +25,7 @@ import { FsSafeError } from "../../infra/fs-safe.js"; import { pruneMapToMaxSize } from "../../infra/map-size.js"; import { normalizeAgentId, parseAgentSessionKey } from "../../routing/session-key.js"; import { - readSessionTranscriptVisibleMessageDelta, + readSessionTranscriptVisibleMessageDeltaCore, resolveTranscriptReadTarget, sqliteMessageEventWithSeq, toTranscriptReadScope, @@ -257,7 +257,7 @@ async function foldSqliteTouchedFiles( let maxBytes = TOUCHED_FILES_DELTA_MAX_BYTES; while (true) { - const delta = readSessionTranscriptVisibleMessageDelta(scope, { + const delta = readSessionTranscriptVisibleMessageDeltaCore(scope, { ...(cursor ? { cursor } : {}), maxBytes, maxMessages: TOUCHED_FILES_DELTA_MAX_MESSAGES, diff --git a/src/gateway/server-methods/sessions-mutations.archive-attribution.test.ts b/src/gateway/server-methods/sessions-mutations.archive-attribution.test.ts index 3c6e51ca8dc5..c19120cc758a 100644 --- a/src/gateway/server-methods/sessions-mutations.archive-attribution.test.ts +++ b/src/gateway/server-methods/sessions-mutations.archive-attribution.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { SessionManager } from "../../agents/sessions/session-manager.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, loadTranscriptEvents, upsertSessionEntry, @@ -214,7 +214,7 @@ describe("sessions.patch archive attribution", () => { ]); const database = openOpenClawAgentDatabase({ agentId: "main", env: state.env }); const readCandidateState = () => ({ - entries: listSessionEntries({ agentId: "main" }) + entries: listSessionEntriesCore({ agentId: "main" }) .filter(({ sessionKey }) => sessionKey === canonicalKey || sessionKey === aliasKey) .toSorted((left, right) => left.sessionKey.localeCompare(right.sessionKey)), members: listSessionMembers(memberScope), diff --git a/src/gateway/server-methods/sessions-read-cache.test.ts b/src/gateway/server-methods/sessions-read-cache.test.ts index c43f1b51af03..2f0b5239cf22 100644 --- a/src/gateway/server-methods/sessions-read-cache.test.ts +++ b/src/gateway/server-methods/sessions-read-cache.test.ts @@ -31,15 +31,15 @@ vi.mock("../session-utils.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - loadCombinedSessionStoreForGateway: ( - ...args: Parameters + loadCombinedSessionStoreForGatewayCore: ( + ...args: Parameters ) => { loader.calls(...args); if (loader.failNext) { loader.failNext = false; throw new Error("synthetic store load failure"); } - return actual.loadCombinedSessionStoreForGateway(...args); + return actual.loadCombinedSessionStoreForGatewayCore(...args); }, listSessionsFromStoreAsync: async ( ...args: Parameters diff --git a/src/gateway/server-methods/sessions-read.ts b/src/gateway/server-methods/sessions-read.ts index 281f64a3ec20..99a1c8f4da08 100644 --- a/src/gateway/server-methods/sessions-read.ts +++ b/src/gateway/server-methods/sessions-read.ts @@ -60,7 +60,7 @@ import type { import { buildGatewaySessionRow, listSessionsFromStoreAsync, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, resolveCanonicalSessionEntryFromStoreKeys, resolveGatewaySessionStoreTarget, resolveGatewaySessionStoreTargetWithStore, @@ -267,7 +267,7 @@ export const sessionReadHandlers: GatewayRequestHandlers = { const { durableStorePath, storePath, store } = measureDiagnosticsTimelineSpanSync( "gateway.sessions.list.store_load", () => - loadCombinedSessionStoreForGateway(cfg, { + loadCombinedSessionStoreForGatewayCore(cfg, { agentId: p.agentId, projection: "list", }), diff --git a/src/gateway/server-methods/sessions-rewind.test.ts b/src/gateway/server-methods/sessions-rewind.test.ts index af9cf2b08b2b..df0a56954ab6 100644 --- a/src/gateway/server-methods/sessions-rewind.test.ts +++ b/src/gateway/server-methods/sessions-rewind.test.ts @@ -33,7 +33,7 @@ vi.mock("../../media/store.js", async (importOriginal) => { import { appendTranscriptEvent, appendTranscriptMessage, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, upsertSessionEntry, } from "../../config/sessions/session-accessor.js"; @@ -528,7 +528,7 @@ describe("session message-cut methods", () => { message: "Codex is offline. Try again.", }); - const entryCount = listSessionEntries({ agentId: "main" }).length; + const entryCount = listSessionEntriesCore({ agentId: "main" }).length; const respond = await invoke("sessions.fork", "user-entry"); expect(respond).toHaveBeenCalledWith( @@ -539,7 +539,7 @@ describe("session message-cut methods", () => { details: { reason: "upstream-unavailable" }, }), ); - expect(listSessionEntries({ agentId: "main" })).toHaveLength(entryCount); + expect(listSessionEntriesCore({ agentId: "main" })).toHaveLength(entryCount); }); it.each(["steer-message", "in-progress-turn", "drift-mismatch"] as const)( diff --git a/src/gateway/server-methods/sessions-search.test.ts b/src/gateway/server-methods/sessions-search.test.ts index c1215c75e7d6..5271acc066bd 100644 --- a/src/gateway/server-methods/sessions-search.test.ts +++ b/src/gateway/server-methods/sessions-search.test.ts @@ -13,7 +13,7 @@ vi.mock("../../config/sessions/session-transcript-search.js", () => ({ })); vi.mock("../../config/sessions/session-accessor.js", async (importOriginal) => ({ ...(await importOriginal()), - listSessionEntries: (...args: unknown[]) => listSessionEntriesMock(...args), + listSessionEntriesCore: (...args: unknown[]) => listSessionEntriesMock(...args), listSessionEntriesReadOnly: (...args: unknown[]) => listSessionEntriesMock(...args), })); vi.mock("../../config/sessions.js", async (importOriginal) => ({ diff --git a/src/gateway/server-methods/sessions-sharing.test.ts b/src/gateway/server-methods/sessions-sharing.test.ts index 5c45dd0417af..30d3ad3cb5e1 100644 --- a/src/gateway/server-methods/sessions-sharing.test.ts +++ b/src/gateway/server-methods/sessions-sharing.test.ts @@ -3,7 +3,7 @@ import { SessionManager } from "../../agents/sessions/session-manager.js"; import { loadSessionEntry, loadTranscriptEvents, - patchSessionEntry, + patchSessionEntryCore, upsertSessionEntry, } from "../../config/sessions/session-accessor.js"; import { @@ -324,7 +324,9 @@ describe("session sharing handlers", () => { // The awaited model-catalog step flips the session to draft after the // pre-await draft filter ran, exercising the final fresh-target filter. const listWith = async (client: GatewayClient) => { - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "shared" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ + visibility: "shared", + })); invalidateSessionSharingSnapshot(sessionKey); const responses: Parameters[] = []; await sessionReadHandlers["sessions.list"]?.({ @@ -333,7 +335,7 @@ describe("session sharing handlers", () => { context: { ...context(vi.fn()), readPreparedGatewayModelCatalog: async () => { - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ visibility: "draft", })); invalidateSessionSharingSnapshot(sessionKey); @@ -409,7 +411,7 @@ describe("session sharing handlers", () => { context: { ...context(vi.fn()), readPreparedGatewayModelCatalog: async () => { - await patchSessionEntry({ agentId: "main", sessionKey: hiddenKey }, () => ({ + await patchSessionEntryCore({ agentId: "main", sessionKey: hiddenKey }, () => ({ visibility: "draft", })); invalidateSessionSharingSnapshot(hiddenKey); @@ -597,10 +599,12 @@ describe("session sharing handlers", () => { }; expectAccess(true); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "draft" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ visibility: "draft" })); invalidateSessionSharingSnapshot(sessionKey); expectAccess(false); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "shared" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ + visibility: "shared", + })); invalidateSessionSharingSnapshot(sessionKey); expectAccess(true); }); @@ -695,7 +699,9 @@ describe("session sharing handlers", () => { ], ]); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "shared" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ + visibility: "shared", + })); invalidateSessionSharingSnapshot(); const viewerClient = identifiedClient("viewer") as never; expect( @@ -706,7 +712,7 @@ describe("session sharing handlers", () => { agentId: "main", }), ).toBe(true); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "draft" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ visibility: "draft" })); invalidateSessionSharingSnapshot(sessionKey); expect( canReceiveSessionEvent({ @@ -717,7 +723,9 @@ describe("session sharing handlers", () => { }), ).toBe(false); - await patchSessionEntry({ agentId: "main", sessionKey }, () => ({ visibility: "shared" })); + await patchSessionEntryCore({ agentId: "main", sessionKey }, () => ({ + visibility: "shared", + })); const append = vi .spyOn(SessionManager, "appendMessageToTranscript") .mockImplementationOnce(() => { diff --git a/src/gateway/server-methods/sessions-sharing.ts b/src/gateway/server-methods/sessions-sharing.ts index bdbc6ed2d787..08272d4bec66 100644 --- a/src/gateway/server-methods/sessions-sharing.ts +++ b/src/gateway/server-methods/sessions-sharing.ts @@ -13,10 +13,10 @@ import { import { addSessionMember, listSessionMembers, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, removeSessionMember, } from "../../config/sessions.js"; -import { patchSessionEntry } from "../../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../../config/sessions/session-accessor.js"; import { runExclusiveSessionLifecycleMutation } from "../../sessions/session-lifecycle-admission.js"; import { listProfiles } from "../../state/user-profiles.js"; import { @@ -131,7 +131,7 @@ function knownSessionIdentities(params: { }); }; remember(params.actor); - for (const entry of Object.values(loadCombinedSessionStoreForGateway(params.cfg).store)) { + for (const entry of Object.values(loadCombinedSessionStoreForGatewayCore(params.cfg).store)) { remember(entry.createdActor ?? null); } for (const profile of listProfiles()) { @@ -216,7 +216,7 @@ export const sessionSharingHandlers: GatewayRequestHandlers = { // session-id check at the storage boundary so an out-of-band row // replacement still cannot inherit this visibility change. let sessionChanged = false; - await patchSessionEntry(scope, (entry) => { + await patchSessionEntryCore(scope, (entry) => { if (entry.sessionId !== current.entry.sessionId) { sessionChanged = true; return null; @@ -239,7 +239,7 @@ export const sessionSharingHandlers: GatewayRequestHandlers = { } catch (error) { // Roll back only the exact instance and value we patched; an unexpected // storage-owner replacement must not inherit the old visibility. - await patchSessionEntry(scope, (entry) => + await patchSessionEntryCore(scope, (entry) => entry.sessionId === current.entry.sessionId && resolveSessionVisibility(entry) === visibility ? { visibility: previous } diff --git a/src/gateway/server-methods/sessions.abort-agent-scope.test.ts b/src/gateway/server-methods/sessions.abort-agent-scope.test.ts index f66d346455c9..123f62e896b9 100644 --- a/src/gateway/server-methods/sessions.abort-agent-scope.test.ts +++ b/src/gateway/server-methods/sessions.abort-agent-scope.test.ts @@ -45,7 +45,7 @@ vi.mock("../session-utils.js", async () => { return { ...actual, listSessionsFromStoreAsync: (...args: unknown[]) => listSessionsFromStoreAsyncMock(...args), - loadCombinedSessionStoreForGateway: (...args: unknown[]) => + loadCombinedSessionStoreForGatewayCore: (...args: unknown[]) => loadCombinedSessionStoreForGatewayMock(...args), loadSessionEntry: (...args: unknown[]) => loadSessionEntryMock(...(args as [string, { agentId?: string }?])), diff --git a/src/gateway/server-methods/usage.sessions-usage-cache.test.ts b/src/gateway/server-methods/usage.sessions-usage-cache.test.ts index 43cc1f9d9bdf..13b793d09ace 100644 --- a/src/gateway/server-methods/usage.sessions-usage-cache.test.ts +++ b/src/gateway/server-methods/usage.sessions-usage-cache.test.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; const mocks = vi.hoisted(() => ({ discoverAllSessions: vi.fn(), - loadCombinedSessionStoreForGateway: vi.fn(), + loadCombinedSessionStoreForGatewayCore: vi.fn(), loadSessionCostSummariesFromCache: vi.fn(), })); @@ -12,7 +12,7 @@ vi.mock("../session-utils.js", async () => { const actual = await vi.importActual("../session-utils.js"); return { ...actual, - loadCombinedSessionStoreForGateway: mocks.loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore: mocks.loadCombinedSessionStoreForGatewayCore, }; }); @@ -82,7 +82,7 @@ describe("sessions.usage result cache", () => { vi.spyOn(Date, "now").mockImplementation(() => now); vi.clearAllMocks(); testApi.sessionsUsageCache.clear(); - mocks.loadCombinedSessionStoreForGateway.mockReturnValue({ + mocks.loadCombinedSessionStoreForGatewayCore.mockReturnValue({ storePath: "(multiple)", store: {}, }); @@ -141,10 +141,10 @@ describe("sessions.usage result cache", () => { expect(mocks.discoverAllSessions).toHaveBeenCalledTimes(variants.length + 1); expect(mocks.loadSessionCostSummariesFromCache).toHaveBeenCalledTimes(variants.length + 1); - const storeLoads = mocks.loadCombinedSessionStoreForGateway.mock.calls.length; + const storeLoads = mocks.loadCombinedSessionStoreForGatewayCore.mock.calls.length; await runSessionsUsage({ ...baseParams, key: "agent:main:missing" }); expect(testApi.sessionsUsageCache.size).toBe(variants.length + 1); - expect(mocks.loadCombinedSessionStoreForGateway).toHaveBeenCalledTimes(storeLoads + 1); + expect(mocks.loadCombinedSessionStoreForGatewayCore).toHaveBeenCalledTimes(storeLoads + 1); }); it("does not share entries across runtime config snapshots", async () => { diff --git a/src/gateway/server-methods/usage.sessions-usage.test.ts b/src/gateway/server-methods/usage.sessions-usage.test.ts index d8e8e11c3e13..4e50230c9082 100644 --- a/src/gateway/server-methods/usage.sessions-usage.test.ts +++ b/src/gateway/server-methods/usage.sessions-usage.test.ts @@ -24,7 +24,7 @@ vi.mock("../session-utils.js", async () => { return { ...actual, loadSessionEntryReadOnly: vi.fn(actual.loadSessionEntryReadOnly), - loadCombinedSessionStoreForGateway: vi.fn(() => ({ storePath: "(multiple)", store: {} })), + loadCombinedSessionStoreForGatewayCore: vi.fn(() => ({ storePath: "(multiple)", store: {} })), }; }); @@ -104,7 +104,10 @@ import { loadSessionUsageTimeSeries, resolveExistingUsageSessionFile, } from "../../infra/session-cost-usage.js"; -import { loadCombinedSessionStoreForGateway, loadSessionEntryReadOnly } from "../session-utils.js"; +import { + loadCombinedSessionStoreForGatewayCore, + loadSessionEntryReadOnly, +} from "../session-utils.js"; import { testApi, usageHandlers } from "./usage.js"; const TEST_RUNTIME_CONFIG = { @@ -230,7 +233,7 @@ describe("sessions.usage", () => { it("defaults list-style usage queries without agentId to the default agent", async () => { const respond = await runSessionsUsage(BASE_USAGE_RANGE); - expect(vi.mocked(loadCombinedSessionStoreForGateway)).toHaveBeenCalledWith( + expect(vi.mocked(loadCombinedSessionStoreForGatewayCore)).toHaveBeenCalledWith( TEST_RUNTIME_CONFIG, { agentId: "main" }, ); @@ -248,7 +251,7 @@ describe("sessions.usage", () => { it("uses explicit all-agent scope for list-style usage queries", async () => { const respond = await runSessionsUsage({ ...BASE_USAGE_RANGE, agentScope: "all" }); - expect(vi.mocked(loadCombinedSessionStoreForGateway)).toHaveBeenCalledWith( + expect(vi.mocked(loadCombinedSessionStoreForGatewayCore)).toHaveBeenCalledWith( TEST_RUNTIME_CONFIG, {}, ); @@ -287,7 +290,7 @@ describe("sessions.usage", () => { it("uses the requested agent for list-style usage queries", async () => { const respond = await runSessionsUsage({ ...BASE_USAGE_RANGE, agentId: "opus" }); - expect(vi.mocked(loadCombinedSessionStoreForGateway)).toHaveBeenCalledWith( + expect(vi.mocked(loadCombinedSessionStoreForGatewayCore)).toHaveBeenCalledWith( TEST_RUNTIME_CONFIG, { agentId: "opus" }, ); @@ -475,7 +478,7 @@ describe("sessions.usage", () => { it("discovers usage for requested disk-only agents not listed in config", async () => { const respond = await runSessionsUsage({ ...BASE_USAGE_RANGE, agentId: "codex" }); - expect(vi.mocked(loadCombinedSessionStoreForGateway)).toHaveBeenCalledWith( + expect(vi.mocked(loadCombinedSessionStoreForGatewayCore)).toHaveBeenCalledWith( TEST_RUNTIME_CONFIG, { agentId: "codex" }, ); @@ -493,7 +496,7 @@ describe("sessions.usage", () => { }); it("does not attach out-of-scope store entries to list-style usage results", async () => { - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { "agent:main:s-opus": { @@ -524,7 +527,7 @@ describe("sessions.usage", () => { writeSessionFile("main.jsonl"); mockStoredSession("agent:opus:main", "main"); - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { "agent:opus:main": { @@ -577,7 +580,7 @@ describe("sessions.usage", () => { label: "Opus global", updatedAt: 999, }; - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { global: sessionEntry, @@ -615,7 +618,7 @@ describe("sessions.usage", () => { await withUsageState(async (writeSessionFile) => { const sessionFile = writeSessionFile("shared.jsonl"); - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { "agent:main:shared": { @@ -660,7 +663,7 @@ describe("sessions.usage", () => { // Swap the store mock for this test: the canonical key differs from the discovered key // but points at the same sessionId. - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { [storeKey]: { @@ -694,7 +697,7 @@ describe("sessions.usage", () => { writeSessionFile("old.jsonl.reset.2026-02-01T00-00-00.000Z"); mockStoredSession(storeKey, "current"); - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { [storeKey]: { @@ -773,7 +776,7 @@ describe("sessions.usage", () => { writeSessionFile("run-dup.jsonl"); mockStoredSession(preferredKey, "run-dup"); - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(multiple)", store: { [preferredKey]: { diff --git a/src/gateway/server-methods/usage.test.ts b/src/gateway/server-methods/usage.test.ts index 3eea5d85b742..5a71f0bcac50 100644 --- a/src/gateway/server-methods/usage.test.ts +++ b/src/gateway/server-methods/usage.test.ts @@ -42,7 +42,7 @@ vi.mock("../session-utils.js", async () => { const actual = await vi.importActual("../session-utils.js"); return { ...actual, - loadCombinedSessionStoreForGateway: vi.fn(() => ({ storePath: "(multiple)", store: {} })), + loadCombinedSessionStoreForGatewayCore: vi.fn(() => ({ storePath: "(multiple)", store: {} })), }; }); diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index 8941165a7f20..019e29f355b0 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -66,7 +66,10 @@ import { resolveSessionStoreAgentId, resolveStoredSessionKeyForAgentStore, } from "../session-store-key.js"; -import { loadCombinedSessionStoreForGateway, loadSessionEntryReadOnly } from "../session-utils.js"; +import { + loadCombinedSessionStoreForGatewayCore, + loadSessionEntryReadOnly, +} from "../session-utils.js"; import { loadUsageStatusStaleWhileRevalidate } from "./models-auth-status-usage-cache.js"; import type { GatewayRequestHandlers, RespondFn } from "./types.js"; import { assertValidParams } from "./validation.js"; @@ -1235,7 +1238,7 @@ export const usageHandlers: GatewayRequestHandlers = { load: async () => { // Load session store for named sessions only on a result-cache miss. const sessionStoreOpts = effectiveAgentId ? { agentId: effectiveAgentId } : {}; - const { store } = loadCombinedSessionStoreForGateway(config, sessionStoreOpts); + const { store } = loadCombinedSessionStoreForGatewayCore(config, sessionStoreOpts); const scopedStore = effectiveAgentId ? filterSessionStoreByAgent({ config, diff --git a/src/gateway/server-plugin-runtime-client.ts b/src/gateway/server-plugin-runtime-client.ts index b76708076df9..91aea6412a7a 100644 --- a/src/gateway/server-plugin-runtime-client.ts +++ b/src/gateway/server-plugin-runtime-client.ts @@ -6,7 +6,7 @@ import { } from "../../packages/gateway-protocol/src/client-info.js"; import { PROTOCOL_VERSION } from "../../packages/gateway-protocol/src/version.js"; import { isKnownCoreToolId } from "../agents/tool-catalog.js"; -import { normalizeToolName } from "../agents/tool-policy.js"; +import { normalizeToolPolicyName } from "../agents/tool-policy.js"; import { getActivePluginRegistry } from "../plugins/runtime.js"; import type { PluginSubagentRequesterContext } from "../plugins/runtime/subagent-requester-context.js"; import type { RuntimePluginToolGrant } from "../plugins/runtime/tool-grant.js"; @@ -92,7 +92,9 @@ export function resolvePluginSubagentToolsAlsoAllow(params: { toolsAlsoAllow?: string[]; }): RuntimePluginToolGrant | undefined { const requested = uniqueStrings( - (params.toolsAlsoAllow ?? []).map((entry) => normalizeToolName(entry.trim())).filter(Boolean), + (params.toolsAlsoAllow ?? []) + .map((entry) => normalizeToolPolicyName(entry.trim())) + .filter(Boolean), ); if (requested.length === 0) { return undefined; @@ -110,7 +112,7 @@ export function resolvePluginSubagentToolsAlsoAllow(params: { (registry?.tools ?? []) .filter((registration) => [...registration.names, ...(registration.declaredNames ?? [])].some( - (registeredName) => normalizeToolName(registeredName) === toolName, + (registeredName) => normalizeToolPolicyName(registeredName) === toolName, ), ) .map((registration) => registration.pluginId), diff --git a/src/gateway/server-session-key.test.ts b/src/gateway/server-session-key.test.ts index 69d0f00b1968..1eeabb7d23e8 100644 --- a/src/gateway/server-session-key.test.ts +++ b/src/gateway/server-session-key.test.ts @@ -19,7 +19,7 @@ vi.mock("./session-utils.js", async () => { const actual = await vi.importActual("./session-utils.js"); return { ...actual, - loadCombinedSessionStoreForGateway: ( + loadCombinedSessionStoreForGatewayCore: ( cfg: OpenClawConfig, opts?: { agentId?: string; configuredAgentsOnly?: boolean }, ) => hoisted.loadCombinedSessionStoreForGatewayMock(cfg, opts), diff --git a/src/gateway/server-session-key.ts b/src/gateway/server-session-key.ts index 0696a43abbf0..80e709a5f3d6 100644 --- a/src/gateway/server-session-key.ts +++ b/src/gateway/server-session-key.ts @@ -17,7 +17,7 @@ import { } from "../routing/session-key.js"; import { resolvePreferredSessionKeyForSessionIdMatches } from "../sessions/session-id-resolution.js"; import { resolveSessionStoreAgentId, resolveSessionStoreKey } from "./session-store-key.js"; -import { loadCombinedSessionStoreForGateway } from "./session-utils.js"; +import { loadCombinedSessionStoreForGatewayCore } from "./session-utils.js"; const RUN_LOOKUP_CACHE_LIMIT = 256; const RUN_LOOKUP_MISS_TTL_MS = 1_000; @@ -117,7 +117,7 @@ export function resolveSessionKeyForRun(runId: string, opts: { agentId?: string } resolvedSessionKeyByRunId.delete(cacheKey); } - const { store } = loadCombinedSessionStoreForGateway(cfg, { agentId: requestedAgentId }); + const { store } = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: requestedAgentId }); const matches = Object.entries(store).filter( (entry): entry is [string, SessionEntry] => entry[1]?.sessionId === runId && sessionKeyMatchesAgent(entry[0], requestedAgentId, cfg), diff --git a/src/gateway/server-startup-handler-prewarm.test.ts b/src/gateway/server-startup-handler-prewarm.test.ts index 733dfcc673e7..6a9554679dce 100644 --- a/src/gateway/server-startup-handler-prewarm.test.ts +++ b/src/gateway/server-startup-handler-prewarm.test.ts @@ -10,7 +10,7 @@ const mocks = vi.hoisted(() => ({ mocks.events.push("sessions.count"); return true; }), - loadCombinedSessionStoreForGateway: vi.fn((_cfg: unknown, options: { agentId: string }) => { + loadCombinedSessionStoreForGatewayCore: vi.fn((_cfg: unknown, options: { agentId: string }) => { mocks.events.push(`sessions.load.${options.agentId}`); return { durableStorePath: `/state/${options.agentId}.sqlite`, @@ -30,7 +30,7 @@ const mocks = vi.hoisted(() => ({ vi.mock("../config/sessions/combined-store-gateway.js", () => ({ canPrewarmCombinedSessionStoresForGateway: mocks.canPrewarmCombinedSessionStoresForGateway, - loadCombinedSessionStoreForGateway: mocks.loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore: mocks.loadCombinedSessionStoreForGatewayCore, })); vi.mock("./session-utils-list.js", () => ({ @@ -50,7 +50,7 @@ beforeEach(() => { mocks.events.push("sessions.count"); return true; }); - mocks.loadCombinedSessionStoreForGateway.mockClear(); + mocks.loadCombinedSessionStoreForGatewayCore.mockClear(); mocks.listSessionsFromStoreAsync.mockClear(); mocks.listManagedPlugins.mockClear(); }); @@ -83,11 +83,11 @@ describe("scheduleGatewayHandlerPrewarm", () => { "sessions.rows.research", "plugins", ]); - expect(mocks.loadCombinedSessionStoreForGateway).toHaveBeenNthCalledWith(1, cfg, { + expect(mocks.loadCombinedSessionStoreForGatewayCore).toHaveBeenNthCalledWith(1, cfg, { agentId: "main", projection: "list", }); - expect(mocks.loadCombinedSessionStoreForGateway).toHaveBeenNthCalledWith(2, cfg, { + expect(mocks.loadCombinedSessionStoreForGatewayCore).toHaveBeenNthCalledWith(2, cfg, { agentId: "research", projection: "list", }); diff --git a/src/gateway/server-startup-handler-prewarm.ts b/src/gateway/server-startup-handler-prewarm.ts index 812967f47935..9942401e1a56 100644 --- a/src/gateway/server-startup-handler-prewarm.ts +++ b/src/gateway/server-startup-handler-prewarm.ts @@ -21,12 +21,12 @@ type GatewayHandlerPrewarmHandle = { }; async function prewarmGatewaySessionListData(cfg: OpenClawConfig, agentId: string): Promise { - const [{ loadCombinedSessionStoreForGateway }, { listSessionsFromStoreAsync }] = + const [{ loadCombinedSessionStoreForGatewayCore }, { listSessionsFromStoreAsync }] = await Promise.all([ import("../config/sessions/combined-store-gateway.js"), import("./session-utils-list.js"), ]); - const { durableStorePath, storePath, store } = loadCombinedSessionStoreForGateway(cfg, { + const { durableStorePath, storePath, store } = loadCombinedSessionStoreForGatewayCore(cfg, { agentId, projection: "list", }); diff --git a/src/gateway/server.chat.gateway-server-chat-b.test.ts b/src/gateway/server.chat.gateway-server-chat-b.test.ts index 3c98f8cc1e4b..94e5d456e7ae 100644 --- a/src/gateway/server.chat.gateway-server-chat-b.test.ts +++ b/src/gateway/server.chat.gateway-server-chat-b.test.ts @@ -19,7 +19,7 @@ import { loadSessionEntry, loadExactSessionEntry, loadTranscriptEventsSync, - patchSessionEntry, + patchSessionEntryCore, replaceTranscriptEvents, replaceSessionEntry, withTranscriptWriteLock, @@ -3185,7 +3185,7 @@ describe("gateway server chat", () => { }); restartRecoveryMocks.retryRestartAbortedMainSessionRecovery.mockImplementationOnce( async ({ sessionKey, storePath: recoveryStorePath }) => { - await patchSessionEntry({ sessionKey, storePath: recoveryStorePath }, () => ({ + await patchSessionEntryCore({ sessionKey, storePath: recoveryStorePath }, () => ({ abortedLastRun: false, updatedAt: Date.now(), })); @@ -3252,7 +3252,7 @@ describe("gateway server chat", () => { () => expect(context.dedupe.has(pendingChatSendDedupeKey(idempotencyKey))).toBe(true), FAST_WAIT_OPTS, ); - await patchSessionEntry({ sessionKey: "agent:main:main", storePath }, () => ({ + await patchSessionEntryCore({ sessionKey: "agent:main:main", storePath }, () => ({ restartRecoveryTerminalRunIds: [idempotencyKey], updatedAt: Date.now(), })); @@ -3323,7 +3323,7 @@ describe("gateway server chat", () => { }); restartRecoveryMocks.retryRestartAbortedMainSessionRecovery.mockImplementationOnce( async ({ sessionKey, storePath: recoveryStorePath }) => { - await patchSessionEntry({ sessionKey, storePath: recoveryStorePath }, () => ({ + await patchSessionEntryCore({ sessionKey, storePath: recoveryStorePath }, () => ({ sessionId: "replacement-session", restartRecoveryDeliveryRunId: "replacement-recovery", restartRecoveryDeliverySourceRunId: "replacement-source", diff --git a/src/gateway/server.sessions.compaction.test.ts b/src/gateway/server.sessions.compaction.test.ts index 2e5a306c5b11..bc591a87125f 100644 --- a/src/gateway/server.sessions.compaction.test.ts +++ b/src/gateway/server.sessions.compaction.test.ts @@ -20,7 +20,7 @@ import { appendTranscriptEvent, loadSessionEntry as loadAccessorSessionEntry, loadTranscriptEvents, - patchSessionEntry as patchAccessorSessionEntry, + patchSessionEntryCore as patchAccessorSessionEntry, replaceSessionEntry, upsertSessionEntry, } from "../config/sessions/session-accessor.js"; diff --git a/src/gateway/server.sessions.create.test.ts b/src/gateway/server.sessions.create.test.ts index e05a8b782eee..f676f281c419 100644 --- a/src/gateway/server.sessions.create.test.ts +++ b/src/gateway/server.sessions.create.test.ts @@ -17,7 +17,7 @@ import { managedWorktrees } from "../agents/worktrees/service.js"; import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js"; import { initSessionState } from "../auto-reply/reply/session.js"; import { getRuntimeConfig } from "../config/io.js"; -import { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; import { loadSessionEntry, loadTranscriptEvents, @@ -283,7 +283,9 @@ test("sessions.create keeps incognito rows process-local through list, spawn, re .get(key), ).toEqual({ session_key: key }); expect(loadSessionEntry({ agentId: "main", sessionKey: key })?.incognito).toBe(true); - expect(loadCombinedSessionStoreForGateway(getRuntimeConfig()).store[key]?.incognito).toBe(true); + expect(loadCombinedSessionStoreForGatewayCore(getRuntimeConfig()).store[key]?.incognito).toBe( + true, + ); expect(loadSessionEntry({ agentId: "main", sessionKey: key, storePath })?.incognito).toBe(true); const persistentDatabase = openOpenClawAgentDatabase({ agentId: "main", @@ -2250,7 +2252,9 @@ test("sessions.create commits no session after delegated authority closes", asyn expect(created.ok).toBe(false); expect(created.error?.message).toContain("agent runtime authority is no longer active"); - expect(loadCombinedSessionStoreForGateway(getRuntimeConfig()).store[sessionKey]).toBeUndefined(); + expect( + loadCombinedSessionStoreForGatewayCore(getRuntimeConfig()).store[sessionKey], + ).toBeUndefined(); }); test("sessions.create starts no initial turn when authority closes after session commit", async () => { @@ -2288,7 +2292,9 @@ test("sessions.create starts no initial turn when authority closes after session expect(created.ok).toBe(true); expect(created.payload?.runStarted).toBe(false); expect(chatSend).not.toHaveBeenCalled(); - expect(loadCombinedSessionStoreForGateway(getRuntimeConfig()).store[sessionKey]).toBeDefined(); + expect( + loadCombinedSessionStoreForGatewayCore(getRuntimeConfig()).store[sessionKey], + ).toBeDefined(); } finally { chatSend.mockRestore(); } diff --git a/src/gateway/server.sessions.list-store-materialization.test.ts b/src/gateway/server.sessions.list-store-materialization.test.ts index f11aa1a85abd..50fc1828817a 100644 --- a/src/gateway/server.sessions.list-store-materialization.test.ts +++ b/src/gateway/server.sessions.list-store-materialization.test.ts @@ -45,11 +45,11 @@ async function countMaterializedEntriesForRows(rows: number): Promise { let materialized = 0; // Only the lookup-store path used by sharing resolution goes through - // `listSessionEntries`; the listing itself and ACP metadata use the read-only + // `listSessionEntriesCore`; the listing itself and ACP metadata use the read-only // variant, so this isolates the per-row store loads under test. - const original = sessionAccessor.listSessionEntries; + const original = sessionAccessor.listSessionEntriesCore; const spies = [ - vi.spyOn(sessionAccessor, "listSessionEntries").mockImplementation(((...args: never[]) => { + vi.spyOn(sessionAccessor, "listSessionEntriesCore").mockImplementation(((...args: never[]) => { const result = (original as (...inner: never[]) => unknown[])(...args); materialized += Array.isArray(result) ? result.length : 0; return result; @@ -281,13 +281,13 @@ test("sessions.list projects out prompt snapshots without changing full entry re const projections: Array = []; const originalReadOnly = sessionAccessor.listSessionEntriesReadOnly; - const originalWritable = sessionAccessor.listSessionEntries; + const originalWritable = sessionAccessor.listSessionEntriesCore; const spies = [ vi.spyOn(sessionAccessor, "listSessionEntriesReadOnly").mockImplementation((scope) => { projections.push(scope?.projection); return originalReadOnly(scope); }), - vi.spyOn(sessionAccessor, "listSessionEntries").mockImplementation((scope) => { + vi.spyOn(sessionAccessor, "listSessionEntriesCore").mockImplementation((scope) => { projections.push(scope?.projection); return originalWritable(scope); }), diff --git a/src/gateway/server.sessions.permissions-hooks.test.ts b/src/gateway/server.sessions.permissions-hooks.test.ts index 262ea21be878..5bfc71bffd42 100644 --- a/src/gateway/server.sessions.permissions-hooks.test.ts +++ b/src/gateway/server.sessions.permissions-hooks.test.ts @@ -14,12 +14,12 @@ import { GATEWAY_CLIENT_MODES, } from "../../packages/gateway-protocol/src/client-info.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, upsertSessionEntry, } from "../config/sessions/session-accessor.js"; import { isSessionPatchEvent } from "../hooks/internal-hooks.js"; -import { requireRecord } from "./test-helpers.assertions.js"; +import { requireGatewayRecord } from "./test-helpers.assertions.js"; import { connectWebchatClient, rpcReq, testState, writeSessionStore } from "./test-helpers.js"; import { setupGatewaySessionsTestHarness, @@ -186,7 +186,7 @@ test("webchat session mutations follow operator scope policy", async () => { } expect( - listSessionEntries({ storePath }) + listSessionEntriesCore({ storePath }) .map(({ sessionKey }) => sessionKey) .toSorted(), ).toEqual(["agent:main:discord:group:dev", "agent:main:main"]); @@ -217,19 +217,19 @@ test("session:patch hook fires with correct context", async () => { }); expect(patched.ok).toBe(true); - const event = requireRecord( + const event = requireGatewayRecord( requireFirstCallArg(sessionHookMocks.triggerInternalHook), "internal hook event", ); expect(event.type).toBe("session"); expect(event.action).toBe("patch"); expect(event.sessionKey).toBe("agent:main:main"); - const context = requireRecord(event.context, "internal hook context"); - const sessionEntry = requireRecord(context.sessionEntry, "session entry"); + const context = requireGatewayRecord(event.context, "internal hook context"); + const sessionEntry = requireGatewayRecord(context.sessionEntry, "session entry"); expect(sessionEntry.sessionId).toBe("sess-hook-test"); expect(sessionEntry.label).toBe("updated-label"); - expect(requireRecord(context.patch, "session patch").label).toBe("updated-label"); - requireRecord(context.cfg, "config"); + expect(requireGatewayRecord(context.patch, "session patch").label).toBe("updated-label"); + requireGatewayRecord(context.cfg, "config"); ws.close(); }); @@ -295,7 +295,7 @@ test("session:patch hook only fires after successful patch", async () => { }); expect(validPatch.ok).toBe(true); - const event = requireRecord( + const event = requireGatewayRecord( requireFirstCallArg(sessionHookMocks.triggerInternalHook), "internal hook event", ); diff --git a/src/gateway/server.sessions.reset-hooks.test.ts b/src/gateway/server.sessions.reset-hooks.test.ts index 130e34dffb19..5ce41072f65a 100644 --- a/src/gateway/server.sessions.reset-hooks.test.ts +++ b/src/gateway/server.sessions.reset-hooks.test.ts @@ -4,7 +4,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { expect, test, vi } from "vitest"; import { formatSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; -import { listSessionEntries, loadSessionEntry } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore, loadSessionEntry } from "../config/sessions/session-accessor.js"; import type { InternalSessionEntry } from "../config/sessions/types.js"; import { beginSessionWorkAdmission } from "../sessions/session-lifecycle-admission.js"; import { embeddedRunMock, testState, writeSessionStore } from "./test-helpers.js"; @@ -319,7 +319,7 @@ async function resolveGatewaySessionStorePathForKey(key: string) { async function loadGatewaySessionStoreForKey(key: string) { const gatewayStorePath = await resolveGatewaySessionStorePathForKey(key); return Object.fromEntries( - listSessionEntries({ storePath: gatewayStorePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ storePath: gatewayStorePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), diff --git a/src/gateway/session-child-sessions.test.ts b/src/gateway/session-child-sessions.test.ts index d4f1310fffc2..145bf3355c28 100644 --- a/src/gateway/session-child-sessions.test.ts +++ b/src/gateway/session-child-sessions.test.ts @@ -2,13 +2,13 @@ * Child-session aggregation tests. */ import { expect, test, vi } from "vitest"; -import { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { findDirectChildSessionsForParent } from "./session-child-sessions.js"; vi.mock("../config/sessions/combined-store-gateway.js", () => ({ - loadCombinedSessionStoreForGateway: vi.fn(), + loadCombinedSessionStoreForGatewayCore: vi.fn(), })); const cfg = {} as unknown as OpenClawConfig; @@ -22,7 +22,7 @@ function entry(patch: Partial): SessionEntry { } test("findDirectChildSessionsForParent matches direct lineage only", () => { - vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({ + vi.mocked(loadCombinedSessionStoreForGatewayCore).mockReturnValue({ storePath: "(combined)", store: { "agent:main:main": entry({ diff --git a/src/gateway/session-child-sessions.ts b/src/gateway/session-child-sessions.ts index 1ca2b662977b..47e2c06d819d 100644 --- a/src/gateway/session-child-sessions.ts +++ b/src/gateway/session-child-sessions.ts @@ -1,7 +1,7 @@ // Gateway session child-discovery helpers. // Finds direct parent/child relationships across canonical and legacy fields. import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -34,7 +34,7 @@ export function findDirectChildSessionsForParent(params: { cfg: OpenClawConfig; parentKey: string; }): DirectChildSessionEntry[] { - const { store } = loadCombinedSessionStoreForGateway(params.cfg); + const { store } = loadCombinedSessionStoreForGatewayCore(params.cfg); return Object.entries(store) .filter(([sessionKey, entry]) => isDirectChildSessionEntry({ diff --git a/src/gateway/session-observer-model.ts b/src/gateway/session-observer-model.ts index f3f5b582ff3a..cb4ddd8a3291 100644 --- a/src/gateway/session-observer-model.ts +++ b/src/gateway/session-observer-model.ts @@ -18,7 +18,7 @@ import type { import type { resolveUtilityModelRefForAgent } from "../agents/utility-model.js"; import { loadSessionEntryReadOnly, - patchSessionEntry, + patchSessionEntryCore, } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -264,7 +264,7 @@ export async function defaultPersistDigest(params: { stillCurrent?: () => boolean; }): Promise { let missingEntry = false; - const result = await patchSessionEntry( + const result = await patchSessionEntryCore( { sessionKey: params.sessionKey, agentId: params.agentId }, (entry, context) => { if (!context.existingEntry) { diff --git a/src/gateway/session-sharing-store-cache.test.ts b/src/gateway/session-sharing-store-cache.test.ts index b8c0ac3bc0ea..7d1018384093 100644 --- a/src/gateway/session-sharing-store-cache.test.ts +++ b/src/gateway/session-sharing-store-cache.test.ts @@ -166,8 +166,8 @@ describe("session mutation authorization store caches", () => { } const materializations = new Map(); - const originalListSessionEntries = sessionAccessor.listSessionEntries; - vi.spyOn(sessionAccessor, "listSessionEntries").mockImplementation((scope) => { + const originalListSessionEntries = sessionAccessor.listSessionEntriesCore; + vi.spyOn(sessionAccessor, "listSessionEntriesCore").mockImplementation((scope) => { const entries = originalListSessionEntries(scope); if (scope?.clone === false) { const storePath = scope.storePath ?? "default"; diff --git a/src/gateway/session-sharing.ts b/src/gateway/session-sharing.ts index 98bd9a77203c..2f2a93a917b5 100644 --- a/src/gateway/session-sharing.ts +++ b/src/gateway/session-sharing.ts @@ -11,7 +11,7 @@ import { resolveAllAgentSessionStoreTargetsSync, type SessionEntry, } from "../config/sessions.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isIncognitoSessionKey } from "../routing/session-key.js"; import { verifyBoardViewTicket } from "./board-view-ticket.js"; @@ -367,7 +367,7 @@ function resolveSessionGroupMutationTargets(params: { return undefined; } return resolveAllAgentSessionStoreTargetsSync(params.getCfg()).flatMap((storeTarget) => - listSessionEntries({ + listSessionEntriesCore({ agentId: storeTarget.agentId, storePath: storeTarget.storePath, }).flatMap(({ sessionKey, entry }) => diff --git a/src/gateway/session-transcript-readers.ts b/src/gateway/session-transcript-readers.ts index ddc24b361187..0ba7c439ec2f 100644 --- a/src/gateway/session-transcript-readers.ts +++ b/src/gateway/session-transcript-readers.ts @@ -31,7 +31,7 @@ import type { SessionPreviewItem } from "./session-utils.types.js"; export type { ReadSessionMessagesAsyncOptions }; export { attachOpenClawTranscriptMeta, capArrayByJsonBytes } from "./session-utils.fs.js"; -export { readSessionTranscriptVisibleMessageDelta } from "../config/sessions/session-accessor.js"; +export { readSessionTranscriptVisibleMessageDeltaCore } from "../config/sessions/session-accessor.js"; export type { SessionTranscriptReadScope }; diff --git a/src/gateway/session-utils-store-lookup.ts b/src/gateway/session-utils-store-lookup.ts index 5048641c6650..1955b2ef6750 100644 --- a/src/gateway/session-utils-store-lookup.ts +++ b/src/gateway/session-utils-store-lookup.ts @@ -10,7 +10,7 @@ import { } from "../config/sessions.js"; import { listSessionChildEntriesReadOnly, - listSessionEntries as listAccessorSessionEntries, + listSessionEntriesCore as listAccessorSessionEntries, listSessionEntriesReadOnly as listAccessorSessionEntriesReadOnly, loadExactSessionEntryReadOnly, type SessionEntryListScope, diff --git a/src/gateway/session-utils.subagent.test.ts b/src/gateway/session-utils.subagent.test.ts index a0b582594da9..a3e90c270757 100644 --- a/src/gateway/session-utils.subagent.test.ts +++ b/src/gateway/session-utils.subagent.test.ts @@ -29,7 +29,7 @@ import { withEnv } from "../test-utils/env.js"; import { buildSingleRowStoreChildSessionsByKey } from "./session-utils-projection.js"; import { listSessionsFromStore, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, resolveGatewayModelSupportsImages, } from "./session-utils.js"; @@ -1388,7 +1388,7 @@ describe("listSessionsFromStore subagent metadata", () => { }); }); -describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)", () => { +describe("loadCombinedSessionStoreForGatewayCore includes disk-only agents (#32804)", () => { test("fixed stores retain a colliding unsuffixed database on the default owner", async () => { await withStateDirEnv("openclaw-fixed-store-collision-", async ({ stateDir }) => { const storePath = path.join(stateDir, "ops.json"); @@ -1416,7 +1416,7 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }), ).toBe(false); - const { diagnostics, store } = loadCombinedSessionStoreForGateway(cfg); + const { diagnostics, store } = loadCombinedSessionStoreForGatewayCore(cfg); expect(store["agent:main:main"]?.sessionId).toBe("s-main-unscoped"); expect(store["agent:ops:main"]).toBeUndefined(); expect(diagnostics).toContainEqual( @@ -1447,7 +1447,7 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" "ops", ); - const { diagnostics, store } = loadCombinedSessionStoreForGateway(cfg); + const { diagnostics, store } = loadCombinedSessionStoreForGatewayCore(cfg); expect(store["agent:ops:main"]?.sessionId).toBe("s-ops-registered"); expect(store["agent:main:main"]).toBeUndefined(); expect(diagnostics).toContainEqual( @@ -1516,12 +1516,12 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }), ).toBe(false); - const { store } = loadCombinedSessionStoreForGateway(cfg); + const { store } = loadCombinedSessionStoreForGatewayCore(cfg); expect(store["agent:ops:main"]?.sessionId).toBe("s-ops"); expect(store["agent:worker:main"]?.sessionId).toBe("s-worker"); expect(store["agent:dynamic:main"]?.sessionId).toBe("s-dynamic"); - const configuredOnly = loadCombinedSessionStoreForGateway(cfg, { + const configuredOnly = loadCombinedSessionStoreForGatewayCore(cfg, { configuredAgentsOnly: true, }).store; expect(configuredOnly["agent:ops:legacy"]?.sessionId).toBe("s-legacy-ops"); @@ -1529,19 +1529,19 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" expect(configuredOnly["agent:dynamic:main"]).toBeUndefined(); expect(configuredOnly[dynamicIncognitoKey]).toBeUndefined(); - const opsOnly = loadCombinedSessionStoreForGateway(cfg, { agentId: "ops" }).store; + const opsOnly = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: "ops" }).store; expect(opsOnly["agent:ops:main"]?.sessionId).toBe("s-ops"); expect(opsOnly["agent:ops:legacy"]?.sessionId).toBe("s-legacy-ops"); expect(opsOnly["agent:worker:main"]).toBeUndefined(); expect(opsOnly["agent:dynamic:main"]).toBeUndefined(); - const explicitDynamic = loadCombinedSessionStoreForGateway(cfg, { + const explicitDynamic = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: "dynamic", configuredAgentsOnly: true, }).store; expect(explicitDynamic["agent:dynamic:main"]?.sessionId).toBe("s-dynamic"); - const mainOnly = loadCombinedSessionStoreForGateway(cfg, { agentId: "main" }).store; + const mainOnly = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: "main" }).store; expect(mainOnly["agent:ops:legacy"]).toBeUndefined(); }); }); @@ -1574,7 +1574,7 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }, } as OpenClawConfig; - const { store } = loadCombinedSessionStoreForGateway(cfg); + const { store } = loadCombinedSessionStoreForGatewayCore(cfg); expect(store["agent:main:main"]?.sessionId).toBe("s-main"); expect(store["agent:codex:acp-task"]?.sessionId).toBe("s-codex"); }); @@ -1610,7 +1610,9 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" }, } as OpenClawConfig; - const { store, storePath } = loadCombinedSessionStoreForGateway(cfg, { agentId: "codex" }); + const { store, storePath } = loadCombinedSessionStoreForGatewayCore(cfg, { + agentId: "codex", + }); expect( canPrewarmCombinedSessionStoresForGateway(cfg, { @@ -1623,7 +1625,7 @@ describe("loadCombinedSessionStoreForGateway includes disk-only agents (#32804)" expect(store["agent:codex:acp-task"]?.sessionId).toBe("s-codex"); expect(store["agent:main:main"]).toBeUndefined(); - const mainOnly = loadCombinedSessionStoreForGateway(cfg, { agentId: "main" }).store; + const mainOnly = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: "main" }).store; expect(mainOnly["agent:main:main"]?.sessionId).toBe("s-main"); }); }); diff --git a/src/gateway/session-utils.telegram-recreate.test.ts b/src/gateway/session-utils.telegram-recreate.test.ts index 4db7506a5a5f..455babd5334d 100644 --- a/src/gateway/session-utils.telegram-recreate.test.ts +++ b/src/gateway/session-utils.telegram-recreate.test.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { MsgContext } from "../auto-reply/templating.js"; -import { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; import { deleteSessionEntryLifecycle, loadSessionEntry, @@ -106,7 +106,7 @@ describe("Telegram direct session recreation after delete", () => { ...cfg, session: { ...cfg.session, store: storePath }, } satisfies OpenClawConfig; - const loaded = loadCombinedSessionStoreForGateway(runtimeCfg, { agentId: "main" }); + const loaded = loadCombinedSessionStoreForGatewayCore(runtimeCfg, { agentId: "main" }); const listed = listSessionsFromStore({ cfg: runtimeCfg, storePath: loaded.storePath, diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index 8983681716e3..efb0049f895c 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -11,7 +11,7 @@ export type { SessionsPreviewResult, } from "./session-utils.types.js"; export { resolveSessionModelRef } from "../agents/session-model-ref.js"; -export { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js"; +export { loadCombinedSessionStoreForGatewayCore } from "../config/sessions/combined-store-gateway.js"; export { deriveSessionTitle } from "./session-utils-core.js"; export { resolveDeletedAgentIdFromSessionKey } from "./session-utils-store.js"; export { loadGatewaySessionEntry as loadSessionEntry } from "./session-utils-store.js"; diff --git a/src/gateway/sessions-resolve.test.ts b/src/gateway/sessions-resolve.test.ts index 1074bd46e39a..7607df02acdf 100644 --- a/src/gateway/sessions-resolve.test.ts +++ b/src/gateway/sessions-resolve.test.ts @@ -28,7 +28,7 @@ vi.mock("./session-utils.js", async () => { listSessionsFromStore: hoisted.listSessionsFromStoreMock, resolveGatewaySessionStoreTargetWithStore: hoisted.resolveGatewaySessionStoreTargetWithStoreMock, - loadCombinedSessionStoreForGateway: hoisted.loadCombinedSessionStoreForGatewayMock, + loadCombinedSessionStoreForGatewayCore: hoisted.loadCombinedSessionStoreForGatewayMock, }; }); diff --git a/src/gateway/sessions-resolve.ts b/src/gateway/sessions-resolve.ts index bbda88e1e4e6..acc6b7cf001d 100644 --- a/src/gateway/sessions-resolve.ts +++ b/src/gateway/sessions-resolve.ts @@ -24,7 +24,7 @@ import { buildGatewaySessionInfo, filterAndSortSessionEntries, listSessionsFromStore, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, resolveDeletedAgentIdFromSessionKey, resolveGatewaySessionStoreTargetWithStore, } from "./session-utils.js"; @@ -231,7 +231,7 @@ export async function resolveSessionKeyFromResolveParams(params: { if (hasSessionId) { // sessionId can collide across stores; delegate selection so exact key // matches and ambiguity rules stay shared with other session-id callers. - const { store } = loadCombinedSessionStoreForGateway(cfg, { agentId: p.agentId }); + const { store } = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: p.agentId }); const matches = findVisibleSessionIdMatches({ cfg, store, p, sessionId, entryFilter }); const selection = resolveSessionIdMatchSelection(matches, sessionId); if (selection.kind === "none") { @@ -270,7 +270,9 @@ export async function resolveSessionKeyFromResolveParams(params: { ), }; } - const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { agentId: p.agentId }); + const { storePath, store } = loadCombinedSessionStoreForGatewayCore(cfg, { + agentId: p.agentId, + }); const matches = findVisibleShortIdMatches({ cfg, storePath, @@ -304,7 +306,7 @@ export async function resolveSessionKeyFromResolveParams(params: { }; } - const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { agentId: p.agentId }); + const { storePath, store } = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: p.agentId }); const list = listSessionsFromStore({ cfg, ...(entryFilter ? { entryFilter } : {}), diff --git a/src/gateway/talk-transcription-relay.test.ts b/src/gateway/talk-transcription-relay.test.ts index 0ecf1caea0e7..aa7617a378ea 100644 --- a/src/gateway/talk-transcription-relay.test.ts +++ b/src/gateway/talk-transcription-relay.test.ts @@ -17,7 +17,7 @@ import { sendTalkTranscriptionRelayAudio, stopTalkTranscriptionRelaySession, } from "./talk-transcription-relay.js"; -import { expectRecordFields, isRecord, requireRecord } from "./test-helpers.assertions.js"; +import { expectRecordFields, isRecord, requireGatewayRecord } from "./test-helpers.assertions.js"; type BroadcastEvent = { event: string; @@ -94,7 +94,7 @@ function findPayloadByType(events: BroadcastEvent[], type: string): Record { final: true, }); for (const { payload, opts } of events) { - const { type } = requireRecord(payload, "transcription relay event"); + const { type } = requireGatewayRecord(payload, "transcription relay event"); expect(opts, `${String(type)} delivery`).toEqual({ dropIfSlow: type === "partial" || type === "inputAudio", }); @@ -285,7 +285,7 @@ describe("talk transcription gateway relay", () => { await vi.advanceTimersByTimeAsync(1_000); const transcripts = events - .map((event) => requireRecord(event.payload, "transcription relay event")) + .map((event) => requireGatewayRecord(event.payload, "transcription relay event")) .filter( (payload) => isRecord(payload.talkEvent) && payload.talkEvent.type === "transcript.done", ); @@ -299,7 +299,7 @@ describe("talk transcription gateway relay", () => { await vi.advanceTimersByTimeAsync(4_000); const terminalEvents = events .map((event) => { - const payload = requireRecord(event.payload, "transcription relay event"); + const payload = requireGatewayRecord(event.payload, "transcription relay event"); return isRecord(payload.talkEvent) ? payload.talkEvent.type : undefined; }) .filter((type) => @@ -346,7 +346,7 @@ describe("talk transcription gateway relay", () => { }); const transcripts = events - .map((event) => requireRecord(event.payload, "transcription relay event")) + .map((event) => requireGatewayRecord(event.payload, "transcription relay event")) .filter( (payload) => isRecord(payload.talkEvent) && payload.talkEvent.type === "transcript.done", ); @@ -421,7 +421,7 @@ describe("talk transcription gateway relay", () => { request?.onTranscript?.("second final"); const updates = events - .map((event) => requireRecord(event.payload, "transcription relay event")) + .map((event) => requireGatewayRecord(event.payload, "transcription relay event")) .filter((payload) => typeof payload.text === "string" && payload.text) .map((payload) => ({ type: payload.type, text: payload.text })); expect(updates).toEqual([ diff --git a/src/gateway/test-helpers.assertions.ts b/src/gateway/test-helpers.assertions.ts index e9e9c4087b43..88d110d90778 100644 --- a/src/gateway/test-helpers.assertions.ts +++ b/src/gateway/test-helpers.assertions.ts @@ -6,7 +6,7 @@ import { expect } from "vitest"; export { isRecord }; /** Requires an unknown value to be a record and throws with a test label. */ -export function requireRecord(value: unknown, label: string): Record { +export function requireGatewayRecord(value: unknown, label: string): Record { expect(isRecord(value), `${label} must be an object`).toBe(true); return value as Record; } @@ -17,7 +17,7 @@ export function expectRecordFields( label: string, expected: Record, ): Record { - const record = requireRecord(value, label); + const record = requireGatewayRecord(value, label); for (const [key, expectedValue] of Object.entries(expected)) { expect(record[key], `${label}.${key}`).toEqual(expectedValue); } diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index a552a6d2bfb4..e705b4af3665 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -14,7 +14,7 @@ import { parseConfigJson5, resetConfigRuntimeState } from "../config/config.js"; import { resolveMainSessionKeyFromConfig, type SessionEntry } from "../config/sessions.js"; import { applySessionEntryLifecycleMutation, - listSessionEntries, + listSessionEntriesCore, replaceTranscriptEvents, } from "../config/sessions/session-accessor.js"; import { clearSessionStoreCacheForTest } from "../config/sessions/store-writer-state.js"; @@ -286,7 +286,7 @@ export async function writeSessionStore(params: { upsertsByAgentId.set(normalizeAgentId(params.agentId ?? DEFAULT_AGENT_ID), []); } for (const [agentId, upserts] of upsertsByAgentId) { - const removals = listSessionEntries({ agentId, storePath }).map(({ sessionKey }) => ({ + const removals = listSessionEntriesCore({ agentId, storePath }).map(({ sessionKey }) => ({ sessionKey, })); await applySessionEntryLifecycleMutation({ diff --git a/src/gateway/tool-resolution.ts b/src/gateway/tool-resolution.ts index 67fc77eccb1e..03ec79507141 100644 --- a/src/gateway/tool-resolution.ts +++ b/src/gateway/tool-resolution.ts @@ -26,7 +26,7 @@ import { collectExplicitDenylist, hasRestrictiveAllowPolicy, mergeAlsoAllowPolicy, - normalizeToolName, + normalizeToolPolicyName, replaceWithEffectiveToolAllowlist, resolveToolProfilePolicy, } from "../agents/tool-policy.js"; @@ -192,7 +192,9 @@ export function resolveGatewayScopedTools(params: { const sandboxPolicy = sandboxRuntime.sandboxed ? sandboxRuntime.toolPolicy : undefined; const excludedToolNames = params.excludeToolNames ? Array.from(params.excludeToolNames) : []; const mediatedToolNames = new Set( - Array.from(params.mediatedToolNames ?? [], (name) => normalizeToolName(name)).filter(Boolean), + Array.from(params.mediatedToolNames ?? [], (name) => normalizeToolPolicyName(name)).filter( + Boolean, + ), ); const gatewayToolsCfg = params.cfg.gateway?.tools; const defaultGatewayDeny = @@ -202,7 +204,7 @@ export function resolveGatewayScopedTools(params: { // normalize both sides so they still lift the matching default deny. (name) => !gatewayToolsCfg?.allow?.some( - (allowed) => normalizeToolName(allowed) === normalizeToolName(name), + (allowed) => normalizeToolPolicyName(allowed) === normalizeToolPolicyName(name), ), ) : []; @@ -394,7 +396,7 @@ export function resolveGatewayScopedTools(params: { // Once a name is server-minted as mediated, only the canonical coding // factory may supply it. A policy-filtered tool must not fall back to a // coincidentally named Gateway/plugin implementation. - ...baseTools.filter((tool) => !mediatedToolNames.has(normalizeToolName(tool.name))), + ...baseTools.filter((tool) => !mediatedToolNames.has(normalizeToolPolicyName(tool.name))), ...mediatedCodingTools, ]; const allTools = nodeExecDefaults diff --git a/src/gateway/worker-environments/live-event-projection.ts b/src/gateway/worker-environments/live-event-projection.ts index 5f403b4d15a0..9eeed7fe1f9b 100644 --- a/src/gateway/worker-environments/live-event-projection.ts +++ b/src/gateway/worker-environments/live-event-projection.ts @@ -4,7 +4,7 @@ import { sanitizeToolArgs, sanitizeToolResult, } from "../../agents/embedded-agent-subscribe.tools.js"; -import { normalizeToolName } from "../../agents/tool-policy.js"; +import { normalizeToolPolicyName } from "../../agents/tool-policy.js"; import { createTrajectoryRuntimeRecorder } from "../../trajectory/runtime.js"; export type WorkerLiveTrajectoryTarget = { @@ -23,7 +23,7 @@ export function prepareWorkerLiveEventData( if (event.kind !== "tool") { return payload; } - const toolName = normalizeToolName(event.payload.name); + const toolName = normalizeToolPolicyName(event.payload.name); payload.name = toolName; if (event.payload.phase === "start") { payload.args = sanitizeToolArgs(event.payload.args); diff --git a/src/gateway/worker-environments/session-target.ts b/src/gateway/worker-environments/session-target.ts index cad6fd115254..e9bb29510236 100644 --- a/src/gateway/worker-environments/session-target.ts +++ b/src/gateway/worker-environments/session-target.ts @@ -2,7 +2,7 @@ import type { SessionTranscriptWriteScope } from "../../config/sessions/session- import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { resolveSessionIdMatchSelection } from "../../sessions/session-id-resolution.js"; import { - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, resolveCanonicalSessionEntryFromStoreKeys, resolveGatewaySessionStoreTargetWithStore, } from "../session-utils.js"; @@ -25,7 +25,7 @@ export function resolveWorkerSessionTarget( cfg: OpenClawConfig, sessionId: string, ): ResolvedWorkerSessionTarget | undefined { - const { store } = loadCombinedSessionStoreForGateway(cfg); + const { store } = loadCombinedSessionStoreForGatewayCore(cfg); const matches = Object.entries(store).filter(([, entry]) => entry.sessionId === sessionId); const selection = resolveSessionIdMatchSelection(matches, sessionId); if (selection.kind !== "selected") { diff --git a/src/hooks/frontmatter.test.ts b/src/hooks/frontmatter.test.ts index 6c9e533fc52c..edb0cd0d12a5 100644 --- a/src/hooks/frontmatter.test.ts +++ b/src/hooks/frontmatter.test.ts @@ -2,7 +2,7 @@ import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; import { - parseFrontmatter, + parseHookFrontmatter, resolveOpenClawMetadata, resolveHookInvocationPolicy, } from "./frontmatter.js"; @@ -22,7 +22,7 @@ function requireOpenClawMetadata(metadata: OpenClawHookMetadata | undefined): Op return metadata; } -describe("parseFrontmatter", () => { +describe("parseHookFrontmatter", () => { it("parses single-line key-value pairs", () => { const content = `--- name: test-hook @@ -32,7 +32,7 @@ homepage: https://example.com # Test Hook `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("test-hook"); expect(result.description).toBe("A test hook"); expect(result.homepage).toBe("https://example.com"); @@ -40,7 +40,7 @@ homepage: https://example.com it("handles missing frontmatter", () => { const content = "# Just a markdown file"; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result).toStrictEqual({}); }); @@ -48,7 +48,7 @@ homepage: https://example.com const content = `--- name: broken `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result).toStrictEqual({}); }); @@ -67,7 +67,7 @@ metadata: # Session Memory Hook `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("session-memory"); expect(result.description).toBe("Save session context"); const metadata = requireString(result.metadata, "session-memory metadata"); @@ -94,7 +94,7 @@ metadata: } --- `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("command-logger"); const parsed = JSON.parse(requireString(result.metadata, "command-logger metadata")); @@ -110,7 +110,7 @@ name: simple-hook metadata: {"openclaw": {"events": ["test"]}} --- `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("simple-hook"); expect(result.metadata).toBe('{"openclaw": {"events": ["test"]}}'); }); @@ -129,7 +129,7 @@ metadata: enabled: true --- `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("mixed-hook"); expect(result.description).toBe("A hook with mixed values"); expect(result.homepage).toBe("https://example.com"); @@ -143,21 +143,21 @@ name: "quoted-name" description: 'single-quoted' --- `; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("quoted-name"); expect(result.description).toBe("single-quoted"); }); it("handles CRLF line endings", () => { const content = "---\r\nname: test\r\ndescription: crlf\r\n---\r\n"; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("test"); expect(result.description).toBe("crlf"); }); it("handles CR line endings", () => { const content = "---\rname: test\rdescription: cr\r---\r"; - const result = parseFrontmatter(content); + const result = parseHookFrontmatter(content); expect(result.name).toBe("test"); expect(result.description).toBe("cr"); }); @@ -270,7 +270,7 @@ metadata: # Session Memory Hook `; - const frontmatter = parseFrontmatter(content); + const frontmatter = parseHookFrontmatter(content); expect(frontmatter.name).toBe("session-memory"); expect(requireString(frontmatter.metadata, "session-memory metadata")).toContain( '"command:reset"', @@ -295,7 +295,7 @@ metadata: - command:new --- `; - const frontmatter = parseFrontmatter(content); + const frontmatter = parseHookFrontmatter(content); const openclaw = resolveOpenClawMetadata(frontmatter); expect(openclaw?.emoji).toBe("disk"); expect(openclaw?.events).toEqual(["command:new"]); diff --git a/src/hooks/frontmatter.ts b/src/hooks/frontmatter.ts index f1707c1c2ab0..c8f82a2cf237 100644 --- a/src/hooks/frontmatter.ts +++ b/src/hooks/frontmatter.ts @@ -21,7 +21,7 @@ import type { } from "./types.js"; /** Parse HOOK.md frontmatter into the generic hook frontmatter record. */ -export function parseFrontmatter(content: string): ParsedHookFrontmatter { +export function parseHookFrontmatter(content: string): ParsedHookFrontmatter { return parseFrontmatterBlock(content); } diff --git a/src/hooks/install.ts b/src/hooks/install.ts index dcc185199c7b..f6492cff2f91 100644 --- a/src/hooks/install.ts +++ b/src/hooks/install.ts @@ -16,7 +16,7 @@ import { PLUGIN_MANIFEST_FILENAME } from "../plugins/manifest.js"; import type { InstallPolicySource } from "../security/install-policy.js"; import { createLazyRuntimeModule } from "../shared/lazy-runtime.js"; import { CONFIG_DIR, resolveUserPath } from "../utils.js"; -import { parseFrontmatter } from "./frontmatter.js"; +import { parseHookFrontmatter } from "./frontmatter.js"; // HOOK.md is only parsed for frontmatter; a small cap prevents a malicious or // malformed hook package from OOMing the install path. @@ -363,7 +363,7 @@ async function resolveHookNameFromDir(hookDir: string): Promise { throw new Error(`HOOK.md missing in ${hookDir}`); } const { buffer } = await readRegularFile({ filePath: hookMdPath, maxBytes: HOOK_MD_MAX_BYTES }); - const frontmatter = parseFrontmatter(buffer.toString("utf-8")); + const frontmatter = parseHookFrontmatter(buffer.toString("utf-8")); return frontmatter.name || path.basename(hookDir); } diff --git a/src/hooks/workspace.ts b/src/hooks/workspace.ts index cfa91a6b87b7..1023f0b088a4 100644 --- a/src/hooks/workspace.ts +++ b/src/hooks/workspace.ts @@ -12,7 +12,7 @@ import { isPathInsideWithRealpath } from "../security/scan-paths.js"; import { CONFIG_DIR, resolveUserPath } from "../utils.js"; import { resolveBundledHooksDir } from "./bundled-dir.js"; import { - parseFrontmatter, + parseHookFrontmatter, resolveHookInvocationPolicy, resolveOpenClawMetadata, } from "./frontmatter.js"; @@ -82,7 +82,7 @@ function loadHookFromDir(params: { return null; } try { - const frontmatter = parseFrontmatter(content); + const frontmatter = parseHookFrontmatter(content); const name = frontmatter.name || params.nameHint || path.basename(params.hookDir); const description = frontmatter.description || ""; diff --git a/src/image-generation/live-test-helpers.test.ts b/src/image-generation/live-test-helpers.test.ts index 54c77369f661..6b882e8ef9a6 100644 --- a/src/image-generation/live-test-helpers.test.ts +++ b/src/image-generation/live-test-helpers.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { parseCaseFilter, - parseCsvFilter, + parseImageProviderFilter, parseProviderModelMap, redactLiveApiKey, resolveConfiguredLiveImageModels, @@ -12,9 +12,9 @@ import { describe("image-generation live-test helpers", () => { it("parses provider filters and treats empty/all as unfiltered", () => { - expect(parseCsvFilter()).toBeNull(); - expect(parseCsvFilter("all")).toBeNull(); - expect(parseCsvFilter(" openai , google ")).toEqual(new Set(["openai", "google"])); + expect(parseImageProviderFilter()).toBeNull(); + expect(parseImageProviderFilter("all")).toBeNull(); + expect(parseImageProviderFilter(" openai , google ")).toEqual(new Set(["openai", "google"])); }); it("parses live case filters and treats empty/all as unfiltered", () => { diff --git a/src/image-generation/live-test-helpers.ts b/src/image-generation/live-test-helpers.ts index 6716f87a6f85..7db05148396c 100644 --- a/src/image-generation/live-test-helpers.ts +++ b/src/image-generation/live-test-helpers.ts @@ -38,7 +38,7 @@ export function parseCaseFilter(raw?: string): Set | null { return values.length > 0 ? new Set(values) : null; } -export function parseCsvFilter(raw?: string): Set | null { +export function parseImageProviderFilter(raw?: string): Set | null { return parseLiveCsvFilter(raw, { lowercase: false }); } diff --git a/src/infra/heartbeat-runner-delivery.ts b/src/infra/heartbeat-runner-delivery.ts index 6a4036509b12..9433426e7f4b 100644 --- a/src/infra/heartbeat-runner-delivery.ts +++ b/src/infra/heartbeat-runner-delivery.ts @@ -6,7 +6,7 @@ import { replaceGenericExternalRunFailureText } from "../agents/failover/user-co import { copyReplyPayloadMetadata, getReplyPayloadMetadata } from "../auto-reply/reply-payload.js"; import { buildRecoverablePendingFinalDeliveryText } from "../auto-reply/reply/pending-final-delivery.js"; import { sendDurableMessageBatch } from "../channels/message/runtime.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { formatErrorMessage } from "./errors.js"; import { @@ -435,7 +435,7 @@ export async function finalizeHeartbeatOutcome(params: { const visibleSendSucceeded = send.status === "sent"; if (visibleSendSucceeded) { const hasHeartbeatText = Boolean(deliveryText.trim()); - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, (current, context) => { if (!context.existingEntry) { @@ -492,7 +492,7 @@ async function clearSatisfiedPendingFinalDelivery( prepared: PreparedHeartbeatRun, expectedText?: string, ) { - await patchSessionEntry( + await patchSessionEntryCore( { storePath: prepared.storePath, sessionKey: prepared.sessionKey }, (current, context) => { if (!context.existingEntry) { diff --git a/src/infra/heartbeat-runner-session.ts b/src/infra/heartbeat-runner-session.ts index df69e38c51bb..a111c2e413ff 100644 --- a/src/infra/heartbeat-runner-session.ts +++ b/src/infra/heartbeat-runner-session.ts @@ -5,7 +5,7 @@ import { resolveAgentMainSessionKey, } from "../config/sessions/main-session.js"; import { resolveStorePath } from "../config/sessions/paths.js"; -import { loadSessionEntry, patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { loadSessionEntry, patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isSubagentSessionKey, @@ -233,7 +233,7 @@ export async function restoreHeartbeatUpdatedAt(params: { if (entry.updatedAt === nextUpdatedAt) { return; } - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, (nextEntry, context) => { if (!context.existingEntry) { diff --git a/src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts b/src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts index 0dd2fd0eb27c..8cec1263b141 100644 --- a/src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts +++ b/src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import { runHeartbeatOnce, type HeartbeatDeps } from "./heartbeat-runner.js"; import { installHeartbeatRunnerTestRuntime } from "./heartbeat-runner.test-harness.js"; import { @@ -55,7 +55,7 @@ describe("runHeartbeatOnce clears stuck pendingFinalDelivery state once delivery sessionKey: string, patch: Record, ): Promise { - await patchSessionEntry({ storePath, sessionKey }, () => patch, { preserveActivity: true }); + await patchSessionEntryCore({ storePath, sessionKey }, () => patch, { preserveActivity: true }); } async function readEntry(storePath: string, sessionKey: string): Promise { diff --git a/src/infra/heartbeat-runner.structured-delivery.test.ts b/src/infra/heartbeat-runner.structured-delivery.test.ts index a20fb29bed83..fe2d9bbb1b94 100644 --- a/src/infra/heartbeat-runner.structured-delivery.test.ts +++ b/src/infra/heartbeat-runner.structured-delivery.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { setReplyPayloadMetadata } from "../auto-reply/reply-payload.js"; import type { OpenClawConfig } from "../config/config.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js"; @@ -144,7 +144,7 @@ describe("runHeartbeatOnce structured heartbeat delivery", () => { lastHeartbeatSentAt: previousSentAt, }); replySpy.mockImplementation(async () => { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, () => ({ pendingFinalDelivery: { diff --git a/src/infra/heartbeat-runner.test-utils.ts b/src/infra/heartbeat-runner.test-utils.ts index 58eb31c1ee38..14d1b8eb808b 100644 --- a/src/infra/heartbeat-runner.test-utils.ts +++ b/src/infra/heartbeat-runner.test-utils.ts @@ -5,7 +5,10 @@ import path from "node:path"; import { vi } from "vitest"; import { heartbeatRunnerTelegramPlugin } from "../../test/helpers/infra/heartbeat-runner-channel-plugins.js"; import { resolveMainSessionKey } from "../config/sessions.js"; -import { listSessionEntries, replaceSessionEntry } from "../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + replaceSessionEntry, +} from "../config/sessions/session-accessor.js"; import type { InternalSessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { writeCronJobScratch } from "../cron/scratch-store.js"; @@ -113,7 +116,7 @@ export function readSessionStoreForTest storePath: string, ): Record { return Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry as T]), + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry as T]), ); } diff --git a/src/infra/heartbeat-runner.tool-response.test.ts b/src/infra/heartbeat-runner.tool-response.test.ts index b1eb256dd738..d6dadebdaab2 100644 --- a/src/infra/heartbeat-runner.tool-response.test.ts +++ b/src/infra/heartbeat-runner.tool-response.test.ts @@ -17,7 +17,7 @@ import { recordReplyOperationAgentTurn } from "../auto-reply/reply/reply-operati import { resolveReplyOperationRunState } from "../auto-reply/reply/reply-operation-run-state.js"; import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; import type { OpenClawConfig } from "../config/config.js"; -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { deleteCronJobScratch, @@ -510,7 +510,7 @@ describe("runHeartbeatOnce heartbeat response tool", () => { const pendingText = `Original exec completion\n\n${warning}`; const sessionKey = await seedTelegramSession(storePath, cfg); replySpy.mockImplementation(async () => { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, () => ({ pendingFinalDelivery: { @@ -554,7 +554,7 @@ describe("runHeartbeatOnce heartbeat response tool", () => { const warning = "⚠️ Message failed"; const sessionKey = await seedTelegramSession(storePath, cfg); replySpy.mockImplementation(async () => { - await patchSessionEntry( + await patchSessionEntryCore( { storePath, sessionKey }, () => ({ pendingFinalDelivery: { diff --git a/src/infra/json-file.test.ts b/src/infra/json-file.test.ts index 2c75a95fb3ee..e72f26e2fef9 100644 --- a/src/infra/json-file.test.ts +++ b/src/infra/json-file.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { withTempDir } from "../test-helpers/temp-dir.js"; -import { loadJsonFile, saveJsonFile } from "./json-file.js"; +import { loadJsonFileThroughSymlink, saveJsonFile } from "./json-file.js"; const SAVED_PAYLOAD = { enabled: true, count: 2 }; const PREVIOUS_JSON = '{"enabled":false}\n'; @@ -45,8 +45,8 @@ async function withJsonSymlink( function expectSavedPayloadThroughSymlink(linkPath: string, targetPath: string) { expect(fs.lstatSync(linkPath).isSymbolicLink()).toBe(true); - expect(loadJsonFile(targetPath)).toEqual(SAVED_PAYLOAD); - expect(loadJsonFile(linkPath)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(targetPath)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(linkPath)).toEqual(SAVED_PAYLOAD); } describe("json-file helpers", () => { @@ -74,7 +74,7 @@ describe("json-file helpers", () => { ])("returns undefined for $name", async ({ setup }) => { await withJsonPath(({ pathname }) => { setup(pathname); - expect(loadJsonFile(pathname)).toBeUndefined(); + expect(loadJsonFileThroughSymlink(pathname)).toBeUndefined(); }); }); @@ -85,7 +85,7 @@ describe("json-file helpers", () => { const raw = fs.readFileSync(pathname, "utf8"); expect(raw.endsWith("\n")).toBe(true); - expect(loadJsonFile(pathname)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(pathname)).toEqual(SAVED_PAYLOAD); const fileMode = fs.statSync(pathname).mode & 0o777; const dirMode = fs.statSync(path.dirname(pathname)).mode & 0o777; @@ -111,7 +111,7 @@ describe("json-file helpers", () => { await withJsonPath(({ pathname }) => { setup(pathname); saveJsonFile(pathname, SAVED_PAYLOAD); - expect(loadJsonFile(pathname)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(pathname)).toEqual(SAVED_PAYLOAD); }); }); @@ -125,7 +125,7 @@ describe("json-file helpers", () => { const renameCall = renameSpy.mock.calls.find(([, target]) => target === pathname); expect(renameCall?.[0]).toMatch(new RegExp(`^${escapeRegExp(pathname)}\\..+\\.tmp$`)); expect(renameSpy).toHaveBeenCalledWith(renameCall?.[0], pathname); - expect(loadJsonFile(pathname)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(pathname)).toEqual(SAVED_PAYLOAD); }); }); @@ -195,7 +195,7 @@ describe("json-file helpers", () => { saveJsonFile(pathname, SAVED_PAYLOAD); expect(renameSpy).toHaveBeenCalled(); - expect(loadJsonFile(pathname)).toEqual(SAVED_PAYLOAD); + expect(loadJsonFileThroughSymlink(pathname)).toEqual(SAVED_PAYLOAD); expect(fs.readdirSync(root)).toEqual(["config.json"]); }); }); diff --git a/src/infra/json-file.ts b/src/infra/json-file.ts index 457ec4580e38..0dc73c2d0bae 100644 --- a/src/infra/json-file.ts +++ b/src/infra/json-file.ts @@ -35,7 +35,7 @@ export function saveJsonFile(pathname: string, data: unknown): void { } // oxlint-disable-next-line typescript-eslint/no-unnecessary-type-parameters -- legacy typed JSON loader alias. -export function loadJsonFile(pathname: string): T | undefined { +export function loadJsonFileThroughSymlink(pathname: string): T | undefined { const direct = tryReadJsonSync(pathname); if (direct !== null) { return direct; diff --git a/src/infra/question-reaction-runtime.ts b/src/infra/question-reaction-runtime.ts index 53ca7f952009..01d7d21b814f 100644 --- a/src/infra/question-reaction-runtime.ts +++ b/src/infra/question-reaction-runtime.ts @@ -1,5 +1,5 @@ // Converts eligible portable question buttons into numbered reaction choices. -import type { ReplyPayload } from "../auto-reply/reply-payload.js"; +import { readAskUserQuestionId, type ReplyPayload } from "../auto-reply/reply-payload.js"; import type { MessagePresentation } from "../interactive/payload.js"; import { renderMessagePresentationFallbackText } from "../interactive/payload.js"; import { @@ -17,17 +17,6 @@ type QuestionReactionBinding = { optionValues: string[]; }; -export function readAskUserQuestionId( - payload: Pick, -): string | undefined { - const askUser = payload.channelData?.askUser; - if (!askUser || typeof askUser !== "object" || Array.isArray(askUser)) { - return undefined; - } - const questionId = (askUser as { questionId?: unknown }).questionId; - return typeof questionId === "string" && questionId ? questionId : undefined; -} - export function readQuestionReactionBinding( payload: Pick, ): QuestionReactionBinding | undefined { diff --git a/src/infra/restart-coordinator.test.ts b/src/infra/restart-coordinator.test.ts index 6194f780183c..92e6913e9d75 100644 --- a/src/infra/restart-coordinator.test.ts +++ b/src/infra/restart-coordinator.test.ts @@ -6,7 +6,7 @@ import { } from "../process/gateway-work-admission.js"; import { createSafeGatewayRestartPreflight, - requestSafeGatewayRestart, + scheduleSafeGatewayRestart, } from "./restart-coordinator.js"; const scheduleGatewaySigusr1Restart = vi.hoisted(() => vi.fn()); @@ -34,7 +34,7 @@ afterEach(() => { describe("safe gateway restart coordinator", () => { const requestPreflight = ( - inspect: NonNullable[0]>["inspect"], + inspect: NonNullable[0]>["inspect"], ) => createSafeGatewayRestartPreflight(inspect); it("reports safe when no restart blockers are active", () => { @@ -194,7 +194,7 @@ describe("safe gateway restart coordinator", () => { cooldownMsApplied: 0, }); - const result = requestSafeGatewayRestart({ + const result = scheduleSafeGatewayRestart({ reason: "test.safe", inspect: { getQueueSize: () => 1, @@ -224,7 +224,7 @@ describe("safe gateway restart coordinator", () => { cooldownMsApplied: 0, }); - const result = requestSafeGatewayRestart({ + const result = scheduleSafeGatewayRestart({ inspect: { getQueueSize: () => 0, getPendingReplies: () => 0, @@ -249,7 +249,7 @@ describe("safe gateway restart coordinator", () => { cooldownMsApplied: 0, }); - const result = requestSafeGatewayRestart({ + const result = scheduleSafeGatewayRestart({ reason: "test.skip-deferral", skipDeferral: true, inspect: { @@ -283,7 +283,7 @@ describe("safe gateway restart coordinator", () => { cooldownMsApplied: 0, }); - requestSafeGatewayRestart({ + scheduleSafeGatewayRestart({ reason: "test.no-skip", inspect: { getQueueSize: () => 0, diff --git a/src/infra/restart-coordinator.ts b/src/infra/restart-coordinator.ts index 2ac563ebb83c..9f4da411f313 100644 --- a/src/infra/restart-coordinator.ts +++ b/src/infra/restart-coordinator.ts @@ -103,7 +103,7 @@ export function createSafeGatewayRestartPreflight( } /** Schedule a gateway restart after collecting tracked active-work blockers. */ -export function requestSafeGatewayRestart( +export function scheduleSafeGatewayRestart( opts: { reason?: string; delayMs?: number; diff --git a/src/infra/state-migrations.legacy-session-store.ts b/src/infra/state-migrations.legacy-session-store.ts index fa2ba3b0ea03..bc1c73ad54e8 100644 --- a/src/infra/state-migrations.legacy-session-store.ts +++ b/src/infra/state-migrations.legacy-session-store.ts @@ -57,7 +57,7 @@ import { import { writeTextAtomic } from "./json-files.js"; import { readSessionStoreJson5 } from "./state-migrations.fs.js"; -export type LegacySessionStoreLoadOptions = { +type LegacySessionStoreLoadOptions = { skipCache?: boolean; maintenanceConfig?: ResolvedSessionMaintenanceConfig; runMaintenance?: boolean; diff --git a/src/infra/state-migrations.legacy-sessions.ts b/src/infra/state-migrations.legacy-sessions.ts index f1a2a0757bb0..a81a31f956dd 100644 --- a/src/infra/state-migrations.legacy-sessions.ts +++ b/src/infra/state-migrations.legacy-sessions.ts @@ -16,7 +16,7 @@ import { emptyDirOrMissing, isAmbiguousSharedStoreKey, isLegacyDefaultMainAliasKey, - mergeSessionEntry, + selectNewerSessionEntry, normalizeSessionEntry, pickLatestLegacyDirectEntry, removeDirIfEmpty, @@ -166,7 +166,7 @@ export async function migrateLegacySessions( merged[key] = entry; } for (const [key, entry] of Object.entries(canonicalizedLegacy.store)) { - merged[key] = mergeSessionEntry({ + merged[key] = selectNewerSessionEntry({ existing: merged[key], incoming: entry, preferIncomingOnTie: false, diff --git a/src/infra/state-migrations.media-persistence.historical-v14.test.ts b/src/infra/state-migrations.media-persistence.historical-v14.test.ts index 95e9e701fac0..407d14756d3a 100644 --- a/src/infra/state-migrations.media-persistence.historical-v14.test.ts +++ b/src/infra/state-migrations.media-persistence.historical-v14.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { cleanupTempDirs, makeTempDir } from "../../test/helpers/temp-dir.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; import { registerOpenClawAgentDatabase } from "../state/openclaw-agent-db-registry.js"; import { closeOpenClawAgentDatabasesForTest, @@ -96,7 +96,7 @@ describe("legacy media persistence Doctor migration from historical v14", () => const result = migrateLegacyMediaPersistence({ env }); expect(result.warnings).toEqual([]); expect( - listSessionEntries({ agentId: "main", env }).map(({ entry, sessionKey }) => ({ + listSessionEntriesCore({ agentId: "main", env }).map(({ entry, sessionKey }) => ({ sessionId: entry.sessionId, sessionKey, })), diff --git a/src/infra/state-migrations.media-persistence.historical-v15.test.ts b/src/infra/state-migrations.media-persistence.historical-v15.test.ts index 66237f692601..bad90b2b548e 100644 --- a/src/infra/state-migrations.media-persistence.historical-v15.test.ts +++ b/src/infra/state-migrations.media-persistence.historical-v15.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { cleanupTempDirs, makeTempDir } from "../../test/helpers/temp-dir.js"; -import { listSessionEntries } from "../config/sessions/session-accessor.js"; +import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; import { registerOpenClawAgentDatabase } from "../state/openclaw-agent-db-registry.js"; import { closeOpenClawAgentDatabasesForTest, @@ -92,7 +92,7 @@ describe("legacy media persistence Doctor migration from historical v15", () => const result = migrateLegacyMediaPersistence({ env }); expect(result.warnings).toEqual([]); expect( - listSessionEntries({ agentId: "main", env }).map(({ entry, sessionKey }) => ({ + listSessionEntriesCore({ agentId: "main", env }).map(({ entry, sessionKey }) => ({ sessionId: entry.sessionId, sessionKey, })), diff --git a/src/infra/state-migrations.session-store.ts b/src/infra/state-migrations.session-store.ts index 35ead7184bb1..14ed447bbb4b 100644 --- a/src/infra/state-migrations.session-store.ts +++ b/src/infra/state-migrations.session-store.ts @@ -267,7 +267,7 @@ function resolveUpdatedAt(entry: SessionEntryLike): number { : 0; } -export function mergeSessionEntry(params: { +export function selectNewerSessionEntry(params: { existing: SessionEntryLike | undefined; incoming: SessionEntryLike; preferIncomingOnTie?: boolean; diff --git a/src/library.ts b/src/library.ts index 8d6168d89fd4..5fdfce5bdb9a 100644 --- a/src/library.ts +++ b/src/library.ts @@ -15,9 +15,7 @@ import { PortInUseError, } from "./infra/ports.js"; import { - loadLegacySessionStore, saveLegacySessionStore, - type LegacySessionStoreLoadOptions, type LegacySessionStoreSaveOptions, } from "./infra/state-migrations.legacy-session-store.js"; import type { monitorWebChannel as monitorWebChannelRuntime } from "./plugins/runtime/runtime-web-channel-plugin.js"; @@ -55,14 +53,7 @@ export const runCommandWithTimeout: RunCommandWithTimeout = async (...args) => export const monitorWebChannel: MonitorWebChannel = async (...args) => (await loadWebChannelRuntime()).monitorWebChannel(...args); -/** - * @deprecated Legacy sessions.json compatibility for package-root consumers. - * Use SQLite-backed session APIs. Remove after 2026-10-12, once the v2026.7.x - * upgrade window no longer requires the legacy doctor importer. - */ -export function loadSessionStore(storePath: string, options?: LegacySessionStoreLoadOptions) { - return loadLegacySessionStore(storePath, options); -} +export { loadLegacySessionStore as loadSessionStore } from "./infra/state-migrations.legacy-session-store.js"; /** * @deprecated Legacy sessions.json compatibility for package-root consumers. diff --git a/src/plugin-activation-boundary.test.ts b/src/plugin-activation-boundary.test.ts index 15ad143d10df..46128272d305 100644 --- a/src/plugin-activation-boundary.test.ts +++ b/src/plugin-activation-boundary.test.ts @@ -20,7 +20,7 @@ const testModelIdNormalization = { }, }; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn((params: { artifactBasename: string }) => { if (params.artifactBasename === "browser-host-inspection.js") { return { @@ -104,7 +104,7 @@ vi.mock("./secrets/channel-env-vars.js", () => ({ vi.mock("./plugin-sdk/facade-loader.js", () => ({ ...facadeMockHelpers, listImportedBundledPluginFacadeIds: () => [], - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, loadFacadeModuleAtLocationSync: vi.fn(), resetFacadeLoaderStateForTest: vi.fn(), })); @@ -114,15 +114,16 @@ vi.mock("./plugin-sdk/facade-runtime.js", () => ({ testing: {}, canLoadActivatedBundledPluginPublicSurface: () => true, listImportedBundledPluginFacadeIds: () => [], - loadActivatedBundledPluginPublicSurfaceModuleSync: loadBundledPluginPublicSurfaceModuleSync, - loadBundledPluginPublicSurfaceModuleSync, + loadActivatedBundledPluginPublicSurfaceModuleSync: loadBundledPluginPublicSurfaceModuleSyncCore, + loadBundledPluginPublicSurfaceModuleSyncCore, resetFacadeRuntimeStateForTest: vi.fn(), - tryLoadActivatedBundledPluginPublicSurfaceModuleSync: loadBundledPluginPublicSurfaceModuleSync, + tryLoadActivatedBundledPluginPublicSurfaceModuleSync: + loadBundledPluginPublicSurfaceModuleSyncCore, })); describe("plugin activation boundary", () => { it("keeps generic boundaries cold and loads only narrow browser helper surfaces on use", () => { - loadBundledPluginPublicSurfaceModuleSync.mockReset(); + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset(); expect(isStaticallyChannelConfigured({}, "telegram", { TELEGRAM_BOT_TOKEN: "token" })).toBe( true, @@ -152,11 +153,11 @@ describe("plugin activation boundary", () => { provider: "xai", model: "grok-4-fast", }); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); expect(parseBrowserMajorVersion("Google Chrome 144.0.7534.0")).toBe(144); expect( - loadBundledPluginPublicSurfaceModuleSync.mock.calls.map( + loadBundledPluginPublicSurfaceModuleSyncCore.mock.calls.map( ([params]) => params.artifactBasename, ), ).toEqual(["browser-host-inspection.js"]); diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index 8c46dab73f47..3fa5ec42e242 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -352,7 +352,8 @@ export async function detectAndLoadAgentHarnessPromptImages(params: { export async function loadCodexBundleMcpThreadConfig( params: LoadCodexBundleMcpThreadConfigParams, ): Promise { - const { loadCodexBundleMcpThreadConfig: load } = await import("../agents/codex-mcp-config.js"); + const { loadCodexBundleMcpThreadConfigCore: load } = + await import("../agents/codex-mcp-config.js"); return load(params); } @@ -407,12 +408,12 @@ export async function prepareHarnessNativeMcpAppPreview(params: { */ export async function materializeRequesterScopedMcpToolsForHarnessRun( params: Parameters< - typeof import("../agents/agent-bundle-mcp-harness.js").materializeRequesterScopedMcpToolsForHarnessRun + typeof import("../agents/agent-bundle-mcp-harness.js").materializeRequesterScopedMcpToolsForHarnessRunCore >[0], ): Promise< Awaited< ReturnType< - typeof import("../agents/agent-bundle-mcp-harness.js").materializeRequesterScopedMcpToolsForHarnessRun + typeof import("../agents/agent-bundle-mcp-harness.js").materializeRequesterScopedMcpToolsForHarnessRunCore > > > { @@ -420,7 +421,7 @@ export async function materializeRequesterScopedMcpToolsForHarnessRun( if (!shouldLoad) { return undefined; } - const { materializeRequesterScopedMcpToolsForHarnessRun: materialize } = + const { materializeRequesterScopedMcpToolsForHarnessRunCore: materialize } = await import("../agents/agent-bundle-mcp-harness.js"); return materialize(params); } diff --git a/src/plugin-sdk/anthropic-cli.ts b/src/plugin-sdk/anthropic-cli.ts index 663971af3736..db900710d185 100644 --- a/src/plugin-sdk/anthropic-cli.ts +++ b/src/plugin-sdk/anthropic-cli.ts @@ -1,5 +1,5 @@ // Manual facade. Keep loader boundary explicit. -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { CLAUDE_CLI_BACKEND_ID: string; @@ -9,7 +9,7 @@ type FacadeModule = { function loadFacadeModule(): FacadeModule { // cli-api.js, not api.js: this facade evaluates at module scope, and the // full barrel costs ~130s per cold jiti worker on source checkouts. - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "anthropic", artifactBasename: "cli-api.js", }); diff --git a/src/plugin-sdk/browser-control-auth.ts b/src/plugin-sdk/browser-control-auth.ts index 15a15b255517..46dba85a3c63 100644 --- a/src/plugin-sdk/browser-control-auth.ts +++ b/src/plugin-sdk/browser-control-auth.ts @@ -1,6 +1,6 @@ // Browser control auth helpers resolve plugin browser credentials from OpenClaw config. import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; /** Browser control credentials resolved from config, env, or generated setup state. */ export type BrowserControlAuth = { @@ -36,7 +36,7 @@ function loadBrowserControlAuthSurface(): BrowserControlAuthSurface { // Browser owns auth generation and env precedence; this SDK wrapper only keeps // the lazy public facade stable for plugin authors. cachedBrowserControlAuthSurface ??= - loadBundledPluginPublicSurfaceModuleSync({ + loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "browser", artifactBasename: "browser-control-auth.js", }); diff --git a/src/plugin-sdk/browser-facades.test.ts b/src/plugin-sdk/browser-facades.test.ts index 4e8c2d8b667a..bb7aec71122b 100644 --- a/src/plugin-sdk/browser-facades.test.ts +++ b/src/plugin-sdk/browser-facades.test.ts @@ -1,17 +1,17 @@ // Browser facade tests cover browser plugin facade loading and runtime API shape. import { beforeEach, describe, expect, it, vi } from "vitest"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); vi.mock("./facade-loader.js", () => ({ - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, })); describe("plugin-sdk browser facades", () => { beforeEach(() => { // Facade wrappers cache successful loads; each case needs a clean wrapper module. vi.resetModules(); - loadBundledPluginPublicSurfaceModuleSync.mockReset(); + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset(); }); it("delegates browser profile helpers to the browser facade", async () => { @@ -24,7 +24,7 @@ describe("plugin-sdk browser facades", () => { const resolveBrowserConfig = vi.fn().mockReturnValue(resolvedConfig); const resolveProfile = vi.fn().mockReturnValue(resolvedProfile); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ resolveBrowserConfig, resolveProfile, }); @@ -35,7 +35,7 @@ describe("plugin-sdk browser facades", () => { expect(browserProfiles.resolveBrowserConfig(cfg, rootConfig)).toBe(resolvedConfig); expect(browserProfiles.resolveProfile(resolvedConfig, "openclaw")).toBe(resolvedProfile); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-profiles.js", }); @@ -44,7 +44,7 @@ describe("plugin-sdk browser facades", () => { }); it("hard-fails when browser profile facade is unavailable", async () => { - loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + loadBundledPluginPublicSurfaceModuleSyncCore.mockImplementation(() => { throw new Error("missing browser profiles facade"); }); @@ -68,7 +68,7 @@ describe("plugin-sdk browser facades", () => { const resolveBrowserControlAuth = vi.fn().mockReturnValue(resolvedAuth); const shouldAutoGenerateBrowserAuth = vi.fn().mockReturnValue(true); const ensureBrowserControlAuth = vi.fn().mockResolvedValue(ensuredAuth); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ resolveBrowserControlAuth, shouldAutoGenerateBrowserAuth, ensureBrowserControlAuth, @@ -83,14 +83,14 @@ describe("plugin-sdk browser facades", () => { expect(controlAuth.resolveBrowserControlAuth(cfg, env)).toBe(resolvedAuth); expect(controlAuth.shouldAutoGenerateBrowserAuth(env)).toBe(true); await expect(controlAuth.ensureBrowserControlAuth({ cfg, env })).resolves.toEqual(ensuredAuth); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "browser-control-auth.js", }); }); it("hard-fails when browser control auth facade is unavailable", async () => { - loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + loadBundledPluginPublicSurfaceModuleSyncCore.mockImplementation(() => { throw new Error("missing browser control auth facade"); }); diff --git a/src/plugin-sdk/browser-host-inspection.test.ts b/src/plugin-sdk/browser-host-inspection.test.ts index d7b657fc2a98..d8cb6e9e6dcd 100644 --- a/src/plugin-sdk/browser-host-inspection.test.ts +++ b/src/plugin-sdk/browser-host-inspection.test.ts @@ -6,17 +6,17 @@ import { mockBrowserHostInspectionFacade, } from "./browser-facade-test-helpers.js"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); vi.mock("./facade-loader.js", () => ({ - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, })); describe("browser host inspection", () => { beforeEach(() => { // Facade wrappers cache successful loads; each case needs a clean wrapper module. vi.resetModules(); - loadBundledPluginPublicSurfaceModuleSync.mockReset(); + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset(); }); it("delegates browser host inspection helpers through the browser facade", async () => { @@ -24,18 +24,20 @@ describe("browser host inspection", () => { kind: "canary", path: "/usr/bin/google-chrome-beta", }; - mockBrowserHostInspectionFacade(loadBundledPluginPublicSurfaceModuleSync, executable); + mockBrowserHostInspectionFacade(loadBundledPluginPublicSurfaceModuleSyncCore, executable); const hostInspection = await import("./browser-host-inspection.js"); expectBrowserHostInspectionDelegation({ executable, hostInspection, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSync: loadBundledPluginPublicSurfaceModuleSyncCore, }); }); it("hard-fails when browser host inspection facade is unavailable", async () => { - await expectBrowserHostInspectionFacadeUnavailable(loadBundledPluginPublicSurfaceModuleSync); + await expectBrowserHostInspectionFacadeUnavailable( + loadBundledPluginPublicSurfaceModuleSyncCore, + ); }); }); diff --git a/src/plugin-sdk/browser-host-inspection.ts b/src/plugin-sdk/browser-host-inspection.ts index 57f2c3d114df..6edca6e317fd 100644 --- a/src/plugin-sdk/browser-host-inspection.ts +++ b/src/plugin-sdk/browser-host-inspection.ts @@ -1,7 +1,7 @@ /** * Public SDK facade for browser executable lookup and browser version inspection. */ -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; /** Browser executable candidate discovered on the host platform. */ export type BrowserExecutable = { @@ -19,7 +19,7 @@ let cachedBrowserHostInspectionSurface: BrowserHostInspectionSurface | undefined function loadBrowserHostInspectionSurface(): BrowserHostInspectionSurface { cachedBrowserHostInspectionSurface ??= - loadBundledPluginPublicSurfaceModuleSync({ + loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "browser", artifactBasename: "browser-host-inspection.js", }); diff --git a/src/plugin-sdk/browser-profiles.ts b/src/plugin-sdk/browser-profiles.ts index f21ad37970af..6e7a62721a15 100644 --- a/src/plugin-sdk/browser-profiles.ts +++ b/src/plugin-sdk/browser-profiles.ts @@ -6,7 +6,7 @@ import type { BrowserConfig } from "../config/types.browser.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import type { ResolvedBrowserConfig, ResolvedBrowserProfile } from "./browser-types.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; export type { ResolvedBrowserConfig, ResolvedBrowserProfile, @@ -44,12 +44,11 @@ type BrowserProfilesSurface = { let cachedBrowserProfilesSurface: BrowserProfilesSurface | undefined; function loadBrowserProfilesSurface(): BrowserProfilesSurface { - cachedBrowserProfilesSurface ??= loadBundledPluginPublicSurfaceModuleSync( - { + cachedBrowserProfilesSurface ??= + loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "browser", artifactBasename: "browser-profiles.js", - }, - ); + }); return cachedBrowserProfilesSurface; } diff --git a/src/plugin-sdk/bundled-channel-config-schema.ts b/src/plugin-sdk/bundled-channel-config-schema.ts index cf169088b048..dc4907f27f87 100644 --- a/src/plugin-sdk/bundled-channel-config-schema.ts +++ b/src/plugin-sdk/bundled-channel-config-schema.ts @@ -10,7 +10,7 @@ import { z, type ZodObject, type ZodOptional, type ZodType } from "zod"; import type { OpenClawConfig } from "./config-contracts.js"; import { createLazyFacadeObjectValue, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, } from "./facade-loader.js"; export { @@ -66,7 +66,7 @@ function loadBundledConfigSchema( dirName: string, exportName: string, ): BundledObjectConfigSchema { - const schema = loadBundledPluginPublicSurfaceModuleSync>({ + const schema = loadBundledPluginPublicSurfaceModuleSyncCore>({ dirName, artifactBasename: "config-api.js", })[exportName]; diff --git a/src/plugin-sdk/codex-mcp-projection.ts b/src/plugin-sdk/codex-mcp-projection.ts index 52b81b77f98b..79b02ad6bb69 100644 --- a/src/plugin-sdk/codex-mcp-projection.ts +++ b/src/plugin-sdk/codex-mcp-projection.ts @@ -21,10 +21,10 @@ export { /** Materialize static configured MCP under a scheduled Codex authority envelope. */ export async function materializeStaticMcpToolsForScheduledHarnessRun( params: Parameters< - typeof import("../agents/agent-bundle-mcp-harness.js").materializeStaticMcpToolsForScheduledHarnessRun + typeof import("../agents/agent-bundle-mcp-harness.js").materializeStaticMcpToolsForScheduledHarnessRunCore >[0], ) { - const { materializeStaticMcpToolsForScheduledHarnessRun: materialize } = + const { materializeStaticMcpToolsForScheduledHarnessRunCore: materialize } = await import("../agents/agent-bundle-mcp-harness.js"); return materialize(params); } diff --git a/src/plugin-sdk/discord.test.ts b/src/plugin-sdk/discord.test.ts index 5b48ff226e79..c36ec21688ed 100644 --- a/src/plugin-sdk/discord.test.ts +++ b/src/plugin-sdk/discord.test.ts @@ -75,7 +75,7 @@ const mocks = vi.hoisted(() => { componentEditResult, runtimeModule, runtimeConfig, - loadBundledPluginPublicSurfaceModuleSync: vi.fn((params: { artifactBasename: string }) => { + loadBundledPluginPublicSurfaceModuleSyncCore: vi.fn((params: { artifactBasename: string }) => { if (params.artifactBasename === "runtime-api.js") { return runtimeModule; } @@ -94,7 +94,7 @@ vi.mock("./facade-loader.js", () => ({ }, }, ), - loadBundledPluginPublicSurfaceModuleSync: mocks.loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore: mocks.loadBundledPluginPublicSurfaceModuleSyncCore, })); vi.mock("./runtime-config-snapshot.js", () => ({ diff --git a/src/plugin-sdk/discord.ts b/src/plugin-sdk/discord.ts index ea6595618c5c..c2b607e00ec4 100644 --- a/src/plugin-sdk/discord.ts +++ b/src/plugin-sdk/discord.ts @@ -13,7 +13,7 @@ import type { MessageReceipt } from "./channel-outbound.js"; import type { OpenClawConfig } from "./config-contracts.js"; import { createLazyFacadeObjectValue, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, } from "./facade-loader.js"; import { getRuntimeConfig, getRuntimeConfigSnapshot } from "./runtime-config-snapshot.js"; @@ -198,14 +198,14 @@ type DiscordRuntimeFacadeModule = { }; function loadDiscordApiFacadeModule(): DiscordApiFacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "discord", artifactBasename: "api.js", }); } function loadDiscordRuntimeFacadeModule(): DiscordRuntimeFacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "discord", artifactBasename: "runtime-api.js", }); diff --git a/src/plugin-sdk/facade-loader.test.ts b/src/plugin-sdk/facade-loader.test.ts index b754ff2d63f1..fc3ea8ddd783 100644 --- a/src/plugin-sdk/facade-loader.test.ts +++ b/src/plugin-sdk/facade-loader.test.ts @@ -11,7 +11,7 @@ import { listImportedBundledPluginFacadeIds, loadFacadeModuleAtLocationSync, loadBundledPluginPublicSurfaceModule, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, MissingPublicSurfaceError, resetFacadeLoaderStateForTest, setFacadeLoaderSourceTransformFactoryForTest, @@ -178,11 +178,11 @@ function createCircularPluginFixture(prefix: string): TrustedBundledPluginFixtur fs.writeFileSync( path.join(pluginRoot, "facade.mjs"), [ - `const loadBundledPluginPublicSurfaceModuleSync = globalThis.${FACADE_LOADER_GLOBAL};`, - `if (typeof loadBundledPluginPublicSurfaceModuleSync !== "function") {`, + `const loadBundledPluginPublicSurfaceModuleSyncCore = globalThis.${FACADE_LOADER_GLOBAL};`, + `if (typeof loadBundledPluginPublicSurfaceModuleSyncCore !== "function") {`, ' throw new Error("missing facade loader test loader");', "}", - `export const marker = loadBundledPluginPublicSurfaceModuleSync({ dirName: ${JSON.stringify( + `export const marker = loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: ${JSON.stringify( pluginId, )}, artifactBasename: "api.js" }).marker;`, "", @@ -261,14 +261,14 @@ describe("plugin-sdk facade loader", () => { }); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideA.bundledPluginsDir; - const fromA = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + const fromA = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: pluginId, artifactBasename: "api.js", }); expect(fromA.marker).toBe("override-a"); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideB.bundledPluginsDir; - const fromB = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + const fromB = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: pluginId, artifactBasename: "api.js", }); @@ -282,7 +282,7 @@ describe("plugin-sdk facade loader", () => { }); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createTempDirSync("openclaw-facade-loader-empty-"); - const loaded = loadBundledPluginPublicSurfaceModuleSync<{ + const loaded = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string; }>({ dirName: fixture.pluginId, @@ -297,7 +297,7 @@ describe("plugin-sdk facade loader", () => { delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; const error = captureThrownError(() => - loadBundledPluginPublicSurfaceModuleSync({ + loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "browser", artifactBasename: "browser-maintenance.js", }), @@ -356,11 +356,11 @@ describe("plugin-sdk facade loader", () => { }); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = fixture.bundledPluginsDir; - const first = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + const first = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }); - const second = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + const second = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }); @@ -390,7 +390,7 @@ describe("plugin-sdk facade loader", () => { withMockedWindowsPlatform(() => { try { expect( - loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }).marker, @@ -406,9 +406,9 @@ describe("plugin-sdk facade loader", () => { const fixture = createCircularPluginFixture("openclaw-facade-loader-circular-"); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = fixture.bundledPluginsDir; (globalThis as typeof globalThis & Record)[FACADE_LOADER_GLOBAL] = - loadBundledPluginPublicSurfaceModuleSync; + loadBundledPluginPublicSurfaceModuleSyncCore; - const loaded = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + const loaded = loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }); @@ -421,7 +421,7 @@ describe("plugin-sdk facade loader", () => { process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = fixture.bundledPluginsDir; expect(() => - loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }), @@ -430,7 +430,7 @@ describe("plugin-sdk facade loader", () => { expect(listImportedBundledPluginFacadeIds()).toStrictEqual([]); expect(() => - loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({ + loadBundledPluginPublicSurfaceModuleSyncCore<{ marker: string }>({ dirName: fixture.pluginId, artifactBasename: "api.js", }), diff --git a/src/plugin-sdk/facade-loader.ts b/src/plugin-sdk/facade-loader.ts index 3d8134543f84..8a5551565062 100644 --- a/src/plugin-sdk/facade-loader.ts +++ b/src/plugin-sdk/facade-loader.ts @@ -212,7 +212,7 @@ export function loadFacadeModuleAtLocationSync(params: { /** Resolve and synchronously load a bundled plugin public surface by plugin dir and artifact name. */ // oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic facade loaders use caller-supplied module surface types. -export function loadBundledPluginPublicSurfaceModuleSync(params: { +export function loadBundledPluginPublicSurfaceModuleSyncCore(params: { dirName: string; artifactBasename: string; trackedPluginId?: string | (() => string); diff --git a/src/plugin-sdk/facade-runtime.ts b/src/plugin-sdk/facade-runtime.ts index 1a6a7b07e04e..64010d25ceec 100644 --- a/src/plugin-sdk/facade-runtime.ts +++ b/src/plugin-sdk/facade-runtime.ts @@ -11,7 +11,7 @@ import { } from "../plugins/plugin-module-loader-cache.js"; import { resolveLoaderPackageRoot } from "../plugins/sdk-alias.js"; import { - loadBundledPluginPublicSurfaceModuleSync as loadBundledPluginPublicSurfaceModuleSyncLight, + loadBundledPluginPublicSurfaceModuleSyncCore as loadBundledPluginPublicSurfaceModuleSyncLight, loadFacadeModuleAtLocationSync as loadFacadeModuleAtLocationSyncShared, resetFacadeLoaderStateForTest, type FacadeModuleLocation, diff --git a/src/plugin-sdk/feishu-security.ts b/src/plugin-sdk/feishu-security.ts index bb0620680b27..7134b9947bed 100644 --- a/src/plugin-sdk/feishu-security.ts +++ b/src/plugin-sdk/feishu-security.ts @@ -1,14 +1,14 @@ // Manual facade. Keep loader boundary explicit. import type { OpenClawConfig } from "../config/types.js"; import type { SecurityAuditFinding } from "../security/audit.types.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type SecuritySurface = { collectFeishuSecurityAuditFindings: (params: { cfg: OpenClawConfig }) => SecurityAuditFinding[]; }; function loadSecuritySurface(): SecuritySurface { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "feishu", artifactBasename: "security-contract-api.js", }); diff --git a/src/plugin-sdk/github-copilot-login.ts b/src/plugin-sdk/github-copilot-login.ts index 113befd57b84..88f90b2e6d2f 100644 --- a/src/plugin-sdk/github-copilot-login.ts +++ b/src/plugin-sdk/github-copilot-login.ts @@ -1,6 +1,6 @@ // Manual facade. Keep loader boundary explicit. import type { RuntimeEnv } from "../runtime.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { githubCopilotLoginCommand: ( @@ -10,7 +10,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "github-copilot", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/litellm.ts b/src/plugin-sdk/litellm.ts index 2e8b10bf79b2..923cb1c0455c 100644 --- a/src/plugin-sdk/litellm.ts +++ b/src/plugin-sdk/litellm.ts @@ -1,6 +1,6 @@ // Manual facade. Keep loader boundary explicit. import type { ModelDefinitionConfig, OpenClawConfig } from "../config/types.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { applyLitellmConfig: (cfg: OpenClawConfig) => OpenClawConfig; @@ -12,7 +12,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "litellm", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/matrix-deps.ts b/src/plugin-sdk/matrix-deps.ts index f4583241fba7..88c2a946b634 100644 --- a/src/plugin-sdk/matrix-deps.ts +++ b/src/plugin-sdk/matrix-deps.ts @@ -1,6 +1,6 @@ // Manual facade. Keep loader boundary explicit. import type { RuntimeEnv } from "../runtime.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { ensureMatrixSdkInstalled: (params: { @@ -11,7 +11,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "matrix", artifactBasename: "runtime-api.js", }); diff --git a/src/plugin-sdk/memory-core-bundled-runtime.test.ts b/src/plugin-sdk/memory-core-bundled-runtime.test.ts index f8eca2aa0607..ccf872dcd73c 100644 --- a/src/plugin-sdk/memory-core-bundled-runtime.test.ts +++ b/src/plugin-sdk/memory-core-bundled-runtime.test.ts @@ -3,7 +3,7 @@ */ import { beforeEach, describe, expect, it, vi } from "vitest"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); const configureMemoryCoreDreamingStateImpl = vi.hoisted(() => vi.fn()); const createEmbeddingProviderImpl = vi.hoisted(() => vi.fn()); const removeGroundedShortTermCandidatesImpl = vi.hoisted(() => vi.fn()); @@ -22,7 +22,7 @@ vi.mock("./facade-loader.js", async () => { const actual = await vi.importActual("./facade-loader.js"); return { ...actual, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, }; }); @@ -41,7 +41,7 @@ describe("plugin-sdk memory-core bundled runtime", () => { removeBackfillDiaryEntriesImpl.mockReset().mockResolvedValue({ removedCount: 1 }); filterRecallEntriesWithinLookbackImpl.mockReset().mockReturnValue([]); previewRemHarnessImpl.mockReset().mockResolvedValue({ ok: true }); - loadBundledPluginPublicSurfaceModuleSync + loadBundledPluginPublicSurfaceModuleSyncCore .mockReset() .mockImplementation(({ artifactBasename }) => { if (artifactBasename === "runtime-api.js") { @@ -73,9 +73,9 @@ describe("plugin-sdk memory-core bundled runtime", () => { it("keeps the bundled memory facade cold until a helper is used", async () => { const module = await import("./memory-core-bundled-runtime.js"); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); await module.createEmbeddingProvider({} as never); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "runtime-api.js", }); @@ -103,11 +103,11 @@ describe("plugin-sdk memory-core bundled runtime", () => { expect(auditShortTermPromotionArtifactsImpl).toHaveBeenCalledWith({} as never); expect(repairDreamingArtifactsImpl).toHaveBeenCalledWith({} as never); expect(repairShortTermPromotionArtifactsImpl).toHaveBeenCalledWith({} as never); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "api.js", }); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "runtime-api.js", }); @@ -123,7 +123,7 @@ describe("plugin-sdk memory-core bundled runtime", () => { expect(result).toBe(kept); expect(filterRecallEntriesWithinLookbackImpl).toHaveBeenCalledWith(params); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "api.js", }); @@ -140,7 +140,7 @@ describe("plugin-sdk memory-core bundled runtime", () => { expect(result).toBe(preview); expect(previewRemHarnessImpl).toHaveBeenCalledWith(params); expect(configureMemoryCoreDreamingStateImpl).toHaveBeenCalledWith(expect.any(Function)); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "memory-core", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/memory-core-bundled-runtime.ts b/src/plugin-sdk/memory-core-bundled-runtime.ts index 21d7fd31b0dc..46ec1104c92b 100644 --- a/src/plugin-sdk/memory-core-bundled-runtime.ts +++ b/src/plugin-sdk/memory-core-bundled-runtime.ts @@ -3,7 +3,7 @@ import { createConfiguredProviderLocalServiceAcquirer } from "../agents/provider import { getRuntimeConfig } from "../config/config.js"; import { createPluginStateKeyedStore } from "../plugin-state/plugin-state-store.js"; // Memory core bundled runtime helpers load the internal memory plugin through SDK facades. -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; import type { MemoryEmbeddingProvider, MemoryEmbeddingProviderCreateOptions, @@ -285,7 +285,7 @@ export type RepairDreamingArtifactsResult = { }; function loadApiFacadeModule(): ApiFacadeModule { - const module = loadBundledPluginPublicSurfaceModuleSync({ + const module = loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "memory-core", artifactBasename: "api.js", }); @@ -296,7 +296,7 @@ function loadApiFacadeModule(): ApiFacadeModule { } function loadRuntimeFacadeModule(): RuntimeFacadeModule { - const module = loadBundledPluginPublicSurfaceModuleSync({ + const module = loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "memory-core", artifactBasename: "runtime-api.js", }); diff --git a/src/plugin-sdk/openrouter.ts b/src/plugin-sdk/openrouter.ts index 7c2d46fccda9..53e287705dcb 100644 --- a/src/plugin-sdk/openrouter.ts +++ b/src/plugin-sdk/openrouter.ts @@ -1,6 +1,6 @@ // Manual facade. Keep loader boundary explicit. import type { ModelProviderConfig, OpenClawConfig } from "../config/types.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { applyOpenrouterConfig: (cfg: OpenClawConfig) => OpenClawConfig; @@ -10,7 +10,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "openrouter", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/provider-auth-copilot-cache.ts b/src/plugin-sdk/provider-auth-copilot-cache.ts index bcb1c05e761d..8211787284ff 100644 --- a/src/plugin-sdk/provider-auth-copilot-cache.ts +++ b/src/plugin-sdk/provider-auth-copilot-cache.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { asDateTimestampMs } from "../../packages/normalization-core/src/number-coercion.js"; import { COPILOT_INTEGRATION_ID } from "../agents/copilot-dynamic-headers.js"; import { resolveStateDir } from "../config/paths.js"; -import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; +import { loadJsonFileThroughSymlink, saveJsonFile } from "../infra/json-file.js"; import { DEFAULT_GITHUB_COPILOT_DOMAIN } from "./github-copilot-domain.js"; const COPILOT_CACHE_NAMESPACE = "github-copilot-token"; @@ -83,7 +83,7 @@ export async function resolveCopilotTokenCache(params: { // Kept only for the deprecated public helper's explicit cache adapters. // Normal runtime state uses the bounded SQLite namespace below. const cachePath = params.cachePath?.trim() || resolveLegacyCopilotTokenCachePath(params.env); - const loadJsonFileFn = params.loadJsonFileImpl ?? loadJsonFile; + const loadJsonFileFn = params.loadJsonFileImpl ?? loadJsonFileThroughSymlink; const saveJsonFileFn = params.saveJsonFileImpl ?? saveJsonFile; return { path: cachePath, diff --git a/src/plugin-sdk/qa-channel-protocol.ts b/src/plugin-sdk/qa-channel-protocol.ts index 638f7d90a6f0..32b1753e1704 100644 --- a/src/plugin-sdk/qa-channel-protocol.ts +++ b/src/plugin-sdk/qa-channel-protocol.ts @@ -26,7 +26,7 @@ function buildQaTargetCore(params: { export { buildQaTargetCore as buildQaTarget }; /** Parse the lowercase, prefix-scoped target grammar shared by QA Channel and QA Lab. */ -export function parseQaTarget( +function parseQaTargetCore( raw: string, options?: { defaultChatType?: QaBusConversationKind }, ): QaTargetParts { @@ -74,6 +74,8 @@ export function parseQaTarget( }; } +export { parseQaTargetCore as parseQaTarget }; + /** Addressable conversation used by QA bus messages and thread state. */ export type QaBusConversation = { id: string; diff --git a/src/plugin-sdk/qa-channel.test.ts b/src/plugin-sdk/qa-channel.test.ts index 82e505aff8ec..dc5d5fc7eac4 100644 --- a/src/plugin-sdk/qa-channel.test.ts +++ b/src/plugin-sdk/qa-channel.test.ts @@ -1,7 +1,7 @@ // QA channel tests cover QA channel runtime behavior and mocked message delivery. import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); const buildQaTargetImpl = vi.hoisted(() => vi.fn()); const pollQaBusImpl = vi.hoisted(() => vi.fn()); @@ -9,7 +9,7 @@ vi.mock("./facade-loader.js", async () => { const actual = await vi.importActual("./facade-loader.js"); return { ...actual, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, }; }); @@ -17,10 +17,10 @@ describe("plugin-sdk qa-channel", () => { beforeEach(() => { buildQaTargetImpl.mockReset(); pollQaBusImpl.mockReset(); - loadBundledPluginPublicSurfaceModuleSync.mockReset(); + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset(); buildQaTargetImpl.mockReturnValue("qa://main"); pollQaBusImpl.mockResolvedValue({ cursor: 1, events: [] }); - loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ buildQaTarget: buildQaTargetImpl, pollQaBus: pollQaBusImpl, qaChannelPlugin: { id: "qa-channel" }, @@ -30,9 +30,9 @@ describe("plugin-sdk qa-channel", () => { it("keeps the qa facade cold until a value is used", async () => { const module = await import("./qa-channel.js"); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); expect(module.qaChannelPlugin.id).toBe("qa-channel"); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledTimes(1); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledTimes(1); }); it("delegates qa helpers through the bundled public surface", async () => { @@ -42,7 +42,7 @@ describe("plugin-sdk qa-channel", () => { expect(buildQaTarget(input)).toBe("qa://main"); expect(formatQaTarget(input)).toBe("qa://main"); expect(buildQaTargetImpl).toHaveBeenCalledTimes(2); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "qa-channel", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/qa-channel.ts b/src/plugin-sdk/qa-channel.ts index c9de46998438..ed00b9454948 100644 --- a/src/plugin-sdk/qa-channel.ts +++ b/src/plugin-sdk/qa-channel.ts @@ -2,7 +2,7 @@ import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; import { createLazyFacadeObjectValue, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, } from "./facade-loader.js"; import type { QaBusAttachment, @@ -92,7 +92,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "qa-channel", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/qa-lab.test.ts b/src/plugin-sdk/qa-lab.test.ts index 0242dcc088f9..e899924ff88a 100644 --- a/src/plugin-sdk/qa-lab.test.ts +++ b/src/plugin-sdk/qa-lab.test.ts @@ -3,7 +3,7 @@ */ import { beforeEach, describe, expect, it, vi } from "vitest"; -const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const loadBundledPluginPublicSurfaceModuleSyncCore = vi.hoisted(() => vi.fn()); const registerQaLabCliImpl = vi.hoisted(() => vi.fn()); const isQaLabCliAvailableImpl = vi.hoisted(() => vi.fn()); @@ -11,7 +11,7 @@ vi.mock("./facade-loader.js", async () => { const actual = await vi.importActual("./facade-loader.js"); return { ...actual, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, }; }); @@ -19,7 +19,7 @@ describe("plugin-sdk qa-lab", () => { beforeEach(() => { registerQaLabCliImpl.mockReset(); isQaLabCliAvailableImpl.mockReset().mockReturnValue(true); - loadBundledPluginPublicSurfaceModuleSync.mockReset().mockReturnValue({ + loadBundledPluginPublicSurfaceModuleSyncCore.mockReset().mockReturnValue({ isQaLabCliAvailable: isQaLabCliAvailableImpl, registerQaLabCli: registerQaLabCliImpl, }); @@ -28,9 +28,9 @@ describe("plugin-sdk qa-lab", () => { it("keeps the qa-lab facade cold until used", async () => { const module = await import("./qa-lab.js"); - expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + expect(loadBundledPluginPublicSurfaceModuleSyncCore).not.toHaveBeenCalled(); module.registerQaLabCli({} as never); - expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "qa-lab", artifactBasename: "cli.js", }); @@ -51,7 +51,7 @@ describe("plugin-sdk qa-lab", () => { }); it("reports qa-lab unavailable when private facade artifacts are not packed", async () => { - loadBundledPluginPublicSurfaceModuleSync.mockImplementation(() => { + loadBundledPluginPublicSurfaceModuleSyncCore.mockImplementation(() => { throw new Error("Unable to resolve bundled plugin public surface qa-lab/cli.js"); }); const module = await import("./qa-lab.js"); diff --git a/src/plugin-sdk/qa-lab.ts b/src/plugin-sdk/qa-lab.ts index d89b7978e176..5b1fce83b6da 100644 --- a/src/plugin-sdk/qa-lab.ts +++ b/src/plugin-sdk/qa-lab.ts @@ -1,5 +1,5 @@ // Manual facade. Keep loader boundary explicit. -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { isQaLabCliAvailable: () => boolean; @@ -7,7 +7,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "qa-lab", artifactBasename: "cli.js", }); diff --git a/src/plugin-sdk/qa-runtime.ts b/src/plugin-sdk/qa-runtime.ts index bb302c452141..166a54c107dd 100644 --- a/src/plugin-sdk/qa-runtime.ts +++ b/src/plugin-sdk/qa-runtime.ts @@ -1,8 +1,7 @@ import { createServer } from "node:net"; // QA runtime helpers register and execute plugin QA scenarios from local files. -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js"; -import { resolvePrivateQaBundledPluginsEnv } from "./private-qa-bundled-env.js"; import { runExec } from "./process-runtime.js"; +import { loadQaRuntimeModule as loadQaRunnerRuntimeModule } from "./qa-runner-runtime.js"; import { fetchWithSsrFGuard } from "./ssrf-runtime.js"; import { normalizeStringEntries } from "./string-coerce-runtime.js"; @@ -62,19 +61,12 @@ function isMissingQaRuntimeError(error: unknown) { ); } -/** Load the bundled QA lab runtime surface, throwing when the private bundle is absent. */ -export function loadQaRuntimeModule(): QaRuntimeSurface { - const env = resolvePrivateQaBundledPluginsEnv(); - return loadBundledPluginPublicSurfaceModuleSync({ - dirName: "qa-lab", - artifactBasename: "runtime-api.js", - ...(env ? { env } : {}), - }); -} +const loadQaLabRuntimeModule = loadQaRunnerRuntimeModule as unknown as () => QaRuntimeSurface; +export { loadQaLabRuntimeModule as loadQaRuntimeModule }; function isQaRuntimeAvailableStrict(): boolean { try { - loadQaRuntimeModule(); + loadQaLabRuntimeModule(); return true; } catch (error) { if (isMissingQaRuntimeError(error)) { diff --git a/src/plugin-sdk/question-gateway-runtime.ts b/src/plugin-sdk/question-gateway-runtime.ts index 162db940bc0a..d73d5b7173c9 100644 --- a/src/plugin-sdk/question-gateway-runtime.ts +++ b/src/plugin-sdk/question-gateway-runtime.ts @@ -1,4 +1,5 @@ /** Runtime SDK subpath for Gateway-backed ask_user question controls. */ +import { readAskUserQuestionId } from "../auto-reply/reply-payload.js"; import { registerQuestionChannelDelivery } from "../infra/question-channel-runtime.js"; // The pre-release named resolver exports were replaced wholesale by this // runtime object before any tagged release shipped them; no compat aliases. @@ -6,7 +7,6 @@ import { resolveQuestionOverGateway } from "../infra/question-gateway-resolver.j import { QUESTION_REACTION_EMOJIS, prepareQuestionReactionPayloadForDelivery, - readAskUserQuestionId, readQuestionReactionBinding, resolveQuestionReactionIndex, resolveQuestionReactionOverGateway, diff --git a/src/plugin-sdk/session-store-runtime.test.ts b/src/plugin-sdk/session-store-runtime.test.ts index 47522c4245fa..9842da3adef3 100644 --- a/src/plugin-sdk/session-store-runtime.test.ts +++ b/src/plugin-sdk/session-store-runtime.test.ts @@ -6,7 +6,7 @@ import { appendTranscriptEvent, appendTranscriptMessage, loadSessionEntry as loadInternalSessionEntry, - patchSessionEntry as patchInternalSessionEntry, + patchSessionEntryCore as patchInternalSessionEntry, replaceSessionEntry as replaceInternalSessionEntry, } from "../config/sessions/session-accessor.js"; import type { InternalSessionEntry } from "../config/sessions/types.js"; diff --git a/src/plugin-sdk/session-store-runtime.ts b/src/plugin-sdk/session-store-runtime.ts index 32aa3f22ebe8..2c684151171d 100644 --- a/src/plugin-sdk/session-store-runtime.ts +++ b/src/plugin-sdk/session-store-runtime.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { - readAmbientTranscriptWatermark as readAmbientTranscriptWatermarkFromEntry, + readAmbientTranscriptWatermarkFromEntry, resolveAmbientTranscriptWatermarkKey, updateAmbientTranscriptWatermark, type AmbientTranscriptWatermarkScope, @@ -17,14 +17,14 @@ import { resolveSessionFilePath as resolveLegacySessionFilePath } from "../confi export { SessionStoreAgentIdRequiredError } from "../config/sessions/paths.js"; import { applySessionStoreProjection as applyAccessorSessionStoreProjection, - cleanupSessionLifecycleArtifacts as cleanupAccessorSessionLifecycleArtifacts, + cleanupSessionLifecycleArtifactsCore as cleanupAccessorSessionLifecycleArtifacts, deleteSessionEntryLifecycle as deleteAccessorSessionEntryLifecycle, loadTranscriptEventsSync as loadAccessorTranscriptEventsSync, - listSessionEntries as listAccessorSessionEntries, + listSessionEntriesCore as listAccessorSessionEntries, listSessionEntriesReadOnly as listAccessorSessionEntriesReadOnly, loadSessionEntryReadOnly, - patchSessionEntry as patchAccessorSessionEntry, - readSessionUpdatedAt as readAccessorSessionUpdatedAt, + patchSessionEntryCore as patchAccessorSessionEntry, + readSessionUpdatedAtCore as readAccessorSessionUpdatedAt, readTranscriptStatsSync as readAccessorTranscriptStatsSync, resolveTranscriptSessionKeyBySessionId as resolveAccessorTranscriptSessionKeyBySessionId, updateSessionEntry, diff --git a/src/plugin-sdk/session-transcript-hit.test.ts b/src/plugin-sdk/session-transcript-hit.test.ts index 5ececd248086..6343b40e46c8 100644 --- a/src/plugin-sdk/session-transcript-hit.test.ts +++ b/src/plugin-sdk/session-transcript-hit.test.ts @@ -14,7 +14,7 @@ import { const loadGatewaySessionStore = vi.hoisted(() => vi.fn()); vi.mock("../config/sessions/combined-store-gateway.js", () => ({ - loadCombinedSessionStoreForGateway: loadGatewaySessionStore, + loadCombinedSessionStoreForGatewayCore: loadGatewaySessionStore, })); it("filters incognito rows from the plugin cross-session store view", () => { diff --git a/src/plugin-sdk/session-transcript-hit.ts b/src/plugin-sdk/session-transcript-hit.ts index 3e4d67af0c58..11b1d6ad54c6 100644 --- a/src/plugin-sdk/session-transcript-hit.ts +++ b/src/plugin-sdk/session-transcript-hit.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { normalizeOptionalString } from "../../packages/normalization-core/src/string-coerce.js"; import { uniqueStrings } from "../../packages/normalization-core/src/string-normalization.js"; import { parseUsageCountedSessionIdFromFileName } from "../config/sessions/artifacts.js"; -import { loadCombinedSessionStoreForGateway as loadGatewaySessionStore } from "../config/sessions/combined-store-gateway.js"; +import { loadCombinedSessionStoreForGatewayCore as loadGatewaySessionStore } from "../config/sessions/combined-store-gateway.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isIncognitoSessionKey, normalizeAgentId } from "../routing/session-key.js"; diff --git a/src/plugin-sdk/session-transcript-runtime.test.ts b/src/plugin-sdk/session-transcript-runtime.test.ts index d0ba8b8f36fc..e6fdb41a6efb 100644 --- a/src/plugin-sdk/session-transcript-runtime.test.ts +++ b/src/plugin-sdk/session-transcript-runtime.test.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { appendTranscriptEvent, - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, replaceTranscriptEvents, upsertSessionEntry, @@ -937,7 +937,7 @@ describe("session transcript runtime SDK", () => { const keys = resolveSessionTranscriptMemoryHitKeyToSessionKeys({ key: formatSessionTranscriptMemoryHitKey(scope), store: Object.fromEntries( - listSessionEntries({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), + listSessionEntriesCore({ storePath }).map(({ sessionKey, entry }) => [sessionKey, entry]), ), }); diff --git a/src/plugin-sdk/session-transcript-runtime.ts b/src/plugin-sdk/session-transcript-runtime.ts index c29aff9a0052..90c145739064 100644 --- a/src/plugin-sdk/session-transcript-runtime.ts +++ b/src/plugin-sdk/session-transcript-runtime.ts @@ -10,7 +10,7 @@ import { publishTranscriptUpdate, persistSessionTranscriptTurn, readTranscriptRawDelta, - readSessionTranscriptVisibleMessageDelta as readVisibleMessageDelta, + readSessionTranscriptVisibleMessageDeltaCore as readVisibleMessageDelta, readLatestTranscriptAssistantText, resolveSessionTranscriptRuntimeTarget, withTranscriptWriteLock, diff --git a/src/plugin-sdk/synology-chat.ts b/src/plugin-sdk/synology-chat.ts index 87ae676438de..63a10b00f19a 100644 --- a/src/plugin-sdk/synology-chat.ts +++ b/src/plugin-sdk/synology-chat.ts @@ -1,6 +1,6 @@ // Manual facade. Keep loader boundary explicit. import type { SecurityAuditFinding } from "../security/audit.types.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; type FacadeModule = { collectSynologyChatSecurityAuditFindings: (params: { @@ -15,7 +15,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "synology-chat", artifactBasename: "contract-api.js", }); diff --git a/src/plugin-sdk/telegram-account.test.ts b/src/plugin-sdk/telegram-account.test.ts index b25e7d3d9408..8fcf4de8e482 100644 --- a/src/plugin-sdk/telegram-account.test.ts +++ b/src/plugin-sdk/telegram-account.test.ts @@ -16,12 +16,12 @@ const mocks = vi.hoisted(() => { return { apiModule, - loadBundledPluginPublicSurfaceModuleSync: vi.fn(() => apiModule), + loadBundledPluginPublicSurfaceModuleSyncCore: vi.fn(() => apiModule), }; }); vi.mock("./facade-loader.js", () => ({ - loadBundledPluginPublicSurfaceModuleSync: mocks.loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore: mocks.loadBundledPluginPublicSurfaceModuleSyncCore, })); describe("telegram account plugin-sdk compatibility facade", () => { @@ -31,7 +31,7 @@ describe("telegram account plugin-sdk compatibility facade", () => { const account = resolveTelegramAccount({ cfg, accountId: "default" }); - expect(mocks.loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(mocks.loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "telegram", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/telegram-account.ts b/src/plugin-sdk/telegram-account.ts index c0180b88e82f..55435cbb3341 100644 --- a/src/plugin-sdk/telegram-account.ts +++ b/src/plugin-sdk/telegram-account.ts @@ -1,6 +1,6 @@ // Telegram account helpers resolve Telegram plugin account config and display metadata. import type { OpenClawConfig } from "./config-contracts.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "./facade-loader.js"; /** * @deprecated Compatibility type for the `openclaw/plugin-sdk/telegram-account` facade. @@ -31,7 +31,7 @@ type TelegramAccountFacadeModule = { }; function loadTelegramAccountFacadeModule(): TelegramAccountFacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "telegram", artifactBasename: "api.js", }); diff --git a/src/plugin-sdk/vercel-ai-gateway.ts b/src/plugin-sdk/vercel-ai-gateway.ts index 3d19a551a565..397bcde266b3 100644 --- a/src/plugin-sdk/vercel-ai-gateway.ts +++ b/src/plugin-sdk/vercel-ai-gateway.ts @@ -2,7 +2,7 @@ import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types.js"; import { createLazyFacadeObjectValue, - loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore, } from "./facade-loader.js"; type ModelCost = ModelDefinitionConfig["cost"]; @@ -21,7 +21,7 @@ type FacadeModule = { }; function loadFacadeModule(): FacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ + return loadBundledPluginPublicSurfaceModuleSyncCore({ dirName: "vercel-ai-gateway", artifactBasename: "api.js", }); diff --git a/src/plugins/contracts/host-hooks.contract.test.ts b/src/plugins/contracts/host-hooks.contract.test.ts index 81ea19b338da..7a439e7fbc62 100644 --- a/src/plugins/contracts/host-hooks.contract.test.ts +++ b/src/plugins/contracts/host-hooks.contract.test.ts @@ -15,7 +15,7 @@ import { import type { SessionEntry } from "../../config/sessions.js"; import { clearPluginOwnedSessionState, - listSessionEntries, + listSessionEntriesCore, replaceSessionEntry, } from "../../config/sessions/session-accessor.js"; import { APPROVALS_SCOPE, READ_SCOPE, WRITE_SCOPE } from "../../gateway/operator-scopes.js"; @@ -109,7 +109,7 @@ function loadSessionStore( _options?: { skipCache?: boolean }, ): Record { return Object.fromEntries( - listSessionEntries({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 800a02ffaa3c..99fdb9cb6ea2 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -112,7 +112,7 @@ const BROWSER_FACADE_SOURCE_CONTRACTS: readonly BrowserFacadeSourceContract[] = subpath: "browser-control-auth", artifactBasename: "browser-control-auth.js", mentions: [ - "loadBundledPluginPublicSurfaceModuleSync", + "loadBundledPluginPublicSurfaceModuleSyncCore", "resolveBrowserControlAuth", "shouldAutoGenerateBrowserAuth", "ensureBrowserControlAuth", @@ -128,7 +128,7 @@ const BROWSER_FACADE_SOURCE_CONTRACTS: readonly BrowserFacadeSourceContract[] = subpath: "browser-profiles", artifactBasename: "browser-profiles.js", mentions: [ - "loadBundledPluginPublicSurfaceModuleSync", + "loadBundledPluginPublicSurfaceModuleSyncCore", "resolveBrowserConfig", "resolveProfile", ], @@ -143,7 +143,7 @@ const BROWSER_FACADE_SOURCE_CONTRACTS: readonly BrowserFacadeSourceContract[] = subpath: "browser-host-inspection", artifactBasename: "browser-host-inspection.js", mentions: [ - "loadBundledPluginPublicSurfaceModuleSync", + "loadBundledPluginPublicSurfaceModuleSyncCore", "resolveGoogleChromeExecutableForPlatform", "readBrowserVersion", "parseBrowserMajorVersion", @@ -486,7 +486,7 @@ function isGeneratedBundledFacadeSubpath(subpath: string): boolean { const source = readPluginSdkSource(subpath); return ( source.startsWith("// Manual facade.") && - sourceMentionsIdentifier(source, "loadBundledPluginPublicSurfaceModuleSync") + sourceMentionsIdentifier(source, "loadBundledPluginPublicSurfaceModuleSyncCore") ); } diff --git a/src/plugins/contracts/session-entry-projection.contract.test.ts b/src/plugins/contracts/session-entry-projection.contract.test.ts index 6f18ca94f588..9fb878884a92 100644 --- a/src/plugins/contracts/session-entry-projection.contract.test.ts +++ b/src/plugins/contracts/session-entry-projection.contract.test.ts @@ -8,7 +8,10 @@ import { import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { SessionEntry } from "../../config/sessions.js"; -import { listSessionEntries, replaceSessionEntry } from "../../config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + replaceSessionEntry, +} from "../../config/sessions/session-accessor.js"; import { withTempConfig } from "../../gateway/test-temp-config.js"; import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"; import { withEnvAsync } from "../../test-utils/env.js"; @@ -46,7 +49,7 @@ function loadSessionStore( _options?: { skipCache?: boolean }, ): Record { return Object.fromEntries( - listSessionEntries({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ + listSessionEntriesCore({ agentId: "main", storePath }).map(({ sessionKey, entry }) => [ sessionKey, entry, ]), diff --git a/src/plugins/hooks.ts b/src/plugins/hooks.ts index fbb820b6a1f5..6705c6907bac 100644 --- a/src/plugins/hooks.ts +++ b/src/plugins/hooks.ts @@ -13,7 +13,7 @@ import { attachToolAllowlistIntersection, expandToolGroups, normalizeToolList, - normalizeToolName, + normalizeToolPolicyName, readToolAllowlistIntersection, } from "../agents/tool-policy.js"; import { copyReplyPayloadMetadata, type ReplyPayload } from "../auto-reply/reply-payload.js"; @@ -418,7 +418,7 @@ export function createHookRunner( } return [...new Set(normalizeToolList([...normalizedLeft, ...normalizedRight]))].filter( (name) => { - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); return ( isToolAllowedByPolicyName(normalized, { allow: normalizedLeft }) && isToolAllowedByPolicyName(normalized, { allow: normalizedRight }) diff --git a/src/plugins/install-persistence.test.ts b/src/plugins/install-persistence.test.ts index 043373692030..4a91f9adfd90 100644 --- a/src/plugins/install-persistence.test.ts +++ b/src/plugins/install-persistence.test.ts @@ -9,10 +9,10 @@ import { buildPluginSnapshotReportMock, clearPluginRegistryLoadCacheMock, enablePluginInConfigMock, - loadPluginManifestRegistry, - planPluginUninstall, - replaceConfigFile, - refreshPluginRegistry, + loadPluginManifestRegistryMock, + planPluginUninstallMock, + replaceConfigFileMock, + refreshPluginRegistryMock, resetPluginsCliTestState, pluginsCliRuntimeLogs, setInstalledPluginIndexInstallRecords, @@ -140,7 +140,7 @@ describe("persistPluginInstall", () => { installedAt: "2026-04-25T00:00:00.000Z", }); expect(writeConfigFile).toHaveBeenCalledWith(enabledConfig); - expect(replaceConfigFile).toHaveBeenCalledWith({ + expect(replaceConfigFileMock).toHaveBeenCalledWith({ nextConfig: enabledConfig, baseHash: "config-1", writeOptions: { @@ -153,7 +153,10 @@ describe("persistPluginInstall", () => { unsetPaths: [["plugins", "installs"]], }, }); - const refreshParams = requireMockCallArg(refreshPluginRegistry, "refreshPluginRegistry"); + const refreshParams = requireMockCallArg( + refreshPluginRegistryMock, + "refreshPluginRegistryMock", + ); expect(refreshParams.config).toBe(enabledConfig); expect(refreshParams.reason).toBe("source-changed"); expect((refreshParams.installRecords as Record).alpha).toEqual({ @@ -199,7 +202,7 @@ describe("persistPluginInstall", () => { }); expect(next).toEqual(enabledConfig); - expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); + expect(refreshPluginRegistryMock).toHaveBeenCalledTimes(1); expectRuntimeLogIncludes("Plugin runtime cache invalidation failed"); }); @@ -225,7 +228,7 @@ describe("persistPluginInstall", () => { installPath: "/tmp/openclaw/extensions/codex", }, }); - planPluginUninstall.mockReturnValueOnce({ + planPluginUninstallMock.mockReturnValueOnce({ ok: true, config: {} as OpenClawConfig, pluginId: "codex", @@ -263,7 +266,7 @@ describe("persistPluginInstall", () => { }, }); - expect(planPluginUninstall).toHaveBeenCalledWith({ + expect(planPluginUninstallMock).toHaveBeenCalledWith({ config: { plugins: { installs: { @@ -284,7 +287,7 @@ describe("persistPluginInstall", () => { const cleanupOrder = applyPluginUninstallDirectoryRemovalMock.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER; - const refreshOrder = refreshPluginRegistry.mock.invocationCallOrder[0] ?? 0; + const refreshOrder = refreshPluginRegistryMock.mock.invocationCallOrder[0] ?? 0; expect(cleanupOrder).toBeLessThan(refreshOrder); expect(pluginsCliRuntimeLogs.join("\n")).toContain( "Removed previous plugin install directory: /tmp/openclaw/extensions/codex", @@ -328,7 +331,7 @@ describe("persistPluginInstall", () => { }, }); - expect(planPluginUninstall).not.toHaveBeenCalled(); + expect(planPluginUninstallMock).not.toHaveBeenCalled(); expect(applyPluginUninstallDirectoryRemovalMock).not.toHaveBeenCalled(); }); @@ -372,7 +375,7 @@ describe("persistPluginInstall", () => { installPath: previousInstallPath, }, }); - planPluginUninstall.mockReturnValueOnce({ + planPluginUninstallMock.mockReturnValueOnce({ ok: true, config: {} as OpenClawConfig, pluginId: "codex", @@ -412,7 +415,7 @@ describe("persistPluginInstall", () => { }, }); - expect(planPluginUninstall).toHaveBeenCalledWith({ + expect(planPluginUninstallMock).toHaveBeenCalledWith({ config: { plugins: { installs: { @@ -552,7 +555,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - refreshPluginRegistry.mockRejectedValueOnce(new Error("registry unavailable")); + refreshPluginRegistryMock.mockRejectedValueOnce(new Error("registry unavailable")); const next = await persistPluginInstall({ snapshot: { @@ -569,7 +572,7 @@ describe("persistPluginInstall", () => { }); expect(next).toEqual(enabledConfig); - expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); + expect(refreshPluginRegistryMock).toHaveBeenCalledTimes(1); expect(clearPluginRegistryLoadCacheMock).toHaveBeenCalledTimes(1); expectRuntimeLogIncludes("Plugin registry refresh failed"); }); @@ -606,7 +609,7 @@ describe("persistPluginInstall", () => { }); expect(next).toEqual(enabledConfig); - expect(refreshPluginRegistry).toHaveBeenCalledTimes(1); + expect(refreshPluginRegistryMock).toHaveBeenCalledTimes(1); expect(clearPluginRegistryLoadCacheMock).not.toHaveBeenCalled(); }); @@ -667,7 +670,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [createManifestRecord("legacy-memory")], diagnostics: [], }); @@ -720,7 +723,8 @@ describe("persistPluginInstall", () => { onlyPluginIds: ["legacy-memory"], }); expect( - requireMockCallArg(loadPluginManifestRegistry, "loadPluginManifestRegistry", 1).config, + requireMockCallArg(loadPluginManifestRegistryMock, "loadPluginManifestRegistryMock", 1) + .config, ).toBe(enabledConfig); expect(next.plugins?.entries?.["legacy-memory-a"]?.enabled).toBe(true); expect(next.plugins?.slots?.memory).toBe("legacy-memory"); @@ -744,7 +748,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [createManifestRecord("memory-b", { kind: "memory" })], diagnostics: [], }); @@ -789,7 +793,8 @@ describe("persistPluginInstall", () => { expect(buildPluginDiagnosticsReportMock).not.toHaveBeenCalled(); expect( - requireMockCallArg(loadPluginManifestRegistry, "loadPluginManifestRegistry", 1).config, + requireMockCallArg(loadPluginManifestRegistryMock, "loadPluginManifestRegistryMock", 1) + .config, ).toBe(enabledConfig); expect(next.plugins?.entries?.["legacy-memory-a"]?.enabled).toBe(true); expect(next.plugins?.slots?.memory).toBe("memory-b"); @@ -810,7 +815,7 @@ describe("persistPluginInstall", () => { }, } as OpenClawConfig; enablePluginInConfigMock.mockReturnValue({ config: enabledConfig }); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [createManifestRecord("plain")], diagnostics: [], }); @@ -844,7 +849,8 @@ describe("persistPluginInstall", () => { onlyPluginIds: ["plain"], }); expect( - requireMockCallArg(loadPluginManifestRegistry, "loadPluginManifestRegistry", 1).config, + requireMockCallArg(loadPluginManifestRegistryMock, "loadPluginManifestRegistryMock", 1) + .config, ).toBe(enabledConfig); expect(next).toEqual(enabledConfig); }); @@ -860,7 +866,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ { id: "needs-config", @@ -926,7 +932,7 @@ describe("persistPluginInstall", () => { }, }, } as OpenClawConfig; - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ { id: "needs-config", diff --git a/src/plugins/install-persistence.warning-sink.test.ts b/src/plugins/install-persistence.warning-sink.test.ts index 8ccf59d4bb6c..a8ef85e2f699 100644 --- a/src/plugins/install-persistence.warning-sink.test.ts +++ b/src/plugins/install-persistence.warning-sink.test.ts @@ -3,9 +3,9 @@ import { applyExclusiveSlotSelectionMock, applyPluginUninstallDirectoryRemovalMock, buildPluginSnapshotReportMock, - loadPluginManifestRegistry, - planPluginUninstall, - refreshPluginRegistry, + loadPluginManifestRegistryMock, + planPluginUninstallMock, + refreshPluginRegistryMock, resetPluginsCliTestState, pluginsCliRuntimeLogs, setInstalledPluginIndexInstallRecords, @@ -31,7 +31,7 @@ describe("plugin install persistence warning audiences", () => { it("reports missing required configuration without forwarding informational logs", async () => { const { persistPluginInstall } = await import("./install-persistence.js"); const warn = vi.fn(); - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ { id: "workboard", @@ -67,7 +67,7 @@ describe("plugin install persistence warning audiences", () => { const { persistPluginInstall } = await import("./install-persistence.js"); const warn = vi.fn(); const warning = 'Exclusive slot "memory" switched from "memory-core" to "workboard".'; - loadPluginManifestRegistry.mockReturnValue({ + loadPluginManifestRegistryMock.mockReturnValue({ plugins: [ { id: "workboard", @@ -116,7 +116,7 @@ describe("plugin install persistence warning audiences", () => { installPath: "/private/previous-source/workboard", }, }); - planPluginUninstall.mockReturnValueOnce({ + planPluginUninstallMock.mockReturnValueOnce({ ok: true, config: {}, pluginId: "workboard", @@ -127,7 +127,7 @@ describe("plugin install persistence warning audiences", () => { directoryRemoved: false, warnings: [cleanupDetail], }); - refreshPluginRegistry.mockRejectedValueOnce(new Error(refreshDetail)); + refreshPluginRegistryMock.mockRejectedValueOnce(new Error(refreshDetail)); buildPluginSnapshotReportMock.mockReturnValue({ plugins: [{ id: "workboard", origin: "config", source: configuredSource }], diagnostics: [], diff --git a/src/plugins/loader.registration.test-utils.ts b/src/plugins/loader.registration.test-utils.ts index 92a547599a66..b6e5eab6fdbd 100644 --- a/src/plugins/loader.registration.test-utils.ts +++ b/src/plugins/loader.registration.test-utils.ts @@ -48,10 +48,8 @@ import { globalAfterEach0, globalAfterAll1, } from "./loader.test-harness.js"; -import { - listMemoryEmbeddingProviders, - registerMemoryEmbeddingProvider, -} from "./memory-embedding-providers.js"; +import { listRegisteredMemoryEmbeddingProviderAdapters } from "./memory-embedding-provider-runtime.js"; +import { registerMemoryEmbeddingProvider } from "./memory-embedding-providers.js"; import { buildMemoryPromptSection, getMemoryCapabilityRegistration, @@ -784,7 +782,9 @@ describe("loadOpenClawPlugins", () => { expect(listMemoryCorpusSupplements()).toHaveLength(1); expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/active.md"); expect(getMemoryRuntime()).toBe(activeRuntime); - expect(listMemoryEmbeddingProviders().map((adapter) => adapter.id)).toEqual(["active"]); + expect(listRegisteredMemoryEmbeddingProviderAdapters().map((adapter) => adapter.id)).toEqual([ + "active", + ]); expect(listMemoryPromptPreparations()).toHaveLength(1); }); @@ -984,7 +984,7 @@ describe("loadOpenClawPlugins", () => { expect(listMemoryPromptPreparations()).toStrictEqual([]); expect(resolveMemoryFlushPlan({})).toBeNull(); expect(getMemoryRuntime()).toBeUndefined(); - expect(listMemoryEmbeddingProviders()).toStrictEqual([]); + expect(listRegisteredMemoryEmbeddingProviderAdapters()).toStrictEqual([]); }); it("does not replace the active detached task runtime during non-activating loads", () => { diff --git a/src/plugins/logger.test.ts b/src/plugins/logger.test.ts deleted file mode 100644 index d022a0af08e0..000000000000 --- a/src/plugins/logger.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** Tests plugin logger formatting and diagnostic forwarding behavior. */ -import { describe, expect, it, vi } from "vitest"; -import { createPluginLoaderLogger } from "./logger.js"; - -describe("plugins/logger", () => { - it.each([ - ["info", "i"], - ["warn", "w"], - ["error", "e"], - ["debug", "d"], - ] as const)("forwards %s", (method, value) => { - const methods = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; - const logger = createPluginLoaderLogger(methods); - - logger[method]?.(value); - expect(methods[method]).toHaveBeenCalledWith(value); - }); -}); diff --git a/src/plugins/logger.ts b/src/plugins/logger.ts deleted file mode 100644 index b6442c6f9c06..000000000000 --- a/src/plugins/logger.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Builds plugin-scoped loggers for runtime and setup code. -import type { PluginLogger } from "./types.js"; - -type LoggerLike = { - info: (message: string) => void; - warn: (message: string) => void; - error: (message: string) => void; - debug?: (message: string) => void; -}; - -/** Adapts a generic logger to the plugin loader logger interface. */ -export function createPluginLoaderLogger(logger: LoggerLike): PluginLogger { - return { - info: (msg) => logger.info(msg), - warn: (msg) => logger.warn(msg), - error: (msg) => logger.error(msg), - debug: (msg) => logger.debug?.(msg), - }; -} diff --git a/src/plugins/manifest-capability-normalizers.ts b/src/plugins/manifest-capability-normalizers.ts index af0366b7d862..3b33f15984ee 100644 --- a/src/plugins/manifest-capability-normalizers.ts +++ b/src/plugins/manifest-capability-normalizers.ts @@ -40,7 +40,7 @@ export function normalizeStringListRecord(value: unknown): Record 0 ? normalized : undefined; } -export function normalizeStringRecord(value: unknown): Record | undefined { +export function normalizeManifestStringRecord(value: unknown): Record | undefined { if (!isRecord(value)) { return undefined; } diff --git a/src/plugins/manifest-model-provider-normalizers.ts b/src/plugins/manifest-model-provider-normalizers.ts index 4820d86035be..88f25e757f5a 100644 --- a/src/plugins/manifest-model-provider-normalizers.ts +++ b/src/plugins/manifest-model-provider-normalizers.ts @@ -4,7 +4,7 @@ import { normalizeTrimmedStringList } from "../../packages/normalization-core/sr import { ENV_SECRET_REF_ID_RE } from "../config/types.secrets.js"; import { isBlockedObjectKey } from "../infra/prototype-keys.js"; import { isRecord } from "../utils.js"; -import { normalizeStringRecord } from "./manifest-capability-normalizers.js"; +import { normalizeManifestStringRecord } from "./manifest-capability-normalizers.js"; import type { PluginManifestModelIdNormalization, PluginManifestModelIdNormalizationProvider, @@ -338,7 +338,7 @@ export function normalizeManifestSecretProviderIntegrations( rawIntegration.maxOutputBytes, MAX_SECRET_PROVIDER_EXEC_OUTPUT_BYTES, ); - const env = normalizeStringRecord(rawIntegration.env); + const env = normalizeManifestStringRecord(rawIntegration.env); const passEnv = normalizeManifestTrimmedStringArray(rawIntegration.passEnv, { maxItems: MAX_SECRET_PROVIDER_EXEC_PASS_ENV, pattern: ENV_SECRET_REF_ID_RE, diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 8ca494a6ceef..82106dfba5ef 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -264,7 +264,9 @@ export function loadPluginManifest( providerUsageAuthEnvVars: capabilityNormalizers.normalizeStringListRecord( raw.providerUsageAuthEnvVars, ), - providerAuthAliases: capabilityNormalizers.normalizeStringRecord(raw.providerAuthAliases), + providerAuthAliases: capabilityNormalizers.normalizeManifestStringRecord( + raw.providerAuthAliases, + ), providerAuthChoices: setupNormalizers.normalizeProviderAuthChoices(raw.providerAuthChoices), activation: setupNormalizers.normalizeManifestActivation(raw.activation), setup: setupNormalizers.normalizeManifestSetup(raw.setup), diff --git a/src/plugins/memory-embedding-providers.test.ts b/src/plugins/memory-embedding-providers.test.ts index d7f0f9795b11..8476e39edbde 100644 --- a/src/plugins/memory-embedding-providers.test.ts +++ b/src/plugins/memory-embedding-providers.test.ts @@ -3,7 +3,6 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { clearMemoryEmbeddingProviders, getRegisteredMemoryEmbeddingProvider, - listMemoryEmbeddingProviders, listRegisteredMemoryEmbeddingProviders, registerMemoryEmbeddingProvider, restoreRegisteredMemoryEmbeddingProviders, @@ -55,7 +54,9 @@ function expectRegisteredProviderState(params: { } function expectMemoryEmbeddingProviderIds(expectedIds: readonly string[]) { - expect(listMemoryEmbeddingProviders().map((adapter) => adapter.id)).toEqual([...expectedIds]); + expect(listRegisteredMemoryEmbeddingProviders().map((entry) => entry.adapter.id)).toEqual([ + ...expectedIds, + ]); } function expectCurrentMemoryEmbeddingProvider( diff --git a/src/plugins/memory-embedding-providers.ts b/src/plugins/memory-embedding-providers.ts index 94209f40d585..5953e90ac107 100644 --- a/src/plugins/memory-embedding-providers.ts +++ b/src/plugins/memory-embedding-providers.ts @@ -65,9 +65,6 @@ export function listRegisteredMemoryEmbeddingProviders(): RegisteredMemoryEmbedd return getMemoryEmbeddingProviders(); } -export function listMemoryEmbeddingProviders(): MemoryEmbeddingProviderAdapter[] { - return listRegisteredMemoryEmbeddingProviders().map((entry) => entry.adapter); -} export function restoreRegisteredMemoryEmbeddingProviders( entries: RegisteredMemoryEmbeddingProvider[], ): void { diff --git a/src/plugins/runtime/runtime-agent.ts b/src/plugins/runtime/runtime-agent.ts index cc5419e4b62f..0c7459f6eef7 100644 --- a/src/plugins/runtime/runtime-agent.ts +++ b/src/plugins/runtime/runtime-agent.ts @@ -20,10 +20,10 @@ import { resolveSessionWorkStartError } from "../../config/sessions/lifecycle.js import { resolveStorePath } from "../../config/sessions/paths.js"; import { deleteSessionEntryLifecycle, - listSessionEntries as listAccessorSessionEntries, + listSessionEntriesCore as listAccessorSessionEntries, listSessionEntriesReadOnly as listAccessorSessionEntriesReadOnly, loadSessionEntryReadOnly, - patchSessionEntry as patchAccessorSessionEntry, + patchSessionEntryCore as patchAccessorSessionEntry, replaceSessionEntry, rollbackAgentHarnessSessionEntryLifecycle, rollbackPluginOwnedSessionEntryLifecycle, diff --git a/src/plugins/runtime/runtime-channel.ts b/src/plugins/runtime/runtime-channel.ts index e3e0dcd0670e..6b127d3bd175 100644 --- a/src/plugins/runtime/runtime-channel.ts +++ b/src/plugins/runtime/runtime-channel.ts @@ -63,7 +63,7 @@ import { resolveMarkdownTableMode } from "../../config/markdown-tables.js"; import { resolveStorePath } from "../../config/sessions.js"; import { resolveSessionEntryResetFreshness } from "../../config/sessions/entry-freshness.js"; import { - readSessionUpdatedAt, + readSessionUpdatedAtCore, recordInboundSessionMeta, updateSessionLastRoute, } from "../../config/sessions/session-accessor.js"; @@ -83,7 +83,7 @@ import type { PluginRuntime } from "./types.js"; export function createRuntimeChannel(): PluginRuntime["channel"] { const sessionRuntime = { resolveStorePath, - readSessionUpdatedAt, + readSessionUpdatedAt: readSessionUpdatedAtCore, // Plugin runtime property names are a shipped contract; the implementations // route through the session accessor boundary. recordSessionMetaFromInbound: recordInboundSessionMeta, diff --git a/src/plugins/runtime/runtime-web-channel-plugin.ts b/src/plugins/runtime/runtime-web-channel-plugin.ts index 10562739cecd..af6f4bb2549a 100644 --- a/src/plugins/runtime/runtime-web-channel-plugin.ts +++ b/src/plugins/runtime/runtime-web-channel-plugin.ts @@ -1,10 +1,5 @@ // Runtime web-channel plugin helpers expose web-channel tools through activated plugin runtimes. -import { - getDefaultLocalRootsCore, - loadWebMedia as loadWebMediaImpl, - loadWebMediaRaw as loadWebMediaRawImpl, - optimizeImageToJpeg as optimizeImageToJpegImpl, -} from "../../media/web-media.js"; +import { getDefaultLocalRootsCore } from "../../media/web-media.js"; import { registerPluginMetadataProcessMemoLifecycleClear } from "../plugin-metadata-lifecycle.js"; import { createPluginModuleLoaderCache, @@ -236,20 +231,6 @@ export function resolveWebChannelAuthDir(): ReturnType< throw new Error("web channel plugin runtime is missing export 'resolveDefaultWebAuthDir'"); } -/** Loads web media through the core media helper. */ -export async function loadWebMedia( - ...args: Parameters -): ReturnType { - return await loadWebMediaImpl(...args); -} - -/** Loads raw web media through the core media helper. */ -export async function loadWebMediaRaw( - ...args: Parameters -): ReturnType { - return await loadWebMediaRawImpl(...args); -} - /** Starts web-channel monitoring through the heavy runtime API. */ export function monitorWebChannel( ...args: Parameters @@ -264,13 +245,6 @@ export async function monitorWebInbox( return (await getHeavyExport("monitorWebInbox"))(...args); } -/** Optimizes an image to JPEG through the core media helper. */ -export async function optimizeImageToJpeg( - ...args: Parameters -): ReturnType { - return await optimizeImageToJpegImpl(...args); -} - /** Starts QR login through the heavy runtime API. */ export async function startWebLoginWithQr( ...args: Parameters diff --git a/src/plugins/tool-grant-allowlist.ts b/src/plugins/tool-grant-allowlist.ts index f01d4661d1a9..000267e86035 100644 --- a/src/plugins/tool-grant-allowlist.ts +++ b/src/plugins/tool-grant-allowlist.ts @@ -1,10 +1,10 @@ -import { normalizeToolName } from "../agents/tool-policy.js"; +import { normalizeToolPolicyName } from "../agents/tool-policy.js"; import type { RuntimePluginToolGrant } from "./runtime/tool-grant.js"; const RUNTIME_PLUGIN_TOOL_GRANT_PREFIX = "__openclaw_runtime_plugin_tool_grant__"; function runtimePluginToolGrantKey(pluginId: string, toolName: string): string { - return `${RUNTIME_PLUGIN_TOOL_GRANT_PREFIX}:${pluginId.trim().toLowerCase()}:${normalizeToolName(toolName)}`; + return `${RUNTIME_PLUGIN_TOOL_GRANT_PREFIX}:${pluginId.trim().toLowerCase()}:${normalizeToolPolicyName(toolName)}`; } export function appendRuntimePluginToolGrant( @@ -24,7 +24,7 @@ export function isPluginToolAllowed( pluginId: string, toolName: string, ): boolean { - const normalizedToolName = normalizeToolName(toolName); + const normalizedToolName = normalizeToolPolicyName(toolName); return ( allowlist.has(normalizedToolName) || allowlist.has(runtimePluginToolGrantKey(pluginId, normalizedToolName)) diff --git a/src/plugins/tools.ts b/src/plugins/tools.ts index 7e95e264eb07..8a40b428fedf 100644 --- a/src/plugins/tools.ts +++ b/src/plugins/tools.ts @@ -6,7 +6,10 @@ import { } from "@openclaw/normalization-core/string-normalization"; import { compileGlobPatterns, matchesAnyGlobPattern } from "../agents/glob-pattern.js"; import type { McpCodexToolAnnotations } from "../agents/mcp-codex-tool-approval.js"; -import { DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, normalizeToolName } from "../agents/tool-policy.js"; +import { + DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, + normalizeToolPolicyName, +} from "../agents/tool-policy.js"; import type { AnyAgentTool } from "../agents/tools/common.js"; import { normalizeConversationReadInvocationOrigin } from "../channels/plugins/conversation-read-origin.js"; import type { McpCodexToolApprovalMode } from "../config/types.mcp.js"; @@ -313,18 +316,18 @@ export function buildPluginToolMetadataKey(pluginId: string, toolName: string): } function normalizeAllowlist(list?: string[]) { - return new Set(normalizeUniqueStringEntries((list ?? []).map(normalizeToolName))); + return new Set(normalizeUniqueStringEntries((list ?? []).map(normalizeToolPolicyName))); } function normalizeDenylist(list?: string[]) { return compileGlobPatterns({ raw: list, - normalize: normalizeToolName, + normalize: normalizeToolPolicyName, }); } function denylistBlocksName(name: string, denylist: ReturnType): boolean { - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); return normalized ? matchesAnyGlobPattern(normalized, denylist) : false; } @@ -399,7 +402,7 @@ function isOptionalToolAllowed(params: { if (isPluginToolAllowed(params.allowlist, params.pluginId, params.toolName)) { return true; } - const pluginKey = normalizeToolName(params.pluginId); + const pluginKey = normalizeToolPolicyName(params.pluginId); if (params.allowlist.has(pluginKey)) { return true; } @@ -417,7 +420,7 @@ function isOptionalToolEntryPotentiallyAllowed(params: { if (params.allowlist.has("*")) { return true; } - const pluginKey = normalizeToolName(params.pluginId); + const pluginKey = normalizeToolPolicyName(params.pluginId); if (params.allowlist.has(pluginKey) || params.allowlist.has("group:plugins")) { return true; } @@ -627,7 +630,7 @@ function listManifestToolNamesForAllowlist(params: { if (params.allowlist.has("*") || params.allowlist.has("group:plugins")) { return [...params.toolNames]; } - const pluginKey = normalizeToolName(params.pluginId); + const pluginKey = normalizeToolPolicyName(params.pluginId); if (params.allowlist.has(pluginKey)) { return [...params.toolNames]; } @@ -792,9 +795,9 @@ function cachedDescriptorsCoverToolNames(params: { toolNames: readonly string[]; }): boolean { const descriptorNames = new Set( - params.descriptors.map((entry) => normalizeToolName(entry.descriptor.name)), + params.descriptors.map((entry) => normalizeToolPolicyName(entry.descriptor.name)), ); - return params.toolNames.every((name) => descriptorNames.has(normalizeToolName(name))); + return params.toolNames.every((name) => descriptorNames.has(normalizeToolPolicyName(name))); } function createCachedDescriptorPluginTool(params: { @@ -838,7 +841,7 @@ function createCachedDescriptorPluginTool(params: { if (!candidates || candidates.length === 0) { throw new Error(`plugin tool runtime unavailable (${pluginId}): ${toolName}`); } - const requestedToolName = normalizeToolName(toolName); + const requestedToolName = normalizeToolPolicyName(toolName); const matchingNamedCandidates: PluginToolRegistration[] = []; const unnamedCandidates: PluginToolRegistration[] = []; for (const candidate of candidates) { @@ -846,7 +849,7 @@ function createCachedDescriptorPluginTool(params: { unnamedCandidates.push(candidate); continue; } - if (candidate.names.some((name) => normalizeToolName(name) === requestedToolName)) { + if (candidate.names.some((name) => normalizeToolPolicyName(name) === requestedToolName)) { matchingNamedCandidates.push(candidate); } } @@ -875,7 +878,7 @@ function createCachedDescriptorPluginTool(params: { continue; } const runtimeTool = toolRaw as AnyAgentTool; - if (normalizeToolName(readPluginToolName(runtimeTool)) === requestedToolName) { + if (normalizeToolPolicyName(readPluginToolName(runtimeTool)) === requestedToolName) { return runtimeTool; } } @@ -975,7 +978,7 @@ function resolveCachedPluginTools(params: { if (availableToolNames.length === 0) { continue; } - if (params.existingNormalized.has(normalizeToolName(plugin.id))) { + if (params.existingNormalized.has(normalizeToolPolicyName(plugin.id))) { continue; } const cached = readCachedPluginToolDescriptors( @@ -999,9 +1002,9 @@ function resolveCachedPluginTools(params: { const pluginTools: AnyAgentTool[] = []; let hasNameConflict = false; const localNormalizedNames = new Set(); - const availableNormalizedToolNames = new Set(availableToolNames.map(normalizeToolName)); + const availableNormalizedToolNames = new Set(availableToolNames.map(normalizeToolPolicyName)); for (const cachedDescriptor of cached) { - const normalizedDescriptorName = normalizeToolName(cachedDescriptor.descriptor.name); + const normalizedDescriptorName = normalizeToolPolicyName(cachedDescriptor.descriptor.name); // Live auth is intentionally absent from the descriptor cache key, so re-project // every cached name through current manifest availability before optional grants. if (!availableNormalizedToolNames.has(normalizedDescriptorName)) { @@ -1062,8 +1065,8 @@ function resolveCachedPluginTools(params: { } for (const pluginTool of pluginTools) { params.existing.add(pluginTool.name); - params.existingNormalized.add(normalizeToolName(pluginTool.name)); - params.pluginToolOwnersByName.set(normalizeToolName(pluginTool.name), plugin.id); + params.existingNormalized.add(normalizeToolPolicyName(pluginTool.name)); + params.pluginToolOwnersByName.set(normalizeToolPolicyName(pluginTool.name), plugin.id); tools.push(pluginTool); } handledPluginIds.add(plugin.id); @@ -1236,7 +1239,7 @@ export function resolvePluginTools(params: { const { context, env, onlyPluginIds, runtimeOptions, snapshot } = loadState; const tools: AnyAgentTool[] = []; const existing = params.existingToolNames ?? new Set(); - const existingNormalized = new Set(Array.from(existing, (tool) => normalizeToolName(tool))); + const existingNormalized = new Set(Array.from(existing, (tool) => normalizeToolPolicyName(tool))); // Tracks which plugin registered each tool name so the plugin-id conflict // guard below cannot fire against the plugin's own tools (a plugin may // register several tools, one of which shares the plugin id, e.g. canvas). @@ -1335,7 +1338,7 @@ export function resolvePluginTools(params: { if (blockedPlugins.has(entry.pluginId)) { continue; } - const pluginIdKey = normalizeToolName(entry.pluginId); + const pluginIdKey = normalizeToolPolicyName(entry.pluginId); // A name owned by this same plugin (e.g. the canvas plugin's own `canvas` // tool registered by an earlier entry) is not a conflict; only core names // and other plugins' tools shadow the plugin id. @@ -1422,16 +1425,16 @@ export function resolvePluginTools(params: { const listRaw: unknown[] = Array.isArray(resolved) ? resolved : [resolved]; const selectedManifestToolNames = manifestPlugin && availabilityNames.length > 0 - ? new Set(allowlistNames.map((name) => normalizeToolName(name))) + ? new Set(allowlistNames.map((name) => normalizeToolPolicyName(name))) : undefined; const manifestContractToolNames = manifestPlugin && availabilityNames.length > 0 - ? new Set(availabilityNames.map((name) => normalizeToolName(name))) + ? new Set(availabilityNames.map((name) => normalizeToolPolicyName(name))) : undefined; const availableList = manifestPlugin ? listRaw.filter((tool) => { const toolName = readPluginToolName(tool); - const normalizedToolName = normalizeToolName(toolName); + const normalizedToolName = normalizeToolPolicyName(toolName); if ( isManifestToolOptional(manifestPlugin, toolName) && !isOptionalToolAllowed({ @@ -1515,7 +1518,7 @@ export function resolvePluginTools(params: { }); continue; } - const normalizedToolName = normalizeToolName(tool.name); + const normalizedToolName = normalizeToolPolicyName(tool.name); if (normalizedNameSet.has(normalizedToolName) || existingNormalized.has(normalizedToolName)) { const message = `plugin tool name conflict (${entry.pluginId}): ${tool.name}`; if (!params.suppressNameConflicts) { diff --git a/src/plugins/update-channel.ts b/src/plugins/update-channel.ts index bfadcd6b6de2..f0904936185d 100644 --- a/src/plugins/update-channel.ts +++ b/src/plugins/update-channel.ts @@ -24,7 +24,7 @@ import { isBridgeBundledPathRecord, isExternalizedBundledPluginEnabled, migratePluginConfigId, - pathsEqual, + userPathsEqual, removeBridgeBundledLoadPaths, resolveBridgeInstallRecord, shouldFallbackClawHubBridgeToNpm, @@ -95,7 +95,7 @@ export async function syncPluginsForUpdateChannel(params: { loadHelpers.addPath(bundledInfo.localPath); const alreadyBundled = - record.source === "path" && pathsEqual(record.sourcePath, bundledInfo.localPath, env); + record.source === "path" && userPathsEqual(record.sourcePath, bundledInfo.localPath, env); if (alreadyBundled) { continue; } @@ -335,7 +335,7 @@ export async function syncPluginsForUpdateChannel(params: { if (record.source !== "path") { continue; } - if (!pathsEqual(record.sourcePath, bundledInfo.localPath, env)) { + if (!userPathsEqual(record.sourcePath, bundledInfo.localPath, env)) { continue; } // Keep explicit bundled installs on release channels. Replacing them with @@ -343,8 +343,8 @@ export async function syncPluginsForUpdateChannel(params: { loadHelpers.addPath(bundledInfo.localPath); const alreadyBundled = record.source === "path" && - pathsEqual(record.sourcePath, bundledInfo.localPath, env) && - pathsEqual(record.installPath, bundledInfo.localPath, env); + userPathsEqual(record.sourcePath, bundledInfo.localPath, env) && + userPathsEqual(record.installPath, bundledInfo.localPath, env); if (alreadyBundled) { continue; } diff --git a/src/plugins/update-config.ts b/src/plugins/update-config.ts index 776a1586aad0..6e8f449b9e14 100644 --- a/src/plugins/update-config.ts +++ b/src/plugins/update-config.ts @@ -35,7 +35,7 @@ export async function hasRunnableInstalledNpmPayload(params: { return validation.ok; } -export function pathsEqual( +export function userPathsEqual( left: string | undefined, right: string | undefined, env: NodeJS.ProcessEnv = process.env, @@ -53,7 +53,7 @@ export function resolveRecordedExtensionsDir(params: { const parentDir = path.dirname(params.installPath); try { const canonicalInstallPath = resolvePluginInstallDir(params.pluginId, parentDir); - return pathsEqual(canonicalInstallPath, params.installPath) ? parentDir : undefined; + return userPathsEqual(canonicalInstallPath, params.installPath) ? parentDir : undefined; } catch { return undefined; } @@ -138,8 +138,8 @@ export function isBridgeBundledPathRecord(params: { } if ( params.bundledLocalPath && - (pathsEqual(params.record.sourcePath, params.bundledLocalPath, params.env) || - pathsEqual(params.record.installPath, params.bundledLocalPath, params.env)) + (userPathsEqual(params.record.sourcePath, params.bundledLocalPath, params.env) || + userPathsEqual(params.record.installPath, params.bundledLocalPath, params.env)) ) { return true; } diff --git a/src/sessions/session-diff-baseline.ts b/src/sessions/session-diff-baseline.ts index 39172dd36047..911379aa3489 100644 --- a/src/sessions/session-diff-baseline.ts +++ b/src/sessions/session-diff-baseline.ts @@ -1,4 +1,4 @@ -import { patchSessionEntry } from "../config/sessions/session-accessor.js"; +import { patchSessionEntryCore } from "../config/sessions/session-accessor.js"; import type { SessionEntry } from "../config/sessions/types.js"; import { captureSessionDiffBaseline } from "./session-diff.js"; @@ -25,7 +25,7 @@ export async function ensureSessionDiffBaseline(params: { if (!baseline) { return params.entry; } - const persisted = await patchSessionEntry( + const persisted = await patchSessionEntryCore( { sessionKey: params.sessionKey, storePath: params.storePath }, (current) => { if ( diff --git a/src/skills/lifecycle/skill-change-hook.ts b/src/skills/lifecycle/skill-change-hook.ts index aca19e821a04..81cde33b106a 100644 --- a/src/skills/lifecycle/skill-change-hook.ts +++ b/src/skills/lifecycle/skill-change-hook.ts @@ -7,7 +7,7 @@ import type { PluginHookSkillArtifact, PluginHookSkillChangedEvent, } from "../../plugins/hook-types.js"; -import { parseFrontmatter } from "../loading/frontmatter.js"; +import { parseSkillFrontmatter } from "../loading/frontmatter.js"; const SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md", "skills.md", "SKILL.MD"] as const; const EXCLUDED_ROOT_DIRS = new Set([".clawhub", ".clawdhub", ".openclaw"]); @@ -57,7 +57,7 @@ async function collectSkillTreeFiles(skillDir: string, relativeDir = ""): Promis return files; } -function parseSkillFrontmatter(content: Buffer): { +function parseSkillArtifactMetadata(content: Buffer): { name?: string; description?: string; declaredVersion?: string; @@ -67,7 +67,7 @@ function parseSkillFrontmatter(content: Buffer): { return {}; } try { - const frontmatter = parseFrontmatter(text); + const frontmatter = parseSkillFrontmatter(text); return { name: normalizeOptionalString(frontmatter.name), description: normalizeOptionalString(frontmatter.description), @@ -109,7 +109,7 @@ async function snapshotCommittedSkillArtifact(params: { throw new Error(`Skill tree is missing SKILL.md: ${skillDir}`); } const skillFile = path.join(skillDir, skillFileEntry.path); - const frontmatter = parseSkillFrontmatter(await fs.readFile(skillFile)); + const frontmatter = parseSkillArtifactMetadata(await fs.readFile(skillFile)); const treeSha256 = sha256Hex(JSON.stringify(files)); return { name: frontmatter.name ?? params.skillKey, diff --git a/src/skills/lifecycle/source-install.ts b/src/skills/lifecycle/source-install.ts index e78efee085a8..6be06afab9fc 100644 --- a/src/skills/lifecycle/source-install.ts +++ b/src/skills/lifecycle/source-install.ts @@ -11,7 +11,7 @@ import { writeJson } from "../../infra/json-files.js"; import { isImmutableGitCommitRef, parseGitPluginSpec } from "../../plugins/git-install.js"; import { runCommandWithTimeout } from "../../process/exec.js"; import { resolveUserPath } from "../../utils.js"; -import { parseFrontmatter } from "../loading/frontmatter.js"; +import { parseSkillFrontmatter } from "../loading/frontmatter.js"; import { installExtractedSkillRoot, validateRequestedSkillSlug } from "./archive-install.js"; import { untrackClawHubSkill } from "./clawhub.js"; @@ -131,7 +131,7 @@ async function resolveGitCommitish(params: { async function readSkillNameFromFrontmatter(skillDir: string): Promise { try { const raw = await fs.readFile(path.join(skillDir, "SKILL.md"), "utf8"); - const frontmatter = parseFrontmatter(raw); + const frontmatter = parseSkillFrontmatter(raw); return normalizeOptionalString(frontmatter.name) ?? null; } catch { return null; diff --git a/src/skills/loading/bundled-frontmatter.test.ts b/src/skills/loading/bundled-frontmatter.test.ts index 1c04d1972e63..0f5383011639 100644 --- a/src/skills/loading/bundled-frontmatter.test.ts +++ b/src/skills/loading/bundled-frontmatter.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; -import { parseFrontmatter } from "./frontmatter.js"; +import { parseSkillFrontmatter } from "./frontmatter.js"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", ".."); @@ -16,7 +16,7 @@ describe("bundled taskflow skill frontmatter", () => { for (const relativePath of skillPaths) { const raw = await fs.readFile(path.join(repoRoot, relativePath), "utf8"); - const frontmatter = parseFrontmatter(raw); + const frontmatter = parseSkillFrontmatter(raw); expect(frontmatter.name, relativePath).toBeTypeOf("string"); expect(frontmatter.name?.trim(), relativePath).not.toBe(""); diff --git a/src/skills/loading/description-summary.test.ts b/src/skills/loading/description-summary.test.ts index b7c403f7bd46..700bb3b94ae8 100644 --- a/src/skills/loading/description-summary.test.ts +++ b/src/skills/loading/description-summary.test.ts @@ -2,13 +2,13 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { parseFrontmatter } from "./frontmatter.js"; +import { parseSkillFrontmatter } from "./frontmatter.js"; describe("skills/summarize frontmatter", () => { it("mentions podcasts, local files, and transcription use cases", () => { const skillPath = path.join(process.cwd(), "skills", "summarize", "SKILL.md"); const raw = fs.readFileSync(skillPath, "utf-8"); - const frontmatter = parseFrontmatter(raw); + const frontmatter = parseSkillFrontmatter(raw); const description = frontmatter.description ?? ""; expect(description).toBe( "Summarize or transcribe URLs, YouTube/videos, podcasts, articles, transcripts, PDFs, and local files.", diff --git a/src/skills/loading/frontmatter.test.ts b/src/skills/loading/frontmatter.test.ts index 984b7a747354..7d0111e70bfb 100644 --- a/src/skills/loading/frontmatter.test.ts +++ b/src/skills/loading/frontmatter.test.ts @@ -1,7 +1,7 @@ // Frontmatter tests cover skill metadata parsing and validation. import { describe, expect, it } from "vitest"; import { - parseFrontmatter, + parseSkillFrontmatter, resolveOpenClawMetadata, resolveSkillInvocationPolicy, } from "./frontmatter.js"; @@ -23,7 +23,7 @@ describe("resolveSkillInvocationPolicy", () => { }); }); -describe("parseFrontmatter", () => { +describe("parseSkillFrontmatter", () => { it.each([ { title: "keeps recoverable colon-rich scalar values", @@ -58,7 +58,7 @@ description: *Experimental expectedDescription: "*Experimental", }, ])("$title", ({ frontmatter, expectedDescription }) => { - const parsed = parseFrontmatter(frontmatter); + const parsed = parseSkillFrontmatter(frontmatter); expect(parsed.description).toBe(expectedDescription); }); @@ -118,12 +118,12 @@ metadata: *missing expectedError: "invalid frontmatter: YAML_EXCEPTION: Unresolved alias", }, ])("$title", ({ frontmatter, expectedError }) => { - expect(() => parseFrontmatter(frontmatter)).toThrow(expectedError); + expect(() => parseSkillFrontmatter(frontmatter)).toThrow(expectedError); }); it("rejects indentation errors following a description", () => { expect(() => - parseFrontmatter(`--- + parseSkillFrontmatter(`--- name: sample-skill description: Working skill \tmetadata: {} @@ -133,7 +133,7 @@ description: Working skill it("rejects unresolved aliases under explicit YAML keys", () => { expect(() => - parseFrontmatter(`--- + parseSkillFrontmatter(`--- name: sample-skill description: Working skill ? metadata @@ -144,7 +144,7 @@ description: Working skill it("does not recover nested description keys inside malformed metadata", () => { expect(() => - parseFrontmatter(`--- + parseSkillFrontmatter(`--- name: sample-skill description: Working skill metadata: { @@ -203,7 +203,7 @@ describe("resolveOpenClawMetadata install validation", () => { }); it("parses Link-style YAML metadata with node install hints", () => { - const frontmatter = parseFrontmatter(`--- + const frontmatter = parseSkillFrontmatter(`--- name: create-payment-credential description: | Gets secure, one-time-use payment credentials from a Link wallet so agents can complete purchases. diff --git a/src/skills/loading/frontmatter.ts b/src/skills/loading/frontmatter.ts index 464f2830668e..3f47eaff7b75 100644 --- a/src/skills/loading/frontmatter.ts +++ b/src/skills/loading/frontmatter.ts @@ -22,7 +22,7 @@ import type { } from "../types.js"; import type { Skill } from "./skill-contract.js"; -export function parseFrontmatter(content: string): ParsedSkillFrontmatter { +export function parseSkillFrontmatter(content: string): ParsedSkillFrontmatter { const parsed = parseFrontmatterBlockResult(content); const issue = parsed.issues[0]; if (issue) { diff --git a/src/skills/loading/local-loader.ts b/src/skills/loading/local-loader.ts index 25165c5a2cb6..2712ae151bf2 100644 --- a/src/skills/loading/local-loader.ts +++ b/src/skills/loading/local-loader.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { openRootFileSync } from "../../infra/boundary-file-read.js"; import type { ParsedSkillFrontmatter } from "../types.js"; -import { parseFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; +import { parseSkillFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; import { createSyntheticSourceInfo, type Skill } from "./skill-contract.js"; import { computeSkillPromptVersion } from "./skill-version.js"; @@ -62,7 +62,7 @@ function loadSingleSkillDirectory(params: { let frontmatter: Record; try { - frontmatter = parseFrontmatter(raw); + frontmatter = parseSkillFrontmatter(raw); } catch (error) { const message = error instanceof Error ? error.message : "failed to parse skill frontmatter"; params.onDiagnostic?.({ path: skillFilePath, message }); @@ -188,7 +188,7 @@ export function readSkillFrontmatterSafe(params: { return null; } try { - return parseFrontmatter(raw); + return parseSkillFrontmatter(raw); } catch { return null; } diff --git a/src/skills/loading/session.test.ts b/src/skills/loading/session.test.ts index 1b1189b1d06f..119225db0bc6 100644 --- a/src/skills/loading/session.test.ts +++ b/src/skills/loading/session.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js"; -import { parseFrontmatter, resolveOpenClawMetadata } from "./frontmatter.js"; +import { parseSkillFrontmatter, resolveOpenClawMetadata } from "./frontmatter.js"; import { loadSkills } from "./session.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); @@ -74,7 +74,7 @@ disable-model-invocation: true ); const result = loadSkillsFromPath(tempDir); - const frontmatter = parseFrontmatter(await fs.readFile(skillFile, "utf-8")); + const frontmatter = parseSkillFrontmatter(await fs.readFile(skillFile, "utf-8")); expect(result.diagnostics).toEqual([]); expect(result.skills).toEqual([ diff --git a/src/skills/loading/session.ts b/src/skills/loading/session.ts index 4a5942fcbac1..d8e7d5536a7e 100644 --- a/src/skills/loading/session.ts +++ b/src/skills/loading/session.ts @@ -8,7 +8,7 @@ import { addIgnoreRules, toPosixPath, type IgnoreMatcher } from "../../shared/ig // Session skill helpers resolve skills attached to a session and its transcript state. import { expandTildePath } from "../../shared/tilde-path.js"; import { getArchivedSkillFiles } from "../workshop/curator.js"; -import { parseFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; +import { parseSkillFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; import { formatSkillsForPromptCore } from "./skill-contract.js"; import { computeSkillPromptVersion } from "./skill-version.js"; @@ -215,7 +215,7 @@ function loadSkillFromFile( try { const rawContent = readFileSync(filePath, "utf-8"); - const frontmatter = parseFrontmatter(rawContent); + const frontmatter = parseSkillFrontmatter(rawContent); const invocation = resolveSkillInvocationPolicy(frontmatter); const skillDir = dirname(filePath); const parentDirName = basename(skillDir); diff --git a/src/skills/runtime/remote-skills.ts b/src/skills/runtime/remote-skills.ts index 34f92a652994..8369213c457b 100644 --- a/src/skills/runtime/remote-skills.ts +++ b/src/skills/runtime/remote-skills.ts @@ -2,7 +2,7 @@ import type { NodeSkillDescriptor } from "../../../packages/gateway-protocol/src import { createSyntheticSourceInfo } from "../../agents/sessions/source-info.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { resolveNodeIdFromNodeList } from "../../shared/node-resolve.js"; -import { parseFrontmatter, resolveSkillInvocationPolicy } from "../loading/frontmatter.js"; +import { parseSkillFrontmatter, resolveSkillInvocationPolicy } from "../loading/frontmatter.js"; import { computeSkillPromptVersion } from "../loading/skill-version.js"; import type { ParsedSkillFrontmatter, SkillEntry } from "../types.js"; import { bumpSkillsSnapshotVersion } from "./refresh-state.js"; @@ -42,7 +42,7 @@ function prepareNodeSkills( const prepared: PreparedNodeSkill[] = []; for (const skill of skills) { try { - const frontmatter = parseFrontmatter(skill.content); + const frontmatter = parseSkillFrontmatter(skill.content); if ( frontmatter.name?.trim() !== skill.name || frontmatter.description?.trim() !== skill.description diff --git a/src/skills/workshop/frontmatter.ts b/src/skills/workshop/frontmatter.ts index 7ec7f6b836c7..b1c41a0b6d16 100644 --- a/src/skills/workshop/frontmatter.ts +++ b/src/skills/workshop/frontmatter.ts @@ -1,6 +1,6 @@ // Workshop frontmatter helpers parse generated skill metadata before saving drafts. import { extractFrontmatterBlock } from "../../../packages/markdown-core/src/frontmatter.js"; -import { parseFrontmatter } from "../loading/frontmatter.js"; +import { parseSkillFrontmatter } from "../loading/frontmatter.js"; type ProposalFrontmatter = { name: string; @@ -54,7 +54,7 @@ export function renderProposalMarkdown(params: { } export function readProposalFrontmatter(content: string): ProposalFrontmatter | null { - const frontmatter = parseFrontmatter(content); + const frontmatter = parseSkillFrontmatter(content); const name = frontmatter.name?.trim(); const description = frontmatter.description?.trim(); const status = frontmatter.status?.trim().toLowerCase(); diff --git a/src/system-agent/operations-execution-helpers.ts b/src/system-agent/operations-execution-helpers.ts index 9e3eadf1cbfb..dea1796f8101 100644 --- a/src/system-agent/operations-execution-helpers.ts +++ b/src/system-agent/operations-execution-helpers.ts @@ -97,9 +97,9 @@ export async function runGatewayLifecycle( surface?: "cli" | "gateway", ): Promise { if (operation === "restart" && surface === "gateway") { - const { requestSafeGatewayRestart } = await import("../infra/restart-coordinator.js"); + const { scheduleSafeGatewayRestart } = await import("../infra/restart-coordinator.js"); // In-process ownership prevents remote URL/config overrides from restarting another Gateway. - return requestSafeGatewayRestart({ reason: "gateway.restart.safe", delayMs: 0 }).ok; + return scheduleSafeGatewayRestart({ reason: "gateway.restart.safe", delayMs: 0 }).ok; } const lifecycle = await import("../cli/daemon-cli/lifecycle.js"); if (operation === "start") { diff --git a/src/talk/client-voice-session.ts b/src/talk/client-voice-session.ts index e4204e1a4db8..c30ca8bcabf5 100644 --- a/src/talk/client-voice-session.ts +++ b/src/talk/client-voice-session.ts @@ -3,7 +3,7 @@ import { createHash, randomUUID } from "node:crypto"; import { appendTranscriptMessage, loadSessionEntryReadOnly, - patchSessionEntry, + patchSessionEntryCore, } from "../config/sessions/session-accessor.js"; import { buildSessionCreationStamp } from "../config/sessions/session-entry-provenance.js"; import { mergeSessionEntry } from "../config/sessions/types.js"; @@ -236,7 +236,7 @@ export async function ensureClientVoiceAgentSessionEntry(params: { sessionKey: string; deadlineAt?: number; }): Promise { - const created = await patchSessionEntry( + const created = await patchSessionEntryCore( params, (_entry, context) => { // Browser credentials can be short-lived. Check at the authoritative diff --git a/src/test-utils/bundled-plugin-public-surface.ts b/src/test-utils/bundled-plugin-public-surface.ts index 8f997dad6390..2713d33d248b 100644 --- a/src/test-utils/bundled-plugin-public-surface.ts +++ b/src/test-utils/bundled-plugin-public-surface.ts @@ -70,7 +70,7 @@ type AsyncBundledPluginPublicSurfaceLoader = (params: { artifactBasename: string; }) => Promise; -export const loadBundledPluginPublicSurface: AsyncBundledPluginPublicSurfaceLoader = (params) => { +export const loadBundledPluginFacade: AsyncBundledPluginPublicSurfaceLoader = (params) => { const metadata = findBundledPluginMetadata(params.pluginId); return loadBundledPluginPublicSurfaceModule({ dirName: metadata.dirName, diff --git a/src/trajectory/export.ts b/src/trajectory/export.ts index 83c85e21f4c8..b92beed1aada 100644 --- a/src/trajectory/export.ts +++ b/src/trajectory/export.ts @@ -10,7 +10,7 @@ import type { FileEntry, SessionEntry, SessionHeader } from "../agents/sessions/ import { resolveStateDir } from "../config/paths.js"; import { parseSqliteSessionFileMarker } from "../config/sessions/legacy-sqlite-marker.js"; import { - listSessionEntries, + listSessionEntriesCore, loadSessionEntry, loadTranscriptEvents, type SessionTranscriptRuntimeTarget, @@ -293,7 +293,7 @@ async function readSessionEntries(params: { storePath: marker.storePath, }) : undefined; - const markerMatches = listSessionEntries({ + const markerMatches = listSessionEntriesCore({ agentId: marker.agentId, storePath: marker.storePath, }).filter(({ entry }) => entry.sessionId === marker.sessionId); diff --git a/src/tts/tts-payload.ts b/src/tts/tts-payload.ts index c453ba99ad54..9e180e6182ff 100644 --- a/src/tts/tts-payload.ts +++ b/src/tts/tts-payload.ts @@ -79,7 +79,7 @@ function hasLegacyFinalMediaDirective(text: string): boolean { return /(?:^|\n)\s*MEDIA\s*:/i.test(text); } -export async function maybeApplyTtsToPayload( +export async function maybeApplyTtsToPayloadCore( params: { payload: ReplyPayload; cfg: OpenClawConfig; diff --git a/src/tts/tts-runtime.test-support.ts b/src/tts/tts-runtime.test-support.ts index 577e04d895c3..3cb6d4cf56ac 100644 --- a/src/tts/tts-runtime.test-support.ts +++ b/src/tts/tts-runtime.test-support.ts @@ -135,8 +135,7 @@ export const { textToSpeechStream, textToSpeechTelephony, } = await import("./runtime-api.js"); -export const { maybeApplyTtsToPayload: maybeApplyTtsToPayloadCore } = - await import("./tts-payload.js"); +export const { maybeApplyTtsToPayloadCore } = await import("./tts-payload.js"); export const { textToSpeech: textToSpeechCore } = await import("./tts-synthesis.js"); export const CODE_HEAVY_SPOKEN_FALLBACK = CODE_HEAVY_SPOKEN_FALLBACK_CORE; diff --git a/src/tts/tts.ts b/src/tts/tts.ts index 22b7b9321d8c..a46db92a3640 100644 --- a/src/tts/tts.ts +++ b/src/tts/tts.ts @@ -6,7 +6,7 @@ import { setTtsMachinePrefsPathResolver, } from "./runtime-api.js"; import { persistTtsAudioToMediaStore } from "./tts-audio-store.js"; -import { maybeApplyTtsToPayload as maybeApplyTtsToPayloadCore } from "./tts-payload.js"; +import { maybeApplyTtsToPayloadCore } from "./tts-payload.js"; import { textToSpeech as textToSpeechCore } from "./tts-synthesis.js"; setSpeechRuntimeAvailabilityGuard(() => { diff --git a/src/tui/embedded-backend.test.ts b/src/tui/embedded-backend.test.ts index d54ea19eee2c..cf1bbbd76304 100644 --- a/src/tui/embedded-backend.test.ts +++ b/src/tui/embedded-backend.test.ts @@ -228,7 +228,7 @@ vi.mock("../gateway/session-utils.js", () => ({ getSessionDefaults: () => getSessionDefaultsMock(), listAgentsForGateway: () => [], listSessionsFromStoreAsync: (...args: unknown[]) => listSessionsFromStoreAsyncMock(...args), - loadCombinedSessionStoreForGateway: (...args: unknown[]) => + loadCombinedSessionStoreForGatewayCore: (...args: unknown[]) => loadCombinedSessionStoreForGatewayMock(...args), loadSessionEntry: (sessionKey: string, opts?: { agentId?: string }) => loadSessionEntryMock(sessionKey, opts), diff --git a/src/tui/embedded-backend.ts b/src/tui/embedded-backend.ts index 3a8a9a4051fb..42babba3c00a 100644 --- a/src/tui/embedded-backend.ts +++ b/src/tui/embedded-backend.ts @@ -73,7 +73,7 @@ import { getSessionDefaults, listAgentsForGateway, listSessionsFromStoreAsync, - loadCombinedSessionStoreForGateway, + loadCombinedSessionStoreForGatewayCore, loadSessionEntry, loadSessionEntryReadOnly, resolveCanonicalGatewaySessionStoreKey, @@ -732,7 +732,7 @@ export class EmbeddedTuiBackend implements TuiBackend { async listSessions(opts?: Parameters[0]): Promise { await this.ready; const cfg = getRuntimeConfig(); - const { storePath, store } = loadCombinedSessionStoreForGateway(cfg, { + const { storePath, store } = loadCombinedSessionStoreForGatewayCore(cfg, { agentId: opts?.agentId, projection: "list", }); diff --git a/src/video-generation/live-test-helpers.test.ts b/src/video-generation/live-test-helpers.test.ts index 27dcab9887fc..cccd006c1eec 100644 --- a/src/video-generation/live-test-helpers.test.ts +++ b/src/video-generation/live-test-helpers.test.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../config/types.js"; import { canRunBufferBackedImageToVideoLiveLane, canRunBufferBackedVideoToVideoLiveLane, - parseCsvFilter, + parseVideoProviderFilter, parseProviderModelMap, redactLiveApiKey, resolveConfiguredLiveVideoModels, @@ -24,9 +24,9 @@ describe("video-generation live-test helpers", () => { }); it("parses provider filters and treats empty/all as unfiltered", () => { - expect(parseCsvFilter()).toBeNull(); - expect(parseCsvFilter("all")).toBeNull(); - expect(parseCsvFilter(" google , openai ")).toEqual(new Set(["google", "openai"])); + expect(parseVideoProviderFilter()).toBeNull(); + expect(parseVideoProviderFilter("all")).toBeNull(); + expect(parseVideoProviderFilter(" google , openai ")).toEqual(new Set(["google", "openai"])); }); it("parses provider model overrides by provider id", () => { diff --git a/src/video-generation/live-test-helpers.ts b/src/video-generation/live-test-helpers.ts index 7aeddf66fb12..2c27d6546782 100644 --- a/src/video-generation/live-test-helpers.ts +++ b/src/video-generation/live-test-helpers.ts @@ -56,7 +56,7 @@ export function resolveLiveVideoResolution(params: { return "480P"; } -export function parseCsvFilter(raw?: string): Set | null { +export function parseVideoProviderFilter(raw?: string): Set | null { return parseLiveCsvFilter(raw); } diff --git a/src/worker/browser-runtime.test.ts b/src/worker/browser-runtime.test.ts index 24e7793f790d..798ac61cdd86 100644 --- a/src/worker/browser-runtime.test.ts +++ b/src/worker/browser-runtime.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ execFile: vi.fn(), - loadBundledPluginPublicSurfaceModuleSync: vi.fn(), + loadBundledPluginPublicSurfaceModuleSyncCore: vi.fn(), })); vi.mock("node:child_process", () => ({ @@ -10,7 +10,7 @@ vi.mock("node:child_process", () => ({ })); vi.mock("../plugin-sdk/facade-loader.js", () => ({ - loadBundledPluginPublicSurfaceModuleSync: mocks.loadBundledPluginPublicSurfaceModuleSync, + loadBundledPluginPublicSurfaceModuleSyncCore: mocks.loadBundledPluginPublicSurfaceModuleSyncCore, })); import { createWorkerBrowserToolRuntime } from "./browser-runtime.js"; @@ -26,7 +26,7 @@ describe("worker Browser runtime", () => { tool: { name: "browser" }, dispose, }); - mocks.loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + mocks.loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ createAttachedBrowserToolRuntime, }); mocks.execFile.mockImplementation( @@ -51,7 +51,7 @@ describe("worker Browser runtime", () => { workspaceDir: "/tmp/workspace", }); - expect(mocks.loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + expect(mocks.loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledWith({ dirName: "browser", artifactBasename: "runtime-api.js", trackedPluginId: "browser", @@ -87,7 +87,7 @@ describe("worker Browser runtime", () => { tool: { name: "browser" }, dispose: vi.fn().mockResolvedValue(undefined), }); - mocks.loadBundledPluginPublicSurfaceModuleSync.mockReturnValue({ + mocks.loadBundledPluginPublicSurfaceModuleSyncCore.mockReturnValue({ createAttachedBrowserToolRuntime, }); mocks.execFile.mockImplementation( @@ -117,6 +117,6 @@ describe("worker Browser runtime", () => { await expect(ensureAttachTarget()).rejects.toThrow( "Worker Browser launcher failed: launcher timed out", ); - expect(mocks.loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledOnce(); + expect(mocks.loadBundledPluginPublicSurfaceModuleSyncCore).toHaveBeenCalledOnce(); }); }); diff --git a/src/worker/browser-runtime.ts b/src/worker/browser-runtime.ts index 4b9b525c7d61..a73ba7726679 100644 --- a/src/worker/browser-runtime.ts +++ b/src/worker/browser-runtime.ts @@ -1,7 +1,7 @@ /** Core-private adapter for the bundled Browser plugin's attached worker runtime. */ import { execFile } from "node:child_process"; import type { AnyAgentTool } from "../agents/tools/common.js"; -import { loadBundledPluginPublicSurfaceModuleSync } from "../plugin-sdk/facade-loader.js"; +import { loadBundledPluginPublicSurfaceModuleSyncCore } from "../plugin-sdk/facade-loader.js"; import type { WorkerBrowserLaunchDescriptor } from "./launch-descriptor.js"; const WORKER_BROWSER_LAUNCH_TIMEOUT_MS = 30_000; @@ -61,11 +61,13 @@ function runWorkerBrowserLauncher(launcherPath: string): Promise { export async function createWorkerBrowserToolRuntime( params: CreateWorkerBrowserToolRuntimeParams, ): Promise { - const browserRuntime = loadBundledPluginPublicSurfaceModuleSync({ - dirName: "browser", - artifactBasename: "runtime-api.js", - trackedPluginId: "browser", - }); + const browserRuntime = loadBundledPluginPublicSurfaceModuleSyncCore( + { + dirName: "browser", + artifactBasename: "runtime-api.js", + trackedPluginId: "browser", + }, + ); return await browserRuntime.createAttachedBrowserToolRuntime({ cdpUrl: params.descriptor.cdpUrl, ensureAttachTarget: async () => await runWorkerBrowserLauncher(params.descriptor.launcherPath), diff --git a/test/e2e/qa-lab/runtime/codex-auth-product-proof.e2e.test.ts b/test/e2e/qa-lab/runtime/codex-auth-product-proof.e2e.test.ts index 8396be9a8648..0ad4156ed944 100644 --- a/test/e2e/qa-lab/runtime/codex-auth-product-proof.e2e.test.ts +++ b/test/e2e/qa-lab/runtime/codex-auth-product-proof.e2e.test.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createJsonlRequestTailer } from "../../../../scripts/e2e/lib/codex-media-path/jsonl-request-tail.mts"; import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { loadBundledPluginPublicSurface } from "../../../../src/test-utils/bundled-plugin-public-surface.js"; +import { loadBundledPluginFacade } from "../../../../src/test-utils/bundled-plugin-public-surface.js"; import { connectGatewayStatusClient, postJson } from "../../../helpers/gateway-e2e-harness.js"; import { createOpenClawTestInstance, @@ -110,7 +110,7 @@ describe("Codex auth product proof", () => { "repairs mixed legacy auth into SQLite and sends the selected OAuth profile to app-server", { timeout: 180_000 }, async () => { - const { CODEX_APP_SERVER_VERSION } = await loadBundledPluginPublicSurface<{ + const { CODEX_APP_SERVER_VERSION } = await loadBundledPluginFacade<{ CODEX_APP_SERVER_VERSION: string; }>({ pluginId: "codex", artifactBasename: "test-api.js" }); const appServerFixture = fileURLToPath( diff --git a/test/embedded-transcript-cursor.e2e.test.ts b/test/embedded-transcript-cursor.e2e.test.ts index 7baa66f91949..b6f61fe10a9e 100644 --- a/test/embedded-transcript-cursor.e2e.test.ts +++ b/test/embedded-transcript-cursor.e2e.test.ts @@ -4,7 +4,10 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht import path from "node:path"; import { readSessionTranscriptRawDelta } from "openclaw/plugin-sdk/session-transcript-runtime"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { listSessionEntries, loadSessionEntry } from "../src/config/sessions/session-accessor.js"; +import { + listSessionEntriesCore, + loadSessionEntry, +} from "../src/config/sessions/session-accessor.js"; import type { OpenClawConfig } from "../src/config/types.openclaw.js"; import { connectGatewayClient, disconnectGatewayClient } from "../src/gateway/test-helpers.e2e.js"; import { @@ -56,7 +59,7 @@ describe("embedded transcript cursor settlement", () => { const storePath = path.join(instance.state.sessionsDir("main"), "sessions.json"); const sessionId = await waitForSessionId(storePath); expect( - listSessionEntries({ agentId: "main", storePath }).map((entry) => entry.sessionKey), + listSessionEntriesCore({ agentId: "main", storePath }).map((entry) => entry.sessionKey), instance.logs(), ).toEqual([SESSION_KEY]); const target = { agentId: "main", sessionId, sessionKey: SESSION_KEY, storePath }; diff --git a/test/helpers/media-generation/bundled-provider-builders.ts b/test/helpers/media-generation/bundled-provider-builders.ts index cead125af2d1..25ad397dca10 100644 --- a/test/helpers/media-generation/bundled-provider-builders.ts +++ b/test/helpers/media-generation/bundled-provider-builders.ts @@ -1,6 +1,6 @@ // Media generation provider builders create bundled provider fixtures for tests. import type { OpenClawPluginApi } from "../../../src/plugins/types.js"; -import { loadBundledPluginPublicSurface } from "../../../src/test-utils/bundled-plugin-public-surface.js"; +import { loadBundledPluginFacade } from "../../../src/test-utils/bundled-plugin-public-surface.js"; // Public-surface loader for bundled media provider plugin tests. @@ -14,7 +14,7 @@ type BundledPluginEntryModule = { export async function loadBundledProviderPlugin( pluginId: string, ): Promise { - const module = await loadBundledPluginPublicSurface({ + const module = await loadBundledPluginFacade({ pluginId, artifactBasename: "index.js", }); diff --git a/test/image-generation.runtime.live.test.ts b/test/image-generation.runtime.live.test.ts index ba2b892372e4..cafd9e3c92ff 100644 --- a/test/image-generation.runtime.live.test.ts +++ b/test/image-generation.runtime.live.test.ts @@ -13,7 +13,7 @@ import { loadConfig, type OpenClawConfig } from "../src/config/config.js"; import { DEFAULT_LIVE_IMAGE_MODELS, parseCaseFilter, - parseCsvFilter, + parseImageProviderFilter, parseProviderModelMap, resolveConfiguredLiveImageModels, resolveLiveImageAuthStore, @@ -28,7 +28,9 @@ const LIVE = isLiveTestEnabled(); const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled() || isTruthyEnvValue(process.env.OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS); const describeLive = LIVE ? describe : describe.skip; -const providerFilter = parseCsvFilter(process.env.OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS); +const providerFilter = parseImageProviderFilter( + process.env.OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS, +); const caseFilter = parseCaseFilter(process.env.OPENCLAW_LIVE_IMAGE_GENERATION_CASES); const envModelMap = parseProviderModelMap(process.env.OPENCLAW_LIVE_IMAGE_GENERATION_MODELS); const DEFAULT_LIVE_IMAGE_GENERATION_TIMEOUT_MS = 120_000; diff --git a/test/plugins/bundled-provider-auth-literal-parity.test.ts b/test/plugins/bundled-provider-auth-literal-parity.test.ts index 4bc68041edbc..ca57e3b1f0da 100644 --- a/test/plugins/bundled-provider-auth-literal-parity.test.ts +++ b/test/plugins/bundled-provider-auth-literal-parity.test.ts @@ -77,14 +77,14 @@ async function loadPluginRegister(pluginId: string): Promise { // Dynamic import keeps this file out of the unit-fast lane: loading built // plugin dists pulls large module graphs into the shared worker cache and // breaks co-resident vi.mock-based unit tests (observed with memory-host-sdk). - const { loadBundledPluginPublicSurface, resolveBundledPluginPublicModulePath } = + const { loadBundledPluginFacade, resolveBundledPluginPublicModulePath } = await import("../../src/test-utils/bundled-plugin-public-surface.js"); // Resolve first so unknown plugin ids fail with a clear path error before import. resolveBundledPluginPublicModulePath({ pluginId, artifactBasename: "index.js", }); - const mod = await loadBundledPluginPublicSurface({ + const mod = await loadBundledPluginFacade({ pluginId, artifactBasename: "index.js", }); diff --git a/test/scripts/check-session-accessor-boundary.test.ts b/test/scripts/check-session-accessor-boundary.test.ts index 50becf3ffc41..696b8c5c2728 100644 --- a/test/scripts/check-session-accessor-boundary.test.ts +++ b/test/scripts/check-session-accessor-boundary.test.ts @@ -32,14 +32,14 @@ describe("session accessor boundary guard", () => { ).toBe(true); expect( findReadOnlySessionAccessorViolations(` - import { listSessionEntries, loadSessionEntry } from "../config/sessions/session-accessor.js"; - listSessionEntries({ storePath }); + import { listSessionEntriesCore, loadSessionEntry } from "../config/sessions/session-accessor.js"; + listSessionEntriesCore({ storePath }); sessionUtils.loadSessionEntry(sessionKey); `), ).toEqual([ - { line: 2, reason: 'imports materializing session entry accessor "listSessionEntries"' }, + { line: 2, reason: 'imports materializing session entry accessor "listSessionEntriesCore"' }, { line: 2, reason: 'imports materializing session entry accessor "loadSessionEntry"' }, - { line: 3, reason: 'calls materializing session entry accessor "listSessionEntries"' }, + { line: 3, reason: 'calls materializing session entry accessor "listSessionEntriesCore"' }, { line: 4, reason: 'references materializing session entry accessor "loadSessionEntry"' }, ]); expect( @@ -400,8 +400,8 @@ describe("session accessor boundary guard", () => { it("allows migrated accessor reads", () => { expect( findSessionAccessorBoundaryViolations(` - import { listSessionEntries } from "../config/sessions/session-accessor.js"; - listSessionEntries({ storePath }); + import { listSessionEntriesCore } from "../config/sessions/session-accessor.js"; + listSessionEntriesCore({ storePath }); `), ).toEqual([]); }); @@ -453,7 +453,7 @@ describe("session accessor boundary guard", () => { expect( findMemoryHostSessionCorpusBoundaryViolations(` function listSessionTranscriptCorpusEntriesForAgentSync(agentId) { - return listSessionEntries({ agentId }); + return listSessionEntriesCore({ agentId }); } export async function listSessionFilesForAgent(agentId) { return (await listSessionTranscriptCorpusEntriesForAgent(agentId)).map((entry) => entry.sessionFile); diff --git a/ui/src/lib/agents/display.ts b/ui/src/lib/agents/display.ts index 852d377af2ae..d4658cc51b8e 100644 --- a/ui/src/lib/agents/display.ts +++ b/ui/src/lib/agents/display.ts @@ -3,7 +3,7 @@ import { formatByteSize } from "@openclaw/normalization-core"; import { html, nothing } from "lit"; import { expandToolGroups, - normalizeToolName, + normalizeToolPolicyName, resolveToolProfilePolicy, } from "../../../../src/agents/tool-policy-shared.js"; import type { @@ -628,7 +628,7 @@ type CompiledPattern = | { kind: "regex"; value: RegExp }; function compilePattern(pattern: string): CompiledPattern { - const normalized = normalizeToolName(pattern); + const normalized = normalizeToolPolicyName(pattern); if (!normalized) { return { kind: "exact", value: "" }; } @@ -672,7 +672,7 @@ export function isAllowedByPolicy(name: string, policy?: ToolPolicy) { if (!policy) { return true; } - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); const deny = compilePatterns(policy.deny); if (matchesAny(normalized, deny)) { return false; @@ -694,7 +694,7 @@ export function matchesList(name: string, list?: string[]) { if (!Array.isArray(list) || list.length === 0) { return false; } - const normalized = normalizeToolName(name); + const normalized = normalizeToolPolicyName(name); const patterns = compilePatterns(list); if (matchesAny(normalized, patterns)) { return true; diff --git a/ui/src/lib/chat/tool-display.ts b/ui/src/lib/chat/tool-display.ts index 0bed3ff5267b..829112d099c2 100644 --- a/ui/src/lib/chat/tool-display.ts +++ b/ui/src/lib/chat/tool-display.ts @@ -3,7 +3,7 @@ import SHARED_TOOL_DISPLAY_JSON from "../../../../apps/shared/OpenClawKit/Source import { defaultTitle, formatToolDetailText, - normalizeToolName, + normalizeToolDisplayName, resolveToolVerbAndDetailForArgs, type ToolDisplaySpec as ToolDisplaySpecBase, } from "../../../../src/agents/tool-display-common.js"; @@ -111,7 +111,7 @@ export function resolveToolDisplay(params: { meta?: string; detailMode?: ToolDetailMode; }): ToolDisplay { - const name = normalizeToolName(params.name); + const name = normalizeToolDisplayName(params.name); const key = normalizeLowercaseStringOrEmpty(name); const spec = TOOL_MAP[key]; const icon = spec?.icon ?? FALLBACK.icon ?? "puzzle"; diff --git a/ui/src/pages/agents/panels-tools-skills.ts b/ui/src/pages/agents/panels-tools-skills.ts index 4bc4e9c8dbef..5bf8ac207ad5 100644 --- a/ui/src/pages/agents/panels-tools-skills.ts +++ b/ui/src/pages/agents/panels-tools-skills.ts @@ -1,6 +1,6 @@ // Control UI view renders agents panels tools skills screen content. import { html, nothing } from "lit"; -import { normalizeToolName } from "../../../../src/agents/tool-policy-shared.js"; +import { normalizeToolPolicyName } from "../../../../src/agents/tool-policy-shared.js"; import type { SkillStatusEntry, SkillStatusReport, @@ -132,7 +132,7 @@ function formatToolRuntimeSummary(params: { } function toToolAnchorId(toolId: string) { - const safe = normalizeToolName(toolId).replace(/[^a-z0-9_-]+/g, "-"); + const safe = normalizeToolPolicyName(toolId).replace(/[^a-z0-9_-]+/g, "-"); return `agent-tool-${safe}`; } @@ -291,7 +291,7 @@ export function renderAgentTools(params: { ? flattenEffectiveTools(params.toolsEffectiveResult?.groups) : []; const uniqueEffectiveTools = Array.from( - new Map(effectiveTools.map((tool) => [normalizeToolName(tool.id), tool])).values(), + new Map(effectiveTools.map((tool) => [normalizeToolPolicyName(tool.id), tool])).values(), ); const visibleEffectiveTools = uniqueEffectiveTools.slice(0, MAX_RUNTIME_TOOL_CHIPS); const hiddenEffectiveToolCount = Math.max( @@ -300,14 +300,14 @@ export function renderAgentTools(params: { ); const liveToolCount = uniqueEffectiveTools.length; const activeToolMap = new Map( - effectiveTools.map((tool) => [normalizeToolName(tool.id), tool] as const), + effectiveTools.map((tool) => [normalizeToolPolicyName(tool.id), tool] as const), ); const activeToolIds = new Set(activeToolMap.keys()); const sortSectionTools = (tools: AgentToolEntry[]) => tools.toSorted((left, right) => { - const leftId = normalizeToolName(left.id); - const rightId = normalizeToolName(right.id); + const leftId = normalizeToolPolicyName(left.id); + const rightId = normalizeToolPolicyName(right.id); const leftActive = activeToolIds.has(leftId) ? 1 : 0; const rightActive = activeToolIds.has(rightId) ? 1 : 0; if (leftActive !== rightActive) { @@ -323,13 +323,13 @@ export function renderAgentTools(params: { const updateTool = (toolId: string, nextEnabled: boolean) => { const nextAllow = new Set( - alsoAllow.map((entry) => normalizeToolName(entry)).filter((entry) => entry.length > 0), + alsoAllow.map((entry) => normalizeToolPolicyName(entry)).filter((entry) => entry.length > 0), ); const nextDeny = new Set( - deny.map((entry) => normalizeToolName(entry)).filter((entry) => entry.length > 0), + deny.map((entry) => normalizeToolPolicyName(entry)).filter((entry) => entry.length > 0), ); const baseAllowed = resolveAllowed(toolId).baseAllowed; - const normalized = normalizeToolName(toolId); + const normalized = normalizeToolPolicyName(toolId); if (nextEnabled) { nextDeny.delete(normalized); if (!baseAllowed) { @@ -344,14 +344,14 @@ export function renderAgentTools(params: { const updateAll = (nextEnabled: boolean) => { const nextAllow = new Set( - alsoAllow.map((entry) => normalizeToolName(entry)).filter((entry) => entry.length > 0), + alsoAllow.map((entry) => normalizeToolPolicyName(entry)).filter((entry) => entry.length > 0), ); const nextDeny = new Set( - deny.map((entry) => normalizeToolName(entry)).filter((entry) => entry.length > 0), + deny.map((entry) => normalizeToolPolicyName(entry)).filter((entry) => entry.length > 0), ); for (const toolId of toolIds) { const baseAllowed = resolveAllowed(toolId).baseAllowed; - const normalized = normalizeToolName(toolId); + const normalized = normalizeToolPolicyName(toolId); if (nextEnabled) { nextDeny.delete(normalized); if (!baseAllowed) { @@ -523,7 +523,7 @@ export function renderAgentTools(params: { (tool) => resolveAllowed(tool.id).allowed, ).length; const activeSectionCount = section.tools.filter((tool) => - activeToolIds.has(normalizeToolName(tool.id)), + activeToolIds.has(normalizeToolPolicyName(tool.id)), ).length; const previewTools = sortedTools.slice(0, 4); const remainingPreviewCount = Math.max(0, sortedTools.length - previewTools.length); @@ -591,7 +591,7 @@ export function renderAgentTools(params: { ${sortedTools.map((tool) => { const anchorId = toToolAnchorId(tool.id); const resolved = resolveAllowed(tool.id); - const activeEntry = activeToolMap.get(normalizeToolName(tool.id)) ?? null; + const activeEntry = activeToolMap.get(normalizeToolPolicyName(tool.id)) ?? null; const defaultProfiles = tool.defaultProfiles ?? []; const rowBadges = buildRowStatusBadges({ section,