modelstat 0.8.3 → 0.9.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 +171 -26
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -34556,7 +34556,9 @@ function load() {
|
|
|
34556
34556
|
cursor: obj.cursor ?? {},
|
|
34557
34557
|
segmentsSent: obj.segmentsSent ?? 0,
|
|
34558
34558
|
processingVersion: obj.processingVersion ?? null,
|
|
34559
|
-
reconcileCache: obj.reconcileCache ?? {}
|
|
34559
|
+
reconcileCache: obj.reconcileCache ?? {},
|
|
34560
|
+
summariserDegraded: obj.summariserDegraded ?? false,
|
|
34561
|
+
summariserRecoveryAt: obj.summariserRecoveryAt ?? 0
|
|
34560
34562
|
};
|
|
34561
34563
|
} catch {
|
|
34562
34564
|
cache2 = { ...DEFAULTS, cursor: {} };
|
|
@@ -34579,7 +34581,9 @@ var init_runtime_state = __esm({
|
|
|
34579
34581
|
cursor: {},
|
|
34580
34582
|
segmentsSent: 0,
|
|
34581
34583
|
processingVersion: null,
|
|
34582
|
-
reconcileCache: {}
|
|
34584
|
+
reconcileCache: {},
|
|
34585
|
+
summariserDegraded: false,
|
|
34586
|
+
summariserRecoveryAt: 0
|
|
34583
34587
|
};
|
|
34584
34588
|
cache2 = null;
|
|
34585
34589
|
runtimeState = {
|
|
@@ -34646,6 +34650,25 @@ var init_runtime_state = __esm({
|
|
|
34646
34650
|
s.processingVersion = v;
|
|
34647
34651
|
persist(s);
|
|
34648
34652
|
},
|
|
34653
|
+
/** Whether the last run shipped extractive (LLM-unavailable) abstracts. */
|
|
34654
|
+
getSummariserDegraded() {
|
|
34655
|
+
return load().summariserDegraded;
|
|
34656
|
+
},
|
|
34657
|
+
setSummariserDegraded(v) {
|
|
34658
|
+
const s = load();
|
|
34659
|
+
if (s.summariserDegraded === v) return;
|
|
34660
|
+
s.summariserDegraded = v;
|
|
34661
|
+
persist(s);
|
|
34662
|
+
},
|
|
34663
|
+
/** ms-epoch of the last degradation-recovery re-scan (0 = never). */
|
|
34664
|
+
getSummariserRecoveryAt() {
|
|
34665
|
+
return load().summariserRecoveryAt;
|
|
34666
|
+
},
|
|
34667
|
+
setSummariserRecoveryAt(ms) {
|
|
34668
|
+
const s = load();
|
|
34669
|
+
s.summariserRecoveryAt = ms;
|
|
34670
|
+
persist(s);
|
|
34671
|
+
},
|
|
34649
34672
|
/** Self-healing reconcile cache (see {@link RuntimeState.reconcileCache}). */
|
|
34650
34673
|
getReconcileCache() {
|
|
34651
34674
|
return load().reconcileCache;
|
|
@@ -35289,6 +35312,61 @@ var init_prompts = __esm({
|
|
|
35289
35312
|
}
|
|
35290
35313
|
});
|
|
35291
35314
|
|
|
35315
|
+
// ../../packages/daemon-core/src/pipeline/heuristic-summary.ts
|
|
35316
|
+
function parsePromptExcerpts(prompt) {
|
|
35317
|
+
const out = [];
|
|
35318
|
+
const re = /\[turn \d+\]\s*"([^"]*)"/g;
|
|
35319
|
+
let m = re.exec(prompt);
|
|
35320
|
+
while (m) {
|
|
35321
|
+
if (m[1]) out.push(m[1]);
|
|
35322
|
+
m = re.exec(prompt);
|
|
35323
|
+
}
|
|
35324
|
+
return out;
|
|
35325
|
+
}
|
|
35326
|
+
function parsePromptFacts(prompt) {
|
|
35327
|
+
const m = /Session context:\s*(.+?)\.\s*(?:\n|$)/.exec(prompt);
|
|
35328
|
+
return m?.[1]?.trim() ?? "";
|
|
35329
|
+
}
|
|
35330
|
+
function pickIntent(lines) {
|
|
35331
|
+
const early = lines.slice(0, 5);
|
|
35332
|
+
const substantive = early.filter((l) => l.length >= 16 && !GREETING.test(l));
|
|
35333
|
+
if (substantive.length > 0) return substantive[0];
|
|
35334
|
+
const byLen = [...early].sort((a, b) => b.length - a.length);
|
|
35335
|
+
return byLen[0] ?? lines.find((l) => l.length > 0) ?? null;
|
|
35336
|
+
}
|
|
35337
|
+
function cleanLead(s) {
|
|
35338
|
+
let t = s.replace(GREETING, "").trim();
|
|
35339
|
+
t = t.replace(LEAD_FILLER, "").trim();
|
|
35340
|
+
if (!t) t = s.trim();
|
|
35341
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
35342
|
+
}
|
|
35343
|
+
function clamp(s, max) {
|
|
35344
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
35345
|
+
if (t.length <= max) return t;
|
|
35346
|
+
const cut = t.slice(0, max - 1);
|
|
35347
|
+
const sp = cut.lastIndexOf(" ");
|
|
35348
|
+
return `${(sp > max * 0.6 ? cut.slice(0, sp) : cut).trimEnd()}\u2026`;
|
|
35349
|
+
}
|
|
35350
|
+
function heuristicSummarize() {
|
|
35351
|
+
return async ({ prompt, excerpts, facts }) => {
|
|
35352
|
+
const lines = (excerpts && excerpts.length > 0 ? excerpts : parsePromptExcerpts(prompt)).map((s) => s.replace(/\s+/g, " ").trim()).filter((s) => s.length > 0);
|
|
35353
|
+
const factText = (facts?.trim() || parsePromptFacts(prompt)).replace(/\s+/g, " ").trim();
|
|
35354
|
+
const intent = pickIntent(lines);
|
|
35355
|
+
const lead = intent ? cleanLead(intent) : "AI coding session";
|
|
35356
|
+
const body = factText ? `${lead} [${factText}]` : lead;
|
|
35357
|
+
return clamp(body, ABSTRACT_OUTPUT_MAX_CHARS);
|
|
35358
|
+
};
|
|
35359
|
+
}
|
|
35360
|
+
var GREETING, LEAD_FILLER;
|
|
35361
|
+
var init_heuristic_summary = __esm({
|
|
35362
|
+
"../../packages/daemon-core/src/pipeline/heuristic-summary.ts"() {
|
|
35363
|
+
"use strict";
|
|
35364
|
+
init_prompts();
|
|
35365
|
+
GREETING = /^(hi|hey|hello|thanks?|thank you|ok(ay)?|yes|no|sure|please|cool|great|nice)\b[\s!.,]*/i;
|
|
35366
|
+
LEAD_FILLER = /^(can you|could you|could we|can we|please|i(?:'d| would)? (?:like|want)(?: you)? to|i need(?: you)? to|let'?s|help me|i'?m trying to|i am trying to|now|so|ok(?:ay)?)\s+/i;
|
|
35367
|
+
}
|
|
35368
|
+
});
|
|
35369
|
+
|
|
35292
35370
|
// ../../packages/daemon-core/src/pipeline/script-summary.ts
|
|
35293
35371
|
function buildScriptSummaryUserPrompt(input) {
|
|
35294
35372
|
const body = input.content.slice(0, SCRIPT_SUMMARY_INPUT_MAX_CHARS);
|
|
@@ -35755,7 +35833,11 @@ ${excerptBlock}
|
|
|
35755
35833
|
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.`;
|
|
35756
35834
|
const rawAbstract = await adapters2.summarize({
|
|
35757
35835
|
prompt,
|
|
35758
|
-
maxTokens: SUMMARISER_MAX_TOKENS
|
|
35836
|
+
maxTokens: SUMMARISER_MAX_TOKENS,
|
|
35837
|
+
// Structured inputs for the dependency-free fallback summariser (used when
|
|
35838
|
+
// the bundled LLM can't load); the LLM path ignores these and uses `prompt`.
|
|
35839
|
+
excerpts,
|
|
35840
|
+
facts: promptFacts
|
|
35759
35841
|
});
|
|
35760
35842
|
if (!rawAbstract || rawAbstract.trim().length === 0) {
|
|
35761
35843
|
throw new Error(
|
|
@@ -35916,6 +35998,7 @@ var init_pipeline = __esm({
|
|
|
35916
35998
|
init_redact();
|
|
35917
35999
|
init_cognition();
|
|
35918
36000
|
init_prompts();
|
|
36001
|
+
init_heuristic_summary();
|
|
35919
36002
|
init_redact();
|
|
35920
36003
|
init_cognition();
|
|
35921
36004
|
init_prompts();
|
|
@@ -36873,10 +36956,41 @@ __export(pipeline_exports, {
|
|
|
36873
36956
|
buildSessionMetadata: () => buildSessionMetadata2,
|
|
36874
36957
|
buildSessionTitles: () => buildSessionTitles2,
|
|
36875
36958
|
enrichScripts: () => enrichScripts,
|
|
36876
|
-
preflightSummariser: () => preflightSummariser
|
|
36959
|
+
preflightSummariser: () => preflightSummariser,
|
|
36960
|
+
summariserDegradedThisProcess: () => summariserDegradedThisProcess
|
|
36877
36961
|
});
|
|
36878
36962
|
import { existsSync as existsSync8 } from "fs";
|
|
36879
36963
|
import { readFile as fsReadFile } from "fs/promises";
|
|
36964
|
+
function summariserDegradedThisProcess() {
|
|
36965
|
+
return degradedThisProcess;
|
|
36966
|
+
}
|
|
36967
|
+
function markDegraded(reason) {
|
|
36968
|
+
if (!degradedThisProcess) {
|
|
36969
|
+
degradedThisProcess = true;
|
|
36970
|
+
console.warn(
|
|
36971
|
+
`[modelstat] \u26A0 summariser DEGRADED \u2014 bundled LLM unavailable (${reason}); shipping extractive fallback abstracts so ingest continues. They re-summarise at model quality automatically once the LLM is healthy again.`
|
|
36972
|
+
);
|
|
36973
|
+
}
|
|
36974
|
+
runtimeState.setSummariserDegraded(true);
|
|
36975
|
+
}
|
|
36976
|
+
function resilientSummarize(llamaCfg) {
|
|
36977
|
+
const llm = llamaSummarize(llamaCfg);
|
|
36978
|
+
const heuristic = heuristicSummarize();
|
|
36979
|
+
return async (input) => {
|
|
36980
|
+
if (Date.now() >= llmRetryAfter) {
|
|
36981
|
+
try {
|
|
36982
|
+
const out = await llm(input);
|
|
36983
|
+
if (out && out.trim().length > 0) return out;
|
|
36984
|
+
} catch (err) {
|
|
36985
|
+
llmRetryAfter = Date.now() + LLM_RETRY_COOLDOWN_MS;
|
|
36986
|
+
markDegraded(err.message);
|
|
36987
|
+
}
|
|
36988
|
+
} else {
|
|
36989
|
+
markDegraded("LLM in post-failure cooldown");
|
|
36990
|
+
}
|
|
36991
|
+
return heuristic(input);
|
|
36992
|
+
};
|
|
36993
|
+
}
|
|
36880
36994
|
async function bundledAdapters() {
|
|
36881
36995
|
const llamaCfg = defaultLlamaConfig();
|
|
36882
36996
|
return {
|
|
@@ -36886,7 +37000,7 @@ async function bundledAdapters() {
|
|
|
36886
37000
|
// vector-less with empty arrays; hooking embeddings here attaches a
|
|
36887
37001
|
// real abstract embedding to each segment.)
|
|
36888
37002
|
embed: createTransformersJsEmbedder(),
|
|
36889
|
-
summarize:
|
|
37003
|
+
summarize: resilientSummarize(llamaCfg),
|
|
36890
37004
|
tokenize: (text) => Math.max(1, Math.ceil(text.length / 4)),
|
|
36891
37005
|
cognize: llamaCognize(llamaCfg),
|
|
36892
37006
|
// Session-title pass — same bundled model, third chat session with
|
|
@@ -36919,14 +37033,15 @@ async function bundledAdapters() {
|
|
|
36919
37033
|
}
|
|
36920
37034
|
async function getAdapters() {
|
|
36921
37035
|
if (adapters) return adapters;
|
|
37036
|
+
let llmReady = true;
|
|
36922
37037
|
try {
|
|
36923
37038
|
await import("node-llama-cpp");
|
|
36924
|
-
} catch
|
|
36925
|
-
|
|
36926
|
-
`modelstat daemon can't start: the bundled summariser (node-llama-cpp) failed to load. Re-run \`modelstat connect\` (or \`npm i -g modelstat\`) so the native runtime is re-staged beside the bundle. Underlying error: ${err.message}`
|
|
36927
|
-
);
|
|
37039
|
+
} catch {
|
|
37040
|
+
llmReady = false;
|
|
36928
37041
|
}
|
|
36929
|
-
console.log(
|
|
37042
|
+
console.log(
|
|
37043
|
+
llmReady ? "[modelstat] using bundled local summariser (Qwen3.5-4B, runs on this machine)" : "[modelstat] bundled summariser runtime not loadable \u2014 using extractive fallback (degraded) until it is"
|
|
37044
|
+
);
|
|
36930
37045
|
adapters = await bundledAdapters();
|
|
36931
37046
|
return adapters;
|
|
36932
37047
|
}
|
|
@@ -36965,16 +37080,19 @@ async function preflightSummariser() {
|
|
|
36965
37080
|
const a = await getAdapters();
|
|
36966
37081
|
const out = await a.summarize({
|
|
36967
37082
|
prompt: 'Session context: smoke test. Sampled excerpts:\n [turn 1] "hello world"\nWrite ONE sentence (\u2264240 chars) describing what the human was doing.',
|
|
36968
|
-
maxTokens: 32
|
|
37083
|
+
maxTokens: 32,
|
|
37084
|
+
excerpts: ["smoke test \u2014 verifying the summariser is alive"],
|
|
37085
|
+
facts: "preflight smoke test"
|
|
36969
37086
|
});
|
|
37087
|
+
const degraded = summariserDegradedThisProcess();
|
|
36970
37088
|
if (!out || out.trim().length === 0) {
|
|
36971
|
-
|
|
36972
|
-
"summariser preflight returned empty output \u2014 the configured summariser is reachable but produced no text. Check the model is loaded."
|
|
36973
|
-
);
|
|
37089
|
+
return { label: "summariser produced no output", degraded: true };
|
|
36974
37090
|
}
|
|
36975
|
-
|
|
37091
|
+
const sample = out.length > 60 ? `${out.slice(0, 57)}\u2026` : out;
|
|
37092
|
+
const engine = degraded ? "extractive fallback (LLM unavailable)" : "Qwen3.5-4B";
|
|
37093
|
+
return { label: `${engine} \u2014 "${sample}"`, degraded };
|
|
36976
37094
|
}
|
|
36977
|
-
var adapters, MAX_SCRIPT_READ_BYTES, scriptSummarizer;
|
|
37095
|
+
var adapters, degradedThisProcess, LLM_RETRY_COOLDOWN_MS, llmRetryAfter, MAX_SCRIPT_READ_BYTES, scriptSummarizer;
|
|
36978
37096
|
var init_pipeline2 = __esm({
|
|
36979
37097
|
"src/pipeline.ts"() {
|
|
36980
37098
|
"use strict";
|
|
@@ -36983,7 +37101,11 @@ var init_pipeline2 = __esm({
|
|
|
36983
37101
|
init_privacy_filter();
|
|
36984
37102
|
init_src2();
|
|
36985
37103
|
init_enrich_scripts();
|
|
37104
|
+
init_runtime_state();
|
|
36986
37105
|
adapters = null;
|
|
37106
|
+
degradedThisProcess = false;
|
|
37107
|
+
LLM_RETRY_COOLDOWN_MS = 10 * 6e4;
|
|
37108
|
+
llmRetryAfter = 0;
|
|
36987
37109
|
MAX_SCRIPT_READ_BYTES = 64 * 1024;
|
|
36988
37110
|
scriptSummarizer = null;
|
|
36989
37111
|
}
|
|
@@ -37193,7 +37315,7 @@ var init_scan = __esm({
|
|
|
37193
37315
|
init_api();
|
|
37194
37316
|
init_config2();
|
|
37195
37317
|
init_pipeline2();
|
|
37196
|
-
DAEMON_VERSION = true ? "daemon-0.
|
|
37318
|
+
DAEMON_VERSION = true ? "daemon-0.9.0" : "daemon-dev";
|
|
37197
37319
|
BATCH_MAX_EVENTS = INGEST_BATCH_MAX_EVENTS;
|
|
37198
37320
|
BATCH_MAX_TOOL_CALLS = 2e4;
|
|
37199
37321
|
BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
|
|
@@ -39996,14 +40118,33 @@ async function runDaemon(opts = {}) {
|
|
|
39996
40118
|
const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
39997
40119
|
hb.unref();
|
|
39998
40120
|
void sendHeartbeat();
|
|
40121
|
+
const wasDegraded = runtimeState.getSummariserDegraded();
|
|
39999
40122
|
try {
|
|
40000
40123
|
setPhase("starting", "Preflight: summariser");
|
|
40001
40124
|
const { preflightSummariser: preflightSummariser2 } = await Promise.resolve().then(() => (init_pipeline2(), pipeline_exports));
|
|
40002
|
-
const
|
|
40003
|
-
|
|
40125
|
+
const { label, degraded } = await preflightSummariser2();
|
|
40126
|
+
if (degraded) {
|
|
40127
|
+
console.warn(`[modelstat] \u26A0 summariser preflight DEGRADED \u2014 ${label}`);
|
|
40128
|
+
setMessage(
|
|
40129
|
+
"summariser degraded: extractive fallback (LLM unavailable) \u2014 ingest continues, self-heals when the model loads"
|
|
40130
|
+
);
|
|
40131
|
+
} else {
|
|
40132
|
+
console.log(`[modelstat] summariser preflight ok: ${label}`);
|
|
40133
|
+
if (wasDegraded) {
|
|
40134
|
+
const since = Date.now() - runtimeState.getSummariserRecoveryAt();
|
|
40135
|
+
if (since > SUMMARISER_RECOVERY_MIN_INTERVAL_MS) {
|
|
40136
|
+
runtimeState.wipeCursors();
|
|
40137
|
+
runtimeState.setSummariserRecoveryAt(Date.now());
|
|
40138
|
+
console.log(
|
|
40139
|
+
"[modelstat] summariser recovered \u2014 re-scanning so extractive fallback abstracts upgrade to model quality"
|
|
40140
|
+
);
|
|
40141
|
+
}
|
|
40142
|
+
}
|
|
40143
|
+
runtimeState.setSummariserDegraded(false);
|
|
40144
|
+
}
|
|
40004
40145
|
} catch (err) {
|
|
40005
|
-
|
|
40006
|
-
|
|
40146
|
+
console.warn(`[modelstat] summariser preflight error (continuing): ${err.message}`);
|
|
40147
|
+
setMessage(`summariser preflight error (continuing): ${err.message}`);
|
|
40007
40148
|
}
|
|
40008
40149
|
const localIngest = await startLocalIngestReceiver();
|
|
40009
40150
|
const LOCAL_DRAIN_INTERVAL_MS = 5e3;
|
|
@@ -40104,7 +40245,7 @@ async function runDaemon(opts = {}) {
|
|
|
40104
40245
|
await new Promise(() => {
|
|
40105
40246
|
});
|
|
40106
40247
|
}
|
|
40107
|
-
var import_undici2, DAEMON_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastVerdict, LOG_MAX_BYTES, LOG_TAIL_KEEP_BYTES, lastStatusPath, scanRunner;
|
|
40248
|
+
var import_undici2, DAEMON_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, SUMMARISER_RECOVERY_MIN_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastVerdict, LOG_MAX_BYTES, LOG_TAIL_KEEP_BYTES, lastStatusPath, scanRunner;
|
|
40108
40249
|
var init_daemon = __esm({
|
|
40109
40250
|
"src/daemon.ts"() {
|
|
40110
40251
|
"use strict";
|
|
@@ -40117,13 +40258,15 @@ var init_daemon = __esm({
|
|
|
40117
40258
|
init_machine_key();
|
|
40118
40259
|
init_receiver();
|
|
40119
40260
|
init_reconcile();
|
|
40261
|
+
init_runtime_state();
|
|
40120
40262
|
init_scan();
|
|
40121
40263
|
init_single_flight();
|
|
40122
40264
|
init_update();
|
|
40123
|
-
DAEMON_VERSION2 = true ? "daemon-0.
|
|
40265
|
+
DAEMON_VERSION2 = true ? "daemon-0.9.0" : "daemon-dev";
|
|
40124
40266
|
HEARTBEAT_INTERVAL_MS = 1e4;
|
|
40125
40267
|
SCAN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
40126
40268
|
DISCOVERY_INTERVAL_MS = 6e4;
|
|
40269
|
+
SUMMARISER_RECOVERY_MIN_INTERVAL_MS = 6 * 60 * 6e4;
|
|
40127
40270
|
status = {
|
|
40128
40271
|
phase: "starting",
|
|
40129
40272
|
message: null,
|
|
@@ -40724,7 +40867,7 @@ function tryOpenBrowser(url) {
|
|
|
40724
40867
|
return false;
|
|
40725
40868
|
}
|
|
40726
40869
|
}
|
|
40727
|
-
var DAEMON_VERSION3 = true ? "daemon-0.
|
|
40870
|
+
var DAEMON_VERSION3 = true ? "daemon-0.9.0" : "daemon-dev";
|
|
40728
40871
|
function osFamily() {
|
|
40729
40872
|
const p = platform6();
|
|
40730
40873
|
if (p === "darwin") return "macos";
|
|
@@ -41097,8 +41240,10 @@ async function cmdDiscover() {
|
|
|
41097
41240
|
}
|
|
41098
41241
|
async function cmdScan() {
|
|
41099
41242
|
const { preflightSummariser: preflightSummariser2 } = await Promise.resolve().then(() => (init_pipeline2(), pipeline_exports));
|
|
41100
|
-
const
|
|
41101
|
-
console.log(
|
|
41243
|
+
const { label, degraded } = await preflightSummariser2();
|
|
41244
|
+
console.log(
|
|
41245
|
+
degraded ? `[modelstat] \u26A0 summariser DEGRADED \u2014 ${label}; extractive fallback, ingest continues` : `[modelstat] summariser preflight ok: ${label}`
|
|
41246
|
+
);
|
|
41102
41247
|
const { reconcileProcessingVersion: reconcileProcessingVersion2 } = await Promise.resolve().then(() => (init_processing_version(), processing_version_exports));
|
|
41103
41248
|
const pv = reconcileProcessingVersion2(state);
|
|
41104
41249
|
if (pv.changed) {
|