aamp-openclaw-plugin 0.1.19-alpha.0 → 0.1.20

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.
@@ -11,6 +11,36 @@ import { fileURLToPath } from 'node:url'
11
11
  const PLUGIN_ID = 'aamp-openclaw-plugin'
12
12
  const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
13
13
  const DEFAULT_CREDENTIALS_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json'
14
+ const CODING_TOOL_ALLOWLIST = [
15
+ 'read',
16
+ 'write',
17
+ 'edit',
18
+ 'apply_patch',
19
+ 'exec',
20
+ 'process',
21
+ 'web_search',
22
+ 'web_fetch',
23
+ 'memory_search',
24
+ 'memory_get',
25
+ 'sessions_list',
26
+ 'sessions_history',
27
+ 'sessions_send',
28
+ 'sessions_spawn',
29
+ 'sessions_yield',
30
+ 'subagents',
31
+ 'session_status',
32
+ 'cron',
33
+ 'image',
34
+ 'image_generate',
35
+ ]
36
+ const AAMP_PLUGIN_TOOL_ALLOWLIST = [
37
+ 'aamp_send_result',
38
+ 'aamp_send_help',
39
+ 'aamp_pending_tasks',
40
+ 'aamp_dispatch_task',
41
+ 'aamp_check_protocol',
42
+ 'aamp_download_attachment',
43
+ ]
14
44
 
