modelstat 0.0.35 → 0.0.37

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
@@ -4500,6 +4500,11 @@ var init_schemas = __esm({
4500
4500
  provider: external_exports.enum(PROVIDERS),
4501
4501
  provider_account_id: external_exports.string().max(200),
4502
4502
  provider_account_label: external_exports.string().max(200).nullable(),
4503
+ /** Human-facing labels — what the user recognises the account by.
4504
+ * Populated from the keychain blob / OAuth JWT where available. */
4505
+ account_email: external_exports.string().max(200).nullable().optional(),
4506
+ account_org: external_exports.string().max(200).nullable().optional(),
4507
+ display_name: external_exports.string().max(200).nullable().optional(),
4503
4508
  owner_scope: external_exports.enum(IDENTITY_OWNER_SCOPES).default("unassigned"),
4504
4509
  detection_source: external_exports.string().max(80)
4505
4510
  });
@@ -7302,14 +7307,19 @@ async function probeIdentities(os2) {
7302
7307
  { timeout: 3e3 }
7303
7308
  ).toString();
7304
7309
  const body = JSON.parse(out);
7305
- const tok = body.claudeAiOauth?.accessToken;
7310
+ const oauth = body.claudeAiOauth;
7311
+ const tok = oauth?.accessToken;
7306
7312
  if (tok) {
7307
- const refresh = body.claudeAiOauth?.refreshToken;
7308
- const seed = (refresh ?? tok).slice(0, 48);
7313
+ const email = oauth?.account?.email_address ?? oauth?.account?.email ?? null;
7314
+ const orgName = oauth?.organization?.name ?? null;
7315
+ const stableId = oauth?.account?.uuid ?? oauth?.organization?.uuid ?? (oauth?.refreshToken ?? tok).slice(0, 48);
7309
7316
  ids.push({
7310
7317
  provider: "anthropic",
7311
- provider_account_id: seed,
7312
- provider_account_label: body.claudeAiOauth?.subscriptionType ?? "Claude Code account",
7318
+ provider_account_id: stableId,
7319
+ provider_account_label: email ?? orgName ?? oauth?.subscriptionType ?? "Claude account",
7320
+ account_email: email,
7321
+ account_org: orgName ?? oauth?.subscriptionType ?? null,
7322
+ display_name: null,
7313
7323
  owner_scope: "unassigned",
7314
7324
  detection_source: "claude_keychain"
7315
7325
  });
@@ -7328,6 +7338,8 @@ async function probeIdentities(os2) {
7328
7338
  const jwt = obj.tokens?.id_token;
7329
7339
  let email = null;
7330
7340
  let sub = null;
7341
+ let name = null;
7342
+ let org = null;
7331
7343
  let provider = "openai";
7332
7344
  if (jwt) {
7333
7345
  const parts = jwt.split(".");
@@ -7339,17 +7351,23 @@ async function probeIdentities(os2) {
7339
7351
  );
7340
7352
  email = body.email ?? null;
7341
7353
  sub = body.sub ?? null;
7354
+ name = body.name ?? null;
7355
+ const oai = body["https://api.openai.com/auth"];
7356
+ org = oai?.organization_id ?? oai?.chatgpt_plan_type ?? null;
7342
7357
  if (body.auth_provider === "google") provider = "openai";
7343
7358
  } catch {
7344
7359
  }
7345
7360
  }
7346
7361
  }
7347
- const pid = sub ?? obj.tokens?.account_id ?? email;
7362
+ const pid = obj.tokens?.account_id ?? sub ?? email;
7348
7363
  if (pid) {
7349
7364
  ids.push({
7350
7365
  provider,
7351
7366
  provider_account_id: pid,
7352
7367
  provider_account_label: email,
7368
+ account_email: email,
7369
+ account_org: org,
7370
+ display_name: name,
7353
7371
  owner_scope: "unassigned",
7354
7372
  detection_source: "codex_auth_json"
7355
7373
  });
@@ -7371,6 +7389,7 @@ async function probeIdentities(os2) {
7371
7389
  provider: "google",
7372
7390
  provider_account_id: email,
7373
7391
  provider_account_label: email,
7392
+ account_email: email,
7374
7393
  owner_scope: "unassigned",
7375
7394
  detection_source: "gemini_oauth_creds"
7376
7395
  });
