@jhizzard/termdeck 1.6.1 → 1.8.0
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/package.json +1 -1
- package/packages/cli/src/doctor.js +100 -0
- package/packages/cli/src/init-mnestra.js +50 -6
- package/packages/cli/src/init-rumen.js +3 -3
- package/packages/client/public/app.js +341 -30
- package/packages/client/public/index.html +0 -1
- package/packages/client/public/style.css +2 -31
- package/packages/server/src/agent-adapters/agy.js +396 -0
- package/packages/server/src/agent-adapters/gemini.js +309 -42
- package/packages/server/src/agent-adapters/grok-models.js +112 -76
- package/packages/server/src/agent-adapters/index.js +19 -0
- package/packages/server/src/agent-adapters/web-chat-grok.js +259 -0
- package/packages/server/src/index.js +572 -10
- package/packages/server/src/setup/audit-upgrade.js +3 -3
- package/packages/server/src/setup/rumen/functions/graph-inference/index.ts +1 -1
- package/packages/stack-installer/assets/hooks/memory-session-end.js +73 -32
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// Antigravity CLI (`agy`) adapter — Sprint 70 T1
|
|
2
|
+
//
|
|
3
|
+
// Fifth adapter in the AGENT_ADAPTERS registry (see ./index.js). Makes an
|
|
4
|
+
// Antigravity panel a first-class TermDeck agent whose transcript is written
|
|
5
|
+
// to Mnestra at panel close, the same lifecycle Claude/Codex/Gemini/Grok get.
|
|
6
|
+
//
|
|
7
|
+
// ── The one hard constraint: NO readable on-disk transcript ──────────────────
|
|
8
|
+
// Antigravity stores conversations as protobuf at
|
|
9
|
+
// ~/.gemini/antigravity-cli/conversations/<uuid>.pb (opaque binary)
|
|
10
|
+
// and a flat prompt-history at
|
|
11
|
+
// ~/.gemini/antigravity-cli/history.jsonl ({display,ts,workspace})
|
|
12
|
+
// — the `.pb` has no readable schema and history.jsonl carries NO assistant
|
|
13
|
+
// turns (same flat-history shape codex.js explicitly rejects). Verified live
|
|
14
|
+
// 2026-06-07 (agy v1.0.0 binary / banner reports 1.0.6). So unlike every other
|
|
15
|
+
// adapter — which resolves a structured transcript FILE and lets the bundled
|
|
16
|
+
// hook parse it — agy has to capture the transcript **in-flight from the PTY
|
|
17
|
+
// stdout stream** and materialize it for the close handler.
|
|
18
|
+
//
|
|
19
|
+
// ── Capture architecture (what's load-bearing vs residual) ───────────────────
|
|
20
|
+
// LOAD-BEARING: the PTY tee. spawnTerminalSession (index.js) tees every PTY
|
|
21
|
+
// chunk into `session._stdoutCapture` when an adapter opts in via
|
|
22
|
+
// `capture.mode === 'stdout'` (this adapter is the first to do so; the other
|
|
23
|
+
// four are unchanged). A PTY is a TTY, so the child flushes on exit and the
|
|
24
|
+
// close-time buffer is lossless — the tee alone satisfies the close-proof.
|
|
25
|
+
// RESIDUAL: the `stdbuf` buffering-defense (capture.unbuffer). agy is a
|
|
26
|
+
// compiled Mach-O binary, so `libstdbuf` (LD_PRELOAD) is inert for it; the
|
|
27
|
+
// wrap is a best-effort, gracefully-degrading layer that only matters for
|
|
28
|
+
// future line-buffered C-stdio capture-mode adapters and for timelier
|
|
29
|
+
// mid-session periodic checkpoints. NOT `unbuffer` (it forks its own pty →
|
|
30
|
+
// double-pty → breaks the interactive-TTY semantics Sprint 64 T2 protects).
|
|
31
|
+
//
|
|
32
|
+
// resolveTranscriptPath reads `session._stdoutCapture`, runs parseTranscript to
|
|
33
|
+
// clean + segment, writes a **Gemini-shaped JSON envelope** to os.tmpdir(), and
|
|
34
|
+
// returns that path — exactly grok.js's "live source → tempfile envelope →
|
|
35
|
+
// existing hook" pattern, so onPanelClose's close→hook path is reused with no
|
|
36
|
+
// second write path. The envelope shape (`{messages:[{type,content}]}`) is what
|
|
37
|
+
// the bundled hook's `parseAutoDetect`/`parseGeminiJson` already consume, so
|
|
38
|
+
// agy rows ingest WITHOUT a dedicated `TRANSCRIPT_PARSERS['antigravity']` entry
|
|
39
|
+
// (decoupling T1 from T3's hook edits; T3 owns only the source_agent allowlist).
|
|
40
|
+
//
|
|
41
|
+
// ── source_agent attribution ─────────────────────────────────────────────────
|
|
42
|
+
// `name: 'antigravity'` is the canonical source_agent — onPanelClose emits
|
|
43
|
+
// `source_agent: adapter.name` (and, post-Sprint-70-T3, `adapter.sourceAgent ||
|
|
44
|
+
// adapter.name`). The explicit `sourceAgent: 'antigravity'` field below is
|
|
45
|
+
// belt-and-suspenders: self-documents intent and survives any future rename of
|
|
46
|
+
// `name`. T3 adds `'antigravity'` to the hook's ALLOWED_SOURCE_AGENTS (+ an
|
|
47
|
+
// `agy → antigravity` alias) so the row isn't coerced to 'claude'.
|
|
48
|
+
//
|
|
49
|
+
// ── Transcript fidelity (honest ceiling) ─────────────────────────────────────
|
|
50
|
+
// Capturing a rich full-screen TUI's stdout yields a FUZZY, RAG-grade transcript
|
|
51
|
+
// — not a verbatim log. agy uses truecolor ANSI, cursor positioning, a brief
|
|
52
|
+
// alt-screen (sign-in spinner: enter `?1049h` → exit `?1049l`), box-drawing
|
|
53
|
+
// rules, Braille spinners, and a slash-command menu. We strip ANSI, collapse
|
|
54
|
+
// carriage-return overdraws, drop box/Braille chrome lines, and de-duplicate
|
|
55
|
+
// redraw frames. The substantive conversation survives (it's embedded for
|
|
56
|
+
// semantic recall); precise per-turn role boundaries are approximate. The clean
|
|
57
|
+
// path is an `agy --print` panel (one-shot, plain CRLF text — verified). Full
|
|
58
|
+
// terminal emulation to perfectly reconstruct turns would be a disproportionate
|
|
59
|
+
// dependency (INSTALLER-PITFALLS Class H); best-effort is the right altitude.
|
|
60
|
+
//
|
|
61
|
+
// Contract — see ./claude.js header for the full annotated adapter shape.
|
|
62
|
+
|
|
63
|
+
'use strict';
|
|
64
|
+
|
|
65
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// Patterns. Best-effort, calibrated against the real interactive capture
|
|
67
|
+
// (2026-06-07). Status detection is NOT load-bearing for the capture proof —
|
|
68
|
+
// follow-up: tune `thinking` against a real model-turn capture (the calibration
|
|
69
|
+
// session exited before the model replied, so the thinking spinner's text label
|
|
70
|
+
// is inferred from the Gemini family, not yet observed verbatim).
|
|
71
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
// Idle / prompt indicator. agy renders an input box `> ` and a persistent
|
|
74
|
+
// "Antigravity CLI" banner line. Anchored on the banner (agy-distinctive — avoids
|
|
75
|
+
// agy's prompt regex stealing cross-adapter detection from other panels' `> `
|
|
76
|
+
// output) OR the bare input-box prompt.
|
|
77
|
+
const PROMPT = /Antigravity CLI|^[>❯]\s/m;
|
|
78
|
+
|
|
79
|
+
// Thinking indicator. Antigravity is a Gemini-family CLI (banner: "Gemini 3.5
|
|
80
|
+
// Flash"); mirror gemini/grok's working-state vocabulary. Conservative —
|
|
81
|
+
// word-anchored to avoid prose false positives.
|
|
82
|
+
const THINKING = /\b(Thinking|Generating|Working|Reasoning)\b/;
|
|
83
|
+
|
|
84
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
85
|
+
// statusFor — best-effort panel status. thinking → idle, first match wins;
|
|
86
|
+
// null leaves meta.status untouched (the contract's "no change" semantics).
|
|
87
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function statusFor(data) {
|
|
90
|
+
if (typeof data !== 'string') return null;
|
|
91
|
+
if (THINKING.test(data)) {
|
|
92
|
+
return { status: 'thinking', statusDetail: 'Antigravity is generating...' };
|
|
93
|
+
}
|
|
94
|
+
if (PROMPT.test(data)) {
|
|
95
|
+
return { status: 'idle', statusDetail: 'Waiting for input' };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Capture cleaning helpers (the raw-TUI path).
|
|
102
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
// Strip ANSI/VT control sequences. Order matters: OSC (terminated by BEL or
|
|
105
|
+
// ST) first, then CSI (ESC [ params intermediates final), then any remaining
|
|
106
|
+
// nF/Fe/Fs two-byte escapes, then a catch-all. Verified against the real agy
|
|
107
|
+
// capture (truecolor SGR, cursor moves, alt-screen toggles, bracketed-paste,
|
|
108
|
+
// cursor-shape — all removed cleanly).
|
|
109
|
+
function _stripAnsi(s) {
|
|
110
|
+
return s
|
|
111
|
+
.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, '') // OSC … BEL|ST
|
|
112
|
+
.replace(/\x1b[\[\]][\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '') // CSI (+ private)
|
|
113
|
+
.replace(/\x1b[\x20-\x2f]*[\x30-\x7e]/g, '') // nF/Fp/Fe/Fs escapes
|
|
114
|
+
.replace(/\x1b./g, ''); // any other ESC pair
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Collapse carriage-return overdraws. agy emits CRLF line endings (verified)
|
|
118
|
+
// AND lone-CR spinner redraws (`⣾…\r⣷…\r⣯…`). Normalize CRLF → LF first, then
|
|
119
|
+
// for each line keep only the text after the LAST lone CR (the final overwrite).
|
|
120
|
+
function _normalizeOverdraw(s) {
|
|
121
|
+
return s
|
|
122
|
+
.replace(/\r\n/g, '\n')
|
|
123
|
+
.split('\n')
|
|
124
|
+
.map((line) => {
|
|
125
|
+
const i = line.lastIndexOf('\r');
|
|
126
|
+
return i >= 0 ? line.slice(i + 1) : line;
|
|
127
|
+
})
|
|
128
|
+
.join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A line is "chrome" (drop it) when it's dominated by box-drawing (U+2500–257F)
|
|
132
|
+
// or Braille (U+2800–28FF, the spinner block) glyphs. ASCII rules like markdown
|
|
133
|
+
// `---`/`***` use hyphen/asterisk (NOT box-drawing), so real markdown content is
|
|
134
|
+
// never caught here. Threshold 0.5 keeps short mixed lines that carry real text.
|
|
135
|
+
function _isChromeLine(line) {
|
|
136
|
+
const stripped = line.replace(/\s/g, '');
|
|
137
|
+
if (stripped.length === 0) return true;
|
|
138
|
+
let glyphs = 0;
|
|
139
|
+
for (const ch of stripped) {
|
|
140
|
+
const cp = ch.codePointAt(0);
|
|
141
|
+
if ((cp >= 0x2500 && cp <= 0x257f) || (cp >= 0x2800 && cp <= 0x28ff)) glyphs += 1;
|
|
142
|
+
}
|
|
143
|
+
return glyphs / stripped.length >= 0.5;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Raw PTY/TUI capture → [{role, content}]. Strip → normalize → de-chrome →
|
|
147
|
+
// de-duplicate consecutive redraw frames → segment. Role attribution is
|
|
148
|
+
// best-effort: default 'assistant' (the bulk of substantive TUI text is model
|
|
149
|
+
// output); a line that is the echo of a typed prompt (sits on/after the `> `
|
|
150
|
+
// input box) is marked 'user'. Each emitted record is truncated to 400 chars to
|
|
151
|
+
// match the other adapters' parsers and the hook's summary builder.
|
|
152
|
+
function _cleanAndSegment(raw) {
|
|
153
|
+
const cleaned = _normalizeOverdraw(_stripAnsi(raw))
|
|
154
|
+
// Drop remaining C0 controls except newline/tab (e.g. BEL, backspace, EOT).
|
|
155
|
+
.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
|
|
156
|
+
|
|
157
|
+
const out = [];
|
|
158
|
+
let prevKept = null;
|
|
159
|
+
let pendingUser = false;
|
|
160
|
+
for (let line of cleaned.split('\n')) {
|
|
161
|
+
line = line.replace(/\s+$/g, '');
|
|
162
|
+
const trimmed = line.trim();
|
|
163
|
+
if (!trimmed) { prevKept = null; continue; }
|
|
164
|
+
if (_isChromeLine(line)) { prevKept = null; continue; }
|
|
165
|
+
|
|
166
|
+
// A lone `>` (or `❯`) is the empty input box — the NEXT substantive line is
|
|
167
|
+
// the user's typed/echoed prompt. Don't emit the marker itself.
|
|
168
|
+
if (/^[>❯]\s*$/.test(trimmed)) { pendingUser = true; prevKept = null; continue; }
|
|
169
|
+
|
|
170
|
+
// Collapse consecutive identical lines (alt-screen / redraw duplication).
|
|
171
|
+
if (trimmed === prevKept) continue;
|
|
172
|
+
prevKept = trimmed;
|
|
173
|
+
|
|
174
|
+
// `> text` on one line: the text after the prompt glyph is user input.
|
|
175
|
+
const promptInline = trimmed.match(/^[>❯]\s+(.+)$/);
|
|
176
|
+
if (promptInline) {
|
|
177
|
+
out.push({ role: 'user', content: promptInline[1].slice(0, 400) });
|
|
178
|
+
pendingUser = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const role = pendingUser ? 'user' : 'assistant';
|
|
182
|
+
pendingUser = false;
|
|
183
|
+
out.push({ role, content: trimmed.slice(0, 400) });
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Structured fast-path: my own resolveTranscriptPath writes a Gemini-shaped
|
|
189
|
+
// `{messages:[{type,content}]}` envelope; also accept a bare `[{role,content}]`
|
|
190
|
+
// array. Lets parseTranscript round-trip its own output, so a hook that calls
|
|
191
|
+
// THIS function on the tempfile (rather than parseAutoDetect) still works.
|
|
192
|
+
function _parseStructured(raw) {
|
|
193
|
+
let obj;
|
|
194
|
+
try { obj = JSON.parse(raw); } catch (_) { return []; }
|
|
195
|
+
const rows = Array.isArray(obj) ? obj : (obj && Array.isArray(obj.messages) ? obj.messages : null);
|
|
196
|
+
if (!rows) return [];
|
|
197
|
+
const out = [];
|
|
198
|
+
for (const m of rows) {
|
|
199
|
+
if (!m || typeof m !== 'object') continue;
|
|
200
|
+
// Accept both shapes: {role} (array form) and {type} (gemini-envelope form,
|
|
201
|
+
// where type 'gemini' maps to assistant for cross-adapter parity).
|
|
202
|
+
let role = m.role;
|
|
203
|
+
if (!role && m.type) role = (m.type === 'user') ? 'user' : 'assistant';
|
|
204
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
205
|
+
const content = m.content;
|
|
206
|
+
let text = '';
|
|
207
|
+
if (typeof content === 'string') text = content;
|
|
208
|
+
else if (Array.isArray(content)) {
|
|
209
|
+
text = content
|
|
210
|
+
.filter((c) => c && typeof c.text === 'string')
|
|
211
|
+
.map((c) => c.text)
|
|
212
|
+
.join(' ');
|
|
213
|
+
}
|
|
214
|
+
if (text) out.push({ role, content: text.slice(0, 400) });
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
220
|
+
// parseTranscript — dual-mode. Structured envelope (this adapter's own
|
|
221
|
+
// tempfile, or a {role,content} array) is parsed directly; otherwise the input
|
|
222
|
+
// is raw PTY/TUI capture and gets the ANSI-strip + de-chrome + segment path.
|
|
223
|
+
// Returns [] on empty/garbage (parity-test fail-soft contract).
|
|
224
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
function parseTranscript(raw) {
|
|
227
|
+
if (typeof raw !== 'string' || raw.length === 0) return [];
|
|
228
|
+
const trimmed = raw.trimStart();
|
|
229
|
+
if (trimmed[0] === '{' || trimmed[0] === '[') {
|
|
230
|
+
const structured = _parseStructured(raw);
|
|
231
|
+
if (structured.length > 0) return structured;
|
|
232
|
+
}
|
|
233
|
+
return _cleanAndSegment(raw);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
237
|
+
// resolveTranscriptPath — Sprint 70 T1. There is no on-disk transcript to
|
|
238
|
+
// resolve; instead we materialize the in-flight PTY capture buffer
|
|
239
|
+
// (`session._stdoutCapture`, populated by spawnTerminalSession's tee) into a
|
|
240
|
+
// tempfile the bundled hook can read. Mirrors grok.js's tempfile-envelope
|
|
241
|
+
// approach. Returns null when the panel produced no output (buffer empty /
|
|
242
|
+
// absent / parses to zero messages) so onPanelClose + the periodic-capture
|
|
243
|
+
// timer no-op cleanly.
|
|
244
|
+
//
|
|
245
|
+
// Side effect (matching grok.js): writes a tempfile. Called by BOTH onPanelClose
|
|
246
|
+
// (once, at exit) and onPanelPeriodicCapture (every interval) — each call
|
|
247
|
+
// re-materializes the current buffer, so the periodic timer's size-delta
|
|
248
|
+
// throttle sees the transcript grow correctly.
|
|
249
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
async function resolveTranscriptPath(session) {
|
|
252
|
+
const fs = require('fs');
|
|
253
|
+
const path = require('path');
|
|
254
|
+
const os = require('os');
|
|
255
|
+
if (!session || !session.meta) return null;
|
|
256
|
+
const cap = session._stdoutCapture;
|
|
257
|
+
if (!cap || !Array.isArray(cap.chunks) || cap.chunks.length === 0) return null;
|
|
258
|
+
|
|
259
|
+
const raw = cap.chunks.join('');
|
|
260
|
+
if (!raw) return null;
|
|
261
|
+
|
|
262
|
+
const messages = parseTranscript(raw);
|
|
263
|
+
if (messages.length === 0) return null;
|
|
264
|
+
|
|
265
|
+
// Gemini-shaped envelope: the bundled hook's parseAutoDetect/parseGeminiJson
|
|
266
|
+
// consume `{messages:[{type,content}]}` as-is (type 'user'|'assistant'), so no
|
|
267
|
+
// dedicated antigravity parser is required in the hook.
|
|
268
|
+
const envelope = {
|
|
269
|
+
messages: messages.map((m) => ({ type: m.role, content: m.content })),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const safeId = String(session.id || `unknown-${session.pid || ''}`)
|
|
273
|
+
.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
274
|
+
const tmpfile = path.join(os.tmpdir(), `termdeck-agy-${safeId}.json`);
|
|
275
|
+
try {
|
|
276
|
+
fs.writeFileSync(tmpfile, JSON.stringify(envelope), 'utf8');
|
|
277
|
+
} catch (_) {
|
|
278
|
+
return null; // fail-soft — a tmpfile write failure must not block teardown
|
|
279
|
+
}
|
|
280
|
+
return tmpfile;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
284
|
+
// bootPromptTemplate — Antigravity reads `AGENTS.md` (its project-prompt
|
|
285
|
+
// convention, shared with Codex/Grok via scripts/sync-agent-instructions.js).
|
|
286
|
+
// Same memory_recall + read-instructional-file + read-sprint-docs scaffold as
|
|
287
|
+
// the other adapters. Contract-complete placeholder; Sprint-46-style per-agent
|
|
288
|
+
// refinement is a follow-up.
|
|
289
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
function bootPromptTemplate(lane = {}, sprint = {}) {
|
|
292
|
+
const tn = lane.id || 'T?';
|
|
293
|
+
const sprintNum = sprint.number || '?';
|
|
294
|
+
const sprintName = sprint.name || 'unnamed';
|
|
295
|
+
const project = (lane.project || sprint.project || 'termdeck');
|
|
296
|
+
const briefing = lane.briefingPath || `docs/sprint-${sprintNum}-${sprintName}/${tn}-<lane>.md`;
|
|
297
|
+
return [
|
|
298
|
+
`You are ${tn} in Sprint ${sprintNum} (${sprintName}). Boot sequence:`,
|
|
299
|
+
`1. memory_recall(project="${project}", query="<topic>")`,
|
|
300
|
+
`2. memory_recall(query="<broader topic>")`,
|
|
301
|
+
`3. Read ~/.claude/CLAUDE.md and ./AGENTS.md`,
|
|
302
|
+
`4. Read docs/sprint-${sprintNum}-${sprintName}/PLANNING.md`,
|
|
303
|
+
`5. Read docs/sprint-${sprintNum}-${sprintName}/STATUS.md`,
|
|
304
|
+
`6. Read ${briefing}`,
|
|
305
|
+
'',
|
|
306
|
+
'Then begin. Stay in your lane. Post FINDING / FIX-PROPOSED / DONE in STATUS.md.',
|
|
307
|
+
"Don't bump versions, don't touch CHANGELOG, don't commit.",
|
|
308
|
+
].join('\n');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
312
|
+
// mcpConfig — UNVERIFIED at lane time. The brief specifies the path
|
|
313
|
+
// `~/.gemini/antigravity-cli/mcp_config.json`, which does NOT exist on disk yet,
|
|
314
|
+
// and agy's `settings.json` carries no `mcpServers` key — so agy's actual
|
|
315
|
+
// MCP-registry read path could not be confirmed against the binary. Modeled on
|
|
316
|
+
// the Gemini-family record shape (`mcpServers.NAME = {command,args,env}`, the
|
|
317
|
+
// same schema gemini.js uses) since Antigravity is a Gemini-family CLI. The
|
|
318
|
+
// shared mcp-autowire helper would CREATE this file on panel spawn. This is a
|
|
319
|
+
// non-load-bearing nicety (auto-wiring Mnestra into agy panels); if a future
|
|
320
|
+
// probe shows agy reads MCP from settings.json or another path/shape, correct
|
|
321
|
+
// here. Env-key omission discipline matches gemini.js (concrete-or-omit; agy,
|
|
322
|
+
// like Claude/Gemini, does not shell-expand `${VAR}` in MCP env).
|
|
323
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
const MNESTRA_ENV_KEYS = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY', 'OPENAI_API_KEY'];
|
|
326
|
+
|
|
327
|
+
function buildMnestraBlock({ secrets } = {}) {
|
|
328
|
+
const env = {};
|
|
329
|
+
for (const key of MNESTRA_ENV_KEYS) {
|
|
330
|
+
const value = secrets && secrets[key];
|
|
331
|
+
if (typeof value === 'string' && value.length > 0 && !/^\$\{[^}]*\}$/.test(value)) {
|
|
332
|
+
env[key] = value;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { mnestra: { command: 'mnestra', args: [], env } };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const antigravityAdapter = {
|
|
339
|
+
name: 'antigravity',
|
|
340
|
+
sessionType: 'antigravity',
|
|
341
|
+
// Explicit canonical source_agent (belt-and-suspenders vs `name`; consumed by
|
|
342
|
+
// the Sprint-70-T3 `adapter.sourceAgent || adapter.name` server change).
|
|
343
|
+
sourceAgent: 'antigravity',
|
|
344
|
+
displayName: 'Antigravity',
|
|
345
|
+
// Match the `agy` binary. `agy` shares no substring with claude/codex/gemini/
|
|
346
|
+
// grok, so this is mutually exclusive across the registry (parity test 108).
|
|
347
|
+
matches: (cmd) => typeof cmd === 'string' && /(?:^|\s|\/)agy(?:\b|$)/i.test(cmd),
|
|
348
|
+
spawn: {
|
|
349
|
+
binary: 'agy',
|
|
350
|
+
defaultArgs: [],
|
|
351
|
+
// OAuth-personal auth (agy stays on OAuth while gemini moved to API-key —
|
|
352
|
+
// the auth-segregation the Sprint 70 migration is built around). No env
|
|
353
|
+
// overlay needed; the PTY inherits the user's environment.
|
|
354
|
+
env: {},
|
|
355
|
+
// Direct spawn (no `zsh -c` wrapper) — same carve-out the other four
|
|
356
|
+
// adapters use. Required by adapter-spawn-shell-wrap.test.js:175.
|
|
357
|
+
shellWrap: false,
|
|
358
|
+
},
|
|
359
|
+
// Sprint 70 T1 — opt-in in-flight stdout capture. Absent on every other
|
|
360
|
+
// adapter, so this is the ONLY adapter spawnTerminalSession tees. `mode`
|
|
361
|
+
// selects the capture strategy; `maxBytes` tail-caps the in-memory buffer
|
|
362
|
+
// (TUI redraws inflate raw bytes far past the de-chromed content, so cap
|
|
363
|
+
// generously and keep the tail — the most recent conversation); `unbuffer`
|
|
364
|
+
// opts into the best-effort `stdbuf` buffering-defense (residual; see header).
|
|
365
|
+
capture: {
|
|
366
|
+
mode: 'stdout',
|
|
367
|
+
maxBytes: 4 * 1024 * 1024,
|
|
368
|
+
unbuffer: true,
|
|
369
|
+
},
|
|
370
|
+
patterns: {
|
|
371
|
+
prompt: PROMPT,
|
|
372
|
+
thinking: THINKING,
|
|
373
|
+
// editing / tool / error intentionally omitted — the TUI screen-scrape is
|
|
374
|
+
// too noisy for reliable line-anchored edit/tool/error detection without a
|
|
375
|
+
// calibrated real-turn capture. session.js falls back to the generic
|
|
376
|
+
// PATTERNS.error, matching gemini's conservative posture.
|
|
377
|
+
},
|
|
378
|
+
patternNames: {},
|
|
379
|
+
statusFor,
|
|
380
|
+
parseTranscript,
|
|
381
|
+
resolveTranscriptPath,
|
|
382
|
+
bootPromptTemplate,
|
|
383
|
+
costBand: 'subscription',
|
|
384
|
+
// Antigravity's input handling hasn't been pasted-against empirically; default
|
|
385
|
+
// true (bracketed-paste fast path), flip to false if a lane-time test shows
|
|
386
|
+
// the TUI input box eats the paste markers.
|
|
387
|
+
acceptsPaste: true,
|
|
388
|
+
mcpConfig: {
|
|
389
|
+
path: '~/.gemini/antigravity-cli/mcp_config.json',
|
|
390
|
+
format: 'json',
|
|
391
|
+
mcpServersKey: 'mcpServers',
|
|
392
|
+
mnestraBlock: buildMnestraBlock,
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
module.exports = antigravityAdapter;
|