aamp-openclaw-plugin 0.1.22 → 0.1.24

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.
@@ -82,6 +82,7 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
82
82
  if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
83
83
  if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
84
84
  if (!next.plugins.entries || typeof next.plugins.entries !== 'object') next.plugins.entries = {}
85
+ if (!next.channels || typeof next.channels !== 'object') next.channels = {}
85
86
 
86
87
  if (!next.plugins.allow.includes(PLUGIN_ID)) {
87
88
  next.plugins.allow.push(PLUGIN_ID)
@@ -89,29 +90,51 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
89
90
 
90
91
  const legacyEntry = next.plugins.entries.aamp
91
92
  const prevEntry = next.plugins.entries[PLUGIN_ID] ?? legacyEntry
92
- const mergedConfig = {
93
- ...(prevEntry?.config && typeof prevEntry.config === 'object' ? prevEntry.config : {}),
94
- ...pluginConfig,
95
- }
96
- if (!pluginConfig.senderPolicies) {
97
- delete mergedConfig.senderPolicies
98
- }
99
-
100
93
  next.plugins.entries[PLUGIN_ID] = {
101
94
  enabled: true,
102
95
  ...(prevEntry && typeof prevEntry === 'object' ? prevEntry : {}),
103
- config: mergedConfig,
104
96
  }
105
97
 
106
98
  if (next.plugins.entries.aamp) {
107
99
  delete next.plugins.entries.aamp
108
100
  }
109
101
 
102
+ const previousChannelConfig =
103
+ next.channels.aamp && typeof next.channels.aamp === 'object' ? next.channels.aamp : {}
104
+ const mergedChannelConfig = {
105
+ ...previousChannelConfig,
106
+ ...pluginConfig,
107
+ enabled: true,
108
+ }
109
+ if (!pluginConfig.senderPolicies) {
110
+ delete mergedChannelConfig.senderPolicies
111
+ }
112
+ next.channels.aamp = mergedChannelConfig
113
+
110
114
  next.tools = ensureAampToolAllowlist(next.tools, options)
111
115
 
112
116
  return next
113
117
  }
114
118
 
119
+ function ensurePluginInstallRecord(config, installRecord) {
120
+ const next = config && typeof config === 'object' ? structuredClone(config) : {}
121
+ if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
122
+ if (!next.plugins.installs || typeof next.plugins.installs !== 'object') next.plugins.installs = {}
123
+
124
+ next.plugins.installs[PLUGIN_ID] = {
125
+ ...(next.plugins.installs[PLUGIN_ID] && typeof next.plugins.installs[PLUGIN_ID] === 'object'
126
+ ? next.plugins.installs[PLUGIN_ID]
127
+ : {}),
128
+ ...installRecord,
129
+ }
130
+
131
+ if (next.plugins.installs.aamp) {
132
+ delete next.plugins.installs.aamp
133
+ }
134
+
135
+ return next
136
+ }
137
+
115
138
  function ensureAampToolAllowlist(toolsConfig, options = {}) {
116
139
  const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
117
140
  const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
@@ -278,7 +301,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
278
301
  writeFileSync(credentialsPath, existingCredentials)
279
302
  }
280
303
 
281
- return extensionDir
304
+ return { extensionDir, packageJson, packageRoot }
282
305
  }
283
306
 
284
307
  function restartGateway() {
@@ -371,9 +394,12 @@ async function runInit() {
371
394
  const configPath = resolveOpenClawConfigPath()
372
395
  const existing = readJsonFile(configPath)
373
396
  const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
374
- const previousConfig = previousEntry?.config && typeof previousEntry.config === 'object'
375
- ? previousEntry.config
376
- : null
397
+ const previousConfig =
398
+ existing?.channels?.aamp && typeof existing.channels.aamp === 'object'
399
+ ? existing.channels.aamp
400
+ : previousEntry?.config && typeof previousEntry.config === 'object'
401
+ ? previousEntry.config
402
+ : null
377
403
  const previousCredentialsFile = previousConfig?.credentialsFile || DEFAULT_CREDENTIALS_FILE
378
404
  const previousSlug = previousConfig?.slug || 'openclaw-agent'
379
405
 
@@ -466,10 +492,10 @@ async function runInit() {
466
492
  }
467
493
 
468
494
  output.write('\nInstalling OpenClaw plugin files...\n')
469
- const extensionDir = installPluginFiles(previousCredentialsFile)
495
+ const { extensionDir, packageJson, packageRoot } = installPluginFiles(previousCredentialsFile)
470
496
 
471
497
  const toolPolicyPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline })
