@rainfall-devkit/sdk 0.1.8 → 0.2.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.
- package/README.md +51 -0
- package/dist/chunk-7MRE4ZVI.mjs +662 -0
- package/dist/chunk-AQFC7YAX.mjs +27 -0
- package/dist/chunk-EI7SJH5K.mjs +85 -0
- package/dist/chunk-NTTAVKRT.mjs +89 -0
- package/dist/chunk-RVKW5KBT.mjs +269 -0
- package/dist/chunk-V5QWJVLC.mjs +662 -0
- package/dist/chunk-VDPKDC3R.mjs +869 -0
- package/dist/chunk-WOITG5TG.mjs +84 -0
- package/dist/chunk-XAHJQRBJ.mjs +269 -0
- package/dist/chunk-XEQ6U3JQ.mjs +269 -0
- package/dist/cli/index.js +3797 -632
- package/dist/cli/index.mjs +453 -36
- package/dist/config-7UT7GYSN.mjs +16 -0
- package/dist/config-DDTQQBN7.mjs +14 -0
- package/dist/config-MD45VGWD.mjs +14 -0
- package/dist/config-ZKNHII2A.mjs +8 -0
- package/dist/daemon/index.d.mts +168 -0
- package/dist/daemon/index.d.ts +168 -0
- package/dist/daemon/index.js +3182 -0
- package/dist/daemon/index.mjs +1548 -0
- package/dist/errors-BMPseAnM.d.mts +47 -0
- package/dist/errors-BMPseAnM.d.ts +47 -0
- package/dist/errors-CZdRoYyw.d.ts +332 -0
- package/dist/errors-Chjq1Mev.d.mts +332 -0
- package/dist/index.d.mts +249 -2
- package/dist/index.d.ts +249 -2
- package/dist/index.js +1247 -3
- package/dist/index.mjs +227 -2
- package/dist/listeners-B5Vy9Ao5.d.ts +372 -0
- package/dist/listeners-BbYIaNCs.d.mts +372 -0
- package/dist/listeners-CP2A9J_2.d.ts +372 -0
- package/dist/listeners-CTRSofnm.d.mts +372 -0
- package/dist/listeners-CYI-YwIF.d.mts +372 -0
- package/dist/listeners-DRwITBW_.d.mts +372 -0
- package/dist/listeners-DrMrvFT5.d.ts +372 -0
- package/dist/listeners-MNAnpZj-.d.mts +372 -0
- package/dist/listeners-PZI7iT85.d.ts +372 -0
- package/dist/listeners-QJeEtLbV.d.ts +372 -0
- package/dist/listeners-hp0Ib2Ox.d.ts +372 -0
- package/dist/listeners-jLwetUnx.d.mts +372 -0
- package/dist/mcp.d.mts +7 -2
- package/dist/mcp.d.ts +7 -2
- package/dist/mcp.js +92 -1
- package/dist/mcp.mjs +1 -1
- package/dist/sdk-4OvXPr8E.d.mts +1054 -0
- package/dist/sdk-4OvXPr8E.d.ts +1054 -0
- package/dist/sdk-CJ9g5lFo.d.mts +772 -0
- package/dist/sdk-CJ9g5lFo.d.ts +772 -0
- package/dist/sdk-CN1ezZrI.d.mts +1054 -0
- package/dist/sdk-CN1ezZrI.d.ts +1054 -0
- package/dist/sdk-DD1OeGRJ.d.mts +871 -0
- package/dist/sdk-DD1OeGRJ.d.ts +871 -0
- package/dist/sdk-Xw0BjsLd.d.mts +1054 -0
- package/dist/sdk-Xw0BjsLd.d.ts +1054 -0
- package/dist/types-GnRAfH-h.d.mts +489 -0
- package/dist/types-GnRAfH-h.d.ts +489 -0
- package/package.json +17 -5
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
// src/services/networked.ts
|
|
2
|
+
var RainfallNetworkedExecutor = class {
|
|
3
|
+
rainfall;
|
|
4
|
+
options;
|
|
5
|
+
nodeId;
|
|
6
|
+
jobCallbacks = /* @__PURE__ */ new Map();
|
|
7
|
+
resultPollingInterval;
|
|
8
|
+
constructor(rainfall, options = {}) {
|
|
9
|
+
this.rainfall = rainfall;
|
|
10
|
+
this.options = {
|
|
11
|
+
wsPort: 8765,
|
|
12
|
+
httpPort: 8787,
|
|
13
|
+
hostname: process.env.HOSTNAME || "local-daemon",
|
|
14
|
+
capabilities: {
|
|
15
|
+
localExec: true,
|
|
16
|
+
fileWatch: true,
|
|
17
|
+
passiveListen: true
|
|
18
|
+
},
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register this node with the Rainfall backend
|
|
24
|
+
*/
|
|
25
|
+
async registerNode() {
|
|
26
|
+
const capabilities = this.buildCapabilitiesList();
|
|
27
|
+
try {
|
|
28
|
+
const result = await this.rainfall.executeTool("register-node", {
|
|
29
|
+
hostname: this.options.hostname,
|
|
30
|
+
capabilities,
|
|
31
|
+
wsPort: this.options.wsPort,
|
|
32
|
+
httpPort: this.options.httpPort,
|
|
33
|
+
version: "0.1.0"
|
|
34
|
+
});
|
|
35
|
+
this.nodeId = result.nodeId;
|
|
36
|
+
console.log(`\u{1F310} Node registered with Rainfall as ${this.nodeId}`);
|
|
37
|
+
return this.nodeId;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.nodeId = `node-${this.options.hostname}-${Date.now()}`;
|
|
40
|
+
console.log(`\u{1F310} Node running in local mode (ID: ${this.nodeId})`);
|
|
41
|
+
return this.nodeId;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Unregister this node on shutdown
|
|
46
|
+
*/
|
|
47
|
+
async unregisterNode() {
|
|
48
|
+
if (!this.nodeId) return;
|
|
49
|
+
try {
|
|
50
|
+
await this.rainfall.executeTool("unregister-node", {
|
|
51
|
+
nodeId: this.nodeId
|
|
52
|
+
});
|
|
53
|
+
console.log(`\u{1F310} Node ${this.nodeId} unregistered`);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
if (this.resultPollingInterval) {
|
|
57
|
+
clearInterval(this.resultPollingInterval);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Queue a tool execution for distributed processing
|
|
62
|
+
* Non-blocking - returns immediately with a job ID
|
|
63
|
+
*/
|
|
64
|
+
async queueToolExecution(toolId, params, options = {}) {
|
|
65
|
+
const executionMode = options.executionMode || "any";
|
|
66
|
+
try {
|
|
67
|
+
const result = await this.rainfall.executeTool("queue-job", {
|
|
68
|
+
toolId,
|
|
69
|
+
params,
|
|
70
|
+
executionMode,
|
|
71
|
+
requesterNodeId: this.nodeId
|
|
72
|
+
});
|
|
73
|
+
if (options.callback) {
|
|
74
|
+
this.jobCallbacks.set(result.jobId, options.callback);
|
|
75
|
+
this.startResultPolling();
|
|
76
|
+
}
|
|
77
|
+
return result.jobId;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (executionMode === "local-only" || executionMode === "any") {
|
|
80
|
+
try {
|
|
81
|
+
const result = await this.rainfall.executeTool(toolId, params);
|
|
82
|
+
if (options.callback) {
|
|
83
|
+
options.callback(result);
|
|
84
|
+
}
|
|
85
|
+
return `local-${Date.now()}`;
|
|
86
|
+
} catch (execError) {
|
|
87
|
+
if (options.callback) {
|
|
88
|
+
options.callback(null, String(execError));
|
|
89
|
+
}
|
|
90
|
+
throw execError;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get status of a queued job
|
|
98
|
+
*/
|
|
99
|
+
async getJobStatus(jobId) {
|
|
100
|
+
try {
|
|
101
|
+
const result = await this.rainfall.executeTool("get-job-status", {
|
|
102
|
+
jobId
|
|
103
|
+
});
|
|
104
|
+
return result.job;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to job results via polling (WebSocket fallback)
|
|
111
|
+
* In the future, this will use WebSocket push from ApresMoi
|
|
112
|
+
*/
|
|
113
|
+
async subscribeToResults(callback) {
|
|
114
|
+
console.log("\u{1F4E1} Subscribed to job results via Rainfall (polling mode)");
|
|
115
|
+
this.onResultReceived = callback;
|
|
116
|
+
}
|
|
117
|
+
onResultReceived;
|
|
118
|
+
/**
|
|
119
|
+
* Start polling for job results (fallback until WebSocket push is ready)
|
|
120
|
+
*/
|
|
121
|
+
startResultPolling() {
|
|
122
|
+
if (this.resultPollingInterval) return;
|
|
123
|
+
this.resultPollingInterval = setInterval(async () => {
|
|
124
|
+
for (const [jobId, callback] of this.jobCallbacks) {
|
|
125
|
+
try {
|
|
126
|
+
const job = await this.getJobStatus(jobId);
|
|
127
|
+
if (job?.status === "completed" || job?.status === "failed") {
|
|
128
|
+
callback(job.result, job.error);
|
|
129
|
+
this.jobCallbacks.delete(jobId);
|
|
130
|
+
if (this.onResultReceived) {
|
|
131
|
+
this.onResultReceived(jobId, job.result, job.error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (this.jobCallbacks.size === 0 && this.resultPollingInterval) {
|
|
138
|
+
clearInterval(this.resultPollingInterval);
|
|
139
|
+
this.resultPollingInterval = void 0;
|
|
140
|
+
}
|
|
141
|
+
}, 2e3);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Claim a job for execution on this node
|
|
145
|
+
*/
|
|
146
|
+
async claimJob() {
|
|
147
|
+
try {
|
|
148
|
+
const result = await this.rainfall.executeTool("claim-job", {
|
|
149
|
+
nodeId: this.nodeId,
|
|
150
|
+
capabilities: this.buildCapabilitiesList()
|
|
151
|
+
});
|
|
152
|
+
return result.job;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Submit job result after execution
|
|
159
|
+
*/
|
|
160
|
+
async submitJobResult(jobId, result, error) {
|
|
161
|
+
try {
|
|
162
|
+
await this.rainfall.executeTool("submit-job-result", {
|
|
163
|
+
jobId,
|
|
164
|
+
nodeId: this.nodeId,
|
|
165
|
+
result,
|
|
166
|
+
error
|
|
167
|
+
});
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get this node's ID
|
|
173
|
+
*/
|
|
174
|
+
getNodeId() {
|
|
175
|
+
return this.nodeId;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build capabilities list from options
|
|
179
|
+
*/
|
|
180
|
+
buildCapabilitiesList() {
|
|
181
|
+
const caps = this.options.capabilities || {};
|
|
182
|
+
const list = [];
|
|
183
|
+
if (caps.localExec) list.push("local-exec");
|
|
184
|
+
if (caps.fileWatch) list.push("file-watch");
|
|
185
|
+
if (caps.passiveListen) list.push("passive-listen");
|
|
186
|
+
if (caps.browser) list.push("browser");
|
|
187
|
+
if (caps.custom) list.push(...caps.custom);
|
|
188
|
+
return list;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/services/context.ts
|
|
193
|
+
var RainfallDaemonContext = class {
|
|
194
|
+
rainfall;
|
|
195
|
+
options;
|
|
196
|
+
localMemories = /* @__PURE__ */ new Map();
|
|
197
|
+
sessions = /* @__PURE__ */ new Map();
|
|
198
|
+
executionHistory = [];
|
|
199
|
+
currentSessionId;
|
|
200
|
+
constructor(rainfall, options = {}) {
|
|
201
|
+
this.rainfall = rainfall;
|
|
202
|
+
this.options = {
|
|
203
|
+
maxLocalMemories: 1e3,
|
|
204
|
+
maxMessageHistory: 100,
|
|
205
|
+
maxExecutionHistory: 500,
|
|
206
|
+
sessionTtl: 24 * 60 * 60 * 1e3,
|
|
207
|
+
// 24 hours
|
|
208
|
+
...options
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Initialize the context - load recent memories from cloud
|
|
213
|
+
*/
|
|
214
|
+
async initialize() {
|
|
215
|
+
try {
|
|
216
|
+
const recentMemories = await this.rainfall.memory.recall({
|
|
217
|
+
query: "daemon:context",
|
|
218
|
+
topK: this.options.maxLocalMemories
|
|
219
|
+
});
|
|
220
|
+
for (const memory of recentMemories) {
|
|
221
|
+
this.localMemories.set(memory.id, {
|
|
222
|
+
id: memory.id,
|
|
223
|
+
content: memory.content,
|
|
224
|
+
keywords: memory.keywords || [],
|
|
225
|
+
timestamp: memory.timestamp,
|
|
226
|
+
source: memory.source,
|
|
227
|
+
metadata: memory.metadata
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
console.log(`\u{1F9E0} Loaded ${this.localMemories.size} memories into context`);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.warn("\u26A0\uFE0F Could not sync memories:", error instanceof Error ? error.message : error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Create or get a session
|
|
237
|
+
*/
|
|
238
|
+
getSession(sessionId) {
|
|
239
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
240
|
+
const session = this.sessions.get(sessionId);
|
|
241
|
+
session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
242
|
+
return session;
|
|
243
|
+
}
|
|
244
|
+
const newSession = {
|
|
245
|
+
id: sessionId || `session-${Date.now()}`,
|
|
246
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
247
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
248
|
+
variables: {},
|
|
249
|
+
messageHistory: []
|
|
250
|
+
};
|
|
251
|
+
this.sessions.set(newSession.id, newSession);
|
|
252
|
+
this.currentSessionId = newSession.id;
|
|
253
|
+
return newSession;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get the current active session
|
|
257
|
+
*/
|
|
258
|
+
getCurrentSession() {
|
|
259
|
+
if (this.currentSessionId) {
|
|
260
|
+
return this.sessions.get(this.currentSessionId);
|
|
261
|
+
}
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Set the current active session
|
|
266
|
+
*/
|
|
267
|
+
setCurrentSession(sessionId) {
|
|
268
|
+
if (this.sessions.has(sessionId)) {
|
|
269
|
+
this.currentSessionId = sessionId;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Add a message to the current session history
|
|
274
|
+
*/
|
|
275
|
+
addMessage(role, content) {
|
|
276
|
+
const session = this.getCurrentSession();
|
|
277
|
+
if (!session) return;
|
|
278
|
+
session.messageHistory.push({
|
|
279
|
+
role,
|
|
280
|
+
content,
|
|
281
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
+
});
|
|
283
|
+
if (session.messageHistory.length > this.options.maxMessageHistory) {
|
|
284
|
+
session.messageHistory = session.messageHistory.slice(-this.options.maxMessageHistory);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Store a memory (local + cloud sync)
|
|
289
|
+
*/
|
|
290
|
+
async storeMemory(content, options = {}) {
|
|
291
|
+
const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
292
|
+
const entry = {
|
|
293
|
+
id,
|
|
294
|
+
content,
|
|
295
|
+
keywords: options.keywords || [],
|
|
296
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
297
|
+
source: options.source || "daemon",
|
|
298
|
+
metadata: options.metadata
|
|
299
|
+
};
|
|
300
|
+
this.localMemories.set(id, entry);
|
|
301
|
+
try {
|
|
302
|
+
await this.rainfall.memory.create({
|
|
303
|
+
content,
|
|
304
|
+
keywords: [...options.keywords || [], "daemon:context"],
|
|
305
|
+
metadata: {
|
|
306
|
+
...options.metadata,
|
|
307
|
+
daemonMemoryId: id,
|
|
308
|
+
source: options.source || "daemon"
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.warn("\u26A0\uFE0F Could not sync memory to cloud:", error instanceof Error ? error.message : error);
|
|
313
|
+
}
|
|
314
|
+
this.trimLocalMemories();
|
|
315
|
+
return id;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Recall memories by query
|
|
319
|
+
*/
|
|
320
|
+
async recallMemories(query, topK = 5) {
|
|
321
|
+
const localResults = Array.from(this.localMemories.values()).filter(
|
|
322
|
+
(m) => m.content.toLowerCase().includes(query.toLowerCase()) || m.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase()))
|
|
323
|
+
).slice(0, topK);
|
|
324
|
+
try {
|
|
325
|
+
const cloudResults = await this.rainfall.memory.recall({ query, topK });
|
|
326
|
+
const seen = new Set(localResults.map((r) => r.id));
|
|
327
|
+
for (const mem of cloudResults) {
|
|
328
|
+
if (!seen.has(mem.id)) {
|
|
329
|
+
localResults.push({
|
|
330
|
+
id: mem.id,
|
|
331
|
+
content: mem.content,
|
|
332
|
+
keywords: mem.keywords || [],
|
|
333
|
+
timestamp: mem.timestamp,
|
|
334
|
+
source: mem.source,
|
|
335
|
+
metadata: mem.metadata
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
return localResults.slice(0, topK);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Set a session variable
|
|
345
|
+
*/
|
|
346
|
+
setVariable(key, value) {
|
|
347
|
+
const session = this.getCurrentSession();
|
|
348
|
+
if (session) {
|
|
349
|
+
session.variables[key] = value;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get a session variable
|
|
354
|
+
*/
|
|
355
|
+
getVariable(key) {
|
|
356
|
+
const session = this.getCurrentSession();
|
|
357
|
+
return session?.variables[key];
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Record a tool execution
|
|
361
|
+
*/
|
|
362
|
+
recordExecution(toolId, params, result, options = { duration: 0 }) {
|
|
363
|
+
const record = {
|
|
364
|
+
id: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
365
|
+
toolId,
|
|
366
|
+
params,
|
|
367
|
+
result,
|
|
368
|
+
error: options.error,
|
|
369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
duration: options.duration,
|
|
371
|
+
nodeId: options.nodeId
|
|
372
|
+
};
|
|
373
|
+
this.executionHistory.push(record);
|
|
374
|
+
if (this.executionHistory.length > this.options.maxExecutionHistory) {
|
|
375
|
+
this.executionHistory = this.executionHistory.slice(-this.options.maxExecutionHistory);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get recent execution history
|
|
380
|
+
*/
|
|
381
|
+
getExecutionHistory(limit = 10) {
|
|
382
|
+
return this.executionHistory.slice(-limit).reverse();
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get execution statistics
|
|
386
|
+
*/
|
|
387
|
+
getExecutionStats() {
|
|
388
|
+
const stats = {
|
|
389
|
+
total: this.executionHistory.length,
|
|
390
|
+
successful: 0,
|
|
391
|
+
failed: 0,
|
|
392
|
+
averageDuration: 0,
|
|
393
|
+
byTool: {}
|
|
394
|
+
};
|
|
395
|
+
let totalDuration = 0;
|
|
396
|
+
for (const exec of this.executionHistory) {
|
|
397
|
+
if (exec.error) {
|
|
398
|
+
stats.failed++;
|
|
399
|
+
} else {
|
|
400
|
+
stats.successful++;
|
|
401
|
+
}
|
|
402
|
+
totalDuration += exec.duration;
|
|
403
|
+
stats.byTool[exec.toolId] = (stats.byTool[exec.toolId] || 0) + 1;
|
|
404
|
+
}
|
|
405
|
+
stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;
|
|
406
|
+
return stats;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Clear old sessions based on TTL
|
|
410
|
+
*/
|
|
411
|
+
cleanupSessions() {
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
const ttl = this.options.sessionTtl;
|
|
414
|
+
for (const [id, session] of this.sessions) {
|
|
415
|
+
const lastActivity = new Date(session.lastActivity).getTime();
|
|
416
|
+
if (now - lastActivity > ttl) {
|
|
417
|
+
this.sessions.delete(id);
|
|
418
|
+
if (this.currentSessionId === id) {
|
|
419
|
+
this.currentSessionId = void 0;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get context summary for debugging
|
|
426
|
+
*/
|
|
427
|
+
getStatus() {
|
|
428
|
+
return {
|
|
429
|
+
memoriesCached: this.localMemories.size,
|
|
430
|
+
activeSessions: this.sessions.size,
|
|
431
|
+
currentSession: this.currentSessionId,
|
|
432
|
+
executionHistorySize: this.executionHistory.length
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
trimLocalMemories() {
|
|
436
|
+
if (this.localMemories.size <= this.options.maxLocalMemories) return;
|
|
437
|
+
const entries = Array.from(this.localMemories.entries()).sort((a, b) => new Date(a[1].timestamp).getTime() - new Date(b[1].timestamp).getTime());
|
|
438
|
+
const toRemove = entries.slice(0, entries.length - this.options.maxLocalMemories);
|
|
439
|
+
for (const [id] of toRemove) {
|
|
440
|
+
this.localMemories.delete(id);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// src/services/listeners.ts
|
|
446
|
+
var RainfallListenerRegistry = class {
|
|
447
|
+
rainfall;
|
|
448
|
+
context;
|
|
449
|
+
executor;
|
|
450
|
+
watchers = /* @__PURE__ */ new Map();
|
|
451
|
+
cronIntervals = /* @__PURE__ */ new Map();
|
|
452
|
+
eventHistory = [];
|
|
453
|
+
maxEventHistory = 100;
|
|
454
|
+
constructor(rainfall, context, executor) {
|
|
455
|
+
this.rainfall = rainfall;
|
|
456
|
+
this.context = context;
|
|
457
|
+
this.executor = executor;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Register a file watcher
|
|
461
|
+
* Note: Actual file watching requires fs.watch or chokidar
|
|
462
|
+
* This is the registry - actual watching is done by the daemon
|
|
463
|
+
*/
|
|
464
|
+
async registerFileWatcher(config) {
|
|
465
|
+
console.log(`\u{1F441}\uFE0F Registering file watcher: ${config.name} (${config.watchPath})`);
|
|
466
|
+
const existing = Array.from(this.watchers.keys());
|
|
467
|
+
if (existing.includes(config.id)) {
|
|
468
|
+
await this.unregisterFileWatcher(config.id);
|
|
469
|
+
}
|
|
470
|
+
this.watchers.set(config.id, {
|
|
471
|
+
stop: () => {
|
|
472
|
+
console.log(`\u{1F441}\uFE0F Stopped file watcher: ${config.name}`);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
await this.context.storeMemory(`File watcher registered: ${config.name}`, {
|
|
476
|
+
keywords: ["listener", "file-watcher", config.name],
|
|
477
|
+
metadata: { config }
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Unregister a file watcher
|
|
482
|
+
*/
|
|
483
|
+
async unregisterFileWatcher(id) {
|
|
484
|
+
const watcher = this.watchers.get(id);
|
|
485
|
+
if (watcher) {
|
|
486
|
+
watcher.stop();
|
|
487
|
+
this.watchers.delete(id);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Register a cron trigger
|
|
492
|
+
*/
|
|
493
|
+
async registerCronTrigger(config) {
|
|
494
|
+
console.log(`\u23F0 Registering cron trigger: ${config.name} (${config.cron})`);
|
|
495
|
+
if (this.cronIntervals.has(config.id)) {
|
|
496
|
+
clearInterval(this.cronIntervals.get(config.id));
|
|
497
|
+
this.cronIntervals.delete(config.id);
|
|
498
|
+
}
|
|
499
|
+
const interval = this.parseCronToMs(config.cron);
|
|
500
|
+
if (interval) {
|
|
501
|
+
const intervalId = setInterval(async () => {
|
|
502
|
+
await this.handleCronTick(config);
|
|
503
|
+
}, interval);
|
|
504
|
+
this.cronIntervals.set(config.id, intervalId);
|
|
505
|
+
}
|
|
506
|
+
await this.context.storeMemory(`Cron trigger registered: ${config.name}`, {
|
|
507
|
+
keywords: ["listener", "cron", config.name],
|
|
508
|
+
metadata: { config }
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Unregister a cron trigger
|
|
513
|
+
*/
|
|
514
|
+
unregisterCronTrigger(id) {
|
|
515
|
+
const interval = this.cronIntervals.get(id);
|
|
516
|
+
if (interval) {
|
|
517
|
+
clearInterval(interval);
|
|
518
|
+
this.cronIntervals.delete(id);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Handle a file event
|
|
523
|
+
*/
|
|
524
|
+
async handleFileEvent(watcherId, eventType, filePath) {
|
|
525
|
+
const event = {
|
|
526
|
+
id: `evt-${Date.now()}`,
|
|
527
|
+
type: "file",
|
|
528
|
+
source: watcherId,
|
|
529
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
530
|
+
data: { eventType, filePath }
|
|
531
|
+
};
|
|
532
|
+
this.recordEvent(event);
|
|
533
|
+
console.log(`\u{1F4C1} File event: ${eventType} ${filePath}`);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Handle a cron tick
|
|
537
|
+
*/
|
|
538
|
+
async handleCronTick(config) {
|
|
539
|
+
const event = {
|
|
540
|
+
id: `evt-${Date.now()}`,
|
|
541
|
+
type: "cron",
|
|
542
|
+
source: config.id,
|
|
543
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
544
|
+
data: { cron: config.cron }
|
|
545
|
+
};
|
|
546
|
+
this.recordEvent(event);
|
|
547
|
+
console.log(`\u23F0 Cron tick: ${config.name}`);
|
|
548
|
+
for (const step of config.workflow) {
|
|
549
|
+
try {
|
|
550
|
+
await this.executor.queueToolExecution(step.toolId, {
|
|
551
|
+
...step.params,
|
|
552
|
+
_event: event
|
|
553
|
+
});
|
|
554
|
+
} catch (error) {
|
|
555
|
+
console.error(`\u274C Workflow step failed: ${step.toolId}`, error);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Trigger a manual event (for testing or programmatic triggers)
|
|
561
|
+
*/
|
|
562
|
+
async triggerManual(name, data = {}) {
|
|
563
|
+
const event = {
|
|
564
|
+
id: `evt-${Date.now()}`,
|
|
565
|
+
type: "manual",
|
|
566
|
+
source: name,
|
|
567
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
568
|
+
data
|
|
569
|
+
};
|
|
570
|
+
this.recordEvent(event);
|
|
571
|
+
console.log(`\u{1F446} Manual trigger: ${name}`);
|
|
572
|
+
await this.context.storeMemory(`Manual trigger fired: ${name}`, {
|
|
573
|
+
keywords: ["trigger", "manual", name],
|
|
574
|
+
metadata: { event }
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get recent events
|
|
579
|
+
*/
|
|
580
|
+
getRecentEvents(limit = 10) {
|
|
581
|
+
return this.eventHistory.slice(-limit).reverse();
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get active listeners status
|
|
585
|
+
*/
|
|
586
|
+
getStatus() {
|
|
587
|
+
return {
|
|
588
|
+
fileWatchers: this.watchers.size,
|
|
589
|
+
cronTriggers: this.cronIntervals.size,
|
|
590
|
+
recentEvents: this.eventHistory.length
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Stop all listeners
|
|
595
|
+
*/
|
|
596
|
+
async stopAll() {
|
|
597
|
+
for (const [id] of this.watchers) {
|
|
598
|
+
await this.unregisterFileWatcher(id);
|
|
599
|
+
}
|
|
600
|
+
for (const [id] of this.cronIntervals) {
|
|
601
|
+
this.unregisterCronTrigger(id);
|
|
602
|
+
}
|
|
603
|
+
console.log("\u{1F6D1} All listeners stopped");
|
|
604
|
+
}
|
|
605
|
+
recordEvent(event) {
|
|
606
|
+
this.eventHistory.push(event);
|
|
607
|
+
if (this.eventHistory.length > this.maxEventHistory) {
|
|
608
|
+
this.eventHistory = this.eventHistory.slice(-this.maxEventHistory);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Simple cron parser - converts basic cron expressions to milliseconds
|
|
613
|
+
* Supports: @hourly, @daily, @weekly, and simple intervals like every N minutes
|
|
614
|
+
*/
|
|
615
|
+
parseCronToMs(cron) {
|
|
616
|
+
switch (cron) {
|
|
617
|
+
case "@hourly":
|
|
618
|
+
return 60 * 60 * 1e3;
|
|
619
|
+
case "@daily":
|
|
620
|
+
return 24 * 60 * 60 * 1e3;
|
|
621
|
+
case "@weekly":
|
|
622
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
623
|
+
case "@minutely":
|
|
624
|
+
return 60 * 1e3;
|
|
625
|
+
}
|
|
626
|
+
const match = cron.match(/^\*\/(\d+)\s/);
|
|
627
|
+
if (match) {
|
|
628
|
+
const minutes = parseInt(match[1], 10);
|
|
629
|
+
if (minutes > 0 && minutes <= 60) {
|
|
630
|
+
return minutes * 60 * 1e3;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
console.warn(`\u26A0\uFE0F Unrecognized cron pattern "${cron}", using 1 minute interval`);
|
|
634
|
+
return 60 * 1e3;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
function createFileWatcherWorkflow(name, watchPath, options) {
|
|
638
|
+
return {
|
|
639
|
+
id: `fw-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
640
|
+
name,
|
|
641
|
+
watchPath,
|
|
642
|
+
pattern: options.pattern,
|
|
643
|
+
events: options.events || ["create"],
|
|
644
|
+
workflow: options.workflow
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function createCronWorkflow(name, cron, workflow) {
|
|
648
|
+
return {
|
|
649
|
+
id: `cron-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
650
|
+
name,
|
|
651
|
+
cron,
|
|
652
|
+
workflow
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export {
|
|
657
|
+
RainfallNetworkedExecutor,
|
|
658
|
+
RainfallDaemonContext,
|
|
659
|
+
RainfallListenerRegistry,
|
|
660
|
+
createFileWatcherWorkflow,
|
|
661
|
+
createCronWorkflow
|
|
662
|
+
};
|