aamp-openclaw-plugin 0.1.37 → 0.1.39

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
@@ -22,6 +22,18 @@ the installer will prompt for:
22
22
 
23
23
  The answers are written into the OpenClaw plugin config automatically, so users do not need to hand-edit `openclaw.json`.
24
24
 
25
+ When the plugin starts, it also prints a five-minute `aamp://connect?...`
26
+ pairing URL and terminal QR code. Scan it with AAMP App, paste it into User UI,
27
+ or run `aamp-cli pair --url ...` to authorize that sender. A valid
28
+ `pair.request` writes the sender and optional dispatch-context rules to the
29
+ paired sender policy file, then consumes the code. The plugin replies with
30
+ `pair.respond`; rejected responses include the failure reason.
31
+
32
+ You can generate a fresh pairing QR code later without restarting OpenClaw:
33
+
34
+ - Ask the agent to use the `aamp_pairing_code` tool.
35
+ - Or run the `/aamp-pair` command in OpenClaw.
36
+
25
37
  ## Build
26
38
 
27
39
  ```bash
@@ -41,6 +53,8 @@ npm run build
41
53
  "taskDispatchConcurrency": 10,
42
54
  "slug": "openclaw-agent",
43
55
  "credentialsFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
56
+ "pairingFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json",
57
+ "senderPoliciesFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json",
44
58
  "senderPolicies": [
45
59
  {
46
60
  "sender": "meegle-bot@meshmail.ai",
@@ -57,7 +71,7 @@ npm run build
57
71
  }
58
72
  ```
59
73
 
60
- If `senderPolicies` is omitted, all senders are accepted. If set, the dispatch sender must match one policy and all configured dispatch-context rules for that sender must pass.
74
+ If `senderPolicies` is omitted, no senders are authorized by default. Use the printed pairing QR/URL or configure at least one policy before sending `task.dispatch`; matching policies can also require all configured dispatch-context rules to pass.
61
75
  `taskDispatchConcurrency` is optional and defaults to `10`.
62
76
 
63
77
  The plugin also understands:
@@ -8,10 +8,13 @@ import { stdin as input, stdout as output, stderr } from 'node:process'
8
8
  import { spawnSync } from 'node:child_process'
9
9
  import { createRequire } from 'node:module'
10
10
  import { fileURLToPath } from 'node:url'
11
+ import { AampClient } from 'aamp-sdk'
11
12
 
12
13
  const PLUGIN_ID = 'aamp-openclaw-plugin'
13
14
  const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
14
15
  const DEFAULT_CREDENTIALS_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json'
16
+ const DEFAULT_PAIRING_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json'
17
+ const DEFAULT_SENDER_POLICIES_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json'
15
18
  const CODING_TOOL_ALLOWLIST = [
16
19
  'read',
17
20
  'write',
@@ -35,9 +38,12 @@ const CODING_TOOL_ALLOWLIST = [
35
38
  'image_generate',
36
39
  ]
37
40
  const AAMP_PLUGIN_TOOL_ALLOWLIST = [
41
+ 'aamp_directory_search',
38
42
  'aamp_send_result',
39
43
  'aamp_send_help',
40
44
  'aamp_pending_tasks',
45
+ 'aamp_pairing_code',
46
+ 'aamp_cancel_task',
41
47
  'aamp_dispatch_task',
42
48
  'aamp_check_protocol',
43
49
  'aamp_download_attachment',
@@ -176,6 +182,19 @@ export function writeJsonFile(path, value) {
176
182
  writeFileSync(path, JSON.stringify(value, null, 2) + '\n', 'utf-8')
177
183
  }
178
184
 
185
+ async function renderTerminalQr(value) {
186
+ try {
187
+ const qrcode = await import('qrcode-terminal')
188
+ const generator = qrcode.default?.generate ?? qrcode.generate
189
+ if (!generator) return ''
190
+ return await new Promise((resolve) => {
191
+ generator(value, { small: true }, (qr) => resolve(qr))
192
+ })
193
+ } catch {
194
+ return ''
195
+ }
196
+ }
197
+
179
198
  export function normalizeBaseUrl(url) {
180
199
  if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
181
200
  return `https://${url.replace(/\/$/, '')}`
@@ -444,50 +463,15 @@ export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile })
444
463
  }
