Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a47a7d4
build(webui): bundle the server and gate the artifact
weekbin Sep 23, 2026
5585844
chore(webui): declare the shared workspace dependency and ignore buil…
weekbin Sep 23, 2026
c84e683
feat(webui): rebuild the frontend on the Next.js desktop stack
weekbin Sep 23, 2026
fa61de5
refactor(webui): serve one static root and delete the unreachable leg…
weekbin Sep 23, 2026
9b337c2
refactor(webui): move the API onto Hono
weekbin Sep 23, 2026
8f26821
refactor(webui): split the server libraries by responsibility
weekbin Sep 23, 2026
6a95ff6
test(webui): reorganise the test tree by subject
weekbin Sep 23, 2026
64d71e3
docs(webui): align the documentation and comments with the implementa…
weekbin Sep 23, 2026
6287a84
chore: regenerate the public source inventory
weekbin Sep 23, 2026
e95ec0d
docs(webui): correct the comments and notes the ACP change falsified
weekbin Sep 23, 2026
47a4369
fix(webui): stop the account card from inventing an identity
weekbin Sep 23, 2026
a459f5e
feat(webui): read the account card from the engine instead of inventi…
weekbin Sep 23, 2026
3a01273
refactor(webui): drop the account menu's profile card
weekbin Sep 23, 2026
cfc556b
fix(webui): read the plan quota from the engine, not a stored key
weekbin Sep 23, 2026
5cd78a1
fix(webui): reuse one Hono app per request and stop promising a kill
weekbin Sep 23, 2026
7e3ec09
fix(webui): give every tool update an owner in the transcript
weekbin Sep 23, 2026
1af4bcf
fix(webui): drop the sidebar's dead controls and put the disclaimer i…
weekbin Sep 23, 2026
0952db7
fix(webui): name the engine's model and keep the composer alive
weekbin Sep 23, 2026
9625f57
fix(webui): count conversations, not the sub-agents under them
weekbin Sep 23, 2026
412a2f6
fix(webui): report the engine's own version, not a pinned constant
weekbin Sep 23, 2026
0af689c
chore(release): record the account route in the public source inventory
weekbin Sep 23, 2026
9397841
test(webui): stop two integration tests from using the operator's sta…
weekbin Sep 23, 2026
4606949
fix(webui): show both quota windows in the usage popover
weekbin Sep 23, 2026
ad9f22c
feat(webui): poll the plan quota instead of waiting to be asked
weekbin Sep 23, 2026
f3dab0e
fix(webui): stop naming a model the engine never named
weekbin Sep 23, 2026
2e8372a
refactor(webui): drop the account menu's Contact us and Learn more rows
weekbin Sep 23, 2026
1b249a5
fix(webui): probe the Windows installer and pnpm-root layouts for bet…
stevenjj33 Sep 23, 2026
cc1d0d0
fix(webui): report the engine version instead of a hardcoded 0.1.2
stevenjj33 Sep 23, 2026
72ff59c
test(webui): make config, fs-containment and router-boot tests run on…
stevenjj33 Sep 23, 2026
f153453
feat(webui): show when a session is running, and how it ended
weekbin Sep 23, 2026
62e0539
feat(webui): adopt antd, wearing the desktop's own theme and skin
weekbin Sep 24, 2026
7b8860c
fix(webui): the LAN gate returned a falsy verdict, and RPC reached th…
weekbin Sep 24, 2026
63fb65c
docs(webui): realign every document with the implementation
weekbin Sep 24, 2026
e64dac3
fix(webui): one live turn per cid, per engine session, and per MAX_CO…
weekbin Sep 24, 2026
4c30506
fix(webui): the exec-mode RPC dispatch, the full-chat leak, and the l…
weekbin Sep 24, 2026
4c2d994
fix(webui): last-write-wins on the client, and an unreachable "up" in…
weekbin Sep 24, 2026
290533e
fix(webui): bound every JSON request body
weekbin Sep 24, 2026
b997c1b
fix(webui): deliver attachments, and make the guards and the test hom…
weekbin Sep 24, 2026
1f4b6d7
chore(webui): keep this branch's transport when rebasing onto main
weekbin Sep 24, 2026
f674e00
test(webui): give the heavy upload test headroom under a full run
weekbin Sep 24, 2026
29827fe
test(windows): give the NTFS location check a realistic budget
weekbin Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
# Build outputs and dependency caches. These are produced by `pnpm install`
# (`node_modules/`, `.pnpm-store/`), `pnpm build` (`dist/`, the Next export
# under `packages/webui/webapp/out`, Next's dev cache under
# `packages/webui/webapp/.next/`, and TypeScript's incremental build files),
# and various tool caches (`.cache/`, `.turbo/`). Keeping every one of them
# out of the working tree is what stops an un-ignored build artifact from
# leaking into the source inventory: `scripts/source-inventory.mjs` scans
# the working tree and records every path it does not skip, so a missed
# pattern here would silently publish the artifact as source.
node_modules/
dist/
.turbo/
.cache/
.pnpm-store/

