@intellectronica/ruler 0.3.23 → 0.3.24

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.
@@ -79,6 +79,13 @@ class AbstractAgent {
79
79
  supportsMcpRemote() {
80
80
  return false;
81
81
  }
82
+ /**
83
+ * Returns whether this agent supports MCP server timeout configuration.
84
+ * Defaults to false if not overridden.
85
+ */
86
+ supportsMcpTimeout() {
87
+ return false;
88
+ }
82
89
  /**
83
90
  * Returns whether this agent has native skills support.
84
91
  * Defaults to false if not overridden.
@@ -95,6 +95,9 @@ class OpenCodeAgent {
95
95
  supportsMcpRemote() {
96
96
  return true;
97
97
  }
98
+ supportsMcpTimeout() {
99
+ return true;
100
+ }
98
101
  supportsNativeSkills() {
99
102
  return true;
100
103
  }
@@ -182,6 +182,9 @@ async function loadUnifiedConfig(options) {
182
182
  if (serverDef.headers && typeof serverDef.headers === 'object') {
183
183
  server.headers = Object.fromEntries(Object.entries(serverDef.headers).filter(([, v]) => typeof v === 'string'));
184
184
  }
185
+ if (typeof serverDef.timeout === 'number') {
186
+ server.timeout = serverDef.timeout;
187
+ }
185
188
  // Validate server configuration
186
189
  const hasCommand = !!server.command;
187
190
  const hasUrl = !!server.url;
@@ -301,6 +304,9 @@ async function loadUnifiedConfig(options) {
301
304
  if (def.headers && typeof def.headers === 'object') {
302
305
  server.headers = Object.fromEntries(Object.entries(def.headers).filter(([, v]) => typeof v === 'string'));
303
306
  }
307
+ if (typeof def.timeout === 'number') {
308
+ server.timeout = def.timeout;
309
+ }
304
310
  // Derive type
305
311
  if (server.url)
306
312
  server.type = 'remote';
@@ -427,17 +427,49 @@ async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, back
427
427
  }
428
428
  }
429
429
  }
430
+ function sanitizeMcpTimeoutsForAgent(agent, mcpJson, dryRun) {
431
+ if (agent.supportsMcpTimeout?.()) {
432
+ return mcpJson;
433
+ }
434
+ if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
435
+ return mcpJson;
436
+ }
437
+ const servers = mcpJson.mcpServers;
438
+ const sanitizedServers = {};
439
+ const strippedTimeouts = [];
440
+ for (const [name, serverDef] of Object.entries(servers)) {
441
+ if (serverDef && typeof serverDef === 'object') {
442
+ const copy = { ...serverDef };
443
+ if ('timeout' in copy) {
444
+ delete copy.timeout;
445
+ strippedTimeouts.push(name);
446
+ }
447
+ sanitizedServers[name] = copy;
448
+ }
449
+ else {
450
+ sanitizedServers[name] = serverDef;
451
+ }
452
+ }
453
+ if (strippedTimeouts.length > 0) {
454
+ (0, constants_1.logWarn)(`${agent.getName()} does not support MCP server timeout configuration; ignoring timeout for: ${strippedTimeouts.join(', ')}`, dryRun);
455
+ }
456
+ return {
457
+ ...mcpJson,
458
+ mcpServers: sanitizedServers,
459
+ };
460
+ }
430
461
  async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
431
462
  // Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
432
463
  if (!dest.startsWith(projectRoot)) {
433
464
  (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
434
465
  return;
435
466
  }
467
+ const agentMcpJson = sanitizeMcpTimeoutsForAgent(agent, filteredMcpJson, dryRun);
436
468
  if (agent.getIdentifier() === 'openhands') {
437
- return await applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup);
469
+ return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
438
470
  }
439
471
  if (agent.getIdentifier() === 'opencode') {
440
- return await applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup);
472
+ return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
441
473
  }
442
474
  // Agents that handle MCP configuration internally should not have external MCP handling
443
475
  if (agent.getIdentifier() === 'zed' ||
@@ -447,7 +479,7 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
447
479
  (0, constants_1.logVerbose)(`Skipping external MCP config for ${agent.getName()} - handled internally by agent`, verbose);
448
480
  return;
449
481
  }
450
- return await applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
482
+ return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
451
483
  }
452
484
  async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
453
485
  if (dryRun) {
@@ -63,6 +63,9 @@ function transformToOpenCodeFormat(rulerMcp) {
63
63
  if (serverDef.headers) {
64
64
  openCodeServer.headers = serverDef.headers;
65
65
  }
66
+ if (typeof serverDef.timeout === 'number') {
67
+ openCodeServer.timeout = serverDef.timeout;
68
+ }
66
69
  }
67
70
  else if (isLocalServer(serverDef)) {
68
71
  openCodeServer.type = 'local';
@@ -74,6 +77,9 @@ function transformToOpenCodeFormat(rulerMcp) {
74
77
  if (serverDef.env) {
75
78
  openCodeServer.environment = serverDef.env;
76
79
  }
80
+ if (typeof serverDef.timeout === 'number') {
81
+ openCodeServer.timeout = serverDef.timeout;
82
+ }
77
83
  }
78
84
  else {
79
85
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.23",
3
+ "version": "0.3.24",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {