@jhizzard/termdeck 1.8.1 → 1.10.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 +2 -1
- package/packages/cli/assets/supervise/com.jhizzard.termdeck-supervise.plist +38 -0
- package/packages/cli/assets/supervise/termdeck-supervise.service +27 -0
- package/packages/cli/assets/supervise/termdeck-supervise.sh +146 -0
- package/packages/cli/assets/supervise/termdeck-supervise.timer +14 -0
- package/packages/cli/src/doctor.js +11 -0
- package/packages/cli/src/index.js +15 -2
- package/packages/cli/src/init-bridge.js +1270 -0
- package/packages/cli/src/init-mnestra.js +104 -13
- package/packages/cli/src/init-rumen.js +7 -0
- package/packages/cli/src/init.js +1 -0
- package/packages/client/public/app.js +135 -9
- package/packages/client/public/index.html +1 -0
- package/packages/client/public/input-guard.js +192 -0
- package/packages/client/public/style.css +63 -0
- package/packages/server/src/agent-adapters/web-chat-grok.js +22 -22
- package/packages/server/src/health.js +21 -3
- package/packages/server/src/index.js +6 -1
- package/packages/server/src/preflight.js +14 -5
- package/packages/server/src/setup/rumen/functions/inbox-promote/index.ts +105 -0
- package/packages/server/src/setup/rumen/functions/inbox-promote/tsconfig.json +14 -0
- package/packages/server/src/setup/supabase-url.js +101 -1
- package/packages/stack-installer/assets/hooks/README.md +25 -15
- package/packages/stack-installer/assets/hooks/memory-pre-compact.js +35 -7
- package/packages/stack-installer/assets/hooks/memory-session-end.js +121 -27
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TermDeck session-end memory hook (Mnestra-direct, no rag-system dependency).
|
|
3
3
|
*
|
|
4
|
+
* @termdeck/stack-installer-hook v5
|
|
5
|
+
*
|
|
6
|
+
* ^ The stamp lives HERE, at the top of the docblock — NOT below the changelog
|
|
7
|
+
* notes. Both readers (stack-installer's installSessionEndHook and
|
|
8
|
+
* init-mnestra's refreshBundledHookIfNewer) scan only the first 4096 bytes
|
|
9
|
+
* of the file for the marker; Sprint 73 T1's v4 note grew the header past
|
|
10
|
+
* 4 KB and a stamp positioned below the notes fell out of the scan window,
|
|
11
|
+
* making the bundled hook read as "unsigned" and silently disabling every
|
|
12
|
+
* refresh path. Keep the stamp above the fold; let the changelog grow below.
|
|
13
|
+
*
|
|
4
14
|
* Vendored into ~/.claude/hooks/memory-session-end.js by @jhizzard/termdeck-stack.
|
|
5
15
|
* Wired into ~/.claude/settings.json under hooks.SessionEnd — fires once per
|
|
6
16
|
* Claude Code session close (`/exit`, Ctrl+D, terminal close, or process kill).
|
|
@@ -34,7 +44,9 @@
|
|
|
34
44
|
* JSONL, Codex JSONL, Gemini single-JSON, or auto-detect when sessionType
|
|
35
45
|
* is absent. Builds a coarse summary from the resulting message list
|
|
36
46
|
* (last ~30 message excerpts).
|
|
37
|
-
* 7. Embeds the summary via OpenAI text-embedding-3-
|
|
47
|
+
* 7. Embeds the summary via OpenAI text-embedding-3-large at
|
|
48
|
+
* dimensions:1536 — recall-parity: MUST match mnestra's query-side
|
|
49
|
+
* embedder (engram src/embeddings.ts) or rows are semantically blind.
|
|
38
50
|
* 8. POSTs ONE row to Supabase /rest/v1/memory_items with source_type='session_summary'.
|
|
39
51
|
* 9. (Sprint 51.6 T3) POSTs ONE row to Supabase /rest/v1/memory_sessions with
|
|
40
52
|
* Prefer: resolution=merge-duplicates so SessionEnd-fires-twice resolves
|
|
@@ -43,9 +55,11 @@
|
|
|
43
55
|
* 10. Logs every step to ~/.claude/hooks/memory-hook.log.
|
|
44
56
|
*
|
|
45
57
|
* Version stamp (Sprint 51.6 T3 — hook upgrade gap fix):
|
|
46
|
-
* The marker `@termdeck/stack-installer-hook v<N>`
|
|
47
|
-
* stack-installer's installSessionEndHook
|
|
48
|
-
* --yes) and `termdeck init --mnestra`
|
|
58
|
+
* The marker `@termdeck/stack-installer-hook v<N>` at the TOP of this
|
|
59
|
+
* docblock is read by both stack-installer's installSessionEndHook
|
|
60
|
+
* (version-aware overwrite under --yes) and `termdeck init --mnestra`
|
|
61
|
+
* (refreshBundledHookIfNewer step) — both scan only the first 4096 bytes,
|
|
62
|
+
* which is why it sits above these notes (see the warning beside it).
|
|
49
63
|
* Bump the integer whenever a change to this file should overwrite an
|
|
50
64
|
* already-installed copy on the user's machine — e.g. a new write path,
|
|
51
65
|
* a new transcript parser, a default PROJECT_MAP change. Comment-only
|
|
@@ -61,12 +75,39 @@
|
|
|
61
75
|
* to bundled-v2 always passes the `installed >= bundled` short-circuit
|
|
62
76
|
* at init-mnestra.js:550 and reaches the refresh path.
|
|
63
77
|
*
|
|
64
|
-
*
|
|
78
|
+
* v4 (Sprint 73 T1 — grok-web provenance + web-chat byte-floor exemption):
|
|
79
|
+
* - ALLOWED_SOURCE_AGENTS gains the four web-surface tags ('grok-web',
|
|
80
|
+
* 'claude-web', 'chatgpt-web', 'gemini-web') per the Sprint 74 ORCH
|
|
81
|
+
* one-churn addendum; only 'grok-web' has a live producer today (the
|
|
82
|
+
* web-chat-grok adapter). New alias 'web-chat-grok' → 'grok-web'
|
|
83
|
+
* (registry-name safety net, mirrors 'agy' → 'antigravity').
|
|
84
|
+
* - Byte-floor exemption extended from antigravity-only to a sessionType
|
|
85
|
+
* set {antigravity, web-chat}: both materialize compact synthesized
|
|
86
|
+
* envelopes (<5 KB for short-but-substantive sessions); the gate is
|
|
87
|
+
* parsed content (>= 1 assistant turn), not raw bytes.
|
|
88
|
+
* - ATOMIC with mnestra migration 024 (Sprint 74 T1): rows tagged
|
|
89
|
+
* 'grok-web' are unfilterable via MCP source_agents until that ships.
|
|
90
|
+
* - Stamp moved to the TOP of the docblock (the readers' 4096-byte head
|
|
91
|
+
* scan missed it below these notes — see the warning at the stamp).
|
|
92
|
+
*
|
|
93
|
+
* v5 (Sprint 73 T1, ORCH handoff — embedding recall-parity):
|
|
94
|
+
* - embedText flipped text-embedding-3-small → text-embedding-3-large at
|
|
95
|
+
* dimensions:1536, matching mnestra's recall query embedder (engram
|
|
96
|
+
* src/embeddings.ts) EXACTLY. The two models do not share a vector
|
|
97
|
+
* space, so rows embedded 3-small score semantic noise against 3-large
|
|
98
|
+
* queries — Sprint 74 T3 quantified 544 production rows half-blind.
|
|
99
|
+
* `dimensions:1536` is LOAD-BEARING: 3-large is natively 3072-dim and
|
|
100
|
+
* the DB column is vector(1536) — without the param every insert 400s
|
|
101
|
+
* and capture is silently lost (hooks are fail-soft).
|
|
102
|
+
* - memory_items rows now stamp metadata.embedding_model =
|
|
103
|
+
* 'text-embedding-3-large@1536' — the marker Sprint 74 T3's re-embed
|
|
104
|
+
* backfill keys idempotency on (unmarked rows get re-embedded; marked
|
|
105
|
+
* rows are skipped). memory_items.metadata exists from migration 001.
|
|
65
106
|
*
|
|
66
107
|
* Required env vars (validated at entry, after the secrets.env fallback):
|
|
67
108
|
* - SUPABASE_URL e.g. https://<project-ref>.supabase.co
|
|
68
109
|
* - SUPABASE_SERVICE_ROLE_KEY service-role key (NOT the anon key — needs INSERT on memory_items)
|
|
69
|
-
* - OPENAI_API_KEY sk-... for text-embedding-3-
|
|
110
|
+
* - OPENAI_API_KEY sk-... for text-embedding-3-large (dimensions:1536)
|
|
70
111
|
*
|
|
71
112
|
* Optional:
|
|
72
113
|
* - TERMDECK_HOOK_DEBUG=1 verbose logging
|
|
@@ -148,6 +189,12 @@ const MIN_TRANSCRIPT_BYTES = parseInt(process.env.TERMDECK_HOOK_MIN_BYTES || '50
|
|
|
148
189
|
// filter; sub-5KB drips still get dropped. Env-configurable for operators who
|
|
149
190
|
// want the legacy permissive-to-zero floor or a higher cutoff than 1.
|
|
150
191
|
const MIN_TRANSCRIPT_MESSAGES = parseInt(process.env.TERMDECK_HOOK_MIN_MESSAGES || '1', 10);
|
|
192
|
+
// Sprint 70 T1 (antigravity) + Sprint 73 T1 (web-chat) — sessionTypes whose
|
|
193
|
+
// transcripts are synthesized COMPACT envelopes (no verbose on-disk JSONL), so
|
|
194
|
+
// the raw-byte floor would wrongly drop short-but-substantive sessions. These
|
|
195
|
+
// skip the MIN_TRANSCRIPT_BYTES gate and are gated on parsed content instead
|
|
196
|
+
// (>= 1 assistant turn) — see the exemption branch in processStdinPayload.
|
|
197
|
+
const BYTE_FLOOR_EXEMPT_SESSION_TYPES = new Set(['antigravity', 'web-chat']);
|
|
151
198
|
const DEBUG = process.env.TERMDECK_HOOK_DEBUG === '1';
|
|
152
199
|
|
|
153
200
|
function log(msg) {
|
|
@@ -602,7 +649,7 @@ function buildSummary(transcriptPath, sessionType) {
|
|
|
602
649
|
const summary =
|
|
603
650
|
`Session with ${messages.length} messages.\n\n` +
|
|
604
651
|
tail.map((m) => `[${m.role}] ${m.content}`).join('\n');
|
|
605
|
-
// OpenAI
|
|
652
|
+
// OpenAI v3 embedding models accept up to 8192 tokens (~32K chars).
|
|
606
653
|
// 7000 chars is a safe headroom that survives multibyte expansion.
|
|
607
654
|
|
|
608
655
|
// Sprint 51.7 T2: merge transcript-derived metadata so the caller (
|
|
@@ -618,6 +665,23 @@ function buildSummary(transcriptPath, sessionType) {
|
|
|
618
665
|
};
|
|
619
666
|
}
|
|
620
667
|
|
|
668
|
+
// Sprint 73 T1 (ORCH handoff, v5) — recall-parity embedding contract.
|
|
669
|
+
// MUST match mnestra's query-side embedder (engram src/embeddings.ts:
|
|
670
|
+
// text-embedding-3-large at dimensions:1536) EXACTLY. OpenAI embedding models
|
|
671
|
+
// do NOT share a vector space — rows embedded with a different model than the
|
|
672
|
+
// query score semantic noise in memory_hybrid_search (Sprint 74 T3 measured
|
|
673
|
+
// 544 production rows half-blind from the 3-small era). `dimensions` is
|
|
674
|
+
// LOAD-BEARING: 3-large natively emits 3072 dims and memory_items.embedding
|
|
675
|
+
// is vector(1536); omitting it turns every insert into a fail-soft 400 (rows
|
|
676
|
+
// silently lost). EMBEDDING_MODEL_MARKER is written to
|
|
677
|
+
// metadata.embedding_model on each row — Sprint 74 T3's re-embed backfill
|
|
678
|
+
// keys its idempotent selection on it (rows without the marker get
|
|
679
|
+
// re-embedded; rows with it are skipped). Bump the marker string in lockstep
|
|
680
|
+
// with any future model/dims change.
|
|
681
|
+
const EMBEDDING_MODEL = 'text-embedding-3-large';
|
|
682
|
+
const EMBEDDING_DIMENSIONS = 1536;
|
|
683
|
+
const EMBEDDING_MODEL_MARKER = `${EMBEDDING_MODEL}@${EMBEDDING_DIMENSIONS}`;
|
|
684
|
+
|
|
621
685
|
async function embedText(text, openaiKey) {
|
|
622
686
|
try {
|
|
623
687
|
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
|
@@ -626,7 +690,11 @@ async function embedText(text, openaiKey) {
|
|
|
626
690
|
'Content-Type': 'application/json',
|
|
627
691
|
'Authorization': `Bearer ${openaiKey}`,
|
|
628
692
|
},
|
|
629
|
-
body: JSON.stringify({
|
|
693
|
+
body: JSON.stringify({
|
|
694
|
+
model: EMBEDDING_MODEL,
|
|
695
|
+
dimensions: EMBEDDING_DIMENSIONS,
|
|
696
|
+
input: text,
|
|
697
|
+
}),
|
|
630
698
|
});
|
|
631
699
|
if (!res.ok) {
|
|
632
700
|
const body = await res.text().catch(() => '');
|
|
@@ -653,15 +721,27 @@ async function embedText(text, openaiKey) {
|
|
|
653
721
|
// binary is `agy` but the canonical provenance tag is `antigravity`, so the
|
|
654
722
|
// alias map below folds `agy` → `antigravity` before the allowlist check —
|
|
655
723
|
// an agy panel's memories must not be mis-tagged `claude`.
|
|
724
|
+
//
|
|
725
|
+
// Sprint 73 T1: the Grok WEB panel (web-chat-grok adapter, sessionType
|
|
726
|
+
// 'web-chat') is distinguishable from the Grok CLI — canonical tag 'grok-web'.
|
|
727
|
+
// Per the Sprint 74 ORCH one-churn addendum, the other three web-surface tags
|
|
728
|
+
// ('claude-web', 'chatgpt-web', 'gemini-web') are forward-declared in the same
|
|
729
|
+
// v4 bump; they have NO termdeck producer yet — inert acceptance-gate entries
|
|
730
|
+
// so the next web-chat adapter doesn't need another stamp/refresh cycle.
|
|
731
|
+
// ATOMIC with mnestra migration 024 (Sprint 74 T1), which adds the same four
|
|
732
|
+
// to the read-side source_agents enum + recall filter.
|
|
656
733
|
const ALLOWED_SOURCE_AGENTS = new Set([
|
|
657
734
|
'claude', 'codex', 'gemini', 'grok', 'orchestrator', 'antigravity',
|
|
735
|
+
'grok-web', 'claude-web', 'chatgpt-web', 'gemini-web',
|
|
658
736
|
]);
|
|
659
737
|
|
|
660
|
-
// Alias → canonical source_agent. Keeps the binary name (`agy`)
|
|
661
|
-
//
|
|
662
|
-
//
|
|
738
|
+
// Alias → canonical source_agent. Keeps the binary name (`agy`), the adapter
|
|
739
|
+
// REGISTRY name (`web-chat-grok` ≠ provenance tag), and any older callers from
|
|
740
|
+
// being dropped to 'claude' by the allowlist gate. Applied (after lowercasing)
|
|
741
|
+
// before the ALLOWED_SOURCE_AGENTS membership test.
|
|
663
742
|
const SOURCE_AGENT_ALIASES = {
|
|
664
743
|
agy: 'antigravity',
|
|
744
|
+
'web-chat-grok': 'grok-web',
|
|
665
745
|
};
|
|
666
746
|
|
|
667
747
|
function normalizeSourceAgent(raw) {
|
|
@@ -690,6 +770,9 @@ async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, pr
|
|
|
690
770
|
project,
|
|
691
771
|
source_session_id: sessionId || null,
|
|
692
772
|
source_agent: normalizeSourceAgent(sourceAgent),
|
|
773
|
+
// v5 — backfill-idempotency marker (see EMBEDDING_MODEL_MARKER).
|
|
774
|
+
// memory_items.metadata exists from migration 001 (jsonb default '{}').
|
|
775
|
+
metadata: { embedding_model: EMBEDDING_MODEL_MARKER },
|
|
693
776
|
}),
|
|
694
777
|
});
|
|
695
778
|
if (!res.ok) {
|
|
@@ -825,24 +908,26 @@ async function processStdinPayload(input) {
|
|
|
825
908
|
try { stat = statSync(transcriptPath); }
|
|
826
909
|
catch (e) { log(`cannot-stat-transcript: ${transcriptPath} — ${e.message}`); return; }
|
|
827
910
|
|
|
828
|
-
// Sprint 70 T1 (A1 RED fix — ORCH 2026-06-07 19:21 ET)
|
|
829
|
-
// calibrated for verbose on-disk JSONL (claude/codex/
|
|
830
|
-
// run 10s of KB even when short). Antigravity
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
911
|
+
// Sprint 70 T1 (A1 RED fix — ORCH 2026-06-07 19:21 ET) + Sprint 73 T1. The
|
|
912
|
+
// raw-byte floor is calibrated for verbose on-disk JSONL (claude/codex/
|
|
913
|
+
// gemini/grok session files run 10s of KB even when short). Antigravity and
|
|
914
|
+
// web-chat have no on-disk transcript — their captures are synthesized
|
|
915
|
+
// COMPACT envelopes (agy: cleaned, de-chromed stdout-tee; web-chat: the
|
|
916
|
+
// in-memory turn buffer — 48/49 live Sprint-72 envelopes measured <5 KB), so
|
|
917
|
+
// a genuinely-substantive short session is legitimately <5KB and the byte
|
|
918
|
+
// floor would wrongly drop it (false zero-row). Exempt those sessionTypes
|
|
919
|
+
// from the byte floor and gate on parsed CONTENT instead — require >= 1
|
|
920
|
+
// assistant turn so an empty / no-model-output capture still no-ops. Do NOT
|
|
921
|
+
// lower the global floor; it correctly filters trivial verbose sessions for
|
|
922
|
+
// every other agent.
|
|
923
|
+
if (BYTE_FLOOR_EXEMPT_SESSION_TYPES.has(sessionType)) {
|
|
924
|
+
let exemptRaw = '';
|
|
925
|
+
try { exemptRaw = readFileSync(transcriptPath, 'utf8'); }
|
|
841
926
|
catch (e) { log(`cannot-read-transcript: ${transcriptPath} — ${e.message}`); return; }
|
|
842
|
-
const
|
|
843
|
-
const assistantTurns =
|
|
927
|
+
const exemptTurns = selectTranscriptParser(sessionType).parser(exemptRaw);
|
|
928
|
+
const assistantTurns = exemptTurns.filter((m) => m && m.role === 'assistant').length;
|
|
844
929
|
if (assistantTurns < 1) {
|
|
845
|
-
debug(
|
|
930
|
+
debug(`${sessionType}-no-assistant-turn: ${exemptTurns.length} parsed, 0 assistant — skipping`);
|
|
846
931
|
return;
|
|
847
932
|
}
|
|
848
933
|
} else if (stat.size < MIN_TRANSCRIPT_BYTES) {
|
|
@@ -943,6 +1028,15 @@ if (require.main === module) {
|
|
|
943
1028
|
// Sprint 50 T2 — source_agent provenance plumbing.
|
|
944
1029
|
normalizeSourceAgent,
|
|
945
1030
|
ALLOWED_SOURCE_AGENTS,
|
|
1031
|
+
// Sprint 73 T1 — compact-envelope sessionTypes exempt from the byte floor.
|
|
1032
|
+
BYTE_FLOOR_EXEMPT_SESSION_TYPES,
|
|
1033
|
+
// Sprint 73 T1 (v5) — recall-parity embedding contract. The pre-compact
|
|
1034
|
+
// hook reads EMBEDDING_MODEL_MARKER via loadHelpers and stamps it only
|
|
1035
|
+
// when defined, so a stale session-end beside a new pre-compact can never
|
|
1036
|
+
// false-mark rows.
|
|
1037
|
+
EMBEDDING_MODEL,
|
|
1038
|
+
EMBEDDING_DIMENSIONS,
|
|
1039
|
+
EMBEDDING_MODEL_MARKER,
|
|
946
1040
|
// Sprint 51.7 T2 — transcript-metadata extractor for memory_sessions.
|
|
947
1041
|
parseTranscriptMetadata,
|
|
948
1042
|
FACT_TOOL_NAMES,
|