aamp-openclaw-plugin 0.1.42 → 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/README.md CHANGED
@@ -36,8 +36,12 @@ paired sender policy file, then consumes the code. The plugin replies with
36
36
  You can generate a fresh pairing QR code later without restarting OpenClaw:
37
37
 
38
38
  - Ask the agent to use the `aamp_pairing_code` tool.
39
+ - Ask naturally, for example "发对接码", "生成配对码", or "show the connect QR".
39
40
  - Or run the `/aamp-pair` command in OpenClaw.
40
41
 
42
+ The response includes both the QR target (`https://meshmail.ai/pair?...`) and
43
+ the raw `aamp://connect?...` URL for copy/paste pairing.
44
+
41
45
  ## Build
42
46
 
43
47
  ```bash
@@ -197,6 +197,15 @@ async function renderTerminalQr(value) {
197
197
  }
198
198
  }
199
199
 
200
+ function pairingUrlToWebUrl(connectUrl) {
201
+ const parsed = new URL(connectUrl)
202
+ const url = new URL('https://meshmail.ai/pair')
203
+ for (const [key, value] of parsed.searchParams) {
204
+ url.searchParams.set(key, value)
205
+ }
206
+ return url.toString()
207
+ }
208
+
200
209
  export function normalizeBaseUrl(url) {
201
210
  if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
202
211
  return `https://${url.replace(/\/$/, '')}`
