@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,261 @@
1
+ import { readFileSync, statSync } from "node:fs"
2
+ import { basename, join } from "node:path"
3
+ import { exists, gitInfo, ls, mtime } from "../../fsutil.ts"
4
+ import type { Session, Turn, Usage } from "../../session/types.ts"
5
+ import { clip, header, iso, lossCounter, noteModel } from "../../session/util.ts"
6
+ import { CONTINUED_NOTE } from "../claude-code/records.ts"
7
+ import type { Reader, SessionRef } from "../types.ts"
8
+ import { type Entry, NOTE_TYPE, type PiFlavor, activeBranch, bashExecutionText, kindOf, migrate, parseEntries } from "./records.ts"
9
+
10
+ // Reads Pi-format sessions (Pi, and Auggie 1.x which is built on it): <root>/--<cwd>--/<file>.jsonl.
11
+ // The session is the active branch, the walk Pi resumes. Compactions become compaction turns placed where
12
+ // the model's context starts over (before the first entry they kept), with every earlier turn kept in
13
+ // front of them, so a writer can start from the summary or carry the whole history. Context edits are
14
+ // applied as Pi applies them: a removed entry is left out, a replaced one carries its new content.
15
+
16
+ const PEEK = 64 * 1024
17
+
18
+ type Lost = ReturnType<typeof lossCounter>
19
+
20
+ const textOf = (content: unknown, lost?: Lost, imageWhat = "images"): string => {
21
+ if (typeof content === "string") return content
22
+ if (!Array.isArray(content)) return ""
23
+ const parts: string[] = []
24
+ for (const b of content as Entry[]) {
25
+ if (b?.type === "text" && typeof b.text === "string") parts.push(b.text)
26
+ else if (b?.type === "image") lost?.add(imageWhat)
27
+ }
28
+ return parts.join("\n")
29
+ }
30
+
31
+ const usageOf = (u: Entry | undefined): Usage | undefined => {
32
+ if (!u || typeof u !== "object") return undefined
33
+ const out: Usage = {}
34
+ if (u.input) out.input = u.input
35
+ if (u.output) out.output = u.output
36
+ if (u.cacheRead) out.cacheRead = u.cacheRead
37
+ if (u.cacheWrite) out.cacheWrite = u.cacheWrite
38
+ return Object.keys(out).length ? out : undefined
39
+ }
40
+
41
+ /** One Pi session file as the portable session. */
42
+ export function parseSession(text: string, path: string, f: PiFlavor, home?: string): Session {
43
+ const entries = parseEntries(text)
44
+ if (!entries.length) throw new Error(`${path} is not a ${f.label} session (no session header)`)
45
+ migrate(entries)
46
+ const head = entries[0]!
47
+ const id = String(head.id)
48
+ const cwd = typeof head.cwd === "string" && head.cwd ? head.cwd : undefined
49
+ const branch = activeBranch(entries)
50
+ const lost = lossCounter()
51
+ const turns: Turn[] = []
52
+ const h = header(f.tool, id, { sourcePath: path, project: { ...(cwd ? { cwd, ...gitInfo(cwd, home) } : {}) }, ...(iso(head.timestamp) ? { startedAt: iso(head.timestamp) } : {}) })
53
+
54
+ // Where each compaction's context starts over: before the first entry it kept, when that comes before it.
55
+ const indexOf = new Map(branch.map((e, i) => [e.id as string, i]))
56
+ const placedBefore = new Map<number, Entry[]>()
57
+ const moved = new Set<Entry>()
58
+ for (const [i, e] of branch.entries()) {
59
+ if (e.type !== "compaction") continue
60
+ const k = indexOf.get(e.firstKeptEntryId)
61
+ if (k !== undefined && k < i) {
62
+ placedBefore.set(k, [...(placedBefore.get(k) ?? []), e])
63
+ moved.add(e)
64
+ }
65
+ }
66
+ // The latest context edit of each entry on the branch wins.
67
+ const edits = new Map<string, Entry>()
68
+ for (const e of branch) if (e.type === "context_edit" && typeof e.targetId === "string") edits.set(e.targetId, e)
69
+
70
+ const calls = new Map<string, Extract<Turn, { kind: "tool_call" }>>()
71
+ const compaction = (e: Entry) => {
72
+ if (typeof e.summary === "string" && e.summary.trim()) turns.push({ kind: "compaction", ...at(e.timestamp), summary: e.summary })
73
+ }
74
+ let title: string | undefined
75
+ let continued: string | undefined
76
+ let last: string | undefined
77
+
78
+ for (const [i, e] of branch.entries()) {
79
+ for (const c of placedBefore.get(i) ?? []) compaction(c)
80
+ if (typeof e.timestamp === "string") last = e.timestamp
81
+ switch (e.type) {
82
+ case "message": {
83
+ const edit = edits.get(e.id)
84
+ if (edit && edit.replacement === null) break
85
+ const m = edit ? { ...e.message, content: edit.replacement?.content } : e.message
86
+ message(m, e.timestamp)
87
+ break
88
+ }
89
+ case "compaction":
90
+ if (!moved.has(e)) compaction(e)
91
+ break
92
+ case "custom_message": {
93
+ const note = typeof e.content === "string" ? e.content.match(CONTINUED_NOTE) : null
94
+ if (e.customType === NOTE_TYPE) {
95
+ if (note) continued = note[1]
96
+ } else lost.add("extension messages")
97
+ break
98
+ }
99
+ case "session_info":
100
+ title = typeof e.name === "string" && e.name.trim() ? e.name.trim() : undefined
101
+ break
102
+ case "model_change":
103
+ noteModel(h, typeof e.modelId === "string" ? e.modelId : undefined)
104
+ break
105
+ case "branch_summary":
106
+ lost.add("summaries of abandoned branches")
107
+ break
108
+ case "thinking_level_change":
109
+ case "usage":
110
+ case "custom":
111
+ case "label":
112
+ case "context_edit":
113
+ break
114
+ default:
115
+ lost.add(`unknown entry types: ${String(e.type)}`)
116
+ }
117
+ }
118
+
119
+ function message(m: Entry, when: string | undefined) {
120
+ if (!m || typeof m !== "object") return
121
+ const stamp = at(typeof m.timestamp === "number" ? m.timestamp : when)
122
+ switch (m.role) {
123
+ case "user": {
124
+ const text = textOf(m.content, lost)
125
+ if (text.trim()) turns.push({ kind: "user", ...stamp, text })
126
+ break
127
+ }
128
+ // Auggie's slash-command and rule messages reach the model as the user's text.
129
+ case "promptCommandInvocation":
130
+ if (typeof m.expandedBody === "string" && m.expandedBody.trim()) turns.push({ kind: "user", ...stamp, text: m.expandedBody })
131
+ break
132
+ case "ruleReference":
133
+ if (typeof m.llmText === "string" && m.llmText.trim()) turns.push({ kind: "user", ...stamp, text: m.llmText })
134
+ break
135
+ case "assistant": {
136
+ const model = typeof m.model === "string" ? m.model : undefined
137
+ noteModel(h, model)
138
+ let usage = usageOf(m.usage)
139
+ for (const b of Array.isArray(m.content) ? (m.content as Entry[]) : []) {
140
+ if (b?.type === "text" && typeof b.text === "string" && b.text.trim()) {
141
+ turns.push({ kind: "assistant", ...stamp, text: b.text, ...(model ? { model } : {}), ...(usage ? { usage } : {}) })
142
+ usage = undefined
143
+ } else if (b?.type === "thinking") {
144
+ if (b.redacted) lost.add("redacted reasoning")
145
+ else if (typeof b.thinking === "string" && b.thinking.trim()) turns.push({ kind: "reasoning", ...stamp, text: b.thinking })
146
+ else if (b.thinkingSignature) lost.add("signed reasoning")
147
+ } else if (b?.type === "toolCall" && typeof b.id === "string") {
148
+ const name = typeof b.name === "string" ? b.name : "tool"
149
+ const call: Extract<Turn, { kind: "tool_call" }> = { kind: "tool_call", ...stamp, id: b.id, name, toolKind: kindOf(name), input: b.arguments ?? {} }
150
+ calls.set(b.id, call)
151
+ turns.push(call)
152
+ }
153
+ }
154
+ break
155
+ }
156
+ case "toolResult": {
157
+ if (typeof m.toolCallId !== "string") break
158
+ const full = clip(textOf(m.content, lost, "tool result images"))
159
+ const d = m.details && typeof m.details === "object" ? (m.details as Entry) : {}
160
+ const fullPath = typeof d.fullOutputPath === "string" ? d.fullOutputPath : undefined
161
+ const ok = m.isError !== true
162
+ turns.push({ kind: "tool_result", ...stamp, callId: m.toolCallId, ok, output: full.text, ...(full.truncated || d.truncation?.truncated ? { truncated: true } : {}), ...(fullPath ? { fullPath } : {}) })
163
+ // Pi's edit and write tools change one file each; the diff is in the edit's details.
164
+ const call = calls.get(m.toolCallId)
165
+ const target = (call?.input as Entry | undefined)?.path
166
+ if (ok && call && (call.name === "edit" || call.name === "write") && typeof target === "string")
167
+ turns.push({ kind: "file_edit", ...stamp, path: target, ...(typeof d.diff === "string" ? { patch: d.diff } : {}), callId: call.id })
168
+ break
169
+ }
170
+ case "bashExecution":
171
+ // A command the person ran with `!`: the model reads it as their message, unless kept out of context.
172
+ if (!m.excludeFromContext) turns.push({ kind: "user", ...stamp, text: bashExecutionText(m) })
173
+ break
174
+ case "custom":
175
+ lost.add("extension messages")
176
+ break
177
+ case "compactionSummary":
178
+ if (typeof m.summary === "string") turns.push({ kind: "compaction", ...stamp, summary: m.summary })
179
+ break
180
+ case "branchSummary":
181
+ lost.add("summaries of abandoned branches")
182
+ break
183
+ case "system":
184
+ lost.add(`${f.label} system prompt and tool declarations (not carried)`)
185
+ break
186
+ default:
187
+ lost.add(`unknown message roles: ${String(m.role)}`)
188
+ }
189
+ }
190
+
191
+ h.title = title ?? (turns.find((t) => t.kind === "user") as Extract<Turn, { kind: "user" }> | undefined)?.text.replace(/\s+/g, " ").trim().slice(0, 80)
192
+ if (!h.title) delete h.title
193
+ const updated = iso(last)
194
+ if (updated) h.updatedAt = updated
195
+ if (continued) h.parent = { id: continued, kind: "continued" }
196
+ else if (typeof head.parentSession === "string" && head.parentSession) {
197
+ const parent = basename(head.parentSession, ".jsonl").replace(/^.*_/, "")
198
+ if (parent) h.parent = { id: `${f.tool}:${parent}`, kind: "fork" }
199
+ }
200
+ lost.flush(h)
201
+ return { header: h, turns }
202
+ }
203
+
204
+ function at(v: unknown): { at?: string } {
205
+ const t = iso(v)
206
+ return t ? { at: t } : {}
207
+ }
208
+
209
+ /** The header, the name the person gave it, and the first request, from the start and end of a file. */
210
+ async function peek(path: string): Promise<Omit<SessionRef, "tool" | "path"> | undefined> {
211
+ const file = Bun.file(path)
212
+ const text = file.size <= 4 * PEEK ? await file.text() : `${await file.slice(0, PEEK).text()}\n${await file.slice(file.size - PEEK).text()}`
213
+ const entries = parseEntries(text)
214
+ const head = entries[0]
215
+ if (!head) return undefined
216
+ let name: string | undefined
217
+ let first: string | undefined
218
+ for (const e of entries) {
219
+ if (e.type === "session_info") name = typeof e.name === "string" && e.name.trim() ? e.name.trim() : undefined
220
+ if (!first && e.type === "message" && e.message?.role === "user") first = textOf(e.message.content).replace(/\s+/g, " ").trim().slice(0, 80) || undefined
221
+ }
222
+ const title = name ?? first
223
+ return {
224
+ sourceId: String(head.id),
225
+ ...(title ? { title } : {}),
226
+ ...(typeof head.cwd === "string" && head.cwd ? { cwd: head.cwd } : {}),
227
+ ...(mtime(path) ? { updatedAt: mtime(path) } : {}),
228
+ }
229
+ }
230
+
231
+ export function piFamilyReader(f: PiFlavor): Reader {
232
+ return {
233
+ tool: f.tool,
234
+ label: f.label,
235
+
236
+ async list(home: string): Promise<SessionRef[]> {
237
+ const root = f.sessionsRoot(home)
238
+ const refs: SessionRef[] = []
239
+ for (const project of ls(root)) {
240
+ const dir = join(root, project)
241
+ for (const name of ls(dir)) {
242
+ if (!name.endsWith(".jsonl")) continue
243
+ const path = join(dir, name)
244
+ try {
245
+ if (!statSync(path).isFile()) continue
246
+ const p = await peek(path)
247
+ if (p) refs.push({ tool: f.tool, path, ...p })
248
+ } catch {
249
+ // Deleted or unreadable between listing and reading: not a session to offer.
250
+ }
251
+ }
252
+ }
253
+ return refs.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))
254
+ },
255
+
256
+ async read(ref: SessionRef, home: string): Promise<Session> {
257
+ if (!exists(ref.path)) throw new Error(`no ${f.label} session at ${ref.path}`)
258
+ return parseSession(readFileSync(ref.path, "utf8"), ref.path, f, home)
259
+ },
260
+ }
261
+ }
@@ -0,0 +1,99 @@
1
+ import { join } from "node:path"
2
+ import type { ToolKind } from "../../session/types.ts"
3
+ import { toolKindOf } from "../../session/util.ts"
4
+
5
+ // Facts about Pi's session files, shared by the Pi and Auggie 1.x adapters: Auggie 1.x is built on Pi and
6
+ // keeps the same entries in a different folder. Checked against earendil-works/pi at 1.5.0
7
+ // (packages/coding-agent/src/core/session-manager.ts, messages.ts, tools/*.ts) and the bundle of
8
+ // @augmentcode/auggie 1.2.0-prerelease.202611061509; see docs/sessions/2026-11-08-session-formats.md, entries 17 and 43.
9
+ //
10
+ // A file is a header line {type:"session", version:3, id, timestamp, cwd} and then entries on a tree:
11
+ // each has an id (8 hex characters) and a parentId. Pi resumes at the last entry and walks parentId back to
12
+ // the root, so the active branch is that walk. Messages hold Pi's AgentMessage: user, assistant (content
13
+ // blocks text / thinking / toolCall), toolResult (one message per call, joined by toolCallId), plus
14
+ // Pi's own roles (bashExecution, custom). A compaction entry replaces everything before firstKeptEntryId
15
+ // with its summary; one that keeps nothing names itself.
16
+
17
+ export const SESSION_VERSION = 3
18
+
19
+ /** customType of the hidden note a writer adds; readers skip it. */
20
+ export const NOTE_TYPE = "brain-continued"
21
+
22
+ export type Entry = Record<string, any>
23
+
24
+ export interface PiFlavor {
25
+ /** Reader and writer id, e.g. "pi". */
26
+ tool: string
27
+ label: string
28
+ /** The command, for `--version` and the open command. */
29
+ bin: string
30
+ /** The folder holding one sub-folder of session files per project. */
31
+ sessionsRoot(home: string): string
32
+ /** The session file's name: Pi prefixes the start time, Auggie does not. */
33
+ fileName(id: string, startedAt: string): string
34
+ /** A new session id in the tool's style. */
35
+ newId(): string
36
+ }
37
+
38
+ /** The folder Pi keeps a project's sessions in: the cwd without its leading separator, / \ and : turned into -, between --. */
39
+ export const projectFolder = (cwd: string) => `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`
40
+
41
+ export const sessionDir = (f: PiFlavor, home: string, cwd: string) => join(f.sessionsRoot(home), projectFolder(cwd))
42
+
43
+ /** Every entry of a session file, header first; none when the first line is not a session header (Pi's own check). */
44
+ export function parseEntries(text: string): Entry[] {
45
+ const out: Entry[] = []
46
+ for (const line of text.split("\n")) {
47
+ if (!line.trim()) continue
48
+ try {
49
+ out.push(JSON.parse(line))
50
+ } catch {
51
+ // a half-written line: Pi skips it too
52
+ }
53
+ }
54
+ const h = out[0]
55
+ return h?.type === "session" && typeof h.id === "string" ? out : []
56
+ }
57
+
58
+ /** Version 1 files have no ids: give them the linear chain Pi's own migration gives them, so one walk reads every version. */
59
+ export function migrate(entries: Entry[]): void {
60
+ if ((entries[0]?.version ?? 1) >= 2) return
61
+ let prev: string | null = null
62
+ entries.forEach((e, i) => {
63
+ if (i === 0) return
64
+ e.id = `v1-${i}`
65
+ e.parentId = prev
66
+ prev = e.id
67
+ })
68
+ for (const e of entries) {
69
+ if (e.type === "compaction" && typeof e.firstKeptEntryIndex === "number") e.firstKeptEntryId = entries[e.firstKeptEntryIndex]?.id
70
+ }
71
+ }
72
+
73
+ /** The active branch: from the last entry back to the root, as Pi resumes it. */
74
+ export function activeBranch(entries: Entry[]): Entry[] {
75
+ const body = entries.filter((e) => e.type !== "session" && e.type !== "queue_state" && typeof e.id === "string")
76
+ const byId = new Map(body.map((e) => [e.id as string, e]))
77
+ const path: Entry[] = []
78
+ const seen = new Set<string>()
79
+ for (let e = body[body.length - 1]; e && !seen.has(e.id); e = e.parentId ? byId.get(e.parentId) : undefined) {
80
+ seen.add(e.id)
81
+ path.push(e)
82
+ }
83
+ return path.reverse()
84
+ }
85
+
86
+ /** What Pi's built-in tools do, so a call reads back with the right kind. */
87
+ const PI_KINDS: Record<string, ToolKind> = { read: "read", bash: "shell", edit: "edit", write: "write", grep: "search", find: "search", ls: "list" }
88
+
89
+ export const kindOf = (name: string): ToolKind => PI_KINDS[name] ?? toolKindOf(name)
90
+
91
+ /** The text Pi gives the model for a shell command the person ran with `!` (messages.ts bashExecutionToText). */
92
+ export function bashExecutionText(m: Entry): string {
93
+ let text = `Ran \`${m.command}\`\n`
94
+ text += m.output ? `\`\`\`\n${m.output}\n\`\`\`` : "(no output)"
95
+ if (m.cancelled) text += "\n\n(command cancelled)"
96
+ else if (m.exitCode !== null && m.exitCode !== undefined && m.exitCode !== 0) text += `\n\nCommand exited with code ${m.exitCode}`
97
+ if (m.truncated && m.fullOutputPath) text += `\n\n[Output truncated. Full output: ${m.fullOutputPath}]`
98
+ return text
99
+ }