omnius 1.0.602 → 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
 
@@ -709817,6 +709909,12 @@ var init_status_bar = __esm({
709817
709909
  _toolCallBreakdown = [];
709818
709910
  _compactionCount = 0;
709819
709911
  _sessionStartAt = 0;
709912
+ /** Whether the uptime timer is currently paused (task complete) */
709913
+ _timerPaused = false;
709914
+ /** Timestamp when timer was paused */
709915
+ _pausedAt = 0;
709916
+ /** Total accumulated paused time in milliseconds */
709917
+ _accumulatedPausedMs = 0;
709820
709918
  _gpuName = "";
709821
709919
  _vramTotal = 0;
709822
709920
  _vramUsed = 0;
@@ -711178,6 +711276,31 @@ var init_status_bar = __esm({
711178
711276
  recordSpeedTaskEnd() {
711179
711277
  this._speedTracker.taskEnd();
711180
711278
  }
711279
+ /** Pause the uptime timer (called when task completes) */
711280
+ pauseUptimeTimer() {
711281
+ if (!this._timerPaused && this._sessionStartAt > 0) {
711282
+ this._timerPaused = true;
711283
+ this._pausedAt = Date.now();
711284
+ if (this.active) this.renderFooterPreserveCursor();
711285
+ }
711286
+ }
711287
+ /** Resume the uptime timer (called when next query begins) */
711288
+ resumeUptimeTimer() {
711289
+ if (this._timerPaused) {
711290
+ const now2 = Date.now();
711291
+ this._accumulatedPausedMs += now2 - this._pausedAt;
711292
+ this._timerPaused = false;
711293
+ this._pausedAt = 0;
711294
+ if (this.active) this.renderFooterPreserveCursor();
711295
+ }
711296
+ }
711297
+ /** Reset the uptime timer completely (for new session) */
711298
+ resetUptimeTimer() {
711299
+ this._sessionStartAt = 0;
711300
+ this._timerPaused = false;
711301
+ this._pausedAt = 0;
711302
+ this._accumulatedPausedMs = 0;
711303
+ }
711181
711304
  /** SNR (Signal-to-Noise Ratio) from context quality evaluation */
711182
711305
  _snr = null;
711183
711306
  /** Update the SNR gauge with a new evaluation result */
@@ -713784,7 +713907,10 @@ ${CONTENT_BG_SEQ}`);
713784
713907
  const m2 = this.metrics;
713785
713908
  const termWidth = getTermWidth();
713786
713909
  const uptime2 = this.formatUptime();
713787
- const uptimeStr = `\x1B[38;5;183m◖ ${uptime2}\x1B[0m`;
713910
+ const isPaused = this._timerPaused;
713911
+ const circleChar = isPaused ? "●" : "◖";
713912
+ const circleColor = isPaused ? 120 : 183;
713913
+ const uptimeStr = `\x1B[38;5;${circleColor}m${circleChar} ${uptime2}\x1B[0m`;
713788
713914
  const ctxUsed = m2.estimatedContextTokens;
713789
713915
  const ctxTotal = this.reportedContextTotal(
713790
713916
  m2.contextWindowSize,
@@ -713881,7 +714007,12 @@ ${CONTENT_BG_SEQ}`);
713881
714007
  }
713882
714008
  formatUptime() {
713883
714009
  if (this._sessionStartAt <= 0) return "0s";
713884
- const ms = Date.now() - this._sessionStartAt;
714010
+ let ms;
714011
+ if (this._timerPaused) {
714012
+ ms = this._pausedAt - this._sessionStartAt - this._accumulatedPausedMs;
714013
+ } else {
714014
+ ms = Date.now() - this._sessionStartAt - this._accumulatedPausedMs;
714015
+ }
713885
714016
  const totalSec = Math.floor(ms / 1e3);
713886
714017
  const h = Math.floor(totalSec / 3600);
713887
714018
  const m2 = Math.floor(totalSec % 3600 / 60);
@@ -719779,7 +719910,7 @@ export PATH="${binDir}:$PATH" # Added by omnius for nvim
719779
719910
  } catch {
719780
719911
  }
719781
719912
  }
719782
- 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;
719783
719914
  var init_setup = __esm({
719784
719915
  "packages/cli/src/tui/setup.ts"() {
719785
719916
  init_model_picker();
@@ -719799,8 +719930,8 @@ var init_setup = __esm({
719799
719930
  "░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ ",
719800
719931
  " ░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓██████▓▒░░▒▓███████▓▒░ "
719801
719932
  ].join("\n");
719802
- ANSI_RE4 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
719803
- 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;
719804
719935
  SETUP_MODEL_VARIANTS = [
719805
719936
  { tag: "robit/ornith:9b", sizeGB: 6.6, label: "9B params (6.6 GB) - recommended minimum", cloud: false },
719806
719937
  { tag: "robit/ornith:35b", sizeGB: 24, label: "35B params (24 GB) - recommended on 32GB+ unified memory/VRAM", cloud: false }
@@ -749529,21 +749660,13 @@ function stripAnsi6(text2) {
749529
749660
  return String(text2 || "").replace(/\u001b\[[0-9;]*[a-zA-Z]/g, "");
749530
749661
  }
749531
749662
  function cleanSessionDisplayLine(line) {
749532
- return stripAnsi6(line).replace(/^[>❯▹∙•*+\-\s]+/, "").replace(/^(?:User|Assistant|You|Open Agent|Omnius)\s*:\s*/i, "").replace(/\s+/g, " ").trim();
749663
+ return normalizeSessionDisplayText(stripAnsi6(line));
749533
749664
  }
749534
749665
  function isNoisySessionDisplayLine(line) {
749535
- const clean7 = cleanSessionDisplayLine(line || "");
749536
- if (!clean7) return true;
749537
- 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 || ""));
749538
749667
  }
749539
749668
  function bestSessionDisplayLine(text2) {
749540
- const lines = stripAnsi6(text2).split(/\r?\n/);
749541
- for (const line of lines) {
749542
- const clean7 = cleanSessionDisplayLine(line);
749543
- if (!isNoisySessionDisplayLine(clean7)) return clean7;
749544
- }
749545
- const fallback = stripAnsi6(text2).replace(/\s+/g, " ").trim();
749546
- return isNoisySessionDisplayLine(fallback) ? "" : fallback;
749669
+ return firstMeaningfulSessionLine(stripAnsi6(text2));
749547
749670
  }
749548
749671
  function makeTitle(text2) {
749549
749672
  const clean7 = bestSessionDisplayLine(text2);
@@ -749757,7 +749880,7 @@ function importTranscriptSession(opts) {
749757
749880
  "",
749758
749881
  cappedTranscript || "(empty transcript)"
749759
749882
  ].filter(Boolean).join("\n");
749760
- 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);
749761
749884
  const existing = lookupSession(id2);
749762
749885
  if (existing) {
749763
749886
  existing.projectRoot = projectRoot;
@@ -749767,14 +749890,12 @@ function importTranscriptSession(opts) {
749767
749890
  existing.preview = preview;
749768
749891
  existing.transcript = cappedTranscript || "(empty transcript)";
749769
749892
  existing.lastActivity = opts.updatedAt ?? Date.now();
749893
+ existing.messages = existing.messages.filter((message2) => !isImportedNotice(message2));
749770
749894
  const idx = existing.messages.findIndex(
749771
749895
  (m2) => m2.role === "system" && m2.content.startsWith("[Imported TUI session transcript]")
749772
749896
  );
749773
749897
  if (idx >= 0) existing.messages[idx] = { role: "system", content: importedContext };
749774
749898
  else existing.messages.splice(1, 0, { role: "system", content: importedContext });
749775
- if (!existing.messages.some((m2) => m2.role === "assistant" && m2.content === visibleNotice)) {
749776
- existing.messages.push({ role: "assistant", content: visibleNotice });
749777
- }
749778
749899
  persistSession(existing);
749779
749900
  return existing;
749780
749901
  }
@@ -749783,8 +749904,7 @@ function importTranscriptSession(opts) {
749783
749904
  id: id2,
749784
749905
  messages: [
749785
749906
  { role: "system", content: buildSystemPrompt(projectRoot) },
749786
- { role: "system", content: importedContext },
749787
- { role: "assistant", content: visibleNotice }
749907
+ { role: "system", content: importedContext }
749788
749908
  ],
749789
749909
  model: opts.model || "unknown",
749790
749910
  createdAt: opts.createdAt ?? opts.updatedAt ?? now2,
@@ -749929,6 +750049,9 @@ function listSessions2(opts = {}) {
749929
750049
  if (!root) return true;
749930
750050
  if (!s2.projectRoot) return !!opts.includeUnscoped;
749931
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);
749932
750055
  }).sort((a2, b) => b.lastActivity - a2.lastActivity).map((s2) => ({
749933
750056
  id: s2.id,
749934
750057
  model: s2.model,
@@ -750037,6 +750160,7 @@ var sessions2, inFlight, SESSION_TTL_MS, INFERENCE_ROLES, PARTIAL_TAIL_BUDGET;
750037
750160
  var init_chat_session = __esm({
750038
750161
  "packages/cli/src/api/chat-session.ts"() {
750039
750162
  init_secret_redactor();
750163
+ init_session_quality();
750040
750164
  sessions2 = /* @__PURE__ */ new Map();
750041
750165
  inFlight = /* @__PURE__ */ new Map();
750042
750166
  SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -796367,8 +796491,10 @@ async function loadServerPrefs() {
796367
796491
  function isNoisyChatSessionText(text) {
796368
796492
  const clean = String(text || '').replace(/\\s+/g, ' ').trim();
796369
796493
  if (!clean) return true;
796370
- const t = clean.replace(/^[>❯▹∙•\\-\\s]+/, '');
796371
- 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);
796372
796498
  }
796373
796499
 
796374
796500
  function isGenericChatTitle(id, title) {
@@ -796385,10 +796511,11 @@ function sessionDisplayTitle(id, session) {
796385
796511
  if (!isGenericChatTitle(id, candidate)) return String(candidate).trim();
796386
796512
  }
796387
796513
  if (Array.isArray(s.messages)) {
796388
- for (let i = s.messages.length - 1; i >= 0; i--) {
796389
- const msg = s.messages[i];
796390
- const content = msg && typeof msg.content === 'string' ? msg.content : '';
796391
- 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
+ }
796392
796519
  }
796393
796520
  }
796394
796521
  return String(id || '').startsWith('tui:') ? 'TUI session ' + String(id).slice(4, 16) : 'Chat ' + String(id || '').slice(0, 8);
@@ -796402,6 +796529,14 @@ function sessionDisplayPreview(session) {
796402
796529
  return '';
796403
796530
  }
796404
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
+
796405
796540
  async function loadServerChatSessions() {
796406
796541
  const root = $currentProject.get()?.root || '';
796407
796542
  if (!root) return;
@@ -796410,23 +796545,30 @@ async function loadServerChatSessions() {
796410
796545
  if (!r.ok) return;
796411
796546
  const data = await r.json();
796412
796547
  const local = loadScopedSessions();
796413
- 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
+ }
796414
796556
  for (const sess of (data.sessions || [])) {
796415
796557
  if (!sess || !sess.id) continue;
796416
- const existing = merged[sess.id] || {};
796558
+ const existing = local[sess.id] || merged[sess.id] || {};
796417
796559
  const serverTitle = sess.title || sess.preview || '';
796418
796560
  const existingTitle = existing.title || existing.name || '';
796419
- const title = !isGenericChatTitle(sess.id, existingTitle)
796420
- ? existingTitle
796421
- : (!isGenericChatTitle(sess.id, serverTitle) ? serverTitle : sessionDisplayTitle(sess.id, sess));
796422
- const preview = !isNoisyChatSessionText(existing.preview)
796423
- ? existing.preview
796424
- : (!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));
796425
796567
  merged[sess.id] = {
796426
796568
  ...existing,
796427
796569
  id: sess.id,
796428
796570
  title,
796429
- name: !isGenericChatTitle(sess.id, existing.name) ? existing.name : title,
796571
+ name: title,
796430
796572
  preview,
796431
796573
  model: existing.model || sess.model || '',
796432
796574
  source: sess.source || existing.source || 'web',
@@ -796435,7 +796577,14 @@ async function loadServerChatSessions() {
796435
796577
  messages: existing.messages || [],
796436
796578
  };
796437
796579
  }
796580
+ saveScopedSessions(merged);
796438
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
+ }
796439
796588
  updateSessionSelect();
796440
796589
  } catch {}
796441
796590
  }
@@ -798879,7 +799028,7 @@ function chatSessionFromRouteSearch(search) {
798879
799028
  }
798880
799029
  function syncRouteForTab(tab, replace) {
798881
799030
  const path = routePathForTab(tab);
798882
- const query = tab === 'chat' && chatSessionId ? '?' + encodeURIComponent(chatSessionId) : '';
799031
+ const query = tab === 'chat' && chatSessionId ? '?session=' + encodeURIComponent(chatSessionId) : '';
798883
799032
  const next = path + query;
798884
799033
  if ((location.pathname + location.search) === next) return;
798885
799034
  const method = replace ? 'replaceState' : 'pushState';
@@ -799480,6 +799629,7 @@ window.addEventListener('popstate', () => {
799480
799629
  if (tab === 'chat') {
799481
799630
  const sid = chatSessionFromRouteSearch(location.search);
799482
799631
  if (sid && sid !== chatSessionId) switchSession(sid);
799632
+ else if (!sid && chatSessionId) switchSession('');
799483
799633
  }
799484
799634
  switchTab(tab, { fromRoute: true });
799485
799635
  });
@@ -800379,6 +800529,7 @@ function updateSessionSelect() {
800379
800529
  const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800380
800530
  const saved = { ...storeSessions, ...loadScopedSessions() };
800381
800531
  const entries = Object.entries(saved)
800532
+ .filter(([id, session]) => isEligibleChatSession(id, session))
800382
800533
  .sort((a, b) => (b[1].updatedAt || '').localeCompare(a[1].updatedAt || ''))
800383
800534
  .slice(0, 20);
800384
800535
  for (const sel of targets) {
@@ -800398,8 +800549,8 @@ function updateSessionSelect() {
800398
800549
  // It delegates to the existing switchSession() so chat history restoration
800399
800550
  // still works exactly as before.
800400
800551
  function switchChatSession(id) {
800401
- switchSession(id);
800402
800552
  switchTab('chat', { replaceRoute: true });
800553
+ switchSession(id);
800403
800554
  }
800404
800555
  function newChatSession() {
800405
800556
  switchSession('');
@@ -800576,7 +800727,8 @@ function switchSession(id) {
800576
800727
  return;
800577
800728
  }
800578
800729
  const saved = loadScopedSessions();
800579
- const s = saved[id];
800730
+ const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800731
+ const s = saved[id] || storeSessions[id];
800580
800732
  if (s) {
800581
800733
  chatSessionId = id;
800582
800734
  messages = s.messages || [];
@@ -800628,6 +800780,9 @@ function switchSession(id) {
800628
800780
  updateSessionSelect();
800629
800781
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800630
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();
800631
800786
  } else {
800632
800787
  // Server-backed sessions (including imported TUI sessions) may only be
800633
800788
  // present in $chatSessions, not localStorage. Activate and let the daemon
@@ -800636,7 +800791,7 @@ function switchSession(id) {
800636
800791
  syncRouteForTab('chat', true);
800637
800792
  updateSessionSelect();
800638
800793
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800639
- restoreChatSession();
800794
+ void restoreChatSession();
800640
800795
  }
800641
800796
  }
800642
800797
 
@@ -802293,6 +802448,7 @@ function renderRecoveredTranscript(raw, conv, title) {
802293
802448
  let mode = null; // 'user' | 'assistant'
802294
802449
  let buf = [];
802295
802450
  let toolCount = 0;
802451
+ let inToolBox = false;
802296
802452
  const out = []; // [{role, text}] or {tools:n}
802297
802453
  const flush = () => {
802298
802454
  const text = buf.join(NL).trim();
@@ -802307,22 +802463,29 @@ function renderRecoveredTranscript(raw, conv, title) {
802307
802463
  line = line.replace(sentRe, '');
802308
802464
  const t = line.trim();
802309
802465
  if (!t) continue;
802310
- // Skip pure box-border lines from expanded tool/task boxes (╭─╮ ╰─╯ etc.).
802311
- if (/^[─-╿s]+$/.test(t)) continue;
802312
- 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; }
802313
802475
  flush(); if (toolCount) { out.push({ tools: toolCount }); toolCount = 0; }
802314
- out.push({ role: 'user', text: t.replace(/^[▹>]s?/, '') }); mode = null; continue;
802476
+ mode = 'user'; buf.push(userMatch[1]); continue;
802315
802477
  }
802316
- if (/^│/.test(t)) { // assistant / box content
802478
+ const assistantMatch = t.match(/^(?:(?:Assistant|Open Agent|Omnius)\\s*:\\s*|│\\s?)(.*)$/i);
802479
+ if (assistantMatch) {
802317
802480
  if (mode !== 'assistant') { flush(); mode = 'assistant'; }
802318
- buf.push(t.replace(/^│s?/, '').replace(/s?│$/, '')); continue; // strip both borders
802481
+ buf.push(assistantMatch[1].replace(/\\s?│$/, '')); continue;
802319
802482
  }
802320
- if (/^[∙!]/.test(t)) continue; // status / internal noise — drop
802321
- if (mode === 'assistant') buf.push(t); // continuation
802483
+ if (/^[∙!EW⚠]/.test(t) || isNoisyChatSessionText(t)) continue;
802484
+ if (mode) buf.push(t); // wrapped authored continuation
802322
802485
  }
802323
802486
  flush(); if (toolCount) out.push({ tools: toolCount });
802324
802487
 
802325
- 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
802326
802489
  const note = document.createElement('div');
802327
802490
  note.style.cssText = 'font-size:0.6rem;color:var(--color-fg-faint);margin:4px 0 8px;text-align:center';
802328
802491
  note.textContent = 'recovered session' + (title ? ' — ' + title : '');
@@ -802337,6 +802500,9 @@ function renderRecoveredTranscript(raw, conv, title) {
802337
802500
  addMessage(item.role, item.text);
802338
802501
  }
802339
802502
  }
802503
+ return out
802504
+ .filter(item => !item.tools && (item.role === 'user' || item.role === 'assistant'))
802505
+ .map(item => ({ role: item.role, content: item.text }));
802340
802506
  }
802341
802507
  window.renderRecoveredTranscript = renderRecoveredTranscript;
802342
802508
 
@@ -802350,6 +802516,7 @@ async function restoreChatSession() {
802350
802516
  const root = $currentProject.get()?.root || '';
802351
802517
  const query = root ? '?root=' + encodeURIComponent(root) : '';
802352
802518
  const r = await fetch('/v1/chat/sessions/' + encodeURIComponent(sid) + query, { headers: headers() });
802519
+ if (chatSessionId !== sid) return;
802353
802520
  if (!r.ok) {
802354
802521
  if (r.status === 404) {
802355
802522
  // Server lost the session (e.g. daemon restart) — clear via store
@@ -802359,16 +802526,17 @@ async function restoreChatSession() {
802359
802526
  return;
802360
802527
  }
802361
802528
  const data = await r.json();
802529
+ if (chatSessionId !== sid) return;
802362
802530
  if (!data || !data.id) return;
802363
802531
  chatSessionId = data.id;
802364
802532
  window.currentSessionId = data.id;
802365
802533
  // Keep the in-memory messages array clean (only user/assistant) so
802366
802534
  // the next inference call sees a valid conversation. Tool events
802367
802535
  // are still rendered into the DOM via the dropdown helpers below.
802368
- const allMessages = data.messages || [];
802369
- messages = allMessages
802370
- .filter(m => m.role === 'user' || m.role === 'assistant')
802371
- .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 = [];
802372
802540
  const conv = document.getElementById('conversation');
802373
802541
  if (conv) {
802374
802542
  conv.innerHTML = '';
@@ -802376,15 +802544,15 @@ async function restoreChatSession() {
802376
802544
  // decisions, sub-agent activity — everything that scrolled past in the
802377
802545
  // TUI) as a visible, scrollable block at the top so opening the session
802378
802546
  // actually shows what happened, instead of just a "loaded" notice.
802379
- if (data.transcript && String(data.transcript).trim() && data.transcript !== '(empty transcript)'
802380
- && (!allMessages || allMessages.length === 0)) {
802547
+ if (data.source === 'tui' && data.transcript && String(data.transcript).trim()
802548
+ && data.transcript !== '(empty transcript)') {
802381
802549
  // TUI/raw transcript: PARSE the rendered-TUI scrollback into real chat
802382
802550
  // bubbles instead of dumping the raw log. The persisted log carries
802383
802551
  // DYNBLOCK:<id> sentinels (dynamic blocks expanded only at TUI
802384
802552
  // paint time — no content here) plus chrome (▹ user, │ assistant,
802385
802553
  // ∙ status, ! internal). We strip the dead sentinels + noise and emit
802386
802554
  // user/assistant messages so it reads like a GUI chat.
802387
- renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802555
+ recoveredMessages = renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802388
802556
  }
802389
802557
  // WO-CHAT-RESUME-TOOLS — replay the FULL intermediate flow on
802390
802558
  // restore: user/assistant text bubbles AND tool_call/tool_result
@@ -802432,6 +802600,12 @@ async function restoreChatSession() {
802432
802600
  }
802433
802601
  }
802434
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
+ ];
802435
802609
  try {
802436
802610
  const saved = loadScopedSessions();
802437
802611
  saved[data.id] = {
@@ -803593,7 +803767,7 @@ function _renderSidebarChats(filter) {
803593
803767
  _renderSidebarFoldersToolbar();
803594
803768
 
803595
803769
  const sessions = (typeof $chatSessions !== 'undefined' && $chatSessions.get) ? $chatSessions.get() : {};
803596
- const allIds = Object.keys(sessions || {});
803770
+ const allIds = Object.keys(sessions || {}).filter(id => isEligibleChatSession(id, sessions[id]));
803597
803771
  const q = (filter || '').trim().toLowerCase();
803598
803772
  const activeId = chatSessionId || (($chatSessionId.get && $chatSessionId.get()) || null);
803599
803773
 
@@ -803605,8 +803779,8 @@ function _renderSidebarChats(filter) {
803605
803779
  // Sort helper
803606
803780
  const sortByRecency = (a, b) => {
803607
803781
  const sa = sessions[a] || {}, sb = sessions[b] || {};
803608
- const ta = sa.updated_at || sa.created_at || a;
803609
- 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;
803610
803784
  return String(tb).localeCompare(String(ta));
803611
803785
  };
803612
803786
  allIds.sort(sortByRecency);
@@ -803640,7 +803814,7 @@ function _renderSidebarChats(filter) {
803640
803814
  const cls = 'sb-chat' + (id === activeId ? ' active' : '');
803641
803815
  const safeId = String(id).replace(/'/g, "\\\\'");
803642
803816
  const safeTitle = title.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
803643
- 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 + '">' +
803644
803818
  '<span class="sb-chat-title">' + safeTitle + '</span>' +
803645
803819
  '<button class="sb-chat-menu" onclick="_showChatRowMenu(event,\\'' + safeId + '\\')" title="More">&#x22EE;</button>' +
803646
803820
  '</div>';
@@ -814321,17 +814495,30 @@ ${historyLines}
814321
814495
  if (!checkAuth(req3, res, "read")) return;
814322
814496
  const queriedRoot = urlObj.searchParams.get("root");
814323
814497
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814324
- const sessions3 = listSessions2({ projectRoot: targetRoot });
814325
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
+ });
814326
814505
  if (includeTui && targetRoot) {
814327
- 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();
814328
814508
  const cfg = loadConfig();
814329
814509
  const needSummary = [];
814330
- for (const t2 of listSessions(targetRoot)) {
814510
+ for (const t2 of tuiHistory) {
814331
814511
  const id2 = `tui:${t2.id}`;
814332
- if (seen.has(id2)) continue;
814333
- if (!t2.aiTitle) needSummary.push(t2.id);
814334
- 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 = {
814335
814522
  id: id2,
814336
814523
  model: t2.model,
814337
814524
  messages: 0,
@@ -814340,9 +814527,15 @@ ${historyLines}
814340
814527
  lastActivity: t2.updatedAt,
814341
814528
  projectRoot: targetRoot,
814342
814529
  source: "tui",
814343
- title: sessionDisplayTitle(t2),
814344
- preview: t2.aiSummary || t2.description
814345
- });
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
+ }
814346
814539
  }
814347
814540
  for (const sid of needSummary.slice(0, 6)) {
814348
814541
  void ensureSessionSummary({
@@ -814520,23 +814713,23 @@ ${historyLines}
814520
814713
  const sid = decodeURIComponent(chatSessionMatch[1]);
814521
814714
  if (method === "GET") {
814522
814715
  if (!checkAuth(req3, res, "read")) return;
814523
- let session = lookupSession(sid);
814524
- if (!session && sid.startsWith("tui:")) {
814716
+ let session = sid.startsWith("tui:") ? null : lookupSession(sid);
814717
+ if (sid.startsWith("tui:")) {
814525
814718
  const queriedRoot = urlObj.searchParams.get("root");
814526
814719
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814527
814720
  const tuiId = sid.slice("tui:".length);
814528
814721
  const lines = loadSessionHistory(targetRoot, tuiId);
814529
- if (lines && lines.length > 0) {
814530
- 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) {
814531
814724
  session = importTranscriptSession({
814532
814725
  id: sid,
814533
814726
  projectRoot: targetRoot,
814534
814727
  model: meta?.model || loadConfig().model,
814535
- title: meta?.name,
814536
- description: meta?.description,
814728
+ title: sessionDisplayTitle(meta, lines.join("\n")),
814729
+ description: meta.aiSummary || meta.description,
814537
814730
  transcriptLines: lines,
814538
- createdAt: meta?.createdAt ? Date.parse(meta.createdAt) : void 0,
814539
- 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
814540
814733
  });
814541
814734
  }
814542
814735
  }
@@ -817227,6 +817420,7 @@ var init_serve = __esm({
817227
817420
  init_usage_tracker();
817228
817421
  init_omnius_directory();
817229
817422
  init_session_summary();
817423
+ init_session_quality();
817230
817424
  init_chat_run_registry();
817231
817425
  init_chat_followup();
817232
817426
  init_omnius_directory();
@@ -822205,7 +822399,7 @@ async function startInteractive(config, repoPath2) {
822205
822399
  const cleanupAndExit = (code8) => {
822206
822400
  interactiveExiting = true;
822207
822401
  try {
822208
- saveVisualSessionSnapshotRef?.("process exit");
822402
+ saveVisualSessionSnapshotRef?.();
822209
822403
  } catch {
822210
822404
  }
822211
822405
  if (_shellToolRef) _shellToolRef.killAll();
@@ -823892,15 +824086,16 @@ This is an independent background session started from /background.`
823892
824086
  }
823893
824087
  }
823894
824088
  setDreamWriteContent(writeContent);
823895
- function saveVisualSessionSnapshot(reason) {
824089
+ function saveVisualSessionSnapshot() {
823896
824090
  try {
823897
824091
  const historySessionId = process.env["OMNIUS_SESSION_ID"] || process.env["OMNIUS_TUI_SESSION_ID"] || `session-${Date.now().toString(36)}`;
823898
824092
  const tuiState = statusBar.capturePersistedSessionState();
823899
824093
  const contentLines = statusBar.capturePersistedSessionLines(100);
823900
824094
  if (contentLines.length === 0) return;
823901
824095
  const description = cleanPromptForDiary(
823902
- lastSubmittedPrompt || lastCompletedSummary || reason
823903
- ).slice(0, 240) || reason;
824096
+ lastSubmittedPrompt || lastCompletedSummary
824097
+ ).slice(0, 240);
824098
+ if (!description || isSessionNoiseText(description)) return;
823904
824099
  const historyTitle = description.slice(0, 80) || void 0;
823905
824100
  saveSessionHistory(repoRoot, historySessionId, contentLines, {
823906
824101
  name: historyTitle,
@@ -824660,7 +824855,7 @@ This is an independent background session started from /background.`
824660
824855
  clearInterval(reminderDispatchTimer);
824661
824856
  reminderDispatchTimer = null;
824662
824857
  }
824663
- saveVisualSessionSnapshot("command exit");
824858
+ saveVisualSessionSnapshot();
824664
824859
  statusBar.deactivate();
824665
824860
  if (carousel.isRunning) carousel.stop();
824666
824861
  banner.stop();
@@ -827308,7 +827503,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827308
827503
  const quitMatch = input.trim().replace(/^\//, "").toLowerCase();
827309
827504
  if (quitMatch === "quit" || quitMatch === "exit" || quitMatch === "q") {
827310
827505
  interactiveExiting = true;
827311
- saveVisualSessionSnapshot("quit");
827506
+ saveVisualSessionSnapshot();
827312
827507
  if (activeTask) activeTask.runner.abort();
827313
827508
  idleMemoryMaintenance?.stop();
827314
827509
  taskManager.stopAll();
@@ -827326,7 +827521,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827326
827521
  );
827327
827522
  if (cmdResult === "exit") {
827328
827523
  interactiveExiting = true;
827329
- saveVisualSessionSnapshot("command exit");
827524
+ saveVisualSessionSnapshot();
827330
827525
  if (activeTask) activeTask.runner.abort();
827331
827526
  idleMemoryMaintenance?.stop();
827332
827527
  taskManager.stopAll();
@@ -827919,6 +828114,7 @@ ${taskInput}`;
827919
828114
  }
827920
828115
  try {
827921
828116
  statusBar.setProcessing(true);
828117
+ statusBar.resumeUptimeTimer();
827922
828118
  statusBar.recordSpeedTaskStart();
827923
828119
  const task = startTask(
827924
828120
  taskInput,
@@ -828001,6 +828197,7 @@ ${taskInput}`;
828001
828197
  } finally {
828002
828198
  statusBar.setProcessing(false);
828003
828199
  statusBar.recordSpeedTaskEnd();
828200
+ statusBar.pauseUptimeTimer();
828004
828201
  try {
828005
828202
  const memSnippets = gatherMemorySnippets(repoRoot);
828006
828203
  if (memSnippets.length > 0 && lastSubmittedPrompt) {
@@ -828196,7 +828393,7 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
828196
828393
  rl.on("close", () => {
828197
828394
  if (interactiveExiting) return;
828198
828395
  interactiveExiting = true;
828199
- saveVisualSessionSnapshot("readline close");
828396
+ saveVisualSessionSnapshot();
828200
828397
  if (peerMesh) {
828201
828398
  peerMesh.stop().catch(() => {
828202
828399
  });
@@ -828782,6 +828979,7 @@ var init_interactive = __esm({
828782
828979
  init_project_context();
828783
828980
  init_realtime();
828784
828981
  init_chat_session();
828982
+ init_session_quality();
828785
828983
  init_identity_memory_tool();
828786
828984
  init_visual_identity_association();
828787
828985
  init_dist();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.602",
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.602",
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.602",
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",