diff --git a/CHANGELOG.md b/CHANGELOG.md index b715e0bf27a..fd8773edab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -170,6 +170,7 @@ Docs: https://docs.openclaw.ai - Providers/DeepSeek: expose native DeepSeek V4 `xhigh` and `max` thinking levels through the provider `resolveThinkingProfile` hook so `/think xhigh|max` applies the intended effort instead of falling back to base levels. (#73008) Thanks @ai-hpc. - Agents/Codex: bound embedded-run cleanup, trajectory flushing, and command-lane task timeouts after runtime failures, so Discord and other chat sessions return to idle instead of staying stuck in processing. Thanks @vincentkoc. - Heartbeat/exec: consume successful metadata-only async exec completions silently so Telegram and other chat surfaces no longer ask users for missing command logs after `No session found`. Fixes #74595. Thanks @gkoch02. +- Active Memory/Memory: materialize allowlisted memory plugin tools for lightweight embedded recall runs so Memory Core tools do not collapse to an empty runtime allowlist. Fixes #74572. (#74592) Thanks @LaFleurAdvertising and @vyctorbrzezowski. - Web fetch: add a documented `tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange` opt-in and thread it through cache keys and DNS/IP checks so trusted fake-IP proxy stacks using `fc00::/7` can work without broad private-network access. Fixes #74351. Thanks @jeffrey701. - OpenAI Codex: restore `/verbose full` persistence and app-server tool-output forwarding, and retry Gateway E2E temp-home cleanup so debug runs do not regress on stale validation or cleanup flakes. Thanks @vincentkoc. - Anthropic/Meridian: preserve text and thinking content seeded on `content_block_start` in anthropic-messages streams, so `[thinking, text]` replies no longer persist as empty turns or trigger empty-response fallbacks. Fixes #74410. Thanks @vyctorbrzezowski. diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 8dd18a641c1..db134ced46e 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -1039,6 +1039,7 @@ describe("active-memory plugin", () => { "If memory_recall is unavailable, use memory_search and memory_get.", ); expect(runParams?.toolsAllow).toEqual(["memory_recall", "memory_search", "memory_get"]); + expect(runParams?.allowGatewaySubagentBinding).toBe(true); expect(runParams?.prompt).toContain( "When searching for preference or habit recall, use a permissive recall limit or memory_search threshold before deciding that no useful memory exists.", ); diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index d8b0194fdfa..24639081981 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -2138,6 +2138,7 @@ async function runRecallSubagent(params: { trigger: "manual", toolsAllow: ["memory_recall", "memory_search", "memory_get"], disableMessageTool: true, + allowGatewaySubagentBinding: true, bootstrapContextMode: "lightweight", verboseLevel: "off", thinkLevel: params.config.thinking, diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index b55712c86cf..31a88f23156 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -30,6 +30,7 @@ import { wrapStreamFnSanitizeMalformedToolCalls, wrapStreamFnTrimToolCallNames, } from "./attempt.js"; +import { buildEmbeddedAttemptToolRunContext } from "./attempt.tool-run-context.js"; type FakeWrappedStream = { result: () => Promise; @@ -82,6 +83,24 @@ describe("applyEmbeddedAttemptToolsAllow", () => { }); }); +describe("buildEmbeddedAttemptToolRunContext", () => { + it("carries runtime toolsAllow into coding tool construction", () => { + expect( + buildEmbeddedAttemptToolRunContext({ + trigger: "manual", + jobId: "job-1", + memoryFlushWritePath: "memory/log.md", + toolsAllow: ["memory_search", "memory_get"], + }), + ).toMatchObject({ + trigger: "manual", + jobId: "job-1", + memoryFlushWritePath: "memory/log.md", + runtimeToolAllowlist: ["memory_search", "memory_get"], + }); + }); +}); + describe("normalizeMessagesForLlmBoundary", () => { it("strips tool result details before provider conversion", () => { const input = [ diff --git a/src/agents/pi-embedded-runner/run/attempt.tool-run-context.ts b/src/agents/pi-embedded-runner/run/attempt.tool-run-context.ts index be92832f32d..577fdcea6a1 100644 --- a/src/agents/pi-embedded-runner/run/attempt.tool-run-context.ts +++ b/src/agents/pi-embedded-runner/run/attempt.tool-run-context.ts @@ -8,17 +8,20 @@ export function buildEmbeddedAttemptToolRunContext(params: { trigger?: EmbeddedRunTrigger; jobId?: string; memoryFlushWritePath?: string; + toolsAllow?: string[]; trace?: DiagnosticTraceContext; }): { trigger?: EmbeddedRunTrigger; jobId?: string; memoryFlushWritePath?: string; + runtimeToolAllowlist?: string[]; trace?: DiagnosticTraceContext; } { return { trigger: params.trigger, jobId: params.jobId, memoryFlushWritePath: params.memoryFlushWritePath, + ...(params.toolsAllow ? { runtimeToolAllowlist: params.toolsAllow } : {}), ...(params.trace ? { trace: freezeDiagnosticTraceContext(params.trace) } : {}), }; } diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts index 514a8dc3ee4..78960d20f84 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { applyXaiModelCompat, @@ -12,6 +12,7 @@ import { import "./test-helpers/fast-bash-tools.js"; import "./test-helpers/fast-coding-tools.js"; import "./test-helpers/fast-openclaw-tools.js"; +import { createOpenClawTools } from "./openclaw-tools.js"; import { createOpenClawCodingTools } from "./pi-tools.js"; import { createHostSandboxFsBridge } from "./test-helpers/host-sandbox-fs-bridge.js"; import { expectReadWriteEditTools } from "./test-helpers/pi-tools-fs-helpers.js"; @@ -163,6 +164,22 @@ describe("createOpenClawCodingTools", () => { ).toBeNull(); }); + it("uses runtime toolsAllow when materializing plugin tools", () => { + const createOpenClawToolsMock = vi.mocked(createOpenClawTools); + createOpenClawToolsMock.mockClear(); + + createOpenClawCodingTools({ + config: testConfig, + runtimeToolAllowlist: ["memory_search", "memory_get"], + }); + + expect(createOpenClawToolsMock).toHaveBeenCalledWith( + expect.objectContaining({ + pluginToolAllowlist: expect.arrayContaining(["memory_search", "memory_get"]), + }), + ); + }); + it("preserves action enums in normalized schemas", () => { const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); const toolNames = ["canvas", "nodes", "cron", "gateway", "message"]; diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index cfc721d5427..793f948ea59 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -333,6 +333,8 @@ export function createOpenClawCodingTools(options?: { hasRepliedRef?: { value: boolean }; /** Allow plugin tools for this run to late-bind the gateway subagent. */ allowGatewaySubagentBinding?: boolean; + /** Runtime-scoped explicit allowlist used to materialize matching plugin tools. */ + runtimeToolAllowlist?: string[]; /** If true, the model has native vision capability */ modelHasVision?: boolean; /** Require explicit message targets (no implicit last-route sends). */ @@ -629,6 +631,7 @@ export function createOpenClawCodingTools(options?: { groupPolicy, sandboxToolPolicy, subagentPolicy, + options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : undefined, ]), currentChannelId: options?.currentChannelId, currentThreadTs: options?.currentThreadTs, diff --git a/src/agents/test-helpers/fast-openclaw-tools.ts b/src/agents/test-helpers/fast-openclaw-tools.ts index aa71bacc98c..fad86a4c05b 100644 --- a/src/agents/test-helpers/fast-openclaw-tools.ts +++ b/src/agents/test-helpers/fast-openclaw-tools.ts @@ -46,8 +46,10 @@ const coreTools = [ stubTool("pdf"), ]; +const createOpenClawToolsMock = vi.fn(() => coreTools.map((tool) => Object.assign({}, tool))); + vi.mock("../openclaw-tools.js", () => ({ - createOpenClawTools: () => coreTools.map((tool) => Object.assign({}, tool)), + createOpenClawTools: createOpenClawToolsMock, __testing: { setDepsForTest: () => {}, },