omnius 1.0.603 → 1.0.604

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/index.js CHANGED
@@ -703755,6 +703755,90 @@ var init_ollama_gpu_policy = __esm({
703755
703755
  }
703756
703756
  });
703757
703757
 
703758
+ // packages/cli/src/session-quality.ts
703759
+ function withoutAnsi(text2) {
703760
+ return String(text2 || "").replace(ANSI_RE, "");
703761
+ }
703762
+ function normalizeSessionDisplayText(text2) {
703763
+ return withoutAnsi(text2).trim().replace(MARKER_PREFIX_RE, "").replace(SPEAKER_RE, "").replace(/\s+/g, " ").trim();
703764
+ }
703765
+ function isSessionControlText(text2) {
703766
+ const raw = withoutAnsi(text2).trim().replace(/^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|\\.\s]+/, "").replace(SPEAKER_RE, "").trim();
703767
+ return CONTROL_ONLY_RE.test(raw);
703768
+ }
703769
+ function isSessionNoiseText(text2) {
703770
+ const raw = withoutAnsi(text2).trim();
703771
+ if (!raw || raw.length < 3 || isSessionControlText(raw)) return true;
703772
+ if (BOX_OR_TOOL_CHROME_RE.test(raw)) return true;
703773
+ const core = normalizeSessionDisplayText(raw);
703774
+ if (!core || core.length < 3 || CONTROL_ONLY_RE.test(core)) return true;
703775
+ return STATUS_RE.test(core);
703776
+ }
703777
+ function firstMeaningfulSessionLine(transcript) {
703778
+ const lines = withoutAnsi(transcript).split(/\r?\n/).map((line) => line.trim());
703779
+ for (const line of lines) {
703780
+ if (USER_LINE_RE.test(line) && !isSessionNoiseText(line)) {
703781
+ return normalizeSessionDisplayText(line);
703782
+ }
703783
+ }
703784
+ for (const line of lines) {
703785
+ if (!isSessionNoiseText(line)) return normalizeSessionDisplayText(line);
703786
+ }
703787
+ return "";
703788
+ }
703789
+ function hasMeaningfulSessionContent(transcript) {
703790
+ return firstMeaningfulSessionLine(transcript).length > 0;
703791
+ }
703792
+ function normalizedSessionFingerprint(transcript) {
703793
+ const semantic = [];
703794
+ let mode = "text";
703795
+ let inToolBox = false;
703796
+ for (const raw of withoutAnsi(transcript).split(/\r?\n/)) {
703797
+ const line = raw.trim();
703798
+ if (!line) continue;
703799
+ if (/^[╭┌]/.test(line)) {
703800
+ inToolBox = true;
703801
+ continue;
703802
+ }
703803
+ if (/^[╰└]/.test(line)) {
703804
+ inToolBox = false;
703805
+ continue;
703806
+ }
703807
+ if (inToolBox) continue;
703808
+ const user = line.match(/^(?:[▹▸►❯>]\s*|(?:User|You)\s*:\s*)(.+)$/i);
703809
+ if (user && !isSessionNoiseText(user[1] || "")) {
703810
+ mode = "user";
703811
+ semantic.push(`user:${normalizeSessionDisplayText(user[1] || "").toLowerCase()}`);
703812
+ continue;
703813
+ }
703814
+ const assistant = line.match(/^(?:(?:Assistant|Open Agent|Omnius)\s*:\s*|│\s?)(.*)$/i);
703815
+ if (assistant) {
703816
+ const content2 = normalizeSessionDisplayText((assistant[1] || "").replace(/\s?│$/, ""));
703817
+ if (content2) {
703818
+ mode = "assistant";
703819
+ semantic.push(`assistant:${content2.toLowerCase()}`);
703820
+ }
703821
+ continue;
703822
+ }
703823
+ if (isSessionNoiseText(line)) continue;
703824
+ const content = normalizeSessionDisplayText(line);
703825
+ if (content) semantic.push(`${mode}:${content.toLowerCase()}`);
703826
+ }
703827
+ return semantic.join("\n");
703828
+ }
703829
+ var ANSI_RE, SPEAKER_RE, MARKER_PREFIX_RE, USER_LINE_RE, CONTROL_ONLY_RE, BOX_OR_TOOL_CHROME_RE, STATUS_RE;
703830
+ var init_session_quality = __esm({
703831
+ "packages/cli/src/session-quality.ts"() {
703832
+ ANSI_RE = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
703833
+ SPEAKER_RE = /^(?:User|Assistant|You|Open Agent|Omnius)\s*:\s*/i;
703834
+ MARKER_PREFIX_RE = /^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|/\\.\s]+/;
703835
+ USER_LINE_RE = /^(?:[▹▸►❯>]\s+\S|(?:User|You)\s*:\s*\S)/i;
703836
+ CONTROL_ONLY_RE = /^\/?(?:q|quit|exit)$/i;
703837
+ BOX_OR_TOOL_CHROME_RE = /^[╭╮╰╯├┤┬┴┌┐└┘─━═╿│▌▐█▒░●○◐◖✔✖$]/;
703838
+ STATUS_RE = /^(?:nexus|rest api|voice feedback|clone ref|connecting to|connected|http:\/\/|https:\/\/|last task:|\(manual save\)|good morning|good evening|good afternoon|cannot reach|could not|unable to|warning:|error:|loaded tui session|session restored|restored previous|using (?:expanded )?context model|using model|tui session\b|chat tui:|previous session found|context (?:auto-)?restored|context restored|recovered session|\[?new_task_intake\b|\[imported tui session transcript\]|title:|description:|project root:|user is asking\b|\[vram|vram |model_info|kv |arch[ -]capped|no context to restore|starting fresh|use \/endpoint|general session$|knowledge graph:|zettelkasten:|episodes captured:|current omnius_host:)/i;
703839
+ }
703840
+ });
703841
+
703758
703842
  // packages/cli/src/tui/omnius-directory.ts
703759
703843
  var omnius_directory_exports = {};
703760
703844
  __export(omnius_directory_exports, {
@@ -705130,16 +705214,18 @@ function firstMeaningfulSessionHistoryLine(lines) {
705130
705214
  const trimmed = line.trimStart();
705131
705215
  if (!trimmed || VISUAL_CHROME_LINE.test(trimmed)) continue;
705132
705216
  const clean7 = cleanSessionHistoryDisplayLine(line);
705133
- if (!clean7 || SESSION_TITLE_STATUS_LINE.test(clean7)) continue;
705217
+ if (!clean7 || SESSION_TITLE_STATUS_LINE.test(clean7) || isSessionNoiseText(clean7)) continue;
705134
705218
  return clean7;
705135
705219
  }
705136
- return "";
705220
+ return firstMeaningfulSessionLine(lines.slice(0, 120).join("\n"));
705137
705221
  }
705138
- function sanitizeSessionHistoryEntry(repoRoot, entry) {
705139
- const nameLooksAuthored = isAuthoredSessionLine(entry.name) || cleanSessionHistoryDisplayLine(entry.name).length > 0;
705140
- const descriptionLooksAuthored = isAuthoredSessionLine(entry.description) || cleanSessionHistoryDisplayLine(entry.description).length > 0;
705222
+ function sanitizeSessionHistoryEntry(repoRoot, entry, loadedLines) {
705223
+ const cleanName = cleanSessionHistoryDisplayLine(entry.name);
705224
+ const cleanDescription = cleanSessionHistoryDisplayLine(entry.description);
705225
+ const nameLooksAuthored = cleanName.length > 0 && !isSessionNoiseText(cleanName);
705226
+ const descriptionLooksAuthored = cleanDescription.length > 0 && !isSessionNoiseText(cleanDescription);
705141
705227
  if (nameLooksAuthored && descriptionLooksAuthored) return entry;
705142
- const lines = loadSessionHistory(repoRoot, entry.id) || [];
705228
+ const lines = loadedLines ?? loadSessionHistory(repoRoot, entry.id) ?? [];
705143
705229
  const fallback = firstMeaningfulSessionHistoryLine(lines);
705144
705230
  return {
705145
705231
  ...entry,
@@ -705166,14 +705252,16 @@ function saveSessionHistory(repoRoot, sessionId, contentLines, meta) {
705166
705252
  } catch {
705167
705253
  }
705168
705254
  const existing = index.findIndex((s2) => s2.id === sessionId);
705255
+ const previous = existing >= 0 ? index[existing] : void 0;
705169
705256
  const record = {
705257
+ ...previous ?? {},
705170
705258
  id: sessionId,
705171
- name: autoName,
705172
- description: autoDesc,
705173
- createdAt: existing >= 0 ? index[existing].createdAt : (/* @__PURE__ */ new Date()).toISOString(),
705259
+ name: !isSessionNoiseText(autoName) ? autoName : previous?.name ?? autoName,
705260
+ description: !isSessionNoiseText(autoDesc) ? autoDesc : previous?.description ?? autoDesc,
705261
+ createdAt: previous?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
705174
705262
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
705175
- taskCount: meta.taskCount ?? 1,
705176
- model: meta.model ?? "unknown"
705263
+ taskCount: meta.taskCount ?? previous?.taskCount ?? 1,
705264
+ model: meta.model ?? previous?.model ?? "unknown"
705177
705265
  };
705178
705266
  if (existing >= 0) {
705179
705267
  index[existing] = record;
@@ -705186,6 +705274,15 @@ function saveSessionHistory(repoRoot, sessionId, contentLines, meta) {
705186
705274
  unlinkSync28(join141(sessDir, `${removed.id}.jsonl`));
705187
705275
  } catch {
705188
705276
  }
705277
+ try {
705278
+ unlinkSync28(join141(sessDir, `${removed.id}${TUI_STATE_SUFFIX}`));
705279
+ } catch {
705280
+ }
705281
+ try {
705282
+ const mirrorId = `tui:${removed.id}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
705283
+ unlinkSync28(join141(homedir45(), ".omnius", "chat-sessions", `${mirrorId}.json`));
705284
+ } catch {
705285
+ }
705189
705286
  }
705190
705287
  writeFileSync70(indexPath, JSON.stringify(index, null, 2), "utf-8");
705191
705288
  }
@@ -705222,7 +705319,13 @@ function listSessions(repoRoot) {
705222
705319
  try {
705223
705320
  if (!existsSync130(indexPath)) return [];
705224
705321
  const index = JSON.parse(readFileSync107(indexPath, "utf-8"));
705225
- return index.map((entry) => sanitizeSessionHistoryEntry(repoRoot, entry)).sort((a2, b) => b.updatedAt.localeCompare(a2.updatedAt));
705322
+ const eligible = [];
705323
+ for (const entry of index) {
705324
+ const lines = loadSessionHistory(repoRoot, entry.id) ?? [];
705325
+ if (!hasMeaningfulSessionContent(lines.join("\n"))) continue;
705326
+ eligible.push(sanitizeSessionHistoryEntry(repoRoot, entry, lines));
705327
+ }
705328
+ return eligible.sort((a2, b) => b.updatedAt.localeCompare(a2.updatedAt));
705226
705329
  } catch {
705227
705330
  return [];
705228
705331
  }
@@ -705244,6 +705347,9 @@ function deleteSession(repoRoot, sessionId) {
705244
705347
  if (existsSync130(contentPath)) unlinkSync28(contentPath);
705245
705348
  const statePath = join141(sessDir, `${sessionId}${TUI_STATE_SUFFIX}`);
705246
705349
  if (existsSync130(statePath)) unlinkSync28(statePath);
705350
+ const mirrorId = `tui:${sessionId}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
705351
+ const mirrorPath = join141(homedir45(), ".omnius", "chat-sessions", `${mirrorId}.json`);
705352
+ if (existsSync130(mirrorPath)) unlinkSync28(mirrorPath);
705247
705353
  if (existsSync130(indexPath)) {
705248
705354
  let index = JSON.parse(readFileSync107(indexPath, "utf-8"));
705249
705355
  index = index.filter((s2) => s2.id !== sessionId);
@@ -705481,6 +705587,7 @@ var init_omnius_directory = __esm({
705481
705587
  "packages/cli/src/tui/omnius-directory.ts"() {
705482
705588
  init_dist5();
705483
705589
  init_task_complete_box();
705590
+ init_session_quality();
705484
705591
  OMNIUS_DIR2 = ".omnius";
705485
705592
  LEGACY_DIRS = [".oa", ".open-agents"];
705486
705593
  SUBDIRS = ["memory", "index", "context", "history", "notes", "embedded", "provenance", "tools", "dreams"];
@@ -705596,29 +705703,12 @@ var init_omnius_directory = __esm({
705596
705703
  });
705597
705704
 
705598
705705
  // packages/cli/src/api/session-summary.ts
705599
- function isNoiseLine(line) {
705600
- const t2 = line.trim();
705601
- if (t2.length < 3) return true;
705602
- const core = t2.replace(TUI_MARKER_PREFIX, "").trim();
705603
- if (core.length < 3) return true;
705604
- return /^(nexus|rest api|voice feedback|clone ref|connecting to|connected|http:\/\/|https:\/\/|last task:|\(manual save\)|good morning|good evening|good afternoon|cannot reach|could not|unable to|warning:|error:|loaded tui session|session restored|restored previous|using (expanded )?context model|using model|tui session\b|chat tui:|previous session found|context (auto-)?restored|context restored|recovered session|\[vram|vram |model_info|kv |arch[ -]capped)/i.test(core);
705605
- }
705606
- function firstMeaningfulLine(transcript) {
705607
- const lines = transcript.split("\n").map((r2) => r2.replace(ANSI_RE, "").trim());
705608
- for (const line of lines) {
705609
- if (/^[▹▸►❯>]\s+\S/.test(line) && !isNoiseLine(line)) return line;
705610
- }
705611
- for (const line of lines) {
705612
- if (line && !isNoiseLine(line)) return line;
705613
- }
705614
- return "";
705615
- }
705616
705706
  function clamp9(value2, max) {
705617
705707
  const v = value2.replace(/\s+/g, " ").trim();
705618
705708
  return v.length > max ? v.slice(0, max - 1).trimEnd() + "…" : v;
705619
705709
  }
705620
705710
  function deterministicSummary(transcript) {
705621
- const first2 = firstMeaningfulLine(transcript);
705711
+ const first2 = firstMeaningfulSessionLine(transcript);
705622
705712
  if (!first2) return { title: "Untitled session", summary: "Empty session." };
705623
705713
  const cleaned = first2.replace(TUI_MARKER_PREFIX, "").replace(/^[>›$#\s]+/, "").trim();
705624
705714
  const title = clamp9(cleaned, TITLE_MAX);
@@ -705640,7 +705730,7 @@ function parseSummaryReply(content) {
705640
705730
  }
705641
705731
  async function generateSessionSummary(args) {
705642
705732
  const fallback = deterministicSummary(args.transcript);
705643
- const transcript = (args.transcript || "").replace(ANSI_RE, "").trim();
705733
+ const transcript = (args.transcript || "").replace(ANSI_RE2, "").trim();
705644
705734
  if (!transcript || !args.config.model || !args.config.backendUrl) return fallback;
705645
705735
  try {
705646
705736
  const url = normalizeBaseUrl(args.config.backendUrl) + "/v1/chat/completions";
@@ -705673,8 +705763,9 @@ async function generateSessionSummary(args) {
705673
705763
  const content = data?.choices?.[0]?.message?.content ?? "";
705674
705764
  const parsed = parseSummaryReply(content);
705675
705765
  if (!parsed) return fallback;
705766
+ const parsedTitle = clamp9(parsed.title, TITLE_MAX);
705676
705767
  return {
705677
- title: clamp9(parsed.title, TITLE_MAX) || fallback.title,
705768
+ title: parsedTitle && !isSessionNoiseText(parsedTitle) ? parsedTitle : fallback.title,
705678
705769
  summary: clamp9(parsed.summary, SUMMARY_MAX) || fallback.summary
705679
705770
  };
705680
705771
  } catch {
@@ -705685,7 +705776,7 @@ async function ensureSessionSummary(args) {
705685
705776
  const entry = listSessions(args.repoRoot).find(
705686
705777
  (e2) => e2.id === args.sessionId
705687
705778
  );
705688
- if (entry?.aiTitle && entry.aiSummary && !args.force) {
705779
+ if (entry?.aiTitle && entry.aiSummary && !isSessionNoiseText(entry.aiTitle) && !args.force) {
705689
705780
  return { title: entry.aiTitle, summary: entry.aiSummary };
705690
705781
  }
705691
705782
  const lines = loadSessionHistory(args.repoRoot, args.sessionId);
@@ -705711,19 +705802,20 @@ async function ensureSessionSummary(args) {
705711
705802
  _inflightSummaries.delete(key);
705712
705803
  }
705713
705804
  }
705714
- function sessionDisplayTitle(entry) {
705715
- if (entry.aiTitle && entry.aiTitle.trim()) return entry.aiTitle.trim();
705716
- return deterministicSummary(entry.name || "").title;
705805
+ function sessionDisplayTitle(entry, transcript = "") {
705806
+ if (entry.aiTitle && !isSessionNoiseText(entry.aiTitle)) return entry.aiTitle.trim();
705807
+ return deterministicSummary(transcript || [entry.name, entry.description].filter(Boolean).join("\n")).title;
705717
705808
  }
705718
- var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE, TUI_MARKER_PREFIX, _inflightSummaries;
705809
+ var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE2, TUI_MARKER_PREFIX, _inflightSummaries;
705719
705810
  var init_session_summary = __esm({
705720
705811
  "packages/cli/src/api/session-summary.ts"() {
705721
705812
  init_dist6();
705722
705813
  init_omnius_directory();
705814
+ init_session_quality();
705723
705815
  TITLE_MAX = 56;
705724
705816
  SUMMARY_MAX = 160;
705725
705817
  TRANSCRIPT_CHARS = 6e3;
705726
- ANSI_RE = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
705818
+ ANSI_RE2 = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
705727
705819
  TUI_MARKER_PREFIX = /^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|/\\.\s]+/;
705728
705820
  _inflightSummaries = /* @__PURE__ */ new Set();
705729
705821
  }
@@ -706617,7 +706709,7 @@ function paintBlockBorder(glyphs, stage2, phase, truecolor, startCol = 0) {
706617
706709
  return `${out}\x1B[0m`;
706618
706710
  }
706619
706711
  function sanitizeSubAgentActivity(value2) {
706620
- return value2.replace(ANSI_RE2, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_ACTIVITY_CHARS);
706712
+ return value2.replace(ANSI_RE3, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_ACTIVITY_CHARS);
706621
706713
  }
706622
706714
  function appendSubAgentActivity(entry, value2, maxLines = 3) {
706623
706715
  const line = sanitizeSubAgentActivity(value2);
@@ -706671,7 +706763,7 @@ function contentRow(value2, width, stage2, phase, truecolor) {
706671
706763
  return `${left} ${fit2(value2, width)} ${right}`;
706672
706764
  }
706673
706765
  function fit2(value2, width) {
706674
- const plain = value2.replace(ANSI_RE2, "").replace(/\s+$/g, "");
706766
+ const plain = value2.replace(ANSI_RE3, "").replace(/\s+$/g, "");
706675
706767
  const chars = Array.from(plain);
706676
706768
  if (chars.length > width) {
706677
706769
  return `${chars.slice(0, Math.max(0, width - 1)).join("")}…`;
@@ -706691,11 +706783,11 @@ function statusIcon(status) {
706691
706783
  return "●";
706692
706784
  }
706693
706785
  }
706694
- var ANSI_RE2, MAX_PREVIEW_AGENTS, MAX_ACTIVITY_CHARS, STAGE_FALLBACK_COLOR, BORDER_GRADIENT_SEG;
706786
+ var ANSI_RE3, MAX_PREVIEW_AGENTS, MAX_ACTIVITY_CHARS, STAGE_FALLBACK_COLOR, BORDER_GRADIENT_SEG;
706695
706787
  var init_sub_agent_live_block = __esm({
706696
706788
  "packages/cli/src/tui/sub-agent-live-block.ts"() {
706697
706789
  init_stageIndicator();
706698
- ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706790
+ ANSI_RE3 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706699
706791
  MAX_PREVIEW_AGENTS = 4;
706700
706792
  MAX_ACTIVITY_CHARS = 220;
706701
706793
  STAGE_FALLBACK_COLOR = {
@@ -706815,19 +706907,19 @@ function row(value2, width, stage2, phase, truecolor) {
706815
706907
  return `${left} ${fit3(value2, width)} ${right}`;
706816
706908
  }
706817
706909
  function fit3(value2, width) {
706818
- const clean7 = value2.replace(ANSI_RE3, "").replace(/\s+/g, " ").trim();
706910
+ const clean7 = value2.replace(ANSI_RE4, "").replace(/\s+/g, " ").trim();
706819
706911
  const chars = Array.from(clean7);
706820
706912
  if (chars.length > width) {
706821
706913
  return `${chars.slice(0, Math.max(0, width - 1)).join("")}…`;
706822
706914
  }
706823
706915
  return clean7 + " ".repeat(Math.max(0, width - chars.length));
706824
706916
  }
706825
- var ANSI_RE3;
706917
+ var ANSI_RE4;
706826
706918
  var init_trajectory_live_block = __esm({
706827
706919
  "packages/cli/src/tui/trajectory-live-block.ts"() {
706828
706920
  init_stageIndicator();
706829
706921
  init_sub_agent_live_block();
706830
- ANSI_RE3 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706922
+ ANSI_RE4 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706831
706923
  }
706832
706924
  });
706833
706925
 
@@ -719818,7 +719910,7 @@ export PATH="${binDir}:$PATH" # Added by omnius for nvim
719818
719910
  } catch {
719819
719911
  }
719820
719912
  }
719821
- var execAsync2, OMNIUS_FIRST_RUN_BANNER, ANSI_RE4, visibleLen2, SETUP_MODEL_VARIANTS, _toolSupportCache, EXPANDED_VARIANT_MIN_NUM_CTX, _cloudflaredInstallPromise;
719913
+ var execAsync2, OMNIUS_FIRST_RUN_BANNER, ANSI_RE5, visibleLen2, SETUP_MODEL_VARIANTS, _toolSupportCache, EXPANDED_VARIANT_MIN_NUM_CTX, _cloudflaredInstallPromise;
719822
719914
  var init_setup = __esm({
719823
719915
  "packages/cli/src/tui/setup.ts"() {
719824
719916
  init_model_picker();
@@ -719838,8 +719930,8 @@ var init_setup = __esm({
719838
719930
  "░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ ",
719839
719931
  " ░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓██████▓▒░░▒▓███████▓▒░ "
719840
719932
  ].join("\n");
719841
- ANSI_RE4 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
719842
- visibleLen2 = (value2) => Array.from(value2.replace(ANSI_RE4, "")).length;
719933
+ ANSI_RE5 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
719934
+ visibleLen2 = (value2) => Array.from(value2.replace(ANSI_RE5, "")).length;
719843
719935
  SETUP_MODEL_VARIANTS = [
719844
719936
  { tag: "robit/ornith:9b", sizeGB: 6.6, label: "9B params (6.6 GB) - recommended minimum", cloud: false },
719845
719937
  { tag: "robit/ornith:35b", sizeGB: 24, label: "35B params (24 GB) - recommended on 32GB+ unified memory/VRAM", cloud: false }
@@ -749568,21 +749660,13 @@ function stripAnsi6(text2) {
749568
749660
  return String(text2 || "").replace(/\u001b\[[0-9;]*[a-zA-Z]/g, "");
749569
749661
  }
749570
749662
  function cleanSessionDisplayLine(line) {
749571
- return stripAnsi6(line).replace(/^[>❯▹∙•*+\-\s]+/, "").replace(/^(?:User|Assistant|You|Open Agent|Omnius)\s*:\s*/i, "").replace(/\s+/g, " ").trim();
749663
+ return normalizeSessionDisplayText(stripAnsi6(line));
749572
749664
  }
749573
749665
  function isNoisySessionDisplayLine(line) {
749574
- const clean7 = cleanSessionDisplayLine(line || "");
749575
- if (!clean7) return true;
749576
- return /^(?:Previous session found|REST API:|Nexus P2P network connected|No context to restore|Starting fresh|Use \/endpoint|Knowledge graph:|Zettelkasten:|Episodes captured:|Current OMNIUS_HOST:|Loaded TUI session|General session$|Chat tui:sess|\[Imported TUI session transcript\]|Title:|Description:|Project root:|i\s+)/i.test(clean7);
749666
+ return isSessionNoiseText(line || "") || /^(?:\[Imported TUI session transcript\]|Title:|Description:|Project root:|i\s+)/i.test(cleanSessionDisplayLine(line || ""));
749577
749667
  }
749578
749668
  function bestSessionDisplayLine(text2) {
749579
- const lines = stripAnsi6(text2).split(/\r?\n/);
749580
- for (const line of lines) {
749581
- const clean7 = cleanSessionDisplayLine(line);
749582
- if (!isNoisySessionDisplayLine(clean7)) return clean7;
749583
- }
749584
- const fallback = stripAnsi6(text2).replace(/\s+/g, " ").trim();
749585
- return isNoisySessionDisplayLine(fallback) ? "" : fallback;
749669
+ return firstMeaningfulSessionLine(stripAnsi6(text2));
749586
749670
  }
749587
749671
  function makeTitle(text2) {
749588
749672
  const clean7 = bestSessionDisplayLine(text2);
@@ -749796,7 +749880,7 @@ function importTranscriptSession(opts) {
749796
749880
  "",
749797
749881
  cappedTranscript || "(empty transcript)"
749798
749882
  ].filter(Boolean).join("\n");
749799
- const visibleNotice = `Loaded TUI session "${title}". Its transcript is attached as context for this chat.`;
749883
+ const isImportedNotice = (message2) => message2.role === "assistant" && /^Loaded TUI session ".*"\. Its transcript is attached as context for this chat\.$/.test(message2.content);
749800
749884
  const existing = lookupSession(id2);
749801
749885
  if (existing) {
749802
749886
  existing.projectRoot = projectRoot;
@@ -749806,14 +749890,12 @@ function importTranscriptSession(opts) {
749806
749890
  existing.preview = preview;
749807
749891
  existing.transcript = cappedTranscript || "(empty transcript)";
749808
749892
  existing.lastActivity = opts.updatedAt ?? Date.now();
749893
+ existing.messages = existing.messages.filter((message2) => !isImportedNotice(message2));
749809
749894
  const idx = existing.messages.findIndex(
749810
749895
  (m2) => m2.role === "system" && m2.content.startsWith("[Imported TUI session transcript]")
749811
749896
  );
749812
749897
  if (idx >= 0) existing.messages[idx] = { role: "system", content: importedContext };
749813
749898
  else existing.messages.splice(1, 0, { role: "system", content: importedContext });
749814
- if (!existing.messages.some((m2) => m2.role === "assistant" && m2.content === visibleNotice)) {
749815
- existing.messages.push({ role: "assistant", content: visibleNotice });
749816
- }
749817
749899
  persistSession(existing);
749818
749900
  return existing;
749819
749901
  }
@@ -749822,8 +749904,7 @@ function importTranscriptSession(opts) {
749822
749904
  id: id2,
749823
749905
  messages: [
749824
749906
  { role: "system", content: buildSystemPrompt(projectRoot) },
749825
- { role: "system", content: importedContext },
749826
- { role: "assistant", content: visibleNotice }
749907
+ { role: "system", content: importedContext }
749827
749908
  ],
749828
749909
  model: opts.model || "unknown",
749829
749910
  createdAt: opts.createdAt ?? opts.updatedAt ?? now2,
@@ -749968,6 +750049,9 @@ function listSessions2(opts = {}) {
749968
750049
  if (!root) return true;
749969
750050
  if (!s2.projectRoot) return !!opts.includeUnscoped;
749970
750051
  return normalizeRoot(s2.projectRoot) === root;
750052
+ }).filter((s2) => {
750053
+ const authoredTurns = s2.messages.filter((message2) => message2.role === "user").map((message2) => message2.content).join("\n");
750054
+ return hasMeaningfulSessionContent(authoredTurns) || hasMeaningfulSessionContent(s2.transcript || "") || !!s2.title && !isNoisySessionDisplayLine(s2.title);
749971
750055
  }).sort((a2, b) => b.lastActivity - a2.lastActivity).map((s2) => ({
749972
750056
  id: s2.id,
749973
750057
  model: s2.model,
@@ -750076,6 +750160,7 @@ var sessions2, inFlight, SESSION_TTL_MS, INFERENCE_ROLES, PARTIAL_TAIL_BUDGET;
750076
750160
  var init_chat_session = __esm({
750077
750161
  "packages/cli/src/api/chat-session.ts"() {
750078
750162
  init_secret_redactor();
750163
+ init_session_quality();
750079
750164
  sessions2 = /* @__PURE__ */ new Map();
750080
750165
  inFlight = /* @__PURE__ */ new Map();
750081
750166
  SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -796406,8 +796491,10 @@ async function loadServerPrefs() {
796406
796491
  function isNoisyChatSessionText(text) {
796407
796492
  const clean = String(text || '').replace(/\\s+/g, ' ').trim();
796408
796493
  if (!clean) return true;
796409
- const t = clean.replace(/^[>❯▹∙•\\-\\s]+/, '');
796410
- return /^(Previous session found|REST API:|Nexus P2P network connected|No context to restore|Starting fresh|Use \\/endpoint|General session$|Loaded TUI session|Chat tui:sess)/i.test(t);
796494
+ const raw = clean.replace(/^[>❯▹∙•\\-\\s]+/, '');
796495
+ if (/^\\/?(?:q|quit|exit)$/i.test(raw)) return true;
796496
+ const t = raw.replace(/^\\/+/, '');
796497
+ return /^(Previous session found|REST API:|Nexus P2P network connected|No context to restore|Starting fresh|Use \\/endpoint|General session$|Loaded TUI session|Chat tui:sess|Last task:|\\(manual save\\)|Using (?:expanded )?context model|Using model|TUI session\\b|\\[?NEW_TASK_INTAKE\\b|User is asking\\b|Context (?:auto-)?restored|Recovered session)/i.test(t);
796411
796498
  }
796412
796499
 
796413
796500
  function isGenericChatTitle(id, title) {
@@ -796424,10 +796511,11 @@ function sessionDisplayTitle(id, session) {
796424
796511
  if (!isGenericChatTitle(id, candidate)) return String(candidate).trim();
796425
796512
  }
796426
796513
  if (Array.isArray(s.messages)) {
796427
- for (let i = s.messages.length - 1; i >= 0; i--) {
796428
- const msg = s.messages[i];
796429
- const content = msg && typeof msg.content === 'string' ? msg.content : '';
796430
- if (!isGenericChatTitle(id, content)) return content.replace(/\\s+/g, ' ').trim().slice(0, 72);
796514
+ for (const preferredRole of ['user', 'assistant']) {
796515
+ for (const msg of s.messages) {
796516
+ const content = msg && msg.role === preferredRole && typeof msg.content === 'string' ? msg.content : '';
796517
+ if (!isGenericChatTitle(id, content)) return content.replace(/\\s+/g, ' ').trim().slice(0, 72);
796518
+ }
796431
796519
  }
796432
796520
  }
796433
796521
  return String(id || '').startsWith('tui:') ? 'TUI session ' + String(id).slice(4, 16) : 'Chat ' + String(id || '').slice(0, 8);
@@ -796441,6 +796529,14 @@ function sessionDisplayPreview(session) {
796441
796529
  return '';
796442
796530
  }
796443
796531
 
796532
+ function isEligibleChatSession(id, session) {
796533
+ const s = session || {};
796534
+ if (![s.title, s.name, s.preview].every(candidate => isGenericChatTitle(id, candidate))) return true;
796535
+ return Array.isArray(s.messages) && s.messages.some(message =>
796536
+ message && message.role === 'user' && !isNoisyChatSessionText(message.content)
796537
+ );
796538
+ }
796539
+
796444
796540
  async function loadServerChatSessions() {
796445
796541
  const root = $currentProject.get()?.root || '';
796446
796542
  if (!root) return;
@@ -796449,23 +796545,30 @@ async function loadServerChatSessions() {
796449
796545
  if (!r.ok) return;
796450
796546
  const data = await r.json();
796451
796547
  const local = loadScopedSessions();
796452
- const merged = { ...local };
796548
+ // A successful canonical load retires local TUI summaries that the server
796549
+ // no longer considers sessions. Keep eligible browser-native chats so an
796550
+ // unsent/local conversation is never lost during reconciliation.
796551
+ const merged = {};
796552
+ for (const [id, session] of Object.entries(local)) {
796553
+ const isTui = String(id).startsWith('tui:') || session?.source === 'tui';
796554
+ if (!isTui && isEligibleChatSession(id, session)) merged[id] = session;
796555
+ }
796453
796556
  for (const sess of (data.sessions || [])) {
796454
796557
  if (!sess || !sess.id) continue;
796455
- const existing = merged[sess.id] || {};
796558
+ const existing = local[sess.id] || merged[sess.id] || {};
796456
796559
  const serverTitle = sess.title || sess.preview || '';
796457
796560
  const existingTitle = existing.title || existing.name || '';
796458
- const title = !isGenericChatTitle(sess.id, existingTitle)
796459
- ? existingTitle
796460
- : (!isGenericChatTitle(sess.id, serverTitle) ? serverTitle : sessionDisplayTitle(sess.id, sess));
796461
- const preview = !isNoisyChatSessionText(existing.preview)
796462
- ? existing.preview
796463
- : (!isNoisyChatSessionText(sess.preview) ? sess.preview : sessionDisplayPreview(sess));
796561
+ const title = !isGenericChatTitle(sess.id, serverTitle)
796562
+ ? serverTitle
796563
+ : (!isGenericChatTitle(sess.id, existingTitle) ? existingTitle : sessionDisplayTitle(sess.id, sess));
796564
+ const preview = !isNoisyChatSessionText(sess.preview)
796565
+ ? sess.preview
796566
+ : (!isNoisyChatSessionText(existing.preview) ? existing.preview : sessionDisplayPreview(sess));
796464
796567
  merged[sess.id] = {
796465
796568
  ...existing,
796466
796569
  id: sess.id,
796467
796570
  title,
796468
- name: !isGenericChatTitle(sess.id, existing.name) ? existing.name : title,
796571
+ name: title,
796469
796572
  preview,
796470
796573
  model: existing.model || sess.model || '',
796471
796574
  source: sess.source || existing.source || 'web',
@@ -796474,7 +796577,14 @@ async function loadServerChatSessions() {
796474
796577
  messages: existing.messages || [],
796475
796578
  };
796476
796579
  }
796580
+ saveScopedSessions(merged);
796477
796581
  $chatSessions.set(merged);
796582
+ if (chatSessionId && !merged[chatSessionId]) {
796583
+ $chatSessionId.set(null);
796584
+ messages = [];
796585
+ const conversation = document.getElementById('conversation');
796586
+ if (conversation) conversation.innerHTML = '';
796587
+ }
796478
796588
  updateSessionSelect();
796479
796589
  } catch {}
796480
796590
  }
@@ -798918,7 +799028,7 @@ function chatSessionFromRouteSearch(search) {
798918
799028
  }
798919
799029
  function syncRouteForTab(tab, replace) {
798920
799030
  const path = routePathForTab(tab);
798921
- const query = tab === 'chat' && chatSessionId ? '?' + encodeURIComponent(chatSessionId) : '';
799031
+ const query = tab === 'chat' && chatSessionId ? '?session=' + encodeURIComponent(chatSessionId) : '';
798922
799032
  const next = path + query;
798923
799033
  if ((location.pathname + location.search) === next) return;
798924
799034
  const method = replace ? 'replaceState' : 'pushState';
@@ -799519,6 +799629,7 @@ window.addEventListener('popstate', () => {
799519
799629
  if (tab === 'chat') {
799520
799630
  const sid = chatSessionFromRouteSearch(location.search);
799521
799631
  if (sid && sid !== chatSessionId) switchSession(sid);
799632
+ else if (!sid && chatSessionId) switchSession('');
799522
799633
  }
799523
799634
  switchTab(tab, { fromRoute: true });
799524
799635
  });
@@ -800418,6 +800529,7 @@ function updateSessionSelect() {
800418
800529
  const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800419
800530
  const saved = { ...storeSessions, ...loadScopedSessions() };
800420
800531
  const entries = Object.entries(saved)
800532
+ .filter(([id, session]) => isEligibleChatSession(id, session))
800421
800533
  .sort((a, b) => (b[1].updatedAt || '').localeCompare(a[1].updatedAt || ''))
800422
800534
  .slice(0, 20);
800423
800535
  for (const sel of targets) {
@@ -800437,8 +800549,8 @@ function updateSessionSelect() {
800437
800549
  // It delegates to the existing switchSession() so chat history restoration
800438
800550
  // still works exactly as before.
800439
800551
  function switchChatSession(id) {
800440
- switchSession(id);
800441
800552
  switchTab('chat', { replaceRoute: true });
800553
+ switchSession(id);
800442
800554
  }
800443
800555
  function newChatSession() {
800444
800556
  switchSession('');
@@ -800615,7 +800727,8 @@ function switchSession(id) {
800615
800727
  return;
800616
800728
  }
800617
800729
  const saved = loadScopedSessions();
800618
- const s = saved[id];
800730
+ const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800731
+ const s = saved[id] || storeSessions[id];
800619
800732
  if (s) {
800620
800733
  chatSessionId = id;
800621
800734
  messages = s.messages || [];
@@ -800667,6 +800780,9 @@ function switchSession(id) {
800667
800780
  updateSessionSelect();
800668
800781
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800669
800782
  try { refreshTodos(id); } catch {}
800783
+ // Local entries are only a fast paint. Always re-fetch the canonical
800784
+ // session so summary-only or stale caches cannot suppress real history.
800785
+ void restoreChatSession();
800670
800786
  } else {
800671
800787
  // Server-backed sessions (including imported TUI sessions) may only be
800672
800788
  // present in $chatSessions, not localStorage. Activate and let the daemon
@@ -800675,7 +800791,7 @@ function switchSession(id) {
800675
800791
  syncRouteForTab('chat', true);
800676
800792
  updateSessionSelect();
800677
800793
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800678
- restoreChatSession();
800794
+ void restoreChatSession();
800679
800795
  }
800680
800796
  }
800681
800797
 
@@ -802332,6 +802448,7 @@ function renderRecoveredTranscript(raw, conv, title) {
802332
802448
  let mode = null; // 'user' | 'assistant'
802333
802449
  let buf = [];
802334
802450
  let toolCount = 0;
802451
+ let inToolBox = false;
802335
802452
  const out = []; // [{role, text}] or {tools:n}
802336
802453
  const flush = () => {
802337
802454
  const text = buf.join(NL).trim();
@@ -802346,22 +802463,29 @@ function renderRecoveredTranscript(raw, conv, title) {
802346
802463
  line = line.replace(sentRe, '');
802347
802464
  const t = line.trim();
802348
802465
  if (!t) continue;
802349
- // Skip pure box-border lines from expanded tool/task boxes (╭─╮ ╰─╯ etc.).
802350
- if (/^[─-╿s]+$/.test(t)) continue;
802351
- if (/^[▹>]s?/.test(t)) { // user prompt
802466
+ // Tool/task panels also use │. Track their box boundaries so their labels,
802467
+ // commands, and output cannot be mistaken for assistant prose.
802468
+ if (/^[╭┌]/.test(t)) { flush(); inToolBox = true; continue; }
802469
+ if (/^[╰└]/.test(t)) { inToolBox = false; continue; }
802470
+ if (inToolBox) continue;
802471
+ if (/^[─━═╿\\s]+$/.test(t)) continue;
802472
+ const userMatch = t.match(/^(?:[▹❯>]\\s*|(?:User|You)\\s*:\\s*)(.+)$/i);
802473
+ if (userMatch) {
802474
+ if (isNoisyChatSessionText(userMatch[1])) { flush(); mode = null; continue; }
802352
802475
  flush(); if (toolCount) { out.push({ tools: toolCount }); toolCount = 0; }
802353
- out.push({ role: 'user', text: t.replace(/^[▹>]s?/, '') }); mode = null; continue;
802476
+ mode = 'user'; buf.push(userMatch[1]); continue;
802354
802477
  }
802355
- if (/^│/.test(t)) { // assistant / box content
802478
+ const assistantMatch = t.match(/^(?:(?:Assistant|Open Agent|Omnius)\\s*:\\s*|│\\s?)(.*)$/i);
802479
+ if (assistantMatch) {
802356
802480
  if (mode !== 'assistant') { flush(); mode = 'assistant'; }
802357
- buf.push(t.replace(/^│s?/, '').replace(/s?│$/, '')); continue; // strip both borders
802481
+ buf.push(assistantMatch[1].replace(/\\s?│$/, '')); continue;
802358
802482
  }
802359
- if (/^[∙!]/.test(t)) continue; // status / internal noise — drop
802360
- if (mode === 'assistant') buf.push(t); // continuation
802483
+ if (/^[∙!EW⚠]/.test(t) || isNoisyChatSessionText(t)) continue;
802484
+ if (mode) buf.push(t); // wrapped authored continuation
802361
802485
  }
802362
802486
  flush(); if (toolCount) out.push({ tools: toolCount });
802363
802487
 
802364
- if (out.length === 0) return; // nothing meaningful — show nothing rather than garbage
802488
+ if (out.length === 0) return []; // nothing meaningful — show nothing rather than garbage
802365
802489
  const note = document.createElement('div');
802366
802490
  note.style.cssText = 'font-size:0.6rem;color:var(--color-fg-faint);margin:4px 0 8px;text-align:center';
802367
802491
  note.textContent = 'recovered session' + (title ? ' — ' + title : '');
@@ -802376,6 +802500,9 @@ function renderRecoveredTranscript(raw, conv, title) {
802376
802500
  addMessage(item.role, item.text);
802377
802501
  }
802378
802502
  }
802503
+ return out
802504
+ .filter(item => !item.tools && (item.role === 'user' || item.role === 'assistant'))
802505
+ .map(item => ({ role: item.role, content: item.text }));
802379
802506
  }
802380
802507
  window.renderRecoveredTranscript = renderRecoveredTranscript;
802381
802508
 
@@ -802389,6 +802516,7 @@ async function restoreChatSession() {
802389
802516
  const root = $currentProject.get()?.root || '';
802390
802517
  const query = root ? '?root=' + encodeURIComponent(root) : '';
802391
802518
  const r = await fetch('/v1/chat/sessions/' + encodeURIComponent(sid) + query, { headers: headers() });
802519
+ if (chatSessionId !== sid) return;
802392
802520
  if (!r.ok) {
802393
802521
  if (r.status === 404) {
802394
802522
  // Server lost the session (e.g. daemon restart) — clear via store
@@ -802398,16 +802526,17 @@ async function restoreChatSession() {
802398
802526
  return;
802399
802527
  }
802400
802528
  const data = await r.json();
802529
+ if (chatSessionId !== sid) return;
802401
802530
  if (!data || !data.id) return;
802402
802531
  chatSessionId = data.id;
802403
802532
  window.currentSessionId = data.id;
802404
802533
  // Keep the in-memory messages array clean (only user/assistant) so
802405
802534
  // the next inference call sees a valid conversation. Tool events
802406
802535
  // are still rendered into the DOM via the dropdown helpers below.
802407
- const allMessages = data.messages || [];
802408
- messages = allMessages
802409
- .filter(m => m.role === 'user' || m.role === 'assistant')
802410
- .map(m => ({ role: m.role, content: m.content }));
802536
+ const allMessages = (data.messages || []).filter(m => !(
802537
+ m.role === 'assistant' && /^Loaded TUI session ".*"\\. Its transcript is attached as context for this chat\\.$/.test(String(m.content || ''))
802538
+ ));
802539
+ let recoveredMessages = [];
802411
802540
  const conv = document.getElementById('conversation');
802412
802541
  if (conv) {
802413
802542
  conv.innerHTML = '';
@@ -802415,15 +802544,15 @@ async function restoreChatSession() {
802415
802544
  // decisions, sub-agent activity — everything that scrolled past in the
802416
802545
  // TUI) as a visible, scrollable block at the top so opening the session
802417
802546
  // actually shows what happened, instead of just a "loaded" notice.
802418
- if (data.transcript && String(data.transcript).trim() && data.transcript !== '(empty transcript)'
802419
- && (!allMessages || allMessages.length === 0)) {
802547
+ if (data.source === 'tui' && data.transcript && String(data.transcript).trim()
802548
+ && data.transcript !== '(empty transcript)') {
802420
802549
  // TUI/raw transcript: PARSE the rendered-TUI scrollback into real chat
802421
802550
  // bubbles instead of dumping the raw log. The persisted log carries
802422
802551
  // DYNBLOCK:<id> sentinels (dynamic blocks expanded only at TUI
802423
802552
  // paint time — no content here) plus chrome (▹ user, │ assistant,
802424
802553
  // ∙ status, ! internal). We strip the dead sentinels + noise and emit
802425
802554
  // user/assistant messages so it reads like a GUI chat.
802426
- renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802555
+ recoveredMessages = renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802427
802556
  }
802428
802557
  // WO-CHAT-RESUME-TOOLS — replay the FULL intermediate flow on
802429
802558
  // restore: user/assistant text bubbles AND tool_call/tool_result
@@ -802471,6 +802600,12 @@ async function restoreChatSession() {
802471
802600
  }
802472
802601
  }
802473
802602
  }
802603
+ messages = [
802604
+ ...recoveredMessages,
802605
+ ...allMessages
802606
+ .filter(m => m.role === 'user' || m.role === 'assistant')
802607
+ .map(m => ({ role: m.role, content: m.content })),
802608
+ ];
802474
802609
  try {
802475
802610
  const saved = loadScopedSessions();
802476
802611
  saved[data.id] = {
@@ -803632,7 +803767,7 @@ function _renderSidebarChats(filter) {
803632
803767
  _renderSidebarFoldersToolbar();
803633
803768
 
803634
803769
  const sessions = (typeof $chatSessions !== 'undefined' && $chatSessions.get) ? $chatSessions.get() : {};
803635
- const allIds = Object.keys(sessions || {});
803770
+ const allIds = Object.keys(sessions || {}).filter(id => isEligibleChatSession(id, sessions[id]));
803636
803771
  const q = (filter || '').trim().toLowerCase();
803637
803772
  const activeId = chatSessionId || (($chatSessionId.get && $chatSessionId.get()) || null);
803638
803773
 
@@ -803644,8 +803779,8 @@ function _renderSidebarChats(filter) {
803644
803779
  // Sort helper
803645
803780
  const sortByRecency = (a, b) => {
803646
803781
  const sa = sessions[a] || {}, sb = sessions[b] || {};
803647
- const ta = sa.updated_at || sa.created_at || a;
803648
- const tb = sb.updated_at || sb.created_at || b;
803782
+ const ta = sa.updatedAt || sa.updated_at || sa.createdAt || sa.created_at || a;
803783
+ const tb = sb.updatedAt || sb.updated_at || sb.createdAt || sb.created_at || b;
803649
803784
  return String(tb).localeCompare(String(ta));
803650
803785
  };
803651
803786
  allIds.sort(sortByRecency);
@@ -803679,7 +803814,7 @@ function _renderSidebarChats(filter) {
803679
803814
  const cls = 'sb-chat' + (id === activeId ? ' active' : '');
803680
803815
  const safeId = String(id).replace(/'/g, "\\\\'");
803681
803816
  const safeTitle = title.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
803682
- return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchSession(\\'' + safeId + '\\'); switchTab(\\'chat\\', { replaceRoute: true })" title="' + safeTitle + '">' +
803817
+ return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchChatSession(\\'' + safeId + '\\')" title="' + safeTitle + '">' +
803683
803818
  '<span class="sb-chat-title">' + safeTitle + '</span>' +
803684
803819
  '<button class="sb-chat-menu" onclick="_showChatRowMenu(event,\\'' + safeId + '\\')" title="More">&#x22EE;</button>' +
803685
803820
  '</div>';
@@ -814360,17 +814495,30 @@ ${historyLines}
814360
814495
  if (!checkAuth(req3, res, "read")) return;
814361
814496
  const queriedRoot = urlObj.searchParams.get("root");
814362
814497
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814363
- const sessions3 = listSessions2({ projectRoot: targetRoot });
814364
814498
  const includeTui = urlObj.searchParams.get("include_tui") !== "0";
814499
+ const tuiHistory = includeTui && targetRoot ? listSessions(targetRoot) : [];
814500
+ const canonicalTuiIds = new Set(tuiHistory.map((entry) => `tui:${entry.id}`));
814501
+ const sessions3 = listSessions2({ projectRoot: targetRoot }).filter((session) => {
814502
+ const isTui = session.source === "tui" || session.id.startsWith("tui:");
814503
+ return !isTui || includeTui && canonicalTuiIds.has(session.id);
814504
+ });
814365
814505
  if (includeTui && targetRoot) {
814366
- const seen = new Set(sessions3.map((s2) => s2.id));
814506
+ const byId = new Map(sessions3.map((session) => [session.id, session]));
814507
+ const seenFingerprints = /* @__PURE__ */ new Set();
814367
814508
  const cfg = loadConfig();
814368
814509
  const needSummary = [];
814369
- for (const t2 of listSessions(targetRoot)) {
814510
+ for (const t2 of tuiHistory) {
814370
814511
  const id2 = `tui:${t2.id}`;
814371
- if (seen.has(id2)) continue;
814372
- if (!t2.aiTitle) needSummary.push(t2.id);
814373
- sessions3.push({
814512
+ const transcriptLines = loadSessionHistory(targetRoot, t2.id) ?? [];
814513
+ const transcript = transcriptLines.join("\n");
814514
+ const fingerprint3 = normalizedSessionFingerprint(transcript);
814515
+ if (fingerprint3 && seenFingerprints.has(fingerprint3)) continue;
814516
+ if (fingerprint3) seenFingerprints.add(fingerprint3);
814517
+ const fallback = deterministicSummary(transcript);
814518
+ const title = sessionDisplayTitle(t2, transcript);
814519
+ const preview = t2.aiSummary && !isSessionNoiseText(t2.aiSummary) ? t2.aiSummary : fallback.summary !== "Empty session." ? fallback.summary : t2.description;
814520
+ if (!t2.aiTitle || isSessionNoiseText(t2.aiTitle)) needSummary.push(t2.id);
814521
+ const canonical3 = {
814374
814522
  id: id2,
814375
814523
  model: t2.model,
814376
814524
  messages: 0,
@@ -814379,9 +814527,15 @@ ${historyLines}
814379
814527
  lastActivity: t2.updatedAt,
814380
814528
  projectRoot: targetRoot,
814381
814529
  source: "tui",
814382
- title: sessionDisplayTitle(t2),
814383
- preview: t2.aiSummary || t2.description
814384
- });
814530
+ title,
814531
+ preview
814532
+ };
814533
+ const existing = byId.get(id2);
814534
+ if (existing) Object.assign(existing, canonical3);
814535
+ else {
814536
+ sessions3.push(canonical3);
814537
+ byId.set(id2, canonical3);
814538
+ }
814385
814539
  }
814386
814540
  for (const sid of needSummary.slice(0, 6)) {
814387
814541
  void ensureSessionSummary({
@@ -814559,23 +814713,23 @@ ${historyLines}
814559
814713
  const sid = decodeURIComponent(chatSessionMatch[1]);
814560
814714
  if (method === "GET") {
814561
814715
  if (!checkAuth(req3, res, "read")) return;
814562
- let session = lookupSession(sid);
814563
- if (!session && sid.startsWith("tui:")) {
814716
+ let session = sid.startsWith("tui:") ? null : lookupSession(sid);
814717
+ if (sid.startsWith("tui:")) {
814564
814718
  const queriedRoot = urlObj.searchParams.get("root");
814565
814719
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814566
814720
  const tuiId = sid.slice("tui:".length);
814567
814721
  const lines = loadSessionHistory(targetRoot, tuiId);
814568
- if (lines && lines.length > 0) {
814569
- const meta = listSessions(targetRoot).find((s2) => s2.id === tuiId);
814722
+ const meta = listSessions(targetRoot).find((s2) => s2.id === tuiId);
814723
+ if (meta && lines && lines.length > 0) {
814570
814724
  session = importTranscriptSession({
814571
814725
  id: sid,
814572
814726
  projectRoot: targetRoot,
814573
814727
  model: meta?.model || loadConfig().model,
814574
- title: meta?.name,
814575
- description: meta?.description,
814728
+ title: sessionDisplayTitle(meta, lines.join("\n")),
814729
+ description: meta.aiSummary || meta.description,
814576
814730
  transcriptLines: lines,
814577
- createdAt: meta?.createdAt ? Date.parse(meta.createdAt) : void 0,
814578
- updatedAt: meta?.updatedAt ? Date.parse(meta.updatedAt) : void 0
814731
+ createdAt: meta.createdAt ? Date.parse(meta.createdAt) : void 0,
814732
+ updatedAt: meta.updatedAt ? Date.parse(meta.updatedAt) : void 0
814579
814733
  });
814580
814734
  }
814581
814735
  }
@@ -817266,6 +817420,7 @@ var init_serve = __esm({
817266
817420
  init_usage_tracker();
817267
817421
  init_omnius_directory();
817268
817422
  init_session_summary();
817423
+ init_session_quality();
817269
817424
  init_chat_run_registry();
817270
817425
  init_chat_followup();
817271
817426
  init_omnius_directory();
@@ -822244,7 +822399,7 @@ async function startInteractive(config, repoPath2) {
822244
822399
  const cleanupAndExit = (code8) => {
822245
822400
  interactiveExiting = true;
822246
822401
  try {
822247
- saveVisualSessionSnapshotRef?.("process exit");
822402
+ saveVisualSessionSnapshotRef?.();
822248
822403
  } catch {
822249
822404
  }
822250
822405
  if (_shellToolRef) _shellToolRef.killAll();
@@ -823931,15 +824086,16 @@ This is an independent background session started from /background.`
823931
824086
  }
823932
824087
  }
823933
824088
  setDreamWriteContent(writeContent);
823934
- function saveVisualSessionSnapshot(reason) {
824089
+ function saveVisualSessionSnapshot() {
823935
824090
  try {
823936
824091
  const historySessionId = process.env["OMNIUS_SESSION_ID"] || process.env["OMNIUS_TUI_SESSION_ID"] || `session-${Date.now().toString(36)}`;
823937
824092
  const tuiState = statusBar.capturePersistedSessionState();
823938
824093
  const contentLines = statusBar.capturePersistedSessionLines(100);
823939
824094
  if (contentLines.length === 0) return;
823940
824095
  const description = cleanPromptForDiary(
823941
- lastSubmittedPrompt || lastCompletedSummary || reason
823942
- ).slice(0, 240) || reason;
824096
+ lastSubmittedPrompt || lastCompletedSummary
824097
+ ).slice(0, 240);
824098
+ if (!description || isSessionNoiseText(description)) return;
823943
824099
  const historyTitle = description.slice(0, 80) || void 0;
823944
824100
  saveSessionHistory(repoRoot, historySessionId, contentLines, {
823945
824101
  name: historyTitle,
@@ -824699,7 +824855,7 @@ This is an independent background session started from /background.`
824699
824855
  clearInterval(reminderDispatchTimer);
824700
824856
  reminderDispatchTimer = null;
824701
824857
  }
824702
- saveVisualSessionSnapshot("command exit");
824858
+ saveVisualSessionSnapshot();
824703
824859
  statusBar.deactivate();
824704
824860
  if (carousel.isRunning) carousel.stop();
824705
824861
  banner.stop();
@@ -827347,7 +827503,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827347
827503
  const quitMatch = input.trim().replace(/^\//, "").toLowerCase();
827348
827504
  if (quitMatch === "quit" || quitMatch === "exit" || quitMatch === "q") {
827349
827505
  interactiveExiting = true;
827350
- saveVisualSessionSnapshot("quit");
827506
+ saveVisualSessionSnapshot();
827351
827507
  if (activeTask) activeTask.runner.abort();
827352
827508
  idleMemoryMaintenance?.stop();
827353
827509
  taskManager.stopAll();
@@ -827365,7 +827521,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827365
827521
  );
827366
827522
  if (cmdResult === "exit") {
827367
827523
  interactiveExiting = true;
827368
- saveVisualSessionSnapshot("command exit");
827524
+ saveVisualSessionSnapshot();
827369
827525
  if (activeTask) activeTask.runner.abort();
827370
827526
  idleMemoryMaintenance?.stop();
827371
827527
  taskManager.stopAll();
@@ -828237,7 +828393,7 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
828237
828393
  rl.on("close", () => {
828238
828394
  if (interactiveExiting) return;
828239
828395
  interactiveExiting = true;
828240
- saveVisualSessionSnapshot("readline close");
828396
+ saveVisualSessionSnapshot();
828241
828397
  if (peerMesh) {
828242
828398
  peerMesh.stop().catch(() => {
828243
828399
  });
@@ -828823,6 +828979,7 @@ var init_interactive = __esm({
828823
828979
  init_project_context();
828824
828980
  init_realtime();
828825
828981
  init_chat_session();
828982
+ init_session_quality();
828826
828983
  init_identity_memory_tool();
828827
828984
  init_visual_identity_association();
828828
828985
  init_dist();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.603",
3
+ "version": "1.0.604",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.603",
9
+ "version": "1.0.604",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.603",
3
+ "version": "1.0.604",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/library.js",