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
|
@@ -8400,17 +8400,29 @@ const CodewhalePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8400
8400
|
rules: zod_mini.z.optional(zod_mini.z.array(CodewhalePermissionRuleSchema))
|
|
8401
8401
|
});
|
|
8402
8402
|
/**
|
|
8403
|
-
* The
|
|
8404
|
-
*
|
|
8403
|
+
* The values VS Code's `chat.permissions.default` setting accepts: the starting
|
|
8404
|
+
* permission level of new Local-harness chat sessions — `default` (Default
|
|
8405
8405
|
* Permissions), `autoApprove` (Bypass Approvals) and `autopilot` (Autopilot).
|
|
8406
8406
|
*
|
|
8407
8407
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
8408
8408
|
*/
|
|
8409
|
-
const
|
|
8409
|
+
const COPILOT_LOCAL_PERMISSION_LEVELS = [
|
|
8410
8410
|
"default",
|
|
8411
8411
|
"autoApprove",
|
|
8412
8412
|
"autopilot"
|
|
8413
8413
|
];
|
|
8414
|
+
/**
|
|
8415
|
+
* The starting permission level the `copilot.defaultPermissionMode` override
|
|
8416
|
+
* sets for new chat sessions: the {@link COPILOT_LOCAL_PERMISSION_LEVELS}, plus
|
|
8417
|
+
* `assisted` (Assisted permissions, GA for Copilot-harness sessions since VS
|
|
8418
|
+
* Code 1.144 and unavailable to Local sessions). Each value is written to both
|
|
8419
|
+
* `chat.permissions.default` (Local sessions) and the `approvals` / `mode` of
|
|
8420
|
+
* `chat.defaultConfiguration` (Copilot-harness sessions, VS Code 1.144+).
|
|
8421
|
+
*
|
|
8422
|
+
* @see https://code.visualstudio.com/updates/v1_144
|
|
8423
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
8424
|
+
*/
|
|
8425
|
+
const COPILOT_DEFAULT_PERMISSION_MODES = [...COPILOT_LOCAL_PERMISSION_LEVELS, "assisted"];
|
|
8414
8426
|
/** `chat.agent.sandbox.enabled`: `"off"` or `"on"`. */
|
|
8415
8427
|
const CopilotSandboxEnabledSchema = zod_mini.z.enum(["off", "on"]);
|
|
8416
8428
|
/** `chat.agent.sandbox.fileSystem.userConfiguredPaths`. */
|
|
@@ -8451,7 +8463,7 @@ const CopilotSandboxSchema = zod_mini.z.strictObject({
|
|
|
8451
8463
|
/**
|
|
8452
8464
|
* Tool-scoped override block for GitHub Copilot in VS Code.
|
|
8453
8465
|
* `defaultPermissionMode` writes the workspace `chat.permissions.default`
|
|
8454
|
-
*
|
|
8466
|
+
* and `chat.defaultConfiguration` settings, and `sandbox` writes the `chat.agent.sandbox.*` settings; neither
|
|
8455
8467
|
* has a canonical category.
|
|
8456
8468
|
*/
|
|
8457
8469
|
const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
@@ -9333,6 +9345,7 @@ const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
9333
9345
|
})),
|
|
9334
9346
|
workflow_runtime_prepare: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9335
9347
|
workflow_command_gates: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9348
|
+
workflow_system_commands: zod_mini.z.optional(zod_mini.z.object({ custom_scripts: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9336
9349
|
sync_conflict_resolver: zod_mini.z.optional(zod_mini.z.object({ auto_approve_tools: zod_mini.z.optional(zod_mini.z.boolean()) })),
|
|
9337
9350
|
allow_git_hooks: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9338
9351
|
allow_git_filters: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -9600,6 +9613,7 @@ const CodexSandboxModeSchema = zod_mini.z.enum([
|
|
|
9600
9613
|
"workspace-write",
|
|
9601
9614
|
"danger-full-access"
|
|
9602
9615
|
]);
|
|
9616
|
+
const CODEX_U32_MAX = 4294967295;
|
|
9603
9617
|
/**
|
|
9604
9618
|
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
9605
9619
|
* value Codex still accepts for backward compatibility, so it is included so
|
|
@@ -9668,6 +9682,9 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
9668
9682
|
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
9669
9683
|
* Defaults to `auto_review` when neither the override nor the existing
|
|
9670
9684
|
* config sets it.
|
|
9685
|
+
* - `auto_review` — the `[auto_review]` table that configures the reviewer
|
|
9686
|
+
* `approvals_reviewer = "auto_review"` selects (`policy`, `extra_policy`,
|
|
9687
|
+
* `timeout_ms`, …). A loose table, shallow-merged like `apps` / `tui`.
|
|
9671
9688
|
* - `git_write_rules` — whether the managed profile's `:workspace_roots` table
|
|
9672
9689
|
* emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
|
|
9673
9690
|
* `:workspace` baseline makes `.git` read-only, which denies basic git
|
|
@@ -9681,7 +9698,8 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
9681
9698
|
* The keys written to `config.toml` are an **allowlist**, not verbatim
|
|
9682
9699
|
* passthrough: only `CODEXCLI_OVERRIDE_KEYS`
|
|
9683
9700
|
* (`src/constants/codexcli-paths.ts` — `approval_policy`, `sandbox_mode`,
|
|
9684
|
-
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`
|
|
9701
|
+
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`, `auto_review`,
|
|
9702
|
+
* `tui`) are emitted, and
|
|
9685
9703
|
* `computeCodexcliOverridePatch` skips anything else with a warning.
|
|
9686
9704
|
* `base_permission_profile` and `git_write_rules` are consumed by the profile
|
|
9687
9705
|
* builder rather than written, as described above, and `permission` is the
|
|
@@ -9713,6 +9731,26 @@ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
9713
9731
|
apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9714
9732
|
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})])),
|
|
9715
9733
|
/**
|
|
9734
|
+
* The `[auto_review]` table of `config.toml`, which configures the reviewer
|
|
9735
|
+
* that `approvals_reviewer = "auto_review"` (Rulesync's default) selects:
|
|
9736
|
+
* `policy` / `extra_policy` (text inserted into the reviewer prompt),
|
|
9737
|
+
* `timeout_ms` (the review budget, Codex 0.165.0+; `0` waits without a
|
|
9738
|
+
* deadline), `circuit_break_action` (`default` | `strict`) and
|
|
9739
|
+
* `conversation_history_max_output_tokens` (a positive token cap). Codex
|
|
9740
|
+
* rejects the whole config file when one of these has the wrong type or is
|
|
9741
|
+
* out of range (`timeout_ms` is a `u32`, the token cap a `NonZeroUsize`), so
|
|
9742
|
+
* they are checked here; every other key passes through verbatim.
|
|
9743
|
+
*
|
|
9744
|
+
* @see https://github.com/openai/codex/blob/rust-v0.166.0/codex-rs/config/src/config_toml.rs (`AutoReviewToml`)
|
|
9745
|
+
*/
|
|
9746
|
+
auto_review: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9747
|
+
policy: zod_mini.z.optional(zod_mini.z.string()),
|
|
9748
|
+
extra_policy: zod_mini.z.optional(zod_mini.z.string()),
|
|
9749
|
+
timeout_ms: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative(), zod_mini.z.lte(CODEX_U32_MAX))),
|
|
9750
|
+
circuit_break_action: zod_mini.z.optional(zod_mini.z.enum(["default", "strict"])),
|
|
9751
|
+
conversation_history_max_output_tokens: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.positive()))
|
|
9752
|
+
})),
|
|
9753
|
+
/**
|
|
9716
9754
|
* The `[tui]` table of `config.toml` (e.g. `vim_mode_default`, `keymap.*`).
|
|
9717
9755
|
* Not a permission surface, but like `apps` it is a top-level table with no
|
|
9718
9756
|
* canonical category, and Codex adds keys to it faster than an explicit model
|
|
@@ -12319,6 +12357,7 @@ const CODEXCLI_OVERRIDE_KEYS = [
|
|
|
12319
12357
|
"sandbox_workspace_write",
|
|
12320
12358
|
"apps",
|
|
12321
12359
|
"approvals_reviewer",
|
|
12360
|
+
"auto_review",
|
|
12322
12361
|
"tui"
|
|
12323
12362
|
];
|
|
12324
12363
|
// src/constants/takt-paths.ts
|
|
@@ -13741,6 +13780,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13741
13780
|
"workflow_arpeggio",
|
|
13742
13781
|
"workflow_runtime_prepare",
|
|
13743
13782
|
"workflow_command_gates",
|
|
13783
|
+
"workflow_system_commands",
|
|
13744
13784
|
"sync_conflict_resolver",
|
|
13745
13785
|
"allow_git_hooks",
|
|
13746
13786
|
"allow_git_filters"
|
|
@@ -13882,6 +13922,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13882
13922
|
"chat.tools.edits.autoApprove",
|
|
13883
13923
|
"chat.tools.urls.autoApprove",
|
|
13884
13924
|
"chat.permissions.default",
|
|
13925
|
+
"chat.defaultConfiguration",
|
|
13885
13926
|
"chat.agent.sandbox.enabled",
|
|
13886
13927
|
"chat.agent.sandbox.network.allowNetwork",
|
|
13887
13928
|
"chat.agent.sandbox.network.allowLocalNetwork",
|
|
@@ -22206,7 +22247,7 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
22206
22247
|
* and `~/.config/mimocode/commands/` (global), so this target reuses
|
|
22207
22248
|
* {@link OpenCodeCommand} with that layout.
|
|
22208
22249
|
*
|
|
22209
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
22250
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/command.ts
|
|
22210
22251
|
*/
|
|
22211
22252
|
var MimocodeCommand = class extends OpenCodeCommand {
|
|
22212
22253
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -30129,7 +30170,7 @@ function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
|
|
|
30129
30170
|
* `~/.config/mimocode/` (global), so this target reuses {@link OpenCodeSkill}
|
|
30130
30171
|
* with that layout.
|
|
30131
30172
|
*
|
|
30132
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
30173
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/skill/index.ts
|
|
30133
30174
|
*/
|
|
30134
30175
|
var MimocodeSkill = class extends OpenCodeSkill {
|
|
30135
30176
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -34123,7 +34164,7 @@ function normalizeAugmentcodeToolList(value) {
|
|
|
34123
34164
|
return tools.length > 0 ? tools : void 0;
|
|
34124
34165
|
}
|
|
34125
34166
|
const AugmentcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
34126
|
-
name: zod_mini.z.string(),
|
|
34167
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
34127
34168
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
34128
34169
|
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
34129
34170
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -34182,7 +34223,7 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34182
34223
|
outputRoot: ".",
|
|
34183
34224
|
frontmatter: {
|
|
34184
34225
|
targets: ["*"],
|
|
34185
|
-
name,
|
|
34226
|
+
name: name ?? (0, node_path.basename)(this.getRelativeFilePath(), (0, node_path.extname)(this.getRelativeFilePath())),
|
|
34186
34227
|
description,
|
|
34187
34228
|
...Object.keys(augmentcodeSection).length > 0 && { augmentcode: augmentcodeSection }
|
|
34188
34229
|
},
|
|
@@ -34248,7 +34289,8 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34248
34289
|
const dirPath = relativeDirPath ?? this.getSettablePaths({ global }).relativeDirPath;
|
|
34249
34290
|
const filePath = (0, node_path.join)(outputRoot, dirPath, relativeFilePath);
|
|
34250
34291
|
const fileContent = await readFileContent(filePath);
|
|
34251
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
34292
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
34293
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
34252
34294
|
const result = AugmentcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
34253
34295
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
34254
34296
|
return new AugmentcodeSubagent({
|
|
@@ -38602,7 +38644,7 @@ var OpenCodeSubagent = class extends OpenCodeStyleSubagent {
|
|
|
38602
38644
|
* `~/.config/mimocode/agents/` (global), so this target reuses
|
|
38603
38645
|
* {@link OpenCodeSubagent} with that layout.
|
|
38604
38646
|
*
|
|
38605
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
38647
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/agent.ts
|
|
38606
38648
|
*/
|
|
38607
38649
|
var MimocodeSubagent = class extends OpenCodeSubagent {
|
|
38608
38650
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -41831,7 +41873,23 @@ function sanitizeOpencodeMcpNamePart(value) {
|
|
|
41831
41873
|
* key that matches nothing.
|
|
41832
41874
|
*/
|
|
41833
41875
|
function opencodeMcpToolPermissionKey(serverName, toolName) {
|
|
41834
|
-
return `${
|
|
41876
|
+
return `${opencodeMcpServerKeyPrefix(serverName)}${toolName.replace(/[^a-zA-Z0-9_\-*?]/g, "_")}`;
|
|
41877
|
+
}
|
|
41878
|
+
/** The sanitized `<server>_` prefix every permission key of the server's tools starts with. */
|
|
41879
|
+
function opencodeMcpServerKeyPrefix(serverName) {
|
|
41880
|
+
return `${sanitizeOpencodeMcpNamePart(serverName)}_`;
|
|
41881
|
+
}
|
|
41882
|
+
/**
|
|
41883
|
+
* Whether `key` is exactly the sanitized `<server>_` prefix of one of
|
|
41884
|
+
* `serverNames` (e.g. `a_b_` for server `a.b` or `a_b`). No tool has an empty
|
|
41885
|
+
* name, so such a key filters nothing of that server, and
|
|
41886
|
+
* {@link findOpencodeMcpToolKeyOwner} gives it no owner. Read as an unlisted
|
|
41887
|
+
* server's filter instead, it would be split at the first `_` into a phantom
|
|
41888
|
+
* server (`a` with tool `b_`), so importers leave it out of the rulesync
|
|
41889
|
+
* config, and the OpenCode family's generate keeps it in the file as it was.
|
|
41890
|
+
*/
|
|
41891
|
+
function isBareOpencodeMcpServerPrefix({ key, serverNames }) {
|
|
41892
|
+
return serverNames.some((serverName) => key === opencodeMcpServerKeyPrefix(serverName));
|
|
41835
41893
|
}
|
|
41836
41894
|
/**
|
|
41837
41895
|
* The server among `serverNames` whose tool a permission key names, with the
|
|
@@ -41845,7 +41903,7 @@ function findOpencodeMcpToolKeyOwner({ key, serverNames, builtinKeys, acceptRawP
|
|
|
41845
41903
|
if (builtinKeys.has(key)) return;
|
|
41846
41904
|
let owner;
|
|
41847
41905
|
for (const serverName of serverNames) {
|
|
41848
|
-
const sanitized =
|
|
41906
|
+
const sanitized = opencodeMcpServerKeyPrefix(serverName);
|
|
41849
41907
|
const prefixes = acceptRawPrefix ? (0, es_toolkit.uniq)([sanitized, `${serverName}_`]) : [sanitized];
|
|
41850
41908
|
for (const prefix of prefixes) if (key.length > prefix.length && key.startsWith(prefix) && (owner === void 0 || prefix.length > owner.prefix.length)) owner = {
|
|
41851
41909
|
serverName,
|
|
@@ -42306,7 +42364,10 @@ function convertFromKiloFormat(kiloMcp, tools, permission) {
|
|
|
42306
42364
|
const serverNames = Object.keys(kiloMcp);
|
|
42307
42365
|
return {
|
|
42308
42366
|
...Object.fromEntries(Object.entries(kiloMcp).map(([serverName, serverConfig]) => [serverName, kiloServerToRulesync(serverName, serverConfig, splitKiloServerTools(serverName, serverNames, filters))])),
|
|
42309
|
-
...orphanMcpToolFiltersToRulesync(kiloMcp, tools)
|
|
42367
|
+
...orphanMcpToolFiltersToRulesync(kiloMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findKiloMcpToolKeyOwner(key, serverNames) && !isBareOpencodeMcpServerPrefix({
|
|
42368
|
+
key,
|
|
42369
|
+
serverNames
|
|
42370
|
+
}))))
|
|
42310
42371
|
};
|
|
42311
42372
|
}
|
|
42312
42373
|
/**
|
|
@@ -42824,16 +42885,20 @@ function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
|
42824
42885
|
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42825
42886
|
key,
|
|
42826
42887
|
serverNames
|
|
42888
|
+
}) && !isBareOpencodeMcpServerPrefix({
|
|
42889
|
+
key,
|
|
42890
|
+
serverNames
|
|
42827
42891
|
})))),
|
|
42828
42892
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42829
42893
|
};
|
|
42830
42894
|
}
|
|
42895
|
+
const OPENCODE_MCP_BUILTIN_TOOL_KEYS = /* @__PURE__ */ new Set();
|
|
42831
42896
|
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42832
42897
|
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42833
42898
|
return findOpencodeMcpToolKeyOwner({
|
|
42834
42899
|
key,
|
|
42835
42900
|
serverNames,
|
|
42836
|
-
builtinKeys:
|
|
42901
|
+
builtinKeys: OPENCODE_MCP_BUILTIN_TOOL_KEYS,
|
|
42837
42902
|
acceptRawPrefix: true
|
|
42838
42903
|
});
|
|
42839
42904
|
}
|
|
@@ -43003,10 +43068,50 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
43003
43068
|
tools
|
|
43004
43069
|
};
|
|
43005
43070
|
}
|
|
43071
|
+
/**
|
|
43072
|
+
* Copy each key of `existingTools` that is a bare sanitized `<server>_` prefix
|
|
43073
|
+
* of one of `serverNames` into `tools`, unless a generated filter already
|
|
43074
|
+
* writes the same key or another listed server owns it (`a_b_` is tool `b_` of
|
|
43075
|
+
* server `a` when `a` is listed too). An owned key is the rulesync config's to
|
|
43076
|
+
* write, so carrying it over would keep a filter the user removed. Mutates
|
|
43077
|
+
* `tools` in place.
|
|
43078
|
+
*/
|
|
43079
|
+
function preserveBareOpencodeMcpServerPrefixKeys({ tools, existingTools, serverNames }) {
|
|
43080
|
+
for (const [key, enabled] of Object.entries(existingTools ?? {})) if (!Object.hasOwn(tools, key) && isBareOpencodeMcpServerPrefix({
|
|
43081
|
+
key,
|
|
43082
|
+
serverNames
|
|
43083
|
+
}) && !findOpencodeToolOwner({
|
|
43084
|
+
key,
|
|
43085
|
+
serverNames
|
|
43086
|
+
})) tools[key] = enabled;
|
|
43087
|
+
}
|
|
43088
|
+
/**
|
|
43089
|
+
* When the existing file does not pass the schema, its `tools` map cannot be
|
|
43090
|
+
* read, so a bare `<server>_` key in it is not carried over. Say so rather
|
|
43091
|
+
* than lift the deny it may hold without a word.
|
|
43092
|
+
*/
|
|
43093
|
+
function warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({ existingJson, serverNames, logger }) {
|
|
43094
|
+
const rawTools = isRecord(existingJson) ? existingJson.tools : void 0;
|
|
43095
|
+
if (!isRecord(rawTools)) return;
|
|
43096
|
+
const bareKeys = Object.keys(rawTools).filter((key) => isBareOpencodeMcpServerPrefix({
|
|
43097
|
+
key,
|
|
43098
|
+
serverNames
|
|
43099
|
+
}) && !findOpencodeToolOwner({
|
|
43100
|
+
key,
|
|
43101
|
+
serverNames
|
|
43102
|
+
}));
|
|
43103
|
+
if (bareKeys.length > 0) logger?.warn(`OpenCode MCP: the existing config does not match the expected schema, so the bare server-prefix tool ${bareKeys.length === 1 ? "key" : "keys"} ${bareKeys.map(quoteValueForWarning).join(", ")} ${bareKeys.length === 1 ? "is" : "are"} not kept in "tools".`);
|
|
43104
|
+
}
|
|
43006
43105
|
const OpencodeMcpFeatureOptionsSchema = zod_mini.z.looseObject({ configShape: zod_mini.z.optional(zod_mini.z.enum(["v1", "v2"])) });
|
|
43007
43106
|
var OpencodeMcp = class extends ToolMcp {
|
|
43008
43107
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
43009
43108
|
static layout = OPENCODE_LAYOUT;
|
|
43109
|
+
/**
|
|
43110
|
+
* Per-server keys a fork's MCP schema adds on top of OpenCode's. On generate
|
|
43111
|
+
* each one a canonical server carries is copied verbatim onto that server's
|
|
43112
|
+
* local or remote entry, as `timeout` and `oauth` are. OpenCode adds none.
|
|
43113
|
+
*/
|
|
43114
|
+
static extraPassthroughServerFields = [];
|
|
43010
43115
|
json;
|
|
43011
43116
|
/**
|
|
43012
43117
|
* With a `logger`, the V2 values that lowering the content drops are warned
|
|
@@ -43125,6 +43230,20 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43125
43230
|
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.`);
|
|
43126
43231
|
}
|
|
43127
43232
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, configShape, logger);
|
|
43233
|
+
this.copyExtraPassthroughServerFields({
|
|
43234
|
+
convertedMcp,
|
|
43235
|
+
mcpServers: transformedServers
|
|
43236
|
+
});
|
|
43237
|
+
if (existingMcp.success) preserveBareOpencodeMcpServerPrefixKeys({
|
|
43238
|
+
tools: mcpTools,
|
|
43239
|
+
existingTools: existingMcp.data.tools,
|
|
43240
|
+
serverNames: Object.keys(transformedServers)
|
|
43241
|
+
});
|
|
43242
|
+
else warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({
|
|
43243
|
+
existingJson: this.lowerConfig(existingJson),
|
|
43244
|
+
serverNames: Object.keys(transformedServers),
|
|
43245
|
+
logger
|
|
43246
|
+
});
|
|
43128
43247
|
if (this.layout.configVersion === "v2") warnAboutOpencodeV2SseServers({
|
|
43129
43248
|
mcpServers: transformedServers,
|
|
43130
43249
|
convertedMcp,
|
|
@@ -43174,6 +43293,21 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43174
43293
|
});
|
|
43175
43294
|
}
|
|
43176
43295
|
/**
|
|
43296
|
+
* Copies {@link OpencodeMcp.extraPassthroughServerFields} from each canonical
|
|
43297
|
+
* server onto its generated transport entry. A toggle entry is left alone:
|
|
43298
|
+
* it has no transport for these keys to configure.
|
|
43299
|
+
*/
|
|
43300
|
+
static copyExtraPassthroughServerFields({ convertedMcp, mcpServers }) {
|
|
43301
|
+
if (this.extraPassthroughServerFields.length === 0) return;
|
|
43302
|
+
for (const [serverName, entry] of Object.entries(convertedMcp)) {
|
|
43303
|
+
const source = Object.hasOwn(mcpServers, serverName) ? mcpServers[serverName] : void 0;
|
|
43304
|
+
if (source === void 0 || !isOpencodeTransportServer(entry)) continue;
|
|
43305
|
+
const sourceRecord = source;
|
|
43306
|
+
const entryRecord = entry;
|
|
43307
|
+
for (const key of this.extraPassthroughServerFields) if (sourceRecord[key] !== void 0) entryRecord[key] = sourceRecord[key];
|
|
43308
|
+
}
|
|
43309
|
+
}
|
|
43310
|
+
/**
|
|
43177
43311
|
* Builds the V2 `mcp` value: each server raised to the V2 spelling under the
|
|
43178
43312
|
* `mcp.servers` envelope, keeping what the existing file has that Rulesync
|
|
43179
43313
|
* cannot express (see {@link raiseOpencodeServerToV2}), plus the existing
|
|
@@ -43303,16 +43437,30 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43303
43437
|
* `.mimocode/mimocode.jsonc` (project) and `~/.config/mimocode/mimocode.jsonc`
|
|
43304
43438
|
* (global), so this target reuses {@link OpencodeMcp} with that layout.
|
|
43305
43439
|
*
|
|
43306
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
43440
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/mcp.ts
|
|
43307
43441
|
*/
|
|
43308
43442
|
var MimocodeMcp = class extends OpencodeMcp {
|
|
43309
43443
|
static layout = MIMOCODE_LAYOUT;
|
|
43310
43444
|
/**
|
|
43445
|
+
* MiMo Code's local and remote server schemas add two keys OpenCode does not
|
|
43446
|
+
* have:
|
|
43447
|
+
* - `sampling` (`"deny"` | `"ask"` | `"allow"`): the policy for the server's
|
|
43448
|
+
* client-side sampling requests (since v0.1.10).
|
|
43449
|
+
* - `alwaysDirectTools` (string array): tools exposed as top-level tools
|
|
43450
|
+
* instead of through `mcp_tool_search` / `mcp_tool_use` (on `main`, not yet
|
|
43451
|
+
* released). Releases without it ignore the key, since MiMo Code only
|
|
43452
|
+
* rejects unknown keys at the top level of the config.
|
|
43453
|
+
*
|
|
43454
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/mcp.ts
|
|
43455
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/v0.1.16/packages/cli/src/config/config.ts
|
|
43456
|
+
*/
|
|
43457
|
+
static extraPassthroughServerFields = ["sampling", "alwaysDirectTools"];
|
|
43458
|
+
/**
|
|
43311
43459
|
* MiMo Code resolves a relative `instructions` entry against the project
|
|
43312
43460
|
* directory, not the config directory, so a global rule is written as a
|
|
43313
43461
|
* `~/`-rooted path (which MiMo Code expands to the home directory).
|
|
43314
43462
|
*
|
|
43315
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
43463
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/session/instruction.ts
|
|
43316
43464
|
*/
|
|
43317
43465
|
static toGlobalInstructionEntry({ path }) {
|
|
43318
43466
|
return `~/${path}`;
|
|
@@ -48682,7 +48830,7 @@ var OpenCodeRule = class extends ToolRule {
|
|
|
48682
48830
|
* `instructions` list of its config, so this target reuses {@link OpenCodeRule}
|
|
48683
48831
|
* with the `.mimocode/` layout.
|
|
48684
48832
|
*
|
|
48685
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
48833
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/session/instruction.ts
|
|
48686
48834
|
*/
|
|
48687
48835
|
var MimocodeRule = class extends OpenCodeRule {
|
|
48688
48836
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -51650,6 +51798,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
51650
51798
|
factory
|
|
51651
51799
|
});
|
|
51652
51800
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
51801
|
+
this.warnForAugmentcodePluginRules(toolRules);
|
|
51653
51802
|
await this.warnForInstructionBudget({
|
|
51654
51803
|
toolRules,
|
|
51655
51804
|
meta
|
|
@@ -52041,6 +52190,20 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52041
52190
|
this.logger.warn(`Writing ${stripControlCharacters(rootFileRelativePath)} for ${this.toolTarget} means ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} will no longer be read. Run \`rulesync import --targets ${this.toolTarget}\` first to carry that content into ${RULESYNC_RULES_RELATIVE_DIR_PATH}, or delete ${listedNames.length === 1 ? "it" : "them"} once you have checked the content is already in the root file.`);
|
|
52042
52191
|
}
|
|
52043
52192
|
/**
|
|
52193
|
+
* Auggie 1.x (still a prerelease on npm at the time of writing; the stable
|
|
52194
|
+
* line is 0.36.0) no longer loads `rules/` from plugins: "Rules shipped in
|
|
52195
|
+
* plugins (`rules/` in a plugin)" is listed among the removed features, while
|
|
52196
|
+
* commands, agents, skills, hooks and MCP servers still load. The rule files
|
|
52197
|
+
* are still written, because the stable 0.x CLI reads them, but under 1.x the
|
|
52198
|
+
* bundle silently loses them, so say so.
|
|
52199
|
+
* @see https://docs.augmentcode.com/cli/whats-new
|
|
52200
|
+
* @see https://docs.augmentcode.com/cli/v0/plugins
|
|
52201
|
+
*/
|
|
52202
|
+
warnForAugmentcodePluginRules(toolRules) {
|
|
52203
|
+
if (this.toolTarget !== "augmentcode-plugin" || toolRules.length === 0) return;
|
|
52204
|
+
this.logger.warn(`augmentcode-plugin: plugin rules (rules/*.md) are loaded by Auggie 0.x only; Auggie 1.x no longer reads rules/ from plugins, so these ${toolRules.length} rule file(s) have no effect there. Ship instructions that must reach Auggie 1.x users as a skill, or have them generate the augmentcode target in their project.`);
|
|
52205
|
+
}
|
|
52206
|
+
/**
|
|
52044
52207
|
* Warp still reads a legacy `WARP.md`, and when both `WARP.md` and
|
|
52045
52208
|
* `AGENTS.md` sit in the same directory, `WARP.md` takes priority — so an
|
|
52046
52209
|
* `AGENTS.md` this run writes next to one would never be read. That holds for
|
|
@@ -63884,7 +64047,7 @@ var OpencodeHooks = class extends ToolHooks {
|
|
|
63884
64047
|
* `~/.config/mimocode/plugins/` (global) and keeps OpenCode's plugin event
|
|
63885
64048
|
* names, so this target reuses {@link OpencodeHooks} with that layout.
|
|
63886
64049
|
*
|
|
63887
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
64050
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/plugin.ts
|
|
63888
64051
|
*/
|
|
63889
64052
|
var MimocodeHooks = class extends OpencodeHooks {
|
|
63890
64053
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -82283,6 +82446,57 @@ function matchesGlobStep(step, character) {
|
|
|
82283
82446
|
const admitted = step.members.has(character) || step.ranges.some(([low, high]) => code >= low && code <= high);
|
|
82284
82447
|
return step.negated ? !admitted : admitted;
|
|
82285
82448
|
}
|
|
82449
|
+
/**
|
|
82450
|
+
* Whether `value` matches `glob` in full, with `*`, `?` and `[...]` carrying
|
|
82451
|
+
* their usual meaning. The glob comes first, unlike Node's own
|
|
82452
|
+
* `path.matchesGlob(path, pattern)`.
|
|
82453
|
+
*
|
|
82454
|
+
* The glob is walked step by step rather than translated to a regex, because
|
|
82455
|
+
* both sides of a comparison can come from a file a repository carries: `*a*a*a*a*b`
|
|
82456
|
+
* translated to `^.*a.*a.*a.*a.*b$` takes minutes against a forty-character
|
|
82457
|
+
* name, while walking the steps is linear in the name per `*`. Remembering only
|
|
82458
|
+
* the most recent `*` to fall back to is enough — an earlier one can always give
|
|
82459
|
+
* up whatever the later one needed.
|
|
82460
|
+
*/
|
|
82461
|
+
function matchesGlob(glob, value) {
|
|
82462
|
+
return compileGlob(glob)(value);
|
|
82463
|
+
}
|
|
82464
|
+
/**
|
|
82465
|
+
* Parse `glob` once and return a predicate that walks it, for a caller that
|
|
82466
|
+
* tests the same glob against a whole list of names.
|
|
82467
|
+
*/
|
|
82468
|
+
function compileGlob(glob) {
|
|
82469
|
+
const steps = parseGlob(glob);
|
|
82470
|
+
return (value) => matchesParsedGlob(steps, value);
|
|
82471
|
+
}
|
|
82472
|
+
function matchesParsedGlob(steps, value) {
|
|
82473
|
+
const characters = [...value];
|
|
82474
|
+
let stepIndex = 0;
|
|
82475
|
+
let characterIndex = 0;
|
|
82476
|
+
let starStepIndex = -1;
|
|
82477
|
+
let starCharacterIndex = 0;
|
|
82478
|
+
while (characterIndex < characters.length) {
|
|
82479
|
+
const step = steps[stepIndex];
|
|
82480
|
+
if (step?.kind === "star") {
|
|
82481
|
+
starStepIndex = stepIndex;
|
|
82482
|
+
starCharacterIndex = characterIndex;
|
|
82483
|
+
stepIndex += 1;
|
|
82484
|
+
continue;
|
|
82485
|
+
}
|
|
82486
|
+
if (step !== void 0 && matchesGlobStep(step, characters[characterIndex] ?? "")) {
|
|
82487
|
+
stepIndex += 1;
|
|
82488
|
+
characterIndex += 1;
|
|
82489
|
+
continue;
|
|
82490
|
+
}
|
|
82491
|
+
if (starStepIndex < 0) return false;
|
|
82492
|
+
starCharacterIndex += 1;
|
|
82493
|
+
stepIndex = starStepIndex + 1;
|
|
82494
|
+
characterIndex = starCharacterIndex;
|
|
82495
|
+
}
|
|
82496
|
+
let remaining = stepIndex;
|
|
82497
|
+
while (steps[remaining]?.kind === "star") remaining += 1;
|
|
82498
|
+
return remaining === steps.length;
|
|
82499
|
+
}
|
|
82286
82500
|
/** Whether two single-character steps can both match one same character. */
|
|
82287
82501
|
function stepsShareACharacter(left, right) {
|
|
82288
82502
|
if (left.kind === "any" || right.kind === "any") return true;
|
|
@@ -83819,9 +84033,13 @@ const CANONICAL_TO_AUGMENT_TOOL_NAMES = {
|
|
|
83819
84033
|
const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENT_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
83820
84034
|
const CURRENT_TO_LEGACY_AUGMENT_TOOL_NAMES = {
|
|
83821
84035
|
terminal: "launch-process",
|
|
84036
|
+
bash: "launch-process",
|
|
84037
|
+
shell: "launch-process",
|
|
83822
84038
|
read: "view",
|
|
83823
84039
|
edit: "str-replace-editor",
|
|
83824
|
-
|
|
84040
|
+
str_replace_editor: "str-replace-editor",
|
|
84041
|
+
write: "save-file",
|
|
84042
|
+
save_file: "save-file"
|
|
83825
84043
|
};
|
|
83826
84044
|
function toAugmentToolName(canonical) {
|
|
83827
84045
|
return lookupOwn({
|
|
@@ -83973,6 +84191,7 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
83973
84191
|
while (i < body.length) {
|
|
83974
84192
|
const ch = body[i];
|
|
83975
84193
|
if (ch === "\\" && i + 1 < body.length) {
|
|
84194
|
+
if (isGlobSpecial(body[i + 1])) return false;
|
|
83976
84195
|
i += 2;
|
|
83977
84196
|
continue;
|
|
83978
84197
|
}
|
|
@@ -83989,6 +84208,37 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
83989
84208
|
}
|
|
83990
84209
|
return true;
|
|
83991
84210
|
}
|
|
84211
|
+
/** Characters a glob reads as syntax (wildcards or the start of a character class). */
|
|
84212
|
+
function isGlobSpecial(ch) {
|
|
84213
|
+
return ch === "*" || ch === "?" || ch === "[";
|
|
84214
|
+
}
|
|
84215
|
+
/**
|
|
84216
|
+
* Whether `shellRegexToGlob` can approximate a `shellInputRegex` without widening it. The lossy
|
|
84217
|
+
* conversion keeps regex quantifiers as characters, and a `*` or `?` left in the glob becomes a
|
|
84218
|
+
* wildcard: `^rm -rf /tmp/a*$` (a run of `a`s) would turn into the glob `rm -rf /tmp/a*`, which
|
|
84219
|
+
* matches `rm -rf /tmp/a /home`. So a regex is unsafe when it has an escaped literal `\*` / `\?` /
|
|
84220
|
+
* `\[` (a `[` would open a character class in targets whose globs support one),
|
|
84221
|
+
* or a bare quantifier `*` / `?` / `+` / `{` that is not part of a `.*` run (which the glob `*`
|
|
84222
|
+
* reproduces faithfully).
|
|
84223
|
+
*/
|
|
84224
|
+
function canApproximateShellRegexWithoutWidening(regex) {
|
|
84225
|
+
let i = 0;
|
|
84226
|
+
while (i < regex.length) {
|
|
84227
|
+
const ch = regex[i];
|
|
84228
|
+
if (ch === "\\" && i + 1 < regex.length) {
|
|
84229
|
+
if (isGlobSpecial(regex[i + 1])) return false;
|
|
84230
|
+
i += 2;
|
|
84231
|
+
continue;
|
|
84232
|
+
}
|
|
84233
|
+
if (ch === "." && regex[i + 1] === "*") {
|
|
84234
|
+
i += 2;
|
|
84235
|
+
continue;
|
|
84236
|
+
}
|
|
84237
|
+
if (ch === "*" || ch === "?" || ch === "+" || ch === "{") return false;
|
|
84238
|
+
i += 1;
|
|
84239
|
+
}
|
|
84240
|
+
return true;
|
|
84241
|
+
}
|
|
83992
84242
|
const MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
|
|
83993
84243
|
var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermissions {
|
|
83994
84244
|
constructor(params) {
|
|
@@ -84425,12 +84675,18 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
84425
84675
|
if (legacyToolName === "launch-process" && entry.shellInputRegex) {
|
|
84426
84676
|
const regex = entry.shellInputRegex;
|
|
84427
84677
|
if (isShellRegexRoundtrippable(regex)) pattern = shellRegexToGlob(regex);
|
|
84428
|
-
else
|
|
84429
|
-
|
|
84430
|
-
|
|
84431
|
-
|
|
84432
|
-
|
|
84433
|
-
|
|
84678
|
+
else {
|
|
84679
|
+
if (action !== "deny" && !canApproximateShellRegexWithoutWidening(regex)) {
|
|
84680
|
+
logger?.warn(`AugmentCode permissions: skipping ${action} entry for tool '${entry.toolName}': shellInputRegex '${regex}' uses a quantifier or a literal '*' / '?' that a glob cannot express, and converting it would widen the rule. The entry is not imported.`);
|
|
84681
|
+
continue;
|
|
84682
|
+
}
|
|
84683
|
+
if (action === "deny") {
|
|
84684
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`);
|
|
84685
|
+
pattern = "*";
|
|
84686
|
+
} else {
|
|
84687
|
+
pattern = shellRegexToGlob(regex);
|
|
84688
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`);
|
|
84689
|
+
}
|
|
84434
84690
|
}
|
|
84435
84691
|
} else pattern = "*";
|
|
84436
84692
|
if (isPrototypePollutionKey(pattern)) {
|
|
@@ -85923,15 +86179,19 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85923
86179
|
* `env` variables a project or local settings file cannot set, because a
|
|
85924
86180
|
* checked-out repository should not control them: where Claude Code stores its
|
|
85925
86181
|
* own files, what session content it exports, where telemetry goes, how it
|
|
85926
|
-
* starts or syncs, the timers on an unanswered
|
|
85927
|
-
*
|
|
86182
|
+
* starts or syncs, which claude.ai sign-in it uses, the timers on an unanswered
|
|
86183
|
+
* dialog, and whether it processes attachments or expands `@` file mentions,
|
|
86184
|
+
* and the screen reader (`CLAUDE_AX_*`) variables. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
85928
86185
|
*
|
|
85929
|
-
* Only the names the settings reference lists are covered; its directory
|
|
85930
|
-
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
86186
|
+
* Only the names the settings reference lists are covered; its directory,
|
|
86187
|
+
* Windows and sign-in groups are introduced with "such as", so an unlisted member of those
|
|
85931
86188
|
* families is still written. Windows variable names are case-insensitive, so
|
|
85932
|
-
* that group is matched without regard to case.
|
|
86189
|
+
* that group is matched without regard to case. `ALLUSERSPROFILE`,
|
|
86190
|
+
* `SystemDrive` and the `CommonProgramFiles` family are ignored there since
|
|
86191
|
+
* Claude Code v2.1.285.
|
|
85933
86192
|
*
|
|
85934
86193
|
* @see https://code.claude.com/docs/en/settings-reference#variables-claude-code-ignores-in-env
|
|
86194
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
|
|
85935
86195
|
*/
|
|
85936
86196
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
85937
86197
|
"BETA_TRACING_ENDPOINT",
|
|
@@ -85941,6 +86201,9 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85941
86201
|
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
85942
86202
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
85943
86203
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
86204
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
86205
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
86206
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
85944
86207
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
85945
86208
|
"CLAUDE_CODE_PLUGIN_SEED_DIR",
|
|
85946
86209
|
"CLAUDE_CODE_PROCESS_WRAPPER",
|
|
@@ -85961,14 +86224,23 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85961
86224
|
"TMPDIR"
|
|
85962
86225
|
]);
|
|
85963
86226
|
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES = /* @__PURE__ */ new Set([
|
|
86227
|
+
"ALLUSERSPROFILE",
|
|
86228
|
+
"COMMONPROGRAMW6432",
|
|
85964
86229
|
"COMSPEC",
|
|
85965
86230
|
"LOCALAPPDATA",
|
|
85966
86231
|
"PATHEXT",
|
|
85967
86232
|
"PROGRAMDATA",
|
|
85968
86233
|
"PROGRAMW6432",
|
|
85969
86234
|
"PSMODULEPATH",
|
|
86235
|
+
"SYSTEMDRIVE",
|
|
85970
86236
|
"SYSTEMROOT"
|
|
85971
86237
|
]);
|
|
86238
|
+
/**
|
|
86239
|
+
* Windows name prefixes matched as whole families: `ProgramFiles*` and
|
|
86240
|
+
* `CommonProgramFiles*`. `ProgramW6432` and `CommonProgramW6432` are exact
|
|
86241
|
+
* names in the set above.
|
|
86242
|
+
*/
|
|
86243
|
+
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES = ["PROGRAMFILES", "COMMONPROGRAMFILES"];
|
|
85972
86244
|
const CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES = [
|
|
85973
86245
|
"_ENDPOINT",
|
|
85974
86246
|
"_HEADERS",
|
|
@@ -86013,7 +86285,7 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
86013
86285
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
86014
86286
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_") || name.startsWith("CLAUDE_AX_")) return true;
|
|
86015
86287
|
const upper = name.toUpperCase();
|
|
86016
|
-
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || upper.startsWith(
|
|
86288
|
+
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES.some((prefix) => upper.startsWith(prefix))) return true;
|
|
86017
86289
|
if (name.startsWith("OTEL_EXPORTER_OTLP_") && CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix))) return true;
|
|
86018
86290
|
if (Object.hasOwn(CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES, name)) {
|
|
86019
86291
|
const offValues = CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES[name];
|
|
@@ -86045,7 +86317,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
86045
86317
|
kept[name] = value;
|
|
86046
86318
|
}
|
|
86047
86319
|
if (ignoredEverywhere.length > 0) logger?.warn(`Claude Code permissions: Claude Code ignores ${ignoredEverywhere.map((name) => `'env.${name}'`).join(", ")} in every settings file, so ${ignoredEverywhere.length === 1 ? "it is" : "they are"} not written to ${relativeFilePath}. Set ${ignoredEverywhere.length === 1 ? "it" : "them"} in the environment Claude Code starts from instead, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`);
|
|
86048
|
-
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
86320
|
+
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set the claude.ai sign-in, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
86049
86321
|
return kept;
|
|
86050
86322
|
}
|
|
86051
86323
|
/**
|
|
@@ -86695,7 +86967,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
86695
86967
|
"ask",
|
|
86696
86968
|
"deny"
|
|
86697
86969
|
];
|
|
86698
|
-
const CATCH_ALL_PATTERN$
|
|
86970
|
+
const CATCH_ALL_PATTERN$14 = "*";
|
|
86699
86971
|
const MCP_CATEGORY = "mcp";
|
|
86700
86972
|
const MCP_RULE_PREFIX = "mcp__";
|
|
86701
86973
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -86765,7 +87037,7 @@ function categoryOfRule$2(rule) {
|
|
|
86765
87037
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
86766
87038
|
*/
|
|
86767
87039
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
86768
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
87040
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$14;
|
|
86769
87041
|
if (category === MCP_CATEGORY) {
|
|
86770
87042
|
if (catchAll && action === "allow") {
|
|
86771
87043
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -86774,7 +87046,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
86774
87046
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
86775
87047
|
}
|
|
86776
87048
|
if (category === "*") {
|
|
86777
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
87049
|
+
if (catchAll) return CATCH_ALL_PATTERN$14;
|
|
86778
87050
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
86779
87051
|
return null;
|
|
86780
87052
|
}
|
|
@@ -86792,7 +87064,7 @@ function parseCodebuddyRule(rule) {
|
|
|
86792
87064
|
const { specifier } = splitCodebuddyRule(rule);
|
|
86793
87065
|
if (specifier === void 0) return {
|
|
86794
87066
|
category,
|
|
86795
|
-
pattern: CATCH_ALL_PATTERN$
|
|
87067
|
+
pattern: CATCH_ALL_PATTERN$14
|
|
86796
87068
|
};
|
|
86797
87069
|
const trimmed = specifier.trim();
|
|
86798
87070
|
return trimmed === "" ? null : {
|
|
@@ -89118,8 +89390,8 @@ function mapBashActionToDecision(action) {
|
|
|
89118
89390
|
// src/features/permissions/commandcode-permissions.ts
|
|
89119
89391
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
89120
89392
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
89121
|
-
const CATCH_ALL_PATTERN$
|
|
89122
|
-
const MCP_CANONICAL_PREFIX$
|
|
89393
|
+
const CATCH_ALL_PATTERN$13 = "*";
|
|
89394
|
+
const MCP_CANONICAL_PREFIX$8 = "mcp__";
|
|
89123
89395
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
89124
89396
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
89125
89397
|
bash: "Shell",
|
|
@@ -89164,10 +89436,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
89164
89436
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
89165
89437
|
*/
|
|
89166
89438
|
function buildCommandcodeRule(category, pattern) {
|
|
89167
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
89168
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89169
|
-
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$
|
|
89170
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
89439
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$13 || pattern === "";
|
|
89440
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$8)) return catchAll ? category : `${category}(${pattern})`;
|
|
89441
|
+
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$8}${pattern}`;
|
|
89442
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$13 : null;
|
|
89171
89443
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
89172
89444
|
if (tool === void 0) return null;
|
|
89173
89445
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -89203,7 +89475,7 @@ function splitCommandcodeRule(rule) {
|
|
|
89203
89475
|
}
|
|
89204
89476
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
89205
89477
|
function isCatchAllSpecifier(inner) {
|
|
89206
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
89478
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$13;
|
|
89207
89479
|
}
|
|
89208
89480
|
/**
|
|
89209
89481
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -89237,34 +89509,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
89237
89509
|
if (server.length === 0) return null;
|
|
89238
89510
|
const everyServer = {
|
|
89239
89511
|
category: "mcp",
|
|
89240
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89512
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89241
89513
|
notInAllow: true
|
|
89242
89514
|
};
|
|
89243
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89244
|
-
if (server === CATCH_ALL_PATTERN$
|
|
89245
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
89515
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8)) {
|
|
89516
|
+
if (server === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89517
|
+
if (server.includes(CATCH_ALL_PATTERN$13)) return null;
|
|
89246
89518
|
return {
|
|
89247
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
89248
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89519
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$13 ? `${MCP_CANONICAL_PREFIX$8}${server}` : `${MCP_CANONICAL_PREFIX$8}${remainder}`,
|
|
89520
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89249
89521
|
notInAllow: false
|
|
89250
89522
|
};
|
|
89251
89523
|
}
|
|
89252
|
-
const category = `${MCP_CANONICAL_PREFIX$
|
|
89253
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
89524
|
+
const category = `${MCP_CANONICAL_PREFIX$8}${remainder}`;
|
|
89525
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$13);
|
|
89254
89526
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
89255
89527
|
category,
|
|
89256
89528
|
pattern: inner,
|
|
89257
89529
|
notInAllow: true
|
|
89258
89530
|
} : null;
|
|
89259
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
89260
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
89261
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
89262
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89531
|
+
if (remainder === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89532
|
+
if (rest === CATCH_ALL_PATTERN$13 && !server.includes(CATCH_ALL_PATTERN$13)) return {
|
|
89533
|
+
category: `${MCP_CANONICAL_PREFIX$8}${server}`,
|
|
89534
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89263
89535
|
notInAllow: true
|
|
89264
89536
|
};
|
|
89265
89537
|
return namesTool ? {
|
|
89266
89538
|
category,
|
|
89267
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89539
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89268
89540
|
notInAllow: false
|
|
89269
89541
|
} : null;
|
|
89270
89542
|
}
|
|
@@ -89288,14 +89560,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89288
89560
|
if (category === "bash") {
|
|
89289
89561
|
if (isCatchAllSpecifier(inner)) return {
|
|
89290
89562
|
category,
|
|
89291
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89563
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89292
89564
|
notInAllow: false
|
|
89293
89565
|
};
|
|
89294
89566
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
89295
89567
|
if (normalized === "" || normalized === ":*") return null;
|
|
89296
89568
|
return isCatchAllSpecifier(normalized) ? {
|
|
89297
89569
|
category,
|
|
89298
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89570
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89299
89571
|
notInAllow: true
|
|
89300
89572
|
} : {
|
|
89301
89573
|
category,
|
|
@@ -89305,7 +89577,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89305
89577
|
}
|
|
89306
89578
|
if (isCatchAllSpecifier(inner)) return {
|
|
89307
89579
|
category,
|
|
89308
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89580
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89309
89581
|
notInAllow: false
|
|
89310
89582
|
};
|
|
89311
89583
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -89324,13 +89596,13 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89324
89596
|
*/
|
|
89325
89597
|
function parseCommandcodeRule(rule) {
|
|
89326
89598
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89327
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
89599
|
+
if (tool === CATCH_ALL_PATTERN$13) return isCatchAllSpecifier(inner) ? {
|
|
89328
89600
|
category: "*",
|
|
89329
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89601
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89330
89602
|
notInAllow: true
|
|
89331
89603
|
} : null;
|
|
89332
89604
|
const lowered = tool.toLowerCase();
|
|
89333
|
-
if (lowered.startsWith(MCP_CANONICAL_PREFIX$
|
|
89605
|
+
if (lowered.startsWith(MCP_CANONICAL_PREFIX$8)) return parseMcpCommandcodeRule({
|
|
89334
89606
|
tool,
|
|
89335
89607
|
inner
|
|
89336
89608
|
});
|
|
@@ -89348,12 +89620,12 @@ function parseCommandcodeRule(rule) {
|
|
|
89348
89620
|
*/
|
|
89349
89621
|
function isFoldedMcpToolNameRule({ rule, category }) {
|
|
89350
89622
|
const { tool } = splitCommandcodeRule(rule);
|
|
89351
|
-
return category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89623
|
+
return category.startsWith(MCP_CANONICAL_PREFIX$8) && !tool.startsWith(MCP_CANONICAL_PREFIX$8);
|
|
89352
89624
|
}
|
|
89353
89625
|
/** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
|
|
89354
89626
|
function scopedMcpRuleTool(rule) {
|
|
89355
89627
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89356
|
-
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89628
|
+
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$8) ? tool : null;
|
|
89357
89629
|
}
|
|
89358
89630
|
/**
|
|
89359
89631
|
* The string entries of a Command Code permission list. Command Code reads
|
|
@@ -89396,7 +89668,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
89396
89668
|
}).filter((rule) => {
|
|
89397
89669
|
const parsed = parseCommandcodeRule(rule);
|
|
89398
89670
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
89399
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
89671
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$13})`);
|
|
89400
89672
|
if (key !== "allow" && isStricterAction({
|
|
89401
89673
|
action: key,
|
|
89402
89674
|
existing: replacement
|
|
@@ -89453,7 +89725,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89453
89725
|
const ranked = /* @__PURE__ */ new Map();
|
|
89454
89726
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
89455
89727
|
for (const [category, rules] of Object.entries(permission)) {
|
|
89456
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
89728
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$13);
|
|
89457
89729
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
89458
89730
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
89459
89731
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -89522,7 +89794,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89522
89794
|
function skippedAllowImportMessage({ rule, category }) {
|
|
89523
89795
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89524
89796
|
if (category === "bash") return `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, which rulesync cannot import without widening the grant to every command, so it was not imported.`;
|
|
89525
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89797
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8) || isCatchAllSpecifier(inner)) return `Command Code ignores '${rule}' in "allow" (an allow rule must name what it grants), so it was not imported.`;
|
|
89526
89798
|
return `Command Code globs the specifier of '${rule}' in "allow" against the call's arguments, which rulesync cannot import without widening the grant to the whole tool, so it was not imported.`;
|
|
89527
89799
|
}
|
|
89528
89800
|
/**
|
|
@@ -89717,7 +89989,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
89717
89989
|
}
|
|
89718
89990
|
// src/features/permissions/continue-permissions.ts
|
|
89719
89991
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
89720
|
-
const CATCH_ALL_PATTERN$
|
|
89992
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
89721
89993
|
/**
|
|
89722
89994
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
89723
89995
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -89725,7 +89997,7 @@ const CATCH_ALL_PATTERN$11 = "*";
|
|
|
89725
89997
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
89726
89998
|
*/
|
|
89727
89999
|
function normalizeCatchAllPattern$1(pattern) {
|
|
89728
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90000
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$12 : pattern;
|
|
89729
90001
|
}
|
|
89730
90002
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
89731
90003
|
bash: "Bash",
|
|
@@ -89769,7 +90041,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
89769
90041
|
if (toolName === "") return;
|
|
89770
90042
|
return {
|
|
89771
90043
|
toolName,
|
|
89772
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
90044
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$12 : pattern
|
|
89773
90045
|
};
|
|
89774
90046
|
}
|
|
89775
90047
|
/**
|
|
@@ -89934,7 +90206,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
89934
90206
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89935
90207
|
continue;
|
|
89936
90208
|
}
|
|
89937
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
90209
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$12) {
|
|
89938
90210
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89939
90211
|
continue;
|
|
89940
90212
|
}
|
|
@@ -89990,13 +90262,116 @@ const AUTO_APPROVE_KEYS = {
|
|
|
89990
90262
|
webfetch: "chat.tools.urls.autoApprove"
|
|
89991
90263
|
};
|
|
89992
90264
|
/**
|
|
89993
|
-
* The workspace setting holding the starting permission level of new
|
|
89994
|
-
* sessions, authored through the
|
|
90265
|
+
* The workspace setting holding the starting permission level of new Local
|
|
90266
|
+
* (`local` harness) chat sessions, authored through the
|
|
90267
|
+
* `copilot.defaultPermissionMode` override. VS Code 1.144 narrowed it to Local
|
|
90268
|
+
* sessions; the Copilot harness reads {@link DEFAULT_CONFIGURATION_KEY}.
|
|
89995
90269
|
*
|
|
89996
90270
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
89997
90271
|
*/
|
|
89998
90272
|
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
89999
90273
|
/**
|
|
90274
|
+
* The workspace setting holding the starting `mode` (`interactive` / `plan` /
|
|
90275
|
+
* `autopilot`) and `approvals` (`manual` / `assisted` / `allowAll`) of new
|
|
90276
|
+
* agent sessions such as the Copilot harness, the default since VS Code 1.143.
|
|
90277
|
+
* It shipped in stable VS Code 1.144 as an object setting with
|
|
90278
|
+
* `additionalProperties: false`; older releases ignore it.
|
|
90279
|
+
*
|
|
90280
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
90281
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/common/constants.ts
|
|
90282
|
+
*/
|
|
90283
|
+
const DEFAULT_CONFIGURATION_KEY = "chat.defaultConfiguration";
|
|
90284
|
+
/**
|
|
90285
|
+
* What each `copilot.defaultPermissionMode` value writes. `local` goes to
|
|
90286
|
+
* `chat.permissions.default`; `approvals` and `autopilot` go to
|
|
90287
|
+
* `chat.defaultConfiguration`. The Local picker has no Assisted level, so
|
|
90288
|
+
* `assisted` starts Local sessions with Default Permissions — the stricter
|
|
90289
|
+
* fallback. Autopilot is a permission level in the Local picker but a `mode`
|
|
90290
|
+
* in `chat.defaultConfiguration`, where it runs with every tool call approved.
|
|
90291
|
+
*/
|
|
90292
|
+
const DEFAULT_PERMISSION_MODE_MAPPINGS = {
|
|
90293
|
+
default: {
|
|
90294
|
+
local: "default",
|
|
90295
|
+
approvals: "manual",
|
|
90296
|
+
autopilot: false
|
|
90297
|
+
},
|
|
90298
|
+
autoApprove: {
|
|
90299
|
+
local: "autoApprove",
|
|
90300
|
+
approvals: "allowAll",
|
|
90301
|
+
autopilot: false
|
|
90302
|
+
},
|
|
90303
|
+
autopilot: {
|
|
90304
|
+
local: "autopilot",
|
|
90305
|
+
approvals: "allowAll",
|
|
90306
|
+
autopilot: true
|
|
90307
|
+
},
|
|
90308
|
+
assisted: {
|
|
90309
|
+
local: "default",
|
|
90310
|
+
approvals: "assisted",
|
|
90311
|
+
autopilot: false
|
|
90312
|
+
}
|
|
90313
|
+
};
|
|
90314
|
+
/**
|
|
90315
|
+
* The `chat.defaultConfiguration` value for `mode`. Rulesync owns only whether
|
|
90316
|
+
* the mode is Autopilot: authoring Autopilot sets it, and any other value
|
|
90317
|
+
* removes any Autopilot mode in the file (whether an earlier run or a person
|
|
90318
|
+
* wrote it, since the two cannot be told apart), while an `interactive` or
|
|
90319
|
+
* `plan` mode is kept.
|
|
90320
|
+
*/
|
|
90321
|
+
function buildDefaultConfigurationValue({ defaultPermissionMode, existingSettings }) {
|
|
90322
|
+
const { approvals, autopilot } = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode];
|
|
90323
|
+
const existing = existingSettings[DEFAULT_CONFIGURATION_KEY];
|
|
90324
|
+
const value = { ...isPlainObject(existing) ? existing : {} };
|
|
90325
|
+
if (autopilot) value.mode = "autopilot";
|
|
90326
|
+
else if (value.mode === "autopilot") delete value.mode;
|
|
90327
|
+
value.approvals = approvals;
|
|
90328
|
+
return value;
|
|
90329
|
+
}
|
|
90330
|
+
/**
|
|
90331
|
+
* The `copilot.defaultPermissionMode` values from least to most permissive,
|
|
90332
|
+
* used to settle a file whose two keys disagree.
|
|
90333
|
+
*/
|
|
90334
|
+
const DEFAULT_PERMISSION_MODE_RANK = {
|
|
90335
|
+
default: 0,
|
|
90336
|
+
assisted: 1,
|
|
90337
|
+
autoApprove: 2,
|
|
90338
|
+
autopilot: 3
|
|
90339
|
+
};
|
|
90340
|
+
/**
|
|
90341
|
+
* The `copilot.defaultPermissionMode` a `chat.defaultConfiguration` value
|
|
90342
|
+
* stands for. Autopilot is recognized only together with `allowAll`, the pair
|
|
90343
|
+
* Rulesync writes for it: an Autopilot mode with stricter approvals is read
|
|
90344
|
+
* from `approvals` alone, so that regenerating never escalates it to Allow
|
|
90345
|
+
* All. An object without `approvals` is read as `default`, since VS Code then
|
|
90346
|
+
* starts sessions with Manual permissions.
|
|
90347
|
+
*/
|
|
90348
|
+
function readDefaultConfigurationMode(configuration) {
|
|
90349
|
+
if (!isPlainObject(configuration)) return;
|
|
90350
|
+
switch (configuration.approvals) {
|
|
90351
|
+
case void 0:
|
|
90352
|
+
case "manual": return "default";
|
|
90353
|
+
case "assisted": return "assisted";
|
|
90354
|
+
case "allowAll": return configuration.mode === "autopilot" ? "autopilot" : "autoApprove";
|
|
90355
|
+
default: return;
|
|
90356
|
+
}
|
|
90357
|
+
}
|
|
90358
|
+
/**
|
|
90359
|
+
* Read `copilot.defaultPermissionMode` back from the settings. When the two
|
|
90360
|
+
* keys agree — `chat.permissions.default` holds the Local level Rulesync
|
|
90361
|
+
* writes for the `chat.defaultConfiguration` value — the latter is returned,
|
|
90362
|
+
* since only it can say `assisted`. When they disagree, the less permissive of
|
|
90363
|
+
* the two is returned, so regenerating never raises either harness's starting
|
|
90364
|
+
* permissions. Either key alone is read as it stands.
|
|
90365
|
+
*/
|
|
90366
|
+
function readDefaultPermissionMode(settings) {
|
|
90367
|
+
const fromConfiguration = readDefaultConfigurationMode(settings[DEFAULT_CONFIGURATION_KEY]);
|
|
90368
|
+
const localValue = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
90369
|
+
const fromLocal = COPILOT_LOCAL_PERMISSION_LEVELS.find((level) => level === localValue);
|
|
90370
|
+
if (fromConfiguration === void 0 || fromLocal === void 0) return fromConfiguration ?? fromLocal;
|
|
90371
|
+
if (DEFAULT_PERMISSION_MODE_MAPPINGS[fromConfiguration].local === fromLocal) return fromConfiguration;
|
|
90372
|
+
return DEFAULT_PERMISSION_MODE_RANK[fromLocal] < DEFAULT_PERMISSION_MODE_RANK[fromConfiguration] ? fromLocal : fromConfiguration;
|
|
90373
|
+
}
|
|
90374
|
+
/**
|
|
90000
90375
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
90001
90376
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
90002
90377
|
*/
|
|
@@ -90183,9 +90558,6 @@ function readSandboxFromSettings(settings) {
|
|
|
90183
90558
|
}
|
|
90184
90559
|
return Object.keys(sandbox).length > 0 ? sandbox : void 0;
|
|
90185
90560
|
}
|
|
90186
|
-
function isCopilotDefaultPermissionMode(value) {
|
|
90187
|
-
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90188
|
-
}
|
|
90189
90561
|
function asAutoApproveMap(value) {
|
|
90190
90562
|
if (!isPlainObject(value)) return {};
|
|
90191
90563
|
const result = {};
|
|
@@ -90229,9 +90601,11 @@ function buildAutoApproveValue({ category, rules }) {
|
|
|
90229
90601
|
* `false`. A key whose canonical category is absent entirely
|
|
90230
90602
|
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
90231
90603
|
* hand-written edits or urls map.
|
|
90232
|
-
* The `copilot.defaultPermissionMode` override writes
|
|
90233
|
-
* (
|
|
90234
|
-
*
|
|
90604
|
+
* The `copilot.defaultPermissionMode` override writes both
|
|
90605
|
+
* `chat.permissions.default` (Local sessions) and `chat.defaultConfiguration`
|
|
90606
|
+
* (Copilot-harness sessions, VS Code 1.144+; see
|
|
90607
|
+
* {@link DEFAULT_PERMISSION_MODE_MAPPINGS}); without it both keys are left
|
|
90608
|
+
* untouched. The `copilot.sandbox` override writes the Agent Host sandbox
|
|
90235
90609
|
* settings (`chat.agent.sandbox.*`, see {@link SANDBOX_LEAVES}) per key, and
|
|
90236
90610
|
* warns about every value it writes that loosens the sandbox.
|
|
90237
90611
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
@@ -90282,17 +90656,24 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90282
90656
|
rules
|
|
90283
90657
|
});
|
|
90284
90658
|
}
|
|
90659
|
+
const existingSettings = () => parseSharedConfig({
|
|
90660
|
+
format: "jsonc",
|
|
90661
|
+
fileContent: existingContent,
|
|
90662
|
+
filePath
|
|
90663
|
+
});
|
|
90285
90664
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
90286
|
-
if (defaultPermissionMode !== void 0)
|
|
90665
|
+
if (defaultPermissionMode !== void 0) {
|
|
90666
|
+
patch[DEFAULT_PERMISSION_MODE_KEY$1] = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode].local;
|
|
90667
|
+
patch[DEFAULT_CONFIGURATION_KEY] = buildDefaultConfigurationValue({
|
|
90668
|
+
defaultPermissionMode,
|
|
90669
|
+
existingSettings: existingSettings()
|
|
90670
|
+
});
|
|
90671
|
+
}
|
|
90287
90672
|
const sandbox = config.copilot?.sandbox;
|
|
90288
90673
|
if (sandbox !== void 0) {
|
|
90289
90674
|
Object.assign(patch, buildSandboxPatch({
|
|
90290
90675
|
sandbox,
|
|
90291
|
-
existingSettings:
|
|
90292
|
-
format: "jsonc",
|
|
90293
|
-
fileContent: existingContent,
|
|
90294
|
-
filePath
|
|
90295
|
-
})
|
|
90676
|
+
existingSettings: existingSettings()
|
|
90296
90677
|
}));
|
|
90297
90678
|
warnOnTrustAffectingEntries({
|
|
90298
90679
|
toolLabel: COPILOT_TOOL_LABEL,
|
|
@@ -90339,10 +90720,10 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90339
90720
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
90340
90721
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
90341
90722
|
}
|
|
90342
|
-
const defaultPermissionMode = settings
|
|
90723
|
+
const defaultPermissionMode = readDefaultPermissionMode(settings);
|
|
90343
90724
|
const sandbox = readSandboxFromSettings(settings);
|
|
90344
90725
|
const copilot = {
|
|
90345
|
-
...
|
|
90726
|
+
...defaultPermissionMode !== void 0 && { defaultPermissionMode },
|
|
90346
90727
|
...sandbox !== void 0 && { sandbox }
|
|
90347
90728
|
};
|
|
90348
90729
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
@@ -90540,14 +90921,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
90540
90921
|
}
|
|
90541
90922
|
};
|
|
90542
90923
|
// src/features/permissions/crush-permissions.ts
|
|
90543
|
-
const CATCH_ALL_PATTERN$
|
|
90924
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
90544
90925
|
/**
|
|
90545
90926
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
90546
90927
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
90547
90928
|
* still be written.
|
|
90548
90929
|
*/
|
|
90549
90930
|
function normalizeCatchAllPattern(pattern) {
|
|
90550
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90931
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$11 : pattern;
|
|
90551
90932
|
}
|
|
90552
90933
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
90553
90934
|
bash: "bash",
|
|
@@ -90559,11 +90940,11 @@ const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
|
90559
90940
|
webfetch: "fetch"
|
|
90560
90941
|
};
|
|
90561
90942
|
const CRUSH_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CRUSH_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
90562
|
-
const MCP_CANONICAL_PREFIX$
|
|
90943
|
+
const MCP_CANONICAL_PREFIX$7 = "mcp__";
|
|
90563
90944
|
const MCP_CRUSH_PREFIX = "mcp_";
|
|
90564
90945
|
function toCrushToolName(canonical) {
|
|
90565
90946
|
if (Object.hasOwn(CANONICAL_TO_CRUSH_TOOL_NAMES, canonical)) return CANONICAL_TO_CRUSH_TOOL_NAMES[canonical] ?? canonical;
|
|
90566
|
-
if (canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
90947
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX$7) && canonical.length > 5) return `${MCP_CRUSH_PREFIX}${canonical.slice(5).replaceAll("__", "_")}`;
|
|
90567
90948
|
return canonical;
|
|
90568
90949
|
}
|
|
90569
90950
|
function toCanonicalToolName$5(crushName) {
|
|
@@ -90737,7 +91118,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
90737
91118
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
90738
91119
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
90739
91120
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
90740
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
91121
|
+
if (pattern === CATCH_ALL_PATTERN$11) {
|
|
90741
91122
|
catchAllAction = action;
|
|
90742
91123
|
continue;
|
|
90743
91124
|
}
|
|
@@ -90810,11 +91191,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
90810
91191
|
};
|
|
90811
91192
|
for (const entry of disabled) {
|
|
90812
91193
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
90813
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91194
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] = "deny";
|
|
90814
91195
|
}
|
|
90815
91196
|
for (const entry of allowed) {
|
|
90816
91197
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
90817
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91198
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] ??= "allow";
|
|
90818
91199
|
}
|
|
90819
91200
|
return { permission };
|
|
90820
91201
|
}
|
|
@@ -90860,13 +91241,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
90860
91241
|
WebFetch: "webfetch",
|
|
90861
91242
|
Mcp: "mcp"
|
|
90862
91243
|
};
|
|
90863
|
-
const MCP_CANONICAL_PREFIX$
|
|
91244
|
+
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
90864
91245
|
/**
|
|
90865
91246
|
* Returns true if the canonical category is the per-tool MCP form
|
|
90866
91247
|
* `mcp__<server>__<tool>`.
|
|
90867
91248
|
*/
|
|
90868
91249
|
function isMcpScopedCategory(canonical) {
|
|
90869
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91250
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$6) && canonical.length > 5;
|
|
90870
91251
|
}
|
|
90871
91252
|
function toCursorType(canonical) {
|
|
90872
91253
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -90900,7 +91281,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
90900
91281
|
if (match) {
|
|
90901
91282
|
const server = match[1] ?? "*";
|
|
90902
91283
|
const tool = match[2] ?? "*";
|
|
90903
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
91284
|
+
return `${MCP_CANONICAL_PREFIX$6}${server}__${tool}`;
|
|
90904
91285
|
}
|
|
90905
91286
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
90906
91287
|
}
|
|
@@ -91162,7 +91543,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
91162
91543
|
const { toolName: type, pattern } = parseToolPatternEntry(entry);
|
|
91163
91544
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
91164
91545
|
if (!permission[canonical]) permission[canonical] = {};
|
|
91165
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91546
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$6) ? "*" : pattern;
|
|
91166
91547
|
permission[canonical][canonicalPattern] = action;
|
|
91167
91548
|
}
|
|
91168
91549
|
};
|
|
@@ -92661,7 +93042,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
92661
93042
|
// src/features/permissions/goose-permissions.ts
|
|
92662
93043
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
92663
93044
|
const GOOSE_USER_KEY = "user";
|
|
92664
|
-
const CATCH_ALL_PATTERN$
|
|
93045
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
92665
93046
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
92666
93047
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
92667
93048
|
shell: ["bash"],
|
|
@@ -92810,7 +93191,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
92810
93191
|
const assigned = /* @__PURE__ */ new Map();
|
|
92811
93192
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
92812
93193
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
92813
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$
|
|
93194
|
+
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$10) logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
92814
93195
|
const { action, downgraded } = failSafeCatchAllAction({
|
|
92815
93196
|
rules,
|
|
92816
93197
|
allToolsRules: category === "*" ? void 0 : config.permission["*"]
|
|
@@ -92843,7 +93224,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
92843
93224
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
92844
93225
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
92845
93226
|
permission[category] ??= {};
|
|
92846
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
93227
|
+
permission[category][CATCH_ALL_PATTERN$10] = action;
|
|
92847
93228
|
}
|
|
92848
93229
|
}
|
|
92849
93230
|
return { permission };
|
|
@@ -92853,8 +93234,8 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
92853
93234
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
92854
93235
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
92855
93236
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
92856
|
-
const CATCH_ALL_PATTERN$
|
|
92857
|
-
const MCP_CANONICAL_PREFIX$
|
|
93237
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
93238
|
+
const MCP_CANONICAL_PREFIX$5 = "mcp__";
|
|
92858
93239
|
const CATEGORY_TO_GROK_TOOL = {
|
|
92859
93240
|
bash: "Bash",
|
|
92860
93241
|
read: "Read",
|
|
@@ -92886,6 +93267,25 @@ const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
|
92886
93267
|
};
|
|
92887
93268
|
const GROK_MCP_TOOL_ALIASES = /* @__PURE__ */ new Set(["mcp", GROK_MCP_TOOL.toLowerCase()]);
|
|
92888
93269
|
const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
93270
|
+
const GROK_PATH_PATTERN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
93271
|
+
"read",
|
|
93272
|
+
"edit",
|
|
93273
|
+
"write",
|
|
93274
|
+
"grep",
|
|
93275
|
+
"glob"
|
|
93276
|
+
]);
|
|
93277
|
+
const CLAUDE_ABSOLUTE_PATH_PREFIX = "//";
|
|
93278
|
+
/**
|
|
93279
|
+
* Rewrite a canonical path pattern into the anchor Grok understands. A
|
|
93280
|
+
* Claude-style `//abs` absolute pattern becomes `/abs`; without this the rule
|
|
93281
|
+
* would match nothing on Grok, so a `deny` or `ask` rule would silently give
|
|
93282
|
+
* no protection. Every other pattern is returned unchanged.
|
|
93283
|
+
*/
|
|
93284
|
+
function toGrokPathPattern(category, pattern) {
|
|
93285
|
+
if (!GROK_PATH_PATTERN_CATEGORIES.has(category)) return pattern;
|
|
93286
|
+
if (!pattern.startsWith(CLAUDE_ABSOLUTE_PATH_PREFIX)) return pattern;
|
|
93287
|
+
return `/${pattern.replace(/^\/+/, "")}`;
|
|
93288
|
+
}
|
|
92889
93289
|
/**
|
|
92890
93290
|
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
92891
93291
|
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
@@ -92896,18 +93296,20 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
92896
93296
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
92897
93297
|
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
92898
93298
|
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
92899
|
-
* under it has no Grok spelling and returns `null`.
|
|
93299
|
+
* under it has no Grok spelling and returns `null`. A Claude-style `//abs`
|
|
93300
|
+
* path pattern on a file tool is rewritten to Grok's `/abs` (see
|
|
93301
|
+
* `toGrokPathPattern`).
|
|
92900
93302
|
*/
|
|
92901
93303
|
function buildGrokEntry(category, pattern) {
|
|
92902
|
-
if (category === "*") return pattern === CATCH_ALL_PATTERN$
|
|
92903
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
93304
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$9 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
93305
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$5)) {
|
|
92904
93306
|
const remainder = category.slice(5);
|
|
92905
93307
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
92906
93308
|
}
|
|
92907
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
93309
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
92908
93310
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
92909
93311
|
if (tool === void 0) return null;
|
|
92910
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
93312
|
+
return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
|
|
92911
93313
|
}
|
|
92912
93314
|
/**
|
|
92913
93315
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -92929,21 +93331,21 @@ function parseGrokEntry(entry) {
|
|
|
92929
93331
|
}
|
|
92930
93332
|
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
92931
93333
|
category: "*",
|
|
92932
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93334
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92933
93335
|
};
|
|
92934
|
-
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
93336
|
+
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$5)) return parseClaudeStyleMcpEntry(tool);
|
|
92935
93337
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
92936
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92937
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93338
|
+
category: `${MCP_CANONICAL_PREFIX$5}${inner}`,
|
|
93339
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92938
93340
|
} : {
|
|
92939
93341
|
category: "mcp",
|
|
92940
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93342
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92941
93343
|
};
|
|
92942
93344
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
92943
93345
|
if (category === void 0) return null;
|
|
92944
93346
|
return {
|
|
92945
93347
|
category,
|
|
92946
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
93348
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$9
|
|
92947
93349
|
};
|
|
92948
93350
|
}
|
|
92949
93351
|
/**
|
|
@@ -92960,14 +93362,14 @@ function parseGrokEntry(entry) {
|
|
|
92960
93362
|
function parseClaudeStyleMcpEntry(entry) {
|
|
92961
93363
|
const rest = entry.slice(5);
|
|
92962
93364
|
if (rest.length === 0) return null;
|
|
92963
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
93365
|
+
if (rest === CATCH_ALL_PATTERN$9) return {
|
|
92964
93366
|
category: "mcp",
|
|
92965
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93367
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92966
93368
|
};
|
|
92967
93369
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
92968
93370
|
return {
|
|
92969
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92970
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93371
|
+
category: `${MCP_CANONICAL_PREFIX$5}${address}`,
|
|
93372
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92971
93373
|
};
|
|
92972
93374
|
}
|
|
92973
93375
|
/**
|
|
@@ -92988,20 +93390,20 @@ function parseGrokRule(rule) {
|
|
|
92988
93390
|
if (typeof tool !== "string") return null;
|
|
92989
93391
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
92990
93392
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
92991
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
93393
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$9;
|
|
92992
93394
|
const lowerTool = tool.trim().toLowerCase();
|
|
92993
|
-
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93395
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
92994
93396
|
category: "*",
|
|
92995
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93397
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
92996
93398
|
action
|
|
92997
93399
|
} : null;
|
|
92998
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93400
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
92999
93401
|
category: "mcp",
|
|
93000
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93402
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93001
93403
|
action
|
|
93002
93404
|
} : {
|
|
93003
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
93004
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93405
|
+
category: `${MCP_CANONICAL_PREFIX$5}${resolvedPattern}`,
|
|
93406
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93005
93407
|
action
|
|
93006
93408
|
};
|
|
93007
93409
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -93151,7 +93553,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
93151
93553
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
93152
93554
|
}
|
|
93153
93555
|
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
93154
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
93556
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$9]: legacyModeAction(parsed) } } };
|
|
93155
93557
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
93156
93558
|
}
|
|
93157
93559
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
@@ -93336,6 +93738,195 @@ function ensureCategory(permission, category) {
|
|
|
93336
93738
|
function removeEmptyCategories(permission) {
|
|
93337
93739
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
93338
93740
|
}
|
|
93741
|
+
/** The prefix Hermes's MCP trust gate keys its persisted "Always" answers with. */
|
|
93742
|
+
const MCP_ALLOWLIST_PREFIX = "mcp:";
|
|
93743
|
+
const MCP_CANONICAL_PREFIX$4 = "mcp__";
|
|
93744
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
93745
|
+
/**
|
|
93746
|
+
* Whether a `command_allowlist` entry is an MCP trust-gate grant rather than a
|
|
93747
|
+
* shell-command pattern. Since Hermes Agent v0.22.6, answering **Always** to
|
|
93748
|
+
* the approval prompt of a `trust: untrusted` MCP server appends
|
|
93749
|
+
* `mcp:<server>/<tool>` to `command_allowlist` (`_trust_approval_key` in
|
|
93750
|
+
* `tools/mcp_tool_handlers.py`), and the gate looks the key up by exact set
|
|
93751
|
+
* membership, so no `mcp:` entry is ever a command.
|
|
93752
|
+
*/
|
|
93753
|
+
function isMcpAllowlistEntry(entry) {
|
|
93754
|
+
return entry.startsWith(MCP_ALLOWLIST_PREFIX);
|
|
93755
|
+
}
|
|
93756
|
+
/**
|
|
93757
|
+
* The canonical category (`mcp__<server>__<tool>`) a Hermes `mcp:<server>/<tool>`
|
|
93758
|
+
* grant stands for, or `null` when it has none: a malformed entry, a server
|
|
93759
|
+
* name containing `__` (the canonical name would read back as another server),
|
|
93760
|
+
* or a `*` in either name (Hermes compares the key literally, while a canonical
|
|
93761
|
+
* `*` is a wildcard).
|
|
93762
|
+
*/
|
|
93763
|
+
function mcpAllowlistEntryToCategory(entry) {
|
|
93764
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93765
|
+
if (parsed === null) return null;
|
|
93766
|
+
const { server, tool } = parsed;
|
|
93767
|
+
if (server.includes("__") || server.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93768
|
+
if (tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93769
|
+
return `${MCP_CANONICAL_PREFIX$4}${server}__${tool}`;
|
|
93770
|
+
}
|
|
93771
|
+
/** The server and tool an `mcp:<server>/<tool>` entry names, or `null` when it is malformed. */
|
|
93772
|
+
function parseMcpAllowlistEntry(entry) {
|
|
93773
|
+
if (!isMcpAllowlistEntry(entry)) return null;
|
|
93774
|
+
const rest = entry.slice(4);
|
|
93775
|
+
const slash = rest.indexOf("/");
|
|
93776
|
+
const server = slash === -1 ? "" : rest.slice(0, slash);
|
|
93777
|
+
const tool = slash === -1 ? "" : rest.slice(slash + 1);
|
|
93778
|
+
return server.length === 0 || tool.length === 0 ? null : {
|
|
93779
|
+
server,
|
|
93780
|
+
tool
|
|
93781
|
+
};
|
|
93782
|
+
}
|
|
93783
|
+
/**
|
|
93784
|
+
* The server and tool a canonical `mcp__<server>__<tool>` category names, or
|
|
93785
|
+
* `null` when it names no single tool (a whole server, a wildcard, or the bare
|
|
93786
|
+
* `mcp` category). Only such a category can become one Hermes grant.
|
|
93787
|
+
*/
|
|
93788
|
+
function parseMcpToolCategory(category) {
|
|
93789
|
+
if (!category.startsWith(MCP_CANONICAL_PREFIX$4)) return null;
|
|
93790
|
+
const remainder = category.slice(5);
|
|
93791
|
+
const separator = remainder.indexOf("__");
|
|
93792
|
+
if (separator <= 0) return null;
|
|
93793
|
+
const server = remainder.slice(0, separator);
|
|
93794
|
+
const tool = remainder.slice(separator + 2);
|
|
93795
|
+
if (tool.length === 0 || server.includes("/") || server.includes(CATCH_ALL_PATTERN$8) || tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
93796
|
+
return {
|
|
93797
|
+
server,
|
|
93798
|
+
tool
|
|
93799
|
+
};
|
|
93800
|
+
}
|
|
93801
|
+
function isMcpCategory$1(category) {
|
|
93802
|
+
return category === "mcp" || category.startsWith(MCP_CANONICAL_PREFIX$4);
|
|
93803
|
+
}
|
|
93804
|
+
function isRestricting(action) {
|
|
93805
|
+
return action === "deny" || action === "ask";
|
|
93806
|
+
}
|
|
93807
|
+
/** The first `deny` or `ask` rule of a category, as `<category>: <pattern> (<action>)`. */
|
|
93808
|
+
function firstRestrictingRule(category, rules, covers = () => true) {
|
|
93809
|
+
const found = Object.entries(rules ?? {}).find(([pattern, action]) => isRestricting(action) && covers(pattern));
|
|
93810
|
+
return found ? `${category}: ${found[0]} (${found[1]})` : null;
|
|
93811
|
+
}
|
|
93812
|
+
/**
|
|
93813
|
+
* Whether an MCP name glob (the remainder of an `mcp__...` category, or a
|
|
93814
|
+
* pattern of the bare `mcp` category) covers `server`'s tool `tool`: it names
|
|
93815
|
+
* the tool (`github__delete_repo`, `github__*`, `*__delete_repo`, `*`) or the
|
|
93816
|
+
* whole server (`github`).
|
|
93817
|
+
*/
|
|
93818
|
+
function mcpNameGlobCovers(glob, server, tool) {
|
|
93819
|
+
return glob === `${server}__${tool}` || glob === server || matchesGlob(glob, `${server}__${tool}`) || matchesGlob(glob, server);
|
|
93820
|
+
}
|
|
93821
|
+
/**
|
|
93822
|
+
* The `deny` or `ask` rule that outranks an allow on `server`'s tool `tool`, or
|
|
93823
|
+
* `null` when none does: any one under the all-tools `*` category (a rule there
|
|
93824
|
+
* applies to every tool, so even a narrower pattern restricts some of the
|
|
93825
|
+
* tool's calls), one under the bare `mcp` category whose pattern covers the
|
|
93826
|
+
* tool, or any one in an `mcp__...` category whose name covers the tool — its
|
|
93827
|
+
* own category included, since a deny or ask scoped to some of its calls still
|
|
93828
|
+
* forbids approving all of them. A Hermes grant approves every call of the
|
|
93829
|
+
* tool and Hermes has no ask tier, so such an allow is withheld, the same way
|
|
93830
|
+
* a `bash` allow a stricter rule covers is.
|
|
93831
|
+
*/
|
|
93832
|
+
function findMcpToolRestriction(permission, server, tool) {
|
|
93833
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93834
|
+
let found = null;
|
|
93835
|
+
if (category === "*") found = firstRestrictingRule(category, rules);
|
|
93836
|
+
else if (category === "mcp") found = firstRestrictingRule(category, rules, (pattern) => mcpNameGlobCovers(pattern, server, tool));
|
|
93837
|
+
else if (category.startsWith(MCP_CANONICAL_PREFIX$4) && mcpNameGlobCovers(category.slice(5), server, tool)) found = firstRestrictingRule(category, rules);
|
|
93838
|
+
if (found !== null) return found;
|
|
93839
|
+
}
|
|
93840
|
+
return null;
|
|
93841
|
+
}
|
|
93842
|
+
function isMcpToolRestricted(permission, server, tool) {
|
|
93843
|
+
return findMcpToolRestriction(permission, server, tool) !== null;
|
|
93844
|
+
}
|
|
93845
|
+
/**
|
|
93846
|
+
* Translate the canonical MCP `allow` rules into Hermes trust-gate grants: a
|
|
93847
|
+
* `*` allow on `mcp__<server>__<tool>` becomes `mcp:<server>/<tool>`. Every
|
|
93848
|
+
* other MCP allow (a whole server, a wildcard, a narrower pattern) has no
|
|
93849
|
+
* Hermes spelling, and an allow a deny or ask covers is withheld.
|
|
93850
|
+
*/
|
|
93851
|
+
function collectMcpAllowlistEntries(permission) {
|
|
93852
|
+
const entries = [];
|
|
93853
|
+
const withheldCategories = [];
|
|
93854
|
+
const unexpressedRules = [];
|
|
93855
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93856
|
+
if (!isMcpCategory$1(category)) continue;
|
|
93857
|
+
const parsed = parseMcpToolCategory(category);
|
|
93858
|
+
for (const pattern of patternsByAction(rules, "allow")) if (parsed === null || pattern !== CATCH_ALL_PATTERN$8) unexpressedRules.push(`${category}: ${pattern}`);
|
|
93859
|
+
else {
|
|
93860
|
+
const restriction = findMcpToolRestriction(permission, parsed.server, parsed.tool);
|
|
93861
|
+
if (restriction === null) entries.push(`${MCP_ALLOWLIST_PREFIX}${parsed.server}/${parsed.tool}`);
|
|
93862
|
+
else withheldCategories.push(`${category} (covered by ${restriction})`);
|
|
93863
|
+
}
|
|
93864
|
+
}
|
|
93865
|
+
return {
|
|
93866
|
+
entries,
|
|
93867
|
+
withheldCategories,
|
|
93868
|
+
unexpressedRules
|
|
93869
|
+
};
|
|
93870
|
+
}
|
|
93871
|
+
function warnAboutUnwrittenMcpAllows({ withheldCategories, unexpressedRules, logger }) {
|
|
93872
|
+
if (withheldCategories.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist was not given the MCP allow rule(s) for ${withheldCategories.join(", ")}: a deny or ask rule covers each, a Hermes grant approves every call of the tool, and Hermes has no ask tier.`);
|
|
93873
|
+
if (unexpressedRules.length > 0) warnWithFallback(logger, `Hermes Agent grants MCP tools one at a time (command_allowlist entries 'mcp:<server>/<tool>'), so only a '*' allow on an 'mcp__<server>__<tool>' category can be written; the MCP allow rule(s) ${unexpressedRules.join(", ")} were skipped and survive only in the permissions.rulesync round-trip block.`);
|
|
93874
|
+
}
|
|
93875
|
+
/**
|
|
93876
|
+
* Make the native `mcp:<server>/<tool>` grants authoritative for the canonical
|
|
93877
|
+
* single-tool MCP allows they can speak for. An allow the generator withheld
|
|
93878
|
+
* because a deny or ask covers it was never written, so its absence
|
|
93879
|
+
* is not a retraction and it is kept, as is every MCP allow with no Hermes
|
|
93880
|
+
* spelling. A grant with no canonical spelling stays only in Hermes, where
|
|
93881
|
+
* generation keeps it (see `setFileContent`).
|
|
93882
|
+
*/
|
|
93883
|
+
function reconcileMcpAllowlist({ permission, mcpEntries, logger }) {
|
|
93884
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
93885
|
+
const parsed = parseMcpToolCategory(category);
|
|
93886
|
+
if (parsed !== null && rules[CATCH_ALL_PATTERN$8] === "allow" && !isMcpToolRestricted(permission, parsed.server, parsed.tool)) delete rules[CATCH_ALL_PATTERN$8];
|
|
93887
|
+
}
|
|
93888
|
+
const keptRestrictions = [];
|
|
93889
|
+
for (const entry of mcpEntries) {
|
|
93890
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93891
|
+
if (category === null) continue;
|
|
93892
|
+
const rules = ensureCategory(permission, category);
|
|
93893
|
+
if (isRestricting(rules[CATCH_ALL_PATTERN$8])) {
|
|
93894
|
+
keptRestrictions.push(entry);
|
|
93895
|
+
continue;
|
|
93896
|
+
}
|
|
93897
|
+
rules[CATCH_ALL_PATTERN$8] = "allow";
|
|
93898
|
+
}
|
|
93899
|
+
if (keptRestrictions.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist grants ${keptRestrictions.join(", ")}, but the permissions.rulesync block denies or asks for those MCP tools; the canonical rule was kept and the grant was not imported.`);
|
|
93900
|
+
}
|
|
93901
|
+
/**
|
|
93902
|
+
* The `mcp:` grants in an existing `command_allowlist` that rulesync does not
|
|
93903
|
+
* manage and must keep when it rewrites the list: Hermes appends one whenever
|
|
93904
|
+
* the user answers **Always** on an untrusted MCP server, so dropping it would
|
|
93905
|
+
* silently revoke the grant. A grant is managed — and so retracted with its
|
|
93906
|
+
* canonical rule — when the previous or the new canonical block has a rule
|
|
93907
|
+
* for its `mcp__<server>__<tool>` category, or when the new block restricts
|
|
93908
|
+
* the tool in any way.
|
|
93909
|
+
*/
|
|
93910
|
+
function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermission, nextAllowlist }) {
|
|
93911
|
+
return (isStringArray(existing.command_allowlist) ? existing.command_allowlist : []).filter((entry) => {
|
|
93912
|
+
if (!isMcpAllowlistEntry(entry) || nextAllowlist.includes(entry)) return false;
|
|
93913
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
93914
|
+
if (parsed !== null && isMcpToolRestricted(nextPermission, parsed.server, parsed.tool)) return false;
|
|
93915
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
93916
|
+
return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
|
|
93917
|
+
});
|
|
93918
|
+
}
|
|
93919
|
+
/** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
|
|
93920
|
+
function canonicalPermissionOf(config) {
|
|
93921
|
+
const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
|
|
93922
|
+
return parsed.success ? parsed.data.permission : {};
|
|
93923
|
+
}
|
|
93924
|
+
/** The canonical `permission` block of a config's `permissions.rulesync` blob, if any. */
|
|
93925
|
+
function provenancePermissionOf(config) {
|
|
93926
|
+
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
93927
|
+
const provenance = isRecord(permissionsRoot.rulesync) ? permissionsRoot.rulesync : {};
|
|
93928
|
+
return isRecord(provenance.permission) ? provenance.permission : {};
|
|
93929
|
+
}
|
|
93339
93930
|
/**
|
|
93340
93931
|
* Make the native `command_allowlist` authoritative for the `bash` allow rules
|
|
93341
93932
|
* it can speak for, and for those only. The allowlist is a list of shell-command
|
|
@@ -93353,8 +93944,9 @@ function reconcileCommandAllowlist({ permission, commandAllowlist }) {
|
|
|
93353
93944
|
writesAllToolsDeny: false
|
|
93354
93945
|
});
|
|
93355
93946
|
const withheld = new Set(shadowedAllowPatterns);
|
|
93947
|
+
const isWithheld = (pattern) => withheld.has(pattern) || isMcpAllowlistEntry(pattern);
|
|
93356
93948
|
const rules = ensureCategory(permission, SHELL_PERMISSION_CATEGORY);
|
|
93357
|
-
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !
|
|
93949
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !isWithheld(pattern)) delete rules[pattern];
|
|
93358
93950
|
for (const pattern of commandAllowlist) rules[pattern] = "allow";
|
|
93359
93951
|
}
|
|
93360
93952
|
/**
|
|
@@ -93442,17 +94034,31 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93442
94034
|
return true;
|
|
93443
94035
|
}
|
|
93444
94036
|
setFileContent(fileContent) {
|
|
94037
|
+
const patch = parseSharedConfig({
|
|
94038
|
+
format: "yaml",
|
|
94039
|
+
fileContent: this.fileContent
|
|
94040
|
+
});
|
|
94041
|
+
if (isStringArray(patch.command_allowlist)) {
|
|
94042
|
+
const existing = parseSharedConfig({
|
|
94043
|
+
format: "yaml",
|
|
94044
|
+
fileContent
|
|
94045
|
+
});
|
|
94046
|
+
const kept = unmanagedMcpAllowlistEntries({
|
|
94047
|
+
existing,
|
|
94048
|
+
previousPermission: provenancePermissionOf(existing),
|
|
94049
|
+
nextPermission: canonicalPermissionOf(patch),
|
|
94050
|
+
nextAllowlist: patch.command_allowlist
|
|
94051
|
+
});
|
|
94052
|
+
if (kept.length > 0) patch.command_allowlist = [...patch.command_allowlist, ...kept];
|
|
94053
|
+
}
|
|
93445
94054
|
this.fileContent = applySharedConfigPatch({
|
|
93446
94055
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
93447
94056
|
feature: "permissions",
|
|
93448
94057
|
existingContent: fileContent,
|
|
93449
|
-
patch
|
|
93450
|
-
format: "yaml",
|
|
93451
|
-
fileContent: this.fileContent
|
|
93452
|
-
})
|
|
94058
|
+
patch
|
|
93453
94059
|
});
|
|
93454
94060
|
}
|
|
93455
|
-
toRulesyncPermissions() {
|
|
94061
|
+
toRulesyncPermissions({ logger } = {}) {
|
|
93456
94062
|
const config = parseSharedConfig({
|
|
93457
94063
|
format: "yaml",
|
|
93458
94064
|
fileContent: this.getFileContent()
|
|
@@ -93464,9 +94070,15 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93464
94070
|
}) : rawProvenance);
|
|
93465
94071
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
93466
94072
|
const permission = clonePermissionBlock(provenance.permission);
|
|
94073
|
+
const commandAllowlist = isStringArray(config.command_allowlist) ? config.command_allowlist : [];
|
|
93467
94074
|
reconcileCommandAllowlist({
|
|
93468
94075
|
permission,
|
|
93469
|
-
commandAllowlist:
|
|
94076
|
+
commandAllowlist: commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry))
|
|
94077
|
+
});
|
|
94078
|
+
reconcileMcpAllowlist({
|
|
94079
|
+
permission,
|
|
94080
|
+
mcpEntries: commandAllowlist.filter(isMcpAllowlistEntry),
|
|
94081
|
+
logger
|
|
93470
94082
|
});
|
|
93471
94083
|
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
93472
94084
|
reconcileNativeDenies({
|
|
@@ -93524,8 +94136,16 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93524
94136
|
logger
|
|
93525
94137
|
});
|
|
93526
94138
|
const webfetchDeny = patternsByAction(permissionBlock[WEBFETCH_PERMISSION_CATEGORY], "deny");
|
|
94139
|
+
const mcpAllows = collectMcpAllowlistEntries(permissionBlock);
|
|
94140
|
+
warnAboutUnwrittenMcpAllows({
|
|
94141
|
+
...mcpAllows,
|
|
94142
|
+
logger
|
|
94143
|
+
});
|
|
94144
|
+
const bashMcpAllows = commandAllowlist.filter(isMcpAllowlistEntry);
|
|
94145
|
+
if (bashMcpAllows.length > 0) warnWithFallback(logger, `Hermes Agent reads 'mcp:' entries in command_allowlist as MCP tool grants, so the bash allow rule(s) ${bashMcpAllows.join(", ")} were skipped; write them as a '*' allow on an 'mcp__<server>__<tool>' category instead.`);
|
|
94146
|
+
const allowlist = [...commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry)), ...mcpAllows.entries];
|
|
93527
94147
|
let config = {};
|
|
93528
|
-
if (
|
|
94148
|
+
if (allowlist.length > 0) config.command_allowlist = allowlist;
|
|
93529
94149
|
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
93530
94150
|
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
93531
94151
|
enabled: true,
|
|
@@ -96010,7 +96630,7 @@ var OpencodePermissions = class extends ToolPermissions {
|
|
|
96010
96630
|
* `.mimocode/` layout. Tool-only categories round-trip through the `mimocode`
|
|
96011
96631
|
* override block.
|
|
96012
96632
|
*
|
|
96013
|
-
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/
|
|
96633
|
+
* @see https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/cli/src/config/permission.ts
|
|
96014
96634
|
*/
|
|
96015
96635
|
var MimocodePermissions = class extends OpencodePermissions {
|
|
96016
96636
|
static layout = MIMOCODE_LAYOUT;
|
|
@@ -100366,6 +100986,7 @@ const TAKT_SECURITY_POLICIES = {
|
|
|
100366
100986
|
],
|
|
100367
100987
|
workflow_runtime_prepare: ["custom_scripts"],
|
|
100368
100988
|
workflow_command_gates: ["custom_scripts"],
|
|
100989
|
+
workflow_system_commands: ["custom_scripts"],
|
|
100369
100990
|
sync_conflict_resolver: ["auto_approve_tools"],
|
|
100370
100991
|
allow_git_hooks: null,
|
|
100371
100992
|
allow_git_filters: null
|