rulesync 43.3.0 → 43.4.1
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/README.md +1 -1
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CedO1sFP.cjs → import-B3x-pkWH.cjs} +376 -103
- package/dist/{import-BQNLyW5s.js → import-BKyt4qe5.js} +377 -104
- package/dist/{import-BQNLyW5s.js.map → import-BKyt4qe5.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +14 -14
|
@@ -5635,11 +5635,28 @@ const POOL_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_POOL_EVENT_N
|
|
|
5635
5635
|
/**
|
|
5636
5636
|
* Hook events supported by Crush.
|
|
5637
5637
|
*
|
|
5638
|
-
* Crush
|
|
5639
|
-
*
|
|
5640
|
-
*
|
|
5638
|
+
* Crush v0.99.0 fires 13 events under its `hooks` config block (Claude
|
|
5639
|
+
* Code-compatible payload; `matcher` is a regex on the tool name and is only
|
|
5640
|
+
* accepted on tool events). Twelve of them have a canonical counterpart;
|
|
5641
|
+
* `PermissionResult` has none and is authored under the `crush.hooks`
|
|
5642
|
+
* override.
|
|
5643
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
5644
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/hooks/events.go
|
|
5641
5645
|
*/
|
|
5642
|
-
const CRUSH_HOOK_EVENTS = [
|
|
5646
|
+
const CRUSH_HOOK_EVENTS = [
|
|
5647
|
+
"preToolUse",
|
|
5648
|
+
"postToolUse",
|
|
5649
|
+
"postToolUseFailure",
|
|
5650
|
+
"permissionRequest",
|
|
5651
|
+
"sessionStart",
|
|
5652
|
+
"sessionEnd",
|
|
5653
|
+
"beforeSubmitPrompt",
|
|
5654
|
+
"stop",
|
|
5655
|
+
"stopFailure",
|
|
5656
|
+
"stopCancelled",
|
|
5657
|
+
"preCompact",
|
|
5658
|
+
"postCompact"
|
|
5659
|
+
];
|
|
5643
5660
|
/**
|
|
5644
5661
|
* Hook events supported by JetBrains Junie CLI.
|
|
5645
5662
|
*
|
|
@@ -5904,9 +5921,10 @@ const ZCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_ZCODE_EVENT
|
|
|
5904
5921
|
* Bob reads `hooks` from `.bob/settings.json` (project) and
|
|
5905
5922
|
* `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
|
|
5906
5923
|
* seven events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
|
|
5907
|
-
* PreCompact, PostCompact (
|
|
5908
|
-
* are `command` or `
|
|
5909
|
-
*
|
|
5924
|
+
* PreCompact, PostCompact and Stop (Bob IDE and Bob Shell list the same
|
|
5925
|
+
* seven). Handlers are `command` or `http` hooks. `matcher` is a regex matched
|
|
5926
|
+
* against the tool name on PreToolUse / PostToolUse, `source` on SessionStart
|
|
5927
|
+
* and `trigger` on PreCompact / PostCompact; the other events ignore it.
|
|
5910
5928
|
*
|
|
5911
5929
|
* @see https://bob.ibm.com/docs/shell/configuration/lifecycle-hooks
|
|
5912
5930
|
* @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
|
|
@@ -7088,10 +7106,38 @@ const CANONICAL_TO_CODEWHALE_EVENT_NAMES = {
|
|
|
7088
7106
|
const CODEWHALE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CODEWHALE_EVENT_NAMES);
|
|
7089
7107
|
/**
|
|
7090
7108
|
* Canonical -> Crush event names. Crush spells its events the Claude Code
|
|
7091
|
-
* way (`PreToolUse`)
|
|
7092
|
-
*
|
|
7109
|
+
* way (`PreToolUse`), but names the turn events `TurnStart` (Claude Code's
|
|
7110
|
+
* `UserPromptSubmit`) and `TurnEnd` (Claude Code's `Stop`), and the
|
|
7111
|
+
* user-cancelled turn `Interrupt`.
|
|
7112
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
7093
7113
|
*/
|
|
7094
|
-
const CANONICAL_TO_CRUSH_EVENT_NAMES = {
|
|
7114
|
+
const CANONICAL_TO_CRUSH_EVENT_NAMES = {
|
|
7115
|
+
preToolUse: "PreToolUse",
|
|
7116
|
+
postToolUse: "PostToolUse",
|
|
7117
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7118
|
+
permissionRequest: "PermissionRequest",
|
|
7119
|
+
sessionStart: "SessionStart",
|
|
7120
|
+
sessionEnd: "SessionEnd",
|
|
7121
|
+
beforeSubmitPrompt: "TurnStart",
|
|
7122
|
+
stop: "TurnEnd",
|
|
7123
|
+
stopFailure: "StopFailure",
|
|
7124
|
+
stopCancelled: "Interrupt",
|
|
7125
|
+
preCompact: "PreCompact",
|
|
7126
|
+
postCompact: "PostCompact"
|
|
7127
|
+
};
|
|
7128
|
+
/**
|
|
7129
|
+
* Crush events that carry a tool call, the only ones whose hooks may set a
|
|
7130
|
+
* `matcher` (Crush's `EventHasTool`). A matcher on any other event makes
|
|
7131
|
+
* Crush reject the whole config at load time.
|
|
7132
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
7133
|
+
*/
|
|
7134
|
+
const CRUSH_TOOL_EVENTS = [
|
|
7135
|
+
"PreToolUse",
|
|
7136
|
+
"PostToolUse",
|
|
7137
|
+
"PostToolUseFailure",
|
|
7138
|
+
"PermissionRequest",
|
|
7139
|
+
"PermissionResult"
|
|
7140
|
+
];
|
|
7095
7141
|
const CRUSH_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CRUSH_EVENT_NAMES);
|
|
7096
7142
|
/**
|
|
7097
7143
|
* Map canonical camelCase event names to Qwen Code PascalCase.
|
|
@@ -7635,14 +7681,18 @@ const toolScopedMcpSchema = zod_mini.z.looseObject({ mcpServers: zod_mini.z.opti
|
|
|
7635
7681
|
* server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
|
|
7636
7682
|
* in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
|
|
7637
7683
|
* experimental `tool-select` flag is on, and is captured when a server connects
|
|
7638
|
-
* (MoonshotAI/kimi-code#4210).
|
|
7684
|
+
* (MoonshotAI/kimi-code#4210). `reconnectInitialDelayMs` / `reconnectMaxDelayMs`
|
|
7685
|
+
* (Kimi Code 2.4.0) set the first delay and the cap of the automatic reconnect
|
|
7686
|
+
* backoff for workspace-level servers (MoonshotAI/kimi-code#4283).
|
|
7639
7687
|
*
|
|
7640
7688
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
7641
7689
|
*/
|
|
7642
7690
|
const kimiCodeScopedMcpSchema = zod_mini.z.extend(toolScopedMcpSchema, {
|
|
7643
7691
|
startupTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7644
7692
|
toolTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7645
|
-
defaultDeferred: zod_mini.z.optional(zod_mini.z.boolean())
|
|
7693
|
+
defaultDeferred: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7694
|
+
reconnectInitialDelayMs: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative())),
|
|
7695
|
+
reconnectMaxDelayMs: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative()))
|
|
7646
7696
|
});
|
|
7647
7697
|
/**
|
|
7648
7698
|
* Qwen Code's tool-scoped block also carries the server-level allow/deny lists
|
|
@@ -11468,6 +11518,7 @@ const piSkillSectionSchema = zod_mini.z.looseObject({
|
|
|
11468
11518
|
});
|
|
11469
11519
|
const antigravitySkillSectionSchema = zod_mini.z.looseObject({
|
|
11470
11520
|
"disable-slash-command": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11521
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11471
11522
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
11472
11523
|
});
|
|
11473
11524
|
const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
@@ -16227,8 +16278,9 @@ var ToolSkill = class extends AiDir {
|
|
|
16227
16278
|
* {@link AntigravitySharedSkill.getToolTarget}.
|
|
16228
16279
|
*
|
|
16229
16280
|
* Frontmatter beyond `name`/`description` (e.g. `disable-slash-command`,
|
|
16230
|
-
* `metadata`) comes from the rulesync section named
|
|
16231
|
-
* root-level `user-invocable` /
|
|
16281
|
+
* `disable-model-invocation`, `metadata`) comes from the rulesync section named
|
|
16282
|
+
* after that target, with the root-level `user-invocable` /
|
|
16283
|
+
* `disable-model-invocation` / `metadata` as defaults. On the shared project
|
|
16232
16284
|
* `.agents/skills/` tree both targets merge the `antigravity-ide` and
|
|
16233
16285
|
* `antigravity-cli` sections so their output never diverges.
|
|
16234
16286
|
*/
|
|
@@ -16301,6 +16353,8 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16301
16353
|
const { name: _name, description: _description, ...sectionFields } = section;
|
|
16302
16354
|
const sectionDisableSlashCommand = sectionFields["disable-slash-command"];
|
|
16303
16355
|
const disableSlashCommand = typeof sectionDisableSlashCommand === "boolean" ? sectionDisableSlashCommand : rulesyncFrontmatter["user-invocable"] === false ? true : void 0;
|
|
16356
|
+
const sectionDisableModelInvocation = sectionFields["disable-model-invocation"];
|
|
16357
|
+
const disableModelInvocation = typeof sectionDisableModelInvocation === "boolean" ? sectionDisableModelInvocation : rulesyncFrontmatter["disable-model-invocation"];
|
|
16304
16358
|
const metadata = resolveMetadata({
|
|
16305
16359
|
rootFrontmatter: rulesyncFrontmatter,
|
|
16306
16360
|
section: sectionFields
|
|
@@ -16310,6 +16364,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16310
16364
|
name: rulesyncFrontmatter.name,
|
|
16311
16365
|
description: rulesyncFrontmatter.description,
|
|
16312
16366
|
...disableSlashCommand !== void 0 && { "disable-slash-command": disableSlashCommand },
|
|
16367
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
|
|
16313
16368
|
...metadata !== void 0 && { metadata }
|
|
16314
16369
|
};
|
|
16315
16370
|
return new this({
|
|
@@ -31738,16 +31793,17 @@ const VibeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
31738
31793
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
31739
31794
|
});
|
|
31740
31795
|
/**
|
|
31741
|
-
* Skill names Vibe's built-in skills occupy. `SkillManager`
|
|
31742
|
-
* reserved: a project or user skill whose frontmatter `name`
|
|
31743
|
-
* skipped at load time with only a debug log ("Skipping skill
|
|
31744
|
-
* because builtin skill names are reserved"), so a generated
|
|
31745
|
-
* name is silently never offered.
|
|
31746
|
-
*
|
|
31747
|
-
*
|
|
31748
|
-
*
|
|
31749
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31750
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31796
|
+
* Skill names Vibe's built-in skills occupy. Before v3.0.4, `SkillManager`
|
|
31797
|
+
* treats them as reserved: a project or user skill whose frontmatter `name`
|
|
31798
|
+
* matches one is skipped at load time with only a debug log ("Skipping skill
|
|
31799
|
+
* '<name>' ... because builtin skill names are reserved"), so a generated
|
|
31800
|
+
* skill by that name is silently never offered. v3.0.4 removed
|
|
31801
|
+
* `_reserved_builtins` and that skip ("A user skill named vibe or
|
|
31802
|
+
* skill-creator is no longer silently skipped"), so the warning below is
|
|
31803
|
+
* scoped to older Vibe releases.
|
|
31804
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/skills/manager.py
|
|
31805
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/vibe/core/skills/manager.py
|
|
31806
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
31751
31807
|
*/
|
|
31752
31808
|
const VIBE_RESERVED_SKILL_NAMES = /* @__PURE__ */ new Set(["vibe", "skill-creator"]);
|
|
31753
31809
|
/**
|
|
@@ -31790,11 +31846,12 @@ function buildVibeFrontmatter(rulesyncFrontmatter) {
|
|
|
31790
31846
|
/**
|
|
31791
31847
|
* The skill is still generated under the reserved name — the name is the
|
|
31792
31848
|
* user's to change, and a rename here would make it diverge from the other
|
|
31793
|
-
* targets — but Vibe would drop it without a visible word, so
|
|
31849
|
+
* targets — but Vibe before v3.0.4 would drop it without a visible word, so
|
|
31850
|
+
* say so now.
|
|
31794
31851
|
*/
|
|
31795
31852
|
function warnAboutReservedSkillName({ name, logger }) {
|
|
31796
31853
|
if (!VIBE_RESERVED_SKILL_NAMES.has(name)) return;
|
|
31797
|
-
logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)}
|
|
31854
|
+
logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)} matches a Vibe built-in skill. Vibe before v3.0.4 skips a project or user skill by that name when it loads skills; upgrade Vibe to v3.0.4 or later, or rename the skill, for it to be available.`);
|
|
31798
31855
|
}
|
|
31799
31856
|
var VibeSkill = class VibeSkill extends ToolSkill {
|
|
31800
31857
|
constructor({ outputRoot = process.cwd(), relativeDirPath = VibeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -42762,20 +42819,35 @@ const OPENCODE_KNOWN_SERVER_KEYS = /* @__PURE__ */ new Set([
|
|
|
42762
42819
|
"disabledTools"
|
|
42763
42820
|
]);
|
|
42764
42821
|
function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
42822
|
+
const serverNames = Object.keys(opencodeMcp);
|
|
42765
42823
|
return {
|
|
42766
|
-
...orphanMcpToolFiltersToRulesync(opencodeMcp, tools)
|
|
42824
|
+
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42825
|
+
key,
|
|
42826
|
+
serverNames
|
|
42827
|
+
})))),
|
|
42767
42828
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42768
42829
|
};
|
|
42769
42830
|
}
|
|
42831
|
+
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42832
|
+
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42833
|
+
return findOpencodeMcpToolKeyOwner({
|
|
42834
|
+
key,
|
|
42835
|
+
serverNames,
|
|
42836
|
+
builtinKeys: /* @__PURE__ */ new Set(),
|
|
42837
|
+
acceptRawPrefix: true
|
|
42838
|
+
});
|
|
42839
|
+
}
|
|
42770
42840
|
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
42771
|
-
function splitOpencodeServerTools(serverName, tools) {
|
|
42841
|
+
function splitOpencodeServerTools(serverName, serverNames, tools) {
|
|
42772
42842
|
const enabledTools = [];
|
|
42773
42843
|
const disabledTools = [];
|
|
42774
|
-
const prefix = `${serverName}_`;
|
|
42775
42844
|
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
42776
|
-
|
|
42777
|
-
|
|
42778
|
-
|
|
42845
|
+
const owner = findOpencodeToolOwner({
|
|
42846
|
+
key: toolName,
|
|
42847
|
+
serverNames
|
|
42848
|
+
});
|
|
42849
|
+
if (owner?.serverName !== serverName) continue;
|
|
42850
|
+
(enabled ? enabledTools : disabledTools).push(owner.toolName);
|
|
42779
42851
|
}
|
|
42780
42852
|
return {
|
|
42781
42853
|
enabledTools,
|
|
@@ -42785,7 +42857,7 @@ function splitOpencodeServerTools(serverName, tools) {
|
|
|
42785
42857
|
function convertOpencodeServers(opencodeMcp, tools, remoteType) {
|
|
42786
42858
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
42787
42859
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
42788
|
-
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
42860
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, Object.keys(opencodeMcp), tools);
|
|
42789
42861
|
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
42790
42862
|
...extraFields,
|
|
42791
42863
|
disabled: serverConfig.enabled === false,
|
|
@@ -42921,8 +42993,11 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
42921
42993
|
return {
|
|
42922
42994
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
42923
42995
|
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
|
|
42924
|
-
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools)
|
|
42925
|
-
|
|
42996
|
+
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) {
|
|
42997
|
+
const key = opencodeMcpToolPermissionKey(serverName, tool);
|
|
42998
|
+
tools[key] = tools[key] !== false;
|
|
42999
|
+
}
|
|
43000
|
+
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = false;
|
|
42926
43001
|
return converted === null ? null : [serverName, converted];
|
|
42927
43002
|
}).filter((entry) => entry !== null)),
|
|
42928
43003
|
tools
|
|
@@ -55950,12 +56025,23 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
|
|
|
55950
56025
|
* into separate groups, so a restricting field is never inherited by a hook
|
|
55951
56026
|
* that did not ask for it.
|
|
55952
56027
|
*/
|
|
55953
|
-
function groupDefinitionsByMatcher({ definitions, converterConfig }) {
|
|
56028
|
+
function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, warn }) {
|
|
55954
56029
|
const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
|
|
55955
56030
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
56031
|
+
const translatedMatchers = /* @__PURE__ */ new Map();
|
|
55956
56032
|
for (const def of definitions) {
|
|
55957
56033
|
const rawMatcher = def.matcher ?? "";
|
|
55958
|
-
const
|
|
56034
|
+
const normalizedMatcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
|
|
56035
|
+
const translation = converterConfig.matcherTranslation;
|
|
56036
|
+
let matcher = normalizedMatcher;
|
|
56037
|
+
if (translation && normalizedMatcher && !converterConfig.noMatcherEvents?.has(eventName)) {
|
|
56038
|
+
matcher = translatedMatchers.get(normalizedMatcher) ?? translation.toTool({
|
|
56039
|
+
matcher: normalizedMatcher,
|
|
56040
|
+
eventName,
|
|
56041
|
+
warn
|
|
56042
|
+
});
|
|
56043
|
+
translatedMatchers.set(normalizedMatcher, matcher);
|
|
56044
|
+
}
|
|
55959
56045
|
const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
|
|
55960
56046
|
const value = def[canonical];
|
|
55961
56047
|
return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
@@ -56465,7 +56551,7 @@ function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
|
56465
56551
|
converterConfig,
|
|
56466
56552
|
warn
|
|
56467
56553
|
}),
|
|
56468
|
-
type:
|
|
56554
|
+
type: hookType,
|
|
56469
56555
|
...command !== void 0 && command !== null && { command },
|
|
56470
56556
|
...def.timeout !== void 0 && def.timeout !== null && { timeout: emitTimeout({
|
|
56471
56557
|
timeout: def.timeout,
|
|
@@ -56515,7 +56601,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
56515
56601
|
}) ?? eventName;
|
|
56516
56602
|
const byMatcher = groupDefinitionsByMatcher({
|
|
56517
56603
|
definitions,
|
|
56518
|
-
|
|
56604
|
+
eventName,
|
|
56605
|
+
converterConfig,
|
|
56606
|
+
warn
|
|
56519
56607
|
});
|
|
56520
56608
|
const entries = [];
|
|
56521
56609
|
const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
|
|
@@ -56594,16 +56682,19 @@ function isImportedHookType(value) {
|
|
|
56594
56682
|
return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
|
|
56595
56683
|
}
|
|
56596
56684
|
/**
|
|
56597
|
-
* The canonical hook type a tool-side `type` value denotes: the
|
|
56598
|
-
*
|
|
56599
|
-
* one, otherwise the value itself when it is a canonical type, and
|
|
56600
|
-
* for anything else
|
|
56685
|
+
* The canonical hook type a tool-side `type` value denotes: the canonical type
|
|
56686
|
+
* an {@link ToolHooksConverterConfig.importedHookTypeAliases} entry maps it to
|
|
56687
|
+
* when it has one, otherwise the value itself when it is a canonical type, and
|
|
56688
|
+
* `command` for anything else.
|
|
56601
56689
|
*/
|
|
56602
56690
|
function resolveImportedHookType({ value, converterConfig }) {
|
|
56603
|
-
const
|
|
56604
|
-
if (
|
|
56605
|
-
|
|
56606
|
-
|
|
56691
|
+
const aliases = converterConfig.importedHookTypeAliases;
|
|
56692
|
+
if (aliases !== void 0 && typeof value === "string") {
|
|
56693
|
+
const aliased = lookupOwn({
|
|
56694
|
+
record: aliases,
|
|
56695
|
+
key: value
|
|
56696
|
+
});
|
|
56697
|
+
if (aliased !== void 0) return aliased;
|
|
56607
56698
|
}
|
|
56608
56699
|
return isImportedHookType(value) ? value : "command";
|
|
56609
56700
|
}
|
|
@@ -56831,7 +56922,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
|
|
|
56831
56922
|
canonical: "description",
|
|
56832
56923
|
warn
|
|
56833
56924
|
});
|
|
56834
|
-
const matcher = rawEntry.matcher;
|
|
56925
|
+
const matcher = typeof rawEntry.matcher === "string" && converterConfig.matcherTranslation ? converterConfig.matcherTranslation.toCanonical(rawEntry.matcher) : rawEntry.matcher;
|
|
56835
56926
|
return {
|
|
56836
56927
|
type: hookType,
|
|
56837
56928
|
...command !== void 0 && command !== null && { command },
|
|
@@ -58329,15 +58420,9 @@ const BOB_SPEC = {
|
|
|
58329
58420
|
canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
|
|
58330
58421
|
toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
|
|
58331
58422
|
projectDirVar: "",
|
|
58332
|
-
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
58333
|
-
"sessionStart",
|
|
58334
|
-
"beforeSubmitPrompt",
|
|
58335
|
-
"preCompact",
|
|
58336
|
-
"postCompact",
|
|
58337
|
-
"stop"
|
|
58338
|
-
]),
|
|
58423
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
58339
58424
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
58340
|
-
|
|
58425
|
+
importedHookTypeAliases: { https: "http" },
|
|
58341
58426
|
wildcardMatcherMeansAll: true
|
|
58342
58427
|
}
|
|
58343
58428
|
};
|
|
@@ -58349,7 +58434,7 @@ const BOB_SPEC = {
|
|
|
58349
58434
|
* `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
|
|
58350
58435
|
* `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
|
|
58351
58436
|
* "command": "...", "timeout"?: <seconds> }] }] }`, where a handler may
|
|
58352
|
-
* instead be `{ "type": "
|
|
58437
|
+
* instead be `{ "type": "http", "url": "https://...", "timeout"?: <seconds> }`. The
|
|
58353
58438
|
* file also holds settings rulesync does not own, so generation merges the
|
|
58354
58439
|
* `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting
|
|
58355
58440
|
* the file.
|
|
@@ -60291,6 +60376,32 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
60291
60376
|
}
|
|
60292
60377
|
};
|
|
60293
60378
|
// src/features/hooks/cortexcode-hooks.ts
|
|
60379
|
+
const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
|
|
60380
|
+
const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
60381
|
+
Bash: "bash",
|
|
60382
|
+
Read: "read",
|
|
60383
|
+
Write: "write",
|
|
60384
|
+
Edit: "edit",
|
|
60385
|
+
Grep: "grep",
|
|
60386
|
+
Glob: "glob"
|
|
60387
|
+
};
|
|
60388
|
+
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_TOOL_NAMES).map(([canonical, tool]) => [tool, canonical]));
|
|
60389
|
+
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60390
|
+
function isMatcherName(token) {
|
|
60391
|
+
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60392
|
+
}
|
|
60393
|
+
function replaceMatcherNames(matcher, names) {
|
|
60394
|
+
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60395
|
+
}
|
|
60396
|
+
function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
|
|
60397
|
+
const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60398
|
+
const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
|
|
60399
|
+
if (unmatched.length > 0) warn(`Cortex Code matches hook matchers case-sensitively against lowercase runtime tool names, and ${[...new Set(unmatched)].map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} is not a tool Cortex Code documents a runtime ID for, so it is written as-is and will likely never match. Replace it with the lowercase name Cortex Code uses for that tool, if it has one.`);
|
|
60400
|
+
return translated;
|
|
60401
|
+
}
|
|
60402
|
+
function cortexcodeToCanonicalMatcher(matcher) {
|
|
60403
|
+
return replaceMatcherNames(matcher, CORTEXCODE_TO_CANONICAL_TOOL_NAMES);
|
|
60404
|
+
}
|
|
60294
60405
|
const CORTEXCODE_SPEC = {
|
|
60295
60406
|
displayName: "Cortex Code",
|
|
60296
60407
|
overrideKey: "cortexcode",
|
|
@@ -60300,7 +60411,7 @@ const CORTEXCODE_SPEC = {
|
|
|
60300
60411
|
toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
|
|
60301
60412
|
projectDirVar: "$CORTEX_PROJECT_DIR",
|
|
60302
60413
|
prefixDotRelativeCommandsOnly: true,
|
|
60303
|
-
noMatcherEvents:
|
|
60414
|
+
noMatcherEvents: CORTEXCODE_NO_MATCHER_EVENTS,
|
|
60304
60415
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
|
|
60305
60416
|
booleanPassthroughFields: [{
|
|
60306
60417
|
canonical: "enabled",
|
|
@@ -60315,7 +60426,11 @@ const CORTEXCODE_SPEC = {
|
|
|
60315
60426
|
canonical: "source",
|
|
60316
60427
|
tool: "source",
|
|
60317
60428
|
commandOnly: true
|
|
60318
|
-
}]
|
|
60429
|
+
}],
|
|
60430
|
+
matcherTranslation: {
|
|
60431
|
+
toTool: canonicalToCortexcodeMatcher,
|
|
60432
|
+
toCanonical: cortexcodeToCanonicalMatcher
|
|
60433
|
+
}
|
|
60319
60434
|
}
|
|
60320
60435
|
};
|
|
60321
60436
|
/**
|
|
@@ -60496,35 +60611,80 @@ const NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entr
|
|
|
60496
60611
|
function normalizeCrushEventName(event) {
|
|
60497
60612
|
return event.replaceAll("_", "").toLowerCase();
|
|
60498
60613
|
}
|
|
60614
|
+
/** Crush tool events, by normalized spelling, on which a `matcher` is valid. */
|
|
60615
|
+
const NORMALIZED_CRUSH_TOOL_EVENTS = new Set(CRUSH_TOOL_EVENTS.map((event) => normalizeCrushEventName(event)));
|
|
60616
|
+
/**
|
|
60617
|
+
* Resolve a `crush.hooks` override key to the canonical event it names, or
|
|
60618
|
+
* null when it names no canonical event. The key may be spelled the canonical
|
|
60619
|
+
* way (`stop`) or in any spelling Crush accepts for the event (`TurnEnd`,
|
|
60620
|
+
* `turn_end`), so an override an earlier import filed under a Crush name still
|
|
60621
|
+
* lands on the same event as the shared hooks.
|
|
60622
|
+
*/
|
|
60623
|
+
function resolveOverrideCanonicalEvent(key) {
|
|
60624
|
+
if (lookupOwn({
|
|
60625
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60626
|
+
key
|
|
60627
|
+
}) !== void 0) return key;
|
|
60628
|
+
return lookupOwn({
|
|
60629
|
+
record: NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES,
|
|
60630
|
+
key: normalizeCrushEventName(key)
|
|
60631
|
+
}) ?? null;
|
|
60632
|
+
}
|
|
60499
60633
|
/**
|
|
60500
60634
|
* Build the `hooks` block of `crush.json` from a canonical hooks config.
|
|
60501
60635
|
* Crush keys a flat array of `{name, matcher, command, timeout}` entries by
|
|
60502
60636
|
* event name; `matcher` is a regex tested against the (lower-case) Crush tool
|
|
60503
60637
|
* name and `timeout` is in seconds. Only `type: "command"` canonical hooks are
|
|
60504
60638
|
* emitted, since a Crush hook is a shell command. A shared canonical event
|
|
60505
|
-
* Crush does not fire is skipped (the HooksProcessor reports it)
|
|
60506
|
-
*
|
|
60507
|
-
*
|
|
60639
|
+
* Crush does not fire is skipped (the HooksProcessor reports it).
|
|
60640
|
+
*
|
|
60641
|
+
* Every `crush.hooks` override key is resolved to its event first, whatever
|
|
60642
|
+
* spelling it uses: the override replaces the shared entries of that event
|
|
60643
|
+
* (as for every other tool's override), and override keys that name the same
|
|
60644
|
+
* event are concatenated in order, the way Crush itself merges them. A key
|
|
60645
|
+
* that names no canonical event — such as `PermissionResult`, or one an
|
|
60646
|
+
* import filed there — is written verbatim so it round-trips; keys of that
|
|
60647
|
+
* kind that Crush reads as the same event are concatenated under the first
|
|
60648
|
+
* spelling. A `matcher` is only written on a tool event: Crush rejects the
|
|
60649
|
+
* whole config when one appears anywhere else.
|
|
60508
60650
|
*/
|
|
60509
60651
|
function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
60510
|
-
const
|
|
60511
|
-
for (const [event, defs] of Object.entries(config.hooks))
|
|
60512
|
-
|
|
60513
|
-
...sharedHooks,
|
|
60514
|
-
...toolOverride
|
|
60515
|
-
};
|
|
60516
|
-
const hooks = {};
|
|
60517
|
-
for (const [event, defs] of Object.entries(effective)) {
|
|
60518
|
-
if (isPrototypePollutionKey(event)) continue;
|
|
60519
|
-
const crushEvent = lookupOwn({
|
|
60652
|
+
const shared = /* @__PURE__ */ new Map();
|
|
60653
|
+
for (const [event, defs] of Object.entries(config.hooks)) {
|
|
60654
|
+
const crushEvent = SUPPORTED_CRUSH_EVENTS.has(event) ? lookupOwn({
|
|
60520
60655
|
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60521
60656
|
key: event
|
|
60522
|
-
})
|
|
60657
|
+
}) : void 0;
|
|
60658
|
+
if (crushEvent !== void 0) shared.set(crushEvent, defs);
|
|
60659
|
+
}
|
|
60660
|
+
const override = /* @__PURE__ */ new Map();
|
|
60661
|
+
const verbatimSpellings = /* @__PURE__ */ new Map();
|
|
60662
|
+
for (const [key, defs] of Object.entries(toolOverride ?? {})) {
|
|
60663
|
+
if (isPrototypePollutionKey(key) || !Array.isArray(defs)) continue;
|
|
60664
|
+
const canonical = resolveOverrideCanonicalEvent(key);
|
|
60665
|
+
let crushEvent;
|
|
60666
|
+
if (canonical !== null) crushEvent = lookupOwn({
|
|
60667
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60668
|
+
key: canonical
|
|
60669
|
+
}) ?? key;
|
|
60670
|
+
else {
|
|
60671
|
+
const normalized = normalizeCrushEventName(key);
|
|
60672
|
+
crushEvent = verbatimSpellings.get(normalized) ?? key;
|
|
60673
|
+
verbatimSpellings.set(normalized, crushEvent);
|
|
60674
|
+
}
|
|
60675
|
+
override.set(crushEvent, [...override.get(crushEvent) ?? [], ...defs]);
|
|
60676
|
+
}
|
|
60677
|
+
const effective = new Map(shared);
|
|
60678
|
+
for (const [crushEvent, defs] of override) effective.set(crushEvent, defs);
|
|
60679
|
+
const hooks = {};
|
|
60680
|
+
for (const [crushEvent, defs] of effective) {
|
|
60681
|
+
const allowsMatcher = NORMALIZED_CRUSH_TOOL_EVENTS.has(normalizeCrushEventName(crushEvent));
|
|
60523
60682
|
const entries = [];
|
|
60524
60683
|
for (const def of defs) {
|
|
60525
60684
|
const entry = canonicalDefToCrushEntry({
|
|
60526
60685
|
def,
|
|
60527
|
-
event,
|
|
60686
|
+
event: crushEvent,
|
|
60687
|
+
allowsMatcher,
|
|
60528
60688
|
logger
|
|
60529
60689
|
});
|
|
60530
60690
|
if (entry !== null) entries.push(entry);
|
|
@@ -60534,7 +60694,7 @@ function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
|
60534
60694
|
return hooks;
|
|
60535
60695
|
}
|
|
60536
60696
|
/** Convert one canonical hook definition to a Crush entry, or null to skip. */
|
|
60537
|
-
function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
60697
|
+
function canonicalDefToCrushEntry({ def, event, allowsMatcher, logger }) {
|
|
60538
60698
|
if ((def.type ?? "command") !== "command") return null;
|
|
60539
60699
|
if (typeof def.command !== "string" || def.command === "") {
|
|
60540
60700
|
logger?.warn(`Crush hook ${quoteValueForWarning(def.name ?? "")} under "${event}" has no "command", which Crush would discard, so it was skipped.`);
|
|
@@ -60542,7 +60702,10 @@ function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
|
60542
60702
|
}
|
|
60543
60703
|
const entry = { command: def.command };
|
|
60544
60704
|
if (typeof def.name === "string" && def.name !== "") entry.name = def.name;
|
|
60545
|
-
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*")
|
|
60705
|
+
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*") {
|
|
60706
|
+
if (allowsMatcher) entry.matcher = def.matcher;
|
|
60707
|
+
else logger?.warn(`matcher ${quoteValueForWarning(def.matcher)} on "${event}" hook will be ignored — Crush only accepts a matcher on tool events.`);
|
|
60708
|
+
}
|
|
60546
60709
|
if (typeof def.timeout === "number") entry.timeout = Math.ceil(def.timeout);
|
|
60547
60710
|
return entry;
|
|
60548
60711
|
}
|
|
@@ -60589,12 +60752,13 @@ function crushHooksToCanonical(hooksBlock) {
|
|
|
60589
60752
|
* `<project>/crush.json` (or an existing `.crush.json`; Crush merges the pair,
|
|
60590
60753
|
* lists concatenated) at project scope and `~/.config/crush/crush.json` at
|
|
60591
60754
|
* user scope — as
|
|
60592
|
-
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`.
|
|
60593
|
-
*
|
|
60755
|
+
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. `matcher` is only
|
|
60756
|
+
* valid on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
60757
|
+
* `PermissionRequest`, `PermissionResult`). The `hooks` key is owned outright; every other top-level key
|
|
60594
60758
|
* of the file is preserved and the file is never deleted.
|
|
60595
60759
|
*
|
|
60596
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60597
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60760
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
60761
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
60598
60762
|
*/
|
|
60599
60763
|
var CrushHooks = class CrushHooks extends ToolHooks {
|
|
60600
60764
|
json;
|
|
@@ -63440,8 +63604,11 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
|
|
|
63440
63604
|
* OpenCode 2.x no longer runs V1 plugin modules: a plugin must default-export
|
|
63441
63605
|
* `{ id, setup(ctx) }`. Inside `setup` hooks are registered per domain
|
|
63442
63606
|
* (`await ctx.tool.hook("execute.before", handler)`), and lifecycle events
|
|
63443
|
-
* are read from `ctx.event.subscribe({ signal })`, an async iterable of
|
|
63444
|
-
* public event stream whose payload lives under `event.data`.
|
|
63607
|
+
* are read from `ctx.event.subscribe({ types, signal })`, an async iterable of
|
|
63608
|
+
* the public event stream whose payload lives under `event.data`. `types`
|
|
63609
|
+
* narrows the stream to the event types the plugin handles (a newer 2.x
|
|
63610
|
+
* option; the per-type checks inside the loop stay, so an older 2.x that
|
|
63611
|
+
* ignores it still runs each command only for its own event).
|
|
63445
63612
|
*
|
|
63446
63613
|
* Commands run through a private Bun Shell instance (`new Bun.$.Shell()`, the
|
|
63447
63614
|
* plugin runs inside the Bun-built OpenCode binary) whose working directory is
|
|
@@ -63544,13 +63711,18 @@ function buildHookRegistrationLines(hookPoint, handlers) {
|
|
|
63544
63711
|
}
|
|
63545
63712
|
/**
|
|
63546
63713
|
* Emit the event-stream loop. Each command is isolated in its own try/catch so
|
|
63547
|
-
* a failing hook neither skips its siblings nor ends the subscription.
|
|
63714
|
+
* a failing hook neither skips its siblings nor ends the subscription. The
|
|
63715
|
+
* subscription passes `types` so the server delivers only the handled events.
|
|
63548
63716
|
*/
|
|
63549
63717
|
function buildEventLoopLines(eventHandlers) {
|
|
63550
63718
|
const lines = [];
|
|
63719
|
+
const types = [...eventHandlers.keys()].map((eventType) => JSON.stringify(eventType)).join(", ");
|
|
63551
63720
|
lines.push("const __rulesyncEvents = new AbortController();");
|
|
63552
63721
|
lines.push("void (async () => {");
|
|
63553
|
-
lines.push(" for await (const event of ctx.event.subscribe({
|
|
63722
|
+
lines.push(" for await (const event of ctx.event.subscribe({");
|
|
63723
|
+
lines.push(` types: [${types}],`);
|
|
63724
|
+
lines.push(" signal: __rulesyncEvents.signal,");
|
|
63725
|
+
lines.push(" })) {");
|
|
63554
63726
|
let isFirst = true;
|
|
63555
63727
|
for (const [eventType, handlers] of eventHandlers) {
|
|
63556
63728
|
lines.push(` ${isFirst ? "if" : "} else if"} (event.type === "${eventType}") {`);
|
|
@@ -65684,7 +65856,12 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65684
65856
|
* resolves inside the bundle and is written as is. The file is loaded only
|
|
65685
65857
|
* when `plugin.json` declares the `ai.mistral.vibe` extension.
|
|
65686
65858
|
*
|
|
65859
|
+
* Plugin hooks run on Vibe v2.x and on v3.0.4+. v3.0.0 through v3.0.3 do not
|
|
65860
|
+
* run them (v3.0.4 Fixed: "Hooks shipped in a native Vibe plugin's
|
|
65861
|
+
* `ai.mistral.vibe/hooks.toml` now run"). The output is the same either way.
|
|
65862
|
+
*
|
|
65687
65863
|
* @see https://github.com/mistralai/mistral-vibe/blob/v2.25.8/vibe/core/plugins/_native.py
|
|
65864
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
65688
65865
|
*/
|
|
65689
65866
|
var VibePluginHooks = class extends VibeHooks {
|
|
65690
65867
|
static getSettablePaths() {
|
|
@@ -65768,6 +65945,14 @@ const ZCODE_CONVERTER_CONFIG = {
|
|
|
65768
65945
|
tool: "shell",
|
|
65769
65946
|
commandOnly: true
|
|
65770
65947
|
}],
|
|
65948
|
+
keywordPassthroughFields: [{
|
|
65949
|
+
canonical: "failClosed",
|
|
65950
|
+
tool: "onError",
|
|
65951
|
+
trueValue: "deny",
|
|
65952
|
+
falseValue: "allow",
|
|
65953
|
+
events: /* @__PURE__ */ new Set(["preToolUse"]),
|
|
65954
|
+
ignoredWhenFlags: ["async"]
|
|
65955
|
+
}],
|
|
65771
65956
|
wildcardMatcherMeansAll: true
|
|
65772
65957
|
};
|
|
65773
65958
|
/**
|
|
@@ -73908,9 +74093,21 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
73908
74093
|
...rest
|
|
73909
74094
|
};
|
|
73910
74095
|
}
|
|
74096
|
+
/**
|
|
74097
|
+
* GitLab Duo CLI reserves the MCP server name `duo` for its first-party server
|
|
74098
|
+
* (gitlab-lsp v9.33.0+): a workspace, user or plugin entry named exactly `duo`
|
|
74099
|
+
* (case-sensitive) is dropped with only a log line, so it would never load.
|
|
74100
|
+
*
|
|
74101
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/0572967c915465c1ac94ca38ef57676eb44e16d6
|
|
74102
|
+
*/
|
|
74103
|
+
const GITLABDUO_RESERVED_MCP_SERVER_NAME = "duo";
|
|
73911
74104
|
function toGitlabduoServers(servers, logger) {
|
|
73912
74105
|
const result = {};
|
|
73913
74106
|
for (const [serverName, serverConfig] of Object.entries(servers)) {
|
|
74107
|
+
if (serverName === GITLABDUO_RESERVED_MCP_SERVER_NAME) {
|
|
74108
|
+
logger?.warn(`GitLab Duo CLI MCP: skipping "${serverName}" because GitLab Duo CLI 9.33.0+ reserves that name for its first-party server and never loads a config entry with it. Rename the server to use it with GitLab Duo CLI.`);
|
|
74109
|
+
continue;
|
|
74110
|
+
}
|
|
73914
74111
|
const converted = toGitlabduoServer({
|
|
73915
74112
|
serverName,
|
|
73916
74113
|
serverConfig,
|
|
@@ -75156,24 +75353,47 @@ async function readKimiCodeConfig({ outputRoot }) {
|
|
|
75156
75353
|
}
|
|
75157
75354
|
}
|
|
75158
75355
|
/**
|
|
75356
|
+
* Kimi's reconnect delays are millisecond counts, so only a non-negative safe
|
|
75357
|
+
* integer is passed through. Anything else (a negative, a fraction, a string)
|
|
75358
|
+
* is dropped with a warning rather than written into, or lifted out of, the
|
|
75359
|
+
* shared global `config.toml`.
|
|
75360
|
+
*/
|
|
75361
|
+
function toKimiCodeReconnectDelay({ value, key, logger }) {
|
|
75362
|
+
if (value === void 0) return;
|
|
75363
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
|
|
75364
|
+
warnWithFallback(logger, `Kimi Code MCP: dropping ${key} because it is not a non-negative integer number of milliseconds.`);
|
|
75365
|
+
}
|
|
75366
|
+
/**
|
|
75159
75367
|
* Read the `[mcp]` defaults back out of the shared user `config.toml` so a
|
|
75160
75368
|
* generate → import round trip keeps them. Absent or unparseable content yields
|
|
75161
75369
|
* no defaults rather than failing the import: this file belongs to three
|
|
75162
75370
|
* features plus the user.
|
|
75163
75371
|
*/
|
|
75164
|
-
async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
75372
|
+
async function readKimiCodeMcpDefaults({ outputRoot, logger }) {
|
|
75165
75373
|
const config = await readKimiCodeConfig({ outputRoot });
|
|
75166
75374
|
if (!config.parsed) return {};
|
|
75375
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75376
|
+
value: config.mcp.reconnect_initial_delay_ms,
|
|
75377
|
+
key: "[mcp] reconnect_initial_delay_ms",
|
|
75378
|
+
logger
|
|
75379
|
+
});
|
|
75380
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75381
|
+
value: config.mcp.reconnect_max_delay_ms,
|
|
75382
|
+
key: "[mcp] reconnect_max_delay_ms",
|
|
75383
|
+
logger
|
|
75384
|
+
});
|
|
75167
75385
|
return {
|
|
75168
75386
|
...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
|
|
75169
75387
|
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
|
|
75170
|
-
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
|
|
75388
|
+
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred },
|
|
75389
|
+
...reconnectInitialDelayMs !== void 0 && { reconnectInitialDelayMs },
|
|
75390
|
+
...reconnectMaxDelayMs !== void 0 && { reconnectMaxDelayMs }
|
|
75171
75391
|
};
|
|
75172
75392
|
}
|
|
75173
75393
|
/**
|
|
75174
75394
|
* Kimi's `[mcp]` section in the shared user config sets the default connect and
|
|
75175
|
-
* per-tool-call timeouts,
|
|
75176
|
-
* server, including ones rulesync did not write. Per-server values in
|
|
75395
|
+
* per-tool-call timeouts, whether tools load on demand, and the automatic
|
|
75396
|
+
* reconnect backoff for *every* MCP server, including ones rulesync did not write. Per-server values in
|
|
75177
75397
|
* `mcp.json` still take precedence; this only moves the default.
|
|
75178
75398
|
*
|
|
75179
75399
|
* Written as an auxiliary file so it goes through the normal write phase and
|
|
@@ -75211,7 +75431,9 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
75211
75431
|
...existingSection,
|
|
75212
75432
|
...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
|
|
75213
75433
|
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
|
|
75214
|
-
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
|
|
75434
|
+
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred },
|
|
75435
|
+
...defaults.reconnectInitialDelayMs !== void 0 && { reconnect_initial_delay_ms: defaults.reconnectInitialDelayMs },
|
|
75436
|
+
...defaults.reconnectMaxDelayMs !== void 0 && { reconnect_max_delay_ms: defaults.reconnectMaxDelayMs }
|
|
75215
75437
|
} },
|
|
75216
75438
|
filePath: relativeConfigPath
|
|
75217
75439
|
});
|
|
@@ -75260,7 +75482,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75260
75482
|
fileContent,
|
|
75261
75483
|
validate,
|
|
75262
75484
|
global,
|
|
75263
|
-
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75485
|
+
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75486
|
+
outputRoot,
|
|
75487
|
+
logger
|
|
75488
|
+
}) : {},
|
|
75264
75489
|
logger
|
|
75265
75490
|
});
|
|
75266
75491
|
}
|
|
@@ -75310,7 +75535,17 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75310
75535
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
75311
75536
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
75312
75537
|
const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
|
|
75313
|
-
|
|
75538
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75539
|
+
value: block.reconnectInitialDelayMs,
|
|
75540
|
+
key: "kimi-code.reconnectInitialDelayMs",
|
|
75541
|
+
logger
|
|
75542
|
+
});
|
|
75543
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75544
|
+
value: block.reconnectMaxDelayMs,
|
|
75545
|
+
key: "kimi-code.reconnectMaxDelayMs",
|
|
75546
|
+
logger
|
|
75547
|
+
});
|
|
75548
|
+
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0 && reconnectInitialDelayMs === void 0 && reconnectMaxDelayMs === void 0) return [];
|
|
75314
75549
|
const existing = await readKimiCodeConfig({ outputRoot });
|
|
75315
75550
|
if (!existing.parsed) {
|
|
75316
75551
|
const paths = kimiCodeConfigRelativePath();
|
|
@@ -75322,7 +75557,9 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75322
75557
|
defaults: {
|
|
75323
75558
|
startupTimeoutMs,
|
|
75324
75559
|
toolTimeoutMs,
|
|
75325
|
-
defaultDeferred
|
|
75560
|
+
defaultDeferred,
|
|
75561
|
+
reconnectInitialDelayMs,
|
|
75562
|
+
reconnectMaxDelayMs
|
|
75326
75563
|
},
|
|
75327
75564
|
existing
|
|
75328
75565
|
})];
|
|
@@ -85673,6 +85910,7 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85673
85910
|
"CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT",
|
|
85674
85911
|
"CLAUDE_CODE_DISABLE_POWERSHELL_CMD_RM_DENY",
|
|
85675
85912
|
"CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT",
|
|
85913
|
+
"CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS",
|
|
85676
85914
|
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
85677
85915
|
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
85678
85916
|
"CLAUDE_CODE_PROJECT_DIR_NAME",
|
|
@@ -104918,32 +105156,59 @@ function buildChecksStrategy(ctx) {
|
|
|
104918
105156
|
}
|
|
104919
105157
|
// src/features/shared/antigravity-plugin-manifest.ts
|
|
104920
105158
|
const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
105159
|
+
const ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
105160
|
+
/**
|
|
105161
|
+
* Lower-case a directory name and turn `_` into `-` (collapsing repeated and
|
|
105162
|
+
* trimming edge hyphens), so `My_Plugin` becomes `my-plugin`.
|
|
105163
|
+
*/
|
|
105164
|
+
function toStrictAntigravityPluginName(name) {
|
|
105165
|
+
return name.toLowerCase().replace(/[-_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
105166
|
+
}
|
|
104921
105167
|
/**
|
|
104922
105168
|
* Create the `plugin.json` manifest Antigravity requires before it treats a
|
|
104923
105169
|
* directory as a plugin, so a tree generated from scratch actually loads.
|
|
104924
105170
|
*
|
|
104925
|
-
*
|
|
104926
|
-
*
|
|
104927
|
-
*
|
|
104928
|
-
*
|
|
105171
|
+
* Only the required `name` is written, derived from the plugin directory's
|
|
105172
|
+
* name and normalized to the lowercase kebab-case form `agy plugin validate
|
|
105173
|
+
* --strict` and the Marketplace require; the other manifest fields (such as
|
|
105174
|
+
* `description`, `version` or `author`) have no rulesync source. An existing
|
|
105175
|
+
* manifest is never rewritten: it may carry hand-authored fields. Its `name` is
|
|
105176
|
+
* read only to report a collision with another output root's name. A
|
|
104929
105177
|
* manifest kept in one of the manifest folders counts as present too, so no
|
|
104930
105178
|
* root stub is written next to it.
|
|
104931
105179
|
*/
|
|
104932
105180
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
104933
105181
|
const paths = [];
|
|
105182
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
105183
|
+
const noteName = (name, manifestPath) => {
|
|
105184
|
+
const previousPath = seenNames.get(name);
|
|
105185
|
+
if (previousPath === void 0) {
|
|
105186
|
+
seenNames.set(name, manifestPath);
|
|
105187
|
+
return;
|
|
105188
|
+
}
|
|
105189
|
+
warnOnceWithFallback(logger, `The Antigravity plugin manifests ${quoteForLog(manifestPath)} and ${quoteForLog(previousPath)} both name the plugin ${quoteForLog(name)}, so the plugins collide in Antigravity. Rename one of the directories or set a distinct "name" by hand.`);
|
|
105190
|
+
};
|
|
104934
105191
|
for (const outputRoot of outputRoots) {
|
|
104935
105192
|
const filePath = (0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
104936
|
-
|
|
105193
|
+
const existingManifestPath = await findAntigravityPluginManifest(outputRoot);
|
|
105194
|
+
if (existingManifestPath !== void 0) {
|
|
105195
|
+
const existingName = await readManifestName(existingManifestPath);
|
|
105196
|
+
if (existingName !== void 0) noteName(existingName, existingManifestPath);
|
|
105197
|
+
continue;
|
|
105198
|
+
}
|
|
104937
105199
|
if (await refusesWriteOutsideRoot({
|
|
104938
105200
|
logger,
|
|
104939
105201
|
rootPath: outputRoot,
|
|
104940
105202
|
targetPath: filePath
|
|
104941
105203
|
})) continue;
|
|
104942
|
-
const
|
|
104943
|
-
|
|
104944
|
-
|
|
105204
|
+
const dirName = (0, node_path.basename)((0, node_path.resolve)(outputRoot));
|
|
105205
|
+
const name = toStrictAntigravityPluginName(dirName);
|
|
105206
|
+
if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(dirName) || !ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.test(name)) {
|
|
105207
|
+
warnOnceWithFallback(logger, `Cannot derive an Antigravity plugin name from the directory ${quoteForLog(dirName)}: it must contain only letters, digits, "-" and "_", and at least one letter or digit, so that it normalizes to ${ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.source}. Create ${quoteForLog(filePath)} by hand with a valid "name".`);
|
|
104945
105208
|
continue;
|
|
104946
105209
|
}
|
|
105210
|
+
if (name !== dirName) warnOnceWithFallback(logger, `The Antigravity plugin directory ${quoteForLog(dirName)} is not lowercase kebab-case, which \`agy plugin validate --strict\` and the Marketplace reject, so ${quoteForLog(filePath)} names the plugin ${quoteForLog(name)}.`);
|
|
105211
|
+
noteName(name, filePath);
|
|
104947
105212
|
const content = addTrailingNewline(JSON.stringify({ name }, null, 2));
|
|
104948
105213
|
if (dryRun) logger.info(`[DRY RUN] Would write: ${stripControlCharacters(filePath)}`);
|
|
104949
105214
|
else await writeFileContent(filePath, content);
|
|
@@ -104956,10 +105221,18 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
104956
105221
|
sourceLoadFailed: false
|
|
104957
105222
|
};
|
|
104958
105223
|
}
|
|
104959
|
-
async function
|
|
105224
|
+
async function findAntigravityPluginManifest(outputRoot) {
|
|
104960
105225
|
const candidates = [(0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => (0, node_path.join)(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
104961
|
-
for (const candidate of candidates) if (await fileExists(candidate)) return
|
|
104962
|
-
|
|
105226
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return candidate;
|
|
105227
|
+
}
|
|
105228
|
+
/** The manifest's string `name`, or `undefined` when it is unreadable or absent. */
|
|
105229
|
+
async function readManifestName(manifestPath) {
|
|
105230
|
+
try {
|
|
105231
|
+
const parsed = JSON.parse(await readFileContent(manifestPath));
|
|
105232
|
+
return isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : void 0;
|
|
105233
|
+
} catch {
|
|
105234
|
+
return;
|
|
105235
|
+
}
|
|
104963
105236
|
}
|
|
104964
105237
|
// src/features/shared/hermes-project-plugin-activation.ts
|
|
104965
105238
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|