feat: add pure ssh2 remote operations

This commit is contained in:
云服务部-叶林立
2026-08-21 19:51:43 +08:00
parent d3bf562189
commit 0ac50eb581
62 changed files with 3701 additions and 47 deletions
@@ -677,7 +677,7 @@ describe("mergeUnifiedConfigs", () => {
});
});
it("shallow-merges shellTools by tool name: override adds without dropping base", () => {
it("merges shellTools by tool name: override adds without dropping base", () => {
const merged = mergeUnifiedConfigs(
{ shellTools: { exec_command: { commandArgument: "cmd" } } },
{ shellTools: { run_shell: { commandArgument: "script" } } },
@@ -688,17 +688,25 @@ describe("mergeUnifiedConfigs", () => {
});
});
it("override shellTools replaces a colliding tool's alias wholesale", () => {
it("field-merges a colliding shell alias and preserves its decision floor", () => {
const merged = mergeUnifiedConfigs(
{
shellTools: {
exec_command: { commandArgument: "cmd", workdirArgument: "workdir" },
exec_command: {
commandArgument: "cmd",
workdirArgument: "workdir",
decisionFloor: "ask",
},
},
},
{ shellTools: { exec_command: { commandArgument: "command" } } },
);
expect(merged.shellTools).toEqual({
exec_command: { commandArgument: "command" },
exec_command: {
commandArgument: "command",
workdirArgument: "workdir",
decisionFloor: "ask",
},
});
});
@@ -127,6 +127,25 @@ describe("unifiedConfigSchema", () => {
expect(result.success).toBe(true);
});
it("accepts ask as a shell alias decision floor", () => {
const result = unifiedConfigSchema.safeParse({
shellTools: {
ssh_bash: { commandArgument: "command", decisionFloor: "ask" },
},
});
expect(result.success).toBe(true);
});
it.each(["allow", "deny", "invalid"] as const)(
"rejects %s as a shell alias decision floor",
(decisionFloor) => {
const result = unifiedConfigSchema.safeParse({
shellTools: { ssh_bash: { commandArgument: "command", decisionFloor } },
});
expect(result.success).toBe(false);
},
);
it("rejects an alias missing commandArgument", () => {
const result = unifiedConfigSchema.safeParse({
shellTools: { exec_command: { workdirArgument: "workdir" } },
@@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";
import {
applyShellDecisionFloor,
SHELL_TOOL_DECISION_FLOOR_PATTERN,
} from "#src/handlers/gates/shell-decision-floor";
import type { PermissionCheckResult, PermissionState } from "#src/types";
function check(state: PermissionState, source: PermissionCheckResult["source"] = "bash") {
return {
state,
toolName: "bash",
source,
origin: source === "session" ? ("session" as const) : ("global" as const),
command: "pwd",
matchedPattern: source === "session" ? "pwd" : "*",
} satisfies PermissionCheckResult;
}
describe("applyShellDecisionFloor", () => {
it("raises allow to ask", () => {
expect(applyShellDecisionFloor(check("allow"), "ask")).toEqual({
...check("allow"),
state: "ask",
source: "bash",
matchedPattern: SHELL_TOOL_DECISION_FLOOR_PATTERN,
reason: undefined,
});
});
it("raises a session allow to ask without retaining the session fast path", () => {
const result = applyShellDecisionFloor(check("allow", "session"), "ask");
expect(result.state).toBe("ask");
expect(result.source).toBe("bash");
expect(result.matchedPattern).toBe(SHELL_TOOL_DECISION_FLOOR_PATTERN);
});
it.each(["ask", "deny"] as const)("preserves an existing %s", (state) => {
const original = check(state);
expect(applyShellDecisionFloor(original, "ask")).toBe(original);
});
it("does nothing when no floor is configured", () => {
const original = check("allow");
expect(applyShellDecisionFloor(original, undefined)).toBe(original);
});
});
@@ -120,6 +120,36 @@ describe("describeToolGate", () => {
expect(desc.payload.request.invokedToolName).toBe("exec_command");
});
it("records a shell alias formatter preview for review and authorization context", () => {
const shell: ShellInvocation = { command: "rm -rf dist", workdir: undefined };
const formatter = new ToolPreviewFormatter(
{
toolInputPreviewMaxLength: TOOL_INPUT_PREVIEW_MAX_LENGTH,
toolTextSummaryMaxLength: TOOL_TEXT_SUMMARY_MAX_LENGTH,
},
{
get: (name) =>
name === "ssh_bash"
? () => "SSH target 'packaging-server' in remote cwd '/srv/build'"
: undefined,
},
);
const desc = describeToolGate(
makeTcc({ toolName: "ssh_bash", input: { command: "rm -rf dist" } }),
makeCheckResult("ask", { toolName: "bash", source: "bash", command: "rm -rf dist" }),
formatter,
undefined,
shell,
);
expect(desc.promptDetails.toolInputPreview).toBe(
"SSH target 'packaging-server' in remote cwd '/srv/build'",
);
expect(desc.logContext.toolInputPreview).toBe(
"SSH target 'packaging-server' in remote cwd '/srv/build'",
);
});
it("returns mcp surface with target in decision.value for MCP tools", () => {
const check = makeCheckResult("ask", {
toolName: "mcp",
@@ -32,6 +32,14 @@ const execShellTools = {
exec_command: { commandArgument: "cmd", workdirArgument: "workdir" },
};
const reviewedExecShellTools = {
exec_command: {
commandArgument: "cmd",
workdirArgument: "workdir",
decisionFloor: "ask" as const,
},
};
describe("shell-tool alias gating (#574)", () => {
it("denies an aliased command that a bash: rule denies", async () => {
const { handler, events } = makeHandler({
@@ -84,6 +92,37 @@ describe("shell-tool alias gating (#574)", () => {
);
});
it("raises an allowed aliased command to ask before execution", async () => {
const prompter = denyingPrompter();
const { handler, events } = makeHandler({
shellTools: reviewedExecShellTools,
tools: ["exec_command"],
prompter,
session: {
checkPermission: makeBashCommandCheck({
deny: /rm -rf/,
denyMatched: "rm -rf *",
allowMatched: "*",
}),
},
});
await handler.handleToolCall(
makeToolCallEvent("exec_command", { input: { cmd: "git status" } }),
makeCtx(),
);
expect(prompter.escalate).toHaveBeenCalledOnce();
expect(getDecisionEvents(events)).toContainEqual(
expect.objectContaining({
surface: "bash",
value: "git status",
result: "deny",
matchedPattern: "<shell-tool-decision-floor>",
}),
);
});
it("decomposes a chained aliased command so a denied sub-command still blocks", async () => {
const { handler, events } = makeHandler({
shellTools: execShellTools,
@@ -189,6 +189,28 @@ describe("buildToolAskPayload", () => {
),
).toBeUndefined();
});
test("carries a registered shell alias preview as authorization evidence", () => {
const formatter = makeFormatter({
get: (name) =>
name === "ssh_bash"
? () => "SSH target 'packaging-server' in remote cwd '/srv/build'"
: undefined,
});
const payload = buildPayload({
check: toolResult("bash", { command: "rm -rf dist" }),
surface: "bash",
invokedToolName: "ssh_bash",
input: { command: "rm -rf dist" },
formatter,
});
expect(findEvidence(payload, "input")).toEqual({
label: "input",
text: "SSH target 'packaging-server' in remote cwd '/srv/build'",
detail: null,
});
});
});
describe("mcp", () => {