diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b9708aea..156219489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,8 @@ opt-out. - Agent Forge: a run on a real provider without an API key fails with `LOUSHO_PROVIDER_MISSING_API_KEY` (HTTP 422) instead of silently using the mock provider, and the provider pill shows what actually ran. - The examples import `@lousho/build-ai-agent` and its public subpaths instead of the repository's `src/`, so they can be copied into an app as they are. +- The `package.json` that `lousho build --target=node-server|docker` writes lists only the optional peers the bundled server loads (the provider package, `@modelcontextprotocol/sdk` with `mcpServers`), no longer `@lousho/build-ai-agent`: `dist/server.js` bundles the SDK, `ai` and `zod`, so the image's install is smaller. +- The npm package no longer ships the CommonJS build of the CLI (`dist/cli/*.js` and maps) or the CLI's type declarations: `bin/lousho.js` loads only `dist/cli/*.mjs`. ### Added - Providers: a `timeout` option (default 10 minutes until the response starts and between chunks; `0` or `Infinity` turns it off) and a `fetch` option on every built-in provider. `OpenAIProvider({ api: 'chat' })` and `LOUSHO_OPENAI_API=chat` use Chat Completions for OpenAI-compatible servers (LM Studio, Ollama, vLLM, llama.cpp; the built-in hosted tools are then refused with `LOUSHO_HOSTED_TOOL_UNSUPPORTED`); see [Local and OpenAI-compatible servers](docs/providers.md#local-and-openai-compatible-servers). `withRateLimit()` / `createRateLimiter()` and `createAgent({ rateLimit })` queue model calls client-side. `promptCaching: 'auto'` (the default) marks Anthropic cache breakpoints for Claude on `anthropic` and `openrouter`. `withRetry({ idleTimeoutMs })` fails a stream that stalls between chunks (`timeoutMs` now bounds only the wait for the first chunk). `withFallback()` results carry `servedBy`. - Model settings: `createAgent({ modelSettings })` and per-call `modelSettings` (`maxTokens`, `temperature`, `topP`, penalties, `stop`, `seed`, `toolChoice`), range-checked up front, also in `agent.json`. Per-call `maxSteps` and `instructions` on `send()` / `stream()`. See [Configuration](docs/configuration.md#model-settings). @@ -79,6 +81,9 @@ opt-out. - Channels and schedules: `githubChannel({ pullRequestOpened: true })`, `webhookChannel({ principal })`, exported `fireSchedule()`, `startSchedules({ keepAlive: true })` and an awaitable `stop()`, and `mountFetchChannels({ waitUntil })`. - CLI: `lousho doctor --ping` and base URLs in its output, and `lousho --version`. +- `lousho build --target=node-server|docker` builds a `.ts`/`.js` agent module (the file `lousho dev src/agent.ts` serves: a default or `agent` export holding `createAgent()` options or a built agent). The `cloudflare-worker` target says it builds only a spec or an agent directory. The minimal and tools scaffolds export their options as `src/agent.ts`'s default export, so `lousho dev|chat|mcp|acp|build src/agent.ts` work on a new project, and every scaffold has a `build` script (and `tsup`); the minimal and tools ones also have `dev:ui` (`lousho dev src/agent.ts`). +- The Docker image `lousho build --target=docker` writes has a `HEALTHCHECK` on `GET /health`, and its `.dockerignore` keeps `.env`, `.env.*` and `*.tgz` out of the image. +- `lousho add`, `studio`, `eval` and `traces --help` describe every flag; `lousho help ` prints that command's help. ### Fixed - Durability: concurrent commits on one session no longer lose a turn (new optional `SessionStore.saveIf()` compare-and-swap; `KVStore` stays best effort), and session objects re-read the store instead of caching the transcript. Concurrent `send({ sessionId })` calls in one process run in order. Parallel tool calls are checkpointed as each settles, so a crash no longer re-runs finished calls. A restarted process that resolves a session's approval commits the turn instead of dropping it. `session.clear()` deletes every turn's checkpoints and history, finished turns stay forkable, `session.fork()` works for 122-128 character ids, and sub-agent task ids are never reused. - Stores: `fileStore` file names are case-safe on Windows and macOS (`Alice` and `alice` no longer share a file), long ids and device names are hashed, older long names are still read, writes are `fsync`ed and Windows `EPERM` / `EBUSY` is retried. A corrupt checkpoint fails with `LOUSHO_CHECKPOINT_CORRUPT` and can be discarded. `SqliteStore` waits 50 ms instead of freezing the event loop, retries locked writes without blocking (`LOUSHO_STORAGE_BUSY`) and `prune()` keeps paused turns. `KVStore` approval claims run a tool once in the common case (`ctx.approval.id` serves as an idempotency key). Image and file bytes survive every store. Taking over a stale file lock is serialized, and `KVCheckpointStore.list()` throws without a `list` binding instead of returning `[]`. `agent.approvals.get()` returns `undefined` for an id that cannot name an approval. `fileMemory` and `sqliteMemory` keep every item with several writers. @@ -112,6 +117,13 @@ opt-out. - Approve-with-edits on a sub-agent's *grouped* approval validates the edited args against the tool's schema like a top-level or single sub-agent decision does: before, `snapshot.subagent` stored `decision.args` raw, so an invalid edit (e.g. a string where `z.number()` wants a number) ran the tool with garbage args; it now throws `LOUSHO_TOOL_ARGS_INVALID` and leaves the approval pending. (Eve2-F4) - Aborting a run during a tool call no longer hangs `send()` / `stream()` / `approvals.resolve()` forever on a tool that ignores its `abortSignal`: the tool batch and the approved-tool wait of `resumeAfterApproval()` are raced against the run's signal the way the model call already was (Eve CORE-F15), the run ends `finishReason: 'aborted'` with a 'cancelled' result for calls still running past a short grace window (a result that lands within it is still kept), and the tool keeps running in the background. (Eve2-F5) - A run that re-pauses on several approvals after a resume persists every pending approval id in its checkpoint: `markAwaitingApproval()` wrote only `approvalId`, so durable discovery (`pausedApprovalIds`, `agent.approvals.list()`) named 1 of N approvals; it now carries the group's `approvalIds` like `markGroupPending()` does. (Eve2-F6) +- The `inbox-triage` registry kit loads under `lousho dev`, `chat`, `acp`, `mcp` and `doctor`: its relative imports carry the `.ts` extension Node's type stripping needs (only bundlers resolved them). A test now installs every registry kit and loads it in a plain `node` process, as the CLI does. An extensionless relative import in an agent directory gets a hint naming the specifier to write (`'./lib/store.ts'`), and on a Node without built-in type stripping (before 22.18) the `.ts` load error says Node 22.19+ is required. +- `OPENAI_BASE_URL` (and `ANTHROPIC_BASE_URL`) is used with `@ai-sdk/openai` 1.x (`@ai-sdk/anthropic` 1.x), the line that pairs with `ai` 4: the provider passes it when no `baseURL` is configured. Before, `model: 'openai/...'` called api.openai.com while `lousho doctor --ping` reported the custom endpoint. +- `PropagatingToolError`, `McpToolError` and `WorkspaceError` pass `instanceof` across the ESM and CJS copies of the SDK, so a `.cjs` tool's "do not retry" error stops the run under the ESM `lousho` CLI instead of being handed to the model (and retried). +- `lousho dev` re-evaluates the local helper files an edited tool or agent module imports (the `?t=` cache-bust is passed on to relative imports), instead of logging "reloaded" while serving the stale helper; editor temp files no longer show in the "changed:" list. +- `lousho eval`: a case that vitest fails after the case finished (it ran past its time limit) is reported as that case failing, not as a pass plus an extra `vitest` row; a run that records no case at all (a mistyped `--tag`) exits 1 like a glob that matches nothing. +- The yarn scaffold's `yarn test` works: the templates list `vite`, vitest's peer, which yarn 1 does not install. The README's "Next 3 commands" lists three, and the tools template's README counts its three tools. +- CLI messages: a module that exports no agent names the command that loaded it (`lousho chat:`, not `lousho dev:`); `lousho mcp` without a path says a spec, directory or module is accepted, and `--port`/`--host` without `--http` are a usage error instead of ignored; `lousho version ` and `init --sdk-path ` exit 2; the unknown-provider error lists `mock`. ## [1.0.0-alpha.19] - 2026-10-08 diff --git a/api/index.api.md b/api/index.api.md index 04f1c7e76..a634b7094 100644 --- a/api/index.api.md +++ b/api/index.api.md @@ -3711,6 +3711,7 @@ export interface McpToolAnnotations { // @public export class McpToolError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; constructor(message: string); readonly toolErrorKind: "mcp"; } @@ -4769,6 +4770,7 @@ export interface PromptScheduleInput extends ScheduleBase { // @public export class PropagatingToolError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; } // @public @@ -7137,6 +7139,7 @@ export type WorkspaceEntryType = 'file' | 'directory' | 'symlink' | 'other'; // @public export class WorkspaceError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; constructor(message: string); } diff --git a/api/mcp.api.md b/api/mcp.api.md index 8e7076148..8966ef479 100644 --- a/api/mcp.api.md +++ b/api/mcp.api.md @@ -1128,6 +1128,7 @@ interface McpToolAnnotations { // @public export class McpToolError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; constructor(message: string); readonly toolErrorKind: "mcp"; } diff --git a/api/tools.api.md b/api/tools.api.md index 905421e24..dc2ff541b 100644 --- a/api/tools.api.md +++ b/api/tools.api.md @@ -1376,6 +1376,7 @@ interface McpToolAnnotations { // @public export class McpToolError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; constructor(message: string); readonly toolErrorKind: "mcp"; } @@ -2864,6 +2865,7 @@ export type WorkspaceEntryType = 'file' | 'directory' | 'symlink' | 'other'; // @public export class WorkspaceError extends Error { + static [Symbol.hasInstance](value: unknown): boolean; constructor(message: string); } diff --git a/bin/lousho.js b/bin/lousho.js index 4209c08dc..6f66944b5 100755 --- a/bin/lousho.js +++ b/bin/lousho.js @@ -18,7 +18,7 @@ const USAGE = [ ' Serve an agent over the Agent Client Protocol on stdio (for editors).', ' lousho add [--registry ] [--dir ] [--yes] [--allow ] [--overwrite] [--dry-run] | --list', ' Install a tool, skill, channel, schedule or kit from a registry into an agent directory.', - ' lousho build --target= [--out=] (or --agent=)', + ' lousho build --target= [--out=] (or --agent=)', ' Bundle an agent for deployment (default output: .lousho/build/).', ' lousho studio [--port N] [--host H] [--prod|--dev]', ' Open Agent Forge, the visual agent builder.', @@ -83,12 +83,17 @@ function loadDotEnv() { } // `lousho --version` / `-v` / `version` print the package version (kept in sync with VERSION by scripts/sync-version.mjs). -function runVersion() { +function runVersion(rest) { + if (rest.length > 0) return usageError(`'version' takes no arguments; got '${rest[0]}'.`); console.log(require(path.join(__dirname, '..', 'package.json')).version); } -function runHelp() { - console.log(USAGE); +// `lousho help ` is `lousho --help`. +function runHelp(rest) { + if (rest.length === 0) return console.log(USAGE); + const entry = COMMANDS.get(rest[0]); + if (!entry) return unknownCommand(rest[0]); + return runCommand(entry, ['--help']); } // `lousho --help` / `-h` / `help` print the usage and succeed (a bare `lousho` is still an error). @@ -104,11 +109,15 @@ const BUILTINS = new Map([ /** The exit code for a usage error, as every command returns it (src/cli/args.ts USAGE_EXIT_CODE). */ const USAGE_EXIT_CODE = 2; -function unknownCommand(command) { - console.error(`lousho: unknown command '${command ?? ''}'.\n${USAGE}`); +function usageError(message) { + console.error(`lousho: ${message}\n${USAGE}`); process.exitCode = USAGE_EXIT_CODE; } +function unknownCommand(command) { + usageError(`unknown command '${command ?? ''}'.`); +} + /** * Drops Node's MODULE_TYPELESS_PACKAGE_JSON warning: the CLI imports a * `tools/*.ts` file as an ES module even in a project whose package.json has @@ -135,7 +144,7 @@ async function runCommand(entry, rest) { async function main() { const [command, ...rest] = process.argv.slice(2); const builtin = BUILTINS.get(command); - if (builtin) return builtin(); + if (builtin) return builtin(rest); const entry = COMMANDS.get(command); return entry ? runCommand(entry, rest) : unknownCommand(command); } diff --git a/docs/cli.md b/docs/cli.md index e0f80afb8..0eb458d1a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -30,7 +30,7 @@ lousho dev [--port N] [--host H] [--all lousho chat [--model provider/model] [--session id] [--store sqlite:] [--traces[=dir]] lousho acp [--model provider/model] lousho add [--registry ] [--dir ] [--yes] [--allow ] [--overwrite] [--dry-run] (or --list) -lousho build --target= [--out=] (or --agent=) +lousho build --target= [--out=] (or --agent=) lousho studio [--port N] [--host H] [--prod|--dev] lousho mcp [--http --port N --host H] lousho doctor [agent.yaml|json|agent-dir] [--json] [--ping] @@ -41,8 +41,8 @@ lousho --version Every command parses its flags the same way (Node's `parseArgs`, strict): `--flag value` and `--flag=value` both work, the last of a repeated flag wins -(`--tag` accumulates), `--` ends the flags, and `-h` / `--help` prints the -command's usage and exits 0. An unknown flag, a flag without its value (it +(`--tag` accumulates), `--` ends the flags, and `-h` / `--help` (or `lousho help +`) prints the command's usage and flags and exits 0. An unknown flag, a flag without its value (it never takes the next flag as its value) or an extra argument fails with `LOUSHO_CONFIG_INVALID` and the usage line. A value that starts with `-` needs the `=` form: `--model=-x`. `--traces` is the one flag whose value is optional: @@ -147,8 +147,10 @@ without restarting the server or dropping the port: `require` specifiers, found once at startup; no bundler). Directory and module files are imported with a `?t=