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