grov 0.5.9 → 0.5.10
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/lib/api-client.d.ts +42 -1
- package/dist/lib/api-client.js +33 -6
- package/dist/lib/cloud-sync.d.ts +57 -3
- package/dist/lib/cloud-sync.js +176 -6
- package/dist/lib/llm-extractor.d.ts +63 -1
- package/dist/lib/llm-extractor.js +882 -92
- package/dist/lib/store/database.js +14 -0
- package/dist/lib/store/index.d.ts +1 -1
- package/dist/lib/store/index.js +1 -1
- package/dist/lib/store/sessions.js +9 -3
- package/dist/lib/store/tasks.d.ts +12 -0
- package/dist/lib/store/tasks.js +43 -3
- package/dist/lib/store/types.d.ts +13 -2
- package/dist/lib/store/types.js +0 -1
- package/dist/proxy/action-parser.d.ts +0 -4
- package/dist/proxy/action-parser.js +0 -29
- package/dist/proxy/cache.d.ts +0 -4
- package/dist/proxy/cache.js +4 -8
- package/dist/proxy/extended-cache.js +6 -16
- package/dist/proxy/handlers/preprocess.js +29 -12
- package/dist/proxy/injection/delta-tracking.js +1 -0
- package/dist/proxy/request-processor.js +44 -54
- package/dist/proxy/response-processor.d.ts +6 -2
- package/dist/proxy/response-processor.js +27 -9
- package/dist/proxy/server.js +72 -38
- package/dist/proxy/utils/logging.d.ts +2 -1
- package/dist/proxy/utils/logging.js +11 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Reference: plan_proxy_local.md Section 2.1
|
|
3
3
|
import { truncate } from '../lib/utils.js';
|
|
4
4
|
import { fetchTeamMemories } from '../lib/api-client.js';
|
|
5
|
+
import { isDebugMode } from './utils/logging.js';
|
|
5
6
|
/**
|
|
6
7
|
* Build context from CLOUD team memory for injection
|
|
7
8
|
* Fetches memories from Supabase via API (cloud-first approach)
|
|
@@ -14,62 +15,36 @@ import { fetchTeamMemories } from '../lib/api-client.js';
|
|
|
14
15
|
* @returns Formatted context string or null if no memories found
|
|
15
16
|
*/
|
|
16
17
|
export async function buildTeamMemoryContextCloud(teamId, projectPath, mentionedFiles, userPrompt) {
|
|
17
|
-
const startTime = Date.now();
|
|
18
18
|
const hasContext = userPrompt && userPrompt.trim().length > 0;
|
|
19
|
-
console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
|
|
20
|
-
console.log(`[CLOUD] buildTeamMemoryContextCloud START`);
|
|
21
|
-
console.log(`[CLOUD] Team: ${teamId.substring(0, 8)}...`);
|
|
22
|
-
console.log(`[CLOUD] Project: ${projectPath}`);
|
|
23
|
-
console.log(`[CLOUD] Prompt: "${hasContext ? userPrompt.substring(0, 60) + '...' : 'N/A'}"`);
|
|
24
|
-
console.log(`[CLOUD] Files for boost: ${mentionedFiles.length > 0 ? mentionedFiles.join(', ') : 'none'}`);
|
|
25
19
|
try {
|
|
26
20
|
// Fetch memories from cloud API (hybrid search if context provided)
|
|
27
|
-
const fetchStart = Date.now();
|
|
28
21
|
const memories = await fetchTeamMemories(teamId, projectPath, {
|
|
29
22
|
status: 'complete',
|
|
30
|
-
limit: 5,
|
|
23
|
+
limit: 5,
|
|
31
24
|
files: mentionedFiles.length > 0 ? mentionedFiles : undefined,
|
|
32
25
|
context: hasContext ? userPrompt : undefined,
|
|
33
26
|
current_files: mentionedFiles.length > 0 ? mentionedFiles : undefined,
|
|
34
27
|
});
|
|
35
|
-
const fetchTime = Date.now() - fetchStart;
|
|
36
28
|
if (memories.length === 0) {
|
|
37
|
-
console.log(`[CLOUD] No memories found (fetch took ${fetchTime}ms)`);
|
|
38
|
-
console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
|
|
39
29
|
return null;
|
|
40
30
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const boosted = mem.file_boost_applied ? '🚀' : ' ';
|
|
51
|
-
const query = String(memories[i].original_query || '').substring(0, 50);
|
|
52
|
-
const filesCount = memories[i].files_touched?.length || 0;
|
|
53
|
-
const reasoningCount = memories[i].reasoning_trace?.length || 0;
|
|
54
|
-
console.log(`[CLOUD] ${i + 1}. ${boosted} [${combScore}] sem=${semScore} lex=${lexScore} | files=${filesCount} reasoning=${reasoningCount}`);
|
|
55
|
-
console.log(`[CLOUD] "${query}..."`);
|
|
31
|
+
// Log injected memories (debug mode only)
|
|
32
|
+
if (isDebugMode()) {
|
|
33
|
+
for (const m of memories) {
|
|
34
|
+
const label = m.goal || m.original_query;
|
|
35
|
+
const sem = m.semantic_score?.toFixed(2) || '-';
|
|
36
|
+
const lex = m.lexical_score?.toFixed(2) || '-';
|
|
37
|
+
const comb = m.combined_score?.toFixed(2) || '-';
|
|
38
|
+
console.log(`[INJECT] ${label.substring(0, 50)}... (${comb}|${sem}|${lex})`);
|
|
39
|
+
}
|
|
56
40
|
}
|
|
57
|
-
console.log(`[CLOUD] ───────────────────────────────────────────────────────────`);
|
|
58
41
|
// Convert Memory[] to Task[] format for the existing formatter
|
|
59
42
|
const tasks = memories.map(memoryToTask);
|
|
60
|
-
// Reuse existing formatter
|
|
43
|
+
// Reuse existing formatter
|
|
61
44
|
const context = formatTeamMemoryContext(tasks, [], mentionedFiles);
|
|
62
|
-
// Estimate tokens (~4 chars per token)
|
|
63
|
-
const estimatedTokens = Math.round(context.length / 4);
|
|
64
|
-
const totalTime = Date.now() - startTime;
|
|
65
|
-
console.log(`[CLOUD] Context built: ${context.length} chars (~${estimatedTokens} tokens)`);
|
|
66
|
-
console.log(`[CLOUD] Total time: ${totalTime}ms (fetch: ${fetchTime}ms, format: ${totalTime - fetchTime}ms)`);
|
|
67
|
-
console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
|
|
68
45
|
return context;
|
|
69
46
|
}
|
|
70
47
|
catch (err) {
|
|
71
|
-
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
72
|
-
console.error(`[CLOUD] buildTeamMemoryContextCloud failed: ${errorMsg}`);
|
|
73
48
|
return null; // Fail silent - don't block Claude Code
|
|
74
49
|
}
|
|
75
50
|
}
|
|
@@ -123,25 +98,32 @@ function formatTeamMemoryContext(tasks, fileReasonings, files) {
|
|
|
123
98
|
const fileList = task.files_touched.slice(0, 5).map(f => f.split('/').pop()).join(', ');
|
|
124
99
|
lines.push(` Files: ${fileList}`);
|
|
125
100
|
}
|
|
126
|
-
// Inject knowledge pairs
|
|
127
|
-
//
|
|
101
|
+
// Inject knowledge pairs - handle both formats:
|
|
102
|
+
// Old format: string[] interleaved (conclusion, insight, conclusion, insight...)
|
|
103
|
+
// New format: Array<{ tags?, conclusion, insight? }> objects
|
|
128
104
|
if (task.reasoning_trace.length > 0) {
|
|
129
105
|
lines.push(' Knowledge:');
|
|
130
106
|
const maxPairs = 5;
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
lines.push(` • ${truncate(cText, 120)}`);
|
|
107
|
+
const entries = task.reasoning_trace.slice(0, maxPairs);
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (typeof entry === 'string') {
|
|
110
|
+
// Old format: plain string - just remove prefix and show
|
|
111
|
+
const text = entry.replace(/^(CONCLUSION|INSIGHT):\s*/i, '');
|
|
112
|
+
if (text) {
|
|
113
|
+
lines.push(` • ${truncate(text, 120)}`);
|
|
114
|
+
}
|
|
140
115
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
116
|
+
else if (typeof entry === 'object' && entry !== null) {
|
|
117
|
+
// New format: object with tags, conclusion, insight
|
|
118
|
+
const cText = entry.conclusion?.replace(/^CONCLUSION:\s*/i, '') || '';
|
|
119
|
+
if (cText) {
|
|
120
|
+
const prefix = entry.tags ? `[${entry.tags}] ` : '';
|
|
121
|
+
lines.push(` • ${prefix}${truncate(cText, 110)}`);
|
|
122
|
+
}
|
|
123
|
+
if (entry.insight) {
|
|
124
|
+
const iText = entry.insight.replace(/^INSIGHT:\s*/i, '');
|
|
125
|
+
lines.push(` → ${truncate(iText, 100)}`);
|
|
126
|
+
}
|
|
145
127
|
}
|
|
146
128
|
}
|
|
147
129
|
}
|
|
@@ -231,11 +213,19 @@ export function extractLastUserPrompt(messages) {
|
|
|
231
213
|
}
|
|
232
214
|
}
|
|
233
215
|
}
|
|
234
|
-
// Strip system-reminder tags to get clean user content
|
|
216
|
+
// Strip system-reminder tags and continuation artifacts to get clean user content
|
|
235
217
|
const cleanContent = textContent
|
|
218
|
+
// Remove actual system-reminder tags
|
|
236
219
|
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
|
|
220
|
+
// Remove escaped/broken system-reminder artifacts from continuation summaries
|
|
221
|
+
.replace(/\\n["']?\s*<\/system-reminder>/g, '')
|
|
222
|
+
.replace(/<\/system-reminder>/g, '')
|
|
223
|
+
// Remove continuation summary header if it's the entire content
|
|
224
|
+
.replace(/^This session is being continued from a previous conversation[\s\S]*?Summary:/gi, '')
|
|
225
|
+
// Clean up leading noise (newlines, quotes, whitespace)
|
|
226
|
+
.replace(/^[\s\n"'\\]+/, '')
|
|
237
227
|
.trim();
|
|
238
|
-
if (cleanContent) {
|
|
228
|
+
if (cleanContent && cleanContent.length > 5) {
|
|
239
229
|
return cleanContent;
|
|
240
230
|
}
|
|
241
231
|
}
|
|
@@ -2,8 +2,12 @@ import { type TriggerReason } from '../lib/store.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Save session to team memory
|
|
4
4
|
* Called on: task complete, subtask complete, session abandoned
|
|
5
|
+
*
|
|
6
|
+
* @param sessionId - Session to save
|
|
7
|
+
* @param triggerReason - Why the save was triggered
|
|
8
|
+
* @param taskType - Task type for shouldUpdateMemory context (information/planning/implementation)
|
|
5
9
|
*/
|
|
6
|
-
export declare function saveToTeamMemory(sessionId: string, triggerReason: TriggerReason): Promise<void>;
|
|
10
|
+
export declare function saveToTeamMemory(sessionId: string, triggerReason: TriggerReason, taskType?: 'information' | 'planning' | 'implementation'): Promise<void>;
|
|
7
11
|
/**
|
|
8
12
|
* Clean up session data after save
|
|
9
13
|
*/
|
|
@@ -11,4 +15,4 @@ export declare function cleanupSession(sessionId: string): void;
|
|
|
11
15
|
/**
|
|
12
16
|
* Save and cleanup session (for session end)
|
|
13
17
|
*/
|
|
14
|
-
export declare function saveAndCleanupSession(sessionId: string, triggerReason: TriggerReason): Promise<void>;
|
|
18
|
+
export declare function saveAndCleanupSession(sessionId: string, triggerReason: TriggerReason, taskType?: 'information' | 'planning' | 'implementation'): Promise<void>;
|
|
@@ -79,8 +79,12 @@ ${actionLines}
|
|
|
79
79
|
/**
|
|
80
80
|
* Save session to team memory
|
|
81
81
|
* Called on: task complete, subtask complete, session abandoned
|
|
82
|
+
*
|
|
83
|
+
* @param sessionId - Session to save
|
|
84
|
+
* @param triggerReason - Why the save was triggered
|
|
85
|
+
* @param taskType - Task type for shouldUpdateMemory context (information/planning/implementation)
|
|
82
86
|
*/
|
|
83
|
-
export async function saveToTeamMemory(sessionId, triggerReason) {
|
|
87
|
+
export async function saveToTeamMemory(sessionId, triggerReason, taskType) {
|
|
84
88
|
const sessionState = getSessionState(sessionId);
|
|
85
89
|
if (!sessionState) {
|
|
86
90
|
return;
|
|
@@ -98,11 +102,11 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
|
|
|
98
102
|
// Fire-and-forget cloud sync; never block capture path
|
|
99
103
|
// NOTE: Do NOT invalidate cache after sync - cache persists until CLEAR/Summary/restart
|
|
100
104
|
// Next SESSION will get fresh data, current session keeps its context
|
|
101
|
-
syncTask(task)
|
|
105
|
+
syncTask(task, undefined, taskType)
|
|
102
106
|
.then((success) => {
|
|
103
107
|
if (success) {
|
|
104
108
|
markTaskSynced(task.id);
|
|
105
|
-
console.log(`[SYNC] Task ${task.id.substring(0, 8)} synced to cloud`);
|
|
109
|
+
console.log(`[SYNC] Task ${task.id.substring(0, 8)} synced to cloud (type=${taskType || 'unknown'})`);
|
|
106
110
|
}
|
|
107
111
|
else {
|
|
108
112
|
setTaskSyncError(task.id, 'Sync not enabled or team not configured');
|
|
@@ -120,8 +124,11 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
|
|
|
120
124
|
* Build task data from session state and steps
|
|
121
125
|
*/
|
|
122
126
|
async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
123
|
-
// Aggregate files from steps
|
|
124
|
-
|
|
127
|
+
// Aggregate files from steps - ONLY edit/write actions, not read
|
|
128
|
+
// For shouldUpdateMemory: "files modified" means actual changes, not reads
|
|
129
|
+
const stepFiles = steps
|
|
130
|
+
.filter(s => s.action_type === 'edit' || s.action_type === 'write')
|
|
131
|
+
.flatMap(s => s.files);
|
|
125
132
|
// Also extract file paths mentioned in reasoning text (Claude's text responses)
|
|
126
133
|
const reasoningFiles = steps
|
|
127
134
|
.filter(s => s.reasoning)
|
|
@@ -146,9 +153,11 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
146
153
|
return `${s.action_type}: ${s.files.length} files`;
|
|
147
154
|
});
|
|
148
155
|
// Try to use Anthropic Haiku for better reasoning & decisions extraction
|
|
149
|
-
let reasoningTrace = basicReasoningTrace;
|
|
156
|
+
let reasoningTrace = basicReasoningTrace; // strings are valid ReasoningTraceEntry
|
|
150
157
|
let decisions = [];
|
|
151
158
|
let constraints = sessionState.constraints || [];
|
|
159
|
+
let summary = undefined;
|
|
160
|
+
let systemName = undefined; // Parent system anchor for semantic search
|
|
152
161
|
if (isReasoningExtractionAvailable()) {
|
|
153
162
|
try {
|
|
154
163
|
// Group steps by reasoning to avoid duplicates and preserve action context
|
|
@@ -166,6 +175,12 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
166
175
|
}
|
|
167
176
|
if (formattedSteps.length > 50) {
|
|
168
177
|
const extracted = await extractReasoningAndDecisions(formattedSteps, sessionState.original_goal || '');
|
|
178
|
+
if (extracted.system_name) {
|
|
179
|
+
systemName = extracted.system_name;
|
|
180
|
+
}
|
|
181
|
+
if (extracted.summary) {
|
|
182
|
+
summary = extracted.summary;
|
|
183
|
+
}
|
|
169
184
|
if (extracted.reasoning_trace.length > 0) {
|
|
170
185
|
reasoningTrace = extracted.reasoning_trace;
|
|
171
186
|
}
|
|
@@ -178,11 +193,14 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
178
193
|
// Fall back to basic extraction
|
|
179
194
|
}
|
|
180
195
|
}
|
|
196
|
+
const original_query = sessionState.raw_user_prompt || sessionState.original_goal || 'Unknown task';
|
|
181
197
|
return {
|
|
182
198
|
project_path: sessionState.project_path,
|
|
183
199
|
user: sessionState.user_id,
|
|
184
|
-
original_query
|
|
200
|
+
original_query,
|
|
185
201
|
goal: sessionState.original_goal,
|
|
202
|
+
system_name: systemName, // Parent system anchor for semantic search
|
|
203
|
+
summary,
|
|
186
204
|
reasoning_trace: reasoningTrace,
|
|
187
205
|
files_touched: filesTouched,
|
|
188
206
|
decisions,
|
|
@@ -201,7 +219,7 @@ export function cleanupSession(sessionId) {
|
|
|
201
219
|
/**
|
|
202
220
|
* Save and cleanup session (for session end)
|
|
203
221
|
*/
|
|
204
|
-
export async function saveAndCleanupSession(sessionId, triggerReason) {
|
|
205
|
-
await saveToTeamMemory(sessionId, triggerReason);
|
|
222
|
+
export async function saveAndCleanupSession(sessionId, triggerReason, taskType) {
|
|
223
|
+
await saveToTeamMemory(sessionId, triggerReason, taskType);
|
|
206
224
|
cleanupSession(sessionId);
|
|
207
225
|
}
|
package/dist/proxy/server.js
CHANGED
|
@@ -358,6 +358,8 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
358
358
|
const textContent = extractTextContent(response);
|
|
359
359
|
// Extract latest user message from request
|
|
360
360
|
const latestUserMessage = extractGoalFromMessages(requestBody.messages) || '';
|
|
361
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
362
|
+
// console.log(`[DEBUG] latestUserMessage extracted: "${latestUserMessage.substring(0, 80)}..." (${latestUserMessage.length} chars)`);
|
|
361
363
|
// Get recent steps for context
|
|
362
364
|
const recentSteps = sessionInfo.currentSession
|
|
363
365
|
? getRecentSteps(sessionInfo.currentSession.session_id, 5)
|
|
@@ -394,7 +396,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
394
396
|
timestamp: Date.now(),
|
|
395
397
|
keepAliveCount: 0,
|
|
396
398
|
});
|
|
397
|
-
|
|
399
|
+
// Cache entry captured silently
|
|
398
400
|
}
|
|
399
401
|
// If not end_turn (tool_use in progress), skip task orchestration but keep session
|
|
400
402
|
if (!isEndTurn) {
|
|
@@ -402,27 +404,22 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
402
404
|
if (sessionInfo.currentSession) {
|
|
403
405
|
activeSessionId = sessionInfo.currentSession.session_id;
|
|
404
406
|
activeSession = sessionInfo.currentSession;
|
|
407
|
+
// console.log(`[DEBUG] PATH A: Reusing existing session ${activeSessionId}, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
|
|
405
408
|
}
|
|
406
409
|
else if (!activeSession) {
|
|
407
410
|
// First request, create session without task analysis
|
|
411
|
+
// NOTE: Don't set original_goal to user prompt - let analyzeTaskContext synthesize it
|
|
412
|
+
// If we set it here, Haiku will "keep it stable" instead of synthesizing
|
|
408
413
|
const newSessionId = randomUUID();
|
|
409
|
-
//
|
|
410
|
-
let goalSummary = latestUserMessage.substring(0, 500) || 'Task in progress';
|
|
411
|
-
if (isIntentExtractionAvailable() && latestUserMessage.length > 10) {
|
|
412
|
-
try {
|
|
413
|
-
const intentData = await extractIntent(latestUserMessage);
|
|
414
|
-
goalSummary = intentData.goal;
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
// Keep fallback goalSummary
|
|
418
|
-
}
|
|
419
|
-
}
|
|
414
|
+
// console.log(`[DEBUG] PATH B: Creating NEW session, raw_user_prompt will be: "${latestUserMessage.substring(0, 50)}"`);
|
|
420
415
|
activeSession = createSessionState({
|
|
421
416
|
session_id: newSessionId,
|
|
422
417
|
project_path: sessionInfo.projectPath,
|
|
423
|
-
original_goal:
|
|
418
|
+
original_goal: '', // Empty - will be synthesized by analyzeTaskContext later
|
|
419
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
424
420
|
task_type: 'main',
|
|
425
421
|
});
|
|
422
|
+
// console.log(`[DEBUG] PATH B: Session created, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
|
|
426
423
|
activeSessionId = newSessionId;
|
|
427
424
|
activeSessions.set(newSessionId, {
|
|
428
425
|
sessionId: newSessionId,
|
|
@@ -443,6 +440,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
443
440
|
msg: 'Task analysis',
|
|
444
441
|
action: taskAnalysis.action,
|
|
445
442
|
task_type: taskAnalysis.task_type,
|
|
443
|
+
goal: taskAnalysis.current_goal?.substring(0, 50),
|
|
446
444
|
reasoning: taskAnalysis.reasoning,
|
|
447
445
|
});
|
|
448
446
|
// TASK LOG: Analysis result
|
|
@@ -450,6 +448,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
450
448
|
sessionId: sessionInfo.sessionId,
|
|
451
449
|
action: taskAnalysis.action,
|
|
452
450
|
task_type: taskAnalysis.task_type,
|
|
451
|
+
goal: taskAnalysis.current_goal || '',
|
|
453
452
|
reasoning: taskAnalysis.reasoning || '',
|
|
454
453
|
userMessage: latestUserMessage.substring(0, 80),
|
|
455
454
|
hasCurrentSession: !!sessionInfo.currentSession,
|
|
@@ -473,11 +472,22 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
473
472
|
if (sessionInfo.currentSession) {
|
|
474
473
|
activeSessionId = sessionInfo.currentSession.session_id;
|
|
475
474
|
activeSession = sessionInfo.currentSession;
|
|
475
|
+
// Update goal if Haiku detected a new instruction from user
|
|
476
|
+
// (same task/topic, but new specific instruction)
|
|
477
|
+
if (taskAnalysis.current_goal &&
|
|
478
|
+
taskAnalysis.current_goal !== activeSession.original_goal &&
|
|
479
|
+
latestUserMessage.length > 30) {
|
|
480
|
+
updateSessionState(activeSessionId, {
|
|
481
|
+
original_goal: taskAnalysis.current_goal,
|
|
482
|
+
});
|
|
483
|
+
activeSession.original_goal = taskAnalysis.current_goal;
|
|
484
|
+
}
|
|
476
485
|
// TASK LOG: Continue existing session
|
|
477
486
|
taskLog('ORCHESTRATION_CONTINUE', {
|
|
478
487
|
sessionId: activeSessionId,
|
|
479
488
|
source: 'current_session',
|
|
480
489
|
goal: activeSession.original_goal,
|
|
490
|
+
goalUpdated: taskAnalysis.current_goal !== activeSession.original_goal,
|
|
481
491
|
});
|
|
482
492
|
}
|
|
483
493
|
else if (sessionInfo.completedSession) {
|
|
@@ -486,6 +496,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
486
496
|
activeSession = sessionInfo.completedSession;
|
|
487
497
|
updateSessionState(activeSessionId, {
|
|
488
498
|
status: 'active',
|
|
499
|
+
original_goal: taskAnalysis.current_goal || activeSession.original_goal,
|
|
489
500
|
});
|
|
490
501
|
activeSession.status = 'active';
|
|
491
502
|
activeSessions.set(activeSessionId, {
|
|
@@ -510,7 +521,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
510
521
|
}
|
|
511
522
|
// Extract full intent for new task (goal, scope, constraints, keywords)
|
|
512
523
|
let intentData = {
|
|
513
|
-
goal:
|
|
524
|
+
goal: taskAnalysis.current_goal,
|
|
514
525
|
expected_scope: [],
|
|
515
526
|
constraints: [],
|
|
516
527
|
keywords: [],
|
|
@@ -540,15 +551,19 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
540
551
|
}
|
|
541
552
|
}
|
|
542
553
|
const newSessionId = randomUUID();
|
|
554
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
555
|
+
// console.log(`[DEBUG] PATH C (new_task): Creating session, latestUserMessage="${latestUserMessage.substring(0, 50)}", intentData.goal="${intentData.goal?.substring(0, 50)}"`);
|
|
543
556
|
activeSession = createSessionState({
|
|
544
557
|
session_id: newSessionId,
|
|
545
558
|
project_path: sessionInfo.projectPath,
|
|
546
559
|
original_goal: intentData.goal,
|
|
560
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
547
561
|
expected_scope: intentData.expected_scope,
|
|
548
562
|
constraints: intentData.constraints,
|
|
549
563
|
keywords: intentData.keywords,
|
|
550
564
|
task_type: 'main',
|
|
551
565
|
});
|
|
566
|
+
// console.log(`[DEBUG] PATH C: Session created, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
|
|
552
567
|
activeSessionId = newSessionId;
|
|
553
568
|
activeSessions.set(newSessionId, {
|
|
554
569
|
sessionId: newSessionId,
|
|
@@ -580,7 +595,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
580
595
|
final_response: textContent.substring(0, 10000),
|
|
581
596
|
});
|
|
582
597
|
// Save to team memory and mark complete
|
|
583
|
-
await saveToTeamMemory(newSessionId, 'complete');
|
|
598
|
+
await saveToTeamMemory(newSessionId, 'complete', taskAnalysis.task_type);
|
|
584
599
|
markSessionCompleted(newSessionId);
|
|
585
600
|
}
|
|
586
601
|
else if (taskAnalysis.task_type === 'information' && actions.length > 0) {
|
|
@@ -597,7 +612,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
597
612
|
case 'subtask': {
|
|
598
613
|
// Extract intent for subtask
|
|
599
614
|
let intentData = {
|
|
600
|
-
goal:
|
|
615
|
+
goal: taskAnalysis.current_goal,
|
|
601
616
|
expected_scope: [],
|
|
602
617
|
constraints: [],
|
|
603
618
|
keywords: [],
|
|
@@ -623,6 +638,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
623
638
|
session_id: subtaskId,
|
|
624
639
|
project_path: sessionInfo.projectPath,
|
|
625
640
|
original_goal: intentData.goal,
|
|
641
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
626
642
|
expected_scope: intentData.expected_scope,
|
|
627
643
|
constraints: intentData.constraints,
|
|
628
644
|
keywords: intentData.keywords,
|
|
@@ -647,7 +663,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
647
663
|
case 'parallel_task': {
|
|
648
664
|
// Extract intent for parallel task
|
|
649
665
|
let intentData = {
|
|
650
|
-
goal:
|
|
666
|
+
goal: taskAnalysis.current_goal,
|
|
651
667
|
expected_scope: [],
|
|
652
668
|
constraints: [],
|
|
653
669
|
keywords: [],
|
|
@@ -673,6 +689,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
673
689
|
session_id: parallelId,
|
|
674
690
|
project_path: sessionInfo.projectPath,
|
|
675
691
|
original_goal: intentData.goal,
|
|
692
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
676
693
|
expected_scope: intentData.expected_scope,
|
|
677
694
|
constraints: intentData.constraints,
|
|
678
695
|
keywords: intentData.keywords,
|
|
@@ -698,11 +715,18 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
698
715
|
// Save to team memory and mark as completed (don't delete yet - keep for new_task detection)
|
|
699
716
|
if (sessionInfo.currentSession) {
|
|
700
717
|
try {
|
|
718
|
+
// Update goal if Haiku synthesized one and current is empty
|
|
719
|
+
if (taskAnalysis.current_goal && !sessionInfo.currentSession.original_goal) {
|
|
720
|
+
updateSessionState(sessionInfo.currentSession.session_id, {
|
|
721
|
+
original_goal: taskAnalysis.current_goal,
|
|
722
|
+
});
|
|
723
|
+
sessionInfo.currentSession.original_goal = taskAnalysis.current_goal;
|
|
724
|
+
}
|
|
701
725
|
// Set final_response BEFORE saving so reasoning extraction has the data
|
|
702
726
|
updateSessionState(sessionInfo.currentSession.session_id, {
|
|
703
727
|
final_response: textContent.substring(0, 10000),
|
|
704
728
|
});
|
|
705
|
-
await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete');
|
|
729
|
+
await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete', taskAnalysis.task_type);
|
|
706
730
|
markSessionCompleted(sessionInfo.currentSession.session_id);
|
|
707
731
|
activeSessions.delete(sessionInfo.currentSession.session_id);
|
|
708
732
|
lastDriftResults.delete(sessionInfo.currentSession.session_id);
|
|
@@ -745,34 +769,26 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
745
769
|
// Example: user asks clarification question, answer is provided in single turn
|
|
746
770
|
try {
|
|
747
771
|
const newSessionId = randomUUID();
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
if (isIntentExtractionAvailable() && latestUserMessage.length > 10) {
|
|
751
|
-
try {
|
|
752
|
-
const intentData = await extractIntent(latestUserMessage);
|
|
753
|
-
goalSummary = intentData.goal;
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
// Keep fallback goalSummary
|
|
757
|
-
}
|
|
758
|
-
}
|
|
772
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
773
|
+
// console.log(`[DEBUG] PATH D (instant_complete): latestUserMessage="${latestUserMessage.substring(0, 50)}"`);
|
|
759
774
|
const instantSession = createSessionState({
|
|
760
775
|
session_id: newSessionId,
|
|
761
776
|
project_path: sessionInfo.projectPath,
|
|
762
|
-
original_goal:
|
|
777
|
+
original_goal: taskAnalysis.current_goal || '', // Don't fallback to user prompt
|
|
778
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
763
779
|
task_type: 'main',
|
|
764
780
|
});
|
|
765
781
|
// Set final_response for reasoning extraction
|
|
766
782
|
updateSessionState(newSessionId, {
|
|
767
783
|
final_response: textContent.substring(0, 10000),
|
|
768
784
|
});
|
|
769
|
-
await saveToTeamMemory(newSessionId, 'complete');
|
|
785
|
+
await saveToTeamMemory(newSessionId, 'complete', taskAnalysis.task_type);
|
|
770
786
|
markSessionCompleted(newSessionId);
|
|
771
787
|
logger.info({ msg: 'Instant complete - new task saved immediately', sessionId: newSessionId.substring(0, 8) });
|
|
772
788
|
// TASK LOG: Instant complete (new task that finished in one turn)
|
|
773
789
|
taskLog('ORCHESTRATION_TASK_COMPLETE', {
|
|
774
790
|
sessionId: newSessionId,
|
|
775
|
-
goal:
|
|
791
|
+
goal: taskAnalysis.current_goal || '',
|
|
776
792
|
source: 'instant_complete',
|
|
777
793
|
});
|
|
778
794
|
}
|
|
@@ -787,7 +803,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
787
803
|
if (sessionInfo.currentSession) {
|
|
788
804
|
const parentId = sessionInfo.currentSession.parent_session_id;
|
|
789
805
|
try {
|
|
790
|
-
await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete');
|
|
806
|
+
await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete', taskAnalysis.task_type);
|
|
791
807
|
markSessionCompleted(sessionInfo.currentSession.session_id);
|
|
792
808
|
activeSessions.delete(sessionInfo.currentSession.session_id);
|
|
793
809
|
lastDriftResults.delete(sessionInfo.currentSession.session_id);
|
|
@@ -820,7 +836,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
820
836
|
// Fall back to existing session or create new with intent extraction
|
|
821
837
|
if (!sessionInfo.currentSession) {
|
|
822
838
|
let intentData = {
|
|
823
|
-
goal:
|
|
839
|
+
goal: '', // Don't copy user prompt - let extractIntent synthesize or leave empty
|
|
824
840
|
expected_scope: [],
|
|
825
841
|
constraints: [],
|
|
826
842
|
keywords: [],
|
|
@@ -844,6 +860,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
844
860
|
session_id: newSessionId,
|
|
845
861
|
project_path: sessionInfo.projectPath,
|
|
846
862
|
original_goal: intentData.goal,
|
|
863
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
847
864
|
expected_scope: intentData.expected_scope,
|
|
848
865
|
constraints: intentData.constraints,
|
|
849
866
|
keywords: intentData.keywords,
|
|
@@ -862,7 +879,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
862
879
|
});
|
|
863
880
|
if (!sessionInfo.currentSession) {
|
|
864
881
|
let intentData = {
|
|
865
|
-
goal:
|
|
882
|
+
goal: '', // Don't copy user prompt - let extractIntent synthesize or leave empty
|
|
866
883
|
expected_scope: [],
|
|
867
884
|
constraints: [],
|
|
868
885
|
keywords: [],
|
|
@@ -887,6 +904,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
887
904
|
session_id: newSessionId,
|
|
888
905
|
project_path: sessionInfo.projectPath,
|
|
889
906
|
original_goal: intentData.goal,
|
|
907
|
+
raw_user_prompt: latestUserMessage.substring(0, 500),
|
|
890
908
|
expected_scope: intentData.expected_scope,
|
|
891
909
|
constraints: intentData.constraints,
|
|
892
910
|
keywords: intentData.keywords,
|
|
@@ -927,16 +945,32 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
927
945
|
// === CLEAR MODE PRE-COMPUTE (85% threshold) ===
|
|
928
946
|
// Pre-compute summary before hitting 100% threshold to avoid blocking Haiku call
|
|
929
947
|
const preComputeThreshold = Math.floor(config.TOKEN_CLEAR_THRESHOLD * 0.85);
|
|
948
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
949
|
+
// console.log('[CLEAR-PRECOMPUTE] ═══════════════════════════════════════');
|
|
950
|
+
// console.log('[CLEAR-PRECOMPUTE] actualContextSize:', actualContextSize);
|
|
951
|
+
// console.log('[CLEAR-PRECOMPUTE] preComputeThreshold:', preComputeThreshold);
|
|
952
|
+
// console.log('[CLEAR-PRECOMPUTE] hasActiveSession:', !!activeSession);
|
|
953
|
+
// console.log('[CLEAR-PRECOMPUTE] hasPendingSummary:', !!activeSession?.pending_clear_summary);
|
|
954
|
+
// console.log('[CLEAR-PRECOMPUTE] isSummaryAvailable:', isSummaryAvailable());
|
|
955
|
+
// console.log('[CLEAR-PRECOMPUTE] shouldPrecompute:',
|
|
956
|
+
// !!activeSession &&
|
|
957
|
+
// actualContextSize > preComputeThreshold &&
|
|
958
|
+
// !activeSession?.pending_clear_summary &&
|
|
959
|
+
// isSummaryAvailable());
|
|
960
|
+
// console.log('[CLEAR-PRECOMPUTE] ═══════════════════════════════════════');
|
|
930
961
|
// Use actualContextSize (cacheCreation + cacheRead) as the real context size
|
|
931
962
|
if (activeSession &&
|
|
932
963
|
actualContextSize > preComputeThreshold &&
|
|
933
964
|
!activeSession.pending_clear_summary &&
|
|
934
965
|
isSummaryAvailable()) {
|
|
966
|
+
// console.log('[CLEAR-PRECOMPUTE] >>> STARTING SUMMARY GENERATION <<<');
|
|
935
967
|
// Get all validated steps for comprehensive summary
|
|
936
968
|
const allSteps = getValidatedSteps(activeSessionId);
|
|
969
|
+
// console.log('[CLEAR-PRECOMPUTE] validatedSteps:', allSteps.length);
|
|
937
970
|
// Generate summary asynchronously (fire-and-forget)
|
|
938
971
|
generateSessionSummary(activeSession, allSteps, 15000).then(summary => {
|
|
939
972
|
updateSessionState(activeSessionId, { pending_clear_summary: summary });
|
|
973
|
+
// console.log('[CLEAR-PRECOMPUTE] >>> SUMMARY SAVED <<<', summary.length, 'chars');
|
|
940
974
|
logger.info({
|
|
941
975
|
msg: 'CLEAR summary pre-computed',
|
|
942
976
|
actualContextSize,
|
|
@@ -944,6 +978,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
|
|
|
944
978
|
summaryLength: summary.length,
|
|
945
979
|
});
|
|
946
980
|
}).catch(err => {
|
|
981
|
+
// console.log('[CLEAR-PRECOMPUTE] >>> SUMMARY FAILED <<<', String(err));
|
|
947
982
|
logger.info({ msg: 'CLEAR summary generation failed', error: String(err) });
|
|
948
983
|
});
|
|
949
984
|
}
|
|
@@ -1166,7 +1201,8 @@ export async function startServer(options = {}) {
|
|
|
1166
1201
|
// Set debug mode based on flag
|
|
1167
1202
|
if (options.debug) {
|
|
1168
1203
|
setDebugMode(true);
|
|
1169
|
-
|
|
1204
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
1205
|
+
// console.log('[DEBUG] Logging to grov-proxy.log');
|
|
1170
1206
|
}
|
|
1171
1207
|
const server = createServer();
|
|
1172
1208
|
// Set server logger for background tasks
|
|
@@ -1194,11 +1230,9 @@ export async function startServer(options = {}) {
|
|
|
1194
1230
|
if (extendedCacheTimer) {
|
|
1195
1231
|
clearInterval(extendedCacheTimer);
|
|
1196
1232
|
extendedCacheTimer = null;
|
|
1197
|
-
log('Extended cache: timer stopped');
|
|
1198
1233
|
}
|
|
1199
1234
|
// 2. Clear sensitive cache data
|
|
1200
1235
|
if (extendedCache.size > 0) {
|
|
1201
|
-
log(`Extended cache: clearing ${extendedCache.size} entries`);
|
|
1202
1236
|
for (const entry of extendedCache.values()) {
|
|
1203
1237
|
for (const key of Object.keys(entry.headers)) {
|
|
1204
1238
|
entry.headers[key] = '';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare function setDebugMode(enabled: boolean): void;
|
|
2
|
+
export declare function isDebugMode(): boolean;
|
|
2
3
|
export declare function getNextRequestId(): number;
|
|
3
|
-
export declare function taskLog(
|
|
4
|
+
export declare function taskLog(_event: string, _data: Record<string, unknown>): void;
|
|
4
5
|
interface ProxyLogEntry {
|
|
5
6
|
timestamp: string;
|
|
6
7
|
requestId: number;
|
|
@@ -8,21 +8,27 @@ const TASK_LOG_PATH = path.join(process.cwd(), 'grov-task.log');
|
|
|
8
8
|
export function setDebugMode(enabled) {
|
|
9
9
|
debugMode = enabled;
|
|
10
10
|
}
|
|
11
|
+
export function isDebugMode() {
|
|
12
|
+
return debugMode;
|
|
13
|
+
}
|
|
11
14
|
export function getNextRequestId() {
|
|
12
15
|
return ++requestCounter;
|
|
13
16
|
}
|
|
14
|
-
export function taskLog(
|
|
17
|
+
export function taskLog(_event, _data) {
|
|
18
|
+
// Disabled - minimal logging mode
|
|
19
|
+
// To re-enable, set DEBUG_TASK_LOG=true
|
|
20
|
+
if (process.env.DEBUG_TASK_LOG !== 'true')
|
|
21
|
+
return;
|
|
15
22
|
const timestamp = new Date().toISOString();
|
|
16
|
-
const sessionId =
|
|
17
|
-
|
|
18
|
-
const kvPairs = Object.entries(data)
|
|
23
|
+
const sessionId = _data.sessionId ? String(_data.sessionId).substring(0, 8) : '-';
|
|
24
|
+
const kvPairs = Object.entries(_data)
|
|
19
25
|
.filter(([k]) => k !== 'sessionId')
|
|
20
26
|
.map(([k, v]) => {
|
|
21
27
|
const val = typeof v === 'string' ? v.substring(0, 100) : JSON.stringify(v);
|
|
22
28
|
return `${k}=${val}`;
|
|
23
29
|
})
|
|
24
30
|
.join(' | ');
|
|
25
|
-
const line = `[${timestamp}] [${sessionId}] ${
|
|
31
|
+
const line = `[${timestamp}] [${sessionId}] ${_event}: ${kvPairs}\n`;
|
|
26
32
|
fs.appendFileSync(TASK_LOG_PATH, line);
|
|
27
33
|
}
|
|
28
34
|
export function proxyLog(entry) {
|