@jhizzard/termdeck 1.6.0 → 1.7.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 +12 -11
- package/packages/client/public/index.html +0 -1
- package/packages/client/public/style.css +3 -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 +7 -0
- package/packages/server/src/index.js +103 -2
- 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
|
@@ -16,6 +16,9 @@ const claude = require('./claude');
|
|
|
16
16
|
const codex = require('./codex');
|
|
17
17
|
const gemini = require('./gemini');
|
|
18
18
|
const grok = require('./grok');
|
|
19
|
+
// Sprint 70 T1 — Antigravity CLI (`agy`). Registered under its canonical
|
|
20
|
+
// adapter name `antigravity` (= source_agent); the binary it matches is `agy`.
|
|
21
|
+
const antigravity = require('./agy');
|
|
19
22
|
|
|
20
23
|
// Keyed by adapter name (NOT session.meta.type — adapters expose their own
|
|
21
24
|
// `sessionType` field for that mapping). Order is iteration order for the
|
|
@@ -25,6 +28,10 @@ const AGENT_ADAPTERS = {
|
|
|
25
28
|
codex,
|
|
26
29
|
gemini,
|
|
27
30
|
grok,
|
|
31
|
+
// Listed last: its idle `> ` prompt overlaps claude's, and claude (first)
|
|
32
|
+
// claims that string in the detect loop. agy panels are normally resolved by
|
|
33
|
+
// exact-binary direct-spawn, not output sniffing, so order is not load-bearing.
|
|
34
|
+
antigravity,
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
// Convenience accessor — returns the adapter whose `sessionType` matches the
|
|
@@ -290,7 +290,13 @@ async function onPanelClose(session) {
|
|
|
290
290
|
session_id: session.id,
|
|
291
291
|
sessionType: adapter.sessionType,
|
|
292
292
|
// Sprint 50 — T2 consumes this via the new memory_items.source_agent column.
|
|
293
|
-
|
|
293
|
+
// Sprint 70 T3 — prefer an explicit `adapter.sourceAgent` provenance tag
|
|
294
|
+
// when an adapter declares one (decouples the provenance string from the
|
|
295
|
+
// registry/binary-match `name`); existing adapters omit it and fall back
|
|
296
|
+
// to `name` (behavior unchanged). The antigravity (`agy`) adapter sets
|
|
297
|
+
// sourceAgent:'antigravity'; the session-end hook's normalizeSourceAgent
|
|
298
|
+
// also aliases the binary name `agy` → `antigravity` as a safety net.
|
|
299
|
+
source_agent: adapter.sourceAgent || adapter.name,
|
|
294
300
|
};
|
|
295
301
|
|
|
296
302
|
_spawnSessionEndHookImpl(hookPath, payload, {
|
|
@@ -355,7 +361,10 @@ async function onPanelPeriodicCapture(session) {
|
|
|
355
361
|
cwd: session.meta.cwd,
|
|
356
362
|
session_id: session.id,
|
|
357
363
|
sessionType: adapter.sessionType,
|
|
358
|
-
|
|
364
|
+
// Sprint 70 T3 — same provenance contract as onPanelClose: an explicit
|
|
365
|
+
// adapter.sourceAgent wins, else fall back to adapter.name (unchanged for
|
|
366
|
+
// existing adapters). agy panels' periodic snapshots tag 'antigravity'.
|
|
367
|
+
source_agent: adapter.sourceAgent || adapter.name,
|
|
359
368
|
// Mode discriminator the hook reads in resolveFiringContext —
|
|
360
369
|
// distinguishes "TermDeck server periodic capture" from "Claude Code
|
|
361
370
|
// PreCompact harness fire."
|
|
@@ -388,6 +397,50 @@ function _resolvePeriodicCaptureIntervalMs() {
|
|
|
388
397
|
return n;
|
|
389
398
|
}
|
|
390
399
|
|
|
400
|
+
// Sprint 70 T1 — best-effort line-buffering wrap for stdout-capture adapters.
|
|
401
|
+
//
|
|
402
|
+
// The LOAD-BEARING capture mechanism is the PTY tee in spawnTerminalSession;
|
|
403
|
+
// this wrap is a RESIDUAL buffering-defense, valuable only for line-buffered
|
|
404
|
+
// C-stdio CLIs and timelier mid-session periodic checkpoints. It is inert for a
|
|
405
|
+
// compiled binary like `agy` (libstdbuf only affects glibc stdio) and a no-op
|
|
406
|
+
// on hosts without a stdbuf-family tool (stock macOS) — the tee captures
|
|
407
|
+
// everything regardless. We PREFER `stdbuf`/`gstdbuf` (GNU coreutils) because
|
|
408
|
+
// it exec()s the target IN PLACE: same controlling TTY, pid preserved, exit
|
|
409
|
+
// code propagated. We deliberately do NOT use `unbuffer` (expect) — it
|
|
410
|
+
// allocates its own pty, producing a double-pty that strips the interactive-TTY
|
|
411
|
+
// context agent CLIs need (Sprint 64 T2 carve-out 2.4 rationale).
|
|
412
|
+
let _stdbufToolCache; // undefined = unprobed, string = tool name, null = none on PATH
|
|
413
|
+
function _defaultLookStdbuf() {
|
|
414
|
+
if (_stdbufToolCache !== undefined) return _stdbufToolCache;
|
|
415
|
+
const { spawnSync } = require('child_process');
|
|
416
|
+
_stdbufToolCache = null;
|
|
417
|
+
for (const name of ['stdbuf', 'gstdbuf']) {
|
|
418
|
+
try {
|
|
419
|
+
const r = spawnSync('/bin/sh', ['-c', `command -v ${name}`], { encoding: 'utf8' });
|
|
420
|
+
if (r && r.status === 0 && typeof r.stdout === 'string' && r.stdout.trim()) {
|
|
421
|
+
_stdbufToolCache = name;
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
} catch (_) { /* try next candidate */ }
|
|
425
|
+
}
|
|
426
|
+
return _stdbufToolCache;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Returns the (possibly rewritten) { binary, args } to hand pty.spawn. No-op
|
|
430
|
+
// unless the adapter declares `capture.mode==='stdout'` AND `capture.unbuffer`.
|
|
431
|
+
// `lookPath` is dependency-injected so tests stay hermetic (no real stdbuf
|
|
432
|
+
// dependence). Resets the memo via `_resetStdbufToolCacheForTesting`.
|
|
433
|
+
function _resolveStdoutCaptureSpawn(binary, args, capture, lookPath = _defaultLookStdbuf) {
|
|
434
|
+
if (!capture || capture.mode !== 'stdout' || !capture.unbuffer) {
|
|
435
|
+
return { binary, args };
|
|
436
|
+
}
|
|
437
|
+
let tool = null;
|
|
438
|
+
try { tool = lookPath(); } catch (_) { tool = null; }
|
|
439
|
+
if (!tool) return { binary, args }; // graceful fallback — bare direct-spawn
|
|
440
|
+
return { binary: tool, args: ['-oL', '-eL', binary, ...args] };
|
|
441
|
+
}
|
|
442
|
+
function _resetStdbufToolCacheForTesting() { _stdbufToolCache = undefined; }
|
|
443
|
+
|
|
391
444
|
// Sprint 37 T3 — lazy resolution of T2's CLI modules. The orchestration-preview
|
|
392
445
|
// helper is decoupled from T2's templates.js / init-project.js; we resolve
|
|
393
446
|
// them here and pass them into the helper. If a module is missing (e.g.
|
|
@@ -1361,6 +1414,18 @@ function createServer(config) {
|
|
|
1361
1414
|
args = (cmdTrim && !isPlainShell) ? ['-c', cmdTrim] : [];
|
|
1362
1415
|
}
|
|
1363
1416
|
|
|
1417
|
+
// Sprint 70 T1 — stdout-capture adapters (agy) may opt into a best-effort
|
|
1418
|
+
// line-buffering wrap of the direct-spawn. No-op for every other adapter
|
|
1419
|
+
// (none declare `capture`) and for the shell-wrap path. Gated on
|
|
1420
|
+
// directSpawnAdapter because a capture declaration only rides the
|
|
1421
|
+
// exact-binary direct-spawn path. Falls back to the bare binary when no
|
|
1422
|
+
// stdbuf-family tool is on PATH; the PTY tee below captures regardless.
|
|
1423
|
+
if (directSpawnAdapter && directSpawnAdapter.capture) {
|
|
1424
|
+
const wrapped = _resolveStdoutCaptureSpawn(spawnShell, args, directSpawnAdapter.capture);
|
|
1425
|
+
spawnShell = wrapped.binary;
|
|
1426
|
+
args = wrapped.args;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1364
1429
|
try {
|
|
1365
1430
|
// Sprint 48 T4: merge ~/.termdeck/secrets.env into the PTY env so
|
|
1366
1431
|
// the bundled session-end memory hook (`memory-session-end.js`) sees
|
|
@@ -1472,10 +1537,43 @@ function createServer(config) {
|
|
|
1472
1537
|
}
|
|
1473
1538
|
} catch (_periodicErr) { /* fail-soft */ }
|
|
1474
1539
|
|
|
1540
|
+
// Sprint 70 T1 — initialize the in-flight stdout capture buffer for
|
|
1541
|
+
// adapters that opt in (agy). The tee in term.onData below appends to
|
|
1542
|
+
// it; resolveTranscriptPath materializes it into a tempfile envelope at
|
|
1543
|
+
// panel close + on the periodic-capture tick. Gated on the direct-spawn
|
|
1544
|
+
// adapter's declaration so non-capture panels carry no buffer (zero
|
|
1545
|
+
// overhead; their behavior is byte-for-byte unchanged).
|
|
1546
|
+
if (directSpawnAdapter && directSpawnAdapter.capture
|
|
1547
|
+
&& directSpawnAdapter.capture.mode === 'stdout') {
|
|
1548
|
+
const declaredMax = directSpawnAdapter.capture.maxBytes;
|
|
1549
|
+
const maxBytes = (typeof declaredMax === 'number' && declaredMax > 0)
|
|
1550
|
+
? declaredMax
|
|
1551
|
+
: 4 * 1024 * 1024;
|
|
1552
|
+
session._stdoutCapture = { chunks: [], bytes: 0, maxBytes };
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1475
1555
|
// PTY output → analyze + broadcast to WebSocket + transcript archive
|
|
1476
1556
|
term.onData((data) => {
|
|
1477
1557
|
session.analyzeOutput(data);
|
|
1478
1558
|
|
|
1559
|
+
// Sprint 70 T1 — tee PTY output into the in-flight capture buffer for
|
|
1560
|
+
// stdout-capture adapters (agy). Tail-capped: when the buffer exceeds
|
|
1561
|
+
// maxBytes we drop whole chunks from the FRONT, keeping the most
|
|
1562
|
+
// recent conversation (TUI redraws inflate raw bytes far past the
|
|
1563
|
+
// de-chromed content). Best-effort — a capture failure must never
|
|
1564
|
+
// disrupt the load-bearing PTY data path below.
|
|
1565
|
+
const cap = session._stdoutCapture;
|
|
1566
|
+
if (cap) {
|
|
1567
|
+
try {
|
|
1568
|
+
cap.chunks.push(data);
|
|
1569
|
+
cap.bytes += Buffer.byteLength(data, 'utf8');
|
|
1570
|
+
while (cap.bytes > cap.maxBytes && cap.chunks.length > 1) {
|
|
1571
|
+
const dropped = cap.chunks.shift();
|
|
1572
|
+
cap.bytes -= Buffer.byteLength(dropped, 'utf8');
|
|
1573
|
+
}
|
|
1574
|
+
} catch (_capErr) { /* capture is best-effort */ }
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1479
1577
|
// Send to connected WebSocket
|
|
1480
1578
|
if (session.ws && session.ws.readyState === 1) {
|
|
1481
1579
|
session.ws.send(JSON.stringify({ type: 'output', data }));
|
|
@@ -3090,4 +3188,7 @@ module.exports = {
|
|
|
3090
3188
|
onPanelPeriodicCapture,
|
|
3091
3189
|
_setSpawnPeriodicCaptureHookImplForTesting,
|
|
3092
3190
|
_resolvePeriodicCaptureIntervalMs,
|
|
3191
|
+
// Sprint 70 T1 — stdout-capture spawn-wrap resolver (best-effort stdbuf).
|
|
3192
|
+
_resolveStdoutCaptureSpawn,
|
|
3193
|
+
_resetStdbufToolCacheForTesting,
|
|
3093
3194
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sprint 51.5 T1 — schema-introspection audit-upgrade.
|
|
2
2
|
//
|
|
3
|
-
// Brad's 2026-05-02
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
323
|
+
else return;
|
|
324
324
|
const content = msg.content;
|
|
325
325
|
let text = '';
|
|
326
|
-
if (typeof content === 'string')
|
|
327
|
-
|
|
328
|
-
|
|
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)
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
}
|