aamp-openclaw-plugin 0.1.21 → 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.
- package/bin/aamp-openclaw-plugin.mjs +66 -16
- package/dist/index.js +31 -11
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { dirname, join } from 'node:path'
|
|
|
6
6
|
import { createInterface } from 'node:readline/promises'
|
|
7
7
|
import { stdin as input, stdout as output, stderr } from 'node:process'
|
|
8
8
|
import { spawnSync } from 'node:child_process'
|
|
9
|
+
import { createRequire } from 'node:module'
|
|
9
10
|
import { fileURLToPath } from 'node:url'
|
|
10
11
|
|
|
11
12
|
const PLUGIN_ID = 'aamp-openclaw-plugin'
|
|
@@ -81,6 +82,7 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
|
81
82
|
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
82
83
|
if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
|
|
83
84
|
if (!next.plugins.entries || typeof next.plugins.entries !== 'object') next.plugins.entries = {}
|
|
85
|
+
if (!next.channels || typeof next.channels !== 'object') next.channels = {}
|
|
84
86
|
|
|
85
87
|
if (!next.plugins.allow.includes(PLUGIN_ID)) {
|
|
86
88
|
next.plugins.allow.push(PLUGIN_ID)
|
|
@@ -88,29 +90,51 @@ function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
|
88
90
|
|
|
89
91
|
const legacyEntry = next.plugins.entries.aamp
|
|
90
92
|
const prevEntry = next.plugins.entries[PLUGIN_ID] ?? legacyEntry
|
|
91
|
-
const mergedConfig = {
|
|
92
|
-
...(prevEntry?.config && typeof prevEntry.config === 'object' ? prevEntry.config : {}),
|
|
93
|
-
...pluginConfig,
|
|
94
|
-
}
|
|
95
|
-
if (!pluginConfig.senderPolicies) {
|
|
96
|
-
delete mergedConfig.senderPolicies
|
|
97
|
-
}
|
|
98
|
-
|
|
99
93
|
next.plugins.entries[PLUGIN_ID] = {
|
|
100
94
|
enabled: true,
|
|
101
95
|
...(prevEntry && typeof prevEntry === 'object' ? prevEntry : {}),
|
|
102
|
-
config: mergedConfig,
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
if (next.plugins.entries.aamp) {
|
|
106
99
|
delete next.plugins.entries.aamp
|
|
107
100
|
}
|
|
108
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
|
+
|
|
109
114
|
next.tools = ensureAampToolAllowlist(next.tools, options)
|
|
110
115
|
|
|
111
116
|
return next
|
|
112
117
|
}
|
|
113
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
|
+
|
|
114
138
|
function ensureAampToolAllowlist(toolsConfig, options = {}) {
|
|
115
139
|
const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
|
|
116
140
|
const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
|
|
@@ -257,8 +281,15 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
|
257
281
|
const nodeModulesDir = join(extensionDir, 'node_modules')
|
|
258
282
|
mkdirSync(nodeModulesDir, { recursive: true })
|
|
259
283
|
|
|
284
|
+
const requireFromPlugin = createRequire(import.meta.url)
|
|
285
|
+
|
|
260
286
|
for (const dep of dependencyPackages) {
|
|
261
|
-
|
|
287
|
+
let depRoot
|
|
288
|
+
try {
|
|
289
|
+
depRoot = dirname(requireFromPlugin.resolve(`${dep}/package.json`))
|
|
290
|
+
} catch {
|
|
291
|
+
depRoot = join(packageRoot, 'node_modules', dep)
|
|
292
|
+
}
|
|
262
293
|
if (!existsSync(depRoot)) {
|
|
263
294
|
throw new Error(`Missing dependency directory: ${depRoot}`)
|
|
264
295
|
}
|
|
@@ -270,7 +301,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
|
270
301
|
writeFileSync(credentialsPath, existingCredentials)
|
|
271
302
|
}
|
|
272
303
|
|
|
273
|
-
return extensionDir
|
|
304
|
+
return { extensionDir, packageJson, packageRoot }
|
|
274
305
|
}
|
|
275
306
|
|
|
276
307
|
function restartGateway() {
|
|
@@ -363,9 +394,12 @@ async function runInit() {
|
|
|
363
394
|
const configPath = resolveOpenClawConfigPath()
|
|
364
395
|
const existing = readJsonFile(configPath)
|
|
365
396
|
const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
|
|
366
|
-
const previousConfig =
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
369
403
|
const previousCredentialsFile = previousConfig?.credentialsFile || DEFAULT_CREDENTIALS_FILE
|
|
370
404
|
const previousSlug = previousConfig?.slug || 'openclaw-agent'
|
|
371
405
|
|
|
@@ -458,10 +492,10 @@ async function runInit() {
|
|
|
458
492
|
}
|
|
459
493
|
|
|
460
494
|
output.write('\nInstalling OpenClaw plugin files...\n')
|
|
461
|
-
const extensionDir = installPluginFiles(previousCredentialsFile)
|
|
495
|
+
const { extensionDir, packageJson, packageRoot } = installPluginFiles(previousCredentialsFile)
|
|
462
496
|
|
|
463
497
|
const toolPolicyPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline })
|
|
464
|
-
|
|
498
|
+
let next = ensurePluginConfig(existing, {
|
|
465
499
|
aampHost,
|
|
466
500
|
slug,
|
|
467
501
|
credentialsFile: DEFAULT_CREDENTIALS_FILE,
|
|
@@ -470,6 +504,20 @@ async function runInit() {
|
|
|
470
504
|
includeCodingBaseline,
|
|
471
505
|
})
|
|
472
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
|
+
|
|
473
521
|
writeJsonFile(configPath, next)
|
|
474
522
|
|
|
475
523
|
const identityResult = await ensureMailboxIdentity({
|
|
@@ -488,6 +536,8 @@ async function runInit() {
|
|
|
488
536
|
'',
|
|
489
537
|
'Configured plugin entry:',
|
|
490
538
|
` plugins.entries["${PLUGIN_ID}"]`,
|
|
539
|
+
` plugins.installs["${PLUGIN_ID}"]`,
|
|
540
|
+
` channels.aamp.enabled: ${next.channels?.aamp?.enabled === true ? 'true' : 'false'}`,
|
|
491
541
|
` aampHost: ${aampHost}`,
|
|
492
542
|
` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
|
|
493
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1807
|
-
api.logger.info(`[AAMP] \u2190 task.
|
|
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.
|
|
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
|
|
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();
|