@swestash/agent-lens 0.16.0 → 0.17.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/agent-lens.js +162 -22
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
- package/web/404.html +2 -2
- package/web/assets/AboutView-Crn85usU.js +1 -0
- package/web/assets/{Dashboard-DF8Xb9E1.js → Dashboard-CH8nv5ZK.js} +2 -2
- package/web/assets/FileView-CtovgU1g.js +1 -0
- package/web/assets/{FilesView-DW_bHt2e.js → FilesView-38qLTWbj.js} +1 -1
- package/web/assets/SecurityView-CmKLXZP9.js +1 -0
- package/web/assets/SessionView-Bt-NmoU3.js +19 -0
- package/web/assets/{SkillView-BdZwKqK6.js → SkillView-DQEklf89.js} +1 -1
- package/web/assets/SkillsView-CNX1nAkf.js +1 -0
- package/web/assets/{WorkflowView-yNhKNcg9.js → WorkflowView-CtFEKBzS.js} +1 -1
- package/web/assets/{index-BuG6Awf5.js → index-C5nCASCF.js} +1 -1
- package/web/assets/index-DN9qB28e.css +1 -0
- package/web/assets/index-DYCDqLKI.js +69 -0
- package/web/assets/jsonish-iCEt12Ty.js +1 -0
- package/web/assets/theme-DzMGF9VA.js +1 -0
- package/web/assets/{useResetOn-BEUZVwM5.js → useResetOn-Bvmnyxvw.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/AboutView-BExwxt7F.js +0 -1
- package/web/assets/FileView-hDF8bAaa.js +0 -1
- package/web/assets/SecurityView-DPRI6HpG.js +0 -1
- package/web/assets/SessionView-eGmw43Bq.js +0 -15
- package/web/assets/SkillsView-DUrXsvh_.js +0 -1
- package/web/assets/index-B6dWnwqZ.css +0 -1
- package/web/assets/index-CMT9aRT7.js +0 -69
- package/web/assets/jsonish-DI8SpR-J.js +0 -1
- package/web/assets/theme-DiOdnv26.js +0 -1
package/dist/agent-lens.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5
5
|
import { join as join12 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7
7
|
|
|
@@ -3729,6 +3729,77 @@ function xmlTag(text, tag) {
|
|
|
3729
3729
|
return text.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`))?.[1]?.trim() ?? null;
|
|
3730
3730
|
}
|
|
3731
3731
|
|
|
3732
|
+
// ../ingest/dist/skillslots.js
|
|
3733
|
+
var ARG_TRAILERS = ["\nARGUMENTS:", "\n## User Request\n"];
|
|
3734
|
+
var EMPTY_OUTPUT = "(Bash completed with no output)";
|
|
3735
|
+
var DYNAMIC_PLACEHOLDER = "(dynamic context: command output omitted)";
|
|
3736
|
+
var isMarker = (t) => t === EMPTY_OUTPUT || t === DYNAMIC_PLACEHOLDER;
|
|
3737
|
+
function uniqueLine(lines, text) {
|
|
3738
|
+
let at = -1;
|
|
3739
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3740
|
+
if (lines[i].trim() !== text)
|
|
3741
|
+
continue;
|
|
3742
|
+
if (at >= 0)
|
|
3743
|
+
return -1;
|
|
3744
|
+
at = i;
|
|
3745
|
+
}
|
|
3746
|
+
return at;
|
|
3747
|
+
}
|
|
3748
|
+
function learnSlots(body) {
|
|
3749
|
+
const lines = body.split("\n");
|
|
3750
|
+
const trimmed = lines.map((l) => l.trim());
|
|
3751
|
+
const slots = [];
|
|
3752
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3753
|
+
if (!isMarker(trimmed[i]))
|
|
3754
|
+
continue;
|
|
3755
|
+
let b = i - 1;
|
|
3756
|
+
while (b >= 0 && trimmed[b] === "")
|
|
3757
|
+
b--;
|
|
3758
|
+
let a = i + 1;
|
|
3759
|
+
while (a < lines.length && (trimmed[a] === "" || isMarker(trimmed[a])))
|
|
3760
|
+
a++;
|
|
3761
|
+
i = a - 1;
|
|
3762
|
+
if (b < 0 || a >= lines.length)
|
|
3763
|
+
continue;
|
|
3764
|
+
const slot = { before: trimmed[b], after: trimmed[a] };
|
|
3765
|
+
if (uniqueLine(lines, slot.before) < 0 || uniqueLine(lines, slot.after) < 0)
|
|
3766
|
+
continue;
|
|
3767
|
+
if (!slots.some((s) => s.before === slot.before && s.after === slot.after))
|
|
3768
|
+
slots.push(slot);
|
|
3769
|
+
}
|
|
3770
|
+
return slots;
|
|
3771
|
+
}
|
|
3772
|
+
function mergeSlots(into, body) {
|
|
3773
|
+
for (const s of learnSlots(body))
|
|
3774
|
+
if (!into.some((x) => x.before === s.before && x.after === s.after))
|
|
3775
|
+
into.push(s);
|
|
3776
|
+
return into;
|
|
3777
|
+
}
|
|
3778
|
+
function maskDynamic(body, slots) {
|
|
3779
|
+
let lines = body.split("\n");
|
|
3780
|
+
for (const { before, after } of slots) {
|
|
3781
|
+
const b = uniqueLine(lines, before);
|
|
3782
|
+
const a = uniqueLine(lines, after);
|
|
3783
|
+
if (b < 0 || a <= b)
|
|
3784
|
+
continue;
|
|
3785
|
+
lines = [...lines.slice(0, b + 1), "", DYNAMIC_PLACEHOLDER, "", ...lines.slice(a)];
|
|
3786
|
+
}
|
|
3787
|
+
return lines.join("\n");
|
|
3788
|
+
}
|
|
3789
|
+
var MIN_MASKED_ARGS = 8;
|
|
3790
|
+
function maskKnownValues(body, known) {
|
|
3791
|
+
const subs = [];
|
|
3792
|
+
const args = known.args?.trim();
|
|
3793
|
+
if (args && args.length >= MIN_MASKED_ARGS)
|
|
3794
|
+
subs.push([args, "$ARGUMENTS"]);
|
|
3795
|
+
if (known.baseDir)
|
|
3796
|
+
subs.push([known.baseDir, "${CLAUDE_SKILL_DIR}"]);
|
|
3797
|
+
if (known.sessionId)
|
|
3798
|
+
subs.push([known.sessionId, "${CLAUDE_SESSION_ID}"]);
|
|
3799
|
+
subs.sort((x, y) => y[0].length - x[0].length);
|
|
3800
|
+
return subs.reduce((out, [value, placeholder]) => out.split(value).join(placeholder), body);
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3732
3803
|
// ../ingest/dist/pipeline.js
|
|
3733
3804
|
function newStats() {
|
|
3734
3805
|
return { files: 0, skipped: 0, malformed: 0, newEvents: 0 };
|
|
@@ -3750,9 +3821,15 @@ function parseSkillInjection(text) {
|
|
|
3750
3821
|
const baseDir = firstLine || null;
|
|
3751
3822
|
let body = firstNl >= 0 ? text.slice(firstNl + 1) : "";
|
|
3752
3823
|
let args = null;
|
|
3753
|
-
|
|
3824
|
+
let argIdx = -1;
|
|
3825
|
+
let trailer = "";
|
|
3826
|
+
for (const t of ARG_TRAILERS) {
|
|
3827
|
+
const i = body.lastIndexOf(t);
|
|
3828
|
+
if (i > argIdx)
|
|
3829
|
+
[argIdx, trailer] = [i, t];
|
|
3830
|
+
}
|
|
3754
3831
|
if (argIdx >= 0) {
|
|
3755
|
-
args = body.slice(argIdx +
|
|
3832
|
+
args = body.slice(argIdx + trailer.length).trim() || null;
|
|
3756
3833
|
body = body.slice(0, argIdx);
|
|
3757
3834
|
}
|
|
3758
3835
|
return { baseDir, nameKey: baseDir ? skillNameKey(baseDir) : "", body: body.trim(), args };
|
|
@@ -3916,6 +3993,15 @@ function linkSkillVersions(db, sessionIds) {
|
|
|
3916
3993
|
first_seen = MIN(first_seen, excluded.first_seen),
|
|
3917
3994
|
base_dir = excluded.base_dir`);
|
|
3918
3995
|
const linkCall = db.prepare("UPDATE tool_calls SET skill_id = ? WHERE id = ?");
|
|
3996
|
+
const selLinkedVersions = db.prepare("SELECT id, base_dir, body, first_seen, last_seen FROM skills WHERE name = ? AND id IN (SELECT skill_id FROM tool_calls WHERE skill_id IS NOT NULL)");
|
|
3997
|
+
const upsertRemasked = db.prepare(`INSERT INTO skills (id, name, base_dir, body, summary, body_bytes, first_seen, last_seen)
|
|
3998
|
+
VALUES (@id, @name, @base_dir, @body, @summary, @body_bytes, @first_seen, @last_seen)
|
|
3999
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
4000
|
+
last_seen = MAX(last_seen, excluded.last_seen),
|
|
4001
|
+
first_seen = MIN(first_seen, excluded.first_seen)`);
|
|
4002
|
+
const relinkVersion = db.prepare("UPDATE tool_calls SET skill_id = ? WHERE skill_id = ?");
|
|
4003
|
+
const delVersion = db.prepare("DELETE FROM skills WHERE id = ?");
|
|
4004
|
+
const matches = [];
|
|
3919
4005
|
const linkTx = transaction(db, () => {
|
|
3920
4006
|
for (const { id: sid } of sessionIds) {
|
|
3921
4007
|
const calls = selSkillCalls.all(sid);
|
|
@@ -3965,18 +4051,53 @@ function linkSkillVersions(db, sessionIds) {
|
|
|
3965
4051
|
idx = 0;
|
|
3966
4052
|
const [match] = queue.splice(idx, 1);
|
|
3967
4053
|
const c = calls.find((x) => x.id === match.id);
|
|
3968
|
-
const
|
|
3969
|
-
|
|
3970
|
-
|
|
4054
|
+
const body = maskKnownValues(inj.body, { args: match.args ?? inj.args, baseDir: inj.baseDir, sessionId: sid });
|
|
4055
|
+
matches.push({ callId: match.id, name: c.skill_name, baseDir: inj.baseDir, body, ts: e.timestamp });
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
const slotsByName = /* @__PURE__ */ new Map();
|
|
4059
|
+
const addSlots = (name, body) => slotsByName.set(name, mergeSlots(slotsByName.get(name) ?? [], body));
|
|
4060
|
+
for (const m of matches)
|
|
4061
|
+
addSlots(m.name, m.body);
|
|
4062
|
+
for (const name of slotsByName.keys()) {
|
|
4063
|
+
for (const v of selLinkedVersions.all(name))
|
|
4064
|
+
addSlots(name, v.body);
|
|
4065
|
+
}
|
|
4066
|
+
for (const m of matches) {
|
|
4067
|
+
const body = maskDynamic(m.body, slotsByName.get(m.name));
|
|
4068
|
+
const vid = skillVersionId(m.name, body);
|
|
4069
|
+
upsertSkill.run({
|
|
4070
|
+
id: vid,
|
|
4071
|
+
name: m.name,
|
|
4072
|
+
base_dir: m.baseDir,
|
|
4073
|
+
body,
|
|
4074
|
+
summary: skillSummary(body),
|
|
4075
|
+
body_bytes: Buffer.byteLength(body, "utf8"),
|
|
4076
|
+
ts: m.ts
|
|
4077
|
+
});
|
|
4078
|
+
linkCall.run(vid, m.callId);
|
|
4079
|
+
}
|
|
4080
|
+
for (const [name, slots] of slotsByName) {
|
|
4081
|
+
if (!slots.length)
|
|
4082
|
+
continue;
|
|
4083
|
+
const versions = selLinkedVersions.all(name);
|
|
4084
|
+
for (const v of versions) {
|
|
4085
|
+
const body = maskDynamic(v.body, slots);
|
|
4086
|
+
if (body === v.body)
|
|
4087
|
+
continue;
|
|
4088
|
+
const vid = skillVersionId(name, body);
|
|
4089
|
+
upsertRemasked.run({
|
|
3971
4090
|
id: vid,
|
|
3972
4091
|
name,
|
|
3973
|
-
base_dir:
|
|
3974
|
-
body
|
|
3975
|
-
summary: skillSummary(
|
|
3976
|
-
body_bytes: Buffer.byteLength(
|
|
3977
|
-
|
|
4092
|
+
base_dir: v.base_dir,
|
|
4093
|
+
body,
|
|
4094
|
+
summary: skillSummary(body),
|
|
4095
|
+
body_bytes: Buffer.byteLength(body, "utf8"),
|
|
4096
|
+
first_seen: v.first_seen,
|
|
4097
|
+
last_seen: v.last_seen
|
|
3978
4098
|
});
|
|
3979
|
-
|
|
4099
|
+
relinkVersion.run(vid, v.id);
|
|
4100
|
+
delVersion.run(v.id);
|
|
3980
4101
|
}
|
|
3981
4102
|
}
|
|
3982
4103
|
});
|
|
@@ -4643,11 +4764,12 @@ function runMetrics(argv = process.argv.slice(2)) {
|
|
|
4643
4764
|
}
|
|
4644
4765
|
|
|
4645
4766
|
// ../server/dist/run.js
|
|
4646
|
-
import { existsSync as
|
|
4767
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
4647
4768
|
import { dirname as dirname7 } from "node:path";
|
|
4648
4769
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4649
4770
|
|
|
4650
4771
|
// ../server/dist/db.js
|
|
4772
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4651
4773
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
4652
4774
|
|
|
4653
4775
|
// ../server/dist/sql-util.js
|
|
@@ -5105,6 +5227,17 @@ function getSession(db, id) {
|
|
|
5105
5227
|
file_changes: loadFileChanges(db, id)
|
|
5106
5228
|
};
|
|
5107
5229
|
}
|
|
5230
|
+
function sessionLocation(db, id) {
|
|
5231
|
+
const session = queryGet(db, "SELECT source_id FROM sessions WHERE id = ?", id);
|
|
5232
|
+
if (!session)
|
|
5233
|
+
return null;
|
|
5234
|
+
const files = queryAll(db, "SELECT DISTINCT source_file FROM events WHERE session_id = ? AND source_file IS NOT NULL", id).map((r) => r.source_file);
|
|
5235
|
+
if (files.length === 0)
|
|
5236
|
+
return null;
|
|
5237
|
+
const live = files.filter((f) => !f.includes("/.versions/"));
|
|
5238
|
+
const path3 = live.find((f) => f.endsWith(`/${id}.jsonl`)) ?? live[0] ?? files[0];
|
|
5239
|
+
return { session_id: id, path: path3, exists: existsSync6(path3), source_id: session.source_id };
|
|
5240
|
+
}
|
|
5108
5241
|
function safeJson(s) {
|
|
5109
5242
|
if (!s)
|
|
5110
5243
|
return null;
|
|
@@ -5468,7 +5601,7 @@ function securitySummary(db) {
|
|
|
5468
5601
|
}
|
|
5469
5602
|
|
|
5470
5603
|
// ../server/dist/app.js
|
|
5471
|
-
import { existsSync as
|
|
5604
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5472
5605
|
import Fastify from "fastify";
|
|
5473
5606
|
import fastifyStatic from "@fastify/static";
|
|
5474
5607
|
|
|
@@ -5956,7 +6089,7 @@ function findingsOverTime(db, w, bucket) {
|
|
|
5956
6089
|
}
|
|
5957
6090
|
|
|
5958
6091
|
// ../server/dist/refresh.js
|
|
5959
|
-
import { existsSync as
|
|
6092
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "node:fs";
|
|
5960
6093
|
import { join as join10 } from "node:path";
|
|
5961
6094
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
5962
6095
|
function originAllowed(origin) {
|
|
@@ -5984,7 +6117,7 @@ function runRefresh() {
|
|
|
5984
6117
|
try {
|
|
5985
6118
|
const collected = collectAll();
|
|
5986
6119
|
const archiveRoot = resolveArchiveDir();
|
|
5987
|
-
if (!
|
|
6120
|
+
if (!existsSync7(archiveRoot))
|
|
5988
6121
|
mkdirSync5(archiveRoot, { recursive: true });
|
|
5989
6122
|
runIngest([]);
|
|
5990
6123
|
return { ok: true, collected };
|
|
@@ -6286,6 +6419,13 @@ async function createApp(db, opts = {}) {
|
|
|
6286
6419
|
return notFound(reply);
|
|
6287
6420
|
return result;
|
|
6288
6421
|
});
|
|
6422
|
+
app.get("/api/sessions/:id/location", async (req, reply) => {
|
|
6423
|
+
const { id } = req.params;
|
|
6424
|
+
const result = sessionLocation(db, id);
|
|
6425
|
+
if (!result)
|
|
6426
|
+
return notFound(reply, "no archive path recorded for this session");
|
|
6427
|
+
return result;
|
|
6428
|
+
});
|
|
6289
6429
|
app.get("/api/workflows/:run_id", async (req, reply) => {
|
|
6290
6430
|
const { run_id } = req.params;
|
|
6291
6431
|
const result = getWorkflow(db, run_id);
|
|
@@ -6405,7 +6545,7 @@ async function createApp(db, opts = {}) {
|
|
|
6405
6545
|
return notFound(reply);
|
|
6406
6546
|
reply.header("content-type", "text/markdown; charset=utf-8").header("content-disposition", `attachment; filename="${out.filename}"`).send(out.markdown);
|
|
6407
6547
|
});
|
|
6408
|
-
if (opts.webDist &&
|
|
6548
|
+
if (opts.webDist && existsSync8(opts.webDist)) {
|
|
6409
6549
|
await app.register(fastifyStatic, { root: opts.webDist });
|
|
6410
6550
|
app.setNotFoundHandler((req, reply) => {
|
|
6411
6551
|
if (req.url.startsWith("/api/"))
|
|
@@ -6430,7 +6570,7 @@ async function startServer(overrides = {}) {
|
|
|
6430
6570
|
console.error(`agent-lens-server: refusing to bind non-loopback host '${host}' (privacy). Set AGENT_LENS_ALLOW_NONLOCAL=1 to override.`);
|
|
6431
6571
|
process.exit(1);
|
|
6432
6572
|
}
|
|
6433
|
-
if (!
|
|
6573
|
+
if (!existsSync9(dbPath)) {
|
|
6434
6574
|
console.error(`agent-lens-server: db not found: ${dbPath} (run ingest first)`);
|
|
6435
6575
|
process.exit(1);
|
|
6436
6576
|
}
|
|
@@ -6446,7 +6586,7 @@ async function startServer(overrides = {}) {
|
|
|
6446
6586
|
try {
|
|
6447
6587
|
await app.listen({ host, port });
|
|
6448
6588
|
console.log(`agent-lens-server: http://${host}:${port} (db: ${dbPath})`);
|
|
6449
|
-
if (!
|
|
6589
|
+
if (!existsSync9(webDist))
|
|
6450
6590
|
console.log(" note: web SPA not built \u2014 run `pnpm --filter @agent-lens/web build`");
|
|
6451
6591
|
} catch (err) {
|
|
6452
6592
|
console.error(err);
|
|
@@ -6455,7 +6595,7 @@ async function startServer(overrides = {}) {
|
|
|
6455
6595
|
}
|
|
6456
6596
|
|
|
6457
6597
|
// src/watch.ts
|
|
6458
|
-
import { existsSync as
|
|
6598
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6459
6599
|
import { join as join11 } from "node:path";
|
|
6460
6600
|
import chokidar from "chokidar";
|
|
6461
6601
|
async function runWatch(opts = {}) {
|
|
@@ -6493,7 +6633,7 @@ async function runWatch(opts = {}) {
|
|
|
6493
6633
|
}
|
|
6494
6634
|
};
|
|
6495
6635
|
cycle();
|
|
6496
|
-
const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) =>
|
|
6636
|
+
const watchPaths = sources.map((s) => join11(s.configDir, "projects")).filter((p) => existsSync10(p));
|
|
6497
6637
|
if (watchPaths.length === 0) {
|
|
6498
6638
|
console.error("agent-lens watch: no source 'projects' directories found \u2014 nothing to watch");
|
|
6499
6639
|
}
|
|
@@ -6570,7 +6710,7 @@ cli.command("metrics", "Re-run classification over an already-ingested DB").opti
|
|
|
6570
6710
|
});
|
|
6571
6711
|
cli.command("export <sessionId>", "Write a session to a shareable Markdown file (redacted by default)").option("--out <file>", "Write to this file (default: stdout)").option("--level <level>", "Redaction level: secrets (default) | structure").option("--no-redact", "Verbatim, UNREDACTED output (opt-out)").option("--db <path>", "SQLite DB path (overrides AGENT_LENS_DB / config db)").action((sessionId, opts) => {
|
|
6572
6712
|
const { path: dbPath } = resolveDbPath(opts.db);
|
|
6573
|
-
if (!
|
|
6713
|
+
if (!existsSync11(dbPath)) {
|
|
6574
6714
|
console.error(`agent-lens: db not found: ${dbPath} (run ingest first)`);
|
|
6575
6715
|
process.exit(1);
|
|
6576
6716
|
}
|