# TypeScript incremental build state. `tsc --incremental` (and Next's
# swc-loader) emit one next to every `tsconfig.json`; the root pattern
# catches every flavour (`*.tsbuildinfo` matches `tsconfig.tsbuildinfo`,
# `tsconfig.*.tsbuildinfo`, and the next-build-internal `.next/cache/.tsbuildinfo`).
*.tsbuildinfo

# Environment files. `.env.example` is the only one we keep: it documents
# the shape without holding values, and is the only file the standalone
# check expects to see.
.env
.env.*
!.env.example

# OS metadata — harmless but noisy; produced by Finder, Tarballs, etc.
.DS_Store

# Vendored research material kept inside the working tree for traceability.
# Desktop unpack (asar extract) — large, never published, regenerated by
# `packages/webui/desktop-unpacked/extract-asar.sh`. See SPEC.md inside that
# directory for the source revision that produced it.
packages/webui/desktop-unpacked/
37 changes: 35 additions & 2 deletions docs/webui.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ node dist/cli.js webui # dev Web UI on 127.0.0.1:18090
node dist/cli.js webui --port 8123 # keep the installed one free
```

### One-shot dev launcher (frontend + backend, hot reload)

When iterating on the Next.js frontend in `packages/webui/webapp/` you want both
the Node backend (port 18090, serves `/api/*`) and the Next dev server (port
18091, with HMR, proxies `/api/*` → 18090) running at once. `pnpm run webui:dev`
boots both in a single shell, prefixes their output so you can tell which side
is talking, and tears them down together on Ctrl+C:

```bash
pnpm run webui:dev # http://127.0.0.1:18091/ ← open this in the browser
```

It is a thin wrapper over `node scripts/dev-webui.mjs` with no extra
dependencies. Stop the official `mcode` runtime first if port 18090 is busy
(`pkill -f "dist/cli.js webui"`), or pass `--port 28090` to `mcode webui` and
export `MCODE_WEBUI_ORIGIN=http://127.0.0.1:28090` so the dev proxy targets
the right backend.

### Day-to-day webui commands (Next-aligned)

| Command | What it does |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `pnpm run webui:dev` | Start both the backend (`:18090`) and Next dev (`:18091`, HMR) together; Ctrl+C cleans up both. |
| `pnpm run webui:build` | `next build` the webapp (`packages/webui/webapp/out/` is the static export). |
| `pnpm run webui:start` | Serve the already-built webui via `node packages/webui/server.js` on `:18090` (no HMR). |
| `pnpm run webui:typecheck`| `tsc --noEmit` over the webapp's TS sources. |
| `pnpm run webui:test` | Run all webui unit tests — backend (`test:webui`) + frontend (`test:webapp`). |

`next start` is intentionally omitted: the webui ships as a `next export` static
build and the backend serves those files directly, so there is no Next server
runtime to start. ESLint is also not wired into the webapp yet — add it via
`npx next lint` once a `.eslintrc` is in place.

### Docker

The repository's Docker setup runs the branch in a **clean environment**: no
Expand Down Expand Up @@ -78,7 +111,7 @@ The canonical disclosure is [`packages/webui/references/SECURITY-NOTES.md`](../p

## Architecture

See [`packages/webui/docs/ARCHITECTURE.md`](../packages/webui/docs/ARCHITECTURE.md) for the runtime topology, request lifecycle, and WebSocket event-stream contract. In short: `server.js` bootstraps an HTTP server; `server/router.js` applies the gate chain (CORS → origin/CSRF → LAN → token → rate limit → read-only) and dispatches to `server/routes/*`; `server/lib/*` holds one-concern modules; `acp.mjs` is the ACP client spawning the engine; `public/` is the SPA.
See [`packages/webui/docs/ARCHITECTURE.md`](../packages/webui/docs/ARCHITECTURE.md) for the runtime topology, request lifecycle, and SSE contract. In short: `packages/webui/server.js` registers the workspace import resolver and delegates to `server/bootstrap.js`; `server/router.js` applies the gate chain (CORS → origin/CSRF → LAN → token → rate limit → read-only) and dispatches to `server/routes/*`; `server/lib/*` holds one-concern modules; `acp.mjs` is the ACP client spawning the engine; `webapp/out/` (the Next static export) is the UI, with `public/trajectory/` and `public/auth-gate.html` (served from the export root) as the only remaining legacy assets.

## Trajectory studio

Expand All @@ -98,7 +131,7 @@ pnpm test:webui # same, from the repository root (CI gate)
node packages/webui/scripts/check-docs-alignment.mjs
```

The package has zero npm runtime dependencies and requires Node 22.19+ (the trajectory studio additionally needs `node:sqlite`, floor 22.13).
The package has three runtime dependencies (`hono` + `@hono/node-server` for the HTTP layer, `@mavis/shared` for the workspace path contract) and requires Node 22.19+ (the trajectory studio additionally needs `node:sqlite`, floor 22.13).

## Origin

Expand Down
26 changes: 24 additions & 2 deletions docs/webui.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ node dist/cli.js webui # dev Web UI on 127.0.0.1:18090
node dist/cli.js webui --port 8123 # keep the installed one free
```

### 一键开发启动器(前后端 + 热重载)

当你迭代 `packages/webui/webapp/` 里的 Next.js 前端时,需要 Node 后端(18090,提供 `/api/*`)与 Next 开发服务器(18091,带 HMR,会把 `/api/*` 代理到 18090)同时跑。`pnpm run webui:dev` 一个 shell 同时拉起两边,给它们的输出加前缀让你能分清谁在说话,并在 Ctrl+C 时一并清理:

```bash
pnpm run webui:dev # http://127.0.0.1:18091/ ← 在浏览器中打开这个
```

它只是 `node scripts/dev-webui.mjs` 的薄包装,不引入额外依赖。如果 18090 被占,先停掉官方 `mcode` 运行时(`pkill -f "dist/cli.js webui"`),或者给 `mcode webui` 传 `--port 28090` 并 `export MCODE_WEBUI_ORIGIN=http://127.0.0.1:28090`,让开发代理指向正确的后端。

### 日常 webui 命令(对齐 Next 原生能力)

| 命令 | 作用 |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| `pnpm run webui:dev` | 同时启动后端(`:18090`)与 Next dev(`:18091`,带 HMR);Ctrl+C 一起清理。 |
| `pnpm run webui:build` | `next build` 构建 webapp(产物在 `packages/webui/webapp/out/`,即静态导出目录)。 |
| `pnpm run webui:start` | 通过 `node packages/webui/server.js` 在 `:18090` 服务已构建好的 webui(不带 HMR)。 |
| `pnpm run webui:typecheck`| 对 webapp 的 TS 源码跑 `tsc --noEmit`。 |
| `pnpm run webui:test` | 跑全部 webui 单元测试——后端 `test:webui` + 前端 `test:webapp`。 |

故意省略了 `next start`:webui 以 `next export` 静态构建并由后端直接服务这些文件,没有 Next server runtime 需要启动。ESLint 暂未集成进 webapp——待 `.eslintrc` 落位后再用 `npx next lint` 即可。

### Docker

仓库的 Docker 设置在**干净环境**中运行当前分支:不挂载任何
Expand Down Expand Up @@ -77,7 +99,7 @@ node dist/cli.js webui --host 0.0.0.0 --no-open # PORT defaults to 18080

## 架构

运行时拓扑、请求生命周期和 WebSocket 事件流契约见 [`packages/webui/docs/ARCHITECTURE.md`](../packages/webui/docs/ARCHITECTURE.md)。简言之:`server.js` 引导一个 HTTP 服务器;`server/router.js` 应用门禁链(CORS → origin/CSRF → LAN → token → rate limit → read-only)并分发到 `server/routes/*`;`server/lib/*` 存放单一职责模块;`acp.mjs` 是生成引擎的 ACP 客户端;`public/` 是 SPA。
运行时拓扑、请求生命周期和 SSE 契约见 [`packages/webui/docs/ARCHITECTURE.md`](../packages/webui/docs/ARCHITECTURE.md)。简言之:`packages/webui/server.js` 注册 workspace 导入解析器,并委派给 `server/bootstrap.js`;`server/router.js` 应用门禁链(CORS → origin/CSRF → LAN → token → rate limit → read-only)并分发到 `server/routes/*`;`server/lib/*` 存放单一职责模块;`acp.mjs` 是生成引擎的 ACP 客户端;`webapp/out/`(Next 静态导出)是 UI,`public/trajectory/` 与 `public/auth-gate.html`(从导出根提供)是仅存的旧版资源。

## 轨迹工作室

Expand All @@ -97,7 +119,7 @@ pnpm test:webui # same, from the repository root (CI gate)
node packages/webui/scripts/check-docs-alignment.mjs
```

该包没有任何 npm 运行时依赖,需要 Node 22.19+(轨迹工作室另外需要 `node:sqlite`,下限 22.13)。
该包有三个运行时依赖(HTTP 层的 `hono` + `@hono/node-server`,以及工作区路径约定的 `@mavis/shared`),需要 Node 22.19+(轨迹工作室另外需要 `node:sqlite`,下限 22.13)。

## 起源

Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
"start": "node dist/cli.js",
"mcode": "node dist/cli.js",
"mcode-web": "node dist/mcode-web.js",
"webui:dev": "node scripts/dev-webui.mjs",
"webui:build": "pnpm --filter @mavis/webui webapp:build",
"webui:start": "pnpm --filter @mavis/webui start",
"webui:typecheck": "pnpm --filter @mavis/webui webapp:typecheck",
"webui:test": "pnpm test:webui && pnpm test:webapp",
"verify": "node scripts/verify.mjs",
"test:smoke": "node --test test/smoke.test.mjs",
"test:policy": "node scripts/run-vitest-suite.mjs policy",
"test:byok": "node --test test/byok.test.mjs",
"test:webui": "pnpm --filter @mavis/webui test",
"test:webapp": "pnpm --filter @mavis/webui test:webapp",
"check:standalone": "node scripts/check-standalone-boundary.mjs",
"check:tsconfig": "node scripts/gen-tsconfig-paths.mjs",
"gen:tsconfig": "node scripts/gen-tsconfig-paths.mjs --write",
Expand Down
62 changes: 61 additions & 1 deletion packages/tui/src/acp/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';

import * as acp from '@agentclientprotocol/sdk';

import type { TuiSession } from '../runtime/port.js';
import type { TuiAccountStatus, TuiSession } from '../runtime/port.js';
import type { TuiAcpRuntime } from './runtime.js';

export const TUI_ACP_EXTENSION_VERSION = 1;
Expand All @@ -26,6 +26,7 @@ export const TUI_ACP_EXTENSION_METHODS = [
...TUI_ACP_GOAL_METHODS,
'mcode/session/delegation/get',
'mcode/session/delegation/stop',
'mcode/account/status',
] as const;

export const TUI_ACP_EXTENSION_NOTIFICATIONS = [
Expand Down Expand Up @@ -54,6 +55,47 @@ export function tuiAcpExtensionCapabilities(runtime: Pick<TuiAcpRuntime, 'isGoal
};
}

/**
* Account status for ACP clients — display fields and quota figures only.
*
* An allow-list projection, not a spread of `TuiAccountStatus`. The payload
* crosses a process boundary and a client is expected to relay it to a browser,
* so a field added to the runtime type later must not arrive here by default.
*
* Nothing here reads a credential: no access/refresh token, no subscription key
* and no provider API key — those are not fields of `TuiAccountStatus` at all,
* and `managedTokenPresent` is a boolean, not the token. `identity.email` is
* omitted deliberately: the UI needs a display name, and carrying an unused PII
* field over the wire is a leak waiting for a logging accident.
*/
function projectAccountStatus(account: TuiAccountStatus): Record<string, unknown> {
return {
status: account.status,
...(account.authMode === undefined ? {} : { authMode: account.authMode }),
...(account.modelSource === undefined ? {} : { modelSource: account.modelSource }),
...(account.defaultModel === undefined ? {} : { defaultModel: account.defaultModel }),
...(account.managedTokenPresent === undefined
? {}
: { managedTokenPresent: account.managedTokenPresent }),
...(account.identity?.name === undefined ? {} : { identity: { name: account.identity.name } }),
...(account.tokenPlanQuotaState === undefined
? {}
: { tokenPlanQuotaState: account.tokenPlanQuotaState }),
...(account.tokenPlanSummary === undefined
? {}
: { tokenPlan: { ...account.tokenPlanSummary } }),
...(account.tokenPlanQuota === undefined
? {}
: {
quota: {
fiveHour: account.tokenPlanQuota.fiveHour,
weekly: account.tokenPlanQuota.weekly,
},
}),
warnings: [...account.warnings],
};
}

