openkbs 0.0.66 ā 0.0.70
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 +1 -0
- package/elastic/README.md +1 -1
- package/elastic/functions.md +5 -5
- package/elastic/pulse.md +2 -2
- package/package.json +2 -2
- package/scripts/deploy.js +68 -0
- package/src/actions.js +59 -0
- package/src/index.js +8 -6
- package/templates/.claude/skills/openkbs/SKILL.md +37 -8
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/README.md +55 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/instructions.txt +40 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/settings.json +41 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/openkbs.json +3 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/actions.js +141 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/handler.js +32 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/memoryHelpers.js +91 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onCronjob.js +105 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onPublicAPIRequest.js +165 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onRequest.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onResponse.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.js +74 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.json +3 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/index.mjs +228 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/package.json +7 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/index.mjs +287 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/package.json +10 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/settings.json +4 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/openkbs.json +16 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/index.html +658 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/settings.json +4 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-batch-processing.md +278 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-monitoring.md +341 -0
- package/templates/.claude/skills/openkbs/patterns/file-upload.md +205 -0
- package/templates/.claude/skills/openkbs/patterns/image-generation.md +139 -0
- package/templates/.claude/skills/openkbs/patterns/memory-system.md +264 -0
- package/templates/.claude/skills/openkbs/patterns/public-api-item-proxy.md +254 -0
- package/templates/.claude/skills/openkbs/patterns/scheduled-tasks.md +157 -0
- package/templates/.claude/skills/openkbs/patterns/telegram-webhook.md +424 -0
- package/templates/.claude/skills/openkbs/patterns/telegram.md +222 -0
- package/templates/.claude/skills/openkbs/patterns/vectordb-archive.md +231 -0
- package/templates/.claude/skills/openkbs/patterns/video-generation.md +145 -0
- package/templates/.claude/skills/openkbs/patterns/web-publishing.md +257 -0
- package/templates/.claude/skills/openkbs/reference/backend-sdk.md +13 -2
- package/templates/.claude/skills/openkbs/reference/elastic-services.md +61 -29
- package/templates/platform/README.md +35 -0
- package/templates/platform/agents/assistant/app/icon.png +0 -0
- package/templates/platform/agents/assistant/app/instructions.txt +13 -0
- package/templates/platform/agents/assistant/app/settings.json +13 -0
- package/templates/platform/agents/assistant/src/Events/actions.js +50 -0
- package/templates/platform/agents/assistant/src/Events/handler.js +54 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.json +5 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.json +5 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.js +27 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.json +10 -0
- package/templates/platform/functions/api/index.mjs +63 -0
- package/templates/platform/functions/api/package.json +4 -0
- package/templates/platform/openkbs.json +19 -0
- package/templates/platform/site/index.html +75 -0
- package/version.json +3 -3
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/run_job.js +0 -26
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/utils/agent_client.js +0 -265
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Cronjob Batch Processing Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for scheduled batch processing of files/documents.
|
|
4
|
+
|
|
5
|
+
## Concept
|
|
6
|
+
|
|
7
|
+
Process files from storage in batches with:
|
|
8
|
+
- Timestamp-based deduplication (prevents reprocessing)
|
|
9
|
+
- Configurable batch size limits
|
|
10
|
+
- Processing state persistence
|
|
11
|
+
- Early termination to avoid timeouts
|
|
12
|
+
|
|
13
|
+
## onCronjob.js
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
export const handler = async (event) => {
|
|
17
|
+
try {
|
|
18
|
+
// Load or create agent config
|
|
19
|
+
let agent;
|
|
20
|
+
try {
|
|
21
|
+
agent = await openkbs.getItem('agent');
|
|
22
|
+
agent = agent.item.body;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// First run - create config
|
|
25
|
+
const now = new Date();
|
|
26
|
+
agent = {
|
|
27
|
+
processingYear: String(now.getFullYear()),
|
|
28
|
+
processingMonth: String(now.getMonth() + 1).padStart(2, '0'),
|
|
29
|
+
batchSize: 10,
|
|
30
|
+
processingStatus: 'RUNNING', // or 'PAUSED'
|
|
31
|
+
lastProcessedTimestamp: null,
|
|
32
|
+
currentBatchDocumentCount: 0,
|
|
33
|
+
totalDocumentsProcessed: 0,
|
|
34
|
+
lastUpdated: now.toISOString(),
|
|
35
|
+
processingStartedAt: null
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
await openkbs.createItem({
|
|
39
|
+
itemType: 'agent',
|
|
40
|
+
itemId: 'agent',
|
|
41
|
+
body: agent
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if paused (optional - can use KB status instead)
|
|
46
|
+
if (agent.processingStatus === 'PAUSED') {
|
|
47
|
+
return { success: true, skipped: true, reason: 'paused' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reset batch counter
|
|
51
|
+
const now = new Date();
|
|
52
|
+
agent.currentBatchDocumentCount = 0;
|
|
53
|
+
agent.currentBatchStartedAt = now.toISOString();
|
|
54
|
+
if (!agent.processingStartedAt) {
|
|
55
|
+
agent.processingStartedAt = now.toISOString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Process files
|
|
59
|
+
const result = await processFilesFromStorage(agent);
|
|
60
|
+
|
|
61
|
+
// Update statistics
|
|
62
|
+
agent.currentBatchDocumentCount = result.processed;
|
|
63
|
+
agent.totalDocumentsProcessed += result.processed;
|
|
64
|
+
agent.lastUpdated = now.toISOString();
|
|
65
|
+
if (result.lastTimestamp) {
|
|
66
|
+
agent.lastProcessedTimestamp = result.lastTimestamp;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Save state
|
|
70
|
+
await openkbs.updateItem({
|
|
71
|
+
itemType: 'agent',
|
|
72
|
+
itemId: 'agent',
|
|
73
|
+
body: agent
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
documentsInBatch: result.processed,
|
|
79
|
+
totalProcessed: agent.totalDocumentsProcessed
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return { success: false, error: error.message };
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
handler.CRON_SCHEDULE = "* * * * *"; // Every minute
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## File Processing Logic
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
async function processFilesFromStorage(agent) {
|
|
94
|
+
// List files from storage
|
|
95
|
+
const files = await openkbs.kb({
|
|
96
|
+
action: 'listObjects',
|
|
97
|
+
namespace: 'files',
|
|
98
|
+
prefix: `invoices/${agent.processingYear}/${agent.processingMonth}/`
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!files?.Contents?.length) {
|
|
102
|
+
return { processed: 0, lastTimestamp: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Filter by timestamp (skip already processed)
|
|
106
|
+
const newFiles = files.Contents.filter(file => {
|
|
107
|
+
if (!agent.lastProcessedTimestamp) return true;
|
|
108
|
+
return file.LastModified > agent.lastProcessedTimestamp;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Sort by timestamp (oldest first)
|
|
112
|
+
newFiles.sort((a, b) => new Date(a.LastModified) - new Date(b.LastModified));
|
|
113
|
+
|
|
114
|
+
// Limit to batch size
|
|
115
|
+
const batch = newFiles.slice(0, agent.batchSize);
|
|
116
|
+
|
|
117
|
+
let processed = 0;
|
|
118
|
+
let lastTimestamp = agent.lastProcessedTimestamp;
|
|
119
|
+
|
|
120
|
+
for (const file of batch) {
|
|
121
|
+
try {
|
|
122
|
+
// Parse folder structure: invoices/CompanyName-EIK/Year/Month/Type/
|
|
123
|
+
const pathParts = file.Key.split('/');
|
|
124
|
+
const companyFolder = pathParts[1]; // "CompanyName-123456789"
|
|
125
|
+
const eik = companyFolder.split('-').pop();
|
|
126
|
+
|
|
127
|
+
// Get file URL
|
|
128
|
+
const fileUrl = await openkbs.kb({
|
|
129
|
+
action: 'createPresignedURL',
|
|
130
|
+
namespace: 'files',
|
|
131
|
+
fileName: file.Key,
|
|
132
|
+
fileType: 'image/jpeg',
|
|
133
|
+
presignedOperation: 'getObject'
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Create chat for LLM processing
|
|
137
|
+
await openkbs.chats({
|
|
138
|
+
chatTitle: `Process: ${file.Key.split('/').pop()}`,
|
|
139
|
+
message: JSON.stringify([
|
|
140
|
+
{ type: "text", text: `PROCESS_DOCUMENT\nCompany EIK: ${eik}\nFile: ${file.Key}` },
|
|
141
|
+
{ type: "image_url", image_url: { url: fileUrl } }
|
|
142
|
+
])
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
processed++;
|
|
146
|
+
lastTimestamp = file.LastModified;
|
|
147
|
+
|
|
148
|
+
// Early termination if approaching timeout
|
|
149
|
+
if (processed >= agent.batchSize) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.error(`Failed to process ${file.Key}:`, e.message);
|
|
155
|
+
// Continue with next file
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { processed, lastTimestamp };
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Agent State Management
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// Pause processing
|
|
167
|
+
async function pauseAgent() {
|
|
168
|
+
const agent = await openkbs.getItem('agent');
|
|
169
|
+
await openkbs.updateItem({
|
|
170
|
+
itemType: 'agent',
|
|
171
|
+
itemId: 'agent',
|
|
172
|
+
body: {
|
|
173
|
+
...agent.item.body,
|
|
174
|
+
processingStatus: 'PAUSED',
|
|
175
|
+
lastUpdated: new Date().toISOString()
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Resume processing
|
|
181
|
+
async function resumeAgent() {
|
|
182
|
+
const agent = await openkbs.getItem('agent');
|
|
183
|
+
await openkbs.updateItem({
|
|
184
|
+
itemType: 'agent',
|
|
185
|
+
itemId: 'agent',
|
|
186
|
+
body: {
|
|
187
|
+
...agent.item.body,
|
|
188
|
+
processingStatus: 'RUNNING',
|
|
189
|
+
lastUpdated: new Date().toISOString()
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Change processing period
|
|
195
|
+
async function setProcessingPeriod(year, month) {
|
|
196
|
+
const agent = await openkbs.getItem('agent');
|
|
197
|
+
await openkbs.updateItem({
|
|
198
|
+
itemType: 'agent',
|
|
199
|
+
itemId: 'agent',
|
|
200
|
+
body: {
|
|
201
|
+
...agent.item.body,
|
|
202
|
+
processingYear: String(year),
|
|
203
|
+
processingMonth: String(month).padStart(2, '0'),
|
|
204
|
+
lastProcessedTimestamp: null, // Reset for new period
|
|
205
|
+
lastUpdated: new Date().toISOString()
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Frontend Status Display
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// In contentRender.js Header component
|
|
215
|
+
const AgentStatusBadge = () => {
|
|
216
|
+
const [status, setStatus] = React.useState(null);
|
|
217
|
+
|
|
218
|
+
React.useEffect(() => {
|
|
219
|
+
const fetchStatus = async () => {
|
|
220
|
+
try {
|
|
221
|
+
const agent = await openkbs.items({ action: 'getItem', itemId: 'agent' });
|
|
222
|
+
setStatus(agent?.item?.body);
|
|
223
|
+
} catch (e) {}
|
|
224
|
+
};
|
|
225
|
+
fetchStatus();
|
|
226
|
+
const interval = setInterval(fetchStatus, 30000);
|
|
227
|
+
return () => clearInterval(interval);
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
230
|
+
if (!status) return null;
|
|
231
|
+
|
|
232
|
+
const isRunning = status.processingStatus === 'RUNNING';
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div style={{
|
|
236
|
+
padding: '4px 8px',
|
|
237
|
+
borderRadius: '4px',
|
|
238
|
+
background: isRunning ? '#4caf50' : '#ff9800',
|
|
239
|
+
color: 'white',
|
|
240
|
+
fontSize: '12px'
|
|
241
|
+
}}>
|
|
242
|
+
{isRunning ? 'š¢ RUNNING' : 'š PAUSED'}
|
|
243
|
+
<div style={{ fontSize: '10px' }}>
|
|
244
|
+
{status.totalDocumentsProcessed} docs processed
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## instructions.txt (LLM prompt)
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Batch Processing Commands:
|
|
255
|
+
|
|
256
|
+
Set processing period:
|
|
257
|
+
<setProcessingPeriod>{"year": 2025, "month": 1}</setProcessingPeriod>
|
|
258
|
+
|
|
259
|
+
Pause/resume:
|
|
260
|
+
<pauseAgent/>
|
|
261
|
+
<resumeAgent/>
|
|
262
|
+
|
|
263
|
+
Get status:
|
|
264
|
+
<getAgentStatus/>
|
|
265
|
+
|
|
266
|
+
Documents are processed automatically every minute when RUNNING.
|
|
267
|
+
Processing period determines which folder to scan.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Key Points
|
|
271
|
+
|
|
272
|
+
1. **Timestamp deduplication** - `lastProcessedTimestamp` prevents reprocessing
|
|
273
|
+
2. **Batch limits** - Process max N files per run to avoid timeouts
|
|
274
|
+
3. **State persistence** - Agent config stored in `agent` item
|
|
275
|
+
4. **Folder structure** - Parse metadata from path (company, year, month)
|
|
276
|
+
5. **Early termination** - Stop when batch limit reached
|
|
277
|
+
6. **Error isolation** - One file failure doesn't stop the batch
|
|
278
|
+
7. **CRON_SCHEDULE** - Export handler property to set schedule
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Cronjob Monitoring Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for continuous monitoring with pulse interval control.
|
|
4
|
+
|
|
5
|
+
## Concept
|
|
6
|
+
|
|
7
|
+
Monitor external sources (cameras, APIs, sensors) with:
|
|
8
|
+
- Pulse interval control (execute every N minutes)
|
|
9
|
+
- Sleep/hibernation mode
|
|
10
|
+
- Weather/sensor data injection
|
|
11
|
+
- Reference image comparison
|
|
12
|
+
- Multi-source parallel fetching
|
|
13
|
+
|
|
14
|
+
## onCronjob.js
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { getAgentSetting, setAgentSetting, setMemoryValue } from './memoryHelpers.js';
|
|
18
|
+
|
|
19
|
+
// Check if agent should execute this minute
|
|
20
|
+
async function shouldExecute() {
|
|
21
|
+
try {
|
|
22
|
+
// Check sleep mode
|
|
23
|
+
const sleepUntil = await getAgentSetting('agent_sleepUntil');
|
|
24
|
+
if (sleepUntil) {
|
|
25
|
+
const sleepUntilDate = new Date(sleepUntil);
|
|
26
|
+
if (sleepUntilDate > new Date()) {
|
|
27
|
+
return { execute: false, reason: 'sleeping' };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check pulse interval (default: every minute)
|
|
32
|
+
const pulseInterval = (await getAgentSetting('agent_pulseInterval')) || 1;
|
|
33
|
+
const currentMinute = new Date().getMinutes();
|
|
34
|
+
|
|
35
|
+
if (currentMinute % pulseInterval !== 0) {
|
|
36
|
+
return { execute: false, reason: 'pulse_interval' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { execute: true, interval: pulseInterval };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return { execute: true, interval: 1 };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const handler = async (event) => {
|
|
46
|
+
try {
|
|
47
|
+
// Check execution conditions
|
|
48
|
+
const status = await shouldExecute();
|
|
49
|
+
if (!status.execute) {
|
|
50
|
+
return { success: true, skipped: true, reason: status.reason };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fetch all data sources in parallel
|
|
54
|
+
const [cameraData, weatherData, sensorData] = await Promise.all([
|
|
55
|
+
fetchCameraImages(),
|
|
56
|
+
fetchWeatherData(),
|
|
57
|
+
fetchSensorReadings()
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
// Build message for LLM analysis
|
|
61
|
+
const message = [];
|
|
62
|
+
|
|
63
|
+
message.push({
|
|
64
|
+
type: "text",
|
|
65
|
+
text: "PROCESS_MONITORING_CHECK"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Add camera images
|
|
69
|
+
for (const camera of cameraData) {
|
|
70
|
+
message.push({
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `\nš¹ ${camera.name} - ${camera.timestamp}`
|
|
73
|
+
});
|
|
74
|
+
message.push({
|
|
75
|
+
type: "image_url",
|
|
76
|
+
image_url: { url: camera.imageUrl }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add reference images from memory
|
|
81
|
+
await injectReferenceImages(message);
|
|
82
|
+
|
|
83
|
+
// Add weather data periodically (every hour)
|
|
84
|
+
const currentMinute = new Date().getMinutes();
|
|
85
|
+
if (currentMinute === 0) {
|
|
86
|
+
await injectWeatherData(message, weatherData);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create analysis chat
|
|
90
|
+
const now = new Date();
|
|
91
|
+
const timeStr = now.toLocaleTimeString('en-GB', {
|
|
92
|
+
hour: '2-digit',
|
|
93
|
+
minute: '2-digit',
|
|
94
|
+
timeZone: 'Europe/Sofia'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await openkbs.chats({
|
|
98
|
+
chatTitle: `Monitoring - ${timeStr}`,
|
|
99
|
+
message: JSON.stringify(message)
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return { success: true, sources: cameraData.length };
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return { success: false, error: error.message };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
handler.CRON_SCHEDULE = "* * * * *";
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Data Fetching Helpers
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// Fetch latest images from cameras
|
|
116
|
+
async function fetchCameraImages() {
|
|
117
|
+
const cameras = [
|
|
118
|
+
{ name: 'Camera-North', url: 'https://cdn.example.com/cam1/' },
|
|
119
|
+
{ name: 'Camera-South', url: 'https://cdn.example.com/cam2/' },
|
|
120
|
+
{ name: 'Camera-East', url: 'https://cdn.example.com/cam3/' },
|
|
121
|
+
{ name: 'Camera-West', url: 'https://cdn.example.com/cam4/' }
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const results = await Promise.all(cameras.map(async (cam) => {
|
|
125
|
+
try {
|
|
126
|
+
// Fetch directory listing
|
|
127
|
+
const response = await fetch(cam.url);
|
|
128
|
+
const html = await response.text();
|
|
129
|
+
|
|
130
|
+
// Parse image filenames (adjust regex for your CDN format)
|
|
131
|
+
const matches = html.match(/href="([^"]+\.jpg)"/g) || [];
|
|
132
|
+
const files = matches.map(m => m.match(/href="([^"]+)"/)[1]);
|
|
133
|
+
|
|
134
|
+
// Get latest image
|
|
135
|
+
const latest = files.sort().pop();
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
name: cam.name,
|
|
139
|
+
imageUrl: `${cam.url}${latest}`,
|
|
140
|
+
timestamp: extractTimestamp(latest)
|
|
141
|
+
};
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return { name: cam.name, error: e.message };
|
|
144
|
+
}
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
return results.filter(r => r.imageUrl);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extract timestamp from filename (e.g., "20250104_143022.jpg")
|
|
151
|
+
function extractTimestamp(filename) {
|
|
152
|
+
const match = filename.match(/(\d{8})_(\d{6})/);
|
|
153
|
+
if (match) {
|
|
154
|
+
const [_, date, time] = match;
|
|
155
|
+
return `${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6,8)} ${time.slice(0,2)}:${time.slice(2,4)}:${time.slice(4,6)}`;
|
|
156
|
+
}
|
|
157
|
+
return 'Unknown';
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Reference Image Injection
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// Inject stored reference images for comparison
|
|
165
|
+
async function injectReferenceImages(message) {
|
|
166
|
+
try {
|
|
167
|
+
const imageMemories = await openkbs.fetchItems({
|
|
168
|
+
beginsWith: 'memory_with_image_',
|
|
169
|
+
limit: 50,
|
|
170
|
+
field: 'itemId'
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (imageMemories?.items?.length > 0) {
|
|
174
|
+
message.push({
|
|
175
|
+
type: "text",
|
|
176
|
+
text: `\nšø REFERENCE IMAGES (${imageMemories.items.length})\nUse these for comparison with current state.`
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
for (const item of imageMemories.items) {
|
|
180
|
+
const value = item.item?.body?.value;
|
|
181
|
+
if (value?.imageUrl) {
|
|
182
|
+
const description = value.description || 'Reference';
|
|
183
|
+
|
|
184
|
+
message.push({
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `\nš·ļø ${description}`
|
|
187
|
+
});
|
|
188
|
+
message.push({
|
|
189
|
+
type: "image_url",
|
|
190
|
+
image_url: { url: value.imageUrl }
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error('Failed to fetch reference images:', e.message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Weather Data Injection
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
async function injectWeatherData(message, weatherData) {
|
|
205
|
+
message.push({
|
|
206
|
+
type: 'text',
|
|
207
|
+
text: `\nš WEATHER DATA\nAnalyze conditions and include weatherSummary in your assessment.`
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Add weather graphs/images if available
|
|
211
|
+
if (weatherData.graphs) {
|
|
212
|
+
for (const graph of weatherData.graphs) {
|
|
213
|
+
message.push({
|
|
214
|
+
type: "image_url",
|
|
215
|
+
image_url: { url: graph.url }
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Fetch current weather for multiple locations
|
|
221
|
+
const locations = [
|
|
222
|
+
{ name: 'Location-A', lat: 42.20, lon: 22.81 },
|
|
223
|
+
{ name: 'Location-B', lat: 42.06, lon: 22.77 }
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const weatherPromises = locations.map(async (loc) => {
|
|
227
|
+
try {
|
|
228
|
+
const response = await axios.get('https://webtools.openkbs.com/weather', {
|
|
229
|
+
params: { lat: loc.lat, lon: loc.lon }
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
name: loc.name,
|
|
233
|
+
temp: response.data.main?.temp,
|
|
234
|
+
humidity: response.data.main?.humidity,
|
|
235
|
+
wind: response.data.wind?.speed,
|
|
236
|
+
condition: response.data.weather?.[0]?.main
|
|
237
|
+
};
|
|
238
|
+
} catch (e) {
|
|
239
|
+
return { name: loc.name, error: e.message };
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const locationWeather = await Promise.all(weatherPromises);
|
|
244
|
+
|
|
245
|
+
// Store in memory for context
|
|
246
|
+
await setMemoryValue('memory_locations_weather', locationWeather);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Agent Control Commands (actions.js)
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
// Hibernate agent until specific time
|
|
254
|
+
[/<hibernateAgent>([\\s\\S]*?)<\\/hibernateAgent>/s, async (match) => {
|
|
255
|
+
try {
|
|
256
|
+
const data = JSON.parse(match[1].trim());
|
|
257
|
+
let sleepUntil;
|
|
258
|
+
|
|
259
|
+
if (data.hours) {
|
|
260
|
+
sleepUntil = new Date(Date.now() + data.hours * 60 * 60 * 1000);
|
|
261
|
+
} else if (data.days) {
|
|
262
|
+
sleepUntil = new Date(Date.now() + data.days * 24 * 60 * 60 * 1000);
|
|
263
|
+
} else if (data.until) {
|
|
264
|
+
sleepUntil = new Date(data.until);
|
|
265
|
+
} else {
|
|
266
|
+
throw new Error('Specify hours, days, or until (ISO timestamp)');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
await setAgentSetting('agent_sleepUntil', sleepUntil.toISOString());
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
type: "AGENT_HIBERNATING",
|
|
273
|
+
sleepUntil: sleepUntil.toISOString(),
|
|
274
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
275
|
+
};
|
|
276
|
+
} catch (e) {
|
|
277
|
+
return { type: "HIBERNATE_ERROR", error: e.message, _meta_actions: ["REQUEST_CHAT_MODEL"] };
|
|
278
|
+
}
|
|
279
|
+
}],
|
|
280
|
+
|
|
281
|
+
// Set pulse interval (how often to execute)
|
|
282
|
+
[/<setAgentSettings>([\\s\\S]*?)<\\/setAgentSettings>/s, async (match) => {
|
|
283
|
+
try {
|
|
284
|
+
const data = JSON.parse(match[1].trim());
|
|
285
|
+
|
|
286
|
+
if (data.pulseInterval !== undefined) {
|
|
287
|
+
const interval = Math.max(1, Math.min(60, parseInt(data.pulseInterval)));
|
|
288
|
+
await setAgentSetting('agent_pulseInterval', interval);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (data.wakeUp === true) {
|
|
292
|
+
await setAgentSetting('agent_sleepUntil', null);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
type: "AGENT_SETTINGS_UPDATED",
|
|
297
|
+
settings: data,
|
|
298
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
299
|
+
};
|
|
300
|
+
} catch (e) {
|
|
301
|
+
return { type: "SETTINGS_ERROR", error: e.message, _meta_actions: ["REQUEST_CHAT_MODEL"] };
|
|
302
|
+
}
|
|
303
|
+
}]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## instructions.txt (LLM prompt)
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
Monitoring Commands:
|
|
310
|
+
|
|
311
|
+
Hibernate agent:
|
|
312
|
+
<hibernateAgent>{"hours": 24}</hibernateAgent>
|
|
313
|
+
<hibernateAgent>{"days": 7}</hibernateAgent>
|
|
314
|
+
<hibernateAgent>{"until": "2025-01-15T08:00:00Z"}</hibernateAgent>
|
|
315
|
+
|
|
316
|
+
Change pulse interval (1-60 minutes):
|
|
317
|
+
<setAgentSettings>{"pulseInterval": 5}</setAgentSettings>
|
|
318
|
+
|
|
319
|
+
Wake up agent:
|
|
320
|
+
<setAgentSettings>{"wakeUp": true}</setAgentSettings>
|
|
321
|
+
|
|
322
|
+
Store reference image:
|
|
323
|
+
<setMemory>{
|
|
324
|
+
"itemId": "memory_with_image_camera1_sunset",
|
|
325
|
+
"value": {
|
|
326
|
+
"imageUrl": "https://...",
|
|
327
|
+
"description": "Normal sunset glow - not fire",
|
|
328
|
+
"camera": "Camera-North"
|
|
329
|
+
}
|
|
330
|
+
}</setMemory>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Key Points
|
|
334
|
+
|
|
335
|
+
1. **Pulse interval** - Control execution frequency (every N minutes)
|
|
336
|
+
2. **Sleep mode** - Hibernate until specific time
|
|
337
|
+
3. **Parallel fetching** - Fetch all sources simultaneously
|
|
338
|
+
4. **Reference images** - Store `memory_with_image_*` for comparison
|
|
339
|
+
5. **Weather injection** - Add contextual data periodically
|
|
340
|
+
6. **Timezone handling** - Use proper timezone for display
|
|
341
|
+
7. **CRON_SCHEDULE** - Runs every minute, agent controls actual execution
|