@awebai/claude-channel 1.4.10 → 1.4.11

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.10",
4
+ "version": "1.4.11",
5
5
  "author": {
6
6
  "name": "awebai"
7
7
  },
package/dist/index.js CHANGED
@@ -21260,10 +21260,7 @@ var StdioServerTransport = class {
21260
21260
  };
21261
21261
 
21262
21262
  // src/index.ts
21263
- import { join as join3 } from "node:path";
21264
- import { homedir as homedir2 } from "node:os";
21265
21263
  import { realpathSync } from "node:fs";
21266
- import { readFile as readFile4 } from "node:fs/promises";
21267
21264
  import { fileURLToPath, pathToFileURL } from "node:url";
21268
21265
 
21269
21266
  // ../channel-core/dist/api/client.js
@@ -26694,97 +26691,114 @@ function escapeJSON3(s) {
26694
26691
  return result;
26695
26692
  }
26696
26693
 
26694
+ // ../channel-core/dist/local_aw.js
26695
+ import { execFile } from "node:child_process";
26696
+ import { promisify } from "node:util";
26697
+ var execFileAsync = promisify(execFile);
26698
+ function createLocalAWDecryptProvider(options) {
26699
+ const awCommand = options.awCommand || process.env.AW_BIN || "aw";
26700
+ return {
26701
+ async mailMessage(messageID) {
26702
+ const id = messageID.trim();
26703
+ if (!id)
26704
+ return null;
26705
+ const { stdout } = await execFileAsync(awCommand, ["mail", "show", "--message-id", id, "--json"], { cwd: options.workdir, timeout: 15e3, maxBuffer: 1024 * 1024 });
26706
+ const payload = parseJSONOutput(stdout);
26707
+ return (payload.messages || []).find((msg) => msg.message_id === id) || null;
26708
+ },
26709
+ async chatMessage(sessionID, messageID) {
26710
+ const session = sessionID.trim();
26711
+ const id = messageID.trim();
26712
+ if (!session || !id)
26713
+ return null;
26714
+ const { stdout } = await execFileAsync(awCommand, ["chat", "history", "--session-id", session, "--message-id", id, "--limit", "1", "--json"], { cwd: options.workdir, timeout: 15e3, maxBuffer: 1024 * 1024 });
26715
+ const payload = parseJSONOutput(stdout);
26716
+ return (payload.messages || []).find((msg) => msg.message_id === id) || null;
26717
+ }
26718
+ };
26719
+ }
26720
+ function parseJSONOutput(stdout) {
26721
+ const trimmed = stdout.trim();
26722
+ if (!trimmed)
26723
+ throw new Error("aw returned empty JSON output");
26724
+ const start = trimmed.indexOf("{");
26725
+ if (start < 0)
26726
+ throw new Error("aw JSON output did not contain an object");
26727
+ return JSON.parse(trimmed.slice(start));
26728
+ }
26729
+
26697
26730
  // ../channel-core/dist/channel.js
26698
26731
  import { dirname as dirname2, join as join2 } from "node:path";
26699
26732
  import { homedir } from "node:os";
26733
+ import { mkdir as mkdir2, readFile as readFile4, writeFile } from "node:fs/promises";
26700
26734
  var DEFAULT_PIN_STORE_PATH = join2(homedir(), ".config", "aw", "known_agents.yaml");
26701
26735
  var DEFAULT_DELIVERY_STORE_PATH = join2(homedir(), ".config", "aw", "channel-delivered-ids.json");
26702
- var DELIVERED_IDS_TTL_MS = 24 * 60 * 60 * 1e3;
26703
-
26704
- // src/index.ts
26705
- var PIN_STORE_PATH = join3(homedir2(), ".config", "aw", "known_agents.yaml");
26706
26736
  var MAX_DISPATCHED_IDS = 2e3;
26737
+ var MAX_DELIVERED_IDS = 5e3;
26738
+ var DELIVERED_IDS_TTL_MS = 24 * 60 * 60 * 1e3;
26707
26739
  var MAIL_FETCH_LIMIT = 200;
26708
26740
  var CHAT_FETCH_LIMIT = 2e3;
