@parkgogogo/openclaw-reflection 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/README.zh-CN.md +8 -5
- package/package.json +1 -1
- package/src/index.ts +4 -1
- package/src/message-handler.ts +107 -21
- package/src/message-reaction.ts +96 -0
- package/src/types.ts +39 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">OpenClaw Reflection</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="./assets/openclaw-reflection-logo.png" alt="OpenClaw Reflection logo" width="180" />
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center"><strong>Make OpenClaw's native memory system sharper without replacing it.</strong></p>
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img alt="OpenClaw Plugin" src="https://img.shields.io/badge/OpenClaw-Plugin-111111?style=flat-square" />
|
|
11
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.x-3178c6?style=flat-square" />
|
|
12
|
+
<img alt="memory_gate 18 cases" src="https://img.shields.io/badge/memory_gate-18%20benchmark%20cases-2ea043?style=flat-square" />
|
|
13
|
+
<img alt="write_guardian 14 cases" src="https://img.shields.io/badge/write_guardian-14%20benchmark%20cases-2ea043?style=flat-square" />
|
|
14
|
+
</p>
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
<p align="center"><a href="./README.zh-CN.md">中文文档</a></p>
|
|
15
17
|
|
|
16
18
|
OpenClaw Reflection is an additive layer on top of OpenClaw's built-in Markdown memory system. It captures message flow, keeps thread noise out of long-term memory, writes durable knowledge into the same human-readable memory files OpenClaw already uses, and periodically consolidates them so your agent gets sharper over time instead of messier.
|
|
17
19
|
|
|
@@ -112,6 +114,7 @@ Once the gateway restarts, Reflection will begin listening to `message_received`
|
|
|
112
114
|
- Reflection now writes an independent write_guardian audit log to:
|
|
113
115
|
- `<workspaceDir>/.openclaw-reflection/write-guardian.log.jsonl`
|
|
114
116
|
- When `logLevel` is `debug`, Reflection also overwrites `logs/debug.json` with the latest raw `message_received` callback payload.
|
|
117
|
+
- When `write_guardian` successfully writes durable memory, Reflection reacts to the triggering user message with `📝`.
|
|
115
118
|
- Register command: `reflections`
|
|
116
119
|
- Returns the most recent 10 write_guardian behaviors (written/refused/failed/skipped), including decision, target file, and reason.
|
|
117
120
|
|
package/README.zh-CN.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">OpenClaw Reflection</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="./assets/openclaw-reflection-logo.png" alt="OpenClaw Reflection logo" width="180" />
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
英文版: [README.md](./README.md)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img alt="OpenClaw Plugin" src="https://img.shields.io/badge/OpenClaw-Plugin-111111?style=flat-square" />
|
|
13
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.x-3178c6?style=flat-square" />
|
|
14
|
+
<img alt="memory_gate 18 cases" src="https://img.shields.io/badge/memory_gate-18%20benchmark%20cases-2ea043?style=flat-square" />
|
|
15
|
+
<img alt="write_guardian 14 cases" src="https://img.shields.io/badge/write_guardian-14%20benchmark%20cases-2ea043?style=flat-square" />
|
|
16
|
+
</p>
|
|
15
17
|
|
|
16
18
|
OpenClaw Reflection 是叠加在 OpenClaw 原生 Markdown memory 之上的一层增强插件。它负责监听消息流,过滤线程噪音,把真正长期有效的信息写回 OpenClaw 的核心记忆文件,并定期整理这些文件,避免长期使用后越记越乱。
|
|
17
19
|
|
|
@@ -107,6 +109,7 @@ Gateway 重启后,Reflection 就会开始监听 `message_received` 和 `before
|
|
|
107
109
|
- Reflection 现在会给 write_guardian 单独写一份审计日志:
|
|
108
110
|
- `<workspaceDir>/.openclaw-reflection/write-guardian.log.jsonl`
|
|
109
111
|
- 当 `logLevel` 为 `debug` 时,Reflection 还会把最近一次 `message_received` callback 的原始 payload 覆盖写入 `logs/debug.json`。
|
|
112
|
+
- 当 `write_guardian` 成功写入长期记忆时,Reflection 会给触发这次写入的用户消息补一个 `📝` reaction。
|
|
110
113
|
- 注册命令:`reflections`
|
|
111
114
|
- 返回最近 10 条 write_guardian 行为(written/refused/failed/skipped),包含 decision、目标文件和原因。
|
|
112
115
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
handleBeforeMessageWrite,
|
|
21
21
|
handleMessageReceived,
|
|
22
22
|
} from "./message-handler.js";
|
|
23
|
+
import { OpenClawMessageReactionService } from "./message-reaction.js";
|
|
23
24
|
import { SessionBufferManager } from "./session-manager.js";
|
|
24
25
|
import type {
|
|
25
26
|
LLMService,
|
|
@@ -275,6 +276,7 @@ export default function activate(api: PluginAPI): void {
|
|
|
275
276
|
let memoryGate: MemoryGateAnalyzer | undefined;
|
|
276
277
|
let writeGuardian: WriteGuardian | undefined;
|
|
277
278
|
let writeGuardianAuditLog: WriteGuardianAuditLog | undefined;
|
|
279
|
+
const reactionService = new OpenClawMessageReactionService(logger);
|
|
278
280
|
|
|
279
281
|
if (config.memoryGate.enabled && llmService) {
|
|
280
282
|
memoryGate = new MemoryGateAnalyzer(llmService, logger);
|
|
@@ -342,7 +344,8 @@ export default function activate(api: PluginAPI): void {
|
|
|
342
344
|
context,
|
|
343
345
|
memoryGate,
|
|
344
346
|
writeGuardian,
|
|
345
|
-
config.memoryGate.windowSize
|
|
347
|
+
config.memoryGate.windowSize,
|
|
348
|
+
reactionService
|
|
346
349
|
);
|
|
347
350
|
} else {
|
|
348
351
|
logger.warn("PluginLifecycle", "Callback skipped: buffer manager missing", {
|
package/src/message-handler.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { SessionBufferManager } from "./session-manager.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
Logger,
|
|
4
|
+
MessageHookContext,
|
|
5
|
+
MessageReactionInput,
|
|
6
|
+
MessageReactionService,
|
|
7
|
+
MessageReceivedHookEvent,
|
|
8
|
+
ReflectionMessage,
|
|
9
|
+
} from "./types.js";
|
|
3
10
|
import { MemoryGateAnalyzer, type MemoryGateOutput } from "./memory-gate/index.js";
|
|
4
11
|
import { WriteGuardian } from "./write-guardian/index.js";
|
|
5
12
|
import { ulid } from "ulid";
|
|
@@ -26,19 +33,6 @@ interface MessageEvent {
|
|
|
26
33
|
channelId?: string;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
interface MessageHookContext {
|
|
30
|
-
channelId?: string;
|
|
31
|
-
accountId?: string;
|
|
32
|
-
conversationId?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface MessageReceivedHookEvent {
|
|
36
|
-
from?: string;
|
|
37
|
-
content?: string;
|
|
38
|
-
timestamp?: number;
|
|
39
|
-
metadata?: Record<string, unknown>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
36
|
interface BeforeMessageWriteEvent {
|
|
43
37
|
message?: {
|
|
44
38
|
role?: string;
|
|
@@ -480,6 +474,7 @@ function createReflectionMessage(
|
|
|
480
474
|
messageId: event.message?.id,
|
|
481
475
|
from: event.from,
|
|
482
476
|
to: event.to,
|
|
477
|
+
accountId: event.accountId,
|
|
483
478
|
success: event.success,
|
|
484
479
|
};
|
|
485
480
|
|
|
@@ -507,6 +502,80 @@ function findLatestMessageByRole(
|
|
|
507
502
|
return "";
|
|
508
503
|
}
|
|
509
504
|
|
|
505
|
+
function findLatestUserReactionTarget(
|
|
506
|
+
messages: ReflectionMessage[]
|
|
507
|
+
): MessageReactionInput | null {
|
|
508
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
509
|
+
const message = messages[index];
|
|
510
|
+
if (message.role !== "user") {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const messageId = getNonEmptyString(message.metadata?.messageId);
|
|
515
|
+
const target = getNonEmptyString(message.metadata?.to);
|
|
516
|
+
const channelId = getNonEmptyString(message.channelId);
|
|
517
|
+
|
|
518
|
+
if (!messageId || !target || !channelId) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
channelId,
|
|
524
|
+
accountId: getNonEmptyString(message.metadata?.accountId),
|
|
525
|
+
target,
|
|
526
|
+
messageId,
|
|
527
|
+
emoji: "📝",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function isReactionService(
|
|
535
|
+
value: unknown
|
|
536
|
+
): value is MessageReactionService {
|
|
537
|
+
return (
|
|
538
|
+
typeof value === "object" &&
|
|
539
|
+
value !== null &&
|
|
540
|
+
"reactToMessage" in value &&
|
|
541
|
+
typeof (value as { reactToMessage?: unknown }).reactToMessage === "function"
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function resolveHandlerOptions(
|
|
546
|
+
memoryGateWindowSizeOrReactionService: number | MessageReactionService | undefined,
|
|
547
|
+
reactionServiceOrWindowSize: MessageReactionService | number | undefined
|
|
548
|
+
): {
|
|
549
|
+
memoryGateWindowSize: number;
|
|
550
|
+
reactionService?: MessageReactionService;
|
|
551
|
+
} {
|
|
552
|
+
const defaultOptions = {
|
|
553
|
+
memoryGateWindowSize: DEFAULT_MEMORY_GATE_WINDOW_SIZE,
|
|
554
|
+
reactionService: undefined,
|
|
555
|
+
} as const;
|
|
556
|
+
|
|
557
|
+
if (typeof memoryGateWindowSizeOrReactionService === "number") {
|
|
558
|
+
return {
|
|
559
|
+
memoryGateWindowSize: memoryGateWindowSizeOrReactionService,
|
|
560
|
+
reactionService: isReactionService(reactionServiceOrWindowSize)
|
|
561
|
+
? reactionServiceOrWindowSize
|
|
562
|
+
: undefined,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (isReactionService(memoryGateWindowSizeOrReactionService)) {
|
|
567
|
+
return {
|
|
568
|
+
memoryGateWindowSize:
|
|
569
|
+
typeof reactionServiceOrWindowSize === "number"
|
|
570
|
+
? reactionServiceOrWindowSize
|
|
571
|
+
: DEFAULT_MEMORY_GATE_WINDOW_SIZE,
|
|
572
|
+
reactionService: memoryGateWindowSizeOrReactionService,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return defaultOptions;
|
|
577
|
+
}
|
|
578
|
+
|
|
510
579
|
function isUpdateDecision(
|
|
511
580
|
decision: MemoryGateOutput["decision"]
|
|
512
581
|
): decision is
|
|
@@ -530,7 +599,8 @@ async function triggerMemoryGate(
|
|
|
530
599
|
memoryGate: MemoryGateAnalyzer,
|
|
531
600
|
writeGuardian: WriteGuardian | undefined,
|
|
532
601
|
logger: Logger,
|
|
533
|
-
memoryGateWindowSize: number
|
|
602
|
+
memoryGateWindowSize: number,
|
|
603
|
+
reactionService?: MessageReactionService
|
|
534
604
|
): Promise<void> {
|
|
535
605
|
const normalizedWindowSize = Number.isInteger(memoryGateWindowSize)
|
|
536
606
|
? Math.max(memoryGateWindowSize, 1)
|
|
@@ -578,6 +648,11 @@ async function triggerMemoryGate(
|
|
|
578
648
|
},
|
|
579
649
|
sessionKey
|
|
580
650
|
);
|
|
651
|
+
|
|
652
|
+
const reactionTarget = findLatestUserReactionTarget(sessionMessages);
|
|
653
|
+
if (reactionService && reactionTarget) {
|
|
654
|
+
await reactionService.reactToMessage(reactionTarget);
|
|
655
|
+
}
|
|
581
656
|
} else if (writeResult.status === "refused") {
|
|
582
657
|
logger.info(
|
|
583
658
|
"MessageHandler",
|
|
@@ -698,8 +773,14 @@ function handleAgentMessage(
|
|
|
698
773
|
hookContext?: unknown,
|
|
699
774
|
memoryGate?: MemoryGateAnalyzer,
|
|
700
775
|
writeGuardian?: WriteGuardian,
|
|
701
|
-
|
|
776
|
+
memoryGateWindowSizeOrReactionService?: number | MessageReactionService,
|
|
777
|
+
reactionServiceOrWindowSize?: MessageReactionService | number
|
|
702
778
|
): void {
|
|
779
|
+
const { memoryGateWindowSize, reactionService } = resolveHandlerOptions(
|
|
780
|
+
memoryGateWindowSizeOrReactionService,
|
|
781
|
+
reactionServiceOrWindowSize
|
|
782
|
+
);
|
|
783
|
+
|
|
703
784
|
const normalizedEvent = normalizeSentEvent(event, hookContext);
|
|
704
785
|
logHookPayloadDebug(
|
|
705
786
|
logger,
|
|
@@ -773,7 +854,8 @@ function handleAgentMessage(
|
|
|
773
854
|
memoryGate,
|
|
774
855
|
writeGuardian,
|
|
775
856
|
logger,
|
|
776
|
-
memoryGateWindowSize
|
|
857
|
+
memoryGateWindowSize,
|
|
858
|
+
reactionService
|
|
777
859
|
)
|
|
778
860
|
);
|
|
779
861
|
}
|
|
@@ -786,7 +868,8 @@ export function handleMessageSent(
|
|
|
786
868
|
hookContext?: unknown,
|
|
787
869
|
memoryGate?: MemoryGateAnalyzer,
|
|
788
870
|
writeGuardian?: WriteGuardian,
|
|
789
|
-
|
|
871
|
+
memoryGateWindowSizeOrReactionService?: number | MessageReactionService,
|
|
872
|
+
reactionServiceOrWindowSize?: MessageReactionService | number
|
|
790
873
|
): void {
|
|
791
874
|
handleAgentMessage(
|
|
792
875
|
event,
|
|
@@ -796,7 +879,8 @@ export function handleMessageSent(
|
|
|
796
879
|
hookContext,
|
|
797
880
|
memoryGate,
|
|
798
881
|
writeGuardian,
|
|
799
|
-
|
|
882
|
+
memoryGateWindowSizeOrReactionService,
|
|
883
|
+
reactionServiceOrWindowSize
|
|
800
884
|
);
|
|
801
885
|
}
|
|
802
886
|
|
|
@@ -807,7 +891,8 @@ export function handleBeforeMessageWrite(
|
|
|
807
891
|
hookContext?: unknown,
|
|
808
892
|
memoryGate?: MemoryGateAnalyzer,
|
|
809
893
|
writeGuardian?: WriteGuardian,
|
|
810
|
-
|
|
894
|
+
memoryGateWindowSizeOrReactionService?: number | MessageReactionService,
|
|
895
|
+
reactionServiceOrWindowSize?: MessageReactionService | number
|
|
811
896
|
): void {
|
|
812
897
|
const normalizedEvent = normalizeBeforeMessageWriteEvent(event, hookContext);
|
|
813
898
|
logHookPayloadDebug(
|
|
@@ -834,7 +919,8 @@ export function handleBeforeMessageWrite(
|
|
|
834
919
|
hookContext,
|
|
835
920
|
memoryGate,
|
|
836
921
|
writeGuardian,
|
|
837
|
-
|
|
922
|
+
memoryGateWindowSizeOrReactionService,
|
|
923
|
+
reactionServiceOrWindowSize
|
|
838
924
|
);
|
|
839
925
|
}
|
|
840
926
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import type { Logger, MessageReactionInput, MessageReactionService } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
type CommandRunner = (command: string, args: string[]) => Promise<void>;
|
|
8
|
+
|
|
9
|
+
async function defaultCommandRunner(command: string, args: string[]): Promise<void> {
|
|
10
|
+
await execFileAsync(command, args);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getNonEmptyString(value: string | undefined): string | undefined {
|
|
14
|
+
if (typeof value !== "string") {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getErrorMessage(error: unknown): string {
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return error.message;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return String(error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class OpenClawMessageReactionService implements MessageReactionService {
|
|
31
|
+
private readonly logger: Logger;
|
|
32
|
+
private readonly commandRunner: CommandRunner;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
logger: Logger,
|
|
36
|
+
commandRunner: CommandRunner = defaultCommandRunner
|
|
37
|
+
) {
|
|
38
|
+
this.logger = logger;
|
|
39
|
+
this.commandRunner = commandRunner;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async reactToMessage(input: MessageReactionInput): Promise<boolean> {
|
|
43
|
+
const channelId = getNonEmptyString(input.channelId);
|
|
44
|
+
const accountId = getNonEmptyString(input.accountId);
|
|
45
|
+
const target = getNonEmptyString(input.target);
|
|
46
|
+
const messageId = getNonEmptyString(input.messageId);
|
|
47
|
+
const emoji = getNonEmptyString(input.emoji);
|
|
48
|
+
|
|
49
|
+
if (!channelId || !target || !messageId || !emoji) {
|
|
50
|
+
this.logger.warn("MessageReaction", "Skipped reaction with incomplete target", {
|
|
51
|
+
channelId,
|
|
52
|
+
accountId,
|
|
53
|
+
target,
|
|
54
|
+
messageId,
|
|
55
|
+
emoji,
|
|
56
|
+
});
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const args = [
|
|
61
|
+
"message",
|
|
62
|
+
"react",
|
|
63
|
+
"--channel",
|
|
64
|
+
channelId,
|
|
65
|
+
...(accountId ? ["--account", accountId] : []),
|
|
66
|
+
"--target",
|
|
67
|
+
target,
|
|
68
|
+
"--message-id",
|
|
69
|
+
messageId,
|
|
70
|
+
"--emoji",
|
|
71
|
+
emoji,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await this.commandRunner("openclaw", args);
|
|
76
|
+
this.logger.info("MessageReaction", "Applied reaction to message", {
|
|
77
|
+
channelId,
|
|
78
|
+
accountId,
|
|
79
|
+
target,
|
|
80
|
+
messageId,
|
|
81
|
+
emoji,
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger.warn("MessageReaction", "Failed to apply reaction to message", {
|
|
86
|
+
channelId,
|
|
87
|
+
accountId,
|
|
88
|
+
target,
|
|
89
|
+
messageId,
|
|
90
|
+
emoji,
|
|
91
|
+
reason: getErrorMessage(error),
|
|
92
|
+
});
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -38,10 +38,49 @@ export interface ReflectionMessage {
|
|
|
38
38
|
from?: string;
|
|
39
39
|
to?: string;
|
|
40
40
|
messageId?: string;
|
|
41
|
+
accountId?: string;
|
|
41
42
|
success?: boolean;
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export interface MessageHookContext {
|
|
47
|
+
channelId?: string;
|
|
48
|
+
accountId?: string;
|
|
49
|
+
conversationId?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MessageReceivedHookMetadata {
|
|
53
|
+
to?: string;
|
|
54
|
+
provider?: string;
|
|
55
|
+
surface?: string;
|
|
56
|
+
originatingChannel?: string;
|
|
57
|
+
originatingTo?: string;
|
|
58
|
+
messageId?: string;
|
|
59
|
+
senderId?: string;
|
|
60
|
+
senderName?: string;
|
|
61
|
+
senderUsername?: string;
|
|
62
|
+
guildId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MessageReceivedHookEvent {
|
|
66
|
+
from?: string;
|
|
67
|
+
content?: string;
|
|
68
|
+
timestamp?: number;
|
|
69
|
+
metadata?: MessageReceivedHookMetadata;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MessageReactionInput {
|
|
73
|
+
channelId: string;
|
|
74
|
+
target: string;
|
|
75
|
+
messageId: string;
|
|
76
|
+
emoji: string;
|
|
77
|
+
accountId?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface MessageReactionService {
|
|
81
|
+
reactToMessage(input: MessageReactionInput): Promise<boolean>;
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
export interface LogEntry {
|
|
46
85
|
timestamp: string;
|
|
47
86
|
level: LogLevel;
|