mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat: make exec command highlighting optional
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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`
|
||||
|
||||
<ParamField path="commandHighlighting" type="boolean" default="false">
|
||||
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.
|
||||
</ParamField>
|
||||
|
||||
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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<RequestExecApprovalDecisionParams> {
|
||||
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,
|
||||
|
||||
@@ -60,6 +60,7 @@ export type ProcessGatewayAllowlistParams = {
|
||||
safeBins: Set<string>;
|
||||
safeBinProfiles: Readonly<Record<string, SafeBinProfile>>;
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,6 +19,7 @@ export type ExecuteNodeHostCommandParams = {
|
||||
security: ExecSecurity;
|
||||
ask: ExecAsk;
|
||||
strictInlineEval?: boolean;
|
||||
commandHighlighting?: boolean;
|
||||
timeoutSec?: number;
|
||||
defaultTimeoutSec: number;
|
||||
approvalRunningNoticeMs: number;
|
||||
|
||||
@@ -14,6 +14,7 @@ export type ExecToolDefaults = {
|
||||
pathPrepend?: string[];
|
||||
safeBins?: string[];
|
||||
strictInlineEval?: boolean;
|
||||
commandHighlighting?: boolean;
|
||||
safeBinTrustedDirs?: string[];
|
||||
safeBinProfiles?: Record<string, SafeBinProfileFixture>;
|
||||
agentId?: string;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -700,6 +700,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"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":
|
||||
|
||||
@@ -259,6 +259,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user