445
464
  }
446
465
 
447
- const base = normalizeBaseUrl(aampHost)
448
- const discoveryRes = await fetch(`${base}/.well-known/aamp`)
449
- if (!discoveryRes.ok) {
450
- const text = await discoveryRes.text().catch(() => '')
451
- throw new Error(`AAMP discovery failed (${discoveryRes.status}): ${text || discoveryRes.statusText}`)
452
- }
453
- const discovery = await discoveryRes.json()
454
- const apiUrl = discovery?.api?.url
455
- if (!apiUrl) {
456
- throw new Error('AAMP discovery did not return api.url')
457
- }
458
- const apiBase = new URL(apiUrl, `${base}/`).toString()
459
-
460
- const registerRes = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
461
- method: 'POST',
462
- headers: { 'Content-Type': 'application/json' },
463
- body: JSON.stringify({
464
- slug,
465
- description: 'OpenClaw AAMP agent node',
466
- }),
466
+ const credData = await AampClient.registerMailbox({
467
+ aampHost: normalizeBaseUrl(aampHost),
468
+ slug,
469
+ description: 'OpenClaw AAMP agent node',
467
470
  })
468
-
469
- if (!registerRes.ok) {
470
- const text = await registerRes.text().catch(() => '')
471
- throw new Error(`AAMP self-register failed (${registerRes.status}): ${text || registerRes.statusText}`)
472
- }
473
-
474
- const registerData = await registerRes.json()
475
- const code = registerData?.registrationCode
476
- if (!code) {
477
- throw new Error('AAMP self-register succeeded but no registrationCode was returned')
478
- }
479
-
480
- const credRes = await fetch(`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(code)}`)
481
- if (!credRes.ok) {
482
- const text = await credRes.text().catch(() => '')
483
- throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
484
- }
485
-
486
- const credData = await credRes.json()
487
471
  const identity = {
488
472
  email: credData?.email,
489
- mailboxToken: credData?.mailbox?.token ?? credData?.jmap?.token,
490
- smtpPassword: credData?.smtp?.password,
473
+ mailboxToken: credData?.mailboxToken,
474
+ smtpPassword: credData?.smtpPassword,
491
475
  }
492
476
 
493
477
  if (!identity.email || !identity.mailboxToken || !identity.smtpPassword) {
@@ -498,6 +482,18 @@ export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile })
498
482
  return { created: true, email: identity.email, credentialsPath: resolvedCreds }
499
483
  }
500
484
 
