@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.
- package/LICENSE +48 -0
- package/README.md +235 -2
- package/package.json +48 -4
- package/src/adapters/aider/index.ts +8 -0
- package/src/adapters/aider/reader.ts +129 -0
- package/src/adapters/aider/records.ts +220 -0
- package/src/adapters/aider/writer.ts +269 -0
- package/src/adapters/amazon-q-cli/index.ts +8 -0
- package/src/adapters/amazon-q-cli/reader.ts +84 -0
- package/src/adapters/amazon-q-cli/records.ts +191 -0
- package/src/adapters/amazon-q-cli/writer.ts +370 -0
- package/src/adapters/amazon-q-ide/index.ts +9 -0
- package/src/adapters/amazon-q-ide/reader.ts +131 -0
- package/src/adapters/amazon-q-ide/records.ts +141 -0
- package/src/adapters/amazon-q-ide/writer.ts +325 -0
- package/src/adapters/antigravity/index.ts +9 -0
- package/src/adapters/antigravity/proto.ts +194 -0
- package/src/adapters/antigravity/reader.ts +424 -0
- package/src/adapters/antigravity/records.ts +236 -0
- package/src/adapters/antigravity/writer.ts +463 -0
- package/src/adapters/auggie/index.ts +36 -0
- package/src/adapters/claude-code/index.ts +8 -0
- package/src/adapters/claude-code/reader.ts +639 -0
- package/src/adapters/claude-code/records.ts +88 -0
- package/src/adapters/claude-code/writer.ts +329 -0
- package/src/adapters/cline/index.ts +12 -0
- package/src/adapters/cline/reader.ts +432 -0
- package/src/adapters/cline/records.ts +151 -0
- package/src/adapters/cline/vscdb.ts +56 -0
- package/src/adapters/cline/writer.ts +656 -0
- package/src/adapters/cline/xml.ts +177 -0
- package/src/adapters/cline4/index.ts +9 -0
- package/src/adapters/cline4/reader.ts +179 -0
- package/src/adapters/cline4/records.ts +92 -0
- package/src/adapters/cline4/writer.ts +361 -0
- package/src/adapters/codebuff/index.ts +8 -0
- package/src/adapters/codebuff/project.ts +207 -0
- package/src/adapters/codebuff/reader.ts +215 -0
- package/src/adapters/codebuff/records.ts +173 -0
- package/src/adapters/codebuff/writer.ts +383 -0
- package/src/adapters/codex/index.ts +8 -0
- package/src/adapters/codex/native.ts +239 -0
- package/src/adapters/codex/reader.ts +747 -0
- package/src/adapters/codex/records.ts +114 -0
- package/src/adapters/codex/writer.ts +479 -0
- package/src/adapters/continue/index.ts +9 -0
- package/src/adapters/continue/reader.ts +68 -0
- package/src/adapters/continue/records.ts +418 -0
- package/src/adapters/continue/writer.ts +162 -0
- package/src/adapters/copilot-cli/index.ts +9 -0
- package/src/adapters/copilot-cli/reader.ts +186 -0
- package/src/adapters/copilot-cli/records.ts +241 -0
- package/src/adapters/copilot-cli/writer.ts +353 -0
- package/src/adapters/crush/index.ts +8 -0
- package/src/adapters/crush/reader.ts +180 -0
- package/src/adapters/crush/records.ts +169 -0
- package/src/adapters/crush/writer.ts +333 -0
- package/src/adapters/cursor-cli/index.ts +405 -0
- package/src/adapters/cursor-ide/agentkv.ts +95 -0
- package/src/adapters/cursor-ide/index.ts +501 -0
- package/src/adapters/cursor-ide/transcripts.ts +122 -0
- package/src/adapters/cursor-ide/workspaces.ts +119 -0
- package/src/adapters/cursor-ide/writer.ts +170 -0
- package/src/adapters/devin/index.ts +8 -0
- package/src/adapters/devin/reader.ts +305 -0
- package/src/adapters/devin/records.ts +277 -0
- package/src/adapters/devin/writer.ts +523 -0
- package/src/adapters/factory-droid/index.ts +12 -0
- package/src/adapters/factory-droid/reader.ts +184 -0
- package/src/adapters/factory-droid/records.ts +79 -0
- package/src/adapters/factory-droid/writer.ts +350 -0
- package/src/adapters/gemini-cli/exchanges.ts +228 -0
- package/src/adapters/gemini-cli/index.ts +396 -0
- package/src/adapters/gemini-cli/records.ts +521 -0
- package/src/adapters/gemini-cli/writer.ts +354 -0
- package/src/adapters/goose/index.ts +8 -0
- package/src/adapters/goose/reader.ts +138 -0
- package/src/adapters/goose/records.ts +199 -0
- package/src/adapters/goose/writer.ts +340 -0
- package/src/adapters/grok-cli/image.ts +44 -0
- package/src/adapters/grok-cli/index.ts +8 -0
- package/src/adapters/grok-cli/reader.ts +302 -0
- package/src/adapters/grok-cli/records.ts +172 -0
- package/src/adapters/grok-cli/writer.ts +329 -0
- package/src/adapters/hermes/index.ts +8 -0
- package/src/adapters/hermes/reader.ts +249 -0
- package/src/adapters/hermes/records.ts +285 -0
- package/src/adapters/hermes/writer.ts +452 -0
- package/src/adapters/iflow-cli/index.ts +72 -0
- package/src/adapters/iflow-cli/records.ts +215 -0
- package/src/adapters/iflow-cli/writer.ts +204 -0
- package/src/adapters/index.ts +112 -0
- package/src/adapters/kilo-cli/index.ts +21 -0
- package/src/adapters/kilo-code/index.ts +11 -0
- package/src/adapters/kilo-code/writer.ts +92 -0
- package/src/adapters/kimi/index.ts +9 -0
- package/src/adapters/kimi/reader.ts +92 -0
- package/src/adapters/kimi/records.ts +221 -0
- package/src/adapters/kimi/writer.ts +367 -0
- package/src/adapters/kiro-cli/index.ts +8 -0
- package/src/adapters/kiro-cli/reader.ts +87 -0
- package/src/adapters/kiro-cli/records.ts +260 -0
- package/src/adapters/kiro-cli/writer.ts +305 -0
- package/src/adapters/kiro-ide/index.ts +8 -0
- package/src/adapters/kiro-ide/writer.ts +52 -0
- package/src/adapters/letta/index.ts +8 -0
- package/src/adapters/letta/reader.ts +177 -0
- package/src/adapters/letta/records.ts +119 -0
- package/src/adapters/letta/writer.ts +413 -0
- package/src/adapters/mastracode/index.ts +8 -0
- package/src/adapters/mastracode/reader.ts +323 -0
- package/src/adapters/mastracode/records.ts +336 -0
- package/src/adapters/mastracode/writer.ts +385 -0
- package/src/adapters/mistral-vibe/index.ts +9 -0
- package/src/adapters/mistral-vibe/reader.ts +62 -0
- package/src/adapters/mistral-vibe/records.ts +104 -0
- package/src/adapters/mistral-vibe/writer.ts +359 -0
- package/src/adapters/muse/index.ts +10 -0
- package/src/adapters/muse/reader.ts +332 -0
- package/src/adapters/muse/records.ts +104 -0
- package/src/adapters/muse/writer.ts +387 -0
- package/src/adapters/omp/index.ts +13 -0
- package/src/adapters/omp/reader.ts +314 -0
- package/src/adapters/omp/records.ts +153 -0
- package/src/adapters/omp/writer.ts +216 -0
- package/src/adapters/opencode/ids.ts +52 -0
- package/src/adapters/opencode/index.ts +31 -0
- package/src/adapters/opencode/note.ts +8 -0
- package/src/adapters/opencode/reader.ts +452 -0
- package/src/adapters/opencode/tools.ts +174 -0
- package/src/adapters/opencode/v2.ts +367 -0
- package/src/adapters/opencode/writer.ts +379 -0
- package/src/adapters/openhands/index.ts +8 -0
- package/src/adapters/openhands/reader.ts +222 -0
- package/src/adapters/openhands/records.ts +136 -0
- package/src/adapters/openhands/writer.ts +404 -0
- package/src/adapters/pearai/index.ts +10 -0
- package/src/adapters/pi/index.ts +28 -0
- package/src/adapters/pi/reader.ts +261 -0
- package/src/adapters/pi/records.ts +99 -0
- package/src/adapters/pi/writer.ts +362 -0
- package/src/adapters/qoder-cli/index.ts +8 -0
- package/src/adapters/qoder-cli/reader.ts +83 -0
- package/src/adapters/qoder-cli/records.ts +45 -0
- package/src/adapters/qoder-cli/writer.ts +72 -0
- package/src/adapters/qwen-code/index.ts +84 -0
- package/src/adapters/qwen-code/records.ts +194 -0
- package/src/adapters/qwen-code/writer.ts +224 -0
- package/src/adapters/tabnine-cli/index.ts +8 -0
- package/src/adapters/tabnine-cli/reader.ts +106 -0
- package/src/adapters/tabnine-cli/records.ts +33 -0
- package/src/adapters/tabnine-cli/writer.ts +156 -0
- package/src/adapters/types.ts +73 -0
- package/src/adapters/void/index.ts +9 -0
- package/src/adapters/void/reader.ts +48 -0
- package/src/adapters/void/records.ts +285 -0
- package/src/adapters/void/writer.ts +93 -0
- package/src/adapters/vscode-copilot/index.ts +9 -0
- package/src/adapters/vscode-copilot/reader.ts +212 -0
- package/src/adapters/vscode-copilot/records.ts +388 -0
- package/src/adapters/vscode-copilot/shared.ts +68 -0
- package/src/adapters/vscode-copilot/writer.ts +546 -0
- package/src/adapters/zed/index.ts +8 -0
- package/src/adapters/zed/reader.ts +198 -0
- package/src/adapters/zed/records.ts +87 -0
- package/src/adapters/zed/writer.ts +341 -0
- package/src/brief-writers.ts +71 -0
- package/src/brief.ts +238 -0
- package/src/cli.ts +351 -0
- package/src/dashboard/cli.ts +148 -0
- package/src/dashboard/conversions.ts +170 -0
- package/src/dashboard/report.ts +261 -0
- package/src/dashboard/send.ts +213 -0
- package/src/dashboard/state.ts +95 -0
- package/src/fsutil.ts +133 -0
- package/src/gateway/common.ts +232 -0
- package/src/gateway/connect.ts +847 -0
- package/src/gateway/connectors/aider.ts +356 -0
- package/src/gateway/connectors/amazon-q-cli.ts +6 -0
- package/src/gateway/connectors/amazon-q-ide.ts +6 -0
- package/src/gateway/connectors/antigravity.ts +6 -0
- package/src/gateway/connectors/auggie.ts +6 -0
- package/src/gateway/connectors/cline-sdk.ts +85 -0
- package/src/gateway/connectors/cline.ts +34 -0
- package/src/gateway/connectors/codebuff.ts +6 -0
- package/src/gateway/connectors/continue.ts +579 -0
- package/src/gateway/connectors/copilot-cli.ts +138 -0
- package/src/gateway/connectors/crush.ts +313 -0
- package/src/gateway/connectors/cursor-cli.ts +6 -0
- package/src/gateway/connectors/cursor.ts +6 -0
- package/src/gateway/connectors/devin.ts +6 -0
- package/src/gateway/connectors/factory-droid.ts +308 -0
- package/src/gateway/connectors/goose.ts +79 -0
- package/src/gateway/connectors/grok-cli.ts +82 -0
- package/src/gateway/connectors/hermes.ts +230 -0
- package/src/gateway/connectors/iflow-cli.ts +50 -0
- package/src/gateway/connectors/index.ts +86 -0
- package/src/gateway/connectors/kilo-cli.ts +434 -0
- package/src/gateway/connectors/kilo-code.ts +32 -0
- package/src/gateway/connectors/kimi-code.ts +241 -0
- package/src/gateway/connectors/kiro-cli.ts +6 -0
- package/src/gateway/connectors/kiro-ide.ts +6 -0
- package/src/gateway/connectors/letta-code.ts +170 -0
- package/src/gateway/connectors/mastracode.ts +91 -0
- package/src/gateway/connectors/mistral-vibe.ts +495 -0
- package/src/gateway/connectors/muse.ts +27 -0
- package/src/gateway/connectors/omp.ts +279 -0
- package/src/gateway/connectors/openhands.ts +296 -0
- package/src/gateway/connectors/pearai.ts +52 -0
- package/src/gateway/connectors/pi.ts +357 -0
- package/src/gateway/connectors/qoder-cli.ts +6 -0
- package/src/gateway/connectors/qwen-code.ts +411 -0
- package/src/gateway/connectors/roo.ts +43 -0
- package/src/gateway/connectors/tabnine-cli.ts +6 -0
- package/src/gateway/connectors/void.ts +6 -0
- package/src/gateway/connectors/vscode-copilot.ts +286 -0
- package/src/gateway/connectors/windsurf.ts +6 -0
- package/src/gateway/connectors/zed.ts +29 -0
- package/src/gateway/signin.ts +323 -0
- package/src/handoff.ts +104 -0
- package/src/hooks.ts +455 -0
- package/src/memory/audit.ts +514 -0
- package/src/memory/blobs.ts +94 -0
- package/src/memory/cli.ts +117 -0
- package/src/memory/distill.ts +381 -0
- package/src/memory/externalize.ts +435 -0
- package/src/memory/lookups.ts +70 -0
- package/src/memory/mcp-install.ts +401 -0
- package/src/memory/mcp-server.ts +296 -0
- package/src/memory/prepare.ts +179 -0
- package/src/memory/preview.ts +154 -0
- package/src/memory/project.ts +35 -0
- package/src/memory/redact.ts +72 -0
- package/src/memory/relevance.ts +187 -0
- package/src/memory/repomap.ts +516 -0
- package/src/memory/store.ts +270 -0
- package/src/memory/sync.ts +166 -0
- package/src/memory/tidy.ts +193 -0
- package/src/memory/types.ts +118 -0
- package/src/org/admin-ops.ts +96 -0
- package/src/org/auth.ts +112 -0
- package/src/org/backup.ts +115 -0
- package/src/org/cli.ts +438 -0
- package/src/org/client.ts +404 -0
- package/src/org/config.ts +44 -0
- package/src/org/crypto.ts +537 -0
- package/src/org/db.ts +435 -0
- package/src/org/graph.ts +318 -0
- package/src/org/oidc.ts +435 -0
- package/src/org/policy.ts +398 -0
- package/src/org/server.ts +920 -0
- package/src/org/sso.ts +316 -0
- package/src/org/tls.ts +66 -0
- package/src/org/types.ts +193 -0
- package/src/self.ts +68 -0
- package/src/session/attachments.ts +53 -0
- package/src/session/peekcache.ts +50 -0
- package/src/session/types.ts +104 -0
- package/src/session/util.ts +130 -0
- package/src/sqlite.ts +48 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, statSync } from "node:fs"
|
|
2
|
+
import { basename, join } from "node:path"
|
|
3
|
+
import { isDir, ls, mtime, readJson } from "../../fsutil.ts"
|
|
4
|
+
import type { Attachment, Session, Turn } from "../../session/types.ts"
|
|
5
|
+
import { asText, clip, header, lossCounter, noteModel } from "../../session/util.ts"
|
|
6
|
+
import type { Reader, SessionRef } from "../types.ts"
|
|
7
|
+
import { CHAT_MESSAGES, CHAT_META, CONTINUED_NOTE, LABEL, RUN_STATE, SYSTEM_WRAPPED, TOOL, chatTime, codebuffKind, projectsDir, resultText, summaryOf } from "./records.ts"
|
|
8
|
+
|
|
9
|
+
// Codebuff / Freebuff chats (see records.ts). A chat is read from what the model is sent, run-state.json's
|
|
10
|
+
// messageHistory: user prompts (tagged USER_PROMPT), assistant text, reasoning and tool-call parts, tool
|
|
11
|
+
// messages with JSON results, and the runtime's <conversation_summary> message after a compaction. The
|
|
12
|
+
// runtime's own scaffolding (instructions, step prompts, <system> notes) is skipped. Without a run state the
|
|
13
|
+
// on-screen chat-messages.json is read instead.
|
|
14
|
+
|
|
15
|
+
type Json = Record<string, any>
|
|
16
|
+
|
|
17
|
+
const textOf = (content: unknown) =>
|
|
18
|
+
typeof content === "string"
|
|
19
|
+
? content
|
|
20
|
+
: (Array.isArray(content) ? content : [])
|
|
21
|
+
.filter((p: Json) => p?.type === "text" && typeof p.text === "string")
|
|
22
|
+
.map((p: Json) => p.text as string)
|
|
23
|
+
.join("")
|
|
24
|
+
|
|
25
|
+
/** A prompt the runtime re-sends to resume a turn after a compaction (conversation-summary.ts). */
|
|
26
|
+
const CONTINUATION_TAG = "CONTINUATION_PROMPT"
|
|
27
|
+
/** A message sent while the agent was working, as the runtime frames it (util/messages.ts frameSteeringText). */
|
|
28
|
+
const STEERING = /^<user_message_sent_while_working>\n([\s\S]*?)\n<\/user_message_sent_while_working>[\s\S]*$/
|
|
29
|
+
|
|
30
|
+
/** The first prompt, for titles: chat-meta.json when it still matches the messages file, as /history uses it. */
|
|
31
|
+
function firstPrompt(dir: string): string | undefined {
|
|
32
|
+
const meta = readJson<Json>(join(dir, CHAT_META))
|
|
33
|
+
try {
|
|
34
|
+
const st = statSync(join(dir, CHAT_MESSAGES))
|
|
35
|
+
if (meta && typeof meta.firstPrompt === "string" && meta.messagesSize === st.size && meta.messagesMtimeMs === st.mtimeMs) return meta.firstPrompt
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
const msgs = readJson<Json[]>(join(dir, CHAT_MESSAGES))
|
|
40
|
+
const first = Array.isArray(msgs) ? msgs.find((m) => m?.variant === "user" && typeof m.content === "string" && m.content.trim()) : undefined
|
|
41
|
+
return first?.content.trim()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The project folder a chat ran in, read from the start of run-state.json's fileContext without parsing it all. */
|
|
45
|
+
function projectRoot(dir: string): string | undefined {
|
|
46
|
+
const path = join(dir, RUN_STATE)
|
|
47
|
+
let size = 0
|
|
48
|
+
try {
|
|
49
|
+
size = statSync(path).size
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
// fileContext follows messageHistory, so search a window of the file rather than the whole of a long chat.
|
|
54
|
+
const fd = openSync(path, "r")
|
|
55
|
+
try {
|
|
56
|
+
const chunk = 1 << 20
|
|
57
|
+
const buf = Buffer.alloc(chunk + 4096)
|
|
58
|
+
for (let pos = 0; pos < size; pos += chunk) {
|
|
59
|
+
const n = readSync(fd, buf, 0, buf.length, pos)
|
|
60
|
+
const m = buf.subarray(0, n).toString("utf8").match(/"fileContext":\{"projectRoot":("(?:[^"\\]|\\.)*")/)
|
|
61
|
+
if (m) return JSON.parse(m[1]!) as string
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
closeSync(fd)
|
|
65
|
+
}
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function readChat(dir: string, id: string): Session {
|
|
70
|
+
const run = readJson<Json>(join(dir, RUN_STATE))
|
|
71
|
+
const state = run?.sessionState as Json | undefined
|
|
72
|
+
const history = state?.mainAgentState?.messageHistory as Json[] | undefined
|
|
73
|
+
const h = header(TOOL, id, { sourcePath: dir, project: { cwd: state?.fileContext?.projectRoot ?? state?.fileContext?.cwd }, startedAt: chatTime(id), updatedAt: mtime(join(dir, CHAT_MESSAGES)) ?? mtime(dir) })
|
|
74
|
+
noteModel(h, state?.mainAgentState?.contextTokenBaseline?.model)
|
|
75
|
+
const lost = lossCounter()
|
|
76
|
+
const turns: Turn[] = []
|
|
77
|
+
if (Array.isArray(history)) readHistory(history, turns, h, lost)
|
|
78
|
+
else if (!readScreen(dir, turns, lost)) throw new Error(`no readable chat in ${dir}`)
|
|
79
|
+
lost.flush(h)
|
|
80
|
+
const first = turns.find((t) => t.kind === "user") as Extract<Turn, { kind: "user" }> | undefined
|
|
81
|
+
if (first) h.title = first.text.replace(/\s+/g, " ").trim().slice(0, 80)
|
|
82
|
+
return { header: h, turns }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readHistory(history: Json[], turns: Turn[], h: Session["header"], lost: ReturnType<typeof lossCounter>) {
|
|
86
|
+
const calls = new Map<string, string>()
|
|
87
|
+
const answered = new Set<string>()
|
|
88
|
+
const at = (m: Json) => (typeof m.sentAt === "number" ? new Date(m.sentAt).toISOString() : undefined)
|
|
89
|
+
for (const m of history) {
|
|
90
|
+
const when = at(m)
|
|
91
|
+
const stamp = when ? { at: when } : {}
|
|
92
|
+
if (m.role === "user") {
|
|
93
|
+
const text = textOf(m.content)
|
|
94
|
+
const tags: string[] = Array.isArray(m.tags) ? m.tags : []
|
|
95
|
+
const summary = summaryOf(text)
|
|
96
|
+
if (summary !== undefined) {
|
|
97
|
+
turns.push({ kind: "compaction", ...stamp, summary })
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
// The person's prompts carry USER_PROMPT; every other tag marks a note the runtime added itself.
|
|
101
|
+
if (tags.length ? !tags.includes("USER_PROMPT") || tags.includes(CONTINUATION_TAG) : SYSTEM_WRAPPED.test(text.trim())) {
|
|
102
|
+
const note = text.match(CONTINUED_NOTE)
|
|
103
|
+
if (note && !turns.length) h.parent = { id: note[1]!, kind: "continued" }
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
const parts: Json[] = Array.isArray(m.content) ? m.content : []
|
|
107
|
+
const images = parts.filter((p) => p?.type === "image" || p?.type === "file").length
|
|
108
|
+
if (images) lost.add("images (kept inline by Codebuff, not as files)", images)
|
|
109
|
+
const attachments: Attachment[] = Array.from({ length: images }, () => ({ kind: "image" }))
|
|
110
|
+
const said = text.replace(STEERING, "$1")
|
|
111
|
+
if (said.trim() || images) turns.push({ kind: "user", ...stamp, text: said, ...(images ? { attachments } : {}) })
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (m.role === "assistant") {
|
|
115
|
+
for (const p of (Array.isArray(m.content) ? m.content : [{ type: "text", text: m.content }]) as Json[]) {
|
|
116
|
+
if (p?.type === "text" && typeof p.text === "string" && p.text.trim()) turns.push({ kind: "assistant", ...stamp, text: p.text, ...(h.models[0] ? { model: h.models[0] } : {}) })
|
|
117
|
+
else if (p?.type === "reasoning") {
|
|
118
|
+
if (typeof p.text === "string" && p.text.trim()) turns.push({ kind: "reasoning", ...stamp, text: p.text })
|
|
119
|
+
if (p.providerOptions) lost.add("signed reasoning")
|
|
120
|
+
} else if (p?.type === "tool-call") {
|
|
121
|
+
const id = String(p.toolCallId ?? "")
|
|
122
|
+
if (!id || calls.has(id)) {
|
|
123
|
+
lost.add("tool calls without a usable id")
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
const name = String(p.toolName ?? "tool")
|
|
127
|
+
calls.set(id, name)
|
|
128
|
+
turns.push({ kind: "tool_call", ...stamp, id, name, toolKind: codebuffKind(name), input: p.input ?? {} })
|
|
129
|
+
if (name === "spawn_agents" || name === "spawn_agent_inline") lost.add("subagent transcripts (only their results are kept)")
|
|
130
|
+
if ((name === "write_file" || name === "str_replace") && typeof p.input?.path === "string") turns.push({ kind: "file_edit", path: p.input.path, callId: id })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
if (m.role === "tool") {
|
|
136
|
+
const id = String(m.toolCallId ?? "")
|
|
137
|
+
if (!calls.has(id)) {
|
|
138
|
+
lost.add("results without a call")
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
if (answered.has(id)) {
|
|
142
|
+
lost.add("repeated results")
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
answered.add(id)
|
|
146
|
+
const r = resultText(String(m.toolName ?? calls.get(id)), m.content)
|
|
147
|
+
if (r.media) lost.add("images in tool results", r.media)
|
|
148
|
+
const c = clip(r.text)
|
|
149
|
+
turns.push({ kind: "tool_result", ...stamp, callId: id, ok: r.ok, output: c.text, ...(c.truncated ? { truncated: true } : {}) })
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
if (m.role !== "system") lost.add(`unknown message roles: ${String(m.role)}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The on-screen messages, for a chat whose run state is missing. */
|
|
157
|
+
function readScreen(dir: string, turns: Turn[], lost: ReturnType<typeof lossCounter>): boolean {
|
|
158
|
+
const msgs = readJson<Json[]>(join(dir, CHAT_MESSAGES))
|
|
159
|
+
if (!Array.isArray(msgs)) return false
|
|
160
|
+
lost.add("the model's history (read from the on-screen chat, run-state.json is missing)")
|
|
161
|
+
const walk = (blocks: Json[]) => {
|
|
162
|
+
for (const b of blocks) {
|
|
163
|
+
if (b?.type === "text" && typeof b.content === "string" && b.content.trim()) turns.push(b.textType === "reasoning" ? { kind: "reasoning", text: b.content } : { kind: "assistant", text: b.content })
|
|
164
|
+
else if (b?.type === "tool" && b.toolCallId) {
|
|
165
|
+
turns.push({ kind: "tool_call", id: String(b.toolCallId), name: String(b.toolName), toolKind: codebuffKind(String(b.toolName)), input: b.input ?? {} })
|
|
166
|
+
turns.push({ kind: "tool_result", callId: String(b.toolCallId), ok: true, output: clip(asText(b.output ?? "")).text })
|
|
167
|
+
} else if (b?.type === "agent" && Array.isArray(b.blocks)) {
|
|
168
|
+
lost.add("subagent transcripts (only their results are kept)")
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const m of msgs) {
|
|
173
|
+
if (m?.variant === "user" && typeof m.content === "string" && m.content.trim()) turns.push({ kind: "user", text: m.content })
|
|
174
|
+
else if (m?.variant === "ai" && String(m.id ?? "").startsWith("ai-")) {
|
|
175
|
+
if (m.content) turns.push({ kind: "assistant", text: String(m.content) })
|
|
176
|
+
walk(Array.isArray(m.blocks) ? m.blocks : [])
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const codebuffReader: Reader = {
|
|
183
|
+
tool: TOOL,
|
|
184
|
+
label: LABEL,
|
|
185
|
+
|
|
186
|
+
async list(home: string): Promise<SessionRef[]> {
|
|
187
|
+
const refs: SessionRef[] = []
|
|
188
|
+
for (const project of ls(projectsDir(home))) {
|
|
189
|
+
const chats = join(projectsDir(home), project, "chats")
|
|
190
|
+
for (const id of ls(chats)) {
|
|
191
|
+
const dir = join(chats, id)
|
|
192
|
+
if (!isDir(dir)) continue
|
|
193
|
+
// As /history lists: chats with messages, newest folder first.
|
|
194
|
+
const title = firstPrompt(dir)
|
|
195
|
+
if (!title) continue
|
|
196
|
+
const cwd = projectRoot(dir)
|
|
197
|
+
refs.push({
|
|
198
|
+
tool: TOOL,
|
|
199
|
+
sourceId: id,
|
|
200
|
+
path: dir,
|
|
201
|
+
title: title.replace(/\s+/g, " ").trim().slice(0, 80),
|
|
202
|
+
...(cwd ? { cwd } : {}),
|
|
203
|
+
updatedAt: mtime(dir),
|
|
204
|
+
extra: { project },
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return refs.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
async read(ref: SessionRef): Promise<Session> {
|
|
212
|
+
if (!isDir(ref.path)) throw new Error(`no Codebuff chat at ${ref.path}`)
|
|
213
|
+
return readChat(ref.path, ref.sourceId || basename(ref.path))
|
|
214
|
+
},
|
|
215
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from "node:fs"
|
|
2
|
+
import { basename, dirname, join } from "node:path"
|
|
3
|
+
import type { ToolKind } from "../../session/types.ts"
|
|
4
|
+
import { asText, toolKindOf } from "../../session/util.ts"
|
|
5
|
+
|
|
6
|
+
// What Codebuff / Freebuff chats look like, as far as this adapter relies on it. Checked against the source of
|
|
7
|
+
// CodebuffAI/freebuff at 0f5dfd6 (2026-11-08), whose release packages are freebuff 0.2.36 and codebuff 1.0.688:
|
|
8
|
+
// cli/src/utils/{run-state-storage,chat-meta,chat-history,config-dir,message-history,send-message-helpers}.ts,
|
|
9
|
+
// cli/src/project-files.ts, sdk/src/run-state.ts, common/src/types/{session-state,messages/*}.ts,
|
|
10
|
+
// common/src/util/messages.ts, packages/agent-runtime/src/{run-agent-step,compact-history}.ts and
|
|
11
|
+
// tools/stream-parser.ts. Catalogue entry 19.
|
|
12
|
+
//
|
|
13
|
+
// Both CLIs keep their chats in ~/.config/manicode/projects/<basename of the project folder>/chats/<chat id>/,
|
|
14
|
+
// the chat id being the start time as an ISO string with ':' replaced by '-'. A chat is three files:
|
|
15
|
+
// run-state.json (the SDK RunState; sessionState.mainAgentState.messageHistory is what the model is sent,
|
|
16
|
+
// fileContext is the project snapshot the system prompt is built from), chat-messages.json (the CLI's
|
|
17
|
+
// on-screen ChatMessage list) and chat-meta.json (a summary /history lists from, bound to the messages file by
|
|
18
|
+
// its size and mtime). `freebuff --continue <chat id>` (or `codebuff --continue`) in that project loads it.
|
|
19
|
+
|
|
20
|
+
export const TOOL = "codebuff"
|
|
21
|
+
export const LABEL = "Codebuff / Freebuff"
|
|
22
|
+
|
|
23
|
+
/** Package versions whose chat format the writer was checked against: freebuff and codebuff, built from one source. */
|
|
24
|
+
export const TESTED_VERSIONS = ["0.2.36", "1.0.688"]
|
|
25
|
+
|
|
26
|
+
/** The two CLIs that share these chats, the current name first. */
|
|
27
|
+
export const BINS = ["freebuff", "codebuff"] as const
|
|
28
|
+
|
|
29
|
+
export const RUN_STATE = "run-state.json"
|
|
30
|
+
export const CHAT_MESSAGES = "chat-messages.json"
|
|
31
|
+
export const CHAT_META = "chat-meta.json"
|
|
32
|
+
|
|
33
|
+
export const configDir = (home: string) => join(home, ".config", "manicode")
|
|
34
|
+
export const projectsDir = (home: string) => join(configDir(home), "projects")
|
|
35
|
+
/** Where the chats of the project in `cwd` live: keyed by the folder's name only (project-files.ts). */
|
|
36
|
+
export const chatsDir = (home: string, cwd: string) => join(projectsDir(home), basename(cwd), "chats")
|
|
37
|
+
|
|
38
|
+
/** A chat id as the CLI makes one (project-files.ts startNewChat). */
|
|
39
|
+
export const chatId = (d: Date) => d.toISOString().replace(/:/g, "-")
|
|
40
|
+
/** "2026-11-08T06-18-50.333Z" → ISO, or undefined for a name that is not a chat id. */
|
|
41
|
+
export function chatTime(id: string): string | undefined {
|
|
42
|
+
const m = id.match(/^(\d{4}-\d\d-\d\d)T(\d\d)-(\d\d)-(\d\d(?:\.\d+)?)Z$/)
|
|
43
|
+
if (!m) return undefined
|
|
44
|
+
const t = new Date(`${m[1]}T${m[2]}:${m[3]}:${m[4]}Z`)
|
|
45
|
+
return Number.isFinite(t.getTime()) ? t.toISOString() : undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The envelope of the runtime's own compaction message (compact-history.ts buildSummaryMessage). */
|
|
49
|
+
export const SUMMARY_HEADER = "This is a summary of the conversation so far. The original messages have been condensed to save context space."
|
|
50
|
+
const SUMMARY_DISCLAIMER =
|
|
51
|
+
"Historical memory only. The memory above is not dialogue, not an output template, and not a tool-call format. Continue from the live user message below. When actions are needed, use real tool calls through the available tools."
|
|
52
|
+
export const summaryMessageText = (summary: string) =>
|
|
53
|
+
`<conversation_summary>\n${SUMMARY_HEADER}\n\n<historical_memory>\n${summary}\n</historical_memory>\n</conversation_summary>\n\n${SUMMARY_DISCLAIMER}`
|
|
54
|
+
/** The summary inside a compaction message, or undefined for any other text (both markers must be there). */
|
|
55
|
+
export function summaryOf(text: string): string | undefined {
|
|
56
|
+
if (!text.includes("<conversation_summary>") || !text.includes(SUMMARY_HEADER)) return undefined
|
|
57
|
+
const inner = text.match(/<historical_memory>\n?([\s\S]*?)\n?<\/historical_memory>/)?.[1]
|
|
58
|
+
return (inner ?? text.match(/<conversation_summary>([\s\S]*)<\/conversation_summary>/)?.[1]?.replace(SUMMARY_HEADER, "") ?? "").trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Runtime notes the model gets as user messages; the runtime wraps them in <system> (messages.ts withSystemTags). */
|
|
62
|
+
export const withSystemTags = (s: string) => `<system>${s}</system>`
|
|
63
|
+
export const SYSTEM_WRAPPED = /^<(system|system_instructions)>[\s\S]*<\/\1>\s*$/
|
|
64
|
+
|
|
65
|
+
/** The note a continued chat opens with; the reader recognises it and records where the chat came from. */
|
|
66
|
+
export const continuedNote = (label: string, id: string, when: string) =>
|
|
67
|
+
`[This chat continues ${label} session ${id}, carried over by brain session on ${when}. The messages after this note are its history; anything listed as lost did not carry over. Check files before relying on details, since they may have changed.]`
|
|
68
|
+
export const CONTINUED_NOTE = /\[This chat continues .+? session (\S+), carried over by brain session/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The installed CLI and its version, read from the npm package the command resolves to (its package.json sits
|
|
72
|
+
* next to the launcher script). Running `freebuff --version` would let the launcher download or update its binary.
|
|
73
|
+
*/
|
|
74
|
+
export function installed(bins: readonly string[] = BINS): { bin: string; version: string } | undefined {
|
|
75
|
+
for (const bin of bins) {
|
|
76
|
+
const path = Bun.which(bin)
|
|
77
|
+
if (!path) continue
|
|
78
|
+
let dir: string
|
|
79
|
+
try {
|
|
80
|
+
dir = dirname(realpathSync(path))
|
|
81
|
+
} catch {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
for (let i = 0; i < 3; i++, dir = dirname(dir)) {
|
|
85
|
+
try {
|
|
86
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as { name?: string; version?: string }
|
|
87
|
+
if (pkg.name === bin && pkg.version) return { bin, version: pkg.version }
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Codebuff's own tools in portable terms; others by name. */
|
|
95
|
+
export function codebuffKind(name: string): ToolKind {
|
|
96
|
+
switch (name) {
|
|
97
|
+
case "run_terminal_command":
|
|
98
|
+
return "shell"
|
|
99
|
+
case "read_files":
|
|
100
|
+
case "read_subtree":
|
|
101
|
+
case "read_docs":
|
|
102
|
+
return "read"
|
|
103
|
+
case "write_file":
|
|
104
|
+
case "propose_write_file":
|
|
105
|
+
return "write"
|
|
106
|
+
case "str_replace":
|
|
107
|
+
case "propose_str_replace":
|
|
108
|
+
case "apply_patch":
|
|
109
|
+
return "edit"
|
|
110
|
+
case "code_search":
|
|
111
|
+
case "find_files":
|
|
112
|
+
case "glob":
|
|
113
|
+
return "search"
|
|
114
|
+
case "list_directory":
|
|
115
|
+
return "list"
|
|
116
|
+
case "web_search":
|
|
117
|
+
case "read_url":
|
|
118
|
+
return "web"
|
|
119
|
+
case "spawn_agents":
|
|
120
|
+
case "spawn_agent_inline":
|
|
121
|
+
return "task"
|
|
122
|
+
case "write_todos":
|
|
123
|
+
return "todo"
|
|
124
|
+
case "ask_user":
|
|
125
|
+
return "ask"
|
|
126
|
+
case "think_deeply":
|
|
127
|
+
case "end_turn":
|
|
128
|
+
case "set_output":
|
|
129
|
+
case "task_completed":
|
|
130
|
+
return "other"
|
|
131
|
+
}
|
|
132
|
+
return toolKindOf(name)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type Json = Record<string, any>
|
|
136
|
+
|
|
137
|
+
/** A tool message's content (ToolResultOutput[]) as the text a person would read, and whether it failed. */
|
|
138
|
+
export function resultText(toolName: string, content: unknown): { ok: boolean; text: string; media: number } {
|
|
139
|
+
const parts = Array.isArray(content) ? (content as Json[]) : []
|
|
140
|
+
let ok = true
|
|
141
|
+
let media = 0
|
|
142
|
+
const texts: string[] = []
|
|
143
|
+
for (const p of parts) {
|
|
144
|
+
if (p?.type === "media") {
|
|
145
|
+
media++
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
if (p?.type !== "json") continue
|
|
149
|
+
const v = p.value as unknown
|
|
150
|
+
if (v && typeof v === "object" && !Array.isArray(v) && typeof (v as Json).errorMessage === "string") {
|
|
151
|
+
ok = false
|
|
152
|
+
texts.push((v as Json).errorMessage)
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
if (toolName === "run_terminal_command" && v && typeof v === "object" && !Array.isArray(v)) {
|
|
156
|
+
const r = v as Json
|
|
157
|
+
if (typeof r.exitCode === "number" && r.exitCode !== 0) ok = false
|
|
158
|
+
const out = [r.stdout, r.stderr].filter((s) => typeof s === "string" && s).join("")
|
|
159
|
+
texts.push(out || (typeof r.message === "string" ? r.message : asText(v)))
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
if (toolName === "read_files" && Array.isArray(v)) {
|
|
163
|
+
texts.push(v.map((f: Json) => (typeof f?.content === "string" ? (v.length > 1 ? `${f.path}:\n${f.content}` : f.content) : asText(f))).join("\n\n"))
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (v && typeof v === "object" && !Array.isArray(v) && typeof (v as Json).message === "string" && Object.keys(v).every((k) => k === "message" || k === "file")) {
|
|
167
|
+
texts.push((v as Json).message)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
texts.push(asText(v))
|
|
171
|
+
}
|
|
172
|
+
return { ok, text: texts.join("\n"), media }
|
|
173
|
+
}
|