@rk0429/agentic-relay 0.15.0 → 0.16.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.
Files changed (2) hide show
  1. package/dist/relay.mjs +159 -37
  2. package/package.json +2 -1
package/dist/relay.mjs CHANGED
@@ -817,7 +817,13 @@ var init_spawn_agent = __esm({
817
817
  taskInstructionPath: z2.string().optional().describe(
818
818
  "Path to a file containing task instructions. Content is prepended to the prompt. Path is resolved relative to the project root and validated against path traversal."
819
819
  ),
820
- label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results and logs.")
820
+ label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results and logs."),
821
+ maxResponseLength: z2.number().int().positive().optional().describe(
822
+ "Maximum response length in characters. When exceeded, response is truncated or summarized based on responseMode."
823
+ ),
824
+ responseMode: z2.enum(["truncate", "summarize"]).optional().describe(
825
+ "How to handle responses exceeding maxResponseLength. 'truncate' (default) cuts at limit with file save. 'summarize' uses AI to condense."
826
+ )
821
827
  });
822
828
  }
823
829
  });
@@ -858,11 +864,11 @@ function extractMentionedPaths(stdout) {
858
864
  for (const pattern of patterns) {
859
865
  let match;
860
866
  while ((match = pattern.exec(stdout)) !== null) {
861
- let path = match[1];
862
- if (path.startsWith("./")) {
863
- path = path.slice(2);
867
+ let path2 = match[1];
868
+ if (path2.startsWith("./")) {
869
+ path2 = path2.slice(2);
864
870
  }
865
- paths.add(path);
871
+ paths.add(path2);
866
872
  }
867
873
  }
868
874
  return paths;
@@ -897,7 +903,7 @@ async function detectConflicts(before, after, agentResults) {
897
903
  }
898
904
  }
899
905
  const conflicts = Array.from(fileToAgents.entries()).map(
900
- ([path, agents]) => ({ path, agents })
906
+ ([path2, agents]) => ({ path: path2, agents })
901
907
  );
902
908
  return {
903
909
  conflicts,
@@ -1207,7 +1213,98 @@ var init_types = __esm({
1207
1213
  }
1208
1214
  });
1209
1215
 
1216
+ // src/mcp-server/response-summarizer.ts
1217
+ import Anthropic from "@anthropic-ai/sdk";
1218
+ async function summarizeResponse(text, targetLength) {
1219
+ const client = new Anthropic();
1220
+ const message = await client.messages.create({
1221
+ model: "claude-haiku-4-5-20251001",
1222
+ max_tokens: Math.max(1024, Math.ceil(targetLength / 3)),
1223
+ messages: [
1224
+ {
1225
+ role: "user",
1226
+ content: `Summarize the following text in ${targetLength} characters or less. Prioritize:
1227
+ 1. Key deliverables, artifacts, and file changes
1228
+ 2. Important decisions and conclusions
1229
+ 3. Action items and next steps
1230
+
1231
+ Preserve technical details (file paths, function names, error messages) when possible.
1232
+
1233
+ <text>
1234
+ ${text}
1235
+ </text>`
1236
+ }
1237
+ ]
1238
+ });
1239
+ const content = message.content[0];
1240
+ if (content?.type !== "text") {
1241
+ throw new Error("Unexpected response format from summarization API");
1242
+ }
1243
+ return content.text;
1244
+ }
1245
+ var init_response_summarizer = __esm({
1246
+ "src/mcp-server/response-summarizer.ts"() {
1247
+ "use strict";
1248
+ }
1249
+ });
1250
+
1210
1251
  // src/mcp-server/response-formatter.ts
1252
+ import * as fs from "fs";
1253
+ import * as path from "path";
1254
+ import * as os from "os";
1255
+ function saveFullResponse(content, sessionId) {
1256
+ try {
1257
+ const dir = sessionId ? path.join(RESPONSE_DIR, sessionId) : path.join(RESPONSE_DIR, `anonymous-${Date.now()}`);
1258
+ fs.mkdirSync(dir, { recursive: true });
1259
+ const filePath = path.join(dir, "response.txt");
1260
+ fs.writeFileSync(filePath, content, "utf-8");
1261
+ try {
1262
+ const entries = fs.readdirSync(RESPONSE_DIR).map((name) => {
1263
+ const fullPath = path.join(RESPONSE_DIR, name);
1264
+ try {
1265
+ const stat = fs.statSync(fullPath);
1266
+ return { name, fullPath, mtimeMs: stat.mtimeMs };
1267
+ } catch {
1268
+ return null;
1269
+ }
1270
+ }).filter((e) => e !== null).sort((a, b) => a.mtimeMs - b.mtimeMs);
1271
+ if (entries.length > MAX_SAVED_RESPONSES) {
1272
+ const toRemove = entries.slice(0, entries.length - MAX_SAVED_RESPONSES);
1273
+ for (const entry of toRemove) {
1274
+ fs.rmSync(entry.fullPath, { recursive: true, force: true });
1275
+ }
1276
+ }
1277
+ } catch {
1278
+ }
1279
+ return filePath;
1280
+ } catch {
1281
+ return void 0;
1282
+ }
1283
+ }
1284
+ async function processStdout(stdout, options) {
1285
+ const maxLength = options?.maxLength ?? STDOUT_DEFAULT_MAX_LENGTH;
1286
+ const mode = options?.mode ?? "truncate";
1287
+ if (stdout.length <= maxLength) {
1288
+ return { text: stdout, truncated: false };
1289
+ }
1290
+ const savedFilePath = saveFullResponse(stdout, options?.sessionId);
1291
+ const savedInfo = savedFilePath ? ` Full response saved to: ${savedFilePath}` : "";
1292
+ if (mode === "summarize") {
1293
+ try {
1294
+ const summary = await summarizeResponse(stdout, maxLength);
1295
+ const footer2 = `
1296
+
1297
+ [RESPONSE SUMMARIZED: original ${stdout.length} chars exceeded limit ${maxLength} chars.${savedInfo}]`;
1298
+ return { text: summary + footer2, truncated: true, savedFilePath };
1299
+ } catch {
1300
+ }
1301
+ }
1302
+ const truncated = stdout.slice(0, maxLength);
1303
+ const footer = `
1304
+
1305
+ [RESPONSE TRUNCATED: original ${stdout.length} chars exceeded limit ${maxLength} chars.${savedInfo}]`;
1306
+ return { text: truncated + footer, truncated: true, savedFilePath };
1307
+ }
1211
1308
  function truncateStderr(stderr) {
1212
1309
  if (!stderr) return stderr;
1213
1310
  const blocks = stderr.split("\n\n");
@@ -1237,7 +1334,7 @@ function truncateStderr(stderr) {
1237
1334
  }
1238
1335
  return stderr;
1239
1336
  }
1240
- function formatSpawnAgentResponse(result) {
1337
+ async function formatSpawnAgentResponse(result, options) {
1241
1338
  const isError = result.exitCode !== 0;
1242
1339
  let text;
1243
1340
  if (isError) {
@@ -1248,9 +1345,10 @@ function formatSpawnAgentResponse(result) {
1248
1345
  ${truncateStderr(result.stderr)}`;
1249
1346
  }
1250
1347
  } else {
1348
+ const processed = await processStdout(result.stdout, options);
1251
1349
  text = `Session: ${result.sessionId}
1252
1350
 
1253
- ${result.stdout}`;
1351
+ ${processed.text}`;
1254
1352
  }
1255
1353
  if (result.metadata) {
1256
1354
  text += `
@@ -1261,7 +1359,7 @@ ${JSON.stringify(result.metadata, null, 2)}
1261
1359
  }
1262
1360
  return { text, isError };
1263
1361
  }
1264
- function formatParallelResponse(result) {
1362
+ async function formatParallelResponse(result, options, perAgentOptions) {
1265
1363
  const isError = result.failureCount === result.totalCount;
1266
1364
  const parts = [];
1267
1365
  if (result.hasConflicts && result.conflicts) {
@@ -1281,7 +1379,9 @@ function formatParallelResponse(result) {
1281
1379
  const duration = r.metadata?.durationMs !== void 0 ? `${(r.metadata.durationMs / 1e3).toFixed(1)}s` : "?s";
1282
1380
  if (r.exitCode === 0) {
1283
1381
  parts.push(`--- Agent ${r.index}${labelPart} (${backend}, ${duration}) SUCCESS ---`);
1284
- parts.push(r.stdout || "(no output)");
1382
+ const agentOptions = perAgentOptions?.get(r.index) ?? options;
1383
+ const processed = await processStdout(r.stdout || "", agentOptions);
1384
+ parts.push(processed.text || "(no output)");
1285
1385
  } else {
1286
1386
  const reasonPart = r.failureReason ? `, reason: ${r.failureReason}` : "";
1287
1387
  parts.push(`--- Agent ${r.index}${labelPart} FAILED (${backend}, ${duration}${reasonPart}) ---`);
@@ -1294,11 +1394,15 @@ ${JSON.stringify(result, null, 2)}
1294
1394
  </metadata>`);
1295
1395
  return { text: parts.join("\n"), isError };
1296
1396
  }
1297
- var STDERR_MAX_LENGTH;
1397
+ var STDERR_MAX_LENGTH, STDOUT_DEFAULT_MAX_LENGTH, MAX_SAVED_RESPONSES, RESPONSE_DIR;
1298
1398
  var init_response_formatter = __esm({
1299
1399
  "src/mcp-server/response-formatter.ts"() {
1300
1400
  "use strict";
1401
+ init_response_summarizer();
1301
1402
  STDERR_MAX_LENGTH = 2e3;
1403
+ STDOUT_DEFAULT_MAX_LENGTH = 2e4;
1404
+ MAX_SAVED_RESPONSES = 100;
1405
+ RESPONSE_DIR = path.join(os.tmpdir(), "agentic-relay-responses");
1302
1406
  }
1303
1407
  });
1304
1408
 
@@ -1345,15 +1449,16 @@ var init_server = __esm({
1345
1449
  };
1346
1450
  MAX_CHILD_HTTP_SESSIONS = 100;
1347
1451
  RelayMCPServer = class {
1348
- constructor(registry2, sessionManager2, guardConfig, hooksEngine2, contextMonitor2) {
1452
+ constructor(registry2, sessionManager2, guardConfig, hooksEngine2, contextMonitor2, defaultMaxResponseLength) {
1349
1453
  this.registry = registry2;
1350
1454
  this.sessionManager = sessionManager2;
1351
1455
  this.hooksEngine = hooksEngine2;
1352
1456
  this.contextMonitor = contextMonitor2;
1457
+ this.defaultMaxResponseLength = defaultMaxResponseLength;
1353
1458
  this.guard = new RecursionGuard(guardConfig);
1354
1459
  this.backendSelector = new BackendSelector();
1355
1460
  this.server = new McpServer(
1356
- { name: "agentic-relay", version: "0.15.0" },
1461
+ { name: "agentic-relay", version: "0.16.0" },
1357
1462
  createMcpServerOptions()
1358
1463
  );
1359
1464
  this.registerTools(this.server);
@@ -1388,7 +1493,12 @@ var init_server = __esm({
1388
1493
  this.backendSelector,
1389
1494
  this._childHttpUrl
1390
1495
  );
1391
- const { text, isError } = formatSpawnAgentResponse(result);
1496
+ const controlOptions = {
1497
+ maxLength: params.maxResponseLength ?? this.defaultMaxResponseLength ?? STDOUT_DEFAULT_MAX_LENGTH,
1498
+ mode: params.responseMode ?? "truncate",
1499
+ sessionId: result.sessionId
1500
+ };
1501
+ const { text, isError } = await formatSpawnAgentResponse(result, controlOptions);
1392
1502
  const callToolResult = {
1393
1503
  content: [{ type: "text", text }],
1394
1504
  isError
@@ -1435,7 +1545,11 @@ var init_server = __esm({
1435
1545
  this.backendSelector,
1436
1546
  this._childHttpUrl
1437
1547
  );
1438
- const { text, isError } = formatParallelResponse(result);
1548
+ const controlOptions = {
1549
+ maxLength: this.defaultMaxResponseLength ?? STDOUT_DEFAULT_MAX_LENGTH,
1550
+ mode: "truncate"
1551
+ };
1552
+ const { text, isError } = await formatParallelResponse(result, controlOptions);
1439
1553
  const callToolResult = {
1440
1554
  content: [{ type: "text", text }],
1441
1555
  isError
@@ -1551,7 +1665,11 @@ var init_server = __esm({
1551
1665
  this.backendSelector,
1552
1666
  this._childHttpUrl
1553
1667
  );
1554
- const { text, isError } = formatParallelResponse(result);
1668
+ const controlOptions = {
1669
+ maxLength: this.defaultMaxResponseLength ?? STDOUT_DEFAULT_MAX_LENGTH,
1670
+ mode: "truncate"
1671
+ };
1672
+ const { text, isError } = await formatParallelResponse(result, controlOptions);
1555
1673
  return {
1556
1674
  content: [{ type: "text", text }],
1557
1675
  isError
@@ -1715,7 +1833,7 @@ var init_server = __esm({
1715
1833
  sessionIdGenerator: () => randomUUID()
1716
1834
  });
1717
1835
  const server = new McpServer(
1718
- { name: "agentic-relay", version: "0.15.0" },
1836
+ { name: "agentic-relay", version: "0.16.0" },
1719
1837
  createMcpServerOptions()
1720
1838
  );
1721
1839
  this.registerTools(server);
@@ -1771,7 +1889,7 @@ var init_server = __esm({
1771
1889
 
1772
1890
  // src/bin/relay.ts
1773
1891
  import { defineCommand as defineCommand10, runMain } from "citty";
1774
- import { join as join9 } from "path";
1892
+ import { join as join10 } from "path";
1775
1893
  import { homedir as homedir6 } from "os";
1776
1894
 
1777
1895
  // src/infrastructure/process-manager.ts
@@ -3315,7 +3433,8 @@ var relayConfigSchema = z.object({
3315
3433
  mcpServerMode: z.object({
3316
3434
  maxDepth: z.number().int().positive(),
3317
3435
  maxCallsPerSession: z.number().int().positive(),
3318
- timeoutSec: z.number().positive()
3436
+ timeoutSec: z.number().positive(),
3437
+ maxResponseLength: z.number().int().positive().optional()
3319
3438
  }).optional(),
3320
3439
  telemetry: z.object({
3321
3440
  enabled: z.boolean()
@@ -3344,8 +3463,8 @@ function deepMerge(target, source) {
3344
3463
  function isPlainObject(value) {
3345
3464
  return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
3346
3465
  }
3347
- function getByPath(obj, path) {
3348
- const parts = path.split(".");
3466
+ function getByPath(obj, path2) {
3467
+ const parts = path2.split(".");
3349
3468
  let current = obj;
3350
3469
  for (const part of parts) {
3351
3470
  if (!isPlainObject(current)) return void 0;
@@ -3353,8 +3472,8 @@ function getByPath(obj, path) {
3353
3472
  }
3354
3473
  return current;
3355
3474
  }
3356
- function setByPath(obj, path, value) {
3357
- const parts = path.split(".");
3475
+ function setByPath(obj, path2, value) {
3476
+ const parts = path2.split(".");
3358
3477
  let current = obj;
3359
3478
  for (let i = 0; i < parts.length - 1; i++) {
3360
3479
  const part = parts[i];
@@ -4546,6 +4665,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
4546
4665
  return;
4547
4666
  }
4548
4667
  let guardConfig;
4668
+ let defaultMaxResponseLength;
4549
4669
  try {
4550
4670
  const config = await configManager2.getConfig();
4551
4671
  if (config.mcpServerMode) {
@@ -4554,6 +4674,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
4554
4674
  maxCallsPerSession: config.mcpServerMode.maxCallsPerSession ?? 20,
4555
4675
  timeoutSec: config.mcpServerMode.timeoutSec ?? 86400
4556
4676
  };
4677
+ defaultMaxResponseLength = config.mcpServerMode.maxResponseLength;
4557
4678
  }
4558
4679
  } catch {
4559
4680
  }
@@ -4564,7 +4685,8 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
4564
4685
  sessionManager2,
4565
4686
  guardConfig,
4566
4687
  hooksEngine2,
4567
- contextMonitor2
4688
+ contextMonitor2,
4689
+ defaultMaxResponseLength
4568
4690
  );
4569
4691
  await server.start({ transport, port });
4570
4692
  }
@@ -4726,7 +4848,7 @@ function createVersionCommand(registry2) {
4726
4848
  description: "Show relay and backend versions"
4727
4849
  },
4728
4850
  async run() {
4729
- const relayVersion = "0.15.0";
4851
+ const relayVersion = "0.16.0";
4730
4852
  console.log(`agentic-relay v${relayVersion}`);
4731
4853
  console.log("");
4732
4854
  console.log("Backends:");
@@ -4751,7 +4873,7 @@ function createVersionCommand(registry2) {
4751
4873
  // src/commands/doctor.ts
4752
4874
  import { defineCommand as defineCommand8 } from "citty";
4753
4875
  import { access, constants, readdir as readdir2 } from "fs/promises";
4754
- import { join as join7 } from "path";
4876
+ import { join as join8 } from "path";
4755
4877
  import { homedir as homedir5 } from "os";
4756
4878
  import { execFile as execFile2 } from "child_process";
4757
4879
  import { promisify as promisify2 } from "util";
@@ -4812,8 +4934,8 @@ async function checkConfig(configManager2) {
4812
4934
  }
4813
4935
  }
4814
4936
  async function checkSessionsDir() {
4815
- const relayHome2 = process.env["RELAY_HOME"] ?? join7(homedir5(), ".relay");
4816
- const sessionsDir = join7(relayHome2, "sessions");
4937
+ const relayHome2 = process.env["RELAY_HOME"] ?? join8(homedir5(), ".relay");
4938
+ const sessionsDir = join8(relayHome2, "sessions");
4817
4939
  try {
4818
4940
  await access(sessionsDir, constants.W_OK);
4819
4941
  return {
@@ -4926,8 +5048,8 @@ async function checkBackendAuthEnv() {
4926
5048
  return results;
4927
5049
  }
4928
5050
  async function checkSessionsDiskUsage() {
4929
- const relayHome2 = process.env["RELAY_HOME"] ?? join7(homedir5(), ".relay");
4930
- const sessionsDir = join7(relayHome2, "sessions");
5051
+ const relayHome2 = process.env["RELAY_HOME"] ?? join8(homedir5(), ".relay");
5052
+ const sessionsDir = join8(relayHome2, "sessions");
4931
5053
  try {
4932
5054
  const entries = await readdir2(sessionsDir);
4933
5055
  const fileCount = entries.length;
@@ -5001,7 +5123,7 @@ function createDoctorCommand(registry2, configManager2) {
5001
5123
  init_logger();
5002
5124
  import { defineCommand as defineCommand9 } from "citty";
5003
5125
  import { mkdir as mkdir6, writeFile as writeFile6, access as access2, readFile as readFile6 } from "fs/promises";
5004
- import { join as join8 } from "path";
5126
+ import { join as join9 } from "path";
5005
5127
  var DEFAULT_CONFIG2 = {
5006
5128
  defaultBackend: "claude",
5007
5129
  backends: {},
@@ -5015,8 +5137,8 @@ function createInitCommand() {
5015
5137
  },
5016
5138
  async run() {
5017
5139
  const projectDir = process.cwd();
5018
- const relayDir = join8(projectDir, ".relay");
5019
- const configPath = join8(relayDir, "config.json");
5140
+ const relayDir = join9(projectDir, ".relay");
5141
+ const configPath = join9(relayDir, "config.json");
5020
5142
  try {
5021
5143
  await access2(relayDir);
5022
5144
  logger.info(
@@ -5032,7 +5154,7 @@ function createInitCommand() {
5032
5154
  "utf-8"
5033
5155
  );
5034
5156
  logger.success(`Created ${configPath}`);
5035
- const gitignorePath = join8(projectDir, ".gitignore");
5157
+ const gitignorePath = join9(projectDir, ".gitignore");
5036
5158
  try {
5037
5159
  const gitignoreContent = await readFile6(gitignorePath, "utf-8");
5038
5160
  if (!gitignoreContent.includes(".relay/config.local.json")) {
@@ -5058,8 +5180,8 @@ registry.registerLazy("claude", () => new ClaudeAdapter(processManager));
5058
5180
  registry.registerLazy("codex", () => new CodexAdapter(processManager));
5059
5181
  registry.registerLazy("gemini", () => new GeminiAdapter(processManager));
5060
5182
  var sessionManager = new SessionManager();
5061
- var relayHome = process.env["RELAY_HOME"] ?? join9(homedir6(), ".relay");
5062
- var projectRelayDir = join9(process.cwd(), ".relay");
5183
+ var relayHome = process.env["RELAY_HOME"] ?? join10(homedir6(), ".relay");
5184
+ var projectRelayDir = join10(process.cwd(), ".relay");
5063
5185
  var configManager = new ConfigManager(relayHome, projectRelayDir);
5064
5186
  var authManager = new AuthManager(registry);
5065
5187
  var eventBus = new EventBus();
@@ -5076,7 +5198,7 @@ void configManager.getConfig().then((config) => {
5076
5198
  var main = defineCommand10({
5077
5199
  meta: {
5078
5200
  name: "relay",
5079
- version: "0.15.0",
5201
+ version: "0.16.0",
5080
5202
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
5081
5203
  },
5082
5204
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI with MCP-based multi-layer sub-agent orchestration",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -50,6 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@anthropic-ai/claude-agent-sdk": "^0.2.59",
53
+ "@anthropic-ai/sdk": "^0.78.0",
53
54
  "@modelcontextprotocol/sdk": "^1.27.1",
54
55
  "@openai/codex-sdk": "^0.105.0",
55
56
  "citty": "^0.1.6",