mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
162 lines
5.4 KiB
TypeScript
162 lines
5.4 KiB
TypeScript
import { PATH_BEARING_TOOLS } from "./access-intent/path-surfaces";
|
|
import { prefix, stripBashCommentLines } from "./bash-arity";
|
|
import { deriveApprovalPattern } from "./session-rules";
|
|
|
|
/** The suggestion returned for a "Yes, for this session" dialog option. */
|
|
export interface SessionApprovalSuggestion {
|
|
/** The permission surface this approval applies to. */
|
|
surface: string;
|
|
/** The wildcard pattern to store as a session rule. */
|
|
pattern: string;
|
|
/** Human-readable label for the "for session" dialog option. */
|
|
label: string;
|
|
}
|
|
|
|
/**
|
|
* Suggest a bash session-approval pattern from a command string.
|
|
*
|
|
* Uses the arity table (`src/bash-arity.ts`) to identify the semantically
|
|
* meaningful prefix tokens for the command, then produces a wildcard pattern:
|
|
*
|
|
* - Single bare token (no args): exact command (`ls`).
|
|
* - Arity prefix covers all tokens: trailing wildcard (`npm run build*`).
|
|
* - Arity prefix shorter than token list: space + wildcard (`git checkout *`).
|
|
* - Unknown command: first token + space wildcard (`mytool *`).
|
|
*/
|
|
export function suggestBashPattern(command: string): string {
|
|
const trimmed = command.trim();
|
|
if (!trimmed) return "";
|
|
// Strip leading shell comment lines so the suggestion is based on the
|
|
// actual command, not a `# description` prefix agents often prepend.
|
|
const stripped = stripBashCommentLines(trimmed);
|
|
if (!stripped) return "";
|
|
const tokens = stripped.split(/\s+/);
|
|
if (tokens.length === 1) return stripped;
|
|
const meaningful = prefix(tokens);
|
|
if (meaningful.length >= tokens.length) {
|
|
return `${stripped}*`;
|
|
}
|
|
return `${meaningful.join(" ")} *`;
|
|
}
|
|
|
|
/**
|
|
* Suggest an MCP session-approval pattern from a resolved target string.
|
|
*
|
|
* - Qualified target (`server:tool`) → `server:*`
|
|
* - Munged target (`server_tool`) → `server_*`
|
|
* - Bare target (no separator) → `*`
|
|
*/
|
|
export function suggestMcpPattern(target: string): string {
|
|
const trimmed = target.trim();
|
|
|
|
const colonIndex = trimmed.indexOf(":");
|
|
if (colonIndex > 0) {
|
|
return `${trimmed.slice(0, colonIndex)}:*`;
|
|
}
|
|
|
|
const underscoreIndex = trimmed.indexOf("_");
|
|
if (underscoreIndex > 0) {
|
|
return `${trimmed.slice(0, underscoreIndex)}_*`;
|
|
}
|
|
|
|
return "*";
|
|
}
|
|
|
|
/** Scope labels for the forwarded-approval two-step scope select. */
|
|
export interface ForwardedScopeLabels {
|
|
/** Least-privilege default: record on the requesting subagent only. */
|
|
subagentLabel: string;
|
|
/** Record on the serving node — covers the parent and all subagents. */
|
|
servingSessionLabel: string;
|
|
}
|
|
|
|
/**
|
|
* Build the two scope labels shown when a human grants a forwarded request
|
|
* "for this session."
|
|
*
|
|
* The subagent option names the requester (least privilege); the whole-session
|
|
* option restates the surface + pattern being granted session-wide.
|
|
*/
|
|
export function buildForwardedScopeLabels(
|
|
agentName: string | null,
|
|
surface: string,
|
|
pattern: string,
|
|
): ForwardedScopeLabels {
|
|
const subagentLabel = agentName
|
|
? `This subagent ('${agentName}') only`
|
|
: "This subagent only";
|
|
return {
|
|
subagentLabel,
|
|
servingSessionLabel: `The whole session — allow ${surface} "${pattern}" for parent and all subagents`,
|
|
};
|
|
}
|
|
|
|
/** Surface-aware human-readable labels for the session-approval option. */
|
|
function buildLabel(pattern: string, surface: string): string {
|
|
switch (surface) {
|
|
case "bash":
|
|
return `Yes, allow bash "${pattern}" for this session`;
|
|
case "mcp":
|
|
return `Yes, allow mcp tool "${pattern}" for this session`;
|
|
case "skill":
|
|
return `Yes, allow skill "${pattern}" for this session`;
|
|
case "external_directory":
|
|
return `Yes, allow access to external directory "${pattern}" for this session`;
|
|
case "path":
|
|
return `Yes, allow path "${pattern}" for this session`;
|
|
default:
|
|
// Path-bearing tools with a specific path pattern show the pattern.
|
|
if (PATH_BEARING_TOOLS.has(surface) && pattern !== "*") {
|
|
return `Yes, allow ${surface} "${pattern}" for this session`;
|
|
}
|
|
// Tool surfaces with catch-all or extension tools.
|
|
return `Yes, allow tool "${surface}" for this session`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Suggest a session-approval pattern for the given permission surface and value.
|
|
*
|
|
* Returns a `SessionApprovalSuggestion` with the surface, the wildcard pattern
|
|
* to store in `SessionRules`, and a human-readable dialog label.
|
|
*
|
|
* `value` is expected to be the canonical (cwd-resolved, absolute) path for
|
|
* path surfaces — callers resolve it before suggesting, so the derived pattern
|
|
* matches the policy values a later tool call produces.
|
|
*/
|
|
export function suggestSessionPattern(
|
|
surface: string,
|
|
value: string,
|
|
): SessionApprovalSuggestion {
|
|
let pattern: string;
|
|
|
|
switch (surface) {
|
|
case "bash":
|
|
pattern = suggestBashPattern(value);
|
|
break;
|
|
case "mcp":
|
|
pattern = suggestMcpPattern(value);
|
|
break;
|
|
case "skill":
|
|
pattern = value;
|
|
break;
|
|
case "external_directory":
|
|
pattern = deriveApprovalPattern(value);
|
|
break;
|
|
case "path":
|
|
pattern = deriveApprovalPattern(value);
|
|
break;
|
|
default:
|
|
// Path-bearing tools: derive a directory-scoped pattern from the path.
|
|
if (PATH_BEARING_TOOLS.has(surface) && value !== "*") {
|
|
pattern = deriveApprovalPattern(value);
|
|
break;
|
|
}
|
|
// Extension tools / fallback.
|
|
pattern = "*";
|
|
break;
|
|
}
|
|
|
|
return { surface, pattern, label: buildLabel(pattern, surface) };
|
|
}
|