@rainfall-devkit/sdk 0.1.7 → 0.2.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.
Files changed (40) hide show
  1. package/README.md +51 -0
  2. package/dist/chunk-7MRE4ZVI.mjs +662 -0
  3. package/dist/chunk-AQFC7YAX.mjs +27 -0
  4. package/dist/chunk-RA3HDYF4.mjs +778 -0
  5. package/dist/chunk-V5QWJVLC.mjs +662 -0
  6. package/dist/chunk-VDPKDC3R.mjs +869 -0
  7. package/dist/chunk-WOITG5TG.mjs +84 -0
  8. package/dist/cli/index.js +2756 -607
  9. package/dist/cli/index.mjs +404 -46
  10. package/dist/config-DDTQQBN7.mjs +14 -0
  11. package/dist/config-ZKNHII2A.mjs +8 -0
  12. package/dist/daemon/index.d.mts +136 -0
  13. package/dist/daemon/index.d.ts +136 -0
  14. package/dist/daemon/index.js +2473 -0
  15. package/dist/daemon/index.mjs +836 -0
  16. package/dist/errors-BMPseAnM.d.mts +47 -0
  17. package/dist/errors-BMPseAnM.d.ts +47 -0
  18. package/dist/errors-CZdRoYyw.d.ts +332 -0
  19. package/dist/errors-Chjq1Mev.d.mts +332 -0
  20. package/dist/index.d.mts +3 -1
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +762 -5
  23. package/dist/index.mjs +14 -2
  24. package/dist/listeners-BbYIaNCs.d.mts +372 -0
  25. package/dist/listeners-CP2A9J_2.d.ts +372 -0
  26. package/dist/listeners-CTRSofnm.d.mts +372 -0
  27. package/dist/listeners-CYI-YwIF.d.mts +372 -0
  28. package/dist/listeners-QJeEtLbV.d.ts +372 -0
  29. package/dist/listeners-hp0Ib2Ox.d.ts +372 -0
  30. package/dist/mcp.d.mts +3 -2
  31. package/dist/mcp.d.ts +3 -2
  32. package/dist/mcp.js +95 -3
  33. package/dist/mcp.mjs +1 -1
  34. package/dist/sdk-CJ9g5lFo.d.mts +772 -0
  35. package/dist/sdk-CJ9g5lFo.d.ts +772 -0
  36. package/dist/sdk-DD1OeGRJ.d.mts +871 -0
  37. package/dist/sdk-DD1OeGRJ.d.ts +871 -0
  38. package/dist/types-GnRAfH-h.d.mts +489 -0
  39. package/dist/types-GnRAfH-h.d.ts +489 -0
  40. package/package.json +14 -5
package/dist/index.js CHANGED
@@ -25,13 +25,18 @@ __export(index_exports, {
25
25
  NotFoundError: () => NotFoundError,
26
26
  Rainfall: () => Rainfall,
27
27
  RainfallClient: () => RainfallClient,
28
+ RainfallDaemonContext: () => RainfallDaemonContext,
28
29
  RainfallError: () => RainfallError,
30
+ RainfallListenerRegistry: () => RainfallListenerRegistry,
31
+ RainfallNetworkedExecutor: () => RainfallNetworkedExecutor,
29
32
  RateLimitError: () => RateLimitError,
30
33
  ServerError: () => ServerError,
31
34
  TimeoutError: () => TimeoutError,
32
35
  ToolNotFoundError: () => ToolNotFoundError,
33
36
  VERSION: () => VERSION,
34
- ValidationError: () => ValidationError
37
+ ValidationError: () => ValidationError,
38
+ createCronWorkflow: () => createCronWorkflow,
39
+ createFileWatcherWorkflow: () => createFileWatcherWorkflow
35
40
  });
36
41
  module.exports = __toCommonJS(index_exports);
37
42
 
@@ -277,10 +282,11 @@ var RainfallClient = class {
277
282
  */
278
283
  async executeTool(toolId, params, options) {
279
284
  const subscriberId = await this.ensureSubscriberId();
280
- return this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}`, {
285
+ const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}`, {
281
286
  method: "POST",
282
- body: params
287
+ body: params || {}
283
288
  }, options);
