aamp-openclaw-plugin 0.1.26 → 0.1.28

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
@@ -57,3 +57,17 @@ npm run build
57
57
  ```
58
58
 
59
59
  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.
60
+
61
+ The plugin also understands:
62
+
63
+ - dispatch priority via `X-AAMP-Priority`
64
+ - dispatch expiry via `X-AAMP-Expires-At`
65
+ - sender-side cancellation via `task.cancel`
66
+
67
+ When multiple tasks are pending locally, the plugin schedules them in this order:
68
+
69
+ 1. `urgent`
70
+ 2. `high`
71
+ 3. `normal`
72
+
73
+ Within the same priority, tasks are processed FIFO by receive time. On startup, the plugin reconciles recent mailbox history so that still-valid tasks can be recovered after the agent was offline.
@@ -43,19 +43,19 @@ const AAMP_PLUGIN_TOOL_ALLOWLIST = [
43
43
  'aamp_download_attachment',
44
44
  ]
45
45
 
46
- function resolveOpenClawHome() {
46
+ export function resolveOpenClawHome() {
47
47
  return process.env.OPENCLAW_HOME?.trim() || join(homedir(), '.openclaw')
48
48
  }
49
49
 
50
- function resolveOpenClawConfigPath() {
50
+ export function resolveOpenClawConfigPath() {
51
51
  return join(resolveOpenClawHome(), 'openclaw.json')
52
52
  }
53
53
 
54
- function resolveExtensionDir() {
54
+ export function resolveExtensionDir() {
55
55
  return join(resolveOpenClawHome(), 'extensions', PLUGIN_ID)
56
56
  }
57
57
 
58
- function expandHome(pathValue) {
58
+ export function expandHome(pathValue) {
59
59
  if (!pathValue) return pathValue
60
60
  if (pathValue === '~') return homedir()
61
61
  if (pathValue.startsWith('~/')) return join(homedir(), pathValue.slice(2))
@@ -155,7 +155,7 @@ function stripTrailingCommas(text) {
155
155
  return result
156
156
  }
157
157
 
158
- function parseJsonConfig(raw, path) {
158
+ export function parseJsonConfig(raw, path) {
159
159
  const normalized = raw.replace(/^\uFEFF/, '')
160
160
  const sanitized = stripTrailingCommas(stripJsonComments(normalized))
161
161
  try {
@@ -166,22 +166,22 @@ function parseJsonConfig(raw, path) {
166
166
  }
167
167
  }
168
168
 
169
- function readJsonFile(path) {
169
+ export function readJsonFile(path) {
170
170
  if (!existsSync(path)) return null
171
171
  return parseJsonConfig(readFileSync(path, 'utf-8'), path)
172
172
  }
173
173
 
174
- function writeJsonFile(path, value) {
174
+ export function writeJsonFile(path, value) {
175
175
  mkdirSync(dirname(path), { recursive: true })
176
176
  writeFileSync(path, JSON.stringify(value, null, 2) + '\n', 'utf-8')
177
177
  }
178
178
 
179
- function normalizeBaseUrl(url) {
179
+ export function normalizeBaseUrl(url) {
180
180
  if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
181
181
  return `https://${url.replace(/\/$/, '')}`
182
182
  }
183
183
 
