micro-models-agent 2.35.1 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,67 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.37.0] - 2026-10-09
8
+
9
+ `mma serve` now describes its own API: a live OpenAPI 3.1 document plus a
10
+ human-readable contract page, so an integrator learns the endpoints from the
11
+ running service instead of from the repository. Auto-update reached `mma web`,
12
+ where it had never run, and the one dead updater flag is gone. README refreshed
13
+ across the board — features, tools, modules, CLI/REPL surfaces and config keys
14
+ had drifted behind the code.
15
+
16
+ ### Added
17
+
18
+ - **Auto-update in `mma web`** (`src/modules/updater/factory.ts`, `cli/web-command.ts`, `webui/assets/js/loader.ts`, `js/footer.ts`): the web UI used to skip the updater entirely, so the four `updater.*` toggles in Settings → Advanced were dead. `createUpdater()` is now the single construction point for both surfaces; web starts the same fire-and-forget npm check and reports through a new SSE `update` event (`phase: available|installed|failed`), which makes **MMART react** — a new sparkly `update` face state that settles back to idle after a few seconds. The persistent half is `HostStateSnapshot.updateAvailable`, rendered as a green `↑ <latest>` chip in the footer next to the installed `mma <version>`, so the notice survives a reconnect and a freshly opened tab. The check still never blocks the server bind, and the checkout safety guard (`isDevEntryPath`) still refuses to `npm install -g` from a dev tree.
19
+ - **`exitOnComplete` no longer disables the updater** (`cli/main.ts`): that flag exists to decide who owns stdin, but the updater never touches stdin (a `fetch` and an `execFile`), so gating it on that flag was a false coupling.
20
+ - **Self-describing service contract** (`src/modules/service/openapi/`, `api/docs-api.ts`, `http/routes.ts`, `cli/service-command.ts`): `GET /v1/openapi.json` serves an OpenAPI 3.1 document of Agent Service API v1 (Swagger UI, Redoc, Postman/Insomnia, client generation), and `GET /v1/docs` serves the same contract as a self-contained HTML page — quickstart cURL, endpoint table with response codes, SSE events and error types. Both URLs are printed on `mma serve` startup (`cli.serve_docs`, en+ru) and both are exempt from the bearer check like `/v1/health` and `/v1/ready`: they describe the public API and expose no tenant data. The page is offline-first (no CDN), with an auth header in the quickstart only when a token is actually required. Single source of truth: the `OPERATIONS` table feeds the OpenAPI document, the docs page, and `tests/service/openapi.test.ts`, whose guard fails when a route exists without a description (or vice versa). Root `security: [{bearerAuth: []}]` appears only when a token is configured; `info.version` is `1.0.0+mma-<runtime version>`, so the spec can never drift from the build.
21
+ - **Spec ↔ reality conformance suite** (`tests/service/contract.test.ts`, `tests/service/contract-helpers.ts`): a hand-written spec rots silently, so every response the service returns is now checked against it in both directions — declared status codes (happy, error, queue-full, cancel-terminal, validation), payload shape (no undocumented fields, all required fields present, valid enums), declared response headers (`Location`/`Retry-After` on the `202` submit), content types, the auth matrix per operation (protected routes reject a missing/wrong token and accept the right one; open routes stay open), RFC 9457 error bodies, SSE frames against `EventEnvelope`, and the webhook body against `JobObject`. A guard also scans the runtime sources and fails if an emitted event type is missing from the enum, or a declared type is never emitted.
22
+
23
+ ### Fixed
24
+
25
+ - **Oversized request bodies no longer kill the service** (`src/modules/service/http/respond.ts`, `api/sessions-api.ts`, `api/tasks-api.ts`): every `mma serve` handler read its JSON body without a guard, so a body over the 1 MiB cap produced an unhandled rejection — the integration got a socket error instead of a status code, and under Node the process could go down on an ordinary bad request. Bodies are now read through `readJsonBodyOrProblem`, which answers `413` (or `400` on a broken stream) as RFC 9457 Problem Details and destroys the socket only after the response is flushed — the same approach `mma web` already used.
26
+ - **Unhandled handler errors return `500 internal`** (`src/modules/service/server.ts`): the request dispatcher now wraps the route table, so any escaping exception becomes a Problem Details response and a warning log instead of a dropped connection. A long-running service must not die from one malformed request.
27
+ - **Webhook signature documented as conditional** (`src/modules/service/openapi/schemas.ts`, `docs/serve-mode.md`, `docs/agents/service-api.md`): `X-Mma-Signature` is sent only when `service.webhookSecret` is configured — an unsigned callback was documented as signed, which would have had integrators trusting unsigned payloads. The docs and the schema description now say so explicitly.
28
+ - **Dead contract entries removed**: the `error` event type was declared in `EventEnvelope.type` but never emitted, and `POST /v1/sessions/:id/tasks` returned `400` on an empty `input` without declaring it. Both are now guarded by tests (see below), and the missing `400`/`413` responses are declared.
29
+
30
+ ### Changed
31
+
32
+ - **`updater.autoInstall` notice now says how to enable it** (`i18n` `updater.auto_install_hint`, en+ru): the default is off, so an "update available" line without a next step read like a dead end. The message now points at `/config updater.autoInstall true` alongside the manual command.
33
+ - **Docs:** the Auto-Update section of `docs/agents/modules.md` described `checkOnStart` and `autoInstall` as defaulting to `true` — both have been `false` since 1.x — and claimed the check only ran in the terminal. Now accurate, including the config file location (`~/.mma/updater.json`), the web surface, and the dev guard.
34
+
35
+ ### Removed
36
+
37
+ - **`updater.checkOnStart`** (`config/types.ts`, `config/defaults.ts`, `config/registry-extra.ts`, `cli/main.ts`, web settings, both dictionaries): the flag was declared in config, the registry, i18n and the web settings panel, and read nowhere — `runLoop()` checked unconditionally. `enabled: false` already means "never check" and `intervalMs: 0` already means "check once per launch", so both states it could express were already spelled. An existing `~/.mma/updater.json` keeps the field; it is simply ignored. Guards in `tests/webui-config-extra.test.ts` keep the panel and the dictionaries free of it.
38
+
39
+ ### Changed
40
+
41
+ - **README refreshed** (features, tools table, module/tool counts, CLI subcommands, REPL command table, web mode, config keys, architecture and project structure): removed the phantom `scope_request` tool, added `define_capability` / `process_wait`, `mma serve`, `mma plugins …`, `mma security …`, `/security`, `/lsp doctor`, `contextAuto`, `critic.enabled`, `service.port`, the `service` module, and pointed first-time users at the new `/v1/docs` + `/v1/openapi.json` endpoints.
42
+ - **Docs:** a discovery section in `docs/agents/service-api.md` and `docs/serve-mode.md`; auth column added to the endpoint table.
43
+
44
+ ## [2.36.0] - 2026-10-08
45
+
46
+ `mma web` fixes from a live two-tab session (a background session's stream
47
+ leaked into the active tab, Settings showed a stale value after slash-command
48
+ config writes, an interrupted turn reported success, the context probe compared
49
+ against the history budget instead of the window, open tabs were lost on
50
+ reload), a context selector that can take the window from the provider
51
+ (`contextAuto`, default on), and the agent's mascot: the loader face is now
52
+ named **MMART** (MMA + art), greets on click, and maps every tool and
53
+ agent-facing SSE event to a distinct face state.
54
+
55
+ ### Added
56
+
57
+ - **Context selector: `auto`** (`assets/js/settings/write.ts`, `config/registry-extra.ts`, `modules/webui/settings/context-auto.ts`, `core/agent.ts`, `core/bootstrap/provider-phase.ts`): the composer context dropdown gains an `auto` item (button reads `context: auto`) that takes the window from the provider for the active model. It is backed by the new `contextAuto` config key — default **on**, so the existing probe behaviour is unchanged; enabling it probes the provider immediately and applies the result (fail-open, the provider staying silent is announced with `web.selectors.auto_failed` instead of a plain "saved"). Writing a concrete `contextWindow` (selector, `/context`, `mma context`) pins the value: the probe no longer overrides it on start or on model/provider switch. Toggled from the selector menu, `/config contextAuto` or Settings → Context. Tests: `tests/config/context-auto.test.ts`, `tests/webui-config.test.ts`, guard in `tests/webui-assets.test.ts`.
58
+ - **MMART — the mascot** (`src/modules/webui/assets/js/loader.ts`, `assets/app.ts`, `assets/styles.css`, `src/i18n/{en,ru}.json`): the loader face gets a name (**MMART** = MMA + art). Clicking the face itself pops a localized greeting bubble (`web.loader.greet`) for 2.2s — a click anywhere else still only blinks it, so the greeting is not a page-wide easter egg; repeated clicks restart the timer. The bubble's font is floored (`max(12px, …)`), because scaling it by the face size produced unreadable ~4.6px text on the 22px face. The greeting text is injected via `setGreetResolver` from `app.ts` (so `loader.ts` stays import-safe under `bun test`) and is re-bound on locale switch. `TOOL_STATE` now gives every core tool a behavioural state instead of the generic `run`: `approve`/`question` → `ask`, `web_browse` → `web`, `load_skill`/`session_info` → `read`, `search_history` → `find`, `define_capability`/`remember` → `create`, `set_thinking` → `think`, `enable_tools` → `run`, `meet_done` → `done`. `loaderStateFor` covers the remaining agent-facing SSE (`retract` → error, `index` → find, `setup_done` → done); background broadcasts (`usage`/`state`/`config`/`files`/`sessions`/`plugins`/`log`) map to `idle` and are deliberately not wired to `faceEvent`, as they are not agent activity. Tests in `tests/webui-loader.test.ts`.
59
+
60
+ ### Fixed
61
+
62
+ - **Cross-tab stream bleed** (`src/modules/webui/assets/js/core/store/feed.ts`, `feed/stream.ts`, `feed/turn.ts`, `feed/core.ts`): streaming/turn/tool bookkeeping moved from one global cursor to a per-feed state (`streamStateFor(feed)`), keyed by the tab's feed element. With two sessions streaming at once, text, reasoning, the tool map (`pendingArgs`/`toolNodes`, where ids like `t1` collide across runners) and turn accumulators no longer bleed into the active tab. `finishMd`/`finishThink`/`flushRun`/`resetTurnText` are now session-scoped.
63
+ - **Stale Settings after slash-command config writes** (`src/modules/webui/chat/commands-shared.ts`, `settings/config-api.ts`, `assets/app.ts`): config writes now broadcast a `config` SSE event; the client re-reads `/api/config`, so the open Settings panel and composer selectors update immediately (`/verbose`, `/config`, `/reasoning`, …) instead of only after a reload.
64
+ - **Interrupted turn reported as "ok"** (`src/core/agent.ts`, `src/core/types.ts`, `src/modules/webui/session/run-agent.ts`): `AgentResult.interrupted` is set on `shutdown`/AbortError and forwarded in `turn_end`; the feed shows `stopped` (not `turn end · ok`) and success is no longer reported for an aborted turn.
65
+ - **Context probe label** (`src/modules/webui/assets/js/feed/sidebar.ts`): the probe's "configured" value now uses the true configured window (`h.contextWindow`) instead of the history budget (`h.contextLimit`, ~75%), which made a 1000K config read as "750.0K configured".
66
+ - **Tabs lost on reload** (`src/modules/webui/assets/js/session/tabs.ts`, `assets/app.ts`): the set of open session tabs and the active one are persisted to `localStorage` (`mma-open-tabs`) and restored on boot before `refresh()`; the URL `?session=` still wins as the active tab for deep links.
67
+
7
68
  ## [2.35.1] - 2026-10-08
8
69
 
9
70
  Closing the last tab in `mma web` now resets the session instead of leaving a
