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.
Files changed (64) hide show
  1. package/README.md +44 -5
  2. package/dist/cli.js +40 -2
  3. package/dist/commands/login.d.ts +1 -0
  4. package/dist/commands/login.js +115 -0
  5. package/dist/commands/logout.d.ts +1 -0
  6. package/dist/commands/logout.js +13 -0
  7. package/dist/commands/sync.d.ts +8 -0
  8. package/dist/commands/sync.js +127 -0
  9. package/dist/lib/api-client.d.ts +57 -0
  10. package/dist/lib/api-client.js +174 -0
  11. package/dist/lib/cloud-sync.d.ts +33 -0
  12. package/dist/lib/cloud-sync.js +176 -0
  13. package/dist/lib/credentials.d.ts +53 -0
  14. package/dist/lib/credentials.js +201 -0
  15. package/dist/lib/llm-extractor.d.ts +15 -39
  16. package/dist/lib/llm-extractor.js +400 -418
  17. package/dist/lib/store/convenience.d.ts +40 -0
  18. package/dist/lib/store/convenience.js +104 -0
  19. package/dist/lib/store/database.d.ts +22 -0
  20. package/dist/lib/store/database.js +375 -0
  21. package/dist/lib/store/drift.d.ts +9 -0
  22. package/dist/lib/store/drift.js +89 -0
  23. package/dist/lib/store/index.d.ts +7 -0
  24. package/dist/lib/store/index.js +13 -0
  25. package/dist/lib/store/sessions.d.ts +32 -0
  26. package/dist/lib/store/sessions.js +240 -0
  27. package/dist/lib/store/steps.d.ts +40 -0
  28. package/dist/lib/store/steps.js +161 -0
  29. package/dist/lib/store/tasks.d.ts +33 -0
  30. package/dist/lib/store/tasks.js +133 -0
  31. package/dist/lib/store/types.d.ts +167 -0
  32. package/dist/lib/store/types.js +2 -0
  33. package/dist/lib/store.d.ts +1 -406
  34. package/dist/lib/store.js +2 -1356
  35. package/dist/lib/utils.d.ts +5 -0
  36. package/dist/lib/utils.js +45 -0
  37. package/dist/proxy/action-parser.d.ts +10 -2
  38. package/dist/proxy/action-parser.js +4 -2
  39. package/dist/proxy/cache.d.ts +36 -0
  40. package/dist/proxy/cache.js +51 -0
  41. package/dist/proxy/config.d.ts +1 -0
  42. package/dist/proxy/config.js +2 -0
  43. package/dist/proxy/extended-cache.d.ts +10 -0
  44. package/dist/proxy/extended-cache.js +155 -0
  45. package/dist/proxy/forwarder.d.ts +7 -1
  46. package/dist/proxy/forwarder.js +157 -7
  47. package/dist/proxy/handlers/preprocess.d.ts +20 -0
  48. package/dist/proxy/handlers/preprocess.js +169 -0
  49. package/dist/proxy/injection/delta-tracking.d.ts +11 -0
  50. package/dist/proxy/injection/delta-tracking.js +93 -0
  51. package/dist/proxy/injection/injectors.d.ts +7 -0
  52. package/dist/proxy/injection/injectors.js +139 -0
  53. package/dist/proxy/request-processor.d.ts +18 -3
  54. package/dist/proxy/request-processor.js +151 -28
  55. package/dist/proxy/response-processor.js +116 -47
  56. package/dist/proxy/server.d.ts +4 -1
  57. package/dist/proxy/server.js +592 -253
  58. package/dist/proxy/types.d.ts +13 -0
  59. package/dist/proxy/types.js +2 -0
  60. package/dist/proxy/utils/extractors.d.ts +18 -0
  61. package/dist/proxy/utils/extractors.js +109 -0
  62. package/dist/proxy/utils/logging.d.ts +18 -0
  63. package/dist/proxy/utils/logging.js +42 -0
  64. 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 { extractReasoning, isLLMAvailable, extractReasoningAndDecisions, isReasoningExtractionAvailable, } from '../lib/llm-extractor.js';
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 (steps.length === 0 && triggerReason !== 'abandoned') {
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() && steps.length > 0) {
152
+ if (isReasoningExtractionAvailable()) {
57
153
  try {
58
- // Collect reasoning from steps
59
- const stepsReasoning = steps
60
- .map(s => s.reasoning)
61
- .filter((r) => !!r && r.length > 10);
62
- if (stepsReasoning.length > 0) {
63
- const extracted = await extractReasoningAndDecisions(stepsReasoning, sessionState.original_goal || '');
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
  */
@@ -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(): Promise<FastifyInstance>;
10
+ export declare function startServer(options?: {
11
+ debug?: boolean;
12
+ }): Promise<FastifyInstance>;