@parkgogogo/openclaw-reflection 0.1.0

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,25 @@
1
+ declare module "proper-lockfile" {
2
+ export interface RetryOptions {
3
+ retries?: number;
4
+ factor?: number;
5
+ minTimeout?: number;
6
+ maxTimeout?: number;
7
+ randomize?: boolean;
8
+ }
9
+
10
+ export interface LockOptions {
11
+ retries?: number | RetryOptions;
12
+ stale?: number;
13
+ realpath?: boolean;
14
+ }
15
+
16
+ export type Release = () => Promise<void>;
17
+
18
+ export function lock(filePath: string, options?: LockOptions): Promise<Release>;
19
+
20
+ const lockfile: {
21
+ lock: typeof lock;
22
+ };
23
+
24
+ export default lockfile;
25
+ }
@@ -0,0 +1,114 @@
1
+ import { CircularBuffer } from "./buffer.js";
2
+ import type { Logger, ReflectionMessage } from "./types.js";
3
+
4
+ interface SessionData {
5
+ buffer: CircularBuffer<ReflectionMessage>;
6
+ lastAccessed: number;
7
+ processedAgentMessageIds: Set<string>;
8
+ pendingTask: Promise<void>;
9
+ }
10
+
11
+ export class SessionBufferManager {
12
+ private sessions: Map<string, SessionData>;
13
+ private capacity: number;
14
+ private logger: Logger;
15
+
16
+ constructor(capacity: number, logger: Logger) {
17
+ this.capacity = capacity;
18
+ this.logger = logger;
19
+ this.sessions = new Map();
20
+ }
21
+
22
+ private getOrCreateSessionData(sessionKey: string): SessionData {
23
+ let sessionData = this.sessions.get(sessionKey);
24
+
25
+ if (!sessionData) {
26
+ this.logger.info(
27
+ "SessionBufferManager",
28
+ "Creating new session buffer",
29
+ { sessionKey },
30
+ sessionKey
31
+ );
32
+ sessionData = {
33
+ buffer: new CircularBuffer<ReflectionMessage>(this.capacity),
34
+ lastAccessed: Date.now(),
35
+ processedAgentMessageIds: new Set<string>(),
36
+ pendingTask: Promise.resolve(),
37
+ };
38
+ this.sessions.set(sessionKey, sessionData);
39
+ }
40
+
41
+ sessionData.lastAccessed = Date.now();
42
+ return sessionData;
43
+ }
44
+
45
+ push(sessionKey: string, message: ReflectionMessage): void {
46
+ const sessionData = this.getOrCreateSessionData(sessionKey);
47
+
48
+ const evicted = sessionData.buffer.push(message);
49
+ if (evicted) {
50
+ this.logger.info(
51
+ "SessionBufferManager",
52
+ "Evicted oldest message",
53
+ { evictedId: evicted.id },
54
+ sessionKey
55
+ );
56
+ }
57
+
58
+ sessionData.lastAccessed = Date.now();
59
+ }
60
+
61
+ getMessages(sessionKey: string): ReflectionMessage[] {
62
+ const sessionData = this.sessions.get(sessionKey);
63
+
64
+ if (!sessionData) {
65
+ return [];
66
+ }
67
+
68
+ sessionData.lastAccessed = Date.now();
69
+ return sessionData.buffer.toArray();
70
+ }
71
+
72
+ hasProcessedAgentMessage(sessionKey: string, messageId: string): boolean {
73
+ const sessionData = this.sessions.get(sessionKey);
74
+ if (!sessionData) {
75
+ return false;
76
+ }
77
+
78
+ sessionData.lastAccessed = Date.now();
79
+ return sessionData.processedAgentMessageIds.has(messageId);
80
+ }
81
+
82
+ markProcessedAgentMessage(sessionKey: string, messageId: string): void {
83
+ const sessionData = this.getOrCreateSessionData(sessionKey);
84
+ sessionData.processedAgentMessageIds.add(messageId);
85
+ }
86
+
87
+ runExclusive(sessionKey: string, task: () => Promise<void>): Promise<void> {
88
+ const sessionData = this.getOrCreateSessionData(sessionKey);
89
+ const nextTask = sessionData.pendingTask
90
+ .catch(() => undefined)
91
+ .then(task);
92
+
93
+ sessionData.pendingTask = nextTask.catch(() => undefined);
94
+ return nextTask;
95
+ }
96
+
97
+ clearSession(sessionKey: string): void {
98
+ const existed = this.sessions.has(sessionKey);
99
+ this.sessions.delete(sessionKey);
100
+
101
+ if (existed) {
102
+ this.logger.info(
103
+ "SessionBufferManager",
104
+ "Session cleared",
105
+ { sessionKey },
106
+ sessionKey
107
+ );
108
+ }
109
+ }
110
+
111
+ getSessionCount(): number {
112
+ return this.sessions.size;
113
+ }
114
+ }
package/src/types.ts ADDED
@@ -0,0 +1,109 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+
3
+ export interface Logger {
4
+ debug(
5
+ component: string,
6
+ event: string,
7
+ details?: Record<string, unknown>,
8
+ sessionKey?: string
9
+ ): void;
10
+ info(
11
+ component: string,
12
+ event: string,
13
+ details?: Record<string, unknown>,
14
+ sessionKey?: string
15
+ ): void;
16
+ warn(
17
+ component: string,
18
+ event: string,
19
+ details?: Record<string, unknown>,
20
+ sessionKey?: string
21
+ ): void;
22
+ error(
23
+ component: string,
24
+ event: string,
25
+ details?: Record<string, unknown>,
26
+ sessionKey?: string
27
+ ): void;
28
+ }
29
+
30
+ export interface ReflectionMessage {
31
+ id: string;
32
+ role: "user" | "agent";
33
+ message: string;
34
+ timestamp: number;
35
+ sessionKey: string;
36
+ channelId: string;
37
+ metadata?: {
38
+ from?: string;
39
+ to?: string;
40
+ messageId?: string;
41
+ success?: boolean;
42
+ };
43
+ }
44
+
45
+ export interface LogEntry {
46
+ timestamp: string;
47
+ level: LogLevel;
48
+ component: string;
49
+ sessionKey?: string;
50
+ event: string;
51
+ details?: Record<string, unknown>;
52
+ }
53
+
54
+ export type MemoryDecision =
55
+ | "NO_WRITE"
56
+ | "UPDATE_MEMORY"
57
+ | "UPDATE_USER"
58
+ | "UPDATE_SOUL"
59
+ | "UPDATE_IDENTITY"
60
+ | "UPDATE_TOOLS";
61
+
62
+ export interface MemoryGateOutput {
63
+ decision: MemoryDecision;
64
+ reason: string;
65
+ candidateFact?: string;
66
+ }
67
+
68
+ export interface MemoryGateConfig {
69
+ enabled: boolean;
70
+ windowSize: number;
71
+ }
72
+
73
+ export interface ConsolidationConfig {
74
+ enabled: boolean;
75
+ schedule: string;
76
+ }
77
+
78
+ export interface LLMConfig {
79
+ baseURL: string;
80
+ apiKey: string;
81
+ model: string;
82
+ }
83
+
84
+ export interface PluginConfig {
85
+ bufferSize: number;
86
+ logLevel: LogLevel;
87
+ llm: LLMConfig;
88
+ memoryGate: MemoryGateConfig;
89
+ consolidation: ConsolidationConfig;
90
+ }
91
+
92
+ export type { MemoryGateInput } from "./memory-gate/types.js";
93
+ export type {
94
+ AgentRunResult,
95
+ AgentStep,
96
+ AgentTool,
97
+ GenerateObjectParams,
98
+ JsonSchema,
99
+ LLMService,
100
+ LLMServiceConfig,
101
+ LLMServiceOptions,
102
+ RunAgentParams,
103
+ } from "./llm/types.js";
104
+ export type {
105
+ ConsolidatedFilename,
106
+ ConsolidationPatch,
107
+ ConsolidationProposal,
108
+ ConsolidationResult,
109
+ } from "./consolidation/types.js";
@@ -0,0 +1,228 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import lockfile from "proper-lockfile";
4
+
5
+ const LOCK_TIMEOUT_MS = 5000;
6
+
7
+ function getErrorMessage(error: unknown): string {
8
+ if (error instanceof Error) {
9
+ return error.message;
10
+ }
11
+
12
+ return String(error);
13
+ }
14
+
15
+ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
16
+ return typeof error === "object" && error !== null && "code" in error;
17
+ }
18
+
19
+ function formatDate(date: Date): string {
20
+ const year = date.getFullYear();
21
+ const month = String(date.getMonth() + 1).padStart(2, "0");
22
+ const day = String(date.getDate()).padStart(2, "0");
23
+ return `${year}-${month}-${day}.md`;
24
+ }
25
+
26
+ async function createFileIfMissing(filePath: string): Promise<void> {
27
+ try {
28
+ await fs.access(filePath);
29
+ } catch (error) {
30
+ if (isNodeError(error) && error.code === "ENOENT") {
31
+ const handle = await fs.open(filePath, "a");
32
+ await handle.close();
33
+ return;
34
+ }
35
+
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ async function lockForWrite(filePath: string): Promise<() => Promise<void>> {
41
+ try {
42
+ await ensureDir(path.dirname(filePath));
43
+ await createFileIfMissing(filePath);
44
+
45
+ return await lockfile.lock(filePath, {
46
+ retries: {
47
+ retries: 5,
48
+ factor: 1,
49
+ minTimeout: 1000,
50
+ maxTimeout: 1000,
51
+ randomize: false,
52
+ },
53
+ stale: LOCK_TIMEOUT_MS,
54
+ realpath: false,
55
+ });
56
+ } catch (error) {
57
+ throw new Error(
58
+ `Failed to acquire lock for "${filePath}" within ${LOCK_TIMEOUT_MS}ms: ${getErrorMessage(error)}`
59
+ );
60
+ }
61
+ }
62
+
63
+ async function releaseLock(
64
+ release: () => Promise<void>,
65
+ filePath: string
66
+ ): Promise<void> {
67
+ try {
68
+ await release();
69
+ } catch (error) {
70
+ throw new Error(
71
+ `Failed to release lock for "${filePath}": ${getErrorMessage(error)}`
72
+ );
73
+ }
74
+ }
75
+
76
+ export async function ensureDir(dirPath: string): Promise<void> {
77
+ try {
78
+ await fs.mkdir(dirPath, { recursive: true });
79
+ } catch (error) {
80
+ throw new Error(`Failed to ensure directory "${dirPath}": ${getErrorMessage(error)}`);
81
+ }
82
+ }
83
+
84
+ export async function readFile(filePath: string): Promise<string | null> {
85
+ try {
86
+ return await fs.readFile(filePath, "utf8");
87
+ } catch (error) {
88
+ if (isNodeError(error) && error.code === "ENOENT") {
89
+ return null;
90
+ }
91
+
92
+ throw new Error(`Failed to read file "${filePath}": ${getErrorMessage(error)}`);
93
+ }
94
+ }
95
+
96
+ export async function writeFile(filePath: string, content: string): Promise<void> {
97
+ try {
98
+ await ensureDir(path.dirname(filePath));
99
+ await fs.writeFile(filePath, content, "utf8");
100
+ } catch (error) {
101
+ throw new Error(`Failed to write file "${filePath}": ${getErrorMessage(error)}`);
102
+ }
103
+ }
104
+
105
+ export async function writeFileWithLock(
106
+ filePath: string,
107
+ content: string
108
+ ): Promise<void> {
109
+ const release = await lockForWrite(filePath);
110
+ let writeError: unknown;
111
+
112
+ try {
113
+ await writeFile(filePath, content);
114
+ } catch (error) {
115
+ writeError = error;
116
+ }
117
+
118
+ try {
119
+ await releaseLock(release, filePath);
120
+ } catch (releaseError) {
121
+ if (writeError) {
122
+ throw new Error(
123
+ `Write and release failed for "${filePath}": write=${getErrorMessage(writeError)}; release=${getErrorMessage(releaseError)}`
124
+ );
125
+ }
126
+
127
+ throw releaseError;
128
+ }
129
+
130
+ if (writeError) {
131
+ throw new Error(
132
+ `Failed to write file with lock "${filePath}": ${getErrorMessage(writeError)}`
133
+ );
134
+ }
135
+ }
136
+
137
+ export async function appendFile(filePath: string, content: string): Promise<void> {
138
+ try {
139
+ await ensureDir(path.dirname(filePath));
140
+ await fs.appendFile(filePath, content, "utf8");
141
+ } catch (error) {
142
+ throw new Error(`Failed to append file "${filePath}": ${getErrorMessage(error)}`);
143
+ }
144
+ }
145
+
146
+ export async function appendFileWithLock(
147
+ filePath: string,
148
+ content: string
149
+ ): Promise<void> {
150
+ const release = await lockForWrite(filePath);
151
+ let appendError: unknown;
152
+
153
+ try {
154
+ await appendFile(filePath, content);
155
+ } catch (error) {
156
+ appendError = error;
157
+ }
158
+
159
+ try {
160
+ await releaseLock(release, filePath);
161
+ } catch (releaseError) {
162
+ if (appendError) {
163
+ throw new Error(
164
+ `Append and release failed for "${filePath}": append=${getErrorMessage(appendError)}; release=${getErrorMessage(releaseError)}`
165
+ );
166
+ }
167
+
168
+ throw releaseError;
169
+ }
170
+
171
+ if (appendError) {
172
+ throw new Error(
173
+ `Failed to append file with lock "${filePath}": ${getErrorMessage(appendError)}`
174
+ );
175
+ }
176
+ }
177
+
178
+ export async function listFiles(dirPath: string): Promise<string[]> {
179
+ try {
180
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
181
+
182
+ return entries
183
+ .filter((entry) => entry.isFile())
184
+ .map((entry) => entry.name)
185
+ .sort();
186
+ } catch (error) {
187
+ if (isNodeError(error) && error.code === "ENOENT") {
188
+ return [];
189
+ }
190
+
191
+ throw new Error(
192
+ `Failed to list files in "${dirPath}": ${getErrorMessage(error)}`
193
+ );
194
+ }
195
+ }
196
+
197
+ export async function moveFile(fromPath: string, toPath: string): Promise<void> {
198
+ try {
199
+ await ensureDir(path.dirname(toPath));
200
+ await fs.rename(fromPath, toPath);
201
+ } catch (error) {
202
+ if (isNodeError(error) && error.code === "EXDEV") {
203
+ try {
204
+ await fs.copyFile(fromPath, toPath);
205
+ await fs.unlink(fromPath);
206
+ return;
207
+ } catch (copyError) {
208
+ throw new Error(
209
+ `Failed to move file "${fromPath}" to "${toPath}": ${getErrorMessage(copyError)}`
210
+ );
211
+ }
212
+ }
213
+
214
+ throw new Error(
215
+ `Failed to move file "${fromPath}" to "${toPath}": ${getErrorMessage(error)}`
216
+ );
217
+ }
218
+ }
219
+
220
+ export function getTodayFilename(): string {
221
+ return formatDate(new Date());
222
+ }
223
+
224
+ export function getYesterdayFilename(): string {
225
+ const date = new Date();
226
+ date.setDate(date.getDate() - 1);
227
+ return formatDate(date);
228
+ }