relay-companion 0.1.5 → 0.1.6

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/bin/relay.js CHANGED
@@ -11,7 +11,7 @@ import { writeConfig, readConfig, apiUrl, DEFAULT_API_URL, DEFAULT_WEB_URL } fro
11
11
  import { runTaskDaemon, pollTaskRuntimeOnce } from "../src/task-daemon.js";
12
12
  import { runMcpServer } from "../src/mcp.js";
13
13
  import { runSetupInstall, runUninstall } from "../src/install.js";
14
- import { shouldRequireLiveTools } from "../src/setup-activation.js";
14
+ import { liveToolRequirement, requiredLiveHosts, shouldRequireLiveTools } from "../src/setup-activation.js";
15
15
  import { openRelay, openTask } from "../src/materializer.js";
16
16
  import { resetCompanionStateForAccount } from "../src/notifications.js";
17
17
  import { finishSetupOpenRelay, normalizeSetupHost, setupOpenRelayToken, setupOpenStatus } from "../src/setup-open.js";
@@ -65,9 +65,19 @@ function printActivationStatus(activation) {
65
65
  if (!activation) return;
66
66
  if (activation.host === "Codex") {
67
67
  if (activation.currentSessionReady) {
68
+ if (activation.readinessMode === "deferred_tool_search") {
69
+ console.log("Relay tools are available in this Codex session through Codex tool search (`relay_send`).");
70
+ return;
71
+ }
68
72
  console.log("Relay tools are live in this Codex session (`relay_send` verified).");
69
73
  return;
70
74
  }
75
+ if (activation.serverCatalogHasTool) {
76
+ console.log(
77
+ "Relay was added to Codex and the MCP server advertises relay_send, but this open Codex session has not exposed relay_send to the agent yet.",
78
+ );
79
+ return;
80
+ }
71
81
  const detail = activation.reason ? ` (${activation.reason})` : "";
72
82
  console.log(`Relay was added to Codex, but this open Codex session does not expose relay_send yet${detail}.`);
73
83
  return;
@@ -82,7 +92,7 @@ function printActivationStatus(activation) {
82
92
  }
83
93
 
84
94
  /** Register the Relay tools into Claude Code + Codex and start the receive daemon. */
85
- async function applyInstall({ requireLiveTools = false } = {}) {
95
+ async function applyInstall({ requireLiveTools = false, requiredHosts = requiredLiveHosts() } = {}) {
86
96
  const { installed, missing, daemon, pill, activations = [] } = await runSetupInstall();
87
97
  if (installed.length) console.log(`Added Relay to ${installed.join(" and ")} on this machine.`);
88
98
  for (const m of missing) {
@@ -98,9 +108,13 @@ async function applyInstall({ requireLiveTools = false } = {}) {
98
108
  else if (pill?.reason === "pill_runtime_missing") {
99
109
  console.log("Run `relay pill` to open the Relay pill.");
100
110
  }
101
- if (requireLiveTools && installed.length && !activations.some((activation) => activation.currentSessionReady)) {
111
+ const liveRequirement = liveToolRequirement({ activations, requiredHosts });
112
+ if (requireLiveTools && installed.length && !liveRequirement.ok) {
113
+ const hostLabel = liveRequirement.missingHosts.length
114
+ ? liveRequirement.missingHosts.join(" and ")
115
+ : "this running agent session";
102
116
  throw new Error(
103
- "Relay paired and installed, but this running agent session cannot call relay_send yet. Setup is not ready in the current agent.",
117
+ `Relay paired and installed, but ${hostLabel} cannot call relay_send yet. Setup is not ready in the current agent.`,
104
118
  );
105
119
  }
106
120
  return { installed, missing, daemon, pill, activations };
@@ -110,7 +124,10 @@ async function applyInstall({ requireLiveTools = false } = {}) {
110
124
  async function cmdSetup(flags) {
111
125
  await cmdPair(flags, { promptForDefaults: Boolean(flags.interactive) });
112
126
  console.log("");
113
- await applyInstall({ requireLiveTools: Boolean(flags["require-live-tools"]) || shouldRequireLiveTools() });
127
+ await applyInstall({
128
+ requireLiveTools: Boolean(flags["require-live-tools"]) || shouldRequireLiveTools(),
129
+ requiredHosts: requiredLiveHosts(),
130
+ });
114
131
  const token = setupOpenRelayToken(flags);
115
132
  if (token) {
116
133
  console.log("");
@@ -136,7 +153,7 @@ async function cmdInstall() {
136
153
  if (!readConfig().deviceToken) {
137
154
  throw new Error("This device is not paired yet. Run `relay setup` with a pairing code first.");
138
155
  }
139
- await applyInstall({ requireLiveTools: shouldRequireLiveTools() });
156
+ await applyInstall({ requireLiveTools: shouldRequireLiveTools(), requiredHosts: requiredLiveHosts() });
140
157
  }
141
158
 
142
159
  /** Remove the Relay tools from both agents and stop the background daemon. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Relay companion: connects local coding agents to Relay tasks, approvals, and connector tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,37 @@ export function shouldRequireLiveTools(env = process.env) {
21
21
  return Boolean(env.CODEX_THREAD_ID || env.CLAUDECODE || env.CLAUDE_CODE_SESSION_ID);
22
22
  }
23
23
 
24
+ export function requiredLiveHosts(env = process.env) {
25
+ const hosts = [];
26
+ if (env.CODEX_THREAD_ID) hosts.push("Codex");
27
+ if (env.CLAUDECODE || env.CLAUDE_CODE_SESSION_ID) hosts.push("Claude Code");
28
+ return hosts;
29
+ }
30
+
31
+ export function liveToolRequirement({ activations = [], requiredHosts = [] } = {}) {
32
+ const readyHosts = new Set(
33
+ activations
34
+ .filter((activation) => activation?.currentSessionReady)
35
+ .map((activation) => activation.host)
36
+ .filter(Boolean),
37
+ );
38
+ if (requiredHosts.length) {
39
+ const missingHosts = requiredHosts.filter((host) => !readyHosts.has(host));
40
+ return { ok: missingHosts.length === 0, readyHosts: Array.from(readyHosts), missingHosts };
41
+ }
42
+ return { ok: readyHosts.size > 0, readyHosts: Array.from(readyHosts), missingHosts: [] };
43
+ }
44
+
45
+ function codexCatalogDetail(statusResponse, serverName = "relay", toolName = "relay_send") {
46
+ const servers = Array.isArray(statusResponse?.data) ? statusResponse.data : [];
47
+ const server = servers.find((item) => item?.name === serverName);
48
+ return {
49
+ serverCatalogReady: Boolean(server),
50
+ serverCatalogHasTool: hasMcpTool(statusResponse, serverName, toolName),
51
+ serverNames: serverNames(statusResponse),
52
+ };
53
+ }
54
+
24
55
  class JsonlRpcClient {
25
56
  constructor({ command = "codex", args = ["app-server", "proxy"], cwd = process.cwd(), timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
26
57
  this.command = command;
@@ -146,30 +177,32 @@ export async function activateCodexMcp({
146
177
 
147
178
  const deadline = Date.now() + timeoutMs;
148
179
  let lastStatus = null;
180
+ let catalog = {
181
+ serverCatalogReady: false,
182
+ serverCatalogHasTool: false,
183
+ serverNames: [],
184
+ };
149
185
  do {
150
186
  lastStatus = await rpc.request("mcpServerStatus/list", {
151
187
  detail: "toolsAndAuthOnly",
152
188
  limit: 100,
153
189
  ...(threadId ? { threadId } : {}),
154
190
  }, timeoutMs);
155
- if (hasMcpTool(lastStatus)) {
156
- return {
157
- host: "Codex",
158
- ok: true,
159
- currentSessionReady: true,
160
- reason: "relay_send_verified",
161
- verifiedTool: "relay_send",
162
- };
163
- }
191
+ catalog = codexCatalogDetail(lastStatus);
192
+ if (catalog.serverCatalogHasTool) break;
164
193
  await new Promise((resolve) => setTimeout(resolve, 300));
165
194
  } while (Date.now() < deadline);
166
195
 
167
196
  return {
168
197
  host: "Codex",
169
- ok: false,
170
- currentSessionReady: false,
171
- reason: "relay_send_not_listed",
172
- serverNames: serverNames(lastStatus),
198
+ ok: catalog.serverCatalogHasTool,
199
+ currentSessionReady: catalog.serverCatalogHasTool,
200
+ reason: catalog.serverCatalogHasTool
201
+ ? "relay_send_available_via_tool_search"
202
+ : "relay_send_not_listed",
203
+ readinessMode: catalog.serverCatalogHasTool ? "deferred_tool_search" : undefined,
204
+ verifiedTool: catalog.serverCatalogHasTool ? "relay_send" : undefined,
205
+ ...catalog,
173
206
  };
174
207
  } catch (error) {
175
208
  return {