grov 0.2.2 → 0.5.2
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 +33 -6
- package/dist/cli.js +32 -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 +40 -0
- package/dist/lib/api-client.js +117 -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 +1 -1
- package/dist/lib/llm-extractor.js +53 -33
- package/dist/lib/store.d.ts +32 -2
- package/dist/lib/store.js +133 -11
- 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/forwarder.d.ts +7 -1
- package/dist/proxy/forwarder.js +157 -7
- package/dist/proxy/request-processor.d.ts +5 -4
- package/dist/proxy/request-processor.js +39 -11
- package/dist/proxy/response-processor.js +38 -7
- package/dist/proxy/server.d.ts +5 -1
- package/dist/proxy/server.js +693 -137
- package/package.json +18 -3
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { getTasksForProject, getTasksByFiles, getStepsReasoningByPath, } from '../lib/store.js';
|
|
4
4
|
import { truncate } from '../lib/utils.js';
|
|
5
5
|
/**
|
|
6
|
-
* Build context from team memory for injection
|
|
7
|
-
* Queries tasks and file_reasoning tables
|
|
6
|
+
* Build context from team memory for injection (PAST sessions only)
|
|
7
|
+
* Queries tasks and file_reasoning tables, excluding current session data
|
|
8
|
+
* @param currentSessionId - Session ID to exclude (ensures only past session data)
|
|
8
9
|
*/
|
|
9
|
-
export function buildTeamMemoryContext(projectPath, mentionedFiles) {
|
|
10
|
+
export function buildTeamMemoryContext(projectPath, mentionedFiles, currentSessionId) {
|
|
10
11
|
// Get recent completed tasks for this project
|
|
11
12
|
const tasks = getTasksForProject(projectPath, {
|
|
12
13
|
status: 'complete',
|
|
@@ -16,9 +17,10 @@ export function buildTeamMemoryContext(projectPath, mentionedFiles) {
|
|
|
16
17
|
const fileTasks = mentionedFiles.length > 0
|
|
17
18
|
? getTasksByFiles(projectPath, mentionedFiles, { status: 'complete', limit: 5 })
|
|
18
19
|
: [];
|
|
19
|
-
// Get file-level reasoning from steps table (
|
|
20
|
+
// Get file-level reasoning from steps table (PAST sessions only)
|
|
21
|
+
// Pass currentSessionId to exclude current session data
|
|
20
22
|
const fileReasonings = mentionedFiles.length > 0
|
|
21
|
-
? mentionedFiles.flatMap(f => getStepsReasoningByPath(f, 3))
|
|
23
|
+
? mentionedFiles.flatMap(f => getStepsReasoningByPath(f, 3, currentSessionId))
|
|
22
24
|
: [];
|
|
23
25
|
// Combine unique tasks
|
|
24
26
|
const allTasks = [...new Map([...tasks, ...fileTasks].map(t => [t.id, t])).values()];
|
|
@@ -32,7 +34,10 @@ export function buildTeamMemoryContext(projectPath, mentionedFiles) {
|
|
|
32
34
|
*/
|
|
33
35
|
function formatTeamMemoryContext(tasks, fileReasonings, files) {
|
|
34
36
|
const lines = [];
|
|
35
|
-
lines.push('
|
|
37
|
+
lines.push('=== VERIFIED TEAM KNOWLEDGE (from previous sessions) ===');
|
|
38
|
+
lines.push('');
|
|
39
|
+
lines.push('IMPORTANT: This context has been verified. USE IT to answer directly.');
|
|
40
|
+
lines.push('DO NOT launch Explore agents or re-investigate files mentioned below.');
|
|
36
41
|
lines.push('');
|
|
37
42
|
// File-level context
|
|
38
43
|
if (fileReasonings.length > 0) {
|
|
@@ -67,21 +72,44 @@ function formatTeamMemoryContext(tasks, fileReasonings, files) {
|
|
|
67
72
|
lines.push('');
|
|
68
73
|
}
|
|
69
74
|
if (files.length > 0) {
|
|
70
|
-
lines.push(`
|
|
75
|
+
lines.push(`Files with existing context: ${files.join(', ')}`);
|
|
71
76
|
}
|
|
72
|
-
lines.push('
|
|
77
|
+
lines.push('');
|
|
78
|
+
lines.push('Answer the user\'s question using the knowledge above. Skip exploration.');
|
|
79
|
+
lines.push('=== END VERIFIED TEAM KNOWLEDGE ===');
|
|
73
80
|
return lines.join('\n');
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
76
|
-
* Extract file paths from messages
|
|
83
|
+
* Extract file paths from messages (user messages only, clean text)
|
|
77
84
|
*/
|
|
78
85
|
export function extractFilesFromMessages(messages) {
|
|
79
86
|
const files = [];
|
|
80
|
-
|
|
87
|
+
// Pattern matches filenames with extensions, allowing common punctuation after
|
|
88
|
+
const filePattern = /(?:^|\s|["'`])([\/\w.-]+\.[a-zA-Z]{1,10})(?:["'`]|\s|$|[:)\]?!,;])/g;
|
|
81
89
|
for (const msg of messages) {
|
|
90
|
+
// Only scan user messages for file mentions
|
|
91
|
+
if (msg.role !== 'user')
|
|
92
|
+
continue;
|
|
93
|
+
let textContent = '';
|
|
94
|
+
// Handle string content
|
|
82
95
|
if (typeof msg.content === 'string') {
|
|
96
|
+
textContent = msg.content;
|
|
97
|
+
}
|
|
98
|
+
// Handle array content (Claude Code API format)
|
|
99
|
+
if (Array.isArray(msg.content)) {
|
|
100
|
+
for (const block of msg.content) {
|
|
101
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'text' && 'text' in block && typeof block.text === 'string') {
|
|
102
|
+
textContent += block.text + '\n';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Strip system-reminder tags to get clean user content
|
|
107
|
+
const cleanContent = textContent
|
|
108
|
+
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
|
|
109
|
+
.trim();
|
|
110
|
+
if (cleanContent) {
|
|
83
111
|
let match;
|
|
84
|
-
while ((match = filePattern.exec(
|
|
112
|
+
while ((match = filePattern.exec(cleanContent)) !== null) {
|
|
85
113
|
const path = match[1];
|
|
86
114
|
// Filter out common false positives
|
|
87
115
|
if (!path.includes('http') && !path.startsWith('.') && path.length > 3) {
|
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
3
|
+
import { getSessionState, getValidatedSteps, deleteStepsForSession, deleteSessionState, createTask, markTaskSynced, setTaskSyncError, } from '../lib/store.js';
|
|
4
|
+
import { syncTask } from '../lib/cloud-sync.js';
|
|
4
5
|
import { extractReasoning, isLLMAvailable, extractReasoningAndDecisions, isReasoningExtractionAvailable, } from '../lib/llm-extractor.js';
|
|
5
6
|
/**
|
|
6
7
|
* Save session to team memory
|
|
@@ -12,20 +13,46 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
const steps = getValidatedSteps(sessionId);
|
|
15
|
-
if
|
|
16
|
+
// Allow saving if: has steps OR has final_response OR is abandoned
|
|
17
|
+
const hasFinalResponse = sessionState.final_response && sessionState.final_response.length > 100;
|
|
18
|
+
if (steps.length === 0 && !hasFinalResponse && triggerReason !== 'abandoned') {
|
|
16
19
|
return; // Nothing to save
|
|
17
20
|
}
|
|
18
21
|
// Build task data from session state and steps
|
|
19
22
|
const taskData = await buildTaskFromSession(sessionState, steps, triggerReason);
|
|
20
23
|
// Create task in team memory
|
|
21
|
-
createTask(taskData);
|
|
24
|
+
const task = createTask(taskData);
|
|
25
|
+
// Fire-and-forget cloud sync; never block capture path
|
|
26
|
+
syncTask(task)
|
|
27
|
+
.then((success) => {
|
|
28
|
+
if (success) {
|
|
29
|
+
markTaskSynced(task.id);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
setTaskSyncError(task.id, 'Sync not enabled or team not configured');
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.catch((err) => {
|
|
36
|
+
const message = err instanceof Error ? err.message : 'Unknown sync error';
|
|
37
|
+
setTaskSyncError(task.id, message);
|
|
38
|
+
});
|
|
22
39
|
}
|
|
23
40
|
/**
|
|
24
41
|
* Build task data from session state and steps
|
|
25
42
|
*/
|
|
26
43
|
async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
27
|
-
// Aggregate files from steps
|
|
28
|
-
const
|
|
44
|
+
// Aggregate files from steps (tool_use actions)
|
|
45
|
+
const stepFiles = steps.flatMap(s => s.files);
|
|
46
|
+
// Also extract file paths mentioned in reasoning text (Claude's text responses)
|
|
47
|
+
const reasoningFiles = steps
|
|
48
|
+
.filter(s => s.reasoning)
|
|
49
|
+
.flatMap(s => {
|
|
50
|
+
// Match common code file extensions
|
|
51
|
+
const matches = s.reasoning?.match(/[\w\/.-]+\.(ts|js|tsx|jsx|py|go|rs|java|css|html|md|json|yaml|yml)/g) || [];
|
|
52
|
+
// Filter out obvious non-paths (urls, version numbers)
|
|
53
|
+
return matches.filter(m => !m.includes('://') && !m.match(/^\d+\.\d+/));
|
|
54
|
+
});
|
|
55
|
+
const filesTouched = [...new Set([...stepFiles, ...reasoningFiles])];
|
|
29
56
|
// Build basic reasoning trace from steps (fallback)
|
|
30
57
|
const basicReasoningTrace = steps
|
|
31
58
|
.filter(s => s.is_key_decision || s.action_type === 'edit' || s.action_type === 'write')
|
|
@@ -43,12 +70,16 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
|
|
|
43
70
|
let reasoningTrace = basicReasoningTrace;
|
|
44
71
|
let decisions = [];
|
|
45
72
|
let constraints = sessionState.constraints || [];
|
|
46
|
-
if (isReasoningExtractionAvailable()
|
|
73
|
+
if (isReasoningExtractionAvailable()) {
|
|
47
74
|
try {
|
|
48
|
-
// Collect reasoning from steps
|
|
75
|
+
// Collect reasoning from steps + final response
|
|
49
76
|
const stepsReasoning = steps
|
|
50
77
|
.map(s => s.reasoning)
|
|
51
78
|
.filter((r) => !!r && r.length > 10);
|
|
79
|
+
// Include final response (contains the actual analysis/conclusion)
|
|
80
|
+
if (sessionState.final_response && sessionState.final_response.length > 100) {
|
|
81
|
+
stepsReasoning.push(sessionState.final_response);
|
|
82
|
+
}
|
|
52
83
|
if (stepsReasoning.length > 0) {
|
|
53
84
|
const extracted = await extractReasoningAndDecisions(stepsReasoning, sessionState.original_goal || '');
|
|
54
85
|
if (extracted.reasoning_trace.length > 0) {
|
package/dist/proxy/server.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify';
|
|
2
|
+
export declare function setDebugMode(enabled: boolean): void;
|
|
2
3
|
/**
|
|
3
4
|
* Create and configure the Fastify server
|
|
4
5
|
*/
|
|
5
6
|
export declare function createServer(): FastifyInstance;
|
|
6
7
|
/**
|
|
7
8
|
* Start the proxy server
|
|
9
|
+
* @param options.debug - Enable debug logging to grov-proxy.log
|
|
8
10
|
*/
|
|
9
|
-
export declare function startServer(
|
|
11
|
+
export declare function startServer(options?: {
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}): Promise<FastifyInstance>;
|