@agentvault/agentvault 0.9.5 → 0.9.7
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/dist/__tests__/crypto-helpers.test.d.ts +2 -0
- package/dist/__tests__/crypto-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/functional.test.d.ts +21 -0
- package/dist/__tests__/functional.test.d.ts.map +1 -0
- package/dist/__tests__/multi-session.test.d.ts +2 -0
- package/dist/__tests__/multi-session.test.d.ts.map +1 -0
- package/dist/__tests__/state.test.d.ts +2 -0
- package/dist/__tests__/state.test.d.ts.map +1 -0
- package/dist/__tests__/transport.test.d.ts +2 -0
- package/dist/__tests__/transport.test.d.ts.map +1 -0
- package/dist/channel.d.ts +6 -2
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +2257 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.js +336 -91
- package/dist/cli.js.map +4 -4
- package/dist/crypto-helpers.js +4 -0
- package/dist/crypto-helpers.js.map +1 -0
- package/dist/index.js +313 -91
- package/dist/index.js.map +4 -4
- package/dist/openclaw-entry.js +1 -1
- package/dist/openclaw-entry.js.map +2 -2
- package/dist/openclaw-plugin.js +222 -0
- package/dist/openclaw-plugin.js.map +1 -0
- package/dist/setup.js +329 -0
- package/dist/setup.js.map +1 -0
- package/dist/state.js +61 -0
- package/dist/state.js.map +1 -0
- package/dist/transport.js +43 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
package/dist/openclaw-entry.js
CHANGED
|
@@ -108,7 +108,7 @@ async function handleInbound(params) {
|
|
|
108
108
|
if (isRoomMessage) {
|
|
109
109
|
await channel.sendToRoom(metadata.roomId, text);
|
|
110
110
|
} else {
|
|
111
|
-
await channel.send(text);
|
|
111
|
+
await channel.send(text, { topicId: metadata.topicId });
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
onError: (err, info) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/openclaw-entry.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * OpenClaw channel plugin entry point.\n *\n * Intentionally thin \u2014 no heavy imports (libsodium etc.) at module load time.\n * SecureChannel is dynamically imported inside gateway.startAccount (already async)\n * so libsodium's top-level await never runs during plugin registration.\n *\n * Loaded by OpenClaw via the `openclaw.extensions` field in package.json.\n */\n\nimport { resolve } from \"node:path\";\n\n// --- Runtime and active channels (set during register) ---\nlet _ocRuntime: any = null;\nconst _channels = new Map<string, any>();\nconst _messageQueue: Array<() => Promise<void>> = [];\n\nfunction _setRuntime(rt: any) {\n _ocRuntime = rt;\n // Flush any messages that arrived before runtime was ready\n if (_messageQueue.length > 0) {\n const pending = _messageQueue.splice(0);\n for (const fn of pending) {\n fn().catch(() => {});\n }\n }\n}\n\n// --- Channel config helpers ---\n\nfunction listAccountIds(cfg: any): string[] {\n return cfg?.channels?.agentvault?.dataDir ? [\"default\"] : [];\n}\n\nfunction resolveAccount(cfg: any, accountId?: string) {\n const av = cfg?.channels?.agentvault ?? {};\n return {\n accountId: accountId ?? \"default\",\n dataDir: av.dataDir ?? \"~/.openclaw/agentvault\",\n apiUrl: av.apiUrl ?? \"https://api.agentvault.chat\",\n agentName: av.agentName ?? \"OpenClaw Agent\",\n httpPort: av.httpPort ?? 18790,\n configured: Boolean(av.dataDir),\n };\n}\n\n// --- Inbound message dispatch ---\n\nasync function handleInbound(params: {\n plaintext: string;\n metadata: any;\n channel: any;\n account: any;\n cfg: any;\n}): Promise<void> {\n const { plaintext, metadata, channel, account, cfg } = params;\n\n // Send typing indicator to owner (non-critical, best-effort)\n try { channel.sendTyping(); } catch { /* ignore */ }\n\n const core = _ocRuntime;\n const isRoomMessage = Boolean(metadata.roomId);\n\n const route = core.channel.routing.resolveAgentRoute({\n cfg,\n channel: \"agentvault\",\n accountId: account.accountId,\n peer: { kind: \"direct\", id: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\" },\n });\n\n const storePath = core.channel.session.resolveStorePath(cfg?.session?.store, {\n agentId: route.agentId,\n });\n\n const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);\n const previousTimestamp = core.channel.session.readSessionUpdatedAt({\n storePath,\n sessionKey: route.sessionKey,\n });\n\n const body = core.channel.reply.formatAgentEnvelope({\n channel: \"AgentVault\",\n from: isRoomMessage ? \"Room\" : \"Owner\",\n timestamp: new Date(metadata.timestamp).getTime(),\n previousTimestamp,\n envelope: envelopeOptions,\n body: plaintext,\n });\n\n // Build attachment fields for the context payload\n const attachmentFields: Record<string, any> = {};\n if (metadata.attachment) {\n attachmentFields.AttachmentPath = metadata.attachment.filePath;\n attachmentFields.AttachmentFilename = metadata.attachment.filename;\n attachmentFields.AttachmentMime = metadata.attachment.mime;\n\n // For images: include as MediaUrl so the LLM can see the visual content\n if (metadata.attachment.base64) {\n attachmentFields.MediaUrl = metadata.attachment.base64;\n attachmentFields.NumMedia = \"1\";\n }\n\n // For text files: content is already inlined in plaintext body\n }\n\n const ctxPayload = core.channel.reply.finalizeInboundContext({\n Body: body,\n RawBody: plaintext,\n CommandBody: plaintext,\n From: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\",\n To: `agentvault:agent:${account.accountId}`,\n SessionKey: route.sessionKey,\n AccountId: account.accountId,\n ChatType: isRoomMessage ? \"room\" : \"direct\",\n ConversationLabel: isRoomMessage ? \"AgentVault Room\" : \"AgentVault\",\n SenderName: isRoomMessage ? \"Room\" : \"Owner\",\n SenderId: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\",\n Provider: \"agentvault\",\n Surface: \"agentvault\",\n MessageSid: metadata.messageId,\n Timestamp: new Date(metadata.timestamp).getTime(),\n OriginatingChannel: \"agentvault\",\n OriginatingTo: `agentvault:agent:${account.accountId}`,\n CommandAuthorized: true,\n ...attachmentFields,\n });\n\n await core.channel.session.recordInboundSession({\n storePath,\n sessionKey: ctxPayload.SessionKey ?? route.sessionKey,\n ctx: ctxPayload,\n onRecordError: (err: Error) => {\n core.error?.(`[AgentVault] session record failed: ${String(err)}`);\n },\n });\n\n await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: ctxPayload,\n cfg,\n dispatcherOptions: {\n deliver: async (payload: { text?: string; kind?: string }) => {\n // Filter out thinking/reasoning blocks \u2014 only deliver actual responses\n if (payload.kind === \"thinking\" || payload.kind === \"reasoning\") return;\n const text = (payload.text ?? \"\").trim();\n if (!text) return;\n // Heuristic: skip blocks that look like leaked chain-of-thought\n if (/^(Reasoning|Thinking|Let me think|I need to|Let me check):/i.test(text)) return;\n\n // Route reply: room messages go back to the room, 1:1 goes to owner\n if (isRoomMessage) {\n await channel.sendToRoom(metadata.roomId, text);\n } else {\n await channel.send(text);\n }\n },\n onError: (err: Error, info?: { kind?: string }) => {\n core.error?.(`[AgentVault] ${info?.kind ?? \"reply\"} error: ${String(err)}`);\n },\n },\n replyOptions: {},\n });\n}\n\n// --- Channel plugin definition ---\n\nconst agentVaultPlugin = {\n id: \"agentvault\",\n meta: {\n id: \"agentvault\",\n label: \"AgentVault\",\n selectionLabel: \"AgentVault (E2E Encrypted)\",\n docsPath: \"https://agentvault.chat/docs\",\n blurb: \"Zero-knowledge, end-to-end encrypted messaging between owners and their AI agents.\",\n aliases: [\"av\", \"agent-vault\"],\n },\n capabilities: { chatTypes: [\"direct\"] },\n config: { listAccountIds, resolveAccount },\n\n gateway: {\n startAccount: async (ctx: any) => {\n const { account, cfg, log, abortSignal } = ctx;\n\n if (!account.configured) {\n throw new Error(\n \"AgentVault channel not configured. Run: npx @agentvault/agentvault setup --token=av_tok_...\\nThen restart OpenClaw.\",\n );\n }\n\n const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? \"~\"));\n log?.info(`[AgentVault] starting (dataDir=${dataDir})`);\n\n // Lazy import \u2014 defers libsodium initialization until channel actually starts\n const { SecureChannel } = await import(\"./index.js\");\n\n // startAccount must STAY PENDING while the channel is running.\n // Resolving signals \"channel stopped\" to the gateway health monitor,\n // which triggers auto-restart. We block here until the abortSignal\n // fires (gateway shutdown / config reload), then clean up.\n await new Promise<void>((resolve, reject) => {\n let channel: any;\n\n const onAbort = async () => {\n await channel?.stop();\n _channels.delete(account.accountId);\n resolve();\n };\n\n abortSignal?.addEventListener(\"abort\", () => void onAbort());\n\n // Lazy import \u2014 defers libsodium initialization until channel starts\n import(\"./index.js\").then(({ SecureChannel }) => {\n channel = new SecureChannel({\n inviteToken: \"\",\n dataDir,\n apiUrl: account.apiUrl,\n agentName: account.agentName,\n onMessage: async (plaintext: string, metadata: any) => {\n if (!_ocRuntime) {\n log?.info(\"[AgentVault] runtime not ready, queuing message\");\n _messageQueue.push(async () => {\n await handleInbound({ plaintext, metadata, channel, account, cfg });\n });\n return;\n }\n try {\n await handleInbound({ plaintext, metadata, channel, account, cfg });\n } catch (err) {\n log?.info(`[AgentVault] inbound error: ${String(err)}`);\n }\n },\n onStateChange: (state: string) => {\n log?.info(`[AgentVault] \u2192 ${state}`);\n // \"error\" is a permanent failure \u2014 reject so gateway can restart\n if (state === \"error\") reject(new Error(\"AgentVault channel permanent error\"));\n // All other states (connecting/ready/disconnected) are handled\n // internally by SecureChannel's reconnect logic \u2014 do NOT resolve.\n },\n });\n\n _channels.set(account.accountId, channel);\n\n // Prevent unhandled \"error\" events from crashing the process.\n // Without this handler, Node.js EventEmitter throws on emit(\"error\").\n channel.on(\"error\", (err: any) => {\n log?.info(`[AgentVault] channel error (non-fatal): ${String(err)}`);\n });\n\n // Start local HTTP server for proactive sends\n const httpPort = account.httpPort ?? 18790;\n channel.on(\"ready\", () => {\n channel.startHttpServer(httpPort);\n log?.info(`[AgentVault] HTTP send server listening on http://127.0.0.1:${httpPort}`);\n });\n\n channel.start().catch(reject);\n }).catch(reject);\n });\n\n return { stop: async () => {} }; // Channel already stopped via abortSignal by this point\n },\n },\n\n outbound: {\n deliveryMode: \"direct\" as const,\n\n // Register valid send targets so OpenClaw's `message` tool can route\n // proactive (agent-initiated) sends \u2014 not just replies to inbound messages.\n targets: [\n {\n id: \"owner\",\n label: \"AgentVault Owner\",\n accountId: \"default\",\n },\n {\n id: \"default\",\n label: \"AgentVault Owner (default)\",\n accountId: \"default\",\n },\n ],\n\n sendText: async ({ to, text, accountId }: { to: string; text: string; accountId?: string }) => {\n const resolvedId = accountId ?? \"default\";\n const ch = _channels.get(resolvedId);\n if (!ch) return { ok: false, error: \"AgentVault channel not connected\" };\n try {\n const wasReady = ch.state === \"ready\";\n await ch.send(text);\n return { ok: true, queued: !wasReady };\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n },\n\n sendMedia: async ({ to, text, mediaUrl, accountId }: { to: string; text?: string; mediaUrl: string; accountId?: string }) => {\n const resolvedId = accountId ?? \"default\";\n const ch = _channels.get(resolvedId);\n if (!ch) return { ok: false, error: \"AgentVault channel not connected\" };\n try {\n // For now, send media URL as text \u2014 AgentVault handles attachments separately\n const message = text ? `${text}\\n${mediaUrl}` : mediaUrl;\n const wasReady = ch.state === \"ready\";\n await ch.send(message);\n return { ok: true, queued: !wasReady };\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n },\n },\n};\n\n// --- Plugin export ---\n\nexport default {\n id: \"agentvault\",\n name: \"AgentVault\",\n description: \"End-to-end encrypted, zero-knowledge messaging between AI agent owners and their agents.\",\n register(api: any) {\n _setRuntime(api.runtime);\n api.registerChannel({ plugin: agentVaultPlugin });\n },\n};\n"],
|
|
5
|
-
"mappings": ";AAUA,SAAS,eAAe;AAGxB,IAAI,aAAkB;AACtB,IAAM,YAAY,oBAAI,IAAiB;AACvC,IAAM,gBAA4C,CAAC;AAEnD,SAAS,YAAY,IAAS;AAC5B,eAAa;AAEb,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,UAAU,cAAc,OAAO,CAAC;AACtC,eAAW,MAAM,SAAS;AACxB,SAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrB;AAAA,EACF;AACF;AAIA,SAAS,eAAe,KAAoB;AAC1C,SAAO,KAAK,UAAU,YAAY,UAAU,CAAC,SAAS,IAAI,CAAC;AAC7D;AAEA,SAAS,eAAe,KAAU,WAAoB;AACpD,QAAM,KAAK,KAAK,UAAU,cAAc,CAAC;AACzC,SAAO;AAAA,IACL,WAAW,aAAa;AAAA,IACxB,SAAS,GAAG,WAAW;AAAA,IACvB,QAAQ,GAAG,UAAU;AAAA,IACrB,WAAW,GAAG,aAAa;AAAA,IAC3B,UAAU,GAAG,YAAY;AAAA,IACzB,YAAY,QAAQ,GAAG,OAAO;AAAA,EAChC;AACF;AAIA,eAAe,cAAc,QAMX;AAChB,QAAM,EAAE,WAAW,UAAU,SAAS,SAAS,IAAI,IAAI;AAGvD,MAAI;AAAE,YAAQ,WAAW;AAAA,EAAG,QAAQ;AAAA,EAAe;AAEnD,QAAM,OAAO;AACb,QAAM,gBAAgB,QAAQ,SAAS,MAAM;AAE7C,QAAM,QAAQ,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,IACnD;AAAA,IACA,SAAS;AAAA,IACT,WAAW,QAAQ;AAAA,IACnB,MAAM,EAAE,MAAM,UAAU,IAAI,gBAAgB,mBAAmB,SAAS,MAAM,KAAK,mBAAmB;AAAA,EACxG,CAAC;AAED,QAAM,YAAY,KAAK,QAAQ,QAAQ,iBAAiB,KAAK,SAAS,OAAO;AAAA,IAC3E,SAAS,MAAM;AAAA,EACjB,CAAC;AAED,QAAM,kBAAkB,KAAK,QAAQ,MAAM,6BAA6B,GAAG;AAC3E,QAAM,oBAAoB,KAAK,QAAQ,QAAQ,qBAAqB;AAAA,IAClE;AAAA,IACA,YAAY,MAAM;AAAA,EACpB,CAAC;AAED,QAAM,OAAO,KAAK,QAAQ,MAAM,oBAAoB;AAAA,IAClD,SAAS;AAAA,IACT,MAAM,gBAAgB,SAAS;AAAA,IAC/B,WAAW,IAAI,KAAK,SAAS,SAAS,EAAE,QAAQ;AAAA,IAChD;AAAA,IACA,UAAU;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AAGD,QAAM,mBAAwC,CAAC;AAC/C,MAAI,SAAS,YAAY;AACvB,qBAAiB,iBAAiB,SAAS,WAAW;AACtD,qBAAiB,qBAAqB,SAAS,WAAW;AAC1D,qBAAiB,iBAAiB,SAAS,WAAW;AAGtD,QAAI,SAAS,WAAW,QAAQ;AAC9B,uBAAiB,WAAW,SAAS,WAAW;AAChD,uBAAiB,WAAW;AAAA,IAC9B;AAAA,EAGF;AAEA,QAAM,aAAa,KAAK,QAAQ,MAAM,uBAAuB;AAAA,IAC3D,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAM,gBAAgB,mBAAmB,SAAS,MAAM,KAAK;AAAA,IAC7D,IAAI,oBAAoB,QAAQ,SAAS;AAAA,IACzC,YAAY,MAAM;AAAA,IAClB,WAAW,QAAQ;AAAA,IACnB,UAAU,gBAAgB,SAAS;AAAA,IACnC,mBAAmB,gBAAgB,oBAAoB;AAAA,IACvD,YAAY,gBAAgB,SAAS;AAAA,IACrC,UAAU,gBAAgB,mBAAmB,SAAS,MAAM,KAAK;AAAA,IACjE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,SAAS;AAAA,IACrB,WAAW,IAAI,KAAK,SAAS,SAAS,EAAE,QAAQ;AAAA,IAChD,oBAAoB;AAAA,IACpB,eAAe,oBAAoB,QAAQ,SAAS;AAAA,IACpD,mBAAmB;AAAA,IACnB,GAAG;AAAA,EACL,CAAC;AAED,QAAM,KAAK,QAAQ,QAAQ,qBAAqB;AAAA,IAC9C;AAAA,IACA,YAAY,WAAW,cAAc,MAAM;AAAA,IAC3C,KAAK;AAAA,IACL,eAAe,CAAC,QAAe;AAC7B,WAAK,QAAQ,uCAAuC,OAAO,GAAG,CAAC,EAAE;AAAA,IACnE;AAAA,EACF,CAAC;AAED,QAAM,KAAK,QAAQ,MAAM,yCAAyC;AAAA,IAChE,KAAK;AAAA,IACL;AAAA,IACA,mBAAmB;AAAA,MACjB,SAAS,OAAO,YAA8C;AAE5D,YAAI,QAAQ,SAAS,cAAc,QAAQ,SAAS,YAAa;AACjE,cAAM,QAAQ,QAAQ,QAAQ,IAAI,KAAK;AACvC,YAAI,CAAC,KAAM;AAEX,YAAI,8DAA8D,KAAK,IAAI,EAAG;AAG9E,YAAI,eAAe;AACjB,gBAAM,QAAQ,WAAW,SAAS,QAAQ,IAAI;AAAA,QAChD,OAAO;AACL,gBAAM,QAAQ,KAAK,
|
|
4
|
+
"sourcesContent": ["/**\n * OpenClaw channel plugin entry point.\n *\n * Intentionally thin \u2014 no heavy imports (libsodium etc.) at module load time.\n * SecureChannel is dynamically imported inside gateway.startAccount (already async)\n * so libsodium's top-level await never runs during plugin registration.\n *\n * Loaded by OpenClaw via the `openclaw.extensions` field in package.json.\n */\n\nimport { resolve } from \"node:path\";\n\n// --- Runtime and active channels (set during register) ---\nlet _ocRuntime: any = null;\nconst _channels = new Map<string, any>();\nconst _messageQueue: Array<() => Promise<void>> = [];\n\nfunction _setRuntime(rt: any) {\n _ocRuntime = rt;\n // Flush any messages that arrived before runtime was ready\n if (_messageQueue.length > 0) {\n const pending = _messageQueue.splice(0);\n for (const fn of pending) {\n fn().catch(() => {});\n }\n }\n}\n\n// --- Channel config helpers ---\n\nfunction listAccountIds(cfg: any): string[] {\n return cfg?.channels?.agentvault?.dataDir ? [\"default\"] : [];\n}\n\nfunction resolveAccount(cfg: any, accountId?: string) {\n const av = cfg?.channels?.agentvault ?? {};\n return {\n accountId: accountId ?? \"default\",\n dataDir: av.dataDir ?? \"~/.openclaw/agentvault\",\n apiUrl: av.apiUrl ?? \"https://api.agentvault.chat\",\n agentName: av.agentName ?? \"OpenClaw Agent\",\n httpPort: av.httpPort ?? 18790,\n configured: Boolean(av.dataDir),\n };\n}\n\n// --- Inbound message dispatch ---\n\nasync function handleInbound(params: {\n plaintext: string;\n metadata: any;\n channel: any;\n account: any;\n cfg: any;\n}): Promise<void> {\n const { plaintext, metadata, channel, account, cfg } = params;\n\n // Send typing indicator to owner (non-critical, best-effort)\n try { channel.sendTyping(); } catch { /* ignore */ }\n\n const core = _ocRuntime;\n const isRoomMessage = Boolean(metadata.roomId);\n\n const route = core.channel.routing.resolveAgentRoute({\n cfg,\n channel: \"agentvault\",\n accountId: account.accountId,\n peer: { kind: \"direct\", id: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\" },\n });\n\n const storePath = core.channel.session.resolveStorePath(cfg?.session?.store, {\n agentId: route.agentId,\n });\n\n const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);\n const previousTimestamp = core.channel.session.readSessionUpdatedAt({\n storePath,\n sessionKey: route.sessionKey,\n });\n\n const body = core.channel.reply.formatAgentEnvelope({\n channel: \"AgentVault\",\n from: isRoomMessage ? \"Room\" : \"Owner\",\n timestamp: new Date(metadata.timestamp).getTime(),\n previousTimestamp,\n envelope: envelopeOptions,\n body: plaintext,\n });\n\n // Build attachment fields for the context payload\n const attachmentFields: Record<string, any> = {};\n if (metadata.attachment) {\n attachmentFields.AttachmentPath = metadata.attachment.filePath;\n attachmentFields.AttachmentFilename = metadata.attachment.filename;\n attachmentFields.AttachmentMime = metadata.attachment.mime;\n\n // For images: include as MediaUrl so the LLM can see the visual content\n if (metadata.attachment.base64) {\n attachmentFields.MediaUrl = metadata.attachment.base64;\n attachmentFields.NumMedia = \"1\";\n }\n\n // For text files: content is already inlined in plaintext body\n }\n\n const ctxPayload = core.channel.reply.finalizeInboundContext({\n Body: body,\n RawBody: plaintext,\n CommandBody: plaintext,\n From: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\",\n To: `agentvault:agent:${account.accountId}`,\n SessionKey: route.sessionKey,\n AccountId: account.accountId,\n ChatType: isRoomMessage ? \"room\" : \"direct\",\n ConversationLabel: isRoomMessage ? \"AgentVault Room\" : \"AgentVault\",\n SenderName: isRoomMessage ? \"Room\" : \"Owner\",\n SenderId: isRoomMessage ? `agentvault:room:${metadata.roomId}` : \"agentvault:owner\",\n Provider: \"agentvault\",\n Surface: \"agentvault\",\n MessageSid: metadata.messageId,\n Timestamp: new Date(metadata.timestamp).getTime(),\n OriginatingChannel: \"agentvault\",\n OriginatingTo: `agentvault:agent:${account.accountId}`,\n CommandAuthorized: true,\n ...attachmentFields,\n });\n\n await core.channel.session.recordInboundSession({\n storePath,\n sessionKey: ctxPayload.SessionKey ?? route.sessionKey,\n ctx: ctxPayload,\n onRecordError: (err: Error) => {\n core.error?.(`[AgentVault] session record failed: ${String(err)}`);\n },\n });\n\n await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: ctxPayload,\n cfg,\n dispatcherOptions: {\n deliver: async (payload: { text?: string; kind?: string }) => {\n // Filter out thinking/reasoning blocks \u2014 only deliver actual responses\n if (payload.kind === \"thinking\" || payload.kind === \"reasoning\") return;\n const text = (payload.text ?? \"\").trim();\n if (!text) return;\n // Heuristic: skip blocks that look like leaked chain-of-thought\n if (/^(Reasoning|Thinking|Let me think|I need to|Let me check):/i.test(text)) return;\n\n // Route reply: room messages go back to the room, 1:1 goes to owner\n if (isRoomMessage) {\n await channel.sendToRoom(metadata.roomId, text);\n } else {\n await channel.send(text, { topicId: metadata.topicId });\n }\n },\n onError: (err: Error, info?: { kind?: string }) => {\n core.error?.(`[AgentVault] ${info?.kind ?? \"reply\"} error: ${String(err)}`);\n },\n },\n replyOptions: {},\n });\n}\n\n// --- Channel plugin definition ---\n\nconst agentVaultPlugin = {\n id: \"agentvault\",\n meta: {\n id: \"agentvault\",\n label: \"AgentVault\",\n selectionLabel: \"AgentVault (E2E Encrypted)\",\n docsPath: \"https://agentvault.chat/docs\",\n blurb: \"Zero-knowledge, end-to-end encrypted messaging between owners and their AI agents.\",\n aliases: [\"av\", \"agent-vault\"],\n },\n capabilities: { chatTypes: [\"direct\"] },\n config: { listAccountIds, resolveAccount },\n\n gateway: {\n startAccount: async (ctx: any) => {\n const { account, cfg, log, abortSignal } = ctx;\n\n if (!account.configured) {\n throw new Error(\n \"AgentVault channel not configured. Run: npx @agentvault/agentvault setup --token=av_tok_...\\nThen restart OpenClaw.\",\n );\n }\n\n const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? \"~\"));\n log?.info(`[AgentVault] starting (dataDir=${dataDir})`);\n\n // Lazy import \u2014 defers libsodium initialization until channel actually starts\n const { SecureChannel } = await import(\"./index.js\");\n\n // startAccount must STAY PENDING while the channel is running.\n // Resolving signals \"channel stopped\" to the gateway health monitor,\n // which triggers auto-restart. We block here until the abortSignal\n // fires (gateway shutdown / config reload), then clean up.\n await new Promise<void>((resolve, reject) => {\n let channel: any;\n\n const onAbort = async () => {\n await channel?.stop();\n _channels.delete(account.accountId);\n resolve();\n };\n\n abortSignal?.addEventListener(\"abort\", () => void onAbort());\n\n // Lazy import \u2014 defers libsodium initialization until channel starts\n import(\"./index.js\").then(({ SecureChannel }) => {\n channel = new SecureChannel({\n inviteToken: \"\",\n dataDir,\n apiUrl: account.apiUrl,\n agentName: account.agentName,\n onMessage: async (plaintext: string, metadata: any) => {\n if (!_ocRuntime) {\n log?.info(\"[AgentVault] runtime not ready, queuing message\");\n _messageQueue.push(async () => {\n await handleInbound({ plaintext, metadata, channel, account, cfg });\n });\n return;\n }\n try {\n await handleInbound({ plaintext, metadata, channel, account, cfg });\n } catch (err) {\n log?.info(`[AgentVault] inbound error: ${String(err)}`);\n }\n },\n onStateChange: (state: string) => {\n log?.info(`[AgentVault] \u2192 ${state}`);\n // \"error\" is a permanent failure \u2014 reject so gateway can restart\n if (state === \"error\") reject(new Error(\"AgentVault channel permanent error\"));\n // All other states (connecting/ready/disconnected) are handled\n // internally by SecureChannel's reconnect logic \u2014 do NOT resolve.\n },\n });\n\n _channels.set(account.accountId, channel);\n\n // Prevent unhandled \"error\" events from crashing the process.\n // Without this handler, Node.js EventEmitter throws on emit(\"error\").\n channel.on(\"error\", (err: any) => {\n log?.info(`[AgentVault] channel error (non-fatal): ${String(err)}`);\n });\n\n // Start local HTTP server for proactive sends\n const httpPort = account.httpPort ?? 18790;\n channel.on(\"ready\", () => {\n channel.startHttpServer(httpPort);\n log?.info(`[AgentVault] HTTP send server listening on http://127.0.0.1:${httpPort}`);\n });\n\n channel.start().catch(reject);\n }).catch(reject);\n });\n\n return { stop: async () => {} }; // Channel already stopped via abortSignal by this point\n },\n },\n\n outbound: {\n deliveryMode: \"direct\" as const,\n\n // Register valid send targets so OpenClaw's `message` tool can route\n // proactive (agent-initiated) sends \u2014 not just replies to inbound messages.\n targets: [\n {\n id: \"owner\",\n label: \"AgentVault Owner\",\n accountId: \"default\",\n },\n {\n id: \"default\",\n label: \"AgentVault Owner (default)\",\n accountId: \"default\",\n },\n ],\n\n sendText: async ({ to, text, accountId }: { to: string; text: string; accountId?: string }) => {\n const resolvedId = accountId ?? \"default\";\n const ch = _channels.get(resolvedId);\n if (!ch) return { ok: false, error: \"AgentVault channel not connected\" };\n try {\n const wasReady = ch.state === \"ready\";\n await ch.send(text);\n return { ok: true, queued: !wasReady };\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n },\n\n sendMedia: async ({ to, text, mediaUrl, accountId }: { to: string; text?: string; mediaUrl: string; accountId?: string }) => {\n const resolvedId = accountId ?? \"default\";\n const ch = _channels.get(resolvedId);\n if (!ch) return { ok: false, error: \"AgentVault channel not connected\" };\n try {\n // For now, send media URL as text \u2014 AgentVault handles attachments separately\n const message = text ? `${text}\\n${mediaUrl}` : mediaUrl;\n const wasReady = ch.state === \"ready\";\n await ch.send(message);\n return { ok: true, queued: !wasReady };\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n },\n },\n};\n\n// --- Plugin export ---\n\nexport default {\n id: \"agentvault\",\n name: \"AgentVault\",\n description: \"End-to-end encrypted, zero-knowledge messaging between AI agent owners and their agents.\",\n register(api: any) {\n _setRuntime(api.runtime);\n api.registerChannel({ plugin: agentVaultPlugin });\n },\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["resolve", "SecureChannel"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentVault OpenClaw Channel Plugin
|
|
3
|
+
*
|
|
4
|
+
* Registers AgentVault as a first-class OpenClaw messaging channel.
|
|
5
|
+
* Messages from the AgentVault UI are decrypted and routed into the
|
|
6
|
+
* OpenClaw agent loop; agent replies are encrypted and sent back.
|
|
7
|
+
*
|
|
8
|
+
* Installation (one-time):
|
|
9
|
+
* npx @agentvault/agentvault setup --token=av_tok_...
|
|
10
|
+
*
|
|
11
|
+
* After setup, restart OpenClaw — no further configuration needed.
|
|
12
|
+
*/
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { SecureChannel } from "./channel.js";
|
|
15
|
+
// Runtime injected by register() — used in inbound handler
|
|
16
|
+
let _ocRuntime = null;
|
|
17
|
+
// Active channel instances keyed by accountId
|
|
18
|
+
const _channels = new Map();
|
|
19
|
+
export function setOcRuntime(runtime) {
|
|
20
|
+
_ocRuntime = runtime;
|
|
21
|
+
}
|
|
22
|
+
export function getActiveChannel(accountId = "default") {
|
|
23
|
+
return _channels.get(accountId);
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Channel plugin definition
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
export const agentVaultPlugin = {
|
|
29
|
+
id: "agentvault",
|
|
30
|
+
meta: {
|
|
31
|
+
id: "agentvault",
|
|
32
|
+
label: "AgentVault",
|
|
33
|
+
selectionLabel: "AgentVault (E2E Encrypted)",
|
|
34
|
+
docsPath: "https://agentvault.chat/docs",
|
|
35
|
+
blurb: "Zero-knowledge, end-to-end encrypted messaging between owners and their AI agents.",
|
|
36
|
+
aliases: ["av", "agent-vault"],
|
|
37
|
+
},
|
|
38
|
+
capabilities: {
|
|
39
|
+
chatTypes: ["direct"],
|
|
40
|
+
},
|
|
41
|
+
config: {
|
|
42
|
+
listAccountIds: (cfg) => {
|
|
43
|
+
const av = cfg?.channels?.agentvault;
|
|
44
|
+
return av?.dataDir ? ["default"] : [];
|
|
45
|
+
},
|
|
46
|
+
resolveAccount: (cfg, accountId) => {
|
|
47
|
+
const av = cfg?.channels?.agentvault ?? {};
|
|
48
|
+
return {
|
|
49
|
+
accountId: accountId ?? "default",
|
|
50
|
+
dataDir: av.dataDir ?? "~/.openclaw/agentvault",
|
|
51
|
+
apiUrl: av.apiUrl ?? "https://api.agentvault.chat",
|
|
52
|
+
agentName: av.agentName ?? "OpenClaw Agent",
|
|
53
|
+
httpPort: av.httpPort ?? 18790,
|
|
54
|
+
configured: Boolean(av.dataDir),
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
gateway: {
|
|
59
|
+
startAccount: async (ctx) => {
|
|
60
|
+
const { account, cfg, log, abortSignal } = ctx;
|
|
61
|
+
if (!account.configured) {
|
|
62
|
+
throw new Error("AgentVault channel is not configured.\n" +
|
|
63
|
+
"Run the one-time setup: npx @agentvault/agentvault setup --token=av_tok_...\n" +
|
|
64
|
+
"Then restart OpenClaw: openclaw gateway restart");
|
|
65
|
+
}
|
|
66
|
+
const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? "~"));
|
|
67
|
+
log?.(`[AgentVault] starting channel (dataDir=${dataDir})`);
|
|
68
|
+
const channel = new SecureChannel({
|
|
69
|
+
// No invite token needed — resuming from persisted enrolled state
|
|
70
|
+
inviteToken: "",
|
|
71
|
+
dataDir,
|
|
72
|
+
apiUrl: account.apiUrl,
|
|
73
|
+
agentName: account.agentName,
|
|
74
|
+
onMessage: async (plaintext, metadata) => {
|
|
75
|
+
if (!_ocRuntime) {
|
|
76
|
+
log?.("[AgentVault] runtime not ready — dropping inbound message");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await _handleInbound({ plaintext, metadata, channel, account, cfg });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
log?.(`[AgentVault] inbound dispatch error: ${String(err)}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
onStateChange: (state) => {
|
|
87
|
+
log?.(`[AgentVault] state → ${state}`);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
_channels.set(account.accountId, channel);
|
|
91
|
+
// Start local HTTP server for proactive sends when channel is ready
|
|
92
|
+
const httpPort = account.httpPort;
|
|
93
|
+
channel.on("ready", () => {
|
|
94
|
+
channel.startHttpServer(httpPort);
|
|
95
|
+
log?.(`[AgentVault] HTTP send server listening on http://127.0.0.1:${httpPort}`);
|
|
96
|
+
});
|
|
97
|
+
// Respect gateway abort signal (OpenClaw gateway shutdown)
|
|
98
|
+
abortSignal?.addEventListener("abort", () => {
|
|
99
|
+
_channels.delete(account.accountId);
|
|
100
|
+
});
|
|
101
|
+
await channel.start();
|
|
102
|
+
return {
|
|
103
|
+
stop: async () => {
|
|
104
|
+
await channel.stop();
|
|
105
|
+
_channels.delete(account.accountId);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
outbound: {
|
|
111
|
+
deliveryMode: "direct",
|
|
112
|
+
targets: [
|
|
113
|
+
{
|
|
114
|
+
id: "owner",
|
|
115
|
+
label: "AgentVault Owner",
|
|
116
|
+
accountId: "default",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "default",
|
|
120
|
+
label: "AgentVault Owner (default)",
|
|
121
|
+
accountId: "default",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
sendText: async ({ text, accountId, targetId, }) => {
|
|
125
|
+
const resolvedId = accountId ?? (targetId === "owner" ? "default" : (targetId ?? "default"));
|
|
126
|
+
const channel = _channels.get(resolvedId);
|
|
127
|
+
if (!channel) {
|
|
128
|
+
return { ok: false, error: "AgentVault channel is not connected" };
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await channel.send(text);
|
|
132
|
+
return { ok: true };
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
return { ok: false, error: String(err) };
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Inbound message handler — routes decrypted messages into the agent loop
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
async function _handleInbound(params) {
|
|
144
|
+
const { plaintext, metadata, channel, account, cfg } = params;
|
|
145
|
+
const core = _ocRuntime;
|
|
146
|
+
// Stable session key scoped to this AgentVault conversation
|
|
147
|
+
const sessionKey = `agentvault:${account.accountId}:${(metadata.conversationId ?? "default").slice(0, 8)}`;
|
|
148
|
+
const senderId = `agentvault:owner`;
|
|
149
|
+
// Resolve agent routing (respects agents.list routing rules if configured)
|
|
150
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
151
|
+
cfg,
|
|
152
|
+
channel: "agentvault",
|
|
153
|
+
accountId: account.accountId,
|
|
154
|
+
peer: { kind: "direct", id: senderId },
|
|
155
|
+
});
|
|
156
|
+
const storePath = core.channel.session.resolveStorePath(cfg?.session?.store, {
|
|
157
|
+
agentId: route.agentId,
|
|
158
|
+
});
|
|
159
|
+
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
160
|
+
const previousTimestamp = core.channel.session.readSessionUpdatedAt({
|
|
161
|
+
storePath,
|
|
162
|
+
sessionKey: route.sessionKey,
|
|
163
|
+
});
|
|
164
|
+
// Format the message body with standard OpenClaw envelope (channel label, timestamps, etc.)
|
|
165
|
+
const body = core.channel.reply.formatAgentEnvelope({
|
|
166
|
+
channel: "AgentVault",
|
|
167
|
+
from: "Owner",
|
|
168
|
+
timestamp: new Date(metadata.timestamp).getTime(),
|
|
169
|
+
previousTimestamp,
|
|
170
|
+
envelope: envelopeOptions,
|
|
171
|
+
body: plaintext,
|
|
172
|
+
});
|
|
173
|
+
// Build the inbound context payload OpenClaw expects
|
|
174
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
175
|
+
Body: body,
|
|
176
|
+
RawBody: plaintext,
|
|
177
|
+
CommandBody: plaintext,
|
|
178
|
+
From: senderId,
|
|
179
|
+
To: `agentvault:agent:${account.accountId}`,
|
|
180
|
+
SessionKey: route.sessionKey,
|
|
181
|
+
AccountId: account.accountId,
|
|
182
|
+
ChatType: "direct",
|
|
183
|
+
ConversationLabel: "AgentVault",
|
|
184
|
+
SenderName: "Owner",
|
|
185
|
+
SenderId: senderId,
|
|
186
|
+
Provider: "agentvault",
|
|
187
|
+
Surface: "agentvault",
|
|
188
|
+
MessageSid: metadata.messageId,
|
|
189
|
+
Timestamp: new Date(metadata.timestamp).getTime(),
|
|
190
|
+
OriginatingChannel: "agentvault",
|
|
191
|
+
OriginatingTo: `agentvault:agent:${account.accountId}`,
|
|
192
|
+
// Owner is cryptographically verified via X3DH + Double Ratchet enrollment
|
|
193
|
+
CommandAuthorized: true,
|
|
194
|
+
});
|
|
195
|
+
// Record session metadata for context continuity
|
|
196
|
+
await core.channel.session.recordInboundSession({
|
|
197
|
+
storePath,
|
|
198
|
+
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
199
|
+
ctx: ctxPayload,
|
|
200
|
+
onRecordError: (err) => {
|
|
201
|
+
core.error?.(`[AgentVault] session record failed: ${String(err)}`);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
// Dispatch to the OpenClaw agent loop; deliver replies back through the channel
|
|
205
|
+
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
206
|
+
ctx: ctxPayload,
|
|
207
|
+
cfg,
|
|
208
|
+
dispatcherOptions: {
|
|
209
|
+
deliver: async (payload) => {
|
|
210
|
+
const text = (payload.text ?? "").trim();
|
|
211
|
+
if (text) {
|
|
212
|
+
await channel.send(text);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onError: (err, info) => {
|
|
216
|
+
core.error?.(`[AgentVault] ${info?.kind ?? "reply"} error: ${String(err)}`);
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
replyOptions: {},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=openclaw-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-plugin.js","sourceRoot":"","sources":["../src/openclaw-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,2DAA2D;AAC3D,IAAI,UAAU,GAAQ,IAAI,CAAC;AAC3B,8CAA8C;AAC9C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEnD,MAAM,UAAU,YAAY,CAAC,OAAY;IACvC,UAAU,GAAG,OAAO,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAS,GAAG,SAAS;IACpD,OAAO,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAeD,8EAA8E;AAC9E,4BAA4B;AAC5B,8EAA8E;AAE9E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE;QACJ,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,YAAY;QACnB,cAAc,EAAE,4BAA4B;QAC5C,QAAQ,EAAE,8BAA8B;QACxC,KAAK,EAAE,oFAAoF;QAC3F,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;KAC/B;IACD,YAAY,EAAE;QACZ,SAAS,EAAE,CAAC,QAAQ,CAAC;KACtB;IACD,MAAM,EAAE;QACN,cAAc,EAAE,CAAC,GAAQ,EAAY,EAAE;YACrC,MAAM,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC;YACrC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,CAAC;QACD,cAAc,EAAE,CAAC,GAAQ,EAAE,SAAkB,EAAqB,EAAE;YAClE,MAAM,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,UAAU,IAAI,EAAE,CAAC;YAC3C,OAAO;gBACL,SAAS,EAAE,SAAS,IAAI,SAAS;gBACjC,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,wBAAwB;gBAC/C,MAAM,EAAE,EAAE,CAAC,MAAM,IAAI,6BAA6B;gBAClD,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,gBAAgB;gBAC3C,QAAQ,EAAE,EAAE,CAAC,QAAQ,IAAI,KAAK;gBAC9B,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC;aAChC,CAAC;QACJ,CAAC;KACF;IACD,OAAO,EAAE;QACP,YAAY,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;YAC/B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,GAK1C,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CACb,yCAAyC;oBACvC,+EAA+E;oBAC/E,iDAAiD,CACpD,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;YAEhF,GAAG,EAAE,CAAC,0CAA0C,OAAO,GAAG,CAAC,CAAC;YAE5D,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC;gBAChC,kEAAkE;gBAClE,WAAW,EAAE,EAAE;gBACf,OAAO;gBACP,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAE5B,SAAS,EAAE,KAAK,EAAE,SAAiB,EAAE,QAAyB,EAAE,EAAE;oBAChE,IAAI,CAAC,UAAU,EAAE,CAAC;wBAChB,GAAG,EAAE,CAAC,2DAA2D,CAAC,CAAC;wBACnE,OAAO;oBACT,CAAC;oBACD,IAAI,CAAC;wBACH,MAAM,cAAc,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;oBACvE,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,GAAG,EAAE,CAAC,wCAAwC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAC/D,CAAC;gBACH,CAAC;gBAED,aAAa,EAAE,CAAC,KAAa,EAAE,EAAE;oBAC/B,GAAG,EAAE,CAAC,wBAAwB,KAAK,EAAE,CAAC,CAAC;gBACzC,CAAC;aACF,CAAC,CAAC;YAEH,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAE1C,oEAAoE;YACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YAClC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;gBAClC,GAAG,EAAE,CAAC,+DAA+D,QAAQ,EAAE,CAAC,CAAC;YACnF,CAAC,CAAC,CAAC;YAEH,2DAA2D;YAC3D,WAAW,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAC1C,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YAEH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YAEtB,OAAO;gBACL,IAAI,EAAE,KAAK,IAAI,EAAE;oBACf,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;oBACrB,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACtC,CAAC;aACF,CAAC;QACJ,CAAC;KACF;IACD,QAAQ,EAAE;QACR,YAAY,EAAE,QAAiB;QAC/B,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,OAAO;gBACX,KAAK,EAAE,kBAAkB;gBACzB,SAAS,EAAE,SAAS;aACrB;YACD;gBACE,EAAE,EAAE,SAAS;gBACb,KAAK,EAAE,4BAA4B;gBACnC,SAAS,EAAE,SAAS;aACrB;SACF;QACD,QAAQ,EAAE,KAAK,EAAE,EACf,IAAI,EACJ,SAAS,EACT,QAAQ,GAKT,EAA4C,EAAE;YAC7C,MAAM,UAAU,GAAG,SAAS,IAAI,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC;YAC7F,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC;YACrE,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3C,CAAC;QACH,CAAC;KACF;CACF,CAAC;AAEF,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAE9E,KAAK,UAAU,cAAc,CAAC,MAM7B;IACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAC9D,MAAM,IAAI,GAAG,UAAU,CAAC;IAExB,4DAA4D;IAC5D,MAAM,UAAU,GAAG,cAAc,OAAO,CAAC,SAAS,IAAI,CAAC,QAAQ,CAAC,cAAc,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC3G,MAAM,QAAQ,GAAG,kBAAkB,CAAC;IAEpC,2EAA2E;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC;QACnD,GAAG;QACH,OAAO,EAAE,YAAY;QACrB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE;KACvC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;QAC3E,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC;IAC7E,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC;QAClE,SAAS;QACT,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC,CAAC;IAEH,4FAA4F;IAC5F,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC;QAClD,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;QACjD,iBAAiB;QACjB,QAAQ,EAAE,eAAe;QACzB,IAAI,EAAE,SAAS;KAChB,CAAC,CAAC;IAEH,qDAAqD;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC;QAC3D,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,SAAS;QAClB,WAAW,EAAE,SAAS;QACtB,IAAI,EAAE,QAAQ;QACd,EAAE,EAAE,oBAAoB,OAAO,CAAC,SAAS,EAAE;QAC3C,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,QAAQ;QAClB,iBAAiB,EAAE,YAAY;QAC/B,UAAU,EAAE,OAAO;QACnB,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,YAAY;QACrB,UAAU,EAAE,QAAQ,CAAC,SAAS;QAC9B,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;QACjD,kBAAkB,EAAE,YAAY;QAChC,aAAa,EAAE,oBAAoB,OAAO,CAAC,SAAS,EAAE;QACtD,2EAA2E;QAC3E,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAC;IAEH,iDAAiD;IACjD,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC;QAC9C,SAAS;QACT,UAAU,EAAE,UAAU,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU;QACrD,GAAG,EAAE,UAAU;QACf,aAAa,EAAE,CAAC,GAAU,EAAE,EAAE;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC,uCAAuC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;KACF,CAAC,CAAC;IAEH,gFAAgF;IAChF,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC;QAChE,GAAG,EAAE,UAAU;QACf,GAAG;QACH,iBAAiB,EAAE;YACjB,OAAO,EAAE,KAAK,EAAE,OAAgD,EAAE,EAAE;gBAClE,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBACzC,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;YACD,OAAO,EAAE,CAAC,GAAU,EAAE,IAAwB,EAAE,EAAE;gBAChD,IAAI,CAAC,KAAK,EAAE,CACV,gBAAgB,IAAI,EAAE,IAAI,IAAI,OAAO,WAAW,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9D,CAAC;YACJ,CAAC;SACF;QACD,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;AACL,CAAC"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentVault one-time setup command.
|
|
3
|
+
*
|
|
4
|
+
* Runs enrollment + waits for owner approval + persists state,
|
|
5
|
+
* then auto-registers the agentvault channel in openclaw config.
|
|
6
|
+
*
|
|
7
|
+
* Usage: npx @agentvault/agentvault setup --token=av_tok_...
|
|
8
|
+
*/
|
|
9
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
10
|
+
import * as readline from "node:readline";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { SecureChannel } from "./channel.js";
|
|
13
|
+
import { VERSION } from "./index.js";
|
|
14
|
+
export async function runSetupCommand(options) {
|
|
15
|
+
const { token, name, apiUrl } = options;
|
|
16
|
+
const dataDir = resolve(options.dataDir.replace(/^~/, process.env.HOME ?? "~"));
|
|
17
|
+
console.log(`
|
|
18
|
+
╔══════════════════════════════════════════════╗
|
|
19
|
+
║ AgentVault — First-Time Setup ║
|
|
20
|
+
╚══════════════════════════════════════════════╝
|
|
21
|
+
|
|
22
|
+
Agent name : ${name}
|
|
23
|
+
Data dir : ${dataDir}
|
|
24
|
+
API : ${apiUrl}
|
|
25
|
+
`);
|
|
26
|
+
let enrollDone = false;
|
|
27
|
+
const channel = new SecureChannel({
|
|
28
|
+
inviteToken: token,
|
|
29
|
+
dataDir,
|
|
30
|
+
apiUrl,
|
|
31
|
+
agentName: name,
|
|
32
|
+
onMessage: () => { }, // Not handling messages during setup
|
|
33
|
+
onStateChange: (state) => {
|
|
34
|
+
switch (state) {
|
|
35
|
+
case "enrolling":
|
|
36
|
+
console.log(" Enrolling with AgentVault server...");
|
|
37
|
+
break;
|
|
38
|
+
case "polling":
|
|
39
|
+
console.log(" Waiting for approval in your AgentVault dashboard...");
|
|
40
|
+
if (channel.fingerprint) {
|
|
41
|
+
console.log(`\n Fingerprint: ${channel.fingerprint}`);
|
|
42
|
+
console.log(" Verify this fingerprint matches the one shown in your dashboard");
|
|
43
|
+
console.log(" before clicking Approve.\n");
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case "activating":
|
|
47
|
+
console.log(" Approved! Setting up encryption...");
|
|
48
|
+
break;
|
|
49
|
+
case "ready":
|
|
50
|
+
enrollDone = true;
|
|
51
|
+
console.log(" ✅ Enrollment complete! Secure channel established.\n");
|
|
52
|
+
break;
|
|
53
|
+
case "error":
|
|
54
|
+
console.error(" ❌ Setup encountered an error.");
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
channel.on("error", (err) => {
|
|
60
|
+
console.error(`\n ❌ Setup failed: ${err.message}\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
// Start enrollment — blocks until ready or error
|
|
64
|
+
await new Promise((res, rej) => {
|
|
65
|
+
const check = setInterval(() => {
|
|
66
|
+
if (enrollDone) {
|
|
67
|
+
clearInterval(check);
|
|
68
|
+
res();
|
|
69
|
+
}
|
|
70
|
+
}, 500);
|
|
71
|
+
channel.start().catch((err) => {
|
|
72
|
+
clearInterval(check);
|
|
73
|
+
rej(err);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// Stop the live connection — OpenClaw's gateway will own it after restart
|
|
77
|
+
await channel.stop();
|
|
78
|
+
// --- Install plugin + register in OpenClaw config ---
|
|
79
|
+
console.log(" Installing AgentVault plugin in OpenClaw...\n");
|
|
80
|
+
// Extend PATH to find openclaw regardless of how npx was invoked
|
|
81
|
+
const home = process.env.HOME ?? "";
|
|
82
|
+
const extraPaths = [
|
|
83
|
+
`${home}/.local/bin`,
|
|
84
|
+
`${home}/.pnpm/bin`,
|
|
85
|
+
`${home}/Library/pnpm/bin`,
|
|
86
|
+
"/usr/local/bin",
|
|
87
|
+
"/opt/homebrew/bin",
|
|
88
|
+
].join(":");
|
|
89
|
+
const env = { ...process.env, PATH: `${extraPaths}:${process.env.PATH ?? ""}` };
|
|
90
|
+
const pluginInstalled = installPlugin(env);
|
|
91
|
+
if (!pluginInstalled) {
|
|
92
|
+
// openclaw not in PATH — print full manual instructions and bail
|
|
93
|
+
console.log(`
|
|
94
|
+
⚠️ Could not auto-configure OpenClaw (is 'openclaw' in your PATH?).
|
|
95
|
+
|
|
96
|
+
1. Install the plugin:
|
|
97
|
+
openclaw plugins install @agentvault/agentvault
|
|
98
|
+
|
|
99
|
+
2. Register the plugin in the allow list:
|
|
100
|
+
openclaw config set plugins.allow '["agentvault"]'
|
|
101
|
+
|
|
102
|
+
3. Add this to your OpenClaw config (openclaw.yaml or openclaw.json):
|
|
103
|
+
channels:
|
|
104
|
+
agentvault:
|
|
105
|
+
dataDir: "${dataDir}"
|
|
106
|
+
apiUrl: "${apiUrl}"
|
|
107
|
+
agentName: "${name}"
|
|
108
|
+
|
|
109
|
+
4. (Recommended) Install pm2 for auto-restart and crash recovery:
|
|
110
|
+
npm install -g pm2
|
|
111
|
+
pm2 start "openclaw gateway start" --name openclaw-gateway
|
|
112
|
+
pm2 startup
|
|
113
|
+
pm2 save
|
|
114
|
+
|
|
115
|
+
5. Restart the gateway:
|
|
116
|
+
openclaw gateway restart
|
|
117
|
+
`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log("\n Registering AgentVault channel in OpenClaw config...\n");
|
|
121
|
+
const patchCommands = [
|
|
122
|
+
`openclaw config set channels.agentvault.dataDir "${dataDir}"`,
|
|
123
|
+
`openclaw config set channels.agentvault.apiUrl "${apiUrl}"`,
|
|
124
|
+
`openclaw config set channels.agentvault.agentName "${name}"`,
|
|
125
|
+
`openclaw config set plugins.allow '["agentvault"]'`,
|
|
126
|
+
];
|
|
127
|
+
let configPatched = false;
|
|
128
|
+
for (const cmd of patchCommands) {
|
|
129
|
+
try {
|
|
130
|
+
execSync(cmd, { stdio: "pipe", env });
|
|
131
|
+
configPatched = true;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// config write failed — fall through to manual
|
|
135
|
+
configPatched = false;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (configPatched) {
|
|
140
|
+
console.log(` ✅ Channel registered in OpenClaw config.\n`);
|
|
141
|
+
// --- Step 6: Configure pm2 process management ---
|
|
142
|
+
console.log(" Configuring pm2 process management...\n");
|
|
143
|
+
configurePm2(env);
|
|
144
|
+
await promptAndRestart(env);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.log(` ⚠️ Could not auto-configure OpenClaw channel.
|
|
148
|
+
Add this to your OpenClaw config (openclaw.yaml or openclaw.json) manually:
|
|
149
|
+
|
|
150
|
+
channels:
|
|
151
|
+
agentvault:
|
|
152
|
+
dataDir: "${dataDir}"
|
|
153
|
+
apiUrl: "${apiUrl}"
|
|
154
|
+
agentName: "${name}"
|
|
155
|
+
|
|
156
|
+
Then restart OpenClaw:
|
|
157
|
+
openclaw gateway restart
|
|
158
|
+
`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Install/update the plugin in OpenClaw's extensions directory.
|
|
163
|
+
* Returns true if the plugin is installed (or was already up-to-date).
|
|
164
|
+
*/
|
|
165
|
+
export function installPlugin(env) {
|
|
166
|
+
// Check if already installed
|
|
167
|
+
try {
|
|
168
|
+
const info = execSync("openclaw plugins info agentvault", {
|
|
169
|
+
stdio: "pipe",
|
|
170
|
+
env,
|
|
171
|
+
});
|
|
172
|
+
const infoStr = info.toString();
|
|
173
|
+
const versionMatch = infoStr.match(/version[:\s]+(\S+)/i);
|
|
174
|
+
const installedVersion = versionMatch?.[1];
|
|
175
|
+
if (installedVersion && installedVersion !== VERSION) {
|
|
176
|
+
console.log(` Updating plugin ${installedVersion} → ${VERSION}...`);
|
|
177
|
+
spawnSync("openclaw", ["plugins", "uninstall", "agentvault"], {
|
|
178
|
+
stdio: "pipe",
|
|
179
|
+
input: "y\n",
|
|
180
|
+
env,
|
|
181
|
+
});
|
|
182
|
+
execSync("openclaw plugins install @agentvault/agentvault", {
|
|
183
|
+
stdio: "pipe",
|
|
184
|
+
env,
|
|
185
|
+
});
|
|
186
|
+
console.log(" ✅ Plugin updated.");
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(` Plugin already installed (v${installedVersion ?? "unknown"}).`);
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Not installed — install fresh
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
execSync("openclaw plugins install @agentvault/agentvault", {
|
|
198
|
+
stdio: "pipe",
|
|
199
|
+
env,
|
|
200
|
+
});
|
|
201
|
+
console.log(" ✅ Plugin installed in OpenClaw.");
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
console.log(" ⚠️ Could not auto-install plugin.");
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Configure pm2 to manage the OpenClaw gateway process.
|
|
211
|
+
* Returns true if pm2 was configured (or already configured).
|
|
212
|
+
*/
|
|
213
|
+
export function configurePm2(env) {
|
|
214
|
+
// Check if pm2 is available
|
|
215
|
+
try {
|
|
216
|
+
execSync("pm2 --version", { stdio: "pipe", env });
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
console.log(`
|
|
220
|
+
ℹ️ pm2 not found. For best reliability, install pm2:
|
|
221
|
+
npm install -g pm2
|
|
222
|
+
|
|
223
|
+
Then configure the gateway to auto-restart:
|
|
224
|
+
pm2 start "openclaw gateway start" --name openclaw-gateway
|
|
225
|
+
pm2 startup
|
|
226
|
+
pm2 save
|
|
227
|
+
`);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
// Check if already configured
|
|
231
|
+
try {
|
|
232
|
+
const info = execSync("pm2 describe openclaw-gateway", { stdio: "pipe", env });
|
|
233
|
+
const infoStr = info.toString();
|
|
234
|
+
if (infoStr.includes("online") || infoStr.includes("stopped")) {
|
|
235
|
+
console.log(" pm2 process 'openclaw-gateway' already exists.");
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Not configured yet — continue
|
|
241
|
+
}
|
|
242
|
+
// Determine platform-specific command
|
|
243
|
+
const isMac = process.platform === "darwin";
|
|
244
|
+
const startCmd = isMac
|
|
245
|
+
? "caffeinate -i openclaw gateway start"
|
|
246
|
+
: "openclaw gateway start";
|
|
247
|
+
try {
|
|
248
|
+
execSync(`pm2 start "${startCmd}" --name openclaw-gateway`, { stdio: "pipe", env });
|
|
249
|
+
console.log(" ✅ Gateway registered with pm2.");
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
console.log(` ⚠️ Failed to register with pm2: ${err}`);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
// Generate startup script
|
|
256
|
+
try {
|
|
257
|
+
execSync("pm2 startup", { stdio: "pipe", env });
|
|
258
|
+
console.log(" ✅ pm2 startup configured (auto-start on boot).");
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
console.log(" ⚠️ pm2 startup failed — you may need to run 'pm2 startup' manually with sudo.");
|
|
262
|
+
}
|
|
263
|
+
// Save process list
|
|
264
|
+
try {
|
|
265
|
+
execSync("pm2 save", { stdio: "pipe", env });
|
|
266
|
+
console.log(" ✅ pm2 process list saved.");
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
console.log(" ⚠️ pm2 save failed.");
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Prompt the user to restart the OpenClaw gateway now or later.
|
|
275
|
+
* Runs `openclaw gateway restart` synchronously if they confirm.
|
|
276
|
+
*/
|
|
277
|
+
async function promptAndRestart(env) {
|
|
278
|
+
const answer = await ask(" ⚠️ OpenClaw gateway will restart, briefly interrupting any active conversations.\n" +
|
|
279
|
+
" Restart now? [y/N] ");
|
|
280
|
+
if (answer.trim().toLowerCase() === "y") {
|
|
281
|
+
console.log("\n Restarting OpenClaw gateway...");
|
|
282
|
+
const result = spawnSync("openclaw", ["gateway", "restart"], {
|
|
283
|
+
stdio: "inherit",
|
|
284
|
+
env,
|
|
285
|
+
});
|
|
286
|
+
if (result.status === 0) {
|
|
287
|
+
console.log(`
|
|
288
|
+
✅ Gateway restarted. AgentVault is now active.
|
|
289
|
+
Your AgentVault UI will route encrypted messages
|
|
290
|
+
directly into your agent.
|
|
291
|
+
`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.log(`
|
|
295
|
+
⚠️ Restart command failed (exit ${result.status ?? "unknown"}).
|
|
296
|
+
Please restart manually:
|
|
297
|
+
|
|
298
|
+
openclaw gateway restart
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log(`
|
|
304
|
+
──────────────────────────────────────────────
|
|
305
|
+
Run this when you're ready to activate:
|
|
306
|
+
|
|
307
|
+
openclaw gateway restart
|
|
308
|
+
|
|
309
|
+
After restart, your AgentVault UI will route
|
|
310
|
+
encrypted messages directly into your agent.
|
|
311
|
+
──────────────────────────────────────────────
|
|
312
|
+
`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Prompt helper — resolves with the user's input line. */
|
|
316
|
+
function ask(question) {
|
|
317
|
+
return new Promise((resolve) => {
|
|
318
|
+
const rl = readline.createInterface({
|
|
319
|
+
input: process.stdin,
|
|
320
|
+
output: process.stdout,
|
|
321
|
+
terminal: true,
|
|
322
|
+
});
|
|
323
|
+
rl.question(question, (answer) => {
|
|
324
|
+
rl.close();
|
|
325
|
+
resolve(answer);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=setup.js.map
|