@ekkos/cli 0.2.18 → 1.0.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.
Files changed (98) hide show
  1. package/README.md +57 -0
  2. package/dist/agent/daemon.d.ts +27 -0
  3. package/dist/agent/daemon.js +254 -29
  4. package/dist/agent/health-check.d.ts +35 -0
  5. package/dist/agent/health-check.js +243 -0
  6. package/dist/agent/pty-runner.d.ts +1 -0
  7. package/dist/agent/pty-runner.js +6 -1
  8. package/dist/capture/eviction-client.d.ts +139 -0
  9. package/dist/capture/eviction-client.js +454 -0
  10. package/dist/capture/index.d.ts +2 -0
  11. package/dist/capture/index.js +2 -0
  12. package/dist/capture/jsonl-rewriter.d.ts +96 -0
  13. package/dist/capture/jsonl-rewriter.js +1369 -0
  14. package/dist/capture/transcript-repair.d.ts +51 -0
  15. package/dist/capture/transcript-repair.js +319 -0
  16. package/dist/commands/agent.d.ts +6 -0
  17. package/dist/commands/agent.js +244 -0
  18. package/dist/commands/dashboard.d.ts +25 -0
  19. package/dist/commands/dashboard.js +1175 -0
  20. package/dist/commands/doctor.js +23 -1
  21. package/dist/commands/run.d.ts +5 -0
  22. package/dist/commands/run.js +1605 -516
  23. package/dist/commands/setup-remote.js +146 -37
  24. package/dist/commands/swarm-dashboard.d.ts +20 -0
  25. package/dist/commands/swarm-dashboard.js +735 -0
  26. package/dist/commands/swarm-setup.d.ts +10 -0
  27. package/dist/commands/swarm-setup.js +956 -0
  28. package/dist/commands/swarm.d.ts +46 -0
  29. package/dist/commands/swarm.js +441 -0
  30. package/dist/commands/test-claude.d.ts +16 -0
  31. package/dist/commands/test-claude.js +156 -0
  32. package/dist/commands/usage/blocks.d.ts +8 -0
  33. package/dist/commands/usage/blocks.js +60 -0
  34. package/dist/commands/usage/daily.d.ts +9 -0
  35. package/dist/commands/usage/daily.js +96 -0
  36. package/dist/commands/usage/dashboard.d.ts +8 -0
  37. package/dist/commands/usage/dashboard.js +104 -0
  38. package/dist/commands/usage/formatters.d.ts +41 -0
  39. package/dist/commands/usage/formatters.js +147 -0
  40. package/dist/commands/usage/index.d.ts +13 -0
  41. package/dist/commands/usage/index.js +87 -0
  42. package/dist/commands/usage/monthly.d.ts +8 -0
  43. package/dist/commands/usage/monthly.js +66 -0
  44. package/dist/commands/usage/session.d.ts +11 -0
  45. package/dist/commands/usage/session.js +193 -0
  46. package/dist/commands/usage/weekly.d.ts +9 -0
  47. package/dist/commands/usage/weekly.js +61 -0
  48. package/dist/commands/usage.d.ts +7 -0
  49. package/dist/commands/usage.js +214 -0
  50. package/dist/cron/index.d.ts +7 -0
  51. package/dist/cron/index.js +13 -0
  52. package/dist/cron/promoter.d.ts +70 -0
  53. package/dist/cron/promoter.js +403 -0
  54. package/dist/deploy/instructions.d.ts +5 -2
  55. package/dist/deploy/instructions.js +11 -8
  56. package/dist/index.js +262 -5
  57. package/dist/lib/tmux-scrollbar.d.ts +14 -0
  58. package/dist/lib/tmux-scrollbar.js +296 -0
  59. package/dist/lib/usage-monitor.d.ts +47 -0
  60. package/dist/lib/usage-monitor.js +124 -0
  61. package/dist/lib/usage-parser.d.ts +162 -0
  62. package/dist/lib/usage-parser.js +583 -0
  63. package/dist/restore/RestoreOrchestrator.d.ts +4 -0
  64. package/dist/restore/RestoreOrchestrator.js +118 -30
  65. package/dist/utils/log-rotate.d.ts +18 -0
  66. package/dist/utils/log-rotate.js +74 -0
  67. package/dist/utils/platform.d.ts +2 -0
  68. package/dist/utils/platform.js +3 -1
  69. package/dist/utils/session-binding.d.ts +5 -0
  70. package/dist/utils/session-binding.js +46 -0
  71. package/dist/utils/state.js +4 -0
  72. package/dist/utils/verify-remote-terminal.d.ts +10 -0
  73. package/dist/utils/verify-remote-terminal.js +415 -0
  74. package/package.json +9 -2
  75. package/templates/CLAUDE.md +135 -23
  76. package/templates/ekkos-manifest.json +5 -5
  77. package/templates/hooks/lib/contract.sh +43 -31
  78. package/templates/hooks/lib/count-tokens.cjs +86 -0
  79. package/templates/hooks/lib/ekkos-reminders.sh +98 -0
  80. package/templates/hooks/lib/state.sh +53 -1
  81. package/templates/hooks/stop.sh +150 -388
  82. package/templates/hooks/user-prompt-submit.sh +353 -443
  83. package/templates/windsurf-hooks/README.md +212 -0
  84. package/templates/windsurf-hooks/hooks.json +9 -2
  85. package/templates/windsurf-hooks/install.sh +148 -0
  86. package/templates/windsurf-hooks/lib/contract.sh +2 -0
  87. package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
  88. package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
  89. package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
  90. package/templates/agents/README.md +0 -182
  91. package/templates/agents/code-reviewer.md +0 -166
  92. package/templates/agents/debug-detective.md +0 -169
  93. package/templates/agents/ekkOS_Vercel.md +0 -99
  94. package/templates/agents/extension-manager.md +0 -229
  95. package/templates/agents/git-companion.md +0 -185
  96. package/templates/agents/github-test-agent.md +0 -321
  97. package/templates/agents/railway-manager.md +0 -215
  98. package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
