aamp-openclaw-plugin 0.1.26 → 0.1.28
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 +14 -0
- package/bin/aamp-openclaw-plugin.mjs +39 -25
- package/dist/file-store.js +37 -3
- package/dist/file-store.js.map +2 -2
- package/dist/index.js +695 -91
- package/dist/index.js.map +3 -3
- package/package.json +5 -3
- package/skills/SKILL.md +36 -32
package/README.md
CHANGED
|
@@ -57,3 +57,17 @@ npm run build
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
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.
|
|
60
|
+
|
|
61
|
+
The plugin also understands:
|
|
62
|
+
|
|
63
|
+
- dispatch priority via `X-AAMP-Priority`
|
|
64
|
+
- dispatch expiry via `X-AAMP-Expires-At`
|
|
65
|
+
- sender-side cancellation via `task.cancel`
|
|
66
|
+
|
|
67
|
+
When multiple tasks are pending locally, the plugin schedules them in this order:
|
|
68
|
+
|
|
69
|
+
1. `urgent`
|
|
70
|
+
2. `high`
|
|
71
|
+
3. `normal`
|
|
72
|
+
|
|
73
|
+
Within the same priority, tasks are processed FIFO by receive time. On startup, the plugin reconciles recent mailbox history so that still-valid tasks can be recovered after the agent was offline.
|
|
@@ -43,19 +43,19 @@ const AAMP_PLUGIN_TOOL_ALLOWLIST = [
|
|
|
43
43
|
'aamp_download_attachment',
|
|
44
44
|
]
|
|
45
45
|
|
|
46
|
-
function resolveOpenClawHome() {
|
|
46
|
+
export function resolveOpenClawHome() {
|
|
47
47
|
return process.env.OPENCLAW_HOME?.trim() || join(homedir(), '.openclaw')
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function resolveOpenClawConfigPath() {
|
|
50
|
+
export function resolveOpenClawConfigPath() {
|
|
51
51
|
return join(resolveOpenClawHome(), 'openclaw.json')
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function resolveExtensionDir() {
|
|
54
|
+
export function resolveExtensionDir() {
|
|
55
55
|
return join(resolveOpenClawHome(), 'extensions', PLUGIN_ID)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function expandHome(pathValue) {
|
|
58
|
+
export function expandHome(pathValue) {
|
|
59
59
|
if (!pathValue) return pathValue
|
|
60
60
|
if (pathValue === '~') return homedir()
|
|
61
61
|
if (pathValue.startsWith('~/')) return join(homedir(), pathValue.slice(2))
|
|
@@ -155,7 +155,7 @@ function stripTrailingCommas(text) {
|
|
|
155
155
|
return result
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function parseJsonConfig(raw, path) {
|
|
158
|
+
export function parseJsonConfig(raw, path) {
|
|
159
159
|
const normalized = raw.replace(/^\uFEFF/, '')
|
|
160
160
|
const sanitized = stripTrailingCommas(stripJsonComments(normalized))
|
|
161
161
|
try {
|
|
@@ -166,22 +166,22 @@ function parseJsonConfig(raw, path) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
function readJsonFile(path) {
|
|
169
|
+
export function readJsonFile(path) {
|
|
170
170
|
if (!existsSync(path)) return null
|
|
171
171
|
return parseJsonConfig(readFileSync(path, 'utf-8'), path)
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
function writeJsonFile(path, value) {
|
|
174
|
+
export function writeJsonFile(path, value) {
|
|
175
175
|
mkdirSync(dirname(path), { recursive: true })
|
|
176
176
|
writeFileSync(path, JSON.stringify(value, null, 2) + '\n', 'utf-8')
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
function normalizeBaseUrl(url) {
|
|
179
|
+
export function normalizeBaseUrl(url) {
|
|
180
180
|
if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
|
|
181
181
|
return `https://${url.replace(/\/$/, '')}`
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
184
|
+
export function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
185
185
|
const next = config && typeof config === 'object' ? structuredClone(config) : {}
|
|
186
186
|
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
187
187
|
if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
|
|
@@ -239,7 +239,7 @@ function ensurePluginInstallRecord(config, installRecord) {
|
|
|
239
239
|
return next
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
function ensureAampToolAllowlist(toolsConfig, options = {}) {
|
|
242
|
+
export function ensureAampToolAllowlist(toolsConfig, options = {}) {
|
|
243
243
|
const next = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
|
|
244
244
|
const existingAllow = Array.isArray(next.allow) ? next.allow.filter((value) => typeof value === 'string' && value.trim()) : []
|
|
245
245
|
const includeCodingBaseline = options.includeCodingBaseline === true
|
|
@@ -255,7 +255,7 @@ function ensureAampToolAllowlist(toolsConfig, options = {}) {
|
|
|
255
255
|
return next
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
function planToolPolicyUpdate(toolsConfig, options = {}) {
|
|
258
|
+
export function planToolPolicyUpdate(toolsConfig, options = {}) {
|
|
259
259
|
const current = toolsConfig && typeof toolsConfig === 'object' ? structuredClone(toolsConfig) : {}
|
|
260
260
|
const existingAllow = Array.isArray(current.allow) ? current.allow.filter((value) => typeof value === 'string' && value.trim()) : []
|
|
261
261
|
const includeCodingBaseline = options.includeCodingBaseline === true
|
|
@@ -293,7 +293,7 @@ function currentToolPolicySummary(plan) {
|
|
|
293
293
|
return lines.join('\n')
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
function parseDispatchContextRules(raw) {
|
|
296
|
+
export function parseDispatchContextRules(raw) {
|
|
297
297
|
const trimmed = raw.trim()
|
|
298
298
|
if (!trimmed) return undefined
|
|
299
299
|
const rules = {}
|
|
@@ -359,7 +359,7 @@ function ensureBuiltArtifacts(packageRoot) {
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
362
|
+
export function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
363
363
|
const extensionDir = resolveExtensionDir()
|
|
364
364
|
const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
|
|
365
365
|
ensureBuiltArtifacts(packageRoot)
|
|
@@ -408,7 +408,7 @@ function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
|
408
408
|
return { extensionDir, packageJson, packageRoot }
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
function restartGateway() {
|
|
411
|
+
export function restartGateway() {
|
|
412
412
|
const result = spawnSync('openclaw', ['gateway', 'restart'], {
|
|
413
413
|
encoding: 'utf-8',
|
|
414
414
|
})
|
|
@@ -433,7 +433,7 @@ function restartGateway() {
|
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
436
|
+
export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
437
437
|
const resolvedCreds = expandHome(credentialsFile)
|
|
438
438
|
if (existsSync(resolvedCreds)) {
|
|
439
439
|
const cachedIdentity = readJsonFile(resolvedCreds)
|
|
@@ -445,7 +445,19 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
const base = normalizeBaseUrl(aampHost)
|
|
448
|
-
const
|
|
448
|
+
const discoveryRes = await fetch(`${base}/.well-known/aamp`)
|
|
449
|
+
if (!discoveryRes.ok) {
|
|
450
|
+
const text = await discoveryRes.text().catch(() => '')
|
|
451
|
+
throw new Error(`AAMP discovery failed (${discoveryRes.status}): ${text || discoveryRes.statusText}`)
|
|
452
|
+
}
|
|
453
|
+
const discovery = await discoveryRes.json()
|
|
454
|
+
const apiUrl = discovery?.api?.url
|
|
455
|
+
if (!apiUrl) {
|
|
456
|
+
throw new Error('AAMP discovery did not return api.url')
|
|
457
|
+
}
|
|
458
|
+
const apiBase = new URL(apiUrl, `${base}/`).toString()
|
|
459
|
+
|
|
460
|
+
const registerRes = await fetch(`${apiBase}?action=aamp.mailbox.register`, {
|
|
449
461
|
method: 'POST',
|
|
450
462
|
headers: { 'Content-Type': 'application/json' },
|
|
451
463
|
body: JSON.stringify({
|
|
@@ -465,7 +477,7 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
|
465
477
|
throw new Error('AAMP self-register succeeded but no registrationCode was returned')
|
|
466
478
|
}
|
|
467
479
|
|
|
468
|
-
const credRes = await fetch(`${
|
|
480
|
+
const credRes = await fetch(`${apiBase}?action=aamp.mailbox.credentials&code=${encodeURIComponent(code)}`)
|
|
469
481
|
if (!credRes.ok) {
|
|
470
482
|
const text = await credRes.text().catch(() => '')
|
|
471
483
|
throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
|
|
@@ -474,11 +486,11 @@ async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
|
474
486
|
const credData = await credRes.json()
|
|
475
487
|
const identity = {
|
|
476
488
|
email: credData?.email,
|
|
477
|
-
|
|
489
|
+
mailboxToken: credData?.mailbox?.token ?? credData?.jmap?.token,
|
|
478
490
|
smtpPassword: credData?.smtp?.password,
|
|
479
491
|
}
|
|
480
492
|
|
|
481
|
-
if (!identity.email || !identity.
|
|
493
|
+
if (!identity.email || !identity.mailboxToken || !identity.smtpPassword) {
|
|
482
494
|
throw new Error('AAMP credential exchange returned an incomplete identity payload')
|
|
483
495
|
}
|
|
484
496
|
|
|
@@ -499,7 +511,7 @@ function printHelp() {
|
|
|
499
511
|
)
|
|
500
512
|
}
|
|
501
513
|
|
|
502
|
-
async function runInit() {
|
|
514
|
+
export async function runInit() {
|
|
503
515
|
const configPath = resolveOpenClawConfigPath()
|
|
504
516
|
const existing = readJsonFile(configPath)
|
|
505
517
|
const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
|
|
@@ -669,7 +681,7 @@ async function runInit() {
|
|
|
669
681
|
)
|
|
670
682
|
}
|
|
671
683
|
|
|
672
|
-
async function main() {
|
|
684
|
+
export async function main() {
|
|
673
685
|
const command = process.argv[2] || 'help'
|
|
674
686
|
|
|
675
687
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
@@ -687,7 +699,9 @@ async function main() {
|
|
|
687
699
|
process.exitCode = 1
|
|
688
700
|
}
|
|
689
701
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
702
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
703
|
+
main().catch((err) => {
|
|
704
|
+
stderr.write(`${err instanceof Error ? err.message : String(err)}\n`)
|
|
705
|
+
process.exit(1)
|
|
706
|
+
})
|
|
707
|
+
}
|
package/dist/file-store.js
CHANGED
|
@@ -5,15 +5,22 @@ import { homedir } from "node:os";
|
|
|
5
5
|
function defaultCredentialsPath() {
|
|
6
6
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
7
7
|
}
|
|
8
|
+
function defaultTaskStatePath() {
|
|
9
|
+
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
|
|
10
|
+
}
|
|
8
11
|
function loadCachedIdentity(file) {
|
|
9
12
|
const resolved = file ?? defaultCredentialsPath();
|
|
10
13
|
if (!existsSync(resolved))
|
|
11
14
|
return null;
|
|
12
15
|
try {
|
|
13
16
|
const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
14
|
-
if (!parsed.email || !parsed.
|
|
17
|
+
if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword)
|
|
15
18
|
return null;
|
|
16
|
-
return
|
|
19
|
+
return {
|
|
20
|
+
email: parsed.email,
|
|
21
|
+
mailboxToken: parsed.mailboxToken,
|
|
22
|
+
smtpPassword: parsed.smtpPassword
|
|
23
|
+
};
|
|
17
24
|
} catch {
|
|
18
25
|
return null;
|
|
19
26
|
}
|
|
@@ -21,7 +28,31 @@ function loadCachedIdentity(file) {
|
|
|
21
28
|
function saveCachedIdentity(identity, file) {
|
|
22
29
|
const resolved = file ?? defaultCredentialsPath();
|
|
23
30
|
mkdirSync(dirname(resolved), { recursive: true });
|
|
24
|
-
writeFileSync(resolved, JSON.stringify(
|
|
31
|
+
writeFileSync(resolved, JSON.stringify({
|
|
32
|
+
email: identity.email,
|
|
33
|
+
mailboxToken: identity.mailboxToken,
|
|
34
|
+
smtpPassword: identity.smtpPassword
|
|
35
|
+
}, null, 2), "utf-8");
|
|
36
|
+
}
|
|
37
|
+
function loadTaskState(file) {
|
|
38
|
+
const resolved = file ?? defaultTaskStatePath();
|
|
39
|
+
if (!existsSync(resolved))
|
|
40
|
+
return { terminalTaskIds: [] };
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
43
|
+
return {
|
|
44
|
+
terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : []
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return { terminalTaskIds: [] };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function saveTaskState(state, file) {
|
|
51
|
+
const resolved = file ?? defaultTaskStatePath();
|
|
52
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
53
|
+
writeFileSync(resolved, JSON.stringify({
|
|
54
|
+
terminalTaskIds: state.terminalTaskIds ?? []
|
|
55
|
+
}, null, 2), "utf-8");
|
|
25
56
|
}
|
|
26
57
|
function ensureDir(dir) {
|
|
27
58
|
mkdirSync(dir, { recursive: true });
|
|
@@ -35,10 +66,13 @@ function writeBinaryFile(path, content) {
|
|
|
35
66
|
}
|
|
36
67
|
export {
|
|
37
68
|
defaultCredentialsPath,
|
|
69
|
+
defaultTaskStatePath,
|
|
38
70
|
ensureDir,
|
|
39
71
|
loadCachedIdentity,
|
|
72
|
+
loadTaskState,
|
|
40
73
|
readBinaryFile,
|
|
41
74
|
saveCachedIdentity,
|
|
75
|
+
saveTaskState,
|
|
42
76
|
writeBinaryFile
|
|
43
77
|
};
|
|
44
78
|
//# sourceMappingURL=file-store.js.map
|
package/dist/file-store.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
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
|
|
5
|
-
"mappings": ";AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;
|
|
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 mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: string[]\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function defaultTaskStatePath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.task-state.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.mailboxToken || !parsed.smtpPassword) return null\n return {\n email: parsed.email,\n mailboxToken: parsed.mailboxToken,\n smtpPassword: parsed.smtpPassword,\n }\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({\n email: identity.email,\n mailboxToken: identity.mailboxToken,\n smtpPassword: identity.smtpPassword,\n }, null, 2), 'utf-8')\n}\n\nexport function loadTaskState(file?: string): CachedTaskState {\n const resolved = file ?? defaultTaskStatePath()\n if (!existsSync(resolved)) return { terminalTaskIds: [] }\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as CachedTaskState\n return {\n terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : [],\n }\n } catch {\n return { terminalTaskIds: [] }\n }\n}\n\nexport function saveTaskState(state: CachedTaskState, file?: string): void {\n const resolved = file ?? defaultTaskStatePath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n terminalTaskIds: state.terminalTaskIds ?? [],\n }, 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;AAYjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,uBAA+B;AAC7C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,kBAAkB;AAC9F;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,gBAAgB,CAAC,OAAO;AAAc,aAAO;AAC1E,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,IACvB;AAAA,EACF,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;AAAA,IACrC,OAAO,SAAS;AAAA,IAChB,cAAc,SAAS;AAAA,IACvB,cAAc,SAAS;AAAA,EACzB,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;AAEO,SAAS,cAAc,MAAgC;AAC5D,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO,EAAE,iBAAiB,CAAC,EAAE;AACxD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,WAAO;AAAA,MACL,iBAAiB,MAAM,QAAQ,OAAO,eAAe,IAAI,OAAO,gBAAgB,OAAO,OAAO,IAAI,CAAC;AAAA,IACrG;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,iBAAiB,CAAC,EAAE;AAAA,EAC/B;AACF;AAEO,SAAS,cAAc,OAAwB,MAAqB;AACzE,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU;AAAA,IACrC,iBAAiB,MAAM,mBAAmB,CAAC;AAAA,EAC7C,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;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
|
}
|