@poolzin/pool-bot 2026.1.29 → 2026.1.31

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 (39) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +11 -0
  3. package/dist/agents/system-prompt.js +16 -16
  4. package/dist/agents/tools/memory-tool.js +2 -1
  5. package/dist/build-info.json +3 -3
  6. package/dist/cli/program/command-registry.js +5 -0
  7. package/dist/cli/program/register.completion.js +355 -0
  8. package/dist/gateway/hooks/index.js +49 -0
  9. package/dist/gateway/hooks/lifecycle-hooks-integration.js +256 -0
  10. package/dist/gateway/hooks/lifecycle-hooks.js +236 -0
  11. package/dist/gateway/hooks/progressive-disclosure-details.js +237 -0
  12. package/dist/gateway/hooks/progressive-disclosure-index.js +354 -0
  13. package/dist/gateway/hooks/progressive-disclosure-timeline.js +231 -0
  14. package/dist/gateway/hooks/progressive-disclosure-types.js +65 -0
  15. package/dist/gateway/hooks/progressive-disclosure.js +242 -0
  16. package/dist/gateway/hooks/tool-usage-capture.js +253 -0
  17. package/dist/gateway/hooks/tool-usage-storage.js +144 -0
  18. package/dist/gateway/server-methods/nodes.js +2 -0
  19. package/dist/gateway/server.impl.js +4 -0
  20. package/dist/imessage/monitor/monitor-provider.js +14 -1
  21. package/dist/media/store.js +37 -1
  22. package/dist/memory/index.js +5 -0
  23. package/dist/memory/manager.js +25 -2
  24. package/docs/WHATSAPP-HEARTBEAT-TROUBLESHOOTING.md +319 -0
  25. package/package.json +1 -1
  26. package/skills/webgpu-threejs-tsl/REFERENCE.md +283 -0
  27. package/skills/webgpu-threejs-tsl/SKILL.md +91 -0
  28. package/skills/webgpu-threejs-tsl/docs/compute-shaders.md +404 -0
  29. package/skills/webgpu-threejs-tsl/docs/core-concepts.md +453 -0
  30. package/skills/webgpu-threejs-tsl/docs/materials.md +353 -0
  31. package/skills/webgpu-threejs-tsl/docs/post-processing.md +434 -0
  32. package/skills/webgpu-threejs-tsl/docs/wgsl-integration.md +324 -0
  33. package/skills/webgpu-threejs-tsl/examples/basic-setup.js +87 -0
  34. package/skills/webgpu-threejs-tsl/examples/custom-material.js +170 -0
  35. package/skills/webgpu-threejs-tsl/examples/earth-shader.js +292 -0
  36. package/skills/webgpu-threejs-tsl/examples/particle-system.js +259 -0
  37. package/skills/webgpu-threejs-tsl/examples/post-processing.js +199 -0
  38. package/skills/webgpu-threejs-tsl/templates/compute-shader.js +305 -0
  39. package/skills/webgpu-threejs-tsl/templates/webgpu-project.js +276 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Progressive Disclosure - Details Layer (Phase 4)
