@bryti/agent 0.0.1 → 0.1.0
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/Dockerfile +27 -0
- package/README.md +77 -50
- package/config.example.yml +265 -0
- package/dist/active-hours.d.ts +23 -0
- package/dist/active-hours.d.ts.map +1 -0
- package/dist/active-hours.js +68 -0
- package/dist/active-hours.js.map +1 -0
- package/dist/agent.d.ts +84 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +383 -0
- package/dist/agent.js.map +1 -0
- package/dist/channels/markdown/ir.d.ts +79 -0
- package/dist/channels/markdown/ir.d.ts.map +1 -0
- package/dist/channels/markdown/ir.js +824 -0
- package/dist/channels/markdown/ir.js.map +1 -0
- package/dist/channels/markdown/render.d.ts +35 -0
- package/dist/channels/markdown/render.d.ts.map +1 -0
- package/dist/channels/markdown/render.js +178 -0
- package/dist/channels/markdown/render.js.map +1 -0
- package/dist/channels/telegram-network-errors.d.ts +27 -0
- package/dist/channels/telegram-network-errors.d.ts.map +1 -0
- package/dist/channels/telegram-network-errors.js +156 -0
- package/dist/channels/telegram-network-errors.js.map +1 -0
- package/dist/channels/telegram.d.ts +76 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +814 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +59 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +9 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +45 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +310 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +635 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +113 -0
- package/dist/commands.js.map +1 -0
- package/dist/compaction/history.d.ts +17 -0
- package/dist/compaction/history.d.ts.map +1 -0
- package/dist/compaction/history.js +35 -0
- package/dist/compaction/history.js.map +1 -0
- package/dist/compaction/index.d.ts +3 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +3 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/proactive.d.ts +25 -0
- package/dist/compaction/proactive.d.ts.map +1 -0
- package/dist/compaction/proactive.js +87 -0
- package/dist/compaction/proactive.js.map +1 -0
- package/dist/compaction/transcript-repair.d.ts +55 -0
- package/dist/compaction/transcript-repair.d.ts.map +1 -0
- package/dist/compaction/transcript-repair.js +215 -0
- package/dist/compaction/transcript-repair.js.map +1 -0
- package/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +317 -0
- package/dist/config.js.map +1 -0
- package/dist/crash-recovery.d.ts +23 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +96 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/defaults/extensions/EXTENSIONS.md +158 -0
- package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
- package/dist/history.d.ts +31 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +673 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/conversation-search.d.ts +15 -0
- package/dist/memory/conversation-search.d.ts.map +1 -0
- package/dist/memory/conversation-search.js +60 -0
- package/dist/memory/conversation-search.js.map +1 -0
- package/dist/memory/core-memory.d.ts +28 -0
- package/dist/memory/core-memory.d.ts.map +1 -0
- package/dist/memory/core-memory.js +102 -0
- package/dist/memory/core-memory.js.map +1 -0
- package/dist/memory/embeddings.d.ts +44 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +139 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/search.d.ts +49 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +97 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +205 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/message-queue.d.ts +73 -0
- package/dist/message-queue.d.ts.map +1 -0
- package/dist/message-queue.js +188 -0
- package/dist/message-queue.js.map +1 -0
- package/dist/model-infra.d.ts +64 -0
- package/dist/model-infra.d.ts.map +1 -0
- package/dist/model-infra.js +202 -0
- package/dist/model-infra.js.map +1 -0
- package/dist/projection/format.d.ts +10 -0
- package/dist/projection/format.d.ts.map +1 -0
- package/dist/projection/format.js +30 -0
- package/dist/projection/format.js.map +1 -0
- package/dist/projection/index.d.ts +11 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +9 -0
- package/dist/projection/index.js.map +1 -0
- package/dist/projection/reflection.d.ts +94 -0
- package/dist/projection/reflection.d.ts.map +1 -0
- package/dist/projection/reflection.js +334 -0
- package/dist/projection/reflection.js.map +1 -0
- package/dist/projection/store.d.ts +144 -0
- package/dist/projection/store.d.ts.map +1 -0
- package/dist/projection/store.js +519 -0
- package/dist/projection/store.js.map +1 -0
- package/dist/projection/tools.d.ts +11 -0
- package/dist/projection/tools.d.ts.map +1 -0
- package/dist/projection/tools.js +237 -0
- package/dist/projection/tools.js.map +1 -0
- package/dist/scheduler.d.ts +36 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +286 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/system-prompt.d.ts +41 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +162 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/time.d.ts +52 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +138 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/archival-memory-tool.d.ts +8 -0
- package/dist/tools/archival-memory-tool.d.ts.map +1 -0
- package/dist/tools/archival-memory-tool.js +68 -0
- package/dist/tools/archival-memory-tool.js.map +1 -0
- package/dist/tools/conversation-search-tool.d.ts +6 -0
- package/dist/tools/conversation-search-tool.d.ts.map +1 -0
- package/dist/tools/conversation-search-tool.js +28 -0
- package/dist/tools/conversation-search-tool.js.map +1 -0
- package/dist/tools/core-memory-tool.d.ts +7 -0
- package/dist/tools/core-memory-tool.d.ts.map +1 -0
- package/dist/tools/core-memory-tool.js +59 -0
- package/dist/tools/core-memory-tool.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +15 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +76 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +127 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +118 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/result.d.ts +21 -0
- package/dist/tools/result.d.ts.map +1 -0
- package/dist/tools/result.js +36 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/skill-install.d.ts +17 -0
- package/dist/tools/skill-install.d.ts.map +1 -0
- package/dist/tools/skill-install.js +148 -0
- package/dist/tools/skill-install.js.map +1 -0
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +237 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/trust/guardrail.d.ts +60 -0
- package/dist/trust/guardrail.d.ts.map +1 -0
- package/dist/trust/guardrail.js +171 -0
- package/dist/trust/guardrail.js.map +1 -0
- package/dist/trust/index.d.ts +12 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +12 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/store.d.ts +118 -0
- package/dist/trust/store.d.ts.map +1 -0
- package/dist/trust/store.js +209 -0
- package/dist/trust/store.js.map +1 -0
- package/dist/trust/wrapper.d.ts +36 -0
- package/dist/trust/wrapper.d.ts.map +1 -0
- package/dist/trust/wrapper.js +142 -0
- package/dist/trust/wrapper.js.map +1 -0
- package/dist/usage.d.ts +53 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +124 -0
- package/dist/usage.js.map +1 -0
- package/dist/util/math.d.ts +9 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +22 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/ssrf.d.ts +21 -0
- package/dist/util/ssrf.d.ts.map +1 -0
- package/dist/util/ssrf.js +77 -0
- package/dist/util/ssrf.js.map +1 -0
- package/dist/workers/index.d.ts +8 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +7 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/registry.d.ts +53 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +38 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scoped-tools.d.ts +21 -0
- package/dist/workers/scoped-tools.d.ts.map +1 -0
- package/dist/workers/scoped-tools.js +111 -0
- package/dist/workers/scoped-tools.js.map +1 -0
- package/dist/workers/spawn.d.ts +62 -0
- package/dist/workers/spawn.d.ts.map +1 -0
- package/dist/workers/spawn.js +314 -0
- package/dist/workers/spawn.js.map +1 -0
- package/dist/workers/tools.d.ts +26 -0
- package/dist/workers/tools.d.ts.map +1 -0
- package/dist/workers/tools.js +380 -0
- package/dist/workers/tools.js.map +1 -0
- package/docker-compose.yml +72 -0
- package/package.json +16 -1
- package/run.sh +27 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection reflection pass.
|
|
3
|
+
*
|
|
4
|
+
* Lightweight background job that scans recent conversation history for
|
|
5
|
+
* future references the agent missed during live chat.
|
|
6
|
+
*
|
|
7
|
+
* Runs every 30 min via cron. Reads the JSONL audit log, makes a single
|
|
8
|
+
* completeSimple() call with a narrow extraction prompt (no agent loop,
|
|
9
|
+
* no tools), parses the JSON output, and writes projections directly to
|
|
10
|
+
* SQLite. Existing pending projections are included in the prompt so the
|
|
11
|
+
* model won't duplicate them. A per-user timestamp tracks the last run
|
|
12
|
+
* to skip unchanged transcripts.
|
|
13
|
+
*
|
|
14
|
+
* Why completeSimple() instead of a full agent loop?
|
|
15
|
+
* The reflection pass has no side effects and requires no tool calls. It only
|
|
16
|
+
* needs one prompt in and one JSON blob out. Using a full agent loop would add
|
|
17
|
+
* latency, cost, and the risk of unintended tool invocations. completeSimple()
|
|
18
|
+
* is cheaper, faster, and keeps the pass strictly read-only from the model's
|
|
19
|
+
* perspective.
|
|
20
|
+
*/
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import Database from "better-sqlite3";
|
|
24
|
+
import { completeSimple } from "@mariozechner/pi-ai";
|
|
25
|
+
import { createProjectionStore } from "./store.js";
|
|
26
|
+
import { formatProjectionsForPrompt } from "./format.js";
|
|
27
|
+
import { toUtc, getUserTimezone } from "../time.js";
|
|
28
|
+
import { createModelInfra, resolveFirstModel } from "../model-infra.js";
|
|
29
|
+
/**
|
|
30
|
+
* Read user+assistant messages from the JSONL audit log for the last
|
|
31
|
+
* `windowMinutes`. Returns entries in chronological order, capped at
|
|
32
|
+
* `maxMessages`.
|
|
33
|
+
*
|
|
34
|
+
* Reads from the JSONL audit log written by src/compaction/history.ts, NOT
|
|
35
|
+
* from the pi SDK session file. The audit log is the only way to get
|
|
36
|
+
* structured turn-by-turn history outside of a live session context: the pi
|
|
37
|
+
* session file is append-only and interleaved with tool scaffolding, whereas
|
|
38
|
+
* the audit log contains clean role/content/timestamp records per message.
|
|
39
|
+
*/
|
|
40
|
+
export function readRecentHistory(historyDir, windowMinutes, maxMessages = 40) {
|
|
41
|
+
if (!fs.existsSync(historyDir))
|
|
42
|
+
return [];
|
|
43
|
+
const cutoff = new Date(Date.now() - windowMinutes * 60 * 1000);
|
|
44
|
+
const files = fs.readdirSync(historyDir)
|
|
45
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
46
|
+
.sort()
|
|
47
|
+
.reverse(); // Most recent first
|
|
48
|
+
const collected = [];
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
// Quick file-level date check: skip files older than cutoff date
|
|
51
|
+
const fileDate = path.basename(file, ".jsonl"); // "YYYY-MM-DD"
|
|
52
|
+
const fileDateObj = new Date(fileDate + "T00:00:00Z");
|
|
53
|
+
// A file from yesterday may still have messages within the window
|
|
54
|
+
if (fileDateObj.getTime() + 86400 * 1000 < cutoff.getTime()) {
|
|
55
|
+
break; // Files are sorted newest first; nothing older will match
|
|
56
|
+
}
|
|
57
|
+
const filePath = path.join(historyDir, file);
|
|
58
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
59
|
+
for (const line of lines.reverse()) { // Newest first within file
|
|
60
|
+
if (!line.trim())
|
|
61
|
+
continue;
|
|
62
|
+
try {
|
|
63
|
+
const entry = JSON.parse(line);
|
|
64
|
+
if (entry.role !== "user" && entry.role !== "assistant")
|
|
65
|
+
continue;
|
|
66
|
+
const ts = new Date(entry.timestamp);
|
|
67
|
+
if (ts < cutoff)
|
|
68
|
+
continue;
|
|
69
|
+
collected.push(entry);
|
|
70
|
+
if (collected.length >= maxMessages)
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Skip malformed lines
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (collected.length >= maxMessages)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
// Return chronological order
|
|
81
|
+
return collected.reverse();
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Last-reflection tracking
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Read/write the last reflection timestamp from a metadata table in memory.db.
|
|
88
|
+
*
|
|
89
|
+
* The timestamp is used to skip reflection when there are no new messages
|
|
90
|
+
* since the last run: if the newest audit-log entry is not newer than the
|
|
91
|
+
* stored timestamp, the pass exits early without calling the LLM. This keeps
|
|
92
|
+
* cron overhead negligible for idle users.
|
|
93
|
+
*
|
|
94
|
+
* The timestamp is stored in the same SQLite database as archival memory
|
|
95
|
+
* (memory.db), so it survives process restarts. A plain text file was
|
|
96
|
+
* considered but SQLite gives atomic writes for free.
|
|
97
|
+
*/
|
|
98
|
+
function getLastReflectionTimestamp(db) {
|
|
99
|
+
db.exec(`
|
|
100
|
+
CREATE TABLE IF NOT EXISTS reflection_meta (
|
|
101
|
+
key TEXT PRIMARY KEY,
|
|
102
|
+
value TEXT NOT NULL
|
|
103
|
+
);
|
|
104
|
+
`);
|
|
105
|
+
const row = db.prepare("SELECT value FROM reflection_meta WHERE key = 'last_reflection'").get();
|
|
106
|
+
return row?.value ?? null;
|
|
107
|
+
}
|
|
108
|
+
function setLastReflectionTimestamp(db, ts) {
|
|
109
|
+
db.prepare("INSERT OR REPLACE INTO reflection_meta (key, value) VALUES ('last_reflection', ?)").run(ts);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Single chat completion via the pi SDK provider layer. Uses reflection_model
|
|
113
|
+
* if configured, otherwise falls back to the primary model and then the
|
|
114
|
+
* fallback chain. This lets operators use a cheaper model for reflection.
|
|
115
|
+
*/
|
|
116
|
+
export async function sdkComplete(config, messages) {
|
|
117
|
+
const { modelRegistry } = createModelInfra(config);
|
|
118
|
+
// Resolve the model: reflection_model > primary model > first fallback
|
|
119
|
+
const candidates = [
|
|
120
|
+
config.agent.reflection_model,
|
|
121
|
+
config.agent.model,
|
|
122
|
+
...(config.agent.fallback_models ?? []),
|
|
123
|
+
].filter(Boolean);
|
|
124
|
+
const model = resolveFirstModel(candidates, modelRegistry);
|
|
125
|
+
if (!model) {
|
|
126
|
+
throw new Error(`Reflection: no usable model found. Tried: ${candidates.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
// Separate system prompt from user/assistant messages
|
|
129
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
130
|
+
const userMessages = messages.filter((m) => m.role !== "system");
|
|
131
|
+
const context = {
|
|
132
|
+
systemPrompt: systemMsg?.content,
|
|
133
|
+
messages: userMessages.map((m) => ({
|
|
134
|
+
role: m.role,
|
|
135
|
+
content: m.content,
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
const apiKey = await modelRegistry.getApiKey(model);
|
|
140
|
+
const result = await completeSimple(model, context, {
|
|
141
|
+
maxTokens: 1024,
|
|
142
|
+
temperature: 0,
|
|
143
|
+
apiKey: apiKey ?? undefined,
|
|
144
|
+
});
|
|
145
|
+
if (result.stopReason === "error") {
|
|
146
|
+
throw new Error(`Reflection LLM error: ${result.errorMessage ?? "unknown"}`);
|
|
147
|
+
}
|
|
148
|
+
return result.content
|
|
149
|
+
.filter((c) => c.type === "text")
|
|
150
|
+
.map((c) => c.type === "text" ? c.text : "")
|
|
151
|
+
.join("");
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Reflection prompt
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
function buildReflectionPrompt(turns, pendingProjections, currentDatetime) {
|
|
157
|
+
const transcript = turns
|
|
158
|
+
.map((t) => `${t.role.toUpperCase()}: ${t.content}`)
|
|
159
|
+
.join("\n\n");
|
|
160
|
+
const systemPrompt = `You are a memory assistant. Your job is to extract future commitments, ` +
|
|
161
|
+
`plans, deadlines, reminders, and events from a conversation transcript.\n\n` +
|
|
162
|
+
`Current datetime: ${currentDatetime}\n\n` +
|
|
163
|
+
`Rules:\n` +
|
|
164
|
+
`- Only extract things that are clearly about the FUTURE (from the perspective of the current datetime).\n` +
|
|
165
|
+
`- Do NOT extract things already listed under "Already stored".\n` +
|
|
166
|
+
`- Resolve time expressions to ISO dates or datetimes where possible.\n` +
|
|
167
|
+
`- For 'when': use "YYYY-MM-DD HH:MM" for exact times, "YYYY-MM-DD" for day-resolution, ` +
|
|
168
|
+
`"YYYY-Www" for week-resolution (e.g. "2026-W09"), "YYYY-MM" for month-resolution, ` +
|
|
169
|
+
`or "someday" for no specific time.\n` +
|
|
170
|
+
`- For 'resolution': use "exact", "day", "week", "month", or "someday".\n` +
|
|
171
|
+
`- Only include items with at least a clear summary. Context is optional.\n` +
|
|
172
|
+
`- If there is nothing new to extract, output: {"project":[],"archive":[]}\n` +
|
|
173
|
+
`- Output valid JSON only. No commentary before or after.\n\n` +
|
|
174
|
+
`Already stored as pending projections:\n${pendingProjections}`;
|
|
175
|
+
const userPrompt = `Here is the recent conversation:\n\n${transcript}\n\n` +
|
|
176
|
+
`What future events, plans, or commitments are mentioned that are NOT already stored?\n` +
|
|
177
|
+
`Output JSON only:`;
|
|
178
|
+
return [
|
|
179
|
+
{ role: "system", content: systemPrompt },
|
|
180
|
+
{ role: "user", content: userPrompt },
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// JSON parsing
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
/**
|
|
187
|
+
* Parse the LLM output, tolerating markdown code fences and minor formatting.
|
|
188
|
+
*
|
|
189
|
+
* Even with `temperature: 0` and an explicit "output JSON only" instruction,
|
|
190
|
+
* models occasionally wrap their response in a ```json ... ``` code fence.
|
|
191
|
+
* The stripping step removes those fences before calling JSON.parse(), so
|
|
192
|
+
* both bare JSON and fenced JSON are accepted.
|
|
193
|
+
*/
|
|
194
|
+
export function parseReflectionOutput(raw) {
|
|
195
|
+
const stripped = raw
|
|
196
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
197
|
+
.replace(/\s*```\s*$/, "")
|
|
198
|
+
.trim();
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(stripped);
|
|
201
|
+
return {
|
|
202
|
+
project: Array.isArray(parsed.project) ? parsed.project : [],
|
|
203
|
+
archive: Array.isArray(parsed.archive) ? parsed.archive : [],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// If the whole thing doesn't parse, return empty
|
|
208
|
+
return { project: [], archive: [] };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Main reflection function
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Run one reflection pass for a user. Reads recent conversation, extracts
|
|
216
|
+
* future references via LLM, and writes new projections to the store.
|
|
217
|
+
*/
|
|
218
|
+
export async function runReflection(config, userId, windowMinutes = 30, store, completeFn) {
|
|
219
|
+
// Step 1: Read history from the JSONL audit log.
|
|
220
|
+
const historyDir = path.join(config.data_dir, "history");
|
|
221
|
+
const turns = readRecentHistory(historyDir, windowMinutes);
|
|
222
|
+
// Open store (or use injected one for tests)
|
|
223
|
+
const ownStore = !store;
|
|
224
|
+
const projStore = store ?? createProjectionStore(userId, config.data_dir);
|
|
225
|
+
// Access the underlying DB for metadata tracking via the store's DB path
|
|
226
|
+
const dbPath = path.join(config.data_dir, "users", userId, "memory.db");
|
|
227
|
+
let metaDb = null;
|
|
228
|
+
try {
|
|
229
|
+
// Step 2: Check for new messages. Skip the LLM call entirely if there is
|
|
230
|
+
// nothing to process: no turns in the window, or no turns newer than the
|
|
231
|
+
// last reflection timestamp.
|
|
232
|
+
if (turns.length === 0) {
|
|
233
|
+
return { projectionsAdded: 0, candidates: [], skipped: true, skipReason: "no recent messages" };
|
|
234
|
+
}
|
|
235
|
+
metaDb = new Database(dbPath);
|
|
236
|
+
const lastReflection = getLastReflectionTimestamp(metaDb);
|
|
237
|
+
if (lastReflection) {
|
|
238
|
+
const lastTs = new Date(lastReflection);
|
|
239
|
+
const newestTurn = turns[turns.length - 1];
|
|
240
|
+
if (new Date(newestTurn.timestamp) <= lastTs) {
|
|
241
|
+
return { projectionsAdded: 0, candidates: [], skipped: true, skipReason: "no new messages since last reflection" };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Step 3: Load existing pending projections so the model can skip them.
|
|
245
|
+
// A 90-day window is used here (wider than the history window) to give the
|
|
246
|
+
// deduplication step the best chance of catching near-duplicates.
|
|
247
|
+
const upcoming = projStore.getUpcoming(90);
|
|
248
|
+
const pendingText = formatProjectionsForPrompt(upcoming, 30);
|
|
249
|
+
const tz = getUserTimezone(config);
|
|
250
|
+
const now = new Date();
|
|
251
|
+
const currentDatetime = now
|
|
252
|
+
.toLocaleString("sv-SE", { timeZone: tz, hour12: false })
|
|
253
|
+
.slice(0, 16)
|
|
254
|
+
.replace("T", " ") + (tz !== "UTC" ? ` (${tz})` : " UTC");
|
|
255
|
+
const messages = buildReflectionPrompt(turns, pendingText, currentDatetime);
|
|
256
|
+
// Step 4: Call the LLM. One prompt in, one JSON blob out — no tool calls.
|
|
257
|
+
const doComplete = completeFn ?? sdkComplete;
|
|
258
|
+
let raw;
|
|
259
|
+
try {
|
|
260
|
+
raw = await doComplete(config, messages);
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
console.error("[reflection] LLM call failed:", err.message);
|
|
264
|
+
return { projectionsAdded: 0, candidates: [], skipped: true, skipReason: `LLM error: ${err.message}` };
|
|
265
|
+
}
|
|
266
|
+
// Step 5: Parse JSON from the raw LLM output.
|
|
267
|
+
const output = parseReflectionOutput(raw);
|
|
268
|
+
// Step 6: Deduplicate against existing projections and write survivors.
|
|
269
|
+
// Deduplication is prompt-based: the existing pending projections were
|
|
270
|
+
// included in the system prompt under "Already stored", and the model is
|
|
271
|
+
// instructed not to re-extract them. This is approximate — the model may
|
|
272
|
+
// still emit a candidate whose summary is a paraphrase of an existing one.
|
|
273
|
+
// A secondary code-level check (substring match on summaries) would reduce
|
|
274
|
+
// false duplicates but is not currently implemented.
|
|
275
|
+
const tz2 = getUserTimezone(config);
|
|
276
|
+
let projectionsAdded = 0;
|
|
277
|
+
for (const candidate of output.project) {
|
|
278
|
+
if (!candidate.summary?.trim())
|
|
279
|
+
continue;
|
|
280
|
+
try {
|
|
281
|
+
let resolved_when;
|
|
282
|
+
let raw_when;
|
|
283
|
+
let resolution = candidate.resolution ?? "day";
|
|
284
|
+
if (candidate.when && candidate.when !== "someday") {
|
|
285
|
+
const isoPattern = /^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2})?/;
|
|
286
|
+
const weekPattern = /^\d{4}-W\d{2}$/;
|
|
287
|
+
const monthPattern = /^\d{4}-\d{2}$/;
|
|
288
|
+
if (weekPattern.test(candidate.when)) {
|
|
289
|
+
raw_when = candidate.when;
|
|
290
|
+
resolution = "week";
|
|
291
|
+
}
|
|
292
|
+
else if (monthPattern.test(candidate.when)) {
|
|
293
|
+
raw_when = candidate.when;
|
|
294
|
+
resolution = "month";
|
|
295
|
+
}
|
|
296
|
+
else if (isoPattern.test(candidate.when)) {
|
|
297
|
+
const hasTime = candidate.when.includes("T") || (candidate.when.length > 10 && candidate.when[10] === " ");
|
|
298
|
+
resolved_when = hasTime ? toUtc(candidate.when, tz2) : candidate.when;
|
|
299
|
+
resolution = hasTime ? "exact" : (candidate.resolution ?? "day");
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
raw_when = candidate.when;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else if (candidate.when === "someday") {
|
|
306
|
+
resolution = "someday";
|
|
307
|
+
}
|
|
308
|
+
projStore.add({
|
|
309
|
+
summary: candidate.summary.trim(),
|
|
310
|
+
raw_when,
|
|
311
|
+
resolved_when,
|
|
312
|
+
resolution,
|
|
313
|
+
context: candidate.context,
|
|
314
|
+
});
|
|
315
|
+
projectionsAdded++;
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
console.warn("[reflection] Failed to store projection:", err.message, candidate);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Update last-reflection timestamp
|
|
322
|
+
const reflectedAt = new Date().toISOString();
|
|
323
|
+
if (metaDb) {
|
|
324
|
+
setLastReflectionTimestamp(metaDb, reflectedAt);
|
|
325
|
+
}
|
|
326
|
+
return { projectionsAdded, candidates: output.project, skipped: false };
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
metaDb?.close();
|
|
330
|
+
if (ownStore)
|
|
331
|
+
projStore.close();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=reflection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflection.js","sourceRoot":"","sources":["../../src/projection/reflection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection store: SQLite-backed storage for forward-looking agent memory.
|
|
3
|
+
*
|
|
4
|
+
* Stores future events, plans, and commitments with a resolution
|
|
5
|
+
* (exact/day/week/month/someday) and lifecycle (pending -> done/cancelled/passed).
|
|
6
|
+
*
|
|
7
|
+
* Lives in the same per-user memory.db as archival memory, its own table.
|
|
8
|
+
*
|
|
9
|
+
* Key columns: summary, raw_when (what the user said), resolved_when (ISO
|
|
10
|
+
* datetime), resolution, recurrence (cron), trigger_on_fact (keyword trigger),
|
|
11
|
+
* status, and dependencies (separate table for DAG relationships).
|
|
12
|
+
*/
|
|
13
|
+
export type ProjectionResolution = "exact" | "day" | "week" | "month" | "someday";
|
|
14
|
+
export type ProjectionStatus = "pending" | "done" | "cancelled" | "passed";
|
|
15
|
+
export type DependencyConditionType = "status_change" | "llm";
|
|
16
|
+
export interface ProjectionDependency {
|
|
17
|
+
id: string;
|
|
18
|
+
observer_id: string;
|
|
19
|
+
subject_id: string;
|
|
20
|
+
condition: string;
|
|
21
|
+
condition_type: DependencyConditionType;
|
|
22
|
+
created_at: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ProjectionDependencyInput {
|
|
25
|
+
subject_id: string;
|
|
26
|
+
condition: string;
|
|
27
|
+
condition_type?: DependencyConditionType;
|
|
28
|
+
}
|
|
29
|
+
export interface Projection {
|
|
30
|
+
id: string;
|
|
31
|
+
summary: string;
|
|
32
|
+
raw_when: string | null;
|
|
33
|
+
resolved_when: string | null;
|
|
34
|
+
resolution: ProjectionResolution;
|
|
35
|
+
recurrence: string | null;
|
|
36
|
+
trigger_on_fact: string | null;
|
|
37
|
+
context: string | null;
|
|
38
|
+
linked_ids: string[];
|
|
39
|
+
status: ProjectionStatus;
|
|
40
|
+
created_at: string;
|
|
41
|
+
resolved_at: string | null;
|
|
42
|
+
}
|
|
43
|
+
export interface ProjectionStore {
|
|
44
|
+
/** Add a projection. Returns the new id. */
|
|
45
|
+
add(params: {
|
|
46
|
+
summary: string;
|
|
47
|
+
raw_when?: string;
|
|
48
|
+
resolved_when?: string;
|
|
49
|
+
resolution?: ProjectionResolution;
|
|
50
|
+
recurrence?: string;
|
|
51
|
+
trigger_on_fact?: string;
|
|
52
|
+
context?: string;
|
|
53
|
+
linked_ids?: string[];
|
|
54
|
+
depends_on?: ProjectionDependencyInput[];
|
|
55
|
+
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* Get active (pending) projections within the next horizon_days days.
|
|
58
|
+
* Always includes someday projections.
|
|
59
|
+
*/
|
|
60
|
+
getUpcoming(horizon_days: number): Projection[];
|
|
61
|
+
/**
|
|
62
|
+
* Get pending projections due in the next window_minutes minutes that have
|
|
63
|
+
* resolution='exact'. Used by the 5-minute scheduler check. window_minutes
|
|
64
|
+
* must be larger than the scheduler tick to avoid missing events that fall
|
|
65
|
+
* between ticks.
|
|
66
|
+
*/
|
|
67
|
+
getExactDue(window_minutes: number): Projection[];
|
|
68
|
+
/**
|
|
69
|
+
* Mark a projection's status (done/cancelled/passed).
|
|
70
|
+
* Returns false if the id does not exist.
|
|
71
|
+
*/
|
|
72
|
+
resolve(id: string, status: ProjectionStatus): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Rearm a recurring projection: reset it to pending with a new resolved_when
|
|
75
|
+
* and clear resolved_at so it looks fresh for the next cycle. Only called
|
|
76
|
+
* after a recurring projection fires; non-recurring projections should be
|
|
77
|
+
* resolved instead.
|
|
78
|
+
* Returns false if the id does not exist.
|
|
79
|
+
*/
|
|
80
|
+
rearm(id: string, nextResolvedWhen: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Check pending projections whose trigger_on_fact condition matches the given
|
|
83
|
+
* fact content. First tries keyword matching (all keywords present). For
|
|
84
|
+
* non-matches, falls back to embedding cosine similarity if an embed function
|
|
85
|
+
* is provided. Activate each match by setting resolved_when to now and
|
|
86
|
+
* resolution to 'exact', then clear its trigger_on_fact.
|
|
87
|
+
* Returns the list of projections that were activated.
|
|
88
|
+
*/
|
|
89
|
+
checkTriggers(factContent: string, embed?: (text: string) => Promise<number[]>, similarityThreshold?: number): Promise<Projection[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Mark pending projections as 'passed' when their resolved_when is more than
|
|
92
|
+
* threshold_hours hours in the past and they are still pending. Exact-resolution
|
|
93
|
+
* projections always expire after 1 hour; other resolutions use threshold_hours.
|
|
94
|
+
* Someday projections are never expired by time.
|
|
95
|
+
* Returns the number of rows updated.
|
|
96
|
+
*/
|
|
97
|
+
autoExpire(threshold_hours?: number): number;
|
|
98
|
+
/**
|
|
99
|
+
* Link an existing observer projection to a subject projection.
|
|
100
|
+
* Throws if the relationship is invalid (missing projection, cycle, or chain too deep).
|
|
101
|
+
*/
|
|
102
|
+
linkDependency(observerId: string, subjectId: string, condition: string, conditionType?: DependencyConditionType): string;
|
|
103
|
+
/**
|
|
104
|
+
* Evaluate pending dependencies and activate observer projections whose
|
|
105
|
+
* conditions are satisfied.
|
|
106
|
+
*/
|
|
107
|
+
evaluateDependencies(): number;
|
|
108
|
+
/** List dependencies, optionally filtered by observer projection id. */
|
|
109
|
+
getDependencies(observerId?: string): ProjectionDependency[];
|
|
110
|
+
/** Close the database connection. */
|
|
111
|
+
close(): void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Open (or create) the per-user projection store. Shares memory.db with
|
|
115
|
+
* archival memory.
|
|
116
|
+
*
|
|
117
|
+
* Schema overview
|
|
118
|
+
* ---------------
|
|
119
|
+
* projections
|
|
120
|
+
* Primary record. Holds the summary, timing fields (raw_when, resolved_when,
|
|
121
|
+
* resolution), recurrence cron, trigger_on_fact keyword, and status.
|
|
122
|
+
*
|
|
123
|
+
* projection_links
|
|
124
|
+
* Many-to-many cross-reference between projections (e.g., "this task belongs
|
|
125
|
+
* to that project"). Links are stored as a JSON array on the projections row
|
|
126
|
+
* (linked_ids) and optionally in this table for reverse lookups.
|
|
127
|
+
*
|
|
128
|
+
* projection_dependencies
|
|
129
|
+
* DAG edges. An observer projection waits for its subject projection(s) to
|
|
130
|
+
* reach a condition before it becomes active. Conditions are either a status
|
|
131
|
+
* keyword ("done", "cancelled", "passed") or an LLM-evaluated expression.
|
|
132
|
+
*
|
|
133
|
+
* Lifecycle state machine
|
|
134
|
+
* -----------------------
|
|
135
|
+
* pending → done (agent or user explicitly resolves it)
|
|
136
|
+
* pending → cancelled (agent or user cancels it)
|
|
137
|
+
* pending → passed (autoExpire: the time window elapsed without action)
|
|
138
|
+
*
|
|
139
|
+
* Recurring projections return to pending after each firing (see rearm()).
|
|
140
|
+
* Trigger-based projections become active (resolution='exact') when a matching
|
|
141
|
+
* fact arrives via checkTriggers(), then fire on the next scheduler tick.
|
|
142
|
+
*/
|
|
143
|
+
export declare function createProjectionStore(userId: string, dataDir: string): ProjectionStore;
|
|
144
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/projection/store.ts"],"names":[],"mappings":"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"}
|