-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrecord-scar-usage-batch.ts
More file actions
216 lines (191 loc) · 6.72 KB
/
Copy pathrecord-scar-usage-batch.ts
File metadata and controls
216 lines (191 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/**
* Batch Scar Usage Recording Tool
* Records multiple scar usages in a single call for improved session close performance
*/
import { v4 as uuidv4 } from "uuid";
import * as supabase from "../services/supabase-client.js";
import { hasSupabase, getTableName } from "../services/tier.js";
import { detectAgent } from "../services/agent-detection.js";
import { getCurrentSession } from "../services/session-state.js";
import { Timer, recordMetrics, buildPerformanceData } from "../services/metrics.js";
import type {
RecordScarUsageBatchParams,
RecordScarUsageBatchResult,
ScarUsageEntry,
Project,
} from "../types/index.js";
const TARGET_LATENCY_MS = 2000; // Target for batch operation
interface ScarRecord {
id: string;
title: string;
description: string;
scar_type: string;
severity: string;
}
/**
* Resolves a scar identifier (UUID or title/description) to a UUID
* @param identifier - UUID or title/description to resolve
* @param project - Project filter
* @returns UUID or null if not found
*/
async function resolveScarIdentifier(
identifier: string,
project?: Project
): Promise<string | null> {
// If it looks like a UUID, return as-is
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (uuidRegex.test(identifier)) {
return identifier;
}
// Otherwise, query by title or description
try {
const filters: Record<string, unknown> = {};
if (project) {
filters.project = project;
}
// Try exact title match first
const titleResult = await supabase.listRecords<ScarRecord>({
table: getTableName("learnings"),
columns: "id,title,description,scar_type,severity",
filters: { ...filters, title: identifier },
limit: 1,
});
if (titleResult && titleResult.length > 0) {
return titleResult[0].id;
}
// Try partial title match (get more records to search)
const partialResult = await supabase.listRecords<ScarRecord>({
table: getTableName("learnings"),
columns: "id,title,description,scar_type,severity",
filters: { ...filters },
limit: 100,
});
if (partialResult) {
// Find by case-insensitive contains
const match = partialResult.find(
(record: ScarRecord) =>
record.title.toLowerCase().includes(identifier.toLowerCase()) ||
record.description?.toLowerCase().includes(identifier.toLowerCase())
);
if (match) {
return match.id;
}
}
return null;
} catch (error) {
console.error(`[record-scar-usage-batch] Error resolving identifier "${identifier}":`, error);
return null;
}
}
/**
* Records multiple scar usages in a single batch operation
*/
export async function recordScarUsageBatch(
params: RecordScarUsageBatchParams
): Promise<RecordScarUsageBatchResult> {
const timer = new Timer();
const metricsId = uuidv4();
if (!hasSupabase()) {
// Free tier: scar usage tracked locally via record-scar-usage (single), not batch
return {
success: true,
usage_ids: [],
resolved_count: 0,
failed_count: 0,
failed_identifiers: [],
performance: buildPerformanceData("record_scar_usage_batch", timer.stop(), 0),
};
}
const usageIds: string[] = [];
const failedIdentifiers: string[] = [];
let resolvedCount = 0;
try {
// Resolve all scar identifiers to UUIDs in parallel
const resolutionPromises = params.scars.map(async (entry) => {
const scarId = await resolveScarIdentifier(entry.scar_identifier, params.project);
return { entry, scarId };
});
const resolvedScars = await Promise.all(resolutionPromises);
// Auto-detect agent and session as fallbacks for entries missing them
const fallbackAgent = detectAgent().agent || null;
const fallbackSessionId = getCurrentSession()?.sessionId || null;
// Build usage records for all successfully resolved scars
const usageRecords = resolvedScars
.filter(({ scarId }) => scarId !== null)
.map(({ entry, scarId }) => {
const usageId = uuidv4();
return {
id: usageId,
scar_id: scarId,
issue_id: entry.issue_id || null,
issue_identifier: entry.issue_identifier || null,
session_id: entry.session_id || fallbackSessionId,
agent: entry.agent || fallbackAgent,
surfaced_at: entry.surfaced_at,
acknowledged_at: entry.acknowledged_at || null,
referenced: entry.reference_type !== "none",
reference_type: entry.reference_type,
reference_context: entry.reference_context,
execution_successful: entry.execution_successful ?? null,
variant_id: entry.variant_id || null,
created_at: new Date().toISOString(),
};
});
// Track failed resolutions
resolvedScars.forEach(({ entry, scarId }) => {
if (scarId === null) {
failedIdentifiers.push(entry.scar_identifier);
} else {
resolvedCount++;
}
});
// Insert all usage records in parallel
const insertPromises = usageRecords.map(async (record) => {
const result = await supabase.directUpsert<{ id: string }>(
"scar_usage",
record
);
return result?.id || null;
});
const insertResults = await Promise.all(insertPromises);
usageIds.push(...insertResults.filter((id): id is string => id !== null));
const latencyMs = timer.stop();
const perfData = buildPerformanceData("record_scar_usage_batch", latencyMs, usageIds.length);
// Record metrics asynchronously
recordMetrics({
id: metricsId,
tool_name: "record_scar_usage_batch",
tables_searched: ["scar_usage", getTableName("learnings")],
latency_ms: latencyMs,
result_count: usageIds.length,
phase_tag: "scar_tracking",
metadata: {
total_scars: params.scars.length,
resolved_count: resolvedCount,
failed_count: failedIdentifiers.length,
},
}).catch(() => {
// Swallow errors - metrics are non-critical
});
return {
success: true,
usage_ids: usageIds,
resolved_count: resolvedCount,
failed_count: failedIdentifiers.length,
failed_identifiers: failedIdentifiers.length > 0 ? failedIdentifiers : undefined,
performance: perfData,
};
} catch (error) {
const latencyMs = timer.stop();
const perfData = buildPerformanceData("record_scar_usage_batch", latencyMs, 0);
console.error("[record-scar-usage-batch] Error recording batch:", error);
return {
success: false,
usage_ids: usageIds,
resolved_count: resolvedCount,
failed_count: params.scars.length - resolvedCount,
failed_identifiers: failedIdentifiers.length > 0 ? failedIdentifiers : undefined,
performance: perfData,
};
}
}