@gotgenes/pi-permission-system 25.2.2 → 25.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/config/config.example.json +3 -0
- package/dist/public.d.ts +156 -41
- package/docs/configuration.md +17 -2
- package/package.json +1 -1
- package/schemas/permissions.schema.json +16 -0
- package/src/access-intent/bash/command-enumeration.ts +45 -117
- package/src/access-intent/bash/wrapper-analysis.ts +335 -0
- package/src/authority/forwarded-request-server.ts +5 -14
- package/src/authority/local-user-authorizer.ts +2 -3
- package/src/authority/permission-prompt-component.ts +87 -47
- package/src/authority/permission-prompter.ts +9 -0
- package/src/config-loader.ts +2 -0
- package/src/config-schema.ts +14 -0
- package/src/extension-config.ts +10 -0
- package/src/handlers/gates/bash-command.ts +7 -2
- package/src/handlers/gates/bash-external-directory.ts +11 -6
- package/src/handlers/gates/bash-path.ts +10 -6
- package/src/handlers/gates/external-directory.ts +13 -8
- package/src/handlers/gates/path.ts +11 -14
- package/src/handlers/gates/skill-input.ts +5 -2
- package/src/handlers/gates/skill-read.ts +5 -6
- package/src/handlers/gates/tool.ts +10 -5
- package/src/index.ts +2 -0
- package/src/permission-prompts.ts +4 -72
- package/src/presentation/dialog-renderer.ts +404 -0
- package/src/presentation/forwarded-ask-payload.ts +45 -0
- package/src/presentation/legacy-message.ts +117 -0
- package/src/presentation/line-fitting.ts +27 -0
- package/src/presentation/path-ask-payload.ts +128 -0
- package/src/presentation/prompt-payload.ts +137 -0
- package/src/presentation/skill-ask-payload.ts +50 -0
- package/src/presentation/tool-ask-payload.ts +104 -0
- package/src/tool-preview-formatter.ts +1 -1
- package/src/types.ts +6 -0
- package/src/handlers/gates/external-directory-messages.ts +0 -28
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { ExternalPathDisclosure } from "#src/denial-messages";
|
|
2
|
+
import type {
|
|
3
|
+
PromptEvidence,
|
|
4
|
+
PromptPayload,
|
|
5
|
+
} from "#src/presentation/prompt-payload";
|
|
6
|
+
import { localRequester } from "#src/presentation/prompt-payload";
|
|
7
|
+
|
|
8
|
+
/** The facts a path-shaped gate holds when it raises an ask. */
|
|
9
|
+
interface PathAskFacts {
|
|
10
|
+
toolName: string;
|
|
11
|
+
/** The path as the caller typed it — what the user recognizes. */
|
|
12
|
+
pathValue: string;
|
|
13
|
+
agentName: string | null;
|
|
14
|
+
matchedPattern?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A tool ask gated by an explicit `path` rule. */
|
|
18
|
+
export function buildPathAskPayload(facts: PathAskFacts): PromptPayload {
|
|
19
|
+
return pathPayload("path", "path", facts, []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The facts the external-directory gate adds: the boundary and the alias. */
|
|
23
|
+
interface ExternalDirectoryAskFacts extends PathAskFacts {
|
|
24
|
+
/** The canonical location, when it names somewhere other than the typed path. */
|
|
25
|
+
resolvedPath?: string;
|
|
26
|
+
/** The working directory the path escapes. */
|
|
27
|
+
cwd: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A tool ask for a path outside the working directory. */
|
|
31
|
+
export function buildExternalDirectoryAskPayload(
|
|
32
|
+
facts: ExternalDirectoryAskFacts,
|
|
33
|
+
): PromptPayload {
|
|
34
|
+
return pathPayload("external_directory", "external_directory", facts, [
|
|
35
|
+
...resolvedAliasEvidence(facts.resolvedPath),
|
|
36
|
+
workingDirectoryEvidence(facts.cwd),
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The facts the bash external-directory gate holds: one command, many paths. */
|
|
41
|
+
interface BashExternalDirectoryAskFacts {
|
|
42
|
+
command: string;
|
|
43
|
+
/** Every uncovered path the command references, with its canonical alias. */
|
|
44
|
+
externalPaths: readonly ExternalPathDisclosure[];
|
|
45
|
+
cwd: string;
|
|
46
|
+
agentName: string | null;
|
|
47
|
+
toolName: string;
|
|
48
|
+
matchedPattern?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A bash ask whose command references paths outside the working directory. */
|
|
52
|
+
export function buildBashExternalDirectoryAskPayload(
|
|
53
|
+
facts: BashExternalDirectoryAskFacts,
|
|
54
|
+
): PromptPayload {
|
|
55
|
+
return {
|
|
56
|
+
kind: "bash_external_directory",
|
|
57
|
+
request: {
|
|
58
|
+
requester: localRequester(facts.agentName),
|
|
59
|
+
surface: "external_directory",
|
|
60
|
+
toolName: facts.toolName,
|
|
61
|
+
invokedToolName: null,
|
|
62
|
+
value: facts.command,
|
|
63
|
+
matchedPattern: facts.matchedPattern ?? null,
|
|
64
|
+
commandContext: null,
|
|
65
|
+
executedUnit: null,
|
|
66
|
+
},
|
|
67
|
+
evidence: [
|
|
68
|
+
workingDirectoryEvidence(facts.cwd),
|
|
69
|
+
...facts.externalPaths.map(externalPathEvidence),
|
|
70
|
+
],
|
|
71
|
+
annotations: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Shared shape ────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The payload common to the single-path asks: the typed path is the
|
|
79
|
+
* decision-relevant value, and the gate surface distinguishes them.
|
|
80
|
+
*/
|
|
81
|
+
function pathPayload(
|
|
82
|
+
kind: "path" | "external_directory",
|
|
83
|
+
surface: string,
|
|
84
|
+
facts: PathAskFacts,
|
|
85
|
+
evidence: PromptEvidence[],
|
|
86
|
+
): PromptPayload {
|
|
87
|
+
return {
|
|
88
|
+
kind,
|
|
89
|
+
request: {
|
|
90
|
+
requester: localRequester(facts.agentName),
|
|
91
|
+
surface,
|
|
92
|
+
toolName: facts.toolName,
|
|
93
|
+
invokedToolName: null,
|
|
94
|
+
value: facts.pathValue,
|
|
95
|
+
matchedPattern: facts.matchedPattern ?? null,
|
|
96
|
+
commandContext: null,
|
|
97
|
+
executedUnit: null,
|
|
98
|
+
},
|
|
99
|
+
evidence,
|
|
100
|
+
annotations: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The canonical location, as its own entry rather than folded into the value:
|
|
106
|
+
* the user decides on the path they typed, and the alias is what that path
|
|
107
|
+
* turns out to name.
|
|
108
|
+
*/
|
|
109
|
+
function resolvedAliasEvidence(resolvedPath?: string): PromptEvidence[] {
|
|
110
|
+
return resolvedPath === undefined
|
|
111
|
+
? []
|
|
112
|
+
: [{ label: "resolves to", text: resolvedPath, detail: null }];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function workingDirectoryEvidence(cwd: string): PromptEvidence {
|
|
116
|
+
return { label: "working directory", text: cwd, detail: null };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* One escaping path. The canonical alias rides as the entry's `detail` so a
|
|
121
|
+
* render cannot separate a path from what it resolves to.
|
|
122
|
+
*/
|
|
123
|
+
function externalPathEvidence({
|
|
124
|
+
path,
|
|
125
|
+
resolvedPath,
|
|
126
|
+
}: ExternalPathDisclosure): PromptEvidence {
|
|
127
|
+
return { label: "external path", text: path, detail: resolvedPath ?? null };
|
|
128
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { BashCommandContext } from "#src/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The complete, structured description of a permission ask (ADR 0011 §2).
|
|
5
|
+
*
|
|
6
|
+
* A gate emits one of these instead of a sentence. It is complete by contract:
|
|
7
|
+
* it never truncates and never decides what a human will see. Every consumer is
|
|
8
|
+
* a renderer over it, eliding under its own budget — so elision is a property
|
|
9
|
+
* of a render, never of the payload.
|
|
10
|
+
*/
|
|
11
|
+
export interface PromptPayload {
|
|
12
|
+
readonly kind: PromptPayloadKind;
|
|
13
|
+
readonly request: PromptRequestFacts;
|
|
14
|
+
/** Complete; each renderer elides to fit its own budget. */
|
|
15
|
+
readonly evidence: readonly PromptEvidence[];
|
|
16
|
+
/** Supplied by registered annotators; always marked as model-generated. */
|
|
17
|
+
readonly annotations: readonly PromptAnnotation[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Which ask this payload describes — the renderers' dispatch discriminant.
|
|
22
|
+
*
|
|
23
|
+
* Present because the ask shapes are not separable by surface alone: a tool
|
|
24
|
+
* external-directory ask and a bash one share the `external_directory` surface,
|
|
25
|
+
* and the `path` gate and the per-tool gate differ only in wording. It mirrors
|
|
26
|
+
* `DenialContext`'s discriminated union, the shape ADR 0011 §7 names as already
|
|
27
|
+
* correct, and gives every renderer an exhaustive switch rather than a set of
|
|
28
|
+
* string comparisons a new variant sails past.
|
|
29
|
+
*/
|
|
30
|
+
export type PromptPayloadKind =
|
|
31
|
+
| "bash"
|
|
32
|
+
| "mcp"
|
|
33
|
+
| "tool"
|
|
34
|
+
| "path"
|
|
35
|
+
| "external_directory"
|
|
36
|
+
| "bash_external_directory"
|
|
37
|
+
| "skill"
|
|
38
|
+
| "skill_read"
|
|
39
|
+
| "forwarded";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The invariant core (ADR 0011 §3): the facts visible in every render, that no
|
|
43
|
+
* renderer's budget may elide.
|
|
44
|
+
*
|
|
45
|
+
* Named for what it holds — the permission request's own facts, matching the
|
|
46
|
+
* package's `PermissionRequest` / `ForwardedPermissionRequest` vocabulary —
|
|
47
|
+
* rather than for its contract, which this comment states instead.
|
|
48
|
+
*/
|
|
49
|
+
export interface PromptRequestFacts {
|
|
50
|
+
/** Who is asking, and whether the ask arrived from a subagent. */
|
|
51
|
+
readonly requester: PromptRequester;
|
|
52
|
+
/** The gate surface the rule fired on. */
|
|
53
|
+
readonly surface: string;
|
|
54
|
+
/** The gated tool name; `null` when the ask is not tool-shaped. */
|
|
55
|
+
readonly toolName: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* The invoked tool name when a shell alias re-exposes bash under another
|
|
58
|
+
* name (#574) — "gated as bash, invoked as exec_command" is two facts.
|
|
59
|
+
* `null` when it adds nothing.
|
|
60
|
+
*/
|
|
61
|
+
readonly invokedToolName: string | null;
|
|
62
|
+
/** The decision-relevant value: the command, path, MCP target, or skill name. */
|
|
63
|
+
readonly value: string;
|
|
64
|
+
/** The matched rule, including a sentinel such as `<indirection-bash-wrapper>`. */
|
|
65
|
+
readonly matchedPattern: string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Where the offending bash unit runs, when it came from a substitution or a
|
|
68
|
+
* subshell. A fact rather than a rendered clause: it is what makes the
|
|
69
|
+
* matched rule intelligible, and how it reads is the renderer's choice.
|
|
70
|
+
*/
|
|
71
|
+
readonly commandContext: BashCommandContext | null;
|
|
72
|
+
/**
|
|
73
|
+
* For bash, the unit that will actually run — including inside an unstrippable
|
|
74
|
+
* wrapper (#713). `null` when it adds nothing over {@link value}.
|
|
75
|
+
*/
|
|
76
|
+
readonly executedUnit: string | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Who is asking, one hop below when the ask was forwarded. */
|
|
80
|
+
export interface PromptRequester {
|
|
81
|
+
readonly agentName: string | null;
|
|
82
|
+
readonly forwarded: boolean;
|
|
83
|
+
/** The requesting session, for a forwarded ask; `null` for a local one. */
|
|
84
|
+
readonly sessionId: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One piece of decision evidence.
|
|
89
|
+
*
|
|
90
|
+
* Complete on the payload; each renderer elides entries and orders them under
|
|
91
|
+
* its own budget (ADR 0011 §4).
|
|
92
|
+
*/
|
|
93
|
+
export interface PromptEvidence {
|
|
94
|
+
readonly label: string;
|
|
95
|
+
readonly text: string;
|
|
96
|
+
/**
|
|
97
|
+
* A secondary fact bound to this entry that a renderer may show alongside
|
|
98
|
+
* {@link text} or elide independently — a path's symlink-resolved alias, for
|
|
99
|
+
* instance. Bound to the entry rather than listed as a second one so an
|
|
100
|
+
* elision cannot separate the two.
|
|
101
|
+
*/
|
|
102
|
+
readonly detail: string | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A model-generated advisory (ADR 0011 §8).
|
|
107
|
+
*
|
|
108
|
+
* The slot owns the attribution and the model-generated marking, so marking is
|
|
109
|
+
* a property of the payload rather than a discipline each annotator must
|
|
110
|
+
* remember. Structurally separate from any verdict: an annotation cannot allow,
|
|
111
|
+
* deny, defer, or suppress.
|
|
112
|
+
*/
|
|
113
|
+
export interface PromptAnnotation {
|
|
114
|
+
readonly source: string;
|
|
115
|
+
readonly text: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The `requester` facts for an ask raised by this session. */
|
|
119
|
+
export function localRequester(agentName: string | null): PromptRequester {
|
|
120
|
+
return { agentName, forwarded: false, sessionId: null };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Find the evidence entry a renderer knows by label. */
|
|
124
|
+
export function findEvidence(
|
|
125
|
+
payload: PromptPayload,
|
|
126
|
+
label: string,
|
|
127
|
+
): PromptEvidence | undefined {
|
|
128
|
+
return payload.evidence.find((entry) => entry.label === label);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Every evidence entry carrying the given label, in payload order. */
|
|
132
|
+
export function allEvidence(
|
|
133
|
+
payload: PromptPayload,
|
|
134
|
+
label: string,
|
|
135
|
+
): readonly PromptEvidence[] {
|
|
136
|
+
return payload.evidence.filter((entry) => entry.label === label);
|
|
137
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
2
|
+
import { localRequester } from "#src/presentation/prompt-payload";
|
|
3
|
+
import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
|
|
4
|
+
|
|
5
|
+
/** A request to load a skill. */
|
|
6
|
+
export function buildSkillAskPayload(
|
|
7
|
+
skillName: string,
|
|
8
|
+
agentName: string | null,
|
|
9
|
+
): PromptPayload {
|
|
10
|
+
return skillPayload("skill", skillName, agentName, []);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A read that reaches a skill through one of its files.
|
|
15
|
+
*
|
|
16
|
+
* The skill is the decision-relevant value — it is what the policy names — and
|
|
17
|
+
* the path is the evidence for why this read counts as reaching it.
|
|
18
|
+
*/
|
|
19
|
+
export function buildSkillPathAskPayload(
|
|
20
|
+
skill: SkillPromptEntry,
|
|
21
|
+
readPath: string,
|
|
22
|
+
agentName: string | null,
|
|
23
|
+
): PromptPayload {
|
|
24
|
+
return skillPayload("skill_read", skill.name, agentName, [
|
|
25
|
+
{ label: "read path", text: readPath, detail: null },
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function skillPayload(
|
|
30
|
+
kind: "skill" | "skill_read",
|
|
31
|
+
skillName: string,
|
|
32
|
+
agentName: string | null,
|
|
33
|
+
evidence: PromptPayload["evidence"],
|
|
34
|
+
): PromptPayload {
|
|
35
|
+
return {
|
|
36
|
+
kind,
|
|
37
|
+
request: {
|
|
38
|
+
requester: localRequester(agentName),
|
|
39
|
+
surface: "skill",
|
|
40
|
+
toolName: null,
|
|
41
|
+
invokedToolName: null,
|
|
42
|
+
value: skillName,
|
|
43
|
+
matchedPattern: null,
|
|
44
|
+
commandContext: null,
|
|
45
|
+
executedUnit: null,
|
|
46
|
+
},
|
|
47
|
+
evidence,
|
|
48
|
+
annotations: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { classifyToolKind, isMcpCheck } from "#src/access-intent/tool-kind";
|
|
2
|
+
import type {
|
|
3
|
+
PromptEvidence,
|
|
4
|
+
PromptPayload,
|
|
5
|
+
} from "#src/presentation/prompt-payload";
|
|
6
|
+
import { localRequester } from "#src/presentation/prompt-payload";
|
|
7
|
+
import type { ToolPreviewFormatter } from "#src/tool-preview-formatter";
|
|
8
|
+
import type { PermissionCheckResult } from "#src/types";
|
|
9
|
+
import { getNonEmptyString, toRecord } from "#src/value-guards";
|
|
10
|
+
|
|
11
|
+
/** The facts the per-tool gate holds when it raises an ask. */
|
|
12
|
+
export interface ToolAskFacts {
|
|
13
|
+
/** The resolved check: the gated tool, the matched rule, the offending unit. */
|
|
14
|
+
check: PermissionCheckResult;
|
|
15
|
+
agentName: string | null;
|
|
16
|
+
/** The gate surface the rule fired on — `bash` for a shell alias (#574). */
|
|
17
|
+
surface: string;
|
|
18
|
+
/** The tool the agent actually called, when a shell alias re-exposes bash. */
|
|
19
|
+
invokedToolName?: string | null;
|
|
20
|
+
/** The raw tool input, the source of the input-preview evidence. */
|
|
21
|
+
input?: unknown;
|
|
22
|
+
/** Renders the per-tool input preview; absent means no preview evidence. */
|
|
23
|
+
formatter?: ToolPreviewFormatter;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the payload for the per-tool gate: a bash, MCP, or generic-tool ask.
|
|
28
|
+
*
|
|
29
|
+
* The branch decides only the payload's `kind` and which fact is the
|
|
30
|
+
* decision-relevant `value`; how any of it reads is a renderer's decision.
|
|
31
|
+
*/
|
|
32
|
+
export function buildToolAskPayload(facts: ToolAskFacts): PromptPayload {
|
|
33
|
+
const { check } = facts;
|
|
34
|
+
const bash = classifyToolKind(check.toolName) === "bash";
|
|
35
|
+
const mcp = isMcpCheck(check) && check.target !== undefined;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
kind: bash ? "bash" : mcp ? "mcp" : "tool",
|
|
39
|
+
request: {
|
|
40
|
+
requester: localRequester(facts.agentName),
|
|
41
|
+
surface: facts.surface,
|
|
42
|
+
toolName: check.toolName,
|
|
43
|
+
invokedToolName: distinctInvokedName(facts),
|
|
44
|
+
value: askValue(check, bash, mcp),
|
|
45
|
+
matchedPattern: check.matchedPattern ?? null,
|
|
46
|
+
commandContext: check.commandContext ?? null,
|
|
47
|
+
executedUnit: check.executedUnit ?? null,
|
|
48
|
+
},
|
|
49
|
+
evidence: bash
|
|
50
|
+
? fullCommandEvidence(facts)
|
|
51
|
+
: inputPreviewEvidence(facts, mcp),
|
|
52
|
+
annotations: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The decision-relevant value: the offending command for bash, the qualified
|
|
58
|
+
* target for MCP, the tool name otherwise.
|
|
59
|
+
*
|
|
60
|
+
* A bash check with no command yields the empty string rather than the tool
|
|
61
|
+
* name — the ask is about a command, and naming the surface instead would
|
|
62
|
+
* assert a command that was never resolved.
|
|
63
|
+
*/
|
|
64
|
+
function askValue(
|
|
65
|
+
check: PermissionCheckResult,
|
|
66
|
+
bash: boolean,
|
|
67
|
+
mcp: boolean,
|
|
68
|
+
): string {
|
|
69
|
+
if (bash) return check.command ?? "";
|
|
70
|
+
if (mcp) return check.target ?? "";
|
|
71
|
+
return check.toolName;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The invoked tool name, but only when it is a fact the gated name does not carry. */
|
|
75
|
+
function distinctInvokedName(facts: ToolAskFacts): string | null {
|
|
76
|
+
const invoked = facts.invokedToolName ?? null;
|
|
77
|
+
return invoked === null || invoked === facts.check.toolName ? null : invoked;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The enclosing command, when the gated unit is only part of what will run. */
|
|
81
|
+
function fullCommandEvidence(facts: ToolAskFacts): PromptEvidence[] {
|
|
82
|
+
const fullCommand = getNonEmptyString(toRecord(facts.input).command);
|
|
83
|
+
if (fullCommand === null || fullCommand === facts.check.command) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
return [{ label: "full command", text: fullCommand, detail: null }];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The per-tool input preview, when a formatter is registered and produces one.
|
|
91
|
+
*
|
|
92
|
+
* An MCP ask previews under the `mcp` key rather than the qualified target, so
|
|
93
|
+
* a registered MCP formatter is consulted for every server.
|
|
94
|
+
*/
|
|
95
|
+
function inputPreviewEvidence(
|
|
96
|
+
facts: ToolAskFacts,
|
|
97
|
+
mcp: boolean,
|
|
98
|
+
): PromptEvidence[] {
|
|
99
|
+
const preview = facts.formatter?.formatToolInputForPrompt(
|
|
100
|
+
mcp ? "mcp" : facts.check.toolName,
|
|
101
|
+
facts.input,
|
|
102
|
+
);
|
|
103
|
+
return preview ? [{ label: "input", text: preview, detail: null }] : [];
|
|
104
|
+
}
|
|
@@ -135,7 +135,7 @@ export class ToolPreviewFormatter {
|
|
|
135
135
|
case "ls":
|
|
136
136
|
return this.formatSearchInputForPrompt(toolName, inputRecord);
|
|
137
137
|
case "mcp":
|
|
138
|
-
// The MCP target is already
|
|
138
|
+
// The MCP target is already a request fact on the prompt payload.
|
|
139
139
|
// When no custom formatter is registered (or it declines), produce no
|
|
140
140
|
// additional preview rather than leaking the raw event JSON.
|
|
141
141
|
return "";
|
package/src/types.ts
CHANGED
|
@@ -58,6 +58,12 @@ export interface PermissionCheckResult {
|
|
|
58
58
|
* (top-level) commands.
|
|
59
59
|
*/
|
|
60
60
|
commandContext?: BashCommandContext;
|
|
61
|
+
/**
|
|
62
|
+
* The command the winning bash unit actually runs, when it is a wrapper whose
|
|
63
|
+
* inner command differs from the unit text (#713). Display-only: the gate
|
|
64
|
+
* still decides on `command`, so this never widens or narrows a decision.
|
|
65
|
+
*/
|
|
66
|
+
executedUnit?: string;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export function isPermissionState(value: unknown): value is PermissionState {
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ExternalPathDisclosure,
|
|
3
|
-
resolvesToSuffix,
|
|
4
|
-
} from "#src/denial-messages";
|
|
5
|
-
|
|
6
|
-
export function formatExternalDirectoryAskPrompt(
|
|
7
|
-
toolName: string,
|
|
8
|
-
pathValue: string,
|
|
9
|
-
resolvedPath: string | undefined,
|
|
10
|
-
cwd: string,
|
|
11
|
-
agentName?: string,
|
|
12
|
-
): string {
|
|
13
|
-
const subject = agentName ? `Agent '${agentName}'` : "Current agent";
|
|
14
|
-
return `${subject} requested tool '${toolName}' for path '${pathValue}'${resolvesToSuffix(resolvedPath)} outside working directory '${cwd}'. Allow this external directory access?`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function formatBashExternalDirectoryAskPrompt(
|
|
18
|
-
command: string,
|
|
19
|
-
externalPaths: ExternalPathDisclosure[],
|
|
20
|
-
cwd: string,
|
|
21
|
-
agentName?: string,
|
|
22
|
-
): string {
|
|
23
|
-
const subject = agentName ? `Agent '${agentName}'` : "Current agent";
|
|
24
|
-
const pathList = externalPaths
|
|
25
|
-
.map(({ path, resolvedPath }) => `${path}${resolvesToSuffix(resolvedPath)}`)
|
|
26
|
-
.join(", ");
|
|
27
|
-
return `${subject} requested bash command '${command}' which references path(s) outside working directory '${cwd}': ${pathList}. Allow this external directory access?`;
|
|
28
|
-
}
|