omnius 1.0.63 → 1.0.66
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/dist/index.js +428 -14
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -609476,6 +609476,147 @@ function parseTelegramInteractionDecision(text, forcedRoute, options2 = {}) {
|
|
|
609476
609476
|
return null;
|
|
609477
609477
|
}
|
|
609478
609478
|
}
|
|
609479
|
+
function parseTelegramTimeRangeQuery(query, now = /* @__PURE__ */ new Date()) {
|
|
609480
|
+
const original = String(query || "");
|
|
609481
|
+
const lower = original.toLowerCase();
|
|
609482
|
+
const nowMs = now.getTime();
|
|
609483
|
+
const dayMs = 864e5;
|
|
609484
|
+
const hourMs = 36e5;
|
|
609485
|
+
const minMs = 6e4;
|
|
609486
|
+
const weekMs = 7 * dayMs;
|
|
609487
|
+
const startOfDay = (d2) => {
|
|
609488
|
+
const x = new Date(d2);
|
|
609489
|
+
x.setHours(0, 0, 0, 0);
|
|
609490
|
+
return x;
|
|
609491
|
+
};
|
|
609492
|
+
const endOfDay = (d2) => {
|
|
609493
|
+
const x = new Date(d2);
|
|
609494
|
+
x.setHours(23, 59, 59, 999);
|
|
609495
|
+
return x;
|
|
609496
|
+
};
|
|
609497
|
+
let since;
|
|
609498
|
+
let until;
|
|
609499
|
+
let bucket;
|
|
609500
|
+
let label = "";
|
|
609501
|
+
let residual = original;
|
|
609502
|
+
const strip = (re) => {
|
|
609503
|
+
residual = residual.replace(re, " ").replace(/\s+/g, " ").trim();
|
|
609504
|
+
};
|
|
609505
|
+
if (/\b(oldest|earliest|first)\b/.test(lower)) {
|
|
609506
|
+
bucket = "earliest";
|
|
609507
|
+
label = label || "earliest";
|
|
609508
|
+
strip(/\b(?:the\s+)?(?:oldest|earliest|first)(?:\s+(?:memory|entry|turn|message|memories))?\b/gi);
|
|
609509
|
+
} else if (/\b(newest|latest|most\s+recent|last\s+memory|last\s+turn|last\s+message)\b/.test(lower)) {
|
|
609510
|
+
bucket = "latest";
|
|
609511
|
+
label = label || "latest";
|
|
609512
|
+
strip(/\b(?:the\s+)?(?:newest|latest|most\s+recent|last\s+(?:memory|turn|message))\b/gi);
|
|
609513
|
+
}
|
|
609514
|
+
if (/\byesterday\b/.test(lower)) {
|
|
609515
|
+
const y = new Date(now);
|
|
609516
|
+
y.setDate(y.getDate() - 1);
|
|
609517
|
+
since = startOfDay(y).getTime();
|
|
609518
|
+
until = endOfDay(y).getTime();
|
|
609519
|
+
label = label ? `${label}, yesterday` : "yesterday";
|
|
609520
|
+
strip(/\byesterday\b/gi);
|
|
609521
|
+
} else if (/\btoday\b/.test(lower)) {
|
|
609522
|
+
since = startOfDay(now).getTime();
|
|
609523
|
+
until = endOfDay(now).getTime();
|
|
609524
|
+
label = label ? `${label}, today` : "today";
|
|
609525
|
+
strip(/\btoday\b/gi);
|
|
609526
|
+
}
|
|
609527
|
+
const partMatch = lower.match(/\bthis\s+(morning|afternoon|evening|night|tonight)\b/);
|
|
609528
|
+
if (partMatch) {
|
|
609529
|
+
const today0 = startOfDay(now).getTime();
|
|
609530
|
+
if (partMatch[1] === "morning") {
|
|
609531
|
+
since = today0;
|
|
609532
|
+
until = today0 + 12 * hourMs;
|
|
609533
|
+
} else if (partMatch[1] === "afternoon") {
|
|
609534
|
+
since = today0 + 12 * hourMs;
|
|
609535
|
+
until = today0 + 18 * hourMs;
|
|
609536
|
+
} else if (partMatch[1] === "evening") {
|
|
609537
|
+
since = today0 + 17 * hourMs;
|
|
609538
|
+
until = today0 + 22 * hourMs;
|
|
609539
|
+
} else {
|
|
609540
|
+
since = today0 + 20 * hourMs;
|
|
609541
|
+
until = endOfDay(now).getTime();
|
|
609542
|
+
}
|
|
609543
|
+
label = label ? `${label}, this ${partMatch[1]}` : `this ${partMatch[1]}`;
|
|
609544
|
+
strip(/\bthis\s+(?:morning|afternoon|evening|night|tonight)\b/gi);
|
|
609545
|
+
}
|
|
609546
|
+
const nMatch = lower.match(/\b(?:last|past|in\s+the\s+last|in\s+the\s+past)\s+(\d+)?\s*(minute|min|hour|hr|day|week|wk)s?\b/);
|
|
609547
|
+
if (nMatch) {
|
|
609548
|
+
const n2 = nMatch[1] ? parseInt(nMatch[1], 10) : 1;
|
|
609549
|
+
const u = nMatch[2];
|
|
609550
|
+
const stepMs = u.startsWith("min") ? minMs : u.startsWith("h") ? hourMs : u.startsWith("d") ? dayMs : weekMs;
|
|
609551
|
+
since = nowMs - n2 * stepMs;
|
|
609552
|
+
until = nowMs;
|
|
609553
|
+
label = label ? `${label}, last ${n2} ${u}${n2 === 1 ? "" : "s"}` : `last ${n2} ${u}${n2 === 1 ? "" : "s"}`;
|
|
609554
|
+
strip(/\b(?:last|past|in\s+the\s+last|in\s+the\s+past)\s+\d*\s*(?:minute|min|hour|hr|day|week|wk)s?\b/gi);
|
|
609555
|
+
}
|
|
609556
|
+
const agoMatch = lower.match(/\b(\d+)\s+(minute|min|hour|hr|day|week|wk)s?\s+ago\b/);
|
|
609557
|
+
if (agoMatch) {
|
|
609558
|
+
const n2 = parseInt(agoMatch[1], 10);
|
|
609559
|
+
const u = agoMatch[2];
|
|
609560
|
+
const stepMs = u.startsWith("min") ? minMs : u.startsWith("h") ? hourMs : u.startsWith("d") ? dayMs : weekMs;
|
|
609561
|
+
const center = nowMs - n2 * stepMs;
|
|
609562
|
+
if (u.startsWith("d")) {
|
|
609563
|
+
const at = new Date(center);
|
|
609564
|
+
since = startOfDay(at).getTime();
|
|
609565
|
+
until = endOfDay(at).getTime();
|
|
609566
|
+
} else {
|
|
609567
|
+
since = center - stepMs;
|
|
609568
|
+
until = center + stepMs;
|
|
609569
|
+
}
|
|
609570
|
+
label = label ? `${label}, ${n2} ${u}${n2 === 1 ? "" : "s"} ago` : `${n2} ${u}${n2 === 1 ? "" : "s"} ago`;
|
|
609571
|
+
strip(/\b\d+\s+(?:minute|min|hour|hr|day|week|wk)s?\s+ago\b/gi);
|
|
609572
|
+
}
|
|
609573
|
+
if (/\bearlier\s+today\b/.test(lower)) {
|
|
609574
|
+
const today0 = startOfDay(now).getTime();
|
|
609575
|
+
since = today0;
|
|
609576
|
+
until = nowMs - 30 * minMs;
|
|
609577
|
+
label = label ? `${label}, earlier today` : "earlier today";
|
|
609578
|
+
strip(/\bearlier\s+today\b/gi);
|
|
609579
|
+
} else if (/\bearlier\b/.test(lower) && since === void 0 && bucket === void 0) {
|
|
609580
|
+
since = nowMs - dayMs;
|
|
609581
|
+
until = nowMs - 30 * minMs;
|
|
609582
|
+
label = label ? `${label}, earlier` : "earlier";
|
|
609583
|
+
strip(/\bearlier\b/gi);
|
|
609584
|
+
}
|
|
609585
|
+
const sinceIsoMatch = original.match(/\bsince\s+(\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}(?::\d{2})?)?)\b/i);
|
|
609586
|
+
if (sinceIsoMatch) {
|
|
609587
|
+
const t2 = Date.parse(sinceIsoMatch[1]);
|
|
609588
|
+
if (Number.isFinite(t2)) {
|
|
609589
|
+
since = t2;
|
|
609590
|
+
until = nowMs;
|
|
609591
|
+
label = label ? `${label}, since ${sinceIsoMatch[1]}` : `since ${sinceIsoMatch[1]}`;
|
|
609592
|
+
strip(new RegExp(`\\bsince\\s+${sinceIsoMatch[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi"));
|
|
609593
|
+
}
|
|
609594
|
+
}
|
|
609595
|
+
const onIsoMatch = original.match(/\bon\s+(\d{4}-\d{2}-\d{2})\b/i);
|
|
609596
|
+
if (onIsoMatch) {
|
|
609597
|
+
const t2 = Date.parse(onIsoMatch[1]);
|
|
609598
|
+
if (Number.isFinite(t2)) {
|
|
609599
|
+
const d2 = new Date(t2);
|
|
609600
|
+
since = startOfDay(d2).getTime();
|
|
609601
|
+
until = endOfDay(d2).getTime();
|
|
609602
|
+
label = label ? `${label}, on ${onIsoMatch[1]}` : `on ${onIsoMatch[1]}`;
|
|
609603
|
+
strip(new RegExp(`\\bon\\s+${onIsoMatch[1]}\\b`, "gi"));
|
|
609604
|
+
}
|
|
609605
|
+
}
|
|
609606
|
+
const betweenMatch = original.match(/\bbetween\s+(\d{4}-\d{2}-\d{2})\s+and\s+(\d{4}-\d{2}-\d{2})\b/i);
|
|
609607
|
+
if (betweenMatch) {
|
|
609608
|
+
const a2 = Date.parse(betweenMatch[1]);
|
|
609609
|
+
const b = Date.parse(betweenMatch[2]);
|
|
609610
|
+
if (Number.isFinite(a2) && Number.isFinite(b)) {
|
|
609611
|
+
since = Math.min(a2, b);
|
|
609612
|
+
until = Math.max(a2, b) + dayMs;
|
|
609613
|
+
label = label ? `${label}, between ${betweenMatch[1]} and ${betweenMatch[2]}` : `between ${betweenMatch[1]} and ${betweenMatch[2]}`;
|
|
609614
|
+
strip(/\bbetween\s+\d{4}-\d{2}-\d{2}\s+and\s+\d{4}-\d{2}-\d{2}\b/gi);
|
|
609615
|
+
}
|
|
609616
|
+
}
|
|
609617
|
+
if (since === void 0 && until === void 0 && bucket === void 0) return null;
|
|
609618
|
+
return { since, until, bucket, label: label || "time-range", residualQuery: residual };
|
|
609619
|
+
}
|
|
609479
609620
|
function parseTelegramReflectionFollowupDecision(text) {
|
|
609480
609621
|
const cleaned = stripTelegramHiddenThinking(text).replace(/```(?:json)?/gi, "").replace(/```/g, "").trim();
|
|
609481
609622
|
const jsonText = cleaned.startsWith("{") ? cleaned : cleaned.match(/\{[\s\S]*\}/)?.[0] ?? "";
|
|
@@ -613237,6 +613378,213 @@ ${lines.join("\n")}`);
|
|
|
613237
613378
|
return `- ${when} ${speaker}${mode}${messageId}: ${telegramContextJsonString(text, maxText)}`;
|
|
613238
613379
|
}).join("\n");
|
|
613239
613380
|
}
|
|
613381
|
+
/**
|
|
613382
|
+
* Build an always-on "memory substrate census" injected into every Telegram
|
|
613383
|
+
* prompt. Anchors the agent against false-empty responses: lists counts for
|
|
613384
|
+
* each store + the chat-scoped memory_read topic filenames that exist on disk.
|
|
613385
|
+
*
|
|
613386
|
+
* Without this, when the lexical scorers (cards/facts/episodes) score zero
|
|
613387
|
+
* for the current query, NO section about those stores reaches the prompt,
|
|
613388
|
+
* and the bot will report "memory empty" in good faith. This section keeps
|
|
613389
|
+
* the substrate visible regardless of per-turn scoring.
|
|
613390
|
+
*/
|
|
613391
|
+
buildTelegramMemorySubstrateCensus(sessionKey, msg) {
|
|
613392
|
+
const cardCount = (this.chatMemoryCards.get(sessionKey) ?? []).length;
|
|
613393
|
+
const memory = this.chatAssociativeMemory.get(sessionKey);
|
|
613394
|
+
const factCount = memory?.facts.length ?? 0;
|
|
613395
|
+
const relationshipCount = memory?.relationships.length ?? 0;
|
|
613396
|
+
const userMemoryCount = memory ? Object.keys(memory.users).length : 0;
|
|
613397
|
+
const historyCount = (this.chatHistory.get(sessionKey) ?? []).length;
|
|
613398
|
+
let sqliteCount = 0;
|
|
613399
|
+
let episodeCount2 = 0;
|
|
613400
|
+
try {
|
|
613401
|
+
const db = this.telegramDb();
|
|
613402
|
+
if (db) {
|
|
613403
|
+
const row = db.prepare("SELECT COUNT(*) AS n FROM telegram_messages WHERE session_key = ?").get(sessionKey);
|
|
613404
|
+
sqliteCount = Number(row?.n) || 0;
|
|
613405
|
+
}
|
|
613406
|
+
} catch {
|
|
613407
|
+
}
|
|
613408
|
+
if (this.repoRoot) {
|
|
613409
|
+
try {
|
|
613410
|
+
const paths = omniusMemoryDbPaths(this.repoRoot);
|
|
613411
|
+
if (existsSync113(paths.episodes)) {
|
|
613412
|
+
const graph = new TemporalGraph(paths.knowledge);
|
|
613413
|
+
const store2 = new EpisodeStore(paths.episodes, graph);
|
|
613414
|
+
try {
|
|
613415
|
+
const sample = store2.search({ sessionId: sessionKey, limit: 1 }) ?? [];
|
|
613416
|
+
if (sample.length > 0) {
|
|
613417
|
+
const dbAny = store2.db;
|
|
613418
|
+
if (dbAny && typeof dbAny.prepare === "function") {
|
|
613419
|
+
const row = dbAny.prepare("SELECT COUNT(*) AS n FROM episodes WHERE session_id = ?").get(sessionKey);
|
|
613420
|
+
episodeCount2 = Number(row?.n) || 0;
|
|
613421
|
+
} else {
|
|
613422
|
+
episodeCount2 = 1;
|
|
613423
|
+
}
|
|
613424
|
+
}
|
|
613425
|
+
} finally {
|
|
613426
|
+
try {
|
|
613427
|
+
store2.close();
|
|
613428
|
+
} catch {
|
|
613429
|
+
}
|
|
613430
|
+
try {
|
|
613431
|
+
graph.close();
|
|
613432
|
+
} catch {
|
|
613433
|
+
}
|
|
613434
|
+
}
|
|
613435
|
+
}
|
|
613436
|
+
} catch {
|
|
613437
|
+
}
|
|
613438
|
+
}
|
|
613439
|
+
const chatId = msg.chatId;
|
|
613440
|
+
const topicFiles = [];
|
|
613441
|
+
if (this.repoRoot && chatId !== void 0) {
|
|
613442
|
+
try {
|
|
613443
|
+
const memDir = resolve43(this.repoRoot, ".omnius", "memory");
|
|
613444
|
+
if (existsSync113(memDir)) {
|
|
613445
|
+
const prefix = this.telegramScopedMemoryPrefix(chatId);
|
|
613446
|
+
for (const file of readdirSync40(memDir)) {
|
|
613447
|
+
if (!file.endsWith(".json") || !file.startsWith(prefix)) continue;
|
|
613448
|
+
const topic = file.slice(prefix.length, -".json".length);
|
|
613449
|
+
if (topic) topicFiles.push(topic);
|
|
613450
|
+
}
|
|
613451
|
+
}
|
|
613452
|
+
} catch {
|
|
613453
|
+
}
|
|
613454
|
+
}
|
|
613455
|
+
const anyMemory = cardCount + factCount + relationshipCount + userMemoryCount + sqliteCount + episodeCount2 + topicFiles.length > 0;
|
|
613456
|
+
if (!anyMemory && historyCount === 0) return "";
|
|
613457
|
+
const lines = [
|
|
613458
|
+
"### Scoped Memory Substrate (this chat — ALWAYS PRESENT)",
|
|
613459
|
+
"Persistent memory IS available for this chat. The current turn's lexical scorers may",
|
|
613460
|
+
"have surfaced 0 matches above — that does NOT mean the substrate is empty. Counts:",
|
|
613461
|
+
`- Memory cards: ${cardCount}`,
|
|
613462
|
+
`- Associative facts: ${factCount}`,
|
|
613463
|
+
`- Associative relationships: ${relationshipCount}`,
|
|
613464
|
+
`- Per-user memories: ${userMemoryCount}`,
|
|
613465
|
+
`- Rolling history entries retained: ${historyCount}`,
|
|
613466
|
+
`- SQLite mirror rows: ${sqliteCount}`,
|
|
613467
|
+
`- Episodes (durable, day+ scope): ${episodeCount2}`
|
|
613468
|
+
];
|
|
613469
|
+
if (topicFiles.length > 0) {
|
|
613470
|
+
lines.push("");
|
|
613471
|
+
lines.push("Per-topic memory files (call memory_read with one of these `topic` values):");
|
|
613472
|
+
for (const topic of topicFiles.slice(0, 80)) {
|
|
613473
|
+
lines.push(` - ${topic}`);
|
|
613474
|
+
}
|
|
613475
|
+
}
|
|
613476
|
+
const sourceHistory = this.chatHistory.get(sessionKey) ?? [];
|
|
613477
|
+
const sortedByTs = [...sourceHistory].filter((e2) => typeof e2.ts === "number" && Number.isFinite(e2.ts)).sort((a2, b) => (a2.ts ?? 0) - (b.ts ?? 0));
|
|
613478
|
+
const fmtHistoryAnchor = (entry) => {
|
|
613479
|
+
const when = entry.ts ? new Date(entry.ts).toISOString() : "(unknown ts)";
|
|
613480
|
+
const speaker = telegramHistorySpeaker(entry);
|
|
613481
|
+
const mode = entry.mode ? `/${entry.mode}` : "";
|
|
613482
|
+
const messageId = entry.messageId ? ` msg:${entry.messageId}` : "";
|
|
613483
|
+
return `${when} ${speaker}${mode}${messageId}: ${telegramContextJsonString(String(entry.text || ""), 320)}`;
|
|
613484
|
+
};
|
|
613485
|
+
if (sortedByTs.length > 0) {
|
|
613486
|
+
lines.push("");
|
|
613487
|
+
lines.push("Chronological anchors — Telegram conversation history (ground truth for 'oldest/newest memory' questions):");
|
|
613488
|
+
lines.push(` EARLIEST turn: ${fmtHistoryAnchor(sortedByTs[0])}`);
|
|
613489
|
+
if (sortedByTs.length > 1) {
|
|
613490
|
+
lines.push(` LATEST turn: ${fmtHistoryAnchor(sortedByTs[sortedByTs.length - 1])}`);
|
|
613491
|
+
}
|
|
613492
|
+
if (sortedByTs.length > 2) {
|
|
613493
|
+
lines.push(` 2nd earliest: ${fmtHistoryAnchor(sortedByTs[1])}`);
|
|
613494
|
+
}
|
|
613495
|
+
if (sortedByTs.length > 3) {
|
|
613496
|
+
lines.push(` 3rd earliest: ${fmtHistoryAnchor(sortedByTs[2])}`);
|
|
613497
|
+
}
|
|
613498
|
+
}
|
|
613499
|
+
if (this.repoRoot) {
|
|
613500
|
+
try {
|
|
613501
|
+
const paths = omniusMemoryDbPaths(this.repoRoot);
|
|
613502
|
+
if (existsSync113(paths.episodes)) {
|
|
613503
|
+
const graph = new TemporalGraph(paths.knowledge);
|
|
613504
|
+
const store2 = new EpisodeStore(paths.episodes, graph);
|
|
613505
|
+
try {
|
|
613506
|
+
const dbAny = store2.db;
|
|
613507
|
+
if (dbAny && typeof dbAny.prepare === "function") {
|
|
613508
|
+
const earliest = dbAny.prepare(
|
|
613509
|
+
"SELECT timestamp, modality, tool_name, content, gist FROM episodes WHERE session_id = ? ORDER BY timestamp ASC LIMIT 1"
|
|
613510
|
+
).get(sessionKey);
|
|
613511
|
+
const latest = dbAny.prepare(
|
|
613512
|
+
"SELECT timestamp, modality, tool_name, content, gist FROM episodes WHERE session_id = ? ORDER BY timestamp DESC LIMIT 1"
|
|
613513
|
+
).get(sessionKey);
|
|
613514
|
+
if (earliest || latest) {
|
|
613515
|
+
lines.push("");
|
|
613516
|
+
lines.push("Chronological anchors — episodes.db (durable, may reach further back than rolling history):");
|
|
613517
|
+
const fmtEp = (row) => {
|
|
613518
|
+
const when = row.timestamp ? new Date(row.timestamp).toISOString() : "(unknown ts)";
|
|
613519
|
+
const tag = `[${row.modality || "?"}${row.tool_name ? ":" + row.tool_name : ""}]`;
|
|
613520
|
+
const text = (row.gist || row.content || "").split("\n").filter((ln) => !/^(Telegram|session:|chat:|message_id:|thread_id:|speaker:|mode:)/i.test(ln.trim())).join(" ").replace(/\s+/g, " ").trim();
|
|
613521
|
+
return `${when} ${tag} ${telegramContextJsonString(text, 320)}`;
|
|
613522
|
+
};
|
|
613523
|
+
if (earliest) lines.push(` EARLIEST episode: ${fmtEp(earliest)}`);
|
|
613524
|
+
if (latest && (!earliest || earliest.timestamp !== latest.timestamp)) lines.push(` LATEST episode: ${fmtEp(latest)}`);
|
|
613525
|
+
}
|
|
613526
|
+
}
|
|
613527
|
+
} finally {
|
|
613528
|
+
try {
|
|
613529
|
+
store2.close();
|
|
613530
|
+
} catch {
|
|
613531
|
+
}
|
|
613532
|
+
try {
|
|
613533
|
+
graph.close();
|
|
613534
|
+
} catch {
|
|
613535
|
+
}
|
|
613536
|
+
}
|
|
613537
|
+
}
|
|
613538
|
+
} catch {
|
|
613539
|
+
}
|
|
613540
|
+
}
|
|
613541
|
+
if (this.repoRoot && chatId !== void 0 && topicFiles.length > 0) {
|
|
613542
|
+
try {
|
|
613543
|
+
const memDir = resolve43(this.repoRoot, ".omnius", "memory");
|
|
613544
|
+
const prefix = this.telegramScopedMemoryPrefix(chatId);
|
|
613545
|
+
let earliestEntry = null;
|
|
613546
|
+
let latestEntry = null;
|
|
613547
|
+
for (const topic of topicFiles) {
|
|
613548
|
+
const file = join127(memDir, `${prefix}${topic}.json`);
|
|
613549
|
+
if (!existsSync113(file)) continue;
|
|
613550
|
+
let parsed;
|
|
613551
|
+
try {
|
|
613552
|
+
parsed = JSON.parse(readFileSync92(file, "utf8"));
|
|
613553
|
+
} catch {
|
|
613554
|
+
continue;
|
|
613555
|
+
}
|
|
613556
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
613557
|
+
if (!entry || typeof entry !== "object") continue;
|
|
613558
|
+
const rawTs = entry.timestamp;
|
|
613559
|
+
const ts = typeof rawTs === "string" ? Date.parse(rawTs) : typeof rawTs === "number" ? rawTs : NaN;
|
|
613560
|
+
if (!Number.isFinite(ts)) continue;
|
|
613561
|
+
const value2 = String(entry.value ?? "");
|
|
613562
|
+
if (!earliestEntry || ts < earliestEntry.ts) earliestEntry = { topic, key, value: value2, ts };
|
|
613563
|
+
if (!latestEntry || ts > latestEntry.ts) latestEntry = { topic, key, value: value2, ts };
|
|
613564
|
+
}
|
|
613565
|
+
}
|
|
613566
|
+
if (earliestEntry || latestEntry) {
|
|
613567
|
+
lines.push("");
|
|
613568
|
+
lines.push("Chronological anchors — memory_write entries (most-trusted, agent-asserted):");
|
|
613569
|
+
const fmtMem = (e2) => {
|
|
613570
|
+
const when = new Date(e2.ts).toISOString();
|
|
613571
|
+
return `${when} topic="${e2.topic}" key="${e2.key}" → ${telegramContextJsonString(e2.value, 320)}`;
|
|
613572
|
+
};
|
|
613573
|
+
if (earliestEntry) lines.push(` EARLIEST memory_write: ${fmtMem(earliestEntry)}`);
|
|
613574
|
+
if (latestEntry && (!earliestEntry || earliestEntry.ts !== latestEntry.ts)) lines.push(` LATEST memory_write: ${fmtMem(latestEntry)}`);
|
|
613575
|
+
}
|
|
613576
|
+
} catch {
|
|
613577
|
+
}
|
|
613578
|
+
}
|
|
613579
|
+
lines.push("");
|
|
613580
|
+
lines.push("RULES:");
|
|
613581
|
+
lines.push(" 1. NEVER tell the user 'memory is empty' or 'nothing has been stored' for this chat without first calling memory_search and memory_read on a relevant topic from the list above.");
|
|
613582
|
+
lines.push(" 2. If the structured sections (cards/facts/sqlite/episodes) above did not surface what the user asked about, that is a SCORING miss, not absence. Call memory_search with broader tokens or pick a topic above with memory_read.");
|
|
613583
|
+
lines.push(" 3. The rolling-history block is base context; the cards/facts/episodes are retrieval-augmented. Treat them as the same memory, surfaced different ways.");
|
|
613584
|
+
lines.push(" 4. For 'what is your oldest/earliest memory' or 'most recent memory' questions: answer DIRECTLY from the 'Chronological anchors' lines above. Quote the timestamp and content. Do NOT call tools first and do NOT report 'empty'.");
|
|
613585
|
+
lines.push(" 5. memory_search accepts NATURAL-LANGUAGE TIME phrases inside the `query` argument or explicit `since`/`until`/`bucket` args. Examples: query='what did manitcor say yesterday', query='last 3 hours', query='earlier today', query='2 days ago', query='since 2026-05-15', query='between 2026-05-15 and 2026-05-16', query='oldest memory about github', query='most recent flux discussion'. Use these for chronological/'how far back' style queries instead of guessing — the tool parses the phrase, filters by time, and returns the right window.");
|
|
613586
|
+
return lines.join("\n");
|
|
613587
|
+
}
|
|
613240
613588
|
buildTelegramConversationContextStream(sessionKey, msg, maxRecent = TELEGRAM_CONTEXT_RECENT_DEFAULT) {
|
|
613241
613589
|
this.ensureTelegramConversationLoaded(sessionKey);
|
|
613242
613590
|
const history = this.chatHistory.get(sessionKey) ?? [];
|
|
@@ -613260,6 +613608,11 @@ ${lines.join("\n")}`);
|
|
|
613260
613608
|
if (currentReplyContext) {
|
|
613261
613609
|
sections.push(currentReplyContext);
|
|
613262
613610
|
}
|
|
613611
|
+
try {
|
|
613612
|
+
const substrateCensus = this.buildTelegramMemorySubstrateCensus(sessionKey, msg);
|
|
613613
|
+
if (substrateCensus) sections.push(substrateCensus);
|
|
613614
|
+
} catch {
|
|
613615
|
+
}
|
|
613263
613616
|
const scopedPersonality = this.renderTelegramScopedPersonality(sessionKey, msg);
|
|
613264
613617
|
if (scopedPersonality) {
|
|
613265
613618
|
sections.push(scopedPersonality);
|
|
@@ -615073,7 +615426,7 @@ ${result.llmContent ?? result.output}` };
|
|
|
615073
615426
|
if (tool.name === "memory_search") {
|
|
615074
615427
|
return {
|
|
615075
615428
|
...tool,
|
|
615076
|
-
description: "Search only this Telegram chat's isolated durable memory: raw SQLite message mirror, episode/knowledge graph recall, associative user facts, and memory cards. Supports scope=group/current_chat or scope=user with user_id/username,
|
|
615429
|
+
description: "Search only this Telegram chat's isolated durable memory: raw SQLite message mirror, episode/knowledge graph recall, associative user facts, and memory cards. Supports scope=group/current_chat or scope=user with user_id/username; also supports natural-language time phrases inside the query string ('yesterday', 'last 3 hours', '2 days ago', 'earlier today', 'since 2026-05-15', 'between A and B', 'oldest', 'most recent') OR explicit since/until/bucket arguments. Never crosses into admin/private/global memory.",
|
|
615077
615430
|
parameters: (() => {
|
|
615078
615431
|
const base3 = tool.parameters ?? {};
|
|
615079
615432
|
const props = base3["properties"] && typeof base3["properties"] === "object" && !Array.isArray(base3["properties"]) ? base3["properties"] : {};
|
|
@@ -615084,15 +615437,57 @@ ${result.llmContent ?? result.output}` };
|
|
|
615084
615437
|
scope: { type: "string", enum: ["group", "current_chat", "user"], description: "Search scope inside the current Telegram chat. scope=user defaults to the current sender when no user_id/username is supplied." },
|
|
615085
615438
|
user_id: { type: "number", description: "Optional Telegram user id to search within the current group only." },
|
|
615086
615439
|
username: { type: "string", description: "Optional Telegram username to search within the current group only." },
|
|
615087
|
-
group_id: { type: "string", description: "Optional Telegram group/chat id. Must match the current chat id; other groups are not accessible here." }
|
|
615440
|
+
group_id: { type: "string", description: "Optional Telegram group/chat id. Must match the current chat id; other groups are not accessible here." },
|
|
615441
|
+
since: { type: "string", description: "Optional lower-bound timestamp (ISO 8601 or epoch ms). Combine with `until` for an explicit window. If omitted, natural-language phrases inside `query` are auto-parsed (e.g. 'yesterday', '3 days ago')." },
|
|
615442
|
+
until: { type: "string", description: "Optional upper-bound timestamp (ISO 8601 or epoch ms)." },
|
|
615443
|
+
bucket: { type: "string", enum: ["earliest", "latest"], description: "Chronological ordering hint when there is no specific window. 'earliest' returns the oldest entries on record; 'latest' returns the newest." }
|
|
615088
615444
|
}
|
|
615089
615445
|
};
|
|
615090
615446
|
})(),
|
|
615091
615447
|
execute: async (args) => {
|
|
615092
|
-
const
|
|
615448
|
+
const rawQuery = String(args["query"] || "").trim();
|
|
615093
615449
|
const maxResults = typeof args["max_results"] === "number" && Number.isFinite(args["max_results"]) ? Math.max(1, Math.min(20, Math.floor(args["max_results"]))) : 8;
|
|
615094
|
-
if (!
|
|
615450
|
+
if (!rawQuery) return { success: true, output: "Search query is required." };
|
|
615095
615451
|
this.ensureTelegramConversationLoaded(msgSessionKey);
|
|
615452
|
+
const parseTimeArg = (v) => {
|
|
615453
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
615454
|
+
if (typeof v === "number" && Number.isFinite(v)) return Math.trunc(v);
|
|
615455
|
+
if (typeof v === "string") {
|
|
615456
|
+
if (/^\d+$/.test(v.trim())) return Number(v.trim());
|
|
615457
|
+
const t2 = Date.parse(v);
|
|
615458
|
+
if (Number.isFinite(t2)) return t2;
|
|
615459
|
+
}
|
|
615460
|
+
return void 0;
|
|
615461
|
+
};
|
|
615462
|
+
let since = parseTimeArg(args["since"]);
|
|
615463
|
+
let until = parseTimeArg(args["until"]);
|
|
615464
|
+
let bucket = args["bucket"] === "earliest" || args["bucket"] === "latest" ? args["bucket"] : void 0;
|
|
615465
|
+
let timeLabel = "";
|
|
615466
|
+
let effectiveQuery = rawQuery;
|
|
615467
|
+
if (since === void 0 && until === void 0 && bucket === void 0) {
|
|
615468
|
+
const parsed = parseTelegramTimeRangeQuery(rawQuery);
|
|
615469
|
+
if (parsed) {
|
|
615470
|
+
since = parsed.since;
|
|
615471
|
+
until = parsed.until;
|
|
615472
|
+
bucket = parsed.bucket;
|
|
615473
|
+
timeLabel = parsed.label;
|
|
615474
|
+
effectiveQuery = parsed.residualQuery || rawQuery;
|
|
615475
|
+
}
|
|
615476
|
+
} else if (since !== void 0 || until !== void 0 || bucket !== void 0) {
|
|
615477
|
+
timeLabel = [
|
|
615478
|
+
since !== void 0 ? `since=${new Date(since).toISOString()}` : "",
|
|
615479
|
+
until !== void 0 ? `until=${new Date(until).toISOString()}` : "",
|
|
615480
|
+
bucket ? `bucket=${bucket}` : ""
|
|
615481
|
+
].filter(Boolean).join(", ");
|
|
615482
|
+
}
|
|
615483
|
+
const inWindow = (ts) => {
|
|
615484
|
+
if (since === void 0 && until === void 0) return true;
|
|
615485
|
+
if (ts == null || !Number.isFinite(ts)) return false;
|
|
615486
|
+
if (since !== void 0 && ts < since) return false;
|
|
615487
|
+
if (until !== void 0 && ts > until) return false;
|
|
615488
|
+
return true;
|
|
615489
|
+
};
|
|
615490
|
+
const query = effectiveQuery.trim();
|
|
615096
615491
|
const currentGroupId = chatId === void 0 ? "" : String(chatId);
|
|
615097
615492
|
const requestedGroupId = String(args["group_id"] ?? args["chat_id"] ?? "").trim();
|
|
615098
615493
|
if (requestedGroupId && currentGroupId && requestedGroupId !== currentGroupId) {
|
|
@@ -615121,13 +615516,17 @@ ${result.llmContent ?? result.output}` };
|
|
|
615121
615516
|
return false;
|
|
615122
615517
|
});
|
|
615123
615518
|
const queryTokens = telegramMemoryTokens(query);
|
|
615124
|
-
const cardResults = cards.map((card) => ({
|
|
615519
|
+
const cardResults = cards.filter((card) => inWindow(card.updatedAt ?? card.createdAt)).map((card) => ({
|
|
615125
615520
|
card,
|
|
615126
615521
|
score: telegramMemorySimilarity(
|
|
615127
615522
|
queryTokens,
|
|
615128
615523
|
telegramMemoryTokens([card.title, card.tags.join(" "), card.speakers.join(" "), card.notes.join(" ")].join(" "))
|
|
615129
615524
|
)
|
|
615130
|
-
})).filter((item) => item.score > 0
|
|
615525
|
+
})).filter((item) => item.score > 0 || since !== void 0 || until !== void 0 || bucket !== void 0).sort((a2, b) => {
|
|
615526
|
+
if (bucket === "earliest") return (a2.card.updatedAt ?? 0) - (b.card.updatedAt ?? 0);
|
|
615527
|
+
if (bucket === "latest") return (b.card.updatedAt ?? 0) - (a2.card.updatedAt ?? 0);
|
|
615528
|
+
return b.score - a2.score || b.card.updatedAt - a2.card.updatedAt;
|
|
615529
|
+
}).slice(0, maxResults);
|
|
615131
615530
|
const cardLines = cardResults.map(({ card, score }) => {
|
|
615132
615531
|
const notes2 = card.notes.slice(-4).map((note) => ` - ${truncateTelegramContextLine(note, 220)}`).join("\n");
|
|
615133
615532
|
const tags = card.tags.length ? ` tags:${card.tags.slice(0, 8).join(",")}` : "";
|
|
@@ -615140,13 +615539,17 @@ ${notes2}`;
|
|
|
615140
615539
|
if (!wantsUserScope) return true;
|
|
615141
615540
|
if (effectiveUserId !== void 0 && fact.userIds.includes(effectiveUserId)) return true;
|
|
615142
615541
|
return !!effectiveUsername && fact.usernames.includes(effectiveUsername);
|
|
615143
|
-
}).map((fact) => ({
|
|
615542
|
+
}).filter((fact) => inWindow(fact.updatedAt ?? fact.createdAt)).map((fact) => ({
|
|
615144
615543
|
fact,
|
|
615145
615544
|
score: telegramMemorySimilarity(
|
|
615146
615545
|
queryTokens,
|
|
615147
615546
|
telegramMemoryTokens([fact.text, fact.tags.join(" "), fact.speakers.join(" ")].join(" "))
|
|
615148
615547
|
) + (effectiveUserId !== void 0 && fact.userIds.includes(effectiveUserId) ? 0.3 : 0) + (effectiveUsername && fact.usernames.includes(effectiveUsername) ? 0.3 : 0)
|
|
615149
|
-
})).filter((item) => item.score > 0
|
|
615548
|
+
})).filter((item) => item.score > 0 || since !== void 0 || until !== void 0 || bucket !== void 0).sort((a2, b) => {
|
|
615549
|
+
if (bucket === "earliest") return (a2.fact.updatedAt ?? 0) - (b.fact.updatedAt ?? 0);
|
|
615550
|
+
if (bucket === "latest") return (b.fact.updatedAt ?? 0) - (a2.fact.updatedAt ?? 0);
|
|
615551
|
+
return b.score - a2.score || b.fact.updatedAt - a2.fact.updatedAt;
|
|
615552
|
+
}).slice(0, maxResults) : [];
|
|
615150
615553
|
const rawRows = this.searchTelegramSqliteMirrorRows(msgSessionKey, query, {
|
|
615151
615554
|
limit: maxResults,
|
|
615152
615555
|
userId: effectiveUserId,
|
|
@@ -615156,8 +615559,9 @@ ${notes2}`;
|
|
|
615156
615559
|
const historyForScan = this.chatHistory.get(msgSessionKey) ?? [];
|
|
615157
615560
|
const lowerQuery = query.toLowerCase().trim();
|
|
615158
615561
|
const queryWords = lowerQuery.split(/\s+/).filter((w) => w.length >= 3);
|
|
615562
|
+
const hasTimeConstraint = since !== void 0 || until !== void 0 || bucket !== void 0;
|
|
615159
615563
|
const rawHistoryMatches = [];
|
|
615160
|
-
if (historyForScan.length > 0
|
|
615564
|
+
if (historyForScan.length > 0) {
|
|
615161
615565
|
for (const entry of historyForScan) {
|
|
615162
615566
|
if (wantsUserScope) {
|
|
615163
615567
|
const eUser = entry.fromUserId;
|
|
@@ -615165,15 +615569,24 @@ ${notes2}`;
|
|
|
615165
615569
|
const matchesUser = effectiveUserId !== void 0 && eUser === effectiveUserId || effectiveUsername.length > 0 && eName === effectiveUsername;
|
|
615166
615570
|
if (!matchesUser) continue;
|
|
615167
615571
|
}
|
|
615572
|
+
if (!inWindow(entry.ts ?? null)) continue;
|
|
615168
615573
|
const hay = String(entry.text || "").toLowerCase();
|
|
615169
615574
|
if (!hay) continue;
|
|
615170
|
-
|
|
615171
|
-
|
|
615172
|
-
|
|
615575
|
+
let lexMatch = false;
|
|
615576
|
+
if (lowerQuery.length > 0) {
|
|
615577
|
+
const hasExact = hay.includes(lowerQuery);
|
|
615578
|
+
const tokenHits = queryWords.filter((w) => hay.includes(w)).length;
|
|
615579
|
+
lexMatch = hasExact || queryWords.length > 0 && tokenHits >= Math.min(2, queryWords.length);
|
|
615580
|
+
}
|
|
615581
|
+
if (lexMatch || hasTimeConstraint) {
|
|
615173
615582
|
rawHistoryMatches.push(entry);
|
|
615174
615583
|
}
|
|
615175
615584
|
}
|
|
615176
|
-
|
|
615585
|
+
if (bucket === "earliest") {
|
|
615586
|
+
rawHistoryMatches.sort((a2, b) => (a2.ts ?? 0) - (b.ts ?? 0));
|
|
615587
|
+
} else {
|
|
615588
|
+
rawHistoryMatches.sort((a2, b) => (b.ts ?? 0) - (a2.ts ?? 0));
|
|
615589
|
+
}
|
|
615177
615590
|
}
|
|
615178
615591
|
const rawHistorySliced = rawHistoryMatches.slice(0, maxResults);
|
|
615179
615592
|
const scopeLabel = wantsUserScope ? `user ${effectiveUsername ? `@${effectiveUsername}` : `id ${effectiveUserId}`}` : `chat ${currentGroupId || msgSessionKey}`;
|
|
@@ -615220,8 +615633,9 @@ ${lines.join("\n")}`
|
|
|
615220
615633
|
output: `No structured matches for "${query}" in ${scopeLabel}, but the scope IS populated: ${scannedCount} memory cards, ${associativeCount} associative facts, ${historyCount} history entries scanned. Try a broader query (single keyword), a different angle (related concept), or memory_read with topic="${this.telegramScopedTopic(chatId, "general")}" to enumerate. If you're looking for an older turn, ask for the speaker name plus a topic word.`
|
|
615221
615634
|
};
|
|
615222
615635
|
}
|
|
615636
|
+
const timeBanner = timeLabel ? ` (time-range: ${timeLabel}${since !== void 0 || until !== void 0 ? `; window=${since !== void 0 ? new Date(since).toISOString() : "−∞"} → ${until !== void 0 ? new Date(until).toISOString() : "+∞"}` : ""})` : "";
|
|
615223
615637
|
const output = [
|
|
615224
|
-
`Scoped Telegram memory search for "${query}" in ${scopeLabel}.`,
|
|
615638
|
+
`Scoped Telegram memory search for "${query}" in ${scopeLabel}${timeBanner}.`,
|
|
615225
615639
|
"Results are scoped to this Telegram chat and may include raw message evidence, graph episodes, associative facts, and memory cards.",
|
|
615226
615640
|
"",
|
|
615227
615641
|
sections.join("\n\n")
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omnius",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.66",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED