omnius 1.0.339 → 1.0.340

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
@@ -689463,6 +689463,41 @@ window.__omniusStores = {
689463
689463
 
689464
689464
  const conv = document.getElementById('conversation');
689465
689465
  const input = document.getElementById('input-area');
689466
+ // SUGGESTED FOLLOW-UP — ghost text (placeholder-grey) shown after a reply
689467
+ // completes; Tab accepts it as real text, typing dismisses it.
689468
+ const DEFAULT_INPUT_PLACEHOLDER = 'Type a message...';
689469
+ let chatFollowup = null;
689470
+ function setFollowupSuggestion(text) {
689471
+ text = (text || '').trim();
689472
+ if (!text || streaming) return;
689473
+ if (input.value.trim().length > 0) return; // don't clobber what the user is typing
689474
+ chatFollowup = text;
689475
+ input.placeholder = text + ' (Tab \\u21E5)';
689476
+ }
689477
+ function clearFollowupSuggestion() {
689478
+ if (chatFollowup !== null) { chatFollowup = null; input.placeholder = DEFAULT_INPUT_PLACEHOLDER; }
689479
+ }
689480
+ function acceptFollowupSuggestion() {
689481
+ if (chatFollowup === null || input.value.trim().length > 0) return false;
689482
+ const t = chatFollowup;
689483
+ clearFollowupSuggestion();
689484
+ input.value = t;
689485
+ try { input.dispatchEvent(new Event('input')); } catch (e) {}
689486
+ input.focus();
689487
+ try { input.setSelectionRange(t.length, t.length); } catch (e) {}
689488
+ return true;
689489
+ }
689490
+ async function fetchFollowupSuggestion() {
689491
+ try {
689492
+ if (!chatSessionId || input.value.trim().length > 0) return;
689493
+ const r = await fetch('/v1/chat/suggest-followup', {
689494
+ method: 'POST', headers: headers(), body: JSON.stringify({ session_id: chatSessionId }),
689495
+ });
689496
+ if (!r.ok) return;
689497
+ const d = await r.json();
689498
+ if (d && d.suggestion) setFollowupSuggestion(d.suggestion);
689499
+ } catch (e) { /* best-effort */ }
689500
+ }
689466
689501
  const sendBtn = document.getElementById('send-btn');
689467
689502
  const modelSelect = document.getElementById('model-select');
689468
689503
  // Persist the selected model on every change so a browser refresh /
@@ -689567,6 +689602,8 @@ if (conv) {
689567
689602
 
689568
689603
  // Auto-resize textarea + WO-CHAT-CHECKIN typing detection
689569
689604
  input.addEventListener('input', () => {
689605
+ // Dismiss the suggested follow-up the moment the user starts typing.
689606
+ if (input.value.length > 0) clearFollowupSuggestion();
689570
689607
  input.style.height = 'auto';
689571
689608
  // OWUI-4: bump max from 120 (~6 lines) to 240 (~12 lines).
689572
689609
  input.style.height = Math.min(input.scrollHeight, 240) + 'px';
@@ -689597,6 +689634,10 @@ input.addEventListener('keydown', (e) => {
689597
689634
  if (e.key === 'Tab') { e.preventDefault(); _slashPaletteAccept(); return; }
689598
689635
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); _slashPaletteAccept(); return; }
689599
689636
  }
689637
+ // Suggested follow-up: Tab accepts the ghost text as real input.
689638
+ if (e.key === 'Tab' && !e.shiftKey && chatFollowup !== null && input.value.trim().length === 0) {
689639
+ if (acceptFollowupSuggestion()) { e.preventDefault(); return; }
689640
+ }
689600
689641
  // OWUI-4: Cmd/Ctrl+Enter — always sends, never inserts a newline.
689601
689642
  if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
689602
689643
  e.preventDefault();
@@ -690309,6 +690350,7 @@ async function runSlashCommandInline(rawInput) {
690309
690350
  async function sendMessage() {
690310
690351
  const text = input.value.trim();
690311
690352
  if (!text || streaming) return;
690353
+ clearFollowupSuggestion(); // a real send supersedes any ghost suggestion
690312
690354
 
690313
690355
  // ─── Slash command passthrough ────────────────────────────────────────
690314
690356
  // /foo args → POST /v1/command, render result inline as a system
@@ -690686,6 +690728,8 @@ async function sendMessage() {
690686
690728
  chatAbortController = null;
690687
690729
  document.getElementById('send-btn').style.display = 'inline-block';
690688
690730
  document.getElementById('stop-btn').style.display = 'none';
690731
+ // Reply finished — offer a suggested follow-up as ghost text in the input.
690732
+ try { fetchFollowupSuggestion(); } catch (e) {}
690689
690733
  // OWUI-3: belt-and-braces — make sure the indicator is gone if we
690690
690734
  // reach this finally{} via an error path before the success branch
690691
690735
  // had a chance to call hideStreamingIndicator.
@@ -697329,6 +697373,7 @@ function getOpenApiSpec() {
697329
697373
  },
697330
697374
  "/v1/chat/sessions": { get: { summary: "List active chat sessions", tags: ["Chat"], responses: { 200: { description: "Session list" } } } },
697331
697375
  "/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" } } } },
697376
+ "/v1/chat/suggest-followup": { post: { summary: "Suggest one short next-message follow-up for a session (ghost-text input) — body {session_id}", tags: ["Chat"], responses: { 200: { description: "{ suggestion }" } } } },
697332
697377
  "/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" } } } },
697333
697378
  // ───── AIWG cascade ─────
697334
697379
  "/v1/aiwg": { get: { summary: "AIWG installation root + control map", tags: ["AIWG"], responses: { 200: { description: "AIWG installation summary" } } } },
