relay-companion 0.1.64 → 0.1.66
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 +18 -0
- package/overlay/main.cjs +34 -2
- package/overlay/preload.cjs +3 -0
- package/overlay/visibility.cjs +23 -0
- package/package.json +1 -1
package/overlay/inbox.html
CHANGED
|
@@ -301,6 +301,8 @@
|
|
|
301
301
|
.row-note { font-size:11.5px; color:var(--muted-2); margin-top:9px; line-height:1.45; }
|
|
302
302
|
.row-err { font-size:11.5px; color:#b4332a; margin-top:8px; line-height:1.4; }
|
|
303
303
|
.row-err:empty { display:none; }
|
|
304
|
+
/* Positive variant: injection confirmations reuse the same slot in green. */
|
|
305
|
+
.row-err.ok { color:#1a7f37; }
|
|
304
306
|
|
|
305
307
|
/* ---- task items — also flat, divided by one hairline ---- */
|
|
306
308
|
.task {
|
|
@@ -2629,6 +2631,22 @@
|
|
|
2629
2631
|
// local flag so the next mousemove re-runs the interactivity handshake cleanly.
|
|
2630
2632
|
if (window.relay.onShown) window.relay.onShown(() => { interactive = false; });
|
|
2631
2633
|
if (window.relay.onOpenDone) window.relay.onOpenDone((id) => stopOpening(id));
|
|
2634
|
+
// Visible confirmation for "Open in current chat": the injection is staged,
|
|
2635
|
+
// but the agent only ingests it at its next checkpoint (tool boundary / turn
|
|
2636
|
+
// end — or your next message if it's idle). Say so, briefly, in green.
|
|
2637
|
+
const injectedNoteTimers = new Map();
|
|
2638
|
+
if (window.relay.onInjected) window.relay.onInjected((id, info) => {
|
|
2639
|
+
const hostName = info.host === "codex" ? "Codex" : "Claude";
|
|
2640
|
+
for (const el of document.querySelectorAll(`[data-err="${CSS.escape(id)}"]`)) {
|
|
2641
|
+
el.textContent = `✓ Sent to your current ${hostName} chat — it will pick this up at its next checkpoint (or your next message if it's idle).`;
|
|
2642
|
+
el.classList.add("ok");
|
|
2643
|
+
if (injectedNoteTimers.has(id)) clearTimeout(injectedNoteTimers.get(id));
|
|
2644
|
+
injectedNoteTimers.set(id, setTimeout(() => {
|
|
2645
|
+
injectedNoteTimers.delete(id);
|
|
2646
|
+
if (el.isConnected) { el.textContent = ""; el.classList.remove("ok"); }
|
|
2647
|
+
}, 12000));
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2632
2650
|
window.relay.refresh().then((p) => {
|
|
2633
2651
|
onPayload(p);
|
|
2634
2652
|
console.log("[overlay] booted ok, relays=" + (p && p.relays ? p.relays.length : 0) + " sent=" + (p && p.sent ? p.sent.length : 0));
|
package/overlay/main.cjs
CHANGED
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
shouldIgnoreDismiss,
|
|
43
43
|
boundedPresentedRelayIds,
|
|
44
44
|
recoverInterruptedAttentionPrefs,
|
|
45
|
+
quitRelayCommand,
|
|
45
46
|
hostPollDelayMs,
|
|
46
47
|
sentRefreshDelayMs,
|
|
47
48
|
} = require("./visibility.cjs");
|
|
@@ -1532,6 +1533,11 @@ async function openPacketInCurrent(packetId) {
|
|
|
1532
1533
|
ackPacket(packetId);
|
|
1533
1534
|
if (win && !win.isDestroyed()) win.webContents.send("openDone", packetId); // stop the row spinner
|
|
1534
1535
|
};
|
|
1536
|
+
// Confirm the staging in the UI: without this, a successful injection is
|
|
1537
|
+
// indistinguishable from a dead click until the agent's next checkpoint.
|
|
1538
|
+
const confirmInjected = (host) => {
|
|
1539
|
+
if (win && !win.isDestroyed()) win.webContents.send("injected", packetId, { host });
|
|
1540
|
+
};
|
|
1535
1541
|
const fallbackFresh = () => {
|
|
1536
1542
|
openPacket(packetId, { fresh: true }).catch((error) =>
|
|
1537
1543
|
console.error("[overlay] openInCurrent fresh fallback failed:", error && error.message),
|
|
@@ -1561,7 +1567,10 @@ async function openPacketInCurrent(packetId) {
|
|
|
1561
1567
|
desktop,
|
|
1562
1568
|
codexRunning,
|
|
1563
1569
|
activateHost: () => activateHost("codex", bundle),
|
|
1564
|
-
finish
|
|
1570
|
+
finish: () => {
|
|
1571
|
+
confirmInjected("codex");
|
|
1572
|
+
finish();
|
|
1573
|
+
},
|
|
1565
1574
|
fallbackFresh,
|
|
1566
1575
|
},
|
|
1567
1576
|
),
|
|
@@ -1599,6 +1608,7 @@ async function openPacketInCurrent(packetId) {
|
|
|
1599
1608
|
// Raise Claude Desktop so the user watches the instruction land; a
|
|
1600
1609
|
// terminal-only session stays where it is (its window is not ours to raise).
|
|
1601
1610
|
if (target.source === "desktop") activateHost("claude", bundle);
|
|
1611
|
+
confirmInjected("claude");
|
|
1602
1612
|
finish();
|
|
1603
1613
|
});
|
|
1604
1614
|
}
|
|
@@ -2175,6 +2185,24 @@ function syncTray() {
|
|
|
2175
2185
|
} catch {}
|
|
2176
2186
|
}
|
|
2177
2187
|
|
|
2188
|
+
// "Quit Relay" from the tray: stop the daemon and the pill's own service via a
|
|
2189
|
+
// DETACHED shell (the second bootout kills this process, so the shell must
|
|
2190
|
+
// outlive us), then quit the app immediately for instant visual feedback.
|
|
2191
|
+
function quitRelayCompletely() {
|
|
2192
|
+
try {
|
|
2193
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 501;
|
|
2194
|
+
const [cmd, args] = quitRelayCommand({ platform: process.platform, uid });
|
|
2195
|
+
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
2196
|
+
child.on("error", (error) => console.error(`[overlay] ${new Date().toISOString()} quit spawn failed:`, error && error.message));
|
|
2197
|
+
child.unref();
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
console.error(`[overlay] ${new Date().toISOString()} quit failed:`, error && error.message);
|
|
2200
|
+
}
|
|
2201
|
+
try {
|
|
2202
|
+
app.quit();
|
|
2203
|
+
} catch {}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2178
2206
|
function createTray() {
|
|
2179
2207
|
const icon = createTrayIcon();
|
|
2180
2208
|
if (!icon || icon.isEmpty()) {
|
|
@@ -2195,7 +2223,11 @@ function createTray() {
|
|
|
2195
2223
|
tray.on("right-click", () => {
|
|
2196
2224
|
try {
|
|
2197
2225
|
tray.popUpContextMenu(
|
|
2198
|
-
Menu.buildFromTemplate([
|
|
2226
|
+
Menu.buildFromTemplate([
|
|
2227
|
+
{ label: pillIsOnScreen() ? "Hide Relay" : "Show Relay", click: toggleFromTray },
|
|
2228
|
+
{ type: "separator" },
|
|
2229
|
+
{ label: "Quit Relay", click: quitRelayCompletely },
|
|
2230
|
+
]),
|
|
2199
2231
|
);
|
|
2200
2232
|
} catch (error) {
|
|
2201
2233
|
console.error("[overlay] tray menu failed:", error && error.message);
|
package/overlay/preload.cjs
CHANGED
|
@@ -5,6 +5,9 @@ contextBridge.exposeInMainWorld("relay", {
|
|
|
5
5
|
onInbox: (cb) => ipcRenderer.on("inbox", (_e, payload) => cb(payload)),
|
|
6
6
|
onNewRelay: (cb) => ipcRenderer.on("newRelay", (_e, relay, opts) => cb(relay, opts || {})),
|
|
7
7
|
onOpenDone: (cb) => ipcRenderer.on("openDone", (_e, id) => cb(id)),
|
|
8
|
+
// "Open in current chat" staged an injection: the UI confirms it so the
|
|
9
|
+
// click has visible feedback (delivery is checkpoint-based, not instant).
|
|
10
|
+
onInjected: (cb) => ipcRenderer.on("injected", (_e, id, info) => cb(id, info || {})),
|
|
8
11
|
onOpenFull: (cb) => ipcRenderer.on("openFull", () => cb()), // status-area icon clicked
|
|
9
12
|
onShown: (cb) => ipcRenderer.on("shown", () => cb()), // window re-shown; reset hover handshake
|
|
10
13
|
|
package/overlay/visibility.cjs
CHANGED
|
@@ -119,7 +119,30 @@ function sentRefreshDelayMs({ testMode = false, engaged = false, showActive = fa
|
|
|
119
119
|
return engaged || showActive ? 5000 : 30000;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// The tray's "Quit Relay": one detached shell that stops BOTH background
|
|
123
|
+
// services. Order matters — the daemon first, the pill's own service LAST,
|
|
124
|
+
// because the second bootout kills the process that spawned the shell.
|
|
125
|
+
// launchctl bootout (not kickstart/stop) is the verb that beats KeepAlive;
|
|
126
|
+
// the jobs return at next login (RunAtLoad) or via `relay pill`.
|
|
127
|
+
function quitRelayCommand({ platform = process.platform, uid = 501 } = {}) {
|
|
128
|
+
if (platform === "win32") {
|
|
129
|
+
return [
|
|
130
|
+
"cmd",
|
|
131
|
+
["/c", 'schtasks /End /TN "Relay Companion Daemon" & schtasks /End /TN "Relay Companion Pill"'],
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
return [
|
|
135
|
+
"/bin/sh",
|
|
136
|
+
[
|
|
137
|
+
"-c",
|
|
138
|
+
`/bin/launchctl bootout gui/${uid}/work.relay.companion 2>/dev/null; ` +
|
|
139
|
+
`/bin/launchctl bootout gui/${uid}/work.relay.companion.pill 2>/dev/null`,
|
|
140
|
+
],
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
module.exports = {
|
|
145
|
+
quitRelayCommand,
|
|
123
146
|
hostPollDelayMs,
|
|
124
147
|
sentRefreshDelayMs,
|
|
125
148
|
overlayWanted,
|