@arinova-ai/openclaw-arinova-ai 0.0.1

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.
@@ -0,0 +1,11 @@
1
+ import { emptyPluginConfigSchema, OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+
3
+ declare const plugin: {
4
+ id: string;
5
+ name: string;
6
+ description: string;
7
+ configSchema: ReturnType<typeof emptyPluginConfigSchema>;
8
+ register: (api: OpenClawPluginApi) => void;
9
+ };
10
+
11
+ export { plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,854 @@
1
+ // src/index.ts
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
+
4
+ // src/channel.ts
5
+ import {
6
+ applyAccountNameToChannelSection,
7
+ buildChannelConfigSchema,
8
+ DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID2,
9
+ formatPairingApproveHint,
10
+ normalizeAccountId as normalizeAccountId2
11
+ } from "openclaw/plugin-sdk";
12
+
13
+ // src/accounts.ts
14
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
15
+ function listConfiguredAccountIds(cfg) {
16
+ const accounts = cfg.channels?.["arinova-chat"]?.accounts;
17
+ if (!accounts || typeof accounts !== "object") {
18
+ return [];
19
+ }
20
+ const ids = /* @__PURE__ */ new Set();
21
+ for (const key of Object.keys(accounts)) {
22
+ if (!key) continue;
23
+ ids.add(normalizeAccountId(key));
24
+ }
25
+ return [...ids];
26
+ }
27
+ function listArinovaChatAccountIds(cfg) {
28
+ const ids = listConfiguredAccountIds(cfg);
29
+ if (ids.length === 0) {
30
+ return [DEFAULT_ACCOUNT_ID];
31
+ }
32
+ return ids.toSorted((a, b) => a.localeCompare(b));
33
+ }
34
+ function resolveDefaultArinovaChatAccountId(cfg) {
35
+ const ids = listArinovaChatAccountIds(cfg);
36
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
37
+ return DEFAULT_ACCOUNT_ID;
38
+ }
39
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
40
+ }
41
+ function resolveAccountConfig(cfg, accountId) {
42
+ const accounts = cfg.channels?.["arinova-chat"]?.accounts;
43
+ if (!accounts || typeof accounts !== "object") {
44
+ return void 0;
45
+ }
46
+ const direct = accounts[accountId];
47
+ if (direct) return direct;
48
+ const normalized = normalizeAccountId(accountId);
49
+ const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
50
+ return matchKey ? accounts[matchKey] : void 0;
51
+ }
52
+ function mergeArinovaChatAccountConfig(cfg, accountId) {
53
+ const { accounts: _ignored, ...base } = cfg.channels?.["arinova-chat"] ?? {};
54
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
55
+ return { ...base, ...account };
56
+ }
57
+ function resolveArinovaChatAccount(params) {
58
+ const normalized = normalizeAccountId(params.accountId);
59
+ const merged = mergeArinovaChatAccountConfig(params.cfg, normalized);
60
+ const baseEnabled = params.cfg.channels?.["arinova-chat"]?.enabled !== false;
61
+ const accountEnabled = merged.enabled !== false;
62
+ return {
63
+ accountId: normalized,
64
+ enabled: baseEnabled && accountEnabled,
65
+ name: merged.name?.trim() || void 0,
66
+ apiUrl: merged.apiUrl?.trim()?.replace(/\/$/, "") ?? "",
67
+ pairingCode: merged.pairingCode?.trim() ?? "",
68
+ agentId: merged.agentId ?? "",
69
+ sessionToken: merged.sessionToken ?? "",
70
+ config: merged
71
+ };
72
+ }
73
+
74
+ // src/config-schema.ts
75
+ import { DmPolicySchema, requireOpenAllowFrom } from "openclaw/plugin-sdk";
76
+ import { z } from "zod";
77
+ var ArinovaChatAccountSchemaBase = z.object({
78
+ name: z.string().optional(),
79
+ enabled: z.boolean().optional(),
80
+ apiUrl: z.string().optional(),
81
+ pairingCode: z.string().optional(),
82
+ email: z.string().optional(),
83
+ password: z.string().optional(),
84
+ sessionToken: z.string().optional(),
85
+ agentId: z.string().uuid().optional(),
86
+ a2aPort: z.number().int().positive().optional(),
87
+ a2aHost: z.string().optional(),
88
+ dmPolicy: DmPolicySchema.optional().default("open"),
89
+ allowFrom: z.array(z.string()).optional(),
90
+ textChunkLimit: z.number().int().positive().optional()
91
+ }).strict();
92
+ var ArinovaChatAccountSchema = ArinovaChatAccountSchemaBase.superRefine((value, ctx) => {
93
+ requireOpenAllowFrom({
94
+ policy: value.dmPolicy,
95
+ allowFrom: value.allowFrom,
96
+ ctx,
97
+ path: ["allowFrom"],
98
+ message: 'channels.arinova-chat.dmPolicy="open" requires channels.arinova-chat.allowFrom to include "*"'
99
+ });
100
+ });
101
+ var ArinovaChatConfigSchema = ArinovaChatAccountSchemaBase.extend({
102
+ accounts: z.record(z.string(), ArinovaChatAccountSchema.optional()).optional()
103
+ }).superRefine((value, ctx) => {
104
+ requireOpenAllowFrom({
105
+ policy: value.dmPolicy,
106
+ allowFrom: value.allowFrom,
107
+ ctx,
108
+ path: ["allowFrom"],
109
+ message: 'channels.arinova-chat.dmPolicy="open" requires channels.arinova-chat.allowFrom to include "*"'
110
+ });
111
+ });
112
+
113
+ // src/normalize.ts
114
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
115
+ function normalizeArinovaChatMessagingTarget(raw) {
116
+ const trimmed = raw.trim();
117
+ if (!trimmed) return void 0;
118
+ let normalized = trimmed;
119
+ if (normalized.startsWith("arinova-chat:")) {
120
+ normalized = normalized.slice("arinova-chat:".length).trim();
121
+ } else if (normalized.startsWith("arinova:")) {
122
+ normalized = normalized.slice("arinova:".length).trim();
123
+ }
124
+ if (!normalized) return void 0;
125
+ return `arinova-chat:${normalized}`.toLowerCase();
126
+ }
127
+ function looksLikeArinovaChatTargetId(raw) {
128
+ const trimmed = raw.trim();
129
+ if (!trimmed) return false;
130
+ if (/^(arinova-chat|arinova):/i.test(trimmed)) {
131
+ return true;
132
+ }
133
+ return UUID_RE.test(trimmed);
134
+ }
135
+
136
+ // src/runtime.ts
137
+ var runtime = null;
138
+ function setArinovaChatRuntime(next) {
139
+ runtime = next;
140
+ }
141
+ function getArinovaChatRuntime() {
142
+ if (!runtime) {
143
+ throw new Error("Arinova Chat runtime not initialized");
144
+ }
145
+ return runtime;
146
+ }
147
+
148
+ // src/send.ts
149
+ async function sendMessageArinovaChat(to, text, opts = {}) {
150
+ const cfg = getArinovaChatRuntime().config.loadConfig();
151
+ const account = resolveArinovaChatAccount({
152
+ cfg,
153
+ accountId: opts.accountId
154
+ });
155
+ if (!account.apiUrl) {
156
+ throw new Error(
157
+ `Arinova Chat apiUrl missing for account "${account.accountId}" (set channels.arinova-chat.apiUrl).`
158
+ );
159
+ }
160
+ if (!text?.trim()) {
161
+ throw new Error("Message must be non-empty for Arinova Chat sends");
162
+ }
163
+ let conversationId = to.trim();
164
+ if (conversationId.startsWith("arinova-chat:")) {
165
+ conversationId = conversationId.slice("arinova-chat:".length).trim();
166
+ } else if (conversationId.startsWith("arinova:")) {
167
+ conversationId = conversationId.slice("arinova:".length).trim();
168
+ }
169
+ if (!conversationId) {
170
+ throw new Error("Conversation ID is required for Arinova Chat sends");
171
+ }
172
+ console.warn(
173
+ `[arinova-chat] Proactive outbound to ${conversationId} is not yet supported. Replies are delivered inline via A2A SSE.`
174
+ );
175
+ getArinovaChatRuntime().channel.activity.record({
176
+ channel: "arinova-chat",
177
+ accountId: account.accountId,
178
+ direction: "outbound"
179
+ });
180
+ return {};
181
+ }
182
+
183
+ // src/auth.ts
184
+ async function authenticateWithArinova(params) {
185
+ const { apiUrl, email, password } = params;
186
+ const response = await fetch(`${apiUrl}/api/auth/sign-in/email`, {
187
+ method: "POST",
188
+ headers: { "Content-Type": "application/json" },
189
+ body: JSON.stringify({ email, password }),
190
+ redirect: "manual"
191
+ });
192
+ if (!response.ok && response.status !== 302) {
193
+ const body = await response.text().catch(() => "");
194
+ throw new Error(
195
+ `Arinova Chat sign-in failed (${response.status}): ${body || "check email/password"}`
196
+ );
197
+ }
198
+ const setCookieHeaders = response.headers.getSetCookie?.() ?? [];
199
+ const sessionCookie = extractSessionCookie(setCookieHeaders);
200
+ if (!sessionCookie) {
201
+ try {
202
+ const data = await response.json();
203
+ const token = data.token ?? data.session?.token;
204
+ if (token) {
205
+ return { sessionCookie: `better-auth.session_token=${token}` };
206
+ }
207
+ } catch {
208
+ }
209
+ throw new Error("Arinova Chat sign-in succeeded but no session cookie was returned");
210
+ }
211
+ return { sessionCookie };
212
+ }
213
+ async function exchangePairingCode(params) {
214
+ const { apiUrl, pairingCode, a2aEndpoint } = params;
215
+ const response = await fetch(`${apiUrl}/api/agents/pair`, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify({ pairingCode, a2aEndpoint })
219
+ });
220
+ if (!response.ok) {
221
+ const body = await response.text().catch(() => "");
222
+ throw new Error(
223
+ `Pairing code exchange failed (${response.status}): ${body || "invalid code"}`
224
+ );
225
+ }
226
+ return await response.json();
227
+ }
228
+ function extractSessionCookie(setCookieHeaders) {
229
+ for (const header of setCookieHeaders) {
230
+ if (header.includes("better-auth.session_token=")) {
231
+ const match = header.match(/better-auth\.session_token=([^;]+)/);
232
+ if (match) {
233
+ return `better-auth.session_token=${match[1]}`;
234
+ }
235
+ }
236
+ }
237
+ return void 0;
238
+ }
239
+
240
+ // src/a2a-server.ts
241
+ import { createServer } from "http";
242
+ var DEFAULT_A2A_PORT = 8790;
243
+ var DEFAULT_A2A_HOST = "0.0.0.0";
244
+ var MAX_BODY_BYTES = 1024 * 1024;
245
+ var BODY_TIMEOUT_MS = 3e4;
246
+ function readBody(req) {
247
+ return new Promise((resolve, reject) => {
248
+ const chunks = [];
249
+ let bytes = 0;
250
+ const timer = setTimeout(() => {
251
+ req.destroy();
252
+ reject(new Error("Request body timeout"));
253
+ }, BODY_TIMEOUT_MS);
254
+ req.on("data", (chunk) => {
255
+ bytes += chunk.length;
256
+ if (bytes > MAX_BODY_BYTES) {
257
+ req.destroy();
258
+ clearTimeout(timer);
259
+ reject(new Error("Payload too large"));
260
+ return;
261
+ }
262
+ chunks.push(chunk);
263
+ });
264
+ req.on("end", () => {
265
+ clearTimeout(timer);
266
+ resolve(Buffer.concat(chunks).toString("utf-8"));
267
+ });
268
+ req.on("error", (err) => {
269
+ clearTimeout(timer);
270
+ reject(err);
271
+ });
272
+ });
273
+ }
274
+ function parseA2ARequest(body) {
275
+ try {
276
+ const data = JSON.parse(body);
277
+ if (data.jsonrpc !== "2.0") return null;
278
+ if (!data.method?.startsWith("tasks/")) return null;
279
+ const taskId = String(data.params?.id ?? data.id ?? "");
280
+ const parts = data.params?.message?.parts;
281
+ if (!Array.isArray(parts)) return null;
282
+ let text = "";
283
+ for (const part of parts) {
284
+ if (part.type === "text" && typeof part.text === "string") {
285
+ text += part.text;
286
+ }
287
+ }
288
+ if (!text.trim()) return null;
289
+ return {
290
+ taskId,
291
+ text: text.trim(),
292
+ timestamp: Date.now()
293
+ };
294
+ } catch {
295
+ return null;
296
+ }
297
+ }
298
+ function writeA2ASSEEvent(res, taskId, state, text) {
299
+ const event = {
300
+ result: {
301
+ id: taskId,
302
+ status: {
303
+ state,
304
+ message: {
305
+ role: "agent",
306
+ parts: [{ type: "text", text }]
307
+ }
308
+ }
309
+ }
310
+ };
311
+ res.write(`data: ${JSON.stringify(event)}
312
+
313
+ `);
314
+ }
315
+ function createA2AServer(opts) {
316
+ const port = opts.port ?? DEFAULT_A2A_PORT;
317
+ const host = opts.host ?? DEFAULT_A2A_HOST;
318
+ const { onTask, onError, abortSignal } = opts;
319
+ const agentCard = {
320
+ name: opts.agentName ?? "OpenClaw Agent",
321
+ description: opts.agentDescription ?? "OpenClaw AI assistant via Arinova Chat",
322
+ url: `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`,
323
+ version: "1.0.0",
324
+ capabilities: {
325
+ streaming: true
326
+ },
327
+ skills: [
328
+ {
329
+ id: "chat",
330
+ name: "Chat",
331
+ description: "General conversation and task assistance"
332
+ }
333
+ ]
334
+ };
335
+ const server = createServer(async (req, res) => {
336
+ res.setHeader("Access-Control-Allow-Origin", "*");
337
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
338
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
339
+ if (req.method === "OPTIONS") {
340
+ res.writeHead(204);
341
+ res.end();
342
+ return;
343
+ }
344
+ if (req.url === "/healthz") {
345
+ res.writeHead(200, { "Content-Type": "text/plain" });
346
+ res.end("ok");
347
+ return;
348
+ }
349
+ if (req.url === "/.well-known/agent.json" && req.method === "GET") {
350
+ res.writeHead(200, { "Content-Type": "application/json" });
351
+ res.end(JSON.stringify(agentCard));
352
+ return;
353
+ }
354
+ if (req.url === "/tasks/send" && req.method === "POST") {
355
+ try {
356
+ const body = await readBody(req);
357
+ const message = parseA2ARequest(body);
358
+ if (!message) {
359
+ res.writeHead(400, { "Content-Type": "application/json" });
360
+ res.end(JSON.stringify({ error: "Invalid A2A request" }));
361
+ return;
362
+ }
363
+ res.writeHead(200, {
364
+ "Content-Type": "text/event-stream",
365
+ "Cache-Control": "no-cache",
366
+ Connection: "keep-alive"
367
+ });
368
+ try {
369
+ await onTask({ message, res });
370
+ } catch (err) {
371
+ const errorText = err instanceof Error ? err.message : String(err);
372
+ if (!res.writableEnded) {
373
+ writeA2ASSEEvent(res, message.taskId, "completed", `Error: ${errorText}`);
374
+ res.end();
375
+ }
376
+ onError?.(err instanceof Error ? err : new Error(errorText));
377
+ }
378
+ } catch (err) {
379
+ if (err instanceof Error && err.message === "Payload too large") {
380
+ if (!res.headersSent) {
381
+ res.writeHead(413, { "Content-Type": "application/json" });
382
+ res.end(JSON.stringify({ error: "Payload too large" }));
383
+ }
384
+ return;
385
+ }
386
+ const error = err instanceof Error ? err : new Error(String(err));
387
+ onError?.(error);
388
+ if (!res.headersSent) {
389
+ res.writeHead(500, { "Content-Type": "application/json" });
390
+ res.end(JSON.stringify({ error: "Internal server error" }));
391
+ }
392
+ }
393
+ return;
394
+ }
395
+ res.writeHead(404);
396
+ res.end();
397
+ });
398
+ const start = () => {
399
+ return new Promise((resolve) => {
400
+ server.listen(port, host, () => resolve());
401
+ });
402
+ };
403
+ const stop = () => {
404
+ server.close();
405
+ };
406
+ if (abortSignal) {
407
+ abortSignal.addEventListener("abort", stop, { once: true });
408
+ }
409
+ return { server, start, stop };
410
+ }
411
+
412
+ // src/inbound.ts
413
+ import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
414
+ var CHANNEL_ID = "arinova-chat";
415
+ async function handleArinovaChatInbound(params) {
416
+ const { message, res, account, config, runtime: runtime2, statusSink } = params;
417
+ const core = getArinovaChatRuntime();
418
+ const rawBody = message.text.trim();
419
+ if (!rawBody) {
420
+ writeA2ASSEEvent(res, message.taskId, "completed", "");
421
+ res.end();
422
+ return;
423
+ }
424
+ statusSink?.({ lastInboundAt: message.timestamp });
425
+ const senderId = "arinova-user";
426
+ const senderName = "Arinova User";
427
+ const dmPolicy = account.config.dmPolicy ?? "open";
428
+ if (dmPolicy === "disabled") {
429
+ runtime2.log?.(`arinova-chat: drop DM (dmPolicy=disabled)`);
430
+ writeA2ASSEEvent(res, message.taskId, "completed", "");
431
+ res.end();
432
+ return;
433
+ }
434
+ const route = core.channel.routing.resolveAgentRoute({
435
+ cfg: config,
436
+ channel: CHANNEL_ID,
437
+ accountId: account.accountId,
438
+ peer: {
439
+ kind: "direct",
440
+ id: senderId
441
+ }
442
+ });
443
+ const fromLabel = senderName;
444
+ const storePath = core.channel.session.resolveStorePath(
445
+ config.session?.store,
446
+ { agentId: route.agentId }
447
+ );
448
+ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config);
449
+ const previousTimestamp = core.channel.session.readSessionUpdatedAt({
450
+ storePath,
451
+ sessionKey: route.sessionKey
452
+ });
453
+ const body = core.channel.reply.formatAgentEnvelope({
454
+ channel: "Arinova Chat",
455
+ from: fromLabel,
456
+ timestamp: message.timestamp,
457
+ previousTimestamp,
458
+ envelope: envelopeOptions,
459
+ body: rawBody
460
+ });
461
+ const ctxPayload = core.channel.reply.finalizeInboundContext({
462
+ Body: body,
463
+ BodyForAgent: rawBody,
464
+ RawBody: rawBody,
465
+ CommandBody: rawBody,
466
+ From: `arinova-chat:${senderId}`,
467
+ To: `arinova-chat:${account.agentId}`,
468
+ SessionKey: route.sessionKey,
469
+ AccountId: route.accountId,
470
+ ChatType: "direct",
471
+ ConversationLabel: fromLabel,
472
+ SenderName: senderName,
473
+ SenderId: senderId,
474
+ Provider: CHANNEL_ID,
475
+ Surface: CHANNEL_ID,
476
+ MessageSid: message.taskId,
477
+ Timestamp: message.timestamp,
478
+ OriginatingChannel: CHANNEL_ID,
479
+ OriginatingTo: `arinova-chat:${account.agentId}`,
480
+ CommandAuthorized: true
481
+ });
482
+ await core.channel.session.recordInboundSession({
483
+ storePath,
484
+ sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
485
+ ctx: ctxPayload,
486
+ onRecordError: (err) => {
487
+ runtime2.error?.(`arinova-chat: failed updating session meta: ${String(err)}`);
488
+ }
489
+ });
490
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
491
+ cfg: config,
492
+ agentId: route.agentId,
493
+ channel: CHANNEL_ID,
494
+ accountId: account.accountId
495
+ });
496
+ let finalText = "";
497
+ await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
498
+ ctx: ctxPayload,
499
+ cfg: config,
500
+ dispatcherOptions: {
501
+ ...prefixOptions,
502
+ deliver: async (payload) => {
503
+ const text = payload.text ?? "";
504
+ if (!text.trim()) return;
505
+ finalText += (finalText ? "\n\n" : "") + text;
506
+ statusSink?.({ lastOutboundAt: Date.now() });
507
+ },
508
+ onError: (err, info) => {
509
+ runtime2.error?.(`arinova-chat ${info.kind} reply failed: ${String(err)}`);
510
+ }
511
+ },
512
+ replyOptions: {
513
+ onModelSelected,
514
+ disableBlockStreaming: false,
515
+ onPartialReply: (payload) => {
516
+ const text = payload.text ?? "";
517
+ if (!text) return;
518
+ if (!res.writableEnded) {
519
+ writeA2ASSEEvent(res, message.taskId, "working", text);
520
+ }
521
+ }
522
+ }
523
+ });
524
+ if (!res.writableEnded) {
525
+ writeA2ASSEEvent(res, message.taskId, "completed", finalText);
526
+ res.end();
527
+ }
528
+ }
529
+
530
+ // src/channel.ts
531
+ var meta = {
532
+ id: "arinova-chat",
533
+ label: "Arinova Chat",
534
+ selectionLabel: "Arinova Chat (A2A streaming)",
535
+ docsPath: "/channels/arinova-chat",
536
+ docsLabel: "arinova-chat",
537
+ blurb: "Human-to-AI messaging via Arinova Chat with native streaming.",
538
+ aliases: ["arinova"],
539
+ order: 70,
540
+ quickstartAllowFrom: true
541
+ };
542
+ var arinovaChatPlugin = {
543
+ id: "arinova-chat",
544
+ meta,
545
+ pairing: {
546
+ idLabel: "arinovaUserId",
547
+ normalizeAllowEntry: (entry) => entry.replace(/^(arinova-chat|arinova):/i, "").toLowerCase(),
548
+ notifyApproval: async ({ id }) => {
549
+ console.log(`[arinova-chat] User ${id} approved for pairing`);
550
+ }
551
+ },
552
+ capabilities: {
553
+ chatTypes: ["direct"],
554
+ reactions: false,
555
+ threads: false,
556
+ media: true,
557
+ nativeCommands: false,
558
+ blockStreaming: false
559
+ },
560
+ reload: { configPrefixes: ["channels.arinova-chat"] },
561
+ configSchema: buildChannelConfigSchema(ArinovaChatConfigSchema),
562
+ config: {
563
+ listAccountIds: (cfg) => listArinovaChatAccountIds(cfg),
564
+ resolveAccount: (cfg, accountId) => resolveArinovaChatAccount({ cfg, accountId }),
565
+ defaultAccountId: (cfg) => resolveDefaultArinovaChatAccountId(cfg),
566
+ isConfigured: (account) => Boolean(
567
+ account.apiUrl?.trim() && (account.agentId?.trim() || account.pairingCode?.trim())
568
+ ),
569
+ describeAccount: (account) => ({
570
+ accountId: account.accountId,
571
+ name: account.name,
572
+ enabled: account.enabled,
573
+ configured: Boolean(
574
+ account.apiUrl?.trim() && (account.agentId?.trim() || account.pairingCode?.trim())
575
+ ),
576
+ apiUrl: account.apiUrl ? "[set]" : "[missing]",
577
+ pairingCode: account.pairingCode ? "[set]" : "[not set]",
578
+ agentId: account.agentId ? "[set]" : "[missing]"
579
+ }),
580
+ resolveAllowFrom: ({ cfg, accountId }) => (resolveArinovaChatAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry).toLowerCase()),
581
+ formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry.replace(/^(arinova-chat|arinova):/i, "")).map((entry) => entry.toLowerCase())
582
+ },
583
+ security: {
584
+ resolveDmPolicy: ({ cfg, accountId, account }) => {
585
+ const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID2;
586
+ const useAccountPath = Boolean(
587
+ cfg.channels?.["arinova-chat"]?.accounts?.[resolvedAccountId]
588
+ );
589
+ const basePath = useAccountPath ? `channels.arinova-chat.accounts.${resolvedAccountId}.` : "channels.arinova-chat.";
590
+ return {
591
+ policy: account.config.dmPolicy ?? "open",
592
+ allowFrom: account.config.allowFrom ?? [],
593
+ policyPath: `${basePath}dmPolicy`,
594
+ allowFromPath: basePath,
595
+ approveHint: formatPairingApproveHint("arinova-chat"),
596
+ normalizeEntry: (raw) => raw.replace(/^(arinova-chat|arinova):/i, "").toLowerCase()
597
+ };
598
+ },
599
+ collectWarnings: () => []
600
+ },
601
+ messaging: {
602
+ normalizeTarget: normalizeArinovaChatMessagingTarget,
603
+ targetResolver: {
604
+ looksLikeId: looksLikeArinovaChatTargetId,
605
+ hint: "<conversationId>"
606
+ }
607
+ },
608
+ setup: {
609
+ resolveAccountId: ({ accountId }) => normalizeAccountId2(accountId),
610
+ applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
611
+ cfg,
612
+ channelKey: "arinova-chat",
613
+ accountId,
614
+ name
615
+ }),
616
+ applyAccountConfig: ({ cfg, accountId, input }) => {
617
+ const setupInput = input;
618
+ const namedConfig = applyAccountNameToChannelSection({
619
+ cfg,
620
+ channelKey: "arinova-chat",
621
+ accountId,
622
+ name: setupInput.name
623
+ });
624
+ if (accountId === DEFAULT_ACCOUNT_ID2) {
625
+ return {
626
+ ...namedConfig,
627
+ channels: {
628
+ ...namedConfig.channels,
629
+ "arinova-chat": {
630
+ ...namedConfig.channels?.["arinova-chat"],
631
+ enabled: true,
632
+ apiUrl: setupInput.apiUrl,
633
+ agentId: setupInput.agentId
634
+ }
635
+ }
636
+ };
637
+ }
638
+ return {
639
+ ...namedConfig,
640
+ channels: {
641
+ ...namedConfig.channels,
642
+ "arinova-chat": {
643
+ ...namedConfig.channels?.["arinova-chat"],
644
+ enabled: true,
645
+ accounts: {
646
+ ...namedConfig.channels?.["arinova-chat"]?.accounts,
647
+ [accountId]: {
648
+ ...namedConfig.channels?.["arinova-chat"]?.accounts?.[accountId],
649
+ enabled: true,
650
+ apiUrl: setupInput.apiUrl,
651
+ agentId: setupInput.agentId
652
+ }
653
+ }
654
+ }
655
+ }
656
+ };
657
+ }
658
+ },
659
+ outbound: {
660
+ deliveryMode: "direct",
661
+ chunker: (text, limit) => getArinovaChatRuntime().channel.text.chunkMarkdownText(text, limit),
662
+ chunkerMode: "markdown",
663
+ textChunkLimit: 32e3,
664
+ sendText: async ({ to, text, accountId }) => {
665
+ const result = await sendMessageArinovaChat(to, text, {
666
+ accountId: accountId ?? void 0
667
+ });
668
+ return { channel: "arinova-chat", messageId: result.messageId ?? "inline", ...result };
669
+ },
670
+ sendMedia: async ({ to, text, mediaUrl, accountId }) => {
671
+ const messageWithMedia = mediaUrl ? `${text}
672
+
673
+ Attachment: ${mediaUrl}` : text;
674
+ const result = await sendMessageArinovaChat(to, messageWithMedia, {
675
+ accountId: accountId ?? void 0
676
+ });
677
+ return { channel: "arinova-chat", messageId: result.messageId ?? "inline", ...result };
678
+ }
679
+ },
680
+ status: {
681
+ defaultRuntime: {
682
+ accountId: DEFAULT_ACCOUNT_ID2,
683
+ running: false,
684
+ lastStartAt: null,
685
+ lastStopAt: null,
686
+ lastError: null
687
+ },
688
+ buildChannelSummary: ({ snapshot }) => ({
689
+ configured: snapshot.configured ?? false,
690
+ running: snapshot.running ?? false,
691
+ mode: "a2a-server",
692
+ lastStartAt: snapshot.lastStartAt ?? null,
693
+ lastStopAt: snapshot.lastStopAt ?? null,
694
+ lastError: snapshot.lastError ?? null
695
+ }),
696
+ buildAccountSnapshot: ({ account, runtime: runtime2 }) => {
697
+ const configured = Boolean(
698
+ account.apiUrl?.trim() && (account.agentId?.trim() || account.pairingCode?.trim())
699
+ );
700
+ return {
701
+ accountId: account.accountId,
702
+ name: account.name,
703
+ enabled: account.enabled,
704
+ configured,
705
+ apiUrl: account.apiUrl ? "[set]" : "[missing]",
706
+ agentId: account.agentId ? "[set]" : "[missing]",
707
+ running: runtime2?.running ?? false,
708
+ lastStartAt: runtime2?.lastStartAt ?? null,
709
+ lastStopAt: runtime2?.lastStopAt ?? null,
710
+ lastError: runtime2?.lastError ?? null,
711
+ mode: "a2a-server",
712
+ lastInboundAt: runtime2?.lastInboundAt ?? null,
713
+ lastOutboundAt: runtime2?.lastOutboundAt ?? null
714
+ };
715
+ }
716
+ },
717
+ gateway: {
718
+ startAccount: async (ctx) => {
719
+ const account = ctx.account;
720
+ if (!account.apiUrl) {
721
+ throw new Error(
722
+ `Arinova Chat not configured for account "${account.accountId}" (missing apiUrl)`
723
+ );
724
+ }
725
+ if (!account.agentId && !account.pairingCode) {
726
+ throw new Error(
727
+ `Arinova Chat not configured for account "${account.accountId}" (missing agentId or pairingCode)`
728
+ );
729
+ }
730
+ const core = getArinovaChatRuntime();
731
+ const cfg = ctx.cfg;
732
+ const logger = core.logging.getChildLogger({
733
+ channel: "arinova-chat",
734
+ accountId: account.accountId
735
+ });
736
+ const runtime2 = ctx.runtime ?? {
737
+ log: (message) => logger.info(message),
738
+ error: (message) => logger.error(message),
739
+ exit: () => {
740
+ throw new Error("Runtime exit not available");
741
+ }
742
+ };
743
+ const a2aPort = account.config.a2aPort ?? 8790;
744
+ const a2aHost = account.config.a2aHost ?? "0.0.0.0";
745
+ logger.info(`[${account.accountId}] starting A2A server on ${a2aHost}:${a2aPort}`);
746
+ const { start, stop } = createA2AServer({
747
+ port: a2aPort,
748
+ host: a2aHost,
749
+ agentName: account.name ?? "OpenClaw Agent",
750
+ onTask: async ({ message, res }) => {
751
+ core.channel.activity.record({
752
+ channel: "arinova-chat",
753
+ accountId: account.accountId,
754
+ direction: "inbound",
755
+ at: message.timestamp
756
+ });
757
+ await handleArinovaChatInbound({
758
+ message,
759
+ res,
760
+ account,
761
+ config: cfg,
762
+ runtime: runtime2,
763
+ statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch })
764
+ });
765
+ },
766
+ onError: (error) => {
767
+ logger.error(`[arinova-chat:${account.accountId}] A2A server error: ${error.message}`);
768
+ },
769
+ abortSignal: ctx.abortSignal
770
+ });
771
+ await start();
772
+ const publicUrl = `http://${a2aHost === "0.0.0.0" ? "localhost" : a2aHost}:${a2aPort}`;
773
+ logger.info(
774
+ `[arinova-chat:${account.accountId}] A2A server listening on ${publicUrl}`
775
+ );
776
+ logger.info(
777
+ `[arinova-chat:${account.accountId}] Agent card: ${publicUrl}/.well-known/agent.json`
778
+ );
779
+ const endpointUrl = `${publicUrl}/.well-known/agent.json`;
780
+ if (account.pairingCode && !account.agentId) {
781
+ logger.info(`[${account.accountId}] exchanging pairing code...`);
782
+ try {
783
+ const result = await exchangePairingCode({
784
+ apiUrl: account.apiUrl,
785
+ pairingCode: account.pairingCode,
786
+ a2aEndpoint: endpointUrl
787
+ });
788
+ account.agentId = result.agentId;
789
+ logger.info(
790
+ `[${account.accountId}] paired successfully \u2014 agentId=${result.agentId} name="${result.name}"`
791
+ );
792
+ } catch (err) {
793
+ const errorMsg = err instanceof Error ? err.message : String(err);
794
+ logger.error(`[${account.accountId}] pairing failed: ${errorMsg}`);
795
+ throw err;
796
+ }
797
+ } else {
798
+ let sessionCookie = account.sessionToken ? `better-auth.session_token=${account.sessionToken}` : "";
799
+ if (!sessionCookie && account.config.email && account.config.password) {
800
+ logger.info(`[${account.accountId}] authenticating with Arinova Chat...`);
801
+ try {
802
+ const authResult = await authenticateWithArinova({
803
+ apiUrl: account.apiUrl,
804
+ email: account.config.email,
805
+ password: account.config.password
806
+ });
807
+ sessionCookie = authResult.sessionCookie;
808
+ logger.info(`[${account.accountId}] authenticated successfully`);
809
+ } catch (err) {
810
+ const errorMsg = err instanceof Error ? err.message : String(err);
811
+ logger.error(`[${account.accountId}] auth failed: ${errorMsg}`);
812
+ throw err;
813
+ }
814
+ }
815
+ if (sessionCookie && account.agentId) {
816
+ try {
817
+ const updateRes = await fetch(`${account.apiUrl}/api/agents/${account.agentId}`, {
818
+ method: "PUT",
819
+ headers: {
820
+ "Content-Type": "application/json",
821
+ Cookie: sessionCookie
822
+ },
823
+ body: JSON.stringify({ a2aEndpoint: endpointUrl })
824
+ });
825
+ if (updateRes.ok) {
826
+ logger.info(
827
+ `[arinova-chat:${account.accountId}] updated agent a2aEndpoint to ${endpointUrl}`
828
+ );
829
+ }
830
+ } catch {
831
+ }
832
+ }
833
+ }
834
+ return { stop };
835
+ }
836
+ }
837
+ };
838
+
839
+ // src/index.ts
840
+ var plugin = {
841
+ id: "arinova-chat",
842
+ name: "Arinova Chat",
843
+ description: "Arinova Chat channel plugin (A2A protocol with native streaming)",
844
+ configSchema: emptyPluginConfigSchema(),
845
+ register(api) {
846
+ setArinovaChatRuntime(api.runtime);
847
+ api.registerChannel({ plugin: arinovaChatPlugin });
848
+ }
849
+ };
850
+ var index_default = plugin;
851
+ export {
852
+ index_default as default
853
+ };
854
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/channel.ts","../src/accounts.ts","../src/config-schema.ts","../src/normalize.ts","../src/runtime.ts","../src/send.ts","../src/auth.ts","../src/a2a-server.ts","../src/inbound.ts"],"sourcesContent":["import type { OpenClawPluginApi } from \"openclaw/plugin-sdk\";\nimport { emptyPluginConfigSchema } from \"openclaw/plugin-sdk\";\nimport { arinovaChatPlugin } from \"./channel.js\";\nimport { setArinovaChatRuntime } from \"./runtime.js\";\n\nconst plugin: {\n id: string;\n name: string;\n description: string;\n configSchema: ReturnType<typeof emptyPluginConfigSchema>;\n register: (api: OpenClawPluginApi) => void;\n} = {\n id: \"arinova-chat\",\n name: \"Arinova Chat\",\n description: \"Arinova Chat channel plugin (A2A protocol with native streaming)\",\n configSchema: emptyPluginConfigSchema(),\n register(api: OpenClawPluginApi) {\n setArinovaChatRuntime(api.runtime);\n api.registerChannel({ plugin: arinovaChatPlugin });\n },\n};\n\nexport default plugin;\n","import {\n applyAccountNameToChannelSection,\n buildChannelConfigSchema,\n DEFAULT_ACCOUNT_ID,\n formatPairingApproveHint,\n normalizeAccountId,\n type ChannelPlugin,\n type ChannelSetupInput,\n type OpenClawConfig,\n type RuntimeEnv,\n} from \"openclaw/plugin-sdk\";\nimport type { CoreConfig } from \"./types.js\";\nimport {\n listArinovaChatAccountIds,\n resolveDefaultArinovaChatAccountId,\n resolveArinovaChatAccount,\n type ResolvedArinovaChatAccount,\n} from \"./accounts.js\";\nimport { ArinovaChatConfigSchema } from \"./config-schema.js\";\nimport {\n looksLikeArinovaChatTargetId,\n normalizeArinovaChatMessagingTarget,\n} from \"./normalize.js\";\nimport { getArinovaChatRuntime } from \"./runtime.js\";\nimport { sendMessageArinovaChat } from \"./send.js\";\nimport { authenticateWithArinova, exchangePairingCode } from \"./auth.js\";\nimport { createA2AServer } from \"./a2a-server.js\";\nimport { handleArinovaChatInbound } from \"./inbound.js\";\n\nconst meta = {\n id: \"arinova-chat\",\n label: \"Arinova Chat\",\n selectionLabel: \"Arinova Chat (A2A streaming)\",\n docsPath: \"/channels/arinova-chat\",\n docsLabel: \"arinova-chat\",\n blurb: \"Human-to-AI messaging via Arinova Chat with native streaming.\",\n aliases: [\"arinova\"],\n order: 70,\n quickstartAllowFrom: true,\n};\n\nexport const arinovaChatPlugin: ChannelPlugin<ResolvedArinovaChatAccount> = {\n id: \"arinova-chat\",\n meta,\n pairing: {\n idLabel: \"arinovaUserId\",\n normalizeAllowEntry: (entry) =>\n entry.replace(/^(arinova-chat|arinova):/i, \"\").toLowerCase(),\n notifyApproval: async ({ id }) => {\n console.log(`[arinova-chat] User ${id} approved for pairing`);\n },\n },\n capabilities: {\n chatTypes: [\"direct\"],\n reactions: false,\n threads: false,\n media: true,\n nativeCommands: false,\n blockStreaming: false,\n },\n reload: { configPrefixes: [\"channels.arinova-chat\"] },\n configSchema: buildChannelConfigSchema(ArinovaChatConfigSchema),\n config: {\n listAccountIds: (cfg) => listArinovaChatAccountIds(cfg as CoreConfig),\n resolveAccount: (cfg, accountId) =>\n resolveArinovaChatAccount({ cfg: cfg as CoreConfig, accountId }),\n defaultAccountId: (cfg) => resolveDefaultArinovaChatAccountId(cfg as CoreConfig),\n isConfigured: (account) =>\n Boolean(\n account.apiUrl?.trim() &&\n (account.agentId?.trim() || account.pairingCode?.trim()),\n ),\n describeAccount: (account) => ({\n accountId: account.accountId,\n name: account.name,\n enabled: account.enabled,\n configured: Boolean(\n account.apiUrl?.trim() &&\n (account.agentId?.trim() || account.pairingCode?.trim()),\n ),\n apiUrl: account.apiUrl ? \"[set]\" : \"[missing]\",\n pairingCode: account.pairingCode ? \"[set]\" : \"[not set]\",\n agentId: account.agentId ? \"[set]\" : \"[missing]\",\n }),\n resolveAllowFrom: ({ cfg, accountId }) =>\n (\n resolveArinovaChatAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []\n ).map((entry) => String(entry).toLowerCase()),\n formatAllowFrom: ({ allowFrom }) =>\n allowFrom\n .map((entry) => String(entry).trim())\n .filter(Boolean)\n .map((entry) => entry.replace(/^(arinova-chat|arinova):/i, \"\"))\n .map((entry) => entry.toLowerCase()),\n },\n security: {\n resolveDmPolicy: ({ cfg, accountId, account }) => {\n const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;\n const useAccountPath = Boolean(\n cfg.channels?.[\"arinova-chat\"]?.accounts?.[resolvedAccountId],\n );\n const basePath = useAccountPath\n ? `channels.arinova-chat.accounts.${resolvedAccountId}.`\n : \"channels.arinova-chat.\";\n return {\n policy: account.config.dmPolicy ?? \"open\",\n allowFrom: account.config.allowFrom ?? [],\n policyPath: `${basePath}dmPolicy`,\n allowFromPath: basePath,\n approveHint: formatPairingApproveHint(\"arinova-chat\"),\n normalizeEntry: (raw) => raw.replace(/^(arinova-chat|arinova):/i, \"\").toLowerCase(),\n };\n },\n collectWarnings: () => [],\n },\n messaging: {\n normalizeTarget: normalizeArinovaChatMessagingTarget,\n targetResolver: {\n looksLikeId: looksLikeArinovaChatTargetId,\n hint: \"<conversationId>\",\n },\n },\n setup: {\n resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),\n applyAccountName: ({ cfg, accountId, name }) =>\n applyAccountNameToChannelSection({\n cfg,\n channelKey: \"arinova-chat\",\n accountId,\n name,\n }),\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const setupInput = input as ChannelSetupInput & {\n apiUrl?: string;\n agentId?: string;\n };\n const namedConfig = applyAccountNameToChannelSection({\n cfg,\n channelKey: \"arinova-chat\",\n accountId,\n name: setupInput.name,\n });\n if (accountId === DEFAULT_ACCOUNT_ID) {\n return {\n ...namedConfig,\n channels: {\n ...namedConfig.channels,\n \"arinova-chat\": {\n ...namedConfig.channels?.[\"arinova-chat\"],\n enabled: true,\n apiUrl: setupInput.apiUrl,\n agentId: setupInput.agentId,\n },\n },\n } as OpenClawConfig;\n }\n return {\n ...namedConfig,\n channels: {\n ...namedConfig.channels,\n \"arinova-chat\": {\n ...namedConfig.channels?.[\"arinova-chat\"],\n enabled: true,\n accounts: {\n ...namedConfig.channels?.[\"arinova-chat\"]?.accounts,\n [accountId]: {\n ...namedConfig.channels?.[\"arinova-chat\"]?.accounts?.[accountId],\n enabled: true,\n apiUrl: setupInput.apiUrl,\n agentId: setupInput.agentId,\n },\n },\n },\n },\n } as OpenClawConfig;\n },\n },\n outbound: {\n deliveryMode: \"direct\",\n chunker: (text, limit) => getArinovaChatRuntime().channel.text.chunkMarkdownText(text, limit),\n chunkerMode: \"markdown\",\n textChunkLimit: 32000,\n sendText: async ({ to, text, accountId }) => {\n const result = await sendMessageArinovaChat(to, text, {\n accountId: accountId ?? undefined,\n });\n return { channel: \"arinova-chat\", messageId: result.messageId ?? \"inline\", ...result };\n },\n sendMedia: async ({ to, text, mediaUrl, accountId }) => {\n const messageWithMedia = mediaUrl ? `${text}\\n\\nAttachment: ${mediaUrl}` : text;\n const result = await sendMessageArinovaChat(to, messageWithMedia, {\n accountId: accountId ?? undefined,\n });\n return { channel: \"arinova-chat\", messageId: result.messageId ?? \"inline\", ...result };\n },\n },\n status: {\n defaultRuntime: {\n accountId: DEFAULT_ACCOUNT_ID,\n running: false,\n lastStartAt: null,\n lastStopAt: null,\n lastError: null,\n },\n buildChannelSummary: ({ snapshot }) => ({\n configured: snapshot.configured ?? false,\n running: snapshot.running ?? false,\n mode: \"a2a-server\",\n lastStartAt: snapshot.lastStartAt ?? null,\n lastStopAt: snapshot.lastStopAt ?? null,\n lastError: snapshot.lastError ?? null,\n }),\n buildAccountSnapshot: ({ account, runtime }) => {\n const configured = Boolean(\n account.apiUrl?.trim() &&\n (account.agentId?.trim() || account.pairingCode?.trim()),\n );\n return {\n accountId: account.accountId,\n name: account.name,\n enabled: account.enabled,\n configured,\n apiUrl: account.apiUrl ? \"[set]\" : \"[missing]\",\n agentId: account.agentId ? \"[set]\" : \"[missing]\",\n running: runtime?.running ?? false,\n lastStartAt: runtime?.lastStartAt ?? null,\n lastStopAt: runtime?.lastStopAt ?? null,\n lastError: runtime?.lastError ?? null,\n mode: \"a2a-server\",\n lastInboundAt: runtime?.lastInboundAt ?? null,\n lastOutboundAt: runtime?.lastOutboundAt ?? null,\n };\n },\n },\n gateway: {\n startAccount: async (ctx) => {\n const account = ctx.account;\n if (!account.apiUrl) {\n throw new Error(\n `Arinova Chat not configured for account \"${account.accountId}\" (missing apiUrl)`,\n );\n }\n if (!account.agentId && !account.pairingCode) {\n throw new Error(\n `Arinova Chat not configured for account \"${account.accountId}\" (missing agentId or pairingCode)`,\n );\n }\n\n const core = getArinovaChatRuntime();\n const cfg = ctx.cfg as CoreConfig;\n const logger = core.logging.getChildLogger({\n channel: \"arinova-chat\",\n accountId: account.accountId,\n });\n const runtime: RuntimeEnv = ctx.runtime ?? {\n log: (message: string) => logger.info(message),\n error: (message: string) => logger.error(message),\n exit: () => {\n throw new Error(\"Runtime exit not available\");\n },\n };\n\n // Start A2A server first (needed for pairing code exchange)\n const a2aPort = account.config.a2aPort ?? 8790;\n const a2aHost = account.config.a2aHost ?? \"0.0.0.0\";\n\n logger.info(`[${account.accountId}] starting A2A server on ${a2aHost}:${a2aPort}`);\n\n const { start, stop } = createA2AServer({\n port: a2aPort,\n host: a2aHost,\n agentName: account.name ?? \"OpenClaw Agent\",\n onTask: async ({ message, res }) => {\n core.channel.activity.record({\n channel: \"arinova-chat\",\n accountId: account.accountId,\n direction: \"inbound\",\n at: message.timestamp,\n });\n\n await handleArinovaChatInbound({\n message,\n res,\n account,\n config: cfg,\n runtime,\n statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),\n });\n },\n onError: (error) => {\n logger.error(`[arinova-chat:${account.accountId}] A2A server error: ${error.message}`);\n },\n abortSignal: ctx.abortSignal,\n });\n\n await start();\n\n const publicUrl = `http://${a2aHost === \"0.0.0.0\" ? \"localhost\" : a2aHost}:${a2aPort}`;\n logger.info(\n `[arinova-chat:${account.accountId}] A2A server listening on ${publicUrl}`,\n );\n logger.info(\n `[arinova-chat:${account.accountId}] Agent card: ${publicUrl}/.well-known/agent.json`,\n );\n\n const endpointUrl = `${publicUrl}/.well-known/agent.json`;\n\n // Pairing code flow: exchange code for agentId + register endpoint (no auth needed)\n if (account.pairingCode && !account.agentId) {\n logger.info(`[${account.accountId}] exchanging pairing code...`);\n try {\n const result = await exchangePairingCode({\n apiUrl: account.apiUrl,\n pairingCode: account.pairingCode,\n a2aEndpoint: endpointUrl,\n });\n account.agentId = result.agentId;\n logger.info(\n `[${account.accountId}] paired successfully — agentId=${result.agentId} name=\"${result.name}\"`,\n );\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n logger.error(`[${account.accountId}] pairing failed: ${errorMsg}`);\n throw err;\n }\n } else {\n // Legacy email/password flow: authenticate then update endpoint\n let sessionCookie = account.sessionToken\n ? `better-auth.session_token=${account.sessionToken}`\n : \"\";\n\n if (!sessionCookie && account.config.email && account.config.password) {\n logger.info(`[${account.accountId}] authenticating with Arinova Chat...`);\n try {\n const authResult = await authenticateWithArinova({\n apiUrl: account.apiUrl,\n email: account.config.email,\n password: account.config.password,\n });\n sessionCookie = authResult.sessionCookie;\n logger.info(`[${account.accountId}] authenticated successfully`);\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n logger.error(`[${account.accountId}] auth failed: ${errorMsg}`);\n throw err;\n }\n }\n\n // Update the agent's a2aEndpoint in Arinova\n if (sessionCookie && account.agentId) {\n try {\n const updateRes = await fetch(`${account.apiUrl}/api/agents/${account.agentId}`, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/json\",\n Cookie: sessionCookie,\n },\n body: JSON.stringify({ a2aEndpoint: endpointUrl }),\n });\n if (updateRes.ok) {\n logger.info(\n `[arinova-chat:${account.accountId}] updated agent a2aEndpoint to ${endpointUrl}`,\n );\n }\n } catch {\n // Non-critical: agent endpoint update is best-effort\n }\n }\n }\n\n return { stop };\n },\n },\n};\n","import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from \"openclaw/plugin-sdk\";\nimport type { ArinovaChatAccountConfig, CoreConfig } from \"./types.js\";\n\nexport type ResolvedArinovaChatAccount = {\n accountId: string;\n enabled: boolean;\n name?: string;\n apiUrl: string;\n pairingCode: string;\n agentId: string;\n sessionToken: string;\n config: ArinovaChatAccountConfig;\n};\n\nfunction listConfiguredAccountIds(cfg: CoreConfig): string[] {\n const accounts = cfg.channels?.[\"arinova-chat\"]?.accounts;\n if (!accounts || typeof accounts !== \"object\") {\n return [];\n }\n const ids = new Set<string>();\n for (const key of Object.keys(accounts)) {\n if (!key) continue;\n ids.add(normalizeAccountId(key));\n }\n return [...ids];\n}\n\nexport function listArinovaChatAccountIds(cfg: CoreConfig): string[] {\n const ids = listConfiguredAccountIds(cfg);\n if (ids.length === 0) {\n return [DEFAULT_ACCOUNT_ID];\n }\n return ids.toSorted((a, b) => a.localeCompare(b));\n}\n\nexport function resolveDefaultArinovaChatAccountId(cfg: CoreConfig): string {\n const ids = listArinovaChatAccountIds(cfg);\n if (ids.includes(DEFAULT_ACCOUNT_ID)) {\n return DEFAULT_ACCOUNT_ID;\n }\n return ids[0] ?? DEFAULT_ACCOUNT_ID;\n}\n\nfunction resolveAccountConfig(\n cfg: CoreConfig,\n accountId: string,\n): ArinovaChatAccountConfig | undefined {\n const accounts = cfg.channels?.[\"arinova-chat\"]?.accounts;\n if (!accounts || typeof accounts !== \"object\") {\n return undefined;\n }\n const direct = accounts[accountId] as ArinovaChatAccountConfig | undefined;\n if (direct) return direct;\n const normalized = normalizeAccountId(accountId);\n const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);\n return matchKey ? (accounts[matchKey] as ArinovaChatAccountConfig | undefined) : undefined;\n}\n\nfunction mergeArinovaChatAccountConfig(\n cfg: CoreConfig,\n accountId: string,\n): ArinovaChatAccountConfig {\n const { accounts: _ignored, ...base } = (cfg.channels?.[\"arinova-chat\"] ??\n {}) as ArinovaChatAccountConfig & { accounts?: unknown };\n const account = resolveAccountConfig(cfg, accountId) ?? {};\n return { ...base, ...account };\n}\n\nexport function resolveArinovaChatAccount(params: {\n cfg: CoreConfig;\n accountId?: string | null;\n}): ResolvedArinovaChatAccount {\n const normalized = normalizeAccountId(params.accountId);\n const merged = mergeArinovaChatAccountConfig(params.cfg, normalized);\n const baseEnabled = params.cfg.channels?.[\"arinova-chat\"]?.enabled !== false;\n const accountEnabled = merged.enabled !== false;\n\n return {\n accountId: normalized,\n enabled: baseEnabled && accountEnabled,\n name: merged.name?.trim() || undefined,\n apiUrl: merged.apiUrl?.trim()?.replace(/\\/$/, \"\") ?? \"\",\n pairingCode: merged.pairingCode?.trim() ?? \"\",\n agentId: merged.agentId ?? \"\",\n sessionToken: merged.sessionToken ?? \"\",\n config: merged,\n };\n}\n","import { DmPolicySchema, requireOpenAllowFrom } from \"openclaw/plugin-sdk\";\nimport { z } from \"zod\";\n\nexport const ArinovaChatAccountSchemaBase = z\n .object({\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n apiUrl: z.string().optional(),\n pairingCode: z.string().optional(),\n email: z.string().optional(),\n password: z.string().optional(),\n sessionToken: z.string().optional(),\n agentId: z.string().uuid().optional(),\n a2aPort: z.number().int().positive().optional(),\n a2aHost: z.string().optional(),\n dmPolicy: DmPolicySchema.optional().default(\"open\"),\n allowFrom: z.array(z.string()).optional(),\n textChunkLimit: z.number().int().positive().optional(),\n })\n .strict();\n\nexport const ArinovaChatAccountSchema = ArinovaChatAccountSchemaBase.superRefine((value, ctx) => {\n requireOpenAllowFrom({\n policy: value.dmPolicy,\n allowFrom: value.allowFrom,\n ctx,\n path: [\"allowFrom\"],\n message:\n 'channels.arinova-chat.dmPolicy=\"open\" requires channels.arinova-chat.allowFrom to include \"*\"',\n });\n});\n\nexport const ArinovaChatConfigSchema = ArinovaChatAccountSchemaBase.extend({\n accounts: z.record(z.string(), ArinovaChatAccountSchema.optional()).optional(),\n}).superRefine((value, ctx) => {\n requireOpenAllowFrom({\n policy: value.dmPolicy,\n allowFrom: value.allowFrom,\n ctx,\n path: [\"allowFrom\"],\n message:\n 'channels.arinova-chat.dmPolicy=\"open\" requires channels.arinova-chat.allowFrom to include \"*\"',\n });\n});\n","const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function normalizeArinovaChatMessagingTarget(raw: string): string | undefined {\n const trimmed = raw.trim();\n if (!trimmed) return undefined;\n\n let normalized = trimmed;\n\n if (normalized.startsWith(\"arinova-chat:\")) {\n normalized = normalized.slice(\"arinova-chat:\".length).trim();\n } else if (normalized.startsWith(\"arinova:\")) {\n normalized = normalized.slice(\"arinova:\".length).trim();\n }\n\n if (!normalized) return undefined;\n\n return `arinova-chat:${normalized}`.toLowerCase();\n}\n\nexport function looksLikeArinovaChatTargetId(raw: string): boolean {\n const trimmed = raw.trim();\n if (!trimmed) return false;\n\n if (/^(arinova-chat|arinova):/i.test(trimmed)) {\n return true;\n }\n\n return UUID_RE.test(trimmed);\n}\n","import type { PluginRuntime } from \"openclaw/plugin-sdk\";\n\nlet runtime: PluginRuntime | null = null;\n\nexport function setArinovaChatRuntime(next: PluginRuntime) {\n runtime = next;\n}\n\nexport function getArinovaChatRuntime(): PluginRuntime {\n if (!runtime) {\n throw new Error(\"Arinova Chat runtime not initialized\");\n }\n return runtime;\n}\n","import type { ArinovaChatSendResult, CoreConfig } from \"./types.js\";\nimport { resolveArinovaChatAccount } from \"./accounts.js\";\nimport { getArinovaChatRuntime } from \"./runtime.js\";\n\ntype ArinovaChatSendOpts = {\n accountId?: string;\n};\n\n/**\n * Send a text message via Arinova Chat.\n *\n * Note: For most replies, the plugin responds inline via the A2A SSE stream.\n * This function is a fallback for proactive outbound messages outside of\n * an A2A request context (e.g. scheduled messages, notifications).\n *\n * Arinova Chat uses WebSocket for 1v1 message sending, so proactive outbound\n * is not yet fully supported. Replies are delivered inline via A2A SSE.\n */\nexport async function sendMessageArinovaChat(\n to: string,\n text: string,\n opts: ArinovaChatSendOpts = {},\n): Promise<ArinovaChatSendResult> {\n const cfg = getArinovaChatRuntime().config.loadConfig() as CoreConfig;\n const account = resolveArinovaChatAccount({\n cfg,\n accountId: opts.accountId,\n });\n\n if (!account.apiUrl) {\n throw new Error(\n `Arinova Chat apiUrl missing for account \"${account.accountId}\" (set channels.arinova-chat.apiUrl).`,\n );\n }\n\n if (!text?.trim()) {\n throw new Error(\"Message must be non-empty for Arinova Chat sends\");\n }\n\n // Strip channel prefix to get conversation ID\n let conversationId = to.trim();\n if (conversationId.startsWith(\"arinova-chat:\")) {\n conversationId = conversationId.slice(\"arinova-chat:\".length).trim();\n } else if (conversationId.startsWith(\"arinova:\")) {\n conversationId = conversationId.slice(\"arinova:\".length).trim();\n }\n\n if (!conversationId) {\n throw new Error(\"Conversation ID is required for Arinova Chat sends\");\n }\n\n console.warn(\n `[arinova-chat] Proactive outbound to ${conversationId} is not yet supported. ` +\n `Replies are delivered inline via A2A SSE.`,\n );\n\n getArinovaChatRuntime().channel.activity.record({\n channel: \"arinova-chat\",\n accountId: account.accountId,\n direction: \"outbound\",\n });\n\n return {};\n}\n","/**\n * Better Auth session management for Arinova Chat.\n * Authenticates via email/password and extracts the session cookie.\n */\n\nexport type AuthResult = {\n sessionCookie: string;\n};\n\n/**\n * Sign in to Arinova Chat using Better Auth email/password flow.\n * Returns the session cookie string for subsequent requests.\n */\nexport async function authenticateWithArinova(params: {\n apiUrl: string;\n email: string;\n password: string;\n}): Promise<AuthResult> {\n const { apiUrl, email, password } = params;\n\n const response = await fetch(`${apiUrl}/api/auth/sign-in/email`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n redirect: \"manual\",\n });\n\n if (!response.ok && response.status !== 302) {\n const body = await response.text().catch(() => \"\");\n throw new Error(\n `Arinova Chat sign-in failed (${response.status}): ${body || \"check email/password\"}`,\n );\n }\n\n const setCookieHeaders = response.headers.getSetCookie?.() ?? [];\n const sessionCookie = extractSessionCookie(setCookieHeaders);\n\n if (!sessionCookie) {\n // Better Auth may return the token in the JSON body instead of cookies\n try {\n const data = (await response.json()) as { token?: string; session?: { token?: string } };\n const token = data.token ?? data.session?.token;\n if (token) {\n return { sessionCookie: `better-auth.session_token=${token}` };\n }\n } catch {\n // ignore parse errors\n }\n throw new Error(\"Arinova Chat sign-in succeeded but no session cookie was returned\");\n }\n\n return { sessionCookie };\n}\n\n/**\n * Validate that a session cookie is still valid.\n */\nexport async function validateSession(params: {\n apiUrl: string;\n sessionCookie: string;\n}): Promise<boolean> {\n const { apiUrl, sessionCookie } = params;\n\n try {\n const response = await fetch(`${apiUrl}/api/auth/session`, {\n headers: { Cookie: sessionCookie },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Exchange a 6-char pairing code for the agent ID (no auth required).\n * Also registers the A2A endpoint with Arinova so the backend knows\n * where to forward messages.\n */\nexport async function exchangePairingCode(params: {\n apiUrl: string;\n pairingCode: string;\n a2aEndpoint: string;\n}): Promise<{ agentId: string; name: string }> {\n const { apiUrl, pairingCode, a2aEndpoint } = params;\n\n const response = await fetch(`${apiUrl}/api/agents/pair`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ pairingCode, a2aEndpoint }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new Error(\n `Pairing code exchange failed (${response.status}): ${body || \"invalid code\"}`,\n );\n }\n\n return (await response.json()) as { agentId: string; name: string };\n}\n\nfunction extractSessionCookie(setCookieHeaders: string[]): string | undefined {\n for (const header of setCookieHeaders) {\n if (header.includes(\"better-auth.session_token=\")) {\n const match = header.match(/better-auth\\.session_token=([^;]+)/);\n if (match) {\n return `better-auth.session_token=${match[1]}`;\n }\n }\n }\n return undefined;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { ArinovaChatInboundMessage } from \"./types.js\";\n\nconst DEFAULT_A2A_PORT = 8790;\nconst DEFAULT_A2A_HOST = \"0.0.0.0\";\nconst MAX_BODY_BYTES = 1024 * 1024;\nconst BODY_TIMEOUT_MS = 30_000;\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let bytes = 0;\n const timer = setTimeout(() => {\n req.destroy();\n reject(new Error(\"Request body timeout\"));\n }, BODY_TIMEOUT_MS);\n req.on(\"data\", (chunk: Buffer) => {\n bytes += chunk.length;\n if (bytes > MAX_BODY_BYTES) {\n req.destroy();\n clearTimeout(timer);\n reject(new Error(\"Payload too large\"));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n clearTimeout(timer);\n resolve(Buffer.concat(chunks).toString(\"utf-8\"));\n });\n req.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n}\n\nexport type A2AServerOptions = {\n port?: number;\n host?: string;\n agentName?: string;\n agentDescription?: string;\n /**\n * Called when an A2A task/sendSubscribe request arrives.\n * The handler must write SSE events to the response and call res.end() when done.\n */\n onTask: (params: {\n message: ArinovaChatInboundMessage;\n res: ServerResponse;\n }) => void | Promise<void>;\n onError?: (error: Error) => void;\n abortSignal?: AbortSignal;\n};\n\n/**\n * Parse the A2A JSON-RPC request body.\n * Expected format:\n * {\n * \"jsonrpc\": \"2.0\",\n * \"id\": \"<messageId>\",\n * \"method\": \"tasks/sendSubscribe\",\n * \"params\": {\n * \"id\": \"<taskId>\",\n * \"message\": {\n * \"role\": \"user\",\n * \"parts\": [{ \"type\": \"text\", \"text\": \"...\" }]\n * }\n * }\n * }\n */\nfunction parseA2ARequest(body: string): ArinovaChatInboundMessage | null {\n try {\n const data = JSON.parse(body);\n if (data.jsonrpc !== \"2.0\") return null;\n if (!data.method?.startsWith(\"tasks/\")) return null;\n\n const taskId = String(data.params?.id ?? data.id ?? \"\");\n const parts = data.params?.message?.parts;\n if (!Array.isArray(parts)) return null;\n\n let text = \"\";\n for (const part of parts) {\n if (part.type === \"text\" && typeof part.text === \"string\") {\n text += part.text;\n }\n }\n\n if (!text.trim()) return null;\n\n return {\n taskId,\n text: text.trim(),\n timestamp: Date.now(),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Write an A2A SSE event to the response.\n * Format matches what Arinova's client.ts:66-93 expects:\n * data: {\"result\":{\"id\":\"<id>\",\"status\":{\"state\":\"working|completed\",\"message\":{\"role\":\"agent\",\"parts\":[{\"type\":\"text\",\"text\":\"<text>\"}]}}}}\n */\nexport function writeA2ASSEEvent(\n res: ServerResponse,\n taskId: string,\n state: \"working\" | \"completed\",\n text: string,\n): void {\n const event = {\n result: {\n id: taskId,\n status: {\n state,\n message: {\n role: \"agent\",\n parts: [{ type: \"text\", text }],\n },\n },\n },\n };\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n}\n\nexport function createA2AServer(opts: A2AServerOptions): {\n server: Server;\n start: () => Promise<void>;\n stop: () => void;\n} {\n const port = opts.port ?? DEFAULT_A2A_PORT;\n const host = opts.host ?? DEFAULT_A2A_HOST;\n const { onTask, onError, abortSignal } = opts;\n\n const agentCard = {\n name: opts.agentName ?? \"OpenClaw Agent\",\n description: opts.agentDescription ?? \"OpenClaw AI assistant via Arinova Chat\",\n url: `http://${host === \"0.0.0.0\" ? \"localhost\" : host}:${port}`,\n version: \"1.0.0\",\n capabilities: {\n streaming: true,\n },\n skills: [\n {\n id: \"chat\",\n name: \"Chat\",\n description: \"General conversation and task assistance\",\n },\n ],\n };\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // CORS headers\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Accept\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Health check\n if (req.url === \"/healthz\") {\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(\"ok\");\n return;\n }\n\n // Agent card\n if (req.url === \"/.well-known/agent.json\" && req.method === \"GET\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(agentCard));\n return;\n }\n\n // A2A task endpoint\n if (req.url === \"/tasks/send\" && req.method === \"POST\") {\n try {\n const body = await readBody(req);\n\n const message = parseA2ARequest(body);\n if (!message) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid A2A request\" }));\n return;\n }\n\n // Set up SSE response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n try {\n await onTask({ message, res });\n } catch (err) {\n const errorText = err instanceof Error ? err.message : String(err);\n if (!res.writableEnded) {\n writeA2ASSEEvent(res, message.taskId, \"completed\", `Error: ${errorText}`);\n res.end();\n }\n onError?.(err instanceof Error ? err : new Error(errorText));\n }\n } catch (err) {\n if (err instanceof Error && err.message === \"Payload too large\") {\n if (!res.headersSent) {\n res.writeHead(413, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Payload too large\" }));\n }\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n }\n }\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n const start = (): Promise<void> => {\n return new Promise((resolve) => {\n server.listen(port, host, () => resolve());\n });\n };\n\n const stop = () => {\n server.close();\n };\n\n if (abortSignal) {\n abortSignal.addEventListener(\"abort\", stop, { once: true });\n }\n\n return { server, start, stop };\n}\n","import type { ServerResponse } from \"node:http\";\nimport { createReplyPrefixOptions, type OpenClawConfig, type RuntimeEnv } from \"openclaw/plugin-sdk\";\nimport type { ResolvedArinovaChatAccount } from \"./accounts.js\";\nimport type { ArinovaChatInboundMessage, CoreConfig } from \"./types.js\";\nimport { getArinovaChatRuntime } from \"./runtime.js\";\nimport { writeA2ASSEEvent } from \"./a2a-server.js\";\n\nconst CHANNEL_ID = \"arinova-chat\" as const;\n\n/**\n * Handle an inbound A2A message: route it to the OpenClaw agent,\n * and stream the reply back as A2A SSE events on the response.\n */\nexport async function handleArinovaChatInbound(params: {\n message: ArinovaChatInboundMessage;\n res: ServerResponse;\n account: ResolvedArinovaChatAccount;\n config: CoreConfig;\n runtime: RuntimeEnv;\n statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;\n}): Promise<void> {\n const { message, res, account, config, runtime, statusSink } = params;\n const core = getArinovaChatRuntime();\n\n const rawBody = message.text.trim();\n if (!rawBody) {\n writeA2ASSEEvent(res, message.taskId, \"completed\", \"\");\n res.end();\n return;\n }\n\n statusSink?.({ lastInboundAt: message.timestamp });\n\n // The sender is the Arinova backend on behalf of the user.\n // A2A protocol doesn't expose user identity, so we use a fixed sender ID.\n const senderId = \"arinova-user\";\n const senderName = \"Arinova User\";\n\n // DM policy check\n const dmPolicy = account.config.dmPolicy ?? \"open\";\n if (dmPolicy === \"disabled\") {\n runtime.log?.(`arinova-chat: drop DM (dmPolicy=disabled)`);\n writeA2ASSEEvent(res, message.taskId, \"completed\", \"\");\n res.end();\n return;\n }\n\n // Resolve agent route\n const route = core.channel.routing.resolveAgentRoute({\n cfg: config as OpenClawConfig,\n channel: CHANNEL_ID,\n accountId: account.accountId,\n peer: {\n kind: \"direct\",\n id: senderId,\n },\n });\n\n const fromLabel = senderName;\n const storePath = core.channel.session.resolveStorePath(\n (config.session as Record<string, unknown> | undefined)?.store as string | undefined,\n { agentId: route.agentId },\n );\n const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config as OpenClawConfig);\n const previousTimestamp = core.channel.session.readSessionUpdatedAt({\n storePath,\n sessionKey: route.sessionKey,\n });\n const body = core.channel.reply.formatAgentEnvelope({\n channel: \"Arinova Chat\",\n from: fromLabel,\n timestamp: message.timestamp,\n previousTimestamp,\n envelope: envelopeOptions,\n body: rawBody,\n });\n\n const ctxPayload = core.channel.reply.finalizeInboundContext({\n Body: body,\n BodyForAgent: rawBody,\n RawBody: rawBody,\n CommandBody: rawBody,\n From: `arinova-chat:${senderId}`,\n To: `arinova-chat:${account.agentId}`,\n SessionKey: route.sessionKey,\n AccountId: route.accountId,\n ChatType: \"direct\",\n ConversationLabel: fromLabel,\n SenderName: senderName,\n SenderId: senderId,\n Provider: CHANNEL_ID,\n Surface: CHANNEL_ID,\n MessageSid: message.taskId,\n Timestamp: message.timestamp,\n OriginatingChannel: CHANNEL_ID,\n OriginatingTo: `arinova-chat:${account.agentId}`,\n CommandAuthorized: true,\n });\n\n await core.channel.session.recordInboundSession({\n storePath,\n sessionKey: ctxPayload.SessionKey ?? route.sessionKey,\n ctx: ctxPayload,\n onRecordError: (err) => {\n runtime.error?.(`arinova-chat: failed updating session meta: ${String(err)}`);\n },\n });\n\n const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({\n cfg: config as OpenClawConfig,\n agentId: route.agentId,\n channel: CHANNEL_ID,\n accountId: account.accountId,\n });\n\n // Track text for final \"completed\" event\n let finalText = \"\";\n\n await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx: ctxPayload,\n cfg: config as OpenClawConfig,\n dispatcherOptions: {\n ...prefixOptions,\n deliver: async (payload) => {\n // Block-level delivery — track final text for \"completed\" event\n const text = (payload as { text?: string }).text ?? \"\";\n if (!text.trim()) return;\n finalText += (finalText ? \"\\n\\n\" : \"\") + text;\n statusSink?.({ lastOutboundAt: Date.now() });\n },\n onError: (err, info) => {\n runtime.error?.(`arinova-chat ${info.kind} reply failed: ${String(err)}`);\n },\n },\n replyOptions: {\n onModelSelected,\n disableBlockStreaming: false,\n onPartialReply: (payload) => {\n // Token-level streaming — text is the full accumulated reply so far\n const text = (payload as { text?: string }).text ?? \"\";\n if (!text) return;\n\n if (!res.writableEnded) {\n writeA2ASSEEvent(res, message.taskId, \"working\", text);\n }\n },\n },\n });\n\n // Send final \"completed\" event and close the SSE stream\n if (!res.writableEnded) {\n writeA2ASSEEvent(res, message.taskId, \"completed\", finalText);\n res.end();\n }\n}\n"],"mappings":"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","names":["DEFAULT_ACCOUNT_ID","normalizeAccountId","runtime","DEFAULT_ACCOUNT_ID","normalizeAccountId","runtime"]}
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "openclaw-arinova-ai",
3
+ "channels": ["arinova-chat"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@arinova-ai/openclaw-arinova-ai",
3
+ "version": "0.0.1",
4
+ "description": "OpenClaw channel plugin for Arinova Chat (A2A protocol with native streaming)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "openclaw.plugin.json"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "tsc --noEmit"
22
+ },
23
+ "openclaw": {
24
+ "extensions": [
25
+ "./dist/index.js"
26
+ ],
27
+ "channel": {
28
+ "id": "arinova-chat",
29
+ "label": "Arinova Chat",
30
+ "selectionLabel": "Arinova Chat (A2A streaming)",
31
+ "docsPath": "/channels/arinova-chat",
32
+ "docsLabel": "arinova-chat",
33
+ "blurb": "Human-to-AI messaging via Arinova Chat with native streaming.",
34
+ "aliases": [
35
+ "arinova"
36
+ ],
37
+ "order": 70,
38
+ "quickstartAllowFrom": true
39
+ }
40
+ },
41
+ "peerDependencies": {
42
+ "openclaw": ">=2026"
43
+ },
44
+ "dependencies": {
45
+ "zod": "^4"
46
+ },
47
+ "devDependencies": {
48
+ "openclaw": "latest",
49
+ "tsup": "^8",
50
+ "typescript": "^5"
51
+ }
52
+ }