micro-models-agent 2.29.1 → 2.36.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +204 -2
  2. package/README.md +15 -0
  3. package/dist/assets/app.js +1 -1
  4. package/dist/assets/index.html +56 -16
  5. package/dist/assets/js/core/store/feed.js +1 -1
  6. package/dist/assets/js/core/store/index.js +1 -1
  7. package/dist/assets/js/dom.js +1 -1
  8. package/dist/assets/js/feed/core.js +1 -1
  9. package/dist/assets/js/feed/sidebar.js +1 -1
  10. package/dist/assets/js/feed/stream.js +1 -1
  11. package/dist/assets/js/feed/turn.js +4 -4
  12. package/dist/assets/js/footer.js +1 -0
  13. package/dist/assets/js/loader.js +1 -0
  14. package/dist/assets/js/md-normalize.js +5 -0
  15. package/dist/assets/js/session/composer.js +1 -1
  16. package/dist/assets/js/session/tabs.js +1 -1
  17. package/dist/assets/js/settings/controls.js +1 -1
  18. package/dist/assets/js/settings/write.js +1 -1
  19. package/dist/assets/js/sidebar/core.js +1 -1
  20. package/dist/assets/js/sidebar/drawer.js +1 -0
  21. package/dist/assets/js/sidebar/files/chrome.js +1 -0
  22. package/dist/assets/js/sidebar/files/constants.js +1 -0
  23. package/dist/assets/js/sidebar/files/crumbs.js +1 -0
  24. package/dist/assets/js/sidebar/files/history.js +1 -0
  25. package/dist/assets/js/sidebar/files/keys.js +1 -0
  26. package/dist/assets/js/sidebar/files/split.js +1 -0
  27. package/dist/assets/js/sidebar/files/tree.js +1 -0
  28. package/dist/assets/js/sidebar/files/view.js +1 -0
  29. package/dist/assets/js/sidebar/files.js +1 -1
  30. package/dist/assets/js/sidebar/state.js +1 -1
  31. package/dist/assets/js/sidebar/trace.js +4 -4
  32. package/dist/assets/js/sidebar.js +1 -1
  33. package/dist/assets/js/state-sync.js +1 -1
  34. package/dist/assets/md.js +1 -1
  35. package/dist/assets/setup.js +2 -2
  36. package/dist/assets/styles.css +1 -1
  37. package/dist/i18n/en.json +1 -1
  38. package/dist/i18n/ru.json +1 -1
  39. package/dist/main.js +370 -355
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,210 @@ 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.36.0] - 2026-10-08
8
+
9
+ `mma web` fixes from a live two-tab session (a background session's stream
10
+ leaked into the active tab, Settings showed a stale value after slash-command
11
+ config writes, an interrupted turn reported success, the context probe compared
12
+ against the history budget instead of the window, open tabs were lost on
13
+ reload), a context selector that can take the window from the provider
14
+ (`contextAuto`, default on), and the agent's mascot: the loader face is now
15
+ named **MMART** (MMA + art), greets on click, and maps every tool and
16
+ agent-facing SSE event to a distinct face state.
17
+
18
+ ### Added
19
+
20
+ - **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`.
21
+ - **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`.
22
+
23
+ ### Fixed
24
+
25
+ - **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.
26
+ - **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.
27
+ - **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.
28
+ - **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".
29
+ - **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.
30
+
31
+ ## [2.35.1] - 2026-10-08
32
+
33
+ Closing the last tab in `mma web` now resets the session instead of leaving a
34
+ stale active session and its context hanging (the next turn / reload reused the
35
+ closed session and the sidebar kept showing its context).
36
+
37
+ ### Fixed
38
+
39
+ - **Last-tab close resets the session** (`src/modules/webui/session/sessions-api.ts`, `src/modules/session/manager.ts`): new `POST /api/sessions/deactivate` clears the server-active session, resets the plan tracker (`switchSession(null)`) and drops the **idle** runner (context freed; a busy/queued session is left alone). Idempotent.
40
+ - **Client projection reset** (`src/modules/webui/assets/js/session/tabs.ts`, `assets/app.ts`): closing the last tab clears `session.activeSessionId`/`host` and re-renders the sidebar chrome (`hooks.renderActive`); switching to a neighbour tab on close now syncs the active session/sidebar and posts `activate`.
41
+ - **Composing on the empty screen starts a fresh chat** (`src/modules/webui/assets/js/session/composer.ts`): `send()` with no session tab creates a new session first, so a message typed after closing all tabs no longer routes into the closed session.
42
+ - **Slash commands render without a tab** (`src/modules/webui/assets/js/feed/turn.ts`): command output falls back to the transcript container instead of being dropped when no tab owns the session.
43
+
44
+ ## [2.35.0] - 2026-10-08
45
+
46
+ Agent Service API v1: MMA can run as a long-lived HTTP service (`mma serve`)
47
+ that accepts tasks from external systems (bots, backends, cron) over a stable,
48
+ versioned contract, without touching the agent loop, REPL or `mma web`.
49
+
50
+ ### Added
51
+
52
+ - **`mma serve`** (`src/cli/service-command.ts`, `src/cli/commands.ts`, `src/cli/main.ts`): boots `bootstrap()` + `RunPool` + per-session runners and serves the Agent Service API on `service.port`/`service.host`, with a bearer token (`service.token`, mandatory for non-loopback), `GET /v1/health` and `GET /v1/ready`.
53
+ - **`src/modules/service/`** runtime: separate public API layer over the shared primitives (`RunPool`, `spawnSessionRunner`, `Agent`, `SessionManager`); process-per-tenant (silo) isolation.
54
+ - **Sessions API** (`/v1/sessions` CRUD) with optional `SessionMeta.externalRef` (backward compatible) for integrator keys.
55
+ - **Tasks API** (`POST /v1/sessions/:id/tasks`, `GET /v1/tasks/:jobId`, `POST /v1/tasks/:jobId/cancel`, `POST /v1/tasks/:jobId/answer`): task = a turn over a session; `job`/`interactive` modes; 202/Location/Retry-After, FIFO queue per session, `wait:true` bounded by `service.syncWaitMs`, `Idempotency-Key` dedup, cooperative cancel and timeout.
56
+ - **SSE event stream** (`GET /v1/sessions/:id/events`): versioned envelope (`v`, per-session monotonic `seq`), keep-alive pings, `Last-Event-ID`/`?since=` resume, `X-Accel-Buffering: no`, abort propagation for interactive tasks.
57
+ - **Structured output** (`output:{format:"json",schema}`): JSON-Schema instruction, runtime validation (strict subset), one retry, then `output_invalid`.
58
+ - **Webhook push** (`callbackUrl`): HMAC-SHA256 signature over `timestamp.body`, `X-Mma-Timestamp`/`X-Mma-Event-Id`/`X-Mma-Signature`, bounded retries with backoff+jitter; pull stays the source of truth.
59
+ - **Errors**: RFC 9457 `application/problem+json` with stable `type` URIs (`is_retriable`, `retry_after_ms`, `suggestions`).
60
+ - **Config domain `service.*`** (`port`, `host`, `token`, `resultRetentionDays`, `idempotencyTtlHours`, `syncWaitMs`, `maxQueueLength`, `callbackTimeoutMs`, `webhookSecret`) in `config/service.json`, `/config`, and web Settings → Network; env `MMA_SERVICE_*`.
61
+ - **Docs**: `docs/agents/service-api.md` (API reference); design spec `docs/specs/2026-10-08-agent-service-api-design.md`; plan `docs/plans/2026-10-08-agent-service-api.md`.
62
+
63
+ ### Changed
64
+
65
+ - **`RunPool`** (`src/modules/webui/session/session-pool.ts`): optional `maxQueue` constructor parameter (defaults to the previous `MAX_QUEUE_LENGTH`); no behavior change for `mma web`.
66
+
67
+ ### Fixed
68
+
69
+ - **Job `toolAllowlist` is now enforced** (`src/modules/service/api/tasks-api.ts`): a `job` task's `toolAllowlist` is applied strictly to the runner's `ToolContext` (tags/`alwaysOn` do not bypass it) and restored after the turn; `interactive` is unrestricted.
70
+ - **`tool-forbidden` surfaced** (`src/modules/service/runtime/run-task.ts`): a job that calls an interactive tool or a tool outside its allowlist is failed with `error.type` = `…/errors/tool-forbidden` (was a generic failure).
71
+ - **Interactive cancel no longer hangs** (`src/modules/service/runtime/prompt-io.ts`, `api/tasks-api.ts`, `api/events-api.ts`): `POST /v1/tasks/:jobId/cancel` and closing the SSE stream of an interactive task now resolve its pending `question`, so `agent.run` does not block forever and the session slot is freed.
72
+ - **Tenant/run audit** (`src/modules/service/api/tasks-api.ts`, `src/modules/session/store.ts`, `src/modules/webui/session/log-api.ts`): auth headers `X-Mma-Tenant` / `X-Mma-Principal` / `X-Mma-Run` / `X-Mma-Tier` / `X-Mma-Scopes` are resolved (never from the body) and written to `session.jsonl` as a `service_task` entry.
73
+
74
+ ## [2.34.1] - 2026-10-08
75
+
76
+ Test-infrastructure health (no product behaviour change): the e2e and
77
+ integration suites carried failures that predated the 2.34.0 work.
78
+
79
+ ### Fixed (tests)
80
+
81
+ - **`web-boot` module-graph guard** (`tests/e2e/web-boot.e2e-test.ts`): the `<100B = stub` heuristic failed on legitimate barrels (`js/store.js` transpiles to 39 bytes); stub detection now matches the `loadAssets` fallback text only.
82
+ - **`web-plugins`** (`tests/e2e/web-plugins.e2e-test.ts`): missing `apiPost` import, and `toContainText` (a Playwright matcher) used with vitest `expect`; live toggles are clicked by name via a synthetic event because the row re-renders from the server response.
83
+ - **`web-context-bar`** (`tests/e2e/web-context-bar.e2e-test.ts`): relied on the removed 2s `/api/state` poll (event-sourced state, 2.24.0); the injected breakdown is now served to the boot fetch after a reload.
84
+ - **`web-settings`** (`tests/e2e/web-settings.e2e-test.ts`): context-window write commits atomically (panel re-render detached the input), and the MCP toggle targets the seeded row by name (plugin rows share `.list-line`) with a re-render wait.
85
+ - **Integration mocks** (`tests/integration/{agent,todo-app}.integration-test.ts`): test loggers gained `diagnostic` (added in 2.11.0); the interval-compaction test zeroes `compactionMinLoad` to isolate the interval trigger.
86
+ - **Three e2e tests skipped** as flaky under full-suite load (green in isolation): `web-commands` slash-autocomplete popup, and the `web-settings` context-window and MCP-toggle scenarios. Re-enable when the harness serializes with more headroom.
87
+
88
+ ## [2.34.0] - 2026-10-08
89
+
90
+ Web final answers read as a dense, occasionally broken wall compared to other
91
+ agent UIs. Two causes: cramped typography and GFM that the strict CommonMark
92
+ parser (`smd.js`) left as raw text.
93
+
94
+ ### Fixed
95
+
96
+ - **GFM not parsed by `smd.js` renders as raw text** (`src/modules/webui/assets/js/md-normalize.ts`, `md.ts`): tables without outer pipes (`A | B` / `---|---`), `1)` ordered lists and `##X` headings (no space) are now normalized before parsing. `MdNormalizer` is streaming-aware (one-line lookahead for the table delimiter) so live answers and history share the exact same output; fenced code is never touched.
97
+ - **Soft line breaks were preserved as hard newlines** (`src/modules/webui/assets/styles.css`): `.md p` dropped `white-space: pre-wrap`, so prose flows as spaces per CommonMark instead of a ragged block of short lines.
98
+
99
+ ### Changed
100
+
101
+ - **Answer typography gets breathing room** (`styles.css`): `.md` `line-height: 1.62`; paragraph margin `4 → 9px`; heading scale `1.5 / 1.28 / 1.12em` with `18px` top margin; list `li 3px`, indent `24px`; table cell padding and a header rule; `hr`/`blockquote`/code-block spacing. First/last child margins are trimmed so the answer block hugs its rail.
102
+
103
+ Tests: `tests/webui-md-normalize.test.ts` (normalizer + streaming splits), `tests/webui-md-render.test.ts` (happy-dom structure), guard in `tests/webui-assets.test.ts`.
104
+
105
+ ## [2.33.1] - 2026-10-08
106
+
107
+ Post-mortem of a live `mma web` session (`ses_muzbb045_9v1r4t`): the sidebar
108
+ plan vanished, the feed had no first message, and the session name never
109
+ reached the open tab. All three were harness defects around per-session state,
110
+ not model behaviour.
111
+
112
+ ### Fixed
113
+
114
+ - **`ExecutionModule.switchSession` wiped another session's plan slot** (`src/modules/execution/module.ts`): the shared web UI module (no in-memory tracker) cleared the outgoing session slot on every switch/create, destroying a plan owned by that session's per-session runner — the sidebar then showed no plan after switching tabs or reloading. The outgoing slot is no longer cleared when this instance has no owned plan; the owner clears it itself via `flushSessionPlan` (abort/archive).
115
+ - **Plan step/todo mutations only wrote the global `active.json`** (`src/modules/execution/{plan-actions,plan-tool,module}.ts`): `plan_step`, `plan update`, `todo add/done` and kind-switch persisted to the shared library but not to the owning session slot, so a reload showed stale step statuses. New `persistPlan` dependency mirrors every plan mutation into the session slot.
116
+ - **Web auto-opened an existing session as "live" without its transcript** (`src/modules/webui/assets/app.ts`, `state-sync.ts`): on boot the server-active session opened with `historyLoaded=true` and no fetch, so a reload mid-turn showed only the live SSE tail — no first message, no earlier turns. It now hydrates the transcript when the session already has messages (new pure helper `shouldHydrateAutoOpen`).
117
+ - **Session renames never reached the open tab/dropdown** (`src/modules/session/manager.ts`, `src/modules/webui/module.ts`): the deterministic auto-name and the async LLM title changed `meta.json` only; the tab kept the stale name until an unrelated state push, and the LLM title never appeared. `SessionManager.onRename` (shared with pinned runner siblings) now broadcasts `sessions` + `state`, so the live tab and list update without a reload.
118
+
119
+ ## [2.33.0] - 2026-10-08
120
+
121
+ A MoE turn on a local LM Studio backend died without a word: every subtask
122
+ failed with `[object Object]`, the orchestrator called it "a technical problem"
123
+ and re-planned the same work, and the session log ended mid-flight with no
124
+ answer and no reason. Post-mortem of `ses_muz7sg1l_0ankgu` traced it to eight
125
+ distinct defects, all in the harness rather than the model.
126
+
127
+ ### Added
128
+
129
+ - **Expert model validation** (`src/modules/execution/expert-model-check.ts`): `experts[tag].model` is checked against the active provider's model list once per turn. An unknown model falls back to the active one with a `warn` naming the tag, the bad model and the models the provider actually serves - instead of three doomed attempts per subtask. Fail-open when the list is unavailable; the live config is never mutated, so `/config` still shows the user's setting.
130
+ - **MoE parallelism cap** (`src/modules/execution/moe-parallelism.ts`, `moe.parallelSubtasks`): a local single-engine backend (LM Studio, Ollama, loopback) runs one subtask at a time. Concurrent requests for the same model fought over engine load/unload and returned `Engine protocol startup was aborted`. Hosted providers keep the `rateLimits` cap; `0` means auto.
131
+ - **Infrastructure vs content failures** (`transient-error.ts`): `isInfraError` separates backend/transport failures from task failures. When every failed subtask is infrastructure, MoE stops with an honest partial result carrying the real error text instead of re-planning work that would run on the same broken backend. `isTransientError` now also matches mid-stream aborts, engine/model-load failures and stalled streams, so they get a backoff retry instead of failing on the first attempt.
132
+
133
+ ### Fixed
134
+
135
+ - **Provider error text was destroyed**: `String(parsed.error)` on LM Studio's `{message, type, param}` object produced `[object Object]`, losing the cause in the log, in the retry classifier and in the subtask result. `extractProviderErrorText` (`src/llm/provider-error.ts`) reads `message`, unwraps nested `error`, joins arrays and falls back to compact JSON.
136
+ - **The MoE verifier invented causes**: only `summary` ("Failed: task-2") reached the session log and the verify prompt, so the model wrote a plausible-sounding reason. Failed subtasks now log their `error` and an `infra` flag, `logMoE` uses the reason as `content`, the rendered result shows the cause, and the verifier prompt is told to base explanations only on the errors it was given and to re-plan only for `failedSubtaskIds`.
137
+ - **An interrupted MoE turn left no trace**: `session.jsonl` ended on `moe_subtask: start` with no answer, no reason and no terminal event. An aborted or crashed turn now writes a final `moe_verify` entry with the reason.
138
+ - **Session meta went stale**: a turn that added no messages left `meta.updatedAt` at session creation time, so the session list showed a session that had ended long ago. `SessionManager.touch()` runs at turn end.
139
+ - **Context probe and session meta disagreed**: the probe auto-set 262144 tokens in the live config while `session_start` and `meta.json` kept the pre-probe 65536, with nothing reporting the mismatch. Applying the probed window now syncs the session meta.
140
+ - **14 identical `Session ended` log lines per turn**: `NotifyPlugin` fired at `info` on every `shutdown()` without a session id. It is now a single `debug` line carrying the id.
141
+
142
+ ## [2.32.0] - 2026-10-08
143
+
144
+ The web sidebar had exactly one responsive shape. On anything wider than 760px
145
+ it was a constant 679px column - so on a 768px tablet it left the chat ~90px -
146
+ and below that it became a bare panel pinned at a hardcoded `top: 41px`. It is
147
+ now a real drawer on every viewport that cannot fit the column beside the chat.
148
+
149
+ ### Added
150
+
151
+ - **Modal drawer below 1024px** (`assets/js/sidebar/drawer.ts`, `index.html`, `styles.css`): the sidebar overlays the chat instead of squeezing it, with a dimming backdrop, close on backdrop click / `Escape`, `role="dialog"` + `aria-modal` while open, a Tab focus trap and focus returned to the toggle on close. An open overlay (settings, confirm) keeps `Escape` - it is the topmost surface.
152
+ - **Measured drawer inset**: the drawer slides under the bars by their real height (`--sb-inset`, re-measured on open and resize) instead of a hardcoded `top: 41px`, which ignored the phone session sub-bar appearing at the same breakpoint.
153
+ - **Viewport sync**: growing past the breakpoint restores the column and clears the drawer state. A rotated tablet used to keep a stale `body.sb-open` over a layout that no longer had a drawer, which then re-armed itself on the way back.
154
+
155
+ ### Changed
156
+
157
+ - **Sidebar tab order**: Session / Files / LSP / Trace / Plugins / Map (was Session / Map / Trace / LSP / Files / Plugins). What you look at while a turn runs comes first (Files), then diagnostics (LSP, Trace), then Plugins; the project map moves to the end, being the one tab you open deliberately. `Alt+1..6` follow the visible order - they index the same array. The remembered tab (`mma-sidebar-tab`) is unaffected, only positions shift.
158
+
159
+ ### Fixed
160
+
161
+ - **Master-detail panes were unusable in the drawer**: the 340px Trace detail and the up-to-480px Files tree overflowed a 360px panel and were silently clipped by `overflow-x: hidden`. Below 1024px both panes now stack (detail above the list, tree above the viewer) and the Files tree width is a CSS custom property, so the drawer ignores the drag-resized column.
162
+ - **The Trace pane could be orphaned**: with `trace` active and the viewport shrinking below 760px (rotation, browser resize), the Trace tab button was hidden but its pane stayed on screen with no visible tab to switch away from. The mobile coercion now re-runs on the breakpoint change, not only on a tab click.
163
+ - The tree width no longer overrides the drawer layout (inline `width` -> `--files-tree-w`).
164
+ - Horizontal-overflow smoke coverage now includes 1024/820/768/390px viewports; nothing below 820px was exercised before.
165
+
166
+ ## [2.31.0] - 2026-10-08
167
+
168
+ Opening and closing files in the `mma web` Files tab used to fight you: an open
169
+ file hid the whole tree, so switching files meant closing the viewer, finding
170
+ the file again and re-expanding folders - and the close/attach controls were
171
+ glyph-sized links. The tree now sits beside the viewer, the viewer remembers
172
+ where you came from, and the tree itself has collapse/expand controls.
173
+
174
+ ### Added
175
+
176
+ - **Files split** (`assets/js/sidebar/files/*`, `index.html`, `styles.css`): `.files-body` = tree + draggable splitter + viewer. The tree stays visible while a file is open (the old `:has()` rule that hid it is gone, with a guard test). Tree width is draggable and keyboard-resizable (`ArrowLeft/Right`, `Home`, double-click to reset), clamped 200-480px, default 240px, persisted in `localStorage["mma-files-tree-w"]`. A header toggle switches to viewer-only mode.
177
+ - **Viewer history** (`sidebar/files/history.ts`): bounded stack (50) of visited files with back/forward buttons and `Alt+ArrowLeft`/`Alt+ArrowRight`. Re-visiting a path ahead of the cursor moves the cursor instead of stacking duplicates; closing the viewer keeps the history.
178
+ - **Tree expand/collapse controls**: "collapse all", "expand all" (lazy, batched, depth- and budget-capped with an explicit cap note instead of a silent stop) and "refresh" (invalidates cached listings and reloads the open file).
179
+ - **Keyboard navigation in the tree** (`sidebar/files/keys.ts`): `ArrowUp/Down` move a cursor, `ArrowRight` expands or descends, `ArrowLeft` collapses or jumps to the parent, `Enter`/`Space` act, `Home`/`End` jump, `*` expands the cursor's ancestors. The cursor highlight is repainted in place so the DOM focus survives a re-render.
180
+ - **Path breadcrumbs**: dimmed, clickable directory segments (click reveals the folder in the tree and moves the cursor there), bright file name, `…` folding for long paths.
181
+ - **Readable controls**: icon buttons with a 24px hit target (was 11px glyph links) instead of `◧`/`✕`, localized `title`/`aria-label` everywhere, a full-row attach target, `role="treeitem"` rows with `aria-expanded`/`aria-selected`, and always-visible attach buttons on touch devices.
182
+
183
+ ### Fixed
184
+
185
+ - **The tree no longer disappears when a file is opened** - the original complaint behind this release.
186
+ - **Closing a file left the Files tab blank**: with the tree hidden ("viewer only" mode), closing the viewer via `✕`, `Escape` or a workspace switch kept the tree hidden — neither tree nor file visible, no way back. Closing now always restores the tree, and the "hide tree" button is disabled while no file is open.
187
+ - Hardcoded English `title` attributes on the Files controls (`attach path`, `close`) are now i18n keys; new keys ship in en + ru.
188
+
189
+ ## [2.30.0] - 2026-10-08
190
+
191
+ MoE orchestration is now visible. A turn led by Mixture-of-Experts says so — one
192
+ line when the plan is ready, one when the turn ends — and `mma web` shows a
193
+ `MoE` chip in the composer footer while the orchestration is actually running.
194
+
195
+ ### Added
196
+
197
+ - **MoE progress lines** (`src/core/agent-moe.ts`, `moe.started`/`moe.finished`): `◇ MoE · plan of N steps` on a validated plan and `✓ MoE · D/T done` at the end, printed regardless of `ui.verbose`. The start line is announced once per turn (re-plan cycles stay silent), and the finish line reports a partial result or a broken merge instead of a success tally.
198
+ - **MoE mode chip** (`mma web`): `HostStateSnapshot.moeActive` + per-session `state.moeSessions` + `assets/js/footer.ts`. `Agent.setMoEStateListener` lets the web host subscribe a spawned runner; the flag rides the existing state snapshot (`pushState`) rather than a new SSE type. Lights up only while MoE leads the turn — a fallback to single-agent never lights it.
199
+ - **`Agent.setMoEStateListener(fn)`** (`src/core/agent.ts`): subscribes/unsubscribes the "MoE leads this turn" observer (`AgentDeps.onMoEState`).
200
+
201
+ ### Changed
202
+
203
+ - **Progress is not verbose-only anymore**: `moe.executing` and `moe.planning` are gone (planning is covered by the thinking spinner, the start line replaces the "executing" line); re-plan, auto-fix and scope decisions remain behind `/verbose`.
204
+ - **`moe.*` strings follow STYLE.md**: emoji (`🤖 ⚙️ 🔄 🔧 ✅ ⛔ 🔓 ⚠️`) replaced with `◇ ✓ ⚠ ↻ ↳`; new keys in en + ru.
205
+
206
+ ### Fixed
207
+
208
+ - **Expert sub-agents keep the session header**: `resolveExpertProvider` builds the dedicated expert provider without `getSessionId`, so OpenCode Go/Zen answered every expert call with `400 MissingSessionID` and a whole MoE plan collapsed. Both call sites (`src/tools/subagent.ts`, `src/modules/execution/moe-executor.ts`) now forward the session id.
209
+ - **`mma web` footer**: long workspace paths no longer stretch the line (ellipsis), and the agent version is pinned to the right.
210
+
7
211
  ## [2.29.1] - 2026-10-07
