claude-session-continuity-mcp 1.16.2 → 1.17.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/README.md +43 -2
- package/dist/hooks/install.js +62 -0
- package/dist/hooks/post-tool-use.js +0 -0
- package/dist/hooks/pre-compact.js +0 -0
- package/dist/hooks/session-end.js +104 -60
- package/dist/hooks/session-start.js +4 -3
- package/dist/hooks/user-prompt-submit.js +0 -0
- package/dist/index.js +0 -0
- package/dist/schemas.d.ts +54 -54
- package/dist/utils/logger.d.ts +10 -2
- package/dist/utils/logger.js +20 -3
- package/package.json +6 -5
- package/dist/dashboard-v2.d.ts +0 -11
- package/dist/dashboard-v2.js +0 -1321
- package/dist/dashboard.d.ts +0 -2
- package/dist/dashboard.js +0 -1196
- package/dist/index-old.d.ts +0 -2
- package/dist/index-old.js +0 -3426
- package/dist/index-v3.d.ts +0 -16
- package/dist/index-v3.js +0 -1024
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# claude-session-continuity-mcp
|
|
2
2
|
|
|
3
|
-
> **Never re-explain your project to your AI coding agent again.** 100% local session memory for **Claude Code
|
|
3
|
+
> **Never re-explain your project to your AI coding agent again.** 100% local session memory for **Claude Code, OpenAI Codex CLI & Google Gemini CLI** — auto context injection, semantic search, and error→solution recall. Zero config, zero API cost.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/claude-session-continuity-mcp)
|
|
6
6
|
[](https://www.npmjs.com/package/claude-session-continuity-mcp)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://glama.ai/mcp/servers/leesgit/claude-session-continuity-mcp)
|
|
9
9
|
|
|
10
|
+
**⚡ One install → context auto-loads every session · 🧩 survives compaction (0 re-explaining) · 🔒 100% local, $0 API**
|
|
11
|
+
|
|
10
12
|

|
|
11
13
|
|
|
12
14
|
## The Problem
|
|
@@ -24,7 +26,7 @@ Every new Claude Code session:
|
|
|
24
26
|
|
|
25
27
|
## The Solution
|
|
26
28
|
|
|
27
|
-
**Fully automatic.** Lifecycle hooks handle everything without manual calls — on **Claude Code** and **
|
|
29
|
+
**Fully automatic.** Lifecycle hooks handle everything without manual calls — on **Claude Code**, **OpenAI Codex CLI**, and **Google Gemini CLI**, sharing one local memory so context carries across all three:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
32
|
# Session start → Auto-loads relevant context + recent session history
|
|
@@ -79,6 +81,19 @@ Most Claude memory tools rely on **explicit tool calls** ("remember this"), a **
|
|
|
79
81
|
|
|
80
82
|
If you want zero-config, offline, no-cost memory that just *happens* while you work — this is it.
|
|
81
83
|
|
|
84
|
+
### Auto-injection vs. explicit search
|
|
85
|
+
|
|
86
|
+
There's also a great class of **local search** tools (e.g. [ctx](https://github.com/ctxrs/ctx)) that index your agent history so you can *query* it (`search "failed migration"`). That's complementary, not the same job:
|
|
87
|
+
|
|
88
|
+
| | claude-session-continuity-mcp | Local-search tools (ctx, etc.) |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| **How you use it** | **Automatic** — context appears on session start, no command | You (or the agent) run a search query |
|
|
91
|
+
| **Compaction** | **PreCompact hook re-injects a handover** → 0 context re-explained after a compact | Not its job (it's a search index) |
|
|
92
|
+
| **Best at** | *Never losing your thread* across sessions & compacts, hands-off | *Finding* a specific past decision/command on demand |
|
|
93
|
+
| **Coverage** | Claude Code + Codex CLI + Gemini CLI (where auto-injection is possible) | Often 30+ agents indexed for search |
|
|
94
|
+
|
|
95
|
+
Use search when you want to *look something up*. Use this when you want your context to *follow you* without asking.
|
|
96
|
+
|
|
82
97
|
---
|
|
83
98
|
|
|
84
99
|
## Works with Codex CLI too (v1.16.0+)
|
|
@@ -99,8 +114,34 @@ interface (it can be null at startup), so host detection uses an installer-injec
|
|
|
99
114
|
|
|
100
115
|
---
|
|
101
116
|
|
|
117
|
+
## Works with Gemini CLI too (v1.17.0+)
|
|
118
|
+
|
|
119
|
+
Also supports **Google Gemini CLI**. If `~/.gemini` exists, the installer registers
|
|
120
|
+
the hooks in `~/.gemini/settings.json` (SessionStart, BeforeAgent, PreCompress,
|
|
121
|
+
SessionEnd — Gemini's event names), preserving your other settings. Same shared
|
|
122
|
+
local `sessions.db`, so context carries across all three agents.
|
|
123
|
+
|
|
124
|
+
Gemini's transcript format was verified against real `~/.gemini/tmp/.../chats/*.jsonl`
|
|
125
|
+
files — it uses **two shapes** (a flat `{type, content}` line and an older
|
|
126
|
+
`{"$set":{"messages":[…]}}` diff line); the parser handles both. Like Codex,
|
|
127
|
+
`transcript_path` can be null at startup, so host detection uses a `--gemini` marker.
|
|
128
|
+
|
|
129
|
+
**Honest scope note:** session save (SessionEnd) and context output are verified working.
|
|
130
|
+
Gemini's `SessionStart` context injection is documented as *advisory-only* upstream
|
|
131
|
+
([gemini-cli#15413](https://github.com/google-gemini/gemini-cli/issues/15413)) — if your
|
|
132
|
+
Gemini build doesn't render the injected context on startup, that's an upstream limit,
|
|
133
|
+
not this tool. Session continuity still works via the saved history.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
102
137
|
## Quick Start
|
|
103
138
|
|
|
139
|
+
> **Requires Node.js 22+.** The native `better-sqlite3` dependency only ships
|
|
140
|
+
> prebuilt binaries for Node 22, 24, and 26 (the currently supported lines —
|
|
141
|
+
> Node 18 and 20 are both end-of-life). On older Node it falls back to compiling
|
|
142
|
+
> from source, which fails without build tools. Node 22 and up install cleanly
|
|
143
|
+
> with no compiler needed.
|
|
144
|
+
|
|
104
145
|
### Recommended: Global Installation
|
|
105
146
|
|
|
106
147
|
```bash
|
package/dist/hooks/install.js
CHANGED
|
@@ -19,6 +19,9 @@ const MCP_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
|
19
19
|
// Codex CLI (2026-07-09): hooks register in ~/.codex/hooks.json (same JSON shape as Claude)
|
|
20
20
|
const CODEX_DIR = path.join(os.homedir(), '.codex');
|
|
21
21
|
const CODEX_HOOKS_FILE = path.join(CODEX_DIR, 'hooks.json');
|
|
22
|
+
// Gemini CLI (2026-07-10): hooks register inside ~/.gemini/settings.json under a "hooks" key
|
|
23
|
+
const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
24
|
+
const GEMINI_SETTINGS_FILE = path.join(GEMINI_DIR, 'settings.json');
|
|
22
25
|
// 설치된 패키지 경로 찾기
|
|
23
26
|
function getPackagePath() {
|
|
24
27
|
// 1. 글로벌 설치 확인
|
|
@@ -164,6 +167,45 @@ function installCodexHooks() {
|
|
|
164
167
|
}
|
|
165
168
|
catch { /* non-fatal: Codex hooks are optional */ }
|
|
166
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Register the same hooks in ~/.gemini/settings.json for Gemini CLI (2026-07-10).
|
|
172
|
+
* Only runs if ~/.gemini exists. Hooks live under a "hooks" key inside settings.json
|
|
173
|
+
* (not a separate file). Preserves the user's other settings and their own hooks;
|
|
174
|
+
* replaces only ours (matched by the claude-hook- command prefix).
|
|
175
|
+
* Gemini event names differ: BeforeAgent (≈UserPromptSubmit), PreCompress (≈PreCompact),
|
|
176
|
+
* SessionEnd (≈Stop). transcript_path can be null at SessionStart, so we inject "--gemini".
|
|
177
|
+
*/
|
|
178
|
+
function installGeminiHooks() {
|
|
179
|
+
if (!fs.existsSync(GEMINI_DIR))
|
|
180
|
+
return; // Gemini not installed -> skip silently
|
|
181
|
+
const OUR_PREFIX = 'claude-hook-';
|
|
182
|
+
let settings = {};
|
|
183
|
+
if (fs.existsSync(GEMINI_SETTINGS_FILE)) {
|
|
184
|
+
try {
|
|
185
|
+
settings = JSON.parse(fs.readFileSync(GEMINI_SETTINGS_FILE, 'utf-8'));
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
settings = {};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const hooks = settings.hooks || {};
|
|
192
|
+
const merge = (event, ourEntries) => {
|
|
193
|
+
const existing = (hooks[event] || []);
|
|
194
|
+
const userEntries = existing.filter(e => !(e.command && e.command.includes(OUR_PREFIX)));
|
|
195
|
+
hooks[event] = [...userEntries, ...ourEntries];
|
|
196
|
+
};
|
|
197
|
+
// Gemini hook entries are flat {type, command} (no nested "hooks" array like Claude/Codex).
|
|
198
|
+
merge('SessionStart', [{ type: 'command', command: 'npm exec -- claude-hook-session-start --gemini' }]);
|
|
199
|
+
merge('BeforeAgent', [{ type: 'command', command: 'npm exec -- claude-hook-user-prompt --gemini' }]);
|
|
200
|
+
merge('PreCompress', [{ type: 'command', command: 'npm exec -- claude-hook-pre-compact --gemini' }]);
|
|
201
|
+
merge('SessionEnd', [{ type: 'command', command: 'npm exec -- claude-hook-session-end --gemini' }]);
|
|
202
|
+
settings.hooks = hooks;
|
|
203
|
+
try {
|
|
204
|
+
fs.writeFileSync(GEMINI_SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
205
|
+
console.log('✅ Gemini CLI hooks installed (~/.gemini/settings.json)');
|
|
206
|
+
}
|
|
207
|
+
catch { /* non-fatal: Gemini hooks are optional */ }
|
|
208
|
+
}
|
|
167
209
|
function install() {
|
|
168
210
|
console.log('');
|
|
169
211
|
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
@@ -214,6 +256,8 @@ function install() {
|
|
|
214
256
|
// Codex CLI hooks (2026-07-09): register the same hooks in ~/.codex/hooks.json
|
|
215
257
|
// if Codex is present. Hooks auto-detect the host and emit the right output format.
|
|
216
258
|
installCodexHooks();
|
|
259
|
+
// Gemini CLI hooks (2026-07-10): same, in ~/.gemini/settings.json if Gemini is present.
|
|
260
|
+
installGeminiHooks();
|
|
217
261
|
console.log('✅ Hooks installed (npm exec mode - works with local or global install!)');
|
|
218
262
|
console.log(' SessionStart: context auto-load');
|
|
219
263
|
console.log(' UserPromptSubmit: relevant memory injection');
|
|
@@ -286,6 +330,24 @@ function uninstall() {
|
|
|
286
330
|
}
|
|
287
331
|
catch { /* non-fatal */ }
|
|
288
332
|
}
|
|
333
|
+
// Also remove our hooks from Gemini (2026-07-10), preserving user's settings + hooks.
|
|
334
|
+
if (fs.existsSync(GEMINI_SETTINGS_FILE)) {
|
|
335
|
+
try {
|
|
336
|
+
const cfg = JSON.parse(fs.readFileSync(GEMINI_SETTINGS_FILE, 'utf-8'));
|
|
337
|
+
const gh = cfg.hooks || {};
|
|
338
|
+
for (const event of ['SessionStart', 'BeforeAgent', 'PreCompress', 'SessionEnd']) {
|
|
339
|
+
const existing = (gh[event] || []);
|
|
340
|
+
const remaining = existing.filter(e => !(e.command && e.command.includes(OUR_PREFIX)));
|
|
341
|
+
if (remaining.length === 0)
|
|
342
|
+
delete gh[event];
|
|
343
|
+
else
|
|
344
|
+
gh[event] = remaining;
|
|
345
|
+
}
|
|
346
|
+
cfg.hooks = gh;
|
|
347
|
+
fs.writeFileSync(GEMINI_SETTINGS_FILE, JSON.stringify(cfg, null, 2));
|
|
348
|
+
}
|
|
349
|
+
catch { /* non-fatal */ }
|
|
350
|
+
}
|
|
289
351
|
console.log('✅ Hooks removed successfully!');
|
|
290
352
|
}
|
|
291
353
|
function status() {
|
|
File without changes
|
|
File without changes
|
|
@@ -14,7 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as readline from 'readline';
|
|
15
15
|
import * as crypto from 'crypto';
|
|
16
16
|
import Database from 'better-sqlite3';
|
|
17
|
-
import { logHookError, isCodexHost } from '../utils/logger.js';
|
|
17
|
+
import { logHookError, isCodexHost, isGeminiHost } from '../utils/logger.js';
|
|
18
18
|
function detectWorkspaceRoot(cwd) {
|
|
19
19
|
let current = cwd;
|
|
20
20
|
const root = path.parse(current).root;
|
|
@@ -209,6 +209,8 @@ function parseUserText(entry) {
|
|
|
209
209
|
text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
|
|
210
210
|
// Codex support (2026-07-09): Codex injects <environment_context> into the first user message -> strip it
|
|
211
211
|
text = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, '').trim();
|
|
212
|
+
// Gemini support (2026-07-10): Gemini injects <session_context> into the first user message -> strip it
|
|
213
|
+
text = text.replace(/<session_context>[\s\S]*?<\/session_context>/g, '').trim();
|
|
212
214
|
text = text.replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '').trim();
|
|
213
215
|
text = text.replace(/<command-name>[\s\S]*?<\/command-name>/g, '').trim();
|
|
214
216
|
text = text.replace(/<command-message>[\s\S]*?<\/command-message>/g, '').trim();
|
|
@@ -297,6 +299,40 @@ function normalizeCodexLine(codexEntry) {
|
|
|
297
299
|
}
|
|
298
300
|
return null; // skip reasoning/token_count/session_meta etc.
|
|
299
301
|
}
|
|
302
|
+
/** One Gemini message object -> Claude entry (or null to skip). */
|
|
303
|
+
function geminiMsgToEntry(m) {
|
|
304
|
+
const t = m.type || m.role; // some builds use role
|
|
305
|
+
if (t !== 'user' && t !== 'gemini' && t !== 'model' && t !== 'assistant')
|
|
306
|
+
return null;
|
|
307
|
+
const text = (m.content || []).map(c => c.text || '').filter(Boolean).join('\n');
|
|
308
|
+
if (!text)
|
|
309
|
+
return null;
|
|
310
|
+
return {
|
|
311
|
+
type: t === 'user' ? 'user' : 'assistant',
|
|
312
|
+
timestamp: m.timestamp,
|
|
313
|
+
message: { content: [{ type: 'text', text }] },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Normalize a Gemini CLI transcript line into Claude entries (2026-07-10).
|
|
318
|
+
* REAL Gemini format has two shapes (verified against actual ~/.gemini transcripts —
|
|
319
|
+
* the docs claimed only the flat shape, but older sessions use the $set diff shape):
|
|
320
|
+
* A) flat: {type:"user"|"gemini", content:[{text}], timestamp}
|
|
321
|
+
* B) diff: {"$set":{"messages":[ {type/role, content:[{text}]}, ... ]}}
|
|
322
|
+
* Top-level type:"info" lines and message_update patches are skipped.
|
|
323
|
+
* Returns an array (shape B can carry multiple messages per line).
|
|
324
|
+
*/
|
|
325
|
+
function normalizeGeminiLine(raw) {
|
|
326
|
+
const entry = raw;
|
|
327
|
+
// Shape B: $set.messages array
|
|
328
|
+
const setMsgs = entry?.['$set']?.messages;
|
|
329
|
+
if (Array.isArray(setMsgs)) {
|
|
330
|
+
const out = setMsgs.map(geminiMsgToEntry).filter((e) => e !== null);
|
|
331
|
+
return out.length ? out : null;
|
|
332
|
+
}
|
|
333
|
+
// Shape A: flat message line
|
|
334
|
+
return geminiMsgToEntry(entry);
|
|
335
|
+
}
|
|
300
336
|
/**
|
|
301
337
|
* Single-Pass Transcript Parser
|
|
302
338
|
* JSONL을 1회 스트림으로 읽으며 모든 데이터를 동시 추출
|
|
@@ -314,8 +350,10 @@ async function parseTranscriptSinglePass(transcriptPath) {
|
|
|
314
350
|
};
|
|
315
351
|
if (!transcriptPath || !fs.existsSync(transcriptPath))
|
|
316
352
|
return result;
|
|
317
|
-
// Host detection: Codex uses ~/.codex/sessions/...rollout-*.jsonl,
|
|
353
|
+
// Host detection: Codex uses ~/.codex/sessions/...rollout-*.jsonl,
|
|
354
|
+
// Gemini uses ~/.gemini/tmp/.../chats/*.jsonl, Claude uses ~/.claude/projects/...
|
|
318
355
|
const isCodex = isCodexHost(transcriptPath); // shared detection (argv marker or path)
|
|
356
|
+
const isGemini = isGeminiHost(transcriptPath);
|
|
319
357
|
// commit 추출용 패턴
|
|
320
358
|
const commitPatterns = [
|
|
321
359
|
/git commit.*?-m\s*"\$\(cat <<'?EOF'?\n(.+?)(?:\n\n|\nCo-Authored|\nEOF)/s,
|
|
@@ -340,73 +378,79 @@ async function parseTranscriptSinglePass(transcriptPath) {
|
|
|
340
378
|
continue;
|
|
341
379
|
try {
|
|
342
380
|
const rawEntry = JSON.parse(line);
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
381
|
+
// Normalize per host into Claude entry shape. Gemini's $set line can yield
|
|
382
|
+
// multiple entries, so unify everything into an array and process each.
|
|
383
|
+
const normalized = isCodex ? normalizeCodexLine(rawEntry)
|
|
384
|
+
: isGemini ? normalizeGeminiLine(rawEntry)
|
|
385
|
+
: rawEntry;
|
|
386
|
+
if (!normalized)
|
|
346
387
|
continue;
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
388
|
+
const entries = Array.isArray(normalized) ? normalized : [normalized];
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const role = entry.type || entry.role || '';
|
|
391
|
+
const content = entry.message?.content;
|
|
392
|
+
// === 0. Timestamp 추출 (세션 duration 계산용) ===
|
|
393
|
+
if (entry.timestamp) {
|
|
394
|
+
if (!result.firstTimestamp)
|
|
395
|
+
result.firstTimestamp = entry.timestamp;
|
|
396
|
+
result.lastTimestamp = entry.timestamp;
|
|
397
|
+
}
|
|
398
|
+
// === 1. Commit 추출 (tool_use 블록에서) ===
|
|
399
|
+
if (line.includes('git commit') && Array.isArray(content)) {
|
|
400
|
+
for (const block of content) {
|
|
401
|
+
if (block.type !== 'tool_use')
|
|
402
|
+
continue;
|
|
403
|
+
const cmd = block.input?.command;
|
|
404
|
+
if (!cmd || !cmd.includes('-m'))
|
|
405
|
+
continue;
|
|
406
|
+
if (!/(?:^|&&\s*)git\s+commit/.test(cmd))
|
|
407
|
+
continue;
|
|
408
|
+
for (const pattern of commitPatterns) {
|
|
409
|
+
const match = cmd.match(pattern);
|
|
410
|
+
if (match?.[1]) {
|
|
411
|
+
const msg = match[1].trim().split('\n')[0];
|
|
412
|
+
if (msg.length > 10 && !msg.startsWith('Co-Authored')) {
|
|
413
|
+
commitSet.add(msg.slice(0, 150));
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
371
416
|
}
|
|
372
|
-
break;
|
|
373
417
|
}
|
|
374
418
|
}
|
|
375
419
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
result.userRequests.
|
|
388
|
-
|
|
420
|
+
// === 2. User Requests 추출 ===
|
|
421
|
+
if (role === 'human' || role === 'user') {
|
|
422
|
+
const text = parseUserText(entry);
|
|
423
|
+
if (text) {
|
|
424
|
+
const planMatch = text.match(/^Implement the following plan:\s*\n+#\s*(.+)/);
|
|
425
|
+
const cleaned = planMatch
|
|
426
|
+
? planMatch[1].trim().slice(0, 100)
|
|
427
|
+
: stripMarkdown(text.split('\n')[0].trim()).slice(0, 100);
|
|
428
|
+
if (cleaned && cleaned.length >= 3) {
|
|
429
|
+
if (!result.userRequests.firstRequest)
|
|
430
|
+
result.userRequests.firstRequest = cleaned;
|
|
431
|
+
result.userRequests.allRequests.push(cleaned);
|
|
432
|
+
}
|
|
389
433
|
}
|
|
390
434
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
435
|
+
// === 3. Assistant 메시지 수집 (decisions + recentMessages) ===
|
|
436
|
+
if (role === 'assistant') {
|
|
437
|
+
const text = extractTextFromContent(content);
|
|
438
|
+
if (text.length > 10) {
|
|
439
|
+
// 최근 10개만 유지 (decision 추출용)
|
|
440
|
+
result.recentAssistantMessages.push(text);
|
|
441
|
+
if (result.recentAssistantMessages.length > 10) {
|
|
442
|
+
result.recentAssistantMessages.shift();
|
|
443
|
+
}
|
|
400
444
|
}
|
|
401
445
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
446
|
+
// === 4. Error-Fix pair용 entries 수집 (최근 30개) ===
|
|
447
|
+
const text = extractTextFromContent(content);
|
|
448
|
+
if (text.length > 5) {
|
|
449
|
+
recentEntries.push({ role, text: text.slice(0, 500) });
|
|
450
|
+
if (recentEntries.length > 30)
|
|
451
|
+
recentEntries.shift();
|
|
452
|
+
}
|
|
453
|
+
} // end for (entry of entries)
|
|
410
454
|
}
|
|
411
455
|
catch { /* skip malformed lines */ }
|
|
412
456
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
|
-
import { logHookError, emitContext, isCodexHost } from '../utils/logger.js';
|
|
8
|
+
import { logHookError, emitContext, isCodexHost, isGeminiHost } from '../utils/logger.js';
|
|
9
9
|
function detectWorkspaceRoot(cwd) {
|
|
10
10
|
let current = cwd;
|
|
11
11
|
const root = path.parse(current).root;
|
|
@@ -262,8 +262,9 @@ async function main() {
|
|
|
262
262
|
emitContext(`\n<session-context project="${project}">\n${context}\n</session-context>\n`, 'SessionStart', input.transcript_path);
|
|
263
263
|
}
|
|
264
264
|
else {
|
|
265
|
-
// Only Claude gets the "no context" placeholder; Codex
|
|
266
|
-
|
|
265
|
+
// Only Claude gets the plain "no context" placeholder; Codex and Gemini
|
|
266
|
+
// expect JSON-only stdout, so a stray console.log would corrupt their parsing.
|
|
267
|
+
if (!isCodexHost(input.transcript_path) && !isGeminiHost(input.transcript_path)) {
|
|
267
268
|
console.log(`\n[Session] Project: ${project} (no context yet)\n`);
|
|
268
269
|
}
|
|
269
270
|
}
|
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|