relay-companion 0.1.477 → 0.1.478

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.
@@ -6,6 +6,7 @@ const path = require("node:path");
6
6
  const { spawn } = require("node:child_process");
7
7
 
8
8
  const STATUS_VERSION = 1;
9
+ const RUNNING_STATUSES = ["starting", "installing", "waiting_authorization"];
9
10
 
10
11
  function statusPath({ homeDir = os.homedir(), env = process.env } = {}) {
11
12
  return env.RELAY_BACKGROUND_INSTALL_STATUS || path.join(env.RELAY_CONFIG_DIR || path.join(homeDir, ".relay"), "companion-install.json");
@@ -42,7 +43,7 @@ function processAlive(pid) {
42
43
 
43
44
  function installationStatus(options = {}) {
44
45
  const state = readStatus(options);
45
- if (["starting", "installing"].includes(state.status) && !processAlive(state.pid)) {
46
+ if (RUNNING_STATUSES.includes(state.status) && !processAlive(state.pid)) {
46
47
  return { ...state, status: "failed", reason: "installer_stopped", finishedAt: new Date().toISOString() };
47
48
  }
48
49
  return state;
@@ -57,7 +58,7 @@ function startBackgroundInstall({
57
58
  } = {}) {
58
59
  const paths = { homeDir, env };
59
60
  const existing = installationStatus(paths);
60
- if (["starting", "installing"].includes(existing.status)) {
61
+ if (RUNNING_STATUSES.includes(existing.status)) {
61
62
  return { ok: true, started: false, alreadyRunning: true, status: existing };
62
63
  }
63
64
  fs.mkdirSync(path.dirname(statusPath(paths)), { recursive: true, mode: 0o700 });
@@ -86,6 +87,37 @@ function option(argv, name) {
86
87
  return index >= 0 ? argv[index + 1] : "";
87
88
  }
88
89
 
90
+ // Download and verify first, but never activate or enroll Companion until the
91
+ // helper has verified the browser grant and finished its atomic handoff. Only
92
+ // inspect file existence here; account/consent checks remain in adoption.
93
+ async function waitForAgentAuthorization({
94
+ env = process.env,
95
+ homeDir = os.homedir(),
96
+ exists = fs.existsSync,
97
+ now = Date.now,
98
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
99
+ timeoutMs = 30 * 60 * 1000,
100
+ intervalMs = 1000,
101
+ } = {}) {
102
+ const root = env.RELAY_CONFIG_DIR || path.join(homeDir, ".relay");
103
+ const credential = env.RELAY_AGENT_CONFIG || path.join(root, "agent-protocol.json");
104
+ const pending = env.RELAY_AGENT_AUTHORIZATION || path.join(root, "agent-authorization.json");
105
+ const deadline = now() + timeoutMs;
106
+ const options = { env, homeDir };
107
+ let waiting = false;
108
+ while (exists(pending) || !exists(credential)) {
109
+ if (!waiting) {
110
+ atomicWriteJson(statusPath(options), { ...readStatus(options), status: "waiting_authorization" });
111
+ waiting = true;
112
+ }
113
+ if (now() >= deadline) {
114
+ throw new Error("Companion is downloaded but browser approval is not complete. Finish the Relay connection, then retry background-install.");
115
+ }
116
+ await sleep(Math.min(intervalMs, deadline - now()));
117
+ }
118
+ if (waiting) atomicWriteJson(statusPath(options), { ...readStatus(options), status: "installing" });
119
+ }
120
+
89
121
  async function runWorker(argv = process.argv.slice(2), { spawnImpl = spawn, env = process.env } = {}) {
90
122
  const entry = path.resolve(option(argv, "--entry") || path.resolve(__dirname, "relay-setup.cjs"));
91
123
  if (path.basename(entry) !== "relay-setup.cjs" || !fs.existsSync(entry)) throw new Error("Relay's background installer entry is invalid.");
@@ -146,4 +178,5 @@ module.exports = {
146
178
  runWorker,
147
179
  startBackgroundInstall,
148
180
  statusPath,
181
+ waitForAgentAuthorization,
149
182
  };
@@ -1377,6 +1377,9 @@ async function setup(argv = []) {
1377
1377
  // archives instead of leaking hundreds of megabytes per interrupted attempt.
1378
1378
  removeAbandonedRuntimeDownloads(layout.releasesDir);
1379
1379
  const runtime = await stageVerifiedRuntime({ version, platformKey, destination: layout.releaseRoot });
1380
+ if (setupCompatibilityArgs.includes("--agent-protocol") && process.env.RELAY_BACKGROUND_INSTALL_WORKER === "1") {
1381
+ await require("./relay-background-install.cjs").waitForAgentAuthorization();
1382
+ }
1380
1383
  const activated = await activateRuntime(layout, runtime, version, { setupCompatibilityArgs });
1381
1384
  if (setupCompatibilityArgs.includes("--code")) {
1382
1385
  console.log(`Relay ${version} is installed and paired. The Relay pill is open.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.477",
3
+ "version": "0.1.478",
4
4
  "description": "Install Relay for Claude Code, Cowork, and Codex, then sign in from the Relay pill.",
5
5
  "homepage": "https://sendrelays.com/get-started",
6
6
  "repository": {
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "relay",
4
- "version": "1.1.7",
4
+ "version": "1.1.8",
5
5
  "consentVersion": 2,
6
- "baseUrl": "https://sendrelays.com/skills/relay/v1.1.7",
6
+ "baseUrl": "https://sendrelays.com/skills/relay/v1.1.8",
7
7
  "files": [
8
8
  {
9
9
  "path": "SKILL.md",
10
- "sha256": "ad86c448a5296a60ec1e4f974d941e6374f1863141d6b2dfbce11edca821cefa"
10
+ "sha256": "ea97b5ec82fd7545a6c851829658e41ab02525cfb06d03d5f1ad25c1aaafcf5b"
11
11
  },
12
12
  {
13
13
  "path": "agents/openai.yaml",
@@ -50,14 +50,22 @@ from the pasted `/i/:token/agent` Relay invite. Never ask the person to paste a
50
50
  password, Google credential, one-time code, or Relay access token into chat. Use
51
51
  the browser approval URL for identity and permission. Start with the pinned
52
52
  protocol helper in this conversation; no skill discovery or agent restart is
53
- needed. After connect-finish succeeds, follow the invitation's host activation
54
- procedure: install the skill, attempt supported discovery refresh in the running
55
- Claude Code or Codex host, and verify the scope of activation. Start
56
- background-install on a supported desktop. It adopts this same
57
- approved account without another login. Skip desktop installation on a
58
- hosted/headless machine. Keep the Companion
59
- installation non-blocking; continue as soon as the HTTPS protocol is ready and
60
- report a later installation failure as a recoverable app-install issue.
53
+ needed. Run connect-start and open its approval URL in the person's usual
54
+ browser. With setup permission already given, start the same pinned package's
55
+ background-install on a supported desktop before yielding to wait for browser
56
+ approval. It downloads and verifies Companion while the person approves; do not
57
+ postpone it until authorization finishes. The installer waits for the verified
58
+ connection before adopting that account and activating Companion, without
59
+ another login. Skip desktop installation on a hosted/headless machine.
60
+
61
+ After browser approval, run connect-finish, then follow the invitation's host
62
+ activation procedure: install the skill, attempt supported discovery refresh in
63
+ the running Claude Code or Codex host, and verify the scope of activation. Keep
64
+ Companion installation non-blocking; continue as soon as the HTTPS protocol is
65
+ ready. If the approval link expires, renew browser approval without restarting
66
+ a running installer. Check background-status; retry installation only if it
67
+ failed or stopped, and report installation failures separately from the working
68
+ connection.
61
69
 
62
70
  Create the authorization with `POST /v1/agent/authorizations` and consume it
63
71
  after browser approval with `POST /v1/agent/authorizations/:id/consume`. Treat