@awebai/claude-channel 1.4.3 → 1.4.5

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.5",
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;
@@ -26377,6 +26403,11 @@ var SenderTrustManager = class {
26377
26403
  if (!recipientDID || !selfDID) {
26378
26404
  return status;
26379
26405
  }
26406
+ if (recipientDID.startsWith("did:aw:")) {
26407
+ if (recipientStableID)
26408
+ return status;
26409
+ return selfStableID ? "identity_mismatch" : status;
26410
+ }
26380
26411
  return recipientDID === selfDID ? status : "identity_mismatch";
26381
26412
  }
26382
26413
  async checkStableIdentityRegistry(status, trustAddress, fromDID, fromStableID) {
@@ -26399,7 +26430,7 @@ var SenderTrustManager = class {
26399
26430
  if (!status || status !== "verified" && status !== "verified_custodial" || !fromDID || !trustAddress || !meta3.resolved) {
26400
26431
  return { status, stored: false };
26401
26432
  }
26402
- if (meta3.lifetime === "ephemeral") {
26433
+ if (meta3.identityScope === "local") {
26403
26434
  let removed = store.removeAddress(trustAddress);
26404
26435
  if (rawAddress && rawAddress !== trustAddress) {
26405
26436
  removed = store.removeAddress(rawAddress) || removed;
@@ -26426,7 +26457,7 @@ var SenderTrustManager = class {
26426
26457
  }
26427
26458
  }
26428
26459
  }
26429
- const pinResult = store.checkPin(trustAddress, pinKey, meta3.lifetime);
26460
+ const pinResult = store.checkPin(trustAddress, pinKey, meta3.identityScope);
26430
26461
  switch (pinResult) {
26431
26462
  case "new":
26432
26463
  store.storePin(pinKey, trustAddress, "", "");
@@ -26552,7 +26583,7 @@ var SenderTrustManager = class {
26552
26583
  const rawAddress = address.trim();
26553
26584
  const trustAddress = this.canonicalTrustAddress(rawAddress);
26554
26585
  if (!trustAddress) {
26555
- return { lifetime: "persistent", custody: "self", resolved: false };
26586
+ return { identityScope: "global", custody: "self", resolved: false };
26556
26587
  }
26557
26588
  const cached2 = this.metaCache.get(trustAddress);
26558
26589
  if (cached2)
@@ -26560,7 +26591,7 @@ var SenderTrustManager = class {
26560
26591
  try {
26561
26592
  const identity = await this.resolveIdentity(rawAddress);
26562
26593
  const meta3 = {
26563
- lifetime: identity.lifetime || "persistent",
26594
+ identityScope: identity.identityScope,
26564
26595
  custody: identity.custody || "self",
26565
26596
  controllerDid: identity.controllerDid,
26566
26597
  resolved: true
@@ -26568,7 +26599,7 @@ var SenderTrustManager = class {
26568
26599
  this.metaCache.set(trustAddress, meta3);
26569
26600
  return meta3;
26570
26601
  } catch {
26571
- return { lifetime: "persistent", custody: "self", resolved: false };
26602
+ return { identityScope: "global", custody: "self", resolved: false };
26572
26603
  }
26573
26604
  }
26574
26605
  async resolveIdentity(address) {
@@ -26588,7 +26619,7 @@ var SenderTrustManager = class {
26588
26619
  stableID: response.did_aw,
26589
26620
  address: response.address || `${this.teamID}/${trimmed}`,
26590
26621
  custody: "self",
26591
- lifetime: response.lifetime || "ephemeral"
26622
+ identityScope: normalizeIdentityScope(response.identity_scope, response.lifetime, "local")
26592
26623
  };
26593
26624
  }
26594
26625
  };
@@ -26659,9 +26690,11 @@ function escapeJSON3(s) {
26659
26690
  }
26660
26691
 
26661
26692
  // ../channel-core/dist/channel.js
26662
- import { join as join2 } from "node:path";
26693
+ import { dirname as dirname2, join as join2 } from "node:path";
26663
26694
  import { homedir } from "node:os";
26664
26695
  var DEFAULT_PIN_STORE_PATH = join2(homedir(), ".config", "aw", "known_agents.yaml");
26696
+ var DEFAULT_DELIVERY_STORE_PATH = join2(homedir(), ".config", "aw", "channel-delivered-ids.json");
26697
+ var DELIVERED_IDS_TTL_MS = 24 * 60 * 60 * 1e3;
26665
26698
 
26666
26699
  // src/index.ts
26667
26700
  var PIN_STORE_PATH = join3(homedir2(), ".config", "aw", "known_agents.yaml");
@@ -26719,7 +26752,7 @@ async function main() {
26719
26752
  },
26720
26753
  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
26754
 
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.
26755
+ 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
26756
 
26724
26757
  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
26758
  If you need more time, send a status update the same way.
@@ -26777,7 +26810,10 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26777
26810
  if (isSelfSender(msg.from_alias, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26778
26811
  const conversationID = msg.conversation_id || event.conversation_id;
26779
26812
  const key = dispatchKey("mail", conversationID, msg.message_id);
26780
- if (dispatched.has(key)) continue;
26813
+ if (dispatched.has(key)) {
26814
+ if (!msg.read_at) await ackMessage(client, msg.message_id);
26815
+ continue;
26816
+ }
26781
26817
  dispatched.add(key);
26782
26818
  const from = senderDisplayAddress(msg.from_alias, msg.from_address);
26783
26819
  const tofu = await trust.normalizeTrust(
@@ -26807,6 +26843,7 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26807
26843
  method: "notifications/claude/channel",
26808
26844
  params: { content: msg.body, meta: meta3 }
26809
26845
  });
26846
+ await ackMessage(client, msg.message_id);
26810
26847
  }
26811
26848
  if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26812
26849
  break;
@@ -26815,11 +26852,15 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26815
26852
  if (!event.session_id) break;
26816
26853
  const messages = await fetchHistory(client, event.session_id, true, CHAT_FETCH_LIMIT, event.message_id);
26817
26854
  let pinsDirty = false;
26855
+ let lastMessageId;
26818
26856
  for (const msg of messages) {
26819
26857
  if (isSelfSender(msg.from_agent, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26820
26858
  const conversationID = msg.conversation_id || event.conversation_id || event.session_id;
26821
26859
  const key = dispatchKey("chat", conversationID, msg.message_id);
26822
- if (dispatched.has(key)) continue;
26860
+ if (dispatched.has(key)) {
26861
+ lastMessageId = msg.message_id;
26862
+ continue;
26863
+ }
26823
26864
  dispatched.add(key);
26824
26865
  const from = senderDisplayAddress(msg.from_agent, msg.from_address);
26825
26866
  const tofu = await trust.normalizeTrust(
@@ -26850,7 +26891,9 @@ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, eve
26850
26891
  method: "notifications/claude/channel",
26851
26892
  params: { content: msg.body, meta: meta3 }
26852
26893
  });
26894
+ lastMessageId = msg.message_id;
26853
26895
  }
26896
+ if (lastMessageId) await markRead(client, event.session_id, lastMessageId);
26854
26897
  if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26855
26898
  break;
26856
26899
  }
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.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",