@bubblebrain-ai/bubble 0.0.1

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 (248) hide show
  1. package/README.md +70 -0
  2. package/dist/agent/evidence-tracker.d.ts +15 -0
  3. package/dist/agent/evidence-tracker.js +93 -0
  4. package/dist/agent/execution-governor.d.ts +30 -0
  5. package/dist/agent/execution-governor.js +169 -0
  6. package/dist/agent/subtask-policy.d.ts +14 -0
  7. package/dist/agent/subtask-policy.js +60 -0
  8. package/dist/agent/task-classifier.d.ts +3 -0
  9. package/dist/agent/task-classifier.js +36 -0
  10. package/dist/agent/tool-arbiter.d.ts +7 -0
  11. package/dist/agent/tool-arbiter.js +33 -0
  12. package/dist/agent/tool-intent.d.ts +20 -0
  13. package/dist/agent/tool-intent.js +176 -0
  14. package/dist/agent.d.ts +95 -0
  15. package/dist/agent.js +672 -0
  16. package/dist/approval/controller.d.ts +48 -0
  17. package/dist/approval/controller.js +78 -0
  18. package/dist/approval/danger.d.ts +13 -0
  19. package/dist/approval/danger.js +55 -0
  20. package/dist/approval/diff-hunks.d.ts +12 -0
  21. package/dist/approval/diff-hunks.js +32 -0
  22. package/dist/approval/session-cache.d.ts +35 -0
  23. package/dist/approval/session-cache.js +68 -0
  24. package/dist/approval/tool-helper.d.ts +14 -0
  25. package/dist/approval/tool-helper.js +32 -0
  26. package/dist/approval/types.d.ts +56 -0
  27. package/dist/approval/types.js +8 -0
  28. package/dist/bubble-home.d.ts +8 -0
  29. package/dist/bubble-home.js +19 -0
  30. package/dist/cli.d.ts +19 -0
  31. package/dist/cli.js +82 -0
  32. package/dist/config.d.ts +41 -0
  33. package/dist/config.js +144 -0
  34. package/dist/context/budget.d.ts +21 -0
  35. package/dist/context/budget.js +72 -0
  36. package/dist/context/compact-llm.d.ts +16 -0
  37. package/dist/context/compact-llm.js +132 -0
  38. package/dist/context/compact.d.ts +15 -0
  39. package/dist/context/compact.js +251 -0
  40. package/dist/context/overflow.d.ts +9 -0
  41. package/dist/context/overflow.js +46 -0
  42. package/dist/context/projector.d.ts +26 -0
  43. package/dist/context/projector.js +150 -0
  44. package/dist/context/prune.d.ts +9 -0
  45. package/dist/context/prune.js +111 -0
  46. package/dist/lsp/config.d.ts +18 -0
  47. package/dist/lsp/config.js +58 -0
  48. package/dist/lsp/diagnostics.d.ts +24 -0
  49. package/dist/lsp/diagnostics.js +103 -0
  50. package/dist/lsp/index.d.ts +3 -0
  51. package/dist/lsp/index.js +3 -0
  52. package/dist/lsp/service.d.ts +85 -0
  53. package/dist/lsp/service.js +695 -0
  54. package/dist/main.d.ts +5 -0
  55. package/dist/main.js +352 -0
  56. package/dist/mcp/client.d.ts +68 -0
  57. package/dist/mcp/client.js +163 -0
  58. package/dist/mcp/config.d.ts +26 -0
  59. package/dist/mcp/config.js +127 -0
  60. package/dist/mcp/manager.d.ts +55 -0
  61. package/dist/mcp/manager.js +296 -0
  62. package/dist/mcp/name.d.ts +26 -0
  63. package/dist/mcp/name.js +40 -0
  64. package/dist/mcp/transports.d.ts +53 -0
  65. package/dist/mcp/transports.js +248 -0
  66. package/dist/mcp/types.d.ts +111 -0
  67. package/dist/mcp/types.js +14 -0
  68. package/dist/memory/db.d.ts +62 -0
  69. package/dist/memory/db.js +313 -0
  70. package/dist/memory/index.d.ts +9 -0
  71. package/dist/memory/index.js +9 -0
  72. package/dist/memory/paths.d.ts +18 -0
  73. package/dist/memory/paths.js +38 -0
  74. package/dist/memory/phase1.d.ts +23 -0
  75. package/dist/memory/phase1.js +172 -0
  76. package/dist/memory/phase2.d.ts +19 -0
  77. package/dist/memory/phase2.js +100 -0
  78. package/dist/memory/prompts.d.ts +19 -0
  79. package/dist/memory/prompts.js +99 -0
  80. package/dist/memory/reset.d.ts +1 -0
  81. package/dist/memory/reset.js +13 -0
  82. package/dist/memory/start.d.ts +24 -0
  83. package/dist/memory/start.js +50 -0
  84. package/dist/memory/storage.d.ts +10 -0
  85. package/dist/memory/storage.js +82 -0
  86. package/dist/memory/store.d.ts +43 -0
  87. package/dist/memory/store.js +193 -0
  88. package/dist/memory/usage.d.ts +1 -0
  89. package/dist/memory/usage.js +38 -0
  90. package/dist/model-catalog.d.ts +20 -0
  91. package/dist/model-catalog.js +99 -0
  92. package/dist/model-config.d.ts +32 -0
  93. package/dist/model-config.js +59 -0
  94. package/dist/model-pricing.d.ts +23 -0
  95. package/dist/model-pricing.js +46 -0
  96. package/dist/oauth/index.d.ts +3 -0
  97. package/dist/oauth/index.js +2 -0
  98. package/dist/oauth/openai-codex.d.ts +9 -0
  99. package/dist/oauth/openai-codex.js +173 -0
  100. package/dist/oauth/storage.d.ts +18 -0
  101. package/dist/oauth/storage.js +60 -0
  102. package/dist/oauth/types.d.ts +15 -0
  103. package/dist/oauth/types.js +1 -0
  104. package/dist/orchestrator/default-hooks.d.ts +2 -0
  105. package/dist/orchestrator/default-hooks.js +96 -0
  106. package/dist/orchestrator/hooks.d.ts +78 -0
  107. package/dist/orchestrator/hooks.js +52 -0
  108. package/dist/orchestrator/workflow.d.ts +10 -0
  109. package/dist/orchestrator/workflow.js +22 -0
  110. package/dist/permission/mode.d.ts +23 -0
  111. package/dist/permission/mode.js +20 -0
  112. package/dist/permissions/rule.d.ts +39 -0
  113. package/dist/permissions/rule.js +234 -0
  114. package/dist/permissions/settings.d.ts +71 -0
  115. package/dist/permissions/settings.js +202 -0
  116. package/dist/permissions/types.d.ts +61 -0
  117. package/dist/permissions/types.js +14 -0
  118. package/dist/prompt/compose.d.ts +12 -0
  119. package/dist/prompt/compose.js +67 -0
  120. package/dist/prompt/environment.d.ts +12 -0
  121. package/dist/prompt/environment.js +38 -0
  122. package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
  123. package/dist/prompt/provider-prompts/anthropic.js +5 -0
  124. package/dist/prompt/provider-prompts/codex.d.ts +1 -0
  125. package/dist/prompt/provider-prompts/codex.js +5 -0
  126. package/dist/prompt/provider-prompts/default.d.ts +1 -0
  127. package/dist/prompt/provider-prompts/default.js +6 -0
  128. package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
  129. package/dist/prompt/provider-prompts/gemini.js +5 -0
  130. package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
  131. package/dist/prompt/provider-prompts/gpt.js +5 -0
  132. package/dist/prompt/reminders.d.ts +30 -0
  133. package/dist/prompt/reminders.js +164 -0
  134. package/dist/prompt/runtime.d.ts +12 -0
  135. package/dist/prompt/runtime.js +31 -0
  136. package/dist/prompt/skills.d.ts +2 -0
  137. package/dist/prompt/skills.js +4 -0
  138. package/dist/provider-openai-codex.d.ts +14 -0
  139. package/dist/provider-openai-codex.js +409 -0
  140. package/dist/provider-registry.d.ts +56 -0
  141. package/dist/provider-registry.js +244 -0
  142. package/dist/provider-transform.d.ts +10 -0
  143. package/dist/provider-transform.js +69 -0
  144. package/dist/provider.d.ts +31 -0
  145. package/dist/provider.js +269 -0
  146. package/dist/question/controller.d.ts +22 -0
  147. package/dist/question/controller.js +97 -0
  148. package/dist/question/index.d.ts +2 -0
  149. package/dist/question/index.js +2 -0
  150. package/dist/question/types.d.ts +42 -0
  151. package/dist/question/types.js +6 -0
  152. package/dist/session-log.d.ts +16 -0
  153. package/dist/session-log.js +267 -0
  154. package/dist/session-types.d.ts +55 -0
  155. package/dist/session-types.js +1 -0
  156. package/dist/session.d.ts +32 -0
  157. package/dist/session.js +135 -0
  158. package/dist/skills/discovery.d.ts +12 -0
  159. package/dist/skills/discovery.js +148 -0
  160. package/dist/skills/format.d.ts +2 -0
  161. package/dist/skills/format.js +47 -0
  162. package/dist/skills/frontmatter.d.ts +5 -0
  163. package/dist/skills/frontmatter.js +60 -0
  164. package/dist/skills/invocation.d.ts +8 -0
  165. package/dist/skills/invocation.js +51 -0
  166. package/dist/skills/registry.d.ts +17 -0
  167. package/dist/skills/registry.js +42 -0
  168. package/dist/skills/types.d.ts +32 -0
  169. package/dist/skills/types.js +1 -0
  170. package/dist/slash-commands/commands.d.ts +7 -0
  171. package/dist/slash-commands/commands.js +779 -0
  172. package/dist/slash-commands/index.d.ts +4 -0
  173. package/dist/slash-commands/index.js +8 -0
  174. package/dist/slash-commands/registry.d.ts +31 -0
  175. package/dist/slash-commands/registry.js +70 -0
  176. package/dist/slash-commands/types.d.ts +44 -0
  177. package/dist/slash-commands/types.js +1 -0
  178. package/dist/slash-commands/unified.d.ts +38 -0
  179. package/dist/slash-commands/unified.js +38 -0
  180. package/dist/system-prompt.d.ts +34 -0
  181. package/dist/system-prompt.js +7 -0
  182. package/dist/tools/bash.d.ts +6 -0
  183. package/dist/tools/bash.js +135 -0
  184. package/dist/tools/edit.d.ts +16 -0
  185. package/dist/tools/edit.js +95 -0
  186. package/dist/tools/exa-mcp.d.ts +3 -0
  187. package/dist/tools/exa-mcp.js +74 -0
  188. package/dist/tools/exit-plan-mode.d.ts +17 -0
  189. package/dist/tools/exit-plan-mode.js +68 -0
  190. package/dist/tools/glob.d.ts +5 -0
  191. package/dist/tools/glob.js +129 -0
  192. package/dist/tools/grep.d.ts +5 -0
  193. package/dist/tools/grep.js +111 -0
  194. package/dist/tools/index.d.ts +36 -0
  195. package/dist/tools/index.js +59 -0
  196. package/dist/tools/lsp.d.ts +4 -0
  197. package/dist/tools/lsp.js +92 -0
  198. package/dist/tools/memory.d.ts +3 -0
  199. package/dist/tools/memory.js +90 -0
  200. package/dist/tools/question.d.ts +3 -0
  201. package/dist/tools/question.js +174 -0
  202. package/dist/tools/read.d.ts +7 -0
  203. package/dist/tools/read.js +83 -0
  204. package/dist/tools/sensitive-paths.d.ts +3 -0
  205. package/dist/tools/sensitive-paths.js +24 -0
  206. package/dist/tools/skill.d.ts +5 -0
  207. package/dist/tools/skill.js +51 -0
  208. package/dist/tools/task.d.ts +2 -0
  209. package/dist/tools/task.js +57 -0
  210. package/dist/tools/todo.d.ts +12 -0
  211. package/dist/tools/todo.js +151 -0
  212. package/dist/tools/tool-search.d.ts +23 -0
  213. package/dist/tools/tool-search.js +124 -0
  214. package/dist/tools/web-fetch.d.ts +6 -0
  215. package/dist/tools/web-fetch.js +75 -0
  216. package/dist/tools/web-search.d.ts +5 -0
  217. package/dist/tools/web-search.js +49 -0
  218. package/dist/tools/write.d.ts +11 -0
  219. package/dist/tools/write.js +77 -0
  220. package/dist/tui/display-history.d.ts +35 -0
  221. package/dist/tui/display-history.js +243 -0
  222. package/dist/tui/file-mentions.d.ts +29 -0
  223. package/dist/tui/file-mentions.js +174 -0
  224. package/dist/tui/image-paste.d.ts +54 -0
  225. package/dist/tui/image-paste.js +288 -0
  226. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  227. package/dist/tui/markdown-theme-rules.js +164 -0
  228. package/dist/tui/markdown-theme.d.ts +5 -0
  229. package/dist/tui/markdown-theme.js +27 -0
  230. package/dist/tui/opencode-spinner.d.ts +21 -0
  231. package/dist/tui/opencode-spinner.js +216 -0
  232. package/dist/tui/prompt-keybindings.d.ts +41 -0
  233. package/dist/tui/prompt-keybindings.js +28 -0
  234. package/dist/tui/recent-activity.d.ts +8 -0
  235. package/dist/tui/recent-activity.js +71 -0
  236. package/dist/tui/run.d.ts +39 -0
  237. package/dist/tui/run.js +5696 -0
  238. package/dist/tui/sidebar-mcp.d.ts +31 -0
  239. package/dist/tui/sidebar-mcp.js +62 -0
  240. package/dist/tui/sidebar-state.d.ts +12 -0
  241. package/dist/tui/sidebar-state.js +69 -0
  242. package/dist/types.d.ts +219 -0
  243. package/dist/types.js +4 -0
  244. package/dist/variant/thinking-level.d.ts +5 -0
  245. package/dist/variant/thinking-level.js +25 -0
  246. package/dist/variant/variant-resolver.d.ts +4 -0
  247. package/dist/variant/variant-resolver.js +12 -0
  248. package/package.json +47 -0