@@ -7395,6 +7414,7 @@ async function probeIdentities(os2) {
7395
7414
  provider: "cursor",
7396
7415
  provider_account_id: auth.sub ?? auth.email,
7397
7416
  provider_account_label: auth.email ?? null,
7417
+ account_email: auth.email ?? null,
7398
7418
  owner_scope: "unassigned",
7399
7419
  detection_source: "cursor_global_storage"
7400
7420
  });
@@ -42435,9 +42455,9 @@ var require_range = __commonJS({
42435
42455
  parseRange(range) {
42436
42456
  const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
42437
42457
  const memoKey = memoOpts + ":" + range;
42438
- const cached = cache.get(memoKey);
42439
- if (cached) {
42440
- return cached;
42458
+ const cached2 = cache.get(memoKey);
42459
+ if (cached2) {
42460
+ return cached2;
42441
42461
  }
42442
42462
  const loose = this.options.loose;
42443
42463
  const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE];
@@ -44254,17 +44274,17 @@ var init_queue = __esm({
44254
44274
  });
44255
44275
 
44256
44276
  // ../../packages/companion-core/src/pipeline/prompts.ts
44257
- var OLLAMA_CHAT_MODEL, OLLAMA_EMBED_MODEL, SUMMARISER_SYSTEM_PROMPT, SUMMARISER_MAX_TOKENS, ABSTRACT_OUTPUT_MAX_CHARS, SUMMARISER_TEMPERATURE, QWEN_CHARS_PER_TOKEN;
44277
+ var OLLAMA_CHAT_MODEL, OLLAMA_EMBED_MODEL, SUMMARISER_SYSTEM_PROMPT, SUMMARISER_MAX_TOKENS, SUMMARISER_TEMPERATURE, QWEN_CHARS_PER_TOKEN, ABSTRACT_OUTPUT_MAX_CHARS;
44258
44278
  var init_prompts = __esm({
44259
44279
  "../../packages/companion-core/src/pipeline/prompts.ts"() {
44260
44280
  "use strict";
44261
- OLLAMA_CHAT_MODEL = "qwen3.5:0.8b";
44281
+ OLLAMA_CHAT_MODEL = "qwen3:4b";
44262
44282
  OLLAMA_EMBED_MODEL = "bge-small-en-v1.5";
44263
- SUMMARISER_SYSTEM_PROMPT = "Write the SHORTEST paragraph (1-3 sentences) that captures EXACTLY what was ACHIEVED in this coding segment, packed with the concrete domain keywords the developer used. Lead with an outcome verb \u2014 shipped, fixed, migrated, ramped, wired, diagnosed, refactored, designed, deployed, reverted, instrumented. Name the specific things touched: feature names, frameworks, components, bug classes, decisions. Density beats length: a 50-char sentence that names the actual feature beats 200 chars of filler. Skip narration ('the developer'), skip vague verbs ('worked on', 'explored', 'looked into'), skip preamble. If only metadata is given, name the project + tool + visible action concisely. Never quote excerpts verbatim. No PII, no API keys, no file paths, no code literals. Reply with ONLY the paragraph.";
44264
- SUMMARISER_MAX_TOKENS = 160;
44265
- ABSTRACT_OUTPUT_MAX_CHARS = 400;
44283
+ SUMMARISER_SYSTEM_PROMPT = "You summarise an AI coding session in ONE sentence, \u2264 240 characters. If the user message includes sampled conversation excerpts, base your summary on what the developer was actually working on (the substance \u2014 what was being built, debugged, refactored, or designed). If only metadata is given, paraphrase the metadata. Never quote the excerpts verbatim. No PII, no code literals, no file paths, no API keys. Reply with only the sentence.";
44284
+ SUMMARISER_MAX_TOKENS = 120;
44266
44285
  SUMMARISER_TEMPERATURE = 0.2;
44267
44286
  QWEN_CHARS_PER_TOKEN = 3.3;
44287
+ ABSTRACT_OUTPUT_MAX_CHARS = 240;
44268
44288
  }
44269
44289
  });
44270
44290
 
@@ -44819,6 +44839,14 @@ function ollamaSummarize(cfg = defaultOllamaConfig()) {
44819
44839
  body: JSON.stringify({
44820
44840
  model: cfg.chatModel,
44821
44841
  stream: false,
44842
+ // Disable reasoning. qwen3 (the default summariser family) is a
44843
+ // thinking model: with `think` on it spends the entire
44844
+ // `num_predict` budget on a <think> block and returns EMPTY
44845
+ // content, so the summariser saw "" and the whole pipeline
44846
+ // crash-looped at preflight. We only want the final terse
44847
+ // abstract, never the chain-of-thought. Ollama ignores this
44848
+ // field for non-thinking models, so it's safe across families.
44849
+ think: false,
44822
44850
  options: {
44823
44851
  temperature: SUMMARISER_TEMPERATURE,
44824
44852
  num_predict: Math.min(maxTokens, SUMMARISER_MAX_TOKENS)
@@ -44848,6 +44876,10 @@ function ollamaCognize(cfg = defaultOllamaConfig()) {
44848
44876
  model: cfg.chatModel,
44849
44877
  stream: false,
44850
44878
  format: "json",
44879
+ // Same reason as the summariser: no thinking budget, just the
44880
+ // JSON cognition tags. Thinking models otherwise emit a long
44881
+ // <think> block and return empty content.
44882
+ think: false,
44851
44883
  options: {
44852
44884
  temperature: COGNITION_TEMPERATURE,
44853
44885
  num_predict: COGNITION_MAX_TOKENS
@@ -44985,12 +45017,21 @@ async function loadOnce(cfg) {
44985
45017
  const modelPath = await ensureLlamaModel(cfg);
44986
45018
  const llama = await llamaMod.getLlama();
44987
45019
  const model = await llama.loadModel({ modelPath });
44988
- const context = await model.createContext({ contextSize: cfg.contextSize });
44989
- const session = new llamaMod.LlamaChatSession({
44990
- contextSequence: context.getSequence(),
45020
+ const summariserContext = await model.createContext({
45021
+ contextSize: cfg.contextSize
45022
+ });
45023
+ const cognizerContext = await model.createContext({
45024
+ contextSize: Math.min(cfg.contextSize, 2048)
45025
+ });
45026
+ const summarizer = new llamaMod.LlamaChatSession({
45027
+ contextSequence: summariserContext.getSequence(),
44991
45028
  systemPrompt: SUMMARISER_SYSTEM_PROMPT
44992
45029
  });
44993
- loaded = { session };
45030
+ const cognizer = new llamaMod.LlamaChatSession({
45031
+ contextSequence: cognizerContext.getSequence(),
45032
+ systemPrompt: COGNITION_SYSTEM_PROMPT
45033
+ });
45034
+ loaded = { summarizer, cognizer };
44994
45035
  return loaded;
44995
45036
  })();
44996
45037
  try {
@@ -45002,11 +45043,11 @@ async function loadOnce(cfg) {
45002
45043
  }
45003
45044
  function llamaSummarize(cfg = defaultLlamaConfig()) {
45004
45045
  return async ({ prompt, maxTokens }) => {
45005
- const { session } = await loadOnce(cfg);
45046
+ const { summarizer } = await loadOnce(cfg);
45006
45047
  const run = inflight.then(async () => {
45007
- session.resetChatHistory();
45048
+ summarizer.resetChatHistory();
45008
45049
  void maxTokens;
45009
- const raw = await session.prompt(prompt, {
45050
+ const raw = await summarizer.prompt(prompt, {
45010
45051
  temperature: SUMMARISER_TEMPERATURE,
45011
45052
  maxTokens: LLAMA_MAX_TOKENS
45012
45053
  });
@@ -45022,11 +45063,42 @@ function llamaSummarize(cfg = defaultLlamaConfig()) {
45022
45063
  return run;
45023
45064
  };
45024
45065
  }
45066
+ function llamaCognize(cfg = defaultLlamaConfig()) {
45067
+ return async ({ abstract }) => {
45068
+ if (!abstract || abstract.trim().length < 12) return null;
45069
+ let loadedSessions;
45070
+ try {
45071
+ loadedSessions = await loadOnce(cfg);
45072
+ } catch {
45073
+ return null;
45074
+ }
45075
+ const { cognizer } = loadedSessions;
45076
+ const run = inflight.then(async () => {
45077
+ cognizer.resetChatHistory();
45078
+ const raw = await cognizer.prompt(buildCognitionUserPrompt(abstract), {
45079
+ temperature: COGNITION_TEMPERATURE,
45080
+ // Qwen3.5 likes to "think" before answering. Give it a small
45081
+ // budget — the JSON answer is ~30 tokens but the thinking can
45082
+ // run 200-400. The strip below removes the <think> block.
45083
+ maxTokens: COGNITION_MAX_TOKENS + 400
45084
+ });
45085
+ const stripped = stripThinking(raw ?? "");
45086
+ return parseCognitionReply(stripped);
45087
+ });
45088
+ inflight = run.catch(() => void 0);
45089
+ try {
45090
+ return await run;
45091
+ } catch {
45092
+ return null;
45093
+ }
45094
+ };
45095
+ }
45025
45096
  var DEFAULT_LLAMA_MODEL_URL, LLAMA_MAX_TOKENS, loaded, loadPromise, inflight;
45026
45097
  var init_llama = __esm({
45027
45098
  "../../packages/companion-core/src/node/llama.ts"() {
45028
45099
  "use strict";
45029
45100
  init_prompts();
45101
+ init_cognition();
45030
45102
  DEFAULT_LLAMA_MODEL_URL = "https://huggingface.co/lmstudio-community/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf";
45031
45103
  LLAMA_MAX_TOKENS = 1024;
45032
45104
  loaded = null;
@@ -45035,15 +45107,77 @@ var init_llama = __esm({
45035
45107
  }
45036
45108
  });
45037
45109
 
45110
+ // ../../packages/companion-core/src/node/transformersjs-embed.ts
45111
+ async function loadPipeline(model) {
45112
+ if (cached) return cached;
45113
+ if (loadFailedPermanently) return null;
45114
+ if (!loadPromise2) {
45115
+ loadPromise2 = (async () => {
45116
+ try {
45117
+ const moduleId = "@huggingface/transformers";
45118
+ const tjs = await import(
45119
+ /* @vite-ignore */
45120
+ moduleId
45121
+ );
45122
+ const p = await tjs.pipeline("feature-extraction", model, {
45123
+ device: "cpu",
45124
+ dtype: "fp32"
45125
+ });
45126
+ cached = p;
45127
+ return p;
45128
+ } catch (err) {
45129
+ const msg = err.message;
45130
+ if (/unsupported|architecture|not supported|onnx|cannot resolve/i.test(msg)) {
45131
+ loadFailedPermanently = true;
45132
+ }
45133
+ console.warn(
45134
+ `[modelstat] transformers.js embedder unavailable (segments will be re-embedded server-side): ${msg}`
45135
+ );
45136
+ loadPromise2 = null;
45137
+ return null;
45138
+ }
45139
+ })();
45140
+ }
45141
+ return loadPromise2;
45142
+ }
45143
+ function createTransformersJsEmbedder(model = DEFAULT_MODEL) {
45144
+ return async (text) => {
45145
+ if (!text || text.trim().length === 0) return [];
45146
+ const pipe = await loadPipeline(model);
45147
+ if (!pipe) return [];
45148
+ try {
45149
+ const out = await pipe(text, { pooling: "mean", normalize: true });
45150
+ return Array.from(out.data);
45151
+ } catch (err) {
45152
+ console.warn(
45153
+ `[modelstat] embed error (returning empty vector, server will re-embed): ${err.message}`
45154
+ );
45155
+ return [];
45156
+ }
45157
+ };
45158
+ }
45159
+ var cached, loadPromise2, loadFailedPermanently, DEFAULT_MODEL;
45160
+ var init_transformersjs_embed = __esm({
45161
+ "../../packages/companion-core/src/node/transformersjs-embed.ts"() {
45162
+ "use strict";
45163
+ cached = null;
45164
+ loadPromise2 = null;
45165
+ loadFailedPermanently = false;
45166
+ DEFAULT_MODEL = "Xenova/bge-small-en-v1.5";
45167
+ }
45168
+ });
45169
+
45038
45170
  // ../../packages/companion-core/src/node/index.ts
45039
45171
  var node_exports = {};
45040
45172
  __export(node_exports, {
45041
45173
  DEFAULT_LLAMA_MODEL_URL: () => DEFAULT_LLAMA_MODEL_URL,
45042
45174
  FileQueueStore: () => FileQueueStore,
45043
45175
  SqliteQueueStore: () => FileQueueStore,
45176
+ createTransformersJsEmbedder: () => createTransformersJsEmbedder,
45044
45177
  defaultLlamaConfig: () => defaultLlamaConfig,
45045
45178
  defaultOllamaConfig: () => defaultOllamaConfig,
45046
45179
  ensureLlamaModel: () => ensureLlamaModel,
45180
+ llamaCognize: () => llamaCognize,
45047
45181
  llamaSummarize: () => llamaSummarize,
45048
45182
  ollamaCognize: () => ollamaCognize,
45049
45183
  ollamaEmbed: () => ollamaEmbed,
@@ -45057,6 +45191,103 @@ var init_node2 = __esm({
45057
45191
  init_file_queue_store();
45058
45192
  init_ollama();
45059
45193
  init_llama();
45194
+ init_transformersjs_embed();
45195
+ }
45196
+ });
45197
+
45198
+ // ../../packages/companion-core/src/redact/privacy-filter.ts
45199
+ async function createPrivacyFilterRedactor(opts = {}) {
45200
+ const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
45201
+ const device = opts.device ?? (isBrowser ? "webgpu" : "cpu");
45202
+ const dtype = opts.dtype ?? "q4";
45203
+ const modelId = opts.model ?? "openai/privacy-filter";
45204
+ let cached2 = null;
45205
+ let loadPromise3 = null;
45206
+ async function loadPipeline2() {
45207
+ if (cached2) return cached2;
45208
+ if (!loadPromise3) {
45209
+ loadPromise3 = (async () => {
45210
+ try {
45211
+ const moduleId = "@huggingface/transformers";
45212
+ const tjs = await import(
45213
+ /* @vite-ignore */
45214
+ moduleId
45215
+ );
45216
+ const p = await tjs.pipeline("token-classification", modelId, {
45217
+ device,
45218
+ dtype,
45219
+ ...opts.onProgress ? { progress_callback: opts.onProgress } : {}
45220
+ });
45221
+ cached2 = p;
45222
+ return p;
45223
+ } catch (err) {
45224
+ loadPromise3 = null;
45225
+ console.warn(
45226
+ "[privacy-filter] adapter unavailable \u2014 install @huggingface/transformers in the consuming package to enable model-based redaction. Falling back to pass-through.",
45227
+ err.message
45228
+ );
45229
+ return null;
45230
+ }
45231
+ })();
45232
+ }
45233
+ return loadPromise3;
45234
+ }
45235
+ return async function redactWithPrivacyFilter(text) {
45236
+ const empty = {
45237
+ text,
45238
+ counts: {
45239
+ secrets_found: 0,
45240
+ emails_redacted: 0,
45241
+ paths_redacted_absolute: 0
45242
+ }
45243
+ };
45244
+ if (!text) return empty;
45245
+ const classify = await loadPipeline2();
45246
+ if (!classify) return empty;
45247
+ let tokens;
45248
+ try {
45249
+ tokens = await classify(text);
45250
+ } catch (err) {
45251
+ console.warn(
45252
+ "[privacy-filter] inference failed, returning input unchanged:",
45253
+ err.message
45254
+ );
45255
+ return empty;
45256
+ }
45257
+ const spans = [];
45258
+ for (const t of tokens) {
45259
+ const ent = t.entity ?? "";
45260
+ if (!ent || ent === "O" || ent === "0") continue;
45261
+ if (t.start == null || t.end == null || t.end <= t.start) continue;
45262
+ const type = ent.replace(/^[BILUE]-/, "").toUpperCase();
45263
+ const last = spans[spans.length - 1];
45264
+ if (last && last.type === type && t.start - last.end <= 2) {
45265
+ last.end = t.end;
45266
+ } else {
45267
+ spans.push({ type, start: t.start, end: t.end });
45268
+ }
45269
+ }
45270
+ spans.sort((a, b) => b.start - a.start);
45271
+ let out = text;
45272
+ const extra = {};
45273
+ for (const s of spans) {
45274
+ extra[`pf_${s.type.toLowerCase()}`] = (extra[`pf_${s.type.toLowerCase()}`] ?? 0) + 1;
45275
+ out = out.slice(0, s.start) + `[REDACTED:${s.type}]` + out.slice(s.end);
45276
+ }
45277
+ return {
45278
+ text: out,
45279
+ counts: {
45280
+ secrets_found: 0,
45281
+ emails_redacted: 0,
45282
+ paths_redacted_absolute: 0,
45283
+ ...extra
45284
+ }
45285
+ };
45286
+ };
45287
+ }
45288
+ var init_privacy_filter = __esm({
45289
+ "../../packages/companion-core/src/redact/privacy-filter.ts"() {
45290
+ "use strict";
45060
45291
  }
45061
45292
  });
45062
45293
 
@@ -45080,11 +45311,26 @@ async function probeOllama(baseUrl) {
45080
45311
  return false;
45081
45312
  }
45082
45313
  }
45083
- function bundledAdapters() {
45314
+ async function bundledAdapters() {
45315
+ const llamaCfg = defaultLlamaConfig();
45084
45316
  return {
45085
- embed: async () => [],
45086
- summarize: llamaSummarize(defaultLlamaConfig()),
45087
- tokenize: (text) => Math.max(1, Math.ceil(text.length / 4))
45317
+ // Same transformers.js BGE-small embedder as the Ollama path. The
45318
+ // bundled-llama path used to ship vector-less (empty arrays);
45319
+ // hooking embeddings here means even no-Ollama installs get
45320
+ // proper segment-vs-leaf cosine matching at classify time.
45321
+ embed: createTransformersJsEmbedder(),
45322
+ summarize: llamaSummarize(llamaCfg),
45323
+ tokenize: (text) => Math.max(1, Math.ceil(text.length / 4)),
45324
+ cognize: llamaCognize(llamaCfg),
45325
+ // Model-based PII redactor (OpenAI Privacy Filter via
45326
+ // transformers.js / ONNX). Runs locally on CPU after the regex
45327
+ // pass in packages/core/redact.ts. ~1 GB model downloaded on
45328
+ // first run; subsequent runs reuse the cached weights. The
45329
+ // factory is async because it dynamic-imports
45330
+ // @huggingface/transformers — if the optional peer dep isn't
45331
+ // installed it returns a pass-through redactor (regex pass is
45332
+ // still the last line of defence).
45333
+ redact: await createPrivacyFilterRedactor()
45088
45334
  };
45089
45335
  }
45090
45336
  async function getAdapters() {
@@ -45097,18 +45343,21 @@ async function getAdapters() {
45097
45343
  `[modelstat] ollama up at ${ollamaCfg.baseUrl} \u2014 using ${ollamaCfg.chatModel} for summarisation`
45098
45344
  );
45099
45345
  adapters = {
45100
- embed: ollamaEmbed(ollamaCfg),
45346
+ // BGE-small via transformers.js — same model the server uses
45347
+ // via fastembed, so segment vectors land in the same 384-dim
45348
+ // space as leaf-description vectors and cosine similarity is
45349
+ // directly meaningful. We do NOT use ollamaEmbed here because
45350
+ // Ollama's library doesn't host bge-small (404 on pull) and
45351
+ // shipping MiniLM-via-Ollama vs BGE-small-server would break
45352
+ // cross-source similarity.
45353
+ embed: createTransformersJsEmbedder(),
45101
45354
  summarize: ollamaSummarize(ollamaCfg),
45102
45355
  tokenize: ollamaTokenize(),
45103
- // Cognition pass — best-effort. Reads each abstract back out
45104
- // and tags the user's mood + meta-cognitive mode, appending a
45105
- // "[Mood: …] [Mind: …]" suffix the server proposer reads as
45106
- // ordinary abstract text. Free, runs locally on the same Ollama
45107
- // daemon as the summariser. The bundled (node-llama-cpp) path
45108
- // doesn't get cognition — it'd require a second model context
45109
- // and the segment ships fine without the suffix; install Ollama
45110
- // for emotion tagging.
45111
- cognize: ollamaCognize(ollamaCfg)
45356
+ cognize: ollamaCognize(ollamaCfg),
45357
+ // Privacy filter same OpenAI Privacy Filter model regardless
45358
+ // of which summariser/embedder runtime we ended up on. Factory
45359
+ // is async (dynamic-imports @huggingface/transformers).
45360
+ redact: await createPrivacyFilterRedactor()
45112
45361
  };
45113
45362
  return adapters;
45114
45363
  }
@@ -45122,7 +45371,7 @@ async function getAdapters() {
45122
45371
  console.log(
45123
45372
  "[modelstat] using bundled local summariser (Qwen3.5-4B, runs on this machine)"
45124
45373
  );
45125
- adapters = bundledAdapters();
45374
+ adapters = await bundledAdapters();
45126
45375
  return adapters;
45127
45376
  }
45128
45377
  async function buildSegments(events) {
@@ -45147,6 +45396,7 @@ var init_pipeline2 = __esm({
45147
45396
  "use strict";
45148
45397
  init_pipeline();
45149
45398
  init_node2();
45399
+ init_privacy_filter();
45150
45400
  adapters = null;
45151
45401
  probed = false;
45152
45402
  }
@@ -45268,7 +45518,7 @@ var init_scan = __esm({
45268
45518
  init_pipeline2();
45269
45519
  init_config2();
45270
45520
  init_api();
45271
- AGENT_VERSION = "agent-0.0.35";
45521
+ AGENT_VERSION = "agent-0.0.37";
45272
45522
  BATCH_MAX_EVENTS = 2e3;
45273
45523
  }
45274
45524
  });
@@ -47360,7 +47610,7 @@ var init_daemon = __esm({
47360
47610
  init_config2();
47361
47611
  init_lock();
47362
47612
  init_scan();
47363
- AGENT_VERSION2 = "agent-0.0.35";
47613
+ AGENT_VERSION2 = "agent-0.0.37";
47364
47614
  HEARTBEAT_INTERVAL_MS = 1e4;
47365
47615
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
47366
47616
  status = {
@@ -47568,6 +47818,11 @@ ${programArgs}
47568
47818
  <key>EnvironmentVariables</key>
47569
47819
  <dict>
47570
47820
  <key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
47821
+ <!-- Heap headroom for the startup scan of a large transcript backlog.
47822
+ Node's default old-space ceiling (~4 GB) OOM-crashed the daemon on
47823
+ big histories; raise it well below typical RAM. Inherited by the
47824
+ tray-spawned 'modelstat start' child too (NODE_OPTIONS propagates). -->
47825
+ <key>NODE_OPTIONS</key><string>--max-old-space-size=8192</string>
47571
47826
  </dict>
47572
47827
  <key>WorkingDirectory</key><string>${home()}</string>
47573
47828
  </dict>
@@ -47631,6 +47886,10 @@ Wants=network-online.target
47631
47886
 
47632
47887
  [Service]
47633
47888
  Type=simple
47889
+ # Heap headroom for the startup scan of a large transcript backlog \u2014
47890
+ # Node's default ~4 GB old-space ceiling OOM-crashed the daemon on big
47891
+ # histories.
47892
+ Environment=NODE_OPTIONS=--max-old-space-size=8192
47634
47893
  ExecStart=${nodeBinary()} ${cliPath} start
47635
47894
  Restart=always
47636
47895
  RestartSec=10
@@ -47785,7 +48044,7 @@ function tryOpenBrowser(url) {
47785
48044
  return false;
47786
48045
  }
47787
48046
  }
47788
- var AGENT_VERSION3 = "agent-0.0.35";
48047
+ var AGENT_VERSION3 = "agent-0.0.37";
47789
48048
  function osFamily() {
47790
48049
  const p = platform4();
47791
48050
  if (p === "darwin") return "macos";