codesesh 0.15.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/README.md +1 -0
- package/dist/{chunk-NBCLV4CX.js → chunk-7APNDHQ6.js} +2148 -1298
- package/dist/chunk-7APNDHQ6.js.map +1 -0
- package/dist/chunk-A4U2SMJJ.js +169 -0
- package/dist/chunk-A4U2SMJJ.js.map +1 -0
- package/dist/dist-KEPJFHOC.js +115 -0
- package/dist/index.js +1052 -906
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +70 -63
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +11 -7
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +2 -1
- package/dist/smart-tag-worker.js.map +1 -1
- 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/{syntax-C_RLNYQn.js → syntax-mZXQuVcd.js} +1 -1
- package/dist/web/assets/vendor-i9U7_fNF.js +41 -0
- package/dist/web/icon/agent/cursor.svg +1 -12
- package/dist/web/icon/agent/pi.svg +2 -2
- package/dist/web/index.html +56 -5
- package/package.json +5 -10
- package/dist/chunk-NBCLV4CX.js.map +0 -1
- package/dist/dist-5356XOFP.js +0 -219
- package/dist/web/assets/index-B6p64k_Q.js +0 -113
- package/dist/web/assets/index-DiFr4_5-.css +0 -2
- package/dist/web/assets/react-3acXkZNw.js +0 -1821
- package/dist/web/assets/vendor-CShgXaOi.js +0 -40
- package/dist/web/icon/agent/antigravity.svg +0 -1
- package/dist/web/icon/agent/kilocode.svg +0 -1
- /package/dist/{dist-5356XOFP.js.map → dist-KEPJFHOC.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
appLogger,
|
|
4
|
+
logSearchIndexSync
|
|
5
|
+
} from "./chunk-A4U2SMJJ.js";
|
|
2
6
|
import {
|
|
3
7
|
FileSystemSessionSource,
|
|
8
|
+
SessionAliasValidationError,
|
|
4
9
|
StateStorageUnavailableError,
|
|
5
10
|
attachMissingProjectIdentities,
|
|
11
|
+
attachProjectMetrics,
|
|
6
12
|
buildAgentCacheMeta,
|
|
7
13
|
buildDashboard,
|
|
8
|
-
classifySessionTags,
|
|
9
|
-
computeIdentity,
|
|
10
14
|
computeSessionDiff,
|
|
11
15
|
createProjectScopeMatcher,
|
|
12
16
|
createRegisteredAgents,
|
|
13
17
|
deleteBookmark,
|
|
14
18
|
deleteSessionAlias,
|
|
15
19
|
executeSessionSearch,
|
|
16
|
-
|
|
20
|
+
filterSessionSearchCandidates,
|
|
21
|
+
formatSessionReference,
|
|
17
22
|
getAgentInfoMap,
|
|
18
23
|
getAgentLastFullSyncAt,
|
|
19
|
-
getCursorDataPath,
|
|
20
24
|
getSessionActivityTime,
|
|
21
|
-
|
|
22
|
-
getSmartTagSourceTimestamp,
|
|
23
|
-
getTotalTokens,
|
|
25
|
+
getSessionAgentKey,
|
|
24
26
|
importBookmarks,
|
|
25
27
|
isAgentCacheInitialized,
|
|
26
28
|
isProjectIdentityKind,
|
|
@@ -28,24 +30,23 @@ import {
|
|
|
28
30
|
listCachedProjectGroups,
|
|
29
31
|
listFileActivity,
|
|
30
32
|
listSessionAliases,
|
|
31
|
-
listSessionFileActivity,
|
|
32
|
-
loadCachedSessionData,
|
|
33
33
|
loadCachedSessions,
|
|
34
34
|
markAgentFullSyncCompleted,
|
|
35
35
|
matchesProjectIdentity,
|
|
36
36
|
matchesProjectScope,
|
|
37
|
+
materializeSessionDetailResponse,
|
|
37
38
|
mergeSearchQueryOptions,
|
|
39
|
+
mergeSortedSessions,
|
|
40
|
+
normalizeSessionReference,
|
|
38
41
|
perf,
|
|
39
|
-
realFs,
|
|
40
42
|
refreshPricingCache,
|
|
41
|
-
resolveProviderRoots,
|
|
42
43
|
scanSessions,
|
|
43
44
|
sessionSignature,
|
|
44
45
|
sortSessions,
|
|
45
46
|
startOfLocalDay,
|
|
46
47
|
upsertBookmark,
|
|
47
48
|
upsertSessionAlias
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-7APNDHQ6.js";
|
|
49
50
|
|
|
50
51
|
// src/index.ts
|
|
51
52
|
import { defineCommand, runMain } from "citty";
|
|
@@ -53,164 +54,16 @@ import { defineCommand, runMain } from "citty";
|
|
|
53
54
|
// src/server.ts
|
|
54
55
|
import { Hono as Hono2 } from "hono";
|
|
55
56
|
import { bodyLimit } from "hono/body-limit";
|
|
57
|
+
import { compress } from "hono/compress";
|
|
56
58
|
import { serve } from "@hono/node-server";
|
|
57
59
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
58
|
-
import { existsSync
|
|
60
|
+
import { existsSync } from "fs";
|
|
59
61
|
import { resolve, dirname } from "path";
|
|
60
62
|
import { fileURLToPath } from "url";
|
|
61
63
|
|
|
62
64
|
// src/api/routes.ts
|
|
63
65
|
import { Hono } from "hono";
|
|
64
66
|
|
|
65
|
-
// src/logging.ts
|
|
66
|
-
import {
|
|
67
|
-
appendFileSync,
|
|
68
|
-
existsSync,
|
|
69
|
-
mkdirSync,
|
|
70
|
-
readdirSync,
|
|
71
|
-
renameSync,
|
|
72
|
-
statSync,
|
|
73
|
-
unlinkSync
|
|
74
|
-
} from "fs";
|
|
75
|
-
import { homedir } from "os";
|
|
76
|
-
import { join } from "path";
|
|
77
|
-
var LEVEL_WEIGHT = {
|
|
78
|
-
debug: 10,
|
|
79
|
-
info: 20,
|
|
80
|
-
warn: 30,
|
|
81
|
-
error: 40
|
|
82
|
-
};
|
|
83
|
-
function parseLevel(value) {
|
|
84
|
-
if (value === "debug" || value === "info" || value === "warn" || value === "error") {
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
return "info";
|
|
88
|
-
}
|
|
89
|
-
function parsePositiveInt(value, fallback) {
|
|
90
|
-
const parsed = Number(value);
|
|
91
|
-
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
92
|
-
}
|
|
93
|
-
function getDefaultLogDir() {
|
|
94
|
-
const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
95
|
-
return join(base, "codesesh", "logs");
|
|
96
|
-
}
|
|
97
|
-
function toLogValue(value, depth = 0) {
|
|
98
|
-
if (value == null || typeof value === "string" || typeof value === "number") return value;
|
|
99
|
-
if (typeof value === "boolean") return value;
|
|
100
|
-
if (typeof value === "bigint") return value.toString();
|
|
101
|
-
if (value instanceof Error) {
|
|
102
|
-
return {
|
|
103
|
-
name: value.name,
|
|
104
|
-
message: value.message,
|
|
105
|
-
stack: value.stack
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
if (depth >= 4) return "[truncated]";
|
|
109
|
-
if (Array.isArray(value)) return value.slice(0, 50).map((item) => toLogValue(item, depth + 1));
|
|
110
|
-
if (typeof value === "object") {
|
|
111
|
-
return Object.fromEntries(
|
|
112
|
-
Object.entries(value).map(([key, item]) => [
|
|
113
|
-
key,
|
|
114
|
-
toLogValue(item, depth + 1)
|
|
115
|
-
])
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
return String(value);
|
|
119
|
-
}
|
|
120
|
-
function timestampForFile(date = /* @__PURE__ */ new Date()) {
|
|
121
|
-
return date.toISOString().replace(/[:.]/g, "-");
|
|
122
|
-
}
|
|
123
|
-
var AppLogger = class {
|
|
124
|
-
logDir;
|
|
125
|
-
level;
|
|
126
|
-
maxBytes;
|
|
127
|
-
maxFiles;
|
|
128
|
-
currentPath;
|
|
129
|
-
rotationIndex = 0;
|
|
130
|
-
constructor(options = {}) {
|
|
131
|
-
this.logDir = options.logDir ?? process.env.CODESESH_LOG_DIR ?? getDefaultLogDir();
|
|
132
|
-
this.level = options.level ?? parseLevel(process.env.CODESESH_LOG_LEVEL);
|
|
133
|
-
this.maxBytes = options.maxBytes ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_BYTES, 5e6);
|
|
134
|
-
this.maxFiles = options.maxFiles ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_FILES, 5);
|
|
135
|
-
this.currentPath = join(this.logDir, "codesesh.log");
|
|
136
|
-
}
|
|
137
|
-
getLogPath() {
|
|
138
|
-
return this.currentPath;
|
|
139
|
-
}
|
|
140
|
-
debug(event, data = {}) {
|
|
141
|
-
this.write("debug", event, data);
|
|
142
|
-
}
|
|
143
|
-
info(event, data = {}) {
|
|
144
|
-
this.write("info", event, data);
|
|
145
|
-
}
|
|
146
|
-
warn(event, data = {}) {
|
|
147
|
-
this.write("warn", event, data);
|
|
148
|
-
}
|
|
149
|
-
error(event, data = {}) {
|
|
150
|
-
this.write("error", event, data);
|
|
151
|
-
}
|
|
152
|
-
write(level, event, data) {
|
|
153
|
-
if (LEVEL_WEIGHT[level] < LEVEL_WEIGHT[this.level]) return;
|
|
154
|
-
try {
|
|
155
|
-
mkdirSync(this.logDir, { recursive: true });
|
|
156
|
-
const line = `${JSON.stringify({
|
|
157
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
-
level,
|
|
159
|
-
event,
|
|
160
|
-
pid: process.pid,
|
|
161
|
-
...toLogValue(data)
|
|
162
|
-
})}
|
|
163
|
-
`;
|
|
164
|
-
this.rotateIfNeeded(Buffer.byteLength(line));
|
|
165
|
-
appendFileSync(this.currentPath, line, "utf8");
|
|
166
|
-
} catch {
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
rotateIfNeeded(nextBytes) {
|
|
170
|
-
if (!existsSync(this.currentPath)) {
|
|
171
|
-
this.removeExpiredLogs();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const currentSize = statSync(this.currentPath).size;
|
|
175
|
-
if (currentSize + nextBytes <= this.maxBytes) return;
|
|
176
|
-
this.rotationIndex += 1;
|
|
177
|
-
const rotatedPath = join(
|
|
178
|
-
this.logDir,
|
|
179
|
-
`codesesh-${timestampForFile()}-${process.pid}-${this.rotationIndex}.log`
|
|
180
|
-
);
|
|
181
|
-
renameSync(this.currentPath, rotatedPath);
|
|
182
|
-
this.removeExpiredLogs();
|
|
183
|
-
}
|
|
184
|
-
removeExpiredLogs() {
|
|
185
|
-
const rotated = readdirSync(this.logDir).filter((name) => /^codesesh-.+\.log$/.test(name)).map((name) => {
|
|
186
|
-
const path = join(this.logDir, name);
|
|
187
|
-
return { path, mtimeMs: statSync(path).mtimeMs };
|
|
188
|
-
}).toSorted((a, b) => b.mtimeMs - a.mtimeMs);
|
|
189
|
-
for (const item of rotated.slice(Math.max(0, this.maxFiles - 1))) {
|
|
190
|
-
unlinkSync(item.path);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
var appLogger = new AppLogger();
|
|
195
|
-
function logSearchIndexSync(context, result, data = {}) {
|
|
196
|
-
if (!result || result.mode !== "bulk" || result.rebuildDurationMs == null) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
appLogger.info("search_index.sync", {
|
|
200
|
-
context,
|
|
201
|
-
agent: result.agentName,
|
|
202
|
-
mode: result.mode,
|
|
203
|
-
sessions: result.sessions,
|
|
204
|
-
changed: result.changed,
|
|
205
|
-
deleted: result.deleted,
|
|
206
|
-
indexed: result.indexed,
|
|
207
|
-
skipped: result.skipped,
|
|
208
|
-
duration_ms: Math.round(result.durationMs),
|
|
209
|
-
rebuild_duration_ms: Math.round(result.rebuildDurationMs),
|
|
210
|
-
...data
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
67
|
// src/time-window-resolution.ts
|
|
215
68
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
216
69
|
var DEFAULT_DASHBOARD_DAYS = 30;
|
|
@@ -274,90 +127,31 @@ function elapsedDays(from, to) {
|
|
|
274
127
|
return Math.max(1, Math.ceil((to - from) / DAY_MS));
|
|
275
128
|
}
|
|
276
129
|
|
|
277
|
-
// src/api/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (!(error instanceof StateStorageUnavailableError)) {
|
|
294
|
-
appLogger.warn("api.session_aliases.load_failed", {
|
|
295
|
-
error: error instanceof Error ? error.message : String(error)
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return /* @__PURE__ */ new Map();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function isStateStorageUnavailable(error) {
|
|
302
|
-
return error instanceof StateStorageUnavailableError;
|
|
303
|
-
}
|
|
304
|
-
function withDisplayTitle(session, agentKey, aliases) {
|
|
305
|
-
const alias = aliases.get(getSessionAliasKey(agentKey, session.id));
|
|
306
|
-
return alias ? { ...session, display_title: alias } : session;
|
|
307
|
-
}
|
|
308
|
-
function withBookmarkDisplayTitle(bookmark, aliases) {
|
|
309
|
-
const alias = aliases.get(getSessionAliasKey(bookmark.agentKey, bookmark.sessionId));
|
|
310
|
-
return alias ? { ...bookmark, display_title: alias } : bookmark;
|
|
311
|
-
}
|
|
312
|
-
function withFileActivityDisplayTitle(activity, aliases) {
|
|
313
|
-
return {
|
|
314
|
-
...activity,
|
|
315
|
-
session: withDisplayTitle(activity.session, activity.agent_name, aliases)
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function findAliasSearchResults(query, options, scanResult, aliases) {
|
|
319
|
-
const search = mergeSearchQueryOptions(query, options);
|
|
320
|
-
const needle = search.text.trim().toLowerCase();
|
|
321
|
-
if (!needle || aliases.size === 0) return [];
|
|
322
|
-
return executeSessionSearch(
|
|
323
|
-
"",
|
|
324
|
-
{ ...search.options, limit: Math.max(scanResult.sessions.length, 1) },
|
|
325
|
-
scanResult
|
|
326
|
-
).flatMap((result) => {
|
|
327
|
-
const alias = aliases.get(getSessionAliasKey(result.agentName, result.session.id));
|
|
328
|
-
if (!alias || !alias.toLowerCase().includes(needle)) return [];
|
|
329
|
-
return [
|
|
330
|
-
{
|
|
331
|
-
agentName: result.agentName,
|
|
332
|
-
session: withDisplayTitle(result.session, result.agentName, aliases),
|
|
333
|
-
snippet: `Alias \xB7 ${result.session.directory}`,
|
|
334
|
-
matchType: "title"
|
|
335
|
-
}
|
|
336
|
-
];
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
function isRecord(value) {
|
|
340
|
-
return typeof value === "object" && value !== null;
|
|
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;
|
|
341
146
|
}
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
);
|
|
345
151
|
}
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
return {
|
|
352
|
-
agentKey: value.agentKey,
|
|
353
|
-
sessionId: value.sessionId,
|
|
354
|
-
fullPath: value.fullPath,
|
|
355
|
-
title: value.title,
|
|
356
|
-
directory: value.directory,
|
|
357
|
-
time_created: value.time_created,
|
|
358
|
-
time_updated: value.time_updated ?? void 0,
|
|
359
|
-
stats: value.stats
|
|
360
|
-
};
|
|
152
|
+
function optionalQueryValue(value) {
|
|
153
|
+
const normalized = value?.trim();
|
|
154
|
+
return normalized ? normalized : void 0;
|
|
361
155
|
}
|
|
362
156
|
function parseDateParam(value, fallback) {
|
|
363
157
|
if (value == null) return fallback;
|
|
@@ -369,30 +163,23 @@ function parseNumberParam(value) {
|
|
|
369
163
|
const number = Number(value);
|
|
370
164
|
return Number.isFinite(number) ? number : void 0;
|
|
371
165
|
}
|
|
372
|
-
function searchParams(c) {
|
|
373
|
-
return new URL(c.req.url ?? "http://localhost/", "http://localhost/").searchParams;
|
|
374
|
-
}
|
|
375
|
-
function queryValues(params, ...names) {
|
|
376
|
-
return names.flatMap(
|
|
377
|
-
(name) => params.getAll(name).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
166
|
function parseSmartTags(values) {
|
|
381
|
-
const tags = values.map((value) => value.toLowerCase()).filter(
|
|
382
|
-
(value) => [
|
|
383
|
-
"bugfix",
|
|
384
|
-
"refactoring",
|
|
385
|
-
"feature-dev",
|
|
386
|
-
"testing",
|
|
387
|
-
"docs",
|
|
388
|
-
"git-ops",
|
|
389
|
-
"build-deploy",
|
|
390
|
-
"exploration",
|
|
391
|
-
"planning"
|
|
392
|
-
].includes(value)
|
|
393
|
-
);
|
|
167
|
+
const tags = values.map((value) => value.toLowerCase()).filter((value) => SMART_TAGS.includes(value));
|
|
394
168
|
return tags.length > 0 ? [...new Set(tags)] : void 0;
|
|
395
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
|
+
}
|
|
396
183
|
function parseSearchOptions(c, defaults, projectIdentity) {
|
|
397
184
|
const params = searchParams(c);
|
|
398
185
|
const limitValue = parseNumberParam(params.get("limit") ?? void 0);
|
|
@@ -412,7 +199,7 @@ function parseSearchOptions(c, defaults, projectIdentity) {
|
|
|
412
199
|
costMax: parseNumberParam(params.get("costMax") ?? void 0),
|
|
413
200
|
from: parseDateParam(params.get("from") ?? void 0, defaults.from),
|
|
414
201
|
to: parseDateParam(params.get("to") ?? void 0, defaults.to),
|
|
415
|
-
limit: limitValue && limitValue > 0 ? Math.min(limitValue,
|
|
202
|
+
limit: limitValue && limitValue > 0 ? Math.min(limitValue, SEARCH_LIMIT_MAX) : SEARCH_LIMIT_DEFAULT
|
|
416
203
|
};
|
|
417
204
|
}
|
|
418
205
|
function filterSessionsByActivityWindow(sessions, from, to) {
|
|
@@ -424,6 +211,169 @@ function filterSessionsByActivityWindow(sessions, from, to) {
|
|
|
424
211
|
return true;
|
|
425
212
|
});
|
|
426
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() {
|
|
220
|
+
try {
|
|
221
|
+
return new Map(listSessionAliases().map((alias) => [aliasKey(alias.reference), alias]));
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (!(error instanceof StateStorageUnavailableError)) {
|
|
224
|
+
appLogger.warn("api.session_aliases.load_failed", {
|
|
225
|
+
error: error instanceof Error ? error.message : String(error)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return /* @__PURE__ */ new Map();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
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();
|
|
246
|
+
}
|
|
247
|
+
function invalidateAliasView() {
|
|
248
|
+
cachedView = null;
|
|
249
|
+
}
|
|
250
|
+
function decorateBookmark(bookmark, aliases) {
|
|
251
|
+
return {
|
|
252
|
+
...bookmark,
|
|
253
|
+
session: aliases.decorate(bookmark.session, bookmark.reference)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function decorateFileActivity(activity, aliases) {
|
|
257
|
+
return {
|
|
258
|
+
...activity,
|
|
259
|
+
session: aliases.decorate(activity.session, activity.reference)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function findAliasSearchResults(query, options, scanResult, aliases) {
|
|
263
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
264
|
+
const needle = search.text.trim().toLowerCase();
|
|
265
|
+
if (!needle || aliases.size === 0) return [];
|
|
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
|
+
};
|
|
275
|
+
const results = [];
|
|
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);
|
|
280
|
+
if (!session) continue;
|
|
281
|
+
results.push({
|
|
282
|
+
reference: alias.reference,
|
|
283
|
+
session: aliases.decorate(session, alias.reference),
|
|
284
|
+
snippet: `Alias \xB7 ${session.directory}`,
|
|
285
|
+
matchType: "title"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return filterSessionSearchCandidates(results, search.options).sort(
|
|
289
|
+
(a, b) => getSessionActivityTime(b.session) - getSessionActivityTime(a.session)
|
|
290
|
+
);
|
|
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
|
+
}
|
|
322
|
+
function isRecord(value) {
|
|
323
|
+
return typeof value === "object" && value !== null;
|
|
324
|
+
}
|
|
325
|
+
function isSessionStats(value) {
|
|
326
|
+
if (!isRecord(value)) return false;
|
|
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");
|
|
328
|
+
}
|
|
329
|
+
function parseBookmarkSession(value, reference) {
|
|
330
|
+
if (!isRecord(value)) return null;
|
|
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)) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
id: reference.sessionId,
|
|
336
|
+
slug: formatSessionReference(reference),
|
|
337
|
+
title: value.title,
|
|
338
|
+
directory: value.directory,
|
|
339
|
+
time_created: value.time_created,
|
|
340
|
+
time_updated: value.time_updated ?? void 0,
|
|
341
|
+
stats: value.stats
|
|
342
|
+
};
|
|
343
|
+
}
|
|
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
|
+
});
|
|
352
|
+
}
|
|
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
|
|
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;
|
|
376
|
+
}
|
|
427
377
|
function sanitizeClientLogData(value) {
|
|
428
378
|
if (!isRecord(value)) return {};
|
|
429
379
|
return Object.fromEntries(
|
|
@@ -436,60 +386,47 @@ function sanitizeClientLogData(value) {
|
|
|
436
386
|
})
|
|
437
387
|
);
|
|
438
388
|
}
|
|
439
|
-
function
|
|
440
|
-
return
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return projects.map((project) => {
|
|
483
|
-
const metric = metrics.get(getProjectGroupKey(project.identityKind, project.identityKey));
|
|
484
|
-
return {
|
|
485
|
-
...project,
|
|
486
|
-
messages: metric?.messages ?? 0,
|
|
487
|
-
tokens: metric?.tokens ?? 0,
|
|
488
|
-
cost: metric?.cost ?? 0,
|
|
489
|
-
cost_source: metric && metric.cost > 0 ? metric.hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
490
|
-
agentStats: [...metric?.agentStats.values() ?? []].sort((a, b) => b.sessions - a.sessions)
|
|
491
|
-
};
|
|
492
|
-
});
|
|
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
|
+
);
|
|
493
430
|
}
|
|
494
431
|
function handleGetConfig(c, defaults) {
|
|
495
432
|
const payload = {
|
|
@@ -508,22 +445,38 @@ function handleGetAgents(c, scanSource, defaults = {}) {
|
|
|
508
445
|
const scanResult = scanSource.getSnapshot();
|
|
509
446
|
const from = parseDateParam(c.req.query("from"), defaults.from);
|
|
510
447
|
const to = parseDateParam(c.req.query("to"), defaults.to);
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
+
}
|
|
516
461
|
);
|
|
517
|
-
return c.json(
|
|
462
|
+
return c.json(agents);
|
|
518
463
|
}
|
|
519
464
|
function handleGetProjects(c, scanSource, defaults = {}) {
|
|
520
465
|
const scanResult = scanSource.getSnapshot();
|
|
521
466
|
const from = parseDateParam(c.req.query("from"), defaults.from);
|
|
522
467
|
const to = parseDateParam(c.req.query("to"), defaults.to);
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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);
|
|
527
480
|
}
|
|
528
481
|
function handleGetSessions(c, scanSource, defaults = {}) {
|
|
529
482
|
const scanResult = scanSource.getSnapshot();
|
|
@@ -558,16 +511,16 @@ function handleGetSessions(c, scanSource, defaults = {}) {
|
|
|
558
511
|
if (tag) {
|
|
559
512
|
sessions = sessions.filter((s) => s.smart_tags?.includes(tag));
|
|
560
513
|
}
|
|
561
|
-
const aliases =
|
|
514
|
+
const aliases = loadAliasView();
|
|
562
515
|
if (q) {
|
|
563
516
|
sessions = sessions.filter((session) => {
|
|
564
|
-
const alias = aliases.get(
|
|
517
|
+
const alias = aliases.get(getSessionHeadReference(session));
|
|
565
518
|
return session.title.toLowerCase().includes(q) || alias?.toLowerCase().includes(q);
|
|
566
519
|
});
|
|
567
520
|
}
|
|
568
521
|
return c.json({
|
|
569
522
|
sessions: sessions.map(
|
|
570
|
-
(session) =>
|
|
523
|
+
(session) => toSessionListItem(aliases.decorate(session, getSessionHeadReference(session)))
|
|
571
524
|
)
|
|
572
525
|
});
|
|
573
526
|
}
|
|
@@ -582,35 +535,18 @@ function handleSearchSessions(c, scanSource, defaults = {}) {
|
|
|
582
535
|
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
583
536
|
}
|
|
584
537
|
const searchOptions = parseSearchOptions(c, defaults, projectIdentity);
|
|
585
|
-
const aliases =
|
|
538
|
+
const aliases = loadAliasView();
|
|
586
539
|
const results = executeSessionSearch(query, searchOptions, scanResult).map((result) => ({
|
|
587
540
|
...result,
|
|
588
|
-
session:
|
|
541
|
+
session: aliases.decorate(result.session, result.reference)
|
|
589
542
|
}));
|
|
590
543
|
const aliasResults = findAliasSearchResults(query, searchOptions, scanResult, aliases);
|
|
591
544
|
const deduped = /* @__PURE__ */ new Map();
|
|
592
545
|
for (const result of [...aliasResults, ...results]) {
|
|
593
|
-
deduped.set(`${result.agentName}\0${result.
|
|
546
|
+
deduped.set(`${result.reference.agentName}\0${result.reference.sessionId}`, result);
|
|
594
547
|
}
|
|
595
548
|
return c.json({ results: [...deduped.values()].slice(0, searchOptions.limit ?? 50) });
|
|
596
549
|
}
|
|
597
|
-
function parseFileActivityKind(value) {
|
|
598
|
-
if (value === "read" || value === "edit" || value === "write" || value === "delete") {
|
|
599
|
-
return value;
|
|
600
|
-
}
|
|
601
|
-
return void 0;
|
|
602
|
-
}
|
|
603
|
-
function optionalQueryValue(value) {
|
|
604
|
-
const normalized = value?.trim();
|
|
605
|
-
return normalized ? normalized : void 0;
|
|
606
|
-
}
|
|
607
|
-
function parseProjectIdentityFilter(kindValue, keyValue) {
|
|
608
|
-
const kind = optionalQueryValue(kindValue);
|
|
609
|
-
const key = optionalQueryValue(keyValue);
|
|
610
|
-
if (!kind && !key) return void 0;
|
|
611
|
-
if (!kind || !key || !isProjectIdentityKind(kind)) return null;
|
|
612
|
-
return { kind, key };
|
|
613
|
-
}
|
|
614
550
|
function handleGetFileActivity(c, defaults = {}) {
|
|
615
551
|
const limitValue = Number(c.req.query("limit"));
|
|
616
552
|
const limit = Number.isFinite(limitValue) && limitValue > 0 ? Math.min(limitValue, 200) : 50;
|
|
@@ -621,7 +557,7 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
621
557
|
if (projectIdentity === null) {
|
|
622
558
|
return c.json({ error: "projectKind and projectKey must form a valid project identity" }, 400);
|
|
623
559
|
}
|
|
624
|
-
const aliases =
|
|
560
|
+
const aliases = loadAliasView();
|
|
625
561
|
return c.json({
|
|
626
562
|
activity: listFileActivity({
|
|
627
563
|
agent: optionalQueryValue(c.req.query("agent")),
|
|
@@ -635,12 +571,11 @@ function handleGetFileActivity(c, defaults = {}) {
|
|
|
635
571
|
from: parseDateParam(c.req.query("from"), defaults.from),
|
|
636
572
|
to: parseDateParam(c.req.query("to"), defaults.to),
|
|
637
573
|
limit
|
|
638
|
-
}).map((activity) =>
|
|
574
|
+
}).map((activity) => decorateFileActivity(activity, aliases))
|
|
639
575
|
});
|
|
640
576
|
}
|
|
641
577
|
async function handleGetSessionData(c, scanSource) {
|
|
642
578
|
const startedAt = performance.now();
|
|
643
|
-
const scanResult = scanSource.getSnapshot();
|
|
644
579
|
const agentName = c.req.param("agent");
|
|
645
580
|
const sessionId = c.req.param("id");
|
|
646
581
|
if (!agentName) {
|
|
@@ -649,19 +584,15 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
649
584
|
if (!sessionId) {
|
|
650
585
|
return c.json({ error: "Missing session ID" }, 400);
|
|
651
586
|
}
|
|
652
|
-
const agent = scanResult.agents.find((a) => a.name === agentName);
|
|
653
|
-
if (!agent) {
|
|
654
|
-
return c.json({ error: `Unknown agent: ${agentName}` }, 404);
|
|
655
|
-
}
|
|
656
587
|
try {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
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") {
|
|
665
596
|
appLogger.warn("api.session_data.cache_miss", {
|
|
666
597
|
agent: agentName,
|
|
667
598
|
session_id: sessionId,
|
|
@@ -669,27 +600,20 @@ async function handleGetSessionData(c, scanSource) {
|
|
|
669
600
|
});
|
|
670
601
|
return c.json({ error: "Session cache not ready" }, 404);
|
|
671
602
|
}
|
|
672
|
-
const tagStartedAt = performance.now();
|
|
673
|
-
const smartTags = data.smart_tags ?? classifySessionTags(data);
|
|
674
|
-
const tagDuration = performance.now() - tagStartedAt;
|
|
675
|
-
const projectIdentity = data.project_identity ?? head?.project_identity ?? computeIdentity(data.directory, realFs);
|
|
676
|
-
const fileActivity = data.file_activity ?? (cacheHasExpectedMessages && cachedData ? listSessionFileActivity(agentName, sessionId) : extractSessionFileActivity(agentName, sessionId, projectIdentity.key, data.messages));
|
|
677
603
|
appLogger.info("api.session_data", {
|
|
678
604
|
agent: agentName,
|
|
679
605
|
session_id: sessionId,
|
|
680
|
-
messages: data.messages.length,
|
|
681
|
-
load_duration_ms: Math.round(loadDuration),
|
|
682
|
-
tag_duration_ms: Math.round(tagDuration),
|
|
606
|
+
messages: result.status === "found-json" ? result.messageCount : result.data.messages.length,
|
|
683
607
|
duration_ms: Math.round(performance.now() - startedAt)
|
|
684
608
|
});
|
|
685
|
-
const aliases =
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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));
|
|
693
617
|
} catch (err) {
|
|
694
618
|
const message = err instanceof Error ? err.message : "Failed to load session";
|
|
695
619
|
appLogger.error("api.session_data.error", {
|
|
@@ -712,32 +636,26 @@ async function handlePostClientLog(c) {
|
|
|
712
636
|
return c.json({ ok: true });
|
|
713
637
|
}
|
|
714
638
|
function handleGetBookmarks(c) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
throw error;
|
|
726
|
-
}
|
|
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
|
+
);
|
|
727
649
|
}
|
|
728
650
|
async function handlePutBookmark(c) {
|
|
729
651
|
const payload = parseBookmarkPayload(await c.req.json().catch(() => null));
|
|
730
652
|
if (!payload) {
|
|
731
653
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
732
654
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
738
|
-
}
|
|
739
|
-
throw error;
|
|
740
|
-
}
|
|
655
|
+
return withStorageErrors(
|
|
656
|
+
() => c.json({ bookmark: upsertBookmark(payload), storageAvailable: true }),
|
|
657
|
+
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
658
|
+
);
|
|
741
659
|
}
|
|
742
660
|
async function handleImportBookmarks(c) {
|
|
743
661
|
const payload = await c.req.json().catch(() => null);
|
|
@@ -748,14 +666,10 @@ async function handleImportBookmarks(c) {
|
|
|
748
666
|
if (bookmarks.length !== payload.length) {
|
|
749
667
|
return c.json({ error: "Invalid bookmark payload" }, 400);
|
|
750
668
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
return c.json({ error: "Bookmark storage is unavailable" }, 503);
|
|
756
|
-
}
|
|
757
|
-
throw error;
|
|
758
|
-
}
|
|
669
|
+
return withStorageErrors(
|
|
670
|
+
() => c.json({ bookmarks: importBookmarks(bookmarks), storageAvailable: true }),
|
|
671
|
+
() => c.json({ error: "Bookmark storage is unavailable" }, 503)
|
|
672
|
+
);
|
|
759
673
|
}
|
|
760
674
|
function handleDeleteBookmark(c) {
|
|
761
675
|
const agentKey = c.req.param("agent");
|
|
@@ -763,32 +677,35 @@ function handleDeleteBookmark(c) {
|
|
|
763
677
|
if (!agentKey || !sessionId) {
|
|
764
678
|
return c.json({ error: "Missing bookmark identifier" }, 400);
|
|
765
679
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
throw error;
|
|
774
|
-
}
|
|
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
|
+
);
|
|
775
687
|
}
|
|
776
688
|
async function handlePutSessionAlias(c) {
|
|
777
689
|
const agentKey = c.req.param("agent");
|
|
778
690
|
const sessionId = c.req.param("id");
|
|
779
691
|
const payload = await c.req.json().catch(() => null);
|
|
780
|
-
|
|
692
|
+
const aliasValue = payload?.alias;
|
|
693
|
+
if (!agentKey || !sessionId || typeof aliasValue !== "string") {
|
|
781
694
|
return c.json({ error: "Invalid session alias payload" }, 400);
|
|
782
695
|
}
|
|
783
696
|
try {
|
|
784
|
-
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
|
+
);
|
|
785
705
|
} catch (error) {
|
|
786
|
-
if (error instanceof
|
|
706
|
+
if (error instanceof SessionAliasValidationError) {
|
|
787
707
|
return c.json({ error: "Session alias must be non-empty and at most 160 characters" }, 400);
|
|
788
708
|
}
|
|
789
|
-
if (isStateStorageUnavailable(error)) {
|
|
790
|
-
return c.json({ error: "Session alias storage is unavailable" }, 503);
|
|
791
|
-
}
|
|
792
709
|
throw error;
|
|
793
710
|
}
|
|
794
711
|
}
|
|
@@ -798,15 +715,14 @@ function handleDeleteSessionAlias(c) {
|
|
|
798
715
|
if (!agentKey || !sessionId) {
|
|
799
716
|
return c.json({ error: "Missing session alias identifier" }, 400);
|
|
800
717
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
}
|
|
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
|
+
);
|
|
810
726
|
}
|
|
811
727
|
function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
812
728
|
const scanResult = scanSource.getSnapshot();
|
|
@@ -831,15 +747,30 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
831
747
|
projectKind: projectIdentity?.kind,
|
|
832
748
|
projectKey: projectIdentity?.key
|
|
833
749
|
};
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
+
);
|
|
843
774
|
const data = {
|
|
844
775
|
...aggregate,
|
|
845
776
|
recentFileActivities: listFileActivity({
|
|
@@ -852,14 +783,15 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
852
783
|
}),
|
|
853
784
|
window: { from, to, days }
|
|
854
785
|
};
|
|
855
|
-
const aliases =
|
|
786
|
+
const aliases = loadAliasView();
|
|
856
787
|
return c.json({
|
|
857
788
|
...data,
|
|
858
|
-
recentSessions: data.recentSessions.map(
|
|
859
|
-
|
|
860
|
-
|
|
789
|
+
recentSessions: data.recentSessions.map((item) => ({
|
|
790
|
+
...item,
|
|
791
|
+
session: aliases.decorate(item.session, item.reference)
|
|
792
|
+
})),
|
|
861
793
|
recentFileActivities: data.recentFileActivities.map(
|
|
862
|
-
(activity) =>
|
|
794
|
+
(activity) => decorateFileActivity(activity, aliases)
|
|
863
795
|
)
|
|
864
796
|
});
|
|
865
797
|
}
|
|
@@ -997,11 +929,11 @@ var MAX_API_REQUEST_BYTES = 1024 * 1024;
|
|
|
997
929
|
function findWebDistPath() {
|
|
998
930
|
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
999
931
|
const packagedPath = resolve(__dirname2, "web");
|
|
1000
|
-
if (
|
|
932
|
+
if (existsSync(packagedPath)) {
|
|
1001
933
|
return packagedPath;
|
|
1002
934
|
}
|
|
1003
935
|
const devPath = resolve(__dirname2, "../../../apps/web/dist");
|
|
1004
|
-
if (
|
|
936
|
+
if (existsSync(devPath)) {
|
|
1005
937
|
return devPath;
|
|
1006
938
|
}
|
|
1007
939
|
return null;
|
|
@@ -1073,6 +1005,7 @@ async function createServer(port, store, options = {}) {
|
|
|
1073
1005
|
onError: (c) => c.json({ error: "Request body too large" }, 413)
|
|
1074
1006
|
})
|
|
1075
1007
|
);
|
|
1008
|
+
app.use("/api/*", compress());
|
|
1076
1009
|
const routeOptions = {
|
|
1077
1010
|
defaultSessionFrom: options.defaultSessionFrom,
|
|
1078
1011
|
defaultSessionTo: options.defaultSessionTo,
|
|
@@ -1140,11 +1073,232 @@ async function createServer(port, store, options = {}) {
|
|
|
1140
1073
|
}
|
|
1141
1074
|
|
|
1142
1075
|
// src/live-scan.ts
|
|
1143
|
-
import { existsSync as
|
|
1076
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1144
1077
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1145
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
|
+
|
|
1146
1300
|
// src/search-index-job-runner.ts
|
|
1147
|
-
import { existsSync as
|
|
1301
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1148
1302
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1149
1303
|
import { Worker } from "worker_threads";
|
|
1150
1304
|
|
|
@@ -1295,7 +1449,6 @@ var SearchIndexJobRunner = class {
|
|
|
1295
1449
|
nextBatchId = 1;
|
|
1296
1450
|
pendingJobs = new PendingSearchIndexJobs();
|
|
1297
1451
|
isShuttingDown = false;
|
|
1298
|
-
hasCheckedFtsIntegrity = false;
|
|
1299
1452
|
enqueue(context, jobs) {
|
|
1300
1453
|
if (jobs.length === 0) return Promise.resolve();
|
|
1301
1454
|
if (this.isShuttingDown) return Promise.reject(new Error(SHUTDOWN_ERROR_MESSAGE));
|
|
@@ -1368,8 +1521,7 @@ var SearchIndexJobRunner = class {
|
|
|
1368
1521
|
jobs: batch.jobs,
|
|
1369
1522
|
agentNames: [],
|
|
1370
1523
|
sessionsByAgent: {},
|
|
1371
|
-
metaByAgent: {}
|
|
1372
|
-
skipFtsIntegrityCheck: this.hasCheckedFtsIntegrity
|
|
1524
|
+
metaByAgent: {}
|
|
1373
1525
|
}
|
|
1374
1526
|
});
|
|
1375
1527
|
worker.unref();
|
|
@@ -1385,7 +1537,6 @@ var SearchIndexJobRunner = class {
|
|
|
1385
1537
|
duration_ms: Math.round(message.durationMs),
|
|
1386
1538
|
sessions: message.sessions
|
|
1387
1539
|
});
|
|
1388
|
-
this.hasCheckedFtsIntegrity = true;
|
|
1389
1540
|
this.settle(batch);
|
|
1390
1541
|
});
|
|
1391
1542
|
worker.on("error", (error) => {
|
|
@@ -1418,7 +1569,7 @@ var SearchIndexJobRunner = class {
|
|
|
1418
1569
|
}
|
|
1419
1570
|
workerUrl() {
|
|
1420
1571
|
const workerUrl = new URL("./search-index-worker.js", import.meta.url);
|
|
1421
|
-
if (workerUrl.protocol === "file:" && !
|
|
1572
|
+
if (workerUrl.protocol === "file:" && !existsSync2(fileURLToPath2(workerUrl))) return null;
|
|
1422
1573
|
return workerUrl;
|
|
1423
1574
|
}
|
|
1424
1575
|
};
|
|
@@ -1541,6 +1692,21 @@ var ScanStatusModel = class {
|
|
|
1541
1692
|
updatedAt: now
|
|
1542
1693
|
});
|
|
1543
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
|
+
}
|
|
1544
1710
|
finishAgent(agentName, sessionCount) {
|
|
1545
1711
|
const pendingAgents = this.status.pendingAgents.filter((agent) => agent !== agentName);
|
|
1546
1712
|
const scanningAgents = this.status.scanningAgents.filter((agent) => agent !== agentName);
|
|
@@ -1549,26 +1715,27 @@ var ScanStatusModel = class {
|
|
|
1549
1715
|
const now = Date.now();
|
|
1550
1716
|
const previousStatus = this.status.agentStatuses[agentName];
|
|
1551
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
|
+
};
|
|
1552
1731
|
return this.set({
|
|
1553
1732
|
...this.status,
|
|
1554
1733
|
active: isActive,
|
|
1555
|
-
phase: isActive ?
|
|
1734
|
+
phase: isActive ? this.activePhase(pendingAgents, scanningAgents, agentStatuses) : "idle",
|
|
1556
1735
|
pendingAgents,
|
|
1557
1736
|
scanningAgents,
|
|
1558
1737
|
completedAgents,
|
|
1559
|
-
agentStatuses
|
|
1560
|
-
...this.status.agentStatuses,
|
|
1561
|
-
[agentName]: {
|
|
1562
|
-
agentName,
|
|
1563
|
-
status: "complete",
|
|
1564
|
-
total,
|
|
1565
|
-
processed: total,
|
|
1566
|
-
sessions: sessionCount ?? previousStatus?.sessions ?? 0,
|
|
1567
|
-
startedAt: previousStatus?.startedAt,
|
|
1568
|
-
updatedAt: now,
|
|
1569
|
-
completedAt: now
|
|
1570
|
-
}
|
|
1571
|
-
},
|
|
1738
|
+
agentStatuses,
|
|
1572
1739
|
updatedAt: now,
|
|
1573
1740
|
completedAt: isActive ? void 0 : now
|
|
1574
1741
|
});
|
|
@@ -1598,6 +1765,12 @@ var ScanStatusModel = class {
|
|
|
1598
1765
|
updatedAt: Date.now()
|
|
1599
1766
|
});
|
|
1600
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
|
+
}
|
|
1601
1774
|
set(status) {
|
|
1602
1775
|
this.status = status;
|
|
1603
1776
|
return this.snapshot();
|
|
@@ -1607,36 +1780,16 @@ var ScanStatusModel = class {
|
|
|
1607
1780
|
// src/agent-sync-engine.ts
|
|
1608
1781
|
var REFRESH_DEBOUNCE_MS = 200;
|
|
1609
1782
|
var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
|
|
1610
|
-
var PENDING_REFRESH_DELAY_MS = 100;
|
|
1611
|
-
var MAX_ADAPTIVE_REFRESH_DELAY_MS = 3e4;
|
|
1612
|
-
var ADAPTIVE_REFRESH_DELAY_MULTIPLIER = 4;
|
|
1613
1783
|
var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
|
|
1614
1784
|
var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1615
|
-
function
|
|
1616
|
-
const { changes, removedSessionIds
|
|
1785
|
+
function buildPersistenceDiff(previousSessions, nextSessions, candidateChangedIds = []) {
|
|
1786
|
+
const { changes, removedSessionIds } = computeSessionDiff(
|
|
1617
1787
|
previousSessions,
|
|
1618
|
-
nextSessions,
|
|
1619
|
-
candidateChangedIds,
|
|
1620
|
-
sessionSignature
|
|
1621
|
-
);
|
|
1622
|
-
|
|
1623
|
-
return { event: null, changedSessions: changes, removedSessionIds };
|
|
1624
|
-
}
|
|
1625
|
-
return {
|
|
1626
|
-
changedSessions: changes,
|
|
1627
|
-
removedSessionIds,
|
|
1628
|
-
event: {
|
|
1629
|
-
type: "sessions-updated",
|
|
1630
|
-
changedAgents: [agentName],
|
|
1631
|
-
newSessions: counts.new,
|
|
1632
|
-
updatedSessions: counts.updated,
|
|
1633
|
-
removedSessions: counts.removed,
|
|
1634
|
-
totalSessions: nextSessions.length,
|
|
1635
|
-
timestamp: Date.now(),
|
|
1636
|
-
changedSessionHeads: changes.map(({ session }) => ({ agentName, session })),
|
|
1637
|
-
removedSessionRefs: removedSessionIds.map((sessionId) => ({ agentName, sessionId }))
|
|
1638
|
-
}
|
|
1639
|
-
};
|
|
1788
|
+
nextSessions,
|
|
1789
|
+
candidateChangedIds,
|
|
1790
|
+
sessionSignature
|
|
1791
|
+
);
|
|
1792
|
+
return { changedSessions: changes, removedSessionIds };
|
|
1640
1793
|
}
|
|
1641
1794
|
function restoreAgentCacheMeta(agent, cached) {
|
|
1642
1795
|
agent.setSessionMetaMap(new Map(Object.entries(cached.meta)));
|
|
@@ -1644,11 +1797,12 @@ function restoreAgentCacheMeta(agent, cached) {
|
|
|
1644
1797
|
var AgentSyncEngine = class {
|
|
1645
1798
|
constructor(options) {
|
|
1646
1799
|
this.options = options;
|
|
1800
|
+
this.scheduler = new AgentOperationScheduler((agentName) => this.performRefresh(agentName));
|
|
1647
1801
|
}
|
|
1648
1802
|
options;
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1803
|
+
lastRefreshAtByAgent = /* @__PURE__ */ new Map();
|
|
1804
|
+
scheduler;
|
|
1805
|
+
sessionIndex = new LiveSessionIndex();
|
|
1652
1806
|
backfillQueue = [];
|
|
1653
1807
|
currentBackfillAgent;
|
|
1654
1808
|
completedBackfillAgents = [];
|
|
@@ -1657,13 +1811,22 @@ var AgentSyncEngine = class {
|
|
|
1657
1811
|
statusChangedListeners = /* @__PURE__ */ new Set();
|
|
1658
1812
|
scanStatus = new ScanStatusModel();
|
|
1659
1813
|
searchIndexJobs = new SearchIndexJobRunner();
|
|
1814
|
+
nextPublicationId = 1;
|
|
1660
1815
|
backgroundRefreshTimer = null;
|
|
1661
1816
|
isShuttingDown = false;
|
|
1662
|
-
initialize(
|
|
1663
|
-
|
|
1664
|
-
|
|
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
|
+
);
|
|
1665
1825
|
}
|
|
1666
1826
|
}
|
|
1827
|
+
snapshot() {
|
|
1828
|
+
return this.sessionIndex.snapshot();
|
|
1829
|
+
}
|
|
1667
1830
|
status() {
|
|
1668
1831
|
return this.scanStatus.snapshot();
|
|
1669
1832
|
}
|
|
@@ -1676,49 +1839,45 @@ var AgentSyncEngine = class {
|
|
|
1676
1839
|
return () => this.statusChangedListeners.delete(listener);
|
|
1677
1840
|
}
|
|
1678
1841
|
async syncInitialIndex() {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
this.
|
|
1682
|
-
|
|
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
|
+
});
|
|
1683
1847
|
}
|
|
1684
1848
|
handleAgentsChanged(agentNames) {
|
|
1685
|
-
const snapshot = this.
|
|
1849
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1686
1850
|
for (const agentName of agentNames) {
|
|
1687
|
-
this.state(agentName).pendingPathCount += 1;
|
|
1688
1851
|
const delayMs = (snapshot.byAgent[agentName]?.length ?? 0) === 0 ? EMPTY_AGENT_REFRESH_DEBOUNCE_MS : REFRESH_DEBOUNCE_MS;
|
|
1689
|
-
this.
|
|
1852
|
+
this.scheduler.notify(agentName, delayMs);
|
|
1690
1853
|
}
|
|
1691
1854
|
}
|
|
1692
1855
|
startBackgroundRefresh() {
|
|
1693
1856
|
if (this.backgroundRefreshTimer) return;
|
|
1694
|
-
const agentNames = this.
|
|
1857
|
+
const agentNames = this.sessionIndex.snapshot().agents.map((agent) => agent.name);
|
|
1695
1858
|
this.startScanBatch(agentNames, "scanning");
|
|
1696
1859
|
this.backgroundRefreshTimer = setTimeout(() => {
|
|
1697
1860
|
this.backgroundRefreshTimer = null;
|
|
1698
|
-
for (const agentName of agentNames) this.
|
|
1861
|
+
for (const agentName of agentNames) this.scheduler.schedule(agentName, 0);
|
|
1699
1862
|
if (agentNames.length === 0) this.finishScanBatch();
|
|
1700
1863
|
}, 0);
|
|
1701
1864
|
}
|
|
1702
1865
|
async refresh(agentName) {
|
|
1703
|
-
await this.
|
|
1866
|
+
await this.scheduler.refresh(agentName);
|
|
1704
1867
|
}
|
|
1705
1868
|
async shutdown() {
|
|
1706
1869
|
this.isShuttingDown = true;
|
|
1870
|
+
const schedulerSnapshot = this.scheduler.snapshot();
|
|
1707
1871
|
const activeOperations = {
|
|
1708
|
-
agent_operations:
|
|
1709
|
-
refreshes:
|
|
1872
|
+
agent_operations: schedulerSnapshot.activeOperations,
|
|
1873
|
+
refreshes: schedulerSnapshot.activeRefreshes,
|
|
1710
1874
|
backfill_running: this.currentBackfillAgent != null || void 0,
|
|
1711
1875
|
scan_workers: this.options.workerRunner.activeCount
|
|
1712
1876
|
};
|
|
1713
1877
|
if (activeOperations.agent_operations > 0 || activeOperations.scan_workers > 0) {
|
|
1714
1878
|
appLogger.warn("scan.shutdown.active_operations", activeOperations);
|
|
1715
1879
|
}
|
|
1716
|
-
|
|
1717
|
-
if (!state.timer) continue;
|
|
1718
|
-
clearTimeout(state.timer);
|
|
1719
|
-
state.timer = null;
|
|
1720
|
-
state.timerDeadline = 0;
|
|
1721
|
-
}
|
|
1880
|
+
this.scheduler.stop();
|
|
1722
1881
|
if (this.backgroundRefreshTimer) {
|
|
1723
1882
|
clearTimeout(this.backgroundRefreshTimer);
|
|
1724
1883
|
this.backgroundRefreshTimer = null;
|
|
@@ -1732,7 +1891,7 @@ var AgentSyncEngine = class {
|
|
|
1732
1891
|
});
|
|
1733
1892
|
await this.searchIndexJobs.shutdown();
|
|
1734
1893
|
await this.options.workerRunner.shutdown();
|
|
1735
|
-
await
|
|
1894
|
+
await this.scheduler.waitForIdle();
|
|
1736
1895
|
const stoppedSearchIndexSnapshot = this.searchIndexJobs.snapshot();
|
|
1737
1896
|
appLogger.info("search_index.shutdown.completed", {
|
|
1738
1897
|
active_batch_id: searchIndexSnapshot.activeBatchId,
|
|
@@ -1740,7 +1899,7 @@ var AgentSyncEngine = class {
|
|
|
1740
1899
|
});
|
|
1741
1900
|
}
|
|
1742
1901
|
startScanBatch(agentNames, phase) {
|
|
1743
|
-
const snapshot = this.
|
|
1902
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1744
1903
|
const sessionCounts = Object.fromEntries(
|
|
1745
1904
|
agentNames.map((agentName) => [agentName, snapshot.byAgent[agentName]?.length ?? 0])
|
|
1746
1905
|
);
|
|
@@ -1750,7 +1909,7 @@ var AgentSyncEngine = class {
|
|
|
1750
1909
|
this.publishStatus(this.scanStatus.setPhase(phase));
|
|
1751
1910
|
}
|
|
1752
1911
|
beginAgentScan(agentName) {
|
|
1753
|
-
const snapshot = this.
|
|
1912
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
1754
1913
|
if (!this.scanStatus.snapshot().active) this.startScanBatch([agentName], "scanning");
|
|
1755
1914
|
this.publishStatus(
|
|
1756
1915
|
this.scanStatus.beginAgent(agentName, snapshot.byAgent[agentName]?.length ?? 0)
|
|
@@ -1759,8 +1918,11 @@ var AgentSyncEngine = class {
|
|
|
1759
1918
|
updateAgentScanProgress(agentName, progress) {
|
|
1760
1919
|
this.publishStatus(this.scanStatus.updateAgent(agentName, progress));
|
|
1761
1920
|
}
|
|
1921
|
+
beginAgentIndexing(agentName) {
|
|
1922
|
+
this.publishStatus(this.scanStatus.indexAgent(agentName));
|
|
1923
|
+
}
|
|
1762
1924
|
finishAgentScan(agentName) {
|
|
1763
|
-
const count = this.
|
|
1925
|
+
const count = this.sessionIndex.snapshot().byAgent[agentName]?.length;
|
|
1764
1926
|
this.publishStatus(this.scanStatus.finishAgent(agentName, count));
|
|
1765
1927
|
}
|
|
1766
1928
|
finishScanBatch() {
|
|
@@ -1777,44 +1939,6 @@ var AgentSyncEngine = class {
|
|
|
1777
1939
|
if (this.isShuttingDown) return;
|
|
1778
1940
|
for (const listener of this.sessionsChangedListeners) listener(change);
|
|
1779
1941
|
}
|
|
1780
|
-
scheduleRefresh(agentName, delayMs) {
|
|
1781
|
-
if (this.isShuttingDown) return;
|
|
1782
|
-
const state = this.state(agentName);
|
|
1783
|
-
const adaptiveDelayMs = Math.min(
|
|
1784
|
-
state.lastRefreshDurationMs * ADAPTIVE_REFRESH_DELAY_MULTIPLIER,
|
|
1785
|
-
MAX_ADAPTIVE_REFRESH_DELAY_MS
|
|
1786
|
-
);
|
|
1787
|
-
const effectiveDelayMs = Math.max(delayMs, adaptiveDelayMs);
|
|
1788
|
-
const deadline = Date.now() + effectiveDelayMs;
|
|
1789
|
-
if (state.timer) {
|
|
1790
|
-
if (deadline >= state.timerDeadline) return;
|
|
1791
|
-
clearTimeout(state.timer);
|
|
1792
|
-
}
|
|
1793
|
-
appLogger.debug("scan.refresh.schedule", { agent: agentName, delay_ms: effectiveDelayMs });
|
|
1794
|
-
state.timerDeadline = deadline;
|
|
1795
|
-
state.timer = setTimeout(() => {
|
|
1796
|
-
state.timer = null;
|
|
1797
|
-
void this.runCoalescedRefresh(agentName);
|
|
1798
|
-
}, effectiveDelayMs);
|
|
1799
|
-
}
|
|
1800
|
-
async runCoalescedRefresh(agentName) {
|
|
1801
|
-
const state = this.state(agentName);
|
|
1802
|
-
if (state.isRunning) {
|
|
1803
|
-
appLogger.debug("scan.refresh.pending", { agent: agentName });
|
|
1804
|
-
state.hasPendingRerun = true;
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
state.isRunning = true;
|
|
1808
|
-
try {
|
|
1809
|
-
await this.serialize(agentName, "refresh", () => this.performRefresh(agentName));
|
|
1810
|
-
} finally {
|
|
1811
|
-
state.isRunning = false;
|
|
1812
|
-
if (state.hasPendingRerun && !this.isShuttingDown) {
|
|
1813
|
-
state.hasPendingRerun = false;
|
|
1814
|
-
this.scheduleRefresh(agentName, PENDING_REFRESH_DELAY_MS);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
1942
|
async performRefresh(agentName) {
|
|
1819
1943
|
this.beginAgentScan(agentName);
|
|
1820
1944
|
try {
|
|
@@ -1831,18 +1955,16 @@ var AgentSyncEngine = class {
|
|
|
1831
1955
|
}
|
|
1832
1956
|
async runRefresh(agentName) {
|
|
1833
1957
|
const startedAt = performance.now();
|
|
1834
|
-
const
|
|
1835
|
-
const pendingPathCount = state.pendingPathCount;
|
|
1836
|
-
state.pendingPathCount = 0;
|
|
1958
|
+
const pendingPathCount = this.scheduler.takePendingSignalCount(agentName);
|
|
1837
1959
|
const agent = this.findAgent(agentName);
|
|
1838
1960
|
if (!agent) {
|
|
1839
1961
|
appLogger.warn("scan.refresh.missing_agent", { agent: agentName });
|
|
1840
1962
|
return "skipped";
|
|
1841
1963
|
}
|
|
1842
|
-
const previousSessions = this.
|
|
1964
|
+
const previousSessions = this.sessionIndex.snapshot().byAgent[agentName] ?? [];
|
|
1843
1965
|
const cached = loadCachedSessions(agentName);
|
|
1844
1966
|
const refreshBaseline = cached?.sessions ?? previousSessions;
|
|
1845
|
-
const cacheTimestamp = cached?.timestamp ??
|
|
1967
|
+
const cacheTimestamp = cached?.timestamp ?? this.lastRefreshAtByAgent.get(agentName) ?? 0;
|
|
1846
1968
|
if (cached) restoreAgentCacheMeta(agent, cached);
|
|
1847
1969
|
const isInitialized = isAgentCacheInitialized(agentName);
|
|
1848
1970
|
const availabilityStartedAt = performance.now();
|
|
@@ -1867,73 +1989,59 @@ var AgentSyncEngine = class {
|
|
|
1867
1989
|
}
|
|
1868
1990
|
if (strategyResult.status === "unchanged") return "unchanged";
|
|
1869
1991
|
const nextSessions = attachMissingProjectIdentities(strategyResult.nextSessions);
|
|
1870
|
-
const diffStartedAt = performance.now();
|
|
1871
|
-
const diff = buildRefreshDiff(
|
|
1872
|
-
agentName,
|
|
1873
|
-
previousSessions,
|
|
1874
|
-
nextSessions,
|
|
1875
|
-
strategyResult.preciseChangedIds ?? []
|
|
1876
|
-
);
|
|
1877
|
-
const diffDuration = performance.now() - diffStartedAt;
|
|
1878
1992
|
const searchIndexOptions = pendingPathCount >= SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD ? { isBulk: true } : void 0;
|
|
1879
|
-
const
|
|
1880
|
-
const
|
|
1881
|
-
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;
|
|
1882
1995
|
const persistStartedAt = performance.now();
|
|
1883
|
-
const persistentJob =
|
|
1996
|
+
const persistentJob = persistenceDiff ? {
|
|
1884
1997
|
kind: "changes",
|
|
1885
1998
|
context: "scan.refresh",
|
|
1886
1999
|
agentName,
|
|
1887
|
-
changes:
|
|
1888
|
-
removedSessionIds:
|
|
2000
|
+
changes: persistenceDiff.changedSessions,
|
|
2001
|
+
removedSessionIds: persistenceDiff.removedSessionIds,
|
|
1889
2002
|
meta: buildAgentCacheMeta(agent, changedSessionIds),
|
|
1890
2003
|
...searchIndexOptions ? { searchIndexOptions } : {}
|
|
1891
|
-
} :
|
|
2004
|
+
} : {
|
|
1892
2005
|
kind: "full",
|
|
1893
2006
|
context: "scan.refresh",
|
|
1894
2007
|
agentName,
|
|
1895
|
-
sessions: strategyResult.fullScanSessions,
|
|
2008
|
+
sessions: strategyResult.fullScanSessions ?? nextSessions,
|
|
1896
2009
|
meta: buildAgentCacheMeta(agent),
|
|
1897
2010
|
saveCache: true,
|
|
1898
2011
|
...searchIndexOptions ? { searchIndexOptions } : {}
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
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
|
+
});
|
|
1911
2021
|
const persistDuration = performance.now() - persistStartedAt;
|
|
1912
2022
|
logSearchIndexSync("scan.refresh", null, { pending_paths: pendingPathCount });
|
|
1913
|
-
this.emitSessionsChanged({ agentName, sessions: nextSessions, event: diff.event });
|
|
1914
2023
|
const totalDurationMs = performance.now() - startedAt;
|
|
1915
|
-
|
|
2024
|
+
this.scheduler.recordRefreshDuration(agentName, totalDurationMs);
|
|
1916
2025
|
appLogger.info("scan.refresh.done", {
|
|
1917
2026
|
agent: agentName,
|
|
1918
2027
|
duration_ms: Math.round(totalDurationMs),
|
|
1919
2028
|
sessions: nextSessions.length,
|
|
1920
|
-
new_sessions:
|
|
1921
|
-
updated_sessions:
|
|
1922
|
-
removed_sessions:
|
|
2029
|
+
new_sessions: publication.event?.newSessions ?? 0,
|
|
2030
|
+
updated_sessions: publication.event?.updatedSessions ?? 0,
|
|
2031
|
+
removed_sessions: publication.event?.removedSessions ?? 0,
|
|
1923
2032
|
pending_paths: pendingPathCount,
|
|
1924
2033
|
availability_ms: Math.round(availabilityDuration),
|
|
1925
2034
|
check_ms: Math.round(strategyResult.checkDuration),
|
|
1926
2035
|
scan_ms: Math.round(strategyResult.scanDuration),
|
|
1927
|
-
diff_ms: Math.round(diffDuration),
|
|
2036
|
+
diff_ms: Math.round(publication.diffDuration),
|
|
1928
2037
|
persist_ms: Math.round(persistDuration),
|
|
1929
|
-
search_index_ms:
|
|
1930
|
-
persistent_index_worker_job: persistentJob
|
|
1931
|
-
persistent_index_skipped: !persistentJob || void 0
|
|
2038
|
+
search_index_ms: Math.round(persistDuration),
|
|
2039
|
+
persistent_index_worker_job: persistentJob.kind
|
|
1932
2040
|
});
|
|
1933
2041
|
return "committed";
|
|
1934
2042
|
}
|
|
1935
2043
|
refreshUnavailableAgent(agentName) {
|
|
1936
|
-
this.
|
|
2044
|
+
this.lastRefreshAtByAgent.set(agentName, Date.now());
|
|
1937
2045
|
return this.refreshStrategyResult([]);
|
|
1938
2046
|
}
|
|
1939
2047
|
async initializeAgent(agent, previousSessions) {
|
|
@@ -1942,7 +2050,7 @@ var AgentSyncEngine = class {
|
|
|
1942
2050
|
const result = await this.runWorker(agent, previousSessions, null, this.startupScanOptions());
|
|
1943
2051
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1944
2052
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
1945
|
-
this.
|
|
2053
|
+
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
1946
2054
|
return this.refreshStrategyResult(sessions, {
|
|
1947
2055
|
fullScanSessions: sessions,
|
|
1948
2056
|
scanDuration: performance.now() - scanStartedAt
|
|
@@ -1957,17 +2065,17 @@ var AgentSyncEngine = class {
|
|
|
1957
2065
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
1958
2066
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
1959
2067
|
const preciseChangedIds = result.changedIds ?? [];
|
|
1960
|
-
const persistenceDiff =
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
+
}
|
|
1968
2077
|
return this.refreshStrategyResult(sessions, {
|
|
1969
2078
|
preciseChangedIds,
|
|
1970
|
-
usedIncrementalScan: true,
|
|
1971
2079
|
persistenceDiff,
|
|
1972
2080
|
scanDuration: performance.now() - scanStartedAt
|
|
1973
2081
|
});
|
|
@@ -1976,20 +2084,29 @@ var AgentSyncEngine = class {
|
|
|
1976
2084
|
const checkStartedAt = performance.now();
|
|
1977
2085
|
const checkResult = await Promise.resolve(agent.checkForChanges(cacheTimestamp, baseline));
|
|
1978
2086
|
const checkDuration = performance.now() - checkStartedAt;
|
|
1979
|
-
this.
|
|
2087
|
+
this.lastRefreshAtByAgent.set(agent.name, checkResult.timestamp);
|
|
1980
2088
|
if (!checkResult.hasChanges) {
|
|
1981
2089
|
this.logUnchangedRefresh(agent.name, refreshStartedAt);
|
|
1982
2090
|
return this.refreshStrategyResult(baseline, { status: "unchanged", checkDuration });
|
|
1983
2091
|
}
|
|
1984
2092
|
const preciseChangedIds = checkResult.changedIds ?? null;
|
|
1985
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
|
+
}
|
|
1986
2104
|
const sessions = attachMissingProjectIdentities(
|
|
1987
|
-
await Promise.resolve(agent.incrementalScan(baseline, checkResult.
|
|
2105
|
+
await Promise.resolve(agent.incrementalScan(baseline, preciseChangedIds, checkResult.refs))
|
|
1988
2106
|
);
|
|
1989
2107
|
return this.refreshStrategyResult(sessions, {
|
|
1990
2108
|
preciseChangedIds,
|
|
1991
|
-
|
|
1992
|
-
persistenceDiff: buildRefreshDiff(agent.name, baseline, sessions, preciseChangedIds ?? []),
|
|
2109
|
+
persistenceDiff: buildPersistenceDiff(baseline, sessions, preciseChangedIds),
|
|
1993
2110
|
checkDuration,
|
|
1994
2111
|
scanDuration: performance.now() - scanStartedAt
|
|
1995
2112
|
});
|
|
@@ -1999,7 +2116,7 @@ var AgentSyncEngine = class {
|
|
|
1999
2116
|
const result = await this.runWorker(agent, previousSessions, null, {});
|
|
2000
2117
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2001
2118
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
2002
|
-
this.
|
|
2119
|
+
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
2003
2120
|
return this.refreshStrategyResult(sessions, {
|
|
2004
2121
|
fullScanSessions: sessions,
|
|
2005
2122
|
scanDuration: performance.now() - scanStartedAt
|
|
@@ -2011,7 +2128,6 @@ var AgentSyncEngine = class {
|
|
|
2011
2128
|
nextSessions,
|
|
2012
2129
|
fullScanSessions: null,
|
|
2013
2130
|
preciseChangedIds: null,
|
|
2014
|
-
usedIncrementalScan: false,
|
|
2015
2131
|
persistenceDiff: null,
|
|
2016
2132
|
checkDuration: 0,
|
|
2017
2133
|
scanDuration: 0,
|
|
@@ -2049,30 +2165,31 @@ var AgentSyncEngine = class {
|
|
|
2049
2165
|
if (!agentName) return;
|
|
2050
2166
|
this.currentBackfillAgent = agentName;
|
|
2051
2167
|
this.publishBackfillStatus();
|
|
2052
|
-
void this.
|
|
2053
|
-
(
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
if (
|
|
2057
|
-
|
|
2058
|
-
this.completedBackfillAgents.push(agentName);
|
|
2059
|
-
}
|
|
2060
|
-
this.failedBackfillAgents = this.failedBackfillAgents.filter(
|
|
2061
|
-
(failedAgent) => failedAgent !== agentName
|
|
2062
|
-
);
|
|
2063
|
-
} else if (!this.failedBackfillAgents.includes(agentName)) {
|
|
2064
|
-
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);
|
|
2065
2174
|
}
|
|
2066
|
-
this.
|
|
2067
|
-
|
|
2175
|
+
this.failedBackfillAgents = this.failedBackfillAgents.filter(
|
|
2176
|
+
(failedAgent) => failedAgent !== agentName
|
|
2177
|
+
);
|
|
2178
|
+
} else if (!this.failedBackfillAgents.includes(agentName)) {
|
|
2179
|
+
this.failedBackfillAgents.push(agentName);
|
|
2068
2180
|
}
|
|
2069
|
-
|
|
2181
|
+
this.publishBackfillStatus();
|
|
2182
|
+
this.pumpBackfillQueue();
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
runBackfill(agentName) {
|
|
2186
|
+
return this.scheduler.run(agentName, "backfill", () => this.performBackfill(agentName));
|
|
2070
2187
|
}
|
|
2071
2188
|
async performBackfill(agentName) {
|
|
2072
2189
|
const startedAt = performance.now();
|
|
2073
2190
|
const agent = this.findAgent(agentName);
|
|
2074
2191
|
if (!agent || !agent.isAvailable()) return "skipped";
|
|
2075
|
-
const snapshot = this.
|
|
2192
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
2076
2193
|
const cached = loadCachedSessions(agentName);
|
|
2077
2194
|
const baseline = cached?.sessions ?? snapshot.byAgent[agentName] ?? [];
|
|
2078
2195
|
const meta = cached?.meta ?? buildAgentCacheMeta(agent);
|
|
@@ -2090,14 +2207,12 @@ var AgentSyncEngine = class {
|
|
|
2090
2207
|
);
|
|
2091
2208
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2092
2209
|
const fullSessions = attachMissingProjectIdentities(result.sessions);
|
|
2093
|
-
|
|
2210
|
+
await this.commitSessionPublication({
|
|
2211
|
+
context: "scan.backfill",
|
|
2094
2212
|
agentName,
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
);
|
|
2099
|
-
await this.searchIndexJobs.enqueue("scan.backfill", [
|
|
2100
|
-
{
|
|
2213
|
+
sessions: fullSessions,
|
|
2214
|
+
candidateChangedIds: result.changedIds ?? [],
|
|
2215
|
+
indexJob: {
|
|
2101
2216
|
kind: "full",
|
|
2102
2217
|
context: "scan.backfill",
|
|
2103
2218
|
agentName,
|
|
@@ -2105,9 +2220,8 @@ var AgentSyncEngine = class {
|
|
|
2105
2220
|
meta: buildAgentCacheMeta(agent),
|
|
2106
2221
|
saveCache: true
|
|
2107
2222
|
}
|
|
2108
|
-
|
|
2223
|
+
});
|
|
2109
2224
|
markAgentFullSyncCompleted(agentName);
|
|
2110
|
-
this.emitSessionsChanged({ agentName, sessions: fullSessions, event: diff.event });
|
|
2111
2225
|
appLogger.info("scan.backfill.done", {
|
|
2112
2226
|
agent: agentName,
|
|
2113
2227
|
duration_ms: Math.round(performance.now() - startedAt),
|
|
@@ -2121,69 +2235,6 @@ var AgentSyncEngine = class {
|
|
|
2121
2235
|
return "failed";
|
|
2122
2236
|
}
|
|
2123
2237
|
}
|
|
2124
|
-
serialize(agentName, kind, operation) {
|
|
2125
|
-
const previous = this.operationTails.get(agentName) ?? Promise.resolve();
|
|
2126
|
-
const run = previous.then(async () => {
|
|
2127
|
-
if (this.isShuttingDown) return "skipped";
|
|
2128
|
-
const lifecycle = this.beginOperation(agentName, kind);
|
|
2129
|
-
try {
|
|
2130
|
-
const result = await operation();
|
|
2131
|
-
this.completeOperation(lifecycle, result);
|
|
2132
|
-
return result;
|
|
2133
|
-
} catch (error) {
|
|
2134
|
-
this.completeOperation(lifecycle, "failed");
|
|
2135
|
-
throw error;
|
|
2136
|
-
}
|
|
2137
|
-
});
|
|
2138
|
-
const tail = run.then(
|
|
2139
|
-
() => void 0,
|
|
2140
|
-
() => void 0
|
|
2141
|
-
);
|
|
2142
|
-
this.operationTails.set(agentName, tail);
|
|
2143
|
-
void tail.finally(() => {
|
|
2144
|
-
if (this.operationTails.get(agentName) === tail) this.operationTails.delete(agentName);
|
|
2145
|
-
});
|
|
2146
|
-
return run;
|
|
2147
|
-
}
|
|
2148
|
-
state(agentName) {
|
|
2149
|
-
const existing = this.refreshStates.get(agentName);
|
|
2150
|
-
if (existing) return existing;
|
|
2151
|
-
const state = {
|
|
2152
|
-
timer: null,
|
|
2153
|
-
timerDeadline: 0,
|
|
2154
|
-
isRunning: false,
|
|
2155
|
-
hasPendingRerun: false,
|
|
2156
|
-
lastRefreshAt: 0,
|
|
2157
|
-
lastRefreshDurationMs: 0,
|
|
2158
|
-
pendingPathCount: 0
|
|
2159
|
-
};
|
|
2160
|
-
this.refreshStates.set(agentName, state);
|
|
2161
|
-
return state;
|
|
2162
|
-
}
|
|
2163
|
-
beginOperation(agentName, kind) {
|
|
2164
|
-
const generation = (this.operationGenerations.get(agentName) ?? 0) + 1;
|
|
2165
|
-
const startedAt = Date.now();
|
|
2166
|
-
this.operationGenerations.set(agentName, generation);
|
|
2167
|
-
appLogger.info("scan.agent_operation.started", {
|
|
2168
|
-
agent: agentName,
|
|
2169
|
-
operation: kind,
|
|
2170
|
-
generation,
|
|
2171
|
-
started_at: startedAt
|
|
2172
|
-
});
|
|
2173
|
-
return { agentName, kind, generation, startedAt };
|
|
2174
|
-
}
|
|
2175
|
-
completeOperation(lifecycle, result) {
|
|
2176
|
-
const completedAt = Date.now();
|
|
2177
|
-
appLogger.info("scan.agent_operation.completed", {
|
|
2178
|
-
agent: lifecycle.agentName,
|
|
2179
|
-
operation: lifecycle.kind,
|
|
2180
|
-
generation: lifecycle.generation,
|
|
2181
|
-
started_at: lifecycle.startedAt,
|
|
2182
|
-
completed_at: completedAt,
|
|
2183
|
-
duration_ms: completedAt - lifecycle.startedAt,
|
|
2184
|
-
result
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
2238
|
backfillStatus() {
|
|
2188
2239
|
return {
|
|
2189
2240
|
active: this.currentBackfillAgent != null || this.backfillQueue.length > 0,
|
|
@@ -2194,7 +2245,7 @@ var AgentSyncEngine = class {
|
|
|
2194
2245
|
};
|
|
2195
2246
|
}
|
|
2196
2247
|
buildFullSearchIndexJobs(context) {
|
|
2197
|
-
const snapshot = this.
|
|
2248
|
+
const snapshot = this.sessionIndex.snapshot();
|
|
2198
2249
|
return snapshot.agents.map((agent) => {
|
|
2199
2250
|
const cached = loadCachedSessions(agent.name);
|
|
2200
2251
|
return cached ? {
|
|
@@ -2212,8 +2263,65 @@ var AgentSyncEngine = class {
|
|
|
2212
2263
|
};
|
|
2213
2264
|
});
|
|
2214
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
|
+
}
|
|
2215
2323
|
findAgent(agentName) {
|
|
2216
|
-
return this.
|
|
2324
|
+
return this.sessionIndex.findAgent(agentName);
|
|
2217
2325
|
}
|
|
2218
2326
|
startupScanOptions() {
|
|
2219
2327
|
return this.options.startupScanOptions ?? {};
|
|
@@ -2227,19 +2335,19 @@ var AgentSyncEngine = class {
|
|
|
2227
2335
|
};
|
|
2228
2336
|
|
|
2229
2337
|
// src/session-watcher.ts
|
|
2230
|
-
import { existsSync as
|
|
2231
|
-
import { dirname as dirname2, isAbsolute, join
|
|
2338
|
+
import { existsSync as existsSync3, readdirSync, statSync, watch } from "fs";
|
|
2339
|
+
import { dirname as dirname2, isAbsolute, join, relative, resolve as resolve2 } from "path";
|
|
2232
2340
|
var WRITE_STABILITY_THRESHOLD_MS = 250;
|
|
2233
2341
|
var WRITE_STABILITY_POLL_MS = 100;
|
|
2234
2342
|
function toAbsolutePath(path) {
|
|
2235
2343
|
return isAbsolute(path) ? path : resolve2(path);
|
|
2236
2344
|
}
|
|
2237
2345
|
function closestWatchablePath(targetPath) {
|
|
2238
|
-
if (!isAbsolute(targetPath) && !
|
|
2346
|
+
if (!isAbsolute(targetPath) && !existsSync3(targetPath)) {
|
|
2239
2347
|
return null;
|
|
2240
2348
|
}
|
|
2241
2349
|
let current = toAbsolutePath(targetPath);
|
|
2242
|
-
while (!
|
|
2350
|
+
while (!existsSync3(current)) {
|
|
2243
2351
|
const parent = dirname2(current);
|
|
2244
2352
|
if (parent === current) {
|
|
2245
2353
|
return null;
|
|
@@ -2249,7 +2357,7 @@ function closestWatchablePath(targetPath) {
|
|
|
2249
2357
|
return current;
|
|
2250
2358
|
}
|
|
2251
2359
|
function getWatchRoot(path) {
|
|
2252
|
-
const stat =
|
|
2360
|
+
const stat = statSync(path);
|
|
2253
2361
|
return stat.isDirectory() ? path : dirname2(path);
|
|
2254
2362
|
}
|
|
2255
2363
|
function isRecursiveWatchSupported(platform = process.platform, nodeVersion = process.versions.node) {
|
|
@@ -2286,50 +2394,7 @@ function resolveWatchEventPath(watchPath, filename) {
|
|
|
2286
2394
|
if (!filenameText) {
|
|
2287
2395
|
return watchPath;
|
|
2288
2396
|
}
|
|
2289
|
-
return isAbsolute(filenameText) ? filenameText :
|
|
2290
|
-
}
|
|
2291
|
-
function resolveAgentWatchTargets(agentName) {
|
|
2292
|
-
const roots = resolveProviderRoots();
|
|
2293
|
-
const cursorDataPath = getCursorDataPath();
|
|
2294
|
-
switch (agentName) {
|
|
2295
|
-
case "claudecode":
|
|
2296
|
-
return [
|
|
2297
|
-
{ root: roots.claudeRoot, path: join2(roots.claudeRoot, "projects") },
|
|
2298
|
-
{ path: "data/claudecode" }
|
|
2299
|
-
];
|
|
2300
|
-
case "codex":
|
|
2301
|
-
return [
|
|
2302
|
-
{ path: join2(roots.codexRoot, "sessions") },
|
|
2303
|
-
{ path: join2(roots.codexRoot, "session_index.jsonl") }
|
|
2304
|
-
];
|
|
2305
|
-
case "pi":
|
|
2306
|
-
return [
|
|
2307
|
-
{ root: roots.piRoot, path: join2(roots.piRoot, "agent", "sessions") },
|
|
2308
|
-
{ root: "data/pi", path: "data/pi" }
|
|
2309
|
-
];
|
|
2310
|
-
case "cursor":
|
|
2311
|
-
return cursorDataPath ? [
|
|
2312
|
-
{
|
|
2313
|
-
root: cursorDataPath,
|
|
2314
|
-
path: join2(cursorDataPath, "globalStorage", "state.vscdb")
|
|
2315
|
-
},
|
|
2316
|
-
{ root: cursorDataPath, path: join2(cursorDataPath, "workspaceStorage") }
|
|
2317
|
-
] : [];
|
|
2318
|
-
case "kimi":
|
|
2319
|
-
return [
|
|
2320
|
-
{ root: roots.kimiRoot, path: join2(roots.kimiRoot, "sessions") },
|
|
2321
|
-
{ path: "data/kimi" }
|
|
2322
|
-
];
|
|
2323
|
-
case "opencode":
|
|
2324
|
-
return [
|
|
2325
|
-
{ root: roots.opencodeRoot, path: join2(roots.opencodeRoot, "opencode.db") },
|
|
2326
|
-
{ root: "data/opencode", path: "data/opencode/opencode.db" }
|
|
2327
|
-
];
|
|
2328
|
-
case "zcode":
|
|
2329
|
-
return roots.zcodeRoot ? [{ root: roots.zcodeRoot, path: join2(roots.zcodeRoot, "cli", "db", "db.sqlite") }] : [];
|
|
2330
|
-
default:
|
|
2331
|
-
return [];
|
|
2332
|
-
}
|
|
2397
|
+
return isAbsolute(filenameText) ? filenameText : join(watchPath, filenameText);
|
|
2333
2398
|
}
|
|
2334
2399
|
var SessionWatcher = class {
|
|
2335
2400
|
watchers = [];
|
|
@@ -2343,16 +2408,24 @@ var SessionWatcher = class {
|
|
|
2343
2408
|
this.listeners.delete(cb);
|
|
2344
2409
|
};
|
|
2345
2410
|
}
|
|
2346
|
-
/** Begin watching the
|
|
2347
|
-
start(
|
|
2411
|
+
/** Begin watching the session sources declared by each agent adapter. */
|
|
2412
|
+
start(agents) {
|
|
2348
2413
|
const scopesByRoot = /* @__PURE__ */ new Map();
|
|
2349
|
-
for (const
|
|
2350
|
-
const
|
|
2351
|
-
if (
|
|
2352
|
-
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 });
|
|
2353
2426
|
continue;
|
|
2354
2427
|
}
|
|
2355
|
-
for (const target of
|
|
2428
|
+
for (const target of plan.targets) {
|
|
2356
2429
|
const watchRootPath = closestWatchablePath(target.root ?? target.path);
|
|
2357
2430
|
if (!watchRootPath) continue;
|
|
2358
2431
|
let rootPath;
|
|
@@ -2364,17 +2437,17 @@ var SessionWatcher = class {
|
|
|
2364
2437
|
}
|
|
2365
2438
|
const targetPath = toAbsolutePath(target.path);
|
|
2366
2439
|
const scopes = scopesByRoot.get(rootPath) ?? [];
|
|
2367
|
-
if (!scopes.some((scope) => scope.agentName ===
|
|
2368
|
-
scopes.push({ agentName, targetPath });
|
|
2440
|
+
if (!scopes.some((scope) => scope.agentName === agent.name && scope.targetPath === targetPath)) {
|
|
2441
|
+
scopes.push({ agentName: agent.name, targetPath });
|
|
2369
2442
|
}
|
|
2370
2443
|
scopesByRoot.set(rootPath, scopes);
|
|
2371
2444
|
}
|
|
2372
2445
|
}
|
|
2373
2446
|
for (const [rootPath, scopes] of scopesByRoot.entries()) {
|
|
2374
|
-
const
|
|
2447
|
+
const agents2 = Array.from(new Set(scopes.map((scope) => scope.agentName)));
|
|
2375
2448
|
appLogger.info("watch.start", {
|
|
2376
2449
|
root: rootPath,
|
|
2377
|
-
agents,
|
|
2450
|
+
agents: agents2,
|
|
2378
2451
|
targets: scopes.map((scope) => ({
|
|
2379
2452
|
agent: scope.agentName,
|
|
2380
2453
|
path: scope.targetPath
|
|
@@ -2437,9 +2510,9 @@ var SessionWatcher = class {
|
|
|
2437
2510
|
const dirPath = pending.pop();
|
|
2438
2511
|
this.watchFallbackDirectory(dirPath, scopes);
|
|
2439
2512
|
try {
|
|
2440
|
-
for (const entry of
|
|
2513
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
2441
2514
|
if (entry.isDirectory()) {
|
|
2442
|
-
pending.push(
|
|
2515
|
+
pending.push(join(dirPath, entry.name));
|
|
2443
2516
|
}
|
|
2444
2517
|
}
|
|
2445
2518
|
} catch (error) {
|
|
@@ -2462,7 +2535,7 @@ var SessionWatcher = class {
|
|
|
2462
2535
|
watchNewDirectories(watchPath, filename, scopes) {
|
|
2463
2536
|
const path = resolveWatchEventPath(watchPath, filename);
|
|
2464
2537
|
try {
|
|
2465
|
-
if (
|
|
2538
|
+
if (statSync(path).isDirectory()) {
|
|
2466
2539
|
this.watchDirectoryTree(path, scopes);
|
|
2467
2540
|
}
|
|
2468
2541
|
} catch {
|
|
@@ -2510,7 +2583,7 @@ var SessionWatcher = class {
|
|
|
2510
2583
|
let size;
|
|
2511
2584
|
let mtimeMs;
|
|
2512
2585
|
try {
|
|
2513
|
-
const stat =
|
|
2586
|
+
const stat = statSync(path);
|
|
2514
2587
|
size = stat.size;
|
|
2515
2588
|
mtimeMs = stat.mtimeMs;
|
|
2516
2589
|
} catch {
|
|
@@ -2546,71 +2619,154 @@ var SessionWatcher = class {
|
|
|
2546
2619
|
|
|
2547
2620
|
// src/worker-runner.ts
|
|
2548
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
|
+
}
|
|
2549
2650
|
var ThreadWorkerRunner = class {
|
|
2550
2651
|
constructor(workerUrl) {
|
|
2551
2652
|
this.workerUrl = workerUrl;
|
|
2552
2653
|
}
|
|
2553
2654
|
workerUrl;
|
|
2554
|
-
workers = /* @__PURE__ */ new
|
|
2655
|
+
workers = /* @__PURE__ */ new Map();
|
|
2656
|
+
nextRequestId = 1;
|
|
2657
|
+
isShuttingDown = false;
|
|
2555
2658
|
get activeCount() {
|
|
2556
|
-
|
|
2659
|
+
let count = 0;
|
|
2660
|
+
for (const slot of this.workers.values()) count += slot.pending.size;
|
|
2661
|
+
return count;
|
|
2557
2662
|
}
|
|
2558
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
|
+
};
|
|
2559
2675
|
return new Promise((resolve4, reject) => {
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
meta: payload.meta
|
|
2568
|
-
}
|
|
2569
|
-
});
|
|
2570
|
-
worker.unref();
|
|
2571
|
-
this.workers.add(worker);
|
|
2572
|
-
let settled = false;
|
|
2573
|
-
const finish = (callback, terminate = true) => {
|
|
2574
|
-
if (settled) return;
|
|
2575
|
-
settled = true;
|
|
2576
|
-
this.workers.delete(worker);
|
|
2577
|
-
if (terminate) void worker.terminate();
|
|
2578
|
-
callback();
|
|
2579
|
-
};
|
|
2580
|
-
worker.on("message", (message) => {
|
|
2581
|
-
if (message.type === "progress") {
|
|
2582
|
-
payload.onProgress?.(message.progress);
|
|
2583
|
-
return;
|
|
2584
|
-
}
|
|
2585
|
-
if (message.type === "done") {
|
|
2586
|
-
finish(
|
|
2587
|
-
() => resolve4({
|
|
2588
|
-
sessions: message.sessions,
|
|
2589
|
-
meta: message.meta,
|
|
2590
|
-
changedIds: message.changedIds
|
|
2591
|
-
})
|
|
2592
|
-
);
|
|
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)));
|
|
2593
2683
|
return;
|
|
2594
2684
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
if (settled) return;
|
|
2602
|
-
appLogger.warn("scan.refresh_worker.exit_before_done", { agent: agentName, code });
|
|
2603
|
-
finish(
|
|
2604
|
-
() => reject(new Error(`Scan refresh worker exited before completing (code ${code})`)),
|
|
2605
|
-
false
|
|
2606
|
-
);
|
|
2685
|
+
}
|
|
2686
|
+
slot.pending.set(request.requestId, {
|
|
2687
|
+
resolve: resolve4,
|
|
2688
|
+
reject,
|
|
2689
|
+
payload,
|
|
2690
|
+
onProgress: payload.onProgress
|
|
2607
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
|
+
}
|
|
2608
2700
|
});
|
|
2609
2701
|
}
|
|
2610
2702
|
async shutdown() {
|
|
2611
|
-
|
|
2612
|
-
|
|
2703
|
+
this.isShuttingDown = true;
|
|
2704
|
+
const slots = [...this.workers.values()];
|
|
2613
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();
|
|
2614
2770
|
}
|
|
2615
2771
|
};
|
|
2616
2772
|
|
|
@@ -2621,7 +2777,7 @@ function mergeEvents(previous, next) {
|
|
|
2621
2777
|
const removedSessionRefs = /* @__PURE__ */ new Map();
|
|
2622
2778
|
const sessionKey = (agentName, sessionId) => `${agentName}\0${sessionId}`;
|
|
2623
2779
|
const addChanged = (item) => {
|
|
2624
|
-
const key = sessionKey(item.agentName, item.
|
|
2780
|
+
const key = sessionKey(item.reference.agentName, item.reference.sessionId);
|
|
2625
2781
|
removedSessionRefs.delete(key);
|
|
2626
2782
|
changedSessionHeads.set(key, item);
|
|
2627
2783
|
};
|
|
@@ -2652,9 +2808,6 @@ var LiveScanStore = class {
|
|
|
2652
2808
|
startupScanOptions;
|
|
2653
2809
|
deferInitialRefresh;
|
|
2654
2810
|
syncEngine;
|
|
2655
|
-
agents = [];
|
|
2656
|
-
byAgent = {};
|
|
2657
|
-
sessions = [];
|
|
2658
2811
|
listeners = /* @__PURE__ */ new Set();
|
|
2659
2812
|
watcher = null;
|
|
2660
2813
|
pendingEvent = null;
|
|
@@ -2668,11 +2821,12 @@ var LiveScanStore = class {
|
|
|
2668
2821
|
this.deferInitialRefresh = options.deferInitialRefresh === true;
|
|
2669
2822
|
const workerRunner = options.workerRunner ?? new ThreadWorkerRunner(new URL("./scan-refresh-worker.js", import.meta.url));
|
|
2670
2823
|
this.syncEngine = new AgentSyncEngine({
|
|
2671
|
-
snapshot: () => this.getSnapshot(),
|
|
2672
2824
|
startupScanOptions: this.startupScanOptions,
|
|
2673
2825
|
workerRunner
|
|
2674
2826
|
});
|
|
2675
|
-
this.syncEngine.subscribeSessionsChanged((change) =>
|
|
2827
|
+
this.syncEngine.subscribeSessionsChanged((change) => {
|
|
2828
|
+
if (change.event) this.emit(change.event);
|
|
2829
|
+
});
|
|
2676
2830
|
}
|
|
2677
2831
|
async initialize() {
|
|
2678
2832
|
const startedAt = performance.now();
|
|
@@ -2693,8 +2847,12 @@ var LiveScanStore = class {
|
|
|
2693
2847
|
smartTagWorkerUrl: this.getSmartTagWorkerUrl() ?? void 0,
|
|
2694
2848
|
includeSmartTags: this.deferInitialRefresh ? false : void 0
|
|
2695
2849
|
});
|
|
2696
|
-
this.
|
|
2697
|
-
|
|
2850
|
+
this.syncEngine.initialize(initialResult, {
|
|
2851
|
+
cacheTimestamps: initialResult.cacheTimestamps,
|
|
2852
|
+
registeredAgents: createRegisteredAgents(),
|
|
2853
|
+
allowedAgents: this.getAllowedAgents()
|
|
2854
|
+
});
|
|
2855
|
+
const snapshot = this.getSnapshot();
|
|
2698
2856
|
const indexStartedAt = performance.now();
|
|
2699
2857
|
if (!this.deferInitialRefresh) await this.syncEngine.syncInitialIndex();
|
|
2700
2858
|
const indexDuration = performance.now() - indexStartedAt;
|
|
@@ -2702,9 +2860,9 @@ var LiveScanStore = class {
|
|
|
2702
2860
|
duration_ms: Math.round(performance.now() - startedAt),
|
|
2703
2861
|
index_ms: this.deferInitialRefresh ? void 0 : Math.round(indexDuration),
|
|
2704
2862
|
deferred: this.deferInitialRefresh || void 0,
|
|
2705
|
-
sessions:
|
|
2863
|
+
sessions: snapshot.sessions.length,
|
|
2706
2864
|
agents: Object.fromEntries(
|
|
2707
|
-
Object.entries(
|
|
2865
|
+
Object.entries(snapshot.byAgent).map(([key, value]) => [key, value.length])
|
|
2708
2866
|
),
|
|
2709
2867
|
agent_timings: initialResult.timings ? Object.fromEntries(
|
|
2710
2868
|
Object.entries(initialResult.timings).map(([name, timing]) => [
|
|
@@ -2723,13 +2881,13 @@ var LiveScanStore = class {
|
|
|
2723
2881
|
if (!this.watchEnabled) return;
|
|
2724
2882
|
this.watcher = new SessionWatcher();
|
|
2725
2883
|
this.watcher.onAgentsChanged((agentNames) => this.syncEngine.handleAgentsChanged(agentNames));
|
|
2726
|
-
this.watcher.start(
|
|
2884
|
+
this.watcher.start(snapshot.agents);
|
|
2727
2885
|
}
|
|
2728
2886
|
startBackgroundRefresh() {
|
|
2729
2887
|
this.syncEngine.startBackgroundRefresh();
|
|
2730
2888
|
}
|
|
2731
2889
|
getSnapshot() {
|
|
2732
|
-
return
|
|
2890
|
+
return this.syncEngine.snapshot();
|
|
2733
2891
|
}
|
|
2734
2892
|
getScanStatus() {
|
|
2735
2893
|
return this.syncEngine.status();
|
|
@@ -2758,13 +2916,6 @@ var LiveScanStore = class {
|
|
|
2758
2916
|
this.watcher = null;
|
|
2759
2917
|
}
|
|
2760
2918
|
}
|
|
2761
|
-
applySessionsChanged(change) {
|
|
2762
|
-
this.byAgent[change.agentName] = sortSessions(change.sessions);
|
|
2763
|
-
this.rebuildSessions();
|
|
2764
|
-
if (!change.event) return;
|
|
2765
|
-
change.event.totalSessions = this.sessions.length;
|
|
2766
|
-
this.emit(change.event);
|
|
2767
|
-
}
|
|
2768
2919
|
emit(event) {
|
|
2769
2920
|
if (this.shuttingDown) return;
|
|
2770
2921
|
if (this.pendingEvent || event.newSessions > 0) {
|
|
@@ -2786,29 +2937,11 @@ var LiveScanStore = class {
|
|
|
2786
2937
|
if (pending) this.emitNow(pending);
|
|
2787
2938
|
}, NEW_SESSION_EVENT_WINDOW_MS);
|
|
2788
2939
|
}
|
|
2789
|
-
rebuildSessions() {
|
|
2790
|
-
this.sessions = sortSessions(Object.values(this.byAgent).flat());
|
|
2791
|
-
}
|
|
2792
2940
|
getSmartTagWorkerUrl() {
|
|
2793
2941
|
const workerUrl = new URL("./smart-tag-worker.js", import.meta.url);
|
|
2794
|
-
if (workerUrl.protocol === "file:" && !
|
|
2942
|
+
if (workerUrl.protocol === "file:" && !existsSync4(fileURLToPath3(workerUrl))) return null;
|
|
2795
2943
|
return workerUrl;
|
|
2796
2944
|
}
|
|
2797
|
-
applyScanResult(result) {
|
|
2798
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
2799
|
-
const allowedAgents = this.getAllowedAgents();
|
|
2800
|
-
for (const agent of result.agents) agentMap.set(agent.name, agent);
|
|
2801
|
-
for (const agent of createRegisteredAgents()) {
|
|
2802
|
-
if (!agentMap.has(agent.name)) agentMap.set(agent.name, agent);
|
|
2803
|
-
}
|
|
2804
|
-
this.agents = [...agentMap.values()].filter(
|
|
2805
|
-
(agent) => !allowedAgents || allowedAgents.has(agent.name.toLowerCase())
|
|
2806
|
-
);
|
|
2807
|
-
this.byAgent = Object.fromEntries(
|
|
2808
|
-
this.agents.map((agent) => [agent.name, sortSessions(result.byAgent[agent.name] ?? [])])
|
|
2809
|
-
);
|
|
2810
|
-
this.rebuildSessions();
|
|
2811
|
-
}
|
|
2812
2945
|
getAllowedAgents() {
|
|
2813
2946
|
if (!this.scanOptions.agents?.length) return null;
|
|
2814
2947
|
return new Set(this.scanOptions.agents.map((agent) => agent.toLowerCase()));
|
|
@@ -2845,29 +2978,32 @@ function printScanResults(agents) {
|
|
|
2845
2978
|
consola.log("");
|
|
2846
2979
|
}
|
|
2847
2980
|
|
|
2848
|
-
// src/
|
|
2849
|
-
var DEFAULT_PORT = 4521;
|
|
2850
|
-
var DEFAULT_PORT_FALLBACK_ATTEMPTS = 20;
|
|
2851
|
-
function parsePort(value) {
|
|
2852
|
-
const port = parseInt(value ?? "", 10);
|
|
2853
|
-
return Number.isNaN(port) ? DEFAULT_PORT : port;
|
|
2854
|
-
}
|
|
2855
|
-
function hasExplicitPortArg(argv) {
|
|
2856
|
-
return argv.some((arg, index) => {
|
|
2857
|
-
if (arg === "--port" || arg === "-p") return index < argv.length - 1;
|
|
2858
|
-
return arg.startsWith("--port=") || /^-p\d+$/.test(arg);
|
|
2859
|
-
});
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
// src/index.ts
|
|
2981
|
+
// src/runtime-plan.ts
|
|
2863
2982
|
function parseSessionUri(uri) {
|
|
2864
2983
|
const match = uri.match(/^([a-z]+):\/\/(.+)$/i);
|
|
2865
2984
|
if (!match) return null;
|
|
2866
2985
|
return { agent: match[1], sessionId: match[2] };
|
|
2867
2986
|
}
|
|
2868
|
-
function
|
|
2987
|
+
function buildCliRuntimePlan(input, environment) {
|
|
2988
|
+
const listWindow = resolveTimeWindow({
|
|
2989
|
+
mode: "cli",
|
|
2990
|
+
from: input.from,
|
|
2991
|
+
to: input.to,
|
|
2992
|
+
days: input.days,
|
|
2993
|
+
now: environment.now
|
|
2994
|
+
});
|
|
2995
|
+
const cwd = input.cwd === "." ? environment.currentWorkingDirectory : input.cwd;
|
|
2996
|
+
const agents = input.targetSession ? [input.targetSession.agent] : input.agent ? input.agent.split(",").map((agent) => agent.trim()) : void 0;
|
|
2997
|
+
return {
|
|
2998
|
+
listWindow,
|
|
2999
|
+
scanOptions: { agents, cwd, useCache: input.useCache },
|
|
3000
|
+
startupScanOptions: input.targetSession || input.jsonOnly ? {} : { from: listWindow.from, to: listWindow.to }
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function resolveStartupUrl(startupUrl, targetSession) {
|
|
3004
|
+
if (!targetSession) return startupUrl;
|
|
2869
3005
|
const url = new URL(startupUrl);
|
|
2870
|
-
url.pathname =
|
|
3006
|
+
url.pathname = `/${targetSession.agent.toLowerCase()}/${targetSession.sessionId}`;
|
|
2871
3007
|
return url.toString();
|
|
2872
3008
|
}
|
|
2873
3009
|
function redactStartupUrl(startupUrl) {
|
|
@@ -2877,6 +3013,22 @@ function redactStartupUrl(startupUrl) {
|
|
|
2877
3013
|
}
|
|
2878
3014
|
return url.toString();
|
|
2879
3015
|
}
|
|
3016
|
+
|
|
3017
|
+
// src/ports.ts
|
|
3018
|
+
var DEFAULT_PORT = 4521;
|
|
3019
|
+
var DEFAULT_PORT_FALLBACK_ATTEMPTS = 20;
|
|
3020
|
+
function parsePort(value) {
|
|
3021
|
+
const port = parseInt(value ?? "", 10);
|
|
3022
|
+
return Number.isNaN(port) ? DEFAULT_PORT : port;
|
|
3023
|
+
}
|
|
3024
|
+
function hasExplicitPortArg(argv) {
|
|
3025
|
+
return argv.some((arg, index) => {
|
|
3026
|
+
if (arg === "--port" || arg === "-p") return index < argv.length - 1;
|
|
3027
|
+
return arg.startsWith("--port=") || /^-p\d+$/.test(arg);
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// src/index.ts
|
|
2880
3032
|
var main = defineCommand({
|
|
2881
3033
|
meta: {
|
|
2882
3034
|
name: "codesesh",
|
|
@@ -2985,7 +3137,7 @@ var main = defineCommand({
|
|
|
2985
3137
|
log_path: appLogger.getLogPath()
|
|
2986
3138
|
});
|
|
2987
3139
|
if (clearCache) {
|
|
2988
|
-
const { clearCache: clear } = await import("./dist-
|
|
3140
|
+
const { clearCache: clear } = await import("./dist-KEPJFHOC.js");
|
|
2989
3141
|
clear();
|
|
2990
3142
|
appLogger.info("cache.clear");
|
|
2991
3143
|
console.log("Cache cleared.");
|
|
@@ -2999,26 +3151,20 @@ var main = defineCommand({
|
|
|
2999
3151
|
process.exit(1);
|
|
3000
3152
|
}
|
|
3001
3153
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
const scanOptions = {
|
|
3017
|
-
agents: targetSession ? [targetSession.agent] : args.agent ? args.agent.split(",").map((a) => a.trim()) : void 0,
|
|
3018
|
-
cwd: cwdFilter,
|
|
3019
|
-
useCache
|
|
3020
|
-
};
|
|
3021
|
-
const startupScanOptions = targetSession || jsonOnly ? {} : { from: listDefaultFrom, to: listDefaultTo };
|
|
3154
|
+
const { listWindow, scanOptions, startupScanOptions } = buildCliRuntimePlan(
|
|
3155
|
+
{
|
|
3156
|
+
agent: args.agent,
|
|
3157
|
+
cwd: args.cwd,
|
|
3158
|
+
from: args.from,
|
|
3159
|
+
to: args.to,
|
|
3160
|
+
days: args.days,
|
|
3161
|
+
jsonOnly,
|
|
3162
|
+
targetSession,
|
|
3163
|
+
useCache
|
|
3164
|
+
},
|
|
3165
|
+
{ currentWorkingDirectory: process.cwd() }
|
|
3166
|
+
);
|
|
3167
|
+
const { from: listDefaultFrom, to: listDefaultTo, days: listDefaultDays } = listWindow;
|
|
3022
3168
|
const store = new LiveScanStore({
|
|
3023
3169
|
watchEnabled: !jsonOnly,
|
|
3024
3170
|
scanOptions,
|
|
@@ -3108,7 +3254,7 @@ var main = defineCommand({
|
|
|
3108
3254
|
});
|
|
3109
3255
|
if (!noOpen) {
|
|
3110
3256
|
const open = (await import("open")).default;
|
|
3111
|
-
const targetUrl =
|
|
3257
|
+
const targetUrl = resolveStartupUrl(url, targetSession);
|
|
3112
3258
|
appLogger.info("browser.open", { url: redactStartupUrl(targetUrl) });
|
|
3113
3259
|
await open(targetUrl);
|
|
3114
3260
|
}
|