diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 82d93fd30f9..49b6ce23d36 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -407,9 +407,7 @@ describe("DiscordVoiceManager", () => { await manager.join({ guildId: "g1", channelId: "1001" }); - const entry = (manager as unknown as { sessions: Map }).sessions.get( - "g1", - ) as + const entry = (manager as unknown as { sessions: Map }).sessions.get("g1") as | { guildId: string; channelId: string; diff --git a/extensions/memory-core/src/memory/manager-search-preflight.ts b/extensions/memory-core/src/memory/manager-search-preflight.ts index cdbf612c03e..087016adca7 100644 --- a/extensions/memory-core/src/memory/manager-search-preflight.ts +++ b/extensions/memory-core/src/memory/manager-search-preflight.ts @@ -1,7 +1,4 @@ -export function resolveMemorySearchPreflight(params: { - query: string; - hasIndexedContent: boolean; -}): +export function resolveMemorySearchPreflight(params: { query: string; hasIndexedContent: boolean }): | { normalizedQuery: string; shouldInitializeProvider: boolean; diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 385815f4d28..a57084e2c6d 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -4,9 +4,9 @@ "description": "OpenClaw Microsoft Teams channel plugin", "type": "module", "dependencies": { - "@sinclair/typebox": "0.34.49", "@microsoft/teams.api": "2.0.7", "@microsoft/teams.apps": "2.0.7", + "@sinclair/typebox": "0.34.49", "express": "^5.2.1", "jsonwebtoken": "^9.0.3", "jwks-rsa": "^4.0.1" diff --git a/src/agents/pi-embedded-runner/compact.hooks.harness.ts b/src/agents/pi-embedded-runner/compact.hooks.harness.ts index 80631d0180f..d8666bcc385 100644 --- a/src/agents/pi-embedded-runner/compact.hooks.harness.ts +++ b/src/agents/pi-embedded-runner/compact.hooks.harness.ts @@ -1,4 +1,5 @@ import { vi, type Mock } from "vitest"; +import { clearAgentHarnesses } from "../harness/registry.js"; type MockResolvedModel = { model: { provider: string; api: string; id: string; input: unknown[] }; @@ -143,6 +144,7 @@ export function resetCompactSessionStateMocks(): void { } export function resetCompactHooksHarnessMocks(): void { + clearAgentHarnesses(); hookRunner.hasHooks.mockReset(); hookRunner.hasHooks.mockReturnValue(false); hookRunner.runBeforeCompaction.mockReset(); @@ -307,13 +309,10 @@ export async function loadCompactHooksHarness(): Promise<{ resolveSessionLockMaxHoldFromTimeout: vi.fn(() => 0), })); - vi.doMock("../../context-engine/index.js", () => ({ - ensureContextEnginesInitialized: vi.fn(), - resolveContextEngine: resolveContextEngineMock, - })); vi.doMock("../../context-engine/init.js", () => ({ ensureContextEnginesInitialized: vi.fn(), })); + vi.doMock("../../context-engine/registry.js", () => ({ resolveContextEngine: resolveContextEngineMock, })); diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 9b04d69736d..935bb22c52e 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -45,15 +45,28 @@ function resolveUserHomeDir(): string | undefined { } function compactSkillPaths(skills: Skill[]): Skill[] { - const home = resolveUserHomeDir(); - if (!home) return skills; - const prefix = home.endsWith(path.sep) ? home : home + path.sep; + const homes = [resolveHomeDir(), resolveUserHomeDir()] + .filter((home): home is string => !!home) + .map((home) => path.resolve(home)) + .filter((home, index, all) => all.indexOf(home) === index) + .sort((a, b) => b.length - a.length); + if (homes.length === 0) return skills; return skills.map((s) => ({ ...s, - filePath: s.filePath.startsWith(prefix) ? "~/" + s.filePath.slice(prefix.length) : s.filePath, + filePath: compactHomePath(s.filePath, homes), })); } +function compactHomePath(filePath: string, homes: readonly string[]): string { + for (const home of homes) { + const prefix = home.endsWith(path.sep) ? home : home + path.sep; + if (filePath.startsWith(prefix)) { + return "~/" + filePath.slice(prefix.length); + } + } + return filePath; +} + function isSkillVisibleInAvailableSkillsPrompt(entry: SkillEntry): boolean { if (entry.exposure) { return entry.exposure.includeInAvailableSkillsPrompt !== false; diff --git a/src/auto-reply/reply/commands-info.test.ts b/src/auto-reply/reply/commands-info.test.ts index 2194a1c1837..def45a6fd6e 100644 --- a/src/auto-reply/reply/commands-info.test.ts +++ b/src/auto-reply/reply/commands-info.test.ts @@ -202,13 +202,10 @@ describe("info command handlers", () => { }); it("preserves the shared session store path when routing /status", async () => { - const params = buildInfoParams( - "/status", - { - commands: { text: true }, - channels: { whatsapp: { allowFrom: ["*"] } }, - } as OpenClawConfig, - ); + const params = buildInfoParams("/status", { + commands: { text: true }, + channels: { whatsapp: { allowFrom: ["*"] } }, + } as OpenClawConfig); params.storePath = "/tmp/target-session-store.json"; const statusResult = await handleStatusCommand(params, true); @@ -222,13 +219,10 @@ describe("info command handlers", () => { }); it("prefers the target session entry when routing /status", async () => { - const params = buildInfoParams( - "/status", - { - commands: { text: true }, - channels: { whatsapp: { allowFrom: ["*"] } }, - } as OpenClawConfig, - ); + const params = buildInfoParams("/status", { + commands: { text: true }, + channels: { whatsapp: { allowFrom: ["*"] } }, + } as OpenClawConfig); params.sessionEntry = { sessionId: "wrapper-session", updatedAt: Date.now(), diff --git a/src/auto-reply/reply/commands-plugin.test.ts b/src/auto-reply/reply/commands-plugin.test.ts index 0f7f156d4ff..97742b4503b 100644 --- a/src/auto-reply/reply/commands-plugin.test.ts +++ b/src/auto-reply/reply/commands-plugin.test.ts @@ -80,13 +80,10 @@ describe("handlePluginCommand", () => { }); executePluginCommandMock.mockResolvedValue({ text: "from plugin" }); - const params = buildPluginParams( - "/card", - { - commands: { text: true }, - channels: { whatsapp: { allowFrom: ["*"] } }, - } as OpenClawConfig, - ); + const params = buildPluginParams("/card", { + commands: { text: true }, + channels: { whatsapp: { allowFrom: ["*"] } }, + } as OpenClawConfig); params.sessionEntry = { sessionId: "wrapper-session", sessionFile: "/tmp/wrapper-session.jsonl", diff --git a/src/auto-reply/reply/commands-reset-hooks.test.ts b/src/auto-reply/reply/commands-reset-hooks.test.ts index 1ca6f6fd809..bd687953583 100644 --- a/src/auto-reply/reply/commands-reset-hooks.test.ts +++ b/src/auto-reply/reply/commands-reset-hooks.test.ts @@ -257,13 +257,10 @@ describe("handleCommands reset hooks", () => { }); it("prefers the target session entry when emitting reset hooks", async () => { - const params = buildResetParams( - "/reset", - { - commands: { text: true }, - channels: { whatsapp: { allowFrom: ["*"] } }, - } as OpenClawConfig, - ); + const params = buildResetParams("/reset", { + commands: { text: true }, + channels: { whatsapp: { allowFrom: ["*"] } }, + } as OpenClawConfig); params.sessionEntry = { sessionId: "wrapper-session", updatedAt: Date.now(), diff --git a/src/auto-reply/reply/commands-subagents-spawn-action.test.ts b/src/auto-reply/reply/commands-subagents-spawn-action.test.ts index 32292a9c634..fee10b8254d 100644 --- a/src/auto-reply/reply/commands-subagents-spawn-action.test.ts +++ b/src/auto-reply/reply/commands-subagents-spawn-action.test.ts @@ -259,8 +259,18 @@ describe("subagents spawn action", () => { ); spawnSubagentDirectMock.mockClear(); - await handleSubagentsSpawnAction( - { + await handleSubagentsSpawnAction({ + ...buildContext({ + requesterKey: "agent:main:target", + sessionEntry: { + sessionId: "wrapper-session", + updatedAt: Date.now(), + groupId: "wrapper-group", + groupChannel: "#wrapper", + space: "wrapper-space", + }, + }), + params: { ...buildContext({ requesterKey: "agent:main:target", sessionEntry: { @@ -270,30 +280,18 @@ describe("subagents spawn action", () => { groupChannel: "#wrapper", space: "wrapper-space", }, - }), - params: { - ...buildContext({ - requesterKey: "agent:main:target", - sessionEntry: { - sessionId: "wrapper-session", - updatedAt: Date.now(), - groupId: "wrapper-group", - groupChannel: "#wrapper", - space: "wrapper-space", - }, - }).params, - sessionStore: { - "agent:main:target": { - sessionId: "target-session", - updatedAt: Date.now(), - groupId: "target-group", - groupChannel: "#target", - space: "target-space", - }, + }).params, + sessionStore: { + "agent:main:target": { + sessionId: "target-session", + updatedAt: Date.now(), + groupId: "target-group", + groupChannel: "#target", + space: "target-space", }, }, }, - ); + }); expect(spawnSubagentDirectMock).toHaveBeenCalledWith( expect.anything(), diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 909a1799fa1..e35088636ba 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -1377,9 +1377,9 @@ export function createNestedChannelParsedAllowFromPrompt(params: { getExistingAllowFrom: ({ cfg }: { cfg: OpenClawConfig }) => params.getExistingAllowFrom?.(cfg) ?? ( - (cfg.channels?.[params.channel] as Record | undefined)?.[ - params.section - ] as { allowFrom?: Array } | undefined + (cfg.channels?.[params.channel] as Record | undefined)?.[params.section] as + | { allowFrom?: Array } + | undefined )?.allowFrom ?? [], ...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}), diff --git a/src/config/plugin-auto-enable.shared.ts b/src/config/plugin-auto-enable.shared.ts index f9d123c0c50..d9a3a7b5641 100644 --- a/src/config/plugin-auto-enable.shared.ts +++ b/src/config/plugin-auto-enable.shared.ts @@ -166,7 +166,7 @@ function resolveProviderPluginsWithOwnedWebSearch( registry: PluginManifestRegistry, ): PluginManifestRecord[] { return registry.plugins - .filter((plugin) => plugin.providers.length > 0) + .filter((plugin) => (plugin.providers?.length ?? 0) > 0) .filter((plugin) => (plugin.contracts?.webSearchProviders?.length ?? 0) > 0); } diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts index 98687cbe9fd..7d6ad86d55f 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts @@ -2534,9 +2534,7 @@ describe("gateway server sessions", () => { expect(deleted.ok).toBe(true); expect(deleted.payload?.deleted).toBe(true); expect(subagentLifecycleHookMocks.runSubagentEnded).toHaveBeenCalledTimes(1); - const event = ( - subagentLifecycleHookMocks.runSubagentEnded.mock.calls as unknown[][] - )[0]?.[0] as + const event = (subagentLifecycleHookMocks.runSubagentEnded.mock.calls as unknown[][])[0]?.[0] as | { targetKind?: string; targetSessionKey?: string; reason?: string; outcome?: string } | undefined; expect(event).toMatchObject({ @@ -2852,9 +2850,7 @@ describe("gateway server sessions", () => { expect(reset.payload?.key).toBe("agent:main:subagent:worker"); expect(reset.payload?.entry.sessionId).not.toBe("sess-subagent"); expect(subagentLifecycleHookMocks.runSubagentEnded).toHaveBeenCalledTimes(1); - const event = ( - subagentLifecycleHookMocks.runSubagentEnded.mock.calls as unknown[][] - )[0]?.[0] as + const event = (subagentLifecycleHookMocks.runSubagentEnded.mock.calls as unknown[][])[0]?.[0] as | { targetKind?: string; targetSessionKey?: string; reason?: string; outcome?: string } | undefined; expect(event).toMatchObject({ diff --git a/src/plugins/text-transforms.runtime.ts b/src/plugins/text-transforms.runtime.ts index fcfbecafe5c..7c5f0185f92 100644 --- a/src/plugins/text-transforms.runtime.ts +++ b/src/plugins/text-transforms.runtime.ts @@ -25,9 +25,9 @@ function loadPluginRuntime(): PluginRuntimeModule | null { } export function resolveRuntimeTextTransforms(): PluginTextTransforms | undefined { - return mergePluginTextTransforms( - ...(loadPluginRuntime() - ?.getActivePluginRegistry() - ?.textTransforms?.map((entry) => entry.transforms) ?? []), - ); + const registry = loadPluginRuntime()?.getActivePluginRegistry(); + const pluginTextTransforms = Array.isArray(registry?.textTransforms) + ? registry.textTransforms.map((entry) => entry.transforms) + : []; + return mergePluginTextTransforms(...pluginTextTransforms); }