modelstat 0.11.1 → 0.13.0

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
@@ -5141,7 +5141,24 @@ var init_schemas = __esm({
5141
5141
  source_event_ids: external_exports.array(external_exports.string()).max(2e3),
5142
5142
  /** Optional embedding of the abstract (BGE-small-en-v1.5, 384 dims).
5143
5143
  * Present when the daemon has an Embedder adapter configured. */
5144
- abstract_embedding: external_exports.array(external_exports.number()).length(384).optional()
5144
+ abstract_embedding: external_exports.array(external_exports.number()).length(384).optional(),
5145
+ /** Privacy-preserving on-device behavioral signal — COUNTS/RATIOS ONLY,
5146
+ * never raw text (mirrors RedactionReport). Powers server-side prompt-
5147
+ * friction detection. Optional so older daemons that omit it still validate. */
5148
+ behavior: external_exports.object({
5149
+ /** Developer messages in this segment. */
5150
+ user_turns: external_exports.number().int().nonnegative().default(0),
5151
+ /** User messages that land right after the assistant — a re-prompt /
5152
+ * correction proxy. */
5153
+ correction_count: external_exports.number().int().nonnegative().default(0),
5154
+ /** 0-1 frustration estimate (re-prompt density + negative mood tags). */
5155
+ frustration: external_exports.number().min(0).max(1).default(0)
5156
+ }).optional(),
5157
+ /** Distilled "what the developer asked for / how they directed the AI" — from
5158
+ * their MESSAGES ONLY (not the assistant's actions), redacted, ≤512. The
5159
+ * source Insights' rule + skill detectors mine, distinct from the outcome
5160
+ * `abstract`. Optional; absent from daemons that predate it. */
5161
+ user_intent: external_exports.string().max(512).optional()
5145
5162
  });
