grov 0.2.3 → 0.5.3
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/README.md +44 -5
- package/dist/cli.js +40 -2
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +115 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +13 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +127 -0
- package/dist/lib/api-client.d.ts +57 -0
- package/dist/lib/api-client.js +174 -0
- package/dist/lib/cloud-sync.d.ts +33 -0
- package/dist/lib/cloud-sync.js +176 -0
- package/dist/lib/credentials.d.ts +53 -0
- package/dist/lib/credentials.js +201 -0
- package/dist/lib/llm-extractor.d.ts +15 -39
- package/dist/lib/llm-extractor.js +400 -418
- package/dist/lib/store/convenience.d.ts +40 -0
- package/dist/lib/store/convenience.js +104 -0
- package/dist/lib/store/database.d.ts +22 -0
- package/dist/lib/store/database.js +375 -0
- package/dist/lib/store/drift.d.ts +9 -0
- package/dist/lib/store/drift.js +89 -0
- package/dist/lib/store/index.d.ts +7 -0
- package/dist/lib/store/index.js +13 -0
- package/dist/lib/store/sessions.d.ts +32 -0
- package/dist/lib/store/sessions.js +240 -0
- package/dist/lib/store/steps.d.ts +40 -0
- package/dist/lib/store/steps.js +161 -0
- package/dist/lib/store/tasks.d.ts +33 -0
- package/dist/lib/store/tasks.js +133 -0
- package/dist/lib/store/types.d.ts +167 -0
- package/dist/lib/store/types.js +2 -0
- package/dist/lib/store.d.ts +1 -406
- package/dist/lib/store.js +2 -1356
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +45 -0
- package/dist/proxy/action-parser.d.ts +10 -2
- package/dist/proxy/action-parser.js +4 -2
- package/dist/proxy/cache.d.ts +36 -0
- package/dist/proxy/cache.js +51 -0
- package/dist/proxy/config.d.ts +1 -0
- package/dist/proxy/config.js +2 -0
- package/dist/proxy/extended-cache.d.ts +10 -0
- package/dist/proxy/extended-cache.js +155 -0
- package/dist/proxy/forwarder.d.ts +7 -1
- package/dist/proxy/forwarder.js +157 -7
- package/dist/proxy/handlers/preprocess.d.ts +20 -0
- package/dist/proxy/handlers/preprocess.js +169 -0
- package/dist/proxy/injection/delta-tracking.d.ts +11 -0
- package/dist/proxy/injection/delta-tracking.js +93 -0
- package/dist/proxy/injection/injectors.d.ts +7 -0
- package/dist/proxy/injection/injectors.js +139 -0
- package/dist/proxy/request-processor.d.ts +18 -3
- package/dist/proxy/request-processor.js +151 -28
- package/dist/proxy/response-processor.js +116 -47
- package/dist/proxy/server.d.ts +4 -1
- package/dist/proxy/server.js +592 -253
- package/dist/proxy/types.d.ts +13 -0
- package/dist/proxy/types.js +2 -0
- package/dist/proxy/utils/extractors.d.ts +18 -0
- package/dist/proxy/utils/extractors.js +109 -0
- package/dist/proxy/utils/logging.d.ts +18 -0
- package/dist/proxy/utils/logging.js +42 -0
- package/package.json +22 -4
|
@@ -1,7 +1,81 @@
|
|
|
1
1
|
// Response processor - handles team memory save triggers and cleanup
|
|
2
2
|
// Reference: plan_proxy_local.md Section 4.6
|
|
3
|
-
import { getSessionState, getValidatedSteps, deleteStepsForSession, deleteSessionState, createTask, } from '../lib/store.js';
|
|
4
|
-
import {
|
|
3
|
+
import { getSessionState, getValidatedSteps, deleteStepsForSession, deleteSessionState, createTask, markTaskSynced, setTaskSyncError, } from '../lib/store.js';
|
|
4
|
+
import { syncTask } from '../lib/cloud-sync.js';
|
|
5
|
+
import { extractReasoningAndDecisions, isReasoningExtractionAvailable, } from '../lib/llm-extractor.js';
|
|
6
|
+
/**
|
|
7
|
+
* Group steps by reasoning (non-NULL starts a group, NULLs continue it)
|
|
8
|
+
* Steps from the same Claude response share identical reasoning, stored only on the first.
|
|
9
|
+
*/
|
|
10
|
+
function groupStepsByReasoning(steps) {
|
|
11
|
+
const groups = [];
|
|
12
|
+
let currentGroup = null;
|
|
13
|
+
for (const step of steps) {
|
|
14
|
+
if (step.reasoning && step.reasoning.length > 0) {
|
|
15
|
+
// Step with reasoning = start new group
|
|
16
|
+
if (currentGroup) {
|
|
17
|
+
groups.push(currentGroup);
|
|
18
|
+
}
|
|
19
|
+
currentGroup = {
|
|
20
|
+
reasoning: step.reasoning,
|
|
21
|
+
actions: [{
|
|
22
|
+
type: step.action_type,
|
|
23
|
+
files: step.files || [],
|
|
24
|
+
folders: step.folders || [],
|
|
25
|
+
command: step.command ?? undefined,
|
|
26
|
+
}],
|
|
27
|
+
allFiles: [...(step.files || [])],
|
|
28
|
+
allFolders: [...(step.folders || [])],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
else if (currentGroup) {
|
|
32
|
+
// Step without reasoning = continue current group
|
|
33
|
+
currentGroup.actions.push({
|
|
34
|
+
type: step.action_type,
|
|
35
|
+
files: step.files || [],
|
|
36
|
+
folders: step.folders || [],
|
|
37
|
+
command: step.command ?? undefined,
|
|
38
|
+
});
|
|
39
|
+
currentGroup.allFiles.push(...(step.files || []));
|
|
40
|
+
currentGroup.allFolders.push(...(step.folders || []));
|
|
41
|
+
}
|
|
42
|
+
// Edge case: step without reasoning and no current group = skip (shouldn't happen with new code)
|
|
43
|
+
}
|
|
44
|
+
// Push last group
|
|
45
|
+
if (currentGroup) {
|
|
46
|
+
groups.push(currentGroup);
|
|
47
|
+
}
|
|
48
|
+
return groups;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format grouped steps for Haiku prompt
|
|
52
|
+
* Provides structured XML with reasoning + associated actions and files
|
|
53
|
+
*/
|
|
54
|
+
function formatGroupsForHaiku(groups) {
|
|
55
|
+
if (groups.length === 0) {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
return groups.map((g, i) => {
|
|
59
|
+
const actionLines = g.actions.map(a => {
|
|
60
|
+
let line = `- ${a.type}`;
|
|
61
|
+
if (a.files.length > 0)
|
|
62
|
+
line += `: ${a.files.join(', ')}`;
|
|
63
|
+
if (a.command)
|
|
64
|
+
line += ` (command: ${a.command.substring(0, 50)})`;
|
|
65
|
+
return line;
|
|
66
|
+
}).join('\n');
|
|
67
|
+
const uniqueFiles = [...new Set(g.allFiles)];
|
|
68
|
+
return `<response index="${i + 1}">
|
|
69
|
+
<reasoning>
|
|
70
|
+
${g.reasoning}
|
|
71
|
+
</reasoning>
|
|
72
|
+
<actions_performed>
|
|
73
|
+
${actionLines}
|
|
74
|
+
</actions_performed>
|
|
75
|
+
<files_touched>${uniqueFiles.join(', ') || 'none'}</files_touched>
|
|
76
|
+
</response>`;
|
|
77
|
+
}).join('\n\n###\n\n');
|
|
78
|
+
}
|
|
5
79
|
/**
|
|
6
80
|
* Save session to team memory
|
|
7
81
|
* Called on: task complete, subtask complete, session abandoned
|
|
@@ -12,13 +86,35 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
|
|
|
12
86
|
return;
|
|
13
87
|
}
|
|
14
88
|
const steps = getValidatedSteps(sessionId);
|
|
15
|
-
if
|
|
89
|
+
// Allow saving if: has steps OR has final_response OR is abandoned
|
|
90
|
+
const hasFinalResponse = sessionState.final_response && sessionState.final_response.length > 100;
|
|
91
|
+
if (steps.length === 0 && !hasFinalResponse && triggerReason !== 'abandoned') {
|
|
16
92
|
return; // Nothing to save
|
|
17
93
|
}
|
|
18
94
|
// Build task data from session state and steps
|
|
19
95
|
const taskData = await buildTaskFromSession(sessionState, steps, triggerReason);
|
|
20
96
|
// Create task in team memory
|
|
21
|
-
createTask(taskData);
|
|
97
|
+
const task = createTask(taskData);
|
|
98
|
+
// Fire-and-forget cloud sync; never block capture path
|
|
99
|
+
// NOTE: Do NOT invalidate cache after sync - cache persists until CLEAR/Summary/restart
|
|
100
|
+
// Next SESSION will get fresh data, current session keeps its context
|
|
101
|
+
syncTask(task)
|
|
102
|
+
.then((success) => {
|
|
103
|
+
if (success) {
|
|
104
|
+
markTaskSynced(task.id);
|
|
105
|
+
console.log(`[SYNC] Task ${task.id.substring(0, 8)} synced to cloud`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
setTaskSyncError(task.id, 'Sync not enabled or team not configured');
|
|
109
|
+
console.log(`[SYNC] Task ${task.id.substring(0, 8)} sync skipped (not enabled)`);
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
const message = err instanceof Error ? err.message : 'Unknown sync error';
|
|
114
|
+
setTaskSyncError(task.id, message);
|
|
115
|
+
console.error(`[SYNC] Task ${task.id.substring(0, 8)} sync failed: ${message}`);
|
|
116
|
+
// NOTE: Do NOT invalidate cache - data not in cloud
|
|
117
|
+
});
|
|
22
118
|
}
|
|
23
119
|
/**
|
|
24
120
|
* Build task data from session state and steps
|
|
@@ -53,14 +149,23 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
53
149
|
let reasoningTrace = basicReasoningTrace;
|
|
54
150
|
let decisions = [];
|
|
55
151
|
let constraints = sessionState.constraints || [];
|
|
56
|
-
if (isReasoningExtractionAvailable()
|
|
152
|
+
if (isReasoningExtractionAvailable()) {
|
|
57
153
|
try {
|
|
58
|
-
//
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
const
|
|
154
|
+
// Group steps by reasoning to avoid duplicates and preserve action context
|
|
155
|
+
const groups = groupStepsByReasoning(steps);
|
|
156
|
+
let formattedSteps = formatGroupsForHaiku(groups);
|
|
157
|
+
// Add final_response as separate section if exists and is different from grouped reasoning
|
|
158
|
+
if (sessionState.final_response && sessionState.final_response.length > 100) {
|
|
159
|
+
const finalAlreadyIncluded = groups.some(g => g.reasoning.includes(sessionState.final_response.substring(0, 100)));
|
|
160
|
+
if (!finalAlreadyIncluded) {
|
|
161
|
+
if (formattedSteps.length > 0) {
|
|
162
|
+
formattedSteps += '\n\n###\n\n';
|
|
163
|
+
}
|
|
164
|
+
formattedSteps += `<final_response>\n${sessionState.final_response.substring(0, 8000)}\n</final_response>`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (formattedSteps.length > 50) {
|
|
168
|
+
const extracted = await extractReasoningAndDecisions(formattedSteps, sessionState.original_goal || '');
|
|
64
169
|
if (extracted.reasoning_trace.length > 0) {
|
|
65
170
|
reasoningTrace = extracted.reasoning_trace;
|
|
66
171
|
}
|
|
@@ -73,22 +178,6 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
73
178
|
// Fall back to basic extraction
|
|
74
179
|
}
|
|
75
180
|
}
|
|
76
|
-
else if (isLLMAvailable() && steps.length > 0) {
|
|
77
|
-
// Fallback to OpenAI-based extraction if Anthropic not available
|
|
78
|
-
try {
|
|
79
|
-
const pseudoSession = buildPseudoSession(sessionState, steps);
|
|
80
|
-
const extracted = await extractReasoning(pseudoSession);
|
|
81
|
-
if (extracted.decisions.length > 0) {
|
|
82
|
-
decisions = extracted.decisions;
|
|
83
|
-
}
|
|
84
|
-
if (extracted.constraints.length > 0) {
|
|
85
|
-
constraints = [...new Set([...constraints, ...extracted.constraints])];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
// Fall back to basic extraction
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
181
|
return {
|
|
93
182
|
project_path: sessionState.project_path,
|
|
94
183
|
user: sessionState.user_id,
|
|
@@ -102,26 +191,6 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
102
191
|
trigger_reason: triggerReason,
|
|
103
192
|
};
|
|
104
193
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Build pseudo ParsedSession for LLM extraction
|
|
107
|
-
*/
|
|
108
|
-
function buildPseudoSession(sessionState, steps) {
|
|
109
|
-
return {
|
|
110
|
-
sessionId: sessionState.session_id,
|
|
111
|
-
projectPath: sessionState.project_path,
|
|
112
|
-
startTime: sessionState.start_time,
|
|
113
|
-
endTime: sessionState.last_update,
|
|
114
|
-
userMessages: [sessionState.original_goal || ''],
|
|
115
|
-
assistantMessages: steps.map(s => `[${s.action_type}] ${s.files.join(', ')}`),
|
|
116
|
-
toolCalls: steps.map(s => ({
|
|
117
|
-
name: s.action_type,
|
|
118
|
-
input: { files: s.files, command: s.command },
|
|
119
|
-
})),
|
|
120
|
-
filesRead: steps.filter(s => s.action_type === 'read').flatMap(s => s.files),
|
|
121
|
-
filesWritten: steps.filter(s => s.action_type === 'write' || s.action_type === 'edit').flatMap(s => s.files),
|
|
122
|
-
rawEntries: [],
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
194
|
/**
|
|
126
195
|
* Clean up session data after save
|
|
127
196
|
*/
|
package/dist/proxy/server.d.ts
CHANGED
|
@@ -5,5 +5,8 @@ import { FastifyInstance } from 'fastify';
|
|
|
5
5
|
export declare function createServer(): FastifyInstance;
|
|
6
6
|
/**
|
|
7
7
|
* Start the proxy server
|
|
8
|
+
* @param options.debug - Enable debug logging to grov-proxy.log
|
|
8
9
|
*/
|
|
9
|
-
export declare function startServer(
|
|
10
|
+
export declare function startServer(options?: {
|
|
11
|
+
debug?: boolean;
|
|
12
|
+
}): Promise<FastifyInstance>;
|