@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,463 @@
1
+ import { Database } from "bun:sqlite"
2
+ import { randomBytes, randomUUID } from "node:crypto"
3
+ import { linkSync, lstatSync, mkdirSync, statSync, unlinkSync, writeFileSync } from "node:fs"
4
+ import { join, resolve } from "node:path"
5
+ import { pathToFileURL } from "node:url"
6
+ import { toolLabel } from "../../brief.ts"
7
+ import { exists, isDir } from "../../fsutil.ts"
8
+ import { withSnapshot } from "../../sqlite.ts"
9
+ import type { Session } from "../../session/types.ts"
10
+ import { countTurns, firstUserText, unpaired } from "../../session/util.ts"
11
+ import { continuedNote } from "../claude-code/records.ts"
12
+ import { continuedTitle, sh } from "../opencode/writer.ts"
13
+ import type { Readiness, WriteOptions, WriteResult, Writer } from "../types.ts"
14
+ import { readConversation } from "./reader.ts"
15
+ import {
16
+ CLI_STORE,
17
+ LABEL,
18
+ PROJECT_ID,
19
+ SCHEMA,
20
+ STATUS,
21
+ STEP,
22
+ SUMMARY_COLUMNS,
23
+ SUMMARY_SCHEMA,
24
+ TESTED_VERSIONS,
25
+ TOOL,
26
+ TRAJECTORY,
27
+ conversationsDir,
28
+ genericBody,
29
+ plannerBody,
30
+ stepBytes,
31
+ summariesPath,
32
+ toSqlTime,
33
+ trajectoryMetadata,
34
+ userInputBody,
35
+ type NativeCall,
36
+ } from "./records.ts"
37
+
38
+ // Continues a portable session in the Antigravity CLI as a new conversation of its own store, built the way
39
+ // session-migrate's clean-room writer builds one for CLI 1.1.16 (docs/antigravity-format.md, "Conversion
40
+ // policy"): a done user-input step per user turn, a done planner-response step per reply, the tool calls of one
41
+ // response together in one planner step, and a generic tool step after it per result (done, or error when the
42
+ // call failed), linked to its call by id. The tools keep their own names and JSON arguments: they are not
43
+ // passed off as Antigravity's built-in tools. Private reasoning, compaction summaries, attachments and the
44
+ // runtime tables are not written. The note saying where the session came from opens the first request.
45
+ //
46
+ // The new database is built in memory, read back with the reader, and only then published under
47
+ // ~/.gemini/antigravity-cli/conversations/<uuid>.db with create-if-absent semantics (a hard link), while an
48
+ // immediate transaction on conversation_summaries.db reserves the id and adds the picker's row; a failure
49
+ // removes only the new file. An existing conversation is never overwritten or merged.
50
+
51
+ const NO_RESULT = "No result was recorded for this call; the session moved before it finished."
52
+
53
+ export interface PlannedStep {
54
+ type: number
55
+ status: number
56
+ payload: Uint8Array
57
+ }
58
+
59
+ export interface AntigravityPlan {
60
+ id: string
61
+ trajectoryId: string
62
+ steps: PlannedStep[]
63
+ title: string
64
+ preview: string
65
+ /** Index of the last user-input step. */
66
+ lastUserStep: number
67
+ lost: string[]
68
+ /** What a read-back must find. */
69
+ expect: { users: number; assistants: number; calls: number; results: number }
70
+ }
71
+
72
+ const isObject = (v: unknown): v is Record<string, unknown> => !!v && typeof v === "object" && !Array.isArray(v)
73
+
74
+ /** The steps of a new conversation `opts.id` continuing `session`. Pure: writes nothing. */
75
+ export function planAntigravity(session: Session, opts: { id: string; trajectoryId?: string; now: Date }): AntigravityPlan {
76
+ const drops = new Map<string, number>()
77
+ const drop = (what: string, n = 1) => drops.set(what, (drops.get(what) ?? 0) + n)
78
+ const steps: PlannedStep[] = []
79
+ const expect = { users: 0, assistants: 0, calls: 0, results: 0 }
80
+ const push = (type: number, status: number, body: Uint8Array) => steps.push({ type, status, payload: stepBytes(type, status, body) })
81
+
82
+ const note = continuedNote(toolLabel(session.header.tool), session.header.id, opts.now.toISOString().slice(0, 10))
83
+ let noted = false
84
+ const userStep = (text: string) => {
85
+ push(STEP.userInput, STATUS.done, userInputBody(noted ? text : text ? `${note}\n\n${text}` : note))
86
+ noted = true
87
+ }
88
+
89
+ // Calls of the response being built, and calls still waiting for a result (by the source's id, in order).
90
+ let open: NativeCall[] = []
91
+ const pending: Array<{ src: string; call: NativeCall; input: Record<string, unknown> }> = []
92
+ const used = new Set<string>()
93
+ let made = 0
94
+ const freshId = () => {
95
+ let id = ""
96
+ do id = `call_brain_${++made}`
97
+ while (used.has(id))
98
+ return id
99
+ }
100
+ const flushCalls = () => {
101
+ if (!open.length) return
102
+ if (!noted) userStep("")
103
+ push(STEP.plannerResponse, STATUS.done, plannerBody({ messageId: randomUUID(), calls: open }))
104
+ open = []
105
+ }
106
+ const resultStep = (call: NativeCall, input: Record<string, unknown>, ok: boolean, output: string) => {
107
+ const args = Object.entries(input)
108
+ .filter(([k]) => k)
109
+ .map(([k, v]): [string, string] => [k, JSON.stringify(v) ?? "null"])
110
+ push(STEP.generic, ok ? STATUS.done : STATUS.error, genericBody({ args, result: output, callId: call.id }))
111
+ expect.results++
112
+ }
113
+ // Calls still open when the person speaks again are closed as failed, so no call is left without a result.
114
+ const closeDangling = () => {
115
+ flushCalls()
116
+ for (const p of pending.splice(0)) {
117
+ resultStep(p.call, p.input, false, NO_RESULT)
118
+ drop("tool calls with no result, closed as failed")
119
+ }
120
+ }
121
+
122
+ let timed = 0
123
+ for (const t of session.turns) {
124
+ if (t.at) timed++
125
+ switch (t.kind) {
126
+ case "user": {
127
+ const n = t.attachments?.length ?? 0
128
+ const images = t.attachments?.filter((a) => a.kind === "image").length ?? 0
129
+ if (images) drop("images", images)
130
+ if (n - images) drop("attached files", n - images)
131
+ const text = t.text.trim() ? t.text : n ? `(${n} attachment${n > 1 ? "s" : ""} not carried over)` : ""
132
+ if (!text) break
133
+ closeDangling()
134
+ userStep(text)
135
+ expect.users++
136
+ break
137
+ }
138
+ case "assistant": {
139
+ if (!t.text.trim()) break
140
+ flushCalls()
141
+ if (!noted) userStep("")
142
+ push(STEP.plannerResponse, STATUS.done, plannerBody({ text: t.text, messageId: randomUUID() }))
143
+ expect.assistants++
144
+ if (t.model || t.usage) drop("model names and token usage on replies")
145
+ break
146
+ }
147
+ case "tool_call": {
148
+ let id = t.id
149
+ if (!id || used.has(id)) {
150
+ id = freshId()
151
+ drop("missing or repeated tool call ids, replaced")
152
+ }
153
+ used.add(id)
154
+ let input: Record<string, unknown> = {}
155
+ if (isObject(t.input)) input = t.input
156
+ else if (t.input !== undefined && t.input !== null) {
157
+ input = { value: t.input }
158
+ drop("tool inputs that were not objects, wrapped")
159
+ }
160
+ let args = "{}"
161
+ try {
162
+ args = JSON.stringify(input)
163
+ } catch {
164
+ input = {}
165
+ drop("tool inputs that are not JSON")
166
+ }
167
+ const call: NativeCall = { id, name: t.name || "unknown_tool", args }
168
+ open.push(call)
169
+ pending.push({ src: t.id, call, input })
170
+ expect.calls++
171
+ break
172
+ }
173
+ case "tool_result": {
174
+ flushCalls()
175
+ const i = pending.findIndex((p) => p.src === t.callId)
176
+ let p = i >= 0 ? pending.splice(i, 1)[0]! : undefined
177
+ if (!p) {
178
+ // A result with no open call before it gets a call of its own: the store links every result to one.
179
+ const id = t.callId && !used.has(t.callId) ? t.callId : freshId()
180
+ used.add(id)
181
+ p = { src: t.callId, call: { id, name: "unknown_tool", args: "{}" }, input: {} }
182
+ if (!noted) userStep("")
183
+ push(STEP.plannerResponse, STATUS.done, plannerBody({ messageId: randomUUID(), calls: [p.call] }))
184
+ expect.calls++
185
+ drop("tool results without a call (given a placeholder call)")
186
+ }
187
+ resultStep(p.call, p.input, t.ok, t.output)
188
+ break
189
+ }
190
+ case "reasoning":
191
+ drop("reasoning")
192
+ break
193
+ case "compaction":
194
+ drop("compaction summaries")
195
+ break
196
+ case "file_edit":
197
+ if (!t.callId) drop("file edits not made by a tool call")
198
+ break
199
+ case "subagent":
200
+ drop("subagent sessions (their call and result are kept)")
201
+ break
202
+ }
203
+ }
204
+ closeDangling()
205
+ // The store needs at least one user input; a session without one still gets the note.
206
+ if (!noted) userStep("")
207
+ if (timed) drop("turn times", timed)
208
+
209
+ let lastUserStep = -1
210
+ steps.forEach((s, i) => {
211
+ if (s.type === STEP.userInput) lastUserStep = i
212
+ })
213
+ const trajectoryId = opts.trajectoryId ?? randomUUID()
214
+ if (trajectoryId === opts.id) throw new Error("the Antigravity conversation and trajectory ids must differ")
215
+ return {
216
+ id: opts.id,
217
+ trajectoryId,
218
+ steps,
219
+ title: continuedTitle(session).slice(0, 4_096),
220
+ preview: (firstUserText(session) ?? "Imported conversation").slice(0, 16_384),
221
+ lastUserStep,
222
+ lost: [...drops].map(([what, n]) => `${what} (${n})`),
223
+ expect,
224
+ }
225
+ }
226
+
227
+ /** The conversation database's bytes, built in memory and read back before anything is written. */
228
+ export function buildConversation(plan: AntigravityPlan, now: Date): Uint8Array {
229
+ const db = new Database(":memory:")
230
+ try {
231
+ db.exec(SCHEMA)
232
+ db.transaction(() => {
233
+ db.prepare("INSERT INTO trajectory_meta (trajectory_id, cascade_id, trajectory_type, source) VALUES (?, ?, ?, ?)").run(plan.trajectoryId, plan.id, TRAJECTORY.cascade, TRAJECTORY.cli)
234
+ const add = db.prepare("INSERT INTO steps (idx, step_type, status, has_subtrajectory, metadata, step_payload, step_format) VALUES (?, ?, ?, 0, NULL, ?, 0)")
235
+ plan.steps.forEach((s, i) => add.run(i, s.type, s.status, s.payload))
236
+ db.prepare("INSERT INTO trajectory_metadata_blob (id, data) VALUES ('main', ?)").run(trajectoryMetadata(Math.floor(now.getTime() / 1000), plan.id))
237
+ })()
238
+ const check = db.query("PRAGMA integrity_check").get() as { integrity_check?: string } | null
239
+ if (check?.integrity_check !== "ok") throw new Error("the new Antigravity conversation failed SQLite's integrity check")
240
+ const back = readConversation(db, plan.id, { path: `${plan.id}.db` })
241
+ const n = countTurns(back)
242
+ const pairs = unpaired(back)
243
+ const e = plan.expect
244
+ if (
245
+ n.user !== e.users ||
246
+ n.assistant !== e.assistants ||
247
+ n.tool_call !== e.calls ||
248
+ n.tool_result !== e.results ||
249
+ n.reasoning ||
250
+ pairs.calls.length ||
251
+ pairs.results.length ||
252
+ back.header.lost.length
253
+ )
254
+ throw new Error(
255
+ `the Antigravity conversation did not read back as written (users ${n.user}/${e.users}, replies ${n.assistant}/${e.assistants}, tool calls ${n.tool_call}/${e.calls}, results ${n.tool_result}/${e.results}, unpaired ${pairs.calls.length + pairs.results.length}, lost ${back.header.lost.join("; ") || "none"}); nothing was written`,
256
+ )
257
+ return db.serialize()
258
+ } finally {
259
+ db.close()
260
+ }
261
+ }
262
+
263
+ /** Summary columns the writer fills that a summaries table lacks. Extra columns are fine. */
264
+ function missingColumns(d: Database): string[] {
265
+ const have = new Set((d.query("PRAGMA table_info(conversation_summaries)").all() as Array<{ name: string }>).map((c) => c.name))
266
+ return have.size ? SUMMARY_COLUMNS.filter((c) => !have.has(c)) : ["conversation_summaries (no such table)"]
267
+ }
268
+
269
+ const lexists = (p: string) => {
270
+ try {
271
+ lstatSync(p)
272
+ return true
273
+ } catch {
274
+ return false
275
+ }
276
+ }
277
+
278
+ /** The binary on PATH (an explicit PATH in the environment is the one searched). */
279
+ export const agyBin = () => Bun.which("agy", { PATH: process.env.PATH ?? "" }) ?? undefined
280
+
281
+ /**
282
+ * "1.1.16" from `agy --version`; undefined when it does not answer. Auto-update is turned off for the call:
283
+ * session-migrate found that even `agy --version` starts the 1.1.16 updater.
284
+ */
285
+ export function agyVersion(bin: string): string | undefined {
286
+ try {
287
+ const p = Bun.spawnSync([bin, "--version"], {
288
+ env: { ...process.env, AGY_CLI_DISABLE_AUTO_UPDATE: "1" },
289
+ stdin: "ignore",
290
+ stdout: "pipe",
291
+ stderr: "pipe",
292
+ timeout: 15_000,
293
+ })
294
+ return p.exitCode === 0 ? p.stdout.toString().trim().split("\n")[0]?.match(/\d+\.\d+\.\d+/)?.[0] : undefined
295
+ } catch {
296
+ return undefined
297
+ }
298
+ }
299
+
300
+ export interface InstallOptions extends WriteOptions {
301
+ /** The new conversation's id (a UUID); a fresh one when omitted. */
302
+ id?: string
303
+ trajectoryId?: string
304
+ }
305
+
306
+ /** Writes the session as a new CLI conversation (or, in a dry run, only checks it would). */
307
+ export async function installConversation(session: Session, opts: InstallOptions): Promise<WriteResult> {
308
+ const cwd = resolve(opts.cwd)
309
+ const now = new Date()
310
+ const id = opts.id ?? randomUUID()
311
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id)) throw new Error(`an Antigravity conversation id is a lowercase UUIDv4, not ${id}`)
312
+ const plan = planAntigravity(session, { id, now, ...(opts.trajectoryId ? { trajectoryId: opts.trajectoryId } : {}) })
313
+ const bytes = buildConversation(plan, now)
314
+ const dir = conversationsDir(opts.home, CLI_STORE)
315
+ const file = join(dir, `${id}.db`)
316
+ const sums = summariesPath(opts.home, CLI_STORE)
317
+ const taken = () => new Error(`Antigravity conversation ${id} already exists; brain never overwrites or merges a conversation`)
318
+
319
+ if (lexists(file)) throw taken()
320
+ if (exists(sums)) {
321
+ const { missing, row } = withSnapshot(sums, (d) => {
322
+ const missing = missingColumns(d)
323
+ return { missing, row: missing.length ? null : d.query("SELECT 1 FROM conversation_summaries WHERE conversation_id = ?").get(id) }
324
+ })
325
+ if (missing.length) throw new Error(`the Antigravity summaries database at ${sums} lacks ${missing.join(", ")}; it is not one this writer knows`)
326
+ if (row) throw taken()
327
+ }
328
+
329
+ if (!opts.dryRun) {
330
+ mkdirSync(dir, { recursive: true, mode: 0o700 })
331
+ ensureSummaries(sums)
332
+ const summary = [
333
+ id,
334
+ plan.title,
335
+ plan.preview,
336
+ plan.steps.length,
337
+ toSqlTime(now),
338
+ JSON.stringify([pathToFileURL(cwd).href]),
339
+ "",
340
+ "antigravity-cli",
341
+ PROJECT_ID,
342
+ "",
343
+ "",
344
+ 0,
345
+ "",
346
+ "",
347
+ 0,
348
+ 0,
349
+ toSqlTime(now),
350
+ plan.lastUserStep,
351
+ "antigravity-cli",
352
+ ]
353
+ publish(bytes, file, sums, id, summary, taken)
354
+ }
355
+ return {
356
+ tool: TOOL,
357
+ way: "native",
358
+ sessionId: id,
359
+ written: [file, sums],
360
+ open: `cd ${sh(cwd)} && agy --conversation ${id}`,
361
+ lost: plan.lost,
362
+ dryRun: !!opts.dryRun,
363
+ }
364
+ }
365
+
366
+ /** Creates the summaries database as 1.1.16 does when there is none; one made meanwhile by someone else is kept. */
367
+ function ensureSummaries(path: string) {
368
+ if (lexists(path)) return
369
+ const mem = new Database(":memory:")
370
+ let bytes: Uint8Array
371
+ try {
372
+ mem.exec(SUMMARY_SCHEMA)
373
+ bytes = mem.serialize()
374
+ } finally {
375
+ mem.close()
376
+ }
377
+ const tmp = `${path}.brain-${randomBytes(6).toString("hex")}`
378
+ writeFileSync(tmp, bytes, { flag: "wx", mode: 0o600 })
379
+ try {
380
+ linkSync(tmp, path)
381
+ } catch (e) {
382
+ if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e
383
+ } finally {
384
+ unlinkSync(tmp)
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Publishes the conversation and its summary row together: the id is reserved in the summaries table with an
390
+ * immediate transaction, the database appears under its final name only if that name is free (a hard link
391
+ * from a private temporary file), and a failure before the commit removes only the file this call created.
392
+ */
393
+ function publish(bytes: Uint8Array, file: string, sums: string, id: string, summary: unknown[], taken: () => Error) {
394
+ const tmp = join(resolve(file, ".."), `.${id}.db.brain-${randomBytes(6).toString("hex")}`)
395
+ writeFileSync(tmp, bytes, { flag: "wx", mode: 0o600 })
396
+ const ino = statSync(tmp).ino
397
+ const db = new Database(sums)
398
+ let linked = false
399
+ try {
400
+ db.exec("PRAGMA busy_timeout = 15000")
401
+ const missing = missingColumns(db)
402
+ if (missing.length) throw new Error(`the Antigravity summaries database at ${sums} lacks ${missing.join(", ")}`)
403
+ db.exec("BEGIN IMMEDIATE")
404
+ try {
405
+ if (db.query("SELECT 1 FROM conversation_summaries WHERE conversation_id = ?").get(id) || lexists(file)) throw taken()
406
+ try {
407
+ linkSync(tmp, file)
408
+ } catch (e) {
409
+ throw (e as NodeJS.ErrnoException).code === "EEXIST" ? taken() : e
410
+ }
411
+ linked = true
412
+ db.prepare(`INSERT INTO conversation_summaries (${SUMMARY_COLUMNS.join(", ")}) VALUES (${SUMMARY_COLUMNS.map(() => "?").join(", ")})`).run(...(summary as Array<string | number>))
413
+ db.exec("COMMIT")
414
+ } catch (e) {
415
+ db.exec("ROLLBACK")
416
+ if (linked)
417
+ try {
418
+ if (statSync(file).ino === ino) unlinkSync(file)
419
+ } catch {
420
+ // already gone
421
+ }
422
+ throw e
423
+ }
424
+ } finally {
425
+ db.close()
426
+ try {
427
+ unlinkSync(tmp)
428
+ } catch {
429
+ // already gone
430
+ }
431
+ }
432
+ }
433
+
434
+ export const antigravityWriter: Writer = {
435
+ tool: TOOL,
436
+ label: LABEL,
437
+ way: "native",
438
+
439
+ async ready(opts: WriteOptions): Promise<Readiness> {
440
+ const bin = agyBin()
441
+ if (!bin) return { ok: false, why: "the Antigravity CLI is not installed (no agy on PATH)" }
442
+ const v = agyVersion(bin)
443
+ if (!v) return { ok: false, why: `could not read the Antigravity CLI's version (${bin} --version failed)` }
444
+ if (!TESTED_VERSIONS.includes(v))
445
+ return { ok: false, why: `Antigravity CLI ${v} has not been tested; its private conversation store was checked on ${TESTED_VERSIONS.join(", ")} only` }
446
+ if (!isDir(opts.cwd)) return { ok: false, why: `the folder to continue in does not exist: ${opts.cwd}` }
447
+ const sums = summariesPath(opts.home, CLI_STORE)
448
+ if (exists(sums)) {
449
+ let missing: string[]
450
+ try {
451
+ missing = withSnapshot(sums, missingColumns)
452
+ } catch (e) {
453
+ return { ok: false, why: `the Antigravity summaries database cannot be read: ${(e as Error).message}` }
454
+ }
455
+ if (missing.length) return { ok: false, why: `the Antigravity summaries database lacks ${missing.join(", ")}; it is not one this writer knows` }
456
+ }
457
+ return { ok: true }
458
+ },
459
+
460
+ write(session: Session, opts: WriteOptions): Promise<WriteResult> {
461
+ return installConversation(session, opts)
462
+ },
463
+ }
@@ -0,0 +1,36 @@
1
+ import { randomUUID } from "node:crypto"
2
+ import { join } from "node:path"
3
+ import { piFamilyReader } from "../pi/reader.ts"
4
+ import type { PiFlavor } from "../pi/records.ts"
5
+ import { piFamilyWriter } from "../pi/writer.ts"
6
+ import type { Reader, Writer } from "../types.ts"
7
+
8
+ // Auggie CLI (Augment) 1.x: a rewrite built on Pi, so its sessions are Pi's format version 3 in its own
9
+ // folder: ~/.augment/sessions-v2/--<cwd>--/<uuid>.jsonl (AUGGIE_CODING_AGENT_DIR / AUGMENT_CACHE_DIR move it,
10
+ // which brain does not follow). Auggie adds a queue_state record, a compaction field (protectedHeadEntryId)
11
+ // and two user-side roles (slash-command invocations, rule references); the Pi reader reads all of them.
12
+ // Continues any session as a new native Auggie session, `auggie --session <id>`. Auggie 0.x keeps JSON
13
+ // conversations in ~/.augment/sessions instead and is not written.
14
+
15
+ /** Auggie versions whose session format this writer was checked against (1.x is published as daily prereleases). */
16
+ export const AUGGIE_TESTED_VERSIONS = ["1.2.0-prerelease.202611061509"]
17
+
18
+ export const auggieFlavor: PiFlavor = {
19
+ tool: "auggie",
20
+ label: "Auggie",
21
+ bin: "auggie",
22
+ sessionsRoot: (home) => join(home, ".augment", "sessions-v2"),
23
+ // <id>.jsonl: Auggie's SessionManager does not prefix the start time.
24
+ fileName: (id) => `${id}.jsonl`,
25
+ newId: () => randomUUID(),
26
+ }
27
+
28
+ /** Auggie 0.x stores another format; say so instead of only "untested". */
29
+ const v0 = (v: string) =>
30
+ /^0\./.test(v) ? `Auggie ${v} keeps sessions as JSON in ~/.augment/sessions; this writer writes the 1.x format (install @augmentcode/auggie@prerelease)` : undefined
31
+
32
+ export const auggieReader = piFamilyReader(auggieFlavor)
33
+ export const auggieWriter = (version?: string) => piFamilyWriter(auggieFlavor, { tested: AUGGIE_TESTED_VERSIONS, why: v0, ...(version ? { version } : {}) })
34
+
35
+ export const readers: Reader[] = [auggieReader]
36
+ export const writers: Writer[] = [auggieWriter()]
@@ -0,0 +1,8 @@
1
+ import type { Reader, Writer } from "../types.ts"
2
+ import { claudeCodeReader } from "./reader.ts"
3
+ import { claudeCodeNativeWriter } from "./writer.ts"
4
+
5
+ // Claude Code (CLI, Agent SDK, and Claude Desktop's Code sessions, which keep the same transcripts):
6
+ // reads every session with its subagents, and continues any session as a new native Claude Code session.
7
+ export const readers: Reader[] = [claudeCodeReader]
8
+ export const writers: Writer[] = [claudeCodeNativeWriter]