@@ -0,0 +1,454 @@
1
+ "use strict";
2
+ /**
3
+ * EVICTION CLIENT
4
+ * ================
5
+ *
6
+ * Client module for the Handshake Eviction Protocol.
7
+ * Provides functions to call ekkOS_EvictPrepare and ekkOS_EvictConfirm
8
+ * ensuring zero data loss during context eviction.
9
+ *
10
+ * Protocol:
11
+ * 1. prepareEviction() - Write to R2, get ACK
12
+ * 2. (caller deletes locally)
13
+ * 3. confirmEviction() - Mark as committed
14
+ *
15
+ * Features:
16
+ * - Retry with exponential backoff
17
+ * - Timeout handling
18
+ * - Fallback detection
19
+ * - Idempotency via clientNonce
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.queueEvictionForRetry = queueEvictionForRetry;
56
+ exports.drainRetryQueue = drainRetryQueue;
57
+ exports.getRetryQueueStats = getRetryQueueStats;
58
+ exports.createEvictionId = createEvictionId;
59
+ exports.createFingerprint = createFingerprint;
60
+ exports.checkEvictionHealth = checkEvictionHealth;
61
+ exports.prepareEviction = prepareEviction;
62
+ exports.confirmEviction = confirmEviction;
63
+ exports.handshakeEviction = handshakeEviction;
64
+ const crypto_1 = require("crypto");
65
+ const fs = __importStar(require("fs"));
66
+ const path = __importStar(require("path"));
67
+ const os = __importStar(require("os"));
68
+ // ═══════════════════════════════════════════════════════════════════════════
69
+ // RETRY QUEUE - Auto-sync when R2 reconnects
70
+ // ═══════════════════════════════════════════════════════════════════════════
71
+ //
72
+ // When handshake fails (R2/proxy down), evictions are queued locally.
73
+ // On next successful handshake, the queue is drained automatically.
74
+ // Bounded: max 50 entries, oldest dropped on overflow.
75
+ //
76
+ const RETRY_QUEUE_PATH = path.join(os.homedir(), '.ekkos', 'eviction-retry-queue.jsonl');
77
+ const MAX_RETRY_QUEUE_SIZE = 50;
78
+ let isDraining = false;
79
+ /**
80
+ * Queue a failed eviction for retry when R2 reconnects
81
+ */
82
+ function queueEvictionForRetry(messages, indices, estimatedTokens, reason, options) {
83
+ try {
84
+ const dir = path.dirname(RETRY_QUEUE_PATH);
85
+ if (!fs.existsSync(dir))
86
+ fs.mkdirSync(dir, { recursive: true });
87
+ const entry = {
88
+ messages,
89
+ indices,
90
+ estimatedTokens,
91
+ reason,
92
+ options,
93
+ queuedAt: Date.now(),
94
+ attempts: 0,
95
+ };
96
+ // Read existing queue
97
+ let entries = [];
98
+ if (fs.existsSync(RETRY_QUEUE_PATH)) {
99
+ entries = fs.readFileSync(RETRY_QUEUE_PATH, 'utf-8')
100
+ .split('\n')
101
+ .filter(l => l.trim());
102
+ }
103
+ // Enforce max size (drop oldest)
104
+ if (entries.length >= MAX_RETRY_QUEUE_SIZE) {
105
+ entries = entries.slice(entries.length - MAX_RETRY_QUEUE_SIZE + 1);
106
+ }
107
+ entries.push(JSON.stringify(entry));
108
+ fs.writeFileSync(RETRY_QUEUE_PATH, entries.join('\n') + '\n');
109
+ console.log(`[EvictionRetry] Queued eviction for retry (${messages.length} msgs, queue size: ${entries.length})`);
110
+ }
111
+ catch (err) {
112
+ console.warn('[EvictionRetry] Failed to queue:', err instanceof Error ? err.message : err);
113
+ }
114
+ }
115
+ /**
116
+ * Drain the retry queue — called after a successful handshake
117
+ * Non-blocking: runs in background, doesn't delay current eviction
118
+ */
119
+ async function drainRetryQueue(apiUrl, authToken) {
120
+ if (isDraining)
121
+ return { drained: 0, failed: 0, remaining: -1 };
122
+ if (!fs.existsSync(RETRY_QUEUE_PATH))
123
+ return { drained: 0, failed: 0, remaining: 0 };
124
+ isDraining = true;
125
+ let drained = 0;
126
+ let failed = 0;
127
+ try {
128
+ const content = fs.readFileSync(RETRY_QUEUE_PATH, 'utf-8');
129
+ const lines = content.split('\n').filter(l => l.trim());
130
+ if (lines.length === 0) {
131
+ isDraining = false;
132
+ return { drained: 0, failed: 0, remaining: 0 };
133
+ }
134
+ console.log(`[EvictionRetry] Draining ${lines.length} queued evictions...`);
135
+ const remaining = [];
136
+ for (const line of lines) {
137
+ try {
138
+ const entry = JSON.parse(line);
139
+ // Skip entries older than 24 hours (data likely stale)
140
+ if (Date.now() - entry.queuedAt > 24 * 60 * 60 * 1000) {
141
+ console.log(`[EvictionRetry] Dropping stale entry (${((Date.now() - entry.queuedAt) / 3600000).toFixed(1)}h old)`);
142
+ continue;
143
+ }
144
+ // Skip entries that failed too many times
145
+ if (entry.attempts >= 3) {
146
+ console.log(`[EvictionRetry] Dropping entry after 3 failed attempts`);
147
+ continue;
148
+ }
149
+ // Attempt handshake
150
+ const result = await handshakeEviction(entry.messages, entry.indices, entry.estimatedTokens, entry.reason, { ...entry.options, apiUrl, authToken });
151
+ if (result.success) {
152
+ drained++;
153
+ console.log(`[EvictionRetry] Successfully synced queued eviction ${result.evictionId}`);
154
+ }
155
+ else {
156
+ failed++;
157
+ entry.attempts++;
158
+ remaining.push(JSON.stringify(entry));
159
+ }
160
+ }
161
+ catch {
162
+ failed++;
163
+ remaining.push(line); // Keep unparseable entries for manual review
164
+ }
165
+ }
166
+ // Rewrite queue with remaining entries
167
+ if (remaining.length > 0) {
168
+ fs.writeFileSync(RETRY_QUEUE_PATH, remaining.join('\n') + '\n');
169
+ }
170
+ else {
171
+ // Queue fully drained — remove file
172
+ try {
173
+ fs.unlinkSync(RETRY_QUEUE_PATH);
174
+ }
175
+ catch { /* ok */ }
176
+ }
177
+ console.log(`[EvictionRetry] Drain complete: ${drained} synced, ${failed} failed, ${remaining.length} remaining`);
178
+ return { drained, failed, remaining: remaining.length };
179
+ }
180
+ catch (err) {
181
+ console.warn('[EvictionRetry] Drain error:', err instanceof Error ? err.message : err);
182
+ return { drained, failed, remaining: -1 };
183
+ }
184
+ finally {
185
+ isDraining = false;
186
+ }
187
+ }
188
+ /**
189
+ * Get retry queue stats (for monitoring)
190
+ */
191
+ function getRetryQueueStats() {
192
+ try {
193
+ if (!fs.existsSync(RETRY_QUEUE_PATH))
194
+ return { size: 0, oldestAge: null };
195
+ const content = fs.readFileSync(RETRY_QUEUE_PATH, 'utf-8');
196
+ const lines = content.split('\n').filter(l => l.trim());
197
+ if (lines.length === 0)
198
+ return { size: 0, oldestAge: null };
199
+ const oldest = JSON.parse(lines[0]);
200
+ return {
201
+ size: lines.length,
202
+ oldestAge: Date.now() - oldest.queuedAt,
203
+ };
204
+ }
205
+ catch {
206
+ return { size: 0, oldestAge: null };
207
+ }
208
+ }
209
+ // ═══════════════════════════════════════════════════════════════════════════
210
+ // CONFIGURATION
211
+ // ═══════════════════════════════════════════════════════════════════════════
212
+ const EVICTION_TIMEOUT_MS = 30000; // 30 second timeout
213
+ const MAX_RETRIES = 3;
214
+ const HEALTH_CHECK_TIMEOUT_MS = 5000;
215
+ // ═══════════════════════════════════════════════════════════════════════════
216
+ // UTILITY FUNCTIONS
217
+ // ═══════════════════════════════════════════════════════════════════════════
218
+ function sleep(ms) {
219
+ return new Promise(resolve => setTimeout(resolve, ms));
220
+ }
221
+ /**
222
+ * Create deterministic eviction ID from messages
223
+ * Same messages = same evictionId (enables dedup)
224
+ */
225
+ function createEvictionId(messages) {
226
+ const data = JSON.stringify(messages);
227
+ return (0, crypto_1.createHash)('sha256').update(data).digest('hex').slice(0, 12);
228
+ }
229
+ /**
230
+ * Create fingerprint for a message
231
+ */
232
+ function createFingerprint(msg) {
233
+ const data = msg.role + JSON.stringify(msg.content);
234
+ return (0, crypto_1.createHash)('sha256').update(data).digest('hex').slice(0, 16);
235
+ }
236
+ // ═══════════════════════════════════════════════════════════════════════════
237
+ // HEALTH CHECK
238
+ // ═══════════════════════════════════════════════════════════════════════════
239
+ /**
240
+ * Check if the eviction API is available
241
+ * Returns true if proxy is reachable and healthy
242
+ */
243
+ async function checkEvictionHealth(apiUrl, authToken) {
244
+ try {
245
+ const controller = new AbortController();
246
+ const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
247
+ const response = await fetch(`${apiUrl}/api/v1/mcp/health`, {
248
+ method: 'GET',
249
+ headers: {
250
+ 'Authorization': `Bearer ${authToken}`,
251
+ },
252
+ signal: controller.signal,
253
+ });
254
+ clearTimeout(timeoutId);
255
+ return response.ok;
256
+ }
257
+ catch {
258
+ return false;
259
+ }
260
+ }
261
+ // ═══════════════════════════════════════════════════════════════════════════
262
+ // PREPARE EVICTION (PHASE 1)
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+ /**
265
+ * Prepare eviction by writing to R2 via the proxy.
266
+ * Returns ACK with evictionId if successful.
267
+ * Caller must NOT delete locally until this succeeds.
268
+ */
269
+ async function prepareEviction(apiUrl, authToken, sessionId, sessionName, userId, tenantId, messages, manifest, projectPath) {
270
+ const clientNonce = (0, crypto_1.randomUUID)();
271
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
272
+ try {
273
+ const controller = new AbortController();
274
+ const timeoutId = setTimeout(() => controller.abort(), EVICTION_TIMEOUT_MS);
275
+ const response = await fetch(`${apiUrl}/api/v1/mcp/call`, {
276
+ method: 'POST',
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ 'Authorization': `Bearer ${authToken}`,
280
+ },
281
+ body: JSON.stringify({
282
+ tool: 'ekkOS_EvictPrepare',
283
+ args: {
284
+ sessionId,
285
+ sessionName,
286
+ userId,
287
+ tenantId,
288
+ projectPath: projectPath || process.cwd(),
289
+ messages,
290
+ manifest,
291
+ clientNonce,
292
+ },
293
+ }),
294
+ signal: controller.signal,
295
+ });
296
+ clearTimeout(timeoutId);
297
+ if (!response.ok) {
298
+ const errorText = await response.text().catch(() => 'Unknown error');
299
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
300
+ }
301
+ const data = await response.json();
302
+ if (!data.success) {
303
+ throw new Error(data.error || 'Prepare failed');
304
+ }
305
+ const result = data.result || data;
306
+ return {
307
+ success: true,
308
+ evictionId: result.evictionId || manifest.evictionId,
309
+ r2Key: result.r2Key,
310
+ bytesWritten: result.bytesWritten,
311
+ checksum: result.checksum,
312
+ status: result.status,
313
+ clientNonce,
314
+ };
315
+ }
316
+ catch (err) {
317
+ const errorMessage = err instanceof Error ? err.message : String(err);
318
+ console.warn(`[EvictionClient] Prepare attempt ${attempt}/${MAX_RETRIES} failed: ${errorMessage}`);
319
+ if (attempt === MAX_RETRIES) {
320
+ return {
321
+ success: false,
322
+ evictionId: manifest.evictionId,
323
+ error: `Failed after ${MAX_RETRIES} attempts: ${errorMessage}`,
324
+ clientNonce,
325
+ };
326
+ }
327
+ // Exponential backoff: 1s, 2s, 4s
328
+ await sleep(Math.pow(2, attempt - 1) * 1000);
329
+ }
330
+ }
331
+ // Should not reach here
332
+ return {
333
+ success: false,
334
+ evictionId: manifest.evictionId,
335
+ error: 'Unexpected failure',
336
+ clientNonce,
337
+ };
338
+ }
339
+ // ═══════════════════════════════════════════════════════════════════════════
340
+ // CONFIRM EVICTION (PHASE 2)
341
+ // ═══════════════════════════════════════════════════════════════════════════
342
+ /**
343
+ * Confirm eviction after local deletion.
344
+ * This is optional but recommended for audit completeness.
345
+ * Failure here is non-critical - data is already safe in R2.
346
+ */
347
+ async function confirmEviction(apiUrl, authToken, evictionId, clientNonce, localDeletedCount) {
348
+ try {
349
+ const controller = new AbortController();
350
+ const timeoutId = setTimeout(() => controller.abort(), EVICTION_TIMEOUT_MS);
351
+ const response = await fetch(`${apiUrl}/api/v1/mcp/call`, {
352
+ method: 'POST',
353
+ headers: {
354
+ 'Content-Type': 'application/json',
355
+ 'Authorization': `Bearer ${authToken}`,
356
+ },
357
+ body: JSON.stringify({
358
+ tool: 'ekkOS_EvictConfirm',
359
+ args: {
360
+ evictionId,
361
+ clientNonce,
362
+ localDeletedCount,
363
+ },
364
+ }),
365
+ signal: controller.signal,
366
+ });
367
+ clearTimeout(timeoutId);
368
+ if (!response.ok) {
369
+ return {
370
+ success: false,
371
+ error: `HTTP ${response.status}`,
372
+ };
373
+ }
374
+ const data = await response.json();
375
+ if (!data.success) {
376
+ return {
377
+ success: false,
378
+ error: data.error || 'Confirm failed',
379
+ };
380
+ }
381
+ const result = data.result || data;
382
+ return {
383
+ success: true,
384
+ status: result.status,
385
+ };
386
+ }
387
+ catch (err) {
388
+ // Non-critical - data is already safe in R2
389
+ console.warn(`[EvictionClient] Confirm failed (non-critical): ${err instanceof Error ? err.message : err}`);
390
+ return {
391
+ success: false,
392
+ error: err instanceof Error ? err.message : String(err),
393
+ };
394
+ }
395
+ }
396
+ /**
397
+ * Full handshake eviction - call this instead of fire-and-forget
398
+ *
399
+ * Returns:
400
+ * - success=true, status='prepared' → Safe to delete locally, then call confirmEviction
401
+ * - success=false → DO NOT delete locally
402
+ */
403
+ async function handshakeEviction(messages, indices, estimatedTokens, reason, options) {
404
+ // Build manifest
405
+ const evictionId = createEvictionId(messages);
406
+ const fingerprints = messages.map(createFingerprint);
407
+ const manifest = {
408
+ evictionId,
409
+ messageIndices: indices,
410
+ fingerprints,
411
+ estimatedTokens,
412
+ evictionReason: reason,
413
+ };
414
+ // Check health first (fast fail)
415
+ const healthy = await checkEvictionHealth(options.apiUrl, options.authToken);
416
+ if (!healthy) {
417
+ console.warn('[EvictionClient] Proxy unavailable - handshake eviction skipped');
418
+ return {
419
+ success: false,
420
+ evictionId,
421
+ status: 'skipped',
422
+ error: 'Proxy unavailable',
423
+ };
424
+ }
425
+ // Phase 1: Prepare
426
+ const prepareResult = await prepareEviction(options.apiUrl, options.authToken, options.sessionId, options.sessionName, options.userId, options.tenantId, messages, manifest, options.projectPath);
427
+ if (!prepareResult.success) {
428
+ return {
429
+ success: false,
430
+ evictionId,
431
+ status: 'failed',
432
+ error: prepareResult.error,
433
+ };
434
+ }
435
+ return {
436
+ success: true,
437
+ evictionId: prepareResult.evictionId,
438
+ r2Key: prepareResult.r2Key,
439
+ bytesWritten: prepareResult.bytesWritten,
440
+ status: 'prepared',
441
+ clientNonce: prepareResult.clientNonce,
442
+ };
443
+ }
444
+ exports.default = {
445
+ prepareEviction,
446
+ confirmEviction,
447
+ handshakeEviction,
448
+ checkEvictionHealth,
449
+ createEvictionId,
450
+ createFingerprint,
451
+ queueEvictionForRetry,
452
+ drainRetryQueue,
453
+ getRetryQueueStats,
454
+ };
@@ -6,3 +6,5 @@
6
6
  */
