brane 0.1.1 → 0.3.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 (352) hide show
  1. package/AGENTS.md +367 -0
  2. package/CHANGELOG.md +66 -0
  3. package/CONTRIBUTING.md +101 -0
  4. package/DESIGN.md +405 -0
  5. package/Dockerfile +61 -0
  6. package/LICENSE +656 -17
  7. package/README.md +174 -2
  8. package/SECURITY.md +213 -0
  9. package/dist/absorb/brane-id.js +11 -0
  10. package/dist/absorb/chatgpt.js +161 -0
  11. package/dist/absorb/claude-code.js +504 -0
  12. package/dist/absorb/index.js +543 -0
  13. package/dist/absorb/organelle.js +345 -0
  14. package/dist/absorb/run.js +520 -0
  15. package/dist/absorb/sources.js +168 -0
  16. package/dist/absorb/types.js +3 -0
  17. package/dist/agent-env.js +45 -0
  18. package/dist/agents.js +1168 -0
  19. package/dist/claude-live-session.js +124 -0
  20. package/dist/cli.js +50 -0
  21. package/dist/codex-tool-log.js +144 -0
  22. package/dist/commands/absorb.js +291 -0
  23. package/dist/commands/attach.js +31 -0
  24. package/dist/commands/certify.js +62 -0
  25. package/dist/commands/config.js +133 -0
  26. package/dist/commands/cost.js +126 -0
  27. package/dist/commands/daemon.js +350 -0
  28. package/dist/commands/doctor.js +262 -0
  29. package/dist/commands/fold.js +95 -0
  30. package/dist/commands/list.js +89 -0
  31. package/dist/commands/mcp.js +84 -0
  32. package/dist/commands/meld.js +360 -0
  33. package/dist/commands/memory.js +215 -0
  34. package/dist/commands/revive.js +74 -0
  35. package/dist/commands/run.js +85 -0
  36. package/dist/commands/spawn.js +1101 -0
  37. package/dist/commands/token.js +154 -0
  38. package/dist/copilot-tool-log.js +145 -0
  39. package/dist/cost/pricing.js +116 -0
  40. package/dist/cost/reprice.js +136 -0
  41. package/dist/crush-tool-log.js +293 -0
  42. package/dist/daemon/agent-scope.js +116 -0
  43. package/dist/daemon/awaiting.js +54 -0
  44. package/dist/daemon/cert-manager.js +349 -0
  45. package/dist/daemon/client.js +262 -0
  46. package/dist/daemon/gated-routes.js +112 -0
  47. package/dist/daemon/keep-awake.js +103 -0
  48. package/dist/daemon/local-socket.js +145 -0
  49. package/dist/daemon/presence-gate.js +206 -0
  50. package/dist/daemon/server.js +6353 -0
  51. package/dist/daemon/signal-source.js +35 -0
  52. package/dist/daemon/tailscale-rebind.js +58 -0
  53. package/dist/daemon/token-ring.js +263 -0
  54. package/dist/daemon/token-routes.js +190 -0
  55. package/dist/db/events.js +152 -0
  56. package/dist/db/mentions.js +179 -0
  57. package/dist/db/schema.js +643 -0
  58. package/dist/db/tokens.js +624 -0
  59. package/dist/db/tool-uses.js +647 -0
  60. package/dist/diagram/index.js +85 -0
  61. package/dist/diagram/label.js +455 -0
  62. package/dist/diagram/layout.js +476 -0
  63. package/dist/diagram/links.js +50 -0
  64. package/dist/diagram/metrics.js +152 -0
  65. package/dist/diagram/parse.js +487 -0
  66. package/dist/diagram/svg.js +389 -0
  67. package/dist/diagram/types.js +9 -0
  68. package/dist/export.js +629 -0
  69. package/dist/files/archive-types.js +2 -0
  70. package/dist/files/archive.js +182 -0
  71. package/dist/files/browse.js +327 -0
  72. package/dist/files/bytes.js +162 -0
  73. package/dist/files/codecs.js +33 -0
  74. package/dist/files/find.js +212 -0
  75. package/dist/files/tar.js +223 -0
  76. package/dist/files/zip.js +209 -0
  77. package/dist/folds.js +72 -0
  78. package/dist/grammars/ebnf.js +92 -0
  79. package/dist/highlight.js +250 -0
  80. package/dist/ids.js +24 -0
  81. package/dist/markdown-diagnostics.js +233 -0
  82. package/dist/math.js +168 -0
  83. package/dist/mcp/server.js +886 -0
  84. package/dist/memory/embedder.js +145 -0
  85. package/dist/memory/embedding-worker.js +71 -0
  86. package/dist/memory/embeddings.js +91 -0
  87. package/dist/memory/index.js +232 -0
  88. package/dist/memory/storage.js +587 -0
  89. package/dist/memory/wiki.js +688 -0
  90. package/dist/memory/wordpiece.js +247 -0
  91. package/dist/mentions-scan.js +40 -0
  92. package/dist/presence.js +259 -0
  93. package/dist/session-affinity.js +185 -0
  94. package/dist/shared/api-request.js +124 -0
  95. package/dist/shared/app-origin.js +26 -0
  96. package/dist/shared/block-glyphs.js +114 -0
  97. package/dist/shared/diagram-css.js +17 -0
  98. package/dist/shared/export-selection.js +101 -0
  99. package/dist/shared/find-in-text.js +110 -0
  100. package/dist/shared/glow-hues.js +225 -0
  101. package/dist/shared/hex-dump.js +62 -0
  102. package/dist/shared/hex-pages.js +52 -0
  103. package/dist/shared/hit-drum.js +79 -0
  104. package/dist/shared/known-nids.js +58 -0
  105. package/dist/shared/list-scroll-keys.js +56 -0
  106. package/dist/shared/markdown-lists.js +72 -0
  107. package/dist/shared/markdown-math.js +485 -0
  108. package/dist/shared/markdown-mentions.js +79 -0
  109. package/dist/shared/markdown-nids.js +41 -0
  110. package/dist/shared/markdown-protocols.js +53 -0
  111. package/dist/shared/markdown-refs.js +123 -0
  112. package/dist/shared/membrane-invasion.js +342 -0
  113. package/dist/shared/membrane-motion.js +83 -0
  114. package/dist/shared/membrane-palette.js +253 -0
  115. package/dist/shared/membrane-terrain.js +212 -0
  116. package/dist/shared/message-selection.js +123 -0
  117. package/dist/shared/mo-glyph.js +57 -0
  118. package/dist/shared/narrative-format.js +237 -0
  119. package/dist/shared/narrative-menu.js +84 -0
  120. package/dist/shared/narrative-sort.js +47 -0
  121. package/dist/shared/narrative-status.js +52 -0
  122. package/dist/shared/no-pan.js +85 -0
  123. package/dist/shared/pane-layout.js +50 -0
  124. package/dist/shared/payload-inline.js +83 -0
  125. package/dist/shared/speaker-color.js +61 -0
  126. package/dist/shared/token-rotation.js +14 -0
  127. package/dist/shared/view-url.js +216 -0
  128. package/dist/shared/wiki-links.js +70 -0
  129. package/dist/shared/wiki-window.js +4 -0
  130. package/dist/tailscale.js +55 -0
  131. package/dist/test/absorb-claude.test.js +892 -0
  132. package/dist/test/agent-scope.test.js +193 -0
  133. package/dist/test/agents.test.js +55 -0
  134. package/dist/test/api-request.test.js +122 -0
  135. package/dist/test/app-host-literal.test.js +53 -0
  136. package/dist/test/awaiting-glow.test.js +54 -0
  137. package/dist/test/awaiting.test.js +122 -0
  138. package/dist/test/bind-token.test.js +88 -0
  139. package/dist/test/block-glyphs.test.js +68 -0
  140. package/dist/test/cert-manager.test.js +262 -0
  141. package/dist/test/claude-live-session.test.js +110 -0
  142. package/dist/test/codex-tool-log.test.js +568 -0
  143. package/dist/test/config.test.js +71 -0
  144. package/dist/test/copilot-tool-log.test.js +93 -0
  145. package/dist/test/cors-preflight.test.js +56 -0
  146. package/dist/test/cost-linger.test.js +31 -0
  147. package/dist/test/cost-pricing.test.js +72 -0
  148. package/dist/test/cost-reprice.test.js +139 -0
  149. package/dist/test/cost-sessions.test.js +301 -0
  150. package/dist/test/crush-tool-log.test.js +289 -0
  151. package/dist/test/daemon-client.test.js +109 -0
  152. package/dist/test/daemon-socket.test.js +293 -0
  153. package/dist/test/diagram-arrows.test.js +205 -0
  154. package/dist/test/diagram-export.test.js +210 -0
  155. package/dist/test/diagram-labels.test.js +313 -0
  156. package/dist/test/diagram-parse.test.js +191 -0
  157. package/dist/test/diagram-render.test.js +175 -0
  158. package/dist/test/diagram-review.test.js +288 -0
  159. package/dist/test/diagram-self-loop-labels.test.js +303 -0
  160. package/dist/test/embedder.test.js +84 -0
  161. package/dist/test/events.test.js +123 -0
  162. package/dist/test/export-selection.test.js +102 -0
  163. package/dist/test/export.test.js +505 -0
  164. package/dist/test/files-browse.test.js +393 -0
  165. package/dist/test/files-find.test.js +272 -0
  166. package/dist/test/find-in-text.test.js +86 -0
  167. package/dist/test/first-paint-viewport.test.js +59 -0
  168. package/dist/test/fixtures/embedding-golden.js +23 -0
  169. package/dist/test/fixtures/wordpiece-golden.js +167 -0
  170. package/dist/test/glow-hues.test.js +202 -0
  171. package/dist/test/harness-cost.test.js +423 -0
  172. package/dist/test/helpers.js +21 -0
  173. package/dist/test/hex-pages.test.js +68 -0
  174. package/dist/test/highlight.test.js +69 -0
  175. package/dist/test/hit-drum.test.js +71 -0
  176. package/dist/test/hook-config.test.js +299 -0
  177. package/dist/test/json-gzip.test.js +70 -0
  178. package/dist/test/key-routing.test.js +58 -0
  179. package/dist/test/known-nids.test.js +106 -0
  180. package/dist/test/link-preview.test.js +67 -0
  181. package/dist/test/list-scroll-keys.test.js +66 -0
  182. package/dist/test/listen-own-words.test.js +147 -0
  183. package/dist/test/listen-timeout.test.js +33 -0
  184. package/dist/test/markdown-diagnostics.test.js +121 -0
  185. package/dist/test/markdown-lists.test.js +182 -0
  186. package/dist/test/markdown-math.test.js +256 -0
  187. package/dist/test/markdown-nids.test.js +146 -0
  188. package/dist/test/markdown-protocols.test.js +139 -0
  189. package/dist/test/mcp-bridge-cancel.test.js +98 -0
  190. package/dist/test/mcp-bridge-reconnect.test.js +121 -0
  191. package/dist/test/mcp-narrative.test.js +193 -0
  192. package/dist/test/mcp.test.js +548 -0
  193. package/dist/test/meld.test.js +143 -0
  194. package/dist/test/membrane-invasion.test.js +482 -0
  195. package/dist/test/membrane-motion.test.js +101 -0
  196. package/dist/test/membrane-palette.test.js +166 -0
  197. package/dist/test/membrane-terrain.test.js +170 -0
  198. package/dist/test/memory-log.test.js +202 -0
  199. package/dist/test/mentions-e2e.test.js +261 -0
  200. package/dist/test/mentions-ledger.test.js +265 -0
  201. package/dist/test/mentions-listen.test.js +265 -0
  202. package/dist/test/mentions.test.js +238 -0
  203. package/dist/test/message-selection.test.js +114 -0
  204. package/dist/test/mo-glyph.test.js +22 -0
  205. package/dist/test/model-resolution.test.js +166 -0
  206. package/dist/test/narrate-validate.test.js +77 -0
  207. package/dist/test/narrate-wait.test.js +160 -0
  208. package/dist/test/narrative-format.test.js +219 -0
  209. package/dist/test/narrative-menu.test.js +150 -0
  210. package/dist/test/narrative-sort.test.js +75 -0
  211. package/dist/test/no-pan.test.js +60 -0
  212. package/dist/test/pane-layout.test.js +153 -0
  213. package/dist/test/paths.test.js +37 -0
  214. package/dist/test/payload-inline.test.js +91 -0
  215. package/dist/test/precache-fonts.test.js +72 -0
  216. package/dist/test/presence-gate.test.js +440 -0
  217. package/dist/test/presence.test.js +160 -0
  218. package/dist/test/read-events.test.js +220 -0
  219. package/dist/test/remote-meld.test.js +128 -0
  220. package/dist/test/revive-cli.test.js +93 -0
  221. package/dist/test/search-floor.test.js +206 -0
  222. package/dist/test/search-fusion.test.js +168 -0
  223. package/dist/test/search.test.js +205 -0
  224. package/dist/test/security.test.js +48 -0
  225. package/dist/test/selection-wiring.test.js +61 -0
  226. package/dist/test/session-affinity.test.js +295 -0
  227. package/dist/test/signal-source.test.js +32 -0
  228. package/dist/test/socket-account.test.js +268 -0
  229. package/dist/test/spawn-toml.test.js +72 -0
  230. package/dist/test/speaker-color.test.js +90 -0
  231. package/dist/test/tailscale-rebind.test.js +79 -0
  232. package/dist/test/terminal-cursor.test.js +61 -0
  233. package/dist/test/terminal-resync.test.js +23 -0
  234. package/dist/test/terminal-size.test.js +36 -0
  235. package/dist/test/thin-cli.test.js +382 -0
  236. package/dist/test/token-bootstrap.test.js +272 -0
  237. package/dist/test/token-devices.test.js +646 -0
  238. package/dist/test/token-lapse.test.js +94 -0
  239. package/dist/test/token-ring.test.js +188 -0
  240. package/dist/test/token-rotate-cli.test.js +58 -0
  241. package/dist/test/token-routes.test.js +95 -0
  242. package/dist/test/tool-uses.test.js +1006 -0
  243. package/dist/test/usage-ledger.test.js +126 -0
  244. package/dist/test/view-url.test.js +627 -0
  245. package/dist/test/wiki-links.test.js +57 -0
  246. package/dist/test/wiki-sources.test.js +85 -0
  247. package/dist/test/wiki-window.test.js +31 -0
  248. package/dist/test/wiki.test.js +399 -0
  249. package/dist/test/wordpiece.test.js +68 -0
  250. package/dist/test/wrangler-route.test.js +27 -0
  251. package/dist/tls.js +148 -0
  252. package/dist/tmux.js +213 -0
  253. package/dist/user-agent.js +46 -0
  254. package/docker-entrypoint.sh +154 -0
  255. package/docs/README.md +3 -0
  256. package/docs/absorb.md +231 -0
  257. package/docs/cost-ledger.md +125 -0
  258. package/docs/daemon-api.md +189 -0
  259. package/docs/diagrams.md +163 -0
  260. package/docs/doctor.md +36 -0
  261. package/docs/export-scope.md +69 -0
  262. package/docs/files-view.md +133 -0
  263. package/docs/link-previews.md +38 -0
  264. package/docs/math.md +32 -0
  265. package/docs/mcp.md +69 -0
  266. package/docs/memory-search.md +75 -0
  267. package/docs/mentions.md +101 -0
  268. package/docs/multi-brane.md +14 -0
  269. package/docs/passkeys.md +165 -0
  270. package/docs/presence.md +202 -0
  271. package/docs/releasing.md +104 -0
  272. package/docs/rendering-warnings.md +22 -0
  273. package/docs/request-layer.md +72 -0
  274. package/docs/selection-store.md +50 -0
  275. package/docs/session-affinity.md +61 -0
  276. package/docs/terminal-mirroring.md +243 -0
  277. package/docs/token-rotation.md +431 -0
  278. package/docs/view-urls.md +134 -0
  279. package/docs/web-app.md +23 -0
  280. package/docs/wiki.md +43 -0
  281. package/expo/dist/_expo/static/js/web/entry-e94247f119fdcd3b424a1764233a41d2.js +1099 -0
  282. package/expo/dist/apple-touch-icon.png +0 -0
  283. package/expo/dist/favicon.svg +3 -0
  284. package/expo/dist/index.html +177 -0
  285. package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  286. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  287. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  288. package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  289. package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  290. package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  291. package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  292. package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  293. package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  294. package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  295. package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  296. package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  297. package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  298. package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  299. package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  300. package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  301. package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  302. package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  303. package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  304. package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  305. package/expo/dist/katex/katex.min.css +1 -0
  306. package/expo/dist/manifest.json +16 -0
  307. package/expo/dist/metadata.json +1 -0
  308. package/expo/dist/og/membrane.v1.png +0 -0
  309. package/expo/dist/pdf.min.mjs +28 -0
  310. package/expo/dist/pdf.worker.min.mjs +28 -0
  311. package/expo/dist/sw.js +154 -0
  312. package/package.json +79 -27
  313. package/protocols/LIVE_loop.md +407 -0
  314. package/protocols/bebrief.md +41 -0
  315. package/protocols/digest.md +231 -0
  316. package/protocols/fly.md +266 -0
  317. package/protocols/idle.md +81 -0
  318. package/protocols/keeper.md +829 -0
  319. package/protocols/markdown.md +80 -0
  320. package/protocols/memory.md +419 -0
  321. package/protocols/mentions.md +93 -0
  322. package/protocols/payloads.md +53 -0
  323. package/protocols/prelude.md +91 -0
  324. package/protocols/ramify.md +105 -0
  325. package/protocols/review.md +356 -0
  326. package/protocols/rrr.md +232 -0
  327. package/protocols/scribe.md +429 -0
  328. package/protocols/summarize.md +203 -0
  329. package/protocols/twin.md +174 -0
  330. package/protocols/wiki.md +438 -0
  331. package/protocols/worker.md +38 -0
  332. package/wiki/shared/append-only-event-log.md +148 -0
  333. package/wiki/shared/index.md +53 -0
  334. package/wiki/shared/owning-your-narratives.md +161 -0
  335. package/wiki/shared/premature-ontologies.md +435 -0
  336. package/wiki/shared/sedimentary-superintelligence.md +128 -0
  337. package/wiki/shared/sequestering-the-narrative.md +143 -0
  338. package/wiki/shared/why-wikis-work.md +216 -0
  339. package/.travis.yml +0 -7
  340. package/lib/brane.cjs.js +0 -12
  341. package/lib/brane.cjs.js.map +0 -1
  342. package/lib/brane.d.ts +0 -11
  343. package/lib/brane.js +0 -6
  344. package/lib/brane.js.map +0 -1
  345. package/lib/tests.d.ts +0 -1
  346. package/lib/tests.js +0 -8
  347. package/lib/tests.js.map +0 -1
  348. package/rollup.config.js +0 -21
  349. package/scripts/test.sh +0 -8
  350. package/src/brane.ts +0 -21
  351. package/src/tests.ts +0 -8
  352. package/tsconfig.json +0 -16
