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
|
@@ -69,6 +69,7 @@ export function initDatabase() {
|
|
|
69
69
|
user TEXT,
|
|
70
70
|
original_query TEXT NOT NULL,
|
|
71
71
|
goal TEXT,
|
|
72
|
+
summary TEXT,
|
|
72
73
|
reasoning_trace JSON DEFAULT '[]',
|
|
73
74
|
files_touched JSON DEFAULT '[]',
|
|
74
75
|
decisions JSON DEFAULT '[]',
|
|
@@ -110,6 +111,14 @@ export function initDatabase() {
|
|
|
110
111
|
db.exec(`ALTER TABLE tasks ADD COLUMN sync_error TEXT`);
|
|
111
112
|
}
|
|
112
113
|
catch { /* column exists */ }
|
|
114
|
+
try {
|
|
115
|
+
db.exec(`ALTER TABLE tasks ADD COLUMN summary TEXT`);
|
|
116
|
+
}
|
|
117
|
+
catch { /* column exists */ }
|
|
118
|
+
try {
|
|
119
|
+
db.exec(`ALTER TABLE tasks ADD COLUMN system_name TEXT`);
|
|
120
|
+
}
|
|
121
|
+
catch { /* column exists */ }
|
|
113
122
|
// Create session_states table (temporary per-session tracking)
|
|
114
123
|
db.exec(`
|
|
115
124
|
CREATE TABLE IF NOT EXISTS session_states (
|
|
@@ -117,6 +126,7 @@ export function initDatabase() {
|
|
|
117
126
|
user_id TEXT,
|
|
118
127
|
project_path TEXT NOT NULL,
|
|
119
128
|
original_goal TEXT,
|
|
129
|
+
raw_user_prompt TEXT,
|
|
120
130
|
expected_scope JSON DEFAULT '[]',
|
|
121
131
|
constraints JSON DEFAULT '[]',
|
|
122
132
|
keywords JSON DEFAULT '[]',
|
|
@@ -197,6 +207,10 @@ export function initDatabase() {
|
|
|
197
207
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_session_completed ON session_states(completed_at)`);
|
|
198
208
|
}
|
|
199
209
|
catch { /* index exists */ }
|
|
210
|
+
try {
|
|
211
|
+
db.exec(`ALTER TABLE session_states ADD COLUMN raw_user_prompt TEXT`);
|
|
212
|
+
}
|
|
213
|
+
catch { /* column exists */ }
|
|
200
214
|
// Create file_reasoning table (file-level reasoning with anchoring)
|
|
201
215
|
db.exec(`
|
|
202
216
|
CREATE TABLE IF NOT EXISTS file_reasoning (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { TaskStatus, TriggerReason, SessionStatus, SessionMode, TaskType, StepActionType, DriftType, CorrectionLevel, Task, CreateTaskInput, RecoveryPlan, DriftEvent, SessionState, CreateSessionStateInput, StepRecord, CreateStepInput, DriftLogEntry, CreateDriftLogInput, } from './types.js';
|
|
2
2
|
export { initDatabase, closeDatabase, getDatabasePath } from './database.js';
|
|
3
|
-
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError } from './tasks.js';
|
|
3
|
+
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError, getSyncedTaskCount, cleanupOldSyncedTasks } from './tasks.js';
|
|
4
4
|
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject } from './sessions.js';
|
|
5
5
|
export { createStep, getStepsForSession, getRecentSteps, getValidatedSteps, getKeyDecisions, getEditedFiles, deleteStepsForSession, updateRecentStepsReasoning, updateLastChecked } from './steps.js';
|
|
6
6
|
export { updateSessionDrift, logDriftEvent } from './drift.js';
|
package/dist/lib/store/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Re-export database functions
|
|
3
3
|
export { initDatabase, closeDatabase, getDatabasePath } from './database.js';
|
|
4
4
|
// Re-export task functions
|
|
5
|
-
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError } from './tasks.js';
|
|
5
|
+
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError, getSyncedTaskCount, cleanupOldSyncedTasks } from './tasks.js';
|
|
6
6
|
// Re-export session functions
|
|
7
7
|
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject } from './sessions.js';
|
|
8
8
|
// Re-export step functions
|
|
@@ -10,6 +10,7 @@ function rowToSessionState(row) {
|
|
|
10
10
|
user_id: row.user_id,
|
|
11
11
|
project_path: row.project_path,
|
|
12
12
|
original_goal: row.original_goal,
|
|
13
|
+
raw_user_prompt: row.raw_user_prompt,
|
|
13
14
|
expected_scope: safeJsonParse(row.expected_scope, []),
|
|
14
15
|
constraints: safeJsonParse(row.constraints, []),
|
|
15
16
|
keywords: safeJsonParse(row.keywords, []),
|
|
@@ -46,6 +47,8 @@ function rowToSessionState(row) {
|
|
|
46
47
|
* Uses INSERT OR IGNORE to handle race conditions safely.
|
|
47
48
|
*/
|
|
48
49
|
export function createSessionState(input) {
|
|
50
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
51
|
+
// console.log(`[DEBUG-DB] createSessionState called with raw_user_prompt="${input.raw_user_prompt?.substring(0, 50)}"`);
|
|
49
52
|
const database = getDb();
|
|
50
53
|
const now = new Date().toISOString();
|
|
51
54
|
const sessionState = {
|
|
@@ -54,6 +57,7 @@ export function createSessionState(input) {
|
|
|
54
57
|
user_id: input.user_id,
|
|
55
58
|
project_path: input.project_path,
|
|
56
59
|
original_goal: input.original_goal,
|
|
60
|
+
raw_user_prompt: input.raw_user_prompt,
|
|
57
61
|
expected_scope: input.expected_scope || [],
|
|
58
62
|
constraints: input.constraints || [],
|
|
59
63
|
keywords: input.keywords || [],
|
|
@@ -82,7 +86,7 @@ export function createSessionState(input) {
|
|
|
82
86
|
};
|
|
83
87
|
const stmt = database.prepare(`
|
|
84
88
|
INSERT OR IGNORE INTO session_states (
|
|
85
|
-
session_id, user_id, project_path, original_goal,
|
|
89
|
+
session_id, user_id, project_path, original_goal, raw_user_prompt,
|
|
86
90
|
expected_scope, constraints, keywords,
|
|
87
91
|
token_count, escalation_count, session_mode,
|
|
88
92
|
waiting_for_recovery, last_checked_at, last_clear_at,
|
|
@@ -90,9 +94,11 @@ export function createSessionState(input) {
|
|
|
90
94
|
parent_session_id, task_type,
|
|
91
95
|
success_criteria, last_drift_score, pending_recovery_plan, drift_history,
|
|
92
96
|
completed_at
|
|
93
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
97
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94
98
|
`);
|
|
95
|
-
stmt.run(sessionState.session_id, sessionState.user_id || null, sessionState.project_path, sessionState.original_goal || null, JSON.stringify(sessionState.expected_scope), JSON.stringify(sessionState.constraints), JSON.stringify(sessionState.keywords), sessionState.token_count, sessionState.escalation_count, sessionState.session_mode, sessionState.waiting_for_recovery ? 1 : 0, sessionState.last_checked_at, sessionState.last_clear_at || null, sessionState.start_time, sessionState.last_update, sessionState.status, sessionState.parent_session_id || null, sessionState.task_type, JSON.stringify(sessionState.success_criteria || []), sessionState.last_drift_score || null, sessionState.pending_recovery_plan ? JSON.stringify(sessionState.pending_recovery_plan) : null, JSON.stringify(sessionState.drift_history || []), sessionState.completed_at || null);
|
|
99
|
+
stmt.run(sessionState.session_id, sessionState.user_id || null, sessionState.project_path, sessionState.original_goal || null, sessionState.raw_user_prompt || null, JSON.stringify(sessionState.expected_scope), JSON.stringify(sessionState.constraints), JSON.stringify(sessionState.keywords), sessionState.token_count, sessionState.escalation_count, sessionState.session_mode, sessionState.waiting_for_recovery ? 1 : 0, sessionState.last_checked_at, sessionState.last_clear_at || null, sessionState.start_time, sessionState.last_update, sessionState.status, sessionState.parent_session_id || null, sessionState.task_type, JSON.stringify(sessionState.success_criteria || []), sessionState.last_drift_score || null, sessionState.pending_recovery_plan ? JSON.stringify(sessionState.pending_recovery_plan) : null, JSON.stringify(sessionState.drift_history || []), sessionState.completed_at || null);
|
|
100
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
101
|
+
// console.log(`[DEBUG-DB] INSERT done. sessionState.raw_user_prompt="${sessionState.raw_user_prompt?.substring(0, 50)}"`);
|
|
96
102
|
return sessionState;
|
|
97
103
|
}
|
|
98
104
|
/**
|
|
@@ -31,3 +31,15 @@ export declare function markTaskSynced(id: string): void;
|
|
|
31
31
|
* Record a sync error for a task
|
|
32
32
|
*/
|
|
33
33
|
export declare function setTaskSyncError(id: string, error: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get count of synced tasks
|
|
36
|
+
*/
|
|
37
|
+
export declare function getSyncedTaskCount(): number;
|
|
38
|
+
/**
|
|
39
|
+
* Delete oldest synced tasks to maintain storage limit
|
|
40
|
+
* Called after successful sync to prevent local DB bloat
|
|
41
|
+
*
|
|
42
|
+
* @param maxSyncedTasks - Maximum number of synced tasks to keep (default 500)
|
|
43
|
+
* @returns Number of tasks deleted
|
|
44
|
+
*/
|
|
45
|
+
export declare function cleanupOldSyncedTasks(maxSyncedTasks?: number): number;
|
package/dist/lib/store/tasks.js
CHANGED
|
@@ -11,6 +11,8 @@ function rowToTask(row) {
|
|
|
11
11
|
user: row.user,
|
|
12
12
|
original_query: row.original_query,
|
|
13
13
|
goal: row.goal,
|
|
14
|
+
system_name: row.system_name,
|
|
15
|
+
summary: row.summary,
|
|
14
16
|
reasoning_trace: safeJsonParse(row.reasoning_trace, []),
|
|
15
17
|
files_touched: safeJsonParse(row.files_touched, []),
|
|
16
18
|
decisions: safeJsonParse(row.decisions, []),
|
|
@@ -37,6 +39,8 @@ export function createTask(input) {
|
|
|
37
39
|
user: input.user,
|
|
38
40
|
original_query: input.original_query,
|
|
39
41
|
goal: input.goal,
|
|
42
|
+
system_name: input.system_name, // Parent system anchor for semantic search
|
|
43
|
+
summary: input.summary,
|
|
40
44
|
reasoning_trace: input.reasoning_trace || [],
|
|
41
45
|
files_touched: input.files_touched || [],
|
|
42
46
|
decisions: input.decisions || [],
|
|
@@ -53,18 +57,18 @@ export function createTask(input) {
|
|
|
53
57
|
};
|
|
54
58
|
const stmt = database.prepare(`
|
|
55
59
|
INSERT INTO tasks (
|
|
56
|
-
id, project_path, user, original_query, goal,
|
|
60
|
+
id, project_path, user, original_query, goal, system_name, summary,
|
|
57
61
|
reasoning_trace, files_touched, decisions, constraints,
|
|
58
62
|
status, trigger_reason, linked_commit,
|
|
59
63
|
parent_task_id, turn_number, tags, created_at, synced_at, sync_error
|
|
60
64
|
) VALUES (
|
|
61
|
-
?, ?, ?, ?, ?,
|
|
65
|
+
?, ?, ?, ?, ?, ?, ?,
|
|
62
66
|
?, ?, ?, ?,
|
|
63
67
|
?, ?, ?,
|
|
64
68
|
?, ?, ?, ?, ?, ?
|
|
65
69
|
)
|
|
66
70
|
`);
|
|
67
|
-
stmt.run(task.id, task.project_path, task.user || null, task.original_query, task.goal || null, JSON.stringify(task.reasoning_trace), JSON.stringify(task.files_touched), JSON.stringify(task.decisions), JSON.stringify(task.constraints), task.status, task.trigger_reason || null, task.linked_commit || null, task.parent_task_id || null, task.turn_number || null, JSON.stringify(task.tags), task.created_at, task.synced_at, task.sync_error);
|
|
71
|
+
stmt.run(task.id, task.project_path, task.user || null, task.original_query, task.goal || null, task.system_name || null, task.summary || null, JSON.stringify(task.reasoning_trace), JSON.stringify(task.files_touched), JSON.stringify(task.decisions), JSON.stringify(task.constraints), task.status, task.trigger_reason || null, task.linked_commit || null, task.parent_task_id || null, task.turn_number || null, JSON.stringify(task.tags), task.created_at, task.synced_at, task.sync_error);
|
|
68
72
|
return task;
|
|
69
73
|
}
|
|
70
74
|
/**
|
|
@@ -131,3 +135,39 @@ export function setTaskSyncError(id, error) {
|
|
|
131
135
|
const database = getDb();
|
|
132
136
|
database.prepare('UPDATE tasks SET sync_error = ? WHERE id = ?').run(error, id);
|
|
133
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Get count of synced tasks
|
|
140
|
+
*/
|
|
141
|
+
export function getSyncedTaskCount() {
|
|
142
|
+
const database = getDb();
|
|
143
|
+
const stmt = database.prepare('SELECT COUNT(*) as count FROM tasks WHERE synced_at IS NOT NULL');
|
|
144
|
+
const row = stmt.get();
|
|
145
|
+
return row?.count ?? 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Delete oldest synced tasks to maintain storage limit
|
|
149
|
+
* Called after successful sync to prevent local DB bloat
|
|
150
|
+
*
|
|
151
|
+
* @param maxSyncedTasks - Maximum number of synced tasks to keep (default 500)
|
|
152
|
+
* @returns Number of tasks deleted
|
|
153
|
+
*/
|
|
154
|
+
export function cleanupOldSyncedTasks(maxSyncedTasks = 500) {
|
|
155
|
+
const database = getDb();
|
|
156
|
+
const currentCount = getSyncedTaskCount();
|
|
157
|
+
if (currentCount <= maxSyncedTasks) {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
const toDelete = currentCount - maxSyncedTasks;
|
|
161
|
+
// Delete oldest synced tasks (by synced_at, not created_at)
|
|
162
|
+
const stmt = database.prepare(`
|
|
163
|
+
DELETE FROM tasks
|
|
164
|
+
WHERE id IN (
|
|
165
|
+
SELECT id FROM tasks
|
|
166
|
+
WHERE synced_at IS NOT NULL
|
|
167
|
+
ORDER BY synced_at ASC
|
|
168
|
+
LIMIT ?
|
|
169
|
+
)
|
|
170
|
+
`);
|
|
171
|
+
const result = stmt.run(toDelete);
|
|
172
|
+
return result.changes;
|
|
173
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReasoningTraceEntry } from '@grov/shared';
|
|
1
2
|
export type TaskStatus = 'complete' | 'question' | 'partial' | 'abandoned';
|
|
2
3
|
export type TriggerReason = 'complete' | 'threshold' | 'abandoned';
|
|
3
4
|
export interface Task {
|
|
@@ -6,9 +7,13 @@ export interface Task {
|
|
|
6
7
|
user?: string;
|
|
7
8
|
original_query: string;
|
|
8
9
|
goal?: string;
|
|
9
|
-
|
|
10
|
+
system_name?: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
reasoning_trace: ReasoningTraceEntry[];
|
|
10
13
|
files_touched: string[];
|
|
11
14
|
decisions: Array<{
|
|
15
|
+
aspect?: string;
|
|
16
|
+
tags?: string;
|
|
12
17
|
choice: string;
|
|
13
18
|
reason: string;
|
|
14
19
|
}>;
|
|
@@ -28,9 +33,13 @@ export interface CreateTaskInput {
|
|
|
28
33
|
user?: string;
|
|
29
34
|
original_query: string;
|
|
30
35
|
goal?: string;
|
|
31
|
-
|
|
36
|
+
system_name?: string;
|
|
37
|
+
summary?: string;
|
|
38
|
+
reasoning_trace?: ReasoningTraceEntry[];
|
|
32
39
|
files_touched?: string[];
|
|
33
40
|
decisions?: Array<{
|
|
41
|
+
aspect?: string;
|
|
42
|
+
tags?: string;
|
|
34
43
|
choice: string;
|
|
35
44
|
reason: string;
|
|
36
45
|
}>;
|
|
@@ -62,6 +71,7 @@ interface SessionStateBase {
|
|
|
62
71
|
user_id?: string;
|
|
63
72
|
project_path: string;
|
|
64
73
|
original_goal?: string;
|
|
74
|
+
raw_user_prompt?: string;
|
|
65
75
|
expected_scope: string[];
|
|
66
76
|
constraints: string[];
|
|
67
77
|
keywords: string[];
|
|
@@ -102,6 +112,7 @@ export interface CreateSessionStateInput {
|
|
|
102
112
|
user_id?: string;
|
|
103
113
|
project_path: string;
|
|
104
114
|
original_goal?: string;
|
|
115
|
+
raw_user_prompt?: string;
|
|
105
116
|
expected_scope?: string[];
|
|
106
117
|
constraints?: string[];
|
|
107
118
|
keywords?: string[];
|
package/dist/lib/store/types.js
CHANGED
|
@@ -52,10 +52,6 @@ export declare function extractTokenUsage(response: AnthropicResponse): {
|
|
|
52
52
|
cacheCreation: number;
|
|
53
53
|
cacheRead: number;
|
|
54
54
|
};
|
|
55
|
-
/**
|
|
56
|
-
* Check if response contains any file-modifying actions
|
|
57
|
-
*/
|
|
58
|
-
export declare function hasModifyingActions(actions: ParsedAction[]): boolean;
|
|
59
55
|
/**
|
|
60
56
|
* Get all unique files from actions
|
|
61
57
|
*/
|
|
@@ -143,35 +143,6 @@ export function extractTokenUsage(response) {
|
|
|
143
143
|
cacheRead: response.usage.cache_read_input_tokens || 0,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Check if response contains any file-modifying actions
|
|
148
|
-
*/
|
|
149
|
-
export function hasModifyingActions(actions) {
|
|
150
|
-
return actions.some(a => a.actionType === 'edit' ||
|
|
151
|
-
a.actionType === 'write' ||
|
|
152
|
-
(a.actionType === 'bash' && a.command && isModifyingBashCommand(a.command)));
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Check if bash command modifies files
|
|
156
|
-
*/
|
|
157
|
-
function isModifyingBashCommand(command) {
|
|
158
|
-
const modifyingPatterns = [
|
|
159
|
-
/\brm\b/,
|
|
160
|
-
/\bmv\b/,
|
|
161
|
-
/\bcp\b/,
|
|
162
|
-
/\bmkdir\b/,
|
|
163
|
-
/\btouch\b/,
|
|
164
|
-
/\bchmod\b/,
|
|
165
|
-
/\bchown\b/,
|
|
166
|
-
/\bsed\b.*-i/,
|
|
167
|
-
/\btee\b/,
|
|
168
|
-
/>/, // redirect
|
|
169
|
-
/\bgit\s+(add|commit|push|checkout|reset)/,
|
|
170
|
-
/\bnpm\s+(install|uninstall)/,
|
|
171
|
-
/\byarn\s+(add|remove)/,
|
|
172
|
-
];
|
|
173
|
-
return modifyingPatterns.some(p => p.test(command));
|
|
174
|
-
}
|
|
175
146
|
/**
|
|
176
147
|
* Get all unique files from actions
|
|
177
148
|
*/
|
package/dist/proxy/cache.d.ts
CHANGED
|
@@ -30,7 +30,3 @@ export declare function getTeamMemoryCache(projectPath: string): string | null;
|
|
|
30
30
|
* Check if cache exists for a specific project
|
|
31
31
|
*/
|
|
32
32
|
export declare function hasCacheForProject(projectPath: string): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Get current cache project path (for logging/debugging)
|
|
35
|
-
*/
|
|
36
|
-
export declare function getCacheProjectPath(): string | null;
|
package/dist/proxy/cache.js
CHANGED
|
@@ -15,7 +15,8 @@ export let globalTeamMemoryCache = null;
|
|
|
15
15
|
*/
|
|
16
16
|
export function invalidateTeamMemoryCache() {
|
|
17
17
|
globalTeamMemoryCache = null;
|
|
18
|
-
|
|
18
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
19
|
+
// console.log('[CACHE] Team memory cache invalidated');
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Set the global team memory cache
|
|
@@ -24,7 +25,8 @@ export function invalidateTeamMemoryCache() {
|
|
|
24
25
|
*/
|
|
25
26
|
export function setTeamMemoryCache(projectPath, content) {
|
|
26
27
|
globalTeamMemoryCache = { projectPath, content };
|
|
27
|
-
|
|
28
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
29
|
+
// console.log(`[CACHE] Team memory cache set for project: ${projectPath} (${content.length} chars)`);
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Get the current cache content if it matches the project path
|
|
@@ -43,9 +45,3 @@ export function getTeamMemoryCache(projectPath) {
|
|
|
43
45
|
export function hasCacheForProject(projectPath) {
|
|
44
46
|
return globalTeamMemoryCache?.projectPath === projectPath;
|
|
45
47
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Get current cache project path (for logging/debugging)
|
|
48
|
-
*/
|
|
49
|
-
export function getCacheProjectPath() {
|
|
50
|
-
return globalTeamMemoryCache?.projectPath || null;
|
|
51
|
-
}
|
|
@@ -23,7 +23,7 @@ export function evictOldestCacheEntry() {
|
|
|
23
23
|
}
|
|
24
24
|
if (oldestId) {
|
|
25
25
|
extendedCache.delete(oldestId);
|
|
26
|
-
|
|
26
|
+
// Evicted silently - capacity limit
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async function sendExtendedCacheKeepAlive(projectPath, entry) {
|
|
@@ -77,7 +77,7 @@ async function sendExtendedCacheKeepAlive(projectPath, entry) {
|
|
|
77
77
|
const keepAliveMsg = messagesIsEmpty
|
|
78
78
|
? '{"role":"user","content":"."}'
|
|
79
79
|
: ',{"role":"user","content":"."}';
|
|
80
|
-
|
|
80
|
+
// Sending keep-alive - result logged after response
|
|
81
81
|
rawBodyStr = rawBodyStr.slice(0, messagesEnd) + keepAliveMsg + rawBodyStr.slice(messagesEnd);
|
|
82
82
|
// NOTE: We do NOT modify max_tokens or stream!
|
|
83
83
|
// Keeping them identical preserves the cache prefix for byte-exact matching.
|
|
@@ -94,12 +94,10 @@ async function sendExtendedCacheKeepAlive(projectPath, entry) {
|
|
|
94
94
|
if (result.statusCode !== 200) {
|
|
95
95
|
throw new Error(`Keep-alive failed: ${result.statusCode}`);
|
|
96
96
|
}
|
|
97
|
-
// Log cache metrics
|
|
97
|
+
// Log cache metrics (minimal)
|
|
98
98
|
const usage = result.body.usage;
|
|
99
99
|
const cacheRead = usage?.cache_read_input_tokens || 0;
|
|
100
|
-
|
|
101
|
-
const inputTokens = usage?.input_tokens || 0;
|
|
102
|
-
log(`Extended cache: keep-alive for ${projectName} - cache_read=${cacheRead}, cache_create=${cacheCreate}, input=${inputTokens}`);
|
|
100
|
+
console.log(`[CACHE] keep-alive ${projectName}: read=${cacheRead}`);
|
|
103
101
|
}
|
|
104
102
|
export async function checkExtendedCache() {
|
|
105
103
|
const now = Date.now();
|
|
@@ -111,7 +109,6 @@ export async function checkExtendedCache() {
|
|
|
111
109
|
// Stale cleanup: user left after 10 minutes
|
|
112
110
|
if (idleTime > EXTENDED_CACHE_MAX_IDLE) {
|
|
113
111
|
extendedCache.delete(projectPath);
|
|
114
|
-
log(`Extended cache: cleared ${projectName} (stale)`);
|
|
115
112
|
continue;
|
|
116
113
|
}
|
|
117
114
|
// Skip if not idle enough yet
|
|
@@ -121,7 +118,6 @@ export async function checkExtendedCache() {
|
|
|
121
118
|
// Skip if already sent max keep-alives
|
|
122
119
|
if (entry.keepAliveCount >= EXTENDED_CACHE_MAX_KEEPALIVES) {
|
|
123
120
|
extendedCache.delete(projectPath);
|
|
124
|
-
log(`Extended cache: cleared ${projectName} (max retries)`);
|
|
125
121
|
continue;
|
|
126
122
|
}
|
|
127
123
|
projectsToKeepAlive.push({ projectPath, entry });
|
|
@@ -137,14 +133,8 @@ export async function checkExtendedCache() {
|
|
|
137
133
|
})
|
|
138
134
|
.catch((err) => {
|
|
139
135
|
extendedCache.delete(projectPath);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
? err.message
|
|
143
|
-
: (err && typeof err === 'object' && 'message' in err)
|
|
144
|
-
? String(err.message)
|
|
145
|
-
: JSON.stringify(err);
|
|
146
|
-
const errType = err && typeof err === 'object' && 'type' in err ? ` [${err.type}]` : '';
|
|
147
|
-
log(`Extended cache: cleared ${projectName} (error${errType}: ${errMsg})`);
|
|
136
|
+
const errMsg = err instanceof Error ? err.message : 'unknown';
|
|
137
|
+
console.error(`[CACHE] keep-alive ${projectName} failed: ${errMsg}`);
|
|
148
138
|
});
|
|
149
139
|
keepAlivePromises.push(promise);
|
|
150
140
|
}
|
|
@@ -23,7 +23,8 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
23
23
|
// No need to do semantic search or cache operations for these
|
|
24
24
|
const earlyUserPrompt = extractLastUserPrompt(modified.messages || []);
|
|
25
25
|
if (earlyUserPrompt === 'Warmup') {
|
|
26
|
-
|
|
26
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
27
|
+
// console.log('[INJECT] Skipping warmup request (no search, no cache)');
|
|
27
28
|
return modified;
|
|
28
29
|
}
|
|
29
30
|
// Detect request type: first, continuation, or retry
|
|
@@ -45,24 +46,26 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
45
46
|
// === PLANNING CLEAR: Reset after planning task completes ===
|
|
46
47
|
// This ensures implementation phase starts fresh with planning context from team memory
|
|
47
48
|
if (pendingPlanClear && pendingPlanClear.projectPath === sessionInfo.projectPath) {
|
|
48
|
-
// 1.
|
|
49
|
-
modified.messages = [];
|
|
50
|
-
// 2. Inject planning summary into system prompt
|
|
51
|
-
appendToSystemPrompt(modified, pendingPlanClear.summary);
|
|
52
|
-
// 3. Rebuild team memory NOW (includes the just-saved planning task)
|
|
49
|
+
// 1. Extract context BEFORE emptying messages (for hybrid search)
|
|
53
50
|
const mentionedFiles = extractFilesFromMessages(modified.messages || []);
|
|
54
51
|
const userPrompt = extractLastUserPrompt(modified.messages || []);
|
|
52
|
+
// 2. Empty messages array (fresh start)
|
|
53
|
+
modified.messages = [];
|
|
54
|
+
// 3. Inject planning summary into system prompt
|
|
55
|
+
appendToSystemPrompt(modified, pendingPlanClear.summary);
|
|
56
|
+
// 4. Rebuild team memory NOW (includes the just-saved planning task)
|
|
55
57
|
// Use cloud-first approach if sync is enabled
|
|
56
58
|
let teamContext = null;
|
|
57
59
|
const teamId = getSyncTeamId();
|
|
58
60
|
if (isSyncEnabled() && teamId) {
|
|
59
|
-
|
|
61
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
62
|
+
// console.log(`[INJECT] PLANNING_CLEAR: Using cloud team memory (teamId=${teamId.substring(0, 8)}...)`);
|
|
60
63
|
teamContext = await buildTeamMemoryContextCloud(teamId, sessionInfo.projectPath, mentionedFiles, userPrompt // For hybrid semantic search
|
|
61
64
|
);
|
|
62
65
|
}
|
|
63
66
|
else {
|
|
64
67
|
// Sync not enabled - no injection (cloud-first approach)
|
|
65
|
-
console.log('[INJECT] Sync not enabled. Enable sync for team memory injection.');
|
|
68
|
+
// console.log('[INJECT] Sync not enabled. Enable sync for team memory injection.');
|
|
66
69
|
teamContext = null;
|
|
67
70
|
}
|
|
68
71
|
if (teamContext) {
|
|
@@ -81,8 +84,18 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
81
84
|
// If token count exceeds threshold AND we have a pre-computed summary, apply CLEAR
|
|
82
85
|
if (sessionState) {
|
|
83
86
|
const currentTokenCount = sessionState.token_count || 0;
|
|
87
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
88
|
+
// console.log('[CLEAR-CHECK] ═══════════════════════════════════════════');
|
|
89
|
+
// console.log('[CLEAR-CHECK] currentTokenCount:', currentTokenCount);
|
|
90
|
+
// console.log('[CLEAR-CHECK] threshold:', config.TOKEN_CLEAR_THRESHOLD);
|
|
91
|
+
// console.log('[CLEAR-CHECK] exceedsThreshold:', currentTokenCount > config.TOKEN_CLEAR_THRESHOLD);
|
|
92
|
+
// console.log('[CLEAR-CHECK] hasPendingSummary:', !!sessionState.pending_clear_summary);
|
|
93
|
+
// console.log('[CLEAR-CHECK] summaryLength:', sessionState.pending_clear_summary?.length || 0);
|
|
94
|
+
// console.log('[CLEAR-CHECK] shouldClear:', currentTokenCount > config.TOKEN_CLEAR_THRESHOLD && !!sessionState.pending_clear_summary);
|
|
95
|
+
// console.log('[CLEAR-CHECK] ═══════════════════════════════════════════');
|
|
84
96
|
if (currentTokenCount > config.TOKEN_CLEAR_THRESHOLD &&
|
|
85
97
|
sessionState.pending_clear_summary) {
|
|
98
|
+
// console.log('[CLEAR-CHECK] >>> CLEAR MODE TRIGGERED! <<<');
|
|
86
99
|
logger.info({
|
|
87
100
|
msg: 'CLEAR MODE ACTIVATED - resetting conversation',
|
|
88
101
|
tokenCount: currentTokenCount,
|
|
@@ -95,6 +108,7 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
95
108
|
appendToSystemPrompt(modified, sessionState.pending_clear_summary);
|
|
96
109
|
// 3. Mark session as cleared
|
|
97
110
|
markCleared(sessionInfo.sessionId);
|
|
111
|
+
console.log(`[CLEAR] Context reset (${sessionState.pending_clear_summary?.length || 0} chars summary)`);
|
|
98
112
|
// 4. Clear pending summary and invalidate GLOBAL team memory cache (new baseline)
|
|
99
113
|
updateSessionState(sessionInfo.sessionId, { pending_clear_summary: undefined });
|
|
100
114
|
invalidateTeamMemoryCache(); // Force recalculation on next request (CLEAR mode)
|
|
@@ -113,7 +127,7 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
113
127
|
// Reuse GLOBAL cached team memory (constant for entire conversation)
|
|
114
128
|
modified.__grovInjection = globalTeamMemoryCache.content;
|
|
115
129
|
modified.__grovInjectionCached = true;
|
|
116
|
-
|
|
130
|
+
// Using cached team memory
|
|
117
131
|
}
|
|
118
132
|
else {
|
|
119
133
|
// First request OR project changed OR cache was invalidated: compute team memory
|
|
@@ -123,16 +137,15 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
123
137
|
let teamContext = null;
|
|
124
138
|
const teamId = getSyncTeamId();
|
|
125
139
|
if (isSyncEnabled() && teamId) {
|
|
126
|
-
console.log(`[INJECT] First/cache miss: Using cloud team memory (teamId=${teamId.substring(0, 8)}...)`);
|
|
127
140
|
teamContext = await buildTeamMemoryContextCloud(teamId, sessionInfo.projectPath, mentionedFiles, userPrompt // For hybrid semantic search
|
|
128
141
|
);
|
|
129
142
|
}
|
|
130
143
|
else {
|
|
131
144
|
// Sync not enabled - no injection (cloud-first approach)
|
|
132
|
-
console.log('[INJECT] Sync not enabled. Enable sync for team memory injection.');
|
|
145
|
+
// console.log('[INJECT] Sync not enabled. Enable sync for team memory injection.');
|
|
133
146
|
teamContext = null;
|
|
134
147
|
}
|
|
135
|
-
console.log(`[CACHE] Computing team memory (first/new), files=${mentionedFiles.length}, result=${teamContext ? teamContext.length : 'null'}`);
|
|
148
|
+
// console.log(`[CACHE] Computing team memory (first/new), files=${mentionedFiles.length}, result=${teamContext ? teamContext.length : 'null'}`);
|
|
136
149
|
if (teamContext) {
|
|
137
150
|
modified.__grovInjection = teamContext;
|
|
138
151
|
modified.__grovInjectionCached = false;
|
|
@@ -141,7 +154,11 @@ export async function preProcessRequest(body, sessionInfo, logger, detectRequest
|
|
|
141
154
|
}
|
|
142
155
|
else {
|
|
143
156
|
// No team memory available - clear global cache for this project
|
|
157
|
+
// DEBUG: Commented out for cleaner terminal - uncomment when debugging
|
|
158
|
+
// console.log('[CACHE-INVALIDATE] teamContext is NULL - why?');
|
|
159
|
+
// console.log('[CACHE-INVALIDATE] isSameProject:', isSameProject);
|
|
144
160
|
if (isSameProject) {
|
|
161
|
+
// console.log('[CACHE-INVALIDATE] >>> INVALIDATING CACHE (teamContext null) <<<');
|
|
145
162
|
invalidateTeamMemoryCache();
|
|
146
163
|
}
|
|
147
164
|
}
|
|
@@ -62,6 +62,7 @@ export function buildDynamicInjection(sessionId, sessionState, logger) {
|
|
|
62
62
|
parts.push(`[DRIFT: ${sessionState.pending_correction}]`);
|
|
63
63
|
debugInfo.hasDriftCorrection = true;
|
|
64
64
|
debugInfo.driftCorrectionLength = sessionState.pending_correction.length;
|
|
65
|
+
console.log(`[DRIFT] Correction injected (${sessionState.pending_correction.length} chars)`);
|
|
65
66
|
}
|
|
66
67
|
// 4. Add forced recovery if pending
|
|
67
68
|
if (sessionState?.pending_forced_recovery) {
|