@@ -511,8 +572,6 @@ background-process leaks. Harness fixes only (no model/task-specific tuning).
511
572
  - **`resolve.ts`** — extracted npx→global-binary resolution (`findOnPath`, `resolveNpxLaunch`) shared by the client and the doctor.
512
573
  - **Docs**: `docs/agents/tools.md` "LSP in locked-down networks" (PATH binaries, `lsp.servers.<lang>` overrides, registry mirror via `env`, `MMA_LSP_OFFLINE`, classes table).
513
574
 
514
- ## [Unreleased]
515
-
516
575
  ## [2.17.2] - 2026-10-04
517
576
 
518
577
  ### Changed
package/README.md CHANGED
@@ -23,8 +23,8 @@ Most agents (Devin, Cursor, Copilot) are glued to cloud models or pricey APIs
23
23
 
24
24
  - **Tuned for 9B models** — designed around Qwen3.5-9B running on ordinary hardware
25
25
  - **Cloud-optional by design** — works fully offline against any OpenAI-compatible backend
26
- - **Thrives in a tight context** — smart sliding-window compaction keeps 32K models productive
27
- - **Catches hallucinations** — a 3-stage validation pipeline spots the lies small models love to tell
26
+ - **Thrives in a tight context** — calibrated compaction (v2 policy) keeps 32K models productive, with a tool ledger so the agent stops re-reading what it already read
27
+ - **Catches hallucinations** — a 3-stage validation pipeline spots the lies small models love to tell, plus artifact/web/file claims and an in-loop critic
28
28
  - **Agent-Level MoE** — hierarchical task decomposition lets small models ship big multi-step work
29
29
 
30
30
  ---
@@ -34,34 +34,37 @@ Most agents (Devin, Cursor, Copilot) are glued to cloud models or pricey APIs
34
34
  **The agent loop**
35
35
 
36
36
  - **6-state agent loop** (`INIT → THINK → ACT → OBSERVE → OUTPUT → ERROR`) — clean, predictable, debuggable
37
- - **Context budget management** — per-model budgets with sliding-window compaction that preserves facts, decisions, and errors
38
- - **Hallucination detection** — fact checks (file paths), consistency checks (decision rollbacks), confidence checks (short or repeated answers)
37
+ - **Context budget management** — per-model budgets with sliding-window compaction that preserves facts, decisions, and errors, keeps a `[Tool ledger]` of what was already read, and calibrates its token estimate against the provider's real `prompt_tokens`
38
+ - **Honest-stop guards** — failed tool batches, read-only loops and retry storms stop the loop with a truthful status instead of letting a small model grind (honest stop, read-only budget, `retry_loop_stopped`)
39
+ - **Hallucination detection** — fact checks (file paths), consistency checks (decision rollbacks), confidence checks, plus claim verification: "created file X" / "output says Y" / "the price is Z" are checked against real tool output
39
40
  - **Execution guarantees** — LLM-driven auto-planning (no keyword heuristics), stuck detection, off-track warnings, automatic plan advancement
40
41
 
41
42
  **Orchestration & scale**
42
43
 
43
44
  - **Agent-Level MoE** — a router plus expert subagents with tool filtering, isolated context, file scoping, topological parallel execution, a re-plan cycle, and machine-readable verification (`success_criteria`)
44
45
  - **YAML pipelines** — a DAG engine with parallel waves, dependencies, and retries
45
- - **Project map** — file walking, export extraction, and an on-disk cache validated by freshness signature
46
+ - **Project map** — symbol index (definitions, outlines, signatures), PageRank-ranked map under a token budget, gitignore-aware incremental cache, LSP fallback for symbol lookup
46
47
 
47
48
  **Tools & integrations**
48
49
 
49
- - **Tools** — filesystem, shell, web search/fetch, browser (Playwright), planning, memory, subagents, MCP, pipelines, LSP, processes. A core set (~26) plus modular `plan`/`todo`/`verify`/`lsp_check`/`project_map`/`set_thinking`/`scope_request`; `question`/`approve` register only in web mode
50
- - **24 modules** — skills, plugins, MCP, pipelines, indexer, memory, context, sessions, browser, execution, hallucination detection, updater, user profile, LSP, model certification, security, artifacts, processes, pricing, onboarding, providers, reasoning, setup, webui
50
+ - **~37 core tools** — filesystem, shell, web search/fetch/browse, browser (Playwright), planning, memory, subagents, MCP, pipelines, LSP, processes, images; plus modular `plan`/`todo`/`verify`/`lsp_check`/`project_map`/`set_thinking`/`define_capability`, and `question`/`approve` in web mode
51
+ - **26 modules** — skills, plugins, MCP, pipelines, indexer, memory, context, sessions, browser, execution, critic, hallucination detection, updater, user profile, LSP, model certification, security, artifacts, processes, pricing, onboarding, providers, reasoning, setup, webui, service
51
52
  - **MCP client** — connect to any MCP server (stdio or SSE)
52
- - **LSP diagnostics** — TypeScript/CSS/HTML servers: type checks after every edit and on demand
53
+ - **LSP diagnostics** — TypeScript/CSS/HTML servers: type checks after every edit, on demand, and `/lsp doctor` explains why a server isn't working
54
+ - **Plugin registry** — plugins can ship their own tools; the agent can draft its own capability with `define_capability` (written to a file, inert until you approve it)
53
55
 
54
56
  **Experience**
55
57
 
56
- - **Web mode `mma web`** — a browser chat (SSE + REST) on the same core and sessions, no TTY needed; first run opens the setup wizard right in the browser
58
+ - **Web mode `mma web`** — a browser chat (SSE + REST) on the same core and sessions, no TTY needed; first run opens the setup wizard right in the browser. Sidebar tabs: Session, Files (workspace tree + viewer), LSP, Trace (live per-session event stream), Plugins, Map. Parallel turns per session, tab persistence, activity rail, MMART the loader face
59
+ - **Service mode `mma serve`** — the same agent as a headless HTTP service with a versioned contract (Agent Service API v1): sessions, tasks, SSE, webhooks, and a live OpenAPI 3.1 spec at `/v1/openapi.json`
57
60
  - **Onboarding** — `mma setup` (provider wizard), `mma meet` ("Let's get acquainted": an interview written to global memory), `mma agents init` (generate/improve `AGENTS.md`)
58
- - **Unified `/config`** — a validated settings registry plus sugar commands `/model`, `/provider`, `/context`, `/reasoning`, `/verbose`
61
+ - **Unified `/config`** — a validated settings registry (~110 keys) plus sugar commands `/model`, `/provider`, `/context`, `/reasoning`, `/verbose`, `/security`
59
62
  - **Prompt caching** — cache hints (llama.cpp / OpenAI / OpenRouter) with hit-rate and savings metrics (the `⟡` cache line)
60
63
  - **Context log** — `--context-log` writes the full outgoing model context to a git-style diff (`context.diff`)
61
- - **Session management** — persistent JSONL sessions, REPL commands, `mma session export`
64
+ - **Session management** — persistent JSONL sessions, auto-naming, REPL commands, `mma session export`, `mma session prune`
62
65
  - **i18n** — every UI string goes through `t()`, with English and Russian bundled
63
66
  - **Race-free output** — single-writer `OutputChannel`: no stray `console.*`/stdout outside the CLI and machine JSON
64
- - **Flexible context** — adapts to any model window from 8K to 120K
67
+ - **Flexible context** — adapts to any model window from 8K to 1M; the window is auto-detected from the provider unless you pin it
65
68
  - **No TUI** — a minimal CLI + REPL with markdown→ANSI formatting
66
69
  - **llama.cpp / Jinja proven** — tested against tricky Jinja-template edge cases (system-first, streaming fallback, explicit `stream: false`)
67
70
 
@@ -119,19 +122,28 @@ Point MMA at any OpenAI-compatible endpoint. With LM Studio:
119
122
  ### 2. Run the setup wizard
120
123
 
121
124
  ```bash
122
- bun run mma setup
125
+ mma setup # or: bun run mma setup (from a source checkout)
123
126
  ```
124
127
 
125
- It scans local ports, finds a model, tests the connection, and writes your config. Legacy alias: `bun run mma init`.
128
+ It scans local ports, finds a model, tests the connection, and writes your config. Legacy alias: `mma init`. Running `mma web` on an empty config gives you the same wizard in the browser.
126
129
 
127
130
  ### 3. Use the agent
128
131
 
132
+ Four modes over the same agent, sessions and tools — pick by who is driving:
133
+
129
134
  ```bash
130
- # One-shot
131
- bun run mma "build an Express REST API and add tests"
135
+ mma "build an Express REST API and add tests" # one-shot
136
+ mma # interactive REPL
137
+ mma web # browser chat
138
+ mma serve # HTTP service for your own programs
139
+ ```
132
140
 
133
- # Interactive REPL
134
- bun run dev
141
+ ### 4. Want the API contract?
142
+
143
+ ```bash
144
+ mma serve &
145
+ curl -s localhost:7331/v1/openapi.json | head # OpenAPI 3.1 spec
146
+ open http://localhost:7331/v1/docs # human-readable page
135
147
  ```
136
148
 
137
149
  ---
@@ -149,7 +161,8 @@ bun run mma init # Legacy alias of mma setup (provider wiz
149
161
  bun run mma meet # "Let's get acquainted" — interview, writes to memory
150
162
  bun run mma agents init # Generate/improve AGENTS.md for the workspace
151
163
  bun run mma config set model qwen/qwen3.5-9b
152
- bun run mma config show
164
+ bun run mma config show # Secrets redacted (--show-secrets opts out)
165
+ bun run mma config migrate # Migrate a legacy ~/.mma/config.json
153
166
  bun run mma model list # Available models (✔ = certified)
154
167
  bun run mma model use qwen3.5-9b # Switch model
155
168
  bun run mma model certify <name> # Certify a model against the backend
@@ -158,19 +171,27 @@ bun run mma model cert-list # All certifications
158
171
  bun run mma model uncertify <name> # Revoke a certification
159
172
  bun run mma provider list # List providers
160
173
  bun run mma provider add local --url http://localhost:1234/v1 # Add a provider
161
- bun run mma provider use opencode-zen # Hosted provider (zen / go)
174
+ bun run mma provider use opencode-zen # Switch the active provider
175
+ bun run mma provider check # Probe the active provider
162
176
  bun run mma context # Context tokens/budget (--system/--reserve)
163
- bun run mma map [summary|refresh|find] # Project map
164
177
  bun run mma usage # Provider balance (OpenRouter /key, /credits)
178
+ bun run mma map [summary|refresh|find] # Project map
165
179
  bun run mma security status # Current security configuration
166
180
  bun run mma security policies # Available policies (strict/balanced/permissive)
167
181
  bun run mma security set-policy strict # Apply a policy
182
+ bun run mma security audit-stats # Audit log counters
168
183
  bun run mma plugins list # Loaded plugins (--all includes built-in)
184
+ bun run mma plugins show <name> # Plugin details (tools, permissions, failures)
185
+ bun run mma plugins enable|disable <name>
186
+ bun run mma plugins drafts [approve|revoke] # Agent-written capability drafts
187
+ bun run mma plugins reload
169
188
  bun run mma session list # List sessions
170
189
  bun run mma session show <id> # Session details
171
190
  bun run mma session export <id> # Export a session (--format md|json|jsonl, --out -)
172
191
  bun run mma session delete <id> # Delete a session
192
+ bun run mma session prune # Drop old sessions
173
193
  bun run mma web [--port N] [--host H] [--token T] [--no-open] # Browser chat
194
+ bun run mma serve [--port N] [--host H] [--token T] # HTTP service (Agent Service API v1)
174
195
 
175
196
  # One-shot flags
176
197
  bun run mma "<prompt>" --json # Machine-readable JSON result
@@ -190,33 +211,35 @@ bun run dev
190
211
 
191
212
  | Command | Aliases | Description |
192
213
  |---------|---------|-------------|