3
+ *
4
+ * Load full memory content on-demand
5
+ * Safe: Read-only, rate-limited, lazy loading
6
+ *
7
+ * Purpose:
8
+ * - Only load full content when user explicitly requests
9
+ * - Provides: Complete file content, line ranges, metadata
10
+ * - Called only for specific results (not all results)
11
+ */
12
+ import { readFile } from "node:fs/promises";
13
+ import { join } from "node:path";
14
+ // ============================================================================
15
+ // Constants
16
+ // ============================================================================
17
+ const MEMORY_DIR = "/root/pool";
18
+ const MAX_CONTENT_SIZE = 100_000; // 100KB max per detail
19
+ const RATE_LIMIT_MS = 100; // Min 100ms between loads
20
+ // ============================================================================
21
+ // Rate Limiting (prevent abuse)
22
+ // ============================================================================
23
+ let lastLoadTime = 0;
24
+ /**
25
+ * Rate limiter for detail loading
26
+ * Safe: Prevents excessive I/O operations
27
+ */
28
+ async function rateLimit() {
29
+ const now = Date.now();
30
+ const elapsed = now - lastLoadTime;
31
+ if (elapsed < RATE_LIMIT_MS) {
32
+ const delay = RATE_LIMIT_MS - elapsed;
33
+ await new Promise((resolve) => setTimeout(resolve, delay));
34
+ }
35
+ lastLoadTime = Date.now();
36
+ }
37
+ // ============================================================================
38
+ // Details Layer Implementation
39
+ // ============================================================================
40
+ /**
41
+ * Load full memory details for given result IDs
42
+ * Safe: Rate-limited, size-limited, handles errors gracefully
43
+ *
44
+ * @param ids - Result IDs (format: "memory/YYYY-MM-DD.md#L123")
45
+ * @returns Map of IDs to MemoryDetail
46
+ */
47
+ export async function loadFullDetails(ids) {
48
+ // Validate inputs
49
+ if (!ids || ids.length === 0) {
50
+ return {};
51
+ }
52
+ try {
53
+ const details = {};
54
+ // Load each detail with rate limiting
55
+ for (const id of ids) {
56
+ await rateLimit();
57
+ const detail = await loadDetailForId(id);
58
+ if (detail) {
59
+ details[id] = detail;
60
+ }
61
+ }
62
+ return details;
63
+ }
64
+ catch (err) {
65
+ console.error("[pd-details] Failed to load details:", err);
66
+ return {};
67
+ }
68
+ }
69
+ /**
70
+ * Load detail for a single ID
71
+ * Safe: Returns null if invalid or error
72
+ */
73
+ async function loadDetailForId(id) {
74
+ try {
75
+ // Parse ID to extract file path and line range
76
+ const { filePath, lineRange } = parseId(id);
77
+ // Read file content
78
+ const content = await readFile(filePath, "utf-8");
79
+ // Validate content size
80
+ if (content.length > MAX_CONTENT_SIZE) {
81
+ console.warn(`[pd-details] Content too large for ${id}, truncating`);
82
+ return createMemoryDetail(id, content.slice(0, MAX_CONTENT_SIZE), filePath, lineRange, true);
83
+ }
84
+ return createMemoryDetail(id, content, filePath, lineRange, false);
85
+ }
86
+ catch (err) {
87
+ if (err.code === "ENOENT") {
88
+ // File doesn't exist
89
+ return null;
90
+ }
91
+ // Other error
92
+ console.error(`[pd-details] Failed to load ${id}:`, err);
93
+ return null;
94
+ }
95
+ }
96
+ /**
97
+ * Parse result ID to extract file path and line range
98
+ * Safe: Returns defaults if invalid format
99
+ */
100
+ function parseId(id) {
101
+ try {
102
+ // Format: "memory/YYYY-MM-DD.md#L123" or "MEMORY.md#L10-20"
103
+ const match = id.match(/^(.+\.md)(?:#L(\d+)(?:-(\d+))?)?$/);
104
+ if (!match) {
105
+ throw new Error(`Invalid ID format: ${id}`);
106
+ }
107
+ const [, fileName, startLine, endLine] = match;
108
+ // Determine file path
109
+ let filePath;
110
+ if (fileName === "MEMORY.md") {
111
+ filePath = join(MEMORY_DIR, "MEMORY.md");
112
+ }
113
+ else {
114
+ filePath = join(MEMORY_DIR, fileName);
115
+ }
116
+ // Parse line range
117
+ const lineRange = startLine
118
+ ? {
119
+ start: parseInt(startLine, 10),
120
+ end: endLine ? parseInt(endLine, 10) : parseInt(startLine, 10),
121
+ }
122
+ : undefined;
123
+ return { filePath, lineRange };
124
+ }
125
+ catch (err) {
126
+ // Invalid format, return defaults
127
+ console.warn(`[pd-details] Invalid ID format: ${id}`);
128
+ return { filePath: join(MEMORY_DIR, "MEMORY.md") };
129
+ }
130
+ }
131
+ /**
132
+ * Create MemoryDetail object
133
+ */
134
+ function createMemoryDetail(id, content, filePath, lineRange, truncated) {
135
+ return {
136
+ id,
137
+ content,
138
+ compressed: truncated,
139
+ lines: lineRange,
140
+ metadata: {
141
+ fileType: filePath.includes("MEMORY.md") && !filePath.includes("memory/") ? "MEMORY" : "DAILY",
142
+ size: content.length,
143
+ lastModified: Date.now(), // Approximate (would need fs.stat for real)
144
+ },
145
+ };
146
+ }
147
+ // ============================================================================
148
+ // Batch Loading (with concurrency limit)
149
+ // ============================================================================
150
+ /**
151
+ * Load details in batches (limited concurrency)
152
+ * Safe: Prevents overwhelming the file system
153
+ *
154
+ * @param ids - Result IDs to load
155
+ * @param batchSize - Max concurrent loads (default: 5)
156
+ * @returns Map of IDs to MemoryDetail
157
+ */
158
+ export async function loadDetailsBatch(ids, batchSize = 5) {
159
+ const details = {};
160
+ // Process in batches
161
+ for (let i = 0; i < ids.length; i += batchSize) {
162
+ const batch = ids.slice(i, i + batchSize);
163
+ // Load batch in parallel
164
+ const promises = batch.map(async (id) => {
165
+ await rateLimit();
166
+ return loadDetailForId(id);
167
+ });
168
+ const results = await Promise.allSettled(promises);
169
+ // Merge results
170
+ for (let j = 0; j < results.length; j++) {
171
+ const result = results[j];
172
+ const id = batch[j];
173
+ if (result.status === "fulfilled" && result.value) {
174
+ details[id] = result.value;
175
+ }
176
+ }
177
+ }
178
+ return details;
179
+ }
180
+ // ============================================================================
181
+ // Content Extraction (for line ranges)
182
+ // ============================================================================
183
+ /**
184
+ * Extract specific lines from content
185
+ * Safe: Handles out-of-range gracefully
186
+ *
187
+ * @param content - Full file content
188
+ * @param startLine - Start line number (1-indexed)
189
+ * @param endLine - End line number (1-indexed)
190
+ * @returns Extracted lines
191
+ */
192
+ export function extractLines(content, startLine, endLine) {
193
+ const lines = content.split("\n");
194
+ // Adjust to 0-indexed
195
+ const start = Math.max(0, startLine - 1);
196
+ const end = Math.min(lines.length, endLine);
197
+ return lines.slice(start, end).join("\n");
198
+ }
199
+ // ============================================================================
200
+ // Utilities (for testing)
201
+ // ============================================================================
202
+ /**
203
+ * Get file stats (size, last modified)
204
+ * Safe: Returns defaults if error
205
+ */
206
+ async function getFileStats(filePath) {
207
+ try {
208
+ const { stat } = await import("node:fs/promises");
209
+ const stats = await stat(filePath);
210
+ return {
211
+ size: stats.size,
212
+ lastModified: stats.mtimeMs,
213
+ };
214
+ }
215
+ catch {
216
+ // Fallback to approximate values
217
+ return {
218
+ size: 0,
219
+ lastModified: Date.now(),
220
+ };
221
+ }
222
+ }
223
+ /**
224
+ * Reset rate limiter (for testing)
225
+ */
226
+ export function resetRateLimiter() {
227
+ lastLoadTime = 0;
228
+ }
229
+ /**
230
+ * Get rate limiter stats (for testing)
231
+ */
232
+ export function getRateLimiterStats() {
233
+ return {
234
+ lastLoadTime,
235
+ rateLimit: RATE_LIMIT_MS,
236
+ };
237
+ }
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Progressive Disclosure - Index Layer (Phase 2)
3
+ *
4
+ * Lightweight search: returns only index + snippets
5
+ * Token savings: ~90% vs full content search
6
+ *
7
+ * Safe:
8
+ * - Read-only operations (no modifications)
9
+ * - Pure functions (no side effects)
10
+ * - Cacheable results
11
+ */
12
+ import { readFile } from "node:fs/promises";
13
+ import { join } from "node:path";
14
+ import { PROGRESSIVE_DISCLOSURE_FLAGS } from "./progressive-disclosure-types.js";
15
+ // ============================================================================
16
+ // Constants
17
+ // ============================================================================
18
+ const MEMORY_DIR = "/root/pool";
19
+ const MAX_SNIPPET_LENGTH = 100;
20
+ const DEFAULT_MAX_RESULTS = 10;
21
+ const MIN_SCORE_THRESHOLD = 0.1;
22
+ // ============================================================================
23
+ // Cache Implementation (in-memory, thread-safe for single-threaded Node.js)
24
+ // ============================================================================
25
+ const cache = new Map();
26
+ /**
27
+ * Generate cache key from query
28
+ */
29
+ function getCacheKey(query, options) {
30
+ const filter = options.tags?.join(",") || "";
31
+ const range = options.dateRange ? `${options.dateRange.start}-${options.dateRange.end}` : "";
32
+ return `${query}:${filter}:${range}:${options.maxResults || DEFAULT_MAX_RESULTS}`;
33
+ }
34
+ /**
35
+ * Get from cache if valid
36
+ */
37
+ function getFromCache(key) {
38
+ if (!PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_ENABLED) {
39
+ return null;
40
+ }
41
+ const cached = cache.get(key);
42
+ if (!cached) {
43
+ return null;
44
+ }
45
+ // Check TTL
46
+ const age = Date.now() - cached.timestamp;
47
+ if (age > PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_TTL_MS) {
48
+ cache.delete(key);
49
+ return null;
50
+ }
51
+ if (PROGRESSIVE_DISCLOSURE_FLAGS.DEBUG_MODE) {
52
+ console.log(`[pd-index] Cache hit: ${key}`);
53
+ }
54
+ return cached.results;
55
+ }
56
+ /**
57
+ * Save to cache (respecting max size)
58
+ */
59
+ function saveToCache(key, results) {
60
+ if (!PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_ENABLED) {
61
+ return;
62
+ }
63
+ // Enforce max cache size
64
+ if (cache.size >= PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_MAX_SIZE) {
65
+ // Remove oldest entry (first key in map)
66
+ const firstKey = cache.keys().next().value;
67
+ if (firstKey) {
68
+ cache.delete(firstKey);
69
+ }
70
+ }
71
+ cache.set(key, {
72
+ key,
73
+ results,
74
+ timestamp: Date.now(),
75
+ ttl: PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_TTL_MS,
76
+ });
77
+ if (PROGRESSIVE_DISCLOSURE_FLAGS.DEBUG_MODE) {
78
+ console.log(`[pd-index] Cached: ${key} (${results.length} results)`);
79
+ }
80
+ }
81
+ /**
82
+ * Clear all cache (for testing)
83
+ */
84
+ export function clearIndexCache() {
85
+ cache.clear();
86
+ }
87
+ /**
88
+ * Get cache statistics
89
+ */
90
+ export function getIndexCacheStats() {
91
+ return {
92
+ size: cache.size,
93
+ maxSize: PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_MAX_SIZE,
94
+ ttl: PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_TTL_MS,
95
+ enabled: PROGRESSIVE_DISCLOSURE_FLAGS.CACHE_ENABLED,
96
+ };
97
+ }
98
+ // ============================================================================
99
+ // Index Layer Implementation
100
+ // ============================================================================
101
+ /**
102
+ * Search memory files and return lightweight index
103
+ * Safe: Read-only, cacheable, pure function
104
+ *
105
+ * @param query - Search query string
106
+ * @param options - Search options
107
+ * @returns Array of search results (lightweight)
108
+ */
109
+ export async function searchIndexOnly(query, options = {}) {
110
+ // Validate inputs
111
+ const safeQuery = query?.trim() || "";
112
+ if (!safeQuery) {
113
+ return [];
114
+ }
115
+ const maxResults = options.maxResults || DEFAULT_MAX_RESULTS;
116
+ const threshold = options.threshold ?? MIN_SCORE_THRESHOLD;
117
+ // Check cache
118
+ const cacheKey = getCacheKey(safeQuery, options);
119
+ const cached = getFromCache(cacheKey);
120
+ if (cached) {
121
+ return cached.slice(0, maxResults);
122
+ }
123
+ try {
124
+ // Search in memory files
125
+ const results = await searchMemoryFiles(safeQuery, options);
126
+ // Filter by threshold
127
+ const filtered = results.filter((r) => r.score >= threshold);
128
+ // Sort by score (descending)
129
+ const sorted = filtered.sort((a, b) => b.score - a.score);
130
+ // Limit results
131
+ const limited = sorted.slice(0, maxResults);
132
+ // Cache results
133
+ saveToCache(cacheKey, limited);
134
+ return limited;
135
+ }
136
+ catch (err) {
137
+ console.error("[pd-index] Search failed:", err);
138
+ return [];
139
+ }
140
+ }
141
+ /**
142
+ * Search memory files (main implementation)
143
+ * Safe: Read-only, handles errors gracefully
144
+ */
145
+ async function searchMemoryFiles(query, options) {
146
+ const results = [];
147
+ const queryLower = query.toLowerCase();
148
+ // Files to search (in order of priority)
149
+ const filesToSearch = getFilesToSearch(options);
150
+ for (const filePath of filesToSearch) {
151
+ try {
152
+ const content = await readFile(filePath, "utf-8");
153
+ const lines = content.split("\n");
154
+ // Extract date from filename
155
+ const date = extractDate(filePath);
156
+ // Search for matches
157
+ const matches = findMatchesInContent(content, lines, queryLower, filePath, date);
158
+ results.push(...matches);
159
+ }
160
+ catch (err) {
161
+ // Skip files that can't be read
162
+ if (PROGRESSIVE_DISCLOSURE_FLAGS.DEBUG_MODE) {
163
+ console.warn(`[pd-index] Failed to read ${filePath}:`, err);
164
+ }
165
+ }
166
+ }
167
+ return results;
168
+ }
169
+ /**
170
+ * Get list of files to search based on options
171
+ */
172
+ function getFilesToSearch(options) {
173
+ const files = [];
174
+ // Always search MEMORY.md first
175
+ files.push(join(MEMORY_DIR, "MEMORY.md"));
176
+ // Search daily memory files
177
+ if (!options.dateRange) {
178
+ // No date filter: search all daily files
179
+ // For now, just search recent files (last 30 days)
180
+ const recentFiles = getRecentDailyFiles(30);
181
+ files.push(...recentFiles);
182
+ }
183
+ else {
184
+ // Date range filter
185
+ const rangeFiles = getDailyFilesInRange(options.dateRange.start, options.dateRange.end);
186
+ files.push(...rangeFiles);
187
+ }
188
+ return files;
189
+ }
190
+ /**
191
+ * Get recent daily memory files
192
+ * Safe: Returns empty array if directory doesn't exist
193
+ */
194
+ function getRecentDailyFiles(days) {
195
+ const files = [];
196
+ const now = new Date();
197
+ for (let i = 0; i < days; i++) {
198
+ const date = new Date(now);
199
+ date.setDate(date.getDate() - i);
200
+ const dateStr = date.toISOString().split("T")[0];
201
+ const filePath = join(MEMORY_DIR, "memory", `${dateStr}.md`);
202
+ files.push(filePath);
203
+ }
204
+ return files;
205
+ }
206
+ /**
207
+ * Get daily files in date range
208
+ */
209
+ function getDailyFilesInRange(start, end) {
210
+ const files = [];
211
+ const startDate = new Date(start);
212
+ const endDate = new Date(end);
213
+ const currentDate = new Date(startDate);
214
+ while (currentDate <= endDate) {
215
+ const dateStr = currentDate.toISOString().split("T")[0];
216
+ const filePath = join(MEMORY_DIR, "memory", `${dateStr}.md`);
217
+ files.push(filePath);
218
+ // Next day
219
+ currentDate.setDate(currentDate.getDate() + 1);
220
+ }
221
+ return files;
222
+ }
223
+ /**
224
+ * Extract date from file path
225
+ */
226
+ function extractDate(filePath) {
227
+ // Format: /root/pool/memory/2026-02-04.md -> 2026-02-04
228
+ const match = filePath.match(/(\d{4}-\d{2}-\d{2})\.md$/);
229
+ if (match) {
230
+ return match[1];
231
+ }
232
+ // MEMORY.md -> today's date
233
+ return new Date().toISOString().split("T")[0];
234
+ }
235
+ /**
236
+ * Find matches in content (simple string matching)
237
+ * Safe: No regex, handles edge cases
238
+ */
239
+ function findMatchesInContent(content, lines, queryLower, filePath, date) {
240
+ const results = [];
241
+ const queryWords = queryLower.split(/\s+/);
242
+ // Scan content for matches
243
+ let matchCount = 0;
244
+ for (let i = 0; i < lines.length; i++) {
245
+ const line = lines[i];
246
+ const lineLower = line.toLowerCase();
247
+ // Simple word matching (all query words must be present)
248
+ const allWordsPresent = queryWords.every((word) => lineLower.includes(word));
249
+ if (!allWordsPresent) {
250
+ continue;
251
+ }
252
+ // Calculate score (simple: word count / line length)
253
+ const score = calculateScore(line, queryWords);
254
+ // Create result
255
+ const result = {
256
+ id: `${filePath}#L${i + 1}`,
257
+ date,
258
+ snippet: extractSnippet(line, content, i),
259
+ score,
260
+ tags: extractTags(line, content),
261
+ title: extractTitle(content),
262
+ };
263
+ results.push(result);
264
+ matchCount++;
265
+ // Limit matches per file (avoid too many from same file)
266
+ if (matchCount >= 5) {
267
+ break;
268
+ }
269
+ }
270
+ return results;
271
+ }
272
+ /**
273
+ * Calculate relevance score
274
+ * Simple heuristic: more query words = higher score
275
+ */
276
+ function calculateScore(line, queryWords) {
277
+ const lineLower = line.toLowerCase();
278
+ let matchCount = 0;
279
+ for (const word of queryWords) {
280
+ if (lineLower.includes(word)) {
281
+ matchCount++;
282
+ }
283
+ }
284
+ // Score = (matches / total words) normalized 0-1
285
+ const baseScore = matchCount / queryWords.length;
286
+ // Boost score for shorter lines (more focused)
287
+ const lengthBonus = Math.max(0, 1 - line.length / 500);
288
+ return Math.min(1, baseScore * 0.8 + lengthBonus * 0.2);
289
+ }
290
+ /**
291
+ * Extract snippet (preview)
292
+ */
293
+ function extractSnippet(line, content, lineIndex) {
294
+ // Return first MAX_SNIPPET_LENGTH chars of line
295
+ if (line.length <= MAX_SNIPPET_LENGTH) {
296
+ return line;
297
+ }
298
+ return line.slice(0, MAX_SNIPPET_LENGTH) + "...";
299
+ }
300
+ /**
301
+ * Extract tags from content (simple heuristic)
302
+ */
303
+ function extractTags(line, content) {
304
+ const tags = [];
305
+ const lineLower = line.toLowerCase();
306
+ // Simple tag detection based on keywords
307
+ const tagKeywords = [
308
+ "decision",
309
+ "project",
310
+ "todo",
311
+ "important",
312
+ "bug",
313
+ "feature",
314
+ "fix",
315
+ "deploy",
316
+ "meeting",
317
+ "review",
318
+ ];
319
+ for (const keyword of tagKeywords) {
320
+ if (lineLower.includes(keyword)) {
321
+ tags.push(keyword);
322
+ }
323
+ }
324
+ return tags;
325
+ }
326
+ /**
327
+ * Extract title from content (first heading)
328
+ */
329
+ function extractTitle(content) {
330
+ const match = content.match(/^#\s+(.+)$/m);
331
+ return match ? match[1].trim() : undefined;
332
+ }
333
+ // ============================================================================
334
+ // Public API (for testing)
335
+ // ============================================================================
336
+ /**
337
+ * Force cache invalidation (for testing)
338
+ */
339
+ export function invalidateIndexCache(query) {
340
+ if (query) {
341
+ // Invalidate all cache keys containing the query
342
+ const keysToDelete = [];
343
+ cache.forEach((_, key) => {
344
+ if (key.startsWith(query)) {
345
+ keysToDelete.push(key);
346
+ }
347
+ });
348
+ keysToDelete.forEach((key) => cache.delete(key));
349
+ }
350
+ else {
351
+ // Clear all cache
352
+ cache.clear();
353
+ }
354
+ }