relay-companion 0.1.410 → 0.1.412

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.410",
3
+ "version": "0.1.412",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "relay-companion",
9
- "version": "0.1.410",
9
+ "version": "0.1.412",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/claude-agent-sdk": "0.3.195",
@@ -2173,8 +2173,17 @@
2173
2173
  .th-mention-copy { display:flex; flex-direction:column; min-width:0; }
2174
2174
  .th-mention-name { font-weight:650; }
2175
2175
  .th-mention-note { color:var(--muted); font-size:10.5px; }
2176
- .sv-agent-defaults select { min-width:122px; max-width:44%; padding:6px 8px; border:1px solid var(--edge); border-radius:8px;
2177
- background:var(--bg); color:var(--ink); font:11.5px var(--sans); }
2176
+ /* Chat-agent defaults stay compact without forcing four controls onto one
2177
+ line. Identity owns the first line; model and thinking share a labelled,
2178
+ responsive grid below it. */
2179
+ .sv-agent-defaults .sv-open-row { display:block; min-height:0; padding:11px 12px 12px; }
2180
+ .sv-agent-identity { display:flex; align-items:center; gap:11px; min-width:0; }
2181
+ .sv-agent-fields { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:8px; margin:10px 0 0 39px; }
2182
+ .sv-agent-field { display:block; min-width:0; }
2183
+ .sv-agent-field-label { display:block; margin:0 0 4px 1px; color:var(--muted-2); font:10px/1.25 var(--sans); }
2184
+ .sv-agent-select { width:100%; min-width:0; height:34px; padding:0 8px; border:1px solid var(--edge); border-radius:9px;
2185
+ background:var(--bg); color:var(--ink); font:11.5px var(--sans); cursor:pointer; }
2186
+ .sv-agent-select:focus-visible { outline:none; box-shadow:var(--focus-ring); }
2178
2187
  /* The chosen Relay is attached to the existing composer as its top face.
2179
2188
  This is one composer with context, not a Slack-style child conversation. */
2180
2189
  .qr.th-qr.replying { gap:0; }
@@ -4268,7 +4277,7 @@
4268
4277
 
4269
4278
  // ---------- state ----------
4270
4279
  let activeView = "relays";
4271
- let payload = { account: { paired: false }, ui: { canDismiss: true, reopenSurface: "the Relay icon", setupTutorialPending:false }, features: { requests:false, cowork:false, environment:"production" }, relays: [], sent: [], requests: [], contacts: [], chats: [], slackChats: [] };
4280
+ let payload = { account: { paired: false }, ui: { canDismiss: true, reopenSurface: "the Relay icon", setupTutorialPending:false }, features: { requests:false, slack:false, peopleMentions:true, agentMentions:false, cowork:false, environment:"production" }, relays: [], sent: [], requests: [], contacts: [], chats: [], slackChats: [] };
4272
4281
  const canonicalChatDetails = new Map();
4273
4282
  const canonicalChatFingerprints = new Map();
4274
4283
  const canonicalChatDetailProjectionKeys = new Map();
@@ -4311,6 +4320,7 @@
4311
4320
  return `${conversationSurface(source)}:${chatId}`;
4312
4321
  }
4313
4322
  function slackMessagesVisible(room, source = threadsSource) {
4323
+ if (payload.features?.slack !== true) return false;
4314
4324
  if (!isSlackIntegratedRoom(room)) return false;
4315
4325
  const key = slackVisibilityKey(room, source);
4316
4326
  return slackVisibilityOverrides.has(key)
@@ -4405,7 +4415,7 @@
4405
4415
  return changed;
4406
4416
  }
