relay-companion 0.1.49 → 0.1.50
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/overlay/inbox.html +190 -1
- package/overlay/main.cjs +37 -0
- package/overlay/preload.cjs +4 -0
- package/package.json +1 -1
- package/src/client.js +8 -0
- package/src/mcp.js +13 -0
package/overlay/inbox.html
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
.tabs {
|
|
123
123
|
flex:0 0 auto;
|
|
124
124
|
display:flex; gap:4px;
|
|
125
|
-
padding:0
|
|
125
|
+
padding:0 12px 6px;
|
|
126
126
|
opacity:1;
|
|
127
127
|
transition:opacity .24s var(--settle);
|
|
128
128
|
}
|
|
@@ -533,6 +533,72 @@
|
|
|
533
533
|
.cv-sugg:hover, .cv-sugg.active { background:rgba(31,26,23,.04); color:var(--ink); }
|
|
534
534
|
.cv-hint { font-size:11px; line-height:1.45; color:var(--muted-2); margin-top:2px; }
|
|
535
535
|
.cv-hint:empty { display:none; }
|
|
536
|
+
|
|
537
|
+
/* ---- My agents tab ---- */
|
|
538
|
+
/* Agent roster rows mirror the Contacts list; the history list mirrors relay rows. */
|
|
539
|
+
.ag-item {
|
|
540
|
+
display:flex; align-items:center; gap:11px; padding:10px 16px; cursor:pointer;
|
|
541
|
+
text-align:left; appearance:none; border:0; border-bottom:1px solid var(--hair-2);
|
|
542
|
+
background:transparent; width:100%; font-family:var(--sans);
|
|
543
|
+
transition:background-color .14s var(--settle);
|
|
544
|
+
}
|
|
545
|
+
.ag-item:hover { background:rgba(31,26,23,.035); }
|
|
546
|
+
.ag-avatar {
|
|
547
|
+
flex:0 0 auto; width:32px; height:32px; border-radius:9px; background:var(--accent-soft); color:var(--accent);
|
|
548
|
+
font-size:11px; font-weight:600; display:flex; align-items:center; justify-content:center; letter-spacing:.02em;
|
|
549
|
+
}
|
|
550
|
+
.ag-body { display:flex; flex-direction:column; min-width:0; flex:1 1 auto; }
|
|
551
|
+
.ag-name-line { display:flex; align-items:center; gap:7px; min-width:0; }
|
|
552
|
+
.ag-name { font-size:13px; font-weight:600; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
553
|
+
.ag-sub { font-size:11.5px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:1px; }
|
|
554
|
+
.ag-time { flex:0 0 auto; margin-left:auto; font-family:var(--mono); font-size:11px; color:var(--muted-2); font-variant-numeric:tabular-nums; padding-left:6px; }
|
|
555
|
+
/* Role badges: Owner = filled prussian pill, Viewer = quiet outline pill. */
|
|
556
|
+
.ag-badge {
|
|
557
|
+
flex:0 0 auto; display:inline-flex; align-items:center;
|
|
558
|
+
font-size:10px; font-weight:600; letter-spacing:.03em; line-height:1;
|
|
559
|
+
padding:3px 7px; border-radius:999px;
|
|
560
|
+
background:var(--accent); color:#fff; border:1px solid var(--accent);
|
|
561
|
+
}
|
|
562
|
+
.ag-badge.viewer { background:transparent; color:var(--muted-2); border-color:rgba(31,26,23,.22); }
|
|
563
|
+
/* history header: back chevron, agent name, role badge */
|
|
564
|
+
.ag-detail-head { display:flex; align-items:center; gap:9px; padding:10px 16px 8px; border-bottom:1px solid var(--hair-2); }
|
|
565
|
+
.ag-back {
|
|
566
|
+
appearance:none; border:0; background:transparent; cursor:pointer; padding:0;
|
|
567
|
+
display:inline-flex; align-items:center; gap:5px;
|
|
568
|
+
font-family:var(--sans); font-size:12px; font-weight:600; color:var(--accent);
|
|
569
|
+
transition:opacity .13s var(--settle);
|
|
570
|
+
}
|
|
571
|
+
.ag-back:hover { opacity:.72; }
|
|
572
|
+
.ag-detail-name {
|
|
573
|
+
font-family:var(--serif); font-size:14.5px; font-weight:500; color:var(--ink);
|
|
574
|
+
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; flex:1 1 auto;
|
|
575
|
+
}
|
|
576
|
+
/* history rows: flat, hairline-divided like relay rows */
|
|
577
|
+
.ag-relay { position:relative; display:block; padding:11px 16px; border-bottom:1px solid var(--hair); }
|
|
578
|
+
.ag-relay-top { display:flex; align-items:baseline; gap:7px; min-width:0; }
|
|
579
|
+
.ag-dir { flex:0 0 auto; font-family:var(--mono); font-size:11px; line-height:1; align-self:center; color:var(--muted-2); }
|
|
580
|
+
.ag-dir.out { color:var(--accent); }
|
|
581
|
+
.ag-parties { font-size:11px; color:var(--muted-2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }
|
|
582
|
+
.ag-parties .p { color:var(--ink-2); font-weight:500; }
|
|
583
|
+
/* type chips: question = filled accent-soft, completion = quiet outline */
|
|
584
|
+
.ag-chip {
|
|
585
|
+
flex:0 0 auto; display:inline-flex; align-items:center; align-self:center;
|
|
586
|
+
font-size:10px; font-weight:600; letter-spacing:.03em; line-height:1;
|
|
587
|
+
padding:3px 7px; border-radius:999px;
|
|
588
|
+
}
|
|
589
|
+
.ag-chip.question { background:var(--accent-soft); color:var(--accent); }
|
|
590
|
+
.ag-chip.completion { background:transparent; color:var(--muted-2); border:1px solid rgba(31,26,23,.18); }
|
|
591
|
+
.ag-relay-title {
|
|
592
|
+
font-family:var(--serif); font-size:14px; line-height:1.3; color:var(--muted); font-weight:400; margin-top:4px;
|
|
593
|
+
white-space:nowrap; overflow:hidden;
|
|
594
|
+
-webkit-mask-image:linear-gradient(to right, #000 calc(100% - 26px), transparent);
|
|
595
|
+
mask-image:linear-gradient(to right, #000 calc(100% - 26px), transparent);
|
|
596
|
+
}
|
|
597
|
+
.ag-relay.unread .ag-relay-title { color:var(--ink); font-weight:500; }
|
|
598
|
+
.ag-unread-dot {
|
|
599
|
+
flex:0 0 auto; width:7px; height:7px; border-radius:50%; align-self:center;
|
|
600
|
+
background:var(--accent); border:1.5px solid var(--accent); box-sizing:border-box;
|
|
601
|
+
}
|
|
536
602
|
</style>
|
|
537
603
|
</head>
|
|
538
604
|
<body>
|
|
@@ -570,6 +636,7 @@
|
|
|
570
636
|
</button>
|
|
571
637
|
<button class="tab" type="button" data-view="sent">Sent</button>
|
|
572
638
|
<button class="tab" type="button" data-view="contacts">Contacts</button>
|
|
639
|
+
<button class="tab" type="button" data-view="agents">My agents</button>
|
|
573
640
|
<button class="mark-all-read gone" id="markAllRead" type="button">Mark all as read</button>
|
|
574
641
|
</nav>
|
|
575
642
|
|
|
@@ -631,6 +698,24 @@
|
|
|
631
698
|
<div class="cv-list" id="cvList"></div>
|
|
632
699
|
<div id="contactsEmpty" class="cv-empty gone">No contacts yet.<br>Add someone, or your agent saves them as you relay.</div>
|
|
633
700
|
</section>
|
|
701
|
+
|
|
702
|
+
<!-- My agents view: agent roster, swapping to one agent's relay history -->
|
|
703
|
+
<section class="view hidden" id="agentsView">
|
|
704
|
+
<div id="agList"></div>
|
|
705
|
+
<div id="agentsEmpty" class="cv-empty gone">No agents yet.<br>Agents your firm deploys with Granular will appear here.</div>
|
|
706
|
+
<div id="agDetail" class="gone">
|
|
707
|
+
<div class="ag-detail-head">
|
|
708
|
+
<button class="ag-back" type="button" id="agBack" aria-label="Back to agents">
|
|
709
|
+
<svg width="7" height="12" viewBox="0 0 8 14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M7 1L1 7l6 6"/></svg>
|
|
710
|
+
Agents
|
|
711
|
+
</button>
|
|
712
|
+
<span class="ag-detail-name" id="agDetailName"></span>
|
|
713
|
+
<span class="ag-badge" id="agDetailRole"></span>
|
|
714
|
+
</div>
|
|
715
|
+
<div id="agHistory"></div>
|
|
716
|
+
<div id="agHistoryEmpty" class="cv-empty gone">No relays with this agent yet.</div>
|
|
717
|
+
</div>
|
|
718
|
+
</section>
|
|
634
719
|
</div>
|
|
635
720
|
</div>
|
|
636
721
|
|
|
@@ -668,6 +753,15 @@
|
|
|
668
753
|
const cvSaveEl = document.getElementById("cvSave");
|
|
669
754
|
const cvCancelEl = document.getElementById("cvCancel");
|
|
670
755
|
const cvDeleteEl = document.getElementById("cvDelete");
|
|
756
|
+
const agentsViewEl = document.getElementById("agentsView");
|
|
757
|
+
const agListEl = document.getElementById("agList");
|
|
758
|
+
const agentsEmptyEl = document.getElementById("agentsEmpty");
|
|
759
|
+
const agDetailEl = document.getElementById("agDetail");
|
|
760
|
+
const agBackEl = document.getElementById("agBack");
|
|
761
|
+
const agDetailNameEl = document.getElementById("agDetailName");
|
|
762
|
+
const agDetailRoleEl = document.getElementById("agDetailRole");
|
|
763
|
+
const agHistoryEl = document.getElementById("agHistory");
|
|
764
|
+
const agHistoryEmptyEl = document.getElementById("agHistoryEmpty");
|
|
671
765
|
|
|
672
766
|
const REDUCED = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
673
767
|
const EXPANDED = { w: 344, h: 524 };
|
|
@@ -2123,6 +2217,95 @@
|
|
|
2123
2217
|
}
|
|
2124
2218
|
});
|
|
2125
2219
|
|
|
2220
|
+
// ---------- My agents view ----------
|
|
2221
|
+
// Roster of Granular foundry agents this human is linked to. Clicking one
|
|
2222
|
+
// swaps the section to its relay history; Back returns to the roster.
|
|
2223
|
+
let agentsList = [];
|
|
2224
|
+
let agentDetail = null; // { agentUserId, name, role } while the history is open
|
|
2225
|
+
function agInitials(name) {
|
|
2226
|
+
const base = String(name || "?").trim();
|
|
2227
|
+
const parts = base.split(/\s+/).filter(Boolean);
|
|
2228
|
+
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
2229
|
+
return base.slice(0, 2).toUpperCase();
|
|
2230
|
+
}
|
|
2231
|
+
function agRoleBadge(role) {
|
|
2232
|
+
const viewer = role === "viewer";
|
|
2233
|
+
return `<span class="ag-badge${viewer ? " viewer" : ""}">${viewer ? "Viewer" : "Owner"}</span>`;
|
|
2234
|
+
}
|
|
2235
|
+
function renderAgents() {
|
|
2236
|
+
const showingDetail = Boolean(agentDetail);
|
|
2237
|
+
agDetailEl.classList.toggle("gone", !showingDetail);
|
|
2238
|
+
agListEl.classList.toggle("gone", showingDetail);
|
|
2239
|
+
agentsEmptyEl.classList.toggle("gone", showingDetail || agentsList.length > 0);
|
|
2240
|
+
if (showingDetail) return;
|
|
2241
|
+
agListEl.innerHTML = agentsList.map((a) => `
|
|
2242
|
+
<button class="ag-item" type="button" data-agent="${esc(a.agentUserId)}">
|
|
2243
|
+
<span class="ag-avatar">${esc(agInitials(a.name))}</span>
|
|
2244
|
+
<span class="ag-body">
|
|
2245
|
+
<span class="ag-name-line">
|
|
2246
|
+
<span class="ag-name">${esc(a.name)}</span>
|
|
2247
|
+
${agRoleBadge(a.role)}
|
|
2248
|
+
</span>
|
|
2249
|
+
<span class="ag-sub">${a.role === "viewer" ? "Follows this agent's relays" : "Exchanges relays with you"}</span>
|
|
2250
|
+
</span>
|
|
2251
|
+
<span class="ag-time">${a.lastRelayAt ? esc(timeAgo(a.lastRelayAt)) : ""}</span>
|
|
2252
|
+
</button>`).join("");
|
|
2253
|
+
for (const el of agListEl.querySelectorAll(".ag-item")) {
|
|
2254
|
+
el.addEventListener("click", () => openAgentHistory(el.getAttribute("data-agent")));
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
async function loadAgents() {
|
|
2258
|
+
try { agentsList = (await window.relay.agents()) || []; }
|
|
2259
|
+
catch { agentsList = []; }
|
|
2260
|
+
renderAgents();
|
|
2261
|
+
}
|
|
2262
|
+
function agHistoryRow(item) {
|
|
2263
|
+
const outbound = item.direction === "outbound";
|
|
2264
|
+
const parties = `<span class="p">${esc(item.fromDisplayName)}</span> → <span class="p">${esc((item.toDisplayNames || []).join(", "))}</span>`;
|
|
2265
|
+
const chip = item.type === "question" || item.type === "completion"
|
|
2266
|
+
? `<span class="ag-chip ${item.type}">${item.type === "question" ? "Question" : "Completion"}</span>`
|
|
2267
|
+
: "";
|
|
2268
|
+
return `
|
|
2269
|
+
<div class="ag-relay${item.unread ? " unread" : ""}">
|
|
2270
|
+
<div class="ag-relay-top">
|
|
2271
|
+
<span class="ag-dir${outbound ? " out" : ""}">${outbound ? "←" : "→"}</span>
|
|
2272
|
+
${item.unread ? '<span class="ag-unread-dot"></span>' : ""}
|
|
2273
|
+
<span class="ag-parties">${parties}</span>
|
|
2274
|
+
${chip}
|
|
2275
|
+
<span class="ag-time">${esc(timeAgo(item.createdAt))}</span>
|
|
2276
|
+
</div>
|
|
2277
|
+
<div class="ag-relay-title">${esc(item.title)}</div>
|
|
2278
|
+
</div>`;
|
|
2279
|
+
}
|
|
2280
|
+
async function openAgentHistory(agentUserId) {
|
|
2281
|
+
const agent = agentsList.find((a) => a.agentUserId === agentUserId);
|
|
2282
|
+
if (!agent) return;
|
|
2283
|
+
agentDetail = agent;
|
|
2284
|
+
agDetailNameEl.textContent = agent.name;
|
|
2285
|
+
agDetailRoleEl.textContent = agent.role === "viewer" ? "Viewer" : "Owner";
|
|
2286
|
+
agDetailRoleEl.classList.toggle("viewer", agent.role === "viewer");
|
|
2287
|
+
agHistoryEl.innerHTML = "";
|
|
2288
|
+
agHistoryEmptyEl.classList.add("gone");
|
|
2289
|
+
renderAgents();
|
|
2290
|
+
try {
|
|
2291
|
+
const res = await window.relay.agentRelays(agentUserId);
|
|
2292
|
+
if (!agentDetail || agentDetail.agentUserId !== agentUserId) return; // navigated away
|
|
2293
|
+
const items = (res && Array.isArray(res.items)) ? res.items : [];
|
|
2294
|
+
// Newest first for the pill (the API returns oldest-first chronology).
|
|
2295
|
+
agHistoryEl.innerHTML = items.slice().reverse().map(agHistoryRow).join("");
|
|
2296
|
+
agHistoryEmptyEl.classList.toggle("gone", items.length > 0);
|
|
2297
|
+
} catch {
|
|
2298
|
+
if (!agentDetail || agentDetail.agentUserId !== agentUserId) return;
|
|
2299
|
+
agHistoryEmptyEl.classList.remove("gone");
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function closeAgentHistory() {
|
|
2303
|
+
agentDetail = null;
|
|
2304
|
+
agHistoryEl.innerHTML = "";
|
|
2305
|
+
renderAgents();
|
|
2306
|
+
}
|
|
2307
|
+
agBackEl.addEventListener("click", closeAgentHistory);
|
|
2308
|
+
|
|
2126
2309
|
// ---------- views / tabs ----------
|
|
2127
2310
|
function syncTabs() {
|
|
2128
2311
|
const tabView = activeView === "taskDetail" ? "sent" : activeView;
|
|
@@ -2133,6 +2316,7 @@
|
|
|
2133
2316
|
sentViewEl.classList.toggle("hidden", activeView !== "sent");
|
|
2134
2317
|
taskDetailViewEl.classList.toggle("hidden", activeView !== "taskDetail");
|
|
2135
2318
|
contactsViewEl.classList.toggle("hidden", activeView !== "contacts");
|
|
2319
|
+
agentsViewEl.classList.toggle("hidden", activeView !== "agents");
|
|
2136
2320
|
if (activeView !== "taskDetail" && taskDetailTimer) {
|
|
2137
2321
|
clearInterval(taskDetailTimer);
|
|
2138
2322
|
taskDetailTimer = null;
|
|
@@ -2143,6 +2327,11 @@
|
|
|
2143
2327
|
cvFormEl.classList.add("hidden");
|
|
2144
2328
|
if (window.relay.setFocusable) window.relay.setFocusable(false);
|
|
2145
2329
|
}
|
|
2330
|
+
if (activeView === "agents") {
|
|
2331
|
+
agentDetail = null; // always land on the roster
|
|
2332
|
+
renderAgents();
|
|
2333
|
+
loadAgents();
|
|
2334
|
+
}
|
|
2146
2335
|
if (activeView === "sent" && window.relay.refreshSent) {
|
|
2147
2336
|
window.relay.refreshSent().catch(() => {});
|
|
2148
2337
|
}
|
package/overlay/main.cjs
CHANGED
|
@@ -544,6 +544,41 @@ async function deleteContactFromBook(input) {
|
|
|
544
544
|
return readContacts();
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
// ---- foundry agents (cached like contacts; refreshed on demand) ------------
|
|
548
|
+
// Same rule as contacts: a slow or failing network must never blank the list.
|
|
549
|
+
// Keep the last good roster/history; refresh lazily when the tab is opened.
|
|
550
|
+
|
|
551
|
+
let agentsCache = [];
|
|
552
|
+
const agentRelaysCache = new Map(); // agentUserId -> last good history payload
|
|
553
|
+
async function refreshAgents() {
|
|
554
|
+
try {
|
|
555
|
+
const client = await relayClient();
|
|
556
|
+
const res = await client.agents();
|
|
557
|
+
agentsCache = Array.isArray(res && res.agents) ? res.agents : [];
|
|
558
|
+
} catch (error) {
|
|
559
|
+
// Keep the last good cache; a transient network failure must not blank the UI.
|
|
560
|
+
console.error("[overlay] listAgents failed:", error && error.message);
|
|
561
|
+
}
|
|
562
|
+
return agentsCache;
|
|
563
|
+
}
|
|
564
|
+
async function readAgents() {
|
|
565
|
+
await refreshAgents();
|
|
566
|
+
return agentsCache;
|
|
567
|
+
}
|
|
568
|
+
async function readAgentRelays(agentUserId) {
|
|
569
|
+
const id = String(agentUserId || "").trim();
|
|
570
|
+
if (!id) throw new Error("Missing agent id.");
|
|
571
|
+
try {
|
|
572
|
+
const client = await relayClient();
|
|
573
|
+
const res = await client.agentRelays(id);
|
|
574
|
+
agentRelaysCache.set(id, res);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
console.error("[overlay] agentRelays failed:", error && error.message);
|
|
577
|
+
if (!agentRelaysCache.has(id)) throw error;
|
|
578
|
+
}
|
|
579
|
+
return agentRelaysCache.get(id);
|
|
580
|
+
}
|
|
581
|
+
|
|
547
582
|
// ---- payload assembly + push to renderer ---------------------------------
|
|
548
583
|
|
|
549
584
|
// buildPayload is synchronous over CACHES so the first paint never waits on the
|
|
@@ -1750,6 +1785,8 @@ ipcMain.handle("relay:answer", (_e, taskId, messageId, text, packetId) => answer
|
|
|
1750
1785
|
ipcMain.handle("relay:contacts", () => readContacts());
|
|
1751
1786
|
ipcMain.handle("relay:contactSave", (_e, input) => saveContact(input));
|
|
1752
1787
|
ipcMain.handle("relay:contactDelete", (_e, input) => deleteContactFromBook(input));
|
|
1788
|
+
ipcMain.handle("relay:agents", () => readAgents());
|
|
1789
|
+
ipcMain.handle("relay:agentRelays", (_e, agentUserId) => readAgentRelays(agentUserId));
|
|
1753
1790
|
|
|
1754
1791
|
// The overlay is normally focusable:false so it never steals keyboard focus from
|
|
1755
1792
|
// Claude/Codex. Text fields (the contact form, quick reply) need focus, so the renderer
|
package/overlay/preload.cjs
CHANGED
|
@@ -37,6 +37,10 @@ contextBridge.exposeInMainWorld("relay", {
|
|
|
37
37
|
contactSave: (input) => ipcRenderer.invoke("relay:contactSave", input),
|
|
38
38
|
contactDelete: (input) => ipcRenderer.invoke("relay:contactDelete", input),
|
|
39
39
|
|
|
40
|
+
// foundry agents ("My agents" tab)
|
|
41
|
+
agents: () => ipcRenderer.invoke("relay:agents"),
|
|
42
|
+
agentRelays: (agentUserId) => ipcRenderer.invoke("relay:agentRelays", agentUserId),
|
|
43
|
+
|
|
40
44
|
// window plumbing
|
|
41
45
|
setFocusable: (v) => ipcRenderer.send("relay:setFocusable", v),
|
|
42
46
|
interactive: (v) => ipcRenderer.send("relay:interactive", v),
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -201,6 +201,14 @@ export class RelayClient {
|
|
|
201
201
|
return this.#req("POST", "/v1/tools/call", payload);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
agents() {
|
|
205
|
+
return this.#req("GET", "/v1/agents");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
agentRelays(agentUserId) {
|
|
209
|
+
return this.#req("GET", `/v1/agents/${encodeURIComponent(agentUserId)}/relays`);
|
|
210
|
+
}
|
|
211
|
+
|
|
204
212
|
searchContacts(q) {
|
|
205
213
|
return this.#req("GET", `/v1/contacts/search?q=${encodeURIComponent(q)}`);
|
|
206
214
|
}
|
package/src/mcp.js
CHANGED
|
@@ -60,6 +60,17 @@ export const TOOLS = [
|
|
|
60
60
|
description: "Absolute local file paths to attach. Relay reads, hashes, and includes the bytes safely.",
|
|
61
61
|
items: { type: "string" },
|
|
62
62
|
},
|
|
63
|
+
inReplyToRelayId: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description:
|
|
66
|
+
"When answering a question relay, pass that relay's id here — this is what resumes the asking agent's run.",
|
|
67
|
+
},
|
|
68
|
+
type: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["question", "completion"],
|
|
71
|
+
description:
|
|
72
|
+
"Agent-only semantics for agent-outbound relays: 'question' pauses the sending agent's run until an owner answers; 'completion' reports a finished run. Ordinary humans omit this field.",
|
|
73
|
+
},
|
|
63
74
|
idempotencyKey: { type: "string" },
|
|
64
75
|
},
|
|
65
76
|
required: ["recipient", "title", "bodyMarkdown", "idempotencyKey"],
|
|
@@ -501,6 +512,8 @@ export async function handleCall(client, name, args, { mode = DEFAULT_COMPANION_
|
|
|
501
512
|
source: { host: "relay-mcp" },
|
|
502
513
|
targetSurfaces: args.targetSurfaces || [],
|
|
503
514
|
attachments: await prepareOrdinaryRelayAttachments(args),
|
|
515
|
+
...(args.inReplyToRelayId ? { inReplyToRelayId: args.inReplyToRelayId } : {}),
|
|
516
|
+
...(args.type ? { type: args.type } : {}),
|
|
504
517
|
idempotencyKey: args.idempotencyKey,
|
|
505
518
|
})),
|
|
506
519
|
);
|