@@ -698184,6 +698229,62 @@ var init_chat_run_registry = __esm({
698184
698229
  }
698185
698230
  });
698186
698231
 
698232
+ // packages/cli/src/api/chat-followup.ts
698233
+ function normalizeBaseUrl2(url) {
698234
+ let u = (url || "").trim().replace(/\/+$/, "");
698235
+ if (u.endsWith("/v1")) u = u.slice(0, -3);
698236
+ return u;
698237
+ }
698238
+ function cleanSuggestion(raw) {
698239
+ let s2 = (raw || "").trim();
698240
+ s2 = s2.split("\n")[0].trim();
698241
+ s2 = s2.replace(/^["'`*\-\d.\s]+/, "").replace(/["'`]+$/, "").trim();
698242
+ if (/^(none|n\/?a|no follow|nothing)\b/i.test(s2)) return "";
698243
+ return s2.length > 140 ? s2.slice(0, 139).trimEnd() + "…" : s2;
698244
+ }
698245
+ async function suggestFollowup(args) {
698246
+ const turns = (args.turns || []).filter((t2) => t2 && t2.content && (t2.role === "user" || t2.role === "assistant"));
698247
+ if (!turns.length || !args.config.model || !args.config.backendUrl) return "";
698248
+ const transcript = turns.slice(-6).map((t2) => `${t2.role === "user" ? "User" : "Assistant"}: ${String(t2.content).slice(0, 800)}`).join("\n");
698249
+ try {
698250
+ const url = normalizeBaseUrl2(args.config.backendUrl) + "/v1/chat/completions";
698251
+ const headers = { "Content-Type": "application/json" };
698252
+ if (args.config.apiKey) headers["Authorization"] = `Bearer ${args.config.apiKey}`;
698253
+ const body = {
698254
+ model: args.config.model,
698255
+ messages: [
698256
+ {
698257
+ role: "system",
698258
+ content: "You suggest the single most likely NEXT message the user would send to continue this conversation. Write it in the user's voice (imperative, first person), as if they typed it. Reply with ONLY that one short prompt — no quotes, no preamble, no markdown, max ~12 words. If no natural follow-up exists, reply with exactly: NONE."
698259
+ },
698260
+ { role: "user", content: `Conversation so far:
698261
+ ${transcript}
698262
+
698263
+ The user's likely next message:` }
698264
+ ],
698265
+ temperature: 0.4,
698266
+ max_tokens: 40,
698267
+ stream: false
698268
+ };
698269
+ const resp = await fetch(url, {
698270
+ method: "POST",
698271
+ headers,
698272
+ body: JSON.stringify(body),
698273
+ signal: AbortSignal.timeout(args.timeoutMs ?? 12e3)
698274
+ });
698275
+ if (!resp.ok) return "";
698276
+ const data = await resp.json();
698277
+ return cleanSuggestion(data?.choices?.[0]?.message?.content ?? "");
698278
+ } catch {
698279
+ return "";
698280
+ }
698281
+ }
698282
+ var init_chat_followup = __esm({
698283
+ "packages/cli/src/api/chat-followup.ts"() {
698284
+ "use strict";
698285
+ }
698286
+ });
698287
+
698187
698288
  // packages/cli/src/docker.ts
698188
698289
  import { execSync as execSync59, spawn as spawn32 } from "node:child_process";
698189
698290
  import { existsSync as existsSync158, mkdirSync as mkdirSync99, writeFileSync as writeFileSync84 } from "node:fs";
@@ -706426,6 +706527,24 @@ ${historyLines}
706426
706527
  });
706427
706528
  return;
706428
706529
  }
706530
+ if (pathname === "/v1/chat/suggest-followup" && method === "POST") {
706531
+ if (!checkAuth(req3, res, "read")) return;
706532
+ const fbBody = await parseJsonBody(req3);
706533
+ const sid = fbBody?.session_id ? String(fbBody.session_id) : "";
706534
+ const session = sid ? lookupSession(sid) : null;
706535
+ const turns = session ? session.messages.filter((m2) => m2 && (m2.role === "user" || m2.role === "assistant") && typeof m2.content === "string").map((m2) => ({ role: m2.role, content: m2.content })) : [];
706536
+ if (turns.length === 0) {
706537
+ jsonResponse(res, 200, { suggestion: "" });
706538
+ return;
706539
+ }
706540
+ const cfg = loadConfig();
706541
+ const suggestion = await suggestFollowup({
706542
+ turns,
706543
+ config: { backendUrl: cfg.backendUrl, model: cfg.model, apiKey: cfg.apiKey }
706544
+ });
706545
+ jsonResponse(res, 200, { suggestion });
706546
+ return;
706547
+ }
706429
706548
  const chatSessionMatch = pathname.match(/^\/v1\/chat\/sessions\/([^/]+)$/);
706430
706549
  if (chatSessionMatch) {
706431
706550
  const sid = decodeURIComponent(chatSessionMatch[1]);
@@ -709080,6 +709199,7 @@ var init_serve = __esm({
709080
709199
  init_omnius_directory();
709081
709200
  init_session_summary();
709082
709201
  init_chat_run_registry();
709202
+ init_chat_followup();
709083
709203
  init_omnius_directory();
709084
709204
  init_command_registry();
709085
709205
  init_profiles();
@@ -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
+ | `POST` | `/v1/chat/suggest-followup` | Suggest one short next-message follow-up (ghost-text input) |
55
56
  | `GET` | `/v1/chat/sessions/{id}/status` | Reactive recall: live run status + unseen deltas (`?since=<seq>`) |
56
57
  | `POST` | `/v1/chat/check-in` | Steering check-in for active chat |
57
58
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.339",
3
+ "version": "1.0.340",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.339",
9
+ "version": "1.0.340",
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.339",
3
+ "version": "1.0.340",
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",