grov 0.6.17 → 0.6.18

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.
@@ -1,4 +1,4 @@
1
- import type { Team, Memory, MemorySyncRequest, MemorySyncResponse, DeviceFlowStartResponse, DeviceFlowPollResponse, ReasoningTraceEntry } from '@grov/shared';
1
+ import type { Team, Memory, MemorySyncRequest, MemorySyncResponse, DeviceFlowStartResponse, DeviceFlowPollResponse, ReasoningTraceEntry, RecordInjectionRequest, RecordInjectionResponse } from '@grov/shared';
2
2
  export interface ApiResponse<T> {
3
3
  data?: T;
4
4
  error?: string;
@@ -88,6 +88,7 @@ export interface MatchResponse {
88
88
  * @returns Match response with memory and score
89
89
  */
90
90
  export declare function fetchMatch(teamId: string, data: MatchInput): Promise<MatchResponse>;
91
+ export declare function reportInjection(event: RecordInjectionRequest): Promise<RecordInjectionResponse | null>;
91
92
  /**
92
93
  * Sleep helper for polling
93
94
  */
@@ -148,6 +148,12 @@ export async function fetchTeamMemories(teamId, projectPath, options) {
148
148
  if (!response.data || !response.data.memories) {
149
149
  return [];
150
150
  }
151
+ // Check if blocked due to quota
152
+ if (response.data.blocked) {
153
+ console.log('\n[grov] \x1b[33m⚠️ Free quota exceeded (110%). Upgrade to continue using memory injection.\x1b[0m');
154
+ console.log('[grov] Manage plan: https://app.grov.dev/settings\n');
155
+ return [];
156
+ }
151
157
  return response.data.memories;
152
158
  }
153
159
  catch (err) {
@@ -186,6 +192,16 @@ export async function fetchMatch(teamId, data) {
186
192
  return { match: null };
187
193
  }
188
194
  }
195
+ // ============= Usage Tracking =============
196
+ export async function reportInjection(event) {
197
+ try {
198
+ const response = await apiRequest('POST', '/usage/injection', event);
199
+ return response.data || null;
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ }
189
205
  // ============= Utility Functions =============
190
206
  /**
191
207
  * Sleep helper for polling
@@ -4,7 +4,10 @@ import { extractFilesFromMessages } from '../request-processor.js';
4
4
  import { fetchTeamMemories } from '../../../core/cloud/api-client.js';
5
5
  import { getSessionState, updateSessionState, markCleared, } from '../../../core/store/store.js';
6
6
  import { isSyncEnabled, getSyncTeamId } from '../../../core/cloud/cloud-sync.js';
7
+ import { getCurrentUser } from '../../../core/cloud/credentials.js';
8
+ import { reportInjection } from '../../../core/cloud/api-client.js';
7
9
  import { clearSessionState, cacheMemories, buildMemoryPreview, buildToolDescription, buildDriftRecoveryInjection, reconstructMessages, addInjectionRecord, commitPendingRecords, setCachedPreview, getCachedPreview, } from '../injection/memory-injection.js';
10
+ import { handleInjectionResponse } from '../utils/usage-warnings.js';
8
11
  let pendingPlanClear = null;
9
12
  export function getPendingPlanClear() {
10
13
  return pendingPlanClear;
@@ -141,6 +144,20 @@ export async function preProcessRequest(adapter, body, sessionInfo, logger, dete
141
144
  previewSize: preview?.length || 0,
142
145
  hasDriftRecovery: !!driftRecovery,
143
146
  });
147
+ const user = getCurrentUser();
148
+ if (user && teamId) {
149
+ reportInjection({
150
+ team_id: teamId,
151
+ user_id: user.id,
152
+ session_id: sessionInfo.sessionId,
153
+ event_id: `${sessionInfo.sessionId}:${Date.now()}:preview`,
154
+ injection_type: 'preview',
155
+ memory_ids: memories.map(m => m.id),
156
+ timestamp: new Date().toISOString(),
157
+ })
158
+ .then(res => handleInjectionResponse(res, teamId))
159
+ .catch(() => { });
160
+ }
144
161
  }
145
162
  else {
146
163
  // No memories found - inject explicit "no entries" so Claude doesn't use old previews
@@ -2,6 +2,9 @@
2
2
  // Handles session management, task orchestration, drift detection, and memory injection
3
3
  import { randomUUID } from 'crypto';
4
4
  import { config, buildSafeHeaders } from './config.js';
5
+ import { getSyncTeamId } from '../../core/cloud/cloud-sync.js';
6
+ import { getCurrentUser } from '../../core/cloud/credentials.js';
7
+ import { reportInjection } from '../../core/cloud/api-client.js';
5
8
  import { extendedCache, evictOldestCacheEntry } from './cache/extended-cache.js';
6
9
  import { getNextRequestId, taskLog, proxyLog, logTokenUsage } from './utils/logging.js';
7
10
  import { preProcessRequest, setPendingPlanClear } from './handlers/preprocess.js';
@@ -11,6 +14,7 @@ import { buildCorrection, formatCorrectionForInjection } from '../../core/extrac
11
14
  import { generateSessionSummary, analyzeTaskContext, } from '../../core/extraction/llm-extractor.js';
12
15
  import { saveToTeamMemory } from './response-processor.js';
13
16
  import { getCachedMemoryById, buildExpandedMemory, addInjectionRecord, hasToolCycleAtPosition, } from './injection/memory-injection.js';
17
+ import { handleInjectionResponse } from './utils/usage-warnings.js';
14
18
  // In-memory state
15
19
  const lastDriftResults = new Map();
16
20
  const lastMessageCount = new Map();
@@ -180,6 +184,24 @@ export async function handleAgentRequest(context) {
180
184
  if (ids.length > 0) {
181
185
  const expandedCount = expandedParts.filter(p => !p.includes('not found')).length;
182
186
  console.log(`[MEMORY] Expanded ${expandedCount}/${ids.length} memories`);
187
+ const teamId = getSyncTeamId();
188
+ const user = getCurrentUser();
189
+ if (teamId && user && expandedCount > 0) {
190
+ const expandedIds = ids.filter((_, i) => !expandedParts[i].includes('not found'));
191
+ for (const memoryId of expandedIds) {
192
+ reportInjection({
193
+ team_id: teamId,
194
+ user_id: user.id,
195
+ session_id: sessionInfo.sessionId,
196
+ event_id: `${sessionInfo.sessionId}:${Date.now()}:expand:${memoryId}`,
197
+ injection_type: 'expand',
198
+ memory_ids: [memoryId],
199
+ timestamp: new Date().toISOString(),
200
+ })
201
+ .then(res => handleInjectionResponse(res, teamId))
202
+ .catch(() => { });
203
+ }
204
+ }
183
205
  }
184
206
  grovExpandResult = expandedParts.join('\n\n');
185
207
  toolResultBlocks.push({
@@ -0,0 +1,2 @@
1
+ import type { RecordInjectionResponse } from '@grov/shared';
2
+ export declare function handleInjectionResponse(response: RecordInjectionResponse | null, teamId: string): void;
@@ -0,0 +1,40 @@
1
+ // Usage warning display for CLI
2
+ // Shows warnings when approaching or exceeding monthly quota
3
+ const SEVERITY = {
4
+ normal: 0,
5
+ warning_80: 1,
6
+ warning_100: 2,
7
+ overage: 3,
8
+ };
9
+ // Cache: teamId -> last warned status (prevents duplicate warnings)
10
+ const warnedStatus = new Map();
11
+ export function handleInjectionResponse(response, teamId) {
12
+ if (!response)
13
+ return;
14
+ // Period reset - clear cache
15
+ if (response.status === 'normal') {
16
+ warnedStatus.delete(teamId);
17
+ return;
18
+ }
19
+ const lastSeverity = SEVERITY[warnedStatus.get(teamId) ?? 'normal'];
20
+ const currentSeverity = SEVERITY[response.status];
21
+ // Only warn on escalation
22
+ if (currentSeverity <= lastSeverity)
23
+ return;
24
+ warnedStatus.set(teamId, response.status);
25
+ const percent = Math.round((response.current_count / response.quota) * 100);
26
+ const usage = `${response.current_count}/${response.quota}`;
27
+ switch (response.status) {
28
+ case 'warning_80':
29
+ console.log(`[grov] ⚠️ Usage at ${percent}% of monthly quota (${usage} injections)`);
30
+ break;
31
+ case 'warning_100':
32
+ console.log(`[grov] ⚠️ Monthly quota reached (${usage} injections)`);
33
+ console.log(`[grov] Overage charges apply after 110% usage`);
34
+ break;
35
+ case 'overage':
36
+ console.log(`[grov] ⚠️ Overage billing now active (${usage} injections)`);
37
+ console.log(`[grov] Manage usage: https://grov.dev/settings`);
38
+ break;
39
+ }
40
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "grov",
3
- "version": "0.6.17",
3
+ "version": "0.6.18",
4
+
4
5
  "description": "Collective AI memory for Coding Agents - captures reasoning from sessions and injects context into future sessions",
5
6
  "type": "module",
6
7
  "main": "dist/cli/index.js",