@@ -0,0 +1,313 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname } from "node:path";
4
+ import { getMemoryPaths } from "./paths.js";
5
+ const SCHEMA_VERSION = 1;
6
+ const GLOBAL_CONSOLIDATION_KIND = "memory_consolidate_global";
7
+ const GLOBAL_CONSOLIDATION_KEY = "global";
8
+ const PHASE1_KIND = "memory_phase1_extract";
9
+ const require = createRequire(import.meta.url);
10
+ export class MemoryDatabase {
11
+ db;
12
+ constructor(cwd) {
13
+ const path = getMemoryPaths(cwd).globalDatabase;
14
+ mkdirSync(dirname(path), { recursive: true });
15
+ this.db = createDatabase(path);
16
+ setWalMode(this.db);
17
+ this.migrate();
18
+ }
19
+ close() {
20
+ this.db.close();
21
+ }
22
+ path(cwd) {
23
+ return getMemoryPaths(cwd).globalDatabase;
24
+ }
25
+ upsertStage1Output(output) {
26
+ this.db.prepare(`
27
+ INSERT INTO memory_stage1_outputs (
28
+ session_file, cwd, entry_count, source_updated_at, generated_at,
29
+ raw_memory, rollout_summary, rollout_slug, usage_count, selected_for_phase2
30
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0)
31
+ ON CONFLICT(session_file) DO UPDATE SET
32
+ cwd = excluded.cwd,
33
+ entry_count = excluded.entry_count,
34
+ source_updated_at = excluded.source_updated_at,
35
+ generated_at = excluded.generated_at,
36
+ raw_memory = excluded.raw_memory,
37
+ rollout_summary = excluded.rollout_summary,
38
+ rollout_slug = excluded.rollout_slug,
39
+ selected_for_phase2 = CASE
40
+ WHEN memory_stage1_outputs.selected_for_phase2_source_updated_at = excluded.source_updated_at
41
+ THEN memory_stage1_outputs.selected_for_phase2
42
+ ELSE 0
43
+ END
44
+ `).run(output.sessionFile, output.cwd, output.entryCount, output.sourceUpdatedAt, output.generatedAt, output.rawMemory, output.rolloutSummary, output.rolloutSlug ?? null);
45
+ }
46
+ getStage1Output(sessionFile) {
47
+ const row = this.db.prepare("SELECT * FROM memory_stage1_outputs WHERE session_file = ?").get(sessionFile);
48
+ return row ? mapStage1(row) : undefined;
49
+ }
50
+ claimPhase1Job(sessionFile, workerId, leaseSeconds) {
51
+ const now = unixNow();
52
+ const leaseUntil = now + Math.max(0, leaseSeconds);
53
+ const tx = this.db.transaction(() => {
54
+ const row = this.db.prepare("SELECT * FROM memory_jobs WHERE kind = ? AND job_key = ?")
55
+ .get(PHASE1_KIND, sessionFile);
56
+ if (!row) {
57
+ this.db.prepare(`
58
+ INSERT INTO memory_jobs (kind, job_key, status, worker_id, lease_until, started_at, input_watermark, last_success_watermark)
59
+ VALUES (?, ?, 'running', ?, ?, ?, 0, 0)
60
+ `).run(PHASE1_KIND, sessionFile, workerId, leaseUntil, now);
61
+ return { claimed: true };
62
+ }
63
+ const job = mapJob(row);
64
+ if (job.status === "running" && (job.leaseUntil ?? 0) > now) {
65
+ return { claimed: false, reason: "already running" };
66
+ }
67
+ if ((job.retryAt ?? 0) > now) {
68
+ return { claimed: false, reason: "retry backoff active" };
69
+ }
70
+ this.db.prepare(`
71
+ UPDATE memory_jobs
72
+ SET status = 'running', worker_id = ?, lease_until = ?, started_at = ?, finished_at = NULL, last_error = NULL
73
+ WHERE kind = ? AND job_key = ?
74
+ `).run(workerId, leaseUntil, now, PHASE1_KIND, sessionFile);
75
+ return { claimed: true };
76
+ });
77
+ return tx();
78
+ }
79
+ finishPhase1Job(sessionFile, ok, error) {
80
+ const now = unixNow();
81
+ this.db.prepare(`
82
+ UPDATE memory_jobs
83
+ SET status = ?,
84
+ finished_at = ?,
85
+ lease_until = NULL,
86
+ retry_at = ?,
87
+ last_error = ?
88
+ WHERE kind = ? AND job_key = ?
89
+ `).run(ok ? "succeeded" : "failed", now, ok ? null : now + 3600, error ?? null, PHASE1_KIND, sessionFile);
90
+ }
91
+ listStage1Outputs(limit = 40) {
92
+ return this.db.prepare(`
93
+ SELECT * FROM memory_stage1_outputs
94
+ ORDER BY usage_count DESC, COALESCE(last_usage, generated_at) DESC, source_updated_at DESC
95
+ LIMIT ?
96
+ `).all(limit).map((row) => mapStage1(row));
97
+ }
98
+ listPreviouslySelectedNotIn(sessionFiles) {
99
+ if (sessionFiles.length === 0) {
100
+ return this.db.prepare("SELECT * FROM memory_stage1_outputs WHERE selected_for_phase2 = 1")
101
+ .all()
102
+ .map((row) => mapStage1(row));
103
+ }
104
+ const placeholders = sessionFiles.map(() => "?").join(", ");
105
+ return this.db.prepare(`
106
+ SELECT * FROM memory_stage1_outputs
107
+ WHERE selected_for_phase2 = 1 AND session_file NOT IN (${placeholders})
108
+ ORDER BY COALESCE(last_usage, generated_at) DESC
109
+ `).all(...sessionFiles).map((row) => mapStage1(row));
110
+ }
111
+ markSelectedForPhase2(outputs) {
112
+ const tx = this.db.transaction((items) => {
113
+ this.db.prepare("UPDATE memory_stage1_outputs SET selected_for_phase2 = 0").run();
114
+ const stmt = this.db.prepare(`
115
+ UPDATE memory_stage1_outputs
116
+ SET selected_for_phase2 = 1,
117
+ selected_for_phase2_source_updated_at = source_updated_at
118
+ WHERE session_file = ?
119
+ `);
120
+ for (const item of items)
121
+ stmt.run(item.sessionFile);
122
+ });
123
+ tx(outputs);
124
+ }
125
+ recordUsage(sessionFiles, now = new Date()) {
126
+ const unique = [...new Set(sessionFiles)];
127
+ const stmt = this.db.prepare(`
128
+ UPDATE memory_stage1_outputs
129
+ SET usage_count = usage_count + 1,
130
+ last_usage = ?
131
+ WHERE session_file = ?
132
+ `);
133
+ const tx = this.db.transaction((items) => {
134
+ let count = 0;
135
+ for (const item of items) {
136
+ count += stmt.run(now.toISOString(), item).changes ?? 0;
137
+ }
138
+ return count;
139
+ });
140
+ return tx(unique);
141
+ }
142
+ setThreadMemoryMode(sessionFile, mode) {
143
+ this.db.prepare(`
144
+ INSERT INTO memory_thread_modes (session_file, mode, updated_at)
145
+ VALUES (?, ?, ?)
146
+ ON CONFLICT(session_file) DO UPDATE SET mode = excluded.mode, updated_at = excluded.updated_at
147
+ `).run(sessionFile, mode, new Date().toISOString());
148
+ }
149
+ getThreadMemoryMode(sessionFile) {
150
+ const row = this.db.prepare("SELECT mode FROM memory_thread_modes WHERE session_file = ?").get(sessionFile);
151
+ return row?.mode === "disabled" ? "disabled" : "enabled";
152
+ }
153
+ claimGlobalPhase2Job(workerId, leaseSeconds) {
154
+ const now = unixNow();
155
+ const leaseUntil = now + Math.max(0, leaseSeconds);
156
+ const tx = this.db.transaction(() => {
157
+ const row = this.db.prepare("SELECT * FROM memory_jobs WHERE kind = ? AND job_key = ?")
158
+ .get(GLOBAL_CONSOLIDATION_KIND, GLOBAL_CONSOLIDATION_KEY);
159
+ if (!row) {
160
+ this.db.prepare(`
161
+ INSERT INTO memory_jobs (kind, job_key, status, worker_id, lease_until, started_at, input_watermark, last_success_watermark)
162
+ VALUES (?, ?, 'running', ?, ?, ?, 0, 0)
163
+ `).run(GLOBAL_CONSOLIDATION_KIND, GLOBAL_CONSOLIDATION_KEY, workerId, leaseUntil, now);
164
+ return { claimed: true, job: this.getGlobalPhase2Job() };
165
+ }
166
+ const job = mapJob(row);
167
+ if (job.status === "running" && (job.leaseUntil ?? 0) > now) {
168
+ return { claimed: false, job, reason: "already running" };
169
+ }
170
+ if ((job.retryAt ?? 0) > now) {
171
+ return { claimed: false, job, reason: "retry backoff active" };
172
+ }
173
+ this.db.prepare(`
174
+ UPDATE memory_jobs
175
+ SET status = 'running', worker_id = ?, lease_until = ?, started_at = ?, finished_at = NULL, last_error = NULL
176
+ WHERE kind = ? AND job_key = ?
177
+ `).run(workerId, leaseUntil, now, GLOBAL_CONSOLIDATION_KIND, GLOBAL_CONSOLIDATION_KEY);
178
+ return { claimed: true, job: this.getGlobalPhase2Job() };
179
+ });
180
+ return tx();
181
+ }
182
+ finishGlobalPhase2Job(ok, watermark, error) {
183
+ const now = unixNow();
184
+ this.db.prepare(`
185
+ UPDATE memory_jobs
186
+ SET status = ?,
187
+ finished_at = ?,
188
+ lease_until = NULL,
189
+ retry_at = ?,
190
+ last_error = ?,
191
+ last_success_watermark = CASE WHEN ? THEN ? ELSE last_success_watermark END
192
+ WHERE kind = ? AND job_key = ?
193
+ `).run(ok ? "succeeded" : "failed", now, ok ? null : now + 3600, error ?? null, ok ? 1 : 0, watermark, GLOBAL_CONSOLIDATION_KIND, GLOBAL_CONSOLIDATION_KEY);
194
+ }
195
+ getGlobalPhase2Job() {
196
+ const row = this.db.prepare("SELECT * FROM memory_jobs WHERE kind = ? AND job_key = ?")
197
+ .get(GLOBAL_CONSOLIDATION_KIND, GLOBAL_CONSOLIDATION_KEY);
198
+ return row ? mapJob(row) : undefined;
199
+ }
200
+ resetStageData() {
201
+ const tx = this.db.transaction(() => {
202
+ this.db.prepare("DELETE FROM memory_stage1_outputs").run();
203
+ this.db.prepare("DELETE FROM memory_jobs").run();
204
+ });
205
+ tx();
206
+ }
207
+ stats() {
208
+ const stage1Outputs = this.db.prepare("SELECT COUNT(*) AS count FROM memory_stage1_outputs").get().count;
209
+ const disabledThreads = this.db.prepare("SELECT COUNT(*) AS count FROM memory_thread_modes WHERE mode = 'disabled'").get().count;
210
+ const jobs = this.db.prepare("SELECT * FROM memory_jobs ORDER BY kind, job_key").all().map((row) => mapJob(row));
211
+ return { stage1Outputs, disabledThreads, jobs };
212
+ }
213
+ migrate() {
214
+ this.db.prepare("CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)").run();
215
+ this.db.prepare(`
216
+ CREATE TABLE IF NOT EXISTS memory_stage1_outputs (
217
+ session_file TEXT PRIMARY KEY,
218
+ cwd TEXT NOT NULL,
219
+ entry_count INTEGER NOT NULL,
220
+ source_updated_at TEXT NOT NULL,
221
+ generated_at TEXT NOT NULL,
222
+ raw_memory TEXT NOT NULL,
223
+ rollout_summary TEXT NOT NULL,
224
+ rollout_slug TEXT,
225
+ usage_count INTEGER NOT NULL DEFAULT 0,
226
+ last_usage TEXT,
227
+ selected_for_phase2 INTEGER NOT NULL DEFAULT 0,
228
+ selected_for_phase2_source_updated_at TEXT
229
+ )
230
+ `).run();
231
+ this.db.prepare(`
232
+ CREATE TABLE IF NOT EXISTS memory_jobs (
233
+ kind TEXT NOT NULL,
234
+ job_key TEXT NOT NULL,
235
+ status TEXT NOT NULL,
236
+ worker_id TEXT,
237
+ lease_until INTEGER,
238
+ retry_at INTEGER,
239
+ input_watermark INTEGER NOT NULL DEFAULT 0,
240
+ last_success_watermark INTEGER NOT NULL DEFAULT 0,
241
+ started_at INTEGER,
242
+ finished_at INTEGER,
243
+ last_error TEXT,
244
+ PRIMARY KEY (kind, job_key)
245
+ )
246
+ `).run();
247
+ this.db.prepare(`
248
+ CREATE TABLE IF NOT EXISTS memory_thread_modes (
249
+ session_file TEXT PRIMARY KEY,
250
+ mode TEXT NOT NULL,
251
+ updated_at TEXT NOT NULL
252
+ )
253
+ `).run();
254
+ this.db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(SCHEMA_VERSION));
255
+ }
256
+ }
257
+ function mapStage1(row) {
258
+ return {
259
+ sessionFile: String(row.session_file),
260
+ cwd: String(row.cwd),
261
+ entryCount: Number(row.entry_count),
262
+ sourceUpdatedAt: String(row.source_updated_at),
263
+ generatedAt: String(row.generated_at),
264
+ rawMemory: String(row.raw_memory),
265
+ rolloutSummary: String(row.rollout_summary),
266
+ rolloutSlug: typeof row.rollout_slug === "string" ? row.rollout_slug : undefined,
267
+ usageCount: Number(row.usage_count ?? 0),
268
+ lastUsage: typeof row.last_usage === "string" ? row.last_usage : undefined,
269
+ selectedForPhase2: Number(row.selected_for_phase2 ?? 0) === 1,
270
+ selectedForPhase2SourceUpdatedAt: typeof row.selected_for_phase2_source_updated_at === "string"
271
+ ? row.selected_for_phase2_source_updated_at
272
+ : undefined,
273
+ };
274
+ }
275
+ function mapJob(row) {
276
+ return {
277
+ kind: String(row.kind),
278
+ jobKey: String(row.job_key),
279
+ status: isJobStatus(row.status) ? row.status : "pending",
280
+ workerId: typeof row.worker_id === "string" ? row.worker_id : undefined,
281
+ leaseUntil: typeof row.lease_until === "number" ? row.lease_until : undefined,
282
+ retryAt: typeof row.retry_at === "number" ? row.retry_at : undefined,
283
+ inputWatermark: Number(row.input_watermark ?? 0),
284
+ lastSuccessWatermark: Number(row.last_success_watermark ?? 0),
285
+ startedAt: typeof row.started_at === "number" ? row.started_at : undefined,
286
+ finishedAt: typeof row.finished_at === "number" ? row.finished_at : undefined,
287
+ lastError: typeof row.last_error === "string" ? row.last_error : undefined,
288
+ };
289
+ }
290
+ function isJobStatus(value) {
291
+ return value === "pending" || value === "running" || value === "succeeded" || value === "failed";
292
+ }
293
+ function unixNow() {
294
+ return Math.floor(Date.now() / 1000);
295
+ }
296
+ function createDatabase(path) {
297
+ if (isBunRuntime()) {
298
+ const bunSqlite = require("bun:sqlite");
299
+ return new bunSqlite.Database(path);
300
+ }
301
+ const Database = require("better-sqlite3");
302
+ return new Database(path);
303
+ }
304
+ function setWalMode(db) {
305
+ if (typeof db.pragma === "function") {
306
+ db.pragma("journal_mode = WAL");
307
+ return;
308
+ }
309
+ db.prepare("PRAGMA journal_mode = WAL").get();
310
+ }
311
+ function isBunRuntime() {
312
+ return typeof globalThis.Bun !== "undefined";
313
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./paths.js";
2
+ export * from "./store.js";
3
+ export * from "./db.js";
4
+ export * from "./phase1.js";
5
+ export * from "./phase2.js";
6
+ export * from "./start.js";
7
+ export * from "./storage.js";
8
+ export * from "./reset.js";
9
+ export * from "./usage.js";
@@ -0,0 +1,9 @@
1
+ export * from "./paths.js";
2
+ export * from "./store.js";
3
+ export * from "./db.js";
4
+ export * from "./phase1.js";
5
+ export * from "./phase2.js";
6
+ export * from "./start.js";
7
+ export * from "./storage.js";
8
+ export * from "./reset.js";
9
+ export * from "./usage.js";
@@ -0,0 +1,18 @@
1
+ export { getBubbleHome } from "../bubble-home.js";
2
+ export interface MemoryPaths {
3
+ globalRoot: string;
4
+ globalAgents: string;
5
+ globalSummary: string;
6
+ globalMemory: string;
7
+ globalRawMemories: string;
8
+ globalRolloutSummaries: string;
9
+ globalSkills: string;
10
+ globalExtensions: string;
11
+ globalDatabase: string;
12
+ projectRoot: string;
13
+ projectBubbleRoot: string;
14
+ projectAgents: string;
15
+ projectLocalAgents: string;
16
+ }
17
+ export declare function getMemoryPaths(cwd: string): MemoryPaths;
18
+ export declare function findProjectRoot(cwd: string): string;
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, parse, resolve } from "node:path";
3
+ export { getBubbleHome } from "../bubble-home.js";
4
+ import { getBubbleHome } from "../bubble-home.js";
5
+ export function getMemoryPaths(cwd) {
6
+ const bubbleHome = getBubbleHome();
7
+ const globalRoot = join(bubbleHome, "memories");
8
+ const projectRoot = findProjectRoot(cwd);
9
+ const projectBubbleRoot = join(projectRoot, ".bubble");
10
+ return {
11
+ globalRoot,
12
+ globalAgents: join(bubbleHome, "AGENTS.md"),
13
+ globalSummary: join(globalRoot, "memory_summary.md"),
14
+ globalMemory: join(globalRoot, "MEMORY.md"),
15
+ globalRawMemories: join(globalRoot, "raw_memories.md"),
16
+ globalRolloutSummaries: join(globalRoot, "rollout_summaries"),
17
+ globalSkills: join(globalRoot, "skills"),
18
+ globalExtensions: join(globalRoot, "memories_extensions"),
19
+ globalDatabase: join(globalRoot, "state.sqlite"),
20
+ projectRoot,
21
+ projectBubbleRoot,
22
+ projectAgents: join(projectRoot, "AGENTS.md"),
23
+ projectLocalAgents: join(projectBubbleRoot, "AGENTS.md"),
24
+ };
25
+ }
26
+ export function findProjectRoot(cwd) {
27
+ let current = resolve(cwd);
28
+ const root = parse(current).root;
29
+ while (true) {
30
+ if (existsSync(join(current, ".git"))) {
31
+ return current;
32
+ }
33
+ if (current === root) {
34
+ return resolve(cwd);
35
+ }
36
+ current = dirname(current);
37
+ }
38
+ }
@@ -0,0 +1,23 @@
1
+ import type { Message, ThinkingLevel } from "../types.js";
2
+ export interface Phase1Options {
3
+ cwd: string;
4
+ complete: (messages: Message[], options?: {
5
+ model?: string;
6
+ temperature?: number;
7
+ thinkingLevel?: ThinkingLevel;
8
+ }) => Promise<string>;
9
+ model?: string;
10
+ minEntries?: number;
11
+ limit?: number;
12
+ now?: Date;
13
+ }
14
+ export interface Phase1Result {
15
+ scanned: number;
16
+ claimed: number;
17
+ succeeded: number;
18
+ empty: number;
19
+ failed: number;
20
+ skipped: number;
21
+ errors: string[];
22
+ }
23
+ export declare function runMemoryPhase1(options: Phase1Options): Promise<Phase1Result>;
@@ -0,0 +1,172 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, readdirSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { SessionManager } from "../session.js";
5
+ import { MemoryDatabase } from "./db.js";
6
+ import { getBubbleHome } from "./paths.js";
7
+ import { buildStageOneMessages, parseJsonObject } from "./prompts.js";
8
+ import { redactSecrets } from "./store.js";
9
+ const DEFAULT_MIN_ENTRIES = 4;
10
+ const DEFAULT_LIMIT = 24;
11
+ const MAX_TRANSCRIPT_CHARS = 70_000;
12
+ const MAX_CONTENT_CHARS = 3_000;
13
+ export async function runMemoryPhase1(options) {
14
+ const result = { scanned: 0, claimed: 0, succeeded: 0, empty: 0, failed: 0, skipped: 0, errors: [] };
15
+ if (!options.model) {
16
+ result.skipped++;
17
+ result.errors.push("no active model");
18
+ return result;
19
+ }
20
+ const db = new MemoryDatabase(options.cwd);
21
+ try {
22
+ const sessions = listEligibleSessionFiles(options.limit ?? DEFAULT_LIMIT);
23
+ result.scanned = sessions.length;
24
+ for (const sessionFile of sessions) {
25
+ if (db.getThreadMemoryMode(sessionFile) === "disabled") {
26
+ result.skipped++;
27
+ continue;
28
+ }
29
+ const session = new SessionManager(sessionFile);
30
+ const sessionCwd = session.getMetadata().cwd ?? options.cwd;
31
+ const entries = session.getEntries();
32
+ const entryCount = entries.length;
33
+ if (countMeaningfulEntries(entries) < (options.minEntries ?? DEFAULT_MIN_ENTRIES)) {
34
+ result.skipped++;
35
+ continue;
36
+ }
37
+ const sourceUpdatedAt = statSync(sessionFile).mtime.toISOString();
38
+ const existing = db.getStage1Output(sessionFile);
39
+ if (existing && existing.entryCount === entryCount && existing.sourceUpdatedAt === sourceUpdatedAt) {
40
+ result.skipped++;
41
+ continue;
42
+ }
43
+ const claim = db.claimPhase1Job(sessionFile, randomUUID(), 3600);
44
+ if (!claim.claimed) {
45
+ result.skipped++;
46
+ continue;
47
+ }
48
+ result.claimed++;
49
+ try {
50
+ const transcript = serializeSessionEntries(entries);
51
+ const raw = await options.complete(buildStageOneMessages({
52
+ cwd: sessionCwd,
53
+ sessionFile,
54
+ transcript,
55
+ }), {
56
+ model: options.model,
57
+ temperature: 0,
58
+ thinkingLevel: "off",
59
+ });
60
+ const parsed = parseJsonObject(raw);
61
+ const rawMemory = stringField(parsed.raw_memory);
62
+ const rolloutSummary = stringField(parsed.rollout_summary);
63
+ if (!rawMemory && !rolloutSummary) {
64
+ result.empty++;
65
+ continue;
66
+ }
67
+ db.upsertStage1Output({
68
+ sessionFile,
69
+ cwd: sessionCwd,
70
+ entryCount,
71
+ sourceUpdatedAt,
72
+ generatedAt: (options.now ?? new Date()).toISOString(),
73
+ rawMemory: redactSecrets(rawMemory || rolloutSummary).text,
74
+ rolloutSummary: redactSecrets(rolloutSummary || rawMemory).text,
75
+ rolloutSlug: stringField(parsed.rollout_slug) || undefined,
76
+ });
77
+ db.finishPhase1Job(sessionFile, true);
78
+ result.succeeded++;
79
+ }
80
+ catch (error) {
81
+ db.finishPhase1Job(sessionFile, false, error instanceof Error ? error.message : String(error));
82
+ result.failed++;
83
+ result.errors.push(error instanceof Error ? error.message : String(error));
84
+ }
85
+ }
86
+ }
87
+ finally {
88
+ db.close();
89
+ }
90
+ return result;
91
+ }
92
+ function listEligibleSessionFiles(limit) {
93
+ const dir = join(getBubbleHome(), "sessions");
94
+ if (!existsSync(dir))
95
+ return [];
96
+ return collectSessionFiles(dir)
97
+ .filter((file) => {
98
+ try {
99
+ return statSync(file).isFile();
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ })
105
+ .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
106
+ .slice(0, limit);
107
+ }
108
+ function collectSessionFiles(dir) {
109
+ const files = [];
110
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
111
+ const path = join(dir, entry.name);
112
+ if (entry.isDirectory()) {
113
+ files.push(...collectSessionFiles(path));
114
+ }
115
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
116
+ files.push(path);
117
+ }
118
+ }
119
+ return files;
120
+ }
121
+ function serializeSessionEntries(entries) {
122
+ const parts = [];
123
+ let total = 0;
124
+ for (const entry of entries) {
125
+ if (total >= MAX_TRANSCRIPT_CHARS)
126
+ break;
127
+ const line = serializeSessionEntry(entry);
128
+ if (!line)
129
+ continue;
130
+ parts.push(line);
131
+ total += line.length + 1;
132
+ }
133
+ return truncate(parts.join("\n"), MAX_TRANSCRIPT_CHARS);
134
+ }
135
+ function serializeSessionEntry(entry) {
136
+ switch (entry.type) {
137
+ case "metadata":
138
+ return `[metadata] ${JSON.stringify(entry.metadata)}`;
139
+ case "summary":
140
+ return `[summary] ${truncate(entry.summary, MAX_CONTENT_CHARS)}`;
141
+ case "marker":
142
+ return `[marker:${entry.kind}] ${entry.value}`;
143
+ case "user_message":
144
+ return `[user] ${truncate(contentToText(entry.message.content), MAX_CONTENT_CHARS)}`;
145
+ case "assistant_message":
146
+ return `[assistant] ${truncate(entry.message.content, MAX_CONTENT_CHARS)}`;
147
+ case "tool_call":
148
+ return `[tool_call:${entry.toolCall.name}] ${truncate(entry.toolCall.arguments, 1_500)}`;
149
+ case "tool_result":
150
+ return `[tool_result${entry.message.isError ? " error=true" : ""}] ${truncate(entry.message.content, 2_000)}`;
151
+ case "todos_snapshot":
152
+ return `[todos] ${truncate(JSON.stringify(entry.todos), 2_000)}`;
153
+ }
154
+ }
155
+ function countMeaningfulEntries(entries) {
156
+ return entries.filter((entry) => entry.type === "user_message"
157
+ || entry.type === "assistant_message"
158
+ || entry.type === "tool_call"
159
+ || entry.type === "tool_result"
160
+ || entry.type === "summary").length;
161
+ }
162
+ function contentToText(content) {
163
+ return typeof content === "string"
164
+ ? content
165
+ : content.map((part) => part.type === "text" ? part.text : "[image]").join("\n");
166
+ }
167
+ function truncate(value, maxChars) {
168
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars - 40).trimEnd()}\n[truncated ${value.length - maxChars + 40} chars]`;
169
+ }
170
+ function stringField(value) {
171
+ return typeof value === "string" ? value.trim() : "";
172
+ }
@@ -0,0 +1,19 @@
1
+ import type { Message, ThinkingLevel } from "../types.js";
2
+ export interface Phase2Options {
3
+ cwd: string;
4
+ complete: (messages: Message[], options?: {
5
+ model?: string;
6
+ temperature?: number;
7
+ thinkingLevel?: ThinkingLevel;
8
+ }) => Promise<string>;
9
+ model?: string;
10
+ limit?: number;
11
+ }
12
+ export interface Phase2Result {
13
+ status: "succeeded" | "skipped" | "failed";
14
+ reason?: string;
15
+ selected: number;
16
+ memoryPath?: string;
17
+ summaryPath?: string;
18
+ }
19
+ export declare function runMemoryPhase2(options: Phase2Options): Promise<Phase2Result>;