193
- | `/help` | | Show help |
214
+ | `/help [<command>]` | | Show help (usage, aliases, examples per command) |
194
215
  | `/sessions` | `/ls` | List sessions (* = active) |
195
216
  | `/new <name>` | `/create` | Create a new session |
196
- | `/resume <id\|name>` | `/switch`, `/use` | Switch to a session |
217
+ | `/resume <id\|name>` | `/switch` | Switch to a session |
197
218
  | `/rename <name>` | | Rename the current session |
198
219
  | `/delete <id>` | `/rm` | Delete a session |
199
220
  | `/model [name]` | | Show/switch model |
200
- | `/provider [list\|use <name>\|add …]` | | Providers (list/switch/add) |
201
- | `/status` | | Status: model, plugins, session, cost |
202
- | `/config [<key> [<value>]]` | | Unified settings registry (`/config reset <key>`) |
203
- | `/context` | `/ctx` | Context tokens and budget |
221
+ | `/provider [list\|use <name>\|add …]` | | Providers (list/switch/add from the REPL) |
222
+ | `/status` | | Status: model, providers, plugins, session, cost |
223
+ | `/config [<key> [<value>]]` | | Unified settings registry (`/config reset <key>\|--all`) |
224
+ | `/security [status\|<key> [value]]` | | Security summary or a single key |
225
+ | `/context [N\|system 10%\|reserve 0.2]` | `/ctx` | Context window and budget |
204
226
  | `/reasoning [show\|hide\|level <...>]` | | Reasoning display / level (`auto`/`low`/…/`max`) |
205
227
  | `/verbose [quiet\|normal\|verbose]` | | Output verbosity level |
206
- | `/memory [get\|set\|unset\|find\|forget]` | | View/edit memory |
228
+ | `/memory [get\|set\|unset\|find\|forget]` | | View/edit global memory |
207
229
  | `/meet` | `/познакомимся` | Onboarding interview (writes to global memory) |
208
- | `/map [summary\|refresh\|find]` | | Project map |
230
+ | `/map [summary\|refresh\|find]` | | Project map (symbol/outline lookup) |
209
231
  | `/export [md\|json\|jsonl]` | | Export the current session |
210
- | `/image <path\|url>` | | Attach an image (or Ctrl+V) |
232
+ | `/image <path\|url>` | `/img` | Attach an image (or Ctrl+V) |
211
233
  | `/run <cmd>` | | Run a shell command without the agent |
212
- | `/sysprompt` | | Show the system prompt |
213
- | `/wizard` | | Setup wizard |
234
+ | `/sysprompt [--full]` | | Show the system prompt |
235
+ | `/wizard` | `/setup` | Setup wizard |
236
+ | `/init` | | Deprecated alias of `/wizard` |
214
237
  | `/skill <name>` | | Load a skill |
215
- | `/plugins` | | Plugins (`--all` for all) |
216
- | `/lsp [status\|restart\|check <path>]` | | LSP diagnostics |
238
+ | `/plugins [list\|show\|enable\|disable\|reload]` | | Plugin registry and drafts |
239
+ | `/lsp [status\|restart\|check <path>\|doctor]` | | LSP diagnostics |
217
240
  | `/reload` | | Reload config and modules |
218
241
  | `/clear` | | Clear the screen (session context is preserved) |
219
- | `/exit` | | Exit the REPL |
242
+ | `/exit` | `/quit`, `/q` | Exit the REPL |
220
243
 
221
244
  **Hotkeys:** `Esc Esc` — interrupt the agent; `Ctrl+C` — quit; `Ctrl+V` — paste an image from the clipboard; `Shift+Enter` — newline.
222
245
 
@@ -230,7 +253,9 @@ bun run mma web --port 8080 --host 127.0.0.1 --no-open
230
253
  ```
231
254
 
232
255
  - **First run** with an empty config: the wizard opens right in the browser (language → provider → key → model → context → security → review). Chat stays locked until setup is complete (the server answers `409 setup_required`).
233
- - Settings panel: models/providers, context, reasoning, memory, skills, plugins/MCP, security. Slash commands (`/help`, `/status`, `/config`, `/model`, `/provider`, `/memory`, …) work in the browser too.
256
+ - **Real parallelism** — every tab is its own session with its own agent and context; turns run concurrently, and a background session streams into its own tab. Tabs survive a reload.
257
+ - **Sidebar** — Session (plan · usage · environment), Files (lazy workspace tree + read-only viewer, click a row to attach the path to your prompt), LSP, Trace (live per-session event stream with details), Plugins, Map (project index).
258
+ - Settings panel: models/providers, context (incl. `auto` from the provider), reasoning, memory, skills, plugins/MCP, security, network (incl. Agent Service API). Slash commands (`/help`, `/status`, `/config`, `/model`, `/provider`, `/memory`, …) work in the browser too, and config writes land in the open panel immediately.
234
259
  - Env: `MMA_WEBUI_PORT`, `MMA_WEBUI_HOST`, `MMA_WEBUI_TOKEN`, `MMA_WEBUI_OPEN=0`, `MMA_WEBUI_MAX_CLIENTS`.
235
260
 
236
261
  ### Service mode (`mma serve`)
@@ -242,9 +267,14 @@ bun run mma serve # 127.0.0.1:7331
242
267
  bun run mma serve --port 8080 --host 0.0.0.0 --token "$SECRET"
243
268
  ```
244
269
 
270
+ - **Discoverable contract** — no hunting through the repo:
271
+ - `GET /v1/openapi.json` — machine-readable **OpenAPI 3.1** spec (feed it to Swagger UI, Redoc, Postman/Insomnia, or generate a client);
272
+ - `GET /v1/docs` — the same contract as a human-readable page (quickstart cURL, endpoint table, SSE events, error types), self-contained HTML that works offline.
273
+ - Both are printed on startup and served without a bearer token — they describe the public API, they don't expose tenant data.
245
274
  - **1 process = 1 tenant** (silo): its own `MMA_CONFIG_DIR`, workspace, memory. A control plane maps tenants to processes; the runtime does not know about neighbours.
246
275
  - **Auth:** `Authorization: Bearer <token>` for the internal control-plane call (`service.token`, mandatory for non-loopback). The runtime is **not** meant to be exposed to the internet directly.
247
276
  - Endpoints: `/v1/sessions` CRUD, `/v1/sessions/:id/tasks` (jobs), `/v1/tasks/:jobId` (+`/cancel`, `/answer`), `/v1/sessions/:id/events` (SSE), `/v1/health`, `/v1/ready`.
277
+ - Task = a turn over a session: `job` (autonomous, strict `toolAllowlist`) or `interactive` (questions over SSE). Pull (`GET /v1/tasks/:jobId`) is the source of truth; SSE and HMAC-signed webhooks are optimizations. Errors are RFC 9457 Problem Details with stable `type` URIs.
248
278
  - Config: `service.port`, `service.host`, `service.token`, `service.resultRetentionDays`, `service.idempotencyTtlHours`, `service.syncWaitMs`, `service.maxQueueLength`, `service.callbackTimeoutMs`, `service.webhookSecret`. Env: `MMA_SERVICE_PORT`, `MMA_SERVICE_HOST`, `MMA_SERVICE_TOKEN`.
249
279
  - Full reference: [docs/agents/service-api.md](./docs/agents/service-api.md) · guide: [docs/serve-mode.md](./docs/serve-mode.md) · design: [docs/specs/2026-10-08-agent-service-api-design.md](./docs/specs/2026-10-08-agent-service-api-design.md).
250
280
 
