relay-companion 0.1.396 → 0.1.398

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.396",
3
+ "version": "0.1.398",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-companion",
9
- "version": "0.1.396",
9
+ "version": "0.1.398",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/claude-agent-sdk": "0.3.195",
@@ -2245,9 +2245,34 @@
2245
2245
  .sv-provider-list { overflow:hidden; border-radius:var(--r-3); background:var(--raised); box-shadow:inset 0 0 0 1px var(--edge); }
2246
2246
  .sv-provider { padding:12px; }
2247
2247
  .sv-provider + .sv-provider { border-top:1px solid var(--edge); }
2248
- .sv-provider-list.sv-agent-provider-list { display:flex; flex-direction:column; gap:10px; overflow:visible; border-radius:0; background:transparent; box-shadow:none; }
2249
- .sv-agent-provider-list .sv-provider { border-radius:var(--r-3); background:var(--raised); box-shadow:inset 0 0 0 1px var(--edge); }
2250
- .sv-agent-provider-list .sv-provider + .sv-provider { border-top:0; }
2248
+ .sv-provider-list.sv-agent-provider-list { background:var(--raised); }
2249
+ .sv-agent-provider-list .sv-provider { padding:0; }
2250
+ .sv-agent-provider-list .sv-provider + .sv-provider { border-top:1px solid var(--edge); }
2251
+ .sv-agent-provider-toggle {
2252
+ appearance:none; width:100%; min-height:64px; padding:12px; border:0; background:transparent; color:inherit;
2253
+ display:grid; grid-template-columns:28px minmax(0,1fr) auto 14px; align-items:center; gap:10px;
2254
+ font-family:var(--sans); text-align:left; cursor:pointer; transition:background-color var(--t-fast) var(--settle);
2255
+ }
2256
+ .sv-agent-provider-toggle:hover { background:var(--wash); }
2257
+ .sv-agent-provider-logo { width:28px; height:28px; border-radius:7px; object-fit:contain; }
2258
+ .sv-agent-provider-summary { min-width:0; display:flex; flex-direction:column; }
2259
+ .sv-agent-provider-count { margin-top:3px; color:var(--muted-2); font-size:11px; line-height:1.2; }
2260
+ .sv-agent-provider-ready { display:inline-flex; align-items:center; gap:5px; color:var(--ok); font:600 10px/1 var(--sans); white-space:nowrap; }
2261
+ .sv-agent-provider-ready::before { content:""; width:5px; height:5px; border-radius:50%; background:currentColor; }
2262
+ .sv-agent-provider-state { color:var(--muted-2); font:600 10px/1 var(--sans); white-space:nowrap; }
2263
+ .sv-agent-provider-chev { color:var(--muted-3); transition:transform var(--t-base) var(--settle); }
2264
+ .sv-agent-provider-toggle[aria-expanded="true"] .sv-agent-provider-chev { transform:rotate(180deg); }
2265
+ .sv-agent-provider-detail { display:grid; grid-template-rows:0fr; transition:grid-template-rows var(--t-base) var(--soft); }
2266
+ .sv-agent-provider-detail.open { grid-template-rows:1fr; }
2267
+ .sv-agent-provider-detail-inner { min-height:0; overflow:hidden; }
2268
+ .sv-agent-provider-detail-body { margin:0 12px 13px 50px; padding-top:11px; border-top:1px solid var(--hair); }
2269
+ .sv-agent-provider-note { margin-bottom:11px; color:var(--muted-2); font-size:10.5px; line-height:1.4; }
2270
+ .sv-agent-provider-group + .sv-agent-provider-group { margin-top:12px; }
2271
+ .sv-agent-provider-group-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; margin-bottom:7px; color:var(--muted-2); font:600 10px/1.25 var(--sans); }
2272
+ .sv-agent-provider-integrations { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:7px 12px; margin:0; padding:0; list-style:none; }
2273
+ .sv-agent-provider-integrations li { min-width:0; overflow:hidden; color:var(--muted); font:500 10px/1.25 var(--sans); text-overflow:ellipsis; white-space:nowrap; }
2274
+ .sv-agent-provider-integrations li::before { content:"·"; margin-right:5px; color:var(--muted-3); }
2275
+ .sv-agent-provider-actions { display:flex; gap:7px; justify-content:flex-end; margin-top:12px; }
2251
2276
  .sv-provider-top { display:flex; align-items:center; gap:9px; }
2252
2277
  .sv-provider-copy { min-width:0; flex:1 1 auto; display:flex; flex-direction:column; }
2253
2278
  .sv-provider-name { font-size:13px; font-weight:600; color:var(--ink); }
@@ -2266,7 +2291,7 @@
2266
2291
  .sv-provider-btn.primary { border-color:color-mix(in srgb, var(--accent) 35%, var(--edge)); background:var(--accent-soft); color:var(--accent); }
