Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c540421
wiki: log merges of #115 #127 #137 #139 #141 #142 #143
claude Oct 2, 2026
5a6efcd
wiki: decision models and Ollama model research (#148)
claude Oct 2, 2026
aaa033c
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 2, 2026
97992d7
wiki: Hermes model roles, memory embedders and Jev plugins (#148, #149)
claude Oct 2, 2026
f1a3427
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 2, 2026
af2d08f
docs: default local Ollama to qwen3:8b and document Ollama cloud (#148)
claude Oct 2, 2026
9c6f11b
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 2, 2026
45f9ef1
wiki: fix Hermes Jev plugin count and update providers after #152
claude Oct 2, 2026
84e91ab
feat(config): per-role provider chains via models.chat and models.com…
claude Oct 2, 2026
9fd71b7
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 2, 2026
c00a4c7
test, docs: cover models.compress through Agent; clarify omitted-role…
claude Oct 2, 2026
b5a20f1
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 3, 2026
b054cc7
feat(plugins): entry settings, host model access and before_llm_call …
claude Oct 3, 2026
d6b712a
fix(plugins): deep-copy hook messages, honor run abort in models.chat…
claude Oct 3, 2026
ab5df46
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 3, 2026
2b01b6d
wiki: ingest Ollama System One/Clef check; record model roles and plu…
claude Oct 3, 2026
e010ef8
wiki: index one-liners match model roles and plugin host features
claude Oct 3, 2026
4640db2
wiki: bump index updated date
claude Oct 3, 2026
98a2c2b
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 3, 2026
b8266bc
feat(examples): decision_lane plugin for game_bridge action selection
claude Oct 3, 2026
bab4850
fix(decision_lane): zero off-criteria answers, require numeric thresh…
claude Oct 3, 2026
e0237cc
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
c59f5d1
wiki: record the decision_lane prototype (#159)
claude Oct 4, 2026
ca1c05f
wiki: decision_lane egress mentions the Bearer key
claude Oct 4, 2026
dcbb9e6
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
2074964
fix(agent): tools_enabled filters plugin tools; guard hooks fail clos…
claude Oct 4, 2026
4f97ea2
fix(loop): emit tool_call_end for last-turn skipped calls so transcri…
claude Oct 4, 2026
1b2d4d9
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
4840336
wiki: record #161 (plugin-tool allowlist, fail-closed guard hooks, la…
claude Oct 4, 2026
0b3d485
wiki: move the #161 fixed note out of the open-holes list
claude Oct 4, 2026
d7e1e95
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
0930c7d
fix: http_request status, terminal_timeout_ms, .. path guard, OpenAI …
claude Oct 4, 2026
09fc444
fix(openai): treat a null tool-call id as missing; correct http_reque…
claude Oct 4, 2026
1294e6a
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
07d0d3b
docs: webhook binds loopback by default; drop stale version from over…
claude Oct 4, 2026
0608068
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
16c4bdb
refactor(providers): share HTTP/error helpers in providers/http.ts (#…
claude Oct 4, 2026
53962b9
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
fe394bd
fix: Ctrl+C/Esc cancel, atomic config update, test tmp cleanup (#133)
claude Oct 4, 2026
84f62a4
fix: on cancel, drop the unanswered user line and do not reprint the …
claude Oct 4, 2026
3cfddb1
fix: keep this turn's reply when a cancel lands during tools
claude Oct 4, 2026
8c5fe08
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
b941191
wiki: map #113 phase children and related issues
claude Oct 4, 2026
39aade9
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 4, 2026
4c8b180
feat(config): global ~/.lich/config.json merged under the project con…
claude Oct 4, 2026
dc815f3
fix(config): read the legacy user config only when ~/.lich/config.jso…
claude Oct 4, 2026
36f8f87
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 5, 2026
146619b
feat(config): lich init --global and a "save as global" wizard step (…
claude Oct 5, 2026
2faf90c
fix(wizard): write the config once, plugins included, when work_dir i…
claude Oct 5, 2026
eff0ea1
fix(wizard): store absolute plugin paths when writing ~/.lich/config.…
claude Oct 5, 2026
558e3d1
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 5, 2026
eaadb01
feat(config): named profiles in ~/.lich/profiles (#117)
claude Oct 5, 2026
ae7e9d8
fix(profiles): profile over the home config, stable errors, guard lis…
claude Oct 5, 2026
2d64bbf
fix(profiles): keep the legacy base at home; disk_usage skips denied …
claude Oct 5, 2026
e63bd5a
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 5, 2026
4957452
wiki: config layers page after #170-#172
claude Oct 5, 2026
0d9590e
wiki: scope config writer and error-path claims on lich-config
claude Oct 5, 2026
99fd78a
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 5, 2026
b95516b
fix(gateway): one session queue, LRU eviction, telegram-only /start, …
claude Oct 5, 2026
53d8118
fix(gateway): enforce max_conversations on store; doc webhook 502
claude Oct 5, 2026
f175158
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 5, 2026
33dbf68
feat(gateway): GatewayPolicy and declared adapter capabilities (#144)
claude Oct 5, 2026
79fa83d
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 8, 2026
13551c8
feat(serve): queue frames per session on each connection (#144)
claude Oct 8, 2026
f405abe
Merge remote-tracking branch 'origin/main' into claude/open-issues-pr…
claude Oct 8, 2026
fbc7030
feat(serve): report protocol version and capabilities in health (#144)
claude Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- `lich serve` `health` also returns `protocol_version` (`1`, bumped only on
breaking changes) and `capabilities: { methods, notifications }` (#144).
`methods` lists `prompt.*` only when the server has an Agent.
- `lich serve` queues frames per session on each connection instead of
per connection (#144). A long `prompt.submit` no longer holds up other
sessions or session-less frames such as `health` on the same socket. Frames
Expand Down
10 changes: 8 additions & 2 deletions docs/architecture/serve.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,21 @@ method `event` (no `id`).

| Method | Params | Result |
| --- | --- | --- |
| `health` | `{}` | `{ status: "ok", version }` (`LICH_VERSION`) |
| `health` | `{}` | `{ status: "ok", version, protocol_version, capabilities }` |
| `session.create` | `{ label?, source }` | `{ session_id }` |
| `session.list` | `{}` | `{ sessions: [{ id, mtime_ms }, ...] }` |
| `session.clear` | `{ session_id }` | `{ session_id }` |
| `session.resume` | `{ id, source? }` | `{ session_id, resumed_id, message_count }` |
| `prompt.submit` | `{ session_id, text }` | reply, usage, `session_path`, `stopped_reason`, … |
| `prompt.abort` | `{ session_id }` | `{ session_id, aborted }` |

`health` reports `version` (`LICH_VERSION`), `protocol_version`
(`SERVE_PROTOCOL_VERSION`, currently `1`) and
`capabilities: { methods, notifications }`. `protocol_version` changes only
on breaking changes; new features are added to `capabilities`, so clients
check it before calling a method. `methods` lists `prompt.submit` and
`prompt.abort` only when the server has an Agent.

`health` and `session.list` have no param fields. Typed clients still send
`params: {}`, because `ServeRequest` requires `params`. JSON-RPC 2.0 also
allows omitting `params`; serve handlers accept that omission the same as `{}`.
Expand Down Expand Up @@ -113,7 +120,6 @@ same WebSocket that issued `prompt.submit` while the call is still in flight.
- Origin allowlisting is deferred until Electron’s page origin policy is decided;
do not assume `file://` / `app://` behavior here.
- Frame size capped at ~1 MiB (`maxPayload`).
- `health` returns `{ status: "ok", version }` (`LICH_VERSION` from `src/version.ts`).
- Frames are queued per session within a connection: frames whose
`params.session_id` match are handled in arrival order and get in-order
replies, while other sessions, and frames that name no session (such as
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export {
SERVE_METHODS,
SERVE_NOTIFICATION_EVENT,
SERVE_ERROR_CODES,
SERVE_PROTOCOL_VERSION,
} from "./serve/protocol.js";
export type {
HealthParams,
Expand All @@ -78,6 +79,7 @@ export type {
PromptAbortResult,
PromptSubmitParams,
PromptSubmitResult,
ServeCapabilities,
ServeEventNotification,
ServeEventParams,
ServeMethod,
Expand Down
18 changes: 18 additions & 0 deletions src/serve/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const SERVE_NOTIFICATION_EVENT = "event" as const;

export type ServeNotificationMethod = typeof SERVE_NOTIFICATION_EVENT;

/**
* Wire protocol version reported by `health`. Bumped only on breaking changes;
* additive features show up in `capabilities` instead.
*/
export const SERVE_PROTOCOL_VERSION = 1;

export type JsonRpcId = string | number;

export interface JsonRpcRequest<M extends string = string, P = unknown> {
Expand Down Expand Up @@ -75,7 +81,19 @@ export type HealthParams = Record<string, never>;

export interface HealthResult {
status: "ok";
/** Lich release (`LICH_VERSION`). */
version: string;
/** `SERVE_PROTOCOL_VERSION` of this server. */
protocol_version: number;
capabilities: ServeCapabilities;
}

/** What this server answers, so clients can check before calling. */
export interface ServeCapabilities {
/** Methods served; `prompt.*` only when the server has an Agent. */
methods: ServeMethod[];
/** Server → client notification methods. */
notifications: ServeNotificationMethod[];
}

export interface SessionCreateParams {
Expand Down
19 changes: 17 additions & 2 deletions src/serve/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import type {
JsonRpcSuccess,
ServeMethod,
} from "./protocol.js";
import { SERVE_ERROR_CODES, SERVE_METHODS } from "./protocol.js";
import {
SERVE_ERROR_CODES,
SERVE_METHODS,
SERVE_NOTIFICATION_EVENT,
SERVE_PROTOCOL_VERSION,
} from "./protocol.js";
import type { ServeEventNotify, ServePromptService } from "./prompts.js";
import type { ServeSessionStore } from "./sessions.js";

Expand Down Expand Up @@ -78,7 +83,17 @@ async function dispatch_method(
if (params !== undefined && is_empty_params(params) !== true) {
return error_response(id, SERVE_ERROR_CODES.INVALID_PARAMS, "Invalid params");
}
const result: HealthResult = { status: "ok", version: context.version };
const result: HealthResult = {
status: "ok",
version: context.version,
protocol_version: SERVE_PROTOCOL_VERSION,
capabilities: {
methods: SERVE_METHODS.filter(
(name) => context.prompts !== undefined || name.startsWith("prompt.") !== true,
),
notifications: [SERVE_NOTIFICATION_EVENT],
},
};
return { jsonrpc: "2.0", id: id as JsonRpcId, result };
}
if (method === "session.create") {
Expand Down
2 changes: 1 addition & 1 deletion test/serve_cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe("bun src/cli.ts serve boot", () => {
expect(result).toEqual({
jsonrpc: "2.0",
id: 1,
result: { status: "ok", version: LICH_VERSION },
result: expect.objectContaining({ status: "ok", version: LICH_VERSION, protocol_version: 1 }),
});
child.kill("SIGTERM");
await wait_exit(child);
Expand Down
4 changes: 2 additions & 2 deletions test/serve_rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe("serve rpc framing", () => {
expect(null_id.body).toEqual({
jsonrpc: "2.0",
id: null,
result: { status: "ok", version: "9.9.9" },
result: expect.objectContaining({ status: "ok", version: "9.9.9" }),
});

const null_params = await dispatch(
Expand All @@ -140,7 +140,7 @@ describe("serve rpc framing", () => {
expect(null_params.body).toEqual({
jsonrpc: "2.0",
id: 3,
result: { status: "ok", version: "9.9.9" },
result: expect.objectContaining({ status: "ok", version: "9.9.9" }),
});
});

Expand Down
26 changes: 23 additions & 3 deletions test/serve_transport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import path from "node:path";
import { Writable } from "node:stream";
import WebSocket from "ws";
import { LICH_VERSION } from "../src/index.js";
import type { ServePromptService } from "../src/serve/prompts.js";
import { SERVE_METHODS, SERVE_PROTOCOL_VERSION, type HealthResult } from "../src/serve/protocol.js";
import { handle_serve_rpc_message } from "../src/serve/rpc.js";
import { create_serve_server, type ServeServer } from "../src/serve/server.js";
import { create_serve_session_store } from "../src/serve/sessions.js";
Expand Down Expand Up @@ -91,7 +93,7 @@ afterEach(async () => {
});

describe("serve rpc health", () => {
it("returns status and version for health", async () => {
it("returns status, versions and capabilities for health", async () => {
const sessions = create_serve_session_store(path.join(await make_temp_dir("serve-health"), "s"));
const raw = await handle_serve_rpc_message(
JSON.stringify({ jsonrpc: "2.0", id: 1, method: "health", params: {} }),
Expand All @@ -100,10 +102,28 @@ describe("serve rpc health", () => {
expect(JSON.parse(raw ?? "")).toEqual({
jsonrpc: "2.0",
id: 1,
result: { status: "ok", version: "9.9.9" },
result: {
status: "ok",
version: "9.9.9",
protocol_version: SERVE_PROTOCOL_VERSION,
capabilities: {
methods: ["health", "session.create", "session.list", "session.clear", "session.resume"],
notifications: ["event"],
},
},
});
});

it("lists prompt methods in health only when an agent is configured", async () => {
const sessions = create_serve_session_store(path.join(await make_temp_dir("serve-health-agent"), "s"));
const raw = await handle_serve_rpc_message(
JSON.stringify({ jsonrpc: "2.0", id: 1, method: "health", params: {} }),
{ version: "9.9.9", sessions, prompts: {} as ServePromptService },
);
const body = JSON.parse(raw ?? "") as { result: HealthResult };
expect(body.result.capabilities.methods).toEqual([...SERVE_METHODS]);
});

it("returns agent-not-configured for prompt.submit without an agent", async () => {
const sessions = create_serve_session_store(path.join(await make_temp_dir("serve-stub"), "s"));
const raw = await handle_serve_rpc_message(
Expand Down Expand Up @@ -146,7 +166,7 @@ describe("serve websocket transport", () => {
expect(result).toEqual({
jsonrpc: "2.0",
id: 1,
result: { status: "ok", version: LICH_VERSION },
result: expect.objectContaining({ status: "ok", version: LICH_VERSION }),
});
});

Expand Down
Loading