adp-openclaw 0.0.61 → 0.0.63
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/package.json +1 -1
- package/src/adp-upload-tool.ts +1 -1
- package/src/channel.ts +1 -0
- package/src/monitor.ts +33 -7
- package/src/session-history.ts +79 -1
package/package.json
CHANGED
package/src/adp-upload-tool.ts
CHANGED
|
@@ -68,7 +68,7 @@ interface FileMetadata {
|
|
|
68
68
|
// ==================== 常量配置 ====================
|
|
69
69
|
|
|
70
70
|
/** 获取临时密钥的接口地址 */
|
|
71
|
-
const CREDENTIAL_API_URL = "https://wss.lke.cloud.tencent.com/
|
|
71
|
+
const CREDENTIAL_API_URL = "https://wss.lke.cloud.tencent.com/v1/gateway/storage/get_credential";
|
|
72
72
|
|
|
73
73
|
/** 请求超时时间 (毫秒) */
|
|
74
74
|
const REQUEST_TIMEOUT_MS = 30000;
|
package/src/channel.ts
CHANGED
package/src/monitor.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type MonitorParams = {
|
|
|
40
40
|
abortSignal?: AbortSignal;
|
|
41
41
|
log?: PluginLogger;
|
|
42
42
|
cfg?: ClawdbotConfig; // OpenClaw config for model settings
|
|
43
|
+
setStatus?: (next: Record<string, unknown>) => void; // SDK health-monitor status reporter
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
// WebSocket message types
|
|
@@ -165,7 +166,7 @@ async function markSessionAborted(params: {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
168
|
-
const { wsUrl, clientToken, signKey, abortSignal, log, cfg } = params;
|
|
169
|
+
const { wsUrl, clientToken, signKey, abortSignal, log, cfg, setStatus } = params;
|
|
169
170
|
const runtime = getAdpOpenclawRuntime();
|
|
170
171
|
|
|
171
172
|
log?.info(`[adp-openclaw] WebSocket monitor started, connecting to ${wsUrl}`);
|
|
@@ -180,6 +181,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
180
181
|
log,
|
|
181
182
|
runtime,
|
|
182
183
|
cfg,
|
|
184
|
+
setStatus,
|
|
183
185
|
});
|
|
184
186
|
} catch (err) {
|
|
185
187
|
if (abortSignal?.aborted) break;
|
|
@@ -204,10 +206,11 @@ type ConnectParams = {
|
|
|
204
206
|
log?: PluginLogger;
|
|
205
207
|
runtime: ReturnType<typeof getAdpOpenclawRuntime>;
|
|
206
208
|
cfg?: ClawdbotConfig;
|
|
209
|
+
setStatus?: (next: Record<string, unknown>) => void;
|
|
207
210
|
};
|
|
208
211
|
|
|
209
212
|
async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
210
|
-
const { wsUrl, clientToken, signKey, abortSignal, log, runtime, cfg } = params;
|
|
213
|
+
const { wsUrl, clientToken, signKey, abortSignal, log, runtime, cfg, setStatus } = params;
|
|
211
214
|
|
|
212
215
|
// Dynamic import for WebSocket (works in both Node.js and browser)
|
|
213
216
|
const WebSocket = (await import("ws")).default;
|
|
@@ -272,6 +275,10 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
272
275
|
authenticated = true;
|
|
273
276
|
log?.info(`[adp-openclaw] Plugin v${PLUGIN_VERSION} authenticated as client ${result.clientId}`);
|
|
274
277
|
|
|
278
|
+
// Report connected status to SDK health-monitor
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
setStatus?.({ connected: true, lastConnectedAt: now, lastEventAt: now });
|
|
281
|
+
|
|
275
282
|
// Start ping interval
|
|
276
283
|
pingInterval = setInterval(() => {
|
|
277
284
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -290,12 +297,17 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
290
297
|
}
|
|
291
298
|
|
|
292
299
|
case MsgType.Pong:
|
|
293
|
-
// Heartbeat response
|
|
300
|
+
// Heartbeat response — update lastEventAt so health-monitor knows connection is alive
|
|
301
|
+
setStatus?.({ lastEventAt: Date.now() });
|
|
294
302
|
break;
|
|
295
303
|
|
|
296
304
|
case MsgType.Inbound: {
|
|
297
305
|
if (!authenticated) break;
|
|
298
306
|
|
|
307
|
+
// Report inbound event to SDK health-monitor
|
|
308
|
+
const inboundAt = Date.now();
|
|
309
|
+
setStatus?.({ lastEventAt: inboundAt, lastInboundAt: inboundAt });
|
|
310
|
+
|
|
299
311
|
// Debug: log raw payload to verify recordId is received
|
|
300
312
|
log?.info(`[adp-openclaw] Raw payload: ${JSON.stringify(msg.payload)}`);
|
|
301
313
|
|
|
@@ -326,15 +338,27 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
326
338
|
|
|
327
339
|
// Use resolveAgentRoute to get proper sessionKey (like QQBot does)
|
|
328
340
|
// This ensures session history is correctly associated
|
|
329
|
-
|
|
341
|
+
//
|
|
342
|
+
// Use "per-channel-peer" so the session key includes the channel name:
|
|
343
|
+
// agent:main:adp-openclaw:direct:{userId}
|
|
344
|
+
// This is critical for cron job delivery inference — inferDeliveryFromSessionKey()
|
|
345
|
+
// parses the session key to extract channel and target user:
|
|
346
|
+
// channel = "adp-openclaw", to = "{userId}"
|
|
347
|
+
// Without the channel in the key, cron delivery defaults to channel:"last" which
|
|
348
|
+
// fails when multiple channels (adp-openclaw, wecom, etc.) are configured.
|
|
349
|
+
//
|
|
350
|
+
// We use the user identifier (not conversationId) as peerId because:
|
|
351
|
+
// 1. The outbound delivery target is the userId (inMsg.from), not the conversationId
|
|
352
|
+
// 2. inferDeliveryFromSessionKey extracts peerId as the delivery "to" field
|
|
353
|
+
const peerId = userIdentifier;
|
|
330
354
|
const route = runtime.channel.routing.resolveAgentRoute({
|
|
331
355
|
cfg: {
|
|
332
356
|
...(cfg ?? {}),
|
|
333
357
|
session: {
|
|
334
358
|
...(cfg?.session ?? {}),
|
|
335
|
-
// Override dmScope to "per-peer" so
|
|
336
|
-
//
|
|
337
|
-
dmScope: "per-peer",
|
|
359
|
+
// Override dmScope to "per-channel-peer" so session key encodes channel + userId
|
|
360
|
+
// Session key format: agent:main:adp-openclaw:direct:{userId}
|
|
361
|
+
dmScope: "per-channel-peer",
|
|
338
362
|
},
|
|
339
363
|
},
|
|
340
364
|
channel: "adp-openclaw",
|
|
@@ -849,6 +873,8 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
849
873
|
abortSignal?.removeEventListener("abort", abortHandler);
|
|
850
874
|
// Clear active WebSocket when connection closes
|
|
851
875
|
setActiveWebSocket(null);
|
|
876
|
+
// Report disconnected status to SDK health-monitor
|
|
877
|
+
setStatus?.({ connected: false, lastEventAt: Date.now() });
|
|
852
878
|
log?.info(`[adp-openclaw] WebSocket closed: ${code} ${reason.toString()}`);
|
|
853
879
|
resolve();
|
|
854
880
|
});
|
package/src/session-history.ts
CHANGED
|
@@ -517,6 +517,84 @@ function findOpenClawCli(config: SessionFileConfig): string | null {
|
|
|
517
517
|
|
|
518
518
|
return null;
|
|
519
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Extract valid JSON from CLI output that may contain TUI decoration characters
|
|
522
|
+
* (e.g. │, ◇, ◆, spinner frames) mixed into stdout.
|
|
523
|
+
*/
|
|
524
|
+
function extractJsonFromOutput(raw: string): string {
|
|
525
|
+
// First, filter out TUI decoration lines (box-drawing, UI separators, warnings)
|
|
526
|
+
const filtered = raw
|
|
527
|
+
.split("\n")
|
|
528
|
+
.filter((line) => {
|
|
529
|
+
// Skip empty lines
|
|
530
|
+
if (!line.trim()) return false;
|
|
531
|
+
// Skip lines starting with box-drawing characters
|
|
532
|
+
if (/^[│├┌┐└┘┤┬┴┼─]/.test(line)) return false;
|
|
533
|
+
// Skip UI separator lines
|
|
534
|
+
if (/^[◆◇]/.test(line)) return false;
|
|
535
|
+
// Skip config warning messages (non-JSON)
|
|
536
|
+
if (line.startsWith("Config warnings")) return false;
|
|
537
|
+
return true;
|
|
538
|
+
})
|
|
539
|
+
.join("\n");
|
|
540
|
+
|
|
541
|
+
const trimmed = filtered.trim();
|
|
542
|
+
|
|
543
|
+
// Fast path: already valid JSON
|
|
544
|
+
if (
|
|
545
|
+
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
546
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
|
547
|
+
) {
|
|
548
|
+
return trimmed;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Try to find the first top-level JSON object or array in the output
|
|
552
|
+
const jsonStart = trimmed.search(/[\[{]/);
|
|
553
|
+
if (jsonStart === -1) {
|
|
554
|
+
return trimmed; // no JSON-like content, return as-is and let caller handle the error
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const opener = trimmed[jsonStart];
|
|
558
|
+
const closer = opener === "{" ? "}" : "]";
|
|
559
|
+
|
|
560
|
+
// Walk forward tracking brace/bracket depth to find the matching close
|
|
561
|
+
let depth = 0;
|
|
562
|
+
let inString = false;
|
|
563
|
+
let escaped = false;
|
|
564
|
+
|
|
565
|
+
for (let i = jsonStart; i < trimmed.length; i++) {
|
|
566
|
+
const ch = trimmed[i];
|
|
567
|
+
|
|
568
|
+
if (escaped) {
|
|
569
|
+
escaped = false;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (ch === "\\") {
|
|
573
|
+
if (inString) escaped = true;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (ch === '"') {
|
|
577
|
+
inString = !inString;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (inString) continue;
|
|
581
|
+
|
|
582
|
+
if (ch === opener || ch === (opener === "{" ? "[" : "{")) {
|
|
583
|
+
// count both kinds of nesting
|
|
584
|
+
if (ch === "{" || ch === "[") depth++;
|
|
585
|
+
}
|
|
586
|
+
if (ch === closer || ch === (closer === "}" ? "]" : "}")) {
|
|
587
|
+
if (ch === "}" || ch === "]") depth--;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (depth === 0) {
|
|
591
|
+
return trimmed.slice(jsonStart, i + 1);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Fallback: return from jsonStart onwards
|
|
596
|
+
return trimmed.slice(jsonStart);
|
|
597
|
+
}
|
|
520
598
|
|
|
521
599
|
/**
|
|
522
600
|
* Execute an openclaw CLI command and return the result.
|
|
@@ -593,7 +671,7 @@ async function executeClawCommand(
|
|
|
593
671
|
log?.error?.(`[session-history] CLI exited with code ${code}: ${stderr}`);
|
|
594
672
|
reject(new Error(`CLI exited with code ${code}: ${stderr}`));
|
|
595
673
|
} else {
|
|
596
|
-
resolve(stdout);
|
|
674
|
+
resolve(extractJsonFromOutput(stdout));
|
|
597
675
|
}
|
|
598
676
|
});
|
|
599
677
|
});
|