289
+ return response.result;
284
290
  }
285
291
  /**
286
292
  * List all available tools
@@ -347,6 +353,49 @@ var RainfallClient = class {
347
353
  sleep(ms) {
348
354
  return new Promise((resolve) => setTimeout(resolve, ms));
349
355
  }
356
+ /**
357
+ * OpenAI-compatible chat completions with tool support
358
+ */
359
+ async chatCompletions(params) {
360
+ const { subscriber_id, ...body } = params;
361
+ if (body.stream) {
362
+ const url = `${this.baseUrl}/olympic/subscribers/${subscriber_id}/v1/chat/completions`;
363
+ const response = await fetch(url, {
364
+ method: "POST",
365
+ headers: {
366
+ "x-api-key": this.apiKey,
367
+ "Content-Type": "application/json",
368
+ "Accept": "text/event-stream"
369
+ },
370
+ body: JSON.stringify(body)
371
+ });
372
+ if (!response.ok) {
373
+ const error = await response.text();
374
+ throw new RainfallError(`Chat completions failed: ${error}`, "CHAT_ERROR");
375
+ }
376
+ if (!response.body) {
377
+ throw new RainfallError("No response body", "CHAT_ERROR");
378
+ }
379
+ return response.body;
380
+ }
381
+ return this.request(
382
+ `/olympic/subscribers/${subscriber_id}/v1/chat/completions`,
383
+ {
384
+ method: "POST",
385
+ body
386
+ }
387
+ );
388
+ }
389
+ /**
390
+ * List available models (OpenAI-compatible format)
391
+ */
392
+ async listModels(subscriberId) {
393
+ const sid = subscriberId || this.subscriberId || await this.ensureSubscriberId();
394
+ const result = await this.request(
395
+ `/olympic/subscribers/${sid}/v1/models`
396
+ );
397
+ return result.data || [];
398
+ }
350
399
  };
351
400
 
352
401
  // src/namespaces/integrations.ts
@@ -516,7 +565,12 @@ function createAI(client) {
516
565
  chat: (params) => client.executeTool("xai-chat-completions", params),
517
566
  complete: (params) => client.executeTool("fim", params),
518
567
  classify: (params) => client.executeTool("jina-document-classifier", params),
519
- segment: (params) => client.executeTool("jina-text-segmenter", params)
568
+ segment: (params) => client.executeTool("jina-text-segmenter", params),
569
+ /**
570
+ * OpenAI-compatible chat completions with full tool support
571
+ * This is the recommended method for multi-turn conversations with tools
572
+ */
573
+ chatCompletions: (params) => client.chatCompletions(params)
520
574
  };
521
575
  }
522
576
 