8
212
 
9
213
  Agent-written capability drafts (`define_capability`) are now quality-gated and
@@ -331,8 +535,6 @@ background-process leaks. Harness fixes only (no model/task-specific tuning).
331
535
  - **`resolve.ts`** — extracted npx→global-binary resolution (`findOnPath`, `resolveNpxLaunch`) shared by the client and the doctor.
332
536
  - **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).
333
537
 
334
- ## [Unreleased]
335
-
336
538
  ## [2.17.2] - 2026-10-04
337
539
 
338
540
  ### Changed
package/README.md CHANGED
@@ -233,6 +233,21 @@ bun run mma web --port 8080 --host 127.0.0.1 --no-open
233
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.
234
234
  - Env: `MMA_WEBUI_PORT`, `MMA_WEBUI_HOST`, `MMA_WEBUI_TOKEN`, `MMA_WEBUI_OPEN=0`, `MMA_WEBUI_MAX_CLIENTS`.
235
235
 
236
+ ### Service mode (`mma serve`)
237
+
238
+ Run MMA as a headless HTTP service (Agent Service API v1) for external systems — bots, backends, cron. Same agent/sessions/tools as the CLI, but a stable, versioned contract (HTTP + SSE) instead of a TTY.
239
+
240
+ ```bash
241
+ bun run mma serve # 127.0.0.1:7331
242
+ bun run mma serve --port 8080 --host 0.0.0.0 --token "$SECRET"
243
+ ```
244
+
245
+ - **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
+ - **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
+ - Endpoints: `/v1/sessions` CRUD, `/v1/sessions/:id/tasks` (jobs), `/v1/tasks/:jobId` (+`/cancel`, `/answer`), `/v1/sessions/:id/events` (SSE), `/v1/health`, `/v1/ready`.
248
+ - 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
+ - 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
+
236
251
  ### Development