7
7
  export * from './types';
8
8
  export * from './stream-tailer';
9
+ export * from './jsonl-rewriter';
10
+ export * from './transcript-repair';
@@ -22,3 +22,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  __exportStar(require("./types"), exports);
24
24
  __exportStar(require("./stream-tailer"), exports);
25
+ __exportStar(require("./jsonl-rewriter"), exports);
26
+ __exportStar(require("./transcript-repair"), exports);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * JSONL Sliding Window - Maximize Context for New Work
3
+ *
4
+ * Progressive eviction - always stay lean:
5
+ * - 60%+: trim junk (streaming chunks only)
6
+ * - 65%+: trim metadata (snapshots, system) + truncate tool_results
7
+ * - 70%+: trim tools (results, calls)
8
+ * - 80%+: emergency dump to 50%
9
+ *
10
+ * THINKING BLOCKS: Preserved (priority 6) - contain valuable reasoning
11
+ * Safety: Last 50 lines are NEVER evicted (recent work protection)
12
+ *
13
+ * PROXY MODE (EKKOS_PROXY_MODE=1):
14
+ * When API proxy is enabled, eviction happens at the API level before
15
+ * requests reach Anthropic. In this mode, JSONL rewriter only does
16
+ * junk cleanup (continuousClean) - no threshold-based eviction.
17
+ * This prevents duplicate eviction from two sources.
18
+ */
19
+ import { type HandshakeEvictionResult } from './eviction-client.js';
20
+ interface HandshakeEvictionContext {
21
+ sessionId: string;
22
+ sessionName: string;
23
+ userId?: string;
24
+ tenantId?: string;
25
+ projectPath?: string;
26
+ }
27
+ /**
28
+ * Result of the eviction flow (sync or async)
29
+ */
30
+ export interface EvictionResult {
31
+ success: boolean;
32
+ evicted: number;
33
+ truncated: number;
34
+ newPercent: number;
35
+ handshakeUsed?: boolean;
36
+ evictionId?: string;
37
+ error?: string;
38
+ }
39
+ /**
40
+ * Evict messages using the Handshake Protocol (two-phase commit)
41
+ *
42
+ * CRITICAL: Returns success ONLY after R2 confirms backup.
43
+ * Caller must NOT delete locally until this returns success=true.
44
+ *
45
+ * @returns result with clientNonce for confirm phase, or error
46
+ */
47
+ export declare function evictWithHandshake(lines: string[], indices: number[], context: HandshakeEvictionContext): Promise<HandshakeEvictionResult>;
48
+ /**
49
+ * Complete the handshake by confirming local deletion
50
+ * Call this AFTER successfully deleting from local JSONL
51
+ */
52
+ export declare function confirmLocalEviction(evictionId: string, clientNonce: string, deletedCount: number): Promise<boolean>;
53
+ /**
54
+ * Check if handshake eviction is available (proxy reachable)
55
+ */
56
+ export declare function isHandshakeEvictionAvailable(): Promise<boolean>;
57
+ export declare function evictToTarget(filePath: string, currentPercent: number, sessionId?: string, sessionName?: string): {
58
+ success: boolean;
59
+ evicted: number;
60
+ truncated: number;
61
+ newPercent: number;
62
+ };
63
+ export declare function needsEviction(percent: number): boolean;
64
+ /**
65
+ * Async eviction with proper two-phase commit handshake.
66
+ * Use this instead of evictToTarget when async context is available.
67
+ *
68
+ * @returns Promise<EvictionResult>
69
+ */
70
+ export declare function evictToTargetAsync(filePath: string, currentPercent: number, sessionId?: string, sessionName?: string): Promise<EvictionResult>;
71
+ /**
72
+ * Continuous clean - run every turn, remove junk regardless of threshold
73
+ * Removes: thinking blocks, streaming chunks, old file-history-snapshots
74
+ */
75
+ export declare function continuousClean(filePath: string): {
76
+ cleaned: number;
77
+ };
78
+ export declare function emergencyEvict(filePath: string): {
79
+ success: boolean;
80
+ evicted: number;
81
+ };
82
+ /**
83
+ * Get eviction stats
84
+ */
85
+ export declare function getEvictionStats(): {
86
+ entries: number;
87
+ lastEviction?: string;
88
+ };
89
+ /**
90
+ * Get evicted content for retrieval (used by ccDNA)
91
+ */
92
+ export declare function getEvictedContent(limit?: number): Array<{
93
+ timestamp: string;
94
+ content: string[];
95
+ }>;
96
+ export {};