26709
- async function loadPinStore2() {
26741
+ async function loadPinStore(path = DEFAULT_PIN_STORE_PATH) {
26710
26742
  try {
26711
- const content = await readFile4(PIN_STORE_PATH, "utf-8");
26743
+ const content = await readFile4(path, "utf-8");
26712
26744
  return PinStore.fromYAML(content);
26713
26745
  } catch {
26714
26746
  return new PinStore();
26715
26747
  }
26716
26748
  }
26717
- function pruneDispatched(dispatched) {
26718
- if (dispatched.size <= MAX_DISPATCHED_IDS) return;
26719
- const excess = dispatched.size - MAX_DISPATCHED_IDS;
26720
- let removed = 0;
26721
- for (const id of dispatched) {
26722
- if (removed >= excess) break;
26723
- dispatched.delete(id);
26724
- removed++;
26749
+ var DeliveryStore = class _DeliveryStore {
26750
+ path;
26751
+ entries;
26752
+ constructor(path, entries) {
26753
+ this.path = path;
26754
+ this.entries = entries;
26725
26755
  }
26726
- }
26727
- function dispatchKey(channel, conversationID, messageID) {
26728
- const conversation = (conversationID || "").trim();
26729
- return `${channel}:${conversation}:${messageID}`;
26730
- }
26731
- async function main() {
26732
- const workdir = process.cwd();
26733
- const config2 = await resolveConfig(workdir);
26734
- const client = new APIClient(config2.baseURL, {
26735
- did: config2.did,
26736
- stableID: config2.stableID,
26737
- signingKey: config2.signingKey,
26738
- teamID: config2.teamID,
26739
- teamCertificateHeader: config2.teamCertificateHeader
26740
- });
26741
- const pinStore = await loadPinStore2();
26742
- const registry2 = new RegistryResolver(fetch, void 0, void 0, {
26743
- fallbackRegistryURL: resolveRegistryFallbackURL2(config2.registryURL)
26744
- });
26745
- const trust = new SenderTrustManager(
26746
- client,
26747
- registry2,
26748
- config2.teamID,
26749
- config2.did,
26750
- config2.stableID
26751
- );
26752
- const mcp = new Server(
26753
- { name: "aweb", version: "0.1.0" },
26754
- {
26755
- capabilities: {
26756
- experimental: { "claude/channel": {} }
26757
- },
26758
- instructions: `Events from the aweb channel are coordination messages from other agents in your team. Use the aw CLI to respond, not MCP tools.
26759
-
26760
- 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>".
26761
-
26762
- 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>"
26763
- If you need more time, send a status update the same way.
26764
-
26765
- Control events (type="control") are operational signals. On "pause", stop current work and wait. On "resume", continue. On "interrupt", stop and await new instructions.`
26756
+ static async load(path = DEFAULT_DELIVERY_STORE_PATH) {
26757
+ try {
26758
+ const raw = JSON.parse(await readFile4(path, "utf-8"));
26759
+ const now = Date.now();
26760
+ const entries = /* @__PURE__ */ new Map();
26761
+ for (const [key, value] of Object.entries(raw)) {
26762
+ const timestamp2 = typeof value === "number" ? value : Date.parse(value);
26763
+ if (Number.isFinite(timestamp2) && now - timestamp2 <= DELIVERED_IDS_TTL_MS) {
26764
+ entries.set(key, timestamp2);
26765
+ }
26766
+ }
26767
+ return new _DeliveryStore(path, entries);
26768
+ } catch {
26769
+ return new _DeliveryStore(path, /* @__PURE__ */ new Map());
26766
26770
  }
26767
- );
26768
- const transport = new StdioServerTransport();
26769
- await mcp.connect(transport);
26770
- const abort = new AbortController();
26771
- process.on("SIGINT", () => abort.abort());
26772
- process.on("SIGTERM", () => abort.abort());
26773
- await startEventLoop(
26774
- mcp,
26775
- client,
26776
- pinStore,
26777
- trust,
26778
- {
26779
- alias: config2.alias,
26780
- address: config2.address,
26781
- did: config2.did,
26782
- stableID: config2.stableID
26783
- },
26784
- abort.signal
26785
- );
26786
- }
26787
- function resolveRegistryFallbackURL2(identityRegistryURL = "") {
26771
+ }
26772
+ has(key) {
26773
+ this.prune();
26774
+ return this.entries.has(key);
26775
+ }
26776
+ mark(key) {
26777
+ this.entries.set(key, Date.now());
26778
+ this.prune();
26779
+ }
26780
+ async save() {
26781
+ this.prune();
26782
+ await mkdir2(dirname2(this.path), { recursive: true });
26783
+ const payload = Object.fromEntries([...this.entries.entries()].map(([key, value]) => [key, new Date(value).toISOString()]));
26784
+ await writeFile(this.path, `${JSON.stringify(payload, null, 2)}
26785
+ `, "utf-8");
26786
+ }
26787
+ prune() {
26788
+ const now = Date.now();
26789
+ for (const [key, value] of this.entries) {
26790
+ if (now - value > DELIVERED_IDS_TTL_MS)
26791
+ this.entries.delete(key);
26792
+ }
26793
+ if (this.entries.size <= MAX_DELIVERED_IDS)
26794
+ return;
26795
+ const sorted = [...this.entries.entries()].sort((a, b) => a[1] - b[1]);
26796
+ for (const [key] of sorted.slice(0, this.entries.size - MAX_DELIVERED_IDS)) {
26797
+ this.entries.delete(key);
26798
+ }
26799
+ }
26800
+ };
26801
+ function resolveRegistryFallbackURL(identityRegistryURL = "") {
26788
26802
  const envRegistryURL = (process.env.AWID_REGISTRY_URL || "").trim();
26789
26803
  if (envRegistryURL) {
26790
26804
  if (envRegistryURL.toLowerCase() === "local") {
@@ -26795,183 +26809,299 @@ function resolveRegistryFallbackURL2(identityRegistryURL = "") {
26795
26809
  const configuredRegistryURL = identityRegistryURL.trim();
26796
26810
  return configuredRegistryURL || void 0;
26797
26811
  }
26798
- async function startEventLoop(mcp, client, pinStore, trust, self, signal) {
26812
+ function createRegistryResolver(registryURL = "") {
26813
+ return new RegistryResolver(fetch, void 0, void 0, {
26814
+ fallbackRegistryURL: resolveRegistryFallbackURL(registryURL)
26815
+ });
26816
+ }
26817
+ function createChannelClient(config2) {
26818
+ return new APIClient(config2.baseURL, {
26819
+ did: config2.did,
26820
+ stableID: config2.stableID,
26821
+ signingKey: config2.signingKey,
26822
+ teamID: config2.teamID,
26823
+ teamCertificateHeader: config2.teamCertificateHeader
26824
+ });
26825
+ }
26826
+ async function startChannelLoop(options) {
26799
26827
  const dispatched = /* @__PURE__ */ new Set();
26800
- for await (const event of streamAgentEvents(client, signal)) {
26828
+ const deliveryStore = options.deliveryStore || await DeliveryStore.load(DEFAULT_DELIVERY_STORE_PATH);
26829
+ const localDecrypt = options.localDecrypt || (options.workdir ? createLocalAWDecryptProvider({ workdir: options.workdir, awCommand: options.awCommand }) : void 0);
26830
+ const log = options.log || (() => {
26831
+ });
26832
+ for await (const event of streamAgentEvents(options.client, options.signal)) {
26801
26833
  try {
26802
- await dispatchEvent(mcp, client, pinStore, trust, self, dispatched, event);
26834
+ await dispatchAgentEvent({ ...options, deliveryStore, localDecrypt }, dispatched, event);
26803
26835
  pruneDispatched(dispatched);
26804
26836
  } catch (err2) {
26805
- console.error(`[aw-channel] dispatch error: ${err2}`);
26837
+ log(`[aw-channel] dispatch error: ${err2}`);
26806
26838
  }
26807
26839
  }
26808
26840
  }
26809
- async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, event) {
26841
+ async function dispatchAgentEvent(options, dispatched, event) {
26810
26842
  switch (event.type) {
26811
- case "mail_message": {
26812
- const messages = await fetchInbox(client, true, MAIL_FETCH_LIMIT, event.message_id);
26813
- let pinsDirty = false;
26814
- for (const msg of messages) {
26815
- if (isSelfSender(msg.from_alias, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26816
- const conversationID = msg.conversation_id || event.conversation_id;
26817
- const key = dispatchKey("mail", conversationID, msg.message_id);
26818
- if (dispatched.has(key)) {
26819
- if (!msg.read_at) await ackMessage(client, msg.message_id);
26820
- continue;
26821
- }
26822
- dispatched.add(key);
26823
- const from = senderDisplayAddress(msg.from_alias, msg.from_address);
26824
- const tofu = await trust.normalizeTrust(
26825
- pinStore,
26826
- msg.verification_status,
26827
- senderTrustAddress(msg.from_alias, msg.from_address),
26828
- msg.from_did,
26829
- msg.from_stable_id,
26830
- msg.to_did,
26831
- msg.to_stable_id,
26832
- msg.rotation_announcement,
26833
- msg.replacement_announcement,
26834
- msg.signed_from || msg.from_address || msg.from_alias || ""
26835
- );
26836
- msg.verification_status = tofu.status;
26837
- if (tofu.stored) pinsDirty = true;
26838
- const meta3 = {
26839
- type: "mail",
26840
- from,
26841
- message_id: msg.message_id
26842
- };
26843
- if (conversationID) meta3.conversation_id = conversationID;
26844
- if (msg.subject) meta3.subject = msg.subject;
26845
- if (msg.priority && msg.priority !== "normal") meta3.priority = msg.priority;
26846
- if (msg.verification_status) meta3.verified = String(msg.verification_status === "verified" || msg.verification_status === "verified_custodial");
26847
- await mcp.notification({
26848
- method: "notifications/claude/channel",
26849
- params: { content: msg.body, meta: meta3 }
26850
- });
26851
- await ackMessage(client, msg.message_id);
26852
- }
26853
- if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26843
+ case "mail_message":
26844
+ await dispatchMailEvent(options, dispatched, event);
26854
26845
  break;
26855
- }
26856
- case "chat_message": {
26857
- if (!event.session_id) break;
26858
- const messages = await fetchHistory(client, event.session_id, true, CHAT_FETCH_LIMIT, event.message_id);
26859
- let pinsDirty = false;
26860
- let lastMessageId;
26861
- for (const msg of messages) {
26862
- if (isSelfSender(msg.from_agent, msg.from_address, msg.from_stable_id, msg.from_did, self)) continue;
26863
- const conversationID = msg.conversation_id || event.conversation_id || event.session_id;
26864
- const key = dispatchKey("chat", conversationID, msg.message_id);
26865
- if (dispatched.has(key)) {
26866
- lastMessageId = msg.message_id;
26867
- continue;
26868
- }
26869
- dispatched.add(key);
26870
- const from = senderDisplayAddress(msg.from_agent, msg.from_address);
26871
- const tofu = await trust.normalizeTrust(
26872
- pinStore,
26873
- msg.verification_status,
26874
- senderTrustAddress(msg.from_agent, msg.from_address),
26875
- msg.from_did,
26876
- msg.from_stable_id,
26877
- msg.to_did,
26878
- msg.to_stable_id,
26879
- msg.rotation_announcement,
26880
- msg.replacement_announcement,
26881
- msg.signed_from || msg.from_address || msg.from_agent || ""
26882
- );
26883
- msg.verification_status = tofu.status;
26884
- if (tofu.stored) pinsDirty = true;
26885
- const meta3 = {
26886
- type: "chat",
26887
- from,
26888
- session_id: event.session_id,
26889
- message_id: msg.message_id
26890
- };
26891
- if (conversationID) meta3.conversation_id = conversationID;
26892
- if (event.sender_waiting) meta3.sender_waiting = "true";
26893
- if (msg.sender_leaving) meta3.sender_leaving = "true";
26894
- if (msg.verification_status) meta3.verified = String(msg.verification_status === "verified" || msg.verification_status === "verified_custodial");
26895
- await mcp.notification({
26896
- method: "notifications/claude/channel",
26897
- params: { content: msg.body, meta: meta3 }
26898
- });
26899
- lastMessageId = msg.message_id;
26900
- }
26901
- if (lastMessageId) await markRead(client, event.session_id, lastMessageId);
26902
- if (pinsDirty) await pinStore.save(PIN_STORE_PATH);
26846
+ case "chat_message":
26847
+ await dispatchChatEvent(options, dispatched, event);
26903
26848
  break;
26904
- }
26905
26849
  case "control_pause":
26906
26850
  case "control_resume":
26907
- case "control_interrupt": {
26908
- const signalType = event.type.replace("control_", "");
26909
- await mcp.notification({
26910
- method: "notifications/claude/channel",
26911
- params: {
26912
- content: "",
26913
- meta: {
26914
- type: "control",
26915
- signal: signalType,
26916
- signal_id: event.signal_id || ""
26917
- }
26851
+ case "control_interrupt":
26852
+ await options.onAwakening({
26853
+ kind: "control",
26854
+ content: "",
26855
+ deliveryIntent: "steer",
26856
+ meta: {
26857
+ type: "control",
26858
+ signal: event.type.replace("control_", ""),
26859
+ signal_id: event.signal_id || ""
26918
26860
  }
26919
26861
  });
26920
26862
  break;
26921
- }
26922
- case "work_available": {
26923
- await mcp.notification({
26924
- method: "notifications/claude/channel",
26925
- params: {
26926
- content: event.title || "",
26927
- meta: {
26928
- type: "work",
26929
- task_id: event.task_id || ""
26930
- }
26863
+ case "work_available":
26864
+ await options.onAwakening({
26865
+ kind: "work",
26866
+ content: event.title || "",
26867
+ deliveryIntent: "ambient",
26868
+ meta: {
26869
+ type: "work",
26870
+ task_id: event.task_id || ""
26931
26871
  }
26932
26872
  });
26933
26873
  break;
26934
- }
26935
- case "claim_update": {
26936
- await mcp.notification({
26937
- method: "notifications/claude/channel",
26938
- params: {
26939
- content: event.title || "",
26940
- meta: {
26941
- type: "claim",
26942
- task_id: event.task_id || "",
26943
- title: event.title || "",
26944
- status: event.status || ""
26945
- }
26874
+ case "claim_update":
26875
+ await options.onAwakening({
26876
+ kind: "claim",
26877
+ content: event.title || "",
26878
+ deliveryIntent: "ambient",
26879
+ meta: {
26880
+ type: "claim",
26881
+ task_id: event.task_id || "",
26882
+ title: event.title || "",
26883
+ status: event.status || ""
26946
26884
  }
26947
26885
  });
26948
26886
  break;
26949
- }
26950
- case "claim_removed": {
26951
- await mcp.notification({
26952
- method: "notifications/claude/channel",
26953
- params: {
26954
- content: "",
26955
- meta: {
26956
- type: "claim_removed",
26957
- task_id: event.task_id || ""
26958
- }
26887
+ case "claim_removed":
26888
+ await options.onAwakening({
26889
+ kind: "claim_removed",
26890
+ content: "",
26891
+ deliveryIntent: "ambient",
26892
+ meta: {
26893
+ type: "claim_removed",
26894
+ task_id: event.task_id || ""
26959
26895
  }
26960
26896
  });
26961
26897
  break;
26962
- }
26963
26898
  default:
26964
26899
  break;
26965
26900
  }
26966
26901
  }
26902
+ async function dispatchMailEvent(options, dispatched, event) {
26903
+ const messages = await fetchInbox(options.client, true, MAIL_FETCH_LIMIT, event.message_id);
26904
+ let pinsDirty = false;
26905
+ for (const msg of messages) {
26906
+ if (isSelfSender(msg.from_alias, msg.from_address, msg.from_stable_id, msg.from_did, options.self))
26907
+ continue;
26908
+ const conversationID = msg.conversation_id || event.conversation_id;
26909
+ const key = dispatchKey("mail", conversationID, msg.message_id);
26910
+ if (dispatched.has(key) || options.deliveryStore?.has(key)) {
26911
+ if (!msg.read_at)
26912
+ await ackMessage(options.client, msg.message_id);
26913
+ continue;
26914
+ }
26915
+ const from = senderDisplayAddress(msg.from_alias, msg.from_address);
26916
+ const decrypt = await resolveMailForDelivery(options, msg);
26917
+ if (!decrypt.ok) {
26918
+ await options.onAwakening({
26919
+ kind: "mail",
26920
+ content: "",
26921
+ meta: encryptedDeliveryFailureMeta("mail", from, msg.message_id, conversationID, decrypt.error),
26922
+ deliveryIntent: "wake"
26923
+ });
26924
+ continue;
26925
+ }
26926
+ const trust = await normalizeMessageTrust(options, msg, msg.from_alias, msg.from_address, msg.to_did, msg.to_stable_id);
26927
+ msg.verification_status = trust.status;
26928
+ if (trust.stored)
26929
+ pinsDirty = true;
26930
+ const meta3 = {
26931
+ type: "mail",
26932
+ from,
26933
+ message_id: msg.message_id,
26934
+ trust_status: msg.verification_status || "unknown",
26935
+ verified: String(isTrustedVerificationStatus(msg.verification_status))
26936
+ };
26937
+ if (conversationID)
26938
+ meta3.conversation_id = conversationID;
26939
+ if (msg.subject)
26940
+ meta3.subject = msg.subject;
26941
+ if (msg.priority && msg.priority !== "normal")
26942
+ meta3.priority = msg.priority;
26943
+ await options.onAwakening({
26944
+ kind: "mail",
26945
+ content: msg.body,
26946
+ meta: meta3,
26947
+ deliveryIntent: "wake"
26948
+ });
26949
+ dispatched.add(key);
26950
+ if (options.deliveryStore) {
26951
+ options.deliveryStore.mark(key);
26952
+ await options.deliveryStore.save();
26953
+ }
26954
+ await ackMessage(options.client, msg.message_id);
26955
+ }
26956
+ if (pinsDirty)
26957
+ await options.pinStore.save(options.pinStorePath || DEFAULT_PIN_STORE_PATH);
26958
+ }
26959
+ async function dispatchChatEvent(options, dispatched, event) {
26960
+ if (!event.session_id)
26961
+ return;
26962
+ const messages = await fetchHistory(options.client, event.session_id, true, CHAT_FETCH_LIMIT, event.message_id);
26963
+ let pinsDirty = false;
26964
+ let lastMessageId;
26965
+ for (const msg of messages) {
26966
+ if (isSelfSender(msg.from_agent, msg.from_address, msg.from_stable_id, msg.from_did, options.self))
26967
+ continue;
26968
+ const conversationID = msg.conversation_id || event.conversation_id || event.session_id;
26969
+ const key = dispatchKey("chat", conversationID, msg.message_id);
26970
+ if (dispatched.has(key) || options.deliveryStore?.has(key)) {
26971
+ lastMessageId = msg.message_id;
26972
+ continue;
26973
+ }
26974
+ const from = senderDisplayAddress(msg.from_agent, msg.from_address);
26975
+ const decrypt = await resolveChatForDelivery(options, event.session_id, msg);
26976
+ if (!decrypt.ok) {
26977
+ await options.onAwakening({
26978
+ kind: "chat",
26979
+ content: "",
26980
+ meta: encryptedDeliveryFailureMeta("chat", from, msg.message_id, conversationID, decrypt.error, event.session_id),
26981
+ deliveryIntent: event.sender_waiting ? "steer" : "wake"
26982
+ });
26983
+ continue;
26984
+ }
26985
+ const trust = await normalizeMessageTrust(options, msg, msg.from_agent, msg.from_address, msg.to_did, msg.to_stable_id);
26986
+ msg.verification_status = trust.status;
26987
+ if (trust.stored)
26988
+ pinsDirty = true;
26989
+ const meta3 = {
26990
+ type: "chat",
26991
+ from,
26992
+ session_id: event.session_id,
26993
+ message_id: msg.message_id,
26994
+ trust_status: msg.verification_status || "unknown",
26995
+ verified: String(isTrustedVerificationStatus(msg.verification_status))
26996
+ };
26997
+ if (conversationID)
26998
+ meta3.conversation_id = conversationID;
26999
+ if (event.sender_waiting)
27000
+ meta3.sender_waiting = "true";
27001
+ if (msg.sender_leaving)
27002
+ meta3.sender_leaving = "true";
27003
+ await options.onAwakening({
27004
+ kind: "chat",
27005
+ content: msg.body,
27006
+ meta: meta3,
27007
+ deliveryIntent: event.sender_waiting ? "steer" : "wake"
27008
+ });
27009
+ dispatched.add(key);
27010
+ if (options.deliveryStore) {
27011
+ options.deliveryStore.mark(key);
27012
+ await options.deliveryStore.save();
27013
+ }
27014
+ lastMessageId = msg.message_id;
27015
+ }
27016
+ if (lastMessageId)
27017
+ await markRead(options.client, event.session_id, lastMessageId);
27018
+ if (pinsDirty)
27019
+ await options.pinStore.save(options.pinStorePath || DEFAULT_PIN_STORE_PATH);
27020
+ }
27021
+ async function normalizeMessageTrust(options, msg, fromAlias, fromAddress, toDID, toStableID) {
27022
+ return options.trust.normalizeTrust(options.pinStore, msg.verification_status, senderTrustAddress(fromAlias, fromAddress), msg.from_did, msg.from_stable_id, toDID, toStableID, msg.rotation_announcement, msg.replacement_announcement, msg.signed_from || fromAddress || fromAlias || "");
27023
+ }
27024
+ async function resolveMailForDelivery(options, msg) {
27025
+ if (!isEncryptedMessage(msg))
27026
+ return { ok: true };
27027
+ if (!options.localDecrypt?.mailMessage) {
27028
+ return { ok: false, error: "local aw decrypt provider is not configured" };
27029
+ }
27030
+ try {
27031
+ const decrypted = await options.localDecrypt.mailMessage(msg.message_id);
27032
+ if (!decrypted || typeof decrypted.body !== "string") {
27033
+ return { ok: false, error: "local aw did not return decrypted mail body" };
27034
+ }
27035
+ Object.assign(msg, decrypted);
27036
+ return { ok: true };
27037
+ } catch (error2) {
27038
+ return { ok: false, error: error2 instanceof Error ? error2.message : String(error2) };
27039
+ }
27040
+ }
27041
+ async function resolveChatForDelivery(options, sessionID, msg) {
27042
+ if (!isEncryptedMessage(msg))
27043
+ return { ok: true };
27044
+ if (!options.localDecrypt?.chatMessage) {
27045
+ return { ok: false, error: "local aw decrypt provider is not configured" };
27046
+ }
27047
+ try {
27048
+ const decrypted = await options.localDecrypt.chatMessage(sessionID, msg.message_id);
27049
+ if (!decrypted || typeof decrypted.body !== "string") {
27050
+ return { ok: false, error: "local aw did not return decrypted chat body" };
27051
+ }
27052
+ Object.assign(msg, decrypted);
27053
+ return { ok: true };
27054
+ } catch (error2) {
27055
+ return { ok: false, error: error2 instanceof Error ? error2.message : String(error2) };
27056
+ }
27057
+ }
27058
+ function isEncryptedMessage(msg) {
27059
+ return msg.content_mode === "encrypted_v2" || msg.message_version === 2 || msg.encrypted_envelope !== void 0;
27060
+ }
27061
+ function encryptedDeliveryFailureMeta(type2, from, messageID, conversationID, error2, sessionID) {
27062
+ const meta3 = {
27063
+ type: type2,
27064
+ from,
27065
+ message_id: messageID,
27066
+ encrypted: "true",
27067
+ decrypted: "false",
27068
+ decrypt_error: error2
27069
+ };
27070
+ if (conversationID)
27071
+ meta3.conversation_id = conversationID;
27072
+ if (sessionID)
27073
+ meta3.session_id = sessionID;
27074
+ return meta3;
27075
+ }
27076
+ function isTrustedVerificationStatus(status) {
27077
+ return status === "verified" || status === "verified_custodial";
27078
+ }
27079
+ function pruneDispatched(dispatched) {
27080
+ if (dispatched.size <= MAX_DISPATCHED_IDS)
27081
+ return;
27082
+ const excess = dispatched.size - MAX_DISPATCHED_IDS;
27083
+ let removed = 0;
27084
+ for (const id of dispatched) {
27085
+ if (removed >= excess)
27086
+ break;
27087
+ dispatched.delete(id);
27088
+ removed++;
27089
+ }
27090
+ }
27091
+ function dispatchKey(channel, conversationID, messageID) {
27092
+ const conversation = (conversationID || "").trim();
27093
+ return `${channel}:${conversation}:${messageID}`;
27094
+ }
26967
27095
  function senderDisplayAddress(alias, address) {
26968
27096
  const qualified = (address || "").trim();
26969
- if (qualified) return qualified;
27097
+ if (qualified)
27098
+ return qualified;
26970
27099
  return (alias || "").trim();
26971
27100
  }
26972
27101
  function senderTrustAddress(alias, address) {
26973
27102
  const qualified = (address || "").trim();
26974
- if (qualified) return qualified;
27103
+ if (qualified)
27104
+ return qualified;
26975
27105
  return (alias || "").trim();
26976
27106
  }
26977
27107
  function isSelfSender(alias, address, stableID, did, self) {
@@ -26981,16 +27111,91 @@ function isSelfSender(alias, address, stableID, did, self) {
26981
27111
  const selfAddress = self.address.trim();
26982
27112
  const selfStableID = self.stableID.trim();
26983
27113
  const selfDID = self.did.trim();
26984
- if (selfAddress && msgAddress && selfAddress === msgAddress) return true;
26985
- if (selfStableID && (msgStableID === selfStableID || msgDID === selfStableID)) return true;
26986
- if (selfDID && (msgStableID === selfDID || msgDID === selfDID)) return true;
27114
+ if (selfAddress && msgAddress && selfAddress === msgAddress)
27115
+ return true;
27116
+ if (selfStableID && (msgStableID === selfStableID || msgDID === selfStableID))
27117
+ return true;
27118
+ if (selfDID && (msgStableID === selfDID || msgDID === selfDID))
27119
+ return true;
26987
27120
  if ((selfAddress || selfStableID || selfDID) && (msgAddress || msgStableID || msgDID)) {
26988
27121
  return false;
26989
27122
  }
26990
27123
  const selfAlias = self.alias.trim();
26991
- if (!selfAlias) return false;
27124
+ if (!selfAlias)
27125
+ return false;
26992
27126
  return (alias || "").trim() === selfAlias;
26993
27127
  }
27128
+
27129
+ // src/index.ts
27130
+ async function main() {
27131
+ const workdir = process.cwd();
27132
+ const config2 = await resolveConfig(workdir);
27133
+ const client = createChannelClient(config2);
27134
+ const pinStore = await loadPinStore();
27135
+ const registry2 = createRegistryResolver(config2.registryURL);
27136
+ const trust = new SenderTrustManager(
27137
+ client,
27138
+ registry2,
27139
+ config2.teamID,
27140
+ config2.did,
27141
+ config2.stableID
27142
+ );
27143
+ const mcp = new Server(
27144
+ { name: "aweb", version: "0.1.0" },
27145
+ {
27146
+ capabilities: {
27147
+ experimental: { "claude/channel": {} }
27148
+ },
27149
+ instructions: `Events from the aweb channel are coordination messages from other agents in your team. Use the aw CLI to respond, not MCP tools.
27150
+
27151
+ 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>".
27152
+
27153
+ 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>"
27154
+ If you need more time, send a status update the same way.
27155
+
27156
+ Control events (type="control") are operational signals. On "pause", stop current work and wait. On "resume", continue. On "interrupt", stop and await new instructions.`
27157
+ }
27158
+ );
27159
+ const transport = new StdioServerTransport();
27160
+ await mcp.connect(transport);
27161
+ const abort = new AbortController();
27162
+ process.on("SIGINT", () => abort.abort());
27163
+ process.on("SIGTERM", () => abort.abort());
27164
+ await startChannelLoop({
27165
+ client,
27166
+ pinStore,
27167
+ trust,
27168
+ self: {
27169
+ alias: config2.alias,
27170
+ address: config2.address,
27171
+ did: config2.did,
27172
+ stableID: config2.stableID
27173
+ },
27174
+ signal: abort.signal,
27175
+ workdir,
27176
+ onAwakening: (awakening) => mcp.notification({
27177
+ method: "notifications/claude/channel",
27178
+ params: { content: awakening.content, meta: awakening.meta }
27179
+ }),
27180
+ log: (message) => console.error(message)
27181
+ });
27182
+ }
27183
+ async function dispatchEvent(mcp, client, pinStore, trust, self, dispatched, event) {
27184
+ await dispatchAgentEvent(
27185
+ {
27186
+ client,
27187
+ pinStore,
27188
+ trust,
27189
+ self,
27190
+ onAwakening: (awakening) => mcp.notification({
27191
+ method: "notifications/claude/channel",
27192
+ params: { content: awakening.content, meta: awakening.meta }
27193
+ })
27194
+ },
27195
+ dispatched,
27196
+ event
27197
+ );
27198
+ }
26994
27199
  function isDirectExecution(moduleURL) {
26995
27200
  const entry = process.argv[1];
26996
27201
  if (!entry) return false;
@@ -27009,7 +27214,7 @@ if (isDirectExecution(import.meta.url)) {
27009
27214
  export {
27010
27215
  dispatchEvent,
27011
27216
  isDirectExecution,
27012
- resolveRegistryFallbackURL2 as resolveRegistryFallbackURL
27217
+ resolveRegistryFallbackURL
27013
27218
  };
27014
27219
  /*! Bundled license information:
27015
27220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/claude-channel",
3
- "version": "1.4.10",
3
+ "version": "1.4.11",
4
4
  "mcpName": "io.github.awebai/channel",
5
5
  "license": "MIT",
6
6
  "type": "module",