modelstat 0.0.45 → 0.0.46

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
@@ -50,9 +50,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
50
50
  ));
51
51
 
52
52
  // ../../packages/parsers/src/types.ts
53
+ var PARSER_EVENT_CHUNK;
53
54
  var init_types = __esm({
54
55
  "../../packages/parsers/src/types.ts"() {
55
56
  "use strict";
57
+ PARSER_EVENT_CHUNK = 256;
56
58
  }
57
59
  });
58
60
 
@@ -4722,6 +4724,21 @@ function extractExcerpt(content) {
4722
4724
  }
4723
4725
  async function parseClaudeCodeJsonl(ctx) {
4724
4726
  const events = [];
4727
+ let chunk = [];
4728
+ let emitted = 0;
4729
+ const emit = async (e) => {
4730
+ emitted += 1;
4731
+ if (!ctx.onEvents) {
4732
+ events.push(e);
4733
+ return;
4734
+ }
4735
+ chunk.push(e);
4736
+ if (chunk.length >= PARSER_EVENT_CHUNK) {
4737
+ const full = chunk;
4738
+ chunk = [];
4739
+ await ctx.onEvents(full);
4740
+ }
4741
+ };
4725
4742
  let rawLines = 0;
4726
4743
  let skipped = 0;
4727
4744
  let bytePos = 0;
@@ -4809,7 +4826,7 @@ async function parseClaudeCodeJsonl(ctx) {
4809
4826
  }
4810
4827
  const slug = guessRepoSlugFromPath(cwd);
4811
4828
  const excerpt = extractExcerpt(a.message?.content);
4812
- events.push({
4829
+ await emit({
4813
4830
  source_event_id: eventId,
4814
4831
  ts: a.timestamp,
4815
4832
  kind: "assistant_message",
@@ -4858,7 +4875,7 @@ async function parseClaudeCodeJsonl(ctx) {
4858
4875
  continue;
4859
4876
  }
4860
4877
  const excerpt = extractExcerpt(u.message?.content);
4861
- events.push({
4878
+ await emit({
4862
4879
  source_event_id: eventId,
4863
4880
  ts: u.timestamp,
4864
4881
  kind: "user_message",
@@ -4883,9 +4900,10 @@ async function parseClaudeCodeJsonl(ctx) {
4883
4900
  skipped += 1;
4884
4901
  }
4885
4902
  }
4903
+ if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
4886
4904
  return {
4887
4905
  events,
4888
- stats: { rawLines, emittedEvents: events.length, skipped },
4906
+ stats: { rawLines, emittedEvents: emitted, skipped },
4889
4907
  sourceFile: ctx.sourceFile
4890
4908
  };
4891
4909
  }
@@ -4907,6 +4925,7 @@ var init_claude_code = __esm({
4907
4925
  "../../packages/parsers/src/claude-code/index.ts"() {
4908
4926
  "use strict";
4909
4927
  init_src();
4928
+ init_types();
4910
4929
  init_git();
4911
4930
  }
4912
4931
  });
@@ -4922,6 +4941,21 @@ function deriveSessionIdFromRolloutPath(path5) {
4922
4941
  }
4923
4942
  async function parseCodexRollout(ctx) {
4924
4943
  const events = [];
4944
+ let chunk = [];
4945
+ let emitted = 0;
4946
+ const emit = async (e) => {
4947
+ emitted += 1;
4948
+ if (!ctx.onEvents) {
4949
+ events.push(e);
4950
+ return;
4951
+ }
4952
+ chunk.push(e);
4953
+ if (chunk.length >= PARSER_EVENT_CHUNK) {
4954
+ const full = chunk;
4955
+ chunk = [];
4956
+ await ctx.onEvents(full);
4957
+ }
4958
+ };
4925
4959
  let rawLines = 0;
4926
4960
  let skipped = 0;
4927
4961
  let bytePos = 0;
@@ -4977,7 +5011,7 @@ async function parseCodexRollout(ctx) {
4977
5011
  continue;
4978
5012
  }
4979
5013
  const slug = guessRepoSlugFromPath(cwd);
4980
- events.push({
5014
+ await emit({
4981
5015
  source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
4982
5016
  ts,
4983
5017
  kind: "assistant_message",
@@ -5016,7 +5050,7 @@ async function parseCodexRollout(ctx) {
5016
5050
  skipped += 1;
5017
5051
  continue;
5018
5052
  }
5019
- events.push({
5053
+ await emit({
5020
5054
  source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
5021
5055
  ts,
5022
5056
  kind: "user_message",
@@ -5042,9 +5076,10 @@ async function parseCodexRollout(ctx) {
5042
5076
  }
5043
5077
  skipped += 1;
5044
5078
  }
5079
+ if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
5045
5080
  return {
5046
5081
  events,
5047
- stats: { rawLines, emittedEvents: events.length, skipped },
5082
+ stats: { rawLines, emittedEvents: emitted, skipped },
5048
5083
  sourceFile: ctx.sourceFile
5049
5084
  };
5050
5085
  }
@@ -5052,6 +5087,7 @@ var init_codex = __esm({
5052
5087
  "../../packages/parsers/src/codex/index.ts"() {
5053
5088
  "use strict";
5054
5089
  init_src();
5090
+ init_types();
5055
5091
  init_git();
5056
5092
  }
5057
5093
  });
@@ -7622,107 +7658,6 @@ var init_src2 = __esm({
7622
7658
  }
7623
7659
  });
7624
7660
 
7625
- // src/identity.ts
7626
- import {
7627
- chmodSync,
7628
- mkdirSync,
7629
- readFileSync as readFileSync2,
7630
- renameSync,
7631
- writeFileSync,
7632
- existsSync as existsSync4
7633
- } from "fs";
7634
- import { homedir as homedir2, hostname as osHostname } from "os";
7635
- import { join as join3 } from "path";
7636
- function ensureRoot() {
7637
- mkdirSync(ROOT, { recursive: true, mode: 448 });
7638
- }
7639
- function writeAtomic(meta) {
7640
- ensureRoot();
7641
- const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
7642
- writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
7643
- renameSync(tmp, IDENTITY_FILE);
7644
- try {
7645
- chmodSync(IDENTITY_FILE, 384);
7646
- } catch {
7647
- }
7648
- }
7649
- function identityPath() {
7650
- return IDENTITY_FILE;
7651
- }
7652
- function hasIdentityFile() {
7653
- return existsSync4(IDENTITY_FILE);
7654
- }
7655
- function parseFile() {
7656
- try {
7657
- const raw = readFileSync2(IDENTITY_FILE, "utf8");
7658
- const obj = JSON.parse(raw);
7659
- if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
7660
- return null;
7661
- }
7662
- return {
7663
- deviceUuid: obj.deviceUuid,
7664
- deviceId: obj.deviceId,
7665
- bearerToken: obj.bearerToken,
7666
- claimCode: obj.claimCode ?? null,
7667
- claimUrl: obj.claimUrl ?? null,
7668
- hostname: obj.hostname ?? osHostname(),
7669
- createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
7670
- userEmail: obj.userEmail ?? null,
7671
- defaultOrgId: obj.defaultOrgId ?? null
7672
- };
7673
- } catch {
7674
- return null;
7675
- }
7676
- }
7677
- function loadIdentity(migrateFromConf2) {
7678
- const fromFile = parseFile();
7679
- if (fromFile) return fromFile;
7680
- if (!migrateFromConf2) return null;
7681
- const legacy = migrateFromConf2();
7682
- if (!legacy) return null;
7683
- if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
7684
- return null;
7685
- }
7686
- const migrated = {
7687
- deviceUuid: legacy.deviceUuid,
7688
- deviceId: legacy.deviceId,
7689
- bearerToken: legacy.bearerToken,
7690
- claimCode: legacy.claimCode ?? null,
7691
- claimUrl: legacy.claimUrl ?? null,
7692
- hostname: osHostname(),
7693
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7694
- userEmail: legacy.userEmail ?? null,
7695
- defaultOrgId: legacy.defaultOrgId ?? null
7696
- };
7697
- writeAtomic(migrated);
7698
- return migrated;
7699
- }
7700
- function saveIdentity(meta) {
7701
- writeAtomic(meta);
7702
- }
7703
- function backupIdentity() {
7704
- if (!existsSync4(IDENTITY_FILE)) return null;
7705
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7706
- const dest = `${IDENTITY_FILE}.bak-${stamp}`;
7707
- renameSync(IDENTITY_FILE, dest);
7708
- return dest;
7709
- }
7710
- function updateIdentity(patch) {
7711
- const current = parseFile();
7712
- if (!current) return null;
7713
- const merged = { ...current, ...patch };
7714
- writeAtomic(merged);
7715
- return merged;
7716
- }
7717
- var ROOT, IDENTITY_FILE;
7718
- var init_identity = __esm({
7719
- "src/identity.ts"() {
7720
- "use strict";
7721
- ROOT = join3(homedir2(), ".modelstat");
7722
- IDENTITY_FILE = join3(ROOT, "identity.json");
7723
- }
7724
- });
7725
-
7726
7661
  // ../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js
7727
7662
  var require_symbols = __commonJS({
7728
7663
  "../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js"(exports, module) {
@@ -32614,6 +32549,9 @@ function classifyStatus(status2, attempt) {
32614
32549
  }
32615
32550
  return { type: "drop", reason: `http_${status2}` };
32616
32551
  }
32552
+ function wellFormedStringify(value) {
32553
+ return JSON.stringify(value, (_key, v) => typeof v === "string" ? v.toWellFormed() : v);
32554
+ }
32617
32555
  function sleep(ms) {
32618
32556
  return new Promise((r) => {
32619
32557
  setTimeout(r, ms);
@@ -32652,7 +32590,10 @@ var init_http = __esm({
32652
32590
  "content-type": "application/json",
32653
32591
  authorization: `Bearer ${token}`
32654
32592
  },
32655
- body: JSON.stringify(batch)
32593
+ // wellFormedStringify, not JSON.stringify: a truncated-emoji
32594
+ // lone surrogate in any excerpt 400s the whole batch on the
32595
+ // serde_json side. See the helper's doc comment.
32596
+ body: wellFormedStringify(batch)
32656
32597
  });
32657
32598
  } catch (err) {
32658
32599
  const detail = describeErrorWithCause(err);
@@ -33301,15 +33242,15 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
33301
33242
  }
33302
33243
  return linux(name);
33303
33244
  }
33304
- var homedir3, tmpdir, env, macos, windows, linux;
33245
+ var homedir2, tmpdir, env, macos, windows, linux;
33305
33246
  var init_env_paths = __esm({
33306
33247
  "../../node_modules/.pnpm/env-paths@3.0.0/node_modules/env-paths/index.js"() {
33307
33248
  "use strict";
33308
- homedir3 = os.homedir();
33249
+ homedir2 = os.homedir();
33309
33250
  tmpdir = os.tmpdir();
33310
33251
  ({ env } = process2);
33311
33252
  macos = (name) => {
33312
- const library = path.join(homedir3, "Library");
33253
+ const library = path.join(homedir2, "Library");
33313
33254
  return {
33314
33255
  data: path.join(library, "Application Support", name),
33315
33256
  config: path.join(library, "Preferences", name),
@@ -33319,8 +33260,8 @@ var init_env_paths = __esm({
33319
33260
  };
33320
33261
  };
33321
33262
  windows = (name) => {
33322
- const appData = env.APPDATA || path.join(homedir3, "AppData", "Roaming");
33323
- const localAppData = env.LOCALAPPDATA || path.join(homedir3, "AppData", "Local");
33263
+ const appData = env.APPDATA || path.join(homedir2, "AppData", "Roaming");
33264
+ const localAppData = env.LOCALAPPDATA || path.join(homedir2, "AppData", "Local");
33324
33265
  return {
33325
33266
  // Data/config/cache/log are invented by me as Windows isn't opinionated about this
33326
33267
  data: path.join(localAppData, name, "Data"),
@@ -33331,13 +33272,13 @@ var init_env_paths = __esm({
33331
33272
  };
33332
33273
  };
33333
33274
  linux = (name) => {
33334
- const username = path.basename(homedir3);
33275
+ const username = path.basename(homedir2);
33335
33276
  return {
33336
- data: path.join(env.XDG_DATA_HOME || path.join(homedir3, ".local", "share"), name),
33337
- config: path.join(env.XDG_CONFIG_HOME || path.join(homedir3, ".config"), name),
33338
- cache: path.join(env.XDG_CACHE_HOME || path.join(homedir3, ".cache"), name),
33277
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir2, ".local", "share"), name),
33278
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir2, ".config"), name),
33279
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir2, ".cache"), name),
33339
33280
  // https://wiki.debian.org/XDGBaseDirectorySpecification#state
33340
- log: path.join(env.XDG_STATE_HOME || path.join(homedir3, ".local", "state"), name),
33281
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir2, ".local", "state"), name),
33341
33282
  temp: path.join(tmpdir, username, name)
33342
33283
  };
33343
33284
  };
@@ -33778,9 +33719,9 @@ import { once } from "events";
33778
33719
  import { createWriteStream } from "fs";
33779
33720
  import path3 from "path";
33780
33721
  import { Readable } from "stream";
33781
- function writeFileSync2(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33722
+ function writeFileSync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33782
33723
  if (isString(options))
33783
- return writeFileSync2(filePath, data, { encoding: options });
33724
+ return writeFileSync(filePath, data, { encoding: options });
33784
33725
  const timeout = options.timeout ?? DEFAULT_TIMEOUT_SYNC;
33785
33726
  const retryOptions = { timeout };
33786
33727
  let tempDisposer = null;
@@ -43903,7 +43844,7 @@ var init_source = __esm({
43903
43844
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43904
43845
  } else {
43905
43846
  try {
43906
- writeFileSync2(this.path, data, { mode: this.#options.configFileMode });
43847
+ writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43907
43848
  } catch (error) {
43908
43849
  if (error?.code === "EXDEV") {
43909
43850
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
@@ -44004,6 +43945,107 @@ var init_source = __esm({
44004
43945
  }
44005
43946
  });
44006
43947
 
43948
+ // src/identity.ts
43949
+ import {
43950
+ chmodSync,
43951
+ mkdirSync,
43952
+ readFileSync as readFileSync2,
43953
+ renameSync,
43954
+ writeFileSync as writeFileSync2,
43955
+ existsSync as existsSync4
43956
+ } from "fs";
43957
+ import { homedir as homedir3, hostname as osHostname } from "os";
43958
+ import { join as join3 } from "path";
43959
+ function ensureRoot() {
43960
+ mkdirSync(ROOT, { recursive: true, mode: 448 });
43961
+ }
43962
+ function writeAtomic(meta) {
43963
+ ensureRoot();
43964
+ const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
43965
+ writeFileSync2(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
43966
+ renameSync(tmp, IDENTITY_FILE);
43967
+ try {
43968
+ chmodSync(IDENTITY_FILE, 384);
43969
+ } catch {
43970
+ }
43971
+ }
43972
+ function identityPath() {
43973
+ return IDENTITY_FILE;
43974
+ }
43975
+ function hasIdentityFile() {
43976
+ return existsSync4(IDENTITY_FILE);
43977
+ }
43978
+ function parseFile() {
43979
+ try {
43980
+ const raw = readFileSync2(IDENTITY_FILE, "utf8");
43981
+ const obj = JSON.parse(raw);
43982
+ if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
43983
+ return null;
43984
+ }
43985
+ return {
43986
+ deviceUuid: obj.deviceUuid,
43987
+ deviceId: obj.deviceId,
43988
+ bearerToken: obj.bearerToken,
43989
+ claimCode: obj.claimCode ?? null,
43990
+ claimUrl: obj.claimUrl ?? null,
43991
+ hostname: obj.hostname ?? osHostname(),
43992
+ createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
43993
+ userEmail: obj.userEmail ?? null,
43994
+ defaultOrgId: obj.defaultOrgId ?? null
43995
+ };
43996
+ } catch {
43997
+ return null;
43998
+ }
43999
+ }
44000
+ function loadIdentity(migrateFromConf2) {
44001
+ const fromFile = parseFile();
44002
+ if (fromFile) return fromFile;
44003
+ if (!migrateFromConf2) return null;
44004
+ const legacy = migrateFromConf2();
44005
+ if (!legacy) return null;
44006
+ if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
44007
+ return null;
44008
+ }
44009
+ const migrated = {
44010
+ deviceUuid: legacy.deviceUuid,
44011
+ deviceId: legacy.deviceId,
44012
+ bearerToken: legacy.bearerToken,
44013
+ claimCode: legacy.claimCode ?? null,
44014
+ claimUrl: legacy.claimUrl ?? null,
44015
+ hostname: osHostname(),
44016
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
44017
+ userEmail: legacy.userEmail ?? null,
44018
+ defaultOrgId: legacy.defaultOrgId ?? null
44019
+ };
44020
+ writeAtomic(migrated);
44021
+ return migrated;
44022
+ }
44023
+ function saveIdentity(meta) {
44024
+ writeAtomic(meta);
44025
+ }
44026
+ function backupIdentity() {
44027
+ if (!existsSync4(IDENTITY_FILE)) return null;
44028
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
44029
+ const dest = `${IDENTITY_FILE}.bak-${stamp}`;
44030
+ renameSync(IDENTITY_FILE, dest);
44031
+ return dest;
44032
+ }
44033
+ function updateIdentity(patch) {
44034
+ const current = parseFile();
44035
+ if (!current) return null;
44036
+ const merged = { ...current, ...patch };
44037
+ writeAtomic(merged);
44038
+ return merged;
44039
+ }
44040
+ var ROOT, IDENTITY_FILE;
44041
+ var init_identity = __esm({
44042
+ "src/identity.ts"() {
44043
+ "use strict";
44044
+ ROOT = join3(homedir3(), ".modelstat");
44045
+ IDENTITY_FILE = join3(ROOT, "identity.json");
44046
+ }
44047
+ });
44048
+
44007
44049
  // src/config.ts
44008
44050
  import { existsSync as existsSync5 } from "fs";
44009
44051
  import { hostname } from "os";
@@ -45322,6 +45364,21 @@ var init_llama = __esm({
45322
45364
  }
45323
45365
  });
45324
45366
 
45367
+ // ../../packages/companion-core/src/optional-module.ts
45368
+ function isMissingOptionalModuleError(err) {
45369
+ const code = err?.code;
45370
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return true;
45371
+ const msg = err instanceof Error ? err.message : String(err);
45372
+ return /cannot find (package|module)|cannot resolve|failed to resolve/i.test(msg);
45373
+ }
45374
+ var OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS;
45375
+ var init_optional_module = __esm({
45376
+ "../../packages/companion-core/src/optional-module.ts"() {
45377
+ "use strict";
45378
+ OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS = 3;
45379
+ }
45380
+ });
45381
+
45325
45382
  // ../../packages/companion-core/src/node/transformersjs-embed.ts
45326
45383
  async function loadPipeline(model) {
45327
45384
  if (cached) return cached;
@@ -45329,11 +45386,7 @@ async function loadPipeline(model) {
45329
45386
  if (!loadPromise2) {
45330
45387
  loadPromise2 = (async () => {
45331
45388
  try {
45332
- const moduleId = "@huggingface/transformers";
45333
- const tjs = await import(
45334
- /* @vite-ignore */
45335
- moduleId
45336
- );
45389
+ const tjs = await importModule("@huggingface/transformers");
45337
45390
  const p = await tjs.pipeline("feature-extraction", model, {
45338
45391
  device: "cpu",
45339
45392
  dtype: "fp32"
@@ -45342,12 +45395,16 @@ async function loadPipeline(model) {
45342
45395
  return p;
45343
45396
  } catch (err) {
45344
45397
  const msg = err.message;
45345
- if (/unsupported|architecture|not supported|onnx|cannot resolve/i.test(msg)) {
45398
+ loadAttempts += 1;
45399
+ if (isMissingOptionalModuleError(err) || /unsupported|architecture|not supported|onnx/i.test(msg) || loadAttempts >= OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS) {
45346
45400
  loadFailedPermanently = true;
45347
45401
  }
45348
- console.warn(
45349
- `[modelstat] transformers.js embedder unavailable (segments will be re-embedded server-side): ${msg}`
45350
- );
45402
+ if (!warnedUnavailable) {
45403
+ warnedUnavailable = true;
45404
+ console.warn(
45405
+ `[modelstat] transformers.js embedder unavailable (segments will be re-embedded server-side; further attempts ${loadFailedPermanently ? "disabled" : `limited to ${OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS}`}, warning once): ${msg}`
45406
+ );
45407
+ }
45351
45408
  loadPromise2 = null;
45352
45409
  return null;
45353
45410
  }
@@ -45364,21 +45421,32 @@ function createTransformersJsEmbedder(model = DEFAULT_MODEL) {
45364
45421
  const out = await pipe(text, { pooling: "mean", normalize: true });
45365
45422
  return Array.from(out.data);
45366
45423
  } catch (err) {
45367
- console.warn(
45368
- `[modelstat] embed error (returning empty vector, server will re-embed): ${err.message}`
45369
- );
45424
+ if (!warnedInferenceError) {
45425
+ warnedInferenceError = true;
45426
+ console.warn(
45427
+ `[modelstat] embed error (returning empty vectors, server will re-embed; warning once): ${err.message}`
45428
+ );
45429
+ }
45370
45430
  return [];
45371
45431
  }
45372
45432
  };
45373
45433
  }
45374
- var cached, loadPromise2, loadFailedPermanently, DEFAULT_MODEL;
45434
+ var cached, loadPromise2, loadFailedPermanently, loadAttempts, warnedUnavailable, warnedInferenceError, DEFAULT_MODEL, importModule;
45375
45435
  var init_transformersjs_embed = __esm({
45376
45436
  "../../packages/companion-core/src/node/transformersjs-embed.ts"() {
45377
45437
  "use strict";
45438
+ init_optional_module();
45378
45439
  cached = null;
45379
45440
  loadPromise2 = null;
45380
45441
  loadFailedPermanently = false;
45442
+ loadAttempts = 0;
45443
+ warnedUnavailable = false;
45444
+ warnedInferenceError = false;
45381
45445
  DEFAULT_MODEL = "Xenova/bge-small-en-v1.5";
45446
+ importModule = (id) => import(
45447
+ /* @vite-ignore */
45448
+ id
45449
+ );
45382
45450
  }
45383
45451
  });
45384
45452
 
@@ -45417,18 +45485,23 @@ async function createPrivacyFilterRedactor(opts = {}) {
45417
45485
  const device = opts.device ?? (isBrowser ? "webgpu" : "cpu");
45418
45486
  const dtype = opts.dtype ?? "q4";
45419
45487
  const modelId = opts.model ?? "openai/privacy-filter";
45488
+ const importModule2 = opts.importModule ?? ((id) => import(
45489
+ /* @vite-ignore */
45490
+ id
45491
+ ));
45420
45492
  let cached2 = null;
45421
45493
  let loadPromise3 = null;
45494
+ let loadFailedPermanently2 = false;
45495
+ let loadAttempts2 = 0;
45496
+ let warnedUnavailable2 = false;
45497
+ let warnedInferenceError2 = false;
45422
45498
  async function loadPipeline2() {
45423
45499
  if (cached2) return cached2;
45500
+ if (loadFailedPermanently2) return null;
45424
45501
  if (!loadPromise3) {
45425
45502
  loadPromise3 = (async () => {
45426
45503
  try {
45427
- const moduleId = "@huggingface/transformers";
45428
- const tjs = await import(
45429
- /* @vite-ignore */
45430
- moduleId
45431
- );
45504
+ const tjs = await importModule2("@huggingface/transformers");
45432
45505
  const p = await tjs.pipeline("token-classification", modelId, {
45433
45506
  device,
45434
45507
  dtype,
@@ -45438,10 +45511,17 @@ async function createPrivacyFilterRedactor(opts = {}) {
45438
45511
  return p;
45439
45512
  } catch (err) {
45440
45513
  loadPromise3 = null;
45441
- console.warn(
45442
- "[privacy-filter] adapter unavailable \u2014 install @huggingface/transformers in the consuming package to enable model-based redaction. Falling back to pass-through.",
45443
- err.message
45444
- );
45514
+ loadAttempts2 += 1;
45515
+ if (isMissingOptionalModuleError(err) || loadAttempts2 >= OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS) {
45516
+ loadFailedPermanently2 = true;
45517
+ }
45518
+ if (!warnedUnavailable2) {
45519
+ warnedUnavailable2 = true;
45520
+ console.warn(
45521
+ "[privacy-filter] adapter unavailable \u2014 install @huggingface/transformers in the consuming package to enable model-based redaction. Falling back to pass-through (warning once).",
45522
+ err.message
45523
+ );
45524
+ }
45445
45525
  return null;
45446
45526
  }
45447
45527
  })();
@@ -45464,10 +45544,13 @@ async function createPrivacyFilterRedactor(opts = {}) {
45464
45544
  try {
45465
45545
  tokens = await classify(text);
45466
45546
  } catch (err) {
45467
- console.warn(
45468
- "[privacy-filter] inference failed, returning input unchanged:",
45469
- err.message
45470
- );
45547
+ if (!warnedInferenceError2) {
45548
+ warnedInferenceError2 = true;
45549
+ console.warn(
45550
+ "[privacy-filter] inference failed, returning input unchanged (warning once):",
45551
+ err.message
45552
+ );
45553
+ }
45471
45554
  return empty;
45472
45555
  }
45473
45556
  const spans = [];
@@ -45504,6 +45587,7 @@ async function createPrivacyFilterRedactor(opts = {}) {
45504
45587
  var init_privacy_filter = __esm({
45505
45588
  "../../packages/companion-core/src/redact/privacy-filter.ts"() {
45506
45589
  "use strict";
45590
+ init_optional_module();
45507
45591
  }
45508
45592
  });
45509
45593
 
@@ -45613,9 +45697,8 @@ async function scanAll(cb = {}) {
45613
45697
  const full = join5(dir, f);
45614
45698
  jobs.push({
45615
45699
  path: full,
45616
- parse: async () => {
45617
- const r = await parseClaudeCodeJsonl({ deviceId, sourceFile: full });
45618
- return { events: r.events, path: full };
45700
+ parse: async (sink2) => {
45701
+ await parseClaudeCodeJsonl({ deviceId, sourceFile: full, onEvents: sink2 });
45619
45702
  }
45620
45703
  });
45621
45704
  }
@@ -45637,9 +45720,8 @@ async function scanAll(cb = {}) {
45637
45720
  const full = join5(base, y, m, d, f);
45638
45721
  jobs.push({
45639
45722
  path: full,
45640
- parse: async () => {
45641
- const r = await parseCodexRollout({ deviceId, sourceFile: full });
45642
- return { events: r.events, path: full };
45723
+ parse: async (sink2) => {
45724
+ await parseCodexRollout({ deviceId, sourceFile: full, onEvents: sink2 });
45643
45725
  }
45644
45726
  });
45645
45727
  }
@@ -45694,6 +45776,17 @@ async function scanAll(cb = {}) {
45694
45776
  buffer = [];
45695
45777
  cb.onUploaded?.({ events: res.accepted, segments: segments.length });
45696
45778
  }
45779
+ const sink = async (events) => {
45780
+ for (const e of events) {
45781
+ buffer.push(e);
45782
+ if (buffer.length >= BATCH_MAX_EVENTS) await flushBatch();
45783
+ }
45784
+ if (buffer.length > BATCH_BUFFER_HARD_CAP) {
45785
+ throw new Error(
45786
+ `scan event buffer exceeded ${BATCH_BUFFER_HARD_CAP} events \u2014 incremental batch flushing has regressed (see BATCH_BUFFER_HARD_CAP)`
45787
+ );
45788
+ }
45789
+ };
45697
45790
  for (let i = 0; i < jobs.length; i++) {
45698
45791
  const job = jobs[i];
45699
45792
  cb.onFile?.(job.path, i, jobs.length);
@@ -45705,13 +45798,7 @@ async function scanAll(cb = {}) {
45705
45798
  }
45706
45799
  filesScanned += 1;
45707
45800
  try {
45708
- const r = await job.parse();
45709
- if (r.events.length) {
45710
- for (const e of r.events) {
45711
- buffer.push(e);
45712
- if (buffer.length >= BATCH_MAX_EVENTS) await flushBatch();
45713
- }
45714
- }
45801
+ await job.parse(sink);
45715
45802
  if (cs) pendingCursors.push({ path: job.path, cs });
45716
45803
  } catch (e) {
45717
45804
  console.warn(` ! parse failed for ${job.path}:`, e.message);
@@ -45720,7 +45807,7 @@ async function scanAll(cb = {}) {
45720
45807
  await flushBatch();
45721
45808
  return { filesScanned, filesUnchanged, batchesUploaded, eventsUploaded, segmentsUploaded };
45722
45809
  }
45723
- var AGENT_VERSION, BATCH_MAX_EVENTS, ZERO_TOKENS;
45810
+ var AGENT_VERSION, BATCH_MAX_EVENTS, BATCH_BUFFER_HARD_CAP, ZERO_TOKENS;
45724
45811
  var init_scan = __esm({
45725
45812
  "src/scan.ts"() {
45726
45813
  "use strict";
@@ -45729,8 +45816,9 @@ var init_scan = __esm({
45729
45816
  init_pipeline2();
45730
45817
  init_config2();
45731
45818
  init_api();
45732
- AGENT_VERSION = true ? "agent-0.0.45" : "agent-dev";
45819
+ AGENT_VERSION = true ? "agent-0.0.46" : "agent-dev";
45733
45820
  BATCH_MAX_EVENTS = 2e3;
45821
+ BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
45734
45822
  ZERO_TOKENS = {
45735
45823
  input: 0,
45736
45824
  output: 0,
@@ -45766,9 +45854,9 @@ function isProcessAlive(pid) {
45766
45854
  return false;
45767
45855
  }
45768
45856
  }
45769
- function readLock() {
45857
+ function readDaemonLock(lockFile = LOCK_FILE) {
45770
45858
  try {
45771
- const raw = readFileSync4(LOCK_FILE, "utf8");
45859
+ const raw = readFileSync4(lockFile, "utf8");
45772
45860
  const obj = JSON.parse(raw);
45773
45861
  if (typeof obj.pid !== "number") return null;
45774
45862
  return {
@@ -45793,7 +45881,7 @@ function writeLockAtomic(meta) {
45793
45881
  renameSync2(tmp, LOCK_FILE);
45794
45882
  }
45795
45883
  function removeLockIfOwned(ownerPid) {
45796
- const lock = readLock();
45884
+ const lock = readDaemonLock();
45797
45885
  if (!lock) return;
45798
45886
  if (lock.pid !== ownerPid) return;
45799
45887
  try {
@@ -45802,7 +45890,7 @@ function removeLockIfOwned(ownerPid) {
45802
45890
  }
45803
45891
  }
45804
45892
  function acquireDaemonLock(opts) {
45805
- const existing = readLock();
45893
+ const existing = readDaemonLock();
45806
45894
  if (existing && isProcessAlive(existing.pid)) {
45807
45895
  if (!opts.force) {
45808
45896
  const ageSec = ageInSeconds(existing.startedAt);
@@ -45820,6 +45908,20 @@ function acquireDaemonLock(opts) {
45820
45908
  apiUrl: opts.apiUrl
45821
45909
  };
45822
45910
  writeLockAtomic(meta);
45911
+ const recheck = setTimeout(() => {
45912
+ const current = readDaemonLock();
45913
+ if (checkLockOwnership(process.pid, current) !== "lost") return;
45914
+ const winner = current;
45915
+ if (opts.onLockLost) {
45916
+ opts.onLockLost(winner);
45917
+ return;
45918
+ }
45919
+ console.log(
45920
+ `[modelstat] another daemon (pid ${winner.pid}, started ${winner.startedAt}) won the lock race \u2014 this instance is standing down.`
45921
+ );
45922
+ process.exit(0);
45923
+ }, LOCK_RECHECK_MS);
45924
+ recheck.unref();
45823
45925
  const cleanup = () => removeLockIfOwned(process.pid);
45824
45926
  process.once("beforeExit", cleanup);
45825
45927
  process.once("SIGINT", () => {
@@ -45837,6 +45939,11 @@ function acquireDaemonLock(opts) {
45837
45939
  });
45838
45940
  return { kind: "acquired" };
45839
45941
  }
45942
+ function checkLockOwnership(myPid, current, pidAlive = isProcessAlive) {
45943
+ if (!current || current.pid === myPid) return "owned";
45944
+ if (!pidAlive(current.pid)) return "winner_dead";
45945
+ return "lost";
45946
+ }
45840
45947
  function ageInSeconds(iso) {
45841
45948
  const t = Date.parse(iso);
45842
45949
  if (Number.isNaN(t)) return -1;
@@ -45851,12 +45958,13 @@ function formatAge(seconds) {
45851
45958
  const h = Math.floor(m / 60);
45852
45959
  return `${h}h ${m % 60}m`;
45853
45960
  }
45854
- var LOCK_DIR, LOCK_FILE;
45961
+ var LOCK_DIR, LOCK_FILE, LOCK_RECHECK_MS;
45855
45962
  var init_lock = __esm({
45856
45963
  "src/lock.ts"() {
45857
45964
  "use strict";
45858
45965
  LOCK_DIR = join7(homedir7(), ".modelstat");
45859
45966
  LOCK_FILE = join7(LOCK_DIR, "daemon.lock");
45967
+ LOCK_RECHECK_MS = 5e3;
45860
45968
  }
45861
45969
  });
45862
45970
 
@@ -47730,17 +47838,44 @@ async function sendHeartbeat() {
47730
47838
  }
47731
47839
  writeLocalStatus(body).catch(() => void 0);
47732
47840
  }
47841
+ async function rotateRunawayLogs() {
47842
+ const { homedir: homedir10 } = await import("os");
47843
+ const { join: join12 } = await import("path");
47844
+ const { open: open2, stat: stat6, truncate, writeFile } = await import("fs/promises");
47845
+ const dir = join12(homedir10(), ".modelstat", "logs");
47846
+ for (const name of ["out.log", "err.log"]) {
47847
+ const p = join12(dir, name);
47848
+ try {
47849
+ const st = await stat6(p);
47850
+ if (st.size <= LOG_MAX_BYTES) continue;
47851
+ const keep = Math.min(LOG_TAIL_KEEP_BYTES, st.size);
47852
+ const fh = await open2(p, "r");
47853
+ try {
47854
+ const buf = Buffer.alloc(keep);
47855
+ await fh.read(buf, 0, keep, st.size - keep);
47856
+ await writeFile(p.replace(/\.log$/, ".old.log"), buf);
47857
+ } finally {
47858
+ await fh.close();
47859
+ }
47860
+ await truncate(p, 0);
47861
+ console.log(
47862
+ `[modelstat] rotated ${name}: was ${(st.size / 1024 / 1024).toFixed(0)} MB (> ${LOG_MAX_BYTES / 1024 / 1024} MB cap), tail kept in ${name.replace(/\.log$/, ".old.log")}`
47863
+ );
47864
+ } catch {
47865
+ }
47866
+ }
47867
+ }
47733
47868
  async function writeLocalStatus(snapshot) {
47734
- const { homedir: homedir9 } = await import("os");
47735
- const { join: join11 } = await import("path");
47869
+ const { homedir: homedir10 } = await import("os");
47870
+ const { join: join12 } = await import("path");
47736
47871
  const { writeFile, mkdir: mkdir2, rename } = await import("fs/promises");
47737
47872
  if (!lastStatusPath) {
47738
- const dir = join11(homedir9(), ".modelstat");
47873
+ const dir = join12(homedir10(), ".modelstat");
47739
47874
  try {
47740
47875
  await mkdir2(dir, { recursive: true });
47741
47876
  } catch {
47742
47877
  }
47743
- lastStatusPath = join11(dir, "last-status.json");
47878
+ lastStatusPath = join12(dir, "last-status.json");
47744
47879
  }
47745
47880
  const tmp = `${lastStatusPath}.tmp`;
47746
47881
  try {
@@ -47822,7 +47957,17 @@ async function runDaemon(opts = {}) {
47822
47957
  const lock = acquireDaemonLock({
47823
47958
  agentVersion: AGENT_VERSION2,
47824
47959
  apiUrl: state.apiUrl,
47825
- force: opts.force === true
47960
+ force: opts.force === true,
47961
+ // If a racing daemon out-renamed us for the lock (see lock.ts
47962
+ // recheck), stand down with exit 0: our supervisor's next health
47963
+ // check sees the winner and adopts it instead of respawning us.
47964
+ onLockLost: (winner) => {
47965
+ setPhase("offline", `another daemon (pid ${winner.pid}) owns the lock \u2014 standing down`);
47966
+ console.log(
47967
+ `[modelstat] another daemon (pid ${winner.pid}, started ${winner.startedAt}) won the lock race \u2014 this instance is standing down.`
47968
+ );
47969
+ process.exit(0);
47970
+ }
47826
47971
  });
47827
47972
  if (lock.kind === "already_running") {
47828
47973
  console.log(
@@ -47839,6 +47984,7 @@ async function runDaemon(opts = {}) {
47839
47984
  return;
47840
47985
  }
47841
47986
  setPhase("starting", "Booting");
47987
+ await rotateRunawayLogs();
47842
47988
  setStat("segments_sent", state.segmentsSent);
47843
47989
  const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
47844
47990
  hb.unref();
@@ -47862,19 +48008,19 @@ async function runDaemon(opts = {}) {
47862
48008
  await runDiscovery();
47863
48009
  await requestScan("startup");
47864
48010
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
47865
- const { homedir: homedir9, platform: platform5 } = await import("os");
47866
- const { join: join11 } = await import("path");
47867
- const home2 = homedir9();
48011
+ const { homedir: homedir10, platform: platform5 } = await import("os");
48012
+ const { join: join12 } = await import("path");
48013
+ const home2 = homedir10();
47868
48014
  const dirs = [
47869
- join11(home2, ".claude/projects"),
47870
- join11(home2, ".codex/sessions"),
47871
- join11(home2, ".cursor/ai-tracking"),
47872
- join11(home2, ".gemini"),
48015
+ join12(home2, ".claude/projects"),
48016
+ join12(home2, ".codex/sessions"),
48017
+ join12(home2, ".cursor/ai-tracking"),
48018
+ join12(home2, ".gemini"),
47873
48019
  ...platform5() === "darwin" ? [
47874
- join11(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47875
- join11(home2, "Library/Application Support/Claude")
48020
+ join12(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
48021
+ join12(home2, "Library/Application Support/Claude")
47876
48022
  ] : [
47877
- join11(home2, ".config/Cursor/User/workspaceStorage")
48023
+ join12(home2, ".config/Cursor/User/workspaceStorage")
47878
48024
  ]
47879
48025
  ].filter((p) => existsSync9(p) && statSync2(p).isDirectory());
47880
48026
  setPhase("watching", `Watching ${dirs.length} directories`);
@@ -47917,7 +48063,7 @@ async function runDaemon(opts = {}) {
47917
48063
  await new Promise(() => {
47918
48064
  });
47919
48065
  }
47920
- var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastStatusPath, scanRunner;
48066
+ var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, LOG_MAX_BYTES, LOG_TAIL_KEEP_BYTES, lastStatusPath, scanRunner;
47921
48067
  var init_daemon = __esm({
47922
48068
  "src/daemon.ts"() {
47923
48069
  "use strict";
@@ -47929,7 +48075,7 @@ var init_daemon = __esm({
47929
48075
  init_lock();
47930
48076
  init_scan();
47931
48077
  init_single_flight();
47932
- AGENT_VERSION2 = true ? "agent-0.0.45" : "agent-dev";
48078
+ AGENT_VERSION2 = true ? "agent-0.0.46" : "agent-dev";
47933
48079
  HEARTBEAT_INTERVAL_MS = 1e4;
47934
48080
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
47935
48081
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -47945,6 +48091,8 @@ var init_daemon = __esm({
47945
48091
  LOCAL_FLUSH_THROTTLE_MS = 400;
47946
48092
  localFlushTimer = null;
47947
48093
  localFlushPending = false;
48094
+ LOG_MAX_BYTES = 64 * 1024 * 1024;
48095
+ LOG_TAIL_KEEP_BYTES = 4 * 1024 * 1024;
47948
48096
  lastStatusPath = null;
47949
48097
  scanRunner = createCoalescingRunner(runScanCycle);
47950
48098
  }
@@ -47956,33 +48104,33 @@ __export(watch_exports, {
47956
48104
  watchForever: () => watchForever
47957
48105
  });
47958
48106
  import { existsSync as existsSync10 } from "fs";
47959
- import { homedir as homedir8, platform as platform3 } from "os";
47960
- import { join as join10 } from "path";
48107
+ import { homedir as homedir9, platform as platform3 } from "os";
48108
+ import { join as join11 } from "path";
47961
48109
  function resolveWatchDirs() {
47962
- const home2 = homedir8();
47963
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(home2, ".config");
47964
- const xdgData = process.env.XDG_DATA_HOME ?? join10(home2, ".local/share");
48110
+ const home2 = homedir9();
48111
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join11(home2, ".config");
48112
+ const xdgData = process.env.XDG_DATA_HOME ?? join11(home2, ".local/share");
47965
48113
  const candidates = [
47966
48114
  // universal (default HOME-rooted CLI data dirs)
47967
- join10(home2, ".claude/projects"),
47968
- join10(home2, ".codex/sessions"),
47969
- join10(home2, ".cursor/ai-tracking"),
47970
- join10(home2, ".gemini"),
47971
- join10(home2, ".aider"),
48115
+ join11(home2, ".claude/projects"),
48116
+ join11(home2, ".codex/sessions"),
48117
+ join11(home2, ".cursor/ai-tracking"),
48118
+ join11(home2, ".gemini"),
48119
+ join11(home2, ".aider"),
47972
48120
  // XDG / Linux
47973
- join10(xdgConfig, "claude/projects"),
47974
- join10(xdgConfig, "codex/sessions"),
47975
- join10(xdgConfig, "Cursor/User/workspaceStorage"),
47976
- join10(xdgConfig, "Code/User/workspaceStorage"),
47977
- join10(xdgConfig, "Code - Insiders/User/workspaceStorage"),
47978
- join10(xdgData, "claude/projects"),
48121
+ join11(xdgConfig, "claude/projects"),
48122
+ join11(xdgConfig, "codex/sessions"),
48123
+ join11(xdgConfig, "Cursor/User/workspaceStorage"),
48124
+ join11(xdgConfig, "Code/User/workspaceStorage"),
48125
+ join11(xdgConfig, "Code - Insiders/User/workspaceStorage"),
48126
+ join11(xdgData, "claude/projects"),
47979
48127
  // macOS
47980
48128
  ...platform3() === "darwin" ? [
47981
- join10(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47982
- join10(home2, "Library/Application Support/Claude"),
47983
- join10(home2, "Library/Application Support/Code/User/workspaceStorage"),
47984
- join10(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
47985
- join10(home2, "Library/Application Support/Zed")
48129
+ join11(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
48130
+ join11(home2, "Library/Application Support/Claude"),
48131
+ join11(home2, "Library/Application Support/Code/User/workspaceStorage"),
48132
+ join11(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
48133
+ join11(home2, "Library/Application Support/Zed")
47986
48134
  ] : []
47987
48135
  ];
47988
48136
  return Array.from(new Set(candidates)).filter((p) => existsSync10(p));
@@ -48047,13 +48195,13 @@ var init_watch = __esm({
48047
48195
 
48048
48196
  // src/cli.ts
48049
48197
  init_src2();
48050
- init_identity();
48051
48198
  init_api();
48052
48199
  init_config2();
48200
+ init_identity();
48053
48201
  init_scan();
48054
48202
  import { spawn } from "child_process";
48055
48203
  import { randomBytes } from "crypto";
48056
- import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
48204
+ import { arch as cpuArch, hostname as hostname2, platform as platform4, release } from "os";
48057
48205
  import { createInterface as createInterface3 } from "readline";
48058
48206
 
48059
48207
  // src/service.ts
@@ -48409,6 +48557,59 @@ function trayStatus() {
48409
48557
  return exe ? { installed: true, path: exe.replace(/\/Contents\/MacOS\/modelstat-tray$/, "") } : { installed: false, path: null };
48410
48558
  }
48411
48559
 
48560
+ // src/supervise.ts
48561
+ init_lock();
48562
+ import { readFileSync as readFileSync5 } from "fs";
48563
+ import { homedir as homedir8 } from "os";
48564
+ import { join as join8 } from "path";
48565
+ var STATUS_FRESH_MS = 12e4;
48566
+ var BOOT_GRACE_MS = 9e4;
48567
+ function decideSupervision(input) {
48568
+ const fresh = input.statusFreshMs ?? STATUS_FRESH_MS;
48569
+ const grace = input.bootGraceMs ?? BOOT_GRACE_MS;
48570
+ if (!input.lock || !input.ownerAlive) return "spawn";
48571
+ if (input.myAgentVersion && input.lock.agentVersion !== "unknown" && input.lock.agentVersion !== input.myAgentVersion) {
48572
+ return "replace";
48573
+ }
48574
+ if (input.statusAgeMs !== null && input.statusAgeMs <= fresh) return "adopt";
48575
+ if (input.lockAgeMs !== null && input.lockAgeMs <= grace) return "adopt";
48576
+ return "replace";
48577
+ }
48578
+ function daemonHealth(opts = {}) {
48579
+ const now = opts.now ?? Date.now();
48580
+ const pidAlive = opts.pidAlive ?? isProcessAlive;
48581
+ const statusPath = opts.statusPath ?? join8(homedir8(), ".modelstat", "last-status.json");
48582
+ const lock = opts.lockPath === void 0 ? readDaemonLock() : readDaemonLock(opts.lockPath);
48583
+ const ownerAlive = lock !== null && pidAlive(lock.pid);
48584
+ const lockAgeMs = lock ? ageMs(lock.startedAt, now) : null;
48585
+ let statusAgeMs = null;
48586
+ try {
48587
+ const raw = readFileSync5(statusPath, "utf8");
48588
+ const writtenAt = JSON.parse(raw).written_at;
48589
+ statusAgeMs = writtenAt ? ageMs(writtenAt, now) : null;
48590
+ } catch {
48591
+ statusAgeMs = null;
48592
+ }
48593
+ return {
48594
+ decision: decideSupervision({
48595
+ lock,
48596
+ ownerAlive,
48597
+ lockAgeMs,
48598
+ statusAgeMs,
48599
+ myAgentVersion: opts.myAgentVersion
48600
+ }),
48601
+ lock,
48602
+ ownerAlive,
48603
+ lockAgeMs,
48604
+ statusAgeMs
48605
+ };
48606
+ }
48607
+ function ageMs(iso, now) {
48608
+ const t = Date.parse(iso);
48609
+ if (Number.isNaN(t)) return null;
48610
+ return Math.max(0, now - t);
48611
+ }
48612
+
48412
48613
  // src/cli.ts
48413
48614
  async function confirmPrompt(question, defaultYes) {
48414
48615
  if (process.stdin.isTTY !== true) return defaultYes;
@@ -48439,7 +48640,7 @@ function tryOpenBrowser(url) {
48439
48640
  return false;
48440
48641
  }
48441
48642
  }
48442
- var AGENT_VERSION3 = true ? "agent-0.0.45" : "agent-dev";
48643
+ var AGENT_VERSION3 = true ? "agent-0.0.46" : "agent-dev";
48443
48644
  function osFamily() {
48444
48645
  const p = platform4();
48445
48646
  if (p === "darwin") return "macos";
@@ -48482,10 +48683,8 @@ async function cmdSelfRegister() {
48482
48683
  process.stdout.write(` \x1B[2mgenerated UUIDv7 ${deviceUuid.slice(0, 8)}\u2026\x1B[0m
48483
48684
  `);
48484
48685
  }
48485
- process.stdout.write(
48486
- ` \x1B[2m\u2192 POST ${state.apiUrl}/v1/devices/self-register\x1B[0m
48487
- `
48488
- );
48686
+ process.stdout.write(` \x1B[2m\u2192 POST ${state.apiUrl}/v1/devices/self-register\x1B[0m
48687
+ `);
48489
48688
  const res = await selfRegister({
48490
48689
  device_uuid: deviceUuid,
48491
48690
  fingerprint
@@ -48499,8 +48698,10 @@ async function cmdSelfRegister() {
48499
48698
  });
48500
48699
  process.stdout.write(` \x1B[32m\u2713\x1B[0m registered device_id=${res.device_id}
48501
48700
  `);
48502
- process.stdout.write(` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
48503
- `);
48701
+ process.stdout.write(
48702
+ ` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
48703
+ `
48704
+ );
48504
48705
  process.stdout.write(` \x1B[32m\u2713\x1B[0m claim code ${res.claim_code}
48505
48706
  `);
48506
48707
  }
@@ -48533,10 +48734,8 @@ async function cmdAwaitClaim() {
48533
48734
  }
48534
48735
  function emitEvent(opts, event, fields = {}) {
48535
48736
  if (!opts.json) return;
48536
- process.stdout.write(
48537
- `${JSON.stringify({ v: 1, ts: Date.now(), event, ...fields })}
48538
- `
48539
- );
48737
+ process.stdout.write(`${JSON.stringify({ v: 1, ts: Date.now(), event, ...fields })}
48738
+ `);
48540
48739
  }
48541
48740
  async function cmdConnect(opts) {
48542
48741
  const step = (msg) => {
@@ -48639,9 +48838,7 @@ async function cmdConnect(opts) {
48639
48838
  error: e.message
48640
48839
  });
48641
48840
  warn(`couldn't prepare summariser model: ${e.message}`);
48642
- warn(
48643
- "the background service will retry the download on its first scan"
48644
- );
48841
+ warn("the background service will retry the download on its first scan");
48645
48842
  }
48646
48843
  step("Installing/refreshing background service so the agent survives reboots");
48647
48844
  let serviceOk = false;
@@ -48657,9 +48854,7 @@ async function cmdConnect(opts) {
48657
48854
  } catch (e) {
48658
48855
  emitEvent(opts, "service_install_failed", { error: e.message });
48659
48856
  warn(`couldn't install service: ${e.message}`);
48660
- warn(
48661
- "the agent will not run in the background \u2014 re-run after fixing the issue"
48662
- );
48857
+ warn("the agent will not run in the background \u2014 re-run after fixing the issue");
48663
48858
  }
48664
48859
  step("Detecting installed AI tools and signed-in accounts");
48665
48860
  let discovered = null;
@@ -48677,9 +48872,7 @@ async function cmdConnect(opts) {
48677
48872
  identities: d.identities.length
48678
48873
  };
48679
48874
  emitEvent(opts, "discovered", discovered);
48680
- ok(
48681
- `${discovered.installations} installs \xB7 ${discovered.identities} accounts`
48682
- );
48875
+ ok(`${discovered.installations} installs \xB7 ${discovered.identities} accounts`);
48683
48876
  } catch (e) {
48684
48877
  emitEvent(opts, "discovery_failed", { error: e.message });
48685
48878
  warn(`couldn't detect accounts: ${e.message}`);
@@ -48775,9 +48968,7 @@ async function cmdRescan() {
48775
48968
  console.log(
48776
48969
  `[modelstat] cursors wiped \u2014 next \`modelstat scan\` (or daemon scan cycle) will re-read every JSONL from the start and re-summarise every session at processing version v${PROCESSING_VERSION2}.`
48777
48970
  );
48778
- console.log(
48779
- " If the daemon is running, kick it with: modelstat stop && modelstat start"
48780
- );
48971
+ console.log(" If the daemon is running, kick it with: modelstat stop && modelstat start");
48781
48972
  }
48782
48973
  async function cmdWatch() {
48783
48974
  const { watchForever: watchForever2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
@@ -48833,10 +49024,10 @@ function fmtTokens(v) {
48833
49024
  }
48834
49025
  async function readLocalStatus() {
48835
49026
  try {
48836
- const { homedir: homedir9 } = await import("os");
48837
- const { join: join11 } = await import("path");
49027
+ const { homedir: homedir10 } = await import("os");
49028
+ const { join: join12 } = await import("path");
48838
49029
  const { readFile } = await import("fs/promises");
48839
- const p = join11(homedir9(), ".modelstat", "last-status.json");
49030
+ const p = join12(homedir10(), ".modelstat", "last-status.json");
48840
49031
  const txt = await readFile(p, "utf8");
48841
49032
  return JSON.parse(txt);
48842
49033
  } catch {
@@ -48879,7 +49070,9 @@ async function cmdStats(args) {
48879
49070
  console.log("device is claimed \u2014 live stats available at:");
48880
49071
  console.log(` ${dashboard}`);
48881
49072
  if (local) {
48882
- console.log(`local agent: ${local.status ?? "?"}${local.message ? ` \xB7 ${local.message}` : ""}`);
49073
+ console.log(
49074
+ `local agent: ${local.status ?? "?"}${local.message ? ` \xB7 ${local.message}` : ""}`
49075
+ );
48883
49076
  const stats = local.stats ?? {};
48884
49077
  for (const [k, v] of Object.entries(stats)) console.log(` ${k}: ${v}`);
48885
49078
  }
@@ -48897,7 +49090,9 @@ async function cmdStats(args) {
48897
49090
  console.log(
48898
49091
  `status: ${view.device.agent_status ?? "(unknown)"}${view.device.last_seen_at ? ` \xB7 last seen ${view.device.last_seen_at}` : ""}`
48899
49092
  );
48900
- console.log(`claim: ${view.status}${view.status === "unclaimed" ? ` (at ${view.claim_url})` : ""}`);
49093
+ console.log(
49094
+ `claim: ${view.status}${view.status === "unclaimed" ? ` (at ${view.claim_url})` : ""}`
49095
+ );
48901
49096
  console.log(`sessions: ${fmtInt(view.analyzed.count)}`);
48902
49097
  console.log(`tokens: ${fmtTokens(view.analyzed.totalTokens)}`);
48903
49098
  console.log(`cost: ${fmtCost(view.analyzed.totalCostUsd)}`);
@@ -48922,10 +49117,8 @@ async function cmdJobs(args) {
48922
49117
  if (!jobs && !ledger) {
48923
49118
  const dashboard = `${state.apiUrl.replace(/\/$/, "")}/dashboard/jobs`;
48924
49119
  if (asJson) {
48925
- process.stdout.write(
48926
- `${JSON.stringify({ paired: true, claimed: true, dashboard })}
48927
- `
48928
- );
49120
+ process.stdout.write(`${JSON.stringify({ paired: true, claimed: true, dashboard })}
49121
+ `);
48929
49122
  } else {
48930
49123
  console.log("device is claimed \u2014 job queue at:");
48931
49124
  console.log(` ${dashboard}`);
@@ -49015,6 +49208,14 @@ async function main() {
49015
49208
  console.log(`\u2713 runtime staged at ${dest}`);
49016
49209
  return;
49017
49210
  }
49211
+ case "_daemon-health": {
49212
+ try {
49213
+ console.log(JSON.stringify(daemonHealth({ myAgentVersion: AGENT_VERSION3 })));
49214
+ } catch (e) {
49215
+ console.log(JSON.stringify({ decision: "spawn", error: e.message }));
49216
+ }
49217
+ return;
49218
+ }
49018
49219
  // ── Diagnostics / dev one-shots ────────────────────────────
49019
49220
  case "status":
49020
49221
  return cmdStatus();
@@ -49042,10 +49243,18 @@ async function main() {
49042
49243
  return cmdAwaitClaim();
49043
49244
  default:
49044
49245
  console.log("usage:");
49045
- console.log(" npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits.");
49046
- console.log(" flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y");
49047
- console.log(" npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity.");
49048
- console.log(" npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit.");
49246
+ console.log(
49247
+ " npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits."
49248
+ );
49249
+ console.log(
49250
+ " flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y"
49251
+ );
49252
+ console.log(
49253
+ " npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity."
49254
+ );
49255
+ console.log(
49256
+ " npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit."
49257
+ );
49049
49258
  console.log();
49050
49259
  console.log("Diagnostics:");
49051
49260
  console.log(" npx modelstat@latest status \u2014 show pairing + service state");
@@ -49056,11 +49265,17 @@ async function main() {
49056
49265
  console.log();
49057
49266
  console.log("Dev / one-shots:");
49058
49267
  console.log(" npx modelstat@latest scan \u2014 one-shot parse + upload of local JSONL");
49059
- console.log(" npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything");
49060
- console.log(" npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop");
49268
+ console.log(
49269
+ " npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything"
49270
+ );
49271
+ console.log(
49272
+ " npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop"
49273
+ );
49061
49274
  console.log(" npx modelstat@latest discover \u2014 one-shot report of installs/identities");
49062
49275
  console.log();
49063
- console.log("Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim");
49276
+ console.log(
49277
+ "Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim"
49278
+ );
49064
49279
  process.exit(1);
49065
49280
  }
49066
49281
  }