rulesync 43.4.2 → 43.5.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/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-ClFasGQ6.cjs → import-CAePeHTT.cjs} +710 -128
- package/dist/{import-BWOC-Tig.js → import-C_XK2L62.js} +711 -129
- package/dist/{import-BWOC-Tig.js.map → import-C_XK2L62.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -8400,17 +8400,29 @@ const CodewhalePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8400
8400
|
rules: zod_mini.z.optional(zod_mini.z.array(CodewhalePermissionRuleSchema))
|
|
8401
8401
|
});
|
|
8402
8402
|
/**
|
|
8403
|
-
* The
|
|
8404
|
-
*
|
|
8403
|
+
* The values VS Code's `chat.permissions.default` setting accepts: the starting
|
|
8404
|
+
* permission level of new Local-harness chat sessions — `default` (Default
|
|
8405
8405
|
* Permissions), `autoApprove` (Bypass Approvals) and `autopilot` (Autopilot).
|
|
8406
8406
|
*
|
|
8407
8407
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
8408
8408
|
*/
|
|
8409
|
-
const
|
|
8409
|
+
const COPILOT_LOCAL_PERMISSION_LEVELS = [
|
|
8410
8410
|
"default",
|
|
8411
8411
|
"autoApprove",
|
|
8412
8412
|
"autopilot"
|
|
8413
8413
|
];
|
|
8414
|
+
/**
|
|
8415
|
+
* The starting permission level the `copilot.defaultPermissionMode` override
|
|
8416
|
+
* sets for new chat sessions: the {@link COPILOT_LOCAL_PERMISSION_LEVELS}, plus
|
|
8417
|
+
* `assisted` (Assisted permissions, GA for Copilot-harness sessions since VS
|
|
8418
|
+
* Code 1.144 and unavailable to Local sessions). Each value is written to both
|
|
8419
|
+
* `chat.permissions.default` (Local sessions) and the `approvals` / `mode` of
|
|
8420
|
+
* `chat.defaultConfiguration` (Copilot-harness sessions, VS Code 1.144+).
|
|
8421
|
+
*
|
|
8422
|
+
* @see https://code.visualstudio.com/updates/v1_144
|
|
8423
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
8424
|
+
*/
|
|
8425
|
+
const COPILOT_DEFAULT_PERMISSION_MODES = [...COPILOT_LOCAL_PERMISSION_LEVELS, "assisted"];
|
|
8414
8426
|
/** `chat.agent.sandbox.enabled`: `"off"` or `"on"`. */
|
|
8415
8427
|
const CopilotSandboxEnabledSchema = zod_mini.z.enum(["off", "on"]);
|
|
8416
8428
|
/** `chat.agent.sandbox.fileSystem.userConfiguredPaths`. */
|
|
@@ -8451,7 +8463,7 @@ const CopilotSandboxSchema = zod_mini.z.strictObject({
|
|
|
8451
8463
|
/**
|
|
8452
8464
|
* Tool-scoped override block for GitHub Copilot in VS Code.
|
|
8453
8465
|
* `defaultPermissionMode` writes the workspace `chat.permissions.default`
|
|
8454
|
-
*
|
|
8466
|
+
* and `chat.defaultConfiguration` settings, and `sandbox` writes the `chat.agent.sandbox.*` settings; neither
|
|
8455
8467
|
* has a canonical category.
|
|
8456
8468
|
*/
|
|
8457
8469
|
const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
@@ -9333,6 +9345,7 @@ const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
9333
9345
|
})),
|
|
9334
9346
|
workflow_runtime_prepare: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9335
9347
|
workflow_command_gates: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9348
|
+
workflow_system_commands: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9336
9349
|
sync_conflict_resolver: zod_mini.z.optional(zod_mini.z.object({ auto_approve_tools: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9337
9350
|
allow_git_hooks: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9338
9351
|
allow_git_filters: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -9600,6 +9613,7 @@ const CodexSandboxModeSchema = zod_mini.z.enum([
|
|
|
9600
9613
|
"workspace-write",
|
|
9601
9614
|
"danger-full-access"
|
|
9602
9615
|
]);
|
|
9616
|
+
const CODEX_U32_MAX = 4294967295;
|
|
9603
9617
|
/**
|
|
9604
9618
|
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
9605
9619
|
* value Codex still accepts for backward compatibility, so it is included so
|
|
@@ -9668,6 +9682,9 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
9668
9682
|
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
9669
9683
|
* Defaults to `auto_review` when neither the override nor the existing
|
|
9670
9684
|
* config sets it.
|
|
9685
|
+
* - `auto_review` — the `[auto_review]` table that configures the reviewer
|
|
9686
|
+
* `approvals_reviewer = "auto_review"` selects (`policy`, `extra_policy`,
|
|
9687
|
+
* `timeout_ms`, …). A loose table, shallow-merged like `apps` / `tui`.
|
|
9671
9688
|
* - `git_write_rules` — whether the managed profile's `:workspace_roots` table
|
|
9672
9689
|
* emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
|
|
9673
9690
|
* `:workspace` baseline makes `.git` read-only, which denies basic git
|
|
@@ -9681,7 +9698,8 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
9681
9698
|
* The keys written to `config.toml` are an **allowlist**, not verbatim
|
|
9682
9699
|
* passthrough: only `CODEXCLI_OVERRIDE_KEYS`
|
|
9683
9700
|
* (`src/constants/codexcli-paths.ts` — `approval_policy`, `sandbox_mode`,
|
|
9684
|
-
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`
|
|
9701
|
+
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`, `auto_review`,
|
|
9702
|
+
* `tui`) are emitted, and
|
|
9685
9703
|
* `computeCodexcliOverridePatch` skips anything else with a warning.
|
|
9686
9704
|
* `base_permission_profile` and `git_write_rules` are consumed by the profile
|
|
9687
9705
|
* builder rather than written, as described above, and `permission` is the
|
|
@@ -9713,6 +9731,26 @@ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
9713
9731
|
apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9714
9732
|
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})])),
|
|
9715
9733
|
/**
|
|
9734
|
+
* The `[auto_review]` table of `config.toml`, which configures the reviewer
|
|
9735
|
+
* that `approvals_reviewer = "auto_review"` (Rulesync's default) selects:
|
|
9736
|
+
* `policy` / `extra_policy` (text inserted into the reviewer prompt),
|
|
9737
|
+
* `timeout_ms` (the review budget, Codex 0.165.0+; `0` waits without a
|
|
9738
|
+
* deadline), `circuit_break_action` (`default` | `strict`) and
|
|
9739
|
+
* `conversation_history_max_output_tokens` (a positive token cap). Codex
|
|
9740
|
+
* rejects the whole config file when one of these has the wrong type or is
|
|
9741
|
+
* out of range (`timeout_ms` is a `u32`, the token cap a `NonZeroUsize`), so
|
|
9742
|
+
* they are checked here; every other key passes through verbatim.
|
|
9743
|
+
*
|
|
9744
|
+
* @see https://github.com/openai/codex/blob/rust-v0.166.0/codex-rs/config/src/config_toml.rs (`AutoReviewToml`)
|
|
9745
|
+
*/
|
|
9746
|
+
auto_review: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9747
|
+
policy: zod_mini.z.optional(zod_mini.z.string()),
|
|
9748
|
+
extra_policy: zod_mini.z.optional(zod_mini.z.string()),
|
|
9749
|
+
timeout_ms: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative(), zod_mini.z.lte(CODEX_U32_MAX))),
|
|
9750
|
+
circuit_break_action: zod_mini.z.optional(zod_mini.z.enum(["default", "strict"])),
|
|
9751
|
+
conversation_history_max_output_tokens: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.positive()))
|
|
9752
|
+
})),
|
|
9753
|
+
/**
|
|
9716
9754
|
* The `[tui]` table of `config.toml` (e.g. `vim_mode_default`, `keymap.*`).
|
|
9717
9755
|
* Not a permission surface, but like `apps` it is a top-level table with no
|
|
9718
9756
|
* canonical category, and Codex adds keys to it faster than an explicit model
|
|
@@ -12319,6 +12357,7 @@ const CODEXCLI_OVERRIDE_KEYS = [
|
|
|
12319
12357
|
"sandbox_workspace_write",
|
|
12320
12358
|
"apps",
|
|
12321
12359
|
"approvals_reviewer",
|
|
12360
|
+
"auto_review",
|
|
12322
12361
|
"tui"
|
|
12323
12362
|
];
|
|
12324
12363
|
// src/constants/takt-paths.ts
|
|
@@ -13741,6 +13780,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13741
13780
|
"workflow_arpeggio",
|
|
13742
13781
|
"workflow_runtime_prepare",
|
|
13743
13782
|
"workflow_command_gates",
|
|
13783
|
+
"workflow_system_commands",
|
|
13744
13784
|
"sync_conflict_resolver",
|
|
13745
13785
|
"allow_git_hooks",
|
|
13746
13786
|
"allow_git_filters"
|
|
@@ -13882,6 +13922,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13882
13922
|
"chat.tools.edits.autoApprove",
|
|
13883
13923
|
"chat.tools.urls.autoApprove",
|
|
13884
13924
|
"chat.permissions.default",
|
|
13925
|
+
"chat.defaultConfiguration",
|
|
13885
13926
|
"chat.agent.sandbox.enabled",
|
|
13886
13927
|
"chat.agent.sandbox.network.allowNetwork",
|
|
13887
13928
|
"chat.agent.sandbox.network.allowLocalNetwork",
|
|
@@ -34123,7 +34164,7 @@ function normalizeAugmentcodeToolList(value) {
|
|
|
34123
34164
|
return tools.length > 0 ? tools : void 0;
|
|
34124
34165
|
}
|
|
34125
34166
|
const AugmentcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
34126
|
-
name: zod_mini.z.string(),
|
|
34167
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
34127
34168
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
34128
34169
|
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
34129
34170
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -34182,7 +34223,7 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34182
34223
|
outputRoot: ".",
|
|
34183
34224
|
frontmatter: {
|
|
34184
34225
|
targets: ["*"],
|
|
34185
|
-
name,
|
|
34226
|
+
name: name ?? (0, node_path.basename)(this.getRelativeFilePath(), (0, node_path.extname)(this.getRelativeFilePath())),
|
|
34186
34227
|
description,
|
|
34187
34228
|
...Object.keys(augmentcodeSection).length > 0 && { augmentcode: augmentcodeSection }
|
|
34188
34229
|
},
|
|
@@ -34248,7 +34289,8 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34248
34289
|
const dirPath = relativeDirPath ?? this.getSettablePaths({ global }).relativeDirPath;
|
|
34249
34290
|
const filePath = (0, node_path.join)(outputRoot, dirPath, relativeFilePath);
|
|
34250
34291
|
const fileContent = await readFileContent(filePath);
|
|
34251
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
34292
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
34293
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
34252
34294
|
const result = AugmentcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
34253
34295
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
34254
34296
|
return new AugmentcodeSubagent({
|
|
@@ -41831,7 +41873,23 @@ function sanitizeOpencodeMcpNamePart(value) {
|
|
|
41831
41873
|
* key that matches nothing.
|
|
41832
41874
|
*/
|
|
41833
41875
|
function opencodeMcpToolPermissionKey(serverName, toolName) {
|
|
41834
|
-
return `${
|
|
41876
|
+
return `${opencodeMcpServerKeyPrefix(serverName)}${toolName.replace(/[^a-zA-Z0-9_\-*?]/g, "_")}`;
|
|
41877
|
+
}
|
|
41878
|
+
/** The sanitized `<server>_` prefix every permission key of the server's tools starts with. */
|
|
41879
|
+
function opencodeMcpServerKeyPrefix(serverName) {
|
|
41880
|
+
return `${sanitizeOpencodeMcpNamePart(serverName)}_`;
|
|
41881
|
+
}
|
|
41882
|
+
/**
|
|
41883
|
+
* Whether `key` is exactly the sanitized `<server>_` prefix of one of
|
|
41884
|
+
* `serverNames` (e.g. `a_b_` for server `a.b` or `a_b`). No tool has an empty
|
|
41885
|
+
* name, so such a key filters nothing of that server, and
|
|
41886
|
+
* {@link findOpencodeMcpToolKeyOwner} gives it no owner. Read as an unlisted
|
|
41887
|
+
* server's filter instead, it would be split at the first `_` into a phantom
|
|
41888
|
+
* server (`a` with tool `b_`), so importers leave it out of the rulesync
|
|
41889
|
+
* config, and the OpenCode family's generate keeps it in the file as it was.
|
|
41890
|
+
*/
|
|
41891
|
+
function isBareOpencodeMcpServerPrefix({ key, serverNames }) {
|
|
41892
|
+
return serverNames.some((serverName) => key === opencodeMcpServerKeyPrefix(serverName));
|
|
41835
41893
|
}
|
|
41836
41894
|
/**
|
|
41837
41895
|
* The server among `serverNames` whose tool a permission key names, with the
|
|
@@ -41845,7 +41903,7 @@ function findOpencodeMcpToolKeyOwner({ key, serverNames, builtinKeys, acceptRawP
|
|
|
41845
41903
|
if (builtinKeys.has(key)) return;
|
|
41846
41904
|
let owner;
|
|
41847
41905
|
for (const serverName of serverNames) {
|
|
41848
|
-
const sanitized =
|
|
41906
|
+
const sanitized = opencodeMcpServerKeyPrefix(serverName);
|
|
41849
41907
|
const prefixes = acceptRawPrefix ? (0, es_toolkit.uniq)([sanitized, `${serverName}_`]) : [sanitized];
|
|
41850
41908
|
for (const prefix of prefixes) if (key.length > prefix.length && key.startsWith(prefix) && (owner === void 0 || prefix.length > owner.prefix.length)) owner = {
|
|
41851
41909
|
serverName,
|
|
@@ -42306,7 +42364,10 @@ function convertFromKiloFormat(kiloMcp, tools, permission) {
|
|
|
42306
42364
|
const serverNames = Object.keys(kiloMcp);
|
|
42307
42365
|
return {
|
|
42308
42366
|
...Object.fromEntries(Object.entries(kiloMcp).map(([serverName, serverConfig]) => [serverName, kiloServerToRulesync(serverName, serverConfig, splitKiloServerTools(serverName, serverNames, filters))])),
|
|
42309
|
-
...orphanMcpToolFiltersToRulesync(kiloMcp, tools)
|
|
42367
|
+
...orphanMcpToolFiltersToRulesync(kiloMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findKiloMcpToolKeyOwner(key, serverNames) && !isBareOpencodeMcpServerPrefix({
|
|
42368
|
+
key,
|
|
42369
|
+
serverNames
|
|
42370
|
+
}))))
|
|
42310
42371
|
};
|
|
42311
42372
|
}
|
|
42312
42373
|
/**
|
|
@@ -42824,16 +42885,20 @@ function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
|
42824
42885
|
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42825
42886
|
key,
|
|
42826
42887
|
serverNames
|
|
42888
|
+
}) && !isBareOpencodeMcpServerPrefix({
|
|
42889
|
+
key,
|
|
42890
|
+
serverNames
|
|
42827
42891
|
})))),
|
|
42828
42892
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42829
42893
|
};
|
|
42830
42894
|
}
|
|
42895
|
+
const OPENCODE_MCP_BUILTIN_TOOL_KEYS = /* @__PURE__ */ new Set();
|
|
42831
42896
|
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42832
42897
|
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42833
42898
|
return findOpencodeMcpToolKeyOwner({
|
|
42834
42899
|
key,
|
|
42835
42900
|
serverNames,
|
|
42836
|
-
builtinKeys:
|
|
42901
|
+
builtinKeys: OPENCODE_MCP_BUILTIN_TOOL_KEYS,
|
|
42837
42902
|
acceptRawPrefix: true
|
|
42838
42903
|
});
|
|
42839
42904
|
}
|
|
@@ -43003,6 +43068,40 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
43003
43068
|
tools
|
|
43004
43069
|
};
|
|
43005
43070
|
}
|
|
43071
|
+
/**
|
|
43072
|
+
* Copy each key of `existingTools` that is a bare sanitized `<server>_` prefix
|
|
43073
|
+
* of one of `serverNames` into `tools`, unless a generated filter already
|
|
43074
|
+
* writes the same key or another listed server owns it (`a_b_` is tool `b_` of
|
|
43075
|
+
* server `a` when `a` is listed too). An owned key is the rulesync config's to
|
|
43076
|
+
* write, so carrying it over would keep a filter the user removed. Mutates
|
|
43077
|
+
* `tools` in place.
|
|
43078
|
+
*/
|
|
43079
|
+
function preserveBareOpencodeMcpServerPrefixKeys({ tools, existingTools, serverNames }) {
|
|
43080
|
+
for (const [key, enabled] of Object.entries(existingTools ?? {})) if (!Object.hasOwn(tools, key) && isBareOpencodeMcpServerPrefix({
|
|
43081
|
+
key,
|
|
43082
|
+
serverNames
|
|
43083
|
+
}) && !findOpencodeToolOwner({
|
|
43084
|
+
key,
|
|
43085
|
+
serverNames
|
|
43086
|
+
})) tools[key] = enabled;
|
|
43087
|
+
}
|
|
43088
|
+
/**
|
|
43089
|
+
* When the existing file does not pass the schema, its `tools` map cannot be
|
|
43090
|
+
* read, so a bare `<server>_` key in it is not carried over. Say so rather
|
|
43091
|
+
* than lift the deny it may hold without a word.
|
|
43092
|
+
*/
|
|
43093
|
+
function warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({ existingJson, serverNames, logger }) {
|
|
43094
|
+
const rawTools = isRecord(existingJson) ? existingJson.tools : void 0;
|
|
43095
|
+
if (!isRecord(rawTools)) return;
|
|
43096
|
+
const bareKeys = Object.keys(rawTools).filter((key) => isBareOpencodeMcpServerPrefix({
|
|
43097
|
+
key,
|
|
43098
|
+
serverNames
|
|
43099
|
+
}) && !findOpencodeToolOwner({
|
|
43100
|
+
key,
|
|
43101
|
+
serverNames
|
|
43102
|
+
}));
|
|
43103
|
+
if (bareKeys.length > 0) logger?.warn(`OpenCode MCP: the existing config does not match the expected schema, so the bare server-prefix tool ${bareKeys.length === 1 ? "key" : "keys"} ${bareKeys.map(quoteValueForWarning).join(", ")} ${bareKeys.length === 1 ? "is" : "are"} not kept in "tools".`);
|
|
43104
|
+
}
|
|
43006
43105
|
const OpencodeMcpFeatureOptionsSchema = zod_mini.z.looseObject({ configShape: zod_mini.z.optional(zod_mini.z.enum(["v1", "v2"])) });
|
|
43007
43106
|
var OpencodeMcp = class extends ToolMcp {
|
|
43008
43107
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -43135,6 +43234,16 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43135
43234
|
convertedMcp,
|
|
43136
43235
|
mcpServers: transformedServers
|
|
43137
43236
|
});
|
|
43237
|
+
if (existingMcp.success) preserveBareOpencodeMcpServerPrefixKeys({
|
|
43238
|
+
tools: mcpTools,
|
|
43239
|
+
existingTools: existingMcp.data.tools,
|
|
43240
|
+
serverNames: Object.keys(transformedServers)
|
|
43241
|
+
});
|
|
43242
|
+
else warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({
|
|
43243
|
+
existingJson: this.lowerConfig(existingJson),
|
|
43244
|
+
serverNames: Object.keys(transformedServers),
|
|
43245
|
+
logger
|
|
43246
|
+
});
|
|
43138
43247
|
if (this.layout.configVersion === "v2") warnAboutOpencodeV2SseServers({
|
|
43139
43248
|
mcpServers: transformedServers,
|
|
43140
43249
|
convertedMcp,
|
|
@@ -51689,6 +51798,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
51689
51798
|
factory
|
|
51690
51799
|
});
|
|
51691
51800
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
51801
|
+
this.warnForAugmentcodePluginRules(toolRules);
|
|
51692
51802
|
await this.warnForInstructionBudget({
|
|
51693
51803
|
toolRules,
|
|
51694
51804
|
meta
|
|
@@ -52080,6 +52190,20 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52080
52190
|
this.logger.warn(`Writing ${stripControlCharacters(rootFileRelativePath)} for ${this.toolTarget} means ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} will no longer be read. Run \`rulesync import --targets ${this.toolTarget}\` first to carry that content into ${RULESYNC_RULES_RELATIVE_DIR_PATH}, or delete ${listedNames.length === 1 ? "it" : "them"} once you have checked the content is already in the root file.`);
|
|
52081
52191
|
}
|
|
52082
52192
|
/**
|
|
52193
|
+
* Auggie 1.x (still a prerelease on npm at the time of writing; the stable
|
|
52194
|
+
* line is 0.36.0) no longer loads `rules/` from plugins: "Rules shipped in
|
|
52195
|
+
* plugins (`rules/` in a plugin)" is listed among the removed features, while
|
|
52196
|
+
* commands, agents, skills, hooks and MCP servers still load. The rule files
|
|
52197
|
+
* are still written, because the stable 0.x CLI reads them, but under 1.x the
|
|
52198
|
+
* bundle silently loses them, so say so.
|
|
52199
|
+
* @see https://docs.augmentcode.com/cli/whats-new
|
|
52200
|
+
* @see https://docs.augmentcode.com/cli/v0/plugins
|
|
52201
|
+
*/
|
|
52202
|
+
warnForAugmentcodePluginRules(toolRules) {
|
|
52203
|
+
if (this.toolTarget !== "augmentcode-plugin" || toolRules.length === 0) return;
|
|
52204
|
+
this.logger.warn(`augmentcode-plugin: plugin rules (rules/*.md) are loaded by Auggie 0.x only; Auggie 1.x no longer reads rules/ from plugins, so these ${toolRules.length} rule file(s) have no effect there. Ship instructions that must reach Auggie 1.x users as a skill, or have them generate the augmentcode target in their project.`);
|
|
52205
|
+
}
|
|
52206
|
+
/**
|
|
52083
52207
|
* Warp still reads a legacy `WARP.md`, and when both `WARP.md` and
|
|
52084
52208
|
* `AGENTS.md` sit in the same directory, `WARP.md` takes priority — so an
|
|
52085
52209
|
* `AGENTS.md` this run writes next to one would never be read. That holds for
|
|
@@ -82322,6 +82446,57 @@ function matchesGlobStep(step, character) {
|
|
|
82322
82446
|
const admitted = step.members.has(character) || step.ranges.some(([low, high]) => code >= low && code <= high);
|
|
82323
82447
|
return step.negated ? !admitted : admitted;
|
|
82324
82448
|
}
|
|
82449
|
+
/**
|
|
82450
|
+
* Whether `value` matches `glob` in full, with `*`, `?` and `[...]` carrying
|
|
82451
|
+
* their usual meaning. The glob comes first, unlike Node's own
|
|
82452
|
+
* `path.matchesGlob(path, pattern)`.
|
|
82453
|
+
*
|
|
82454
|
+
* The glob is walked step by step rather than translated to a regex, because
|
|
82455
|
+
* both sides of a comparison can come from a file a repository carries: `*a*a*a*a*b`
|
|
82456
|
+
* translated to `^.*a.*a.*a.*a.*b$` takes minutes against a forty-character
|
|
82457
|
+
* name, while walking the steps is linear in the name per `*`. Remembering only
|
|
82458
|
+
* the most recent `*` to fall back to is enough — an earlier one can always give
|
|
82459
|
+
* up whatever the later one needed.
|
|
82460
|
+
*/
|
|
82461
|
+
function matchesGlob(glob, value) {
|
|
82462
|
+
return compileGlob(glob)(value);
|
|
82463
|
+
}
|
|
82464
|
+
/**
|
|
82465
|
+
* Parse `glob` once and return a predicate that walks it, for a caller that
|
|
82466
|
+
* tests the same glob against a whole list of names.
|
|
82467
|
+
*/
|
|
82468
|
+
function compileGlob(glob) {
|
|
82469
|
+
const steps = parseGlob(glob);
|
|
82470
|
+
return (value) => matchesParsedGlob(steps, value);
|
|
82471
|
+
}
|
|
82472
|
+
function matchesParsedGlob(steps, value) {
|
|
82473
|
+
const characters = [...value];
|
|
82474
|
+
let stepIndex = 0;
|
|
82475
|
+
let characterIndex = 0;
|
|
82476
|
+
let starStepIndex = -1;
|
|
82477
|
+
let starCharacterIndex = 0;
|
|
82478
|
+
while (characterIndex < characters.length) {
|
|
82479
|
+
const step = steps[stepIndex];
|
|
82480
|
+
if (step?.kind === "star") {
|
|
82481
|
+
starStepIndex = stepIndex;
|
|
82482
|
+
starCharacterIndex = characterIndex;
|
|
82483
|
+
stepIndex += 1;
|
|
82484
|
+
continue;
|
|
82485
|
+
}
|
|
82486
|
+
if (step !== void 0 && matchesGlobStep(step, characters[characterIndex] ?? "")) {
|
|
82487
|
+
stepIndex += 1;
|
|
82488
|
+
characterIndex += 1;
|
|
82489
|
+
continue;
|
|
82490
|
+
}
|
|
82491
|
+
if (starStepIndex < 0) return false;
|
|
82492
|
+
starCharacterIndex += 1;
|
|
82493
|
+
stepIndex = starStepIndex + 1;
|
|
82494
|
+
characterIndex = starCharacterIndex;
|
|
82495
|
+
}
|
|
82496
|
+
let remaining = stepIndex;
|
|
82497
|
+
while (steps[remaining]?.kind === "star") remaining += 1;
|
|
82498
|
+
return remaining === steps.length;
|
|
82499
|
+
}
|
|
82325
82500
|
/** Whether two single-character steps can both match one same character. */
|
|
82326
82501
|
function stepsShareACharacter(left, right) {
|
|
82327
82502
|
if (left.kind === "any" || right.kind === "any") return true;
|
|
@@ -83858,9 +84033,13 @@ const CANONICAL_TO_AUGMENT_TOOL_NAMES = {
|
|
|
83858
84033
|
const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENT_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
83859
84034
|
const CURRENT_TO_LEGACY_AUGMENT_TOOL_NAMES = {
|
|
83860
84035
|
terminal: "launch-process",
|
|
84036
|
+
bash: "launch-process",
|
|
84037
|
+
shell: "launch-process",
|
|
83861
84038
|
read: "view",
|
|
83862
84039
|
edit: "str-replace-editor",
|
|
83863
|
-
|
|
84040
|
+
str_replace_editor: "str-replace-editor",
|
|
84041
|
+
write: "save-file",
|
|
84042
|
+
save_file: "save-file"
|
|
83864
84043
|
};
|
|
83865
84044
|
function toAugmentToolName(canonical) {
|
|
83866
84045
|
return lookupOwn({
|
|
@@ -84012,6 +84191,7 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
84012
84191
|
while (i < body.length) {
|
|
84013
84192
|
const ch = body[i];
|
|
84014
84193
|
if (ch === "\\" && i + 1 < body.length) {
|
|
84194
|
+
if (isGlobSpecial(body[i + 1])) return false;
|
|
84015
84195
|
i += 2;
|
|
84016
84196
|
continue;
|
|
84017
84197
|
}
|
|
@@ -84028,6 +84208,37 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
84028
84208
|
}
|
|
84029
84209
|
return true;
|
|
84030
84210
|
}
|
|
84211
|
+
/** Characters a glob reads as syntax (wildcards or the start of a character class). */
|
|
84212
|
+
function isGlobSpecial(ch) {
|
|
84213
|
+
return ch === "*" || ch === "?" || ch === "[";
|
|
84214
|
+
}
|
|
84215
|
+
/**
|
|
84216
|
+
* Whether `shellRegexToGlob` can approximate a `shellInputRegex` without widening it. The lossy
|
|
84217
|
+
* conversion keeps regex quantifiers as characters, and a `*` or `?` left in the glob becomes a
|
|
84218
|
+
* wildcard: `^rm -rf /tmp/a*$` (a run of `a`s) would turn into the glob `rm -rf /tmp/a*`, which
|
|
84219
|
+
* matches `rm -rf /tmp/a /home`. So a regex is unsafe when it has an escaped literal `\*` / `\?` /
|
|
84220
|
+
* `\[` (a `[` would open a character class in targets whose globs support one),
|
|
84221
|
+
* or a bare quantifier `*` / `?` / `+` / `{` that is not part of a `.*` run (which the glob `*`
|
|
84222
|
+
* reproduces faithfully).
|
|
84223
|
+
*/
|
|
84224
|
+
function canApproximateShellRegexWithoutWidening(regex) {
|
|
84225
|
+
let i = 0;
|
|
84226
|
+
while (i < regex.length) {
|
|
84227
|
+
const ch = regex[i];
|
|
84228
|
+
if (ch === "\\" && i + 1 < regex.length) {
|
|
84229
|
+
if (isGlobSpecial(regex[i + 1])) return false;
|
|
84230
|
+
i += 2;
|
|
84231
|
+
continue;
|
|
84232
|
+
}
|
|
84233
|
+
if (ch === "." && regex[i + 1] === "*") {
|
|
84234
|
+
i += 2;
|
|
84235
|
+
continue;
|
|
84236
|
+
}
|
|
84237
|
+
if (ch === "*" || ch === "?" || ch === "+" || ch === "{") return false;
|
|
84238
|
+
i += 1;
|
|
84239
|
+
}
|
|
84240
|
+
return true;
|
|
84241
|
+
}
|
|
84031
84242
|
const MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
|
|
84032
84243
|
var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermissions {
|
|
84033
84244
|
constructor(params) {
|
|
@@ -84464,12 +84675,18 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
84464
84675
|
if (legacyToolName === "launch-process" && entry.shellInputRegex) {
|
|
84465
84676
|
const regex = entry.shellInputRegex;
|
|
84466
84677
|
if (isShellRegexRoundtrippable(regex)) pattern = shellRegexToGlob(regex);
|
|
84467
|
-
else
|
|
84468
|
-
|
|
84469
|
-
|
|
84470
|
-
|
|
84471
|
-
|
|
84472
|
-
|
|
84678
|
+
else {
|
|
84679
|
+
if (action !== "deny" && !canApproximateShellRegexWithoutWidening(regex)) {
|
|
84680
|
+
logger?.warn(`AugmentCode permissions: skipping ${action} entry for tool '${entry.toolName}': shellInputRegex '${regex}' uses a quantifier or a literal '*' / '?' that a glob cannot express, and converting it would widen the rule. The entry is not imported.`);
|
|
84681
|
+
continue;
|
|
84682
|
+
}
|
|
84683
|
+
if (action === "deny") {
|
|
84684
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`);
|
|
84685
|
+
pattern = "*";
|
|
84686
|
+
} else {
|
|
84687
|
+
pattern = shellRegexToGlob(regex);
|
|
84688
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`);
|
|
84689
|
+
}
|
|
84473
84690
|
}
|
|
84474
84691
|
} else pattern = "*";
|
|
84475
84692
|
if (isPrototypePollutionKey(pattern)) {
|
|
@@ -85962,15 +86179,19 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85962
86179
|
* `env` variables a project or local settings file cannot set, because a
|
|
85963
86180
|
* checked-out repository should not control them: where Claude Code stores its
|
|
85964
86181
|
* own files, what session content it exports, where telemetry goes, how it
|
|
85965
|
-
* starts or syncs, the timers on an unanswered
|
|
85966
|
-
*
|
|
86182
|
+
* starts or syncs, which claude.ai sign-in it uses, the timers on an unanswered
|
|
86183
|
+
* dialog, and whether it processes attachments or expands `@` file mentions,
|
|
86184
|
+
* and the screen reader (`CLAUDE_AX_*`) variables. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
85967
86185
|
*
|
|
85968
|
-
* Only the names the settings reference lists are covered; its directory
|
|
85969
|
-
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
86186
|
+
* Only the names the settings reference lists are covered; its directory,
|
|
86187
|
+
* Windows and sign-in groups are introduced with "such as", so an unlisted member of those
|
|
85970
86188
|
* families is still written. Windows variable names are case-insensitive, so
|
|
85971
|
-
* that group is matched without regard to case.
|
|
86189
|
+
* that group is matched without regard to case. `ALLUSERSPROFILE`,
|
|
86190
|
+
* `SystemDrive` and the `CommonProgramFiles` family are ignored there since
|
|
86191
|
+
* Claude Code v2.1.285.
|
|
85972
86192
|
*
|
|
85973
86193
|
* @see https://code.claude.com/docs/en/settings-reference#variables-claude-code-ignores-in-env
|
|
86194
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
|
|
85974
86195
|
*/
|
|
85975
86196
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
85976
86197
|
"BETA_TRACING_ENDPOINT",
|
|
@@ -85980,6 +86201,9 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85980
86201
|
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
85981
86202
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
85982
86203
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
86204
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
86205
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
86206
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
85983
86207
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
85984
86208
|
"CLAUDE_CODE_PLUGIN_SEED_DIR",
|
|
85985
86209
|
"CLAUDE_CODE_PROCESS_WRAPPER",
|
|
@@ -86000,14 +86224,23 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
86000
86224
|
"TMPDIR"
|
|
86001
86225
|
]);
|
|
86002
86226
|
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES = /* @__PURE__ */ new Set([
|
|
86227
|
+
"ALLUSERSPROFILE",
|
|
86228
|
+
"COMMONPROGRAMW6432",
|
|
86003
86229
|
"COMSPEC",
|
|
86004
86230
|
"LOCALAPPDATA",
|
|
86005
86231
|
"PATHEXT",
|
|
86006
86232
|
"PROGRAMDATA",
|
|
86007
86233
|
"PROGRAMW6432",
|
|
86008
86234
|
"PSMODULEPATH",
|
|
86235
|
+
"SYSTEMDRIVE",
|
|
86009
86236
|
"SYSTEMROOT"
|
|
86010
86237
|
]);
|
|
86238
|
+
/**
|
|
86239
|
+
* Windows name prefixes matched as whole families: `ProgramFiles*` and
|
|
86240
|
+
* `CommonProgramFiles*`. `ProgramW6432` and `CommonProgramW6432` are exact
|
|
86241
|
+
* names in the set above.
|
|
86242
|
+
*/
|
|
86243
|
+
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES = ["PROGRAMFILES", "COMMONPROGRAMFILES"];
|
|
86011
86244
|
const CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES = [
|
|
86012
86245
|
"_ENDPOINT",
|
|
86013
86246
|
"_HEADERS",
|
|
@@ -86052,7 +86285,7 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
86052
86285
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
86053
86286
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_") || name.startsWith("CLAUDE_AX_")) return true;
|
|
86054
86287
|
const upper = name.toUpperCase();
|
|
86055
|
-
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || upper.startsWith(
|
|
86288
|
+
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES.some((prefix) => upper.startsWith(prefix))) return true;
|
|
86056
86289
|
if (name.startsWith("OTEL_EXPORTER_OTLP_") && CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix))) return true;
|
|
86057
86290
|
if (Object.hasOwn(CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES, name)) {
|
|
86058
86291
|
const offValues = CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES[name];
|
|
@@ -86084,7 +86317,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
86084
86317
|
kept[name] = value;
|
|
86085
86318
|
}
|
|
86086
86319
|
if (ignoredEverywhere.length > 0) logger?.warn(`Claude Code permissions: Claude Code ignores ${ignoredEverywhere.map((name) => `'env.${name}'`).join(", ")} in every settings file, so ${ignoredEverywhere.length === 1 ? "it is" : "they are"} not written to ${relativeFilePath}. Set ${ignoredEverywhere.length === 1 ? "it" : "them"} in the environment Claude Code starts from instead, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`);
|
|
86087
|
-
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
86320
|
+
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set the claude.ai sign-in, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
86088
86321
|
return kept;
|
|
86089
86322
|
}
|
|
86090
86323
|
/**
|
|
@@ -86734,7 +86967,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
86734
86967
|
"ask",
|
|
86735
86968
|
"deny"
|
|
86736
86969
|
];
|
|
86737
|
-
const CATCH_ALL_PATTERN$
|
|
86970
|
+
const CATCH_ALL_PATTERN$14 = "*";
|
|
86738
86971
|
const MCP_CATEGORY = "mcp";
|
|
86739
86972
|
const MCP_RULE_PREFIX = "mcp__";
|
|
86740
86973
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -86804,7 +87037,7 @@ function categoryOfRule$2(rule) {
|
|
|
86804
87037
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
86805
87038
|
*/
|
|
86806
87039
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
86807
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
87040
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$14;
|
|
86808
87041
|
if (category === MCP_CATEGORY) {
|
|
86809
87042
|
if (catchAll && action === "allow") {
|
|
86810
87043
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -86813,7 +87046,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
86813
87046
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
86814
87047
|
}
|
|
86815
87048
|
if (category === "*") {
|
|
86816
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
87049
|
+
if (catchAll) return CATCH_ALL_PATTERN$14;
|
|
86817
87050
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
86818
87051
|
return null;
|
|
86819
87052
|
}
|
|
@@ -86831,7 +87064,7 @@ function parseCodebuddyRule(rule) {
|
|
|
86831
87064
|
const { specifier } = splitCodebuddyRule(rule);
|
|
86832
87065
|
if (specifier === void 0) return {
|
|
86833
87066
|
category,
|
|
86834
|
-
pattern: CATCH_ALL_PATTERN$
|
|
87067
|
+
pattern: CATCH_ALL_PATTERN$14
|
|
86835
87068
|
};
|
|
86836
87069
|
const trimmed = specifier.trim();
|
|
86837
87070
|
return trimmed === "" ? null : {
|
|
@@ -89157,8 +89390,8 @@ function mapBashActionToDecision(action) {
|
|
|
89157
89390
|
// src/features/permissions/commandcode-permissions.ts
|
|
89158
89391
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
89159
89392
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
89160
|
-
const CATCH_ALL_PATTERN$
|
|
89161
|
-
const MCP_CANONICAL_PREFIX$
|
|
89393
|
+
const CATCH_ALL_PATTERN$13 = "*";
|
|
89394
|
+
const MCP_CANONICAL_PREFIX$8 = "mcp__";
|
|
89162
89395
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
89163
89396
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
89164
89397
|
bash: "Shell",
|
|
@@ -89203,10 +89436,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
89203
89436
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
89204
89437
|
*/
|
|
89205
89438
|
function buildCommandcodeRule(category, pattern) {
|
|
89206
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
89207
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89208
|
-
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$
|
|
89209
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
89439
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$13 || pattern === "";
|
|
89440
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$8)) return catchAll ? category : `${category}(${pattern})`;
|
|
89441
|
+
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$8}${pattern}`;
|
|
89442
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$13 : null;
|
|
89210
89443
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
89211
89444
|
if (tool === void 0) return null;
|
|
89212
89445
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -89242,7 +89475,7 @@ function splitCommandcodeRule(rule) {
|
|
|
89242
89475
|
}
|
|
89243
89476
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
89244
89477
|
function isCatchAllSpecifier(inner) {
|
|
89245
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
89478
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$13;
|
|
89246
89479
|
}
|
|
89247
89480
|
/**
|
|
89248
89481
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -89276,34 +89509,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
89276
89509
|
if (server.length === 0) return null;
|
|
89277
89510
|
const everyServer = {
|
|
89278
89511
|
category: "mcp",
|
|
89279
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89512
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89280
89513
|
notInAllow: true
|
|
89281
89514
|
};
|
|
89282
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89283
|
-
if (server === CATCH_ALL_PATTERN$
|
|
89284
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
89515
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8)) {
|
|
89516
|
+
if (server === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89517
|
+
if (server.includes(CATCH_ALL_PATTERN$13)) return null;
|
|
89285
89518
|
return {
|
|
89286
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
89287
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89519
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$13 ? `${MCP_CANONICAL_PREFIX$8}${server}` : `${MCP_CANONICAL_PREFIX$8}${remainder}`,
|
|
89520
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89288
89521
|
notInAllow: false
|
|
89289
89522
|
};
|
|
89290
89523
|
}
|
|
89291
|
-
const category = `${MCP_CANONICAL_PREFIX$
|
|
89292
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
89524
|
+
const category = `${MCP_CANONICAL_PREFIX$8}${remainder}`;
|
|
89525
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$13);
|
|
89293
89526
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
89294
89527
|
category,
|
|
89295
89528
|
pattern: inner,
|
|
89296
89529
|
notInAllow: true
|
|
89297
89530
|
} : null;
|
|
89298
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
89299
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
89300
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
89301
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89531
|
+
if (remainder === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89532
|
+
if (rest === CATCH_ALL_PATTERN$13 && !server.includes(CATCH_ALL_PATTERN$13)) return {
|
|
89533
|
+
category: `${MCP_CANONICAL_PREFIX$8}${server}`,
|
|
89534
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89302
89535
|
notInAllow: true
|
|
89303
89536
|
};
|
|
89304
89537
|
return namesTool ? {
|
|
89305
89538
|
category,
|
|
89306
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89539
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89307
89540
|
notInAllow: false
|
|
89308
89541
|
} : null;
|
|
89309
89542
|
}
|
|
@@ -89327,14 +89560,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89327
89560
|
if (category === "bash") {
|
|
89328
89561
|
if (isCatchAllSpecifier(inner)) return {
|
|
89329
89562
|
category,
|
|
89330
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89563
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89331
89564
|
notInAllow: false
|
|
89332
89565
|
};
|
|
89333
89566
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
89334
89567
|
if (normalized === "" || normalized === ":*") return null;
|
|
89335
89568
|
return isCatchAllSpecifier(normalized) ? {
|
|
89336
89569
|
category,
|
|
89337
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89570
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89338
89571
|
notInAllow: true
|
|
89339
89572
|
} : {
|
|
89340
89573
|
category,
|
|
@@ -89344,7 +89577,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89344
89577
|
}
|
|
89345
89578
|
if (isCatchAllSpecifier(inner)) return {
|
|
89346
89579
|
category,
|
|
89347
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89580
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89348
89581
|
notInAllow: false
|
|
89349
89582
|
};
|
|
89350
89583
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -89363,13 +89596,13 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89363
89596
|
*/
|
|
89364
89597
|
function parseCommandcodeRule(rule) {
|
|
89365
89598
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89366
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
89599
|
+
if (tool === CATCH_ALL_PATTERN$13) return isCatchAllSpecifier(inner) ? {
|
|
89367
89600
|
category: "*",
|
|
89368
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89601
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89369
89602
|
notInAllow: true
|
|
89370
89603
|
} : null;
|
|
89371
89604
|
const lowered = tool.toLowerCase();
|
|
89372
|
-
if (lowered.startsWith(MCP_CANONICAL_PREFIX$
|
|
89605
|
+
if (lowered.startsWith(MCP_CANONICAL_PREFIX$8)) return parseMcpCommandcodeRule({
|
|
89373
89606
|
tool,
|
|
89374
89607
|
inner
|
|
89375
89608
|
});
|
|
@@ -89387,12 +89620,12 @@ function parseCommandcodeRule(rule) {
|
|
|
89387
89620
|
*/
|
|
89388
89621
|
function isFoldedMcpToolNameRule({ rule, category }) {
|
|
89389
89622
|
const { tool } = splitCommandcodeRule(rule);
|
|
89390
|
-
return category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89623
|
+
return category.startsWith(MCP_CANONICAL_PREFIX$8) && !tool.startsWith(MCP_CANONICAL_PREFIX$8);
|
|
89391
89624
|
}
|
|
89392
89625
|
/** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
|
|
89393
89626
|
function scopedMcpRuleTool(rule) {
|
|
89394
89627
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89395
|
-
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89628
|
+
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$8) ? tool : null;
|
|
89396
89629
|
}
|
|
89397
89630
|
/**
|
|
89398
89631
|
* The string entries of a Command Code permission list. Command Code reads
|
|
@@ -89435,7 +89668,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
89435
89668
|
}).filter((rule) => {
|
|
89436
89669
|
const parsed = parseCommandcodeRule(rule);
|
|
89437
89670
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
89438
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
89671
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$13})`);
|
|
89439
89672
|
if (key !== "allow" && isStricterAction({
|
|
89440
89673
|
action: key,
|
|
89441
89674
|
existing: replacement
|
|
@@ -89492,7 +89725,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89492
89725
|
const ranked = /* @__PURE__ */ new Map();
|
|
89493
89726
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
89494
89727
|
for (const [category, rules] of Object.entries(permission)) {
|
|
89495
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
89728
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$13);
|
|
89496
89729
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
89497
89730
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
89498
89731
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -89561,7 +89794,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89561
89794
|
function skippedAllowImportMessage({ rule, category }) {
|
|
89562
89795
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89563
89796
|
if (category === "bash") return `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, which rulesync cannot import without widening the grant to every command, so it was not imported.`;
|
|
89564
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89797
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8) || isCatchAllSpecifier(inner)) return `Command Code ignores '${rule}' in "allow" (an allow rule must name what it grants), so it was not imported.`;
|
|
89565
89798
|
return `Command Code globs the specifier of '${rule}' in "allow" against the call's arguments, which rulesync cannot import without widening the grant to the whole tool, so it was not imported.`;
|
|
89566
89799
|
}
|
|
89567
89800
|
/**
|
|
@@ -89756,7 +89989,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
89756
89989
|
}
|
|
89757
89990
|
// src/features/permissions/continue-permissions.ts
|
|
89758
89991
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
89759
|
-
const CATCH_ALL_PATTERN$
|
|
89992
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
89760
89993
|
/**
|
|
89761
89994
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
89762
89995
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -89764,7 +89997,7 @@ const CATCH_ALL_PATTERN$11 = "*";
|
|
|
89764
89997
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
89765
89998
|
*/
|
|
89766
89999
|
function normalizeCatchAllPattern$1(pattern) {
|
|
89767
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90000
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$12 : pattern;
|
|
89768
90001
|
}
|
|
89769
90002
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
89770
90003
|
bash: "Bash",
|
|
@@ -89808,7 +90041,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
89808
90041
|
if (toolName === "") return;
|
|
89809
90042
|
return {
|
|
89810
90043
|
toolName,
|
|
89811
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
90044
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$12 : pattern
|
|
89812
90045
|
};
|
|
89813
90046
|
}
|
|
89814
90047
|
/**
|
|
@@ -89973,7 +90206,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
89973
90206
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89974
90207
|
continue;
|
|
89975
90208
|
}
|
|
89976
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
90209
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$12) {
|
|
89977
90210
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89978
90211
|
continue;
|
|
89979
90212
|
}
|
|
@@ -90029,13 +90262,116 @@ const AUTO_APPROVE_KEYS = {
|
|
|
90029
90262
|
webfetch: "chat.tools.urls.autoApprove"
|
|
90030
90263
|
};
|
|
90031
90264
|
/**
|
|
90032
|
-
* The workspace setting holding the starting permission level of new
|
|
90033
|
-
* sessions, authored through the
|
|
90265
|
+
* The workspace setting holding the starting permission level of new Local
|
|
90266
|
+
* (`local` harness) chat sessions, authored through the
|
|
90267
|
+
* `copilot.defaultPermissionMode` override. VS Code 1.144 narrowed it to Local
|
|
90268
|
+
* sessions; the Copilot harness reads {@link DEFAULT_CONFIGURATION_KEY}.
|
|
90034
90269
|
*
|
|
90035
90270
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
90036
90271
|
*/
|
|
90037
90272
|
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
90038
90273
|
/**
|
|
90274
|
+
* The workspace setting holding the starting `mode` (`interactive` / `plan` /
|
|
90275
|
+
* `autopilot`) and `approvals` (`manual` / `assisted` / `allowAll`) of new
|
|
90276
|
+
* agent sessions such as the Copilot harness, the default since VS Code 1.143.
|
|
90277
|
+
* It shipped in stable VS Code 1.144 as an object setting with
|
|
90278
|
+
* `additionalProperties: false`; older releases ignore it.
|
|
90279
|
+
*
|
|
90280
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
90281
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/common/constants.ts
|
|
90282
|
+
*/
|
|
90283
|
+
const DEFAULT_CONFIGURATION_KEY = "chat.defaultConfiguration";
|
|
90284
|
+
/**
|
|
90285
|
+
* What each `copilot.defaultPermissionMode` value writes. `local` goes to
|
|
90286
|
+
* `chat.permissions.default`; `approvals` and `autopilot` go to
|
|
90287
|
+
* `chat.defaultConfiguration`. The Local picker has no Assisted level, so
|
|
90288
|
+
* `assisted` starts Local sessions with Default Permissions — the stricter
|
|
90289
|
+
* fallback. Autopilot is a permission level in the Local picker but a `mode`
|
|
90290
|
+
* in `chat.defaultConfiguration`, where it runs with every tool call approved.
|
|
90291
|
+
*/
|
|
90292
|
+
const DEFAULT_PERMISSION_MODE_MAPPINGS = {
|
|
90293
|
+
default: {
|
|
90294
|
+
local: "default",
|
|
90295
|
+
approvals: "manual",
|
|
90296
|
+
autopilot: false
|
|
90297
|
+
},
|
|
90298
|
+
autoApprove: {
|
|
90299
|
+
local: "autoApprove",
|
|
90300
|
+
approvals: "allowAll",
|
|
90301
|
+
autopilot: false
|
|
90302
|
+
},
|
|
90303
|
+
autopilot: {
|
|
90304
|
+
local: "autopilot",
|
|
90305
|
+
approvals: "allowAll",
|
|
90306
|
+
autopilot: true
|
|
90307
|
+
},
|
|
90308
|
+
assisted: {
|
|
90309
|
+
local: "default",
|
|
90310
|
+
approvals: "assisted",
|
|
90311
|
+
autopilot: false
|
|
90312
|
+
}
|
|
90313
|
+
};
|
|
90314
|
+
/**
|
|
90315
|
+
* The `chat.defaultConfiguration` value for `mode`. Rulesync owns only whether
|
|
90316
|
+
* the mode is Autopilot: authoring Autopilot sets it, and any other value
|
|
90317
|
+
* removes any Autopilot mode in the file (whether an earlier run or a person
|
|
90318
|
+
* wrote it, since the two cannot be told apart), while an `interactive` or
|
|
90319
|
+
* `plan` mode is kept.
|
|
90320
|
+
*/
|
|
90321
|
+
function buildDefaultConfigurationValue({ defaultPermissionMode, existingSettings }) {
|
|
90322
|
+
const { approvals, autopilot } = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode];
|
|
90323
|
+
const existing = existingSettings[DEFAULT_CONFIGURATION_KEY];
|
|
90324
|
+
const value = { ...isPlainObject(existing) ? existing : {} };
|
|
90325
|
+
if (autopilot) value.mode = "autopilot";
|
|
90326
|
+
else if (value.mode === "autopilot") delete value.mode;
|
|
90327
|
+
value.approvals = approvals;
|
|
90328
|
+
return value;
|
|
90329
|
+
}
|
|
90330
|
+
/**
|
|
90331
|
+
* The `copilot.defaultPermissionMode` values from least to most permissive,
|
|
90332
|
+
* used to settle a file whose two keys disagree.
|
|
90333
|
+
*/
|
|
90334
|
+
const DEFAULT_PERMISSION_MODE_RANK = {
|
|
90335
|
+
default: 0,
|
|
90336
|
+
assisted: 1,
|
|
90337
|
+
autoApprove: 2,
|
|
90338
|
+
autopilot: 3
|
|
90339
|
+
};
|
|
90340
|
+
/**
|
|
90341
|
+
* The `copilot.defaultPermissionMode` a `chat.defaultConfiguration` value
|
|
90342
|
+
* stands for. Autopilot is recognized only together with `allowAll`, the pair
|
|
90343
|
+
* Rulesync writes for it: an Autopilot mode with stricter approvals is read
|
|
90344
|
+
* from `approvals` alone, so that regenerating never escalates it to Allow
|
|
90345
|
+
* All. An object without `approvals` is read as `default`, since VS Code then
|
|
90346
|
+
* starts sessions with Manual permissions.
|
|
90347
|
+
*/
|
|
90348
|
+
function readDefaultConfigurationMode(configuration) {
|
|
90349
|
+
if (!isPlainObject(configuration)) return;
|
|
90350
|
+
switch (configuration.approvals) {
|
|
90351
|
+
case void 0:
|
|
90352
|
+
case "manual": return "default";
|
|
90353
|
+
case "assisted": return "assisted";
|
|
90354
|
+
case "allowAll": return configuration.mode === "autopilot" ? "autopilot" : "autoApprove";
|
|
90355
|
+
default: return;
|
|
90356
|
+
}
|
|
90357
|
+
}
|
|
90358
|
+
/**
|
|
90359
|
+
* Read `copilot.defaultPermissionMode` back from the settings. When the two
|
|
90360
|
+
* keys agree — `chat.permissions.default` holds the Local level Rulesync
|
|
90361
|
+
* writes for the `chat.defaultConfiguration` value — the latter is returned,
|
|
90362
|
+
* since only it can say `assisted`. When they disagree, the less permissive of
|
|
90363
|
+
* the two is returned, so regenerating never raises either harness's starting
|
|
90364
|
+
* permissions. Either key alone is read as it stands.
|
|
90365
|
+
*/
|
|
90366
|
+
function readDefaultPermissionMode(settings) {
|
|
90367
|
+
const fromConfiguration = readDefaultConfigurationMode(settings[DEFAULT_CONFIGURATION_KEY]);
|
|
90368
|
+
const localValue = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
90369
|
+
const fromLocal = COPILOT_LOCAL_PERMISSION_LEVELS.find((level) => level === localValue);
|
|
90370
|
+
if (fromConfiguration === void 0 || fromLocal === void 0) return fromConfiguration ?? fromLocal;
|
|
90371
|
+
if (DEFAULT_PERMISSION_MODE_MAPPINGS[fromConfiguration].local === fromLocal) return fromConfiguration;
|
|
90372
|
+
return DEFAULT_PERMISSION_MODE_RANK[fromLocal] < DEFAULT_PERMISSION_MODE_RANK[fromConfiguration] ? fromLocal : fromConfiguration;
|
|
90373
|
+
}
|
|
90374
|
+
/**
|
|
90039
90375
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
90040
90376
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
90041
90377
|
*/
|
|
@@ -90222,9 +90558,6 @@ function readSandboxFromSettings(settings) {
|
|
|
90222
90558
|
}
|
|
90223
90559
|
return Object.keys(sandbox).length > 0 ? sandbox : void 0;
|
|
90224
90560
|
}
|
|
90225
|
-
function isCopilotDefaultPermissionMode(value) {
|
|
90226
|
-
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90227
|
-
}
|
|
90228
90561
|
function asAutoApproveMap(value) {
|
|
90229
90562
|
if (!isPlainObject(value)) return {};
|
|
90230
90563
|
const result = {};
|
|
@@ -90268,9 +90601,11 @@ function buildAutoApproveValue({ category, rules }) {
|
|
|
90268
90601
|
* `false`. A key whose canonical category is absent entirely
|
|
90269
90602
|
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
90270
90603
|
* hand-written edits or urls map.
|
|
90271
|
-
* The `copilot.defaultPermissionMode` override writes
|
|
90272
|
-
* (
|
|
90273
|
-
*
|
|
90604
|
+
* The `copilot.defaultPermissionMode` override writes both
|
|
90605
|
+
* `chat.permissions.default` (Local sessions) and `chat.defaultConfiguration`
|
|
90606
|
+
* (Copilot-harness sessions, VS Code 1.144+; see
|
|
90607
|
+
* {@link DEFAULT_PERMISSION_MODE_MAPPINGS}); without it both keys are left
|
|
90608
|
+
* untouched. The `copilot.sandbox` override writes the Agent Host sandbox
|
|
90274
90609
|
* settings (`chat.agent.sandbox.*`, see {@link SANDBOX_LEAVES}) per key, and
|
|
90275
90610
|
* warns about every value it writes that loosens the sandbox.
|
|
90276
90611
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
@@ -90321,17 +90656,24 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90321
90656
|
rules
|
|
90322
90657
|
});
|
|
90323
90658
|
}
|
|
90659
|
+
const existingSettings = () => parseSharedConfig({
|
|
90660
|
+
format: "jsonc",
|
|
90661
|
+
fileContent: existingContent,
|
|
90662
|
+
filePath
|
|
90663
|
+
});
|
|
90324
90664
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
90325
|
-
if (defaultPermissionMode !== void 0)
|
|
90665
|
+
if (defaultPermissionMode !== void 0) {
|
|
90666
|
+
patch[DEFAULT_PERMISSION_MODE_KEY$1] = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode].local;
|
|
90667
|
+
patch[DEFAULT_CONFIGURATION_KEY] = buildDefaultConfigurationValue({
|
|
90668
|
+
defaultPermissionMode,
|
|
90669
|
+
existingSettings: existingSettings()
|
|
90670
|
+
});
|
|
90671
|
+
}
|
|
90326
90672
|
const sandbox = config.copilot?.sandbox;
|
|
90327
90673
|
if (sandbox !== void 0) {
|
|
90328
90674
|
Object.assign(patch, buildSandboxPatch({
|
|
90329
90675
|
sandbox,
|
|
90330
|
-
existingSettings:
|
|
90331
|
-
format: "jsonc",
|
|
90332
|
-
fileContent: existingContent,
|
|
90333
|
-
filePath
|
|
90334
|
-
})
|
|
90676
|
+
existingSettings: existingSettings()
|
|
90335
90677
|
}));
|
|
90336
90678
|
warnOnTrustAffectingEntries({
|
|
90337
90679
|
toolLabel: COPILOT_TOOL_LABEL,
|
|
@@ -90378,10 +90720,10 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90378
90720
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
90379
90721
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
90380
90722
|
}
|
|
90381
|
-
const defaultPermissionMode = settings
|
|
90723
|
+
const defaultPermissionMode = readDefaultPermissionMode(settings);
|
|
90382
90724
|
const sandbox = readSandboxFromSettings(settings);
|
|
90383
90725
|
const copilot = {
|
|
90384
|
-
...
|
|
90726
|
+
...defaultPermissionMode !== void 0 && { defaultPermissionMode },
|
|
90385
90727
|
...sandbox !== void 0 && { sandbox }
|
|
90386
90728
|
};
|
|
90387
90729
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
@@ -90579,14 +90921,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
90579
90921
|
}
|
|
90580
90922
|
};
|
|
90581
90923
|
// src/features/permissions/crush-permissions.ts
|
|
90582
|
-
const CATCH_ALL_PATTERN$
|
|
90924
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
90583
90925
|
/**
|
|
90584
90926
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
90585
90927
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
90586
90928
|
* still be written.
|
|
90587
90929
|
*/
|
|
90588
90930
|
function normalizeCatchAllPattern(pattern) {
|
|
90589
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90931
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$11 : pattern;
|
|
90590
90932
|
}
|
|
90591
90933
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
90592
90934
|
bash: "bash",
|
|
@@ -90598,11 +90940,11 @@ const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
|
90598
90940
|
webfetch: "fetch"
|
|
90599
90941
|
};
|
|
90600
90942
|
const CRUSH_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CRUSH_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
90601
|
-
const MCP_CANONICAL_PREFIX$
|
|
90943
|
+
const MCP_CANONICAL_PREFIX$7 = "mcp__";
|
|
90602
90944
|
const MCP_CRUSH_PREFIX = "mcp_";
|
|
90603
90945
|
function toCrushToolName(canonical) {
|
|
90604
90946
|
if (Object.hasOwn(CANONICAL_TO_CRUSH_TOOL_NAMES, canonical)) return CANONICAL_TO_CRUSH_TOOL_NAMES[canonical] ?? canonical;
|
|
90605
|
-
if (canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
90947
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX$7) && canonical.length > 5) return `${MCP_CRUSH_PREFIX}${canonical.slice(5).replaceAll("__", "_")}`;
|
|
90606
90948
|
return canonical;
|
|
90607
90949
|
}
|
|
90608
90950
|
function toCanonicalToolName$5(crushName) {
|
|
@@ -90776,7 +91118,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
90776
91118
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
90777
91119
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
90778
91120
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
90779
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
91121
|
+
if (pattern === CATCH_ALL_PATTERN$11) {
|
|
90780
91122
|
catchAllAction = action;
|
|
90781
91123
|
continue;
|
|
90782
91124
|
}
|
|
@@ -90849,11 +91191,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
90849
91191
|
};
|
|
90850
91192
|
for (const entry of disabled) {
|
|
90851
91193
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
90852
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91194
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] = "deny";
|
|
90853
91195
|
}
|
|
90854
91196
|
for (const entry of allowed) {
|
|
90855
91197
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
90856
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91198
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] ??= "allow";
|
|
90857
91199
|
}
|
|
90858
91200
|
return { permission };
|
|
90859
91201
|
}
|
|
@@ -90899,13 +91241,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
90899
91241
|
WebFetch: "webfetch",
|
|
90900
91242
|
Mcp: "mcp"
|
|
90901
91243
|
};
|
|
90902
|
-
const MCP_CANONICAL_PREFIX$
|
|
91244
|
+
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
90903
91245
|
/**
|
|
90904
91246
|
* Returns true if the canonical category is the per-tool MCP form
|
|
90905
91247
|
* `mcp__<server>__<tool>`.
|
|
90906
91248
|
*/
|
|
90907
91249
|
function isMcpScopedCategory(canonical) {
|
|
90908
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91250
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$6) && canonical.length > 5;
|
|
90909
91251
|
}
|
|
90910
91252
|
function toCursorType(canonical) {
|
|
90911
91253
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -90939,7 +91281,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
90939
91281
|
if (match) {
|
|
90940
91282
|
const server = match[1] ?? "*";
|
|
90941
91283
|
const tool = match[2] ?? "*";
|
|
90942
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
91284
|
+
return `${MCP_CANONICAL_PREFIX$6}${server}__${tool}`;
|
|
90943
91285
|
}
|
|
90944
91286
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
90945
91287
|
}
|
|
@@ -91201,7 +91543,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
91201
91543
|
const { toolName: type, pattern } = parseToolPatternEntry(entry);
|
|
91202
91544
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
91203
91545
|
if (!permission[canonical]) permission[canonical] = {};
|
|
91204
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91546
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$6) ? "*" : pattern;
|
|
91205
91547
|
permission[canonical][canonicalPattern] = action;
|
|
91206
91548
|
}
|
|
91207
91549
|
};
|
|
@@ -92700,7 +93042,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
92700
93042
|
// src/features/permissions/goose-permissions.ts
|
|
92701
93043
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
92702
93044
|
const GOOSE_USER_KEY = "user";
|
|
92703
|
-
const CATCH_ALL_PATTERN$
|
|
93045
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
92704
93046
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
92705
93047
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
92706
93048
|
shell: ["bash"],
|
|
@@ -92849,7 +93191,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
92849
93191
|
const assigned = /* @__PURE__ */ new Map();
|
|
92850
93192
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
92851
93193
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
92852
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$
|
|
93194
|
+
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$10) logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
92853
93195
|
const { action, downgraded } = failSafeCatchAllAction({
|
|
92854
93196
|
rules,
|
|
92855
93197
|
allToolsRules: category === "*" ? void 0 : config.permission["*"]
|
|
@@ -92882,7 +93224,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
92882
93224
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
92883
93225
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
92884
93226
|
permission[category] ??= {};
|
|
92885
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
93227
|
+
permission[category][CATCH_ALL_PATTERN$10] = action;
|
|
92886
93228
|
}
|
|
92887
93229
|
}
|
|
92888
93230
|
return { permission };
|
|
@@ -92892,8 +93234,8 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
92892
93234
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
92893
93235
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
92894
93236
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
92895
|
-
const CATCH_ALL_PATTERN$
|
|
92896
|
-
const MCP_CANONICAL_PREFIX$
|
|
93237
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
93238
|
+
const MCP_CANONICAL_PREFIX$5 = "mcp__";
|
|
92897
93239
|
const CATEGORY_TO_GROK_TOOL = {
|
|
92898
93240
|
bash: "Bash",
|
|
92899
93241
|
read: "Read",
|
|
@@ -92925,6 +93267,25 @@ const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
|
92925
93267
|
};
|
|
92926
93268
|
const GROK_MCP_TOOL_ALIASES = /* @__PURE__ */ new Set(["mcp", GROK_MCP_TOOL.toLowerCase()]);
|
|
92927
93269
|
const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
93270
|
+
const GROK_PATH_PATTERN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
93271
|
+
"read",
|
|
93272
|
+
"edit",
|
|
93273
|
+
"write",
|
|
93274
|
+
"grep",
|
|
93275
|
+
"glob"
|
|
93276
|
+
]);
|
|
93277
|
+
const CLAUDE_ABSOLUTE_PATH_PREFIX = "//";
|
|
93278
|
+
/**
|
|
93279
|
+
* Rewrite a canonical path pattern into the anchor Grok understands. A
|
|
93280
|
+
* Claude-style `//abs` absolute pattern becomes `/abs`; without this the rule
|
|
93281
|
+
* would match nothing on Grok, so a `deny` or `ask` rule would silently give
|
|
93282
|
+
* no protection. Every other pattern is returned unchanged.
|
|
93283
|
+
*/
|
|
93284
|
+
function toGrokPathPattern(category, pattern) {
|
|
93285
|
+
if (!GROK_PATH_PATTERN_CATEGORIES.has(category)) return pattern;
|
|
93286
|
+
if (!pattern.startsWith(CLAUDE_ABSOLUTE_PATH_PREFIX)) return pattern;
|
|
93287
|
+
return `/${pattern.replace(/^\/+/, "")}`;
|
|
93288
|
+
}
|
|
92928
93289
|
/**
|
|
92929
93290
|
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
92930
93291
|
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
@@ -92935,18 +93296,20 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
92935
93296
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
92936
93297
|
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
92937
93298
|
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
92938
|
-
* under it has no Grok spelling and returns `null`.
|
|
93299
|
+
* under it has no Grok spelling and returns `null`. A Claude-style `//abs`
|
|
93300
|
+
* path pattern on a file tool is rewritten to Grok's `/abs` (see
|
|
93301
|
+
* `toGrokPathPattern`).
|
|
92939
93302
|
*/
|
|
92940
93303
|
function buildGrokEntry(category, pattern) {
|
|
92941
|
-
if (category === "*") return pattern === CATCH_ALL_PATTERN$
|
|
92942
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
93304
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$9 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
93305
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$5)) {
|
|
92943
93306
|
const remainder = category.slice(5);
|
|
92944
93307
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
92945
93308
|
}
|
|
92946
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
93309
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
92947
93310
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
92948
93311
|
if (tool === void 0) return null;
|
|
92949
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
93312
|
+
return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
|
|
92950
93313
|
}
|
|
92951
93314
|
/**
|
|
92952
93315
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -92968,21 +93331,21 @@ function parseGrokEntry(entry) {
|
|
|
92968
93331
|
}
|
|
92969
93332
|
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
92970
93333
|
category: "*",
|
|
92971
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93334
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92972
93335
|
};
|
|
92973
|
-
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
93336
|
+
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$5)) return parseClaudeStyleMcpEntry(tool);
|
|
92974
93337
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
92975
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92976
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93338
|
+
category: `${MCP_CANONICAL_PREFIX$5}${inner}`,
|
|
93339
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92977
93340
|
} : {
|
|
92978
93341
|
category: "mcp",
|
|
92979
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93342
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92980
93343
|
};
|
|
92981
93344
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
92982
93345
|
if (category === void 0) return null;
|
|
92983
93346
|
return {
|
|
92984
93347
|
category,
|
|
92985
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
93348
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$9
|
|
92986
93349
|
};
|
|
92987
93350
|
}
|
|
92988
93351
|
/**
|
|
@@ -92999,14 +93362,14 @@ function parseGrokEntry(entry) {
|
|
|
92999
93362
|
function parseClaudeStyleMcpEntry(entry) {
|
|
93000
93363
|
const rest = entry.slice(5);
|
|
93001
93364
|
if (rest.length === 0) return null;
|
|
93002
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
93365
|
+
if (rest === CATCH_ALL_PATTERN$9) return {
|
|
93003
93366
|
category: "mcp",
|
|
93004
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93367
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
93005
93368
|
};
|
|
93006
93369
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
93007
93370
|
return {
|
|
93008
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
93009
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93371
|
+
category: `${MCP_CANONICAL_PREFIX$5}${address}`,
|
|
93372
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
93010
93373
|
};
|
|
93011
93374
|
}
|
|
93012
93375
|
/**
|
|
@@ -93027,20 +93390,20 @@ function parseGrokRule(rule) {
|
|
|
93027
93390
|
if (typeof tool !== "string") return null;
|
|
93028
93391
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
93029
93392
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
93030
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
93393
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$9;
|
|
93031
93394
|
const lowerTool = tool.trim().toLowerCase();
|
|
93032
|
-
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93395
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
93033
93396
|
category: "*",
|
|
93034
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93397
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93035
93398
|
action
|
|
93036
93399
|
} : null;
|
|
93037
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93400
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
93038
93401
|
category: "mcp",
|
|
93039
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93402
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93040
93403
|
action
|
|
93041
93404
|
} : {
|
|
93042
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
93043
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93405
|
+
category: `${MCP_CANONICAL_PREFIX$5}${resolvedPattern}`,
|
|
93406
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93044
93407
|
action
|
|
93045
93408
|
};
|
|
93046
93409
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -93190,7 +93553,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
93190
93553
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
93191
93554
|
}
|
|
93192
93555
|
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
93193
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
93556
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$9]: legacyModeAction(parsed) } } };
|
|
93194
93557
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
93195
93558
|
}
|
|
93196
93559
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
@@ -93375,6 +93738,195 @@ function ensureCategory(permission, category) {
|
|
|
93375
93738
|
function removeEmptyCategories(permission) {
|
|
93376
93739
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
93377
93740
|
}
|
|
93741
|
+
/** The prefix Hermes's MCP trust gate keys its persisted "Always" answers with. */
|
|
93742
|
+
const MCP_ALLOWLIST_PREFIX = "mcp:";
|
|
93743
|
+
const MCP_CANONICAL_PREFIX$4 = "mcp__";
|
|
93744
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
93745
|
+
/**
|
|
93746
|
+
* Whether a `command_allowlist` entry is an MCP trust-gate grant rather than a
|
|
93747
|
+
* shell-command pattern. Since Hermes Agent v0.22.6, answering **Always** to
|
|
93748
|
+
* the approval prompt of a `trust: untrusted` MCP server appends
|
|
93749
|
+
* `mcp:<server>/<tool>` to `command_allowlist` (`_trust_approval_key` in
|
|
93750
|
+
* `tools/mcp_tool_handlers.py`), and the gate looks the key up by exact set
|
|
93751
|
+
* membership, so no `mcp:` entry is ever a command.
|
|
93752
|
+
*/
|
|
93753
|
+
function isMcpAllowlistEntry(entry) {
|
|
93754
|
+
return entry.startsWith(MCP_ALLOWLIST_PREFIX);
|
|
93755
|
+
}
|
|
93756
|
+
/**
|
|
93757
|
+
* The canonical category (`mcp__<server>__<tool>`) a Hermes `mcp:<server>/<tool>`
|
|
93758
|
+
* grant stands for, or `null` when it has none: a malformed entry, a server
|
|
93759
|
+
* name containing `__` (the canonical name would read back as another server),
|
|
93760
|
+
* or a `*` in either name (Hermes compares the key literally, while a canonical
|
|
93761
|
+
* `*` is a wildcard).
|
|
93762
|
+
*/
|
|
93763
|
+
function mcpAllowlistEntryToCategory(entry) {
|
|
93764
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93765
|
+
if (parsed === null) return null;
|
|
93766
|
+
const { server, tool } = parsed;
|
|
93767
|
+
if (server.includes("__") || server.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93768
|
+
if (tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93769
|
+
return `${MCP_CANONICAL_PREFIX$4}${server}__${tool}`;
|
|
93770
|
+
}
|
|
93771
|
+
/** The server and tool an `mcp:<server>/<tool>` entry names, or `null` when it is malformed. */
|
|
93772
|
+
function parseMcpAllowlistEntry(entry) {
|
|
93773
|
+
if (!isMcpAllowlistEntry(entry)) return null;
|
|
93774
|
+
const rest = entry.slice(4);
|
|
93775
|
+
const slash = rest.indexOf("/");
|
|
93776
|
+
const server = slash === -1 ? "" : rest.slice(0, slash);
|
|
93777
|
+
const tool = slash === -1 ? "" : rest.slice(slash + 1);
|
|
93778
|
+
return server.length === 0 || tool.length === 0 ? null : {
|
|
93779
|
+
server,
|
|
93780
|
+
tool
|
|
93781
|
+
};
|
|
93782
|
+
}
|
|
93783
|
+
/**
|
|
93784
|
+
* The server and tool a canonical `mcp__<server>__<tool>` category names, or
|
|
93785
|
+
* `null` when it names no single tool (a whole server, a wildcard, or the bare
|
|
93786
|
+
* `mcp` category). Only such a category can become one Hermes grant.
|
|
93787
|
+
*/
|
|
93788
|
+
function parseMcpToolCategory(category) {
|
|
93789
|
+
if (!category.startsWith(MCP_CANONICAL_PREFIX$4)) return null;
|
|
93790
|
+
const remainder = category.slice(5);
|
|
93791
|
+
const separator = remainder.indexOf("__");
|
|
93792
|
+
if (separator <= 0) return null;
|
|
93793
|
+
const server = remainder.slice(0, separator);
|
|
93794
|
+
const tool = remainder.slice(separator + 2);
|
|
93795
|
+
if (tool.length === 0 || server.includes("/") || server.includes(CATCH_ALL_PATTERN$8) || tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93796
|
+
return {
|
|
93797
|
+
server,
|
|
93798
|
+
tool
|
|
93799
|
+
};
|
|
93800
|
+
}
|
|
93801
|
+
function isMcpCategory$1(category) {
|
|
93802
|
+
return category === "mcp" || category.startsWith(MCP_CANONICAL_PREFIX$4);
|
|
93803
|
+
}
|
|
93804
|
+
function isRestricting(action) {
|
|
93805
|
+
return action === "deny" || action === "ask";
|
|
93806
|
+
}
|
|
93807
|
+
/** The first `deny` or `ask` rule of a category, as `<category>: <pattern> (<action>)`. */
|
|
93808
|
+
function firstRestrictingRule(category, rules, covers = () => true) {
|
|
93809
|
+
const found = Object.entries(rules ?? {}).find(([pattern, action]) => isRestricting(action) && covers(pattern));
|
|
93810
|
+
return found ? `${category}: ${found[0]} (${found[1]})` : null;
|
|
93811
|
+
}
|
|
93812
|
+
/**
|
|
93813
|
+
* Whether an MCP name glob (the remainder of an `mcp__...` category, or a
|
|
93814
|
+
* pattern of the bare `mcp` category) covers `server`'s tool `tool`: it names
|
|
93815
|
+
* the tool (`github__delete_repo`, `github__*`, `*__delete_repo`, `*`) or the
|
|
93816
|
+
* whole server (`github`).
|
|
93817
|
+
*/
|
|
93818
|
+
function mcpNameGlobCovers(glob, server, tool) {
|
|
93819
|
+
return glob === `${server}__${tool}` || glob === server || matchesGlob(glob, `${server}__${tool}`) || matchesGlob(glob, server);
|
|
93820
|
+
}
|
|
93821
|
+
/**
|
|
93822
|
+
* The `deny` or `ask` rule that outranks an allow on `server`'s tool `tool`, or
|
|
93823
|
+
* `null` when none does: any one under the all-tools `*` category (a rule there
|
|
93824
|
+
* applies to every tool, so even a narrower pattern restricts some of the
|
|
93825
|
+
* tool's calls), one under the bare `mcp` category whose pattern covers the
|
|
93826
|
+
* tool, or any one in an `mcp__...` category whose name covers the tool — its
|
|
93827
|
+
* own category included, since a deny or ask scoped to some of its calls still
|
|
93828
|
+
* forbids approving all of them. A Hermes grant approves every call of the
|
|
93829
|
+
* tool and Hermes has no ask tier, so such an allow is withheld, the same way
|
|
93830
|
+
* a `bash` allow a stricter rule covers is.
|
|
93831
|
+
*/
|
|
93832
|
+
function findMcpToolRestriction(permission, server, tool) {
|
|
93833
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93834
|
+
let found = null;
|
|
93835
|
+
if (category === "*") found = firstRestrictingRule(category, rules);
|
|
93836
|
+
else if (category === "mcp") found = firstRestrictingRule(category, rules, (pattern) => mcpNameGlobCovers(pattern, server, tool));
|
|
93837
|
+
else if (category.startsWith(MCP_CANONICAL_PREFIX$4) && mcpNameGlobCovers(category.slice(5), server, tool)) found = firstRestrictingRule(category, rules);
|
|
93838
|
+
if (found !== null) return found;
|
|
93839
|
+
}
|
|
93840
|
+
return null;
|
|
93841
|
+
}
|
|
93842
|
+
function isMcpToolRestricted(permission, server, tool) {
|
|
93843
|
+
return findMcpToolRestriction(permission, server, tool) !== null;
|
|
93844
|
+
}
|
|
93845
|
+
/**
|
|
93846
|
+
* Translate the canonical MCP `allow` rules into Hermes trust-gate grants: a
|
|
93847
|
+
* `*` allow on `mcp__<server>__<tool>` becomes `mcp:<server>/<tool>`. Every
|
|
93848
|
+
* other MCP allow (a whole server, a wildcard, a narrower pattern) has no
|
|
93849
|
+
* Hermes spelling, and an allow a deny or ask covers is withheld.
|
|
93850
|
+
*/
|
|
93851
|
+
function collectMcpAllowlistEntries(permission) {
|
|
93852
|
+
const entries = [];
|
|
93853
|
+
const withheldCategories = [];
|
|
93854
|
+
const unexpressedRules = [];
|
|
93855
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93856
|
+
if (!isMcpCategory$1(category)) continue;
|
|
93857
|
+
const parsed = parseMcpToolCategory(category);
|
|
93858
|
+
for (const pattern of patternsByAction(rules, "allow")) if (parsed === null || pattern !== CATCH_ALL_PATTERN$8) unexpressedRules.push(`${category}: ${pattern}`);
|
|
93859
|
+
else {
|
|
93860
|
+
const restriction = findMcpToolRestriction(permission, parsed.server, parsed.tool);
|
|
93861
|
+
if (restriction === null) entries.push(`${MCP_ALLOWLIST_PREFIX}${parsed.server}/${parsed.tool}`);
|
|
93862
|
+
else withheldCategories.push(`${category} (covered by ${restriction})`);
|
|
93863
|
+
}
|
|
93864
|
+
}
|
|
93865
|
+
return {
|
|
93866
|
+
entries,
|
|
93867
|
+
withheldCategories,
|
|
93868
|
+
unexpressedRules
|
|
93869
|
+
};
|
|
93870
|
+
}
|
|
93871
|
+
function warnAboutUnwrittenMcpAllows({ withheldCategories, unexpressedRules, logger }) {
|
|
93872
|
+
if (withheldCategories.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist was not given the MCP allow rule(s) for ${withheldCategories.join(", ")}: a deny or ask rule covers each, a Hermes grant approves every call of the tool, and Hermes has no ask tier.`);
|
|
93873
|
+
if (unexpressedRules.length > 0) warnWithFallback(logger, `Hermes Agent grants MCP tools one at a time (command_allowlist entries 'mcp:<server>/<tool>'), so only a '*' allow on an 'mcp__<server>__<tool>' category can be written; the MCP allow rule(s) ${unexpressedRules.join(", ")} were skipped and survive only in the permissions.rulesync round-trip block.`);
|
|
93874
|
+
}
|
|
93875
|
+
/**
|
|
93876
|
+
* Make the native `mcp:<server>/<tool>` grants authoritative for the canonical
|
|
93877
|
+
* single-tool MCP allows they can speak for. An allow the generator withheld
|
|
93878
|
+
* because a deny or ask covers it was never written, so its absence
|
|
93879
|
+
* is not a retraction and it is kept, as is every MCP allow with no Hermes
|
|
93880
|
+
* spelling. A grant with no canonical spelling stays only in Hermes, where
|
|
93881
|
+
* generation keeps it (see `setFileContent`).
|
|
93882
|
+
*/
|
|
93883
|
+
function reconcileMcpAllowlist({ permission, mcpEntries, logger }) {
|
|
93884
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93885
|
+
const parsed = parseMcpToolCategory(category);
|
|
93886
|
+
if (parsed !== null && rules[CATCH_ALL_PATTERN$8] === "allow" && !isMcpToolRestricted(permission, parsed.server, parsed.tool)) delete rules[CATCH_ALL_PATTERN$8];
|
|
93887
|
+
}
|
|
93888
|
+
const keptRestrictions = [];
|
|
93889
|
+
for (const entry of mcpEntries) {
|
|
93890
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93891
|
+
if (category === null) continue;
|
|
93892
|
+
const rules = ensureCategory(permission, category);
|
|
93893
|
+
if (isRestricting(rules[CATCH_ALL_PATTERN$8])) {
|
|
93894
|
+
keptRestrictions.push(entry);
|
|
93895
|
+
continue;
|
|
93896
|
+
}
|
|
93897
|
+
rules[CATCH_ALL_PATTERN$8] = "allow";
|
|
93898
|
+
}
|
|
93899
|
+
if (keptRestrictions.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist grants ${keptRestrictions.join(", ")}, but the permissions.rulesync block denies or asks for those MCP tools; the canonical rule was kept and the grant was not imported.`);
|
|
93900
|
+
}
|
|
93901
|
+
/**
|
|
93902
|
+
* The `mcp:` grants in an existing `command_allowlist` that rulesync does not
|
|
93903
|
+
* manage and must keep when it rewrites the list: Hermes appends one whenever
|
|
93904
|
+
* the user answers **Always** on an untrusted MCP server, so dropping it would
|
|
93905
|
+
* silently revoke the grant. A grant is managed — and so retracted with its
|
|
93906
|
+
* canonical rule — when the previous or the new canonical block has a rule
|
|
93907
|
+
* for its `mcp__<server>__<tool>` category, or when the new block restricts
|
|
93908
|
+
* the tool in any way.
|
|
93909
|
+
*/
|
|
93910
|
+
function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermission, nextAllowlist }) {
|
|
93911
|
+
return (isStringArray(existing.command_allowlist) ? existing.command_allowlist : []).filter((entry) => {
|
|
93912
|
+
if (!isMcpAllowlistEntry(entry) || nextAllowlist.includes(entry)) return false;
|
|
93913
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93914
|
+
if (parsed !== null && isMcpToolRestricted(nextPermission, parsed.server, parsed.tool)) return false;
|
|
93915
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93916
|
+
return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
|
|
93917
|
+
});
|
|
93918
|
+
}
|
|
93919
|
+
/** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
|
|
93920
|
+
function canonicalPermissionOf(config) {
|
|
93921
|
+
const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
|
|
93922
|
+
return parsed.success ? parsed.data.permission : {};
|
|
93923
|
+
}
|
|
93924
|
+
/** The canonical `permission` block of a config's `permissions.rulesync` blob, if any. */
|
|
93925
|
+
function provenancePermissionOf(config) {
|
|
93926
|
+
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
93927
|
+
const provenance = isRecord(permissionsRoot.rulesync) ? permissionsRoot.rulesync : {};
|
|
93928
|
+
return isRecord(provenance.permission) ? provenance.permission : {};
|
|
93929
|
+
}
|
|
93378
93930
|
/**
|
|
93379
93931
|
* Make the native `command_allowlist` authoritative for the `bash` allow rules
|
|
93380
93932
|
* it can speak for, and for those only. The allowlist is a list of shell-command
|
|
@@ -93392,8 +93944,9 @@ function reconcileCommandAllowlist({ permission, commandAllowlist }) {
|
|
|
93392
93944
|
writesAllToolsDeny: false
|
|
93393
93945
|
});
|
|
93394
93946
|
const withheld = new Set(shadowedAllowPatterns);
|
|
93947
|
+
const isWithheld = (pattern) => withheld.has(pattern) || isMcpAllowlistEntry(pattern);
|
|
93395
93948
|
const rules = ensureCategory(permission, SHELL_PERMISSION_CATEGORY);
|
|
93396
|
-
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !
|
|
93949
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !isWithheld(pattern)) delete rules[pattern];
|
|
93397
93950
|
for (const pattern of commandAllowlist) rules[pattern] = "allow";
|
|
93398
93951
|
}
|
|
93399
93952
|
/**
|
|
@@ -93481,17 +94034,31 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93481
94034
|
return true;
|
|
93482
94035
|
}
|
|
93483
94036
|
setFileContent(fileContent) {
|
|
94037
|
+
const patch = parseSharedConfig({
|
|
94038
|
+
format: "yaml",
|
|
94039
|
+
fileContent: this.fileContent
|
|
94040
|
+
});
|
|
94041
|
+
if (isStringArray(patch.command_allowlist)) {
|
|
94042
|
+
const existing = parseSharedConfig({
|
|
94043
|
+
format: "yaml",
|
|
94044
|
+
fileContent
|
|
94045
|
+
});
|
|
94046
|
+
const kept = unmanagedMcpAllowlistEntries({
|
|
94047
|
+
existing,
|
|
94048
|
+
previousPermission: provenancePermissionOf(existing),
|
|
94049
|
+
nextPermission: canonicalPermissionOf(patch),
|
|
94050
|
+
nextAllowlist: patch.command_allowlist
|
|
94051
|
+
});
|
|
94052
|
+
if (kept.length > 0) patch.command_allowlist = [...patch.command_allowlist, ...kept];
|
|
94053
|
+
}
|
|
93484
94054
|
this.fileContent = applySharedConfigPatch({
|
|
93485
94055
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
93486
94056
|
feature: "permissions",
|
|
93487
94057
|
existingContent: fileContent,
|
|
93488
|
-
patch
|
|
93489
|
-
format: "yaml",
|
|
93490
|
-
fileContent: this.fileContent
|
|
93491
|
-
})
|
|
94058
|
+
patch
|
|
93492
94059
|
});
|
|
93493
94060
|
}
|
|
93494
|
-
toRulesyncPermissions() {
|
|
94061
|
+
toRulesyncPermissions({ logger } = {}) {
|
|
93495
94062
|
const config = parseSharedConfig({
|
|
93496
94063
|
format: "yaml",
|
|
93497
94064
|
fileContent: this.getFileContent()
|
|
@@ -93503,9 +94070,15 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93503
94070
|
}) : rawProvenance);
|
|
93504
94071
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
93505
94072
|
const permission = clonePermissionBlock(provenance.permission);
|
|
94073
|
+
const commandAllowlist = isStringArray(config.command_allowlist) ? config.command_allowlist : [];
|
|
93506
94074
|
reconcileCommandAllowlist({
|
|
93507
94075
|
permission,
|
|
93508
|
-
commandAllowlist:
|
|
94076
|
+
commandAllowlist: commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry))
|
|
94077
|
+
});
|
|
94078
|
+
reconcileMcpAllowlist({
|
|
94079
|
+
permission,
|
|
94080
|
+
mcpEntries: commandAllowlist.filter(isMcpAllowlistEntry),
|
|
94081
|
+
logger
|
|
93509
94082
|
});
|
|
93510
94083
|
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
93511
94084
|
reconcileNativeDenies({
|
|
@@ -93563,8 +94136,16 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93563
94136
|
logger
|
|
93564
94137
|
});
|
|
93565
94138
|
const webfetchDeny = patternsByAction(permissionBlock[WEBFETCH_PERMISSION_CATEGORY], "deny");
|
|
94139
|
+
const mcpAllows = collectMcpAllowlistEntries(permissionBlock);
|
|
94140
|
+
warnAboutUnwrittenMcpAllows({
|
|
94141
|
+
...mcpAllows,
|
|
94142
|
+
logger
|
|
94143
|
+
});
|
|
94144
|
+
const bashMcpAllows = commandAllowlist.filter(isMcpAllowlistEntry);
|
|
94145
|
+
if (bashMcpAllows.length > 0) warnWithFallback(logger, `Hermes Agent reads 'mcp:' entries in command_allowlist as MCP tool grants, so the bash allow rule(s) ${bashMcpAllows.join(", ")} were skipped; write them as a '*' allow on an 'mcp__<server>__<tool>' category instead.`);
|
|
94146
|
+
const allowlist = [...commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry)), ...mcpAllows.entries];
|
|
93566
94147
|
let config = {};
|
|
93567
|
-
if (
|
|
94148
|
+
if (allowlist.length > 0) config.command_allowlist = allowlist;
|
|
93568
94149
|
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
93569
94150
|
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
93570
94151
|
enabled: true,
|
|
@@ -100405,6 +100986,7 @@ const TAKT_SECURITY_POLICIES = {
|
|
|
100405
100986
|
],
|
|
100406
100987
|
workflow_runtime_prepare: ["custom_scripts"],
|
|
100407
100988
|
workflow_command_gates: ["custom_scripts"],
|
|
100989
|
+
workflow_system_commands: ["custom_scripts"],
|
|
100408
100990
|
sync_conflict_resolver: ["auto_approve_tools"],
|
|
100409
100991
|
allow_git_hooks: null,
|
|
100410
100992
|
allow_git_filters: null
|