relay-companion 0.1.74 → 0.1.76
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 +44 -9
- package/package.json +1 -1
- package/src/auto-update.js +8 -1
- package/src/client.js +13 -0
package/overlay/inbox.html
CHANGED
|
@@ -578,8 +578,10 @@
|
|
|
578
578
|
.cv-hint:empty { display:none; }
|
|
579
579
|
|
|
580
580
|
/* ---- Threads tab: thread rows + one thread's message history ---- */
|
|
581
|
+
/* A div, not a button: it hosts the delete button, and nesting buttons is
|
|
582
|
+
invalid HTML (the parser hoists the inner one and breaks the row). */
|
|
581
583
|
.th-item {
|
|
582
|
-
display:block; width:100%; text-align:left; appearance:none; border:0; cursor:pointer;
|
|
584
|
+
position:relative; display:block; width:100%; text-align:left; appearance:none; border:0; cursor:pointer;
|
|
583
585
|
padding:11px 16px; border-bottom:1px solid var(--hair); background:transparent;
|
|
584
586
|
font-family:var(--sans); transition:background-color .14s var(--settle);
|
|
585
587
|
}
|
|
@@ -770,9 +772,9 @@
|
|
|
770
772
|
</div>
|
|
771
773
|
<div id="thDetail" class="gone">
|
|
772
774
|
<div class="th-detail-head">
|
|
773
|
-
<button class="th-back" type="button" id="thBack" aria-label="Back to
|
|
775
|
+
<button class="th-back" type="button" id="thBack" aria-label="Back to relays">
|
|
774
776
|
<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>
|
|
775
|
-
|
|
777
|
+
Relays
|
|
776
778
|
</button>
|
|
777
779
|
<span class="th-detail-name" id="thDetailName"></span>
|
|
778
780
|
</div>
|
|
@@ -1582,7 +1584,8 @@
|
|
|
1582
1584
|
}
|
|
1583
1585
|
const sentOnly = inbound.length === 0;
|
|
1584
1586
|
return `
|
|
1585
|
-
<
|
|
1587
|
+
<div class="th-item${t.unreadCount ? " unread" : ""}${sentOnly ? " th-sent-only" : ""} deletable" role="button" tabindex="0" data-thread="${esc(t.threadId)}" data-party="${esc(t.party)}">
|
|
1588
|
+
${threadDeleteButton(t)}
|
|
1586
1589
|
<span class="th-top">
|
|
1587
1590
|
${t.unreadCount ? '<span class="th-unread"></span>' : ""}
|
|
1588
1591
|
<span class="th-party">${sentOnly ? "To " : ""}${esc(t.party)}</span>
|
|
@@ -1590,12 +1593,12 @@
|
|
|
1590
1593
|
<span class="th-time">${esc(timeAgo(t.latest.at))}</span>
|
|
1591
1594
|
</span>
|
|
1592
1595
|
<span class="th-title">${esc(t.latest.title)}</span>
|
|
1593
|
-
</
|
|
1596
|
+
</div>`;
|
|
1594
1597
|
}).join("");
|
|
1595
1598
|
prevRelayIds = new Set(allRows.map((r) => r.id));
|
|
1596
1599
|
wireRelayRows();
|
|
1597
1600
|
for (const el of relaysListEl.querySelectorAll(".th-item")) {
|
|
1598
|
-
el.addEventListener("click", () => openThreadDetail(el.getAttribute("data-thread")));
|
|
1601
|
+
el.addEventListener("click", () => openThreadDetail(el.getAttribute("data-thread"), el.getAttribute("data-party") || ""));
|
|
1599
1602
|
}
|
|
1600
1603
|
}
|
|
1601
1604
|
|
|
@@ -1613,6 +1616,17 @@
|
|
|
1613
1616
|
</div>`;
|
|
1614
1617
|
}
|
|
1615
1618
|
|
|
1619
|
+
// Conversations get the same trash affordance as single relays — the unified
|
|
1620
|
+
// list made that inconsistent (a 1-message conversation renders as a relay row
|
|
1621
|
+
// WITH delete; a 2-message one rendered as a conversation row without). One
|
|
1622
|
+
// click removes every inbound relay in the conversation; outbound copies are
|
|
1623
|
+
// the recipient's to delete, so a sent-only conversation has nothing to trash.
|
|
1624
|
+
function threadDeleteButton(t) {
|
|
1625
|
+
const ids = (t.msgs || []).filter((m) => m.direction === "in").map((m) => m.id).filter(Boolean);
|
|
1626
|
+
if (!ids.length) return "";
|
|
1627
|
+
return `<button class="row-delete" type="button" data-delete-thread="${esc(ids.join(","))}" data-stop="1" aria-label="Move this conversation to Recently Deleted" title="Move this conversation to Recently Deleted"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13M10 11v5M14 11v5"/></svg></button>`;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1616
1630
|
// The classic relay row (used flat while peeking, and for single-message
|
|
1617
1631
|
// conversations in the unified list). Row click toggles the open-actions
|
|
1618
1632
|
// card; the buttons carry the actual opens.
|
|
@@ -1744,6 +1758,19 @@
|
|
|
1744
1758
|
ta.addEventListener("input", () => { ta.style.height = "auto"; ta.style.height = Math.min(84, ta.scrollHeight) + "px"; });
|
|
1745
1759
|
ta.addEventListener("mousedown", () => setInteractive(true));
|
|
1746
1760
|
}
|
|
1761
|
+
for (const btn of relaysListEl.querySelectorAll("[data-delete-thread]")) {
|
|
1762
|
+
const ids = (btn.getAttribute("data-delete-thread") || "").split(",").filter(Boolean);
|
|
1763
|
+
btn.addEventListener("click", async (event) => {
|
|
1764
|
+
event.stopPropagation();
|
|
1765
|
+
if (btn.disabled || !ids.length) return;
|
|
1766
|
+
btn.disabled = true;
|
|
1767
|
+
try {
|
|
1768
|
+
for (const id of ids) await window.relay.deleteRelay(id);
|
|
1769
|
+
} catch {
|
|
1770
|
+
btn.disabled = false;
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1747
1774
|
for (const btn of relaysListEl.querySelectorAll("[data-delete]")) {
|
|
1748
1775
|
const id = btn.getAttribute("data-delete");
|
|
1749
1776
|
btn.addEventListener("click", async (event) => {
|
|
@@ -2530,6 +2557,7 @@
|
|
|
2530
2557
|
// relays plus the live Sent list. Local data only — the pill shows the
|
|
2531
2558
|
// user's own exchanges; the API's /v1/threads serves web and agents.
|
|
2532
2559
|
let threadDetailId = null; // threadId while a thread's history is open
|
|
2560
|
+
let threadDetailPartyHint = ""; // who it's with, for the header while messages load
|
|
2533
2561
|
function threadMessages() {
|
|
2534
2562
|
const msgs = [];
|
|
2535
2563
|
for (const r of payload.relays || []) {
|
|
@@ -2602,8 +2630,14 @@
|
|
|
2602
2630
|
function renderThreadDetail() {
|
|
2603
2631
|
const thread = buildThreads().find((t) => t.threadId === threadDetailId);
|
|
2604
2632
|
if (!thread) {
|
|
2605
|
-
|
|
2606
|
-
|
|
2633
|
+
// A conversation whose messages live on the SENT side renders empty until
|
|
2634
|
+
// the sent cache loads — and since 0.1.73 that refresh is lazy (30s when
|
|
2635
|
+
// idle). Ask for it now and show a loading line rather than a blank pane;
|
|
2636
|
+
// the next payload re-renders this view with the messages.
|
|
2637
|
+
thDetailNameEl.textContent = threadDetailPartyHint || "Conversation";
|
|
2638
|
+
thHistoryEl.innerHTML = '<div class="cv-empty">Loading conversation…</div>';
|
|
2639
|
+
if (window.relay.refreshSent) window.relay.refreshSent().catch(() => {});
|
|
2640
|
+
return;
|
|
2607
2641
|
}
|
|
2608
2642
|
thDetailNameEl.textContent = thread.party;
|
|
2609
2643
|
// Thread-level opens act on the newest inbound message (falling back to the
|
|
@@ -2638,8 +2672,9 @@
|
|
|
2638
2672
|
applyView();
|
|
2639
2673
|
renderAll();
|
|
2640
2674
|
});
|
|
2641
|
-
function openThreadDetail(threadId) {
|
|
2675
|
+
function openThreadDetail(threadId, partyHint = "") {
|
|
2642
2676
|
threadDetailId = threadId;
|
|
2677
|
+
threadDetailPartyHint = partyHint;
|
|
2643
2678
|
activeView = "threads";
|
|
2644
2679
|
syncTabs();
|
|
2645
2680
|
applyView();
|
package/package.json
CHANGED
package/src/auto-update.js
CHANGED
|
@@ -623,6 +623,13 @@ export function spawnDetachedUpdate({
|
|
|
623
623
|
` echo "[relay-update] $(date) electron runtime missing; running electron install.js" >> ${LOG} 2>&1`,
|
|
624
624
|
` "$NODE" "$ELECTRON_DIR/install.js" >> ${LOG} 2>&1 || true`,
|
|
625
625
|
` fi`,
|
|
626
|
+
// A half-extracted dist (binary present, Frameworks missing) SIGABRTs at
|
|
627
|
+
// launch and passed the old executable-only check. Force a clean re-extract.
|
|
628
|
+
` ELECTRON_FRAMEWORKS="$ELECTRON_DIR/dist/Electron.app/Contents/Frameworks/Electron Framework.framework"`,
|
|
629
|
+
` if [ -x "$ELECTRON" ] && [ ! -d "$ELECTRON_FRAMEWORKS" ] && [ -f "$ELECTRON_DIR/install.js" ]; then`,
|
|
630
|
+
` echo "[relay-update] $(date) electron dist incomplete (no Frameworks); re-extracting" >> ${LOG} 2>&1`,
|
|
631
|
+
` rm -rf "$ELECTRON_DIR/dist" 2>/dev/null; "$NODE" "$ELECTRON_DIR/install.js" >> ${LOG} 2>&1 || true`,
|
|
632
|
+
` fi`,
|
|
626
633
|
// Verify the package at the exact launchd-owned root reached the exact discovered
|
|
627
634
|
// version. A mismatched prefix or stale npm response must never trigger restart.
|
|
628
635
|
` INSTALLED_VERSION="$("$NODE" -e 'process.stdout.write(require(process.argv[1]).version)' "$PKG/package.json" 2>/dev/null)"`,
|
|
@@ -631,7 +638,7 @@ export function spawnDetachedUpdate({
|
|
|
631
638
|
? ` VERSION_OK=0; if [ "$INSTALLED_VERSION" = ${q(target)} ]; then VERSION_OK=1; else echo "[relay-update] $(date) verify FAILED: installed $INSTALLED_VERSION != exact target ${target}" >> ${LOG} 2>&1; fi`
|
|
632
639
|
: ` VERSION_OK=1`,
|
|
633
640
|
` DESKTOP_OK=0`,
|
|
634
|
-
` if [ "$VERSION_OK" = 1 ] && [ -x "$ELECTRON" ] && [ -f "$PKG/bin/relay.js" ] && grep -q '"version"' "$PKG/package.json" 2>/dev/null; then`,
|
|
641
|
+
` if [ "$VERSION_OK" = 1 ] && [ -x "$ELECTRON" ] && [ -d "$ELECTRON_FRAMEWORKS" ] && [ -f "$PKG/bin/relay.js" ] && grep -q '"version"' "$PKG/package.json" 2>/dev/null; then`,
|
|
635
642
|
// Rebuild version-coupled app/plist paths from the NEW package before restart.
|
|
636
643
|
// --no-restart keeps sequencing here: pill first, daemon (our parent) last.
|
|
637
644
|
` if "$NODE" "$PKG/bin/relay.js" repair-desktop --no-restart${repairModeArgs ? ` ${repairModeArgs}` : ""} >> ${LOG} 2>&1; then DESKTOP_OK=1; else echo "[relay-update] $(date) desktop repair FAILED" >> ${LOG} 2>&1; fi`,
|
package/src/client.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { apiUrl, deviceToken } from "./config.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
|
|
4
|
+
// Reported on every device-authenticated call so the server always knows which
|
|
5
|
+
// companion version each device runs — support and rollout questions get
|
|
6
|
+
// answered from data, never from screenshots.
|
|
7
|
+
const COMPANION_VERSION = (() => {
|
|
8
|
+
try {
|
|
9
|
+
return String(createRequire(import.meta.url)("../package.json").version || "");
|
|
10
|
+
} catch {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
2
14
|
|
|
3
15
|
/** Thin authenticated client for relay-api, used by the CLI, MCP server, and daemon. */
|
|
4
16
|
export class RelayClient {
|
|
@@ -9,6 +21,7 @@ export class RelayClient {
|
|
|
9
21
|
|
|
10
22
|
async #req(method, path, body, { auth = true } = {}) {
|
|
11
23
|
const headers = { "Content-Type": "application/json" };
|
|
24
|
+
if (COMPANION_VERSION) headers["x-relay-version"] = COMPANION_VERSION;
|
|
12
25
|
if (auth && this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
13
26
|
const res = await fetch(`${this.url}${path}`, {
|
|
14
27
|
method,
|