aamp-openclaw-plugin 0.1.10 → 0.1.13

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
@@ -8,10 +8,17 @@ OpenClaw plugin that gives an OpenClaw agent an AAMP mailbox identity.
8
8
  npm install aamp-openclaw-plugin
9
9
  ```
10
10
 
11
- When installed from OpenClaw, the plugin onboarding will prompt for:
11
+ When installed via:
12
+
13
+ ```bash
14
+ npx aamp-openclaw-plugin init
15
+ ```
16
+
17
+ the installer will prompt for:
12
18
 
13
19
  - `AAMP Host`
14
- - `Allowed Senders`
20
+ - `Primary trusted dispatch sender`
21
+ - optional `Dispatch context rules`
15
22
 
16
23
  The answers are written into the OpenClaw plugin config automatically, so users do not need to hand-edit `openclaw.json`.
17
24
 
@@ -33,8 +40,14 @@ npm run build
33
40
  "aampHost": "https://meshmail.ai",
34
41
  "slug": "openclaw-agent",
35
42
  "credentialsFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
36
- "allowedSenders": [
37
- "meegle-bot@meshmail.ai"
43
+ "senderPolicies": [
44
+ {
45
+ "sender": "meegle-bot@meshmail.ai",
46
+ "dispatchContextRules": {
47
+ "project_key": ["proj_123"],
48
+ "user_key": ["alice"]
49
+ }
50
+ }
38
51
  ]
39
52
  }
40
53
  }
@@ -43,4 +56,4 @@ npm run build
43
56
  }
44
57
  ```
45
58
 
46
- If `allowedSenders` is omitted, all senders are accepted. If set, only listed email addresses are allowed.
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.
@@ -3,6 +3,7 @@
3
3
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { homedir } from 'node:os'
5
5
  import { dirname, join } from 'node:path'
6
+ import { createRequire } from 'node:module'
6
7
  import { createInterface } from 'node:readline/promises'
7
8
  import { stdin as input, stdout as output, stderr } from 'node:process'
8
9
  import { spawnSync } from 'node:child_process'
@@ -62,8 +63,8 @@ function ensurePluginConfig(config, pluginConfig) {
62
63
  ...(prevEntry?.config && typeof prevEntry.config === 'object' ? prevEntry.config : {}),
63
64
  ...pluginConfig,
64
65
  }
