relay-companion 0.1.36 → 0.1.37

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.
@@ -22,28 +22,31 @@ function isRelayBundle(bundle) {
22
22
  return clean.includes("relay") || clean.includes("electron");
23
23
  }
24
24
 
25
- // Cold-start choice when NEITHER agent is running: pick by what's installed.
26
- // macOS preserves the original preference (Codex, then Claude). Windows is
27
- // Claude-only for now: the Codex materializer still depends on macOS Codex
28
- // Desktop paths/app-server behavior, while Claude's claude:// resume handler is
29
- // registered by Claude Desktop on Windows.
25
+ // Cold-start choice when NEITHER agent is foregrounded/running: pick by what's
26
+ // installed. When BOTH are available and neither is foregrounded, prefer Claude
27
+ // (per the product rule: "if I have claude and codex and neither is foregrounded,
28
+ // foreground claude"). Windows is Claude-only for now: the Codex materializer still
29
+ // depends on macOS Codex Desktop paths/app-server behavior, while Claude's claude://
30
+ // resume handler is registered by Claude Desktop on Windows.
30
31
  function defaultClickHost({ codexInstalled = false, claudeInstalled = false, platform = process.platform } = {}) {
31
32
  if (platform === "win32") return "claude";
32
- if (codexInstalled) return "codex";
33
33
  if (claudeInstalled) return "claude";
34
- return "codex";
34
+ if (codexInstalled) return "codex";
35
+ return "claude";
35
36
  }
36
37
 
37
38
  // Decide which host a pill click should open into. Precedence, strongest signal first:
38
- // 1. The frontmost app IS a host — the user is literally looking at it.
39
+ // 1. The frontmost app IS a host — the user is literally looking at it, so open there.
40
+ // (Codex frontmost -> Codex; Claude frontmost -> Claude.)
39
41
  // 2. The click momentarily activated the accessory overlay (or no frontmost app could
40
- // be read) AND a host was foregrounded moments ago — use that captured host.
42
+ // be read) AND a host was foregrounded moments ago — use that just-foregrounded host.
41
43
  // 3. Exactly one host PROCESS is running — route to it. This is the off-Space fix:
42
44
  // `ps` sees a host on any Space, so a running-but-not-frontmost Claude wins over a
43
- // closed Codex instead of cold-starting Codex.
44
- // 4. Both hosts are running and none is frontmost — fall back to the most-recently
45
- // foregrounded host if we ever saw one.
46
- // 5. Nothing better — installed-preference default (true cold start).
45
+ // closed Codex instead of cold-starting Codex. (Only Codex up -> Codex; only Claude
46
+ // up -> Claude.)
47
+ // 4. Both hosts are running but NEITHER is foregrounded — prefer Claude (product rule).
48
+ // 5. Nothing running — installed-preference default (true cold start; prefers Claude
49
+ // when both are installed).
47
50
  function chooseClickHost({
48
51
  frontHost = null,
49
52
  bundle = null,
@@ -67,7 +70,8 @@ function chooseClickHost({
67
70
 
68
71
  if (claudeRunning && !effectiveCodexRunning) return "claude";
69
72
  if (effectiveCodexRunning && !claudeRunning) return "codex";
70
- if (claudeRunning && effectiveCodexRunning && lastHost) return lastHost;
73
+ // Both running, neither foregrounded (and none foregrounded moments ago): prefer Claude.
74
+ if (claudeRunning && effectiveCodexRunning) return "claude";
71
75
 
72
76
  return defaultClickHost({ codexInstalled: effectiveCodexInstalled, claudeInstalled, platform });
73
77
  }
package/overlay/main.cjs CHANGED
@@ -631,6 +631,23 @@ function rememberForegroundHost(host) {
631
631
  lastHostSeenAt = Date.now();
632
632
  }
633
633
 
634
+ // Bring the host app to the FOREGROUND so the just-opened relay is actually visible.
635
+ // shell.openExternal(claude://…) / the Codex bridge route the content to the host but
636
+ // do NOT reliably raise it: the overlay is a non-activating accessory window, and a
637
+ // host sitting on another macOS Space stays put (observed live: "it opened it in claude
638
+ // (good) but didnt foreground claude"). `open -b <bundle>` activates the app — switching
639
+ // to its Space and launching it if it wasn't running. `-g` is intentionally NOT passed,
640
+ // because raising the app is exactly the point.
641
+ const HOST_BUNDLE_ID = { claude: "com.anthropic.claudefordesktop", codex: "com.openai.codex" };
642
+ function activateHost(host) {
643
+ if (process.platform !== "darwin") return; // Windows foregrounding is handled by its own open path
644
+ const bundle = HOST_BUNDLE_ID[host];
645
+ if (!bundle) return;
646
+ execFile("/usr/bin/open", ["-b", bundle], (error) => {
647
+ if (error) console.error("[overlay] activateHost failed:", host, error && error.message);
648
+ });
649
+ }
650
+
634
651
  function appInstalled(appName) {
635
652
  const name = String(appName || "").replace(/[^A-Za-z0-9 ._-]/g, "");
636
653
  if (!name) return false;
@@ -878,6 +895,10 @@ function openPacket(packetId) {
878
895
  freshlyForged = Boolean(parsed.claudeFreshlyForged);
879
896
  } catch {}
880
897
  if (url || skipExternalOpen) {
898
+ // Raise the host so the relay is visible, whichever open path ran (Claude deep
899
+ // link, Codex bridge, or a plain url). This is the fix for "opened in the right
900
+ // host but didn't foreground it".
901
+ activateHost(host);
881
902
  if (url && !skipExternalOpen) {
882
903
  const webFallback = row && row.taskId ? taskWebUrl(row.taskId) : row && row.actionUrl ? absoluteUrl(row.actionUrl) : webBase();
883
904
  if (claudeSessionIdFromUrl(url)) {
@@ -952,6 +973,7 @@ function openTaskDetail(taskId) {
952
973
  freshlyForged = Boolean(parsed.claudeFreshlyForged);
953
974
  } catch {}
954
975
  if (url || skipExternalOpen) {
976
+ activateHost(host); // raise the host so the opened task is visible
955
977
  if (url && !skipExternalOpen) {
956
978
  if (claudeSessionIdFromUrl(url)) {
957
979
  openClaudeDeepLinkVerified(url, taskWebUrl(taskId), taskId, { freshlyForged });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Relay companion: connects local coding agents to Relay tasks, approvals, and connector tools.",
5
5
  "type": "module",
6
6
  "bin": {