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
@@ -0,0 +1,30 @@
import type { ShellToolsConfig } from "#src/config-schema";
import type { PermissionCheckResult } from "#src/types";
/** Synthetic rule marker recorded when a shell alias raises allow to ask. */
export const SHELL_TOOL_DECISION_FLOOR_PATTERN = "<shell-tool-decision-floor>";
type ShellDecisionFloor = ShellToolsConfig[string]["decisionFloor"];
/**
* Enforce the minimum review decision configured for a shell alias.
*
* The only supported floor is `ask`: it raises an allow to ask while preserving
* existing asks and hard denies. The synthetic result no longer reports a
* session source, so a prior Bash session approval cannot bypass an alias whose
* contract requires every invocation to enter the authorizer chain.
*/
export function applyShellDecisionFloor(
check: PermissionCheckResult,
floor: ShellDecisionFloor,
): PermissionCheckResult {
if (floor !== "ask" || check.state !== "allow") return check;
return {
...check,
state: "ask",
source: "bash",
matchedPattern: SHELL_TOOL_DECISION_FLOOR_PATTERN,
reason: undefined,
};
}
@@ -25,6 +25,7 @@ import { describePathGate } from "./path";
import type { GateRunner } from "./runner";
import { describeSkillReadGate } from "./skill-read";
import { describeToolGate } from "./tool";
import { applyShellDecisionFloor } from "./shell-decision-floor";
import type { GateOutcome, ToolCallContext } from "./types";
/**
@@ -164,25 +165,27 @@ export class ToolCallGatePipeline {
): { toolCheck: PermissionCheckResult; accessPath?: AccessPath } {
if (shell) {
if (bashProgram) {
const check = resolveBashCommandCheck(
bashProgram.commandText(),
bashProgram.commands(),
tcc.agentName ?? undefined,
this.resolver,
);
return {
toolCheck: resolveBashCommandCheck(
bashProgram.commandText(),
bashProgram.commands(),
tcc.agentName ?? undefined,
this.resolver,
),
toolCheck: applyShellDecisionFloor(check, shell.decisionFloor),
};
}
// A shell invocation whose command did not parse (e.g. empty) still
// resolves on the `bash` surface, so an aliased tool never falls through
// to its own extension-tool surface.
const check = this.resolver.resolve({
kind: "tool",
surface: "bash",
input: { command: shell.command },
agentName: tcc.agentName ?? undefined,
});
return {
toolCheck: this.resolver.resolve({
kind: "tool",
surface: "bash",
input: { command: shell.command },
agentName: tcc.agentName ?? undefined,
}),
toolCheck: applyShellDecisionFloor(check, shell.decisionFloor),
};
}
@@ -65,6 +65,10 @@ export function describeToolGate(
tcc.input,
PATH_BEARING_TOOLS,
);
if (shell && tcc.toolName !== check.toolName) {
permissionLogContext.toolInputPreview =
formatter.formatToolInputForPrompt(tcc.toolName, tcc.input) || undefined;
}
// Compute session approval suggestion for the "for this session" option.
const suggestion = suggestSessionPattern(