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