codesesh 0.17.0 → 0.19.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 +6 -4
- package/dist/{chunk-7APNDHQ6.js → chunk-G2BTNW3C.js} +2262 -495
- package/dist/chunk-G2BTNW3C.js.map +1 -0
- package/dist/{chunk-A4U2SMJJ.js → chunk-KD3DGWZY.js} +20 -12
- package/dist/chunk-KD3DGWZY.js.map +1 -0
- package/dist/{dist-KEPJFHOC.js → dist-ALXOBKV2.js} +26 -4
- package/dist/index.js +382 -114
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +243 -24
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +73 -39
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +2 -2
- package/dist/web/assets/AgentIcon-CEj7HO1m.js +1 -0
- package/dist/web/assets/BookmarkButton-DLEnIyeE.js +1 -0
- package/dist/web/assets/Dashboard-DUG0IFcy.js +39 -0
- package/dist/web/assets/DialogTitle-Dv6DQPIb.js +1 -0
- package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +1 -0
- package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +6 -0
- package/dist/web/assets/Projects-BMaZc6J5.js +1 -0
- package/dist/web/assets/SearchFilterBar-iP6pzlZo.js +1 -0
- package/dist/web/assets/SearchResultsPanel-RuiLPkbP.js +1 -0
- package/dist/web/assets/SessionDetail-De1Wl3mu.js +54 -0
- package/dist/web/assets/index-B-CV7pFu.css +2 -0
- package/dist/web/assets/index-CQI8F_8X.js +1930 -0
- package/dist/web/assets/index.dom-CCLJ9_FR.js +1 -0
- package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +1 -0
- package/dist/web/assets/session-indexes-BuxBafvh.js +4 -0
- package/dist/web/assets/session-title-CNhapdPJ.js +1 -0
- package/dist/web/assets/useTheme-CwDcVdGz.js +1 -0
- package/dist/web/assets/utils-B6KiDbIe.js +1 -0
- package/dist/web/index.html +10 -7
- package/package.json +3 -3
- package/dist/chunk-7APNDHQ6.js.map +0 -1
- package/dist/chunk-A4U2SMJJ.js.map +0 -1
- package/dist/web/assets/index-B076CRkD.js +0 -109
- package/dist/web/assets/index-CWNbVhet.css +0 -2
- package/dist/web/assets/markdown-dR4kAewZ.js +0 -14
- package/dist/web/assets/react-D9s532MZ.js +0 -1821
- package/dist/web/assets/rolldown-runtime-QTnfLwEv.js +0 -1
- package/dist/web/assets/syntax-mZXQuVcd.js +0 -6
- package/dist/web/assets/vendor-i9U7_fNF.js +0 -41
- /package/dist/{dist-KEPJFHOC.js.map → dist-ALXOBKV2.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
import {
|
|
3
3
|
appLogger,
|
|
4
4
|
logSearchIndexSync
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KD3DGWZY.js";
|
|
6
6
|
import {
|
|
7
7
|
FileSystemSessionSource,
|
|
8
8
|
SessionAliasValidationError,
|
|
9
9
|
StateStorageUnavailableError,
|
|
10
|
+
addCalendarDays,
|
|
10
11
|
attachMissingProjectIdentities,
|
|
11
12
|
attachProjectMetrics,
|
|
12
13
|
buildAgentCacheMeta,
|
|
13
14
|
buildDashboard,
|
|
14
15
|
computeSessionDiff,
|
|
16
|
+
countCalendarDays,
|
|
15
17
|
createProjectScopeMatcher,
|
|
16
18
|
createRegisteredAgents,
|
|
17
19
|
deleteBookmark,
|
|
18
20
|
deleteSessionAlias,
|
|
19
21
|
executeSessionSearch,
|
|
20
22
|
filterSessionSearchCandidates,
|
|
23
|
+
filterSessionTreeByActivityWindow,
|
|
21
24
|
formatSessionReference,
|
|
25
|
+
getAgentFullSyncCursor,
|
|
22
26
|
getAgentInfoMap,
|
|
23
27
|
getAgentLastFullSyncAt,
|
|
24
28
|
getSessionActivityTime,
|
|
@@ -31,22 +35,30 @@ import {
|
|
|
31
35
|
listFileActivity,
|
|
32
36
|
listSessionAliases,
|
|
33
37
|
loadCachedSessions,
|
|
38
|
+
markAgentCacheInitialized,
|
|
34
39
|
markAgentFullSyncCompleted,
|
|
40
|
+
markAgentFullSyncProgress,
|
|
41
|
+
markAgentFullSyncStarted,
|
|
35
42
|
matchesProjectIdentity,
|
|
36
43
|
matchesProjectScope,
|
|
37
44
|
materializeSessionDetailResponse,
|
|
38
45
|
mergeSearchQueryOptions,
|
|
46
|
+
mergeSessionsUpdatedEvents,
|
|
39
47
|
mergeSortedSessions,
|
|
40
48
|
normalizeSessionReference,
|
|
41
49
|
perf,
|
|
50
|
+
publishPendingPricing,
|
|
42
51
|
refreshPricingCache,
|
|
52
|
+
saveCachedSessionChanges,
|
|
53
|
+
saveCachedSessions,
|
|
43
54
|
scanSessions,
|
|
55
|
+
sessionRoutePath,
|
|
44
56
|
sessionSignature,
|
|
45
57
|
sortSessions,
|
|
46
|
-
|
|
58
|
+
startOfCalendarDay,
|
|
47
59
|
upsertBookmark,
|
|
48
60
|
upsertSessionAlias
|
|
49
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-G2BTNW3C.js";
|
|
50
62
|
|
|
51
63
|
// src/index.ts
|
|
52
64
|
import { defineCommand, runMain } from "citty";
|
|
@@ -58,6 +70,7 @@ import { compress } from "hono/compress";
|
|
|
58
70
|
import { serve } from "@hono/node-server";
|
|
59
71
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
60
72
|
import { existsSync } from "fs";
|
|
73
|
+
import { createServer as createHttpsServer } from "https";
|
|
61
74
|
import { resolve, dirname } from "path";
|
|
62
75
|
import { fileURLToPath } from "url";
|
|
63
76
|
|
|
@@ -65,7 +78,7 @@ import { fileURLToPath } from "url";
|
|
|
65
78
|
import { Hono } from "hono";
|
|
66
79
|
|
|
67
80
|
// src/time-window-resolution.ts
|
|
68
|
-
var
|
|
81
|
+
var ROLLING_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
69
82
|
var DEFAULT_DASHBOARD_DAYS = 30;
|
|
70
83
|
function resolveTimeWindow(request) {
|
|
71
84
|
return request.mode === "cli" ? resolveCliWindow(request) : resolveDashboardWindow(request);
|
|
@@ -78,7 +91,7 @@ function resolveCliWindow(request) {
|
|
|
78
91
|
const days = parseDays(request.days);
|
|
79
92
|
if (days === 0) return { to, days };
|
|
80
93
|
if (days == null || days < 0) return { to };
|
|
81
|
-
const rollingFrom = now - days *
|
|
94
|
+
const rollingFrom = now - days * ROLLING_DAY_MS;
|
|
82
95
|
return Number.isFinite(rollingFrom) ? { from: rollingFrom, to, days } : { to };
|
|
83
96
|
}
|
|
84
97
|
function resolveDashboardWindow(request) {
|
|
@@ -90,19 +103,19 @@ function resolveDashboardWindow(request) {
|
|
|
90
103
|
let days = parsedDays != null && parsedDays > 0 ? parsedDays : defaults.days;
|
|
91
104
|
const queryFrom = parseOptionalDate(request.query.from);
|
|
92
105
|
if (queryFrom != null) {
|
|
93
|
-
days ??=
|
|
106
|
+
days ??= countCalendarDays(queryFrom, to);
|
|
94
107
|
return { from: queryFrom, to, days };
|
|
95
108
|
}
|
|
96
109
|
if (parsedDays === 0 || !hasQueryDays && defaults.days === 0) {
|
|
97
110
|
return { to, days: 0 };
|
|
98
111
|
}
|
|
99
112
|
if (defaults.from != null) {
|
|
100
|
-
days ??=
|
|
113
|
+
days ??= countCalendarDays(defaults.from, to);
|
|
101
114
|
return { from: defaults.from, to, days };
|
|
102
115
|
}
|
|
103
116
|
const resolvedDays = days != null && days > 0 ? days : DEFAULT_DASHBOARD_DAYS;
|
|
104
117
|
return {
|
|
105
|
-
from:
|
|
118
|
+
from: addCalendarDays(startOfCalendarDay(to), -(resolvedDays - 1)),
|
|
106
119
|
to,
|
|
107
120
|
days: resolvedDays
|
|
108
121
|
};
|
|
@@ -121,10 +134,7 @@ function parseOptionalDate(value) {
|
|
|
121
134
|
function parseDays(value) {
|
|
122
135
|
if (value == null) return void 0;
|
|
123
136
|
const days = Number.parseInt(value, 10);
|
|
124
|
-
return Number.isSafeInteger(days) && Number.isSafeInteger(days *
|
|
125
|
-
}
|
|
126
|
-
function elapsedDays(from, to) {
|
|
127
|
-
return Math.max(1, Math.ceil((to - from) / DAY_MS));
|
|
137
|
+
return Number.isSafeInteger(days) && Number.isSafeInteger(days * ROLLING_DAY_MS) ? days : void 0;
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
// src/api/query-params.ts
|
|
@@ -203,13 +213,7 @@ function parseSearchOptions(c, defaults, projectIdentity) {
|
|
|
203
213
|
};
|
|
204
214
|
}
|
|
205
215
|
function filterSessionsByActivityWindow(sessions, from, to) {
|
|
206
|
-
|
|
207
|
-
return sessions.filter((session) => {
|
|
208
|
-
const activity = getSessionActivityTime(session);
|
|
209
|
-
if (from != null && activity < from) return false;
|
|
210
|
-
if (to != null && activity > to) return false;
|
|
211
|
-
return true;
|
|
212
|
-
});
|
|
216
|
+
return filterSessionTreeByActivityWindow(sessions, from, to);
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
// src/api/session-aliases-view.ts
|
|
@@ -398,6 +402,7 @@ function getSessionHeadReference(session) {
|
|
|
398
402
|
sessionId: session.id
|
|
399
403
|
};
|
|
400
404
|
}
|
|
405
|
+
var SESSION_DETAIL_STREAM_BATCH_CHARS = 64 * 1024;
|
|
401
406
|
function createSessionDetailJsonResponse(data, messages) {
|
|
402
407
|
const encoder = new TextEncoder();
|
|
403
408
|
const headerJson = JSON.stringify(data);
|
|
@@ -412,14 +417,24 @@ function createSessionDetailJsonResponse(data, messages) {
|
|
|
412
417
|
wroteHeader = true;
|
|
413
418
|
return;
|
|
414
419
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
420
|
+
const batch = [];
|
|
421
|
+
let batchLength = 0;
|
|
422
|
+
let next = iterator.next();
|
|
423
|
+
while (!next.done) {
|
|
424
|
+
const prefix = wroteMessage ? "," : "";
|
|
425
|
+
batch.push(prefix, next.value);
|
|
426
|
+
batchLength += prefix.length + next.value.length;
|
|
418
427
|
wroteMessage = true;
|
|
428
|
+
if (batchLength >= SESSION_DETAIL_STREAM_BATCH_CHARS) break;
|
|
429
|
+
next = iterator.next();
|
|
430
|
+
}
|
|
431
|
+
if (next.done) {
|
|
432
|
+
batch.push("]}");
|
|
433
|
+
controller.enqueue(encoder.encode(batch.join("")));
|
|
434
|
+
controller.close();
|
|
419
435
|
return;
|
|
420
436
|
}
|
|
421
|
-
controller.enqueue(encoder.encode("
|
|
422
|
-
controller.close();
|
|
437
|
+
controller.enqueue(encoder.encode(batch.join("")));
|
|
423
438
|
},
|
|
424
439
|
cancel() {
|
|
425
440
|
iterator.return?.();
|
|
@@ -757,7 +772,7 @@ function handleGetDashboard(c, scanSource, defaults = {}) {
|
|
|
757
772
|
scope.projectKind,
|
|
758
773
|
scope.projectKey,
|
|
759
774
|
from,
|
|
760
|
-
fixedTo ??
|
|
775
|
+
fixedTo ?? startOfCalendarDay(to)
|
|
761
776
|
],
|
|
762
777
|
() => {
|
|
763
778
|
const agentInfo = getAgentInfoMap({});
|
|
@@ -888,8 +903,48 @@ function createApiRoutes(scanSource, eventSource, options = {}) {
|
|
|
888
903
|
|
|
889
904
|
// src/remote-access.ts
|
|
890
905
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
906
|
+
import { readFileSync } from "fs";
|
|
891
907
|
import { isIP } from "net";
|
|
892
908
|
var REMOTE_ACCESS_QUERY_PARAM = "access_token";
|
|
909
|
+
var RemoteTransportError = class extends Error {
|
|
910
|
+
};
|
|
911
|
+
function resolveRemoteTransport(request) {
|
|
912
|
+
const hasCert = Boolean(request.tlsCertPath);
|
|
913
|
+
const hasKey = Boolean(request.tlsKeyPath);
|
|
914
|
+
if (hasCert !== hasKey) {
|
|
915
|
+
throw new RemoteTransportError("TLS requires both --tls-cert and --tls-key.");
|
|
916
|
+
}
|
|
917
|
+
if (hasCert && request.trustProxy) {
|
|
918
|
+
throw new RemoteTransportError(
|
|
919
|
+
"Use either --tls-cert/--tls-key or --trust-proxy, not both: only one of them terminates TLS."
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
if (hasCert) {
|
|
923
|
+
try {
|
|
924
|
+
return {
|
|
925
|
+
kind: "tls",
|
|
926
|
+
cert: readFileSync(request.tlsCertPath),
|
|
927
|
+
key: readFileSync(request.tlsKeyPath)
|
|
928
|
+
};
|
|
929
|
+
} catch (error) {
|
|
930
|
+
throw new RemoteTransportError(
|
|
931
|
+
`Unable to read the TLS certificate or key: ${error instanceof Error ? error.message : String(error)}`
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (request.trustProxy) return { kind: "trusted-proxy" };
|
|
936
|
+
return isLoopbackHostname(request.hostname) ? { kind: "loopback" } : { kind: "plaintext" };
|
|
937
|
+
}
|
|
938
|
+
var FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";
|
|
939
|
+
function requireProxyTls() {
|
|
940
|
+
return async (c, next) => {
|
|
941
|
+
const proto = c.req.header(FORWARDED_PROTO_HEADER)?.split(",")[0]?.trim().toLowerCase();
|
|
942
|
+
if (proto !== "https") {
|
|
943
|
+
return c.json({ error: "Requests must arrive over TLS through the trusted proxy" }, 403);
|
|
944
|
+
}
|
|
945
|
+
await next();
|
|
946
|
+
};
|
|
947
|
+
}
|
|
893
948
|
function createRemoteAccessToken() {
|
|
894
949
|
return randomBytes(32).toString("base64url");
|
|
895
950
|
}
|
|
@@ -969,6 +1024,7 @@ async function createServer(port, store, options = {}) {
|
|
|
969
1024
|
const app = new Hono2();
|
|
970
1025
|
const hostname = options.hostname ?? "127.0.0.1";
|
|
971
1026
|
const isLoopback = isLoopbackHostname(hostname);
|
|
1027
|
+
const transport = options.transport ?? (isLoopback ? { kind: "loopback" } : { kind: "plaintext" });
|
|
972
1028
|
const remoteAccessToken = !isLoopback ? options.remoteAccessToken ?? (options.remoteAccess ? createRemoteAccessToken() : null) : null;
|
|
973
1029
|
if (!isLoopback && !remoteAccessToken) {
|
|
974
1030
|
throw new Error(
|
|
@@ -995,6 +1051,9 @@ async function createServer(port, store, options = {}) {
|
|
|
995
1051
|
});
|
|
996
1052
|
}
|
|
997
1053
|
});
|
|
1054
|
+
if (transport.kind === "trusted-proxy") {
|
|
1055
|
+
app.use("/api/*", requireProxyTls());
|
|
1056
|
+
}
|
|
998
1057
|
if (remoteAccessToken) {
|
|
999
1058
|
app.use("/api/*", remoteAccessAuth(remoteAccessToken));
|
|
1000
1059
|
}
|
|
@@ -1012,7 +1071,7 @@ async function createServer(port, store, options = {}) {
|
|
|
1012
1071
|
defaultSessionDays: options.defaultSessionDays
|
|
1013
1072
|
};
|
|
1014
1073
|
app.route("/api", createApiRoutes(store, store, routeOptions));
|
|
1015
|
-
const webDistPath = findWebDistPath();
|
|
1074
|
+
const webDistPath = options.webDistPath ?? findWebDistPath();
|
|
1016
1075
|
if (webDistPath) {
|
|
1017
1076
|
app.use("/*", serveStatic({ root: webDistPath }));
|
|
1018
1077
|
app.get("/*", serveStatic({ root: webDistPath, path: "index.html" }));
|
|
@@ -1022,7 +1081,15 @@ async function createServer(port, store, options = {}) {
|
|
|
1022
1081
|
let actualPort = port;
|
|
1023
1082
|
for (let offset = 0; offset < attempts; offset += 1) {
|
|
1024
1083
|
const candidatePort = port + offset;
|
|
1025
|
-
server = serve({
|
|
1084
|
+
server = serve({
|
|
1085
|
+
fetch: app.fetch,
|
|
1086
|
+
port: candidatePort,
|
|
1087
|
+
hostname,
|
|
1088
|
+
...transport.kind === "tls" ? {
|
|
1089
|
+
createServer: createHttpsServer,
|
|
1090
|
+
serverOptions: { cert: transport.cert, key: transport.key }
|
|
1091
|
+
} : {}
|
|
1092
|
+
});
|
|
1026
1093
|
try {
|
|
1027
1094
|
await waitForListening(server);
|
|
1028
1095
|
actualPort = getListeningPort(server, candidatePort);
|
|
@@ -1042,19 +1109,33 @@ async function createServer(port, store, options = {}) {
|
|
|
1042
1109
|
throw new Error(getServerStartupErrorMessage(error, candidatePort));
|
|
1043
1110
|
}
|
|
1044
1111
|
}
|
|
1045
|
-
const
|
|
1112
|
+
const scheme = transport.kind === "tls" ? "https" : "http";
|
|
1113
|
+
const baseUrl = isLoopback ? `${scheme}://localhost:${actualPort}` : `${scheme}://${hostname}:${actualPort}`;
|
|
1046
1114
|
const url = remoteAccessToken ? `${baseUrl}/?${REMOTE_ACCESS_QUERY_PARAM}=${encodeURIComponent(remoteAccessToken)}` : baseUrl;
|
|
1047
1115
|
appLogger.info("server.listen", {
|
|
1048
1116
|
port: actualPort,
|
|
1049
1117
|
requested_port: port,
|
|
1050
1118
|
hostname,
|
|
1051
|
-
remote_access: Boolean(remoteAccessToken)
|
|
1119
|
+
remote_access: Boolean(remoteAccessToken),
|
|
1120
|
+
transport: transport.kind
|
|
1052
1121
|
});
|
|
1053
1122
|
if (!isLoopback) {
|
|
1054
|
-
appLogger.warn("server.listen.remote_access", {
|
|
1123
|
+
appLogger.warn("server.listen.remote_access", {
|
|
1124
|
+
hostname,
|
|
1125
|
+
port: actualPort,
|
|
1126
|
+
transport: transport.kind
|
|
1127
|
+
});
|
|
1055
1128
|
console.warn(`
|
|
1056
1129
|
\u26A0 \u8FDC\u7A0B\u8BBF\u95EE\u5DF2\u542F\u7528\u3002\u4EFB\u4F55\u6301\u6709\u542F\u52A8 URL \u7684\u4EBA\u90FD\u53EF\u4EE5\u8BFB\u53D6\u4F60\u7684 AI \u4F1A\u8BDD\u8BB0\u5F55\u3002
|
|
1057
1130
|
`);
|
|
1131
|
+
if (transport.kind === "plaintext") {
|
|
1132
|
+
appLogger.warn("server.listen.plaintext", { hostname, port: actualPort });
|
|
1133
|
+
console.warn(
|
|
1134
|
+
`\u26A0 \u4F20\u8F93\u672A\u52A0\u5BC6\u3002\u8BBF\u95EE\u4EE4\u724C\u4E0E\u5B8C\u6574\u4F1A\u8BDD\u5185\u5BB9\u4EE5\u660E\u6587\u7ECF\u8FC7\u7F51\u7EDC\uFF0CURL \u4E2D\u7684\u4EE4\u724C\u8FD8\u53EF\u80FD\u5199\u5165\u4EE3\u7406\u8BBF\u95EE\u65E5\u5FD7\u3002
|
|
1135
|
+
\u4F7F\u7528 --tls-cert/--tls-key \u76F4\u63A5\u542F\u7528 TLS\uFF0C\u6216\u5728\u53D7\u4FE1\u53CD\u5411\u4EE3\u7406\u540E\u4F7F\u7528 --trust-proxy\u3002
|
|
1136
|
+
`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1058
1139
|
}
|
|
1059
1140
|
return {
|
|
1060
1141
|
url,
|
|
@@ -1532,6 +1613,22 @@ var SearchIndexJobRunner = class {
|
|
|
1532
1613
|
logSearchIndexSync(message.context, message.result);
|
|
1533
1614
|
return;
|
|
1534
1615
|
}
|
|
1616
|
+
if (message.type === "persist-failed") {
|
|
1617
|
+
appLogger.error("search_index.persist_failed", {
|
|
1618
|
+
batch_id: batch.id,
|
|
1619
|
+
context: message.context,
|
|
1620
|
+
stage: message.stage,
|
|
1621
|
+
agent: message.agentName,
|
|
1622
|
+
sessions: message.sessions
|
|
1623
|
+
});
|
|
1624
|
+
this.settle(
|
|
1625
|
+
batch,
|
|
1626
|
+
new Error(
|
|
1627
|
+
`Search index worker failed to persist ${message.stage} for ${message.agentName}`
|
|
1628
|
+
)
|
|
1629
|
+
);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1535
1632
|
if (message.type !== "done") return;
|
|
1536
1633
|
appLogger.info(`${message.context}.done`, {
|
|
1537
1634
|
duration_ms: Math.round(message.durationMs),
|
|
@@ -1588,6 +1685,17 @@ var ScanStatusModel = class {
|
|
|
1588
1685
|
backfill: { active: false, pendingAgents: [], completedAgents: [], failedAgents: [] }
|
|
1589
1686
|
};
|
|
1590
1687
|
snapshot() {
|
|
1688
|
+
const backfill = {
|
|
1689
|
+
...this.status.backfill,
|
|
1690
|
+
pendingAgents: [...this.status.backfill.pendingAgents],
|
|
1691
|
+
completedAgents: [...this.status.backfill.completedAgents],
|
|
1692
|
+
failedAgents: [...this.status.backfill.failedAgents]
|
|
1693
|
+
};
|
|
1694
|
+
if (this.status.backfill.progress) {
|
|
1695
|
+
backfill.progress = { ...this.status.backfill.progress };
|
|
1696
|
+
} else {
|
|
1697
|
+
delete backfill.progress;
|
|
1698
|
+
}
|
|
1591
1699
|
return {
|
|
1592
1700
|
type: "scan-status",
|
|
1593
1701
|
...this.status,
|
|
@@ -1600,12 +1708,7 @@ var ScanStatusModel = class {
|
|
|
1600
1708
|
{ ...status }
|
|
1601
1709
|
])
|
|
1602
1710
|
),
|
|
1603
|
-
backfill
|
|
1604
|
-
...this.status.backfill,
|
|
1605
|
-
pendingAgents: [...this.status.backfill.pendingAgents],
|
|
1606
|
-
completedAgents: [...this.status.backfill.completedAgents],
|
|
1607
|
-
failedAgents: [...this.status.backfill.failedAgents]
|
|
1608
|
-
}
|
|
1711
|
+
backfill
|
|
1609
1712
|
};
|
|
1610
1713
|
}
|
|
1611
1714
|
startBatch(agentNames, phase, sessionCounts) {
|
|
@@ -1675,26 +1778,34 @@ var ScanStatusModel = class {
|
|
|
1675
1778
|
}
|
|
1676
1779
|
updateAgent(agentName, progress) {
|
|
1677
1780
|
const status = this.status.agentStatuses[agentName];
|
|
1678
|
-
if (!status || status.status !== "
|
|
1781
|
+
if (!status || status.status === "pending" || status.status === "complete" || status.status === "indexing" && progress.phase !== "finalizing") {
|
|
1782
|
+
return null;
|
|
1783
|
+
}
|
|
1679
1784
|
const now = Date.now();
|
|
1785
|
+
const nextStatus = progress.phase === "finalizing" ? "finalizing" : "scanning";
|
|
1786
|
+
const agentStatuses = {
|
|
1787
|
+
...this.status.agentStatuses,
|
|
1788
|
+
[agentName]: {
|
|
1789
|
+
...status,
|
|
1790
|
+
status: nextStatus,
|
|
1791
|
+
total: progress.total ?? status.total,
|
|
1792
|
+
processed: progress.processed ?? status.processed,
|
|
1793
|
+
sessions: progress.sessions ?? status.sessions,
|
|
1794
|
+
updatedAt: now
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1680
1797
|
return this.set({
|
|
1681
1798
|
...this.status,
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
[agentName]: {
|
|
1685
|
-
...status,
|
|
1686
|
-
total: progress.total ?? status.total,
|
|
1687
|
-
processed: progress.processed ?? status.processed,
|
|
1688
|
-
sessions: progress.sessions ?? status.sessions,
|
|
1689
|
-
updatedAt: now
|
|
1690
|
-
}
|
|
1691
|
-
},
|
|
1799
|
+
phase: this.activePhase(this.status.pendingAgents, this.status.scanningAgents, agentStatuses),
|
|
1800
|
+
agentStatuses,
|
|
1692
1801
|
updatedAt: now
|
|
1693
1802
|
});
|
|
1694
1803
|
}
|
|
1695
1804
|
indexAgent(agentName) {
|
|
1696
1805
|
const status = this.status.agentStatuses[agentName];
|
|
1697
|
-
if (!this.status.active || !status || status.status !== "scanning")
|
|
1806
|
+
if (!this.status.active || !status || status.status !== "scanning" && status.status !== "finalizing") {
|
|
1807
|
+
return null;
|
|
1808
|
+
}
|
|
1698
1809
|
const now = Date.now();
|
|
1699
1810
|
const agentStatuses = {
|
|
1700
1811
|
...this.status.agentStatuses,
|
|
@@ -1766,7 +1877,10 @@ var ScanStatusModel = class {
|
|
|
1766
1877
|
});
|
|
1767
1878
|
}
|
|
1768
1879
|
activePhase(pendingAgents, scanningAgents, agentStatuses) {
|
|
1769
|
-
if (pendingAgents.length === 0 && scanningAgents.length > 0 && scanningAgents.every((agentName) =>
|
|
1880
|
+
if (pendingAgents.length === 0 && scanningAgents.length > 0 && scanningAgents.every((agentName) => {
|
|
1881
|
+
const status = agentStatuses[agentName]?.status;
|
|
1882
|
+
return status === "finalizing" || status === "indexing";
|
|
1883
|
+
})) {
|
|
1770
1884
|
return "indexing";
|
|
1771
1885
|
}
|
|
1772
1886
|
return this.status.phase === "initializing" ? "initializing" : "scanning";
|
|
@@ -1782,6 +1896,7 @@ var REFRESH_DEBOUNCE_MS = 200;
|
|
|
1782
1896
|
var EMPTY_AGENT_REFRESH_DEBOUNCE_MS = 3e4;
|
|
1783
1897
|
var SEARCH_INDEX_BULK_PENDING_PATH_THRESHOLD = 100;
|
|
1784
1898
|
var BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1899
|
+
var CACHE_TRUNCATION_COVERAGE = 0.5;
|
|
1785
1900
|
function buildPersistenceDiff(previousSessions, nextSessions, candidateChangedIds = []) {
|
|
1786
1901
|
const { changes, removedSessionIds } = computeSessionDiff(
|
|
1787
1902
|
previousSessions,
|
|
@@ -1805,6 +1920,8 @@ var AgentSyncEngine = class {
|
|
|
1805
1920
|
sessionIndex = new LiveSessionIndex();
|
|
1806
1921
|
backfillQueue = [];
|
|
1807
1922
|
currentBackfillAgent;
|
|
1923
|
+
currentBackfillProgress;
|
|
1924
|
+
cacheIntegrityCheckedAgents = /* @__PURE__ */ new Set();
|
|
1808
1925
|
completedBackfillAgents = [];
|
|
1809
1926
|
failedBackfillAgents = [];
|
|
1810
1927
|
sessionsChangedListeners = /* @__PURE__ */ new Set();
|
|
@@ -1884,6 +2001,8 @@ var AgentSyncEngine = class {
|
|
|
1884
2001
|
}
|
|
1885
2002
|
this.backfillQueue.length = 0;
|
|
1886
2003
|
this.currentBackfillAgent = void 0;
|
|
2004
|
+
this.currentBackfillProgress = void 0;
|
|
2005
|
+
this.cacheIntegrityCheckedAgents.clear();
|
|
1887
2006
|
const searchIndexSnapshot = this.searchIndexJobs.snapshot();
|
|
1888
2007
|
appLogger.info("search_index.shutdown.started", {
|
|
1889
2008
|
active_batch_id: searchIndexSnapshot.activeBatchId,
|
|
@@ -1916,6 +2035,16 @@ var AgentSyncEngine = class {
|
|
|
1916
2035
|
);
|
|
1917
2036
|
}
|
|
1918
2037
|
updateAgentScanProgress(agentName, progress) {
|
|
2038
|
+
if (this.currentBackfillAgent === agentName) {
|
|
2039
|
+
this.currentBackfillProgress = {
|
|
2040
|
+
phase: progress.phase,
|
|
2041
|
+
total: progress.total,
|
|
2042
|
+
processed: progress.processed,
|
|
2043
|
+
sessions: progress.sessions
|
|
2044
|
+
};
|
|
2045
|
+
this.publishBackfillStatus();
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
1919
2048
|
this.publishStatus(this.scanStatus.updateAgent(agentName, progress));
|
|
1920
2049
|
}
|
|
1921
2050
|
beginAgentIndexing(agentName) {
|
|
@@ -2040,14 +2169,30 @@ var AgentSyncEngine = class {
|
|
|
2040
2169
|
});
|
|
2041
2170
|
return "committed";
|
|
2042
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* An agent that cannot be reached was never scanned, so there is nothing to
|
|
2174
|
+
* compare against. Publishing an empty result here would diff every known
|
|
2175
|
+
* session into a removal — the same mistake as reading a failed query as an
|
|
2176
|
+
* empty database.
|
|
2177
|
+
*/
|
|
2043
2178
|
refreshUnavailableAgent(agentName) {
|
|
2044
2179
|
this.lastRefreshAtByAgent.set(agentName, Date.now());
|
|
2180
|
+
const previousSessions = this.sessionIndex.snapshot().byAgent[agentName] ?? [];
|
|
2181
|
+
if (previousSessions.length > 0) {
|
|
2182
|
+
appLogger.warn("scan.refresh.agent_unavailable", {
|
|
2183
|
+
agent: agentName,
|
|
2184
|
+
retained_sessions: previousSessions.length
|
|
2185
|
+
});
|
|
2186
|
+
return this.refreshStrategyResult(previousSessions, { status: "unchanged" });
|
|
2187
|
+
}
|
|
2045
2188
|
return this.refreshStrategyResult([]);
|
|
2046
2189
|
}
|
|
2047
2190
|
async initializeAgent(agent, previousSessions) {
|
|
2048
2191
|
this.setScanPhase("initializing");
|
|
2049
2192
|
const scanStartedAt = performance.now();
|
|
2050
|
-
const result = await this.runWorker(agent, previousSessions, null, this.startupScanOptions()
|
|
2193
|
+
const result = await this.runWorker(agent, previousSessions, null, this.startupScanOptions(), {
|
|
2194
|
+
checkpoint: true
|
|
2195
|
+
});
|
|
2051
2196
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2052
2197
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
2053
2198
|
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
@@ -2092,7 +2237,7 @@ var AgentSyncEngine = class {
|
|
|
2092
2237
|
const preciseChangedIds = checkResult.changedIds ?? null;
|
|
2093
2238
|
const scanStartedAt = performance.now();
|
|
2094
2239
|
if (preciseChangedIds === null) {
|
|
2095
|
-
const result = await this.runWorker(agent, baseline, null, {});
|
|
2240
|
+
const result = await this.runWorker(agent, baseline, null, {}, { checkpoint: true });
|
|
2096
2241
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2097
2242
|
const sessions2 = attachMissingProjectIdentities(result.sessions);
|
|
2098
2243
|
return this.refreshStrategyResult(sessions2, {
|
|
@@ -2113,7 +2258,7 @@ var AgentSyncEngine = class {
|
|
|
2113
2258
|
}
|
|
2114
2259
|
async scanAgentFully(agent, previousSessions) {
|
|
2115
2260
|
const scanStartedAt = performance.now();
|
|
2116
|
-
const result = await this.runWorker(agent, previousSessions, null, {});
|
|
2261
|
+
const result = await this.runWorker(agent, previousSessions, null, {}, { checkpoint: true });
|
|
2117
2262
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2118
2263
|
const sessions = attachMissingProjectIdentities(result.sessions);
|
|
2119
2264
|
this.lastRefreshAtByAgent.set(agent.name, Date.now());
|
|
@@ -2140,8 +2285,63 @@ var AgentSyncEngine = class {
|
|
|
2140
2285
|
changedIds,
|
|
2141
2286
|
scanOptions,
|
|
2142
2287
|
sourceSync: workerOptions.sourceSync,
|
|
2288
|
+
backfill: workerOptions.backfill,
|
|
2289
|
+
backfillCursor: workerOptions.backfillCursor,
|
|
2290
|
+
checkpoint: workerOptions.checkpoint,
|
|
2143
2291
|
meta: workerOptions.meta ?? buildAgentCacheMeta(agent),
|
|
2144
|
-
onProgress: (progress) => this.updateAgentScanProgress(agent.name, progress)
|
|
2292
|
+
onProgress: (progress) => this.updateAgentScanProgress(agent.name, progress),
|
|
2293
|
+
onCheckpoint: (checkpoint) => this.handleWorkerCheckpoint(agent, checkpoint)
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
handleWorkerCheckpoint(agent, checkpoint) {
|
|
2297
|
+
if (this.isShuttingDown) return;
|
|
2298
|
+
if (checkpoint.stage === "scanned") {
|
|
2299
|
+
let persisted2 = false;
|
|
2300
|
+
try {
|
|
2301
|
+
persisted2 = saveCachedSessions(agent.name, checkpoint.sessions, checkpoint.meta);
|
|
2302
|
+
if (persisted2) markAgentCacheInitialized(agent.name);
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
appLogger.error("scan.checkpoint.failed", {
|
|
2305
|
+
agent: agent.name,
|
|
2306
|
+
stage: checkpoint.stage,
|
|
2307
|
+
error
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
agent.setSessionMetaMap(new Map(Object.entries(checkpoint.meta)));
|
|
2311
|
+
const event = this.sessionIndex.commitAgentSessions(agent.name, checkpoint.sessions);
|
|
2312
|
+
this.emitSessionsChanged({
|
|
2313
|
+
agentName: agent.name,
|
|
2314
|
+
sessions: this.sessionIndex.snapshot().byAgent[agent.name] ?? [],
|
|
2315
|
+
event
|
|
2316
|
+
});
|
|
2317
|
+
appLogger.info("scan.checkpoint.persisted", {
|
|
2318
|
+
agent: agent.name,
|
|
2319
|
+
stage: checkpoint.stage,
|
|
2320
|
+
sessions: checkpoint.sessions.length,
|
|
2321
|
+
persisted: persisted2
|
|
2322
|
+
});
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
let persisted = false;
|
|
2326
|
+
try {
|
|
2327
|
+
persisted = saveCachedSessionChanges(agent.name, checkpoint.changes, [], checkpoint.meta);
|
|
2328
|
+
} catch (error) {
|
|
2329
|
+
appLogger.error("scan.checkpoint.failed", {
|
|
2330
|
+
agent: agent.name,
|
|
2331
|
+
stage: checkpoint.stage,
|
|
2332
|
+
sessions: checkpoint.changes.length,
|
|
2333
|
+
error
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
if (persisted && checkpoint.backfillCursor) {
|
|
2337
|
+
markAgentFullSyncProgress(agent.name, checkpoint.backfillCursor);
|
|
2338
|
+
}
|
|
2339
|
+
appLogger.debug("scan.checkpoint.persisted", {
|
|
2340
|
+
agent: agent.name,
|
|
2341
|
+
stage: checkpoint.stage,
|
|
2342
|
+
sessions: checkpoint.changes.length,
|
|
2343
|
+
backfill_cursor: checkpoint.backfillCursor,
|
|
2344
|
+
persisted
|
|
2145
2345
|
});
|
|
2146
2346
|
}
|
|
2147
2347
|
needsBackfill(agent) {
|
|
@@ -2149,7 +2349,23 @@ var AgentSyncEngine = class {
|
|
|
2149
2349
|
if (startupScanOptions.from == null && startupScanOptions.to == null) return false;
|
|
2150
2350
|
if (!agent.isAvailable()) return false;
|
|
2151
2351
|
const lastSyncAt = getAgentLastFullSyncAt(agent.name);
|
|
2152
|
-
|
|
2352
|
+
if (lastSyncAt == null || Date.now() - lastSyncAt > BACKFILL_INTERVAL_MS) return true;
|
|
2353
|
+
if (!(agent instanceof FileSystemSessionSource)) return false;
|
|
2354
|
+
if (this.cacheIntegrityCheckedAgents.has(agent.name)) return false;
|
|
2355
|
+
const cached = loadCachedSessions(agent.name);
|
|
2356
|
+
const sourceCount = agent.listSessionSources().length;
|
|
2357
|
+
const cachedCount = cached?.sessions.length ?? 0;
|
|
2358
|
+
this.cacheIntegrityCheckedAgents.add(agent.name);
|
|
2359
|
+
if (sourceCount > 0 && cachedCount / sourceCount < CACHE_TRUNCATION_COVERAGE) {
|
|
2360
|
+
appLogger.warn("scan.backfill.cache_truncated", {
|
|
2361
|
+
agent: agent.name,
|
|
2362
|
+
cached_sessions: cachedCount,
|
|
2363
|
+
source_files: sourceCount,
|
|
2364
|
+
last_sync_at: lastSyncAt
|
|
2365
|
+
});
|
|
2366
|
+
return true;
|
|
2367
|
+
}
|
|
2368
|
+
return false;
|
|
2153
2369
|
}
|
|
2154
2370
|
enqueueBackfill(agentName) {
|
|
2155
2371
|
if (this.isShuttingDown || this.currentBackfillAgent === agentName || this.backfillQueue.includes(agentName)) {
|
|
@@ -2164,10 +2380,12 @@ var AgentSyncEngine = class {
|
|
|
2164
2380
|
const agentName = this.backfillQueue.shift();
|
|
2165
2381
|
if (!agentName) return;
|
|
2166
2382
|
this.currentBackfillAgent = agentName;
|
|
2383
|
+
this.currentBackfillProgress = void 0;
|
|
2167
2384
|
this.publishBackfillStatus();
|
|
2168
2385
|
void this.runBackfill(agentName).then((result) => {
|
|
2169
2386
|
if (this.isShuttingDown) return;
|
|
2170
2387
|
this.currentBackfillAgent = void 0;
|
|
2388
|
+
this.currentBackfillProgress = void 0;
|
|
2171
2389
|
if (result === "committed") {
|
|
2172
2390
|
if (!this.completedBackfillAgents.includes(agentName)) {
|
|
2173
2391
|
this.completedBackfillAgents.push(agentName);
|
|
@@ -2177,6 +2395,7 @@ var AgentSyncEngine = class {
|
|
|
2177
2395
|
);
|
|
2178
2396
|
} else if (!this.failedBackfillAgents.includes(agentName)) {
|
|
2179
2397
|
this.failedBackfillAgents.push(agentName);
|
|
2398
|
+
this.cacheIntegrityCheckedAgents.delete(agentName);
|
|
2180
2399
|
}
|
|
2181
2400
|
this.publishBackfillStatus();
|
|
2182
2401
|
this.pumpBackfillQueue();
|
|
@@ -2193,8 +2412,10 @@ var AgentSyncEngine = class {
|
|
|
2193
2412
|
const cached = loadCachedSessions(agentName);
|
|
2194
2413
|
const baseline = cached?.sessions ?? snapshot.byAgent[agentName] ?? [];
|
|
2195
2414
|
const meta = cached?.meta ?? buildAgentCacheMeta(agent);
|
|
2415
|
+
const backfillCursor = getAgentFullSyncCursor(agentName);
|
|
2196
2416
|
if (cached) restoreAgentCacheMeta(agent, cached);
|
|
2197
2417
|
try {
|
|
2418
|
+
markAgentFullSyncStarted(agentName);
|
|
2198
2419
|
const result = await this.runWorker(
|
|
2199
2420
|
agent,
|
|
2200
2421
|
baseline,
|
|
@@ -2202,11 +2423,16 @@ var AgentSyncEngine = class {
|
|
|
2202
2423
|
{},
|
|
2203
2424
|
{
|
|
2204
2425
|
sourceSync: agent instanceof FileSystemSessionSource,
|
|
2205
|
-
|
|
2426
|
+
backfill: true,
|
|
2427
|
+
backfillCursor,
|
|
2428
|
+
meta,
|
|
2429
|
+
checkpoint: true
|
|
2206
2430
|
}
|
|
2207
2431
|
);
|
|
2208
2432
|
agent.setSessionMetaMap(new Map(Object.entries(result.meta)));
|
|
2209
2433
|
const fullSessions = attachMissingProjectIdentities(result.sessions);
|
|
2434
|
+
this.currentBackfillProgress = { phase: "indexing", sessions: fullSessions.length };
|
|
2435
|
+
this.publishBackfillStatus();
|
|
2210
2436
|
await this.commitSessionPublication({
|
|
2211
2437
|
context: "scan.backfill",
|
|
2212
2438
|
agentName,
|
|
@@ -2240,6 +2466,7 @@ var AgentSyncEngine = class {
|
|
|
2240
2466
|
active: this.currentBackfillAgent != null || this.backfillQueue.length > 0,
|
|
2241
2467
|
pendingAgents: [...this.backfillQueue],
|
|
2242
2468
|
currentAgent: this.currentBackfillAgent,
|
|
2469
|
+
progress: this.currentBackfillProgress,
|
|
2243
2470
|
completedAgents: [...this.completedBackfillAgents],
|
|
2244
2471
|
failedAgents: [...this.failedBackfillAgents]
|
|
2245
2472
|
};
|
|
@@ -2377,8 +2604,12 @@ function isSameOrChildPath(parentPath, childPath) {
|
|
|
2377
2604
|
const path = relative(parentPath, childPath);
|
|
2378
2605
|
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
2379
2606
|
}
|
|
2607
|
+
var SQLITE_SIDECAR_SUFFIXES = ["-wal", "-journal"];
|
|
2608
|
+
function isSqliteSidecarOf(changedPath, targetPath) {
|
|
2609
|
+
return SQLITE_SIDECAR_SUFFIXES.some((suffix) => changedPath === `${targetPath}${suffix}`);
|
|
2610
|
+
}
|
|
2380
2611
|
function isRelatedPath(changedPath, targetPath) {
|
|
2381
|
-
return isSameOrChildPath(targetPath, changedPath) || isSameOrChildPath(changedPath, targetPath);
|
|
2612
|
+
return isSameOrChildPath(targetPath, changedPath) || isSameOrChildPath(changedPath, targetPath) || isSqliteSidecarOf(changedPath, targetPath);
|
|
2382
2613
|
}
|
|
2383
2614
|
function mergeScopes(target, scopes) {
|
|
2384
2615
|
for (const scope of scopes) {
|
|
@@ -2669,6 +2900,9 @@ var ThreadWorkerRunner = class {
|
|
|
2669
2900
|
previousSessions: payload.previousSessions,
|
|
2670
2901
|
changedIds: payload.changedIds,
|
|
2671
2902
|
sourceSync: payload.sourceSync,
|
|
2903
|
+
backfill: payload.backfill,
|
|
2904
|
+
backfillCursor: payload.backfillCursor,
|
|
2905
|
+
checkpoint: payload.checkpoint,
|
|
2672
2906
|
scanOptions: payload.scanOptions,
|
|
2673
2907
|
meta: payload.meta
|
|
2674
2908
|
};
|
|
@@ -2687,7 +2921,8 @@ var ThreadWorkerRunner = class {
|
|
|
2687
2921
|
resolve: resolve4,
|
|
2688
2922
|
reject,
|
|
2689
2923
|
payload,
|
|
2690
|
-
onProgress: payload.onProgress
|
|
2924
|
+
onProgress: payload.onProgress,
|
|
2925
|
+
onCheckpoint: payload.onCheckpoint
|
|
2691
2926
|
});
|
|
2692
2927
|
if (!isNewWorker) {
|
|
2693
2928
|
try {
|
|
@@ -2739,6 +2974,10 @@ var ThreadWorkerRunner = class {
|
|
|
2739
2974
|
pending.onProgress?.(message.progress);
|
|
2740
2975
|
return;
|
|
2741
2976
|
}
|
|
2977
|
+
if (message.type === "checkpoint") {
|
|
2978
|
+
pending.onCheckpoint?.(message.checkpoint);
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2742
2981
|
slot.pending.delete(message.requestId);
|
|
2743
2982
|
if (message.type === "error") {
|
|
2744
2983
|
pending.reject(new Error(message.error));
|
|
@@ -2772,36 +3011,6 @@ var ThreadWorkerRunner = class {
|
|
|
2772
3011
|
|
|
2773
3012
|
// src/live-scan.ts
|
|
2774
3013
|
var NEW_SESSION_EVENT_WINDOW_MS = 250;
|
|
2775
|
-
function mergeEvents(previous, next) {
|
|
2776
|
-
const changedSessionHeads = /* @__PURE__ */ new Map();
|
|
2777
|
-
const removedSessionRefs = /* @__PURE__ */ new Map();
|
|
2778
|
-
const sessionKey = (agentName, sessionId) => `${agentName}\0${sessionId}`;
|
|
2779
|
-
const addChanged = (item) => {
|
|
2780
|
-
const key = sessionKey(item.reference.agentName, item.reference.sessionId);
|
|
2781
|
-
removedSessionRefs.delete(key);
|
|
2782
|
-
changedSessionHeads.set(key, item);
|
|
2783
|
-
};
|
|
2784
|
-
const addRemoved = (item) => {
|
|
2785
|
-
const key = sessionKey(item.agentName, item.sessionId);
|
|
2786
|
-
changedSessionHeads.delete(key);
|
|
2787
|
-
removedSessionRefs.set(key, item);
|
|
2788
|
-
};
|
|
2789
|
-
for (const item of previous.changedSessionHeads) addChanged(item);
|
|
2790
|
-
for (const item of previous.removedSessionRefs) addRemoved(item);
|
|
2791
|
-
for (const item of next.changedSessionHeads) addChanged(item);
|
|
2792
|
-
for (const item of next.removedSessionRefs) addRemoved(item);
|
|
2793
|
-
return {
|
|
2794
|
-
type: "sessions-updated",
|
|
2795
|
-
changedAgents: Array.from(/* @__PURE__ */ new Set([...previous.changedAgents, ...next.changedAgents])),
|
|
2796
|
-
newSessions: previous.newSessions + next.newSessions,
|
|
2797
|
-
updatedSessions: previous.updatedSessions + next.updatedSessions,
|
|
2798
|
-
removedSessions: previous.removedSessions + next.removedSessions,
|
|
2799
|
-
totalSessions: next.totalSessions,
|
|
2800
|
-
timestamp: next.timestamp,
|
|
2801
|
-
changedSessionHeads: [...changedSessionHeads.values()],
|
|
2802
|
-
removedSessionRefs: [...removedSessionRefs.values()]
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
3014
|
var LiveScanStore = class {
|
|
2806
3015
|
watchEnabled;
|
|
2807
3016
|
scanOptions;
|
|
@@ -2854,7 +3063,11 @@ var LiveScanStore = class {
|
|
|
2854
3063
|
});
|
|
2855
3064
|
const snapshot = this.getSnapshot();
|
|
2856
3065
|
const indexStartedAt = performance.now();
|
|
2857
|
-
if (!this.deferInitialRefresh)
|
|
3066
|
+
if (!this.deferInitialRefresh) {
|
|
3067
|
+
await this.syncEngine.syncInitialIndex().catch((error) => {
|
|
3068
|
+
appLogger.error("scan.initial.index_failed", { error });
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
2858
3071
|
const indexDuration = performance.now() - indexStartedAt;
|
|
2859
3072
|
appLogger.info("scan.initial.done", {
|
|
2860
3073
|
duration_ms: Math.round(performance.now() - startedAt),
|
|
@@ -2928,7 +3141,7 @@ var LiveScanStore = class {
|
|
|
2928
3141
|
for (const listener of this.listeners) listener(event);
|
|
2929
3142
|
}
|
|
2930
3143
|
queueEvent(event) {
|
|
2931
|
-
this.pendingEvent = this.pendingEvent ?
|
|
3144
|
+
this.pendingEvent = this.pendingEvent ? mergeSessionsUpdatedEvents(this.pendingEvent, event) : event;
|
|
2932
3145
|
if (this.pendingEventTimer) return;
|
|
2933
3146
|
this.pendingEventTimer = setTimeout(() => {
|
|
2934
3147
|
const pending = this.pendingEvent;
|
|
@@ -2952,11 +3165,11 @@ var LiveScanStore = class {
|
|
|
2952
3165
|
import { consola } from "consola";
|
|
2953
3166
|
|
|
2954
3167
|
// src/version.ts
|
|
2955
|
-
import { readFileSync } from "fs";
|
|
3168
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2956
3169
|
import { resolve as resolve3, dirname as dirname3 } from "path";
|
|
2957
3170
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2958
3171
|
var __dirname = dirname3(fileURLToPath4(import.meta.url));
|
|
2959
|
-
var pkg = JSON.parse(
|
|
3172
|
+
var pkg = JSON.parse(readFileSync2(resolve3(__dirname, "../package.json"), "utf-8"));
|
|
2960
3173
|
var VERSION = pkg.version;
|
|
2961
3174
|
|
|
2962
3175
|
// src/output.ts
|
|
@@ -2978,6 +3191,25 @@ function printScanResults(agents) {
|
|
|
2978
3191
|
consola.log("");
|
|
2979
3192
|
}
|
|
2980
3193
|
|
|
3194
|
+
// src/session-index-output.ts
|
|
3195
|
+
function buildSessionIndexOutput(snapshot, window = {}) {
|
|
3196
|
+
const sessions = filterSessionTreeByActivityWindow(snapshot.sessions, window.from, window.to);
|
|
3197
|
+
const agents = getAgentInfoMap(
|
|
3198
|
+
Object.fromEntries(
|
|
3199
|
+
Object.entries(snapshot.byAgent).map(([name, list]) => [
|
|
3200
|
+
name,
|
|
3201
|
+
filterSessionTreeByActivityWindow(list, window.from, window.to).length
|
|
3202
|
+
])
|
|
3203
|
+
)
|
|
3204
|
+
).map(({ name, displayName, count }) => ({
|
|
3205
|
+
name,
|
|
3206
|
+
displayName,
|
|
3207
|
+
count,
|
|
3208
|
+
available: count > 0
|
|
3209
|
+
}));
|
|
3210
|
+
return { agents, sessions };
|
|
3211
|
+
}
|
|
3212
|
+
|
|
2981
3213
|
// src/runtime-plan.ts
|
|
2982
3214
|
function parseSessionUri(uri) {
|
|
2983
3215
|
const match = uri.match(/^([a-z]+):\/\/(.+)$/i);
|
|
@@ -3003,7 +3235,10 @@ function buildCliRuntimePlan(input, environment) {
|
|
|
3003
3235
|
function resolveStartupUrl(startupUrl, targetSession) {
|
|
3004
3236
|
if (!targetSession) return startupUrl;
|
|
3005
3237
|
const url = new URL(startupUrl);
|
|
3006
|
-
url.pathname =
|
|
3238
|
+
url.pathname = sessionRoutePath({
|
|
3239
|
+
agentName: targetSession.agent,
|
|
3240
|
+
sessionId: targetSession.sessionId
|
|
3241
|
+
});
|
|
3007
3242
|
return url.toString();
|
|
3008
3243
|
}
|
|
3009
3244
|
function redactStartupUrl(startupUrl) {
|
|
@@ -3028,6 +3263,27 @@ function hasExplicitPortArg(argv) {
|
|
|
3028
3263
|
});
|
|
3029
3264
|
}
|
|
3030
3265
|
|
|
3266
|
+
// src/pricing-refresh.ts
|
|
3267
|
+
var REFRESH_TIMEOUT_MS = 1e4;
|
|
3268
|
+
function startPricingRefresh(timeoutMs = REFRESH_TIMEOUT_MS) {
|
|
3269
|
+
const controller = new AbortController();
|
|
3270
|
+
const completion = refreshPricingCache({ signal: controller.signal, timeoutMs }).catch(
|
|
3271
|
+
(error) => {
|
|
3272
|
+
appLogger.warn("pricing.refresh.error", { error });
|
|
3273
|
+
return false;
|
|
3274
|
+
}
|
|
3275
|
+
);
|
|
3276
|
+
return {
|
|
3277
|
+
publish() {
|
|
3278
|
+
if (publishPendingPricing()) appLogger.info("pricing.refresh.published", {});
|
|
3279
|
+
},
|
|
3280
|
+
async cancel() {
|
|
3281
|
+
controller.abort();
|
|
3282
|
+
await completion;
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3031
3287
|
// src/index.ts
|
|
3032
3288
|
var main = defineCommand({
|
|
3033
3289
|
meta: {
|
|
@@ -3052,6 +3308,19 @@ var main = defineCommand({
|
|
|
3052
3308
|
description: "Allow authenticated access when binding to a non-loopback address",
|
|
3053
3309
|
default: false
|
|
3054
3310
|
},
|
|
3311
|
+
"tls-cert": {
|
|
3312
|
+
type: "string",
|
|
3313
|
+
description: "Path to a TLS certificate; serve remote access over HTTPS"
|
|
3314
|
+
},
|
|
3315
|
+
"tls-key": {
|
|
3316
|
+
type: "string",
|
|
3317
|
+
description: "Path to the TLS private key matching --tls-cert"
|
|
3318
|
+
},
|
|
3319
|
+
"trust-proxy": {
|
|
3320
|
+
type: "boolean",
|
|
3321
|
+
description: "A reverse proxy in front of CodeSesh terminates TLS",
|
|
3322
|
+
default: false
|
|
3323
|
+
},
|
|
3055
3324
|
agent: {
|
|
3056
3325
|
type: "string",
|
|
3057
3326
|
alias: "a",
|
|
@@ -3124,6 +3393,18 @@ var main = defineCommand({
|
|
|
3124
3393
|
);
|
|
3125
3394
|
process.exit(1);
|
|
3126
3395
|
}
|
|
3396
|
+
let transport;
|
|
3397
|
+
try {
|
|
3398
|
+
transport = resolveRemoteTransport({
|
|
3399
|
+
hostname,
|
|
3400
|
+
tlsCertPath: args["tls-cert"],
|
|
3401
|
+
tlsKeyPath: args["tls-key"],
|
|
3402
|
+
trustProxy: args["trust-proxy"]
|
|
3403
|
+
});
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3406
|
+
process.exit(1);
|
|
3407
|
+
}
|
|
3127
3408
|
if (trace) {
|
|
3128
3409
|
perf.enable();
|
|
3129
3410
|
}
|
|
@@ -3137,12 +3418,12 @@ var main = defineCommand({
|
|
|
3137
3418
|
log_path: appLogger.getLogPath()
|
|
3138
3419
|
});
|
|
3139
3420
|
if (clearCache) {
|
|
3140
|
-
const { clearCache: clear } = await import("./dist-
|
|
3421
|
+
const { clearCache: clear } = await import("./dist-ALXOBKV2.js");
|
|
3141
3422
|
clear();
|
|
3142
3423
|
appLogger.info("cache.clear");
|
|
3143
3424
|
console.log("Cache cleared.");
|
|
3144
3425
|
}
|
|
3145
|
-
|
|
3426
|
+
const pricingRefresh = startPricingRefresh();
|
|
3146
3427
|
let targetSession = null;
|
|
3147
3428
|
if (args.session) {
|
|
3148
3429
|
targetSession = parseSessionUri(args.session);
|
|
@@ -3186,26 +3467,10 @@ var main = defineCommand({
|
|
|
3186
3467
|
console.log(perf.getReport());
|
|
3187
3468
|
}
|
|
3188
3469
|
if (jsonOnly) {
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
if (listDefaultFrom != null && activity < listDefaultFrom) return false;
|
|
3192
|
-
if (listDefaultTo != null && activity > listDefaultTo) return false;
|
|
3193
|
-
return true;
|
|
3194
|
-
});
|
|
3195
|
-
const info = getAgentInfoMap(
|
|
3196
|
-
Object.fromEntries(Object.entries(result.byAgent).map(([k, v]) => [k, v.length]))
|
|
3197
|
-
);
|
|
3198
|
-
const output = {
|
|
3199
|
-
agents: info.map(({ name, displayName, count }) => ({
|
|
3200
|
-
name,
|
|
3201
|
-
displayName,
|
|
3202
|
-
count,
|
|
3203
|
-
available: count > 0
|
|
3204
|
-
})),
|
|
3205
|
-
sessions: windowed
|
|
3206
|
-
};
|
|
3470
|
+
await pricingRefresh.cancel();
|
|
3471
|
+
const output = buildSessionIndexOutput(result, { from: listDefaultFrom, to: listDefaultTo });
|
|
3207
3472
|
appLogger.info("cli.json_output", {
|
|
3208
|
-
sessions:
|
|
3473
|
+
sessions: output.sessions.length,
|
|
3209
3474
|
duration_ms: Math.round(performance.now() - startedAt)
|
|
3210
3475
|
});
|
|
3211
3476
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -3221,7 +3486,8 @@ var main = defineCommand({
|
|
|
3221
3486
|
defaultSessionDays: listDefaultDays,
|
|
3222
3487
|
portFallbackAttempts: explicitPort ? 1 : DEFAULT_PORT_FALLBACK_ATTEMPTS,
|
|
3223
3488
|
hostname,
|
|
3224
|
-
remoteAccess
|
|
3489
|
+
remoteAccess,
|
|
3490
|
+
transport
|
|
3225
3491
|
});
|
|
3226
3492
|
} catch (error) {
|
|
3227
3493
|
console.error(getServerStartupErrorMessage(error, port));
|
|
@@ -3229,6 +3495,7 @@ var main = defineCommand({
|
|
|
3229
3495
|
}
|
|
3230
3496
|
const { url } = app;
|
|
3231
3497
|
if (!jsonOnly) {
|
|
3498
|
+
pricingRefresh.publish();
|
|
3232
3499
|
store.startBackgroundRefresh();
|
|
3233
3500
|
}
|
|
3234
3501
|
let shuttingDown = false;
|
|
@@ -3236,6 +3503,7 @@ var main = defineCommand({
|
|
|
3236
3503
|
if (shuttingDown) return;
|
|
3237
3504
|
shuttingDown = true;
|
|
3238
3505
|
appLogger.info("cli.shutdown", { signal });
|
|
3506
|
+
await pricingRefresh.cancel();
|
|
3239
3507
|
await app.shutdown();
|
|
3240
3508
|
process.exit(0);
|
|
3241
3509
|
};
|