@poolzin/pool-bot 2026.1.29 → 2026.1.30
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/CHANGELOG.md +29 -1
- package/README.md +11 -0
- package/dist/agents/system-prompt.js +16 -16
- package/dist/agents/tools/memory-tool.js +2 -1
- package/dist/build-info.json +3 -3
- package/dist/cli/program/command-registry.js +5 -0
- package/dist/cli/program/register.completion.js +355 -0
- package/dist/gateway/hooks/index.js +53 -0
- package/dist/gateway/hooks/lifecycle-hooks-integration.js +256 -0
- package/dist/gateway/hooks/lifecycle-hooks.js +236 -0
- package/dist/gateway/hooks/progressive-disclosure-details.js +237 -0
- package/dist/gateway/hooks/progressive-disclosure-index.js +354 -0
- package/dist/gateway/hooks/progressive-disclosure-timeline.js +231 -0
- package/dist/gateway/hooks/progressive-disclosure-types.js +65 -0
- package/dist/gateway/hooks/progressive-disclosure.js +242 -0
- package/dist/gateway/hooks/tool-usage-capture.js +253 -0
- package/dist/gateway/hooks/tool-usage-storage.js +144 -0
- package/dist/gateway/server-methods/nodes.js +2 -0
- package/dist/gateway/server.impl.js +4 -0
- package/dist/imessage/monitor/monitor-provider.js +14 -1
- package/dist/media/store.js +37 -1
- package/dist/memory/index.js +5 -0
- package/dist/memory/manager.js +25 -2
- package/docs/WHATSAPP-HEARTBEAT-TROUBLESHOOTING.md +319 -0
- package/package.json +1 -1
- package/skills/webgpu-threejs-tsl/REFERENCE.md +283 -0
- package/skills/webgpu-threejs-tsl/SKILL.md +91 -0
- package/skills/webgpu-threejs-tsl/docs/compute-shaders.md +404 -0
- package/skills/webgpu-threejs-tsl/docs/core-concepts.md +453 -0
- package/skills/webgpu-threejs-tsl/docs/materials.md +353 -0
- package/skills/webgpu-threejs-tsl/docs/post-processing.md +434 -0
- package/skills/webgpu-threejs-tsl/docs/wgsl-integration.md +324 -0
- package/skills/webgpu-threejs-tsl/examples/basic-setup.js +87 -0
- package/skills/webgpu-threejs-tsl/examples/custom-material.js +170 -0
- package/skills/webgpu-threejs-tsl/examples/earth-shader.js +292 -0
- package/skills/webgpu-threejs-tsl/examples/particle-system.js +259 -0
- package/skills/webgpu-threejs-tsl/examples/post-processing.js +199 -0
- package/skills/webgpu-threejs-tsl/templates/compute-shader.js +305 -0
- package/skills/webgpu-threejs-tsl/templates/webgpu-project.js +276 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Disclosure - Timeline Layer (Phase 3)
|
|
3
|
+
*
|
|
4
|
+
* Load contextual timeline information for search results
|
|
5
|
+
* Safe: Read-only, lazy loading, batched I/O
|
|
6
|
+
*
|
|
7
|
+
* Purpose:
|
|
8
|
+
* - After user sees index results, load timeline context
|
|
9
|
+
* - Provides: Date summaries, result counts, temporal context
|
|
10
|
+
* - Called only when user drills down (not automatic)
|
|
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_SUMMARY_LENGTH = 300;
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Timeline Layer Implementation
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Load timeline context for given result IDs
|
|
24
|
+
* Safe: Read-only, handles errors gracefully
|
|
25
|
+
*
|
|
26
|
+
* @param ids - Result IDs (format: "memory/YYYY-MM-DD.md#L123")
|
|
27
|
+
* @returns Map of date strings to TimelineEntry
|
|
28
|
+
*/
|
|
29
|
+
export async function loadTimelineContext(ids) {
|
|
30
|
+
// Validate inputs
|
|
31
|
+
if (!ids || ids.length === 0) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
// Extract unique dates from IDs
|
|
36
|
+
const dates = extractUniqueDates(ids);
|
|
37
|
+
// Load timeline for each date
|
|
38
|
+
const timeline = {};
|
|
39
|
+
for (const date of dates) {
|
|
40
|
+
const entry = await loadTimelineForDate(date);
|
|
41
|
+
if (entry) {
|
|
42
|
+
timeline[date] = entry;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return timeline;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error("[pd-timeline] Failed to load timeline:", err);
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract unique dates from result IDs
|
|
54
|
+
* Safe: Handles invalid ID formats gracefully
|
|
55
|
+
*/
|
|
56
|
+
function extractUniqueDates(ids) {
|
|
57
|
+
const dates = new Set();
|
|
58
|
+
for (const id of ids) {
|
|
59
|
+
try {
|
|
60
|
+
// Extract date from ID: "memory/2026-02-04.md#L123" -> "2026-02-04"
|
|
61
|
+
const match = id.match(/(\d{4}-\d{2}-\d{2})\.md/);
|
|
62
|
+
if (match) {
|
|
63
|
+
dates.add(match[1]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// Skip invalid IDs
|
|
68
|
+
if (PROGRESSIVE_DISCLOSURE_FLAGS.DEBUG_MODE) {
|
|
69
|
+
console.warn(`[pd-timeline] Invalid ID format: ${id}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return Array.from(dates).sort(); // Sort chronologically
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Load timeline entry for a specific date
|
|
77
|
+
* Safe: Returns null if file doesn't exist or can't be read
|
|
78
|
+
*/
|
|
79
|
+
async function loadTimelineForDate(date) {
|
|
80
|
+
try {
|
|
81
|
+
// Try daily memory file first
|
|
82
|
+
const dailyPath = join(MEMORY_DIR, "memory", `${date}.md`);
|
|
83
|
+
const content = await readFile(dailyPath, "utf-8");
|
|
84
|
+
return parseTimelineEntry(date, content);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
// Daily file doesn't exist, try MEMORY.md
|
|
88
|
+
if (err.code === "ENOENT") {
|
|
89
|
+
try {
|
|
90
|
+
const memoryPath = join(MEMORY_DIR, "MEMORY.md");
|
|
91
|
+
const content = await readFile(memoryPath, "utf-8");
|
|
92
|
+
// For MEMORY.md, use today's date
|
|
93
|
+
return parseTimelineEntry(date, content);
|
|
94
|
+
}
|
|
95
|
+
catch (memoryErr) {
|
|
96
|
+
// MEMORY.md also doesn't exist or can't be read
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Other error (permission, etc)
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse timeline entry from content
|
|
106
|
+
* Safe: Handles malformed content gracefully
|
|
107
|
+
*/
|
|
108
|
+
function parseTimelineEntry(date, content) {
|
|
109
|
+
// Extract title (first heading)
|
|
110
|
+
const title = extractTitle(content) || `Memory: ${date}`;
|
|
111
|
+
// Extract summary (first paragraph or heading)
|
|
112
|
+
const summary = extractSummary(content);
|
|
113
|
+
// Count results (rough estimate: count headings)
|
|
114
|
+
const resultCount = countHeadings(content);
|
|
115
|
+
return {
|
|
116
|
+
date,
|
|
117
|
+
title,
|
|
118
|
+
summary,
|
|
119
|
+
resultCount,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Extract title from content (first # heading)
|
|
124
|
+
*/
|
|
125
|
+
function extractTitle(content) {
|
|
126
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
127
|
+
return match ? match[1].trim() : undefined;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Extract summary from content
|
|
131
|
+
* Safe: Returns first meaningful text
|
|
132
|
+
*/
|
|
133
|
+
function extractSummary(content) {
|
|
134
|
+
// Try to find first paragraph
|
|
135
|
+
const lines = content.split("\n");
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
// Skip empty lines, headings, code blocks
|
|
139
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("```")) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Found first paragraph
|
|
143
|
+
if (trimmed.length > MAX_SUMMARY_LENGTH) {
|
|
144
|
+
return trimmed.slice(0, MAX_SUMMARY_LENGTH) + "...";
|
|
145
|
+
}
|
|
146
|
+
return trimmed;
|
|
147
|
+
}
|
|
148
|
+
// No summary found, return generic
|
|
149
|
+
return "No summary available";
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Count headings in content (rough result count estimate)
|
|
153
|
+
*/
|
|
154
|
+
function countHeadings(content) {
|
|
155
|
+
const matches = content.match(/^#{2,}\s+.+$/gm);
|
|
156
|
+
return matches ? matches.length : 0;
|
|
157
|
+
}
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Batch Loading (Performance Optimization)
|
|
160
|
+
// ============================================================================
|
|
161
|
+
/**
|
|
162
|
+
* Load timeline for multiple date ranges in parallel
|
|
163
|
+
* Safe: Limits concurrency, handles errors
|
|
164
|
+
*
|
|
165
|
+
* @param dateRanges - Array of date ranges to load
|
|
166
|
+
* @returns Map of dates to TimelineEntry
|
|
167
|
+
*/
|
|
168
|
+
export async function loadTimelineBatch(dateRanges) {
|
|
169
|
+
const timeline = {};
|
|
170
|
+
// Process ranges in parallel (with limit)
|
|
171
|
+
const promises = dateRanges.map((range) => loadDateRange(range));
|
|
172
|
+
const results = await Promise.allSettled(promises);
|
|
173
|
+
// Merge results
|
|
174
|
+
for (const result of results) {
|
|
175
|
+
if (result.status === "fulfilled") {
|
|
176
|
+
Object.assign(timeline, result.value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return timeline;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Load timeline for a date range
|
|
183
|
+
*/
|
|
184
|
+
async function loadDateRange(range) {
|
|
185
|
+
const timeline = {};
|
|
186
|
+
const startDate = new Date(range.start);
|
|
187
|
+
const endDate = new Date(range.end);
|
|
188
|
+
const currentDate = new Date(startDate);
|
|
189
|
+
while (currentDate <= endDate) {
|
|
190
|
+
const dateStr = currentDate.toISOString().split("T")[0];
|
|
191
|
+
const entry = await loadTimelineForDate(dateStr);
|
|
192
|
+
if (entry) {
|
|
193
|
+
timeline[dateStr] = entry;
|
|
194
|
+
}
|
|
195
|
+
// Next day
|
|
196
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
197
|
+
}
|
|
198
|
+
return timeline;
|
|
199
|
+
}
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Utilities (for testing)
|
|
202
|
+
// ============================================================================
|
|
203
|
+
/**
|
|
204
|
+
* Parse date from result ID
|
|
205
|
+
* Safe: Returns null if invalid format
|
|
206
|
+
*/
|
|
207
|
+
export function parseDateFromId(id) {
|
|
208
|
+
const match = id.match(/(\d{4}-\d{2}-\d{2})\.md/);
|
|
209
|
+
return match ? match[1] : null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Format date for display
|
|
213
|
+
* Safe: Returns original if invalid format
|
|
214
|
+
*/
|
|
215
|
+
export function formatTimelineDate(date) {
|
|
216
|
+
try {
|
|
217
|
+
const d = new Date(date);
|
|
218
|
+
return d.toLocaleDateString("en-US", {
|
|
219
|
+
year: "numeric",
|
|
220
|
+
month: "short",
|
|
221
|
+
day: "numeric",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return date;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// Feature Flags (imported from types)
|
|
230
|
+
// ============================================================================
|
|
231
|
+
import { PROGRESSIVE_DISCLOSURE_FLAGS } from "./progressive-disclosure-types.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Disclosure - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Type definitions for memory search v2
|
|
5
|
+
* Safe: All new types, backward compatible, no breaking changes
|
|
6
|
+
*
|
|
7
|
+
* Concept:
|
|
8
|
+
* - Layer 1 (index): IDs + snippets (~90% token savings)
|
|
9
|
+
* - Layer 2 (timeline): Contextual information by date
|
|
10
|
+
* - Layer 3 (details): Full content on-demand
|
|
11
|
+
*/
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Feature Flags
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Progressive disclosure feature flags
|
|
17
|
+
*/
|
|
18
|
+
export const PROGRESSIVE_DISCLOSURE_FLAGS = {
|
|
19
|
+
/**
|
|
20
|
+
* Master switch for progressive disclosure
|
|
21
|
+
* Default: false (opt-in for safety)
|
|
22
|
+
*/
|
|
23
|
+
ENABLED: false,
|
|
24
|
+
/**
|
|
25
|
+
* Enable index caching (performance optimization)
|
|
26
|
+
* Default: true (safe to enable)
|
|
27
|
+
*/
|
|
28
|
+
CACHE_ENABLED: true,
|
|
29
|
+
/**
|
|
30
|
+
* Maximum cache size (number of entries)
|
|
31
|
+
* Default: 100
|
|
32
|
+
*/
|
|
33
|
+
CACHE_MAX_SIZE: 100,
|
|
34
|
+
/**
|
|
35
|
+
* Cache TTL in milliseconds (5 minutes)
|
|
36
|
+
* Default: 300000
|
|
37
|
+
*/
|
|
38
|
+
CACHE_TTL_MS: 300000,
|
|
39
|
+
/**
|
|
40
|
+
* Enable debug logging
|
|
41
|
+
* Default: false
|
|
42
|
+
*/
|
|
43
|
+
DEBUG_MODE: false,
|
|
44
|
+
};
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Type Guards (safe runtime checking)
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Check if search result is success
|
|
50
|
+
*/
|
|
51
|
+
export function isSearchSuccess(result) {
|
|
52
|
+
return result.success === true;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if search result is error
|
|
56
|
+
*/
|
|
57
|
+
export function isSearchError(result) {
|
|
58
|
+
return result.success === false;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if layer is valid
|
|
62
|
+
*/
|
|
63
|
+
export function isValidLayer(layer) {
|
|
64
|
+
return layer === "index" || layer === "timeline" || layer === "details";
|
|
65
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Disclosure - Integration Layer (Phase 5)
|
|
3
|
+
*
|
|
4
|
+
* Main entry point: memory_search_v2
|
|
5
|
+
* Safe: Feature-flagged, backward compatible, never throws
|
|
6
|
+
*
|
|
7
|
+
* This is the ONLY file that exports the public API.
|
|
8
|
+
* All other files are internal implementation details.
|
|
9
|
+
*/
|
|
10
|
+
import { PROGRESSIVE_DISCLOSURE_FLAGS } from "./progressive-disclosure-types.js";
|
|
11
|
+
import { searchIndexOnly, clearIndexCache } from "./progressive-disclosure-index.js";
|
|
12
|
+
import { loadTimelineContext } from "./progressive-disclosure-timeline.js";
|
|
13
|
+
import { loadFullDetails, resetRateLimiter } from "./progressive-disclosure-details.js";
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Feature Flag (MASTER SWITCH)
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Main feature flag for progressive disclosure
|
|
19
|
+
* Default: OFF (opt-in for safety)
|
|
20
|
+
*
|
|
21
|
+
* To enable: Set PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED = true
|
|
22
|
+
* To disable: Set PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED = false
|
|
23
|
+
*
|
|
24
|
+
* When disabled: memory_search_v2 throws error "Progressive disclosure not enabled"
|
|
25
|
+
* When enabled: memory_search_v2 works normally
|
|
26
|
+
*/
|
|
27
|
+
export const PROGRESSIVE_DISCLOSURE_ENABLED = PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED;
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Main API: memory_search_v2
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Progressive disclosure memory search v2
|
|
33
|
+
*
|
|
34
|
+
* 3-layer search for token efficiency:
|
|
35
|
+
* - Layer 1 (index): IDs + snippets (~90% token savings)
|
|
36
|
+
* - Layer 2 (timeline): Contextual information by date
|
|
37
|
+
* - Layer 3 (details): Full content on-demand
|
|
38
|
+
*
|
|
39
|
+
* Safe:
|
|
40
|
+
* - Feature-flagged (throws when disabled)
|
|
41
|
+
* - Never throws (returns error object on failure)
|
|
42
|
+
* - Backward compatible (memory_search v1 still works)
|
|
43
|
+
*
|
|
44
|
+
* @param query - Search query string
|
|
45
|
+
* @param options - Search options (layer, maxResults, filters)
|
|
46
|
+
* @returns Search result or error object (never throws)
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // Index only (default, fastest)
|
|
51
|
+
* const result = await memory_search_v2('decision');
|
|
52
|
+
* // → { index: [...], timeline: {}, details: {} }
|
|
53
|
+
*
|
|
54
|
+
* // Index + timeline
|
|
55
|
+
* const result = await memory_search_v2('decision', { layer: 'timeline' });
|
|
56
|
+
* // → { index: [...], timeline: {...}, details: {} }
|
|
57
|
+
*
|
|
58
|
+
* // Full details (slowest, most tokens)
|
|
59
|
+
* const result = await memory_search_v2('decision', { layer: 'details' });
|
|
60
|
+
* // → { index: [...], timeline: {...}, details: {...} }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export async function memory_search_v2(query, options = {}) {
|
|
64
|
+
// Feature flag check (safe exit)
|
|
65
|
+
if (!PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: {
|
|
69
|
+
code: "FEATURE_DISABLED",
|
|
70
|
+
message: "Progressive disclosure is not enabled. Set PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED = true to use memory_search_v2.",
|
|
71
|
+
query,
|
|
72
|
+
recoverable: false,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
try {
|
|
78
|
+
// Validate inputs
|
|
79
|
+
const safeQuery = query?.trim() || "";
|
|
80
|
+
if (!safeQuery) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: {
|
|
84
|
+
code: "INVALID_QUERY",
|
|
85
|
+
message: "Query cannot be empty",
|
|
86
|
+
query,
|
|
87
|
+
recoverable: false,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const layer = options.layer || "index";
|
|
92
|
+
const maxResults = options.maxResults || 10;
|
|
93
|
+
// Execute search (layer by layer)
|
|
94
|
+
const result = await executeSearch(safeQuery, layer, maxResults, options);
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
data: result,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// Never throw: return error object
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: {
|
|
105
|
+
code: "SEARCH_FAILED",
|
|
106
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
107
|
+
query,
|
|
108
|
+
layer: options.layer,
|
|
109
|
+
recoverable: true,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
if (PROGRESSIVE_DISCLOSURE_FLAGS.DEBUG_MODE) {
|
|
115
|
+
const duration = Date.now() - startTime;
|
|
116
|
+
console.log(`[pd-v2] Search completed in ${duration}ms`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Execute search layer by layer
|
|
122
|
+
* Safe: Handles all errors, returns complete result
|
|
123
|
+
*/
|
|
124
|
+
async function executeSearch(query, layer, maxResults, options) {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
// Layer 1: Index (always executed)
|
|
127
|
+
const index = await searchIndexOnly(query, {
|
|
128
|
+
maxResults,
|
|
129
|
+
threshold: options.threshold,
|
|
130
|
+
tags: options.tags,
|
|
131
|
+
dateRange: options.dateRange,
|
|
132
|
+
});
|
|
133
|
+
// Layer 2: Timeline (optional)
|
|
134
|
+
let timeline = {};
|
|
135
|
+
if (layer === "timeline" || layer === "details") {
|
|
136
|
+
const ids = index.map((r) => r.id);
|
|
137
|
+
timeline = await loadTimelineContext(ids);
|
|
138
|
+
}
|
|
139
|
+
// Layer 3: Details (optional)
|
|
140
|
+
let details = {};
|
|
141
|
+
if (layer === "details") {
|
|
142
|
+
const ids = index.map((r) => r.id);
|
|
143
|
+
details = await loadFullDetails(ids);
|
|
144
|
+
}
|
|
145
|
+
const executionTimeMs = Date.now() - startTime;
|
|
146
|
+
// Estimate token usage (rough approximation)
|
|
147
|
+
const tokensEstimated = {
|
|
148
|
+
index: index.length * 50, // ~50 tokens per index result
|
|
149
|
+
timeline: Object.keys(timeline).length * 100, // ~100 tokens per timeline entry
|
|
150
|
+
details: Object.values(details).reduce((sum, d) => sum + (d?.content?.length || 0) / 4, 0), // ~4 chars per token
|
|
151
|
+
total: 0, // Calculated below
|
|
152
|
+
};
|
|
153
|
+
tokensEstimated.total =
|
|
154
|
+
tokensEstimated.index + tokensEstimated.timeline + tokensEstimated.details;
|
|
155
|
+
return {
|
|
156
|
+
query,
|
|
157
|
+
layer,
|
|
158
|
+
index,
|
|
159
|
+
timeline: timeline,
|
|
160
|
+
details: details,
|
|
161
|
+
metadata: {
|
|
162
|
+
totalResults: index.length,
|
|
163
|
+
executionTimeMs,
|
|
164
|
+
layerExecuted: layer,
|
|
165
|
+
tokensEstimated: tokensEstimated,
|
|
166
|
+
timestamp: Date.now(),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// Convenience Functions (for ease of use)
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Search index only (fastest, least tokens)
|
|
175
|
+
* Convenience wrapper for memory_search_v2(query, { layer: 'index' })
|
|
176
|
+
*/
|
|
177
|
+
export async function searchIndex(query, maxResults) {
|
|
178
|
+
return memory_search_v2(query, { layer: "index", maxResults });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Search with timeline (medium speed, medium tokens)
|
|
182
|
+
* Convenience wrapper for memory_search_v2(query, { layer: 'timeline' })
|
|
183
|
+
*/
|
|
184
|
+
export async function searchTimeline(query, maxResults) {
|
|
185
|
+
return memory_search_v2(query, { layer: "timeline", maxResults });
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Search with full details (slowest, most tokens)
|
|
189
|
+
* Convenience wrapper for memory_search_v2(query, { layer: 'details' })
|
|
190
|
+
*/
|
|
191
|
+
export async function searchDetails(query, maxResults) {
|
|
192
|
+
return memory_search_v2(query, { layer: "details", maxResults });
|
|
193
|
+
}
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Cache Management (for testing and optimization)
|
|
196
|
+
// ============================================================================
|
|
197
|
+
/**
|
|
198
|
+
* Clear all progressive disclosure caches
|
|
199
|
+
* Safe: No-op when feature flag is disabled
|
|
200
|
+
*/
|
|
201
|
+
export function clearProgressiveDisclosureCache() {
|
|
202
|
+
if (!PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
clearIndexCache();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Reset progressive disclosure state (for testing)
|
|
209
|
+
* Safe: No-op when feature flag is disabled
|
|
210
|
+
*/
|
|
211
|
+
export function resetProgressiveDisclosure() {
|
|
212
|
+
if (!PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
clearIndexCache();
|
|
216
|
+
resetRateLimiter();
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Statistics (for monitoring and debugging)
|
|
220
|
+
// ============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Get progressive disclosure statistics
|
|
223
|
+
* Safe: Returns empty stats when feature flag is disabled
|
|
224
|
+
*/
|
|
225
|
+
export function getProgressiveDisclosureStats() {
|
|
226
|
+
if (!PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED) {
|
|
227
|
+
return {
|
|
228
|
+
enabled: false,
|
|
229
|
+
cache: { size: 0 },
|
|
230
|
+
rateLimiter: { lastLoadTime: 0 },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// Import dynamically to avoid circular dependencies
|
|
234
|
+
const { getIndexCacheStats } = require("./progressive-disclosure-index.js");
|
|
235
|
+
const { getRateLimiterStats } = require("./progressive-disclosure-details.js");
|
|
236
|
+
return {
|
|
237
|
+
enabled: true,
|
|
238
|
+
featureFlags: PROGRESSIVE_DISCLOSURE_FLAGS,
|
|
239
|
+
cache: getIndexCacheStats(),
|
|
240
|
+
rateLimiter: getRateLimiterStats(),
|
|
241
|
+
};
|
|
242
|
+
}
|