15
45
  function resolveOpenClawHome() {
16
46
  return process.env.OPENCLAW_HOME?.trim() || join(homedir(), '.openclaw')
@@ -46,12 +76,11 @@ function normalizeBaseUrl(url) {
46
76
  return `https://${url.replace(/\/$/, '')}`
47
77
  }
48
78
 
49
- function ensurePluginConfig(config, pluginConfig, installRecord) {
79
+ function ensurePluginConfig(config, pluginConfig, options = {}) {
50
80
  const next = config && typeof config === 'object' ? structuredClone(config) : {}
51
81
  if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
52
82
  if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
53
83
  if (!next.plugins.entries || typeof next.plugins.entries !== 'object') next.plugins.entries = {}
54
- if (!next.plugins.installs || typeof next.plugins.installs !== 'object') next.plugins.installs = {}
55
84
 
56
85
  if (!next.plugins.allow.includes(PLUGIN_ID)) {
57
86
  next.plugins.allow.push(PLUGIN_ID)
@@ -77,17 +106,65 @@ function ensurePluginConfig(config, pluginConfig, installRecord) {
77
106
  delete next.plugins.entries.aamp
78
107
  }
79
108
 
80
- if (next.plugins.installs.aamp) {
81
- delete next.plugins.installs.aamp
82
- }
109
+ next.tools = ensureAampToolAllowlist(next.tools, options)
83
110
 
84
- if (installRecord) {
85
- next.plugins.installs[PLUGIN_ID] = installRecord
86
- }
111
+ return next
112
+ }
113
+
114
+ function ensureAampToolAllowlist(toolsConfig, options = {}) {
115
+ const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
116
+ const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
117
+ const includeCodingBaseline = options.includeCodingBaseline === true
118
+
119
+ const mergedAllow = [
120
+ ...existingAllow,
121
+ ...(includeCodingBaseline ? CODING_TOOL_ALLOWLIST : []),
122
+ ...AAMP_PLUGIN_TOOL_ALLOWLIST,
123
+ ]
124
+
125
+ next.allow = Array.from(new Set(mergedAllow))
87
126
 
88
127
  return next
89
128
  }
90
129
 
130
+ function planToolPolicyUpdate(toolsConfig, options = {}) {
131
+ const current = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
132
+ const existingAllow = Array.isArray(current.allow) ? current.allow.filter((value) => typeof value === 'string' && value.trim()) : []
133
+ const includeCodingBaseline = options.includeCodingBaseline === true
134
+ const missingAampTools = AAMP_PLUGIN_TOOL_ALLOWLIST.filter((tool) => !existingAllow.includes(tool))
135
+ const currentProfile = typeof current.profile === 'string' ? current.profile : undefined
136
+ const missingCodingTools = includeCodingBaseline
137
+ ? CODING_TOOL_ALLOWLIST.filter((tool) => !existingAllow.includes(tool))
138
+ : []
139
+
140
+ return {
141
+ current,
142
+ missingAampTools,
143
+ missingCodingTools,
144
+ needsAnyChange: missingAampTools.length > 0 || missingCodingTools.length > 0,
145
+ needsNonPluginChange: missingCodingTools.length > 0,
146
+ currentProfile,
147
+ next: ensureAampToolAllowlist(current, { includeCodingBaseline }),
148
+ }
149
+ }
150
+
151
+ function currentToolPolicySummary(plan) {
152
+ const lines = []
153
+ if (plan.currentProfile) {
154
+ lines.push(` current tools.profile: ${plan.currentProfile}`)
155
+ } else {
156
+ lines.push(` current tools.profile: (none)`)
157
+ }
158
+ lines.push(` current tools.allow count: ${Array.isArray(plan.current.allow) ? plan.current.allow.length : 0}`)
159
+ if (plan.missingAampTools.length > 0) {
160
+ lines.push(` missing AAMP tools: ${plan.missingAampTools.join(', ')}`)
161
+ }
162
+ if (plan.needsNonPluginChange) {
163
+ lines.push(` additional core tools to add: ${plan.missingCodingTools.join(', ')}`)
164
+ }
165
+ return lines.join('\n')
166
+ }
167
+
91
168
  function parseDispatchContextRules(raw) {
92
169
  const trimmed = raw.trim()
93
170
  if (!trimmed) return undefined
@@ -130,9 +207,34 @@ function copyIntoDir(src, dest) {
130
207
  cpSync(src, dest, { recursive: true, force: true })
131
208
  }
132
209
 
210
+ function ensureBuiltArtifacts(packageRoot) {
211
+ const entryFile = join(packageRoot, 'dist', 'index.js')
212
+ if (existsSync(entryFile)) return
213
+
214
+ const result = spawnSync('npm', ['run', 'build'], {
215
+ cwd: packageRoot,
216
+ encoding: 'utf-8',
217
+ })
218
+
219
+ if (result.error) {
220
+ throw new Error(`Failed to build plugin artifacts: ${result.error.message}`)
221
+ }
222
+
223
+ if (result.status !== 0) {
224
+ throw new Error(
225
+ `Failed to build plugin artifacts: ${(result.stderr || result.stdout || `exit code ${result.status}`).trim()}`,
226
+ )
227
+ }
228
+
229
+ if (!existsSync(entryFile)) {
230
+ throw new Error(`Plugin build completed but ${entryFile} is still missing`)
231
+ }
232
+ }
233
+
133
234
  function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
134
235
  const extensionDir = resolveExtensionDir()
135
236
  const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
237
+ ensureBuiltArtifacts(packageRoot)
136
238
  const packageJson = readJsonFile(join(packageRoot, 'package.json'))
137
239
  const credentialsPath = expandHome(credentialsFile)
138
240
  const existingCredentials = existsSync(credentialsPath)
@@ -151,12 +253,24 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
151
253
 
152
254
  writeJsonFile(join(extensionDir, 'package.json'), packageJson)
153
255
 
256
+ const dependencyPackages = ['aamp-sdk', 'ws', 'nodemailer']
257
+ const nodeModulesDir = join(extensionDir, 'node_modules')
258
+ mkdirSync(nodeModulesDir, { recursive: true })
259
+
260
+ for (const dep of dependencyPackages) {
261
+ const depRoot = join(packageRoot, 'node_modules', dep)
262
+ if (!existsSync(depRoot)) {
263
+ throw new Error(`Missing dependency directory: ${depRoot}`)
264
+ }
265
+ copyIntoDir(depRoot, join(nodeModulesDir, dep))
266
+ }
267
+
154
268
  if (existingCredentials) {
155
269
  mkdirSync(dirname(credentialsPath), { recursive: true })
156
270
  writeFileSync(credentialsPath, existingCredentials)
157
271
  }
158
272
 
159
- return { extensionDir, packageJson, packageRoot }
273
+ return extensionDir
160
274
  }
161
275
 
162
276
  function restartGateway() {
@@ -184,28 +298,6 @@ function restartGateway() {
184
298
  }
185
299
  }
186
300
 
187
- async function fetchJson(url, init, stepLabel) {
188
- let res
189
- try {
190
- res = await fetch(url, init)
191
- } catch (error) {
192
- const message = error instanceof Error ? error.message : String(error)
193
- throw new Error(`${stepLabel} failed for ${url}: ${message}`)
194
- }
195
-
196
- if (!res.ok) {
197
- const text = await res.text().catch(() => '')
198
- throw new Error(`${stepLabel} failed (${res.status}) for ${url}: ${text || res.statusText}`)
199
- }
200
-
201
- try {
202
- return await res.json()
203
- } catch (error) {
204
- const message = error instanceof Error ? error.message : String(error)
205
- throw new Error(`${stepLabel} returned invalid JSON for ${url}: ${message}`)
206
- }
207
- }
208
-
209
301
  async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
210
302
  const resolvedCreds = expandHome(credentialsFile)
211
303
  if (existsSync(resolvedCreds)) {
@@ -213,30 +305,33 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
213
305
  }
214
306
 
215
307
  const base = normalizeBaseUrl(aampHost)
216
- const registerUrl = `${base}/api/nodes/self-register`
217
- const registerData = await fetchJson(
218
- registerUrl,
219
- {
220
- method: 'POST',
221
- headers: { 'Content-Type': 'application/json' },
222
- body: JSON.stringify({
223
- slug,
224
- description: 'OpenClaw AAMP agent node',
225
- }),
226
- },
227
- 'AAMP self-register',
228
- )
308
+ const registerRes = await fetch(`${base}/api/nodes/self-register`, {
309
+ method: 'POST',
310
+ headers: { 'Content-Type': 'application/json' },
311
+ body: JSON.stringify({
312
+ slug,
313
+ description: 'OpenClaw AAMP agent node',
314
+ }),
315
+ })
316
+
317
+ if (!registerRes.ok) {
318
+ const text = await registerRes.text().catch(() => '')
319
+ throw new Error(`AAMP self-register failed (${registerRes.status}): ${text || registerRes.statusText}`)
320
+ }
321
+
322
+ const registerData = await registerRes.json()
229
323
  const code = registerData?.registrationCode
230
324
  if (!code) {
231
325
  throw new Error('AAMP self-register succeeded but no registrationCode was returned')
232
326
  }
233
327
 
234
- const credUrl = `${base}/api/nodes/credentials?code=${encodeURIComponent(code)}`
235
- const credData = await fetchJson(
236
- credUrl,
237
- undefined,
238
- 'AAMP credential exchange',
239
- )
328
+ const credRes = await fetch(`${base}/api/nodes/credentials?code=${encodeURIComponent(code)}`)
329
+ if (!credRes.ok) {
330
+ const text = await credRes.text().catch(() => '')
331
+ throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
332
+ }
333
+
334
+ const credData = await credRes.json()
240
335
  const identity = {
241
336
  email: credData?.email,
242
337
  jmapToken: credData?.jmap?.token,
@@ -278,6 +373,7 @@ async function runInit() {
278
373
  let senderPolicies = previousConfig?.senderPolicies
279
374
  let slug = previousSlug
280
375
  let reuseExistingConfig = Boolean(previousConfig)
376
+ let includeCodingBaseline = false
281
377
 
282
378
  if (input.isTTY) {
283
379
  const rl = createInterface({ input, output })
@@ -319,6 +415,28 @@ async function runInit() {
319
415
  senderPolicies = undefined
320
416
  }
321
417
  }
418
+
419
+ const codingPromptPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline: true })
420
+ const shouldOfferCodingBaseline =
421
+ codingPromptPlan.missingCodingTools.length > 0 &&
422
+ !Array.isArray(existing?.tools?.allow) &&
423
+ !(typeof existing?.tools?.profile === 'string' && existing.tools.profile.trim())
424
+
425
+ if (shouldOfferCodingBaseline) {
426
+ output.write(
427
+ [
428
+ '',
429
+ 'Optional tool policy upgrade:',
430
+ ' Default init only adds the AAMP plugin tools needed for mailbox-style task receive/reply.',
431
+ ' If this agent also needs file/shell/web coding workflows, you can additionally add',
432
+ ' the coding baseline tool set now.',
433
+ currentToolPolicySummary(codingPromptPlan),
434
+ '',
435
+ ].join('\n'),
436
+ )
437
+ const toolAnswer = await rl.question('Also add coding baseline tools? [y/N]: ')
438
+ includeCodingBaseline = isYes(toolAnswer, false)
439
+ }
322
440
  } finally {
323
441
  rl.close()
324
442
  }
@@ -340,27 +458,17 @@ async function runInit() {
340
458
  }
341
459
 
342
460
  output.write('\nInstalling OpenClaw plugin files...\n')
343
- const { extensionDir, packageJson, packageRoot } = installPluginFiles(previousCredentialsFile)
344
- const nowIso = new Date().toISOString()
345
- const installRecord = {
346
- source: 'npm',
347
- spec: packageJson.name,
348
- sourcePath: packageRoot,
349
- installPath: extensionDir,
350
- version: packageJson.version,
351
- resolvedName: packageJson.name,
352
- resolvedVersion: packageJson.version,
353
- resolvedSpec: `${packageJson.name}@${packageJson.version}`,
354
- installedAt: nowIso,
355
- resolvedAt: nowIso,
356
- }
461
+ const extensionDir = installPluginFiles(previousCredentialsFile)
357
462
 
463
+ const toolPolicyPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline })
358
464
  const next = ensurePluginConfig(existing, {
359
465
  aampHost,
360
466
  slug,
361
467
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
362
468
  ...(senderPolicies ? { senderPolicies } : {}),
363
- }, installRecord)
469
+ }, {
470
+ includeCodingBaseline,
471
+ })
364
472
 
