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
|
@@ -1048,6 +1048,57 @@ async function assertWritablePathInsideRoot(params) {
|
|
|
1048
1048
|
existingPath = parentPath;
|
|
1049
1049
|
}
|
|
1050
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Thrown by {@link assertPathResolvesInsideRoot} when a symbolic link leads
|
|
1053
|
+
* the target outside the root, or the target is reached through a dangling
|
|
1054
|
+
* link. Any other error it throws comes from the filesystem itself.
|
|
1055
|
+
*/
|
|
1056
|
+
var PathOutsideRootError = class extends Error {
|
|
1057
|
+
constructor(message, options) {
|
|
1058
|
+
super(message, options);
|
|
1059
|
+
this.name = "PathOutsideRootError";
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
/**
|
|
1063
|
+
* Refuse a write or removal target whose real location is outside `rootPath`.
|
|
1064
|
+
*
|
|
1065
|
+
* Unlike {@link assertWritablePathInsideRoot}, a symbolic link on the way is
|
|
1066
|
+
* allowed as long as it resolves inside the root: projects deliberately link a
|
|
1067
|
+
* skill directory to a shared one next to it. What this stops is a link that
|
|
1068
|
+
* leads elsewhere — a cloned repository committing `.rulesync/skills/x ->
|
|
1069
|
+
* ~/.ssh`, say — which a textual check such as {@link checkPathTraversal}
|
|
1070
|
+
* cannot see. The deepest existing ancestor of `targetPath` is resolved, and
|
|
1071
|
+
* an existing target that is itself a link is resolved too.
|
|
1072
|
+
*/
|
|
1073
|
+
async function assertPathResolvesInsideRoot(params) {
|
|
1074
|
+
const { rootPath, targetPath } = params;
|
|
1075
|
+
let realRoot;
|
|
1076
|
+
try {
|
|
1077
|
+
realRoot = await realpath(rootPath);
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
if (isFileNotFoundError(error)) {
|
|
1080
|
+
if (await isSymlink(rootPath)) throw new PathOutsideRootError(`Refusing to write through a dangling symbolic link: ${rootPath}.`, { cause: error });
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
throw error;
|
|
1084
|
+
}
|
|
1085
|
+
let existingPath = resolve(targetPath);
|
|
1086
|
+
while (true) {
|
|
1087
|
+
let realExisting;
|
|
1088
|
+
try {
|
|
1089
|
+
realExisting = await realpath(existingPath);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
1092
|
+
if (await isSymlink(existingPath)) throw new PathOutsideRootError(`Refusing to write through a dangling symbolic link: ${targetPath}.`, { cause: error });
|
|
1093
|
+
const parentPath = dirname(existingPath);
|
|
1094
|
+
if (parentPath === existingPath) throw error;
|
|
1095
|
+
existingPath = parentPath;
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (pathEscapesRoot(relative(realRoot, realExisting))) throw new PathOutsideRootError(`Path resolves outside ${rootPath} through a symbolic link: ${targetPath}.`);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1051
1102
|
async function assertTreeContainsNoSymlinks(dirPath) {
|
|
1052
1103
|
for (const entry of await readdir(dirPath, { withFileTypes: true })) {
|
|
1053
1104
|
const entryPath = join(dirPath, entry.name);
|
|
@@ -5112,6 +5163,40 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
5112
5163
|
"userPromptExpansion"
|
|
5113
5164
|
];
|
|
5114
5165
|
/**
|
|
5166
|
+
* The {@link COPILOT_HOOK_EVENTS} whose hook entries honor an optional
|
|
5167
|
+
* `matcher` regex. The hooks reference documents matcher filtering on
|
|
5168
|
+
* `preToolUse` / `postToolUse` / `permissionRequest` (tool name), `preCompact`
|
|
5169
|
+
* (trigger) and `subagentStart` (agent name), plus `notification`; the last
|
|
5170
|
+
* two are not authorable for `copilot`, so they are not listed here. VS Code's
|
|
5171
|
+
* migration guide confirms the Copilot harness "applies documented matcher
|
|
5172
|
+
* rules" (only the legacy Local harness ignores them).
|
|
5173
|
+
*
|
|
5174
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
5175
|
+
* @see https://code.visualstudio.com/docs/agent-customization/migrate-customizations
|
|
5176
|
+
*/
|
|
5177
|
+
const COPILOT_MATCHER_HOOK_EVENTS = [
|
|
5178
|
+
"preToolUse",
|
|
5179
|
+
"postToolUse",
|
|
5180
|
+
"preCompact",
|
|
5181
|
+
"subagentStart"
|
|
5182
|
+
];
|
|
5183
|
+
/**
|
|
5184
|
+
* The {@link COPILOTCLI_HOOK_EVENTS} whose hook entries honor an optional
|
|
5185
|
+
* `matcher` regex: the shared {@link COPILOT_MATCHER_HOOK_EVENTS} plus the two
|
|
5186
|
+
* CLI-only events, `permissionRequest` (tool name) and `notification`
|
|
5187
|
+
* (notification type).
|
|
5188
|
+
*
|
|
5189
|
+
* Confirmed by the upstream changelog: v1.0.36 (2026-04-24, `preToolUse.matcher`
|
|
5190
|
+
* honored) and v1.0.63 (2026-06-15, "PostToolUse hook matchers (e.g.
|
|
5191
|
+
* `Edit|Write`) are now honored instead of silently dropped").
|
|
5192
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
5193
|
+
*/
|
|
5194
|
+
const COPILOTCLI_MATCHER_HOOK_EVENTS = [
|
|
5195
|
+
...COPILOT_MATCHER_HOOK_EVENTS,
|
|
5196
|
+
"permissionRequest",
|
|
5197
|
+
"notification"
|
|
5198
|
+
];
|
|
5199
|
+
/**
|
|
5115
5200
|
* Hook events supported by the GitHub Copilot CLI (`copilotcli-hooks.ts`).
|
|
5116
5201
|
*
|
|
5117
5202
|
* The CLI documents a wider event surface than the shared cloud-agent set, so
|
|
@@ -5265,12 +5350,15 @@ const KIRO_IDE_HOOK_EVENTS = [
|
|
|
5265
5350
|
/**
|
|
5266
5351
|
* Hook events supported by Google Antigravity (both the IDE and the CLI).
|
|
5267
5352
|
*
|
|
5268
|
-
* Antigravity exposes a Claude-style hooks surface covering the
|
|
5269
|
-
* tool/model/turn lifecycle events it documents: `
|
|
5270
|
-
* `PreInvocation`, `PostInvocation`, and `Stop`.
|
|
5271
|
-
* (`PreInvocation`/`PostInvocation`)
|
|
5353
|
+
* Antigravity exposes a Claude-style hooks surface covering the six
|
|
5354
|
+
* session/tool/model/turn lifecycle events it documents: `SessionStart`,
|
|
5355
|
+
* `PreToolUse`, `PostToolUse`, `PreInvocation`, `PostInvocation`, and `Stop`.
|
|
5356
|
+
* `SessionStart`, the model-invocation events (`PreInvocation`/`PostInvocation`)
|
|
5357
|
+
* and `Stop` are matcher-less handler lists.
|
|
5358
|
+
* https://antigravity.google/docs/hooks
|
|
5272
5359
|
*/
|
|
5273
5360
|
const ANTIGRAVITY_HOOK_EVENTS = [
|
|
5361
|
+
"sessionStart",
|
|
5274
5362
|
"preToolUse",
|
|
5275
5363
|
"postToolUse",
|
|
5276
5364
|
"preModelInvocation",
|
|
@@ -6043,8 +6131,8 @@ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
6043
6131
|
/**
|
|
6044
6132
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
6045
6133
|
*
|
|
6046
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set —
|
|
6047
|
-
* of v0.
|
|
6134
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 46 entries as
|
|
6135
|
+
* of v0.22.3 (tag `v0.22.3`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
6048
6136
|
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
6049
6137
|
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
6050
6138
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
@@ -6068,15 +6156,16 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
6068
6156
|
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
6069
6157
|
* Shell hooks are gated on the same set.
|
|
6070
6158
|
*
|
|
6071
|
-
* Verified against the tag `v0.
|
|
6159
|
+
* Verified against the tag `v0.22.3`: `VALID_HOOKS` holds 46 entries, grown
|
|
6072
6160
|
* from 23 at `v2026.8.3` (v0.20.0) and 37 at `v2026.8.16` (v0.20.2) — v0.21
|
|
6073
6161
|
* added `agent_loop_stopped` and `on_room_member_activity`, v0.21.5 added
|
|
6074
|
-
* `pre_auxiliary_call` and `post_auxiliary_call`,
|
|
6162
|
+
* `pre_auxiliary_call` and `post_auxiliary_call`, v0.21.6 added
|
|
6075
6163
|
* `post_gateway_admission`, `on_human_input_request` and
|
|
6076
|
-
* `on_human_input_resolved
|
|
6164
|
+
* `on_human_input_resolved`, and v0.22.3 added `on_session_delete` and
|
|
6165
|
+
* `post_compaction`. This list holds 45 of them — see
|
|
6077
6166
|
* {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
6078
6167
|
*
|
|
6079
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6168
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/hermes_cli/plugins.py
|
|
6080
6169
|
*/
|
|
6081
6170
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
6082
6171
|
"pre_tool_call",
|
|
@@ -6100,6 +6189,7 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6100
6189
|
"on_session_end",
|
|
6101
6190
|
"on_session_finalize",
|
|
6102
6191
|
"on_session_reset",
|
|
6192
|
+
"on_session_delete",
|
|
6103
6193
|
"on_skill_lifecycle",
|
|
6104
6194
|
"subagent_start",
|
|
6105
6195
|
"subagent_stop",
|
|
@@ -6121,7 +6211,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6121
6211
|
"on_kanban_task_updated",
|
|
6122
6212
|
"on_kanban_dispatch_tick",
|
|
6123
6213
|
"gateway_platform_event",
|
|
6124
|
-
"pre_command"
|
|
6214
|
+
"pre_command",
|
|
6215
|
+
"post_compaction"
|
|
6125
6216
|
];
|
|
6126
6217
|
/**
|
|
6127
6218
|
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
@@ -6133,8 +6224,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
6133
6224
|
* rulesync only ever writes shell hooks, so these are excluded from
|
|
6134
6225
|
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
6135
6226
|
*
|
|
6136
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6137
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v0.
|
|
6227
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
6228
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v0.22.3/agent/shell_hooks.py — `_parse_hooks_block`
|
|
6138
6229
|
*/
|
|
6139
6230
|
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
6140
6231
|
/**
|
|
@@ -6316,10 +6407,11 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6316
6407
|
const AUGMENTCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENTCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6317
6408
|
/**
|
|
6318
6409
|
* Map canonical camelCase event names to Antigravity PascalCase.
|
|
6319
|
-
* Antigravity uses the same PascalCase names as Claude for its tool/turn events,
|
|
6410
|
+
* Antigravity uses the same PascalCase names as Claude for its session/tool/turn events,
|
|
6320
6411
|
* plus `PreInvocation`/`PostInvocation` for the model-invocation lifecycle.
|
|
6321
6412
|
*/
|
|
6322
6413
|
const CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES = {
|
|
6414
|
+
sessionStart: "SessionStart",
|
|
6323
6415
|
preToolUse: "PreToolUse",
|
|
6324
6416
|
postToolUse: "PostToolUse",
|
|
6325
6417
|
preModelInvocation: "PreInvocation",
|
|
@@ -7338,14 +7430,19 @@ const toolScopedMcpSchema = z.looseObject({ mcpServers: z.optional(z.record(z.st
|
|
|
7338
7430
|
/**
|
|
7339
7431
|
* Kimi Code's tool-scoped block also carries the `[mcp]` defaults from its
|
|
7340
7432
|
* shared user config: connect and per-tool-call timeouts that apply to *every*
|
|
7341
|
-
* MCP server, including ones rulesync did not write.
|
|
7342
|
-
* `
|
|
7433
|
+
* MCP server, including ones rulesync did not write, and (Kimi Code 2.3.0)
|
|
7434
|
+
* `defaultDeferred`, which loads every server's tools on demand unless the
|
|
7435
|
+
* server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
|
|
7436
|
+
* in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
|
|
7437
|
+
* experimental `tool-select` flag is on, and is captured when a server connects
|
|
7438
|
+
* (MoonshotAI/kimi-code#4210).
|
|
7343
7439
|
*
|
|
7344
7440
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
7345
7441
|
*/
|
|
7346
7442
|
const kimiCodeScopedMcpSchema = z.extend(toolScopedMcpSchema, {
|
|
7347
7443
|
startupTimeoutMs: z.optional(z.number()),
|
|
7348
|
-
toolTimeoutMs: z.optional(z.number())
|
|
7444
|
+
toolTimeoutMs: z.optional(z.number()),
|
|
7445
|
+
defaultDeferred: z.optional(z.boolean())
|
|
7349
7446
|
});
|
|
7350
7447
|
/**
|
|
7351
7448
|
* Qwen Code's tool-scoped block also carries the server-level allow/deny lists
|
|
@@ -8114,13 +8211,16 @@ const CopilotPermissionsOverrideSchema = z.looseObject({
|
|
|
8114
8211
|
});
|
|
8115
8212
|
/**
|
|
8116
8213
|
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
8117
|
-
* before prompting. `always-ask` auto-approves only read-only tools,
|
|
8118
|
-
*
|
|
8214
|
+
* before prompting. `always-ask` auto-approves only read-only tools,
|
|
8215
|
+
* `always-ask-cached` (Ask and remember) also prompts before file reads and
|
|
8216
|
+
* lets a prompt remember the granted file or folder, `write` also
|
|
8217
|
+
* auto-approves workspace writes, and `yolo` (oh-my-pi's default) every tier.
|
|
8119
8218
|
*
|
|
8120
8219
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
8121
8220
|
*/
|
|
8122
8221
|
const OMP_APPROVAL_MODES = [
|
|
8123
8222
|
"always-ask",
|
|
8223
|
+
"always-ask-cached",
|
|
8124
8224
|
"write",
|
|
8125
8225
|
"yolo"
|
|
8126
8226
|
];
|
|
@@ -8141,10 +8241,13 @@ const KimiCodePermissionsOverrideSchema = z.looseObject({
|
|
|
8141
8241
|
])),
|
|
8142
8242
|
/**
|
|
8143
8243
|
* Kimi's `[permission] dangerous_command_guard` switch (Kimi Code 0.40.0,
|
|
8144
|
-
* default `true`). `false` turns off
|
|
8145
|
-
*
|
|
8244
|
+
* default `true`). Since Kimi Code 2.3.0, `false` turns off every built-in
|
|
8245
|
+
* guard confirmation policy: dangerous commands (`rm -rf`, `shutdown` and the
|
|
8246
|
+
* like), sensitive-file access (`.env`, `id_rsa`, ...) and git control-path
|
|
8247
|
+
* access. Before 2.3.0 it only covered dangerous commands.
|
|
8146
8248
|
*
|
|
8147
8249
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
8250
|
+
* @see https://github.com/MoonshotAI/kimi-code/pull/4250
|
|
8148
8251
|
*/
|
|
8149
8252
|
dangerousCommandGuard: z.optional(z.boolean()),
|
|
8150
8253
|
rules: z.optional(z.array(z.looseObject({
|
|
@@ -8504,6 +8607,8 @@ const TabninePermissionsOverrideSchema = z.looseObject({
|
|
|
8504
8607
|
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
8505
8608
|
* @example
|
|
8506
8609
|
* { "mode": "deny" }
|
|
8610
|
+
* @example
|
|
8611
|
+
* { "tools": { "disabled": ["web_fetch"] }, "askOnExternalOrigin": true }
|
|
8507
8612
|
*/
|
|
8508
8613
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
8509
8614
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
@@ -8517,7 +8622,9 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8517
8622
|
"ask",
|
|
8518
8623
|
"allow",
|
|
8519
8624
|
"deny"
|
|
8520
|
-
]))
|
|
8625
|
+
])),
|
|
8626
|
+
askOnExternalOrigin: z.optional(z.boolean()),
|
|
8627
|
+
tools: z.optional(z.strictObject({ disabled: z.optional(z.array(z.string())) }))
|
|
8521
8628
|
});
|
|
8522
8629
|
/**
|
|
8523
8630
|
* Tool-scoped override block for Qoder. `outputStyle` is written to the
|
|
@@ -11331,6 +11438,15 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
11331
11438
|
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
11332
11439
|
metadata: z.optional(z.looseObject({}))
|
|
11333
11440
|
})),
|
|
11441
|
+
xum: z.optional(z.looseObject({
|
|
11442
|
+
"disable-model-invocation": z.optional(z.boolean()),
|
|
11443
|
+
advertise: z.optional(z.boolean()),
|
|
11444
|
+
"user-invocable": z.optional(z.boolean()),
|
|
11445
|
+
"argument-hint": z.optional(z.string()),
|
|
11446
|
+
when_to_use: z.optional(z.string()),
|
|
11447
|
+
"when-to-use": z.optional(z.string()),
|
|
11448
|
+
paths: z.optional(z.union([z.string(), z.array(z.string())]))
|
|
11449
|
+
})),
|
|
11334
11450
|
zcode: z.optional(z.looseObject({
|
|
11335
11451
|
when_to_use: z.optional(z.string()),
|
|
11336
11452
|
license: z.optional(z.string()),
|
|
@@ -11609,7 +11725,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11609
11725
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11610
11726
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11611
11727
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11612
|
-
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11728
|
+
* commandcode, lettacode, opencode, mimocode, xum). Each tool's own section may override that
|
|
11613
11729
|
* default with a per-target value. A defined section value (including `false`)
|
|
11614
11730
|
* always wins over the root default.
|
|
11615
11731
|
*
|
|
@@ -11629,7 +11745,7 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
11629
11745
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
11630
11746
|
* that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11631
11747
|
* copilotcli, crush, cursor, qwencode, vibe, grokcli, factorydroid, dsh, reasonix, commandcode,
|
|
11632
|
-
* lettacode). Each tool's own section may override that default with a
|
|
11748
|
+
* lettacode, xum). Each tool's own section may override that default with a
|
|
11633
11749
|
* per-target value. A defined section value (including `false`) always wins
|
|
11634
11750
|
* over the root default.
|
|
11635
11751
|
*
|
|
@@ -13944,7 +14060,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13944
14060
|
ownedKeys: [
|
|
13945
14061
|
"permissions",
|
|
13946
14062
|
"sandbox",
|
|
13947
|
-
"agent"
|
|
14063
|
+
"agent",
|
|
14064
|
+
"tools"
|
|
13948
14065
|
]
|
|
13949
14066
|
}
|
|
13950
14067
|
}
|
|
@@ -13965,7 +14082,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13965
14082
|
ownedKeys: [
|
|
13966
14083
|
"permissions",
|
|
13967
14084
|
"sandbox",
|
|
13968
|
-
"agent"
|
|
14085
|
+
"agent",
|
|
14086
|
+
"tools"
|
|
13969
14087
|
]
|
|
13970
14088
|
}
|
|
13971
14089
|
}
|
|
@@ -24090,6 +24208,7 @@ var QwencodeCommand = class QwencodeCommand extends ToolCommand {
|
|
|
24090
24208
|
const REASONIX_PROJECT_MCP_FILE_NAME = "reasonix.toml";
|
|
24091
24209
|
const REASONIX_GLOBAL_DIR = ".reasonix";
|
|
24092
24210
|
const REASONIX_GLOBAL_MCP_FILE_NAME = "config.toml";
|
|
24211
|
+
const REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH = join(".reasonix", REASONIX_PROJECT_MCP_FILE_NAME);
|
|
24093
24212
|
const REASONIX_PROJECT_PERMISSIONS_FILE_NAME = REASONIX_PROJECT_MCP_FILE_NAME;
|
|
24094
24213
|
const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
|
|
24095
24214
|
const REASONIX_DIR = REASONIX_GLOBAL_DIR;
|
|
@@ -28437,25 +28556,65 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
28437
28556
|
//#endregion
|
|
28438
28557
|
//#region src/features/hooks/antigravity-hooks.ts
|
|
28439
28558
|
/**
|
|
28440
|
-
*
|
|
28441
|
-
* `
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28559
|
+
* Canonical events whose Antigravity counterparts (`SessionStart`,
|
|
28560
|
+
* `PreInvocation`, `PostInvocation`, `Stop`) take no matcher.
|
|
28561
|
+
*/
|
|
28562
|
+
const ANTIGRAVITY_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
28563
|
+
"sessionStart",
|
|
28564
|
+
"preModelInvocation",
|
|
28565
|
+
"postModelInvocation",
|
|
28566
|
+
"stop"
|
|
28567
|
+
]);
|
|
28568
|
+
/**
|
|
28569
|
+
* Antigravity event names whose value is a flat list of handlers
|
|
28570
|
+
* (`"Stop": [ { "type": "command", "command": "..." } ]`) rather than a list
|
|
28571
|
+
* of `{ "matcher", "hooks": [...] }` groups. Antigravity's loader rejects a
|
|
28572
|
+
* wrapped group under these events ("command hook must specify 'command'")
|
|
28573
|
+
* and then skips the whole `hooks.json`.
|
|
28574
|
+
* https://antigravity.google/docs/hooks
|
|
28575
|
+
*/
|
|
28576
|
+
const ANTIGRAVITY_FLAT_HANDLER_EVENTS = new Set([...ANTIGRAVITY_NO_MATCHER_EVENTS].map((event) => lookupOwn({
|
|
28577
|
+
record: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
|
|
28578
|
+
key: event
|
|
28579
|
+
}) ?? event));
|
|
28580
|
+
/**
|
|
28581
|
+
* Antigravity uses a Claude-Code-like `hooks.json`. The `PreToolUse` /
|
|
28582
|
+
* `PostToolUse` events use the matcher-based group shape, while `SessionStart`,
|
|
28583
|
+
* `PreInvocation`, `PostInvocation` and `Stop` are flat handler lists (see
|
|
28584
|
+
* {@link toAntigravityEventMap}). `projectDirVar` is empty because Antigravity
|
|
28585
|
+
* does not document a project-directory variable for commands.
|
|
28445
28586
|
*/
|
|
28446
28587
|
const ANTIGRAVITY_CONVERTER_CONFIG = {
|
|
28447
28588
|
supportedEvents: ANTIGRAVITY_HOOK_EVENTS,
|
|
28448
28589
|
canonicalToToolEventNames: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
|
|
28449
28590
|
toolToCanonicalEventNames: ANTIGRAVITY_TO_CANONICAL_EVENT_NAMES,
|
|
28450
28591
|
projectDirVar: "",
|
|
28451
|
-
noMatcherEvents:
|
|
28452
|
-
"preModelInvocation",
|
|
28453
|
-
"postModelInvocation",
|
|
28454
|
-
"stop"
|
|
28455
|
-
]),
|
|
28592
|
+
noMatcherEvents: ANTIGRAVITY_NO_MATCHER_EVENTS,
|
|
28456
28593
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"])
|
|
28457
28594
|
};
|
|
28458
28595
|
/**
|
|
28596
|
+
* Turn the shared converter's matcher-group output into Antigravity's shape:
|
|
28597
|
+
* the matcher-less events get their handlers directly under the event key.
|
|
28598
|
+
*/
|
|
28599
|
+
function toAntigravityEventMap(eventMap) {
|
|
28600
|
+
return Object.fromEntries(Object.entries(eventMap).map(([event, entries]) => {
|
|
28601
|
+
if (!ANTIGRAVITY_FLAT_HANDLER_EVENTS.has(event)) return [event, entries];
|
|
28602
|
+
return [event, entries.flatMap((entry) => isPlainObject$1(entry) && Array.isArray(entry.hooks) ? entry.hooks : [])];
|
|
28603
|
+
}));
|
|
28604
|
+
}
|
|
28605
|
+
/**
|
|
28606
|
+
* Reverse of {@link toAntigravityEventMap} for import: wrap each flat handler
|
|
28607
|
+
* of a matcher-less event in a `{ hooks: [...] }` group so the shared converter
|
|
28608
|
+
* can read it. An entry that is already a group (`{ hooks: [...] }`, as older
|
|
28609
|
+
* rulesync versions wrote) is kept as is.
|
|
28610
|
+
*/
|
|
28611
|
+
function toMatcherGroupEventMap(eventMap) {
|
|
28612
|
+
return Object.fromEntries(Object.entries(eventMap).map(([event, entries]) => {
|
|
28613
|
+
if (!ANTIGRAVITY_FLAT_HANDLER_EVENTS.has(event) || !Array.isArray(entries)) return [event, entries];
|
|
28614
|
+
return [event, entries.map((entry) => isPlainObject$1(entry) && !("hooks" in entry) ? { hooks: [entry] } : entry)];
|
|
28615
|
+
}));
|
|
28616
|
+
}
|
|
28617
|
+
/**
|
|
28459
28618
|
* Antigravity's `hooks.json` is keyed by a named hook whose value holds the
|
|
28460
28619
|
* per-event map (plus an optional `enabled` flag). rulesync writes a single
|
|
28461
28620
|
* generated hook under this stable name.
|
|
@@ -28492,8 +28651,8 @@ function flattenAntigravityHooks(parsed) {
|
|
|
28492
28651
|
* Hooks generator for Google Antigravity (both the IDE and the CLI).
|
|
28493
28652
|
*
|
|
28494
28653
|
* Antigravity writes a dedicated `hooks.json` keyed by a named hook whose
|
|
28495
|
-
* value holds the
|
|
28496
|
-
*
|
|
28654
|
+
* value holds the event map (`{ "<name>": { "<Event>": [...] } }`): matcher
|
|
28655
|
+
* groups for the tool events, flat handler lists for the others. Project and global modes share the
|
|
28497
28656
|
* same shape; only the location differs (`.agents/hooks.json` vs
|
|
28498
28657
|
* `~/.gemini/config/hooks.json`).
|
|
28499
28658
|
*
|
|
@@ -28537,12 +28696,12 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
28537
28696
|
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
28538
28697
|
const paths = this.getSettablePaths({ global });
|
|
28539
28698
|
const config = rulesyncHooks.getJson();
|
|
28540
|
-
const eventMap = canonicalToToolHooks({
|
|
28699
|
+
const eventMap = toAntigravityEventMap(canonicalToToolHooks({
|
|
28541
28700
|
config,
|
|
28542
28701
|
toolOverrideHooks: config[this.getOverrideKey()]?.hooks,
|
|
28543
28702
|
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
28544
28703
|
logger
|
|
28545
|
-
});
|
|
28704
|
+
}));
|
|
28546
28705
|
const antigravityHooks = Object.keys(eventMap).length > 0 ? { [ANTIGRAVITY_HOOK_NAME]: eventMap } : {};
|
|
28547
28706
|
const fileContent = JSON.stringify(antigravityHooks, null, 2);
|
|
28548
28707
|
return new this({
|
|
@@ -28561,7 +28720,7 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
28561
28720
|
throw new Error(`Failed to parse Antigravity hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28562
28721
|
}
|
|
28563
28722
|
const hooks = toolHooksToCanonical({
|
|
28564
|
-
hooks: flattenAntigravityHooks(parsed),
|
|
28723
|
+
hooks: toMatcherGroupEventMap(flattenAntigravityHooks(parsed)),
|
|
28565
28724
|
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
28566
28725
|
logger
|
|
28567
28726
|
});
|
|
@@ -31025,11 +31184,17 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
31025
31184
|
* entry that uses them instead of a shell field is not mistaken for one
|
|
31026
31185
|
* without a command.
|
|
31027
31186
|
*
|
|
31187
|
+
* An optional `matcher` regex is honored on the events in
|
|
31188
|
+
* {@link COPILOT_MATCHER_HOOK_EVENTS}. The VS Code per-OS override fields come
|
|
31189
|
+
* from the legacy Local hooks reference and are kept for existing files.
|
|
31190
|
+
*
|
|
31028
31191
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
31192
|
+
* @see https://code.visualstudio.com/docs/agent-customization/hooks
|
|
31029
31193
|
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
31030
31194
|
*/
|
|
31031
31195
|
const CopilotHookEntrySchema = z.looseObject({
|
|
31032
31196
|
type: z.string(),
|
|
31197
|
+
matcher: z.optional(z.string()),
|
|
31033
31198
|
bash: z.optional(z.string()),
|
|
31034
31199
|
powershell: z.optional(z.string()),
|
|
31035
31200
|
command: z.optional(z.string()),
|
|
@@ -31063,8 +31228,10 @@ function canonicalToCopilotHooks(config) {
|
|
|
31063
31228
|
...sharedConfigHooks,
|
|
31064
31229
|
...config.copilot?.hooks
|
|
31065
31230
|
};
|
|
31231
|
+
const matcherEvents = new Set(COPILOT_MATCHER_HOOK_EVENTS);
|
|
31066
31232
|
const copilot = {};
|
|
31067
31233
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
31234
|
+
const matcherSupported = matcherEvents.has(eventName);
|
|
31068
31235
|
const copilotEventName = lookupOwn({
|
|
31069
31236
|
record: CANONICAL_TO_COPILOT_EVENT_NAMES,
|
|
31070
31237
|
key: eventName
|
|
@@ -31072,7 +31239,7 @@ function canonicalToCopilotHooks(config) {
|
|
|
31072
31239
|
const entries = [];
|
|
31073
31240
|
for (const def of definitions) {
|
|
31074
31241
|
const hookType = def.type ?? "command";
|
|
31075
|
-
if (def.matcher) continue;
|
|
31242
|
+
if (def.matcher && !matcherSupported) continue;
|
|
31076
31243
|
if (hookType !== "command") continue;
|
|
31077
31244
|
const command = def.command;
|
|
31078
31245
|
const timeout = def.timeout;
|
|
@@ -31080,6 +31247,7 @@ function canonicalToCopilotHooks(config) {
|
|
|
31080
31247
|
const rest = Object.fromEntries(Object.entries(def).filter(([k]) => !canonicalSchemaKeys.includes(k)));
|
|
31081
31248
|
entries.push({
|
|
31082
31249
|
type: hookType,
|
|
31250
|
+
...def.matcher && { matcher: def.matcher },
|
|
31083
31251
|
...command !== void 0 && command !== null && { [commandField]: command },
|
|
31084
31252
|
...def.env !== void 0 && { env: def.env },
|
|
31085
31253
|
...timeout !== void 0 && timeout !== null && { timeoutSec: timeout },
|
|
@@ -31174,6 +31342,7 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
31174
31342
|
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.`);
|
|
31175
31343
|
defs.push({
|
|
31176
31344
|
type: "command",
|
|
31345
|
+
...entry.matcher !== void 0 && entry.matcher !== "" && { matcher: entry.matcher },
|
|
31177
31346
|
...command !== void 0 && { command },
|
|
31178
31347
|
...shell !== void 0 && { shell },
|
|
31179
31348
|
...timeout !== void 0 && { timeout },
|
|
@@ -31317,21 +31486,13 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
31317
31486
|
*/
|
|
31318
31487
|
/**
|
|
31319
31488
|
* Canonical events whose Copilot CLI hook entries honor an optional `matcher`
|
|
31320
|
-
* field (a regex compiled as `^(?:PATTERN)
|
|
31321
|
-
*
|
|
31322
|
-
*
|
|
31323
|
-
*
|
|
31324
|
-
*
|
|
31325
|
-
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
31489
|
+
* field (a regex compiled as `^(?:PATTERN)$`). The hooks reference filters each
|
|
31490
|
+
* on a different field: the tool name for `preToolUse` / `postToolUse` /
|
|
31491
|
+
* `permissionRequest`, `notification_type`, the `preCompact` trigger
|
|
31492
|
+
* (`manual` | `auto`) and the `subagentStart` agent name.
|
|
31493
|
+
* See {@link COPILOTCLI_MATCHER_HOOK_EVENTS}.
|
|
31326
31494
|
*/
|
|
31327
|
-
const COPILOTCLI_MATCHER_EVENTS =
|
|
31328
|
-
"preToolUse",
|
|
31329
|
-
"postToolUse",
|
|
31330
|
-
"notification",
|
|
31331
|
-
"permissionRequest",
|
|
31332
|
-
"preCompact",
|
|
31333
|
-
"subagentStart"
|
|
31334
|
-
]);
|
|
31495
|
+
const COPILOTCLI_MATCHER_EVENTS = new Set(COPILOTCLI_MATCHER_HOOK_EVENTS);
|
|
31335
31496
|
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
31336
31497
|
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
31337
31498
|
const CopilotCliHookEntrySchema = z.looseObject({
|
|
@@ -33467,7 +33628,7 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
33467
33628
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
33468
33629
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
33469
33630
|
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.`);
|
|
33470
|
-
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.
|
|
33631
|
+
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.`);
|
|
33471
33632
|
setHermesHookEntries({
|
|
33472
33633
|
result,
|
|
33473
33634
|
event: nativeEvent,
|
|
@@ -37557,7 +37718,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
37557
37718
|
},
|
|
37558
37719
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
37559
37720
|
supportedHookTypes: ["command"],
|
|
37560
|
-
supportsMatcher:
|
|
37721
|
+
supportsMatcher: true,
|
|
37722
|
+
matcherEvents: COPILOT_MATCHER_HOOK_EVENTS
|
|
37561
37723
|
}],
|
|
37562
37724
|
["copilotcli", {
|
|
37563
37725
|
class: CopilotcliHooks,
|
|
@@ -37572,7 +37734,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
37572
37734
|
"prompt",
|
|
37573
37735
|
"http"
|
|
37574
37736
|
],
|
|
37575
|
-
supportsMatcher:
|
|
37737
|
+
supportsMatcher: true
|
|
37576
37738
|
}],
|
|
37577
37739
|
["kilo", {
|
|
37578
37740
|
class: KiloHooks,
|
|
@@ -39962,6 +40124,31 @@ const REASONIX_READ_TOOL_NAME = "read_file";
|
|
|
39962
40124
|
* treated as rulesync-owned on generate so the dormant copy is replaced.
|
|
39963
40125
|
*/
|
|
39964
40126
|
const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
|
|
40127
|
+
async function isPathPresent(path) {
|
|
40128
|
+
return await fileExists(path) || await isPresentButUnresolvable(path);
|
|
40129
|
+
}
|
|
40130
|
+
/**
|
|
40131
|
+
* Warns when a project-scope generate is about to create `./reasonix.toml`
|
|
40132
|
+
* next to an existing `.reasonix/reasonix.toml`.
|
|
40133
|
+
*
|
|
40134
|
+
* Since v2.46.0 Reasonix reads `.reasonix/reasonix.toml` as the project config
|
|
40135
|
+
* when `./reasonix.toml` is absent, and never merges the two: once the root
|
|
40136
|
+
* file exists it wins, so every setting in the nested file (permissions,
|
|
40137
|
+
* sandbox, disabled tools, MCP servers, ...) silently stops applying. rulesync
|
|
40138
|
+
* writes only the root file, so it cannot keep the nested one in effect; this
|
|
40139
|
+
* at least names the problem, once per run however many of the features that
|
|
40140
|
+
* write the file (mcp, permissions, ignore) are generated. Like Reasonix, anything at the root path
|
|
40141
|
+
* (including a broken symlink) counts as the root file being there.
|
|
40142
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/internal/contract/config/paths.go
|
|
40143
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/docs/CONFIG_PATHS.md
|
|
40144
|
+
*/
|
|
40145
|
+
async function warnIfReasonixNestedProjectConfigShadowed({ outputRoot, global, logger }) {
|
|
40146
|
+
if (global) return;
|
|
40147
|
+
if (await isPathPresent(join(outputRoot, "reasonix.toml"))) return;
|
|
40148
|
+
if (!await isPathPresent(join(outputRoot, REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH))) return;
|
|
40149
|
+
const nested = toPosixPath(REASONIX_NESTED_PROJECT_CONFIG_RELATIVE_FILE_PATH);
|
|
40150
|
+
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.`);
|
|
40151
|
+
}
|
|
39965
40152
|
//#endregion
|
|
39966
40153
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
39967
40154
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -40045,7 +40232,12 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
40045
40232
|
fileContent: rulesyncPatterns.join("\n")
|
|
40046
40233
|
});
|
|
40047
40234
|
}
|
|
40048
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
40235
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false, logger }) {
|
|
40236
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
40237
|
+
outputRoot,
|
|
40238
|
+
global,
|
|
40239
|
+
logger
|
|
40240
|
+
});
|
|
40049
40241
|
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));
|
|
40050
40242
|
const paths = this.getSettablePaths({ global });
|
|
40051
40243
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
@@ -40623,7 +40815,8 @@ var IgnoreProcessor = class extends FeatureProcessor {
|
|
|
40623
40815
|
outputRoot: this.outputRoot,
|
|
40624
40816
|
rulesyncIgnore,
|
|
40625
40817
|
options: this.featureOptions,
|
|
40626
|
-
global: this.global
|
|
40818
|
+
global: this.global,
|
|
40819
|
+
logger: this.logger
|
|
40627
40820
|
});
|
|
40628
40821
|
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
|
|
40629
40822
|
toolIgnore,
|
|
@@ -40973,13 +41166,31 @@ function renameServerField(servers, from, to) {
|
|
|
40973
41166
|
return [name, Object.fromEntries(Object.entries(config).map(([key, value]) => key === from ? [to, value] : [key, value]))];
|
|
40974
41167
|
}));
|
|
40975
41168
|
}
|
|
41169
|
+
function isEmptyValue(value) {
|
|
41170
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
41171
|
+
return isPlainObject$1(value) && Object.keys(value).length === 0;
|
|
41172
|
+
}
|
|
40976
41173
|
/**
|
|
40977
41174
|
* Antigravity uses `serverUrl` (not the canonical `url`) for HTTP/SSE MCP
|
|
40978
41175
|
* servers. Convert the canonical shape into Antigravity's on the way out, and
|
|
40979
|
-
* back to canonical on the way in
|
|
41176
|
+
* back to canonical on the way in.
|
|
41177
|
+
*
|
|
41178
|
+
* Antigravity documents no `type` field: the transport follows from `command`
|
|
41179
|
+
* (stdio) or `serverUrl` (Streamable HTTP / SSE), and `agy plugin validate`
|
|
41180
|
+
* reports the canonical `type` as an unknown field, so it is not written. A
|
|
41181
|
+
* remote server that carries `command`, `args` or `env` was also observed to be
|
|
41182
|
+
* refused at startup (runtime behavior reported by `agy plugin validate` 1.4.4,
|
|
41183
|
+
* not documented), so an empty `args` / `env` left on a `serverUrl` server is
|
|
41184
|
+
* dropped.
|
|
41185
|
+
* https://antigravity.google/docs/mcp
|
|
40980
41186
|
*/
|
|
40981
41187
|
function toAntigravityMcpServers(servers) {
|
|
40982
|
-
|
|
41188
|
+
const renamed = renameServerField(servers, "url", "serverUrl");
|
|
41189
|
+
return Object.fromEntries(Object.entries(renamed).map(([name, config]) => {
|
|
41190
|
+
if (!isPlainObject$1(config)) return [name, config];
|
|
41191
|
+
const isRemote = typeof config.serverUrl === "string";
|
|
41192
|
+
return [name, Object.fromEntries(Object.entries(config).filter(([key, value]) => key !== "type" && !(isRemote && (key === "args" || key === "env") && isEmptyValue(value))))];
|
|
41193
|
+
}));
|
|
40983
41194
|
}
|
|
40984
41195
|
function toCanonicalMcpServers(servers) {
|
|
40985
41196
|
return renameServerField(servers, "serverUrl", "url");
|
|
@@ -45169,6 +45380,60 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
|
|
|
45169
45380
|
return true;
|
|
45170
45381
|
}
|
|
45171
45382
|
/**
|
|
45383
|
+
* Translate a canonical `oauth` block into dcode's custom Slack OAuth app
|
|
45384
|
+
* settings, or `undefined` (after warning) when it has to be left out.
|
|
45385
|
+
*
|
|
45386
|
+
* Since deepagents-code 0.1.87, `_validate_slack_oauth_config` accepts `oauth`
|
|
45387
|
+
* only as `{ client_id, client_secret? }` of non-empty strings, and only on a
|
|
45388
|
+
* `slack.com` / `*.slack.com` server with `auth: "oauth"`; anything else makes
|
|
45389
|
+
* dcode skip the whole server. The canonical block usually carries Claude
|
|
45390
|
+
* Code's `{ clientId, callbackPort, ... }` shape, so `clientId` becomes
|
|
45391
|
+
* `client_id` (a native `client_id` wins), `client_secret` passes through, and
|
|
45392
|
+
* every other key is dropped.
|
|
45393
|
+
*
|
|
45394
|
+
* @see https://github.com/langchain-ai/deepagents/blob/deepagents-code%3D%3D0.1.87/libs/code/deepagents_code/mcp_config.py
|
|
45395
|
+
*/
|
|
45396
|
+
function toDeepagentsOauth({ name, oauth, converted, logger }) {
|
|
45397
|
+
if (!isSlackOauthServer(converted)) {
|
|
45398
|
+
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.`);
|
|
45399
|
+
return;
|
|
45400
|
+
}
|
|
45401
|
+
const source = isRecord$1(oauth) ? oauth : {};
|
|
45402
|
+
const result = {};
|
|
45403
|
+
for (const [key, value] of Object.entries(source)) {
|
|
45404
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
45405
|
+
const nativeKey = key === "clientId" ? "client_id" : key;
|
|
45406
|
+
if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
|
|
45407
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
|
|
45408
|
+
continue;
|
|
45409
|
+
}
|
|
45410
|
+
if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
|
|
45411
|
+
if (!isDeepagentsOauthValue(value)) {
|
|
45412
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
|
|
45413
|
+
continue;
|
|
45414
|
+
}
|
|
45415
|
+
result[nativeKey] = value;
|
|
45416
|
+
}
|
|
45417
|
+
if (result.client_id === void 0) {
|
|
45418
|
+
logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
|
|
45419
|
+
return;
|
|
45420
|
+
}
|
|
45421
|
+
return result;
|
|
45422
|
+
}
|
|
45423
|
+
function isDeepagentsOauthValue(value) {
|
|
45424
|
+
return typeof value === "string" && value.trim() !== "" && !/[\x00-\x1f\x7f]/.test(value);
|
|
45425
|
+
}
|
|
45426
|
+
function isSlackOauthServer(server) {
|
|
45427
|
+
if (server.auth !== "oauth" || typeof server.url !== "string") return false;
|
|
45428
|
+
let host;
|
|
45429
|
+
try {
|
|
45430
|
+
host = new URL(server.url).hostname.toLowerCase();
|
|
45431
|
+
} catch {
|
|
45432
|
+
return false;
|
|
45433
|
+
}
|
|
45434
|
+
return host === "slack.com" || host.endsWith(".slack.com");
|
|
45435
|
+
}
|
|
45436
|
+
/**
|
|
45172
45437
|
* Translate one canonical server into dcode's `.mcp.json` shape, or `null` to
|
|
45173
45438
|
* skip it.
|
|
45174
45439
|
*
|
|
@@ -45191,7 +45456,7 @@ function toDeepagentsServer({ name, server, logger }) {
|
|
|
45191
45456
|
reason: "the WebSocket transport, which deepagents does not support",
|
|
45192
45457
|
logger
|
|
45193
45458
|
});
|
|
45194
|
-
const { enabledTools, disabledTools, type: _type, transport, disabled: _disabled, httpUrl, ...rest } = server;
|
|
45459
|
+
const { enabledTools, disabledTools, type: _type, transport, disabled: _disabled, httpUrl, oauth, ...rest } = server;
|
|
45195
45460
|
const converted = { ...rest };
|
|
45196
45461
|
if (!applyDeepagentsCommand({
|
|
45197
45462
|
name,
|
|
@@ -45206,6 +45471,15 @@ function toDeepagentsServer({ name, server, logger }) {
|
|
|
45206
45471
|
if (transport !== void 0) converted.transport = normalized;
|
|
45207
45472
|
else converted.type = normalized;
|
|
45208
45473
|
}
|
|
45474
|
+
if (oauth !== void 0) {
|
|
45475
|
+
const deepagentsOauth = toDeepagentsOauth({
|
|
45476
|
+
name,
|
|
45477
|
+
oauth,
|
|
45478
|
+
converted,
|
|
45479
|
+
logger
|
|
45480
|
+
});
|
|
45481
|
+
if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
|
|
45482
|
+
}
|
|
45209
45483
|
if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
|
|
45210
45484
|
toolName: TOOL_NAME,
|
|
45211
45485
|
serverName: name,
|
|
@@ -45232,6 +45506,13 @@ function toRulesyncServer(server) {
|
|
|
45232
45506
|
const converted = { ...rest };
|
|
45233
45507
|
for (const key of ["type", "transport"]) if (converted[key] === "streamable_http" || converted[key] === "streamable-http") converted[key] = "http";
|
|
45234
45508
|
if (Array.isArray(allowedTools)) converted.enabledTools = allowedTools;
|
|
45509
|
+
if (isRecord$1(converted.oauth) && Object.hasOwn(converted.oauth, "client_id")) {
|
|
45510
|
+
const { client_id: clientId, ...oauthRest } = converted.oauth;
|
|
45511
|
+
converted.oauth = Object.hasOwn(oauthRest, "clientId") ? oauthRest : {
|
|
45512
|
+
clientId,
|
|
45513
|
+
...oauthRest
|
|
45514
|
+
};
|
|
45515
|
+
}
|
|
45235
45516
|
return converted;
|
|
45236
45517
|
}
|
|
45237
45518
|
var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
@@ -46964,6 +47245,50 @@ function copyHermesOauth(source) {
|
|
|
46964
47245
|
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
46965
47246
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
46966
47247
|
}
|
|
47248
|
+
const HERMES_COST_UNIT_MAX_LENGTH = 32;
|
|
47249
|
+
function describeHermesValue(value) {
|
|
47250
|
+
return typeof value === "number" ? String(value) : JSON.stringify(value) ?? String(value);
|
|
47251
|
+
}
|
|
47252
|
+
/**
|
|
47253
|
+
* Copies the per-server MCP cost keys Hermes v0.22.3 reads in
|
|
47254
|
+
* `tools/mcp_tool_costs.py`: `tool_costs` (declared per-tool costs keyed by
|
|
47255
|
+
* MCP tool name; each a number, a `[min, max]` range or `{credits, note}`),
|
|
47256
|
+
* `spend_limit` (per-chat budget for the server's costed tools; `0`/absent =
|
|
47257
|
+
* no limit) and `cost_unit` (label shown with costs; default `credits`).
|
|
47258
|
+
*
|
|
47259
|
+
* A costed call that would exceed `spend_limit` asks first even on a
|
|
47260
|
+
* `trust: full` server, so dropping the key on regenerate would silently
|
|
47261
|
+
* remove the guard. Values Hermes would reject when loading the server are
|
|
47262
|
+
* dropped with a warning instead of being written into a config Hermes then
|
|
47263
|
+
* refuses: `spend_limit` must be a finite non-negative number, `cost_unit` a
|
|
47264
|
+
* non-blank string of at most 32 characters, and `tool_costs` a mapping. The
|
|
47265
|
+
* mapping is rebuilt as an opaque object (so no reference is shared with the
|
|
47266
|
+
* source), with prototype-pollution keys removed at every depth since its
|
|
47267
|
+
* values may themselves be objects.
|
|
47268
|
+
*/
|
|
47269
|
+
function copyHermesCostFields({ source, target, logger }) {
|
|
47270
|
+
let copied = false;
|
|
47271
|
+
const { spend_limit: spendLimit, cost_unit: costUnit, tool_costs: toolCosts } = source;
|
|
47272
|
+
if (spendLimit !== void 0) {
|
|
47273
|
+
if (typeof spendLimit === "number" && Number.isFinite(spendLimit) && spendLimit >= 0) {
|
|
47274
|
+
target.spend_limit = spendLimit;
|
|
47275
|
+
copied = true;
|
|
47276
|
+
} else logger?.warn(`Hermes MCP: dropping spend_limit ${describeHermesValue(spendLimit)}; Hermes requires a non-negative number.`);
|
|
47277
|
+
}
|
|
47278
|
+
if (costUnit !== void 0) {
|
|
47279
|
+
if (typeof costUnit === "string" && costUnit.trim() !== "" && costUnit.length <= HERMES_COST_UNIT_MAX_LENGTH) {
|
|
47280
|
+
target.cost_unit = costUnit;
|
|
47281
|
+
copied = true;
|
|
47282
|
+
} 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.`);
|
|
47283
|
+
}
|
|
47284
|
+
if (toolCosts !== void 0) {
|
|
47285
|
+
if (isPlainObject$1(toolCosts)) {
|
|
47286
|
+
target.tool_costs = omitPrototypePollutionKeysDeep(toolCosts);
|
|
47287
|
+
copied = true;
|
|
47288
|
+
} else logger?.warn("Hermes MCP: dropping tool_costs; Hermes requires a mapping of MCP tool names to costs.");
|
|
47289
|
+
}
|
|
47290
|
+
return copied;
|
|
47291
|
+
}
|
|
46967
47292
|
const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
46968
47293
|
"sampling",
|
|
46969
47294
|
"elicitation",
|
|
@@ -46975,13 +47300,18 @@ const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
|
46975
47300
|
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
46976
47301
|
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
46977
47302
|
* `connect_timeout` (seconds), `supports_parallel_tool_calls`, `protocol`,
|
|
46978
|
-
* `lazy`, `keepalive_interval`, `elicitation`, `trust`,
|
|
47303
|
+
* `lazy`, `keepalive_interval`, `elicitation`, `trust`, `identity_header`, and
|
|
47304
|
+
* the cost keys (see {@link copyHermesCostFields}) —
|
|
46979
47305
|
* verbatim from `source` to `target`. Field names are identical on both sides
|
|
46980
47306
|
* (the canonical `McpServerSchema` is a `looseObject`), so this serves export
|
|
46981
47307
|
* and import alike. See the Hermes mcp-config-reference.
|
|
46982
47308
|
*/
|
|
46983
|
-
function copyHermesAdvancedFields(source, target) {
|
|
46984
|
-
let copied =
|
|
47309
|
+
function copyHermesAdvancedFields(source, target, logger) {
|
|
47310
|
+
let copied = copyHermesCostFields({
|
|
47311
|
+
source,
|
|
47312
|
+
target,
|
|
47313
|
+
logger
|
|
47314
|
+
});
|
|
46985
47315
|
if (typeof source.auth === "string") {
|
|
46986
47316
|
target.auth = source.auth;
|
|
46987
47317
|
copied = true;
|
|
@@ -47090,7 +47420,7 @@ function applyHermesToolsBlock(hermesTools, server) {
|
|
|
47090
47420
|
* as Hermes's own `transport: sse`, without which Hermes would connect to it
|
|
47091
47421
|
* over Streamable HTTP.
|
|
47092
47422
|
*/
|
|
47093
|
-
function convertServerToHermes(config) {
|
|
47423
|
+
function convertServerToHermes(config, logger) {
|
|
47094
47424
|
const out = {};
|
|
47095
47425
|
const command = config.command;
|
|
47096
47426
|
const url = resolveHermesUrl(config);
|
|
@@ -47113,7 +47443,7 @@ function convertServerToHermes(config) {
|
|
|
47113
47443
|
if (config.disabled === true) out.enabled = false;
|
|
47114
47444
|
const timeout = resolveHermesTimeout(config);
|
|
47115
47445
|
if (timeout !== void 0) out.timeout = timeout;
|
|
47116
|
-
copyHermesAdvancedFields(config, out);
|
|
47446
|
+
copyHermesAdvancedFields(config, out, logger);
|
|
47117
47447
|
const tools = buildHermesToolsBlock(config);
|
|
47118
47448
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
47119
47449
|
return out;
|
|
@@ -47121,11 +47451,11 @@ function convertServerToHermes(config) {
|
|
|
47121
47451
|
/**
|
|
47122
47452
|
* Converts rulesync canonical MCP servers into Hermes `mcp_servers:` entries.
|
|
47123
47453
|
*/
|
|
47124
|
-
function convertToHermesFormat(mcpServers) {
|
|
47454
|
+
function convertToHermesFormat(mcpServers, logger) {
|
|
47125
47455
|
const result = {};
|
|
47126
47456
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
47127
47457
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
47128
|
-
result[name] = convertServerToHermes(config);
|
|
47458
|
+
result[name] = convertServerToHermes(config, logger);
|
|
47129
47459
|
}
|
|
47130
47460
|
return result;
|
|
47131
47461
|
}
|
|
@@ -47145,7 +47475,7 @@ function mergeHermesMcpServers(config, mcpServers) {
|
|
|
47145
47475
|
* Mirrors {@link convertToHermesFormat}: `enabled: false` maps back to the
|
|
47146
47476
|
* canonical `disabled: true`, and only recognized fields are carried over.
|
|
47147
47477
|
*/
|
|
47148
|
-
function convertFromHermesFormat(mcpServers) {
|
|
47478
|
+
function convertFromHermesFormat(mcpServers, logger) {
|
|
47149
47479
|
const result = {};
|
|
47150
47480
|
const hermesOverrides = {};
|
|
47151
47481
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
@@ -47162,7 +47492,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
47162
47492
|
if (isRecord$1(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
47163
47493
|
result[name] = server;
|
|
47164
47494
|
const hermesServer = { ...server };
|
|
47165
|
-
if (copyHermesAdvancedFields(config, hermesServer)) hermesOverrides[name] = hermesServer;
|
|
47495
|
+
if (copyHermesAdvancedFields(config, hermesServer, logger)) hermesOverrides[name] = hermesServer;
|
|
47166
47496
|
}
|
|
47167
47497
|
return {
|
|
47168
47498
|
mcpServers: result,
|
|
@@ -47181,8 +47511,10 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
47181
47511
|
*/
|
|
47182
47512
|
var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
47183
47513
|
config;
|
|
47184
|
-
|
|
47514
|
+
logger;
|
|
47515
|
+
constructor({ logger, ...params }) {
|
|
47185
47516
|
super(params);
|
|
47517
|
+
this.logger = logger;
|
|
47186
47518
|
this.config = this.fileContent !== void 0 ? parseSharedConfig({
|
|
47187
47519
|
format: "yaml",
|
|
47188
47520
|
fileContent: this.fileContent
|
|
@@ -47226,7 +47558,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47226
47558
|
static getExtraSharedWritePaths() {
|
|
47227
47559
|
return getHermesagentSharedConfigWritePaths();
|
|
47228
47560
|
}
|
|
47229
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
47561
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
|
|
47230
47562
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
47231
47563
|
const paths = this.getSettablePaths({ global });
|
|
47232
47564
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
@@ -47236,17 +47568,18 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47236
47568
|
relativeFilePath: paths.relativeFilePath,
|
|
47237
47569
|
fileContent,
|
|
47238
47570
|
validate,
|
|
47239
|
-
global
|
|
47571
|
+
global,
|
|
47572
|
+
logger
|
|
47240
47573
|
});
|
|
47241
47574
|
}
|
|
47242
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
47575
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
47243
47576
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
47244
47577
|
const paths = this.getSettablePaths({ global });
|
|
47245
47578
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
47246
47579
|
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
47247
47580
|
format: "yaml",
|
|
47248
47581
|
fileContent
|
|
47249
|
-
}), convertToHermesFormat(rulesyncMcp.getMcpServers()));
|
|
47582
|
+
}), convertToHermesFormat(rulesyncMcp.getMcpServers(), logger));
|
|
47250
47583
|
return new HermesagentMcp({
|
|
47251
47584
|
outputRoot,
|
|
47252
47585
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -47258,11 +47591,12 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
47258
47591
|
patch: { mcp_servers: merged.mcp_servers }
|
|
47259
47592
|
}),
|
|
47260
47593
|
validate,
|
|
47261
|
-
global
|
|
47594
|
+
global,
|
|
47595
|
+
logger
|
|
47262
47596
|
});
|
|
47263
47597
|
}
|
|
47264
47598
|
toRulesyncMcp() {
|
|
47265
|
-
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
47599
|
+
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {}, this.logger);
|
|
47266
47600
|
return this.toRulesyncMcpDefault({
|
|
47267
47601
|
outputRoot: getHermesagentRulesyncOutputRoot({
|
|
47268
47602
|
nativeOutputRoot: this.outputRoot,
|
|
@@ -48146,14 +48480,15 @@ async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
|
48146
48480
|
if (!config.parsed) return {};
|
|
48147
48481
|
return {
|
|
48148
48482
|
...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
|
|
48149
|
-
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms }
|
|
48483
|
+
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
|
|
48484
|
+
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
|
|
48150
48485
|
};
|
|
48151
48486
|
}
|
|
48152
48487
|
/**
|
|
48153
48488
|
* Kimi's `[mcp]` section in the shared user config sets the default connect and
|
|
48154
|
-
* per-tool-call timeouts
|
|
48155
|
-
* write. Per-server values in
|
|
48156
|
-
* the
|
|
48489
|
+
* per-tool-call timeouts, and whether tools load on demand, for *every* MCP
|
|
48490
|
+
* server, including ones rulesync did not write. Per-server values in
|
|
48491
|
+
* `mcp.json` still take precedence; this only moves the default.
|
|
48157
48492
|
*
|
|
48158
48493
|
* Written as an auxiliary file so it goes through the normal write phase and
|
|
48159
48494
|
* respects `--dry-run`, and merged through the shared-config gateway so the
|
|
@@ -48161,7 +48496,8 @@ async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
|
48161
48496
|
*
|
|
48162
48497
|
* The gateway replaces an owned key wholesale, and `mcp` is a table, so the
|
|
48163
48498
|
* section is recomputed from the existing file: authoring only
|
|
48164
|
-
* `startupTimeoutMs` must not delete a hand-written `tool_timeout_ms
|
|
48499
|
+
* `startupTimeoutMs` must not delete a hand-written `tool_timeout_ms` or
|
|
48500
|
+
* `default_deferred`. Same
|
|
48165
48501
|
* approach the `.vibe/config.toml` `tools` writer takes.
|
|
48166
48502
|
*
|
|
48167
48503
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
@@ -48188,7 +48524,8 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
48188
48524
|
patch: { mcp: {
|
|
48189
48525
|
...existingSection,
|
|
48190
48526
|
...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
|
|
48191
|
-
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs }
|
|
48527
|
+
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
|
|
48528
|
+
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
|
|
48192
48529
|
} },
|
|
48193
48530
|
filePath: relativeConfigPath
|
|
48194
48531
|
});
|
|
@@ -48286,18 +48623,20 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
48286
48623
|
if (!isRecord$1(block)) return [];
|
|
48287
48624
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
48288
48625
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
48289
|
-
|
|
48626
|
+
const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
|
|
48627
|
+
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0) return [];
|
|
48290
48628
|
const existing = await readKimiCodeConfig({ outputRoot });
|
|
48291
48629
|
if (!existing.parsed) {
|
|
48292
48630
|
const paths = kimiCodeConfigRelativePath();
|
|
48293
|
-
warnWithFallback(logger, `Skipping the Kimi Code MCP
|
|
48631
|
+
warnWithFallback(logger, `Skipping the Kimi Code MCP [mcp] defaults: ${join(paths.relativeDirPath, paths.relativeFilePath)} is not valid TOML.`);
|
|
48294
48632
|
return [];
|
|
48295
48633
|
}
|
|
48296
48634
|
return [KimiCodeMcpConfigToml.fromDefaults({
|
|
48297
48635
|
outputRoot,
|
|
48298
48636
|
defaults: {
|
|
48299
48637
|
startupTimeoutMs,
|
|
48300
|
-
toolTimeoutMs
|
|
48638
|
+
toolTimeoutMs,
|
|
48639
|
+
defaultDeferred
|
|
48301
48640
|
},
|
|
48302
48641
|
existing
|
|
48303
48642
|
})];
|
|
@@ -50873,6 +51212,11 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
50873
51212
|
});
|
|
50874
51213
|
}
|
|
50875
51214
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
51215
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
51216
|
+
outputRoot,
|
|
51217
|
+
global,
|
|
51218
|
+
logger
|
|
51219
|
+
});
|
|
50876
51220
|
const paths = this.getSettablePaths({ global });
|
|
50877
51221
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
50878
51222
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -52900,6 +53244,144 @@ function buildCommandString(server) {
|
|
|
52900
53244
|
if (typeof server.command !== "string" || server.command.trim() === "") return void 0;
|
|
52901
53245
|
return [server.command, ...args.map(quoteShellWord)].join(" ");
|
|
52902
53246
|
}
|
|
53247
|
+
/** A name bash accepts on the left of a `NAME=value` assignment. */
|
|
53248
|
+
const SHELL_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
53249
|
+
/** A canonical `${VAR}` reference whose name bash can expand. */
|
|
53250
|
+
const ENV_VAR_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
53251
|
+
/**
|
|
53252
|
+
* Quote an env value for a `NAME=value` assignment. Literal text is
|
|
53253
|
+
* single-quoted, so nothing in it is expanded or run, while every canonical
|
|
53254
|
+
* `${VAR}` reference is emitted as `"${VAR}"` so bash still expands it from
|
|
53255
|
+
* the environment Xum starts the server with. The pieces are concatenated into
|
|
53256
|
+
* one shell word: `Bearer ${TOKEN}` becomes `'Bearer '"${TOKEN}"`.
|
|
53257
|
+
*/
|
|
53258
|
+
function quoteEnvValue(value) {
|
|
53259
|
+
if (value === "") return "''";
|
|
53260
|
+
const parts = [];
|
|
53261
|
+
let last = 0;
|
|
53262
|
+
for (const match of value.matchAll(ENV_VAR_REFERENCE)) {
|
|
53263
|
+
if (match.index > last) parts.push(quoteShellWord(value.slice(last, match.index)));
|
|
53264
|
+
parts.push(`"\${${match[1]}}"`);
|
|
53265
|
+
last = match.index + match[0].length;
|
|
53266
|
+
}
|
|
53267
|
+
if (last < value.length) parts.push(quoteShellWord(value.slice(last)));
|
|
53268
|
+
return parts.join("");
|
|
53269
|
+
}
|
|
53270
|
+
/**
|
|
53271
|
+
* Build the `NAME=value ` prefix that sets a stdio server's `env` for its
|
|
53272
|
+
* command only (Xum runs the command string with bash). Returns the prefix
|
|
53273
|
+
* plus the entries left out: names that cannot be bash assignment targets and
|
|
53274
|
+
* entries whose value is not a string.
|
|
53275
|
+
*/
|
|
53276
|
+
function buildEnvPrefix(env) {
|
|
53277
|
+
const assignments = [];
|
|
53278
|
+
const invalidNames = [];
|
|
53279
|
+
const nonStringNames = [];
|
|
53280
|
+
for (const [name, value] of Object.entries(env)) {
|
|
53281
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
53282
|
+
if (!SHELL_ENV_NAME.test(name)) {
|
|
53283
|
+
invalidNames.push(name);
|
|
53284
|
+
continue;
|
|
53285
|
+
}
|
|
53286
|
+
if (typeof value !== "string") {
|
|
53287
|
+
nonStringNames.push(name);
|
|
53288
|
+
continue;
|
|
53289
|
+
}
|
|
53290
|
+
assignments.push(`${name}=${quoteEnvValue(value)}`);
|
|
53291
|
+
}
|
|
53292
|
+
return {
|
|
53293
|
+
prefix: assignments.map((assignment) => `${assignment} `).join(""),
|
|
53294
|
+
invalidNames,
|
|
53295
|
+
nonStringNames
|
|
53296
|
+
};
|
|
53297
|
+
}
|
|
53298
|
+
/** Bash reserved words; a command starting with one cannot take an assignment prefix. */
|
|
53299
|
+
const BASH_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
53300
|
+
"!",
|
|
53301
|
+
"[[",
|
|
53302
|
+
"]]",
|
|
53303
|
+
"{",
|
|
53304
|
+
"}",
|
|
53305
|
+
"case",
|
|
53306
|
+
"coproc",
|
|
53307
|
+
"do",
|
|
53308
|
+
"done",
|
|
53309
|
+
"elif",
|
|
53310
|
+
"else",
|
|
53311
|
+
"esac",
|
|
53312
|
+
"fi",
|
|
53313
|
+
"for",
|
|
53314
|
+
"function",
|
|
53315
|
+
"if",
|
|
53316
|
+
"in",
|
|
53317
|
+
"select",
|
|
53318
|
+
"then",
|
|
53319
|
+
"time",
|
|
53320
|
+
"until",
|
|
53321
|
+
"while"
|
|
53322
|
+
]);
|
|
53323
|
+
/**
|
|
53324
|
+
* Explain why a `NAME=value` prefix would not reach the server, or return
|
|
53325
|
+
* `undefined` when it would. Bash applies such assignments to the first simple
|
|
53326
|
+
* command only: with a list or pipeline (`cd x && srv`) the server would not
|
|
53327
|
+
* see them, and in front of a group, parenthesized list or reserved word (`( srv )`,
|
|
53328
|
+
* `{ srv; }`, `if ...`) they are a syntax error that keeps the server from
|
|
53329
|
+
* starting. Only the verbatim `command` string is checked for operators,
|
|
53330
|
+
* outside complete single-quoted words; argv elements are quoted by
|
|
53331
|
+
* {@link quoteShellWord}.
|
|
53332
|
+
*/
|
|
53333
|
+
function envPrefixBlocker(server, baseCommand) {
|
|
53334
|
+
if (typeof server.command === "string") {
|
|
53335
|
+
const unquoted = server.command.replace(/'[^']*'/g, "");
|
|
53336
|
+
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";
|
|
53337
|
+
}
|
|
53338
|
+
const [firstWord] = baseCommand.trimStart().split(/\s+/);
|
|
53339
|
+
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`;
|
|
53340
|
+
}
|
|
53341
|
+
/**
|
|
53342
|
+
* One leading `NAME=value` assignment in exactly the shape {@link quoteEnvValue}
|
|
53343
|
+
* writes: bare shell-safe characters, single-quoted text (with the `'\''`
|
|
53344
|
+
* escape) and `"${VAR}"` references, followed by spaces or tabs (a newline
|
|
53345
|
+
* would end the command, so the assignment would not apply to what follows). Bare characters
|
|
53346
|
+
* are matched one at a time, so every alternative starts with a distinct
|
|
53347
|
+
* character and the pattern cannot backtrack exponentially.
|
|
53348
|
+
*/
|
|
53349
|
+
const LEADING_ENV_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)=((?:[A-Za-z0-9_\-.,:/@%+=]|'[^']*'|\\'|"\$\{[A-Za-z_][A-Za-z0-9_]*\}")*)[ \t]+/;
|
|
53350
|
+
/** Decode a value written by {@link quoteEnvValue} back into its canonical form. */
|
|
53351
|
+
function unquoteEnvValue(quoted) {
|
|
53352
|
+
return quoted.replace(/'([^']*)'|\\'|"(\$\{[A-Za-z_][A-Za-z0-9_]*\})"/g, (_m, single, ref) => typeof single === "string" ? single : typeof ref === "string" ? ref : "'");
|
|
53353
|
+
}
|
|
53354
|
+
/**
|
|
53355
|
+
* Split leading `NAME=value` assignments off a command string, but only when
|
|
53356
|
+
* every one of them is in the shape rulesync writes and a plain command
|
|
53357
|
+
* follows (see {@link envPrefixBlocker}); anything else (for example a `$(...)` substitution) keeps the whole string as
|
|
53358
|
+
* the command.
|
|
53359
|
+
*/
|
|
53360
|
+
function splitEnvPrefix(command) {
|
|
53361
|
+
const env = {};
|
|
53362
|
+
let rest = command.trimStart();
|
|
53363
|
+
for (;;) {
|
|
53364
|
+
const match = LEADING_ENV_ASSIGNMENT.exec(rest);
|
|
53365
|
+
if (match === null) break;
|
|
53366
|
+
const [whole, name, quoted] = match;
|
|
53367
|
+
if (name === void 0 || quoted === void 0 || PROTOTYPE_POLLUTION_KEYS.has(name)) break;
|
|
53368
|
+
env[name] = unquoteEnvValue(quoted);
|
|
53369
|
+
rest = rest.slice(whole.length);
|
|
53370
|
+
}
|
|
53371
|
+
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 };
|
|
53372
|
+
return {
|
|
53373
|
+
env,
|
|
53374
|
+
rest
|
|
53375
|
+
};
|
|
53376
|
+
}
|
|
53377
|
+
/** Read a command string back into `env` + `command` + `args`. */
|
|
53378
|
+
function parseCommandString(command) {
|
|
53379
|
+
const { env, rest } = splitEnvPrefix(command);
|
|
53380
|
+
return {
|
|
53381
|
+
...env !== void 0 && { env },
|
|
53382
|
+
...splitCommandString(rest)
|
|
53383
|
+
};
|
|
53384
|
+
}
|
|
52903
53385
|
/** Split a plain command string back into `command` + `args` when it is safe to. */
|
|
52904
53386
|
function splitCommandString(command) {
|
|
52905
53387
|
const trimmed = command.trim();
|
|
@@ -52943,10 +53425,20 @@ function convertServerToXum({ name, server, logger }) {
|
|
|
52943
53425
|
...settings
|
|
52944
53426
|
};
|
|
52945
53427
|
}
|
|
52946
|
-
const
|
|
52947
|
-
if (
|
|
52948
|
-
|
|
52949
|
-
if (
|
|
53428
|
+
const baseCommand = buildCommandString(server);
|
|
53429
|
+
if (baseCommand === void 0) return skip("it has no command");
|
|
53430
|
+
let command = baseCommand;
|
|
53431
|
+
if (isPlainObject$1(server.env) && Object.keys(server.env).length > 0) {
|
|
53432
|
+
const blocker = envPrefixBlocker(server, baseCommand);
|
|
53433
|
+
if (blocker !== void 0) warnWithFallback(logger, `Xum MCP: ${quoteValueForWarning(name)} sets env, but ${blocker}; the server is written without env.`);
|
|
53434
|
+
else {
|
|
53435
|
+
const { prefix, invalidNames, nonStringNames } = buildEnvPrefix(server.env);
|
|
53436
|
+
command = `${prefix}${baseCommand}`;
|
|
53437
|
+
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.`);
|
|
53438
|
+
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.`);
|
|
53439
|
+
}
|
|
53440
|
+
}
|
|
53441
|
+
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).`);
|
|
52950
53442
|
return Object.keys(settings).length > 0 ? {
|
|
52951
53443
|
command,
|
|
52952
53444
|
...settings
|
|
@@ -52966,7 +53458,7 @@ function convertFromXumFormat(servers) {
|
|
|
52966
53458
|
for (const [name, entry] of Object.entries(servers)) {
|
|
52967
53459
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
52968
53460
|
if (typeof entry === "string") {
|
|
52969
|
-
result[name] =
|
|
53461
|
+
result[name] = parseCommandString(entry);
|
|
52970
53462
|
continue;
|
|
52971
53463
|
}
|
|
52972
53464
|
if (!isPlainObject$1(entry)) continue;
|
|
@@ -52976,7 +53468,7 @@ function convertFromXumFormat(servers) {
|
|
|
52976
53468
|
server.url = entry.url;
|
|
52977
53469
|
const headers = copyStringHeaders(entry.headers);
|
|
52978
53470
|
if (headers !== void 0) server.headers = headers;
|
|
52979
|
-
} else if (typeof entry.command === "string") Object.assign(server,
|
|
53471
|
+
} else if (typeof entry.command === "string") Object.assign(server, parseCommandString(entry.command));
|
|
52980
53472
|
else continue;
|
|
52981
53473
|
if (entry.disabled === true) server.disabled = true;
|
|
52982
53474
|
if (isStringArray$1(entry.toolAllowlist)) server.enabledTools = entry.toolAllowlist;
|
|
@@ -52991,9 +53483,10 @@ function convertFromXumFormat(servers) {
|
|
|
52991
53483
|
* (project; a server there wins over the user one of the same name). Both are
|
|
52992
53484
|
* a JSONC document with a `servers` map:
|
|
52993
53485
|
*
|
|
52994
|
-
* - a stdio server is a shell command string (the object form
|
|
52995
|
-
* carries `disabled` / `toolAllowlist`);
|
|
52996
|
-
* `
|
|
53486
|
+
* - a stdio server is a shell command string run with bash (the object form
|
|
53487
|
+
* `{ command }` carries `disabled` / `toolAllowlist`); `env` is written as
|
|
53488
|
+
* `NAME=value` assignments in front of the command, and `cwd`, which has no
|
|
53489
|
+
* equivalent, is dropped with a warning;
|
|
52997
53490
|
* - a remote server is `{ transport: "http" | "sse" | "auto", url, headers }`
|
|
52998
53491
|
* (the transport defaults to auto-detection).
|
|
52999
53492
|
*
|
|
@@ -53125,7 +53618,7 @@ function asZcodeRemoteType(stated, url) {
|
|
|
53125
53618
|
}
|
|
53126
53619
|
/**
|
|
53127
53620
|
* The keys each OAuth flow of ZCode's per-server `oauth` union accepts on a
|
|
53128
|
-
* remote server, and which of them are required. Read off the 3.14.3
|
|
53621
|
+
* remote server, and which of them are required. Read off the 3.14.3–3.16.2
|
|
53129
53622
|
* runtime config schema, since ZCode's MCP page only says to "enable OAuth for
|
|
53130
53623
|
* the service in its configuration". Both variants are strict, so any other
|
|
53131
53624
|
* key would make ZCode skip the server.
|
|
@@ -53229,7 +53722,11 @@ function resolveZcodeTimeoutMs({ serverName, config, logger }) {
|
|
|
53229
53722
|
}
|
|
53230
53723
|
/**
|
|
53231
53724
|
* The per-server keys ZCode reads on every transport: `timeoutMs`,
|
|
53232
|
-
* `protocolVersion` and the enable toggle (`enableKey`).
|
|
53725
|
+
* `protocolVersion`, `alwaysLoad` and the enable toggle (`enableKey`).
|
|
53726
|
+
*
|
|
53727
|
+
* `alwaysLoad` (added in 3.16.2 alongside tool search) keeps a server's tools
|
|
53728
|
+
* loaded up front instead of deferring them behind tool search. ZCode's schema
|
|
53729
|
+
* types it as a boolean, so any other value is dropped with a warning.
|
|
53233
53730
|
*/
|
|
53234
53731
|
function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
53235
53732
|
const common = {};
|
|
@@ -53242,6 +53739,9 @@ function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
|
53242
53739
|
const { protocolVersion } = config;
|
|
53243
53740
|
if (ZCODE_PROTOCOL_VERSIONS.has(protocolVersion)) common.protocolVersion = protocolVersion;
|
|
53244
53741
|
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(", ")}.`);
|
|
53742
|
+
const { alwaysLoad } = config;
|
|
53743
|
+
if (typeof alwaysLoad === "boolean") common.alwaysLoad = alwaysLoad;
|
|
53744
|
+
else if (alwaysLoad !== void 0) logger?.warn(`ZCode MCP server "${serverName}": dropping alwaysLoad ${JSON.stringify(alwaysLoad)}, as it must be a boolean.`);
|
|
53245
53745
|
if (config.disabled === true) common[enableKey] = false;
|
|
53246
53746
|
return common;
|
|
53247
53747
|
}
|
|
@@ -53254,11 +53754,11 @@ function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
|
53254
53754
|
* absent.
|
|
53255
53755
|
*
|
|
53256
53756
|
* Every server also carries `timeoutMs` (from the canonical `timeout`, or an
|
|
53257
|
-
* explicit `timeoutMs`)
|
|
53258
|
-
* carry `cwd`, and remote servers an `oauth` built
|
|
53259
|
-
* `zcodeOauth`. That key is stripped by `RulesyncMcp.getMcpServers()`, so it
|
|
53757
|
+
* explicit `timeoutMs`), an allow-listed `protocolVersion` and a boolean
|
|
53758
|
+
* `alwaysLoad`; stdio servers carry `cwd`, and remote servers an `oauth` built
|
|
53759
|
+
* from the namespaced `zcodeOauth`. That key is stripped by `RulesyncMcp.getMcpServers()`, so it
|
|
53260
53760
|
* is read from the authored server through `rawServerOf`. ZCode's docs do not
|
|
53261
|
-
* name these keys, so they follow the 3.14.3
|
|
53761
|
+
* name these keys, so they follow the 3.14.3–3.16.2 runtime's per-server
|
|
53262
53762
|
* schema, which is strict: an unknown key makes ZCode skip the server, which
|
|
53263
53763
|
* is why the output stays a whitelist.
|
|
53264
53764
|
*
|
|
@@ -58395,6 +58895,11 @@ const CLAUDECODE_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
|
58395
58895
|
reason: "lets sandboxed commands send Apple Events, which removes code-execution isolation",
|
|
58396
58896
|
widens: isNotFalse
|
|
58397
58897
|
},
|
|
58898
|
+
{
|
|
58899
|
+
path: ["allowNestedUserNamespaces"],
|
|
58900
|
+
reason: "lets sandboxed Linux commands create user namespaces, which gets around the sandbox's file protections",
|
|
58901
|
+
widens: isNotFalse
|
|
58902
|
+
},
|
|
58398
58903
|
{
|
|
58399
58904
|
path: ["allowUnsandboxedCommands"],
|
|
58400
58905
|
reason: "controls whether Claude may retry a blocked command outside the sandbox",
|
|
@@ -68844,7 +69349,7 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
68844
69349
|
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
68845
69350
|
*
|
|
68846
69351
|
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
68847
|
-
* `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
69352
|
+
* `always-ask-cached`, `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
68848
69353
|
* import a recognized value is read back into the override.
|
|
68849
69354
|
*
|
|
68850
69355
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
@@ -71116,6 +71621,8 @@ function buildReasonixPermissionEntry(toolName, pattern) {
|
|
|
71116
71621
|
const REASONIX_TOOL_LABEL = "Reasonix";
|
|
71117
71622
|
/** The `[permissions]` key the override's `allowDynamicBash` writes and reads. */
|
|
71118
71623
|
const REASONIX_ALLOW_DYNAMIC_BASH_KEY = "allow_dynamic_bash";
|
|
71624
|
+
/** The `[permissions]` key the override's `askOnExternalOrigin` writes and reads. */
|
|
71625
|
+
const REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY = "ask_on_external_origin";
|
|
71119
71626
|
/**
|
|
71120
71627
|
* `[sandbox]` keys whose authored value loosens the enforcement layer beneath
|
|
71121
71628
|
* the permission policy: they take Bash out of its OS jail, open that jail to
|
|
@@ -71318,6 +71825,95 @@ function applyPermissionsMode({ permissions, authored, global, relativeFilePath,
|
|
|
71318
71825
|
reason: "lets every writer call that no rule matches run without a prompt"
|
|
71319
71826
|
}];
|
|
71320
71827
|
}
|
|
71828
|
+
/**
|
|
71829
|
+
* Writes the override's `askOnExternalOrigin` into `[permissions]`. Reasonix
|
|
71830
|
+
* honors the key only in the user config (`narrowPermissions` in
|
|
71831
|
+
* `project_scope.go`), so a project-scope generate skips it with a warning and
|
|
71832
|
+
* leaves whatever the file had. Turning it on only adds prompts, so that is
|
|
71833
|
+
* quiet; turning off a value the file had on is reported, since Auto and YOLO
|
|
71834
|
+
* then run calls that repeat text from a web page or tool result without asking.
|
|
71835
|
+
* As with `applyAllowDynamicBash`, only a literal `true` counts as quiet, so a
|
|
71836
|
+
* value a `--no-validate` run let through is never written in silence.
|
|
71837
|
+
*
|
|
71838
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.47.1/docs/GUIDE.md
|
|
71839
|
+
*/
|
|
71840
|
+
function applyAskOnExternalOrigin({ permissions, override, global, relativeFilePath, logger }) {
|
|
71841
|
+
const authored = override?.askOnExternalOrigin;
|
|
71842
|
+
if (authored === void 0) return [];
|
|
71843
|
+
if (!global) {
|
|
71844
|
+
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.`);
|
|
71845
|
+
return [];
|
|
71846
|
+
}
|
|
71847
|
+
const previous = permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY];
|
|
71848
|
+
permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY] = authored;
|
|
71849
|
+
if (authored === true || previous !== true && authored === false) return [];
|
|
71850
|
+
return [{
|
|
71851
|
+
label: `permissions.${REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY}`,
|
|
71852
|
+
reason: "lets Auto and YOLO run a call that repeats text from a web page or tool result without asking first"
|
|
71853
|
+
}];
|
|
71854
|
+
}
|
|
71855
|
+
/**
|
|
71856
|
+
* The `tools` patch entry — the `[tools]` table with the override's `disabled`
|
|
71857
|
+
* list laid over it, or nothing when the override does not author one — and the
|
|
71858
|
+
* trust rows it carries. Adding a name only removes a tool, and a project file
|
|
71859
|
+
* can only add to the user's list, so that stays quiet. The list is replaced
|
|
71860
|
+
* whole, though, so a name the file had turned off and the override leaves out
|
|
71861
|
+
* is turned back on, and that is reported, as are a `[tools]` or a `disabled`
|
|
71862
|
+
* the file holds in a shape that cannot be read. Only `disabled` is written:
|
|
71863
|
+
* the rest of `[tools]` (timeouts, search backends, ...) stays as it is.
|
|
71864
|
+
*/
|
|
71865
|
+
function applyToolsDisabled({ existing, override }) {
|
|
71866
|
+
const disabled = override?.tools?.disabled;
|
|
71867
|
+
if (disabled === void 0) return {
|
|
71868
|
+
patch: {},
|
|
71869
|
+
entries: []
|
|
71870
|
+
};
|
|
71871
|
+
const patch = { tools: {
|
|
71872
|
+
...asReasonixRecord(existing),
|
|
71873
|
+
disabled
|
|
71874
|
+
} };
|
|
71875
|
+
if (existing === void 0) return {
|
|
71876
|
+
patch,
|
|
71877
|
+
entries: []
|
|
71878
|
+
};
|
|
71879
|
+
if (!isRecord$1(existing)) return {
|
|
71880
|
+
patch,
|
|
71881
|
+
entries: [{
|
|
71882
|
+
label: "tools",
|
|
71883
|
+
reason: replacedUnreadableReason({
|
|
71884
|
+
shape: "object",
|
|
71885
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
71886
|
+
})
|
|
71887
|
+
}]
|
|
71888
|
+
};
|
|
71889
|
+
const before = existing.disabled;
|
|
71890
|
+
if (before === void 0) return {
|
|
71891
|
+
patch,
|
|
71892
|
+
entries: []
|
|
71893
|
+
};
|
|
71894
|
+
if (!Array.isArray(before)) return {
|
|
71895
|
+
patch,
|
|
71896
|
+
entries: [{
|
|
71897
|
+
label: "tools.disabled",
|
|
71898
|
+
reason: replacedUnreadableReason({
|
|
71899
|
+
shape: "list",
|
|
71900
|
+
toolLabel: REASONIX_TOOL_LABEL
|
|
71901
|
+
})
|
|
71902
|
+
}]
|
|
71903
|
+
};
|
|
71904
|
+
const reEnabled = uniq(before.filter((name) => !disabled.includes(name)));
|
|
71905
|
+
if (reEnabled.length === 0) return {
|
|
71906
|
+
patch,
|
|
71907
|
+
entries: []
|
|
71908
|
+
};
|
|
71909
|
+
return {
|
|
71910
|
+
patch,
|
|
71911
|
+
entries: [{
|
|
71912
|
+
label: "tools.disabled",
|
|
71913
|
+
reason: `turns back on ${reEnabled.map((name) => quoteValueForWarning(name)).join(", ")}, which the file had turned off`
|
|
71914
|
+
}]
|
|
71915
|
+
};
|
|
71916
|
+
}
|
|
71321
71917
|
function parseReasonixConfig(fileContent) {
|
|
71322
71918
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
71323
71919
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -71373,6 +71969,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71373
71969
|
});
|
|
71374
71970
|
}
|
|
71375
71971
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
71972
|
+
await warnIfReasonixNestedProjectConfigShadowed({
|
|
71973
|
+
outputRoot,
|
|
71974
|
+
global,
|
|
71975
|
+
logger
|
|
71976
|
+
});
|
|
71376
71977
|
const paths = this.getSettablePaths({ global });
|
|
71377
71978
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
71378
71979
|
const relativeFilePathForLog = toPosixPath(join(paths.relativeDirPath, paths.relativeFilePath));
|
|
@@ -71432,7 +72033,20 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71432
72033
|
relativeFilePath: relativeFilePathForLog,
|
|
71433
72034
|
logger
|
|
71434
72035
|
}));
|
|
72036
|
+
trustAffecting.push(...applyAskOnExternalOrigin({
|
|
72037
|
+
permissions: mergedPermissions,
|
|
72038
|
+
override,
|
|
72039
|
+
global,
|
|
72040
|
+
relativeFilePath: relativeFilePathForLog,
|
|
72041
|
+
logger
|
|
72042
|
+
}));
|
|
71435
72043
|
const patch = { permissions: mergedPermissions };
|
|
72044
|
+
const toolsDisabled = applyToolsDisabled({
|
|
72045
|
+
existing: parsed.tools,
|
|
72046
|
+
override
|
|
72047
|
+
});
|
|
72048
|
+
Object.assign(patch, toolsDisabled.patch);
|
|
72049
|
+
trustAffecting.push(...toolsDisabled.entries);
|
|
71436
72050
|
if (override?.sandbox !== void 0) {
|
|
71437
72051
|
const authoredSandbox = dropUserOnlySandboxKeys({
|
|
71438
72052
|
sandbox: override.sandbox,
|
|
@@ -71506,6 +72120,14 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
71506
72120
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
71507
72121
|
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
71508
72122
|
if (this.global && typeof permissions.mode === "string" && REASONIX_PERMISSION_MODES.has(permissions.mode)) reasonixOverride.mode = permissions.mode;
|
|
72123
|
+
const askOnExternalOrigin = permissions[REASONIX_ASK_ON_EXTERNAL_ORIGIN_KEY];
|
|
72124
|
+
if (this.global && typeof askOnExternalOrigin === "boolean") reasonixOverride.askOnExternalOrigin = askOnExternalOrigin;
|
|
72125
|
+
const toolsDisabled = asReasonixRecord(this.toml.tools).disabled;
|
|
72126
|
+
if (Array.isArray(toolsDisabled)) {
|
|
72127
|
+
const names = toReasonixStringArray(toolsDisabled);
|
|
72128
|
+
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.`);
|
|
72129
|
+
reasonixOverride.tools = { disabled: names };
|
|
72130
|
+
}
|
|
71509
72131
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
71510
72132
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
71511
72133
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -73087,7 +73709,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
73087
73709
|
const authoredProviderOptions = isPlainObject$1(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
73088
73710
|
const runtimeModeRefusesProviderOptions = authoredProviderOptions !== void 0 && runtime.active;
|
|
73089
73711
|
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.`);
|
|
73090
|
-
const overrideProviderOptions = runtimeModeRefusesProviderOptions ? void 0 :
|
|
73712
|
+
const overrideProviderOptions = runtimeModeRefusesProviderOptions ? void 0 : dropUntrustedProviderOptions({
|
|
73713
|
+
providerOptions: authoredProviderOptions,
|
|
73714
|
+
global,
|
|
73715
|
+
filePath,
|
|
73716
|
+
logger
|
|
73717
|
+
});
|
|
73091
73718
|
const authoredPolicies = pickSecurityPolicies(override, {
|
|
73092
73719
|
filePath,
|
|
73093
73720
|
logger,
|
|
@@ -73591,6 +74218,68 @@ function taktModeToRulesyncConfig(mode) {
|
|
|
73591
74218
|
default: return { permission: { bash: { [CATCH_ALL_PATTERN$1]: "deny" } } };
|
|
73592
74219
|
}
|
|
73593
74220
|
}
|
|
74221
|
+
const TAKT_BASE_URL_PROVIDERS = [
|
|
74222
|
+
"codex",
|
|
74223
|
+
"claude",
|
|
74224
|
+
"deepseek_harness"
|
|
74225
|
+
];
|
|
74226
|
+
const TAKT_DEEPSEEK_HARNESS_KEY = "deepseek_harness";
|
|
74227
|
+
/** Mirrors Takt's `isLoopbackBaseUrl`: localhost, *.localhost, ::1 or 127.x.x.x. */
|
|
74228
|
+
function isLoopbackBaseUrl(value) {
|
|
74229
|
+
let hostname;
|
|
74230
|
+
try {
|
|
74231
|
+
hostname = new URL(value).hostname.toLowerCase();
|
|
74232
|
+
} catch {
|
|
74233
|
+
return false;
|
|
74234
|
+
}
|
|
74235
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || hostname === "[::1]") return true;
|
|
74236
|
+
const octets = hostname.split(".");
|
|
74237
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d+$/.test(octet) && Number(octet) <= 255);
|
|
74238
|
+
}
|
|
74239
|
+
/** The keys of one `provider_options.<provider>` entry Takt rejects in config.yaml. */
|
|
74240
|
+
function untrustedProviderOptionKeys({ provider, options, global }) {
|
|
74241
|
+
const keys = [];
|
|
74242
|
+
if (provider === TAKT_DEEPSEEK_HARNESS_KEY) {
|
|
74243
|
+
if (options.reasoning_effort !== void 0) keys.push("reasoning_effort");
|
|
74244
|
+
const { plugins } = options;
|
|
74245
|
+
if (!global && plugins !== void 0 && (!Array.isArray(plugins) || plugins.length > 0)) keys.push("plugins");
|
|
74246
|
+
}
|
|
74247
|
+
const baseUrl = options.base_url;
|
|
74248
|
+
if (!global && TAKT_BASE_URL_PROVIDERS.includes(provider) && baseUrl !== void 0 && (typeof baseUrl !== "string" || !isLoopbackBaseUrl(baseUrl))) keys.push("base_url");
|
|
74249
|
+
return keys;
|
|
74250
|
+
}
|
|
74251
|
+
/**
|
|
74252
|
+
* Drop the `provider_options` entries Takt refuses to load from `config.yaml`
|
|
74253
|
+
* (it fails at startup with a `Configuration error`), warning for each:
|
|
74254
|
+
* - `deepseek_harness.reasoning_effort` in either scope — Takt accepts it only
|
|
74255
|
+
* in runtime profile options or `TAKT_PROVIDER_OPTIONS_DEEPSEEK_HARNESS_REASONING_EFFORT`.
|
|
74256
|
+
* - at project scope, a non-empty `deepseek_harness.plugins` (Takt 0.71.0+,
|
|
74257
|
+
* global config only) and a non-loopback `{codex,claude,deepseek_harness}.base_url`.
|
|
74258
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/providerOptions.ts
|
|
74259
|
+
* https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
74260
|
+
*/
|
|
74261
|
+
function dropUntrustedProviderOptions({ providerOptions, global, filePath, logger }) {
|
|
74262
|
+
if (providerOptions === void 0) return;
|
|
74263
|
+
const dropped = [];
|
|
74264
|
+
const result = {};
|
|
74265
|
+
for (const [provider, options] of Object.entries(providerOptions)) {
|
|
74266
|
+
if (!isPlainObject$1(options)) {
|
|
74267
|
+
result[provider] = options;
|
|
74268
|
+
continue;
|
|
74269
|
+
}
|
|
74270
|
+
const untrusted = untrustedProviderOptionKeys({
|
|
74271
|
+
provider,
|
|
74272
|
+
options,
|
|
74273
|
+
global
|
|
74274
|
+
});
|
|
74275
|
+
const kept = Object.fromEntries(Object.entries(options).filter(([key]) => !untrusted.includes(key)));
|
|
74276
|
+
dropped.push(...untrusted.map((key) => `${TAKT_PROVIDER_OPTIONS_KEY}.${provider}.${key}`));
|
|
74277
|
+
if (Object.keys(kept).length > 0 || Object.keys(options).length === 0) result[provider] = kept;
|
|
74278
|
+
}
|
|
74279
|
+
if (dropped.length === 0) return providerOptions;
|
|
74280
|
+
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.`);
|
|
74281
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
74282
|
+
}
|
|
73594
74283
|
/**
|
|
73595
74284
|
* Lift Takt's security-policy keys out of a source object, keeping only the
|
|
73596
74285
|
* shapes Takt itself accepts (a boolean, or a table of booleans). Used in both
|
|
@@ -85920,6 +86609,19 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
85920
86609
|
//#endregion
|
|
85921
86610
|
//#region src/features/skills/xum-skill.ts
|
|
85922
86611
|
/**
|
|
86612
|
+
* The Agent Skills standard keys the `agentsskills` section carries. On import
|
|
86613
|
+
* they go back there, exactly as for the `agentsskills` target, and every other
|
|
86614
|
+
* key besides `name` / `description` goes into the `xum` section.
|
|
86615
|
+
*/
|
|
86616
|
+
const AGENT_SKILLS_STANDARD_KEYS = /* @__PURE__ */ new Set([
|
|
86617
|
+
"name",
|
|
86618
|
+
"description",
|
|
86619
|
+
"license",
|
|
86620
|
+
"compatibility",
|
|
86621
|
+
"metadata",
|
|
86622
|
+
"allowed-tools"
|
|
86623
|
+
]);
|
|
86624
|
+
/**
|
|
85923
86625
|
* Xum skills follow the Agent Skills layout (`<name>/SKILL.md` with the
|
|
85924
86626
|
* required `name` and `description` frontmatter; the directory name is
|
|
85925
86627
|
* lowercase letters, digits and single hyphens up to 64 characters, and
|
|
@@ -85927,12 +86629,82 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
85927
86629
|
* Xum's locations: `.xum/skills/` (project) and `~/.xum/skills/` (user).
|
|
85928
86630
|
* Within one directory `.xum/skills` wins over `.agents/skills`.
|
|
85929
86631
|
*
|
|
86632
|
+
* On top of the standard fields, Xum reads `disable-model-invocation` (and its
|
|
86633
|
+
* own `advertise: false` alias), `user-invocable`, `argument-hint`,
|
|
86634
|
+
* `when_to_use` / `when-to-use` and `paths` (globs that make the skill
|
|
86635
|
+
* path-scoped). Those come from the `xum` section of the rulesync skill and are
|
|
86636
|
+
* written verbatim; the two invocation gates fall back to the root-level
|
|
86637
|
+
* rulesync values.
|
|
86638
|
+
*
|
|
85930
86639
|
* @see https://xum.cdr.dev/agents/agent-skills
|
|
85931
86640
|
*/
|
|
85932
|
-
var XumSkill = class extends AgentsSkillsSkill {
|
|
86641
|
+
var XumSkill = class XumSkill extends AgentsSkillsSkill {
|
|
85933
86642
|
static getSettablePaths(_options = {}) {
|
|
85934
86643
|
return { relativeDirPath: XUM_SKILLS_DIR_PATH };
|
|
85935
86644
|
}
|
|
86645
|
+
toRulesyncSkill() {
|
|
86646
|
+
const base = super.toRulesyncSkill();
|
|
86647
|
+
const xumSection = Object.fromEntries(Object.entries(this.getFrontmatter()).filter(([key]) => !AGENT_SKILLS_STANDARD_KEYS.has(key)));
|
|
86648
|
+
if (Object.keys(xumSection).length === 0) return base;
|
|
86649
|
+
const rulesyncFrontmatter = {
|
|
86650
|
+
...base.getFrontmatter(),
|
|
86651
|
+
xum: xumSection
|
|
86652
|
+
};
|
|
86653
|
+
return new RulesyncSkill({
|
|
86654
|
+
outputRoot: base.getOutputRoot(),
|
|
86655
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
86656
|
+
dirName: base.getDirName(),
|
|
86657
|
+
frontmatter: rulesyncFrontmatter,
|
|
86658
|
+
body: base.getBody(),
|
|
86659
|
+
otherFiles: base.getOtherFiles(),
|
|
86660
|
+
validate: true,
|
|
86661
|
+
global: this.global
|
|
86662
|
+
});
|
|
86663
|
+
}
|
|
86664
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false, logger }) {
|
|
86665
|
+
const settablePaths = XumSkill.getSettablePaths({ global });
|
|
86666
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
86667
|
+
const dirName = rulesyncSkill.getDirName();
|
|
86668
|
+
const xumSection = {};
|
|
86669
|
+
const ignoredKeys = [];
|
|
86670
|
+
for (const [key, value] of Object.entries(rulesyncFrontmatter.xum ?? {})) if (AGENT_SKILLS_STANDARD_KEYS.has(key)) ignoredKeys.push(key);
|
|
86671
|
+
else xumSection[key] = value;
|
|
86672
|
+
if (ignoredKeys.length > 0) warnWithFallback(logger, `${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.`);
|
|
86673
|
+
const userInvocable = resolveUserInvocable({
|
|
86674
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
86675
|
+
section: xumSection
|
|
86676
|
+
});
|
|
86677
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
86678
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
86679
|
+
section: xumSection
|
|
86680
|
+
});
|
|
86681
|
+
const frontmatter = {
|
|
86682
|
+
name: rulesyncFrontmatter.name,
|
|
86683
|
+
description: rulesyncFrontmatter.description,
|
|
86684
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter)),
|
|
86685
|
+
...xumSection,
|
|
86686
|
+
...userInvocable !== void 0 && { "user-invocable": userInvocable },
|
|
86687
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
|
|
86688
|
+
};
|
|
86689
|
+
AgentsSkillsSkill.reportSpecViolations({
|
|
86690
|
+
outputRoot,
|
|
86691
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
86692
|
+
dirName,
|
|
86693
|
+
frontmatter,
|
|
86694
|
+
sourceAllowedTools: rulesyncFrontmatter.agentsskills?.["allowed-tools"],
|
|
86695
|
+
logger
|
|
86696
|
+
});
|
|
86697
|
+
return new XumSkill({
|
|
86698
|
+
outputRoot,
|
|
86699
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
86700
|
+
dirName,
|
|
86701
|
+
frontmatter,
|
|
86702
|
+
body: rulesyncSkill.getBody(),
|
|
86703
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
86704
|
+
validate,
|
|
86705
|
+
global
|
|
86706
|
+
});
|
|
86707
|
+
}
|
|
85936
86708
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
85937
86709
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
85938
86710
|
return targets.includes("*") || targets.includes("xum");
|
|
@@ -92088,6 +92860,15 @@ const KiroCliSubagentJsonSchema = z.looseObject({
|
|
|
92088
92860
|
allowedTools: z.optional(z.nullable(z.array(z.string()))),
|
|
92089
92861
|
includeMcpJson: z.optional(z.nullable(z.boolean()))
|
|
92090
92862
|
});
|
|
92863
|
+
/**
|
|
92864
|
+
* The name Kiro gives an agent file: its path relative to the agents directory
|
|
92865
|
+
* without the extension, so a nested `team/planner.md` is `team/planner`.
|
|
92866
|
+
* https://kiro.dev/docs/custom-agents/
|
|
92867
|
+
*/
|
|
92868
|
+
function kiroAgentNameFromPath(relativeFilePath) {
|
|
92869
|
+
const posixPath = toPosixPath(relativeFilePath);
|
|
92870
|
+
return posixPath.slice(0, posixPath.length - extname(posixPath).length);
|
|
92871
|
+
}
|
|
92091
92872
|
var KiroSubagent = class extends ToolSubagent {
|
|
92092
92873
|
body;
|
|
92093
92874
|
constructor({ body, ...rest }) {
|
|
@@ -92123,7 +92904,7 @@ var KiroSubagent = class extends ToolSubagent {
|
|
|
92123
92904
|
outputRoot: ".",
|
|
92124
92905
|
frontmatter: {
|
|
92125
92906
|
targets: [toolTarget],
|
|
92126
|
-
name: name ??
|
|
92907
|
+
name: name ?? kiroAgentNameFromPath(this.getRelativeFilePath()),
|
|
92127
92908
|
description: description ?? void 0,
|
|
92128
92909
|
...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
|
|
92129
92910
|
},
|
|
@@ -92273,7 +93054,7 @@ var KiroIdeSubagent = class KiroIdeSubagent extends ToolSubagent {
|
|
|
92273
93054
|
outputRoot: ".",
|
|
92274
93055
|
frontmatter: {
|
|
92275
93056
|
targets: ["*"],
|
|
92276
|
-
name: name ?? this.getRelativeFilePath()
|
|
93057
|
+
name: name ?? kiroAgentNameFromPath(this.getRelativeFilePath()),
|
|
92277
93058
|
description: description ?? "",
|
|
92278
93059
|
...Object.keys(kiroIdeSection).length > 0 && { "kiro-ide": kiroIdeSection }
|
|
92279
93060
|
},
|
|
@@ -94891,7 +95672,8 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
94891
95672
|
supportsProject: true,
|
|
94892
95673
|
supportsSimulated: false,
|
|
94893
95674
|
supportsGlobal: true,
|
|
94894
|
-
filePattern: "
|
|
95675
|
+
filePattern: "**/*.json",
|
|
95676
|
+
supportsNestedPaths: true
|
|
94895
95677
|
}
|
|
94896
95678
|
}],
|
|
94897
95679
|
["kiro-ide", {
|
|
@@ -94900,7 +95682,8 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
94900
95682
|
supportsProject: true,
|
|
94901
95683
|
supportsSimulated: false,
|
|
94902
95684
|
supportsGlobal: true,
|
|
94903
|
-
filePattern: "
|
|
95685
|
+
filePattern: "**/*.md",
|
|
95686
|
+
supportsNestedPaths: true
|
|
94904
95687
|
}
|
|
94905
95688
|
}],
|
|
94906
95689
|
["lettacode", {
|
|
@@ -108890,6 +109673,6 @@ async function importChecksCore(params) {
|
|
|
108890
109673
|
return writtenCount;
|
|
108891
109674
|
}
|
|
108892
109675
|
//#endregion
|
|
108893
|
-
export { RulesyncHooks as $,
|
|
109676
|
+
export { RulesyncHooks as $, stripHiddenCharacters as $n, readFileContentOrNull as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_MCP_RELATIVE_FILE_PATH as An, PathOutsideRootError as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_USER_CONFIG_DIR_NAME as Bn, fileExists as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_RELATIVE_DIR_PATH as Fn, assertWritablePathInsideRoot as Ft, RulesyncSubagentFrontmatterSchema as G, DEPRECATED_FEATURE_REPLACEMENTS as Gn, isFileSystemError as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, parseCommaSeparatedList as Hn, getFileSizeAndExecutableBit as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_RULES_RELATIVE_DIR_PATH as In, checkPathTraversal as It, RulesyncRule as J, hasDeceptiveHiddenCharacters as Jn, listFilePathsRecursively as Jt, RulesyncSkill as K, formatError as Kn, isSymlink as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Ln, createTempDirectory as Lt, CODEXCLI_DIR as M, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Mn, assertDirectoryIfExists as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Nn, assertPathResolvesInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_IGNORE_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_PERMISSIONS_SCHEMA_URL as Pn, assertTreeContainsNoSymlinks as Pt, RulesyncIgnore as Q, stripControlCharactersKeepingLineFeeds as Qn, readFileContent as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Rn, directoryExists as Rt, COMMANDCODE_DIR as S, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, ALL_FEATURES as Un, getHomeDirectory as Ut, AUGMENTCODE_DIR as V, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Vn, findFilesByGlobs as Vt, RulesyncSubagent as W, ALL_FEATURES_WITH_WILDCARD as Wn, isFileNotFoundError as Wt, RulesyncPermissions as X, quoteForLog as Xn, pathEscapesRoot as Xt, RulesyncRuleFrontmatterSchema as Y, hasEnclosingMarkOutsideKeycap as Yn, listSubdirectoryNames as Yt, RulesyncMcp as Z, stripControlCharacters as Zn, posixRelativePathEscapesRoot as Zt, CRUSH_LOCAL_RULE_FILE_NAME as _, CURATED_RULES_FEATURE_SUBDIR as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, resolvePath as an, RulesyncCheck as at, LETTACODE_DIR as b, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, splitPathSegments as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, writeFileContent as dn, ConfigResolver as dt, removeDirectory as en, getRulesyncSourceCandidates as et, displayWidthOf as f, mapWithConcurrency as fn, mergeInputRootConfigs as ft, HooksProcessor as g, ToolTargetSchema as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, PACKAGING_TOOL_TARGETS as hn, ConfigFileSchema as ht, inspectInputRoots as i, removeTempDirectory as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_MCP_SCHEMA_URL as jn, applyFileMode as jt, caseFoldIdentity as k, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as kn, ErrorCodes as kt, SubagentsProcessor as l, toPosixPath as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, ALL_TOOL_TARGETS_WITH_WILDCARD as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeFile as nn, parseJsonc as nt, convertFromTool as o, resolvedRelativePath as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, ALL_TOOL_TARGETS as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, truncateText as qn, listDirectoryEntryNames as qt, generate as r, removeFileStrict as rn, RulesyncCommand as rt, isPackagingToolTarget as s, runWithDirectoryRollback as sn, stringifyFrontmatter as st, importFromTool as t, removeDirectoryStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, writeFileBuffer as un, SKILL_FILE_NAME as ut, CommandsProcessor as v, FEATURE_SOURCE_TREE_ENTRIES as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_CONFIG_SCHEMA_URL as wn, warnOnConflictingFlags as wt, LETTACODE_SETTINGS_LOCAL_FILE_NAME as x, RULESYNC_CHECKS_RELATIVE_DIR_PATH as xn, JsonLogger as xt, REASONIX_LOCAL_RULE_FILE_NAME as y, MAX_FILE_SIZE as yn, findControlCharacter as yt, QWENCODE_DIR as z, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as zn, ensureDir as zt };
|
|
108894
109677
|
|
|
108895
|
-
//# sourceMappingURL=import-
|
|
109678
|
+
//# sourceMappingURL=import-Chn7Nqnm.js.map
|