export interface RegisterTuiAcpExtensionsOptions {
readonly app: acp.AgentApp;
readonly runtime: TuiAcpRuntime;
Expand Down Expand Up @@ -93,6 +135,12 @@ export function registerTuiAcpExtensions(options: RegisterTuiAcpExtensionsOption
activateSession(params.sessionId, client),
);

options.app.onRequest('mcode/account/status', parseOptionalSessionRequest, async ({ params }) =>
projectAccountStatus(
await options.runtime.getAccountStatus(params.sessionId, { includeMembership: true }),
),
);

options.app.onRequest('mcode/session/steer', parseTextRequest, async ({ params }) => {
resolve(params.sessionId);
const expectedTurnId = options.activePromptTurnId(params.sessionId);
Expand Down Expand Up @@ -263,6 +311,18 @@ function requireSession(
return session;
}

/**
* `mcode/account/status` answers before a session exists (the client's account
* card is visible then too), so its session id is optional.
*/
function parseOptionalSessionRequest(value: unknown): { sessionId?: string } {
if (value === undefined || value === null) return {};
const record = requireRecord(value);
const sessionId = record.sessionId;
if (sessionId === undefined || sessionId === null || sessionId === '') return {};
return { sessionId: requireText(sessionId, 'sessionId') };
}

function parseSessionRequest(value: unknown): { sessionId: string } {
const record = requireRecord(value);
return { sessionId: requireText(record.sessionId, 'sessionId') };
Expand Down
Loading
Loading