@rk0429/agentic-relay 0.11.0 → 0.12.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.
Files changed (2) hide show
  1. package/dist/relay.mjs +219 -113
  2. package/package.json +1 -1
package/dist/relay.mjs CHANGED
@@ -658,27 +658,45 @@ var init_spawn_agent = __esm({
658
658
  init_recursion_guard();
659
659
  init_logger();
660
660
  spawnAgentInputSchema = z2.object({
661
- backend: z2.enum(["claude", "codex", "gemini"]),
662
- prompt: z2.string(),
663
- agent: z2.string().optional(),
664
- systemPrompt: z2.string().optional(),
665
- resumeSessionId: z2.string().optional(),
666
- model: z2.string().optional(),
667
- maxTurns: z2.number().optional(),
661
+ backend: z2.enum(["claude", "codex", "gemini"]).describe(
662
+ "Required fallback backend. Overridden by automatic selection when BackendSelector is active (priority: preferredBackend > agentType mapping > taskType mapping > default)."
663
+ ),
664
+ prompt: z2.string().describe(
665
+ "The task instruction for the sub-agent. Be specific and include all necessary context for autonomous execution."
666
+ ),
667
+ agent: z2.string().optional().describe(
668
+ "Agent type identifier (e.g., 'coder', 'researcher', 'documenter', 'software-engineer'). Affects backend auto-selection: coder/researcher/software-engineer/devops-engineer\u2192codex, documenter\u2192claude. Also used to resolve agentDefinition files from .agents/agents/<agent>.md."
669
+ ),
670
+ systemPrompt: z2.string().optional().describe(
671
+ "Custom system prompt prepended to the agent's context. Use agentDefinition instead when a .md definition file exists."
672
+ ),
673
+ resumeSessionId: z2.string().optional().describe(
674
+ "Relay session ID to resume. Enables multi-turn continuation of a previous agent execution."
675
+ ),
676
+ model: z2.string().optional().describe(
677
+ "Model to use (e.g., 'opus', 'sonnet', 'o3'). Passed to the backend CLI. Model availability varies by backend."
678
+ ),
679
+ maxTurns: z2.number().optional().describe(
680
+ "Maximum agentic turns (tool-use cycles). Limits execution depth to prevent runaway agents."
681
+ ),
668
682
  skillContext: z2.object({
669
683
  skillPath: z2.string().describe("Path to the skill directory (e.g., '.agents/skills/software-engineer/')"),
670
- subskill: z2.string().optional().describe("Specific subskill to activate")
671
- }).optional().describe("Skill context to inject into the subagent's system prompt"),
684
+ subskill: z2.string().optional().describe("Specific subskill to activate within the skill")
685
+ }).optional().describe("Skill context to inject into the sub-agent's system prompt. Loads SKILL.md/SUBSKILL.md content."),
672
686
  agentDefinition: z2.object({
673
- definitionPath: z2.string().describe("Path to the agent definition file (e.g., '.claude/agents/software-engineer.md')")
674
- }).optional().describe("Agent definition file to inject into the sub-agent's system prompt"),
675
- preferredBackend: z2.enum(["claude", "codex", "gemini"]).optional().describe("Preferred backend override. Takes priority over automatic selection based on agent/task type."),
676
- taskType: z2.enum(["code-writing", "code-review", "document-writing", "document-review", "research", "mixed"]).optional().describe("Task type hint for automatic backend selection when preferredBackend is not specified. code-writing\u2192codex, code-review\u2192claude, document-writing\u2192claude, document-review\u2192codex, research\u2192codex, mixed\u2192claude."),
687
+ definitionPath: z2.string().describe("Path to the agent definition .md file (e.g., '.agents/agents/coder.md')")
688
+ }).optional().describe("Agent definition file to inject into the sub-agent's system prompt as <agent-definition> context."),
689
+ preferredBackend: z2.enum(["claude", "codex", "gemini"]).optional().describe(
690
+ "Preferred backend override. Takes highest priority in backend selection. Use when the automatic agentType/taskType mapping does not match your needs."
691
+ ),
692
+ taskType: z2.enum(["code-writing", "code-review", "document-writing", "document-review", "research", "mixed"]).optional().describe(
693
+ "Task type hint for automatic backend selection (priority 3, after preferredBackend and agentType). Mapping: code-writing\u2192codex, code-review\u2192claude, document-writing\u2192claude, document-review\u2192codex, research\u2192codex, mixed\u2192claude."
694
+ ),
677
695
  timeoutMs: z2.number().optional().describe("Timeout in milliseconds for agent execution. Default: no timeout."),
