@awebai/claude-channel 1.4.3 → 1.4.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "aweb-channel",
3
3
  "description": "aweb agent coordination channel — receive mail, chat, tasks, and control signals from your agent team in real time.",
4
- "version": "1.4.3",
4
+ "version": "1.4.4",
5
5
  "author": {
6
6
  "name": "awebai"
7
7
  },
package/dist/index.js CHANGED
@@ -22437,7 +22437,9 @@ async function* streamAgentEvents(client, signal) {
22437
22437
  } catch (err2) {
22438
22438
  if (signal.aborted)
22439
22439
  return;
22440
- console.error(`[aw-channel] events stream parse failed: ${err2}`);
22440
+ if (!isExpectedStreamTermination(err2)) {
22441
+ console.error(`[aw-channel] events stream parse failed: ${err2}`);
22442
+ }
22441
22443
  await sleep(1e3, signal);
22442
22444
  } finally {
22443
22445
  resp.body?.cancel().catch(() => {
@@ -22523,6 +22525,13 @@ function parseAgentEvent(eventName, data) {
22523
22525
  return { type: eventName };
22524
22526
  }
22525
22527
  }
22528
+ function isExpectedStreamTermination(err2) {
22529
+ if (!(err2 instanceof Error))
22530
+ return false;
22531
+ const name = err2.name.toLowerCase();
22532
+ const message = err2.message.toLowerCase();
22533
+ return name === "aborterror" || message === "terminated";
22534
+ }
22526
22535
  function sleep(ms, signal) {
22527
22536
  return new Promise((resolve) => {
22528
22537
  if (signal.aborted) {
@@ -22879,6 +22888,9 @@ function hydrateAddressesFromSignedPayload(msg) {
22879
22888
  } catch {
22880
22889
  }
22881
22890
  }
22891
+ async function ackMessage(client, messageId) {
22892
+ await client.post(`/v1/messages/${encodeURIComponent(messageId)}/ack`);
22893
+ }
22882
22894
  async function verifyInboxMessage(msg) {
22883
22895
  if (msg.signed_payload && msg.signature && msg.from_did) {
22884
22896
  const status2 = await verifySignedPayload(msg.signed_payload, msg.signature, msg.from_did, msg.signing_key_id || "");
@@ -22954,6 +22966,9 @@ function hydrateAddressesFromSignedPayload2(msg) {
22954
22966
  } catch {
22955
22967
  }
22956
22968
  }
22969
+ async function markRead(client, sessionId, upToMessageId) {
22970
+ await client.post(`/v1/chat/sessions/${encodeURIComponent(sessionId)}/read`, { up_to_message_id: upToMessageId });
22971
+ }
22957
22972
  async function verifyChatMessage(msg) {
22958
22973
  if (msg.signed_payload && msg.signature && msg.from_did) {
22959
22974
  const status2 = await verifySignedPayload(msg.signed_payload, msg.signature, msg.from_did, msg.signing_key_id || "");
@@ -25749,9 +25764,9 @@ import { mkdir, open, rename, rm } from "node:fs/promises";
25749
25764
  var PinStore = class _PinStore {
25750
25765
  pins = /* @__PURE__ */ new Map();
25751
25766
  addresses = /* @__PURE__ */ new Map();
25752
- /** Check whether a DID matches the stored pin for an address. */
25753
- checkPin(address, did, lifetime) {
25754
- if (lifetime === "ephemeral")
25767
+ /** Check whether a DID matches the stored pin for a global address. */
25768
+ checkPin(address, did, identityScope) {
25769
+ if (identityScope === "local")
25755
25770
  return "skipped";
25756
25771
  const pinnedDID = this.addresses.get(address);
25757
25772
  if (pinnedDID === void 0)
@@ -25947,7 +25962,7 @@ var RegistryResolver = class {
25947
25962
  address: `${split2.domain}/${split2.name}`,
25948
25963
  controllerDid: authority.controllerDid,
25949
25964
  custody: "self",
25950
- lifetime: "persistent"
25965
+ identityScope: "global"
25951
25966
  };
25952
25967
  }
25953
25968
  async discoverRegistry(domain2) {
@@ -26338,6 +26353,17 @@ function escapeJSON2(s) {
26338
26353
  // ../channel-core/dist/identity/trust.js
26339
26354
  etc.sha512Sync = (...m) => sha512(etc.concatBytes(...m));
26340
26355
  var ANNOUNCEMENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
26356
+ function normalizeIdentityScope(identityScope, legacyLifetime, defaultScope) {
26357
+ const normalizedScope = (identityScope || "").trim().toLowerCase();
26358
+ if (normalizedScope === "global" || normalizedScope === "local")
26359
+ return normalizedScope;
26360
+ const normalizedLegacy = (legacyLifetime || "").trim().toLowerCase();
26361
+ if (normalizedLegacy === "persistent")
26362
+ return "global";
26363
+ if (normalizedLegacy === "ephemeral")
26364
+ return "local";
26365
+ return defaultScope;
26366
+ }
26341
26367
  var SenderTrustManager = class {
26342
26368
  client;
26343
26369
  registry;
@@ -26399,7 +26425,7 @@ var SenderTrustManager = class {
26399
26425
  if (!status || status !== "verified" && status !== "verified_custodial" || !fromDID || !trustAddress || !meta3.resolved) {
26400
26426
  return { status, stored: false };
26401
26427
  }
26402
- if (meta3.lifetime === "ephemeral") {
26428
+ if (meta3.identityScope === "local") {
26403
26429
  let removed = store.removeAddress(trustAddress);
26404
26430
  if (rawAddress && rawAddress !== trustAddress) {
26405
26431
  removed = store.removeAddress(rawAddress) || removed;
@@ -26426,7 +26452,7 @@ var SenderTrustManager = class {
26426
26452
  }
26427
26453
  }
26428
26454
  }
26429
- const pinResult = store.checkPin(trustAddress, pinKey, meta3.lifetime);
26455
+ const pinResult = store.checkPin(trustAddress, pinKey, meta3.identityScope);
26430
26456
  switch (pinResult) {
26431
26457
  case "new":
26432
26458
  store.storePin(pinKey, trustAddress, "", "");
@@ -26552,7 +26578,7 @@ var SenderTrustManager = class {
26552
26578
  const rawAddress = address.trim();
26553
26579
  const trustAddress = this.canonicalTrustAddress(rawAddress);
26554
26580
  if (!trustAddress) {
26555
- return { lifetime: "persistent", custody: "self", resolved: false };
26581
+ return { identityScope: "global", custody: "self", resolved: false };
26556
26582
  }
26557
26583
  const cached2 = this.metaCache.get(trustAddress);
26558
26584
  if (cached2)
@@ -26560,7 +26586,7 @@ var SenderTrustManager = class {
26560
26586
  try {
26561
26587
  const identity = await this.resolveIdentity(rawAddress);
26562
26588
  const meta3 = {
26563
- lifetime: identity.lifetime || "persistent",
26589
+ identityScope: identity.identityScope,
26564
26590
  custody: identity.custody || "self",
26565
26591
  controllerDid: identity.controllerDid,
26566
26592
  resolved: true
@@ -26568,7 +26594,7 @@ var SenderTrustManager = class {
26568
26594
  this.metaCache.set(trustAddress, meta3);
26569
26595
  return meta3;
26570
26596
  } catch {
26571
- return { lifetime: "persistent", custody: "self", resolved: false };
26597
+ return { identityScope: "global", custody: "self", resolved: false };
26572
26598
  }
26573
26599
  }
26574
26600
  async resolveIdentity(address) {
@@ -26588,7 +26614,7 @@ var SenderTrustManager = class {
26588
26614
  stableID: response.did_aw,
26589
26615
  address: response.address || `${this.teamID}/${trimmed}`,
26590
26616
  custody: "self",
26591
- lifetime: response.lifetime || "ephemeral"
26617
+ identityScope: normalizeIdentityScope(response.identity_scope, response.lifetime, "local")
26592
26618
  };
26593
26619
  }
26594
26620
  };
@@ -26659,9 +26685,11 @@ function escapeJSON3(s) {
26659
26685
  }
26660
26686
 
26661
26687
  // ../channel-core/dist/channel.js
26662
- import { join as join2 } from "node:path";
26688
+ import { dirname as dirname2, join as join2 } from "node:path";
26663
26689
  import { homedir } from "node:os";
26664
26690
  var DEFAULT_PIN_STORE_PATH = join2(homedir(), ".config", "aw", "known_agents.yaml");
26691
+ var DEFAULT_DELIVERY_STORE_PATH = join2(homedir(), ".config", "aw", "channel-delivered-ids.json");
26692
+ var DELIVERED_IDS_TTL_MS = 24 * 60 * 60 * 1e3;
26665
26693
 
26666
26694
  // src/index.ts
26667
26695
  var PIN_STORE_PATH = join3(homedir2(), ".config", "aw", "known_agents.yaml");
@@ -26719,7 +26747,7 @@ async function main() {
26719
26747
  },
26720
26748
  instructions: `Events from the aweb channel are coordination messages from other agents in your team. Use the aw CLI to respond, not MCP tools.
26721
26749
 
26722
- Mail events (type="mail") are async. Read them and act if needed. Channel delivery does not acknowledge or mark the message handled. Reply with: aw mail reply <message_id> --body "<reply>". Replying marks the source message handled after the reply is sent. Running aw mail inbox explicitly marks displayed unread mail as read; aw mail show is read-only.
26750
+ Mail events (type="mail") are async. Read them and act if needed. Confirmed channel delivery marks the source message handled so reconnects do not replay stale mail. Reply with: aw mail reply <message_id> --body "<reply>".
26723
26751
 
26724
26752
  Chat events (type="chat") may have sender_waiting="true", meaning the sender is blocked waiting for your reply. Respond promptly with: aw chat send-and-wait <from> "<reply>"
26725
26753
  If you need more time, send a status update the same way.
@@ -26777,7 +26805,10 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26777
26805
  if (isSelfSender(msg.from_alias, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26778
26806
  const conversationID = msg.conversation_id || event.conversation_id;
26779
26807
  const key = dispatchKey("mail", conversationID, msg.message_id);
26780
- if (dispatched.has(key)) continue;
26808
+ if (dispatched.has(key)) {
26809
+ if (!msg.read_at) await ackMessage(client, msg.message_id);
26810
+ continue;
26811
+ }
26781
26812
  dispatched.add(key);
26782
26813
  const from = senderDisplayAddress(msg.from_alias, msg.from_address);
26783
26814
  const tofu = await trust.normalizeTrust(
@@ -26807,6 +26838,7 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26807
26838
  method: "notifications/claude/channel",
26808
26839
  params: { content: msg.body, meta: meta3 }
26809
26840
  });
26841
+ await ackMessage(client, msg.message_id);
26810
26842
  }
26811
26843
  if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26812
26844
  break;
@@ -26815,11 +26847,15 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26815
26847
  if (!event.session_id) break;
26816
26848
  const messages = await fetchHistory(client, event.session_id, true, CHAT_FETCH_LIMIT, event.message_id);
26817
26849
  let pinsDirty = false;
26850
+ let lastMessageId;
26818
26851
  for (const msg of messages) {
26819
26852
  if (isSelfSender(msg.from_agent, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26820
26853
  const conversationID = msg.conversation_id || event.conversation_id || event.session_id;
26821
26854
  const key = dispatchKey("chat", conversationID, msg.message_id);
26822
- if (dispatched.has(key)) continue;
26855
+ if (dispatched.has(key)) {
26856
+ lastMessageId = msg.message_id;
26857
+ continue;
26858
+ }
26823
26859
  dispatched.add(key);
26824
26860
  const from = senderDisplayAddress(msg.from_agent, msg.from_address);
26825
26861
  const tofu = await trust.normalizeTrust(
@@ -26850,7 +26886,9 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26850
26886
  method: "notifications/claude/channel",
26851
26887
  params: { content: msg.body, meta: meta3 }
26852
26888
  });
26889
+ lastMessageId = msg.message_id;
26853
26890
  }
26891
+ if (lastMessageId) await markRead(client, event.session_id, lastMessageId);
26854
26892
  if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26855
26893
  break;
26856
26894
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/claude-channel",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",