Commit Graph
7 Commits
Author SHA1 Message Date
adrianbonpin 1ae44adc7b fix: end a spent tool budget in an answer, not a notice about the loop
Asked for the fish in every Valheim biome, the turn researched for five
rounds and then displayed "Stopped after 5 rounds of tool calls without a
final answer." Three things compounded:

- MAX_TOOL_ITERATIONS of 5 is too few for a question that needs several
  searches and a few pages. It is a safety net against looping, not a
  budget anyone chose.
- The loop discarded everything. `content` was scoped to the iteration, so
  every preamble the model wrote was thrown away and the turn ended with an
  app-generated message about its own control flow.
- The model was never told it was running out, so it kept searching until
  the hard stop instead of answering with what it had.

The budget is now spent on a closing round with the tools taken away and an
instruction to answer from what was gathered, naming anything it could not
confirm. So the worst case is a partial answer that says what is missing.
If the closing round produces nothing, the last preamble is used, and only
then the notice. The cap is 8 because the extra round is not more searching,
it is the answer.

Verified: 349 tests, tsc, eslint, prettier, and vici build.
2026-10-04 16:08:26 +08:00
adrianbonpin 35d88f635c feat: let the model answer with tables, lists and headings
The model refused to write a table and described one instead: a bulleted
list of rows with the column names in inline code. That was our instruction,
not a rendering limit. The default prompt asked for brevity in a narrow
window and then said "skip large headings and wide tables", so the model
obeyed and flattened the answer.

Vicinae renders tables properly. The screenshot that prompted this is a
rendered one: `MdTable.qml` is wired to block type 5, and `markdown-model.cpp`
attaches the `table` GFM extension. Nothing was missing.

- The prompt now names what actually renders (tables, bulleted and numbered
  lists, headings, blockquotes, bold, italics, links, code) and asks for the
  shape that fits, with a cap of about four narrow columns because the detail
  pane really is narrow. It also says outright not to hand back a table as a
  bulleted list of rows.
- `separateTables` adds the blank line a table needs in front of it. GFM tables
  cannot interrupt a paragraph, so a table written straight under a line of
  prose renders as a paragraph of pipes and dashes. Only a genuine table start
  is touched, which is a row followed by a delimiter row; prose with a pipe, a
  table already at a block boundary, and a fenced example are all left alone.
- A regression test asserts a real table survives the whole repair pass
  untouched: pipes are not fences, not links, and not lone spans.

Verified: 304 tests, tsc, eslint, prettier, and vici build.
2026-10-04 15:44:29 +08:00
adrianbonpin 5e083d8142 feat: snip a region into chat, and make links clickable
Two changes: screenshot input gated on the model really reporting vision,
and a fix for URLs that arrived as code.

Links
- A fence whose body is all URLs, a lone `https://...` inline span, and a
  line that is only a URL are rewritten into [url](url) markdown links.
  Vicinae attaches only the table and strikethrough GFM extensions, so
  autolink is off and a bare URL is dead text; clickability comes from
  MarkdownModel turning CMARK_NODE_LINK into an <a href>. A fenced URL was
  a dark panel with a Copy button and no way to open it.
- promoteStandaloneCodeSpans was itself promoting a lone URL span into a
  fence, so it made this worse. Link restoration now runs first.
- The default prompt says never to fence a URL.
- Anything that is not one of those three shapes is copied through, so a
  curl invocation containing an address is never rewritten.

Screenshots
- capture.ts shells out to an auto-detected tool: grimblast, grim + slurp,
  spectacle, maim, scrot, flameshot, or screencapture on macOS. There is
  no screenshot call in @vicinae/api and Clipboard moves no binary, so the
  extension owns the capture and reads the file it wrote rather than
  guessing at the clipboard. Cancel is detected by whether an image landed
  on disk, because the tools disagree on exit codes.
- In chat: "Snip a Region" in the action panel (cmd+shift+S) attaches to
  the current conversation. Standalone: "Snip to Lam" starts a new one.
- vision.ts caches the /api/show capability answer per host and model. A
  failed check is "unknown", never "no", so a dropped connection is not
  reported as a fact about the user's model. Actions still fail closed.
- Only the path is stored, base64 is read at send time, and the image goes
  on its own turn only. Capture is never a model-callable tool.

Verified: 244 tests, tsc, eslint, and vici build (both entrypoints).
2026-10-04 15:13:15 +08:00
adrianbonpin 95edbcd20b fix: fence lone commands and give the prompt some warmth (#12) 2026-10-03 22:00:38 +00:00
adrianbonpin ac2da73669 feat: label code fences so Vicinae can colour them (#10) 2026-10-03 21:37:11 +00:00
adrianbonpin c49ffeb274 feat: strip speaker labels from the transcript, add a plain-prose prompt (#9) 2026-10-03 21:28:38 +00:00
adrianbonpin 8b507e7953 feat: initial Lam Agent extension (#1) 2026-10-03 20:32:40 +00:00