@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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync10, writeFileSync as writeFileSync3 } from "node:fs";
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
- const argIdx = body.lastIndexOf("\nARGUMENTS:");
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 + "\nARGUMENTS:".length).trim() || null;
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 name = c.skill_name;
3969
- const vid = skillVersionId(name, inj.body);
3970
- upsertSkill.run({
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: inj.baseDir,
3974
- body: inj.body,
3975
- summary: skillSummary(inj.body),
3976
- body_bytes: Buffer.byteLength(inj.body, "utf8"),
3977
- ts: e.timestamp
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
- linkCall.run(vid, match.id);
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 existsSync8 } from "node:fs";
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 existsSync7 } from "node:fs";
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 existsSync6, mkdirSync as mkdirSync5 } from "node:fs";
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 (!existsSync6(archiveRoot))
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 && existsSync7(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 (!existsSync8(dbPath)) {
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 (!existsSync8(webDist))
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 existsSync9 } from "node:fs";
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) => existsSync9(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 (!existsSync10(dbPath)) {
6713
+ if (!existsSync11(dbPath)) {
6574
6714
  console.error(`agent-lens: db not found: ${dbPath} (run ingest first)`);
6575
6715
  process.exit(1);
6576
6716
  }