@@ -276,8 +306,10 @@ Key options (full list in `src/config/defaults.ts`; view/edit via `/config` in t
276
306
  | Option | Default | Description |
277
307
  |--------|---------|-------------|
278
308
  | `model` | `qwen/qwen3.5-9b` | Model name for the backend |
279
- | `provider.baseUrl` | `http://localhost:1234/v1` | OpenAI-compatible API URL (or `provider.entries[]` for failover) |
309
+ | `provider.baseUrl` | `http://localhost:1234/v1` | OpenAI-compatible API URL of the active provider |
310
+ | `provider.entries[]` | — | Named providers (multi-provider setup); `provider.active` holds the active entry's name |
280
311
  | `contextWindow` | `32768` | Model context window in tokens |
312
+ | `contextAuto` | `true` | Take the window from the provider; writing a value pins it |
281
313
  | `contextBudget.systemPrompt` | `0.10` | Share of the window for the system prompt |
282
314
  | `contextBudget.responseReserve` | `0.15` | Reserve for the response |
283
315
  | `autoPlan` | `true` | Auto-create plans for multi-step tasks |
@@ -285,12 +317,16 @@ Key options (full list in `src/config/defaults.ts`; view/edit via `/config` in t
285
317
  | `reasoning.baseline` | `low` | Starting level in auto (grows on signals) |
286
318
  | `maxToolIterations` | `1000` | Max tool calls per run |
287
319
  | `stuckThreshold` | `6` | Iterations without progress before stuck detection |
288
- | `moe.enabled` | `false` | Enable Agent-Level MoE |
320
+ | `moe.enabled` | `false` | Enable Agent-Level MoE (orchestrator falls back to the active model) |
321
+ | `critic.enabled` | `true` | In-loop critic: verifies the final answer before it's accepted |
289
322
  | `security.enabled` | `true` | Security module (balanced policy by default) |
323
+ | `service.port` | `7331` | Port for `mma serve` |
290
324
  | `ui.verbosity` | `normal` | `quiet` / `normal` / `verbose` |
291
325
  | `locale` | `en` | UI language (`en` / `ru`) |
292
326
  | `logLevel` | `info` | Logging level |
293
327
 
328
+ Everything else — ~110 keys across context budget, reasoning, MoE, critic, plugins, images, service, providers, webui — is reachable through `/config`, `mma config show` and web Settings → … (`config show` redacts secrets unless `--show-secrets`).
329
+
294
330
  ### Agent-Level MoE
295
331
 
296
332
  ```json
@@ -313,34 +349,43 @@ Key options (full list in `src/config/defaults.ts`; view/edit via `/config` in t
313
349
  ## Architecture
314
350
 
315
351
  ```
316
- CLI (main.ts → commands.ts / repl.ts / setup.ts / web-command.ts)
352
+ CLI (main.ts → commands.ts / repl.ts / service-command.ts / web-command.ts / setup.ts)
317
353
  → Core (agent.ts state machine → bootstrap.ts buildSystemInfo)
318
354
  → LLM Layer (provider.ts → openai-compat.ts → cache-usage.ts → token-counter.ts)
319
355
  → Tools (registry.ts → executor.ts → core tools + module-registered)
320
356
  → Modules (skills, plugins, mcp, pipelines, indexer, memory, context, session,
321
- hallucination, execution, updater, user-profile, browser, lsp,
357
+ hallucination, execution, critic, updater, user-profile, browser, lsp,
322
358
  security, certification, artifacts, processes, pricing,
323
- onboarding, providers, reasoning, setup, webui)
359
+ onboarding, providers, reasoning, setup, webui, service)
324
360
  → Output (OutputBus → OutputChannel single writer; JSON/SSE sinks)
325
361
  ```
326
362
 
363
+ Three hosts over the same core: the terminal REPL (`cli/repl.ts`), the browser
364
+ (`core/web-host.ts` + `modules/webui/`), and the HTTP service
365
+ (`modules/service/`) — each with its own transport, none reimplementing the loop.
366
+
327
367
  ### Project structure
328
368
 
329
369
  ```
330
370
  src/
331
371
  ├── core/ # Agent loop, bootstrap/system prompt, WebHost, types
332
372
  ├── llm/ # Provider abstraction, OpenAI compatibility, streaming, tokens, cache metrics
333
- ├── tools/ # Tool core (~26) + registry, executor, scope guards
334
- ├── modules/ # 24 modules (skills, plugins, mcp, pipelines, indexer, memory, context,
335
- │ # sessions, hallucination, execution, updater, profile, browser, LSP,
373
+ ├── tools/ # Tool core (~37) + registry, executor, scope guards
374
+ ├── modules/ # 26 modules (skills, plugins, mcp, pipelines, indexer, memory, context,
375
+ │ # sessions, hallucination, execution, critic, updater, profile, browser, LSP,
336
376
  │ # security, certification, artifacts, processes, pricing, onboarding,
337
- │ # providers, reasoning, setup, webui)
377
+ │ # providers, reasoning, setup, webui, service)
338
378
  ├── output/ # Single-writer OutputChannel + OutputBus, JSON/SSE sinks
339
- ├── cli/ # Entry point, commands, REPL, wizards (setup), `mma web`
379
+ ├── cli/ # Entry point, commands, REPL, wizards (setup), `mma web`, `mma serve`
340
380
  ├── config/ # 3-layer config: defaults → domains → project
341
381
  ├── i18n/ # en.json + ru.json + t()
342
382
  ├── ui/ # Markdown→ANSI formatter, renderer, line-editor
343
383
  └── logger/ # Structured logger with levels and child loggers
384
+
385
+ docs/
386
+ ├── serve-mode.md # Integrator guide for `mma serve`
387
+ ├── design/ui/STYLE.md # Canonical chat-output style (single source of truth)
388
+ └── agents/ # Architecture, code style, tools, modules, execution, testing, service API
344
389
  ```
345
390
 
346
391
  ---
@@ -353,39 +398,39 @@ src/
353
398
  | `write_file` | Create/overwrite, creating directories as needed |
354
399
  | `edit_file` | Find-and-replace in existing files |
355
400
  | `glob` | Search by glob patterns |
356
- | `grep` | Search file contents via ripgrep |
401
+ | `grep` | Search file contents (ripgrep → grep → built-in JS fallback) |
357
402
  | `list_dir` | List directory contents |
358
403
  | `create_dir` | Create a directory (recursively) |
359
404
  | `delete_file` | Delete a file or empty directory |
360
405
  | `move_file` | Move/rename a file or directory |
361
406
  | `file_info` | File/directory metadata |
362
- | `bash` | Run shell commands |
363
- | `subagent` | Isolated, scoped subagent |
407
+ | `bash` | Run shell commands (foreground → background process) |
408
+ | `subagent` | Isolated, scoped subagent (runs in parallel within one batch) |
364
409
  | `web_search` | Web search |
365
- | `web_fetch` | Fetch a web page → markdown (~5K chars, 15s timeout, SSRF-safe redirects) |
366
- | `web_browse` | JS-less HTTP page fetch without an engine (up to 3K chars) |
410
+ | `web_fetch` | Fetch a web page → markdown (SSRF-safe, main-content extraction) |
411
+ | `web_browse` | JS-less HTTP page fetch without an engine |
367
412
  | `browser` | Playwright browser (click, type, scroll, screenshot) |
368
413
  | `plan` | Create/update/cancel plans |
369
414
  | `todo` | Task tracking |
415
+ | `verify` | Verify plan steps |
370
416
  | `load_skill` | Load a skill at runtime |
371
417
  | `pipeline_run` | Run a YAML pipeline |
372
418
  | `mcp_call` | Call an MCP server tool |
373
419
  | `search_history` | Search session history |
374
- | `project_map` | Query the project map (summary, refresh, find) |
420
+ | `project_map` | Query the project map (summary, refresh, find, symbol, outline) |
375
421
  | `chunk_query` | Process large texts in parallel chunks |
376
422
  | `download_file` | Download a binary file by URL to disk |
377
- | `process_list` / `process_log` / `process_kill` | Manage background processes |
423
+ | `process_list` / `process_log` / `process_wait` / `process_kill` | Manage background processes |
378
424
  | `remember` / `recall` | Persistent agent memory |
379
- | `attach_image` | Attach an image (file/URL/clipboard; added to context with a vision model) |
425
+ | `attach_image` | Attach an image (file/URL/clipboard; queued into the next user message) |
380
426
  | `enable_tools` | Enable hidden tool groups on the fly |
381
427
  | `lsp_check` | LSP diagnostics (TypeScript/CSS/HTML) |
382
- | `verify` | Verify plan steps |
383
428
  | `session_info` | Current session info (id, model, context, messages) |
384
429
  | `set_thinking` | Change the reasoning level for upcoming iterations |
385
- | `scope_request` | Request a subagent file-scope extension |
430
+ | `define_capability` | Draft a new capability (plugin tool) — inert until you approve it |
386
431
  | `question` / `approve` | Interactive menus (registered only in web mode `mma web`) |
387
432
 
388
- > Note: in the terminal, the interactive tools `question`/`approve` are not registered — the model asks questions as plain text. In `mma web` they work via SSE + `POST /api/answer`.
433
+ > Note: in the terminal, the interactive tools `question`/`approve` are not registered — the model asks questions as plain text. In `mma web` they work via SSE + `POST /api/answer`; in `mma serve` they are available in `interactive` tasks via `POST /v1/tasks/:jobId/answer`.
389
434
 
390
435
  ---
391
436
 
@@ -427,6 +472,8 @@ bun run mma "<prompt>" --no-agents-md --exit-on-complete -d <sandbox-path>
427
472
  8. **No hardcoded strings** — all text through `t()` i18n
428
473
  9. **No keyword matching** — the LLM decides when to plan, not heuristics
429
474
  10. **No TUI** — a minimal CLI + REPL with markdown→ANSI
475
+ 11. **Handler maps, not if-chains** — REPL/CLI/HTTP subcommands dispatch through a `Record<string, handler>`
476
+ 12. **One source of truth per contract** — HTTP routes, the OpenAPI spec and the docs page all render from the same operation table, with a guard test
430
477
 
431
478
  ---
432
479
 
@@ -1 +1 @@
1
- import{msgs as K,inp as S,connStatus as k,setIcon as _,icon as A}from"./js/dom.js";import{api as V,token as R}from"./js/api.js";import{I18N as X,t as a,loadI18n as Y}from"./js/i18n.js";import{tabs as l,session as u,settings as b,hooks as v}from"./js/store.js";import{routeState as Z,mergeUsage as $,storeSnapshot as q,applyCounters as P,shouldHydrateAutoOpen as ee}from"./js/state-sync.js";import{onText as te,onReasoning as se,onMeta as ne,onPhase as oe,onToolStart as ie,onToolEnd as ae,onTurnEnd as re,onErrorEvent as de,onCommandOutput as ce,onLog as le,onPlan as fe,onQuestion as ue,onRetract as pe,showEmptyFeed as y,renderPlan as be,renderUsage as B,renderEnvironment as me,renderWorkspace as he,feedHasContent as we,loadHistory as ge}from"./js/feed.js";import{openTab as F,updateTabName as Se,renderSessionsDropdown as I,reflectBusy as ve,loadSessions as H,loadCommands as ye,setTabStatus as w,reconcileTabStatuses as Ie}from"./js/session/tabs.js";import{renderSelectors as E,renderSettings as Ee,loadConfig as Le,loadModels as xe,loadProviders as Te,loadMemory as Ce,loadSkills as ke,loadPlugins as _e,applyTheme as Ae,currentTheme as Re,applyFontSize as qe,setConnStatus as L,setStaleBar as M}from"./js/settings.js";import{initRail as Pe,setPlan as N,railEvent as Be,applyRailI18n as Fe}from"./js/rail.js";import{initSidebar as He,sidebarSessionChanged as Me,sidebarMapState as Ne,sidebarTraceEvent as x,sidebarTurnEnd as Oe,sidebarBusy as O,sidebarMapIndicator as Ue,sidebarPullLog as We,sidebarSessionId as De,setSessionLoading as je,sidebarLspState as ze,sidebarLspEvent as Je,applySidebarI18n as U,initFiles as Qe,sidebarFilesState as Ge,sidebarFilesChanged as W,sidebarPluginsState as Ke,sidebarPluginsEvent as Ve,initSidebarDrawer as Xe}from"./js/sidebar.js";import{insertIntoDraft as Ye}from"./js/session/composer.js";import{initCursor as Ze,createFace as $e,setFaceState as D,loaderStateFor as et}from"./js/loader.js";let g=null,T=0;function m(s){T++,D(g,s)}function tt(){const s=document.getElementById("composerFace");s&&(g=$e("idle","sm"),s.replaceChildren(g))}document.querySelectorAll("[data-collapse]").forEach(s=>{const e=s.querySelector(".arrow");e&&(e.textContent="",e.appendChild(A("chevron-down"))),s.addEventListener("click",()=>{const i=s.nextElementSibling;if(!i)return;const o=i.classList.toggle("collapsed");s.setAttribute("aria-expanded",o?"false":"true");const r=s.querySelector(".arrow");r&&(r.textContent="",r.appendChild(A(o?"chevron-right":"chevron-down")))})});let C=-1;function h(){const s=l.tabs.find(r=>r.id===l.activeTabId)||null,e=s&&s.state||{};u.host=e;const i=e.sessionId||s&&s.id||null;u.activeSessionId=i,l.tabs.length||y(),be(e.plan),N(u.busy&&e?e.plan:null),B(e),typeof e.contextWindow=="number"&&b.config&&b.config.contextWindow!==e.contextWindow&&(b.config.contextWindow=e.contextWindow,E()),me(e),Ne(e),Ue(e),ze(),Ge(e),Ke();const o=typeof e.filesRevision=="number"?e.filesRevision:void 0;if(o!==void 0&&o!==C){const r=C===-1;C=o,!r&&o>0&&W()}Me(i),he(e),I(),je(!1),M(!!e.i18nStale)}function st(s){const e=Z(s);ve(),Ie(),s.state&&typeof s.state.sessionId=="string"&&Se(s.state.sessionId,s.state.sessionName),e&&h()}async function j(s){const e=s?"/api/state?session="+encodeURIComponent(s):"/api/state",i=await V(e);return i.ok&&i.data?i.data:null}async function z(s){const e=l.tabs.find(o=>o.id===s);if(!e)return;if(e.state){l.activeTabId===s&&h();return}const i=await j(s);i&&(q(i.state),l.activeTabId===s&&(P(i),h()))}async function J(){const s=l.activeTabId,e=await j(s);if(!e||l.activeTabId!==s)return;const i=e.state;if(!s&&i&&i.sessionId&&!l.tabs.some(o=>o.id===i.sessionId)){const o=ee(u.sessions,i.sessionId);F(i.sessionId,i.sessionName||String(i.sessionId),"session",o)}q(i),P(e),h()}v.ensureTabState=z,v.renderActive=h;function nt(){for(const e of l.tabs)e.kind==="session"&&(e.state=null,z(e.id));J(),H();const s=l.tabs.find(e=>e.id===l.activeTabId);s&&s.kind==="session"&&!we(s.feed)&&(s.historyLoaded=!1,ge(s))}function ot(){const s="/events"+(R?"?token="+encodeURIComponent(R):""),e=new EventSource(s);let i=!1;e.onopen=()=>{i&&(i=!1,L(!1),nt())},e.onerror=t=>{t&&typeof t.data=="string"||(i=!0,L(!0))};const o=t=>{try{return JSON.parse(t.data)}catch{return null}},r=t=>!t.sessionId||t.sessionId===De(),c=[["text",t=>te(t.chunk||"",t.sessionId)],["meta",t=>ne(t.chunk||"",t.sessionId,t.kind)],["reasoning",t=>se(t.chunk||"",t.sessionId)],["phase",t=>oe(t.phase,t.sessionId)],["tool_start",ie],["tool_end",ae],["plan",fe],["question",ue],["turn_end",t=>{re(t),r(t)&&(Oe(t.turnSeq),O(!1),We())}],["log",le],["retract",t=>pe(t.reason||"",t.sessionId)]];for(const[t,n]of c)e.addEventListener(t,d=>{const p=o(d);p&&n(p)});for(const t of["tool_start","tool_end","text","plan"])e.addEventListener(t,n=>{const d=o(n);d&&(t==="plan"?N(d):Be(t,d.name))});const f=[["tool_start","tool_start"],["tool_end","tool_end"],["text","text"],["question","question"],["error","error"]];for(const[t,n]of f)e.addEventListener(t,d=>{const p=o(d);p&&m(et({type:n,name:typeof p.name=="string"?p.name:""}))});e.addEventListener("phase",t=>{const n=o(t),d=n&&typeof n.phase=="string"?n.phase:"";m(d==="composing"?"type":d==="tool"?"run":"think")}),e.addEventListener("reasoning",()=>m("think")),e.addEventListener("plan",()=>m("plan")),e.addEventListener("turn_end",t=>{const n=o(t);m(n&&n.success===!1?"error":"done");const d=T;window.setTimeout(()=>{d===T&&D(g,"idle")},1500)});for(const t of["text","reasoning","phase","tool_start","plan"])e.addEventListener(t,n=>{const d=o(n);d&&d.sessionId&&w(d.sessionId,"working")});e.addEventListener("question",t=>{const n=o(t);n&&n.sessionId&&w(n.sessionId,"waiting")}),e.addEventListener("turn_end",t=>{const n=o(t);n&&n.sessionId&&w(n.sessionId,n.success===!1?"failed":"idle")});for(const t of["tool_start","tool_end","phase","plan","question","meta","log","retract","index"])e.addEventListener(t,n=>{const d=o(n);d&&r(d)&&x(t,d)});e.addEventListener("text",t=>{const n=o(t);n&&r(n)&&x("text",n)}),e.addEventListener("lsp",t=>{const n=o(t);n&&Je(n)}),e.addEventListener("files",()=>W()),e.addEventListener("plugins",()=>Ve()),e.addEventListener("command",t=>{const n=o(t);n&&typeof n.text=="string"&&ce(n.text,n.sessionId)}),e.addEventListener("state",t=>{const n=o(t);n&&(O(!!n.busy),st(n))}),e.addEventListener("sessions",t=>{const n=o(t);n&&Array.isArray(n.sessions)&&(u.sessions=n.sessions,I())}),e.addEventListener("usage",t=>{const n=o(t);if(!n||!n.usage||typeof n.usage!="object")return;const d=n.usageSessionId||n.sessionId||null;$(d,n.usage)&&B(u.host)}),e.addEventListener("error",t=>{if(!t.data)return;const n=o(t);n&&(n.sessionId&&w(n.sessionId,"failed"),de(n),x("error",n))})}function Q(){document.documentElement.lang=X.locale;const s=(r,c)=>{const f=document.getElementById(r);f&&(f.textContent=c)};s("planHeaderLabel",a("web.sidebar.plan")),s("usageHeaderLabel",a("web.sidebar.session")+" · "+a("web.sidebar.usage")),s("envHeaderLabel",a("web.sidebar.env")),s("settingsTitle",a("web.settings.title")),s("sessActiveHeader",a("web.sessions.active")),s("sessRecentHeader",a("web.sessions.recent")),s("hintEnter",a("web.input.enter")),s("hintShift",a("web.input.shift_enter")),s("hintEsc",a("web.input.esc")),s("hintAttach",a("web.input.attach")),s("bootCap",a("web.loader.boot"));const e={newChat:["plus",a("web.topbar.new_chat")],sessBtn:["menu",a("web.topbar.all_sessions")],toggleSidebar:["panel",a("web.topbar.sidebar")],openSettings:["settings",a("web.topbar.settings")],attachBtn:["image",a("web.attach.title")],closeSettings:["x",a("web.topbar.close")],sessNew:["plus",a("web.sessions.new")],sessRefresh:["refresh",a("web.sessions.refresh")],sessPrev:["chevron-left",a("web.sessbar.prev")],sessNext:["chevron-right",a("web.sessbar.next")],sessClose:["x",a("web.topbar.close")]};Object.keys(e).forEach(r=>{const c=document.getElementById(r);if(!c)return;const[f,t]=e[r];_(c,f,r==="closeSettings"||r==="sessNew"||r==="sessRefresh"?t:null),c.title=t,c.setAttribute("aria-label",t)});const i=document.getElementById("settingsIcon");i&&_(i,"settings",null),S&&(S.placeholder=a("web.input.placeholder"),S.setAttribute("aria-label",a("web.input.placeholder"))),document.querySelectorAll("#setMenu .mi").forEach(r=>{const c=r.dataset.p;c&&(r.textContent=a("web.settings."+c))});const o=document.getElementById("setSearch");o&&(o.setAttribute("placeholder",a("web.settings.search_placeholder")),o.setAttribute("aria-label",a("web.settings.search_placeholder"))),document.querySelectorAll(".content-col .panel").forEach(r=>{const c=r.querySelector("h3"),f=r.id.replace(/^p-/,"");c&&f&&(c.textContent=a("web.settings."+f))}),E(),I(),Fe(),U(),K.querySelector(".empty")&&y(),k&&!k.hidden&&L(!0),M(void 0,!0)}async function it(){Ze();const s=new URLSearchParams(location.search).get("session");if(window.MmaSetup)try{if(await window.MmaSetup.maybeOpen()){G();return}}catch{}try{await Y(),await ye(),Ae(Re()),qe(),Q(),y(),E(),Ee(),await Le(),Pe(b.config&&b.config.ui?b.config.ui.activityRail:void 0),tt(),He(),Xe(),U(),Qe({insertPath:e=>Ye("`"+e+"` ")}),await xe(),await Te(),await H(),await Ce(),await ke(),await _e();try{await J()}catch(e){console.error("initial refresh failed",e)}if(s){const e=u.sessions.find(i=>i.id===s);e&&F(s,e.name||String(s),"session",!0)}}finally{G()}ot()}function G(){const s=document.getElementById("bootSplash");!s||s.hidden||(s.classList.add("hide"),window.setTimeout(()=>{s.hidden=!0},500))}v.applyStaticI18n=Q,it();
1
+ import{msgs as V,inp as S,connStatus as A,setIcon as R,icon as q}from"./js/dom.js";import{api as X,token as P}from"./js/api.js";import{I18N as Y,t as a,loadI18n as Z}from"./js/i18n.js";import{tabs as f,session as p,settings as m,hooks as y}from"./js/store.js";import{routeState as $,mergeUsage as ee,storeSnapshot as B,applyCounters as F,shouldHydrateAutoOpen as te}from"./js/state-sync.js";import{onText as ne,onReasoning as se,onMeta as oe,onPhase as ie,onToolStart as ae,onToolEnd as re,onTurnEnd as de,onErrorEvent as ce,onCommandOutput as le,onLog as fe,onPlan as ue,onQuestion as pe,onRetract as be,showEmptyFeed as E,renderPlan as me,renderUsage as H,renderEnvironment as he,renderWorkspace as we,feedHasContent as ge,loadHistory as ve}from"./js/feed.js";import{openTab as I,updateTabName as Se,renderSessionsDropdown as L,reflectBusy as ye,loadSessions as M,loadCommands as Ee,setTabStatus as v,reconcileTabStatuses as Ie,readSavedTabs as Le}from"./js/session/tabs.js";import{renderSelectors as x,renderSettings as xe,loadConfig as N,loadModels as Te,loadProviders as Ce,loadMemory as ke,loadSkills as _e,loadPlugins as Ae,applyTheme as Re,currentTheme as qe,applyFontSize as Pe,setConnStatus as T,setStaleBar as O}from"./js/settings.js";import{initRail as Be,setPlan as U,railEvent as Fe,applyRailI18n as He}from"./js/rail.js";import{initSidebar as Me,sidebarSessionChanged as Ne,sidebarMapState as Oe,sidebarTraceEvent as C,sidebarTurnEnd as Ue,sidebarBusy as W,sidebarMapIndicator as We,sidebarPullLog as De,sidebarSessionId as je,setSessionLoading as ze,sidebarLspState as Ge,sidebarLspEvent as Je,applySidebarI18n as D,initFiles as Qe,sidebarFilesState as Ke,sidebarFilesChanged as j,sidebarPluginsState as Ve,sidebarPluginsEvent as Xe,initSidebarDrawer as Ye}from"./js/sidebar.js";import{insertIntoDraft as Ze}from"./js/session/composer.js";import{initCursor as $e,createFace as et,setFaceState as k,loaderStateFor as tt,setGreetResolver as nt}from"./js/loader.js";let h=null,w=0;function u(s){w++,k(h,s)}function st(){const s=document.getElementById("composerFace");s&&(h=et("idle","sm"),s.replaceChildren(h))}document.querySelectorAll("[data-collapse]").forEach(s=>{const e=s.querySelector(".arrow");e&&(e.textContent="",e.appendChild(q("chevron-down"))),s.addEventListener("click",()=>{const i=s.nextElementSibling;if(!i)return;const o=i.classList.toggle("collapsed");s.setAttribute("aria-expanded",o?"false":"true");const l=s.querySelector(".arrow");l&&(l.textContent="",l.appendChild(q(o?"chevron-right":"chevron-down")))})});let _=-1;function g(){const s=f.tabs.find(l=>l.id===f.activeTabId)||null,e=s&&s.state||{};p.host=e;const i=e.sessionId||s&&s.id||null;p.activeSessionId=i,f.tabs.length||E(),me(e.plan),U(p.busy&&e?e.plan:null),H(e),typeof e.contextWindow=="number"&&m.config&&m.config.contextWindow!==e.contextWindow&&(m.config.contextWindow=e.contextWindow,x()),he(e),Oe(e),We(e),Ge(),Ke(e),Ve();const o=typeof e.filesRevision=="number"?e.filesRevision:void 0;if(o!==void 0&&o!==_){const l=_===-1;_=o,!l&&o>0&&j()}Ne(i),we(e),L(),ze(!1),O(!!e.i18nStale)}function ot(s){const e=$(s);ye(),Ie(),s.state&&typeof s.state.sessionId=="string"&&Se(s.state.sessionId,s.state.sessionName),e&&g()}async function z(s){const e=s?"/api/state?session="+encodeURIComponent(s):"/api/state",i=await X(e);return i.ok&&i.data?i.data:null}async function G(s){const e=f.tabs.find(o=>o.id===s);if(!e)return;if(e.state){f.activeTabId===s&&g();return}const i=await z(s);i&&(B(i.state),f.activeTabId===s&&(F(i),g()))}async function J(){const s=f.activeTabId,e=await z(s);if(!e||f.activeTabId!==s)return;const i=e.state;if(!s&&i&&i.sessionId&&!f.tabs.some(o=>o.id===i.sessionId)){const o=te(p.sessions,i.sessionId);I(i.sessionId,i.sessionName||String(i.sessionId),"session",o)}B(i),F(e),g()}y.ensureTabState=G,y.renderActive=g;function it(){for(const e of f.tabs)e.kind==="session"&&(e.state=null,G(e.id));J(),M();const s=f.tabs.find(e=>e.id===f.activeTabId);s&&s.kind==="session"&&!ge(s.feed)&&(s.historyLoaded=!1,ve(s))}function at(){const s="/events"+(P?"?token="+encodeURIComponent(P):""),e=new EventSource(s);let i=!1;e.onopen=()=>{i&&(i=!1,T(!1),it())},e.onerror=t=>{t&&typeof t.data=="string"||(i=!0,T(!0))};const o=t=>{try{return JSON.parse(t.data)}catch{return null}},l=t=>!t.sessionId||t.sessionId===je(),d=[["text",t=>ne(t.chunk||"",t.sessionId)],["meta",t=>oe(t.chunk||"",t.sessionId,t.kind)],["reasoning",t=>se(t.chunk||"",t.sessionId)],["phase",t=>ie(t.phase,t.sessionId)],["tool_start",ae],["tool_end",re],["plan",ue],["question",pe],["turn_end",t=>{de(t),l(t)&&(Ue(t.turnSeq),W(!1),De())}],["log",fe],["retract",t=>be(t.reason||"",t.sessionId)]];for(const[t,n]of d)e.addEventListener(t,c=>{const b=o(c);b&&n(b)});for(const t of["tool_start","tool_end","text","plan"])e.addEventListener(t,n=>{const c=o(n);c&&(t==="plan"?U(c):Fe(t,c.name))});const r=[["tool_start","tool_start"],["tool_end","tool_end"],["text","text"],["question","question"],["error","error"]];for(const[t,n]of r)e.addEventListener(t,c=>{const b=o(c);b&&u(tt({type:n,name:typeof b.name=="string"?b.name:""}))});e.addEventListener("phase",t=>{const n=o(t),c=n&&typeof n.phase=="string"?n.phase:"";u(c==="composing"?"type":c==="tool"?"run":"think")}),e.addEventListener("reasoning",()=>u("think")),e.addEventListener("plan",()=>u("plan")),e.addEventListener("turn_end",t=>{const n=o(t);u(n&&n.success===!1&&!n.interrupted?"error":"done");const c=w;window.setTimeout(()=>{c===w&&k(h,"idle")},1500)}),e.addEventListener("retract",()=>u("error")),e.addEventListener("index",()=>u("find")),e.addEventListener("setup_done",()=>u("done")),e.addEventListener("update",t=>{const n=o(t);if(n&&n.phase==="failed"){u("error");return}u("update");const c=w;window.setTimeout(()=>{c===w&&k(h,"idle")},4e3)});for(const t of["text","reasoning","phase","tool_start","plan"])e.addEventListener(t,n=>{const c=o(n);c&&c.sessionId&&v(c.sessionId,"working")});e.addEventListener("question",t=>{const n=o(t);n&&n.sessionId&&v(n.sessionId,"waiting")}),e.addEventListener("turn_end",t=>{const n=o(t);n&&n.sessionId&&v(n.sessionId,n.success===!1&&!n.interrupted?"failed":"idle")});for(const t of["tool_start","tool_end","phase","plan","question","meta","log","retract","index"])e.addEventListener(t,n=>{const c=o(n);c&&l(c)&&C(t,c)});e.addEventListener("text",t=>{const n=o(t);n&&l(n)&&C("text",n)}),e.addEventListener("lsp",t=>{const n=o(t);n&&Je(n)}),e.addEventListener("files",()=>j()),e.addEventListener("plugins",()=>Xe()),e.addEventListener("config",()=>{N()}),e.addEventListener("command",t=>{const n=o(t);n&&typeof n.text=="string"&&le(n.text,n.sessionId)}),e.addEventListener("state",t=>{const n=o(t);n&&(W(!!n.busy),ot(n))}),e.addEventListener("sessions",t=>{const n=o(t);n&&Array.isArray(n.sessions)&&(p.sessions=n.sessions,L())}),e.addEventListener("usage",t=>{const n=o(t);if(!n||!n.usage||typeof n.usage!="object")return;const c=n.usageSessionId||n.sessionId||null;ee(c,n.usage)&&H(p.host)}),e.addEventListener("error",t=>{if(!t.data)return;const n=o(t);n&&(n.sessionId&&v(n.sessionId,"failed"),ce(n),C("error",n))})}function Q(){document.documentElement.lang=Y.locale;const s=(d,r)=>{const t=document.getElementById(d);t&&(t.textContent=r)};s("planHeaderLabel",a("web.sidebar.plan")),s("usageHeaderLabel",a("web.sidebar.session")+" · "+a("web.sidebar.usage")),s("envHeaderLabel",a("web.sidebar.env")),s("settingsTitle",a("web.settings.title")),s("sessActiveHeader",a("web.sessions.active")),s("sessRecentHeader",a("web.sessions.recent")),s("hintEnter",a("web.input.enter")),s("hintShift",a("web.input.shift_enter")),s("hintEsc",a("web.input.esc")),s("hintAttach",a("web.input.attach")),s("bootCap",a("web.loader.boot")),nt(()=>a("web.loader.greet"));const e=h?.querySelector(".mdl-tip");e&&(e.textContent=a("web.loader.greet"));const i={newChat:["plus",a("web.topbar.new_chat")],sessBtn:["menu",a("web.topbar.all_sessions")],toggleSidebar:["panel",a("web.topbar.sidebar")],openSettings:["settings",a("web.topbar.settings")],attachBtn:["image",a("web.attach.title")],closeSettings:["x",a("web.topbar.close")],sessNew:["plus",a("web.sessions.new")],sessRefresh:["refresh",a("web.sessions.refresh")],sessPrev:["chevron-left",a("web.sessbar.prev")],sessNext:["chevron-right",a("web.sessbar.next")],sessClose:["x",a("web.topbar.close")]};Object.keys(i).forEach(d=>{const r=document.getElementById(d);if(!r)return;const[t,n]=i[d];R(r,t,d==="closeSettings"||d==="sessNew"||d==="sessRefresh"?n:null),r.title=n,r.setAttribute("aria-label",n)});const o=document.getElementById("settingsIcon");o&&R(o,"settings",null),S&&(S.placeholder=a("web.input.placeholder"),S.setAttribute("aria-label",a("web.input.placeholder"))),document.querySelectorAll("#setMenu .mi").forEach(d=>{const r=d.dataset.p;r&&(d.textContent=a("web.settings."+r))});const l=document.getElementById("setSearch");l&&(l.setAttribute("placeholder",a("web.settings.search_placeholder")),l.setAttribute("aria-label",a("web.settings.search_placeholder"))),document.querySelectorAll(".content-col .panel").forEach(d=>{const r=d.querySelector("h3"),t=d.id.replace(/^p-/,"");r&&t&&(r.textContent=a("web.settings."+t))}),x(),L(),He(),D(),V.querySelector(".empty")&&E(),A&&!A.hidden&&T(!0),O(void 0,!0)}function rt(s){const e=Le(),i=r=>p.sessions.some(t=>t.id===r),o=r=>{const t=p.sessions.find(n=>n.id===r);return t&&t.name?String(t.name):r},l=(s&&i(s)?s:e.active&&i(e.active)?e.active:e.ids.find(i))||null,d=[];s&&i(s)&&d.push(s),l&&!d.includes(l)&&d.push(l);for(const r of e.ids)i(r)&&!d.includes(r)&&d.push(r);if(d.length){I(d[0],o(d[0]),"session",!0);for(const r of d.slice(1))I(r,o(r),"session",!1,!1)}}async function dt(){$e();const s=new URLSearchParams(location.search).get("session");if(window.MmaSetup)try{if(await window.MmaSetup.maybeOpen()){K();return}}catch{}try{await Z(),await Ee(),Re(qe()),Pe(),Q(),E(),x(),xe(),await N(),Be(m.config&&m.config.ui?m.config.ui.activityRail:void 0),st(),Me(),Ye(),D(),Qe({insertPath:e=>Ze("`"+e+"` ")}),await Te(),await Ce(),await M(),await ke(),await _e(),await Ae(),rt(s);try{await J()}catch(e){console.error("initial refresh failed",e)}}finally{K()}at()}function K(){const s=document.getElementById("bootSplash");!s||s.hidden||(s.classList.add("hide"),window.setTimeout(()=>{s.hidden=!0},500))}y.applyStaticI18n=Q,dt();
@@ -1 +1 @@
1
- export const feed={curText:null,curMd:null,turnMd:"",turnRoots:[],curThink:null,lastPlanSig:null,lastUsageSig:null,lastEnvSig:null,runCount:0,runFails:0,runReconOnly:!0,runNodes:[],runDuration:0,busyTimer:null,pendingArgs:new Map,toolNodes:new Map};
1
+ export const feed={lastPlanSig:null,lastUsageSig:null,lastEnvSig:null};const e=new WeakMap;function u(){return{curText:null,curMd:null,turnMd:"",turnRoots:[],curThink:null,runCount:0,runFails:0,runReconOnly:!0,runNodes:[],runDuration:0,busyTimer:null,pendingArgs:new Map,toolNodes:new Map,pendingText:"",textScheduled:!1}}export function streamStateFor(t){let n=e.get(t);return n||(n=u(),e.set(t,n)),n}
@@ -1 +1 @@
1
- import{feed as s}from"./feed.js";import{tabs as c}from"./tabs.js";import{settings as f}from"./settings.js";import{session as i}from"./session.js";export{s as feed,c as tabs,f as settings,i as session};export const THEME_KEY="mma-theme",MAX_IMAGES=10,MAX_IMAGE_BYTES=50*1024*1024,hooks={applyStaticI18n:null};export function setPath(r,n,p){const t=n.split(".");let o=r;for(let e=0;e<t.length-1;e++)(o[t[e]]==null||typeof o[t[e]]!="object")&&(o[t[e]]={}),o=o[t[e]];o[t[t.length-1]]=p}export function getPath(r,n){const p=n.split(".");let t=r;for(const o of p){if(t==null||typeof t!="object")return;t=t[o]}return t}
1
+ import{feed as s}from"./feed.js";import{tabs as f}from"./tabs.js";import{settings as c}from"./settings.js";import{session as i}from"./session.js";export{s as feed,f as tabs,c as settings,i as session};export{streamStateFor}from"./feed.js";export const THEME_KEY="mma-theme",MAX_IMAGES=10,MAX_IMAGE_BYTES=50*1024*1024,hooks={applyStaticI18n:null};export function setPath(r,n,p){const t=n.split(".");let o=r;for(let e=0;e<t.length-1;e++)(o[t[e]]==null||typeof o[t[e]]!="object")&&(o[t[e]]={}),o=o[t[e]];o[t[t.length-1]]=p}export function getPath(r,n){const p=n.split(".");let t=r;for(const o of p){if(t==null||typeof t!="object")return;t=t[o]}return t}
@@ -1 +1 @@
1
- import{msgs as s,el as r,setIcon as C}from"../dom.js";import{t as l,pluralCount as w}from"../i18n.js";import{feed as o,tabs as c}from"../store.js";import{formatMs as x,verbosity as T}from"../format.js";import{copyText as v}from"../../md.js";import{createFace as g}from"../loader.js";export function mdOpts(t){return{copyLabel:l("web.code.copy"),copiedLabel:l("web.code.copied"),copyFailedLabel:l("web.code.copy_failed"),animate:t===!0}}export function buildThinkBlock(t,e){const n=r("div","think"+(e===!1?"":" live")),i=r("button","think-h");i.type="button",i.setAttribute("aria-expanded","false"),i.appendChild(r("span","g","✦")),i.appendChild(r("span","lab",l("web.action.thinking"))),t&&i.appendChild(r("span","dur",""));const d=r("span","tog");C(d,"chevron-right",l("web.action.show")),i.appendChild(d);const p=r("div","think-body hidden");return n.appendChild(i),n.appendChild(p),{wrap:n,head:i,body:p}}export function copyAction(t,e){const n=r("button","act-btn",l(e));return n.type="button",n.addEventListener("click",()=>{v(t).then(i=>{n.textContent=i?"✓ "+l("web.code.copied"):"⚠ "+l("web.code.copy_failed"),window.setTimeout(()=>{n.textContent=l(e)},1400)})}),n}export function appendActionsRow(t,e,n){const i=String(e??""),d=String(n??"");if(!t||!i.trim()&&!d.trim())return;const p=r("div","msg-actions");i.trim()&&p.appendChild(copyAction(i,"web.msg.copy")),d.trim()&&p.appendChild(copyAction(d,"web.msg.copy_md")),appendTo(t,p)}export function insertAfter(t,e){t&&t.parentNode?t.parentNode.insertBefore(e,t.nextSibling):appendTo(liveFeed(),e)}function b(){const t=r("div","empty");return t.appendChild(r("span","g","✦")),t.appendChild(document.createTextNode(" "+l("web.empty.title"))),t.appendChild(document.createElement("br")),t.appendChild(r("span","hint",l("web.empty.hint"))),t}export function makeFeed(){const t=r("div","feed");return t.appendChild(b()),t}export function showEmptyFeed(){s.replaceChildren(b()),u=!0,f(),syncUrl()}export function clearFeed(t){if(!t)return;t.replaceChildren(b()),u=!0,f();const e=c.tabs.find(n=>n.feed===t);e&&(e.planCard=null),t===activeFeed()&&scroll()}export function activeFeed(){const t=c.tabs.find(e=>e.id===c.activeTabId);return t?t.feed:null}export function liveFeed(){const t=c.tabs.find(e=>e.id===c.liveTabId)||c.tabs.find(e=>e.id===c.activeTabId);return t?t.feed:null}export function feedForEvent(t){if(t){const e=c.tabs.find(n=>n.id===t);if(e)return e.feed}return liveFeed()}function h(){return s.scrollHeight-s.scrollTop-s.clientHeight<140}let u=!0;export function scroll(t){t||u||h()?(s.scrollTop=s.scrollHeight,u=!0,f()):m()}export function scrollToBottom(t){if(u=!0,f(),t&&typeof s.scrollTo=="function")try{s.scrollTo({top:s.scrollHeight,behavior:"smooth"});return}catch{}s.scrollTop=s.scrollHeight}export function isFollowing(){return u}export function feedHasContent(t){return!!t.querySelector(".u, .md, .ans, .code, .act, .think, .metaln, .errline, .sysline, .detail")}export function syncUrl(){const t=new URL(location.href),e=c.tabs.find(n=>n.id===c.activeTabId);e&&e.kind==="session"?t.searchParams.set("session",e.id):t.searchParams.delete("session"),history.replaceState(null,"",t)}let a=null;function L(){if(a)return a;const t=document.querySelector(".chat");return t?(a=r("button","to-latest"),a.type="button",a.hidden=!0,a.addEventListener("click",()=>scrollToBottom(!0)),t.appendChild(a),a):null}function m(){const t=L();t&&(t.textContent="↓ "+l("web.scroll.latest"),t.hidden=!1,s.classList.add("unfollowed"))}function f(){a&&(a.hidden=!0),s.classList.remove("unfollowed")}s?.addEventListener("scroll",()=>{const t=h();u=t,s.classList.toggle("scrolled",s.scrollTop>24),t?f():liveFeed()&&feedHasContent(liveFeed())&&m()});export function appendTo(t,e){return t&&(t.appendChild(e),t===activeFeed()&&scroll()),e}export function clearEmpty(t){const e=t&&t.querySelector(".empty");e&&e.remove()}export function clearBusy(t){if(o.busyTimer&&(clearInterval(o.busyTimer),o.busyTimer=null),t=t||liveFeed(),!t)return;const e=t.querySelector(".busy");e&&e.remove()}export function showBusy(t){if(t=t||liveFeed(),!t||o.curMd||o.curThink||(clearEmpty(t),t.querySelector(".busy")))return;o.busyTimer&&(clearInterval(o.busyTimer),o.busyTimer=null);const e=r("div","busy");e.appendChild(g("think","sm")),e.appendChild(r("span","lab",l("web.action.thinking")));const n=r("span","busytime","· 0s");e.appendChild(n),e.appendChild(r("span","","· "+l("web.input.esc"))),appendTo(t,e);const i=Date.now();o.busyTimer=setInterval(()=>{if(!e.parentNode){clearInterval(o.busyTimer),o.busyTimer=null;return}n.textContent="· "+Math.round((Date.now()-i)/1e3)+"s"},1e3)}export function flushRun(){if(o.runCount<=0){y();return}if(T()==="quiet"&&o.runReconOnly&&o.runFails===0&&o.runCount>1){const t=o.runNodes.slice();t.forEach(i=>i.classList.add("fold-out")),window.setTimeout(()=>t.forEach(i=>i.remove()),160);const e=r("div","grouped");e.appendChild(r("span","pipe","│"));const n=x(o.runDuration);e.appendChild(document.createTextNode(" "+w("web.action.actions",o.runCount)+" ✓"+(n?" · "+n:""))),appendTo(liveFeed(),e)}y()}function y(){o.runCount=0,o.runFails=0,o.runReconOnly=!0,o.runNodes=[],o.runDuration=0}
1
+ import{msgs as i,el as r,setIcon as C}from"../dom.js";import{t as s,pluralCount as w}from"../i18n.js";import{streamStateFor as f,tabs as a}from"../store.js";import{formatMs as x,verbosity as T}from"../format.js";import{copyText as v}from"../../md.js";import{createFace as g}from"../loader.js";export function mdOpts(t){return{copyLabel:s("web.code.copy"),copiedLabel:s("web.code.copied"),copyFailedLabel:s("web.code.copy_failed"),animate:t===!0}}export function buildThinkBlock(t,e){const n=r("div","think"+(e===!1?"":" live")),o=r("button","think-h");o.type="button",o.setAttribute("aria-expanded","false"),o.appendChild(r("span","g","✦")),o.appendChild(r("span","lab",s("web.action.thinking"))),t&&o.appendChild(r("span","dur",""));const c=r("span","tog");C(c,"chevron-right",s("web.action.show")),o.appendChild(c);const l=r("div","think-body hidden");return n.appendChild(o),n.appendChild(l),{wrap:n,head:o,body:l}}export function copyAction(t,e){const n=r("button","act-btn",s(e));return n.type="button",n.addEventListener("click",()=>{v(t).then(o=>{n.textContent=o?"✓ "+s("web.code.copied"):"⚠ "+s("web.code.copy_failed"),window.setTimeout(()=>{n.textContent=s(e)},1400)})}),n}export function appendActionsRow(t,e,n){const o=String(e??""),c=String(n??"");if(!t||!o.trim()&&!c.trim())return;const l=r("div","msg-actions");o.trim()&&l.appendChild(copyAction(o,"web.msg.copy")),c.trim()&&l.appendChild(copyAction(c,"web.msg.copy_md")),appendTo(t,l)}export function insertAfter(t,e){t&&t.parentNode?t.parentNode.insertBefore(e,t.nextSibling):appendTo(liveFeed(),e)}function b(){const t=r("div","empty");return t.appendChild(r("span","g","✦")),t.appendChild(document.createTextNode(" "+s("web.empty.title"))),t.appendChild(document.createElement("br")),t.appendChild(r("span","hint",s("web.empty.hint"))),t}export function makeFeed(){const t=r("div","feed");return t.appendChild(b()),t}export function showEmptyFeed(){i.replaceChildren(b()),p=!0,d(),syncUrl()}export function clearFeed(t){if(!t)return;t.replaceChildren(b()),p=!0,d();const e=a.tabs.find(n=>n.feed===t);e&&(e.planCard=null),t===activeFeed()&&scroll()}export function activeFeed(){const t=a.tabs.find(e=>e.id===a.activeTabId);return t?t.feed:null}export function liveFeed(){const t=a.tabs.find(e=>e.id===a.liveTabId)||a.tabs.find(e=>e.id===a.activeTabId);return t?t.feed:null}export function feedForEvent(t){if(t){const e=a.tabs.find(n=>n.id===t);if(e)return e.feed}return liveFeed()}function h(){return i.scrollHeight-i.scrollTop-i.clientHeight<140}let p=!0;export function scroll(t){t||p||h()?(i.scrollTop=i.scrollHeight,p=!0,d()):m()}export function scrollToBottom(t){if(p=!0,d(),t&&typeof i.scrollTo=="function")try{i.scrollTo({top:i.scrollHeight,behavior:"smooth"});return}catch{}i.scrollTop=i.scrollHeight}export function isFollowing(){return p}export function feedHasContent(t){return!!t.querySelector(".u, .md, .ans, .code, .act, .think, .metaln, .errline, .sysline, .detail")}export function syncUrl(){const t=new URL(location.href),e=a.tabs.find(n=>n.id===a.activeTabId);e&&e.kind==="session"?t.searchParams.set("session",e.id):t.searchParams.delete("session"),history.replaceState(null,"",t)}let u=null;function L(){if(u)return u;const t=document.querySelector(".chat");return t?(u=r("button","to-latest"),u.type="button",u.hidden=!0,u.addEventListener("click",()=>scrollToBottom(!0)),t.appendChild(u),u):null}function m(){const t=L();t&&(t.textContent="↓ "+s("web.scroll.latest"),t.hidden=!1,i.classList.add("unfollowed"))}function d(){u&&(u.hidden=!0),i.classList.remove("unfollowed")}i?.addEventListener("scroll",()=>{const t=h();p=t,i.classList.toggle("scrolled",i.scrollTop>24),t?d():liveFeed()&&feedHasContent(liveFeed())&&m()});export function appendTo(t,e){return t&&(t.appendChild(e),t===activeFeed()&&scroll()),e}export function clearEmpty(t){const e=t&&t.querySelector(".empty");e&&e.remove()}export function clearBusy(t){if(t=t||liveFeed(),!t)return;const e=f(t);e.busyTimer&&(clearInterval(e.busyTimer),e.busyTimer=null);const n=t.querySelector(".busy");n&&n.remove()}export function showBusy(t){if(t=t||liveFeed(),!t)return;const e=f(t);if(e.curMd||e.curThink||(clearEmpty(t),t.querySelector(".busy")))return;e.busyTimer&&(clearInterval(e.busyTimer),e.busyTimer=null);const n=r("div","busy");n.appendChild(g("think","sm")),n.appendChild(r("span","lab",s("web.action.thinking")));const o=r("span","busytime","· 0s");n.appendChild(o),n.appendChild(r("span","","· "+s("web.input.esc"))),appendTo(t,n);const c=Date.now();e.busyTimer=setInterval(()=>{if(!n.parentNode){clearInterval(e.busyTimer),e.busyTimer=null;return}o.textContent="· "+Math.round((Date.now()-c)/1e3)+"s"},1e3)}export function flushRun(t){if(t=t||liveFeed(),!t)return;const e=f(t);if(e.runCount<=0){y(e);return}if(T()==="quiet"&&e.runReconOnly&&e.runFails===0&&e.runCount>1){const n=e.runNodes.slice();n.forEach(l=>l.classList.add("fold-out")),window.setTimeout(()=>n.forEach(l=>l.remove()),160);const o=r("div","grouped");o.appendChild(r("span","pipe","│"));const c=x(e.runDuration);o.appendChild(document.createTextNode(" "+w("web.action.actions",e.runCount)+" ✓"+(c?" · "+c:""))),appendTo(t,o)}y(e)}function y(t){t.runCount=0,t.runFails=0,t.runReconOnly=!0,t.runNodes=[],t.runDuration=0}
@@ -1 +1 @@
1
- import{usageEl as c,envEl as x,wsLine as L,el as s,icon as C,attachTooltip as h}from"../dom.js";import{I18N as k,t as a,pluralCount as v}from"../i18n.js";import{feed as g,settings as A}from"../store.js";import{renderFooter as B}from"../footer.js";import{fmtTokens as b}from"../format.js";import{copyText as E}from"../../md.js";function m(e,i,n){const t=s("div","kv");return t.appendChild(s("span","",e)),t.appendChild(s("b",n||"",i)),t}function S(e){return[["seg-system","web.usage.part_system",e.system],["seg-tools","web.usage.part_tools",e.tools],["seg-history","web.usage.part_history",e.history],["seg-reserve","web.usage.part_reserve",e.reserve],["seg-free","web.usage.part_free",e.free]]}function _(e,i,n,t){const o=document.createDocumentFragment();return o.appendChild(s("i","ctip-dot "+e)),o.appendChild(s("span","ctip-label",a(i))),o.appendChild(s("b","",b(n))),o.appendChild(s("span","ctip-pct",Math.round(n/t*100)+"%")),o}const M=new Map;function F(e,i,n){const t=M.get(i);if(M.set(i,n),t===void 0||t===n){e.style.flexGrow=String(n);return}e.style.flexGrow=String(t);const o=()=>{e.style.flexGrow=String(n)};typeof requestAnimationFrame=="function"?requestAnimationFrame(o):o()}function I(e,i,n){const t=s("div","bar segbar"),o=e.contextBreakdown;if(!o||typeof o.window!="number"||o.window<=0){const l=s("i");return l.style.flex="0 0 "+Math.min(100,Math.round(i/n*100))+"%",t.appendChild(l),t}const d=Math.max(o.window,o.used+o.reserve);for(const[l,r,p]of S(o)){if(!p||p<=0)continue;const u=s("i",l);u.style.flexBasis="0",F(u,l,p),u.tabIndex=0,u.setAttribute("aria-label",[a(r),b(p),Math.round(p/d*100)+"%"].join(" · ")),h(u,()=>_(l,r,p,d)),t.appendChild(u)}return t}function R(e){const i=s("div","seglegend"),n=e.contextBreakdown;if(!n)return i;const t=Math.max(n.window,n.used+n.reserve);for(const[o,d,l]of S(n)){if(!l||l<=0)continue;const r=s("span","segl-item");r.tabIndex=0,r.setAttribute("aria-label",[a(d),b(l),Math.round(l/t*100)+"%"].join(" · ")),r.appendChild(s("i","segl-dot "+o)),r.appendChild(s("span","segl-label",a(d))),r.appendChild(s("b","",b(l))),h(r,()=>_(o,d,l,t)),i.appendChild(r)}return i}function T(e){const i=s("div","kv");i.appendChild(s("span","",a("web.usage.session")));const n=s("span","kv-val");if(!e)n.appendChild(s("b","","—"));else{n.appendChild(s("b","",e));const t=s("button","copy-btn");t.type="button",t.title=a("web.usage.copy_id"),t.setAttribute("aria-label",a("web.usage.copy_id")),t.appendChild(C("copy")),t.addEventListener("click",()=>{E(e).then(o=>{t.replaceChildren(C(o?"check":"copy")),setTimeout(()=>t.replaceChildren(C("copy")),1500)})}),n.appendChild(t)}return i.appendChild(n),i}export function renderUsage(e){const i=A.config||{},n=JSON.stringify([e.sessionId,e.model,e.contextUsed,e.contextLimit,e.cacheHitRate,e.costUsd,e.compactionCount,e.contextQuality,e.contextProbe,e.contextBreakdown,i.model,i.contextWindow,k.locale]);if(n===g.lastUsageSig)return;g.lastUsageSig=n,c.textContent="";const t=e.sessionId?String(e.sessionId):"";c.appendChild(T(t)),c.appendChild(m(a("web.usage.model"),e.model||i.model||"—"));const o=typeof e.contextUsed=="number"?e.contextUsed:null,d=typeof e.contextLimit=="number"?e.contextLimit:i.contextWindow||null;c.appendChild(m(a("web.usage.context"),o!=null&&d?b(o)+" / "+b(d):"—")),o!=null&&d&&(c.appendChild(I(e,o,d)),c.appendChild(R(e)));const l=typeof e.cacheHitRate=="number"?e.cacheHitRate:null;c.appendChild(m(a("web.usage.cache"),l!=null?Math.round(l*100)+"%":"—",l!=null?"":"dim")),c.appendChild(m(a("web.usage.cost"),typeof e.costUsd=="number"?"$"+e.costUsd.toFixed(4):"—")),c.appendChild(m(a("web.usage.compactions"),"⟳ "+(e.compactionCount||0),e.compactionCount?"wn":"dim"));const r=typeof e.contextQuality=="number"?e.contextQuality:null;c.appendChild(m(a("web.usage.quality"),r!=null?Math.round(r)+"%":"—",r!=null?"":"dim"));let p="—",u="dim";const f=e.contextProbe;if(f&&typeof f.actual=="number"){const y=b(f.actual),w=e.contextLimit||i.contextWindow||null;w&&f.actual<w?(p=a("web.usage.probe_low",{actual:y,configured:b(w)}),u="wn"):(p=a("web.usage.probe_loaded",{actual:y}),u="")}c.appendChild(m(a("web.usage.probe"),p,u))}export function renderWorkspace(e){B(L,{workspace:e.workspace,version:e.version,moeActive:e.moeActive===!0,dirLabel:a("web.env.dir")},s)}function U(e,i){const n=s("div","env-item");return n.appendChild(s("div","env-k",e)),i.forEach(t=>{const o=s("div","env-v",t);o.title=t,n.appendChild(o)}),n}export function renderEnvironment(e){const i=!!(e.instructions&&e.instructions.disabled),n=e.instructions&&!i?e.instructions.files||[]:[],t=e.projectMap||null,o=JSON.stringify([i,n,t,k.locale]);o!==g.lastEnvSig&&(g.lastEnvSig=o,x.textContent="",x.appendChild(U(a("web.env.agents"),i?[a("web.env.agents_disabled")]:n.length?n:[a("web.env.agents_none")])),x.appendChild(U(a("web.env.map"),t&&t.indexed?[[v("web.env.files",t.files),v("web.env.symbols",t.symbols),v("web.env.tokens",t.tokens)].join(" · ")]:[a("web.env.map_not_indexed")])))}
1
+ import{usageEl as p,envEl as v,wsLine as U,el as s,icon as x,attachTooltip as h}from"../dom.js";import{I18N as k,t as a,pluralCount as C}from"../i18n.js";import{feed as w,settings as B}from"../store.js";import{renderFooter as E}from"../footer.js";import{fmtTokens as b}from"../format.js";import{copyText as L}from"../../md.js";function f(e,i,n){const t=s("div","kv");return t.appendChild(s("span","",e)),t.appendChild(s("b",n||"",i)),t}function _(e){return[["seg-system","web.usage.part_system",e.system],["seg-tools","web.usage.part_tools",e.tools],["seg-history","web.usage.part_history",e.history],["seg-reserve","web.usage.part_reserve",e.reserve],["seg-free","web.usage.part_free",e.free]]}function S(e,i,n,t){const o=document.createDocumentFragment();return o.appendChild(s("i","ctip-dot "+e)),o.appendChild(s("span","ctip-label",a(i))),o.appendChild(s("b","",b(n))),o.appendChild(s("span","ctip-pct",Math.round(n/t*100)+"%")),o}const M=new Map;function T(e,i,n){const t=M.get(i);if(M.set(i,n),t===void 0||t===n){e.style.flexGrow=String(n);return}e.style.flexGrow=String(t);const o=()=>{e.style.flexGrow=String(n)};typeof requestAnimationFrame=="function"?requestAnimationFrame(o):o()}function W(e,i,n){const t=s("div","bar segbar"),o=e.contextBreakdown;if(!o||typeof o.window!="number"||o.window<=0){const l=s("i");return l.style.flex="0 0 "+Math.min(100,Math.round(i/n*100))+"%",t.appendChild(l),t}const r=Math.max(o.window,o.used+o.reserve);for(const[l,d,c]of _(o)){if(!c||c<=0)continue;const u=s("i",l);u.style.flexBasis="0",T(u,l,c),u.tabIndex=0,u.setAttribute("aria-label",[a(d),b(c),Math.round(c/r*100)+"%"].join(" · ")),h(u,()=>S(l,d,c,r)),t.appendChild(u)}return t}function F(e){const i=s("div","seglegend"),n=e.contextBreakdown;if(!n)return i;const t=Math.max(n.window,n.used+n.reserve);for(const[o,r,l]of _(n)){if(!l||l<=0)continue;const d=s("span","segl-item");d.tabIndex=0,d.setAttribute("aria-label",[a(r),b(l),Math.round(l/t*100)+"%"].join(" · ")),d.appendChild(s("i","segl-dot "+o)),d.appendChild(s("span","segl-label",a(r))),d.appendChild(s("b","",b(l))),h(d,()=>S(o,r,l,t)),i.appendChild(d)}return i}function I(e){const i=s("div","kv");i.appendChild(s("span","",a("web.usage.session")));const n=s("span","kv-val");if(!e)n.appendChild(s("b","","—"));else{n.appendChild(s("b","",e));const t=s("button","copy-btn");t.type="button",t.title=a("web.usage.copy_id"),t.setAttribute("aria-label",a("web.usage.copy_id")),t.appendChild(x("copy")),t.addEventListener("click",()=>{L(e).then(o=>{t.replaceChildren(x(o?"check":"copy")),setTimeout(()=>t.replaceChildren(x("copy")),1500)})}),n.appendChild(t)}return i.appendChild(n),i}export function renderUsage(e){const i=B.config||{},n=JSON.stringify([e.sessionId,e.model,e.contextUsed,e.contextLimit,e.contextWindow,e.cacheHitRate,e.costUsd,e.compactionCount,e.contextQuality,e.contextProbe,e.contextBreakdown,i.model,i.contextWindow,k.locale]);if(n===w.lastUsageSig)return;w.lastUsageSig=n,p.textContent="";const t=e.sessionId?String(e.sessionId):"";p.appendChild(I(t)),p.appendChild(f(a("web.usage.model"),e.model||i.model||"—"));const o=typeof e.contextUsed=="number"?e.contextUsed:null,r=typeof e.contextLimit=="number"?e.contextLimit:i.contextWindow||null;p.appendChild(f(a("web.usage.context"),o!=null&&r?b(o)+" / "+b(r):"—")),o!=null&&r&&(p.appendChild(W(e,o,r)),p.appendChild(F(e)));const l=typeof e.cacheHitRate=="number"?e.cacheHitRate:null;p.appendChild(f(a("web.usage.cache"),l!=null?Math.round(l*100)+"%":"—",l!=null?"":"dim")),p.appendChild(f(a("web.usage.cost"),typeof e.costUsd=="number"?"$"+e.costUsd.toFixed(4):"—")),p.appendChild(f(a("web.usage.compactions"),"⟳ "+(e.compactionCount||0),e.compactionCount?"wn":"dim"));const d=typeof e.contextQuality=="number"?e.contextQuality:null;p.appendChild(f(a("web.usage.quality"),d!=null?Math.round(d)+"%":"—",d!=null?"":"dim"));let c="—",u="dim";const m=e.contextProbe;if(m&&typeof m.actual=="number"){const y=b(m.actual),g=e.contextWindow||i.contextWindow||null;g&&m.actual<g?(c=a("web.usage.probe_low",{actual:y,configured:b(g)}),u="wn"):(c=a("web.usage.probe_loaded",{actual:y}),u="")}p.appendChild(f(a("web.usage.probe"),c,u))}export function renderWorkspace(e){E(U,{workspace:e.workspace,version:e.version,moeActive:e.moeActive===!0,updateAvailable:e.updateAvailable||void 0,dirLabel:a("web.env.dir"),updateTitle:a("web.update.chip_title",{latest:e.updateAvailable?.latest??""})},s)}function A(e,i){const n=s("div","env-item");return n.appendChild(s("div","env-k",e)),i.forEach(t=>{const o=s("div","env-v",t);o.title=t,n.appendChild(o)}),n}export function renderEnvironment(e){const i=!!(e.instructions&&e.instructions.disabled),n=e.instructions&&!i?e.instructions.files||[]:[],t=e.projectMap||null,o=JSON.stringify([i,n,t,k.locale]);o!==w.lastEnvSig&&(w.lastEnvSig=o,v.textContent="",v.appendChild(A(a("web.env.agents"),i?[a("web.env.agents_disabled")]:n.length?n:[a("web.env.agents_none")])),v.appendChild(A(a("web.env.map"),t&&t.indexed?[[C("web.env.files",t.files),C("web.env.symbols",t.symbols),C("web.env.tokens",t.tokens)].join(" · ")]:[a("web.env.map_not_indexed")])))}