@rk0429/agentic-relay 0.10.1 → 0.12.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 +190 -100
  2. package/package.json +1 -1
package/dist/relay.mjs CHANGED
@@ -55,8 +55,8 @@ var init_recursion_guard = __esm({
55
55
  RecursionGuard = class _RecursionGuard {
56
56
  constructor(config = {
57
57
  maxDepth: 5,
58
- maxCallsPerSession: 20,
59
- timeoutSec: 300
58
+ maxCallsPerSession: 50,
59
+ timeoutSec: 1800
60
60
  }) {
61
61
  this.config = config;
62
62
  }
@@ -110,10 +110,15 @@ var init_recursion_guard = __esm({
110
110
  reason: `Max calls per session exceeded: ${currentCount} >= ${this.config.maxCallsPerSession}`
111
111
  };
112
112
  }
113
- if (this.detectLoop(context.traceId, context.backend, context.promptHash)) {
113
+ if (this.detectLoop(
114
+ context.traceId,
115
+ context.backend,
116
+ context.promptHash,
117
+ context.taskType
118
+ )) {
114
119
  return {
115
120
  allowed: false,
116
- reason: `Loop detected: same (backend=${context.backend}, promptHash=${context.promptHash}) appeared 3+ times in trace ${context.traceId}`
121
+ reason: `Loop detected: same (backend=${context.backend}, taskType=${context.taskType ?? ""}, promptHash=${context.promptHash}) appeared 5+ times in trace ${context.traceId}`
117
122
  };
118
123
  }
119
124
  return { allowed: true };
@@ -127,7 +132,7 @@ var init_recursion_guard = __esm({
127
132
  } else {
128
133
  this.callCounts.set(context.traceId, { count: 1, createdAt: Date.now() });
129
134
  }
130
- const key = `${context.backend}:${context.promptHash}`;
135
+ const key = `${context.backend}:${context.taskType ?? ""}:${context.promptHash}`;
131
136
  const hashEntry = this.promptHashes.get(context.traceId);
132
137
  if (hashEntry) {
133
138
  hashEntry.hashes.push(key);
@@ -135,12 +140,12 @@ var init_recursion_guard = __esm({
135
140
  this.promptHashes.set(context.traceId, { hashes: [key], createdAt: Date.now() });
136
141
  }
137
142
  }
138
- /** Detect if the same (backend + promptHash) combination has appeared 3+ times */
139
- detectLoop(traceId, backend, promptHash) {
140
- const key = `${backend}:${promptHash}`;
143
+ /** Detect if the same (backend + taskType + promptHash) combination has appeared 5+ times */
144
+ detectLoop(traceId, backend, promptHash, taskType) {
145
+ const key = `${backend}:${taskType ?? ""}:${promptHash}`;
141
146
  const hashes = this.promptHashes.get(traceId)?.hashes ?? [];
142
147
  const count = hashes.filter((h) => h === key).length;
143
- return count >= 3;
148
+ return count >= 5;
144
149
  }
145
150
  /** Get current config (for testing/inspection) */
146
151
  getConfig() {
@@ -344,7 +349,8 @@ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooks
344
349
  traceId: envContext.traceId,
345
350
  depth: envContext.depth,
346
351
  backend: effectiveBackend,
347
- promptHash
352
+ promptHash,
353
+ taskType: input.taskType
348
354
  };
349
355
  const guardResult = guard.canSpawn(context);
350
356
  if (!guardResult.allowed) {
@@ -1041,6 +1047,24 @@ var init_backend_selector = __esm({
1041
1047
  }
1042
1048
  });
1043
1049
 
1050
+ // src/types/mcp.ts
1051
+ var DEFAULT_TASK_TTL, DEFAULT_POLL_INTERVAL;
1052
+ var init_mcp = __esm({
1053
+ "src/types/mcp.ts"() {
1054
+ "use strict";
1055
+ DEFAULT_TASK_TTL = 5 * 60 * 1e3;
1056
+ DEFAULT_POLL_INTERVAL = 3e3;
1057
+ }
1058
+ });
1059
+
1060
+ // src/types/index.ts
1061
+ var init_types = __esm({
1062
+ "src/types/index.ts"() {
1063
+ "use strict";
1064
+ init_mcp();
1065
+ }
1066
+ });
1067
+
1044
1068
  // src/mcp-server/response-formatter.ts
1045
1069
  function formatSpawnAgentResponse(result) {
1046
1070
  const isError = result.exitCode !== 0;
@@ -1113,9 +1137,20 @@ __export(server_exports, {
1113
1137
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1114
1138
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1115
1139
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1140
+ import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
1141
+ import { InMemoryTaskMessageQueue } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
1116
1142
  import { createServer } from "http";
1117
1143
  import { randomUUID } from "crypto";
1118
1144
  import { z as z5 } from "zod";
1145
+ function createMcpServerOptions() {
1146
+ const taskStore = new InMemoryTaskStore();
1147
+ return {
1148
+ capabilities: { tasks: { requests: { tools: { call: {} } } } },
1149
+ taskStore,
1150
+ taskMessageQueue: new InMemoryTaskMessageQueue(),
1151
+ defaultTaskPollInterval: DEFAULT_POLL_INTERVAL
1152
+ };
1153
+ }
1119
1154
  var spawnAgentsParallelInputShape, MAX_CHILD_HTTP_SESSIONS, RelayMCPServer;
1120
1155
  var init_server = __esm({
1121
1156
  "src/mcp-server/server.ts"() {
@@ -1128,6 +1163,7 @@ var init_server = __esm({
1128
1163
  init_list_available_backends();
1129
1164
  init_backend_selector();
1130
1165
  init_logger();
1166
+ init_types();
1131
1167
  init_response_formatter();
1132
1168
  spawnAgentsParallelInputShape = {
1133
1169
  agents: z5.array(spawnAgentInputSchema).min(1).max(10).describe(
@@ -1143,10 +1179,10 @@ var init_server = __esm({
1143
1179
  this.contextMonitor = contextMonitor2;
1144
1180
  this.guard = new RecursionGuard(guardConfig);
1145
1181
  this.backendSelector = new BackendSelector();
1146
- this.server = new McpServer({
1147
- name: "agentic-relay",
1148
- version: "0.10.1"
1149
- });
1182
+ this.server = new McpServer(
1183
+ { name: "agentic-relay", version: "0.12.0" },
1184
+ createMcpServerOptions()
1185
+ );
1150
1186
  this.registerTools(this.server);
1151
1187
  }
1152
1188
  server;
@@ -1162,94 +1198,148 @@ var init_server = __esm({
1162
1198
  get childHttpServer() {
1163
1199
  return this._childHttpServer;
1164
1200
  }
1165
- registerTools(server) {
1166
- server.tool(
1167
- "spawn_agent",
1168
- "Spawn a sub-agent on the specified backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Use 'agent' for named agent configurations (Claude only), 'systemPrompt' for custom role instructions (all backends), 'skillContext' to inject a skill definition (SKILL.md/SUBSKILL.md), 'agentDefinition' to inject an agent definition file into the sub-agent's system prompt, 'preferredBackend' to override automatic backend selection, or 'taskType' to hint at the task nature for backend selection.",
1169
- spawnAgentInputSchema.shape,
1170
- async (params, extra) => {
1201
+ /**
1202
+ * Run spawn_agent in the background and store the result in taskStore.
1203
+ * Fire-and-forget: errors are caught and stored as failed task results.
1204
+ */
1205
+ runSpawnAgentInBackground(taskId, params, extra) {
1206
+ const run = async () => {
1207
+ try {
1208
+ const result = await executeSpawnAgent(
1209
+ params,
1210
+ this.registry,
1211
+ this.sessionManager,
1212
+ this.guard,
1213
+ this.hooksEngine,
1214
+ this.contextMonitor,
1215
+ this.backendSelector,
1216
+ this._childHttpUrl
1217
+ );
1218
+ const { text, isError } = formatSpawnAgentResponse(result);
1219
+ const callToolResult = {
1220
+ content: [{ type: "text", text }],
1221
+ isError
1222
+ };
1223
+ await extra.taskStore.storeTaskResult(
1224
+ taskId,
1225
+ "completed",
1226
+ callToolResult
1227
+ );
1228
+ } catch (outerError) {
1171
1229
  try {
1172
- const onProgress = (progress) => {
1173
- const progressToken = params._meta ? params._meta.progressToken : void 0;
1174
- if (progressToken !== void 0) {
1175
- void extra.sendNotification({
1176
- method: "notifications/progress",
1177
- params: {
1178
- progressToken,
1179
- progress: progress.percent ?? 0,
1180
- total: 100,
1181
- message: progress.stage
1182
- }
1183
- });
1230
+ const message = outerError instanceof Error ? outerError.message : String(outerError);
1231
+ await extra.taskStore.storeTaskResult(
1232
+ taskId,
1233
+ "failed",
1234
+ {
1235
+ content: [{ type: "text", text: `Error: ${message}` }],
1236
+ isError: true
1184
1237
  }
1185
- };
1186
- const result = await executeSpawnAgent(
1187
- params,
1188
- this.registry,
1189
- this.sessionManager,
1190
- this.guard,
1191
- this.hooksEngine,
1192
- this.contextMonitor,
1193
- this.backendSelector,
1194
- this._childHttpUrl,
1195
- onProgress
1196
1238
  );
1197
- const { text, isError } = formatSpawnAgentResponse(result);
1198
- return {
1199
- content: [{ type: "text", text }],
1200
- isError
1201
- };
1202
- } catch (error) {
1203
- const message = error instanceof Error ? error.message : String(error);
1204
- return {
1205
- content: [{ type: "text", text: `Error: ${message}` }],
1206
- isError: true
1207
- };
1239
+ } catch (storeError) {
1240
+ logger.error(
1241
+ `Failed to store task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
1242
+ );
1208
1243
  }
1209
1244
  }
1210
- );
1211
- server.tool(
1212
- "spawn_agents_parallel",
1213
- "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. Failed results include 'originalInput' for retry via retry_failed_agents.",
1214
- spawnAgentsParallelInputShape,
1215
- async (params, extra) => {
1245
+ };
1246
+ void run();
1247
+ }
1248
+ /**
1249
+ * Run spawn_agents_parallel in the background and store the result in taskStore.
1250
+ * Fire-and-forget: errors are caught and stored as failed task results.
1251
+ */
1252
+ runSpawnAgentsParallelInBackground(taskId, agents, extra) {
1253
+ const run = async () => {
1254
+ try {
1255
+ const result = await executeSpawnAgentsParallel(
1256
+ agents,
1257
+ this.registry,
1258
+ this.sessionManager,
1259
+ this.guard,
1260
+ this.hooksEngine,
1261
+ this.contextMonitor,
1262
+ this.backendSelector,
1263
+ this._childHttpUrl
1264
+ );
1265
+ const { text, isError } = formatParallelResponse(result);
1266
+ const callToolResult = {
1267
+ content: [{ type: "text", text }],
1268
+ isError
1269
+ };
1270
+ await extra.taskStore.storeTaskResult(
1271
+ taskId,
1272
+ "completed",
1273
+ callToolResult
1274
+ );
1275
+ } catch (outerError) {
1216
1276
  try {
1217
- const onProgress = (progress) => {
1218
- const progressToken = params._meta ? params._meta.progressToken : void 0;
1219
- if (progressToken !== void 0) {
1220
- void extra.sendNotification({
1221
- method: "notifications/progress",
1222
- params: {
1223
- progressToken,
1224
- progress: progress.percent ?? 0,
1225
- total: 100,
1226
- message: progress.stage
1227
- }
1228
- });
1277
+ const message = outerError instanceof Error ? outerError.message : String(outerError);
1278
+ await extra.taskStore.storeTaskResult(
1279
+ taskId,
1280
+ "failed",
1281
+ {
1282
+ content: [{ type: "text", text: `Error: ${message}` }],
1283
+ isError: true
1229
1284
  }
1230
- };
1231
- const result = await executeSpawnAgentsParallel(
1232
- params.agents,
1233
- this.registry,
1234
- this.sessionManager,
1235
- this.guard,
1236
- this.hooksEngine,
1237
- this.contextMonitor,
1238
- this.backendSelector,
1239
- this._childHttpUrl,
1240
- onProgress
1241
1285
  );
1242
- const { text, isError } = formatParallelResponse(result);
1243
- return {
1244
- content: [{ type: "text", text }],
1245
- isError
1246
- };
1247
- } catch (error) {
1248
- const message = error instanceof Error ? error.message : String(error);
1249
- return {
1250
- content: [{ type: "text", text: `Error: ${message}` }],
1251
- isError: true
1252
- };
1286
+ } catch (storeError) {
1287
+ logger.error(
1288
+ `Failed to store parallel task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
1289
+ );
1290
+ }
1291
+ }
1292
+ };
1293
+ void run();
1294
+ }
1295
+ registerTools(server) {
1296
+ server.experimental.tasks.registerToolTask(
1297
+ "spawn_agent",
1298
+ {
1299
+ description: "Spawn a sub-agent on the specified backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Use 'agent' for named agent configurations (Claude only), 'systemPrompt' for custom role instructions (all backends), 'skillContext' to inject a skill definition (SKILL.md/SUBSKILL.md), 'agentDefinition' to inject an agent definition file into the sub-agent's system prompt, 'preferredBackend' to override automatic backend selection, or 'taskType' to hint at the task nature for backend selection.",
1300
+ inputSchema: spawnAgentInputSchema.shape,
1301
+ execution: { taskSupport: "optional" }
1302
+ },
1303
+ {
1304
+ createTask: async (params, extra) => {
1305
+ const task = await extra.taskStore.createTask({
1306
+ ttl: DEFAULT_TASK_TTL,
1307
+ pollInterval: DEFAULT_POLL_INTERVAL
1308
+ });
1309
+ this.runSpawnAgentInBackground(task.taskId, params, extra);
1310
+ return { task };
1311
+ },
1312
+ getTask: async (_params, extra) => {
1313
+ const task = await extra.taskStore.getTask(extra.taskId);
1314
+ return { task: task ?? void 0 };
1315
+ },
1316
+ getTaskResult: async (_params, extra) => {
1317
+ return await extra.taskStore.getTaskResult(extra.taskId);
1318
+ }
1319
+ }
1320
+ );
1321
+ server.experimental.tasks.registerToolTask(
1322
+ "spawn_agents_parallel",
1323
+ {
1324
+ description: "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. Failed results include 'originalInput' for retry via retry_failed_agents.",
1325
+ inputSchema: spawnAgentsParallelInputShape,
1326
+ execution: { taskSupport: "optional" }
1327
+ },
1328
+ {
1329
+ createTask: async (params, extra) => {
1330
+ const task = await extra.taskStore.createTask({
1331
+ ttl: DEFAULT_TASK_TTL,
1332
+ pollInterval: DEFAULT_POLL_INTERVAL
1333
+ });
1334
+ this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra);
1335
+ return { task };
1336
+ },
1337
+ getTask: async (_params, extra) => {
1338
+ const task = await extra.taskStore.getTask(extra.taskId);
1339
+ return { task: task ?? void 0 };
1340
+ },
1341
+ getTaskResult: async (_params, extra) => {
1342
+ return await extra.taskStore.getTaskResult(extra.taskId);
1253
1343
  }
1254
1344
  }
1255
1345
  );
@@ -1451,10 +1541,10 @@ var init_server = __esm({
1451
1541
  const transport = new StreamableHTTPServerTransport({
1452
1542
  sessionIdGenerator: () => randomUUID()
1453
1543
  });
1454
- const server = new McpServer({
1455
- name: "agentic-relay",
1456
- version: "0.10.1"
1457
- });
1544
+ const server = new McpServer(
1545
+ { name: "agentic-relay", version: "0.12.0" },
1546
+ createMcpServerOptions()
1547
+ );
1458
1548
  this.registerTools(server);
1459
1549
  transport.onclose = () => {
1460
1550
  const sid2 = transport.sessionId;
@@ -4405,7 +4495,7 @@ function createVersionCommand(registry2) {
4405
4495
  description: "Show relay and backend versions"
4406
4496
  },
4407
4497
  async run() {
4408
- const relayVersion = "0.10.1";
4498
+ const relayVersion = "0.12.0";
4409
4499
  console.log(`agentic-relay v${relayVersion}`);
4410
4500
  console.log("");
4411
4501
  console.log("Backends:");
@@ -4755,7 +4845,7 @@ void configManager.getConfig().then((config) => {
4755
4845
  var main = defineCommand10({
4756
4846
  meta: {
4757
4847
  name: "relay",
4758
- version: "0.10.1",
4848
+ version: "0.12.0",
4759
4849
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
4760
4850
  },
4761
4851
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.10.1",
3
+ "version": "0.12.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",