@misha_misha/agentwatch 0.1.1 → 0.1.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/index.js +154 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -190,6 +190,40 @@ var init_version = __esm({
|
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
// src/util/backfill.ts
|
|
194
|
+
var backfill_exports = {};
|
|
195
|
+
__export(backfill_exports, {
|
|
196
|
+
BACKFILL_MAX_AGE_MS: () => BACKFILL_MAX_AGE_MS,
|
|
197
|
+
backfillStartOffset: () => backfillStartOffset,
|
|
198
|
+
setStaleSkipEnabled: () => setStaleSkipEnabled
|
|
199
|
+
});
|
|
200
|
+
import { statSync } from "fs";
|
|
201
|
+
function mtimeMs(file) {
|
|
202
|
+
try {
|
|
203
|
+
return statSync(file).mtimeMs;
|
|
204
|
+
} catch {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function setStaleSkipEnabled(enabled) {
|
|
209
|
+
staleSkipEnabled = enabled;
|
|
210
|
+
}
|
|
211
|
+
function backfillStartOffset(file, size, isInitialAdd, backfillBytes) {
|
|
212
|
+
if (!isInitialAdd) return size;
|
|
213
|
+
if (staleSkipEnabled && mtimeMs(file) < Date.now() - BACKFILL_MAX_AGE_MS) {
|
|
214
|
+
return size;
|
|
215
|
+
}
|
|
216
|
+
return Math.max(0, size - backfillBytes);
|
|
217
|
+
}
|
|
218
|
+
var BACKFILL_MAX_AGE_MS, staleSkipEnabled;
|
|
219
|
+
var init_backfill = __esm({
|
|
220
|
+
"src/util/backfill.ts"() {
|
|
221
|
+
"use strict";
|
|
222
|
+
BACKFILL_MAX_AGE_MS = 48 * 60 * 60 * 1e3;
|
|
223
|
+
staleSkipEnabled = false;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
193
227
|
// src/util/compaction.ts
|
|
194
228
|
function contextWindow() {
|
|
195
229
|
const env = process.env.AGENTWATCH_CONTEXT_WINDOW;
|
|
@@ -412,7 +446,7 @@ __export(workspace_exports, {
|
|
|
412
446
|
claudeProjectsDir: () => claudeProjectsDir,
|
|
413
447
|
detectWorkspaceRoot: () => detectWorkspaceRoot
|
|
414
448
|
});
|
|
415
|
-
import { existsSync as existsSync3, statSync } from "fs";
|
|
449
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
416
450
|
import { homedir as homedir3 } from "os";
|
|
417
451
|
import { join as join4 } from "path";
|
|
418
452
|
function detectWorkspaceRoot() {
|
|
@@ -436,7 +470,7 @@ function claudeProjectsDir() {
|
|
|
436
470
|
}
|
|
437
471
|
function isDir(p) {
|
|
438
472
|
try {
|
|
439
|
-
return existsSync3(p) &&
|
|
473
|
+
return existsSync3(p) && statSync2(p).isDirectory();
|
|
440
474
|
} catch {
|
|
441
475
|
return false;
|
|
442
476
|
}
|
|
@@ -764,6 +798,38 @@ var init_jsonl_stream = __esm({
|
|
|
764
798
|
}
|
|
765
799
|
});
|
|
766
800
|
|
|
801
|
+
// src/util/backfill-queue.ts
|
|
802
|
+
function createBackfillQueue(processFile) {
|
|
803
|
+
const queue = [];
|
|
804
|
+
let draining2 = false;
|
|
805
|
+
const drain = () => {
|
|
806
|
+
const file = queue.shift();
|
|
807
|
+
if (file === void 0) {
|
|
808
|
+
draining2 = false;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
processFile(file);
|
|
813
|
+
} catch {
|
|
814
|
+
}
|
|
815
|
+
setImmediate(drain);
|
|
816
|
+
};
|
|
817
|
+
return {
|
|
818
|
+
enqueue(file) {
|
|
819
|
+
queue.push(file);
|
|
820
|
+
if (!draining2) {
|
|
821
|
+
draining2 = true;
|
|
822
|
+
setImmediate(drain);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
var init_backfill_queue = __esm({
|
|
828
|
+
"src/util/backfill-queue.ts"() {
|
|
829
|
+
"use strict";
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
767
833
|
// src/util/parse-errors.ts
|
|
768
834
|
function createParseErrorTracker(agent, sink, options = {}) {
|
|
769
835
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -818,7 +884,7 @@ var init_parse_errors = __esm({
|
|
|
818
884
|
|
|
819
885
|
// src/adapters/claude-code.ts
|
|
820
886
|
import chokidar2 from "chokidar";
|
|
821
|
-
import { existsSync as existsSync4, statSync as
|
|
887
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
822
888
|
import { basename as basename2, sep } from "path";
|
|
823
889
|
function capMap(m, max) {
|
|
824
890
|
while (m.size > max) {
|
|
@@ -850,7 +916,7 @@ function startClaudeAdapter(sink) {
|
|
|
850
916
|
const size = safeSize(file);
|
|
851
917
|
let cursor = cursors.get(file);
|
|
852
918
|
if (!cursor) {
|
|
853
|
-
const start2 =
|
|
919
|
+
const start2 = backfillStartOffset(file, size, isInitialAdd, BACKFILL_BYTES);
|
|
854
920
|
cursor = { offset: start2 };
|
|
855
921
|
cursors.set(file, cursor);
|
|
856
922
|
}
|
|
@@ -913,8 +979,16 @@ function startClaudeAdapter(sink) {
|
|
|
913
979
|
}
|
|
914
980
|
}
|
|
915
981
|
};
|
|
916
|
-
|
|
982
|
+
let scanReady = false;
|
|
983
|
+
const backfillQueue = createBackfillQueue((f) => process2(f, true));
|
|
984
|
+
watcher2.on("add", (f) => {
|
|
985
|
+
if (scanReady) process2(f, true);
|
|
986
|
+
else backfillQueue.enqueue(f);
|
|
987
|
+
});
|
|
917
988
|
watcher2.on("change", (f) => process2(f, false));
|
|
989
|
+
watcher2.on("ready", () => {
|
|
990
|
+
scanReady = true;
|
|
991
|
+
});
|
|
918
992
|
watcher2.on("error", (err) => {
|
|
919
993
|
if (typeof err === "object" && err !== null) {
|
|
920
994
|
const code = err.code;
|
|
@@ -1012,7 +1086,7 @@ function capBytes(s, max = MAX_TOOL_RESULT_BYTES) {
|
|
|
1012
1086
|
}
|
|
1013
1087
|
function safeSize(file) {
|
|
1014
1088
|
try {
|
|
1015
|
-
return
|
|
1089
|
+
return statSync3(file).size;
|
|
1016
1090
|
} catch {
|
|
1017
1091
|
return 0;
|
|
1018
1092
|
}
|
|
@@ -1214,6 +1288,8 @@ var init_claude_code = __esm({
|
|
|
1214
1288
|
init_cost();
|
|
1215
1289
|
init_recent_writes();
|
|
1216
1290
|
init_jsonl_stream();
|
|
1291
|
+
init_backfill();
|
|
1292
|
+
init_backfill_queue();
|
|
1217
1293
|
init_parse_errors();
|
|
1218
1294
|
MAX_PENDING_TOOL_USES = 5e3;
|
|
1219
1295
|
pendingToolUses = /* @__PURE__ */ new Map();
|
|
@@ -1323,7 +1399,7 @@ var init_openclaw_cron = __esm({
|
|
|
1323
1399
|
|
|
1324
1400
|
// src/adapters/openclaw.ts
|
|
1325
1401
|
import chokidar3 from "chokidar";
|
|
1326
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as
|
|
1402
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
|
|
1327
1403
|
import { basename as basename3, join as join5, sep as sep2 } from "path";
|
|
1328
1404
|
import { homedir as homedir4 } from "os";
|
|
1329
1405
|
function capMap2(m, max) {
|
|
@@ -1383,8 +1459,16 @@ function startOpenClawAdapter(sink) {
|
|
|
1383
1459
|
if (!sessionRe.test(f)) return;
|
|
1384
1460
|
processSession(f, initial, cursors, normalized, parseErrors);
|
|
1385
1461
|
};
|
|
1386
|
-
|
|
1462
|
+
let sessionsReady = false;
|
|
1463
|
+
const sessionBackfillQueue = createBackfillQueue((f) => handleSession(f, true));
|
|
1464
|
+
sessionsWatcher.on("add", (f) => {
|
|
1465
|
+
if (sessionsReady) handleSession(f, true);
|
|
1466
|
+
else sessionBackfillQueue.enqueue(f);
|
|
1467
|
+
});
|
|
1387
1468
|
sessionsWatcher.on("change", (f) => handleSession(f, false));
|
|
1469
|
+
sessionsWatcher.on("ready", () => {
|
|
1470
|
+
sessionsReady = true;
|
|
1471
|
+
});
|
|
1388
1472
|
sessionsWatcher.on("error", swallow);
|
|
1389
1473
|
stoppers.push(() => {
|
|
1390
1474
|
void sessionsWatcher.close();
|
|
@@ -1524,8 +1608,7 @@ function streamLines(file, isInitialAdd, cursors, onLine) {
|
|
|
1524
1608
|
const size = safeSize2(file);
|
|
1525
1609
|
let cursor = cursors.get(file);
|
|
1526
1610
|
if (!cursor) {
|
|
1527
|
-
|
|
1528
|
-
cursor = { offset: isInitialAdd ? backfillStart : size };
|
|
1611
|
+
cursor = { offset: backfillStartOffset(file, size, isInitialAdd, BACKFILL_BYTES2) };
|
|
1529
1612
|
cursors.set(file, cursor);
|
|
1530
1613
|
}
|
|
1531
1614
|
if (size <= cursor.offset) return;
|
|
@@ -1550,7 +1633,7 @@ function swallow(err) {
|
|
|
1550
1633
|
}
|
|
1551
1634
|
function safeSize2(file) {
|
|
1552
1635
|
try {
|
|
1553
|
-
return
|
|
1636
|
+
return statSync4(file).size;
|
|
1554
1637
|
} catch {
|
|
1555
1638
|
return 0;
|
|
1556
1639
|
}
|
|
@@ -1738,6 +1821,8 @@ var init_openclaw = __esm({
|
|
|
1738
1821
|
init_schema();
|
|
1739
1822
|
init_ids();
|
|
1740
1823
|
init_jsonl_stream();
|
|
1824
|
+
init_backfill();
|
|
1825
|
+
init_backfill_queue();
|
|
1741
1826
|
init_parse_errors();
|
|
1742
1827
|
init_openclaw_cron();
|
|
1743
1828
|
sessionCwd = /* @__PURE__ */ new Map();
|
|
@@ -1757,7 +1842,7 @@ import {
|
|
|
1757
1842
|
readFileSync as readFileSync4,
|
|
1758
1843
|
existsSync as existsSync6,
|
|
1759
1844
|
readdirSync,
|
|
1760
|
-
statSync as
|
|
1845
|
+
statSync as statSync5
|
|
1761
1846
|
} from "fs";
|
|
1762
1847
|
import { homedir as homedir5 } from "os";
|
|
1763
1848
|
import { join as join6 } from "path";
|
|
@@ -1939,7 +2024,7 @@ function discoverCursorrules(workspace) {
|
|
|
1939
2024
|
if (name === "node_modules") continue;
|
|
1940
2025
|
const dir = join6(workspace, name);
|
|
1941
2026
|
try {
|
|
1942
|
-
if (!
|
|
2027
|
+
if (!statSync5(dir).isDirectory()) continue;
|
|
1943
2028
|
} catch {
|
|
1944
2029
|
continue;
|
|
1945
2030
|
}
|
|
@@ -2220,7 +2305,7 @@ var init_gemini = __esm({
|
|
|
2220
2305
|
|
|
2221
2306
|
// src/adapters/codex.ts
|
|
2222
2307
|
import chokidar6 from "chokidar";
|
|
2223
|
-
import { existsSync as existsSync8, statSync as
|
|
2308
|
+
import { existsSync as existsSync8, statSync as statSync6 } from "fs";
|
|
2224
2309
|
import { basename as basename5, join as join8, sep as sep4 } from "path";
|
|
2225
2310
|
import os5 from "os";
|
|
2226
2311
|
function codexSessionsDir(home = os5.homedir()) {
|
|
@@ -2243,7 +2328,7 @@ function startCodexAdapter(sink) {
|
|
|
2243
2328
|
const size = safeSize3(file);
|
|
2244
2329
|
let cursor = cursors.get(file);
|
|
2245
2330
|
if (!cursor) {
|
|
2246
|
-
const start2 =
|
|
2331
|
+
const start2 = backfillStartOffset(file, size, isInitialAdd, BACKFILL_BYTES3);
|
|
2247
2332
|
cursor = {
|
|
2248
2333
|
offset: start2,
|
|
2249
2334
|
project: "",
|
|
@@ -2364,8 +2449,16 @@ function startCodexAdapter(sink) {
|
|
|
2364
2449
|
}
|
|
2365
2450
|
}
|
|
2366
2451
|
};
|
|
2367
|
-
|
|
2452
|
+
let scanReady = false;
|
|
2453
|
+
const backfillQueue = createBackfillQueue((f) => handle(f, true));
|
|
2454
|
+
watcher2.on("add", (f) => {
|
|
2455
|
+
if (scanReady) handle(f, true);
|
|
2456
|
+
else backfillQueue.enqueue(f);
|
|
2457
|
+
});
|
|
2368
2458
|
watcher2.on("change", (f) => handle(f, false));
|
|
2459
|
+
watcher2.on("ready", () => {
|
|
2460
|
+
scanReady = true;
|
|
2461
|
+
});
|
|
2369
2462
|
watcher2.on("error", (err) => {
|
|
2370
2463
|
if (typeof err === "object" && err !== null) {
|
|
2371
2464
|
const code = err.code;
|
|
@@ -2501,7 +2594,7 @@ function truncate5(s, n) {
|
|
|
2501
2594
|
}
|
|
2502
2595
|
function safeSize3(file) {
|
|
2503
2596
|
try {
|
|
2504
|
-
return
|
|
2597
|
+
return statSync6(file).size;
|
|
2505
2598
|
} catch {
|
|
2506
2599
|
return 0;
|
|
2507
2600
|
}
|
|
@@ -2515,6 +2608,8 @@ var init_codex = __esm({
|
|
|
2515
2608
|
init_cost();
|
|
2516
2609
|
init_spawn_tracker();
|
|
2517
2610
|
init_jsonl_stream();
|
|
2611
|
+
init_backfill();
|
|
2612
|
+
init_backfill_queue();
|
|
2518
2613
|
init_parse_errors();
|
|
2519
2614
|
BACKFILL_BYTES3 = 512 * 1024;
|
|
2520
2615
|
MAX_PENDING = 2e3;
|
|
@@ -4781,7 +4876,7 @@ var init_claude_hooks = __esm({
|
|
|
4781
4876
|
|
|
4782
4877
|
// src/git/correlate.ts
|
|
4783
4878
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4784
|
-
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as
|
|
4879
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync7 } from "fs";
|
|
4785
4880
|
import { join as join12, resolve } from "path";
|
|
4786
4881
|
function runGit(args, opts = {}) {
|
|
4787
4882
|
const verb = args[0];
|
|
@@ -4814,7 +4909,7 @@ function findProjectGitRoot(workspaceRoot, projectName) {
|
|
|
4814
4909
|
if (entry !== projectName) continue;
|
|
4815
4910
|
const candidate = join12(workspaceRoot, entry);
|
|
4816
4911
|
try {
|
|
4817
|
-
const s =
|
|
4912
|
+
const s = statSync7(candidate);
|
|
4818
4913
|
if (!s.isDirectory()) continue;
|
|
4819
4914
|
} catch {
|
|
4820
4915
|
continue;
|
|
@@ -6024,6 +6119,19 @@ function buildStore(db2) {
|
|
|
6024
6119
|
const rows = sessionEventsStmt.all(sessionId);
|
|
6025
6120
|
return rows.map(rowToEvent);
|
|
6026
6121
|
},
|
|
6122
|
+
budgetRollup() {
|
|
6123
|
+
const todayStart = /* @__PURE__ */ new Date();
|
|
6124
|
+
todayStart.setUTCHours(0, 0, 0, 0);
|
|
6125
|
+
const monthAgo = new Date(Date.now() - 30 * 864e5).toISOString();
|
|
6126
|
+
const day = db2.prepare("SELECT SUM(cost_usd) AS c FROM events WHERE ts >= ?").get(todayStart.toISOString());
|
|
6127
|
+
const top = db2.prepare(
|
|
6128
|
+
"SELECT session_id, cost_usd FROM sessions WHERE last_ts >= ? ORDER BY cost_usd DESC LIMIT 1"
|
|
6129
|
+
).get(monthAgo);
|
|
6130
|
+
return {
|
|
6131
|
+
dayCost: day.c ?? 0,
|
|
6132
|
+
maxSession: top ? { id: top.session_id, cost: top.cost_usd } : { id: "", cost: 0 }
|
|
6133
|
+
};
|
|
6134
|
+
},
|
|
6027
6135
|
listRecentEvents(opts = {}) {
|
|
6028
6136
|
const limit = clamp4(opts.limit ?? 1e3, 1, 5e4);
|
|
6029
6137
|
const order = opts.order === "asc" ? "ASC" : "DESC";
|
|
@@ -6677,7 +6785,7 @@ __export(server_exports2, {
|
|
|
6677
6785
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6678
6786
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6679
6787
|
import { z } from "zod";
|
|
6680
|
-
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as
|
|
6788
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync8 } from "fs";
|
|
6681
6789
|
import { homedir as homedir12 } from "os";
|
|
6682
6790
|
import { join as join16 } from "path";
|
|
6683
6791
|
import Database4 from "better-sqlite3";
|
|
@@ -7000,7 +7108,7 @@ function listAllSessions() {
|
|
|
7000
7108
|
for (const f of readdirSync3(projPath)) {
|
|
7001
7109
|
if (!f.endsWith(".jsonl")) continue;
|
|
7002
7110
|
const full = join16(projPath, f);
|
|
7003
|
-
const s =
|
|
7111
|
+
const s = statSync8(full);
|
|
7004
7112
|
out.push({
|
|
7005
7113
|
agent: "claude-code",
|
|
7006
7114
|
sessionId: f.replace(/\.jsonl$/, ""),
|
|
@@ -7068,7 +7176,7 @@ function walkOpenClaw(root, out) {
|
|
|
7068
7176
|
const full = join16(sessionsDir, name);
|
|
7069
7177
|
let st;
|
|
7070
7178
|
try {
|
|
7071
|
-
st =
|
|
7179
|
+
st = statSync8(full);
|
|
7072
7180
|
} catch {
|
|
7073
7181
|
continue;
|
|
7074
7182
|
}
|
|
@@ -7088,7 +7196,7 @@ function walkHermes(dbPath, out) {
|
|
|
7088
7196
|
const db2 = openHermesDb(dbPath);
|
|
7089
7197
|
if (!db2) return;
|
|
7090
7198
|
try {
|
|
7091
|
-
const st =
|
|
7199
|
+
const st = statSync8(dbPath);
|
|
7092
7200
|
const rows = db2.prepare(
|
|
7093
7201
|
"SELECT id, source, message_count, started_at, ended_at FROM sessions"
|
|
7094
7202
|
).all();
|
|
@@ -7131,7 +7239,7 @@ function walkGemini(dir, out) {
|
|
|
7131
7239
|
const full = join16(chatsDir, name);
|
|
7132
7240
|
let st;
|
|
7133
7241
|
try {
|
|
7134
|
-
st =
|
|
7242
|
+
st = statSync8(full);
|
|
7135
7243
|
} catch {
|
|
7136
7244
|
continue;
|
|
7137
7245
|
}
|
|
@@ -7159,7 +7267,7 @@ function walkCodex(dir, out) {
|
|
|
7159
7267
|
const full = join16(dir, name);
|
|
7160
7268
|
let st;
|
|
7161
7269
|
try {
|
|
7162
|
-
st =
|
|
7270
|
+
st = statSync8(full);
|
|
7163
7271
|
} catch {
|
|
7164
7272
|
continue;
|
|
7165
7273
|
}
|
|
@@ -7313,7 +7421,7 @@ var init_install = __esm({
|
|
|
7313
7421
|
});
|
|
7314
7422
|
|
|
7315
7423
|
// src/daemon/log-rotate.ts
|
|
7316
|
-
import { closeSync as closeSync2, openSync as openSync2, renameSync, statSync as
|
|
7424
|
+
import { closeSync as closeSync2, openSync as openSync2, renameSync, statSync as statSync9, writeSync } from "fs";
|
|
7317
7425
|
import { dirname as dirname5 } from "path";
|
|
7318
7426
|
import { mkdirSync as mkdirSync4 } from "fs";
|
|
7319
7427
|
var DEFAULT_MAX_BYTES, RotatingLogStream;
|
|
@@ -7332,7 +7440,7 @@ var init_log_rotate = __esm({
|
|
|
7332
7440
|
mkdirSync4(dirname5(this.path), { recursive: true });
|
|
7333
7441
|
this.fd = openSync2(this.path, "a");
|
|
7334
7442
|
try {
|
|
7335
|
-
this.bytes =
|
|
7443
|
+
this.bytes = statSync9(this.path).size;
|
|
7336
7444
|
} catch {
|
|
7337
7445
|
this.bytes = 0;
|
|
7338
7446
|
}
|
|
@@ -8191,6 +8299,9 @@ function computeBudgetStatus(events, budgets = loadBudgets(), now = /* @__PURE__
|
|
|
8191
8299
|
const t = new Date(e.ts).getTime();
|
|
8192
8300
|
if (t >= todayMs) dayCost += c;
|
|
8193
8301
|
}
|
|
8302
|
+
return budgetStatusFromTotals(dayCost, maxSession, budgets);
|
|
8303
|
+
}
|
|
8304
|
+
function budgetStatusFromTotals(dayCost, maxSession, budgets = loadBudgets()) {
|
|
8194
8305
|
const status = {
|
|
8195
8306
|
sessionCost: maxSession.cost,
|
|
8196
8307
|
dayCost,
|
|
@@ -8204,6 +8315,9 @@ function computeBudgetStatus(events, budgets = loadBudgets(), now = /* @__PURE__
|
|
|
8204
8315
|
return status;
|
|
8205
8316
|
}
|
|
8206
8317
|
|
|
8318
|
+
// src/ui/App.tsx
|
|
8319
|
+
init_backfill();
|
|
8320
|
+
|
|
8207
8321
|
// src/util/otel.ts
|
|
8208
8322
|
init_compaction();
|
|
8209
8323
|
init_version();
|
|
@@ -9266,6 +9380,7 @@ function App() {
|
|
|
9266
9380
|
try {
|
|
9267
9381
|
const seed = store.listRecentEvents({ limit: 500, order: "desc" });
|
|
9268
9382
|
if (seed.length > 0) dispatch({ type: "events-batch", events: seed });
|
|
9383
|
+
setStaleSkipEnabled(seed.length > 0);
|
|
9269
9384
|
} catch {
|
|
9270
9385
|
}
|
|
9271
9386
|
}
|
|
@@ -9337,19 +9452,17 @@ function App() {
|
|
|
9337
9452
|
const agentFiltered = state.filterAgent ? state.events.filter((e) => e.agent === state.filterAgent) : state.events;
|
|
9338
9453
|
const filtered = state.searchQuery ? agentFiltered.filter((e) => matchesQuery(e, state.searchQuery)) : agentFiltered;
|
|
9339
9454
|
const eventsRef = state.events;
|
|
9455
|
+
const [rollupTick, setRollupTick] = useState(0);
|
|
9456
|
+
useEffect(() => {
|
|
9457
|
+
if (!store) return;
|
|
9458
|
+
const id = setInterval(() => setRollupTick((t) => t + 1), 2500);
|
|
9459
|
+
return () => clearInterval(id);
|
|
9460
|
+
}, [store]);
|
|
9340
9461
|
const budgetStatus = useMemo(() => {
|
|
9341
9462
|
if (!store) return computeBudgetStatus(eventsRef);
|
|
9342
|
-
const
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
const monthAgo = new Date(Date.now() - 30 * 864e5).toISOString();
|
|
9346
|
-
const events = store.listRecentEvents({
|
|
9347
|
-
sinceTs: monthAgo < since ? monthAgo : since,
|
|
9348
|
-
limit: 5e4,
|
|
9349
|
-
order: "asc"
|
|
9350
|
-
});
|
|
9351
|
-
return computeBudgetStatus(events);
|
|
9352
|
-
}, [eventsRef, store]);
|
|
9463
|
+
const { dayCost, maxSession } = store.budgetRollup();
|
|
9464
|
+
return budgetStatusFromTotals(dayCost, maxSession);
|
|
9465
|
+
}, [store ? rollupTick : eventsRef, store]);
|
|
9353
9466
|
const anomalies = useMemo(() => {
|
|
9354
9467
|
const source = store ? store.listRecentEvents({ limit: 5e3, order: "desc" }) : eventsRef;
|
|
9355
9468
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -9390,7 +9503,7 @@ function App() {
|
|
|
9390
9503
|
}
|
|
9391
9504
|
}
|
|
9392
9505
|
return out;
|
|
9393
|
-
}, [eventsRef, store]);
|
|
9506
|
+
}, [store ? rollupTick : eventsRef, store]);
|
|
9394
9507
|
const budgetBreachKey = [
|
|
9395
9508
|
budgetStatus.breachedSession ?? "",
|
|
9396
9509
|
budgetStatus.dayBreach ? "day" : ""
|
|
@@ -9842,6 +9955,8 @@ if (arg === "serve") {
|
|
|
9842
9955
|
try {
|
|
9843
9956
|
const seed = store.listRecentEvents({ limit: 5e3, order: "desc" });
|
|
9844
9957
|
for (let i = seed.length - 1; i >= 0; i--) addEventToServer2(server, seed[i]);
|
|
9958
|
+
const { setStaleSkipEnabled: setStaleSkipEnabled2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
|
|
9959
|
+
setStaleSkipEnabled2(seed.length > 0);
|
|
9845
9960
|
} catch (err) {
|
|
9846
9961
|
process.stderr.write(`[agentwatch] ring seed skipped: ${String(err)}
|
|
9847
9962
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misha_misha/agentwatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local-only observability + control plane for every AI coding agent on your machine. TUI live tail + browser dashboard on localhost. Unified timeline across Claude Code, Codex, Gemini CLI, Cursor, Hermes, OpenClaw — token + cost accounting, compaction + anomaly detection, SVG call graphs, diff attribution, agent-aware replay, MCP server mode, OpenTelemetry exporter. No cloud, no telemetry, no sign-in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Misha Nefedov",
|