@exreve/exk 1.0.44 → 1.0.46
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/dist/agentSession.js +121 -10
- package/dist/app-child.js +163 -1674
- package/dist/appHandlers.js +142 -0
- package/dist/appManager.js +5 -5
- package/dist/appRunner.js +2 -2
- package/dist/benchmark-startup.js +347 -0
- package/dist/cloudflaredHandlers.js +279 -0
- package/dist/containerHandlers.js +193 -0
- package/dist/fsHandlers.js +86 -0
- package/dist/githubHandlers.js +521 -0
- package/dist/index.js +164 -1743
- package/dist/projectAnalyzer.js +10 -3
- package/dist/projectManager.js +1 -1
- package/dist/runnerGenerator.js +2 -3
- package/dist/sessionHandlers.js +271 -0
- package/dist/ttc-cli.tar.gz +0 -0
- package/dist/updateHandlers.js +82 -0
- package/dist/updater.js +2 -5
- package/package.json +3 -3
package/dist/agentSession.js
CHANGED
|
@@ -135,6 +135,9 @@ function lookupToolNameFromHistory(messages, toolUseId) {
|
|
|
135
135
|
// (Do not read ANTHROPIC_* / CLAUDE_MODEL from the host environment — only this file + code default model.)
|
|
136
136
|
const AI_CONFIG_PATH = path.join(os.homedir(), '.talk-to-code', 'ai-config.json');
|
|
137
137
|
const DEFAULT_AI_MODEL = 'glm-5.1';
|
|
138
|
+
/** TTL cache for ai-config.json reads to avoid hitting disk on every call */
|
|
139
|
+
let _aiConfigCache = null;
|
|
140
|
+
const AI_CONFIG_TTL_MS = 5_000;
|
|
138
141
|
const PROVIDERS = {
|
|
139
142
|
zai: {
|
|
140
143
|
apiKey: process.env.ZHIPU_API_KEY || '',
|
|
@@ -184,6 +187,10 @@ function resolveProvider(model, providerId) {
|
|
|
184
187
|
};
|
|
185
188
|
}
|
|
186
189
|
function loadAiConfig() {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
if (_aiConfigCache && (now - _aiConfigCache.ts) < AI_CONFIG_TTL_MS) {
|
|
192
|
+
return _aiConfigCache.data;
|
|
193
|
+
}
|
|
187
194
|
try {
|
|
188
195
|
const data = readFileSync(AI_CONFIG_PATH, 'utf-8');
|
|
189
196
|
const config = JSON.parse(data);
|
|
@@ -193,10 +200,14 @@ function loadAiConfig() {
|
|
|
193
200
|
const proxy = typeof config.proxy === 'string' ? config.proxy.trim() : '';
|
|
194
201
|
const minimaxApiKey = typeof config.minimaxApiKey === 'string' ? config.minimaxApiKey.trim() : '';
|
|
195
202
|
const openrouterApiKey = typeof config.openrouterApiKey === 'string' ? config.openrouterApiKey.trim() : '';
|
|
196
|
-
|
|
203
|
+
const result = { apiKey, baseUrl, model, proxy, minimaxApiKey, openrouterApiKey };
|
|
204
|
+
_aiConfigCache = { data: result, ts: now };
|
|
205
|
+
return result;
|
|
197
206
|
}
|
|
198
207
|
catch {
|
|
199
|
-
|
|
208
|
+
const fallback = { apiKey: '', baseUrl: '', model: DEFAULT_AI_MODEL, proxy: '', minimaxApiKey: '', openrouterApiKey: '' };
|
|
209
|
+
_aiConfigCache = { data: fallback, ts: now };
|
|
210
|
+
return fallback;
|
|
200
211
|
}
|
|
201
212
|
}
|
|
202
213
|
/** Get OpenRouter API key from ai-config.json (served by backend) */
|
|
@@ -240,7 +251,7 @@ function readProxyToggle() {
|
|
|
240
251
|
/** Env for the Claude Code child: copy of host env with host ANTHROPIC_* stripped, then inject from provider or ai-config.
|
|
241
252
|
* If a local model is provided, override baseUrl to point to the anthropic-proxy adapter.
|
|
242
253
|
* If resolvedProvider is provided, use its credentials instead of ai-config defaults. */
|
|
243
|
-
function envForClaudeCodeChild(
|
|
254
|
+
function envForClaudeCodeChild(_localModel, resolvedProvider) {
|
|
244
255
|
const env = { ...process.env };
|
|
245
256
|
// Strip any host ANTHROPIC_* vars to prevent leaking credentials or wrong URLs
|
|
246
257
|
delete env.ANTHROPIC_API_KEY;
|
|
@@ -322,6 +333,71 @@ export class AgentSessionManager {
|
|
|
322
333
|
promptAbortControllers = new Map(); // Map promptId -> AbortController for cancellation
|
|
323
334
|
emergencyStopInProgress = new Set(); // Track sessions being emergency stopped
|
|
324
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
|
+
}
|
|
325
401
|
async createSession(handler) {
|
|
326
402
|
const { sessionId, projectPath, model } = handler;
|
|
327
403
|
const sessionModel = model || CLAUDE_CONFIG.model;
|
|
@@ -445,8 +521,10 @@ export class AgentSessionManager {
|
|
|
445
521
|
while (session.promptQueue.length > 0 && !this.emergencyStopInProgress.has(sessionId)) {
|
|
446
522
|
const queuedPrompt = session.promptQueue.shift();
|
|
447
523
|
const { enhancers, handler, promptId: queuedPromptId, abortController: queuedAbortController } = queuedPrompt;
|
|
448
|
-
const { projectPath, promptId, onOutput, onError, onComplete, onStatusUpdate } = handler;
|
|
449
|
-
const
|
|
524
|
+
const { projectPath, promptId, onOutput: _onOutput, onError: _onError, onComplete: _onComplete, onStatusUpdate } = handler;
|
|
525
|
+
const onOutput = _onOutput;
|
|
526
|
+
const onError = _onError;
|
|
527
|
+
const onComplete = _onComplete;
|
|
450
528
|
// Write attachments to temp dir and inject paths into prompt
|
|
451
529
|
let effectivePrompt = queuedPrompt.prompt;
|
|
452
530
|
let attachmentDir;
|
|
@@ -492,7 +570,9 @@ export class AgentSessionManager {
|
|
|
492
570
|
try {
|
|
493
571
|
for await (const _ of session.activeQueryStream) { }
|
|
494
572
|
}
|
|
495
|
-
catch {
|
|
573
|
+
catch (err) {
|
|
574
|
+
console.error(`[AgentSession] Error draining active query stream:`, err);
|
|
575
|
+
}
|
|
496
576
|
session.activeQueryStream = undefined;
|
|
497
577
|
}
|
|
498
578
|
session.activeQueryStream = undefined;
|
|
@@ -504,6 +584,25 @@ export class AgentSessionManager {
|
|
|
504
584
|
finalPrompt = `${skillContent}\n\n${effectivePrompt}`;
|
|
505
585
|
}
|
|
506
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
|
+
}
|
|
507
606
|
// Add user message to history
|
|
508
607
|
session.messages.push({
|
|
509
608
|
role: 'user',
|
|
@@ -566,7 +665,9 @@ export class AgentSessionManager {
|
|
|
566
665
|
mkdirSync(cwd2, { recursive: true });
|
|
567
666
|
}
|
|
568
667
|
}
|
|
569
|
-
catch {
|
|
668
|
+
catch (err) {
|
|
669
|
+
console.error(`[AgentSession] Failed to create working directory ${cwd2}:`, err);
|
|
670
|
+
}
|
|
570
671
|
const isWin = process.platform === 'win32';
|
|
571
672
|
// Ensure PATH includes common node locations, especially in containers
|
|
572
673
|
const defaultPath = isWin
|
|
@@ -767,15 +868,24 @@ export class AgentSessionManager {
|
|
|
767
868
|
// Capture Claude SDK session ID from system init message
|
|
768
869
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
769
870
|
const systemMsg = message;
|
|
770
|
-
if (systemMsg.session_id
|
|
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
|
+
}
|
|
771
877
|
session.claudeSessionId = systemMsg.session_id;
|
|
772
878
|
saveSessionState(sessionId, { claudeSessionId: systemMsg.session_id, model: session.model, provider: resolveProvider(session.model).provider, updatedAt: Date.now() });
|
|
773
879
|
}
|
|
774
880
|
}
|
|
775
881
|
if (message.type === 'assistant') {
|
|
776
882
|
const msg = message;
|
|
777
|
-
// Capture Claude session ID from assistant message
|
|
778
|
-
if (msg.session_id
|
|
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
|
+
}
|
|
779
889
|
session.claudeSessionId = msg.session_id;
|
|
780
890
|
saveSessionState(sessionId, { claudeSessionId: msg.session_id, model: session.model, provider: resolveProvider(session.model).provider, updatedAt: Date.now() });
|
|
781
891
|
}
|
|
@@ -1082,6 +1192,7 @@ export class AgentSessionManager {
|
|
|
1082
1192
|
session.totalInputTokens = 0;
|
|
1083
1193
|
session.totalOutputTokens = 0;
|
|
1084
1194
|
session.totalCostUsd = 0;
|
|
1195
|
+
session.contextLost = false;
|
|
1085
1196
|
// Clear Claude session ID to start fresh
|
|
1086
1197
|
session.claudeSessionId = undefined;
|
|
1087
1198
|
session.lastUsage = undefined;
|