agent-relay-server 0.7.2 → 0.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -38,6 +38,7 @@ Usage:
38
38
  agent-relay message <target> <body> [options]
39
39
  agent-relay /pair <target|accept|reject|send|status> [...]
40
40
  agent-relay /message <target> <body>
41
+ agent-relay /reply <messageId> <body>
41
42
  agent-relay /send-claimable <target> <body>
42
43
  agent-relay /disconnect [PAIR_ID]
43
44
  agent-relay /status
@@ -52,6 +53,7 @@ Pair examples:
52
53
  agent-relay pair accept PAIR_ID --agent AGENT_ID
53
54
  agent-relay pair send PAIR_ID --from AGENT_ID --body "What do you see?"
54
55
  agent-relay /message codex "Can you look at that failing action?"
56
+ agent-relay /reply 206 "Sounds good, I'll take a look"
55
57
  agent-relay /send-claimable tag:backend "Please claim and fix the failing API test"
56
58
  agent-relay /disconnect
57
59
  agent-relay /status
@@ -123,6 +125,10 @@ export async function handleCli(args: string[]): Promise<"start" | "handled"> {
123
125
  await handleMessageCommand(args.slice(1), { claimable: command === "/send-claimable" });
124
126
  return "handled";
125
127
  }
128
+ if (command === "reply" || command === "/reply") {
129
+ await handleReplyCommand(args.slice(1));
130
+ return "handled";
131
+ }
126
132
  if (command === "/status" || command === "status") {
127
133
  await handleStatusCommand(args.slice(1));
128
134
  return "handled";
@@ -499,6 +505,42 @@ async function handleMessageCommand(args: string[], defaults: { claimable?: bool
499
505
  else console.log(`${claimable ? "Claimable message" : "Message"} sent: ${(message as any).id} -> ${target}`);
500
506
  }
501
507
 
508
+ async function handleReplyCommand(args: string[]): Promise<void> {
509
+ const msgIdRaw = args[0];
510
+ if (!msgIdRaw || msgIdRaw.startsWith("--")) {
511
+ throw new Error("Usage: agent-relay /reply <messageId> <body> [--from AGENT_ID] [--subject TEXT] [--json]");
512
+ }
513
+ const replyTo = Number.parseInt(msgIdRaw, 10);
514
+ if (!Number.isFinite(replyTo) || replyTo <= 0) throw new Error("messageId must be a positive integer");
515
+
516
+ let from = await detectAgentId();
517
+ let subject: string | undefined;
518
+ let json = false;
519
+ const bodyParts: string[] = [];
520
+
521
+ for (let i = 1; i < args.length; i++) {
522
+ const arg = args[i];
523
+ if (arg === "--from" && i + 1 < args.length) from = args[++i];
524
+ else if (arg === "--subject" && i + 1 < args.length) subject = args[++i];
525
+ else if (arg === "--json") json = true;
526
+ else bodyParts.push(arg!);
527
+ }
528
+
529
+ const body = bodyParts.join(" ").trim();
530
+ if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
531
+ if (!body) throw new Error("Reply body required.");
532
+
533
+ const message = await apiRequest("POST", "/api/messages", {
534
+ from,
535
+ body,
536
+ subject,
537
+ replyTo,
538
+ });
539
+ const msg = message as any;
540
+ if (json) console.log(JSON.stringify(msg, null, 2));
541
+ else console.log(`Reply sent: ${msg.id} -> ${msg.to} (reply to #${replyTo})`);
542
+ }
543
+
502
544
  async function handleStatusCommand(args: string[]): Promise<void> {
503
545
  let agentId = await detectAgentId();
504
546
  let json = false;
package/src/db.ts CHANGED
@@ -997,7 +997,7 @@ function upsertChannelForAgent(agent: AgentCard): void {
997
997
  $now: now,
998
998
  });
999
999
 
1000
- const defaultTarget = routeTargetFromLegacyTarget(stringValue(agent.meta?.target));
1000
+ const defaultTarget = routeTargetFromLegacyTarget(stringValue(agent.meta?.target) ?? stringValue(agent.meta?.configuredTarget));
1001
1001
  if (defaultTarget) {
1002
1002
  upsertChannelBinding({
1003
1003
  channelId,
package/src/routes.ts CHANGED
@@ -335,7 +335,7 @@ function normalizeMessageInput(body: unknown): SendMessageInput {
335
335
 
336
336
  const input: SendMessageInput = {
337
337
  from: cleanString(body.from, "from", { required: true, max: 200 })!,
338
- to: cleanString(body.to, "to", { required: true, max: 200 })!,
338
+ to: cleanString(body.to, "to", { max: 200 }) ?? "",
339
339
  body: cleanString(body.body, "body", { required: true, max: MAX_BODY_BYTES })!,
340
340
  kind: kind as SendMessageInput["kind"] | undefined,
341
341
  replyTo: cleanPositiveId(body.replyTo, "replyTo"),
@@ -355,6 +355,27 @@ function normalizeMessageInput(body: unknown): SendMessageInput {
355
355
  return input;
356
356
  }
357
357
 
358
+ function applyReplyRouting(input: SendMessageInput): void {
359
+ if (input.to || !input.replyTo) return;
360
+ const parent = getMessage(input.replyTo);
361
+ if (!parent) return;
362
+ input.to = parent.from;
363
+ if (!input.channel && parent.channel) input.channel = parent.channel;
364
+ const parentPayload = parent.payload ?? {};
365
+ if (parentPayload.schema === "agent-relay.channel.v1" || parentPayload.conversation) {
366
+ const replyContext: Record<string, unknown> = {};
367
+ if (parent.channel) replyContext.channelId = parent.channel;
368
+ if (parentPayload.conversation && typeof parentPayload.conversation === "object") {
369
+ replyContext.conversationId = (parentPayload.conversation as Record<string, unknown>).id;
370
+ }
371
+ if (parentPayload.event && typeof parentPayload.event === "object") {
372
+ replyContext.parentEventId = (parentPayload.event as Record<string, unknown>).id;
373
+ }
374
+ if (parentPayload.source) replyContext.source = parentPayload.source;
375
+ input.payload = { ...input.payload, replyContext };
376
+ }
377
+ }
378
+
358
379
  function normalizeChannelBindingInput(body: unknown): {
359
380
  channelId: string;
360
381
  conversationId?: string;
@@ -1326,6 +1347,8 @@ const postMessage: Handler = async (req) => {
1326
1347
  if (!input.idempotencyKey) {
1327
1348
  input.idempotencyKey = cleanString(req.headers.get("Idempotency-Key") ?? undefined, "idempotencyKey", { max: 240 });
1328
1349
  }
1350
+ applyReplyRouting(input);
1351
+ if (!input.to) return error("to is required (or provide replyTo to auto-route)");
1329
1352
  const result = sendMessageWithResult(input);
1330
1353
  if (result.created) {
1331
1354
  emitNewMessage(result.message);