mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 18:04:32 +00:00
Exec approvals: render forwarded commands in monospace (#11937)
* fix(exec-approvals): format forwarded commands as code * fix(exec-approvals): place fenced command blocks on new line (#11937) (thanks @sebslight)
This commit is contained in:
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Exec approvals: format forwarded command text as inline/fenced monospace for safer approval scanning across channels. (#11917)
|
||||||
- Thinking: allow xhigh for `github-copilot/gpt-5.2-codex` and `github-copilot/gpt-5.2`. (#11646) Thanks @seans-openclawbot.
|
- Thinking: allow xhigh for `github-copilot/gpt-5.2-codex` and `github-copilot/gpt-5.2`. (#11646) Thanks @seans-openclawbot.
|
||||||
- Discord: support forum/media `thread create` starter messages, wire `message thread create --message`, and harden thread-create routing. (#10062) Thanks @jarvis89757.
|
- Discord: support forum/media `thread create` starter messages, wire `message thread create --message`, and harden thread-create routing. (#10062) Thanks @jarvis89757.
|
||||||
- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.
|
- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ afterEach(() => {
|
|||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getFirstDeliveryText(deliver: ReturnType<typeof vi.fn>): string {
|
||||||
|
const firstCall = deliver.mock.calls[0]?.[0] as
|
||||||
|
| { payloads?: Array<{ text?: string }> }
|
||||||
|
| undefined;
|
||||||
|
return firstCall?.payloads?.[0]?.text ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
describe("exec approval forwarder", () => {
|
describe("exec approval forwarder", () => {
|
||||||
it("forwards to session target and resolves", async () => {
|
it("forwards to session target and resolves", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -73,4 +80,91 @@ describe("exec approval forwarder", () => {
|
|||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
expect(deliver).toHaveBeenCalledTimes(2);
|
expect(deliver).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("formats single-line commands as inline code", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const deliver = vi.fn().mockResolvedValue([]);
|
||||||
|
const cfg = {
|
||||||
|
approvals: {
|
||||||
|
exec: {
|
||||||
|
enabled: true,
|
||||||
|
mode: "targets",
|
||||||
|
targets: [{ channel: "telegram", to: "123" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig;
|
||||||
|
|
||||||
|
const forwarder = createExecApprovalForwarder({
|
||||||
|
getConfig: () => cfg,
|
||||||
|
deliver,
|
||||||
|
nowMs: () => 1000,
|
||||||
|
resolveSessionTarget: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await forwarder.handleRequested(baseRequest);
|
||||||
|
|
||||||
|
expect(getFirstDeliveryText(deliver)).toContain("Command: `echo hello`");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats complex commands as fenced code blocks", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const deliver = vi.fn().mockResolvedValue([]);
|
||||||
|
const cfg = {
|
||||||
|
approvals: {
|
||||||
|
exec: {
|
||||||
|
enabled: true,
|
||||||
|
mode: "targets",
|
||||||
|
targets: [{ channel: "telegram", to: "123" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig;
|
||||||
|
|
||||||
|
const forwarder = createExecApprovalForwarder({
|
||||||
|
getConfig: () => cfg,
|
||||||
|
deliver,
|
||||||
|
nowMs: () => 1000,
|
||||||
|
resolveSessionTarget: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await forwarder.handleRequested({
|
||||||
|
...baseRequest,
|
||||||
|
request: {
|
||||||
|
...baseRequest.request,
|
||||||
|
command: "echo `uname`\necho done",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getFirstDeliveryText(deliver)).toContain("Command:\n```\necho `uname`\necho done\n```");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a longer fence when command already contains triple backticks", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const deliver = vi.fn().mockResolvedValue([]);
|
||||||
|
const cfg = {
|
||||||
|
approvals: {
|
||||||
|
exec: {
|
||||||
|
enabled: true,
|
||||||
|
mode: "targets",
|
||||||
|
targets: [{ channel: "telegram", to: "123" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig;
|
||||||
|
|
||||||
|
const forwarder = createExecApprovalForwarder({
|
||||||
|
getConfig: () => cfg,
|
||||||
|
deliver,
|
||||||
|
nowMs: () => 1000,
|
||||||
|
resolveSessionTarget: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await forwarder.handleRequested({
|
||||||
|
...baseRequest,
|
||||||
|
request: {
|
||||||
|
...baseRequest.request,
|
||||||
|
command: "echo ```danger```",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getFirstDeliveryText(deliver)).toContain("Command:\n````\necho ```danger```\n````");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -115,9 +115,27 @@ function buildTargetKey(target: ExecApprovalForwardTarget): string {
|
|||||||
return [channel, target.to, accountId, threadId].join(":");
|
return [channel, target.to, accountId, threadId].join(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatApprovalCommand(command: string): { inline: boolean; text: string } {
|
||||||
|
if (!command.includes("\n") && !command.includes("`")) {
|
||||||
|
return { inline: true, text: `\`${command}\`` };
|
||||||
|
}
|
||||||
|
|
||||||
|
let fence = "```";
|
||||||
|
while (command.includes(fence)) {
|
||||||
|
fence += "`";
|
||||||
|
}
|
||||||
|
return { inline: false, text: `${fence}\n${command}\n${fence}` };
|
||||||
|
}
|
||||||
|
|
||||||
function buildRequestMessage(request: ExecApprovalRequest, nowMs: number) {
|
function buildRequestMessage(request: ExecApprovalRequest, nowMs: number) {
|
||||||
const lines: string[] = ["🔒 Exec approval required", `ID: ${request.id}`];
|
const lines: string[] = ["🔒 Exec approval required", `ID: ${request.id}`];
|
||||||
lines.push(`Command: ${request.request.command}`);
|
const command = formatApprovalCommand(request.request.command);
|
||||||
|
if (command.inline) {
|
||||||
|
lines.push(`Command: ${command.text}`);
|
||||||
|
} else {
|
||||||
|
lines.push("Command:");
|
||||||
|
lines.push(command.text);
|
||||||
|
}
|
||||||
if (request.request.cwd) {
|
if (request.request.cwd) {
|
||||||
lines.push(`CWD: ${request.request.cwd}`);
|
lines.push(`CWD: ${request.request.cwd}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user