237
252
 
238
253
  ```bash
@@ -1 +1 @@
1
- import{msgs as D,inp as g,connStatus as L,setIcon as x,icon as T}from"./js/dom.js";import{api as z,token as A}from"./js/api.js";import{I18N as J,t as a,loadI18n as Q}from"./js/i18n.js";import{tabs as l,session as f,settings as u,hooks as C}from"./js/store.js";import{routeState as X,mergeUsage as G,storeSnapshot as k,applyCounters as R}from"./js/state-sync.js";import{onText as K,onReasoning as V,onMeta as Y,onPhase as Z,onToolStart as $,onToolEnd as ee,onTurnEnd as te,onErrorEvent as se,onCommandOutput as ne,onLog as oe,onPlan as ie,onQuestion as ae,onRetract as re,showEmptyFeed as w,renderPlan as de,renderUsage as P,renderEnvironment as ce,renderWorkspace as le,feedHasContent as fe,loadHistory as ue}from"./js/feed.js";import{openTab as _,updateTabName as be,renderSessionsDropdown as h,reflectBusy as pe,loadSessions as q,loadCommands as me,setTabStatus as b,reconcileTabStatuses as ge}from"./js/session/tabs.js";import{renderSelectors as S,renderSettings as we,loadConfig as he,loadModels as Se,loadProviders as ve,loadMemory as ye,loadSkills as Ie,loadPlugins as Ee,applyTheme as Le,currentTheme as xe,applyFontSize as Te,setConnStatus as v,setStaleBar as B}from"./js/settings.js";import{initRail as Ae,setPlan as N,railEvent as Ce,applyRailI18n as ke}from"./js/rail.js";import{initSidebar as Re,sidebarSessionChanged as Pe,sidebarMapState as _e,sidebarTraceEvent as y,sidebarTurnEnd as qe,sidebarBusy as M,sidebarMapIndicator as Be,sidebarPullLog as Ne,sidebarSessionId as Me,setSessionLoading as He,sidebarLspState as Ue,sidebarLspEvent as Fe,applySidebarI18n as H,initFiles as We,sidebarFilesState as Oe,sidebarFilesChanged as U,sidebarPluginsState as je,sidebarPluginsEvent as De}from"./js/sidebar.js";import{insertIntoDraft as ze}from"./js/session/composer.js";document.querySelectorAll("[data-collapse]").forEach(s=>{const e=s.querySelector(".arrow");e&&(e.textContent="",e.appendChild(T("chevron-down"))),s.addEventListener("click",()=>{const o=s.nextElementSibling;if(!o)return;const i=o.classList.toggle("collapsed");s.setAttribute("aria-expanded",i?"false":"true");const r=s.querySelector(".arrow");r&&(r.textContent="",r.appendChild(T(i?"chevron-right":"chevron-down")))})});const p=document.getElementById("toggleSidebar");p&&(p.addEventListener("click",()=>{const s=document.body.classList.toggle("sb-open");p.setAttribute("aria-expanded",s?"true":"false")}),document.addEventListener("keydown",s=>{if(s.key!=="Escape"||!document.body.classList.contains("sb-open"))return;const e=document.activeElement?String(document.activeElement.tagName):"";e==="TEXTAREA"||e==="INPUT"||(document.body.classList.remove("sb-open"),p.setAttribute("aria-expanded","false"))}));let I=-1;function m(){const s=l.tabs.find(r=>r.id===l.activeTabId)||null,e=s&&s.state||{};f.host=e;const o=e.sessionId||s&&s.id||null;f.activeSessionId=o,l.tabs.length||w(),de(e.plan),N(f.busy&&e?e.plan:null),P(e),typeof e.contextWindow=="number"&&u.config&&u.config.contextWindow!==e.contextWindow&&(u.config.contextWindow=e.contextWindow,S()),ce(e),_e(e),Be(e),Ue(),Oe(e),je();const i=typeof e.filesRevision=="number"?e.filesRevision:void 0;if(i!==void 0&&i!==I){const r=I===-1;I=i,!r&&i>0&&U()}Pe(o),le(e),h(),He(!1),B(!!e.i18nStale)}function Je(s){const e=X(s);pe(),ge(),s.state&&typeof s.state.sessionId=="string"&&be(s.state.sessionId,s.state.sessionName),e&&m()}async function F(s){const e=s?"/api/state?session="+encodeURIComponent(s):"/api/state",o=await z(e);return o.ok&&o.data?o.data:null}async function W(s){const e=l.tabs.find(i=>i.id===s);if(!e)return;if(e.state){l.activeTabId===s&&m();return}const o=await F(s);o&&(k(o.state),l.activeTabId===s&&(R(o),m()))}async function O(){const s=l.activeTabId,e=await F(s);if(!e||l.activeTabId!==s)return;const o=e.state;!s&&o&&o.sessionId&&!l.tabs.some(i=>i.id===o.sessionId)&&_(o.sessionId,o.sessionName||String(o.sessionId),"session",!1),k(o),R(e),m()}C.ensureTabState=W;function Qe(){for(const e of l.tabs)e.kind==="session"&&(e.state=null,W(e.id));O(),q();const s=l.tabs.find(e=>e.id===l.activeTabId);s&&s.kind==="session"&&!fe(s.feed)&&(s.historyLoaded=!1,ue(s))}function Xe(){const s="/events"+(A?"?token="+encodeURIComponent(A):""),e=new EventSource(s);let o=!1;e.onopen=()=>{o&&(o=!1,v(!1),Qe())},e.onerror=t=>{t&&typeof t.data=="string"||(o=!0,v(!0))};const i=t=>{try{return JSON.parse(t.data)}catch{return null}},r=t=>!t.sessionId||t.sessionId===Me(),c=[["text",t=>K(t.chunk||"",t.sessionId)],["meta",t=>Y(t.chunk||"",t.sessionId,t.kind)],["reasoning",t=>V(t.chunk||"",t.sessionId)],["phase",t=>Z(t.phase,t.sessionId)],["tool_start",$],["tool_end",ee],["plan",ie],["question",ae],["turn_end",t=>{te(t),r(t)&&(qe(t.turnSeq),M(!1),Ne())}],["log",oe],["retract",t=>re(t.reason||"",t.sessionId)]];for(const[t,n]of c)e.addEventListener(t,d=>{const E=i(d);E&&n(E)});for(const t of["tool_start","tool_end","text","plan"])e.addEventListener(t,n=>{const d=i(n);d&&(t==="plan"?N(d):Ce(t,d.name))});for(const t of["text","reasoning","phase","tool_start","plan"])e.addEventListener(t,n=>{const d=i(n);d&&d.sessionId&&b(d.sessionId,"working")});e.addEventListener("question",t=>{const n=i(t);n&&n.sessionId&&b(n.sessionId,"waiting")}),e.addEventListener("turn_end",t=>{const n=i(t);n&&n.sessionId&&b(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=i(n);d&&r(d)&&y(t,d)});e.addEventListener("text",t=>{const n=i(t);n&&r(n)&&y("text",n)}),e.addEventListener("lsp",t=>{const n=i(t);n&&Fe(n)}),e.addEventListener("files",()=>U()),e.addEventListener("plugins",()=>De()),e.addEventListener("command",t=>{const n=i(t);n&&typeof n.text=="string"&&ne(n.text,n.sessionId)}),e.addEventListener("state",t=>{const n=i(t);n&&(M(!!n.busy),Je(n))}),e.addEventListener("sessions",t=>{const n=i(t);n&&Array.isArray(n.sessions)&&(f.sessions=n.sessions,h())}),e.addEventListener("usage",t=>{const n=i(t);if(!n||!n.usage||typeof n.usage!="object")return;const d=n.usageSessionId||n.sessionId||null;G(d,n.usage)&&P(f.host)}),e.addEventListener("error",t=>{if(!t.data)return;const n=i(t);n&&(n.sessionId&&b(n.sessionId,"failed"),se(n),y("error",n))})}function j(){document.documentElement.lang=J.locale;const s=(r,c)=>{const t=document.getElementById(r);t&&(t.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"));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[t,n]=e[r];x(c,t,r==="closeSettings"||r==="sessNew"||r==="sessRefresh"?n:null),c.title=n,c.setAttribute("aria-label",n)});const o=document.getElementById("settingsIcon");o&&x(o,"settings",null),g&&(g.placeholder=a("web.input.placeholder"),g.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 i=document.getElementById("setSearch");i&&(i.setAttribute("placeholder",a("web.settings.search_placeholder")),i.setAttribute("aria-label",a("web.settings.search_placeholder"))),document.querySelectorAll(".content-col .panel").forEach(r=>{const c=r.querySelector("h3"),t=r.id.replace(/^p-/,"");c&&t&&(c.textContent=a("web.settings."+t))}),S(),h(),ke(),H(),D.querySelector(".empty")&&w(),L&&!L.hidden&&v(!0),B(void 0,!0)}async function Ge(){const s=new URLSearchParams(location.search).get("session");if(window.MmaSetup)try{if(await window.MmaSetup.maybeOpen())return}catch{}await Q(),await me(),Le(xe()),Te(),j(),w(),S(),we(),await he(),Ae(u.config&&u.config.ui?u.config.ui.activityRail:void 0),Re(),H(),We({insertPath:e=>ze("`"+e+"` ")}),await Se(),await ve(),await q(),await ye(),await Ie(),await Ee();try{await O()}catch(e){console.error("initial refresh failed",e)}if(s){const e=f.sessions.find(o=>o.id===s);e&&_(s,e.name||String(s),"session",!0)}Xe()}C.applyStaticI18n=j,Ge();
1
+ import{msgs as V,inp as v,connStatus as _,setIcon as A,icon as R}from"./js/dom.js";import{api as X,token as q}from"./js/api.js";import{I18N as Y,t as a,loadI18n as Z}from"./js/i18n.js";import{tabs as f,session as u,settings as m,hooks as S}from"./js/store.js";import{routeState as $,mergeUsage as ee,storeSnapshot as P,applyCounters as B,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 y,renderPlan as me,renderUsage as F,renderEnvironment as he,renderWorkspace as ge,feedHasContent as we,loadHistory as ve}from"./js/feed.js";import{openTab as E,updateTabName as Se,renderSessionsDropdown as I,reflectBusy as ye,loadSessions as H,loadCommands as Ee,setTabStatus as w,reconcileTabStatuses as Ie,readSavedTabs as Le}from"./js/session/tabs.js";import{renderSelectors as L,renderSettings as xe,loadConfig as M,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 x,setStaleBar as N}from"./js/settings.js";import{initRail as Be,setPlan as O,railEvent as Fe,applyRailI18n as He}from"./js/rail.js";import{initSidebar as Me,sidebarSessionChanged as Ne,sidebarMapState as Oe,sidebarTraceEvent as T,sidebarTurnEnd as Ue,sidebarBusy as U,sidebarMapIndicator as We,sidebarPullLog as De,sidebarSessionId as je,setSessionLoading as ze,sidebarLspState as Ge,sidebarLspEvent as Je,applySidebarI18n as W,initFiles as Qe,sidebarFilesState as Ke,sidebarFilesChanged as D,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 j,loaderStateFor as tt,setGreetResolver as nt}from"./js/loader.js";let h=null,C=0;function p(n){C++,j(h,n)}function st(){const n=document.getElementById("composerFace");n&&(h=et("idle","sm"),n.replaceChildren(h))}document.querySelectorAll("[data-collapse]").forEach(n=>{const e=n.querySelector(".arrow");e&&(e.textContent="",e.appendChild(R("chevron-down"))),n.addEventListener("click",()=>{const i=n.nextElementSibling;if(!i)return;const o=i.classList.toggle("collapsed");n.setAttribute("aria-expanded",o?"false":"true");const l=n.querySelector(".arrow");l&&(l.textContent="",l.appendChild(R(o?"chevron-right":"chevron-down")))})});let k=-1;function g(){const n=f.tabs.find(l=>l.id===f.activeTabId)||null,e=n&&n.state||{};u.host=e;const i=e.sessionId||n&&n.id||null;u.activeSessionId=i,f.tabs.length||y(),me(e.plan),O(u.busy&&e?e.plan:null),F(e),typeof e.contextWindow=="number"&&m.config&&m.config.contextWindow!==e.contextWindow&&(m.config.contextWindow=e.contextWindow,L()),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!==k){const l=k===-1;k=o,!l&&o>0&&D()}Ne(i),ge(e),I(),ze(!1),N(!!e.i18nStale)}function ot(n){const e=$(n);ye(),Ie(),n.state&&typeof n.state.sessionId=="string"&&Se(n.state.sessionId,n.state.sessionName),e&&g()}async function z(n){const e=n?"/api/state?session="+encodeURIComponent(n):"/api/state",i=await X(e);return i.ok&&i.data?i.data:null}async function G(n){const e=f.tabs.find(o=>o.id===n);if(!e)return;if(e.state){f.activeTabId===n&&g();return}const i=await z(n);i&&(P(i.state),f.activeTabId===n&&(B(i),g()))}async function J(){const n=f.activeTabId,e=await z(n);if(!e||f.activeTabId!==n)return;const i=e.state;if(!n&&i&&i.sessionId&&!f.tabs.some(o=>o.id===i.sessionId)){const o=te(u.sessions,i.sessionId);E(i.sessionId,i.sessionName||String(i.sessionId),"session",o)}P(i),B(e),g()}S.ensureTabState=G,S.renderActive=g;function it(){for(const e of f.tabs)e.kind==="session"&&(e.state=null,G(e.id));J(),H();const n=f.tabs.find(e=>e.id===f.activeTabId);n&&n.kind==="session"&&!we(n.feed)&&(n.historyLoaded=!1,ve(n))}function at(){const n="/events"+(q?"?token="+encodeURIComponent(q):""),e=new EventSource(n);let i=!1;e.onopen=()=>{i&&(i=!1,x(!1),it())},e.onerror=t=>{t&&typeof t.data=="string"||(i=!0,x(!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),U(!1),De())}],["log",fe],["retract",t=>be(t.reason||"",t.sessionId)]];for(const[t,s]of d)e.addEventListener(t,c=>{const b=o(c);b&&s(b)});for(const t of["tool_start","tool_end","text","plan"])e.addEventListener(t,s=>{const c=o(s);c&&(t==="plan"?O(c):Fe(t,c.name))});const r=[["tool_start","tool_start"],["tool_end","tool_end"],["text","text"],["question","question"],["error","error"]];for(const[t,s]of r)e.addEventListener(t,c=>{const b=o(c);b&&p(tt({type:s,name:typeof b.name=="string"?b.name:""}))});e.addEventListener("phase",t=>{const s=o(t),c=s&&typeof s.phase=="string"?s.phase:"";p(c==="composing"?"type":c==="tool"?"run":"think")}),e.addEventListener("reasoning",()=>p("think")),e.addEventListener("plan",()=>p("plan")),e.addEventListener("turn_end",t=>{const s=o(t);p(s&&s.success===!1&&!s.interrupted?"error":"done");const c=C;window.setTimeout(()=>{c===C&&j(h,"idle")},1500)}),e.addEventListener("retract",()=>p("error")),e.addEventListener("index",()=>p("find")),e.addEventListener("setup_done",()=>p("done"));for(const t of["text","reasoning","phase","tool_start","plan"])e.addEventListener(t,s=>{const c=o(s);c&&c.sessionId&&w(c.sessionId,"working")});e.addEventListener("question",t=>{const s=o(t);s&&s.sessionId&&w(s.sessionId,"waiting")}),e.addEventListener("turn_end",t=>{const s=o(t);s&&s.sessionId&&w(s.sessionId,s.success===!1&&!s.interrupted?"failed":"idle")});for(const t of["tool_start","tool_end","phase","plan","question","meta","log","retract","index"])e.addEventListener(t,s=>{const c=o(s);c&&l(c)&&T(t,c)});e.addEventListener("text",t=>{const s=o(t);s&&l(s)&&T("text",s)}),e.addEventListener("lsp",t=>{const s=o(t);s&&Je(s)}),e.addEventListener("files",()=>D()),e.addEventListener("plugins",()=>Xe()),e.addEventListener("config",()=>{M()}),e.addEventListener("command",t=>{const s=o(t);s&&typeof s.text=="string"&&le(s.text,s.sessionId)}),e.addEventListener("state",t=>{const s=o(t);s&&(U(!!s.busy),ot(s))}),e.addEventListener("sessions",t=>{const s=o(t);s&&Array.isArray(s.sessions)&&(u.sessions=s.sessions,I())}),e.addEventListener("usage",t=>{const s=o(t);if(!s||!s.usage||typeof s.usage!="object")return;const c=s.usageSessionId||s.sessionId||null;ee(c,s.usage)&&F(u.host)}),e.addEventListener("error",t=>{if(!t.data)return;const s=o(t);s&&(s.sessionId&&w(s.sessionId,"failed"),ce(s),T("error",s))})}function Q(){document.documentElement.lang=Y.locale;const n=(d,r)=>{const t=document.getElementById(d);t&&(t.textContent=r)};n("planHeaderLabel",a("web.sidebar.plan")),n("usageHeaderLabel",a("web.sidebar.session")+" · "+a("web.sidebar.usage")),n("envHeaderLabel",a("web.sidebar.env")),n("settingsTitle",a("web.settings.title")),n("sessActiveHeader",a("web.sessions.active")),n("sessRecentHeader",a("web.sessions.recent")),n("hintEnter",a("web.input.enter")),n("hintShift",a("web.input.shift_enter")),n("hintEsc",a("web.input.esc")),n("hintAttach",a("web.input.attach")),n("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,s]=i[d];A(r,t,d==="closeSettings"||d==="sessNew"||d==="sessRefresh"?s:null),r.title=s,r.setAttribute("aria-label",s)});const o=document.getElementById("settingsIcon");o&&A(o,"settings",null),v&&(v.placeholder=a("web.input.placeholder"),v.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))}),L(),I(),He(),W(),V.querySelector(".empty")&&y(),_&&!_.hidden&&x(!0),N(void 0,!0)}function rt(n){const e=Le(),i=r=>u.sessions.some(t=>t.id===r),o=r=>{const t=u.sessions.find(s=>s.id===r);return t&&t.name?String(t.name):r},l=(n&&i(n)?n:e.active&&i(e.active)?e.active:e.ids.find(i))||null,d=[];n&&i(n)&&d.push(n),l&&!d.includes(l)&&d.push(l);for(const r of e.ids)i(r)&&!d.includes(r)&&d.push(r);if(d.length){E(d[0],o(d[0]),"session",!0);for(const r of d.slice(1))E(r,o(r),"session",!1,!1)}}async function dt(){$e();const n=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(),y(),L(),xe(),await M(),Be(m.config&&m.config.ui?m.config.ui.activityRail:void 0),st(),Me(),Ye(),W(),Qe({insertPath:e=>Ze("`"+e+"` ")}),await Te(),await Ce(),await H(),await ke(),await _e(),await Ae(),rt(n);try{await J()}catch(e){console.error("initial refresh failed",e)}}finally{K()}at()}function K(){const n=document.getElementById("bootSplash");!n||n.hidden||(n.classList.add("hide"),window.setTimeout(()=>{n.hidden=!0},500))}S.applyStaticI18n=Q,dt();
@@ -9,8 +9,29 @@
9
9
  </head>
10
10
  <body>
11
11
 
12
+ <script>
13
+ // Apply the stored theme before the boot splash paints (no dark flash on light).
14
+ (function () {
15
+ try {
16
+ var theme = localStorage.getItem("mma-theme") || "auto";
17
+ var light = theme === "light" ||
18
+ (theme === "auto" && window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches);
19
+ if (light) document.body.classList.add("light");
20
+ } catch (e) { /* ignore */ }
21
+ })();
22
+ </script>
23
+
12
24
  <div class="setup-gate" id="setupGate" hidden></div>
13
25
 
26
+ <div class="splash" id="bootSplash" role="status" aria-live="polite">
27
+ <span class="mdl bs-face c-dim st-load" aria-hidden="true"><span class="eyes"><i class="eye left"></i><i class="eye right"></i></span></span>
28
+ <div class="bs-cap">
29
+ <div class="l1" id="bootCap">connecting…</div>
30
+ <div class="l2">micro-models-agent</div>
31
+ </div>
32
+ <div class="bs-prog" aria-hidden="true"><i></i></div>
33
+ </div>
34
+
14
35
  <div class="bar-top">
15
36
  <button class="icon-btn primary" id="newChat" title="new chat" aria-label="new chat">+</button>
16
37
  <div class="dd" id="sessDD">
@@ -66,6 +87,7 @@
66
87
  </div>
67
88
  </div>
68
89
  <div class="selectors">
90
+ <span class="composer-face" id="composerFace" aria-hidden="true"></span>
69
91
  <div class="sel" data-sel="provider">
70
92
  <button id="selProviderBtn">provider: <b>—</b> ▾</button>
71
93
  <div class="menu" id="selProviderMenu"></div>
@@ -104,30 +126,31 @@
104
126
  <span class="sb-ico" data-icon="session" aria-hidden="true"></span>
105
127
  <span class="sb-dot" aria-hidden="true"></span>
106
128
  </button>
107
- <button class="sb-tab" type="button" role="tab" data-tab="map" aria-selected="false" aria-controls="pane-map" title="Map">
108
- <span class="sb-ico" data-icon="map" aria-hidden="true"></span>
109
- <span class="sb-dot" aria-hidden="true"></span>
110
- </button>
111
- <button class="sb-tab" type="button" role="tab" data-tab="trace" aria-selected="false" aria-controls="pane-trace" title="Trace">
112
- <span class="sb-ico" data-icon="trace" aria-hidden="true"></span>
129
+ <button class="sb-tab" type="button" role="tab" data-tab="files" aria-selected="false" aria-controls="pane-files" title="Files">
130
+ <span class="sb-ico" data-icon="files" aria-hidden="true"></span>
113
131
  <span class="sb-dot" aria-hidden="true"></span>
114
132
  </button>
115
133
  <button class="sb-tab" type="button" role="tab" data-tab="lsp" aria-selected="false" aria-controls="pane-lsp" title="LSP">
116
134
  <span class="sb-ico" data-icon="lsp" aria-hidden="true"></span>
117
135
  <span class="sb-dot" aria-hidden="true"></span>
118
136
  </button>
119
- <button class="sb-tab" type="button" role="tab" data-tab="files" aria-selected="false" aria-controls="pane-files" title="Files">
120
- <span class="sb-ico" data-icon="files" aria-hidden="true"></span>
137
+ <button class="sb-tab" type="button" role="tab" data-tab="trace" aria-selected="false" aria-controls="pane-trace" title="Trace">
138
+ <span class="sb-ico" data-icon="trace" aria-hidden="true"></span>
121
139
  <span class="sb-dot" aria-hidden="true"></span>
122
140
  </button>
123
141
  <button class="sb-tab" type="button" role="tab" data-tab="plugins" aria-selected="false" aria-controls="pane-plugins" title="Plugins">
124
142
  <span class="sb-ico" data-icon="plugins" aria-hidden="true"></span>
125
143
  <span class="sb-dot" aria-hidden="true"></span>
126
144
  </button>
145
+ <button class="sb-tab" type="button" role="tab" data-tab="map" aria-selected="false" aria-controls="pane-map" title="Map">
146
+ <span class="sb-ico" data-icon="map" aria-hidden="true"></span>
147
+ <span class="sb-dot" aria-hidden="true"></span>
148
+ </button>
127
149
  </div>
128
150
  <div class="sb-name" id="sbName"><span class="sb-ico" data-icon="session" aria-hidden="true"></span><span id="sbNameText">Session</span></div>
129
151
 
130
152
  <div class="sb-pane" id="pane-session" role="tabpanel" data-pane="session">
153
+ <div class="sb-loadface" aria-hidden="true"><span class="mdl c-dim st-think"><span class="eyes"><i class="eye left"></i><i class="eye right"></i></span></span></div>
131
154
  <div class="sec plan-sec">
132
155
  <button class="sec-h" type="button" data-collapse aria-expanded="true"><span class="arrow">▾</span> <span id="planHeaderLabel">plan</span> <span class="n" id="planCount"></span></button>
133
156
  <div class="sec-body" id="plan"></div>
@@ -149,6 +172,7 @@
149
172
  </div>
150
173
 
151
174
  <div class="sb-pane" id="pane-trace" role="tabpanel" data-pane="trace" hidden>
175
+ <div class="trc-face" aria-hidden="true"></div>
152
176
  <div class="trace-list" id="traceList"></div>
153
177
  <div class="trc-detail" id="traceDetail" hidden>
154
178
  <div class="detail-h"><span class="g" id="detailGlyph">◇</span><span id="detailTitle"></span><button class="x linkbtn" type="button" id="detailClose">✕</button></div>
@@ -169,20 +193,36 @@
169
193
  </div>
170
194
 
171
195
  <div class="sb-pane" id="pane-files" role="tabpanel" data-pane="files" hidden>
172
- <div class="files-tree" id="filesTree"></div>
173
- <div class="files-detail" id="filesDetail" hidden>
174
- <div class="detail-h">
175
- <span class="g" id="filesGlyph" aria-hidden="true">◆</span>
176
- <span id="filesTitle"></span>
177
- <button class="x linkbtn" type="button" id="filesAttach" title="attach path">◧</button>
178
- <button class="x linkbtn" type="button" id="filesClose" title="close">✕</button>
196
+ <div class="files-body">
197
+ <div class="files-side" id="filesSide">
198
+ <div class="files-bar" id="filesBar">
199
+ <button class="fbtn" type="button" id="filesCollapseAll" aria-label="collapse all"></button>
200
+ <button class="fbtn" type="button" id="filesExpandAll" aria-label="expand all"></button>
201
+ <button class="fbtn" type="button" id="filesRefresh" aria-label="refresh"></button>
202
+ </div>
203
+ <div class="files-tree" id="filesTree" role="tree" tabindex="0" aria-label="files"></div>
204
+ </div>
205
+ <div class="files-splitter" id="filesSplitter" role="separator" aria-orientation="vertical" tabindex="0" title="tree width"></div>
206
+ <div class="files-detail" id="filesDetail" hidden>
207
+ <div class="detail-h">
208
+ <button class="fbtn" type="button" id="filesBack" aria-label="back" disabled></button>
209
+ <button class="fbtn" type="button" id="filesFwd" aria-label="forward" disabled></button>
210
+ <span class="fcrumbs" id="filesCrumbs"></span>
211
+ <button class="fbtn" type="button" id="filesAttach" aria-label="attach path"></button>
212
+ <button class="fbtn" type="button" id="filesWide" aria-label="hide tree"></button>
213
+ <button class="fbtn" type="button" id="filesClose" aria-label="close"></button>
214
+ </div>
215
+ <div class="detail-b" id="filesBody"></div>
179
216
  </div>
180
- <div class="detail-b" id="filesBody"></div>
181
217
  </div>
182
218
  </div>
183
219
  </div>
184
220
  </div>
185
221
 
222
+ <!-- Drawer backdrop for ≤1024px, where the sidebar overlays the chat
223
+ (visible only under body.sb-open, see styles.css + sidebar/drawer.ts). -->
224
+ <div class="sb-backdrop" id="sbBackdrop"></div>
225
+
186
226
  <div class="overlay" id="overlay">
187
227
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="settingsTitle">
188
228
  <div class="modal-top"><span class="modal-top-icon" id="settingsIcon" aria-hidden="true"></span> <span id="settingsTitle">settings</span> <span class="saved" id="saveIndicator" role="status" aria-live="polite"></span> <button class="x linkbtn" id="closeSettings" type="button">✕ close</button></div>
@@ -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
- export const msgs=document.getElementById("msgs"),inp=document.getElementById("inp"),tabstripEl=document.getElementById("tabstrip"),planEl=document.getElementById("plan"),planCountEl=document.getElementById("planCount"),usageEl=document.getElementById("usage"),envEl=document.getElementById("env"),wsLine=document.getElementById("wsLine"),saveIndicator=document.getElementById("saveIndicator"),attachStrip=document.getElementById("attachStrip"),attachErr=document.getElementById("attachErr"),imgPicker=document.getElementById("imgPicker"),connStatus=document.getElementById("connStatus"),staleBar=document.getElementById("staleBar"),liveEl=document.getElementById("live"),sendBtn=document.getElementById("sendBtn");export function announce(e){!liveEl||!e||(liveEl.textContent="",setTimeout(()=>{liveEl.textContent=e},30))}export function el(e,a,t){const n=document.createElement(e);return a&&(n.className=a),t!=null&&(n.textContent=String(t)),n}let i=null,s=!1;function h(){return i||(i=el("div","ctip"),i.setAttribute("role","tooltip"),document.body.appendChild(i)),i}function c(){i&&i.classList.remove("show")}function p(){s||(s=!0,window.addEventListener("scroll",c,!0),window.addEventListener("resize",c))}export function attachTooltip(e,a){const t=()=>{const n=h();n.replaceChildren(a()),n.classList.add("show");const o=e.getBoundingClientRect(),r=n.getBoundingClientRect();let d=o.left+o.width/2-r.width/2;d=Math.max(6,Math.min(d,window.innerWidth-r.width-6));let l=o.top-r.height-7;l<6&&(l=o.bottom+7),n.style.left=d+"px",n.style.top=l+"px"};e.addEventListener("mouseenter",t),e.addEventListener("mouseleave",c),e.addEventListener("focus",t),e.addEventListener("blur",c),p()}export const SVG_NS="http://www.w3.org/2000/svg",ICONS={plus:[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]],menu:[["line",{x1:4,y1:6,x2:20,y2:6}],["line",{x1:4,y1:12,x2:20,y2:12}],["line",{x1:4,y1:18,x2:20,y2:18}]],settings:[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"}],["circle",{cx:12,cy:12,r:3}]],image:[["rect",{width:18,height:18,x:3,y:3,rx:2,ry:2}],["circle",{cx:9,cy:9,r:2}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}]],x:[["path",{d:"M18 6 6 18"}],["path",{d:"m6 6 12 12"}]],trash:[["path",{d:"M3 6h18"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}],["line",{x1:10,y1:11,x2:10,y2:17}],["line",{x1:14,y1:11,x2:14,y2:17}]],refresh:[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"}],["path",{d:"M8 21H3v-5"}]],"chevron-down":[["path",{d:"m6 9 6 6 6-6"}]],"chevron-left":[["path",{d:"m15 18-6-6 6-6"}]],"chevron-right":[["path",{d:"m9 18 6-6-6-6"}]],search:[["circle",{cx:11,cy:11,r:8}],["path",{d:"m21 21-4.3-4.3"}]],"list-checks":[["path",{d:"m3 17 2 2 4-4"}],["path",{d:"m3 7 2 2 4-4"}],["path",{d:"M13 6h8"}],["path",{d:"M13 12h8"}],["path",{d:"M13 18h8"}]],"layout-grid":[["rect",{width:7,height:7,x:3,y:3,rx:1}],["rect",{width:7,height:7,x:14,y:3,rx:1}],["rect",{width:7,height:7,x:14,y:14,rx:1}],["rect",{width:7,height:7,x:3,y:14,rx:1}]],code:[["path",{d:"m16 18 6-6-6-6"}],["path",{d:"m8 6-6 6 6 6"}]],plug:[["path",{d:"M12 22v-5"}],["path",{d:"M9 8V2"}],["path",{d:"M15 8V2"}],["path",{d:"M18 8v5a6 6 0 0 1-12 0V8Z"}]],activity:[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"}]],"arrow-up":[["path",{d:"M12 19V5"}],["path",{d:"m5 12 7-7 7 7"}]],panel:[["rect",{width:18,height:18,x:3,y:3,rx:2,ry:2}],["line",{x1:15,y1:3,x2:15,y2:21}]],square:[["rect",{width:14,height:14,x:5,y:5,rx:2}]],copy:[["rect",{width:14,height:14,x:8,y:8,rx:2,ry:2}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],check:[["path",{d:"M20 6 9 17l-5-5"}]],info:[["circle",{cx:12,cy:12,r:10}],["path",{d:"M12 16v-4"}],["path",{d:"M12 8h.01"}]],"folder-tree":[["path",{d:"M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.9-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M3 5a2 2 0 0 1 2-2h3"}],["path",{d:"M3 3v13a3 3 0 0 0 3 3h4"}]],folder:[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}]],file:[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4"}]],paperclip:[["path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"}]]};export function icon(e,a){const t=document.createElementNS(SVG_NS,"svg");return t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.setAttribute("stroke","currentColor"),t.setAttribute("stroke-width","2"),t.setAttribute("stroke-linecap","round"),t.setAttribute("stroke-linejoin","round"),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),t.setAttribute("class","svg-icon"+(a?" "+a:"")),(ICONS[e]||[]).forEach(([n,o])=>{const r=document.createElementNS(SVG_NS,n);Object.keys(o).forEach(d=>r.setAttribute(d,String(o[d]))),t.appendChild(r)}),t}export function setIcon(e,a,t){return e?(e.textContent="",e.appendChild(icon(a)),t!=null&&e.appendChild(el("span","",t)),e):null}export function makeToggle(e,a){const t=el("button","toggle"+(e?" on":""));return t.type="button",t.setAttribute("role","switch"),t.setAttribute("aria-checked",e?"true":"false"),t.addEventListener("click",()=>{const n=!t.classList.contains("on");a(n)&&(t.classList.toggle("on",n),t.setAttribute("aria-checked",n?"true":"false"))}),t}export function link(e,a,t){const n=el("button","linkbtn"+(a?" "+a:""),e);return n.type="button",n.addEventListener("click",t),n}export function inlineTextNodes(e){const a=document.createDocumentFragment();return String(e??"").replace(/^-\s*/,"").split("**").forEach((n,o)=>{if(n!=="")if(o%2===1){const r=document.createElement("b");r.textContent=n,a.appendChild(r)}else a.appendChild(document.createTextNode(n))}),a}
1
+ export const msgs=document.getElementById("msgs"),inp=document.getElementById("inp"),tabstripEl=document.getElementById("tabstrip"),planEl=document.getElementById("plan"),planCountEl=document.getElementById("planCount"),usageEl=document.getElementById("usage"),envEl=document.getElementById("env"),wsLine=document.getElementById("wsLine"),saveIndicator=document.getElementById("saveIndicator"),attachStrip=document.getElementById("attachStrip"),attachErr=document.getElementById("attachErr"),imgPicker=document.getElementById("imgPicker"),connStatus=document.getElementById("connStatus"),staleBar=document.getElementById("staleBar"),liveEl=document.getElementById("live"),sendBtn=document.getElementById("sendBtn");export function announce(e){!liveEl||!e||(liveEl.textContent="",setTimeout(()=>{liveEl.textContent=e},30))}export function el(e,a,t){const n=document.createElement(e);return a&&(n.className=a),t!=null&&(n.textContent=String(t)),n}let d=null,h=!1;function p(){return d||(d=el("div","ctip"),d.setAttribute("role","tooltip"),document.body.appendChild(d)),d}function c(){d&&d.classList.remove("show")}function s(){h||(h=!0,window.addEventListener("scroll",c,!0),window.addEventListener("resize",c))}export function attachTooltip(e,a){const t=()=>{const n=p();n.replaceChildren(a()),n.classList.add("show");const r=e.getBoundingClientRect(),o=n.getBoundingClientRect();let i=r.left+r.width/2-o.width/2;i=Math.max(6,Math.min(i,window.innerWidth-o.width-6));let l=r.top-o.height-7;l<6&&(l=r.bottom+7),n.style.left=i+"px",n.style.top=l+"px"};e.addEventListener("mouseenter",t),e.addEventListener("mouseleave",c),e.addEventListener("focus",t),e.addEventListener("blur",c),s()}export const SVG_NS="http://www.w3.org/2000/svg",ICONS={plus:[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]],menu:[["line",{x1:4,y1:6,x2:20,y2:6}],["line",{x1:4,y1:12,x2:20,y2:12}],["line",{x1:4,y1:18,x2:20,y2:18}]],settings:[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"}],["circle",{cx:12,cy:12,r:3}]],image:[["rect",{width:18,height:18,x:3,y:3,rx:2,ry:2}],["circle",{cx:9,cy:9,r:2}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}]],x:[["path",{d:"M18 6 6 18"}],["path",{d:"m6 6 12 12"}]],trash:[["path",{d:"M3 6h18"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}],["line",{x1:10,y1:11,x2:10,y2:17}],["line",{x1:14,y1:11,x2:14,y2:17}]],refresh:[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"}],["path",{d:"M21 3v5h-5"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"}],["path",{d:"M8 21H3v-5"}]],"chevron-down":[["path",{d:"m6 9 6 6 6-6"}]],"chevron-left":[["path",{d:"m15 18-6-6 6-6"}]],"chevron-right":[["path",{d:"m9 18 6-6-6-6"}]],search:[["circle",{cx:11,cy:11,r:8}],["path",{d:"m21 21-4.3-4.3"}]],"list-checks":[["path",{d:"m3 17 2 2 4-4"}],["path",{d:"m3 7 2 2 4-4"}],["path",{d:"M13 6h8"}],["path",{d:"M13 12h8"}],["path",{d:"M13 18h8"}]],"layout-grid":[["rect",{width:7,height:7,x:3,y:3,rx:1}],["rect",{width:7,height:7,x:14,y:3,rx:1}],["rect",{width:7,height:7,x:14,y:14,rx:1}],["rect",{width:7,height:7,x:3,y:14,rx:1}]],code:[["path",{d:"m16 18 6-6-6-6"}],["path",{d:"m8 6-6 6 6 6"}]],plug:[["path",{d:"M12 22v-5"}],["path",{d:"M9 8V2"}],["path",{d:"M15 8V2"}],["path",{d:"M18 8v5a6 6 0 0 1-12 0V8Z"}]],activity:[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"}]],"arrow-up":[["path",{d:"M12 19V5"}],["path",{d:"m5 12 7-7 7 7"}]],"arrow-left":[["path",{d:"M19 12H5"}],["path",{d:"m12 19-7-7 7-7"}]],"arrow-right":[["path",{d:"M5 12h14"}],["path",{d:"m12 5 7 7-7 7"}]],"chevrons-down-up":[["path",{d:"m7 15 5 5 5-5"}],["path",{d:"m7 9 5-5 5 5"}]],"chevrons-up-down":[["path",{d:"m7 9 5-5 5 5"}],["path",{d:"m7 15 5 5 5-5"}]],panel:[["rect",{width:18,height:18,x:3,y:3,rx:2,ry:2}],["line",{x1:15,y1:3,x2:15,y2:21}]],"panel-left":[["rect",{width:18,height:18,x:3,y:3,rx:2,ry:2}],["line",{x1:9,y1:3,x2:9,y2:21}]],square:[["rect",{width:14,height:14,x:5,y:5,rx:2}]],copy:[["rect",{width:14,height:14,x:8,y:8,rx:2,ry:2}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],check:[["path",{d:"M20 6 9 17l-5-5"}]],info:[["circle",{cx:12,cy:12,r:10}],["path",{d:"M12 16v-4"}],["path",{d:"M12 8h.01"}]],"folder-tree":[["path",{d:"M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.9-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"}],["path",{d:"M3 5a2 2 0 0 1 2-2h3"}],["path",{d:"M3 3v13a3 3 0 0 0 3 3h4"}]],folder:[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"}]],file:[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4"}]],paperclip:[["path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"}]]};export function icon(e,a){const t=document.createElementNS(SVG_NS,"svg");return t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.setAttribute("stroke","currentColor"),t.setAttribute("stroke-width","2"),t.setAttribute("stroke-linecap","round"),t.setAttribute("stroke-linejoin","round"),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),t.setAttribute("class","svg-icon"+(a?" "+a:"")),(ICONS[e]||[]).forEach(([n,r])=>{const o=document.createElementNS(SVG_NS,n);Object.keys(r).forEach(i=>o.setAttribute(i,String(r[i]))),t.appendChild(o)}),t}export function setIcon(e,a,t){return e?(e.textContent="",e.appendChild(icon(a)),t!=null&&e.appendChild(el("span","",t)),e):null}export function makeToggle(e,a){const t=el("button","toggle"+(e?" on":""));return t.type="button",t.setAttribute("role","switch"),t.setAttribute("aria-checked",e?"true":"false"),t.addEventListener("click",()=>{const n=!t.classList.contains("on");a(n)&&(t.classList.toggle("on",n),t.setAttribute("aria-checked",n?"true":"false"))}),t}export function link(e,a,t){const n=el("button","linkbtn"+(a?" "+a:""),e);return n.type="button",n.addEventListener("click",t),n}export function inlineTextNodes(e){const a=document.createDocumentFragment();return String(e??"").replace(/^-\s*/,"").split("**").forEach((n,r)=>{if(n!=="")if(r%2===1){const o=document.createElement("b");o.textContent=n,a.appendChild(o)}else a.appendChild(document.createTextNode(n))}),a}
@@ -1 +1 @@
1
- import{msgs as s,el as i,setIcon as C}from"../dom.js";import{t as l,pluralCount as w}from"../i18n.js";import{feed as o,tabs as a}from"../store.js";import{formatMs as x,verbosity as T}from"../format.js";import{copyText as v}from"../../md.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=i("div","think"+(e===!1?"":" live")),r=i("button","think-h");r.type="button",r.setAttribute("aria-expanded","false"),r.appendChild(i("span","g","✦")),r.appendChild(i("span","lab",l("web.action.thinking"))),t&&r.appendChild(i("span","dur",""));const d=i("span","tog");C(d,"chevron-right",l("web.action.show")),r.appendChild(d);const p=i("div","think-body hidden");return n.appendChild(r),n.appendChild(p),{wrap:n,head:r,body:p}}export function copyAction(t,e){const n=i("button","act-btn",l(e));return n.type="button",n.addEventListener("click",()=>{v(t).then(r=>{n.textContent=r?"✓ "+l("web.code.copied"):"⚠ "+l("web.code.copy_failed"),window.setTimeout(()=>{n.textContent=l(e)},1400)})}),n}export function appendActionsRow(t,e,n){const r=String(e??""),d=String(n??"");if(!t||!r.trim()&&!d.trim())return;const p=i("div","msg-actions");r.trim()&&p.appendChild(copyAction(r,"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=i("div","empty");return t.appendChild(i("span","g","✦")),t.appendChild(document.createTextNode(" "+l("web.empty.title"))),t.appendChild(document.createElement("br")),t.appendChild(i("span","hint",l("web.empty.hint"))),t}export function makeFeed(){const t=i("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=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 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=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 c=null;function g(){if(c)return c;const t=document.querySelector(".chat");return t?(c=i("button","to-latest"),c.type="button",c.hidden=!0,c.addEventListener("click",()=>scrollToBottom(!0)),t.appendChild(c),c):null}function m(){const t=g();t&&(t.textContent="↓ "+l("web.scroll.latest"),t.hidden=!1,s.classList.add("unfollowed"))}function f(){c&&(c.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=i("div","busy");e.appendChild(i("span","spin","✦")),e.appendChild(i("span","lab",l("web.action.thinking")));const n=i("span","busytime","· 0s");e.appendChild(n),e.appendChild(i("span","","· "+l("web.input.esc"))),appendTo(t,e);const r=Date.now();o.busyTimer=setInterval(()=>{if(!e.parentNode){clearInterval(o.busyTimer),o.busyTimer=null;return}n.textContent="· "+Math.round((Date.now()-r)/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(r=>r.classList.add("fold-out")),window.setTimeout(()=>t.forEach(r=>r.remove()),160);const e=i("div","grouped");e.appendChild(i("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 p,envEl as x,wsLine as m,el as s,icon as v,attachTooltip as k}from"../dom.js";import{I18N as S,t as a,pluralCount as y}from"../i18n.js";import{feed as g,settings as E}from"../store.js";import{fmtTokens as b}from"../format.js";import{copyText as L}from"../../md.js";function f(e,o,n){const t=s("div","kv");return t.appendChild(s("span","",e)),t.appendChild(s("b",n||"",o)),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 M(e,o,n,t){const i=document.createDocumentFragment();return i.appendChild(s("i","ctip-dot "+e)),i.appendChild(s("span","ctip-label",a(o))),i.appendChild(s("b","",b(n))),i.appendChild(s("span","ctip-pct",Math.round(n/t*100)+"%")),i}const U=new Map;function A(e,o,n){const t=U.get(o);if(U.set(o,n),t===void 0||t===n){e.style.flexGrow=String(n);return}e.style.flexGrow=String(t);const i=()=>{e.style.flexGrow=String(n)};typeof requestAnimationFrame=="function"?requestAnimationFrame(i):i()}function I(e,o,n){const t=s("div","bar segbar"),i=e.contextBreakdown;if(!i||typeof i.window!="number"||i.window<=0){const d=s("i");return d.style.flex="0 0 "+Math.min(100,Math.round(o/n*100))+"%",t.appendChild(d),t}const r=Math.max(i.window,i.used+i.reserve);for(const[d,l,c]of _(i)){if(!c||c<=0)continue;const u=s("i",d);u.style.flexBasis="0",A(u,d,c),u.tabIndex=0,u.setAttribute("aria-label",[a(l),b(c),Math.round(c/r*100)+"%"].join(" · ")),k(u,()=>M(d,l,c,r)),t.appendChild(u)}return t}function R(e){const o=s("div","seglegend"),n=e.contextBreakdown;if(!n)return o;const t=Math.max(n.window,n.used+n.reserve);for(const[i,r,d]of _(n)){if(!d||d<=0)continue;const l=s("span","segl-item");l.tabIndex=0,l.setAttribute("aria-label",[a(r),b(d),Math.round(d/t*100)+"%"].join(" · ")),l.appendChild(s("i","segl-dot "+i)),l.appendChild(s("span","segl-label",a(r))),l.appendChild(s("b","",b(d))),k(l,()=>M(i,r,d,t)),o.appendChild(l)}return o}function T(e){const o=s("div","kv");o.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(v("copy")),t.addEventListener("click",()=>{L(e).then(i=>{t.replaceChildren(v(i?"check":"copy")),setTimeout(()=>t.replaceChildren(v("copy")),1500)})}),n.appendChild(t)}return o.appendChild(n),o}export function renderUsage(e){const o=E.config||{},n=JSON.stringify([e.sessionId,e.model,e.contextUsed,e.contextLimit,e.cacheHitRate,e.costUsd,e.compactionCount,e.contextQuality,e.contextProbe,e.contextBreakdown,o.model,o.contextWindow,S.locale]);if(n===g.lastUsageSig)return;g.lastUsageSig=n,p.textContent="";const t=e.sessionId?String(e.sessionId):"";p.appendChild(T(t)),p.appendChild(f(a("web.usage.model"),e.model||o.model||"—"));const i=typeof e.contextUsed=="number"?e.contextUsed:null,r=typeof e.contextLimit=="number"?e.contextLimit:o.contextWindow||null;p.appendChild(f(a("web.usage.context"),i!=null&&r?b(i)+" / "+b(r):"—")),i!=null&&r&&(p.appendChild(I(e,i,r)),p.appendChild(R(e)));const d=typeof e.cacheHitRate=="number"?e.cacheHitRate:null;p.appendChild(f(a("web.usage.cache"),d!=null?Math.round(d*100)+"%":"—",d!=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 l=typeof e.contextQuality=="number"?e.contextQuality:null;p.appendChild(f(a("web.usage.quality"),l!=null?Math.round(l)+"%":"—",l!=null?"":"dim"));let c="—",u="dim";const w=e.contextProbe;if(w&&typeof w.actual=="number"){const h=b(w.actual),C=e.contextLimit||o.contextWindow||null;C&&w.actual<C?(c=a("web.usage.probe_low",{actual:h,configured:b(C)}),u="wn"):(c=a("web.usage.probe_loaded",{actual:h}),u="")}p.appendChild(f(a("web.usage.probe"),c,u))}export function renderWorkspace(e){const o=e.workspace||"";if(m.dataset.ws===o||(m.dataset.ws=o,m.textContent="",m.hidden=!o,!o))return;m.appendChild(s("span","ws-k",a("web.env.dir")));const n=s("span","ws-v",o);n.title=o,m.appendChild(n)}function B(e,o){const n=s("div","env-item");return n.appendChild(s("div","env-k",e)),o.forEach(t=>{const i=s("div","env-v",t);i.title=t,n.appendChild(i)}),n}export function renderEnvironment(e){const o=!!(e.instructions&&e.instructions.disabled),n=e.instructions&&!o?e.instructions.files||[]:[],t=e.projectMap||null,i=JSON.stringify([o,n,t,S.locale]);i!==g.lastEnvSig&&(g.lastEnvSig=i,x.textContent="",x.appendChild(B(a("web.env.agents"),o?[a("web.env.agents_disabled")]:n.length?n:[a("web.env.agents_none")])),x.appendChild(B(a("web.env.map"),t&&t.indexed?[[y("web.env.files",t.files),y("web.env.symbols",t.symbols),y("web.env.tokens",t.tokens)].join(" · ")]:[a("web.env.map_not_indexed")])))}
1
+ import{usageEl as c,envEl as x,wsLine as A,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 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 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 W(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 F(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",W(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 I(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 R(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",()=>{L(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=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===g.lastUsageSig)return;g.lastUsageSig=n,c.textContent="";const t=e.sessionId?String(e.sessionId):"";c.appendChild(R(t)),c.appendChild(f(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(f(a("web.usage.context"),o!=null&&d?b(o)+" / "+b(d):"—")),o!=null&&d&&(c.appendChild(F(e,o,d)),c.appendChild(I(e)));const l=typeof e.cacheHitRate=="number"?e.cacheHitRate:null;c.appendChild(f(a("web.usage.cache"),l!=null?Math.round(l*100)+"%":"—",l!=null?"":"dim")),c.appendChild(f(a("web.usage.cost"),typeof e.costUsd=="number"?"$"+e.costUsd.toFixed(4):"—")),c.appendChild(f(a("web.usage.compactions"),"⟳ "+(e.compactionCount||0),e.compactionCount?"wn":"dim"));const r=typeof e.contextQuality=="number"?e.contextQuality:null;c.appendChild(f(a("web.usage.quality"),r!=null?Math.round(r)+"%":"—",r!=null?"":"dim"));let p="—",u="dim";const m=e.contextProbe;if(m&&typeof m.actual=="number"){const y=b(m.actual),w=e.contextWindow||i.contextWindow||null;w&&m.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(f(a("web.usage.probe"),p,u))}export function renderWorkspace(e){E(A,{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")])))}