modelstat 0.0.39 → 0.0.41

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/cli.mjs CHANGED
@@ -32612,10 +32612,14 @@ var init_http = __esm({
32612
32612
  return { kind: "drop", reason: decision.reason };
32613
32613
  }
32614
32614
  if (decision.type === "reauth") {
32615
+ await res.body?.cancel().catch(() => {
32616
+ });
32615
32617
  const ok = await this.opts.auth.onInvalidToken();
32616
32618
  if (!ok) return { kind: "drop", reason: "reauth_failed" };
32617
32619
  continue;
32618
32620
  }
32621
+ await res.body?.cancel().catch(() => {
32622
+ });
32619
32623
  this.opts.logger.warn("ingest backoff", {
32620
32624
  status: res.status,
32621
32625
  delay_ms: decision.delayMs,
@@ -44002,6 +44006,7 @@ var init_config2 = __esm({
44002
44006
  userEmail: null,
44003
44007
  defaultOrgId: null,
44004
44008
  cursor: {},
44009
+ segmentsSent: 0,
44005
44010
  processingVersion: null
44006
44011
  }
44007
44012
  });
@@ -44101,6 +44106,18 @@ var init_config2 = __esm({
44101
44106
  wipeCursors() {
44102
44107
  store.set("cursor", {});
44103
44108
  },
44109
+ /** Lifetime tally of segments uploaded from this machine. Survives
44110
+ * daemon restarts so "total sent" keeps climbing across reboots. */
44111
+ get segmentsSent() {
44112
+ return store.get("segmentsSent") ?? 0;
44113
+ },
44114
+ /** Add to the lifetime tally and return the new total. Persisted
44115
+ * synchronously (same cadence as cursor writes during a scan). */
44116
+ bumpSegmentsSent(n) {
44117
+ const next = (store.get("segmentsSent") ?? 0) + n;
44118
+ store.set("segmentsSent", next);
44119
+ return next;
44120
+ },
44104
44121
  get processingVersion() {
44105
44122
  return store.get("processingVersion");
44106
44123
  },
@@ -44371,7 +44388,7 @@ var init_cognition = __esm({
44371
44388
  });
44372
44389
 
44373
44390
  // ../../packages/companion-core/src/pipeline/index.ts
44374
- async function buildSegmentsForSession(events, adapters2) {
44391
+ async function buildSegmentsForSession(events, adapters2, onProgress) {
44375
44392
  if (events.length === 0) return [];
44376
44393
  const bySession = /* @__PURE__ */ new Map();
44377
44394
  for (const ev of events) {
@@ -44379,14 +44396,22 @@ async function buildSegmentsForSession(events, adapters2) {
44379
44396
  arr.push(ev);
44380
44397
  bySession.set(ev.session_id, arr);
44381
44398
  }
44399
+ const sessions = [...bySession.entries()];
44382
44400
  const out = [];
44383
- for (const [sessionId, evs] of bySession) {
44384
- const segs = await buildForOneSession(sessionId, evs, adapters2);
44401
+ for (let si = 0; si < sessions.length; si++) {
44402
+ const [sessionId, evs] = sessions[si];
44403
+ const segs = await buildForOneSession(
44404
+ sessionId,
44405
+ evs,
44406
+ adapters2,
44407
+ onProgress ? (segment, segmentTotal) => onProgress({ session: si + 1, sessionTotal: sessions.length, segment, segmentTotal }) : void 0
44408
+ );
44385
44409
  out.push(...segs);
44386
44410
  }
44387
44411
  return out;
44388
44412
  }
44389
- async function buildForOneSession(sessionId, events, adapters2) {
44413
+ async function buildForOneSession(sessionId, events, adapters2, onSlice) {
44414
+ onSlice?.(0, 0);
44390
44415
  const sorted = [...events].sort((a, b) => a.ts.localeCompare(b.ts));
44391
44416
  const turnSurfaces = sorted.map((e) => turnSurface(e));
44392
44417
  const turnEmbeddings = [];
@@ -44447,7 +44472,9 @@ async function buildForOneSession(sessionId, events, adapters2) {
44447
44472
  const segments = [];
44448
44473
  let failed = 0;
44449
44474
  let lastError = null;
44450
- for (const slice of merged) {
44475
+ for (let mi = 0; mi < merged.length; mi++) {
44476
+ const slice = merged[mi];
44477
+ onSlice?.(mi + 1, merged.length);
44451
44478
  try {
44452
44479
  const seg = await summariseSlice(sessionId, slice, adapters2);
44453
44480
  if (seg) segments.push(seg);
@@ -45297,27 +45324,15 @@ __export(pipeline_exports, {
45297
45324
  buildSegments: () => buildSegments,
45298
45325
  preflightSummariser: () => preflightSummariser
45299
45326
  });
45300
- async function probeOllama(baseUrl) {
45301
- try {
45302
- const ctrl = new AbortController();
45303
- const t = setTimeout(() => ctrl.abort(), 500);
45304
- const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/tags`, {
45305
- method: "GET",
45306
- signal: ctrl.signal
45307
- });
45308
- clearTimeout(t);
45309
- return res.ok;
45310
- } catch {
45311
- return false;
45312
- }
45313
- }
45314
45327
  async function bundledAdapters() {
45315
45328
  const llamaCfg = defaultLlamaConfig();
45316
45329
  return {
45317
- // Same transformers.js BGE-small embedder as the Ollama path. The
45318
- // bundled-llama path used to ship vector-less (empty arrays);
45319
- // hooking embeddings here means even no-Ollama installs get
45320
- // proper segment-vs-leaf cosine matching at classify time.
45330
+ // transformers.js BGE-small embedder the same model the server
45331
+ // uses, so segment vectors land in the same 384-dim space as
45332
+ // leaf-description vectors and cosine similarity is directly
45333
+ // meaningful. (This path used to ship vector-less with empty
45334
+ // arrays; hooking embeddings here gives proper segment-vs-leaf
45335
+ // matching at classify time.)
45321
45336
  embed: createTransformersJsEmbedder(),
45322
45337
  summarize: llamaSummarize(llamaCfg),
45323
45338
  tokenize: (text) => Math.max(1, Math.ceil(text.length / 4)),
@@ -45334,38 +45349,12 @@ async function bundledAdapters() {
45334
45349
  };
45335
45350
  }
45336
45351
  async function getAdapters() {
45337
- if (adapters && probed) return adapters;
45338
- const ollamaCfg = defaultOllamaConfig();
45339
- const ollamaUp = await probeOllama(ollamaCfg.baseUrl);
45340
- probed = true;
45341
- if (ollamaUp) {
45342
- console.log(
45343
- `[modelstat] ollama up at ${ollamaCfg.baseUrl} \u2014 using ${ollamaCfg.chatModel} for summarisation`
45344
- );
45345
- adapters = {
45346
- // BGE-small via transformers.js — same model the server uses,
45347
- // so segment vectors land in the same 384-dim space as
45348
- // leaf-description vectors and cosine similarity is directly
45349
- // meaningful. We do NOT use ollamaEmbed here because Ollama's
45350
- // library doesn't host bge-small (404 on pull) and shipping
45351
- // MiniLM via Ollama vs BGE-small server-side would break
45352
- // cross-source similarity.
45353
- embed: createTransformersJsEmbedder(),
45354
- summarize: ollamaSummarize(ollamaCfg),
45355
- tokenize: ollamaTokenize(),
45356
- cognize: ollamaCognize(ollamaCfg),
45357
- // Privacy filter — same OpenAI Privacy Filter model regardless
45358
- // of which summariser/embedder runtime we ended up on. Factory
45359
- // is async (dynamic-imports @huggingface/transformers).
45360
- redact: await createPrivacyFilterRedactor()
45361
- };
45362
- return adapters;
45363
- }
45352
+ if (adapters) return adapters;
45364
45353
  try {
45365
45354
  await import("node-llama-cpp");
45366
45355
  } catch (err) {
45367
45356
  throw new Error(
45368
- `modelstat agent can't start: ollama is not reachable AND the bundled summariser (node-llama-cpp) failed to load. Either start ollama (\`ollama serve\`) with \`ollama pull ${ollamaCfg.chatModel}\`, or reinstall the agent so the native binding is rebuilt for this platform. Underlying error: ${err.message}`
45357
+ `modelstat agent can't start: the bundled summariser (node-llama-cpp) failed to load. Re-run \`modelstat connect\` (or \`npm i -g modelstat\`) so the native runtime is re-staged beside the bundle. Underlying error: ${err.message}`
45369
45358
  );
45370
45359
  }
45371
45360
  console.log(
@@ -45374,8 +45363,8 @@ async function getAdapters() {
45374
45363
  adapters = await bundledAdapters();
45375
45364
  return adapters;
45376
45365
  }
45377
- async function buildSegments(events) {
45378
- return buildSegmentsForSession(events, await getAdapters());
45366
+ async function buildSegments(events, onProgress) {
45367
+ return buildSegmentsForSession(events, await getAdapters(), onProgress);
45379
45368
  }
45380
45369
  async function preflightSummariser() {
45381
45370
  const a = await getAdapters();
@@ -45390,7 +45379,7 @@ async function preflightSummariser() {
45390
45379
  }
45391
45380
  return out.length > 60 ? `${out.slice(0, 57)}\u2026` : out;
45392
45381
  }
45393
- var adapters, probed;
45382
+ var adapters;
45394
45383
  var init_pipeline2 = __esm({
45395
45384
  "src/pipeline.ts"() {
45396
45385
  "use strict";
@@ -45398,7 +45387,6 @@ var init_pipeline2 = __esm({
45398
45387
  init_node2();
45399
45388
  init_privacy_filter();
45400
45389
  adapters = null;
45401
- probed = false;
45402
45390
  }
45403
45391
  });
45404
45392
 
@@ -45406,6 +45394,9 @@ var init_pipeline2 = __esm({
45406
45394
  import { readdir, stat as stat2 } from "fs/promises";
45407
45395
  import { homedir as homedir5 } from "os";
45408
45396
  import { join as join4 } from "path";
45397
+ function withNonNullTokens(e) {
45398
+ return e.tokens ? e : { ...e, tokens: { ...ZERO_TOKENS } };
45399
+ }
45409
45400
  async function scanAll(cb = {}) {
45410
45401
  const deviceId = state.deviceId;
45411
45402
  if (!deviceId) throw new Error("agent not enrolled \u2014 run `register` first");
@@ -45463,25 +45454,29 @@ async function scanAll(cb = {}) {
45463
45454
  let filesUnchanged = 0;
45464
45455
  let batchesUploaded = 0;
45465
45456
  let eventsUploaded = 0;
45457
+ let segmentsUploaded = 0;
45466
45458
  let buffer = [];
45467
45459
  let pendingCursors = [];
45468
45460
  async function flushBatch() {
45469
45461
  if (!buffer.length) return;
45470
- cb.onUpload?.(buffer.length);
45471
- const segments = await buildSegments(buffer);
45462
+ const events = buffer.map(withNonNullTokens);
45463
+ const segments = await buildSegments(events, cb.onProgress);
45472
45464
  const batch = {
45473
45465
  batch_id: batchId(),
45474
45466
  device_id: deviceId,
45475
45467
  agent_version: AGENT_VERSION,
45476
- events: buffer,
45468
+ events,
45477
45469
  segments
45478
45470
  };
45471
+ cb.onUpload?.({ events: events.length, segments: segments.length });
45479
45472
  const res = await uploadBatch(batch);
45480
45473
  batchesUploaded += 1;
45481
45474
  eventsUploaded += res.accepted;
45475
+ segmentsUploaded += segments.length;
45482
45476
  for (const pc of pendingCursors) state.setCursor(pc.path, pc.cs);
45483
45477
  pendingCursors = [];
45484
45478
  buffer = [];
45479
+ cb.onUploaded?.({ events: res.accepted, segments: segments.length });
45485
45480
  }
45486
45481
  for (let i = 0; i < jobs.length; i++) {
45487
45482
  const job = jobs[i];
@@ -45507,9 +45502,9 @@ async function scanAll(cb = {}) {
45507
45502
  }
45508
45503
  }
45509
45504
  await flushBatch();
45510
- return { filesScanned, filesUnchanged, batchesUploaded, eventsUploaded };
45505
+ return { filesScanned, filesUnchanged, batchesUploaded, eventsUploaded, segmentsUploaded };
45511
45506
  }
45512
- var AGENT_VERSION, BATCH_MAX_EVENTS;
45507
+ var AGENT_VERSION, BATCH_MAX_EVENTS, ZERO_TOKENS;
45513
45508
  var init_scan = __esm({
45514
45509
  "src/scan.ts"() {
45515
45510
  "use strict";
@@ -45518,8 +45513,15 @@ var init_scan = __esm({
45518
45513
  init_pipeline2();
45519
45514
  init_config2();
45520
45515
  init_api();
45521
- AGENT_VERSION = "agent-0.0.39";
45516
+ AGENT_VERSION = true ? "agent-0.0.41" : "agent-dev";
45522
45517
  BATCH_MAX_EVENTS = 2e3;
45518
+ ZERO_TOKENS = {
45519
+ input: 0,
45520
+ output: 0,
45521
+ cache_creation: 0,
45522
+ cache_read: 0,
45523
+ reasoning: 0
45524
+ };
45523
45525
  }
45524
45526
  });
45525
45527
 
@@ -45529,7 +45531,7 @@ import {
45529
45531
  existsSync as existsSync7,
45530
45532
  mkdirSync as mkdirSync3,
45531
45533
  openSync,
45532
- readFileSync as readFileSync3,
45534
+ readFileSync as readFileSync4,
45533
45535
  renameSync as renameSync2,
45534
45536
  unlinkSync as unlinkSync2,
45535
45537
  writeFileSync as writeFileSync4,
@@ -45550,7 +45552,7 @@ function isProcessAlive(pid) {
45550
45552
  }
45551
45553
  function readLock() {
45552
45554
  try {
45553
- const raw = readFileSync3(LOCK_FILE, "utf8");
45555
+ const raw = readFileSync4(LOCK_FILE, "utf8");
45554
45556
  const obj = JSON.parse(raw);
45555
45557
  if (typeof obj.pid !== "number") return null;
45556
45558
  return {
@@ -45642,6 +45644,47 @@ var init_lock = __esm({
45642
45644
  }
45643
45645
  });
45644
45646
 
45647
+ // src/single-flight.ts
45648
+ function createCoalescingRunner(task) {
45649
+ let running = false;
45650
+ let next = null;
45651
+ let chain = Promise.resolve();
45652
+ function trigger(arg) {
45653
+ if (running) {
45654
+ next = { arg };
45655
+ return chain;
45656
+ }
45657
+ running = true;
45658
+ chain = (async () => {
45659
+ let cur = arg;
45660
+ try {
45661
+ for (; ; ) {
45662
+ try {
45663
+ await task(cur);
45664
+ } catch {
45665
+ }
45666
+ if (!next) break;
45667
+ cur = next.arg;
45668
+ next = null;
45669
+ }
45670
+ } finally {
45671
+ running = false;
45672
+ }
45673
+ })();
45674
+ return chain;
45675
+ }
45676
+ return {
45677
+ trigger,
45678
+ isRunning: () => running,
45679
+ isPending: () => next !== null
45680
+ };
45681
+ }
45682
+ var init_single_flight = __esm({
45683
+ "src/single-flight.ts"() {
45684
+ "use strict";
45685
+ }
45686
+ });
45687
+
45645
45688
  // src/processing-version.ts
45646
45689
  var processing_version_exports = {};
45647
45690
  __export(processing_version_exports, {
@@ -47387,35 +47430,47 @@ __export(daemon_exports, {
47387
47430
  bumpStat: () => bumpStat,
47388
47431
  noteEventAt: () => noteEventAt,
47389
47432
  runDaemon: () => runDaemon,
47433
+ setMessage: () => setMessage,
47390
47434
  setPhase: () => setPhase,
47391
47435
  setProgress: () => setProgress,
47392
- setQueue: () => setQueue
47436
+ setQueue: () => setQueue,
47437
+ setStat: () => setStat
47393
47438
  });
47394
47439
  import { existsSync as existsSync8, statSync as statSync2 } from "fs";
47395
47440
  function setPhase(phase, message) {
47396
47441
  status.phase = phase;
47397
47442
  status.message = message ?? null;
47443
+ scheduleLocalFlush();
47444
+ }
47445
+ function setMessage(message) {
47446
+ status.message = message;
47447
+ scheduleLocalFlush();
47398
47448
  }
47399
47449
  function setProgress(done, total) {
47400
47450
  status.progressDone = done;
47401
47451
  status.progressTotal = total;
47452
+ scheduleLocalFlush();
47402
47453
  }
47403
47454
  function setQueue(n) {
47404
47455
  status.queueSize = n;
47456
+ scheduleLocalFlush();
47405
47457
  }
47406
47458
  function bumpStat(key, delta) {
47407
47459
  const cur = Number(status.stats[key] ?? 0);
47408
47460
  status.stats[key] = cur + delta;
47461
+ scheduleLocalFlush();
47462
+ }
47463
+ function setStat(key, value) {
47464
+ status.stats[key] = value;
47465
+ scheduleLocalFlush();
47409
47466
  }
47410
47467
  function noteEventAt(iso) {
47411
47468
  status.lastEventAt = iso;
47469
+ scheduleLocalFlush();
47412
47470
  }
47413
- async function sendHeartbeat() {
47414
- const bearer = state.bearer;
47415
- const deviceId = state.deviceId;
47416
- if (!bearer || !deviceId) return;
47417
- const body = {
47418
- device_id: deviceId,
47471
+ function snapshotBody() {
47472
+ return {
47473
+ device_id: state.deviceId ?? null,
47419
47474
  status: status.phase,
47420
47475
  message: status.message,
47421
47476
  progress_done: status.progressDone,
@@ -47425,6 +47480,27 @@ async function sendHeartbeat() {
47425
47480
  last_event_at: status.lastEventAt,
47426
47481
  agent_version: AGENT_VERSION2
47427
47482
  };
47483
+ }
47484
+ function scheduleLocalFlush() {
47485
+ if (localFlushTimer) {
47486
+ localFlushPending = true;
47487
+ return;
47488
+ }
47489
+ writeLocalStatus(snapshotBody()).catch(() => void 0);
47490
+ localFlushTimer = setTimeout(() => {
47491
+ localFlushTimer = null;
47492
+ if (localFlushPending) {
47493
+ localFlushPending = false;
47494
+ scheduleLocalFlush();
47495
+ }
47496
+ }, LOCAL_FLUSH_THROTTLE_MS);
47497
+ localFlushTimer.unref();
47498
+ }
47499
+ async function sendHeartbeat() {
47500
+ const bearer = state.bearer;
47501
+ const deviceId = state.deviceId;
47502
+ if (!bearer || !deviceId) return;
47503
+ const body = { ...snapshotBody(), device_id: deviceId };
47428
47504
  try {
47429
47505
  const res = await (0, import_undici2.request)(`${state.apiUrl}/v1/agent/heartbeat`, {
47430
47506
  method: "POST",
@@ -47482,13 +47558,29 @@ async function runScanCycle(reason) {
47482
47558
  try {
47483
47559
  const r = await scanAll({
47484
47560
  onFile(path5, index, total) {
47485
- setProgress(index, total);
47486
- status.message = `Scanning ${index}/${total}: ${basename3(path5)}`;
47561
+ setProgress(index + 1, total);
47562
+ setMessage(`Scanning ${index + 1}/${total}: ${basename3(path5)}`);
47487
47563
  },
47488
- onUpload(eventsInBatch) {
47489
- setPhase("uploading", `Uploading ${eventsInBatch} events`);
47490
- bumpStat("events_uploaded", eventsInBatch);
47564
+ onProgress(p) {
47565
+ const sess = p.sessionTotal > 1 ? ` \xB7 session ${p.session}/${p.sessionTotal}` : "";
47566
+ if (p.segment === 0) {
47567
+ setPhase("processing", `Analyzing${sess}`);
47568
+ } else {
47569
+ setPhase(
47570
+ "processing",
47571
+ `Summarising segment ${p.segment}/${p.segmentTotal}${sess}`
47572
+ );
47573
+ }
47574
+ },
47575
+ onUpload({ segments }) {
47576
+ setPhase("uploading", `Uploading ${segments} segments`);
47577
+ setStat("segments_sending", segments);
47578
+ },
47579
+ onUploaded({ events, segments }) {
47580
+ bumpStat("events_uploaded", events);
47491
47581
  bumpStat("batches_uploaded", 1);
47582
+ setStat("segments_sent", state.bumpSegmentsSent(segments));
47583
+ setStat("segments_sending", 0);
47492
47584
  status.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
47493
47585
  }
47494
47586
  });
@@ -47497,9 +47589,13 @@ async function runScanCycle(reason) {
47497
47589
  setPhase("watching", "Waiting for new events");
47498
47590
  setProgress(0, 0);
47499
47591
  } catch (e) {
47592
+ setStat("segments_sending", 0);
47500
47593
  setPhase("offline", `Upload failed: ${describeErrorWithCause(e)}`);
47501
47594
  }
47502
47595
  }
47596
+ function requestScan(reason) {
47597
+ return scanRunner.trigger(reason);
47598
+ }
47503
47599
  function basename3(p) {
47504
47600
  return p.split("/").pop() ?? p;
47505
47601
  }
@@ -47527,6 +47623,7 @@ async function runDaemon(opts = {}) {
47527
47623
  return;
47528
47624
  }
47529
47625
  setPhase("starting", "Booting");
47626
+ setStat("segments_sent", state.segmentsSent);
47530
47627
  const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
47531
47628
  hb.unref();
47532
47629
  void sendHeartbeat();
@@ -47547,7 +47644,7 @@ async function runDaemon(opts = {}) {
47547
47644
  );
47548
47645
  }
47549
47646
  await runDiscovery();
47550
- await runScanCycle("startup");
47647
+ await requestScan("startup");
47551
47648
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
47552
47649
  const { homedir: homedir9, platform: platform5 } = await import("os");
47553
47650
  const { join: join10 } = await import("path");
@@ -47576,7 +47673,7 @@ async function runDaemon(opts = {}) {
47576
47673
  if (scanTimer) return;
47577
47674
  scanTimer = setTimeout(() => {
47578
47675
  scanTimer = null;
47579
- void runScanCycle(reason);
47676
+ void requestScan(reason);
47580
47677
  }, 1e3);
47581
47678
  };
47582
47679
  watcher.on("add", (p) => {
@@ -47584,9 +47681,9 @@ async function runDaemon(opts = {}) {
47584
47681
  }).on("change", (p) => {
47585
47682
  if (p.endsWith(".jsonl") || p.endsWith(".db")) scheduleScan(`change ${basename3(p)}`);
47586
47683
  }).on("error", (e) => {
47587
- status.message = `watcher error: ${e.message}`;
47684
+ setMessage(`watcher error: ${e.message}`);
47588
47685
  });
47589
- const backstop = setInterval(() => void runScanCycle("interval"), SCAN_INTERVAL_MS);
47686
+ const backstop = setInterval(() => void requestScan("interval"), SCAN_INTERVAL_MS);
47590
47687
  backstop.unref();
47591
47688
  const discoveryTimer = setInterval(
47592
47689
  () => void runDiscovery(),
@@ -47604,7 +47701,7 @@ async function runDaemon(opts = {}) {
47604
47701
  await new Promise(() => {
47605
47702
  });
47606
47703
  }
47607
- var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, lastStatusPath;
47704
+ var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastStatusPath, scanRunner;
47608
47705
  var init_daemon = __esm({
47609
47706
  "src/daemon.ts"() {
47610
47707
  "use strict";
@@ -47615,7 +47712,8 @@ var init_daemon = __esm({
47615
47712
  init_config2();
47616
47713
  init_lock();
47617
47714
  init_scan();
47618
- AGENT_VERSION2 = "agent-0.0.39";
47715
+ init_single_flight();
47716
+ AGENT_VERSION2 = true ? "agent-0.0.41" : "agent-dev";
47619
47717
  HEARTBEAT_INTERVAL_MS = 1e4;
47620
47718
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
47621
47719
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -47628,7 +47726,11 @@ var init_daemon = __esm({
47628
47726
  stats: {},
47629
47727
  lastEventAt: null
47630
47728
  };
47729
+ LOCAL_FLUSH_THROTTLE_MS = 400;
47730
+ localFlushTimer = null;
47731
+ localFlushPending = false;
47631
47732
  lastStatusPath = null;
47733
+ scanRunner = createCoalescingRunner(runScanCycle);
47632
47734
  }
47633
47735
  });
47634
47736
 
@@ -47682,7 +47784,9 @@ async function safeScan(reason) {
47682
47784
  console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] scan (${reason})`);
47683
47785
  const r = await scanAll();
47684
47786
  if (r.batchesUploaded || r.eventsUploaded) {
47685
- console.log(` \u2192 ${r.eventsUploaded} events in ${r.batchesUploaded} batches`);
47787
+ console.log(
47788
+ ` \u2192 ${r.segmentsUploaded} segments \xB7 ${r.eventsUploaded} events in ${r.batchesUploaded} batches`
47789
+ );
47686
47790
  }
47687
47791
  } catch (e) {
47688
47792
  console.warn(" ! scan failed:", e.message);
@@ -47742,9 +47846,12 @@ import {
47742
47846
  copyFileSync,
47743
47847
  existsSync as existsSync6,
47744
47848
  mkdirSync as mkdirSync2,
47849
+ readFileSync as readFileSync3,
47850
+ realpathSync,
47745
47851
  unlinkSync,
47746
47852
  writeFileSync as writeFileSync3
47747
47853
  } from "fs";
47854
+ import { createRequire } from "module";
47748
47855
  import { homedir as homedir6, platform as platform2, userInfo } from "os";
47749
47856
  import { dirname as dirname5, join as join5 } from "path";
47750
47857
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -47779,8 +47886,74 @@ function installBundle() {
47779
47886
  );
47780
47887
  }
47781
47888
  copyFileSync(src, dest);
47889
+ installNativeRuntime(src);
47782
47890
  return dest;
47783
47891
  }
47892
+ function setupRuntime() {
47893
+ return installBundle();
47894
+ }
47895
+ var NODE_LLAMA_CPP_FALLBACK_VERSION = "3.18.1";
47896
+ function sourceLlamaVersion(sourceCli) {
47897
+ try {
47898
+ const req = createRequire(sourceCli);
47899
+ let d = dirname5(realpathSync(req.resolve("node-llama-cpp")));
47900
+ for (let i = 0; i < 10; i++) {
47901
+ const pj = join5(d, "package.json");
47902
+ if (existsSync6(pj)) {
47903
+ const p = JSON.parse(readFileSync3(pj, "utf8"));
47904
+ if (p.name === "node-llama-cpp" && p.version) return p.version;
47905
+ }
47906
+ const up = dirname5(d);
47907
+ if (up === d) break;
47908
+ d = up;
47909
+ }
47910
+ } catch {
47911
+ }
47912
+ return null;
47913
+ }
47914
+ function installNativeRuntime(sourceCli) {
47915
+ const version = sourceLlamaVersion(sourceCli) ?? NODE_LLAMA_CPP_FALLBACK_VERSION;
47916
+ const dest = binDir();
47917
+ try {
47918
+ const have = JSON.parse(
47919
+ readFileSync3(
47920
+ join5(dest, "node_modules", "node-llama-cpp", "package.json"),
47921
+ "utf8"
47922
+ )
47923
+ );
47924
+ if (have.version === version) return [`node-llama-cpp@${version} (cached)`];
47925
+ } catch {
47926
+ }
47927
+ mkdirSync2(dest, { recursive: true });
47928
+ const childEnv = { ...process.env };
47929
+ delete childEnv.npm_config_global;
47930
+ delete childEnv.npm_config_prefix;
47931
+ const r = spawnSync(
47932
+ "npm",
47933
+ [
47934
+ "install",
47935
+ "--prefix",
47936
+ dest,
47937
+ "--global=false",
47938
+ "--no-save",
47939
+ "--omit=dev",
47940
+ "--no-audit",
47941
+ "--no-fund",
47942
+ "--loglevel=error",
47943
+ `node-llama-cpp@${version}`
47944
+ ],
47945
+ { encoding: "utf8", stdio: "pipe", env: childEnv }
47946
+ );
47947
+ if (r.status !== 0) {
47948
+ process.stderr.write(
47949
+ `[modelstat] couldn't stage the bundled summariser runtime via npm (node-llama-cpp@${version}); the daemon's summariser preflight will fail until this is resolved.
47950
+ ${(r.stderr || r.stdout || "").trim()}
47951
+ `
47952
+ );
47953
+ return [];
47954
+ }
47955
+ return [`node-llama-cpp@${version}`];
47956
+ }
47784
47957
  function nodeBinary() {
47785
47958
  return process.execPath;
47786
47959
  }
@@ -48050,7 +48223,7 @@ function tryOpenBrowser(url) {
48050
48223
  return false;
48051
48224
  }
48052
48225
  }
48053
- var AGENT_VERSION3 = "agent-0.0.39";
48226
+ var AGENT_VERSION3 = true ? "agent-0.0.41" : "agent-dev";
48054
48227
  function osFamily() {
48055
48228
  const p = platform4();
48056
48229
  if (p === "darwin") return "macos";
@@ -48376,7 +48549,7 @@ async function cmdScan() {
48376
48549
  const r = await scanAll();
48377
48550
  console.log();
48378
48551
  console.log(
48379
- `Done: ${r.filesScanned} files scanned, ${r.filesUnchanged} unchanged, ${r.batchesUploaded} batches, ${r.eventsUploaded} events uploaded`
48552
+ `Done: ${r.filesScanned} files scanned, ${r.filesUnchanged} unchanged, ${r.batchesUploaded} batches, ${r.segmentsUploaded} segments, ${r.eventsUploaded} events uploaded`
48380
48553
  );
48381
48554
  }
48382
48555
  async function cmdRescan() {
@@ -48605,6 +48778,11 @@ async function main() {
48605
48778
  case "_daemon":
48606
48779
  case "start":
48607
48780
  return cmdStart(rest);
48781
+ case "_setup-runtime": {
48782
+ const dest = setupRuntime();
48783
+ console.log(`\u2713 runtime staged at ${dest}`);
48784
+ return;
48785
+ }
48608
48786
  // ── Diagnostics / dev one-shots ────────────────────────────
48609
48787
  case "status":
48610
48788
  return cmdStatus();