@@ -753,7 +762,8 @@ export async function runInit() {
753
762
  const pairing = identityResult.email
754
763
  ? createPairingFile({ email: identityResult.email, pairingFile: DEFAULT_PAIRING_FILE })
755
764
  : null
756
- const qr = pairing ? await renderTerminalQr(pairing.connectUrl) : ''
765
+ const webPairingUrl = pairing ? pairingUrlToWebUrl(pairing.connectUrl) : ''
766
+ const qr = webPairingUrl ? await renderTerminalQr(webPairingUrl) : ''
757
767
 
758
768
  const restartResult = restartGateway()
759
769
 
@@ -786,7 +796,8 @@ export async function runInit() {
786
796
  restartResult.ok
787
797
  ? 'Plugin changes should now be active.'
788
798
  : 'Please restart the OpenClaw gateway manually for the plugin changes to take effect.',
789
- pairing ? `Pairing URL (expires ${pairing.expiresAt}): ${pairing.connectUrl}` : '',
799
+ pairing ? `Pairing link (expires ${pairing.expiresAt}): ${webPairingUrl}` : '',
800
+ pairing ? `Pairing URL: ${pairing.connectUrl}` : '',
790
801
  qr ? `\n${qr}` : '',
791
802
  '',
792
803
  ].join('\n'),
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { randomBytes } from "node:crypto";
6
+ var DEFAULT_PAIRING_WEB_URL = "https://meshmail.ai/pair";
6
7
  function defaultCredentialsPath() {
7
8
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
8
9
  }
@@ -76,6 +77,14 @@ function createPairingCode(params) {
76
77
  writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
77
78
  return state;
78
79
  }
80
+ function pairingUrlToWebUrl(connectUrl) {
81
+ const parsed = new URL(connectUrl);
82
+ const url = new URL(DEFAULT_PAIRING_WEB_URL);
83
+ for (const [key, value] of parsed.searchParams) {
84
+ url.searchParams.set(key, value);
85
+ }
86
+ return url.toString();
87
+ }
79
88
  function consumePairingCode(params) {
80
89
  const resolved = params.file ?? defaultPairingPath();
81
90
  if (!existsSync(resolved))
@@ -132,6 +141,7 @@ function writeBinaryFile(path, content) {
132
141
  writeFileSync(path, content);
133
142
  }
134
143
  export {
144
+ DEFAULT_PAIRING_WEB_URL,
135
145
  addPairedSenderPolicy,
136
146
  consumePairingCode,
137
147
  createPairingCode,
@@ -143,6 +153,7 @@ export {
143
153
  loadCachedIdentity,
144
154
  loadPairedSenderPolicies,
145
155
  loadTaskState,
156
+ pairingUrlToWebUrl,
146
157
  readBinaryFile,
147
158
  saveCachedIdentity,
148
159
  saveTaskState,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/file-store.ts"],
4
- "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: string[]\n}\n\nexport interface DispatchContextRules {\n [key: string]: string[]\n}\n\nexport interface PairingCodeState {\n mailbox: string\n pairCode: string\n expiresAt: string\n connectUrl: string\n consumedAt?: string\n}\n\nexport interface PairedSenderPolicy {\n sender: string\n dispatchContextRules: DispatchContextRules\n pairedAt: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function defaultTaskStatePath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.task-state.json')\n}\n\nexport function defaultPairingPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.pairing.json')\n}\n\nexport function defaultSenderPoliciesPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.sender-policies.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword) return null\n return {\n email: parsed.email,\n mailboxToken: parsed.mailboxToken,\n smtpPassword: parsed.smtpPassword,\n }\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n email: identity.email,\n mailboxToken: identity.mailboxToken,\n smtpPassword: identity.smtpPassword,\n }, null, 2), 'utf-8')\n}\n\nexport function loadTaskState(file?: string): CachedTaskState {\n const resolved = file ?? defaultTaskStatePath()\n if (!existsSync(resolved)) return { terminalTaskIds: [] }\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as CachedTaskState\n return {\n terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : [],\n }\n } catch {\n return { terminalTaskIds: [] }\n }\n}\n\nexport function saveTaskState(state: CachedTaskState, file?: string): void {\n const resolved = file ?? defaultTaskStatePath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n terminalTaskIds: state.terminalTaskIds ?? [],\n }, null, 2), 'utf-8')\n}\n\nexport function createPairingCode(params: {\n mailbox: string\n file?: string\n ttlSeconds?: number\n}): PairingCodeState {\n const pairCode = randomBytes(6).toString('base64url')\n const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1000).toISOString()\n const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`\n const state: PairingCodeState = {\n mailbox: params.mailbox.toLowerCase(),\n pairCode,\n expiresAt,\n connectUrl,\n }\n const resolved = params.file ?? defaultPairingPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(state, null, 2), 'utf-8')\n return state\n}\n\nexport function consumePairingCode(params: {\n mailbox: string\n pairCode: string\n file?: string\n}): PairingCodeState | null {\n const resolved = params.file ?? defaultPairingPath()\n if (!existsSync(resolved)) return null\n const state = JSON.parse(readFileSync(resolved, 'utf-8')) as PairingCodeState\n if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase()) return null\n if (state.pairCode !== params.pairCode) return null\n if (state.consumedAt) return null\n if (new Date(state.expiresAt).getTime() <= Date.now()) return null\n writeFileSync(resolved, JSON.stringify({ ...state, pairCode: '', consumedAt: new Date().toISOString() }, null, 2), 'utf-8')\n return state\n}\n\nfunction isPairedSenderPolicy(value: unknown): value is PairedSenderPolicy {\n if (!value || typeof value !== 'object') return false\n const policy = value as PairedSenderPolicy\n return typeof policy.sender === 'string'\n && typeof policy.dispatchContextRules === 'object'\n && typeof policy.pairedAt === 'string'\n}\n\nexport function loadPairedSenderPolicies(file?: string): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n if (!existsSync(resolved)) return []\n try {\n const data = JSON.parse(readFileSync(resolved, 'utf-8')) as unknown\n return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : []\n } catch {\n return []\n }\n}\n\nexport function addPairedSenderPolicy(file: string | undefined, policy: PairedSenderPolicy): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n const policies = loadPairedSenderPolicies(resolved)\n const normalizedSender = policy.sender.toLowerCase()\n const next = [\n ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),\n { ...policy, sender: normalizedSender },\n ]\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(next, null, 2), 'utf-8')\n return next\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: string[]\n}\n\nexport interface DispatchContextRules {\n [key: string]: string[]\n}\n\nexport interface PairingCodeState {\n mailbox: string\n pairCode: string\n expiresAt: string\n connectUrl: string\n consumedAt?: string\n}\n\nexport const DEFAULT_PAIRING_WEB_URL = 'https://meshmail.ai/pair'\n\nexport interface PairedSenderPolicy {\n sender: string\n dispatchContextRules: DispatchContextRules\n pairedAt: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function defaultTaskStatePath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.task-state.json')\n}\n\nexport function defaultPairingPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.pairing.json')\n}\n\nexport function defaultSenderPoliciesPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.sender-policies.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword) return null\n return {\n email: parsed.email,\n mailboxToken: parsed.mailboxToken,\n smtpPassword: parsed.smtpPassword,\n }\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n email: identity.email,\n mailboxToken: identity.mailboxToken,\n smtpPassword: identity.smtpPassword,\n }, null, 2), 'utf-8')\n}\n\nexport function loadTaskState(file?: string): CachedTaskState {\n const resolved = file ?? defaultTaskStatePath()\n if (!existsSync(resolved)) return { terminalTaskIds: [] }\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as CachedTaskState\n return {\n terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : [],\n }\n } catch {\n return { terminalTaskIds: [] }\n }\n}\n\nexport function saveTaskState(state: CachedTaskState, file?: string): void {\n const resolved = file ?? defaultTaskStatePath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n terminalTaskIds: state.terminalTaskIds ?? [],\n }, null, 2), 'utf-8')\n}\n\nexport function createPairingCode(params: {\n mailbox: string\n file?: string\n ttlSeconds?: number\n}): PairingCodeState {\n const pairCode = randomBytes(6).toString('base64url')\n const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1000).toISOString()\n const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`\n const state: PairingCodeState = {\n mailbox: params.mailbox.toLowerCase(),\n pairCode,\n expiresAt,\n connectUrl,\n }\n const resolved = params.file ?? defaultPairingPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(state, null, 2), 'utf-8')\n return state\n}\n\nexport function pairingUrlToWebUrl(connectUrl: string): string {\n const parsed = new URL(connectUrl)\n const url = new URL(DEFAULT_PAIRING_WEB_URL)\n for (const [key, value] of parsed.searchParams) {\n url.searchParams.set(key, value)\n }\n return url.toString()\n}\n\nexport function consumePairingCode(params: {\n mailbox: string\n pairCode: string\n file?: string\n}): PairingCodeState | null {\n const resolved = params.file ?? defaultPairingPath()\n if (!existsSync(resolved)) return null\n const state = JSON.parse(readFileSync(resolved, 'utf-8')) as PairingCodeState\n if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase()) return null\n if (state.pairCode !== params.pairCode) return null\n if (state.consumedAt) return null\n if (new Date(state.expiresAt).getTime() <= Date.now()) return null\n writeFileSync(resolved, JSON.stringify({ ...state, pairCode: '', consumedAt: new Date().toISOString() }, null, 2), 'utf-8')\n return state\n}\n\nfunction isPairedSenderPolicy(value: unknown): value is PairedSenderPolicy {\n if (!value || typeof value !== 'object') return false\n const policy = value as PairedSenderPolicy\n return typeof policy.sender === 'string'\n && typeof policy.dispatchContextRules === 'object'\n && typeof policy.pairedAt === 'string'\n}\n\nexport function loadPairedSenderPolicies(file?: string): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n if (!existsSync(resolved)) return []\n try {\n const data = JSON.parse(readFileSync(resolved, 'utf-8')) as unknown\n return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : []\n } catch {\n return []\n }\n}\n\nexport function addPairedSenderPolicy(file: string | undefined, policy: PairedSenderPolicy): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n const policies = loadPairedSenderPolicies(resolved)\n const normalizedSender = policy.sender.toLowerCase()\n const next = [\n ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),\n { ...policy, sender: normalizedSender },\n ]\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(next, null, 2), 'utf-8')\n return next\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
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") ?? "";
@@ -2365,6 +2385,7 @@ var AampClient = class _AampClient extends TinyEmitter {
2365
2385
  taskDispatchConcurrency;
2366
2386
  pendingTaskDispatches = [];
2367
2387
  activeTaskDispatchCount = 0;
2388
+ discoveryPromise;
2368
2389
  streamAppendQueues = /* @__PURE__ */ new Map();
2369
2390
  constructor(config) {
2370
2391
  super();
@@ -2476,6 +2497,8 @@ var AampClient = class _AampClient extends TinyEmitter {
2476
2497
  }
2477
2498
  static createPairingCode = createPairingCode;
2478
2499
  static buildPairingUrl = buildPairingUrl;
2500
+ static buildPairingWebUrl = buildPairingWebUrl;
2501
+ static pairingUrlToWebUrl = pairingUrlToWebUrl;
2479
2502
  static parsePairingUrl = parsePairingUrl;
2480
2503
  static isPairingUrl = isPairingUrl;
2481
2504
  static consumePairingCode = consumePairingCode;
@@ -2513,6 +2536,38 @@ var AampClient = class _AampClient extends TinyEmitter {
2513
2536
  ...opts.body ? { body: JSON.stringify(opts.body) } : {}
2514
2537
  });
2515
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
+ }
2516
2571
  static async registerMailbox(opts) {
2517
2572
  const base = opts.aampHost.replace(/\/$/, "");
2518
2573
  const registerRes = await _AampClient.callDiscoveredApi(base, {
@@ -2738,7 +2793,7 @@ var AampClient = class _AampClient extends TinyEmitter {
2738
2793
  };
2739
2794
  }
2740
2795
  async resolveStreamCapability() {
2741
- const discovery = await _AampClient.discoverAampService(this.config.baseUrl, this.config.fetch);
2796
+ const discovery = await this.discoverCachedAampService();
2742
2797
  const stream = discovery.capabilities?.stream;
2743
2798
  if (!stream?.transport) {
2744
2799
  throw new Error("AAMP stream capability is not available on this service");
@@ -2747,11 +2802,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2747
2802
  }
2748
2803
  async createStream(opts) {
2749
2804
  const stream = await this.resolveStreamCapability();
2750
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2805
+ const res = await this.callAampApi({
2751
2806
  action: stream.createAction ?? "aamp.stream.create",
2752
2807
  method: "POST",
2753
2808
  authToken: this.config.mailboxToken,
2754
- fetch: this.config.fetch,
2755
2809
  body: opts
2756
2810
  });
2757
2811
  if (!res.ok) {
@@ -2794,11 +2848,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2794
2848
  }
2795
2849
  async dispatchStreamAppend(opts) {
2796
2850
  const stream = await this.resolveStreamCapability();
2797
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2851
+ const res = await this.callAampApi({
2798
2852
  action: stream.appendAction ?? "aamp.stream.append",
2799
2853
  method: "POST",
2800
2854
  authToken: this.config.mailboxToken,
2801
- fetch: this.config.fetch,
2802
2855
  body: opts
2803
2856
  });
2804
2857
  if (!res.ok) {
@@ -2900,11 +2953,10 @@ var AampClient = class _AampClient extends TinyEmitter {
2900
2953
  async closeStream(opts) {
2901
2954
  await this.flushStreamAppendQueue(opts.streamId);
2902
2955
  const stream = await this.resolveStreamCapability();
2903
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2956
+ const res = await this.callAampApi({
2904
2957
  action: stream.closeAction ?? "aamp.stream.close",
2905
2958
  method: "POST",
2906
2959
  authToken: this.config.mailboxToken,
2907
- fetch: this.config.fetch,
2908
2960
  body: opts
2909
2961
  });
2910
2962
  if (!res.ok) {
@@ -2915,10 +2967,9 @@ var AampClient = class _AampClient extends TinyEmitter {
2915
2967
  }
2916
2968
  async getTaskStream(opts) {
2917
2969
  const stream = await this.resolveStreamCapability();
2918
- const res = await _AampClient.callDiscoveredApi(this.config.baseUrl, {
2970
+ const res = await this.callAampApi({
2919
2971
  action: stream.getAction ?? "aamp.stream.get",
2920
2972
  authToken: this.config.mailboxToken,
2921
- fetch: this.config.fetch,
2922
2973
  query: {
2923
2974
  ...opts.taskId ? { taskId: opts.taskId } : {},
2924
2975
  ...opts.streamId ? { streamId: opts.streamId } : {}
@@ -3058,6 +3109,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3058
3109
  import { dirname, join } from "node:path";
3059
3110
  import { homedir } from "node:os";
3060
3111
  import { randomBytes as randomBytes2 } from "node:crypto";
3112
+ var DEFAULT_PAIRING_WEB_URL2 = "https://meshmail.ai/pair";
3061
3113
  function defaultCredentialsPath() {
3062
3114
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
3063
3115
  }
@@ -3131,6 +3183,14 @@ function createPairingCode2(params) {
3131
3183
  writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
3132
3184
  return state;
3133
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
+ }
3134
3194
  function consumePairingCode2(params) {
3135
3195
  const resolved = params.file ?? defaultPairingPath();
3136
3196
  if (!existsSync(resolved))
@@ -3750,8 +3810,10 @@ var src_default = {
3750
3810
  mailbox: email,
3751
3811
  file: cfg.pairingFile ?? defaultPairingPath()
3752
3812
  });
3753
- const qr = await renderTerminalQr(pairing.connectUrl);
3813
+ const webUrl = pairingUrlToWebUrl2(pairing.connectUrl);
3814
+ const qr = await renderTerminalQr(webUrl);
3754
3815
  api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
3816
+ api.logger.info(`[AAMP] Web pairing link: ${webUrl}`);
3755
3817
  if (qr)
3756
3818
  api.logger.info(`
3757
3819
  ${qr}`);
@@ -3762,7 +3824,8 @@ ${qr}`);
3762
3824
  Scan this QR code:
3763
3825
  ${qr}` : "\nCould not render a terminal QR code.",
3764
3826
  `
3765
- Pairing URL: ${pairing.connectUrl}`
3827
+ Pairing link: ${webUrl}`,
3828
+ `Pairing URL: ${pairing.connectUrl}`
3766
3829
  ].join("\n");
3767
3830
  }
3768
3831
  function wakeAgentForPendingTask(task) {
@@ -3854,7 +3917,7 @@ Pairing URL: ${pairing.connectUrl}`
3854
3917
  file: cfg.pairingFile ?? defaultPairingPath()
3855
3918
  });
3856
3919
  api.logger.info(`[AAMP] Pair with AAMP App before ${pairing.expiresAt}: ${pairing.connectUrl}`);
3857
- const qr = await renderTerminalQr(pairing.connectUrl);
3920
+ const qr = await renderTerminalQr(pairingUrlToWebUrl2(pairing.connectUrl));
3858
3921
  if (qr)
3859
3922
  api.logger.info(`
3860
3923
  ${qr}`);
@@ -4751,7 +4814,7 @@ ${lines.join("\n")}`
4751
4814
  }, { name: "aamp_pending_tasks" });
4752
4815
  api.registerTool({
4753
4816
  name: "aamp_pairing_code",
4754
- 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".',
4755
4818
  parameters: { type: "object", properties: {} },
4756
4819
  execute: async () => ({
4757
4820
  content: [{ type: "text", text: await renderPairingCodeForCurrentAgent() }]
@@ -5020,7 +5083,7 @@ Question: ${h.question}`,
5020
5083
  });
5021
5084
  api.registerCommand({
5022
5085
  name: "aamp-pair",
5023
- 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",
5024
5087
  acceptsArgs: false,
5025
5088
  requireAuth: false,
5026
5089
  handler: async () => ({