@lucascouts/claude-agent-acp-plus 0.8.1 → 0.9.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/dist/acp-agent.d.ts +107 -95
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +1050 -817
- package/dist/clear-context-coordinator.d.ts +58 -0
- package/dist/clear-context-coordinator.d.ts.map +1 -0
- package/dist/clear-context-coordinator.js +79 -0
- package/dist/exit-plan.d.ts +42 -0
- package/dist/exit-plan.d.ts.map +1 -0
- package/dist/exit-plan.js +154 -0
- package/dist/file-change-audit.d.ts +56 -0
- package/dist/file-change-audit.d.ts.map +1 -0
- package/dist/file-change-audit.js +379 -0
- package/dist/index.js +24 -1
- package/dist/permissions/effects.d.ts +19 -0
- package/dist/permissions/effects.d.ts.map +1 -0
- package/dist/permissions/effects.js +166 -0
- package/dist/permissions/modes.d.ts +8 -0
- package/dist/permissions/modes.d.ts.map +1 -0
- package/dist/permissions/modes.js +41 -0
- package/dist/permissions/normalization.d.ts +6 -0
- package/dist/permissions/normalization.d.ts.map +1 -0
- package/dist/permissions/normalization.js +100 -0
- package/dist/permissions/options/filesystem.d.ts +9 -0
- package/dist/permissions/options/filesystem.d.ts.map +1 -0
- package/dist/permissions/options/filesystem.js +124 -0
- package/dist/permissions/options/shared.d.ts +39 -0
- package/dist/permissions/options/shared.d.ts.map +1 -0
- package/dist/permissions/options/shared.js +64 -0
- package/dist/permissions/options/shell.d.ts +5 -0
- package/dist/permissions/options/shell.d.ts.map +1 -0
- package/dist/permissions/options/shell.js +100 -0
- package/dist/permissions/options/tools.d.ts +11 -0
- package/dist/permissions/options/tools.d.ts.map +1 -0
- package/dist/permissions/options/tools.js +135 -0
- package/dist/permissions/options.d.ts +5 -0
- package/dist/permissions/options.d.ts.map +1 -0
- package/dist/permissions/options.js +60 -0
- package/dist/permissions/presentation.d.ts +15 -0
- package/dist/permissions/presentation.d.ts.map +1 -0
- package/dist/permissions/presentation.js +83 -0
- package/dist/permissions/response.d.ts +11 -0
- package/dist/permissions/response.d.ts.map +1 -0
- package/dist/permissions/response.js +19 -0
- package/dist/session-config-ids.d.ts +5 -0
- package/dist/session-config-ids.d.ts.map +1 -0
- package/dist/session-config-ids.js +4 -0
- package/dist/session-failure-extension.d.ts +104 -0
- package/dist/session-failure-extension.d.ts.map +1 -0
- package/dist/session-failure-extension.js +352 -0
- package/dist/session-mode.d.ts +66 -0
- package/dist/session-mode.d.ts.map +1 -0
- package/dist/session-mode.js +246 -0
- package/dist/session-titles.d.ts +77 -0
- package/dist/session-titles.d.ts.map +1 -0
- package/dist/session-titles.js +199 -0
- package/dist/tool-result-meta.d.ts +8 -0
- package/dist/tool-result-meta.d.ts.map +1 -0
- package/dist/tool-result-meta.js +19 -0
- package/dist/tools.d.ts +4 -6
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +129 -24
- package/package.json +3 -3
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const MAX_UPDATES = 100;
|
|
2
|
+
const MAX_RULES_PER_UPDATE = 100;
|
|
3
|
+
const MAX_DIRECTORIES_PER_UPDATE = 100;
|
|
4
|
+
const MAX_TOOL_NAME_LENGTH = 512;
|
|
5
|
+
const MAX_RULE_CONTENT_LENGTH = 10_000;
|
|
6
|
+
const MAX_DIRECTORY_LENGTH = 4_096;
|
|
7
|
+
const DESTINATIONS = new Set([
|
|
8
|
+
"session",
|
|
9
|
+
"cliArg",
|
|
10
|
+
"userSettings",
|
|
11
|
+
"projectSettings",
|
|
12
|
+
"localSettings",
|
|
13
|
+
]);
|
|
14
|
+
const BEHAVIORS = new Set(["allow", "deny", "ask"]);
|
|
15
|
+
const MODES = new Set([
|
|
16
|
+
"default",
|
|
17
|
+
"acceptEdits",
|
|
18
|
+
"bypassPermissions",
|
|
19
|
+
"plan",
|
|
20
|
+
"dontAsk",
|
|
21
|
+
"auto",
|
|
22
|
+
]);
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function isDestination(value) {
|
|
27
|
+
return typeof value === "string" && DESTINATIONS.has(value);
|
|
28
|
+
}
|
|
29
|
+
function safePlainString(value, maxLength) {
|
|
30
|
+
if (typeof value !== "string")
|
|
31
|
+
return false;
|
|
32
|
+
const normalized = value.trim();
|
|
33
|
+
return (normalized.length > 0 &&
|
|
34
|
+
value.length <= maxLength &&
|
|
35
|
+
!Array.from(normalized).some((character) => {
|
|
36
|
+
const code = character.charCodeAt(0);
|
|
37
|
+
return (code <= 0x08 ||
|
|
38
|
+
code === 0x0b ||
|
|
39
|
+
code === 0x0c ||
|
|
40
|
+
(code >= 0x0e && code <= 0x1f) ||
|
|
41
|
+
code === 0x7f);
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function isRule(value) {
|
|
45
|
+
if (!isRecord(value) || !safePlainString(value.toolName, MAX_TOOL_NAME_LENGTH)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return (value.ruleContent === undefined || safePlainString(value.ruleContent, MAX_RULE_CONTENT_LENGTH));
|
|
49
|
+
}
|
|
50
|
+
function isKnownUpdate(value) {
|
|
51
|
+
if (!isRecord(value) || !isDestination(value.destination) || typeof value.type !== "string") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
switch (value.type) {
|
|
55
|
+
case "addRules":
|
|
56
|
+
case "replaceRules":
|
|
57
|
+
case "removeRules":
|
|
58
|
+
return (Array.isArray(value.rules) &&
|
|
59
|
+
value.rules.length <= MAX_RULES_PER_UPDATE &&
|
|
60
|
+
(value.type === "replaceRules" || value.rules.length > 0) &&
|
|
61
|
+
value.rules.every(isRule) &&
|
|
62
|
+
typeof value.behavior === "string" &&
|
|
63
|
+
BEHAVIORS.has(value.behavior));
|
|
64
|
+
case "setMode":
|
|
65
|
+
return typeof value.mode === "string" && MODES.has(value.mode);
|
|
66
|
+
case "addDirectories":
|
|
67
|
+
case "removeDirectories":
|
|
68
|
+
return (Array.isArray(value.directories) &&
|
|
69
|
+
value.directories.length > 0 &&
|
|
70
|
+
value.directories.length <= MAX_DIRECTORIES_PER_UPDATE &&
|
|
71
|
+
value.directories.every((directory) => safePlainString(directory, MAX_DIRECTORY_LENGTH)));
|
|
72
|
+
default:
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function normalizeDurablePermissionChangeSet(suggestions, forcedAsk = false) {
|
|
77
|
+
if (forcedAsk)
|
|
78
|
+
return undefined;
|
|
79
|
+
try {
|
|
80
|
+
if (!Array.isArray(suggestions) ||
|
|
81
|
+
suggestions.length === 0 ||
|
|
82
|
+
suggestions.length > MAX_UPDATES ||
|
|
83
|
+
!suggestions.every(isKnownUpdate)) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
// The ACP prompt may stay open for an arbitrary amount of time. Snapshot the
|
|
88
|
+
// provider payload so the label the user approved and the effect returned to
|
|
89
|
+
// Claude cannot diverge through later mutation of the callback argument.
|
|
90
|
+
updates: structuredClone(suggestions),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// SDK data is expected to be plain structured-cloneable input. Treat an
|
|
95
|
+
// accessor, function, proxy, or other non-wire value as an invalid bundle.
|
|
96
|
+
// Validation belongs in this boundary too because merely reading one of those
|
|
97
|
+
// values can throw before structuredClone gets a chance to reject it.
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type PermissionOptionContext } from "./shared.js";
|
|
3
|
+
export declare function buildReadPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
4
|
+
export declare function buildGlobPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
5
|
+
export declare function buildGrepPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
6
|
+
export declare function buildEditPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
7
|
+
export declare function buildWritePermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
8
|
+
export declare function buildNotebookEditPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
9
|
+
//# sourceMappingURL=filesystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../../src/permissions/options/filesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,OAAO,EAAe,KAAK,uBAAuB,EAAsB,MAAM,aAAa,CAAC;AA6J5F,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAE/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAE/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAE/F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAE/F;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAEhG;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAEpB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { plainString, withOptionalUpdate } from "./shared.js";
|
|
4
|
+
function isFileSessionChangeSet(changeSet, operation) {
|
|
5
|
+
return (!!changeSet &&
|
|
6
|
+
changeSet.updates.every((update) => {
|
|
7
|
+
if (update.destination !== "session")
|
|
8
|
+
return false;
|
|
9
|
+
if (update.type === "setMode") {
|
|
10
|
+
return operation === "write" && update.mode === "acceptEdits";
|
|
11
|
+
}
|
|
12
|
+
if (update.type === "addDirectories")
|
|
13
|
+
return operation === "write";
|
|
14
|
+
if (update.type !== "addRules" || update.behavior !== "allow")
|
|
15
|
+
return false;
|
|
16
|
+
return update.rules.every((rule) => operation === "read" ? rule.toolName === "Read" : rule.toolName === "Edit");
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function inputPath(context, key) {
|
|
20
|
+
return plainString(context.input[key]);
|
|
21
|
+
}
|
|
22
|
+
function isInside(directory, candidate) {
|
|
23
|
+
const relative = path.relative(path.resolve(directory), path.resolve(candidate));
|
|
24
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
|
|
25
|
+
}
|
|
26
|
+
function hasSessionAllowRuleFor(changeSet, toolName) {
|
|
27
|
+
return !!changeSet?.updates.some((update) => update.type === "addRules" &&
|
|
28
|
+
update.destination === "session" &&
|
|
29
|
+
update.behavior === "allow" &&
|
|
30
|
+
update.rules.some((rule) => rule.toolName === toolName));
|
|
31
|
+
}
|
|
32
|
+
function hasBroadSessionAllowRuleFor(changeSet, toolName) {
|
|
33
|
+
return changeSet.updates.some((update) => update.type === "addRules" &&
|
|
34
|
+
update.destination === "session" &&
|
|
35
|
+
update.behavior === "allow" &&
|
|
36
|
+
update.rules.some((rule) => rule.toolName === toolName && rule.ruleContent === undefined));
|
|
37
|
+
}
|
|
38
|
+
function hasAcceptEditsMode(changeSet) {
|
|
39
|
+
return changeSet.updates.some((update) => update.type === "setMode" &&
|
|
40
|
+
update.destination === "session" &&
|
|
41
|
+
update.mode === "acceptEdits");
|
|
42
|
+
}
|
|
43
|
+
function permissionPath(ruleContent) {
|
|
44
|
+
const value = plainString(ruleContent);
|
|
45
|
+
if (!value)
|
|
46
|
+
return undefined;
|
|
47
|
+
return value.replace(/[/\\]\*\*$/, "");
|
|
48
|
+
}
|
|
49
|
+
function effectCoversFilePath(changeSet, filePath, operation, cwd) {
|
|
50
|
+
const directory = path.dirname(filePath);
|
|
51
|
+
const comparable = (candidate) => {
|
|
52
|
+
const resolved = path.resolve(cwd, candidate);
|
|
53
|
+
return resolved === directory || isInside(resolved, filePath);
|
|
54
|
+
};
|
|
55
|
+
const paths = changeSet.updates.flatMap((update) => {
|
|
56
|
+
if (update.type === "addDirectories")
|
|
57
|
+
return update.directories;
|
|
58
|
+
if (update.type !== "addRules" || update.behavior !== "allow")
|
|
59
|
+
return [];
|
|
60
|
+
return update.rules
|
|
61
|
+
.filter((rule) => operation === "read" ? rule.toolName === "Read" : rule.toolName === "Edit")
|
|
62
|
+
.map((rule) => permissionPath(rule.ruleContent))
|
|
63
|
+
.filter((value) => value !== undefined);
|
|
64
|
+
});
|
|
65
|
+
return paths.some(comparable);
|
|
66
|
+
}
|
|
67
|
+
function fileSessionLabel(context, filePath, operation) {
|
|
68
|
+
if (!isFileSessionChangeSet(context.durableChangeSet, operation))
|
|
69
|
+
return undefined;
|
|
70
|
+
const resolvedFilePath = filePath ? path.resolve(context.cwd, filePath) : undefined;
|
|
71
|
+
const isClaudePath = operation === "write" &&
|
|
72
|
+
!!resolvedFilePath &&
|
|
73
|
+
(isInside(path.join(context.cwd, ".claude"), resolvedFilePath) ||
|
|
74
|
+
isInside(path.join(os.homedir(), ".claude"), resolvedFilePath));
|
|
75
|
+
const broadRule = hasBroadSessionAllowRuleFor(context.durableChangeSet, operation === "read" ? "Read" : "Edit");
|
|
76
|
+
if (resolvedFilePath) {
|
|
77
|
+
const coversCurrentPath = effectCoversFilePath(context.durableChangeSet, resolvedFilePath, operation, context.cwd) ||
|
|
78
|
+
broadRule ||
|
|
79
|
+
(operation === "write" && !isClaudePath && hasAcceptEditsMode(context.durableChangeSet));
|
|
80
|
+
if (!coversCurrentPath)
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
else if (!broadRule &&
|
|
84
|
+
!(operation === "write" && hasAcceptEditsMode(context.durableChangeSet))) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (operation === "write" &&
|
|
88
|
+
resolvedFilePath &&
|
|
89
|
+
isClaudePath &&
|
|
90
|
+
hasSessionAllowRuleFor(context.durableChangeSet, "Edit")) {
|
|
91
|
+
return "Yes, and allow Claude to edit its own settings for this session";
|
|
92
|
+
}
|
|
93
|
+
if (!resolvedFilePath || isInside(context.cwd, resolvedFilePath)) {
|
|
94
|
+
return operation === "read"
|
|
95
|
+
? "Yes, during this session"
|
|
96
|
+
: "Yes, allow all edits during this session";
|
|
97
|
+
}
|
|
98
|
+
const directory = path.dirname(resolvedFilePath);
|
|
99
|
+
const directoryName = path.basename(directory) || "this directory";
|
|
100
|
+
return operation === "read"
|
|
101
|
+
? `Yes, allow reading from ${directoryName}${path.sep} during this session`
|
|
102
|
+
: `Yes, allow all edits in ${directoryName}${path.sep} during this session`;
|
|
103
|
+
}
|
|
104
|
+
function buildFilePermissionOptions(context, filePath, operation) {
|
|
105
|
+
return withOptionalUpdate(context.durableChangeSet, fileSessionLabel(context, filePath, operation), "Yes", "No");
|
|
106
|
+
}
|
|
107
|
+
export function buildReadPermissionOptions(context) {
|
|
108
|
+
return buildFilePermissionOptions(context, inputPath(context, "file_path"), "read");
|
|
109
|
+
}
|
|
110
|
+
export function buildGlobPermissionOptions(context) {
|
|
111
|
+
return buildFilePermissionOptions(context, inputPath(context, "path") ?? context.cwd, "read");
|
|
112
|
+
}
|
|
113
|
+
export function buildGrepPermissionOptions(context) {
|
|
114
|
+
return buildFilePermissionOptions(context, inputPath(context, "path") ?? context.cwd, "read");
|
|
115
|
+
}
|
|
116
|
+
export function buildEditPermissionOptions(context) {
|
|
117
|
+
return buildFilePermissionOptions(context, inputPath(context, "file_path"), "write");
|
|
118
|
+
}
|
|
119
|
+
export function buildWritePermissionOptions(context) {
|
|
120
|
+
return buildFilePermissionOptions(context, inputPath(context, "file_path"), "write");
|
|
121
|
+
}
|
|
122
|
+
export function buildNotebookEditPermissionOptions(context) {
|
|
123
|
+
return buildFilePermissionOptions(context, inputPath(context, "notebook_path"), "write");
|
|
124
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { DurablePermissionChangeSet } from "../normalization.js";
|
|
3
|
+
export declare const PERMISSION_OPTION_ID: {
|
|
4
|
+
readonly allowOnce: "allow-once";
|
|
5
|
+
readonly allowWithUpdates: "allow-with-updates";
|
|
6
|
+
readonly allowSkillExact: "allow-skill-exact";
|
|
7
|
+
readonly allowSkillPrefix: "allow-skill-prefix";
|
|
8
|
+
readonly exitPlanBypass: "exit-plan-bypass";
|
|
9
|
+
readonly exitPlanAuto: "exit-plan-auto";
|
|
10
|
+
readonly exitPlanAcceptEdits: "exit-plan-accept-edits";
|
|
11
|
+
readonly exitPlanDefault: "exit-plan-default";
|
|
12
|
+
readonly exitPlanClearAuto: "exit-plan-clear-auto";
|
|
13
|
+
readonly exitPlanClearBypass: "exit-plan-clear-bypass";
|
|
14
|
+
readonly exitPlanClearAcceptEdits: "exit-plan-clear-accept-edits";
|
|
15
|
+
readonly reject: "reject";
|
|
16
|
+
};
|
|
17
|
+
export interface PermissionOptionContext {
|
|
18
|
+
toolName: string;
|
|
19
|
+
displayName?: string;
|
|
20
|
+
input: Record<string, unknown>;
|
|
21
|
+
cwd: string;
|
|
22
|
+
durableChangeSet?: DurablePermissionChangeSet;
|
|
23
|
+
allowPersistentOptions?: boolean;
|
|
24
|
+
availableModes?: readonly string[];
|
|
25
|
+
contextUsedPercent?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function allowOnce(name?: string): PermissionOption;
|
|
28
|
+
export declare function allowWithUpdates(name: string): PermissionOption;
|
|
29
|
+
export declare function reject(name?: string): PermissionOption;
|
|
30
|
+
export declare function withOptionalUpdate(changeSet: DurablePermissionChangeSet | undefined, updateName: string | undefined, allowName?: string, rejectName?: string): PermissionOption[];
|
|
31
|
+
export declare function plainString(value: unknown): string | undefined;
|
|
32
|
+
export declare function exactLocalAllowRule(changeSet: DurablePermissionChangeSet | undefined, toolName: string, ruleContent?: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* An MCP "don't ask again" option is only truthful when every provider update
|
|
35
|
+
* adds an unrestricted allow rule for the tool currently being prompted.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isMcpAllowChangeSet(changeSet: DurablePermissionChangeSet | undefined, toolName: string): boolean;
|
|
38
|
+
export declare function withGeneratedUpdate(name: string, rejectName?: string): PermissionOption[];
|
|
39
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/permissions/options/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;CAavB,CAAC;AAEX,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,SAAS,CAAC,IAAI,SAAQ,GAAG,gBAAgB,CAExD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAE/D;AAED,wBAAgB,MAAM,CAAC,IAAI,SAAO,GAAG,gBAAgB,CAEpD;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,0BAA0B,GAAG,SAAS,EACjD,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,SAAQ,EACjB,UAAU,SAAO,GAChB,gBAAgB,EAAE,CAKpB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,0BAA0B,GAAG,SAAS,EACjD,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAkBT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,0BAA0B,GAAG,SAAS,EACjD,QAAQ,EAAE,MAAM,GACf,OAAO,CAYT;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAO,GAAG,gBAAgB,EAAE,CAEvF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const PERMISSION_OPTION_ID = {
|
|
2
|
+
allowOnce: "allow-once",
|
|
3
|
+
allowWithUpdates: "allow-with-updates",
|
|
4
|
+
allowSkillExact: "allow-skill-exact",
|
|
5
|
+
allowSkillPrefix: "allow-skill-prefix",
|
|
6
|
+
exitPlanBypass: "exit-plan-bypass",
|
|
7
|
+
exitPlanAuto: "exit-plan-auto",
|
|
8
|
+
exitPlanAcceptEdits: "exit-plan-accept-edits",
|
|
9
|
+
exitPlanDefault: "exit-plan-default",
|
|
10
|
+
exitPlanClearAuto: "exit-plan-clear-auto",
|
|
11
|
+
exitPlanClearBypass: "exit-plan-clear-bypass",
|
|
12
|
+
exitPlanClearAcceptEdits: "exit-plan-clear-accept-edits",
|
|
13
|
+
reject: "reject",
|
|
14
|
+
};
|
|
15
|
+
export function allowOnce(name = "Yes") {
|
|
16
|
+
return { optionId: PERMISSION_OPTION_ID.allowOnce, name, kind: "allow_once" };
|
|
17
|
+
}
|
|
18
|
+
export function allowWithUpdates(name) {
|
|
19
|
+
return { optionId: PERMISSION_OPTION_ID.allowWithUpdates, name, kind: "allow_always" };
|
|
20
|
+
}
|
|
21
|
+
export function reject(name = "No") {
|
|
22
|
+
return { optionId: PERMISSION_OPTION_ID.reject, name, kind: "reject_once" };
|
|
23
|
+
}
|
|
24
|
+
export function withOptionalUpdate(changeSet, updateName, allowName = "Yes", rejectName = "No") {
|
|
25
|
+
const options = [allowOnce(allowName)];
|
|
26
|
+
if (changeSet && updateName)
|
|
27
|
+
options.push(allowWithUpdates(updateName));
|
|
28
|
+
options.push(reject(rejectName));
|
|
29
|
+
return options;
|
|
30
|
+
}
|
|
31
|
+
export function plainString(value) {
|
|
32
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
33
|
+
}
|
|
34
|
+
export function exactLocalAllowRule(changeSet, toolName, ruleContent) {
|
|
35
|
+
if (!changeSet || changeSet.updates.length !== 1)
|
|
36
|
+
return false;
|
|
37
|
+
const update = changeSet.updates[0];
|
|
38
|
+
if (update?.type !== "addRules" ||
|
|
39
|
+
update.behavior !== "allow" ||
|
|
40
|
+
update.destination !== "localSettings" ||
|
|
41
|
+
update.rules.length !== 1) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const rule = update.rules[0];
|
|
45
|
+
return (rule?.toolName === toolName &&
|
|
46
|
+
(ruleContent === undefined
|
|
47
|
+
? rule.ruleContent === undefined
|
|
48
|
+
: plainString(rule.ruleContent) === ruleContent));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* An MCP "don't ask again" option is only truthful when every provider update
|
|
52
|
+
* adds an unrestricted allow rule for the tool currently being prompted.
|
|
53
|
+
*/
|
|
54
|
+
export function isMcpAllowChangeSet(changeSet, toolName) {
|
|
55
|
+
return (!!changeSet &&
|
|
56
|
+
changeSet.updates.length > 0 &&
|
|
57
|
+
changeSet.updates.every((update) => update.type === "addRules" &&
|
|
58
|
+
update.behavior === "allow" &&
|
|
59
|
+
update.rules.length > 0 &&
|
|
60
|
+
update.rules.every((rule) => rule.toolName === toolName && rule.ruleContent === undefined)));
|
|
61
|
+
}
|
|
62
|
+
export function withGeneratedUpdate(name, rejectName = "No") {
|
|
63
|
+
return [allowOnce(), allowWithUpdates(name), reject(rejectName)];
|
|
64
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type PermissionOptionContext } from "./shared.js";
|
|
3
|
+
export declare function buildBashPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
4
|
+
export declare function buildPowerShellPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
5
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/permissions/options/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,EAAe,KAAK,uBAAuB,EAAsB,MAAM,aAAa,CAAC;AA6G5F,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAE/F;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAEpB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { plainString, withOptionalUpdate } from "./shared.js";
|
|
3
|
+
function permissionRulePrefix(value) {
|
|
4
|
+
return value.endsWith(":*") ? value.slice(0, -2) : value;
|
|
5
|
+
}
|
|
6
|
+
function displayList(values) {
|
|
7
|
+
if (values.join(", ").length > 50)
|
|
8
|
+
return "similar";
|
|
9
|
+
if (values.length === 1)
|
|
10
|
+
return values[0];
|
|
11
|
+
if (values.length === 2)
|
|
12
|
+
return `${values[0]} and ${values[1]}`;
|
|
13
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
14
|
+
}
|
|
15
|
+
/** Shorten paths only as much as needed to distinguish them in prompt text. */
|
|
16
|
+
function displayPaths(paths) {
|
|
17
|
+
const normalizedPaths = [...new Set(paths.map((value) => path.normalize(value)))];
|
|
18
|
+
const segments = normalizedPaths.map((value) => value.split(path.sep).filter(Boolean));
|
|
19
|
+
// Compare equally deep suffixes so duplicate basenames gain the smallest
|
|
20
|
+
// useful parent prefix (for example, codex-acp/src/ vs claude-acp/src/).
|
|
21
|
+
const suffix = (index, depth) => segments[index].slice(-depth).join(path.sep) || normalizedPaths[index];
|
|
22
|
+
const names = normalizedPaths.map((value, index) => {
|
|
23
|
+
const parts = segments[index];
|
|
24
|
+
let name = path.basename(value) || value;
|
|
25
|
+
for (let depth = 1; depth <= parts.length; depth++) {
|
|
26
|
+
const candidate = suffix(index, depth);
|
|
27
|
+
if (normalizedPaths.every((_, other) => other === index || suffix(other, depth) !== candidate)) {
|
|
28
|
+
name = candidate;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (depth === parts.length)
|
|
32
|
+
name = value;
|
|
33
|
+
}
|
|
34
|
+
return name.endsWith(path.sep) ? name : `${name}${path.sep}`;
|
|
35
|
+
});
|
|
36
|
+
// Native Claude labels enumerate two paths and summarize larger sets.
|
|
37
|
+
if (names.length <= 2)
|
|
38
|
+
return displayList(names);
|
|
39
|
+
return `${names[0]}, ${names[1]} and ${names.length - 2} more`;
|
|
40
|
+
}
|
|
41
|
+
/** Plain-text port of Claude Code's generateShellSuggestionsLabel. */
|
|
42
|
+
function shellSuggestionsLabel(toolName, changeSet) {
|
|
43
|
+
const representable = changeSet.updates.every((update) => {
|
|
44
|
+
if (update.type === "addDirectories")
|
|
45
|
+
return update.destination === "session";
|
|
46
|
+
if (update.type !== "addRules" || update.behavior !== "allow")
|
|
47
|
+
return false;
|
|
48
|
+
return update.rules.every((rule) => (rule.toolName === toolName || rule.toolName === "Read") &&
|
|
49
|
+
plainString(rule.ruleContent) !== undefined);
|
|
50
|
+
});
|
|
51
|
+
if (!representable)
|
|
52
|
+
return undefined;
|
|
53
|
+
const rules = changeSet.updates
|
|
54
|
+
.filter((update) => update.type === "addRules")
|
|
55
|
+
.flatMap((update) => update.rules);
|
|
56
|
+
const readPaths = rules
|
|
57
|
+
.filter((rule) => rule.toolName === "Read")
|
|
58
|
+
.map((rule) => rule.ruleContent?.replace(/\/\*\*$/, ""))
|
|
59
|
+
.filter((value) => !!value);
|
|
60
|
+
const commands = [
|
|
61
|
+
...new Set(rules
|
|
62
|
+
.filter((rule) => rule.toolName === toolName && rule.ruleContent)
|
|
63
|
+
.map((rule) => permissionRulePrefix(rule.ruleContent))),
|
|
64
|
+
];
|
|
65
|
+
const directories = changeSet.updates
|
|
66
|
+
.filter((update) => update.type === "addDirectories")
|
|
67
|
+
.flatMap((update) => update.directories);
|
|
68
|
+
const hasPaths = readPaths.length > 0 || directories.length > 0;
|
|
69
|
+
if (readPaths.length > 0 && directories.length === 0 && commands.length === 0) {
|
|
70
|
+
return `Yes, allow reading from ${displayPaths(readPaths)} from this project`;
|
|
71
|
+
}
|
|
72
|
+
if (directories.length > 0 && readPaths.length === 0 && commands.length === 0) {
|
|
73
|
+
return `Yes, and always allow access to ${displayPaths(directories)} from this project`;
|
|
74
|
+
}
|
|
75
|
+
if (commands.length > 0 && !hasPaths) {
|
|
76
|
+
return `Yes, and don't ask again for ${displayList(commands)} commands`;
|
|
77
|
+
}
|
|
78
|
+
if (hasPaths && commands.length === 0) {
|
|
79
|
+
return `Yes, and always allow access to ${displayPaths([...directories, ...readPaths])} from this project`;
|
|
80
|
+
}
|
|
81
|
+
if (hasPaths && commands.length > 0) {
|
|
82
|
+
const paths = [...directories, ...readPaths];
|
|
83
|
+
return paths.length === 1 && commands.length === 1
|
|
84
|
+
? `Yes, and allow access to ${displayPaths(paths)} and ${displayList(commands)} commands`
|
|
85
|
+
: `Yes, and allow ${displayPaths(paths)} access and ${displayList(commands)} commands`;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
function buildShellPermissionOptions(context, toolName) {
|
|
90
|
+
const name = context.durableChangeSet
|
|
91
|
+
? shellSuggestionsLabel(toolName, context.durableChangeSet)
|
|
92
|
+
: undefined;
|
|
93
|
+
return withOptionalUpdate(context.durableChangeSet, name, "Yes", "No");
|
|
94
|
+
}
|
|
95
|
+
export function buildBashPermissionOptions(context) {
|
|
96
|
+
return buildShellPermissionOptions(context, "Bash");
|
|
97
|
+
}
|
|
98
|
+
export function buildPowerShellPermissionOptions(context) {
|
|
99
|
+
return buildShellPermissionOptions(context, "PowerShell");
|
|
100
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { type PermissionOptionContext } from "./shared.js";
|
|
3
|
+
export declare function buildWebFetchPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
4
|
+
export declare function buildSkillPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
5
|
+
export declare function buildEnterPlanModePermissionOptions(): PermissionOption[];
|
|
6
|
+
export declare function buildExitPlanModePermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
7
|
+
export declare function buildSandboxNetworkPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
8
|
+
export declare function buildFallbackPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
9
|
+
export declare function isComputerUseMcpTool(toolName: string): boolean;
|
|
10
|
+
export declare function buildComputerUseMcpPermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
11
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/permissions/options/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAML,KAAK,uBAAuB,EAI7B,MAAM,aAAa,CAAC;AAErB,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAapB;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAqBhG;AAED,wBAAgB,mCAAmC,IAAI,gBAAgB,EAAE,CAExE;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAyDpB;AAED,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAOpB;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAiBpB;AAID,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAWpB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { allowOnce, exactLocalAllowRule, isMcpAllowChangeSet, PERMISSION_OPTION_ID, plainString, reject, withGeneratedUpdate, withOptionalUpdate, } from "./shared.js";
|
|
2
|
+
export function buildWebFetchPermissionOptions(context) {
|
|
3
|
+
const url = plainString(context.input.url);
|
|
4
|
+
if (url && context.allowPersistentOptions !== false) {
|
|
5
|
+
try {
|
|
6
|
+
const hostname = new URL(url).hostname;
|
|
7
|
+
if (hostname) {
|
|
8
|
+
return withGeneratedUpdate(`Yes, and don't ask again for ${hostname}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Invalid input cannot produce Claude Code's domain-specific option.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return [allowOnce(), reject("No")];
|
|
16
|
+
}
|
|
17
|
+
export function buildSkillPermissionOptions(context) {
|
|
18
|
+
const skill = plainString(context.input.skill);
|
|
19
|
+
const options = [allowOnce()];
|
|
20
|
+
if (skill && context.allowPersistentOptions !== false) {
|
|
21
|
+
options.push({
|
|
22
|
+
optionId: PERMISSION_OPTION_ID.allowSkillExact,
|
|
23
|
+
name: `Yes, and don't ask again for ${skill}`,
|
|
24
|
+
kind: "allow_always",
|
|
25
|
+
});
|
|
26
|
+
const spaceIndex = skill.indexOf(" ");
|
|
27
|
+
if (spaceIndex > 0) {
|
|
28
|
+
const prefix = `${skill.slice(0, spaceIndex)}:*`;
|
|
29
|
+
options.push({
|
|
30
|
+
optionId: PERMISSION_OPTION_ID.allowSkillPrefix,
|
|
31
|
+
name: `Yes, and don't ask again for ${prefix} commands`,
|
|
32
|
+
kind: "allow_always",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
options.push(reject("No"));
|
|
37
|
+
return options;
|
|
38
|
+
}
|
|
39
|
+
export function buildEnterPlanModePermissionOptions() {
|
|
40
|
+
return [allowOnce("Yes, enter plan mode"), reject("No, start implementing now")];
|
|
41
|
+
}
|
|
42
|
+
export function buildExitPlanModePermissionOptions(context) {
|
|
43
|
+
const modes = new Set(context.availableModes);
|
|
44
|
+
const options = [];
|
|
45
|
+
const elevatedMode = modes.has("auto")
|
|
46
|
+
? "auto"
|
|
47
|
+
: modes.has("bypassPermissions")
|
|
48
|
+
? "bypassPermissions"
|
|
49
|
+
: "acceptEdits";
|
|
50
|
+
if (plainString(context.input.plan)) {
|
|
51
|
+
const usage = context.contextUsedPercent === undefined ? "" : ` (${context.contextUsedPercent}% used)`;
|
|
52
|
+
if (elevatedMode === "auto") {
|
|
53
|
+
options.push({
|
|
54
|
+
optionId: PERMISSION_OPTION_ID.exitPlanClearAuto,
|
|
55
|
+
name: `Yes, clear context${usage} and use auto mode`,
|
|
56
|
+
kind: "allow_always",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (elevatedMode === "bypassPermissions") {
|
|
60
|
+
options.push({
|
|
61
|
+
optionId: PERMISSION_OPTION_ID.exitPlanClearBypass,
|
|
62
|
+
name: `Yes, clear context${usage} and bypass permissions`,
|
|
63
|
+
kind: "allow_always",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
options.push({
|
|
68
|
+
optionId: PERMISSION_OPTION_ID.exitPlanClearAcceptEdits,
|
|
69
|
+
name: `Yes, clear context${usage} and auto-accept edits`,
|
|
70
|
+
kind: "allow_always",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (elevatedMode === "auto") {
|
|
75
|
+
options.push({
|
|
76
|
+
optionId: PERMISSION_OPTION_ID.exitPlanAuto,
|
|
77
|
+
name: "Yes, and use auto mode",
|
|
78
|
+
kind: "allow_always",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else if (elevatedMode === "bypassPermissions") {
|
|
82
|
+
options.push({
|
|
83
|
+
optionId: PERMISSION_OPTION_ID.exitPlanBypass,
|
|
84
|
+
name: "Yes, and bypass permissions",
|
|
85
|
+
kind: "allow_always",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
options.push({
|
|
90
|
+
optionId: PERMISSION_OPTION_ID.exitPlanAcceptEdits,
|
|
91
|
+
name: "Yes, auto-accept edits",
|
|
92
|
+
kind: "allow_always",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
options.push({
|
|
96
|
+
optionId: PERMISSION_OPTION_ID.exitPlanDefault,
|
|
97
|
+
name: "Yes, manually approve edits",
|
|
98
|
+
kind: "allow_once",
|
|
99
|
+
});
|
|
100
|
+
options.push(reject("No, keep planning"));
|
|
101
|
+
return options;
|
|
102
|
+
}
|
|
103
|
+
export function buildSandboxNetworkPermissionOptions(context) {
|
|
104
|
+
const host = plainString(context.input.host);
|
|
105
|
+
const name = host && exactLocalAllowRule(context.durableChangeSet, context.toolName, host)
|
|
106
|
+
? `Yes, and don't ask again for ${host}`
|
|
107
|
+
: undefined;
|
|
108
|
+
return withOptionalUpdate(context.durableChangeSet, name, "Yes", "No");
|
|
109
|
+
}
|
|
110
|
+
export function buildFallbackPermissionOptions(context) {
|
|
111
|
+
const toolLabel = plainString(context.displayName) ?? context.toolName;
|
|
112
|
+
if (context.toolName.startsWith("mcp__")) {
|
|
113
|
+
const changeSet = context.allowPersistentOptions !== false &&
|
|
114
|
+
isMcpAllowChangeSet(context.durableChangeSet, context.toolName)
|
|
115
|
+
? context.durableChangeSet
|
|
116
|
+
: undefined;
|
|
117
|
+
return withOptionalUpdate(changeSet, changeSet ? `Yes, and don't ask again for ${toolLabel} commands` : undefined);
|
|
118
|
+
}
|
|
119
|
+
if (context.allowPersistentOptions !== false) {
|
|
120
|
+
return withGeneratedUpdate(`Yes, and don't ask again for ${toolLabel} commands`);
|
|
121
|
+
}
|
|
122
|
+
return [allowOnce(), reject("No")];
|
|
123
|
+
}
|
|
124
|
+
const COMPUTER_USE_MCP_TOOL_PREFIX = "mcp__computer-use__";
|
|
125
|
+
export function isComputerUseMcpTool(toolName) {
|
|
126
|
+
return toolName.startsWith(COMPUTER_USE_MCP_TOOL_PREFIX);
|
|
127
|
+
}
|
|
128
|
+
export function buildComputerUseMcpPermissionOptions(context) {
|
|
129
|
+
const toolLabel = plainString(context.displayName) ?? context.toolName;
|
|
130
|
+
const changeSet = context.allowPersistentOptions !== false &&
|
|
131
|
+
isMcpAllowChangeSet(context.durableChangeSet, context.toolName)
|
|
132
|
+
? context.durableChangeSet
|
|
133
|
+
: undefined;
|
|
134
|
+
return withOptionalUpdate(changeSet, changeSet ? `Yes, and don't ask again for ${toolLabel}` : undefined);
|
|
135
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { PermissionOptionContext } from "./options/shared.js";
|
|
3
|
+
export { PERMISSION_OPTION_ID } from "./options/shared.js";
|
|
4
|
+
export declare function buildClaudePermissionOptions(context: PermissionOptionContext): PermissionOption[];
|
|
5
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/permissions/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAUjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAYnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,EAAE,CAIjG"}
|