485
+ export function createPairingFile({ email, pairingFile }) {
486
+ if (!email) return null
487
+ const pairing = AampClient.createPairingCode({ mailbox: email })
488
+ writeJsonFile(expandHome(pairingFile), {
489
+ mailbox: pairing.mailbox,
490
+ pairCode: pairing.pairCode,
491
+ expiresAt: pairing.expiresAt,
492
+ connectUrl: pairing.connectUrl,
493
+ })
494
+ return pairing
495
+ }
496
+
501
497
  function printHelp() {
502
498
  output.write(
503
499
  [
@@ -541,7 +537,7 @@ export async function runInit() {
541
537
  'Detected existing plugin config:',
542
538
  ` aampHost: ${previousConfig.aampHost ?? DEFAULT_AAMP_HOST}`,
543
539
  ` slug: ${previousConfig.slug ?? 'openclaw-agent'}`,
544
- ` senderPolicies: ${previousConfig.senderPolicies ? JSON.stringify(previousConfig.senderPolicies) : '(allow all)'}`,
540
+ ` senderPolicies: ${previousConfig.senderPolicies ? JSON.stringify(previousConfig.senderPolicies) : '(default deny until paired or configured)'}`,
545
541
  '',
546
542
  ].join('\n'),
547
543
  )
@@ -554,7 +550,7 @@ export async function runInit() {
554
550
  aampHost = aampHostAnswer.trim() || aampHost
555
551
 
556
552
  const senderAnswer = await rl.question(
557
- 'Primary trusted dispatch sender (e.g. meegle-bot@meshmail.ai, leave blank to allow all): ',
553
+ 'Primary trusted dispatch sender (e.g. meegle-bot@meshmail.ai, leave blank to pair later): ',
558
554
  )
559
555
  const sender = senderAnswer.trim()
560
556
  if (sender) {
@@ -620,6 +616,8 @@ export async function runInit() {
620
616
  aampHost,
621
617
  slug,
622
618
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
619
+ pairingFile: DEFAULT_PAIRING_FILE,
620
+ senderPoliciesFile: DEFAULT_SENDER_POLICIES_FILE,
623
621
  ...(senderPolicies ? { senderPolicies } : {}),
624
622
  }, {
625
623
  includeCodingBaseline,
@@ -646,6 +644,10 @@ export async function runInit() {
646
644
  slug,
647
645
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
648
646
  })
647
+ const pairing = identityResult.email
648
+ ? createPairingFile({ email: identityResult.email, pairingFile: DEFAULT_PAIRING_FILE })
649
+ : null
650
+ const qr = pairing ? await renderTerminalQr(pairing.connectUrl) : ''
649
651
 
650
652
  const restartResult = restartGateway()
651
653
 
@@ -661,7 +663,9 @@ export async function runInit() {
661
663
  ` channels.aamp.enabled: ${next.channels?.aamp?.enabled === true ? 'true' : 'false'}`,
662
664
  ` aampHost: ${aampHost}`,
663
665
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
664
- ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
666
+ ` pairingFile: ${DEFAULT_PAIRING_FILE}`,
667
+ ` senderPoliciesFile: ${DEFAULT_SENDER_POLICIES_FILE}`,
668
+ ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(default deny until paired or configured)'}`,
665
669
  ` tools.allow: ${JSON.stringify(next.tools?.allow ?? [])}`,
666
670
  ` codingBaselineAdded: ${toolPolicyPlan.missingCodingTools.length > 0 && includeCodingBaseline ? 'yes' : 'no'}`,
667
671
  identityResult.created
@@ -676,6 +680,8 @@ export async function runInit() {
676
680
  restartResult.ok
677
681
  ? 'Plugin changes should now be active.'
678
682
  : 'Please restart the OpenClaw gateway manually for the plugin changes to take effect.',
683
+ pairing ? `Pairing URL (expires ${pairing.expiresAt}): ${pairing.connectUrl}` : '',
684
+ qr ? `\n${qr}` : '',
679
685
  '',
680
686
  ].join('\n'),
681
687
  )
@@ -2,12 +2,19 @@
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
+ import { randomBytes } from "node:crypto";
5
6
  function defaultCredentialsPath() {
6
7
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
7
8
  }
8
9
  function defaultTaskStatePath() {
9
10
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
10
11
  }
12
+ function defaultPairingPath() {
13
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".pairing.json");
14
+ }
15
+ function defaultSenderPoliciesPath() {
16
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".sender-policies.json");
17
+ }
11
18
  function loadCachedIdentity(file) {
12
19
  const resolved = file ?? defaultCredentialsPath();
13
20
  if (!existsSync(resolved))
@@ -54,6 +61,66 @@ function saveTaskState(state, file) {
54
61
  terminalTaskIds: state.terminalTaskIds ?? []
55
62
  }, null, 2), "utf-8");
56
63
  }
