bertrand 0.22.1 → 0.22.2
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 +38 -580
- package/dist/dashboard/assets/index-CCHVGoKS.js +654 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/migrations/0005_drop_pr_count.sql +1 -0
- package/dist/migrations/0006_drop_worktrees_last_question.sql +2 -0
- package/dist/migrations/meta/_journal.json +14 -0
- package/dist/run-screen.js +0 -15
- package/package.json +1 -1
- package/dist/dashboard/assets/index-SB5vH7Ec.js +0 -684
package/dist/bertrand.js
CHANGED
|
@@ -599,8 +599,6 @@ var init_router = __esm(() => {
|
|
|
599
599
|
aliases = new Map;
|
|
600
600
|
HOOK_COMMANDS = new Set([
|
|
601
601
|
"update",
|
|
602
|
-
"snapshot",
|
|
603
|
-
"recap-thinking",
|
|
604
602
|
"assistant-message",
|
|
605
603
|
"notify",
|
|
606
604
|
"badge"
|
|
@@ -610,7 +608,6 @@ var init_router = __esm(() => {
|
|
|
610
608
|
// src/db/schema.ts
|
|
611
609
|
var exports_schema = {};
|
|
612
610
|
__export(exports_schema, {
|
|
613
|
-
worktreeAssociations: () => worktreeAssociations,
|
|
614
611
|
sessions: () => sessions,
|
|
615
612
|
sessionStats: () => sessionStats,
|
|
616
613
|
sessionLabels: () => sessionLabels,
|
|
@@ -621,7 +618,7 @@ __export(exports_schema, {
|
|
|
621
618
|
});
|
|
622
619
|
import { sqliteTable, text, integer, index, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
623
620
|
import { sql } from "drizzle-orm";
|
|
624
|
-
var categories, labels, sessions, sessionLabels, conversations, events,
|
|
621
|
+
var categories, labels, sessions, sessionLabels, conversations, events, sessionStats;
|
|
625
622
|
var init_schema = __esm(() => {
|
|
626
623
|
categories = sqliteTable("categories", {
|
|
627
624
|
id: text("id").primaryKey(),
|
|
@@ -677,7 +674,6 @@ var init_schema = __esm(() => {
|
|
|
677
674
|
startedAt: text("started_at").notNull().default(sql`(datetime('now'))`),
|
|
678
675
|
endedAt: text("ended_at"),
|
|
679
676
|
discarded: integer("discarded", { mode: "boolean" }).notNull().default(false),
|
|
680
|
-
lastQuestion: text("last_question"),
|
|
681
677
|
eventCount: integer("event_count").notNull().default(0)
|
|
682
678
|
}, (t) => [index("conv_session").on(t.sessionId)]);
|
|
683
679
|
events = sqliteTable("events", {
|
|
@@ -694,24 +690,11 @@ var init_schema = __esm(() => {
|
|
|
694
690
|
index("ev_event_created").on(t.event, t.createdAt),
|
|
695
691
|
index("ev_conversation").on(t.conversationId)
|
|
696
692
|
]);
|
|
697
|
-
worktreeAssociations = sqliteTable("worktree_associations", {
|
|
698
|
-
id: text("id").primaryKey(),
|
|
699
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
700
|
-
branch: text("branch").notNull(),
|
|
701
|
-
worktreePath: text("worktree_path"),
|
|
702
|
-
active: integer("active", { mode: "boolean" }).notNull().default(true),
|
|
703
|
-
enteredAt: text("entered_at").notNull().default(sql`(datetime('now'))`),
|
|
704
|
-
exitedAt: text("exited_at")
|
|
705
|
-
}, (t) => [
|
|
706
|
-
index("wt_session").on(t.sessionId),
|
|
707
|
-
index("wt_active").on(t.active)
|
|
708
|
-
]);
|
|
709
693
|
sessionStats = sqliteTable("session_stats", {
|
|
710
694
|
sessionId: text("session_id").primaryKey().references(() => sessions.id, { onDelete: "cascade" }),
|
|
711
695
|
eventCount: integer("event_count").notNull().default(0),
|
|
712
696
|
conversationCount: integer("conversation_count").notNull().default(0),
|
|
713
697
|
interactionCount: integer("interaction_count").notNull().default(0),
|
|
714
|
-
prCount: integer("pr_count").notNull().default(0),
|
|
715
698
|
claudeWorkS: integer("claude_work_s").notNull().default(0),
|
|
716
699
|
userWaitS: integer("user_wait_s").notNull().default(0),
|
|
717
700
|
activePct: integer("active_pct").notNull().default(0),
|
|
@@ -859,9 +842,6 @@ function getConversationsBySession(sessionId) {
|
|
|
859
842
|
function endConversation(id) {
|
|
860
843
|
return getDb().update(conversations).set({ endedAt: sql3`(datetime('now'))` }).where(eq2(conversations.id, id)).returning().get();
|
|
861
844
|
}
|
|
862
|
-
function updateLastQuestion(id, question) {
|
|
863
|
-
return getDb().update(conversations).set({ lastQuestion: question }).where(eq2(conversations.id, id)).returning().get();
|
|
864
|
-
}
|
|
865
845
|
var init_conversations = __esm(() => {
|
|
866
846
|
init_client();
|
|
867
847
|
init_schema();
|
|
@@ -966,50 +946,6 @@ var init_events = __esm(() => {
|
|
|
966
946
|
});
|
|
967
947
|
|
|
968
948
|
// src/db/events/emit.ts
|
|
969
|
-
function emitSessionStarted(args) {
|
|
970
|
-
return insertEvent({
|
|
971
|
-
sessionId: args.sessionId,
|
|
972
|
-
conversationId: args.conversationId,
|
|
973
|
-
event: "session.started",
|
|
974
|
-
meta: {
|
|
975
|
-
category_path: args.categoryPath,
|
|
976
|
-
session_name: args.sessionName,
|
|
977
|
-
session_slug: args.sessionSlug,
|
|
978
|
-
labels: args.labels,
|
|
979
|
-
summary: args.summary ?? null
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
function emitSessionResumed(args) {
|
|
984
|
-
return insertEvent({
|
|
985
|
-
sessionId: args.sessionId,
|
|
986
|
-
conversationId: args.conversationId,
|
|
987
|
-
event: "session.resumed",
|
|
988
|
-
meta: { claude_id: args.conversationId }
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
function emitSessionPaused(args) {
|
|
992
|
-
return insertEvent({
|
|
993
|
-
sessionId: args.sessionId,
|
|
994
|
-
conversationId: args.conversationId,
|
|
995
|
-
event: "session.paused",
|
|
996
|
-
meta: { claude_id: args.conversationId }
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
function emitSessionPausedByRecovery(args) {
|
|
1000
|
-
return insertEvent({
|
|
1001
|
-
sessionId: args.sessionId,
|
|
1002
|
-
event: "session.paused",
|
|
1003
|
-
summary: "Recovered from stale state (process not found)",
|
|
1004
|
-
meta: { stale_pid: args.stalePid }
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
function emitSessionEnded(args) {
|
|
1008
|
-
return insertEvent({
|
|
1009
|
-
sessionId: args.sessionId,
|
|
1010
|
-
event: "session.end"
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
949
|
function emitClaudeStarted(args) {
|
|
1014
950
|
return insertEvent({
|
|
1015
951
|
sessionId: args.sessionId,
|
|
@@ -1017,9 +953,6 @@ function emitClaudeStarted(args) {
|
|
|
1017
953
|
event: "claude.started",
|
|
1018
954
|
meta: {
|
|
1019
955
|
claude_id: args.conversationId,
|
|
1020
|
-
model: args.model,
|
|
1021
|
-
claude_version: args.claudeVersion,
|
|
1022
|
-
git: args.git,
|
|
1023
956
|
cwd: args.cwd
|
|
1024
957
|
}
|
|
1025
958
|
});
|
|
@@ -1073,31 +1006,6 @@ function emitSessionRecap(args) {
|
|
|
1073
1006
|
meta: { recap: args.recap, claude_id: args.conversationId }
|
|
1074
1007
|
});
|
|
1075
1008
|
}
|
|
1076
|
-
function emitPermissionRequested(args) {
|
|
1077
|
-
return insertEvent({
|
|
1078
|
-
sessionId: args.sessionId,
|
|
1079
|
-
conversationId: args.conversationId,
|
|
1080
|
-
event: "permission.request",
|
|
1081
|
-
meta: {
|
|
1082
|
-
tool: args.tool,
|
|
1083
|
-
detail: args.detail,
|
|
1084
|
-
claude_id: args.conversationId
|
|
1085
|
-
}
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
function emitPermissionResolved(args) {
|
|
1089
|
-
return insertEvent({
|
|
1090
|
-
sessionId: args.sessionId,
|
|
1091
|
-
conversationId: args.conversationId,
|
|
1092
|
-
event: "permission.resolve",
|
|
1093
|
-
meta: {
|
|
1094
|
-
tool: args.tool,
|
|
1095
|
-
detail: args.detail,
|
|
1096
|
-
outcome: args.outcome,
|
|
1097
|
-
claude_id: args.conversationId
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
1009
|
function emitToolUsed(args) {
|
|
1102
1010
|
const summary = formatToolSummary(args.tool, args.detail);
|
|
1103
1011
|
return insertEvent({
|
|
@@ -1171,23 +1079,6 @@ function emitAssistantRecap(args) {
|
|
|
1171
1079
|
meta: { recap: args.recap, claude_id: args.conversationId }
|
|
1172
1080
|
});
|
|
1173
1081
|
}
|
|
1174
|
-
function emitContextSnapshot(args) {
|
|
1175
|
-
return insertEvent({
|
|
1176
|
-
sessionId: args.sessionId,
|
|
1177
|
-
conversationId: args.conversationId,
|
|
1178
|
-
event: "context.snapshot",
|
|
1179
|
-
summary: `${args.remainingPct}% remaining`,
|
|
1180
|
-
meta: {
|
|
1181
|
-
model: args.model,
|
|
1182
|
-
input_tokens: String(args.inputTokens),
|
|
1183
|
-
cache_creation_tokens: String(args.cacheCreationTokens),
|
|
1184
|
-
cache_read_tokens: String(args.cacheReadTokens),
|
|
1185
|
-
context_window_tokens: String(args.totalContextTokens),
|
|
1186
|
-
remaining_pct: String(args.remainingPct),
|
|
1187
|
-
claude_id: args.conversationId
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
1082
|
var init_emit = __esm(() => {
|
|
1192
1083
|
init_events();
|
|
1193
1084
|
});
|
|
@@ -1237,26 +1128,6 @@ function dispatchHookEvent(event, ctx) {
|
|
|
1237
1128
|
recap: String(meta.recap ?? "")
|
|
1238
1129
|
});
|
|
1239
1130
|
return true;
|
|
1240
|
-
case "session.paused":
|
|
1241
|
-
emitSessionPaused({ sessionId, conversationId });
|
|
1242
|
-
return true;
|
|
1243
|
-
case "permission.request":
|
|
1244
|
-
emitPermissionRequested({
|
|
1245
|
-
sessionId,
|
|
1246
|
-
conversationId,
|
|
1247
|
-
tool: String(meta.tool ?? ""),
|
|
1248
|
-
detail: String(meta.detail ?? "")
|
|
1249
|
-
});
|
|
1250
|
-
return true;
|
|
1251
|
-
case "permission.resolve":
|
|
1252
|
-
emitPermissionResolved({
|
|
1253
|
-
sessionId,
|
|
1254
|
-
conversationId,
|
|
1255
|
-
tool: String(meta.tool ?? ""),
|
|
1256
|
-
detail: String(meta.detail ?? ""),
|
|
1257
|
-
outcome: meta.outcome === "denied" ? "denied" : "approved"
|
|
1258
|
-
});
|
|
1259
|
-
return true;
|
|
1260
1131
|
case "tool.applied":
|
|
1261
1132
|
emitToolApplied({
|
|
1262
1133
|
sessionId,
|
|
@@ -1283,15 +1154,11 @@ var init_update = __esm(() => {
|
|
|
1283
1154
|
init_router();
|
|
1284
1155
|
init_sessions();
|
|
1285
1156
|
init_conversations();
|
|
1286
|
-
init_trigger();
|
|
1287
1157
|
init_emit();
|
|
1288
1158
|
EVENT_STATUS_MAP = {
|
|
1289
1159
|
"session.waiting": "waiting",
|
|
1290
1160
|
"session.answered": "active",
|
|
1291
|
-
"session.
|
|
1292
|
-
"session.paused": "paused",
|
|
1293
|
-
"session.started": "active",
|
|
1294
|
-
"session.end": "paused"
|
|
1161
|
+
"session.paused": "paused"
|
|
1295
1162
|
};
|
|
1296
1163
|
register("update", async (args) => {
|
|
1297
1164
|
let sessionId = "";
|
|
@@ -1340,24 +1207,15 @@ var init_update = __esm(() => {
|
|
|
1340
1207
|
}
|
|
1341
1208
|
const rawConvoId = meta?.claude_id || process.env.BERTRAND_CLAUDE_ID || undefined;
|
|
1342
1209
|
const conversationId = rawConvoId && getConversation(rawConvoId) ? rawConvoId : undefined;
|
|
1343
|
-
|
|
1210
|
+
dispatchHookEvent(event, {
|
|
1344
1211
|
sessionId,
|
|
1345
1212
|
conversationId,
|
|
1346
1213
|
meta,
|
|
1347
1214
|
summary: summaryArg
|
|
1348
1215
|
});
|
|
1349
|
-
if (!dispatched) {
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
1216
|
if (newStatus && !ignoreStatusFlip) {
|
|
1353
1217
|
updateSessionStatus(sessionId, newStatus);
|
|
1354
1218
|
}
|
|
1355
|
-
if (event === "session.waiting" && conversationId && meta?.question) {
|
|
1356
|
-
updateLastQuestion(conversationId, meta.question);
|
|
1357
|
-
}
|
|
1358
|
-
if (event === "session.end") {
|
|
1359
|
-
triggerBackgroundPush();
|
|
1360
|
-
}
|
|
1361
1219
|
});
|
|
1362
1220
|
});
|
|
1363
1221
|
|
|
@@ -1365,13 +1223,6 @@ var init_update = __esm(() => {
|
|
|
1365
1223
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1366
1224
|
import { homedir as homedir2 } from "os";
|
|
1367
1225
|
import { join as join6 } from "path";
|
|
1368
|
-
function getContextWindowSize(model) {
|
|
1369
|
-
for (const [prefix, size] of Object.entries(CONTEXT_WINDOW_SIZES)) {
|
|
1370
|
-
if (model.startsWith(prefix))
|
|
1371
|
-
return size;
|
|
1372
|
-
}
|
|
1373
|
-
return 200000;
|
|
1374
|
-
}
|
|
1375
1226
|
function claudeTranscriptPath(sessionId, cwd) {
|
|
1376
1227
|
const dir = (cwd ?? process.cwd()).replace(/\//g, "-");
|
|
1377
1228
|
return join6(homedir2(), ".claude", "projects", dir, `${sessionId}.jsonl`);
|
|
@@ -1439,108 +1290,7 @@ function getLatestAssistantTurn(filePath) {
|
|
|
1439
1290
|
thinkingBytes
|
|
1440
1291
|
};
|
|
1441
1292
|
}
|
|
1442
|
-
|
|
1443
|
-
if (!existsSync5(filePath))
|
|
1444
|
-
return null;
|
|
1445
|
-
const text2 = readFileSync4(filePath, "utf-8");
|
|
1446
|
-
const lines = text2.split(`
|
|
1447
|
-
`);
|
|
1448
|
-
for (let i = lines.length - 1;i >= 0; i--) {
|
|
1449
|
-
const line = lines[i];
|
|
1450
|
-
if (!line)
|
|
1451
|
-
continue;
|
|
1452
|
-
let entry;
|
|
1453
|
-
try {
|
|
1454
|
-
entry = JSON.parse(line);
|
|
1455
|
-
} catch {
|
|
1456
|
-
continue;
|
|
1457
|
-
}
|
|
1458
|
-
if (entry.type !== "assistant")
|
|
1459
|
-
continue;
|
|
1460
|
-
const message = entry.message;
|
|
1461
|
-
const usage = message?.usage;
|
|
1462
|
-
if (!usage)
|
|
1463
|
-
continue;
|
|
1464
|
-
const model = message?.model ?? "";
|
|
1465
|
-
const inputTokens = usage.input_tokens ?? 0;
|
|
1466
|
-
const outputTokens = usage.output_tokens ?? 0;
|
|
1467
|
-
const cacheCreationTokens = usage.cache_creation_input_tokens ?? 0;
|
|
1468
|
-
const cacheReadTokens = usage.cache_read_input_tokens ?? 0;
|
|
1469
|
-
const totalContextTokens = inputTokens + cacheCreationTokens + cacheReadTokens;
|
|
1470
|
-
const windowSize = getContextWindowSize(model);
|
|
1471
|
-
const remainingPct = Math.max(0, Math.min(100, Math.round(100 - totalContextTokens * 100 / windowSize)));
|
|
1472
|
-
return {
|
|
1473
|
-
model,
|
|
1474
|
-
inputTokens,
|
|
1475
|
-
outputTokens,
|
|
1476
|
-
cacheCreationTokens,
|
|
1477
|
-
cacheReadTokens,
|
|
1478
|
-
totalContextTokens,
|
|
1479
|
-
remainingPct
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
return null;
|
|
1483
|
-
}
|
|
1484
|
-
var CONTEXT_WINDOW_SIZES;
|
|
1485
|
-
var init_transcript = __esm(() => {
|
|
1486
|
-
CONTEXT_WINDOW_SIZES = {
|
|
1487
|
-
"claude-opus-4": 1e6,
|
|
1488
|
-
"claude-sonnet-4": 200000,
|
|
1489
|
-
"claude-haiku-4": 200000
|
|
1490
|
-
};
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
// src/cli/commands/snapshot.ts
|
|
1494
|
-
var exports_snapshot = {};
|
|
1495
|
-
var init_snapshot = __esm(() => {
|
|
1496
|
-
init_router();
|
|
1497
|
-
init_sessions();
|
|
1498
|
-
init_conversations();
|
|
1499
|
-
init_emit();
|
|
1500
|
-
init_transcript();
|
|
1501
|
-
register("snapshot", async (args) => {
|
|
1502
|
-
let sessionId = "";
|
|
1503
|
-
let transcriptPath = "";
|
|
1504
|
-
let conversationId = "";
|
|
1505
|
-
for (let i = 0;i < args.length; i++) {
|
|
1506
|
-
const arg = args[i];
|
|
1507
|
-
const next = args[i + 1];
|
|
1508
|
-
if (arg === "--session-id" && next) {
|
|
1509
|
-
sessionId = next;
|
|
1510
|
-
i++;
|
|
1511
|
-
} else if (arg === "--transcript-path" && next) {
|
|
1512
|
-
transcriptPath = next;
|
|
1513
|
-
i++;
|
|
1514
|
-
} else if (arg === "--conversation-id" && next) {
|
|
1515
|
-
conversationId = next;
|
|
1516
|
-
i++;
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
if (!sessionId || !transcriptPath) {
|
|
1520
|
-
console.error("Usage: bertrand snapshot --session-id <id> --transcript-path <path> [--conversation-id <id>]");
|
|
1521
|
-
process.exit(1);
|
|
1522
|
-
}
|
|
1523
|
-
const session = getSession(sessionId);
|
|
1524
|
-
if (!session) {
|
|
1525
|
-
console.error(`Session not found: ${sessionId}`);
|
|
1526
|
-
process.exit(1);
|
|
1527
|
-
}
|
|
1528
|
-
const snapshot = getContextSnapshot(transcriptPath);
|
|
1529
|
-
if (!snapshot)
|
|
1530
|
-
return;
|
|
1531
|
-
const convoId = conversationId && getConversation(conversationId) ? conversationId : undefined;
|
|
1532
|
-
emitContextSnapshot({
|
|
1533
|
-
sessionId,
|
|
1534
|
-
conversationId: convoId,
|
|
1535
|
-
model: snapshot.model,
|
|
1536
|
-
inputTokens: snapshot.inputTokens,
|
|
1537
|
-
cacheCreationTokens: snapshot.cacheCreationTokens,
|
|
1538
|
-
cacheReadTokens: snapshot.cacheReadTokens,
|
|
1539
|
-
totalContextTokens: snapshot.totalContextTokens,
|
|
1540
|
-
remainingPct: snapshot.remainingPct
|
|
1541
|
-
});
|
|
1542
|
-
});
|
|
1543
|
-
});
|
|
1293
|
+
var init_transcript = () => {};
|
|
1544
1294
|
|
|
1545
1295
|
// src/cli/commands/assistant-message.ts
|
|
1546
1296
|
var exports_assistant_message = {};
|
|
@@ -1624,56 +1374,6 @@ var init_assistant_message = __esm(() => {
|
|
|
1624
1374
|
});
|
|
1625
1375
|
});
|
|
1626
1376
|
|
|
1627
|
-
// src/cli/commands/recap-thinking.ts
|
|
1628
|
-
var exports_recap_thinking = {};
|
|
1629
|
-
var RECAP_RE2;
|
|
1630
|
-
var init_recap_thinking = __esm(() => {
|
|
1631
|
-
init_router();
|
|
1632
|
-
init_sessions();
|
|
1633
|
-
init_conversations();
|
|
1634
|
-
init_emit();
|
|
1635
|
-
init_transcript();
|
|
1636
|
-
RECAP_RE2 = /<recap>([\s\S]*?)<\/recap>/i;
|
|
1637
|
-
register("recap-thinking", async (args) => {
|
|
1638
|
-
let sessionId = "";
|
|
1639
|
-
let transcriptPath = "";
|
|
1640
|
-
let conversationId = "";
|
|
1641
|
-
for (let i = 0;i < args.length; i++) {
|
|
1642
|
-
const arg = args[i];
|
|
1643
|
-
const next = args[i + 1];
|
|
1644
|
-
if (arg === "--session-id" && next) {
|
|
1645
|
-
sessionId = next;
|
|
1646
|
-
i++;
|
|
1647
|
-
} else if (arg === "--transcript-path" && next) {
|
|
1648
|
-
transcriptPath = next;
|
|
1649
|
-
i++;
|
|
1650
|
-
} else if (arg === "--conversation-id" && next) {
|
|
1651
|
-
conversationId = next;
|
|
1652
|
-
i++;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
if (!sessionId || !transcriptPath) {
|
|
1656
|
-
console.error("Usage: bertrand recap-thinking --session-id <id> --transcript-path <path> [--conversation-id <id>]");
|
|
1657
|
-
process.exit(1);
|
|
1658
|
-
}
|
|
1659
|
-
if (!getSession(sessionId))
|
|
1660
|
-
return;
|
|
1661
|
-
const turn = getLatestAssistantTurn(transcriptPath);
|
|
1662
|
-
if (!turn?.text)
|
|
1663
|
-
return;
|
|
1664
|
-
const match = turn.text.match(RECAP_RE2);
|
|
1665
|
-
const recap = match?.[1]?.trim();
|
|
1666
|
-
if (!recap)
|
|
1667
|
-
return;
|
|
1668
|
-
const convoId = conversationId && getConversation(conversationId) ? conversationId : undefined;
|
|
1669
|
-
emitAssistantRecap({
|
|
1670
|
-
sessionId,
|
|
1671
|
-
conversationId: convoId,
|
|
1672
|
-
recap
|
|
1673
|
-
});
|
|
1674
|
-
});
|
|
1675
|
-
});
|
|
1676
|
-
|
|
1677
1377
|
// src/terminal/wave.ts
|
|
1678
1378
|
import { execSync } from "child_process";
|
|
1679
1379
|
|
|
@@ -1935,7 +1635,6 @@ function computeTimings(events2) {
|
|
|
1935
1635
|
function computeSessionStats(sessionId) {
|
|
1936
1636
|
const events2 = getEventsBySession(sessionId);
|
|
1937
1637
|
const summary = computeTimings(events2);
|
|
1938
|
-
const prEvents = getEventsByType(sessionId, "gh.pr.created");
|
|
1939
1638
|
const conversationIds = new Set(events2.filter((e) => e.conversationId).map((e) => e.conversationId));
|
|
1940
1639
|
const interactionCount = events2.filter((e) => e.event === "session.waiting" || e.event === "session.answered").length;
|
|
1941
1640
|
const diff = computeDiffStats(sessionId);
|
|
@@ -1943,7 +1642,6 @@ function computeSessionStats(sessionId) {
|
|
|
1943
1642
|
eventCount: events2.length,
|
|
1944
1643
|
conversationCount: conversationIds.size,
|
|
1945
1644
|
interactionCount,
|
|
1946
|
-
prCount: prEvents.length,
|
|
1947
1645
|
claudeWorkS: Math.round(summary.totalClaudeWorkMs / 1000),
|
|
1948
1646
|
userWaitS: Math.round(summary.totalUserWaitMs / 1000),
|
|
1949
1647
|
activePct: summary.activePct,
|
|
@@ -1981,14 +1679,6 @@ function aggregateToolUsage(sessionId) {
|
|
|
1981
1679
|
counts[p.tool] = (counts[p.tool] ?? 0) + (p.count ?? 1);
|
|
1982
1680
|
}
|
|
1983
1681
|
}
|
|
1984
|
-
const resolves = getEventsByType(sessionId, "permission.resolve");
|
|
1985
|
-
for (const ev of resolves) {
|
|
1986
|
-
const meta = ev.meta;
|
|
1987
|
-
const tool = meta?.tool;
|
|
1988
|
-
if (!tool)
|
|
1989
|
-
continue;
|
|
1990
|
-
counts[tool] = (counts[tool] ?? 0) + 1;
|
|
1991
|
-
}
|
|
1992
1682
|
const used = getEventsByType(sessionId, "tool.used");
|
|
1993
1683
|
for (const ev of used) {
|
|
1994
1684
|
const meta = ev.meta;
|
|
@@ -1999,40 +1689,6 @@ function aggregateToolUsage(sessionId) {
|
|
|
1999
1689
|
}
|
|
2000
1690
|
return counts;
|
|
2001
1691
|
}
|
|
2002
|
-
function contextTokenStats(sessionId) {
|
|
2003
|
-
const snapshots = getEventsByType(sessionId, "context.snapshot");
|
|
2004
|
-
const samples = [];
|
|
2005
|
-
for (const ev of snapshots) {
|
|
2006
|
-
const meta = ev.meta;
|
|
2007
|
-
const total = parseInt(meta?.context_window_tokens ?? "0", 10);
|
|
2008
|
-
if (Number.isFinite(total) && total > 0)
|
|
2009
|
-
samples.push(total);
|
|
2010
|
-
}
|
|
2011
|
-
if (samples.length === 0)
|
|
2012
|
-
return { avg: 0, max: 0, latest: 0 };
|
|
2013
|
-
let sum = 0;
|
|
2014
|
-
let max = 0;
|
|
2015
|
-
for (const n of samples) {
|
|
2016
|
-
sum += n;
|
|
2017
|
-
if (n > max)
|
|
2018
|
-
max = n;
|
|
2019
|
-
}
|
|
2020
|
-
const avg = Math.round(sum / samples.length);
|
|
2021
|
-
const latest = samples[samples.length - 1] ?? 0;
|
|
2022
|
-
return { avg, max, latest };
|
|
2023
|
-
}
|
|
2024
|
-
function permissionDenialCount(sessionId) {
|
|
2025
|
-
const all = getEventsBySession(sessionId);
|
|
2026
|
-
let requests = 0;
|
|
2027
|
-
let resolves = 0;
|
|
2028
|
-
for (const ev of all) {
|
|
2029
|
-
if (ev.event === "permission.request")
|
|
2030
|
-
requests++;
|
|
2031
|
-
else if (ev.event === "permission.resolve")
|
|
2032
|
-
resolves++;
|
|
2033
|
-
}
|
|
2034
|
-
return Math.max(0, requests - resolves);
|
|
2035
|
-
}
|
|
2036
1692
|
function discardRate(sessionId) {
|
|
2037
1693
|
const row = getDb().select({
|
|
2038
1694
|
total: sql5`count(*)`,
|
|
@@ -2046,8 +1702,6 @@ function discardRate(sessionId) {
|
|
|
2046
1702
|
function computeEngagementStats(sessionId) {
|
|
2047
1703
|
return {
|
|
2048
1704
|
toolUsage: aggregateToolUsage(sessionId),
|
|
2049
|
-
contextTokens: contextTokenStats(sessionId),
|
|
2050
|
-
permissionDenials: permissionDenialCount(sessionId),
|
|
2051
1705
|
discardRate: discardRate(sessionId)
|
|
2052
1706
|
};
|
|
2053
1707
|
}
|
|
@@ -2364,7 +2018,7 @@ function cleanupSnapshot() {
|
|
|
2364
2018
|
}
|
|
2365
2019
|
}
|
|
2366
2020
|
var SIDECAR_SUFFIXES;
|
|
2367
|
-
var
|
|
2021
|
+
var init_snapshot = __esm(() => {
|
|
2368
2022
|
init_resolve();
|
|
2369
2023
|
SIDECAR_SUFFIXES = ["", "-wal", "-shm"];
|
|
2370
2024
|
});
|
|
@@ -2563,7 +2217,7 @@ var init_engine = __esm(() => {
|
|
|
2563
2217
|
init_resolve();
|
|
2564
2218
|
init_lsof();
|
|
2565
2219
|
init_config();
|
|
2566
|
-
|
|
2220
|
+
init_snapshot();
|
|
2567
2221
|
init_crypto();
|
|
2568
2222
|
});
|
|
2569
2223
|
|
|
@@ -3281,73 +2935,6 @@ var init_process = __esm(() => {
|
|
|
3281
2935
|
init_resolve();
|
|
3282
2936
|
});
|
|
3283
2937
|
|
|
3284
|
-
// src/engine/spawn-context.ts
|
|
3285
|
-
import { execFile as execFile2 } from "child_process";
|
|
3286
|
-
import { promisify } from "util";
|
|
3287
|
-
function captureModel() {
|
|
3288
|
-
return process.env.BERTRAND_MODEL || process.env.CLAUDE_MODEL || undefined;
|
|
3289
|
-
}
|
|
3290
|
-
async function captureClaudeVersion() {
|
|
3291
|
-
if (cachedClaudeVersion !== null)
|
|
3292
|
-
return cachedClaudeVersion;
|
|
3293
|
-
try {
|
|
3294
|
-
const { stdout } = await execFileAsync("claude", ["--version"], {
|
|
3295
|
-
timeout: 5000
|
|
3296
|
-
});
|
|
3297
|
-
const match2 = stdout.trim().match(/(\d+\.\d+\.\d+(?:\.\d+)?)/);
|
|
3298
|
-
cachedClaudeVersion = match2 ? match2[1] : stdout.trim() || undefined;
|
|
3299
|
-
return cachedClaudeVersion;
|
|
3300
|
-
} catch {
|
|
3301
|
-
cachedClaudeVersion = undefined;
|
|
3302
|
-
return;
|
|
3303
|
-
}
|
|
3304
|
-
}
|
|
3305
|
-
async function captureGit() {
|
|
3306
|
-
try {
|
|
3307
|
-
const [statusRes, shaRes] = await Promise.all([
|
|
3308
|
-
execFileAsync("git", ["status", "--porcelain=v2", "--branch"], {
|
|
3309
|
-
timeout: 5000
|
|
3310
|
-
}),
|
|
3311
|
-
execFileAsync("git", ["rev-parse", "HEAD"], { timeout: 5000 })
|
|
3312
|
-
]);
|
|
3313
|
-
let branch;
|
|
3314
|
-
let dirty = false;
|
|
3315
|
-
for (const line of statusRes.stdout.split(`
|
|
3316
|
-
`)) {
|
|
3317
|
-
if (line.startsWith("# branch.head ")) {
|
|
3318
|
-
branch = line.slice("# branch.head ".length).trim();
|
|
3319
|
-
} else if (line && !line.startsWith("#")) {
|
|
3320
|
-
dirty = true;
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
if (!branch)
|
|
3324
|
-
return;
|
|
3325
|
-
return {
|
|
3326
|
-
branch,
|
|
3327
|
-
sha: shaRes.stdout.trim(),
|
|
3328
|
-
dirty
|
|
3329
|
-
};
|
|
3330
|
-
} catch {
|
|
3331
|
-
return;
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
async function captureSpawnContext() {
|
|
3335
|
-
const [claudeVersion, git] = await Promise.all([
|
|
3336
|
-
captureClaudeVersion(),
|
|
3337
|
-
captureGit()
|
|
3338
|
-
]);
|
|
3339
|
-
return {
|
|
3340
|
-
model: captureModel(),
|
|
3341
|
-
claudeVersion,
|
|
3342
|
-
git,
|
|
3343
|
-
cwd: process.cwd()
|
|
3344
|
-
};
|
|
3345
|
-
}
|
|
3346
|
-
var execFileAsync, cachedClaudeVersion = null;
|
|
3347
|
-
var init_spawn_context = __esm(() => {
|
|
3348
|
-
execFileAsync = promisify(execFile2);
|
|
3349
|
-
});
|
|
3350
|
-
|
|
3351
2938
|
// src/lib/server-lifecycle.ts
|
|
3352
2939
|
import { spawn as spawn3 } from "child_process";
|
|
3353
2940
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
@@ -3496,24 +3083,11 @@ async function launch(opts) {
|
|
|
3496
3083
|
liveSession = { sessionId: session.id, claudeId };
|
|
3497
3084
|
installExitHandlers();
|
|
3498
3085
|
await ensureServerStarted();
|
|
3499
|
-
const spawnContext = await captureSpawnContext();
|
|
3500
3086
|
const sessionName = `${opts.categoryPath}/${opts.slug}`;
|
|
3501
|
-
emitSessionStarted({
|
|
3502
|
-
sessionId: session.id,
|
|
3503
|
-
conversationId: claudeId,
|
|
3504
|
-
categoryPath: opts.categoryPath,
|
|
3505
|
-
sessionName: opts.name ?? opts.slug,
|
|
3506
|
-
sessionSlug: opts.slug,
|
|
3507
|
-
labels: opts.labelNames ?? [],
|
|
3508
|
-
summary: session.summary ?? null
|
|
3509
|
-
});
|
|
3510
3087
|
emitClaudeStarted({
|
|
3511
3088
|
sessionId: session.id,
|
|
3512
3089
|
conversationId: claudeId,
|
|
3513
|
-
|
|
3514
|
-
claudeVersion: spawnContext.claudeVersion,
|
|
3515
|
-
git: spawnContext.git,
|
|
3516
|
-
cwd: spawnContext.cwd
|
|
3090
|
+
cwd: process.cwd()
|
|
3517
3091
|
});
|
|
3518
3092
|
const siblingContext = buildSiblingContext(categoryId, opts.categoryPath, session.id);
|
|
3519
3093
|
const contract = buildContract(sessionName, siblingContext);
|
|
@@ -3538,19 +3112,11 @@ async function resume(opts) {
|
|
|
3538
3112
|
liveSession = { sessionId: session.id, claudeId: opts.conversationId };
|
|
3539
3113
|
installExitHandlers();
|
|
3540
3114
|
await ensureServerStarted();
|
|
3541
|
-
emitSessionResumed({
|
|
3542
|
-
sessionId: session.id,
|
|
3543
|
-
conversationId: opts.conversationId
|
|
3544
|
-
});
|
|
3545
3115
|
if (isFreshClaudeSession) {
|
|
3546
|
-
const spawnContext = await captureSpawnContext();
|
|
3547
3116
|
emitClaudeStarted({
|
|
3548
3117
|
sessionId: session.id,
|
|
3549
3118
|
conversationId: opts.conversationId,
|
|
3550
|
-
|
|
3551
|
-
claudeVersion: spawnContext.claudeVersion,
|
|
3552
|
-
git: spawnContext.git,
|
|
3553
|
-
cwd: spawnContext.cwd
|
|
3119
|
+
cwd: process.cwd()
|
|
3554
3120
|
});
|
|
3555
3121
|
}
|
|
3556
3122
|
const categoryPath = category?.path ?? "";
|
|
@@ -3587,7 +3153,6 @@ function finalizeSession(sessionId, conversationId, exitCode) {
|
|
|
3587
3153
|
});
|
|
3588
3154
|
if (liveSession?.sessionId === sessionId)
|
|
3589
3155
|
liveSession = null;
|
|
3590
|
-
emitSessionEnded({ sessionId });
|
|
3591
3156
|
computeAndPersist(sessionId);
|
|
3592
3157
|
stopServerIfIdle();
|
|
3593
3158
|
triggerBackgroundPush();
|
|
@@ -3602,7 +3167,6 @@ var init_session = __esm(() => {
|
|
|
3602
3167
|
init_template2();
|
|
3603
3168
|
init_context();
|
|
3604
3169
|
init_process();
|
|
3605
|
-
init_spawn_context();
|
|
3606
3170
|
init_timing();
|
|
3607
3171
|
init_server_lifecycle();
|
|
3608
3172
|
init_trigger();
|
|
@@ -3820,10 +3384,6 @@ function recoverStaleSessions() {
|
|
|
3820
3384
|
status: "paused",
|
|
3821
3385
|
pid: null
|
|
3822
3386
|
});
|
|
3823
|
-
emitSessionPausedByRecovery({
|
|
3824
|
-
sessionId: session.id,
|
|
3825
|
-
stalePid: session.pid
|
|
3826
|
-
});
|
|
3827
3387
|
recovered++;
|
|
3828
3388
|
}
|
|
3829
3389
|
}
|
|
@@ -3831,7 +3391,6 @@ function recoverStaleSessions() {
|
|
|
3831
3391
|
}
|
|
3832
3392
|
var init_recovery = __esm(() => {
|
|
3833
3393
|
init_sessions();
|
|
3834
|
-
init_emit();
|
|
3835
3394
|
});
|
|
3836
3395
|
|
|
3837
3396
|
// src/cli/commands/launch.ts
|
|
@@ -3900,14 +3459,11 @@ rm -f "${runtimeDir}/working-$sid"
|
|
|
3900
3459
|
|
|
3901
3460
|
bq update --session-id "$sid" --event session.waiting --meta "$(jq -n --arg q "$question" --arg cid "$cid" '{question:$q, claude_id:$cid}')"
|
|
3902
3461
|
|
|
3903
|
-
#
|
|
3904
|
-
#
|
|
3905
|
-
#
|
|
3906
|
-
# inside the command makes it idempotent vs the matching Stop-time capture,
|
|
3907
|
-
# so the same turn never lands twice.
|
|
3462
|
+
# Capture the latest assistant turn's text + recap tag. Dedup inside the
|
|
3463
|
+
# command makes it idempotent vs the matching Stop-time capture so the same
|
|
3464
|
+
# turn never lands twice.
|
|
3908
3465
|
tpath="$(printf '%s' "$input" | grep -o '"transcript_path":"[^"]*"' | cut -d'"' -f4)"
|
|
3909
3466
|
if [ -n "$tpath" ]; then
|
|
3910
|
-
bq snapshot --session-id "$sid" --transcript-path "$tpath" --conversation-id "$cid" &
|
|
3911
3467
|
bq assistant-message --session-id "$sid" --transcript-path "$tpath" --conversation-id "$cid" &
|
|
3912
3468
|
fi
|
|
3913
3469
|
|
|
@@ -3971,35 +3527,9 @@ fi
|
|
|
3971
3527
|
wait
|
|
3972
3528
|
`;
|
|
3973
3529
|
}
|
|
3974
|
-
function activeScript(bin, runtimeDir) {
|
|
3975
|
-
return `#!/usr/bin/env bash
|
|
3976
|
-
# Hook: PreToolUse (catch-all) \u2192 flip waiting to active
|
|
3977
|
-
${quietHelper(bin)}
|
|
3978
|
-
sid="\${BERTRAND_SESSION:-}"
|
|
3979
|
-
[ -z "$sid" ] && exit 0
|
|
3980
|
-
|
|
3981
|
-
# Debounce: skip if we already sent session.active within the last 5 seconds.
|
|
3982
|
-
# This avoids spawning bertrand (~31ms) on every tool call during rapid sequences.
|
|
3983
|
-
marker="${runtimeDir}/working-$sid"
|
|
3984
|
-
if [ -f "$marker" ]; then
|
|
3985
|
-
age=$(( $(date +%s) - $(stat -f%m "$marker" 2>/dev/null || echo 0) ))
|
|
3986
|
-
[ "$age" -lt 5 ] && exit 0
|
|
3987
|
-
fi
|
|
3988
|
-
|
|
3989
|
-
input="$(cat)"
|
|
3990
|
-
${EXTRACT_TOOL}
|
|
3991
|
-
|
|
3992
|
-
# AskUserQuestion has its own PreToolUse hook
|
|
3993
|
-
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
3994
|
-
|
|
3995
|
-
touch "$marker"
|
|
3996
|
-
cid="\${BERTRAND_CLAUDE_ID:-}"
|
|
3997
|
-
bq update --session-id "$sid" --event session.active --meta "$(jq -n --arg cid "$cid" '{claude_id:$cid}')"
|
|
3998
|
-
`;
|
|
3999
|
-
}
|
|
4000
3530
|
function permissionWaitScript(bin, runtimeDir) {
|
|
4001
3531
|
return `#!/usr/bin/env bash
|
|
4002
|
-
# Hook: PermissionRequest \u2192 mark pending,
|
|
3532
|
+
# Hook: PermissionRequest \u2192 mark pending, badge + notify
|
|
4003
3533
|
${quietHelper(bin)}
|
|
4004
3534
|
sid="\${BERTRAND_SESSION:-}"
|
|
4005
3535
|
[ -z "$sid" ] && exit 0
|
|
@@ -4008,19 +3538,11 @@ input="$(cat)"
|
|
|
4008
3538
|
${EXTRACT_TOOL}
|
|
4009
3539
|
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
4010
3540
|
|
|
4011
|
-
#
|
|
3541
|
+
# Marker tells the PostToolUse hook to emit tool.used with outcome:approved
|
|
3542
|
+
# instead of outcome:auto. Without it, every prompted-then-approved tool call
|
|
3543
|
+
# would look identical to an auto-approved one.
|
|
4012
3544
|
touch "${runtimeDir}/perm-pending-$sid"
|
|
4013
3545
|
|
|
4014
|
-
# Extract detail from tool_input via grep (avoid jq for simple fields)
|
|
4015
|
-
detail=""
|
|
4016
|
-
case "$tool" in
|
|
4017
|
-
Bash) detail="$(printf '%s' "$input" | grep -o '"command":"[^"]*"' | head -1 | cut -d'"' -f4 | cut -c1-1000)" ;;
|
|
4018
|
-
Edit|Write|Read) detail="$(printf '%s' "$input" | grep -o '"file_path":"[^"]*"' | head -1 | cut -d'"' -f4 | cut -c1-1000)" ;;
|
|
4019
|
-
esac
|
|
4020
|
-
|
|
4021
|
-
cid="\${BERTRAND_CLAUDE_ID:-}"
|
|
4022
|
-
bq update --session-id "$sid" --event permission.request --meta "$(jq -n --arg t "$tool" --arg d "$detail" --arg cid "$cid" '{tool:$t, detail:$d, claude_id:$cid}')"
|
|
4023
|
-
|
|
4024
3546
|
# Badge + notify in background
|
|
4025
3547
|
bq badge bell-exclamation --color '#ff6b35' --priority 25 --beep &
|
|
4026
3548
|
bq notify bertrand "Needs permission: $tool" &
|
|
@@ -4031,17 +3553,15 @@ function permissionDoneScript(bin, runtimeDir) {
|
|
|
4031
3553
|
return `#!/usr/bin/env bash
|
|
4032
3554
|
# Hook: PostToolUse (catch-all)
|
|
4033
3555
|
#
|
|
4034
|
-
# Captures every tool call Claude makes.
|
|
3556
|
+
# Captures every tool call Claude makes. Two event flows:
|
|
4035
3557
|
# 1. Edit/Write/MultiEdit \u2192 tool.applied with diff payload. Keeps the
|
|
4036
3558
|
# existing dashboard diff-renderer happy and is the only place we get
|
|
4037
3559
|
# old_string/new_string on auto-approved edits.
|
|
4038
|
-
# 2.
|
|
4039
|
-
#
|
|
4040
|
-
#
|
|
4041
|
-
#
|
|
4042
|
-
#
|
|
4043
|
-
# with outcome:"auto". Previously this case dropped the call entirely;
|
|
4044
|
-
# now Claude's read-only / shell activity shows up in the timeline.
|
|
3560
|
+
# 2. Everything else \u2192 tool.used. The PermissionRequest hook may have set
|
|
3561
|
+
# a marker; if so the call was prompted-then-approved (outcome:approved),
|
|
3562
|
+
# otherwise it was auto-approved (outcome:auto). Denials never reach
|
|
3563
|
+
# PostToolUse, so absence of a tool.used after a permission.request means
|
|
3564
|
+
# the user said no.
|
|
4045
3565
|
${quietHelper(bin)}
|
|
4046
3566
|
sid="\${BERTRAND_SESSION:-}"
|
|
4047
3567
|
[ -z "$sid" ] && exit 0
|
|
@@ -4100,14 +3620,9 @@ case "$tool" in
|
|
|
4100
3620
|
WebSearch) detail="$(printf '%s' "$input" | grep -o '"query":"[^"]*"' | head -1 | cut -d'"' -f4 | cut -c1-200)" ;;
|
|
4101
3621
|
esac
|
|
4102
3622
|
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
bq update --session-id "$sid" --event permission.resolve --meta "$(jq -n --arg t "$tool" --arg d "$detail" --arg cid "$cid" '{tool:$t, detail:$d, outcome:"approved", claude_id:$cid}')"
|
|
4107
|
-
else
|
|
4108
|
-
# Auto-approved path. Without tool.used, these calls were invisible.
|
|
4109
|
-
bq update --session-id "$sid" --event tool.used --meta "$(jq -n --arg t "$tool" --arg d "$detail" --arg cid "$cid" '{tool:$t, detail:$d, outcome:"auto", claude_id:$cid}')"
|
|
4110
|
-
fi
|
|
3623
|
+
outcome="auto"
|
|
3624
|
+
[ "$had_marker" = "1" ] && outcome="approved"
|
|
3625
|
+
bq update --session-id "$sid" --event tool.used --meta "$(jq -n --arg t "$tool" --arg d "$detail" --arg o "$outcome" --arg cid "$cid" '{tool:$t, detail:$d, outcome:$o, claude_id:$cid}')"
|
|
4111
3626
|
wait
|
|
4112
3627
|
`;
|
|
4113
3628
|
}
|
|
@@ -4129,22 +3644,22 @@ bq update --session-id "$sid" --event user.prompt --meta "$meta"
|
|
|
4129
3644
|
}
|
|
4130
3645
|
function doneScript(bin, _runtimeDir) {
|
|
4131
3646
|
return `#!/usr/bin/env bash
|
|
4132
|
-
# Hook: Stop \u2192
|
|
3647
|
+
# Hook: Stop \u2192 flip session status to paused; no event row written.
|
|
3648
|
+
# The status flip is driven by EVENT_STATUS_MAP[session.paused]; dispatchHookEvent
|
|
3649
|
+
# no longer has a session.paused case, so update.ts flips status without inserting.
|
|
4133
3650
|
${quietHelper(bin)}
|
|
4134
3651
|
sid="\${BERTRAND_SESSION:-}"
|
|
4135
3652
|
[ -z "$sid" ] && exit 0
|
|
4136
3653
|
|
|
4137
3654
|
input="$(cat)"
|
|
4138
3655
|
cid="\${BERTRAND_CLAUDE_ID:-}"
|
|
4139
|
-
bq update --session-id "$sid" --event session.paused
|
|
3656
|
+
bq update --session-id "$sid" --event session.paused
|
|
4140
3657
|
|
|
4141
|
-
# Final
|
|
4142
|
-
#
|
|
4143
|
-
#
|
|
4144
|
-
# do land new ones.
|
|
3658
|
+
# Final assistant-message read. Dedups against the most-recent AskUQ-time
|
|
3659
|
+
# capture, so a Done-for-now exit (no work between AskUQ and Stop) lands zero
|
|
3660
|
+
# new events here; intermediate Stops with fresh assistant output do.
|
|
4145
3661
|
tpath="$(printf '%s' "$input" | grep -o '"transcript_path":"[^"]*"' | cut -d'"' -f4)"
|
|
4146
3662
|
if [ -n "$tpath" ]; then
|
|
4147
|
-
bq snapshot --session-id "$sid" --transcript-path "$tpath" --conversation-id "$cid" &
|
|
4148
3663
|
bq assistant-message --session-id "$sid" --transcript-path "$tpath" --conversation-id "$cid" &
|
|
4149
3664
|
fi
|
|
4150
3665
|
|
|
@@ -4156,7 +3671,6 @@ var init_scripts = __esm(() => {
|
|
|
4156
3671
|
HOOK_SCRIPTS = {
|
|
4157
3672
|
"on-waiting.sh": waitingScript,
|
|
4158
3673
|
"on-answered.sh": answeredScript,
|
|
4159
|
-
"on-active.sh": activeScript,
|
|
4160
3674
|
"on-permission-wait.sh": permissionWaitScript,
|
|
4161
3675
|
"on-permission-done.sh": permissionDoneScript,
|
|
4162
3676
|
"on-user-prompt.sh": userPromptScript,
|
|
@@ -4215,10 +3729,6 @@ var init_settings = __esm(() => {
|
|
|
4215
3729
|
{
|
|
4216
3730
|
matcher: "AskUserQuestion",
|
|
4217
3731
|
hooks: [{ type: "command", command: `${paths.hooks}/on-waiting.sh` }]
|
|
4218
|
-
},
|
|
4219
|
-
{
|
|
4220
|
-
matcher: "",
|
|
4221
|
-
hooks: [{ type: "command", command: `${paths.hooks}/on-active.sh` }]
|
|
4222
3732
|
}
|
|
4223
3733
|
],
|
|
4224
3734
|
PostToolUse: [
|
|
@@ -4542,31 +4052,8 @@ function extractSummary(row) {
|
|
|
4542
4052
|
const answers = meta.answers;
|
|
4543
4053
|
return answers ? Object.values(answers).join(", ") : "";
|
|
4544
4054
|
}
|
|
4545
|
-
case "permission.request":
|
|
4546
|
-
case "permission.resolve": {
|
|
4547
|
-
const tool = meta.tool ?? "";
|
|
4548
|
-
const detail = meta.detail ?? "";
|
|
4549
|
-
return detail ? `${tool}: ${detail}` : tool;
|
|
4550
|
-
}
|
|
4551
|
-
case "gh.pr.created": {
|
|
4552
|
-
const title = meta.pr_title ?? "";
|
|
4553
|
-
const url = meta.pr_url ?? "";
|
|
4554
|
-
return title || url;
|
|
4555
|
-
}
|
|
4556
|
-
case "gh.pr.merged":
|
|
4557
|
-
return meta.branch ?? "";
|
|
4558
|
-
case "worktree.entered":
|
|
4559
|
-
return meta.branch ?? "";
|
|
4560
|
-
case "linear.issue.read":
|
|
4561
|
-
return meta.issue_title ?? "";
|
|
4562
|
-
case "notion.page.read":
|
|
4563
|
-
return meta.page_title ?? "";
|
|
4564
|
-
case "vercel.deploy":
|
|
4565
|
-
return meta.project_name ?? "";
|
|
4566
4055
|
case "user.prompt":
|
|
4567
4056
|
return meta.prompt ?? "";
|
|
4568
|
-
case "context.snapshot":
|
|
4569
|
-
return meta.remaining_pct ? `${meta.remaining_pct}% remaining` : "";
|
|
4570
4057
|
case "session.recap":
|
|
4571
4058
|
return meta.recap ?? "";
|
|
4572
4059
|
default:
|
|
@@ -4596,26 +4083,13 @@ function enrichAll(rows) {
|
|
|
4596
4083
|
var catalog, DEFAULT_INFO;
|
|
4597
4084
|
var init_catalog = __esm(() => {
|
|
4598
4085
|
catalog = {
|
|
4599
|
-
"session.started": { label: "started", category: "lifecycle", color: 34, detailColor: 245, skip: false },
|
|
4600
|
-
"session.resumed": { label: "resumed", category: "lifecycle", color: 34, detailColor: 245, skip: false },
|
|
4601
|
-
"session.end": { label: "ended", category: "lifecycle", color: 245, detailColor: 245, skip: false },
|
|
4602
4086
|
"claude.started": { label: "claude started", category: "lifecycle", color: 35, detailColor: 245, skip: false },
|
|
4603
4087
|
"claude.ended": { label: "claude ended", category: "lifecycle", color: 35, detailColor: 245, skip: false },
|
|
4604
4088
|
"claude.discarded": { label: "discarded", category: "lifecycle", color: 245, detailColor: 245, skip: false },
|
|
4605
4089
|
"session.waiting": { label: "waiting", category: "interaction", color: 33, detailColor: 245, skip: false },
|
|
4606
4090
|
"session.answered": { label: "answered", category: "interaction", color: 36, detailColor: 245, skip: false },
|
|
4607
|
-
"permission.request": { label: "permission", category: "work", color: 214, detailColor: 245, skip: false },
|
|
4608
|
-
"permission.resolve": { label: "allowed", category: "work", color: 214, detailColor: 245, skip: false },
|
|
4609
|
-
"worktree.entered": { label: "worktree", category: "lifecycle", color: 35, detailColor: 245, skip: false },
|
|
4610
|
-
"worktree.exited": { label: "worktree exited", category: "lifecycle", color: 35, detailColor: 245, skip: false },
|
|
4611
|
-
"gh.pr.created": { label: "PR created", category: "integration", color: 32, detailColor: 32, skip: false },
|
|
4612
|
-
"gh.pr.merged": { label: "PR merged", category: "integration", color: 35, detailColor: 35, skip: false },
|
|
4613
|
-
"linear.issue.read": { label: "Linear issue", category: "integration", color: 33, detailColor: 33, skip: false },
|
|
4614
|
-
"notion.page.read": { label: "Notion page", category: "integration", color: 245, detailColor: 245, skip: false },
|
|
4615
|
-
"vercel.deploy": { label: "deployed", category: "integration", color: 245, detailColor: 245, skip: false },
|
|
4616
4091
|
"user.prompt": { label: "prompt", category: "interaction", color: 36, detailColor: 245, skip: false },
|
|
4617
|
-
"
|
|
4618
|
-
"tool.used": { label: "tool", category: "work", color: 214, detailColor: 245, skip: true },
|
|
4092
|
+
"tool.used": { label: "tool", category: "work", color: 214, detailColor: 245, skip: false },
|
|
4619
4093
|
"tool.work": { label: "tool work", category: "work", color: 214, detailColor: 245, skip: false },
|
|
4620
4094
|
"session.recap": { label: "session recap", category: "lifecycle", color: 33, detailColor: 245, skip: false },
|
|
4621
4095
|
"assistant.message": { label: "claude", category: "interaction", color: 39, detailColor: 245, skip: false },
|
|
@@ -4676,8 +4150,6 @@ function collapsePermissions(events2) {
|
|
|
4676
4150
|
continue;
|
|
4677
4151
|
const toolCounts = new Map;
|
|
4678
4152
|
for (const pev of batch) {
|
|
4679
|
-
if (pev.event !== "permission.request" && pev.event !== "tool.used")
|
|
4680
|
-
continue;
|
|
4681
4153
|
const meta = pev.meta;
|
|
4682
4154
|
const tool = meta?.tool ?? "unknown";
|
|
4683
4155
|
toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
|
|
@@ -4739,8 +4211,6 @@ var ROLLUP_EVENTS;
|
|
|
4739
4211
|
var init_compact = __esm(() => {
|
|
4740
4212
|
init_catalog();
|
|
4741
4213
|
ROLLUP_EVENTS = new Set([
|
|
4742
|
-
"permission.request",
|
|
4743
|
-
"permission.resolve",
|
|
4744
4214
|
"tool.used"
|
|
4745
4215
|
]);
|
|
4746
4216
|
});
|
|
@@ -4982,14 +4452,11 @@ function getMetrics(sessionId, name, status2) {
|
|
|
4982
4452
|
const allEvents = getEventsBySession(sessionId);
|
|
4983
4453
|
const conversations2 = new Set;
|
|
4984
4454
|
let interactionCount = 0;
|
|
4985
|
-
let prCount = 0;
|
|
4986
4455
|
for (const ev of allEvents) {
|
|
4987
4456
|
if (ev.conversationId)
|
|
4988
4457
|
conversations2.add(ev.conversationId);
|
|
4989
4458
|
if (ev.event === "session.waiting" || ev.event === "session.answered")
|
|
4990
4459
|
interactionCount++;
|
|
4991
|
-
if (ev.event === "gh.pr.created")
|
|
4992
|
-
prCount++;
|
|
4993
4460
|
}
|
|
4994
4461
|
return {
|
|
4995
4462
|
name,
|
|
@@ -4997,7 +4464,6 @@ function getMetrics(sessionId, name, status2) {
|
|
|
4997
4464
|
eventCount: allEvents.length,
|
|
4998
4465
|
conversationCount: conversations2.size,
|
|
4999
4466
|
interactionCount,
|
|
5000
|
-
prCount,
|
|
5001
4467
|
claudeWorkS: Math.round(timing.totalClaudeWorkMs / 1000),
|
|
5002
4468
|
userWaitS: Math.round(timing.totalUserWaitMs / 1000),
|
|
5003
4469
|
activePct: timing.activePct,
|
|
@@ -5018,9 +4484,8 @@ function renderGlobal(metrics, isJson) {
|
|
|
5018
4484
|
claudeWorkS: acc.claudeWorkS + m.claudeWorkS,
|
|
5019
4485
|
userWaitS: acc.userWaitS + m.userWaitS,
|
|
5020
4486
|
conversations: acc.conversations + m.conversationCount,
|
|
5021
|
-
prs: acc.prs + m.prCount,
|
|
5022
4487
|
interactions: acc.interactions + m.interactionCount
|
|
5023
|
-
}), { sessions: 0, active: 0, durationS: 0, claudeWorkS: 0, userWaitS: 0, conversations: 0,
|
|
4488
|
+
}), { sessions: 0, active: 0, durationS: 0, claudeWorkS: 0, userWaitS: 0, conversations: 0, interactions: 0 });
|
|
5024
4489
|
const totalTracked = totals.claudeWorkS + totals.userWaitS;
|
|
5025
4490
|
const globalActivePct = totalTracked > 0 ? Math.round(totals.claudeWorkS / totalTracked * 100) : 0;
|
|
5026
4491
|
if (isJson) {
|
|
@@ -5037,7 +4502,6 @@ function renderGlobal(metrics, isJson) {
|
|
|
5037
4502
|
console.log(` Claude work: ${dur(totals.claudeWorkS)} ${dim}(${pct(globalActivePct)})${reset}`);
|
|
5038
4503
|
console.log(` User wait: ${dur(totals.userWaitS)} ${dim}(${pct(100 - globalActivePct)})${reset}`);
|
|
5039
4504
|
console.log(` Conversations: ${totals.conversations}`);
|
|
5040
|
-
console.log(` PRs: ${totals.prs}`);
|
|
5041
4505
|
console.log(` Interactions: ${totals.interactions}`);
|
|
5042
4506
|
}
|
|
5043
4507
|
function renderCategory(metrics, categoryPath, isJson) {
|
|
@@ -5052,21 +4516,20 @@ function renderCategory(metrics, categoryPath, isJson) {
|
|
|
5052
4516
|
const maxName = Math.max(...sorted.map((m) => m.name.length), 4);
|
|
5053
4517
|
console.log(`${bold}${categoryPath}${reset}
|
|
5054
4518
|
`);
|
|
5055
|
-
console.log(`${dim}${"NAME".padEnd(maxName)} ${"DURATION".padEnd(8)} ${"CLAUDE".padEnd(8)} ${"WAIT".padEnd(8)} ${"ACT%".padEnd(5)}
|
|
4519
|
+
console.log(`${dim}${"NAME".padEnd(maxName)} ${"DURATION".padEnd(8)} ${"CLAUDE".padEnd(8)} ${"WAIT".padEnd(8)} ${"ACT%".padEnd(5)} CONVOS${reset}`);
|
|
5056
4520
|
for (const m of sorted) {
|
|
5057
|
-
console.log(`${m.name.padEnd(maxName)} ${dur(m.durationS).padEnd(8)} ${dur(m.claudeWorkS).padEnd(8)} ${dur(m.userWaitS).padEnd(8)} ${pct(m.activePct).padEnd(5)} ${
|
|
4521
|
+
console.log(`${m.name.padEnd(maxName)} ${dur(m.durationS).padEnd(8)} ${dur(m.claudeWorkS).padEnd(8)} ${dur(m.userWaitS).padEnd(8)} ${pct(m.activePct).padEnd(5)} ${m.conversationCount}`);
|
|
5058
4522
|
}
|
|
5059
4523
|
const totals = sorted.reduce((acc, m) => ({
|
|
5060
4524
|
durationS: acc.durationS + m.durationS,
|
|
5061
4525
|
claudeWorkS: acc.claudeWorkS + m.claudeWorkS,
|
|
5062
4526
|
userWaitS: acc.userWaitS + m.userWaitS,
|
|
5063
|
-
conversations: acc.conversations + m.conversationCount
|
|
5064
|
-
|
|
5065
|
-
}), { durationS: 0, claudeWorkS: 0, userWaitS: 0, conversations: 0, prs: 0 });
|
|
4527
|
+
conversations: acc.conversations + m.conversationCount
|
|
4528
|
+
}), { durationS: 0, claudeWorkS: 0, userWaitS: 0, conversations: 0 });
|
|
5066
4529
|
const totalTracked = totals.claudeWorkS + totals.userWaitS;
|
|
5067
4530
|
const totalPct = totalTracked > 0 ? Math.round(totals.claudeWorkS / totalTracked * 100) : 0;
|
|
5068
4531
|
console.log(`${dim}${"\u2500".repeat(maxName + 50)}${reset}`);
|
|
5069
|
-
console.log(`${"TOTAL".padEnd(maxName)} ${dur(totals.durationS).padEnd(8)} ${dur(totals.claudeWorkS).padEnd(8)} ${dur(totals.userWaitS).padEnd(8)} ${pct(totalPct).padEnd(5)} ${
|
|
4532
|
+
console.log(`${"TOTAL".padEnd(maxName)} ${dur(totals.durationS).padEnd(8)} ${dur(totals.claudeWorkS).padEnd(8)} ${dur(totals.userWaitS).padEnd(8)} ${pct(totalPct).padEnd(5)} ${totals.conversations}`);
|
|
5070
4533
|
}
|
|
5071
4534
|
function renderSession(m, isJson) {
|
|
5072
4535
|
if (isJson) {
|
|
@@ -5084,7 +4547,6 @@ function renderSession(m, isJson) {
|
|
|
5084
4547
|
console.log(` Events: ${m.eventCount}`);
|
|
5085
4548
|
console.log(` Conversations: ${m.conversationCount}`);
|
|
5086
4549
|
console.log(` Interactions: ${m.interactionCount}`);
|
|
5087
|
-
console.log(` PRs: ${m.prCount}`);
|
|
5088
4550
|
}
|
|
5089
4551
|
var ACTIVE_STATUSES2;
|
|
5090
4552
|
var init_stats2 = __esm(() => {
|
|
@@ -5534,9 +4996,7 @@ init_router();
|
|
|
5534
4996
|
var command = process.argv[2];
|
|
5535
4997
|
var hotPath = {
|
|
5536
4998
|
update: () => Promise.resolve().then(() => (init_update(), exports_update)),
|
|
5537
|
-
snapshot: () => Promise.resolve().then(() => (init_snapshot(), exports_snapshot)),
|
|
5538
4999
|
"assistant-message": () => Promise.resolve().then(() => (init_assistant_message(), exports_assistant_message)),
|
|
5539
|
-
"recap-thinking": () => Promise.resolve().then(() => (init_recap_thinking(), exports_recap_thinking)),
|
|
5540
5000
|
badge: () => Promise.resolve().then(() => (init_badge(), exports_badge)),
|
|
5541
5001
|
notify: () => Promise.resolve().then(() => (init_notify(), exports_notify)),
|
|
5542
5002
|
serve: () => Promise.resolve().then(() => (init_serve(), exports_serve)),
|
|
@@ -5554,9 +5014,7 @@ if (command && command in hotPath) {
|
|
|
5554
5014
|
Promise.resolve().then(() => (init_backfill_stats(), exports_backfill_stats)),
|
|
5555
5015
|
Promise.resolve().then(() => (init_archive(), exports_archive)),
|
|
5556
5016
|
Promise.resolve().then(() => (init_update(), exports_update)),
|
|
5557
|
-
Promise.resolve().then(() => (init_snapshot(), exports_snapshot)),
|
|
5558
5017
|
Promise.resolve().then(() => (init_assistant_message(), exports_assistant_message)),
|
|
5559
|
-
Promise.resolve().then(() => (init_recap_thinking(), exports_recap_thinking)),
|
|
5560
5018
|
Promise.resolve().then(() => (init_serve(), exports_serve)),
|
|
5561
5019
|
Promise.resolve().then(() => (init_badge(), exports_badge)),
|
|
5562
5020
|
Promise.resolve().then(() => (init_notify(), exports_notify)),
|