




















@@ -74,7 +74,11 @@ import { createSandboxContext } from "./sandbox-exec-server.test-helpers.js";
7474import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
7575import * as sharedClientModule from "./shared-client.js";
7676import { createCodexTestModel } from "./test-support.js";
77-import { buildTurnStartParams, startOrResumeThread } from "./thread-lifecycle.js";
77+import {
78+buildTurnStartParams,
79+codexDynamicToolsFingerprint,
80+startOrResumeThread,
81+} from "./thread-lifecycle.js";
78827983function flushDiagnosticEvents() {
8084return waitForDiagnosticEventsDrained();
@@ -211,7 +215,7 @@ async function buildDynamicToolsForTest(
211215options: Partial<
212216Pick<
213217Parameters<typeof testing.buildDynamicTools>[0],
214-"forceHeartbeatTool" | "ignoreRuntimePlan"
218+"forceHeartbeatTool" | "ignoreDisableMessageTool" | "ignoreRuntimePlan"
215219>
216220> = {},
217221) {
@@ -309,7 +313,7 @@ function createCodexToolBridgeForTest(
309313 tools,
310314 registeredTools,
311315 signal,
312-directToolNames: testing.shouldForceMessageTool(params) ? ["message"] : [],
316+directToolNames: testing.resolveCodexDynamicToolDirectNames(params),
313317});
314318}
315319@@ -1678,6 +1682,64 @@ describe("runCodexAppServerAttempt", () => {
16781682expect(specNames(nextNormalBridge.specs)).toEqual(registeredToolNames);
16791683});
168016841685+it("keeps message in the registered schema when disabled for an internal turn", async () => {
1686+testing.setOpenClawCodingToolsFactoryForTests((options) =>
1687+options?.disableMessageTool ? [] : [createRuntimeDynamicTool("message")],
1688+);
1689+const sessionFile = path.join(tempDir, "session.jsonl");
1690+const workspaceDir = path.join(tempDir, "workspace");
1691+const params = createParams(sessionFile, workspaceDir);
1692+params.disableTools = false;
1693+params.disableMessageTool = true;
1694+params.sourceReplyDeliveryMode = "message_tool_only";
1695+params.runtimePlan = createCodexRuntimePlanFixture();
1696+1697+const availableTools = await buildDynamicToolsForTest(params, workspaceDir);
1698+const registeredTools = await buildDynamicToolsForTest(params, workspaceDir, {
1699+ignoreDisableMessageTool: true,
1700+ignoreRuntimePlan: true,
1701+});
1702+const bridge = createCodexToolBridgeForTest(params, availableTools, registeredTools);
1703+const normalParams = createParams(sessionFile, workspaceDir);
1704+normalParams.disableTools = false;
1705+normalParams.sourceReplyDeliveryMode = "message_tool_only";
1706+normalParams.runtimePlan = createCodexRuntimePlanFixture();
1707+const normalTools = await buildDynamicToolsForTest(normalParams, workspaceDir);
1708+const normalRegisteredTools = await buildDynamicToolsForTest(normalParams, workspaceDir, {
1709+ignoreDisableMessageTool: true,
1710+ignoreRuntimePlan: true,
1711+});
1712+const normalBridge = createCodexToolBridgeForTest(
1713+normalParams,
1714+normalTools,
1715+normalRegisteredTools,
1716+);
1717+1718+expect(bridge.availableSpecs.map((tool) => tool.name)).not.toContain("message");
1719+expect(bridge.specs.map((tool) => tool.name)).toContain("message");
1720+expect(codexDynamicToolsFingerprint(bridge.specs)).toBe(
1721+codexDynamicToolsFingerprint(normalBridge.specs),
1722+);
1723+await expect(
1724+bridge.handleToolCall({
1725+threadId: "thread-1",
1726+turnId: "turn-1",
1727+callId: "call-1",
1728+namespace: null,
1729+tool: "message",
1730+arguments: {},
1731+}),
1732+).resolves.toMatchObject({
1733+success: false,
1734+contentItems: [
1735+{
1736+type: "inputText",
1737+text: "OpenClaw tool is not available for this turn: message",
1738+},
1739+],
1740+});
1741+});
1742+16811743it("keeps the persistent dynamic schema stable across heartbeat-only turns", async () => {
16821744testing.setOpenClawCodingToolsFactoryForTests((options) => [
16831745createRuntimeDynamicTool("message"),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。