omnius 1.0.603 → 1.0.605

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
@@ -363,6 +363,7 @@ __export(update_service_exports, {
363
363
  UpdateInProgressError: () => UpdateInProgressError,
364
364
  assertExactUpdateTarget: () => assertExactUpdateTarget,
365
365
  inspectGlobalOmniusInstall: () => inspectGlobalOmniusInstall,
366
+ readReconciledUpdateState: () => readReconciledUpdateState,
366
367
  readUpdateLogTail: () => readUpdateLogTail,
367
368
  readUpdateState: () => readUpdateState,
368
369
  releaseUpdateLock: () => releaseUpdateLock,
@@ -373,6 +374,7 @@ __export(update_service_exports, {
373
374
  updateStateFileIsPrivate: () => updateStateFileIsPrivate,
374
375
  updateStateIsFreshRunning: () => updateStateIsFreshRunning,
375
376
  updateStatusSnapshot: () => updateStatusSnapshot,
377
+ waitForUpdateTransaction: () => waitForUpdateTransaction,
376
378
  writeUpdateState: () => writeUpdateState
377
379
  });
378
380
  import { spawn, spawnSync } from "node:child_process";
@@ -613,6 +615,68 @@ function transition(current, patch, paths) {
613
615
  function permissionRemediation(evidence) {
614
616
  return /EACCES|EPERM|permission denied/i.test(evidence) ? "The discovered npm global prefix is not writable. Configure a user-owned npm prefix or rerun the explicit update from a privileged terminal; Omnius never silently elevates." : void 0;
615
617
  }
618
+ function installGlobalPackageStreaming(input) {
619
+ return new Promise((resolve87) => {
620
+ const stderrTail = [];
621
+ let settled = false;
622
+ let timedOut = false;
623
+ const finish = (exitCode, error) => {
624
+ if (settled) return;
625
+ settled = true;
626
+ clearTimeout(timer);
627
+ resolve87({ exitCode, ...error ? { error } : {} });
628
+ };
629
+ const child = spawn(
630
+ input.npmPath,
631
+ [
632
+ "install",
633
+ "-g",
634
+ `omnius@${input.targetVersion}`,
635
+ "--prefer-online",
636
+ "--no-audit",
637
+ "--no-fund",
638
+ "--no-progress"
639
+ ],
640
+ {
641
+ env: input.env,
642
+ stdio: ["ignore", "pipe", "pipe"],
643
+ windowsHide: true
644
+ }
645
+ );
646
+ child.stdout?.on("data", (chunk) => {
647
+ process.stdout.write(chunk);
648
+ });
649
+ child.stderr?.on("data", (chunk) => {
650
+ const text2 = String(chunk);
651
+ process.stderr.write(text2);
652
+ stderrTail.push(text2);
653
+ while (stderrTail.join("").length > 8e3 && stderrTail.length > 1) stderrTail.shift();
654
+ });
655
+ child.once("error", (error) => finish(-1, error.message));
656
+ child.once("close", (code8, signal) => {
657
+ const exitCode = code8 ?? -1;
658
+ const evidence = stderrTail.join("").trim();
659
+ finish(
660
+ exitCode,
661
+ exitCode === 0 ? void 0 : timedOut ? "npm install timed out after 5 minutes" : evidence || `npm install exited with ${signal ? `signal ${signal}` : `code ${exitCode}`}`
662
+ );
663
+ });
664
+ const timer = setTimeout(() => {
665
+ timedOut = true;
666
+ try {
667
+ child.kill("SIGTERM");
668
+ } catch {
669
+ }
670
+ const forceTimer = setTimeout(() => {
671
+ try {
672
+ child.kill("SIGKILL");
673
+ } catch {
674
+ }
675
+ }, 2500);
676
+ forceTimer.unref?.();
677
+ }, 5 * 6e4);
678
+ });
679
+ }
616
680
  async function runVerifiedUpdateTransaction(initial, dependencies, paths = resolveUpdatePaths()) {
617
681
  let state = initial;
618
682
  const target = assertExactUpdateTarget(initial.target_version);
@@ -632,27 +696,7 @@ async function runVerifiedUpdateTransaction(initial, dependencies, paths = resol
632
696
  npm_path: npmPath,
633
697
  ...prefixProbe.status === 0 && prefixProbe.stdout.trim() ? { npm_prefix: prefixProbe.stdout.trim() } : {}
634
698
  }, paths);
635
- const installer = dependencies.install ?? (async ({ npmPath: command, targetVersion, env: childEnv }) => {
636
- const result = runSync(
637
- command,
638
- [
639
- "install",
640
- "-g",
641
- `omnius@${targetVersion}`,
642
- "--prefer-online",
643
- "--no-audit",
644
- "--no-fund",
645
- "--no-progress"
646
- ],
647
- { env: childEnv, timeout: 5 * 6e4 }
648
- );
649
- if (result.stdout) process.stdout.write(result.stdout);
650
- if (result.stderr) process.stderr.write(result.stderr);
651
- return {
652
- exitCode: result.status ?? -1,
653
- ...result.error || result.status !== 0 ? { error: result.error?.message || result.stderr.trim() || `npm install exited with code ${result.status ?? -1}` } : {}
654
- };
655
- });
699
+ const installer = dependencies.install ?? installGlobalPackageStreaming;
656
700
  const installResult = await installer({ npmPath, targetVersion: target, env: env2 });
657
701
  state = transition(state, { installer_exit_code: installResult.exitCode }, paths);
658
702
  if (installResult.exitCode !== 0) {
@@ -721,6 +765,13 @@ function startDetachedGlobalUpdate(input, paths = resolveUpdatePaths()) {
721
765
  const operationId = randomUUID();
722
766
  const lockFd = acquireUpdateLock(operationId, paths);
723
767
  closeSync(lockFd);
768
+ const workerPath = fileURLToPath(new URL("./update-worker.js", import.meta.url));
769
+ if (!existsSync2(workerPath)) {
770
+ releaseUpdateLock(paths, operationId);
771
+ throw new Error(
772
+ `The coordinated update worker is missing at ${workerPath}. Reinstall Omnius from a package that ships dist/update-worker.js.`
773
+ );
774
+ }
724
775
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
725
776
  let state = {
726
777
  schema_version: 1,
@@ -737,10 +788,15 @@ function startDetachedGlobalUpdate(input, paths = resolveUpdatePaths()) {
737
788
  tray_restarted: false
738
789
  };
739
790
  writeUpdateState(state, paths);
740
- const workerPath = fileURLToPath(new URL("./update-worker.js", import.meta.url));
741
791
  rotateUpdateLog(paths);
742
792
  const logFd = openSync(paths.logFile, "a", 384);
743
793
  try {
794
+ writeSync(
795
+ logFd,
796
+ `
797
+ [omnius update ${operationId}] ${input.fromVersion} -> ${targetVersion} ${now2}
798
+ `
799
+ );
744
800
  const child = spawn(
745
801
  process.execPath,
746
802
  [
@@ -805,6 +861,32 @@ function updateStatusSnapshot(paths = resolveUpdatePaths()) {
805
861
  log_tail: readUpdateLogTail(paths)
806
862
  };
807
863
  }
864
+ function readReconciledUpdateState(paths = resolveUpdatePaths()) {
865
+ const snapshot = updateStatusSnapshot(paths).state;
866
+ return snapshot.status === "idle" ? null : snapshot;
867
+ }
868
+ async function waitForUpdateTransaction(operationId, options2 = {}, paths = resolveUpdatePaths()) {
869
+ const pollIntervalMs = Math.max(25, options2.pollIntervalMs ?? 300);
870
+ const timeoutMs = Math.max(pollIntervalMs, options2.timeoutMs ?? 10 * 6e4);
871
+ const deadline = Date.now() + timeoutMs;
872
+ while (Date.now() <= deadline) {
873
+ const snapshot = updateStatusSnapshot(paths);
874
+ if (snapshot.state.status !== "idle") {
875
+ if (snapshot.state.operation_id !== operationId) {
876
+ throw new Error(
877
+ `Update operation changed from ${operationId} to ${snapshot.state.operation_id}`
878
+ );
879
+ }
880
+ options2.onProgress?.({
881
+ state: snapshot.state,
882
+ log_tail: snapshot.log_tail
883
+ });
884
+ if (snapshot.state.status !== "running") return snapshot.state;
885
+ }
886
+ await new Promise((resolve87) => setTimeout(resolve87, pollIntervalMs));
887
+ }
888
+ throw new Error(`Update operation ${operationId} did not finish within ${timeoutMs}ms`);
889
+ }
808
890
  function updateStateIsFreshRunning(state) {
809
891
  if (!state || state.status !== "running") return false;
810
892
  if (isPidAlive(state.pid)) return true;
@@ -679731,7 +679813,7 @@ async function runTrayForeground(explicitEndpoint) {
679731
679813
  let updateView = trayUpdatePresentation(
679732
679814
  health.version,
679733
679815
  availableUpdate?.latestVersion,
679734
- readUpdateState()
679816
+ readReconciledUpdateState()
679735
679817
  );
679736
679818
  const menuState = menuForHealth(health, endpoint, registered, updateView);
679737
679819
  const SysTray = await loadSysTray();
@@ -679773,7 +679855,7 @@ async function runTrayForeground(explicitEndpoint) {
679773
679855
  updateView = trayUpdatePresentation(
679774
679856
  health.version,
679775
679857
  availableUpdate?.latestVersion,
679776
- readUpdateState()
679858
+ readReconciledUpdateState()
679777
679859
  );
679778
679860
  menuState.updateItem.title = updateView.title;
679779
679861
  menuState.updateItem.tooltip = updateView.tooltip;
@@ -679842,7 +679924,7 @@ async function runTrayForeground(explicitEndpoint) {
679842
679924
  endpoint,
679843
679925
  trayWasRunning: true
679844
679926
  });
679845
- updateView = trayUpdatePresentation(currentVersion, targetVersion, readUpdateState());
679927
+ updateView = trayUpdatePresentation(currentVersion, targetVersion, readReconciledUpdateState());
679846
679928
  if (updateView.enabled) {
679847
679929
  updateView = {
679848
679930
  title: `Updating to v${targetVersion} — starting`,
@@ -679879,7 +679961,7 @@ async function runTrayForeground(explicitEndpoint) {
679879
679961
  updateView = trayUpdatePresentation(
679880
679962
  currentVersion,
679881
679963
  availableUpdate?.latestVersion,
679882
- readUpdateState()
679964
+ readReconciledUpdateState()
679883
679965
  );
679884
679966
  menuState.updateItem.title = updateView.title;
679885
679967
  menuState.updateItem.tooltip = updateView.tooltip;
@@ -703755,6 +703837,90 @@ var init_ollama_gpu_policy = __esm({
703755
703837
  }
703756
703838
  });
703757
703839
 
703840
+ // packages/cli/src/session-quality.ts
703841
+ function withoutAnsi(text2) {
703842
+ return String(text2 || "").replace(ANSI_RE, "");
703843
+ }
703844
+ function normalizeSessionDisplayText(text2) {
703845
+ return withoutAnsi(text2).trim().replace(MARKER_PREFIX_RE, "").replace(SPEAKER_RE, "").replace(/\s+/g, " ").trim();
703846
+ }
703847
+ function isSessionControlText(text2) {
703848
+ const raw = withoutAnsi(text2).trim().replace(/^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|\\.\s]+/, "").replace(SPEAKER_RE, "").trim();
703849
+ return CONTROL_ONLY_RE.test(raw);
703850
+ }
703851
+ function isSessionNoiseText(text2) {
703852
+ const raw = withoutAnsi(text2).trim();
703853
+ if (!raw || raw.length < 3 || isSessionControlText(raw)) return true;
703854
+ if (BOX_OR_TOOL_CHROME_RE.test(raw)) return true;
703855
+ const core = normalizeSessionDisplayText(raw);
703856
+ if (!core || core.length < 3 || CONTROL_ONLY_RE.test(core)) return true;
703857
+ return STATUS_RE.test(core);
703858
+ }
703859
+ function firstMeaningfulSessionLine(transcript) {
703860
+ const lines = withoutAnsi(transcript).split(/\r?\n/).map((line) => line.trim());
703861
+ for (const line of lines) {
703862
+ if (USER_LINE_RE.test(line) && !isSessionNoiseText(line)) {
703863
+ return normalizeSessionDisplayText(line);
703864
+ }
703865
+ }
703866
+ for (const line of lines) {
703867
+ if (!isSessionNoiseText(line)) return normalizeSessionDisplayText(line);
703868
+ }
703869
+ return "";
703870
+ }
703871
+ function hasMeaningfulSessionContent(transcript) {
703872
+ return firstMeaningfulSessionLine(transcript).length > 0;
703873
+ }
703874
+ function normalizedSessionFingerprint(transcript) {
703875
+ const semantic = [];
703876
+ let mode = "text";
703877
+ let inToolBox = false;
703878
+ for (const raw of withoutAnsi(transcript).split(/\r?\n/)) {
703879
+ const line = raw.trim();
703880
+ if (!line) continue;
703881
+ if (/^[╭┌]/.test(line)) {
703882
+ inToolBox = true;
703883
+ continue;
703884
+ }
703885
+ if (/^[╰└]/.test(line)) {
703886
+ inToolBox = false;
703887
+ continue;
703888
+ }
703889
+ if (inToolBox) continue;
703890
+ const user = line.match(/^(?:[▹▸►❯>]\s*|(?:User|You)\s*:\s*)(.+)$/i);
703891
+ if (user && !isSessionNoiseText(user[1] || "")) {
703892
+ mode = "user";
703893
+ semantic.push(`user:${normalizeSessionDisplayText(user[1] || "").toLowerCase()}`);
703894
+ continue;
703895
+ }
703896
+ const assistant = line.match(/^(?:(?:Assistant|Open Agent|Omnius)\s*:\s*|│\s?)(.*)$/i);
703897
+ if (assistant) {
703898
+ const content2 = normalizeSessionDisplayText((assistant[1] || "").replace(/\s?│$/, ""));
703899
+ if (content2) {
703900
+ mode = "assistant";
703901
+ semantic.push(`assistant:${content2.toLowerCase()}`);
703902
+ }
703903
+ continue;
703904
+ }
703905
+ if (isSessionNoiseText(line)) continue;
703906
+ const content = normalizeSessionDisplayText(line);
703907
+ if (content) semantic.push(`${mode}:${content.toLowerCase()}`);
703908
+ }
703909
+ return semantic.join("\n");
703910
+ }
703911
+ var ANSI_RE, SPEAKER_RE, MARKER_PREFIX_RE, USER_LINE_RE, CONTROL_ONLY_RE, BOX_OR_TOOL_CHROME_RE, STATUS_RE;
703912
+ var init_session_quality = __esm({
703913
+ "packages/cli/src/session-quality.ts"() {
703914
+ ANSI_RE = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
703915
+ SPEAKER_RE = /^(?:User|Assistant|You|Open Agent|Omnius)\s*:\s*/i;
703916
+ MARKER_PREFIX_RE = /^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|/\\.\s]+/;
703917
+ USER_LINE_RE = /^(?:[▹▸►❯>]\s+\S|(?:User|You)\s*:\s*\S)/i;
703918
+ CONTROL_ONLY_RE = /^\/?(?:q|quit|exit)$/i;
703919
+ BOX_OR_TOOL_CHROME_RE = /^[╭╮╰╯├┤┬┴┌┐└┘─━═╿│▌▐█▒░●○◐◖✔✖$]/;
703920
+ 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;
703921
+ }
703922
+ });
703923
+
703758
703924
  // packages/cli/src/tui/omnius-directory.ts
703759
703925
  var omnius_directory_exports = {};
703760
703926
  __export(omnius_directory_exports, {
@@ -705130,16 +705296,18 @@ function firstMeaningfulSessionHistoryLine(lines) {
705130
705296
  const trimmed = line.trimStart();
705131
705297
  if (!trimmed || VISUAL_CHROME_LINE.test(trimmed)) continue;
705132
705298
  const clean7 = cleanSessionHistoryDisplayLine(line);
705133
- if (!clean7 || SESSION_TITLE_STATUS_LINE.test(clean7)) continue;
705299
+ if (!clean7 || SESSION_TITLE_STATUS_LINE.test(clean7) || isSessionNoiseText(clean7)) continue;
705134
705300
  return clean7;
705135
705301
  }
705136
- return "";
705302
+ return firstMeaningfulSessionLine(lines.slice(0, 120).join("\n"));
705137
705303
  }
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;
705304
+ function sanitizeSessionHistoryEntry(repoRoot, entry, loadedLines) {
705305
+ const cleanName = cleanSessionHistoryDisplayLine(entry.name);
705306
+ const cleanDescription = cleanSessionHistoryDisplayLine(entry.description);
705307
+ const nameLooksAuthored = cleanName.length > 0 && !isSessionNoiseText(cleanName);
705308
+ const descriptionLooksAuthored = cleanDescription.length > 0 && !isSessionNoiseText(cleanDescription);
705141
705309
  if (nameLooksAuthored && descriptionLooksAuthored) return entry;
705142
- const lines = loadSessionHistory(repoRoot, entry.id) || [];
705310
+ const lines = loadedLines ?? loadSessionHistory(repoRoot, entry.id) ?? [];
705143
705311
  const fallback = firstMeaningfulSessionHistoryLine(lines);
705144
705312
  return {
705145
705313
  ...entry,
@@ -705166,14 +705334,16 @@ function saveSessionHistory(repoRoot, sessionId, contentLines, meta) {
705166
705334
  } catch {
705167
705335
  }
705168
705336
  const existing = index.findIndex((s2) => s2.id === sessionId);
705337
+ const previous = existing >= 0 ? index[existing] : void 0;
705169
705338
  const record = {
705339
+ ...previous ?? {},
705170
705340
  id: sessionId,
705171
- name: autoName,
705172
- description: autoDesc,
705173
- createdAt: existing >= 0 ? index[existing].createdAt : (/* @__PURE__ */ new Date()).toISOString(),
705341
+ name: !isSessionNoiseText(autoName) ? autoName : previous?.name ?? autoName,
705342
+ description: !isSessionNoiseText(autoDesc) ? autoDesc : previous?.description ?? autoDesc,
705343
+ createdAt: previous?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
705174
705344
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
705175
- taskCount: meta.taskCount ?? 1,
705176
- model: meta.model ?? "unknown"
705345
+ taskCount: meta.taskCount ?? previous?.taskCount ?? 1,
705346
+ model: meta.model ?? previous?.model ?? "unknown"
705177
705347
  };
705178
705348
  if (existing >= 0) {
705179
705349
  index[existing] = record;
@@ -705186,6 +705356,15 @@ function saveSessionHistory(repoRoot, sessionId, contentLines, meta) {
705186
705356
  unlinkSync28(join141(sessDir, `${removed.id}.jsonl`));
705187
705357
  } catch {
705188
705358
  }
705359
+ try {
705360
+ unlinkSync28(join141(sessDir, `${removed.id}${TUI_STATE_SUFFIX}`));
705361
+ } catch {
705362
+ }
705363
+ try {
705364
+ const mirrorId = `tui:${removed.id}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
705365
+ unlinkSync28(join141(homedir45(), ".omnius", "chat-sessions", `${mirrorId}.json`));
705366
+ } catch {
705367
+ }
705189
705368
  }
705190
705369
  writeFileSync70(indexPath, JSON.stringify(index, null, 2), "utf-8");
705191
705370
  }
@@ -705222,7 +705401,13 @@ function listSessions(repoRoot) {
705222
705401
  try {
705223
705402
  if (!existsSync130(indexPath)) return [];
705224
705403
  const index = JSON.parse(readFileSync107(indexPath, "utf-8"));
705225
- return index.map((entry) => sanitizeSessionHistoryEntry(repoRoot, entry)).sort((a2, b) => b.updatedAt.localeCompare(a2.updatedAt));
705404
+ const eligible = [];
705405
+ for (const entry of index) {
705406
+ const lines = loadSessionHistory(repoRoot, entry.id) ?? [];
705407
+ if (!hasMeaningfulSessionContent(lines.join("\n"))) continue;
705408
+ eligible.push(sanitizeSessionHistoryEntry(repoRoot, entry, lines));
705409
+ }
705410
+ return eligible.sort((a2, b) => b.updatedAt.localeCompare(a2.updatedAt));
705226
705411
  } catch {
705227
705412
  return [];
705228
705413
  }
@@ -705244,6 +705429,9 @@ function deleteSession(repoRoot, sessionId) {
705244
705429
  if (existsSync130(contentPath)) unlinkSync28(contentPath);
705245
705430
  const statePath = join141(sessDir, `${sessionId}${TUI_STATE_SUFFIX}`);
705246
705431
  if (existsSync130(statePath)) unlinkSync28(statePath);
705432
+ const mirrorId = `tui:${sessionId}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
705433
+ const mirrorPath = join141(homedir45(), ".omnius", "chat-sessions", `${mirrorId}.json`);
705434
+ if (existsSync130(mirrorPath)) unlinkSync28(mirrorPath);
705247
705435
  if (existsSync130(indexPath)) {
705248
705436
  let index = JSON.parse(readFileSync107(indexPath, "utf-8"));
705249
705437
  index = index.filter((s2) => s2.id !== sessionId);
@@ -705481,6 +705669,7 @@ var init_omnius_directory = __esm({
705481
705669
  "packages/cli/src/tui/omnius-directory.ts"() {
705482
705670
  init_dist5();
705483
705671
  init_task_complete_box();
705672
+ init_session_quality();
705484
705673
  OMNIUS_DIR2 = ".omnius";
705485
705674
  LEGACY_DIRS = [".oa", ".open-agents"];
705486
705675
  SUBDIRS = ["memory", "index", "context", "history", "notes", "embedded", "provenance", "tools", "dreams"];
@@ -705596,29 +705785,12 @@ var init_omnius_directory = __esm({
705596
705785
  });
705597
705786
 
705598
705787
  // 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
705788
  function clamp9(value2, max) {
705617
705789
  const v = value2.replace(/\s+/g, " ").trim();
705618
705790
  return v.length > max ? v.slice(0, max - 1).trimEnd() + "…" : v;
705619
705791
  }
705620
705792
  function deterministicSummary(transcript) {
705621
- const first2 = firstMeaningfulLine(transcript);
705793
+ const first2 = firstMeaningfulSessionLine(transcript);
705622
705794
  if (!first2) return { title: "Untitled session", summary: "Empty session." };
705623
705795
  const cleaned = first2.replace(TUI_MARKER_PREFIX, "").replace(/^[>›$#\s]+/, "").trim();
705624
705796
  const title = clamp9(cleaned, TITLE_MAX);
@@ -705640,7 +705812,7 @@ function parseSummaryReply(content) {
705640
705812
  }
705641
705813
  async function generateSessionSummary(args) {
705642
705814
  const fallback = deterministicSummary(args.transcript);
705643
- const transcript = (args.transcript || "").replace(ANSI_RE, "").trim();
705815
+ const transcript = (args.transcript || "").replace(ANSI_RE2, "").trim();
705644
705816
  if (!transcript || !args.config.model || !args.config.backendUrl) return fallback;
705645
705817
  try {
705646
705818
  const url = normalizeBaseUrl(args.config.backendUrl) + "/v1/chat/completions";
@@ -705673,8 +705845,9 @@ async function generateSessionSummary(args) {
705673
705845
  const content = data?.choices?.[0]?.message?.content ?? "";
705674
705846
  const parsed = parseSummaryReply(content);
705675
705847
  if (!parsed) return fallback;
705848
+ const parsedTitle = clamp9(parsed.title, TITLE_MAX);
705676
705849
  return {
705677
- title: clamp9(parsed.title, TITLE_MAX) || fallback.title,
705850
+ title: parsedTitle && !isSessionNoiseText(parsedTitle) ? parsedTitle : fallback.title,
705678
705851
  summary: clamp9(parsed.summary, SUMMARY_MAX) || fallback.summary
705679
705852
  };
705680
705853
  } catch {
@@ -705685,7 +705858,7 @@ async function ensureSessionSummary(args) {
705685
705858
  const entry = listSessions(args.repoRoot).find(
705686
705859
  (e2) => e2.id === args.sessionId
705687
705860
  );
705688
- if (entry?.aiTitle && entry.aiSummary && !args.force) {
705861
+ if (entry?.aiTitle && entry.aiSummary && !isSessionNoiseText(entry.aiTitle) && !args.force) {
705689
705862
  return { title: entry.aiTitle, summary: entry.aiSummary };
705690
705863
  }
705691
705864
  const lines = loadSessionHistory(args.repoRoot, args.sessionId);
@@ -705711,19 +705884,20 @@ async function ensureSessionSummary(args) {
705711
705884
  _inflightSummaries.delete(key);
705712
705885
  }
705713
705886
  }
705714
- function sessionDisplayTitle(entry) {
705715
- if (entry.aiTitle && entry.aiTitle.trim()) return entry.aiTitle.trim();
705716
- return deterministicSummary(entry.name || "").title;
705887
+ function sessionDisplayTitle(entry, transcript = "") {
705888
+ if (entry.aiTitle && !isSessionNoiseText(entry.aiTitle)) return entry.aiTitle.trim();
705889
+ return deterministicSummary(transcript || [entry.name, entry.description].filter(Boolean).join("\n")).title;
705717
705890
  }
705718
- var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE, TUI_MARKER_PREFIX, _inflightSummaries;
705891
+ var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE2, TUI_MARKER_PREFIX, _inflightSummaries;
705719
705892
  var init_session_summary = __esm({
705720
705893
  "packages/cli/src/api/session-summary.ts"() {
705721
705894
  init_dist6();
705722
705895
  init_omnius_directory();
705896
+ init_session_quality();
705723
705897
  TITLE_MAX = 56;
705724
705898
  SUMMARY_MAX = 160;
705725
705899
  TRANSCRIPT_CHARS = 6e3;
705726
- ANSI_RE = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
705900
+ ANSI_RE2 = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
705727
705901
  TUI_MARKER_PREFIX = /^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|/\\.\s]+/;
705728
705902
  _inflightSummaries = /* @__PURE__ */ new Set();
705729
705903
  }
@@ -706617,7 +706791,7 @@ function paintBlockBorder(glyphs, stage2, phase, truecolor, startCol = 0) {
706617
706791
  return `${out}\x1B[0m`;
706618
706792
  }
706619
706793
  function sanitizeSubAgentActivity(value2) {
706620
- return value2.replace(ANSI_RE2, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_ACTIVITY_CHARS);
706794
+ return value2.replace(ANSI_RE3, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_ACTIVITY_CHARS);
706621
706795
  }
706622
706796
  function appendSubAgentActivity(entry, value2, maxLines = 3) {
706623
706797
  const line = sanitizeSubAgentActivity(value2);
@@ -706671,7 +706845,7 @@ function contentRow(value2, width, stage2, phase, truecolor) {
706671
706845
  return `${left} ${fit2(value2, width)} ${right}`;
706672
706846
  }
706673
706847
  function fit2(value2, width) {
706674
- const plain = value2.replace(ANSI_RE2, "").replace(/\s+$/g, "");
706848
+ const plain = value2.replace(ANSI_RE3, "").replace(/\s+$/g, "");
706675
706849
  const chars = Array.from(plain);
706676
706850
  if (chars.length > width) {
706677
706851
  return `${chars.slice(0, Math.max(0, width - 1)).join("")}…`;
@@ -706691,11 +706865,11 @@ function statusIcon(status) {
706691
706865
  return "●";
706692
706866
  }
706693
706867
  }
706694
- var ANSI_RE2, MAX_PREVIEW_AGENTS, MAX_ACTIVITY_CHARS, STAGE_FALLBACK_COLOR, BORDER_GRADIENT_SEG;
706868
+ var ANSI_RE3, MAX_PREVIEW_AGENTS, MAX_ACTIVITY_CHARS, STAGE_FALLBACK_COLOR, BORDER_GRADIENT_SEG;
706695
706869
  var init_sub_agent_live_block = __esm({
706696
706870
  "packages/cli/src/tui/sub-agent-live-block.ts"() {
706697
706871
  init_stageIndicator();
706698
- ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706872
+ ANSI_RE3 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706699
706873
  MAX_PREVIEW_AGENTS = 4;
706700
706874
  MAX_ACTIVITY_CHARS = 220;
706701
706875
  STAGE_FALLBACK_COLOR = {
@@ -706815,19 +706989,19 @@ function row(value2, width, stage2, phase, truecolor) {
706815
706989
  return `${left} ${fit3(value2, width)} ${right}`;
706816
706990
  }
706817
706991
  function fit3(value2, width) {
706818
- const clean7 = value2.replace(ANSI_RE3, "").replace(/\s+/g, " ").trim();
706992
+ const clean7 = value2.replace(ANSI_RE4, "").replace(/\s+/g, " ").trim();
706819
706993
  const chars = Array.from(clean7);
706820
706994
  if (chars.length > width) {
706821
706995
  return `${chars.slice(0, Math.max(0, width - 1)).join("")}…`;
706822
706996
  }
706823
706997
  return clean7 + " ".repeat(Math.max(0, width - chars.length));
706824
706998
  }
706825
- var ANSI_RE3;
706999
+ var ANSI_RE4;
706826
707000
  var init_trajectory_live_block = __esm({
706827
707001
  "packages/cli/src/tui/trajectory-live-block.ts"() {
706828
707002
  init_stageIndicator();
706829
707003
  init_sub_agent_live_block();
706830
- ANSI_RE3 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
707004
+ ANSI_RE4 = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)/g;
706831
707005
  }
706832
707006
  });
706833
707007
 
@@ -719818,7 +719992,7 @@ export PATH="${binDir}:$PATH" # Added by omnius for nvim
719818
719992
  } catch {
719819
719993
  }
719820
719994
  }
719821
- var execAsync2, OMNIUS_FIRST_RUN_BANNER, ANSI_RE4, visibleLen2, SETUP_MODEL_VARIANTS, _toolSupportCache, EXPANDED_VARIANT_MIN_NUM_CTX, _cloudflaredInstallPromise;
719995
+ var execAsync2, OMNIUS_FIRST_RUN_BANNER, ANSI_RE5, visibleLen2, SETUP_MODEL_VARIANTS, _toolSupportCache, EXPANDED_VARIANT_MIN_NUM_CTX, _cloudflaredInstallPromise;
719822
719996
  var init_setup = __esm({
719823
719997
  "packages/cli/src/tui/setup.ts"() {
719824
719998
  init_model_picker();
@@ -719838,8 +720012,8 @@ var init_setup = __esm({
719838
720012
  "░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ ",
719839
720013
  " ░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓██████▓▒░░▒▓███████▓▒░ "
719840
720014
  ].join("\n");
719841
- ANSI_RE4 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
719842
- visibleLen2 = (value2) => Array.from(value2.replace(ANSI_RE4, "")).length;
720015
+ ANSI_RE5 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
720016
+ visibleLen2 = (value2) => Array.from(value2.replace(ANSI_RE5, "")).length;
719843
720017
  SETUP_MODEL_VARIANTS = [
719844
720018
  { tag: "robit/ornith:9b", sizeGB: 6.6, label: "9B params (6.6 GB) - recommended minimum", cloud: false },
719845
720019
  { tag: "robit/ornith:35b", sizeGB: 24, label: "35B params (24 GB) - recommended on 32GB+ unified memory/VRAM", cloud: false }
@@ -747847,8 +748021,12 @@ async function handleUpdate(subcommand, ctx3) {
747847
748021
  renderInfo(`Omnius v${currentVersion} is already up to date.`);
747848
748022
  return;
747849
748023
  }
748024
+ const updateOverlay = startInstallOverlay(info.latestVersion);
748025
+ updateOverlay.setPhase("Preparing");
748026
+ updateOverlay.setProgress(0, 6);
748027
+ updateOverlay.setStatus("starting coordinated global update");
747850
748028
  try {
747851
- const [{ startDetachedGlobalUpdate: startDetachedGlobalUpdate2 }, { getTrayStatus: getTrayStatus2 }] = await Promise.all([
748029
+ const [{ startDetachedGlobalUpdate: startDetachedGlobalUpdate2, waitForUpdateTransaction: waitForUpdateTransaction2 }, { getTrayStatus: getTrayStatus2 }] = await Promise.all([
747852
748030
  Promise.resolve().then(() => (init_update_service(), update_service_exports)),
747853
748031
  Promise.resolve().then(() => (init_tray(), tray_exports))
747854
748032
  ]);
@@ -747859,11 +748037,52 @@ async function handleUpdate(subcommand, ctx3) {
747859
748037
  endpoint: trayStatus.endpoint,
747860
748038
  trayWasRunning: trayStatus.running
747861
748039
  });
748040
+ const phasePresentation = {
748041
+ queued: { label: "Preparing", progress: 0, status: "update worker queued" },
748042
+ installing: { label: "Package", progress: 1, status: `installing omnius@${info.latestVersion}` },
748043
+ package_verified: { label: "Verification", progress: 2, status: "global package and executable verified" },
748044
+ daemon_restarting: { label: "Daemon", progress: 3, status: "restarting shared daemon" },
748045
+ runtime_verified: { label: "Runtime", progress: 4, status: "daemon version and package hash verified" },
748046
+ tray_restarting: { label: "Indicator", progress: 5, status: "restarting system indicator" },
748047
+ completed: { label: "Complete", progress: 6, status: "global runtime update verified" },
748048
+ failed: { label: "Failed", progress: 6, status: "update verification failed" }
748049
+ };
748050
+ const marker = `[omnius update ${started.state.operation_id}]`;
748051
+ const terminal = await waitForUpdateTransaction2(
748052
+ started.state.operation_id,
748053
+ {
748054
+ pollIntervalMs: 250,
748055
+ timeoutMs: 10 * 6e4,
748056
+ onProgress(snapshot) {
748057
+ const view = phasePresentation[snapshot.state.phase];
748058
+ updateOverlay.setPhase(view.label);
748059
+ updateOverlay.setProgress(view.progress, 6);
748060
+ const operationLog = snapshot.log_tail.includes(marker) ? snapshot.log_tail.slice(snapshot.log_tail.lastIndexOf(marker) + marker.length) : snapshot.log_tail;
748061
+ const liveLine = operationLog.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !/^npm notice/i.test(line)).at(-1);
748062
+ updateOverlay.setStatus((liveLine || view.status).slice(0, 100));
748063
+ }
748064
+ }
748065
+ );
748066
+ if (terminal.status === "failed") {
748067
+ updateOverlay.dismiss();
748068
+ renderError(`Update to v${info.latestVersion} failed: ${terminal.error || "unknown error"}`);
748069
+ if (terminal.remediation) renderWarning(terminal.remediation);
748070
+ return;
748071
+ }
748072
+ updateOverlay.stop("Package, daemon, and indicator verified");
748073
+ await new Promise((resolve87) => setTimeout(resolve87, 1e3));
748074
+ updateOverlay.dismiss();
747862
748075
  renderInfo(
747863
- `Global update to v${info.latestVersion} started (${started.state.operation_id.slice(0, 8)}). Track exact install/runtime verification at ${started.state.log_path}.`
748076
+ `Updated Omnius v${currentVersion} v${terminal.installed_version ?? info.latestVersion}; daemon v${terminal.daemon_version ?? info.latestVersion}${terminal.tray_was_running ? ", indicator restarted" : ""}.`
747864
748077
  );
748078
+ ctx3.contextSave?.();
748079
+ ctx3.savePendingTaskState?.();
748080
+ if (ctx3.hasActiveTask?.()) ctx3.abortActiveTask?.();
748081
+ ctx3.killEphemeral?.({ preserveInfrastructure: true });
748082
+ process.exit(120);
747865
748083
  } catch (error) {
747866
- renderError(`Update failed to start: ${error instanceof Error ? error.message : String(error)}`);
748084
+ updateOverlay.dismiss();
748085
+ renderError(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
747867
748086
  }
747868
748087
  return;
747869
748088
  }
@@ -749568,21 +749787,13 @@ function stripAnsi6(text2) {
749568
749787
  return String(text2 || "").replace(/\u001b\[[0-9;]*[a-zA-Z]/g, "");
749569
749788
  }
749570
749789
  function cleanSessionDisplayLine(line) {
749571
- return stripAnsi6(line).replace(/^[>❯▹∙•*+\-\s]+/, "").replace(/^(?:User|Assistant|You|Open Agent|Omnius)\s*:\s*/i, "").replace(/\s+/g, " ").trim();
749790
+ return normalizeSessionDisplayText(stripAnsi6(line));
749572
749791
  }
749573
749792
  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);
749793
+ return isSessionNoiseText(line || "") || /^(?:\[Imported TUI session transcript\]|Title:|Description:|Project root:|i\s+)/i.test(cleanSessionDisplayLine(line || ""));
749577
749794
  }
749578
749795
  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;
749796
+ return firstMeaningfulSessionLine(stripAnsi6(text2));
749586
749797
  }
749587
749798
  function makeTitle(text2) {
749588
749799
  const clean7 = bestSessionDisplayLine(text2);
@@ -749796,7 +750007,7 @@ function importTranscriptSession(opts) {
749796
750007
  "",
749797
750008
  cappedTranscript || "(empty transcript)"
749798
750009
  ].filter(Boolean).join("\n");
749799
- const visibleNotice = `Loaded TUI session "${title}". Its transcript is attached as context for this chat.`;
750010
+ const isImportedNotice = (message2) => message2.role === "assistant" && /^Loaded TUI session ".*"\. Its transcript is attached as context for this chat\.$/.test(message2.content);
749800
750011
  const existing = lookupSession(id2);
749801
750012
  if (existing) {
749802
750013
  existing.projectRoot = projectRoot;
@@ -749806,14 +750017,12 @@ function importTranscriptSession(opts) {
749806
750017
  existing.preview = preview;
749807
750018
  existing.transcript = cappedTranscript || "(empty transcript)";
749808
750019
  existing.lastActivity = opts.updatedAt ?? Date.now();
750020
+ existing.messages = existing.messages.filter((message2) => !isImportedNotice(message2));
749809
750021
  const idx = existing.messages.findIndex(
749810
750022
  (m2) => m2.role === "system" && m2.content.startsWith("[Imported TUI session transcript]")
749811
750023
  );
749812
750024
  if (idx >= 0) existing.messages[idx] = { role: "system", content: importedContext };
749813
750025
  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
750026
  persistSession(existing);
749818
750027
  return existing;
749819
750028
  }
@@ -749822,8 +750031,7 @@ function importTranscriptSession(opts) {
749822
750031
  id: id2,
749823
750032
  messages: [
749824
750033
  { role: "system", content: buildSystemPrompt(projectRoot) },
749825
- { role: "system", content: importedContext },
749826
- { role: "assistant", content: visibleNotice }
750034
+ { role: "system", content: importedContext }
749827
750035
  ],
749828
750036
  model: opts.model || "unknown",
749829
750037
  createdAt: opts.createdAt ?? opts.updatedAt ?? now2,
@@ -749968,6 +750176,9 @@ function listSessions2(opts = {}) {
749968
750176
  if (!root) return true;
749969
750177
  if (!s2.projectRoot) return !!opts.includeUnscoped;
749970
750178
  return normalizeRoot(s2.projectRoot) === root;
750179
+ }).filter((s2) => {
750180
+ const authoredTurns = s2.messages.filter((message2) => message2.role === "user").map((message2) => message2.content).join("\n");
750181
+ return hasMeaningfulSessionContent(authoredTurns) || hasMeaningfulSessionContent(s2.transcript || "") || !!s2.title && !isNoisySessionDisplayLine(s2.title);
749971
750182
  }).sort((a2, b) => b.lastActivity - a2.lastActivity).map((s2) => ({
749972
750183
  id: s2.id,
749973
750184
  model: s2.model,
@@ -750076,6 +750287,7 @@ var sessions2, inFlight, SESSION_TTL_MS, INFERENCE_ROLES, PARTIAL_TAIL_BUDGET;
750076
750287
  var init_chat_session = __esm({
750077
750288
  "packages/cli/src/api/chat-session.ts"() {
750078
750289
  init_secret_redactor();
750290
+ init_session_quality();
750079
750291
  sessions2 = /* @__PURE__ */ new Map();
750080
750292
  inFlight = /* @__PURE__ */ new Map();
750081
750293
  SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -796406,8 +796618,10 @@ async function loadServerPrefs() {
796406
796618
  function isNoisyChatSessionText(text) {
796407
796619
  const clean = String(text || '').replace(/\\s+/g, ' ').trim();
796408
796620
  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);
796621
+ const raw = clean.replace(/^[>❯▹∙•\\-\\s]+/, '');
796622
+ if (/^\\/?(?:q|quit|exit)$/i.test(raw)) return true;
796623
+ const t = raw.replace(/^\\/+/, '');
796624
+ 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
796625
  }
796412
796626
 
796413
796627
  function isGenericChatTitle(id, title) {
@@ -796424,10 +796638,11 @@ function sessionDisplayTitle(id, session) {
796424
796638
  if (!isGenericChatTitle(id, candidate)) return String(candidate).trim();
796425
796639
  }
796426
796640
  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);
796641
+ for (const preferredRole of ['user', 'assistant']) {
796642
+ for (const msg of s.messages) {
796643
+ const content = msg && msg.role === preferredRole && typeof msg.content === 'string' ? msg.content : '';
796644
+ if (!isGenericChatTitle(id, content)) return content.replace(/\\s+/g, ' ').trim().slice(0, 72);
796645
+ }
796431
796646
  }
796432
796647
  }
796433
796648
  return String(id || '').startsWith('tui:') ? 'TUI session ' + String(id).slice(4, 16) : 'Chat ' + String(id || '').slice(0, 8);
@@ -796441,6 +796656,14 @@ function sessionDisplayPreview(session) {
796441
796656
  return '';
796442
796657
  }
796443
796658
 
796659
+ function isEligibleChatSession(id, session) {
796660
+ const s = session || {};
796661
+ if (![s.title, s.name, s.preview].every(candidate => isGenericChatTitle(id, candidate))) return true;
796662
+ return Array.isArray(s.messages) && s.messages.some(message =>
796663
+ message && message.role === 'user' && !isNoisyChatSessionText(message.content)
796664
+ );
796665
+ }
796666
+
796444
796667
  async function loadServerChatSessions() {
796445
796668
  const root = $currentProject.get()?.root || '';
796446
796669
  if (!root) return;
@@ -796449,23 +796672,30 @@ async function loadServerChatSessions() {
796449
796672
  if (!r.ok) return;
796450
796673
  const data = await r.json();
796451
796674
  const local = loadScopedSessions();
796452
- const merged = { ...local };
796675
+ // A successful canonical load retires local TUI summaries that the server
796676
+ // no longer considers sessions. Keep eligible browser-native chats so an
796677
+ // unsent/local conversation is never lost during reconciliation.
796678
+ const merged = {};
796679
+ for (const [id, session] of Object.entries(local)) {
796680
+ const isTui = String(id).startsWith('tui:') || session?.source === 'tui';
796681
+ if (!isTui && isEligibleChatSession(id, session)) merged[id] = session;
796682
+ }
796453
796683
  for (const sess of (data.sessions || [])) {
796454
796684
  if (!sess || !sess.id) continue;
796455
- const existing = merged[sess.id] || {};
796685
+ const existing = local[sess.id] || merged[sess.id] || {};
796456
796686
  const serverTitle = sess.title || sess.preview || '';
796457
796687
  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));
796688
+ const title = !isGenericChatTitle(sess.id, serverTitle)
796689
+ ? serverTitle
796690
+ : (!isGenericChatTitle(sess.id, existingTitle) ? existingTitle : sessionDisplayTitle(sess.id, sess));
796691
+ const preview = !isNoisyChatSessionText(sess.preview)
796692
+ ? sess.preview
796693
+ : (!isNoisyChatSessionText(existing.preview) ? existing.preview : sessionDisplayPreview(sess));
796464
796694
  merged[sess.id] = {
796465
796695
  ...existing,
796466
796696
  id: sess.id,
796467
796697
  title,
796468
- name: !isGenericChatTitle(sess.id, existing.name) ? existing.name : title,
796698
+ name: title,
796469
796699
  preview,
796470
796700
  model: existing.model || sess.model || '',
796471
796701
  source: sess.source || existing.source || 'web',
@@ -796474,7 +796704,14 @@ async function loadServerChatSessions() {
796474
796704
  messages: existing.messages || [],
796475
796705
  };
796476
796706
  }
796707
+ saveScopedSessions(merged);
796477
796708
  $chatSessions.set(merged);
796709
+ if (chatSessionId && !merged[chatSessionId]) {
796710
+ $chatSessionId.set(null);
796711
+ messages = [];
796712
+ const conversation = document.getElementById('conversation');
796713
+ if (conversation) conversation.innerHTML = '';
796714
+ }
796478
796715
  updateSessionSelect();
796479
796716
  } catch {}
796480
796717
  }
@@ -798918,7 +799155,7 @@ function chatSessionFromRouteSearch(search) {
798918
799155
  }
798919
799156
  function syncRouteForTab(tab, replace) {
798920
799157
  const path = routePathForTab(tab);
798921
- const query = tab === 'chat' && chatSessionId ? '?' + encodeURIComponent(chatSessionId) : '';
799158
+ const query = tab === 'chat' && chatSessionId ? '?session=' + encodeURIComponent(chatSessionId) : '';
798922
799159
  const next = path + query;
798923
799160
  if ((location.pathname + location.search) === next) return;
798924
799161
  const method = replace ? 'replaceState' : 'pushState';
@@ -799519,6 +799756,7 @@ window.addEventListener('popstate', () => {
799519
799756
  if (tab === 'chat') {
799520
799757
  const sid = chatSessionFromRouteSearch(location.search);
799521
799758
  if (sid && sid !== chatSessionId) switchSession(sid);
799759
+ else if (!sid && chatSessionId) switchSession('');
799522
799760
  }
799523
799761
  switchTab(tab, { fromRoute: true });
799524
799762
  });
@@ -800418,6 +800656,7 @@ function updateSessionSelect() {
800418
800656
  const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800419
800657
  const saved = { ...storeSessions, ...loadScopedSessions() };
800420
800658
  const entries = Object.entries(saved)
800659
+ .filter(([id, session]) => isEligibleChatSession(id, session))
800421
800660
  .sort((a, b) => (b[1].updatedAt || '').localeCompare(a[1].updatedAt || ''))
800422
800661
  .slice(0, 20);
800423
800662
  for (const sel of targets) {
@@ -800437,8 +800676,8 @@ function updateSessionSelect() {
800437
800676
  // It delegates to the existing switchSession() so chat history restoration
800438
800677
  // still works exactly as before.
800439
800678
  function switchChatSession(id) {
800440
- switchSession(id);
800441
800679
  switchTab('chat', { replaceRoute: true });
800680
+ switchSession(id);
800442
800681
  }
800443
800682
  function newChatSession() {
800444
800683
  switchSession('');
@@ -800615,7 +800854,8 @@ function switchSession(id) {
800615
800854
  return;
800616
800855
  }
800617
800856
  const saved = loadScopedSessions();
800618
- const s = saved[id];
800857
+ const storeSessions = ($chatSessions.get && $chatSessions.get()) || {};
800858
+ const s = saved[id] || storeSessions[id];
800619
800859
  if (s) {
800620
800860
  chatSessionId = id;
800621
800861
  messages = s.messages || [];
@@ -800667,6 +800907,9 @@ function switchSession(id) {
800667
800907
  updateSessionSelect();
800668
800908
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800669
800909
  try { refreshTodos(id); } catch {}
800910
+ // Local entries are only a fast paint. Always re-fetch the canonical
800911
+ // session so summary-only or stale caches cannot suppress real history.
800912
+ void restoreChatSession();
800670
800913
  } else {
800671
800914
  // Server-backed sessions (including imported TUI sessions) may only be
800672
800915
  // present in $chatSessions, not localStorage. Activate and let the daemon
@@ -800675,7 +800918,7 @@ function switchSession(id) {
800675
800918
  syncRouteForTab('chat', true);
800676
800919
  updateSessionSelect();
800677
800920
  try { _renderSidebarChats(_lastSidebarFilter || ''); } catch {}
800678
- restoreChatSession();
800921
+ void restoreChatSession();
800679
800922
  }
800680
800923
  }
800681
800924
 
@@ -802332,6 +802575,7 @@ function renderRecoveredTranscript(raw, conv, title) {
802332
802575
  let mode = null; // 'user' | 'assistant'
802333
802576
  let buf = [];
802334
802577
  let toolCount = 0;
802578
+ let inToolBox = false;
802335
802579
  const out = []; // [{role, text}] or {tools:n}
802336
802580
  const flush = () => {
802337
802581
  const text = buf.join(NL).trim();
@@ -802346,22 +802590,29 @@ function renderRecoveredTranscript(raw, conv, title) {
802346
802590
  line = line.replace(sentRe, '');
802347
802591
  const t = line.trim();
802348
802592
  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
802593
+ // Tool/task panels also use │. Track their box boundaries so their labels,
802594
+ // commands, and output cannot be mistaken for assistant prose.
802595
+ if (/^[╭┌]/.test(t)) { flush(); inToolBox = true; continue; }
802596
+ if (/^[╰└]/.test(t)) { inToolBox = false; continue; }
802597
+ if (inToolBox) continue;
802598
+ if (/^[─━═╿\\s]+$/.test(t)) continue;
802599
+ const userMatch = t.match(/^(?:[▹❯>]\\s*|(?:User|You)\\s*:\\s*)(.+)$/i);
802600
+ if (userMatch) {
802601
+ if (isNoisyChatSessionText(userMatch[1])) { flush(); mode = null; continue; }
802352
802602
  flush(); if (toolCount) { out.push({ tools: toolCount }); toolCount = 0; }
802353
- out.push({ role: 'user', text: t.replace(/^[▹>]s?/, '') }); mode = null; continue;
802603
+ mode = 'user'; buf.push(userMatch[1]); continue;
802354
802604
  }
802355
- if (/^│/.test(t)) { // assistant / box content
802605
+ const assistantMatch = t.match(/^(?:(?:Assistant|Open Agent|Omnius)\\s*:\\s*|│\\s?)(.*)$/i);
802606
+ if (assistantMatch) {
802356
802607
  if (mode !== 'assistant') { flush(); mode = 'assistant'; }
802357
- buf.push(t.replace(/^│s?/, '').replace(/s?│$/, '')); continue; // strip both borders
802608
+ buf.push(assistantMatch[1].replace(/\\s?│$/, '')); continue;
802358
802609
  }
802359
- if (/^[∙!]/.test(t)) continue; // status / internal noise — drop
802360
- if (mode === 'assistant') buf.push(t); // continuation
802610
+ if (/^[∙!EW⚠]/.test(t) || isNoisyChatSessionText(t)) continue;
802611
+ if (mode) buf.push(t); // wrapped authored continuation
802361
802612
  }
802362
802613
  flush(); if (toolCount) out.push({ tools: toolCount });
802363
802614
 
802364
- if (out.length === 0) return; // nothing meaningful — show nothing rather than garbage
802615
+ if (out.length === 0) return []; // nothing meaningful — show nothing rather than garbage
802365
802616
  const note = document.createElement('div');
802366
802617
  note.style.cssText = 'font-size:0.6rem;color:var(--color-fg-faint);margin:4px 0 8px;text-align:center';
802367
802618
  note.textContent = 'recovered session' + (title ? ' — ' + title : '');
@@ -802376,6 +802627,9 @@ function renderRecoveredTranscript(raw, conv, title) {
802376
802627
  addMessage(item.role, item.text);
802377
802628
  }
802378
802629
  }
802630
+ return out
802631
+ .filter(item => !item.tools && (item.role === 'user' || item.role === 'assistant'))
802632
+ .map(item => ({ role: item.role, content: item.text }));
802379
802633
  }
802380
802634
  window.renderRecoveredTranscript = renderRecoveredTranscript;
802381
802635
 
@@ -802389,6 +802643,7 @@ async function restoreChatSession() {
802389
802643
  const root = $currentProject.get()?.root || '';
802390
802644
  const query = root ? '?root=' + encodeURIComponent(root) : '';
802391
802645
  const r = await fetch('/v1/chat/sessions/' + encodeURIComponent(sid) + query, { headers: headers() });
802646
+ if (chatSessionId !== sid) return;
802392
802647
  if (!r.ok) {
802393
802648
  if (r.status === 404) {
802394
802649
  // Server lost the session (e.g. daemon restart) — clear via store
@@ -802398,16 +802653,17 @@ async function restoreChatSession() {
802398
802653
  return;
802399
802654
  }
802400
802655
  const data = await r.json();
802656
+ if (chatSessionId !== sid) return;
802401
802657
  if (!data || !data.id) return;
802402
802658
  chatSessionId = data.id;
802403
802659
  window.currentSessionId = data.id;
802404
802660
  // Keep the in-memory messages array clean (only user/assistant) so
802405
802661
  // the next inference call sees a valid conversation. Tool events
802406
802662
  // 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 }));
802663
+ const allMessages = (data.messages || []).filter(m => !(
802664
+ m.role === 'assistant' && /^Loaded TUI session ".*"\\. Its transcript is attached as context for this chat\\.$/.test(String(m.content || ''))
802665
+ ));
802666
+ let recoveredMessages = [];
802411
802667
  const conv = document.getElementById('conversation');
802412
802668
  if (conv) {
802413
802669
  conv.innerHTML = '';
@@ -802415,15 +802671,15 @@ async function restoreChatSession() {
802415
802671
  // decisions, sub-agent activity — everything that scrolled past in the
802416
802672
  // TUI) as a visible, scrollable block at the top so opening the session
802417
802673
  // 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)) {
802674
+ if (data.source === 'tui' && data.transcript && String(data.transcript).trim()
802675
+ && data.transcript !== '(empty transcript)') {
802420
802676
  // TUI/raw transcript: PARSE the rendered-TUI scrollback into real chat
802421
802677
  // bubbles instead of dumping the raw log. The persisted log carries
802422
802678
  // DYNBLOCK:<id> sentinels (dynamic blocks expanded only at TUI
802423
802679
  // paint time — no content here) plus chrome (▹ user, │ assistant,
802424
802680
  // ∙ status, ! internal). We strip the dead sentinels + noise and emit
802425
802681
  // user/assistant messages so it reads like a GUI chat.
802426
- renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802682
+ recoveredMessages = renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
802427
802683
  }
802428
802684
  // WO-CHAT-RESUME-TOOLS — replay the FULL intermediate flow on
802429
802685
  // restore: user/assistant text bubbles AND tool_call/tool_result
@@ -802471,6 +802727,12 @@ async function restoreChatSession() {
802471
802727
  }
802472
802728
  }
802473
802729
  }
802730
+ messages = [
802731
+ ...recoveredMessages,
802732
+ ...allMessages
802733
+ .filter(m => m.role === 'user' || m.role === 'assistant')
802734
+ .map(m => ({ role: m.role, content: m.content })),
802735
+ ];
802474
802736
  try {
802475
802737
  const saved = loadScopedSessions();
802476
802738
  saved[data.id] = {
@@ -803632,7 +803894,7 @@ function _renderSidebarChats(filter) {
803632
803894
  _renderSidebarFoldersToolbar();
803633
803895
 
803634
803896
  const sessions = (typeof $chatSessions !== 'undefined' && $chatSessions.get) ? $chatSessions.get() : {};
803635
- const allIds = Object.keys(sessions || {});
803897
+ const allIds = Object.keys(sessions || {}).filter(id => isEligibleChatSession(id, sessions[id]));
803636
803898
  const q = (filter || '').trim().toLowerCase();
803637
803899
  const activeId = chatSessionId || (($chatSessionId.get && $chatSessionId.get()) || null);
803638
803900
 
@@ -803644,8 +803906,8 @@ function _renderSidebarChats(filter) {
803644
803906
  // Sort helper
803645
803907
  const sortByRecency = (a, b) => {
803646
803908
  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;
803909
+ const ta = sa.updatedAt || sa.updated_at || sa.createdAt || sa.created_at || a;
803910
+ const tb = sb.updatedAt || sb.updated_at || sb.createdAt || sb.created_at || b;
803649
803911
  return String(tb).localeCompare(String(ta));
803650
803912
  };
803651
803913
  allIds.sort(sortByRecency);
@@ -803679,7 +803941,7 @@ function _renderSidebarChats(filter) {
803679
803941
  const cls = 'sb-chat' + (id === activeId ? ' active' : '');
803680
803942
  const safeId = String(id).replace(/'/g, "\\\\'");
803681
803943
  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 + '">' +
803944
+ return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchChatSession(\\'' + safeId + '\\')" title="' + safeTitle + '">' +
803683
803945
  '<span class="sb-chat-title">' + safeTitle + '</span>' +
803684
803946
  '<button class="sb-chat-menu" onclick="_showChatRowMenu(event,\\'' + safeId + '\\')" title="More">&#x22EE;</button>' +
803685
803947
  '</div>';
@@ -814360,17 +814622,30 @@ ${historyLines}
814360
814622
  if (!checkAuth(req3, res, "read")) return;
814361
814623
  const queriedRoot = urlObj.searchParams.get("root");
814362
814624
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814363
- const sessions3 = listSessions2({ projectRoot: targetRoot });
814364
814625
  const includeTui = urlObj.searchParams.get("include_tui") !== "0";
814626
+ const tuiHistory = includeTui && targetRoot ? listSessions(targetRoot) : [];
814627
+ const canonicalTuiIds = new Set(tuiHistory.map((entry) => `tui:${entry.id}`));
814628
+ const sessions3 = listSessions2({ projectRoot: targetRoot }).filter((session) => {
814629
+ const isTui = session.source === "tui" || session.id.startsWith("tui:");
814630
+ return !isTui || includeTui && canonicalTuiIds.has(session.id);
814631
+ });
814365
814632
  if (includeTui && targetRoot) {
814366
- const seen = new Set(sessions3.map((s2) => s2.id));
814633
+ const byId = new Map(sessions3.map((session) => [session.id, session]));
814634
+ const seenFingerprints = /* @__PURE__ */ new Set();
814367
814635
  const cfg = loadConfig();
814368
814636
  const needSummary = [];
814369
- for (const t2 of listSessions(targetRoot)) {
814637
+ for (const t2 of tuiHistory) {
814370
814638
  const id2 = `tui:${t2.id}`;
814371
- if (seen.has(id2)) continue;
814372
- if (!t2.aiTitle) needSummary.push(t2.id);
814373
- sessions3.push({
814639
+ const transcriptLines = loadSessionHistory(targetRoot, t2.id) ?? [];
814640
+ const transcript = transcriptLines.join("\n");
814641
+ const fingerprint3 = normalizedSessionFingerprint(transcript);
814642
+ if (fingerprint3 && seenFingerprints.has(fingerprint3)) continue;
814643
+ if (fingerprint3) seenFingerprints.add(fingerprint3);
814644
+ const fallback = deterministicSummary(transcript);
814645
+ const title = sessionDisplayTitle(t2, transcript);
814646
+ const preview = t2.aiSummary && !isSessionNoiseText(t2.aiSummary) ? t2.aiSummary : fallback.summary !== "Empty session." ? fallback.summary : t2.description;
814647
+ if (!t2.aiTitle || isSessionNoiseText(t2.aiTitle)) needSummary.push(t2.id);
814648
+ const canonical3 = {
814374
814649
  id: id2,
814375
814650
  model: t2.model,
814376
814651
  messages: 0,
@@ -814379,9 +814654,15 @@ ${historyLines}
814379
814654
  lastActivity: t2.updatedAt,
814380
814655
  projectRoot: targetRoot,
814381
814656
  source: "tui",
814382
- title: sessionDisplayTitle(t2),
814383
- preview: t2.aiSummary || t2.description
814384
- });
814657
+ title,
814658
+ preview
814659
+ };
814660
+ const existing = byId.get(id2);
814661
+ if (existing) Object.assign(existing, canonical3);
814662
+ else {
814663
+ sessions3.push(canonical3);
814664
+ byId.set(id2, canonical3);
814665
+ }
814385
814666
  }
814386
814667
  for (const sid of needSummary.slice(0, 6)) {
814387
814668
  void ensureSessionSummary({
@@ -814559,23 +814840,23 @@ ${historyLines}
814559
814840
  const sid = decodeURIComponent(chatSessionMatch[1]);
814560
814841
  if (method === "GET") {
814561
814842
  if (!checkAuth(req3, res, "read")) return;
814562
- let session = lookupSession(sid);
814563
- if (!session && sid.startsWith("tui:")) {
814843
+ let session = sid.startsWith("tui:") ? null : lookupSession(sid);
814844
+ if (sid.startsWith("tui:")) {
814564
814845
  const queriedRoot = urlObj.searchParams.get("root");
814565
814846
  const targetRoot = queriedRoot && queriedRoot.trim() ? resolve82(queriedRoot.trim()) : getCurrentProject()?.root ?? process.cwd();
814566
814847
  const tuiId = sid.slice("tui:".length);
814567
814848
  const lines = loadSessionHistory(targetRoot, tuiId);
814568
- if (lines && lines.length > 0) {
814569
- const meta = listSessions(targetRoot).find((s2) => s2.id === tuiId);
814849
+ const meta = listSessions(targetRoot).find((s2) => s2.id === tuiId);
814850
+ if (meta && lines && lines.length > 0) {
814570
814851
  session = importTranscriptSession({
814571
814852
  id: sid,
814572
814853
  projectRoot: targetRoot,
814573
814854
  model: meta?.model || loadConfig().model,
814574
- title: meta?.name,
814575
- description: meta?.description,
814855
+ title: sessionDisplayTitle(meta, lines.join("\n")),
814856
+ description: meta.aiSummary || meta.description,
814576
814857
  transcriptLines: lines,
814577
- createdAt: meta?.createdAt ? Date.parse(meta.createdAt) : void 0,
814578
- updatedAt: meta?.updatedAt ? Date.parse(meta.updatedAt) : void 0
814858
+ createdAt: meta.createdAt ? Date.parse(meta.createdAt) : void 0,
814859
+ updatedAt: meta.updatedAt ? Date.parse(meta.updatedAt) : void 0
814579
814860
  });
814580
814861
  }
814581
814862
  }
@@ -817266,6 +817547,7 @@ var init_serve = __esm({
817266
817547
  init_usage_tracker();
817267
817548
  init_omnius_directory();
817268
817549
  init_session_summary();
817550
+ init_session_quality();
817269
817551
  init_chat_run_registry();
817270
817552
  init_chat_followup();
817271
817553
  init_omnius_directory();
@@ -822244,7 +822526,7 @@ async function startInteractive(config, repoPath2) {
822244
822526
  const cleanupAndExit = (code8) => {
822245
822527
  interactiveExiting = true;
822246
822528
  try {
822247
- saveVisualSessionSnapshotRef?.("process exit");
822529
+ saveVisualSessionSnapshotRef?.();
822248
822530
  } catch {
822249
822531
  }
822250
822532
  if (_shellToolRef) _shellToolRef.killAll();
@@ -823931,15 +824213,16 @@ This is an independent background session started from /background.`
823931
824213
  }
823932
824214
  }
823933
824215
  setDreamWriteContent(writeContent);
823934
- function saveVisualSessionSnapshot(reason) {
824216
+ function saveVisualSessionSnapshot() {
823935
824217
  try {
823936
824218
  const historySessionId = process.env["OMNIUS_SESSION_ID"] || process.env["OMNIUS_TUI_SESSION_ID"] || `session-${Date.now().toString(36)}`;
823937
824219
  const tuiState = statusBar.capturePersistedSessionState();
823938
824220
  const contentLines = statusBar.capturePersistedSessionLines(100);
823939
824221
  if (contentLines.length === 0) return;
823940
824222
  const description = cleanPromptForDiary(
823941
- lastSubmittedPrompt || lastCompletedSummary || reason
823942
- ).slice(0, 240) || reason;
824223
+ lastSubmittedPrompt || lastCompletedSummary
824224
+ ).slice(0, 240);
824225
+ if (!description || isSessionNoiseText(description)) return;
823943
824226
  const historyTitle = description.slice(0, 80) || void 0;
823944
824227
  saveSessionHistory(repoRoot, historySessionId, contentLines, {
823945
824228
  name: historyTitle,
@@ -824699,7 +824982,7 @@ This is an independent background session started from /background.`
824699
824982
  clearInterval(reminderDispatchTimer);
824700
824983
  reminderDispatchTimer = null;
824701
824984
  }
824702
- saveVisualSessionSnapshot("command exit");
824985
+ saveVisualSessionSnapshot();
824703
824986
  statusBar.deactivate();
824704
824987
  if (carousel.isRunning) carousel.stop();
824705
824988
  banner.stop();
@@ -827347,7 +827630,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827347
827630
  const quitMatch = input.trim().replace(/^\//, "").toLowerCase();
827348
827631
  if (quitMatch === "quit" || quitMatch === "exit" || quitMatch === "q") {
827349
827632
  interactiveExiting = true;
827350
- saveVisualSessionSnapshot("quit");
827633
+ saveVisualSessionSnapshot();
827351
827634
  if (activeTask) activeTask.runner.abort();
827352
827635
  idleMemoryMaintenance?.stop();
827353
827636
  taskManager.stopAll();
@@ -827365,7 +827648,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
827365
827648
  );
827366
827649
  if (cmdResult === "exit") {
827367
827650
  interactiveExiting = true;
827368
- saveVisualSessionSnapshot("command exit");
827651
+ saveVisualSessionSnapshot();
827369
827652
  if (activeTask) activeTask.runner.abort();
827370
827653
  idleMemoryMaintenance?.stop();
827371
827654
  taskManager.stopAll();
@@ -828237,7 +828520,7 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
828237
828520
  rl.on("close", () => {
828238
828521
  if (interactiveExiting) return;
828239
828522
  interactiveExiting = true;
828240
- saveVisualSessionSnapshot("readline close");
828523
+ saveVisualSessionSnapshot();
828241
828524
  if (peerMesh) {
828242
828525
  peerMesh.stop().catch(() => {
828243
828526
  });
@@ -828823,6 +829106,7 @@ var init_interactive = __esm({
828823
829106
  init_project_context();
828824
829107
  init_realtime();
828825
829108
  init_chat_session();
829109
+ init_session_quality();
828826
829110
  init_identity_memory_tool();
828827
829111
  init_visual_identity_association();
828828
829112
  init_dist();