365
473
  writeJsonFile(configPath, next)
366
474
 
@@ -383,6 +491,8 @@ async function runInit() {
383
491
  ` aampHost: ${aampHost}`,
384
492
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
385
493
  ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
494
+ ` tools.allow: ${JSON.stringify(next.tools?.allow ?? [])}`,
495
+ ` codingBaselineAdded: ${toolPolicyPlan.missingCodingTools.length > 0 && includeCodingBaseline ? 'yes' : 'no'}`,
386
496
  identityResult.created
387
497
  ? ` mailbox: ${identityResult.email} (registered and saved to ${identityResult.credentialsPath})`
388
498
  : ` mailbox: existing credentials reused from ${identityResult.credentialsPath}`,
@@ -1,12 +1,38 @@
1
- import { createRequire as __aampCreateRequire } from "module"; const require = __aampCreateRequire(import.meta.url);
2
- import {
3
- defaultCredentialsPath,
4
- ensureDir,
5
- loadCachedIdentity,
6
- readBinaryFile,
7
- saveCachedIdentity,
8
- writeBinaryFile
9
- } from "./chunk-ORTVVAMV.js";
1
+ // src/file-store.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ function defaultCredentialsPath() {
6
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
7
+ }
8
+ function loadCachedIdentity(file) {
9
+ const resolved = file ?? defaultCredentialsPath();
10
+ if (!existsSync(resolved))
11
+ return null;
12
+ try {
13
+ const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
14
+ if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword)
15
+ return null;
16
+ return parsed;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function saveCachedIdentity(identity, file) {
22
+ const resolved = file ?? defaultCredentialsPath();
23
+ mkdirSync(dirname(resolved), { recursive: true });
24
+ writeFileSync(resolved, JSON.stringify(identity, null, 2), "utf-8");
25
+ }
26
+ function ensureDir(dir) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ function readBinaryFile(path) {
30
+ return readFileSync(path);
31
+ }
32
+ function writeBinaryFile(path, content) {
33
+ mkdirSync(dirname(path), { recursive: true });
34
+ writeFileSync(path, content);
35
+ }
10
36
  export {
11
37
  defaultCredentialsPath,
12
38
  ensureDir,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
3
+ "sources": ["../src/file-store.ts"],
4
+ "sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\n\nexport interface Identity {\n email: string\n jmapToken: string\n smtpPassword: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword) return null\n return parsed as Identity\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(identity, null, 2), 'utf-8')\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
5
+ "mappings": ";AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AAQjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,mBAAmB,MAAgC;AACjE,QAAM,WAAW,QAAQ,uBAAuB;AAChD,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,aAAa,CAAC,OAAO;AAAc,aAAO;AACvE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAoB,MAAqB;AAC1E,QAAM,WAAW,QAAQ,uBAAuB;AAChD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AACpE;AAEO,SAAS,UAAU,KAAmB;AAC3C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,aAAa,IAAI;AAC1B;AAEO,SAAS,gBAAgB,MAAc,SAAoC;AAChF,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,OAAO;AAC7B;",
6
6
  "names": []
7
7
  }