5146
5163
  ToolAction = external_exports.object({
5147
5164
  /** Where it ran: `shell`, `mcp`, `builtin`, `browser`. (tier 0) */
@@ -35265,11 +35282,11 @@ var init_prompts = __esm({
35265
35282
  "use strict";
35266
35283
  OLLAMA_CHAT_MODEL = "qwen3:4b";
35267
35284
  OLLAMA_EMBED_MODEL = "bge-small-en-v1.5";
35268
- 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.";
35269
- SUMMARISER_MAX_TOKENS = 120;
35285
+ SUMMARISER_SYSTEM_PROMPT = 'You summarise an AI coding session in 1-2 sentences, \u2264 400 characters. Name the CONCRETE work: the action taken, WHAT it acted on, and the specific target \u2014 the repository (e.g. erpc/erpc), branch, service, or component \u2014 whenever the session facts or excerpts identify it. Good: "Triggered a GitHub Actions release for erpc/erpc"; "Fixed a null dereference in the auth middleware of api-gateway". Lead with an outcome verb and pack in concrete domain keywords (frameworks, features, decisions). Base the substance on the excerpts when present, else the metadata. Never quote excerpts verbatim. No PII, no secrets, no API keys, no absolute file paths. Reply with only the summary.';
35286
+ SUMMARISER_MAX_TOKENS = 180;
35270
35287
  SUMMARISER_TEMPERATURE = 0.2;
35271
35288
  QWEN_CHARS_PER_TOKEN = 3.3;
35272
- ABSTRACT_OUTPUT_MAX_CHARS = 240;
35289
+ ABSTRACT_OUTPUT_MAX_CHARS = 400;
35273
35290
  }
35274
35291
  });
35275
35292
 
@@ -35782,7 +35799,7 @@ async function summariseSlice(sessionId, slice, adapters2) {
35782
35799
  Sampled excerpts from the conversation (already redacted of PII and secrets):
35783
35800
  ${excerptBlock}
35784
35801
 
35785
- Write the SHORTEST keyword-dense paragraph (1-3 sentences, \u2264${ABSTRACT_OUTPUT_MAX_CHARS} chars) naming exactly what was achieved. Lead with an outcome verb. Pack with concrete domain keywords (frameworks, features, components, decisions). Skip narration and filler.`;
35802
+ Write a \u2264${ABSTRACT_OUTPUT_MAX_CHARS}-char summary (1-2 sentences) naming exactly what was achieved: the concrete action, what it acted on, and the specific target (repo/branch/service/component) when identifiable from the context above. Lead with an outcome verb and pack in concrete domain keywords (frameworks, features, decisions). Skip narration and filler.`;
35786
35803
  const rawAbstract = await adapters2.summarize({
35787
35804
  prompt,
35788
35805
  maxTokens: SUMMARISER_MAX_TOKENS,
@@ -35810,15 +35827,16 @@ Write the SHORTEST keyword-dense paragraph (1-3 sentences, \u2264${ABSTRACT_OUTP
35810
35827
  }
35811
35828
  }
35812
35829
  const redacted = { text: abstractText, counts };
35813
- let abstractWithCognition = redacted.text;
35830
+ let cognition = null;
35814
35831
  if (adapters2.cognize) {
35815
35832
  try {
35816
- const tags2 = await adapters2.cognize({ abstract: redacted.text });
35817
- const suffix = formatCognitionSuffix(tags2);
35818
- if (suffix) abstractWithCognition = `${redacted.text} ${suffix}`;
35833
+ cognition = await adapters2.cognize({ abstract: redacted.text });
35819
35834
  } catch {
35820
35835
  }
35821
35836
  }
35837
+ const cognitionSuffix = cognition ? formatCognitionSuffix(cognition) : "";
35838
+ const abstractWithCognition = cognitionSuffix ? `${redacted.text} ${cognitionSuffix}` : redacted.text;
35839
+ const behavior = computeBehavior(slice, cognition);
35822
35840
  const tags = [
35823
35841
  { root_key: "agents", name: first.agent, confidence: 1 },
35824
35842
  { root_key: "providers", name: first.provider, confidence: 1 }
@@ -35866,6 +35884,7 @@ Write the SHORTEST keyword-dense paragraph (1-3 sentences, \u2264${ABSTRACT_OUTP
35866
35884
  } catch {
35867
35885
  segmentEmbedding = void 0;
35868
35886
  }
35887
+ const userIntent = await summariseUserIntent(slice, adapters2);
35869
35888
  const sourceEventIds = slice.map((e) => e.source_event_id);
35870
35889
  const id = segmentId(sessionId, startedAtMs, endedAtMs, sourceEventIds);
35871
35890
  return {
@@ -35887,7 +35906,66 @@ Write the SHORTEST keyword-dense paragraph (1-3 sentences, \u2264${ABSTRACT_OUTP
35887
35906
  // number-valued catchall for pf_*.
35888
35907
  redaction: redacted.counts,
35889
35908
  source_event_ids: sourceEventIds,
35890
- abstract_embedding: segmentEmbedding && segmentEmbedding.length === 384 ? segmentEmbedding : void 0
35909
+ abstract_embedding: segmentEmbedding && segmentEmbedding.length === 384 ? segmentEmbedding : void 0,
35910
+ behavior,
35911
+ user_intent: userIntent
35912
+ };
35913
+ }
35914
+ async function summariseUserIntent(slice, adapters2) {
35915
+ const userExcerpts = slice.filter((e) => e.kind === "user_message").map((e) => e.content_excerpt?.replace(/\s+/g, " ").trim()).filter((x) => !!x && x.length > 0);
35916
+ if (userExcerpts.length === 0) return void 0;
35917
+ const sample = userExcerpts.length <= 6 ? userExcerpts : [...userExcerpts.slice(0, 4), ...userExcerpts.slice(-2)];
35918
+ const block = sample.map((e, i) => ` [msg ${i + 1}] "${e.slice(0, 240)}"`).join("\n");
35919
+ try {
35920
+ const raw = await adapters2.summarize({
35921
+ prompt: `The developer's own messages to an AI coding assistant (already redacted of PII and secrets):
35922
+ ${block}
35923
+
35924
+ In \u2264${USER_INTENT_MAX_CHARS} chars, summarise WHAT THE DEVELOPER ASKED FOR or DIRECTED \u2014 their goal or task in their own framing, AND any standing preferences / directives / conventions they expressed (e.g. "always be thorough", "ship fast", a naming or workflow convention). Focus on the DEVELOPER'S intent and voice, NOT what the assistant did. Reply with only the summary.`,
35925
+ maxTokens: USER_INTENT_MAX_TOKENS,
35926
+ excerpts: sample,
35927
+ facts: ""
35928
+ });
35929
+ if (!raw || !raw.trim()) return void 0;
35930
+ const regexPass = redact(raw);
35931
+ let text = regexPass.text;
35932
+ if (adapters2.redact) {
35933
+ try {
35934
+ text = (await adapters2.redact(regexPass.text)).text;
35935
+ } catch {
35936
+ }
35937
+ }
35938
+ const trimmed = text.trim().slice(0, USER_INTENT_MAX_CHARS);
35939
+ return trimmed.length > 0 ? trimmed : void 0;
35940
+ } catch {
35941
+ return void 0;
35942
+ }
35943
+ }
35944
+ function computeBehavior(slice, cognition) {
35945
+ let userTurns = 0;
35946
+ let correctionCount = 0;
35947
+ let prevWasAssistant = false;
35948
+ for (const ev of slice) {
35949
+ if (ev.kind === "user_message") {
35950
+ userTurns++;
35951
+ if (prevWasAssistant) correctionCount++;
35952
+ prevWasAssistant = false;
35953
+ } else if (ev.kind === "assistant_message") {
35954
+ prevWasAssistant = true;
35955
+ }
35956
+ }
35957
+ const frustratedMood = cognition?.emotions?.some((e) => {
35958
+ const lower = e.toLowerCase();
35959
+ return FRUSTRATION_MARKERS.some((m) => lower.includes(m));
35960
+ }) ?? false;
35961
+ const frustration = Math.min(
35962
+ 1,
35963
+ Math.max(correctionCount / 4, frustratedMood ? 0.8 : 0)
35964
+ );
35965
+ return {
35966
+ user_turns: userTurns,
35967
+ correction_count: correctionCount,
35968
+ frustration: Math.round(frustration * 100) / 100
35891
35969
  };
35892
35970
  }
35893
35971
  function sampleAndRedactExcerpts(slice) {
@@ -35942,7 +36020,7 @@ function inferEnvironment(branch) {
35942
36020
  if (b === "dev" || b === "develop" || b.startsWith("dev/")) return "Dev";
35943
36021
  return null;
35944
36022
  }
35945
- var SEGMENT_TIME_GAP_MS, SEGMENT_TOPIC_THRESHOLD, SEGMENT_MAX_TURNS, SEGMENT_MAX_DURATION_MS, SEGMENT_MAX_CONTENT_CHARS, ABSTRACT_MAX_CHARS;
36023
+ var SEGMENT_TIME_GAP_MS, SEGMENT_TOPIC_THRESHOLD, SEGMENT_MAX_TURNS, SEGMENT_MAX_DURATION_MS, SEGMENT_MAX_CONTENT_CHARS, ABSTRACT_MAX_CHARS, USER_INTENT_MAX_CHARS, USER_INTENT_MAX_TOKENS, FRUSTRATION_MARKERS;
35946
36024
  var init_pipeline = __esm({
35947
36025
  "../../packages/daemon-core/src/pipeline/index.ts"() {
35948
36026
  "use strict";
@@ -35964,6 +36042,19 @@ var init_pipeline = __esm({
35964
36042
  SEGMENT_MAX_DURATION_MS = 30 * 6e4;
35965
36043
  SEGMENT_MAX_CONTENT_CHARS = 12e3;
35966
36044
  ABSTRACT_MAX_CHARS = 512;
36045
+ USER_INTENT_MAX_CHARS = 240;
36046
+ USER_INTENT_MAX_TOKENS = 120;
36047
+ FRUSTRATION_MARKERS = [
36048
+ "frustrat",
36049
+ "annoy",
36050
+ "stuck",
36051
+ "confus",
36052
+ "irritat",
36053
+ "block",
36054
+ "stress",
36055
+ "angr",
36056
+ "overwhelm"
36057
+ ];
35967
36058
  }
35968
36059
  });
35969
36060
 
@@ -37267,7 +37358,7 @@ var init_scan = __esm({
37267
37358
  init_api();
37268
37359
  init_config2();
37269
37360
  init_pipeline2();
37270
- DAEMON_VERSION = true ? "daemon-0.11.1" : "daemon-dev";
37361
+ DAEMON_VERSION = true ? "daemon-0.13.0" : "daemon-dev";
37271
37362
  BATCH_MAX_EVENTS = INGEST_BATCH_MAX_EVENTS;
37272
37363
  BATCH_MAX_TOOL_CALLS = 2e4;
37273
37364
  BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
@@ -37834,7 +37925,7 @@ var PROCESSING_VERSION;
37834
37925
  var init_processing_version = __esm({
37835
37926
  "src/processing-version.ts"() {
37836
37927
  "use strict";
37837
- PROCESSING_VERSION = 9;
37928
+ PROCESSING_VERSION = 11;
37838
37929
  }
37839
37930
  });
37840
37931
 
@@ -40214,7 +40305,7 @@ var init_daemon = __esm({
40214
40305
  init_scan();
40215
40306
  init_single_flight();
40216
40307
  init_update();
40217
- DAEMON_VERSION2 = true ? "daemon-0.11.1" : "daemon-dev";
40308
+ DAEMON_VERSION2 = true ? "daemon-0.13.0" : "daemon-dev";
40218
40309
  HEARTBEAT_INTERVAL_MS = 1e4;
40219
40310
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
40220
40311
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -40846,7 +40937,7 @@ function tryOpenBrowser(url) {
40846
40937
  return false;
40847
40938
  }
40848
40939
  }
40849
- var DAEMON_VERSION3 = true ? "daemon-0.11.1" : "daemon-dev";
40940
+ var DAEMON_VERSION3 = true ? "daemon-0.13.0" : "daemon-dev";
40850
40941
  function osFamily() {
40851
40942
  const p = platform6();
40852
40943
  if (p === "darwin") return "macos";