feat: initial Lam Agent extension for Vicinae

Agentic AI chat inside the Vicinae launcher, powered by Ollama.

- chat: the search bar is the composer (Return sends), cmd+return opens a
  multi-line composer, answers stream into a markdown detail panel, the
  newest turn renders first, and Tab reaches a conversation switcher in
  the search bar accessory
- sessions: browse, reopen, copy, and delete past conversations
- setup: verifies the Ollama connection and lists the models the key can
  actually reach, rather than trusting a hand-typed model name
- tools: get_datetime, get_location, web_search, web_fetch. All read-only,
  and the tool-calling loop is capped at 5 rounds

Notes on two constraints that shaped the design:

The search bar is single-line and there is no API to change it, so
"Return sends" and "cmd+return inserts a newline" cannot both live in one
field. They are split across the search bar and a pushed Form instead.

An extension can read preferences but cannot write them, so setup hands
over the exact model name to paste rather than saving it.

The logic layer (ollama, agent, tools, location, history, markdown) holds
no Vicinae imports and is unit tested without a runtime; a test enforces
that boundary. 77 tests cover NDJSON chunk-boundary parsing, the tool
loop, session caps, and timezone handling.
This commit is contained in:
2026-10-04 04:32:31 +08:00
parent 1d39aabb50
commit 121d644e90
36 changed files with 7136 additions and 2 deletions
+26
View File
@@ -0,0 +1,26 @@
const { defineConfig } = require("eslint/config");
const js = require("@eslint/js");
const tseslint = require("typescript-eslint");
const prettier = require("eslint-config-prettier");
const globals = require("globals");
module.exports = defineConfig([
{
ignores: ["node_modules/**", "vicinae-env.d.ts", "eslint.config.js", "dist/**"],
},
js.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
languageOptions: {
globals: { ...globals.node },
},
},
{
// Tests exercise the Vitest globals declared in vitest.config.ts.
files: ["src/__tests__/**/*.ts", "src/__tests__/**/*.tsx"],
languageOptions: {
globals: { ...globals.node, ...globals.vitest },
},
},
]);