agent-relay-server 0.32.3 → 0.33.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 +2 -2
- package/public/assets/{activity-DT1JGHnp.js → activity-B0_uE6Yh.js} +2 -2
- package/public/assets/{activity-DT1JGHnp.js.map → activity-B0_uE6Yh.js.map} +1 -1
- package/public/assets/{agent-profiles-CrMemMkZ.js → agent-profiles-Rwxrcf9F.js} +2 -2
- package/public/assets/{agent-profiles-CrMemMkZ.js.map → agent-profiles-Rwxrcf9F.js.map} +1 -1
- package/public/assets/{agents-Bl-rrgOy.js → agents-Dp1EXJc8.js} +2 -2
- package/public/assets/{agents-Bl-rrgOy.js.map → agents-Dp1EXJc8.js.map} +1 -1
- package/public/assets/{analytics-a663ak56.js → analytics-D5OT5ajj.js} +2 -2
- package/public/assets/{analytics-a663ak56.js.map → analytics-D5OT5ajj.js.map} +1 -1
- package/public/assets/automation-Dm6rXNxK.js +2 -0
- package/public/assets/{automation-CiaLThdO.js.map → automation-Dm6rXNxK.js.map} +1 -1
- package/public/assets/{branch-state-badge-D4ur3m3_.js → branch-state-badge-FX5Yww2s.js} +2 -2
- package/public/assets/{branch-state-badge-D4ur3m3_.js.map → branch-state-badge-FX5Yww2s.js.map} +1 -1
- package/public/assets/{channels-o9KLTHoK.js → channels--rdAiX17.js} +2 -2
- package/public/assets/{channels-o9KLTHoK.js.map → channels--rdAiX17.js.map} +1 -1
- package/public/assets/chat-JZAEDGfX.js +2 -0
- package/public/assets/chat-JZAEDGfX.js.map +1 -0
- package/public/assets/{connectors-CdC806mA.js → connectors-Bx4gzvNf.js} +2 -2
- package/public/assets/{connectors-CdC806mA.js.map → connectors-Bx4gzvNf.js.map} +1 -1
- package/public/assets/display-Bebqs1qu.js +3 -0
- package/public/assets/display-Bebqs1qu.js.map +1 -0
- package/public/assets/{formatted-body-impl-Ca74OAEH.js → formatted-body-impl-CVq4qHix.js} +2 -2
- package/public/assets/{formatted-body-impl-Ca74OAEH.js.map → formatted-body-impl-CVq4qHix.js.map} +1 -1
- package/public/assets/{index-C_33ymaw.js → index-BHRtR4q7.js} +8 -8
- package/public/assets/{index-C_33ymaw.js.map → index-BHRtR4q7.js.map} +1 -1
- package/public/assets/{insights-ClI68s39.js → insights-yJFgCa3o.js} +2 -2
- package/public/assets/{insights-ClI68s39.js.map → insights-yJFgCa3o.js.map} +1 -1
- package/public/assets/{integrations-1nxMizDY.js → integrations-k1HIONjo.js} +2 -2
- package/public/assets/{integrations-1nxMizDY.js.map → integrations-k1HIONjo.js.map} +1 -1
- package/public/assets/maintenance-CsoOFBXx.js +2 -0
- package/public/assets/{maintenance-DiFNzNPN.js.map → maintenance-CsoOFBXx.js.map} +1 -1
- package/public/assets/{managed-agents-Do3dKvfj.js → managed-agents-Q3HuVjGg.js} +2 -2
- package/public/assets/{managed-agents-Do3dKvfj.js.map → managed-agents-Q3HuVjGg.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CLA0J255.js → markdown-preview-impl-CnsMjrnu.js} +2 -2
- package/public/assets/{markdown-preview-impl-CLA0J255.js.map → markdown-preview-impl-CnsMjrnu.js.map} +1 -1
- package/public/assets/{memory-IjwqFzBd.js → memory-D3-K5eJS.js} +2 -2
- package/public/assets/{memory-IjwqFzBd.js.map → memory-D3-K5eJS.js.map} +1 -1
- package/public/assets/{messages-DjvWqHyn.js → messages-B4lCP5rS.js} +2 -2
- package/public/assets/{messages-DjvWqHyn.js.map → messages-B4lCP5rS.js.map} +1 -1
- package/public/assets/{orchestrators-D2IqDxDT.js → orchestrators-CRoZtLeQ.js} +2 -2
- package/public/assets/{orchestrators-D2IqDxDT.js.map → orchestrators-CRoZtLeQ.js.map} +1 -1
- package/public/assets/{overview-DKC3TbAh.js → overview-CxCU2fOF.js} +2 -2
- package/public/assets/{overview-DKC3TbAh.js.map → overview-CxCU2fOF.js.map} +1 -1
- package/public/assets/pairs-unqjPlmq.js +2 -0
- package/public/assets/{pairs-WpKCPE1n.js.map → pairs-unqjPlmq.js.map} +1 -1
- package/public/assets/{security-BF7ZtPQe.js → security-B7HhSYNy.js} +2 -2
- package/public/assets/{security-BF7ZtPQe.js.map → security-B7HhSYNy.js.map} +1 -1
- package/public/assets/{settings-CQnjrTa-.js → settings-B9NDhsAb.js} +2 -2
- package/public/assets/{settings-CQnjrTa-.js.map → settings-B9NDhsAb.js.map} +1 -1
- package/public/assets/store-DiSzYHj9.js +9 -0
- package/public/assets/{store-C9VcSo05.js.map → store-DiSzYHj9.js.map} +1 -1
- package/public/assets/{tasks-CbN_GSSb.js → tasks-CIQolvNm.js} +2 -2
- package/public/assets/{tasks-CbN_GSSb.js.map → tasks-CIQolvNm.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BJRohThT.js → terminal-viewer-impl-DCifVqFR.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BJRohThT.js.map → terminal-viewer-impl-DCifVqFR.js.map} +1 -1
- package/public/assets/{work-queue-C5xLBLmm.js → work-queue-Dr3c1V6O.js} +2 -2
- package/public/assets/{work-queue-C5xLBLmm.js.map → work-queue-Dr3c1V6O.js.map} +1 -1
- package/public/assets/{workspaces-D91H3wDX.js → workspaces-B1Jxop7h.js} +3 -3
- package/public/assets/{workspaces-D91H3wDX.js.map → workspaces-B1Jxop7h.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/src/adapter.ts +1 -1
- package/src/agent-lifecycle-events.ts +137 -0
- package/src/artifact-storage.ts +3 -5
- package/src/branch-landed.ts +38 -2
- package/src/cli/_shared.ts +80 -0
- package/src/cli/agent-detect.ts +188 -0
- package/src/cli/agent-meta.ts +95 -0
- package/src/cli/context-probe.ts +88 -0
- package/src/cli/daemon.ts +111 -0
- package/src/cli/dev.ts +173 -0
- package/src/cli/index.ts +361 -0
- package/src/cli/introspect.ts +73 -0
- package/src/cli/memory.ts +37 -0
- package/src/cli/message.ts +201 -0
- package/src/cli/orchestrator.ts +227 -0
- package/src/cli/pair.ts +125 -0
- package/src/cli/provider.ts +209 -0
- package/src/cli/recipe.ts +110 -0
- package/src/cli/reply.ts +141 -0
- package/src/cli/setup.ts +57 -0
- package/src/cli/steward.ts +59 -0
- package/src/cli/token.ts +81 -0
- package/src/cli/upgrade.ts +193 -0
- package/src/cli/workspace.ts +215 -0
- package/src/cli.ts +4 -2718
- package/src/config-store.ts +10 -6
- package/src/maintenance.ts +25 -21
- package/src/mcp-errors.ts +7 -0
- package/src/mcp.ts +34 -36
- package/src/routes/agents-spawn.ts +9 -1
- package/src/routes/agents.ts +5 -0
- package/src/routes/commands.ts +15 -0
- package/src/routes/workspaces.ts +13 -4
- package/src/spawn-targets.ts +159 -0
- package/src/utils.ts +16 -1
- package/src/workspace-actions.ts +7 -1
- package/src/workspace-merge.ts +12 -1
- package/public/assets/automation-CiaLThdO.js +0 -2
- package/public/assets/chat-5hvHZcAe.js +0 -2
- package/public/assets/chat-5hvHZcAe.js.map +0 -1
- package/public/assets/display-JI19Vc7L.js +0 -3
- package/public/assets/display-JI19Vc7L.js.map +0 -1
- package/public/assets/maintenance-DiFNzNPN.js +0 -2
- package/public/assets/pairs-WpKCPE1n.js +0 -2
- package/public/assets/store-C9VcSo05.js +0 -9
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Introspect command — auto-split from cli.ts (#294). Captures the agent's
|
|
2
|
+
// end-of-session self-view as a bounded, structured Insights observation (#185).
|
|
3
|
+
import { apiRequest, readStdin } from "./_shared";
|
|
4
|
+
import { detectAgentId } from "./agent-detect";
|
|
5
|
+
|
|
6
|
+
// Insights #185: capture the agent's end-of-session self-view as a bounded, structured
|
|
7
|
+
// artifact (epic #183, docs/self-improvement.md). Manual rail in v0 — the agent or
|
|
8
|
+
// operator runs this; the auto-trigger is chosen later on real data. The relay drops it
|
|
9
|
+
// (409) if Insights or the introspection feature is toggled off.
|
|
10
|
+
const INTROSPECT_FIELD_MAX = 600;
|
|
11
|
+
|
|
12
|
+
export async function handleIntrospectCommand(args: string[]): Promise<void> {
|
|
13
|
+
let from = await detectAgentId();
|
|
14
|
+
let project: string | undefined;
|
|
15
|
+
let sessionId: string | undefined;
|
|
16
|
+
let thin: string | undefined;
|
|
17
|
+
let workedAround: string | undefined;
|
|
18
|
+
let wouldHaveHelped: string | undefined;
|
|
19
|
+
let stdin = false;
|
|
20
|
+
let json = false;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
const arg = args[i];
|
|
24
|
+
if (arg === "--from" && i + 1 < args.length) from = args[++i];
|
|
25
|
+
else if (arg === "--project" && i + 1 < args.length) project = args[++i];
|
|
26
|
+
else if (arg === "--session" && i + 1 < args.length) sessionId = args[++i];
|
|
27
|
+
else if (arg === "--thin" && i + 1 < args.length) thin = args[++i];
|
|
28
|
+
else if (arg === "--worked-around" && i + 1 < args.length) workedAround = args[++i];
|
|
29
|
+
else if (arg === "--would-have-helped" && i + 1 < args.length) wouldHaveHelped = args[++i];
|
|
30
|
+
else if (arg === "--stdin") stdin = true;
|
|
31
|
+
else if (arg === "--json") json = true;
|
|
32
|
+
else throw new Error(`Unknown introspect option "${arg}"`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --stdin reads a JSON object { thin, workedAround, wouldHaveHelped } — lets agents
|
|
36
|
+
// pipe a file without shell-quoting three multi-line fields.
|
|
37
|
+
if (stdin) {
|
|
38
|
+
const raw = (await readStdin()).trim();
|
|
39
|
+
if (!raw) throw new Error("--stdin given but no input received.");
|
|
40
|
+
let parsed: unknown;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(raw);
|
|
43
|
+
} catch {
|
|
44
|
+
throw new Error('--stdin must be JSON: { "thin": "...", "workedAround": "...", "wouldHaveHelped": "..." }');
|
|
45
|
+
}
|
|
46
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--stdin JSON must be an object");
|
|
47
|
+
const obj = parsed as Record<string, unknown>;
|
|
48
|
+
if (typeof obj.thin === "string") thin = obj.thin;
|
|
49
|
+
if (typeof obj.workedAround === "string") workedAround = obj.workedAround;
|
|
50
|
+
if (typeof obj.wouldHaveHelped === "string") wouldHaveHelped = obj.wouldHaveHelped;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const clip = (v: string | undefined): string => (v ?? "").trim().slice(0, INTROSPECT_FIELD_MAX);
|
|
54
|
+
const value = { thin: clip(thin), workedAround: clip(workedAround), wouldHaveHelped: clip(wouldHaveHelped) };
|
|
55
|
+
if (!value.thin && !value.workedAround && !value.wouldHaveHelped) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Usage: agent-relay /introspect [--thin TEXT] [--worked-around TEXT] [--would-have-helped TEXT] [--stdin] [--from AGENT_ID]\n" +
|
|
58
|
+
"At least one field is required. The three fields: what context was thin, what you worked around, what would've helped.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
62
|
+
|
|
63
|
+
const observation = await apiRequest("POST", "/api/insights/observations", {
|
|
64
|
+
sessionId: sessionId || process.env.AGENT_RELAY_PROVIDER_SESSION_ID || `manual-${from}`,
|
|
65
|
+
project: project || process.env.AGENT_RELAY_PROJECT || process.cwd().split("/").filter(Boolean).at(-1) || process.cwd(),
|
|
66
|
+
agentId: from,
|
|
67
|
+
signal: "introspection",
|
|
68
|
+
source: "agent",
|
|
69
|
+
value,
|
|
70
|
+
});
|
|
71
|
+
if (json) console.log(JSON.stringify(observation, null, 2));
|
|
72
|
+
else console.log("Introspection recorded.");
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Memory command — auto-split from cli.ts (#294). Runs the memory-broker smoke test.
|
|
2
|
+
import { formatMemoryBrokerSmokeResult, runMemoryBrokerSmoke } from "../memory-broker-smoke";
|
|
3
|
+
|
|
4
|
+
export async function handleMemoryCommand(args: string[]): Promise<void> {
|
|
5
|
+
const section = args[0];
|
|
6
|
+
const action = args[1];
|
|
7
|
+
if (section !== "broker" || action !== "smoke") {
|
|
8
|
+
throw new Error("Usage: agent-relay memory broker smoke [--relay-url URL] [--token TOKEN] [--agent-id ID] [--scope SCOPE] [--tag TAG] [--no-cleanup] [--json]");
|
|
9
|
+
}
|
|
10
|
+
let baseUrl: string | undefined;
|
|
11
|
+
let token: string | undefined;
|
|
12
|
+
let agentId: string | undefined;
|
|
13
|
+
let scope: string | undefined;
|
|
14
|
+
let tag: string | undefined;
|
|
15
|
+
let cleanup = true;
|
|
16
|
+
let json = false;
|
|
17
|
+
for (let i = 2; i < args.length; i++) {
|
|
18
|
+
const arg = args[i];
|
|
19
|
+
if (arg === "--relay-url" && i + 1 < args.length) baseUrl = args[++i];
|
|
20
|
+
else if (arg === "--token" && i + 1 < args.length) token = args[++i];
|
|
21
|
+
else if (arg === "--agent-id" && i + 1 < args.length) agentId = args[++i];
|
|
22
|
+
else if (arg === "--scope" && i + 1 < args.length) scope = args[++i];
|
|
23
|
+
else if (arg === "--tag" && i + 1 < args.length) tag = args[++i];
|
|
24
|
+
else if (arg === "--no-cleanup") cleanup = false;
|
|
25
|
+
else if (arg === "--json") json = true;
|
|
26
|
+
else throw new Error(`Unknown memory broker smoke option "${arg}"`);
|
|
27
|
+
}
|
|
28
|
+
const result = await runMemoryBrokerSmoke({
|
|
29
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
30
|
+
...(token ? { token } : {}),
|
|
31
|
+
...(agentId ? { agentId } : {}),
|
|
32
|
+
...(scope ? { scope } : {}),
|
|
33
|
+
...(tag ? { tag } : {}),
|
|
34
|
+
cleanup,
|
|
35
|
+
});
|
|
36
|
+
console.log(json ? JSON.stringify(result, null, 2) : formatMemoryBrokerSmokeResult(result));
|
|
37
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Message commands — auto-split from cli.ts (#294). Send a message (or claimable
|
|
2
|
+
// task), fetch a full message, and add/remove reactions.
|
|
3
|
+
import { apiRequest } from "./_shared";
|
|
4
|
+
import { detectAgentId } from "./agent-detect";
|
|
5
|
+
|
|
6
|
+
export async function handleMessageCommand(args: string[], defaults: { claimable?: boolean } = {}): Promise<void> {
|
|
7
|
+
const target = args[0];
|
|
8
|
+
if (!target || target.startsWith("--")) {
|
|
9
|
+
throw new Error("Usage: agent-relay message <target> <body> [--from AGENT_ID] [--subject TEXT] [--channel NAME] [--reply-to ID] [--claimable]");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let from = await detectAgentId();
|
|
13
|
+
let subject: string | undefined;
|
|
14
|
+
let channel: string | undefined;
|
|
15
|
+
let replyTo: number | undefined;
|
|
16
|
+
let idempotencyKey: string | undefined;
|
|
17
|
+
let json = false;
|
|
18
|
+
let claimable = defaults.claimable ?? false;
|
|
19
|
+
const bodyParts: string[] = [];
|
|
20
|
+
|
|
21
|
+
for (let i = 1; i < args.length; i++) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
if (arg === "--from" && i + 1 < args.length) from = args[++i];
|
|
24
|
+
else if (arg === "--subject" && i + 1 < args.length) subject = args[++i];
|
|
25
|
+
else if (arg === "--channel" && i + 1 < args.length) channel = args[++i];
|
|
26
|
+
else if (arg === "--reply-to" && i + 1 < args.length) {
|
|
27
|
+
const parsed = Number.parseInt(args[++i]!, 10);
|
|
28
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error("--reply-to must be a positive message id");
|
|
29
|
+
replyTo = parsed;
|
|
30
|
+
} else if (arg === "--idempotency-key" && i + 1 < args.length) idempotencyKey = args[++i];
|
|
31
|
+
else if (arg === "--claimable") claimable = true;
|
|
32
|
+
else if (arg === "--json") json = true;
|
|
33
|
+
else bodyParts.push(arg!);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const body = bodyParts.join(" ").trim();
|
|
37
|
+
if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
38
|
+
if (!body) throw new Error("Message body required.");
|
|
39
|
+
|
|
40
|
+
const message = await apiRequest("POST", "/api/messages", {
|
|
41
|
+
from,
|
|
42
|
+
to: target,
|
|
43
|
+
kind: claimable ? "task" : "chat",
|
|
44
|
+
subject,
|
|
45
|
+
channel,
|
|
46
|
+
body,
|
|
47
|
+
replyTo,
|
|
48
|
+
claimable,
|
|
49
|
+
payload: claimable ? { title: subject || "Claimable task" } : undefined,
|
|
50
|
+
idempotencyKey,
|
|
51
|
+
});
|
|
52
|
+
if (json) console.log(JSON.stringify(message, null, 2));
|
|
53
|
+
else console.log(`${claimable ? "Claimable message" : "Message"} sent: ${(message as any).id} -> ${target}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function handleGetMessageCommand(args: string[]): Promise<void> {
|
|
57
|
+
const msgIdRaw = args[0];
|
|
58
|
+
if (!msgIdRaw || msgIdRaw.startsWith("--")) {
|
|
59
|
+
throw new Error("Usage: agent-relay get-message <messageId> [--json] [--body]");
|
|
60
|
+
}
|
|
61
|
+
const messageId = Number.parseInt(msgIdRaw, 10);
|
|
62
|
+
if (!Number.isFinite(messageId) || messageId <= 0) throw new Error("messageId must be a positive integer");
|
|
63
|
+
|
|
64
|
+
let json = false;
|
|
65
|
+
let bodyOnly = false;
|
|
66
|
+
for (let i = 1; i < args.length; i++) {
|
|
67
|
+
const arg = args[i];
|
|
68
|
+
if (arg === "--json") json = true;
|
|
69
|
+
else if (arg === "--body") bodyOnly = true;
|
|
70
|
+
else throw new Error(`Unknown get-message option "${arg}".`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [message, artifacts, thread] = await Promise.all([
|
|
74
|
+
apiRequest("GET", `/api/messages/${messageId}`),
|
|
75
|
+
apiRequest("GET", `/api/messages/${messageId}/artifacts`).catch(() => []),
|
|
76
|
+
apiRequest("GET", `/api/messages/${messageId}/thread`).catch(() => []),
|
|
77
|
+
]);
|
|
78
|
+
if (bodyOnly) {
|
|
79
|
+
console.log(String((message as any).body ?? ""));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const msg = message as any;
|
|
84
|
+
const attachments = Array.isArray(msg.payload?.attachments)
|
|
85
|
+
? msg.payload.attachments
|
|
86
|
+
: Array.isArray(msg.attachments)
|
|
87
|
+
? msg.attachments
|
|
88
|
+
: [];
|
|
89
|
+
const payload = {
|
|
90
|
+
id: msg.id,
|
|
91
|
+
from: msg.from,
|
|
92
|
+
to: msg.to,
|
|
93
|
+
kind: msg.kind,
|
|
94
|
+
body: msg.body,
|
|
95
|
+
subject: msg.subject,
|
|
96
|
+
channel: msg.channel,
|
|
97
|
+
replyToId: msg.replyTo,
|
|
98
|
+
threadId: msg.threadId,
|
|
99
|
+
createdAt: msg.createdAt,
|
|
100
|
+
payload: msg.payload,
|
|
101
|
+
meta: msg.meta,
|
|
102
|
+
attachments,
|
|
103
|
+
message,
|
|
104
|
+
artifacts,
|
|
105
|
+
thread,
|
|
106
|
+
replyTo: msg.replyTo ? (thread as any[]).find((item) => item.id === msg.replyTo) ?? null : null,
|
|
107
|
+
};
|
|
108
|
+
if (json) {
|
|
109
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(formatMessageDetails(payload));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function handleReactCommand(args: string[]): Promise<void> {
|
|
116
|
+
const msgIdRaw = args[0];
|
|
117
|
+
const emoji = args[1];
|
|
118
|
+
if (!msgIdRaw || msgIdRaw.startsWith("--") || !emoji || emoji.startsWith("--")) {
|
|
119
|
+
throw new Error("Usage: agent-relay /react <messageId> <emoji> [--from AGENT_ID] [--remove] [--json]");
|
|
120
|
+
}
|
|
121
|
+
const messageId = Number.parseInt(msgIdRaw, 10);
|
|
122
|
+
if (!Number.isFinite(messageId) || messageId <= 0) throw new Error("messageId must be a positive integer");
|
|
123
|
+
|
|
124
|
+
let actorId = await detectAgentId();
|
|
125
|
+
let action: "add" | "remove" = "add";
|
|
126
|
+
let json = false;
|
|
127
|
+
for (let i = 2; i < args.length; i++) {
|
|
128
|
+
const arg = args[i];
|
|
129
|
+
if (arg === "--from" && i + 1 < args.length) actorId = args[++i];
|
|
130
|
+
else if (arg === "--remove") action = "remove";
|
|
131
|
+
else if (arg === "--json") json = true;
|
|
132
|
+
else throw new Error(`Unknown react option "${arg}"`);
|
|
133
|
+
}
|
|
134
|
+
if (!actorId) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
135
|
+
|
|
136
|
+
const message = await apiRequest("POST", `/api/messages/${messageId}/reactions`, {
|
|
137
|
+
actorId,
|
|
138
|
+
emoji,
|
|
139
|
+
action,
|
|
140
|
+
});
|
|
141
|
+
if (json) console.log(JSON.stringify(message, null, 2));
|
|
142
|
+
else console.log(`${action === "remove" ? "Reaction removed" : "Reaction sent"}: ${emoji} on #${messageId}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatMessageDetails(payload: {
|
|
146
|
+
message: any;
|
|
147
|
+
artifacts: any;
|
|
148
|
+
thread: any;
|
|
149
|
+
replyTo: any;
|
|
150
|
+
}): string {
|
|
151
|
+
const message = payload.message;
|
|
152
|
+
const lines = [
|
|
153
|
+
`Message #${message.id}`,
|
|
154
|
+
`From: ${message.from}`,
|
|
155
|
+
`To: ${message.to}`,
|
|
156
|
+
`Kind: ${message.kind}`,
|
|
157
|
+
];
|
|
158
|
+
if (message.subject) lines.push(`Subject: ${message.subject}`);
|
|
159
|
+
if (message.channel) lines.push(`Channel: ${message.channel}`);
|
|
160
|
+
if (message.replyTo) lines.push(`Reply-to: #${message.replyTo}`);
|
|
161
|
+
if (message.threadId) lines.push(`Thread: #${message.threadId}`);
|
|
162
|
+
if (message.createdAt) lines.push(`Created: ${new Date(message.createdAt).toISOString()}`);
|
|
163
|
+
if (message.deliveryStatus) lines.push(`Delivery: ${message.deliveryStatus}`);
|
|
164
|
+
if (message.resolvedToAgent) lines.push(`Resolved-to: ${message.resolvedToAgent}`);
|
|
165
|
+
lines.push("", "Body:", String(message.body ?? ""));
|
|
166
|
+
|
|
167
|
+
if (message.payload && Object.keys(message.payload).length) {
|
|
168
|
+
lines.push("", "Payload:", JSON.stringify(message.payload, null, 2));
|
|
169
|
+
}
|
|
170
|
+
if (message.meta && Object.keys(message.meta).length) {
|
|
171
|
+
lines.push("", "Meta:", JSON.stringify(message.meta, null, 2));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const artifacts = Array.isArray(payload.artifacts) ? payload.artifacts : [];
|
|
175
|
+
if (artifacts.length) {
|
|
176
|
+
lines.push("", "Attachments:");
|
|
177
|
+
for (const artifact of artifacts) {
|
|
178
|
+
lines.push(`- ${artifact.id ?? artifact.artifactId} ${artifact.filename ? `(${artifact.filename}) ` : ""}${artifact.mediaType ?? artifact.kind ?? ""}`.trim());
|
|
179
|
+
}
|
|
180
|
+
} else if (Array.isArray(message.payload?.attachments) && message.payload.attachments.length) {
|
|
181
|
+
lines.push("", "Attachments:");
|
|
182
|
+
for (const attachment of message.payload.attachments) {
|
|
183
|
+
lines.push(`- ${attachment.artifactId}${attachment.title ? ` (${attachment.title})` : ""}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (payload.replyTo) {
|
|
188
|
+
lines.push("", "Reply Context:", `Parent: #${payload.replyTo.id} from ${payload.replyTo.from} to ${payload.replyTo.to}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const thread = Array.isArray(payload.thread) ? payload.thread : [];
|
|
192
|
+
if (thread.length) {
|
|
193
|
+
lines.push("", "Thread:");
|
|
194
|
+
for (const item of thread) {
|
|
195
|
+
const subject = item.subject ? ` ${item.subject}` : "";
|
|
196
|
+
lines.push(`- #${item.id} ${item.from} -> ${item.to}${subject}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Orchestrator install command — auto-split from cli.ts (#294). Writes the
|
|
2
|
+
// orchestrator env file, installs the runtime packages, and registers the daemon.
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { hostname as osHostname, homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
createDaemonPlan,
|
|
8
|
+
detectDaemonEnvironment,
|
|
9
|
+
executeDaemonPlan,
|
|
10
|
+
formatDaemonPlan,
|
|
11
|
+
} from "../daemon";
|
|
12
|
+
import { renderEnvFile } from "../setup";
|
|
13
|
+
import { defaultRuntimePrefix, runtimeBinPath } from "../runtime-prefix";
|
|
14
|
+
import { VERSION } from "../config";
|
|
15
|
+
import { shellQuote } from "agent-relay-sdk/shell-utils";
|
|
16
|
+
import { RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
17
|
+
import { expandTilde } from "../utils";
|
|
18
|
+
import { confirm } from "./_shared";
|
|
19
|
+
|
|
20
|
+
export async function handleOrchestratorCommand(args: string[]): Promise<void> {
|
|
21
|
+
const subcommand = args[0];
|
|
22
|
+
if (subcommand !== "install") {
|
|
23
|
+
throw new Error("Usage: agent-relay orchestrator install --relay-url URL --token TOKEN|--bootstrap-token TOKEN [options]");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let relayUrl = "";
|
|
27
|
+
let token = "";
|
|
28
|
+
let bootstrapToken = "";
|
|
29
|
+
let id = sanitizeOrchestratorId(osHostname());
|
|
30
|
+
let baseDir = join(homedir(), "projects");
|
|
31
|
+
let providers = "claude,codex";
|
|
32
|
+
let apiPort = 4860;
|
|
33
|
+
let runtimePrefix = defaultRuntimePrefix();
|
|
34
|
+
let serviceName = "agent-relay-orchestrator";
|
|
35
|
+
let version = VERSION;
|
|
36
|
+
const pathPrefix: string[] = [];
|
|
37
|
+
let dryRun = false;
|
|
38
|
+
let yes = false;
|
|
39
|
+
let force = false;
|
|
40
|
+
let json = false;
|
|
41
|
+
|
|
42
|
+
for (let i = 1; i < args.length; i++) {
|
|
43
|
+
const arg = args[i];
|
|
44
|
+
if (arg === "--relay-url" && i + 1 < args.length) relayUrl = args[++i]!;
|
|
45
|
+
else if (arg === "--token" && i + 1 < args.length) token = args[++i]!;
|
|
46
|
+
else if (arg === "--bootstrap-token" && i + 1 < args.length) bootstrapToken = args[++i]!;
|
|
47
|
+
else if (arg === "--id" && i + 1 < args.length) id = sanitizeOrchestratorId(args[++i]!);
|
|
48
|
+
else if (arg === "--base-dir" && i + 1 < args.length) baseDir = resolve(expandTilde(args[++i]!));
|
|
49
|
+
else if (arg === "--providers" && i + 1 < args.length) providers = args[++i]!;
|
|
50
|
+
else if (arg === "--api-port" && i + 1 < args.length) apiPort = parseInt(args[++i]!, 10);
|
|
51
|
+
else if (arg === "--runtime-prefix" && i + 1 < args.length) runtimePrefix = resolve(expandTilde(args[++i]!));
|
|
52
|
+
else if (arg === "--service-name" && i + 1 < args.length) serviceName = args[++i]!;
|
|
53
|
+
else if (arg === "--version" && i + 1 < args.length) version = args[++i]!;
|
|
54
|
+
else if (arg === "--path-prefix" && i + 1 < args.length) pathPrefix.push(resolve(expandTilde(args[++i]!)));
|
|
55
|
+
else if (arg === "--dry-run") dryRun = true;
|
|
56
|
+
else if (arg === "--yes" || arg === "-y") yes = true;
|
|
57
|
+
else if (arg === "--force") force = true;
|
|
58
|
+
else if (arg === "--json") json = true;
|
|
59
|
+
else throw new Error(`Unknown orchestrator install option "${arg}"`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!relayUrl) throw new Error("--relay-url is required");
|
|
63
|
+
if (!token && !bootstrapToken) throw new Error("--token or --bootstrap-token is required");
|
|
64
|
+
if (!Number.isInteger(apiPort) || apiPort < 1 || apiPort > 65535) throw new Error("--api-port must be an integer from 1 to 65535");
|
|
65
|
+
|
|
66
|
+
const providerList = providers.split(",").map((p) => p.trim()).filter(Boolean);
|
|
67
|
+
for (const provider of providerList) {
|
|
68
|
+
if (provider !== "claude" && provider !== "codex") throw new Error(`Unknown provider "${provider}". Expected claude or codex.`);
|
|
69
|
+
}
|
|
70
|
+
const detectedPathPrefix = [...pathPrefix, ...detectProviderPathPrefixes(providerList as Array<"claude" | "codex">)];
|
|
71
|
+
const uniquePathPrefix = [...new Set(detectedPathPrefix)];
|
|
72
|
+
const envToken = token || (dryRun || json ? "<runtime-token-from-bootstrap>" : await exchangeOrchestratorBootstrapToken(relayUrl, bootstrapToken, id, baseDir));
|
|
73
|
+
const envFile = defaultOrchestratorEnvFile();
|
|
74
|
+
const binaryPath = runtimeBinPath("agent-relay-orchestrator", runtimePrefix);
|
|
75
|
+
const envValues: Record<string, string> = {
|
|
76
|
+
AGENT_RELAY_URL: relayUrl,
|
|
77
|
+
AGENT_RELAY_TOKEN: envToken,
|
|
78
|
+
AGENT_RELAY_ORCHESTRATOR_ID: id,
|
|
79
|
+
AGENT_RELAY_ORCHESTRATOR_HOSTNAME: osHostname(),
|
|
80
|
+
AGENT_RELAY_ORCHESTRATOR_BASE_DIR: baseDir,
|
|
81
|
+
AGENT_RELAY_ORCHESTRATOR_PROVIDERS: providerList.join(","),
|
|
82
|
+
AGENT_RELAY_ORCHESTRATOR_API_PORT: String(apiPort),
|
|
83
|
+
};
|
|
84
|
+
if (uniquePathPrefix.length) envValues.AGENT_RELAY_ORCHESTRATOR_PATH_PREFIX = uniquePathPrefix.join(":");
|
|
85
|
+
|
|
86
|
+
const daemonEnv = await detectDaemonEnvironment();
|
|
87
|
+
const daemonPlan = createDaemonPlan({
|
|
88
|
+
action: "install",
|
|
89
|
+
name: serviceName,
|
|
90
|
+
envFile,
|
|
91
|
+
binaryPath,
|
|
92
|
+
runtimePrefix,
|
|
93
|
+
pathPrefix: uniquePathPrefix,
|
|
94
|
+
enable: true,
|
|
95
|
+
start: true,
|
|
96
|
+
}, daemonEnv);
|
|
97
|
+
const packageCommand = ["npm", "install", "--prefix", runtimePrefix, `agent-relay-runner@${version}`, `agent-relay-orchestrator@${version}`];
|
|
98
|
+
|
|
99
|
+
if (json || dryRun) {
|
|
100
|
+
const plan = {
|
|
101
|
+
id,
|
|
102
|
+
relayUrl,
|
|
103
|
+
baseDir,
|
|
104
|
+
providers: providerList,
|
|
105
|
+
apiPort,
|
|
106
|
+
runtimePrefix,
|
|
107
|
+
envFile,
|
|
108
|
+
binaryPath,
|
|
109
|
+
serviceName,
|
|
110
|
+
version,
|
|
111
|
+
pathPrefix: uniquePathPrefix,
|
|
112
|
+
packageCommand,
|
|
113
|
+
daemon: daemonPlan,
|
|
114
|
+
};
|
|
115
|
+
if (json) console.log(JSON.stringify({ plan }, null, 2));
|
|
116
|
+
else console.log(formatOrchestratorInstallPlan(plan));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!yes) {
|
|
121
|
+
const ok = await confirm(`Install orchestrator "${id}" as daemon "${serviceName}"?`);
|
|
122
|
+
if (!ok) {
|
|
123
|
+
console.log("Orchestrator install cancelled.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
mkdirSync(dirname(envFile), { recursive: true });
|
|
129
|
+
mkdirSync(baseDir, { recursive: true });
|
|
130
|
+
writeFileSync(envFile, renderEnvFile(envValues), { mode: 0o600 });
|
|
131
|
+
chmodSync(envFile, 0o600);
|
|
132
|
+
console.log(`Wrote ${envFile}`);
|
|
133
|
+
|
|
134
|
+
runChecked(packageCommand);
|
|
135
|
+
const result = await executeDaemonPlan(daemonPlan, { force });
|
|
136
|
+
console.log(result.output);
|
|
137
|
+
console.log(`Orchestrator install complete. Verify in Relay: ${relayUrl}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function defaultOrchestratorEnvFile(): string {
|
|
141
|
+
const platform = process.platform;
|
|
142
|
+
if (platform === "darwin") return join(homedir(), "Library", "Application Support", "agent-relay", "orchestrator.env");
|
|
143
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "agent-relay", "orchestrator.env");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function exchangeOrchestratorBootstrapToken(relayUrl: string, bootstrapToken: string, id: string, baseDir: string): Promise<string> {
|
|
147
|
+
const response = await fetch(new URL("/api/orchestrators/bootstrap/exchange", relayUrl), {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: {
|
|
150
|
+
"Content-Type": "application/json",
|
|
151
|
+
[RELAY_TOKEN_HEADER]: bootstrapToken,
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({ id, baseDir }),
|
|
154
|
+
});
|
|
155
|
+
const payload = await response.json().catch(() => null) as { token?: string; error?: string } | null;
|
|
156
|
+
if (!response.ok || !payload?.token) {
|
|
157
|
+
throw new Error(`bootstrap token exchange failed (${response.status}): ${payload?.error ?? response.statusText}`);
|
|
158
|
+
}
|
|
159
|
+
return payload.token;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function sanitizeOrchestratorId(value: string): string {
|
|
163
|
+
const clean = value.trim().replace(/\./g, "-").replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
164
|
+
if (!clean) throw new Error("--id must contain at least one letter or number");
|
|
165
|
+
return clean.slice(0, 120);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function detectProviderPathPrefixes(providers: Array<"claude" | "codex">): string[] {
|
|
169
|
+
const prefixes: string[] = [];
|
|
170
|
+
for (const provider of providers) {
|
|
171
|
+
const path = resolveExecutableOnPath(provider);
|
|
172
|
+
if (path) prefixes.push(dirname(path));
|
|
173
|
+
}
|
|
174
|
+
return prefixes;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveExecutableOnPath(command: string): string | undefined {
|
|
178
|
+
if (command.includes("/")) return existsSync(command) ? command : undefined;
|
|
179
|
+
for (const entry of (process.env.PATH ?? "").split(":")) {
|
|
180
|
+
if (!entry) continue;
|
|
181
|
+
const candidate = join(entry, command);
|
|
182
|
+
try {
|
|
183
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
|
|
184
|
+
} catch {}
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function runChecked(command: string[]): void {
|
|
190
|
+
console.log(`$ ${command.map(shellQuote).join(" ")}`);
|
|
191
|
+
const result = Bun.spawnSync(command, { stdin: "ignore", stdout: "inherit", stderr: "inherit" });
|
|
192
|
+
if (result.exitCode !== 0) throw new Error(`${command[0]} exited with code ${result.exitCode}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function formatOrchestratorInstallPlan(plan: {
|
|
196
|
+
id: string;
|
|
197
|
+
relayUrl: string;
|
|
198
|
+
baseDir: string;
|
|
199
|
+
providers: string[];
|
|
200
|
+
apiPort: number;
|
|
201
|
+
runtimePrefix: string;
|
|
202
|
+
envFile: string;
|
|
203
|
+
binaryPath: string;
|
|
204
|
+
serviceName: string;
|
|
205
|
+
version: string;
|
|
206
|
+
pathPrefix: string[];
|
|
207
|
+
packageCommand: string[];
|
|
208
|
+
daemon: ReturnType<typeof createDaemonPlan>;
|
|
209
|
+
}): string {
|
|
210
|
+
const lines = [
|
|
211
|
+
"Orchestrator install plan",
|
|
212
|
+
`ID: ${plan.id}`,
|
|
213
|
+
`Relay: ${plan.relayUrl}`,
|
|
214
|
+
`Base dir: ${plan.baseDir}`,
|
|
215
|
+
`Providers: ${plan.providers.join(", ") || "(none)"}`,
|
|
216
|
+
`API port: ${plan.apiPort}`,
|
|
217
|
+
`Runtime prefix: ${plan.runtimePrefix}`,
|
|
218
|
+
`Env file: ${plan.envFile}`,
|
|
219
|
+
`Binary: ${plan.binaryPath}`,
|
|
220
|
+
`Service: ${plan.serviceName}`,
|
|
221
|
+
`Package version: ${plan.version}`,
|
|
222
|
+
];
|
|
223
|
+
if (plan.pathPrefix.length) lines.push(`PATH prefix: ${plan.pathPrefix.join(":")}`);
|
|
224
|
+
lines.push("", "Package command:", ` ${plan.packageCommand.join(" ")}`);
|
|
225
|
+
lines.push("", formatDaemonPlan(plan.daemon));
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
package/src/cli/pair.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Pair commands — auto-split from cli.ts (#294). Two-agent pair sessions:
|
|
2
|
+
// create/status/accept/reject/hangup/send, plus the /pair and /disconnect slash
|
|
3
|
+
// aliases.
|
|
4
|
+
import { apiRequest } from "./_shared";
|
|
5
|
+
import { detectActivePairId, detectAgentId } from "./agent-detect";
|
|
6
|
+
|
|
7
|
+
export async function handleSlashOrPairCommand(command: string, args: string[]): Promise<void> {
|
|
8
|
+
if (command === "/disconnect") {
|
|
9
|
+
await handlePairCommand(["hangup", ...args]);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (command === "/pair") {
|
|
13
|
+
await handlePairCommand(args);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await handlePairCommand(args);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function handlePairCommand(args: string[]): Promise<void> {
|
|
20
|
+
if (!args.length) throw new Error("Usage: agent-relay pair <target|create|status|accept|reject|hangup|send> [options]");
|
|
21
|
+
const knownActions = new Set(["create", "status", "list", "accept", "reject", "hangup", "disconnect", "send"]);
|
|
22
|
+
const action = knownActions.has(args[0]!) ? args[0]! : "create";
|
|
23
|
+
const rest = action === "create" && args[0] !== "create" ? args : args.slice(1);
|
|
24
|
+
|
|
25
|
+
if (action === "status" || action === "list") {
|
|
26
|
+
let agent: string | undefined = await detectAgentId();
|
|
27
|
+
let status: string | undefined;
|
|
28
|
+
let json = false;
|
|
29
|
+
for (let i = 0; i < rest.length; i++) {
|
|
30
|
+
const arg = rest[i];
|
|
31
|
+
if (arg === "--agent" && i + 1 < rest.length) agent = rest[++i];
|
|
32
|
+
else if (arg === "--status" && i + 1 < rest.length) status = rest[++i];
|
|
33
|
+
else if (arg === "--json") json = true;
|
|
34
|
+
else throw new Error(`Unknown pair status option "${arg}"`);
|
|
35
|
+
}
|
|
36
|
+
const query = new URLSearchParams();
|
|
37
|
+
if (agent) query.set("agent", agent);
|
|
38
|
+
if (status) query.set("status", status);
|
|
39
|
+
const pairs = await apiRequest("GET", `/api/pairs${query.size ? `?${query}` : ""}`);
|
|
40
|
+
if (json) console.log(JSON.stringify(pairs, null, 2));
|
|
41
|
+
else console.log(formatPairs(pairs as any[]));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (action === "create") {
|
|
46
|
+
const target = rest[0];
|
|
47
|
+
if (!target || target.startsWith("--")) throw new Error("Usage: agent-relay pair <target> [--from AGENT_ID] [--objective TEXT]");
|
|
48
|
+
let from = await detectAgentId();
|
|
49
|
+
let objective: string | undefined;
|
|
50
|
+
let ttlMs: number | undefined;
|
|
51
|
+
let json = false;
|
|
52
|
+
const objectiveParts: string[] = [];
|
|
53
|
+
for (let i = 1; i < rest.length; i++) {
|
|
54
|
+
const arg = rest[i];
|
|
55
|
+
if (arg === "--from" && i + 1 < rest.length) from = rest[++i];
|
|
56
|
+
else if (arg === "--objective" && i + 1 < rest.length) objective = rest[++i];
|
|
57
|
+
else if (arg === "--ttl-ms" && i + 1 < rest.length) ttlMs = parseInt(rest[++i]!, 10);
|
|
58
|
+
else if (arg === "--json") json = true;
|
|
59
|
+
else objectiveParts.push(arg!);
|
|
60
|
+
}
|
|
61
|
+
objective ??= objectiveParts.length ? objectiveParts.join(" ") : undefined;
|
|
62
|
+
if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
63
|
+
const result = await apiRequest("POST", "/api/pairs", { from, target, objective, ttlMs });
|
|
64
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
else {
|
|
66
|
+
const pair = (result as any).pair;
|
|
67
|
+
console.log(`Pair invite ${pair.id} sent: ${pair.requesterId} -> ${pair.targetId}`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === "accept" || action === "reject" || action === "hangup" || action === "disconnect") {
|
|
73
|
+
const pairId = rest[0];
|
|
74
|
+
let agentId = await detectAgentId();
|
|
75
|
+
let reason: string | undefined;
|
|
76
|
+
let json = false;
|
|
77
|
+
let startIndex = 0;
|
|
78
|
+
if (pairId && !pairId.startsWith("--")) startIndex = 1;
|
|
79
|
+
for (let i = startIndex; i < rest.length; i++) {
|
|
80
|
+
const arg = rest[i];
|
|
81
|
+
if (arg === "--agent" && i + 1 < rest.length) agentId = rest[++i];
|
|
82
|
+
else if (arg === "--reason" && i + 1 < rest.length) reason = rest[++i];
|
|
83
|
+
else if (arg === "--json") json = true;
|
|
84
|
+
else throw new Error(`Unknown pair ${action} option "${arg}"`);
|
|
85
|
+
}
|
|
86
|
+
if (!agentId) throw new Error("Could not detect current Agent Relay ID. Pass --agent AGENT_ID or set AGENT_RELAY_ID.");
|
|
87
|
+
const resolvedPairId = pairId && !pairId.startsWith("--") ? pairId : await detectActivePairId(agentId);
|
|
88
|
+
if (!resolvedPairId) throw new Error(`Usage: agent-relay pair ${action} PAIR_ID --agent AGENT_ID`);
|
|
89
|
+
const endpoint = action === "disconnect" ? "hangup" : action;
|
|
90
|
+
const pair = await apiRequest("POST", `/api/pairs/${encodeURIComponent(resolvedPairId)}/${endpoint}`, { agentId, reason });
|
|
91
|
+
if (json) console.log(JSON.stringify(pair, null, 2));
|
|
92
|
+
else console.log(`Pair ${resolvedPairId}: ${(pair as any).status}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (action === "send") {
|
|
97
|
+
const pairId = rest[0];
|
|
98
|
+
if (!pairId || pairId.startsWith("--")) throw new Error("Usage: agent-relay pair send PAIR_ID --from AGENT_ID --body TEXT");
|
|
99
|
+
let from = await detectAgentId();
|
|
100
|
+
let body: string | undefined;
|
|
101
|
+
let subject: string | undefined;
|
|
102
|
+
let json = false;
|
|
103
|
+
for (let i = 1; i < rest.length; i++) {
|
|
104
|
+
const arg = rest[i];
|
|
105
|
+
if (arg === "--from" && i + 1 < rest.length) from = rest[++i];
|
|
106
|
+
else if (arg === "--body" && i + 1 < rest.length) body = rest[++i];
|
|
107
|
+
else if (arg === "--subject" && i + 1 < rest.length) subject = rest[++i];
|
|
108
|
+
else if (arg === "--json") json = true;
|
|
109
|
+
else throw new Error(`Unknown pair send option "${arg}"`);
|
|
110
|
+
}
|
|
111
|
+
if (!from) throw new Error("Could not detect current Agent Relay ID. Pass --from AGENT_ID or set AGENT_RELAY_ID.");
|
|
112
|
+
if (!body) throw new Error("--body TEXT required");
|
|
113
|
+
const result = await apiRequest("POST", `/api/pairs/${encodeURIComponent(pairId)}/messages`, { from, body, subject });
|
|
114
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
115
|
+
else console.log(`Pair message sent: ${(result as any).message.id}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatPairs(pairs: any[]): string {
|
|
121
|
+
if (!pairs.length) return "No pair sessions.";
|
|
122
|
+
return pairs
|
|
123
|
+
.map((pair) => `${pair.id} ${pair.status} ${pair.requesterId} <-> ${pair.targetId}${pair.objective ? ` ${pair.objective}` : ""}`)
|
|
124
|
+
.join("\n");
|
|
125
|
+
}
|