678
696
  taskInstructionPath: z2.string().optional().describe(
679
697
  "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."
680
698
  ),
681
- label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results")
699
+ label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results and logs.")
682
700
  });
683
701
  }
684
702
  });
@@ -994,7 +1012,10 @@ var init_backend_selector = __esm({
994
1012
  "skill-developer": "codex",
995
1013
  "security-auditor": "codex",
996
1014
  "analytics-engineer": "codex",
997
- "payments-billing": "codex"
1015
+ "payments-billing": "codex",
1016
+ "coder": "codex",
1017
+ "researcher": "codex",
1018
+ "documenter": "claude"
998
1019
  };
999
1020
  TASK_TYPE_TO_BACKEND_MAP = {
1000
1021
  "code-writing": "codex",
@@ -1047,6 +1068,24 @@ var init_backend_selector = __esm({
1047
1068
  }
1048
1069
  });
1049
1070
 
1071
+ // src/types/mcp.ts
1072
+ var DEFAULT_TASK_TTL, DEFAULT_POLL_INTERVAL;
1073
+ var init_mcp = __esm({
1074
+ "src/types/mcp.ts"() {
1075
+ "use strict";
1076
+ DEFAULT_TASK_TTL = 5 * 60 * 1e3;
1077
+ DEFAULT_POLL_INTERVAL = 3e3;
1078
+ }
1079
+ });
1080
+
1081
+ // src/types/index.ts
1082
+ var init_types = __esm({
1083
+ "src/types/index.ts"() {
1084
+ "use strict";
1085
+ init_mcp();
1086
+ }
1087
+ });
1088
+
1050
1089
  // src/mcp-server/response-formatter.ts
1051
1090
  function formatSpawnAgentResponse(result) {
1052
1091
  const isError = result.exitCode !== 0;
@@ -1119,9 +1158,20 @@ __export(server_exports, {
1119
1158
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1120
1159
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1121
1160
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1161
+ import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
1162
+ import { InMemoryTaskMessageQueue } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
1122
1163
  import { createServer } from "http";
1123
1164
  import { randomUUID } from "crypto";
1124
1165
  import { z as z5 } from "zod";
1166
+ function createMcpServerOptions() {
1167
+ const taskStore = new InMemoryTaskStore();
1168
+ return {
1169
+ capabilities: { tasks: { requests: { tools: { call: {} } } } },
1170
+ taskStore,
1171
+ taskMessageQueue: new InMemoryTaskMessageQueue(),
1172
+ defaultTaskPollInterval: DEFAULT_POLL_INTERVAL
1173
+ };
1174
+ }
1125
1175
  var spawnAgentsParallelInputShape, MAX_CHILD_HTTP_SESSIONS, RelayMCPServer;
1126
1176
  var init_server = __esm({
1127
1177
  "src/mcp-server/server.ts"() {
@@ -1134,6 +1184,7 @@ var init_server = __esm({
1134
1184
  init_list_available_backends();
1135
1185
  init_backend_selector();
1136
1186
  init_logger();
1187
+ init_types();
1137
1188
  init_response_formatter();
1138
1189
  spawnAgentsParallelInputShape = {
1139
1190
  agents: z5.array(spawnAgentInputSchema).min(1).max(10).describe(
@@ -1149,10 +1200,10 @@ var init_server = __esm({
1149
1200
  this.contextMonitor = contextMonitor2;
1150
1201
  this.guard = new RecursionGuard(guardConfig);
1151
1202
  this.backendSelector = new BackendSelector();
1152
- this.server = new McpServer({
1153
- name: "agentic-relay",
1154
- version: "0.11.0"
1155
- });
1203
+ this.server = new McpServer(
1204
+ { name: "agentic-relay", version: "0.12.1" },
1205
+ createMcpServerOptions()
1206
+ );
1156
1207
  this.registerTools(this.server);
1157
1208
  }
1158
1209
  server;
@@ -1168,94 +1219,150 @@ var init_server = __esm({
1168
1219
  get childHttpServer() {
1169
1220
  return this._childHttpServer;
1170
1221
  }
1171
- registerTools(server) {
1172
- server.tool(
1173
- "spawn_agent",
1174
- "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.",
1175
- spawnAgentInputSchema.shape,
1176
- async (params, extra) => {
1222
+ /**
1223
+ * Run spawn_agent in the background and store the result in taskStore.
1224
+ * Fire-and-forget: errors are caught and stored as failed task results.
1225
+ */
1226
+ runSpawnAgentInBackground(taskId, params, extra) {
1227
+ const run = async () => {
1228
+ try {
1229
+ const result = await executeSpawnAgent(
1230
+ params,
1231
+ this.registry,
1232
+ this.sessionManager,
1233
+ this.guard,
1234
+ this.hooksEngine,
1235
+ this.contextMonitor,
1236
+ this.backendSelector,
1237
+ this._childHttpUrl
1238
+ );
1239
+ const { text, isError } = formatSpawnAgentResponse(result);
1240
+ const callToolResult = {
1241
+ content: [{ type: "text", text }],
1242
+ isError
1243
+ };
1244
+ await extra.taskStore.storeTaskResult(
1245
+ taskId,
1246
+ "completed",
1247
+ callToolResult
1248
+ );
1249
+ } catch (outerError) {
1177
1250
  try {
1178
- const onProgress = (progress) => {
1179
- const progressToken = params._meta ? params._meta.progressToken : void 0;
1180
- if (progressToken !== void 0) {
1181
- void extra.sendNotification({
1182
- method: "notifications/progress",
1183
- params: {
1184
- progressToken,
1185
- progress: progress.percent ?? 0,
1186
- total: 100,
1187
- message: progress.stage
1188
- }
1189
- });
1251
+ const message = outerError instanceof Error ? outerError.message : String(outerError);
1252
+ await extra.taskStore.storeTaskResult(
1253
+ taskId,
1254
+ "failed",
1255
+ {
1256
+ content: [{ type: "text", text: `Error: ${message}` }],
1257
+ isError: true
1190
1258
  }
1191
- };
1192
- const result = await executeSpawnAgent(
1193
- params,
1194
- this.registry,
1195
- this.sessionManager,
1196
- this.guard,
1197
- this.hooksEngine,
1198
- this.contextMonitor,
1199
- this.backendSelector,
1200
- this._childHttpUrl,
1201
- onProgress
1202
1259
  );
1203
- const { text, isError } = formatSpawnAgentResponse(result);
1204
- return {
1205
- content: [{ type: "text", text }],
1206
- isError
1207
- };
1208
- } catch (error) {
1209
- const message = error instanceof Error ? error.message : String(error);
1210
- return {
1211
- content: [{ type: "text", text: `Error: ${message}` }],
1212
- isError: true
1213
- };
1260
+ } catch (storeError) {
1261
+ logger.error(
1262
+ `Failed to store task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
1263
+ );
1214
1264
  }
1215
1265
  }
1216
- );
1217
- server.tool(
1218
- "spawn_agents_parallel",
1219
- "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.",
1220
- spawnAgentsParallelInputShape,
1221
- async (params, extra) => {
1266
+ };
1267
+ void run();
1268
+ }
1269
+ /**
1270
+ * Run spawn_agents_parallel in the background and store the result in taskStore.
1271
+ * Fire-and-forget: errors are caught and stored as failed task results.
1272
+ */
1273
+ runSpawnAgentsParallelInBackground(taskId, agents, extra) {
1274
+ const run = async () => {
1275
+ try {
1276
+ const result = await executeSpawnAgentsParallel(
1277
+ agents,
1278
+ this.registry,
1279
+ this.sessionManager,
1280
+ this.guard,
1281
+ this.hooksEngine,
1282
+ this.contextMonitor,
1283
+ this.backendSelector,
1284
+ this._childHttpUrl
1285
+ );
1286
+ const { text, isError } = formatParallelResponse(result);
1287
+ const callToolResult = {
1288
+ content: [{ type: "text", text }],
1289
+ isError
1290
+ };
1291
+ await extra.taskStore.storeTaskResult(
1292
+ taskId,
1293
+ "completed",
1294
+ callToolResult
1295
+ );
1296
+ } catch (outerError) {
1222
1297
  try {
1223
- const onProgress = (progress) => {
1224
- const progressToken = params._meta ? params._meta.progressToken : void 0;
1225
- if (progressToken !== void 0) {
1226
- void extra.sendNotification({
1227
- method: "notifications/progress",
1228
- params: {
1229
- progressToken,
1230
- progress: progress.percent ?? 0,
1231
- total: 100,
1232
- message: progress.stage
1233
- }
1234
- });
1298
+ const message = outerError instanceof Error ? outerError.message : String(outerError);
1299
+ await extra.taskStore.storeTaskResult(
1300
+ taskId,
1301
+ "failed",
1302
+ {
1303
+ content: [{ type: "text", text: `Error: ${message}` }],
1304
+ isError: true
1235
1305
  }
1236
- };
1237
- const result = await executeSpawnAgentsParallel(
1238
- params.agents,
1239
- this.registry,
1240
- this.sessionManager,
1241
- this.guard,
1242
- this.hooksEngine,
1243
- this.contextMonitor,
1244
- this.backendSelector,
1245
- this._childHttpUrl,
1246
- onProgress
1247
1306
  );
1248
- const { text, isError } = formatParallelResponse(result);
1249
- return {
1250
- content: [{ type: "text", text }],
1251
- isError
1252
- };
1253
- } catch (error) {
1254
- const message = error instanceof Error ? error.message : String(error);
1255
- return {
1256
- content: [{ type: "text", text: `Error: ${message}` }],
1257
- isError: true
1258
- };
1307
+ } catch (storeError) {
1308
+ logger.error(
1309
+ `Failed to store parallel task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
1310
+ );
1311
+ }
1312
+ }
1313
+ };
1314
+ void run();
1315
+ }
1316
+ registerTools(server) {
1317
+ server.experimental.tasks.registerToolTask(
1318
+ "spawn_agent",
1319
+ {
1320
+ description: "Spawn a sub-agent on a backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Backend is auto-selected by priority: preferredBackend > agentType mapping (coder/researcher\u2192codex, documenter\u2192claude) > taskType mapping > default (claude). Use 'agentDefinition' to inject an agent .md file, 'skillContext' for a SKILL.md, or 'systemPrompt' for custom instructions.",
1321
+ inputSchema: spawnAgentInputSchema.shape,
1322
+ execution: { taskSupport: "optional" }
1323
+ },
1324
+ {
1325
+ createTask: async (params, extra) => {
1326
+ const task = await extra.taskStore.createTask({
1327
+ ttl: DEFAULT_TASK_TTL,
1328
+ pollInterval: DEFAULT_POLL_INTERVAL
1329
+ });
1330
+ this.runSpawnAgentInBackground(task.taskId, params, extra);
1331
+ return { task };
1332
+ },
1333
+ getTask: async (_params, extra) => {
1334
+ return await extra.taskStore.getTask(extra.taskId);
1335
+ },
1336
+ getTaskResult: async (_params, extra) => {
1337
+ return await extra.taskStore.getTaskResult(
1338
+ extra.taskId
1339
+ );
1340
+ }
1341
+ }
1342
+ );
1343
+ server.experimental.tasks.registerToolTask(
1344
+ "spawn_agents_parallel",
1345
+ {
1346
+ description: "Spawn multiple sub-agents in parallel across available backends. Use this when you have 2+ independent tasks that can execute concurrently. Each agent entry accepts the same parameters as spawn_agent. Results are returned as an array with per-agent status (success/failure). RecursionGuard batch pre-validation ensures the batch fits within call limits. Failed results include 'originalInput' for retry via retry_failed_agents.",
1347
+ inputSchema: spawnAgentsParallelInputShape,
1348
+ execution: { taskSupport: "optional" }
1349
+ },
1350
+ {
1351
+ createTask: async (params, extra) => {
1352
+ const task = await extra.taskStore.createTask({
1353
+ ttl: DEFAULT_TASK_TTL,
1354
+ pollInterval: DEFAULT_POLL_INTERVAL
1355
+ });
1356
+ this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra);
1357
+ return { task };
1358
+ },
1359
+ getTask: async (_params, extra) => {
1360
+ return await extra.taskStore.getTask(extra.taskId);
1361
+ },
1362
+ getTaskResult: async (_params, extra) => {
1363
+ return await extra.taskStore.getTaskResult(
1364
+ extra.taskId
1365
+ );
1259
1366
  }
1260
1367
  }
1261
1368
  );
@@ -1312,8 +1419,8 @@ var init_server = __esm({
1312
1419
  "list_sessions",
1313
1420
  "List relay sessions, optionally filtered by backend.",
1314
1421
  {
1315
- backend: z5.enum(["claude", "codex", "gemini"]).optional(),
1316
- limit: z5.number().optional()
1422
+ backend: z5.enum(["claude", "codex", "gemini"]).optional().describe("Filter sessions by backend type."),
1423
+ limit: z5.number().optional().describe("Maximum number of sessions to return. Default: 10.")
1317
1424
  },
1318
1425
  async (params) => {
1319
1426
  try {
@@ -1342,7 +1449,7 @@ var init_server = __esm({
1342
1449
  "get_context_status",
1343
1450
  "Get the context usage status of a relay session. Returns usage data from ContextMonitor when available, otherwise estimated values.",
1344
1451
  {
1345
- sessionId: z5.string()
1452
+ sessionId: z5.string().describe("Relay session ID to query context usage for.")
1346
1453
  },
1347
1454
  async (params) => {
1348
1455
  try {
@@ -1457,10 +1564,10 @@ var init_server = __esm({
1457
1564
  const transport = new StreamableHTTPServerTransport({
1458
1565
  sessionIdGenerator: () => randomUUID()
1459
1566
  });
1460
- const server = new McpServer({
1461
- name: "agentic-relay",
1462
- version: "0.11.0"
1463
- });
1567
+ const server = new McpServer(
1568
+ { name: "agentic-relay", version: "0.12.1" },
1569
+ createMcpServerOptions()
1570
+ );
1464
1571
  this.registerTools(server);
1465
1572
  transport.onclose = () => {
1466
1573
  const sid2 = transport.sessionId;
@@ -3330,7 +3437,8 @@ var HooksEngine = class _HooksEngine {
3330
3437
  const message = error instanceof Error ? error.message : String(error);
3331
3438
  if (strategy === "abort") {
3332
3439
  throw new Error(
3333
- `Hook "${def.command}" failed (abort): ${message}`
3440
+ `Hook "${def.command}" failed (abort): ${message}`,
3441
+ { cause: error }
3334
3442
  );
3335
3443
  }
3336
3444
  if (strategy === "warn") {
@@ -3377,7 +3485,8 @@ var HooksEngine = class _HooksEngine {
3377
3485
  const message = error instanceof Error ? error.message : String(error);
3378
3486
  if (strategy === "abort") {
3379
3487
  throw new Error(
3380
- `Hook "${def.command}" failed (abort): ${message}`
3488
+ `Hook "${def.command}" failed (abort): ${message}`,
3489
+ { cause: error }
3381
3490
  );
3382
3491
  }
3383
3492
  if (strategy === "warn") {
@@ -3769,7 +3878,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
3769
3878
  try {
3770
3879
  if (flags.prompt) {
3771
3880
  logger.debug(`Executing prompt on ${backendId}`);
3772
- let nativeSessionId;
3773
3881
  if (adapter.executeStreaming) {
3774
3882
  for await (const event of adapter.executeStreaming(flags)) {
3775
3883
  switch (event.type) {
@@ -3810,7 +3918,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
3810
3918
  }
3811
3919
  case "done":
3812
3920
  process.exitCode = event.result.exitCode;
3813
- nativeSessionId = event.nativeSessionId;
3814
3921
  if (event.nativeSessionId && sessionManager2 && relaySessionId) {
3815
3922
  try {
3816
3923
  await sessionManager2.update(relaySessionId, {
@@ -3827,7 +3934,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
3827
3934
  if (result.stdout) process.stdout.write(result.stdout);
3828
3935
  if (result.stderr) process.stderr.write(result.stderr);
3829
3936
  process.exitCode = result.exitCode;
3830
- nativeSessionId = result.nativeSessionId;
3831
3937
  if (result.nativeSessionId && sessionManager2 && relaySessionId) {
3832
3938
  try {
3833
3939
  await sessionManager2.update(relaySessionId, {
@@ -4411,7 +4517,7 @@ function createVersionCommand(registry2) {
4411
4517
  description: "Show relay and backend versions"
4412
4518
  },
4413
4519
  async run() {
4414
- const relayVersion = "0.11.0";
4520
+ const relayVersion = "0.12.1";
4415
4521
  console.log(`agentic-relay v${relayVersion}`);
4416
4522
  console.log("");
4417
4523
  console.log("Backends:");
@@ -4761,7 +4867,7 @@ void configManager.getConfig().then((config) => {
4761
4867
  var main = defineCommand10({
4762
4868
  meta: {
4763
4869
  name: "relay",
4764
- version: "0.11.0",
4870
+ version: "0.12.1",
4765
4871
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
4766
4872
  },
4767
4873
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
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",