diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6767eb7039ae..d6fb979742b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai
- Dependencies: refresh workspace pins and move the WhatsApp plugin from `@whiskeysockets/baileys` to `baileys` while keeping the `7.0.0-rc10` runtime.
- Plugin SDK: add bundled-plugin session actions, `sendSessionAttachment`, and Cron-backed `scheduleSessionTurn`/tag cleanup under the grouped session namespace. Replaces #75578/#75581/#75588 and part of #73384/#74483. Thanks @100yenadmin.
- Plugin SDK/media-understanding: add `extractStructuredWithModel(...)` plus the optional provider-side `extractStructured(...)` seam so trusted plugins can run bounded image-first structured extraction with optional supplemental text context through provider-owned runtimes such as Codex.
+- Exec approvals: add `tools.exec.commandHighlighting` so parser-derived command highlighting in approval prompts can be enabled globally or per agent. (#79348) Thanks @jesse-merhi.
### Fixes
diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256
index 5bf77b7a49bf..764d9cb011b7 100644
--- a/docs/.generated/config-baseline.sha256
+++ b/docs/.generated/config-baseline.sha256
@@ -1,4 +1,4 @@
-2800c9a6377fd02cb55bbf2577d63f2acc7193ed89be6fcffd32439893eaabc1 config-baseline.json
-ebbf966b41d99cd17282553f7882d1b826782599cbca6719b6f2c25b0e6b235d config-baseline.core.json
+17b1ca7d3087be090b456b125d1e380b552e5bb4359132751f1a55590aba8fad config-baseline.json
+3325af3a6292959bb38166e9136c638dce5d2093d2339076742890848088a972 config-baseline.core.json
222d0338d6ed290870cac70cdf5e390bc1bb60c4462e46f847003bafe25c5a6e config-baseline.channel.json
18f71e9d4a62fe68fbd5bf18d5833a4e380fc705ad641769e1cf05794286344c config-baseline.plugin.json
diff --git a/docs/gateway/config-tools.md b/docs/gateway/config-tools.md
index 11233b791b13..6e70c76c0516 100644
--- a/docs/gateway/config-tools.md
+++ b/docs/gateway/config-tools.md
@@ -131,6 +131,7 @@ Controls elevated exec access outside the sandbox:
cleanupMs: 1800000,
notifyOnExit: true,
notifyOnExitEmptySuccess: false,
+ commandHighlighting: false,
applyPatch: {
enabled: false,
allowModels: ["gpt-5.5"],
diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md
index c68e9eb37d76..8e23a32fc3c0 100644
--- a/docs/tools/exec-approvals.md
+++ b/docs/tools/exec-approvals.md
@@ -166,6 +166,20 @@ In strict mode these commands still need explicit approval, and
`allow-always` does not persist new allowlist entries for them
automatically.
+### `tools.exec.commandHighlighting`
+
+
+ Controls only presentation in exec approval prompts. When enabled,
+ OpenClaw may attach parser-derived command spans so Web approval
+ prompts can highlight command tokens. Set it to `true` to enable
+ command text highlighting.
+
+
+This setting does **not** change `security`, `ask`, allowlist matching,
+strict inline-eval behavior, approval forwarding, or command execution.
+It can be set globally under `tools.exec.commandHighlighting` or per
+agent under `agents.list[].tools.exec.commandHighlighting`.
+
## YOLO mode (no-approval)
If you want host exec to run without approval prompts, you must open
diff --git a/docs/tools/exec.md b/docs/tools/exec.md
index c86bada78a4c..711d4ab1b370 100644
--- a/docs/tools/exec.md
+++ b/docs/tools/exec.md
@@ -109,6 +109,7 @@ Notes:
- In `security=full` plus `ask=off` mode, host exec follows the configured policy directly; there is no extra heuristic command-obfuscation prefilter or script-preflight rejection layer.
- `tools.exec.node` (default: unset)
- `tools.exec.strictInlineEval` (default: false): when true, inline interpreter eval forms such as `python -c`, `node -e`, `ruby -e`, `perl -e`, `php -r`, `lua -e`, and `osascript -e` always require explicit approval. `allow-always` can still persist benign interpreter/script invocations, but inline-eval forms still prompt each time.
+- `tools.exec.commandHighlighting` (default: false): when true, approval prompts can highlight parser-derived command spans in the command text. Set to `true` globally or per agent to enable command text highlighting without changing exec approval policy.
- `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only).
- `tools.exec.safeBins`: stdin-only safe binaries that can run without explicit allowlist entries. For behavior details, see [Safe bins](/tools/exec-approvals-advanced#safe-bins-stdin-only).
- `tools.exec.safeBinTrustedDirs`: additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted. Built-in defaults are `/bin` and `/usr/bin`.
diff --git a/src/agents/bash-tools.exec-approval-request.test.ts b/src/agents/bash-tools.exec-approval-request.test.ts
index 419acf7802e1..e6a2468cbc05 100644
--- a/src/agents/bash-tools.exec-approval-request.test.ts
+++ b/src/agents/bash-tools.exec-approval-request.test.ts
@@ -250,6 +250,7 @@ describe("requestExecApprovalDecision", () => {
await registerExecApprovalRequestForHost({
approvalId: "approval-id",
command: 'ls | grep "stuff" | python -c \'print("hi")\'',
+ commandHighlighting: true,
workdir: "/tmp/project",
host: "node",
security: "allowlist",
@@ -264,6 +265,47 @@ describe("requestExecApprovalDecision", () => {
expect(payload?.commandSpans).toContainEqual({ startIndex: 20, endIndex: 26 });
});
+ it("does not generate command spans by default", async () => {
+ vi.mocked(callGatewayTool).mockResolvedValue({ id: "approval-id", expiresAtMs: 1234 });
+
+ await registerExecApprovalRequestForHost({
+ approvalId: "approval-id",
+ command: 'ls | grep "stuff" | python -c \'print("hi")\'',
+ workdir: "/tmp/project",
+ host: "node",
+ security: "allowlist",
+ ask: "always",
+ });
+
+ expect(commandExplainerMock.explainShellCommand).not.toHaveBeenCalled();
+ expect(commandExplainerMock.formatCommandSpans).not.toHaveBeenCalled();
+ const payload = vi.mocked(callGatewayTool).mock.calls[0]?.[2] as
+ | { commandSpans?: unknown }
+ | undefined;
+ expect(payload?.commandSpans).toBeUndefined();
+ });
+
+ it("does not generate command spans when command highlighting is disabled", async () => {
+ vi.mocked(callGatewayTool).mockResolvedValue({ id: "approval-id", expiresAtMs: 1234 });
+
+ await registerExecApprovalRequestForHost({
+ approvalId: "approval-id",
+ command: 'ls | grep "stuff" | python -c \'print("hi")\'',
+ commandHighlighting: false,
+ workdir: "/tmp/project",
+ host: "node",
+ security: "allowlist",
+ ask: "always",
+ });
+
+ expect(commandExplainerMock.explainShellCommand).not.toHaveBeenCalled();
+ expect(commandExplainerMock.formatCommandSpans).not.toHaveBeenCalled();
+ const payload = vi.mocked(callGatewayTool).mock.calls[0]?.[2] as
+ | { commandSpans?: unknown }
+ | undefined;
+ expect(payload?.commandSpans).toBeUndefined();
+ });
+
it("uses system run plan command text for host approval explanations", async () => {
vi.mocked(callGatewayTool).mockResolvedValue({ id: "approval-id", expiresAtMs: 1234 });
@@ -276,6 +318,7 @@ describe("requestExecApprovalDecision", () => {
agentId: null,
sessionKey: null,
},
+ commandHighlighting: true,
workdir: "/tmp/project",
host: "node",
security: "allowlist",
@@ -362,6 +405,7 @@ describe("requestExecApprovalDecision", () => {
approvalId: "approval-id",
command: "echo hi",
commandSpans: [{ startIndex: 0, endIndex: 4 }],
+ commandHighlighting: true,
workdir: "/tmp/project",
host: "node",
security: "allowlist",
diff --git a/src/agents/bash-tools.exec-approval-request.ts b/src/agents/bash-tools.exec-approval-request.ts
index 520c0d89652c..eece585f9149 100644
--- a/src/agents/bash-tools.exec-approval-request.ts
+++ b/src/agents/bash-tools.exec-approval-request.ts
@@ -185,6 +185,7 @@ type HostExecApprovalParams = {
ask: ExecAsk;
warningText?: string;
commandSpans?: ExecApprovalCommandSpan[];
+ commandHighlighting?: boolean;
agentId?: string;
resolvedPath?: string;
sessionKey?: string;
@@ -269,10 +270,12 @@ async function buildHostApprovalDecisionParams(
params: HostExecApprovalParams,
): Promise {
const commandSpans =
- params.commandSpans ??
- (shouldSkipGeneratedCommandSpans(params)
- ? undefined
- : await resolveCommandSpans(params.command ?? params.systemRunPlan?.commandText));
+ params.commandHighlighting === true
+ ? (params.commandSpans ??
+ (shouldSkipGeneratedCommandSpans(params)
+ ? undefined
+ : await resolveCommandSpans(params.command ?? params.systemRunPlan?.commandText)))
+ : undefined;
return {
id: params.approvalId,
command: params.command,
diff --git a/src/agents/bash-tools.exec-host-gateway.ts b/src/agents/bash-tools.exec-host-gateway.ts
index 3562ae09f85e..ff360c7ae009 100644
--- a/src/agents/bash-tools.exec-host-gateway.ts
+++ b/src/agents/bash-tools.exec-host-gateway.ts
@@ -60,6 +60,7 @@ export type ProcessGatewayAllowlistParams = {
safeBins: Set;
safeBinProfiles: Readonly>;
strictInlineEval?: boolean;
+ commandHighlighting?: boolean;
trigger?: string;
agentId?: string;
sessionKey?: string;
@@ -373,6 +374,7 @@ export async function processGatewayAllowlist(
host: "gateway",
security: hostSecurity,
ask: hostAsk,
+ commandHighlighting: params.commandHighlighting,
warningText: params.warnings.join("\n").trim() || undefined,
...buildExecApprovalRequesterContext({
agentId: params.agentId,
diff --git a/src/agents/bash-tools.exec-host-node.ts b/src/agents/bash-tools.exec-host-node.ts
index 479eb2a1bec1..d61ca66500da 100644
--- a/src/agents/bash-tools.exec-host-node.ts
+++ b/src/agents/bash-tools.exec-host-node.ts
@@ -92,6 +92,7 @@ export async function executeNodeHostCommand(
nodeId: target.nodeId,
security: hostSecurity,
ask: hostAsk,
+ commandHighlighting: params.commandHighlighting,
...buildExecApprovalRequesterContext({
agentId: prepared.agentId,
sessionKey: prepared.sessionKey,
diff --git a/src/agents/bash-tools.exec-host-node.types.ts b/src/agents/bash-tools.exec-host-node.types.ts
index eaac6a428024..c44fd00167c8 100644
--- a/src/agents/bash-tools.exec-host-node.types.ts
+++ b/src/agents/bash-tools.exec-host-node.types.ts
@@ -19,6 +19,7 @@ export type ExecuteNodeHostCommandParams = {
security: ExecSecurity;
ask: ExecAsk;
strictInlineEval?: boolean;
+ commandHighlighting?: boolean;
timeoutSec?: number;
defaultTimeoutSec: number;
approvalRunningNoticeMs: number;
diff --git a/src/agents/bash-tools.exec-types.ts b/src/agents/bash-tools.exec-types.ts
index 9da0e83244aa..a540a1cbdf68 100644
--- a/src/agents/bash-tools.exec-types.ts
+++ b/src/agents/bash-tools.exec-types.ts
@@ -14,6 +14,7 @@ export type ExecToolDefaults = {
pathPrepend?: string[];
safeBins?: string[];
strictInlineEval?: boolean;
+ commandHighlighting?: boolean;
safeBinTrustedDirs?: string[];
safeBinProfiles?: Record;
agentId?: string;
diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts
index c80a702b5a84..ce2cb125ca15 100644
--- a/src/agents/bash-tools.exec.ts
+++ b/src/agents/bash-tools.exec.ts
@@ -1486,6 +1486,7 @@ export function createExecTool(
security,
ask,
strictInlineEval: defaults?.strictInlineEval,
+ commandHighlighting: defaults?.commandHighlighting,
trigger: defaults?.trigger,
timeoutSec: params.timeout,
defaultTimeoutSec,
@@ -1515,6 +1516,7 @@ export function createExecTool(
safeBins,
safeBinProfiles,
strictInlineEval: defaults?.strictInlineEval,
+ commandHighlighting: defaults?.commandHighlighting,
trigger: defaults?.trigger,
agentId,
sessionKey: defaults?.sessionKey,
diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts
index a2eca39a7049..c4b410440126 100644
--- a/src/agents/pi-tools.ts
+++ b/src/agents/pi-tools.ts
@@ -1,6 +1,7 @@
import { createCodingTools, createReadTool } from "@earendil-works/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 { resolveExecCommandHighlighting } from "../config/exec-command-highlighting.js";
import type { ModelCompatConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
@@ -274,6 +275,10 @@ function resolveExecConfig(params: { cfg?: OpenClawConfig; agentId?: string }) {
pathPrepend: agentExec?.pathPrepend ?? globalExec?.pathPrepend,
safeBins: agentExec?.safeBins ?? globalExec?.safeBins,
strictInlineEval: agentExec?.strictInlineEval ?? globalExec?.strictInlineEval,
+ commandHighlighting: resolveExecCommandHighlighting({
+ config: cfg,
+ agentId: params.agentId,
+ }),
safeBinTrustedDirs: agentExec?.safeBinTrustedDirs ?? globalExec?.safeBinTrustedDirs,
safeBinProfiles: resolveMergedSafeBinProfileFixtures({
global: globalExec,
@@ -678,6 +683,7 @@ export function createOpenClawCodingTools(options?: {
pathPrepend: options?.exec?.pathPrepend ?? execConfig.pathPrepend,
safeBins: options?.exec?.safeBins ?? execConfig.safeBins,
strictInlineEval: options?.exec?.strictInlineEval ?? execConfig.strictInlineEval,
+ commandHighlighting: options?.exec?.commandHighlighting ?? execConfig.commandHighlighting,
safeBinTrustedDirs: options?.exec?.safeBinTrustedDirs ?? execConfig.safeBinTrustedDirs,
safeBinProfiles: options?.exec?.safeBinProfiles ?? execConfig.safeBinProfiles,
agentId,
diff --git a/src/config/exec-command-highlighting.ts b/src/config/exec-command-highlighting.ts
new file mode 100644
index 000000000000..11c68fd18057
--- /dev/null
+++ b/src/config/exec-command-highlighting.ts
@@ -0,0 +1,16 @@
+import { normalizeAgentId } from "../routing/session-key.js";
+import type { OpenClawConfig } from "./types.openclaw.js";
+
+export function resolveExecCommandHighlighting(params: {
+ config?: OpenClawConfig | null;
+ agentId?: string | null;
+}): boolean {
+ const config = params.config ?? {};
+ const globalValue = config.tools?.exec?.commandHighlighting;
+ const agentId = params.agentId ? normalizeAgentId(params.agentId) : null;
+ const agentValue = agentId
+ ? config.agents?.list?.find((entry) => normalizeAgentId(entry.id) === agentId)?.tools?.exec
+ ?.commandHighlighting
+ : undefined;
+ return agentValue ?? globalValue ?? false;
+}
diff --git a/src/config/schema.help.quality.test.ts b/src/config/schema.help.quality.test.ts
index 03d8681db45b..9e74a2a7c9c8 100644
--- a/src/config/schema.help.quality.test.ts
+++ b/src/config/schema.help.quality.test.ts
@@ -500,6 +500,7 @@ const TOOLS_HOOKS_TARGET_KEYS = [
"tools.byProvider",
"tools.exec.approvalRunningNoticeMs",
"tools.exec.strictInlineEval",
+ "tools.exec.commandHighlighting",
"tools.links.enabled",
"tools.links.maxLinks",
"tools.links.models",
diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts
index 9a319bd45e92..2b89d3f4f87c 100644
--- a/src/config/schema.help.ts
+++ b/src/config/schema.help.ts
@@ -700,6 +700,8 @@ export const FIELD_HELP: Record = {
"Allow stdin-only safe binaries to run without explicit allowlist entries.",
"tools.exec.strictInlineEval":
"Require explicit approval for interpreter inline-eval forms such as `python -c`, `node -e`, `ruby -e`, or `osascript -e`. Prevents silent allowlist reuse and downgrades allow-always to ask-each-time for those forms.",
+ "tools.exec.commandHighlighting":
+ "Show parser-derived command highlights in exec approval prompts (default: false). Enable this to render highlighted command text without changing exec approval policy.",
"tools.exec.safeBinTrustedDirs":
"Additional explicit directories trusted for safe-bin path checks (PATH entries are never auto-trusted).",
"tools.exec.safeBinProfiles":
diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts
index 570fba87aa2c..4af051319ecf 100644
--- a/src/config/schema.labels.ts
+++ b/src/config/schema.labels.ts
@@ -259,6 +259,7 @@ export const FIELD_LABELS: Record = {
"tools.exec.pathPrepend": "Exec PATH Prepend",
"tools.exec.safeBins": "Exec Safe Bins",
"tools.exec.strictInlineEval": "Require Inline-Eval Approval",
+ "tools.exec.commandHighlighting": "Exec Command Highlighting",
"tools.exec.safeBinTrustedDirs": "Exec Safe Bin Trusted Dirs",
"tools.exec.safeBinProfiles": "Exec Safe Bin Profiles",
approvals: "Approvals",
diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts
index d5005cf5b82b..16f319aca381 100644
--- a/src/config/schema.test.ts
+++ b/src/config/schema.test.ts
@@ -329,6 +329,31 @@ describe("config schema", () => {
});
});
+ it("accepts exec command highlighting config in global and agent scopes", () => {
+ const tools = ToolsSchema.parse({
+ exec: {
+ commandHighlighting: false,
+ },
+ });
+ expect(tools?.exec?.commandHighlighting).toBe(false);
+
+ const config = OpenClawSchema.parse({
+ agents: {
+ list: [
+ {
+ id: "main",
+ tools: {
+ exec: {
+ commandHighlighting: false,
+ },
+ },
+ },
+ ],
+ },
+ });
+ expect(config.agents?.list?.[0]?.tools?.exec?.commandHighlighting).toBe(false);
+ });
+
it("accepts experimental tool flags in the runtime zod schema", () => {
const parsed = ToolsSchema.parse({
experimental: {
diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts
index 0d4ca008b6a3..8003b6e75010 100644
--- a/src/config/types.tools.ts
+++ b/src/config/types.tools.ts
@@ -284,6 +284,8 @@ export type ExecToolConfig = {
* Prevents silent allowlist reuse and allow-always persistence for those forms.
*/
strictInlineEval?: boolean;
+ /** Render parser-derived command highlights in exec approval prompts (default: false). */
+ commandHighlighting?: boolean;
/** Extra explicit directories trusted for safeBins path checks (never derived from PATH). */
safeBinTrustedDirs?: string[];
/** Optional custom safe-bin profiles for entries in tools.exec.safeBins. */
diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts
index ee2872cd447a..30f24725bcf3 100644
--- a/src/config/zod-schema.agent-runtime.ts
+++ b/src/config/zod-schema.agent-runtime.ts
@@ -471,6 +471,7 @@ const ToolExecBaseShape = {
pathPrepend: z.array(z.string()).optional(),
safeBins: z.array(z.string()).optional(),
strictInlineEval: z.boolean().optional(),
+ commandHighlighting: z.boolean().optional(),
safeBinTrustedDirs: z.array(z.string()).optional(),
safeBinProfiles: z.record(z.string(), ToolExecSafeBinProfileSchema).optional(),
backgroundMs: z.number().int().positive().optional(),
diff --git a/src/gateway/server-methods/exec-approval.ts b/src/gateway/server-methods/exec-approval.ts
index 64b86b870819..2512c6887391 100644
--- a/src/gateway/server-methods/exec-approval.ts
+++ b/src/gateway/server-methods/exec-approval.ts
@@ -1,3 +1,4 @@
+import { resolveExecCommandHighlighting } from "../../config/exec-command-highlighting.js";
import { resolveCommandAnalysisSummaryForDisplay } from "../../infra/command-analysis/explain.js";
import {
resolveExecApprovalCommandDisplay,
@@ -241,6 +242,12 @@ export function createExecApprovalHandlers(
}
const envBinding = buildSystemRunApprovalEnvBinding(p.env);
const warningText = normalizeOptionalString(p.warningText);
+ const runtimeConfig =
+ typeof context.getRuntimeConfig === "function" ? context.getRuntimeConfig() : {};
+ const commandHighlighting = resolveExecCommandHighlighting({
+ config: runtimeConfig,
+ agentId: effectiveAgentId,
+ });
const commandAnalysis = resolveCommandAnalysisSummaryForDisplay({
host,
commandText: effectiveCommandText,
@@ -250,7 +257,7 @@ export function createExecApprovalHandlers(
});
const sanitizedCommandText = sanitizeExecApprovalDisplayText(effectiveCommandText);
const commandSpans =
- sanitizedCommandText === effectiveCommandText
+ commandHighlighting && sanitizedCommandText === effectiveCommandText
? normalizeCommandSpans(p.commandSpans, sanitizedCommandText.length)
: undefined;
const systemRunBinding =
diff --git a/src/gateway/server-methods/server-methods.test.ts b/src/gateway/server-methods/server-methods.test.ts
index 62b1bb998511..395383bd511c 100644
--- a/src/gateway/server-methods/server-methods.test.ts
+++ b/src/gateway/server-methods/server-methods.test.ts
@@ -4,6 +4,7 @@ import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { emitAgentEvent } from "../../infra/agent-events.js";
import { formatZonedTimestamp } from "../../infra/format-time/format-datetime.js";
import {
@@ -861,12 +862,13 @@ describe("exec approval handlers", () => {
});
}
- function createExecApprovalFixture() {
+ function createExecApprovalFixture(opts?: { config?: OpenClawConfig }) {
const manager = new ExecApprovalManager();
const handlers = createExecApprovalHandlers(manager);
const broadcasts: Array<{ event: string; payload: unknown }> = [];
const respond = vi.fn();
const context = {
+ getRuntimeConfig: () => opts?.config ?? {},
broadcast: (event: string, payload: unknown) => {
broadcasts.push({ event, payload });
},
@@ -1476,7 +1478,9 @@ describe("exec approval handlers", () => {
});
it("preserves command analysis and normalizes command spans", async () => {
- const { handlers, broadcasts, respond, context } = createExecApprovalFixture();
+ const { handlers, broadcasts, respond, context } = createExecApprovalFixture({
+ config: { tools: { exec: { commandHighlighting: true } } },
+ });
await requestExecApproval({
handlers,
respond,
@@ -1503,8 +1507,56 @@ describe("exec approval handlers", () => {
]);
});
- it("drops command spans when command display sanitization changes offsets", async () => {
+ it("drops command spans by default", async () => {
const { handlers, broadcasts, respond, context } = createExecApprovalFixture();
+ await requestExecApproval({
+ handlers,
+ respond,
+ context,
+ params: {
+ timeoutMs: 10,
+ command: "ls | python -c 'print(1)'",
+ commandSpans: [
+ { startIndex: 0, endIndex: 2 },
+ { startIndex: 5, endIndex: 11 },
+ ],
+ },
+ });
+ const { request } = getRequestedExecApprovalPayload(broadcasts);
+ expect(request["commandAnalysis"]).toEqual(
+ expect.objectContaining({ commandCount: 1, nestedCommandCount: 0 }),
+ );
+ expect(request["commandSpans"]).toBeUndefined();
+ });
+
+ it("drops command spans when command highlighting is disabled", async () => {
+ const { handlers, broadcasts, respond, context } = createExecApprovalFixture({
+ config: { tools: { exec: { commandHighlighting: false } } },
+ });
+ await requestExecApproval({
+ handlers,
+ respond,
+ context,
+ params: {
+ timeoutMs: 10,
+ command: "ls | python -c 'print(1)'",
+ commandSpans: [
+ { startIndex: 0, endIndex: 2 },
+ { startIndex: 5, endIndex: 11 },
+ ],
+ },
+ });
+ const { request } = getRequestedExecApprovalPayload(broadcasts);
+ expect(request["commandAnalysis"]).toEqual(
+ expect.objectContaining({ commandCount: 1, nestedCommandCount: 0 }),
+ );
+ expect(request["commandSpans"]).toBeUndefined();
+ });
+
+ it("drops command spans when command display sanitization changes offsets", async () => {
+ const { handlers, broadcasts, respond, context } = createExecApprovalFixture({
+ config: { tools: { exec: { commandHighlighting: true } } },
+ });
await requestExecApproval({
handlers,
respond,