@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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +431 -226
- package/package.json +1 -1
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
|
|
26741
|
+
async function loadPinStore(path = DEFAULT_PIN_STORE_PATH) {
|
|
26710
26742
|
try {
|
|
26711
|
-
const content = await readFile4(
|
|
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
|
-
|
|
26718
|
-
|
|
26719
|
-
|
|
26720
|
-
|
|
26721
|
-
|
|
26722
|
-
|
|
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
|
-
|
|
26728
|
-
|
|
26729
|
-
|
|
26730
|
-
|
|
26731
|
-
|
|
26732
|
-
|
|
26733
|
-
|
|
26734
|
-
|
|
26735
|
-
|
|
26736
|
-
|
|
26737
|
-
|
|
26738
|
-
|
|
26739
|
-
|
|
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
|
-
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
|
|
26772
|
-
|
|
26773
|
-
|
|
26774
|
-
|
|
26775
|
-
|
|
26776
|
-
|
|
26777
|
-
|
|
26778
|
-
{
|
|
26779
|
-
|
|
26780
|
-
|
|
26781
|
-
|
|
26782
|
-
|
|
26783
|
-
|
|
26784
|
-
|
|
26785
|
-
|
|
26786
|
-
|
|
26787
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26834
|
+
await dispatchAgentEvent({ ...options, deliveryStore, localDecrypt }, dispatched, event);
|
|
26803
26835
|
pruneDispatched(dispatched);
|
|
26804
26836
|
} catch (err2) {
|
|
26805
|
-
|
|
26837
|
+
log(`[aw-channel] dispatch error: ${err2}`);
|
|
26806
26838
|
}
|
|
26807
26839
|
}
|
|
26808
26840
|
}
|
|
26809
|
-
async function
|
|
26841
|
+
async function dispatchAgentEvent(options, dispatched, event) {
|
|
26810
26842
|
switch (event.type) {
|
|
26811
|
-
case "mail_message":
|
|
26812
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
26911
|
-
|
|
26912
|
-
|
|
26913
|
-
|
|
26914
|
-
|
|
26915
|
-
|
|
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
|
-
|
|
26923
|
-
|
|
26924
|
-
|
|
26925
|
-
|
|
26926
|
-
|
|
26927
|
-
|
|
26928
|
-
|
|
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
|
-
|
|
26936
|
-
|
|
26937
|
-
|
|
26938
|
-
|
|
26939
|
-
|
|
26940
|
-
|
|
26941
|
-
|
|
26942
|
-
|
|
26943
|
-
|
|
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
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
|
|
26954
|
-
|
|
26955
|
-
|
|
26956
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
26985
|
-
|
|
26986
|
-
if (
|
|
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)
|
|
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
|
-
|
|
27217
|
+
resolveRegistryFallbackURL
|
|
27013
27218
|
};
|
|
27014
27219
|
/*! Bundled license information:
|
|
27015
27220
|
|