@rk0429/agentic-relay 0.6.3 → 0.7.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 +181 -18
  2. package/package.json +1 -1
package/dist/relay.mjs CHANGED
@@ -542,6 +542,97 @@ var init_spawn_agent = __esm({
542
542
  }
543
543
  });
544
544
 
545
+ // src/mcp-server/tools/spawn-agents-parallel.ts
546
+ async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl) {
547
+ const envContext = buildContextFromEnv();
548
+ if (envContext.depth >= guard.getConfig().maxDepth) {
549
+ const reason = `Max depth exceeded: ${envContext.depth} >= ${guard.getConfig().maxDepth}`;
550
+ logger.warn(`Batch spawn blocked by RecursionGuard: ${reason}`);
551
+ return {
552
+ results: agents.map((_, index) => ({
553
+ index,
554
+ sessionId: "",
555
+ exitCode: 1,
556
+ stdout: "",
557
+ stderr: `Batch spawn blocked: ${reason}`,
558
+ error: reason
559
+ })),
560
+ totalCount: agents.length,
561
+ successCount: 0,
562
+ failureCount: agents.length
563
+ };
564
+ }
565
+ const currentCount = guard.getCallCount(envContext.traceId);
566
+ const maxCalls = guard.getConfig().maxCallsPerSession;
567
+ if (currentCount + agents.length > maxCalls) {
568
+ const reason = `Batch would exceed max calls per session: ${currentCount} + ${agents.length} > ${maxCalls}`;
569
+ logger.warn(`Batch spawn blocked by RecursionGuard: ${reason}`);
570
+ return {
571
+ results: agents.map((_, index) => ({
572
+ index,
573
+ sessionId: "",
574
+ exitCode: 1,
575
+ stdout: "",
576
+ stderr: `Batch spawn blocked: ${reason}`,
577
+ error: reason
578
+ })),
579
+ totalCount: agents.length,
580
+ successCount: 0,
581
+ failureCount: agents.length
582
+ };
583
+ }
584
+ const settled = await Promise.allSettled(
585
+ agents.map(
586
+ (agent) => executeSpawnAgent(
587
+ agent,
588
+ registry2,
589
+ sessionManager2,
590
+ guard,
591
+ hooksEngine2,
592
+ contextMonitor2,
593
+ backendSelector,
594
+ childHttpUrl
595
+ )
596
+ )
597
+ );
598
+ const results = settled.map((outcome, index) => {
599
+ if (outcome.status === "fulfilled") {
600
+ const r = outcome.value;
601
+ return {
602
+ index,
603
+ sessionId: r.sessionId,
604
+ exitCode: r.exitCode,
605
+ stdout: r.stdout,
606
+ stderr: r.stderr,
607
+ ...r.nativeSessionId ? { nativeSessionId: r.nativeSessionId } : {}
608
+ };
609
+ }
610
+ const errorMessage = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
611
+ return {
612
+ index,
613
+ sessionId: "",
614
+ exitCode: 1,
615
+ stdout: "",
616
+ stderr: errorMessage,
617
+ error: errorMessage
618
+ };
619
+ });
620
+ const successCount = results.filter((r) => r.exitCode === 0).length;
621
+ return {
622
+ results,
623
+ totalCount: agents.length,
624
+ successCount,
625
+ failureCount: agents.length - successCount
626
+ };
627
+ }
628
+ var init_spawn_agents_parallel = __esm({
629
+ "src/mcp-server/tools/spawn-agents-parallel.ts"() {
630
+ "use strict";
631
+ init_spawn_agent();
632
+ init_logger();
633
+ }
634
+ });
635
+
545
636
  // src/mcp-server/tools/list-sessions.ts
546
637
  import { z as z3 } from "zod";
