aamp-openclaw-plugin 0.1.19 → 0.1.21

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,7 +76,7 @@ function normalizeBaseUrl(url) {
46
76
  return `https://${url.replace(/\/$/, '')}`
47
77
  }
48
78
 
49
- function ensurePluginConfig(config, pluginConfig) {
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 = []
@@ -76,9 +106,65 @@ function ensurePluginConfig(config, pluginConfig) {
76
106
  delete next.plugins.entries.aamp
77
107
  }
78
108
 
109
+ next.tools = ensureAampToolAllowlist(next.tools, options)
110
+
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))
126
+
79
127
  return next
80
128
  }
81
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
+
82
168
  function parseDispatchContextRules(raw) {
83
169
  const trimmed = raw.trim()
84
170
  if (!trimmed) return undefined
@@ -121,9 +207,34 @@ function copyIntoDir(src, dest) {
121
207
  cpSync(src, dest, { recursive: true, force: true })
122
208
  }
123
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
+
124
234
  function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
125
235
  const extensionDir = resolveExtensionDir()
126
236
  const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
237
+ ensureBuiltArtifacts(packageRoot)
127
238
  const packageJson = readJsonFile(join(packageRoot, 'package.json'))
128
239
  const credentialsPath = expandHome(credentialsFile)
129
240
  const existingCredentials = existsSync(credentialsPath)
@@ -142,6 +253,18 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
142
253
 
143
254
  writeJsonFile(join(extensionDir, 'package.json'), packageJson)
144
255
 
256
+ const dependencyPackages = ['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
+
145
268
  if (existingCredentials) {
146
269
  mkdirSync(dirname(credentialsPath), { recursive: true })
147
270
  writeFileSync(credentialsPath, existingCredentials)
@@ -175,28 +298,6 @@ function restartGateway() {
175
298
  }
176
299
  }
177
300
 
178
- async function fetchJson(url, init, stepLabel) {
179
- let res
180
- try {
181
- res = await fetch(url, init)
182
- } catch (error) {
183
- const message = error instanceof Error ? error.message : String(error)
184
- throw new Error(`${stepLabel} failed for ${url}: ${message}`)
185
- }
186
-
187
- if (!res.ok) {
188
- const text = await res.text().catch(() => '')
189
- throw new Error(`${stepLabel} failed (${res.status}) for ${url}: ${text || res.statusText}`)
190
- }
191
-
192
- try {
193
- return await res.json()
194
- } catch (error) {
195
- const message = error instanceof Error ? error.message : String(error)
196
- throw new Error(`${stepLabel} returned invalid JSON for ${url}: ${message}`)
197
- }
198
- }
199
-
200
301
  async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
201
302
  const resolvedCreds = expandHome(credentialsFile)
202
303
  if (existsSync(resolvedCreds)) {
@@ -204,30 +305,33 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
204
305
  }
205
306
 
206
307
  const base = normalizeBaseUrl(aampHost)
207
- const registerUrl = `${base}/api/nodes/self-register`
208
- const registerData = await fetchJson(
209
- registerUrl,
210
- {
211
- method: 'POST',
212
- headers: { 'Content-Type': 'application/json' },
213
- body: JSON.stringify({
214
- slug,
215
- description: 'OpenClaw AAMP agent node',
216
- }),
217
- },
218
- 'AAMP self-register',
219
- )
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()
220
323
  const code = registerData?.registrationCode
221
324
  if (!code) {
222
325
  throw new Error('AAMP self-register succeeded but no registrationCode was returned')
223
326
  }
224
327
 
225
- const credUrl = `${base}/api/nodes/credentials?code=${encodeURIComponent(code)}`
226
- const credData = await fetchJson(
227
- credUrl,
228
- undefined,
229
- 'AAMP credential exchange',
230
- )
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()
231
335
  const identity = {
232
336
  email: credData?.email,
233
337
  jmapToken: credData?.jmap?.token,
@@ -269,6 +373,7 @@ async function runInit() {
269
373
  let senderPolicies = previousConfig?.senderPolicies
270
374
  let slug = previousSlug
271
375
  let reuseExistingConfig = Boolean(previousConfig)
376
+ let includeCodingBaseline = false
272
377
 
273
378
  if (input.isTTY) {
274
379
  const rl = createInterface({ input, output })
@@ -310,6 +415,28 @@ async function runInit() {
310
415
  senderPolicies = undefined
311
416
  }
312
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
+ }
313
440
  } finally {
314
441
  rl.close()
315
442
  }
@@ -333,11 +460,14 @@ async function runInit() {
333
460
  output.write('\nInstalling OpenClaw plugin files...\n')
334
461
  const extensionDir = installPluginFiles(previousCredentialsFile)
335
462
 
463
+ const toolPolicyPlan = planToolPolicyUpdate(existing?.tools, { includeCodingBaseline })
336
464
  const next = ensurePluginConfig(existing, {
337
465
  aampHost,
338
466
  slug,
339
467
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
340
468
  ...(senderPolicies ? { senderPolicies } : {}),
469
+ }, {
470
+ includeCodingBaseline,
341
471
  })
342
472
 
343
473
  writeJsonFile(configPath, next)
@@ -361,6 +491,8 @@ async function runInit() {
361
491
  ` aampHost: ${aampHost}`,
362
492
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
363
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'}`,
364
496
  identityResult.created
365
497
  ? ` mailbox: ${identityResult.email} (registered and saved to ${identityResult.credentialsPath})`
366
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
  }