@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.
Files changed (228) hide show
  1. package/Dockerfile +27 -0
  2. package/README.md +77 -50
  3. package/config.example.yml +265 -0
  4. package/dist/active-hours.d.ts +23 -0
  5. package/dist/active-hours.d.ts.map +1 -0
  6. package/dist/active-hours.js +68 -0
  7. package/dist/active-hours.js.map +1 -0
  8. package/dist/agent.d.ts +84 -0
  9. package/dist/agent.d.ts.map +1 -0
  10. package/dist/agent.js +383 -0
  11. package/dist/agent.js.map +1 -0
  12. package/dist/channels/markdown/ir.d.ts +79 -0
  13. package/dist/channels/markdown/ir.d.ts.map +1 -0
  14. package/dist/channels/markdown/ir.js +824 -0
  15. package/dist/channels/markdown/ir.js.map +1 -0
  16. package/dist/channels/markdown/render.d.ts +35 -0
  17. package/dist/channels/markdown/render.d.ts.map +1 -0
  18. package/dist/channels/markdown/render.js +178 -0
  19. package/dist/channels/markdown/render.js.map +1 -0
  20. package/dist/channels/telegram-network-errors.d.ts +27 -0
  21. package/dist/channels/telegram-network-errors.d.ts.map +1 -0
  22. package/dist/channels/telegram-network-errors.js +156 -0
  23. package/dist/channels/telegram-network-errors.js.map +1 -0
  24. package/dist/channels/telegram.d.ts +76 -0
  25. package/dist/channels/telegram.d.ts.map +1 -0
  26. package/dist/channels/telegram.js +814 -0
  27. package/dist/channels/telegram.js.map +1 -0
  28. package/dist/channels/types.d.ts +59 -0
  29. package/dist/channels/types.d.ts.map +1 -0
  30. package/dist/channels/types.js +9 -0
  31. package/dist/channels/types.js.map +1 -0
  32. package/dist/channels/whatsapp.d.ts +45 -0
  33. package/dist/channels/whatsapp.d.ts.map +1 -0
  34. package/dist/channels/whatsapp.js +310 -0
  35. package/dist/channels/whatsapp.js.map +1 -0
  36. package/dist/cli.d.ts +13 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +635 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/commands.d.ts +35 -0
  41. package/dist/commands.d.ts.map +1 -0
  42. package/dist/commands.js +113 -0
  43. package/dist/commands.js.map +1 -0
  44. package/dist/compaction/history.d.ts +17 -0
  45. package/dist/compaction/history.d.ts.map +1 -0
  46. package/dist/compaction/history.js +35 -0
  47. package/dist/compaction/history.js.map +1 -0
  48. package/dist/compaction/index.d.ts +3 -0
  49. package/dist/compaction/index.d.ts.map +1 -0
  50. package/dist/compaction/index.js +3 -0
  51. package/dist/compaction/index.js.map +1 -0
  52. package/dist/compaction/proactive.d.ts +25 -0
  53. package/dist/compaction/proactive.d.ts.map +1 -0
  54. package/dist/compaction/proactive.js +87 -0
  55. package/dist/compaction/proactive.js.map +1 -0
  56. package/dist/compaction/transcript-repair.d.ts +55 -0
  57. package/dist/compaction/transcript-repair.d.ts.map +1 -0
  58. package/dist/compaction/transcript-repair.js +215 -0
  59. package/dist/compaction/transcript-repair.js.map +1 -0
  60. package/dist/config.d.ts +128 -0
  61. package/dist/config.d.ts.map +1 -0
  62. package/dist/config.js +317 -0
  63. package/dist/config.js.map +1 -0
  64. package/dist/crash-recovery.d.ts +23 -0
  65. package/dist/crash-recovery.d.ts.map +1 -0
  66. package/dist/crash-recovery.js +96 -0
  67. package/dist/crash-recovery.js.map +1 -0
  68. package/dist/defaults/extensions/EXTENSIONS.md +158 -0
  69. package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
  70. package/dist/history.d.ts +31 -0
  71. package/dist/history.d.ts.map +1 -0
  72. package/dist/history.js +49 -0
  73. package/dist/history.js.map +1 -0
  74. package/dist/index.d.ts +19 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +673 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/logger.d.ts +39 -0
  79. package/dist/logger.d.ts.map +1 -0
  80. package/dist/logger.js +143 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/memory/conversation-search.d.ts +15 -0
  83. package/dist/memory/conversation-search.d.ts.map +1 -0
  84. package/dist/memory/conversation-search.js +60 -0
  85. package/dist/memory/conversation-search.js.map +1 -0
  86. package/dist/memory/core-memory.d.ts +28 -0
  87. package/dist/memory/core-memory.d.ts.map +1 -0
  88. package/dist/memory/core-memory.js +102 -0
  89. package/dist/memory/core-memory.js.map +1 -0
  90. package/dist/memory/embeddings.d.ts +44 -0
  91. package/dist/memory/embeddings.d.ts.map +1 -0
  92. package/dist/memory/embeddings.js +139 -0
  93. package/dist/memory/embeddings.js.map +1 -0
  94. package/dist/memory/search.d.ts +49 -0
  95. package/dist/memory/search.d.ts.map +1 -0
  96. package/dist/memory/search.js +97 -0
  97. package/dist/memory/search.js.map +1 -0
  98. package/dist/memory/store.d.ts +32 -0
  99. package/dist/memory/store.d.ts.map +1 -0
  100. package/dist/memory/store.js +205 -0
  101. package/dist/memory/store.js.map +1 -0
  102. package/dist/message-queue.d.ts +73 -0
  103. package/dist/message-queue.d.ts.map +1 -0
  104. package/dist/message-queue.js +188 -0
  105. package/dist/message-queue.js.map +1 -0
  106. package/dist/model-infra.d.ts +64 -0
  107. package/dist/model-infra.d.ts.map +1 -0
  108. package/dist/model-infra.js +202 -0
  109. package/dist/model-infra.js.map +1 -0
  110. package/dist/projection/format.d.ts +10 -0
  111. package/dist/projection/format.d.ts.map +1 -0
  112. package/dist/projection/format.js +30 -0
  113. package/dist/projection/format.js.map +1 -0
  114. package/dist/projection/index.d.ts +11 -0
  115. package/dist/projection/index.d.ts.map +1 -0
  116. package/dist/projection/index.js +9 -0
  117. package/dist/projection/index.js.map +1 -0
  118. package/dist/projection/reflection.d.ts +94 -0
  119. package/dist/projection/reflection.d.ts.map +1 -0
  120. package/dist/projection/reflection.js +334 -0
  121. package/dist/projection/reflection.js.map +1 -0
  122. package/dist/projection/store.d.ts +144 -0
  123. package/dist/projection/store.d.ts.map +1 -0
  124. package/dist/projection/store.js +519 -0
  125. package/dist/projection/store.js.map +1 -0
  126. package/dist/projection/tools.d.ts +11 -0
  127. package/dist/projection/tools.d.ts.map +1 -0
  128. package/dist/projection/tools.js +237 -0
  129. package/dist/projection/tools.js.map +1 -0
  130. package/dist/scheduler.d.ts +36 -0
  131. package/dist/scheduler.d.ts.map +1 -0
  132. package/dist/scheduler.js +286 -0
  133. package/dist/scheduler.js.map +1 -0
  134. package/dist/system-prompt.d.ts +41 -0
  135. package/dist/system-prompt.d.ts.map +1 -0
  136. package/dist/system-prompt.js +162 -0
  137. package/dist/system-prompt.js.map +1 -0
  138. package/dist/time.d.ts +52 -0
  139. package/dist/time.d.ts.map +1 -0
  140. package/dist/time.js +138 -0
  141. package/dist/time.js.map +1 -0
  142. package/dist/tools/archival-memory-tool.d.ts +8 -0
  143. package/dist/tools/archival-memory-tool.d.ts.map +1 -0
  144. package/dist/tools/archival-memory-tool.js +68 -0
  145. package/dist/tools/archival-memory-tool.js.map +1 -0
  146. package/dist/tools/conversation-search-tool.d.ts +6 -0
  147. package/dist/tools/conversation-search-tool.d.ts.map +1 -0
  148. package/dist/tools/conversation-search-tool.js +28 -0
  149. package/dist/tools/conversation-search-tool.js.map +1 -0
  150. package/dist/tools/core-memory-tool.d.ts +7 -0
  151. package/dist/tools/core-memory-tool.d.ts.map +1 -0
  152. package/dist/tools/core-memory-tool.js +59 -0
  153. package/dist/tools/core-memory-tool.js.map +1 -0
  154. package/dist/tools/fetch-url.d.ts +15 -0
  155. package/dist/tools/fetch-url.d.ts.map +1 -0
  156. package/dist/tools/fetch-url.js +76 -0
  157. package/dist/tools/fetch-url.js.map +1 -0
  158. package/dist/tools/files.d.ts +10 -0
  159. package/dist/tools/files.d.ts.map +1 -0
  160. package/dist/tools/files.js +127 -0
  161. package/dist/tools/files.js.map +1 -0
  162. package/dist/tools/index.d.ts +17 -0
  163. package/dist/tools/index.d.ts.map +1 -0
  164. package/dist/tools/index.js +118 -0
  165. package/dist/tools/index.js.map +1 -0
  166. package/dist/tools/result.d.ts +21 -0
  167. package/dist/tools/result.d.ts.map +1 -0
  168. package/dist/tools/result.js +36 -0
  169. package/dist/tools/result.js.map +1 -0
  170. package/dist/tools/skill-install.d.ts +17 -0
  171. package/dist/tools/skill-install.d.ts.map +1 -0
  172. package/dist/tools/skill-install.js +148 -0
  173. package/dist/tools/skill-install.js.map +1 -0
  174. package/dist/tools/web-search.d.ts +42 -0
  175. package/dist/tools/web-search.d.ts.map +1 -0
  176. package/dist/tools/web-search.js +237 -0
  177. package/dist/tools/web-search.js.map +1 -0
  178. package/dist/trust/guardrail.d.ts +60 -0
  179. package/dist/trust/guardrail.d.ts.map +1 -0
  180. package/dist/trust/guardrail.js +171 -0
  181. package/dist/trust/guardrail.js.map +1 -0
  182. package/dist/trust/index.d.ts +12 -0
  183. package/dist/trust/index.d.ts.map +1 -0
  184. package/dist/trust/index.js +12 -0
  185. package/dist/trust/index.js.map +1 -0
  186. package/dist/trust/store.d.ts +118 -0
  187. package/dist/trust/store.d.ts.map +1 -0
  188. package/dist/trust/store.js +209 -0
  189. package/dist/trust/store.js.map +1 -0
  190. package/dist/trust/wrapper.d.ts +36 -0
  191. package/dist/trust/wrapper.d.ts.map +1 -0
  192. package/dist/trust/wrapper.js +142 -0
  193. package/dist/trust/wrapper.js.map +1 -0
  194. package/dist/usage.d.ts +53 -0
  195. package/dist/usage.d.ts.map +1 -0
  196. package/dist/usage.js +124 -0
  197. package/dist/usage.js.map +1 -0
  198. package/dist/util/math.d.ts +9 -0
  199. package/dist/util/math.d.ts.map +1 -0
  200. package/dist/util/math.js +22 -0
  201. package/dist/util/math.js.map +1 -0
  202. package/dist/util/ssrf.d.ts +21 -0
  203. package/dist/util/ssrf.d.ts.map +1 -0
  204. package/dist/util/ssrf.js +77 -0
  205. package/dist/util/ssrf.js.map +1 -0
  206. package/dist/workers/index.d.ts +8 -0
  207. package/dist/workers/index.d.ts.map +1 -0
  208. package/dist/workers/index.js +7 -0
  209. package/dist/workers/index.js.map +1 -0
  210. package/dist/workers/registry.d.ts +53 -0
  211. package/dist/workers/registry.d.ts.map +1 -0
  212. package/dist/workers/registry.js +38 -0
  213. package/dist/workers/registry.js.map +1 -0
  214. package/dist/workers/scoped-tools.d.ts +21 -0
  215. package/dist/workers/scoped-tools.d.ts.map +1 -0
  216. package/dist/workers/scoped-tools.js +111 -0
  217. package/dist/workers/scoped-tools.js.map +1 -0
  218. package/dist/workers/spawn.d.ts +62 -0
  219. package/dist/workers/spawn.d.ts.map +1 -0
  220. package/dist/workers/spawn.js +314 -0
  221. package/dist/workers/spawn.js.map +1 -0
  222. package/dist/workers/tools.d.ts +26 -0
  223. package/dist/workers/tools.d.ts.map +1 -0
  224. package/dist/workers/tools.js +380 -0
  225. package/dist/workers/tools.js.map +1 -0
  226. package/docker-compose.yml +72 -0
  227. package/package.json +16 -1
  228. 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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"}