2267
2292
  .sv-provider-btn:hover { color:var(--ink); border-color:var(--edge-strong); }
2268
2293
  .sv-provider-btn:disabled { opacity:.4; cursor:default; }
2269
- @media (prefers-reduced-motion:reduce) { .sv-provider-dot.busy { animation:none; } }
2294
+ @media (prefers-reduced-motion:reduce) { .sv-provider-dot.busy { animation:none; } .sv-agent-provider-detail, .sv-agent-provider-chev { transition:none; } }
2270
2295
 
2271
2296
  /* ---- Quiet mode: the two durable on/off preferences ----
2272
2297
  A real switch, not the Enable/Disable label-flip the provider rows use: these
@@ -11183,6 +11208,7 @@
11183
11208
  let chatAgentPreferencesError = "";
11184
11209
  let providerAuthInfo = null; // safe status only; credentials stay in provider CLIs
11185
11210
  let providerAuthBusy = "";
11211
+ let providerDetailsOpen = "";
11186
11212
  let providerAuthNote = "";
11187
11213
  let providerAuthError = "";
11188
11214
  let providerInventoryRefreshing = false;
@@ -11337,7 +11363,6 @@
11337
11363
  const item = providers[id] || { id, label: id === "claude" ? "Claude Code" : "Codex", enabled:true, installed:false, connected:false, detail:"Checking connection…", busy:false, authLoaded:false };
11338
11364
  const authLoaded = item.authLoaded === true;
11339
11365
  const busy = providerAuthBusy === id || item.busy;
11340
- const state = !item.enabled ? "disabled" : busy ? "busy" : item.connected ? "connected" : "";
11341
11366
  const requirement = item.requirement || (id === "claude" ? "Claude subscription required" : "ChatGPT subscription required");
11342
11367
  const status = !item.enabled
11343
11368
  ? `${item.connected ? `${item.detail} · ` : ""}Disabled for Tasks`
@@ -11349,15 +11374,16 @@
11349
11374
  const integrations = item.integrations && typeof item.integrations === "object" ? item.integrations : {};
11350
11375
  const mcpServers = Array.isArray(integrations.mcpServers) ? integrations.mcpServers : [];
11351
11376
  const apps = Array.isArray(integrations.apps) ? integrations.apps : [];
11377
+ const totalConnections = mcpServers.length + apps.length;
11352
11378
  const integrationList = (label, values) => {
11353
11379
  if (!values.length) return "";
11354
- const preview = values.map((entry) => {
11380
+ const list = values.map((entry) => {
11355
11381
  const name = typeof entry === "string" ? entry : entry && entry.name;
11356
- return `<span class="sv-integration-chip" title="${esc(name || "")}">${esc(name || "")}</span>`;
11382
+ return `<li title="${esc(name || "")}">${esc(name || "")}</li>`;
11357
11383
  }).join("");
11358
- return `<div class="sv-integration-summary">
11359
- <div class="sv-integration-heading">${esc(values.length)} ${esc(label)}</div>
11360
- <div class="sv-integration-preview">${preview}</div>
11384
+ return `<div class="sv-agent-provider-group">
11385
+ <div class="sv-agent-provider-group-head"><span>${esc(label)}</span><span>${esc(values.length)}</span></div>
11386
+ <ul class="sv-agent-provider-integrations">${list}</ul>
11361
11387
  </div>`;
11362
11388
  };
11363
11389
  const enableLabel = item.enabled ? "Disable" : "Enable";
@@ -11370,26 +11396,35 @@
11370
11396
  ? (id === "claude" ? "Use Claude subscription" : "Use ChatGPT subscription")
11371
11397
  : signInLabel;
11372
11398
  const connectDisabled = busy || !authLoaded || !item.installed || !item.enabled || item.connected;
11399
+ const open = providerDetailsOpen === id;
11400
+ const healthy = item.enabled && item.connected && !busy;
11401
+ const logo = id === "claude" ? "claudeCodeMark.svg" : "codexMark.svg";
11402
+ const countLabel = totalConnections === 1 ? "1 connection" : `${totalConnections} connections`;
11403
+ const headerMeta = healthy ? countLabel : status;
11404
+ const headerState = healthy
11405
+ ? '<span class="sv-agent-provider-ready">Ready</span>'
11406
+ : `<span class="sv-agent-provider-state">${esc(busy ? "Connecting" : item.enabled ? "Needs attention" : "Disabled")}</span>`;
11407
+ const detailNote = healthy ? "" : `<div class="sv-agent-provider-note">${esc(requirement)}${status ? ` · ${esc(status)}` : ""}</div>`;
11373
11408
  return `<div class="sv-provider" data-provider-row="${id}">
11374
- <div class="sv-provider-top">
11375
- <span class="sv-provider-dot ${state}" aria-hidden="true"></span>
11376
- <span class="sv-provider-copy">
11377
- <span class="sv-provider-name">${esc(item.label)}</span>
11378
- <span class="sv-provider-status">${esc(requirement)}</span>
11379
- <span class="sv-provider-status">${esc(status)}</span>
11380
- ${integrationList(id === "claude" ? "MCPs and account connectors" : "local MCPs", mcpServers)}
11381
- ${integrationList("connected apps", apps)}
11382
- </span>
11383
- </div>
11384
- <div class="sv-provider-actions" data-stop="1">
11385
- <button class="sv-provider-btn primary" type="button" data-provider-connect="${id}" ${connectDisabled ? "disabled" : ""}>${esc(connectLabel)}</button>
11386
- <button class="sv-provider-btn" type="button" data-provider-enable="${id}" data-enabled="${item.enabled ? "1" : "0"}" ${busy || !authLoaded ? "disabled" : ""}>${enableLabel}</button>
11387
- </div>
11409
+ <button class="sv-agent-provider-toggle" type="button" data-provider-details="${id}" aria-expanded="${open ? "true" : "false"}" aria-controls="svProviderDetail-${id}">
11410
+ <img class="sv-agent-provider-logo" src="${logo}" alt="" />
11411
+ <span class="sv-agent-provider-summary"><span class="sv-provider-name">${esc(item.label)}</span><span class="sv-agent-provider-count">${esc(headerMeta)}</span></span>
11412
+ ${headerState}
11413
+ <svg class="sv-agent-provider-chev" width="12" height="7" viewBox="0 0 12 7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1.5 1.5L6 5.5L10.5 1.5"/></svg>
11414
+ </button>
11415
+ <div class="sv-agent-provider-detail${open ? " open" : ""}" id="svProviderDetail-${id}"><div class="sv-agent-provider-detail-inner"><div class="sv-agent-provider-detail-body">
11416
+ ${detailNote}
11417
+ ${integrationList(id === "claude" ? "MCPs and account connectors" : "Local MCPs", mcpServers)}
11418
+ ${integrationList("Connected apps", apps)}
11419
+ <div class="sv-agent-provider-actions">
11420
+ ${item.connected ? "" : `<button class="sv-provider-btn primary" type="button" data-provider-connect="${id}" ${connectDisabled ? "disabled" : ""}>${esc(connectLabel)}</button>`}
11421
+ <button class="sv-provider-btn" type="button" data-provider-enable="${id}" data-enabled="${item.enabled ? "1" : "0"}" ${busy || !authLoaded ? "disabled" : ""}>${enableLabel}</button>
11422
+ </div>
11423
+ </div></div></div>
11388
11424
  </div>`;
11389
11425
  }).join("");
11390
11426
  return `<section class="sv-provider-section" data-stop="1">
11391
11427
  <div class="sv-open-title">Connections</div>
11392
- <div class="sv-open-intro">Relay uses each app's local profile. Your credentials stay with the app.</div>
11393
11428
  <div class="sv-provider-list sv-agent-provider-list">${rows}</div>
11394
11429
  <div class="sv-hint${providerInventoryRefreshing || providerInventoryStale || providerInventoryError ? "" : " hidden"}" id="svProviderInventoryState">${esc(providerInventoryStateText())}</div>
11395
11430
  ${providerAuthNote ? `<div class="sv-note">${esc(providerAuthNote)}</div>` : ""}
@@ -11810,6 +11845,19 @@
11810
11845
  for (const button of settingsViewEl.querySelectorAll("[data-provider-connect]")) {
11811
11846
  button.addEventListener("click", () => connectProviderFromSettings(button.getAttribute("data-provider-connect")));
11812
11847
  }
11848
+ for (const button of settingsViewEl.querySelectorAll("[data-provider-details]")) {
11849
+ button.addEventListener("click", () => {
11850
+ const provider = button.getAttribute("data-provider-details");
11851
+ const willOpen = button.getAttribute("aria-expanded") !== "true";
11852
+ providerDetailsOpen = willOpen ? provider : "";
11853
+ for (const other of settingsViewEl.querySelectorAll("[data-provider-details]")) {
11854
+ const open = willOpen && other === button;
11855
+ other.setAttribute("aria-expanded", open ? "true" : "false");
11856
+ const detail = document.getElementById(other.getAttribute("aria-controls"));
11857
+ if (detail) detail.classList.toggle("open", open);
11858
+ }
11859
+ });
11860
+ }
11813
11861
  for (const button of settingsViewEl.querySelectorAll("[data-provider-enable]")) {
11814
11862
  button.addEventListener("click", () => setProviderEnabledFromSettings(
11815
11863
  button.getAttribute("data-provider-enable"),
package/overlay/main.cjs CHANGED
@@ -5161,8 +5161,12 @@ async function sendPreviewReply(input, entry) {
5161
5161
  }
5162
5162
  }
5163
5163
 
5164
+ function isTaggedAgentWorkRow(row) {
5165
+ return Boolean(row?.source?.host === "relay-agent-run" && row?.source?.agentSessionId);
5166
+ }
5167
+
5164
5168
  function agentWorkEnabledForRow(row) {
5165
- if (row?.source?.host === "relay-agent-run" && row?.source?.agentSessionId) return PRODUCT_FEATURES.requests === true;
5169
+ if (isTaggedAgentWorkRow(row)) return PRODUCT_FEATURES.requests === true;
5166
5170
  if (row?.relayNotificationKind === "task") return PRODUCT_FEATURES.requests === true;
5167
5171
  if (row?.relayNotificationKind === "plain_relay") return PRODUCT_FEATURES.relayWork === true;
5168
5172
  return false;
@@ -6149,7 +6153,8 @@ async function previewTaskSession(relayId) {
6149
6153
  const id = String(relayId || "");
6150
6154
  const row = rowById(id);
6151
6155
  const isRequest = row?.relayNotificationKind === "task";
6152
- const isLocalWork = row?.relayNotificationKind === "plain_relay" && Boolean(row?.workStartedAt);
6156
+ const isLocalWork = (row?.relayNotificationKind === "plain_relay" && Boolean(row?.workStartedAt))
6157
+ || isTaggedAgentWorkRow(row);
6153
6158
  if (!row || (!isRequest && !isLocalWork)) return { ok: false, error: "No local agent work exists for this Relay." };
6154
6159
  if (!agentWorkEnabledForRow(row)) return agentWorkUnavailable();
6155
6160
  const claudePath = row.claudeNativeSession && row.claudeNativeSession.sessionPath;
@@ -6497,7 +6502,13 @@ async function previewTaskSteer(input) {
6497
6502
  if (body.length > 20000) return { ok: false, error: "That is too long to send." };
6498
6503
  const row = rowById(id);
6499
6504
  const isRequest = row?.relayNotificationKind === "task";
6500
- const isLocalWork = row?.relayNotificationKind === "plain_relay" && Boolean(row?.workStartedAt);
6505
+ // @Claude/@Codex Work rows are server-authored plain chat responses. Their
6506
+ // durable session identity lives in source.agentSessionId, not in the
6507
+ // legacy workStartedAt field used by recipient-started ordinary Relay Work.
6508
+ // Treating only the legacy shape as Work lets the transcript render but
6509
+ // rejects every same-session follow-up before it reaches the session API.
6510
+ const isLocalWork = (row?.relayNotificationKind === "plain_relay" && Boolean(row?.workStartedAt))
6511
+ || isTaggedAgentWorkRow(row);
6501
6512
  if (!row || (!isRequest && !isLocalWork)) return { ok: false, error: "No local agent work exists for this Relay." };
6502
6513
  if (!agentWorkEnabledForRow(row)) return agentWorkUnavailable();
6503
6514
  if (row.source?.host === "relay-agent-run") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.396",
3
+ "version": "0.1.398",
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": {
@@ -92,6 +92,9 @@ function freshPlainRelays(ledger, items) {
92
92
  !seen ||
93
93
  seen.updatedAt !== item.updatedAt ||
94
94
  seen.state !== item.state ||
95
+ (seen.taskStartedAt || "") !== (item.taskStartedAt || "") ||
96
+ (seen.taskCompletedAt || "") !== (item.taskCompletedAt || "") ||
97
+ (seen.taskRunOwner || "null") !== JSON.stringify(item.taskRunOwner || null) ||
95
98
  (seen.restoredAt || "") !== (item.restoredAt || "")
96
99
  );
97
100
  });
@@ -104,6 +107,14 @@ function markPlainRelaysProcessed(ledger, items) {
104
107
  ledger.plainRelays[item.relayId] = {
105
108
  state: item.state,
106
109
  updatedAt: item.updatedAt || item.createdAt || "",
110
+ // Task lifecycle receipts are projected independently of the Relay row.
111
+ // Starting or completing a Task deliberately does not edit the original
112
+ // Relay's updatedAt, so these fields must participate in the daemon's
113
+ // generation key or the pill keeps rendering its initially staged
114
+ // "Waiting on you" copy forever.
115
+ taskStartedAt: item.taskStartedAt || "",
116
+ taskCompletedAt: item.taskCompletedAt || "",
117
+ taskRunOwner: JSON.stringify(item.taskRunOwner || null),
107
118
  restoredAt: item.restoredAt || "",
108
119
  processedAt,
109
120
  };