relay-companion 0.1.5 → 0.1.7
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 +27 -6
- package/package.json +1 -1
- package/src/setup-activation.js +59 -14
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,23 @@ 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
|
+
}
|
|
72
|
+
if (activation.readinessMode === "deferred_tool_search_unverified") {
|
|
73
|
+
console.log("Relay was added to Codex. Codex can load the Relay tools through tool search (`relay_send`).");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
68
76
|
console.log("Relay tools are live in this Codex session (`relay_send` verified).");
|
|
69
77
|
return;
|
|
70
78
|
}
|
|
79
|
+
if (activation.serverCatalogHasTool) {
|
|
80
|
+
console.log(
|
|
81
|
+
"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.",
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
71
85
|
const detail = activation.reason ? ` (${activation.reason})` : "";
|
|
72
86
|
console.log(`Relay was added to Codex, but this open Codex session does not expose relay_send yet${detail}.`);
|
|
73
87
|
return;
|
|
@@ -82,7 +96,7 @@ function printActivationStatus(activation) {
|
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
/** Register the Relay tools into Claude Code + Codex and start the receive daemon. */
|
|
85
|
-
async function applyInstall({ requireLiveTools = false } = {}) {
|
|
99
|
+
async function applyInstall({ requireLiveTools = false, requiredHosts = requiredLiveHosts() } = {}) {
|
|
86
100
|
const { installed, missing, daemon, pill, activations = [] } = await runSetupInstall();
|
|
87
101
|
if (installed.length) console.log(`Added Relay to ${installed.join(" and ")} on this machine.`);
|
|
88
102
|
for (const m of missing) {
|
|
@@ -98,9 +112,13 @@ async function applyInstall({ requireLiveTools = false } = {}) {
|
|
|
98
112
|
else if (pill?.reason === "pill_runtime_missing") {
|
|
99
113
|
console.log("Run `relay pill` to open the Relay pill.");
|
|
100
114
|
}
|
|
101
|
-
|
|
115
|
+
const liveRequirement = liveToolRequirement({ activations, requiredHosts });
|
|
116
|
+
if (requireLiveTools && installed.length && !liveRequirement.ok) {
|
|
117
|
+
const hostLabel = liveRequirement.missingHosts.length
|
|
118
|
+
? liveRequirement.missingHosts.join(" and ")
|
|
119
|
+
: "this running agent session";
|
|
102
120
|
throw new Error(
|
|
103
|
-
|
|
121
|
+
`Relay paired and installed, but ${hostLabel} cannot call relay_send yet. Setup is not ready in the current agent.`,
|
|
104
122
|
);
|
|
105
123
|
}
|
|
106
124
|
return { installed, missing, daemon, pill, activations };
|
|
@@ -110,7 +128,10 @@ async function applyInstall({ requireLiveTools = false } = {}) {
|
|
|
110
128
|
async function cmdSetup(flags) {
|
|
111
129
|
await cmdPair(flags, { promptForDefaults: Boolean(flags.interactive) });
|
|
112
130
|
console.log("");
|
|
113
|
-
await applyInstall({
|
|
131
|
+
await applyInstall({
|
|
132
|
+
requireLiveTools: Boolean(flags["require-live-tools"]) || shouldRequireLiveTools(),
|
|
133
|
+
requiredHosts: requiredLiveHosts(),
|
|
134
|
+
});
|
|
114
135
|
const token = setupOpenRelayToken(flags);
|
|
115
136
|
if (token) {
|
|
116
137
|
console.log("");
|
|
@@ -136,7 +157,7 @@ async function cmdInstall() {
|
|
|
136
157
|
if (!readConfig().deviceToken) {
|
|
137
158
|
throw new Error("This device is not paired yet. Run `relay setup` with a pairing code first.");
|
|
138
159
|
}
|
|
139
|
-
await applyInstall({ requireLiveTools: shouldRequireLiveTools() });
|
|
160
|
+
await applyInstall({ requireLiveTools: shouldRequireLiveTools(), requiredHosts: requiredLiveHosts() });
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
/** Remove the Relay tools from both agents and stop the background daemon. */
|
package/package.json
CHANGED
package/src/setup-activation.js
CHANGED
|
@@ -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,37 +177,51 @@ 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
|
-
|
|
156
|
-
|
|
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:
|
|
170
|
-
currentSessionReady:
|
|
171
|
-
reason:
|
|
172
|
-
|
|
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) {
|
|
208
|
+
const reason = classifyCodexActivationError(error);
|
|
209
|
+
if (reason === "codex_control_socket_unavailable") {
|
|
210
|
+
return {
|
|
211
|
+
host: "Codex",
|
|
212
|
+
ok: true,
|
|
213
|
+
currentSessionReady: true,
|
|
214
|
+
reason: "relay_registered_for_codex_tool_search",
|
|
215
|
+
readinessMode: "deferred_tool_search_unverified",
|
|
216
|
+
verifiedTool: "relay_send",
|
|
217
|
+
detail: error?.stderr || error?.message || String(error),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
175
220
|
return {
|
|
176
221
|
host: "Codex",
|
|
177
222
|
ok: false,
|
|
178
223
|
currentSessionReady: false,
|
|
179
|
-
reason
|
|
224
|
+
reason,
|
|
180
225
|
detail: error?.stderr || error?.message || String(error),
|
|
181
226
|
};
|
|
182
227
|
} finally {
|