modelstat 0.0.42 → 0.0.44

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
@@ -4421,7 +4421,15 @@ var init_schemas = __esm({
4421
4421
  installation_id: external_exports.string(),
4422
4422
  identity_id: external_exports.string().nullable()
4423
4423
  })
4424
- ).optional()
4424
+ ).optional(),
4425
+ /** Optional per-session titles — session_id → short redacted title
4426
+ * (≤120 chars) produced by the companion's local titler from the
4427
+ * session's segment abstracts. Server-side this is last-write-wins
4428
+ * per session; companions recompute it from the full session view on
4429
+ * every upload, so the latest batch always carries the freshest
4430
+ * title. Absent for runtimes without a titler (older agents, no-op
4431
+ * browser summariser) — the server keeps whatever it has. */
4432
+ session_titles: external_exports.record(external_exports.string(), external_exports.string().max(120)).optional()
4425
4433
  });
4426
4434
  HeartbeatPayload = external_exports.object({
4427
4435
  device_id: external_exports.string(),
@@ -4542,6 +4550,8 @@ function sourceEventId(deviceId, sourceOrFilePath, byteOffsetMaybe) {
4542
4550
  key = `fs::${sourceOrFilePath}::${byteOffset}`;
4543
4551
  } else if ("file" in sourceOrFilePath) {
4544
4552
  key = `fs::${sourceOrFilePath.file}::${sourceOrFilePath.byteOffset}`;
4553
+ } else if ("lineUuid" in sourceOrFilePath) {
4554
+ key = `uuid::${sourceOrFilePath.lineUuid}`;
4545
4555
  } else {
4546
4556
  key = `web::${sourceOrFilePath.host}::${sourceOrFilePath.conversationId}::${sourceOrFilePath.messageId}`;
4547
4557
  }
@@ -4674,8 +4684,9 @@ var init_src = __esm({
4674
4684
 
4675
4685
  // ../../packages/parsers/src/claude-code/index.ts
4676
4686
  import { createHash } from "crypto";
4677
- import { createReadStream } from "fs";
4687
+ import { createReadStream, existsSync as existsSync2, readdirSync } from "fs";
4678
4688
  import { stat } from "fs/promises";
4689
+ import { dirname as dirname2, join } from "path";
4679
4690
  import { createInterface } from "readline";
4680
4691
  function extractExcerpt(content) {
4681
4692
  if (!content) return void 0;
@@ -4714,6 +4725,37 @@ async function parseClaudeCodeJsonl(ctx) {
4714
4725
  let cwd = null;
4715
4726
  let gitBranch = null;
4716
4727
  let lastModel = null;
4728
+ const filenameSessionId = deriveSessionIdFromFilename(ctx.sourceFile);
4729
+ const ancestorExistsCache = /* @__PURE__ */ new Map();
4730
+ function ancestorFileExists(sid) {
4731
+ const cached2 = ancestorExistsCache.get(sid);
4732
+ if (cached2 !== void 0) return cached2;
4733
+ let found = false;
4734
+ const dir = dirname2(ctx.sourceFile);
4735
+ try {
4736
+ if (existsSync2(join(dir, `${sid}.jsonl`))) {
4737
+ found = true;
4738
+ } else {
4739
+ const root = dirname2(dir);
4740
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
4741
+ if (!entry.isDirectory()) continue;
4742
+ if (existsSync2(join(root, entry.name, `${sid}.jsonl`))) {
4743
+ found = true;
4744
+ break;
4745
+ }
4746
+ }
4747
+ }
4748
+ } catch {
4749
+ }
4750
+ ancestorExistsCache.set(sid, found);
4751
+ return found;
4752
+ }
4753
+ function dedupeIdFor(lineUuid, byteOffset) {
4754
+ const isResumeCopy = filenameSessionId !== null && sessionId !== filenameSessionId;
4755
+ if (!isResumeCopy) return sourceEventId(ctx.deviceId, ctx.sourceFile, byteOffset);
4756
+ if (ancestorFileExists(sessionId)) return null;
4757
+ return sourceEventId(ctx.deviceId, { lineUuid });
4758
+ }
4717
4759
  for await (const line of rl) {
4718
4760
  const byteLen = Buffer.byteLength(line, "utf8") + 1;
4719
4761
  const offsetAtLineStart = startOffset + bytePos;
@@ -4743,15 +4785,22 @@ async function parseClaudeCodeJsonl(ctx) {
4743
4785
  if (obj.type === "assistant") {
4744
4786
  const a = obj;
4745
4787
  const usage = a.message?.usage ?? {};
4746
- lastModel = a.message?.model ?? lastModel;
4788
+ if (a.message?.model && a.message.model !== "<synthetic>") {
4789
+ lastModel = a.message.model;
4790
+ }
4747
4791
  if (!a.uuid || !sessionId) {
4748
4792
  skipped += 1;
4749
4793
  continue;
4750
4794
  }
4795
+ const eventId = dedupeIdFor(a.uuid, offsetAtLineStart);
4796
+ if (eventId === null) {
4797
+ skipped += 1;
4798
+ continue;
4799
+ }
4751
4800
  const slug = guessRepoSlugFromPath(cwd);
4752
4801
  const excerpt = extractExcerpt(a.message?.content);
4753
4802
  events.push({
4754
- source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
4803
+ source_event_id: eventId,
4755
4804
  ts: a.timestamp,
4756
4805
  kind: "assistant_message",
4757
4806
  tool: "claude_code",
@@ -4793,9 +4842,14 @@ async function parseClaudeCodeJsonl(ctx) {
4793
4842
  skipped += 1;
4794
4843
  continue;
4795
4844
  }
4845
+ const eventId = dedupeIdFor(u.uuid, offsetAtLineStart);
4846
+ if (eventId === null) {
4847
+ skipped += 1;
4848
+ continue;
4849
+ }
4796
4850
  const excerpt = extractExcerpt(u.message?.content);
4797
4851
  events.push({
4798
- source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
4852
+ source_event_id: eventId,
4799
4853
  ts: u.timestamp,
4800
4854
  kind: "user_message",
4801
4855
  tool: "claude_code",
@@ -4825,6 +4879,10 @@ async function parseClaudeCodeJsonl(ctx) {
4825
4879
  sourceFile: ctx.sourceFile
4826
4880
  };
4827
4881
  }
4882
+ function deriveSessionIdFromFilename(path5) {
4883
+ const m = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/.exec(path5);
4884
+ return m ? m[1] ?? null : null;
4885
+ }
4828
4886
  async function quickChecksum(path5) {
4829
4887
  const st = await stat(path5);
4830
4888
  const stream = createReadStream(path5, {
@@ -7145,9 +7203,9 @@ var init_cursor = __esm({
7145
7203
 
7146
7204
  // ../../packages/parsers/src/discovery/index.ts
7147
7205
  import { execFile as execFile2, execSync } from "child_process";
7148
- import { existsSync as existsSync2, statSync } from "fs";
7206
+ import { existsSync as existsSync3, statSync } from "fs";
7149
7207
  import { homedir, platform } from "os";
7150
- import { join, resolve as resolve2 } from "path";
7208
+ import { join as join2, resolve as resolve2 } from "path";
7151
7209
  import { promisify as promisify2 } from "util";
7152
7210
  async function discover(options = {}) {
7153
7211
  const os2 = platform() === "darwin" ? "macos" : "linux";
@@ -7163,7 +7221,7 @@ async function discover(options = {}) {
7163
7221
  }
7164
7222
  for (const extra of options.extraDataDirs?.[spec.tool] ?? []) candidates.add(expandPath(extra));
7165
7223
  for (const p of candidates) {
7166
- if (existsSync2(p) && statSync(p).isDirectory()) {
7224
+ if (existsSync3(p) && statSync(p).isDirectory()) {
7167
7225
  installations.push({
7168
7226
  tool: spec.tool,
7169
7227
  install_method: "manual",
@@ -7180,8 +7238,8 @@ async function discover(options = {}) {
7180
7238
  for (const spec of SOURCES) {
7181
7239
  for (const bin of spec.binaries ?? []) {
7182
7240
  for (const dir of binDirs) {
7183
- const p = join(dir, bin);
7184
- if (existsSync2(p)) {
7241
+ const p = join2(dir, bin);
7242
+ if (existsSync3(p)) {
7185
7243
  const version = await safeVersionProbe(p);
7186
7244
  installations.push({
7187
7245
  tool: spec.tool,
@@ -7331,7 +7389,7 @@ async function probeIdentities(os2) {
7331
7389
  `${homedir()}/.codex/auth.json`,
7332
7390
  `${homedir()}/.config/codex/auth.json`
7333
7391
  ]) {
7334
- if (!existsSync2(candidate)) continue;
7392
+ if (!existsSync3(candidate)) continue;
7335
7393
  try {
7336
7394
  const data = await fs4.promises.readFile(candidate, "utf8");
7337
7395
  const obj = JSON.parse(data);
@@ -7379,7 +7437,7 @@ async function probeIdentities(os2) {
7379
7437
  `${homedir()}/.gemini/oauth_creds.json`,
7380
7438
  `${homedir()}/.config/gemini/oauth_creds.json`
7381
7439
  ]) {
7382
- if (!existsSync2(candidate)) continue;
7440
+ if (!existsSync3(candidate)) continue;
7383
7441
  try {
7384
7442
  const data = await fs4.promises.readFile(candidate, "utf8");
7385
7443
  const obj = JSON.parse(data);
@@ -7401,7 +7459,7 @@ async function probeIdentities(os2) {
7401
7459
  `${homedir()}/Library/Application Support/Cursor/User/globalStorage/storage.json`,
7402
7460
  `${homedir()}/.config/Cursor/User/globalStorage/storage.json`
7403
7461
  ]) {
7404
- if (!existsSync2(candidate)) continue;
7462
+ if (!existsSync3(candidate)) continue;
7405
7463
  try {
7406
7464
  const data = await fs4.promises.readFile(candidate, "utf8");
7407
7465
  const obj = JSON.parse(data);
@@ -7561,10 +7619,10 @@ import {
7561
7619
  readFileSync as readFileSync2,
7562
7620
  renameSync,
7563
7621
  writeFileSync,
7564
- existsSync as existsSync3
7622
+ existsSync as existsSync4
7565
7623
  } from "fs";
7566
7624
  import { homedir as homedir2, hostname as osHostname } from "os";
7567
- import { join as join2 } from "path";
7625
+ import { join as join3 } from "path";
7568
7626
  function ensureRoot() {
7569
7627
  mkdirSync(ROOT, { recursive: true, mode: 448 });
7570
7628
  }
@@ -7582,7 +7640,7 @@ function identityPath() {
7582
7640
  return IDENTITY_FILE;
7583
7641
  }
7584
7642
  function hasIdentityFile() {
7585
- return existsSync3(IDENTITY_FILE);
7643
+ return existsSync4(IDENTITY_FILE);
7586
7644
  }
7587
7645
  function parseFile() {
7588
7646
  try {
@@ -7633,7 +7691,7 @@ function saveIdentity(meta) {
7633
7691
  writeAtomic(meta);
7634
7692
  }
7635
7693
  function backupIdentity() {
7636
- if (!existsSync3(IDENTITY_FILE)) return null;
7694
+ if (!existsSync4(IDENTITY_FILE)) return null;
7637
7695
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7638
7696
  const dest = `${IDENTITY_FILE}.bak-${stamp}`;
7639
7697
  renameSync(IDENTITY_FILE, dest);
@@ -7650,8 +7708,8 @@ var ROOT, IDENTITY_FILE;
7650
7708
  var init_identity = __esm({
7651
7709
  "src/identity.ts"() {
7652
7710
  "use strict";
7653
- ROOT = join2(homedir2(), ".modelstat");
7654
- IDENTITY_FILE = join2(ROOT, "identity.json");
7711
+ ROOT = join3(homedir2(), ".modelstat");
7712
+ IDENTITY_FILE = join3(ROOT, "identity.json");
7655
7713
  }
7656
7714
  });
7657
7715
 
@@ -21152,7 +21210,7 @@ var require_snapshot_recorder = __commonJS({
21152
21210
  "../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
21153
21211
  "use strict";
21154
21212
  var { writeFile, readFile, mkdir: mkdir2 } = __require("fs/promises");
21155
- var { dirname: dirname8, resolve: resolve6 } = __require("path");
21213
+ var { dirname: dirname9, resolve: resolve6 } = __require("path");
21156
21214
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
21157
21215
  var { InvalidArgumentError, UndiciError } = require_errors();
21158
21216
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -21383,7 +21441,7 @@ var require_snapshot_recorder = __commonJS({
21383
21441
  throw new InvalidArgumentError("Snapshot path is required");
21384
21442
  }
21385
21443
  const resolvedPath = resolve6(path5);
21386
- await mkdir2(dirname8(resolvedPath), { recursive: true });
21444
+ await mkdir2(dirname9(resolvedPath), { recursive: true });
21387
21445
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
21388
21446
  hash,
21389
21447
  snapshot
@@ -43937,9 +43995,9 @@ var init_source = __esm({
43937
43995
  });
43938
43996
 
43939
43997
  // src/config.ts
43940
- import { existsSync as existsSync4 } from "fs";
43998
+ import { existsSync as existsSync5 } from "fs";
43941
43999
  import { hostname } from "os";
43942
- import { dirname as dirname2, resolve as resolve3 } from "path";
44000
+ import { dirname as dirname3, resolve as resolve3 } from "path";
43943
44001
  import { fileURLToPath } from "url";
43944
44002
  function migrateFromConf() {
43945
44003
  const bearer = store.get("bearerToken");
@@ -43979,10 +44037,10 @@ var init_config2 = __esm({
43979
44037
  import_dotenv = __toESM(require_main(), 1);
43980
44038
  init_source();
43981
44039
  init_identity();
43982
- here = dirname2(fileURLToPath(import.meta.url));
44040
+ here = dirname3(fileURLToPath(import.meta.url));
43983
44041
  for (let d = here, i = 0; i < 8; i++, d = resolve3(d, "..")) {
43984
44042
  const candidate = resolve3(d, ".env");
43985
- if (existsSync4(candidate)) {
44043
+ if (existsSync5(candidate)) {
43986
44044
  (0, import_dotenv.config)({ path: candidate });
43987
44045
  break;
43988
44046
  }
@@ -44387,6 +44445,89 @@ var init_cognition = __esm({
44387
44445
  }
44388
44446
  });
44389
44447
 
44448
+ // ../../packages/companion-core/src/pipeline/title.ts
44449
+ function buildTitleUserPrompt(input) {
44450
+ const lines = input.abstracts.map(
44451
+ (a, i) => ` [part ${i + 1}] ${a.replace(/\s+/g, " ").trim().slice(0, TITLER_ABSTRACT_SLICE_CHARS)}`
44452
+ ).join("\n");
44453
+ const facts = input.facts?.trim();
44454
+ return `${facts ? `Session context: ${facts}.
44455
+
44456
+ ` : ""}Summaries of the session's parts (chronological):
44457
+ ${lines}
44458
+
44459
+ Write the title.`;
44460
+ }
44461
+ function stripCognitionSuffix(abstract) {
44462
+ return abstract.replace(/\s*\[(?:Mood|Mind):[^\]]*\]/g, "").trim();
44463
+ }
44464
+ function sanitiseTitle(raw) {
44465
+ if (!raw) return "";
44466
+ const firstLine = raw.replace(/```[a-z]*\n?/gi, "").split(/[\n\r]/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
44467
+ let t = firstLine.replace(/\s+/g, " ").replace(/^["'`“”]+/, "").replace(/["'`“”]+$/, "").replace(/[.!]+$/, "").trim();
44468
+ if (!t) return "";
44469
+ t = redact(t).text;
44470
+ return t.slice(0, TITLE_MAX_CHARS).trim();
44471
+ }
44472
+ function fallbackTitle(abstracts) {
44473
+ const first = abstracts.find((a) => a.trim().length > 0);
44474
+ if (!first) return "";
44475
+ const sentence = first.split(/(?<=[.!?])\s/, 1)[0] ?? first;
44476
+ return sanitiseTitle(sentence);
44477
+ }
44478
+ function sampleAbstracts(abstracts, max = TITLER_MAX_ABSTRACTS) {
44479
+ if (abstracts.length <= max) return abstracts;
44480
+ const picks = /* @__PURE__ */ new Set([0, abstracts.length - 1]);
44481
+ for (let i = 1; picks.size < max; i++) {
44482
+ picks.add(Math.round(i * (abstracts.length - 1) / (max - 1)));
44483
+ }
44484
+ return [...picks].sort((a, b) => a - b).map((i) => abstracts[i]);
44485
+ }
44486
+ async function buildSessionTitles(segments, entitle) {
44487
+ const bySession = /* @__PURE__ */ new Map();
44488
+ for (const s of segments) {
44489
+ const arr = bySession.get(s.session_id) ?? [];
44490
+ arr.push(s);
44491
+ bySession.set(s.session_id, arr);
44492
+ }
44493
+ const out = {};
44494
+ for (const [sessionId, segs] of bySession) {
44495
+ const sorted = [...segs].sort((a, b) => a.started_at.localeCompare(b.started_at));
44496
+ const abstracts = sorted.map((s) => stripCognitionSuffix(s.abstract)).filter((a) => a.length > 0);
44497
+ if (abstracts.length === 0) continue;
44498
+ let title = "";
44499
+ if (entitle) {
44500
+ const first = sorted[0];
44501
+ const project = first.tags.find((t) => t.root_key === "projects")?.name;
44502
+ const facts = [
44503
+ project ? `repo ${project}` : null,
44504
+ `${sorted.length} part${sorted.length === 1 ? "" : "s"} on ${first.tool}`
44505
+ ].filter(Boolean).join("; ");
44506
+ try {
44507
+ title = sanitiseTitle(await entitle({ abstracts: sampleAbstracts(abstracts), facts }));
44508
+ } catch {
44509
+ title = "";
44510
+ }
44511
+ }
44512
+ if (!title) title = fallbackTitle(abstracts);
44513
+ if (title) out[sessionId] = title;
44514
+ }
44515
+ return out;
44516
+ }
44517
+ var TITLER_SYSTEM_PROMPT, TITLE_MAX_CHARS, TITLER_MAX_TOKENS, TITLER_TEMPERATURE, TITLER_MAX_ABSTRACTS, TITLER_ABSTRACT_SLICE_CHARS;
44518
+ var init_title = __esm({
44519
+ "../../packages/companion-core/src/pipeline/title.ts"() {
44520
+ "use strict";
44521
+ init_redact();
44522
+ TITLER_SYSTEM_PROMPT = "You write a short TITLE for an AI coding session, given one-sentence summaries of its parts in chronological order. The title names what the session was about at a high level, in 3-8 words. If the session clearly spans more than one theme, name the top themes (at most 3) separated by ' \xB7 '. Use concrete domain keywords (features, components, subsystems). No narration, no filler words like 'session' or 'work on', no quotes, no trailing period, no PII, no code literals, no file paths. Reply with only the title.";
44523
+ TITLE_MAX_CHARS = 80;
44524
+ TITLER_MAX_TOKENS = 60;
44525
+ TITLER_TEMPERATURE = 0.2;
44526
+ TITLER_MAX_ABSTRACTS = 10;
44527
+ TITLER_ABSTRACT_SLICE_CHARS = 240;
44528
+ }
44529
+ });
44530
+
44390
44531
  // ../../packages/companion-core/src/pipeline/index.ts
44391
44532
  async function buildSegmentsForSession(events, adapters2, onProgress) {
44392
44533
  if (events.length === 0) return [];
@@ -44682,6 +44823,7 @@ var init_pipeline = __esm({
44682
44823
  init_redact();
44683
44824
  init_prompts();
44684
44825
  init_cognition();
44826
+ init_title();
44685
44827
  SEGMENT_TIME_GAP_MS = 15 * 6e4;
44686
44828
  SEGMENT_TOPIC_THRESHOLD = 0.35;
44687
44829
  SEGMENT_MAX_TURNS = 100;
@@ -44707,7 +44849,7 @@ var init_src3 = __esm({
44707
44849
 
44708
44850
  // ../../packages/companion-core/src/node/file-queue-store.ts
44709
44851
  import { promises as fs3 } from "fs";
44710
- import { dirname as dirname3 } from "path";
44852
+ import { dirname as dirname4 } from "path";
44711
44853
  var SENT_TTL_MS, FileQueueStore;
44712
44854
  var init_file_queue_store = __esm({
44713
44855
  "../../packages/companion-core/src/node/file-queue-store.ts"() {
@@ -44763,7 +44905,7 @@ var init_file_queue_store = __esm({
44763
44905
  items: Object.fromEntries(this.items.entries())
44764
44906
  };
44765
44907
  const tmp = `${this.filePath}.tmp`;
44766
- await fs3.mkdir(dirname3(this.filePath), { recursive: true });
44908
+ await fs3.mkdir(dirname4(this.filePath), { recursive: true });
44767
44909
  await fs3.writeFile(tmp, JSON.stringify(snap), "utf8");
44768
44910
  try {
44769
44911
  await fs3.rename(this.filePath, `${this.filePath}.bak`);
@@ -44940,14 +45082,14 @@ var init_ollama = __esm({
44940
45082
 
44941
45083
  // ../../packages/companion-core/src/node/llama.ts
44942
45084
  import { mkdir } from "fs/promises";
44943
- import { existsSync as existsSync5 } from "fs";
45085
+ import { existsSync as existsSync6 } from "fs";
44944
45086
  import { homedir as homedir4 } from "os";
44945
- import { dirname as dirname4, join as join3 } from "path";
45087
+ import { dirname as dirname5, join as join4 } from "path";
44946
45088
  function defaultLlamaConfig() {
44947
45089
  const env2 = globalThis.process?.env ?? {};
44948
- const modelsDir = env2.MODELSTAT_MODELS_DIR ?? join3(homedir4(), ".modelstat", "models");
45090
+ const modelsDir = env2.MODELSTAT_MODELS_DIR ?? join4(homedir4(), ".modelstat", "models");
44949
45091
  const modelUrl = env2.MODELSTAT_LLAMA_MODEL_URL ?? DEFAULT_LLAMA_MODEL_URL;
44950
- const modelPath = env2.MODELSTAT_LLAMA_MODEL_PATH ?? join3(modelsDir, basenameFromUrl(modelUrl));
45092
+ const modelPath = env2.MODELSTAT_LLAMA_MODEL_PATH ?? join4(modelsDir, basenameFromUrl(modelUrl));
44951
45093
  return {
44952
45094
  modelPath,
44953
45095
  modelUrl,
@@ -44968,8 +45110,8 @@ function basenameFromUrl(url) {
44968
45110
  return parts[parts.length - 1] || "model.gguf";
44969
45111
  }
44970
45112
  async function ensureLlamaModel(cfg = defaultLlamaConfig()) {
44971
- if (existsSync5(cfg.modelPath)) return cfg.modelPath;
44972
- await mkdir(dirname4(cfg.modelPath), { recursive: true });
45113
+ if (existsSync6(cfg.modelPath)) return cfg.modelPath;
45114
+ await mkdir(dirname5(cfg.modelPath), { recursive: true });
44973
45115
  const res = await fetch(cfg.modelUrl);
44974
45116
  if (!res.ok || !res.body) {
44975
45117
  throw new Error(
@@ -45050,6 +45192,9 @@ async function loadOnce(cfg) {
45050
45192
  const cognizerContext = await model.createContext({
45051
45193
  contextSize: Math.min(cfg.contextSize, 2048)
45052
45194
  });
45195
+ const entitlerContext = await model.createContext({
45196
+ contextSize: cfg.contextSize
45197
+ });
45053
45198
  const summarizer = new llamaMod.LlamaChatSession({
45054
45199
  contextSequence: summariserContext.getSequence(),
45055
45200
  systemPrompt: SUMMARISER_SYSTEM_PROMPT
@@ -45058,7 +45203,11 @@ async function loadOnce(cfg) {
45058
45203
  contextSequence: cognizerContext.getSequence(),
45059
45204
  systemPrompt: COGNITION_SYSTEM_PROMPT
45060
45205
  });
45061
- loaded = { summarizer, cognizer };
45206
+ const entitler = new llamaMod.LlamaChatSession({
45207
+ contextSequence: entitlerContext.getSequence(),
45208
+ systemPrompt: TITLER_SYSTEM_PROMPT
45209
+ });
45210
+ loaded = { summarizer, cognizer, entitler };
45062
45211
  return loaded;
45063
45212
  })();
45064
45213
  try {
@@ -45120,12 +45269,41 @@ function llamaCognize(cfg = defaultLlamaConfig()) {
45120
45269
  }
45121
45270
  };
45122
45271
  }
45272
+ function llamaEntitle(cfg = defaultLlamaConfig()) {
45273
+ return async (input) => {
45274
+ if (input.abstracts.length === 0) return null;
45275
+ let loadedSessions;
45276
+ try {
45277
+ loadedSessions = await loadOnce(cfg);
45278
+ } catch {
45279
+ return null;
45280
+ }
45281
+ const { entitler } = loadedSessions;
45282
+ const run = inflight.then(async () => {
45283
+ entitler.resetChatHistory();
45284
+ const raw = await entitler.prompt(buildTitleUserPrompt(input), {
45285
+ temperature: TITLER_TEMPERATURE,
45286
+ // Same thinking-budget rationale as the cognition pass: the
45287
+ // answer is tiny but Qwen3.5 reasons first.
45288
+ maxTokens: TITLER_MAX_TOKENS + 400
45289
+ });
45290
+ return stripThinking(raw ?? "") || null;
45291
+ });
45292
+ inflight = run.catch(() => void 0);
45293
+ try {
45294
+ return await run;
45295
+ } catch {
45296
+ return null;
45297
+ }
45298
+ };
45299
+ }
45123
45300
  var DEFAULT_LLAMA_MODEL_URL, LLAMA_MAX_TOKENS, loaded, loadPromise, inflight;
45124
45301
  var init_llama = __esm({
45125
45302
  "../../packages/companion-core/src/node/llama.ts"() {
45126
45303
  "use strict";
45127
45304
  init_prompts();
45128
45305
  init_cognition();
45306
+ init_title();
45129
45307
  DEFAULT_LLAMA_MODEL_URL = "https://huggingface.co/lmstudio-community/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf";
45130
45308
  LLAMA_MAX_TOKENS = 1024;
45131
45309
  loaded = null;
@@ -45205,6 +45383,7 @@ __export(node_exports, {
45205
45383
  defaultOllamaConfig: () => defaultOllamaConfig,
45206
45384
  ensureLlamaModel: () => ensureLlamaModel,
45207
45385
  llamaCognize: () => llamaCognize,
45386
+ llamaEntitle: () => llamaEntitle,
45208
45387
  llamaSummarize: () => llamaSummarize,
45209
45388
  ollamaCognize: () => ollamaCognize,
45210
45389
  ollamaEmbed: () => ollamaEmbed,
@@ -45322,6 +45501,7 @@ var init_privacy_filter = __esm({
45322
45501
  var pipeline_exports = {};
45323
45502
  __export(pipeline_exports, {
45324
45503
  buildSegments: () => buildSegments,
45504
+ buildSessionTitles: () => buildSessionTitles2,
45325
45505
  preflightSummariser: () => preflightSummariser
45326
45506
  });
45327
45507
  async function bundledAdapters() {
@@ -45337,6 +45517,11 @@ async function bundledAdapters() {
45337
45517
  summarize: llamaSummarize(llamaCfg),
45338
45518
  tokenize: (text) => Math.max(1, Math.ceil(text.length / 4)),
45339
45519
  cognize: llamaCognize(llamaCfg),
45520
+ // Session-title pass — same bundled model, third chat session with
45521
+ // TITLER_SYSTEM_PROMPT. One short call per session per upload (the
45522
+ // sessions-list title in the dashboard). Best-effort like cognize:
45523
+ // failures fall back to a deterministic title in buildSessionTitles.
45524
+ entitle: llamaEntitle(llamaCfg),
45340
45525
  // Model-based PII redactor (OpenAI Privacy Filter via
45341
45526
  // transformers.js / ONNX). Runs locally on CPU after the regex
45342
45527
  // pass in packages/core/redact.ts. ~1 GB model downloaded on
@@ -45366,6 +45551,10 @@ async function getAdapters() {
45366
45551
  async function buildSegments(events, onProgress) {
45367
45552
  return buildSegmentsForSession(events, await getAdapters(), onProgress);
45368
45553
  }
45554
+ async function buildSessionTitles2(segments) {
45555
+ const a = await getAdapters();
45556
+ return buildSessionTitles(segments, a.entitle);
45557
+ }
45369
45558
  async function preflightSummariser() {
45370
45559
  const a = await getAdapters();
45371
45560
  const out = await a.summarize({
@@ -45393,7 +45582,7 @@ var init_pipeline2 = __esm({
45393
45582
  // src/scan.ts
45394
45583
  import { readdir, stat as stat2 } from "fs/promises";
45395
45584
  import { homedir as homedir5 } from "os";
45396
- import { join as join4 } from "path";
45585
+ import { join as join5 } from "path";
45397
45586
  function withNonNullTokens(e) {
45398
45587
  return e.tokens ? e : { ...e, tokens: { ...ZERO_TOKENS } };
45399
45588
  }
@@ -45402,16 +45591,16 @@ async function scanAll(cb = {}) {
45402
45591
  if (!deviceId) throw new Error("agent not enrolled \u2014 run `register` first");
45403
45592
  const jobs = [];
45404
45593
  try {
45405
- const base = join4(homedir5(), ".claude/projects");
45594
+ const base = join5(homedir5(), ".claude/projects");
45406
45595
  const projects = await readdir(base).catch(() => []);
45407
45596
  for (const p of projects) {
45408
- const dir = join4(base, p);
45597
+ const dir = join5(base, p);
45409
45598
  const ds = await stat2(dir).catch(() => null);
45410
45599
  if (!ds?.isDirectory()) continue;
45411
45600
  const files = await readdir(dir);
45412
45601
  for (const f of files) {
45413
45602
  if (!f.endsWith(".jsonl")) continue;
45414
- const full = join4(dir, f);
45603
+ const full = join5(dir, f);
45415
45604
  jobs.push({
45416
45605
  path: full,
45417
45606
  parse: async () => {
@@ -45425,17 +45614,17 @@ async function scanAll(cb = {}) {
45425
45614
  console.warn("claude scan skipped:", e.message);
45426
45615
  }
45427
45616
  try {
45428
- const base = join4(homedir5(), ".codex/sessions");
45617
+ const base = join5(homedir5(), ".codex/sessions");
45429
45618
  const years = await readdir(base).catch(() => []);
45430
45619
  for (const y of years) {
45431
- const months = await readdir(join4(base, y)).catch(() => []);
45620
+ const months = await readdir(join5(base, y)).catch(() => []);
45432
45621
  for (const m of months) {
45433
- const days = await readdir(join4(base, y, m)).catch(() => []);
45622
+ const days = await readdir(join5(base, y, m)).catch(() => []);
45434
45623
  for (const d of days) {
45435
- const files = await readdir(join4(base, y, m, d)).catch(() => []);
45624
+ const files = await readdir(join5(base, y, m, d)).catch(() => []);
45436
45625
  for (const f of files) {
45437
45626
  if (!f.startsWith("rollout-") || !f.endsWith(".jsonl")) continue;
45438
- const full = join4(base, y, m, d, f);
45627
+ const full = join5(base, y, m, d, f);
45439
45628
  jobs.push({
45440
45629
  path: full,
45441
45630
  parse: async () => {
@@ -45457,16 +45646,33 @@ async function scanAll(cb = {}) {
45457
45646
  let segmentsUploaded = 0;
45458
45647
  let buffer = [];
45459
45648
  let pendingCursors = [];
45649
+ const runSegmentsBySession = /* @__PURE__ */ new Map();
45460
45650
  async function flushBatch() {
45461
45651
  if (!buffer.length) return;
45462
45652
  const events = buffer.map(withNonNullTokens);
45463
45653
  const segments = await buildSegments(events, cb.onProgress);
45654
+ for (const seg of segments) {
45655
+ const arr = runSegmentsBySession.get(seg.session_id) ?? [];
45656
+ arr.push(seg);
45657
+ runSegmentsBySession.set(seg.session_id, arr);
45658
+ }
45659
+ const titleInput = [];
45660
+ for (const sessionId of new Set(segments.map((s) => s.session_id))) {
45661
+ titleInput.push(...runSegmentsBySession.get(sessionId) ?? []);
45662
+ }
45663
+ let sessionTitles = {};
45664
+ try {
45665
+ sessionTitles = await buildSessionTitles2(titleInput);
45666
+ } catch (e) {
45667
+ console.warn("session titling failed \u2014 shipping batch untitled:", e.message);
45668
+ }
45464
45669
  const batch = {
45465
45670
  batch_id: batchId(),
45466
45671
  device_id: deviceId,
45467
45672
  agent_version: AGENT_VERSION,
45468
45673
  events,
45469
- segments
45674
+ segments,
45675
+ ...Object.keys(sessionTitles).length ? { session_titles: sessionTitles } : {}
45470
45676
  };
45471
45677
  cb.onUpload?.({ events: events.length, segments: segments.length });
45472
45678
  const res = await uploadBatch(batch);
@@ -45513,7 +45719,7 @@ var init_scan = __esm({
45513
45719
  init_pipeline2();
45514
45720
  init_config2();
45515
45721
  init_api();
45516
- AGENT_VERSION = true ? "agent-0.0.42" : "agent-dev";
45722
+ AGENT_VERSION = true ? "agent-0.0.44" : "agent-dev";
45517
45723
  BATCH_MAX_EVENTS = 2e3;
45518
45724
  ZERO_TOKENS = {
45519
45725
  input: 0,
@@ -45528,7 +45734,7 @@ var init_scan = __esm({
45528
45734
  // src/lock.ts
45529
45735
  import {
45530
45736
  closeSync,
45531
- existsSync as existsSync7,
45737
+ existsSync as existsSync8,
45532
45738
  mkdirSync as mkdirSync3,
45533
45739
  openSync,
45534
45740
  readFileSync as readFileSync4,
@@ -45538,7 +45744,7 @@ import {
45538
45744
  writeSync
45539
45745
  } from "fs";
45540
45746
  import { homedir as homedir7 } from "os";
45541
- import { join as join6 } from "path";
45747
+ import { join as join7 } from "path";
45542
45748
  function isProcessAlive(pid) {
45543
45749
  if (!pid || pid <= 0) return false;
45544
45750
  try {
@@ -45639,8 +45845,8 @@ var LOCK_DIR, LOCK_FILE;
45639
45845
  var init_lock = __esm({
45640
45846
  "src/lock.ts"() {
45641
45847
  "use strict";
45642
- LOCK_DIR = join6(homedir7(), ".modelstat");
45643
- LOCK_FILE = join6(LOCK_DIR, "daemon.lock");
45848
+ LOCK_DIR = join7(homedir7(), ".modelstat");
45849
+ LOCK_FILE = join7(LOCK_DIR, "daemon.lock");
45644
45850
  }
45645
45851
  });
45646
45852
 
@@ -45704,7 +45910,7 @@ var PROCESSING_VERSION;
45704
45910
  var init_processing_version = __esm({
45705
45911
  "src/processing-version.ts"() {
45706
45912
  "use strict";
45707
- PROCESSING_VERSION = 3;
45913
+ PROCESSING_VERSION = 4;
45708
45914
  }
45709
45915
  });
45710
45916
 
@@ -46441,9 +46647,9 @@ var init_handler = __esm({
46441
46647
  if (this.fsw.closed) {
46442
46648
  return;
46443
46649
  }
46444
- const dirname8 = sysPath.dirname(file);
46650
+ const dirname9 = sysPath.dirname(file);
46445
46651
  const basename4 = sysPath.basename(file);
46446
- const parent = this.fsw._getWatchedDir(dirname8);
46652
+ const parent = this.fsw._getWatchedDir(dirname9);
46447
46653
  let prevStats = stats;
46448
46654
  if (parent.has(basename4))
46449
46655
  return;
@@ -46470,7 +46676,7 @@ var init_handler = __esm({
46470
46676
  prevStats = newStats2;
46471
46677
  }
46472
46678
  } catch (error) {
46473
- this.fsw._remove(dirname8, basename4);
46679
+ this.fsw._remove(dirname9, basename4);
46474
46680
  }
46475
46681
  } else if (parent.has(basename4)) {
46476
46682
  const at = newStats.atimeMs;
@@ -47436,7 +47642,7 @@ __export(daemon_exports, {
47436
47642
  setQueue: () => setQueue,
47437
47643
  setStat: () => setStat
47438
47644
  });
47439
- import { existsSync as existsSync8, statSync as statSync2 } from "fs";
47645
+ import { existsSync as existsSync9, statSync as statSync2 } from "fs";
47440
47646
  function setPhase(phase, message) {
47441
47647
  status.phase = phase;
47442
47648
  status.message = message ?? null;
@@ -47516,15 +47722,15 @@ async function sendHeartbeat() {
47516
47722
  }
47517
47723
  async function writeLocalStatus(snapshot) {
47518
47724
  const { homedir: homedir9 } = await import("os");
47519
- const { join: join10 } = await import("path");
47725
+ const { join: join11 } = await import("path");
47520
47726
  const { writeFile, mkdir: mkdir2, rename } = await import("fs/promises");
47521
47727
  if (!lastStatusPath) {
47522
- const dir = join10(homedir9(), ".modelstat");
47728
+ const dir = join11(homedir9(), ".modelstat");
47523
47729
  try {
47524
47730
  await mkdir2(dir, { recursive: true });
47525
47731
  } catch {
47526
47732
  }
47527
- lastStatusPath = join10(dir, "last-status.json");
47733
+ lastStatusPath = join11(dir, "last-status.json");
47528
47734
  }
47529
47735
  const tmp = `${lastStatusPath}.tmp`;
47530
47736
  try {
@@ -47647,20 +47853,20 @@ async function runDaemon(opts = {}) {
47647
47853
  await requestScan("startup");
47648
47854
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
47649
47855
  const { homedir: homedir9, platform: platform5 } = await import("os");
47650
- const { join: join10 } = await import("path");
47856
+ const { join: join11 } = await import("path");
47651
47857
  const home2 = homedir9();
47652
47858
  const dirs = [
47653
- join10(home2, ".claude/projects"),
47654
- join10(home2, ".codex/sessions"),
47655
- join10(home2, ".cursor/ai-tracking"),
47656
- join10(home2, ".gemini"),
47859
+ join11(home2, ".claude/projects"),
47860
+ join11(home2, ".codex/sessions"),
47861
+ join11(home2, ".cursor/ai-tracking"),
47862
+ join11(home2, ".gemini"),
47657
47863
  ...platform5() === "darwin" ? [
47658
- join10(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47659
- join10(home2, "Library/Application Support/Claude")
47864
+ join11(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47865
+ join11(home2, "Library/Application Support/Claude")
47660
47866
  ] : [
47661
- join10(home2, ".config/Cursor/User/workspaceStorage")
47867
+ join11(home2, ".config/Cursor/User/workspaceStorage")
47662
47868
  ]
47663
- ].filter((p) => existsSync8(p) && statSync2(p).isDirectory());
47869
+ ].filter((p) => existsSync9(p) && statSync2(p).isDirectory());
47664
47870
  setPhase("watching", `Watching ${dirs.length} directories`);
47665
47871
  const watcher = chokidar.watch(dirs, {
47666
47872
  persistent: true,
@@ -47713,7 +47919,7 @@ var init_daemon = __esm({
47713
47919
  init_lock();
47714
47920
  init_scan();
47715
47921
  init_single_flight();
47716
- AGENT_VERSION2 = true ? "agent-0.0.42" : "agent-dev";
47922
+ AGENT_VERSION2 = true ? "agent-0.0.44" : "agent-dev";
47717
47923
  HEARTBEAT_INTERVAL_MS = 1e4;
47718
47924
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
47719
47925
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -47739,37 +47945,37 @@ var watch_exports = {};
47739
47945
  __export(watch_exports, {
47740
47946
  watchForever: () => watchForever
47741
47947
  });
47742
- import { existsSync as existsSync9 } from "fs";
47948
+ import { existsSync as existsSync10 } from "fs";
47743
47949
  import { homedir as homedir8, platform as platform3 } from "os";
47744
- import { join as join9 } from "path";
47950
+ import { join as join10 } from "path";
47745
47951
  function resolveWatchDirs() {
47746
47952
  const home2 = homedir8();
47747
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(home2, ".config");
47748
- const xdgData = process.env.XDG_DATA_HOME ?? join9(home2, ".local/share");
47953
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(home2, ".config");
47954
+ const xdgData = process.env.XDG_DATA_HOME ?? join10(home2, ".local/share");
47749
47955
  const candidates = [
47750
47956
  // universal (default HOME-rooted CLI data dirs)
47751
- join9(home2, ".claude/projects"),
47752
- join9(home2, ".codex/sessions"),
47753
- join9(home2, ".cursor/ai-tracking"),
47754
- join9(home2, ".gemini"),
47755
- join9(home2, ".aider"),
47957
+ join10(home2, ".claude/projects"),
47958
+ join10(home2, ".codex/sessions"),
47959
+ join10(home2, ".cursor/ai-tracking"),
47960
+ join10(home2, ".gemini"),
47961
+ join10(home2, ".aider"),
47756
47962
  // XDG / Linux
47757
- join9(xdgConfig, "claude/projects"),
47758
- join9(xdgConfig, "codex/sessions"),
47759
- join9(xdgConfig, "Cursor/User/workspaceStorage"),
47760
- join9(xdgConfig, "Code/User/workspaceStorage"),
47761
- join9(xdgConfig, "Code - Insiders/User/workspaceStorage"),
47762
- join9(xdgData, "claude/projects"),
47963
+ join10(xdgConfig, "claude/projects"),
47964
+ join10(xdgConfig, "codex/sessions"),
47965
+ join10(xdgConfig, "Cursor/User/workspaceStorage"),
47966
+ join10(xdgConfig, "Code/User/workspaceStorage"),
47967
+ join10(xdgConfig, "Code - Insiders/User/workspaceStorage"),
47968
+ join10(xdgData, "claude/projects"),
47763
47969
  // macOS
47764
47970
  ...platform3() === "darwin" ? [
47765
- join9(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47766
- join9(home2, "Library/Application Support/Claude"),
47767
- join9(home2, "Library/Application Support/Code/User/workspaceStorage"),
47768
- join9(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
47769
- join9(home2, "Library/Application Support/Zed")
47971
+ join10(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47972
+ join10(home2, "Library/Application Support/Claude"),
47973
+ join10(home2, "Library/Application Support/Code/User/workspaceStorage"),
47974
+ join10(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
47975
+ join10(home2, "Library/Application Support/Zed")
47770
47976
  ] : []
47771
47977
  ];
47772
- return Array.from(new Set(candidates)).filter((p) => existsSync9(p));
47978
+ return Array.from(new Set(candidates)).filter((p) => existsSync10(p));
47773
47979
  }
47774
47980
  async function safeScan(reason) {
47775
47981
  if (scanning) {
@@ -47844,7 +48050,7 @@ import { createInterface as createInterface3 } from "readline";
47844
48050
  import { spawnSync } from "child_process";
47845
48051
  import {
47846
48052
  copyFileSync,
47847
- existsSync as existsSync6,
48053
+ existsSync as existsSync7,
47848
48054
  mkdirSync as mkdirSync2,
47849
48055
  readFileSync as readFileSync3,
47850
48056
  realpathSync,
@@ -47853,7 +48059,7 @@ import {
47853
48059
  } from "fs";
47854
48060
  import { createRequire } from "module";
47855
48061
  import { homedir as homedir6, platform as platform2, userInfo } from "os";
47856
- import { dirname as dirname5, join as join5 } from "path";
48062
+ import { dirname as dirname6, join as join6 } from "path";
47857
48063
  import { fileURLToPath as fileURLToPath2 } from "url";
47858
48064
  var SERVICE_LABEL = "ai.modelstat.agent";
47859
48065
  var SYSTEMD_UNIT = "modelstat";
@@ -47861,16 +48067,16 @@ function home() {
47861
48067
  return homedir6();
47862
48068
  }
47863
48069
  function stateDir() {
47864
- return join5(home(), ".modelstat");
48070
+ return join6(home(), ".modelstat");
47865
48071
  }
47866
48072
  function binDir() {
47867
- return join5(stateDir(), "bin");
48073
+ return join6(stateDir(), "bin");
47868
48074
  }
47869
48075
  function logDir() {
47870
- return join5(stateDir(), "logs");
48076
+ return join6(stateDir(), "logs");
47871
48077
  }
47872
48078
  function installedCliPath() {
47873
- return join5(binDir(), "modelstat.mjs");
48079
+ return join6(binDir(), "modelstat.mjs");
47874
48080
  }
47875
48081
  function runningCliPath() {
47876
48082
  return fileURLToPath2(import.meta.url).replace(/service\.(mjs|js|ts)$/, "cli.mjs");
@@ -47880,7 +48086,7 @@ function installBundle() {
47880
48086
  mkdirSync2(logDir(), { recursive: true });
47881
48087
  const src = runningCliPath();
47882
48088
  const dest = installedCliPath();
47883
- if (!existsSync6(src)) {
48089
+ if (!existsSync7(src)) {
47884
48090
  throw new Error(
47885
48091
  `Can't find the CLI bundle to install from (${src}). Are you running a local dev build?`
47886
48092
  );
@@ -47896,14 +48102,14 @@ var NODE_LLAMA_CPP_FALLBACK_VERSION = "3.18.1";
47896
48102
  function sourceLlamaVersion(sourceCli) {
47897
48103
  try {
47898
48104
  const req = createRequire(sourceCli);
47899
- let d = dirname5(realpathSync(req.resolve("node-llama-cpp")));
48105
+ let d = dirname6(realpathSync(req.resolve("node-llama-cpp")));
47900
48106
  for (let i = 0; i < 10; i++) {
47901
- const pj = join5(d, "package.json");
47902
- if (existsSync6(pj)) {
48107
+ const pj = join6(d, "package.json");
48108
+ if (existsSync7(pj)) {
47903
48109
  const p = JSON.parse(readFileSync3(pj, "utf8"));
47904
48110
  if (p.name === "node-llama-cpp" && p.version) return p.version;
47905
48111
  }
47906
- const up = dirname5(d);
48112
+ const up = dirname6(d);
47907
48113
  if (up === d) break;
47908
48114
  d = up;
47909
48115
  }
@@ -47917,7 +48123,7 @@ function installNativeRuntime(sourceCli) {
47917
48123
  try {
47918
48124
  const have = JSON.parse(
47919
48125
  readFileSync3(
47920
- join5(dest, "node_modules", "node-llama-cpp", "package.json"),
48126
+ join6(dest, "node_modules", "node-llama-cpp", "package.json"),
47921
48127
  "utf8"
47922
48128
  )
47923
48129
  );
@@ -47958,21 +48164,21 @@ function nodeBinary() {
47958
48164
  return process.execPath;
47959
48165
  }
47960
48166
  function plistPath() {
47961
- return join5(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
48167
+ return join6(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
47962
48168
  }
47963
48169
  function locateTrayExecutable() {
47964
48170
  const candidates = [
47965
- join5(home(), "Applications", "ModelstatTray.app", "Contents", "MacOS", "modelstat-tray"),
48171
+ join6(home(), "Applications", "ModelstatTray.app", "Contents", "MacOS", "modelstat-tray"),
47966
48172
  "/Applications/ModelstatTray.app/Contents/MacOS/modelstat-tray"
47967
48173
  ];
47968
48174
  for (const p of candidates) {
47969
- if (existsSync6(p)) return p;
48175
+ if (existsSync7(p)) return p;
47970
48176
  }
47971
48177
  return null;
47972
48178
  }
47973
48179
  function writePlist(cliPath) {
47974
48180
  const p = plistPath();
47975
- mkdirSync2(dirname5(p), { recursive: true });
48181
+ mkdirSync2(dirname6(p), { recursive: true });
47976
48182
  const tray = locateTrayExecutable();
47977
48183
  const programArgs = tray ? ` <string>${tray}</string>` : [
47978
48184
  ` <string>${nodeBinary()}</string>`,
@@ -47992,8 +48198,8 @@ ${programArgs}
47992
48198
  <key>KeepAlive</key>
47993
48199
  <dict><key>SuccessfulExit</key><false/></dict>
47994
48200
  <key>ThrottleInterval</key><integer>30</integer>
47995
- <key>StandardOutPath</key><string>${join5(logDir(), "out.log")}</string>
47996
- <key>StandardErrorPath</key><string>${join5(logDir(), "err.log")}</string>
48201
+ <key>StandardOutPath</key><string>${join6(logDir(), "out.log")}</string>
48202
+ <key>StandardErrorPath</key><string>${join6(logDir(), "err.log")}</string>
47997
48203
  <key>EnvironmentVariables</key>
47998
48204
  <dict>
47999
48205
  <key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
@@ -48038,7 +48244,7 @@ function macUninstall() {
48038
48244
  const target = `gui/${uid}/${SERVICE_LABEL}`;
48039
48245
  launchctl(["bootout", target]);
48040
48246
  const plist = plistPath();
48041
- if (existsSync6(plist)) {
48247
+ if (existsSync7(plist)) {
48042
48248
  try {
48043
48249
  unlinkSync(plist);
48044
48250
  } catch {
@@ -48051,12 +48257,12 @@ function macStatus() {
48051
48257
  return { running: r.ok, hint: r.ok ? "launchd managed" : "not installed" };
48052
48258
  }
48053
48259
  function systemdUnitPath() {
48054
- const xdg = process.env.XDG_CONFIG_HOME ?? join5(home(), ".config");
48055
- return join5(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
48260
+ const xdg = process.env.XDG_CONFIG_HOME ?? join6(home(), ".config");
48261
+ return join6(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
48056
48262
  }
48057
48263
  function writeSystemdUnit(cliPath) {
48058
48264
  const unitPath = systemdUnitPath();
48059
- mkdirSync2(dirname5(unitPath), { recursive: true });
48265
+ mkdirSync2(dirname6(unitPath), { recursive: true });
48060
48266
  const unit = `[Unit]
48061
48267
  Description=modelstat agent
48062
48268
  Documentation=https://modelstat.ai
@@ -48075,8 +48281,8 @@ RestartSec=10
48075
48281
  # Don't restart-storm if the service is persistently unreachable.
48076
48282
  StartLimitIntervalSec=300
48077
48283
  StartLimitBurst=10
48078
- StandardOutput=append:${join5(logDir(), "out.log")}
48079
- StandardError=append:${join5(logDir(), "err.log")}
48284
+ StandardOutput=append:${join6(logDir(), "out.log")}
48285
+ StandardError=append:${join6(logDir(), "err.log")}
48080
48286
 
48081
48287
  [Install]
48082
48288
  WantedBy=default.target
@@ -48101,7 +48307,7 @@ function linuxInstall() {
48101
48307
  function linuxUninstall() {
48102
48308
  systemctl(["disable", "--now", `${SYSTEMD_UNIT}.service`]);
48103
48309
  const unit = systemdUnitPath();
48104
- if (existsSync6(unit)) {
48310
+ if (existsSync7(unit)) {
48105
48311
  try {
48106
48312
  unlinkSync(unit);
48107
48313
  } catch {
@@ -48145,9 +48351,9 @@ function logsDir() {
48145
48351
  }
48146
48352
  function installTrayApp(sourceAppPath) {
48147
48353
  if (platform2() !== "darwin") return null;
48148
- if (!existsSync6(sourceAppPath)) return null;
48149
- const dest = join5(home(), "Applications", "ModelstatTray.app");
48150
- mkdirSync2(dirname5(dest), { recursive: true });
48354
+ if (!existsSync7(sourceAppPath)) return null;
48355
+ const dest = join6(home(), "Applications", "ModelstatTray.app");
48356
+ mkdirSync2(dirname6(dest), { recursive: true });
48151
48357
  spawnSync("rm", ["-rf", dest]);
48152
48358
  const r = spawnSync("cp", ["-R", sourceAppPath, dest], { encoding: "utf8" });
48153
48359
  if (r.status !== 0) {
@@ -48157,28 +48363,28 @@ function installTrayApp(sourceAppPath) {
48157
48363
  }
48158
48364
  function bundledTrayAppPath() {
48159
48365
  if (platform2() !== "darwin") return null;
48160
- const here2 = dirname5(fileURLToPath2(import.meta.url));
48366
+ const here2 = dirname6(fileURLToPath2(import.meta.url));
48161
48367
  const candidates = [
48162
48368
  // Pre-built .app — CI with codesigning drops one here.
48163
- join5(here2, "..", "vendor", "ModelstatTray.app"),
48369
+ join6(here2, "..", "vendor", "ModelstatTray.app"),
48164
48370
  // Local dev layout: apps/agent-dev/src/service.ts → ../../tray-mac/build/ModelstatTray.app
48165
- join5(here2, "..", "..", "tray-mac", "build", "ModelstatTray.app")
48371
+ join6(here2, "..", "..", "tray-mac", "build", "ModelstatTray.app")
48166
48372
  ];
48167
48373
  for (const c of candidates) {
48168
- if (existsSync6(c)) return c;
48374
+ if (existsSync7(c)) return c;
48169
48375
  }
48170
48376
  const sourceDirs = [
48171
- join5(here2, "..", "vendor", "tray-mac"),
48172
- join5(here2, "..", "..", "tray-mac")
48377
+ join6(here2, "..", "vendor", "tray-mac"),
48378
+ join6(here2, "..", "..", "tray-mac")
48173
48379
  ];
48174
48380
  for (const src of sourceDirs) {
48175
- const build = join5(src, "build-app.sh");
48176
- if (!existsSync6(build)) continue;
48381
+ const build = join6(src, "build-app.sh");
48382
+ if (!existsSync7(build)) continue;
48177
48383
  if (!hasSwift()) return null;
48178
48384
  const r = spawnSync("bash", [build], { cwd: src, encoding: "utf8" });
48179
48385
  if (r.status === 0) {
48180
- const app = join5(src, "build", "ModelstatTray.app");
48181
- if (existsSync6(app)) return app;
48386
+ const app = join6(src, "build", "ModelstatTray.app");
48387
+ if (existsSync7(app)) return app;
48182
48388
  }
48183
48389
  }
48184
48390
  return null;
@@ -48223,7 +48429,7 @@ function tryOpenBrowser(url) {
48223
48429
  return false;
48224
48430
  }
48225
48431
  }
48226
- var AGENT_VERSION3 = true ? "agent-0.0.42" : "agent-dev";
48432
+ var AGENT_VERSION3 = true ? "agent-0.0.44" : "agent-dev";
48227
48433
  function osFamily() {
48228
48434
  const p = platform4();
48229
48435
  if (p === "darwin") return "macos";
@@ -48618,9 +48824,9 @@ function fmtTokens(v) {
48618
48824
  async function readLocalStatus() {
48619
48825
  try {
48620
48826
  const { homedir: homedir9 } = await import("os");
48621
- const { join: join10 } = await import("path");
48827
+ const { join: join11 } = await import("path");
48622
48828
  const { readFile } = await import("fs/promises");
48623
- const p = join10(homedir9(), ".modelstat", "last-status.json");
48829
+ const p = join11(homedir9(), ".modelstat", "last-status.json");
48624
48830
  const txt = await readFile(p, "utf8");
48625
48831
  return JSON.parse(txt);
48626
48832
  } catch {