relay-companion 0.1.43 → 0.1.45

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/overlay/main.cjs CHANGED
@@ -44,6 +44,7 @@ const {
44
44
  recoverInterruptedAttentionPrefs,
45
45
  } = require("./visibility.cjs");
46
46
  const { withJsonLock } = require("../src/state-lock.cjs");
47
+ const { canonicalInboxItemId, packetIdsForCanonicalItem } = require("../src/inbox-item-id.cjs");
47
48
  const {
48
49
  reinforceSpacePresence,
49
50
  resetWindowZoom,
@@ -660,18 +661,23 @@ async function deletePacket(packetId) {
660
661
  if (!packetId) return { ok: false, error: "Missing relay id." };
661
662
  const row = rowById(packetId);
662
663
  if (!row) return { ok: true };
664
+ const itemId = canonicalInboxItemId(packetId, row);
663
665
  try {
664
666
  // Server first: a transient network/API error must never make a recoverable
665
667
  // item disappear locally without actually entering Recently Deleted.
666
668
  const client = await relayClient();
667
- await client.deleteInboxItem(packetId, { idempotencyKey: idempotencyKey("inbox_delete") });
669
+ await client.deleteInboxItem(itemId, { idempotencyKey: idempotencyKey("inbox_delete") });
670
+ let removedPacketIds = [];
668
671
  withJsonLock(STATE_PATH, () => {
669
672
  const store = readStore();
670
- if (store.packets) delete store.packets[packetId];
673
+ removedPacketIds = packetIdsForCanonicalItem(store.packets, itemId);
674
+ for (const id of removedPacketIds) delete store.packets[id];
671
675
  writeStateAtomic(store);
672
676
  });
673
- presentedRelayIds.delete(packetId);
674
- activeAttentionIds.delete(packetId);
677
+ for (const id of removedPacketIds) {
678
+ presentedRelayIds.delete(id);
679
+ activeAttentionIds.delete(id);
680
+ }
675
681
  writeOverlayPrefs();
676
682
  await pushInbox(true);
677
683
  return { ok: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "Relay companion: connects local coding agents to Relay tasks, approvals, and connector tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Old companion releases staged task notifications with a timestamp suffix.
5
+ * The API intentionally uses the stable condition id, so normalize both old
6
+ * and current rows before deleting, suppressing, or comparing them.
7
+ */
8
+ function canonicalInboxItemId(packetId, row = {}) {
9
+ const kind = String(row.relayNotificationKind || "");
10
+ if (kind === "human_question" && row.messageId) {
11
+ return `human_question:${row.messageId}`;
12
+ }
13
+ if (["task_completed", "task_cancelled", "task_failed"].includes(kind) && row.taskId) {
14
+ return `${kind}:${row.taskId}`;
15
+ }
16
+ return String(packetId || "");
17
+ }
18
+
19
+ function packetIdsForCanonicalItem(packets, canonicalItemId) {
20
+ return Object.entries(packets || {})
21
+ .filter(([packetId, row]) => canonicalInboxItemId(packetId, row) === canonicalItemId)
22
+ .map(([packetId]) => packetId);
23
+ }
24
+
25
+ module.exports = { canonicalInboxItemId, packetIdsForCanonicalItem };
@@ -4,6 +4,9 @@ import path from "node:path";
4
4
  import { currentUser, readConfig, webUrl } from "./config.js";
5
5
  import { relayBinPath } from "./install.js";
6
6
  import { withJsonLock } from "./state-lock.cjs";
7
+ import inboxItemIds from "./inbox-item-id.cjs";
8
+
9
+ const { canonicalInboxItemId } = inboxItemIds;
7
10
 
8
11
  const DEFAULT_COMPANION_STATE = {
9
12
  version: 1,
@@ -249,6 +252,24 @@ function writeCompanionState(statePath, state) {
249
252
  fs.renameSync(tmp, statePath);
250
253
  }
251
254
 
255
+ /** Apply server tombstones to rows already staged by this companion. */
256
+ export function removeSuppressedCompanionItems(itemIds, { statePath = companionStatePath() } = {}) {
257
+ const suppressed = new Set((itemIds || []).filter(Boolean).map(String));
258
+ if (!suppressed.size) return 0;
259
+ return withJsonLock(statePath, () => {
260
+ const state = readCompanionState(statePath);
261
+ state.packets ||= {};
262
+ let removed = 0;
263
+ for (const [id, row] of Object.entries(state.packets)) {
264
+ if (!suppressed.has(canonicalInboxItemId(id, row))) continue;
265
+ delete state.packets[id];
266
+ removed += 1;
267
+ }
268
+ if (removed) writeCompanionState(statePath, state);
269
+ return removed;
270
+ });
271
+ }
272
+
252
273
  function accountMarker({ user = currentUser(), deviceId = readConfig().deviceId || "" } = {}) {
253
274
  return {
254
275
  userId: user?.id || "",
@@ -14,6 +14,7 @@ import {
14
14
  defaultStageRelayCompanionItem,
15
15
  freshNotifications,
16
16
  markNotificationsProcessed,
17
+ removeSuppressedCompanionItems,
17
18
  reconcileAnsweredQuestions,
18
19
  stagePlainRelayItem as defaultStagePlainRelayItem,
19
20
  } from "./notifications.js";
@@ -171,6 +172,8 @@ async function pollHumanNotifications({ client, ledger, stageCompanionItem, stag
171
172
  client.listConnectors(),
172
173
  ]);
173
174
  const events = await pollVisibleTaskEvents({ client, ledger, tasks, log });
175
+ const removed = removeSuppressedCompanionItems(relays.inboxState?.suppressedItemIds || []);
176
+ if (removed) log(`removed ${removed} Relay item(s) moved to Recently Deleted elsewhere`);
174
177
  const notifications = buildHumanNotifications({
175
178
  user: me.user,
176
179
  tasks: tasks.tasks || [],