@exreve/exk 1.0.74 → 1.0.76

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.
@@ -588,23 +588,43 @@ export class AgentSessionManager {
588
588
  finalPrompt = `${skillContent}\n\n${effectivePrompt}`;
589
589
  }
590
590
  }
591
- // Inject DB history if context was lost in a previous prompt (resume failed)
591
+ // Inject compacted summary or raw DB history if context was lost
592
592
  if (session.contextLost) {
593
- console.log(`[agentSession] Context was lost previously, fetching DB history for session ${sessionId}`);
594
- try {
595
- const history = await this.fetchSessionHistory(sessionId);
596
- if (history.length > 0) {
597
- const historyPrefix = this.formatHistoryForPrompt(history);
598
- finalPrompt = historyPrefix + finalPrompt;
599
- console.log(`[agentSession] Injected ${history.length} history entries into prompt for session ${sessionId}`);
593
+ if (session.compactionSummary) {
594
+ // Use AI-generated summary from compaction
595
+ console.log(`[agentSession] Injecting AI compaction summary for session ${sessionId}`);
596
+ const summaryPrefix = [
597
+ '[Compacted Session Context]',
598
+ 'The following is an AI-generated summary of the previous conversation in this session.',
599
+ 'Use it as context to continue where you left off.',
600
+ '',
601
+ session.compactionSummary,
602
+ '',
603
+ '[End of Compacted Context]',
604
+ '',
605
+ '',
606
+ ].join('\n');
607
+ finalPrompt = summaryPrefix + finalPrompt;
608
+ session.compactionSummary = undefined; // Only inject once
609
+ }
610
+ else {
611
+ // Fallback: inject raw DB history
612
+ console.log(`[agentSession] No compaction summary, fetching DB history for session ${sessionId}`);
613
+ try {
614
+ const history = await this.fetchSessionHistory(sessionId);
615
+ if (history.length > 0) {
616
+ const historyPrefix = this.formatHistoryForPrompt(history);
617
+ finalPrompt = historyPrefix + finalPrompt;
618
+ console.log(`[agentSession] Injected ${history.length} history entries into prompt for session ${sessionId}`);
619
+ }
620
+ else {
621
+ console.log(`[agentSession] No DB history available for session ${sessionId}`);
622
+ }
600
623
  }
601
- else {
602
- console.log(`[agentSession] No DB history available for session ${sessionId}`);
624
+ catch (err) {
625
+ console.error(`[agentSession] Failed to fetch/format history:`, err);
603
626
  }
604
627
  }
605
- catch (err) {
606
- console.error(`[agentSession] Failed to fetch/format history:`, err);
607
- }
608
628
  session.contextLost = false; // Reset after injection attempt
609
629
  }
610
630
  // Add user message to history
@@ -1087,6 +1107,130 @@ export class AgentSessionManager {
1087
1107
  session.claudeProcessGroupId = undefined;
1088
1108
  }
1089
1109
  }
1110
+ /**
1111
+ * AI-powered session compaction.
1112
+ *
1113
+ * 1. Fetches full conversation history from the backend DB
1114
+ * 2. Writes it to a temp file
1115
+ * 3. Spawns a real prompt asking the model to summarize the conversation
1116
+ * 4. Captures the model's summary
1117
+ * 5. Clears the SDK session and stores the summary for injection on next prompt
1118
+ *
1119
+ * Returns true if the compaction prompt was queued successfully.
1120
+ * The `onCompactionComplete` callback fires when the model finishes summarizing.
1121
+ */
1122
+ async compactSession(sessionId, projectPath, onOutput, onCompactionComplete) {
1123
+ const session = this.sessions.get(sessionId);
1124
+ if (!session)
1125
+ return false;
1126
+ // Don't compact while a prompt is actively running
1127
+ if (session.isProcessingQueue && session.activeBackendStream)
1128
+ return false;
1129
+ // 1. Fetch conversation history from DB
1130
+ const history = await this.fetchSessionHistory(sessionId);
1131
+ if (history.length === 0) {
1132
+ console.log(`[AgentSessionManager] No history to compact for session ${sessionId}`);
1133
+ // Still allow compaction even without history — just clears context
1134
+ session.sdkSessionId = undefined;
1135
+ session.contextLost = true;
1136
+ session.messages = [];
1137
+ deleteSessionState(sessionId);
1138
+ onCompactionComplete?.(true, 'Session context cleared (no history available)');
1139
+ return true;
1140
+ }
1141
+ console.log(`[AgentSessionManager] Starting AI compaction for session ${sessionId} with ${history.length} history entries`);
1142
+ // 2. Build the conversation text
1143
+ const conversationLines = [];
1144
+ for (const entry of history) {
1145
+ const content = typeof entry.content === 'string' ? entry.content : JSON.stringify(entry.content);
1146
+ conversationLines.push(`=== ${entry.role.toUpperCase()} ===\n${content}`);
1147
+ }
1148
+ const conversationText = conversationLines.join('\n\n');
1149
+ // 3. Write conversation to a temp file
1150
+ const attachmentDir = path.join(os.tmpdir(), 'talk-to-code', 'compaction', sessionId);
1151
+ mkdirSync(attachmentDir, { recursive: true });
1152
+ const conversationFilePath = path.join(attachmentDir, 'conversation.md');
1153
+ writeFileSync(conversationFilePath, conversationText, 'utf-8');
1154
+ // 4. Build the compaction prompt
1155
+ const compactionPrompt = [
1156
+ 'You are performing a CONTEXT COMPACTION for an ongoing coding session.',
1157
+ '',
1158
+ 'Read the full conversation history in the attached file `conversation.md`.',
1159
+ '',
1160
+ 'Your task: Create a comprehensive summary that captures:',
1161
+ '1. What the user is working on (project, goals, current task)',
1162
+ '2. All decisions made and their reasoning',
1163
+ '3. The current state of the codebase (what was changed, added, removed)',
1164
+ '4. Any open issues, bugs, or TODOs discussed',
1165
+ '5. Important technical details (file paths, configurations, API designs)',
1166
+ '6. The last thing the user was doing or about to do',
1167
+ '',
1168
+ 'Be thorough but concise. Include specific file paths, code snippets, and technical details.',
1169
+ 'This summary will be the ONLY context carried forward — everything else is lost.',
1170
+ '',
1171
+ 'Format the summary as a structured document with clear sections.',
1172
+ 'Write the summary directly — no preamble or meta-commentary.',
1173
+ ].join('\n');
1174
+ // 5. Create a synthetic attachment for the conversation file
1175
+ const attachment = {
1176
+ filename: 'conversation.md',
1177
+ mimeType: 'text/markdown',
1178
+ content: Buffer.from(conversationText).toString('base64'),
1179
+ };
1180
+ // 6. Spawn a compaction prompt through the normal pipeline
1181
+ const compactionPromptId = `compact-${sessionId}-${Date.now()}`;
1182
+ let summaryParts = [];
1183
+ await this.sendPrompt(sessionId, compactionPrompt, [], {
1184
+ sessionId,
1185
+ projectPath,
1186
+ promptId: compactionPromptId,
1187
+ model: session.model,
1188
+ attachments: [attachment],
1189
+ onOutput: (output) => {
1190
+ // Forward to caller for UI display (shows compacting progress)
1191
+ onOutput?.(output);
1192
+ // Capture assistant text for the summary
1193
+ if (output.type === 'assistant' && output.data) {
1194
+ const d = output.data;
1195
+ const text = typeof output.data === 'string'
1196
+ ? output.data
1197
+ : (d?.content?.[0]?.text || d?.text || '');
1198
+ if (text)
1199
+ summaryParts.push(text);
1200
+ }
1201
+ },
1202
+ onError: (error) => {
1203
+ console.error(`[AgentSessionManager] Compaction prompt error: ${error}`);
1204
+ onCompactionComplete?.(false, `Compaction failed: ${error}`);
1205
+ },
1206
+ onComplete: (exitCode) => {
1207
+ const summary = summaryParts.join('\n').trim();
1208
+ if (summary && exitCode === 0) {
1209
+ // Store summary and clear old context
1210
+ session.compactionSummary = summary;
1211
+ session.sdkSessionId = undefined;
1212
+ session.contextLost = true;
1213
+ session.messages = [];
1214
+ deleteSessionState(sessionId);
1215
+ console.log(`[AgentSessionManager] AI compaction complete for session ${sessionId} (${summary.length} chars summary)`);
1216
+ onCompactionComplete?.(true, 'Session context compacted with AI summary');
1217
+ }
1218
+ else {
1219
+ // Compaction prompt failed — fall back to simple clear
1220
+ session.sdkSessionId = undefined;
1221
+ session.contextLost = true;
1222
+ session.messages = [];
1223
+ deleteSessionState(sessionId);
1224
+ console.warn(`[AgentSessionManager] Compaction prompt failed (exit=${exitCode}), falling back to simple clear`);
1225
+ onCompactionComplete?.(true, 'Session context cleared (compaction prompt did not produce summary)');
1226
+ }
1227
+ },
1228
+ onStatusUpdate: () => {
1229
+ // Status updates from the compaction prompt — ignore
1230
+ },
1231
+ });
1232
+ return true;
1233
+ }
1090
1234
  /**
1091
1235
  * Emergency stop - immediately halt all activity in a session
1092
1236
  * This is a forceful stop that kills all processes and clears state
@@ -243,6 +243,67 @@ export function registerSessionHandlers(socket, foreground, activeSessions, getS
243
243
  callback?.({ success: false, error: error.message });
244
244
  }
245
245
  });
246
+ socket.on('session:compact', async (data, callback) => {
247
+ try {
248
+ const { sessionId } = data;
249
+ if (!sessionId) {
250
+ callback?.({ success: false, message: 'Missing sessionId' });
251
+ return;
252
+ }
253
+ const sessionInfo = activeSessions.get(sessionId);
254
+ if (!sessionInfo) {
255
+ callback?.({ success: false, message: 'Session not found' });
256
+ return;
257
+ }
258
+ if (foreground) {
259
+ console.log(`[CLI] Compacting session: ${sessionId}`);
260
+ }
261
+ // Respond immediately — compaction runs in the background
262
+ callback?.({ success: true, message: 'Compaction started' });
263
+ const compacted = await agentSessionManager.compactSession(sessionId, sessionInfo.projectPath, (output) => {
264
+ // Forward compaction prompt output to the backend for UI display
265
+ const compactPromptId = `compact-${sessionId}`;
266
+ getSocket().emit('prompt:output', {
267
+ sessionId,
268
+ promptId: compactPromptId,
269
+ type: output.type,
270
+ data: output.data,
271
+ timestamp: output.timestamp,
272
+ metadata: { ...output.metadata, isCompaction: true },
273
+ });
274
+ }, (success, message) => {
275
+ // Compaction finished — notify UI
276
+ getSocket().emit('session:compacted', {
277
+ sessionId,
278
+ timestamp: Date.now(),
279
+ success,
280
+ message,
281
+ });
282
+ if (foreground) {
283
+ console.log(`[CLI] Compaction ${success ? 'complete' : 'failed'}: ${message}`);
284
+ }
285
+ });
286
+ if (!compacted) {
287
+ getSocket().emit('session:compacted', {
288
+ sessionId,
289
+ timestamp: Date.now(),
290
+ success: false,
291
+ message: 'Session not found or prompt is running',
292
+ });
293
+ }
294
+ }
295
+ catch (error) {
296
+ if (foreground) {
297
+ console.error(`✗ Error compacting session: ${error.message}`);
298
+ }
299
+ getSocket().emit('session:compacted', {
300
+ sessionId: data.sessionId,
301
+ timestamp: Date.now(),
302
+ success: false,
303
+ message: error.message,
304
+ });
305
+ }
306
+ });
246
307
  socket.on('emergency:stop', async (data, callback) => {
247
308
  try {
248
309
  const { sessionId } = data;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.74",
3
+ "version": "1.0.76",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {