omnius 1.0.334 → 1.0.336

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
@@ -555501,8 +555501,8 @@ var init_registry2 = __esm({
555501
555501
  PluginContextImpl = class {
555502
555502
  _registry;
555503
555503
  _manifest;
555504
- constructor(_registry5, _manifest) {
555505
- this._registry = _registry5;
555504
+ constructor(_registry6, _manifest) {
555505
+ this._registry = _registry6;
555506
555506
  this._manifest = _manifest;
555507
555507
  }
555508
555508
  get manifest() {
@@ -599736,8 +599736,8 @@ function codePreviewLines(output, maxLines) {
599736
599736
  return [{ text: "(empty file)", mode: "wrap", kind: "dim" }];
599737
599737
  let hlLines = null;
599738
599738
  if (isAvailable()) {
599739
- const codeBlock = shown.join("\n");
599740
- const hl = highlightBlock(codeBlock);
599739
+ const codeBlock2 = shown.join("\n");
599740
+ const hl = highlightBlock(codeBlock2);
599741
599741
  if (hl.length === shown.length) hlLines = hl;
599742
599742
  }
599743
599743
  const body = shown.map((line, i2) => ({
@@ -607196,7 +607196,7 @@ function cleanSessionHistoryDisplayLine(line) {
607196
607196
  function isNoisySessionHistoryLine(line) {
607197
607197
  const clean5 = cleanSessionHistoryDisplayLine(line || "");
607198
607198
  if (!clean5) return true;
607199
- 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|i\s+)/i.test(clean5);
607199
+ 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|TUI session\b|Using (?:expanded )?context model|Using model|Context (?:auto-)?restored|Context restored|Recovered session|Last task:|\(manual save\)|\[VRAM|VRAM |model_info|KV |arch[ -]capped|i\s+)/i.test(clean5);
607200
607200
  }
607201
607201
  function firstMeaningfulSessionHistoryLine(lines) {
607202
607202
  for (const line of lines) {
@@ -607576,18 +607576,17 @@ var init_omnius_directory = __esm({
607576
607576
  function isNoiseLine(line) {
607577
607577
  const t2 = line.trim();
607578
607578
  if (t2.length < 3) return true;
607579
- const core = t2.replace(/^[∙•·›>\-=_*#|/\\.\s]+/, "").trim();
607579
+ const core = t2.replace(TUI_MARKER_PREFIX, "").trim();
607580
607580
  if (core.length < 3) return true;
607581
- 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)/i.test(core);
607581
+ 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);
607582
607582
  }
607583
607583
  function firstMeaningfulLine(transcript) {
607584
- for (const raw of transcript.split("\n")) {
607585
- const line = raw.replace(ANSI_RE, "").trim();
607586
- if (line && !isNoiseLine(line)) return line;
607584
+ const lines = transcript.split("\n").map((r2) => r2.replace(ANSI_RE, "").trim());
607585
+ for (const line of lines) {
607586
+ if (/^[▹▸►❯>]\s+\S/.test(line) && !isNoiseLine(line)) return line;
607587
607587
  }
607588
- for (const raw of transcript.split("\n")) {
607589
- const line = raw.replace(ANSI_RE, "").trim();
607590
- if (line) return line;
607588
+ for (const line of lines) {
607589
+ if (line && !isNoiseLine(line)) return line;
607591
607590
  }
607592
607591
  return "";
607593
607592
  }
@@ -607598,8 +607597,9 @@ function clamp7(value2, max) {
607598
607597
  function deterministicSummary(transcript) {
607599
607598
  const first2 = firstMeaningfulLine(transcript);
607600
607599
  if (!first2) return { title: "Untitled session", summary: "Empty session." };
607601
- const title = clamp7(first2.replace(/^[>›$#\s]+/, ""), TITLE_MAX);
607602
- return { title: title || "Untitled session", summary: clamp7(first2, SUMMARY_MAX) };
607600
+ const cleaned = first2.replace(TUI_MARKER_PREFIX, "").replace(/^[>›$#\s]+/, "").trim();
607601
+ const title = clamp7(cleaned, TITLE_MAX);
607602
+ return { title: title || "Untitled session", summary: clamp7(cleaned || first2, SUMMARY_MAX) };
607603
607603
  }
607604
607604
  function parseSummaryReply(content) {
607605
607605
  if (!content) return null;
@@ -607692,7 +607692,7 @@ function sessionDisplayTitle(entry) {
607692
607692
  if (entry.aiTitle && entry.aiTitle.trim()) return entry.aiTitle.trim();
607693
607693
  return deterministicSummary(entry.name || "").title;
607694
607694
  }
607695
- var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE, _inflightSummaries;
607695
+ var TITLE_MAX, SUMMARY_MAX, TRANSCRIPT_CHARS, ANSI_RE, TUI_MARKER_PREFIX, _inflightSummaries;
607696
607696
  var init_session_summary = __esm({
607697
607697
  "packages/cli/src/api/session-summary.ts"() {
607698
607698
  "use strict";
@@ -607702,6 +607702,7 @@ var init_session_summary = __esm({
607702
607702
  SUMMARY_MAX = 160;
607703
607703
  TRANSCRIPT_CHARS = 6e3;
607704
607704
  ANSI_RE = /\x1B\[[0-9;]*m|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
607705
+ TUI_MARKER_PREFIX = /^[∙•·‹›▹▸▶►◆◇❯❮>\-=_*#|/\\.\s]+/;
607705
607706
  _inflightSummaries = /* @__PURE__ */ new Set();
607706
607707
  }
607707
607708
  });
@@ -610158,6 +610159,34 @@ var init_status_bar = __esm({
610158
610159
  unregisterDynamicBlock(id) {
610159
610160
  this._dynamicBlocks.delete(id);
610160
610161
  }
610162
+ /**
610163
+ * Expand every dynamic-block sentinel in `lines` into its registered render
610164
+ * output. Used at SESSION-SAVE time so the persisted transcript carries the
610165
+ * REAL tool / shell / task-complete content instead of `\x01DYNBLOCK:<id>\x01`
610166
+ * sentinels whose content otherwise lives only in this in-memory store and is
610167
+ * lost on reload. Stale/unknown sentinels are dropped (not left as dead
610168
+ * markers). ANSI is stripped by the persistence layer afterwards.
610169
+ */
610170
+ expandDynamicBlockSentinels(lines, width = 100) {
610171
+ const out = [];
610172
+ for (const line of lines) {
610173
+ if (typeof line === "string" && line.startsWith(this.DYNAMIC_BLOCK_MARK_PREFIX) && line.endsWith(this.DYNAMIC_BLOCK_MARK_SUFFIX) && line.length > this.DYNAMIC_BLOCK_MARK_PREFIX.length + this.DYNAMIC_BLOCK_MARK_SUFFIX.length) {
610174
+ const id = line.slice(
610175
+ this.DYNAMIC_BLOCK_MARK_PREFIX.length,
610176
+ line.length - this.DYNAMIC_BLOCK_MARK_SUFFIX.length
610177
+ );
610178
+ const renderer = this._dynamicBlocks.get(id);
610179
+ if (!renderer) continue;
610180
+ try {
610181
+ for (const seg of renderer(width)) out.push(seg);
610182
+ } catch {
610183
+ }
610184
+ } else {
610185
+ out.push(line);
610186
+ }
610187
+ }
610188
+ return out;
610189
+ }
610161
610190
  /**
610162
610191
  * Append a previously-registered dynamic block's sentinel to scrollback
610163
610192
  * and trigger a repaint. The block's renderer fires immediately at the
@@ -654923,6 +654952,140 @@ var init_emotion_engine = __esm({
654923
654952
  }
654924
654953
  });
654925
654954
 
654955
+ // packages/cli/src/tui/telegram-format.ts
654956
+ function escapeHtml2(text2) {
654957
+ return String(text2 ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
654958
+ }
654959
+ function toolCallBlock(name10, args) {
654960
+ const header = `<b>Tool call</b> <code>${escapeHtml2(name10 || "tool")}</code>`;
654961
+ if (args == null || typeof args === "object" && Object.keys(args).length === 0) {
654962
+ return header;
654963
+ }
654964
+ const compact3 = typeof args === "string" ? args : safeJson(args, false);
654965
+ if (compact3.length <= 120 && !compact3.includes("\n")) {
654966
+ return `${header} <code>${escapeHtml2(compact3)}</code>`;
654967
+ }
654968
+ const pretty = typeof args === "string" ? args : safeJson(args, true);
654969
+ return `${header}
654970
+ ${codeBlock(pretty, "json")}`;
654971
+ }
654972
+ function toolResultBlock(name10, ok3, output) {
654973
+ const header = `<b>Tool result</b> <code>${escapeHtml2(name10 || "tool")}</code>`;
654974
+ const body = String(output ?? "").trim();
654975
+ if (!body) return `${header}
654976
+ <i>${ok3 ? "completed" : "failed"}</i>`;
654977
+ if (body.length <= 160 && !body.includes("\n")) {
654978
+ return `${header}
654979
+ <blockquote>${escapeHtml2(body)}</blockquote>`;
654980
+ }
654981
+ return `${header}
654982
+ <blockquote expandable>${escapeHtml2(body)}</blockquote>`;
654983
+ }
654984
+ function codeBlock(code8, language = "") {
654985
+ const inner = escapeHtml2(String(code8 ?? "").replace(/\s+$/, ""));
654986
+ return language ? `<pre><code class="language-${escapeHtml2(language)}">${inner}</code></pre>` : `<pre>${inner}</pre>`;
654987
+ }
654988
+ function safeJson(v, pretty) {
654989
+ try {
654990
+ return pretty ? JSON.stringify(v, null, 2) : JSON.stringify(v);
654991
+ } catch {
654992
+ return String(v);
654993
+ }
654994
+ }
654995
+ function tokenizeTelegramHtml(html) {
654996
+ const tokens = [];
654997
+ const re = /<(\/?)([a-zA-Z][\w-]*)((?:\s[^>]*)?)>/g;
654998
+ let last2 = 0;
654999
+ let m2;
655000
+ while ((m2 = re.exec(html)) !== null) {
655001
+ if (m2.index > last2) tokens.push({ type: "text", raw: html.slice(last2, m2.index) });
655002
+ const closing = m2[1] === "/";
655003
+ const tag = (m2[2] || "").toLowerCase();
655004
+ if (SPAN_TAGS.has(tag)) {
655005
+ tokens.push(closing ? { type: "close", tag, raw: m2[0] } : { type: "open", tag, raw: m2[0] });
655006
+ } else {
655007
+ tokens.push({ type: "text", raw: m2[0] });
655008
+ }
655009
+ last2 = re.lastIndex;
655010
+ }
655011
+ if (last2 < html.length) tokens.push({ type: "text", raw: html.slice(last2) });
655012
+ return tokens;
655013
+ }
655014
+ function segmentTelegramHtml(html, maxLength = SAFE_LIMIT) {
655015
+ const text2 = String(html ?? "");
655016
+ if (text2.length <= maxLength) return text2.trim() ? [text2] : [];
655017
+ const tokens = tokenizeTelegramHtml(text2);
655018
+ const out = [];
655019
+ let cur = "";
655020
+ const stack = [];
655021
+ const closeAll = () => stack.slice().reverse().map((t2) => `</${t2.tag}>`).join("");
655022
+ const reopen = () => stack.map((t2) => t2.raw).join("");
655023
+ const closeLen = () => stack.reduce((n2, t2) => n2 + t2.tag.length + 3, 0);
655024
+ const flush3 = () => {
655025
+ const finished = cur + closeAll();
655026
+ if (finished.trim()) out.push(finished);
655027
+ cur = reopen();
655028
+ };
655029
+ for (const tok of tokens) {
655030
+ if (tok.type === "open") {
655031
+ if (cur.length + tok.raw.length + closeLen() > maxLength) flush3();
655032
+ cur += tok.raw;
655033
+ stack.push({ tag: tok.tag, raw: tok.raw });
655034
+ } else if (tok.type === "close") {
655035
+ cur += tok.raw;
655036
+ for (let i2 = stack.length - 1; i2 >= 0; i2--) {
655037
+ if (stack[i2].tag === tok.tag) {
655038
+ stack.splice(i2, 1);
655039
+ break;
655040
+ }
655041
+ }
655042
+ } else {
655043
+ let body = tok.raw;
655044
+ while (cur.length + body.length + closeLen() > maxLength) {
655045
+ const room = maxLength - cur.length - closeLen();
655046
+ if (room <= 1) {
655047
+ flush3();
655048
+ continue;
655049
+ }
655050
+ let bp = body.lastIndexOf("\n", room);
655051
+ if (bp < room * 0.5) bp = body.lastIndexOf(" ", room);
655052
+ if (bp < room * 0.5) bp = room;
655053
+ cur += body.slice(0, bp);
655054
+ flush3();
655055
+ body = body.slice(bp).replace(/^\s+/, "");
655056
+ }
655057
+ cur += body;
655058
+ }
655059
+ }
655060
+ const tail = cur + closeAll();
655061
+ if (tail.trim()) out.push(tail);
655062
+ return out;
655063
+ }
655064
+ var SAFE_LIMIT, SPAN_TAGS;
655065
+ var init_telegram_format = __esm({
655066
+ "packages/cli/src/tui/telegram-format.ts"() {
655067
+ "use strict";
655068
+ SAFE_LIMIT = 3900;
655069
+ SPAN_TAGS = /* @__PURE__ */ new Set([
655070
+ "b",
655071
+ "strong",
655072
+ "i",
655073
+ "em",
655074
+ "u",
655075
+ "ins",
655076
+ "s",
655077
+ "strike",
655078
+ "del",
655079
+ "code",
655080
+ "pre",
655081
+ "blockquote",
655082
+ "tg-spoiler",
655083
+ "a",
655084
+ "span"
655085
+ ]);
655086
+ }
655087
+ });
655088
+
654926
655089
  // packages/cli/src/tui/tool-policy.ts
654927
655090
  function getDefaultPolicy(context2) {
654928
655091
  switch (context2) {
@@ -660554,8 +660717,8 @@ function convertMarkdownToTelegramHTML(md) {
660554
660717
  let html = normalizeTelegramOutboundLinks(md);
660555
660718
  html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
660556
660719
  html = html.replace(
660557
- /```[\w]*\n([\s\S]*?)```/g,
660558
- (_m, code8) => `<pre>${code8.trimEnd()}</pre>`
660720
+ /```([\w+-]*)\n?([\s\S]*?)```/g,
660721
+ (_m, lang, code8) => lang ? `<pre><code class="language-${lang}">${code8.trimEnd()}</code></pre>` : `<pre>${code8.trimEnd()}</pre>`
660559
660722
  );
660560
660723
  html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
660561
660724
  html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
@@ -660604,6 +660767,9 @@ function truncateTelegramUrlSafe(text2, maxLength, suffix = "...") {
660604
660767
  function splitTelegramMessageText(text2, maxLength = 3900) {
660605
660768
  const trimmed = text2.trim();
660606
660769
  if (!trimmed) return [];
660770
+ if (/<\/?[a-zA-Z][\w-]*(?:\s[^>]*)?>/.test(trimmed)) {
660771
+ return segmentTelegramHtml(trimmed, maxLength);
660772
+ }
660607
660773
  const chunks = [];
660608
660774
  let remaining = trimmed;
660609
660775
  while (remaining.length > maxLength) {
@@ -661052,20 +661218,12 @@ function formatTelegramProgressEvent(event) {
661052
661218
  if (event.type === "tool_result" && event.toolName === "task_complete")
661053
661219
  return null;
661054
661220
  if (event.type === "tool_call") {
661055
- return `<b>Tool call</b> <code>${escapeTelegramHTML(event.toolName || "tool")}</code>`;
661221
+ return toolCallBlock(event.toolName || "tool");
661056
661222
  }
661057
661223
  if (event.type === "tool_result") {
661058
- const preview = sanitizeTelegramProgressText(event.content || "", 180);
661224
+ const preview = sanitizeTelegramProgressText(event.content || "", 1500);
661059
661225
  if (isTelegramInternalStatusText(preview)) return null;
661060
- const toolName = escapeTelegramHTML(event.toolName || "tool");
661061
- if (preview) {
661062
- return [
661063
- `<b>Tool result</b> <code>${toolName}</code>`,
661064
- `<blockquote>${escapeTelegramHTML(preview)}</blockquote>`
661065
- ].join("\n");
661066
- }
661067
- return `<b>Tool result</b> <code>${toolName}</code>
661068
- <i>${event.success ? "completed" : "failed"}</i>`;
661226
+ return toolResultBlock(event.toolName || "tool", Boolean(event.success), preview);
661069
661227
  }
661070
661228
  if (event.type === "status") {
661071
661229
  if (isCodebaseMemoryStatus(event.content || "")) {
@@ -662179,6 +662337,7 @@ var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTAT
662179
662337
  var init_telegram_bridge = __esm({
662180
662338
  "packages/cli/src/tui/telegram-bridge.ts"() {
662181
662339
  "use strict";
662340
+ init_telegram_format();
662182
662341
  init_dist8();
662183
662342
  init_dist5();
662184
662343
  init_project_context();
@@ -693960,6 +694119,64 @@ window.refreshTodos = refreshTodos;
693960
694119
  // own process group)
693961
694120
  // - Frontend re-attaches via this restore path and shows the partial
693962
694121
  // output via partial_output_tail in the in_flight payload
694122
+ // Parse a raw rendered-TUI transcript into clean GUI chat bubbles. Strips the
694123
+ // DYNBLOCK:<id> sentinels (no content in the persisted log) and TUI
694124
+ // chrome, then emits user (▹/>) and assistant (│) messages.
694125
+ function renderRecoveredTranscript(raw, conv, title) {
694126
+ const SOH = String.fromCharCode(1);
694127
+ const NL = String.fromCharCode(10);
694128
+ const sentRe = new RegExp(SOH + 'DYNBLOCK:[^' + SOH + ']*' + SOH, 'g');
694129
+ const lines = String(raw).replace(new RegExp(String.fromCharCode(13), 'g'), '').split(NL);
694130
+ let mode = null; // 'user' | 'assistant'
694131
+ let buf = [];
694132
+ let toolCount = 0;
694133
+ const out = []; // [{role, text}] or {tools:n}
694134
+ const flush = () => {
694135
+ const text = buf.join(NL).trim();
694136
+ if (text && mode) out.push({ role: mode, text: text });
694137
+ buf = [];
694138
+ };
694139
+ for (let line of lines) {
694140
+ // Count + drop dynamic-block sentinels (tool/shell/etc. — their content is
694141
+ // not in the log; surface them as a compact "tool activity" marker).
694142
+ const sentinels = (line.match(sentRe) || []).length;
694143
+ if (sentinels) toolCount += sentinels;
694144
+ line = line.replace(sentRe, '');
694145
+ const t = line.trim();
694146
+ if (!t) continue;
694147
+ // Skip pure box-border lines from expanded tool/task boxes (╭─╮ ╰─╯ etc.).
694148
+ if (/^[─-╿s]+$/.test(t)) continue;
694149
+ if (/^[▹>]s?/.test(t)) { // user prompt
694150
+ flush(); if (toolCount) { out.push({ tools: toolCount }); toolCount = 0; }
694151
+ out.push({ role: 'user', text: t.replace(/^[▹>]s?/, '') }); mode = null; continue;
694152
+ }
694153
+ if (/^│/.test(t)) { // assistant / box content
694154
+ if (mode !== 'assistant') { flush(); mode = 'assistant'; }
694155
+ buf.push(t.replace(/^│s?/, '').replace(/s?│$/, '')); continue; // strip both borders
694156
+ }
694157
+ if (/^[∙!]/.test(t)) continue; // status / internal noise — drop
694158
+ if (mode === 'assistant') buf.push(t); // continuation
694159
+ }
694160
+ flush(); if (toolCount) out.push({ tools: toolCount });
694161
+
694162
+ if (out.length === 0) return; // nothing meaningful — show nothing rather than garbage
694163
+ const note = document.createElement('div');
694164
+ note.style.cssText = 'font-size:0.6rem;color:var(--color-fg-faint);margin:4px 0 8px;text-align:center';
694165
+ note.textContent = 'recovered session' + (title ? ' — ' + title : '');
694166
+ conv.appendChild(note);
694167
+ for (const item of out) {
694168
+ if (item.tools) {
694169
+ const td = document.createElement('div');
694170
+ td.style.cssText = 'font-size:0.6rem;color:var(--color-fg-faint);margin:2px 0 6px;padding-left:8px';
694171
+ td.textContent = '⚙ ' + item.tools + ' tool action' + (item.tools === 1 ? '' : 's');
694172
+ conv.appendChild(td);
694173
+ } else {
694174
+ addMessage(item.role, item.text);
694175
+ }
694176
+ }
694177
+ }
694178
+ window.renderRecoveredTranscript = renderRecoveredTranscript;
694179
+
693963
694180
  async function restoreChatSession() {
693964
694181
  // chatSessionId is the window-property shim → $chatSessionId.get().
693965
694182
  // Project-scoped storage already hydrated this on $currentProject.set,
@@ -693996,18 +694213,15 @@ async function restoreChatSession() {
693996
694213
  // decisions, sub-agent activity — everything that scrolled past in the
693997
694214
  // TUI) as a visible, scrollable block at the top so opening the session
693998
694215
  // actually shows what happened, instead of just a "loaded" notice.
693999
- if (data.transcript && String(data.transcript).trim() && data.transcript !== '(empty transcript)') {
694000
- const wrap = document.createElement('div');
694001
- wrap.style.cssText = 'margin:6px 0 14px';
694002
- const head = document.createElement('div');
694003
- head.style.cssText = 'font-size:0.66rem;color:var(--color-fg-muted);margin-bottom:4px;display:flex;justify-content:space-between;align-items:center';
694004
- head.innerHTML = '<span>Recovered session transcript' + (data.title ? ' — ' + escapeHtml(data.title) : '') + '</span>';
694005
- const pre = document.createElement('pre');
694006
- pre.style.cssText = 'max-height:380px;overflow:auto;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:10px 12px;font-size:0.7rem;line-height:1.35;white-space:pre-wrap;word-break:break-word;color:var(--color-fg-muted);margin:0';
694007
- pre.textContent = String(data.transcript);
694008
- wrap.appendChild(head);
694009
- wrap.appendChild(pre);
694010
- conv.appendChild(wrap);
694216
+ if (data.transcript && String(data.transcript).trim() && data.transcript !== '(empty transcript)'
694217
+ && (!allMessages || allMessages.length === 0)) {
694218
+ // TUI/raw transcript: PARSE the rendered-TUI scrollback into real chat
694219
+ // bubbles instead of dumping the raw log. The persisted log carries
694220
+ // DYNBLOCK:<id> sentinels (dynamic blocks expanded only at TUI
694221
+ // paint time no content here) plus chrome ( user, assistant,
694222
+ // status, ! internal). We strip the dead sentinels + noise and emit
694223
+ // user/assistant messages so it reads like a GUI chat.
694224
+ renderRecoveredTranscript(String(data.transcript), conv, data.title || '');
694011
694225
  }
694012
694226
  // WO-CHAT-RESUME-TOOLS — replay the FULL intermediate flow on
694013
694227
  // restore: user/assistant text bubbles AND tool_call/tool_result
@@ -696546,6 +696760,7 @@ function getOpenApiSpec() {
696546
696760
  },
696547
696761
  "/v1/chat/sessions": { get: { summary: "List active chat sessions", tags: ["Chat"], responses: { 200: { description: "Session list" } } } },
696548
696762
  "/v1/chat/sessions/{id}/summarize": { post: { summary: "Generate + cache an inference-based title/summary for a session — body {force?, root?}", tags: ["Chat"], responses: { 200: { description: "Title + summary" }, 500: { description: "Generation failed" } } } },
696763
+ "/v1/chat/sessions/{id}/status": { get: { summary: "Reactive recall: is this session running right now + unseen deltas since ?since=<seq> (running, phase, seq, partial, deltas[])", tags: ["Chat"], responses: { 200: { description: "Live run status + catch-up deltas" } } } },
696549
696764
  // ───── AIWG cascade ─────
696550
696765
  "/v1/aiwg": { get: { summary: "AIWG installation root + control map", tags: ["AIWG"], responses: { 200: { description: "AIWG installation summary" } } } },
696551
696766
  "/v1/aiwg/frameworks": { get: { summary: "List AIWG frameworks (paginated)", tags: ["AIWG"], responses: { 200: { description: "Framework list" } } } },
@@ -697310,6 +697525,96 @@ var init_usage_tracker = __esm({
697310
697525
  }
697311
697526
  });
697312
697527
 
697528
+ // packages/cli/src/api/chat-run-registry.ts
697529
+ function getChatRunRegistry() {
697530
+ if (!_registry5) _registry5 = new ChatRunRegistry();
697531
+ return _registry5;
697532
+ }
697533
+ var MAX_BUFFER2, ChatRunRegistry, _registry5;
697534
+ var init_chat_run_registry = __esm({
697535
+ "packages/cli/src/api/chat-run-registry.ts"() {
697536
+ "use strict";
697537
+ MAX_BUFFER2 = 400;
697538
+ ChatRunRegistry = class {
697539
+ runs = /* @__PURE__ */ new Map();
697540
+ /** Begin (or restart) a run for a session. Returns the fresh state. */
697541
+ start(sessionId, label) {
697542
+ const now2 = Date.now();
697543
+ const state = {
697544
+ sessionId,
697545
+ phase: "running",
697546
+ startedAt: now2,
697547
+ lastActivityMs: now2,
697548
+ seq: 0,
697549
+ buffer: [],
697550
+ partial: "",
697551
+ ...label ? { label } : {}
697552
+ };
697553
+ this.runs.set(sessionId, state);
697554
+ return state;
697555
+ }
697556
+ /** Append a live delta to a running session; no-op if unknown. Returns the seq. */
697557
+ push(sessionId, delta) {
697558
+ const state = this.runs.get(sessionId);
697559
+ if (!state) return -1;
697560
+ const seq = ++state.seq;
697561
+ const entry = { seq, ts: Date.now(), ...delta };
697562
+ state.buffer.push(entry);
697563
+ if (state.buffer.length > MAX_BUFFER2) state.buffer.splice(0, state.buffer.length - MAX_BUFFER2);
697564
+ if (delta.kind === "assistant_delta" && delta.text) state.partial += delta.text;
697565
+ state.lastActivityMs = entry.ts;
697566
+ return seq;
697567
+ }
697568
+ /** Mark a run finished. Keeps the state briefly so late visitors see the result. */
697569
+ finish(sessionId, phase = "completed") {
697570
+ const state = this.runs.get(sessionId);
697571
+ if (!state) return;
697572
+ state.phase = phase;
697573
+ state.lastActivityMs = Date.now();
697574
+ this.push(sessionId, { kind: phase === "completed" ? "done" : "error" });
697575
+ }
697576
+ /** Current status for a session (null when never run this daemon lifetime). */
697577
+ status(sessionId) {
697578
+ const s2 = this.runs.get(sessionId);
697579
+ if (!s2) return null;
697580
+ return {
697581
+ running: s2.phase === "running",
697582
+ phase: s2.phase,
697583
+ lastActivityMs: s2.lastActivityMs,
697584
+ seq: s2.seq,
697585
+ ...s2.label ? { label: s2.label } : {}
697586
+ };
697587
+ }
697588
+ /** Deltas the client hasn't seen yet (seq > sinceSeq), for live catch-up. */
697589
+ since(sessionId, sinceSeq) {
697590
+ const s2 = this.runs.get(sessionId);
697591
+ if (!s2) return null;
697592
+ return { deltas: s2.buffer.filter((d2) => d2.seq > sinceSeq), partial: s2.partial, phase: s2.phase };
697593
+ }
697594
+ /** True while a session is actively producing output. */
697595
+ isRunning(sessionId) {
697596
+ return this.runs.get(sessionId)?.phase === "running";
697597
+ }
697598
+ /** Drop a session's run state (e.g. on delete). */
697599
+ clear(sessionId) {
697600
+ this.runs.delete(sessionId);
697601
+ }
697602
+ /** Evict finished runs older than `maxAgeMs` to bound memory. */
697603
+ sweep(maxAgeMs = 30 * 60 * 1e3, now2 = Date.now()) {
697604
+ let removed = 0;
697605
+ for (const [id, s2] of this.runs) {
697606
+ if (s2.phase !== "running" && now2 - s2.lastActivityMs > maxAgeMs) {
697607
+ this.runs.delete(id);
697608
+ removed++;
697609
+ }
697610
+ }
697611
+ return removed;
697612
+ }
697613
+ };
697614
+ _registry5 = null;
697615
+ }
697616
+ });
697617
+
697313
697618
  // packages/cli/src/docker.ts
697314
697619
  import { execSync as execSync59, spawn as spawn32 } from "node:child_process";
697315
697620
  import { existsSync as existsSync156, mkdirSync as mkdirSync98, writeFileSync as writeFileSync83 } from "node:fs";
@@ -704804,6 +705109,13 @@ data: ${JSON.stringify(data)}
704804
705109
  const realtimeOpts = realtimeEnabled ? realtimeOptionsFromBody(chatBody, cwdPath, session.id) : null;
704805
705110
  addUserMessage(session, chatBody.message);
704806
705111
  compactSession(session);
705112
+ {
705113
+ const _runReg = getChatRunRegistry();
705114
+ _runReg.start(session.id, String(chatBody.message).slice(0, 80));
705115
+ res.on("close", () => {
705116
+ _runReg.finish(session.id, res.writableEnded ? "completed" : "aborted");
705117
+ });
705118
+ }
704807
705119
  const streamMode = chatBody.stream !== false;
704808
705120
  const toolsField = chatBody["tools"];
704809
705121
  const isOpenAIToolsArray = Array.isArray(toolsField) && toolsField.length > 0 && toolsField.every(
@@ -705522,6 +705834,29 @@ ${historyLines}
705522
705834
  });
705523
705835
  return;
705524
705836
  }
705837
+ const chatStatusMatch = pathname.match(/^\/v1\/chat\/sessions\/([^/]+)\/status$/);
705838
+ if (chatStatusMatch && method === "GET") {
705839
+ if (!checkAuth(req3, res, "read")) return;
705840
+ const sid = decodeURIComponent(chatStatusMatch[1]);
705841
+ const reg = getChatRunRegistry();
705842
+ const st = reg.status(sid);
705843
+ if (!st) {
705844
+ jsonResponse(res, 200, { session_id: sid, running: false, phase: "idle", seq: 0 });
705845
+ return;
705846
+ }
705847
+ const sinceSeq = Number(new URL(req3.url || "", "http://localhost").searchParams.get("since") ?? "0") || 0;
705848
+ const catchUp = reg.since(sid, sinceSeq);
705849
+ jsonResponse(res, 200, {
705850
+ session_id: sid,
705851
+ running: st.running,
705852
+ phase: st.phase,
705853
+ seq: st.seq,
705854
+ lastActivityMs: st.lastActivityMs,
705855
+ ...st.label ? { label: st.label } : {},
705856
+ ...catchUp ? { deltas: catchUp.deltas, partial: catchUp.partial } : {}
705857
+ });
705858
+ return;
705859
+ }
705525
705860
  const chatSessionMatch = pathname.match(/^\/v1\/chat\/sessions\/([^/]+)$/);
705526
705861
  if (chatSessionMatch) {
705527
705862
  const sid = decodeURIComponent(chatSessionMatch[1]);
@@ -708175,6 +708510,7 @@ var init_serve = __esm({
708175
708510
  init_usage_tracker();
708176
708511
  init_omnius_directory();
708177
708512
  init_session_summary();
708513
+ init_chat_run_registry();
708178
708514
  init_omnius_directory();
708179
708515
  init_command_registry();
708180
708516
  init_profiles();
@@ -711992,7 +712328,8 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
711992
712328
  }
711993
712329
  try {
711994
712330
  const historySessionId = process.env["OMNIUS_SESSION_ID"] || `session-${Date.now().toString(36)}`;
711995
- const contentLines = statusBar?._contentLines ?? [];
712331
+ const rawContentLines = statusBar?._contentLines ?? [];
712332
+ const contentLines = typeof statusBar?.expandDynamicBlockSentinels === "function" ? statusBar.expandDynamicBlockSentinels(rawContentLines, 100) : rawContentLines;
711996
712333
  if (contentLines.length > 0) {
711997
712334
  const description = cleanPromptForDiary(task).slice(0, 240);
711998
712335
  const historyTitle = description.slice(0, 80) || void 0;
@@ -52,6 +52,7 @@ pnpm docs:check
52
52
  | `GET` | `/api/tags` | Ollama-compatible model tags |
53
53
  | `GET` | `/v1/chat/sessions` | Active chat sessions |
54
54
  | `POST` | `/v1/chat/sessions/{id}/summarize` | Generate + cache an inference-based session title/summary |
55
+ | `GET` | `/v1/chat/sessions/{id}/status` | Reactive recall: live run status + unseen deltas (`?since=<seq>`) |
55
56
  | `POST` | `/v1/chat/check-in` | Steering check-in for active chat |
56
57
 
57
58
  ## Agentic Runs
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.334",
3
+ "version": "1.0.336",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.334",
9
+ "version": "1.0.336",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
@@ -3986,9 +3986,9 @@
3986
3986
  }
3987
3987
  },
3988
3988
  "node_modules/hono": {
3989
- "version": "4.12.26",
3990
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz",
3991
- "integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==",
3989
+ "version": "4.12.27",
3990
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz",
3991
+ "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==",
3992
3992
  "license": "MIT",
3993
3993
  "engines": {
3994
3994
  "node": ">=16.9.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.334",
3
+ "version": "1.0.336",
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/index.js",