@100xprompt/brain 0.0.0-stage → 0.1.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 (260) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +235 -2
  3. package/package.json +48 -4
  4. package/src/adapters/aider/index.ts +8 -0
  5. package/src/adapters/aider/reader.ts +129 -0
  6. package/src/adapters/aider/records.ts +220 -0
  7. package/src/adapters/aider/writer.ts +269 -0
  8. package/src/adapters/amazon-q-cli/index.ts +8 -0
  9. package/src/adapters/amazon-q-cli/reader.ts +84 -0
  10. package/src/adapters/amazon-q-cli/records.ts +191 -0
  11. package/src/adapters/amazon-q-cli/writer.ts +370 -0
  12. package/src/adapters/amazon-q-ide/index.ts +9 -0
  13. package/src/adapters/amazon-q-ide/reader.ts +131 -0
  14. package/src/adapters/amazon-q-ide/records.ts +141 -0
  15. package/src/adapters/amazon-q-ide/writer.ts +325 -0
  16. package/src/adapters/antigravity/index.ts +9 -0
  17. package/src/adapters/antigravity/proto.ts +194 -0
  18. package/src/adapters/antigravity/reader.ts +424 -0
  19. package/src/adapters/antigravity/records.ts +236 -0
  20. package/src/adapters/antigravity/writer.ts +463 -0
  21. package/src/adapters/auggie/index.ts +36 -0
  22. package/src/adapters/claude-code/index.ts +8 -0
  23. package/src/adapters/claude-code/reader.ts +639 -0
  24. package/src/adapters/claude-code/records.ts +88 -0
  25. package/src/adapters/claude-code/writer.ts +329 -0
  26. package/src/adapters/cline/index.ts +12 -0
  27. package/src/adapters/cline/reader.ts +432 -0
  28. package/src/adapters/cline/records.ts +151 -0
  29. package/src/adapters/cline/vscdb.ts +56 -0
  30. package/src/adapters/cline/writer.ts +656 -0
  31. package/src/adapters/cline/xml.ts +177 -0
  32. package/src/adapters/cline4/index.ts +9 -0
  33. package/src/adapters/cline4/reader.ts +179 -0
  34. package/src/adapters/cline4/records.ts +92 -0
  35. package/src/adapters/cline4/writer.ts +361 -0
  36. package/src/adapters/codebuff/index.ts +8 -0
  37. package/src/adapters/codebuff/project.ts +207 -0
  38. package/src/adapters/codebuff/reader.ts +215 -0
  39. package/src/adapters/codebuff/records.ts +173 -0
  40. package/src/adapters/codebuff/writer.ts +383 -0
  41. package/src/adapters/codex/index.ts +8 -0
  42. package/src/adapters/codex/native.ts +239 -0
  43. package/src/adapters/codex/reader.ts +747 -0
  44. package/src/adapters/codex/records.ts +114 -0
  45. package/src/adapters/codex/writer.ts +479 -0
  46. package/src/adapters/continue/index.ts +9 -0
  47. package/src/adapters/continue/reader.ts +68 -0
  48. package/src/adapters/continue/records.ts +418 -0
  49. package/src/adapters/continue/writer.ts +162 -0
  50. package/src/adapters/copilot-cli/index.ts +9 -0
  51. package/src/adapters/copilot-cli/reader.ts +186 -0
  52. package/src/adapters/copilot-cli/records.ts +241 -0
  53. package/src/adapters/copilot-cli/writer.ts +353 -0
  54. package/src/adapters/crush/index.ts +8 -0
  55. package/src/adapters/crush/reader.ts +180 -0
  56. package/src/adapters/crush/records.ts +169 -0
  57. package/src/adapters/crush/writer.ts +333 -0
  58. package/src/adapters/cursor-cli/index.ts +405 -0
  59. package/src/adapters/cursor-ide/agentkv.ts +95 -0
  60. package/src/adapters/cursor-ide/index.ts +501 -0
  61. package/src/adapters/cursor-ide/transcripts.ts +122 -0
  62. package/src/adapters/cursor-ide/workspaces.ts +119 -0
  63. package/src/adapters/cursor-ide/writer.ts +170 -0
  64. package/src/adapters/devin/index.ts +8 -0
  65. package/src/adapters/devin/reader.ts +305 -0
  66. package/src/adapters/devin/records.ts +277 -0
  67. package/src/adapters/devin/writer.ts +523 -0
  68. package/src/adapters/factory-droid/index.ts +12 -0
  69. package/src/adapters/factory-droid/reader.ts +184 -0
  70. package/src/adapters/factory-droid/records.ts +79 -0
  71. package/src/adapters/factory-droid/writer.ts +350 -0
  72. package/src/adapters/gemini-cli/exchanges.ts +228 -0
  73. package/src/adapters/gemini-cli/index.ts +396 -0
  74. package/src/adapters/gemini-cli/records.ts +521 -0
  75. package/src/adapters/gemini-cli/writer.ts +354 -0
  76. package/src/adapters/goose/index.ts +8 -0
  77. package/src/adapters/goose/reader.ts +138 -0
  78. package/src/adapters/goose/records.ts +199 -0
  79. package/src/adapters/goose/writer.ts +340 -0
  80. package/src/adapters/grok-cli/image.ts +44 -0
  81. package/src/adapters/grok-cli/index.ts +8 -0
  82. package/src/adapters/grok-cli/reader.ts +302 -0
  83. package/src/adapters/grok-cli/records.ts +172 -0
  84. package/src/adapters/grok-cli/writer.ts +329 -0
  85. package/src/adapters/hermes/index.ts +8 -0
  86. package/src/adapters/hermes/reader.ts +249 -0
  87. package/src/adapters/hermes/records.ts +285 -0
  88. package/src/adapters/hermes/writer.ts +452 -0
  89. package/src/adapters/iflow-cli/index.ts +72 -0
  90. package/src/adapters/iflow-cli/records.ts +215 -0
  91. package/src/adapters/iflow-cli/writer.ts +204 -0
  92. package/src/adapters/index.ts +112 -0
  93. package/src/adapters/kilo-cli/index.ts +21 -0
  94. package/src/adapters/kilo-code/index.ts +11 -0
  95. package/src/adapters/kilo-code/writer.ts +92 -0
  96. package/src/adapters/kimi/index.ts +9 -0
  97. package/src/adapters/kimi/reader.ts +92 -0
  98. package/src/adapters/kimi/records.ts +221 -0
  99. package/src/adapters/kimi/writer.ts +367 -0
  100. package/src/adapters/kiro-cli/index.ts +8 -0
  101. package/src/adapters/kiro-cli/reader.ts +87 -0
  102. package/src/adapters/kiro-cli/records.ts +260 -0
  103. package/src/adapters/kiro-cli/writer.ts +305 -0
  104. package/src/adapters/kiro-ide/index.ts +8 -0
  105. package/src/adapters/kiro-ide/writer.ts +52 -0
  106. package/src/adapters/letta/index.ts +8 -0
  107. package/src/adapters/letta/reader.ts +177 -0
  108. package/src/adapters/letta/records.ts +119 -0
  109. package/src/adapters/letta/writer.ts +413 -0
  110. package/src/adapters/mastracode/index.ts +8 -0
  111. package/src/adapters/mastracode/reader.ts +323 -0
  112. package/src/adapters/mastracode/records.ts +336 -0
  113. package/src/adapters/mastracode/writer.ts +385 -0
  114. package/src/adapters/mistral-vibe/index.ts +9 -0
  115. package/src/adapters/mistral-vibe/reader.ts +62 -0
  116. package/src/adapters/mistral-vibe/records.ts +104 -0
  117. package/src/adapters/mistral-vibe/writer.ts +359 -0
  118. package/src/adapters/muse/index.ts +10 -0
  119. package/src/adapters/muse/reader.ts +332 -0
  120. package/src/adapters/muse/records.ts +104 -0
  121. package/src/adapters/muse/writer.ts +387 -0
  122. package/src/adapters/omp/index.ts +13 -0
  123. package/src/adapters/omp/reader.ts +314 -0
  124. package/src/adapters/omp/records.ts +153 -0
  125. package/src/adapters/omp/writer.ts +216 -0
  126. package/src/adapters/opencode/ids.ts +52 -0
  127. package/src/adapters/opencode/index.ts +31 -0
  128. package/src/adapters/opencode/note.ts +8 -0
  129. package/src/adapters/opencode/reader.ts +452 -0
  130. package/src/adapters/opencode/tools.ts +174 -0
  131. package/src/adapters/opencode/v2.ts +367 -0
  132. package/src/adapters/opencode/writer.ts +379 -0
  133. package/src/adapters/openhands/index.ts +8 -0
  134. package/src/adapters/openhands/reader.ts +222 -0
  135. package/src/adapters/openhands/records.ts +136 -0
  136. package/src/adapters/openhands/writer.ts +404 -0
  137. package/src/adapters/pearai/index.ts +10 -0
  138. package/src/adapters/pi/index.ts +28 -0
  139. package/src/adapters/pi/reader.ts +261 -0
  140. package/src/adapters/pi/records.ts +99 -0
  141. package/src/adapters/pi/writer.ts +362 -0
  142. package/src/adapters/qoder-cli/index.ts +8 -0
  143. package/src/adapters/qoder-cli/reader.ts +83 -0
  144. package/src/adapters/qoder-cli/records.ts +45 -0
  145. package/src/adapters/qoder-cli/writer.ts +72 -0
  146. package/src/adapters/qwen-code/index.ts +84 -0
  147. package/src/adapters/qwen-code/records.ts +194 -0
  148. package/src/adapters/qwen-code/writer.ts +224 -0
  149. package/src/adapters/tabnine-cli/index.ts +8 -0
  150. package/src/adapters/tabnine-cli/reader.ts +106 -0
  151. package/src/adapters/tabnine-cli/records.ts +33 -0
  152. package/src/adapters/tabnine-cli/writer.ts +156 -0
  153. package/src/adapters/types.ts +73 -0
  154. package/src/adapters/void/index.ts +9 -0
  155. package/src/adapters/void/reader.ts +48 -0
  156. package/src/adapters/void/records.ts +285 -0
  157. package/src/adapters/void/writer.ts +93 -0
  158. package/src/adapters/vscode-copilot/index.ts +9 -0
  159. package/src/adapters/vscode-copilot/reader.ts +212 -0
  160. package/src/adapters/vscode-copilot/records.ts +388 -0
  161. package/src/adapters/vscode-copilot/shared.ts +68 -0
  162. package/src/adapters/vscode-copilot/writer.ts +546 -0
  163. package/src/adapters/zed/index.ts +8 -0
  164. package/src/adapters/zed/reader.ts +198 -0
  165. package/src/adapters/zed/records.ts +87 -0
  166. package/src/adapters/zed/writer.ts +341 -0
  167. package/src/brief-writers.ts +71 -0
  168. package/src/brief.ts +238 -0
  169. package/src/cli.ts +351 -0
  170. package/src/dashboard/cli.ts +148 -0
  171. package/src/dashboard/conversions.ts +170 -0
  172. package/src/dashboard/report.ts +261 -0
  173. package/src/dashboard/send.ts +213 -0
  174. package/src/dashboard/state.ts +95 -0
  175. package/src/fsutil.ts +133 -0
  176. package/src/gateway/common.ts +232 -0
  177. package/src/gateway/connect.ts +847 -0
  178. package/src/gateway/connectors/aider.ts +356 -0
  179. package/src/gateway/connectors/amazon-q-cli.ts +6 -0
  180. package/src/gateway/connectors/amazon-q-ide.ts +6 -0
  181. package/src/gateway/connectors/antigravity.ts +6 -0
  182. package/src/gateway/connectors/auggie.ts +6 -0
  183. package/src/gateway/connectors/cline-sdk.ts +85 -0
  184. package/src/gateway/connectors/cline.ts +34 -0
  185. package/src/gateway/connectors/codebuff.ts +6 -0
  186. package/src/gateway/connectors/continue.ts +579 -0
  187. package/src/gateway/connectors/copilot-cli.ts +138 -0
  188. package/src/gateway/connectors/crush.ts +313 -0
  189. package/src/gateway/connectors/cursor-cli.ts +6 -0
  190. package/src/gateway/connectors/cursor.ts +6 -0
  191. package/src/gateway/connectors/devin.ts +6 -0
  192. package/src/gateway/connectors/factory-droid.ts +308 -0
  193. package/src/gateway/connectors/goose.ts +79 -0
  194. package/src/gateway/connectors/grok-cli.ts +82 -0
  195. package/src/gateway/connectors/hermes.ts +230 -0
  196. package/src/gateway/connectors/iflow-cli.ts +50 -0
  197. package/src/gateway/connectors/index.ts +86 -0
  198. package/src/gateway/connectors/kilo-cli.ts +434 -0
  199. package/src/gateway/connectors/kilo-code.ts +32 -0
  200. package/src/gateway/connectors/kimi-code.ts +241 -0
  201. package/src/gateway/connectors/kiro-cli.ts +6 -0
  202. package/src/gateway/connectors/kiro-ide.ts +6 -0
  203. package/src/gateway/connectors/letta-code.ts +170 -0
  204. package/src/gateway/connectors/mastracode.ts +91 -0
  205. package/src/gateway/connectors/mistral-vibe.ts +495 -0
  206. package/src/gateway/connectors/muse.ts +27 -0
  207. package/src/gateway/connectors/omp.ts +279 -0
  208. package/src/gateway/connectors/openhands.ts +296 -0
  209. package/src/gateway/connectors/pearai.ts +52 -0
  210. package/src/gateway/connectors/pi.ts +357 -0
  211. package/src/gateway/connectors/qoder-cli.ts +6 -0
  212. package/src/gateway/connectors/qwen-code.ts +411 -0
  213. package/src/gateway/connectors/roo.ts +43 -0
  214. package/src/gateway/connectors/tabnine-cli.ts +6 -0
  215. package/src/gateway/connectors/void.ts +6 -0
  216. package/src/gateway/connectors/vscode-copilot.ts +286 -0
  217. package/src/gateway/connectors/windsurf.ts +6 -0
  218. package/src/gateway/connectors/zed.ts +29 -0
  219. package/src/gateway/signin.ts +323 -0
  220. package/src/handoff.ts +104 -0
  221. package/src/hooks.ts +455 -0
  222. package/src/memory/audit.ts +514 -0
  223. package/src/memory/blobs.ts +94 -0
  224. package/src/memory/cli.ts +117 -0
  225. package/src/memory/distill.ts +381 -0
  226. package/src/memory/externalize.ts +435 -0
  227. package/src/memory/lookups.ts +70 -0
  228. package/src/memory/mcp-install.ts +401 -0
  229. package/src/memory/mcp-server.ts +296 -0
  230. package/src/memory/prepare.ts +179 -0
  231. package/src/memory/preview.ts +154 -0
  232. package/src/memory/project.ts +35 -0
  233. package/src/memory/redact.ts +72 -0
  234. package/src/memory/relevance.ts +187 -0
  235. package/src/memory/repomap.ts +516 -0
  236. package/src/memory/store.ts +270 -0
  237. package/src/memory/sync.ts +166 -0
  238. package/src/memory/tidy.ts +193 -0
  239. package/src/memory/types.ts +118 -0
  240. package/src/org/admin-ops.ts +96 -0
  241. package/src/org/auth.ts +112 -0
  242. package/src/org/backup.ts +115 -0
  243. package/src/org/cli.ts +438 -0
  244. package/src/org/client.ts +404 -0
  245. package/src/org/config.ts +44 -0
  246. package/src/org/crypto.ts +537 -0
  247. package/src/org/db.ts +435 -0
  248. package/src/org/graph.ts +318 -0
  249. package/src/org/oidc.ts +435 -0
  250. package/src/org/policy.ts +398 -0
  251. package/src/org/server.ts +920 -0
  252. package/src/org/sso.ts +316 -0
  253. package/src/org/tls.ts +66 -0
  254. package/src/org/types.ts +193 -0
  255. package/src/self.ts +68 -0
  256. package/src/session/attachments.ts +53 -0
  257. package/src/session/peekcache.ts +50 -0
  258. package/src/session/types.ts +104 -0
  259. package/src/session/util.ts +130 -0
  260. package/src/sqlite.ts +48 -0
