adp-openclaw 0.0.53 → 0.0.55
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/monitor.ts +54 -0
- package/src/session-history.ts +1 -64
package/package.json
CHANGED
package/src/monitor.ts
CHANGED
|
@@ -59,6 +59,8 @@ const MsgType = {
|
|
|
59
59
|
// OpenClaw sessions list
|
|
60
60
|
FetchOpenClawSessions: "fetch_openclaw_sessions",
|
|
61
61
|
OpenClawSessionsResponse: "openclaw_sessions_response",
|
|
62
|
+
// Cancel generation
|
|
63
|
+
Cancel: "cancel", // Server → Client: cancel ongoing generation
|
|
62
64
|
} as const;
|
|
63
65
|
|
|
64
66
|
type WSMessage = {
|
|
@@ -167,6 +169,9 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
167
169
|
let authenticated = false;
|
|
168
170
|
let pingInterval: NodeJS.Timeout | null = null;
|
|
169
171
|
|
|
172
|
+
// Track active generations for cancel support (keyed by conversationId)
|
|
173
|
+
const activeGenerations = new Map<string, AbortController>();
|
|
174
|
+
|
|
170
175
|
// Handle abort signal
|
|
171
176
|
const abortHandler = () => {
|
|
172
177
|
ws.close();
|
|
@@ -330,6 +335,9 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
330
335
|
Timestamp: inMsg.timestamp || Date.now(),
|
|
331
336
|
OriginatingChannel: "adp-openclaw",
|
|
332
337
|
OriginatingTo: "adp-openclaw:bot",
|
|
338
|
+
// Authorize slash commands (/new, /status, /reset, etc.)
|
|
339
|
+
// Without this, commands are silently dropped (deny-by-default)
|
|
340
|
+
CommandAuthorized: true,
|
|
333
341
|
// Pass user metadata through context (like Feishu does)
|
|
334
342
|
...userMetadata,
|
|
335
343
|
});
|
|
@@ -340,6 +348,15 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
340
348
|
let lastPartialText = ""; // Track last sent text for delta calculation
|
|
341
349
|
let finalSent = false; // Track if outbound_end has been sent
|
|
342
350
|
const displayName = inMsg.user?.username || inMsg.from;
|
|
351
|
+
|
|
352
|
+
// Per-message AbortController for cancel support
|
|
353
|
+
const generationController = new AbortController();
|
|
354
|
+
const convId = inMsg.conversationId || streamId;
|
|
355
|
+
// Cancel any previous generation for the same conversation
|
|
356
|
+
if (activeGenerations.has(convId)) {
|
|
357
|
+
activeGenerations.get(convId)!.abort();
|
|
358
|
+
}
|
|
359
|
+
activeGenerations.set(convId, generationController);
|
|
343
360
|
|
|
344
361
|
// 收集上传结果,在发送最终回复时追加完整的下载链接
|
|
345
362
|
let pendingUploadResults: AdpUploadToolResult[] = [];
|
|
@@ -427,6 +444,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
427
444
|
// Enable block streaming for SSE support
|
|
428
445
|
replyOptions: {
|
|
429
446
|
disableBlockStreaming: false, // Force enable block streaming
|
|
447
|
+
abortSignal: generationController.signal, // Per-message cancel support
|
|
430
448
|
// Use onPartialReply for real-time streaming (character-level)
|
|
431
449
|
// onPartialReply receives cumulative text, so we need to calculate delta
|
|
432
450
|
onPartialReply: async (payload: { text?: string }) => {
|
|
@@ -556,6 +574,16 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
556
574
|
|
|
557
575
|
log?.info(`[adp-openclaw] dispatchReplyWithBufferedBlockDispatcher returned (finalSent=${finalSent}, chunkIndex=${chunkIndex})`);
|
|
558
576
|
|
|
577
|
+
// Clean up active generation tracking
|
|
578
|
+
activeGenerations.delete(convId);
|
|
579
|
+
|
|
580
|
+
// If generation was cancelled (aborted), send outbound_end with partial text
|
|
581
|
+
if (generationController.signal.aborted && !finalSent) {
|
|
582
|
+
const cancelText = lastPartialText ? `${lastPartialText}\n\n[已停止生成]` : "[已停止生成]";
|
|
583
|
+
log?.info(`[adp-openclaw] Generation cancelled, sending outbound_end with partial text`);
|
|
584
|
+
sendOutboundEnd(cancelText);
|
|
585
|
+
}
|
|
586
|
+
|
|
559
587
|
// IMPORTANT: After dispatchReplyWithBufferedBlockDispatcher completes,
|
|
560
588
|
// ensure outbound_end is sent even if "final" deliver was not called.
|
|
561
589
|
// This handles cases where the SDK only sends blocks without a final callback.
|
|
@@ -566,6 +594,8 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
566
594
|
sendOutboundEnd(finalText);
|
|
567
595
|
}
|
|
568
596
|
} catch (err) {
|
|
597
|
+
// Clean up on error
|
|
598
|
+
activeGenerations.delete(convId);
|
|
569
599
|
log?.error(`[adp-openclaw] Failed to process message: ${err}`);
|
|
570
600
|
}
|
|
571
601
|
break;
|
|
@@ -582,6 +612,30 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
582
612
|
break;
|
|
583
613
|
}
|
|
584
614
|
|
|
615
|
+
// Handle cancel generation request from server (user clicked "stop generating")
|
|
616
|
+
case MsgType.Cancel: {
|
|
617
|
+
if (!authenticated) break;
|
|
618
|
+
const cancelPayload = msg.payload as { conversationId?: string; streamId?: string };
|
|
619
|
+
const cancelConvId = cancelPayload.conversationId || cancelPayload.streamId;
|
|
620
|
+
log?.info(`[adp-openclaw] Received cancel request for conv=${cancelConvId}`);
|
|
621
|
+
|
|
622
|
+
if (cancelConvId && activeGenerations.has(cancelConvId)) {
|
|
623
|
+
activeGenerations.get(cancelConvId)!.abort();
|
|
624
|
+
log?.info(`[adp-openclaw] Generation cancelled for conv=${cancelConvId}`);
|
|
625
|
+
} else {
|
|
626
|
+
// If no specific convId, cancel all active generations
|
|
627
|
+
if (!cancelConvId && activeGenerations.size > 0) {
|
|
628
|
+
for (const [id, controller] of activeGenerations) {
|
|
629
|
+
controller.abort();
|
|
630
|
+
log?.info(`[adp-openclaw] Generation cancelled for conv=${id} (cancel-all)`);
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
log?.warn(`[adp-openclaw] No active generation found for conv=${cancelConvId}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
|
|
585
639
|
// Handle fetch OpenClaw chat history request from GoServer
|
|
586
640
|
case MsgType.ConvHistory: {
|
|
587
641
|
if (!authenticated) break;
|
package/src/session-history.ts
CHANGED
|
@@ -518,69 +518,6 @@ function findOpenClawCli(config: SessionFileConfig): string | null {
|
|
|
518
518
|
return null;
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
/**
|
|
522
|
-
* Extract valid JSON from CLI output that may contain TUI decoration characters
|
|
523
|
-
* (e.g. │, ◇, ◆, spinner frames) mixed into stdout.
|
|
524
|
-
*/
|
|
525
|
-
function extractJsonFromOutput(raw: string): string {
|
|
526
|
-
const trimmed = raw.trim();
|
|
527
|
-
|
|
528
|
-
// Fast path: already valid JSON
|
|
529
|
-
if (
|
|
530
|
-
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
531
|
-
(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
|
532
|
-
) {
|
|
533
|
-
return trimmed;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Try to find the first top-level JSON object or array in the output
|
|
537
|
-
const jsonStart = trimmed.search(/[\[{]/);
|
|
538
|
-
if (jsonStart === -1) {
|
|
539
|
-
return trimmed; // no JSON-like content, return as-is and let caller handle the error
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const opener = trimmed[jsonStart];
|
|
543
|
-
const closer = opener === "{" ? "}" : "]";
|
|
544
|
-
|
|
545
|
-
// Walk forward tracking brace/bracket depth to find the matching close
|
|
546
|
-
let depth = 0;
|
|
547
|
-
let inString = false;
|
|
548
|
-
let escaped = false;
|
|
549
|
-
|
|
550
|
-
for (let i = jsonStart; i < trimmed.length; i++) {
|
|
551
|
-
const ch = trimmed[i];
|
|
552
|
-
|
|
553
|
-
if (escaped) {
|
|
554
|
-
escaped = false;
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
if (ch === "\\") {
|
|
558
|
-
if (inString) escaped = true;
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
if (ch === '"') {
|
|
562
|
-
inString = !inString;
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
if (inString) continue;
|
|
566
|
-
|
|
567
|
-
if (ch === opener || ch === (opener === "{" ? "[" : "{")) {
|
|
568
|
-
// count both kinds of nesting
|
|
569
|
-
if (ch === "{" || ch === "[") depth++;
|
|
570
|
-
}
|
|
571
|
-
if (ch === closer || ch === (closer === "}" ? "]" : "}")) {
|
|
572
|
-
if (ch === "}" || ch === "]") depth--;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (depth === 0) {
|
|
576
|
-
return trimmed.slice(jsonStart, i + 1);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Fallback: return from jsonStart onwards
|
|
581
|
-
return trimmed.slice(jsonStart);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
521
|
/**
|
|
585
522
|
* Execute an openclaw CLI command and return the result.
|
|
586
523
|
* @param subcommands - Array of subcommands, e.g. ["gateway", "call", "chat.history"] or ["sessions"]
|
|
@@ -656,7 +593,7 @@ async function executeClawCommand(
|
|
|
656
593
|
log?.error?.(`[session-history] CLI exited with code ${code}: ${stderr}`);
|
|
657
594
|
reject(new Error(`CLI exited with code ${code}: ${stderr}`));
|
|
658
595
|
} else {
|
|
659
|
-
resolve(
|
|
596
|
+
resolve(stdout);
|
|
660
597
|
}
|
|
661
598
|
});
|
|
662
599
|
});
|