@@ -796,7 +850,705 @@ var Rainfall = class {
796
850
  getRateLimitInfo() {
797
851
  return this.client.getRateLimitInfo();
798
852
  }
853
+ /**
854
+ * OpenAI-compatible chat completions with tool support
855
+ *
856
+ * @example
857
+ * ```typescript
858
+ * // Simple chat
859
+ * const response = await rainfall.chatCompletions({
860
+ * subscriber_id: 'my-subscriber',
861
+ * messages: [{ role: 'user', content: 'Hello!' }],
862
+ * model: 'llama-3.3-70b-versatile'
863
+ * });
864
+ *
865
+ * // With tools
866
+ * const response = await rainfall.chatCompletions({
867
+ * subscriber_id: 'my-subscriber',
868
+ * messages: [{ role: 'user', content: 'Search for AI news' }],
869
+ * tools: [{ type: 'function', function: { name: 'web-search' } }],
870
+ * enable_stacked: true
871
+ * });
872
+ *
873
+ * // Streaming
874
+ * const stream = await rainfall.chatCompletions({
875
+ * subscriber_id: 'my-subscriber',
876
+ * messages: [{ role: 'user', content: 'Tell me a story' }],
877
+ * stream: true
878
+ * });
879
+ * ```
880
+ */
881
+ async chatCompletions(params) {
882
+ return this.client.chatCompletions(params);
883
+ }
884
+ /**
885
+ * List available models (OpenAI-compatible format)
886
+ *
887
+ * @example
888
+ * ```typescript
889
+ * const models = await rainfall.listModels();
890
+ * console.log(models); // [{ id: 'llama-3.3-70b-versatile', ... }]
891
+ * ```
892
+ */
893
+ async listModels(subscriberId) {
894
+ return this.client.listModels(subscriberId);
895
+ }
896
+ };
897
+
898
+ // src/services/networked.ts
899
+ var RainfallNetworkedExecutor = class {
900
+ rainfall;
901
+ options;
902
+ edgeNodeId;
903
+ jobCallbacks = /* @__PURE__ */ new Map();
904
+ resultPollingInterval;
905
+ constructor(rainfall, options = {}) {
906
+ this.rainfall = rainfall;
907
+ this.options = {
908
+ wsPort: 8765,
909
+ httpPort: 8787,
910
+ hostname: process.env.HOSTNAME || "local-daemon",
911
+ capabilities: {
912
+ localExec: true,
913
+ fileWatch: true,
914
+ passiveListen: true
915
+ },
916
+ ...options
917
+ };
918
+ }
919
+ /**
920
+ * Register this edge node with the Rainfall backend
921
+ */
922
+ async registerEdgeNode() {
923
+ const capabilities = this.buildCapabilitiesList();
924
+ try {
925
+ const result = await this.rainfall.executeTool("register-edge-node", {
926
+ hostname: this.options.hostname,
927
+ capabilities,
928
+ wsPort: this.options.wsPort,
929
+ httpPort: this.options.httpPort,
930
+ version: "0.1.0"
931
+ });
932
+ this.edgeNodeId = result.edgeNodeId;
933
+ console.log(`\u{1F310} Edge node registered with Rainfall as ${this.edgeNodeId}`);
934
+ return this.edgeNodeId;
935
+ } catch (error) {
936
+ this.edgeNodeId = `edge-${this.options.hostname}-${Date.now()}`;
937
+ console.log(`\u{1F310} Edge node running in local mode (ID: ${this.edgeNodeId})`);
938
+ return this.edgeNodeId;
939
+ }
940
+ }
941
+ /**
942
+ * Unregister this edge node on shutdown
943
+ */
944
+ async unregisterEdgeNode() {
945
+ if (!this.edgeNodeId) return;
946
+ try {
947
+ await this.rainfall.executeTool("unregister-edge-node", {
948
+ edgeNodeId: this.edgeNodeId
949
+ });
950
+ console.log(`\u{1F310} Edge node ${this.edgeNodeId} unregistered`);
951
+ } catch {
952
+ }
953
+ if (this.resultPollingInterval) {
954
+ clearInterval(this.resultPollingInterval);
955
+ }
956
+ }
957
+ /**
958
+ * Queue a tool execution for distributed processing
959
+ * Non-blocking - returns immediately with a job ID
960
+ */
961
+ async queueToolExecution(toolId, params, options = {}) {
962
+ const executionMode = options.executionMode || "any";
963
+ try {
964
+ const result = await this.rainfall.executeTool("queue-job", {
965
+ toolId,
966
+ params,
967
+ executionMode,
968
+ requesterEdgeNodeId: this.edgeNodeId
969
+ });
970
+ if (options.callback) {
971
+ this.jobCallbacks.set(result.jobId, options.callback);
972
+ this.startResultPolling();
973
+ }
974
+ return result.jobId;
975
+ } catch (error) {
976
+ if (executionMode === "local-only" || executionMode === "any") {
977
+ try {
978
+ const result = await this.rainfall.executeTool(toolId, params);
979
+ if (options.callback) {
980
+ options.callback(result);
981
+ }
982
+ return `local-${Date.now()}`;
983
+ } catch (execError) {
984
+ if (options.callback) {
985
+ options.callback(null, String(execError));
986
+ }
987
+ throw execError;
988
+ }
989
+ }
990
+ throw error;
991
+ }
992
+ }
993
+ /**
994
+ * Get status of a queued job
995
+ */
996
+ async getJobStatus(jobId) {
997
+ try {
998
+ const result = await this.rainfall.executeTool("get-job-status", {
999
+ jobId
1000
+ });
1001
+ return result.job;
1002
+ } catch {
1003
+ return null;
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Subscribe to job results via polling (WebSocket fallback)
1008
+ * In the future, this will use WebSocket push from ApresMoi
1009
+ */
1010
+ async subscribeToResults(callback) {
1011
+ console.log("\u{1F4E1} Subscribed to job results via Rainfall (polling mode)");
1012
+ this.onResultReceived = callback;
1013
+ }
1014
+ onResultReceived;
1015
+ /**
1016
+ * Start polling for job results (fallback until WebSocket push is ready)
1017
+ */
1018
+ startResultPolling() {
1019
+ if (this.resultPollingInterval) return;
1020
+ this.resultPollingInterval = setInterval(async () => {
1021
+ for (const [jobId, callback] of this.jobCallbacks) {
1022
+ try {
1023
+ const job = await this.getJobStatus(jobId);
1024
+ if (job?.status === "completed" || job?.status === "failed") {
1025
+ callback(job.result, job.error);
1026
+ this.jobCallbacks.delete(jobId);
1027
+ if (this.onResultReceived) {
1028
+ this.onResultReceived(jobId, job.result, job.error);
1029
+ }
1030
+ }
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ if (this.jobCallbacks.size === 0 && this.resultPollingInterval) {
1035
+ clearInterval(this.resultPollingInterval);
1036
+ this.resultPollingInterval = void 0;
1037
+ }
1038
+ }, 2e3);
1039
+ }
1040
+ /**
1041
+ * Claim a job for execution on this edge node
1042
+ */
1043
+ async claimJob() {
1044
+ try {
1045
+ const result = await this.rainfall.executeTool("claim-job", {
1046
+ edgeNodeId: this.edgeNodeId,
1047
+ capabilities: this.buildCapabilitiesList()
1048
+ });
1049
+ return result.job;
1050
+ } catch {
1051
+ return null;
1052
+ }
1053
+ }
1054
+ /**
1055
+ * Submit job result after execution
1056
+ */
1057
+ async submitJobResult(jobId, result, error) {
1058
+ try {
1059
+ await this.rainfall.executeTool("submit-job-result", {
1060
+ jobId,
1061
+ edgeNodeId: this.edgeNodeId,
1062
+ result,
1063
+ error
1064
+ });
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Get this edge node's ID
1070
+ */
1071
+ getEdgeNodeId() {
1072
+ return this.edgeNodeId;
1073
+ }
1074
+ /**
1075
+ * Build capabilities list from options
1076
+ */
1077
+ buildCapabilitiesList() {
1078
+ const caps = this.options.capabilities || {};
1079
+ const list = [];
1080
+ if (caps.localExec) list.push("local-exec");
1081
+ if (caps.fileWatch) list.push("file-watch");
1082
+ if (caps.passiveListen) list.push("passive-listen");
1083
+ if (caps.browser) list.push("browser");
1084
+ if (caps.custom) list.push(...caps.custom);
1085
+ return list;
1086
+ }
1087
+ };
1088
+
1089
+ // src/services/context.ts
1090
+ var RainfallDaemonContext = class {
1091
+ rainfall;
1092
+ options;
1093
+ localMemories = /* @__PURE__ */ new Map();
1094
+ sessions = /* @__PURE__ */ new Map();
1095
+ executionHistory = [];
1096
+ currentSessionId;
1097
+ constructor(rainfall, options = {}) {
1098
+ this.rainfall = rainfall;
1099
+ this.options = {
1100
+ maxLocalMemories: 1e3,
1101
+ maxMessageHistory: 100,
1102
+ maxExecutionHistory: 500,
1103
+ sessionTtl: 24 * 60 * 60 * 1e3,
1104
+ // 24 hours
1105
+ ...options
1106
+ };
1107
+ }
1108
+ /**
1109
+ * Initialize the context - load recent memories from cloud
1110
+ */
1111
+ async initialize() {
1112
+ try {
1113
+ const recentMemories = await this.rainfall.memory.recall({
1114
+ query: "daemon:context",
1115
+ topK: this.options.maxLocalMemories
1116
+ });
1117
+ for (const memory of recentMemories) {
1118
+ this.localMemories.set(memory.id, {
1119
+ id: memory.id,
1120
+ content: memory.content,
1121
+ keywords: memory.keywords || [],
1122
+ timestamp: memory.timestamp,
1123
+ source: memory.source,
1124
+ metadata: memory.metadata
1125
+ });
1126
+ }
1127
+ console.log(`\u{1F9E0} Loaded ${this.localMemories.size} memories into context`);
1128
+ } catch (error) {
1129
+ console.warn("\u26A0\uFE0F Could not sync memories:", error instanceof Error ? error.message : error);
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Create or get a session
1134
+ */
1135
+ getSession(sessionId) {
1136
+ if (sessionId && this.sessions.has(sessionId)) {
1137
+ const session = this.sessions.get(sessionId);
1138
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1139
+ return session;
1140
+ }
1141
+ const newSession = {
1142
+ id: sessionId || `session-${Date.now()}`,
1143
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1144
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
1145
+ variables: {},
1146
+ messageHistory: []
1147
+ };
1148
+ this.sessions.set(newSession.id, newSession);
1149
+ this.currentSessionId = newSession.id;
1150
+ return newSession;
1151
+ }
1152
+ /**
1153
+ * Get the current active session
1154
+ */
1155
+ getCurrentSession() {
1156
+ if (this.currentSessionId) {
1157
+ return this.sessions.get(this.currentSessionId);
1158
+ }
1159
+ return void 0;
1160
+ }
1161
+ /**
1162
+ * Set the current active session
1163
+ */
1164
+ setCurrentSession(sessionId) {
1165
+ if (this.sessions.has(sessionId)) {
1166
+ this.currentSessionId = sessionId;
1167
+ }
1168
+ }
1169
+ /**
1170
+ * Add a message to the current session history
1171
+ */
1172
+ addMessage(role, content) {
1173
+ const session = this.getCurrentSession();
1174
+ if (!session) return;
1175
+ session.messageHistory.push({
1176
+ role,
1177
+ content,
1178
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1179
+ });
1180
+ if (session.messageHistory.length > this.options.maxMessageHistory) {
1181
+ session.messageHistory = session.messageHistory.slice(-this.options.maxMessageHistory);
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Store a memory (local + cloud sync)
1186
+ */
1187
+ async storeMemory(content, options = {}) {
1188
+ const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1189
+ const entry = {
1190
+ id,
1191
+ content,
1192
+ keywords: options.keywords || [],
1193
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1194
+ source: options.source || "daemon",
1195
+ metadata: options.metadata
1196
+ };
1197
+ this.localMemories.set(id, entry);
1198
+ try {
1199
+ await this.rainfall.memory.create({
1200
+ content,
1201
+ keywords: [...options.keywords || [], "daemon:context"],
1202
+ metadata: {
1203
+ ...options.metadata,
1204
+ daemonMemoryId: id,
1205
+ source: options.source || "daemon"
1206
+ }
1207
+ });
1208
+ } catch (error) {
1209
+ console.warn("\u26A0\uFE0F Could not sync memory to cloud:", error instanceof Error ? error.message : error);
1210
+ }
1211
+ this.trimLocalMemories();
1212
+ return id;
1213
+ }
1214
+ /**
1215
+ * Recall memories by query
1216
+ */
1217
+ async recallMemories(query, topK = 5) {
1218
+ const localResults = Array.from(this.localMemories.values()).filter(
1219
+ (m) => m.content.toLowerCase().includes(query.toLowerCase()) || m.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase()))
1220
+ ).slice(0, topK);
1221
+ try {
1222
+ const cloudResults = await this.rainfall.memory.recall({ query, topK });
1223
+ const seen = new Set(localResults.map((r) => r.id));
1224
+ for (const mem of cloudResults) {
1225
+ if (!seen.has(mem.id)) {
1226
+ localResults.push({
1227
+ id: mem.id,
1228
+ content: mem.content,
1229
+ keywords: mem.keywords || [],
1230
+ timestamp: mem.timestamp,
1231
+ source: mem.source,
1232
+ metadata: mem.metadata
1233
+ });
1234
+ }
1235
+ }
1236
+ } catch {
1237
+ }
1238
+ return localResults.slice(0, topK);
1239
+ }
1240
+ /**
1241
+ * Set a session variable
1242
+ */
1243
+ setVariable(key, value) {
1244
+ const session = this.getCurrentSession();
1245
+ if (session) {
1246
+ session.variables[key] = value;
1247
+ }
1248
+ }
1249
+ /**
1250
+ * Get a session variable
1251
+ */
1252
+ getVariable(key) {
1253
+ const session = this.getCurrentSession();
1254
+ return session?.variables[key];
1255
+ }
1256
+ /**
1257
+ * Record a tool execution
1258
+ */
1259
+ recordExecution(toolId, params, result, options = { duration: 0 }) {
1260
+ const record = {
1261
+ id: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1262
+ toolId,
1263
+ params,
1264
+ result,
1265
+ error: options.error,
1266
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1267
+ duration: options.duration,
1268
+ edgeNodeId: options.edgeNodeId
1269
+ };
1270
+ this.executionHistory.push(record);
1271
+ if (this.executionHistory.length > this.options.maxExecutionHistory) {
1272
+ this.executionHistory = this.executionHistory.slice(-this.options.maxExecutionHistory);
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Get recent execution history
1277
+ */
1278
+ getExecutionHistory(limit = 10) {
1279
+ return this.executionHistory.slice(-limit).reverse();
1280
+ }
1281
+ /**
1282
+ * Get execution statistics
1283
+ */
1284
+ getExecutionStats() {
1285
+ const stats = {
1286
+ total: this.executionHistory.length,
1287
+ successful: 0,
1288
+ failed: 0,
1289
+ averageDuration: 0,
1290
+ byTool: {}
1291
+ };
1292
+ let totalDuration = 0;
1293
+ for (const exec of this.executionHistory) {
1294
+ if (exec.error) {
1295
+ stats.failed++;
1296
+ } else {
1297
+ stats.successful++;
1298
+ }
1299
+ totalDuration += exec.duration;
1300
+ stats.byTool[exec.toolId] = (stats.byTool[exec.toolId] || 0) + 1;
1301
+ }
1302
+ stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;
1303
+ return stats;
1304
+ }
1305
+ /**
1306
+ * Clear old sessions based on TTL
1307
+ */
1308
+ cleanupSessions() {
1309
+ const now = Date.now();
1310
+ const ttl = this.options.sessionTtl;
1311
+ for (const [id, session] of this.sessions) {
1312
+ const lastActivity = new Date(session.lastActivity).getTime();
1313
+ if (now - lastActivity > ttl) {
1314
+ this.sessions.delete(id);
1315
+ if (this.currentSessionId === id) {
1316
+ this.currentSessionId = void 0;
1317
+ }
1318
+ }
1319
+ }
1320
+ }
1321
+ /**
1322
+ * Get context summary for debugging
1323
+ */
1324
+ getStatus() {
1325
+ return {
1326
+ memoriesCached: this.localMemories.size,
1327
+ activeSessions: this.sessions.size,
1328
+ currentSession: this.currentSessionId,
1329
+ executionHistorySize: this.executionHistory.length
1330
+ };
1331
+ }
1332
+ trimLocalMemories() {
1333
+ if (this.localMemories.size <= this.options.maxLocalMemories) return;
1334
+ const entries = Array.from(this.localMemories.entries()).sort((a, b) => new Date(a[1].timestamp).getTime() - new Date(b[1].timestamp).getTime());
1335
+ const toRemove = entries.slice(0, entries.length - this.options.maxLocalMemories);
1336
+ for (const [id] of toRemove) {
1337
+ this.localMemories.delete(id);
1338
+ }
1339
+ }
1340
+ };
1341
+
1342
+ // src/services/listeners.ts
1343
+ var RainfallListenerRegistry = class {
1344
+ rainfall;
1345
+ context;
1346
+ executor;
1347
+ watchers = /* @__PURE__ */ new Map();
1348
+ cronIntervals = /* @__PURE__ */ new Map();
1349
+ eventHistory = [];
1350
+ maxEventHistory = 100;
1351
+ constructor(rainfall, context, executor) {
1352
+ this.rainfall = rainfall;
1353
+ this.context = context;
1354
+ this.executor = executor;
1355
+ }
1356
+ /**
1357
+ * Register a file watcher
1358
+ * Note: Actual file watching requires fs.watch or chokidar
1359
+ * This is the registry - actual watching is done by the daemon
1360
+ */
1361
+ async registerFileWatcher(config) {
1362
+ console.log(`\u{1F441}\uFE0F Registering file watcher: ${config.name} (${config.watchPath})`);
1363
+ const existing = Array.from(this.watchers.keys());
1364
+ if (existing.includes(config.id)) {
1365
+ await this.unregisterFileWatcher(config.id);
1366
+ }
1367
+ this.watchers.set(config.id, {
1368
+ stop: () => {
1369
+ console.log(`\u{1F441}\uFE0F Stopped file watcher: ${config.name}`);
1370
+ }
1371
+ });
1372
+ await this.context.storeMemory(`File watcher registered: ${config.name}`, {
1373
+ keywords: ["listener", "file-watcher", config.name],
1374
+ metadata: { config }
1375
+ });
1376
+ }
1377
+ /**
1378
+ * Unregister a file watcher
1379
+ */
1380
+ async unregisterFileWatcher(id) {
1381
+ const watcher = this.watchers.get(id);
1382
+ if (watcher) {
1383
+ watcher.stop();
1384
+ this.watchers.delete(id);
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Register a cron trigger
1389
+ */
1390
+ async registerCronTrigger(config) {
1391
+ console.log(`\u23F0 Registering cron trigger: ${config.name} (${config.cron})`);
1392
+ if (this.cronIntervals.has(config.id)) {
1393
+ clearInterval(this.cronIntervals.get(config.id));
1394
+ this.cronIntervals.delete(config.id);
1395
+ }
1396
+ const interval = this.parseCronToMs(config.cron);
1397
+ if (interval) {
1398
+ const intervalId = setInterval(async () => {
1399
+ await this.handleCronTick(config);
1400
+ }, interval);
1401
+ this.cronIntervals.set(config.id, intervalId);
1402
+ }
1403
+ await this.context.storeMemory(`Cron trigger registered: ${config.name}`, {
1404
+ keywords: ["listener", "cron", config.name],
1405
+ metadata: { config }
1406
+ });
1407
+ }
1408
+ /**
1409
+ * Unregister a cron trigger
1410
+ */
1411
+ unregisterCronTrigger(id) {
1412
+ const interval = this.cronIntervals.get(id);
1413
+ if (interval) {
1414
+ clearInterval(interval);
1415
+ this.cronIntervals.delete(id);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Handle a file event
1420
+ */
1421
+ async handleFileEvent(watcherId, eventType, filePath) {
1422
+ const event = {
1423
+ id: `evt-${Date.now()}`,
1424
+ type: "file",
1425
+ source: watcherId,
1426
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1427
+ data: { eventType, filePath }
1428
+ };
1429
+ this.recordEvent(event);
1430
+ console.log(`\u{1F4C1} File event: ${eventType} ${filePath}`);
1431
+ }
1432
+ /**
1433
+ * Handle a cron tick
1434
+ */
1435
+ async handleCronTick(config) {
1436
+ const event = {
1437
+ id: `evt-${Date.now()}`,
1438
+ type: "cron",
1439
+ source: config.id,
1440
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1441
+ data: { cron: config.cron }
1442
+ };
1443
+ this.recordEvent(event);
1444
+ console.log(`\u23F0 Cron tick: ${config.name}`);
1445
+ for (const step of config.workflow) {
1446
+ try {
1447
+ await this.executor.queueToolExecution(step.toolId, {
1448
+ ...step.params,
1449
+ _event: event
1450
+ });
1451
+ } catch (error) {
1452
+ console.error(`\u274C Workflow step failed: ${step.toolId}`, error);
1453
+ }
1454
+ }
1455
+ }
1456
+ /**
1457
+ * Trigger a manual event (for testing or programmatic triggers)
1458
+ */
1459
+ async triggerManual(name, data = {}) {
1460
+ const event = {
1461
+ id: `evt-${Date.now()}`,
1462
+ type: "manual",
1463
+ source: name,
1464
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1465
+ data
1466
+ };
1467
+ this.recordEvent(event);
1468
+ console.log(`\u{1F446} Manual trigger: ${name}`);
1469
+ await this.context.storeMemory(`Manual trigger fired: ${name}`, {
1470
+ keywords: ["trigger", "manual", name],
1471
+ metadata: { event }
1472
+ });
1473
+ }
1474
+ /**
1475
+ * Get recent events
1476
+ */
1477
+ getRecentEvents(limit = 10) {
1478
+ return this.eventHistory.slice(-limit).reverse();
1479
+ }
1480
+ /**
1481
+ * Get active listeners status
1482
+ */
1483
+ getStatus() {
1484
+ return {
1485
+ fileWatchers: this.watchers.size,
1486
+ cronTriggers: this.cronIntervals.size,
1487
+ recentEvents: this.eventHistory.length
1488
+ };
1489
+ }
1490
+ /**
1491
+ * Stop all listeners
1492
+ */
1493
+ async stopAll() {
1494
+ for (const [id] of this.watchers) {
1495
+ await this.unregisterFileWatcher(id);
1496
+ }
1497
+ for (const [id] of this.cronIntervals) {
1498
+ this.unregisterCronTrigger(id);
1499
+ }
1500
+ console.log("\u{1F6D1} All listeners stopped");
1501
+ }
1502
+ recordEvent(event) {
1503
+ this.eventHistory.push(event);
1504
+ if (this.eventHistory.length > this.maxEventHistory) {
1505
+ this.eventHistory = this.eventHistory.slice(-this.maxEventHistory);
1506
+ }
1507
+ }
1508
+ /**
1509
+ * Simple cron parser - converts basic cron expressions to milliseconds
1510
+ * Supports: @hourly, @daily, @weekly, and simple intervals like every N minutes
1511
+ */
1512
+ parseCronToMs(cron) {
1513
+ switch (cron) {
1514
+ case "@hourly":
1515
+ return 60 * 60 * 1e3;
1516
+ case "@daily":
1517
+ return 24 * 60 * 60 * 1e3;
1518
+ case "@weekly":
1519
+ return 7 * 24 * 60 * 60 * 1e3;
1520
+ case "@minutely":
1521
+ return 60 * 1e3;
1522
+ }
1523
+ const match = cron.match(/^\*\/(\d+)\s/);
1524
+ if (match) {
1525
+ const minutes = parseInt(match[1], 10);
1526
+ if (minutes > 0 && minutes <= 60) {
1527
+ return minutes * 60 * 1e3;
1528
+ }
1529
+ }
1530
+ console.warn(`\u26A0\uFE0F Unrecognized cron pattern "${cron}", using 1 minute interval`);
1531
+ return 60 * 1e3;
1532
+ }
799
1533
  };
1534
+ function createFileWatcherWorkflow(name, watchPath, options) {
1535
+ return {
1536
+ id: `fw-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1537
+ name,
1538
+ watchPath,
1539
+ pattern: options.pattern,
1540
+ events: options.events || ["create"],
1541
+ workflow: options.workflow
1542
+ };
1543
+ }
1544
+ function createCronWorkflow(name, cron, workflow) {
1545
+ return {
1546
+ id: `cron-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1547
+ name,
1548
+ cron,
1549
+ workflow
1550
+ };
1551
+ }
800
1552
 
801
1553
  // src/index.ts
802
1554
  var VERSION = "0.1.0";
@@ -807,11 +1559,16 @@ var VERSION = "0.1.0";
807
1559
  NotFoundError,
808
1560
  Rainfall,
809
1561
  RainfallClient,
1562
+ RainfallDaemonContext,
810
1563
  RainfallError,
1564
+ RainfallListenerRegistry,
1565
+ RainfallNetworkedExecutor,
811
1566
  RateLimitError,
812
1567
  ServerError,
813
1568
  TimeoutError,
814
1569
  ToolNotFoundError,
815
1570
  VERSION,
816
- ValidationError
1571
+ ValidationError,
1572
+ createCronWorkflow,
1573
+ createFileWatcherWorkflow
817
1574
  });