4407
4417
  function syncSlackVisibilityButton(room = threadDetailId ? chatRoomForThread(threadDetailId) : null) {
4408
- const linked = isSlackIntegratedRoom(room);
4418
+ const linked = payload.features?.slack === true && isSlackIntegratedRoom(room);
4409
4419
  thSlackVisibilityEl.classList.toggle("hidden", !linked);
4410
4420
  if (!linked) return;
4411
4421
  const visible = slackMessagesVisible(room);
@@ -11551,7 +11561,7 @@
11551
11561
  // recipients only; the owned Claude and Codex options remain available
11552
11562
  // in every developer composer below.
11553
11563
  const participantNames = new Set();
11554
- if (thread.isGroup) {
11564
+ if (payload.features?.peopleMentions === true && thread.isGroup) {
11555
11565
  const group = groupsList.find((candidate) => String(candidate.id || "") === String(thread.groupId || ""))
11556
11566
  || groupsList.find((candidate) => normalizedPartyName(candidate.name) === normalizedPartyName(thread.groupName));
11557
11567
  // The current roster decides who can be mentioned. Historical sender
@@ -11562,7 +11572,7 @@
11562
11572
  }
11563
11573
  }
11564
11574
  const mentionOptions = [
11565
- ...(payload.features?.requests === true ? [
11575
+ ...(payload.features?.agentMentions === true ? [
11566
11576
  { token:"Claude", name:"@Claude", note:"Run Claude on my computer", agent:true },
11567
11577
  { token:"Codex", name:"@Codex", note:"Run Codex on my computer", agent:true },
11568
11578
  ] : []),
@@ -11719,7 +11729,7 @@
11719
11729
  attachments,
11720
11730
  preview: "",
11721
11731
  });
11722
- const agentMentions = payload.features?.requests === true ? agentMentionSpans(text) : [];
11732
+ const agentMentions = payload.features?.agentMentions === true ? agentMentionSpans(text) : [];
11723
11733
  const invokedProviders = agentMentions.map((mention) => mention.provider);
11724
11734
  for (const [index, provider] of invokedProviders.entries()) {
11725
11735
  optimisticChatReplies.set(`${idempotencyKey}:agent:${provider}`, {
@@ -12161,6 +12171,7 @@
12161
12171
  }
12162
12172
 
12163
12173
  async function refreshSlackConnection({ preserveWaiting = true } = {}) {
12174
+ if (payload.features?.slack !== true) return;
12164
12175
  if (!window.relay.slackConnection) return;
12165
12176
  if (!rendererSurfaceActive()) {
12166
12177
  slackConnectionRefreshDeferred = true;
@@ -12484,9 +12495,12 @@
12484
12495
  <div class="sv-open-intro">Defaults for Work sessions that run on this computer.</div>
12485
12496
  <div class="sv-open-list">${rows.map((row) => {
12486
12497
  const current = chatAgentPreferences[row.key] || {};
12487
- return `<div class="sv-open-row"><img class="sv-open-logo" src="${row.logo}" alt="" /><span class="sv-open-copy"><span class="sv-open-name">${row.label}</span></span>
12488
- <select data-chat-agent-model="${row.key}" aria-label="${row.label} model">${row.models.map(([value,label]) => `<option value="${value}"${current.model === value ? " selected" : ""}>${label}</option>`).join("")}</select>
12489
- <select data-chat-agent-effort="${row.key}" aria-label="${row.label} thinking">${row.efforts.map((value) => `<option value="${value}"${current.effort === value ? " selected" : ""}>${value}</option>`).join("")}</select>
12498
+ return `<div class="sv-open-row">
12499
+ <span class="sv-agent-identity"><img class="sv-open-logo" src="${row.logo}" alt="" /><span class="sv-open-copy"><span class="sv-open-name">${row.label}</span></span></span>
12500
+ <span class="sv-agent-fields">
12501
+ <label class="sv-agent-field"><span class="sv-agent-field-label">Model</span><select class="sv-agent-select" data-chat-agent-model="${row.key}" aria-label="${row.label} model">${row.models.map(([value,label]) => `<option value="${value}"${current.model === value ? " selected" : ""}>${label}</option>`).join("")}</select></label>
12502
+ <label class="sv-agent-field"><span class="sv-agent-field-label">Thinking</span><select class="sv-agent-select" data-chat-agent-effort="${row.key}" aria-label="${row.label} thinking">${row.efforts.map((value) => `<option value="${value}"${current.effort === value ? " selected" : ""}>${value}</option>`).join("")}</select></label>
12503
+ </span>
12490
12504
  </div>`;
12491
12505
  }).join("")}</div>
12492
12506
  ${chatAgentPreferencesError ? `<div class="row-err sv-err">${esc(chatAgentPreferencesError)}</div>` : ""}
@@ -12524,6 +12538,7 @@
12524
12538
  </section>`;
12525
12539
  }
12526
12540
  function slackSettingsHtml(info) {
12541
+ if (payload.features?.slack !== true) return "";
12527
12542
  if (!info?.paired || !window.relay.slackConnection) return "";
12528
12543
  const connection = slackConnectionInfo;
12529
12544
  const state = connection?.state || "disconnected";
@@ -13154,6 +13169,7 @@
13154
13169
  renderSettings();
13155
13170
  })();
13156
13171
  const slackTask = (async () => {
13172
+ if (payload.features?.slack !== true) return;
13157
13173
  if (seq !== settingsLoadSeq) return;
13158
13174
  await refreshSlackConnection({ preserveWaiting:true });
13159
13175
  })();
@@ -13212,14 +13228,14 @@
13212
13228
  window.addEventListener("focus", () => {
13213
13229
  if (activeView === "settings") {
13214
13230
  loadSettings();
13215
- } else if (activeView === "slack") {
13231
+ } else if (activeView === "slack" && payload.features?.slack === true) {
13216
13232
  refreshSlackConnection({ preserveWaiting:true });
13217
13233
  }
13218
13234
  });
13219
13235
  document.addEventListener("visibilitychange", () => {
13220
13236
  if (document.visibilityState === "visible") {
13221
13237
  if (activeView === "settings") loadSettings();
13222
- else if (activeView === "slack") refreshSlackConnection({ preserveWaiting:true });
13238
+ else if (activeView === "slack" && payload.features?.slack === true) refreshSlackConnection({ preserveWaiting:true });
13223
13239
  }
13224
13240
  });
13225
13241
  // Slack OAuth completes in the browser, not in this renderer. Focus and
@@ -13228,7 +13244,7 @@
13228
13244
  // a manual refresh. The tab and Settings card share the same lightweight
13229
13245
  // status call; all other Settings checks retain their focus/entry cadence.
13230
13246
  setInterval(() => {
13231
- if ((activeView === "settings" || activeView === "slack") && rendererSurfaceActive()) {
13247
+ if (payload.features?.slack === true && (activeView === "settings" || activeView === "slack") && rendererSurfaceActive()) {
13232
13248
  refreshSlackConnection({ preserveWaiting:true });
13233
13249
  }
13234
13250
  }, SLACK_CONNECTION_POLL_MS);
@@ -13699,7 +13715,9 @@
13699
13715
  : activeView;
13700
13716
  for (const tab of tabEls) {
13701
13717
  const view = tab.getAttribute("data-view");
13702
- tab.classList.toggle("gone", view === "tasks" && payload.features?.requests !== true);
13718
+ tab.classList.toggle("gone",
13719
+ (view === "tasks" && payload.features?.requests !== true)
13720
+ || (view === "slack" && payload.features?.slack !== true));
13703
13721
  tab.classList.toggle("active", view === tabView);
13704
13722
  }
13705
13723
  }
@@ -13732,7 +13750,7 @@
13732
13750
  } else {
13733
13751
  resetSignOutArm(); // leaving the view disarms the confirm state
13734
13752
  }
13735
- if (activeView === "slack" && viewChanged) {
13753
+ if (activeView === "slack" && payload.features?.slack === true && viewChanged) {
13736
13754
  refreshSlackConnection({ preserveWaiting:true });
13737
13755
  }
13738
13756
  // A room owns its sent-cache refresh in hydrateThreadEntry(), where the
@@ -13866,8 +13884,8 @@
13866
13884
  const unread = relayUnreadIds.size;
13867
13885
  setCount(unread);
13868
13886
  setBadge(relaysBadgeEl, unread);
13869
- setBadge(slackBadgeEl, (payload.slackChats || []).reduce(
13870
- (sum, chat) => sum + Math.max(0, Number(chat?.unreadCount || 0)), 0));
13887
+ setBadge(slackBadgeEl, payload.features?.slack === true ? (payload.slackChats || []).reduce(
13888
+ (sum, chat) => sum + Math.max(0, Number(chat?.unreadCount || 0)), 0) : 0);
13871
13889
  markAllReadEl.classList.toggle("gone", activeView !== "relays" || unread === 0);
13872
13890
  markAllReadEl.disabled = unread === 0;
13873
13891
  setBadge(tasksBadgeEl, requestsUnreadCount());
@@ -13982,6 +14000,10 @@
13982
14000
  if (payload.features?.requests !== true && ["tasks", "requestDetail"].includes(activeView)) {
13983
14001
  activeView = "relays";
13984
14002
  }
14003
+ if (payload.features?.slack !== true && (activeView === "slack" || threadsSource === "slack")) {
14004
+ activeView = "relays";
14005
+ threadsSource = "relay";
14006
+ }
13985
14007
  // Delivery can happen after navigation. If its room is the visible Chat
13986
14008
  // transcript, receipt of the full payload is itself a read event.
13987
14009
  if (paintSurface) readVisibleChatRoom();
package/overlay/main.cjs CHANGED
@@ -874,7 +874,9 @@ async function refreshAccountProductFeatures() {
874
874
  TASK_FEATURES_ALLOWED = next.requests;
875
875
  if (changed) {
876
876
  tasksLoadedOnce = null;
877
+ canonicalChatsLoadedOnce = null;
877
878
  await refreshTasks();
879
+ await refreshCanonicalChats();
878
880
  await pushInbox(true);
879
881
  }
880
882
  return changed;
@@ -1714,6 +1716,12 @@ function isSlackLinkedChat(chat) {
1714
1716
  ));
1715
1717
  }
1716
1718
  async function refreshCanonicalChats() {
1719
+ if (PRODUCT_FEATURES.slack !== true) {
1720
+ canonicalChatsCache = [];
1721
+ slackChatsCache = [];
1722
+ canonicalChatsFingerprint = canonicalChatsFingerprintOf([], []);
1723
+ return { relay: canonicalChatsCache, slack: slackChatsCache };
1724
+ }
1717
1725
  if (!deviceToken()) return { relay: canonicalChatsCache, slack: slackChatsCache };
1718
1726
  try {
1719
1727
  const client = await relayClient();
@@ -7319,6 +7327,9 @@ ipcMain.handle("relay:canonicalChat", async (event, input) => {
7319
7327
  }
7320
7328
  const { chatId: id, surface, includeSlack } = canonicalChatIpcInput(input);
7321
7329
  if (!id) return { ok: false, error: "Missing channel id." };
7330
+ if (PRODUCT_FEATURES.slack !== true && (surface === "slack" || includeSlack)) {
7331
+ return { ok: false, error: "Slack is available only to Relay developer accounts on dev." };
7332
+ }
7322
7333
  try {
7323
7334
  const client = await relayClient();
7324
7335
  const chat = await client.chat(id, { surface, includeSlack });
@@ -7333,6 +7344,9 @@ ipcMain.handle("relay:canonicalChatRead", async (event, input) => {
7333
7344
  }
7334
7345
  const { chatId: id, surface, includeSlack } = canonicalChatIpcInput(input);
7335
7346
  if (!id) return { ok: false, error: "Missing channel id." };
7347
+ if (PRODUCT_FEATURES.slack !== true && (surface === "slack" || includeSlack)) {
7348
+ return { ok: false, error: "Slack is available only to Relay developer accounts on dev." };
7349
+ }
7336
7350
  if (!chatReadPresenceIsAvailable(win)) {
7337
7351
  deferChatRead();
7338
7352
  return { ok: false, deferred: true };
@@ -7976,10 +7990,16 @@ ipcMain.handle("relay:contactsSearch", (_e, q) => groupCall((c) => c.searchConta
7976
7990
  // Settings tab: account card + the sign-out / switch-account lifecycle.
7977
7991
  ipcMain.handle("relay:accountInfo", () => accountInfo());
7978
7992
  ipcMain.handle("relay:slackConnection", async () => {
7993
+ if (PRODUCT_FEATURES.slack !== true) {
7994
+ return { ok: false, error: "Slack is available only to Relay developer accounts on dev." };
7995
+ }
7979
7996
  try { return { ok: true, connection: await (await relayClient()).slackConnection() }; }
7980
7997
  catch (error) { return { ok: false, error: (error && error.message) || String(error) }; }
7981
7998
  });
7982
7999
  ipcMain.handle("relay:slackConnect", async (_event, input = {}) => {
8000
+ if (PRODUCT_FEATURES.slack !== true) {
8001
+ return { ok: false, error: "Slack is available only to Relay developer accounts on dev." };
8002
+ }
7983
8003
  try {
7984
8004
  const client = await relayClient();
7985
8005
  const result = input?.reconnect
@@ -7997,6 +8017,9 @@ ipcMain.handle("relay:slackConnect", async (_event, input = {}) => {
7997
8017
  }
7998
8018
  });
7999
8019
  ipcMain.handle("relay:slackDisconnect", async () => {
8020
+ if (PRODUCT_FEATURES.slack !== true) {
8021
+ return { ok: false, error: "Slack is available only to Relay developer accounts on dev." };
8022
+ }
8000
8023
  try { return { ok: true, result: await (await relayClient()).disconnectSlack() }; }
8001
8024
  catch (error) { return { ok: false, error: (error && error.message) || String(error) }; }
8002
8025
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.410",
3
+ "version": "0.1.412",
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": {
@@ -33,6 +33,13 @@ function productFeatures(options = {}) {
33
33
  environment,
34
34
  developer,
35
35
  requests: developer,
36
+ // Slack is an internal proving surface alongside Tasks. Staging and
37
+ // production exercise the customer product, even for developer accounts.
38
+ slack: developer,
39
+ // Human mentions are ordinary conversation affordances. Agent mentions
40
+ // start local runs, so they retain the same boundary as Tasks.
41
+ peopleMentions: true,
42
+ agentMentions: developer,
36
43
  // Cowork is intentionally unavailable. Do not add an override here: its
37
44
  // former transport inspected Claude Desktop process/session credentials.
38
45
  cowork: false,