diff --git a/acp.mdx b/acp.mdx index afd0a97..936234c 100644 --- a/acp.mdx +++ b/acp.mdx @@ -14,9 +14,9 @@ it, watch its tool calls and approve them from the editor's agent panel. ## The command ```bash -npx lousho acp agent.yaml # a spec file -npx lousho acp ./my-agent --model openai/gpt-4o # an agent directory, on another model -npx lousho acp src/agent.ts # a .ts/.js module (createAgent() options or an agent) +npx @linuxdevil/lousho acp agent.yaml # a spec file +npx @linuxdevil/lousho acp ./my-agent --model openai/gpt-4o # an agent directory, on another model +npx @linuxdevil/lousho acp src/agent.ts # a .ts/.js module (createAgent() options or an agent) ``` ```text @@ -122,7 +122,7 @@ It resolves once `input` ends, after aborting any running turn. ```ts import * as readline from 'node:readline'; -import { createAgent, serveAcp } from 'lousho'; +import { createAgent, serveAcp } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You are a coding assistant.' }); diff --git a/agent-directories.mdx b/agent-directories.mdx index 542975d..f21a211 100644 --- a/agent-directories.mdx +++ b/agent-directories.mdx @@ -14,7 +14,7 @@ code at any time without a rewrite. > implied; the files run with your process's full permissions. ```ts -import { loadAgentDir } from 'lousho'; +import { loadAgentDir } from '@linuxdevil/lousho'; const agent = await loadAgentDir('./my-agent'); const { text } = await agent.send('Hello!'); @@ -111,7 +111,7 @@ rule decides. ```ts // hooks.ts — the default export is a hook or a list of them -import type { AgentHook } from 'lousho'; +import type { AgentHook } from '@linuxdevil/lousho'; const audit: AgentHook = { name: 'audit', postToolCall(ctx) { @@ -123,7 +123,7 @@ export default audit; ```ts // approve.ts — the default export decides every `ask`ed call -import type { ApproveToolCall } from 'lousho'; +import type { ApproveToolCall } from '@linuxdevil/lousho'; const approve: ApproveToolCall = ({ args }) => !String(args.path ?? '').endsWith('.test.js'); export default approve; ``` @@ -148,7 +148,7 @@ that exports no tool at all is an error, as are two tools with the same name ```ts // tools/send_email.ts import { z } from 'zod'; -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; export default defineTool({ name: 'send_email', @@ -239,7 +239,7 @@ internal helper of the generated bundle); with your own server, use ```ts import { createServer } from 'node:http'; -import { createAgent, mountChannels, resolveAgentDir } from 'lousho'; +import { createAgent, mountChannels, resolveAgentDir } from '@linuxdevil/lousho'; // channels/support.ts: export default webhookChannel({ secret: process.env.HOOK_SECRET ?? '' }) const { config, channels } = await resolveAgentDir('./my-agent'); @@ -262,7 +262,7 @@ naming the file. A `memory` override passed to `loadAgentDir(dir, { overrides }) is merged with the directory's slots by name: the override wins a name clash. ```ts -import { defineMemory, fileMemory, resolveAgentDir } from 'lousho'; +import { defineMemory, fileMemory, resolveAgentDir } from '@linuxdevil/lousho'; // memory/notes.ts: export default { scope: 'global', provider: fileMemory({ dir: './.lousho/memory' }) } const { manifest } = await resolveAgentDir('./my-agent', { @@ -276,7 +276,7 @@ A relative `dir` in a `memory/*.ts` file resolves against the process working di ```ts // memory/notes.ts import { fileURLToPath } from 'node:url'; -import { fileMemory } from 'lousho'; +import { fileMemory } from '@linuxdevil/lousho'; export default { scope: 'global', @@ -300,7 +300,7 @@ The path follows the module: a copied or bundled build keeps its memory next to handy for tests and tooling: ```ts -import { resolveAgentDir, createAgent } from 'lousho'; +import { resolveAgentDir, createAgent } from '@linuxdevil/lousho'; const { config, manifest } = await resolveAgentDir('./my-agent'); console.log(manifest.tools, manifest.skills, manifest.subagents); @@ -320,8 +320,8 @@ declares (`guardrails`, `onEvent`, `retry`, `reasoning`, `redactContent`, `output`, ...). Use it to swap the model in tests: ```ts -import { loadAgentDir } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { loadAgentDir } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = await loadAgentDir('./my-agent', { provider: mockModel(['Hi there.']) }); ``` @@ -352,7 +352,7 @@ says so. Compile the directory first, or use `.js`/`.mjs` tools with an ## Run it with `lousho dev` ```bash -npx lousho dev ./my-agent +npx @linuxdevil/lousho dev ./my-agent ``` Serves the chat UI and `POST /chat` for the directory and reloads it when @@ -371,7 +371,7 @@ dev](/schedules#in-lousho-dev)). ## Deploy it with `lousho build` ```bash -npx lousho build ./my-agent --target=node-server # or docker +npx @linuxdevil/lousho build ./my-agent --target=node-server # or docker ``` The agent directory is the unit of deployment: the built server loads it with diff --git a/agent-forge.mdx b/agent-forge.mdx index 5f60db7..e85d14e 100644 --- a/agent-forge.mdx +++ b/agent-forge.mdx @@ -1,77 +1,224 @@ --- -title: "Agent Forge" +title: "Lousho Studio" +description: "Lousho Studio is a local web app for building, testing and watching every agent across your projects." --- -{/* Generated from docs/agent-forge.md in LinuxDevil/lousho by scripts/sync-sdk-docs.mjs. Do not edit here. */} +{/* Generated from docs/agent-forge.md in LinuxDevil/agent-sdk by scripts/sync-sdk-docs.mjs. Do not edit here. */} -Agent Forge (`apps/agent-forge`, also called the Studio) is this SDK's -companion dashboard: one app with a left navigation to build and run agents, -try prompts, and read what your agents did from the observe daemon. Build an -agent's graph on a canvas, run it, chat with it and author sandboxed -pre/post hooks (all reading and writing the same `AgentSpec` YAML that -`lousho dev` and `lousho build` use), try prompts in a playground, and read -the [observe daemon](/observability#the-observe-daemon)'s traces, -threads, fleet monitoring, eval scores, datasets, annotation queues, prompt -registry and context hub. It's launched with one command, -`lousho studio`, and ships as part of this SDK's npm package. +It is a separate npm package, `@linuxdevil/studio` — the SDK (`@linuxdevil/lousho`) +does not contain it. One Studio server shows every agent you have: the spec-v2 +graph agents saved in your project folders, code agents Studio finds by scanning +a workspace, and agents running anywhere that report to the observe collector. -This doc covers: installation, the `lousho studio` quickstart, the -[navigation and screens](#navigation), a first-agent walkthrough, hook -authoring, time travel (replaying a run from a past step), the playground -endpoint, and how settings/secrets/deploy wiring works. +This page covers installing and starting Studio, workspaces, which SDK runs a +workspace's agents (and what each banner means), the collector, and how agents +deployed on other machines show up here. [Using Studio](#using-studio) covers +the builder, the playground, traces and the rest of the app; the builder, +Author and lifecycle screens have their own pages: [the visual +builder](/forge-builder), [the Agent +Author](/forge-author) and [versions, triggers and +publishing](/forge-lifecycle). -![The Agents screen: nav, agent rail, the fitted canvas with the Inspector folded, and the bottom drawer](/images/studio-agents.png) +## Install and start -## Installation +Any of these works: -Agent Forge ships inside `lousho` itself - there's no -separate package to install: +```bash +npx @linuxdevil/studio +``` ```bash -npm install lousho +npm i -g @linuxdevil/studio +lousho-studio ``` -That's it. `lousho studio` (below) runs a pre-built copy of the app; you -don't need `apps/agent-forge`'s own source or its devDependencies (Vite, -`tsx`, etc.) to use it. +Or, inside a project that already has `@linuxdevil/lousho` installed, the SDK's +launcher finds the installed Studio and opens this folder as a workspace: -If you're working inside this SDK's own monorepo instead (contributing to -Agent Forge itself), see [Dev mode](#dev-mode) below. +```bash +lousho studio +``` -## Quickstart +When no Studio is installed the launcher says so instead of bundling one: -```bash -npx lousho studio ``` +Studio is a separate install: npx @linuxdevil/studio +``` + +`lousho studio` accepts `--port` and `--host` and passes them through; run +`lousho studio --help` for its options. `--prod` and `--dev` only apply inside +the SDK's own repository checkout (see [Dev mode](#dev-mode)). -This starts one local server and prints its URL (default -`http://127.0.0.1:4750/?token=`). Open that exact URL in a browser. -It lands on the Agents screen (`#/agents`): the left navigation, the build -toolbar (the agent's name and run status, the model badge, then Debug, Stop, -Run, Import, Export and Save), the -agent rail (your saved agents plus a node/hook palette), the canvas, the -Inspector (right), and a bottom drawer with Chat, Logs, Trace, Output, -History and Settings tabs. An agent opens with nothing selected and the -whole graph fitted in view; the Inspector stays folded away until you -select a node, or press the **Inspector** button at the canvas's -top-right (its accessible name is "Show inspector"), which is remembered. The other screens are one click away in the -navigation; see [Navigation](#navigation). +`lousho-studio` (and `npx @linuxdevil/studio`) take the same flags: + +| Flag | Default | Meaning | +| --- | --- | --- | +| `--workspace ` | the current folder | A project folder to open; it is added to your workspaces. | +| `--port N` | `4750` | The port to listen on. | +| `--host H` | `127.0.0.1` | The interface to bind to. | + +The server prints its URL, `http://127.0.0.1:4750/?token=`. The +`?token=` is a per-launch access token: the page keeps it for that tab and +sends it on every API call, so open the printed URL, not the bare host (see +[Access token](#access-token)). + +Opening Studio lands on the Agents list (`#/agents`): your saved agents as +cards, plus a **New agent** menu. Opening or creating one takes you to its +Canvas tab — the builder: the build toolbar (the agent's name and run +status, the model badge, then Debug, Stop, Run, Import, Export and Save), +the agent rail (your saved agents plus a node/hook palette), the canvas, the +Inspector (right), and a bottom drawer with Test, Problems, Logs, Trace, +Output, History and Settings tabs. An agent opens with nothing selected and +the whole graph fitted in view; the Inspector stays folded away until you +select a node, or press the **Inspector** button at the canvas's top-right +(its accessible name is "Show inspector"), which is remembered. The other +screens are one click away in the navigation; see [Navigation](#navigation). Below 860px wide the navigation becomes a drawer opened from the **Navigation** (menu) button at the top; picking a screen, the scrim or Esc -closes it. On the Agents screen the agent rail and the Inspector become +closes it. On the builder the agent rail and the Inspector become slide-over panels you open from the top bar; Run (or Stop) and Save stay in the bar, and Debug, Import, Export and the model badge move into its **More actions** (`⋯`) menu. -Useful flags: +## Workspaces + +A workspace is a project folder on disk. The folder you launch Studio from is +added automatically; add more with **Add workspace → folder path** (or the +**Add workspace…** entry in the switcher). + +The switcher at the top of the nav lists **All workspaces**, each workspace, +and **Add workspace…**. `#/agents` is the all-workspaces list; an agent route +is `#/w//agents//`. + +Things live in three places: + +| Where | What | +| --- | --- | +| `~/.lousho/studio/` | Studio-wide state: the workspace registry, settings, secrets, assistant sessions, pass spend, inbox alerts. Shared across workspaces. | +| `/.lousho/agents/` | The project's agents: spec, metadata, layout, working copy, versions, drafts, suggestions, traces, chats, approvals, checkpoints. Travels with the project. | +| `~/.lousho/observe.db` | The observe collector's database (traces, scores, sessions). | + +On first launch after the split, if a workspace still has a project-level +`.lousho/settings.json` or `.lousho/secrets.json`, Studio offers **Import +settings and keys from <workspace>** once, copies them into +`~/.lousho/studio/`, and leaves the originals alone. + +## Three kinds of agent + +The all-workspaces agent list mixes three kinds: + +| Kind | Source | What you can do | +| --- | --- | --- | +| Workspace agent | `/.lousho/agents/.yaml` (Agent spec v2) | Everything: canvas, Author, versions, test, publish. | +| Code agent | An agent directory or `agent.ts` found by scanning a workspace | Read-only canvas, traces, runs. | +| Observed agent | Any agent reporting to the observe collector (`GET /api/v1/agents`): deployed workers, CI, other machines | Read-only card: name, project, instances, last seen, traces, scores, evals. | + +Observed agents whose collector `project` matches a workspace id appear under +that workspace (see [See agents running elsewhere](#see-agents-running-elsewhere)). +The rest are grouped under **Elsewhere**. + +## Which SDK runs your agents + +For each workspace Studio picks how agent processes run: + +- **Bridge** — the normal case. Studio spawns `lousho bridge` from the SDK + installed in that project (`@linuxdevil/lousho`), so runs use the project's + own SDK version. This matters because a project's `{ file }` tools and + skills import that project's dependencies, which only resolve inside the + project. +- **In-process** — Studio's own built-in SDK, used when the project has no + SDK installed or its checkout is not built. +- **Read-only** — the project's SDK is too old or speaks a protocol this + Studio does not know, so edits are refused until it is fixed. + +Each non-default state shows a banner at the top of the workspace's pages: + +| Banner | What to do | +| --- | --- | +| `This project has no @linuxdevil/lousho installed. Runs use Studio's built-in SDK .` | Runs still work on Studio's SDK. Install `@linuxdevil/lousho` in the project to run its own version. | +| `This SDK checkout is not built, so runs use Studio's built-in SDK . Run npm run build to use it.` | Inside the SDK repository: run `npm run build` in the checkout. | +| `Update @linuxdevil/lousho in this project to or later to edit agents here.` | The project's SDK is older than this Studio understands. The workspace is read-only: update `@linuxdevil/lousho` in the project to edit. | +| `This project's @linuxdevil/lousho is newer than this Studio understands. Update Studio to edit agents here.` | The project has a newer SDK than this Studio was built for. Update Studio (`npm i -g @linuxdevil/studio@latest`) to edit. | +| `This project's agent process keeps stopping. Check the Studio terminal for its output, then restart it.` | The bridge process crashed repeatedly. Its output is in the terminal that started Studio; use **Restart** on the banner to try again. | + +While a workspace is read-only, writes under its agents (saves, drafts, runs, +deploys) are refused; reads still work. + +A few features still need Studio's built-in SDK and are unavailable on +bridge workspaces: + +- Flow runs: `Flow runs need Studio's built-in SDK for now. Remove the project's SDK from this workspace or run the flow from code.` +- Breakpoints: `Breakpoints need Studio's built-in SDK for now.` +- Time travel: `Time travel is not available yet for projects that run their own SDK.` + +## The collector + +Everything under Observe, Evaluate and Assets — and run reporting — reads the +observe collector. On boot Studio probes the configured URL (default +`http://127.0.0.1:7777`): + +- **Attached** — a `lousho observe` daemon is already running there; Studio + uses it. +- **Started** — none answered on loopback, so Studio started its own and + stops it on exit. It never stops a daemon it did not start. +- **Remote** — a non-loopback URL (team or hosted collector); Studio probes + it but does not start or stop anything there. +- **Down** — nothing answers; daemon screens show the start command instead + of data and recover on their own. + +The collector card at the bottom of the nav shows the mode (`Attached to +`, `Started by Studio on `, `Remote collector at `, `Not +reachable: `). **Settings → Collector** sets a remote URL and bearer +token; create a token with `lousho observe token create --project `. +`LOUSHO_OBSERVE_URL` and `LOUSHO_OBSERVE_TOKEN` in Studio's own environment +override the stored setting, and `LOUSHO_STUDIO_COLLECTOR=0` stops Studio +from auto-starting a local daemon. + +## See agents running elsewhere + +An agent deployed on another machine — a server, CI, a teammate's laptop — +shows up in Studio once it reports to a collector Studio can read. Reporting +is pure SDK configuration; nothing in the agent is Studio-specific: + +```ts +import { createAgent } from '@linuxdevil/lousho'; + +// LOUSHO_OBSERVE, LOUSHO_OBSERVE_TOKEN and LOUSHO_OBSERVE_PROJECT are read +// from the environment - no Studio-specific code. +export const agent = createAgent({ model: 'openai/gpt-4.1-mini' }); +``` + +Set these in the deployed environment: + +| Variable | Meaning | +| --- | --- | +| `LOUSHO_OBSERVE` | The collector URL, e.g. `http://127.0.0.1:7777` or a tunnel/remote URL. `1`/`true` uses the default. | +| `LOUSHO_OBSERVE_TOKEN` | Bearer token, when the collector requires one (`lousho observe token create`). | +| `LOUSHO_OBSERVE_PROJECT` | The workspace id to group the agent under — shown in **Settings → Workspaces**. Without it the project name is the package name and the agent appears under **Elsewhere**. | + +Point Studio at the same collector under **Settings → Collector** (or with +`LOUSHO_OBSERVE_URL`/`LOUSHO_OBSERVE_TOKEN` in Studio's environment), and the +deployed agent's traces, scores and sessions appear in Studio's Observe +screens. + +## Updating + +Studio (`@linuxdevil/studio`) and the SDK (`@linuxdevil/lousho`) are separate +packages and update independently: ```bash -npx lousho studio --port 5000 # pick a different port -npx lousho studio --host 0.0.0.0 # bind to all interfaces -npx lousho studio --prod # force production mode (see below) -npx lousho studio --dev # force dev mode (monorepo only) +npm i -g @linuxdevil/studio@latest # Studio itself +npm i @linuxdevil/lousho@latest # inside each project, its SDK ``` +A workspace's agents edit only when the project's SDK is at least +`MIN_BRIDGE_SDK_VERSION` — the SDK version this Studio was built against. Older +SDKs get the read-only banner above; newer ones get the "update Studio" banner. +Bridge workspaces also need `lousho bridge` in the project's SDK, which speaks +bridge protocol `1`. + +## Using Studio + ### Access token The studio can store provider keys, start runs and execute hook code, so its @@ -103,40 +250,38 @@ without the CLI: ### Where agents live -Agent specs and run state are stored under `.lousho/` in the directory you -ran `lousho studio` from (agent YAML files under `.lousho/agents/`, -checkpoints and approvals alongside them) - the same `.lousho/` layout -`lousho dev`/`lousho build` use. Those YAML files are -[Agent spec v2](/agent-spec) documents, which that page explains field -by field. - -The Agents screen's agent rail lists exactly the agents in that `.lousho/agents/`: a YAML -file you add there shows up, and an agent you create in the studio is -written there at once (autosave and **Save** both write the file), so it -can be chatted with straight away. Another project, opened in its own -`lousho studio`, shows only its own agents. The browser keeps nothing but a -draft of an edit that could not reach the server (it is pushed on the next -load), stored per workspace directory. A file in `.lousho/agents/` that no -longer parses is skipped with a warning in the server log. +Each workspace's agent specs and run state live under `.lousho/` in that +project folder (agent YAML files under `.lousho/agents/`, checkpoints and +approvals alongside them) - the same `.lousho/` layout `lousho dev`/`lousho +build` use, so the files travel with the project and stay diffable. +Studio-wide state (settings, secrets, the workspace registry) lives in +`~/.lousho/studio/` instead; see [Workspaces](#workspaces). Those YAML files +are [Agent spec v2](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/agent-spec.md) documents, which that page explains +field by field. + +The Agents screen's agent rail lists exactly the agents in that workspace's +`.lousho/agents/`: a YAML file you add there shows up, and an agent you +create in the studio is written there at once (autosave and **Save** both +write the file), so it can be chatted with straight away. Another workspace +shows only its own agents. The browser keeps nothing but a draft of an edit +that could not reach the server (it is pushed on the next load), stored per +workspace directory. A file in `.lousho/agents/` that no longer parses is +skipped with a warning in the server log. ### Production vs. dev mode -`lousho studio` has two modes, and picks the right one automatically: - -- **Production** (the default once Agent Forge has been built): a single - Express server serves both the REST/WebSocket API *and* the pre-built - client (React app) as static files, on one port. This is what runs when - you `npm install` the published package - there's no separate Vite dev - server and no TypeScript loader involved. -- **Dev** (falls back to this if the build hasn't been run yet, i.e. inside - a source checkout of this monorepo): the API server runs straight off - its TypeScript source via `tsx`, alongside a real Vite dev server (with - hot-module reload) that proxies API requests to it. Two processes, two - ports internally, one command. +An installed Studio (`lousho-studio`, `npx @linuxdevil/studio`, or +`lousho studio` finding it) is always production mode: a single Express +server serves both the REST/WebSocket API *and* the pre-built client (React +app) as static files, on one port. There is no separate Vite dev server and +no TypeScript loader involved. -`--prod`/`--dev` force one or the other; run without either flag and -`lousho studio` auto-detects based on whether `apps/agent-forge/dist-server` -exists. +Dev mode - the API server running from TypeScript via `tsx` next to a Vite +dev server with hot-module reload - exists only inside the SDK's own +repository checkout, for people contributing to Studio itself. There +`lousho studio --dev` (or `--prod` to force the built bundle) picks the mode; +run without either flag it auto-detects based on whether +`apps/agent-forge/dist-server` exists. See [Dev mode](#dev-mode). ## Navigation @@ -144,8 +289,11 @@ The left navigation lists every screen, grouped: | Group | Item | Screen | | --- | --- | --- | -| Build | **Agents** | The builder: canvas, Inspector, bottom drawer. The build toolbar (Run, Stop, Debug, Import, Export, Save, model badge) shows only here. | +| Build | **Agents** | The agents list; an agent opens on tabs — Canvas (the builder), Overview, Configure, Code, Runs, Versions, Suggestions. The build toolbar (Run, Stop, Debug, Import, Export, Save, model badge) shows on the Canvas tab. | +| Build | **Inbox** | Pending approvals, questions, drafts, suggestions and notices, one row each with inline actions. | +| Build | **Templates** | Agent templates you can start from. | | Build | **Playground** | Try messages against a model without editing an agent. | +| Build | **Workspaces** | Workspaces in this project: their pages, problems, drafts and revisions. | | Observe | **Traces** | Every run the observe daemon holds, filtered and sorted; a row opens the trace. | | Observe | **Threads** | Sessions (conversations): one row per session, one turn per run. | | Observe | **Monitoring** | The fleet's health and charts over a time window. | @@ -154,7 +302,7 @@ The left navigation lists every screen, grouped: | Assets | **Context Hub** | Shared key-values agents publish to the daemon. | | | **Settings** | Appearance, provider keys, settings profiles, deploy, collector and shortcuts. | -Off the Agents screen a page header names the screen and keeps the live run +Off the builder a page header names the screen and keeps the live run status pill at the end of its title row, after any chips the screen adds (a prompt's labels, say). Under Settings, a small card shows the collector status (see [The collector](#the-collector-observe-daemon)). The button beside the @@ -175,7 +323,9 @@ unknown hash goes to `#/agents` (the `?token=` stays). | Route | Screen | Query keys | | --- | --- | --- | -| `#/agents`, `#/agents/` | Agents, with that agent open | | +| `#/agents` | Agents list | | +| `#/agents//canvas` | The builder for that agent | | +| `#/agents//` | One agent tab: `overview`, `configure`, `code`, `runs`, `versions`, `suggestions` | | | `#/playground` | Playground | `trace` + `fromObs`, or `prompt` (see [Playground](#playground)) | | `#/traces` | Traces list | `agent`, `status`, `model`, `project`, `q`, `prompt` (`` or `@`), `from` + `to` (ms epoch, half-open), `sort` (`duration`, `tokens`, `cost`) + `order` (`asc`, `desc`), `offset` | | `#/traces/` | One trace | `obs` (the selected step) | @@ -189,6 +339,8 @@ unknown hash goes to `#/agents` (the `?token=` stays). | `#/prompts` | Prompt registry | | | `#/prompts/`, `#/prompts/@` | One prompt (latest version by default) | `diff=,` | | `#/hub`, `#/hub/` | Context Hub | `agent` (the "Viewing as" agent), `scope` (`private`, `fleet`) | +| `#/workspaces` | Workspaces list | `q` (filters name, id, domain) | +| `#/workspaces/`, `#/workspaces//
` | One workspace; section is `overview`, `main-agent`, `pages`, `agents`, `runs`, `improve` or `revisions` | | | `#/settings` | Settings | | A time range picked from a preset other than the default is written as @@ -211,6 +363,9 @@ browser); type to filter. Paste a trace id (8 or more hex digits and dashes) and the first entry is **Open trace** for it. Arrow keys move, Enter runs. +The builder's own shortcuts — save version, undo/redo, palette search, +node delete — are listed in [the visual builder](/forge-builder#keyboard-shortcuts). + ![The command palette over the Traces screen](/images/studio-command-palette.png) In an annotation queue's review, Enter submits the scores and `S` skips @@ -231,40 +386,43 @@ Every status in the Studio is the same pill: ### The collector (observe daemon) -Everything under Observe, Evaluate and Assets reads the observe daemon -(`lousho observe start`) through the Studio server's `/observe` proxy. The -proxy reads only `LOUSHO_OBSERVE_URL` (default `http://127.0.0.1:7777`); -`LOUSHO_OBSERVE` does not change where the screens read (it only picks where -builder runs report, see the [walkthrough](#first-agent-walkthrough)). When -`LOUSHO_OBSERVE_TOKEN` is set, the proxy sends it to the daemon as the bearer -token on every read and write (as run reporting and the judge evaluators do), -so the screens also work against a daemon started with `--require-token`. -Without it, such a daemon answers `401` to every `/api/` read and the screens -show that error instead of data (`/health` still answers, so the collector -card says running). The browser's own credentials (the Studio token, cookies, -`Authorization`) are never passed on. The proxy only forwards reads plus the -writes the screens make (feedback scores, new prompt versions from the -Playground, prompt label moves, hub entry edits, dataset and example writes, -annotation queue writes, evaluator rule writes); every other write, ingest -above all, is refused with `405`. It always calls the daemon at -`LOUSHO_OBSERVE_URL`, whatever host the request line names. - -The collector card at the bottom of the nav says whether the daemon answers: -**Checking collector…** until the first probe, then **Collector running** -or **Collector unreachable**, with the daemon URL underneath. The probe keeps polling. While the daemon is down, each -daemon screen shows the command to start it instead of an error, keeps its -URL, and swaps the data back in by itself once the daemon answers. The -Agents screen and Playground runs do not need the daemon. +Everything under Observe, Evaluate and Assets reads the observe collector +through the Studio server's `/observe` proxy, which calls whatever URL the +collector manager resolved (see [The collector](#the-collector)). The proxy +forwards reads plus the writes the screens make (feedback scores, new prompt +versions from the Playground, prompt label moves, hub entry edits, dataset +and example writes, annotation queue writes, evaluator rule writes); every +other write, ingest above all, is refused with `405`. When a bearer token is +configured the proxy sends it to the collector on every read and write (as +run reporting and the judge evaluators do), so the screens also work against +a collector started with `--require-token`. Without it, such a collector +answers `401` to every `/api/` read and the screens show that error instead +of data (`/health` still answers, so the collector card says running). The +browser's own credentials (the Studio token, cookies, `Authorization`) are +never passed on. + +The collector card at the bottom of the nav shows the resolved mode — +**Attached to \<url\>**, **Started by Studio on \<url\>**, **Remote collector +at \<url\>** or **Not reachable: \<error\>** — and keeps polling. While the +collector is down, each daemon screen shows the command to start it instead +of an error, keeps its URL, and swaps the data back in by itself once it +answers. The Agents screen and Playground runs do not need the collector. ## Screens ### Agents -The builder: see the [First-agent walkthrough](#first-agent-walkthrough). -`#/agents/` opens that agent; leaving the screen (or switching agents) -with unsaved changes asks first. Runs started here report to the observe -daemon, and the Run status pill's **Open trace** link opens the run on the -Trace screen. +`#/agents` is the agents list: a card per agent in `.lousho/agents/`, a +**New agent** menu (**Blank agent**, **From template**, +**Describe with AI** — see [the Agent Author](/forge-author) — or +**Import from file…**), and search and label filters. +Opening a card goes to its Canvas tab, the builder — see +[the visual builder](/forge-builder) and the +[first-agent walkthrough](#first-agent-walkthrough). `#/agents/` (the +legacy builder URL) redirects to `#/agents//canvas`. Leaving the +builder with unsaved changes asks first. Runs started there report to the +observe daemon, and the Run status pill's **Open trace** link opens the +run on the Trace screen. ### Playground @@ -301,6 +459,24 @@ says so and keeps an empty editor, and runs still work. Runs go through the [playground run endpoint](#playground-run-endpoint) and report as agent `playground`. +### Workspaces + +`#/workspaces` lists the workspaces the typed workspaces API knows: name and +domain, page count, a `Valid`/`Invalid (n)` badge from the workspace's +problems, the live revision and a last-run column (filled in by a later +ticket). `#/workspaces/` opens one workspace with a header that carries +the live revision, the problems badge, an **Draft open** badge while a draft +exists, and disabled **Run**/**Chat** actions; a left nav switches between +the `overview`, `main-agent`, `pages`, `agents`, `runs`, `improve` and +`revisions` sections, the ones not built yet naming their ticket. Every edit +is a typed authoring operation sent through `ApiClient.call('applyOps', …)` +into a draft with `expectedRevision`; the draft is applied or discarded +later. When the live revision moved under an open screen the operation gets a +409, the latest revision reloads and a toast says "This workspace changed +since you opened it." The screens call only the workspaces API +(`/api/workspaces`, mounted by a later ticket), through an injected +`ApiClient`, so they run on fixtures today and move to the Next.js app later. + ### Traces ![The Traces list](/images/studio-traces.png) @@ -443,7 +619,7 @@ filter (agent, status `ok` or `error`, name contains), and the kind: the provider's environment variable where the Studio runs (the stored key wins); the provider list offers the providers that have either, plus `ollama` and `mock`. With no key at all the judge option is disabled, with - a link to Settings. Judges run only while `lousho studio` is running; until + a link to Settings. Judges run only while Studio is running; until then their jobs wait in the daemon (see [Judge rules](/observability#judge-rules-through-agent-forge)). Set `LOUSHO_STUDIO_JUDGE=0` on the Studio server to turn its judge loop off. @@ -513,27 +689,28 @@ and deploy cards; see [Settings, secrets and deploy wiring](#settings-secrets-an ## First-agent walkthrough -1. **Create an agent.** In the agent rail's **Agents** tab, click - **New agent**, give it a name, and pick a starting template - **Blank graph** (a single - LLM node using the built-in `mock` provider) is the fastest way to try - things out without any API keys. Agent Forge creates the agent under that - name (`.lousho/agents/.yaml`, with `name: ` in the spec) and +1. **Create an agent.** On the agents list (`#/agents`) click **New agent** + and pick **Blank agent** - it starts from an empty canvas, which is the + fastest way to try things out without any API keys (**From template** + offers ready-made starting points instead). Agent Forge creates the agent + (`.lousho/agents/.yaml`, with `name:` in the spec) and opens it on the canvas. The open agent's card has **Rename** and **Delete**. Rename moves the spec to the new name; earlier chats, traces and checkpoints stay under the old one. -2. **Look at the graph.** A blank agent is one `llm` node. Drag more nodes - in from the rail's **Nodes** tab (**Trigger**, **LLM step**, **Tool - call**, **Approval gate**, **Response / output**), or click (or press +2. **Look at the graph.** A blank agent is a single agent node. Drag more + nodes in from the rail's **Nodes** tab (a **Tool**, an **MCP server**, + a **Guardrail**, a **Chat** trigger, an **Output schema** — the full + list is in [the visual builder](/forge-builder#the-canvas)), or + click (or press Enter on) a Nodes item to add it at the centre of the canvas, and connect them by - dragging between their handles. Each node card shows its name, an LLM - node's `provider/model` chip and tool count, and the node's status in the + dragging between their handles. Each node card shows its name, an agent + node's `provider/model` chip, and the node's status in the current run (`idle` until a run reaches it). Click a node to edit its - settings (prompt, provider/model, tools, breakpoints) in the Inspector on - the right; its **Node**, **Debug** and **Hooks** sections collapse, and - each stays the way you left it. An agent whose canvas has no nodes shows - the templates on the canvas: choosing one fills the canvas (unsaved until - it autosaves, like any edit) and keeps the agent's name. + settings in the Inspector on + the right; its **Problems**, **Settings** and **Debug** sections + collapse, and + each stays the way you left it. 3. **Run it.** Click **Run** in the top bar. With the `mock` provider there's nothing to configure - it returns deterministic canned output, which is exactly the point for trying the rest of the UI without needing @@ -542,12 +719,12 @@ and deploy cards; see [Settings, secrets and deploy wiring](#settings-secrets-an `awaiting approval`), and once the run's trace id is known an **Open trace** link next to it opens the run at `#/traces/` (in the narrow layout it is in the **More** menu). Builder runs report to the - observe daemon as agent ``, the same way the Playground does: - when `LOUSHO_OBSERVE` is set its reporter is used (nothing is reported - twice), a daemon that is down only drops events and never fails the run, - and message content is sent only to a loopback daemon (never to the + observe collector as agent ``, the same way the Playground + does (see [The collector](#the-collector)): a collector that is down + only drops events and never fails the run, and message content is sent + only to a loopback collector (never to the trace files or the live Trace tab). Run sends a fixed - prompt, which the Chat tab shows + prompt, which the Test tab's chat shows as a **Run input** bubble instead of a message from you. If the studio server goes away, a banner says the connection was lost and the studio reconnects when it is back. @@ -576,9 +753,9 @@ and deploy cards; see [Settings, secrets and deploy wiring](#settings-secrets-an live. Selecting a step highlights its node on the canvas. The list refreshes when a run ends. The files are the ones `lousho traces` reads, in `.lousho/agents//traces//.jsonl` under the - directory `lousho studio` was started in (see + workspace folder (see [Local traces](/observability#local-traces)): - `npx lousho traces --dir .lousho/agents//traces` shows the same + `npx @linuxdevil/lousho traces --dir .lousho/agents//traces` shows the same runs in the terminal, and deleting the agent's folder deletes its traces. The tab shows what the spans hold: model and tool names, token counts, cost, durations and error messages, and in the inspector's Input, @@ -586,52 +763,53 @@ and deploy cards; see [Settings, secrets and deploy wiring](#settings-secrets-an tool content. The server reads traces only from that folder (an id with a path separator is a 400) and never sends the file path to the browser. Keep `.lousho/` out of version control. -5. **Chat with it.** The **Chat** tab is a separate conversational +5. **Chat with it.** The chat in the bottom drawer's **Test** tab is a + separate conversational transport (`POST /agents/:id/message`, streamed back over the same WebSocket as everything else) - send it a message and it replies in the thread, independent of the graph "Run" button above. Each agent keeps its own chat history (`+ New chat` starts a fresh session; old ones stay browsable). -6. **Approve a paused tool call.** Add a **Tool call** node, open it in the +6. **Approve a paused tool call.** Add a **Tool** node, open it in the Inspector, and pick a tool - some tools (or an agent's own policy) can require human approval before running. When a run or chat hits one, an inline approval card appears (in the top bar for a graph run, or as a - message bubble in the Chat thread) showing the tool name and its + message bubble in the chat thread) showing the tool name and its arguments. Click **Approve** to let it proceed or **Reject** to abort that call; the run resumes automatically either way. ## Hooks Hooks are small, sandboxed functions that run immediately before or -after a tool call or an LLM `generate` - the same mechanism the SDK core +after a tool call or a model call - the same mechanism the SDK core exposes as `HookRegistry`/`AgentHook` (see -[API Overview](/api-overview)), but authored visually and attached to a -specific canvas node. - -To attach one: - -1. Select an `llm` or `tool` node on the canvas (hooks only apply to these - - they're the two points `AgentExecutor` actually calls out to). -2. Open the Inspector's **Hooks** section. Drag a **Pre-hook** or - **Post-hook** entry from the rail's **Nodes** tab onto the node (or drop - it directly in the Hooks section), or click the palette entry while the - node is selected, to attach a new one; you'll get a - choice of starter templates (`redact-pii`, `rate-limit`, `audit-log`, - `inject-context`) as an editable starting point. -3. Click the hook's chip to select it, then edit its code in the CodeMirror - editor. The code is the **body** of an async function invoked as +[API Overview](/api-overview)), authored visually as `hooks[]` entries +on the agent. + +To add one: + +1. Drag a **Hook** entry from the rail's **Nodes** tab (Safety group) onto + the canvas, or click it while the agent is open; it attaches to the + agent as a node. Each spec `hooks` entry is one node. +2. Click the hook node and edit its fields in the Inspector: an id, a + name, the **Source** (**Code here** for an inline body, or **File** for + a `.ts`/`.js` file inside the agent folder), the **Runs** point - + *Before/After each tool call* or *Before/After each model call* - the + on switch, and the code itself in a CodeMirror editor, with starter + templates (`redact-pii`, `rate-limit`, `audit-log`, `inject-context`) + filtered to the current point. +3. The code is the **body** of an async function invoked as `hook(ctx)` inside a sandboxed subprocess (`server/hookSandbox.ts`) - never in the browser or the API server process itself: - - For a tool-call hook, `ctx` is `{ toolName, args, result?, error? }`. + - For a tool-call point, `ctx` is `{ toolName, args, result?, error? }`. A pre-hook can mutate `ctx.args`; a post-hook can inspect/mutate `ctx.result`/`ctx.error`. Return `ctx`. - - For an LLM-generate hook, `ctx` is `{ messages, model }`. Mutate/push + - For a model-call point, `ctx` is `{ messages, model }`. Mutate/push onto `ctx.messages` and return `ctx`. - **Throwing aborts the step** - a rate-limit hook, for example, throws to block the tool call outright rather than letting it run. -4. Toggle the switch on a hook's chip to enable/disable it without removing - it, or use its **x** to remove it entirely. Only enabled hooks are - compiled into the run. +4. Switch a hook off without removing it, or delete its node; only enabled + hooks are compiled into the run. Example: a redact-PII pre-hook on a tool node (this is the `redact-pii` starter template) - @@ -646,7 +824,7 @@ for (const key of Object.keys(ctx.args || {})) { return ctx; ``` -Saving the agent persists these as `spec.policy.hooks` in the agent's YAML; +Saving the agent persists these as `hooks` entries in the agent's YAML; the server compiles enabled hooks into a real `HookRegistry` for the run (`server/compileHooks.ts`), routed through the same `SandboxAdapter` a sandboxed tool uses. @@ -665,7 +843,7 @@ and replay it next to the original, with ### The History tab -1. Run the agent (Topbar **Run**, or a Chat message). +1. Run the agent (Topbar **Run**, or a chat message). 2. Open the bottom drawer's **History** tab. It lists the run's steps: step number, status, the model's finish reason, the tool calls made, and the tokens and cost of the step's model call when known. @@ -760,36 +938,36 @@ Every stream ends with exactly one `done` or `error`, even a run that stops without a result. `usage` has the `run.done` event's usage shape (`AgentEventUsage`). -Closing the connection aborts the run. Every run reports to the observe -daemon the Studio reads (`LOUSHO_OBSERVE_URL`, default -`http://127.0.0.1:7777`, or `LOUSHO_OBSERVE` when set) as agent +Closing the connection aborts the run. Every run reports to the collector +the Studio reads (see [The collector](#the-collector)) as agent `playground`, with `lousho.prompt.ref` on the run's root span when -`promptRef` is given. `LOUSHO_OBSERVE_TOKEN` is sent as the bearer token, for a -daemon bound beyond loopback. A daemon that is down does not slow or fail the run. +`promptRef` is given. A bearer token is sent when one is configured, for a +collector bound beyond loopback. A collector that is down does not slow or +fail the run. ## Settings, secrets and deploy wiring The Settings screen (`#/settings`, the last screen in the nav) and the builder drawer's **Settings** tab share three parts: provider API keys -(OpenAI and Anthropic, stored encrypted under `.lousho/` and never shown +(OpenAI and Anthropic, stored encrypted under `~/.lousho/studio/` and never shown again; each key shows **Set** with its masked last characters, **From environment** when only `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` is set, or **Not set**), named settings profiles (provider, deploy adapter, hook timeout, -OpenTelemetry toggle; kept in `.lousho/settings.json`, which holds no +OpenTelemetry toggle; kept in `~/.lousho/studio/settings.json`, which holds no secrets), and a **Deploy** section that picks an adapter (`node-server`, `docker` or `cloudflare-worker`) and runs `lousho build` for the selected agent. Other providers still read their environment variables, the same way `lousho dev`/`lousho build` do (see [Configuration](/configuration)), and the `mock` provider needs no credentials. -Stored keys are encrypted with AES-256-GCM in `.lousho/secrets.json`. The -encryption key is generated on first use and kept next to it in -`.lousho/secrets.key` (both `0600` on POSIX). This keeps keys out of +Stored keys are encrypted with AES-256-GCM in `~/.lousho/studio/secrets.json`. +The encryption key is generated on first use and kept next to it in +`~/.lousho/studio/secrets.key` (both `0600` on POSIX). This keeps keys out of anything that copies the ciphertext alone - a backup, a zip of the project, a shared agent YAML - but anyone who can read both files as your user can -decrypt them. It is not an OS keychain. Keep `.lousho/` out of version -control and out of shared archives, and prefer environment variables on -shared machines. +decrypt them. It is not an OS keychain. Keep `~/.lousho/studio/` out of +version control and out of shared archives, and prefer environment variables +on shared machines. An agent whose provider has no key (no stored key and no environment variable) does not run. **Run** and **Chat** fail with @@ -801,16 +979,18 @@ mock agent carries a **MOCK** badge. ## Dev mode -If you're working inside this SDK's own monorepo (contributing to Agent -Forge itself, not just using it), `lousho studio` falls back to dev mode -automatically as long as `apps/agent-forge/dist-server` hasn't been built -yet: +Dev mode exists only for people contributing to Studio inside the SDK's own +monorepo - an installed Studio is always the production bundle (see +[Production vs. dev mode](#production-vs-dev-mode)). From a checkout of this +repository, `lousho studio` detects the Studio source and falls back to dev +mode automatically as long as `apps/agent-forge/dist-server` hasn't been +built yet: ```bash git clone https://github.com/LinuxDevil/lousho.git cd agent-sdk npm install -npx lousho studio --dev # or just `npx lousho studio` before building +node bin/lousho.js studio --dev # or just `node bin/lousho.js studio` before building ``` This runs the API server straight from TypeScript (`tsx`) and a real Vite @@ -822,7 +1002,7 @@ is pinned to port 5173 and the API server accepts that origin. To build the prod ```bash npm run build:studio # builds apps/agent-forge/dist (client) and # apps/agent-forge/dist-server (bundled server) -npx lousho studio # now runs in production mode +node bin/lousho.js studio # now runs in production mode ``` `npm run build:studio` also runs automatically as part of the root @@ -839,7 +1019,7 @@ at a server-local `demo-approval` tool (always `needsApproval: true` - see `server/buildAgent.ts`'s doc comment for why this exists, since none of the SDK's spec-resolvable built-in tools require approval), sends it a chat message that triggers the mock provider's tool-call heuristic, waits -for the run to pause on the approval gate, approves it via the Chat tab's +for the run to pause on the approval gate, approves it via the Test tab's inline approval card, and asserts the run completes. Run it with: ```bash diff --git a/ai-sdk-ui.mdx b/ai-sdk-ui.mdx index 20dee65..6dc1396 100644 --- a/ai-sdk-ui.mdx +++ b/ai-sdk-ui.mdx @@ -22,7 +22,7 @@ The `vendor/` model prefix chooses the provider; with OpenRouter use `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from 'lousho'; +import { createAgent, fromUIMessages, memoryStore, toUIMessageStreamResponse, type UIMessageLike } from '@linuxdevil/lousho'; // The store must outlive the request: without one, every `agent.session({ id })` // gets a fresh in-memory transcript and the next turn would start empty. @@ -159,5 +159,5 @@ kept: a `system` message (or any other role, or an entry that is not a message) is dropped, so a caller cannot add instructions to the agent's prompt. Set instructions on the agent (`createAgent({ instructions })`), not in the request. -A dedicated `lousho/ai-sdk-ui` subpath may follow; for now the +A dedicated `@linuxdevil/lousho/ai-sdk-ui` subpath may follow; for now the functions come from the package root. diff --git a/api-overview.mdx b/api-overview.mdx index 3052a56..84159f6 100644 --- a/api-overview.mdx +++ b/api-overview.mdx @@ -45,9 +45,9 @@ How the pieces fit: | Export | Description | | ----------------------------- | --------------------------------------------------------------------------- | | `createAgent(config)` | Zero-config `{ send(message) }` agent from a `model` string or provider (+ instructions, tools). | -| `AgentBuilder` (from `lousho/executor`) | Fluent builder for an `AgentConfig` (`AgentBuilder.create().setName(...)...build()`). Advanced: see [the executor API](/executor-api). | -| `AgentExecutor.execute(opts)` (from `lousho/executor`) | Static executor: runs an agent (LLM + tool-calling loop) and resolves to an `ExecutionResult`. Advanced: see [the executor API](/executor-api). | -| `resumeAfterApproval()` (from `lousho/executor`) | Resume an execution paused for human approval. Advanced: see [the executor API](/executor-api). | +| `AgentBuilder` (from `@linuxdevil/lousho/executor`) | Fluent builder for an `AgentConfig` (`AgentBuilder.create().setName(...)...build()`). Advanced: see [the executor API](/executor-api). | +| `AgentExecutor.execute(opts)` (from `@linuxdevil/lousho/executor`) | Static executor: runs an agent (LLM + tool-calling loop) and resolves to an `ExecutionResult`. Advanced: see [the executor API](/executor-api). | +| `resumeAfterApproval()` (from `@linuxdevil/lousho/executor`) | Resume an execution paused for human approval. Advanced: see [the executor API](/executor-api). | | `InMemoryApprovalStore` | Process-local `ApprovalStore`; the default store of `createAgent()` agents. | | `StorageServiceApprovalStore`, `LocalStorageCheckpointStore` (from `/utils`) | File-backed approval and checkpoint stores over a `StorageService` (see [Approvals](/approvals), [Durable execution](/durable-execution)). | | `SqliteStore` (from `/sqlite`) | Sessions, checkpoints and approvals in one SQLite file (see [Sessions](/sessions#choosing-a-store)). | @@ -75,8 +75,8 @@ resolves with the task prompt as `input` and the lead run's `sessionId`, `metadata` and `principal`. ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const refund = defineTool({ name: 'refund', description: 'Refunds an order', input: z.object({ orderId: z.string() }), execute: async () => 'ok' }); @@ -105,14 +105,14 @@ when it is created. ### UI bindings -`lousho/react` exports `useLoushoAgent(source, options?)`, a +`@linuxdevil/lousho/react` exports `useLoushoAgent(source, options?)`, a React hook that runs an agent in process (`{ agent, sessionId? }`) or over HTTP (`{ url }`) and returns `messages`, `status`, `pendingApproval`, `send()`, `stop()`, `approve()` and `reject()`. Its framework-neutral parts, `reduceAgentEvents()` and `parseEventStream()`, are exported too. See -[React](/react). `lousho/vue` exports the same +[React](/react). `@linuxdevil/lousho/vue` exports the same `useLoushoAgent()` as a Vue 3 composable, with the state as refs. See -[Vue](/vue). `lousho/svelte` exports `loushoAgent()`, the +[Vue](/vue). `@linuxdevil/lousho/svelte` exports `loushoAgent()`, the same state and actions as a Svelte store (`$agent`). See [Svelte](/svelte). ### Sub-agents @@ -210,7 +210,7 @@ See [Providers](/providers) for how a model string is resolved and which model r ## Testing -Exported from `lousho/testing` (see [Testing agents](/testing)). +Exported from `@linuxdevil/lousho/testing` (see [Testing agents](/testing)). | Export | Description | | --------------------- | ---------------------------------------------------------------------------------------- | @@ -223,18 +223,18 @@ Exported from `lousho/testing` (see [Testing agents](/testing)). | -------------------------------------------- | --------------------------------------------- | | `defineTool({ name, description, input, execute, ... })` | Define a tool; `execute`/`needsApproval` args are inferred from the zod `input`. Accepted by `createAgent({ tools: [...] })`, `ToolRegistry.register(tool)` and `AgentBuilder.addTool(tool)`. | | `ToolInput`, `ToolOutput` | Argument and result types of a defined tool. | -| `ToolRegistry` (from `lousho/tools` or `lousho/executor`) | Holds the tools an agent config refers to (advanced: `register(tool)` or `register(name, descriptor)`). | +| `ToolRegistry` (from `@linuxdevil/lousho/tools` or `@linuxdevil/lousho/executor`) | Holds the tools an agent config refers to (advanced: `register(tool)` or `register(name, descriptor)`). | | `httpTool`, `currentDateTool`, `dayNameTool` | Built-in tools. | | `createFsTools(fs, options?)`, `createShellTool(shell, options?)` | Workspace tools (`read_file`, `write_file`, `edit_file`, `list_dir`, `glob`, `grep`, `shell`) over an `FsProvider` / `ShellProvider`. See [Workspace tools](/workspace-tools). | | `NodeWorkspace`, `MemoryWorkspace`, `SandboxShell` | Workspace providers: a real directory (paths confined to `root`, minimal shell env), an in-memory tree with a scripted `exec` for tests, and a `ShellProvider` over a `SandboxAdapter` (Docker). | | `createTodoTools({ store?, onChange? })` | `todo_write` / `todo_read` tools (plus `getTodos()`) so agents can plan and track multi-step work; see [Todo tools](/tools#todo-tools). | | `connectMcp(servers, options?)` | Connect MCP servers from config (stdio or HTTP) and load their tools; see [Share servers with `connectMcp()`](/mcp-integration#share-servers-with-connectmcp). | -| `loadMcpTools(client, connectionName)` | Load a connected MCP server's tools as `ToolDescriptor`s. Available from the package root, `lousho/tools`, and `lousho/mcp`. | +| `loadMcpTools(client, connectionName)` | Load a connected MCP server's tools as `ToolDescriptor`s. Available from the package root, `@linuxdevil/lousho/tools`, and `@linuxdevil/lousho/mcp`. | See [Tools](/tools) for a guide to defining and registering tools. ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -260,7 +260,7 @@ execute }`) is legacy, still built for compatibility, and used only for descriptors that do not set `inputSchema` / `execute`. ```ts -import { loadMcpTools } from 'lousho/mcp'; +import { loadMcpTools } from '@linuxdevil/lousho/mcp'; const tools = await loadMcpTools(mcpClient, 'my-server'); ``` @@ -287,7 +287,7 @@ warning naming the server, the tool and the reason, and `onSkip` to collect what was left out: ```ts -import { loadMcpTools, type SkippedMcpTool } from 'lousho/mcp'; +import { loadMcpTools, type SkippedMcpTool } from '@linuxdevil/lousho/mcp'; const skipped: SkippedMcpTool[] = []; const tools = await loadMcpTools(mcpClient, 'my-server', { @@ -316,13 +316,13 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo ## Flows, evals, observability and security -- `FlowBuilder` / `FlowExecutor` (`lousho/flows`) - multi-step +- `FlowBuilder` / `FlowExecutor` (`@linuxdevil/lousho/flows`) - multi-step workflow graphs; see [Flows](/flows). -- `TriggerAdapter` and `TriggerRegistry` (`lousho/triggers`) - +- `TriggerAdapter` and `TriggerRegistry` (`@linuxdevil/lousho/triggers`) - the extension point for your own wake-up mechanism, plus `verifySlackSignature()`, `parseCronExpression()` and the `WebhookAuth` helpers; see [Triggers](/triggers). - `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` and `routeAuth()` - (`lousho/auth`) - route auth as an ordered list; the caller + (`@linuxdevil/lousho/auth`) - route auth as an ordered list; the caller it accepts reaches the run as `principal`; see [Route auth and principals](/auth). - `defineEval()`, scorers such as `exactMatch`, `toolCallOrder` and `budget`, checks such as `includes` and `atLeast`, and `llmJudge()` - agent evals run under vitest @@ -330,7 +330,7 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo - `withSpan()` and `TraceExporter` - tracing for `AgentExecutor.execute()`. `TraceExporter` is a bring-your-own-exporter interface (no exporter ships by default); for real OpenTelemetry spans, import - `createOtelTraceExporter()` from the `lousho/otel` + `createOtelTraceExporter()` from the `@linuxdevil/lousho/otel` subpath (requires the optional peer dependency `@opentelemetry/api`) instead of hand-rolling the OTel bridge - see `examples/tracing/run-otel.ts`. Spans follow the OpenTelemetry GenAI @@ -344,7 +344,7 @@ Token estimates, the model price table, and the usage and cost of a run: see [Mo `GenerateHookContext` - hooks that observe, deny, rewrite or redact tool calls and model calls. See [Hooks](/hooks). - `EncryptionUtils`, `sha256`, `StorageService` - (`lousho/utils`) - supporting utilities; see + (`@linuxdevil/lousho/utils`) - supporting utilities; see [Utilities](/utilities). ## Deployment diff --git a/approvals.mdx b/approvals.mdx index 0bc6de7..56a58f5 100644 --- a/approvals.mdx +++ b/approvals.mdx @@ -13,7 +13,7 @@ See also: [Pause, restart, resume](/pause-and-resume), a short end-to-end tutori > Snippets on this page leave some names to you: `provider` is any `LLMProvider` (offline: `createMockProvider()`); `agent` is a `createAgent()` result; `input` is the user's text, a `string`; `emailTool` is a tool built with `defineTool()`; `storage` is a `StorageService`; `toolRegistry` is a `ToolRegistry`. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -39,7 +39,7 @@ if (paused.finishReason === 'awaiting-approval') { arguments, typed from the tool's zod `input`: ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -92,7 +92,7 @@ error. `allow(tools)`, `deny(tools, reason?)` and `ask(tools)` build the common rules. ```ts -import { allow, ask, createAgent, defineTool, deny, type PermissionRule } from 'lousho'; +import { allow, ask, createAgent, defineTool, deny, type PermissionRule } from '@linuxdevil/lousho'; import { z } from 'zod'; const shell = defineTool({ @@ -171,7 +171,7 @@ session, each `send()` is its own transcript. Compacting away that message makes the tool ask again. ```ts -import { createAgent, defineTool, once } from 'lousho'; +import { createAgent, defineTool, once } from '@linuxdevil/lousho'; import { z } from 'zod'; const askOnce = once(); @@ -237,8 +237,8 @@ restart, give the agent a durable store, such as the SQLite one (see [Choosing a store](/sessions#choosing-a-store)): ```ts -import { createAgent } from 'lousho'; -import { SqliteStore } from 'lousho/sqlite'; +import { createAgent } from '@linuxdevil/lousho'; +import { SqliteStore } from '@linuxdevil/lousho/sqlite'; const store = new SqliteStore('./.lousho/agent.db'); const agent = createAgent({ provider, tools: [emailTool], store }); // or approvalStore: store.approvals @@ -272,7 +272,7 @@ The step's calls that need no approval do not wait: they run when the step does, before the pause. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' /* , tools: [payInvoice, archiveThread] */ }); const paused = await agent.send('Pay the invoice and archive the thread'); @@ -314,7 +314,7 @@ decided. `approved: false`. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', tools: [emailTool] }); const support = agent.session({ id: 'support-42' }); @@ -341,7 +341,7 @@ approval. There is no option on `resolve()` that ends the run. To stop that: model gets the reason and answers: ```ts -import { createAgent, type AgentHook } from 'lousho'; +import { createAgent, type AgentHook } from '@linuxdevil/lousho'; const noRefile: AgentHook = { name: 'no-refile', @@ -372,7 +372,7 @@ is only on the audit entry (`onPermissionDecision`). An expired pause resolved with `approved: true` does not run the tool. ```ts -import { createAgent, defineTool } from 'lousho'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -439,7 +439,7 @@ Its argument's `principal` is whose call it is, and tool's `ctx.approval.by` (see [auth](/auth#who-approved)). ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const trusted = createAgent({ provider, @@ -448,6 +448,69 @@ const trusted = createAgent({ }); ``` +### Model-decided approvals + +`createAutoApprove` wraps a cheap model as the `approve` callback: calls an +`auto` permission rule matched get a 0–1 safety score, and a score at or +above 0.8 (within 8 s) runs the tool unreviewed. Everything else — a lower +score, a timeout, a model error, arguments too long to judge — defers to a +human exactly like `'defer'`. It never denies a call on its own. + +```ts +import { ask, createAgent, createAutoApprove, modelAutoApprover } from '@linuxdevil/lousho'; + +declare const issueTool: any; +declare const cheapProvider: import('@linuxdevil/lousho').LLMProvider; + +const agent = createAgent({ + provider, + tools: [issueTool, emailTool], + permissions: [ask(['create_issue'])], + approve: createAutoApprove({ + autoApprover: modelAutoApprover({ provider: cheapProvider }), + isAuto: (toolName) => toolName === 'create_issue', + // fallback: the agent's own approve, asked for non-auto and escalated calls + onDecision: (d) => console.log(d.toolName, d.outcome, d.cause), + }), +}); +``` + +`modelAutoApprover` asks the provider for `{"safety": <0..1>, "reason": +"..."}` at temperature 0; `createAutoApprove` clips long arguments before +they reach the model and reports every decision through `onDecision`. Pass a +`fallback` approve callback to keep a second line of defense for escalated +calls. + +An AgentSpec v2 wires the same machinery from a permission rule: `action: +'auto'` compiles to `ask`, and a host that supplies `autoApprover` (and +optionally `onAutoApproval`) gets the model-scored callback for exactly +those rules — first match still wins, so an `allow` listed before an `auto` +rule means the model is never consulted. + +```yaml +permissions: + - id: p1 + tool: github_create_issue + action: auto +``` + +```ts +import { modelAutoApprover, specV2ToAgent } from '@linuxdevil/lousho'; + +declare const spec: import('@linuxdevil/lousho').AgentSpecV2; +declare const baseDir: string; +declare const cheapProvider: import('@linuxdevil/lousho').LLMProvider; + +const agent = await specV2ToAgent(spec, { + baseDir, + autoApprover: modelAutoApprover({ provider: cheapProvider }), + onAutoApproval: (d) => console.log(d.toolName, d.outcome, d.cause), +}); +``` + +With no `autoApprover` on the host, `action: 'auto'` behaves exactly like +`ask`. + ### Streaming the continued run `agent.approvals.streamResolve(decision, { signal })` and @@ -463,7 +526,7 @@ is what `resolve()` resolves with. A continuation that pauses again ends with continues in that session, and `run.done` comes once the transcript is saved. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ provider, tools: [emailTool] }); const paused = await agent.send('Email Sam the report'); @@ -500,7 +563,7 @@ approval: in sessions, in durable stores and across a restart. in tests and scripted agents. ```ts -import { createAgent } from 'lousho'; +import { createAgent } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'Plan the trip with the user.', provider, askQuestion: true }); @@ -537,8 +600,8 @@ tool. Resume later, after a real restart if you like, with `resumeAfterApproval()`: ```ts -import { AgentExecutor, resumeAfterApproval } from 'lousho/executor'; -import { StorageServiceApprovalStore } from 'lousho/utils'; +import { AgentExecutor, resumeAfterApproval } from '@linuxdevil/lousho/executor'; +import { StorageServiceApprovalStore } from '@linuxdevil/lousho/utils'; const approvalStore = new StorageServiceApprovalStore(storage); diff --git a/ar/cli.mdx b/ar/cli.mdx index 76565bb..6fcea0e 100644 --- a/ar/cli.mdx +++ b/ar/cli.mdx @@ -14,7 +14,7 @@ title: "CLI" | `lousho acp ` | يقدّم ملف spec أو مجلد agent أو agent مكتوب بـ TS لمحرر (Zed وغيره من ACP clients) عبر Agent Client Protocol على stdio. | [ACP](/ar/acp) | | `lousho add ` | يثبّت tool أو skill أو channel أو schedule أو memory slot من JSON registry داخل مجلد agent، بعد أن يعرض الـ permission manifest ويفحص الكود مقابله. | [Registry](/ar/registry) | | `lousho mcp ` | يقدّم الـ agent كسيرفر MCP (stdio، أو HTTP مع `--http`). | [MCP](/ar/mcp-integration#تشغيل-agent-كسيرفر-mcp) | -| `lousho eval [globs...]` | يشغّل ملفات `*.eval.ts` تحت vitest؛ ويطبع ملخصًا ويكتب تقارير JUnit/JSON. | [Evals](/ar/evals#lousho-eval) | +| `lousho eval [globs...]` | يشغّل ملفات `*.eval.ts` تحت vitest؛ ويطبع ملخصًا ويكتب تقارير JUnit/JSON. | [Evals](/ar/evals#حدود-الوقت) | | `lousho traces [id]` | يعرض آخر runs حفظها `fileTraceExporter()` (المدة، والـ model والـ tool calls، والـ tokens، والتكلفة)، أو يطبع run واحد كشجرة spans. | [Observability](/ar/observability#local-traces) | | `lousho build --target= --agent=` | يبني سيرفر Node أو Docker image أو Cloudflare Worker جاهزًا للنشر. | [النشر](/ar/deployment) | | `lousho studio` | يفتح Agent Forge، الـ dashboard المرئية، على port محلي واحد. | [Agent Forge](/ar/agent-forge) | diff --git a/ar/flows.mdx b/ar/flows.mdx index fbf41d1..d40aa5a 100644 --- a/ar/flows.mdx +++ b/ar/flows.mdx @@ -339,7 +339,7 @@ const result = await FlowExecutor.execute(flow, { agent: { name: 'report' }, pro استخدم flow إذا كان الـpipeline معروفاً مسبقاً. استخدم [sub-agents](/ar/sub-agents) عندما تريد أن يقرر الـmodel نفسه ما يفوّضه، و[skills](/ar/skills) عندما يحتاج الـagent نفسه تعليمات إضافية لبعض المهام فقط. راجع [sub-agent أم skill أم flow؟](/ar/sub-agents#sub-agent-أم-skill-أم-flow؟). -الـtracing يغطي الـflows مثل أي run للـagent (راجع [Tracing والـobservability](/ar/observability#flows)). +الـtracing يغطي الـflows مثل أي run للـagent (راجع [Tracing والـobservability](/ar/observability#الأخطاء)). ## تعبيرات الـflow diff --git a/ar/forge-author.mdx b/ar/forge-author.mdx new file mode 100644 index 0000000..db7de94 --- /dev/null +++ b/ar/forge-author.mdx @@ -0,0 +1,8 @@ +--- +title: "The Agent Author" +--- + +{/* pending-translation */} + +هذه الصفحة لم تُترجَم إلى العربية بعد. اقرأ [النسخة الإنجليزية](/forge-author). + diff --git a/ar/forge-builder.mdx b/ar/forge-builder.mdx new file mode 100644 index 0000000..21124e6 --- /dev/null +++ b/ar/forge-builder.mdx @@ -0,0 +1,8 @@ +--- +title: "The visual builder" +--- + +{/* pending-translation */} + +هذه الصفحة لم تُترجَم إلى العربية بعد. اقرأ [النسخة الإنجليزية](/forge-builder). + diff --git a/ar/forge-lifecycle.mdx b/ar/forge-lifecycle.mdx new file mode 100644 index 0000000..86a874c --- /dev/null +++ b/ar/forge-lifecycle.mdx @@ -0,0 +1,8 @@ +--- +title: "Versions, triggers and publishing" +--- + +{/* pending-translation */} + +هذه الصفحة لم تُترجَم إلى العربية بعد. اقرأ [النسخة الإنجليزية](/forge-lifecycle). + diff --git a/ar/hosted-tools.mdx b/ar/hosted-tools.mdx index f9a312d..7b4aacd 100644 --- a/ar/hosted-tools.mdx +++ b/ar/hosted-tools.mdx @@ -56,7 +56,7 @@ console.log(result.text, result.usage.hostedToolCalls); // { web_search: 1 } في OpenRouter، `webSearch()` تضيف الـ server tool الخاصة بـ OpenRouter، وهي `openrouter:web_search`، إلى الطلب. يجري البحث على جهة OpenRouter لأي model. OpenRouter هو من يختار محرك البحث: بحث الـ model نفسه إن كان عنده، وإلا Exa. ويحاسبك على كل بحث فوق الـ tokens بالأسعار الموجودة في [صفحة أسعار OpenRouter](https://openrouter.ai/pricing). راجع [دليل web search](https://openrouter.ai/docs/guides/features/server-tools/web-search) لتفاصيل المحركات. ولأن السيرفر ينفّذ البحث داخل الطلب، الـ model لا يبلّغ عن أي tool call. بدلها يبلّغ الـ SDK عن hosted call واحدة لكل model step بحث فيها، ويقرأها من ردّ OpenRouter: `name: 'web_search'`، و`args` فارغة، والروابط المستشهد بها من `url_citation` annotations تصبح `sources`، و`result: { requests }` إذا بلّغ OpenRouter عن `usage.server_tool_use.web_search_requests` (وإلا `result` تكون `{}`؛ الاختبار الحي لم يستلم عددًا). الاستعلام الذي بحث عنه الـ model لا يُبلَّغ عنه. -أي توليفة غير مدعومة ترفض الـ run قبل أول model call بخطأ [`LOUSHO_HOSTED_TOOL_UNSUPPORTED`](/ar/errors#lousho_hosted_tool_unsupported) يذكر الـ provider والـ tool وما الذي سيشتغل. +أي توليفة غير مدعومة ترفض الـ run قبل أول model call بخطأ [`LOUSHO_HOSTED_TOOL_UNSUPPORTED`](/ar/errors#lousho_unsupported_content) يذكر الـ provider والـ tool وما الذي سيشتغل. الـ `LLMProvider` المخصّص يعلن عمّا يستطيع إرساله عبر `supportsHostedTool(type)` — قيمة `type` هي `'web_search'` أو `'code_interpreter'` أو `'file_search'` أو `'custom'` لـ `hostedTool()` — ويستلم الـ tools في `GenerateOptions.hostedTools`. الـ provider الذي لا يملك هذه الدالة لا يدعم أي hosted tool. و`withRetry()` و`withFallback()` تسألان الـ provider المغلَّف (الأول). diff --git a/ar/mcp-integration.mdx b/ar/mcp-integration.mdx index 1d84303..7c35eb2 100644 --- a/ar/mcp-integration.mdx +++ b/ar/mcp-integration.mdx @@ -49,9 +49,9 @@ await agent.close(); أي متغير آخر يحتاجه السيرفر (API key، أو `NODE_ENV`، أو إعداد proxy) لا يُورَّث إلا إذا ذكرته: `env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN }`. الـ tool call الفاشل (السيرفر خرج، أو انتهت مهلته، أو ردّ بخطأ في البروتوكول) -يُرفض بـ[`LOUSHO_MCP_CALL_FAILED`](/ar/errors#lousho_mcp_call_failed) مع ذكر +يُرفض بـ[`LOUSHO_MCP_CALL_FAILED`](/ar/errors#lousho_approval_not_found) مع ذكر `__`. والسيرفر الذي لا يبدأ يفشل بـ -[`LOUSHO_MCP_START_FAILED`](/ar/errors#lousho_mcp_start_failed)، ورسالته +[`LOUSHO_MCP_START_FAILED`](/ar/errors#lousho_approval_store_missing)، ورسالته فيها exit code وآخر الأسطر التي كتبتها العملية على stderr. `stderr: 'forward'` (الافتراضي) ينسخ أيضًا stderr العملية إلى stderr العملية الحالية، و`'capture'` يحتفظ بآخر الأسطر فقط، و`'inherit'` / `'ignore'` لا يحتفظان بشيء. diff --git a/ar/pending.json b/ar/pending.json index fe51488..27f8904 100644 --- a/ar/pending.json +++ b/ar/pending.json @@ -1 +1,5 @@ -[] +[ + "forge-builder", + "forge-author", + "forge-lifecycle" +] diff --git a/ar/schedules.mdx b/ar/schedules.mdx index d2c7f73..c9b923d 100644 --- a/ar/schedules.mdx +++ b/ar/schedules.mdx @@ -28,7 +28,7 @@ const running = startSchedules(agent, [morning, cleanup]); await running.stop(); ``` -- `cron`: خمسة حقول (`minute hour day-of-month month day-of-week`) أو `@hourly` أو `@daily` أو `@weekly` أو `@monthly`، ويُحسَب في `timezone` (اسم IANA، والافتراضي منطقة الجهاز الزمنية). إذا كان التعبير غير صالح، أو حدّدت `prompt` و`run` معًا، أو ما حدّدت أي واحد منهما، `defineSchedule()` يرمي `LOUSHO_SCHEDULE_INVALID` مباشرة — لا عند أول firing. راجع [الأخطاء](/ar/errors#lousho_schedule_invalid). +- `cron`: خمسة حقول (`minute hour day-of-month month day-of-week`) أو `@hourly` أو `@daily` أو `@weekly` أو `@monthly`، ويُحسَب في `timezone` (اسم IANA، والافتراضي منطقة الجهاز الزمنية). إذا كان التعبير غير صالح، أو حدّدت `prompt` و`run` معًا، أو ما حدّدت أي واحد منهما، `defineSchedule()` يرمي `LOUSHO_SCHEDULE_INVALID` مباشرة — لا عند أول firing. راجع [الأخطاء](/ar/errors#السجل). - `run` تستقبل `{ agent, firedAt, name }`. `startSchedules(agent, schedules, { now?, setTimer?, onError?, keepAlive? })` يحتفظ بمؤقّت واحد لكل schedule. أي run يرمي خطأ يذهب إلى `onError` (الافتراضي: `console.error`) ولا يوقّف باقي الـ schedules أبدًا. والـ turn الخاص بـ prompt schedule إذا انتهى بأي `finishReason` غير `'stop'` يُعتبَر run فاشلًا أيضًا: `onError` يستقبل خطأ `LOUSHO_SCHEDULE_RUN_INCOMPLETE` يذكر السبب، مثلًا `'awaiting-approval'` (مع id الـ approval المعلّقة، وتبقى معلّقة إلى أن تحسمها بـ `agent.approvals.resolve()`)، أو `'output-invalid'` أو `'max-steps'`. الـ schedule لا يتداخل مع نفسه: لو الـ firing السابق ما زال شغّالًا، يُتخطّى الإطلاق التالي ويُبلَّغ به `onError`. والـ fires التي فاتت أثناء تعليق العملية تُتخطّى بدل إعادة تشغيلها. `now` و`setTimer` قابلان للحقن حتى لا تنام الاختبارات أبدًا. diff --git a/ar/triggers.mdx b/ar/triggers.mdx index 7b3edb8..be032a3 100644 --- a/ar/triggers.mdx +++ b/ar/triggers.mdx @@ -91,7 +91,7 @@ webhookChannel({ ## تواقيع طلبات Slack -`slackChannel()` يتحقق من كل طلب نيابةً عنك (راجع [Slack في Channels](/ar/channels#slack)). أما في الـhandler الخاص بك (slash commands والتفاعلات)، فتحقّق من الـbody الخام بـ`verifySlackSignature()`: HMAC-SHA256 على `v0:{X-Slack-Request-Timestamp}:{raw body}`، يُقارَن في زمن ثابت مع `X-Slack-Signature` (`v0=`). الطلبات الأقدم من خمس دقائق، أو التي تسبق الوقت المحلي بأكثر من دقيقة، تُرفَض (الـtimestamp بالثواني، كما ترسله Slack). راجع [توثيق Slack](https://docs.slack.dev/authentication/verifying-requests-from-slack). +`slackChannel()` يتحقق من كل طلب نيابةً عنك (راجع [Slack في Channels](/ar/channels#discord)). أما في الـhandler الخاص بك (slash commands والتفاعلات)، فتحقّق من الـbody الخام بـ`verifySlackSignature()`: HMAC-SHA256 على `v0:{X-Slack-Request-Timestamp}:{raw body}`، يُقارَن في زمن ثابت مع `X-Slack-Signature` (`v0=`). الطلبات الأقدم من خمس دقائق، أو التي تسبق الوقت المحلي بأكثر من دقيقة، تُرفَض (الـtimestamp بالثواني، كما ترسله Slack). راجع [توثيق Slack](https://docs.slack.dev/authentication/verifying-requests-from-slack). ```ts import { verifySlackSignature } from 'lousho/triggers'; diff --git a/ar/troubleshooting.mdx b/ar/troubleshooting.mdx index efe6769..a141e56 100644 --- a/ar/troubleshooting.mdx +++ b/ar/troubleshooting.mdx @@ -17,13 +17,13 @@ title: "حل المشاكل" - **السبب:** متغير مفتاح الـ provider (`OPENAI_API_KEY` أو `ANTHROPIC_API_KEY` أو `OPENROUTER_API_KEY`) غير موجود في بيئة العملية التي تشغّل الـ agent (`LOUSHO_PROVIDER_MISSING_API_KEY`). - **الحل:** اضبطه، أو مرّر instance جاهز من الـ provider. `npx lousho doctor` يطبع `set` أو `not set` لكل مفتاح، ولا يطبع القيمة أبدًا. بادئة `vendor/` في اسم الـ model هي التي تختار الـ provider؛ مع OpenRouter استخدم `openrouter//` (مثال `openrouter/openai/gpt-4o-mini`) — `model: 'openai/gpt-4o-mini'` يحتاج `OPENAI_API_KEY` حتى لو `OPENROUTER_API_KEY` هو الوحيد المضبوط. -- **للمزيد:** [الخطأ](/ar/errors#lousho_provider_missing_api_key). +- **للمزيد:** [الخطأ](/ar/errors#lousho_provider_request_failed). ### "Cannot find package `@ai-sdk/openai`" أو "install the optional peer" - **السبب:** كل provider يعتمد على حزمة اختيارية تُحمَّل عند أول استخدام، بإصدار رئيسي يقابل إصدار `ai` الرئيسي عندك (`LOUSHO_PEER_MISSING`). نفس الخطأ يغطي باقي الـ peer dependencies الاختيارية، مثل `dockerode` و`@modelcontextprotocol/sdk`. - **الحل:** نفّذ أمر `npm install` الموجود في الرسالة (موجود أيضًا في `error.installCommand`)، أو ابحث عن إصدار `ai` الرئيسي عندك في جدول الاقتران. -- **للمزيد:** [حزم الـ providers](/ar/installation#حزم-الـ-providers)، [الـ peer dependencies الاختيارية](/ar/installation#optional-peers)، [الخطأ](/ar/errors#lousho_peer_missing). +- **للمزيد:** [حزم الـ providers](/ar/installation#حزم-الـ-providers)، [الـ peer dependencies الاختيارية](/ar/installation#optional-peers)، [الخطأ](/ar/errors#lousho_hosted_tool_unsupported). ### "`ai` 5 is installed" @@ -73,7 +73,7 @@ title: "حل المشاكل" - **السبب:** الـ session الموقوفة على approval لا تقبل مدخل جديد (`LOUSHO_SESSION_AWAITING_APPROVAL`)؛ أو أن `session.compact()` أو `session.clear()` استُدعيت وفي turn شغّال أو في الطابور (`LOUSHO_SESSION_BUSY`). - **الحل:** حسم الـ approval ثم أرسل مجددًا؛ أو انتظر `send()` الخاصة بالـ turn (أو ألغِها)، ثم استدعِ من جديد. -- **للمزيد:** [بانتظار الـ approval](/ar/errors#lousho_session_awaiting_approval)، [مشغولة](/ar/errors#lousho_session_busy). +- **للمزيد:** [بانتظار الـ approval](/ar/errors#lousho_session_turn_pending)، [مشغولة](/ar/errors#lousho_session_step_not_found). ## Tools @@ -93,7 +93,7 @@ title: "حل المشاكل" - **السبب:** الـ model أرسل arguments لا تطابق الـ input schema للـ tool. داخل الـ run ترجع له الـ issues كنتيجة ويقدر يعيد المحاولة، فنادرًا ما ينتهي run بسببه. - **الحل:** إذا الـ model يغلط في نفس الحقل، خلّ نص `.describe()` أوضح. وإذا إنت استدعيت الـ tool بنفسك، صحّح الـ arguments المذكورة في الرسالة. -- **للمزيد:** [الخطأ](/ar/errors#lousho_tool_args_invalid). +- **للمزيد:** [الخطأ](/ar/errors#الـ-approvals-والـ-sessions). ## Providers و models @@ -101,7 +101,7 @@ title: "حل المشاكل" - **السبب:** الـ provider طبّق rate limit على الاستدعاء (`LOUSHO_PROVIDER_RATE_LIMITED`) أو فشل فشلًا عابرًا. - **الحل:** `createAgent()` أصلًا تعيد الاستدعاء الفاشل مرتين — لكن فقط لـ model مُمرّر كسلسلة `provider/model` أو محلول من البيئة؛ أما `provider` instance تمرّره إنت فلا تُعاد محاولاته إلا إذا ضبطت `retry`. ارفعها بـ `retry: { maxRetries: 3 }`، واضبط `fallbackModels` للانتقال إلى الـ model التالي إذا الاستدعاء ظل يفشل. -- **للمزيد:** [إعادة المحاولة و fallback models](/ar/providers#retry-ونماذج-fallback)، [الخطأ](/ar/errors#lousho_provider_rate_limited). +- **للمزيد:** [إعادة المحاولة و fallback models](/ar/providers#retry-ونماذج-fallback)، [الخطأ](/ar/errors#lousho_peer_missing). ### الـ model لا يقرأ ملفاتي @@ -121,7 +121,7 @@ title: "حل المشاكل" - **السبب:** `SubprocessSandbox` مع `network: { allow }` و`broker` يحتاج أن يكون الـ broker المخرج الوحيد للحاوية. Docker Desktop وrootless Docker وdaemon على جهاز آخر وEngine أقدم من 25.0.5 كلها لا تستطيع أن تعطيك هذا، فلا تبدأ أي حاوية (`LOUSHO_SANDBOX_EGRESS_UNSUPPORTED`). الرفض مقصود، بدل فتح egress مفتوح. - **الحل:** شغّل الـ agent على Linux host عليه Docker Engine 25.0.5 أو أحدث، أو استخدم `network: 'none'`. -- **للمزيد:** [Sandboxed shell](/ar/workspace-tools#sandboxed-shell-sandboxshell)، [الخطأ](/ar/errors#lousho_sandbox_egress_unsupported). +- **للمزيد:** [Sandboxed shell](/ar/workspace-tools#sandboxed-shell-sandboxshell)، [الخطأ](/ar/errors#lousho_flow_invalid). ### أدوات سيرفر MCP لا تظهر diff --git a/auth.mdx b/auth.mdx index b9e3d32..eb88a5c 100644 --- a/auth.mdx +++ b/auth.mdx @@ -4,7 +4,7 @@ title: "Route auth and principals" {/* Generated from docs/auth.md in LinuxDevil/lousho by scripts/sync-sdk-docs.mjs. Do not edit here. */} An agent served over HTTP needs to know two things about each request: may it -call at all, and who is it. `lousho/auth` answers both. You +call at all, and who is it. `@linuxdevil/lousho/auth` answers both. You give a route an ordered list of auth entries (`jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` or your own function); the first entry that accepts the request returns a **principal**, and that principal goes into the @@ -12,9 +12,9 @@ run, where your `model` / `instructions` / `tools` functions and memory scopes can read it. ```ts -import { createAgent, createRouteHandler } from 'lousho'; -import { apiToken, jwt } from 'lousho/auth'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; +import { apiToken, jwt } from '@linuxdevil/lousho/auth'; +import { mockModel } from '@linuxdevil/lousho/testing'; const agent = createAgent({ provider: mockModel(['Hello!']), @@ -86,7 +86,7 @@ exactly one key source: | `jwksUrl` | a JWKS endpoint (https, or http on localhost) | `RS256`, `ES256` by default; RSA and EC keys only | ```ts -import { jwt } from 'lousho/auth'; +import { jwt } from '@linuxdevil/lousho/auth'; const fromOurAuthServer = jwt({ jwksUrl: 'https://auth.example.com/.well-known/jwks.json', @@ -126,7 +126,7 @@ or http on `localhost` / `127.0.0.1` / `[::1]` — the same rule as `jwt()`'s `jwksUrl`). Algorithms default to `RS256` and `ES256`; HMAC is not allowed. ```ts -import { oidc } from 'lousho/auth'; +import { oidc } from '@linuxdevil/lousho/auth'; const google = oidc({ issuer: 'https://accounts.google.com', audience: process.env.GOOGLE_CLIENT_ID ?? 'client-id' }); ``` @@ -142,7 +142,7 @@ carries `WWW-Authenticate: Basic realm="", charset="UTF-8"` so browsers show their login prompt. ```ts -import { basic } from 'lousho/auth'; +import { basic } from '@linuxdevil/lousho/auth'; const operators = basic({ users: { ops: process.env.OPS_PASSWORD! }, realm: 'agent' }); // Or check against your own store (compare in constant time there): @@ -171,7 +171,7 @@ Any function of the `Request` is an entry. Declare the challenges it answers a `401` with, or it advertises `Bearer`: ```ts -import { AuthError, type AuthFn } from 'lousho/auth'; +import { AuthError, type AuthFn } from '@linuxdevil/lousho/auth'; const tenantKey: AuthFn = async (request) => { const key = request.headers.get('x-tenant-key'); @@ -231,7 +231,7 @@ In an [agent directory](/agent-directories), put the list in `auth.ts` (or ```ts // my-agent/auth.ts -import { apiToken, oidc } from 'lousho/auth'; +import { apiToken, oidc } from '@linuxdevil/lousho/auth'; export default [ oidc({ issuer: 'https://login.example.com', audience: 'agent-api' }), @@ -261,8 +261,8 @@ The principal is on the run context next to `sessionId` and `metadata`. The `openrouter//` (e.g. `openrouter/openai/gpt-4o-mini`). ```ts -import { createAgent, defineMemory, inMemoryMemory, type Principal } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineMemory, inMemoryMemory, type Principal } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const notes = defineMemory({ name: 'notes', @@ -346,7 +346,7 @@ none in any of these places. ### In a tool ```ts -import { defineTool } from 'lousho'; +import { defineTool } from '@linuxdevil/lousho'; import { z } from 'zod'; export const myOrders = defineTool({ @@ -367,8 +367,8 @@ from the request body or the model, and a tool cannot change it. ### Approval policies and permission rules ```ts -import { createAgent, defineTool } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const deploy = defineTool({ @@ -396,8 +396,8 @@ A decision can name who made it. The approved tool sees the decider as `ctx.approval.by`, next to `ctx.approval.note`: ```ts -import { createAgent, defineTool, type Principal } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, defineTool, type Principal } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; import { z } from 'zod'; const sendEmail = defineTool({ @@ -467,8 +467,8 @@ see the example under [Who may decide an approval](#who-may-decide-an-approval)) caller keeps the check stateless: ```ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; -import { jwt } from 'lousho/auth'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; +import { jwt } from '@linuxdevil/lousho/auth'; const agent = createAgent({ model: 'openai/gpt-4o-mini', store: memoryStore() }); // The user id a session id starts with: everything before its last `-`. A prefix match would let @@ -524,7 +524,7 @@ Behind a reverse proxy: rewrites `Host`, list the upstream name the proxy sends, not the public one. ```ts -import { createAgent, createRouteHandler } from 'lousho'; +import { createAgent, createRouteHandler } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini' }); @@ -554,8 +554,8 @@ customer can no longer approve their own refund, and a supervisor can read any session (to see what they are approving) and decide in it: ```ts -import { createAgent, createRouteHandler, memoryStore } from 'lousho'; -import { jwt } from 'lousho/auth'; +import { createAgent, createRouteHandler, memoryStore } from '@linuxdevil/lousho'; +import { jwt } from '@linuxdevil/lousho/auth'; const agent = createAgent({ model: 'openai/gpt-4o-mini', store: memoryStore() }); const isSupervisor = (principal?: { claims?: Readonly> }) => principal?.claims?.role === 'supervisor'; @@ -577,8 +577,8 @@ claims, a manager decides them, and nobody approves their own: ```ts // my-agent/auth.ts -import { apiToken } from 'lousho/auth'; -import type { ApprovalAccessRequest, SessionAccessRequest } from 'lousho'; +import { apiToken } from '@linuxdevil/lousho/auth'; +import type { ApprovalAccessRequest, SessionAccessRequest } from '@linuxdevil/lousho'; export default [ apiToken(process.env.ALICE_TOKEN!, { id: 'alice' }), diff --git a/build-a-coding-agent.mdx b/build-a-coding-agent.mdx index 47503b7..b28c5db 100644 --- a/build-a-coding-agent.mdx +++ b/build-a-coding-agent.mdx @@ -20,7 +20,7 @@ file system and a shell for it, and `createFsTools()` and `createShellTool()` turn it into tools. ```ts -import { NodeWorkspace, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createFsTools, createShellTool } from '@linuxdevil/lousho'; const workspace = new NodeWorkspace({ root: process.cwd() }); const tools = [...createFsTools(workspace), createShellTool(workspace)]; @@ -45,7 +45,7 @@ per-tool map. For the shell, pass a predicate over the command string and gate only the commands you consider risky. ```ts -import { NodeWorkspace, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createFsTools, createShellTool } from '@linuxdevil/lousho'; const DESTRUCTIVE = /\b(rm|mv|chmod|chown|git\s+(push|reset|clean|checkout))\b|>/; @@ -77,7 +77,7 @@ written and `tool.start` announces each tool call (see [Streaming](/streaming)). ```ts -import { NodeWorkspace, createAgent, createFsTools, createShellTool } from 'lousho'; +import { NodeWorkspace, createAgent, createFsTools, createShellTool } from '@linuxdevil/lousho'; const workspace = new NodeWorkspace({ root: process.cwd() }); const agent = createAgent({ @@ -114,7 +114,7 @@ the rest of the work. That run can pause again, so loop until there is no ```ts import { createInterface } from 'node:readline/promises'; -import { createAgent, type AgentRun } from 'lousho'; +import { createAgent, type AgentRun } from '@linuxdevil/lousho'; const agent = createAgent({ model: 'openai/gpt-4o-mini', instructions: 'You are a coding agent.' }); const rl = createInterface({ input: process.stdin, output: process.stdout }); @@ -169,7 +169,7 @@ same pause handling. // coding.ts // Run: OPENAI_API_KEY=... npx tsx coding.ts "Fix the failing test" import { createInterface } from 'node:readline/promises'; -import { NodeWorkspace, createAgent, createFsTools, createShellTool, type AgentRun } from 'lousho'; +import { NodeWorkspace, createAgent, createFsTools, createShellTool, type AgentRun } from '@linuxdevil/lousho'; const DESTRUCTIVE = /\b(rm|mv|chmod|chown|git\s+(push|reset|clean|checkout))\b|>/; @@ -253,8 +253,8 @@ memory and takes a scripted `exec` for the shell, and `mockModel` plays the model. Assert on `workspace.snapshot()` and `workspace.commands`. ```ts -import { createAgent, createFsTools, createShellTool, MemoryWorkspace } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createFsTools, createShellTool, MemoryWorkspace } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const workspace = new MemoryWorkspace({ files: { 'math.js': 'exports.add = (a, b) => a - b;\n' }, @@ -281,8 +281,8 @@ To test the approval path, gate `write_file` the way the real agent does. runs the write and finishes the turn. ```ts -import { createAgent, createFsTools, MemoryWorkspace } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, createFsTools, MemoryWorkspace } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const workspace = new MemoryWorkspace({ files: { 'notes.txt': 'old\n' } }); const agent = createAgent({ diff --git a/changelog.mdx b/changelog.mdx index fdab60e..c907767 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -6,6 +6,120 @@ description: "Every notable change to lousho, newest first." ## [Unreleased] +- **Breaking:** the package is renamed from `lousho` to `@linuxdevil/lousho`. Update imports from `'lousho'` / `'lousho/…'` to `'@linuxdevil/lousho'` / `'@linuxdevil/lousho/…'` and dependencies from `"lousho"` to `"@linuxdevil/lousho"`. The `lousho` binary name is unchanged, so `npx @linuxdevil/lousho …` and `lousho …` commands keep working. +- Docs: `docs/agent-forge.md` is retitled **Lousho Studio** and now documents installing the separate `@linuxdevil/studio` package, workspaces, which SDK runs a workspace's agents (with each banner), the collector, and reporting deployed agents via `LOUSHO_OBSERVE_PROJECT`. +- **Breaking:** Studio is now the separate package `@linuxdevil/studio`; the SDK tarball no longer contains `apps/agent-forge`. `lousho studio` finds an installed `@linuxdevil/studio` (local, next to the SDK, or global) and opens the current folder as a workspace, or prints `Studio is a separate install: npx @linuxdevil/studio` when it is missing. Inside the SDK checkout it keeps its dev/prod modes (`--prod`/`--dev`). Install Studio with `npm i -g @linuxdevil/studio` (or `npx @linuxdevil/studio`). +- Studio: the Code tab gains "Eject to project" (`POST /agents/:id/eject`), which writes the agent-directory form (`specToAgentDir`) to `agents//` in the workspace after a confirm dialog, with an explicit overwrite step when the folder already exists. +- New `@linuxdevil/lousho/observe` subpath: `startObserveDaemon`, `probeHealth`, `isObserveDaemon`, `defaultDbPath`, `OBSERVE_DEFAULT_PORT` and the daemon option/retention defaults, so a host process (Studio) can run and probe the collector through public exports. +- Install docs use the public npm packages again (`npm install @linuxdevil/lousho`, `npm create lousho`). +- `@linuxdevil/lousho/improve` (experimental): `improveAgent()` proposes tested edits to an agent's instructions and skills, offline, gated against the current harness and held-out cases. It applies nothing. See [Improving an agent](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/improve.md). +- `lousho bridge`: a new command that speaks Studio's bridge protocol (NDJSON on stdin/stdout, protocol 1) so Lousho Studio can build and run a project's agents with that project's own SDK and dependencies. It answers `hello`, `catalog`, `compile` and `shutdown`, and runs agents: `run`/`chat` start a run or chat turn, `steer`/`abort` control it, `resolveApproval`/`answerQuestion` resume a paused run, and every `AgentEvent`, trace span, steer join and run settle arrives as a pushed event (flows are refused with `LOUSHO_HOST_UNSUPPORTED`). +- Model strings resolve any official AI SDK provider: `mistral/...`, `groq/...`, `xai/...`, `bedrock/...`, `vertex/...`, `azure/...` and 13 more load `@ai-sdk/` on the first call, and `ai-sdk//` loads any other `@ai-sdk/*` package. The package reads its own credentials. See [Providers](/providers#any-ai-sdk-vendor-by-name). + +## [1.0.0-alpha.117] - 2026-11-09 + +## [1.0.0-alpha.116] - 2026-11-09 + +## [1.0.0-alpha.115] - 2026-11-09 + +## [1.0.0-alpha.114] - 2026-11-09 + +## [1.0.0-alpha.113] - 2026-11-09 + +## [1.0.0-alpha.112] - 2026-11-09 + +## [1.0.0-alpha.111] - 2026-11-09 + +## [1.0.0-alpha.110] - 2026-11-09 + +## [1.0.0-alpha.109] - 2026-11-09 + +## [1.0.0-alpha.108] - 2026-11-09 + +## [1.0.0-alpha.107] - 2026-11-09 + +## [1.0.0-alpha.106] - 2026-11-09 + +## [1.0.0-alpha.105] - 2026-11-09 + +## [1.0.0-alpha.104] - 2026-11-09 + +## [1.0.0-alpha.103] - 2026-11-08 + +## [1.0.0-alpha.102] - 2026-11-08 + +## [1.0.0-alpha.101] - 2026-11-08 + +## [1.0.0-alpha.100] - 2026-11-08 + +## [1.0.0-alpha.99] - 2026-11-08 + +## [1.0.0-alpha.98] - 2026-11-08 + +## [1.0.0-alpha.97] - 2026-11-08 + +## [1.0.0-alpha.96] - 2026-11-08 + +## [1.0.0-alpha.95] - 2026-11-08 + +## [1.0.0-alpha.94] - 2026-11-08 + +## [1.0.0-alpha.93] - 2026-11-08 + +## [1.0.0-alpha.92] - 2026-11-08 + +## [1.0.0-alpha.91] - 2026-11-08 + +## [1.0.0-alpha.90] - 2026-11-08 + +## [1.0.0-alpha.89] - 2026-11-08 + +## [1.0.0-alpha.88] - 2026-11-08 + +## [1.0.0-alpha.87] - 2026-11-08 + +## [1.0.0-alpha.86] - 2026-11-08 + +## [1.0.0-alpha.85] - 2026-11-08 + +## [1.0.0-alpha.84] - 2026-11-08 + +## [1.0.0-alpha.83] - 2026-11-08 + +## [1.0.0-alpha.82] - 2026-11-08 + +## [1.0.0-alpha.81] - 2026-11-08 + +## [1.0.0-alpha.80] - 2026-11-08 + +## [1.0.0-alpha.79] - 2026-11-08 + +## [1.0.0-alpha.78] - 2026-11-08 + +## [1.0.0-alpha.77] - 2026-11-08 + +## [1.0.0-alpha.76] - 2026-11-08 + +## [1.0.0-alpha.75] - 2026-11-08 + +## [1.0.0-alpha.74] - 2026-11-08 + +## [1.0.0-alpha.73] - 2026-11-08 + +## [1.0.0-alpha.72] - 2026-11-08 + +## [1.0.0-alpha.71] - 2026-11-08 + +## [1.0.0-alpha.70] - 2026-11-08 + +## [1.0.0-alpha.69] - 2026-11-08 + +## [1.0.0-alpha.68] - 2026-11-08 + +## [1.0.0-alpha.67] - 2026-11-08 + +## [1.0.0-alpha.66] - 2026-11-08 + ## [1.0.0-alpha.65] - 2026-11-08 ## [1.0.0-alpha.64] - 2026-11-08 @@ -74,8 +188,8 @@ description: "Every notable change to lousho, newest first." ## [1.0.0-alpha.32] - 2026-11-07 -- Install docs use the public npm packages again (`npm install lousho`, `npm create lousho`). -- `lousho/improve` (experimental): `improveAgent()` proposes tested edits to an agent's instructions and skills, offline, gated against the current harness and held-out cases. It applies nothing. See [Improving an agent](/improve). +- Install docs use the public npm packages again (`npm install @linuxdevil/lousho`, `npm create lousho`). +- `@linuxdevil/lousho/improve` (experimental): `improveAgent()` proposes tested edits to an agent's instructions and skills, offline, gated against the current harness and held-out cases. It applies nothing. See [Improving an agent](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/improve.md). - `agent.recover({ retryWithDiagnosis: true })` gives a run that reached `maxAttempts` one fork told why it failed, before quarantining it. `createOutputCheck()` is a new hook that runs your check after a tool succeeds and tells the model what it found. A fork no longer inherits `lastError` and `attempts` from its source. - **Behavior change:** `agent.recover()` no longer resumes a failing run forever. It backs off exponentially from the last failure (up to 15 minutes) and stops after `maxAttempts` failures in a row (default 10), calling the new `onQuarantined(run)` once. `agent.resume()` still runs a quarantined run. Pass a larger `maxAttempts` to keep retrying longer. - `lousho observe failures` also lists runs whose process died and scores below `--min-score` (default 0.5). @@ -86,7 +200,7 @@ description: "Every notable change to lousho, newest first." ## [1.0.0-alpha.31] - 2026-11-01 -- **Behavior change:** the package is renamed from `@lousho/build-ai-agent` to `lousho`, and the scaffolder from `create-lousho-agent` to `create-lousho` (`npm create lousho`). Update imports from `@lousho/build-ai-agent` to `lousho` (subpaths such as `lousho/testing` keep their names). Install with `npm install lousho` or `npm create lousho` (no token needed). The packages are also mirrored to GitHub Packages as `@linuxdevil/lousho` and `@linuxdevil/create-lousho` for the repository's Packages tab. +- **Behavior change:** the package is renamed from `@lousho/build-ai-agent` to `@linuxdevil/lousho`, and the scaffolder from `create-lousho-agent` to `create-lousho` (`npm create lousho`). Update imports from `@lousho/build-ai-agent` to `@linuxdevil/lousho` (subpaths such as `@linuxdevil/lousho/testing` keep their names). Install with `npm install @linuxdevil/lousho` or `npm create lousho` (no token needed). The packages are also mirrored to GitHub Packages as `@linuxdevil/lousho` and `@linuxdevil/create-lousho` for the repository's Packages tab. ## [1.0.0-alpha.30] - 2026-10-31 @@ -228,7 +342,7 @@ opt-out. - A decision on an approval whose winning decision is still streaming is a `409` (`LOUSHO_APPROVAL_CONFLICT`), as docs/auth.md says, instead of a `404`. - The session routes' `400` bodies carry a `code`: `LOUSHO_REQUEST_INVALID` (invalid JSON, missing or empty fields), `LOUSHO_APPROVAL_DECISION_INVALID` (an approval body with neither `approved` nor `answer`), `LOUSHO_SESSION_ID_INVALID`. - `lousho chat` prints a failed turn with its error code, the provider's failure category and HTTP status, and the fix (`error: User not found. (auth-failure, HTTP 401)` then `[LOUSHO_PROVIDER_REQUEST_FAILED] ...`), as docs/cli.md says, instead of the bare provider message. The terminal chat of a project scaffolded by `lousho init` / `create-lousho-agent` does the same. -- A `.env` saved with a UTF-8 byte order mark (PowerShell 5.1's `Set-Content -Encoding utf8`) no longer loses its first variable: the `lousho` commands and the scaffolded `src/index.ts` drop the mark before parsing. Variables already set in the environment still win. +- A `.env` saved with a UTF-8 byte order mark (PowerShell 5.1's `Set-Content -Encoding utf8`) no longer loses its first variable: the `@linuxdevil/lousho` commands and the scaffolded `src/index.ts` drop the mark before parsing. Variables already set in the environment still win. - `lousho doctor src/agent.ts` loads a `.ts`/`.js` agent module the way `lousho dev` does instead of failing with `LOUSHO_SPEC_UNSUPPORTED_FORMAT`, and a missing key for the module's provider is a failure. A scaffolded project's `npm run doctor` now runs `lousho doctor src/agent.ts`, so it fails when the agent's key is missing. - `lousho doctor --ping` checks OpenRouter at `/api/v1/key`, which needs the key (its `/models` answers without one, so a wrong key passed); a key the provider rejects is now a failure, and a ping of a custom `OPENAI_BASE_URL` says it shows reachability only. - `lousho doctor` fix lines use PowerShell syntax (`$env:NAME=""`) on Windows. @@ -439,7 +553,7 @@ create-lousho-agent 0.1.2-alpha.5. - 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. - 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). +- `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 `@linuxdevil/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. @@ -546,7 +660,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro - Permission modes: `createAgent({ permissionMode })`, `send()` / `stream()` `{ permissionMode }`, `agent.session({ permissionMode })` and `session.setPermissionMode()` put an agent in `'plan'` (only read-only tools run; everything else is refused with `kind: 'denied'`, also when an `allow` rule matched, and a run that starts in plan mode gets one system-prompt paragraph saying so), `'acceptEdits'` (file edits that would ask run without asking) or `'dontAsk'` (a call that would ask is refused; nothing pauses). The modes are presets applied after hooks, permission rules, tool guardrails and `needsApproval`, so no mode turns a deny into a run. A function mode, and a session's mode, are read at every tool call, so a switch applies from the next call, also mid-turn and in a paused turn continued by `agent.approvals.resolve()`. Sub-agents run under the lead's mode unless it is `'default'`; a remote sub-agent is refused in plan mode. Audit: under a mode other than `'default'` every call is audited, `PermissionDecisionEntry.mode` says when the mode changed the outcome, and the new `onPermissionModeChange` records each switch. New: `PermissionMode` and `PermissionModeChange` types, `defineTool({ editsFiles })` (`metadata.editsFiles`, set on `write_file` and `edit_file`), `readOnlyHint: true` on `load_skill`, `recall_`, `agent_status` and `agent_await`. The mode is not saved in checkpoints. Example: `examples/plan-mode`. See [Permission modes](/permission-modes). - Semantic recall: a memory slot can recall by meaning. New `EmbeddingProvider` (`{ id, embed(texts) }`) and `aiSdkEmbedder(model, { id?, maxBatch? })`, which wraps any AI SDK embedding model through `embedMany` from the installed `ai` (4, 6 or 7; `ai` is loaded on first use, no new dependency), and two providers that rank by cosine similarity: `inMemoryVectorMemory({ embedder, maxItems?, minScore? })` from the root and `sqliteVectorMemory(store, options)` from `@lousho/build-ai-agent/sqlite` (a new `memory_vectors` table, added to an existing database file on open; `memory_items` and `sqliteMemory()` are unchanged). With a query, `list()` returns the scope key's items by score (floor `minScore`, default 0.2; score in `metadata.score`); without one it lists the newest first. Items embedded by another embedder id are skipped when ranking until `provider.reindex(scopeKey?)` re-embeds them. A `MemoryProvider` may set `ranking: 'relevance'`, which makes the `recall_` tool say "by meaning, most relevant first". `hashEmbedder({ dimensions? })` from `@lousho/build-ai-agent/testing` is a deterministic offline embedder for tests. `describeMemoryProviderContract` takes `{ query: 'filter' | 'rank' }`. docs/memory.md has a new `## Semantic recall` section at the end. - `teamsChannel({ appId, appPassword, tenantId?, name?, fetch?, approvers?, onError? })`: an agent behind an Azure Bot in Microsoft Teams, mounted like the other channels (`POST /teams`). The inbound Bot Framework token is verified with the auth module (`oidc()`: RS256, the key set of the fixed Bot Framework OpenID metadata, issuer `https://api.botframework.com`, audience `appId`, 5-minute tolerance) before the body is parsed, and its `serviceurl` claim must equal the activity's `serviceUrl`; replies go only to that service URL (https). Personal chats always reach the agent, group chats and channels only on an `@mention` (stripped from the text); one session per conversation; Markdown replies split at 25,000 characters; tool approvals are Adaptive Cards with Approve and Deny buttons (`approvers` as on the other channels; the card updates to "Approved by..." and names its conversation, so a copied or replayed reference decides nothing); `ask_question` is answered by the next message, also after a restart with durable stores. No `node:*` import. Telegram and GitHub now set the sender as the run's `principal` too (`authenticator: 'telegram'` with the user id, `'github'` with the login), as Slack and Discord do. See docs/channels.md#microsoft-teams. -- Cloudflare Worker target builds agent directories (config, instructions, TypeScript tools, skills): `npx lousho build./my-agent --target=cloudflare-worker`. The build reads the directory on Node and generates `agent.module.ts` (static imports of `tools/*` and of an `agent.ts` config; `instructions.md`, a JSON/YAML config and the skills embedded as JSON), and the Worker builds the agent with the rules of `resolveAgentDir()` (the config check and the tool-export rules now live in Node-free modules both use). The config's `model` must name a Worker provider, or `agent.ts` sets a `provider` instance. `subagents/`, `schedules/`, `channels/`, `memory/`, `projectInstructions` and an unsupported provider are rejected with `LOUSHO_DEPLOY_FAILED`, naming what to remove. In a Worker build, an agent file's `@lousho/build-ai-agent` import resolves to a Worker-safe subset (`defineTool`, `defineSkill`, the Worker providers, `fromAiSdk`, errors,...); another name fails the build with the list. The leak check now names the file that imported a Node builtin (`"node:fs" imported by my-agent/tools/files.ts`) instead of failing with esbuild's "Could not resolve". Tools run in the Worker's isolate with its rights: see docs/cloudflare-workers.md#build-and-deploy. +- Cloudflare Worker target builds agent directories (config, instructions, TypeScript tools, skills): `npx @linuxdevil/lousho build./my-agent --target=cloudflare-worker`. The build reads the directory on Node and generates `agent.module.ts` (static imports of `tools/*` and of an `agent.ts` config; `instructions.md`, a JSON/YAML config and the skills embedded as JSON), and the Worker builds the agent with the rules of `resolveAgentDir()` (the config check and the tool-export rules now live in Node-free modules both use). The config's `model` must name a Worker provider, or `agent.ts` sets a `provider` instance. `subagents/`, `schedules/`, `channels/`, `memory/`, `projectInstructions` and an unsupported provider are rejected with `LOUSHO_DEPLOY_FAILED`, naming what to remove. In a Worker build, an agent file's `@lousho/build-ai-agent` import resolves to a Worker-safe subset (`defineTool`, `defineSkill`, the Worker providers, `fromAiSdk`, errors,...); another name fails the build with the list. The leak check now names the file that imported a Node builtin (`"node:fs" imported by my-agent/tools/files.ts`) instead of failing with esbuild's "Could not resolve". Tools run in the Worker's isolate with its rights: see docs/cloudflare-workers.md#build-and-deploy. - Workspace rewind: `createFsTools(fs, { checkpoints })` with a new `WorkspaceCheckpoints` backs up every file `write_file` and `edit_file` change (content and, where the provider has the new optional `FsProvider.getMode` / `chmod`, permission bits), grouped by session and turn. `checkpoints.list({ sessionId })` shows the changed paths per turn; `checkpoints.rewind(toTurn, { sessionId, dryRun, force })` restores every file changed in that turn or later, deletes files the agent created, and skips files changed since by something else (`'changed-since'`), files over `maxFileBytes` (`'too-large'`) and paths that are no longer files. Every path is checked before anything is written, and restores go through the provider, so `NodeWorkspace` keeps them inside its root. Retention: the newest `maxTurns` turns per session (default 20), the old content only for a turn's first write of a path, rewound turns dropped, `clear({ sessionId })`. Stores: `MemoryWorkspaceCheckpointStore` (default) and `FileWorkspaceCheckpointStore(dir)` (one JSON file per session, atomic writes). `NodeWorkspace` gains `getMode()` and `chmod()`. Without `checkpoints` the tools behave as before. See [Workspace tools](/workspace-tools#the-tools). - Route auth and principals: a new subpath, `@lousho/build-ai-agent/auth`, with `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` and `routeAuth()`. A route takes an ordered list of entries: the first one that returns a `Principal` accepts the request, `null` skips to the next, `AuthError(401 | 403)` stops; when every entry skips the answer is a generic `401` with one `WWW-Authenticate` header per challenge (`Bearer`, `Basic realm="..."`), and the body never says which check failed. JWTs are verified with Web Crypto only (no new dependency, no `node:` import, so the helpers run on Workers and edge routes): `alg: none`, algorithms outside the configured list, a `crit` header and algorithm confusion (an HS token checked against a public key) are refused; `exp` is required, `nbf` / `iat` honored, `iss` / `aud` checked, with a clock tolerance of 60 s (at most 300); JWKS key sets are cached for 10 minutes and refetched at most once per 30 s, and a key-set URL is never taken from a token. `oidc()` uses the configured issuer's discovery document and requires it to name the same issuer. `basic()` and `apiToken()` compare in constant time. `createRouteHandler({ auth })` and `createDeployedServer(agent, { auth })` take the list (a token string and a boolean function work as before); the node server appends `apiToken(LOUSHO_API_TOKEN)` when that variable is set; an agent directory's `auth.ts` is bundled by `lousho build --target=node-server` / `docker` and guards the built server (`resolveAgentDir()` returns it as `auth`, the manifest has `auth: true`). The accepted principal reaches the run: `send()` / `stream()` / session turns take `principal`, and `RunConfigContext` (the `model` / `instructions` / `tools` functions) and `MemoryScopeContext` (memory scopes) carry it; Slack and Discord channels set the sender as the principal. `Principal` is also exported from the root. Bad helper options throw the new error code `LOUSHO_AUTH_CONFIG_INVALID` when the helper is created. `createRouteHandler()` without `auth` logs one warning when `NODE_ENV` is `production`. Route auth does not check session ownership, the principal does not reach tools or approval policies yet, and the Cloudflare Worker target keeps the `LOUSHO_API_TOKEN` token only. See [Route auth and principals](/auth). - OAuth token storage: `AgentStore` gains a fourth optional part, `tokens` (`OAuthTokenStore`: `get` / `set` / `delete` / `list` per provider and credential owner, single-use pending sign-ins with `putPending` / `takePending`, and `getClient` / `setClient` for dynamically registered clients). Owners are `{ owner: 'app' }` or `{ owner: 'user', principalId, issuer? }`; `tokenStoreKey(provider, owner)` is the stable, percent-encoded record key (`|app`, `|user||`). `memoryStore()` keeps tokens in process memory; `fileStore(dir)`, `SqliteStore` and `KVStore` encrypt every record with AES-256-GCM (Web Crypto, a fresh 12-byte IV per write, the record key as additional authenticated data) under a 32-byte key the application supplies as `tokenKey` or `LOUSHO_TOKEN_KEY`; there is no default key. Several keys (newest first, or comma-separated in the variable) rotate: writes use the first, reads try each. New error codes `LOUSHO_TOKEN_KEY_MISSING` (first write, or a read of an existing record, without a key) and `LOUSHO_TOKEN_DECRYPT_FAILED` (wrong key or a changed record; names the provider, never the token). `list()` returns metadata only. `generateTokenKey()` makes a key. SQLite: schema migration 4 adds the `oauth_tokens` and `oauth_pending` tables, and `prune()` also deletes expired pending sign-ins (its result gains `oauthPending`). KV: keys under `oauth/`, pending sign-ins written with an `expirationTtl`; `KVBinding` gains an optional `list()` (used only by `tokens.list()`), and `KVListOptions` / `KVListResult` are exported from `/kv`. The generated Cloudflare Worker passes its `LOUSHO_TOKEN_KEY` secret to `KVStore`. Nothing signs in yet; the sign-in flow follows. New page [OAuth](/oauth); new `## OAuth` section at the end of docs/errors.md. @@ -562,7 +676,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro - `web_fetch` built-in tool: `webFetchTool` / `createWebFetchTool(options)`, and `web-fetch` in spec files. It `GET`s one public web page and returns `{ url, finalUrl, status, contentType, content, truncated }`: HTML converted to text by a small built-in converter (scripts and styles dropped, link URLs kept, entities decoded), JSON and `text/*` as sent, a note for other types; a 4xx/5xx is returned with its body. Loopback, private and link-local destinations are refused on every hop with the connection pinned to the checked address; redirects (10), bytes read (2 MiB), characters returned (50 000) and time (30 s) are capped; `allowedHosts` / `blockedHosts` are checked before DNS and `allowPrivate` exempts named hosts. Node only. See docs/tools.md#built-in-tools. - `src/security/privateAddress.ts` (internal): `isPrivateAddress()` and `pinnedLookup()`, shared by the credential broker, `http_request` and `web_fetch`. The credential broker uses it instead of its own list, so it also refuses `192.0.0.0/24`, `198.18.0.0/15`, NAT64 and 6to4 addresses. - Slack and Discord: a pending `ask_question` survives a restart. Given durable stores for sessions, checkpoints and approvals, the next message in the Slack thread (or the next `/ask` in the Discord channel) after a restart is still the answer: the turn continues, the reply is posted, and the question, the answer and the reply are appended to the session transcript (before, the answer became a new turn and the question was orphaned). A message in a conversation that waits on a tool approval still does not decide it. New `ChannelContext.pendingQuestion(sessionKey)` for custom channels: the id of the `ask_question` that key's session waits on, also one asked before a restart (it binds the approval to the session, so the continuation is recorded); handed out once until that answer has run. A channel's answer and click continuations now run one at a time with the session's turns. Supporting additions: `Checkpoint.approvalKind`, `PendingTurn.approvalKind` (`session.pending()`) and `SessionAwaitingApprovalError.approvalKind` are `'question'` for a turn paused on an `ask_question`. Slack thread replies that answer a pending question no longer need a saved transcript first (a first turn paused on a question with checkpoints has none yet). See docs/channels.md. -- Local traces and `lousho traces`: `createAgent({ exporter, captureContent })` traces every run of the agent (`send()`, `stream()`, session turns, `agent.resume()` and runs continued by `agent.approvals.resolve()`; sub-agents join the lead's trace). The new subpath `@lousho/build-ai-agent/traces` exports `fileTraceExporter({ dir })`, which writes each run as JSON Lines to `//.jsonl` (default `.lousho/traces`; one finished span per line; a write error warns once and never fails the run), and `listTraces()` / `readTrace()` to read them. `npx lousho traces [--dir D] [--limit N] [--json]` lists recent runs with duration, model and tool calls, tokens and cost; `npx lousho traces [--json] [--content]` prints one run as a span tree with duration bars. Projects made by `lousho init` now ignore `.lousho/`. See docs/observability.md#local-traces. +- Local traces and `lousho traces`: `createAgent({ exporter, captureContent })` traces every run of the agent (`send()`, `stream()`, session turns, `agent.resume()` and runs continued by `agent.approvals.resolve()`; sub-agents join the lead's trace). The new subpath `@lousho/build-ai-agent/traces` exports `fileTraceExporter({ dir })`, which writes each run as JSON Lines to `//.jsonl` (default `.lousho/traces`; one finished span per line; a write error warns once and never fails the run), and `listTraces()` / `readTrace()` to read them. `npx @linuxdevil/lousho traces [--dir D] [--limit N] [--json]` lists recent runs with duration, model and tool calls, tokens and cost; `npx @linuxdevil/lousho traces [--json] [--content]` prints one run as a span tree with duration bars. Projects made by `lousho init` now ignore `.lousho/`. See docs/observability.md#local-traces. - `openApiTools(document, options)`: an OpenAPI 3.0 / 3.1 document (object, JSON or YAML string, or https URL) becomes one typed tool per operation, exported from the root and `./tools`. Names come from `operationId` (or `_`); the input has path, query, header and cookie parameters plus an `application/json` `body`, with `$ref`s resolved; every HTTP response is returned as `{ status, statusText, body }`. GET, HEAD and OPTIONS run and other methods ask for approval by default (`approval`), with read-only / destructive annotations. Options: `baseUrl`, `include`, `exclude`, `prefix`, `headers`, `bearerToken`, `providedArguments` (removed from the model's schema), `timeoutMs`, `maxResponseChars`, `fetch`. The base URL must be https (http only for loopback), credentials never reach events or the transcript, and redirects are followed only on the base origin. Swagger 2.0, multipart bodies and remote `$ref`s are refused. `jsonSchemaToZod(schema, root?)` takes an optional root for `$ref` resolution. New page docs/openapi-tools.md. - Built-in OpenAI, Anthropic and OpenRouter providers send PDF file parts on `ai` 6 and 7 (Anthropic also `text/plain`); `ai` 4 and Ollama keep the text note. A provider subclass opts more media types in with `fileMediaTypes()` (`acceptsFileParts = true` still sends every type). The text-note warning is now once per provider and media type and names the type and the `ai` major. Added `npm run test:live` (`vitest.live.config.ts`) for `*.live.test.ts` files, which the default suite excludes. See docs/providers.md#multimodal-input. - Principals in tools, approval policies and permissions: the run's principal reaches a tool's `execute(args, ctx)` as `ctx.principal`, `needsApproval` functions (`ApprovalCheckContext.principal`), permission rules' `when(args, ctx)` (`PermissionContext.principal`), `preToolCall` / `postToolCall` hooks (`ToolCallHookContext.principal`), `onPermissionDecision`'s new second argument (`PermissionAuditContext`), pending approvals (`PendingApproval.principal`, seen by `approve` and by a channel's `approvers` function as `request.principal`) and in-process sub-agents (`task`, background tasks, `createDelegateTool()`; `remoteAgent()` does not forward it). New `ExecuteOptions.principal`, set by `createAgent()` for `send()`, `stream()`, session turns and channel turns. It is saved in checkpoints (`Checkpoint.principal`) and approval snapshots (`ExecutionSnapshot.principal`), so a static or dynamic run resumed after an approval or a crash acts for the same caller. `agent.approvals.resolve()` / `answer()` / `streamResolve()` / `streamAnswer()` take `{ principal }` (new `ResolveApprovalOptions`) for who decides, and the approved tool sees it as `ctx.approval.by`; `resumeAfterApproval()` takes it as `executeOptions.approver`. The approvals route (`POST /chat/:sessionId/approvals/:id`) passes the caller route auth accepted, and `mountChannels()` the clicking user (`{ id, type: 'user', authenticator: }`) or, for a question answered by a message, its sender. `agent.fork()` keeps the source run's principal; `session.fork()` copies only the conversation. The principal is not added to events, spans or trace files. See [Principals in tools and approvals](/auth#principals-in-tools-and-approvals). @@ -571,7 +685,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro - `SlackTriggerAdapter`, `CronTriggerAdapter` and `WebhookTriggerAdapter` (`@lousho/build-ai-agent/triggers`) are deprecated and will be removed in a future major version. Use `slackChannel()` for Slack (sessions per thread, approval buttons), `defineSchedule()` with `startSchedules()` for cron (or `schedules/` in an agent directory, or cron triggers in a spec file), and `webhookChannel()` for webhooks, each mounted with `mountChannels()`. `verifySlackSignature()`, the `WebhookAuth` helpers, `parseCronExpression()`, `TriggerAdapter` and `TriggerRegistry` are not deprecated. Their options types (`SlackTriggerAdapterOptions`, `CronTriggerAdapterOptions` with `CronIntervalOptions` and `CronExpressionOptions`, `WebhookTriggerAdapterOptions`) and `WebhookTriggerHandle` carry `@deprecated` too. Nothing changes at run time. See docs/triggers.md. ### Fixed -- `lousho` CLI surface: `lousho dev`/`lousho chat --traces` now writes traces for a `.ts`/`.js` module that exports an already-built `createAgent()` agent - the module's export could not take the flag's exporter after the fact, so nothing was written. `createAgent()` remembers the config it was built with (readable via the internal `createAgentConfigOf()`), and the loader rebuilds the agent with the flag's exporter (the module's own `exporter` is replaced while the flag is set; a module exporting an agent not made by `createAgent()` prints a warning and keeps its own). A spec path that does not exist (e.g. `lousho chat missing.yaml`) now fails with the coded `LOUSHO_SPEC_NOT_FOUND` instead of a raw ENOENT. `lousho add --dry-run` no longer fails when a target file already exists; it prints the file list with an "(exists - a real install needs --overwrite)" marker and exits 0 (the symlink checks still run). Top-level `lousho --help` lists the implemented flags again: `--no-schedules`/`--traces` on `dev` and `chat`, `eval`'s record/replay/drift/`--config` flags, `init`'s `--sdk-path`, and `build`'s positional form. +- `@linuxdevil/lousho` CLI surface: `lousho dev`/`lousho chat --traces` now writes traces for a `.ts`/`.js` module that exports an already-built `createAgent()` agent - the module's export could not take the flag's exporter after the fact, so nothing was written. `createAgent()` remembers the config it was built with (readable via the internal `createAgentConfigOf()`), and the loader rebuilds the agent with the flag's exporter (the module's own `exporter` is replaced while the flag is set; a module exporting an agent not made by `createAgent()` prints a warning and keeps its own). A spec path that does not exist (e.g. `lousho chat missing.yaml`) now fails with the coded `LOUSHO_SPEC_NOT_FOUND` instead of a raw ENOENT. `lousho add --dry-run` no longer fails when a target file already exists; it prints the file list with an "(exists - a real install needs --overwrite)" marker and exits 0 (the symlink checks still run). Top-level `lousho --help` lists the implemented flags again: `--no-schedules`/`--traces` on `dev` and `chat`, `eval`'s record/replay/drift/`--config` flags, `init`'s `--sdk-path`, and `build`'s positional form. - A cassette failure keeps its type through `agent.send()`: a replay mismatch (or a missing cassette raised by the provider-interception seam that `lousho eval` installs) was compacted into a generic `CompactedLLMProviderError`, losing the cassette path, the call number and the re-record hint. `CassetteMismatchError` is now an `SDKError` with code `LOUSHO_CASSETTE_INVALID` (docs/errors.md already claimed cassette errors carry it), and the run loop rethrows cassette errors untouched instead of compacting them, so `send()` rejects with the `CassetteMismatchError` itself (`.cassette`, `.callNumber`). `setProviderInterceptor()` and the `ProviderInterceptor` type are now exported from `@lousho/build-ai-agent/testing`, the public entry for the model-boundary seam docs/evals.md describes. - A tool call approved with `agent.approvals.resolve()` (or `resumeAfterApproval()`) now has its `execute_tool` span. It ran before the continued run started, outside any span, so OpenTelemetry and `fileTraceExporter()` showed the continued run's `invoke_agent` and `chat` spans but not the approved tool. The continued run's `invoke_agent` span now opens first and the approved call runs inside it: its `execute_tool` span is a child of it (a `run_code` call's inner calls and a sub-agent the tool starts are children of the tool span), with the same `captureContent` / `redactContent` rules as any tool span, and no span is made for a rejected call. A pause that happens again (sign-in) leaves the `invoke_agent` span with the tool span and no `chat`. - A tool that returns `undefined` (or an async generator that yields nothing) no longer writes a `tool` message with `content: undefined` into the transcript. Its result is now `null` everywhere: the transcript and the next model request carry `"null"`, and `tool.done`, approval resumes, `run_code` and flows see `null`. Providers, session stores and checkpoints get a valid string. @@ -665,7 +779,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro ### Added - Remote sub-agent approvals go through the lead run: a `remoteAgent()` task whose remote run pauses for a tool approval now pauses the lead run the way a local sub-agent does (it used to fail with `LOUSHO_SESSION_AWAITING_APPROVAL`): the lead's pending approval (`agent.approvals.list()`, channel buttons, the dev chat, ACP permission requests) has the remote tool's name and input and `subagentPath: []`, and a remote `ask_question` arrives as a question. Deciding it on the lead (`resolve`, `streamResolve`, `answer`) posts the decision to the remote `POST /chat/:sessionId/approvals/:id` and the continuation's final answer is the `task` result; a further pause pauses the lead again. The lead's approval snapshot stores the remote session id, the remote approval id, the `taskId` and the agent name (never the token), so a fresh lead process on the same store can decide it. Failures while deciding (401, other non-2xx including the remote's 404 for an approval no longer pending, network) are the `task` call's structured tool error with `LOUSHO_REMOTE_UNAUTHORIZED` / `LOUSHO_REMOTE_REQUEST_FAILED`. A remote agent with an `output` schema now returns its object as JSON with the footer (the V4.2 shape), read from `run.done`'s `object`. `RemoteSubagent.run()` takes `pausable` and `decision` (type `RemoteRunOptions`); the internal session client gains `resolveRemoteApproval()` and `SessionTurnSummary.object`. Only a lead run without an approval store keeps the old `LOUSHO_SESSION_AWAITING_APPROVAL` error. See docs/sub-agents.md#remote-approvals. - Structured output for sessions and sub-agents, typed for either zod major: `createAgent({ output })` and `ExecuteOptions.output` accept a zod 3 schema, a zod 4 schema (`zod/v4` on zod 3.25, or zod 4) or a Standard Schema that can produce JSON Schema, and `result.object` is inferred from each without a cast (`InferSchemaOutput`, as `defineTool`). `AgentSession` is generic (`AgentSession`): `agent.session().send()` and `.stream()` results carry the typed `object`. A sub-agent with its own `output` returns its validated object as JSON (then the `taskId` footer) as the `task` and `agent_await` result; its `output-invalid` finish is a structured tool error. Sub-agents do not inherit the lead's `output`; `remoteAgent()` returns text only. The exported spec schemas (`agentSpecSchema`, ...) are typed as `SpecSchema` / `SpecObjectSchema`, so the published `schema-*.d.ts` no longer depends on zod 3 generics (`skipLibCheck: false` projects on zod 4). The Ollama missing-peer note now says `ollama-ai-provider-v2` needs zod 4 (`npm install zod@^4.0.0`). See docs/structured-output.md. -- Publish readiness: `npm run pack-smoke` (scripts/pack-smoke.ts, a CI job) packs the SDK and `create-lousho-agent`, checks the tarball (no `.env`, tests or secret-looking strings; entry count and size caps), runs `npm publish --dry-run` for both (nothing is published), installs the tarballs plus peers from the registry into a fresh project and verifies ESM and CJS loads of every `exports` entry, a mock-model agent turn, the `lousho` bin (`--help`, `doctor`) and `tsc` with `moduleResolution` bundler and node16. +- Publish readiness: `npm run pack-smoke` (scripts/pack-smoke.ts, a CI job) packs the SDK and `create-lousho-agent`, checks the tarball (no `.env`, tests or secret-looking strings; entry count and size caps), runs `npm publish --dry-run` for both (nothing is published), installs the tarballs plus peers from the registry into a fresh project and verifies ESM and CJS loads of every `exports` entry, a mock-model agent turn, the `@linuxdevil/lousho` bin (`--help`, `doctor`) and `tsc` with `moduleResolution` bundler and node16. - One event system: `AgentEvent` listeners for callers who do not iterate a run. `createAgent({ onEvent: (event: AgentEvent) => void })` and the new `ExecuteOptions.onAgentEvent` (also taken by `AgentExecutor.stream()` and `resumeAfterApproval()`) are called synchronously with every `AgentEvent` of the run, on `send()` / `execute()` as on `stream()`, sub-agents' events included: the same events in the same order as the stream yields (a non-streamed run generates each model step whole, so its text is one `text.delta` per step). A run with a listener also reports hook events, permission decisions, budgets and guardrails to it when it is not streamed. A non-streamed `resumeAfterApproval()` with a listener now reports the decided call (`run.start`, `tool.start`, `tool.done`) like a streamed one. See docs/streaming.md#listening-without-iterating. - Reasoning: new `reasoning` option on `createAgent()`, `send()` / `stream()` (per call, overriding the agent's), `ExecuteOptions` and `GenerateOptions`: `'none' | 'minimal' | 'low' | 'medium' | 'high'` or `{ effort?, budgetTokens?, summary?: 'auto' | 'none', force? }` (types `ReasoningOption`, `ReasoningEffort`, `ReasoningSettings`). The built-in providers send it through `providerOptions` on `ai` 4.3 and 6/7: OpenAI `reasoningEffort` (+ `reasoningSummary`), Anthropic `thinking: { type: 'enabled', budgetTokens }` (effort table 1024 / 2048 / 8192 / 24576 tokens), Ollama `think` (`ollama-ai-provider-v2` only; ignored with one warning on `ai` 4), and OpenRouter's unified `reasoning` body field (`effort` or `max_tokens`). It is only sent to model families known to accept it (`force: true` overrides); `'none'` sends nothing. New events `reasoning.start`, `reasoning.delta` (`text`) and `reasoning.done` (`text`, `tokens?`) in the `AgentEvent` union, emitted inside the step before its first text or tool call, from `ai` v4 `reasoning` parts and v6/v7 `reasoning-start/delta/end`; `ExecutionResult.reasoning` holds the run's reasoning text and `GenerateResult.reasoning` a call's blocks (new type `ReasoningBlock`); new stream chunk types `reasoning-delta` / `reasoning-end` (`StreamChunk.reasoning`). Reasoning is not written to the transcript as text; an assistant tool-call turn keeps Anthropic's signed or redacted thinking blocks in the new `Message.reasoning`, which the Anthropic provider sends back unmodified (it survives checkpoints and resumes). The UI reducer keeps `UIMessage.reasoning`, `toUIMessageStream()` emits `reasoning-start` / `reasoning-delta` / `reasoning-end`, `lousho acp` sends `agent_thought_chunk`, `lousho chat` prints reasoning dimmed, and `chat` spans carry `lousho.usage.reasoning_tokens`. Cassettes record the reasoning chunks. See docs/reasoning.md. - Tools know their session: `ToolExecutionContext.sessionId` is now set whenever the run has one: `AgentExecutor.execute({ sessionId })`, `send({ sessionId })`, every `agent.session()` turn (the session id; a checkpointed turn's own `.turn-`, as hooks see it), approval resumes (the paused run's), and sub-agents (`/`). A session turn's run now carries the session id even without a checkpoint store (no checkpoint is written), so hooks and the `invoke_agent` span's conversation id see it too. @@ -696,7 +810,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro - Svelte store: new `@lousho/build-ai-agent/svelte` subpath with `loushoAgent(source, options?)`. It returns a Svelte store (`subscribe(run) => unsubscribe`, so `$agent` works in Svelte 4 and 5) whose value is the same state as the React hook and the Vue composable (`messages`, `status`, `pendingApproval`, `error`, `usage`, `lastEvent`), plus `send()`, `stop()`, `approve()`, `reject()`, `answer()` and `reset()`. The run in flight is aborted by `stop()` and when the last subscriber unsubscribes. The store contract is implemented by hand: the entry imports nothing from `svelte`, so there is no new peer dependency. See docs/svelte.md. - Per-run dynamic config: `createAgent()`'s `model`, `instructions` / `prompt` and `tools` each also take a function of the run, `(ctx) => value | Promise` (new types `PerRun` and `RunConfigContext` = `{ sessionId?, input, metadata? }`), resolved once at the start of each run and of each session turn, before the first model call. `fallbackModels`, `retry`, `projectInstructions`, memory, MCP tools, sub-agents, permissions, approvals and guardrails apply to the resolved values; a dynamic agent used as a sub-agent resolves with the task prompt as `input`. `session.send()` / `session.stream()` gain the `metadata` option (`send()` / `stream()` already had it, and now pass it to these functions too). A function that throws fails the run with the new `LOUSHO_CONFIG_RESOLVER_FAILED` (`field` names the option, `cause` is the error); a session keeps its transcript. A run paused for approval stores its `ctx` and resolved model in the approval snapshot (`agent.metadata.loushoRunConfig`): resuming it uses that model and re-resolves the tools with the same `ctx`; a crash resume from a checkpoint resolves with `input: []`. Static options behave and type as before. `SessionRunner` / `SessionStreamRunner` take an optional fourth `call` argument (new type `SessionTurnCall`). See docs/api-overview.md#dynamic-config. - Vue composable: new `@lousho/build-ai-agent/vue` subpath with `useLoushoAgent(source, options?)` for Vue 3. It takes the same sources and options as the React hook (either may be a `ref`, read when a turn starts) and returns `messages`, `status`, `pendingApproval`, `error`, `usage` and `lastEvent` as read-only refs, plus `send()`, `stop()`, `approve()`, `reject()`, `answer()` and a new `reset()` (aborts, forgets the in-process session, clears the chat). The run in flight is aborted by `stop()` and when the owning scope is disposed (`onScopeDispose`: component unmount, `effectScope().stop()`). `vue` (`^3`) is an optional peer dependency, loaded by no other entry. To share the logic, the reducer and the stream parser moved from `src/react/` to a framework-neutral `src/ui/`, together with the run logic extracted from the React hook (`createAgentRunner()`); the `./react` exports are unchanged, and the reducer gains an additive `ui.reset` action. See docs/vue.md. -- One flag parser for the `lousho` CLI: every command (`dev`, `chat`, `eval`, `build`, `mcp`, `doctor`, `studio`; `init` already did) parses its flags with `node:util` `parseArgs` in strict mode through `src/cli/args.ts`. `--flag value` and `--flag=value` work everywhere, `--` ends the flags, `-h` / `--help` prints the usage and exits 0, and an unknown flag, a flag without its value or an extra argument fails with `LOUSHO_CONFIG_INVALID` and the command's usage as the hint. **Behaviour change (migration):** `dev`, `studio`, `mcp`, `build` and `doctor` used to ignore unknown flags, and a flag missing its value could take the next flag or a prefix match (`--portx 1` read as `--port`); they now fail with exit 1 and the usage line, so remove typos from scripts. A value that starts with `-` must be written `--flag=-x` (a bare `--flag -x` is a missing value); `--port` must be an integer from 0 to 65535 for `dev`, `studio` and `mcp`; `dev`, `mcp` and `doctor` reject a second path; `studio --prod --dev` is rejected instead of preferring `--prod`. `lousho dev` and `lousho studio` parsing moved from `bin/lousho.js` into `src/cli/dev.ts` / `src/cli/studio.ts` (`parseDevArgs`, `runDev`, `parseStudioArgs`, `runStudio`). See docs/cli.md. +- One flag parser for the `@linuxdevil/lousho` CLI: every command (`dev`, `chat`, `eval`, `build`, `mcp`, `doctor`, `studio`; `init` already did) parses its flags with `node:util` `parseArgs` in strict mode through `src/cli/args.ts`. `--flag value` and `--flag=value` work everywhere, `--` ends the flags, `-h` / `--help` prints the usage and exits 0, and an unknown flag, a flag without its value or an extra argument fails with `LOUSHO_CONFIG_INVALID` and the command's usage as the hint. **Behaviour change (migration):** `dev`, `studio`, `mcp`, `build` and `doctor` used to ignore unknown flags, and a flag missing its value could take the next flag or a prefix match (`--portx 1` read as `--port`); they now fail with exit 1 and the usage line, so remove typos from scripts. A value that starts with `-` must be written `--flag=-x` (a bare `--flag -x` is a missing value); `--port` must be an integer from 0 to 65535 for `dev`, `studio` and `mcp`; `dev`, `mcp` and `doctor` reject a second path; `studio --prod --dev` is rejected instead of preferring `--prod`. `lousho dev` and `lousho studio` parsing moved from `bin/lousho.js` into `src/cli/dev.ts` / `src/cli/studio.ts` (`parseDevArgs`, `runDev`, `parseStudioArgs`, `runStudio`). See docs/cli.md. - `sqliteMemory(store, { maxItems? })` (exported from `@lousho/build-ai-agent/sqlite`) keeps `defineMemory` slots in the agent's `SqliteStore` file: a new `memory_items` table (migration 3, one JSON array per scope key, with the same serialization, `maxItems` and query rules as `fileMemory`). An existing database gains the table on open and keeps its sessions and checkpoints. The memory provider contract tests now run against the in-memory, file and SQLite providers. See docs/memory.md#sqlite. - Slack channel: `slackChannel({ signingSecret, botToken, name?, fetch? })` (new export, with types `SlackChannelOptions`, `SlackChannelEvent`, `SlackThread`) serves a Slack app's Events API and interactivity requests through `mountChannels()`. It verifies the `v0` request signature with Web Crypto (no `node:*` import; the timestamp and header checks are shared with `verifySlackSignature()`), answers the `url_verification` challenge, and acknowledges every request before the turn runs. Each Slack thread is one session (team + channel + thread root `ts`): a mention starts or continues it, and later messages in a thread the bot was mentioned in continue it. Retries (`X-Slack-Retry-Num`) and bot messages are skipped. Replies go to the thread with `chat.postMessage` over `fetch`; a tool approval is posted as Approve / Deny buttons whose click resumes the session, and an `ask_question` as text answered by the next thread message. The channel contract gains two additive hooks for this: `parse(req, respond)` may call `respond` to answer the request before the turn runs, and may return `{ decision }` (new type `ChannelDecision`; `ChannelApprovalDecision` now lives in `defineChannel.ts`, same public export) to resolve a pause instead of starting a turn. See docs/channels.md#slack. - Remote sub-agents: `remoteAgent({ url, auth?, name?, description?, headers?, fetch? })` (new export, with types `RemoteAgentOptions` and `RemoteSubagent`) makes a deployed agent usable wherever a local sub-agent is: in `createAgent({ subagents })` records and `SubagentCatalog.resolve()`, delegated to by the `task` tool and by `background: true` tasks. Each task opens a fresh session on the remote agent over `POST /chat` (bearer `auth`: a string or a function, called per task), reads the SSE stream to the end and returns the remote agent's final text; the lead run's abort signal aborts the request. Failures (network, 401 or other non-2xx, malformed stream, remote run error) are structured tool errors with the new code `LOUSHO_REMOTE_AGENT_FAILED`, and the token never appears in errors or events; a remote run that pauses for approval fails with `LOUSHO_SESSION_AWAITING_APPROVAL` naming the remote session (proxying approvals is a follow-up). Worker-safe (`fetch` only). See docs/sub-agents.md#remote-sub-agents. @@ -783,7 +897,7 @@ See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 thro - README revamped into a short front page; the details it dropped moved to `docs/` (new pages: tools, approvals, providers, cli, flows, guardrails, utilities). ### Renamed -- The project is now **lousho** (it was `loushy`), before the first npm release, so nothing was ever published under the old name. Everything that carried the name changed with it, and this changelog uses the new names throughout, including in older entries: the package `@lousho/build-ai-agent` (was `@loushy/build-ai-agent`), the `lousho` CLI (was `loushy`), `create-lousho-agent` (was `create-loushy-agent`), the exports `useLoushoAgent`, `loushoAgent`, `LoushoAgentSource`, `LoushoUIMessageChunk` and the other `Lousho*` types, every `LOUSHO_*` error code and environment variable (`LOUSHO_MODEL`, `LOUSHO_API_TOKEN`, `LOUSHO_STORE`, ...), the `.lousho/` directory, the `lousho.*` span attributes and the `data-lousho-approval` stream part. Migration for a checkout that used the old name: replace `loushy` with `lousho` (keeping the case) in imports, scripts, environment variables and config, and rename an existing `.loushy/` directory to `.lousho/`. +- The project is now **lousho** (it was `loushy`), before the first npm release, so nothing was ever published under the old name. Everything that carried the name changed with it, and this changelog uses the new names throughout, including in older entries: the package `@lousho/build-ai-agent` (was `@loushy/build-ai-agent`), the `@linuxdevil/lousho` CLI (was `loushy`), `create-lousho-agent` (was `create-loushy-agent`), the exports `useLoushoAgent`, `loushoAgent`, `LoushoAgentSource`, `LoushoUIMessageChunk` and the other `Lousho*` types, every `LOUSHO_*` error code and environment variable (`LOUSHO_MODEL`, `LOUSHO_API_TOKEN`, `LOUSHO_STORE`, ...), the `.lousho/` directory, the `lousho.*` span attributes and the `data-lousho-approval` stream part. Migration for a checkout that used the old name: replace `loushy` with `@linuxdevil/lousho` (keeping the case) in imports, scripts, environment variables and config, and rename an existing `.loushy/` directory to `.lousho/`. ### Published - `@lousho/build-ai-agent` and `create-lousho-agent` are on npm. The README and the installation, quick start, CLI and Agent Forge docs no longer say the package is unpublished; "Installing before the first release" is now "Installing from a local build" (docs/installation.md#installing-from-a-local-build) and covers trying an unreleased commit. The hint printed after a failed `lousho init` install no longer mentions a 404; it points to `--sdk-path`. The docs site moved to https://lousho.com. diff --git a/channels.mdx b/channels.mdx index fa3a26e..f69d67b 100644 --- a/channels.mdx +++ b/channels.mdx @@ -46,7 +46,7 @@ const channels = mountFetchChannels(agent, [slack], { waitUntil: (turn) => void ```ts import * as http from 'node:http'; -import { createAgent, createMockProvider, httpChannel, mountChannels } from 'lousho'; +import { createAgent, createMockProvider, httpChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider: createMockProvider() }); const channels = mountChannels(agent, [ @@ -121,7 +121,7 @@ Discord, Telegram, Teams, GitHub) keeps its own answer and tells the user the request failed in the conversation. ```ts -import { createAgent, defineChannel, mountChannels } from 'lousho'; +import { createAgent, defineChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You answer text messages.', provider }); @@ -205,7 +205,7 @@ dropped silently once the surface was acknowledged. `resolveApproval()` throws `LOUSHO_APPROVAL_CONFLICT` for an approval another request is deciding. ```ts -import { createAgent, defineChannel, mountChannels } from 'lousho'; +import { createAgent, defineChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider, askQuestion: true }); @@ -250,7 +250,7 @@ the verified request (the alerting system that sent the hook, an integration user); `body` is the parsed JSON body, `undefined` when it is not JSON. ```ts -import { createAgent, httpChannel, mountChannels, webhookChannel } from 'lousho'; +import { createAgent, httpChannel, mountChannels, webhookChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are helpful.', provider }); @@ -343,7 +343,7 @@ Set up the app at [api.slack.com/apps](https://api.slack.com/apps): ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, slackChannel } from 'lousho'; +import { createAgent, mountChannels, slackChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Slack bot.', provider }); @@ -418,7 +418,7 @@ curl -X POST "https://discord.com/api/v10/applications/$APP_ID/guilds/$GUILD_ID/ ```ts import * as http from 'node:http'; -import { createAgent, discordChannel, mountChannels } from 'lousho'; +import { createAgent, discordChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Discord bot.', provider }); @@ -502,7 +502,7 @@ curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" -H "Co ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, telegramChannel } from 'lousho'; +import { createAgent, mountChannels, telegramChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You are a helpful Telegram bot.', provider }); @@ -554,7 +554,7 @@ so it also runs on Workers. them somewhere shared and durable, e.g. Redis `SET NX EX`: ```ts - import { githubChannel, type DeliveryStore } from 'lousho'; + import { githubChannel, type DeliveryStore } from '@linuxdevil/lousho'; const seenIds = new Set(); // stands in for Redis or a database table const deliveries: DeliveryStore = { seen: (id) => seenIds.has(id) || (seenIds.add(id), false) }; @@ -655,7 +655,7 @@ agent from answering its own comments. ```ts import * as http from 'node:http'; -import { createAgent, githubChannel, mountChannels } from 'lousho'; +import { createAgent, githubChannel, mountChannels } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You answer questions about this repository.', provider }); @@ -751,7 +751,7 @@ Set up the bot: ```ts import * as http from 'node:http'; -import { createAgent, mountChannels, teamsChannel } from 'lousho'; +import { createAgent, mountChannels, teamsChannel } from '@linuxdevil/lousho'; const agent = createAgent({ instructions: 'You help the team with questions.', provider }); diff --git a/claude-projects.mdx b/claude-projects.mdx index a2eaad5..57cdc98 100644 --- a/claude-projects.mdx +++ b/claude-projects.mdx @@ -10,8 +10,8 @@ into `createAgent()`, so the same directory drives both harnesses. Nothing is copied or renamed; Lousho reads the files where they already are. ```ts -import { createAgent, claudeProject } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { createAgent, claudeProject } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'ok' }]); @@ -68,8 +68,8 @@ task-shaped notes, hooks can pause on `scratchpad` writes like any tool. ## Options ```ts -import { claudeProject } from 'lousho'; -import { mockModel } from 'lousho/testing'; +import { claudeProject } from '@linuxdevil/lousho'; +import { mockModel } from '@linuxdevil/lousho/testing'; const provider = mockModel([{ text: 'ok' }]); diff --git a/cli.mdx b/cli.mdx index d46bba0..39a5944 100644 --- a/cli.mdx +++ b/cli.mdx @@ -1,12 +1,12 @@ --- title: "CLI" --- -{/* Generated from docs/cli.md in LinuxDevil/lousho by scripts/sync-sdk-docs.mjs. Do not edit here. */} +{/* Generated from docs/cli.md in LinuxDevil/agent-sdk by scripts/sync-sdk-docs.mjs. Do not edit here. */} -Installing the package also installs the `lousho` command. It scaffolds +Installing the package also installs the `@linuxdevil/lousho` command. It scaffolds projects, checks your setup, runs an agent locally, serves it over MCP, runs -evals, shows saved traces, builds a deployable artifact and launches the Agent Forge dashboard. -Run it with `npx lousho ` inside a project that has the SDK installed. +evals, shows saved traces, builds a deployable artifact and opens Lousho Studio. +Run it with `npx @linuxdevil/lousho ` inside a project that has the SDK installed. | Command | What it does | Details | | ------- | ------------ | ------- | @@ -20,8 +20,11 @@ Run it with `npx lousho ` inside a project that has the SDK installed. | `lousho eval [globs...]` | Run `*.eval.ts` files under vitest; print a summary and write JUnit/JSON reports. | [Evals](/evals#lousho-eval) | | `lousho traces [id]` | List the recent runs saved by `fileTraceExporter()` (duration, model and tool calls, tokens, cost), or print one run as a span tree. | [Observability](/observability#local-traces) | | `lousho build --target= --agent=` | Build a deployable Node server, Docker image or Cloudflare Worker. | [Deployment](/deployment) | -| `lousho studio` | Launch Agent Forge, the visual dashboard, on one local port. | [Agent Forge](/agent-forge) | -| `lousho improve --check ` | Experimental. Propose edits to an agent's instructions and skills from failed runs in your local observe database. Prints a review card and applies nothing. | [Below](#lousho-improve) | +| `lousho studio` | Open Lousho Studio (the separate `@linuxdevil/studio` package) on this project. | [Lousho Studio](/agent-forge) | +| `lousho bridge` | Speak Studio's bridge protocol on stdio; started by Studio, not run by hand. | [Below](#lousho-bridge) | +| `lousho workspace ` | Create, inspect, validate and run a workspace from a folder or a running service. | [Workspaces](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/workspaces.md) | +| `lousho observe ` | Run the local collector every agent reports to when `LOUSHO_OBSERVE` is set: `start`, `status`, `stop`, `rebuild-rollups`, `prune`, `export`, `failures`, `score`, `token`. | [Below](#lousho-observe) | +| `lousho improve --check ` | Experimental. Run one self-healing cycle on an agent directory: propose edits to its instructions and skills from failed runs in your local observe database, gate them, replay them on fresh runs, and apply (auto mode, the default) or queue (manual mode) one that passes. `--watch` keeps it running; `pause`, `status`, `rollback`, `approve` and the other subcommands manage it. `--dry-run` prints the review card and applies nothing. | [Below](#lousho-improve) | Every command that takes a `` reads an agent spec file (`.yaml`, `.yml` or `.json`, see [Configuration](/configuration#agent-spec-files-agentspec)). @@ -35,14 +38,17 @@ lousho chat [--model provider/model] [- lousho acp [--model provider/model] lousho add [--registry ] [--dir ] [--yes] [--allow ] [--overwrite] [--dry-run] (or --list) lousho build --target= [--out=] (or --agent=) -lousho studio [--port N] [--host H] [--prod|--dev] +lousho studio [--port N] [--host H] +lousho bridge +lousho workspace [args] [--source | --service ] lousho mcp [--http --port N --host H] lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping] lousho eval [globs...] [--tag t] [--junit path] [--json path] [--strict] [--judge] [--record | --replay | --drift [--drift-usage]] [--url [--token ]] [--config vitest.config.ts] [--timeout ] lousho traces [traceId|prefix] [--dir D] [--limit N] [--json] [--content] lousho observe [--port N] [--db path] [--host H] [--require-token] [--project X] [--webhook URL] [--retention-days N] [--session-idle-minutes N] [--older-than d] [--since d|h|m] [--agent X] [--out file] lousho prompt |list|get |label