mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 14:10:43 +00:00
fix: annotate message-tool-only replies in Codex tool spec
Thread sourceReplyDeliveryMode into Codex/OpenClaw tool construction and annotate the message tool description for message-tool-only turns so visible replies use message(action=send).\n\nAlso adds focused regression coverage and a changelog entry.
This commit is contained in:
committed by
GitHub
parent
657d2331b3
commit
b62166301e
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/voice: add bounded realtime gateway logs for voice channel joins, realtime model/voice selection, transcripts, consult routing/answers, and playback start, allow OpenAI realtime Discord sessions to disable input-triggered response interruption for echo-heavy rooms while keeping explicit Discord barge-in available for new and already-active speakers, and allow voice turns to target an existing Discord channel agent session.
|
||||
- Discord/voice: include a bounded one-line STT transcript preview in verbose voice logs so live voice debugging shows what speakers said before the agent reply.
|
||||
- Codex app-server: pin the managed Codex harness and Codex CLI smoke package to `@openai/codex@0.129.0`, defer OpenClaw integration dynamic tools behind Codex tool search by default, and accept current Codex service-tier values so legacy `fast` settings survive the stable harness upgrade as `priority`.
|
||||
- Codex app-server: annotate message-tool-only direct chat turns in the dynamic `message` tool spec so visible replies are sent through `message(action="send")` instead of staying private. (#79704)
|
||||
- Codex app-server: default implicit local stdio app-server permissions to guardian when Codex system requirements disallow the YOLO approval, reviewer, or sandbox value, including hostname-scoped remote sandbox entries, avoiding turn-start failures on managed hosts that permit only reviewed approval or narrower sandboxes.
|
||||
- Plugins/install: run managed npm-root install, uninstall, prune, and repair commands from the managed root without a redundant `--prefix .`, avoiding npm 10.9.3 Arborist crashes on native Windows WhatsApp plugin installs. Fixes #78514. (#78902) Thanks @melihselamett-stack.
|
||||
- Discord/voice: stream ElevenLabs TTS directly into Discord playback and send ElevenLabs latency optimization as the documented query parameter so spoken replies can start sooner.
|
||||
|
||||
@@ -1821,6 +1821,7 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
|
||||
modelHasVision,
|
||||
requireExplicitMessageTarget:
|
||||
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
||||
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
||||
disableMessageTool: params.disableMessageTool,
|
||||
forceMessageTool: shouldForceMessageTool(params),
|
||||
enableHeartbeatTool: params.trigger === "heartbeat",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SourceReplyDeliveryMode } from "../auto-reply/get-reply-options.types.js";
|
||||
import { selectApplicableRuntimeConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
@@ -117,6 +118,8 @@ export function createOpenClawTools(
|
||||
cronSelfRemoveOnlyJobId?: string;
|
||||
/** Require explicit message targets (no implicit last-route sends). */
|
||||
requireExplicitMessageTarget?: boolean;
|
||||
/** Visible source replies must be sent through the message tool when set to message_tool_only. */
|
||||
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
|
||||
/** If true, omit the message tool from the tool list. */
|
||||
disableMessageTool?: boolean;
|
||||
/** If true, include the heartbeat response tool for structured heartbeat outcomes. */
|
||||
@@ -294,6 +297,7 @@ export function createOpenClawTools(
|
||||
hasRepliedRef: options?.hasRepliedRef,
|
||||
sandboxRoot: options?.sandboxRoot,
|
||||
requireExplicitTarget: options?.requireExplicitMessageTarget,
|
||||
sourceReplyDeliveryMode: options?.sourceReplyDeliveryMode,
|
||||
requesterSenderId: options?.requesterSenderId ?? undefined,
|
||||
senderIsOwner: options?.senderIsOwner,
|
||||
});
|
||||
|
||||
@@ -721,6 +721,7 @@ async function compactEmbeddedPiSessionDirectOnce(
|
||||
workspaceDir: effectiveWorkspace,
|
||||
config: params.config,
|
||||
abortSignal: runAbortController.signal,
|
||||
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
||||
modelProvider: model.provider,
|
||||
modelId,
|
||||
modelCompat: extractModelCompat(effectiveModel),
|
||||
|
||||
@@ -901,6 +901,7 @@ export async function runEmbeddedAttempt(
|
||||
modelHasVision: params.model.input?.includes("image") ?? false,
|
||||
requireExplicitMessageTarget:
|
||||
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
||||
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
||||
disableMessageTool: params.disableMessageTool,
|
||||
forceMessageTool: params.forceMessageTool,
|
||||
enableHeartbeatTool: params.enableHeartbeatTool,
|
||||
|
||||
@@ -200,6 +200,23 @@ describe("createOpenClawCodingTools", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes source reply delivery mode to OpenClaw tool construction", () => {
|
||||
const createOpenClawToolsMock = vi.mocked(createOpenClawTools);
|
||||
createOpenClawToolsMock.mockClear();
|
||||
|
||||
createOpenClawCodingTools({
|
||||
config: testConfig,
|
||||
forceMessageTool: true,
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
});
|
||||
|
||||
expect(createOpenClawToolsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips unrelated tool families when construction is planned from a narrow allowlist", () => {
|
||||
const createOpenClawToolsMock = vi.mocked(createOpenClawTools);
|
||||
createOpenClawToolsMock.mockClear();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createCodingTools, createReadTool } from "@mariozechner/pi-coding-agent";
|
||||
import type { SourceReplyDeliveryMode } from "../auto-reply/get-reply-options.types.js";
|
||||
import { HEARTBEAT_RESPONSE_TOOL_NAME } from "../auto-reply/heartbeat-tool-response.js";
|
||||
import type { ModelCompatConfig } from "../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
@@ -341,6 +342,8 @@ export function createOpenClawCodingTools(options?: {
|
||||
modelHasVision?: boolean;
|
||||
/** Require explicit message targets (no implicit last-route sends). */
|
||||
requireExplicitMessageTarget?: boolean;
|
||||
/** Visible source replies must be sent through the message tool when set to message_tool_only. */
|
||||
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
|
||||
/** If true, omit the message tool from the tool list. */
|
||||
disableMessageTool?: boolean;
|
||||
/** Keep the message tool available even when the selected profile omits it. */
|
||||
@@ -743,6 +746,7 @@ export function createOpenClawCodingTools(options?: {
|
||||
hasRepliedRef: options?.hasRepliedRef,
|
||||
modelHasVision: options?.modelHasVision,
|
||||
requireExplicitMessageTarget: options?.requireExplicitMessageTarget,
|
||||
sourceReplyDeliveryMode: options?.sourceReplyDeliveryMode,
|
||||
disableMessageTool: options?.disableMessageTool,
|
||||
enableHeartbeatTool,
|
||||
disablePluginTools: !includePluginTools,
|
||||
|
||||
@@ -325,6 +325,41 @@ async function executeSend(params: {
|
||||
}
|
||||
|
||||
describe("message tool secret scoping", () => {
|
||||
it("marks message-tool-only source replies in the tool description", () => {
|
||||
const scopedTool = createMessageTool({
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
});
|
||||
const explicitTargetTool = createMessageTool({
|
||||
requireExplicitTarget: true,
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
});
|
||||
const defaultTool = createMessageTool();
|
||||
|
||||
expect(scopedTool.description).toContain(
|
||||
'visible replies to the current source conversation must use action="send"',
|
||||
);
|
||||
expect(scopedTool.description).toContain("target defaults to the current source conversation");
|
||||
expect(scopedTool.description).toContain("Normal final answers are private");
|
||||
expect(explicitTargetTool.description).toContain("Include target when sending");
|
||||
expect(explicitTargetTool.description).not.toContain(
|
||||
"target defaults to the current source conversation",
|
||||
);
|
||||
expect(defaultTool.description).not.toContain(
|
||||
"visible replies to the current source conversation",
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards source reply delivery mode through createOpenClawTools", () => {
|
||||
const tool = createOpenClawTools({
|
||||
config: {} as never,
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
}).find((candidate) => candidate.name === "message");
|
||||
|
||||
expect(tool?.description).toContain(
|
||||
'visible replies to the current source conversation must use action="send"',
|
||||
);
|
||||
});
|
||||
|
||||
it("scopes command-time secret resolution to the selected channel/account", async () => {
|
||||
mockSendResult({ channel: "discord", to: "discord:123" });
|
||||
mocks.getRuntimeConfig.mockReturnValue({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Type, type TSchema } from "typebox";
|
||||
import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options.types.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
channelSupportsMessageCapability,
|
||||
@@ -520,6 +521,7 @@ type MessageToolOptions = {
|
||||
hasRepliedRef?: { value: boolean };
|
||||
sandboxRoot?: string;
|
||||
requireExplicitTarget?: boolean;
|
||||
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
|
||||
requesterSenderId?: string;
|
||||
senderIsOwner?: boolean;
|
||||
};
|
||||
@@ -648,6 +650,8 @@ function buildMessageToolDescription(options?: {
|
||||
sessionKey?: string;
|
||||
sessionId?: string;
|
||||
agentId?: string;
|
||||
requireExplicitTarget?: boolean;
|
||||
sourceReplyDeliveryMode?: SourceReplyDeliveryMode;
|
||||
requesterSenderId?: string;
|
||||
senderIsOwner?: boolean;
|
||||
}): string {
|
||||
@@ -679,13 +683,35 @@ function buildMessageToolDescription(options?: {
|
||||
ChannelMessageActionName | "send"
|
||||
>;
|
||||
return appendMessageToolReadHint(
|
||||
`${baseDescription} Supports actions: ${sortedActions.join(", ")}.`,
|
||||
appendMessageToolVisibleReplyHint(
|
||||
`${baseDescription} Supports actions: ${sortedActions.join(", ")}.`,
|
||||
resolvedOptions.sourceReplyDeliveryMode,
|
||||
resolvedOptions.requireExplicitTarget,
|
||||
),
|
||||
sortedActions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return `${baseDescription} Supports actions: send, delete, react, poll, pin, threads, and more.`;
|
||||
return appendMessageToolVisibleReplyHint(
|
||||
`${baseDescription} Supports actions: send, delete, react, poll, pin, threads, and more.`,
|
||||
resolvedOptions.sourceReplyDeliveryMode,
|
||||
resolvedOptions.requireExplicitTarget,
|
||||
);
|
||||
}
|
||||
|
||||
function appendMessageToolVisibleReplyHint(
|
||||
description: string,
|
||||
sourceReplyDeliveryMode?: SourceReplyDeliveryMode,
|
||||
requireExplicitTarget?: boolean,
|
||||
): string {
|
||||
if (sourceReplyDeliveryMode !== "message_tool_only") {
|
||||
return description;
|
||||
}
|
||||
const targetGuidance = requireExplicitTarget
|
||||
? "Include target when sending."
|
||||
: "The target defaults to the current source conversation, so omit target unless sending elsewhere.";
|
||||
return `${description} For this turn, visible replies to the current source conversation must use action="send" with message. ${targetGuidance} Normal final answers are private and are not posted.`;
|
||||
}
|
||||
|
||||
function appendMessageToolReadHint(
|
||||
@@ -743,6 +769,8 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
|
||||
sessionKey: options?.agentSessionKey,
|
||||
sessionId: options?.sessionId,
|
||||
agentId: resolvedAgentId,
|
||||
requireExplicitTarget: options?.requireExplicitTarget,
|
||||
sourceReplyDeliveryMode: options?.sourceReplyDeliveryMode,
|
||||
requesterSenderId: options?.requesterSenderId,
|
||||
senderIsOwner: options?.senderIsOwner,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user