65
- if (!pluginConfig.allowedSenders) {
66
- delete mergedConfig.allowedSenders
66
+ if (!pluginConfig.senderPolicies) {
67
+ delete mergedConfig.senderPolicies
67
68
  }
68
69
 
69
70
  next.plugins.entries[PLUGIN_ID] = {
@@ -79,13 +80,33 @@ function ensurePluginConfig(config, pluginConfig) {
79
80
  return next
80
81
  }
81
82
 
82
- function parseAllowedSenders(raw) {
83
+ function parseDispatchContextRules(raw) {
83
84
  const trimmed = raw.trim()
84
85
  if (!trimmed) return undefined
85
- return trimmed
86
- .split(',')
87
- .map((s) => s.trim())
88
- .filter(Boolean)
86
+ const rules = {}
87
+ for (const part of trimmed.split(';')) {
88
+ const segment = part.trim()
89
+ if (!segment) continue
90
+ const eqIdx = segment.indexOf('=')
91
+ if (eqIdx <= 0) continue
92
+ const key = segment.slice(0, eqIdx).trim().toLowerCase()
93
+ if (!/^[a-z0-9_-]+$/.test(key)) continue
94
+ const values = segment
95
+ .slice(eqIdx + 1)
96
+ .split(',')
97
+ .map((s) => s.trim())
98
+ .filter(Boolean)
99
+ if (values.length) {
100
+ rules[key] = values
101
+ }
102
+ }
103
+ return Object.keys(rules).length ? rules : undefined
104
+ }
105
+
106
+ function isYes(answer, defaultValue = true) {
107
+ const normalized = answer.trim().toLowerCase()
108
+ if (!normalized) return defaultValue
109
+ return ['y', 'yes'].includes(normalized)
89
110
  }
90
111
 
91
112
  function packageRootFromEntry(entryPath) {
@@ -101,6 +122,12 @@ function copyIntoDir(src, dest) {
101
122
  cpSync(src, dest, { recursive: true, force: true })
102
123
  }
103
124
 
125
+ function resolveInstalledPackageRoot(packageRoot, specifier) {
126
+ const packageRequire = createRequire(join(packageRoot, 'package.json'))
127
+ const resolvedEntry = packageRequire.resolve(specifier)
128
+ return packageRootFromEntry(resolvedEntry)
129
+ }
130
+
104
131
  function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
105
132
  const extensionDir = resolveExtensionDir()
106
133
  const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
@@ -127,7 +154,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
127
154
  mkdirSync(nodeModulesDir, { recursive: true })
128
155
 
129
156
  for (const dep of dependencyPackages) {
130
- const depRoot = join(packageRoot, 'node_modules', dep)
157
+ const depRoot = resolveInstalledPackageRoot(packageRoot, dep)
131
158
  if (!existsSync(depRoot)) {
132
159
  throw new Error(`Missing dependency directory: ${depRoot}`)
133
160
  }
@@ -229,50 +256,95 @@ function printHelp() {
229
256
  }
230
257
 
231
258
  async function runInit() {
232
- let aampHost = DEFAULT_AAMP_HOST
233
- let allowedSenders
259
+ const configPath = resolveOpenClawConfigPath()
260
+ const existing = readJsonFile(configPath)
261
+ const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
262
+ const previousConfig = previousEntry?.config && typeof previousEntry.config === 'object'
263
+ ? previousEntry.config
264
+ : null
265
+ const previousCredentialsFile = previousConfig?.credentialsFile || DEFAULT_CREDENTIALS_FILE
266
+ const previousSlug = previousConfig?.slug || 'openclaw-agent'
267
+
268
+ let aampHost = previousConfig?.aampHost || DEFAULT_AAMP_HOST
269
+ let senderPolicies = previousConfig?.senderPolicies
270
+ let slug = previousSlug
271
+ let reuseExistingConfig = Boolean(previousConfig)
234
272
 
235
273
  if (input.isTTY) {
236
274
  const rl = createInterface({ input, output })
237
275
  try {
238
276
  output.write('AAMP OpenClaw Plugin Setup\n\n')
239
277
 
240
- const aampHostAnswer = await rl.question(`AAMP Host (${DEFAULT_AAMP_HOST}): `)
241
- aampHost = aampHostAnswer.trim() || DEFAULT_AAMP_HOST
242
-
243
- const allowedSendersAnswer = await rl.question(
244
- 'Allowed Senders (comma-separated, leave blank to allow all): ',
245
- )
246
- allowedSenders = parseAllowedSenders(allowedSendersAnswer)
278
+ if (previousConfig) {
279
+ output.write(
280
+ [
281
+ 'Detected existing plugin config:',
282
+ ` aampHost: ${previousConfig.aampHost ?? DEFAULT_AAMP_HOST}`,
283
+ ` slug: ${previousConfig.slug ?? 'openclaw-agent'}`,
284
+ ` senderPolicies: ${previousConfig.senderPolicies ? JSON.stringify(previousConfig.senderPolicies) : '(allow all)'}`,
285
+ '',
286
+ ].join('\n'),
287
+ )
288
+ const reuseAnswer = await rl.question('Reuse current plugin config? [Y/n]: ')
289
+ reuseExistingConfig = isYes(reuseAnswer, true)
290
+ }
291
+
292
+ if (!reuseExistingConfig) {
293
+ const aampHostAnswer = await rl.question(`AAMP Host (${aampHost}): `)
294
+ aampHost = aampHostAnswer.trim() || aampHost
295
+
296
+ const senderAnswer = await rl.question(
297
+ 'Primary trusted dispatch sender (e.g. meegle-bot@meshmail.ai, leave blank to allow all): ',
298
+ )
299
+ const sender = senderAnswer.trim()
300
+ if (sender) {
301
+ const rulesAnswer = await rl.question(
302
+ 'Dispatch context rules for that sender (optional, format: project_key=proj1,proj2; user_key=alice): ',
303
+ )
304
+ const dispatchContextRules = parseDispatchContextRules(rulesAnswer)
305
+ senderPolicies = [{
306
+ sender,
307
+ ...(dispatchContextRules ? { dispatchContextRules } : {}),
308
+ }]
309
+ } else {
310
+ senderPolicies = undefined
311
+ }
312
+ }
247
313
  } finally {
248
314
  rl.close()
249
315
  }
250
316
  } else {
251
- const [hostLine = '', sendersLine = ''] = readFileSync(0, 'utf-8').split(/\r?\n/)
252
- aampHost = hostLine.trim() || DEFAULT_AAMP_HOST
253
- allowedSenders = parseAllowedSenders(sendersLine)
317
+ if (!reuseExistingConfig) {
318
+ const [hostLine = '', senderLine = '', rulesLine = ''] = readFileSync(0, 'utf-8').split(/\r?\n/)
319
+ aampHost = hostLine.trim() || aampHost
320
+ const sender = senderLine.trim()
321
+ if (sender) {
322
+ const dispatchContextRules = parseDispatchContextRules(rulesLine)
323
+ senderPolicies = [{
324
+ sender,
325
+ ...(dispatchContextRules ? { dispatchContextRules } : {}),
326
+ }]
327
+ } else {
328
+ senderPolicies = undefined
329
+ }
330
+ }
254
331
  }
255
332
 
256
- const configPath = resolveOpenClawConfigPath()
257
- const existing = readJsonFile(configPath)
258
- const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
259
- const previousCredentialsFile = previousEntry?.config?.credentialsFile || DEFAULT_CREDENTIALS_FILE
260
-
261
333
  output.write('\nInstalling OpenClaw plugin files...\n')
262
334
  const extensionDir = installPluginFiles(previousCredentialsFile)
263
335
 
264
336
  const next = ensurePluginConfig(existing, {
265
337
  aampHost,
266
- slug: 'openclaw-agent',
338
+ slug,
267
339
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
268
- ...(allowedSenders ? { allowedSenders } : {}),
340
+ ...(senderPolicies ? { senderPolicies } : {}),
269
341
  })
270
342
 
271
343
  writeJsonFile(configPath, next)
272
344
 
273
345
  const identityResult = await ensureMailboxIdentity({
274
346
  aampHost,
275
- slug: 'openclaw-agent',
347
+ slug,
276
348
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
277
349
  })
278
350
 
@@ -288,7 +360,7 @@ async function runInit() {
288
360
  ` plugins.entries["${PLUGIN_ID}"]`,
289
361
  ` aampHost: ${aampHost}`,
290
362
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
291
- ` allowedSenders: ${allowedSenders ? allowedSenders.join(', ') : '(allow all)'}`,
363
+ ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
292
364
  identityResult.created
293
365
  ? ` mailbox: ${identityResult.email} (registered and saved to ${identityResult.credentialsPath})`
294
366
  : ` mailbox: existing credentials reused from ${identityResult.credentialsPath}`,
package/dist/index.js CHANGED
@@ -7,6 +7,37 @@ import {
7
7
  saveCachedIdentity,
8
8
  writeBinaryFile
9
9
  } from "./file-store.js";
10
+ function matchSenderPolicy(task, senderPolicies) {
11
+ if (!senderPolicies?.length)
12
+ return { allowed: true };
13
+ const sender = task.from.toLowerCase();
14
+ const policy = senderPolicies.find((item) => item.sender.trim().toLowerCase() === sender);
15
+ if (!policy) {
16
+ return { allowed: false, reason: `sender ${task.from} is not allowed by senderPolicies` };
17
+ }
18
+ const rules = policy.dispatchContextRules;
19
+ if (!rules || Object.keys(rules).length === 0) {
20
+ return { allowed: true };
21
+ }
22
+ const context = task.dispatchContext ?? {};
23
+ const effectiveRules = Object.entries(rules).map(([key, allowedValues]) => [
24
+ key,
25
+ (allowedValues ?? []).map((value) => value.trim()).filter(Boolean)
26
+ ]).filter(([, allowedValues]) => allowedValues.length > 0);
27
+ if (effectiveRules.length === 0) {
28
+ return { allowed: true };
29
+ }
30
+ for (const [key, allowedValues] of effectiveRules) {
31
+ const contextValue = context[key];
32
+ if (!contextValue) {
33
+ return { allowed: false, reason: `dispatchContext missing required key "${key}"` };
34
+ }
35
+ if (!allowedValues.includes(contextValue)) {
36
+ return { allowed: false, reason: `dispatchContext ${key}=${contextValue} is not allowed` };
37
+ }
38
+ }
39
+ return { allowed: true };
40
+ }
10
41
  function baseUrl(aampHost) {
11
42
  if (aampHost.startsWith("http://") || aampHost.startsWith("https://")) {
12
43
  return aampHost.replace(/\/$/, "");
@@ -95,10 +126,27 @@ var src_default = {
95
126
  type: "string",
96
127
  description: "Absolute path to cache AAMP credentials between gateway restarts. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json. Delete this file to force re-registration with a new mailbox."
97
128
  },
98
- allowedSenders: {
129
+ senderPolicies: {
99
130
  type: "array",
100
- items: { type: "string" },
101
- description: 'Sender whitelist. Only task.dispatch emails from these addresses are accepted. Matching is case-insensitive and exact. If omitted, all senders are accepted. If set to [], all senders are rejected. Example: ["meego-abc123@aamp.local", "ci-bot-ff09@aamp.local"]'
131
+ description: "Per-sender authorization policies. Each sender can optionally require specific X-AAMP-Dispatch-Context key/value pairs before a task is accepted.",
132
+ items: {
133
+ type: "object",
134
+ required: ["sender"],
135
+ properties: {
136
+ sender: {
137
+ type: "string",
138
+ description: "Dispatch sender email address (case-insensitive exact match)."
139
+ },
140
+ dispatchContextRules: {
141
+ type: "object",
142
+ description: "Optional exact-match rules over X-AAMP-Dispatch-Context. All listed keys must be present and their values must match one of the configured entries.",
143
+ additionalProperties: {
144
+ type: "array",
145
+ items: { type: "string" }
146
+ }
147
+ }
148
+ }
149
+ }
102
150
  }
103
151
  }
104
152
  },
@@ -132,6 +180,70 @@ var src_default = {
132
180
  }
133
181
  }
134
182
  });
183
+ function triggerHeartbeatWake(sessionKey, label) {
184
+ try {
185
+ api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey });
186
+ api.logger.info(`[AAMP] Heartbeat triggered for ${label} via session ${sessionKey}`);
187
+ } catch (err) {
188
+ api.logger.warn(`[AAMP] Could not trigger heartbeat for ${label}: ${err.message}`);
189
+ }
190
+ }
191
+ function wakeAgentForPendingTask(task) {
192
+ const fallback = () => triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`);
193
+ const dispatcher = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher;
194
+ api.logger.info(
195
+ `[AAMP] Wake requested for task ${task.taskId} \u2014 channelRuntime=${channelRuntime ? "yes" : "no"} channelCfg=${channelCfg ? "yes" : "no"} dispatcher=${typeof dispatcher === "function" ? "yes" : "no"} session=${currentSessionKey}`
196
+ );
197
+ if (!channelRuntime || !channelCfg || typeof dispatcher !== "function") {
198
+ fallback();
199
+ return;
200
+ }
201
+ const prompt = [
202
+ "## New AAMP Task",
203
+ "",
204
+ "A new AAMP task just arrived.",
205
+ "Use the pending AAMP task in system context as the source of truth and handle it now.",
206
+ "Reply with aamp_send_result or aamp_send_help before responding."
207
+ ].join("\n");
208
+ try {
209
+ void Promise.resolve(dispatcher({
210
+ ctx: {
211
+ Body: task.bodyText || task.title,
212
+ BodyForAgent: prompt,
213
+ From: task.from,
214
+ To: agentEmail,
215
+ SessionKey: `aamp:default:task:${task.taskId}`,
216
+ AccountId: "default",
217
+ ChatType: "dm",
218
+ Provider: "aamp",
219
+ Surface: "aamp",
220
+ OriginatingChannel: "aamp",
221
+ OriginatingTo: task.from,
222
+ MessageSid: task.messageId || task.taskId,
223
+ Timestamp: Date.now(),
224
+ SenderName: task.from,
225
+ SenderId: task.from,
226
+ CommandAuthorized: true
227
+ },
228
+ cfg: channelCfg,
229
+ dispatcherOptions: {
230
+ deliver: async () => {
231
+ },
232
+ onError: (err) => {
233
+ api.logger.error(`[AAMP] Channel dispatch error for task ${task.taskId}: ${err instanceof Error ? err.message : String(err)}`);
234
+ }
235
+ }
236
+ })).then(() => {
237
+ api.logger.info(`[AAMP] Channel dispatch triggered for task ${task.taskId}`);
238
+ }).catch((err) => {
239
+ api.logger.error(`[AAMP] Channel dispatch failed for task ${task.taskId}: ${err.message}`);
240
+ fallback();
241
+ });
242
+ } catch (err) {
243
+ api.logger.error(`[AAMP] Channel dispatch threw synchronously for task ${task.taskId}: ${err.message}`);
244
+ fallback();
245
+ }
246
+ }
135
247
  async function doConnect(identity) {
136
248
  if (reconcileTimer) {
137
249
  clearInterval(reconcileTimer);
@@ -161,39 +273,37 @@ var src_default = {
161
273
  });
162
274
  aampClient.on("task.dispatch", (task) => {
163
275
  api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
164
- const allowed = cfg.allowedSenders;
165
- if (allowed !== void 0) {
166
- const fromLower = task.from.toLowerCase();
167
- const permitted = allowed.some((e) => e.toLowerCase() === fromLower);
168
- if (!permitted) {
169
- api.logger.warn(`[AAMP] \u2717 rejected (not in allowedSenders): ${task.from} task=${task.taskId}`);
276
+ try {
277
+ const decision = matchSenderPolicy(task, cfg.senderPolicies);
278
+ if (!decision.allowed) {
279
+ api.logger.warn(`[AAMP] \u2717 rejected by senderPolicies: ${task.from} task=${task.taskId} reason=${decision.reason}`);
170
280
  void aampClient.sendResult({
171
281
  to: task.from,
172
282
  taskId: task.taskId,
173
283
  status: "rejected",
174
284
  output: "",
175
- errorMsg: `Sender ${task.from} is not in the allowed senders list.`
285
+ errorMsg: decision.reason ?? `Sender ${task.from} is not allowed.`
176
286
  }).catch((err) => {
177
287
  api.logger.error(`[AAMP] Failed to send rejection for task ${task.taskId}: ${err.message}`);
178
288
  });
179
289
  return;
180
290
  }
181
- }
182
- pendingTasks.set(task.taskId, {
183
- taskId: task.taskId,
184
- from: task.from,
185
- title: task.title,
186
- bodyText: task.bodyText ?? "",
187
- contextLinks: task.contextLinks,
188
- timeoutSecs: task.timeoutSecs,
189
- messageId: task.messageId ?? "",
190
- receivedAt: (/* @__PURE__ */ new Date()).toISOString()
191
- });
192
- try {
193
- api.runtime.system.requestHeartbeatNow({ reason: "wake", sessionKey: currentSessionKey });
194
- api.logger.info(`[AAMP] Heartbeat triggered for session ${currentSessionKey}`);
291
+ pendingTasks.set(task.taskId, {
292
+ taskId: task.taskId,
293
+ from: task.from,
294
+ title: task.title,
295
+ bodyText: task.bodyText ?? "",
296
+ contextLinks: task.contextLinks,
297
+ timeoutSecs: task.timeoutSecs,
298
+ messageId: task.messageId ?? "",
299
+ receivedAt: (/* @__PURE__ */ new Date()).toISOString()
300
+ });
301
+ wakeAgentForPendingTask(pendingTasks.get(task.taskId));
195
302
  } catch (err) {
196
- api.logger.warn(`[AAMP] Could not trigger heartbeat: ${err.message}`);
303
+ api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${err.message}`);
304
+ if (pendingTasks.has(task.taskId)) {
305
+ triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`);
306
+ }
197
307
  }
198
308
  });
199
309
  aampClient.on("task.result", (result) => {
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/**\n * @aamp/openclaw-plugin\n *\n * OpenClaw plugin that gives the agent an AAMP mailbox identity and lets it\n * receive, process, and reply to AAMP tasks \u2014 entirely through standard email.\n *\n * How it works:\n * 1. Plugin resolves or auto-registers an AAMP mailbox identity on startup.\n * 2. Credentials are cached to a local file so the same mailbox is reused\n * across gateway restarts (no re-registration needed).\n * 3. Background JMAP WebSocket Push receives incoming task.dispatch emails.\n * 4. Incoming tasks are stored in an in-memory pending-task queue.\n * 5. before_prompt_build injects the oldest pending task into the LLM's\n * system context so the agent sees it and acts without user prompting.\n * 6. The agent calls aamp_send_result or aamp_send_help to reply.\n *\n * OpenClaw config (openclaw.json):\n *\n * \"plugins\": {\n * \"entries\": {\n * \"aamp\": {\n * \"enabled\": true,\n * \"config\": {\n * \"aampHost\": \"https://meshmail.ai\",\n * \"slug\": \"openclaw-agent\",\n * \"credentialsFile\": \"/absolute/path/to/.aamp-credentials.json\"\n * }\n * }\n * }\n * }\n *\n * Install:\n * openclaw plugins install ./packages/openclaw-plugin\n */\n\nimport { AampClient } from 'aamp-sdk'\nimport type { TaskDispatch, TaskResult, TaskHelp, AampAttachment, ReceivedAttachment } from 'aamp-sdk'\nimport {\n defaultCredentialsPath,\n ensureDir,\n loadCachedIdentity,\n readBinaryFile,\n saveCachedIdentity,\n writeBinaryFile,\n type Identity,\n} from './file-store.js'\n\n// \u2500\u2500\u2500 Shared runtime state (single instance per plugin lifetime) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface PendingTask {\n taskId: string\n from: string\n title: string\n bodyText: string\n contextLinks: string[]\n timeoutSecs: number\n messageId: string\n receivedAt: string // ISO-8601\n}\n\ninterface PluginConfig {\n /** e.g. \"meshmail.ai\" \u2014 all URLs are derived from this */\n aampHost: string\n slug?: string\n /** Absolute path to cache AAMP credentials. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json */\n credentialsFile?: string\n /**\n * Sender whitelist. Only task.dispatch emails whose `from` address appears in\n * this list are enqueued and processed. All other senders are silently rejected\n * with an automatic task.result status=rejected email.\n *\n * Matching is case-insensitive and exact (no wildcards).\n * If omitted (undefined), all senders are accepted.\n * If set to an empty array [], all senders are rejected.\n *\n * Example: [\"meego-abc123@aamp.local\", \"ci-bot-ff09@aamp.local\"]\n */\n allowedSenders?: string[]\n}\n\ntype StructuredResultFieldInput = {\n fieldKey: string\n fieldTypeKey: string\n fieldAlias?: string\n value?: unknown\n index?: string\n attachmentFilenames?: string[]\n}\n\n/** Normalise aampHost to a base URL with scheme and no trailing slash */\nfunction baseUrl(aampHost: string): string {\n if (aampHost.startsWith('http://') || aampHost.startsWith('https://')) {\n return aampHost.replace(/\\/$/, '')\n }\n return `https://${aampHost}`\n}\n\nconst pendingTasks = new Map<string, PendingTask>()\n// Tracks sub-tasks dispatched TO other agents \u2014 waiting for their result/help replies\nconst dispatchedSubtasks = new Map<string, { to: string; title: string; dispatchedAt: string; parentTaskId?: string }>()\n// Tracks notification keys that have been shown to LLM (auto-cleaned on next prompt build)\nconst shownNotifications = new Set<string>()\n// Pending synchronous dispatch waiters \u2014 resolve callback keyed by sub-task ID.\n// When aamp_dispatch_task sends a sub-task, it parks a Promise here and waits.\n// When task.result/help arrives for that sub-task ID, the waiter is resolved\n// directly, keeping the LLM awake with full context (no heartbeat needed).\nconst waitingDispatches = new Map<string, (reply: { type: 'result' | 'help'; data: unknown }) => void>()\nlet aampClient: AampClient | null = null\nlet agentEmail = ''\nlet lastConnectionError = ''\nlet lastDisconnectReason = ''\nlet lastTransportMode: 'disconnected' | 'websocket' | 'polling' = 'disconnected'\nlet lastLoggedTransportMode: 'disconnected' | 'websocket' | 'polling' = 'disconnected'\nlet reconcileTimer: NodeJS.Timeout | null = null\nlet transportMonitorTimer: NodeJS.Timeout | null = null\n// Tracks the most recently seen session key so task.dispatch can wake the right session.\n// Default 'agent:main:main' is the standard OpenClaw single-agent session key.\nlet currentSessionKey = 'agent:main:main'\n// Channel runtime \u2014 captured from channel adapter's startAccount for instant dispatch.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet channelRuntime: any = null\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet channelCfg: any = null\n\nfunction logTransportState(\n api: { logger: { info: (msg: string) => void; warn: (msg: string) => void } },\n mode: 'websocket' | 'polling',\n email: string,\n previousMode: 'disconnected' | 'websocket' | 'polling',\n): void {\n if (mode === previousMode) return\n\n if (mode === 'polling') {\n api.logger.info(`[AAMP] Connected (polling fallback active) \u2014 listening as ${email}`)\n return\n }\n\n if (previousMode === 'polling') {\n api.logger.info(`[AAMP] WebSocket restored \u2014 listening as ${email}`)\n return\n }\n\n api.logger.info(`[AAMP] Connected \u2014 listening as ${email}`)\n}\n\n// \u2500\u2500\u2500 Identity helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface Identity {\n email: string\n jmapToken: string\n smtpPassword: string\n}\n\n/**\n * Register a new AAMP node via the management service.\n *\n * Always creates a NEW mailbox (always returns 201). The slug is just a\n * human-readable prefix; a random hex suffix makes the email unique.\n * Callers should only call this once and persist the returned credentials.\n */\nasync function registerNode(cfg: PluginConfig): Promise<Identity> {\n const slug = (cfg.slug ?? 'openclaw-agent')\n .toLowerCase()\n .replace(/[\\s_]+/g, '-')\n .replace(/[^a-z0-9-]/g, '')\n\n const base = baseUrl(cfg.aampHost)\n\n // Step 1: Self-register \u2192 get one-time registration code\n const res = await fetch(`${base}/api/nodes/self-register`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug, description: 'OpenClaw AAMP agent node' }),\n })\n\n if (!res.ok) {\n const err = (await res.json().catch(() => ({}))) as { error?: string }\n throw new Error(`AAMP registration failed (${res.status}): ${err.error ?? res.statusText}`)\n }\n\n const regData = (await res.json()) as {\n registrationCode: string\n email: string\n }\n\n // Step 2: Exchange registration code for credentials\n const credRes = await fetch(\n `${base}/api/nodes/credentials?code=${encodeURIComponent(regData.registrationCode)}`,\n )\n\n if (!credRes.ok) {\n const err = (await credRes.json().catch(() => ({}))) as { error?: string }\n throw new Error(`AAMP credential exchange failed (${credRes.status}): ${err.error ?? credRes.statusText}`)\n }\n\n const credData = (await credRes.json()) as {\n email: string\n jmap: { token: string }\n smtp: { password: string }\n }\n\n return {\n email: credData.email,\n jmapToken: credData.jmap.token,\n smtpPassword: credData.smtp.password,\n }\n}\n\n/**\n * Resolve this agent's identity:\n * 1. Return cached credentials from disk if available.\n * 2. Otherwise register a new node and cache the result.\n */\nasync function resolveIdentity(cfg: PluginConfig): Promise<Identity> {\n const cached = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath())\n if (cached) return cached\n\n const identity = await registerNode(cfg)\n saveCachedIdentity(identity, cfg.credentialsFile ?? defaultCredentialsPath())\n return identity\n}\n\n// \u2500\u2500\u2500 Plugin definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default {\n id: 'aamp-openclaw-plugin',\n name: 'AAMP Agent Mail Protocol',\n\n configSchema: {\n type: 'object',\n properties: {\n aampHost: {\n type: 'string',\n description: 'AAMP service host, e.g. https://meshmail.ai',\n },\n slug: {\n type: 'string',\n default: 'openclaw-agent',\n description: 'Agent name prefix used in the mailbox address',\n },\n credentialsFile: {\n type: 'string',\n description:\n 'Absolute path to cache AAMP credentials between gateway restarts. ' +\n 'Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json. ' +\n 'Delete this file to force re-registration with a new mailbox.',\n },\n allowedSenders: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Sender whitelist. Only task.dispatch emails from these addresses are accepted. ' +\n 'Matching is case-insensitive and exact. ' +\n 'If omitted, all senders are accepted. If set to [], all senders are rejected. ' +\n 'Example: [\"meego-abc123@aamp.local\", \"ci-bot-ff09@aamp.local\"]',\n },\n },\n },\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(api: any) {\n // api.pluginConfig = the config block under plugins.entries[\"aamp-openclaw-plugin\"].config in openclaw.json\n // api.config = the full global OpenClaw config (NOT our plugin's config)\n const cfg = (api.pluginConfig ?? {}) as PluginConfig\n\n // \u2500\u2500 Register lightweight channel adapter to capture channelRuntime \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // We register as a channel SOLELY to get access to channelRuntime, which provides\n // dispatchReplyWithBufferedBlockDispatcher for instant LLM dispatch (bypassing\n // heartbeat's global running-mutex + requests-in-flight ~60s delay).\n // JMAP connection is managed by registerService, NOT startAccount.\n api.registerChannel({\n id: 'aamp',\n meta: { label: 'AAMP' },\n capabilities: { chatTypes: ['dm'] },\n config: {\n listAccountIds: () => cfg.aampHost ? ['default'] : [],\n resolveAccount: () => ({ aampHost: cfg.aampHost }),\n isEnabled: () => !!cfg.aampHost,\n isConfigured: () => !!loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath()),\n },\n gateway: {\n startAccount: async (ctx: { channelRuntime?: unknown; cfg?: unknown; abortSignal?: AbortSignal }) => {\n // Capture channelRuntime for use by sub-task notification dispatch\n channelRuntime = ctx.channelRuntime ?? null\n channelCfg = ctx.cfg ?? null\n api.logger.info(`[AAMP] Channel adapter started \u2014 channelRuntime ${channelRuntime ? 'available' : 'NOT available'}`)\n\n // Keep alive until abort \u2014 JMAP connection is managed by registerService\n await new Promise<void>((resolve) => {\n ctx.abortSignal?.addEventListener('abort', () => resolve())\n })\n channelRuntime = null\n channelCfg = null\n },\n stopAccount: async () => {\n channelRuntime = null\n channelCfg = null\n },\n },\n })\n\n // \u2500\u2500 Shared connect logic (used by service auto-connect and startup recovery) \u2500\u2500\u2500\u2500\u2500\u2500\n async function doConnect(identity: { email: string; jmapToken: string; smtpPassword: string }) {\n if (reconcileTimer) {\n clearInterval(reconcileTimer)\n reconcileTimer = null\n }\n if (transportMonitorTimer) {\n clearInterval(transportMonitorTimer)\n transportMonitorTimer = null\n }\n\n agentEmail = identity.email\n lastConnectionError = ''\n lastDisconnectReason = ''\n lastTransportMode = 'disconnected'\n lastLoggedTransportMode = 'disconnected'\n api.logger.info(`[AAMP] Mailbox identity ready \u2014 ${agentEmail}`)\n\n // All traffic goes through aampHost (port 3000).\n // The management service proxies /jmap/* and /.well-known/jmap \u2192 Stalwart:8080.\n const base = baseUrl(cfg.aampHost)\n\n aampClient = new AampClient({\n email: identity.email,\n jmapToken: identity.jmapToken,\n jmapUrl: base,\n smtpHost: new URL(base).hostname,\n smtpPort: 587,\n smtpPassword: identity.smtpPassword,\n // Local/dev: management-service proxy uses plain HTTP, no TLS cert to verify.\n // Production: set to true when using wss:// with valid certs.\n rejectUnauthorized: false,\n })\n\n aampClient.on('task.dispatch', (task: TaskDispatch) => {\n api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} \"${task.title}\" from=${task.from}`)\n\n // \u2500\u2500 Whitelist check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // undefined \u2192 allow all; [] \u2192 deny all; [...] \u2192 exact match only\n const allowed = cfg.allowedSenders\n if (allowed !== undefined) {\n const fromLower = task.from.toLowerCase()\n const permitted = allowed.some((e) => e.toLowerCase() === fromLower)\n if (!permitted) {\n api.logger.warn(`[AAMP] \u2717 rejected (not in allowedSenders): ${task.from} task=${task.taskId}`)\n void aampClient!.sendResult({\n to: task.from,\n taskId: task.taskId,\n status: 'rejected',\n output: '',\n errorMsg: `Sender ${task.from} is not in the allowed senders list.`,\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Failed to send rejection for task ${task.taskId}: ${err.message}`)\n })\n return\n }\n }\n\n pendingTasks.set(task.taskId, {\n taskId: task.taskId,\n from: task.from,\n title: task.title,\n bodyText: task.bodyText ?? '',\n contextLinks: task.contextLinks,\n timeoutSecs: task.timeoutSecs,\n messageId: task.messageId ?? '',\n receivedAt: new Date().toISOString(),\n })\n\n // Autonomously wake the agent so it processes the task without manual prompting.\n // reason:'wake' bypasses the HEARTBEAT.md file gate (which would skip if the file\n // is empty). before_prompt_build injects the task context into the LLM prompt.\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: currentSessionKey })\n api.logger.info(`[AAMP] Heartbeat triggered for session ${currentSessionKey}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Could not trigger heartbeat: ${(err as Error).message}`)\n }\n })\n\n // \u2500\u2500 Sub-task result: another agent completed a task we dispatched \u2500\u2500\u2500\u2500\u2500\u2500\n aampClient.on('task.result', (result: TaskResult) => {\n api.logger.info(`[AAMP] \u2190 task.result ${result.taskId} status=${result.status} from=${result.from}`)\n\n const sub = dispatchedSubtasks.get(result.taskId)\n dispatchedSubtasks.delete(result.taskId)\n\n // \u2500\u2500 Synchronous dispatch: if aamp_dispatch_task is waiting, resolve it directly \u2500\u2500\n const waiter = waitingDispatches.get(result.taskId)\n if (waiter) {\n waitingDispatches.delete(result.taskId)\n api.logger.info(`[AAMP] Resolving sync waiter for sub-task ${result.taskId}`)\n waiter({ type: 'result', data: result })\n return // Don't go through heartbeat/channel \u2014 the LLM is already awake\n }\n\n // Pre-download attachments to local disk so the LLM can reference them\n // by local file path (instead of requiring a separate download tool call).\n const downloadedFiles: Array<{ filename: string; path: string; size: number }> = []\n const downloadPromise = (async () => {\n if (!result.attachments?.length) return\n const dir = '/tmp/aamp-files'\n ensureDir(dir)\n for (const att of result.attachments) {\n try {\n const buffer = await aampClient!.downloadBlob(att.blobId, att.filename)\n const filepath = `${dir}/${att.filename}`\n writeBinaryFile(filepath, buffer)\n downloadedFiles.push({ filename: att.filename, path: filepath, size: buffer.length })\n api.logger.info(`[AAMP] Pre-downloaded: ${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`)\n } catch (dlErr) {\n api.logger.warn(`[AAMP] Pre-download failed for ${att.filename}: ${(dlErr as Error).message}`)\n }\n }\n })()\n\n downloadPromise.then(() => {\n // Build notification with pre-downloaded file paths\n const MAX_OUTPUT_CHARS = 800\n const label = result.status === 'completed' ? 'Sub-task completed' : 'Sub-task rejected'\n const rawOutput = result.output ?? ''\n const truncatedOutput = rawOutput.length > MAX_OUTPUT_CHARS\n ? rawOutput.slice(0, MAX_OUTPUT_CHARS) + `\\n\\n... [truncated, ${rawOutput.length} chars total]`\n : rawOutput\n\n let attachmentInfo = ''\n if (downloadedFiles.length > 0) {\n attachmentInfo = `\\n\\nAttachments (pre-downloaded to local disk):\\n${downloadedFiles.map(f =>\n `- ${f.filename} (${(f.size / 1024).toFixed(1)} KB) \u2192 ${f.path}`\n ).join('\\n')}\\nUse aamp_send_result with attachments: [${downloadedFiles.map(f => `{ filename: \"${f.filename}\", path: \"${f.path}\" }`).join(', ')}] to forward them.`\n } else if (result.attachments?.length) {\n const files = result.attachments.map((a: ReceivedAttachment) =>\n `${a.filename} (${(a.size / 1024).toFixed(1)} KB, blobId: ${a.blobId})`,\n )\n attachmentInfo = `\\n\\nAttachments (download failed \u2014 use aamp_download_attachment manually):\\n${files.join('\\n')}`\n }\n\n pendingTasks.set(`result:${result.taskId}`, {\n taskId: result.taskId,\n from: result.from,\n title: `${label}: ${sub?.title ?? result.taskId}`,\n bodyText: result.status === 'completed'\n ? `Agent ${result.from} completed the sub-task.\\n\\nOutput:\\n${truncatedOutput}${attachmentInfo}`\n : `Agent ${result.from} rejected the sub-task.\\n\\nReason: ${result.errorMsg ?? 'unknown'}`,\n contextLinks: [],\n timeoutSecs: 0,\n messageId: '',\n receivedAt: new Date().toISOString(),\n })\n\n // Wake LLM via channel dispatch (instant) or heartbeat (fallback)\n if (channelRuntime && channelCfg) {\n const notifyBody = pendingTasks.get(`result:${result.taskId}`)\n const actionableTasks = [...pendingTasks.entries()]\n .filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n .map(([, t]) => t)\n const actionSection = actionableTasks.length > 0\n ? `\\n\\n### Action Required\\nYou MUST call aamp_send_result to complete the pending task(s):\\n${actionableTasks.map(t => `- Task ID: ${t.taskId} | From: ${t.from} | Title: \"${t.title}\"`).join('\\n')}`\n : ''\n const prompt = `## Sub-task Update\\n\\n${notifyBody?.bodyText ?? 'Sub-task completed.'}${actionSection}`\n\n channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: {\n Body: `Sub-task result: ${result.taskId}`,\n BodyForAgent: prompt,\n From: result.from,\n To: agentEmail,\n SessionKey: `aamp:default:${result.from}`,\n AccountId: 'default',\n ChatType: 'dm',\n Provider: 'aamp',\n Surface: 'aamp',\n OriginatingChannel: 'aamp',\n OriginatingTo: result.from,\n MessageSid: result.taskId,\n Timestamp: Date.now(),\n SenderName: result.from,\n SenderId: result.from,\n CommandAuthorized: true,\n },\n cfg: channelCfg,\n dispatcherOptions: {\n deliver: async () => {},\n onError: (err: unknown) => {\n api.logger.error(`[AAMP] Channel dispatch error: ${err instanceof Error ? err.message : String(err)}`)\n },\n },\n }).then(() => {\n api.logger.info(`[AAMP] Channel dispatch completed for sub-task result ${result.taskId}`)\n pendingTasks.delete(`result:${result.taskId}`)\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Channel dispatch failed: ${err.message}`)\n })\n } else {\n const notifySessionKey = `agent:main:aamp-notify-${Date.now()}`\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: notifySessionKey })\n api.logger.info(`[AAMP] Heartbeat for sub-task result ${result.taskId}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Heartbeat for sub-task result failed: ${(err as Error).message}`)\n }\n }\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Sub-task result processing failed: ${err.message}`)\n })\n })\n\n // \u2500\u2500 Sub-task help: another agent asks for clarification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n aampClient.on('task.help', (help: TaskHelp) => {\n api.logger.info(`[AAMP] \u2190 task.help ${help.taskId} question=\"${help.question}\" from=${help.from}`)\n\n // \u2500\u2500 Synchronous dispatch: if aamp_dispatch_task is waiting, resolve it directly \u2500\u2500\n const waiter = waitingDispatches.get(help.taskId)\n if (waiter) {\n waitingDispatches.delete(help.taskId)\n api.logger.info(`[AAMP] Resolving sync waiter for sub-task help ${help.taskId}`)\n waiter({ type: 'help', data: help })\n return\n }\n\n const sub = dispatchedSubtasks.get(help.taskId)\n\n pendingTasks.set(`help:${help.taskId}`, {\n taskId: help.taskId,\n from: help.from,\n title: `Sub-task needs help: ${sub?.title ?? help.taskId}`,\n bodyText: `Agent ${help.from} is asking for help on the sub-task.\\n\\nQuestion: ${help.question}\\nBlocked reason: ${help.blockedReason}${help.suggestedOptions?.length ? `\\nSuggested options: ${help.suggestedOptions.join(', ')}` : ''}`,\n contextLinks: [],\n timeoutSecs: 0,\n messageId: '',\n receivedAt: new Date().toISOString(),\n })\n\n if (channelRuntime && channelCfg) {\n const notifyBody = pendingTasks.get(`help:${help.taskId}`)\n const prompt = `## Sub-task Help Request\\n\\n${notifyBody?.bodyText ?? help.question}`\n\n channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: {\n Body: `Sub-task help: ${help.taskId}`,\n BodyForAgent: prompt,\n From: help.from,\n To: agentEmail,\n SessionKey: `aamp:default:${help.from}`,\n AccountId: 'default',\n ChatType: 'dm',\n Provider: 'aamp',\n Surface: 'aamp',\n OriginatingChannel: 'aamp',\n OriginatingTo: help.from,\n MessageSid: help.taskId,\n Timestamp: Date.now(),\n SenderName: help.from,\n SenderId: help.from,\n CommandAuthorized: true,\n },\n cfg: channelCfg,\n dispatcherOptions: {\n deliver: async () => {},\n onError: (err: unknown) => {\n api.logger.error(`[AAMP] Channel dispatch error (help): ${err instanceof Error ? err.message : String(err)}`)\n },\n },\n }).then(() => {\n pendingTasks.delete(`help:${help.taskId}`)\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Channel dispatch failed for help: ${err.message}`)\n })\n } else {\n const helpSessionKey = `agent:main:aamp-notify-${Date.now()}`\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: helpSessionKey })\n api.logger.info(`[AAMP] Heartbeat fallback for sub-task help ${help.taskId}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Heartbeat for sub-task help failed: ${(err as Error).message}`)\n }\n }\n })\n\n aampClient.on('connected', () => {\n lastConnectionError = ''\n lastDisconnectReason = ''\n const mode = aampClient?.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n })\n\n aampClient.on('disconnected', (reason: string) => {\n lastDisconnectReason = reason\n if (lastTransportMode !== 'disconnected') {\n api.logger.warn(`[AAMP] Disconnected: ${reason} (will auto-reconnect)`)\n lastTransportMode = 'disconnected'\n lastLoggedTransportMode = 'disconnected'\n }\n })\n\n aampClient.on('error', (err: Error) => {\n lastConnectionError = err.message\n if (err.message.startsWith('JMAP WebSocket unavailable, falling back to polling:')) {\n if (lastTransportMode !== 'polling') {\n logTransportState(api, 'polling', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'polling'\n lastLoggedTransportMode = 'polling'\n }\n return\n }\n api.logger.error(`[AAMP] ${err.message}`)\n })\n\n await aampClient.connect()\n\n api.logger.info(\n `[AAMP] Transport after connect \u2014 ${aampClient.isUsingPollingFallback() ? 'polling fallback' : 'websocket'} as ${agentEmail}`,\n )\n\n if (aampClient.isConnected() && lastTransportMode === 'disconnected') {\n if (aampClient.isUsingPollingFallback()) {\n logTransportState(api, 'polling', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'polling'\n lastLoggedTransportMode = 'polling'\n } else {\n logTransportState(api, 'websocket', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'websocket'\n lastLoggedTransportMode = 'websocket'\n }\n }\n\n setTimeout(() => {\n if (!aampClient?.isConnected()) return\n const mode = aampClient.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n }, 1000)\n\n transportMonitorTimer = setInterval(() => {\n if (!aampClient) return\n if (!aampClient.isConnected()) {\n if (lastTransportMode !== 'disconnected') {\n lastTransportMode = 'disconnected'\n }\n return\n }\n const mode = aampClient.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n }, 5000)\n\n reconcileTimer = setInterval(() => {\n if (!aampClient) return\n void aampClient.reconcileRecentEmails(20).catch((err: Error) => {\n lastConnectionError = err.message\n api.logger.warn(`[AAMP] Mailbox reconcile failed: ${err.message}`)\n })\n }, 15000)\n }\n\n // \u2500\u2500 Service: auto-connect at gateway startup, disconnect on shutdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // registerService causes this plugin to load eagerly (at gateway startup),\n // not lazily (on first agent run). start() is called once the gateway is up.\n api.registerService({\n id: 'aamp-service',\n start: async () => {\n if (!cfg.aampHost) {\n api.logger.info('[AAMP] aampHost not configured \u2014 skipping auto-connect')\n return\n }\n try {\n const identity = await resolveIdentity(cfg)\n await doConnect(identity)\n } catch (err) {\n api.logger.warn(`[AAMP] Service auto-connect failed: ${(err as Error).message}`)\n }\n },\n stop: () => {\n if (reconcileTimer) {\n clearInterval(reconcileTimer)\n reconcileTimer = null\n }\n if (transportMonitorTimer) {\n clearInterval(transportMonitorTimer)\n transportMonitorTimer = null\n }\n if (aampClient) {\n try {\n aampClient.disconnect()\n api.logger.info('[AAMP] Disconnected on gateway stop')\n } catch {\n // ignore disconnect errors on shutdown\n }\n }\n },\n })\n\n // \u2500\u2500 gateway_start hook: re-trigger heartbeat after runner is initialized \u2500\u2500\u2500\n // Service start() runs BEFORE the heartbeat runner is ready, so\n // requestHeartbeatNow() called during JMAP initial fetch is silently dropped\n // (handler == null). gateway_start fires AFTER the heartbeat runner starts,\n // so we re-trigger here to process any tasks queued during startup.\n api.on('gateway_start', () => {\n if (pendingTasks.size === 0) return\n api.logger.info(`[AAMP] gateway_start: re-triggering heartbeat for ${pendingTasks.size} pending task(s)`)\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: currentSessionKey })\n } catch (err) {\n api.logger.warn(`[AAMP] gateway_start heartbeat failed: ${(err as Error).message}`)\n }\n })\n\n // \u2500\u2500 2. Prompt injection: surface the oldest pending task to the LLM \u2500\u2500\u2500\u2500\u2500\u2500\n api.on(\n 'before_prompt_build',\n (_event, ctx) => {\n // Keep currentSessionKey fresh \u2014 used by task.dispatch to target the right session.\n // Skip channel dispatch sessions (aamp:*) to avoid polluting the heartbeat session key.\n if (ctx?.sessionKey && !String(ctx.sessionKey).startsWith('aamp:')) {\n currentSessionKey = ctx.sessionKey\n }\n\n // Expire tasks that have exceeded their timeout\n const now = Date.now()\n for (const [id, t] of pendingTasks) {\n if (t.timeoutSecs && now - new Date(t.receivedAt).getTime() > t.timeoutSecs * 1000) {\n api.logger.warn(`[AAMP] Task ${id} timed out \u2014 removing from queue`)\n pendingTasks.delete(id)\n }\n }\n\n if (pendingTasks.size === 0) return {}\n\n // Prioritize notifications (sub-task results/help) over actionable tasks.\n // Without this, the oldest actionable task blocks notification delivery,\n // preventing the LLM from seeing sub-task results and completing the parent task.\n const allEntries = [...pendingTasks.entries()]\n const notifications = allEntries.filter(([key]) => key.startsWith('result:') || key.startsWith('help:'))\n const actionable = allEntries.filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n\n // Pick notification first if available, otherwise oldest actionable task\n const [taskKey, task] = notifications.length > 0\n ? notifications.sort((a, b) => new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime())[0]\n : actionable.sort((a, b) => new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime())[0]\n\n const isNotification = taskKey.startsWith('result:') || taskKey.startsWith('help:')\n\n // Notifications are one-shot: remove immediately after injecting into prompt\n if (isNotification && taskKey) {\n pendingTasks.delete(taskKey)\n }\n\n // Find remaining actionable tasks (non-notification) that still need a response\n const actionableTasks = [...pendingTasks.entries()]\n .filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n .map(([, t]) => t)\n\n const hasAttachmentInfo = isNotification && (task.bodyText?.includes('aamp_download_attachment') ?? false)\n const actionRequiredSection = isNotification && actionableTasks.length > 0\n ? [\n ``,\n `### Action Required`,\n ``,\n `You still have ${actionableTasks.length} pending task(s) that need a response.`,\n `Use the sub-task result above to complete them by calling aamp_send_result.`,\n ``,\n ...actionableTasks.map((t) =>\n `- Task ID: ${t.taskId} | From: ${t.from} | Title: \"${t.title}\"`\n ),\n ...(hasAttachmentInfo ? [\n ``,\n `### Forwarding Attachments`,\n `The sub-task result includes file attachments. To forward them:`,\n `1. Call aamp_download_attachment for each blobId listed above`,\n `2. Include the downloaded files in aamp_send_result via the attachments parameter`,\n ` Example: attachments: [{ filename: \"file.html\", path: \"/tmp/aamp-files/file.html\" }]`,\n ] : []),\n ].join('\\n')\n : ''\n\n const lines = isNotification ? [\n `## Sub-task Update`,\n ``,\n `A sub-task you dispatched has returned a result. Review the information below.`,\n `If the sub-task included attachments, use aamp_download_attachment to fetch them.`,\n ``,\n `Task ID: ${task.taskId}`,\n `From: ${task.from}`,\n `Title: ${task.title}`,\n task.bodyText ? `\\n${task.bodyText}` : '',\n actionRequiredSection,\n pendingTasks.size > 1 ? `\\n(+${pendingTasks.size - 1} more items queued)` : '',\n ] : [\n `## Pending AAMP Task (action required)`,\n ``,\n `You have received a task via AAMP email. You MUST call one of the two tools below`,\n `BEFORE responding to the user \u2014 do not skip this step.`,\n ``,\n `### Tool selection rules (follow strictly):`,\n ``,\n `Use aamp_send_result ONLY when ALL of the following are true:`,\n ` 1. The title contains a clear, specific action verb (e.g. \"summarise\", \"review\",`,\n ` \"translate\", \"generate\", \"fix\", \"search\", \"compare\", \"list\")`,\n ` 2. You know exactly what input/resource to act on`,\n ` 3. No ambiguity remains \u2014 you could start work immediately without asking anything`,\n ``,\n `Use aamp_send_help in ALL other cases, including:`,\n ` - Title is a greeting or salutation (\"hello\", \"hi\", \"hey\", \"test\", \"ping\", etc.)`,\n ` - Title is fewer than 4 words and contains no actionable verb`,\n ` - Title is too vague to act on without guessing (e.g. \"help\", \"task\", \"question\")`,\n ` - Required context is missing (which file? which URL? which criteria?)`,\n ` - Multiple interpretations are equally plausible`,\n ``,\n `IMPORTANT: Responding to a greeting with a greeting is WRONG. \"hello\" is not a`,\n `valid task description \u2014 ask what specific task the dispatcher needs done.`,\n ``,\n `### Sub-task dispatch rules:`,\n `If you delegate work to another agent via aamp_dispatch_task, you MUST pass`,\n `parentTaskId: \"${task.taskId}\" to establish the parent-child relationship.`,\n ``,\n `Task ID: ${task.taskId}`,\n `From: ${task.from}`,\n `Title: ${task.title}`,\n task.bodyText ? `Description:\\n${task.bodyText}` : '',\n task.contextLinks.length\n ? `Context Links:\\n${task.contextLinks.map((l) => ` - ${l}`).join('\\n')}`\n : '',\n task.timeoutSecs ? `Deadline: ${task.timeoutSecs}s from dispatch` : `Deadline: none`,\n `Received: ${task.receivedAt}`,\n pendingTasks.size > 1 ? `\\n(+${pendingTasks.size - 1} more tasks queued)` : '',\n ]\n .filter(Boolean)\n .join('\\n')\n\n return { prependContext: lines }\n },\n { priority: 5 },\n )\n\n // \u2500\u2500 3. Tool: send task result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_send_result',\n description:\n 'Send the result of an AAMP task back to the dispatcher. ' +\n 'Call this after you have finished processing the task.',\n parameters: {\n type: 'object',\n required: ['taskId', 'status', 'output'],\n properties: {\n taskId: {\n type: 'string',\n description: 'The AAMP task ID to reply to (from the system context)',\n },\n status: {\n type: 'string',\n enum: ['completed', 'rejected'],\n description: '\"completed\" on success, \"rejected\" if the task cannot be done',\n },\n output: {\n type: 'string',\n description: 'Your result or explanation',\n },\n errorMsg: {\n type: 'string',\n description: 'Optional error details (use only when status = rejected)',\n },\n attachments: {\n type: 'array',\n description: 'File attachments. Each item: { filename, contentType, path (local file path) }',\n items: {\n type: 'object',\n properties: {\n filename: { type: 'string' },\n contentType: { type: 'string' },\n path: { type: 'string', description: 'Absolute path to the file on disk' },\n },\n required: ['filename', 'path'],\n },\n },\n structuredResult: {\n type: 'array',\n description: 'Optional structured Meego field values.',\n items: {\n type: 'object',\n required: ['fieldKey', 'fieldTypeKey'],\n properties: {\n fieldKey: { type: 'string' },\n fieldTypeKey: { type: 'string' },\n fieldAlias: { type: 'string' },\n value: {\n description: 'Field value in the exact format required by Meego for this field type.',\n },\n index: { type: 'string' },\n attachmentFilenames: {\n type: 'array',\n items: { type: 'string' },\n description: 'For attachment fields, filenames from attachments[] that should be uploaded into this field.',\n },\n },\n },\n },\n },\n },\n execute: async (_id, params) => {\n const p = params as {\n taskId: string\n status: 'completed' | 'rejected'\n output: string\n errorMsg?: string\n attachments?: Array<{ filename: string; contentType?: string; path: string }>\n structuredResult?: StructuredResultFieldInput[]\n }\n\n const task = pendingTasks.get(p.taskId)\n if (!task) {\n return {\n content: [{ type: 'text', text: `Error: task ${p.taskId} not found in pending queue.` }],\n }\n }\n\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n api.logger.info(`[AAMP] aamp_send_result params ${JSON.stringify({\n taskId: p.taskId,\n status: p.status,\n output: p.output,\n errorMsg: p.errorMsg,\n attachments: p.attachments?.map((a) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n path: a.path,\n })) ?? [],\n structuredResult: p.structuredResult?.map((field) => ({\n fieldKey: field.fieldKey,\n fieldTypeKey: field.fieldTypeKey,\n fieldAlias: field.fieldAlias,\n value: field.value,\n index: field.index,\n attachmentFilenames: field.attachmentFilenames ?? [],\n })) ?? [],\n })}`)\n\n // Build attachments from file paths\n let attachments: AampAttachment[] | undefined\n if (p.attachments?.length) {\n attachments = p.attachments.map((a: { filename: string; contentType?: string; path: string }) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n content: readBinaryFile(a.path),\n }))\n }\n\n await aampClient.sendResult({\n to: task.from,\n taskId: task.taskId,\n status: p.status,\n output: p.output,\n errorMsg: p.errorMsg,\n structuredResult: p.structuredResult?.length ? p.structuredResult : undefined,\n inReplyTo: task.messageId || undefined,\n attachments,\n })\n\n pendingTasks.delete(task.taskId)\n api.logger.info(`[AAMP] \u2192 task.result ${task.taskId} ${p.status}`)\n\n // If more tasks remain, wake the agent to process them\n if (pendingTasks.size > 0) {\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: currentSessionKey })\n } catch { /* ignore */ }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Result sent for task ${task.taskId} (status: ${p.status}).`,\n },\n ],\n }\n },\n }, { name: 'aamp_send_result' })\n\n // \u2500\u2500 4. Tool: ask for help \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_send_help',\n description:\n 'Send a help request for an AAMP task when you are blocked or need human clarification ' +\n 'before you can proceed.',\n parameters: {\n type: 'object',\n required: ['taskId', 'question', 'blockedReason'],\n properties: {\n taskId: {\n type: 'string',\n description: 'The AAMP task ID',\n },\n question: {\n type: 'string',\n description: 'Your question for the human dispatcher',\n },\n blockedReason: {\n type: 'string',\n description: 'Why you cannot proceed without their input',\n },\n suggestedOptions: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional list of choices for the dispatcher to pick from',\n },\n },\n },\n execute: async (_id, params) => {\n const p = params as {\n taskId: string\n question: string\n blockedReason: string\n suggestedOptions?: string[]\n }\n\n const task = pendingTasks.get(p.taskId)\n if (!task) {\n return {\n content: [{ type: 'text', text: `Error: task ${p.taskId} not found in pending queue.` }],\n }\n }\n\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n await aampClient.sendHelp({\n to: task.from,\n taskId: task.taskId,\n question: p.question,\n blockedReason: p.blockedReason,\n suggestedOptions: p.suggestedOptions ?? [],\n inReplyTo: task.messageId || undefined,\n })\n\n api.logger.info(`[AAMP] \u2192 task.help ${task.taskId}`)\n\n // Keep the task in pending \u2014 the help reply may arrive later\n return {\n content: [\n {\n type: 'text',\n text: `Help request sent for task ${task.taskId}. The task remains pending until the dispatcher replies.`,\n },\n ],\n }\n },\n }, { name: 'aamp_send_help' })\n\n // \u2500\u2500 5. Tool: inspect queue \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_pending_tasks',\n description: 'List all AAMP tasks currently waiting to be processed.',\n parameters: { type: 'object', properties: {} },\n execute: async () => {\n if (pendingTasks.size === 0) {\n return { content: [{ type: 'text', text: 'No pending AAMP tasks.' }] }\n }\n\n const lines = [...pendingTasks.values()]\n .sort((a, b) => new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime())\n .map(\n (t, i) =>\n `${i + 1}. [${t.taskId}] \"${t.title}\"${t.bodyText ? `\\n Description: ${t.bodyText}` : ''} \u2014 from ${t.from} (received ${t.receivedAt})`,\n )\n\n return {\n content: [\n {\n type: 'text',\n text: `${pendingTasks.size} pending task(s):\\n${lines.join('\\n')}`,\n },\n ],\n }\n },\n }, { name: 'aamp_pending_tasks' })\n\n // \u2500\u2500 6. Tool: dispatch task to another agent (SYNCHRONOUS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Sends the task and BLOCKS until the sub-agent replies with task.result or\n // task.help. The reply is returned directly as the tool result, keeping the\n // LLM awake with full context \u2014 no heartbeat/channel dispatch needed.\n api.registerTool({\n name: 'aamp_dispatch_task',\n description:\n 'Send a task to another AAMP agent and WAIT for the result. ' +\n 'This tool blocks until the sub-agent replies (typically 5-60s). ' +\n 'The sub-agent\\'s output and any attachment file paths are returned directly.',\n parameters: {\n type: 'object',\n required: ['to', 'title'],\n properties: {\n to: { type: 'string', description: 'Target agent AAMP email address' },\n title: { type: 'string', description: 'Task title (concise summary)' },\n bodyText: { type: 'string', description: 'Detailed task description' },\n parentTaskId: { type: 'string', description: 'If you are processing a pending AAMP task, pass its Task ID here to establish parent-child nesting. Omit for top-level tasks.' },\n timeoutSecs: { type: 'number', description: 'Timeout in seconds (optional)' },\n contextLinks: {\n type: 'array', items: { type: 'string' },\n description: 'URLs providing context (optional)',\n },\n attachments: {\n type: 'array',\n description: 'File attachments. Each item: { filename, contentType, path (local file path) }',\n items: {\n type: 'object',\n properties: {\n filename: { type: 'string' },\n contentType: { type: 'string' },\n path: { type: 'string', description: 'Absolute path to the file on disk' },\n },\n required: ['filename', 'path'],\n },\n },\n },\n },\n execute: async (_id: unknown, params: {\n to: string; title: string; bodyText?: string;\n parentTaskId?: string; timeoutSecs?: number; contextLinks?: string[];\n attachments?: Array<{ filename: string; contentType?: string; path: string }>\n }) => {\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n try {\n // Build attachments from file paths\n let attachments: AampAttachment[] | undefined\n if (params.attachments?.length) {\n attachments = params.attachments.map((a: { filename: string; contentType?: string; path: string }) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n content: readBinaryFile(a.path),\n }))\n }\n\n const result = await aampClient.sendTask({\n to: params.to,\n title: params.title,\n parentTaskId: params.parentTaskId,\n timeoutSecs: params.timeoutSecs,\n contextLinks: params.contextLinks,\n attachments,\n })\n\n // Track as dispatched sub-task\n dispatchedSubtasks.set(result.taskId, {\n to: params.to,\n title: params.title,\n dispatchedAt: new Date().toISOString(),\n parentTaskId: params.parentTaskId,\n })\n\n api.logger.info(`[AAMP] \u2192 task.dispatch ${result.taskId} to=${params.to} parent=${params.parentTaskId ?? 'none'} (waiting for reply\u2026)`)\n\n // \u2500\u2500 SYNCHRONOUS WAIT: block until sub-agent replies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const timeoutMs = (params.timeoutSecs ?? 300) * 1000\n const reply = await new Promise<{ type: 'result' | 'help'; data: unknown }>((resolve, reject) => {\n waitingDispatches.set(result.taskId, resolve)\n setTimeout(() => {\n if (waitingDispatches.delete(result.taskId)) {\n reject(new Error(`Sub-task ${result.taskId} timed out after ${params.timeoutSecs ?? 300}s`))\n }\n }, timeoutMs)\n })\n\n api.logger.info(`[AAMP] \u2190 sync reply for ${result.taskId}: type=${reply.type} attachments=${JSON.stringify((reply.data as any)?.attachments?.length ?? 0)}`)\n\n if (reply.type === 'result') {\n const r = reply.data as TaskResult\n\n // Pre-download attachments \u2014 use direct JMAP blob download (bypass SDK's downloadBlob\n // which was returning 404 due to URL construction issues in the esbuild bundle).\n let attachmentLines = ''\n if (r.attachments?.length) {\n api.logger.info(`[AAMP] Downloading ${r.attachments.length} attachment(s) from sync reply...`)\n const dir = '/tmp/aamp-files'\n ensureDir(dir)\n const downloaded: string[] = []\n const base = baseUrl(cfg.aampHost)\n const identity = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath())\n const authHeader = identity ? `Basic ${Buffer.from(identity.email + ':' + identity.smtpPassword).toString('base64')}` : ''\n for (const att of r.attachments) {\n try {\n // Direct JMAP blob download \u2014 construct URL manually\n const dlUrl = `${base}/jmap/download/n/${encodeURIComponent(att.blobId)}/${encodeURIComponent(att.filename)}?accept=application/octet-stream`\n api.logger.info(`[AAMP] Fetching ${dlUrl}`)\n const dlRes = await fetch(dlUrl, { headers: { Authorization: authHeader } })\n if (!dlRes.ok) throw new Error(`HTTP ${dlRes.status}`)\n const buffer = Buffer.from(await dlRes.arrayBuffer())\n const filepath = `${dir}/${att.filename}`\n writeBinaryFile(filepath, buffer)\n downloaded.push(`${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`)\n api.logger.info(`[AAMP] Downloaded: ${att.filename} (${(buffer.length / 1024).toFixed(1)} KB)`)\n } catch (dlErr) {\n api.logger.error(`[AAMP] Download failed for ${att.filename}: ${(dlErr as Error).message}`)\n }\n }\n if (downloaded.length) {\n attachmentLines = `\\n\\nAttachments downloaded:\\n${downloaded.join('\\n')}`\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: [\n `Sub-task ${r.status}: ${params.title}`,\n `Agent: ${r.from}`,\n `Task ID: ${result.taskId}`,\n r.status === 'completed' ? `\\nOutput:\\n${r.output}` : `\\nError: ${r.errorMsg ?? 'rejected'}`,\n attachmentLines,\n ].filter(Boolean).join('\\n'),\n }],\n }\n } else {\n const h = reply.data as TaskHelp\n return {\n content: [{\n type: 'text',\n text: [\n `Sub-task needs help: ${params.title}`,\n `Agent: ${h.from}`,\n `Task ID: ${result.taskId}`,\n `\\nQuestion: ${h.question}`,\n `Blocked reason: ${h.blockedReason}`,\n h.suggestedOptions?.length ? `Options: ${h.suggestedOptions.join(' | ')}` : '',\n ].filter(Boolean).join('\\n'),\n }],\n }\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Error dispatching task: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_dispatch_task' })\n\n // \u2500\u2500 7. Tool: check AAMP protocol support \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_check_protocol',\n description:\n 'Check if an email address supports the AAMP protocol. ' +\n 'Returns { aamp: true/false } indicating whether the address is an AAMP agent.',\n parameters: {\n type: 'object',\n required: ['email'],\n properties: {\n email: { type: 'string', description: 'Email address to check' },\n },\n },\n execute: async (_id: unknown, params: { email: string }) => {\n const base = baseUrl(cfg.aampHost)\n const email = params?.email ?? ''\n if (!email) {\n return { content: [{ type: 'text', text: 'Error: email parameter is required' }] }\n }\n try {\n const res = await fetch(`${base}/api/aamp-check?email=${encodeURIComponent(email)}`)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const data = await res.json() as { aamp: boolean; domain?: string }\n return {\n content: [{\n type: 'text',\n text: data.aamp\n ? `${params.email} supports AAMP protocol (domain: ${data.domain ?? 'unknown'})`\n : `${params.email} does not support AAMP protocol`,\n }],\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Could not check ${params.email}: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_check_protocol' })\n\n // \u2500\u2500 8. Tool: download attachment blob \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_download_attachment',\n description:\n 'Download an AAMP email attachment to local disk by its blobId. ' +\n 'Use this to retrieve files received from sub-agent task results.',\n parameters: {\n type: 'object',\n required: ['blobId', 'filename'],\n properties: {\n blobId: { type: 'string', description: 'The JMAP blobId from the attachment metadata' },\n filename: { type: 'string', description: 'Filename to save as' },\n saveTo: { type: 'string', description: 'Directory to save to (default: /tmp/aamp-files)' },\n },\n },\n execute: async (_id: unknown, params: { blobId: string; filename: string; saveTo?: string }) => {\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n const dir = params.saveTo ?? '/tmp/aamp-files'\n ensureDir(dir)\n\n try {\n const buffer = await aampClient.downloadBlob(params.blobId, params.filename)\n const filepath = `${dir}/${params.filename}`\n writeBinaryFile(filepath, buffer)\n return {\n content: [{\n type: 'text',\n text: `Downloaded ${params.filename} (${(buffer.length / 1024).toFixed(1)} KB) to ${filepath}`,\n }],\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Download failed: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_download_attachment' })\n\n // \u2500\u2500 9. Slash command: /aamp-status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerCommand({\n name: 'aamp-status',\n description: 'Show AAMP connection status and pending task queue',\n acceptsArgs: false,\n requireAuth: false,\n handler: () => {\n const isPollingFallback = aampClient?.isUsingPollingFallback?.() ?? false\n const connectionLine = aampClient?.isConnected()\n ? (isPollingFallback ? '\uD83D\uDFE1 connected (polling fallback)' : '\u2705 connected')\n : '\u274C disconnected'\n\n return {\n text: [\n `**AAMP Plugin Status**`,\n `Host: ${cfg.aampHost || '(not configured)'}`,\n `Identity: ${agentEmail || '(not yet registered)'}`,\n `Connection: ${connectionLine}`,\n `Cached: ${loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath()) ? 'yes' : 'no'}`,\n lastConnectionError ? `Last error: ${lastConnectionError}` : '',\n lastDisconnectReason ? `Last disconnect: ${lastDisconnectReason}` : '',\n `Pending: ${pendingTasks.size} task(s)`,\n ...[...pendingTasks.values()].map(\n (t) => ` \u2022 ${t.taskId.slice(0, 8)}\u2026 \"${t.title}\" from ${t.from}`,\n ),\n ]\n .filter(Boolean)\n .join('\\n'),\n }\n },\n })\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @aamp/openclaw-plugin\n *\n * OpenClaw plugin that gives the agent an AAMP mailbox identity and lets it\n * receive, process, and reply to AAMP tasks \u2014 entirely through standard email.\n *\n * How it works:\n * 1. Plugin resolves or auto-registers an AAMP mailbox identity on startup.\n * 2. Credentials are cached to a local file so the same mailbox is reused\n * across gateway restarts (no re-registration needed).\n * 3. Background JMAP WebSocket Push receives incoming task.dispatch emails.\n * 4. Incoming tasks are stored in an in-memory pending-task queue.\n * 5. before_prompt_build injects the oldest pending task into the LLM's\n * system context so the agent sees it and acts without user prompting.\n * 6. The agent calls aamp_send_result or aamp_send_help to reply.\n *\n * OpenClaw config (openclaw.json):\n *\n * \"plugins\": {\n * \"entries\": {\n * \"aamp\": {\n * \"enabled\": true,\n * \"config\": {\n * \"aampHost\": \"https://meshmail.ai\",\n * \"slug\": \"openclaw-agent\",\n * \"credentialsFile\": \"/absolute/path/to/.aamp-credentials.json\"\n * }\n * }\n * }\n * }\n *\n * Install:\n * openclaw plugins install ./packages/openclaw-plugin\n */\n\nimport { AampClient } from 'aamp-sdk'\nimport type { TaskDispatch, TaskResult, TaskHelp, AampAttachment, ReceivedAttachment } from 'aamp-sdk'\nimport {\n defaultCredentialsPath,\n ensureDir,\n loadCachedIdentity,\n readBinaryFile,\n saveCachedIdentity,\n writeBinaryFile,\n type Identity,\n} from './file-store.js'\n\n// \u2500\u2500\u2500 Shared runtime state (single instance per plugin lifetime) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface PendingTask {\n taskId: string\n from: string\n title: string\n bodyText: string\n contextLinks: string[]\n timeoutSecs: number\n messageId: string\n receivedAt: string // ISO-8601\n}\n\ninterface PluginConfig {\n /** e.g. \"meshmail.ai\" \u2014 all URLs are derived from this */\n aampHost: string\n slug?: string\n /** Absolute path to cache AAMP credentials. Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json */\n credentialsFile?: string\n senderPolicies?: SenderPolicy[]\n}\n\ninterface SenderPolicy {\n sender: string\n dispatchContextRules?: Record<string, string[]>\n}\n\nfunction matchSenderPolicy(\n task: TaskDispatch,\n senderPolicies: SenderPolicy[] | undefined,\n): { allowed: boolean; reason?: string } {\n if (!senderPolicies?.length) return { allowed: true }\n\n const sender = task.from.toLowerCase()\n const policy = senderPolicies.find((item) => item.sender.trim().toLowerCase() === sender)\n if (!policy) {\n return { allowed: false, reason: `sender ${task.from} is not allowed by senderPolicies` }\n }\n\n const rules = policy.dispatchContextRules\n if (!rules || Object.keys(rules).length === 0) {\n return { allowed: true }\n }\n\n const context = task.dispatchContext ?? {}\n const effectiveRules = Object.entries(rules)\n .map(([key, allowedValues]) => [\n key,\n (allowedValues ?? []).map((value) => value.trim()).filter(Boolean),\n ] as const)\n .filter(([, allowedValues]) => allowedValues.length > 0)\n\n if (effectiveRules.length === 0) {\n return { allowed: true }\n }\n\n for (const [key, allowedValues] of effectiveRules) {\n const contextValue = context[key]\n if (!contextValue) {\n return { allowed: false, reason: `dispatchContext missing required key \"${key}\"` }\n }\n if (!allowedValues.includes(contextValue)) {\n return { allowed: false, reason: `dispatchContext ${key}=${contextValue} is not allowed` }\n }\n }\n\n return { allowed: true }\n}\n\ntype StructuredResultFieldInput = {\n fieldKey: string\n fieldTypeKey: string\n fieldAlias?: string\n value?: unknown\n index?: string\n attachmentFilenames?: string[]\n}\n\n/** Normalise aampHost to a base URL with scheme and no trailing slash */\nfunction baseUrl(aampHost: string): string {\n if (aampHost.startsWith('http://') || aampHost.startsWith('https://')) {\n return aampHost.replace(/\\/$/, '')\n }\n return `https://${aampHost}`\n}\n\nconst pendingTasks = new Map<string, PendingTask>()\n// Tracks sub-tasks dispatched TO other agents \u2014 waiting for their result/help replies\nconst dispatchedSubtasks = new Map<string, { to: string; title: string; dispatchedAt: string; parentTaskId?: string }>()\n// Tracks notification keys that have been shown to LLM (auto-cleaned on next prompt build)\nconst shownNotifications = new Set<string>()\n// Pending synchronous dispatch waiters \u2014 resolve callback keyed by sub-task ID.\n// When aamp_dispatch_task sends a sub-task, it parks a Promise here and waits.\n// When task.result/help arrives for that sub-task ID, the waiter is resolved\n// directly, keeping the LLM awake with full context (no heartbeat needed).\nconst waitingDispatches = new Map<string, (reply: { type: 'result' | 'help'; data: unknown }) => void>()\nlet aampClient: AampClient | null = null\nlet agentEmail = ''\nlet lastConnectionError = ''\nlet lastDisconnectReason = ''\nlet lastTransportMode: 'disconnected' | 'websocket' | 'polling' = 'disconnected'\nlet lastLoggedTransportMode: 'disconnected' | 'websocket' | 'polling' = 'disconnected'\nlet reconcileTimer: NodeJS.Timeout | null = null\nlet transportMonitorTimer: NodeJS.Timeout | null = null\n// Tracks the most recently seen session key so task.dispatch can wake the right session.\n// Default 'agent:main:main' is the standard OpenClaw single-agent session key.\nlet currentSessionKey = 'agent:main:main'\n// Channel runtime \u2014 captured from channel adapter's startAccount for instant dispatch.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet channelRuntime: any = null\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet channelCfg: any = null\n\nfunction logTransportState(\n api: { logger: { info: (msg: string) => void; warn: (msg: string) => void } },\n mode: 'websocket' | 'polling',\n email: string,\n previousMode: 'disconnected' | 'websocket' | 'polling',\n): void {\n if (mode === previousMode) return\n\n if (mode === 'polling') {\n api.logger.info(`[AAMP] Connected (polling fallback active) \u2014 listening as ${email}`)\n return\n }\n\n if (previousMode === 'polling') {\n api.logger.info(`[AAMP] WebSocket restored \u2014 listening as ${email}`)\n return\n }\n\n api.logger.info(`[AAMP] Connected \u2014 listening as ${email}`)\n}\n\n// \u2500\u2500\u2500 Identity helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface Identity {\n email: string\n jmapToken: string\n smtpPassword: string\n}\n\n/**\n * Register a new AAMP node via the management service.\n *\n * Always creates a NEW mailbox (always returns 201). The slug is just a\n * human-readable prefix; a random hex suffix makes the email unique.\n * Callers should only call this once and persist the returned credentials.\n */\nasync function registerNode(cfg: PluginConfig): Promise<Identity> {\n const slug = (cfg.slug ?? 'openclaw-agent')\n .toLowerCase()\n .replace(/[\\s_]+/g, '-')\n .replace(/[^a-z0-9-]/g, '')\n\n const base = baseUrl(cfg.aampHost)\n\n // Step 1: Self-register \u2192 get one-time registration code\n const res = await fetch(`${base}/api/nodes/self-register`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug, description: 'OpenClaw AAMP agent node' }),\n })\n\n if (!res.ok) {\n const err = (await res.json().catch(() => ({}))) as { error?: string }\n throw new Error(`AAMP registration failed (${res.status}): ${err.error ?? res.statusText}`)\n }\n\n const regData = (await res.json()) as {\n registrationCode: string\n email: string\n }\n\n // Step 2: Exchange registration code for credentials\n const credRes = await fetch(\n `${base}/api/nodes/credentials?code=${encodeURIComponent(regData.registrationCode)}`,\n )\n\n if (!credRes.ok) {\n const err = (await credRes.json().catch(() => ({}))) as { error?: string }\n throw new Error(`AAMP credential exchange failed (${credRes.status}): ${err.error ?? credRes.statusText}`)\n }\n\n const credData = (await credRes.json()) as {\n email: string\n jmap: { token: string }\n smtp: { password: string }\n }\n\n return {\n email: credData.email,\n jmapToken: credData.jmap.token,\n smtpPassword: credData.smtp.password,\n }\n}\n\n/**\n * Resolve this agent's identity:\n * 1. Return cached credentials from disk if available.\n * 2. Otherwise register a new node and cache the result.\n */\nasync function resolveIdentity(cfg: PluginConfig): Promise<Identity> {\n const cached = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath())\n if (cached) return cached\n\n const identity = await registerNode(cfg)\n saveCachedIdentity(identity, cfg.credentialsFile ?? defaultCredentialsPath())\n return identity\n}\n\n// \u2500\u2500\u2500 Plugin definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default {\n id: 'aamp-openclaw-plugin',\n name: 'AAMP Agent Mail Protocol',\n\n configSchema: {\n type: 'object',\n properties: {\n aampHost: {\n type: 'string',\n description: 'AAMP service host, e.g. https://meshmail.ai',\n },\n slug: {\n type: 'string',\n default: 'openclaw-agent',\n description: 'Agent name prefix used in the mailbox address',\n },\n credentialsFile: {\n type: 'string',\n description:\n 'Absolute path to cache AAMP credentials between gateway restarts. ' +\n 'Default: ~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json. ' +\n 'Delete this file to force re-registration with a new mailbox.',\n },\n senderPolicies: {\n type: 'array',\n description:\n 'Per-sender authorization policies. Each sender can optionally require specific ' +\n 'X-AAMP-Dispatch-Context key/value pairs before a task is accepted.',\n items: {\n type: 'object',\n required: ['sender'],\n properties: {\n sender: {\n type: 'string',\n description: 'Dispatch sender email address (case-insensitive exact match).',\n },\n dispatchContextRules: {\n type: 'object',\n description:\n 'Optional exact-match rules over X-AAMP-Dispatch-Context. ' +\n 'All listed keys must be present and their values must match one of the configured entries.',\n additionalProperties: {\n type: 'array',\n items: { type: 'string' },\n },\n },\n },\n },\n },\n },\n },\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n register(api: any) {\n // api.pluginConfig = the config block under plugins.entries[\"aamp-openclaw-plugin\"].config in openclaw.json\n // api.config = the full global OpenClaw config (NOT our plugin's config)\n const cfg = (api.pluginConfig ?? {}) as PluginConfig\n\n // \u2500\u2500 Register lightweight channel adapter to capture channelRuntime \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // We register as a channel SOLELY to get access to channelRuntime, which provides\n // dispatchReplyWithBufferedBlockDispatcher for instant LLM dispatch (bypassing\n // heartbeat's global running-mutex + requests-in-flight ~60s delay).\n // JMAP connection is managed by registerService, NOT startAccount.\n api.registerChannel({\n id: 'aamp',\n meta: { label: 'AAMP' },\n capabilities: { chatTypes: ['dm'] },\n config: {\n listAccountIds: () => cfg.aampHost ? ['default'] : [],\n resolveAccount: () => ({ aampHost: cfg.aampHost }),\n isEnabled: () => !!cfg.aampHost,\n isConfigured: () => !!loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath()),\n },\n gateway: {\n startAccount: async (ctx: { channelRuntime?: unknown; cfg?: unknown; abortSignal?: AbortSignal }) => {\n // Capture channelRuntime for use by sub-task notification dispatch\n channelRuntime = ctx.channelRuntime ?? null\n channelCfg = ctx.cfg ?? null\n api.logger.info(`[AAMP] Channel adapter started \u2014 channelRuntime ${channelRuntime ? 'available' : 'NOT available'}`)\n\n // Keep alive until abort \u2014 JMAP connection is managed by registerService\n await new Promise<void>((resolve) => {\n ctx.abortSignal?.addEventListener('abort', () => resolve())\n })\n channelRuntime = null\n channelCfg = null\n },\n stopAccount: async () => {\n channelRuntime = null\n channelCfg = null\n },\n },\n })\n\n function triggerHeartbeatWake(sessionKey: string, label: string): void {\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey })\n api.logger.info(`[AAMP] Heartbeat triggered for ${label} via session ${sessionKey}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Could not trigger heartbeat for ${label}: ${(err as Error).message}`)\n }\n }\n\n function wakeAgentForPendingTask(task: PendingTask): void {\n const fallback = () => triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`)\n const dispatcher = channelRuntime?.reply?.dispatchReplyWithBufferedBlockDispatcher\n\n api.logger.info(\n `[AAMP] Wake requested for task ${task.taskId} \u2014 channelRuntime=${channelRuntime ? 'yes' : 'no'} channelCfg=${channelCfg ? 'yes' : 'no'} dispatcher=${typeof dispatcher === 'function' ? 'yes' : 'no'} session=${currentSessionKey}`,\n )\n\n if (!channelRuntime || !channelCfg || typeof dispatcher !== 'function') {\n fallback()\n return\n }\n\n const prompt = [\n '## New AAMP Task',\n '',\n 'A new AAMP task just arrived.',\n 'Use the pending AAMP task in system context as the source of truth and handle it now.',\n 'Reply with aamp_send_result or aamp_send_help before responding.',\n ].join('\\n')\n\n try {\n void Promise.resolve(dispatcher({\n ctx: {\n Body: task.bodyText || task.title,\n BodyForAgent: prompt,\n From: task.from,\n To: agentEmail,\n SessionKey: `aamp:default:task:${task.taskId}`,\n AccountId: 'default',\n ChatType: 'dm',\n Provider: 'aamp',\n Surface: 'aamp',\n OriginatingChannel: 'aamp',\n OriginatingTo: task.from,\n MessageSid: task.messageId || task.taskId,\n Timestamp: Date.now(),\n SenderName: task.from,\n SenderId: task.from,\n CommandAuthorized: true,\n },\n cfg: channelCfg,\n dispatcherOptions: {\n deliver: async () => {},\n onError: (err: unknown) => {\n api.logger.error(`[AAMP] Channel dispatch error for task ${task.taskId}: ${err instanceof Error ? err.message : String(err)}`)\n },\n },\n })).then(() => {\n api.logger.info(`[AAMP] Channel dispatch triggered for task ${task.taskId}`)\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Channel dispatch failed for task ${task.taskId}: ${err.message}`)\n fallback()\n })\n } catch (err) {\n api.logger.error(`[AAMP] Channel dispatch threw synchronously for task ${task.taskId}: ${(err as Error).message}`)\n fallback()\n }\n }\n\n // \u2500\u2500 Shared connect logic (used by service auto-connect and startup recovery) \u2500\u2500\u2500\u2500\u2500\u2500\n async function doConnect(identity: { email: string; jmapToken: string; smtpPassword: string }) {\n if (reconcileTimer) {\n clearInterval(reconcileTimer)\n reconcileTimer = null\n }\n if (transportMonitorTimer) {\n clearInterval(transportMonitorTimer)\n transportMonitorTimer = null\n }\n\n agentEmail = identity.email\n lastConnectionError = ''\n lastDisconnectReason = ''\n lastTransportMode = 'disconnected'\n lastLoggedTransportMode = 'disconnected'\n api.logger.info(`[AAMP] Mailbox identity ready \u2014 ${agentEmail}`)\n\n // All traffic goes through aampHost (port 3000).\n // The management service proxies /jmap/* and /.well-known/jmap \u2192 Stalwart:8080.\n const base = baseUrl(cfg.aampHost)\n\n aampClient = new AampClient({\n email: identity.email,\n jmapToken: identity.jmapToken,\n jmapUrl: base,\n smtpHost: new URL(base).hostname,\n smtpPort: 587,\n smtpPassword: identity.smtpPassword,\n // Local/dev: management-service proxy uses plain HTTP, no TLS cert to verify.\n // Production: set to true when using wss:// with valid certs.\n rejectUnauthorized: false,\n })\n\n aampClient.on('task.dispatch', (task: TaskDispatch) => {\n api.logger.info(`[AAMP] \u2190 task.dispatch ${task.taskId} \"${task.title}\" from=${task.from}`)\n\n try {\n // \u2500\u2500 Sender policy / dispatch-context authorization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const decision = matchSenderPolicy(task, cfg.senderPolicies)\n if (!decision.allowed) {\n api.logger.warn(`[AAMP] \u2717 rejected by senderPolicies: ${task.from} task=${task.taskId} reason=${decision.reason}`)\n void aampClient!.sendResult({\n to: task.from,\n taskId: task.taskId,\n status: 'rejected',\n output: '',\n errorMsg: decision.reason ?? `Sender ${task.from} is not allowed.`,\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Failed to send rejection for task ${task.taskId}: ${err.message}`)\n })\n return\n }\n\n pendingTasks.set(task.taskId, {\n taskId: task.taskId,\n from: task.from,\n title: task.title,\n bodyText: task.bodyText ?? '',\n contextLinks: task.contextLinks,\n timeoutSecs: task.timeoutSecs,\n messageId: task.messageId ?? '',\n receivedAt: new Date().toISOString(),\n })\n\n // Wake the agent immediately after enqueueing the task.\n // In polling fallback mode heartbeat wakes can be delayed, so prefer a direct\n // channel dispatch when channelRuntime is available and only fall back to heartbeat.\n wakeAgentForPendingTask(pendingTasks.get(task.taskId)!)\n } catch (err) {\n api.logger.error(`[AAMP] task.dispatch handler failed for ${task.taskId}: ${(err as Error).message}`)\n if (pendingTasks.has(task.taskId)) {\n triggerHeartbeatWake(currentSessionKey, `task ${task.taskId}`)\n }\n }\n })\n\n // \u2500\u2500 Sub-task result: another agent completed a task we dispatched \u2500\u2500\u2500\u2500\u2500\u2500\n aampClient.on('task.result', (result: TaskResult) => {\n api.logger.info(`[AAMP] \u2190 task.result ${result.taskId} status=${result.status} from=${result.from}`)\n\n const sub = dispatchedSubtasks.get(result.taskId)\n dispatchedSubtasks.delete(result.taskId)\n\n // \u2500\u2500 Synchronous dispatch: if aamp_dispatch_task is waiting, resolve it directly \u2500\u2500\n const waiter = waitingDispatches.get(result.taskId)\n if (waiter) {\n waitingDispatches.delete(result.taskId)\n api.logger.info(`[AAMP] Resolving sync waiter for sub-task ${result.taskId}`)\n waiter({ type: 'result', data: result })\n return // Don't go through heartbeat/channel \u2014 the LLM is already awake\n }\n\n // Pre-download attachments to local disk so the LLM can reference them\n // by local file path (instead of requiring a separate download tool call).\n const downloadedFiles: Array<{ filename: string; path: string; size: number }> = []\n const downloadPromise = (async () => {\n if (!result.attachments?.length) return\n const dir = '/tmp/aamp-files'\n ensureDir(dir)\n for (const att of result.attachments) {\n try {\n const buffer = await aampClient!.downloadBlob(att.blobId, att.filename)\n const filepath = `${dir}/${att.filename}`\n writeBinaryFile(filepath, buffer)\n downloadedFiles.push({ filename: att.filename, path: filepath, size: buffer.length })\n api.logger.info(`[AAMP] Pre-downloaded: ${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`)\n } catch (dlErr) {\n api.logger.warn(`[AAMP] Pre-download failed for ${att.filename}: ${(dlErr as Error).message}`)\n }\n }\n })()\n\n downloadPromise.then(() => {\n // Build notification with pre-downloaded file paths\n const MAX_OUTPUT_CHARS = 800\n const label = result.status === 'completed' ? 'Sub-task completed' : 'Sub-task rejected'\n const rawOutput = result.output ?? ''\n const truncatedOutput = rawOutput.length > MAX_OUTPUT_CHARS\n ? rawOutput.slice(0, MAX_OUTPUT_CHARS) + `\\n\\n... [truncated, ${rawOutput.length} chars total]`\n : rawOutput\n\n let attachmentInfo = ''\n if (downloadedFiles.length > 0) {\n attachmentInfo = `\\n\\nAttachments (pre-downloaded to local disk):\\n${downloadedFiles.map(f =>\n `- ${f.filename} (${(f.size / 1024).toFixed(1)} KB) \u2192 ${f.path}`\n ).join('\\n')}\\nUse aamp_send_result with attachments: [${downloadedFiles.map(f => `{ filename: \"${f.filename}\", path: \"${f.path}\" }`).join(', ')}] to forward them.`\n } else if (result.attachments?.length) {\n const files = result.attachments.map((a: ReceivedAttachment) =>\n `${a.filename} (${(a.size / 1024).toFixed(1)} KB, blobId: ${a.blobId})`,\n )\n attachmentInfo = `\\n\\nAttachments (download failed \u2014 use aamp_download_attachment manually):\\n${files.join('\\n')}`\n }\n\n pendingTasks.set(`result:${result.taskId}`, {\n taskId: result.taskId,\n from: result.from,\n title: `${label}: ${sub?.title ?? result.taskId}`,\n bodyText: result.status === 'completed'\n ? `Agent ${result.from} completed the sub-task.\\n\\nOutput:\\n${truncatedOutput}${attachmentInfo}`\n : `Agent ${result.from} rejected the sub-task.\\n\\nReason: ${result.errorMsg ?? 'unknown'}`,\n contextLinks: [],\n timeoutSecs: 0,\n messageId: '',\n receivedAt: new Date().toISOString(),\n })\n\n // Wake LLM via channel dispatch (instant) or heartbeat (fallback)\n if (channelRuntime && channelCfg) {\n const notifyBody = pendingTasks.get(`result:${result.taskId}`)\n const actionableTasks = [...pendingTasks.entries()]\n .filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n .map(([, t]) => t)\n const actionSection = actionableTasks.length > 0\n ? `\\n\\n### Action Required\\nYou MUST call aamp_send_result to complete the pending task(s):\\n${actionableTasks.map(t => `- Task ID: ${t.taskId} | From: ${t.from} | Title: \"${t.title}\"`).join('\\n')}`\n : ''\n const prompt = `## Sub-task Update\\n\\n${notifyBody?.bodyText ?? 'Sub-task completed.'}${actionSection}`\n\n channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: {\n Body: `Sub-task result: ${result.taskId}`,\n BodyForAgent: prompt,\n From: result.from,\n To: agentEmail,\n SessionKey: `aamp:default:${result.from}`,\n AccountId: 'default',\n ChatType: 'dm',\n Provider: 'aamp',\n Surface: 'aamp',\n OriginatingChannel: 'aamp',\n OriginatingTo: result.from,\n MessageSid: result.taskId,\n Timestamp: Date.now(),\n SenderName: result.from,\n SenderId: result.from,\n CommandAuthorized: true,\n },\n cfg: channelCfg,\n dispatcherOptions: {\n deliver: async () => {},\n onError: (err: unknown) => {\n api.logger.error(`[AAMP] Channel dispatch error: ${err instanceof Error ? err.message : String(err)}`)\n },\n },\n }).then(() => {\n api.logger.info(`[AAMP] Channel dispatch completed for sub-task result ${result.taskId}`)\n pendingTasks.delete(`result:${result.taskId}`)\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Channel dispatch failed: ${err.message}`)\n })\n } else {\n const notifySessionKey = `agent:main:aamp-notify-${Date.now()}`\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: notifySessionKey })\n api.logger.info(`[AAMP] Heartbeat for sub-task result ${result.taskId}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Heartbeat for sub-task result failed: ${(err as Error).message}`)\n }\n }\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Sub-task result processing failed: ${err.message}`)\n })\n })\n\n // \u2500\u2500 Sub-task help: another agent asks for clarification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n aampClient.on('task.help', (help: TaskHelp) => {\n api.logger.info(`[AAMP] \u2190 task.help ${help.taskId} question=\"${help.question}\" from=${help.from}`)\n\n // \u2500\u2500 Synchronous dispatch: if aamp_dispatch_task is waiting, resolve it directly \u2500\u2500\n const waiter = waitingDispatches.get(help.taskId)\n if (waiter) {\n waitingDispatches.delete(help.taskId)\n api.logger.info(`[AAMP] Resolving sync waiter for sub-task help ${help.taskId}`)\n waiter({ type: 'help', data: help })\n return\n }\n\n const sub = dispatchedSubtasks.get(help.taskId)\n\n pendingTasks.set(`help:${help.taskId}`, {\n taskId: help.taskId,\n from: help.from,\n title: `Sub-task needs help: ${sub?.title ?? help.taskId}`,\n bodyText: `Agent ${help.from} is asking for help on the sub-task.\\n\\nQuestion: ${help.question}\\nBlocked reason: ${help.blockedReason}${help.suggestedOptions?.length ? `\\nSuggested options: ${help.suggestedOptions.join(', ')}` : ''}`,\n contextLinks: [],\n timeoutSecs: 0,\n messageId: '',\n receivedAt: new Date().toISOString(),\n })\n\n if (channelRuntime && channelCfg) {\n const notifyBody = pendingTasks.get(`help:${help.taskId}`)\n const prompt = `## Sub-task Help Request\\n\\n${notifyBody?.bodyText ?? help.question}`\n\n channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: {\n Body: `Sub-task help: ${help.taskId}`,\n BodyForAgent: prompt,\n From: help.from,\n To: agentEmail,\n SessionKey: `aamp:default:${help.from}`,\n AccountId: 'default',\n ChatType: 'dm',\n Provider: 'aamp',\n Surface: 'aamp',\n OriginatingChannel: 'aamp',\n OriginatingTo: help.from,\n MessageSid: help.taskId,\n Timestamp: Date.now(),\n SenderName: help.from,\n SenderId: help.from,\n CommandAuthorized: true,\n },\n cfg: channelCfg,\n dispatcherOptions: {\n deliver: async () => {},\n onError: (err: unknown) => {\n api.logger.error(`[AAMP] Channel dispatch error (help): ${err instanceof Error ? err.message : String(err)}`)\n },\n },\n }).then(() => {\n pendingTasks.delete(`help:${help.taskId}`)\n }).catch((err: Error) => {\n api.logger.error(`[AAMP] Channel dispatch failed for help: ${err.message}`)\n })\n } else {\n const helpSessionKey = `agent:main:aamp-notify-${Date.now()}`\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: helpSessionKey })\n api.logger.info(`[AAMP] Heartbeat fallback for sub-task help ${help.taskId}`)\n } catch (err) {\n api.logger.warn(`[AAMP] Heartbeat for sub-task help failed: ${(err as Error).message}`)\n }\n }\n })\n\n aampClient.on('connected', () => {\n lastConnectionError = ''\n lastDisconnectReason = ''\n const mode = aampClient?.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n })\n\n aampClient.on('disconnected', (reason: string) => {\n lastDisconnectReason = reason\n if (lastTransportMode !== 'disconnected') {\n api.logger.warn(`[AAMP] Disconnected: ${reason} (will auto-reconnect)`)\n lastTransportMode = 'disconnected'\n lastLoggedTransportMode = 'disconnected'\n }\n })\n\n aampClient.on('error', (err: Error) => {\n lastConnectionError = err.message\n if (err.message.startsWith('JMAP WebSocket unavailable, falling back to polling:')) {\n if (lastTransportMode !== 'polling') {\n logTransportState(api, 'polling', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'polling'\n lastLoggedTransportMode = 'polling'\n }\n return\n }\n api.logger.error(`[AAMP] ${err.message}`)\n })\n\n await aampClient.connect()\n\n api.logger.info(\n `[AAMP] Transport after connect \u2014 ${aampClient.isUsingPollingFallback() ? 'polling fallback' : 'websocket'} as ${agentEmail}`,\n )\n\n if (aampClient.isConnected() && lastTransportMode === 'disconnected') {\n if (aampClient.isUsingPollingFallback()) {\n logTransportState(api, 'polling', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'polling'\n lastLoggedTransportMode = 'polling'\n } else {\n logTransportState(api, 'websocket', agentEmail, lastLoggedTransportMode)\n lastTransportMode = 'websocket'\n lastLoggedTransportMode = 'websocket'\n }\n }\n\n setTimeout(() => {\n if (!aampClient?.isConnected()) return\n const mode = aampClient.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n }, 1000)\n\n transportMonitorTimer = setInterval(() => {\n if (!aampClient) return\n if (!aampClient.isConnected()) {\n if (lastTransportMode !== 'disconnected') {\n lastTransportMode = 'disconnected'\n }\n return\n }\n const mode = aampClient.isUsingPollingFallback() ? 'polling' : 'websocket'\n logTransportState(api, mode, agentEmail, lastLoggedTransportMode)\n lastTransportMode = mode\n lastLoggedTransportMode = mode\n }, 5000)\n\n reconcileTimer = setInterval(() => {\n if (!aampClient) return\n void aampClient.reconcileRecentEmails(20).catch((err: Error) => {\n lastConnectionError = err.message\n api.logger.warn(`[AAMP] Mailbox reconcile failed: ${err.message}`)\n })\n }, 15000)\n }\n\n // \u2500\u2500 Service: auto-connect at gateway startup, disconnect on shutdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // registerService causes this plugin to load eagerly (at gateway startup),\n // not lazily (on first agent run). start() is called once the gateway is up.\n api.registerService({\n id: 'aamp-service',\n start: async () => {\n if (!cfg.aampHost) {\n api.logger.info('[AAMP] aampHost not configured \u2014 skipping auto-connect')\n return\n }\n try {\n const identity = await resolveIdentity(cfg)\n await doConnect(identity)\n } catch (err) {\n api.logger.warn(`[AAMP] Service auto-connect failed: ${(err as Error).message}`)\n }\n },\n stop: () => {\n if (reconcileTimer) {\n clearInterval(reconcileTimer)\n reconcileTimer = null\n }\n if (transportMonitorTimer) {\n clearInterval(transportMonitorTimer)\n transportMonitorTimer = null\n }\n if (aampClient) {\n try {\n aampClient.disconnect()\n api.logger.info('[AAMP] Disconnected on gateway stop')\n } catch {\n // ignore disconnect errors on shutdown\n }\n }\n },\n })\n\n // \u2500\u2500 gateway_start hook: re-trigger heartbeat after runner is initialized \u2500\u2500\u2500\n // Service start() runs BEFORE the heartbeat runner is ready, so\n // requestHeartbeatNow() called during JMAP initial fetch is silently dropped\n // (handler == null). gateway_start fires AFTER the heartbeat runner starts,\n // so we re-trigger here to process any tasks queued during startup.\n api.on('gateway_start', () => {\n if (pendingTasks.size === 0) return\n api.logger.info(`[AAMP] gateway_start: re-triggering heartbeat for ${pendingTasks.size} pending task(s)`)\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: currentSessionKey })\n } catch (err) {\n api.logger.warn(`[AAMP] gateway_start heartbeat failed: ${(err as Error).message}`)\n }\n })\n\n // \u2500\u2500 2. Prompt injection: surface the oldest pending task to the LLM \u2500\u2500\u2500\u2500\u2500\u2500\n api.on(\n 'before_prompt_build',\n (_event, ctx) => {\n // Keep currentSessionKey fresh \u2014 used by task.dispatch to target the right session.\n // Skip channel dispatch sessions (aamp:*) to avoid polluting the heartbeat session key.\n if (ctx?.sessionKey && !String(ctx.sessionKey).startsWith('aamp:')) {\n currentSessionKey = ctx.sessionKey\n }\n\n // Expire tasks that have exceeded their timeout\n const now = Date.now()\n for (const [id, t] of pendingTasks) {\n if (t.timeoutSecs && now - new Date(t.receivedAt).getTime() > t.timeoutSecs * 1000) {\n api.logger.warn(`[AAMP] Task ${id} timed out \u2014 removing from queue`)\n pendingTasks.delete(id)\n }\n }\n\n if (pendingTasks.size === 0) return {}\n\n // Prioritize notifications (sub-task results/help) over actionable tasks.\n // Without this, the oldest actionable task blocks notification delivery,\n // preventing the LLM from seeing sub-task results and completing the parent task.\n const allEntries = [...pendingTasks.entries()]\n const notifications = allEntries.filter(([key]) => key.startsWith('result:') || key.startsWith('help:'))\n const actionable = allEntries.filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n\n // Pick notification first if available, otherwise oldest actionable task\n const [taskKey, task] = notifications.length > 0\n ? notifications.sort((a, b) => new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime())[0]\n : actionable.sort((a, b) => new Date(a[1].receivedAt).getTime() - new Date(b[1].receivedAt).getTime())[0]\n\n const isNotification = taskKey.startsWith('result:') || taskKey.startsWith('help:')\n\n // Notifications are one-shot: remove immediately after injecting into prompt\n if (isNotification && taskKey) {\n pendingTasks.delete(taskKey)\n }\n\n // Find remaining actionable tasks (non-notification) that still need a response\n const actionableTasks = [...pendingTasks.entries()]\n .filter(([key]) => !key.startsWith('result:') && !key.startsWith('help:'))\n .map(([, t]) => t)\n\n const hasAttachmentInfo = isNotification && (task.bodyText?.includes('aamp_download_attachment') ?? false)\n const actionRequiredSection = isNotification && actionableTasks.length > 0\n ? [\n ``,\n `### Action Required`,\n ``,\n `You still have ${actionableTasks.length} pending task(s) that need a response.`,\n `Use the sub-task result above to complete them by calling aamp_send_result.`,\n ``,\n ...actionableTasks.map((t) =>\n `- Task ID: ${t.taskId} | From: ${t.from} | Title: \"${t.title}\"`\n ),\n ...(hasAttachmentInfo ? [\n ``,\n `### Forwarding Attachments`,\n `The sub-task result includes file attachments. To forward them:`,\n `1. Call aamp_download_attachment for each blobId listed above`,\n `2. Include the downloaded files in aamp_send_result via the attachments parameter`,\n ` Example: attachments: [{ filename: \"file.html\", path: \"/tmp/aamp-files/file.html\" }]`,\n ] : []),\n ].join('\\n')\n : ''\n\n const lines = isNotification ? [\n `## Sub-task Update`,\n ``,\n `A sub-task you dispatched has returned a result. Review the information below.`,\n `If the sub-task included attachments, use aamp_download_attachment to fetch them.`,\n ``,\n `Task ID: ${task.taskId}`,\n `From: ${task.from}`,\n `Title: ${task.title}`,\n task.bodyText ? `\\n${task.bodyText}` : '',\n actionRequiredSection,\n pendingTasks.size > 1 ? `\\n(+${pendingTasks.size - 1} more items queued)` : '',\n ] : [\n `## Pending AAMP Task (action required)`,\n ``,\n `You have received a task via AAMP email. You MUST call one of the two tools below`,\n `BEFORE responding to the user \u2014 do not skip this step.`,\n ``,\n `### Tool selection rules (follow strictly):`,\n ``,\n `Use aamp_send_result ONLY when ALL of the following are true:`,\n ` 1. The title contains a clear, specific action verb (e.g. \"summarise\", \"review\",`,\n ` \"translate\", \"generate\", \"fix\", \"search\", \"compare\", \"list\")`,\n ` 2. You know exactly what input/resource to act on`,\n ` 3. No ambiguity remains \u2014 you could start work immediately without asking anything`,\n ``,\n `Use aamp_send_help in ALL other cases, including:`,\n ` - Title is a greeting or salutation (\"hello\", \"hi\", \"hey\", \"test\", \"ping\", etc.)`,\n ` - Title is fewer than 4 words and contains no actionable verb`,\n ` - Title is too vague to act on without guessing (e.g. \"help\", \"task\", \"question\")`,\n ` - Required context is missing (which file? which URL? which criteria?)`,\n ` - Multiple interpretations are equally plausible`,\n ``,\n `IMPORTANT: Responding to a greeting with a greeting is WRONG. \"hello\" is not a`,\n `valid task description \u2014 ask what specific task the dispatcher needs done.`,\n ``,\n `### Sub-task dispatch rules:`,\n `If you delegate work to another agent via aamp_dispatch_task, you MUST pass`,\n `parentTaskId: \"${task.taskId}\" to establish the parent-child relationship.`,\n ``,\n `Task ID: ${task.taskId}`,\n `From: ${task.from}`,\n `Title: ${task.title}`,\n task.bodyText ? `Description:\\n${task.bodyText}` : '',\n task.contextLinks.length\n ? `Context Links:\\n${task.contextLinks.map((l) => ` - ${l}`).join('\\n')}`\n : '',\n task.timeoutSecs ? `Deadline: ${task.timeoutSecs}s from dispatch` : `Deadline: none`,\n `Received: ${task.receivedAt}`,\n pendingTasks.size > 1 ? `\\n(+${pendingTasks.size - 1} more tasks queued)` : '',\n ]\n .filter(Boolean)\n .join('\\n')\n\n return { prependContext: lines }\n },\n { priority: 5 },\n )\n\n // \u2500\u2500 3. Tool: send task result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_send_result',\n description:\n 'Send the result of an AAMP task back to the dispatcher. ' +\n 'Call this after you have finished processing the task.',\n parameters: {\n type: 'object',\n required: ['taskId', 'status', 'output'],\n properties: {\n taskId: {\n type: 'string',\n description: 'The AAMP task ID to reply to (from the system context)',\n },\n status: {\n type: 'string',\n enum: ['completed', 'rejected'],\n description: '\"completed\" on success, \"rejected\" if the task cannot be done',\n },\n output: {\n type: 'string',\n description: 'Your result or explanation',\n },\n errorMsg: {\n type: 'string',\n description: 'Optional error details (use only when status = rejected)',\n },\n attachments: {\n type: 'array',\n description: 'File attachments. Each item: { filename, contentType, path (local file path) }',\n items: {\n type: 'object',\n properties: {\n filename: { type: 'string' },\n contentType: { type: 'string' },\n path: { type: 'string', description: 'Absolute path to the file on disk' },\n },\n required: ['filename', 'path'],\n },\n },\n structuredResult: {\n type: 'array',\n description: 'Optional structured Meego field values.',\n items: {\n type: 'object',\n required: ['fieldKey', 'fieldTypeKey'],\n properties: {\n fieldKey: { type: 'string' },\n fieldTypeKey: { type: 'string' },\n fieldAlias: { type: 'string' },\n value: {\n description: 'Field value in the exact format required by Meego for this field type.',\n },\n index: { type: 'string' },\n attachmentFilenames: {\n type: 'array',\n items: { type: 'string' },\n description: 'For attachment fields, filenames from attachments[] that should be uploaded into this field.',\n },\n },\n },\n },\n },\n },\n execute: async (_id, params) => {\n const p = params as {\n taskId: string\n status: 'completed' | 'rejected'\n output: string\n errorMsg?: string\n attachments?: Array<{ filename: string; contentType?: string; path: string }>\n structuredResult?: StructuredResultFieldInput[]\n }\n\n const task = pendingTasks.get(p.taskId)\n if (!task) {\n return {\n content: [{ type: 'text', text: `Error: task ${p.taskId} not found in pending queue.` }],\n }\n }\n\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n api.logger.info(`[AAMP] aamp_send_result params ${JSON.stringify({\n taskId: p.taskId,\n status: p.status,\n output: p.output,\n errorMsg: p.errorMsg,\n attachments: p.attachments?.map((a) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n path: a.path,\n })) ?? [],\n structuredResult: p.structuredResult?.map((field) => ({\n fieldKey: field.fieldKey,\n fieldTypeKey: field.fieldTypeKey,\n fieldAlias: field.fieldAlias,\n value: field.value,\n index: field.index,\n attachmentFilenames: field.attachmentFilenames ?? [],\n })) ?? [],\n })}`)\n\n // Build attachments from file paths\n let attachments: AampAttachment[] | undefined\n if (p.attachments?.length) {\n attachments = p.attachments.map((a: { filename: string; contentType?: string; path: string }) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n content: readBinaryFile(a.path),\n }))\n }\n\n await aampClient.sendResult({\n to: task.from,\n taskId: task.taskId,\n status: p.status,\n output: p.output,\n errorMsg: p.errorMsg,\n structuredResult: p.structuredResult?.length ? p.structuredResult : undefined,\n inReplyTo: task.messageId || undefined,\n attachments,\n })\n\n pendingTasks.delete(task.taskId)\n api.logger.info(`[AAMP] \u2192 task.result ${task.taskId} ${p.status}`)\n\n // If more tasks remain, wake the agent to process them\n if (pendingTasks.size > 0) {\n try {\n api.runtime.system.requestHeartbeatNow({ reason: 'wake', sessionKey: currentSessionKey })\n } catch { /* ignore */ }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Result sent for task ${task.taskId} (status: ${p.status}).`,\n },\n ],\n }\n },\n }, { name: 'aamp_send_result' })\n\n // \u2500\u2500 4. Tool: ask for help \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_send_help',\n description:\n 'Send a help request for an AAMP task when you are blocked or need human clarification ' +\n 'before you can proceed.',\n parameters: {\n type: 'object',\n required: ['taskId', 'question', 'blockedReason'],\n properties: {\n taskId: {\n type: 'string',\n description: 'The AAMP task ID',\n },\n question: {\n type: 'string',\n description: 'Your question for the human dispatcher',\n },\n blockedReason: {\n type: 'string',\n description: 'Why you cannot proceed without their input',\n },\n suggestedOptions: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional list of choices for the dispatcher to pick from',\n },\n },\n },\n execute: async (_id, params) => {\n const p = params as {\n taskId: string\n question: string\n blockedReason: string\n suggestedOptions?: string[]\n }\n\n const task = pendingTasks.get(p.taskId)\n if (!task) {\n return {\n content: [{ type: 'text', text: `Error: task ${p.taskId} not found in pending queue.` }],\n }\n }\n\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n await aampClient.sendHelp({\n to: task.from,\n taskId: task.taskId,\n question: p.question,\n blockedReason: p.blockedReason,\n suggestedOptions: p.suggestedOptions ?? [],\n inReplyTo: task.messageId || undefined,\n })\n\n api.logger.info(`[AAMP] \u2192 task.help ${task.taskId}`)\n\n // Keep the task in pending \u2014 the help reply may arrive later\n return {\n content: [\n {\n type: 'text',\n text: `Help request sent for task ${task.taskId}. The task remains pending until the dispatcher replies.`,\n },\n ],\n }\n },\n }, { name: 'aamp_send_help' })\n\n // \u2500\u2500 5. Tool: inspect queue \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_pending_tasks',\n description: 'List all AAMP tasks currently waiting to be processed.',\n parameters: { type: 'object', properties: {} },\n execute: async () => {\n if (pendingTasks.size === 0) {\n return { content: [{ type: 'text', text: 'No pending AAMP tasks.' }] }\n }\n\n const lines = [...pendingTasks.values()]\n .sort((a, b) => new Date(a.receivedAt).getTime() - new Date(b.receivedAt).getTime())\n .map(\n (t, i) =>\n `${i + 1}. [${t.taskId}] \"${t.title}\"${t.bodyText ? `\\n Description: ${t.bodyText}` : ''} \u2014 from ${t.from} (received ${t.receivedAt})`,\n )\n\n return {\n content: [\n {\n type: 'text',\n text: `${pendingTasks.size} pending task(s):\\n${lines.join('\\n')}`,\n },\n ],\n }\n },\n }, { name: 'aamp_pending_tasks' })\n\n // \u2500\u2500 6. Tool: dispatch task to another agent (SYNCHRONOUS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Sends the task and BLOCKS until the sub-agent replies with task.result or\n // task.help. The reply is returned directly as the tool result, keeping the\n // LLM awake with full context \u2014 no heartbeat/channel dispatch needed.\n api.registerTool({\n name: 'aamp_dispatch_task',\n description:\n 'Send a task to another AAMP agent and WAIT for the result. ' +\n 'This tool blocks until the sub-agent replies (typically 5-60s). ' +\n 'The sub-agent\\'s output and any attachment file paths are returned directly.',\n parameters: {\n type: 'object',\n required: ['to', 'title'],\n properties: {\n to: { type: 'string', description: 'Target agent AAMP email address' },\n title: { type: 'string', description: 'Task title (concise summary)' },\n bodyText: { type: 'string', description: 'Detailed task description' },\n parentTaskId: { type: 'string', description: 'If you are processing a pending AAMP task, pass its Task ID here to establish parent-child nesting. Omit for top-level tasks.' },\n timeoutSecs: { type: 'number', description: 'Timeout in seconds (optional)' },\n contextLinks: {\n type: 'array', items: { type: 'string' },\n description: 'URLs providing context (optional)',\n },\n attachments: {\n type: 'array',\n description: 'File attachments. Each item: { filename, contentType, path (local file path) }',\n items: {\n type: 'object',\n properties: {\n filename: { type: 'string' },\n contentType: { type: 'string' },\n path: { type: 'string', description: 'Absolute path to the file on disk' },\n },\n required: ['filename', 'path'],\n },\n },\n },\n },\n execute: async (_id: unknown, params: {\n to: string; title: string; bodyText?: string;\n parentTaskId?: string; timeoutSecs?: number; contextLinks?: string[];\n attachments?: Array<{ filename: string; contentType?: string; path: string }>\n }) => {\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n try {\n // Build attachments from file paths\n let attachments: AampAttachment[] | undefined\n if (params.attachments?.length) {\n attachments = params.attachments.map((a: { filename: string; contentType?: string; path: string }) => ({\n filename: a.filename,\n contentType: a.contentType ?? 'application/octet-stream',\n content: readBinaryFile(a.path),\n }))\n }\n\n const result = await aampClient.sendTask({\n to: params.to,\n title: params.title,\n parentTaskId: params.parentTaskId,\n timeoutSecs: params.timeoutSecs,\n contextLinks: params.contextLinks,\n attachments,\n })\n\n // Track as dispatched sub-task\n dispatchedSubtasks.set(result.taskId, {\n to: params.to,\n title: params.title,\n dispatchedAt: new Date().toISOString(),\n parentTaskId: params.parentTaskId,\n })\n\n api.logger.info(`[AAMP] \u2192 task.dispatch ${result.taskId} to=${params.to} parent=${params.parentTaskId ?? 'none'} (waiting for reply\u2026)`)\n\n // \u2500\u2500 SYNCHRONOUS WAIT: block until sub-agent replies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const timeoutMs = (params.timeoutSecs ?? 300) * 1000\n const reply = await new Promise<{ type: 'result' | 'help'; data: unknown }>((resolve, reject) => {\n waitingDispatches.set(result.taskId, resolve)\n setTimeout(() => {\n if (waitingDispatches.delete(result.taskId)) {\n reject(new Error(`Sub-task ${result.taskId} timed out after ${params.timeoutSecs ?? 300}s`))\n }\n }, timeoutMs)\n })\n\n api.logger.info(`[AAMP] \u2190 sync reply for ${result.taskId}: type=${reply.type} attachments=${JSON.stringify((reply.data as any)?.attachments?.length ?? 0)}`)\n\n if (reply.type === 'result') {\n const r = reply.data as TaskResult\n\n // Pre-download attachments \u2014 use direct JMAP blob download (bypass SDK's downloadBlob\n // which was returning 404 due to URL construction issues in the esbuild bundle).\n let attachmentLines = ''\n if (r.attachments?.length) {\n api.logger.info(`[AAMP] Downloading ${r.attachments.length} attachment(s) from sync reply...`)\n const dir = '/tmp/aamp-files'\n ensureDir(dir)\n const downloaded: string[] = []\n const base = baseUrl(cfg.aampHost)\n const identity = loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath())\n const authHeader = identity ? `Basic ${Buffer.from(identity.email + ':' + identity.smtpPassword).toString('base64')}` : ''\n for (const att of r.attachments) {\n try {\n // Direct JMAP blob download \u2014 construct URL manually\n const dlUrl = `${base}/jmap/download/n/${encodeURIComponent(att.blobId)}/${encodeURIComponent(att.filename)}?accept=application/octet-stream`\n api.logger.info(`[AAMP] Fetching ${dlUrl}`)\n const dlRes = await fetch(dlUrl, { headers: { Authorization: authHeader } })\n if (!dlRes.ok) throw new Error(`HTTP ${dlRes.status}`)\n const buffer = Buffer.from(await dlRes.arrayBuffer())\n const filepath = `${dir}/${att.filename}`\n writeBinaryFile(filepath, buffer)\n downloaded.push(`${att.filename} (${(buffer.length / 1024).toFixed(1)} KB) \u2192 ${filepath}`)\n api.logger.info(`[AAMP] Downloaded: ${att.filename} (${(buffer.length / 1024).toFixed(1)} KB)`)\n } catch (dlErr) {\n api.logger.error(`[AAMP] Download failed for ${att.filename}: ${(dlErr as Error).message}`)\n }\n }\n if (downloaded.length) {\n attachmentLines = `\\n\\nAttachments downloaded:\\n${downloaded.join('\\n')}`\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: [\n `Sub-task ${r.status}: ${params.title}`,\n `Agent: ${r.from}`,\n `Task ID: ${result.taskId}`,\n r.status === 'completed' ? `\\nOutput:\\n${r.output}` : `\\nError: ${r.errorMsg ?? 'rejected'}`,\n attachmentLines,\n ].filter(Boolean).join('\\n'),\n }],\n }\n } else {\n const h = reply.data as TaskHelp\n return {\n content: [{\n type: 'text',\n text: [\n `Sub-task needs help: ${params.title}`,\n `Agent: ${h.from}`,\n `Task ID: ${result.taskId}`,\n `\\nQuestion: ${h.question}`,\n `Blocked reason: ${h.blockedReason}`,\n h.suggestedOptions?.length ? `Options: ${h.suggestedOptions.join(' | ')}` : '',\n ].filter(Boolean).join('\\n'),\n }],\n }\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Error dispatching task: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_dispatch_task' })\n\n // \u2500\u2500 7. Tool: check AAMP protocol support \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_check_protocol',\n description:\n 'Check if an email address supports the AAMP protocol. ' +\n 'Returns { aamp: true/false } indicating whether the address is an AAMP agent.',\n parameters: {\n type: 'object',\n required: ['email'],\n properties: {\n email: { type: 'string', description: 'Email address to check' },\n },\n },\n execute: async (_id: unknown, params: { email: string }) => {\n const base = baseUrl(cfg.aampHost)\n const email = params?.email ?? ''\n if (!email) {\n return { content: [{ type: 'text', text: 'Error: email parameter is required' }] }\n }\n try {\n const res = await fetch(`${base}/api/aamp-check?email=${encodeURIComponent(email)}`)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const data = await res.json() as { aamp: boolean; domain?: string }\n return {\n content: [{\n type: 'text',\n text: data.aamp\n ? `${params.email} supports AAMP protocol (domain: ${data.domain ?? 'unknown'})`\n : `${params.email} does not support AAMP protocol`,\n }],\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Could not check ${params.email}: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_check_protocol' })\n\n // \u2500\u2500 8. Tool: download attachment blob \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerTool({\n name: 'aamp_download_attachment',\n description:\n 'Download an AAMP email attachment to local disk by its blobId. ' +\n 'Use this to retrieve files received from sub-agent task results.',\n parameters: {\n type: 'object',\n required: ['blobId', 'filename'],\n properties: {\n blobId: { type: 'string', description: 'The JMAP blobId from the attachment metadata' },\n filename: { type: 'string', description: 'Filename to save as' },\n saveTo: { type: 'string', description: 'Directory to save to (default: /tmp/aamp-files)' },\n },\n },\n execute: async (_id: unknown, params: { blobId: string; filename: string; saveTo?: string }) => {\n if (!aampClient?.isConnected()) {\n return { content: [{ type: 'text', text: 'Error: AAMP client is not connected.' }] }\n }\n\n const dir = params.saveTo ?? '/tmp/aamp-files'\n ensureDir(dir)\n\n try {\n const buffer = await aampClient.downloadBlob(params.blobId, params.filename)\n const filepath = `${dir}/${params.filename}`\n writeBinaryFile(filepath, buffer)\n return {\n content: [{\n type: 'text',\n text: `Downloaded ${params.filename} (${(buffer.length / 1024).toFixed(1)} KB) to ${filepath}`,\n }],\n }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Download failed: ${(err as Error).message}` }],\n }\n }\n },\n }, { name: 'aamp_download_attachment' })\n\n // \u2500\u2500 9. Slash command: /aamp-status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n api.registerCommand({\n name: 'aamp-status',\n description: 'Show AAMP connection status and pending task queue',\n acceptsArgs: false,\n requireAuth: false,\n handler: () => {\n const isPollingFallback = aampClient?.isUsingPollingFallback?.() ?? false\n const connectionLine = aampClient?.isConnected()\n ? (isPollingFallback ? '\uD83D\uDFE1 connected (polling fallback)' : '\u2705 connected')\n : '\u274C disconnected'\n\n return {\n text: [\n `**AAMP Plugin Status**`,\n `Host: ${cfg.aampHost || '(not configured)'}`,\n `Identity: ${agentEmail || '(not yet registered)'}`,\n `Connection: ${connectionLine}`,\n `Cached: ${loadCachedIdentity(cfg.credentialsFile ?? defaultCredentialsPath()) ? 'yes' : 'no'}`,\n lastConnectionError ? `Last error: ${lastConnectionError}` : '',\n lastDisconnectReason ? `Last disconnect: ${lastDisconnectReason}` : '',\n `Pending: ${pendingTasks.size} task(s)`,\n ...[...pendingTasks.values()].map(\n (t) => ` \u2022 ${t.taskId.slice(0, 8)}\u2026 \"${t.title}\" from ${t.from}`,\n ),\n ]\n .filter(Boolean)\n .join('\\n'),\n }\n },\n })\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,9 +8,22 @@
8
8
  "aampHost": { "type": "string" },
9
9
  "slug": { "type": "string", "default": "openclaw-agent" },
10
10
  "credentialsFile": { "type": "string", "default": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json" },
11
- "allowedSenders": {
11
+ "senderPolicies": {
12
12
  "type": "array",
13
- "items": { "type": "string" }
13
+ "items": {
14
+ "type": "object",
15
+ "required": ["sender"],
16
+ "properties": {
17
+ "sender": { "type": "string" },
18
+ "dispatchContextRules": {
19
+ "type": "object",
20
+ "additionalProperties": {
21
+ "type": "array",
22
+ "items": { "type": "string" }
23
+ }
24
+ }
25
+ }
26
+ }
14
27
  }
15
28
  }
16
29
  },
@@ -18,7 +31,7 @@
18
31
  "aampHost": { "label": "AAMP Host", "placeholder": "https://meshmail.ai", "promptOnInstall": true },
19
32
  "slug": { "label": "Agent Slug", "placeholder": "openclaw-agent", "promptOnInstall": false },
20
33
  "credentialsFile": { "label": "Credentials File", "placeholder": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json", "promptOnInstall": false },
21
- "allowedSenders": { "label": "Allowed Senders", "placeholder": "meegle-bot@meshmail.ai, trusted@example.com", "promptOnInstall": true }
34
+ "senderPolicies": { "label": "Sender Policies", "placeholder": "[{\"sender\":\"meegle-bot@meshmail.ai\",\"dispatchContextRules\":{\"project_key\":[\"proj_123\"]}}]", "promptOnInstall": true }
22
35
  },
23
36
  "skills": ["./skills"]
24
37
  }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "skills"
8
8
  ],
9
9
  "license": "MIT",
10
- "version": "0.1.10",
10
+ "version": "0.1.13",
11
11
  "description": "AAMP Agent Mail Protocol — OpenClaw plugin. Gives OpenClaw an AAMP mailbox identity and lets it receive, process and reply to AAMP tasks.",
12
12
  "type": "module",
13
13
  "main": "dist/index.js",
@@ -34,10 +34,23 @@
34
34
  "default": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
35
35
  "description": "Path to persist AAMP credentials between restarts"
36
36
  },
37
- "allowedSenders": {
37
+ "senderPolicies": {
38
38
  "type": "array",
39
- "items": { "type": "string" },
40
- "description": "Sender whitelist. Only task.dispatch emails from these addresses are accepted. Case-insensitive exact match. Omit to allow all; set to [] to deny all."
39
+ "items": {
40
+ "type": "object",
41
+ "required": ["sender"],
42
+ "properties": {
43
+ "sender": { "type": "string" },
44
+ "dispatchContextRules": {
45
+ "type": "object",
46
+ "additionalProperties": {
47
+ "type": "array",
48
+ "items": { "type": "string" }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "description": "Per-sender authorization policies. Each sender can optionally require exact-match X-AAMP-Dispatch-Context key/value pairs."
41
54
  }
42
55
  }
43
56
  },
@@ -45,7 +58,7 @@
45
58
  "aampHost": { "label": "AAMP Host", "placeholder": "https://meshmail.ai", "promptOnInstall": true },
46
59
  "slug": { "label": "Agent Slug", "placeholder": "openclaw-agent", "promptOnInstall": false },
47
60
  "credentialsFile": { "label": "Credentials File", "placeholder": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json", "promptOnInstall": false },
48
- "allowedSenders": { "label": "Allowed Senders", "placeholder": "meegle-bot@meshmail.ai, trusted@example.com", "promptOnInstall": true }
61
+ "senderPolicies": { "label": "Sender Policies", "placeholder": "[{\"sender\":\"meegle-bot@meshmail.ai\",\"dispatchContextRules\":{\"project_key\":[\"proj_123\"]}}]", "promptOnInstall": true }
49
62
  }
50
63
  },
51
64
  "scripts": {