184
- function ensurePluginConfig(config, pluginConfig, options = {}) {
184
+ export function ensurePluginConfig(config, pluginConfig, options = {}) {
185
185
  const next = config && typeof config === 'object' ? structuredClone(config) : {}
186
186
  if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
187
187
  if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
@@ -239,7 +239,7 @@ function ensurePluginInstallRecord(config, installRecord) {
239
239
  return next
240
240
  }
241
241
 
242
- function ensureAampToolAllowlist(toolsConfig, options = {}) {
242
+ export function ensureAampToolAllowlist(toolsConfig, options = {}) {
243
243
  const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
244
244
  const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
245
245
  const includeCodingBaseline = options.includeCodingBaseline === true
@@ -255,7 +255,7 @@ function ensureAampToolAllowlist(toolsConfig, options = {}) {
255
255
  return next
256
256
  }
257
257
 
258
- function planToolPolicyUpdate(toolsConfig, options = {}) {
258
+ export function planToolPolicyUpdate(toolsConfig, options = {}) {
259
259
  const current = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
260
260
  const existingAllow = Array.isArray(current.allow) ? current.allow.filter((value) => typeof value === 'string' && value.trim()) : []
261
261
  const includeCodingBaseline = options.includeCodingBaseline === true
@@ -293,7 +293,7 @@ function currentToolPolicySummary(plan) {
293
293
  return lines.join('\n')
294
294
  }
295
295
 
296
- function parseDispatchContextRules(raw) {
296
+ export function parseDispatchContextRules(raw) {
297
297
  const trimmed = raw.trim()
298
298
  if (!trimmed) return undefined
299
299
  const rules = {}
@@ -359,7 +359,7 @@ function ensureBuiltArtifacts(packageRoot) {
359
359
  }
360
360
  }
361
361
 
362
- function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
362
+ export function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
363
363
  const extensionDir = resolveExtensionDir()
364
364
  const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
365
365
  ensureBuiltArtifacts(packageRoot)
@@ -408,7 +408,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
408
408
  return { extensionDir, packageJson, packageRoot }
409
409
  }
410
410
 
411
- function restartGateway() {
411
+ export function restartGateway() {
412
412
  const result = spawnSync('openclaw', ['gateway', 'restart'], {
413
413
  encoding: 'utf-8',
414
414
  })
@@ -433,7 +433,7 @@ function restartGateway() {
433
433
  }
434
434
  }
435
435
 
436
- async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
436
+ export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
437
437
  const resolvedCreds = expandHome(credentialsFile)
438
438
  if (existsSync(resolvedCreds)) {
439
439
  const cachedIdentity = readJsonFile(resolvedCreds)
@@ -445,7 +445,19 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
445
445
  }
446
446
 
447
447
  const base = normalizeBaseUrl(aampHost)
448
- const registerRes = await fetch(`${base}/api/nodes/self-register`, {
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`, {
449
461
  method: 'POST',
450
462
  headers: { 'Content-Type': 'application/json' },
451
463
  body: JSON.stringify({
@@ -465,7 +477,7 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
465
477
  throw new Error('AAMP self-register succeeded but no registrationCode was returned')
466
478
  }
467
479
 
468
- const credRes = await fetch(`${base}/api/nodes/credentials?code=${encodeURIComponent(code)}`)
480
+ const credRes = await fetch(`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(code)}`)
469
481
  if (!credRes.ok) {
470
482
  const text = await credRes.text().catch(() => '')
471
483
  throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
@@ -474,11 +486,11 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
474
486
  const credData = await credRes.json()
475
487
  const identity = {
476
488
  email: credData?.email,
477
- jmapToken: credData?.jmap?.token,
489
+ mailboxToken: credData?.mailbox?.token ?? credData?.jmap?.token,
478
490
  smtpPassword: credData?.smtp?.password,
479
491
  }
480
492
 
481
- if (!identity.email || !identity.jmapToken || !identity.smtpPassword) {
493
+ if (!identity.email || !identity.mailboxToken || !identity.smtpPassword) {
482
494
  throw new Error('AAMP credential exchange returned an incomplete identity payload')
483
495
  }
484
496
 
@@ -499,7 +511,7 @@ function printHelp() {
499
511
  )
500
512
  }
501
513
 
502
- async function runInit() {
514
+ export async function runInit() {
503
515
  const configPath = resolveOpenClawConfigPath()
504
516
  const existing = readJsonFile(configPath)
505
517
  const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
@@ -669,7 +681,7 @@ async function runInit() {
669
681
  )
670
682
  }
671
683
 
672
- async function main() {
684
+ export async function main() {
673
685
  const command = process.argv[2] || 'help'
674
686
 
675
687
  if (command === 'help' || command === '--help' || command === '-h') {
@@ -687,7 +699,9 @@ async function main() {
687
699
  process.exitCode = 1
688
700
  }
689
701
 
690
- main().catch((err) => {
691
- stderr.write(`${err instanceof Error ? err.message : String(err)}\n`)
692
- process.exit(1)
693
- })
702
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
703
+ main().catch((err) => {
704
+ stderr.write(`${err instanceof Error ? err.message : String(err)}\n`)
705
+ process.exit(1)
706
+ })
707
+ }
@@ -5,15 +5,22 @@ import { homedir } from "node:os";
5
5
  function defaultCredentialsPath() {
6
6
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
7
7
  }
8
+ function defaultTaskStatePath() {
9
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
10
+ }
8
11
  function loadCachedIdentity(file) {
9
12
  const resolved = file ?? defaultCredentialsPath();
10
13
  if (!existsSync(resolved))
11
14
  return null;
12
15
  try {
13
16
  const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
14
- if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword)
17
+ if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword)
15
18
  return null;
16
- return parsed;
19
+ return {
20
+ email: parsed.email,
21
+ mailboxToken: parsed.mailboxToken,
22
+ smtpPassword: parsed.smtpPassword
23
+ };
17
24
  } catch {
18
25
  return null;
19
26
  }
@@ -21,7 +28,31 @@ function loadCachedIdentity(file) {
21
28
  function saveCachedIdentity(identity, file) {
22
29
  const resolved = file ?? defaultCredentialsPath();
23
30
  mkdirSync(dirname(resolved), { recursive: true });
24
- writeFileSync(resolved, JSON.stringify(identity, null, 2), "utf-8");
31
+ writeFileSync(resolved, JSON.stringify({
32
+ email: identity.email,
33
+ mailboxToken: identity.mailboxToken,
34
+ smtpPassword: identity.smtpPassword
35
+ }, null, 2), "utf-8");
36
+ }
37
+ function loadTaskState(file) {
38
+ const resolved = file ?? defaultTaskStatePath();
39
+ if (!existsSync(resolved))
40
+ return { terminalTaskIds: [] };
41
+ try {
42
+ const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
43
+ return {
44
+ terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : []
45
+ };
46
+ } catch {
47
+ return { terminalTaskIds: [] };
48
+ }
49
+ }
50
+ function saveTaskState(state, file) {
51
+ const resolved = file ?? defaultTaskStatePath();
52
+ mkdirSync(dirname(resolved), { recursive: true });
53
+ writeFileSync(resolved, JSON.stringify({
54
+ terminalTaskIds: state.terminalTaskIds ?? []
55
+ }, null, 2), "utf-8");
25
56
  }
26
57
  function ensureDir(dir) {
27
58
  mkdirSync(dir, { recursive: true });
@@ -35,10 +66,13 @@ function writeBinaryFile(path, content) {
35
66
  }
36
67
  export {
37
68
  defaultCredentialsPath,
69
+ defaultTaskStatePath,
38
70
  ensureDir,
39
71
  loadCachedIdentity,
72
+ loadTaskState,
40
73
  readBinaryFile,
41
74
  saveCachedIdentity,
75
+ saveTaskState,
42
76
  writeBinaryFile
43
77
  };
44
78
  //# sourceMappingURL=file-store.js.map
@@ -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 jmapToken: string\n smtpPassword: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.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.jmapToken || !parsed.smtpPassword) return null\n return parsed as Identity\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(identity, 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;AAQjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;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,aAAa,CAAC,OAAO;AAAc,aAAO;AACvE,WAAO;AAAA,EACT,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,UAAU,MAAM,CAAC,GAAG,OAAO;AACpE;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'\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;",
6
6
  "names": []
7
7
  }