9 Commits

Author SHA1 Message Date
Vykos
b59bbe80ce Harden chat streaming DOM sinks (#2498) 2026-06-04 20:49:37 +02:00
Kenny Van de Maele
7443c36bd9 feat: Add edit_file tool + file-change diffs (#1239)
* Add edit_file tool + file-change diffs

edit_file is an exact old_string -> new_string replacement on a file on disk
(fails if old_string is missing or non-unique unless replace_all); write_file
also returns a unified diff. Diffs render collapsed in the tool bubble
(filename + +adds/-dels, theme colors); the raw JSON command box is hidden.

Security: edit_file is a sensitive filesystem-write tool, treated everywhere
write_file is —
  - added to NON_ADMIN_BLOCKED_TOOLS (is_public_blocked_tool / blocked_tools_for_owner),
    so on auth-enabled deployments a non-admin cannot run it; execute_tool_block
    refuses it for non-admin owners.
  - confined by the same path policy as read_file/write_file (allowlist +
    sensitive-file deny) via _resolve_tool_path.

Disambiguation in tool descriptions + bash prompt: edit_file/write_file are the
only way to write files (they show a diff) — never edit_document (editor panel)
or a bash heredoc/redirect.

Tests (tests/test_edit_file.py): non-admin block (policy + execution gate),
successful edit, not-found old_string, non-unique old_string (+ replace_all),
and path outside the allowed roots.

Files: src/tool_execution.py, src/agent_loop.py, src/tool_schemas.py,
src/agent_tools.py, src/tool_index.py, static/js/chat.js, static/style.css,
tests/test_edit_file.py.

* Drop redundant import os in write_file closure

os is already imported at module top.
2026-06-04 18:29:10 +02:00
Kenny Van de Maele
147d1fbde6 Show the serving provider in the model-info card (#2185)
* Show the serving provider in the model-info card

The model-info popup (click the model name on a message) shows the model
and pricing, with a logo inferred from the model NAME. But the same model
can be served by different endpoints — e.g. claude-haiku via OpenRouter
vs GitHub Copilot vs Anthropic direct — which the name-based logo can't
distinguish.

Add a 'Provider' line derived from the session's endpoint URL:
- new providerLabel(endpointUrl) in static/js/providers.js maps the host
  to a friendly name (GitHub Copilot, OpenRouter, Anthropic, OpenAI,
  Google, AWS Bedrock, DeepSeek, Mistral, Groq, Together, Fireworks,
  Perplexity, xAI), 'Local' for loopback/LAN, else the bare host.
- static/js/chatRenderer.js renders it under Model in the card, from
  window.sessionModule.getCurrentEndpointUrl().

* Anchor provider-label patterns to the hostname

providerLabel matched its patterns against the full endpoint URL with
unanchored substrings, so a host like max.airlines.com matched /x\.ai/ and was
mislabeled "xAI". Anchor each pattern to the end of the hostname ((^|.)domain$)
and test against the parsed host instead of the raw URL.
2026-06-04 18:22:31 +02:00
Afonso Coutinho
eac354629a fix: model cost/info matches first substring key (gpt-4o-mini billed as gpt-4o) (#1439)
* fix: match model name to the longest known key, not the first substring

* test: model key matching prefers the longest specific key
2026-06-04 03:05:37 +01:00
lekt8
4d1829add0 Clear the composer draft when entering the New Chat / welcome state (#1408)
Clicking "New chat" (the brand/welcome navigation path) left the previous
session's unsent draft in the composer (issue #1343). The direct model-picker
path (createDirectChat) already cleared it, but the welcome path did not.

Clear `#message` in chatRenderer.showWelcomeScreen() — the shared entry point
for that state — resetting its autosized height and dispatching an `input` event
so the send button / autosize listeners update. Switching between existing
sessions loads them directly and does not call showWelcomeScreen, so genuine
drafts are not erased.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:07:31 +09:00
nsgds
a857d2016d fix: don't bill self-hosted models reached by a container/service hostname (#596)
* fix(cost): treat dotless container hostnames as local (free)

getModelCost() substring-matches model names against a cloud price table, so a self-hosted 'nemotron'/'llama' model was billed at cloud rates. isLocalEndpoint() only recognized IPs / localhost / .local, not bare Docker service names (nim-nano, llamaswap), so the local-is-free guard missed them. A single-label hostname (no dot) can never be a public API -> treat as local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(cost): isLocalEndpoint classifies service names local, cloud FQDNs billable

Covers @pewdiepie-archdaemon's requested cases: llamaswap/nim-nano + localhost/private-IPs/.local => local (free); api.openai.com/openrouter.ai/etc => not local. Drives the real function via node --input-type=module (same approach as test_reply_recipients_js.py), skips when node is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:47:58 +09:00
Collin Osborne
471ee494f0 fix: make transient dropdown/popup menus close on Escape
The global Escape arbiter in ui.js only sees `.modal` elements, so the many
ad-hoc dropdowns and context popups that are built on the fly and appended to
<body> ignored Escape entirely: document-library card/chat menus, chat
context/stats/overflow popups, cookbook serve & running menus, calendar event
menus, and compare pane menus.

Add a small DOM-free dismissal registry (static/js/escMenuStack.js). Menus
register a dismiss callback while open, and the arbiter closes the
most-recently-opened one first, so a menu opened over a modal closes before the
modal. bindMenuDismiss() wires the ubiquitous "append-to-body, close on outside
click" idiom to both the outside-click listener and the Escape stack in one
call, and dismissOrRemove() lets the pre-existing bulk removers (scroll/swipe/
modal-dismiss cleanup, reopen sweeps) tear a menu down through its real teardown
instead of orphaning its stack entry.

Covers ~14 menus across documentLibrary, chatRenderer, cookbookServe,
cookbookRunning, calendar, and compare/panes. Every teardown path — item click,
outside click, swipe, toggle, rebuild, bulk cleanup — routes through the
registry so no entry is ever stranded.

tests/test_esc_menu_stack_js.py pins the registry's LIFO and
exactly-one-per-press guarantees (node-driven; skips when node is absent).
2026-06-01 14:23:22 -04:00
pewdiepie-archdaemon
7e1a9ccc88 Close stale setup and line-ending gaps 2026-06-01 10:43:35 +09:00
pewdiepie-archdaemon
e5c99a5eee Odysseus v1.0 2026-05-31 23:58:26 +09:00