bertrand 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bertrand.js +76 -45
- package/dist/dashboard/assets/index-DVv_2taj.js +696 -0
- package/dist/dashboard/assets/index-ldnBrPCg.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/run-screen.js +6 -4
- package/package.json +1 -1
- package/dist/dashboard/assets/index-BXNZ0sJ0.css +0 -1
- package/dist/dashboard/assets/index-DM4zHgCh.js +0 -694
package/dist/bertrand.js
CHANGED
|
@@ -919,8 +919,8 @@ function createSession(opts) {
|
|
|
919
919
|
const id = createId();
|
|
920
920
|
return db.insert(sessions).values({ id, ...opts }).returning().get();
|
|
921
921
|
}
|
|
922
|
-
function getSession(id) {
|
|
923
|
-
return
|
|
922
|
+
function getSession(id, db = getDb()) {
|
|
923
|
+
return db.select().from(sessions).where(eq2(sessions.id, id)).get();
|
|
924
924
|
}
|
|
925
925
|
function getSessionByCategorySlug(categoryId, slug) {
|
|
926
926
|
return getDb().select().from(sessions).where(and(eq2(sessions.categoryId, categoryId), eq2(sessions.slug, slug))).get();
|
|
@@ -931,8 +931,11 @@ function getSessionsByCategory(categoryId) {
|
|
|
931
931
|
function getActiveSessions() {
|
|
932
932
|
return getDb().select({ session: sessions, categoryPath: categories.path }).from(sessions).innerJoin(categories, eq2(sessions.categoryId, categories.id)).where(inArray(sessions.status, ["active", "waiting"])).all();
|
|
933
933
|
}
|
|
934
|
-
function
|
|
935
|
-
const
|
|
934
|
+
function countLiveSessions(db = getDb()) {
|
|
935
|
+
const row = db.select({ n: sql2`count(*)` }).from(sessions).where(inArray(sessions.status, ["active", "waiting"])).get();
|
|
936
|
+
return row?.n ?? 0;
|
|
937
|
+
}
|
|
938
|
+
function selectSessions(db, opts) {
|
|
936
939
|
const query = db.select({ session: sessions, categoryPath: categories.path }).from(sessions).innerJoin(categories, eq2(sessions.categoryId, categories.id));
|
|
937
940
|
if (opts?.excludeArchived) {
|
|
938
941
|
return query.where(inArray(sessions.status, [
|
|
@@ -943,6 +946,15 @@ function getAllSessions(opts) {
|
|
|
943
946
|
}
|
|
944
947
|
return query.all();
|
|
945
948
|
}
|
|
949
|
+
function getAllSessions(opts) {
|
|
950
|
+
return selectSessions(getDb(), opts);
|
|
951
|
+
}
|
|
952
|
+
function getAllSessionsForProject(project, opts) {
|
|
953
|
+
return selectSessions(getDbForProject(project.slug), opts).map((s) => ({
|
|
954
|
+
...s,
|
|
955
|
+
project
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
946
958
|
function updateSessionStatus(id, status) {
|
|
947
959
|
return getDb().update(sessions).set({ status, updatedAt: sql2`(datetime('now'))` }).where(eq2(sessions.id, id)).returning().get();
|
|
948
960
|
}
|
|
@@ -1036,11 +1048,11 @@ function insertEvent(opts) {
|
|
|
1036
1048
|
meta: normalizeEventMeta(opts.event, opts.meta)
|
|
1037
1049
|
}).returning().get();
|
|
1038
1050
|
}
|
|
1039
|
-
function getEventsBySession(sessionId) {
|
|
1040
|
-
return
|
|
1051
|
+
function getEventsBySession(sessionId, db = getDb()) {
|
|
1052
|
+
return db.select().from(events).where(eq4(events.sessionId, sessionId)).orderBy(events.createdAt, events.id).all();
|
|
1041
1053
|
}
|
|
1042
|
-
function getEventsByType(sessionId, eventType) {
|
|
1043
|
-
return
|
|
1054
|
+
function getEventsByType(sessionId, eventType, db = getDb()) {
|
|
1055
|
+
return db.select().from(events).where(and3(eq4(events.sessionId, sessionId), eq4(events.event, eventType))).orderBy(events.createdAt).all();
|
|
1044
1056
|
}
|
|
1045
1057
|
function getLatestEventOfType(sessionId, eventType, conversationId) {
|
|
1046
1058
|
const conditions = [
|
|
@@ -1745,8 +1757,8 @@ var init_notify = __esm(() => {
|
|
|
1745
1757
|
|
|
1746
1758
|
// src/db/queries/stats.ts
|
|
1747
1759
|
import { eq as eq5, sql as sql4 } from "drizzle-orm";
|
|
1748
|
-
function getSessionStats(sessionId) {
|
|
1749
|
-
return
|
|
1760
|
+
function getSessionStats(sessionId, db = getDb()) {
|
|
1761
|
+
return db.select().from(sessionStats).where(eq5(sessionStats.sessionId, sessionId)).get();
|
|
1750
1762
|
}
|
|
1751
1763
|
function upsertSessionStats(sessionId, data) {
|
|
1752
1764
|
return getDb().insert(sessionStats).values({
|
|
@@ -1770,8 +1782,8 @@ function lineCount(s) {
|
|
|
1770
1782
|
return s.split(`
|
|
1771
1783
|
`).length;
|
|
1772
1784
|
}
|
|
1773
|
-
function computeDiffStats(sessionId) {
|
|
1774
|
-
const applied = getEventsByType(sessionId, "tool.applied");
|
|
1785
|
+
function computeDiffStats(sessionId, db = getDb()) {
|
|
1786
|
+
const applied = getEventsByType(sessionId, "tool.applied", db);
|
|
1775
1787
|
const files = new Set;
|
|
1776
1788
|
let linesAdded = 0;
|
|
1777
1789
|
let linesRemoved = 0;
|
|
@@ -1796,6 +1808,7 @@ function computeDiffStats(sessionId) {
|
|
|
1796
1808
|
}
|
|
1797
1809
|
var init_diff_stats = __esm(() => {
|
|
1798
1810
|
init_events();
|
|
1811
|
+
init_client();
|
|
1799
1812
|
});
|
|
1800
1813
|
|
|
1801
1814
|
// src/lib/timing.ts
|
|
@@ -1885,12 +1898,12 @@ function computeTimings(events2) {
|
|
|
1885
1898
|
}
|
|
1886
1899
|
return { totalClaudeWorkMs, totalUserWaitMs, activePct, durationS, segments };
|
|
1887
1900
|
}
|
|
1888
|
-
function computeSessionStats(sessionId) {
|
|
1889
|
-
const events2 = getEventsBySession(sessionId);
|
|
1901
|
+
function computeSessionStats(sessionId, db = getDb()) {
|
|
1902
|
+
const events2 = getEventsBySession(sessionId, db);
|
|
1890
1903
|
const summary = computeTimings(events2);
|
|
1891
1904
|
const conversationIds = new Set(events2.filter((e) => e.conversationId).map((e) => e.conversationId));
|
|
1892
1905
|
const interactionCount = events2.filter((e) => e.event === "session.waiting" || e.event === "session.answered").length;
|
|
1893
|
-
const diff = computeDiffStats(sessionId);
|
|
1906
|
+
const diff = computeDiffStats(sessionId, db);
|
|
1894
1907
|
return {
|
|
1895
1908
|
eventCount: events2.length,
|
|
1896
1909
|
conversationCount: conversationIds.size,
|
|
@@ -1916,13 +1929,14 @@ var init_timing = __esm(() => {
|
|
|
1916
1929
|
init_events();
|
|
1917
1930
|
init_stats();
|
|
1918
1931
|
init_diff_stats();
|
|
1932
|
+
init_client();
|
|
1919
1933
|
});
|
|
1920
1934
|
|
|
1921
1935
|
// src/lib/engagement_stats.ts
|
|
1922
1936
|
import { eq as eq6, sql as sql5 } from "drizzle-orm";
|
|
1923
|
-
function aggregateToolUsage(sessionId) {
|
|
1937
|
+
function aggregateToolUsage(sessionId, db) {
|
|
1924
1938
|
const counts = {};
|
|
1925
|
-
const applied = getEventsByType(sessionId, "tool.applied");
|
|
1939
|
+
const applied = getEventsByType(sessionId, "tool.applied", db);
|
|
1926
1940
|
for (const ev of applied) {
|
|
1927
1941
|
const meta = ev.meta;
|
|
1928
1942
|
const permissions = meta?.permissions ?? [];
|
|
@@ -1932,7 +1946,7 @@ function aggregateToolUsage(sessionId) {
|
|
|
1932
1946
|
counts[p.tool] = (counts[p.tool] ?? 0) + (p.count ?? 1);
|
|
1933
1947
|
}
|
|
1934
1948
|
}
|
|
1935
|
-
const used = getEventsByType(sessionId, "tool.used");
|
|
1949
|
+
const used = getEventsByType(sessionId, "tool.used", db);
|
|
1936
1950
|
for (const ev of used) {
|
|
1937
1951
|
const meta = ev.meta;
|
|
1938
1952
|
const tool = meta?.tool;
|
|
@@ -1942,8 +1956,8 @@ function aggregateToolUsage(sessionId) {
|
|
|
1942
1956
|
}
|
|
1943
1957
|
return counts;
|
|
1944
1958
|
}
|
|
1945
|
-
function discardRate(sessionId) {
|
|
1946
|
-
const row =
|
|
1959
|
+
function discardRate(sessionId, db) {
|
|
1960
|
+
const row = db.select({
|
|
1947
1961
|
total: sql5`count(*)`,
|
|
1948
1962
|
discarded: sql5`sum(case when ${conversations.discarded} then 1 else 0 end)`
|
|
1949
1963
|
}).from(conversations).where(eq6(conversations.sessionId, sessionId)).get();
|
|
@@ -1952,10 +1966,10 @@ function discardRate(sessionId) {
|
|
|
1952
1966
|
discarded: row?.discarded ?? 0
|
|
1953
1967
|
};
|
|
1954
1968
|
}
|
|
1955
|
-
function computeEngagementStats(sessionId) {
|
|
1969
|
+
function computeEngagementStats(sessionId, db = getDb()) {
|
|
1956
1970
|
return {
|
|
1957
|
-
toolUsage: aggregateToolUsage(sessionId),
|
|
1958
|
-
discardRate: discardRate(sessionId)
|
|
1971
|
+
toolUsage: aggregateToolUsage(sessionId, db),
|
|
1972
|
+
discardRate: discardRate(sessionId, db)
|
|
1959
1973
|
};
|
|
1960
1974
|
}
|
|
1961
1975
|
var init_engagement_stats = __esm(() => {
|
|
@@ -2008,13 +2022,28 @@ var init_session_archive = __esm(() => {
|
|
|
2008
2022
|
import { execFile } from "child_process";
|
|
2009
2023
|
import { existsSync as existsSync6 } from "fs";
|
|
2010
2024
|
import { join as join8 } from "path";
|
|
2011
|
-
function liveStats(sessionId) {
|
|
2025
|
+
function liveStats(sessionId, db) {
|
|
2012
2026
|
return {
|
|
2013
2027
|
sessionId,
|
|
2014
|
-
...computeSessionStats(sessionId),
|
|
2028
|
+
...computeSessionStats(sessionId, db),
|
|
2015
2029
|
updatedAt: new Date().toISOString()
|
|
2016
2030
|
};
|
|
2017
2031
|
}
|
|
2032
|
+
function resolveProjectScope(url) {
|
|
2033
|
+
const nameBySlug = new Map(listProjects().map((p) => [p.slug, p.name]));
|
|
2034
|
+
const param = url.searchParams.get("projects");
|
|
2035
|
+
if (param === null) {
|
|
2036
|
+
const active = resolveActiveProject();
|
|
2037
|
+
return [{ slug: active.slug, name: active.name }];
|
|
2038
|
+
}
|
|
2039
|
+
return param.split(",").map((s) => s.trim()).filter((slug) => nameBySlug.has(slug)).map((slug) => ({ slug, name: nameBySlug.get(slug) }));
|
|
2040
|
+
}
|
|
2041
|
+
function resolveDb(url) {
|
|
2042
|
+
const slug = url.searchParams.get("project");
|
|
2043
|
+
if (slug && projectExists(slug))
|
|
2044
|
+
return getDbForProject(slug);
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2018
2047
|
function archiveResponse(result) {
|
|
2019
2048
|
if (result.ok)
|
|
2020
2049
|
return Response.json(result.session);
|
|
@@ -2158,42 +2187,44 @@ function startServer(port = PORT) {
|
|
|
2158
2187
|
}
|
|
2159
2188
|
var PORT, listSessions = (_params, url) => {
|
|
2160
2189
|
const excludeArchived = url.searchParams.get("excludeArchived") !== "false";
|
|
2161
|
-
return
|
|
2190
|
+
return resolveProjectScope(url).flatMap((project) => getAllSessionsForProject(project, { excludeArchived }));
|
|
2162
2191
|
}, getSessionById = ({ id }) => getSession(id), listEvents = ({ sessionId }, url) => {
|
|
2192
|
+
const db = resolveDb(url);
|
|
2163
2193
|
const eventType = url.searchParams.get("type");
|
|
2164
2194
|
if (eventType)
|
|
2165
|
-
return getEventsByType(sessionId, eventType);
|
|
2166
|
-
return getEventsBySession(sessionId);
|
|
2167
|
-
}, listAllStats = () => {
|
|
2195
|
+
return getEventsByType(sessionId, eventType, db);
|
|
2196
|
+
return getEventsBySession(sessionId, db);
|
|
2197
|
+
}, listAllStats = (_params, url) => {
|
|
2168
2198
|
const result = {};
|
|
2169
|
-
for (const
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2199
|
+
for (const project of resolveProjectScope(url)) {
|
|
2200
|
+
const db = getDbForProject(project.slug);
|
|
2201
|
+
for (const { session } of getAllSessionsForProject(project)) {
|
|
2202
|
+
const isLive = session.status === "active" || session.status === "waiting";
|
|
2203
|
+
if (isLive) {
|
|
2204
|
+
result[session.id] = liveStats(session.id, db);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
result[session.id] = getSessionStats(session.id, db) ?? liveStats(session.id, db);
|
|
2174
2208
|
}
|
|
2175
|
-
result[session.id] = getSessionStats(session.id) ?? liveStats(session.id);
|
|
2176
2209
|
}
|
|
2177
2210
|
return result;
|
|
2178
|
-
}, getStatsBySession = ({
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
const session = getSession(sessionId);
|
|
2211
|
+
}, getStatsBySession = ({ sessionId }, url) => {
|
|
2212
|
+
const db = resolveDb(url);
|
|
2213
|
+
const session = getSession(sessionId, db);
|
|
2182
2214
|
if (!session)
|
|
2183
2215
|
return null;
|
|
2184
2216
|
const isLive = session.status === "active" || session.status === "waiting";
|
|
2185
2217
|
if (isLive)
|
|
2186
|
-
return liveStats(sessionId);
|
|
2187
|
-
return getSessionStats(sessionId) ?? liveStats(sessionId);
|
|
2188
|
-
}, getEngagement = ({
|
|
2189
|
-
sessionId
|
|
2190
|
-
}) => computeEngagementStats(sessionId), listAllProjects = () => {
|
|
2218
|
+
return liveStats(sessionId, db);
|
|
2219
|
+
return getSessionStats(sessionId, db) ?? liveStats(sessionId, db);
|
|
2220
|
+
}, getEngagement = ({ sessionId }, url) => computeEngagementStats(sessionId, resolveDb(url)), listAllProjects = () => {
|
|
2191
2221
|
const active = resolveActiveProject();
|
|
2192
2222
|
return listProjects().map((p) => ({
|
|
2193
2223
|
slug: p.slug,
|
|
2194
2224
|
name: p.name,
|
|
2195
2225
|
active: p.slug === active.slug,
|
|
2196
|
-
lastUsedAt: p.lastUsedAt
|
|
2226
|
+
lastUsedAt: p.lastUsedAt,
|
|
2227
|
+
liveCount: countLiveSessions(getDbForProject(p.slug))
|
|
2197
2228
|
}));
|
|
2198
2229
|
}, getActiveProjectMeta = () => {
|
|
2199
2230
|
const active = resolveActiveProject();
|