64
+ function createPairingCode(params) {
65
+ const pairCode = randomBytes(6).toString("base64url");
66
+ const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1e3).toISOString();
67
+ const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`;
68
+ const state = {
69
+ mailbox: params.mailbox.toLowerCase(),
70
+ pairCode,
71
+ expiresAt,
72
+ connectUrl
73
+ };
74
+ const resolved = params.file ?? defaultPairingPath();
75
+ mkdirSync(dirname(resolved), { recursive: true });
76
+ writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
77
+ return state;
78
+ }
79
+ function consumePairingCode(params) {
80
+ const resolved = params.file ?? defaultPairingPath();
81
+ if (!existsSync(resolved))
82
+ return null;
83
+ const state = JSON.parse(readFileSync(resolved, "utf-8"));
84
+ if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase())
85
+ return null;
86
+ if (state.pairCode !== params.pairCode)
87
+ return null;
88
+ if (state.consumedAt)
89
+ return null;
90
+ if (new Date(state.expiresAt).getTime() <= Date.now())
91
+ return null;
92
+ writeFileSync(resolved, JSON.stringify({ ...state, pairCode: "", consumedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
93
+ return state;
94
+ }
95
+ function isPairedSenderPolicy(value) {
96
+ if (!value || typeof value !== "object")
97
+ return false;
98
+ const policy = value;
99
+ return typeof policy.sender === "string" && typeof policy.dispatchContextRules === "object" && typeof policy.pairedAt === "string";
100
+ }
101
+ function loadPairedSenderPolicies(file) {
102
+ const resolved = file ?? defaultSenderPoliciesPath();
103
+ if (!existsSync(resolved))
104
+ return [];
105
+ try {
106
+ const data = JSON.parse(readFileSync(resolved, "utf-8"));
107
+ return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : [];
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+ function addPairedSenderPolicy(file, policy) {
113
+ const resolved = file ?? defaultSenderPoliciesPath();
114
+ const policies = loadPairedSenderPolicies(resolved);
115
+ const normalizedSender = policy.sender.toLowerCase();
116
+ const next = [
117
+ ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),
118
+ { ...policy, sender: normalizedSender }
119
+ ];
120
+ mkdirSync(dirname(resolved), { recursive: true });
121
+ writeFileSync(resolved, JSON.stringify(next, null, 2), "utf-8");
122
+ return next;
123
+ }
57
124
  function ensureDir(dir) {
58
125
  mkdirSync(dir, { recursive: true });
59
126
  }
@@ -65,10 +132,16 @@ function writeBinaryFile(path, content) {
65
132
  writeFileSync(path, content);
66
133
  }
67
134
  export {
135
+ addPairedSenderPolicy,
136
+ consumePairingCode,
137
+ createPairingCode,
68
138
  defaultCredentialsPath,
139
+ defaultPairingPath,
140
+ defaultSenderPoliciesPath,
69
141
  defaultTaskStatePath,
70
142
  ensureDir,
71
143
  loadCachedIdentity,
144
+ loadPairedSenderPolicies,
72
145
  loadTaskState,
73
146
  readBinaryFile,
74
147
  saveCachedIdentity,
@@ -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'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: 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 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 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": ";AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AAYjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,uBAA+B;AAC7C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,kBAAkB;AAC9F;AAEO,SAAS,mBAAmB,MAAgC;AACjE,QAAM,WAAW,QAAQ,uBAAuB;AAChD,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,gBAAgB,CAAC,OAAO;AAAc,aAAO;AAC1E,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,IACvB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAoB,MAAqB;AAC1E,QAAM,WAAW,QAAQ,uBAAuB;AAChD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU;AAAA,IACrC,OAAO,SAAS;AAAA,IAChB,cAAc,SAAS;AAAA,IACvB,cAAc,SAAS;AAAA,EACzB,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;AAEO,SAAS,cAAc,MAAgC;AAC5D,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO,EAAE,iBAAiB,CAAC,EAAE;AACxD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,WAAO;AAAA,MACL,iBAAiB,MAAM,QAAQ,OAAO,eAAe,IAAI,OAAO,gBAAgB,OAAO,OAAO,IAAI,CAAC;AAAA,IACrG;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,iBAAiB,CAAC,EAAE;AAAA,EAC/B;AACF;AAEO,SAAS,cAAc,OAAwB,MAAqB;AACzE,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU;AAAA,IACrC,iBAAiB,MAAM,mBAAmB,CAAC;AAAA,EAC7C,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;AAEO,SAAS,UAAU,KAAmB;AAC3C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,aAAa,IAAI;AAC1B;AAEO,SAAS,gBAAgB,MAAc,SAAoC;AAChF,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,OAAO;AAC7B;",
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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",
6
6
  "names": []
7
7
  }