codesesh 0.16.0 → 0.17.0
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/dist/{chunk-MWSJTNOW.js → chunk-7APNDHQ6.js} +1545 -1021
- package/dist/chunk-7APNDHQ6.js.map +1 -0
- package/dist/{chunk-EWC6IODK.js → chunk-A4U2SMJJ.js} +5 -2
- package/dist/{chunk-EWC6IODK.js.map → chunk-A4U2SMJJ.js.map} +1 -1
- package/dist/dist-KEPJFHOC.js +115 -0
- package/dist/index.js +967 -713
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +70 -64
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +2 -7
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +2 -2
- package/dist/web/assets/index-B076CRkD.js +109 -0
- package/dist/web/assets/index-CWNbVhet.css +2 -0
- package/dist/web/assets/react-D9s532MZ.js +1821 -0
- package/dist/web/assets/{vendor-BVE7Fps8.js → vendor-i9U7_fNF.js} +15 -15
- package/dist/web/index.html +4 -4
- package/package.json +5 -10
- package/dist/chunk-MWSJTNOW.js.map +0 -1
- package/dist/dist-AIDCOQZO.js +0 -237
- package/dist/web/assets/index-BpmbwB2_.js +0 -113
- package/dist/web/assets/index-Bxe686Vr.css +0 -2
- package/dist/web/assets/react-3acXkZNw.js +0 -1821
- package/dist/web/icon/agent/antigravity.svg +0 -1
- package/dist/web/icon/agent/kilocode.svg +0 -1
- /package/dist/{dist-AIDCOQZO.js.map → dist-KEPJFHOC.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,29 +2,27 @@
|
|
|
2
2
|
import {
|
|
3
3
|
appLogger,
|
|
4
4
|
logSearchIndexSync
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-A4U2SMJJ.js";
|
|
6
6
|
import {
|
|
7
7
|
FileSystemSessionSource,
|
|
8
|
+
SessionAliasValidationError,
|
|
8
9
|
StateStorageUnavailableError,
|
|
9
10
|
attachMissingProjectIdentities,
|
|
11
|
+
attachProjectMetrics,
|
|
10
12
|
buildAgentCacheMeta,
|
|
11
13
|
buildDashboard,
|
|
12
|
-
classifySessionTags,
|
|
13
|
-
computeIdentity,
|
|
14
14
|
computeSessionDiff,
|
|
15
15
|
createProjectScopeMatcher,
|
|
16
16
|
createRegisteredAgents,
|
|
17
17
|
deleteBookmark,
|
|
18
18
|
deleteSessionAlias,
|
|
19
19
|
executeSessionSearch,
|
|
20
|
-
|
|
20
|
+
filterSessionSearchCandidates,
|
|
21
|
+
formatSessionReference,
|
|
21
22
|
getAgentInfoMap,
|
|
22
23
|
getAgentLastFullSyncAt,
|
|
23
|
-
getCursorDataPath,
|
|
24
24
|
getSessionActivityTime,
|
|
25
|
-
|
|
26
|
-
getSmartTagSourceTimestamp,
|
|
27
|
-
getTotalTokens,
|
|
25
|
+
getSessionAgentKey,
|
|
28
26
|
importBookmarks,
|
|
29
27
|
isAgentCacheInitialized,
|
|
30
28
|
isProjectIdentityKind,
|
|
@@ -32,25 +30,23 @@ import {
|
|
|
32
30
|
listCachedProjectGroups,
|
|
33
31
|
listFileActivity,
|
|
34
32
|
listSessionAliases,
|
|
35
|
-
listSessionFileActivity,
|
|
36
|
-
loadCachedSessionDataEntry,
|
|
37
33
|
loadCachedSessions,
|
|
38
34
|
markAgentFullSyncCompleted,
|
|
39
35
|
matchesProjectIdentity,
|
|
40
36
|
matchesProjectScope,
|
|
41
|
-
|
|
37
|
+
materializeSessionDetailResponse,
|
|
42
38
|
mergeSearchQueryOptions,
|
|
39
|
+
mergeSortedSessions,
|
|
40
|
+
normalizeSessionReference,
|
|
43
41
|
perf,
|
|
44
|
-
realFs,
|
|
45
42
|
refreshPricingCache,
|
|
46
|
-
resolveProviderRoots,
|
|
47
43
|
scanSessions,
|
|
48
44
|
sessionSignature,
|
|
49
45
|
sortSessions,
|
|
50
46
|
startOfLocalDay,
|
|
51
47
|
upsertBookmark,
|
|
52
48
|
upsertSessionAlias
|
|
53
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-7APNDHQ6.js";
|
|
54
50
|
|
|
55
51
|
// src/index.ts
|
|
56
52
|
import { defineCommand, runMain } from "citty";
|
|
@@ -131,26 +127,98 @@ function elapsedDays(from, to) {
|
|
|
131
127
|
return Math.max(1, Math.ceil((to - from) / DAY_MS));
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
// src/api/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
// src/api/query-params.ts
|
|
131
|
+
var SMART_TAGS = [
|
|
132
|
+
"bugfix",
|
|
133
|
+
"refactoring",
|
|
134
|
+
"feature-dev",
|
|
135
|
+
"testing",
|
|
136
|
+
"docs",
|
|
137
|
+
"git-ops",
|
|
138
|
+
"build-deploy",
|
|
139
|
+
"exploration",
|
|
140
|
+
"planning"
|
|
141
|
+
];
|
|
142
|
+
var SEARCH_LIMIT_MAX = 100;
|
|
143
|
+
var SEARCH_LIMIT_DEFAULT = 50;
|
|
144
|
+
function searchParams(c) {
|
|
145
|
+
return new URL(c.req.url ?? "http://localhost/", "http://localhost/").searchParams;
|
|
139
146
|
}
|
|
140
|
-
function
|
|
141
|
-
return
|
|
147
|
+
function queryValues(params, ...names) {
|
|
148
|
+
return names.flatMap(
|
|
149
|
+
(name) => params.getAll(name).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
|
|
150
|
+
);
|
|
142
151
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
152
|
+
function optionalQueryValue(value) {
|
|
153
|
+
const normalized = value?.trim();
|
|
154
|
+
return normalized ? normalized : void 0;
|
|
145
155
|
}
|
|
146
|
-
function
|
|
156
|
+
function parseDateParam(value, fallback) {
|
|
157
|
+
if (value == null) return fallback;
|
|
158
|
+
const ts = new Date(value).getTime();
|
|
159
|
+
return Number.isNaN(ts) ? fallback : ts;
|
|
160
|
+
}
|
|
161
|
+
function parseNumberParam(value) {
|
|
162
|
+
if (value == null || !value.trim()) return void 0;
|
|
163
|
+
const number = Number(value);
|
|
164
|
+
return Number.isFinite(number) ? number : void 0;
|
|
165
|
+
}
|
|
166
|
+
function parseSmartTags(values) {
|
|
167
|
+
const tags = values.map((value) => value.toLowerCase()).filter((value) => SMART_TAGS.includes(value));
|
|
168
|
+
return tags.length > 0 ? [...new Set(tags)] : void 0;
|
|
169
|
+
}
|
|
170
|
+
function parseFileActivityKind(value) {
|
|
171
|
+
if (value === "read" || value === "edit" || value === "write" || value === "delete") {
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function parseProjectIdentityFilter(kindValue, keyValue) {
|
|
177
|
+
const kind = optionalQueryValue(kindValue);
|
|
178
|
+
const key = optionalQueryValue(keyValue);
|
|
179
|
+
if (!kind && !key) return void 0;
|
|
180
|
+
if (!kind || !key || !isProjectIdentityKind(kind)) return null;
|
|
181
|
+
return { kind, key };
|
|
182
|
+
}
|
|
183
|
+
function parseSearchOptions(c, defaults, projectIdentity) {
|
|
184
|
+
const params = searchParams(c);
|
|
185
|
+
const limitValue = parseNumberParam(params.get("limit") ?? void 0);
|
|
186
|
+
return {
|
|
187
|
+
agent: optionalQueryValue(params.get("agent") ?? void 0),
|
|
188
|
+
project: optionalQueryValue(params.get("project") ?? void 0),
|
|
189
|
+
projectKind: projectIdentity?.kind,
|
|
190
|
+
projectKey: projectIdentity?.key,
|
|
191
|
+
cwd: optionalQueryValue(params.get("cwd") ?? void 0),
|
|
192
|
+
tags: parseSmartTags(queryValues(params, "tag", "tags", "signal")),
|
|
193
|
+
tools: queryValues(params, "tool", "tools").map((tool) => tool.toLowerCase()),
|
|
194
|
+
file: optionalQueryValue(params.get("file") ?? params.get("path") ?? void 0),
|
|
195
|
+
fileKind: parseFileActivityKind(
|
|
196
|
+
optionalQueryValue(params.get("fileKind") ?? params.get("fileActivity") ?? void 0)
|
|
197
|
+
),
|
|
198
|
+
costMin: parseNumberParam(params.get("costMin") ?? void 0),
|
|
199
|
+
costMax: parseNumberParam(params.get("costMax") ?? void 0),
|
|
200
|
+
from: parseDateParam(params.get("from") ?? void 0, defaults.from),
|
|
201
|
+
to: parseDateParam(params.get("to") ?? void 0, defaults.to),
|
|
202
|
+
limit: limitValue && limitValue > 0 ? Math.min(limitValue, SEARCH_LIMIT_MAX) : SEARCH_LIMIT_DEFAULT
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function filterSessionsByActivityWindow(sessions, from, to) {
|
|
206
|
+
if (from == null && to == null) return sessions;
|
|
207
|
+
return sessions.filter((session) => {
|
|
208
|
+
const activity = getSessionActivityTime(session);
|
|
209
|
+
if (from != null && activity < from) return false;
|
|
210
|
+
if (to != null && activity > to) return false;
|
|
211
|
+
return true;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/api/session-aliases-view.ts
|
|
216
|
+
function aliasKey(reference) {
|
|
217
|
+
return `${reference.agentName.toLowerCase()}\0${reference.sessionId}`;
|
|
218
|
+
}
|
|
219
|
+
function loadAliasMap() {
|
|
147
220
|
try {
|
|
148
|
-
return new Map(
|
|
149
|
-
listSessionAliases().map((alias) => [
|
|
150
|
-
getSessionAliasKey(alias.agentKey, alias.sessionId),
|
|
151
|
-
alias.alias
|
|
152
|
-
])
|
|
153
|
-
);
|
|
221
|
+
return new Map(listSessionAliases().map((alias) => [aliasKey(alias.reference), alias]));
|
|
154
222
|
} catch (error) {
|
|
155
223
|
if (!(error instanceof StateStorageUnavailableError)) {
|
|
156
224
|
appLogger.warn("api.session_aliases.load_failed", {
|
|
@@ -160,52 +228,97 @@ function loadSessionAliasMap() {
|
|
|
160
228
|
return /* @__PURE__ */ new Map();
|
|
161
229
|
}
|
|
162
230
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
231
|
+
function buildAliasView() {
|
|
232
|
+
const aliases = loadAliasMap();
|
|
233
|
+
return {
|
|
234
|
+
size: aliases.size,
|
|
235
|
+
get: (reference) => aliases.get(aliasKey(reference))?.alias,
|
|
236
|
+
decorate(record, reference) {
|
|
237
|
+
const alias = aliases.get(aliasKey(reference))?.alias;
|
|
238
|
+
return alias ? { ...record, display_title: alias } : record;
|
|
239
|
+
},
|
|
240
|
+
entries: () => aliases.values()
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
var cachedView = null;
|
|
244
|
+
function loadAliasView() {
|
|
245
|
+
return cachedView ??= buildAliasView();
|
|
165
246
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
return alias ? { ...session, display_title: alias } : session;
|
|
247
|
+
function invalidateAliasView() {
|
|
248
|
+
cachedView = null;
|
|
169
249
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
250
|
+
function decorateBookmark(bookmark, aliases) {
|
|
251
|
+
return {
|
|
252
|
+
...bookmark,
|
|
253
|
+
session: aliases.decorate(bookmark.session, bookmark.reference)
|
|
254
|
+
};
|
|
173
255
|
}
|
|
174
|
-
function
|
|
256
|
+
function decorateFileActivity(activity, aliases) {
|
|
175
257
|
return {
|
|
176
258
|
...activity,
|
|
177
|
-
session:
|
|
259
|
+
session: aliases.decorate(activity.session, activity.reference)
|
|
178
260
|
};
|
|
179
261
|
}
|
|
180
|
-
function findSessionByAliasKey(scanResult, aliasKey) {
|
|
181
|
-
const separatorIndex = aliasKey.indexOf("\0");
|
|
182
|
-
const agentName = aliasKey.slice(0, separatorIndex);
|
|
183
|
-
const sessionId = aliasKey.slice(separatorIndex + 1);
|
|
184
|
-
return scanResult.byAgent[agentName]?.find((session) => session.id === sessionId);
|
|
185
|
-
}
|
|
186
262
|
function findAliasSearchResults(query, options, scanResult, aliases) {
|
|
187
263
|
const search = mergeSearchQueryOptions(query, options);
|
|
188
264
|
const needle = search.text.trim().toLowerCase();
|
|
189
265
|
if (!needle || aliases.size === 0) return [];
|
|
190
|
-
const
|
|
266
|
+
const sessionsByAgent = /* @__PURE__ */ new Map();
|
|
267
|
+
const lookupSession = (agentName, sessionId) => {
|
|
268
|
+
let byId = sessionsByAgent.get(agentName);
|
|
269
|
+
if (!byId) {
|
|
270
|
+
byId = new Map((scanResult.byAgent[agentName] ?? []).map((item) => [item.id, item]));
|
|
271
|
+
sessionsByAgent.set(agentName, byId);
|
|
272
|
+
}
|
|
273
|
+
return byId.get(sessionId);
|
|
274
|
+
};
|
|
191
275
|
const results = [];
|
|
192
|
-
for (const
|
|
193
|
-
if (!alias.toLowerCase().includes(needle)) continue;
|
|
194
|
-
const
|
|
276
|
+
for (const alias of aliases.entries()) {
|
|
277
|
+
if (!alias.alias.toLowerCase().includes(needle)) continue;
|
|
278
|
+
const { agentName, sessionId } = alias.reference;
|
|
279
|
+
const session = lookupSession(agentName, sessionId);
|
|
195
280
|
if (!session) continue;
|
|
196
|
-
const agentName = aliasKey.slice(0, aliasKey.indexOf("\0"));
|
|
197
|
-
if (!matchesSessionSearchFilters(agentName, session, search.options, projectScope)) continue;
|
|
198
281
|
results.push({
|
|
199
|
-
|
|
200
|
-
session:
|
|
282
|
+
reference: alias.reference,
|
|
283
|
+
session: aliases.decorate(session, alias.reference),
|
|
201
284
|
snippet: `Alias \xB7 ${session.directory}`,
|
|
202
285
|
matchType: "title"
|
|
203
286
|
});
|
|
204
287
|
}
|
|
205
|
-
return results.sort(
|
|
288
|
+
return filterSessionSearchCandidates(results, search.options).sort(
|
|
206
289
|
(a, b) => getSessionActivityTime(b.session) - getSessionActivityTime(a.session)
|
|
207
290
|
);
|
|
208
291
|
}
|
|
292
|
+
|
|
293
|
+
// src/api/handlers.ts
|
|
294
|
+
var SNAPSHOT_AGGREGATION_CACHE_LIMIT = 64;
|
|
295
|
+
var snapshotAggregationCaches = /* @__PURE__ */ new WeakMap();
|
|
296
|
+
function getSnapshotAggregation(source, sessions, key, build) {
|
|
297
|
+
let cache = snapshotAggregationCaches.get(source);
|
|
298
|
+
if (!cache || cache.sessions !== sessions) {
|
|
299
|
+
cache = { sessions, values: /* @__PURE__ */ new Map() };
|
|
300
|
+
snapshotAggregationCaches.set(source, cache);
|
|
301
|
+
}
|
|
302
|
+
const cacheKey = JSON.stringify(key);
|
|
303
|
+
if (cache.values.has(cacheKey)) return cache.values.get(cacheKey);
|
|
304
|
+
const value = build();
|
|
305
|
+
if (cache.values.size >= SNAPSHOT_AGGREGATION_CACHE_LIMIT) {
|
|
306
|
+
const oldestKey = cache.values.keys().next().value;
|
|
307
|
+
if (oldestKey != null) cache.values.delete(oldestKey);
|
|
308
|
+
}
|
|
309
|
+
cache.values.set(cacheKey, value);
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
function withStorageErrors(handler, onUnavailable) {
|
|
313
|
+
try {
|
|
314
|
+
return handler();
|
|
315
|
+
} catch (error) {
|
|
316
|
+
if (error instanceof StateStorageUnavailableError) {
|
|
317
|
+
return onUnavailable();
|
|
318
|
+
}
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
209
322
|
function isRecord(value) {
|
|
210
323
|
return typeof value === "object" && value !== null;
|
|
211
324
|
}
|
|
@@ -213,15 +326,14 @@ function isSessionStats(value) {
|
|
|
213
326
|
if (!isRecord(value)) return false;
|
|
214
327
|
return typeof value.message_count === "number" && typeof value.total_input_tokens === "number" && typeof value.total_output_tokens === "number" && typeof value.total_cost === "number" && (value.total_tokens == null || typeof value.total_tokens === "number");
|
|
215
328
|
}
|
|
216
|
-
function
|
|
329
|
+
function parseBookmarkSession(value, reference) {
|
|
217
330
|
if (!isRecord(value)) return null;
|
|
218
|
-
if (
|
|
331
|
+
if (value.id !== reference.sessionId || typeof value.slug !== "string" || typeof value.title !== "string" || typeof value.directory !== "string" || typeof value.time_created !== "number" || value.time_updated != null && typeof value.time_updated !== "number" || !isSessionStats(value.stats)) {
|
|
219
332
|
return null;
|
|
220
333
|
}
|
|
221
334
|
return {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
fullPath: value.fullPath,
|
|
335
|
+
id: reference.sessionId,
|
|
336
|
+
slug: formatSessionReference(reference),
|
|
225
337
|
title: value.title,
|
|
226
338
|
directory: value.directory,
|
|
227
339
|
time_created: value.time_created,
|
|
@@ -229,70 +341,38 @@ function parseBookmarkPayload(value) {
|
|
|
229
341
|
stats: value.stats
|
|
230
342
|
};
|
|
231
343
|
}
|
|
232
|
-
function
|
|
233
|
-
if (value
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return Number.isFinite(number) ? number : void 0;
|
|
241
|
-
}
|
|
242
|
-
function searchParams(c) {
|
|
243
|
-
return new URL(c.req.url ?? "http://localhost/", "http://localhost/").searchParams;
|
|
244
|
-
}
|
|
245
|
-
function queryValues(params, ...names) {
|
|
246
|
-
return names.flatMap(
|
|
247
|
-
(name) => params.getAll(name).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
function parseSmartTags(values) {
|
|
251
|
-
const tags = values.map((value) => value.toLowerCase()).filter(
|
|
252
|
-
(value) => [
|
|
253
|
-
"bugfix",
|
|
254
|
-
"refactoring",
|
|
255
|
-
"feature-dev",
|
|
256
|
-
"testing",
|
|
257
|
-
"docs",
|
|
258
|
-
"git-ops",
|
|
259
|
-
"build-deploy",
|
|
260
|
-
"exploration",
|
|
261
|
-
"planning"
|
|
262
|
-
].includes(value)
|
|
263
|
-
);
|
|
264
|
-
return tags.length > 0 ? [...new Set(tags)] : void 0;
|
|
265
|
-
}
|
|
266
|
-
function parseSearchOptions(c, defaults, projectIdentity) {
|
|
267
|
-
const params = searchParams(c);
|
|
268
|
-
const limitValue = parseNumberParam(params.get("limit") ?? void 0);
|
|
269
|
-
return {
|
|
270
|
-
agent: optionalQueryValue(params.get("agent") ?? void 0),
|
|
271
|
-
project: optionalQueryValue(params.get("project") ?? void 0),
|
|
272
|
-
projectKind: projectIdentity?.kind,
|
|
273
|
-
projectKey: projectIdentity?.key,
|
|
274
|
-
cwd: optionalQueryValue(params.get("cwd") ?? void 0),
|
|
275
|
-
tags: parseSmartTags(queryValues(params, "tag", "tags", "signal")),
|
|
276
|
-
tools: queryValues(params, "tool", "tools").map((tool) => tool.toLowerCase()),
|
|
277
|
-
file: optionalQueryValue(params.get("file") ?? params.get("path") ?? void 0),
|
|
278
|
-
fileKind: parseFileActivityKind(
|
|
279
|
-
optionalQueryValue(params.get("fileKind") ?? params.get("fileActivity") ?? void 0)
|
|
280
|
-
),
|
|
281
|
-
costMin: parseNumberParam(params.get("costMin") ?? void 0),
|
|
282
|
-
costMax: parseNumberParam(params.get("costMax") ?? void 0),
|
|
283
|
-
from: parseDateParam(params.get("from") ?? void 0, defaults.from),
|
|
284
|
-
to: parseDateParam(params.get("to") ?? void 0, defaults.to),
|
|
285
|
-
limit: limitValue && limitValue > 0 ? Math.min(limitValue, 100) : 50
|
|
286
|
-
};
|
|
344
|
+
function parseSessionReferencePayload(value) {
|
|
345
|
+
if (!isRecord(value) || typeof value.agentName !== "string" || typeof value.sessionId !== "string" || !value.agentName.trim() || !value.sessionId) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return normalizeSessionReference({
|
|
349
|
+
agentName: value.agentName.trim().toLowerCase(),
|
|
350
|
+
sessionId: value.sessionId
|
|
351
|
+
});
|
|
287
352
|
}
|
|
288
|
-
function
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
353
|
+
function parseBookmarkPayload(value) {
|
|
354
|
+
if (!isRecord(value)) return null;
|
|
355
|
+
const reference = parseSessionReferencePayload(value.reference);
|
|
356
|
+
if (reference) {
|
|
357
|
+
const session2 = parseBookmarkSession(value.session, reference);
|
|
358
|
+
return session2 ? { reference, session: session2 } : null;
|
|
359
|
+
}
|
|
360
|
+
if (typeof value.agentKey !== "string" || typeof value.sessionId !== "string" || typeof value.fullPath !== "string" || !value.agentKey.trim() || !value.sessionId) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const legacyReference = normalizeSessionReference({
|
|
364
|
+
agentName: value.agentKey.trim().toLowerCase(),
|
|
365
|
+
sessionId: value.sessionId
|
|
295
366
|
});
|
|
367
|
+
const session = parseBookmarkSession(
|
|
368
|
+
{
|
|
369
|
+
...value,
|
|
370
|
+
id: value.sessionId,
|
|
371
|
+
slug: value.fullPath
|
|
372
|
+
},
|
|
373
|
+
legacyReference
|
|
374
|
+
);
|
|
375
|
+
return session ? { reference: legacyReference, session } : null;
|
|
296
376
|
}
|
|
297
377
|
function sanitizeClientLogData(value) {
|
|
298
378
|
if (!isRecord(value)) return {};
|
|
@@ -306,60 +386,47 @@ function sanitizeClientLogData(value) {
|
|
|
306
386
|
})
|
|
307
387
|
);
|
|
308
388
|
}
|
|
309
|
-
function
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return projects.map((project) => {
|
|
353
|
-
const metric = metrics.get(getProjectGroupKey(project.identityKind, project.identityKey));
|
|
354
|
-
return {
|
|
355
|
-
...project,
|
|
356
|
-
messages: metric?.messages ?? 0,
|
|
357
|
-
tokens: metric?.tokens ?? 0,
|
|
358
|
-
cost: metric?.cost ?? 0,
|
|
359
|
-
cost_source: metric && metric.cost > 0 ? metric.hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
360
|
-
agentStats: [...metric?.agentStats.values() ?? []].sort((a, b) => b.sessions - a.sessions)
|
|
361
|
-
};
|
|
362
|
-
});
|
|
389
|
+
function toSessionListItem(session) {
|
|
390
|
+
if (!session.model_usage) return session;
|
|
391
|
+
const item = { ...session };
|
|
392
|
+
delete item.model_usage;
|
|
393
|
+
return item;
|
|
394
|
+
}
|
|
395
|
+
function getSessionHeadReference(session) {
|
|
396
|
+
return {
|
|
397
|
+
agentName: getSessionAgentKey(session),
|
|
398
|
+
sessionId: session.id
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function createSessionDetailJsonResponse(data, messages) {
|
|
402
|
+
const encoder = new TextEncoder();
|
|
403
|
+
const headerJson = JSON.stringify(data);
|
|
404
|
+
const iterator = messages[Symbol.iterator]();
|
|
405
|
+
let wroteHeader = false;
|
|
406
|
+
let wroteMessage = false;
|
|
407
|
+
return new Response(
|
|
408
|
+
new ReadableStream({
|
|
409
|
+
pull(controller) {
|
|
410
|
+
if (!wroteHeader) {
|
|
411
|
+
controller.enqueue(encoder.encode(`${headerJson.slice(0, -1)},"messages":[`));
|
|
412
|
+
wroteHeader = true;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const next = iterator.next();
|
|
416
|
+
if (!next.done) {
|
|
417
|
+
controller.enqueue(encoder.encode(`${wroteMessage ? "," : ""}${next.value}`));
|
|
418
|
+
wroteMessage = true;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
controller.enqueue(encoder.encode("]}"));
|
|
422
|
+
controller.close();
|
|
423
|
+
},
|
|
424
|
+
cancel() {
|
|
425
|
+
iterator.return?.();
|
|
426
|
+
}
|
|
427
|
+
}),
|
|
428
|
+
{ headers: { "Content-Type": "application/json; charset=UTF-8" } }
|
|
429
|
+
);
|
|
363
430
|
}
|
|
364
431
|
function handleGetConfig(c, defaults) {
|
|
365
432
|
const payload = {
|
|
@@ -378,22 +445,38 @@ function handleGetAgents(c, scanSource, defaults = {}) {
|
|
|
378
445
|
const scanResult = scanSource.getSnapshot();
|
|
379
446
|
const from = parseDateParam(c.req.query("from"), defaults.from);
|
|
380
447
|
const to = parseDateParam(c.req.query("to"), defaults.to);
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
448
|
+
const agents = getSnapshotAggregation(
|
|
449
|
+
scanSource,
|
|
450
|
+
scanResult.sessions,
|
|
451
|
+
["agents", from, to],
|
|
452
|
+
() => {
|
|
453
|
+
const counts = Object.fromEntries(
|
|
454
|
+
Object.entries(scanResult.byAgent).map(([agentName, sessions]) => [
|
|
455
|
+
agentName,
|
|
456
|
+
filterSessionsByActivityWindow(sessions, from, to).length
|
|
457
|
+
])
|
|
458
|
+
);
|
|
459
|
+
return getAgentInfoMap(counts);
|
|
460
|
+
}
|
|
386
461
|
);
|
|
387
|
-
return c.json(
|
|
462
|
+
return c.json(agents);
|
|
388
463
|
}
|
|
389
464
|
function handleGetProjects(c, scanSource, defaults = {}) {
|
|
390
465
|
const scanResult = scanSource.getSnapshot();
|
|
391
466
|
const from = parseDateParam(c.req.query("from"), defaults.from);
|
|
392
467
|
const to = parseDateParam(c.req.query("to"), defaults.to);
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
468
|
+
const projects = getSnapshotAggregation(
|
|
469
|
+
scanSource,
|
|
470
|
+
scanResult.sessions,
|
|
471
|
+
["projects", from, to],
|
|
472
|
+
() => {
|
|
473
|
+
const sessions = filterSessionsByActivityWindow(scanResult.sessions, from, to);
|
|
474
|
+
return {
|
|
475
|
+
projects: attachProjectMetrics(listCachedProjectGroups(sessions), sessions)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
return c.json(projects);
|
|
397
480
|
}
|
|
398
481
|
function handleGetSessions(c, scanSource, defaults = {}) {
|
|
399
482
|
const scanResult = scanSource.getSnapshot();
|
|
@@ -428,16 +511,16 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
428
511
|
if (tag) {
|
|
429
512
|
sessions = sessions.filter((s) => s.smart_tags?.includes(tag));
|
|
430
513
|
}
|
|
431
|
-
const aliases =
|
|
514
|
+
const aliases = loadAliasView();
|
|
432
515
|
if (q) {
|
|
433
516
|
sessions = sessions.filter((session) => {
|
|
434
|
-
const alias = aliases.get(
|
|
517
|
+
const alias = aliases.get(getSessionHeadReference(session));
|
|
435
518
|
return session.title.toLowerCase().includes(q) || alias?.toLowerCase().includes(q);
|
|
436
519
|
});
|
|
437
520
|
}
|
|
438
521
|
return c.json({
|
|
439
522
|
sessions: sessions.map(
|
|
440
|
-
(session) =>
|
|
523
|
+
(session) => toSessionListItem(aliases.decorate(session, getSessionHeadReference(session)))
|
|
441
524
|
)
|
|
442
525
|
});
|
|
443
526
|
}
|
|
@@ -452,35 +535,18 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
|
452
535
|
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
453
536
|
}
|
|
454
537
|
const searchOptions = parseSearchOptions(c, defaults, projectIdentity);
|
|
455
|
-
const aliases =
|
|
538
|
+
const aliases = loadAliasView();
|
|
456
539
|
const results = executeSessionSearch(query, searchOptions, scanResult).map((result) => ({
|
|
457
540
|
...result,
|
|
458
|
-
session:
|
|
541
|
+
session: aliases.decorate(result.session, result.reference)
|
|
459
542
|
}));
|
|
460
543
|
const aliasResults = findAliasSearchResults(query, searchOptions, scanResult, aliases);
|
|
461
544
|
const deduped = /* @__PURE__ */ new Map();
|
|
462
545
|
for (const result of [...aliasResults, ...results]) {
|
|
463
|
-
deduped.set(`${result.agentName}\0${result.
|
|
546
|
+
deduped.set(`${result.reference.agentName}\0${result.reference.sessionId}`, result);
|
|
464
547
|
}
|
|
465
548
|
return c.json({ results: [...deduped.values()].slice(0, searchOptions.limit ?? 50) });
|
|
466
549
|
}
|
|
467
|
-
function parseFileActivityKind(value) {
|
|
468
|
-
if (value === "read" || value === "edit" || value === "write" || value === "delete") {
|
|
469
|
-
return value;
|
|
470
|
-
}
|
|
471
|
-
return void 0;
|
|
472
|
-
}
|
|
473
|
-
function optionalQueryValue(value) {
|
|
474
|
-
const normalized = value?.trim();
|
|
475
|
-
return normalized ? normalized : void 0;
|
|
476
|
-
}
|
|
477
|
-
function parseProjectIdentityFilter(kindValue, keyValue) {
|
|
478
|
-
const kind = optionalQueryValue(kindValue);
|
|
479
|
-
const key = optionalQueryValue(keyValue);
|
|
480
|
-
if (!kind && !key) return void 0;
|
|
481
|
-
if (!kind || !key || !isProjectIdentityKind(kind)) return null;
|
|
482
|
-
return { kind, key };
|
|
483
|
-
}
|
|
484
550
|
function handleGetFileActivity(c, defaults = {}) {
|
|
485
551
|
const limitValue = Number(c.req.query("limit"));
|
|
486
552
|
const limit = Number.isFinite(limitValue) && limitValue > 0 ? Math.min(limitValue, 200) : 50;
|
|
@@ -491,7 +557,7 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
491
557
|
if (projectIdentity === null) {
|
|
492
558
|
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
493
559
|
}
|
|
494
|
-
const aliases =
|
|
560
|
+
const aliases = loadAliasView();
|
|
495
561
|
return c.json({
|
|
496
562
|
activity: listFileActivity({
|
|
497
563
|
agent: optionalQueryValue(c.req.query("agent")),
|
|
@@ -505,12 +571,11 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
505
571
|
from: parseDateParam(c.req.query("from"), defaults.from),
|
|
506
572
|
to: parseDateParam(c.req.query("to"), defaults.to),
|
|
507
573
|
limit
|
|
508
|
-
}).map((activity) =>
|
|
574
|
+
}).map((activity) => decorateFileActivity(activity, aliases))
|
|
509
575
|
});
|
|
510
576
|
}
|
|
511
577
|
async function handleGetSessionData(c, scanSource) {
|
|
512
578
|
const startedAt = performance.now();
|
|
513
|
-
const scanResult = scanSource.getSnapshot();
|
|
514
579
|
const agentName = c.req.param("agent");
|
|
515
580
|
const sessionId = c.req.param("id");
|
|
516
581
|
if (!agentName) {
|
|
@@ -519,21 +584,15 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
519
584
|
if (!sessionId) {
|
|
520
585
|
return c.json({ error: "Missing session ID" }, 400);
|
|
521
586
|
}
|
|
522
|
-
const agent = scanResult.agents.find((a) => a.name === agentName);
|
|
523
|
-
if (!agent) {
|
|
524
|
-
return c.json({ error: `Unknown agent: ${agentName}` }, 404);
|
|
525
|
-
}
|
|
526
587
|
try {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const loadDuration = performance.now() - loadStartedAt;
|
|
536
|
-
if (!data) {
|
|
588
|
+
const result = materializeSessionDetailResponse(scanSource.getSnapshot(), {
|
|
589
|
+
agentName,
|
|
590
|
+
sessionId
|
|
591
|
+
});
|
|
592
|
+
if (result.status === "unknown-agent") {
|
|
593
|
+
return c.json({ error: `Unknown agent: ${agentName}` }, 404);
|
|
594
|
+
}
|
|
595
|
+
if (result.status === "not-ready") {
|
|
537
596
|
appLogger.warn("api.session_data.cache_miss", {
|
|
538
597
|
agent: agentName,
|
|
539
598
|
session_id: sessionId,
|
|
@@ -541,27 +600,20 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
541
600
|
});
|
|
542
601
|
return c.json({ error: "Session cache not ready" }, 404);
|
|
543
602
|
}
|
|
544
|
-
const tagStartedAt = performance.now();
|
|
545
|
-
const smartTags = data.smart_tags ?? classifySessionTags(data);
|
|
546
|
-
const tagDuration = performance.now() - tagStartedAt;
|
|
547
|
-
const projectIdentity = data.project_identity ?? head?.project_identity ?? computeIdentity(data.directory, realFs);
|
|
548
|
-
const fileActivity = data.file_activity ?? (cacheHasExpectedMessages && cachedData ? listSessionFileActivity(agentName, sessionId) : extractSessionFileActivity(agentName, sessionId, projectIdentity.key, data.messages));
|
|
549
603
|
appLogger.info("api.session_data", {
|
|
550
604
|
agent: agentName,
|
|
551
605
|
session_id: sessionId,
|
|
552
|
-
messages: data.messages.length,
|
|
553
|
-
load_duration_ms: Math.round(loadDuration),
|
|
554
|
-
tag_duration_ms: Math.round(tagDuration),
|
|
606
|
+
messages: result.status === "found-json" ? result.messageCount : result.data.messages.length,
|
|
555
607
|
duration_ms: Math.round(performance.now() - startedAt)
|
|
556
608
|
});
|
|
557
|
-
const aliases =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
609
|
+
const aliases = loadAliasView();
|
|
610
|
+
if (result.status === "found-json") {
|
|
611
|
+
return createSessionDetailJsonResponse(
|
|
612
|
+
aliases.decorate(result.data, result.data.reference),
|
|
613
|
+
result.messages
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return c.json(aliases.decorate(result.data, result.data.reference));
|
|
565
617
|
} catch (err) {
|
|
566
618
|
const message = err instanceof Error ? err.message : "Failed to load session";
|
|
567
619
|
appLogger.error("api.session_data.error", {
|
|
@@ -584,32 +636,26 @@ async function handlePostClientLog(c) {
|
|
|
584
636
|
return c.json({ ok: true });
|
|
585
637
|
}
|
|
586
638
|
function handleGetBookmarks(c) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
throw error;
|
|
598
|
-
}
|
|
639
|
+
return withStorageErrors(
|
|
640
|
+
() => {
|
|
641
|
+
const aliases = loadAliasView();
|
|
642
|
+
return c.json({
|
|
643
|
+
bookmarks: listBookmarks().map((bookmark) => decorateBookmark(bookmark, aliases)),
|
|
644
|
+
storageAvailable: true
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
() => c.json({ bookmarks: [], storageAvailable: false })
|
|
648
|
+
);
|
|
599
649
|
}
|
|
600
650
|
async function handlePutBookmark(c) {
|
|
601
651
|
const payload = parseBookmarkPayload(await c.req.json().catch(() => null));
|
|
602
652
|
if (!payload) {
|
|
603
653
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
604
654
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
610
|
-
}
|
|
611
|
-
throw error;
|
|
612
|
-
}
|
|
655
|
+
return withStorageErrors(
|
|
656
|
+
() => c.json({ bookmark: upsertBookmark(payload), storageAvailable: true }),
|
|
657
|
+
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
658
|
+
);
|
|
613
659
|
}
|
|
614
660
|
async function handleImportBookmarks(c) {
|
|
615
661
|
const payload = await c.req.json().catch(() => null);
|
|
@@ -620,14 +666,10 @@ async function handleImportBookmarks(c) {
|
|
|
620
666
|
if (bookmarks.length !== payload.length) {
|
|
621
667
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
622
668
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
628
|
-
}
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
669
|
+
return withStorageErrors(
|
|
670
|
+
() => c.json({ bookmarks: importBookmarks(bookmarks), storageAvailable: true }),
|
|
671
|
+
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
672
|
+
);
|
|
631
673
|
}
|
|
632
674
|
function handleDeleteBookmark(c) {
|
|
633
675
|
const agentKey = c.req.param("agent");
|
|
@@ -635,32 +677,35 @@ function handleDeleteBookmark(c) {
|
|
|
635
677
|
if (!agentKey || !sessionId) {
|
|
636
678
|
return c.json({ error: "Missing bookmark identifier" }, 400);
|
|
637
679
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
throw error;
|
|
646
|
-
}
|
|
680
|
+
return withStorageErrors(
|
|
681
|
+
() => {
|
|
682
|
+
deleteBookmark({ agentName: agentKey, sessionId });
|
|
683
|
+
return c.json({ ok: true, storageAvailable: true });
|
|
684
|
+
},
|
|
685
|
+
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
686
|
+
);
|
|
647
687
|
}
|
|
648
688
|
async function handlePutSessionAlias(c) {
|
|
649
689
|
const agentKey = c.req.param("agent");
|
|
650
690
|
const sessionId = c.req.param("id");
|
|
651
691
|
const payload = await c.req.json().catch(() => null);
|
|
652
|
-
|
|
692
|
+
const aliasValue = payload?.alias;
|
|
693
|
+
if (!agentKey || !sessionId || typeof aliasValue !== "string") {
|
|
653
694
|
return c.json({ error: "Invalid session alias payload" }, 400);
|
|
654
695
|
}
|
|
655
696
|
try {
|
|
656
|
-
return
|
|
697
|
+
return withStorageErrors(
|
|
698
|
+
() => {
|
|
699
|
+
const alias = upsertSessionAlias({ agentName: agentKey, sessionId }, aliasValue);
|
|
700
|
+
invalidateAliasView();
|
|
701
|
+
return c.json({ alias });
|
|
702
|
+
},
|
|
703
|
+
() => c.json({ error: "Session alias storage is unavailable" }, 503)
|
|
704
|
+
);
|
|
657
705
|
} catch (error) {
|
|
658
|
-
if (error instanceof
|
|
706
|
+
if (error instanceof SessionAliasValidationError) {
|
|
659
707
|
return c.json({ error: "Session alias must be non-empty and at most 160 characters" }, 400);
|
|
660
708
|
}
|
|
661
|
-
if (isStateStorageUnavailable(error)) {
|
|
662
|
-
return c.json({ error: "Session alias storage is unavailable" }, 503);
|
|
663
|
-
}
|
|
664
709
|
throw error;
|
|
665
710
|
}
|
|
666
711
|
}
|
|
@@ -670,15 +715,14 @@ function handleDeleteSessionAlias(c) {
|
|
|
670
715
|
if (!agentKey || !sessionId) {
|
|
671
716
|
return c.json({ error: "Missing session alias identifier" }, 400);
|
|
672
717
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
}
|
|
718
|
+
return withStorageErrors(
|
|
719
|
+
() => {
|
|
720
|
+
deleteSessionAlias({ agentName: agentKey, sessionId });
|
|
721
|
+
invalidateAliasView();
|
|
722
|
+
return c.json({ ok: true });
|
|
723
|
+
},
|
|
724
|
+
() => c.json({ error: "Session alias storage is unavailable" }, 503)
|
|
725
|
+
);
|
|
682
726
|
}
|
|
683
727
|
function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
684
728
|
const scanResult = scanSource.getSnapshot();
|
|
@@ -703,15 +747,30 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
703
747
|
projectKind: projectIdentity?.kind,
|
|
704
748
|
projectKey: projectIdentity?.key
|
|
705
749
|
};
|
|
706
|
-
const
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
750
|
+
const fixedTo = parseDateParam(c.req.query("to"), defaults.to);
|
|
751
|
+
const aggregate = getSnapshotAggregation(
|
|
752
|
+
scanSource,
|
|
753
|
+
scanResult.sessions,
|
|
754
|
+
[
|
|
755
|
+
"dashboard",
|
|
756
|
+
scope.agent,
|
|
757
|
+
scope.projectKind,
|
|
758
|
+
scope.projectKey,
|
|
759
|
+
from,
|
|
760
|
+
fixedTo ?? startOfLocalDay(to)
|
|
761
|
+
],
|
|
762
|
+
() => {
|
|
763
|
+
const agentInfo = getAgentInfoMap({});
|
|
764
|
+
const agentInfoMap = new Map(agentInfo.map((agent) => [agent.name, agent]));
|
|
765
|
+
return buildDashboard(scanResult.sessions, {
|
|
766
|
+
byAgentNames: Object.keys(scanResult.byAgent),
|
|
767
|
+
scope,
|
|
768
|
+
from,
|
|
769
|
+
to,
|
|
770
|
+
agentInfoMap
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
);
|
|
715
774
|
const data = {
|
|
716
775
|
...aggregate,
|
|
717
776
|
recentFileActivities: listFileActivity({
|
|
@@ -724,14 +783,15 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
724
783
|
}),
|
|
725
784
|
window: { from, to, days }
|
|
726
785
|
};
|
|
727
|
-
const aliases =
|
|
786
|
+
const aliases = loadAliasView();
|
|
728
787
|
return c.json({
|
|
729
788
|
...data,
|
|
730
|
-
recentSessions: data.recentSessions.map(
|
|
731
|
-
|
|
732
|
-
|
|
789
|
+
recentSessions: data.recentSessions.map((item) => ({
|
|
790
|
+
...item,
|
|
791
|
+
session: aliases.decorate(item.session, item.reference)
|
|
792
|
+
})),
|
|
733
793
|
recentFileActivities: data.recentFileActivities.map(
|
|
734
|
-
(activity) =>
|
|
794
|
+
(activity) => decorateFileActivity(activity, aliases)
|
|
735
795
|
)
|
|
736
796
|
});
|
|
737
797
|
}
|
|
@@ -1016,6 +1076,227 @@ async function createServer(port, store, options = {}) {
|
|
|
1016
1076
|
import { existsSync as existsSync4 } from "fs";
|
|
1017
1077
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1018
1078
|
|
|
1079
|
+
// src/agent-operation-scheduler.ts
|
|
1080
|
+
var PENDING_REFRESH_DELAY_MS = 100;
|
|
1081
|
+
var MAX_ADAPTIVE_REFRESH_DELAY_MS = 3e4;
|
|
1082
|
+
var ADAPTIVE_REFRESH_DELAY_MULTIPLIER = 4;
|
|
1083
|
+
var AgentOperationScheduler = class {
|
|
1084
|
+
constructor(runRefresh) {
|
|
1085
|
+
this.runRefresh = runRefresh;
|
|
1086
|
+
}
|
|
1087
|
+
runRefresh;
|
|
1088
|
+
states = /* @__PURE__ */ new Map();
|
|
1089
|
+
operationGenerations = /* @__PURE__ */ new Map();
|
|
1090
|
+
operationTails = /* @__PURE__ */ new Map();
|
|
1091
|
+
isStopped = false;
|
|
1092
|
+
notify(agentName, delayMs) {
|
|
1093
|
+
if (this.isStopped) return;
|
|
1094
|
+
this.state(agentName).pendingSignalCount += 1;
|
|
1095
|
+
this.schedule(agentName, delayMs);
|
|
1096
|
+
}
|
|
1097
|
+
schedule(agentName, delayMs) {
|
|
1098
|
+
if (this.isStopped) return;
|
|
1099
|
+
const state = this.state(agentName);
|
|
1100
|
+
const adaptiveDelayMs = Math.min(
|
|
1101
|
+
state.lastRefreshDurationMs * ADAPTIVE_REFRESH_DELAY_MULTIPLIER,
|
|
1102
|
+
MAX_ADAPTIVE_REFRESH_DELAY_MS
|
|
1103
|
+
);
|
|
1104
|
+
const effectiveDelayMs = Math.max(delayMs, adaptiveDelayMs);
|
|
1105
|
+
const deadline = Date.now() + effectiveDelayMs;
|
|
1106
|
+
if (state.timer) {
|
|
1107
|
+
if (deadline >= state.timerDeadline) return;
|
|
1108
|
+
clearTimeout(state.timer);
|
|
1109
|
+
}
|
|
1110
|
+
appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: effectiveDelayMs });
|
|
1111
|
+
state.timerDeadline = deadline;
|
|
1112
|
+
state.timer = setTimeout(() => {
|
|
1113
|
+
state.timer = null;
|
|
1114
|
+
state.timerDeadline = 0;
|
|
1115
|
+
void this.refresh(agentName);
|
|
1116
|
+
}, effectiveDelayMs);
|
|
1117
|
+
}
|
|
1118
|
+
async refresh(agentName) {
|
|
1119
|
+
if (this.isStopped) return;
|
|
1120
|
+
const state = this.state(agentName);
|
|
1121
|
+
if (state.isRefreshRunning) {
|
|
1122
|
+
appLogger.debug("scan.refresh.pending", { agent: agentName });
|
|
1123
|
+
state.hasPendingRefresh = true;
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
state.isRefreshRunning = true;
|
|
1127
|
+
try {
|
|
1128
|
+
await this.run(agentName, "refresh", () => this.runRefresh(agentName));
|
|
1129
|
+
} finally {
|
|
1130
|
+
state.isRefreshRunning = false;
|
|
1131
|
+
if (state.hasPendingRefresh && !this.isStopped) {
|
|
1132
|
+
state.hasPendingRefresh = false;
|
|
1133
|
+
this.schedule(agentName, PENDING_REFRESH_DELAY_MS);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
run(agentName, kind, operation) {
|
|
1138
|
+
const previous = this.operationTails.get(agentName) ?? Promise.resolve();
|
|
1139
|
+
const run = previous.then(async () => {
|
|
1140
|
+
if (this.isStopped) return "skipped";
|
|
1141
|
+
const lifecycle = this.beginOperation(agentName, kind);
|
|
1142
|
+
try {
|
|
1143
|
+
const result = await operation();
|
|
1144
|
+
this.completeOperation(lifecycle, result);
|
|
1145
|
+
return result;
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
this.completeOperation(lifecycle, "failed");
|
|
1148
|
+
throw error;
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
const tail = run.then(
|
|
1152
|
+
() => void 0,
|
|
1153
|
+
() => void 0
|
|
1154
|
+
);
|
|
1155
|
+
this.operationTails.set(agentName, tail);
|
|
1156
|
+
void tail.finally(() => {
|
|
1157
|
+
if (this.operationTails.get(agentName) === tail) this.operationTails.delete(agentName);
|
|
1158
|
+
});
|
|
1159
|
+
return run;
|
|
1160
|
+
}
|
|
1161
|
+
takePendingSignalCount(agentName) {
|
|
1162
|
+
const state = this.state(agentName);
|
|
1163
|
+
const count = state.pendingSignalCount;
|
|
1164
|
+
state.pendingSignalCount = 0;
|
|
1165
|
+
return count;
|
|
1166
|
+
}
|
|
1167
|
+
recordRefreshDuration(agentName, durationMs) {
|
|
1168
|
+
this.state(agentName).lastRefreshDurationMs = durationMs;
|
|
1169
|
+
}
|
|
1170
|
+
snapshot() {
|
|
1171
|
+
return {
|
|
1172
|
+
activeOperations: this.operationTails.size,
|
|
1173
|
+
activeRefreshes: [...this.states.values()].filter((state) => state.isRefreshRunning).length
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
stop() {
|
|
1177
|
+
this.isStopped = true;
|
|
1178
|
+
for (const state of this.states.values()) {
|
|
1179
|
+
if (state.timer) clearTimeout(state.timer);
|
|
1180
|
+
state.timer = null;
|
|
1181
|
+
state.timerDeadline = 0;
|
|
1182
|
+
state.hasPendingRefresh = false;
|
|
1183
|
+
state.pendingSignalCount = 0;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async waitForIdle() {
|
|
1187
|
+
await Promise.allSettled(this.operationTails.values());
|
|
1188
|
+
}
|
|
1189
|
+
state(agentName) {
|
|
1190
|
+
const existing = this.states.get(agentName);
|
|
1191
|
+
if (existing) return existing;
|
|
1192
|
+
const state = {
|
|
1193
|
+
timer: null,
|
|
1194
|
+
timerDeadline: 0,
|
|
1195
|
+
isRefreshRunning: false,
|
|
1196
|
+
hasPendingRefresh: false,
|
|
1197
|
+
lastRefreshDurationMs: 0,
|
|
1198
|
+
pendingSignalCount: 0
|
|
1199
|
+
};
|
|
1200
|
+
this.states.set(agentName, state);
|
|
1201
|
+
return state;
|
|
1202
|
+
}
|
|
1203
|
+
beginOperation(agentName, kind) {
|
|
1204
|
+
const generation = (this.operationGenerations.get(agentName) ?? 0) + 1;
|
|
1205
|
+
const startedAt = Date.now();
|
|
1206
|
+
this.operationGenerations.set(agentName, generation);
|
|
1207
|
+
appLogger.info("scan.agent_operation.started", {
|
|
1208
|
+
agent: agentName,
|
|
1209
|
+
operation: kind,
|
|
1210
|
+
generation,
|
|
1211
|
+
started_at: startedAt
|
|
1212
|
+
});
|
|
1213
|
+
return { agentName, kind, generation, startedAt };
|
|
1214
|
+
}
|
|
1215
|
+
completeOperation(lifecycle, result) {
|
|
1216
|
+
const completedAt = Date.now();
|
|
1217
|
+
appLogger.info("scan.agent_operation.completed", {
|
|
1218
|
+
agent: lifecycle.agentName,
|
|
1219
|
+
operation: lifecycle.kind,
|
|
1220
|
+
generation: lifecycle.generation,
|
|
1221
|
+
started_at: lifecycle.startedAt,
|
|
1222
|
+
completed_at: completedAt,
|
|
1223
|
+
duration_ms: completedAt - lifecycle.startedAt,
|
|
1224
|
+
result
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
// src/live-session-index.ts
|
|
1230
|
+
var LiveSessionIndex = class {
|
|
1231
|
+
agents = [];
|
|
1232
|
+
agentsByName = /* @__PURE__ */ new Map();
|
|
1233
|
+
byAgent = {};
|
|
1234
|
+
sessions = [];
|
|
1235
|
+
signatureCaches = /* @__PURE__ */ new Map();
|
|
1236
|
+
initialize(snapshot, options = {}) {
|
|
1237
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
1238
|
+
for (const agent of snapshot.agents) agentMap.set(agent.name, agent);
|
|
1239
|
+
for (const agent of options.registeredAgents ?? []) {
|
|
1240
|
+
if (!agentMap.has(agent.name)) agentMap.set(agent.name, agent);
|
|
1241
|
+
}
|
|
1242
|
+
this.agents = [...agentMap.values()].filter(
|
|
1243
|
+
(agent) => !options.allowedAgents || options.allowedAgents.has(agent.name.toLowerCase())
|
|
1244
|
+
);
|
|
1245
|
+
this.agentsByName = new Map(this.agents.map((agent) => [agent.name, agent]));
|
|
1246
|
+
this.byAgent = Object.fromEntries(
|
|
1247
|
+
this.agents.map((agent) => [agent.name, sortSessions(snapshot.byAgent[agent.name] ?? [])])
|
|
1248
|
+
);
|
|
1249
|
+
this.sessions = mergeSortedSessions(Object.values(this.byAgent));
|
|
1250
|
+
this.signatureCaches.clear();
|
|
1251
|
+
}
|
|
1252
|
+
snapshot() {
|
|
1253
|
+
return {
|
|
1254
|
+
agents: this.agents,
|
|
1255
|
+
byAgent: this.byAgent,
|
|
1256
|
+
sessions: this.sessions
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
findAgent(agentName) {
|
|
1260
|
+
return this.agentsByName.get(agentName);
|
|
1261
|
+
}
|
|
1262
|
+
commitAgentSessions(agentName, nextSessions, candidateChangedIds = []) {
|
|
1263
|
+
const previousSessions = this.byAgent[agentName] ?? [];
|
|
1264
|
+
const signatureCache = this.signatureCache(agentName);
|
|
1265
|
+
const { changes, removedSessionIds, counts } = computeSessionDiff(
|
|
1266
|
+
previousSessions,
|
|
1267
|
+
nextSessions,
|
|
1268
|
+
candidateChangedIds,
|
|
1269
|
+
sessionSignature,
|
|
1270
|
+
signatureCache
|
|
1271
|
+
);
|
|
1272
|
+
for (const removedId of removedSessionIds) signatureCache.delete(removedId);
|
|
1273
|
+
this.byAgent[agentName] = sortSessions(nextSessions);
|
|
1274
|
+
this.sessions = mergeSortedSessions(Object.values(this.byAgent));
|
|
1275
|
+
if (counts.new === 0 && counts.updated === 0 && counts.removed === 0) return null;
|
|
1276
|
+
return {
|
|
1277
|
+
type: "sessions-updated",
|
|
1278
|
+
changedAgents: [agentName],
|
|
1279
|
+
newSessions: counts.new,
|
|
1280
|
+
updatedSessions: counts.updated,
|
|
1281
|
+
removedSessions: counts.removed,
|
|
1282
|
+
totalSessions: this.sessions.length,
|
|
1283
|
+
timestamp: Date.now(),
|
|
1284
|
+
changedSessionHeads: changes.map(({ session }) => ({
|
|
1285
|
+
reference: { agentName, sessionId: session.id },
|
|
1286
|
+
session
|
|
1287
|
+
})),
|
|
1288
|
+
removedSessionRefs: removedSessionIds.map((sessionId) => ({ agentName, sessionId }))
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
signatureCache(agentName) {
|
|
1292
|
+
const existing = this.signatureCaches.get(agentName);
|
|
1293
|
+
if (existing) return existing;
|
|
1294
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1295
|
+
this.signatureCaches.set(agentName, cache);
|
|
1296
|
+
return cache;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1019
1300
|
// src/search-index-job-runner.ts
|
|
1020
1301
|
import { existsSync as existsSync2 } from "fs";
|
|
1021
1302
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1168,7 +1449,6 @@ var SearchIndexJobRunner = class {
|
|
|
1168
1449
|
nextBatchId = 1;
|
|
1169
1450
|
pendingJobs = new PendingSearchIndexJobs();
|
|
1170
1451
|
isShuttingDown = false;
|
|
1171
|
-
hasCheckedFtsIntegrity = false;
|
|
1172
1452
|
enqueue(context, jobs) {
|
|
1173
1453
|
if (jobs.length === 0) return Promise.resolve();
|
|
1174
1454
|
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE));
|
|
@@ -1241,8 +1521,7 @@ var SearchIndexJobRunner = class {
|
|
|
1241
1521
|
jobs: batch.jobs,
|
|
1242
1522
|
agentNames: [],
|
|
1243
1523
|
sessionsByAgent: {},
|
|
1244
|
-
metaByAgent: {}
|
|
1245
|
-
skipFtsIntegrityCheck: this.hasCheckedFtsIntegrity
|
|
1524
|
+
metaByAgent: {}
|
|
1246
1525
|
}
|
|
1247
1526
|
});
|
|
1248
1527
|
worker.unref();
|
|
@@ -1258,7 +1537,6 @@ var SearchIndexJobRunner = class {
|
|
|
1258
1537
|
duration_ms: Math.round(message.durationMs),
|
|
1259
1538
|
sessions: message.sessions
|
|
1260
1539
|
});
|
|
1261
|
-
this.hasCheckedFtsIntegrity = true;
|
|
1262
1540
|
this.settle(batch);
|
|
1263
1541
|
});
|
|
1264
1542
|
worker.on("error", (error) => {
|
|
@@ -1414,6 +1692,21 @@ var ScanStatusModel = class {
|
|
|
1414
1692
|
updatedAt: now
|
|
1415
1693
|
});
|
|
1416
1694
|
}
|
|
1695
|
+
indexAgent(agentName) {
|
|
1696
|
+
const status = this.status.agentStatuses[agentName];
|
|
1697
|
+
if (!this.status.active || !status || status.status !== "scanning") return null;
|
|
1698
|
+
const now = Date.now();
|
|
1699
|
+
const agentStatuses = {
|
|
1700
|
+
...this.status.agentStatuses,
|
|
1701
|
+
[agentName]: { ...status, status: "indexing", updatedAt: now }
|
|
1702
|
+
};
|
|
1703
|
+
return this.set({
|
|
1704
|
+
...this.status,
|
|
1705
|
+
phase: this.activePhase(this.status.pendingAgents, this.status.scanningAgents, agentStatuses),
|
|
1706
|
+
agentStatuses,
|
|
1707
|
+
updatedAt: now
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1417
1710
|
finishAgent(agentName, sessionCount) {
|
|
1418
1711
|
const pendingAgents = this.status.pendingAgents.filter((agent) => agent !== agentName);
|
|
1419
1712
|
const scanningAgents = this.status.scanningAgents.filter((agent) => agent !== agentName);
|
|
@@ -1422,26 +1715,27 @@ var ScanStatusModel = class {
|
|
|
1422
1715
|
const now = Date.now();
|
|
1423
1716
|
const previousStatus = this.status.agentStatuses[agentName];
|
|
1424
1717
|
const total = previousStatus?.total ?? previousStatus?.processed;
|
|
1718
|
+
const agentStatuses = {
|
|
1719
|
+
...this.status.agentStatuses,
|
|
1720
|
+
[agentName]: {
|
|
1721
|
+
agentName,
|
|
1722
|
+
status: "complete",
|
|
1723
|
+
total,
|
|
1724
|
+
processed: total,
|
|
1725
|
+
sessions: sessionCount ?? previousStatus?.sessions ?? 0,
|
|
1726
|
+
startedAt: previousStatus?.startedAt,
|
|
1727
|
+
updatedAt: now,
|
|
1728
|
+
completedAt: now
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1425
1731
|
return this.set({
|
|
1426
1732
|
...this.status,
|
|
1427
1733
|
active: isActive,
|
|
1428
|
-
phase: isActive ?
|
|
1734
|
+
phase: isActive ? this.activePhase(pendingAgents, scanningAgents, agentStatuses) : "idle",
|
|
1429
1735
|
pendingAgents,
|
|
1430
1736
|
scanningAgents,
|
|
1431
1737
|
completedAgents,
|
|
1432
|
-
agentStatuses
|
|
1433
|
-
...this.status.agentStatuses,
|
|
1434
|
-
[agentName]: {
|
|
1435
|
-
agentName,
|
|
1436
|
-
status: "complete",
|
|
1437
|
-
total,
|
|
1438
|
-
processed: total,
|
|
1439
|
-
sessions: sessionCount ?? previousStatus?.sessions ?? 0,
|
|
1440
|
-
startedAt: previousStatus?.startedAt,
|
|
1441
|
-
updatedAt: now,
|
|
1442
|
-
completedAt: now
|
|
1443
|
-
}
|
|
1444
|
-
},
|
|
1738
|
+
agentStatuses,
|
|
1445
1739
|
updatedAt: now,
|
|
1446
1740
|
completedAt: isActive ? void 0 : now
|
|
1447
1741
|
});
|
|
@@ -1471,6 +1765,12 @@ var ScanStatusModel = class {
|
|
|
1471
1765
|
updatedAt: Date.now()
|
|
1472
1766
|
});
|
|
1473
1767
|
}
|
|
1768
|
+
activePhase(pendingAgents, scanningAgents, agentStatuses) {
|
|
1769
|
+
if (pendingAgents.length === 0 && scanningAgents.length > 0 && scanningAgents.every((agentName) => agentStatuses[agentName]?.status === "indexing")) {
|
|
1770
|
+
return "indexing";
|
|
1771
|
+
}
|
|
1772
|
+
return this.status.phase === "initializing" ? "initializing" : "scanning";
|
|
1773
|
+
}
|
|
1474
1774
|
set(status) {
|
|
1475
1775
|
this.status = status;
|
|
1476
1776
|
return this.snapshot();
|
|
@@ -1480,38 +1780,16 @@ var ScanStatusModel = class {
|
|
|
1480
1780
|
// src/agent-sync-engine.ts
|
|
1481
1781
|
var REFRESH_DEBOUNCE_MS = 200;
|
|
1482
1782
|
var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
|
|
1483
|
-
var PENDING_REFRESH_DELAY_MS = 100;
|
|
1484
|
-
var MAX_ADAPTIVE_REFRESH_DELAY_MS = 3e4;
|
|
1485
|
-
var ADAPTIVE_REFRESH_DELAY_MULTIPLIER = 4;
|
|
1486
1783
|
var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
|
|
1487
1784
|
var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1488
|
-
function
|
|
1489
|
-
const { changes, removedSessionIds
|
|
1785
|
+
function buildPersistenceDiff(previousSessions, nextSessions, candidateChangedIds = []) {
|
|
1786
|
+
const { changes, removedSessionIds } = computeSessionDiff(
|
|
1490
1787
|
previousSessions,
|
|
1491
1788
|
nextSessions,
|
|
1492
1789
|
candidateChangedIds,
|
|
1493
|
-
sessionSignature
|
|
1494
|
-
signatureCache
|
|
1790
|
+
sessionSignature
|
|
1495
1791
|
);
|
|
1496
|
-
|
|
1497
|
-
if (counts.new === 0 && counts.updated === 0 && counts.removed === 0) {
|
|
1498
|
-
return { event: null, changedSessions: changes, removedSessionIds };
|
|
1499
|
-
}
|
|
1500
|
-
return {
|
|
1501
|
-
changedSessions: changes,
|
|
1502
|
-
removedSessionIds,
|
|
1503
|
-
event: {
|
|
1504
|
-
type: "sessions-updated",
|
|
1505
|
-
changedAgents: [agentName],
|
|
1506
|
-
newSessions: counts.new,
|
|
1507
|
-
updatedSessions: counts.updated,
|
|
1508
|
-
removedSessions: counts.removed,
|
|
1509
|
-
totalSessions: nextSessions.length,
|
|
1510
|
-
timestamp: Date.now(),
|
|
1511
|
-
changedSessionHeads: changes.map(({ session }) => ({ agentName, session })),
|
|
1512
|
-
removedSessionRefs: removedSessionIds.map((sessionId) => ({ agentName, sessionId }))
|
|
1513
|
-
}
|
|
1514
|
-
};
|
|
1792
|
+
return { changedSessions: changes, removedSessionIds };
|
|
1515
1793
|
}
|
|
1516
1794
|
function restoreAgentCacheMeta(agent, cached) {
|
|
1517
1795
|
agent.setSessionMetaMap(new Map(Object.entries(cached.meta)));
|
|
@@ -1519,11 +1797,12 @@ function restoreAgentCacheMeta(agent, cached) {
|
|
|
1519
1797
|
var AgentSyncEngine = class {
|
|
1520
1798
|
constructor(options) {
|
|
1521
1799
|
this.options = options;
|
|
1800
|
+
this.scheduler = new AgentOperationScheduler((agentName) => this.performRefresh(agentName));
|
|
1522
1801
|
}
|
|
1523
1802
|
options;
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1803
|
+
lastRefreshAtByAgent = /* @__PURE__ */ new Map();
|
|
1804
|
+
scheduler;
|
|
1805
|
+
sessionIndex = new LiveSessionIndex();
|
|
1527
1806
|
backfillQueue = [];
|
|
1528
1807
|
currentBackfillAgent;
|
|
1529
1808
|
completedBackfillAgents = [];
|
|
@@ -1532,13 +1811,22 @@ var AgentSyncEngine = class {
|
|
|
1532
1811
|
statusChangedListeners = /* @__PURE__ */ new Set();
|
|
1533
1812
|
scanStatus = new ScanStatusModel();
|
|
1534
1813
|
searchIndexJobs = new SearchIndexJobRunner();
|
|
1814
|
+
nextPublicationId = 1;
|
|
1535
1815
|
backgroundRefreshTimer = null;
|
|
1536
1816
|
isShuttingDown = false;
|
|
1537
|
-
initialize(
|
|
1538
|
-
|
|
1539
|
-
|
|
1817
|
+
initialize(snapshot, options = {}) {
|
|
1818
|
+
this.sessionIndex.initialize(snapshot, options);
|
|
1819
|
+
this.lastRefreshAtByAgent.clear();
|
|
1820
|
+
for (const agent of this.sessionIndex.snapshot().agents) {
|
|
1821
|
+
this.lastRefreshAtByAgent.set(
|
|
1822
|
+
agent.name,
|
|
1823
|
+
options.cacheTimestamps?.[agent.name] ?? Date.now()
|
|
1824
|
+
);
|
|
1540
1825
|
}
|
|
1541
1826
|
}
|
|
1827
|
+
snapshot() {
|
|
1828
|
+
return this.sessionIndex.snapshot();
|
|
1829
|
+
}
|
|
1542
1830
|
status() {
|
|
1543
1831
|
return this.scanStatus.snapshot();
|
|
1544
1832
|
}
|
|
@@ -1551,49 +1839,45 @@ var AgentSyncEngine = class {
|
|
|
1551
1839
|
return () => this.statusChangedListeners.delete(listener);
|
|
1552
1840
|
}
|
|
1553
1841
|
async syncInitialIndex() {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
this.
|
|
1557
|
-
|
|
1842
|
+
const jobs = this.buildFullSearchIndexJobs("scan.initial");
|
|
1843
|
+
await this.commitSearchIndex("scan.initial", jobs, {
|
|
1844
|
+
publicationId: this.publicationId("scan.initial"),
|
|
1845
|
+
agents: jobs.map((job) => job.agentName)
|
|
1846
|
+
});
|
|
1558
1847
|
}
|
|
1559
1848
|
handleAgentsChanged(agentNames) {
|
|
1560
|
-
const snapshot = this.
|
|
1849
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1561
1850
|
for (const agentName of agentNames) {
|
|
1562
|
-
this.state(agentName).pendingPathCount += 1;
|
|
1563
1851
|
const delayMs = (snapshot.byAgent[agentName]?.length ?? 0) === 0 ? EMPTY_AGENT_REFRESH_DEBOUNCE_MS : REFRESH_DEBOUNCE_MS;
|
|
1564
|
-
this.
|
|
1852
|
+
this.scheduler.notify(agentName, delayMs);
|
|
1565
1853
|
}
|
|
1566
1854
|
}
|
|
1567
1855
|
startBackgroundRefresh() {
|
|
1568
1856
|
if (this.backgroundRefreshTimer) return;
|
|
1569
|
-
const agentNames = this.
|
|
1857
|
+
const agentNames = this.sessionIndex.snapshot().agents.map((agent) => agent.name);
|
|
1570
1858
|
this.startScanBatch(agentNames, "scanning");
|
|
1571
1859
|
this.backgroundRefreshTimer = setTimeout(() => {
|
|
1572
1860
|
this.backgroundRefreshTimer = null;
|
|
1573
|
-
for (const agentName of agentNames) this.
|
|
1861
|
+
for (const agentName of agentNames) this.scheduler.schedule(agentName, 0);
|
|
1574
1862
|
if (agentNames.length === 0) this.finishScanBatch();
|
|
1575
1863
|
}, 0);
|
|
1576
1864
|
}
|
|
1577
1865
|
async refresh(agentName) {
|
|
1578
|
-
await this.
|
|
1866
|
+
await this.scheduler.refresh(agentName);
|
|
1579
1867
|
}
|
|
1580
1868
|
async shutdown() {
|
|
1581
1869
|
this.isShuttingDown = true;
|
|
1870
|
+
const schedulerSnapshot = this.scheduler.snapshot();
|
|
1582
1871
|
const activeOperations = {
|
|
1583
|
-
agent_operations:
|
|
1584
|
-
refreshes:
|
|
1872
|
+
agent_operations: schedulerSnapshot.activeOperations,
|
|
1873
|
+
refreshes: schedulerSnapshot.activeRefreshes,
|
|
1585
1874
|
backfill_running: this.currentBackfillAgent != null || void 0,
|
|
1586
1875
|
scan_workers: this.options.workerRunner.activeCount
|
|
1587
1876
|
};
|
|
1588
1877
|
if (activeOperations.agent_operations > 0 || activeOperations.scan_workers > 0) {
|
|
1589
1878
|
appLogger.warn("scan.shutdown.active_operations", activeOperations);
|
|
1590
1879
|
}
|
|
1591
|
-
|
|
1592
|
-
if (!state.timer) continue;
|
|
1593
|
-
clearTimeout(state.timer);
|
|
1594
|
-
state.timer = null;
|
|
1595
|
-
state.timerDeadline = 0;
|
|
1596
|
-
}
|
|
1880
|
+
this.scheduler.stop();
|
|
1597
1881
|
if (this.backgroundRefreshTimer) {
|
|
1598
1882
|
clearTimeout(this.backgroundRefreshTimer);
|
|
1599
1883
|
this.backgroundRefreshTimer = null;
|
|
@@ -1607,7 +1891,7 @@ var AgentSyncEngine = class {
|
|
|
1607
1891
|
});
|
|
1608
1892
|
await this.searchIndexJobs.shutdown();
|
|
1609
1893
|
await this.options.workerRunner.shutdown();
|
|
1610
|
-
await
|
|
1894
|
+
await this.scheduler.waitForIdle();
|
|
1611
1895
|
const stoppedSearchIndexSnapshot = this.searchIndexJobs.snapshot();
|
|
1612
1896
|
appLogger.info("search_index.shutdown.completed", {
|
|
1613
1897
|
active_batch_id: searchIndexSnapshot.activeBatchId,
|
|
@@ -1615,7 +1899,7 @@ var AgentSyncEngine = class {
|
|
|
1615
1899
|
});
|
|
1616
1900
|
}
|
|
1617
1901
|
startScanBatch(agentNames, phase) {
|
|
1618
|
-
const snapshot = this.
|
|
1902
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1619
1903
|
const sessionCounts = Object.fromEntries(
|
|
1620
1904
|
agentNames.map((agentName) => [agentName, snapshot.byAgent[agentName]?.length ?? 0])
|
|
1621
1905
|
);
|
|
@@ -1625,7 +1909,7 @@ var AgentSyncEngine = class {
|
|
|
1625
1909
|
this.publishStatus(this.scanStatus.setPhase(phase));
|
|
1626
1910
|
}
|
|
1627
1911
|
beginAgentScan(agentName) {
|
|
1628
|
-
const snapshot = this.
|
|
1912
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1629
1913
|
if (!this.scanStatus.snapshot().active) this.startScanBatch([agentName], "scanning");
|
|
1630
1914
|
this.publishStatus(
|
|
1631
1915
|
this.scanStatus.beginAgent(agentName, snapshot.byAgent[agentName]?.length ?? 0)
|
|
@@ -1634,8 +1918,11 @@ var AgentSyncEngine = class {
|
|
|
1634
1918
|
updateAgentScanProgress(agentName, progress) {
|
|
1635
1919
|
this.publishStatus(this.scanStatus.updateAgent(agentName, progress));
|
|
1636
1920
|
}
|
|
1921
|
+
beginAgentIndexing(agentName) {
|
|
1922
|
+
this.publishStatus(this.scanStatus.indexAgent(agentName));
|
|
1923
|
+
}
|
|
1637
1924
|
finishAgentScan(agentName) {
|
|
1638
|
-
const count = this.
|
|
1925
|
+
const count = this.sessionIndex.snapshot().byAgent[agentName]?.length;
|
|
1639
1926
|
this.publishStatus(this.scanStatus.finishAgent(agentName, count));
|
|
1640
1927
|
}
|
|
1641
1928
|
finishScanBatch() {
|
|
@@ -1652,44 +1939,6 @@ var AgentSyncEngine = class {
|
|
|
1652
1939
|
if (this.isShuttingDown) return;
|
|
1653
1940
|
for (const listener of this.sessionsChangedListeners) listener(change);
|
|
1654
1941
|
}
|
|
1655
|
-
scheduleRefresh(agentName, delayMs) {
|
|
1656
|
-
if (this.isShuttingDown) return;
|
|
1657
|
-
const state = this.state(agentName);
|
|
1658
|
-
const adaptiveDelayMs = Math.min(
|
|
1659
|
-
state.lastRefreshDurationMs * ADAPTIVE_REFRESH_DELAY_MULTIPLIER,
|
|
1660
|
-
MAX_ADAPTIVE_REFRESH_DELAY_MS
|
|
1661
|
-
);
|
|
1662
|
-
const effectiveDelayMs = Math.max(delayMs, adaptiveDelayMs);
|
|
1663
|
-
const deadline = Date.now() + effectiveDelayMs;
|
|
1664
|
-
if (state.timer) {
|
|
1665
|
-
if (deadline >= state.timerDeadline) return;
|
|
1666
|
-
clearTimeout(state.timer);
|
|
1667
|
-
}
|
|
1668
|
-
appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: effectiveDelayMs });
|
|
1669
|
-
state.timerDeadline = deadline;
|
|
1670
|
-
state.timer = setTimeout(() => {
|
|
1671
|
-
state.timer = null;
|
|
1672
|
-
void this.runCoalescedRefresh(agentName);
|
|
1673
|
-
}, effectiveDelayMs);
|
|
1674
|
-
}
|
|
1675
|
-
async runCoalescedRefresh(agentName) {
|
|
1676
|
-
const state = this.state(agentName);
|
|
1677
|
-
if (state.isRunning) {
|
|
1678
|
-
appLogger.debug("scan.refresh.pending", { agent: agentName });
|
|
1679
|
-
state.hasPendingRerun = true;
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
state.isRunning = true;
|
|
1683
|
-
try {
|
|
1684
|
-
await this.serialize(agentName, "refresh", () => this.performRefresh(agentName));
|
|
1685
|
-
} finally {
|
|
1686
|
-
state.isRunning = false;
|
|
1687
|
-
if (state.hasPendingRerun && !this.isShuttingDown) {
|
|
1688
|
-
state.hasPendingRerun = false;
|
|
1689
|
-
this.scheduleRefresh(agentName, PENDING_REFRESH_DELAY_MS);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
1942
|
async performRefresh(agentName) {
|
|
1694
1943
|
this.beginAgentScan(agentName);
|
|
1695
1944
|
try {
|
|
@@ -1706,18 +1955,16 @@ var AgentSyncEngine = class {
|
|
|
1706
1955
|
}
|
|
1707
1956
|
async runRefresh(agentName) {
|
|
1708
1957
|
const startedAt = performance.now();
|
|
1709
|
-
const
|
|
1710
|
-
const pendingPathCount = state.pendingPathCount;
|
|
1711
|
-
state.pendingPathCount = 0;
|
|
1958
|
+
const pendingPathCount = this.scheduler.takePendingSignalCount(agentName);
|
|
1712
1959
|
const agent = this.findAgent(agentName);
|
|
1713
1960
|
if (!agent) {
|
|
1714
1961
|
appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
|
|
1715
1962
|
return "skipped";
|
|
1716
1963
|
}
|
|
1717
|
-
const previousSessions = this.
|
|
1964
|
+
const previousSessions = this.sessionIndex.snapshot().byAgent[agentName] ?? [];
|
|
1718
1965
|
const cached = loadCachedSessions(agentName);
|
|
1719
1966
|
const refreshBaseline = cached?.sessions ?? previousSessions;
|
|
1720
|
-
const cacheTimestamp = cached?.timestamp ??
|
|
1967
|
+
const cacheTimestamp = cached?.timestamp ?? this.lastRefreshAtByAgent.get(agentName) ?? 0;
|
|
1721
1968
|
if (cached) restoreAgentCacheMeta(agent, cached);
|
|
1722
1969
|
const isInitialized = isAgentCacheInitialized(agentName);
|
|
1723
1970
|
const availabilityStartedAt = performance.now();
|
|
@@ -1742,74 +1989,59 @@ var AgentSyncEngine = class {
|
|
|
1742
1989
|
}
|
|
1743
1990
|
if (strategyResult.status === "unchanged") return "unchanged";
|
|
1744
1991
|
const nextSessions = attachMissingProjectIdentities(strategyResult.nextSessions);
|
|
1745
|
-
const diffStartedAt = performance.now();
|
|
1746
|
-
const diff = buildRefreshDiff(
|
|
1747
|
-
agentName,
|
|
1748
|
-
previousSessions,
|
|
1749
|
-
nextSessions,
|
|
1750
|
-
strategyResult.preciseChangedIds ?? [],
|
|
1751
|
-
state.signatureCache
|
|
1752
|
-
);
|
|
1753
|
-
const diffDuration = performance.now() - diffStartedAt;
|
|
1754
1992
|
const searchIndexOptions = pendingPathCount >= SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD ? { isBulk: true } : void 0;
|
|
1755
|
-
const
|
|
1756
|
-
const
|
|
1757
|
-
const changedSessionIds = strategyResult.usedIncrementalScan ? new Set(persistentChanges.map(({ session }) => session.id)) : void 0;
|
|
1993
|
+
const persistenceDiff = strategyResult.persistenceDiff;
|
|
1994
|
+
const changedSessionIds = persistenceDiff ? new Set(persistenceDiff.changedSessions.map(({ session }) => session.id)) : void 0;
|
|
1758
1995
|
const persistStartedAt = performance.now();
|
|
1759
|
-
const persistentJob =
|
|
1996
|
+
const persistentJob = persistenceDiff ? {
|
|
1760
1997
|
kind: "changes",
|
|
1761
1998
|
context: "scan.refresh",
|
|
1762
1999
|
agentName,
|
|
1763
|
-
changes:
|
|
1764
|
-
removedSessionIds:
|
|
2000
|
+
changes: persistenceDiff.changedSessions,
|
|
2001
|
+
removedSessionIds: persistenceDiff.removedSessionIds,
|
|
1765
2002
|
meta: buildAgentCacheMeta(agent, changedSessionIds),
|
|
1766
2003
|
...searchIndexOptions ? { searchIndexOptions } : {}
|
|
1767
|
-
} :
|
|
2004
|
+
} : {
|
|
1768
2005
|
kind: "full",
|
|
1769
2006
|
context: "scan.refresh",
|
|
1770
2007
|
agentName,
|
|
1771
|
-
sessions: strategyResult.fullScanSessions,
|
|
2008
|
+
sessions: strategyResult.fullScanSessions ?? nextSessions,
|
|
1772
2009
|
meta: buildAgentCacheMeta(agent),
|
|
1773
2010
|
saveCache: true,
|
|
1774
2011
|
...searchIndexOptions ? { searchIndexOptions } : {}
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
2012
|
+
};
|
|
2013
|
+
this.beginAgentIndexing(agentName);
|
|
2014
|
+
const publication = await this.commitSessionPublication({
|
|
2015
|
+
context: "scan.refresh",
|
|
2016
|
+
agentName,
|
|
2017
|
+
sessions: nextSessions,
|
|
2018
|
+
candidateChangedIds: strategyResult.preciseChangedIds ?? [],
|
|
2019
|
+
indexJob: persistentJob
|
|
2020
|
+
});
|
|
1787
2021
|
const persistDuration = performance.now() - persistStartedAt;
|
|
1788
2022
|
logSearchIndexSync("scan.refresh", null, { pending_paths: pendingPathCount });
|
|
1789
|
-
this.emitSessionsChanged({ agentName, sessions: nextSessions, event: diff.event });
|
|
1790
2023
|
const totalDurationMs = performance.now() - startedAt;
|
|
1791
|
-
|
|
2024
|
+
this.scheduler.recordRefreshDuration(agentName, totalDurationMs);
|
|
1792
2025
|
appLogger.info("scan.refresh.done", {
|
|
1793
2026
|
agent: agentName,
|
|
1794
2027
|
duration_ms: Math.round(totalDurationMs),
|
|
1795
2028
|
sessions: nextSessions.length,
|
|
1796
|
-
new_sessions:
|
|
1797
|
-
updated_sessions:
|
|
1798
|
-
removed_sessions:
|
|
2029
|
+
new_sessions: publication.event?.newSessions ?? 0,
|
|
2030
|
+
updated_sessions: publication.event?.updatedSessions ?? 0,
|
|
2031
|
+
removed_sessions: publication.event?.removedSessions ?? 0,
|
|
1799
2032
|
pending_paths: pendingPathCount,
|
|
1800
2033
|
availability_ms: Math.round(availabilityDuration),
|
|
1801
2034
|
check_ms: Math.round(strategyResult.checkDuration),
|
|
1802
2035
|
scan_ms: Math.round(strategyResult.scanDuration),
|
|
1803
|
-
diff_ms: Math.round(diffDuration),
|
|
2036
|
+
diff_ms: Math.round(publication.diffDuration),
|
|
1804
2037
|
persist_ms: Math.round(persistDuration),
|
|
1805
|
-
search_index_ms:
|
|
1806
|
-
persistent_index_worker_job: persistentJob
|
|
1807
|
-
persistent_index_skipped: !persistentJob || void 0
|
|
2038
|
+
search_index_ms: Math.round(persistDuration),
|
|
2039
|
+
persistent_index_worker_job: persistentJob.kind
|
|
1808
2040
|
});
|
|
1809
2041
|
return "committed";
|
|
1810
2042
|
}
|
|
1811
2043
|
refreshUnavailableAgent(agentName) {
|
|
1812
|
-
this.
|
|
2044
|
+
this.lastRefreshAtByAgent.set(agentName, Date.now());
|
|
1813
2045
|
return this.refreshStrategyResult([]);
|
|
1814
2046
|
}
|
|
1815
2047
|
async initializeAgent(agent, previousSessions) {
|
|
@@ -1818,7 +2050,7 @@ var AgentSyncEngine = class {
|
|
|
1818
2050
|
const result = await this.runWorker(agent, previousSessions, null, this.startupScanOptions());
|
|
1819
2051
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1820
2052
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
1821
|
-
this.
|
|
2053
|
+
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
1822
2054
|
return this.refreshStrategyResult(sessions, {
|
|
1823
2055
|
fullScanSessions: sessions,
|
|
1824
2056
|
scanDuration: performance.now() - scanStartedAt
|
|
@@ -1833,17 +2065,17 @@ var AgentSyncEngine = class {
|
|
|
1833
2065
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1834
2066
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
1835
2067
|
const preciseChangedIds = result.changedIds ?? [];
|
|
1836
|
-
const persistenceDiff =
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2068
|
+
const persistenceDiff = buildPersistenceDiff(cached.sessions, sessions, preciseChangedIds);
|
|
2069
|
+
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
2070
|
+
if (persistenceDiff.changedSessions.length === 0 && persistenceDiff.removedSessionIds.length === 0) {
|
|
2071
|
+
this.logUnchangedRefresh(agent.name, refreshStartedAt);
|
|
2072
|
+
return this.refreshStrategyResult(sessions, {
|
|
2073
|
+
status: "unchanged",
|
|
2074
|
+
scanDuration: performance.now() - scanStartedAt
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
1844
2077
|
return this.refreshStrategyResult(sessions, {
|
|
1845
2078
|
preciseChangedIds,
|
|
1846
|
-
usedIncrementalScan: true,
|
|
1847
2079
|
persistenceDiff,
|
|
1848
2080
|
scanDuration: performance.now() - scanStartedAt
|
|
1849
2081
|
});
|
|
@@ -1852,22 +2084,29 @@ var AgentSyncEngine = class {
|
|
|
1852
2084
|
const checkStartedAt = performance.now();
|
|
1853
2085
|
const checkResult = await Promise.resolve(agent.checkForChanges(cacheTimestamp, baseline));
|
|
1854
2086
|
const checkDuration = performance.now() - checkStartedAt;
|
|
1855
|
-
this.
|
|
2087
|
+
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
1856
2088
|
if (!checkResult.hasChanges) {
|
|
1857
2089
|
this.logUnchangedRefresh(agent.name, refreshStartedAt);
|
|
1858
2090
|
return this.refreshStrategyResult(baseline, { status: "unchanged", checkDuration });
|
|
1859
2091
|
}
|
|
1860
2092
|
const preciseChangedIds = checkResult.changedIds ?? null;
|
|
1861
2093
|
const scanStartedAt = performance.now();
|
|
2094
|
+
if (preciseChangedIds === null) {
|
|
2095
|
+
const result = await this.runWorker(agent, baseline, null, {});
|
|
2096
|
+
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2097
|
+
const sessions2 = attachMissingProjectIdentities(result.sessions);
|
|
2098
|
+
return this.refreshStrategyResult(sessions2, {
|
|
2099
|
+
persistenceDiff: buildPersistenceDiff(baseline, sessions2),
|
|
2100
|
+
checkDuration,
|
|
2101
|
+
scanDuration: performance.now() - scanStartedAt
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1862
2104
|
const sessions = attachMissingProjectIdentities(
|
|
1863
|
-
await Promise.resolve(
|
|
1864
|
-
agent.incrementalScan(baseline, checkResult.changedIds ?? [], checkResult.refs)
|
|
1865
|
-
)
|
|
2105
|
+
await Promise.resolve(agent.incrementalScan(baseline, preciseChangedIds, checkResult.refs))
|
|
1866
2106
|
);
|
|
1867
2107
|
return this.refreshStrategyResult(sessions, {
|
|
1868
2108
|
preciseChangedIds,
|
|
1869
|
-
|
|
1870
|
-
persistenceDiff: buildRefreshDiff(agent.name, baseline, sessions, preciseChangedIds ?? []),
|
|
2109
|
+
persistenceDiff: buildPersistenceDiff(baseline, sessions, preciseChangedIds),
|
|
1871
2110
|
checkDuration,
|
|
1872
2111
|
scanDuration: performance.now() - scanStartedAt
|
|
1873
2112
|
});
|
|
@@ -1877,7 +2116,7 @@ var AgentSyncEngine = class {
|
|
|
1877
2116
|
const result = await this.runWorker(agent, previousSessions, null, {});
|
|
1878
2117
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1879
2118
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
1880
|
-
this.
|
|
2119
|
+
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
1881
2120
|
return this.refreshStrategyResult(sessions, {
|
|
1882
2121
|
fullScanSessions: sessions,
|
|
1883
2122
|
scanDuration: performance.now() - scanStartedAt
|
|
@@ -1889,7 +2128,6 @@ var AgentSyncEngine = class {
|
|
|
1889
2128
|
nextSessions,
|
|
1890
2129
|
fullScanSessions: null,
|
|
1891
2130
|
preciseChangedIds: null,
|
|
1892
|
-
usedIncrementalScan: false,
|
|
1893
2131
|
persistenceDiff: null,
|
|
1894
2132
|
checkDuration: 0,
|
|
1895
2133
|
scanDuration: 0,
|
|
@@ -1927,30 +2165,31 @@ var AgentSyncEngine = class {
|
|
|
1927
2165
|
if (!agentName) return;
|
|
1928
2166
|
this.currentBackfillAgent = agentName;
|
|
1929
2167
|
this.publishBackfillStatus();
|
|
1930
|
-
void this.
|
|
1931
|
-
(
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
if (
|
|
1935
|
-
|
|
1936
|
-
this.completedBackfillAgents.push(agentName);
|
|
1937
|
-
}
|
|
1938
|
-
this.failedBackfillAgents = this.failedBackfillAgents.filter(
|
|
1939
|
-
(failedAgent) => failedAgent !== agentName
|
|
1940
|
-
);
|
|
1941
|
-
} else if (!this.failedBackfillAgents.includes(agentName)) {
|
|
1942
|
-
this.failedBackfillAgents.push(agentName);
|
|
2168
|
+
void this.runBackfill(agentName).then((result) => {
|
|
2169
|
+
if (this.isShuttingDown) return;
|
|
2170
|
+
this.currentBackfillAgent = void 0;
|
|
2171
|
+
if (result === "committed") {
|
|
2172
|
+
if (!this.completedBackfillAgents.includes(agentName)) {
|
|
2173
|
+
this.completedBackfillAgents.push(agentName);
|
|
1943
2174
|
}
|
|
1944
|
-
this.
|
|
1945
|
-
|
|
2175
|
+
this.failedBackfillAgents = this.failedBackfillAgents.filter(
|
|
2176
|
+
(failedAgent) => failedAgent !== agentName
|
|
2177
|
+
);
|
|
2178
|
+
} else if (!this.failedBackfillAgents.includes(agentName)) {
|
|
2179
|
+
this.failedBackfillAgents.push(agentName);
|
|
1946
2180
|
}
|
|
1947
|
-
|
|
2181
|
+
this.publishBackfillStatus();
|
|
2182
|
+
this.pumpBackfillQueue();
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
runBackfill(agentName) {
|
|
2186
|
+
return this.scheduler.run(agentName, "backfill", () => this.performBackfill(agentName));
|
|
1948
2187
|
}
|
|
1949
2188
|
async performBackfill(agentName) {
|
|
1950
2189
|
const startedAt = performance.now();
|
|
1951
2190
|
const agent = this.findAgent(agentName);
|
|
1952
2191
|
if (!agent || !agent.isAvailable()) return "skipped";
|
|
1953
|
-
const snapshot = this.
|
|
2192
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1954
2193
|
const cached = loadCachedSessions(agentName);
|
|
1955
2194
|
const baseline = cached?.sessions ?? snapshot.byAgent[agentName] ?? [];
|
|
1956
2195
|
const meta = cached?.meta ?? buildAgentCacheMeta(agent);
|
|
@@ -1968,14 +2207,12 @@ var AgentSyncEngine = class {
|
|
|
1968
2207
|
);
|
|
1969
2208
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1970
2209
|
const fullSessions = attachMissingProjectIdentities(result.sessions);
|
|
1971
|
-
|
|
2210
|
+
await this.commitSessionPublication({
|
|
2211
|
+
context: "scan.backfill",
|
|
1972
2212
|
agentName,
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
);
|
|
1977
|
-
await this.searchIndexJobs.enqueue("scan.backfill", [
|
|
1978
|
-
{
|
|
2213
|
+
sessions: fullSessions,
|
|
2214
|
+
candidateChangedIds: result.changedIds ?? [],
|
|
2215
|
+
indexJob: {
|
|
1979
2216
|
kind: "full",
|
|
1980
2217
|
context: "scan.backfill",
|
|
1981
2218
|
agentName,
|
|
@@ -1983,9 +2220,8 @@ var AgentSyncEngine = class {
|
|
|
1983
2220
|
meta: buildAgentCacheMeta(agent),
|
|
1984
2221
|
saveCache: true
|
|
1985
2222
|
}
|
|
1986
|
-
|
|
2223
|
+
});
|
|
1987
2224
|
markAgentFullSyncCompleted(agentName);
|
|
1988
|
-
this.emitSessionsChanged({ agentName, sessions: fullSessions, event: diff.event });
|
|
1989
2225
|
appLogger.info("scan.backfill.done", {
|
|
1990
2226
|
agent: agentName,
|
|
1991
2227
|
duration_ms: Math.round(performance.now() - startedAt),
|
|
@@ -1999,70 +2235,6 @@ var AgentSyncEngine = class {
|
|
|
1999
2235
|
return "failed";
|
|
2000
2236
|
}
|
|
2001
2237
|
}
|
|
2002
|
-
serialize(agentName, kind, operation) {
|
|
2003
|
-
const previous = this.operationTails.get(agentName) ?? Promise.resolve();
|
|
2004
|
-
const run = previous.then(async () => {
|
|
2005
|
-
if (this.isShuttingDown) return "skipped";
|
|
2006
|
-
const lifecycle = this.beginOperation(agentName, kind);
|
|
2007
|
-
try {
|
|
2008
|
-
const result = await operation();
|
|
2009
|
-
this.completeOperation(lifecycle, result);
|
|
2010
|
-
return result;
|
|
2011
|
-
} catch (error) {
|
|
2012
|
-
this.completeOperation(lifecycle, "failed");
|
|
2013
|
-
throw error;
|
|
2014
|
-
}
|
|
2015
|
-
});
|
|
2016
|
-
const tail = run.then(
|
|
2017
|
-
() => void 0,
|
|
2018
|
-
() => void 0
|
|
2019
|
-
);
|
|
2020
|
-
this.operationTails.set(agentName, tail);
|
|
2021
|
-
void tail.finally(() => {
|
|
2022
|
-
if (this.operationTails.get(agentName) === tail) this.operationTails.delete(agentName);
|
|
2023
|
-
});
|
|
2024
|
-
return run;
|
|
2025
|
-
}
|
|
2026
|
-
state(agentName) {
|
|
2027
|
-
const existing = this.refreshStates.get(agentName);
|
|
2028
|
-
if (existing) return existing;
|
|
2029
|
-
const state = {
|
|
2030
|
-
timer: null,
|
|
2031
|
-
timerDeadline: 0,
|
|
2032
|
-
isRunning: false,
|
|
2033
|
-
hasPendingRerun: false,
|
|
2034
|
-
lastRefreshAt: 0,
|
|
2035
|
-
lastRefreshDurationMs: 0,
|
|
2036
|
-
pendingPathCount: 0,
|
|
2037
|
-
signatureCache: /* @__PURE__ */ new Map()
|
|
2038
|
-
};
|
|
2039
|
-
this.refreshStates.set(agentName, state);
|
|
2040
|
-
return state;
|
|
2041
|
-
}
|
|
2042
|
-
beginOperation(agentName, kind) {
|
|
2043
|
-
const generation = (this.operationGenerations.get(agentName) ?? 0) + 1;
|
|
2044
|
-
const startedAt = Date.now();
|
|
2045
|
-
this.operationGenerations.set(agentName, generation);
|
|
2046
|
-
appLogger.info("scan.agent_operation.started", {
|
|
2047
|
-
agent: agentName,
|
|
2048
|
-
operation: kind,
|
|
2049
|
-
generation,
|
|
2050
|
-
started_at: startedAt
|
|
2051
|
-
});
|
|
2052
|
-
return { agentName, kind, generation, startedAt };
|
|
2053
|
-
}
|
|
2054
|
-
completeOperation(lifecycle, result) {
|
|
2055
|
-
const completedAt = Date.now();
|
|
2056
|
-
appLogger.info("scan.agent_operation.completed", {
|
|
2057
|
-
agent: lifecycle.agentName,
|
|
2058
|
-
operation: lifecycle.kind,
|
|
2059
|
-
generation: lifecycle.generation,
|
|
2060
|
-
started_at: lifecycle.startedAt,
|
|
2061
|
-
completed_at: completedAt,
|
|
2062
|
-
duration_ms: completedAt - lifecycle.startedAt,
|
|
2063
|
-
result
|
|
2064
|
-
});
|
|
2065
|
-
}
|
|
2066
2238
|
backfillStatus() {
|
|
2067
2239
|
return {
|
|
2068
2240
|
active: this.currentBackfillAgent != null || this.backfillQueue.length > 0,
|
|
@@ -2073,7 +2245,7 @@ var AgentSyncEngine = class {
|
|
|
2073
2245
|
};
|
|
2074
2246
|
}
|
|
2075
2247
|
buildFullSearchIndexJobs(context) {
|
|
2076
|
-
const snapshot = this.
|
|
2248
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
2077
2249
|
return snapshot.agents.map((agent) => {
|
|
2078
2250
|
const cached = loadCachedSessions(agent.name);
|
|
2079
2251
|
return cached ? {
|
|
@@ -2091,8 +2263,65 @@ var AgentSyncEngine = class {
|
|
|
2091
2263
|
};
|
|
2092
2264
|
});
|
|
2093
2265
|
}
|
|
2266
|
+
publicationId(context, agentName) {
|
|
2267
|
+
const id = this.nextPublicationId++;
|
|
2268
|
+
return agentName ? `${context}:${agentName}:${id}` : `${context}:${id}`;
|
|
2269
|
+
}
|
|
2270
|
+
async commitSearchIndex(context, jobs, details) {
|
|
2271
|
+
appLogger.info("session.publication.prepared", {
|
|
2272
|
+
publication_id: details.publicationId,
|
|
2273
|
+
context,
|
|
2274
|
+
agent: details.agent,
|
|
2275
|
+
agents: details.agents,
|
|
2276
|
+
jobs: jobs.length
|
|
2277
|
+
});
|
|
2278
|
+
try {
|
|
2279
|
+
await this.searchIndexJobs.enqueue(context, jobs);
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
appLogger.error("session.publication.failed", {
|
|
2282
|
+
publication_id: details.publicationId,
|
|
2283
|
+
context,
|
|
2284
|
+
agent: details.agent,
|
|
2285
|
+
stage: "search_index",
|
|
2286
|
+
error
|
|
2287
|
+
});
|
|
2288
|
+
throw error;
|
|
2289
|
+
}
|
|
2290
|
+
appLogger.info("session.publication.index_committed", {
|
|
2291
|
+
publication_id: details.publicationId,
|
|
2292
|
+
context,
|
|
2293
|
+
agent: details.agent
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
async commitSessionPublication(publication) {
|
|
2297
|
+
const publicationId = this.publicationId(publication.context, publication.agentName);
|
|
2298
|
+
await this.commitSearchIndex(publication.context, [publication.indexJob], {
|
|
2299
|
+
publicationId,
|
|
2300
|
+
agent: publication.agentName
|
|
2301
|
+
});
|
|
2302
|
+
const diffStartedAt = performance.now();
|
|
2303
|
+
const event = this.sessionIndex.commitAgentSessions(
|
|
2304
|
+
publication.agentName,
|
|
2305
|
+
publication.sessions,
|
|
2306
|
+
publication.candidateChangedIds
|
|
2307
|
+
);
|
|
2308
|
+
const diffDuration = performance.now() - diffStartedAt;
|
|
2309
|
+
this.emitSessionsChanged({
|
|
2310
|
+
agentName: publication.agentName,
|
|
2311
|
+
sessions: this.sessionIndex.snapshot().byAgent[publication.agentName] ?? [],
|
|
2312
|
+
event
|
|
2313
|
+
});
|
|
2314
|
+
appLogger.info("session.publication.published", {
|
|
2315
|
+
publication_id: publicationId,
|
|
2316
|
+
context: publication.context,
|
|
2317
|
+
agent: publication.agentName,
|
|
2318
|
+
sessions: publication.sessions.length,
|
|
2319
|
+
has_event: event != null
|
|
2320
|
+
});
|
|
2321
|
+
return { event, diffDuration };
|
|
2322
|
+
}
|
|
2094
2323
|
findAgent(agentName) {
|
|
2095
|
-
return this.
|
|
2324
|
+
return this.sessionIndex.findAgent(agentName);
|
|
2096
2325
|
}
|
|
2097
2326
|
startupScanOptions() {
|
|
2098
2327
|
return this.options.startupScanOptions ?? {};
|
|
@@ -2167,49 +2396,6 @@ function resolveWatchEventPath(watchPath, filename) {
|
|
|
2167
2396
|
}
|
|
2168
2397
|
return isAbsolute(filenameText) ? filenameText : join(watchPath, filenameText);
|
|
2169
2398
|
}
|
|
2170
|
-
function resolveAgentWatchTargets(agentName) {
|
|
2171
|
-
const roots = resolveProviderRoots();
|
|
2172
|
-
const cursorDataPath = getCursorDataPath();
|
|
2173
|
-
switch (agentName) {
|
|
2174
|
-
case "claudecode":
|
|
2175
|
-
return [
|
|
2176
|
-
{ root: roots.claudeRoot, path: join(roots.claudeRoot, "projects") },
|
|
2177
|
-
{ path: "data/claudecode" }
|
|
2178
|
-
];
|
|
2179
|
-
case "codex":
|
|
2180
|
-
return [
|
|
2181
|
-
{ path: join(roots.codexRoot, "sessions") },
|
|
2182
|
-
{ path: join(roots.codexRoot, "session_index.jsonl") }
|
|
2183
|
-
];
|
|
2184
|
-
case "pi":
|
|
2185
|
-
return [
|
|
2186
|
-
{ root: roots.piRoot, path: join(roots.piRoot, "agent", "sessions") },
|
|
2187
|
-
{ root: "data/pi", path: "data/pi" }
|
|
2188
|
-
];
|
|
2189
|
-
case "cursor":
|
|
2190
|
-
return cursorDataPath ? [
|
|
2191
|
-
{
|
|
2192
|
-
root: cursorDataPath,
|
|
2193
|
-
path: join(cursorDataPath, "globalStorage", "state.vscdb")
|
|
2194
|
-
},
|
|
2195
|
-
{ root: cursorDataPath, path: join(cursorDataPath, "workspaceStorage") }
|
|
2196
|
-
] : [];
|
|
2197
|
-
case "kimi":
|
|
2198
|
-
return [
|
|
2199
|
-
{ root: roots.kimiRoot, path: join(roots.kimiRoot, "sessions") },
|
|
2200
|
-
{ path: "data/kimi" }
|
|
2201
|
-
];
|
|
2202
|
-
case "opencode":
|
|
2203
|
-
return [
|
|
2204
|
-
{ root: roots.opencodeRoot, path: join(roots.opencodeRoot, "opencode.db") },
|
|
2205
|
-
{ root: "data/opencode", path: "data/opencode/opencode.db" }
|
|
2206
|
-
];
|
|
2207
|
-
case "zcode":
|
|
2208
|
-
return roots.zcodeRoot ? [{ root: roots.zcodeRoot, path: join(roots.zcodeRoot, "cli", "db", "db.sqlite") }] : [];
|
|
2209
|
-
default:
|
|
2210
|
-
return [];
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
2399
|
var SessionWatcher = class {
|
|
2214
2400
|
watchers = [];
|
|
2215
2401
|
fallbackWatchScopes = /* @__PURE__ */ new Map();
|
|
@@ -2222,16 +2408,24 @@ var SessionWatcher = class {
|
|
|
2222
2408
|
this.listeners.delete(cb);
|
|
2223
2409
|
};
|
|
2224
2410
|
}
|
|
2225
|
-
/** Begin watching the
|
|
2226
|
-
start(
|
|
2411
|
+
/** Begin watching the session sources declared by each agent adapter. */
|
|
2412
|
+
start(agents) {
|
|
2227
2413
|
const scopesByRoot = /* @__PURE__ */ new Map();
|
|
2228
|
-
for (const
|
|
2229
|
-
const
|
|
2230
|
-
if (
|
|
2231
|
-
appLogger.debug("watch.skip", {
|
|
2414
|
+
for (const agent of agents) {
|
|
2415
|
+
const plan = agent.getSessionWatchPlan();
|
|
2416
|
+
if (plan.status !== "supported") {
|
|
2417
|
+
appLogger.debug("watch.skip", {
|
|
2418
|
+
agent: agent.name,
|
|
2419
|
+
status: plan.status,
|
|
2420
|
+
reason: plan.reason
|
|
2421
|
+
});
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
if (plan.targets.length === 0) {
|
|
2425
|
+
appLogger.debug("watch.skip", { agent: agent.name, status: plan.status });
|
|
2232
2426
|
continue;
|
|
2233
2427
|
}
|
|
2234
|
-
for (const target of
|
|
2428
|
+
for (const target of plan.targets) {
|
|
2235
2429
|
const watchRootPath = closestWatchablePath(target.root ?? target.path);
|
|
2236
2430
|
if (!watchRootPath) continue;
|
|
2237
2431
|
let rootPath;
|
|
@@ -2243,17 +2437,17 @@ var SessionWatcher = class {
|
|
|
2243
2437
|
}
|
|
2244
2438
|
const targetPath = toAbsolutePath(target.path);
|
|
2245
2439
|
const scopes = scopesByRoot.get(rootPath) ?? [];
|
|
2246
|
-
if (!scopes.some((scope) => scope.agentName ===
|
|
2247
|
-
scopes.push({ agentName, targetPath });
|
|
2440
|
+
if (!scopes.some((scope) => scope.agentName === agent.name && scope.targetPath === targetPath)) {
|
|
2441
|
+
scopes.push({ agentName: agent.name, targetPath });
|
|
2248
2442
|
}
|
|
2249
2443
|
scopesByRoot.set(rootPath, scopes);
|
|
2250
2444
|
}
|
|
2251
2445
|
}
|
|
2252
2446
|
for (const [rootPath, scopes] of scopesByRoot.entries()) {
|
|
2253
|
-
const
|
|
2447
|
+
const agents2 = Array.from(new Set(scopes.map((scope) => scope.agentName)));
|
|
2254
2448
|
appLogger.info("watch.start", {
|
|
2255
2449
|
root: rootPath,
|
|
2256
|
-
agents,
|
|
2450
|
+
agents: agents2,
|
|
2257
2451
|
targets: scopes.map((scope) => ({
|
|
2258
2452
|
agent: scope.agentName,
|
|
2259
2453
|
path: scope.targetPath
|
|
@@ -2425,71 +2619,154 @@ var SessionWatcher = class {
|
|
|
2425
2619
|
|
|
2426
2620
|
// src/worker-runner.ts
|
|
2427
2621
|
import { Worker as Worker2 } from "worker_threads";
|
|
2622
|
+
var SHUTDOWN_ERROR_MESSAGE2 = "Scan refresh worker shut down";
|
|
2623
|
+
function applySessionChanges2(previousSessions, changes, removedSessionIds) {
|
|
2624
|
+
const replacedIds = new Set(removedSessionIds);
|
|
2625
|
+
for (const { session } of changes) replacedIds.add(session.id);
|
|
2626
|
+
const retained = previousSessions.filter((session) => !replacedIds.has(session.id));
|
|
2627
|
+
const next = Array.from({
|
|
2628
|
+
length: retained.length + changes.length
|
|
2629
|
+
});
|
|
2630
|
+
for (const { session, sortIndex } of changes) {
|
|
2631
|
+
if (sortIndex < 0 || sortIndex >= next.length || next[sortIndex]) {
|
|
2632
|
+
throw new Error(`Invalid scan refresh sort index: ${sortIndex}`);
|
|
2633
|
+
}
|
|
2634
|
+
next[sortIndex] = session;
|
|
2635
|
+
}
|
|
2636
|
+
let retainedIndex = 0;
|
|
2637
|
+
for (let index = 0; index < next.length; index += 1) {
|
|
2638
|
+
if (!next[index]) next[index] = retained[retainedIndex++];
|
|
2639
|
+
}
|
|
2640
|
+
if (retainedIndex !== retained.length || next.some((session) => !session)) {
|
|
2641
|
+
throw new Error("Invalid scan refresh delta");
|
|
2642
|
+
}
|
|
2643
|
+
return next;
|
|
2644
|
+
}
|
|
2645
|
+
function applyMetaChanges(previous, changed, replacedSessionIds) {
|
|
2646
|
+
const next = { ...previous };
|
|
2647
|
+
for (const id of replacedSessionIds) delete next[id];
|
|
2648
|
+
return Object.assign(next, changed);
|
|
2649
|
+
}
|
|
2428
2650
|
var ThreadWorkerRunner = class {
|
|
2429
2651
|
constructor(workerUrl) {
|
|
2430
2652
|
this.workerUrl = workerUrl;
|
|
2431
2653
|
}
|
|
2432
2654
|
workerUrl;
|
|
2433
|
-
workers = /* @__PURE__ */ new
|
|
2655
|
+
workers = /* @__PURE__ */ new Map();
|
|
2656
|
+
nextRequestId = 1;
|
|
2657
|
+
isShuttingDown = false;
|
|
2434
2658
|
get activeCount() {
|
|
2435
|
-
|
|
2659
|
+
let count = 0;
|
|
2660
|
+
for (const slot of this.workers.values()) count += slot.pending.size;
|
|
2661
|
+
return count;
|
|
2436
2662
|
}
|
|
2437
2663
|
run(agentName, payload) {
|
|
2664
|
+
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE2));
|
|
2665
|
+
const request = {
|
|
2666
|
+
type: "run",
|
|
2667
|
+
requestId: this.nextRequestId++,
|
|
2668
|
+
agentName,
|
|
2669
|
+
previousSessions: payload.previousSessions,
|
|
2670
|
+
changedIds: payload.changedIds,
|
|
2671
|
+
sourceSync: payload.sourceSync,
|
|
2672
|
+
scanOptions: payload.scanOptions,
|
|
2673
|
+
meta: payload.meta
|
|
2674
|
+
};
|
|
2438
2675
|
return new Promise((resolve4, reject) => {
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
meta: payload.meta
|
|
2447
|
-
}
|
|
2448
|
-
});
|
|
2449
|
-
worker.unref();
|
|
2450
|
-
this.workers.add(worker);
|
|
2451
|
-
let settled = false;
|
|
2452
|
-
const finish = (callback, terminate = true) => {
|
|
2453
|
-
if (settled) return;
|
|
2454
|
-
settled = true;
|
|
2455
|
-
this.workers.delete(worker);
|
|
2456
|
-
if (terminate) void worker.terminate();
|
|
2457
|
-
callback();
|
|
2458
|
-
};
|
|
2459
|
-
worker.on("message", (message) => {
|
|
2460
|
-
if (message.type === "progress") {
|
|
2461
|
-
payload.onProgress?.(message.progress);
|
|
2462
|
-
return;
|
|
2463
|
-
}
|
|
2464
|
-
if (message.type === "done") {
|
|
2465
|
-
finish(
|
|
2466
|
-
() => resolve4({
|
|
2467
|
-
sessions: message.sessions,
|
|
2468
|
-
meta: message.meta,
|
|
2469
|
-
changedIds: message.changedIds
|
|
2470
|
-
})
|
|
2471
|
-
);
|
|
2676
|
+
let slot = this.workers.get(agentName);
|
|
2677
|
+
const isNewWorker = !slot;
|
|
2678
|
+
if (!slot) {
|
|
2679
|
+
try {
|
|
2680
|
+
slot = this.createWorker(agentName, request);
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2472
2683
|
return;
|
|
2473
2684
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
if (settled) return;
|
|
2481
|
-
appLogger.warn("scan.refresh_worker.exit_before_done", { agent: agentName, code });
|
|
2482
|
-
finish(
|
|
2483
|
-
() => reject(new Error(`Scan refresh worker exited before completing (code ${code})`)),
|
|
2484
|
-
false
|
|
2485
|
-
);
|
|
2685
|
+
}
|
|
2686
|
+
slot.pending.set(request.requestId, {
|
|
2687
|
+
resolve: resolve4,
|
|
2688
|
+
reject,
|
|
2689
|
+
payload,
|
|
2690
|
+
onProgress: payload.onProgress
|
|
2486
2691
|
});
|
|
2692
|
+
if (!isNewWorker) {
|
|
2693
|
+
try {
|
|
2694
|
+
slot.worker.postMessage(request);
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
slot.pending.delete(request.requestId);
|
|
2697
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2487
2700
|
});
|
|
2488
2701
|
}
|
|
2489
2702
|
async shutdown() {
|
|
2490
|
-
|
|
2491
|
-
|
|
2703
|
+
this.isShuttingDown = true;
|
|
2704
|
+
const slots = [...this.workers.values()];
|
|
2492
2705
|
this.workers.clear();
|
|
2706
|
+
const shutdownError = new Error(SHUTDOWN_ERROR_MESSAGE2);
|
|
2707
|
+
for (const slot of slots) {
|
|
2708
|
+
slot.closed = true;
|
|
2709
|
+
for (const pending of slot.pending.values()) pending.reject(shutdownError);
|
|
2710
|
+
slot.pending.clear();
|
|
2711
|
+
}
|
|
2712
|
+
await Promise.allSettled(slots.map((slot) => slot.worker.terminate()));
|
|
2713
|
+
}
|
|
2714
|
+
createWorker(agentName, request) {
|
|
2715
|
+
const worker = new Worker2(this.workerUrl, { workerData: request });
|
|
2716
|
+
const slot = { worker, pending: /* @__PURE__ */ new Map(), closed: false };
|
|
2717
|
+
worker.unref();
|
|
2718
|
+
this.workers.set(agentName, slot);
|
|
2719
|
+
worker.on("message", (message) => {
|
|
2720
|
+
this.handleMessage(slot, message);
|
|
2721
|
+
});
|
|
2722
|
+
worker.on("error", (error) => {
|
|
2723
|
+
this.closeWorker(agentName, slot, error);
|
|
2724
|
+
});
|
|
2725
|
+
worker.on("exit", (code) => {
|
|
2726
|
+
if (slot.closed) return;
|
|
2727
|
+
const error = new Error(`Scan refresh worker exited before completing (code ${code})`);
|
|
2728
|
+
if (slot.pending.size > 0) {
|
|
2729
|
+
appLogger.warn("scan.refresh_worker.exit_before_done", { agent: agentName, code });
|
|
2730
|
+
}
|
|
2731
|
+
this.closeWorker(agentName, slot, error);
|
|
2732
|
+
});
|
|
2733
|
+
return slot;
|
|
2734
|
+
}
|
|
2735
|
+
handleMessage(slot, message) {
|
|
2736
|
+
const pending = slot.pending.get(message.requestId);
|
|
2737
|
+
if (!pending) return;
|
|
2738
|
+
if (message.type === "progress") {
|
|
2739
|
+
pending.onProgress?.(message.progress);
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
slot.pending.delete(message.requestId);
|
|
2743
|
+
if (message.type === "error") {
|
|
2744
|
+
pending.reject(new Error(message.error));
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const changedIds = message.changes.map(({ session }) => session.id);
|
|
2748
|
+
const replacedSessionIds = [...changedIds, ...message.removedSessionIds];
|
|
2749
|
+
const removedMetaIds = [...message.removedSessionIds, ...message.removedMetaIds];
|
|
2750
|
+
try {
|
|
2751
|
+
pending.resolve({
|
|
2752
|
+
sessions: applySessionChanges2(
|
|
2753
|
+
pending.payload.previousSessions,
|
|
2754
|
+
message.changes,
|
|
2755
|
+
message.removedSessionIds
|
|
2756
|
+
),
|
|
2757
|
+
meta: applyMetaChanges(pending.payload.meta, message.meta, removedMetaIds),
|
|
2758
|
+
changedIds: pending.payload.sourceSync ? replacedSessionIds : void 0
|
|
2759
|
+
});
|
|
2760
|
+
} catch (error) {
|
|
2761
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
closeWorker(agentName, slot, error) {
|
|
2765
|
+
if (slot.closed) return;
|
|
2766
|
+
slot.closed = true;
|
|
2767
|
+
if (this.workers.get(agentName) === slot) this.workers.delete(agentName);
|
|
2768
|
+
for (const pending of slot.pending.values()) pending.reject(error);
|
|
2769
|
+
slot.pending.clear();
|
|
2493
2770
|
}
|
|
2494
2771
|
};
|
|
2495
2772
|
|
|
@@ -2500,7 +2777,7 @@ function mergeEvents(previous, next) {
|
|
|
2500
2777
|
const removedSessionRefs = /* @__PURE__ */ new Map();
|
|
2501
2778
|
const sessionKey = (agentName, sessionId) => `${agentName}\0${sessionId}`;
|
|
2502
2779
|
const addChanged = (item) => {
|
|
2503
|
-
const key = sessionKey(item.agentName, item.
|
|
2780
|
+
const key = sessionKey(item.reference.agentName, item.reference.sessionId);
|
|
2504
2781
|
removedSessionRefs.delete(key);
|
|
2505
2782
|
changedSessionHeads.set(key, item);
|
|
2506
2783
|
};
|
|
@@ -2531,9 +2808,6 @@ var LiveScanStore = class {
|
|
|
2531
2808
|
startupScanOptions;
|
|
2532
2809
|
deferInitialRefresh;
|
|
2533
2810
|
syncEngine;
|
|
2534
|
-
agents = [];
|
|
2535
|
-
byAgent = {};
|
|
2536
|
-
sessions = [];
|
|
2537
2811
|
listeners = /* @__PURE__ */ new Set();
|
|
2538
2812
|
watcher = null;
|
|
2539
2813
|
pendingEvent = null;
|
|
@@ -2547,11 +2821,12 @@ var LiveScanStore = class {
|
|
|
2547
2821
|
this.deferInitialRefresh = options.deferInitialRefresh === true;
|
|
2548
2822
|
const workerRunner = options.workerRunner ?? new ThreadWorkerRunner(new URL("./scan-refresh-worker.js", import.meta.url));
|
|
2549
2823
|
this.syncEngine = new AgentSyncEngine({
|
|
2550
|
-
snapshot: () => this.getSnapshot(),
|
|
2551
2824
|
startupScanOptions: this.startupScanOptions,
|
|
2552
2825
|
workerRunner
|
|
2553
2826
|
});
|
|
2554
|
-
this.syncEngine.subscribeSessionsChanged((change) =>
|
|
2827
|
+
this.syncEngine.subscribeSessionsChanged((change) => {
|
|
2828
|
+
if (change.event) this.emit(change.event);
|
|
2829
|
+
});
|
|
2555
2830
|
}
|
|
2556
2831
|
async initialize() {
|
|
2557
2832
|
const startedAt = performance.now();
|
|
@@ -2572,8 +2847,12 @@ var LiveScanStore = class {
|
|
|
2572
2847
|
smartTagWorkerUrl: this.getSmartTagWorkerUrl() ?? void 0,
|
|
2573
2848
|
includeSmartTags: this.deferInitialRefresh ? false : void 0
|
|
2574
2849
|
});
|
|
2575
|
-
this.
|
|
2576
|
-
|
|
2850
|
+
this.syncEngine.initialize(initialResult, {
|
|
2851
|
+
cacheTimestamps: initialResult.cacheTimestamps,
|
|
2852
|
+
registeredAgents: createRegisteredAgents(),
|
|
2853
|
+
allowedAgents: this.getAllowedAgents()
|
|
2854
|
+
});
|
|
2855
|
+
const snapshot = this.getSnapshot();
|
|
2577
2856
|
const indexStartedAt = performance.now();
|
|
2578
2857
|
if (!this.deferInitialRefresh) await this.syncEngine.syncInitialIndex();
|
|
2579
2858
|
const indexDuration = performance.now() - indexStartedAt;
|
|
@@ -2581,9 +2860,9 @@ var LiveScanStore = class {
|
|
|
2581
2860
|
duration_ms: Math.round(performance.now() - startedAt),
|
|
2582
2861
|
index_ms: this.deferInitialRefresh ? void 0 : Math.round(indexDuration),
|
|
2583
2862
|
deferred: this.deferInitialRefresh || void 0,
|
|
2584
|
-
sessions:
|
|
2863
|
+
sessions: snapshot.sessions.length,
|
|
2585
2864
|
agents: Object.fromEntries(
|
|
2586
|
-
Object.entries(
|
|
2865
|
+
Object.entries(snapshot.byAgent).map(([key, value]) => [key, value.length])
|
|
2587
2866
|
),
|
|
2588
2867
|
agent_timings: initialResult.timings ? Object.fromEntries(
|
|
2589
2868
|
Object.entries(initialResult.timings).map(([name, timing]) => [
|
|
@@ -2602,13 +2881,13 @@ var LiveScanStore = class {
|
|
|
2602
2881
|
if (!this.watchEnabled) return;
|
|
2603
2882
|
this.watcher = new SessionWatcher();
|
|
2604
2883
|
this.watcher.onAgentsChanged((agentNames) => this.syncEngine.handleAgentsChanged(agentNames));
|
|
2605
|
-
this.watcher.start(
|
|
2884
|
+
this.watcher.start(snapshot.agents);
|
|
2606
2885
|
}
|
|
2607
2886
|
startBackgroundRefresh() {
|
|
2608
2887
|
this.syncEngine.startBackgroundRefresh();
|
|
2609
2888
|
}
|
|
2610
2889
|
getSnapshot() {
|
|
2611
|
-
return
|
|
2890
|
+
return this.syncEngine.snapshot();
|
|
2612
2891
|
}
|
|
2613
2892
|
getScanStatus() {
|
|
2614
2893
|
return this.syncEngine.status();
|
|
@@ -2637,13 +2916,6 @@ var LiveScanStore = class {
|
|
|
2637
2916
|
this.watcher = null;
|
|
2638
2917
|
}
|
|
2639
2918
|
}
|
|
2640
|
-
applySessionsChanged(change) {
|
|
2641
|
-
this.byAgent[change.agentName] = sortSessions(change.sessions);
|
|
2642
|
-
this.rebuildSessions();
|
|
2643
|
-
if (!change.event) return;
|
|
2644
|
-
change.event.totalSessions = this.sessions.length;
|
|
2645
|
-
this.emit(change.event);
|
|
2646
|
-
}
|
|
2647
2919
|
emit(event) {
|
|
2648
2920
|
if (this.shuttingDown) return;
|
|
2649
2921
|
if (this.pendingEvent || event.newSessions > 0) {
|
|
@@ -2665,29 +2937,11 @@ var LiveScanStore = class {
|
|
|
2665
2937
|
if (pending) this.emitNow(pending);
|
|
2666
2938
|
}, NEW_SESSION_EVENT_WINDOW_MS);
|
|
2667
2939
|
}
|
|
2668
|
-
rebuildSessions() {
|
|
2669
|
-
this.sessions = sortSessions(Object.values(this.byAgent).flat());
|
|
2670
|
-
}
|
|
2671
2940
|
getSmartTagWorkerUrl() {
|
|
2672
2941
|
const workerUrl = new URL("./smart-tag-worker.js", import.meta.url);
|
|
2673
2942
|
if (workerUrl.protocol === "file:" && !existsSync4(fileURLToPath3(workerUrl))) return null;
|
|
2674
2943
|
return workerUrl;
|
|
2675
2944
|
}
|
|
2676
|
-
applyScanResult(result) {
|
|
2677
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
2678
|
-
const allowedAgents = this.getAllowedAgents();
|
|
2679
|
-
for (const agent of result.agents) agentMap.set(agent.name, agent);
|
|
2680
|
-
for (const agent of createRegisteredAgents()) {
|
|
2681
|
-
if (!agentMap.has(agent.name)) agentMap.set(agent.name, agent);
|
|
2682
|
-
}
|
|
2683
|
-
this.agents = [...agentMap.values()].filter(
|
|
2684
|
-
(agent) => !allowedAgents || allowedAgents.has(agent.name.toLowerCase())
|
|
2685
|
-
);
|
|
2686
|
-
this.byAgent = Object.fromEntries(
|
|
2687
|
-
this.agents.map((agent) => [agent.name, sortSessions(result.byAgent[agent.name] ?? [])])
|
|
2688
|
-
);
|
|
2689
|
-
this.rebuildSessions();
|
|
2690
|
-
}
|
|
2691
2945
|
getAllowedAgents() {
|
|
2692
2946
|
if (!this.scanOptions.agents?.length) return null;
|
|
2693
2947
|
return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
|
|
@@ -2883,7 +3137,7 @@ var main = defineCommand({
|
|
|
2883
3137
|
log_path: appLogger.getLogPath()
|
|
2884
3138
|
});
|
|
2885
3139
|
if (clearCache) {
|
|
2886
|
-
const { clearCache: clear } = await import("./dist-
|
|
3140
|
+
const { clearCache: clear } = await import("./dist-KEPJFHOC.js");
|
|
2887
3141
|
clear();
|
|
2888
3142
|
appLogger.info("cache.clear");
|
|
2889
3143
|
console.log("Cache cleared.");
|