@@ -0,0 +1,194 @@
1
+ // A small, bounded protobuf wire codec: enough to read and write the few Antigravity messages brain needs,
2
+ // with no generated classes and no descriptors. It takes varints (canonical, at most 64 bits), fixed64,
3
+ // length-delimited values and fixed32; it rejects groups and unknown wire types, caps every length and the
4
+ // number of fields in one message, and never recurses on its own (callers decode nested messages they know).
5
+ // Ported from the wire subset session-migrate (MIT, xhluca) pins for Antigravity CLI 1.1.16.
6
+
7
+ export const WIRE = { varint: 0, fixed64: 1, bytes: 2, fixed32: 5 } as const
8
+
9
+ export interface Field {
10
+ no: number
11
+ wire: 0 | 1 | 2 | 5
12
+ /** A varint as a number when it is a safe integer, else a bigint; raw bytes for the other wire types. */
13
+ value: number | bigint | Uint8Array
14
+ }
15
+
16
+ export interface Limits {
17
+ /** Longest length-delimited value, and longest message. */
18
+ maxBytes: number
19
+ /** Most fields in one message. */
20
+ maxFields: number
21
+ }
22
+
23
+ export const LIMITS: Limits = { maxBytes: 64 * 1024 * 1024, maxFields: 100_000 }
24
+
25
+ export class ProtoError extends Error {
26
+ constructor(why: string) {
27
+ super(`protobuf: ${why}`)
28
+ }
29
+ }
30
+
31
+ const MAX_FIELD_NO = 536_870_911
32
+
33
+ /** A varint at `at`: its value and where the next byte is. Rejects truncated, over-long and non-canonical encodings. */
34
+ function readVarint(b: Uint8Array, at: number): [bigint, number] {
35
+ let v = 0n
36
+ for (let i = 0; i < 10; i++) {
37
+ if (at >= b.length) throw new ProtoError("varint is truncated")
38
+ const byte = b[at++]!
39
+ if (i === 9 && byte > 1) throw new ProtoError("varint exceeds 64 bits")
40
+ v |= BigInt(byte & 0x7f) << BigInt(7 * i)
41
+ if (byte < 0x80) {
42
+ // Canonical: no trailing zero byte (0x80 0x00 is 0 written long).
43
+ if (i > 0 && byte === 0) throw new ProtoError("varint is not canonical")
44
+ return [v, at]
45
+ }
46
+ }
47
+ throw new ProtoError("varint exceeds 64 bits")
48
+ }
49
+
50
+ const small = (v: bigint): number | bigint => (v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v)
51
+
52
+ /** The fields of one message, in wire order. Throws ProtoError on anything outside the subset or the limits. */
53
+ export function decode(b: Uint8Array, limits: Limits = LIMITS): Field[] {
54
+ if (b.length > limits.maxBytes) throw new ProtoError("message exceeds the size limit")
55
+ const out: Field[] = []
56
+ let at = 0
57
+ while (at < b.length) {
58
+ if (out.length >= limits.maxFields) throw new ProtoError("message has too many fields")
59
+ const [key, next] = readVarint(b, at)
60
+ at = next
61
+ const no = Number(key >> 3n)
62
+ const wire = Number(key & 7n)
63
+ if (no <= 0 || no > MAX_FIELD_NO) throw new ProtoError(`invalid field number ${no}`)
64
+ switch (wire) {
65
+ case 0: {
66
+ const [v, n] = readVarint(b, at)
67
+ at = n
68
+ out.push({ no, wire: 0, value: small(v) })
69
+ break
70
+ }
71
+ case 1:
72
+ case 5: {
73
+ const size = wire === 1 ? 8 : 4
74
+ if (at + size > b.length) throw new ProtoError(`fixed${size * 8} value is truncated`)
75
+ out.push({ no, wire, value: b.slice(at, at + size) })
76
+ at += size
77
+ break
78
+ }
79
+ case 2: {
80
+ const [len, n] = readVarint(b, at)
81
+ at = n
82
+ if (len > BigInt(limits.maxBytes)) throw new ProtoError("value exceeds the size limit")
83
+ const end = at + Number(len)
84
+ if (end > b.length) throw new ProtoError("length-delimited value is truncated")
85
+ out.push({ no, wire: 2, value: b.slice(at, end) })
86
+ at = end
87
+ break
88
+ }
89
+ case 3:
90
+ case 4:
91
+ throw new ProtoError("groups are not supported")
92
+ default:
93
+ throw new ProtoError(`unknown wire type ${wire}`)
94
+ }
95
+ }
96
+ return out
97
+ }
98
+
99
+ // Encoding.
100
+
101
+ function varintBytes(v: number | bigint): Uint8Array {
102
+ if (typeof v === "number" && (!Number.isSafeInteger(v) || v < 0)) throw new ProtoError("cannot encode a negative, fractional or unsafe varint")
103
+ let n = typeof v === "bigint" ? v : BigInt(v)
104
+ if (n < 0n || n >= 1n << 64n) throw new ProtoError("varint out of range")
105
+ const out: number[] = []
106
+ while (n >= 0x80n) {
107
+ out.push(Number(n & 0x7fn) | 0x80)
108
+ n >>= 7n
109
+ }
110
+ out.push(Number(n))
111
+ return Uint8Array.from(out)
112
+ }
113
+
114
+ export function concat(parts: Uint8Array[]): Uint8Array {
115
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0))
116
+ let at = 0
117
+ for (const p of parts) {
118
+ out.set(p, at)
119
+ at += p.length
120
+ }
121
+ return out
122
+ }
123
+
124
+ const key = (no: number, wire: number) => {
125
+ if (!Number.isInteger(no) || no <= 0 || no > MAX_FIELD_NO) throw new ProtoError(`invalid field number ${no}`)
126
+ return varintBytes(BigInt(no) * 8n + BigInt(wire))
127
+ }
128
+
129
+ export const varintField = (no: number, v: number | bigint) => concat([key(no, 0), varintBytes(v)])
130
+
131
+ export function bytesField(no: number, v: Uint8Array, limits: Limits = LIMITS): Uint8Array {
132
+ if (v.length > limits.maxBytes) throw new ProtoError("value exceeds the size limit")
133
+ return concat([key(no, 2), varintBytes(v.length), v])
134
+ }
135
+
136
+ export const textField = (no: number, s: string) => bytesField(no, new TextEncoder().encode(s))
137
+
138
+ /** A nested message as a length-delimited field. */
139
+ export const messageField = (no: number, parts: Uint8Array[]) => bytesField(no, concat(parts))
140
+
141
+ export function fixedField(no: number, v: Uint8Array): Uint8Array {
142
+ if (v.length !== 4 && v.length !== 8) throw new ProtoError("a fixed value is 4 or 8 bytes")
143
+ return concat([key(no, v.length === 8 ? 1 : 5), v])
144
+ }
145
+
146
+ /** Decoded fields back to bytes: encode(decode(b)) is b for any canonical message. */
147
+ export function encode(fields: Field[]): Uint8Array {
148
+ return concat(
149
+ fields.map((f) => {
150
+ if (f.wire === 0) return varintField(f.no, f.value as number | bigint)
151
+ if (f.wire === 2) return bytesField(f.no, f.value as Uint8Array)
152
+ return fixedField(f.no, f.value as Uint8Array)
153
+ }),
154
+ )
155
+ }
156
+
157
+ // Reading fields. Protobuf lets a singular field repeat (the last one wins), so these take the last.
158
+
159
+ const last = (fields: Field[], no: number, wire: number) => {
160
+ for (let i = fields.length - 1; i >= 0; i--) if (fields[i]!.no === no && fields[i]!.wire === wire) return fields[i]!
161
+ return undefined
162
+ }
163
+
164
+ /** Every length-delimited value of a repeated field, in order. */
165
+ export const allBytes = (fields: Field[], no: number): Uint8Array[] => fields.filter((f) => f.no === no && f.wire === 2).map((f) => f.value as Uint8Array)
166
+
167
+ export const bytesOf = (fields: Field[], no: number): Uint8Array | undefined => last(fields, no, 2)?.value as Uint8Array | undefined
168
+
169
+ export function numberOf(fields: Field[], no: number): number | undefined {
170
+ const v = last(fields, no, 0)?.value
171
+ return v === undefined ? undefined : Number(v)
172
+ }
173
+
174
+ const utf8 = new TextDecoder("utf-8", { fatal: true })
175
+
176
+ /** A value as UTF-8 text; undefined when absent or not valid UTF-8. */
177
+ export function textOf(fields: Field[], no: number): string | undefined {
178
+ const b = bytesOf(fields, no)
179
+ return b === undefined ? undefined : asText(b)
180
+ }
181
+
182
+ export function asText(b: Uint8Array): string | undefined {
183
+ try {
184
+ return utf8.decode(b)
185
+ } catch {
186
+ return undefined
187
+ }
188
+ }
189
+
190
+ /** A nested message, decoded; undefined when absent. */
191
+ export function messageOf(fields: Field[], no: number, limits: Limits = LIMITS): Field[] | undefined {
192
+ const b = bytesOf(fields, no)
193
+ return b === undefined ? undefined : decode(b, limits)
194
+ }
@@ -0,0 +1,424 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import { statSync } from "node:fs"
3
+ import { basename, dirname, join } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { exists, gitInfo, ls, mtime } from "../../fsutil.ts"
6
+ import { withSnapshot } from "../../sqlite.ts"
7
+ import type { Session, SessionHeader, Turn } from "../../session/types.ts"
8
+ import { clip, header, lossCounter } from "../../session/util.ts"
9
+ import { CONTINUED_NOTE } from "../claude-code/records.ts"
10
+ import type { Reader, SessionRef } from "../types.ts"
11
+ import { allBytes, bytesOf, decode, numberOf, textOf, type Field } from "./proto.ts"
12
+ import {
13
+ CONVERSATION_FILE,
14
+ LABEL,
15
+ LINK_KEY,
16
+ MAX_DB_BYTES,
17
+ MAX_STEPS,
18
+ PAYLOAD_FIELD,
19
+ STATUS,
20
+ STEP,
21
+ STORES,
22
+ TOOL,
23
+ antigravityToolKind,
24
+ conversationsDir,
25
+ decodeCall,
26
+ entriesOf,
27
+ hasThinking,
28
+ sqlTime,
29
+ summariesPath,
30
+ timestampOf,
31
+ workspaceOf,
32
+ type NativeCall,
33
+ } from "./records.ts"
34
+
35
+ // Reads Antigravity conversations (records.ts) from the CLI's store and the IDE's two. Each conversation
36
+ // database is read from a copy with its write-ahead log, so a live one reads consistently; the summaries
37
+ // database beside it gives the title, folder and time, reading only the columns it needs (the IDE's table has
38
+ // more). The projection follows session-migrate's: user inputs are user turns, planner responses are replies
39
+ // and tool calls, generic tool steps and the run_command, view_file, shell_exec and MCP steps are results.
40
+ // Private thinking never becomes a turn: it is only counted. Every other step type is counted by number.
41
+
42
+ interface Summary {
43
+ title?: string
44
+ cwd?: string
45
+ updatedAt?: string
46
+ parent?: string
47
+ }
48
+
49
+ /** A local folder from a file:// URI; undefined for anything else. */
50
+ function pathOfUri(uri: unknown): string | undefined {
51
+ if (typeof uri !== "string" || !uri.startsWith("file://")) return undefined
52
+ try {
53
+ const u = new URL(uri)
54
+ return u.host ? undefined : fileURLToPath(u)
55
+ } catch {
56
+ return undefined
57
+ }
58
+ }
59
+
60
+ const WANTED = ["conversation_id", "title", "workspace_uris", "last_modified_time", "parent_conversation_id"]
61
+
62
+ /** Summary rows by conversation id, from whichever of the wanted columns the table has. */
63
+ function summaryRows(d: Database, id?: string): Map<string, Summary> {
64
+ const out = new Map<string, Summary>()
65
+ const cols = new Set((d.query("PRAGMA table_info(conversation_summaries)").all() as Array<{ name: string }>).map((c) => c.name))
66
+ if (!cols.has("conversation_id")) return out
67
+ const sql = `SELECT ${WANTED.filter((c) => cols.has(c))
68
+ .map((c) => `"${c}"`)
69
+ .join(", ")} FROM conversation_summaries${id ? " WHERE conversation_id = ?" : ""}`
70
+ const rows = (id ? d.query(sql).all(id) : d.query(sql).all()) as Array<Record<string, unknown>>
71
+ for (const r of rows) {
72
+ let cwd: string | undefined
73
+ try {
74
+ const uris = JSON.parse(String(r.workspace_uris ?? ""))
75
+ if (Array.isArray(uris)) cwd = pathOfUri(uris[0])
76
+ } catch {
77
+ // no folder
78
+ }
79
+ out.set(String(r.conversation_id), {
80
+ ...(typeof r.title === "string" && r.title.trim() ? { title: r.title } : {}),
81
+ ...(cwd ? { cwd } : {}),
82
+ ...(sqlTime(r.last_modified_time) ? { updatedAt: sqlTime(r.last_modified_time)! } : {}),
83
+ ...(typeof r.parent_conversation_id === "string" && r.parent_conversation_id ? { parent: r.parent_conversation_id } : {}),
84
+ })
85
+ }
86
+ return out
87
+ }
88
+
89
+ /** A store's summaries, read from a copy; none when the database is missing or unreadable. */
90
+ function readSummaries(path: string, id?: string): Map<string, Summary> {
91
+ if (!exists(path)) return new Map()
92
+ try {
93
+ return withSnapshot(path, (d) => summaryRows(d, id))
94
+ } catch {
95
+ return new Map()
96
+ }
97
+ }
98
+
99
+ export const antigravityReader: Reader = {
100
+ tool: TOOL,
101
+ label: LABEL,
102
+ async list(home) {
103
+ const found = new Map<string, SessionRef>()
104
+ for (const store of STORES) {
105
+ const dir = conversationsDir(home, store)
106
+ // Only the SQLite conversations: the IDE's <uuid>.pb files are encrypted.
107
+ const files = ls(dir).filter((n) => CONVERSATION_FILE.test(n))
108
+ if (!files.length) continue
109
+ const sums = readSummaries(summariesPath(home, store))
110
+ for (const name of files) {
111
+ const id = name.slice(0, -3)
112
+ const path = join(dir, name)
113
+ const s = sums.get(id)
114
+ const updatedAt = s?.updatedAt ?? mtime(path)
115
+ const ref: SessionRef = {
116
+ tool: TOOL,
117
+ sourceId: id,
118
+ path,
119
+ ...(s?.title ? { title: s.title } : {}),
120
+ ...(s?.cwd ? { cwd: s.cwd } : {}),
121
+ ...(updatedAt ? { updatedAt } : {}),
122
+ extra: { store },
123
+ }
124
+ // The same conversation in two stores: the newer copy.
125
+ const prev = found.get(id)
126
+ if (!prev || (ref.updatedAt ?? "") > (prev.updatedAt ?? "")) found.set(id, ref)
127
+ }
128
+ }
129
+ return [...found.values()].sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))
130
+ },
131
+ async read(ref) {
132
+ if (!exists(ref.path)) throw new Error(`Antigravity conversation not found: ${ref.path}`)
133
+ if (statSync(ref.path).size > MAX_DB_BYTES) throw new Error(`Antigravity conversation is larger than ${MAX_DB_BYTES} bytes: ${ref.path}`)
134
+ const id = basename(ref.path).replace(/\.db$/i, "")
135
+ const s = readSummaries(join(dirname(dirname(ref.path)), "conversation_summaries.db"), id).get(id)
136
+ const title = s?.title ?? ref.title
137
+ const cwd = s?.cwd ?? ref.cwd
138
+ const updatedAt = s?.updatedAt ?? ref.updatedAt
139
+ return withSnapshot(ref.path, (d) =>
140
+ readConversation(d, id, { path: ref.path, ...(title ? { title } : {}), ...(cwd ? { cwd } : {}), ...(updatedAt ? { updatedAt } : {}), ...(s?.parent ? { parent: s.parent } : {}) }),
141
+ )
142
+ },
143
+ }
144
+
145
+ export interface Where {
146
+ path: string
147
+ title?: string
148
+ cwd?: string
149
+ updatedAt?: string
150
+ /** The conversation this one was spawned from, per the summaries table. */
151
+ parent?: string
152
+ }
153
+
154
+ const stamp = (at: string | undefined) => (at ? { at } : {})
155
+
156
+ /** A nested message, or undefined when it is absent or not a message. */
157
+ function tryMessage(fields: Field[] | undefined, no: number): Field[] | undefined {
158
+ const b = fields && bytesOf(fields, no)
159
+ if (!b) return undefined
160
+ try {
161
+ return decode(b)
162
+ } catch {
163
+ return undefined
164
+ }
165
+ }
166
+
167
+ function tryCall(b: Uint8Array | undefined): NativeCall | undefined {
168
+ if (!b) return undefined
169
+ try {
170
+ return decodeCall(b)
171
+ } catch {
172
+ return undefined
173
+ }
174
+ }
175
+
176
+ /** Step types read as tool results by `builtin`. */
177
+ const BUILTIN: Record<number, true> = { [STEP.runCommand]: true, [STEP.viewFile]: true, [STEP.shellExec]: true }
178
+
179
+ /** A text from a built-in tool step: what it was given and what it returned. */
180
+ function builtin(type: number, body: Field[]): { name: string; input: Record<string, unknown>; output?: string } {
181
+ if (type === STEP.runCommand) {
182
+ const command = textOf(body, 23) || textOf(body, 25)
183
+ const out = tryMessage(body, 21) ?? tryMessage(body, 26)
184
+ const output = (out && textOf(out, 1)) || textOf(body, 4) || textOf(body, 5)
185
+ return { name: "run_command", input: command ? { command } : {}, ...(output !== undefined ? { output } : {}) }
186
+ }
187
+ if (type === STEP.viewFile) {
188
+ const path = textOf(body, 1)
189
+ const output = textOf(body, 4) || textOf(body, 9)
190
+ return { name: "view_file", input: path ? { AbsolutePath: path } : {}, ...(output !== undefined ? { output } : {}) }
191
+ }
192
+ const command = textOf(body, 1)
193
+ const output = textOf(body, 3) || textOf(body, 4)
194
+ return { name: "shell_exec", input: command ? { command } : {}, ...(output !== undefined ? { output } : {}) }
195
+ }
196
+
197
+ /** One conversation from an open database (a snapshot, or the writer's own before it is published). */
198
+ export function readConversation(d: Database, id: string, where: Where): Session {
199
+ const tables = new Set((d.query("SELECT name FROM sqlite_master WHERE type = 'table'").all() as Array<{ name: string }>).map((r) => r.name))
200
+ if (!tables.has("steps")) throw new Error(`not an Antigravity conversation database (no steps table): ${where.path}`)
201
+ const count = (d.query("SELECT count(*) AS n FROM steps").get() as { n: number }).n
202
+ if (count > MAX_STEPS) throw new Error(`Antigravity conversation has ${count} steps, more than ${MAX_STEPS}: ${where.path}`)
203
+
204
+ const lost = lossCounter()
205
+ const turns: Turn[] = []
206
+
207
+ // Subagent and battle trajectories are stored beside the conversation's own and are not read.
208
+ if (tables.has("trajectory_meta")) {
209
+ const n = (d.query("SELECT count(*) AS n FROM trajectory_meta").get() as { n: number }).n
210
+ if (n > 1) lost.add("auxiliary trajectories (subagents, battles)", n - 1)
211
+ }
212
+ if (tables.has("battle_mode_infos")) {
213
+ const n = (d.query("SELECT count(*) AS n FROM battle_mode_infos").get() as { n: number }).n
214
+ if (n) lost.add("battle mode records", n)
215
+ }
216
+ let startedAt: string | undefined
217
+ let metaCwd: string | undefined
218
+ if (tables.has("trajectory_metadata_blob")) {
219
+ const row = d.query("SELECT data FROM trajectory_metadata_blob WHERE id = 'main'").get() as { data: unknown } | null
220
+ if (row?.data instanceof Uint8Array)
221
+ try {
222
+ const m = decode(row.data)
223
+ startedAt = timestampOf(tryMessage(m, 2))
224
+ metaCwd = pathOfUri(workspaceOf(m))
225
+ } catch {
226
+ // Metadata that does not decode only costs the start time and folder.
227
+ }
228
+ }
229
+
230
+ // Calls waiting for their result: the id the store gave them, and the id they have here.
231
+ const pending: Array<{ native: string; id: string }> = []
232
+ const used = new Set<string>()
233
+ let continued: string | undefined
234
+ let firstAt: string | undefined
235
+ let lastAt: string | undefined
236
+
237
+ const parseArgs = (args: string): unknown => {
238
+ if (!args.trim()) return {}
239
+ try {
240
+ return JSON.parse(args)
241
+ } catch {
242
+ lost.add("unreadable tool arguments")
243
+ return { arguments: args }
244
+ }
245
+ }
246
+ const call = (at: string | undefined, native: string, name: string, input: unknown, kind = antigravityToolKind(name)) => {
247
+ let callId = native
248
+ for (let k = 2; !callId || used.has(callId); k++) callId = `${native || "call"}#${k}`
249
+ used.add(callId)
250
+ turns.push({ kind: "tool_call", ...stamp(at), id: callId, name, toolKind: kind, input })
251
+ return callId
252
+ }
253
+ /** The pending call a result answers: the linked one when the step names it, else the oldest. */
254
+ const take = (link: string | undefined) => {
255
+ if (link === undefined) return pending.shift()
256
+ const i = pending.findIndex((p) => p.native === link)
257
+ return i >= 0 ? pending.splice(i, 1)[0] : undefined
258
+ }
259
+ const result = (at: string | undefined, link: Partial<NativeCall> | undefined, fallback: { name: string; input: unknown }, ok: boolean, output: string) => {
260
+ let callId = take(link?.id)?.id
261
+ if (!callId) {
262
+ // A result whose call was not recorded (or not readable): the call is rebuilt from what the step says.
263
+ callId = call(at, link?.id ?? "", link?.name ?? fallback.name, link?.args !== undefined ? parseArgs(link.args) : fallback.input)
264
+ lost.add("tool calls rebuilt from their results")
265
+ }
266
+ const c = clip(output)
267
+ turns.push({ kind: "tool_result", ...stamp(at), callId, ok, output: c.text, ...(c.truncated ? { truncated: true } : {}) })
268
+ }
269
+
270
+ const rows = d.query("SELECT idx, step_type, status, has_subtrajectory, metadata, step_payload, step_format FROM steps ORDER BY idx").all() as Array<Record<string, unknown>>
271
+ for (const r of rows) {
272
+ const type = Number(r.step_type)
273
+ const status = Number(r.status)
274
+ if (Number(r.step_format ?? 0) !== 0) {
275
+ lost.add("Antigravity steps in an unknown encoding")
276
+ continue
277
+ }
278
+ if (!(r.step_payload instanceof Uint8Array)) {
279
+ lost.add("unreadable Antigravity steps")
280
+ continue
281
+ }
282
+ let outer: Field[]
283
+ let body: Field[] | undefined
284
+ try {
285
+ outer = decode(r.step_payload)
286
+ // The row and the serialised step say the same type and status, or the step is not trusted.
287
+ const t = numberOf(outer, 1)
288
+ const s = numberOf(outer, 4)
289
+ if ((t !== undefined && t !== type) || (s !== undefined && s !== status)) throw new Error("step row and payload disagree")
290
+ const field = PAYLOAD_FIELD[type]
291
+ const b = field === undefined ? undefined : bytesOf(outer, field)
292
+ body = b ? decode(b) : undefined
293
+ } catch {
294
+ lost.add("unreadable Antigravity steps")
295
+ continue
296
+ }
297
+ if (Number(r.has_subtrajectory) > 0) lost.add("subtrajectories under steps")
298
+ let meta: Field[] | undefined
299
+ try {
300
+ meta = r.metadata instanceof Uint8Array ? decode(r.metadata) : tryMessage(outer, 5)
301
+ } catch {
302
+ meta = undefined
303
+ }
304
+ const at = timestampOf(tryMessage(meta, 1))
305
+ if (at) {
306
+ firstAt ??= at
307
+ lastAt = at
308
+ }
309
+ // A result step's metadata names the call it answers.
310
+ const linked = tryCall(meta && bytesOf(meta, 4))
311
+
312
+ if (!body && (type === STEP.userInput || type === STEP.plannerResponse || type === STEP.generic || type in BUILTIN || type === STEP.mcpTool)) {
313
+ lost.add("unreadable Antigravity steps")
314
+ continue
315
+ }
316
+ switch (type) {
317
+ case STEP.userInput: {
318
+ let text = textOf(body!, 2) || textOf(body!, 1) || ""
319
+ // The note brain puts before a continued session's first request says where it came from.
320
+ const note = CONTINUED_NOTE.exec(text)
321
+ if (note) {
322
+ continued ??= note[1]
323
+ text = text.replace(/^\[[^\n]*\]\n*/, "")
324
+ }
325
+ const context = allBytes(body!, 3).length + allBytes(body!, 9).length
326
+ if (context) lost.add("context attached to user messages", context)
327
+ if (text.trim()) turns.push({ kind: "user", ...stamp(at), text })
328
+ break
329
+ }
330
+ case STEP.plannerResponse: {
331
+ const thinking = hasThinking(body!)
332
+ if (thinking) lost.add("private reasoning")
333
+ const text = textOf(body!, 1) ?? ""
334
+ if (text.trim()) turns.push({ kind: "assistant", ...stamp(at), text })
335
+ const calls = allBytes(body!, 7)
336
+ for (const b of calls) {
337
+ const c = tryCall(b)
338
+ if (!c) {
339
+ lost.add("unreadable tool calls")
340
+ continue
341
+ }
342
+ pending.push({ native: c.id, id: call(at, c.id, c.name, parseArgs(c.args)) })
343
+ }
344
+ if (!text.trim() && !calls.length && !thinking) lost.add("Antigravity replies with nothing readable")
345
+ break
346
+ }
347
+ case STEP.generic: {
348
+ const res = tryMessage(body, 2)
349
+ if (!res) {
350
+ lost.add("unreadable Antigravity steps")
351
+ break
352
+ }
353
+ const key = entriesOf(res, 2).find(([k]) => k === LINK_KEY)?.[1]
354
+ const args = Object.fromEntries(
355
+ entriesOf(body!, 1).map(([k, v]) => {
356
+ try {
357
+ return [k, JSON.parse(v)]
358
+ } catch {
359
+ return [k, v]
360
+ }
361
+ }),
362
+ )
363
+ result(at, key ? { id: key } : linked, { name: "generic", input: args }, status !== STATUS.error, textOf(res, 1) ?? "")
364
+ break
365
+ }
366
+ case STEP.mcpTool: {
367
+ const c = tryCall(bytesOf(body!, 2))
368
+ let callId: string | undefined
369
+ if (c) {
370
+ const i = pending.findIndex((p) => p.native === c.id)
371
+ callId = i >= 0 ? pending.splice(i, 1)[0]!.id : call(at, c.id, c.name, parseArgs(c.args), "mcp")
372
+ }
373
+ const output = textOf(body!, 3) ?? textOf(body!, 8)
374
+ if (output === undefined) {
375
+ lost.add("MCP steps without a readable result")
376
+ break
377
+ }
378
+ callId ??= call(at, "", "mcp_tool", {}, "mcp")
379
+ const o = clip(output)
380
+ turns.push({ kind: "tool_result", ...stamp(at), callId, ok: status !== STATUS.error, output: o.text, ...(o.truncated ? { truncated: true } : {}) })
381
+ break
382
+ }
383
+ case STEP.runCommand:
384
+ case STEP.viewFile:
385
+ case STEP.shellExec: {
386
+ const b = builtin(type, body!)
387
+ result(at, linked, b, status !== STATUS.error, b.output ?? "")
388
+ break
389
+ }
390
+ default: {
391
+ lost.add(`antigravity step type ${type}`)
392
+ // A step brain cannot read that answers a pending call still closes it, so the call is not left open.
393
+ const i = linked ? pending.findIndex((p) => p.native === linked.id) : -1
394
+ if (i >= 0)
395
+ turns.push({
396
+ kind: "tool_result",
397
+ ...stamp(at),
398
+ callId: pending.splice(i, 1)[0]!.id,
399
+ ok: status !== STATUS.error,
400
+ output: `[Antigravity kept this result in a step type brain cannot read (${type}).]`,
401
+ })
402
+ }
403
+ }
404
+ }
405
+
406
+ const cwd = where.cwd ?? metaCwd
407
+ const started = startedAt ?? firstAt
408
+ const updated = where.updatedAt ?? lastAt
409
+ const parent: SessionHeader["parent"] = continued
410
+ ? { id: continued, kind: "continued" }
411
+ : where.parent
412
+ ? { id: `${TOOL}:${where.parent}`, kind: "subagent" }
413
+ : undefined
414
+ const h = header(TOOL, id, {
415
+ sourcePath: where.path,
416
+ ...(where.title ? { title: where.title } : {}),
417
+ project: { ...(cwd ? { cwd } : {}), ...gitInfo(cwd) },
418
+ ...(started ? { startedAt: started } : {}),
419
+ ...(updated ? { updatedAt: updated } : {}),
420
+ ...(parent ? { parent } : {}),
421
+ })
422
+ lost.flush(h)
423
+ return { header: h, turns }
424
+ }