aamp-openclaw-plugin 0.1.40 → 0.1.43

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/dist/index.js CHANGED
@@ -1196,6 +1196,7 @@ var JmapPushClient = class extends TinyEmitter {
1196
1196
 
1197
1197
  // ../sdk/dist/pairing.js
1198
1198
  import { randomBytes } from "node:crypto";
1199
+ var DEFAULT_PAIRING_WEB_URL = "https://meshmail.ai/pair";
1199
1200
  function normalizeMailbox(value) {
1200
1201
  const mailbox = value.trim().toLowerCase();
1201
1202
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(mailbox)) {
@@ -1243,6 +1244,23 @@ function buildPairingUrl(payload) {
1243
1244
  }
1244
1245
  return url.toString();
1245
1246
  }
1247
+ function buildPairingWebUrl(payload, baseUrl2 = DEFAULT_PAIRING_WEB_URL) {
1248
+ const mailbox = normalizeMailbox(payload.mailbox);
1249
+ const pairCode = payload.pairCode.trim();
1250
+ if (!pairCode)
1251
+ throw new Error("pairCode cannot be empty");
1252
+ const url = new URL(baseUrl2);
1253
+ url.searchParams.set("mailbox", mailbox);
1254
+ url.searchParams.set("pair_code", pairCode);
1255
+ const rules = normalizeDispatchContextRules(payload.dispatchContextRules);
1256
+ if (rules) {
1257
+ url.searchParams.set("dispatch_context_rules", encodeBase64UrlJson2(rules));
1258
+ }
1259
+ return url.toString();
1260
+ }
1261
+ function pairingUrlToWebUrl(input, baseUrl2 = DEFAULT_PAIRING_WEB_URL) {
1262
+ return buildPairingWebUrl(parsePairingUrl(input), baseUrl2);
1263
+ }
1246
1264
  function createPairingCode(options) {
1247
1265
  const pairCode = options.pairCode?.trim() || randomBytes(6).toString("base64url");
1248
1266
  const dispatchContextRules = normalizeDispatchContextRules(options.dispatchContextRules);
@@ -1265,8 +1283,10 @@ function parsePairingUrl(input) {
1265
1283
  } catch {
1266
1284
  throw new Error("Invalid pairing URL");
1267
1285
  }
1268
- if (url.protocol !== "aamp:" || url.hostname !== "connect") {
1269
- throw new Error("Pairing URL must start with aamp://connect");
1286
+ const isDeepLink = url.protocol === "aamp:" && url.hostname === "connect";
1287
+ const isWebLink = (url.protocol === "https:" || url.protocol === "http:") && url.hostname === "meshmail.ai" && url.pathname === "/pair";
1288
+ if (!isDeepLink && !isWebLink) {
1289
+ throw new Error("Pairing URL must start with aamp://connect or https://meshmail.ai/pair");
1270
1290
  }
1271
1291
  const mailbox = url.searchParams.get("mailbox") ?? "";
1272
1292
  const pairCode = url.searchParams.get("pair_code") ?? "";
@@ -1499,12 +1519,34 @@ var SmtpSender = class _SmtpSender {
1499
1519
  return false;
1500
1520
  return Boolean(this.config.httpBaseUrl && this.config.authToken);
1501
1521
  }
1522
+ normalizeAttachments(attachments) {
1523
+ if (!attachments?.length)
1524
+ return void 0;
1525
+ return attachments.map((a) => ({
1526
+ filename: a.filename,
1527
+ contentType: a.contentType,
1528
+ content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content
1529
+ }));
1530
+ }
1502
1531
  getJmapAuthHeader() {
1503
1532
  if (!this.config.authToken) {
1504
1533
  throw new Error("JMAP auth token is not configured");
1505
1534
  }
1506
1535
  return `Basic ${this.config.authToken}`;
1507
1536
  }
1537
+ rewriteUrlToConfiguredOrigin(rawUrl) {
1538
+ const base = this.config.httpBaseUrl?.replace(/\/$/, "");
1539
+ if (!base)
1540
+ return rawUrl;
1541
+ const parsed = new URL(rawUrl, `${base}/`);
1542
+ const configured = new URL(base);
1543
+ parsed.protocol = configured.protocol;
1544
+ parsed.username = configured.username;
1545
+ parsed.password = configured.password;
1546
+ parsed.hostname = configured.hostname;
1547
+ parsed.port = configured.port;
1548
+ return parsed.toString();
1549
+ }
1508
1550
  async resolveJmapSession() {
1509
1551
  const base = this.config.httpBaseUrl?.replace(/\/$/, "");
1510
1552
  if (!base) {
@@ -1525,7 +1567,8 @@ var SmtpSender = class _SmtpSender {
1525
1567
  }
1526
1568
  return {
1527
1569
  accountId,
1528
- apiUrl: `${base}/jmap/`
1570
+ apiUrl: `${base}/jmap/`,
1571
+ uploadUrl: this.rewriteUrlToConfiguredOrigin(session.uploadUrl ?? `${base}/jmap/upload/{accountId}/`)
1529
1572
  };
1530
1573
  })();
1531
1574
  }
@@ -1562,6 +1605,38 @@ var SmtpSender = class _SmtpSender {
1562
1605
  const data = await res.json();
1563
1606
  return data.methodResponses ?? [];
1564
1607
  }
1608
+ async uploadSentAttachment(attachment) {
1609
+ const session = await this.resolveJmapSession();
1610
+ const content = typeof attachment.content === "string" ? Buffer.from(attachment.content, "base64") : attachment.content;
1611
+ const uploadUrl = session.uploadUrl.replace(/\{accountId\}|%7BaccountId%7D/gi, encodeURIComponent(session.accountId));
1612
+ const res = await this.fetch(uploadUrl, {
1613
+ method: "POST",
1614
+ headers: {
1615
+ Authorization: this.getJmapAuthHeader(),
1616
+ "Content-Type": attachment.contentType
1617
+ },
1618
+ body: content
1619
+ });
1620
+ const bodyText = await res.text();
1621
+ if (!res.ok) {
1622
+ throw new Error(`JMAP attachment upload failed: ${res.status} ${bodyText}`);
1623
+ }
1624
+ let data;
1625
+ try {
1626
+ data = JSON.parse(bodyText);
1627
+ } catch {
1628
+ throw new Error("JMAP attachment upload returned invalid JSON");
1629
+ }
1630
+ if (!data.blobId) {
1631
+ throw new Error("JMAP attachment upload did not return blobId");
1632
+ }
1633
+ return {
1634
+ blobId: data.blobId,
1635
+ type: data.type ?? attachment.contentType,
1636
+ size: data.size ?? content.byteLength,
1637
+ name: attachment.filename
1638
+ };
1639
+ }
1565
1640
  async getSentMailboxId() {
1566
1641
  if (!this.sentMailboxIdPromise) {
1567
1642
  this.sentMailboxIdPromise = (async () => {
@@ -1586,20 +1661,43 @@ var SmtpSender = class _SmtpSender {
1586
1661
  const sentMailboxId = await this.getSentMailboxId();
1587
1662
  if (!sentMailboxId)
1588
1663
  return;
1664
+ const uploadedAttachments = params.attachments?.length ? await Promise.all(params.attachments.map((attachment) => this.uploadSentAttachment(attachment))) : [];
1589
1665
  const emailCreate = {
1590
1666
  mailboxIds: { [sentMailboxId]: true },
1591
1667
  from: [{ email: params.from }],
1592
1668
  to: [{ email: params.to }],
1593
1669
  subject: params.subject,
1594
- bodyValues: {
1670
+ keywords: { "$seen": true }
1671
+ };
1672
+ if (uploadedAttachments.length) {
1673
+ emailCreate.bodyStructure = {
1674
+ type: "multipart/mixed",
1675
+ subParts: [
1676
+ { partId: "body", type: "text/plain" },
1677
+ ...uploadedAttachments.map((attachment) => ({
1678
+ blobId: attachment.blobId,
1679
+ type: attachment.type,
1680
+ size: attachment.size,
1681
+ name: attachment.name,
1682
+ disposition: "attachment"
1683
+ }))
1684
+ ]
1685
+ };
1686
+ emailCreate.bodyValues = {
1687
+ body: {
1688
+ value: params.text,
1689
+ isTruncated: false
1690
+ }
1691
+ };
1692
+ } else {
1693
+ emailCreate.bodyValues = {
1595
1694
  body: {
1596
1695
  value: params.text,
1597
1696
  charset: "utf-8"
1598
1697
  }
1599
- },
1600
- textBody: [{ partId: "body", type: "text/plain" }],
1601
- keywords: { "$seen": true }
1602
- };
1698
+ };
1699
+ emailCreate.textBody = [{ partId: "body", type: "text/plain" }];
1700
+ }
1603
1701
  if (params.inReplyTo) {
1604
1702
  emailCreate["header:In-Reply-To:asText"] = ` ${sanitize(params.inReplyTo)}`;
1605
1703
  }
@@ -1622,6 +1720,12 @@ var SmtpSender = class _SmtpSender {
1622
1720
  try {
1623
1721
  await this.saveToSent(params);
1624
1722
  } catch {
1723
+ if (params.attachments?.length) {
1724
+ try {
1725
+ await this.saveToSent({ ...params, attachments: void 0 });
1726
+ } catch {
1727
+ }
1728
+ }
1625
1729
  }
1626
1730
  }
1627
1731
  /**
@@ -1631,6 +1735,7 @@ var SmtpSender = class _SmtpSender {
1631
1735
  */
1632
1736
  async sendTask(opts) {
1633
1737
  const taskId = opts.taskId ?? randomUUID();
1738
+ const attachments = this.normalizeAttachments(opts.attachments);
1634
1739
  const aampHeaders = buildDispatchHeaders({
1635
1740
  taskId,
1636
1741
  priority: opts.priority,
@@ -1653,12 +1758,8 @@ var SmtpSender = class _SmtpSender {
1653
1758
  ].filter(Boolean).join("\n"),
1654
1759
  headers: aampHeaders
1655
1760
  };
1656
- if (opts.attachments?.length) {
1657
- sendMailOpts.attachments = opts.attachments.map((a) => ({
1658
- filename: a.filename,
1659
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content,
1660
- contentType: a.contentType
1661
- }));
1761
+ if (attachments) {
1762
+ sendMailOpts.attachments = attachments;
1662
1763
  }
1663
1764
  if (this.shouldUseHttpFallback(opts.to)) {
1664
1765
  const info2 = await this.sendViaHttp({
@@ -1666,11 +1767,7 @@ var SmtpSender = class _SmtpSender {
1666
1767
  subject: sendMailOpts.subject,
1667
1768
  text: sendMailOpts.text,
1668
1769
  aampHeaders,
1669
- attachments: opts.attachments?.map((a) => ({
1670
- filename: a.filename,
1671
- contentType: a.contentType,
1672
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content
1673
- }))
1770
+ attachments
1674
1771
  });
1675
1772
  await this.saveToSentBestEffort({
1676
1773
  from: this.config.user,
@@ -1678,7 +1775,8 @@ var SmtpSender = class _SmtpSender {
1678
1775
  subject: sendMailOpts.subject,
1679
1776
  text: sendMailOpts.text,
1680
1777
  aampHeaders,
1681
- messageId: info2.messageId
1778
+ messageId: info2.messageId,
1779
+ attachments
1682
1780
  });
1683
1781
  return { taskId, messageId: info2.messageId ?? "" };
1684
1782
  }
@@ -1689,7 +1787,8 @@ var SmtpSender = class _SmtpSender {
1689
1787
  subject: sendMailOpts.subject,
1690
1788
  text: sendMailOpts.text,
1691
1789
  aampHeaders,
1692
- messageId: info.messageId
1790
+ messageId: info.messageId,
1791
+ attachments
1693
1792
  });
1694
1793
  return { taskId, messageId: info.messageId ?? "" };
1695
1794
  }
@@ -1697,6 +1796,7 @@ var SmtpSender = class _SmtpSender {
1697
1796
  * Send a task.result email back to the dispatcher
1698
1797
  */
1699
1798
  async sendResult(opts) {
1799
+ const attachments = this.normalizeAttachments(opts.attachments);
1700
1800
  const aampHeaders = buildResultHeaders({
1701
1801
  taskId: opts.taskId,
1702
1802
  status: opts.status,
@@ -1725,12 +1825,8 @@ Error: ${opts.errorMsg}` : ""
1725
1825
  mailOpts.inReplyTo = opts.inReplyTo;
1726
1826
  mailOpts.references = opts.inReplyTo;
1727
1827
  }
1728
- if (opts.attachments?.length) {
1729
- mailOpts.attachments = opts.attachments.map((a) => ({
1730
- filename: a.filename,
1731
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content,
1732
- contentType: a.contentType
1733
- }));
1828
+ if (attachments) {
1829
+ mailOpts.attachments = attachments;
1734
1830
  }
1735
1831
  if (this.shouldUseHttpFallback(opts.to)) {
1736
1832
  const info2 = await this.sendViaHttp({
@@ -1738,11 +1834,7 @@ Error: ${opts.errorMsg}` : ""
1738
1834
  subject: mailOpts.subject,
1739
1835
  text: mailOpts.text,
1740
1836
  aampHeaders,
1741
- attachments: opts.attachments?.map((a) => ({
1742
- filename: a.filename,
1743
- contentType: a.contentType,
1744
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content
1745
- }))
1837
+ attachments
1746
1838
  });
1747
1839
  await this.saveToSentBestEffort({
1748
1840
  from: this.config.user,
@@ -1752,7 +1844,8 @@ Error: ${opts.errorMsg}` : ""
1752
1844
  aampHeaders,
1753
1845
  messageId: info2.messageId,
1754
1846
  inReplyTo: opts.inReplyTo,
1755
- references: opts.inReplyTo
1847
+ references: opts.inReplyTo,
1848
+ attachments
1756
1849
  });
1757
1850
  return;
1758
1851
  }
@@ -1765,13 +1858,15 @@ Error: ${opts.errorMsg}` : ""
1765
1858
  aampHeaders,
1766
1859
  messageId: info.messageId,
1767
1860
  inReplyTo: opts.inReplyTo,
1768
- references: opts.inReplyTo
1861
+ references: opts.inReplyTo,
1862
+ attachments
1769
1863
  });
1770
1864
  }
1771
1865
  /**
1772
1866
  * Send a task.help_needed email when the agent is blocked
1773
1867
  */
1774
1868
  async sendHelp(opts) {
1869
+ const attachments = this.normalizeAttachments(opts.attachments);
1775
1870
  const aampHeaders = buildHelpHeaders({
1776
1871
  taskId: opts.taskId,
1777
1872
  question: opts.question,
@@ -1800,12 +1895,8 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
1800
1895
  helpMailOpts.inReplyTo = opts.inReplyTo;
1801
1896
  helpMailOpts.references = opts.inReplyTo;
1802
1897
  }
1803
- if (opts.attachments?.length) {
1804
- helpMailOpts.attachments = opts.attachments.map((a) => ({
1805
- filename: a.filename,
1806
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content,
1807
- contentType: a.contentType
1808
- }));
1898
+ if (attachments) {
1899
+ helpMailOpts.attachments = attachments;
1809
1900
  }
1810
1901
  if (this.shouldUseHttpFallback(opts.to)) {
1811
1902
  const info2 = await this.sendViaHttp({
@@ -1813,11 +1904,7 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
1813
1904
  subject: helpMailOpts.subject,
1814
1905
  text: helpMailOpts.text,
1815
1906
  aampHeaders,
1816
- attachments: opts.attachments?.map((a) => ({
1817
- filename: a.filename,
1818
- contentType: a.contentType,
1819
- content: typeof a.content === "string" ? Buffer.from(a.content, "base64") : a.content
1820
- }))
1907
+ attachments
1821
1908
  });
1822
1909
  await this.saveToSentBestEffort({
1823
1910
  from: this.config.user,
@@ -1827,7 +1914,8 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
1827
1914
  aampHeaders,
1828
1915
  messageId: info2.messageId,
1829
1916
  inReplyTo: opts.inReplyTo,
1830
- references: opts.inReplyTo
1917
+ references: opts.inReplyTo,
1918
+ attachments
1831
1919
  });
1832
1920
  return;
1833
1921
  }
@@ -1840,7 +1928,8 @@ ${opts.suggestedOptions.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}` : ""
1840
1928
  aampHeaders,
1841
1929
  messageId: info.messageId,
1842
1930
  inReplyTo: opts.inReplyTo,
1843
- references: opts.inReplyTo
1931
+ references: opts.inReplyTo,
1932
+ attachments
1844
1933
  });
1845
1934
  }
1846
1935
  /**
@@ -2296,6 +2385,7 @@ var AampClient = class _AampClient extends TinyEmitter {
2296
2385
  taskDispatchConcurrency;
2297
2386
  pendingTaskDispatches = [];
2298
2387
  activeTaskDispatchCount = 0;
2388
+ discoveryPromise;
2299
2389
  streamAppendQueues = /* @__PURE__ */ new Map();
2300
2390
  constructor(config) {
2301
2391
  super();
@@ -2407,6 +2497,8 @@ var AampClient = class _AampClient extends TinyEmitter {
2407
2497
  }
2408
2498
  static createPairingCode = createPairingCode;
2409
2499
  static buildPairingUrl = buildPairingUrl;
2500
+ static buildPairingWebUrl = buildPairingWebUrl;
2501
+ static pairingUrlToWebUrl = pairingUrlToWebUrl;
2410
2502
  static parsePairingUrl = parsePairingUrl;
2411
2503
  static isPairingUrl = isPairingUrl;
2412
2504
  static consumePairingCode = consumePairingCode;
@@ -2444,6 +2536,38 @@ var AampClient = class _AampClient extends TinyEmitter {
2444
2536
  ...opts.body ? { body: JSON.stringify(opts.body) } : {}
2445
2537
  });
2446
2538
  }
2539
+ discoverCachedAampService() {
2540
+ if (!this.discoveryPromise) {
2541
+ const discoveryPromise = _AampClient.discoverAampService(this.config.baseUrl, this.config.fetch);
2542
+ this.discoveryPromise = discoveryPromise;
2543
+ void discoveryPromise.catch(() => {
2544
+ if (this.discoveryPromise === discoveryPromise) {
2545
+ this.discoveryPromise = void 0;
2546
+ }
2547
+ });
2548
+ }
2549
+ return this.discoveryPromise;
2550
+ }
2551
+ async callAampApi(opts) {
2552
+ const fetchImpl = this.config.fetch ?? fetch;
2553
+ const discovery = await this.discoverCachedAampService();
2554
+ const base = this.config.baseUrl.replace(/\/$/, "");
2555
+ const apiUrl = new URL(discovery.api.url, `${base}/`);
2556
+ apiUrl.searchParams.set("action", opts.action);
2557
+ for (const [key, value] of Object.entries(opts.query ?? {})) {
2558
+ if (value == null)
2559
+ continue;
2560
+ apiUrl.searchParams.set(key, String(value));
2561
+ }
2562
+ return fetchImpl(apiUrl, {
2563
+ method: opts.method ?? "GET",
2564
+ headers: {
2565
+ ...opts.authToken ? { Authorization: `Basic ${opts.authToken}` } : {},
2566
+ ...opts.body ? { "Content-Type": "application/json" } : {}
2567
+ },
2568
+ body: opts.body ? JSON.stringify(opts.body) : void 0
2569
+ });
2570
+ }
2447
2571
  static async registerMailbox(opts) {
2448
2572
  const base = opts.aampHost.replace(/\/$/, "");
2449
2573
  const registerRes = await _AampClient.callDiscoveredApi(base, {
@@ -2669,7 +2793,7 @@ var AampClient = class _AampClient extends TinyEmitter {
2669
2793
  };
2670
2794
  }
2671
2795
  async resolveStreamCapability() {
2672
- const discovery = await _AampClient.discoverAampService(this.config.baseUrl, this.config.fetch);
2796
+ const discovery = await this.discoverCachedAampService();
2673
2797
  const stream = discovery.capabilities?.stream;
2674
2798
  if (!stream?.transport) {
2675
2799
  throw new Error("AAMP stream capability is not available on this service");
@@ -2678,11 +2802,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2678
2802
  }
2679
2803
  async createStream(opts) {
2680
2804
  const stream = await this.resolveStreamCapability();
2681
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2805
+ const res = await this.callAampApi({
2682
2806
  action: stream.createAction ?? "aamp.stream.create",
2683
2807
  method: "POST",
2684
2808
  authToken: this.config.mailboxToken,
2685
- fetch: this.config.fetch,
2686
2809
  body: opts
2687
2810
  });
2688
2811
  if (!res.ok) {
@@ -2725,11 +2848,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2725
2848
  }
2726
2849
  async dispatchStreamAppend(opts) {
2727
2850
  const stream = await this.resolveStreamCapability();
2728
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2851
+ const res = await this.callAampApi({
2729
2852
  action: stream.appendAction ?? "aamp.stream.append",
2730
2853
  method: "POST",
2731
2854
  authToken: this.config.mailboxToken,
2732
- fetch: this.config.fetch,
2733
2855
  body: opts
2734
2856
  });
2735
2857
  if (!res.ok) {
@@ -2831,11 +2953,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2831
2953
  async closeStream(opts) {
2832
2954
  await this.flushStreamAppendQueue(opts.streamId);
2833
2955
  const stream = await this.resolveStreamCapability();
2834
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2956
+ const res = await this.callAampApi({
2835
2957
  action: stream.closeAction ?? "aamp.stream.close",
2836
2958
  method: "POST",
2837
2959
  authToken: this.config.mailboxToken,
2838
- fetch: this.config.fetch,
2839
2960
  body: opts
2840
2961
  });
2841
2962
  if (!res.ok) {
@@ -2846,10 +2967,9 @@ var AampClient = class _AampClient extends TinyEmitter {
2846
2967
  }
2847
2968
  async getTaskStream(opts) {
2848
2969
  const stream = await this.resolveStreamCapability();
2849
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2970
+ const res = await this.callAampApi({
2850
2971
  action: stream.getAction ?? "aamp.stream.get",
2851
2972
  authToken: this.config.mailboxToken,
2852
- fetch: this.config.fetch,
2853
2973
  query: {
2854
2974
  ...opts.taskId ? { taskId: opts.taskId } : {},
2855
2975
  ...opts.streamId ? { streamId: opts.streamId } : {}
@@ -2989,6 +3109,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2989
3109
  import { dirname, join } from "node:path";
2990
3110
  import { homedir } from "node:os";
2991
3111
  import { randomBytes as randomBytes2 } from "node:crypto";
3112
+ var DEFAULT_PAIRING_WEB_URL2 = "https://meshmail.ai/pair";
2992
3113
  function defaultCredentialsPath() {
2993
3114
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
2994
3115
  }
@@ -3062,6 +3183,14 @@ function createPairingCode2(params) {
3062
3183
  writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
3063
3184
  return state;
3064
3185
  }
3186
+ function pairingUrlToWebUrl2(connectUrl) {
3187
+ const parsed = new URL(connectUrl);
3188
+ const url = new URL(DEFAULT_PAIRING_WEB_URL2);
3189
+ for (const [key, value] of parsed.searchParams) {
3190
+ url.searchParams.set(key, value);
3191
+ }
3192
+ return url.toString();
3193
+ }
3065
3194
  function consumePairingCode2(params) {
3066
3195
  const resolved = params.file ?? defaultPairingPath();
3067
3196
  if (!existsSync(resolved))
@@ -3652,24 +3781,24 @@ var src_default = {
3652
3781
  return;
3653
3782
  if (request.to.trim().toLowerCase() !== agentEmail.trim().toLowerCase())
3654
3783
  return;
3784
+ const senderPoliciesFile = cfg.senderPoliciesFile ?? defaultSenderPoliciesPath();
3655
3785
  const consumed = consumePairingCode2({
3656
3786
  file: cfg.pairingFile ?? defaultPairingPath(),
3657
3787
  mailbox: agentEmail,
3658
3788
  pairCode: request.pairCode
3659
3789
  });
3660
- if (!consumed) {
3661
- const reason = "invalid or expired pair code";
3662
- api.logger.warn(`[AAMP] Rejected pair.request from ${request.from}: ${reason}`);
3663
- await sendPairResponse(request, false, reason);
3664
- return;
3790
+ const pairResponse = consumed ? { success: true } : { success: false, reason: "invalid or expired pair code" };
3791
+ if (pairResponse.success) {
3792
+ pairedSenderPolicies = addPairedSenderPolicy(senderPoliciesFile, {
3793
+ sender: request.from.trim().toLowerCase(),
3794
+ dispatchContextRules: request.dispatchContextRules ?? {},
3795
+ pairedAt: (/* @__PURE__ */ new Date()).toISOString()
3796
+ });
3797
+ api.logger.info(`[AAMP] Paired sender ${request.from}; sender policy saved to ${senderPoliciesFile}`);
3798
+ } else {
3799
+ api.logger.warn(`[AAMP] Rejected pair.request from ${request.from}: ${pairResponse.reason}`);
3665
3800
  }
3666
- pairedSenderPolicies = addPairedSenderPolicy(cfg.senderPoliciesFile ?? defaultSenderPoliciesPath(), {
3667
- sender: request.from.trim().toLowerCase(),
3668
- dispatchContextRules: request.dispatchContextRules ?? {},
3669
- pairedAt: (/* @__PURE__ */ new Date()).toISOString()
3670
- });
3671
- api.logger.info(`[AAMP] Paired sender ${request.from}; sender policy saved to ${cfg.senderPoliciesFile ?? defaultSenderPoliciesPath()}`);
3672
- await sendPairResponse(request, true);
3801
+ await sendPairResponse(request, pairResponse.success, pairResponse.reason);
3673
3802
  }
3674
3803
  async function renderPairingCodeForCurrentAgent() {
3675
3804
  const identity = agentEmail ? { email: agentEmail } : loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath());
@@ -3681,8 +3810,10 @@ var src_default = {
3681
3810
  mailbox: email,
3682
3811
  file: cfg.pairingFile ?? defaultPairingPath()
3683
3812
  });
3684
- const qr = await renderTerminalQr(pairing.connectUrl);
3813
+ const webUrl = pairingUrlToWebUrl2(pairing.connectUrl);
3814
+ const qr = await renderTerminalQr(webUrl);
3685
3815
  api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
3816
+ api.logger.info(`[AAMP] Web pairing link: ${webUrl}`);
3686
3817
  if (qr)
3687
3818
  api.logger.info(`
3688
3819
  ${qr}`);
@@ -3693,7 +3824,8 @@ ${qr}`);
3693
3824
  Scan this QR code:
3694
3825
  ${qr}` : "\nCould not render a terminal QR code.",
3695
3826
  `
3696
- Pairing URL: ${pairing.connectUrl}`
3827
+ Pairing link: ${webUrl}`,
3828
+ `Pairing URL: ${pairing.connectUrl}`
3697
3829
  ].join("\n");
3698
3830
  }
3699
3831
  function wakeAgentForPendingTask(task) {
@@ -3785,7 +3917,7 @@ Pairing URL: ${pairing.connectUrl}`
3785
3917
  file: cfg.pairingFile ?? defaultPairingPath()
3786
3918
  });
3787
3919
  api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
3788
- const qr = await renderTerminalQr(pairing.connectUrl);
3920
+ const qr = await renderTerminalQr(pairingUrlToWebUrl2(pairing.connectUrl));
3789
3921
  if (qr)
3790
3922
  api.logger.info(`
3791
3923
  ${qr}`);
@@ -4682,7 +4814,7 @@ ${lines.join("\n")}`
4682
4814
  }, { name: "aamp_pending_tasks" });
4683
4815
  api.registerTool({
4684
4816
  name: "aamp_pairing_code",
4685
- description: "Generate a fresh five-minute AAMP pairing code for this OpenClaw agent and show a QR code. Use this when the user asks to pair AAMP App or another AAMP runtime with this agent.",
4817
+ description: 'Generate a fresh five-minute AAMP pairing code for this OpenClaw agent and show a QR code. Use this immediately when the user asks for a pairing code, connect code, QR code, pairing link, invite link, or Chinese requests such as "\u53D1\u5BF9\u63A5\u7801", "\u751F\u6210\u914D\u5BF9\u7801", "\u5F39\u51FA\u4E8C\u7EF4\u7801", or "\u7ED9\u6211\u8FDE\u63A5\u4E8C\u7EF4\u7801".',
4686
4818
  parameters: { type: "object", properties: {} },
4687
4819
  execute: async () => ({
4688
4820
  content: [{ type: "text", text: await renderPairingCodeForCurrentAgent() }]
@@ -4951,7 +5083,7 @@ Question: ${h.question}`,
4951
5083
  });
4952
5084
  api.registerCommand({
4953
5085
  name: "aamp-pair",
4954
- description: "Show a fresh AAMP pairing QR code for this OpenClaw agent",
5086
+ description: "Show a fresh AAMP pairing QR code, web pairing link, and aamp://connect URL for this OpenClaw agent",
4955
5087
  acceptsArgs: false,
4956
5088
  requireAuth: false,
4957
5089
  handler: async () => ({