obol-ai 0.3.32 → 0.3.34
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
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.3.34
|
|
2
|
+
- generic PostgREST filters for memory, self-memory, and events
|
|
3
|
+
- drop select= filters — return all columns from memory and events
|
|
4
|
+
|
|
5
|
+
## 0.3.33
|
|
6
|
+
- schedule wizard — use config timezone, drop tz selector
|
|
7
|
+
|
|
1
8
|
## 0.3.32
|
|
2
9
|
- tz confirm button shows toast feedback
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,7 +40,7 @@ const definitions = [
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
name: 'memory_query',
|
|
43
|
-
description: 'Filter memories by tag, date, category, source, or
|
|
43
|
+
description: 'Filter memories by tag, date, category, source, importance, or any column. Use for "what did we do today", "anything tagged X", "all decisions this week".',
|
|
44
44
|
input_schema: {
|
|
45
45
|
type: 'object',
|
|
46
46
|
properties: {
|
|
@@ -50,6 +50,7 @@ const definitions = [
|
|
|
50
50
|
source: { type: 'string', description: 'Filter by source (e.g. "turn-extraction", "evolution-3")' },
|
|
51
51
|
minImportance: { type: 'number', description: 'Minimum importance threshold (0-1)' },
|
|
52
52
|
limit: { type: 'number', description: 'Max results (default 20)' },
|
|
53
|
+
filters: { type: 'object', description: 'PostgREST column filters. Key = column name, value = operator.value. E.g. {"importance":"gte.0.8","source":"eq.evolution-3","content":"like.*bitcoin*","access_count":"gte.5"}', additionalProperties: { type: 'string' } },
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
},
|
|
@@ -98,6 +99,7 @@ const handlers = {
|
|
|
98
99
|
source: input.source,
|
|
99
100
|
minImportance: input.minImportance,
|
|
100
101
|
limit: input.limit,
|
|
102
|
+
filters: input.filters,
|
|
101
103
|
});
|
|
102
104
|
return JSON.stringify(results.map(formatMemory));
|
|
103
105
|
},
|
|
@@ -34,6 +34,7 @@ Always search memory first for the user's timezone/location.`,
|
|
|
34
34
|
type: 'object',
|
|
35
35
|
properties: {
|
|
36
36
|
status: { type: 'string', enum: ['pending', 'sent', 'cancelled', 'completed'], description: 'Filter by status (default: pending)' },
|
|
37
|
+
filters: { type: 'object', description: 'PostgREST column filters. Key = column name, value = operator.value. E.g. {"title":"like.*briefing*","cron_expr":"not.is.null","run_count":"gte.5"}', additionalProperties: { type: 'string' } },
|
|
37
38
|
},
|
|
38
39
|
},
|
|
39
40
|
},
|
|
@@ -107,7 +108,7 @@ const handlers = {
|
|
|
107
108
|
|
|
108
109
|
async list_events(input, memory, context) {
|
|
109
110
|
if (!context.scheduler) return 'Scheduler not available (Supabase not configured).';
|
|
110
|
-
const events = await context.scheduler.list({ status: input.status });
|
|
111
|
+
const events = await context.scheduler.list({ status: input.status, filters: input.filters });
|
|
111
112
|
if (events.length === 0) return `No ${input.status || 'pending'} events.`;
|
|
112
113
|
return JSON.stringify(events.map(e => {
|
|
113
114
|
const entry = {
|
package/src/memory/index.js
CHANGED
|
@@ -80,7 +80,7 @@ async function createMemory(supabaseConfig, userId = 0) {
|
|
|
80
80
|
const { start, end } = parseDateRange(dateStr);
|
|
81
81
|
const limit = opts.limit || 50;
|
|
82
82
|
|
|
83
|
-
let fetchUrl = `${url}/rest/v1/obol_memory?
|
|
83
|
+
let fetchUrl = `${url}/rest/v1/obol_memory?created_at=gte.${start.toISOString()}&created_at=lt.${end.toISOString()}&order=created_at.asc&limit=${limit}&user_id=eq.${userId}`;
|
|
84
84
|
if (opts.category) fetchUrl += `&category=eq.${opts.category}`;
|
|
85
85
|
|
|
86
86
|
const res = await fetch(fetchUrl, { headers });
|
|
@@ -91,7 +91,7 @@ async function createMemory(supabaseConfig, userId = 0) {
|
|
|
91
91
|
|
|
92
92
|
async function recent(opts = {}) {
|
|
93
93
|
const limit = opts.limit || 10;
|
|
94
|
-
let fetchUrl = `${url}/rest/v1/obol_memory?
|
|
94
|
+
let fetchUrl = `${url}/rest/v1/obol_memory?order=created_at.desc&limit=${limit}&user_id=eq.${userId}`;
|
|
95
95
|
if (opts.category) fetchUrl += `&category=eq.${opts.category}`;
|
|
96
96
|
|
|
97
97
|
const res = await fetch(fetchUrl, { headers });
|
|
@@ -129,7 +129,7 @@ async function createMemory(supabaseConfig, userId = 0) {
|
|
|
129
129
|
|
|
130
130
|
async function stats() {
|
|
131
131
|
const countHeaders = { ...headers, 'Prefer': 'count=exact' };
|
|
132
|
-
const res = await fetch(`${url}/rest/v1/obol_memory?
|
|
132
|
+
const res = await fetch(`${url}/rest/v1/obol_memory?user_id=eq.${userId}`, { headers: countHeaders });
|
|
133
133
|
if (!res.ok) throw new Error(`Stats failed: HTTP ${res.status}`);
|
|
134
134
|
const contentRange = res.headers?.get?.('content-range');
|
|
135
135
|
const data = await res.json();
|
|
@@ -159,9 +159,14 @@ async function createMemory(supabaseConfig, userId = 0) {
|
|
|
159
159
|
parts.push(`created_at=gte.${start.toISOString()}`);
|
|
160
160
|
parts.push(`created_at=lt.${end.toISOString()}`);
|
|
161
161
|
}
|
|
162
|
+
if (opts.filters) {
|
|
163
|
+
for (const [col, op] of Object.entries(opts.filters)) {
|
|
164
|
+
if (/^[a-z_]+$/.test(col)) parts.push(`${col}=${op}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
162
167
|
const filter = parts.join('&');
|
|
163
168
|
const res = await fetch(
|
|
164
|
-
`${url}/rest/v1/obol_memory
|
|
169
|
+
`${url}/rest/v1/obol_memory?${filter}&order=created_at.desc&limit=${limit}`,
|
|
165
170
|
{ headers }
|
|
166
171
|
);
|
|
167
172
|
const data = await res.json();
|
package/src/memory/self.js
CHANGED
|
@@ -64,7 +64,7 @@ async function createSelfMemory(supabaseConfig, userId) {
|
|
|
64
64
|
|
|
65
65
|
async function recent(opts = {}) {
|
|
66
66
|
const limit = opts.limit || 10;
|
|
67
|
-
let fetchUrl = `${url}/rest/v1/obol_self_memory?
|
|
67
|
+
let fetchUrl = `${url}/rest/v1/obol_self_memory?order=created_at.desc&limit=${limit}&user_id=eq.${userId}`;
|
|
68
68
|
if (opts.category) fetchUrl += `&category=eq.${opts.category}`;
|
|
69
69
|
|
|
70
70
|
const res = await fetch(fetchUrl, { headers });
|
|
@@ -79,9 +79,14 @@ async function createSelfMemory(supabaseConfig, userId) {
|
|
|
79
79
|
if (opts.source) parts.push(`source=eq.${opts.source}`);
|
|
80
80
|
if (opts.minImportance) parts.push(`importance=gte.${opts.minImportance}`);
|
|
81
81
|
if (opts.tags?.length) parts.push(`tags=ov.{${opts.tags.join(',')}}`);
|
|
82
|
+
if (opts.filters) {
|
|
83
|
+
for (const [col, op] of Object.entries(opts.filters)) {
|
|
84
|
+
if (/^[a-z_]+$/.test(col)) parts.push(`${col}=${op}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
const res = await fetch(
|
|
84
|
-
`${url}/rest/v1/obol_self_memory
|
|
89
|
+
`${url}/rest/v1/obol_self_memory?${parts.join('&')}&order=created_at.desc&limit=${limit}`,
|
|
85
90
|
{ headers }
|
|
86
91
|
);
|
|
87
92
|
const data = await res.json();
|
package/src/runtime/scheduler.js
CHANGED
|
@@ -37,7 +37,12 @@ function createScheduler(supabaseConfig, userId = 0) {
|
|
|
37
37
|
async function list(opts = {}) {
|
|
38
38
|
const status = opts.status || 'pending';
|
|
39
39
|
const limit = opts.limit || 20;
|
|
40
|
-
|
|
40
|
+
let fetchUrl = `${url}/rest/v1/obol_events?user_id=eq.${userId}&status=eq.${status}&order=due_at.asc&limit=${limit}`;
|
|
41
|
+
if (opts.filters) {
|
|
42
|
+
for (const [col, op] of Object.entries(opts.filters)) {
|
|
43
|
+
if (/^[a-z_]+$/.test(col)) fetchUrl += `&${col}=${op}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
41
46
|
const res = await fetch(fetchUrl, { headers });
|
|
42
47
|
const data = await res.json();
|
|
43
48
|
if (!res.ok) throw new Error(JSON.stringify(data));
|
|
@@ -57,7 +62,7 @@ function createScheduler(supabaseConfig, userId = 0) {
|
|
|
57
62
|
|
|
58
63
|
async function getDue() {
|
|
59
64
|
const now = new Date().toISOString();
|
|
60
|
-
const fetchUrl = `${url}/rest/v1/obol_events?status=eq.pending&due_at=lte.${now}
|
|
65
|
+
const fetchUrl = `${url}/rest/v1/obol_events?status=eq.pending&due_at=lte.${now}`;
|
|
61
66
|
const res = await fetch(fetchUrl, { headers });
|
|
62
67
|
const data = await res.json();
|
|
63
68
|
if (!res.ok) throw new Error(JSON.stringify(data));
|
|
@@ -78,36 +78,20 @@ async function stepTitle(ctx, userId) {
|
|
|
78
78
|
trackMsg(userId, msg.chat.id, msg.message_id);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async function stepTime(ctx, userId) {
|
|
81
|
+
async function stepTime(ctx, userId, config) {
|
|
82
82
|
const draft = schedDrafts.get(userId);
|
|
83
83
|
if (!draft) return;
|
|
84
84
|
draft.step = 'time';
|
|
85
85
|
|
|
86
|
-
const {
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const tenant = await getTenant(userId, ctx._config || {});
|
|
90
|
-
if (tenant.memory) {
|
|
91
|
-
const hits = await tenant.memory.search('timezone', { limit: 1, threshold: 0.3 });
|
|
92
|
-
for (const h of hits) {
|
|
93
|
-
const match = h.content.match(/(?:timezone|time zone)[:\s]+([A-Za-z_/]+)/i);
|
|
94
|
-
if (match) { tz = match[1]; break; }
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} catch {}
|
|
98
|
-
|
|
86
|
+
const { getUserTimezone } = require('../config');
|
|
87
|
+
const tz = getUserTimezone(config, userId);
|
|
99
88
|
draft.timezone = tz;
|
|
100
89
|
setPendingInput(userId, 'time', TIME_TTL_MS);
|
|
101
90
|
|
|
102
|
-
const kb = new InlineKeyboard()
|
|
103
|
-
.text(`Use ${tz}`, 'sched:tz:confirm')
|
|
104
|
-
.text('Change timezone', 'sched:tz:change');
|
|
105
|
-
|
|
106
91
|
const msg = await sendHtml(ctx,
|
|
107
92
|
`When should it ${draft.isRecurring ? 'first fire' : 'fire'}?\n\n` +
|
|
108
93
|
`Examples: \`2026-03-10 14:00\`, \`tomorrow at 9am\`\n` +
|
|
109
|
-
`Timezone: ${tz}
|
|
110
|
-
{ reply_markup: kb }
|
|
94
|
+
`Timezone: ${tz}`
|
|
111
95
|
);
|
|
112
96
|
trackMsg(userId, msg.chat.id, msg.message_id);
|
|
113
97
|
}
|
|
@@ -331,31 +315,10 @@ async function handleSchedCallback(ctx, data, answer, { getTenant, config, bot }
|
|
|
331
315
|
draft.isRecurring = value === 'recurring' || value === 'agentic-rec';
|
|
332
316
|
draft.isAgentic = value === 'agentic' || value === 'agentic-rec';
|
|
333
317
|
await answer();
|
|
334
|
-
ctx._config = config;
|
|
335
318
|
await stepTitle(ctx, userId);
|
|
336
319
|
return;
|
|
337
320
|
}
|
|
338
321
|
|
|
339
|
-
if (action === 'tz') {
|
|
340
|
-
const draft = schedDrafts.get(userId);
|
|
341
|
-
if (!draft) return answer({ text: 'Session expired' });
|
|
342
|
-
|
|
343
|
-
if (value === 'confirm') {
|
|
344
|
-
await answer({ text: `Using ${draft.timezone} — now type the date/time` });
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
if (value === 'change') {
|
|
348
|
-
await answer();
|
|
349
|
-
cancelPending(userId);
|
|
350
|
-
setPendingInput(userId, 'timezone', TIME_TTL_MS);
|
|
351
|
-
const msg = await ctx.api.sendMessage(ctx.chat?.id || userId,
|
|
352
|
-
'Enter your timezone (e.g. Europe/Brussels, America/New_York, Asia/Tokyo):');
|
|
353
|
-
trackMsg(userId, msg.chat.id, msg.message_id);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
return answer();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
322
|
if (action === 'cron') {
|
|
360
323
|
const draft = schedDrafts.get(userId);
|
|
361
324
|
if (!draft) return answer({ text: 'Session expired' });
|
|
@@ -432,7 +395,7 @@ async function handleSchedCallback(ctx, data, answer, { getTenant, config, bot }
|
|
|
432
395
|
|
|
433
396
|
switch (value) {
|
|
434
397
|
case 'title': await stepTitle(ctx, userId); break;
|
|
435
|
-
case 'time':
|
|
398
|
+
case 'time': await stepTime(ctx, userId, config); break;
|
|
436
399
|
case 'cron': await stepCron(ctx, userId); break;
|
|
437
400
|
case 'desc': await stepDescription(ctx, userId); break;
|
|
438
401
|
case 'instr': await stepInstructions(ctx, userId); break;
|
|
@@ -458,23 +421,7 @@ async function handleSchedText(ctx, text, { getTenant, config, bot }) {
|
|
|
458
421
|
|
|
459
422
|
if (field === 'title') {
|
|
460
423
|
draft.title = text.substring(0, 200);
|
|
461
|
-
ctx
|
|
462
|
-
await stepTime(ctx, userId);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (field === 'timezone') {
|
|
467
|
-
try {
|
|
468
|
-
Intl.DateTimeFormat(undefined, { timeZone: text.trim() });
|
|
469
|
-
draft.timezone = text.trim();
|
|
470
|
-
const msg = await ctx.reply(`Timezone set to ${draft.timezone}. Now enter the date/time.`);
|
|
471
|
-
trackMsg(userId, msg.chat.id, msg.message_id);
|
|
472
|
-
setPendingInput(userId, 'time', TIME_TTL_MS);
|
|
473
|
-
} catch {
|
|
474
|
-
const msg = await ctx.reply('Invalid timezone. Try again (e.g. Europe/Brussels, America/New_York):');
|
|
475
|
-
trackMsg(userId, msg.chat.id, msg.message_id);
|
|
476
|
-
setPendingInput(userId, 'timezone', TIME_TTL_MS);
|
|
477
|
-
}
|
|
424
|
+
await stepTime(ctx, userId, config);
|
|
478
425
|
return;
|
|
479
426
|
}
|
|
480
427
|
|