rulesync 43.4.1 → 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-B3x-pkWH.cjs → import-CAePeHTT.cjs} +757 -136
- package/dist/{import-BKyt4qe5.js → import-C_XK2L62.js} +758 -137
- package/dist/{import-BKyt4qe5.js.map → import-C_XK2L62.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +12 -17
|
@@ -8378,17 +8378,29 @@ const CodewhalePermissionsOverrideSchema = z.looseObject({
|
|
|
8378
8378
|
rules: z.optional(z.array(CodewhalePermissionRuleSchema))
|
|
8379
8379
|
});
|
|
8380
8380
|
/**
|
|
8381
|
-
* The
|
|
8382
|
-
*
|
|
8381
|
+
* The values VS Code's `chat.permissions.default` setting accepts: the starting
|
|
8382
|
+
* permission level of new Local-harness chat sessions — `default` (Default
|
|
8383
8383
|
* Permissions), `autoApprove` (Bypass Approvals) and `autopilot` (Autopilot).
|
|
8384
8384
|
*
|
|
8385
8385
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
8386
8386
|
*/
|
|
8387
|
-
const
|
|
8387
|
+
const COPILOT_LOCAL_PERMISSION_LEVELS = [
|
|
8388
8388
|
"default",
|
|
8389
8389
|
"autoApprove",
|
|
8390
8390
|
"autopilot"
|
|
8391
8391
|
];
|
|
8392
|
+
/**
|
|
8393
|
+
* The starting permission level the `copilot.defaultPermissionMode` override
|
|
8394
|
+
* sets for new chat sessions: the {@link COPILOT_LOCAL_PERMISSION_LEVELS}, plus
|
|
8395
|
+
* `assisted` (Assisted permissions, GA for Copilot-harness sessions since VS
|
|
8396
|
+
* Code 1.144 and unavailable to Local sessions). Each value is written to both
|
|
8397
|
+
* `chat.permissions.default` (Local sessions) and the `approvals` / `mode` of
|
|
8398
|
+
* `chat.defaultConfiguration` (Copilot-harness sessions, VS Code 1.144+).
|
|
8399
|
+
*
|
|
8400
|
+
* @see https://code.visualstudio.com/updates/v1_144
|
|
8401
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
8402
|
+
*/
|
|
8403
|
+
const COPILOT_DEFAULT_PERMISSION_MODES = [...COPILOT_LOCAL_PERMISSION_LEVELS, "assisted"];
|
|
8392
8404
|
/** `chat.agent.sandbox.enabled`: `"off"` or `"on"`. */
|
|
8393
8405
|
const CopilotSandboxEnabledSchema = z.enum(["off", "on"]);
|
|
8394
8406
|
/** `chat.agent.sandbox.fileSystem.userConfiguredPaths`. */
|
|
@@ -8429,7 +8441,7 @@ const CopilotSandboxSchema = z.strictObject({
|
|
|
8429
8441
|
/**
|
|
8430
8442
|
* Tool-scoped override block for GitHub Copilot in VS Code.
|
|
8431
8443
|
* `defaultPermissionMode` writes the workspace `chat.permissions.default`
|
|
8432
|
-
*
|
|
8444
|
+
* and `chat.defaultConfiguration` settings, and `sandbox` writes the `chat.agent.sandbox.*` settings; neither
|
|
8433
8445
|
* has a canonical category.
|
|
8434
8446
|
*/
|
|
8435
8447
|
const CopilotPermissionsOverrideSchema = z.looseObject({
|
|
@@ -9311,6 +9323,7 @@ const TaktPermissionsOverrideSchema = z.looseObject({
|
|
|
9311
9323
|
})),
|
|
9312
9324
|
workflow_runtime_prepare: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9313
9325
|
workflow_command_gates: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9326
|
+
workflow_system_commands: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9314
9327
|
sync_conflict_resolver: z.optional(z.object({ auto_approve_tools: z.optional(z.boolean()) })),
|
|
9315
9328
|
allow_git_hooks: z.optional(z.boolean()),
|
|
9316
9329
|
allow_git_filters: z.optional(z.boolean())
|
|
@@ -9578,6 +9591,7 @@ const CodexSandboxModeSchema = z.enum([
|
|
|
9578
9591
|
"workspace-write",
|
|
9579
9592
|
"danger-full-access"
|
|
9580
9593
|
]);
|
|
9594
|
+
const CODEX_U32_MAX = 4294967295;
|
|
9581
9595
|
/**
|
|
9582
9596
|
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
9583
9597
|
* value Codex still accepts for backward compatibility, so it is included so
|
|
@@ -9646,6 +9660,9 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
|
|
|
9646
9660
|
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
9647
9661
|
* Defaults to `auto_review` when neither the override nor the existing
|
|
9648
9662
|
* config sets it.
|
|
9663
|
+
* - `auto_review` — the `[auto_review]` table that configures the reviewer
|
|
9664
|
+
* `approvals_reviewer = "auto_review"` selects (`policy`, `extra_policy`,
|
|
9665
|
+
* `timeout_ms`, …). A loose table, shallow-merged like `apps` / `tui`.
|
|
9649
9666
|
* - `git_write_rules` — whether the managed profile's `:workspace_roots` table
|
|
9650
9667
|
* emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
|
|
9651
9668
|
* `:workspace` baseline makes `.git` read-only, which denies basic git
|
|
@@ -9659,7 +9676,8 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
|
|
|
9659
9676
|
* The keys written to `config.toml` are an **allowlist**, not verbatim
|
|
9660
9677
|
* passthrough: only `CODEXCLI_OVERRIDE_KEYS`
|
|
9661
9678
|
* (`src/constants/codexcli-paths.ts` — `approval_policy`, `sandbox_mode`,
|
|
9662
|
-
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`
|
|
9679
|
+
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`, `auto_review`,
|
|
9680
|
+
* `tui`) are emitted, and
|
|
9663
9681
|
* `computeCodexcliOverridePatch` skips anything else with a warning.
|
|
9664
9682
|
* `base_permission_profile` and `git_write_rules` are consumed by the profile
|
|
9665
9683
|
* builder rather than written, as described above, and `permission` is the
|
|
@@ -9691,6 +9709,26 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
|
9691
9709
|
apps: z.optional(z.looseObject({})),
|
|
9692
9710
|
approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})])),
|
|
9693
9711
|
/**
|
|
9712
|
+
* The `[auto_review]` table of `config.toml`, which configures the reviewer
|
|
9713
|
+
* that `approvals_reviewer = "auto_review"` (Rulesync's default) selects:
|
|
9714
|
+
* `policy` / `extra_policy` (text inserted into the reviewer prompt),
|
|
9715
|
+
* `timeout_ms` (the review budget, Codex 0.165.0+; `0` waits without a
|
|
9716
|
+
* deadline), `circuit_break_action` (`default` | `strict`) and
|
|
9717
|
+
* `conversation_history_max_output_tokens` (a positive token cap). Codex
|
|
9718
|
+
* rejects the whole config file when one of these has the wrong type or is
|
|
9719
|
+
* out of range (`timeout_ms` is a `u32`, the token cap a `NonZeroUsize`), so
|
|
9720
|
+
* they are checked here; every other key passes through verbatim.
|
|
9721
|
+
*
|
|
9722
|
+
* @see https://github.com/openai/codex/blob/rust-v0.166.0/codex-rs/config/src/config_toml.rs (`AutoReviewToml`)
|
|
9723
|
+
*/
|
|
9724
|
+
auto_review: z.optional(z.looseObject({
|
|
9725
|
+
policy: z.optional(z.string()),
|
|
9726
|
+
extra_policy: z.optional(z.string()),
|
|
9727
|
+
timeout_ms: z.optional(z.int().check(z.nonnegative(), z.lte(CODEX_U32_MAX))),
|
|
9728
|
+
circuit_break_action: z.optional(z.enum(["default", "strict"])),
|
|
9729
|
+
conversation_history_max_output_tokens: z.optional(z.int().check(z.positive()))
|
|
9730
|
+
})),
|
|
9731
|
+
/**
|
|
9694
9732
|
* The `[tui]` table of `config.toml` (e.g. `vim_mode_default`, `keymap.*`).
|
|
9695
9733
|
* Not a permission surface, but like `apps` it is a top-level table with no
|
|
9696
9734
|
* canonical category, and Codex adds keys to it faster than an explicit model
|
|
@@ -12297,6 +12335,7 @@ const CODEXCLI_OVERRIDE_KEYS = [
|
|
|
12297
12335
|
"sandbox_workspace_write",
|
|
12298
12336
|
"apps",
|
|
12299
12337
|
"approvals_reviewer",
|
|
12338
|
+
"auto_review",
|
|
12300
12339
|
"tui"
|
|
12301
12340
|
];
|
|
12302
12341
|
// src/constants/takt-paths.ts
|
|
@@ -13719,6 +13758,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13719
13758
|
"workflow_arpeggio",
|
|
13720
13759
|
"workflow_runtime_prepare",
|
|
13721
13760
|
"workflow_command_gates",
|
|
13761
|
+
"workflow_system_commands",
|
|
13722
13762
|
"sync_conflict_resolver",
|
|
13723
13763
|
"allow_git_hooks",
|
|
13724
13764
|
"allow_git_filters"
|
|
@@ -13860,6 +13900,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13860
13900
|
"chat.tools.edits.autoApprove",
|
|
13861
13901
|
"chat.tools.urls.autoApprove",
|
|
13862
13902
|
"chat.permissions.default",
|
|
13903
|
+
"chat.defaultConfiguration",
|
|
13863
13904
|
"chat.agent.sandbox.enabled",
|
|
13864
13905
|
"chat.agent.sandbox.network.allowNetwork",
|
|
13865
13906
|
"chat.agent.sandbox.network.allowLocalNetwork",
|
|
@@ -22184,7 +22225,7 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
22184
22225
|
* and `~/.config/mimocode/commands/` (global), so this target reuses
|
|
22185
22226
|
* {@link OpenCodeCommand} with that layout.
|
|
22186
22227
|
*
|
|
22187
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
22228
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/command.ts
|
|
22188
22229
|
*/
|
|
22189
22230
|
var MimocodeCommand = class extends OpenCodeCommand {
|
|
22190
22231
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -30107,7 +30148,7 @@ function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
|
|
|
30107
30148
|
* `~/.config/mimocode/` (global), so this target reuses {@link OpenCodeSkill}
|
|
30108
30149
|
* with that layout.
|
|
30109
30150
|
*
|
|
30110
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
30151
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/skill/index.ts
|
|
30111
30152
|
*/
|
|
30112
30153
|
var MimocodeSkill = class extends OpenCodeSkill {
|
|
30113
30154
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -34101,7 +34142,7 @@ function normalizeAugmentcodeToolList(value) {
|
|
|
34101
34142
|
return tools.length > 0 ? tools : void 0;
|
|
34102
34143
|
}
|
|
34103
34144
|
const AugmentcodeSubagentFrontmatterSchema = z.looseObject({
|
|
34104
|
-
name: z.string(),
|
|
34145
|
+
name: z.optional(z.string()),
|
|
34105
34146
|
description: z.optional(z.string()),
|
|
34106
34147
|
color: z.optional(z.string()),
|
|
34107
34148
|
model: z.optional(z.string()),
|
|
@@ -34160,7 +34201,7 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34160
34201
|
outputRoot: ".",
|
|
34161
34202
|
frontmatter: {
|
|
34162
34203
|
targets: ["*"],
|
|
34163
|
-
name,
|
|
34204
|
+
name: name ?? basename(this.getRelativeFilePath(), extname(this.getRelativeFilePath())),
|
|
34164
34205
|
description,
|
|
34165
34206
|
...Object.keys(augmentcodeSection).length > 0 && { augmentcode: augmentcodeSection }
|
|
34166
34207
|
},
|
|
@@ -34226,7 +34267,8 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34226
34267
|
const dirPath = relativeDirPath ?? this.getSettablePaths({ global }).relativeDirPath;
|
|
34227
34268
|
const filePath = join(outputRoot, dirPath, relativeFilePath);
|
|
34228
34269
|
const fileContent = await readFileContent(filePath);
|
|
34229
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
34270
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
34271
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
34230
34272
|
const result = AugmentcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
34231
34273
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
34232
34274
|
return new AugmentcodeSubagent({
|
|
@@ -38580,7 +38622,7 @@ var OpenCodeSubagent = class extends OpenCodeStyleSubagent {
|
|
|
38580
38622
|
* `~/.config/mimocode/agents/` (global), so this target reuses
|
|
38581
38623
|
* {@link OpenCodeSubagent} with that layout.
|
|
38582
38624
|
*
|
|
38583
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
38625
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/agent.ts
|
|
38584
38626
|
*/
|
|
38585
38627
|
var MimocodeSubagent = class extends OpenCodeSubagent {
|
|
38586
38628
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -41809,7 +41851,23 @@ function sanitizeOpencodeMcpNamePart(value) {
|
|
|
41809
41851
|
* key that matches nothing.
|
|
41810
41852
|
*/
|
|
41811
41853
|
function opencodeMcpToolPermissionKey(serverName, toolName) {
|
|
41812
|
-
return `${
|
|
41854
|
+
return `${opencodeMcpServerKeyPrefix(serverName)}${toolName.replace(/[^a-zA-Z0-9_\-*?]/g, "_")}`;
|
|
41855
|
+
}
|
|
41856
|
+
/** The sanitized `<server>_` prefix every permission key of the server's tools starts with. */
|
|
41857
|
+
function opencodeMcpServerKeyPrefix(serverName) {
|
|
41858
|
+
return `${sanitizeOpencodeMcpNamePart(serverName)}_`;
|
|
41859
|
+
}
|
|
41860
|
+
/**
|
|
41861
|
+
* Whether `key` is exactly the sanitized `<server>_` prefix of one of
|
|
41862
|
+
* `serverNames` (e.g. `a_b_` for server `a.b` or `a_b`). No tool has an empty
|
|
41863
|
+
* name, so such a key filters nothing of that server, and
|
|
41864
|
+
* {@link findOpencodeMcpToolKeyOwner} gives it no owner. Read as an unlisted
|
|
41865
|
+
* server's filter instead, it would be split at the first `_` into a phantom
|
|
41866
|
+
* server (`a` with tool `b_`), so importers leave it out of the rulesync
|
|
41867
|
+
* config, and the OpenCode family's generate keeps it in the file as it was.
|
|
41868
|
+
*/
|
|
41869
|
+
function isBareOpencodeMcpServerPrefix({ key, serverNames }) {
|
|
41870
|
+
return serverNames.some((serverName) => key === opencodeMcpServerKeyPrefix(serverName));
|
|
41813
41871
|
}
|
|
41814
41872
|
/**
|
|
41815
41873
|
* The server among `serverNames` whose tool a permission key names, with the
|
|
@@ -41823,7 +41881,7 @@ function findOpencodeMcpToolKeyOwner({ key, serverNames, builtinKeys, acceptRawP
|
|
|
41823
41881
|
if (builtinKeys.has(key)) return;
|
|
41824
41882
|
let owner;
|
|
41825
41883
|
for (const serverName of serverNames) {
|
|
41826
|
-
const sanitized =
|
|
41884
|
+
const sanitized = opencodeMcpServerKeyPrefix(serverName);
|
|
41827
41885
|
const prefixes = acceptRawPrefix ? uniq([sanitized, `${serverName}_`]) : [sanitized];
|
|
41828
41886
|
for (const prefix of prefixes) if (key.length > prefix.length && key.startsWith(prefix) && (owner === void 0 || prefix.length > owner.prefix.length)) owner = {
|
|
41829
41887
|
serverName,
|
|
@@ -42284,7 +42342,10 @@ function convertFromKiloFormat(kiloMcp, tools, permission) {
|
|
|
42284
42342
|
const serverNames = Object.keys(kiloMcp);
|
|
42285
42343
|
return {
|
|
42286
42344
|
...Object.fromEntries(Object.entries(kiloMcp).map(([serverName, serverConfig]) => [serverName, kiloServerToRulesync(serverName, serverConfig, splitKiloServerTools(serverName, serverNames, filters))])),
|
|
42287
|
-
...orphanMcpToolFiltersToRulesync(kiloMcp, tools)
|
|
42345
|
+
...orphanMcpToolFiltersToRulesync(kiloMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findKiloMcpToolKeyOwner(key, serverNames) && !isBareOpencodeMcpServerPrefix({
|
|
42346
|
+
key,
|
|
42347
|
+
serverNames
|
|
42348
|
+
}))))
|
|
42288
42349
|
};
|
|
42289
42350
|
}
|
|
42290
42351
|
/**
|
|
@@ -42802,16 +42863,20 @@ function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
|
42802
42863
|
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42803
42864
|
key,
|
|
42804
42865
|
serverNames
|
|
42866
|
+
}) && !isBareOpencodeMcpServerPrefix({
|
|
42867
|
+
key,
|
|
42868
|
+
serverNames
|
|
42805
42869
|
})))),
|
|
42806
42870
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42807
42871
|
};
|
|
42808
42872
|
}
|
|
42873
|
+
const OPENCODE_MCP_BUILTIN_TOOL_KEYS = /* @__PURE__ */ new Set();
|
|
42809
42874
|
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42810
42875
|
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42811
42876
|
return findOpencodeMcpToolKeyOwner({
|
|
42812
42877
|
key,
|
|
42813
42878
|
serverNames,
|
|
42814
|
-
builtinKeys:
|
|
42879
|
+
builtinKeys: OPENCODE_MCP_BUILTIN_TOOL_KEYS,
|
|
42815
42880
|
acceptRawPrefix: true
|
|
42816
42881
|
});
|
|
42817
42882
|
}
|
|
@@ -42981,10 +43046,50 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
42981
43046
|
tools
|
|
42982
43047
|
};
|
|
42983
43048
|
}
|
|
43049
|
+
/**
|
|
43050
|
+
* Copy each key of `existingTools` that is a bare sanitized `<server>_` prefix
|
|
43051
|
+
* of one of `serverNames` into `tools`, unless a generated filter already
|
|
43052
|
+
* writes the same key or another listed server owns it (`a_b_` is tool `b_` of
|
|
43053
|
+
* server `a` when `a` is listed too). An owned key is the rulesync config's to
|
|
43054
|
+
* write, so carrying it over would keep a filter the user removed. Mutates
|
|
43055
|
+
* `tools` in place.
|
|
43056
|
+
*/
|
|
43057
|
+
function preserveBareOpencodeMcpServerPrefixKeys({ tools, existingTools, serverNames }) {
|
|
43058
|
+
for (const [key, enabled] of Object.entries(existingTools ?? {})) if (!Object.hasOwn(tools, key) && isBareOpencodeMcpServerPrefix({
|
|
43059
|
+
key,
|
|
43060
|
+
serverNames
|
|
43061
|
+
}) && !findOpencodeToolOwner({
|
|
43062
|
+
key,
|
|
43063
|
+
serverNames
|
|
43064
|
+
})) tools[key] = enabled;
|
|
43065
|
+
}
|
|
43066
|
+
/**
|
|
43067
|
+
* When the existing file does not pass the schema, its `tools` map cannot be
|
|
43068
|
+
* read, so a bare `<server>_` key in it is not carried over. Say so rather
|
|
43069
|
+
* than lift the deny it may hold without a word.
|
|
43070
|
+
*/
|
|
43071
|
+
function warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({ existingJson, serverNames, logger }) {
|
|
43072
|
+
const rawTools = isRecord(existingJson) ? existingJson.tools : void 0;
|
|
43073
|
+
if (!isRecord(rawTools)) return;
|
|
43074
|
+
const bareKeys = Object.keys(rawTools).filter((key) => isBareOpencodeMcpServerPrefix({
|
|
43075
|
+
key,
|
|
43076
|
+
serverNames
|
|
43077
|
+
}) && !findOpencodeToolOwner({
|
|
43078
|
+
key,
|
|
43079
|
+
serverNames
|
|
43080
|
+
}));
|
|
43081
|
+
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".`);
|
|
43082
|
+
}
|
|
42984
43083
|
const OpencodeMcpFeatureOptionsSchema = z.looseObject({ configShape: z.optional(z.enum(["v1", "v2"])) });
|
|
42985
43084
|
var OpencodeMcp = class extends ToolMcp {
|
|
42986
43085
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
42987
43086
|
static layout = OPENCODE_LAYOUT;
|
|
43087
|
+
/**
|
|
43088
|
+
* Per-server keys a fork's MCP schema adds on top of OpenCode's. On generate
|
|
43089
|
+
* each one a canonical server carries is copied verbatim onto that server's
|
|
43090
|
+
* local or remote entry, as `timeout` and `oauth` are. OpenCode adds none.
|
|
43091
|
+
*/
|
|
43092
|
+
static extraPassthroughServerFields = [];
|
|
42988
43093
|
json;
|
|
42989
43094
|
/**
|
|
42990
43095
|
* With a `logger`, the V2 values that lowering the content drops are warned
|
|
@@ -43103,6 +43208,20 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43103
43208
|
if (containers.length > 0) logger?.warn(`OpenCode MCP: the whole "mcp" key is rewritten in the flat V1 shape, so ${containers.map(quoteValueForWarning).join(" and ")} ${containers.length === 1 ? "is" : "are"} not kept.`);
|
|
43104
43209
|
}
|
|
43105
43210
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, configShape, logger);
|
|
43211
|
+
this.copyExtraPassthroughServerFields({
|
|
43212
|
+
convertedMcp,
|
|
43213
|
+
mcpServers: transformedServers
|
|
43214
|
+
});
|
|
43215
|
+
if (existingMcp.success) preserveBareOpencodeMcpServerPrefixKeys({
|
|
43216
|
+
tools: mcpTools,
|
|
43217
|
+
existingTools: existingMcp.data.tools,
|
|
43218
|
+
serverNames: Object.keys(transformedServers)
|
|
43219
|
+
});
|
|
43220
|
+
else warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({
|
|
43221
|
+
existingJson: this.lowerConfig(existingJson),
|
|
43222
|
+
serverNames: Object.keys(transformedServers),
|
|
43223
|
+
logger
|
|
43224
|
+
});
|
|
43106
43225
|
if (this.layout.configVersion === "v2") warnAboutOpencodeV2SseServers({
|
|
43107
43226
|
mcpServers: transformedServers,
|
|
43108
43227
|
convertedMcp,
|
|
@@ -43152,6 +43271,21 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43152
43271
|
});
|
|
43153
43272
|
}
|
|
43154
43273
|
/**
|
|
43274
|
+
* Copies {@link OpencodeMcp.extraPassthroughServerFields} from each canonical
|
|
43275
|
+
* server onto its generated transport entry. A toggle entry is left alone:
|
|
43276
|
+
* it has no transport for these keys to configure.
|
|
43277
|
+
*/
|
|
43278
|
+
static copyExtraPassthroughServerFields({ convertedMcp, mcpServers }) {
|
|
43279
|
+
if (this.extraPassthroughServerFields.length === 0) return;
|
|
43280
|
+
for (const [serverName, entry] of Object.entries(convertedMcp)) {
|
|
43281
|
+
const source = Object.hasOwn(mcpServers, serverName) ? mcpServers[serverName] : void 0;
|
|
43282
|
+
if (source === void 0 || !isOpencodeTransportServer(entry)) continue;
|
|
43283
|
+
const sourceRecord = source;
|
|
43284
|
+
const entryRecord = entry;
|
|
43285
|
+
for (const key of this.extraPassthroughServerFields) if (sourceRecord[key] !== void 0) entryRecord[key] = sourceRecord[key];
|
|
43286
|
+
}
|
|
43287
|
+
}
|
|
43288
|
+
/**
|
|
43155
43289
|
* Builds the V2 `mcp` value: each server raised to the V2 spelling under the
|
|
43156
43290
|
* `mcp.servers` envelope, keeping what the existing file has that Rulesync
|
|
43157
43291
|
* cannot express (see {@link raiseOpencodeServerToV2}), plus the existing
|
|
@@ -43281,16 +43415,30 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43281
43415
|
* `.mimocode/mimocode.jsonc` (project) and `~/.config/mimocode/mimocode.jsonc`
|
|
43282
43416
|
* (global), so this target reuses {@link OpencodeMcp} with that layout.
|
|
43283
43417
|
*
|
|
43284
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
43418
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/mcp.ts
|
|
43285
43419
|
*/
|
|
43286
43420
|
var MimocodeMcp = class extends OpencodeMcp {
|
|
43287
43421
|
static layout = MIMOCODE_LAYOUT;
|
|
43288
43422
|
/**
|
|
43423
|
+
* MiMo Code's local and remote server schemas add two keys OpenCode does not
|
|
43424
|
+
* have:
|
|
43425
|
+
* - `sampling` (`"deny"` | `"ask"` | `"allow"`): the policy for the server's
|
|
43426
|
+
* client-side sampling requests (since v0.1.10).
|
|
43427
|
+
* - `alwaysDirectTools` (string array): tools exposed as top-level tools
|
|
43428
|
+
* instead of through `mcp_tool_search` / `mcp_tool_use` (on `main`, not yet
|
|
43429
|
+
* released). Releases without it ignore the key, since MiMo Code only
|
|
43430
|
+
* rejects unknown keys at the top level of the config.
|
|
43431
|
+
*
|
|
43432
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/mcp.ts
|
|
43433
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/v0.1.16/packages/cli/src/config/config.ts
|
|
43434
|
+
*/
|
|
43435
|
+
static extraPassthroughServerFields = ["sampling", "alwaysDirectTools"];
|
|
43436
|
+
/**
|
|
43289
43437
|
* MiMo Code resolves a relative `instructions` entry against the project
|
|
43290
43438
|
* directory, not the config directory, so a global rule is written as a
|
|
43291
43439
|
* `~/`-rooted path (which MiMo Code expands to the home directory).
|
|
43292
43440
|
*
|
|
43293
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
43441
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/session/instruction.ts
|
|
43294
43442
|
*/
|
|
43295
43443
|
static toGlobalInstructionEntry({ path }) {
|
|
43296
43444
|
return `~/${path}`;
|
|
@@ -48660,7 +48808,7 @@ var OpenCodeRule = class extends ToolRule {
|
|
|
48660
48808
|
* `instructions` list of its config, so this target reuses {@link OpenCodeRule}
|
|
48661
48809
|
* with the `.mimocode/` layout.
|
|
48662
48810
|
*
|
|
48663
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
48811
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/session/instruction.ts
|
|
48664
48812
|
*/
|
|
48665
48813
|
var MimocodeRule = class extends OpenCodeRule {
|
|
48666
48814
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -51628,6 +51776,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
51628
51776
|
factory
|
|
51629
51777
|
});
|
|
51630
51778
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
51779
|
+
this.warnForAugmentcodePluginRules(toolRules);
|
|
51631
51780
|
await this.warnForInstructionBudget({
|
|
51632
51781
|
toolRules,
|
|
51633
51782
|
meta
|
|
@@ -52019,6 +52168,20 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52019
52168
|
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.`);
|
|
52020
52169
|
}
|
|
52021
52170
|
/**
|
|
52171
|
+
* Auggie 1.x (still a prerelease on npm at the time of writing; the stable
|
|
52172
|
+
* line is 0.36.0) no longer loads `rules/` from plugins: "Rules shipped in
|
|
52173
|
+
* plugins (`rules/` in a plugin)" is listed among the removed features, while
|
|
52174
|
+
* commands, agents, skills, hooks and MCP servers still load. The rule files
|
|
52175
|
+
* are still written, because the stable 0.x CLI reads them, but under 1.x the
|
|
52176
|
+
* bundle silently loses them, so say so.
|
|
52177
|
+
* @see https://docs.augmentcode.com/cli/whats-new
|
|
52178
|
+
* @see https://docs.augmentcode.com/cli/v0/plugins
|
|
52179
|
+
*/
|
|
52180
|
+
warnForAugmentcodePluginRules(toolRules) {
|
|
52181
|
+
if (this.toolTarget !== "augmentcode-plugin" || toolRules.length === 0) return;
|
|
52182
|
+
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.`);
|
|
52183
|
+
}
|
|
52184
|
+
/**
|
|
52022
52185
|
* Warp still reads a legacy `WARP.md`, and when both `WARP.md` and
|
|
52023
52186
|
* `AGENTS.md` sit in the same directory, `WARP.md` takes priority — so an
|
|
52024
52187
|
* `AGENTS.md` this run writes next to one would never be read. That holds for
|
|
@@ -63862,7 +64025,7 @@ var OpencodeHooks = class extends ToolHooks {
|
|
|
63862
64025
|
* `~/.config/mimocode/plugins/` (global) and keeps OpenCode's plugin event
|
|
63863
64026
|
* names, so this target reuses {@link OpencodeHooks} with that layout.
|
|
63864
64027
|
*
|
|
63865
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
64028
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/plugin.ts
|
|
63866
64029
|
*/
|
|
63867
64030
|
var MimocodeHooks = class extends OpencodeHooks {
|
|
63868
64031
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -82261,6 +82424,57 @@ function matchesGlobStep(step, character) {
|
|
|
82261
82424
|
const admitted = step.members.has(character) || step.ranges.some(([low, high]) => code >= low && code <= high);
|
|
82262
82425
|
return step.negated ? !admitted : admitted;
|
|
82263
82426
|
}
|
|
82427
|
+
/**
|
|
82428
|
+
* Whether `value` matches `glob` in full, with `*`, `?` and `[...]` carrying
|
|
82429
|
+
* their usual meaning. The glob comes first, unlike Node's own
|
|
82430
|
+
* `path.matchesGlob(path, pattern)`.
|
|
82431
|
+
*
|
|
82432
|
+
* The glob is walked step by step rather than translated to a regex, because
|
|
82433
|
+
* both sides of a comparison can come from a file a repository carries: `*a*a*a*a*b`
|
|
82434
|
+
* translated to `^.*a.*a.*a.*a.*b$` takes minutes against a forty-character
|
|
82435
|
+
* name, while walking the steps is linear in the name per `*`. Remembering only
|
|
82436
|
+
* the most recent `*` to fall back to is enough — an earlier one can always give
|
|
82437
|
+
* up whatever the later one needed.
|
|
82438
|
+
*/
|
|
82439
|
+
function matchesGlob(glob, value) {
|
|
82440
|
+
return compileGlob(glob)(value);
|
|
82441
|
+
}
|
|
82442
|
+
/**
|
|
82443
|
+
* Parse `glob` once and return a predicate that walks it, for a caller that
|
|
82444
|
+
* tests the same glob against a whole list of names.
|
|
82445
|
+
*/
|
|
82446
|
+
function compileGlob(glob) {
|
|
82447
|
+
const steps = parseGlob(glob);
|
|
82448
|
+
return (value) => matchesParsedGlob(steps, value);
|
|
82449
|
+
}
|
|
82450
|
+
function matchesParsedGlob(steps, value) {
|
|
82451
|
+
const characters = [...value];
|
|
82452
|
+
let stepIndex = 0;
|
|
82453
|
+
let characterIndex = 0;
|
|
82454
|
+
let starStepIndex = -1;
|
|
82455
|
+
let starCharacterIndex = 0;
|
|
82456
|
+
while (characterIndex < characters.length) {
|
|
82457
|
+
const step = steps[stepIndex];
|
|
82458
|
+
if (step?.kind === "star") {
|
|
82459
|
+
starStepIndex = stepIndex;
|
|
82460
|
+
starCharacterIndex = characterIndex;
|
|
82461
|
+
stepIndex += 1;
|
|
82462
|
+
continue;
|
|
82463
|
+
}
|
|
82464
|
+
if (step !== void 0 && matchesGlobStep(step, characters[characterIndex] ?? "")) {
|
|
82465
|
+
stepIndex += 1;
|
|
82466
|
+
characterIndex += 1;
|
|
82467
|
+
continue;
|
|
82468
|
+
}
|
|
82469
|
+
if (starStepIndex < 0) return false;
|
|
82470
|
+
starCharacterIndex += 1;
|
|
82471
|
+
stepIndex = starStepIndex + 1;
|
|
82472
|
+
characterIndex = starCharacterIndex;
|
|
82473
|
+
}
|
|
82474
|
+
let remaining = stepIndex;
|
|
82475
|
+
while (steps[remaining]?.kind === "star") remaining += 1;
|
|
82476
|
+
return remaining === steps.length;
|
|
82477
|
+
}
|
|
82264
82478
|
/** Whether two single-character steps can both match one same character. */
|
|
82265
82479
|
function stepsShareACharacter(left, right) {
|
|
82266
82480
|
if (left.kind === "any" || right.kind === "any") return true;
|
|
@@ -83797,9 +84011,13 @@ const CANONICAL_TO_AUGMENT_TOOL_NAMES = {
|
|
|
83797
84011
|
const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENT_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
83798
84012
|
const CURRENT_TO_LEGACY_AUGMENT_TOOL_NAMES = {
|
|
83799
84013
|
terminal: "launch-process",
|
|
84014
|
+
bash: "launch-process",
|
|
84015
|
+
shell: "launch-process",
|
|
83800
84016
|
read: "view",
|
|
83801
84017
|
edit: "str-replace-editor",
|
|
83802
|
-
|
|
84018
|
+
str_replace_editor: "str-replace-editor",
|
|
84019
|
+
write: "save-file",
|
|
84020
|
+
save_file: "save-file"
|
|
83803
84021
|
};
|
|
83804
84022
|
function toAugmentToolName(canonical) {
|
|
83805
84023
|
return lookupOwn({
|
|
@@ -83951,6 +84169,7 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
83951
84169
|
while (i < body.length) {
|
|
83952
84170
|
const ch = body[i];
|
|
83953
84171
|
if (ch === "\\" && i + 1 < body.length) {
|
|
84172
|
+
if (isGlobSpecial(body[i + 1])) return false;
|
|
83954
84173
|
i += 2;
|
|
83955
84174
|
continue;
|
|
83956
84175
|
}
|
|
@@ -83967,6 +84186,37 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
83967
84186
|
}
|
|
83968
84187
|
return true;
|
|
83969
84188
|
}
|
|
84189
|
+
/** Characters a glob reads as syntax (wildcards or the start of a character class). */
|
|
84190
|
+
function isGlobSpecial(ch) {
|
|
84191
|
+
return ch === "*" || ch === "?" || ch === "[";
|
|
84192
|
+
}
|
|
84193
|
+
/**
|
|
84194
|
+
* Whether `shellRegexToGlob` can approximate a `shellInputRegex` without widening it. The lossy
|
|
84195
|
+
* conversion keeps regex quantifiers as characters, and a `*` or `?` left in the glob becomes a
|
|
84196
|
+
* wildcard: `^rm -rf /tmp/a*$` (a run of `a`s) would turn into the glob `rm -rf /tmp/a*`, which
|
|
84197
|
+
* matches `rm -rf /tmp/a /home`. So a regex is unsafe when it has an escaped literal `\*` / `\?` /
|
|
84198
|
+
* `\[` (a `[` would open a character class in targets whose globs support one),
|
|
84199
|
+
* or a bare quantifier `*` / `?` / `+` / `{` that is not part of a `.*` run (which the glob `*`
|
|
84200
|
+
* reproduces faithfully).
|
|
84201
|
+
*/
|
|
84202
|
+
function canApproximateShellRegexWithoutWidening(regex) {
|
|
84203
|
+
let i = 0;
|
|
84204
|
+
while (i < regex.length) {
|
|
84205
|
+
const ch = regex[i];
|
|
84206
|
+
if (ch === "\\" && i + 1 < regex.length) {
|
|
84207
|
+
if (isGlobSpecial(regex[i + 1])) return false;
|
|
84208
|
+
i += 2;
|
|
84209
|
+
continue;
|
|
84210
|
+
}
|
|
84211
|
+
if (ch === "." && regex[i + 1] === "*") {
|
|
84212
|
+
i += 2;
|
|
84213
|
+
continue;
|
|
84214
|
+
}
|
|
84215
|
+
if (ch === "*" || ch === "?" || ch === "+" || ch === "{") return false;
|
|
84216
|
+
i += 1;
|
|
84217
|
+
}
|
|
84218
|
+
return true;
|
|
84219
|
+
}
|
|
83970
84220
|
const MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
|
|
83971
84221
|
var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermissions {
|
|
83972
84222
|
constructor(params) {
|
|
@@ -84403,12 +84653,18 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
84403
84653
|
if (legacyToolName === "launch-process" && entry.shellInputRegex) {
|
|
84404
84654
|
const regex = entry.shellInputRegex;
|
|
84405
84655
|
if (isShellRegexRoundtrippable(regex)) pattern = shellRegexToGlob(regex);
|
|
84406
|
-
else
|
|
84407
|
-
|
|
84408
|
-
|
|
84409
|
-
|
|
84410
|
-
|
|
84411
|
-
|
|
84656
|
+
else {
|
|
84657
|
+
if (action !== "deny" && !canApproximateShellRegexWithoutWidening(regex)) {
|
|
84658
|
+
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.`);
|
|
84659
|
+
continue;
|
|
84660
|
+
}
|
|
84661
|
+
if (action === "deny") {
|
|
84662
|
+
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.`);
|
|
84663
|
+
pattern = "*";
|
|
84664
|
+
} else {
|
|
84665
|
+
pattern = shellRegexToGlob(regex);
|
|
84666
|
+
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.`);
|
|
84667
|
+
}
|
|
84412
84668
|
}
|
|
84413
84669
|
} else pattern = "*";
|
|
84414
84670
|
if (isPrototypePollutionKey(pattern)) {
|
|
@@ -85901,15 +86157,19 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85901
86157
|
* `env` variables a project or local settings file cannot set, because a
|
|
85902
86158
|
* checked-out repository should not control them: where Claude Code stores its
|
|
85903
86159
|
* own files, what session content it exports, where telemetry goes, how it
|
|
85904
|
-
* starts or syncs, the timers on an unanswered
|
|
85905
|
-
*
|
|
86160
|
+
* starts or syncs, which claude.ai sign-in it uses, the timers on an unanswered
|
|
86161
|
+
* dialog, and whether it processes attachments or expands `@` file mentions,
|
|
86162
|
+
* and the screen reader (`CLAUDE_AX_*`) variables. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
85906
86163
|
*
|
|
85907
|
-
* Only the names the settings reference lists are covered; its directory
|
|
85908
|
-
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
86164
|
+
* Only the names the settings reference lists are covered; its directory,
|
|
86165
|
+
* Windows and sign-in groups are introduced with "such as", so an unlisted member of those
|
|
85909
86166
|
* families is still written. Windows variable names are case-insensitive, so
|
|
85910
|
-
* that group is matched without regard to case.
|
|
86167
|
+
* that group is matched without regard to case. `ALLUSERSPROFILE`,
|
|
86168
|
+
* `SystemDrive` and the `CommonProgramFiles` family are ignored there since
|
|
86169
|
+
* Claude Code v2.1.285.
|
|
85911
86170
|
*
|
|
85912
86171
|
* @see https://code.claude.com/docs/en/settings-reference#variables-claude-code-ignores-in-env
|
|
86172
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
|
|
85913
86173
|
*/
|
|
85914
86174
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
85915
86175
|
"BETA_TRACING_ENDPOINT",
|
|
@@ -85919,6 +86179,9 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85919
86179
|
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
85920
86180
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
85921
86181
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
86182
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
86183
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
86184
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
85922
86185
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
85923
86186
|
"CLAUDE_CODE_PLUGIN_SEED_DIR",
|
|
85924
86187
|
"CLAUDE_CODE_PROCESS_WRAPPER",
|
|
@@ -85939,14 +86202,23 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85939
86202
|
"TMPDIR"
|
|
85940
86203
|
]);
|
|
85941
86204
|
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES = /* @__PURE__ */ new Set([
|
|
86205
|
+
"ALLUSERSPROFILE",
|
|
86206
|
+
"COMMONPROGRAMW6432",
|
|
85942
86207
|
"COMSPEC",
|
|
85943
86208
|
"LOCALAPPDATA",
|
|
85944
86209
|
"PATHEXT",
|
|
85945
86210
|
"PROGRAMDATA",
|
|
85946
86211
|
"PROGRAMW6432",
|
|
85947
86212
|
"PSMODULEPATH",
|
|
86213
|
+
"SYSTEMDRIVE",
|
|
85948
86214
|
"SYSTEMROOT"
|
|
85949
86215
|
]);
|
|
86216
|
+
/**
|
|
86217
|
+
* Windows name prefixes matched as whole families: `ProgramFiles*` and
|
|
86218
|
+
* `CommonProgramFiles*`. `ProgramW6432` and `CommonProgramW6432` are exact
|
|
86219
|
+
* names in the set above.
|
|
86220
|
+
*/
|
|
86221
|
+
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES = ["PROGRAMFILES", "COMMONPROGRAMFILES"];
|
|
85950
86222
|
const CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES = [
|
|
85951
86223
|
"_ENDPOINT",
|
|
85952
86224
|
"_HEADERS",
|
|
@@ -85991,7 +86263,7 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
85991
86263
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
85992
86264
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_") || name.startsWith("CLAUDE_AX_")) return true;
|
|
85993
86265
|
const upper = name.toUpperCase();
|
|
85994
|
-
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || upper.startsWith(
|
|
86266
|
+
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES.some((prefix) => upper.startsWith(prefix))) return true;
|
|
85995
86267
|
if (name.startsWith("OTEL_EXPORTER_OTLP_") && CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix))) return true;
|
|
85996
86268
|
if (Object.hasOwn(CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES, name)) {
|
|
85997
86269
|
const offValues = CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES[name];
|
|
@@ -86023,7 +86295,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
86023
86295
|
kept[name] = value;
|
|
86024
86296
|
}
|
|
86025
86297
|
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.`);
|
|
86026
|
-
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.`);
|
|
86298
|
+
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.`);
|
|
86027
86299
|
return kept;
|
|
86028
86300
|
}
|
|
86029
86301
|
/**
|
|
@@ -86673,7 +86945,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
86673
86945
|
"ask",
|
|
86674
86946
|
"deny"
|
|
86675
86947
|
];
|
|
86676
|
-
const CATCH_ALL_PATTERN$
|
|
86948
|
+
const CATCH_ALL_PATTERN$14 = "*";
|
|
86677
86949
|
const MCP_CATEGORY = "mcp";
|
|
86678
86950
|
const MCP_RULE_PREFIX = "mcp__";
|
|
86679
86951
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -86743,7 +87015,7 @@ function categoryOfRule$2(rule) {
|
|
|
86743
87015
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
86744
87016
|
*/
|
|
86745
87017
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
86746
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
87018
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$14;
|
|
86747
87019
|
if (category === MCP_CATEGORY) {
|
|
86748
87020
|
if (catchAll && action === "allow") {
|
|
86749
87021
|
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').`);
|
|
@@ -86752,7 +87024,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
86752
87024
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
86753
87025
|
}
|
|
86754
87026
|
if (category === "*") {
|
|
86755
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
87027
|
+
if (catchAll) return CATCH_ALL_PATTERN$14;
|
|
86756
87028
|
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').`);
|
|
86757
87029
|
return null;
|
|
86758
87030
|
}
|
|
@@ -86770,7 +87042,7 @@ function parseCodebuddyRule(rule) {
|
|
|
86770
87042
|
const { specifier } = splitCodebuddyRule(rule);
|
|
86771
87043
|
if (specifier === void 0) return {
|
|
86772
87044
|
category,
|
|
86773
|
-
pattern: CATCH_ALL_PATTERN$
|
|
87045
|
+
pattern: CATCH_ALL_PATTERN$14
|
|
86774
87046
|
};
|
|
86775
87047
|
const trimmed = specifier.trim();
|
|
86776
87048
|
return trimmed === "" ? null : {
|
|
@@ -89096,8 +89368,8 @@ function mapBashActionToDecision(action) {
|
|
|
89096
89368
|
// src/features/permissions/commandcode-permissions.ts
|
|
89097
89369
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
89098
89370
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
89099
|
-
const CATCH_ALL_PATTERN$
|
|
89100
|
-
const MCP_CANONICAL_PREFIX$
|
|
89371
|
+
const CATCH_ALL_PATTERN$13 = "*";
|
|
89372
|
+
const MCP_CANONICAL_PREFIX$8 = "mcp__";
|
|
89101
89373
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
89102
89374
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
89103
89375
|
bash: "Shell",
|
|
@@ -89142,10 +89414,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
89142
89414
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
89143
89415
|
*/
|
|
89144
89416
|
function buildCommandcodeRule(category, pattern) {
|
|
89145
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
89146
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89147
|
-
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$
|
|
89148
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
89417
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$13 || pattern === "";
|
|
89418
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$8)) return catchAll ? category : `${category}(${pattern})`;
|
|
89419
|
+
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$8}${pattern}`;
|
|
89420
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$13 : null;
|
|
89149
89421
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
89150
89422
|
if (tool === void 0) return null;
|
|
89151
89423
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -89181,7 +89453,7 @@ function splitCommandcodeRule(rule) {
|
|
|
89181
89453
|
}
|
|
89182
89454
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
89183
89455
|
function isCatchAllSpecifier(inner) {
|
|
89184
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
89456
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$13;
|
|
89185
89457
|
}
|
|
89186
89458
|
/**
|
|
89187
89459
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -89215,34 +89487,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
89215
89487
|
if (server.length === 0) return null;
|
|
89216
89488
|
const everyServer = {
|
|
89217
89489
|
category: "mcp",
|
|
89218
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89490
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89219
89491
|
notInAllow: true
|
|
89220
89492
|
};
|
|
89221
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89222
|
-
if (server === CATCH_ALL_PATTERN$
|
|
89223
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
89493
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8)) {
|
|
89494
|
+
if (server === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89495
|
+
if (server.includes(CATCH_ALL_PATTERN$13)) return null;
|
|
89224
89496
|
return {
|
|
89225
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
89226
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89497
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$13 ? `${MCP_CANONICAL_PREFIX$8}${server}` : `${MCP_CANONICAL_PREFIX$8}${remainder}`,
|
|
89498
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89227
89499
|
notInAllow: false
|
|
89228
89500
|
};
|
|
89229
89501
|
}
|
|
89230
|
-
const category = `${MCP_CANONICAL_PREFIX$
|
|
89231
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
89502
|
+
const category = `${MCP_CANONICAL_PREFIX$8}${remainder}`;
|
|
89503
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$13);
|
|
89232
89504
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
89233
89505
|
category,
|
|
89234
89506
|
pattern: inner,
|
|
89235
89507
|
notInAllow: true
|
|
89236
89508
|
} : null;
|
|
89237
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
89238
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
89239
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
89240
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89509
|
+
if (remainder === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89510
|
+
if (rest === CATCH_ALL_PATTERN$13 && !server.includes(CATCH_ALL_PATTERN$13)) return {
|
|
89511
|
+
category: `${MCP_CANONICAL_PREFIX$8}${server}`,
|
|
89512
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89241
89513
|
notInAllow: true
|
|
89242
89514
|
};
|
|
89243
89515
|
return namesTool ? {
|
|
89244
89516
|
category,
|
|
89245
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89517
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89246
89518
|
notInAllow: false
|
|
89247
89519
|
} : null;
|
|
89248
89520
|
}
|
|
@@ -89266,14 +89538,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89266
89538
|
if (category === "bash") {
|
|
89267
89539
|
if (isCatchAllSpecifier(inner)) return {
|
|
89268
89540
|
category,
|
|
89269
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89541
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89270
89542
|
notInAllow: false
|
|
89271
89543
|
};
|
|
89272
89544
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
89273
89545
|
if (normalized === "" || normalized === ":*") return null;
|
|
89274
89546
|
return isCatchAllSpecifier(normalized) ? {
|
|
89275
89547
|
category,
|
|
89276
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89548
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89277
89549
|
notInAllow: true
|
|
89278
89550
|
} : {
|
|
89279
89551
|
category,
|
|
@@ -89283,7 +89555,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89283
89555
|
}
|
|
89284
89556
|
if (isCatchAllSpecifier(inner)) return {
|
|
89285
89557
|
category,
|
|
89286
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89558
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89287
89559
|
notInAllow: false
|
|
89288
89560
|
};
|
|
89289
89561
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -89302,13 +89574,13 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89302
89574
|
*/
|
|
89303
89575
|
function parseCommandcodeRule(rule) {
|
|
89304
89576
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89305
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
89577
|
+
if (tool === CATCH_ALL_PATTERN$13) return isCatchAllSpecifier(inner) ? {
|
|
89306
89578
|
category: "*",
|
|
89307
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89579
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89308
89580
|
notInAllow: true
|
|
89309
89581
|
} : null;
|
|
89310
89582
|
const lowered = tool.toLowerCase();
|
|
89311
|
-
if (lowered.startsWith(MCP_CANONICAL_PREFIX$
|
|
89583
|
+
if (lowered.startsWith(MCP_CANONICAL_PREFIX$8)) return parseMcpCommandcodeRule({
|
|
89312
89584
|
tool,
|
|
89313
89585
|
inner
|
|
89314
89586
|
});
|
|
@@ -89326,12 +89598,12 @@ function parseCommandcodeRule(rule) {
|
|
|
89326
89598
|
*/
|
|
89327
89599
|
function isFoldedMcpToolNameRule({ rule, category }) {
|
|
89328
89600
|
const { tool } = splitCommandcodeRule(rule);
|
|
89329
|
-
return category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89601
|
+
return category.startsWith(MCP_CANONICAL_PREFIX$8) && !tool.startsWith(MCP_CANONICAL_PREFIX$8);
|
|
89330
89602
|
}
|
|
89331
89603
|
/** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
|
|
89332
89604
|
function scopedMcpRuleTool(rule) {
|
|
89333
89605
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89334
|
-
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89606
|
+
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$8) ? tool : null;
|
|
89335
89607
|
}
|
|
89336
89608
|
/**
|
|
89337
89609
|
* The string entries of a Command Code permission list. Command Code reads
|
|
@@ -89374,7 +89646,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
89374
89646
|
}).filter((rule) => {
|
|
89375
89647
|
const parsed = parseCommandcodeRule(rule);
|
|
89376
89648
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
89377
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
89649
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$13})`);
|
|
89378
89650
|
if (key !== "allow" && isStricterAction({
|
|
89379
89651
|
action: key,
|
|
89380
89652
|
existing: replacement
|
|
@@ -89431,7 +89703,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89431
89703
|
const ranked = /* @__PURE__ */ new Map();
|
|
89432
89704
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
89433
89705
|
for (const [category, rules] of Object.entries(permission)) {
|
|
89434
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
89706
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$13);
|
|
89435
89707
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
89436
89708
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
89437
89709
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -89500,7 +89772,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89500
89772
|
function skippedAllowImportMessage({ rule, category }) {
|
|
89501
89773
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89502
89774
|
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.`;
|
|
89503
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89775
|
+
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.`;
|
|
89504
89776
|
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.`;
|
|
89505
89777
|
}
|
|
89506
89778
|
/**
|
|
@@ -89695,7 +89967,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
89695
89967
|
}
|
|
89696
89968
|
// src/features/permissions/continue-permissions.ts
|
|
89697
89969
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
89698
|
-
const CATCH_ALL_PATTERN$
|
|
89970
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
89699
89971
|
/**
|
|
89700
89972
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
89701
89973
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -89703,7 +89975,7 @@ const CATCH_ALL_PATTERN$11 = "*";
|
|
|
89703
89975
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
89704
89976
|
*/
|
|
89705
89977
|
function normalizeCatchAllPattern$1(pattern) {
|
|
89706
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
89978
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$12 : pattern;
|
|
89707
89979
|
}
|
|
89708
89980
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
89709
89981
|
bash: "Bash",
|
|
@@ -89747,7 +90019,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
89747
90019
|
if (toolName === "") return;
|
|
89748
90020
|
return {
|
|
89749
90021
|
toolName,
|
|
89750
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
90022
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$12 : pattern
|
|
89751
90023
|
};
|
|
89752
90024
|
}
|
|
89753
90025
|
/**
|
|
@@ -89912,7 +90184,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
89912
90184
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89913
90185
|
continue;
|
|
89914
90186
|
}
|
|
89915
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
90187
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$12) {
|
|
89916
90188
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89917
90189
|
continue;
|
|
89918
90190
|
}
|
|
@@ -89968,13 +90240,116 @@ const AUTO_APPROVE_KEYS = {
|
|
|
89968
90240
|
webfetch: "chat.tools.urls.autoApprove"
|
|
89969
90241
|
};
|
|
89970
90242
|
/**
|
|
89971
|
-
* The workspace setting holding the starting permission level of new
|
|
89972
|
-
* sessions, authored through the
|
|
90243
|
+
* The workspace setting holding the starting permission level of new Local
|
|
90244
|
+
* (`local` harness) chat sessions, authored through the
|
|
90245
|
+
* `copilot.defaultPermissionMode` override. VS Code 1.144 narrowed it to Local
|
|
90246
|
+
* sessions; the Copilot harness reads {@link DEFAULT_CONFIGURATION_KEY}.
|
|
89973
90247
|
*
|
|
89974
90248
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
89975
90249
|
*/
|
|
89976
90250
|
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
89977
90251
|
/**
|
|
90252
|
+
* The workspace setting holding the starting `mode` (`interactive` / `plan` /
|
|
90253
|
+
* `autopilot`) and `approvals` (`manual` / `assisted` / `allowAll`) of new
|
|
90254
|
+
* agent sessions such as the Copilot harness, the default since VS Code 1.143.
|
|
90255
|
+
* It shipped in stable VS Code 1.144 as an object setting with
|
|
90256
|
+
* `additionalProperties: false`; older releases ignore it.
|
|
90257
|
+
*
|
|
90258
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
90259
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/common/constants.ts
|
|
90260
|
+
*/
|
|
90261
|
+
const DEFAULT_CONFIGURATION_KEY = "chat.defaultConfiguration";
|
|
90262
|
+
/**
|
|
90263
|
+
* What each `copilot.defaultPermissionMode` value writes. `local` goes to
|
|
90264
|
+
* `chat.permissions.default`; `approvals` and `autopilot` go to
|
|
90265
|
+
* `chat.defaultConfiguration`. The Local picker has no Assisted level, so
|
|
90266
|
+
* `assisted` starts Local sessions with Default Permissions — the stricter
|
|
90267
|
+
* fallback. Autopilot is a permission level in the Local picker but a `mode`
|
|
90268
|
+
* in `chat.defaultConfiguration`, where it runs with every tool call approved.
|
|
90269
|
+
*/
|
|
90270
|
+
const DEFAULT_PERMISSION_MODE_MAPPINGS = {
|
|
90271
|
+
default: {
|
|
90272
|
+
local: "default",
|
|
90273
|
+
approvals: "manual",
|
|
90274
|
+
autopilot: false
|
|
90275
|
+
},
|
|
90276
|
+
autoApprove: {
|
|
90277
|
+
local: "autoApprove",
|
|
90278
|
+
approvals: "allowAll",
|
|
90279
|
+
autopilot: false
|
|
90280
|
+
},
|
|
90281
|
+
autopilot: {
|
|
90282
|
+
local: "autopilot",
|
|
90283
|
+
approvals: "allowAll",
|
|
90284
|
+
autopilot: true
|
|
90285
|
+
},
|
|
90286
|
+
assisted: {
|
|
90287
|
+
local: "default",
|
|
90288
|
+
approvals: "assisted",
|
|
90289
|
+
autopilot: false
|
|
90290
|
+
}
|
|
90291
|
+
};
|
|
90292
|
+
/**
|
|
90293
|
+
* The `chat.defaultConfiguration` value for `mode`. Rulesync owns only whether
|
|
90294
|
+
* the mode is Autopilot: authoring Autopilot sets it, and any other value
|
|
90295
|
+
* removes any Autopilot mode in the file (whether an earlier run or a person
|
|
90296
|
+
* wrote it, since the two cannot be told apart), while an `interactive` or
|
|
90297
|
+
* `plan` mode is kept.
|
|
90298
|
+
*/
|
|
90299
|
+
function buildDefaultConfigurationValue({ defaultPermissionMode, existingSettings }) {
|
|
90300
|
+
const { approvals, autopilot } = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode];
|
|
90301
|
+
const existing = existingSettings[DEFAULT_CONFIGURATION_KEY];
|
|
90302
|
+
const value = { ...isPlainObject(existing) ? existing : {} };
|
|
90303
|
+
if (autopilot) value.mode = "autopilot";
|
|
90304
|
+
else if (value.mode === "autopilot") delete value.mode;
|
|
90305
|
+
value.approvals = approvals;
|
|
90306
|
+
return value;
|
|
90307
|
+
}
|
|
90308
|
+
/**
|
|
90309
|
+
* The `copilot.defaultPermissionMode` values from least to most permissive,
|
|
90310
|
+
* used to settle a file whose two keys disagree.
|
|
90311
|
+
*/
|
|
90312
|
+
const DEFAULT_PERMISSION_MODE_RANK = {
|
|
90313
|
+
default: 0,
|
|
90314
|
+
assisted: 1,
|
|
90315
|
+
autoApprove: 2,
|
|
90316
|
+
autopilot: 3
|
|
90317
|
+
};
|
|
90318
|
+
/**
|
|
90319
|
+
* The `copilot.defaultPermissionMode` a `chat.defaultConfiguration` value
|
|
90320
|
+
* stands for. Autopilot is recognized only together with `allowAll`, the pair
|
|
90321
|
+
* Rulesync writes for it: an Autopilot mode with stricter approvals is read
|
|
90322
|
+
* from `approvals` alone, so that regenerating never escalates it to Allow
|
|
90323
|
+
* All. An object without `approvals` is read as `default`, since VS Code then
|
|
90324
|
+
* starts sessions with Manual permissions.
|
|
90325
|
+
*/
|
|
90326
|
+
function readDefaultConfigurationMode(configuration) {
|
|
90327
|
+
if (!isPlainObject(configuration)) return;
|
|
90328
|
+
switch (configuration.approvals) {
|
|
90329
|
+
case void 0:
|
|
90330
|
+
case "manual": return "default";
|
|
90331
|
+
case "assisted": return "assisted";
|
|
90332
|
+
case "allowAll": return configuration.mode === "autopilot" ? "autopilot" : "autoApprove";
|
|
90333
|
+
default: return;
|
|
90334
|
+
}
|
|
90335
|
+
}
|
|
90336
|
+
/**
|
|
90337
|
+
* Read `copilot.defaultPermissionMode` back from the settings. When the two
|
|
90338
|
+
* keys agree — `chat.permissions.default` holds the Local level Rulesync
|
|
90339
|
+
* writes for the `chat.defaultConfiguration` value — the latter is returned,
|
|
90340
|
+
* since only it can say `assisted`. When they disagree, the less permissive of
|
|
90341
|
+
* the two is returned, so regenerating never raises either harness's starting
|
|
90342
|
+
* permissions. Either key alone is read as it stands.
|
|
90343
|
+
*/
|
|
90344
|
+
function readDefaultPermissionMode(settings) {
|
|
90345
|
+
const fromConfiguration = readDefaultConfigurationMode(settings[DEFAULT_CONFIGURATION_KEY]);
|
|
90346
|
+
const localValue = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
90347
|
+
const fromLocal = COPILOT_LOCAL_PERMISSION_LEVELS.find((level) => level === localValue);
|
|
90348
|
+
if (fromConfiguration === void 0 || fromLocal === void 0) return fromConfiguration ?? fromLocal;
|
|
90349
|
+
if (DEFAULT_PERMISSION_MODE_MAPPINGS[fromConfiguration].local === fromLocal) return fromConfiguration;
|
|
90350
|
+
return DEFAULT_PERMISSION_MODE_RANK[fromLocal] < DEFAULT_PERMISSION_MODE_RANK[fromConfiguration] ? fromLocal : fromConfiguration;
|
|
90351
|
+
}
|
|
90352
|
+
/**
|
|
89978
90353
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
89979
90354
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
89980
90355
|
*/
|
|
@@ -90161,9 +90536,6 @@ function readSandboxFromSettings(settings) {
|
|
|
90161
90536
|
}
|
|
90162
90537
|
return Object.keys(sandbox).length > 0 ? sandbox : void 0;
|
|
90163
90538
|
}
|
|
90164
|
-
function isCopilotDefaultPermissionMode(value) {
|
|
90165
|
-
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90166
|
-
}
|
|
90167
90539
|
function asAutoApproveMap(value) {
|
|
90168
90540
|
if (!isPlainObject(value)) return {};
|
|
90169
90541
|
const result = {};
|
|
@@ -90207,9 +90579,11 @@ function buildAutoApproveValue({ category, rules }) {
|
|
|
90207
90579
|
* `false`. A key whose canonical category is absent entirely
|
|
90208
90580
|
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
90209
90581
|
* hand-written edits or urls map.
|
|
90210
|
-
* The `copilot.defaultPermissionMode` override writes
|
|
90211
|
-
* (
|
|
90212
|
-
*
|
|
90582
|
+
* The `copilot.defaultPermissionMode` override writes both
|
|
90583
|
+
* `chat.permissions.default` (Local sessions) and `chat.defaultConfiguration`
|
|
90584
|
+
* (Copilot-harness sessions, VS Code 1.144+; see
|
|
90585
|
+
* {@link DEFAULT_PERMISSION_MODE_MAPPINGS}); without it both keys are left
|
|
90586
|
+
* untouched. The `copilot.sandbox` override writes the Agent Host sandbox
|
|
90213
90587
|
* settings (`chat.agent.sandbox.*`, see {@link SANDBOX_LEAVES}) per key, and
|
|
90214
90588
|
* warns about every value it writes that loosens the sandbox.
|
|
90215
90589
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
@@ -90260,17 +90634,24 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90260
90634
|
rules
|
|
90261
90635
|
});
|
|
90262
90636
|
}
|
|
90637
|
+
const existingSettings = () => parseSharedConfig({
|
|
90638
|
+
format: "jsonc",
|
|
90639
|
+
fileContent: existingContent,
|
|
90640
|
+
filePath
|
|
90641
|
+
});
|
|
90263
90642
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
90264
|
-
if (defaultPermissionMode !== void 0)
|
|
90643
|
+
if (defaultPermissionMode !== void 0) {
|
|
90644
|
+
patch[DEFAULT_PERMISSION_MODE_KEY$1] = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode].local;
|
|
90645
|
+
patch[DEFAULT_CONFIGURATION_KEY] = buildDefaultConfigurationValue({
|
|
90646
|
+
defaultPermissionMode,
|
|
90647
|
+
existingSettings: existingSettings()
|
|
90648
|
+
});
|
|
90649
|
+
}
|
|
90265
90650
|
const sandbox = config.copilot?.sandbox;
|
|
90266
90651
|
if (sandbox !== void 0) {
|
|
90267
90652
|
Object.assign(patch, buildSandboxPatch({
|
|
90268
90653
|
sandbox,
|
|
90269
|
-
existingSettings:
|
|
90270
|
-
format: "jsonc",
|
|
90271
|
-
fileContent: existingContent,
|
|
90272
|
-
filePath
|
|
90273
|
-
})
|
|
90654
|
+
existingSettings: existingSettings()
|
|
90274
90655
|
}));
|
|
90275
90656
|
warnOnTrustAffectingEntries({
|
|
90276
90657
|
toolLabel: COPILOT_TOOL_LABEL,
|
|
@@ -90317,10 +90698,10 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90317
90698
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
90318
90699
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
90319
90700
|
}
|
|
90320
|
-
const defaultPermissionMode = settings
|
|
90701
|
+
const defaultPermissionMode = readDefaultPermissionMode(settings);
|
|
90321
90702
|
const sandbox = readSandboxFromSettings(settings);
|
|
90322
90703
|
const copilot = {
|
|
90323
|
-
...
|
|
90704
|
+
...defaultPermissionMode !== void 0 && { defaultPermissionMode },
|
|
90324
90705
|
...sandbox !== void 0 && { sandbox }
|
|
90325
90706
|
};
|
|
90326
90707
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
@@ -90518,14 +90899,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
90518
90899
|
}
|
|
90519
90900
|
};
|
|
90520
90901
|
// src/features/permissions/crush-permissions.ts
|
|
90521
|
-
const CATCH_ALL_PATTERN$
|
|
90902
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
90522
90903
|
/**
|
|
90523
90904
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
90524
90905
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
90525
90906
|
* still be written.
|
|
90526
90907
|
*/
|
|
90527
90908
|
function normalizeCatchAllPattern(pattern) {
|
|
90528
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90909
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$11 : pattern;
|
|
90529
90910
|
}
|
|
90530
90911
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
90531
90912
|
bash: "bash",
|
|
@@ -90537,11 +90918,11 @@ const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
|
90537
90918
|
webfetch: "fetch"
|
|
90538
90919
|
};
|
|
90539
90920
|
const CRUSH_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CRUSH_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
90540
|
-
const MCP_CANONICAL_PREFIX$
|
|
90921
|
+
const MCP_CANONICAL_PREFIX$7 = "mcp__";
|
|
90541
90922
|
const MCP_CRUSH_PREFIX = "mcp_";
|
|
90542
90923
|
function toCrushToolName(canonical) {
|
|
90543
90924
|
if (Object.hasOwn(CANONICAL_TO_CRUSH_TOOL_NAMES, canonical)) return CANONICAL_TO_CRUSH_TOOL_NAMES[canonical] ?? canonical;
|
|
90544
|
-
if (canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
90925
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX$7) && canonical.length > 5) return `${MCP_CRUSH_PREFIX}${canonical.slice(5).replaceAll("__", "_")}`;
|
|
90545
90926
|
return canonical;
|
|
90546
90927
|
}
|
|
90547
90928
|
function toCanonicalToolName$5(crushName) {
|
|
@@ -90715,7 +91096,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
90715
91096
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
90716
91097
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
90717
91098
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
90718
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
91099
|
+
if (pattern === CATCH_ALL_PATTERN$11) {
|
|
90719
91100
|
catchAllAction = action;
|
|
90720
91101
|
continue;
|
|
90721
91102
|
}
|
|
@@ -90788,11 +91169,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
90788
91169
|
};
|
|
90789
91170
|
for (const entry of disabled) {
|
|
90790
91171
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
90791
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91172
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] = "deny";
|
|
90792
91173
|
}
|
|
90793
91174
|
for (const entry of allowed) {
|
|
90794
91175
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
90795
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91176
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] ??= "allow";
|
|
90796
91177
|
}
|
|
90797
91178
|
return { permission };
|
|
90798
91179
|
}
|
|
@@ -90838,13 +91219,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
90838
91219
|
WebFetch: "webfetch",
|
|
90839
91220
|
Mcp: "mcp"
|
|
90840
91221
|
};
|
|
90841
|
-
const MCP_CANONICAL_PREFIX$
|
|
91222
|
+
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
90842
91223
|
/**
|
|
90843
91224
|
* Returns true if the canonical category is the per-tool MCP form
|
|
90844
91225
|
* `mcp__<server>__<tool>`.
|
|
90845
91226
|
*/
|
|
90846
91227
|
function isMcpScopedCategory(canonical) {
|
|
90847
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91228
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$6) && canonical.length > 5;
|
|
90848
91229
|
}
|
|
90849
91230
|
function toCursorType(canonical) {
|
|
90850
91231
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -90878,7 +91259,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
90878
91259
|
if (match) {
|
|
90879
91260
|
const server = match[1] ?? "*";
|
|
90880
91261
|
const tool = match[2] ?? "*";
|
|
90881
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
91262
|
+
return `${MCP_CANONICAL_PREFIX$6}${server}__${tool}`;
|
|
90882
91263
|
}
|
|
90883
91264
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
90884
91265
|
}
|
|
@@ -91140,7 +91521,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
91140
91521
|
const { toolName: type, pattern } = parseToolPatternEntry(entry);
|
|
91141
91522
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
91142
91523
|
if (!permission[canonical]) permission[canonical] = {};
|
|
91143
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91524
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$6) ? "*" : pattern;
|
|
91144
91525
|
permission[canonical][canonicalPattern] = action;
|
|
91145
91526
|
}
|
|
91146
91527
|
};
|
|
@@ -92639,7 +93020,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
92639
93020
|
// src/features/permissions/goose-permissions.ts
|
|
92640
93021
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
92641
93022
|
const GOOSE_USER_KEY = "user";
|
|
92642
|
-
const CATCH_ALL_PATTERN$
|
|
93023
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
92643
93024
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
92644
93025
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
92645
93026
|
shell: ["bash"],
|
|
@@ -92788,7 +93169,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
92788
93169
|
const assigned = /* @__PURE__ */ new Map();
|
|
92789
93170
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
92790
93171
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
92791
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$
|
|
93172
|
+
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.`);
|
|
92792
93173
|
const { action, downgraded } = failSafeCatchAllAction({
|
|
92793
93174
|
rules,
|
|
92794
93175
|
allToolsRules: category === "*" ? void 0 : config.permission["*"]
|
|
@@ -92821,7 +93202,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
92821
93202
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
92822
93203
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
92823
93204
|
permission[category] ??= {};
|
|
92824
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
93205
|
+
permission[category][CATCH_ALL_PATTERN$10] = action;
|
|
92825
93206
|
}
|
|
92826
93207
|
}
|
|
92827
93208
|
return { permission };
|
|
@@ -92831,8 +93212,8 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
92831
93212
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
92832
93213
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
92833
93214
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
92834
|
-
const CATCH_ALL_PATTERN$
|
|
92835
|
-
const MCP_CANONICAL_PREFIX$
|
|
93215
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
93216
|
+
const MCP_CANONICAL_PREFIX$5 = "mcp__";
|
|
92836
93217
|
const CATEGORY_TO_GROK_TOOL = {
|
|
92837
93218
|
bash: "Bash",
|
|
92838
93219
|
read: "Read",
|
|
@@ -92864,6 +93245,25 @@ const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
|
92864
93245
|
};
|
|
92865
93246
|
const GROK_MCP_TOOL_ALIASES = /* @__PURE__ */ new Set(["mcp", GROK_MCP_TOOL.toLowerCase()]);
|
|
92866
93247
|
const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
93248
|
+
const GROK_PATH_PATTERN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
93249
|
+
"read",
|
|
93250
|
+
"edit",
|
|
93251
|
+
"write",
|
|
93252
|
+
"grep",
|
|
93253
|
+
"glob"
|
|
93254
|
+
]);
|
|
93255
|
+
const CLAUDE_ABSOLUTE_PATH_PREFIX = "//";
|
|
93256
|
+
/**
|
|
93257
|
+
* Rewrite a canonical path pattern into the anchor Grok understands. A
|
|
93258
|
+
* Claude-style `//abs` absolute pattern becomes `/abs`; without this the rule
|
|
93259
|
+
* would match nothing on Grok, so a `deny` or `ask` rule would silently give
|
|
93260
|
+
* no protection. Every other pattern is returned unchanged.
|
|
93261
|
+
*/
|
|
93262
|
+
function toGrokPathPattern(category, pattern) {
|
|
93263
|
+
if (!GROK_PATH_PATTERN_CATEGORIES.has(category)) return pattern;
|
|
93264
|
+
if (!pattern.startsWith(CLAUDE_ABSOLUTE_PATH_PREFIX)) return pattern;
|
|
93265
|
+
return `/${pattern.replace(/^\/+/, "")}`;
|
|
93266
|
+
}
|
|
92867
93267
|
/**
|
|
92868
93268
|
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
92869
93269
|
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
@@ -92874,18 +93274,20 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
92874
93274
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
92875
93275
|
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
92876
93276
|
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
92877
|
-
* under it has no Grok spelling and returns `null`.
|
|
93277
|
+
* under it has no Grok spelling and returns `null`. A Claude-style `//abs`
|
|
93278
|
+
* path pattern on a file tool is rewritten to Grok's `/abs` (see
|
|
93279
|
+
* `toGrokPathPattern`).
|
|
92878
93280
|
*/
|
|
92879
93281
|
function buildGrokEntry(category, pattern) {
|
|
92880
|
-
if (category === "*") return pattern === CATCH_ALL_PATTERN$
|
|
92881
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
93282
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$9 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
93283
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$5)) {
|
|
92882
93284
|
const remainder = category.slice(5);
|
|
92883
93285
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
92884
93286
|
}
|
|
92885
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
93287
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
92886
93288
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
92887
93289
|
if (tool === void 0) return null;
|
|
92888
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
93290
|
+
return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
|
|
92889
93291
|
}
|
|
92890
93292
|
/**
|
|
92891
93293
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -92907,21 +93309,21 @@ function parseGrokEntry(entry) {
|
|
|
92907
93309
|
}
|
|
92908
93310
|
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
92909
93311
|
category: "*",
|
|
92910
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93312
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92911
93313
|
};
|
|
92912
|
-
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
93314
|
+
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$5)) return parseClaudeStyleMcpEntry(tool);
|
|
92913
93315
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
92914
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92915
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93316
|
+
category: `${MCP_CANONICAL_PREFIX$5}${inner}`,
|
|
93317
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92916
93318
|
} : {
|
|
92917
93319
|
category: "mcp",
|
|
92918
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93320
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92919
93321
|
};
|
|
92920
93322
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
92921
93323
|
if (category === void 0) return null;
|
|
92922
93324
|
return {
|
|
92923
93325
|
category,
|
|
92924
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
93326
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$9
|
|
92925
93327
|
};
|
|
92926
93328
|
}
|
|
92927
93329
|
/**
|
|
@@ -92938,14 +93340,14 @@ function parseGrokEntry(entry) {
|
|
|
92938
93340
|
function parseClaudeStyleMcpEntry(entry) {
|
|
92939
93341
|
const rest = entry.slice(5);
|
|
92940
93342
|
if (rest.length === 0) return null;
|
|
92941
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
93343
|
+
if (rest === CATCH_ALL_PATTERN$9) return {
|
|
92942
93344
|
category: "mcp",
|
|
92943
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93345
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92944
93346
|
};
|
|
92945
93347
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
92946
93348
|
return {
|
|
92947
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92948
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93349
|
+
category: `${MCP_CANONICAL_PREFIX$5}${address}`,
|
|
93350
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92949
93351
|
};
|
|
92950
93352
|
}
|
|
92951
93353
|
/**
|
|
@@ -92966,20 +93368,20 @@ function parseGrokRule(rule) {
|
|
|
92966
93368
|
if (typeof tool !== "string") return null;
|
|
92967
93369
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
92968
93370
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
92969
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
93371
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$9;
|
|
92970
93372
|
const lowerTool = tool.trim().toLowerCase();
|
|
92971
|
-
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93373
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
92972
93374
|
category: "*",
|
|
92973
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93375
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
92974
93376
|
action
|
|
92975
93377
|
} : null;
|
|
92976
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93378
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
92977
93379
|
category: "mcp",
|
|
92978
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93380
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
92979
93381
|
action
|
|
92980
93382
|
} : {
|
|
92981
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92982
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93383
|
+
category: `${MCP_CANONICAL_PREFIX$5}${resolvedPattern}`,
|
|
93384
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
92983
93385
|
action
|
|
92984
93386
|
};
|
|
92985
93387
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -93129,7 +93531,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
93129
93531
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
93130
93532
|
}
|
|
93131
93533
|
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
93132
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
93534
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$9]: legacyModeAction(parsed) } } };
|
|
93133
93535
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
93134
93536
|
}
|
|
93135
93537
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
@@ -93314,6 +93716,195 @@ function ensureCategory(permission, category) {
|
|
|
93314
93716
|
function removeEmptyCategories(permission) {
|
|
93315
93717
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
93316
93718
|
}
|
|
93719
|
+
/** The prefix Hermes's MCP trust gate keys its persisted "Always" answers with. */
|
|
93720
|
+
const MCP_ALLOWLIST_PREFIX = "mcp:";
|
|
93721
|
+
const MCP_CANONICAL_PREFIX$4 = "mcp__";
|
|
93722
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
93723
|
+
/**
|
|
93724
|
+
* Whether a `command_allowlist` entry is an MCP trust-gate grant rather than a
|
|
93725
|
+
* shell-command pattern. Since Hermes Agent v0.22.6, answering **Always** to
|
|
93726
|
+
* the approval prompt of a `trust: untrusted` MCP server appends
|
|
93727
|
+
* `mcp:<server>/<tool>` to `command_allowlist` (`_trust_approval_key` in
|
|
93728
|
+
* `tools/mcp_tool_handlers.py`), and the gate looks the key up by exact set
|
|
93729
|
+
* membership, so no `mcp:` entry is ever a command.
|
|
93730
|
+
*/
|
|
93731
|
+
function isMcpAllowlistEntry(entry) {
|
|
93732
|
+
return entry.startsWith(MCP_ALLOWLIST_PREFIX);
|
|
93733
|
+
}
|
|
93734
|
+
/**
|
|
93735
|
+
* The canonical category (`mcp__<server>__<tool>`) a Hermes `mcp:<server>/<tool>`
|
|
93736
|
+
* grant stands for, or `null` when it has none: a malformed entry, a server
|
|
93737
|
+
* name containing `__` (the canonical name would read back as another server),
|
|
93738
|
+
* or a `*` in either name (Hermes compares the key literally, while a canonical
|
|
93739
|
+
* `*` is a wildcard).
|
|
93740
|
+
*/
|
|
93741
|
+
function mcpAllowlistEntryToCategory(entry) {
|
|
93742
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93743
|
+
if (parsed === null) return null;
|
|
93744
|
+
const { server, tool } = parsed;
|
|
93745
|
+
if (server.includes("__") || server.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93746
|
+
if (tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93747
|
+
return `${MCP_CANONICAL_PREFIX$4}${server}__${tool}`;
|
|
93748
|
+
}
|
|
93749
|
+
/** The server and tool an `mcp:<server>/<tool>` entry names, or `null` when it is malformed. */
|
|
93750
|
+
function parseMcpAllowlistEntry(entry) {
|
|
93751
|
+
if (!isMcpAllowlistEntry(entry)) return null;
|
|
93752
|
+
const rest = entry.slice(4);
|
|
93753
|
+
const slash = rest.indexOf("/");
|
|
93754
|
+
const server = slash === -1 ? "" : rest.slice(0, slash);
|
|
93755
|
+
const tool = slash === -1 ? "" : rest.slice(slash + 1);
|
|
93756
|
+
return server.length === 0 || tool.length === 0 ? null : {
|
|
93757
|
+
server,
|
|
93758
|
+
tool
|
|
93759
|
+
};
|
|
93760
|
+
}
|
|
93761
|
+
/**
|
|
93762
|
+
* The server and tool a canonical `mcp__<server>__<tool>` category names, or
|
|
93763
|
+
* `null` when it names no single tool (a whole server, a wildcard, or the bare
|
|
93764
|
+
* `mcp` category). Only such a category can become one Hermes grant.
|
|
93765
|
+
*/
|
|
93766
|
+
function parseMcpToolCategory(category) {
|
|
93767
|
+
if (!category.startsWith(MCP_CANONICAL_PREFIX$4)) return null;
|
|
93768
|
+
const remainder = category.slice(5);
|
|
93769
|
+
const separator = remainder.indexOf("__");
|
|
93770
|
+
if (separator <= 0) return null;
|
|
93771
|
+
const server = remainder.slice(0, separator);
|
|
93772
|
+
const tool = remainder.slice(separator + 2);
|
|
93773
|
+
if (tool.length === 0 || server.includes("/") || server.includes(CATCH_ALL_PATTERN$8) || tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93774
|
+
return {
|
|
93775
|
+
server,
|
|
93776
|
+
tool
|
|
93777
|
+
};
|
|
93778
|
+
}
|
|
93779
|
+
function isMcpCategory$1(category) {
|
|
93780
|
+
return category === "mcp" || category.startsWith(MCP_CANONICAL_PREFIX$4);
|
|
93781
|
+
}
|
|
93782
|
+
function isRestricting(action) {
|
|
93783
|
+
return action === "deny" || action === "ask";
|
|
93784
|
+
}
|
|
93785
|
+
/** The first `deny` or `ask` rule of a category, as `<category>: <pattern> (<action>)`. */
|
|
93786
|
+
function firstRestrictingRule(category, rules, covers = () => true) {
|
|
93787
|
+
const found = Object.entries(rules ?? {}).find(([pattern, action]) => isRestricting(action) && covers(pattern));
|
|
93788
|
+
return found ? `${category}: ${found[0]} (${found[1]})` : null;
|
|
93789
|
+
}
|
|
93790
|
+
/**
|
|
93791
|
+
* Whether an MCP name glob (the remainder of an `mcp__...` category, or a
|
|
93792
|
+
* pattern of the bare `mcp` category) covers `server`'s tool `tool`: it names
|
|
93793
|
+
* the tool (`github__delete_repo`, `github__*`, `*__delete_repo`, `*`) or the
|
|
93794
|
+
* whole server (`github`).
|
|
93795
|
+
*/
|
|
93796
|
+
function mcpNameGlobCovers(glob, server, tool) {
|
|
93797
|
+
return glob === `${server}__${tool}` || glob === server || matchesGlob(glob, `${server}__${tool}`) || matchesGlob(glob, server);
|
|
93798
|
+
}
|
|
93799
|
+
/**
|
|
93800
|
+
* The `deny` or `ask` rule that outranks an allow on `server`'s tool `tool`, or
|
|
93801
|
+
* `null` when none does: any one under the all-tools `*` category (a rule there
|
|
93802
|
+
* applies to every tool, so even a narrower pattern restricts some of the
|
|
93803
|
+
* tool's calls), one under the bare `mcp` category whose pattern covers the
|
|
93804
|
+
* tool, or any one in an `mcp__...` category whose name covers the tool — its
|
|
93805
|
+
* own category included, since a deny or ask scoped to some of its calls still
|
|
93806
|
+
* forbids approving all of them. A Hermes grant approves every call of the
|
|
93807
|
+
* tool and Hermes has no ask tier, so such an allow is withheld, the same way
|
|
93808
|
+
* a `bash` allow a stricter rule covers is.
|
|
93809
|
+
*/
|
|
93810
|
+
function findMcpToolRestriction(permission, server, tool) {
|
|
93811
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93812
|
+
let found = null;
|
|
93813
|
+
if (category === "*") found = firstRestrictingRule(category, rules);
|
|
93814
|
+
else if (category === "mcp") found = firstRestrictingRule(category, rules, (pattern) => mcpNameGlobCovers(pattern, server, tool));
|
|
93815
|
+
else if (category.startsWith(MCP_CANONICAL_PREFIX$4) && mcpNameGlobCovers(category.slice(5), server, tool)) found = firstRestrictingRule(category, rules);
|
|
93816
|
+
if (found !== null) return found;
|
|
93817
|
+
}
|
|
93818
|
+
return null;
|
|
93819
|
+
}
|
|
93820
|
+
function isMcpToolRestricted(permission, server, tool) {
|
|
93821
|
+
return findMcpToolRestriction(permission, server, tool) !== null;
|
|
93822
|
+
}
|
|
93823
|
+
/**
|
|
93824
|
+
* Translate the canonical MCP `allow` rules into Hermes trust-gate grants: a
|
|
93825
|
+
* `*` allow on `mcp__<server>__<tool>` becomes `mcp:<server>/<tool>`. Every
|
|
93826
|
+
* other MCP allow (a whole server, a wildcard, a narrower pattern) has no
|
|
93827
|
+
* Hermes spelling, and an allow a deny or ask covers is withheld.
|
|
93828
|
+
*/
|
|
93829
|
+
function collectMcpAllowlistEntries(permission) {
|
|
93830
|
+
const entries = [];
|
|
93831
|
+
const withheldCategories = [];
|
|
93832
|
+
const unexpressedRules = [];
|
|
93833
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93834
|
+
if (!isMcpCategory$1(category)) continue;
|
|
93835
|
+
const parsed = parseMcpToolCategory(category);
|
|
93836
|
+
for (const pattern of patternsByAction(rules, "allow")) if (parsed === null || pattern !== CATCH_ALL_PATTERN$8) unexpressedRules.push(`${category}: ${pattern}`);
|
|
93837
|
+
else {
|
|
93838
|
+
const restriction = findMcpToolRestriction(permission, parsed.server, parsed.tool);
|
|
93839
|
+
if (restriction === null) entries.push(`${MCP_ALLOWLIST_PREFIX}${parsed.server}/${parsed.tool}`);
|
|
93840
|
+
else withheldCategories.push(`${category} (covered by ${restriction})`);
|
|
93841
|
+
}
|
|
93842
|
+
}
|
|
93843
|
+
return {
|
|
93844
|
+
entries,
|
|
93845
|
+
withheldCategories,
|
|
93846
|
+
unexpressedRules
|
|
93847
|
+
};
|
|
93848
|
+
}
|
|
93849
|
+
function warnAboutUnwrittenMcpAllows({ withheldCategories, unexpressedRules, logger }) {
|
|
93850
|
+
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.`);
|
|
93851
|
+
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.`);
|
|
93852
|
+
}
|
|
93853
|
+
/**
|
|
93854
|
+
* Make the native `mcp:<server>/<tool>` grants authoritative for the canonical
|
|
93855
|
+
* single-tool MCP allows they can speak for. An allow the generator withheld
|
|
93856
|
+
* because a deny or ask covers it was never written, so its absence
|
|
93857
|
+
* is not a retraction and it is kept, as is every MCP allow with no Hermes
|
|
93858
|
+
* spelling. A grant with no canonical spelling stays only in Hermes, where
|
|
93859
|
+
* generation keeps it (see `setFileContent`).
|
|
93860
|
+
*/
|
|
93861
|
+
function reconcileMcpAllowlist({ permission, mcpEntries, logger }) {
|
|
93862
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93863
|
+
const parsed = parseMcpToolCategory(category);
|
|
93864
|
+
if (parsed !== null && rules[CATCH_ALL_PATTERN$8] === "allow" && !isMcpToolRestricted(permission, parsed.server, parsed.tool)) delete rules[CATCH_ALL_PATTERN$8];
|
|
93865
|
+
}
|
|
93866
|
+
const keptRestrictions = [];
|
|
93867
|
+
for (const entry of mcpEntries) {
|
|
93868
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93869
|
+
if (category === null) continue;
|
|
93870
|
+
const rules = ensureCategory(permission, category);
|
|
93871
|
+
if (isRestricting(rules[CATCH_ALL_PATTERN$8])) {
|
|
93872
|
+
keptRestrictions.push(entry);
|
|
93873
|
+
continue;
|
|
93874
|
+
}
|
|
93875
|
+
rules[CATCH_ALL_PATTERN$8] = "allow";
|
|
93876
|
+
}
|
|
93877
|
+
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.`);
|
|
93878
|
+
}
|
|
93879
|
+
/**
|
|
93880
|
+
* The `mcp:` grants in an existing `command_allowlist` that rulesync does not
|
|
93881
|
+
* manage and must keep when it rewrites the list: Hermes appends one whenever
|
|
93882
|
+
* the user answers **Always** on an untrusted MCP server, so dropping it would
|
|
93883
|
+
* silently revoke the grant. A grant is managed — and so retracted with its
|
|
93884
|
+
* canonical rule — when the previous or the new canonical block has a rule
|
|
93885
|
+
* for its `mcp__<server>__<tool>` category, or when the new block restricts
|
|
93886
|
+
* the tool in any way.
|
|
93887
|
+
*/
|
|
93888
|
+
function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermission, nextAllowlist }) {
|
|
93889
|
+
return (isStringArray(existing.command_allowlist) ? existing.command_allowlist : []).filter((entry) => {
|
|
93890
|
+
if (!isMcpAllowlistEntry(entry) || nextAllowlist.includes(entry)) return false;
|
|
93891
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93892
|
+
if (parsed !== null && isMcpToolRestricted(nextPermission, parsed.server, parsed.tool)) return false;
|
|
93893
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93894
|
+
return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
|
|
93895
|
+
});
|
|
93896
|
+
}
|
|
93897
|
+
/** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
|
|
93898
|
+
function canonicalPermissionOf(config) {
|
|
93899
|
+
const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
|
|
93900
|
+
return parsed.success ? parsed.data.permission : {};
|
|
93901
|
+
}
|
|
93902
|
+
/** The canonical `permission` block of a config's `permissions.rulesync` blob, if any. */
|
|
93903
|
+
function provenancePermissionOf(config) {
|
|
93904
|
+
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
93905
|
+
const provenance = isRecord(permissionsRoot.rulesync) ? permissionsRoot.rulesync : {};
|
|
93906
|
+
return isRecord(provenance.permission) ? provenance.permission : {};
|
|
93907
|
+
}
|
|
93317
93908
|
/**
|
|
93318
93909
|
* Make the native `command_allowlist` authoritative for the `bash` allow rules
|
|
93319
93910
|
* it can speak for, and for those only. The allowlist is a list of shell-command
|
|
@@ -93331,8 +93922,9 @@ function reconcileCommandAllowlist({ permission, commandAllowlist }) {
|
|
|
93331
93922
|
writesAllToolsDeny: false
|
|
93332
93923
|
});
|
|
93333
93924
|
const withheld = new Set(shadowedAllowPatterns);
|
|
93925
|
+
const isWithheld = (pattern) => withheld.has(pattern) || isMcpAllowlistEntry(pattern);
|
|
93334
93926
|
const rules = ensureCategory(permission, SHELL_PERMISSION_CATEGORY);
|
|
93335
|
-
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !
|
|
93927
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !isWithheld(pattern)) delete rules[pattern];
|
|
93336
93928
|
for (const pattern of commandAllowlist) rules[pattern] = "allow";
|
|
93337
93929
|
}
|
|
93338
93930
|
/**
|
|
@@ -93420,17 +94012,31 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93420
94012
|
return true;
|
|
93421
94013
|
}
|
|
93422
94014
|
setFileContent(fileContent) {
|
|
94015
|
+
const patch = parseSharedConfig({
|
|
94016
|
+
format: "yaml",
|
|
94017
|
+
fileContent: this.fileContent
|
|
94018
|
+
});
|
|
94019
|
+
if (isStringArray(patch.command_allowlist)) {
|
|
94020
|
+
const existing = parseSharedConfig({
|
|
94021
|
+
format: "yaml",
|
|
94022
|
+
fileContent
|
|
94023
|
+
});
|
|
94024
|
+
const kept = unmanagedMcpAllowlistEntries({
|
|
94025
|
+
existing,
|
|
94026
|
+
previousPermission: provenancePermissionOf(existing),
|
|
94027
|
+
nextPermission: canonicalPermissionOf(patch),
|
|
94028
|
+
nextAllowlist: patch.command_allowlist
|
|
94029
|
+
});
|
|
94030
|
+
if (kept.length > 0) patch.command_allowlist = [...patch.command_allowlist, ...kept];
|
|
94031
|
+
}
|
|
93423
94032
|
this.fileContent = applySharedConfigPatch({
|
|
93424
94033
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
93425
94034
|
feature: "permissions",
|
|
93426
94035
|
existingContent: fileContent,
|
|
93427
|
-
patch
|
|
93428
|
-
format: "yaml",
|
|
93429
|
-
fileContent: this.fileContent
|
|
93430
|
-
})
|
|
94036
|
+
patch
|
|
93431
94037
|
});
|
|
93432
94038
|
}
|
|
93433
|
-
toRulesyncPermissions() {
|
|
94039
|
+
toRulesyncPermissions({ logger } = {}) {
|
|
93434
94040
|
const config = parseSharedConfig({
|
|
93435
94041
|
format: "yaml",
|
|
93436
94042
|
fileContent: this.getFileContent()
|
|
@@ -93442,9 +94048,15 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93442
94048
|
}) : rawProvenance);
|
|
93443
94049
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
93444
94050
|
const permission = clonePermissionBlock(provenance.permission);
|
|
94051
|
+
const commandAllowlist = isStringArray(config.command_allowlist) ? config.command_allowlist : [];
|
|
93445
94052
|
reconcileCommandAllowlist({
|
|
93446
94053
|
permission,
|
|
93447
|
-
commandAllowlist:
|
|
94054
|
+
commandAllowlist: commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry))
|
|
94055
|
+
});
|
|
94056
|
+
reconcileMcpAllowlist({
|
|
94057
|
+
permission,
|
|
94058
|
+
mcpEntries: commandAllowlist.filter(isMcpAllowlistEntry),
|
|
94059
|
+
logger
|
|
93448
94060
|
});
|
|
93449
94061
|
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
93450
94062
|
reconcileNativeDenies({
|
|
@@ -93502,8 +94114,16 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93502
94114
|
logger
|
|
93503
94115
|
});
|
|
93504
94116
|
const webfetchDeny = patternsByAction(permissionBlock[WEBFETCH_PERMISSION_CATEGORY], "deny");
|
|
94117
|
+
const mcpAllows = collectMcpAllowlistEntries(permissionBlock);
|
|
94118
|
+
warnAboutUnwrittenMcpAllows({
|
|
94119
|
+
...mcpAllows,
|
|
94120
|
+
logger
|
|
94121
|
+
});
|
|
94122
|
+
const bashMcpAllows = commandAllowlist.filter(isMcpAllowlistEntry);
|
|
94123
|
+
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.`);
|
|
94124
|
+
const allowlist = [...commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry)), ...mcpAllows.entries];
|
|
93505
94125
|
let config = {};
|
|
93506
|
-
if (
|
|
94126
|
+
if (allowlist.length > 0) config.command_allowlist = allowlist;
|
|
93507
94127
|
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
93508
94128
|
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
93509
94129
|
enabled: true,
|
|
@@ -95988,7 +96608,7 @@ var OpencodePermissions = class extends ToolPermissions {
|
|
|
95988
96608
|
* `.mimocode/` layout. Tool-only categories round-trip through the `mimocode`
|
|
95989
96609
|
* override block.
|
|
95990
96610
|
*
|
|
95991
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
96611
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/permission.ts
|
|
95992
96612
|
*/
|
|
95993
96613
|
var MimocodePermissions = class extends OpencodePermissions {
|
|
95994
96614
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -100344,6 +100964,7 @@ const TAKT_SECURITY_POLICIES = {
|
|
|
100344
100964
|
],
|
|
100345
100965
|
workflow_runtime_prepare: ["custom_scripts"],
|
|
100346
100966
|
workflow_command_gates: ["custom_scripts"],
|
|
100967
|
+
workflow_system_commands: ["custom_scripts"],
|
|
100347
100968
|
sync_conflict_resolver: ["auto_approve_tools"],
|
|
100348
100969
|
allow_git_hooks: null,
|
|
100349
100970
|
allow_git_filters: null
|
|
@@ -107313,4 +107934,4 @@ async function importChecksCore(params) {
|
|
|
107313
107934
|
}
|
|
107314
107935
|
export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
|
|
107315
107936
|
|
|
107316
|
-
//# sourceMappingURL=import-
|
|
107937
|
+
//# sourceMappingURL=import-C_XK2L62.js.map
|