@exreve/exk 1.0.45 → 1.0.47

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.
@@ -251,7 +251,7 @@ function readProxyToggle() {
251
251
  /** Env for the Claude Code child: copy of host env with host ANTHROPIC_* stripped, then inject from provider or ai-config.
252
252
  * If a local model is provided, override baseUrl to point to the anthropic-proxy adapter.
253
253
  * If resolvedProvider is provided, use its credentials instead of ai-config defaults. */
254
- function envForClaudeCodeChild(localModel, resolvedProvider) {
254
+ function envForClaudeCodeChild(_localModel, resolvedProvider) {
255
255
  const env = { ...process.env };
256
256
  // Strip any host ANTHROPIC_* vars to prevent leaking credentials or wrong URLs
257
257
  delete env.ANTHROPIC_API_KEY;
@@ -333,6 +333,71 @@ export class AgentSessionManager {
333
333
  promptAbortControllers = new Map(); // Map promptId -> AbortController for cancellation
334
334
  emergencyStopInProgress = new Set(); // Track sessions being emergency stopped
335
335
  sessionHandlers = new Map(); // Track handlers for each session
336
+ socketRef = null; // Socket.IO reference for fetching session history from backend
337
+ /** Set the socket reference for backend communication (called from app-child.ts) */
338
+ setSocketRef(socket) {
339
+ this.socketRef = socket;
340
+ }
341
+ /**
342
+ * Fetch conversation history from the backend DB for a session.
343
+ * Returns array of { role, content } pairs (user prompts + assistant responses).
344
+ */
345
+ async fetchSessionHistory(sessionId) {
346
+ return new Promise((resolve) => {
347
+ if (!this.socketRef?.connected) {
348
+ console.log(`[AgentSessionManager] Cannot fetch history: socket not connected`);
349
+ resolve([]);
350
+ return;
351
+ }
352
+ const timeoutId = setTimeout(() => {
353
+ console.warn(`[AgentSessionManager] fetchSessionHistory timed out for ${sessionId}`);
354
+ resolve([]);
355
+ }, 5000);
356
+ this.socketRef.emit('session:history', { sessionId }, (response) => {
357
+ clearTimeout(timeoutId);
358
+ if (response?.history && Array.isArray(response.history)) {
359
+ console.log(`[AgentSessionManager] Fetched ${response.history.length} history entries for session ${sessionId}`);
360
+ resolve(response.history);
361
+ }
362
+ else {
363
+ resolve([]);
364
+ }
365
+ });
366
+ });
367
+ }
368
+ /**
369
+ * Format conversation history into a text block for injection into a prompt.
370
+ * Takes the last N exchanges to avoid token overflow.
371
+ */
372
+ formatHistoryForPrompt(history) {
373
+ if (!history.length)
374
+ return '';
375
+ // Take last N entries to stay within reasonable token limits
376
+ const maxEntries = 40;
377
+ const trimmed = history.slice(-maxEntries);
378
+ const lines = trimmed.map(m => {
379
+ const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
380
+ // Truncate very long individual messages
381
+ const maxLen = 2000;
382
+ const truncated = content.length > maxLen
383
+ ? content.slice(0, maxLen) + '...[truncated]'
384
+ : content;
385
+ return `<${m.role}>\n${truncated}\n</${m.role}>`;
386
+ });
387
+ return [
388
+ '[Previous Conversation Context]',
389
+ 'The following is conversation history from this session that was lost due to a session reset.',
390
+ 'Use it as context for the current request.',
391
+ '',
392
+ '<conversation>',
393
+ ...lines,
394
+ '</conversation>',
395
+ '',
396
+ '[End of Previous Context]',
397
+ '',
398
+ '',
399
+ ].join('\n');
400
+ }
336
401
  async createSession(handler) {
337
402
  const { sessionId, projectPath, model } = handler;
338
403
  const sessionModel = model || CLAUDE_CONFIG.model;
@@ -456,8 +521,10 @@ export class AgentSessionManager {
456
521
  while (session.promptQueue.length > 0 && !this.emergencyStopInProgress.has(sessionId)) {
457
522
  const queuedPrompt = session.promptQueue.shift();
458
523
  const { enhancers, handler, promptId: queuedPromptId, abortController: queuedAbortController } = queuedPrompt;
459
- const { projectPath, promptId, onOutput, onError, onComplete, onStatusUpdate } = handler;
460
- const promptStartTime = Date.now();
524
+ const { projectPath, promptId, onOutput: _onOutput, onError: _onError, onComplete: _onComplete, onStatusUpdate } = handler;
525
+ const onOutput = _onOutput;
526
+ const onError = _onError;
527
+ const onComplete = _onComplete;
461
528
  // Write attachments to temp dir and inject paths into prompt
462
529
  let effectivePrompt = queuedPrompt.prompt;
463
530
  let attachmentDir;
@@ -503,7 +570,9 @@ export class AgentSessionManager {
503
570
  try {
504
571
  for await (const _ of session.activeQueryStream) { }
505
572
  }
506
- catch { }
573
+ catch (err) {
574
+ console.error(`[AgentSession] Error draining active query stream:`, err);
575
+ }
507
576
  session.activeQueryStream = undefined;
508
577
  }
509
578
  session.activeQueryStream = undefined;
@@ -515,6 +584,25 @@ export class AgentSessionManager {
515
584
  finalPrompt = `${skillContent}\n\n${effectivePrompt}`;
516
585
  }
517
586
  }
587
+ // Inject DB history if context was lost in a previous prompt (resume failed)
588
+ if (session.contextLost) {
589
+ console.log(`[agentSession] Context was lost previously, fetching DB history for session ${sessionId}`);
590
+ try {
591
+ const history = await this.fetchSessionHistory(sessionId);
592
+ if (history.length > 0) {
593
+ const historyPrefix = this.formatHistoryForPrompt(history);
594
+ finalPrompt = historyPrefix + finalPrompt;
595
+ console.log(`[agentSession] Injected ${history.length} history entries into prompt for session ${sessionId}`);
596
+ }
597
+ else {
598
+ console.log(`[agentSession] No DB history available for session ${sessionId}`);
599
+ }
600
+ }
601
+ catch (err) {
602
+ console.error(`[agentSession] Failed to fetch/format history:`, err);
603
+ }
604
+ session.contextLost = false; // Reset after injection attempt
605
+ }
518
606
  // Add user message to history
519
607
  session.messages.push({
520
608
  role: 'user',
@@ -577,7 +665,9 @@ export class AgentSessionManager {
577
665
  mkdirSync(cwd2, { recursive: true });
578
666
  }
579
667
  }
580
- catch { }
668
+ catch (err) {
669
+ console.error(`[AgentSession] Failed to create working directory ${cwd2}:`, err);
670
+ }
581
671
  const isWin = process.platform === 'win32';
582
672
  // Ensure PATH includes common node locations, especially in containers
583
673
  const defaultPath = isWin
@@ -778,15 +868,24 @@ export class AgentSessionManager {
778
868
  // Capture Claude SDK session ID from system init message
779
869
  if (message.type === 'system' && message.subtype === 'init') {
780
870
  const systemMsg = message;
781
- if (systemMsg.session_id && !session.claudeSessionId) {
871
+ if (systemMsg.session_id) {
872
+ // Detect context loss: session_id changed unexpectedly (resume failed)
873
+ if (session.claudeSessionId && session.claudeSessionId !== systemMsg.session_id) {
874
+ session.contextLost = true;
875
+ console.warn(`[AgentSessionManager] Context lost! Session ID changed: ${session.claudeSessionId} → ${systemMsg.session_id}`);
876
+ }
782
877
  session.claudeSessionId = systemMsg.session_id;
783
878
  saveSessionState(sessionId, { claudeSessionId: systemMsg.session_id, model: session.model, provider: resolveProvider(session.model).provider, updatedAt: Date.now() });
784
879
  }
785
880
  }
786
881
  if (message.type === 'assistant') {
787
882
  const msg = message;
788
- // Capture Claude session ID from assistant message if not already set
789
- if (msg.session_id && !session.claudeSessionId) {
883
+ // Capture Claude session ID from assistant message (always update to track session changes)
884
+ if (msg.session_id) {
885
+ if (session.claudeSessionId && session.claudeSessionId !== msg.session_id) {
886
+ session.contextLost = true;
887
+ console.warn(`[AgentSessionManager] Context lost! Session ID changed in assistant msg: ${session.claudeSessionId} → ${msg.session_id}`);
888
+ }
790
889
  session.claudeSessionId = msg.session_id;
791
890
  saveSessionState(sessionId, { claudeSessionId: msg.session_id, model: session.model, provider: resolveProvider(session.model).provider, updatedAt: Date.now() });
792
891
  }
@@ -1073,33 +1172,6 @@ export class AgentSessionManager {
1073
1172
  this.processPromptQueue(sessionId);
1074
1173
  }
1075
1174
  }
1076
- getContextInfo(sessionId) {
1077
- const session = this.sessions.get(sessionId);
1078
- if (!session)
1079
- return null;
1080
- return {
1081
- messageCount: session.messages.length,
1082
- totalInputTokens: session.totalInputTokens,
1083
- totalOutputTokens: session.totalOutputTokens,
1084
- totalTokens: session.totalInputTokens + session.totalOutputTokens,
1085
- totalCostUsd: session.totalCostUsd,
1086
- lastUsage: session.lastUsage
1087
- };
1088
- }
1089
- clearContext(sessionId) {
1090
- const session = this.sessions.get(sessionId);
1091
- if (session) {
1092
- session.messages = [];
1093
- session.totalInputTokens = 0;
1094
- session.totalOutputTokens = 0;
1095
- session.totalCostUsd = 0;
1096
- // Clear Claude session ID to start fresh
1097
- session.claudeSessionId = undefined;
1098
- session.lastUsage = undefined;
1099
- // Also clear persisted state
1100
- deleteSessionState(sessionId);
1101
- }
1102
- }
1103
1175
  async deleteSession(sessionId) {
1104
1176
  const session = this.sessions.get(sessionId);
1105
1177
  if (session) {