547
638
  async function executeListSessions(input, sessionManager2) {
@@ -702,6 +793,7 @@ var init_server = __esm({
702
793
  "use strict";
703
794
  init_recursion_guard();
704
795
  init_spawn_agent();
796
+ init_spawn_agents_parallel();
705
797
  init_list_sessions();
706
798
  init_get_context_status();
707
799
  init_list_available_backends();
@@ -717,12 +809,11 @@ var init_server = __esm({
717
809
  this.backendSelector = new BackendSelector();
718
810
  this.server = new McpServer({
719
811
  name: "agentic-relay",
720
- version: "0.6.3"
812
+ version: "0.7.0"
721
813
  });
722
814
  this.registerTools(this.server);
723
815
  }
724
816
  server;
725
- childServer;
726
817
  guard;
727
818
  backendSelector;
728
819
  _childHttpServer;
@@ -788,6 +879,56 @@ ${result.stdout}`;
788
879
  }
789
880
  }
790
881
  );
882
+ server.tool(
883
+ "spawn_agents_parallel",
884
+ "Spawn multiple sub-agents in parallel across available backends. Each agent entry accepts the same parameters as spawn_agent. All agents are executed concurrently via Promise.allSettled, and results are returned as an array with per-agent status. RecursionGuard batch pre-validation ensures the entire batch fits within call limits before execution begins.",
885
+ {
886
+ agents: z5.array(z5.object({
887
+ backend: z5.enum(["claude", "codex", "gemini"]),
888
+ prompt: z5.string(),
889
+ agent: z5.string().optional().describe("Named agent configuration (Claude only)"),
890
+ systemPrompt: z5.string().optional().describe("System prompt / role instructions for the sub-agent (all backends)"),
891
+ resumeSessionId: z5.string().optional(),
892
+ model: z5.string().optional(),
893
+ maxTurns: z5.number().optional(),
894
+ skillContext: z5.object({
895
+ skillPath: z5.string().describe("Path to the skill directory"),
896
+ subskill: z5.string().optional().describe("Specific subskill to activate")
897
+ }).optional().describe("Skill context to inject into the sub-agent's system prompt"),
898
+ agentDefinition: z5.object({
899
+ definitionPath: z5.string().describe("Path to the agent definition file")
900
+ }).optional().describe("Agent definition file to inject into the sub-agent's system prompt"),
901
+ preferredBackend: z5.enum(["claude", "codex", "gemini"]).optional().describe("Preferred backend override."),
902
+ taskType: z5.enum(["code", "document", "analysis", "mixed"]).optional().describe("Task type hint for automatic backend selection.")
903
+ })).min(1).max(10).describe("Array of agent configurations to execute in parallel (1-10 agents)")
904
+ },
905
+ async (params) => {
906
+ try {
907
+ const result = await executeSpawnAgentsParallel(
908
+ params.agents,
909
+ this.registry,
910
+ this.sessionManager,
911
+ this.guard,
912
+ this.hooksEngine,
913
+ this.contextMonitor,
914
+ this.backendSelector,
915
+ this._childHttpUrl
916
+ );
917
+ const isError = result.failureCount === result.totalCount;
918
+ const text = JSON.stringify(result, null, 2);
919
+ return {
920
+ content: [{ type: "text", text }],
921
+ isError
922
+ };
923
+ } catch (error) {
924
+ const message = error instanceof Error ? error.message : String(error);
925
+ return {
926
+ content: [{ type: "text", text: `Error: ${message}` }],
927
+ isError: true
928
+ };
929
+ }
930
+ }
931
+ );
791
932
  server.tool(
792
933
  "list_sessions",
793
934
  "List relay sessions, optionally filtered by backend.",
@@ -912,29 +1053,51 @@ ${result.stdout}`;
912
1053
  }
913
1054
  /**
914
1055
  * Start an HTTP server for child agents.
915
- * Creates a separate McpServer instance (since McpServer.connect() can only be called once)
916
- * and binds it to a random port on 127.0.0.1.
1056
+ * Each client connection gets its own McpServer + StreamableHTTPServerTransport pair
1057
+ * because StreamableHTTPServerTransport only supports a single session per instance.
1058
+ * Routes requests to the correct session based on the Mcp-Session-Id header.
917
1059
  */
918
1060
  async startChildHttpServer() {
919
- this.childServer = new McpServer({
920
- name: "agentic-relay",
921
- version: "0.6.3"
922
- });
923
- this.registerTools(this.childServer);
924
- const childTransport = new StreamableHTTPServerTransport({
925
- sessionIdGenerator: () => randomUUID()
926
- });
1061
+ const sessions = /* @__PURE__ */ new Map();
927
1062
  const httpServer = createServer(async (req, res) => {
928
1063
  const url = req.url ?? "";
929
- if (url === "/mcp" || url.startsWith("/mcp?")) {
930
- await childTransport.handleRequest(req, res);
931
- } else {
1064
+ if (url !== "/mcp" && !url.startsWith("/mcp?")) {
932
1065
  res.writeHead(404, { "Content-Type": "text/plain" });
933
1066
  res.end("Not found");
1067
+ return;
1068
+ }
1069
+ const sessionId = req.headers["mcp-session-id"];
1070
+ if (sessionId) {
1071
+ const session = sessions.get(sessionId);
1072
+ if (session) {
1073
+ await session.transport.handleRequest(req, res);
1074
+ return;
1075
+ }
1076
+ }
1077
+ const transport = new StreamableHTTPServerTransport({
1078
+ sessionIdGenerator: () => randomUUID()
1079
+ });
1080
+ const server = new McpServer({
1081
+ name: "agentic-relay",
1082
+ version: "0.7.0"
1083
+ });
1084
+ this.registerTools(server);
1085
+ transport.onclose = () => {
1086
+ const sid2 = transport.sessionId;
1087
+ if (sid2) {
1088
+ sessions.delete(sid2);
1089
+ logger.debug(`Child MCP session closed: ${sid2}`);
1090
+ }
1091
+ };
1092
+ await server.connect(transport);
1093
+ await transport.handleRequest(req, res);
1094
+ const sid = transport.sessionId;
1095
+ if (sid) {
1096
+ sessions.set(sid, { transport, server });
1097
+ logger.debug(`Child MCP session created: ${sid}`);
934
1098
  }
935
1099
  });
936
1100
  this._childHttpServer = httpServer;
937
- await this.childServer.connect(childTransport);
938
1101
  await new Promise((resolve) => {
939
1102
  httpServer.listen(0, "127.0.0.1", () => {
940
1103
  const addr = httpServer.address();
@@ -3898,7 +4061,7 @@ function createVersionCommand(registry2) {
3898
4061
  description: "Show relay and backend versions"
3899
4062
  },
3900
4063
  async run() {
3901
- const relayVersion = "0.6.3";
4064
+ const relayVersion = "0.7.0";
3902
4065
  console.log(`agentic-relay v${relayVersion}`);
3903
4066
  console.log("");
3904
4067
  console.log("Backends:");
@@ -4248,7 +4411,7 @@ void configManager.getConfig().then((config) => {
4248
4411
  var main = defineCommand10({
4249
4412
  meta: {
4250
4413
  name: "relay",
4251
- version: "0.6.3",
4414
+ version: "0.7.0",
4252
4415
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
4253
4416
  },
4254
4417
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.6.3",
3
+ "version": "0.7.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",