@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.
@@ -1,6 +1,6 @@
1
1
  // Sprint 51.5 T1 — schema-introspection audit-upgrade.
2
2
  //
3
- // Brad's 2026-05-02 jizzard-brain report (INSTALLER-PITFALLS.md ledger #13)
3
+ // Brad's 2026-05-02 peer install report (INSTALLER-PITFALLS.md ledger #13)
4
4
  // surfaced Class A — schema drift. The user upgraded npm packages but the
5
5
  // database stayed frozen at first-kickstart: graph-inference Edge Function
6
6
  // never deployed, vault key never created, Mnestra migrations 009-015 + TD
@@ -124,7 +124,7 @@ const PROBES = Object.freeze([
124
124
  // the rich rag-system column set to memory_sessions; canonical engram
125
125
  // mig 001 only ships (id, project, summary, metadata, created_at).
126
126
  // Probe for memory_sessions.session_id (the most distinctive of the
127
- // mig-017 columns) and apply mig 017 if absent. Idempotent on petvetbid
127
+ // mig-017 columns) and apply mig 017 if absent. Idempotent on the daily-driver project
128
128
  // where the columns are already present from hand-applied DDL.
129
129
  name: 'memory_sessions.session_id',
130
130
  kind: 'mnestra',
@@ -180,7 +180,7 @@ const PROBES = Object.freeze([
180
180
  // Sprint 51.6 T3 — Brad's Bug D: function-existence probes (cron schedule
181
181
  // checks for jobname presence) are not enough. The deployed Edge Function
182
182
  // SOURCE may be stale even when the cron job and function both exist.
183
- // jizzard-brain on 2026-05-03: deployed rumen-tick was missing the
183
+ // peer install on 2026-05-03: deployed rumen-tick was missing the
184
184
  // SUPABASE_DB_URL fallback that Sprint 51.5 T1 added; cron probe said
185
185
  // "present", source was old. The marker check below detects that drift.
186
186
  //
@@ -135,7 +135,7 @@ async function fetchCandidatePairs(
135
135
  // m2 is the outer m1 (which IS recent). So filtering only m1 by `since`
136
136
  // is sufficient and saves ~99% of work in steady state.
137
137
  //
138
- // EXPLAIN ANALYZE on petvetbid corpus (5,822 active rows, 2026-04-28):
138
+ // EXPLAIN ANALYZE on the daily-driver project corpus (5,822 active rows, 2026-04-28):
139
139
  // 13.5s cold start (since=NULL), HNSW correctly engaged, 718 raw
140
140
  // matches → 359 unique pairs at threshold 0.85.
141
141
  const result = await sql.unsafe(
@@ -39,7 +39,7 @@
39
39
  * 9. (Sprint 51.6 T3) POSTs ONE row to Supabase /rest/v1/memory_sessions with
40
40
  * Prefer: resolution=merge-duplicates so SessionEnd-fires-twice resolves
41
41
  * to a single row. Requires Mnestra migration 017 on canonical installs;
42
- * petvetbid already has the rich schema from rag-system bootstrap.
42
+ * the daily-driver project already has the rich schema from rag-system bootstrap.
43
43
  * 10. Logs every step to ~/.claude/hooks/memory-hook.log.
44
44
  *
45
45
  * Version stamp (Sprint 51.6 T3 — hook upgrade gap fix):
@@ -61,7 +61,7 @@
61
61
  * to bundled-v2 always passes the `installed >= bundled` short-circuit
62
62
  * at init-mnestra.js:550 and reaches the refresh path.
63
63
  *
64
- * @termdeck/stack-installer-hook v2
64
+ * @termdeck/stack-installer-hook v3
65
65
  *
66
66
  * Required env vars (validated at entry, after the secrets.env fallback):
67
67
  * - SUPABASE_URL e.g. https://<project-ref>.supabase.co
@@ -305,35 +305,43 @@ function parseCodexJsonl(raw) {
305
305
  }
306
306
 
307
307
  function parseGeminiJson(raw) {
308
- // Gemini CLI persists each session as a single JSON object (NOT JSONL):
309
- // { sessionId, projectHash, startTime, lastUpdated, kind,
310
- // messages: [{ id, timestamp, type: 'user'|'gemini', content }] }
311
- // user content: [{ text }]; gemini content: string. Map type='gemini' →
312
- // role='assistant' to match the rest of the dispatch shape.
308
+ // Sprint 70 T2/T3 cross-lane: handles BOTH transcript shapes
309
+ // (A) legacy single JSON object {..., messages:[{type,content}]} (.json) +
310
+ // (B) modern JSONL — header line, `{ "$set": ... }` deltas, and message lines
311
+ // {id,timestamp,type:'user'|'gemini'|'info',content} (.jsonl, ships today).
312
+ // Pre-Sprint-70 this did one whole-blob JSON.parse threw on every modern
313
+ // .jsonl file → returned [] → captured nothing. user content = array of {text};
314
+ // gemini content = string; gemini → assistant.
315
+ // Keep in sync with packages/server/src/agent-adapters/gemini.js::parseTranscript.
313
316
  if (typeof raw !== 'string' || raw.length === 0) return [];
314
- let obj;
315
- try { obj = JSON.parse(raw); } catch (_) { return []; }
316
- if (!obj || !Array.isArray(obj.messages)) return [];
317
- const messages = [];
318
- for (const msg of obj.messages) {
319
- if (!msg || typeof msg !== 'object') continue;
317
+ const out = [];
318
+ const pushMsg = (msg) => {
319
+ if (!msg || typeof msg !== 'object') return;
320
320
  let role;
321
321
  if (msg.type === 'user') role = 'user';
322
322
  else if (msg.type === 'gemini' || msg.type === 'assistant') role = 'assistant';
323
- else continue;
323
+ else return;
324
324
  const content = msg.content;
325
325
  let text = '';
326
- if (typeof content === 'string') {
327
- text = content;
328
- } else if (Array.isArray(content)) {
329
- text = content
330
- .filter((c) => c && typeof c.text === 'string')
331
- .map((c) => c.text)
332
- .join(' ');
326
+ if (typeof content === 'string') text = content;
327
+ else if (Array.isArray(content)) {
328
+ text = content.filter((c) => c && typeof c.text === 'string').map((c) => c.text).join(' ');
333
329
  }
334
- if (text) messages.push({ role, content: text.slice(0, 400) });
330
+ if (text) out.push({ role, content: text.slice(0, 400) });
331
+ };
332
+ const collect = (node) => {
333
+ if (!node || typeof node !== 'object') return;
334
+ if (Array.isArray(node.messages)) node.messages.forEach(pushMsg);
335
+ else pushMsg(node);
336
+ };
337
+ try { collect(JSON.parse(raw)); if (out.length) return out; } catch (_) { /* JSONL */ }
338
+ for (const line of raw.split(/\r?\n/)) {
339
+ const t = line.trim();
340
+ if (!t) continue;
341
+ let node; try { node = JSON.parse(t); } catch (_) { continue; }
342
+ collect(node);
335
343
  }
336
- return messages;
344
+ return out;
337
345
  }
338
346
 
339
347
  // Sprint 50 T1 — Grok parser. Mirrors packages/server/src/agent-adapters/grok.js
@@ -469,7 +477,7 @@ function selectTranscriptParser(sessionType) {
469
477
  // per-message timestamps. The legacy rag-system writer
470
478
  // (~/Documents/Graciella/rag-system/src/scripts/process-session.ts) populated
471
479
  // those fields by parsing the transcript JSONL passed to it on stdin, and
472
- // petvetbid's 289 baseline rows carried the rich shape from that writer.
480
+ // the daily-driver project's 289 baseline rows carried the rich shape from that writer.
473
481
  // v2 closes the gap in pure Node so the bundled hook reaches parity without
474
482
  // the rag-system dependency (Class E hidden-dependency rule).
475
483
  //
@@ -637,18 +645,31 @@ async function embedText(text, openaiKey) {
637
645
  // tag (memory_items.source_agent). Defaults to 'claude' for backwards
638
646
  // compat with Claude Code's existing SessionEnd payload, which doesn't
639
647
  // supply the field; TermDeck server's per-adapter onPanelClose
640
- // interceptor (Sprint 50 T1) sets it explicitly to 'codex'/'gemini'/'grok'
641
- // for non-Claude panels. The set is open-ended on the server side; this
642
- // constant gates only the spelling-mistake/empty-string case.
648
+ // interceptor (Sprint 50 T1) sets it explicitly to 'codex'/'gemini'/'grok'/
649
+ // 'antigravity' for non-Claude panels. The set is open-ended on the server
650
+ // side; this constant gates only the spelling-mistake/empty-string case.
651
+ //
652
+ // Sprint 70 T3: Antigravity (`agy`) is a first-class source_agent. The CLI
653
+ // binary is `agy` but the canonical provenance tag is `antigravity`, so the
654
+ // alias map below folds `agy` → `antigravity` before the allowlist check —
655
+ // an agy panel's memories must not be mis-tagged `claude`.
643
656
  const ALLOWED_SOURCE_AGENTS = new Set([
644
- 'claude', 'codex', 'gemini', 'grok', 'orchestrator',
657
+ 'claude', 'codex', 'gemini', 'grok', 'orchestrator', 'antigravity',
645
658
  ]);
646
659
 
660
+ // Alias → canonical source_agent. Keeps the binary name (`agy`) and any older
661
+ // callers from being dropped to 'claude' by the allowlist gate. Applied (after
662
+ // lowercasing) before the ALLOWED_SOURCE_AGENTS membership test.
663
+ const SOURCE_AGENT_ALIASES = {
664
+ agy: 'antigravity',
665
+ };
666
+
647
667
  function normalizeSourceAgent(raw) {
648
668
  if (typeof raw !== 'string') return 'claude';
649
669
  const v = raw.trim().toLowerCase();
650
670
  if (!v) return 'claude';
651
- return ALLOWED_SOURCE_AGENTS.has(v) ? v : 'claude';
671
+ const canonical = SOURCE_AGENT_ALIASES[v] || v;
672
+ return ALLOWED_SOURCE_AGENTS.has(canonical) ? canonical : 'claude';
652
673
  }
653
674
 
654
675
  async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, project, sessionId, sourceAgent }) {
@@ -694,10 +715,10 @@ async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, pr
694
715
  // closes it.
695
716
  //
696
717
  // Schema target: Mnestra migration 017 brings canonical engram in line with
697
- // petvetbid's rag-system flavor (session_id, summary_embedding, started_at,
718
+ // the daily-driver project's rag-system flavor (session_id, summary_embedding, started_at,
698
719
  // ended_at, duration_minutes, messages_count, transcript_path, etc). The
699
720
  // bundled hook writes the rich shape on every install — fresh-canonical
700
- // (post-mig-017) and petvetbid alike.
721
+ // (post-mig-017) and the daily-driver project alike.
701
722
  //
702
723
  // Idempotency: Prefer: resolution=merge-duplicates relies on the
703
724
  // memory_sessions_session_id_key unique constraint. Mig 017 adds it where
@@ -804,7 +825,27 @@ async function processStdinPayload(input) {
804
825
  try { stat = statSync(transcriptPath); }
805
826
  catch (e) { log(`cannot-stat-transcript: ${transcriptPath} — ${e.message}`); return; }
806
827
 
807
- if (stat.size < MIN_TRANSCRIPT_BYTES) {
828
+ // Sprint 70 T1 (A1 RED fix — ORCH 2026-06-07 19:21 ET). The raw-byte floor is
829
+ // calibrated for verbose on-disk JSONL (claude/codex/gemini/grok session files
830
+ // run 10s of KB even when short). Antigravity has no on-disk transcript — its
831
+ // capture is a synthesized COMPACT stdout-tee envelope (cleaned, de-chromed
832
+ // content only), so a genuinely-substantive short agy session is legitimately
833
+ // <5KB and the byte floor would wrongly drop it (false zero-row). Exempt
834
+ // sessionType==='antigravity' from the byte floor and gate on parsed CONTENT
835
+ // instead — require >= 1 assistant turn so an empty / no-model-output capture
836
+ // still no-ops. Do NOT lower the global floor; it correctly filters trivial
837
+ // verbose sessions for every other agent.
838
+ if (sessionType === 'antigravity') {
839
+ let agyRaw = '';
840
+ try { agyRaw = readFileSync(transcriptPath, 'utf8'); }
841
+ catch (e) { log(`cannot-read-transcript: ${transcriptPath} — ${e.message}`); return; }
842
+ const agyTurns = selectTranscriptParser(sessionType).parser(agyRaw);
843
+ const assistantTurns = agyTurns.filter((m) => m && m.role === 'assistant').length;
844
+ if (assistantTurns < 1) {
845
+ debug(`antigravity-no-assistant-turn: ${agyTurns.length} parsed, 0 assistant — skipping`);
846
+ return;
847
+ }
848
+ } else if (stat.size < MIN_TRANSCRIPT_BYTES) {
808
849
  debug(`small-transcript: ${stat.size} bytes < ${MIN_TRANSCRIPT_BYTES}, skipping`);
809
850
  return;
810
851
  }