rulesync 39.0.0 → 40.0.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 +626 -385
- package/dist/cli/index.js +626 -385
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-HHSMWQi-.cjs → import-BRZzK3w5.cjs} +923 -128
- package/dist/{import-CfVOkld5.js → import-Chn7Nqnm.js} +896 -113
- package/dist/{import-CfVOkld5.js.map → import-Chn7Nqnm.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -457,36 +457,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
457
457
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
458
458
|
//#endregion
|
|
459
459
|
//#region src/constants/rulesync-paths.ts
|
|
460
|
-
const { join: join$
|
|
460
|
+
const { join: join$441 } = node_path.posix;
|
|
461
461
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
462
462
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
463
463
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
464
464
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
465
465
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
466
466
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
467
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
467
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$441(RULES_FEATURE_SUBDIR, ".curated");
|
|
468
468
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
469
469
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
470
470
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
471
471
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
472
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
473
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
475
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
477
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
478
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
480
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
481
|
-
join$
|
|
482
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
483
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$441(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
473
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
475
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
477
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
478
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
479
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
480
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
481
|
+
join$441(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
482
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
483
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
484
484
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
485
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
485
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
486
486
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
487
487
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
488
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
489
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
488
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
489
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$441(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
490
490
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
491
491
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
492
492
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -1070,6 +1070,57 @@ async function assertWritablePathInsideRoot(params) {
|
|
|
1070
1070
|
existingPath = parentPath;
|
|
1071
1071
|
}
|
|
1072
1072
|
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Thrown by {@link assertPathResolvesInsideRoot} when a symbolic link leads
|
|
1075
|
+
* the target outside the root, or the target is reached through a dangling
|
|
1076
|
+
* link. Any other error it throws comes from the filesystem itself.
|
|
1077
|
+
*/
|
|
1078
|
+
var PathOutsideRootError = class extends Error {
|
|
1079
|
+
constructor(message, options) {
|
|
1080
|
+
super(message, options);
|
|
1081
|
+
this.name = "PathOutsideRootError";
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
/**
|
|
1085
|
+
* Refuse a write or removal target whose real location is outside `rootPath`.
|
|
1086
|
+
*
|
|
1087
|
+
* Unlike {@link assertWritablePathInsideRoot}, a symbolic link on the way is
|
|
1088
|
+
* allowed as long as it resolves inside the root: projects deliberately link a
|
|
1089
|
+
* skill directory to a shared one next to it. What this stops is a link that
|
|
1090
|
+
* leads elsewhere — a cloned repository committing `.rulesync/skills/x ->
|
|
1091
|
+
* ~/.ssh`, say — which a textual check such as {@link checkPathTraversal}
|
|
1092
|
+
* cannot see. The deepest existing ancestor of `targetPath` is resolved, and
|
|
1093
|
+
* an existing target that is itself a link is resolved too.
|
|
1094
|
+
*/
|
|
1095
|
+
async function assertPathResolvesInsideRoot(params) {
|
|
1096
|
+
const { rootPath, targetPath } = params;
|
|
1097
|
+
let realRoot;
|
|
1098
|
+
try {
|
|
1099
|
+
realRoot = await (0, node_fs_promises.realpath)(rootPath);
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
if (isFileNotFoundError(error)) {
|
|
1102
|
+
if (await isSymlink(rootPath)) throw new PathOutsideRootError(`Refusing to write through a dangling symbolic link: ${rootPath}.`, { cause: error });
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
throw error;
|
|
1106
|
+
}
|
|
1107
|
+
let existingPath = (0, node_path.resolve)(targetPath);
|
|
1108
|
+
while (true) {
|
|
1109
|
+
let realExisting;
|
|
1110
|
+
try {
|
|
1111
|
+
realExisting = await (0, node_fs_promises.realpath)(existingPath);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
1114
|
+
if (await isSymlink(existingPath)) throw new PathOutsideRootError(`Refusing to write through a dangling symbolic link: ${targetPath}.`, { cause: error });
|
|
1115
|
+
const parentPath = (0, node_path.dirname)(existingPath);
|
|
1116
|
+
if (parentPath === existingPath) throw error;
|
|
1117
|
+
existingPath = parentPath;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (pathEscapesRoot((0, node_path.relative)(realRoot, realExisting))) throw new PathOutsideRootError(`Path resolves outside ${rootPath} through a symbolic link: ${targetPath}.`);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1073
1124
|
async function assertTreeContainsNoSymlinks(dirPath) {
|
|
1074
1125
|
for (const entry of await (0, node_fs_promises.readdir)(dirPath, { withFileTypes: true })) {
|
|
1075
1126
|
const entryPath = (0, node_path.join)(dirPath, entry.name);
|
|
@@ -5134,6 +5185,40 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
5134
5185
|
"userPromptExpansion"
|
|
5135
5186
|
];
|
|
5136
5187
|
/**
|
|
5188
|
+
* The {@link COPILOT_HOOK_EVENTS} whose hook entries honor an optional
|
|
5189
|
+
* `matcher` regex. The hooks reference documents matcher filtering on
|
|
5190
|
+
* `preToolUse` / `postToolUse` / `permissionRequest` (tool name), `preCompact`
|
|
5191
|
+
* (trigger) and `subagentStart` (agent name), plus `notification`; the last
|
|
5192
|
+
* two are not authorable for `copilot`, so they are not listed here. VS Code's
|
|
5193
|
+
* migration guide confirms the Copilot harness "applies documented matcher
|
|
5194
|
+
* rules" (only the legacy Local harness ignores them).
|
|
5195
|
+
*
|
|
5196
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
5197
|
+
* @see https://code.visualstudio.com/docs/agent-customization/migrate-customizations
|
|
5198
|
+
*/
|
|
5199
|
+
const COPILOT_MATCHER_HOOK_EVENTS = [
|
|
5200
|
+
"preToolUse",
|
|
5201
|
+
"postToolUse",
|
|
5202
|
+
"preCompact",
|
|
5203
|
+
"subagentStart"
|
|
5204
|
+
];
|
|
5205
|
+
/**
|
|
5206
|
+
* The {@link COPILOTCLI_HOOK_EVENTS} whose hook entries honor an optional
|
|
5207
|
+
* `matcher` regex: the shared {@link COPILOT_MATCHER_HOOK_EVENTS} plus the two
|
|
5208
|
+
* CLI-only events, `permissionRequest` (tool name) and `notification`
|
|
5209
|
+
* (notification type).
|
|
5210
|
+
*
|
|
5211
|
+
* Confirmed by the upstream changelog: v1.0.36 (2026-04-24, `preToolUse.matcher`
|
|
5212
|
+
* honored) and v1.0.63 (2026-06-15, "PostToolUse hook matchers (e.g.
|
|
5213
|
+
* `Edit|Write`) are now honored instead of silently dropped").
|
|
5214
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
5215
|
+
*/
|
|
5216
|
+
const COPILOTCLI_MATCHER_HOOK_EVENTS = [
|
|
5217
|
+
...COPILOT_MATCHER_HOOK_EVENTS,
|
|
5218
|
+
"permissionRequest",
|
|
5219
|
+
"notification"
|
|
5220
|
+
];
|
|
5221
|
+
/**
|
|
5137
5222
|
* Hook events supported by the GitHub Copilot CLI (`copilotcli-hooks.ts`).
|
|
5138
5223
|
*
|
|
5139
5224
|
* The CLI documents a wider event surface than the shared cloud-agent set, so
|
|
@@ -5287,12 +5372,15 @@ const KIRO_IDE_HOOK_EVENTS = [
|
|
|
5287
5372
|
/**
|
|
5288
5373
|
* Hook events supported by Google Antigravity (both the IDE and the CLI).
|
|
5289
5374
|
*
|
|
5290
|
-
* Antigravity exposes a Claude-style hooks surface covering the
|
|
5291
|
-
* tool/model/turn lifecycle events it documents: `
|
|
5292
|
-
* `PreInvocation`, `PostInvocation`, and `Stop`.
|
|
5293
|
-
* (`PreInvocation`/`PostInvocation`)
|
|
5375
|
+
* Antigravity exposes a Claude-style hooks surface covering the six
|
|
5376
|
+
* session/tool/model/turn lifecycle events it documents: `SessionStart`,
|
|
5377
|
+
* `PreToolUse`, `PostToolUse`, `PreInvocation`, `PostInvocation`, and `Stop`.
|
|
5378
|
+
* `SessionStart`, the model-invocation events (`PreInvocation`/`PostInvocation`)
|
|
5379
|
+
* and `Stop` are matcher-less handler lists.
|
|
5380
|
+
* https://antigravity.google/docs/hooks
|
|
5294
5381
|
*/
|
|
5295
5382
|
const ANTIGRAVITY_HOOK_EVENTS = [
|
|
5383
|
+
"sessionStart",
|
|
5296
5384
|
"preToolUse",
|
|
5297
5385
|
"postToolUse",
|
|
5298
5386
|
"preModelInvocation",
|
|
@@ -6065,8 +6153,8 @@ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
6065
6153
|
/**
|
|
6066
6154
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
6067
6155
|
*
|
|
6068
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set —
|
|
6069
|
-
* of v0.
|
|
6156
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 46 entries as
|
|
6157
|
+
* of v0.22.3 (tag `v0.22.3`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
6070
6158
|
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
6071
6159
|
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
6072
6160
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
@@ -6090,15 +6178,16 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
6090
6178
|
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
6091
6179
|
* Shell hooks are gated on the same set.
|
|
6092
6180
|
*
|
|
6093
|
-
* Verified against the tag `v0.
|
|
6181
|
+
* Verified against the tag `v0.22.3`: `VALID_HOOKS` holds 46 entries, grown
|
|
6094
6182
|
* from 23 at `v2026.8.3` (v0.20.0) and 37 at `v2026.8.16` (v0.20.2) — v0.21
|
|
6095
6183
|
* added `agent_loop_stopped` and `on_room_member_activity`, v0.21.5 added
|
|
6096
|
-
* `pre_auxiliary_call` and `post_auxiliary_call`,
|
|
6184
|
+
* `pre_auxiliary_call` and `post_auxiliary_call`, v0.21.6 added
|
|
6097
6185
|
* `post_gateway_admission`, `on_human_input_request` and
|
|
6098
|
-
* `on_human_input_resolved
|
|
6186
|
+
* `on_human_input_resolved`, and v0.22.3 added `on_session_delete` and
|
|
6187
|
+
* `post_compaction`. This list holds 45 of them — see
|
|
6099
6188
|
* {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
6100
6189
|
*
|
|
6101
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6190
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/hermes_cli/plugins.py
|
|
6102
6191
|
*/
|
|
6103
6192
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
6104
6193
|
"pre_tool_call",
|
|
@@ -6122,6 +6211,7 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6122
6211
|
"on_session_end",
|
|
6123
6212
|
"on_session_finalize",
|
|
6124
6213
|
"on_session_reset",
|
|
6214
|
+
"on_session_delete",
|
|
6125
6215
|
"on_skill_lifecycle",
|
|
6126
6216
|
"subagent_start",
|
|
6127
6217
|
"subagent_stop",
|
|
@@ -6143,7 +6233,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6143
6233
|
"on_kanban_task_updated",
|
|
6144
6234
|
"on_kanban_dispatch_tick",
|
|
6145
6235
|
"gateway_platform_event",
|
|
6146
|
-
"pre_command"
|
|
6236
|
+
"pre_command",
|
|
6237
|
+
"post_compaction"
|
|
6147
6238
|
];
|
|
6148
6239
|
/**
|
|
6149
6240
|
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
@@ -6155,8 +6246,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6155
6246
|
* rulesync only ever writes shell hooks, so these are excluded from
|
|
6156
6247
|
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
6157
6248
|
*
|
|
6158
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6159
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6249
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
6250
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/agent/shell_hooks.py — `_parse_hooks_block`
|
|
6160
6251
|
*/
|
|
6161
6252
|
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
6162
6253
|
/**
|
|
@@ -6338,10 +6429,11 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6338
6429
|
const AUGMENTCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENTCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6339
6430
|
/**
|
|
6340
6431
|
* Map canonical camelCase event names to Antigravity PascalCase.
|
|
6341
|
-
* Antigravity uses the same PascalCase names as Claude for its tool/turn events,
|
|
6432
|
+
* Antigravity uses the same PascalCase names as Claude for its session/tool/turn events,
|
|
6342
6433
|
* plus `PreInvocation`/`PostInvocation` for the model-invocation lifecycle.
|
|
6343
6434
|
*/
|
|
6344
6435
|
const CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES = {
|
|
6436
|
+
sessionStart: "SessionStart",
|
|
6345
6437
|
preToolUse: "PreToolUse",
|
|
6346
6438
|
postToolUse: "PostToolUse",
|
|
6347
6439
|
preModelInvocation: "PreInvocation",
|
|
@@ -7360,14 +7452,19 @@ const toolScopedMcpSchema = zod_mini.z.looseObject({ mcpServers: zod_mini.z.opti
|
|
|
7360
7452
|
/**
|
|
7361
7453
|
* Kimi Code's tool-scoped block also carries the `[mcp]` defaults from its
|
|
7362
7454
|
* shared user config: connect and per-tool-call timeouts that apply to *every*
|
|
7363
|
-
* MCP server, including ones rulesync did not write.
|
|
7364
|
-
* `
|
|
7455
|
+
* MCP server, including ones rulesync did not write, and (Kimi Code 2.3.0)
|
|
7456
|
+
* `defaultDeferred`, which loads every server's tools on demand unless the
|
|
7457
|
+
* server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
|
|
7458
|
+
* in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
|
|
7459
|
+
* experimental `tool-select` flag is on, and is captured when a server connects
|
|
7460
|
+
* (MoonshotAI/kimi-code#4210).
|
|
7365
7461
|
*
|
|
7366
7462
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
7367
7463
|
*/
|
|
7368
7464
|
const kimiCodeScopedMcpSchema = zod_mini.z.extend(toolScopedMcpSchema, {
|
|
7369
7465
|
startupTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7370
|
-
toolTimeoutMs: zod_mini.z.optional(zod_mini.z.number())
|
|
7466
|
+
toolTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7467
|
+
defaultDeferred: zod_mini.z.optional(zod_mini.z.boolean())
|
|
7371
7468
|
});
|
|
7372
7469
|
/**
|
|
7373
7470
|
* Qwen Code's tool-scoped block also carries the server-level allow/deny lists
|
|
@@ -8136,13 +8233,16 @@ const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8136
8233
|
});
|
|
8137
8234
|
/**
|
|
8138
8235
|
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
8139
|
-
* before prompting. `always-ask` auto-approves only read-only tools,
|
|
8140
|
-
*
|
|
8236
|
+
* before prompting. `always-ask` auto-approves only read-only tools,
|
|
8237
|
+
* `always-ask-cached` (Ask and remember) also prompts before file reads and
|
|
8238
|
+
* lets a prompt remember the granted file or folder, `write` also
|
|
8239
|
+
* auto-approves workspace writes, and `yolo` (oh-my-pi's default) every tier.
|
|
8141
8240
|
*
|
|
8142
8241
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
8143
8242
|
*/
|
|
8144
8243
|
const OMP_APPROVAL_MODES = [
|
|
8145
8244
|
"always-ask",
|
|
8245
|
+
"always-ask-cached",
|
|
8146
8246
|
"write",
|
|
8147
8247
|
"yolo"
|
|
8148
8248
|
];
|
|
@@ -8163,10 +8263,13 @@ const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8163
8263
|
])),
|
|
8164
8264
|
/**
|
|
8165
8265
|
* Kimi's `[permission] dangerous_command_guard` switch (Kimi Code 0.40.0,
|
|
8166
|
-
* default `true`). `false` turns off
|
|
8167
|
-
*
|
|
8266
|
+
* default `true`). Since Kimi Code 2.3.0, `false` turns off every built-in
|
|
8267
|
+
* guard confirmation policy: dangerous commands (`rm -rf`, `shutdown` and the
|
|
8268
|
+
* like), sensitive-file access (`.env`, `id_rsa`, ...) and git control-path
|
|
8269
|
+
* access. Before 2.3.0 it only covered dangerous commands.
|
|
8168
8270
|
*
|
|
8169
8271
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
8272
|
+
* @see https://github.com/MoonshotAI/kimi-code/pull/4250
|
|
8170
8273
|
*/
|
|
8171
8274
|
dangerousCommandGuard: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8172
8275
|
rules: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
@@ -8526,6 +8629,8 @@ const TabninePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8526
8629
|
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
8527
8630
|
* @example
|
|
8528
8631
|
* { "mode": "deny" }
|
|
8632
|
+
* @example
|
|
8633
|
+
* { "tools": { "disabled": ["web_fetch"] }, "askOnExternalOrigin": true }
|
|
8529
8634
|
*/
|
|
8530
8635
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8531
8636
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
@@ -8539,7 +8644,9 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8539
8644
|
"ask",
|
|
8540
8645
|
"allow",
|
|
8541
8646
|
"deny"
|
|
8542
|
-
]))
|
|
8647
|
+
])),
|
|
8648
|
+
askOnExternalOrigin: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8649
|
+
tools: zod_mini.z.optional(zod_mini.z.strictObject({ disabled: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) }))
|
|
8543
8650
|
});
|
|
8544
8651
|
/**
|
|
8545
8652
|
* Tool-scoped override block for Qoder. `outputStyle` is written to the
|
|
@@ -11353,6 +11460,15 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
11353
11460
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
11354
11461
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
11355
11462
|
})),
|
|
11463
|
+
xum: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
11464
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11465
|
+
advertise: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11466
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11467
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
11468
|
+
when_to_use: zod_mini.z.optional(zod_mini.z.string()),
|
|
11469
|
+
"when-to-use": zod_mini.z.optional(zod_mini.z.string()),
|
|
11470
|
+
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
11471
|
+
})),
|
|
11356
11472
|
zcode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
11357
11473
|
when_to_use: zod_mini.z.optional(zod_mini.z.string()),
|
|
11358
11474
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -11631,7 +11747,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11631
11747
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11632
11748
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11633
11749
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11634
|
-
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11750
|
+
* commandcode, lettacode, opencode, mimocode, xum). Each tool's own section may override that
|
|
11635
11751
|
* default with a per-target value. A defined section value (including `false`)
|
|
11636
11752
|
* always wins over the root default.
|
|
11637
11753
|
*
|
|
@@ -11651,7 +11767,7 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
11651
11767
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
11652
11768
|
* that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11653
11769
|
* copilotcli, crush, cursor, qwencode, vibe, grokcli, factorydroid, dsh, reasonix, commandcode,
|
|
11654
|
-
* lettacode). Each tool's own section may override that default with a
|
|
11770
|
+
* lettacode, xum). Each tool's own section may override that default with a
|
|
11655
11771
|
* per-target value. A defined section value (including `false`) always wins
|
|
11656
11772
|
* over the root default.
|
|
11657
11773
|
*
|
|
@@ -13966,7 +14082,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13966
14082
|
ownedKeys: [
|
|
13967
14083
|
"permissions",
|
|
13968
14084
|
"sandbox",
|
|
13969
|
-
"agent"
|
|
14085
|
+
"agent",
|
|
14086
|
+
"tools"
|
|
13970
14087
|
]
|
|
13971
14088
|
}
|
|
13972
14089
|
}
|
|
@@ -13987,7 +14104,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13987
14104
|
ownedKeys: [
|
|
13988
14105
|
"permissions",
|
|
13989
14106
|
"sandbox",
|
|
13990
|
-
"agent"
|
|
14107
|
+
"agent",
|
|
14108
|
+
"tools"
|
|
13991
14109
|
]
|
|
13992
14110
|
}
|
|
13993
14111
|
}
|
|
@@ -24112,6 +24230,7 @@ var QwencodeCommand = class QwencodeCommand extends ToolCommand {
|
|
|
24112
24230
|
const REASONIX_PROJECT_MCP_FILE_NAME = "reasonix.toml";
|
|
24113
24231
|
const REASONIX_GLOBAL_DIR = ".reasonix";
|
|
24114
24232
|
const REASONIX_GLOBAL_MCP_FILE_NAME = "config.toml";
|
|
24233
|
+
const REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH = (0, node_path.join)(".reasonix", REASONIX_PROJECT_MCP_FILE_NAME);
|
|
24115
24234
|
const REASONIX_PROJECT_PERMISSIONS_FILE_NAME = REASONIX_PROJECT_MCP_FILE_NAME;
|
|
24116
24235
|
const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
|
|
24117
24236
|
const REASONIX_DIR = REASONIX_GLOBAL_DIR;
|
|
@@ -28459,25 +28578,65 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
28459
28578
|
//#endregion
|
|
28460
28579
|
//#region src/features/hooks/antigravity-hooks.ts
|
|
28461
28580
|
/**
|
|
28462
|
-
*
|
|
28463
|
-
* `
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
|
|
28581
|
+
* Canonical events whose Antigravity counterparts (`SessionStart`,
|
|
28582
|
+
* `PreInvocation`, `PostInvocation`, `Stop`) take no matcher.
|
|
28583
|
+
*/
|
|
28584
|
+
const ANTIGRAVITY_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
28585
|
+
"sessionStart",
|
|
28586
|
+
"preModelInvocation",
|
|
28587
|
+
"postModelInvocation",
|
|
28588
|
+
"stop"
|
|
28589
|
+
]);
|
|
28590
|
+
/**
|
|
28591
|
+
* Antigravity event names whose value is a flat list of handlers
|
|
28592
|
+
* (`"Stop": [ { "type": "command", "command": "..." } ]`) rather than a list
|
|
28593
|
+
* of `{ "matcher", "hooks": [...] }` groups. Antigravity's loader rejects a
|
|
28594
|
+
* wrapped group under these events ("command hook must specify 'command'")
|
|
28595
|
+
* and then skips the whole `hooks.json`.
|
|
28596
|
+
* https://antigravity.google/docs/hooks
|
|
28597
|
+
*/
|
|
28598
|
+
const ANTIGRAVITY_FLAT_HANDLER_EVENTS = new Set([...ANTIGRAVITY_NO_MATCHER_EVENTS].map((event) => lookupOwn({
|
|
28599
|
+
record: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
|
|
28600
|
+
key: event
|
|
28601
|
+
}) ?? event));
|
|
28602
|
+
/**
|
|
28603
|
+
* Antigravity uses a Claude-Code-like `hooks.json`. The `PreToolUse` /
|
|
28604
|
+
* `PostToolUse` events use the matcher-based group shape, while `SessionStart`,
|
|
28605
|
+
* `PreInvocation`, `PostInvocation` and `Stop` are flat handler lists (see
|
|
28606
|
+
* {@link toAntigravityEventMap}). `projectDirVar` is empty because Antigravity
|
|
28607
|
+
* does not document a project-directory variable for commands.
|
|
28467
28608
|
*/
|
|
28468
28609
|
const ANTIGRAVITY_CONVERTER_CONFIG = {
|
|
28469
28610
|
supportedEvents: ANTIGRAVITY_HOOK_EVENTS,
|
|
28470
28611
|
canonicalToToolEventNames: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
|
|
28471
28612
|
toolToCanonicalEventNames: ANTIGRAVITY_TO_CANONICAL_EVENT_NAMES,
|
|
28472
28613
|
projectDirVar: "",
|
|
28473
|
-
noMatcherEvents:
|
|
28474
|
-
"preModelInvocation",
|
|
28475
|
-
"postModelInvocation",
|
|
28476
|
-
"stop"
|
|
28477
|
-
]),
|
|
28614
|
+
noMatcherEvents: ANTIGRAVITY_NO_MATCHER_EVENTS,
|
|
28478
28615
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"])
|
|
28479
28616
|
};
|
|
28480
28617
|
/**
|
|
28618
|
+
* Turn the shared converter's matcher-group output into Antigravity's shape:
|
|
28619
|
+
* the matcher-less events get their handlers directly under the event key.
|
|
28620
|
+
*/
|
|
28621
|
+
function toAntigravityEventMap(eventMap) {
|
|
28622
|
+
return Object.fromEntries(Object.entries(eventMap).map(([event, entries]) => {
|
|
28623
|
+
if (!ANTIGRAVITY_FLAT_HANDLER_EVENTS.has(event)) return [event, entries];
|
|
28624
|
+
return [event, entries.flatMap((entry) => isPlainObject$1(entry) && Array.isArray(entry.hooks) ? entry.hooks : [])];
|
|
28625
|
+
}));
|
|
28626
|
+
}
|
|
28627
|
+
/**
|
|
28628
|
+
* Reverse of {@link toAntigravityEventMap} for import: wrap each flat handler
|
|
28629
|
+
* of a matcher-less event in a `{ hooks: [...] }` group so the shared converter
|
|
28630
|
+
* can read it. An entry that is already a group (`{ hooks: [...] }`, as older
|
|
28631
|
+
* rulesync versions wrote) is kept as is.
|
|
28632
|
+
*/
|
|
28633
|
+
function toMatcherGroupEventMap(eventMap) {
|
|
28634
|
+
return Object.fromEntries(Object.entries(eventMap).map(([event, entries]) => {
|
|
28635
|
+
if (!ANTIGRAVITY_FLAT_HANDLER_EVENTS.has(event) || !Array.isArray(entries)) return [event, entries];
|
|
28636
|
+
return [event, entries.map((entry) => isPlainObject$1(entry) && !("hooks" in entry) ? { hooks: [entry] } : entry)];
|
|
28637
|
+
}));
|
|
28638
|
+
}
|
|
28639
|
+
/**
|
|
28481
28640
|
* Antigravity's `hooks.json` is keyed by a named hook whose value holds the
|
|
28482
28641
|
* per-event map (plus an optional `enabled` flag). rulesync writes a single
|
|
28483
28642
|
* generated hook under this stable name.
|
|
@@ -28514,8 +28673,8 @@ function flattenAntigravityHooks(parsed) {
|
|
|
28514
28673
|
* Hooks generator for Google Antigravity (both the IDE and the CLI).
|
|
28515
28674
|
*
|
|
28516
28675
|
* Antigravity writes a dedicated `hooks.json` keyed by a named hook whose
|
|
28517
|
-
* value holds the
|
|
28518
|
-
*
|
|
28676
|
+
* value holds the event map (`{ "<name>": { "<Event>": [...] } }`): matcher
|
|
28677
|
+
* groups for the tool events, flat handler lists for the others. Project and global modes share the
|
|
28519
28678
|
* same shape; only the location differs (`.agents/hooks.json` vs
|
|
28520
28679
|
* `~/.gemini/config/hooks.json`).
|
|
28521
28680
|
*
|
|
@@ -28559,12 +28718,12 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
28559
28718
|
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
28560
28719
|
const paths = this.getSettablePaths({ global });
|
|
28561
28720
|
const config = rulesyncHooks.getJson();
|
|
28562
|
-
const eventMap = canonicalToToolHooks({
|
|
28721
|
+
const eventMap = toAntigravityEventMap(canonicalToToolHooks({
|
|
28563
28722
|
config,
|
|
28564
28723
|
toolOverrideHooks: config[this.getOverrideKey()]?.hooks,
|
|
28565
28724
|
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
28566
28725
|
logger
|
|
28567
|
-
});
|
|
28726
|
+
}));
|
|
28568
28727
|
const antigravityHooks = Object.keys(eventMap).length > 0 ? { [ANTIGRAVITY_HOOK_NAME]: eventMap } : {};
|
|
28569
28728
|
const fileContent = JSON.stringify(antigravityHooks, null, 2);
|
|
28570
28729
|
return new this({
|
|
@@ -28583,7 +28742,7 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
28583
28742
|
throw new Error(`Failed to parse Antigravity hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28584
28743
|
}
|
|
28585
28744
|
const hooks = toolHooksToCanonical({
|
|
28586
|
-
hooks: flattenAntigravityHooks(parsed),
|
|
28745
|
+
hooks: toMatcherGroupEventMap(flattenAntigravityHooks(parsed)),
|
|
28587
28746
|
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
28588
28747
|
logger
|
|
28589
28748
|
});
|
|
@@ -31047,11 +31206,17 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
31047
31206
|
* entry that uses them instead of a shell field is not mistaken for one
|
|
31048
31207
|
* without a command.
|
|
31049
31208
|
*
|
|
31209
|
+
* An optional `matcher` regex is honored on the events in
|
|
31210
|
+
* {@link COPILOT_MATCHER_HOOK_EVENTS}. The VS Code per-OS override fields come
|
|
31211
|
+
* from the legacy Local hooks reference and are kept for existing files.
|
|
31212
|
+
*
|
|
31050
31213
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
31214
|
+
* @see https://code.visualstudio.com/docs/agent-customization/hooks
|
|
31051
31215
|
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
31052
31216
|
*/
|
|
31053
31217
|
const CopilotHookEntrySchema = zod_mini.z.looseObject({
|
|
31054
31218
|
type: zod_mini.z.string(),
|
|
31219
|
+
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
31055
31220
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
31056
31221
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
31057
31222
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -31085,8 +31250,10 @@ function canonicalToCopilotHooks(config) {
|
|
|
31085
31250
|
...sharedConfigHooks,
|
|
31086
31251
|
...config.copilot?.hooks
|
|
31087
31252
|
};
|
|
31253
|
+
const matcherEvents = new Set(COPILOT_MATCHER_HOOK_EVENTS);
|
|
31088
31254
|
const copilot = {};
|
|
31089
31255
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
31256
|
+
const matcherSupported = matcherEvents.has(eventName);
|
|
31090
31257
|
const copilotEventName = lookupOwn({
|
|
31091
31258
|
record: CANONICAL_TO_COPILOT_EVENT_NAMES,
|
|
31092
31259
|
key: eventName
|
|
@@ -31094,7 +31261,7 @@ function canonicalToCopilotHooks(config) {
|
|
|
31094
31261
|
const entries = [];
|
|
31095
31262
|
for (const def of definitions) {
|
|
31096
31263
|
const hookType = def.type ?? "command";
|
|
31097
|
-
if (def.matcher) continue;
|
|
31264
|
+
if (def.matcher && !matcherSupported) continue;
|
|
31098
31265
|
if (hookType !== "command") continue;
|
|
31099
31266
|
const command = def.command;
|
|
31100
31267
|
const timeout = def.timeout;
|
|
@@ -31102,6 +31269,7 @@ function canonicalToCopilotHooks(config) {
|
|
|
31102
31269
|
const rest = Object.fromEntries(Object.entries(def).filter(([k]) => !canonicalSchemaKeys.includes(k)));
|
|
31103
31270
|
entries.push({
|
|
31104
31271
|
type: hookType,
|
|
31272
|
+
...def.matcher && { matcher: def.matcher },
|
|
31105
31273
|
...command !== void 0 && command !== null && { [commandField]: command },
|
|
31106
31274
|
...def.env !== void 0 && { env: def.env },
|
|
31107
31275
|
...timeout !== void 0 && timeout !== null && { timeoutSec: timeout },
|
|
@@ -31196,6 +31364,7 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
31196
31364
|
if (command === void 0 && hasOsOverride(passthrough)) logger?.warn(`Copilot hook on '${copilotEventName}' has only VS Code per-OS overrides (windows/linux/osx) and no portable command; it is preserved for copilot, but other targets will generate it without a command until a \`command\` is added.`);
|
|
31197
31365
|
defs.push({
|
|
31198
31366
|
type: "command",
|
|
31367
|
+
...entry.matcher !== void 0 && entry.matcher !== "" && { matcher: entry.matcher },
|
|
31199
31368
|
...command !== void 0 && { command },
|
|
31200
31369
|
...shell !== void 0 && { shell },
|
|
31201
31370
|
...timeout !== void 0 && { timeout },
|
|
@@ -31339,21 +31508,13 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
31339
31508
|
*/
|
|
31340
31509
|
/**
|
|
31341
31510
|
* Canonical events whose Copilot CLI hook entries honor an optional `matcher`
|
|
31342
|
-
* field (a regex compiled as `^(?:PATTERN)
|
|
31343
|
-
*
|
|
31344
|
-
*
|
|
31345
|
-
*
|
|
31346
|
-
*
|
|
31347
|
-
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
31511
|
+
* field (a regex compiled as `^(?:PATTERN)$`). The hooks reference filters each
|
|
31512
|
+
* on a different field: the tool name for `preToolUse` / `postToolUse` /
|
|
31513
|
+
* `permissionRequest`, `notification_type`, the `preCompact` trigger
|
|
31514
|
+
* (`manual` | `auto`) and the `subagentStart` agent name.
|
|
31515
|
+
* See {@link COPILOTCLI_MATCHER_HOOK_EVENTS}.
|
|
31348
31516
|
*/
|
|
31349
|
-
const COPILOTCLI_MATCHER_EVENTS =
|
|
31350
|
-
"preToolUse",
|
|
31351
|
-
"postToolUse",
|
|
31352
|
-
"notification",
|
|
31353
|
-
"permissionRequest",
|
|
31354
|
-
"preCompact",
|
|
31355
|
-
"subagentStart"
|
|
31356
|
-
]);
|
|
31517
|
+
const COPILOTCLI_MATCHER_EVENTS = new Set(COPILOTCLI_MATCHER_HOOK_EVENTS);
|
|
31357
31518
|
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
31358
31519
|
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
31359
31520
|
const CopilotCliHookEntrySchema = zod_mini.z.looseObject({
|
|
@@ -33489,7 +33650,7 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
33489
33650
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
33490
33651
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
33491
33652
|
if (HERMESAGENT_SHELL_UNSUPPORTED_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is Python-plugin-only; Hermes Agent refuses it for shell hooks, so the generated entry will never run.`);
|
|
33492
|
-
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.
|
|
33653
|
+
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.22.3; preserving it for forward compatibility.`);
|
|
33493
33654
|
setHermesHookEntries({
|
|
33494
33655
|
result,
|
|
33495
33656
|
event: nativeEvent,
|
|
@@ -37579,7 +37740,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
37579
37740
|
},
|
|
37580
37741
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
37581
37742
|
supportedHookTypes: ["command"],
|
|
37582
|
-
supportsMatcher:
|
|
37743
|
+
supportsMatcher: true,
|
|
37744
|
+
matcherEvents: COPILOT_MATCHER_HOOK_EVENTS
|
|
37583
37745
|
}],
|
|
37584
37746
|
["copilotcli", {
|
|
37585
37747
|
class: CopilotcliHooks,
|
|
@@ -37594,7 +37756,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
37594
37756
|
"prompt",
|
|
37595
37757
|
"http"
|
|
37596
37758
|
],
|
|
37597
|
-
supportsMatcher:
|
|
37759
|
+
supportsMatcher: true
|
|
37598
37760
|
}],
|
|
37599
37761
|
["kilo", {
|
|
37600
37762
|
class: KiloHooks,
|
|
@@ -39984,6 +40146,31 @@ const REASONIX_READ_TOOL_NAME = "read_file";
|
|
|
39984
40146
|
* treated as rulesync-owned on generate so the dormant copy is replaced.
|
|
39985
40147
|
*/
|
|
39986
40148
|
const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
|
|
40149
|
+
async function isPathPresent(path) {
|
|
40150
|
+
return await fileExists(path) || await isPresentButUnresolvable(path);
|
|
40151
|
+
}
|
|
40152
|
+
/**
|
|
40153
|
+
* Warns when a project-scope generate is about to create `./reasonix.toml`
|
|
40154
|
+
* next to an existing `.reasonix/reasonix.toml`.
|
|
40155
|
+
*
|
|
40156
|
+
* Since v2.46.0 Reasonix reads `.reasonix/reasonix.toml` as the project config
|
|
40157
|
+
* when `./reasonix.toml` is absent, and never merges the two: once the root
|
|
40158
|
+
* file exists it wins, so every setting in the nested file (permissions,
|
|
40159
|
+
* sandbox, disabled tools, MCP servers, ...) silently stops applying. rulesync
|
|
40160
|
+
* writes only the root file, so it cannot keep the nested one in effect; this
|
|
40161
|
+
* at least names the problem, once per run however many of the features that
|
|
40162
|
+
* write the file (mcp, permissions, ignore) are generated. Like Reasonix, anything at the root path
|
|
40163
|
+
* (including a broken symlink) counts as the root file being there.
|
|
40164
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/internal/contract/config/paths.go
|
|
40165
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/docs/CONFIG_PATHS.md
|
|
40166
|
+
*/
|
|
40167
|
+
async function warnIfReasonixNestedProjectConfigShadowed({ outputRoot, global, logger }) {
|
|
40168
|
+
if (global) return;
|
|
40169
|
+
if (await isPathPresent((0, node_path.join)(outputRoot, "reasonix.toml"))) return;
|
|
40170
|
+
if (!await isPathPresent((0, node_path.join)(outputRoot, REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH))) return;
|
|
40171
|
+
const nested = toPosixPath(REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH);
|
|
40172
|
+
warnOnceWithFallback(logger, `Reasonix: ${nested} exists but ${REASONIX_PROJECT_MCP_FILE_NAME} does not. Generating creates ${REASONIX_PROJECT_MCP_FILE_NAME}, and Reasonix then reads it instead of ${nested} (the two are never merged), so every setting in ${nested} stops applying. Move those settings into ${REASONIX_PROJECT_MCP_FILE_NAME} or the rulesync sources.`);
|
|
40173
|
+
}
|
|
39987
40174
|
//#endregion
|
|
39988
40175
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
39989
40176
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -40067,7 +40254,12 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
40067
40254
|
fileContent: rulesyncPatterns.join("\n")
|
|
40068
40255
|
});
|
|
40069
40256
|
}
|
|
40070
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
40257
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false, logger }) {
|
|
40258
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
40259
|
+
outputRoot,
|
|
40260
|
+
global,
|
|
40261
|
+
logger
|
|
40262
|
+
});
|
|
40071
40263
|
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern, REASONIX_READ_TOOL_NAME));
|
|
40072
40264
|
const paths = this.getSettablePaths({ global });
|
|
40073
40265
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
@@ -40645,7 +40837,8 @@ var IgnoreProcessor = class extends FeatureProcessor {
|
|
|
40645
40837
|
outputRoot: this.outputRoot,
|
|
40646
40838
|
rulesyncIgnore,
|
|
40647
40839
|
options: this.featureOptions,
|
|
40648
|
-
global: this.global
|
|
40840
|
+
global: this.global,
|
|
40841
|
+
logger: this.logger
|
|
40649
40842
|
});
|
|
40650
40843
|
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
|
|
40651
40844
|
toolIgnore,
|
|
@@ -40995,13 +41188,31 @@ function renameServerField(servers, from, to) {
|
|
|
40995
41188
|
return [name, Object.fromEntries(Object.entries(config).map(([key, value]) => key === from ? [to, value] : [key, value]))];
|
|
40996
41189
|
}));
|
|
40997
41190
|
}
|
|
41191
|
+
function isEmptyValue(value) {
|
|
41192
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
41193
|
+
return isPlainObject$1(value) && Object.keys(value).length === 0;
|
|
41194
|
+
}
|
|
40998
41195
|
/**
|
|
40999
41196
|
* Antigravity uses `serverUrl` (not the canonical `url`) for HTTP/SSE MCP
|
|
41000
41197
|
* servers. Convert the canonical shape into Antigravity's on the way out, and
|
|
41001
|
-
* back to canonical on the way in
|
|
41198
|
+
* back to canonical on the way in.
|
|
41199
|
+
*
|
|
41200
|
+
* Antigravity documents no `type` field: the transport follows from `command`
|
|
41201
|
+
* (stdio) or `serverUrl` (Streamable HTTP / SSE), and `agy plugin validate`
|
|
41202
|
+
* reports the canonical `type` as an unknown field, so it is not written. A
|
|
41203
|
+
* remote server that carries `command`, `args` or `env` was also observed to be
|
|
41204
|
+
* refused at startup (runtime behavior reported by `agy plugin validate` 1.4.4,
|
|
41205
|
+
* not documented), so an empty `args` / `env` left on a `serverUrl` server is
|
|
41206
|
+
* dropped.
|
|
41207
|
+
* https://antigravity.google/docs/mcp
|
|
41002
41208
|
*/
|
|
41003
41209
|
function toAntigravityMcpServers(servers) {
|
|
41004
|
-
|
|
41210
|
+
const renamed = renameServerField(servers, "url", "serverUrl");
|
|
41211
|
+
return Object.fromEntries(Object.entries(renamed).map(([name, config]) => {
|
|
41212
|
+
if (!isPlainObject$1(config)) return [name, config];
|
|
41213
|
+
const isRemote = typeof config.serverUrl === "string";
|
|
41214
|
+
return [name, Object.fromEntries(Object.entries(config).filter(([key, value]) => key !== "type" && !(isRemote && (key === "args" || key === "env") && isEmptyValue(value))))];
|
|
41215
|
+
}));
|
|
41005
41216
|
}
|
|
41006
41217
|
function toCanonicalMcpServers(servers) {
|
|
41007
41218
|
return renameServerField(servers, "serverUrl", "url");
|
|
@@ -45191,6 +45402,60 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
|
|
|
45191
45402
|
return true;
|
|
45192
45403
|
}
|
|
45193
45404
|
/**
|
|
45405
|
+
* Translate a canonical `oauth` block into dcode's custom Slack OAuth app
|
|
45406
|
+
* settings, or `undefined` (after warning) when it has to be left out.
|
|
45407
|
+
*
|
|
45408
|
+
* Since deepagents-code 0.1.87, `_validate_slack_oauth_config` accepts `oauth`
|
|
45409
|
+
* only as `{ client_id, client_secret? }` of non-empty strings, and only on a
|
|
45410
|
+
* `slack.com` / `*.slack.com` server with `auth: "oauth"`; anything else makes
|
|
45411
|
+
* dcode skip the whole server. The canonical block usually carries Claude
|
|
45412
|
+
* Code's `{ clientId, callbackPort, ... }` shape, so `clientId` becomes
|
|
45413
|
+
* `client_id` (a native `client_id` wins), `client_secret` passes through, and
|
|
45414
|
+
* every other key is dropped.
|
|
45415
|
+
*
|
|
45416
|
+
* @see https://github.com/langchain-ai/deepagents/blob/deepagents-code%3D%3D0.1.87/libs/code/deepagents_code/mcp_config.py
|
|
45417
|
+
*/
|
|
45418
|
+
function toDeepagentsOauth({ name, oauth, converted, logger }) {
|
|
45419
|
+
if (!isSlackOauthServer(converted)) {
|
|
45420
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
|
|
45421
|
+
return;
|
|
45422
|
+
}
|
|
45423
|
+
const source = isRecord$1(oauth) ? oauth : {};
|
|
45424
|
+
const result = {};
|
|
45425
|
+
for (const [key, value] of Object.entries(source)) {
|
|
45426
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
45427
|
+
const nativeKey = key === "clientId" ? "client_id" : key;
|
|
45428
|
+
if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
|
|
45429
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
|
|
45430
|
+
continue;
|
|
45431
|
+
}
|
|
45432
|
+
if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
|
|
45433
|
+
if (!isDeepagentsOauthValue(value)) {
|
|
45434
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
|
|
45435
|
+
continue;
|
|
45436
|
+
}
|
|
45437
|
+
result[nativeKey] = value;
|
|
45438
|
+
}
|
|
45439
|
+
if (result.client_id === void 0) {
|
|
45440
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
|
|
45441
|
+
return;
|
|
45442
|
+
}
|
|
45443
|
+
return result;
|
|
45444
|
+
}
|
|
45445
|
+
function isDeepagentsOauthValue(value) {
|
|
45446
|
+
return typeof value === "string" && value.trim() !== "" && !/[\x00-\x1f\x7f]/.test(value);
|
|
45447
|
+
}
|
|
45448
|
+
function isSlackOauthServer(server) {
|
|
45449
|
+
if (server.auth !== "oauth" || typeof server.url !== "string") return false;
|
|
45450
|
+
let host;
|
|
45451
|
+
try {
|
|
45452
|
+
host = new URL(server.url).hostname.toLowerCase();
|
|
45453
|
+
} catch {
|
|
45454
|
+
return false;
|
|
45455
|
+
}
|
|
45456
|
+
return host === "slack.com" || host.endsWith(".slack.com");
|
|
45457
|
+
}
|
|
45458
|
+
/**
|
|
45194
45459
|
* Translate one canonical server into dcode's `.mcp.json` shape, or `null` to
|
|
45195
45460
|
* skip it.
|
|
45196
45461
|
*
|
|
@@ -45213,7 +45478,7 @@ function toDeepagentsServer({ name, server, logger }) {
|
|
|
45213
45478
|
reason: "the WebSocket transport, which deepagents does not support",
|
|
45214
45479
|
logger
|
|
45215
45480
|
});
|
|
45216
|
-
const { enabledTools, disabledTools, type: _type, transport, disabled: _disabled, httpUrl, ...rest } = server;
|
|
45481
|
+
const { enabledTools, disabledTools, type: _type, transport, disabled: _disabled, httpUrl, oauth, ...rest } = server;
|
|
45217
45482
|
const converted = { ...rest };
|
|
45218
45483
|
if (!applyDeepagentsCommand({
|
|
45219
45484
|
name,
|
|
@@ -45228,6 +45493,15 @@ function toDeepagentsServer({ name, server, logger }) {
|
|
|
45228
45493
|
if (transport !== void 0) converted.transport = normalized;
|
|
45229
45494
|
else converted.type = normalized;
|
|
45230
45495
|
}
|
|
45496
|
+
if (oauth !== void 0) {
|
|
45497
|
+
const deepagentsOauth = toDeepagentsOauth({
|
|
45498
|
+
name,
|
|
45499
|
+
oauth,
|
|
45500
|
+
converted,
|
|
45501
|
+
logger
|
|
45502
|
+
});
|
|
45503
|
+
if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
|
|
45504
|
+
}
|
|
45231
45505
|
if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
|
|
45232
45506
|
toolName: TOOL_NAME,
|
|
45233
45507
|
serverName: name,
|
|
@@ -45254,6 +45528,13 @@ function toRulesyncServer(server) {
|
|
|
45254
45528
|
const converted = { ...rest };
|
|
45255
45529
|
for (const key of ["type", "transport"]) if (converted[key] === "streamable_http" || converted[key] === "streamable-http") converted[key] = "http";
|
|
45256
45530
|
if (Array.isArray(allowedTools)) converted.enabledTools = allowedTools;
|
|
45531
|
+
if (isRecord$1(converted.oauth) && Object.hasOwn(converted.oauth, "client_id")) {
|
|
45532
|
+
const { client_id: clientId, ...oauthRest } = converted.oauth;
|
|
45533
|
+
converted.oauth = Object.hasOwn(oauthRest, "clientId") ? oauthRest : {
|
|
45534
|
+
clientId,
|
|
45535
|
+
...oauthRest
|
|
45536
|
+
};
|
|
45537
|
+
}
|
|
45257
45538
|
return converted;
|
|
45258
45539
|
}
|
|
45259
45540
|
var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
@@ -46986,6 +47267,50 @@ function copyHermesOauth(source) {
|
|
|
46986
47267
|
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
46987
47268
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
46988
47269
|
}
|
|
47270
|
+
const HERMES_COST_UNIT_MAX_LENGTH = 32;
|
|
47271
|
+
function describeHermesValue(value) {
|
|
47272
|
+
return typeof value === "number" ? String(value) : JSON.stringify(value) ?? String(value);
|
|
47273
|
+
}
|
|
47274
|
+
/**
|
|
47275
|
+
* Copies the per-server MCP cost keys Hermes v0.22.3 reads in
|
|
47276
|
+
* `tools/mcp_tool_costs.py`: `tool_costs` (declared per-tool costs keyed by
|
|
47277
|
+
* MCP tool name; each a number, a `[min, max]` range or `{credits, note}`),
|
|
47278
|
+
* `spend_limit` (per-chat budget for the server's costed tools; `0`/absent =
|
|
47279
|
+
* no limit) and `cost_unit` (label shown with costs; default `credits`).
|
|
47280
|
+
*
|
|
47281
|
+
* A costed call that would exceed `spend_limit` asks first even on a
|
|
47282
|
+
* `trust: full` server, so dropping the key on regenerate would silently
|
|
47283
|
+
* remove the guard. Values Hermes would reject when loading the server are
|
|
47284
|
+
* dropped with a warning instead of being written into a config Hermes then
|
|
47285
|
+
* refuses: `spend_limit` must be a finite non-negative number, `cost_unit` a
|
|
47286
|
+
* non-blank string of at most 32 characters, and `tool_costs` a mapping. The
|
|
47287
|
+
* mapping is rebuilt as an opaque object (so no reference is shared with the
|
|
47288
|
+
* source), with prototype-pollution keys removed at every depth since its
|
|
47289
|
+
* values may themselves be objects.
|
|
47290
|
+
*/
|
|
47291
|
+
function copyHermesCostFields({ source, target, logger }) {
|
|
47292
|
+
let copied = false;
|
|
47293
|
+
const { spend_limit: spendLimit, cost_unit: costUnit, tool_costs: toolCosts } = source;
|
|
47294
|
+
if (spendLimit !== void 0) {
|
|
47295
|
+
if (typeof spendLimit === "number" && Number.isFinite(spendLimit) && spendLimit >= 0) {
|
|
47296
|
+
target.spend_limit = spendLimit;
|
|
47297
|
+
copied = true;
|
|
47298
|
+
} else logger?.warn(`Hermes MCP: dropping spend_limit ${describeHermesValue(spendLimit)}; Hermes requires a non-negative number.`);
|
|
47299
|
+
}
|
|
47300
|
+
if (costUnit !== void 0) {
|
|
47301
|
+
if (typeof costUnit === "string" && costUnit.trim() !== "" && costUnit.length <= HERMES_COST_UNIT_MAX_LENGTH) {
|
|
47302
|
+
target.cost_unit = costUnit;
|
|
47303
|
+
copied = true;
|
|
47304
|
+
} else logger?.warn(`Hermes MCP: dropping cost_unit ${describeHermesValue(costUnit)}; Hermes requires a non-empty string of at most ${HERMES_COST_UNIT_MAX_LENGTH} characters.`);
|
|
47305
|
+
}
|
|
47306
|
+
if (toolCosts !== void 0) {
|
|
47307
|
+
if (isPlainObject$1(toolCosts)) {
|
|
47308
|
+
target.tool_costs = omitPrototypePollutionKeysDeep(toolCosts);
|
|
47309
|
+
copied = true;
|
|
47310
|
+
} else logger?.warn("Hermes MCP: dropping tool_costs; Hermes requires a mapping of MCP tool names to costs.");
|
|
47311
|
+
}
|
|
47312
|
+
return copied;
|
|
47313
|
+
}
|
|
46989
47314
|
const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
46990
47315
|
"sampling",
|
|
46991
47316
|
"elicitation",
|
|
@@ -46997,13 +47322,18 @@ const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
|
46997
47322
|
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
46998
47323
|
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
46999
47324
|
* `connect_timeout` (seconds), `supports_parallel_tool_calls`, `protocol`,
|
|
47000
|
-
* `lazy`, `keepalive_interval`, `elicitation`, `trust`,
|
|
47325
|
+
* `lazy`, `keepalive_interval`, `elicitation`, `trust`, `identity_header`, and
|
|
47326
|
+
* the cost keys (see {@link copyHermesCostFields}) —
|
|
47001
47327
|
* verbatim from `source` to `target`. Field names are identical on both sides
|
|
47002
47328
|
* (the canonical `McpServerSchema` is a `looseObject`), so this serves export
|
|
47003
47329
|
* and import alike. See the Hermes mcp-config-reference.
|
|
47004
47330
|
*/
|
|
47005
|
-
function copyHermesAdvancedFields(source, target) {
|
|
47006
|
-
let copied =
|
|
47331
|
+
function copyHermesAdvancedFields(source, target, logger) {
|
|
47332
|
+
let copied = copyHermesCostFields({
|
|
47333
|
+
source,
|
|
47334
|
+
target,
|
|
47335
|
+
logger
|
|
47336
|
+
});
|
|
47007
47337
|
if (typeof source.auth === "string") {
|
|
47008
47338
|
target.auth = source.auth;
|
|
47009
47339
|
copied = true;
|
|
@@ -47112,7 +47442,7 @@ function applyHermesToolsBlock(hermesTools, server) {
|
|
|
47112
47442
|
* as Hermes's own `transport: sse`, without which Hermes would connect to it
|
|
47113
47443
|
* over Streamable HTTP.
|
|
47114
47444
|
*/
|
|
47115
|
-
function convertServerToHermes(config) {
|
|
47445
|
+
function convertServerToHermes(config, logger) {
|
|
47116
47446
|
const out = {};
|
|
47117
47447
|
const command = config.command;
|
|
47118
47448
|
const url = resolveHermesUrl(config);
|
|
@@ -47135,7 +47465,7 @@ function convertServerToHermes(config) {
|
|
|
47135
47465
|
if (config.disabled === true) out.enabled = false;
|
|
47136
47466
|
const timeout = resolveHermesTimeout(config);
|
|
47137
47467
|
if (timeout !== void 0) out.timeout = timeout;
|
|
47138
|
-
copyHermesAdvancedFields(config, out);
|
|
47468
|
+
copyHermesAdvancedFields(config, out, logger);
|
|
47139
47469
|
const tools = buildHermesToolsBlock(config);
|
|
47140
47470
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
47141
47471
|
return out;
|
|
@@ -47143,11 +47473,11 @@ function convertServerToHermes(config) {
|
|
|
47143
47473
|
/**
|
|
47144
47474
|
* Converts rulesync canonical MCP servers into Hermes `mcp_servers:` entries.
|
|
47145
47475
|
*/
|
|
47146
|
-
function convertToHermesFormat(mcpServers) {
|
|
47476
|
+
function convertToHermesFormat(mcpServers, logger) {
|
|
47147
47477
|
const result = {};
|
|
47148
47478
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
47149
47479
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
47150
|
-
result[name] = convertServerToHermes(config);
|
|
47480
|
+
result[name] = convertServerToHermes(config, logger);
|
|
47151
47481
|
}
|
|
47152
47482
|
return result;
|
|
47153
47483
|
}
|
|
@@ -47167,7 +47497,7 @@ function mergeHermesMcpServers(config, mcpServers) {
|
|
|
47167
47497
|
* Mirrors {@link convertToHermesFormat}: `enabled: false` maps back to the
|
|
47168
47498
|
* canonical `disabled: true`, and only recognized fields are carried over.
|
|
47169
47499
|
*/
|
|
47170
|
-
function convertFromHermesFormat(mcpServers) {
|
|
47500
|
+
function convertFromHermesFormat(mcpServers, logger) {
|
|
47171
47501
|
const result = {};
|
|
47172
47502
|
const hermesOverrides = {};
|
|
47173
47503
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
@@ -47184,7 +47514,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
47184
47514
|
if (isRecord$1(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
47185
47515
|
result[name] = server;
|
|
47186
47516
|
const hermesServer = { ...server };
|
|
47187
|
-
if (copyHermesAdvancedFields(config, hermesServer)) hermesOverrides[name] = hermesServer;
|
|
47517
|
+
if (copyHermesAdvancedFields(config, hermesServer, logger)) hermesOverrides[name] = hermesServer;
|
|
47188
47518
|
}
|
|
47189
47519
|
return {
|
|
47190
47520
|
mcpServers: result,
|
|
@@ -47203,8 +47533,10 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
47203
47533
|
*/
|
|
47204
47534
|
var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
47205
47535
|
config;
|
|
47206
|
-
|
|
47536
|
+
logger;
|
|
47537
|
+
constructor({ logger, ...params }) {
|
|
47207
47538
|
super(params);
|
|
47539
|
+
this.logger = logger;
|
|
47208
47540
|
this.config = this.fileContent !== void 0 ? parseSharedConfig({
|
|
47209
47541
|
format: "yaml",
|
|
47210
47542
|
fileContent: this.fileContent
|
|
@@ -47248,7 +47580,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47248
47580
|
static getExtraSharedWritePaths() {
|
|
47249
47581
|
return getHermesagentSharedConfigWritePaths();
|
|
47250
47582
|
}
|
|
47251
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
47583
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
|
|
47252
47584
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
47253
47585
|
const paths = this.getSettablePaths({ global });
|
|
47254
47586
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
@@ -47258,17 +47590,18 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47258
47590
|
relativeFilePath: paths.relativeFilePath,
|
|
47259
47591
|
fileContent,
|
|
47260
47592
|
validate,
|
|
47261
|
-
global
|
|
47593
|
+
global,
|
|
47594
|
+
logger
|
|
47262
47595
|
});
|
|
47263
47596
|
}
|
|
47264
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
47597
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
47265
47598
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
47266
47599
|
const paths = this.getSettablePaths({ global });
|
|
47267
47600
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
47268
47601
|
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
47269
47602
|
format: "yaml",
|
|
47270
47603
|
fileContent
|
|
47271
|
-
}), convertToHermesFormat(rulesyncMcp.getMcpServers()));
|
|
47604
|
+
}), convertToHermesFormat(rulesyncMcp.getMcpServers(), logger));
|
|
47272
47605
|
return new HermesagentMcp({
|
|
47273
47606
|
outputRoot,
|
|
47274
47607
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -47280,11 +47613,12 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47280
47613
|
patch: { mcp_servers: merged.mcp_servers }
|
|
47281
47614
|
}),
|
|
47282
47615
|
validate,
|
|
47283
|
-
global
|
|
47616
|
+
global,
|
|
47617
|
+
logger
|
|
47284
47618
|
});
|
|
47285
47619
|
}
|
|
47286
47620
|
toRulesyncMcp() {
|
|
47287
|
-
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
47621
|
+
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {}, this.logger);
|
|
47288
47622
|
return this.toRulesyncMcpDefault({
|
|
47289
47623
|
outputRoot: getHermesagentRulesyncOutputRoot({
|
|
47290
47624
|
nativeOutputRoot: this.outputRoot,
|
|
@@ -48168,14 +48502,15 @@ async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
|
48168
48502
|
if (!config.parsed) return {};
|
|
48169
48503
|
return {
|
|
48170
48504
|
...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
|
|
48171
|
-
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms }
|
|
48505
|
+
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
|
|
48506
|
+
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
|
|
48172
48507
|
};
|
|
48173
48508
|
}
|
|
48174
48509
|
/**
|
|
48175
48510
|
* Kimi's `[mcp]` section in the shared user config sets the default connect and
|
|
48176
|
-
* per-tool-call timeouts
|
|
48177
|
-
* write. Per-server values in
|
|
48178
|
-
* the
|
|
48511
|
+
* per-tool-call timeouts, and whether tools load on demand, for *every* MCP
|
|
48512
|
+
* server, including ones rulesync did not write. Per-server values in
|
|
48513
|
+
* `mcp.json` still take precedence; this only moves the default.
|
|
48179
48514
|
*
|
|
48180
48515
|
* Written as an auxiliary file so it goes through the normal write phase and
|
|
48181
48516
|
* respects `--dry-run`, and merged through the shared-config gateway so the
|
|
@@ -48183,7 +48518,8 @@ async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
|
48183
48518
|
*
|
|
48184
48519
|
* The gateway replaces an owned key wholesale, and `mcp` is a table, so the
|
|
48185
48520
|
* section is recomputed from the existing file: authoring only
|
|
48186
|
-
* `startupTimeoutMs` must not delete a hand-written `tool_timeout_ms
|
|
48521
|
+
* `startupTimeoutMs` must not delete a hand-written `tool_timeout_ms` or
|
|
48522
|
+
* `default_deferred`. Same
|
|
48187
48523
|
* approach the `.vibe/config.toml` `tools` writer takes.
|
|
48188
48524
|
*
|
|
48189
48525
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
@@ -48210,7 +48546,8 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
48210
48546
|
patch: { mcp: {
|
|
48211
48547
|
...existingSection,
|
|
48212
48548
|
...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
|
|
48213
|
-
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs }
|
|
48549
|
+
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
|
|
48550
|
+
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
|
|
48214
48551
|
} },
|
|
48215
48552
|
filePath: relativeConfigPath
|
|
48216
48553
|
});
|
|
@@ -48308,18 +48645,20 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
48308
48645
|
if (!isRecord$1(block)) return [];
|
|
48309
48646
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
48310
48647
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
48311
|
-
|
|
48648
|
+
const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
|
|
48649
|
+
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0) return [];
|
|
48312
48650
|
const existing = await readKimiCodeConfig({ outputRoot });
|
|
48313
48651
|
if (!existing.parsed) {
|
|
48314
48652
|
const paths = kimiCodeConfigRelativePath();
|
|
48315
|
-
warnWithFallback(logger, `Skipping the Kimi Code MCP
|
|
48653
|
+
warnWithFallback(logger, `Skipping the Kimi Code MCP [mcp] defaults: ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)} is not valid TOML.`);
|
|
48316
48654
|
return [];
|
|
48317
48655
|
}
|
|
48318
48656
|
return [KimiCodeMcpConfigToml.fromDefaults({
|
|
48319
48657
|
outputRoot,
|
|
48320
48658
|
defaults: {
|
|
48321
48659
|
startupTimeoutMs,
|
|
48322
|
-
toolTimeoutMs
|
|
48660
|
+
toolTimeoutMs,
|
|
48661
|
+
defaultDeferred
|
|
48323
48662
|
},
|
|
48324
48663
|
existing
|
|
48325
48664
|
})];
|
|
@@ -50895,6 +51234,11 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
50895
51234
|
});
|
|
50896
51235
|
}
|
|
50897
51236
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
51237
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
51238
|
+
outputRoot,
|
|
51239
|
+
global,
|
|
51240
|
+
logger
|
|
51241
|
+
});
|
|
50898
51242
|
const paths = this.getSettablePaths({ global });
|
|
50899
51243
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
50900
51244
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -52922,6 +53266,144 @@ function buildCommandString(server) {
|
|
|
52922
53266
|
if (typeof server.command !== "string" || server.command.trim() === "") return void 0;
|
|
52923
53267
|
return [server.command, ...args.map(quoteShellWord)].join(" ");
|
|
52924
53268
|
}
|
|
53269
|
+
/** A name bash accepts on the left of a `NAME=value` assignment. */
|
|
53270
|
+
const SHELL_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
53271
|
+
/** A canonical `${VAR}` reference whose name bash can expand. */
|
|
53272
|
+
const ENV_VAR_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
53273
|
+
/**
|
|
53274
|
+
* Quote an env value for a `NAME=value` assignment. Literal text is
|
|
53275
|
+
* single-quoted, so nothing in it is expanded or run, while every canonical
|
|
53276
|
+
* `${VAR}` reference is emitted as `"${VAR}"` so bash still expands it from
|
|
53277
|
+
* the environment Xum starts the server with. The pieces are concatenated into
|
|
53278
|
+
* one shell word: `Bearer ${TOKEN}` becomes `'Bearer '"${TOKEN}"`.
|
|
53279
|
+
*/
|
|
53280
|
+
function quoteEnvValue(value) {
|
|
53281
|
+
if (value === "") return "''";
|
|
53282
|
+
const parts = [];
|
|
53283
|
+
let last = 0;
|
|
53284
|
+
for (const match of value.matchAll(ENV_VAR_REFERENCE)) {
|
|
53285
|
+
if (match.index > last) parts.push(quoteShellWord(value.slice(last, match.index)));
|
|
53286
|
+
parts.push(`"\${${match[1]}}"`);
|
|
53287
|
+
last = match.index + match[0].length;
|
|
53288
|
+
}
|
|
53289
|
+
if (last < value.length) parts.push(quoteShellWord(value.slice(last)));
|
|
53290
|
+
return parts.join("");
|
|
53291
|
+
}
|
|
53292
|
+
/**
|
|
53293
|
+
* Build the `NAME=value ` prefix that sets a stdio server's `env` for its
|
|
53294
|
+
* command only (Xum runs the command string with bash). Returns the prefix
|
|
53295
|
+
* plus the entries left out: names that cannot be bash assignment targets and
|
|
53296
|
+
* entries whose value is not a string.
|
|
53297
|
+
*/
|
|
53298
|
+
function buildEnvPrefix(env) {
|
|
53299
|
+
const assignments = [];
|
|
53300
|
+
const invalidNames = [];
|
|
53301
|
+
const nonStringNames = [];
|
|
53302
|
+
for (const [name, value] of Object.entries(env)) {
|
|
53303
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
53304
|
+
if (!SHELL_ENV_NAME.test(name)) {
|
|
53305
|
+
invalidNames.push(name);
|
|
53306
|
+
continue;
|
|
53307
|
+
}
|
|
53308
|
+
if (typeof value !== "string") {
|
|
53309
|
+
nonStringNames.push(name);
|
|
53310
|
+
continue;
|
|
53311
|
+
}
|
|
53312
|
+
assignments.push(`${name}=${quoteEnvValue(value)}`);
|
|
53313
|
+
}
|
|
53314
|
+
return {
|
|
53315
|
+
prefix: assignments.map((assignment) => `${assignment} `).join(""),
|
|
53316
|
+
invalidNames,
|
|
53317
|
+
nonStringNames
|
|
53318
|
+
};
|
|
53319
|
+
}
|
|
53320
|
+
/** Bash reserved words; a command starting with one cannot take an assignment prefix. */
|
|
53321
|
+
const BASH_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
53322
|
+
"!",
|
|
53323
|
+
"[[",
|
|
53324
|
+
"]]",
|
|
53325
|
+
"{",
|
|
53326
|
+
"}",
|
|
53327
|
+
"case",
|
|
53328
|
+
"coproc",
|
|
53329
|
+
"do",
|
|
53330
|
+
"done",
|
|
53331
|
+
"elif",
|
|
53332
|
+
"else",
|
|
53333
|
+
"esac",
|
|
53334
|
+
"fi",
|
|
53335
|
+
"for",
|
|
53336
|
+
"function",
|
|
53337
|
+
"if",
|
|
53338
|
+
"in",
|
|
53339
|
+
"select",
|
|
53340
|
+
"then",
|
|
53341
|
+
"time",
|
|
53342
|
+
"until",
|
|
53343
|
+
"while"
|
|
53344
|
+
]);
|
|
53345
|
+
/**
|
|
53346
|
+
* Explain why a `NAME=value` prefix would not reach the server, or return
|
|
53347
|
+
* `undefined` when it would. Bash applies such assignments to the first simple
|
|
53348
|
+
* command only: with a list or pipeline (`cd x && srv`) the server would not
|
|
53349
|
+
* see them, and in front of a group, parenthesized list or reserved word (`( srv )`,
|
|
53350
|
+
* `{ srv; }`, `if ...`) they are a syntax error that keeps the server from
|
|
53351
|
+
* starting. Only the verbatim `command` string is checked for operators,
|
|
53352
|
+
* outside complete single-quoted words; argv elements are quoted by
|
|
53353
|
+
* {@link quoteShellWord}.
|
|
53354
|
+
*/
|
|
53355
|
+
function envPrefixBlocker(server, baseCommand) {
|
|
53356
|
+
if (typeof server.command === "string") {
|
|
53357
|
+
const unquoted = server.command.replace(/'[^']*'/g, "");
|
|
53358
|
+
if (unquoted.includes("'") || /[;&|(){}\n]/.test(unquoted)) return "its command string contains shell operators, grouping or a newline, so bash would not pass NAME=value assignments in front of it to the server";
|
|
53359
|
+
}
|
|
53360
|
+
const [firstWord] = baseCommand.trimStart().split(/\s+/);
|
|
53361
|
+
if (firstWord !== void 0 && BASH_RESERVED_WORDS.has(firstWord)) return `its command starts with the bash reserved word ${quoteValueForWarning(firstWord)}, which cannot follow NAME=value assignments`;
|
|
53362
|
+
}
|
|
53363
|
+
/**
|
|
53364
|
+
* One leading `NAME=value` assignment in exactly the shape {@link quoteEnvValue}
|
|
53365
|
+
* writes: bare shell-safe characters, single-quoted text (with the `'\''`
|
|
53366
|
+
* escape) and `"${VAR}"` references, followed by spaces or tabs (a newline
|
|
53367
|
+
* would end the command, so the assignment would not apply to what follows). Bare characters
|
|
53368
|
+
* are matched one at a time, so every alternative starts with a distinct
|
|
53369
|
+
* character and the pattern cannot backtrack exponentially.
|
|
53370
|
+
*/
|
|
53371
|
+
const LEADING_ENV_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)=((?:[A-Za-z0-9_\-.,:/@%+=]|'[^']*'|\\'|"\$\{[A-Za-z_][A-Za-z0-9_]*\}")*)[ \t]+/;
|
|
53372
|
+
/** Decode a value written by {@link quoteEnvValue} back into its canonical form. */
|
|
53373
|
+
function unquoteEnvValue(quoted) {
|
|
53374
|
+
return quoted.replace(/'([^']*)'|\\'|"(\$\{[A-Za-z_][A-Za-z0-9_]*\})"/g, (_m, single, ref) => typeof single === "string" ? single : typeof ref === "string" ? ref : "'");
|
|
53375
|
+
}
|
|
53376
|
+
/**
|
|
53377
|
+
* Split leading `NAME=value` assignments off a command string, but only when
|
|
53378
|
+
* every one of them is in the shape rulesync writes and a plain command
|
|
53379
|
+
* follows (see {@link envPrefixBlocker}); anything else (for example a `$(...)` substitution) keeps the whole string as
|
|
53380
|
+
* the command.
|
|
53381
|
+
*/
|
|
53382
|
+
function splitEnvPrefix(command) {
|
|
53383
|
+
const env = {};
|
|
53384
|
+
let rest = command.trimStart();
|
|
53385
|
+
for (;;) {
|
|
53386
|
+
const match = LEADING_ENV_ASSIGNMENT.exec(rest);
|
|
53387
|
+
if (match === null) break;
|
|
53388
|
+
const [whole, name, quoted] = match;
|
|
53389
|
+
if (name === void 0 || quoted === void 0 || PROTOTYPE_POLLUTION_KEYS.has(name)) break;
|
|
53390
|
+
env[name] = unquoteEnvValue(quoted);
|
|
53391
|
+
rest = rest.slice(whole.length);
|
|
53392
|
+
}
|
|
53393
|
+
if (Object.keys(env).length === 0 || rest.trim() === "" || /^[A-Za-z_][A-Za-z0-9_]*=/.test(rest) || envPrefixBlocker({ command: rest }, rest) !== void 0) return { rest: command };
|
|
53394
|
+
return {
|
|
53395
|
+
env,
|
|
53396
|
+
rest
|
|
53397
|
+
};
|
|
53398
|
+
}
|
|
53399
|
+
/** Read a command string back into `env` + `command` + `args`. */
|
|
53400
|
+
function parseCommandString(command) {
|
|
53401
|
+
const { env, rest } = splitEnvPrefix(command);
|
|
53402
|
+
return {
|
|
53403
|
+
...env !== void 0 && { env },
|
|
53404
|
+
...splitCommandString(rest)
|
|
53405
|
+
};
|
|
53406
|
+
}
|
|
52925
53407
|
/** Split a plain command string back into `command` + `args` when it is safe to. */
|
|
52926
53408
|
function splitCommandString(command) {
|
|
52927
53409
|
const trimmed = command.trim();
|
|
@@ -52965,10 +53447,20 @@ function convertServerToXum({ name, server, logger }) {
|
|
|
52965
53447
|
...settings
|
|
52966
53448
|
};
|
|
52967
53449
|
}
|
|
52968
|
-
const
|
|
52969
|
-
if (
|
|
52970
|
-
|
|
52971
|
-
if (
|
|
53450
|
+
const baseCommand = buildCommandString(server);
|
|
53451
|
+
if (baseCommand === void 0) return skip("it has no command");
|
|
53452
|
+
let command = baseCommand;
|
|
53453
|
+
if (isPlainObject$1(server.env) && Object.keys(server.env).length > 0) {
|
|
53454
|
+
const blocker = envPrefixBlocker(server, baseCommand);
|
|
53455
|
+
if (blocker !== void 0) warnWithFallback(logger, `Xum MCP: ${quoteValueForWarning(name)} sets env, but ${blocker}; the server is written without env.`);
|
|
53456
|
+
else {
|
|
53457
|
+
const { prefix, invalidNames, nonStringNames } = buildEnvPrefix(server.env);
|
|
53458
|
+
command = `${prefix}${baseCommand}`;
|
|
53459
|
+
if (invalidNames.length > 0) warnWithFallback(logger, `Xum MCP: ${quoteValueForWarning(name)} sets env ${invalidNames.map(quoteValueForWarning).join(", ")}, which cannot be a bash variable name; the server is written without it.`);
|
|
53460
|
+
if (nonStringNames.length > 0) warnWithFallback(logger, `Xum MCP: ${quoteValueForWarning(name)} sets env ${nonStringNames.map(quoteValueForWarning).join(", ")} to a value that is not a string; the server is written without it.`);
|
|
53461
|
+
}
|
|
53462
|
+
}
|
|
53463
|
+
if (typeof server.cwd === "string") warnWithFallback(logger, `Xum MCP: ${quoteValueForWarning(name)} sets cwd, which Xum's mcp.jsonc cannot express; the server is written without it (Xum starts stdio servers in the workspace directory).`);
|
|
52972
53464
|
return Object.keys(settings).length > 0 ? {
|
|
52973
53465
|
command,
|
|
52974
53466
|
...settings
|
|
@@ -52988,7 +53480,7 @@ function convertFromXumFormat(servers) {
|
|
|
52988
53480
|
for (const [name, entry] of Object.entries(servers)) {
|
|
52989
53481
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
52990
53482
|
if (typeof entry === "string") {
|
|
52991
|
-
result[name] =
|
|
53483
|
+
result[name] = parseCommandString(entry);
|
|
52992
53484
|
continue;
|
|
52993
53485
|
}
|
|
52994
53486
|
if (!isPlainObject$1(entry)) continue;
|
|
@@ -52998,7 +53490,7 @@ function convertFromXumFormat(servers) {
|
|
|
52998
53490
|
server.url = entry.url;
|
|
52999
53491
|
const headers = copyStringHeaders(entry.headers);
|
|
53000
53492
|
if (headers !== void 0) server.headers = headers;
|
|
53001
|
-
} else if (typeof entry.command === "string") Object.assign(server,
|
|
53493
|
+
} else if (typeof entry.command === "string") Object.assign(server, parseCommandString(entry.command));
|
|
53002
53494
|
else continue;
|
|
53003
53495
|
if (entry.disabled === true) server.disabled = true;
|
|
53004
53496
|
if (isStringArray$1(entry.toolAllowlist)) server.enabledTools = entry.toolAllowlist;
|
|
@@ -53013,9 +53505,10 @@ function convertFromXumFormat(servers) {
|
|
|
53013
53505
|
* (project; a server there wins over the user one of the same name). Both are
|
|
53014
53506
|
* a JSONC document with a `servers` map:
|
|
53015
53507
|
*
|
|
53016
|
-
* - a stdio server is a shell command string (the object form
|
|
53017
|
-
* carries `disabled` / `toolAllowlist`);
|
|
53018
|
-
* `
|
|
53508
|
+
* - a stdio server is a shell command string run with bash (the object form
|
|
53509
|
+
* `{ command }` carries `disabled` / `toolAllowlist`); `env` is written as
|
|
53510
|
+
* `NAME=value` assignments in front of the command, and `cwd`, which has no
|
|
53511
|
+
* equivalent, is dropped with a warning;
|
|
53019
53512
|
* - a remote server is `{ transport: "http" | "sse" | "auto", url, headers }`
|
|
53020
53513
|
* (the transport defaults to auto-detection).
|
|
53021
53514
|
*
|
|
@@ -53147,7 +53640,7 @@ function asZcodeRemoteType(stated, url) {
|
|
|
53147
53640
|
}
|
|
53148
53641
|
/**
|
|
53149
53642
|
* The keys each OAuth flow of ZCode's per-server `oauth` union accepts on a
|
|
53150
|
-
* remote server, and which of them are required. Read off the 3.14.3
|
|
53643
|
+
* remote server, and which of them are required. Read off the 3.14.3–3.16.2
|
|
53151
53644
|
* runtime config schema, since ZCode's MCP page only says to "enable OAuth for
|
|
53152
53645
|
* the service in its configuration". Both variants are strict, so any other
|
|
53153
53646
|
* key would make ZCode skip the server.
|
|
@@ -53251,7 +53744,11 @@ function resolveZcodeTimeoutMs({ serverName, config, logger }) {
|
|
|
53251
53744
|
}
|
|
53252
53745
|
/**
|
|
53253
53746
|
* The per-server keys ZCode reads on every transport: `timeoutMs`,
|
|
53254
|
-
* `protocolVersion` and the enable toggle (`enableKey`).
|
|
53747
|
+
* `protocolVersion`, `alwaysLoad` and the enable toggle (`enableKey`).
|
|
53748
|
+
*
|
|
53749
|
+
* `alwaysLoad` (added in 3.16.2 alongside tool search) keeps a server's tools
|
|
53750
|
+
* loaded up front instead of deferring them behind tool search. ZCode's schema
|
|
53751
|
+
* types it as a boolean, so any other value is dropped with a warning.
|
|
53255
53752
|
*/
|
|
53256
53753
|
function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
53257
53754
|
const common = {};
|
|
@@ -53264,6 +53761,9 @@ function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
|
53264
53761
|
const { protocolVersion } = config;
|
|
53265
53762
|
if (ZCODE_PROTOCOL_VERSIONS.has(protocolVersion)) common.protocolVersion = protocolVersion;
|
|
53266
53763
|
else if (protocolVersion !== void 0) logger?.warn(`ZCode MCP server "${serverName}": dropping protocolVersion ${JSON.stringify(protocolVersion)}, as ZCode accepts only ${[...ZCODE_PROTOCOL_VERSIONS].map((v) => JSON.stringify(v)).join(", ")}.`);
|
|
53764
|
+
const { alwaysLoad } = config;
|
|
53765
|
+
if (typeof alwaysLoad === "boolean") common.alwaysLoad = alwaysLoad;
|
|
53766
|
+
else if (alwaysLoad !== void 0) logger?.warn(`ZCode MCP server "${serverName}": dropping alwaysLoad ${JSON.stringify(alwaysLoad)}, as it must be a boolean.`);
|
|
53267
53767
|
if (config.disabled === true) common[enableKey] = false;
|
|
53268
53768
|
return common;
|
|
53269
53769
|
}
|
|
@@ -53276,11 +53776,11 @@ function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
|
53276
53776
|
* absent.
|
|
53277
53777
|
*
|
|
53278
53778
|
* Every server also carries `timeoutMs` (from the canonical `timeout`, or an
|
|
53279
|
-
* explicit `timeoutMs`)
|
|
53280
|
-
* carry `cwd`, and remote servers an `oauth` built
|
|
53281
|
-
* `zcodeOauth`. That key is stripped by `RulesyncMcp.getMcpServers()`, so it
|
|
53779
|
+
* explicit `timeoutMs`), an allow-listed `protocolVersion` and a boolean
|
|
53780
|
+
* `alwaysLoad`; stdio servers carry `cwd`, and remote servers an `oauth` built
|
|
53781
|
+
* from the namespaced `zcodeOauth`. That key is stripped by `RulesyncMcp.getMcpServers()`, so it
|
|
53282
53782
|
* is read from the authored server through `rawServerOf`. ZCode's docs do not
|
|
53283
|
-
* name these keys, so they follow the 3.14.3
|
|
53783
|
+
* name these keys, so they follow the 3.14.3–3.16.2 runtime's per-server
|
|
53284
53784
|
* schema, which is strict: an unknown key makes ZCode skip the server, which
|
|
53285
53785
|
* is why the output stays a whitelist.
|
|
53286
53786
|
*
|
|
@@ -58417,6 +58917,11 @@ const CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
|
58417
58917
|
reason: "lets sandboxed commands send Apple Events, which removes code-execution isolation",
|
|
58418
58918
|
widens: isNotFalse
|
|
58419
58919
|
},
|
|
58920
|
+
{
|
|
58921
|
+
path: ["allowNestedUserNamespaces"],
|
|
58922
|
+
reason: "lets sandboxed Linux commands create user namespaces, which gets around the sandbox's file protections",
|
|
58923
|
+
widens: isNotFalse
|
|
58924
|
+
},
|
|
58420
58925
|
{
|
|
58421
58926
|
path: ["allowUnsandboxedCommands"],
|
|
58422
58927
|
reason: "controls whether Claude may retry a blocked command outside the sandbox",
|
|
@@ -68866,7 +69371,7 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
68866
69371
|
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
68867
69372
|
*
|
|
68868
69373
|
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
68869
|
-
* `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
69374
|
+
* `always-ask-cached`, `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
68870
69375
|
* import a recognized value is read back into the override.
|
|
68871
69376
|
*
|
|
68872
69377
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
@@ -71138,6 +71643,8 @@ function buildReasonixPermissionEntry(toolName, pattern) {
|
|
|
71138
71643
|
const REASONIX_TOOL_LABEL = "Reasonix";
|
|
71139
71644
|
/** The `[permissions]` key the override's `allowDynamicBash` writes and reads. */
|
|
71140
71645
|
const REASONIX_ALLOW_DYNAMIC_BASH_KEY = "allow_dynamic_bash";
|
|
71646
|
+
/** The `[permissions]` key the override's `askOnExternalOrigin` writes and reads. */
|
|
71647
|
+
const REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY = "ask_on_external_origin";
|
|
71141
71648
|
/**
|
|
71142
71649
|
* `[sandbox]` keys whose authored value loosens the enforcement layer beneath
|
|
71143
71650
|
* the permission policy: they take Bash out of its OS jail, open that jail to
|
|
@@ -71340,6 +71847,95 @@ function applyPermissionsMode({ permissions, authored, global, relativeFilePath,
|
|
|
71340
71847
|
reason: "lets every writer call that no rule matches run without a prompt"
|
|
71341
71848
|
}];
|
|
71342
71849
|
}
|
|
71850
|
+
/**
|
|
71851
|
+
* Writes the override's `askOnExternalOrigin` into `[permissions]`. Reasonix
|
|
71852
|
+
* honors the key only in the user config (`narrowPermissions` in
|
|
71853
|
+
* `project_scope.go`), so a project-scope generate skips it with a warning and
|
|
71854
|
+
* leaves whatever the file had. Turning it on only adds prompts, so that is
|
|
71855
|
+
* quiet; turning off a value the file had on is reported, since Auto and YOLO
|
|
71856
|
+
* then run calls that repeat text from a web page or tool result without asking.
|
|
71857
|
+
* As with `applyAllowDynamicBash`, only a literal `true` counts as quiet, so a
|
|
71858
|
+
* value a `--no-validate` run let through is never written in silence.
|
|
71859
|
+
*
|
|
71860
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/docs/GUIDE.md
|
|
71861
|
+
*/
|
|
71862
|
+
function applyAskOnExternalOrigin({ permissions, override, global, relativeFilePath, logger }) {
|
|
71863
|
+
const authored = override?.askOnExternalOrigin;
|
|
71864
|
+
if (authored === void 0) return [];
|
|
71865
|
+
if (!global) {
|
|
71866
|
+
logger?.warn(`Reasonix permissions: skipping reasonix.askOnExternalOrigin for the project-scoped ${relativeFilePath}; Reasonix honors [permissions] ask_on_external_origin only in the user config and ignores a project value, so generate it with --global instead.`);
|
|
71867
|
+
return [];
|
|
71868
|
+
}
|
|
71869
|
+
const previous = permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY];
|
|
71870
|
+
permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY] = authored;
|
|
71871
|
+
if (authored === true || previous !== true && authored === false) return [];
|
|
71872
|
+
return [{
|
|
71873
|
+
label: `permissions.${REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY}`,
|
|
71874
|
+
reason: "lets Auto and YOLO run a call that repeats text from a web page or tool result without asking first"
|
|
71875
|
+
}];
|
|
71876
|
+
}
|
|
71877
|
+
/**
|
|
71878
|
+
* The `tools` patch entry — the `[tools]` table with the override's `disabled`
|
|
71879
|
+
* list laid over it, or nothing when the override does not author one — and the
|
|
71880
|
+
* trust rows it carries. Adding a name only removes a tool, and a project file
|
|
71881
|
+
* can only add to the user's list, so that stays quiet. The list is replaced
|
|
71882
|
+
* whole, though, so a name the file had turned off and the override leaves out
|
|
71883
|
+
* is turned back on, and that is reported, as are a `[tools]` or a `disabled`
|
|
71884
|
+
* the file holds in a shape that cannot be read. Only `disabled` is written:
|
|
71885
|
+
* the rest of `[tools]` (timeouts, search backends, ...) stays as it is.
|
|
71886
|
+
*/
|
|
71887
|
+
function applyToolsDisabled({ existing, override }) {
|
|
71888
|
+
const disabled = override?.tools?.disabled;
|
|
71889
|
+
if (disabled === void 0) return {
|
|
71890
|
+
patch: {},
|
|
71891
|
+
entries: []
|
|
71892
|
+
};
|
|
71893
|
+
const patch = { tools: {
|
|
71894
|
+
...asReasonixRecord(existing),
|
|
71895
|
+
disabled
|
|
71896
|
+
} };
|
|
71897
|
+
if (existing === void 0) return {
|
|
71898
|
+
patch,
|
|
71899
|
+
entries: []
|
|
71900
|
+
};
|
|
71901
|
+
if (!isRecord$1(existing)) return {
|
|
71902
|
+
patch,
|
|
71903
|
+
entries: [{
|
|
71904
|
+
label: "tools",
|
|
71905
|
+
reason: replacedUnreadableReason({
|
|
71906
|
+
shape: "object",
|
|
71907
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
71908
|
+
})
|
|
71909
|
+
}]
|
|
71910
|
+
};
|
|
71911
|
+
const before = existing.disabled;
|
|
71912
|
+
if (before === void 0) return {
|
|
71913
|
+
patch,
|
|
71914
|
+
entries: []
|
|
71915
|
+
};
|
|
71916
|
+
if (!Array.isArray(before)) return {
|
|
71917
|
+
patch,
|
|
71918
|
+
entries: [{
|
|
71919
|
+
label: "tools.disabled",
|
|
71920
|
+
reason: replacedUnreadableReason({
|
|
71921
|
+
shape: "list",
|
|
71922
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
71923
|
+
})
|
|
71924
|
+
}]
|
|
71925
|
+
};
|
|
71926
|
+
const reEnabled = (0, es_toolkit.uniq)(before.filter((name) => !disabled.includes(name)));
|
|
71927
|
+
if (reEnabled.length === 0) return {
|
|
71928
|
+
patch,
|
|
71929
|
+
entries: []
|
|
71930
|
+
};
|
|
71931
|
+
return {
|
|
71932
|
+
patch,
|
|
71933
|
+
entries: [{
|
|
71934
|
+
label: "tools.disabled",
|
|
71935
|
+
reason: `turns back on ${reEnabled.map((name) => quoteValueForWarning(name)).join(", ")}, which the file had turned off`
|
|
71936
|
+
}]
|
|
71937
|
+
};
|
|
71938
|
+
}
|
|
71343
71939
|
function parseReasonixConfig(fileContent) {
|
|
71344
71940
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
71345
71941
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -71395,6 +71991,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71395
71991
|
});
|
|
71396
71992
|
}
|
|
71397
71993
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
71994
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
71995
|
+
outputRoot,
|
|
71996
|
+
global,
|
|
71997
|
+
logger
|
|
71998
|
+
});
|
|
71398
71999
|
const paths = this.getSettablePaths({ global });
|
|
71399
72000
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
71400
72001
|
const relativeFilePathForLog = toPosixPath((0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath));
|
|
@@ -71454,7 +72055,20 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71454
72055
|
relativeFilePath: relativeFilePathForLog,
|
|
71455
72056
|
logger
|
|
71456
72057
|
}));
|
|
72058
|
+
trustAffecting.push(...applyAskOnExternalOrigin({
|
|
72059
|
+
permissions: mergedPermissions,
|
|
72060
|
+
override,
|
|
72061
|
+
global,
|
|
72062
|
+
relativeFilePath: relativeFilePathForLog,
|
|
72063
|
+
logger
|
|
72064
|
+
}));
|
|
71457
72065
|
const patch = { permissions: mergedPermissions };
|
|
72066
|
+
const toolsDisabled = applyToolsDisabled({
|
|
72067
|
+
existing: parsed.tools,
|
|
72068
|
+
override
|
|
72069
|
+
});
|
|
72070
|
+
Object.assign(patch, toolsDisabled.patch);
|
|
72071
|
+
trustAffecting.push(...toolsDisabled.entries);
|
|
71458
72072
|
if (override?.sandbox !== void 0) {
|
|
71459
72073
|
const authoredSandbox = dropUserOnlySandboxKeys({
|
|
71460
72074
|
sandbox: override.sandbox,
|
|
@@ -71528,6 +72142,14 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71528
72142
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
71529
72143
|
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
71530
72144
|
if (this.global && typeof permissions.mode === "string" && REASONIX_PERMISSION_MODES.has(permissions.mode)) reasonixOverride.mode = permissions.mode;
|
|
72145
|
+
const askOnExternalOrigin = permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY];
|
|
72146
|
+
if (this.global && typeof askOnExternalOrigin === "boolean") reasonixOverride.askOnExternalOrigin = askOnExternalOrigin;
|
|
72147
|
+
const toolsDisabled = asReasonixRecord(this.toml.tools).disabled;
|
|
72148
|
+
if (Array.isArray(toolsDisabled)) {
|
|
72149
|
+
const names = toReasonixStringArray(toolsDisabled);
|
|
72150
|
+
if (names.length < toolsDisabled.length) warnWithFallback(void 0, `Reasonix permissions: ${this.getRelativePathFromCwd()} [tools] disabled holds ${toolsDisabled.length - names.length} non-string entries; they are not imported into reasonix.tools.disabled, so the next generate drops them.`);
|
|
72151
|
+
reasonixOverride.tools = { disabled: names };
|
|
72152
|
+
}
|
|
71531
72153
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
71532
72154
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
71533
72155
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -73109,7 +73731,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
73109
73731
|
const authoredProviderOptions = isPlainObject$1(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
73110
73732
|
const runtimeModeRefusesProviderOptions = authoredProviderOptions !== void 0 && runtime.active;
|
|
73111
73733
|
if (runtimeModeRefusesProviderOptions) logger?.warn(`Takt permissions: not writing "${TAKT_PROVIDER_OPTIONS_KEY}" to ${filePath} because ${runtime.filePaths.join(" + ")} puts Takt in runtime provider mode (Takt 0.56.0+), where any legacy provider setting in config.yaml makes Takt fail with "Mixed provider configuration detected". Move those options to \`provider.profiles.<profile>.options\` in runtime.yaml, and remove them from the \`takt\` block of the rulesync source. A "${TAKT_PROVIDER_OPTIONS_KEY}" already in ${filePath} is left untouched — rulesync does not own that key.`);
|
|
73112
|
-
const overrideProviderOptions = runtimeModeRefusesProviderOptions ? void 0 :
|
|
73734
|
+
const overrideProviderOptions = runtimeModeRefusesProviderOptions ? void 0 : dropUntrustedProviderOptions({
|
|
73735
|
+
providerOptions: authoredProviderOptions,
|
|
73736
|
+
global,
|
|
73737
|
+
filePath,
|
|
73738
|
+
logger
|
|
73739
|
+
});
|
|
73113
73740
|
const authoredPolicies = pickSecurityPolicies(override, {
|
|
73114
73741
|
filePath,
|
|
73115
73742
|
logger,
|
|
@@ -73613,6 +74240,68 @@ function taktModeToRulesyncConfig(mode) {
|
|
|
73613
74240
|
default: return { permission: { bash: { [CATCH_ALL_PATTERN$1]: "deny" } } };
|
|
73614
74241
|
}
|
|
73615
74242
|
}
|
|
74243
|
+
const TAKT_BASE_URL_PROVIDERS = [
|
|
74244
|
+
"codex",
|
|
74245
|
+
"claude",
|
|
74246
|
+
"deepseek_harness"
|
|
74247
|
+
];
|
|
74248
|
+
const TAKT_DEEPSEEK_HARNESS_KEY = "deepseek_harness";
|
|
74249
|
+
/** Mirrors Takt's `isLoopbackBaseUrl`: localhost, *.localhost, ::1 or 127.x.x.x. */
|
|
74250
|
+
function isLoopbackBaseUrl(value) {
|
|
74251
|
+
let hostname;
|
|
74252
|
+
try {
|
|
74253
|
+
hostname = new URL(value).hostname.toLowerCase();
|
|
74254
|
+
} catch {
|
|
74255
|
+
return false;
|
|
74256
|
+
}
|
|
74257
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || hostname === "[::1]") return true;
|
|
74258
|
+
const octets = hostname.split(".");
|
|
74259
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d+$/.test(octet) && Number(octet) <= 255);
|
|
74260
|
+
}
|
|
74261
|
+
/** The keys of one `provider_options.<provider>` entry Takt rejects in config.yaml. */
|
|
74262
|
+
function untrustedProviderOptionKeys({ provider, options, global }) {
|
|
74263
|
+
const keys = [];
|
|
74264
|
+
if (provider === TAKT_DEEPSEEK_HARNESS_KEY) {
|
|
74265
|
+
if (options.reasoning_effort !== void 0) keys.push("reasoning_effort");
|
|
74266
|
+
const { plugins } = options;
|
|
74267
|
+
if (!global && plugins !== void 0 && (!Array.isArray(plugins) || plugins.length > 0)) keys.push("plugins");
|
|
74268
|
+
}
|
|
74269
|
+
const baseUrl = options.base_url;
|
|
74270
|
+
if (!global && TAKT_BASE_URL_PROVIDERS.includes(provider) && baseUrl !== void 0 && (typeof baseUrl !== "string" || !isLoopbackBaseUrl(baseUrl))) keys.push("base_url");
|
|
74271
|
+
return keys;
|
|
74272
|
+
}
|
|
74273
|
+
/**
|
|
74274
|
+
* Drop the `provider_options` entries Takt refuses to load from `config.yaml`
|
|
74275
|
+
* (it fails at startup with a `Configuration error`), warning for each:
|
|
74276
|
+
* - `deepseek_harness.reasoning_effort` in either scope — Takt accepts it only
|
|
74277
|
+
* in runtime profile options or `TAKT_PROVIDER_OPTIONS_DEEPSEEK_HARNESS_REASONING_EFFORT`.
|
|
74278
|
+
* - at project scope, a non-empty `deepseek_harness.plugins` (Takt 0.71.0+,
|
|
74279
|
+
* global config only) and a non-loopback `{codex,claude,deepseek_harness}.base_url`.
|
|
74280
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/providerOptions.ts
|
|
74281
|
+
* https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
74282
|
+
*/
|
|
74283
|
+
function dropUntrustedProviderOptions({ providerOptions, global, filePath, logger }) {
|
|
74284
|
+
if (providerOptions === void 0) return;
|
|
74285
|
+
const dropped = [];
|
|
74286
|
+
const result = {};
|
|
74287
|
+
for (const [provider, options] of Object.entries(providerOptions)) {
|
|
74288
|
+
if (!isPlainObject$1(options)) {
|
|
74289
|
+
result[provider] = options;
|
|
74290
|
+
continue;
|
|
74291
|
+
}
|
|
74292
|
+
const untrusted = untrustedProviderOptionKeys({
|
|
74293
|
+
provider,
|
|
74294
|
+
options,
|
|
74295
|
+
global
|
|
74296
|
+
});
|
|
74297
|
+
const kept = Object.fromEntries(Object.entries(options).filter(([key]) => !untrusted.includes(key)));
|
|
74298
|
+
dropped.push(...untrusted.map((key) => `${TAKT_PROVIDER_OPTIONS_KEY}.${provider}.${key}`));
|
|
74299
|
+
if (Object.keys(kept).length > 0 || Object.keys(options).length === 0) result[provider] = kept;
|
|
74300
|
+
}
|
|
74301
|
+
if (dropped.length === 0) return providerOptions;
|
|
74302
|
+
logger?.warn(`Takt permissions: not writing ${dropped.map((key) => `"${key}"`).join(", ")} to ${filePath} because Takt refuses to load ${dropped.length === 1 ? "it" : "them"} from ${global ? "config.yaml" : "a project config.yaml"} and fails at startup with a "Configuration error". Set non-loopback base URLs and DeepSeek Harness plugins in the global ~/.takt/config.yaml (or the TAKT_PROVIDER_OPTIONS_* environment variables), and DeepSeek Harness reasoning_effort in a runtime.yaml profile's options or TAKT_PROVIDER_OPTIONS_DEEPSEEK_HARNESS_REASONING_EFFORT, then remove them from the \`takt\` block of the rulesync source.`);
|
|
74303
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
74304
|
+
}
|
|
73616
74305
|
/**
|
|
73617
74306
|
* Lift Takt's security-policy keys out of a source object, keeping only the
|
|
73618
74307
|
* shapes Takt itself accepts (a boolean, or a table of booleans). Used in both
|
|
@@ -85942,6 +86631,19 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
85942
86631
|
//#endregion
|
|
85943
86632
|
//#region src/features/skills/xum-skill.ts
|
|
85944
86633
|
/**
|
|
86634
|
+
* The Agent Skills standard keys the `agentsskills` section carries. On import
|
|
86635
|
+
* they go back there, exactly as for the `agentsskills` target, and every other
|
|
86636
|
+
* key besides `name` / `description` goes into the `xum` section.
|
|
86637
|
+
*/
|
|
86638
|
+
const AGENT_SKILLS_STANDARD_KEYS = /* @__PURE__ */ new Set([
|
|
86639
|
+
"name",
|
|
86640
|
+
"description",
|
|
86641
|
+
"license",
|
|
86642
|
+
"compatibility",
|
|
86643
|
+
"metadata",
|
|
86644
|
+
"allowed-tools"
|
|
86645
|
+
]);
|
|
86646
|
+
/**
|
|
85945
86647
|
* Xum skills follow the Agent Skills layout (`<name>/SKILL.md` with the
|
|
85946
86648
|
* required `name` and `description` frontmatter; the directory name is
|
|
85947
86649
|
* lowercase letters, digits and single hyphens up to 64 characters, and
|
|
@@ -85949,12 +86651,82 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
85949
86651
|
* Xum's locations: `.xum/skills/` (project) and `~/.xum/skills/` (user).
|
|
85950
86652
|
* Within one directory `.xum/skills` wins over `.agents/skills`.
|
|
85951
86653
|
*
|
|
86654
|
+
* On top of the standard fields, Xum reads `disable-model-invocation` (and its
|
|
86655
|
+
* own `advertise: false` alias), `user-invocable`, `argument-hint`,
|
|
86656
|
+
* `when_to_use` / `when-to-use` and `paths` (globs that make the skill
|
|
86657
|
+
* path-scoped). Those come from the `xum` section of the rulesync skill and are
|
|
86658
|
+
* written verbatim; the two invocation gates fall back to the root-level
|
|
86659
|
+
* rulesync values.
|
|
86660
|
+
*
|
|
85952
86661
|
* @see https://xum.cdr.dev/agents/agent-skills
|
|
85953
86662
|
*/
|
|
85954
|
-
var XumSkill = class extends AgentsSkillsSkill {
|
|
86663
|
+
var XumSkill = class XumSkill extends AgentsSkillsSkill {
|
|
85955
86664
|
static getSettablePaths(_options = {}) {
|
|
85956
86665
|
return { relativeDirPath: XUM_SKILLS_DIR_PATH };
|
|
85957
86666
|
}
|
|
86667
|
+
toRulesyncSkill() {
|
|
86668
|
+
const base = super.toRulesyncSkill();
|
|
86669
|
+
const xumSection = Object.fromEntries(Object.entries(this.getFrontmatter()).filter(([key]) => !AGENT_SKILLS_STANDARD_KEYS.has(key)));
|
|
86670
|
+
if (Object.keys(xumSection).length === 0) return base;
|
|
86671
|
+
const rulesyncFrontmatter = {
|
|
86672
|
+
...base.getFrontmatter(),
|
|
86673
|
+
xum: xumSection
|
|
86674
|
+
};
|
|
86675
|
+
return new RulesyncSkill({
|
|
86676
|
+
outputRoot: base.getOutputRoot(),
|
|
86677
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
86678
|
+
dirName: base.getDirName(),
|
|
86679
|
+
frontmatter: rulesyncFrontmatter,
|
|
86680
|
+
body: base.getBody(),
|
|
86681
|
+
otherFiles: base.getOtherFiles(),
|
|
86682
|
+
validate: true,
|
|
86683
|
+
global: this.global
|
|
86684
|
+
});
|
|
86685
|
+
}
|
|
86686
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false, logger }) {
|
|
86687
|
+
const settablePaths = XumSkill.getSettablePaths({ global });
|
|
86688
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
86689
|
+
const dirName = rulesyncSkill.getDirName();
|
|
86690
|
+
const xumSection = {};
|
|
86691
|
+
const ignoredKeys = [];
|
|
86692
|
+
for (const [key, value] of Object.entries(rulesyncFrontmatter.xum ?? {})) if (AGENT_SKILLS_STANDARD_KEYS.has(key)) ignoredKeys.push(key);
|
|
86693
|
+
else xumSection[key] = value;
|
|
86694
|
+
if (ignoredKeys.length > 0) warnWithFallback(logger, `${(0, node_path.join)(outputRoot, settablePaths.relativeDirPath, dirName, SKILL_FILE_NAME)}: the xum section sets ${ignoredKeys.map(quoteValueForWarning).join(", ")}, which ${ignoredKeys.length === 1 ? "is" : "are"} ignored there; set name/description at the root and the Agent Skills standard fields at the root or in the agentsskills section.`);
|
|
86695
|
+
const userInvocable = resolveUserInvocable({
|
|
86696
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
86697
|
+
section: xumSection
|
|
86698
|
+
});
|
|
86699
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
86700
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
86701
|
+
section: xumSection
|
|
86702
|
+
});
|
|
86703
|
+
const frontmatter = {
|
|
86704
|
+
name: rulesyncFrontmatter.name,
|
|
86705
|
+
description: rulesyncFrontmatter.description,
|
|
86706
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter)),
|
|
86707
|
+
...xumSection,
|
|
86708
|
+
...userInvocable !== void 0 && { "user-invocable": userInvocable },
|
|
86709
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
|
|
86710
|
+
};
|
|
86711
|
+
AgentsSkillsSkill.reportSpecViolations({
|
|
86712
|
+
outputRoot,
|
|
86713
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
86714
|
+
dirName,
|
|
86715
|
+
frontmatter,
|
|
86716
|
+
sourceAllowedTools: rulesyncFrontmatter.agentsskills?.["allowed-tools"],
|
|
86717
|
+
logger
|
|
86718
|
+
});
|
|
86719
|
+
return new XumSkill({
|
|
86720
|
+
outputRoot,
|
|
86721
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
86722
|
+
dirName,
|
|
86723
|
+
frontmatter,
|
|
86724
|
+
body: rulesyncSkill.getBody(),
|
|
86725
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
86726
|
+
validate,
|
|
86727
|
+
global
|
|
86728
|
+
});
|
|
86729
|
+
}
|
|
85958
86730
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
85959
86731
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
85960
86732
|
return targets.includes("*") || targets.includes("xum");
|
|
@@ -92110,6 +92882,15 @@ const KiroCliSubagentJsonSchema = zod_mini.z.looseObject({
|
|
|
92110
92882
|
allowedTools: zod_mini.z.optional(zod_mini.z.nullable(zod_mini.z.array(zod_mini.z.string()))),
|
|
92111
92883
|
includeMcpJson: zod_mini.z.optional(zod_mini.z.nullable(zod_mini.z.boolean()))
|
|
92112
92884
|
});
|
|
92885
|
+
/**
|
|
92886
|
+
* The name Kiro gives an agent file: its path relative to the agents directory
|
|
92887
|
+
* without the extension, so a nested `team/planner.md` is `team/planner`.
|
|
92888
|
+
* https://kiro.dev/docs/custom-agents/
|
|
92889
|
+
*/
|
|
92890
|
+
function kiroAgentNameFromPath(relativeFilePath) {
|
|
92891
|
+
const posixPath = toPosixPath(relativeFilePath);
|
|
92892
|
+
return posixPath.slice(0, posixPath.length - (0, node_path.extname)(posixPath).length);
|
|
92893
|
+
}
|
|
92113
92894
|
var KiroSubagent = class extends ToolSubagent {
|
|
92114
92895
|
body;
|
|
92115
92896
|
constructor({ body, ...rest }) {
|
|
@@ -92145,7 +92926,7 @@ var KiroSubagent = class extends ToolSubagent {
|
|
|
92145
92926
|
outputRoot: ".",
|
|
92146
92927
|
frontmatter: {
|
|
92147
92928
|
targets: [toolTarget],
|
|
92148
|
-
name: name ?? (
|
|
92929
|
+
name: name ?? kiroAgentNameFromPath(this.getRelativeFilePath()),
|
|
92149
92930
|
description: description ?? void 0,
|
|
92150
92931
|
...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
|
|
92151
92932
|
},
|
|
@@ -92295,7 +93076,7 @@ var KiroIdeSubagent = class KiroIdeSubagent extends ToolSubagent {
|
|
|
92295
93076
|
outputRoot: ".",
|
|
92296
93077
|
frontmatter: {
|
|
92297
93078
|
targets: ["*"],
|
|
92298
|
-
name: name ?? this.getRelativeFilePath()
|
|
93079
|
+
name: name ?? kiroAgentNameFromPath(this.getRelativeFilePath()),
|
|
92299
93080
|
description: description ?? "",
|
|
92300
93081
|
...Object.keys(kiroIdeSection).length > 0 && { "kiro-ide": kiroIdeSection }
|
|
92301
93082
|
},
|
|
@@ -94913,7 +95694,8 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
94913
95694
|
supportsProject: true,
|
|
94914
95695
|
supportsSimulated: false,
|
|
94915
95696
|
supportsGlobal: true,
|
|
94916
|
-
filePattern: "
|
|
95697
|
+
filePattern: "**/*.json",
|
|
95698
|
+
supportsNestedPaths: true
|
|
94917
95699
|
}
|
|
94918
95700
|
}],
|
|
94919
95701
|
["kiro-ide", {
|
|
@@ -94922,7 +95704,8 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
94922
95704
|
supportsProject: true,
|
|
94923
95705
|
supportsSimulated: false,
|
|
94924
95706
|
supportsGlobal: true,
|
|
94925
|
-
filePattern: "
|
|
95707
|
+
filePattern: "**/*.md",
|
|
95708
|
+
supportsNestedPaths: true
|
|
94926
95709
|
}
|
|
94927
95710
|
}],
|
|
94928
95711
|
["lettacode", {
|
|
@@ -109164,6 +109947,12 @@ Object.defineProperty(exports, "PACKAGING_TOOL_TARGETS", {
|
|
|
109164
109947
|
return PACKAGING_TOOL_TARGETS;
|
|
109165
109948
|
}
|
|
109166
109949
|
});
|
|
109950
|
+
Object.defineProperty(exports, "PathOutsideRootError", {
|
|
109951
|
+
enumerable: true,
|
|
109952
|
+
get: function() {
|
|
109953
|
+
return PathOutsideRootError;
|
|
109954
|
+
}
|
|
109955
|
+
});
|
|
109167
109956
|
Object.defineProperty(exports, "QWENCODE_DIR", {
|
|
109168
109957
|
enumerable: true,
|
|
109169
109958
|
get: function() {
|
|
@@ -109464,6 +110253,12 @@ Object.defineProperty(exports, "assertDirectoryIfExists", {
|
|
|
109464
110253
|
return assertDirectoryIfExists;
|
|
109465
110254
|
}
|
|
109466
110255
|
});
|
|
110256
|
+
Object.defineProperty(exports, "assertPathResolvesInsideRoot", {
|
|
110257
|
+
enumerable: true,
|
|
110258
|
+
get: function() {
|
|
110259
|
+
return assertPathResolvesInsideRoot;
|
|
110260
|
+
}
|
|
110261
|
+
});
|
|
109467
110262
|
Object.defineProperty(exports, "assertTargetsFeaturesExclusive", {
|
|
109468
110263
|
enumerable: true,
|
|
109469
110264
|
get: function() {
|