472
- const next = ensurePluginConfig(existing, {
498
+ let next = ensurePluginConfig(existing, {
473
499
  aampHost,
474
500
  slug,
475
501
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
@@ -478,6 +504,20 @@ async function runInit() {
478
504
  includeCodingBaseline,
479
505
  })
480
506
 
507
+ const now = new Date().toISOString()
508
+ next = ensurePluginInstallRecord(next, {
509
+ source: 'npm',
510
+ spec: packageJson?.name || PLUGIN_ID,
511
+ sourcePath: packageRoot,
512
+ installPath: extensionDir,
513
+ version: packageJson?.version || '0.0.0',
514
+ resolvedName: packageJson?.name || PLUGIN_ID,
515
+ resolvedVersion: packageJson?.version || '0.0.0',
516
+ resolvedSpec: `${packageJson?.name || PLUGIN_ID}@${packageJson?.version || '0.0.0'}`,
517
+ installedAt: now,
518
+ resolvedAt: now,
519
+ })
520
+
481
521
  writeJsonFile(configPath, next)
482
522
 
483
523
  const identityResult = await ensureMailboxIdentity({
@@ -496,6 +536,8 @@ async function runInit() {
496
536
  '',
497
537
  'Configured plugin entry:',
498
538
  ` plugins.entries["${PLUGIN_ID}"]`,
539
+ ` plugins.installs["${PLUGIN_ID}"]`,
540
+ ` channels.aamp.enabled: ${next.channels?.aamp?.enabled === true ? 'true' : 'false'}`,
499
541
  ` aampHost: ${aampHost}`,
500
542
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
501
543
  ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import WebSocket from "ws";
3
3
 
4
4
  // ../sdk/src/types.ts
5
+ var AAMP_PROTOCOL_VERSION = "1.0";
5
6
  var AAMP_HEADER = {
7
+ VERSION: "X-AAMP-Version",
6
8
  INTENT: "X-AAMP-Intent",
7
9
  TASK_ID: "X-AAMP-TaskId",
8
10
  TIMEOUT: "X-AAMP-Timeout",
@@ -122,6 +124,7 @@ function parseAampHeaders(meta) {
122
124
  const headers = normalizeHeaders(meta.headers);
123
125
  const intent = getAampHeader(headers, AAMP_HEADER.INTENT);
124
126
  const taskId = getAampHeader(headers, AAMP_HEADER.TASK_ID);
127
+ const protocolVersion = getAampHeader(headers, AAMP_HEADER.VERSION) ?? AAMP_PROTOCOL_VERSION;
125
128
  if (!intent || !taskId)
126
129
  return null;
127
130
  const from = meta.from.replace(/^<|>$/g, "");
@@ -135,6 +138,7 @@ function parseAampHeaders(meta) {
135
138
  );
136
139
  const parentTaskId = getAampHeader(headers, AAMP_HEADER.PARENT_TASK_ID);
137
140
  const dispatch = {
141
+ protocolVersion,
138
142
  intent: "task.dispatch",
139
143
  taskId,
140
144
  title: decodedSubject.replace(/^\[AAMP Task\]\s*/, "").trim() || "Untitled Task",
@@ -159,6 +163,7 @@ function parseAampHeaders(meta) {
159
163
  getAampHeader(headers, AAMP_HEADER.STRUCTURED_RESULT)
160
164
  );
161
165
  const result = {
166
+ protocolVersion,
162
167
  intent: "task.result",
163
168
  taskId,
164
169
  status,
@@ -171,12 +176,13 @@ function parseAampHeaders(meta) {
171
176
  };
172
177
  return result;
173
178
  }
174
- if (intent === "task.help") {
179
+ if (intent === "task.help_needed" || intent === "task.help") {
175
180
  const question = getAampHeader(headers, AAMP_HEADER.QUESTION) ?? "";
176
181
  const blockedReason = getAampHeader(headers, AAMP_HEADER.BLOCKED_REASON) ?? "";
177
182
  const suggestedOptionsStr = getAampHeader(headers, AAMP_HEADER.SUGGESTED_OPTIONS) ?? "";
178
183
  const help = {
179
- intent: "task.help",
184
+ protocolVersion,
185
+ intent: "task.help_needed",
180
186
  taskId,
181
187
  question: decodeMimeEncodedWords(question),
182
188
  blockedReason: decodeMimeEncodedWords(blockedReason),
@@ -189,6 +195,7 @@ function parseAampHeaders(meta) {
189
195
  }
190
196
  if (intent === "task.ack") {
191
197
  const ack = {
198
+ protocolVersion,
192
199
  intent: "task.ack",
193
200
  taskId,
194
201
  from,
@@ -201,6 +208,7 @@ function parseAampHeaders(meta) {
201
208
  }
202
209
  function buildDispatchHeaders(params) {
203
210
  const headers = {
211
+ [AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
204
212
  [AAMP_HEADER.INTENT]: "task.dispatch",
205
213
  [AAMP_HEADER.TASK_ID]: params.taskId
206
214
  };
@@ -221,12 +229,14 @@ function buildDispatchHeaders(params) {
221
229
  }
222
230
  function buildAckHeaders(opts) {
223
231
  return {
232
+ [AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
224
233
  [AAMP_HEADER.INTENT]: "task.ack",
225
234
  [AAMP_HEADER.TASK_ID]: opts.taskId
226
235
  };
227
236
  }
228
237
  function buildResultHeaders(params) {
229
238
  const headers = {
239
+ [AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
230
240
  [AAMP_HEADER.INTENT]: "task.result",
231
241
  [AAMP_HEADER.TASK_ID]: params.taskId,
232
242
  [AAMP_HEADER.STATUS]: params.status,
@@ -243,7 +253,8 @@ function buildResultHeaders(params) {
243
253
  }
244
254
  function buildHelpHeaders(params) {
245
255
  return {
246
- [AAMP_HEADER.INTENT]: "task.help",
256
+ [AAMP_HEADER.VERSION]: AAMP_PROTOCOL_VERSION,
257
+ [AAMP_HEADER.INTENT]: "task.help_needed",
247
258
  [AAMP_HEADER.TASK_ID]: params.taskId,
248
259
  [AAMP_HEADER.QUESTION]: params.question,
249
260
  [AAMP_HEADER.BLOCKED_REASON]: params.blockedReason,
@@ -493,7 +504,7 @@ var JmapPushClient = class extends TinyEmitter {
493
504
  * Process a received email.
494
505
  *
495
506
  * Priority:
496
- * 1. If X-AAMP-Intent is present → emit typed AAMP event (task.dispatch / task.result / task.help)
507
+ * 1. If X-AAMP-Intent is present → emit typed AAMP event (task.dispatch / task.result / task.help_needed)
497
508
  * 2. If In-Reply-To is present → emit 'reply' event so the application layer can
498
509
  * resolve the thread (inReplyTo → taskId via Redis/DB) and handle human replies.
499
510
  * 3. Otherwise → ignore (not an AAMP-related email)
@@ -541,7 +552,8 @@ var JmapPushClient = class extends TinyEmitter {
541
552
  case "task.result":
542
553
  this.emit("task.result", aampMsg);
543
554
  break;
544
- case "task.help":
555
+ case "task.help_needed":
556
+ this.emit("task.help_needed", aampMsg);
545
557
  this.emit("task.help", aampMsg);
546
558
  break;
547
559
  case "task.ack":
@@ -1240,7 +1252,8 @@ var AampClient = class extends TinyEmitter {
1240
1252
  this.jmapClient.on("task.result", (result) => {
1241
1253
  this.emit("task.result", result);
1242
1254
  });
1243
- this.jmapClient.on("task.help", (help) => {
1255
+ this.jmapClient.on("task.help_needed", (help) => {
1256
+ this.emit("task.help_needed", help);
1244
1257
  this.emit("task.help", help);
1245
1258
  });
1246
1259
  this.jmapClient.on("task.ack", (ack) => {
@@ -1523,7 +1536,7 @@ var src_default = {
1523
1536
  },
1524
1537
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1525
1538
  register(api) {
1526
- const cfg = api.pluginConfig ?? {};
1539
+ const cfg = api.config?.channels?.aamp ?? api.pluginConfig ?? {};
1527
1540
  api.registerChannel({
1528
1541
  id: "aamp",
1529
1542
  meta: { label: "AAMP" },
@@ -1803,8 +1816,8 @@ ${notifyBody?.bodyText ?? "Sub-task completed."}${actionSection}`;
1803
1816
  api.logger.error(`[AAMP] Sub-task result processing failed: ${err.message}`);
1804
1817
  });
1805
1818
  });
1806
- aampClient.on("task.help", (help) => {
1807
- api.logger.info(`[AAMP] \u2190 task.help ${help.taskId} question="${help.question}" from=${help.from}`);
1819
+ aampClient.on("task.help_needed", (help) => {
1820
+ api.logger.info(`[AAMP] \u2190 task.help_needed ${help.taskId} question="${help.question}" from=${help.from}`);
1808
1821
  const waiter = waitingDispatches.get(help.taskId);
1809
1822
  if (waiter) {
1810
1823
  waitingDispatches.delete(help.taskId);
@@ -2264,7 +2277,7 @@ ${task.contextLinks.map((l) => ` - ${l}`).join("\n")}` : "",
2264
2277
  suggestedOptions: p.suggestedOptions ?? [],
2265
2278
  inReplyTo: task.messageId || void 0
2266
2279
  });
2267
- api.logger.info(`[AAMP] \u2192 task.help ${task.taskId}`);
2280
+ api.logger.info(`[AAMP] \u2192 task.help_needed ${task.taskId}`);
2268
2281
  return {
2269
2282
  content: [
2270
2283
  {
@@ -2458,7 +2471,14 @@ Question: ${h.question}`,
2458
2471
  return { content: [{ type: "text", text: "Error: email parameter is required" }] };
2459
2472
  }
2460
2473
  try {
2461
- const res = await fetch(`${base}/api/aamp-check?email=${encodeURIComponent(email)}`);
2474
+ const discoveryRes = await fetch(`${base}/.well-known/aamp`);
2475
+ if (!discoveryRes.ok)
2476
+ throw new Error(`HTTP ${discoveryRes.status}`);
2477
+ const discovery = await discoveryRes.json();
2478
+ const apiUrl = discovery.api?.url;
2479
+ if (!apiUrl)
2480
+ throw new Error("AAMP discovery did not return api.url");
2481
+ const res = await fetch(`${base}${apiUrl}?action=aamp.mailbox.check&email=${encodeURIComponent(email)}`);
2462
2482
  if (!res.ok)
2463
2483
  throw new Error(`HTTP ${res.status}`);
2464
2484
  const data = await res.json();