rulesync 16.22.1 → 16.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-B7o8So93.cjs → import-C0WSCbU9.cjs} +848 -130
- package/dist/{import-DUE1P1zV.js → import-DyWRanf6.js} +849 -131
- package/dist/import-DyWRanf6.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-DUE1P1zV.js.map +0 -1
|
@@ -6633,10 +6633,25 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
6633
6633
|
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
6634
6634
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
6635
6635
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
6636
|
-
* `sandbox`, `sandboxImage`, `disabled
|
|
6637
|
-
* `
|
|
6638
|
-
* Qwen Code
|
|
6639
|
-
*
|
|
6636
|
+
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `listDirectory`,
|
|
6637
|
+
* `workflowsEnabled`) and `security` (`folderTrust`, `allowedHttpHookUrls`,
|
|
6638
|
+
* `allowPrivateNetworkHooks`, `allowedInsecureVoiceBaseUrls`). Qwen Code strips
|
|
6639
|
+
* `tools.workflowsEnabled`, `security.allowPrivateNetworkHooks` and
|
|
6640
|
+
* `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
|
|
6641
|
+
* skips those three in project scope and announces a granting value in global
|
|
6642
|
+
* scope. `security.allowedHttpHookUrls` (honored in a workspace only while no
|
|
6643
|
+
* higher scope sets it) and `security.folderTrust` (the initial trust decision
|
|
6644
|
+
* is made from user/system settings alone, before the workspace merge) are
|
|
6645
|
+
* written in both scopes, with a note in project scope and an announcement of
|
|
6646
|
+
* any global change. Every other key is honored in either scope, so a write that
|
|
6647
|
+
* changes what the file said is reported in either scope, naming what that key
|
|
6648
|
+
* decides there — the autonomy and containment
|
|
6649
|
+
* controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
|
|
6650
|
+
* registry controls (`disabled`, `visible`, `listDirectory`), the Auto Mode
|
|
6651
|
+
* classifier config, and, because these groups are loose objects, any key
|
|
6652
|
+
* rulesync does not model. Import flags a scope-dependent key only when it read
|
|
6653
|
+
* the project file, since that is the value a `--global` regenerate would
|
|
6654
|
+
* promote. It also
|
|
6640
6655
|
* exposes `permissions.autoMode` (the Auto Mode classifier config:
|
|
6641
6656
|
* `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
|
|
6642
6657
|
* https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
|
|
@@ -6674,6 +6689,8 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
|
6674
6689
|
*
|
|
6675
6690
|
* @example
|
|
6676
6691
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
6692
|
+
* @example
|
|
6693
|
+
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
6677
6694
|
*/
|
|
6678
6695
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
6679
6696
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
@@ -6681,7 +6698,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
6681
6698
|
agent: z.optional(z.looseObject({})),
|
|
6682
6699
|
rawAllow: z.optional(z.array(z.string())),
|
|
6683
6700
|
rawAsk: z.optional(z.array(z.string())),
|
|
6684
|
-
rawDeny: z.optional(z.array(z.string()))
|
|
6701
|
+
rawDeny: z.optional(z.array(z.string())),
|
|
6702
|
+
allowDynamicBash: z.optional(z.boolean())
|
|
6685
6703
|
});
|
|
6686
6704
|
/**
|
|
6687
6705
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -7325,6 +7343,32 @@ const ZedPermissionsOverrideSchema = z.looseObject({
|
|
|
7325
7343
|
})))
|
|
7326
7344
|
});
|
|
7327
7345
|
/**
|
|
7346
|
+
* Tool-scoped override block for Devin Local. `sandbox` is the sibling
|
|
7347
|
+
* top-level `config.json` block that governs the sandbox Devin runs commands
|
|
7348
|
+
* in: `allowed_domains` / `denied_domains` (proxy domain patterns, deny beating
|
|
7349
|
+
* allow), `network_mode` (`full`, the upstream default, allows every HTTP
|
|
7350
|
+
* method; `limited` only GET/HEAD/OPTIONS) and `excluded` (`allow` / `ask` /
|
|
7351
|
+
* `deny` lists of `Exec(...)` matchers deciding which commands run *outside* the
|
|
7352
|
+
* sandbox — `deny` pins them inside it). It constrains how
|
|
7353
|
+
* a permitted command runs rather than which commands are permitted, so it has
|
|
7354
|
+
* no canonical category and is authored here.
|
|
7355
|
+
*
|
|
7356
|
+
* Upstream lists `sandbox` as a **User Config Only** key, so it is emitted at
|
|
7357
|
+
* global scope only; at project scope it is dropped with a warning rather than
|
|
7358
|
+
* written into a file Devin would ignore.
|
|
7359
|
+
*
|
|
7360
|
+
* @example
|
|
7361
|
+
* { "sandbox": { "allowed_domains": ["github.com"], "network_mode": "limited" } }
|
|
7362
|
+
* @example
|
|
7363
|
+
* { "sandbox": { "excluded": { "allow": ["Exec(git status *)"], "deny": ["Exec(git tag *)"] } } }
|
|
7364
|
+
* @see https://docs.devin.ai/cli/sandbox
|
|
7365
|
+
* @see https://docs.devin.ai/cli/reference/configuration/config-file
|
|
7366
|
+
*/
|
|
7367
|
+
const DevinPermissionsOverrideSchema = z.looseObject({
|
|
7368
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
7369
|
+
sandbox: z.optional(z.looseObject({}))
|
|
7370
|
+
});
|
|
7371
|
+
/**
|
|
7328
7372
|
* Permissions configuration.
|
|
7329
7373
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
7330
7374
|
* Values are pattern-to-action mappings for that tool category.
|
|
@@ -7374,10 +7418,10 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
7374
7418
|
kiro: z.optional(KiroPermissionsOverrideSchema),
|
|
7375
7419
|
codexcli: z.optional(CodexcliPermissionsOverrideSchema),
|
|
7376
7420
|
zed: z.optional(ZedPermissionsOverrideSchema),
|
|
7421
|
+
devin: z.optional(DevinPermissionsOverrideSchema),
|
|
7377
7422
|
"antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
|
|
7378
7423
|
copilot: z.optional(CanonicalPermissionsOverrideSchema),
|
|
7379
7424
|
copilotcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
7380
|
-
devin: z.optional(CanonicalPermissionsOverrideSchema),
|
|
7381
7425
|
goose: z.optional(CanonicalPermissionsOverrideSchema),
|
|
7382
7426
|
grokcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
7383
7427
|
"kimi-code": z.optional(KimiCodePermissionsOverrideSchema),
|
|
@@ -12603,7 +12647,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12603
12647
|
},
|
|
12604
12648
|
permissions: {
|
|
12605
12649
|
kind: "replace-owned-keys",
|
|
12606
|
-
ownedKeys: ["permissions"]
|
|
12650
|
+
ownedKeys: ["permissions", "sandbox"]
|
|
12607
12651
|
}
|
|
12608
12652
|
}
|
|
12609
12653
|
},
|
|
@@ -36324,6 +36368,180 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
36324
36368
|
return { permission };
|
|
36325
36369
|
}
|
|
36326
36370
|
//#endregion
|
|
36371
|
+
//#region src/features/permissions/sandbox-trust.ts
|
|
36372
|
+
/** A key whose quiet value is an explicit `false`. */
|
|
36373
|
+
const isNotFalse = (value) => value !== false;
|
|
36374
|
+
/** A key whose quiet value is an explicit `true`. */
|
|
36375
|
+
const isNotTrue = (value) => value !== true;
|
|
36376
|
+
/** A list-valued key whose quiet value is the empty list. */
|
|
36377
|
+
const isNonEmptyList = (value) => !Array.isArray(value) || value.length > 0;
|
|
36378
|
+
/** The map-valued counterpart of {@link isNonEmptyList}. */
|
|
36379
|
+
const isNonEmptyMap = (value) => !isRecord$1(value) || Object.keys(value).length > 0;
|
|
36380
|
+
/**
|
|
36381
|
+
* What {@link readSandboxPath} returns when a container on the way to the leaf
|
|
36382
|
+
* is present but is not an object, so the leaf cannot be read at all. It is not
|
|
36383
|
+
* `undefined`, because the two mean opposite things to a caller: `undefined` is
|
|
36384
|
+
* "this path is not being written", while this is "something is being written
|
|
36385
|
+
* here and its shape hides what". The same fail-safe rule the predicates follow
|
|
36386
|
+
* applies to the walk — silence must mean "this cannot loosen anything", not
|
|
36387
|
+
* "this is not the shape the table expected".
|
|
36388
|
+
*/
|
|
36389
|
+
const UNREADABLE_SANDBOX_PATH = Symbol("unreadable-sandbox-path");
|
|
36390
|
+
/**
|
|
36391
|
+
* Reads `sandbox` at `path`. Returns `undefined` when a segment is absent, and
|
|
36392
|
+
* {@link UNREADABLE_SANDBOX_PATH} when one is present but is not an object.
|
|
36393
|
+
* Shared by everything that addresses a `sandbox` path so a nested path added to
|
|
36394
|
+
* one of the tables is actually traversed rather than silently skipped, and so a
|
|
36395
|
+
* hostile shape (an array, a string, `null`) is reported rather than throwing.
|
|
36396
|
+
*/
|
|
36397
|
+
function readSandboxPath({ sandbox, path }) {
|
|
36398
|
+
let cursor = sandbox;
|
|
36399
|
+
for (const segment of path) {
|
|
36400
|
+
if (cursor === void 0) return void 0;
|
|
36401
|
+
if (!isRecord$1(cursor)) return UNREADABLE_SANDBOX_PATH;
|
|
36402
|
+
cursor = cursor[segment];
|
|
36403
|
+
}
|
|
36404
|
+
return cursor;
|
|
36405
|
+
}
|
|
36406
|
+
/**
|
|
36407
|
+
* Every path in `paths` whose value in `sandbox` loosens the policy. Nothing is
|
|
36408
|
+
* removed — the values are written, just not silently. Call it on the block this
|
|
36409
|
+
* generate authored, after any scope filter has run: a value the file already
|
|
36410
|
+
* held is the user's own, not something rulesync opened, and a path a filter
|
|
36411
|
+
* dropped is not being written at all.
|
|
36412
|
+
*/
|
|
36413
|
+
function collectTrustAffectingSandboxPaths({ sandbox, paths }) {
|
|
36414
|
+
const entries = [];
|
|
36415
|
+
const reportedContainers = /* @__PURE__ */ new Set();
|
|
36416
|
+
for (const { path, reason, widens } of paths) {
|
|
36417
|
+
const value = readSandboxPath({
|
|
36418
|
+
sandbox,
|
|
36419
|
+
path
|
|
36420
|
+
});
|
|
36421
|
+
if (value === void 0) continue;
|
|
36422
|
+
if (value === UNREADABLE_SANDBOX_PATH) {
|
|
36423
|
+
const label = findUnreadableContainer({
|
|
36424
|
+
sandbox,
|
|
36425
|
+
path
|
|
36426
|
+
});
|
|
36427
|
+
if (label === void 0 || reportedContainers.has(label)) continue;
|
|
36428
|
+
reportedContainers.add(label);
|
|
36429
|
+
entries.push({
|
|
36430
|
+
label,
|
|
36431
|
+
reason: UNREADABLE_CONTAINER_REASON
|
|
36432
|
+
});
|
|
36433
|
+
continue;
|
|
36434
|
+
}
|
|
36435
|
+
if (!widens(value)) continue;
|
|
36436
|
+
entries.push({
|
|
36437
|
+
label: `sandbox.${path.join(".")}`,
|
|
36438
|
+
reason
|
|
36439
|
+
});
|
|
36440
|
+
}
|
|
36441
|
+
return entries;
|
|
36442
|
+
}
|
|
36443
|
+
/** The reason printed for a container that hides the settings underneath it. */
|
|
36444
|
+
const UNREADABLE_CONTAINER_REASON = "is not the object it has to be, so nothing under it can be checked for what it opens";
|
|
36445
|
+
/**
|
|
36446
|
+
* The prefix of `path` that {@link readSandboxPath} could not walk past, as a
|
|
36447
|
+
* label. `undefined` when the walk was not blocked at all. Callers that report
|
|
36448
|
+
* an unreadable path name the container rather than the leaf, because the leaf
|
|
36449
|
+
* is not what the file actually holds.
|
|
36450
|
+
*/
|
|
36451
|
+
function findUnreadableContainer({ sandbox, path }) {
|
|
36452
|
+
let cursor = sandbox;
|
|
36453
|
+
const walked = [];
|
|
36454
|
+
for (const segment of path) {
|
|
36455
|
+
if (cursor === void 0) return void 0;
|
|
36456
|
+
if (!isRecord$1(cursor)) return walked.length === 0 ? "sandbox" : `sandbox.${walked.join(".")}`;
|
|
36457
|
+
walked.push(segment);
|
|
36458
|
+
cursor = cursor[segment];
|
|
36459
|
+
}
|
|
36460
|
+
}
|
|
36461
|
+
/**
|
|
36462
|
+
* The reason printed for a value the file held in a shape that cannot be read,
|
|
36463
|
+
* which this generate is about to replace. `shape` names what the tool documents
|
|
36464
|
+
* there, so the message says which expectation the file's value missed.
|
|
36465
|
+
*/
|
|
36466
|
+
const replacedUnreadableReason = ({ shape, toolLabel }) => `replaces a value already in the file that is not the ${shape} ${toolLabel} documents, so what it restricted cannot be read`;
|
|
36467
|
+
/**
|
|
36468
|
+
* The restrictions this generate would weaken, compared between the `sandbox`
|
|
36469
|
+
* already in the file and the one about to replace it. A `before` that is
|
|
36470
|
+
* present but not a list is reported outright: a shape the tool may still honor
|
|
36471
|
+
* is not something to go quiet about just because it cannot be diffed. Shared by
|
|
36472
|
+
* every tool whose override replaces a restricting list whole rather than
|
|
36473
|
+
* merging into it — Claude Code needs no equivalent, because it merges its lists
|
|
36474
|
+
* across settings scopes, so a file can only ever add to them.
|
|
36475
|
+
*/
|
|
36476
|
+
function collectRestrictionLosingSandboxEntries({ existing, merged, paths, toolLabel }) {
|
|
36477
|
+
const entries = [];
|
|
36478
|
+
const reportedContainers = /* @__PURE__ */ new Set();
|
|
36479
|
+
for (const { path, reason, loosens } of paths) {
|
|
36480
|
+
const before = readSandboxPath({
|
|
36481
|
+
sandbox: existing,
|
|
36482
|
+
path
|
|
36483
|
+
});
|
|
36484
|
+
if (before === void 0) continue;
|
|
36485
|
+
const [rootKey] = path;
|
|
36486
|
+
if (rootKey !== void 0 && existing[rootKey] === merged[rootKey]) continue;
|
|
36487
|
+
const after = readSandboxPath({
|
|
36488
|
+
sandbox: merged,
|
|
36489
|
+
path
|
|
36490
|
+
});
|
|
36491
|
+
const label = `sandbox.${path.join(".")}`;
|
|
36492
|
+
if (before === UNREADABLE_SANDBOX_PATH) {
|
|
36493
|
+
const container = findUnreadableContainer({
|
|
36494
|
+
sandbox: existing,
|
|
36495
|
+
path
|
|
36496
|
+
});
|
|
36497
|
+
if (container === void 0 || reportedContainers.has(container)) continue;
|
|
36498
|
+
reportedContainers.add(container);
|
|
36499
|
+
entries.push({
|
|
36500
|
+
label: container,
|
|
36501
|
+
reason: replacedUnreadableReason({
|
|
36502
|
+
shape: "object",
|
|
36503
|
+
toolLabel
|
|
36504
|
+
})
|
|
36505
|
+
});
|
|
36506
|
+
continue;
|
|
36507
|
+
}
|
|
36508
|
+
if (!Array.isArray(before)) {
|
|
36509
|
+
entries.push({
|
|
36510
|
+
label,
|
|
36511
|
+
reason: replacedUnreadableReason({
|
|
36512
|
+
shape: "list",
|
|
36513
|
+
toolLabel
|
|
36514
|
+
})
|
|
36515
|
+
});
|
|
36516
|
+
continue;
|
|
36517
|
+
}
|
|
36518
|
+
if (before.length === 0) continue;
|
|
36519
|
+
if (!loosens({
|
|
36520
|
+
before,
|
|
36521
|
+
after: Array.isArray(after) ? after : []
|
|
36522
|
+
})) continue;
|
|
36523
|
+
entries.push({
|
|
36524
|
+
label,
|
|
36525
|
+
reason
|
|
36526
|
+
});
|
|
36527
|
+
}
|
|
36528
|
+
return entries;
|
|
36529
|
+
}
|
|
36530
|
+
/**
|
|
36531
|
+
* The one warning that names every trust-affecting setting this generate wrote
|
|
36532
|
+
* to `relativeFilePath`. Emitted once per file: the individual reasons are what
|
|
36533
|
+
* matter, but the "review this as you would a hook" framing only needs saying
|
|
36534
|
+
* once, and repeating it per key buries the reasons in boilerplate. `noun` lets
|
|
36535
|
+
* a tool whose entries are not all additions call them something more accurate
|
|
36536
|
+
* than "setting".
|
|
36537
|
+
*/
|
|
36538
|
+
function warnOnTrustAffectingEntries({ toolLabel, noun = "setting", entries, relativeFilePath, logger }) {
|
|
36539
|
+
if (entries.length === 0) return;
|
|
36540
|
+
const one = entries.length === 1;
|
|
36541
|
+
const details = entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
|
|
36542
|
+
logger?.warn(`${toolLabel} permissions: writing ${entries.length} trust-affecting ${noun}${one ? "" : "s"} to ${relativeFilePath}; review ${one ? "it" : "them"} as you would a hook, especially if this permissions file came from 'rulesync fetch'. ${details}.`);
|
|
36543
|
+
}
|
|
36544
|
+
//#endregion
|
|
36327
36545
|
//#region src/features/permissions/claudecode-permissions.ts
|
|
36328
36546
|
/**
|
|
36329
36547
|
* Mapping from rulesync canonical tool category names (lowercase) to Claude Code tool names (PascalCase).
|
|
@@ -36372,19 +36590,6 @@ function parseClaudePermissionEntry(entry) {
|
|
|
36372
36590
|
};
|
|
36373
36591
|
}
|
|
36374
36592
|
/**
|
|
36375
|
-
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
36376
|
-
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
36377
|
-
* but never matched by those checks, so Claude Code warns at startup for each
|
|
36378
|
-
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
36379
|
-
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
36380
|
-
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
36381
|
-
* matches the tool everywhere and produces no warning.
|
|
36382
|
-
* @see https://code.claude.com/docs/en/permissions
|
|
36383
|
-
*/
|
|
36384
|
-
function isPlainRecord(value) {
|
|
36385
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36386
|
-
}
|
|
36387
|
-
/**
|
|
36388
36593
|
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
36389
36594
|
* any depth survives. Arrays and scalars are replaced, since a list the author
|
|
36390
36595
|
* states is the list they mean.
|
|
@@ -36394,7 +36599,7 @@ function deepMergeRecords(base, patch) {
|
|
|
36394
36599
|
for (const [key, value] of Object.entries(patch)) {
|
|
36395
36600
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
36396
36601
|
const existing = merged[key];
|
|
36397
|
-
merged[key] =
|
|
36602
|
+
merged[key] = isRecord$1(existing) && isRecord$1(value) ? deepMergeRecords(existing, value) : value;
|
|
36398
36603
|
}
|
|
36399
36604
|
return merged;
|
|
36400
36605
|
}
|
|
@@ -36459,13 +36664,11 @@ const CLAUDECODE_MANAGED_ONLY_SANDBOX_PATHS = [["filesystem", "allowManagedReadP
|
|
|
36459
36664
|
* traversed rather than silently skipped.
|
|
36460
36665
|
*/
|
|
36461
36666
|
function resolveSandboxParent({ root, segments }) {
|
|
36462
|
-
|
|
36463
|
-
|
|
36464
|
-
|
|
36465
|
-
|
|
36466
|
-
|
|
36467
|
-
}
|
|
36468
|
-
return parent;
|
|
36667
|
+
const resolved = readSandboxPath({
|
|
36668
|
+
sandbox: root,
|
|
36669
|
+
path: segments
|
|
36670
|
+
});
|
|
36671
|
+
return isRecord$1(resolved) ? resolved : void 0;
|
|
36469
36672
|
}
|
|
36470
36673
|
/**
|
|
36471
36674
|
* Deletes `path` from `target` in place and reports whether anything was there,
|
|
@@ -36499,18 +36702,6 @@ function deleteSandboxPath({ target, path }) {
|
|
|
36499
36702
|
return true;
|
|
36500
36703
|
}
|
|
36501
36704
|
/**
|
|
36502
|
-
* The one warning that names every trust-affecting setting this generate wrote
|
|
36503
|
-
* to `relativeFilePath`. Emitted once per file: the individual reasons are what
|
|
36504
|
-
* matter, but the "review this as you would a hook" framing only needs saying
|
|
36505
|
-
* once, and repeating it per key buries the reasons in boilerplate.
|
|
36506
|
-
*/
|
|
36507
|
-
function warnOnTrustAffectingEntries({ entries, relativeFilePath, logger }) {
|
|
36508
|
-
if (entries.length === 0) return;
|
|
36509
|
-
const one = entries.length === 1;
|
|
36510
|
-
const details = entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
|
|
36511
|
-
logger?.warn(`Claude Code permissions: writing ${entries.length} trust-affecting ${one ? "setting" : "settings"} to ${relativeFilePath}; review ${one ? "it" : "them"} as you would a hook, especially if this permissions file came from 'rulesync fetch'. ${details}.`);
|
|
36512
|
-
}
|
|
36513
|
-
/**
|
|
36514
36705
|
* The `permissions.defaultMode` values that start a session with fewer prompts
|
|
36515
36706
|
* than the default. `plan` and `default` are absent because they do not widen
|
|
36516
36707
|
* anything.
|
|
@@ -36555,18 +36746,6 @@ const CLAUDECODE_COMMAND_EXECUTING_SANDBOX_PATHS = [
|
|
|
36555
36746
|
["socatPath"]
|
|
36556
36747
|
];
|
|
36557
36748
|
/**
|
|
36558
|
-
* The predicates the "which value actually widens?" tables are built from.
|
|
36559
|
-
* Each names the value that does *not* widen and reports everything else, never
|
|
36560
|
-
* the reverse: the override is authored JSONC, so a key can carry any value at
|
|
36561
|
-
* all, and one Claude Code coerces is still honored. Reporting an off-type value
|
|
36562
|
-
* keeps the warning fail-safe — silence has to mean "this cannot loosen
|
|
36563
|
-
* anything", not "this is not the type the table expected".
|
|
36564
|
-
*/
|
|
36565
|
-
const isNotFalse = (value) => value !== false;
|
|
36566
|
-
const isNotTrue = (value) => value !== true;
|
|
36567
|
-
const isNonEmptyList = (value) => !Array.isArray(value) || value.length > 0;
|
|
36568
|
-
const isNonEmptyMap = (value) => !isPlainRecord(value) || Object.keys(value).length > 0;
|
|
36569
|
-
/**
|
|
36570
36749
|
* `sandbox` paths that loosen the sandbox rather than naming something to run:
|
|
36571
36750
|
* they let commands out of it, weaken the isolation it provides, or redirect
|
|
36572
36751
|
* where its traffic goes. They are written like `env` is — the ordinary uses are
|
|
@@ -36669,30 +36848,6 @@ const CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
|
36669
36848
|
widens: () => true
|
|
36670
36849
|
}
|
|
36671
36850
|
];
|
|
36672
|
-
/**
|
|
36673
|
-
* Every authored `sandbox` path that loosens the sandbox. Nothing is removed —
|
|
36674
|
-
* the values are written, just not silently. Called on the filtered `sandbox`
|
|
36675
|
-
* so it never claims to be writing a path the scope filters dropped.
|
|
36676
|
-
*/
|
|
36677
|
-
function collectTrustAffectingSandboxPaths({ sandbox }) {
|
|
36678
|
-
const entries = [];
|
|
36679
|
-
for (const { path, reason, widens } of CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS) {
|
|
36680
|
-
const leaf = path.at(-1);
|
|
36681
|
-
if (leaf === void 0) continue;
|
|
36682
|
-
const parent = resolveSandboxParent({
|
|
36683
|
-
root: sandbox,
|
|
36684
|
-
segments: path.slice(0, -1)
|
|
36685
|
-
});
|
|
36686
|
-
if (parent === void 0) continue;
|
|
36687
|
-
const value = parent[leaf];
|
|
36688
|
-
if (value === void 0 || !widens(value)) continue;
|
|
36689
|
-
entries.push({
|
|
36690
|
-
label: `sandbox.${path.join(".")}`,
|
|
36691
|
-
reason
|
|
36692
|
-
});
|
|
36693
|
-
}
|
|
36694
|
-
return entries;
|
|
36695
|
-
}
|
|
36696
36851
|
/** Paths that name an executable Claude Code runs. Refused in both scopes. */
|
|
36697
36852
|
const CLAUDECODE_COMMAND_EXECUTING_SANDBOX_REFUSAL = {
|
|
36698
36853
|
paths: CLAUDECODE_COMMAND_EXECUTING_SANDBOX_PATHS,
|
|
@@ -36763,13 +36918,13 @@ const CLAUDECODE_MASKABLE_CREDENTIAL_LISTS = ["envVars", "files"];
|
|
|
36763
36918
|
*/
|
|
36764
36919
|
function stripProjectIgnoredMaskEntries({ sandbox, relativeFilePath, logger }) {
|
|
36765
36920
|
const credentials = sandbox.credentials;
|
|
36766
|
-
if (!
|
|
36921
|
+
if (!isRecord$1(credentials)) return sandbox;
|
|
36767
36922
|
const filteredCredentials = { ...credentials };
|
|
36768
36923
|
let changed = false;
|
|
36769
36924
|
for (const listKey of CLAUDECODE_MASKABLE_CREDENTIAL_LISTS) {
|
|
36770
36925
|
const list = filteredCredentials[listKey];
|
|
36771
36926
|
if (!Array.isArray(list)) continue;
|
|
36772
|
-
const kept = list.filter((entry) => !(
|
|
36927
|
+
const kept = list.filter((entry) => !(isRecord$1(entry) && entry.mode === "mask"));
|
|
36773
36928
|
if (kept.length === list.length) continue;
|
|
36774
36929
|
changed = true;
|
|
36775
36930
|
const dropped = list.length - kept.length;
|
|
@@ -37048,6 +37203,16 @@ function stripUnhonoredTopLevelKeys({ overrides, global, relativeFilePath, logge
|
|
|
37048
37203
|
trustAffecting
|
|
37049
37204
|
};
|
|
37050
37205
|
}
|
|
37206
|
+
/**
|
|
37207
|
+
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
37208
|
+
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
37209
|
+
* but never matched by those checks, so Claude Code warns at startup for each
|
|
37210
|
+
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
37211
|
+
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
37212
|
+
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
37213
|
+
* matches the tool everywhere and produces no warning.
|
|
37214
|
+
* @see https://code.claude.com/docs/en/permissions
|
|
37215
|
+
*/
|
|
37051
37216
|
const CLAUDE_PATH_RULE_ALIASES = {
|
|
37052
37217
|
Write: "Edit",
|
|
37053
37218
|
NotebookEdit: "Edit",
|
|
@@ -37128,7 +37293,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
37128
37293
|
};
|
|
37129
37294
|
}
|
|
37130
37295
|
const overrideSandbox = config.claudecode?.sandbox;
|
|
37131
|
-
if (
|
|
37296
|
+
if (isRecord$1(overrideSandbox)) {
|
|
37132
37297
|
const honorableSandbox = stripSandboxPaths({
|
|
37133
37298
|
sandbox: overrideSandbox,
|
|
37134
37299
|
refusals: [
|
|
@@ -37144,8 +37309,11 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
37144
37309
|
relativeFilePath: paths.relativeFilePath,
|
|
37145
37310
|
logger
|
|
37146
37311
|
});
|
|
37147
|
-
trustAffecting.push(...collectTrustAffectingSandboxPaths({
|
|
37148
|
-
|
|
37312
|
+
trustAffecting.push(...collectTrustAffectingSandboxPaths({
|
|
37313
|
+
sandbox: scopedSandbox,
|
|
37314
|
+
paths: CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS
|
|
37315
|
+
}));
|
|
37316
|
+
if (Object.keys(scopedSandbox).length > 0) settings.sandbox = deepMergeRecords(isRecord$1(settings.sandbox) ? settings.sandbox : {}, scopedSandbox);
|
|
37149
37317
|
}
|
|
37150
37318
|
const overrideTopLevel = {};
|
|
37151
37319
|
for (const [key, value] of Object.entries(config.claudecode ?? {})) {
|
|
@@ -37163,8 +37331,9 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
37163
37331
|
trustAffecting.push(...trustAffectingTopLevel);
|
|
37164
37332
|
if (Object.keys(scopedTopLevel).length > 0) settings = deepMergeRecords(settings, scopedTopLevel);
|
|
37165
37333
|
warnOnTrustAffectingEntries({
|
|
37334
|
+
toolLabel: "Claude Code",
|
|
37166
37335
|
entries: trustAffecting,
|
|
37167
|
-
relativeFilePath: paths.relativeFilePath,
|
|
37336
|
+
relativeFilePath: toPosixPath(join(paths.relativeDirPath, paths.relativeFilePath)),
|
|
37168
37337
|
logger
|
|
37169
37338
|
});
|
|
37170
37339
|
const managedToolNames = managedClaudeToolNames(config);
|
|
@@ -37207,7 +37376,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
37207
37376
|
const nonListFields = Object.fromEntries(Object.entries(permissionsRest).filter(([key]) => !PROTOTYPE_POLLUTION_KEYS.has(key)));
|
|
37208
37377
|
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
37209
37378
|
const { sandbox } = settings;
|
|
37210
|
-
if (
|
|
37379
|
+
if (isRecord$1(sandbox)) {
|
|
37211
37380
|
const importedSandbox = structuredClone(sandbox);
|
|
37212
37381
|
for (const path of CLAUDECODE_COMMAND_EXECUTING_SANDBOX_PATHS) deleteSandboxPath({
|
|
37213
37382
|
target: importedSandbox,
|
|
@@ -39460,6 +39629,80 @@ function buildDevinPermissionEntry(scope, pattern) {
|
|
|
39460
39629
|
if (pattern === "*") return scope;
|
|
39461
39630
|
return `${scope}(${pattern})`;
|
|
39462
39631
|
}
|
|
39632
|
+
function asDevinRecord(value) {
|
|
39633
|
+
return isRecord$1(value) ? { ...value } : {};
|
|
39634
|
+
}
|
|
39635
|
+
/**
|
|
39636
|
+
* `sandbox` paths whose authored value loosens the sandbox on its own: they let
|
|
39637
|
+
* a command out of it, or widen what a command left inside it may reach. They
|
|
39638
|
+
* are written — the ordinary uses are far too common to refuse — but never
|
|
39639
|
+
* silently, because a permissions file is shareable (`rulesync fetch` copies one
|
|
39640
|
+
* into a project) and should not be able to open the sandbox without saying so.
|
|
39641
|
+
* This is the same stance `CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS` takes for
|
|
39642
|
+
* the equivalent Claude Code keys, and `widens` follows the same convention of
|
|
39643
|
+
* naming the restrictive value rather than the permissive ones, so a spelling
|
|
39644
|
+
* Devin does not recognize is reported rather than waved through.
|
|
39645
|
+
*
|
|
39646
|
+
* The three keys that restrict — `allowed_domains` (an allowlist only while it
|
|
39647
|
+
* has entries), `denied_domains` and `excluded.deny` — are not here: they loosen
|
|
39648
|
+
* by losing entries, which `DEVIN_RESTRICTION_LOSING_SANDBOX_PATHS` covers.
|
|
39649
|
+
*
|
|
39650
|
+
* @see https://docs.devin.ai/cli/sandbox
|
|
39651
|
+
*/
|
|
39652
|
+
const DEVIN_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
39653
|
+
{
|
|
39654
|
+
path: ["network_mode"],
|
|
39655
|
+
reason: "anything but 'limited' lets sandboxed requests use every HTTP method, not just GET/HEAD/OPTIONS",
|
|
39656
|
+
widens: (value) => value !== "limited"
|
|
39657
|
+
},
|
|
39658
|
+
{
|
|
39659
|
+
path: ["excluded", "allow"],
|
|
39660
|
+
reason: "names commands that run outside the sandbox with no prompt and no sandbox policy",
|
|
39661
|
+
widens: isNonEmptyList
|
|
39662
|
+
},
|
|
39663
|
+
{
|
|
39664
|
+
path: ["excluded", "ask"],
|
|
39665
|
+
reason: "names commands that run outside the sandbox once confirmed, with no sandbox policy",
|
|
39666
|
+
widens: isNonEmptyList
|
|
39667
|
+
}
|
|
39668
|
+
];
|
|
39669
|
+
/** How Devin is named in the warnings this file emits. */
|
|
39670
|
+
const DEVIN_TOOL_LABEL = "Devin";
|
|
39671
|
+
/**
|
|
39672
|
+
* `sandbox` paths that restrict, and that therefore loosen the policy by losing
|
|
39673
|
+
* entries rather than by holding a value. Devin's config is one file rather than
|
|
39674
|
+
* a stack of settings scopes, and the override is shallow-merged over the
|
|
39675
|
+
* existing `sandbox` at its top level: each of these lists is replaced whole,
|
|
39676
|
+
* and `excluded.deny` vanishes as soon as the override states any other
|
|
39677
|
+
* `excluded` key. Losing an entry has the same effect as adding one to the
|
|
39678
|
+
* permissive keys above, so it is announced the same way. Claude Code needs no
|
|
39679
|
+
* equivalent — it merges its lists across settings scopes, so a file can only
|
|
39680
|
+
* ever add to them.
|
|
39681
|
+
*
|
|
39682
|
+
* `loosens` is asked only about a `before` that actually restricted something,
|
|
39683
|
+
* and the two directions are not symmetric: `allowed_domains` restricts by
|
|
39684
|
+
* listing what is reachable, so it loosens by gaining entries or by emptying
|
|
39685
|
+
* out altogether, while the deny lists loosen by losing entries.
|
|
39686
|
+
*
|
|
39687
|
+
* @see https://docs.devin.ai/cli/sandbox
|
|
39688
|
+
*/
|
|
39689
|
+
const DEVIN_RESTRICTION_LOSING_SANDBOX_PATHS = [
|
|
39690
|
+
{
|
|
39691
|
+
path: ["allowed_domains"],
|
|
39692
|
+
reason: "adds to the proxy allowlist already in the file, or empties it so every domain becomes reachable again",
|
|
39693
|
+
loosens: ({ before, after }) => after.length === 0 || after.some((entry) => !before.includes(entry))
|
|
39694
|
+
},
|
|
39695
|
+
{
|
|
39696
|
+
path: ["denied_domains"],
|
|
39697
|
+
reason: "drops domains the deny list already in the file kept out of reach",
|
|
39698
|
+
loosens: ({ before, after }) => before.some((entry) => !after.includes(entry))
|
|
39699
|
+
},
|
|
39700
|
+
{
|
|
39701
|
+
path: ["excluded", "deny"],
|
|
39702
|
+
reason: "drops commands the list already in the file pinned inside the sandbox",
|
|
39703
|
+
loosens: ({ before, after }) => before.some((entry) => !after.includes(entry))
|
|
39704
|
+
}
|
|
39705
|
+
];
|
|
39463
39706
|
/**
|
|
39464
39707
|
* Permissions generator for Devin Local (native `.devin/` configuration).
|
|
39465
39708
|
*
|
|
@@ -39475,10 +39718,18 @@ function buildDevinPermissionEntry(scope, pattern) {
|
|
|
39475
39718
|
*
|
|
39476
39719
|
* In global mode the config file is shared with the hooks (`hooks`) feature
|
|
39477
39720
|
* (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
|
|
39478
|
-
* merge into the existing JSON and the file is never deleted; only the
|
|
39479
|
-
* `permissions`
|
|
39721
|
+
* merge into the existing JSON and the file is never deleted; only the keys
|
|
39722
|
+
* this feature manages are rewritten — `permissions`, plus `sandbox` in global
|
|
39723
|
+
* mode when the `devin` override authors it.
|
|
39724
|
+
*
|
|
39725
|
+
* The sibling `sandbox` block — which decides what a permitted command may
|
|
39726
|
+
* reach rather than which commands are permitted — has no canonical category
|
|
39727
|
+
* and is authored through the `devin` override in `.rulesync/permissions.jsonc`.
|
|
39728
|
+
* Devin documents it as a user-config-only key, so it is written at global
|
|
39729
|
+
* scope only.
|
|
39480
39730
|
*
|
|
39481
39731
|
* @see https://docs.devin.ai/cli/reference/permissions
|
|
39732
|
+
* @see https://docs.devin.ai/cli/sandbox
|
|
39482
39733
|
*/
|
|
39483
39734
|
var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
39484
39735
|
constructor(params) {
|
|
@@ -39489,7 +39740,8 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
39489
39740
|
}
|
|
39490
39741
|
/**
|
|
39491
39742
|
* config.json may carry the MCP/hooks features' keys, so it is never deleted;
|
|
39492
|
-
* only the
|
|
39743
|
+
* only the keys this feature manages are rewritten — `permissions`, plus
|
|
39744
|
+
* `sandbox` in global mode when the `devin` override authors it.
|
|
39493
39745
|
*/
|
|
39494
39746
|
isDeletable() {
|
|
39495
39747
|
return false;
|
|
@@ -39515,7 +39767,7 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
39515
39767
|
validate
|
|
39516
39768
|
});
|
|
39517
39769
|
}
|
|
39518
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, validate = true }) {
|
|
39770
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, validate = true, logger }) {
|
|
39519
39771
|
const paths = DevinPermissions.getSettablePaths({ global });
|
|
39520
39772
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
39521
39773
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -39541,6 +39793,46 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
39541
39793
|
else delete mergedPermissions.ask;
|
|
39542
39794
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
39543
39795
|
else delete mergedPermissions.deny;
|
|
39796
|
+
const patch = { permissions: mergedPermissions };
|
|
39797
|
+
const authoredSandbox = config.devin?.sandbox;
|
|
39798
|
+
if (authoredSandbox !== void 0) {
|
|
39799
|
+
const authoredSandboxRecord = asDevinRecord(authoredSandbox);
|
|
39800
|
+
if (global) {
|
|
39801
|
+
const existingSandbox = asDevinRecord(settings.sandbox);
|
|
39802
|
+
const mergedSandbox = {
|
|
39803
|
+
...existingSandbox,
|
|
39804
|
+
...authoredSandboxRecord
|
|
39805
|
+
};
|
|
39806
|
+
const writesSandbox = Object.keys(mergedSandbox).length > 0;
|
|
39807
|
+
if (writesSandbox) patch.sandbox = mergedSandbox;
|
|
39808
|
+
const replacesUnreadableSandbox = writesSandbox && settings.sandbox !== void 0 && !isRecord$1(settings.sandbox);
|
|
39809
|
+
warnOnTrustAffectingEntries({
|
|
39810
|
+
toolLabel: DEVIN_TOOL_LABEL,
|
|
39811
|
+
noun: "sandbox change",
|
|
39812
|
+
entries: [
|
|
39813
|
+
...replacesUnreadableSandbox ? [{
|
|
39814
|
+
label: "sandbox",
|
|
39815
|
+
reason: replacedUnreadableReason({
|
|
39816
|
+
shape: "object",
|
|
39817
|
+
toolLabel: DEVIN_TOOL_LABEL
|
|
39818
|
+
})
|
|
39819
|
+
}] : [],
|
|
39820
|
+
...collectTrustAffectingSandboxPaths({
|
|
39821
|
+
sandbox: authoredSandboxRecord,
|
|
39822
|
+
paths: DEVIN_TRUST_AFFECTING_SANDBOX_PATHS
|
|
39823
|
+
}),
|
|
39824
|
+
...collectRestrictionLosingSandboxEntries({
|
|
39825
|
+
existing: existingSandbox,
|
|
39826
|
+
merged: mergedSandbox,
|
|
39827
|
+
paths: DEVIN_RESTRICTION_LOSING_SANDBOX_PATHS,
|
|
39828
|
+
toolLabel: DEVIN_TOOL_LABEL
|
|
39829
|
+
})
|
|
39830
|
+
],
|
|
39831
|
+
relativeFilePath: toPosixPath(join(paths.relativeDirPath, paths.relativeFilePath)),
|
|
39832
|
+
logger
|
|
39833
|
+
});
|
|
39834
|
+
} else if (Object.keys(authoredSandboxRecord).length > 0) logger?.warn("Devin reads 'sandbox' from the user config only, so the 'devin.sandbox' override was dropped from the project config. Generate with --global to author it.");
|
|
39835
|
+
}
|
|
39544
39836
|
return new DevinPermissions({
|
|
39545
39837
|
outputRoot,
|
|
39546
39838
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -39549,7 +39841,7 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
39549
39841
|
fileKey: sharedConfigFileKey(paths),
|
|
39550
39842
|
feature: "permissions",
|
|
39551
39843
|
existingContent,
|
|
39552
|
-
patch
|
|
39844
|
+
patch,
|
|
39553
39845
|
filePath
|
|
39554
39846
|
}),
|
|
39555
39847
|
validate
|
|
@@ -39569,7 +39861,10 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
39569
39861
|
ask: Array.isArray(permissions.ask) ? permissions.ask : [],
|
|
39570
39862
|
deny: Array.isArray(permissions.deny) ? permissions.deny : []
|
|
39571
39863
|
});
|
|
39572
|
-
|
|
39864
|
+
const sandbox = asDevinRecord(settings.sandbox);
|
|
39865
|
+
const result = { ...config };
|
|
39866
|
+
if (Object.keys(sandbox).length > 0) result.devin = { sandbox };
|
|
39867
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
39573
39868
|
}
|
|
39574
39869
|
validate() {
|
|
39575
39870
|
return {
|
|
@@ -42199,15 +42494,129 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
42199
42494
|
"sandbox",
|
|
42200
42495
|
"sandboxImage",
|
|
42201
42496
|
"disabled",
|
|
42202
|
-
"visible"
|
|
42497
|
+
"visible",
|
|
42498
|
+
"listDirectory",
|
|
42499
|
+
"workflowsEnabled"
|
|
42203
42500
|
];
|
|
42204
42501
|
const QWEN_OVERRIDE_SECURITY_KEYS = [
|
|
42205
42502
|
"folderTrust",
|
|
42206
42503
|
"allowedHttpHookUrls",
|
|
42207
|
-
"allowPrivateNetworkHooks"
|
|
42504
|
+
"allowPrivateNetworkHooks",
|
|
42505
|
+
"allowedInsecureVoiceBaseUrls"
|
|
42208
42506
|
];
|
|
42209
|
-
|
|
42507
|
+
/**
|
|
42508
|
+
* What each rule means for the two directions, so a key's behavior is declared
|
|
42509
|
+
* once beside its rule rather than spelled out at every branch.
|
|
42510
|
+
*
|
|
42511
|
+
* `stripInProjectScope` separates the keys a project file cannot use at all from
|
|
42512
|
+
* the ones whose project value still does something, just not unconditionally —
|
|
42513
|
+
* only the former are dropped. `announceOnlyGrants` marks the rule whose risk is
|
|
42514
|
+
* a value that turns something on; for the others the dangerous value is often
|
|
42515
|
+
* the falsy one (an empty allow-all list, folder trust switched off, a sandbox
|
|
42516
|
+
* turned off), so every change is announced instead. `announceOnImport` marks
|
|
42517
|
+
* the keys whose meaning depends on the scope they are written in — importing
|
|
42518
|
+
* one and regenerating globally turns a value a workspace could not decide for
|
|
42519
|
+
* itself into one Qwen Code enforces everywhere. `global-machine-wide` keys are
|
|
42520
|
+
* honored identically in either scope, so import neither promotes nor weakens
|
|
42521
|
+
* them and the announcement on the generate side is the control point.
|
|
42522
|
+
* `projectNote` is `null` for a rule that has nothing to say about the project
|
|
42523
|
+
* scope. A `globalNote` here is the rule's default; a key whose meaning the
|
|
42524
|
+
* default does not describe carries its own.
|
|
42525
|
+
*/
|
|
42526
|
+
const QWEN_SCOPE_RULES = {
|
|
42527
|
+
"workspace-stripped": {
|
|
42528
|
+
stripInProjectScope: true,
|
|
42529
|
+
announceOnlyGrants: true,
|
|
42530
|
+
announceOnImport: true,
|
|
42531
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} is only honored in user/system settings, so it is skipped for the project-scoped ${filePath} (a value already in that file is left as it is). Check it if the override arrived with a repository you cloned, and author it in the global scope only if that is a value you want every project on this machine to run under.`,
|
|
42532
|
+
globalNote: "Qwen Code ignores this key in workspace settings so a repository cannot grant it per project; in the global scope it applies to every project on this machine."
|
|
42533
|
+
},
|
|
42534
|
+
"workspace-non-overriding": {
|
|
42535
|
+
stripInProjectScope: false,
|
|
42536
|
+
announceOnlyGrants: false,
|
|
42537
|
+
announceOnImport: true,
|
|
42538
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, but Qwen Code honors a workspace value for it only while no user, system, or system-defaults scope sets the key — a repository cannot replace the list a user configured in a higher scope. It does replace one written by hand in this same file, and an empty list is Qwen Code's allow-all, so check what it now says. Author it in the global scope if it has to apply unconditionally.`,
|
|
42539
|
+
globalNote: "A global value outranks every repository's own list for this key, and an empty list means allow-all, so this decides where HTTP hooks may send data for every project on this machine."
|
|
42540
|
+
},
|
|
42541
|
+
"user-scope-trust-check": {
|
|
42542
|
+
stripInProjectScope: false,
|
|
42543
|
+
announceOnlyGrants: false,
|
|
42544
|
+
announceOnImport: true,
|
|
42545
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, but Qwen Code makes the initial trust decision from user and system settings alone, before a workspace file is merged, so a project value cannot decide whether this workspace is trusted (it still drives folder trust once the workspace is trusted). Check it if the override arrived with a repository you cloned, and author it in the global scope only if that is a value you want every project on this machine to run under.`,
|
|
42546
|
+
globalNote: "Qwen Code reads this key from the global scope when it decides whether a workspace is trusted, so this changes which projects on this machine it trusts."
|
|
42547
|
+
},
|
|
42548
|
+
"global-machine-wide": {
|
|
42549
|
+
stripInProjectScope: false,
|
|
42550
|
+
announceOnlyGrants: false,
|
|
42551
|
+
announceOnImport: false,
|
|
42552
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code honors this key in a workspace file, so it takes effect while you work in this repository. Check it if the override arrived with a repository you cloned.`,
|
|
42553
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope it settles how much the agent may do unattended — how far approvals are skipped, and whether tool calls are contained at all — for every project on this machine."
|
|
42554
|
+
},
|
|
42555
|
+
unmodeled: {
|
|
42556
|
+
stripInProjectScope: false,
|
|
42557
|
+
announceOnlyGrants: false,
|
|
42558
|
+
announceOnImport: false,
|
|
42559
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. This is not a key rulesync models, so it cannot say what Qwen Code does with it — and some settings in these groups are potent (\`tools.discoveryCommand\` and \`tools.callCommand\` are spawned as commands when Qwen Code starts). Check it if the override arrived with a repository you cloned.`,
|
|
42560
|
+
globalNote: "This is not a key rulesync models, so it cannot say what Qwen Code does with it — and some settings in these groups are potent (`tools.discoveryCommand` and `tools.callCommand` are spawned as commands when Qwen Code starts). Check what it now says for every project on this machine."
|
|
42561
|
+
}
|
|
42562
|
+
};
|
|
42563
|
+
const QWEN_OVERRIDE_GROUPS = [{
|
|
42564
|
+
groupName: "tools",
|
|
42565
|
+
overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
|
|
42566
|
+
scopedKeys: {
|
|
42567
|
+
workflowsEnabled: { rule: "workspace-stripped" },
|
|
42568
|
+
approvalMode: {
|
|
42569
|
+
rule: "global-machine-wide",
|
|
42570
|
+
projectNote: autonomyProjectNote
|
|
42571
|
+
},
|
|
42572
|
+
autoAccept: {
|
|
42573
|
+
rule: "global-machine-wide",
|
|
42574
|
+
projectNote: autonomyProjectNote
|
|
42575
|
+
},
|
|
42576
|
+
sandbox: {
|
|
42577
|
+
rule: "global-machine-wide",
|
|
42578
|
+
projectNote: autonomyProjectNote
|
|
42579
|
+
},
|
|
42580
|
+
sandboxImage: {
|
|
42581
|
+
rule: "global-machine-wide",
|
|
42582
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so a sandboxed run in this repository executes inside the named image — check that it is one you trust to run your code.`,
|
|
42583
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope every sandboxed run on this machine executes inside the named image — check that it is one you trust to run your code."
|
|
42584
|
+
},
|
|
42585
|
+
disabled: {
|
|
42586
|
+
rule: "global-machine-wide",
|
|
42587
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the list in that file rather than adding to it, so a name that is gone is a tool the model can call again in this repository unless another scope still disables it.`,
|
|
42588
|
+
globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the list in this file rather than adding to it, so in the global scope this decides which tools stay out of the registry for every project on this machine — dropping a name hands that tool back to the model unless another scope still disables it."
|
|
42589
|
+
},
|
|
42590
|
+
visible: {
|
|
42591
|
+
rule: "global-machine-wide",
|
|
42592
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides which deferred tools are visible at startup in this repository.`,
|
|
42593
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides which deferred tools are visible at startup for every project on this machine."
|
|
42594
|
+
},
|
|
42595
|
+
listDirectory: {
|
|
42596
|
+
rule: "global-machine-wide",
|
|
42597
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`list_directory\` tool is registered in this repository.`,
|
|
42598
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `list_directory` tool is registered for every project on this machine."
|
|
42599
|
+
}
|
|
42600
|
+
}
|
|
42601
|
+
}, {
|
|
42602
|
+
groupName: "security",
|
|
42603
|
+
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
42604
|
+
scopedKeys: {
|
|
42605
|
+
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
42606
|
+
allowedInsecureVoiceBaseUrls: {
|
|
42607
|
+
rule: "workspace-stripped",
|
|
42608
|
+
grants: isNonEmptyArray
|
|
42609
|
+
},
|
|
42610
|
+
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
42611
|
+
folderTrust: { rule: "user-scope-trust-check" }
|
|
42612
|
+
}
|
|
42613
|
+
}];
|
|
42210
42614
|
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
42615
|
+
const QWEN_SCOPED_PERMISSIONS_KEYS = { autoMode: {
|
|
42616
|
+
rule: "global-machine-wide",
|
|
42617
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the whole block rather than merging into it, so it rewrites the instructions Auto Mode's classifier follows in this repository — including any deny hint you had configured.`,
|
|
42618
|
+
globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the whole block rather than merging into it, so in the global scope this rewrites the instructions Auto Mode's classifier follows — including any deny hint you had configured — for every project on this machine."
|
|
42619
|
+
} };
|
|
42211
42620
|
function asPlainRecord(value) {
|
|
42212
42621
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
42213
42622
|
}
|
|
@@ -42219,20 +42628,172 @@ function pickQwenOverrideKeys(group, keys) {
|
|
|
42219
42628
|
return picked;
|
|
42220
42629
|
}
|
|
42221
42630
|
/**
|
|
42222
|
-
*
|
|
42223
|
-
*
|
|
42224
|
-
*
|
|
42631
|
+
* Whether a value written for a scoped key actually grants something. Qwen Code
|
|
42632
|
+
* reads the boolean ones with a plain truthiness check, so anything truthy turns
|
|
42633
|
+
* the capability on — `1` and the string `"false"` included, which is why this is
|
|
42634
|
+
* not a `=== true` test. Not all of them are booleans either:
|
|
42635
|
+
* `security.allowedInsecureVoiceBaseUrls` is a list of base URLs, and an empty
|
|
42636
|
+
* list grants nothing.
|
|
42637
|
+
*
|
|
42638
|
+
* Only the rules whose `announceOnlyGrants` is set consult this, because "empty"
|
|
42639
|
+
* does not mean the same thing for every list: an empty
|
|
42640
|
+
* `security.allowedHttpHookUrls` is Qwen Code's allow-all, the widest value there
|
|
42641
|
+
* is, so that key is announced whatever it says.
|
|
42225
42642
|
*/
|
|
42226
|
-
function
|
|
42227
|
-
|
|
42228
|
-
|
|
42229
|
-
|
|
42230
|
-
|
|
42231
|
-
|
|
42232
|
-
|
|
42643
|
+
function isNonEmptyArray(value) {
|
|
42644
|
+
return Array.isArray(value) && value.length > 0;
|
|
42645
|
+
}
|
|
42646
|
+
/**
|
|
42647
|
+
* Whether a value turns something on, read the way Qwen Code reads it.
|
|
42648
|
+
*
|
|
42649
|
+
* Plain truthiness by default, because that is what Qwen Code applies to these
|
|
42650
|
+
* settings: `1` and the string `"false"` enable them, and so does an empty array
|
|
42651
|
+
* — which is why an empty list cannot be treated as harmless everywhere. A key
|
|
42652
|
+
* whose list Qwen Code matches against, rather than tests for truth, carries its
|
|
42653
|
+
* own `grants` instead.
|
|
42654
|
+
*/
|
|
42655
|
+
function grantsSomething(value, scopedKey) {
|
|
42656
|
+
return (scopedKey?.grants ?? Boolean)(value);
|
|
42657
|
+
}
|
|
42658
|
+
/**
|
|
42659
|
+
* The autonomy and containment controls say the same thing in project scope, so
|
|
42660
|
+
* they share one note rather than repeating it three times.
|
|
42661
|
+
*/
|
|
42662
|
+
function autonomyProjectNote({ qualifiedKey, quotedValue, filePath }) {
|
|
42663
|
+
return `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code honors this key in a workspace file, so it settles how far approvals are skipped and whether tool calls are contained while you work in this repository. Check it if the override arrived with a repository you cloned.`;
|
|
42664
|
+
}
|
|
42665
|
+
/**
|
|
42666
|
+
* Quote a `group.key` pair for a warning. The key goes through the same quoting
|
|
42667
|
+
* a value does, because it is one: the override's groups are `z.looseObject`s,
|
|
42668
|
+
* so a key the scope gate names may have been written by whoever authored the
|
|
42669
|
+
* `.rulesync/permissions.jsonc` rather than chosen from a list rulesync knows.
|
|
42670
|
+
* Serializing it — rather than wrapping it in quotes of our own — is what keeps
|
|
42671
|
+
* a key that contains a quote from closing it and continuing the sentence.
|
|
42672
|
+
*/
|
|
42673
|
+
function quoteQualifiedKey({ groupName, key }) {
|
|
42674
|
+
return quoteValueForWarning(`${groupName}.${key}`);
|
|
42675
|
+
}
|
|
42676
|
+
/**
|
|
42677
|
+
* Compare two settings values without letting key order decide the answer, so
|
|
42678
|
+
* re-emitting `{ enabled: true, note: "x" }` as `{ note: "x", enabled: true }`
|
|
42679
|
+
* is not announced as a change.
|
|
42680
|
+
*/
|
|
42681
|
+
function stableStringify(value) {
|
|
42682
|
+
return JSON.stringify(value, (_key, nested) => nested !== null && typeof nested === "object" && !Array.isArray(nested) ? Object.fromEntries(Object.entries(nested).toSorted(([left], [right]) => left.localeCompare(right))) : nested) ?? "undefined";
|
|
42683
|
+
}
|
|
42684
|
+
function sameSettingsValue(a, b) {
|
|
42685
|
+
return stableStringify(a) === stableStringify(b);
|
|
42686
|
+
}
|
|
42687
|
+
/**
|
|
42688
|
+
* Apply the scope rules for a settings group's scoped keys.
|
|
42689
|
+
*
|
|
42690
|
+
* Generating project settings drops the keys Qwen Code strips from a workspace
|
|
42691
|
+
* file before the merge — writing one there would be dead configuration — and
|
|
42692
|
+
* explains the keys whose project value is honored only conditionally rather
|
|
42693
|
+
* than dropping those. Either way only the override copy is touched, so a value
|
|
42694
|
+
* the user already wrote into the project file stays where it is.
|
|
42695
|
+
*
|
|
42696
|
+
* Generating global settings keeps every key, and announces the write: the
|
|
42697
|
+
* `.rulesync/permissions.jsonc` carrying it may have arrived with a cloned or
|
|
42698
|
+
* fetched repository, and the global file applies to every project on this
|
|
42699
|
+
* machine. So the new value is named alongside the one it replaces, exactly as
|
|
42700
|
+
* the deepagents startup override announces its own relaxations. Every key the
|
|
42701
|
+
* override writes is announced, not only the ones with a scope rule — the
|
|
42702
|
+
* groups are `z.looseObject`s, so an unmodeled key falls back to the
|
|
42703
|
+
* `unmodeled` rule rather than slipping past the gate that the modeled ones
|
|
42704
|
+
* pass through.
|
|
42705
|
+
*/
|
|
42706
|
+
function scopeOverrideGroup(overrideGroup, { groupName, scopedKeys, existingGroup, global, filePath, logger }) {
|
|
42707
|
+
const scoped = { ...asPlainRecord(overrideGroup) };
|
|
42708
|
+
const previous = asPlainRecord(existingGroup);
|
|
42709
|
+
const rulesByKey = new Map(Object.entries(scopedKeys));
|
|
42710
|
+
for (const key of Object.keys(scoped)) {
|
|
42711
|
+
const value = scoped[key];
|
|
42712
|
+
if (value === void 0) continue;
|
|
42713
|
+
const scopedKey = rulesByKey.get(key);
|
|
42714
|
+
const rule = scopedKey?.rule ?? "unmodeled";
|
|
42715
|
+
const { stripInProjectScope, announceOnlyGrants } = QWEN_SCOPE_RULES[rule];
|
|
42716
|
+
const projectNote = scopedKey?.projectNote ?? QWEN_SCOPE_RULES[rule].projectNote;
|
|
42717
|
+
const globalNote = scopedKey?.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
|
|
42718
|
+
const previousValue = Object.hasOwn(previous, key) ? previous[key] : void 0;
|
|
42719
|
+
const unchanged = sameSettingsValue(previousValue, value);
|
|
42720
|
+
if (!global) {
|
|
42721
|
+
if (stripInProjectScope) delete scoped[key];
|
|
42722
|
+
else if (unchanged) continue;
|
|
42723
|
+
if (projectNote) warnWithFallback(logger, `Qwen permissions: ${projectNote({
|
|
42724
|
+
qualifiedKey: quoteQualifiedKey({
|
|
42725
|
+
groupName,
|
|
42726
|
+
key
|
|
42727
|
+
}),
|
|
42728
|
+
quotedValue: quoteValueForWarning(value),
|
|
42729
|
+
filePath
|
|
42730
|
+
})}`);
|
|
42731
|
+
continue;
|
|
42732
|
+
}
|
|
42733
|
+
if (unchanged) continue;
|
|
42734
|
+
if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
|
|
42735
|
+
const replaced = previousValue === void 0 ? "" : ` (was ${quoteValueForWarning(previousValue)})`;
|
|
42736
|
+
warnWithFallback(logger, `Qwen permissions: the qwencode override wrote ${quoteQualifiedKey({
|
|
42737
|
+
groupName,
|
|
42738
|
+
key
|
|
42739
|
+
})} = ${quoteValueForWarning(value)}${replaced} into ${filePath}, your global Qwen Code settings. ${globalNote}`);
|
|
42233
42740
|
}
|
|
42234
42741
|
return scoped;
|
|
42235
42742
|
}
|
|
42743
|
+
/**
|
|
42744
|
+
* Build the `tools`/`security` patch groups contributed by the `qwencode`
|
|
42745
|
+
* override. Each group is shallow-merged over what `settings.json` already has,
|
|
42746
|
+
* after the scope gate has handled the keys Qwen Code honors in one scope only.
|
|
42747
|
+
*/
|
|
42748
|
+
function buildOverrideGroupsPatch({ settings, override, global, filePath, logger }) {
|
|
42749
|
+
const patch = {};
|
|
42750
|
+
for (const { groupName, scopedKeys } of QWEN_OVERRIDE_GROUPS) {
|
|
42751
|
+
const overrideGroup = override?.[groupName];
|
|
42752
|
+
if (overrideGroup === void 0) continue;
|
|
42753
|
+
const existingGroup = settings[groupName];
|
|
42754
|
+
const scoped = scopeOverrideGroup(overrideGroup, {
|
|
42755
|
+
groupName,
|
|
42756
|
+
scopedKeys,
|
|
42757
|
+
existingGroup,
|
|
42758
|
+
global,
|
|
42759
|
+
filePath,
|
|
42760
|
+
logger
|
|
42761
|
+
});
|
|
42762
|
+
const merged = {
|
|
42763
|
+
...asPlainRecord(existingGroup),
|
|
42764
|
+
...scoped
|
|
42765
|
+
};
|
|
42766
|
+
if (Object.keys(merged).length > 0) patch[groupName] = merged;
|
|
42767
|
+
}
|
|
42768
|
+
return patch;
|
|
42769
|
+
}
|
|
42770
|
+
/**
|
|
42771
|
+
* Warn about a scoped key lifted out of a *project* settings file being
|
|
42772
|
+
* imported. The file carries no scope marker, so import keeps the key either
|
|
42773
|
+
* way — but regenerating in the global scope would turn a value a workspace
|
|
42774
|
+
* could not decide for itself into one Qwen Code enforces everywhere, and a
|
|
42775
|
+
* settings file read out of a cloned repository is exactly where such a value
|
|
42776
|
+
* comes from. Importing the global file is the case this has nothing to say
|
|
42777
|
+
* about: the value is already in the scope the warning would send it to.
|
|
42778
|
+
*/
|
|
42779
|
+
function warnAboutImportedScopedKeys(groups) {
|
|
42780
|
+
for (const { groupName, scopedKeys } of QWEN_OVERRIDE_GROUPS) {
|
|
42781
|
+
const group = groups[groupName];
|
|
42782
|
+
for (const [key, scopedKey] of Object.entries(scopedKeys)) {
|
|
42783
|
+
const { rule } = scopedKey;
|
|
42784
|
+
const value = group[key];
|
|
42785
|
+
if (value === void 0) continue;
|
|
42786
|
+
const { announceOnlyGrants, announceOnImport } = QWEN_SCOPE_RULES[rule];
|
|
42787
|
+
if (!announceOnImport) continue;
|
|
42788
|
+
if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
|
|
42789
|
+
const globalNote = scopedKey.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
|
|
42790
|
+
moduleLogger$1.warn(`Qwen permissions: imported ${quoteQualifiedKey({
|
|
42791
|
+
groupName,
|
|
42792
|
+
key
|
|
42793
|
+
})} = ${quoteValueForWarning(value)}. ${globalNote} Review it before generating with the global scope.`);
|
|
42794
|
+
}
|
|
42795
|
+
}
|
|
42796
|
+
}
|
|
42236
42797
|
var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
42237
42798
|
constructor(params) {
|
|
42238
42799
|
super({
|
|
@@ -42261,7 +42822,8 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
42261
42822
|
relativeDirPath: paths.relativeDirPath,
|
|
42262
42823
|
relativeFilePath: paths.relativeFilePath,
|
|
42263
42824
|
fileContent,
|
|
42264
|
-
validate
|
|
42825
|
+
validate,
|
|
42826
|
+
global
|
|
42265
42827
|
});
|
|
42266
42828
|
}
|
|
42267
42829
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
@@ -42277,6 +42839,7 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
42277
42839
|
} catch (error) {
|
|
42278
42840
|
throw new Error(`Failed to parse existing Qwen settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
42279
42841
|
}
|
|
42842
|
+
const displayPath = toPosixPath(join(global ? "~" : ".", paths.relativeDirPath, paths.relativeFilePath));
|
|
42280
42843
|
const config = rulesyncPermissions.getJson();
|
|
42281
42844
|
const { allow, ask, deny } = convertRulesyncToQwenPermissions(config);
|
|
42282
42845
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toQwenToolName(category)));
|
|
@@ -42295,24 +42858,25 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
42295
42858
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
42296
42859
|
else delete mergedPermissions.deny;
|
|
42297
42860
|
const override = config.qwencode;
|
|
42298
|
-
if (override?.autoMode !== void 0)
|
|
42299
|
-
|
|
42300
|
-
|
|
42301
|
-
|
|
42302
|
-
|
|
42303
|
-
};
|
|
42304
|
-
if (override?.security !== void 0) {
|
|
42305
|
-
const scopedSecurity = scopeOverrideSecurity(override.security, {
|
|
42861
|
+
if (override?.autoMode !== void 0) {
|
|
42862
|
+
const scopedAutoMode = scopeOverrideGroup({ autoMode: override.autoMode }, {
|
|
42863
|
+
groupName: "permissions",
|
|
42864
|
+
scopedKeys: QWEN_SCOPED_PERMISSIONS_KEYS,
|
|
42865
|
+
existingGroup: existingPermissions,
|
|
42306
42866
|
global,
|
|
42307
|
-
|
|
42867
|
+
filePath: displayPath,
|
|
42308
42868
|
logger
|
|
42309
42869
|
});
|
|
42310
|
-
|
|
42311
|
-
...asPlainRecord(settings.security),
|
|
42312
|
-
...scopedSecurity
|
|
42313
|
-
};
|
|
42314
|
-
if (Object.keys(mergedSecurity).length > 0) patch.security = mergedSecurity;
|
|
42870
|
+
if (scopedAutoMode.autoMode !== void 0) mergedPermissions.autoMode = scopedAutoMode.autoMode;
|
|
42315
42871
|
}
|
|
42872
|
+
const patch = { permissions: mergedPermissions };
|
|
42873
|
+
Object.assign(patch, buildOverrideGroupsPatch({
|
|
42874
|
+
settings,
|
|
42875
|
+
override,
|
|
42876
|
+
global,
|
|
42877
|
+
filePath: displayPath,
|
|
42878
|
+
logger
|
|
42879
|
+
}));
|
|
42316
42880
|
const fileContent = applySharedConfigPatch({
|
|
42317
42881
|
fileKey: sharedConfigFileKey(paths),
|
|
42318
42882
|
feature: "permissions",
|
|
@@ -42344,16 +42908,17 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
42344
42908
|
ask: permissions.ask ?? [],
|
|
42345
42909
|
deny: permissions.deny ?? []
|
|
42346
42910
|
});
|
|
42347
|
-
const
|
|
42348
|
-
|
|
42349
|
-
|
|
42350
|
-
|
|
42351
|
-
|
|
42352
|
-
}
|
|
42911
|
+
const overrideGroups = {
|
|
42912
|
+
tools: pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS),
|
|
42913
|
+
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS)
|
|
42914
|
+
};
|
|
42915
|
+
if (!this.global) warnAboutImportedScopedKeys(overrideGroups);
|
|
42353
42916
|
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
42354
42917
|
const qwencodeOverride = {};
|
|
42355
|
-
|
|
42356
|
-
|
|
42918
|
+
for (const { groupName } of QWEN_OVERRIDE_GROUPS) {
|
|
42919
|
+
const group = overrideGroups[groupName];
|
|
42920
|
+
if (Object.keys(group).length > 0) qwencodeOverride[groupName] = group;
|
|
42921
|
+
}
|
|
42357
42922
|
if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
|
|
42358
42923
|
const result = { ...config };
|
|
42359
42924
|
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
@@ -42517,6 +43082,137 @@ function buildReasonixPermissionEntry(toolName, pattern) {
|
|
|
42517
43082
|
if (pattern === "*") return toolName;
|
|
42518
43083
|
return `${toolName}(${pattern})`;
|
|
42519
43084
|
}
|
|
43085
|
+
/** How Reasonix is named in the warnings this file emits. */
|
|
43086
|
+
const REASONIX_TOOL_LABEL = "Reasonix";
|
|
43087
|
+
/** The `[permissions]` key the override's `allowDynamicBash` writes and reads. */
|
|
43088
|
+
const REASONIX_ALLOW_DYNAMIC_BASH_KEY = "allow_dynamic_bash";
|
|
43089
|
+
/**
|
|
43090
|
+
* `[sandbox]` keys whose authored value loosens the enforcement layer beneath
|
|
43091
|
+
* the permission policy: they take Bash out of its OS jail, open that jail to
|
|
43092
|
+
* the network, or widen where the file-writing built-ins may write. Written —
|
|
43093
|
+
* the ordinary uses are far too common to refuse — but never silently, the same
|
|
43094
|
+
* stance `DEVIN_TRUST_AFFECTING_SANDBOX_PATHS` takes, and `widens` likewise
|
|
43095
|
+
* names the restrictive value so a spelling Reasonix does not recognize is
|
|
43096
|
+
* reported rather than waved through.
|
|
43097
|
+
*
|
|
43098
|
+
* `forbid_read` is not here: it restricts, so it loosens by losing entries
|
|
43099
|
+
* rather than by holding one, which needs the before/after comparison
|
|
43100
|
+
* {@link REASONIX_RESTRICTION_LOSING_SANDBOX_PATHS} below does instead.
|
|
43101
|
+
*
|
|
43102
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
43103
|
+
*/
|
|
43104
|
+
const REASONIX_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
43105
|
+
{
|
|
43106
|
+
path: ["bash"],
|
|
43107
|
+
reason: "anything but 'enforce' takes Bash out of the OS sandbox, so a command may write and read wherever the user can",
|
|
43108
|
+
widens: (value) => value !== "enforce"
|
|
43109
|
+
},
|
|
43110
|
+
{
|
|
43111
|
+
path: ["network"],
|
|
43112
|
+
reason: "lets sandboxed Bash reach the network",
|
|
43113
|
+
widens: isNotFalse
|
|
43114
|
+
},
|
|
43115
|
+
{
|
|
43116
|
+
path: ["allow_write"],
|
|
43117
|
+
reason: "adds directories the file-writing tools may modify outside the workspace root, which a headless run would otherwise refuse",
|
|
43118
|
+
widens: isNonEmptyList
|
|
43119
|
+
},
|
|
43120
|
+
{
|
|
43121
|
+
path: ["workspace_root"],
|
|
43122
|
+
reason: "moves the root the file-writing tools and sandboxed Bash are confined to, so what they may reach is decided by this path rather than by the project directory",
|
|
43123
|
+
widens: escapesTheProject
|
|
43124
|
+
}
|
|
43125
|
+
];
|
|
43126
|
+
/**
|
|
43127
|
+
* Whether a `workspace_root` points somewhere other than inside the project the
|
|
43128
|
+
* generate runs in. The key moves the write confinement rather than adding to
|
|
43129
|
+
* it, so the ordinary value — the project directory, spelled relatively — would
|
|
43130
|
+
* otherwise be announced on every generate; anything else is the case worth
|
|
43131
|
+
* naming, since it is how a fetched permissions file would put `~/.ssh` or
|
|
43132
|
+
* `C:\\Users\\<user>` inside the jail: an absolute path in either flavour, one
|
|
43133
|
+
* carrying a drive letter, a home-relative one, a shell or environment
|
|
43134
|
+
* expansion, and any path holding a `..` segment — even one that would land back
|
|
43135
|
+
* inside, since resolving it here would only be a guess at what Reasonix does.
|
|
43136
|
+
* Both path flavours are asked because the file is authored on one machine and
|
|
43137
|
+
* generated on another, so a Windows-shaped root reaching a POSIX check must not
|
|
43138
|
+
* read as relative. Anything that is not a string is reported, per the fail-safe
|
|
43139
|
+
* rule the predicates in `sandbox-trust.ts` follow.
|
|
43140
|
+
*/
|
|
43141
|
+
function escapesTheProject(value) {
|
|
43142
|
+
if (typeof value !== "string") return true;
|
|
43143
|
+
const trimmed = value.trim();
|
|
43144
|
+
if (trimmed === "") return false;
|
|
43145
|
+
if (trimmed.startsWith("~")) return true;
|
|
43146
|
+
if (posix.isAbsolute(trimmed) || win32.isAbsolute(trimmed)) return true;
|
|
43147
|
+
if (/^[A-Za-z]:/.test(trimmed)) return true;
|
|
43148
|
+
if (trimmed.includes("$") || /%[^%]+%/.test(trimmed)) return true;
|
|
43149
|
+
return trimmed.split(/[\\/]/).includes("..");
|
|
43150
|
+
}
|
|
43151
|
+
/**
|
|
43152
|
+
* The `[sandbox]` key that restricts, and so loosens by losing entries rather
|
|
43153
|
+
* than by holding a value. The override is shallow-merged over the existing
|
|
43154
|
+
* `[sandbox]` at its top level, so an authored `forbid_read` replaces the list
|
|
43155
|
+
* the file had whole — emptying it, or dropping the `${HOME}/.ssh` entry
|
|
43156
|
+
* Reasonix's own example recommends, opens exactly what the list kept closed.
|
|
43157
|
+
*
|
|
43158
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
43159
|
+
*/
|
|
43160
|
+
const REASONIX_RESTRICTION_LOSING_SANDBOX_PATHS = [{
|
|
43161
|
+
path: ["forbid_read"],
|
|
43162
|
+
reason: "drops paths the list already in the file kept out of read, list and search",
|
|
43163
|
+
loosens: ({ before, after }) => before.some((entry) => !after.includes(entry))
|
|
43164
|
+
}];
|
|
43165
|
+
/**
|
|
43166
|
+
* Everything worth naming about the `[sandbox]` table this generate is about to
|
|
43167
|
+
* write: the authored values that loosen enforcement, the restrictions the
|
|
43168
|
+
* shallow merge would drop, and a `[sandbox]` the file holds in some shape other
|
|
43169
|
+
* than a table, which the write replaces wholesale. The widening check reads the
|
|
43170
|
+
* authored block alone — a loosening value the file already held is the user's
|
|
43171
|
+
* own, and re-announcing it on every generate would bury the values rulesync
|
|
43172
|
+
* actually wrote — while a loss can only be seen from both sides.
|
|
43173
|
+
*/
|
|
43174
|
+
function collectSandboxOverlayEntries({ existing, authored, merged }) {
|
|
43175
|
+
return [
|
|
43176
|
+
...existing !== void 0 && !isRecord$1(existing) ? [{
|
|
43177
|
+
label: "sandbox",
|
|
43178
|
+
reason: replacedUnreadableReason({
|
|
43179
|
+
shape: "object",
|
|
43180
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
43181
|
+
})
|
|
43182
|
+
}] : [],
|
|
43183
|
+
...collectTrustAffectingSandboxPaths({
|
|
43184
|
+
sandbox: asReasonixRecord(authored),
|
|
43185
|
+
paths: REASONIX_TRUST_AFFECTING_SANDBOX_PATHS
|
|
43186
|
+
}),
|
|
43187
|
+
...collectRestrictionLosingSandboxEntries({
|
|
43188
|
+
existing: asReasonixRecord(existing),
|
|
43189
|
+
merged,
|
|
43190
|
+
paths: REASONIX_RESTRICTION_LOSING_SANDBOX_PATHS,
|
|
43191
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
43192
|
+
})
|
|
43193
|
+
];
|
|
43194
|
+
}
|
|
43195
|
+
/**
|
|
43196
|
+
* Writes the override's `allow_dynamic_bash` into `[permissions]`, where it sits
|
|
43197
|
+
* beside allow/ask/deny rather than in a table of its own, and reports it when
|
|
43198
|
+
* it is being turned on: it widens what a shareable permissions file lets run
|
|
43199
|
+
* with no human in the loop. Only an authored value is written — leaving the key
|
|
43200
|
+
* out of the override keeps whatever the file already had — and turning it off
|
|
43201
|
+
* narrows, so that stays quiet. Only a literal `false` is quiet, not everything
|
|
43202
|
+
* falsy: `getJson()` casts rather than parses, so a `--no-validate` run can put
|
|
43203
|
+
* a value the schema forbids here, and a value Reasonix might still coerce is
|
|
43204
|
+
* not something to write in silence. The entries are returned rather than logged so
|
|
43205
|
+
* one generate still produces one warning naming everything it wrote.
|
|
43206
|
+
*/
|
|
43207
|
+
function applyAllowDynamicBash({ permissions, authored }) {
|
|
43208
|
+
if (authored === void 0) return [];
|
|
43209
|
+
permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY] = authored;
|
|
43210
|
+
if (!isNotFalse(authored)) return [];
|
|
43211
|
+
return [{
|
|
43212
|
+
label: `permissions.${REASONIX_ALLOW_DYNAMIC_BASH_KEY}`,
|
|
43213
|
+
reason: "lets an Allow fallback, Auto included, run the nested and indirect Bash that otherwise needs a human or an exact-literal grant — command and process substitution, a dynamic command name, 'eval', 'source', 'sh -c' and their kind"
|
|
43214
|
+
}];
|
|
43215
|
+
}
|
|
42520
43216
|
function parseReasonixConfig(fileContent) {
|
|
42521
43217
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
42522
43218
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -42573,6 +43269,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
42573
43269
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
42574
43270
|
const paths = this.getSettablePaths({ global });
|
|
42575
43271
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
43272
|
+
const relativeFilePathForLog = toPosixPath(join(paths.relativeDirPath, paths.relativeFilePath));
|
|
42576
43273
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
42577
43274
|
const parsed = parseReasonixConfig(existingContent);
|
|
42578
43275
|
const config = rulesyncPermissions.getJson();
|
|
@@ -42617,11 +43314,23 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
42617
43314
|
...deny,
|
|
42618
43315
|
...rawDeny
|
|
42619
43316
|
]);
|
|
43317
|
+
const trustAffecting = applyAllowDynamicBash({
|
|
43318
|
+
permissions: mergedPermissions,
|
|
43319
|
+
authored: override?.allowDynamicBash
|
|
43320
|
+
});
|
|
42620
43321
|
const patch = { permissions: mergedPermissions };
|
|
42621
|
-
if (override?.sandbox !== void 0)
|
|
42622
|
-
|
|
42623
|
-
|
|
42624
|
-
|
|
43322
|
+
if (override?.sandbox !== void 0) {
|
|
43323
|
+
const mergedSandbox = {
|
|
43324
|
+
...asReasonixRecord(parsed.sandbox),
|
|
43325
|
+
...asReasonixRecord(override.sandbox)
|
|
43326
|
+
};
|
|
43327
|
+
patch.sandbox = mergedSandbox;
|
|
43328
|
+
trustAffecting.push(...collectSandboxOverlayEntries({
|
|
43329
|
+
existing: parsed.sandbox,
|
|
43330
|
+
authored: override.sandbox,
|
|
43331
|
+
merged: mergedSandbox
|
|
43332
|
+
}));
|
|
43333
|
+
}
|
|
42625
43334
|
if (override?.agent !== void 0) {
|
|
42626
43335
|
const mergedAgent = {
|
|
42627
43336
|
...asReasonixRecord(parsed.agent),
|
|
@@ -42629,9 +43338,16 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
42629
43338
|
};
|
|
42630
43339
|
const retired = REASONIX_RETIRED_AGENT_KEYS.filter((key) => mergedAgent[key] !== void 0);
|
|
42631
43340
|
for (const key of retired) delete mergedAgent[key];
|
|
42632
|
-
if (retired.length > 0) logger?.warn(`Reasonix permissions: removing ${retired.map((key) => `"${key}"`).join(", ")} from [agent] in ${
|
|
43341
|
+
if (retired.length > 0) logger?.warn(`Reasonix permissions: removing ${retired.map((key) => `"${key}"`).join(", ")} from [agent] in ${relativeFilePathForLog}; Reasonix took the key off its config surface in v1.17.18, so what it used to express now belongs in the shared \`permission\` block.`);
|
|
42633
43342
|
patch.agent = mergedAgent;
|
|
42634
43343
|
}
|
|
43344
|
+
warnOnTrustAffectingEntries({
|
|
43345
|
+
toolLabel: REASONIX_TOOL_LABEL,
|
|
43346
|
+
noun: "change",
|
|
43347
|
+
entries: trustAffecting,
|
|
43348
|
+
relativeFilePath: relativeFilePathForLog,
|
|
43349
|
+
logger
|
|
43350
|
+
});
|
|
42635
43351
|
return new ReasonixPermissions({
|
|
42636
43352
|
outputRoot,
|
|
42637
43353
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -42666,6 +43382,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
42666
43382
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
42667
43383
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
42668
43384
|
const reasonixOverride = {};
|
|
43385
|
+
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
43386
|
+
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
42669
43387
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
42670
43388
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
42671
43389
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -68704,4 +69422,4 @@ async function importChecksCore(params) {
|
|
|
68704
69422
|
//#endregion
|
|
68705
69423
|
export { RulesyncCheckFrontmatterSchema as $, ALL_TOOL_TARGETS_WITH_WILDCARD as $t, FACTORYDROID_DIR as A, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as An, fileExists as At, RulesyncSkillFrontmatterSchema as B, stripControlCharacters as Bn, readFileContent as Bt, CODEXCLI_BASH_RULES_FILE_NAME as C, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Cn, assertDirectoryIfExists as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, RULESYNC_RULES_RELATIVE_DIR_PATH as Dn, createTempDirectory as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_RELATIVE_DIR_PATH as En, checkPathTraversal as Et, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as F, formatError as Fn, isSymlink as Ft, RulesyncIgnore as G, removeFileStrict as Gt, RulesyncRuleFrontmatterSchema as H, stripHiddenCharacters as Hn, removeDirectory as Ht, getLocalSkillDirNames as I, truncateText as In, listDirectoryEntryNames as It, resolveRulesyncSourceWritePath as J, runWithDirectoryRollback as Jt, RulesyncHooks as K, removeTempDirectory as Kt, RulesyncSubagent as L, hasDeceptiveHiddenCharacters as Ln, listFilePathsRecursively as Lt, caseFoldIdentity as M, ALL_FEATURES as Mn, getHomeDirectory as Mt, groupSpellingsByCaseFoldedIdentity as N, ALL_FEATURES_WITH_WILDCARD as Nn, isFileNotFoundError as Nt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_SKILLS_RELATIVE_DIR_PATH as On, directoryExists as Ot, AUGMENTCODE_DIR as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, isFileSystemError as Pt, RulesyncCheck as Q, ALL_TOOL_TARGETS as Qt, RulesyncSubagentFrontmatterSchema as R, hasEnclosingMarkOutsideKeycap as Rn, listSubdirectoryNames as Rt, ChecksProcessor as S, RULESYNC_PERMISSIONS_FILE_NAME as Sn, applyFileMode as St, CLAUDECODE_DIR as T, RULESYNC_PERMISSIONS_SCHEMA_URL as Tn, assertWritablePathInsideRoot as Tt, RulesyncPermissions as U, removeDirectoryStrict as Ut, RulesyncRule as V, stripControlCharactersKeepingLineFeeds as Vn, readFileContentOrNull as Vt, RulesyncMcp as W, removeFile as Wt, RulesyncCommand as X, writeFileBuffer as Xt, parseJsonc as Y, toPosixPath as Yt, RulesyncCommandFrontmatterSchema as Z, writeFileContent as Zt, IgnoreProcessor as _, RULESYNC_MCP_FILE_NAME as _n, withFallbackLoggerTarget as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as an, mergeInputRootConfigs as at, QWENCODE_DIR as b, RULESYNC_MCP_SCHEMA_URL as bn, CLIError as bt, RulesProcessor as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as cn, ConfigFileSchema as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dn, findControlCharacter as dt, PACKAGING_TOOL_TARGETS as en, stringifyFrontmatter as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_HOOKS_FILE_NAME as fn, ConsoleLogger as ft, McpProcessor as g, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as gn, warnOnConflictingFlags as gt, shortenToWidth as h, RULESYNC_IGNORE_RELATIVE_FILE_PATH as hn, fallbackLogger as ht, inspectInputRoots as i, RULESYNC_AIIGNORE_FILE_NAME as in, ConfigResolver as it, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, getFileSize as jt, CLAUDECODE_SKILLS_DIR_PATH as k, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ensureDir as kt, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as ln, GITIGNORE_DESTINATION_KEY as lt, displayWidthOf as m, RULESYNC_HOOKS_RELATIVE_FILE_PATH as mn, WarningCollectingLogger as mt, formatSourceLoadFailure as n, CURATED_RULES_FEATURE_SUBDIR as nn, SHARED_USER_MANAGED_CONFIG_PATHS as nt, convertFromTool as o, RULESYNC_CHECKS_RELATIVE_DIR_PATH as on, resolveEffectiveInputRoots as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_LEGACY_FILE_NAME as pn, JsonLogger as pt, getRulesyncSourceCandidates as q, resolvePath as qt, generate as r, MAX_FILE_SIZE as rn, SKILL_FILE_NAME as rt, isPackagingToolTarget as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as sn, CONFLICTING_TARGET_PAIRS as st, importFromTool as t, ToolTargetSchema as tn, loadYaml as tt, SkillsProcessor as u, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as un, SourceEntrySchema as ut, HooksProcessor as v, RULESYNC_MCP_LEGACY_FILE_NAME as vn, resetRunWarningState as vt, CODEXCLI_DIR as w, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as wn, assertTreeContainsNoSymlinks as wt, QWENCODE_LOCAL_RULE_FILE_NAME as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, ErrorCodes as xt, CommandsProcessor as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, withWarnOnceScope as yt, RulesyncSkill as z, quoteForLog as zn, pathEscapesRoot as zt };
|
|
68706
69424
|
|
|
68707
|
-
//# sourceMappingURL=import-
|
|
69425
|
+
//# sourceMappingURL=import-DyWRanf6.js.map
|