@@ -0,0 +1,152 @@
1
+ /** Convert a raw DB row to a clean API event (parse JSON, omit nulls) */
2
+ function toApiEvent(row) {
3
+ const evt = {
4
+ seq: row.seq,
5
+ time: row.time,
6
+ type: row.type,
7
+ };
8
+ if (row.content)
9
+ evt.content = row.content;
10
+ if (row.content_type)
11
+ evt.content_type = row.content_type;
12
+ if (row.source)
13
+ evt.source = row.source;
14
+ if (row.re) {
15
+ try {
16
+ evt.re = JSON.parse(row.re);
17
+ }
18
+ catch { /* ignore */ }
19
+ }
20
+ if (row.payloads) {
21
+ try {
22
+ evt.payloads = JSON.parse(row.payloads);
23
+ }
24
+ catch { /* ignore */ }
25
+ }
26
+ if (row.meta) {
27
+ try {
28
+ evt.meta = JSON.parse(row.meta);
29
+ }
30
+ catch { /* ignore */ }
31
+ }
32
+ return evt;
33
+ }
34
+ export function appendEvent(db, narrativeId, type, opts = {}) {
35
+ const maxSeqRow = db
36
+ .prepare("SELECT MAX(seq) as max_seq FROM events WHERE narrative_id = ?")
37
+ .get(narrativeId);
38
+ const seq = (maxSeqRow.max_seq ?? 0) + 1;
39
+ const time = Date.now();
40
+ const re = opts.re && opts.re.length > 0 ? JSON.stringify(opts.re) : null;
41
+ const payloads = opts.payloads && opts.payloads.length > 0 ? JSON.stringify(opts.payloads) : null;
42
+ const meta = opts.meta ? JSON.stringify(opts.meta) : null;
43
+ db.prepare(`INSERT INTO events (narrative_id, seq, time, type, content, content_type, source, re, payloads, meta)
44
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(narrativeId, seq, time, type, opts.content ?? null, opts.content_type ?? null, opts.source ?? null, re, payloads, meta);
45
+ return toApiEvent({
46
+ narrative_id: narrativeId,
47
+ seq,
48
+ time,
49
+ type,
50
+ content: opts.content ?? null,
51
+ content_type: opts.content_type ?? null,
52
+ source: opts.source ?? null,
53
+ re,
54
+ payloads,
55
+ meta,
56
+ });
57
+ }
58
+ export function readEventsSince(db, narrativeId, sinceSeq, limit) {
59
+ const sql = limit !== undefined
60
+ ? `SELECT * FROM events WHERE narrative_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?`
61
+ : `SELECT * FROM events WHERE narrative_id = ? AND seq > ? ORDER BY seq ASC`;
62
+ const params = limit !== undefined
63
+ ? [narrativeId, sinceSeq, limit]
64
+ : [narrativeId, sinceSeq];
65
+ const rows = db.prepare(sql).all(...params);
66
+ return rows.map(toApiEvent);
67
+ }
68
+ export function readEventsBefore(db, narrativeId, beforeSeq, limit) {
69
+ const rows = db.prepare(`SELECT * FROM events WHERE narrative_id = ? AND seq < ? ORDER BY seq DESC LIMIT ?`).all(narrativeId, beforeSeq, limit);
70
+ // Reverse to return in ascending seq order
71
+ rows.reverse();
72
+ return rows.map(toApiEvent);
73
+ }
74
+ /**
75
+ * The events with these exact seqs, ascending — an indexed lookup rather than a
76
+ * scan, for the caller that already knows which rows it wants. `src/export.ts`
77
+ * uses it for an excerpt: exporting two messages out of ten thousand should
78
+ * cost two rows, not ten thousand.
79
+ *
80
+ * Chunked because SQLite bounds the number of bound parameters in one
81
+ * statement, and a selection has no ceiling imposed on it anywhere else.
82
+ */
83
+ export function readEventsBySeqs(db, narrativeId, seqs) {
84
+ if (seqs.length === 0)
85
+ return [];
86
+ const CHUNK = 500;
87
+ const rows = [];
88
+ for (let i = 0; i < seqs.length; i += CHUNK) {
89
+ const chunk = seqs.slice(i, i + CHUNK);
90
+ const placeholders = chunk.map(() => "?").join(",");
91
+ rows.push(...db
92
+ .prepare(`SELECT * FROM events WHERE narrative_id = ? AND seq IN (${placeholders}) ORDER BY seq ASC`)
93
+ .all(narrativeId, ...chunk));
94
+ }
95
+ // Chunks are each ascending; a multi-chunk read has to be sorted as a whole.
96
+ rows.sort((a, b) => a.seq - b.seq);
97
+ return rows.map(toApiEvent);
98
+ }
99
+ /**
100
+ * Flexible event query supporting after (lower bound), before (upper bound),
101
+ * and tail (last N). All bounds are exclusive. Results always in ascending
102
+ * seq order.
103
+ *
104
+ * Combinations:
105
+ * - {} → all events (like after: 0)
106
+ * - { after: N } → events where seq > N
107
+ * - { before: N } → events where seq < N
108
+ * - { after: A, before: B } → events where seq > A AND seq < B
109
+ * - { tail: N } → last N events
110
+ * - { tail: N, before: B } → last N events where seq < B (backward pagination)
111
+ * - { after: A, tail: N } → last N events where seq > A
112
+ * - { after: A, before: B, tail: N } → last N events in range (A, B)
113
+ */
114
+ export function readEvents(db, narrativeId, opts) {
115
+ const { after, before, tail } = opts;
116
+ const conditions = ["narrative_id = ?"];
117
+ const params = [narrativeId];
118
+ if (after !== undefined && after > 0) {
119
+ conditions.push("seq > ?");
120
+ params.push(after);
121
+ }
122
+ if (before !== undefined) {
123
+ conditions.push("seq < ?");
124
+ params.push(before);
125
+ }
126
+ const where = conditions.join(" AND ");
127
+ if (tail !== undefined) {
128
+ if (tail <= 0)
129
+ return [];
130
+ // Fetch last N matching rows (DESC), then reverse for ASC order
131
+ const sql = `SELECT * FROM events WHERE ${where} ORDER BY seq DESC LIMIT ?`;
132
+ const rows = db.prepare(sql).all(...params, tail);
133
+ rows.reverse();
134
+ return rows.map(toApiEvent);
135
+ }
136
+ const sql = `SELECT * FROM events WHERE ${where} ORDER BY seq ASC`;
137
+ const rows = db.prepare(sql).all(...params);
138
+ return rows.map(toApiEvent);
139
+ }
140
+ export function getMinSeq(db, narrativeId) {
141
+ const row = db
142
+ .prepare("SELECT MIN(seq) as min_seq FROM events WHERE narrative_id = ?")
143
+ .get(narrativeId);
144
+ return row.min_seq ?? 0;
145
+ }
146
+ export function getMaxSeq(db, narrativeId) {
147
+ const row = db
148
+ .prepare("SELECT MAX(seq) as max_seq FROM events WHERE narrative_id = ?")
149
+ .get(narrativeId);
150
+ return row.max_seq ?? 0;
151
+ }
152
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1,179 @@
1
+ /** The mention ledger: who was addressed, from where, and what became of it.
2
+ *
3
+ * Two kinds of state share a row, on purpose.
4
+ *
5
+ * The **mention** — `mentioned_nid`, `src_narrative`, `src_seq`, `author`,
6
+ * `time` — is derived data. A re-scan of the event log reproduces it exactly,
7
+ * because `UNIQUE (mentioned_nid, src_narrative, src_seq)` makes a second
8
+ * insert a no-op. Rebuild freely, but only additively (see the schema
9
+ * comment: rowids are stable and the row carries state).
10
+ *
11
+ * **`sent_at` and `seen_at`** are not derived from anything. They record what
12
+ * passed between the daemon and one agent, which no amount of re-reading the
13
+ * log can recompute, and they answer two different questions:
14
+ *
15
+ * `sent_at` — handed over. Governs whether a parked `listen` resolves now,
16
+ * so a mention wakes an agent exactly once and can't spin.
17
+ *
18
+ * `seen_at` — demonstrably attended to, evidenced by the agent reading the
19
+ * narrative the mention was written in. Delivery alone doesn't set it: a
20
+ * frame can arrive and still go unnoticed — buried in a long result, lost
21
+ * to compaction, or simply not acted on. So a delivered-but-unread mention
22
+ * keeps reappearing in the receipt.
23
+ *
24
+ * Both live on the row rather than as a high-water mark per narrative,
25
+ * because acknowledgment is per *source*. Reading narrative B says nothing
26
+ * about a mention written in C, and a scalar watermark over a shared id space
27
+ * settles every lower id regardless of origin — silently marking unread
28
+ * mentions read. NULL → timestamp also makes monotonicity structural: there
29
+ * is no value that could rewind.
30
+ */
31
+ /** Inserts one row per mentioned nid; returns the nids *newly* recorded.
32
+ *
33
+ * The return value is the rising edge, and callers use it to decide whom to
34
+ * wake. A re-scan of an event already in the ledger returns `[]` and wakes
35
+ * nobody, which is what makes a backfill safe to run against live
36
+ * narratives. */
37
+ export function recordMentions(db, input) {
38
+ const stmt = db.prepare(`INSERT OR IGNORE INTO mentions (mentioned_nid, src_narrative, src_seq, author, time)
39
+ VALUES (?, ?, ?, ?, ?)`);
40
+ const fresh = [];
41
+ for (const nid of input.mentionedNids) {
42
+ const info = stmt.run(nid, input.srcNarrative, input.srcSeq, input.author, input.time);
43
+ if (info.changes === 1)
44
+ fresh.push(nid);
45
+ }
46
+ return fresh;
47
+ }
48
+ const SELECT_ROWS = `
49
+ SELECT m.id, m.src_narrative AS src, m.src_seq AS seq, m.author, m.time,
50
+ n.brane_id AS src_brane
51
+ FROM mentions m
52
+ LEFT JOIN narratives n ON n.id = m.src_narrative
53
+ `;
54
+ function toRows(raw, ownBrane) {
55
+ return raw.map((r) => ({
56
+ id: r.id,
57
+ src: r.src,
58
+ seq: r.seq,
59
+ author: r.author,
60
+ time: r.time,
61
+ // Stamped only when it differs, so the common case stays quiet and a
62
+ // cross-brane mention is legible rather than spooky.
63
+ ...(r.src_brane && r.src_brane !== ownBrane ? { brane_id: r.src_brane } : {}),
64
+ }));
65
+ }
66
+ /** Whether a mention is waiting that this agent has not been woken for.
67
+ *
68
+ * This is the listen predicate, and it reads `sent_at` — not `seen_at`. An
69
+ * unread mention must keep appearing in receipts without re-waking the agent
70
+ * every time it parks. */
71
+ export function hasUnsentMentions(db, nid) {
72
+ return (db
73
+ .prepare("SELECT 1 FROM mentions WHERE mentioned_nid = ? AND sent_at IS NULL LIMIT 1")
74
+ .get(nid) !== undefined);
75
+ }
76
+ /** What the agent is still owed: everything it hasn't demonstrably read.
77
+ *
78
+ * `total` counts all of them; `rows` is the capped slice actually shown. The
79
+ * remainder stays owed and surfaces in later receipts — it just doesn't cost
80
+ * another wake, because `markSent` covers every pending row, not only the
81
+ * ones displayed. */
82
+ export function pendingMentions(db, nid, limit, ownBrane) {
83
+ const { total } = db
84
+ .prepare("SELECT COUNT(*) AS total FROM mentions WHERE mentioned_nid = ? AND seen_at IS NULL")
85
+ .get(nid);
86
+ if (!total)
87
+ return { rows: [], total: 0 };
88
+ const raw = db
89
+ .prepare(`${SELECT_ROWS} WHERE m.mentioned_nid = ? AND m.seen_at IS NULL ORDER BY m.id DESC LIMIT ?`)
90
+ .all(nid, limit);
91
+ // Newest first out of the query so the cap keeps the most recent; ascending
92
+ // on the way out so the agent reads them in the order they happened.
93
+ return { rows: toRows(raw.reverse(), ownBrane), total };
94
+ }
95
+ /** Every mention of this narrative, capped — regardless of either mark, and
96
+ * moving neither.
97
+ *
98
+ * This is the recovery surface. Delivery is at-most-once (a tool result can
99
+ * be dropped by a socket that closes mid-flight), so "settled" must never
100
+ * mean "unreachable". An agent that suspects it missed something, or that has
101
+ * restarted with no memory of its cursor, reads here. */
102
+ export function allMentions(db, nid, limit, ownBrane) {
103
+ const raw = db
104
+ .prepare(`${SELECT_ROWS} WHERE m.mentioned_nid = ? ORDER BY m.id DESC LIMIT ?`)
105
+ .all(nid, limit);
106
+ return toRows(raw.reverse(), ownBrane);
107
+ }
108
+ /** Records that everything currently owed has been handed over.
109
+ *
110
+ * Covers every pending row, including any the receipt's cap left out —
111
+ * otherwise the uncapped remainder would re-wake the agent the moment it
112
+ * parked again, which is the storm the cap exists to prevent. */
113
+ export function markSent(db, nid, now = Date.now()) {
114
+ db.prepare("UPDATE mentions SET sent_at = ? WHERE mentioned_nid = ? AND sent_at IS NULL").run(now, nid);
115
+ }
116
+ /** Which narratives each of these events mentions.
117
+ *
118
+ * Keyed `"<src>:<seq>"`, and present only for events that mention someone —
119
+ * an event with no mentions has no entry, so annotating a batch adds nothing
120
+ * to the common case. Lets a returned event say what it addresses, right
121
+ * where the agent reads it, rather than only in a separate receipt. */
122
+ export function mentionsInEvents(db, delivered) {
123
+ const out = {};
124
+ const stmt = db.prepare("SELECT src_seq, mentioned_nid FROM mentions WHERE src_narrative = ? AND src_seq = ?");
125
+ for (const [src, seqs] of Object.entries(delivered)) {
126
+ for (const seq of seqs) {
127
+ for (const row of stmt.all(src, seq)) {
128
+ (out[`${src}:${row.src_seq}`] ??= []).push(row.mentioned_nid);
129
+ }
130
+ }
131
+ }
132
+ return out;
133
+ }
134
+ /** Marks mentions read because the agent was handed the very events carrying
135
+ * them — the acknowledgment that cannot be faked.
136
+ *
137
+ * Interpreting a mention means going and looking at its source, and an agent
138
+ * does that by naming the narrative in the cursor it already passes to
139
+ * `listen`/`narrate`:
140
+ *
141
+ * { "after": { "self": 42, "claude-4c7289d1": 17 } }
142
+ *
143
+ * So the evidence and the attention are the same act. An explicit ack tool
144
+ * would be weaker: an agent can call one reflexively without looking, but it
145
+ * cannot receive a narrative's events without them landing in its context.
146
+ *
147
+ * `delivered` maps narrative id → the seqs actually returned, **not** the
148
+ * cursor bound. Those differ: `before` truncates a range, and `tail: N`
149
+ * returns the last N events while skipping everything in between. Settling on
150
+ * `seq > after` would mark mentions read that the read never showed — the
151
+ * silent drop this whole mechanism exists to prevent. Keys arrive already
152
+ * resolved ("self" → the real nid), and the agent's own narrative is skipped:
153
+ * reading your own log is not evidence you looked at someone else's.
154
+ *
155
+ * Returns how many were acknowledged, for logging. */
156
+ export function ackMentionsByRead(db, nid, delivered, now = Date.now()) {
157
+ const stmt = db.prepare(`UPDATE mentions SET seen_at = ?
158
+ WHERE mentioned_nid = ? AND src_narrative = ? AND src_seq = ? AND seen_at IS NULL`);
159
+ let count = 0;
160
+ for (const [src, seqs] of Object.entries(delivered)) {
161
+ if (src === nid)
162
+ continue;
163
+ for (const seq of seqs)
164
+ count += stmt.run(now, nid, src, seq).changes;
165
+ }
166
+ return count;
167
+ }
168
+ /** The explicit escape hatch: "I've seen these and I am not going to read
169
+ * those narratives."
170
+ *
171
+ * Without it, a mention an agent has rightly judged irrelevant reappears in
172
+ * every receipt forever. Deliberately a parameter on `listen` rather than a
173
+ * fourth tool — one call, no new vocabulary. */
174
+ export function dismissMentions(db, nid, now = Date.now()) {
175
+ return db
176
+ .prepare("UPDATE mentions SET seen_at = ? WHERE mentioned_nid = ? AND seen_at IS NULL")
177
+ .run(now, nid).changes;
178
+ }
179
+ //# sourceMappingURL=mentions.js.map