omnius 1.0.27 → 1.0.28

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
@@ -610964,7 +610964,7 @@ body { display:flex; flex-direction:column; height:100vh; margin:0; overflow:hid
610964
610964
  color: var(--color-fg);
610965
610965
  }
610966
610966
  .msg.assistant::before {
610967
- content: 'Omnius' !important;
610967
+ content: 'O' !important;
610968
610968
  position: absolute;
610969
610969
  top: 14px;
610970
610970
  left: 0;
@@ -612301,6 +612301,36 @@ const $sidebarWidth = writable(260);
612301
612301
  const $config = writable(null); // {endpoint, model, ...}
612302
612302
  const $activeTab = writable('chat');
612303
612303
 
612304
+ function parseInitialGuiRoute() {
612305
+ const path = (location.pathname || '/').replace(//+$/, '') || '/';
612306
+ const pathToTab = {
612307
+ '/chat': 'chat',
612308
+ '/agent': 'agent',
612309
+ '/voice': 'voice',
612310
+ '/projects': 'projects',
612311
+ '/dashboard': 'jobs',
612312
+ '/jobs': 'jobs',
612313
+ '/activity': 'activity',
612314
+ '/settings': 'config',
612315
+ '/config': 'config',
612316
+ };
612317
+ const tab = pathToTab[path] || null;
612318
+ let chatSession = null;
612319
+ if (tab === 'chat') {
612320
+ const qs = location.search.replace(/^?/, '').trim();
612321
+ if (qs) {
612322
+ try {
612323
+ const params = new URLSearchParams(location.search);
612324
+ chatSession = params.get('session') || params.get('id') || params.get('s') || decodeURIComponent(qs.split('&')[0] || '');
612325
+ } catch {
612326
+ chatSession = qs;
612327
+ }
612328
+ }
612329
+ }
612330
+ return { tab, chatSession };
612331
+ }
612332
+ const initialGuiRoute = parseInitialGuiRoute();
612333
+
612304
612334
  // Global writes (not project-scoped)
612305
612335
  $apiKey.subscribe(v => {
612306
612336
  try { v ? localStorage.setItem('omnius-api-key', v) : localStorage.removeItem('omnius-api-key'); } catch {}
@@ -612350,11 +612380,12 @@ $currentProject.subscribe(async (proj) => {
612350
612380
  // Server hydrate (overrides local on first load — server is cross-device truth)
612351
612381
  const server = await loadServerPrefs();
612352
612382
  $selectedModel.set(server?.selectedModel ?? localModel);
612353
- $chatSessionId.set(server?.currentChatSessionId ?? (localChat || null));
612383
+ $chatSessionId.set(initialGuiRoute.chatSession || server?.currentChatSessionId || localChat || null);
612354
612384
  $agentRunId.set(server?.currentAgentRunId ?? (localAgent || null));
612355
612385
  $sidebarWidth.set(server?.sidebarWidth ?? localWidth);
612356
612386
  if (server?.theme && server.theme !== $theme.get()) $theme.set(server.theme);
612357
- if (server?.activeTab) $activeTab.set(server.activeTab);
612387
+ if (initialGuiRoute.tab) $activeTab.set(initialGuiRoute.tab);
612388
+ else if (server?.activeTab) $activeTab.set(server.activeTab);
612358
612389
  else if (localTab) $activeTab.set(localTab);
612359
612390
  });
612360
612391
 
@@ -612657,6 +612688,42 @@ function persistSelectedModel() {
612657
612688
  }).catch(() => { /* offline / read-only — store still saved */ });
612658
612689
  }
612659
612690
 
612691
+ function appendAssistantActions(div, textProvider) {
612692
+ const actions = document.createElement('div');
612693
+ actions.className = 'msg-actions';
612694
+ const mkBtn = (label, title, fn) => {
612695
+ const b = document.createElement('button');
612696
+ b.textContent = label;
612697
+ b.title = title;
612698
+ b.onclick = fn;
612699
+ return b;
612700
+ };
612701
+ actions.appendChild(mkBtn('copy', 'Copy message', () => {
612702
+ const txt = (typeof textProvider === 'function' ? textProvider() : '') || '';
612703
+ try { navigator.clipboard.writeText(txt); } catch {}
612704
+ const first = actions.querySelector('button');
612705
+ if (!first) return;
612706
+ const orig = first.textContent;
612707
+ first.textContent = 'copied';
612708
+ setTimeout(() => { first.textContent = orig; }, 1500);
612709
+ }));
612710
+ actions.appendChild(mkBtn('regen', 'Regenerate this response', () => {
612711
+ try { regenerateAssistantMessage(div); } catch (e) { console.warn('regen failed', e); }
612712
+ }));
612713
+ actions.appendChild(mkBtn('del', 'Delete this message', () => {
612714
+ try { deleteMessageDiv(div); } catch (e) { console.warn('del failed', e); }
612715
+ }));
612716
+ div.appendChild(actions);
612717
+ return actions;
612718
+ }
612719
+
612720
+ function removeAssistantActions(div) {
612721
+ if (!div) return;
612722
+ Array.from(div.children || []).forEach(ch => {
612723
+ if (ch && ch.classList && ch.classList.contains('msg-actions')) ch.remove();
612724
+ });
612725
+ }
612726
+
612660
612727
  function addMessage(role, content) {
612661
612728
  const div = document.createElement('div');
612662
612729
  div.className = 'msg ' + role;
@@ -612678,32 +612745,7 @@ function addMessage(role, content) {
612678
612745
  }
612679
612746
  // OWUI-3: rich per-message action row on assistant messages.
612680
612747
  if (role === 'assistant') {
612681
- const actions = document.createElement('div');
612682
- actions.className = 'msg-actions';
612683
- const mkBtn = (label, title, fn) => {
612684
- const b = document.createElement('button');
612685
- b.textContent = label;
612686
- b.title = title;
612687
- b.onclick = fn;
612688
- return b;
612689
- };
612690
- actions.appendChild(mkBtn('copy', 'Copy message', () => {
612691
- // Copy what's actually visible (post-render text), not the raw arg —
612692
- // the closure-captured "content" may be empty when this message was
612693
- // built incrementally during streaming.
612694
- const txt = host.innerText || host.textContent || content || '';
612695
- try { navigator.clipboard.writeText(txt); } catch {}
612696
- const orig = actions.querySelector('button').textContent;
612697
- actions.querySelector('button').textContent = 'copied';
612698
- setTimeout(() => { actions.querySelector('button').textContent = orig; }, 1500);
612699
- }));
612700
- actions.appendChild(mkBtn('regen', 'Regenerate this response', () => {
612701
- try { regenerateAssistantMessage(div); } catch (e) { console.warn('regen failed', e); }
612702
- }));
612703
- actions.appendChild(mkBtn('del', 'Delete this message', () => {
612704
- try { deleteMessageDiv(div); } catch (e) { console.warn('del failed', e); }
612705
- }));
612706
- div.appendChild(actions);
612748
+ appendAssistantActions(div, () => host.innerText || host.textContent || content || '');
612707
612749
  }
612708
612750
  conv.appendChild(div);
612709
612751
  // OWUI-3: schedule syntax highlight for any new code blocks.
@@ -612894,6 +612936,12 @@ function appendExpandableContent(parent, fullText, opts) {
612894
612936
  return wrapper;
612895
612937
  }
612896
612938
 
612939
+ function isInternalChatTool(toolName) {
612940
+ return toolName === 'task_complete'
612941
+ || toolName === 'todo_write'
612942
+ || toolName === 'todo_read';
612943
+ }
612944
+
612897
612945
  // WO-CHAT-RESUME-TOOLS — render a single tool_call event into a parent
612898
612946
  // container. Used by BOTH the live SSE handler AND the chat history
612899
612947
  // restore path so reloads see the same intermediate-state dropdowns
@@ -612901,12 +612949,13 @@ function appendExpandableContent(parent, fullText, opts) {
612901
612949
  // SSE chunk shape ({type:'tool_call', tool, args}) OR the persisted
612902
612950
  // session message shape ({role:'tool_call', tool, args}).
612903
612951
  function renderToolCallEvent(parent, chunkLike) {
612952
+ const toolName = chunkLike.tool || 'tool';
612953
+ if (isInternalChatTool(toolName)) return null;
612904
612954
  const details = document.createElement('details');
612905
612955
  details.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0;font-size:0.7rem';
612906
612956
  const summary = document.createElement('summary');
612907
612957
  summary.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer';
612908
612958
 
612909
- const toolName = chunkLike.tool || 'tool';
612910
612959
  let a = (chunkLike.args && typeof chunkLike.args === 'object') ? chunkLike.args : {};
612911
612960
  if (typeof chunkLike.args === 'string') {
612912
612961
  try { a = JSON.parse(chunkLike.args); } catch { a = { _raw: chunkLike.args }; }
@@ -612995,6 +613044,7 @@ function renderToolCallEvent(parent, chunkLike) {
612995
613044
  // WO-CHAT-RESUME-TOOLS — render a single tool_result event with the
612996
613045
  // show-more/hide expandable helper. Used by both live SSE and restore.
612997
613046
  function renderToolResultEvent(parent, chunkLike) {
613047
+ if (isInternalChatTool(chunkLike.tool)) return null;
612998
613048
  const resultEl = document.createElement('div');
612999
613049
  const errStyle = chunkLike.success === false
613000
613050
  ? 'background:#2a1e1e;border-left:2px solid var(--color-error);color:var(--color-error);'
@@ -613304,6 +613354,7 @@ async function sendMessage() {
613304
613354
  // $chatSessionId.set(...) — the subscriber persists to project-
613305
613355
  // scoped localStorage AND server prefs. Setting it here is enough.
613306
613356
  chatSessionId = sid;
613357
+ syncRouteForTab('chat', true);
613307
613358
  // WO-TASK-02: Surface the session ID globally so refreshTodos works
613308
613359
  window.currentSessionId = sid;
613309
613360
  // Initial refresh in case the agent set todos before the SSE stream
@@ -613343,16 +613394,20 @@ async function sendMessage() {
613343
613394
  || (typeof ta.result === 'string' && ta.result)
613344
613395
  || '';
613345
613396
  if (summaryText) {
613346
- if (fullContent && !fullContent.endsWith('\\n')) fullContent += '\\n\\n';
613347
- fullContent += summaryText;
613397
+ if (fullContent.trim() !== summaryText.trim()) {
613398
+ if (fullContent && !fullContent.endsWith('\\n')) fullContent += '\\n\\n';
613399
+ fullContent += summaryText;
613400
+ }
613348
613401
  contentDiv.innerHTML = renderMarkdown(fullContent);
613402
+ hideStreamingIndicator(msgDiv);
613349
613403
  maybeAutoScroll();
613350
613404
  }
613351
- // fall through so the dropdown still renders for inspection
613405
+ continue;
613352
613406
  }
613353
613407
 
613354
613408
  // Tool call event — show live as expandable section
613355
613409
  if (chunk.type === 'tool_call') {
613410
+ if (isInternalChatTool(chunk.tool || '')) continue;
613356
613411
  chatTools.push(chunk);
613357
613412
  const details = document.createElement('details');
613358
613413
  details.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0;font-size:0.7rem';
@@ -613466,6 +613521,7 @@ async function sendMessage() {
613466
613521
  // expand to see the FULL output instead of being capped at
613467
613522
  // 150 chars. The button sits underneath the result block.
613468
613523
  if (chunk.type === 'tool_result') {
613524
+ if (isInternalChatTool(chunk.tool || '')) continue;
613469
613525
  const resultEl = document.createElement('div');
613470
613526
  resultEl.style.cssText = 'background:var(--color-bg-elevated);padding:4px 8px 4px 18px;margin:0 0 2px 0;color:var(--color-fg-muted);font-size:0.65rem';
613471
613527
  appendExpandableContent(resultEl, chunk.output || '', { truncateAt: 150, baseStyle: 'color:var(--color-fg-muted);' });
@@ -613482,12 +613538,7 @@ async function sendMessage() {
613482
613538
  // Content delta
613483
613539
  const delta = chunk.choices?.[0]?.delta?.content || '';
613484
613540
  if (delta) {
613485
- // OWUI-3: switch indicator from 'thinking' -> 'writing' on
613486
- // first delta, and remove on subsequent deltas (it lives at
613487
- // the bottom of the message, below content).
613488
- if (!fullContent) {
613489
- showStreamingIndicator(msgDiv, 'writing');
613490
- }
613541
+ hideStreamingIndicator(msgDiv);
613491
613542
  fullContent += delta;
613492
613543
  contentDiv.innerHTML = renderMarkdown(fullContent);
613493
613544
  try { _highlightCodeBlocks(contentDiv); } catch {}
@@ -613502,6 +613553,7 @@ async function sendMessage() {
613502
613553
  // extract it here so the next message uses the same session.
613503
613554
  if (!chatSessionId && metaInfo && metaInfo.session_id) {
613504
613555
  chatSessionId = metaInfo.session_id;
613556
+ syncRouteForTab('chat', true);
613505
613557
  }
613506
613558
 
613507
613559
  // Save with metadata for session recall
@@ -613541,14 +613593,8 @@ async function sendMessage() {
613541
613593
  toolsContainer.appendChild(details);
613542
613594
  }
613543
613595
 
613544
- // Action buttons
613545
- const actions = document.createElement('div');
613546
- actions.className = 'msg-actions';
613547
- const copyBtn = document.createElement('button');
613548
- copyBtn.textContent = 'copy';
613549
- copyBtn.onclick = () => { navigator.clipboard.writeText(fullContent); copyBtn.textContent = 'copied'; setTimeout(() => copyBtn.textContent = 'copy', 1500); };
613550
- actions.appendChild(copyBtn);
613551
- msgDiv.appendChild(actions);
613596
+ removeAssistantActions(msgDiv);
613597
+ appendAssistantActions(msgDiv, () => fullContent);
613552
613598
  } catch (err) {
613553
613599
  // Match the red left-border styling used by failed tool_result
613554
613600
  // and the stop-button. Sits inside the assistant bubble so it
@@ -614398,9 +614444,56 @@ window.closeRemoteConnection = closeRemoteConnection;
614398
614444
  } catch {}
614399
614445
  })();
614400
614446
 
614401
- // Tab switching
614447
+ // Tab switching + browser route sync
614402
614448
  const allPanels = ['chat-container','agent-panel','jobs-panel','config-panel','activity-panel','projects-panel','voice-panel'];
614403
- function switchTab(tab) {
614449
+ function routePathForTab(tab) {
614450
+ const map = {
614451
+ chat: '/chat',
614452
+ agent: '/agent',
614453
+ voice: '/voice',
614454
+ projects: '/projects',
614455
+ jobs: '/dashboard',
614456
+ activity: '/activity',
614457
+ config: '/settings',
614458
+ };
614459
+ return map[tab] || '/chat';
614460
+ }
614461
+ function tabForRoutePath(pathname) {
614462
+ const path = (pathname || '/').replace(//+$/, '') || '/';
614463
+ const map = {
614464
+ '/': 'chat',
614465
+ '/chat': 'chat',
614466
+ '/agent': 'agent',
614467
+ '/voice': 'voice',
614468
+ '/projects': 'projects',
614469
+ '/dashboard': 'jobs',
614470
+ '/jobs': 'jobs',
614471
+ '/activity': 'activity',
614472
+ '/settings': 'config',
614473
+ '/config': 'config',
614474
+ };
614475
+ return map[path] || 'chat';
614476
+ }
614477
+ function chatSessionFromRouteSearch(search) {
614478
+ const qs = String(search || '').replace(/^?/, '').trim();
614479
+ if (!qs) return null;
614480
+ try {
614481
+ const params = new URLSearchParams(search);
614482
+ return params.get('session') || params.get('id') || params.get('s') || decodeURIComponent(qs.split('&')[0] || '');
614483
+ } catch {
614484
+ return qs;
614485
+ }
614486
+ }
614487
+ function syncRouteForTab(tab, replace) {
614488
+ const path = routePathForTab(tab);
614489
+ const query = tab === 'chat' && chatSessionId ? '?' + encodeURIComponent(chatSessionId) : '';
614490
+ const next = path + query;
614491
+ if ((location.pathname + location.search) === next) return;
614492
+ const method = replace ? 'replaceState' : 'pushState';
614493
+ try { history[method]({}, '', next); } catch {}
614494
+ }
614495
+ function switchTab(tab, opts) {
614496
+ opts = opts || {};
614404
614497
  const panelMap = {chat:'chat-container',agent:'agent-panel',jobs:'jobs-panel',config:'config-panel',activity:'activity-panel',projects:'projects-panel',voice:'voice-panel'};
614405
614498
  allPanels.forEach(id => { const el = document.getElementById(id); if(el) el.style.display = 'none'; });
614406
614499
  const panel = document.getElementById(panelMap[tab]);
@@ -614417,6 +614510,8 @@ function switchTab(tab) {
614417
614510
  document.querySelectorAll('#omnius-sidebar .sb-nav').forEach(b => {
614418
614511
  b.classList.toggle('active', b.getAttribute('data-tab') === tab);
614419
614512
  });
614513
+ if ($activeTab.get && $activeTab.get() !== tab) $activeTab.set(tab);
614514
+ if (!opts.fromRoute) syncRouteForTab(tab, !!opts.replaceRoute);
614420
614515
  if (tab === 'jobs') loadJobs();
614421
614516
  if (tab === 'agent') {
614422
614517
  loadProfiles();
@@ -614432,6 +614527,14 @@ function switchTab(tab) {
614432
614527
  try { loadVoiceTab(); } catch {}
614433
614528
  }
614434
614529
  }
614530
+ window.addEventListener('popstate', () => {
614531
+ const tab = tabForRoutePath(location.pathname);
614532
+ if (tab === 'chat') {
614533
+ const sid = chatSessionFromRouteSearch(location.search);
614534
+ if (sid && sid !== chatSessionId) switchSession(sid);
614535
+ }
614536
+ switchTab(tab, { fromRoute: true });
614537
+ });
614435
614538
 
614436
614539
  // Projects tab — list all workspaces the user has run 'omnius' in, and let them
614437
614540
  // switch the active workspace. A switch is a server-side state change; the
@@ -615125,6 +615228,7 @@ function updateSessionSelect() {
615125
615228
  // still works exactly as before.
615126
615229
  function switchChatSession(id) {
615127
615230
  switchSession(id);
615231
+ switchTab('chat', { replaceRoute: true });
615128
615232
  }
615129
615233
  function newChatSession() {
615130
615234
  switchSession('');
@@ -615294,6 +615398,7 @@ function switchSession(id) {
615294
615398
  chatSessionId = null;
615295
615399
  messages = [];
615296
615400
  document.getElementById('conversation').innerHTML = '';
615401
+ syncRouteForTab('chat', true);
615297
615402
  return;
615298
615403
  }
615299
615404
  const saved = loadScopedSessions();
@@ -615345,6 +615450,7 @@ function switchSession(id) {
615345
615450
  div.appendChild(outerDetails);
615346
615451
  }
615347
615452
  }
615453
+ syncRouteForTab('chat', true);
615348
615454
  }
615349
615455
  }
615350
615456
 
@@ -616354,32 +616460,14 @@ async function refreshTodos(sessionId) {
616354
616460
  }
616355
616461
  const j = await r.json();
616356
616462
  const todos = (j && Array.isArray(j.todos)) ? j.todos : [];
616357
- // 1) Update the inline checklist panel above the conversation
616463
+ // 1) Keep the full checklist out of the chat transcript. The compact
616464
+ // tasks strip below is the visible progress surface.
616358
616465
  if (todoChecklistEl && todoListEl) {
616359
- if (todos.length === 0) {
616360
- todoChecklistEl.style.display = 'none';
616361
- } else {
616362
- todoChecklistEl.style.display = 'block';
616363
- todoListEl.innerHTML = '';
616364
- for (const t of todos) {
616365
- const li = document.createElement('li');
616366
- li.style.cssText = 'padding:2px 0;display:flex;gap:8px;align-items:flex-start';
616367
- const mark = document.createElement('span');
616368
- mark.style.cssText = 'color:var(--color-brand);font-family:monospace;flex-shrink:0';
616369
- mark.textContent = statusMark(t.status);
616370
- li.appendChild(mark);
616371
- const content = document.createElement('span');
616372
- content.style.cssText = t.status === 'completed'
616373
- ? 'color:var(--color-fg-subtle);text-decoration:line-through'
616374
- : 'color:var(--color-fg)';
616375
- content.textContent = t.content + (t.blocker ? ' (blocked: ' + t.blocker + ')' : '');
616376
- li.appendChild(content);
616377
- todoListEl.appendChild(li);
616378
- }
616379
- }
616380
- }
616381
- // 2) Update the compact tasks-row strip in the footer
616382
- renderTasksRow(todos);
616466
+ todoChecklistEl.style.display = 'none';
616467
+ todoListEl.innerHTML = '';
616468
+ }
616469
+ // 2) Chat mode should not surface transient planning todos in the UI.
616470
+ renderTasksRow([]);
616383
616471
  } catch { /* network or parse failure — leave panel as-is */ }
616384
616472
  }
616385
616473
 
@@ -616456,9 +616544,11 @@ async function restoreChatSession() {
616456
616544
  currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
616457
616545
  msgDiv.appendChild(currentAssistantTools);
616458
616546
  } else if (m.role === 'tool_call') {
616547
+ if (isInternalChatTool(m.tool)) continue;
616459
616548
  renderToolCallEvent(ensureTools(), { tool: m.tool, args: m.args });
616460
616549
  } else if (m.role === 'tool_result') {
616461
- renderToolResultEvent(ensureTools(), { output: m.output, success: m.success });
616550
+ if (isInternalChatTool(m.tool)) continue;
616551
+ renderToolResultEvent(ensureTools(), { tool: m.tool, output: m.output, success: m.success });
616462
616552
  } else if (m.role === 'user_checkin') {
616463
616553
  // WO-CHAT-CHECKIN — replay the teal user check-in entry
616464
616554
  renderCheckinEvent(ensureTools(), m.content);
@@ -616527,11 +616617,13 @@ function attachInFlightPoller(sessionId, initialJob) {
616527
616617
  curTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
616528
616618
  md.appendChild(curTools);
616529
616619
  } else if (m.role === 'tool_call') {
616620
+ if (isInternalChatTool(m.tool)) continue;
616530
616621
  if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
616531
616622
  renderToolCallEvent(curTools, { tool: m.tool, args: m.args });
616532
616623
  } else if (m.role === 'tool_result') {
616624
+ if (isInternalChatTool(m.tool)) continue;
616533
616625
  if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
616534
- renderToolResultEvent(curTools, { output: m.output, success: m.success });
616626
+ renderToolResultEvent(curTools, { tool: m.tool, output: m.output, success: m.success });
616535
616627
  }
616536
616628
  }
616537
616629
  }
@@ -616565,11 +616657,13 @@ function attachInFlightPoller(sessionId, initialJob) {
616565
616657
  curTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
616566
616658
  md.appendChild(curTools);
616567
616659
  } else if (m.role === 'tool_call') {
616660
+ if (isInternalChatTool(m.tool)) continue;
616568
616661
  if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
616569
616662
  renderToolCallEvent(curTools, { tool: m.tool, args: m.args });
616570
616663
  } else if (m.role === 'tool_result') {
616664
+ if (isInternalChatTool(m.tool)) continue;
616571
616665
  if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
616572
- renderToolResultEvent(curTools, { output: m.output, success: m.success });
616666
+ renderToolResultEvent(curTools, { tool: m.tool, output: m.output, success: m.success });
616573
616667
  }
616574
616668
  }
616575
616669
  }
@@ -617003,9 +617097,9 @@ $activeTab.subscribe((tab) => {
617003
617097
  if (!tab || typeof switchTab !== 'function') return;
617004
617098
  // Avoid recursion: switchTab is called by user click and writes back
617005
617099
  // via window.switchTab('chat') style; we only DOM-update if needed.
617006
- const tabBtn = document.getElementById('tab-' + tab);
617007
- if (tabBtn && !tabBtn.classList.contains('active')) {
617008
- try { switchTab(tab); } catch {}
617100
+ const navBtn = document.querySelector('#omnius-sidebar .sb-nav[data-tab="' + tab + '"]');
617101
+ if (!navBtn || !navBtn.classList.contains('active')) {
617102
+ try { switchTab(tab, { replaceRoute: true }); } catch {}
617009
617103
  }
617010
617104
  });
617011
617105
 
@@ -617504,7 +617598,7 @@ function _renderSidebarChats(filter) {
617504
617598
  const cls = 'sb-chat' + (id === activeId ? ' active' : '');
617505
617599
  const safeId = String(id).replace(/'/g, "\\\\'");
617506
617600
  const safeTitle = title.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
617507
- return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchTab(\\'chat\\'); switchSession(\\'' + safeId + '\\')" title="' + safeTitle + '">' +
617601
+ return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchSession(\\'' + safeId + '\\'); switchTab(\\'chat\\', { replaceRoute: true })" title="' + safeTitle + '">' +
617508
617602
  '<span class="sb-chat-title">' + safeTitle + '</span>' +
617509
617603
  '<button class="sb-chat-menu" onclick="_showChatRowMenu(event,\\'' + safeId + '\\')" title="More">&#x22EE;</button>' +
617510
617604
  '</div>';
@@ -623749,7 +623843,19 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
623749
623843
  res.end(svg);
623750
623844
  return;
623751
623845
  }
623752
- if (pathname === "/" && method === "GET" && req2.headers.accept?.includes("text/html")) {
623846
+ const guiRoutes = /* @__PURE__ */ new Set([
623847
+ "/",
623848
+ "/chat",
623849
+ "/agent",
623850
+ "/voice",
623851
+ "/projects",
623852
+ "/dashboard",
623853
+ "/jobs",
623854
+ "/activity",
623855
+ "/settings",
623856
+ "/config"
623857
+ ]);
623858
+ if (guiRoutes.has(pathname) && method === "GET" && req2.headers.accept?.includes("text/html")) {
623753
623859
  res.writeHead(200, {
623754
623860
  "Content-Type": "text/html; charset=utf-8",
623755
623861
  "Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
@@ -624901,8 +625007,9 @@ data: ${JSON.stringify(data)}
624901
625007
  session.id,
624902
625008
  5
624903
625009
  );
624904
- let currentTodos = readTodos(session.id);
624905
- if (currentTodos.length === 0) {
625010
+ const chatTodosEnabled = chatBody["todos"] === true || chatBody["enable_todos"] === true || chatBody["todo_injection"] === true;
625011
+ let currentTodos = chatTodosEnabled ? readTodos(session.id) : [];
625012
+ if (chatTodosEnabled && currentTodos.length === 0) {
624906
625013
  const seeded = autoSeedTodosFromPrompt(chatBody.message);
624907
625014
  if (seeded.length >= 2) {
624908
625015
  try {
@@ -624915,7 +625022,7 @@ data: ${JSON.stringify(data)}
624915
625022
  }
624916
625023
  }
624917
625024
  }
624918
- const todoBlock = currentTodos.length > 0 ? "CURRENT TODO LIST:\n" + currentTodos.map((t2) => {
625025
+ const todoBlock = chatTodosEnabled && currentTodos.length > 0 ? "CURRENT TODO LIST:\n" + currentTodos.map((t2) => {
624919
625026
  const mark = t2.status === "completed" ? "◉" : t2.status === "in_progress" ? "◐" : t2.status === "blocked" ? "◍" : "○";
624920
625027
  return `${mark} ${t2.content}` + (t2.blocker ? ` (blocked: ${t2.blocker})` : "");
624921
625028
  }).join("\n") + "\n\nUse the todo_write tool to update this list as you complete steps. Mark each item completed once you finish it.\n\n" : "";
@@ -625227,19 +625334,21 @@ ${historyLines}
625227
625334
  finishInFlightChat(session.id, "completed", content.trim());
625228
625335
  } catch {
625229
625336
  }
625230
- try {
625231
- const finalTodos = readTodos(session.id);
625232
- if (finalTodos.length > 0 && finalTodos.some((t2) => t2.status !== "completed" && t2.status !== "blocked")) {
625233
- const updated = finalTodos.map((t2) => ({
625234
- id: t2.id,
625235
- content: t2.content,
625236
- status: t2.status === "blocked" ? "blocked" : "completed",
625237
- parentId: t2.parentId,
625238
- blocker: t2.blocker
625239
- }));
625240
- writeTodos(session.id, updated);
625337
+ if (chatTodosEnabled) {
625338
+ try {
625339
+ const finalTodos = readTodos(session.id);
625340
+ if (finalTodos.length > 0 && finalTodos.some((t2) => t2.status !== "completed" && t2.status !== "blocked")) {
625341
+ const updated = finalTodos.map((t2) => ({
625342
+ id: t2.id,
625343
+ content: t2.content,
625344
+ status: t2.status === "blocked" ? "blocked" : "completed",
625345
+ parentId: t2.parentId,
625346
+ blocker: t2.blocker
625347
+ }));
625348
+ writeTodos(session.id, updated);
625349
+ }
625350
+ } catch {
625241
625351
  }
625242
- } catch {
625243
625352
  }
625244
625353
  writeMemoryEpisodes(session.id, chatBody.message, content.trim(), toolCallCount).catch(() => {
625245
625354
  });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.27",
9
+ "version": "1.0.28",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
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",