diff --git a/AGENTS.md b/AGENTS.md
index 1173031..3cc0566 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# Repository Guidelines (Code-Verified)
-> Last verified against current codebase: 2026-05-11
+> Last verified against current codebase: 2026-05-15
> Scope: `/Users/jojo/Desktop/agents/正在修复中/agents`
这份文档基于当前仓库实际代码重写。若与未来代码冲突,以代码为准并及时更新本文档。
@@ -19,10 +19,14 @@
- `utils/`: API client、日志、上下文与对话工具等公共函数
- **前端目录**
- `static/src/`: Vue 3 + TS 前端
+ - `cli/src/`: React 19 + Ink 6 + TypeScript CLI 前端(正在重写中)
- 监控动画相关核心文件:
- `static/src/components/chat/monitor/MonitorDirector.ts`
- `static/src/stores/monitor.ts`
- `static/src/components/chat/monitor/*`
+- **CLI 相关文档**
+ - `docs/cli_ui_display_spec.md`: CLI 显示层设计说明
+ - `docs/cli_slash_commands_spec.md`: CLI `/` 指令设计说明
- **其他子项目/资源**
- `android-webview-app/`: Android WebView 客户端工程
- `easyagent/`: 独立前端子目录(与根目录前端并存)
@@ -42,18 +46,32 @@
- 开发监听(当前脚本是 build watch):`npm run dev`
- Lint:`npm run lint`
+### CLI(React / Ink)
+- 安装依赖:`npm --prefix cli install`
+- 开发启动:`npm run cli` 或 `npm --prefix cli run dev`
+- 构建:`npm run cli:build`
+- 类型检查:`npm run cli:typecheck`
+- 可执行命令名(构建后):`agents` / `agents-cli`
+
## 3) 测试现状(不要再写过时命令)
- 当前仓库内可见自动化冒烟:`test/test_server_refactor_smoke.py`(`unittest`)
- 运行方式:`python -m unittest test.test_server_refactor_smoke`
- `test_system_message.py` 依赖外部 `MOONSHOT_API_KEY` 与网络,不属于离线稳定 CI 用例。
- 当前仓库未发现 `pytest.ini`/`pyproject.toml`/`tox.ini`;不要默认要求 `pytest` 作为唯一入口。
+- CLI 当前最小可复现验证:
+ - `npm --prefix cli run typecheck`
+ - `npm --prefix cli run build`
+- 若改动 `server/chat.py` 等后端接口适配,补充:
+ - `python3 -m py_compile server/chat.py`
+ - `python -m unittest test.test_server_refactor_smoke`
## 4) 代码修改约定(实用版)
- **最小改动原则**:只改与需求直接相关文件,避免顺手重构。
- **后端改动优先级**:先改 `modules/`、`server/` 内对应模块,最后才动入口。
- **前端改动优先级**:按 `static/src` 现有分层改(`app/`、`stores/`、`components/`、`composables/`)。
+- **CLI 改动优先级**:优先在 `cli/src/App.tsx`、`cli/src/components.tsx`、`cli/src/eventMapper.ts`、`cli/src/api.ts` 内做最小闭环修改。
- 涉及 monitor 动画/事件联动时,至少同步检查:
- `MonitorDirector.ts`(动画与场景执行)
- `stores/monitor.ts`(事件队列与状态机)
@@ -62,9 +80,18 @@
- Python:4 空格、`snake_case` 函数、`PascalCase` 类,新增公共函数尽量补 type hints。
- Vue/TS:保持现有代码风格,不做无关风格清洗。
+- CLI React/TS:保留现有 Ink 渲染方式与光标修正逻辑,不要轻易重写输入框定位策略。
- 日志:优先复用现有 logger/日志路径,不引入大量临时 `print`。
- 提交前至少做与改动相关的最小验证(命令输出或手工步骤要可复现)。
+### CLI 当前交互约束(2026-05-15)
+
+- CLI 连接的是现有本地 Web API(默认 `127.0.0.1:8091`),不是独立 agent runtime。
+- 启动 CLI 时应清屏、连接本地服务、创建新会话,并将输入区固定在底部。
+- 当前目录若不在工作区中,应先弹出“是否添加到工作区”的选择。
+- 思考内容当前默认隐藏,只显示“思考中 / 思考完成”标题;相关折叠代码保留,后续可继续修。
+- 不要在未获得用户要求的情况下运行交互式 TUI 压测或长时间模拟输入,以免刷屏占满上下文。
+
## 6) Git 工作流(开发 + Review)
### 6.1 核心原则
diff --git a/README.md b/README.md
index 23ffc2f..495afff 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,66 @@
一个功能完整的 AI 智能体系统,支持多模态交互、子智能体协作、实时终端操作和丰富的工具集成。
+## 当前状态(2026-05)
+
+- Web 端仍然是主线入口,基于 Flask + Vue 3。
+- 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。
+- 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API,并复用现有会话、任务、权限、工作区等接口。
+- CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。
+
+## CLI 快速开始
+
+### 安装与构建
+
+```bash
+npm install
+npm --prefix cli install
+npm run cli:build
+```
+
+### 开发启动
+
+```bash
+npm run cli
+```
+
+或直接:
+
+```bash
+npm --prefix cli run dev
+```
+
+### 在目标目录启动 CLI
+
+```bash
+cd /path/to/your/project
+npm --prefix /Users/jojo/Desktop/agents/正在修复中/agents/cli run dev
+```
+
+说明:
+
+- CLI 会默认把“当前终端目录”视为目标工作目录。
+- 若当前目录尚未加入工作区,启动后会先提示是否将该目录加入工作区。
+- CLI 会优先连接 `http://127.0.0.1:8091`;若本地服务未启动,会尝试拉起:
+
+```bash
+python3 -m server.app --path <当前目录> --port 8091 --thinking-mode
+```
+
+### 当前 CLI 已支持的能力
+
+- 启动即创建会话,不必等首条消息发送后再建会话
+- `/new` `/resume` `/model` `/versioning` `/permission` `/execution`
+- `/workspace` `/compact` `/tools` `/path` `/skill` `/status` `/help` `/clear` `/exit`
+- 基于任务轮询的时间线展示:用户消息、工具调用、模型输出、审批、后台状态
+- 宿主机工作区接入、版本控制开关、权限模式与执行环境切换
+
+### 当前 CLI 已知限制
+
+- 默认隐藏思考内容,只显示“思考中 / 思考完成”标题
+- 时间线布局、终端输入法光标、长内容换行等细节仍在持续修正
+- 目前更适合本地开发与人工交互,不建议当作完全稳定的自动化终端入口
+
## 核心特性
### 🎯 多模式运行
diff --git a/cli-react-demo/.gitignore b/cli-react-demo/.gitignore
new file mode 100644
index 0000000..5fa7c01
--- /dev/null
+++ b/cli-react-demo/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+
+.easyagent/
diff --git a/cli-react-demo/demo.jsx b/cli-react-demo/demo.jsx
new file mode 100644
index 0000000..ed51b27
--- /dev/null
+++ b/cli-react-demo/demo.jsx
@@ -0,0 +1,226 @@
+import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import {render, Box, Text, useApp, useInput} from 'ink';
+
+const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+const nowTime = () => new Date().toLocaleTimeString('zh-CN', {hour12: false});
+
+function Header({running, turn}) {
+ return (
+
+ Agents CLI React/Ink Demo
+
+ React 组件渲染到终端 · 支持粘贴多行 · 输入后回车发送 · Ctrl+C 取消当前任务 · /help 或 /exit
+
+
+ 状态: {running ? '运行中' : '空闲'}
+ Turn: #{turn}
+
+
+ );
+}
+
+function Message({item}) {
+ if (item.type === 'user') {
+ return (
+
+ 你
+ {item.time}
+ {item.text}
+
+ );
+ }
+
+ if (item.type === 'assistant') {
+ return (
+
+ 助手 {item.time}
+ {item.text || 正在生成...}
+
+ );
+ }
+
+ if (item.type === 'tool') {
+ const statusColor = item.status === 'done' ? 'green' : item.status === 'running' ? 'yellow' : 'red';
+ return (
+
+ {item.status === 'done' ? '✓' : '●'} 工具调用:{item.name}
+ {item.detail}
+ {item.output ? {item.output} : null}
+
+ );
+ }
+
+ if (item.type === 'system') {
+ return ◆ {item.text};
+ }
+
+ return null;
+}
+
+function Transcript({items}) {
+ const visible = items.slice(-10);
+ return (
+
+ {visible.map(item => )}
+
+ );
+}
+
+function Composer({value, running}) {
+ return (
+
+
+ ›
+ {value}
+
+
+ 快捷键:Enter 发送 · 直接粘贴可保留多行 · Backspace 删除 · Ctrl+C {running ? '取消任务' : '退出'} · /help 查看命令
+
+ );
+}
+
+function App() {
+ const {exit} = useApp();
+ const [input, setInput] = useState('');
+ const [items, setItems] = useState(() => [
+ {id: 'welcome', type: 'system', text: '这是一个本地 React/Ink CLI demo:消息区、工具卡片、输入框和状态栏都是 React 组件。'}
+ ]);
+ const [running, setRunning] = useState(false);
+ const [turn, setTurn] = useState(0);
+ const cancelRef = useRef(false);
+ const nextId = useRef(1);
+
+ const append = useCallback(item => {
+ setItems(prev => [...prev, {id: String(nextId.current++), ...item}]);
+ }, []);
+
+ const patchLastAssistant = useCallback(delta => {
+ setItems(prev => {
+ const copy = [...prev];
+ for (let i = copy.length - 1; i >= 0; i--) {
+ if (copy[i].type === 'assistant') {
+ copy[i] = {...copy[i], text: (copy[i].text || '') + delta};
+ break;
+ }
+ }
+ return copy;
+ });
+ }, []);
+
+ const markLastToolDone = useCallback(output => {
+ setItems(prev => {
+ const copy = [...prev];
+ for (let i = copy.length - 1; i >= 0; i--) {
+ if (copy[i].type === 'tool' && copy[i].status === 'running') {
+ copy[i] = {...copy[i], status: 'done', output};
+ break;
+ }
+ }
+ return copy;
+ });
+ }, []);
+
+ const runFakeAgent = useCallback(async prompt => {
+ setRunning(true);
+ cancelRef.current = false;
+ const currentTurn = turn + 1;
+ setTurn(currentTurn);
+
+ append({type: 'user', text: prompt, time: nowTime()});
+
+ if (prompt === '/help') {
+ append({type: 'assistant', time: nowTime(), text: '可用命令:/help、/clear、/exit。普通文本会触发一段模拟 Agent 流程。现在可以直接粘贴多行内容,换行会保留在输入框和提交内容中。'});
+ setRunning(false);
+ return;
+ }
+ if (prompt === '/clear') {
+ setItems([{id: 'cleared', type: 'system', text: '已清屏。'}]);
+ setRunning(false);
+ return;
+ }
+ if (prompt === '/exit') {
+ exit();
+ return;
+ }
+
+ append({type: 'assistant', time: nowTime(), text: ''});
+ const sentence = '我会先读取项目结构,然后模拟一次工具调用,最后给出结论。这些内容是流式追加的,不会破坏下方输入框。';
+ for (const char of sentence) {
+ if (cancelRef.current) {
+ patchLastAssistant('\n\n[已取消]');
+ setRunning(false);
+ return;
+ }
+ patchLastAssistant(char);
+ await sleep(28);
+ }
+
+ append({type: 'tool', status: 'running', name: 'list_files', detail: '扫描当前 workspace 的关键文件...'});
+ await sleep(900);
+ if (cancelRef.current) {
+ markLastToolDone('任务取消,工具调用未继续。');
+ setRunning(false);
+ return;
+ }
+ markLastToolDone('发现 main.py、server/tasks.py、core/main_terminal.py。');
+
+ append({type: 'assistant', time: nowTime(), text: ''});
+ const finalText = '结论:CLI 可以像一个终端 App 一样工作。后续只需要把这里的模拟事件替换成 /api/tasks 的真实事件流。';
+ for (const char of finalText) {
+ if (cancelRef.current) {
+ patchLastAssistant('\n\n[已取消]');
+ setRunning(false);
+ return;
+ }
+ patchLastAssistant(char);
+ await sleep(24);
+ }
+ setRunning(false);
+ }, [append, exit, markLastToolDone, patchLastAssistant, turn]);
+
+ useInput((chunk, key) => {
+ if (key.ctrl && chunk === 'c') {
+ if (running) {
+ cancelRef.current = true;
+ append({type: 'system', text: '收到 Ctrl+C:正在取消当前模拟任务...'});
+ } else {
+ exit();
+ }
+ return;
+ }
+
+ if (running) {
+ return;
+ }
+
+ if (key.return) {
+ const text = input.trim();
+ if (!text) return;
+ setInput('');
+ void runFakeAgent(text);
+ return;
+ }
+
+ if (key.backspace || key.delete) {
+ setInput(prev => Array.from(prev).slice(0, -1).join(''));
+ return;
+ }
+
+ if (chunk && !key.ctrl && !key.meta) {
+ // Ink 在 bracketed paste 或快速粘贴时,可能一次性给到包含换行的 chunk。
+ // 这里把 CRLF/CR 统一成 LF,并保留多行内容;单独按 Enter 的提交逻辑在上面处理。
+ const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
+ setInput(prev => prev + normalized);
+ }
+ });
+
+ return (
+
+
+
+
+
+ );
+}
+
+render();
diff --git a/cli-react-demo/package-lock.json b/cli-react-demo/package-lock.json
new file mode 100644
index 0000000..71864c4
--- /dev/null
+++ b/cli-react-demo/package-lock.json
@@ -0,0 +1,1067 @@
+{
+ "name": "agents-cli-react-demo",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "agents-cli-react-demo",
+ "version": "0.1.0",
+ "dependencies": {
+ "ink": "^6.5.1",
+ "react": "^19.2.1",
+ "tsx": "^4.20.6"
+ }
+ },
+ "node_modules/@alcalzone/ansi-tokenize": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz",
+ "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
+ "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/auto-bind": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
+ "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
+ "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^8.0.0",
+ "string-width": "^8.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/code-excerpt": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz",
+ "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==",
+ "license": "MIT",
+ "dependencies": {
+ "convert-to-spaces": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/convert-to-spaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz",
+ "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.46.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
+ "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ink": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz",
+ "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@alcalzone/ansi-tokenize": "^0.2.4",
+ "ansi-escapes": "^7.3.0",
+ "ansi-styles": "^6.2.1",
+ "auto-bind": "^5.0.1",
+ "chalk": "^5.6.0",
+ "cli-boxes": "^3.0.0",
+ "cli-cursor": "^4.0.0",
+ "cli-truncate": "^5.1.1",
+ "code-excerpt": "^4.0.0",
+ "es-toolkit": "^1.39.10",
+ "indent-string": "^5.0.0",
+ "is-in-ci": "^2.0.0",
+ "patch-console": "^2.0.0",
+ "react-reconciler": "^0.33.0",
+ "scheduler": "^0.27.0",
+ "signal-exit": "^3.0.7",
+ "slice-ansi": "^8.0.0",
+ "stack-utils": "^2.0.6",
+ "string-width": "^8.1.1",
+ "terminal-size": "^4.0.1",
+ "type-fest": "^5.4.1",
+ "widest-line": "^6.0.0",
+ "wrap-ansi": "^9.0.0",
+ "ws": "^8.18.0",
+ "yoga-layout": "~3.2.1"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@types/react": ">=19.0.0",
+ "react": ">=19.0.0",
+ "react-devtools-core": ">=6.1.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react-devtools-core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-in-ci": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz",
+ "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==",
+ "license": "MIT",
+ "bin": {
+ "is-in-ci": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/patch-console": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz",
+ "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz",
+ "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/slice-ansi": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
+ "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.3",
+ "is-fullwidth-code-point": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/tagged-tag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
+ "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/terminal-size": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz",
+ "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz",
+ "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==",
+ "license": "(MIT OR CC0-1.0)",
+ "dependencies": {
+ "tagged-tag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/widest-line": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz",
+ "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^8.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
+ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yoga-layout": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
+ "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/cli-react-demo/package.json b/cli-react-demo/package.json
new file mode 100644
index 0000000..51c6c6e
--- /dev/null
+++ b/cli-react-demo/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "agents-cli-react-demo",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "demo": "tsx demo.jsx"
+ },
+ "dependencies": {
+ "ink": "^6.5.1",
+ "react": "^19.2.1",
+ "tsx": "^4.20.6"
+ }
+}
diff --git a/cli/.gitignore b/cli/.gitignore
new file mode 100644
index 0000000..b947077
--- /dev/null
+++ b/cli/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
diff --git a/cli/package-lock.json b/cli/package-lock.json
new file mode 100644
index 0000000..7ae9a01
--- /dev/null
+++ b/cli/package-lock.json
@@ -0,0 +1,1660 @@
+{
+ "name": "agents-cli",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "agents-cli",
+ "version": "0.1.0",
+ "dependencies": {
+ "ink": "^6.5.1",
+ "react": "^19.2.1",
+ "react-devtools-core": "^6.1.5"
+ },
+ "bin": {
+ "agents": "dist/main.js",
+ "agents-cli": "dist/main.js"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "esbuild": "^0.27.0",
+ "tsx": "^4.20.6",
+ "typescript": "^5.9.3"
+ }
+ },
+ "node_modules/@alcalzone/ansi-tokenize": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz",
+ "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
+ "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
+ "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/auto-bind": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
+ "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+ "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
+ "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^8.0.0",
+ "string-width": "^8.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/code-excerpt": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz",
+ "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==",
+ "license": "MIT",
+ "dependencies": {
+ "convert-to-spaces": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/convert-to-spaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz",
+ "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.46.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
+ "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+ "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ink": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz",
+ "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@alcalzone/ansi-tokenize": "^0.2.4",
+ "ansi-escapes": "^7.3.0",
+ "ansi-styles": "^6.2.1",
+ "auto-bind": "^5.0.1",
+ "chalk": "^5.6.0",
+ "cli-boxes": "^3.0.0",
+ "cli-cursor": "^4.0.0",
+ "cli-truncate": "^5.1.1",
+ "code-excerpt": "^4.0.0",
+ "es-toolkit": "^1.39.10",
+ "indent-string": "^5.0.0",
+ "is-in-ci": "^2.0.0",
+ "patch-console": "^2.0.0",
+ "react-reconciler": "^0.33.0",
+ "scheduler": "^0.27.0",
+ "signal-exit": "^3.0.7",
+ "slice-ansi": "^8.0.0",
+ "stack-utils": "^2.0.6",
+ "string-width": "^8.1.1",
+ "terminal-size": "^4.0.1",
+ "type-fest": "^5.4.1",
+ "widest-line": "^6.0.0",
+ "wrap-ansi": "^9.0.0",
+ "ws": "^8.18.0",
+ "yoga-layout": "~3.2.1"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@types/react": ">=19.0.0",
+ "react": ">=19.0.0",
+ "react-devtools-core": ">=6.1.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react-devtools-core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-in-ci": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz",
+ "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==",
+ "license": "MIT",
+ "bin": {
+ "is-in-ci": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/patch-console": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz",
+ "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-devtools-core": {
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz",
+ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
+ "license": "MIT",
+ "dependencies": {
+ "shell-quote": "^1.6.1",
+ "ws": "^7"
+ }
+ },
+ "node_modules/react-devtools-core/node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.33.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz",
+ "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.0"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+ "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/slice-ansi": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
+ "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.3",
+ "is-fullwidth-code-point": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/tagged-tag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
+ "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/terminal-size": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz",
+ "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.22.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.0.tgz",
+ "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
+ "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
+ "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
+ "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
+ "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
+ "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
+ "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
+ "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
+ "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
+ "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
+ "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
+ "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
+ "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
+ "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
+ "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
+ "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
+ "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
+ "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
+ "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
+ "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
+ "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
+ "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
+ "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.0",
+ "@esbuild/android-arm": "0.28.0",
+ "@esbuild/android-arm64": "0.28.0",
+ "@esbuild/android-x64": "0.28.0",
+ "@esbuild/darwin-arm64": "0.28.0",
+ "@esbuild/darwin-x64": "0.28.0",
+ "@esbuild/freebsd-arm64": "0.28.0",
+ "@esbuild/freebsd-x64": "0.28.0",
+ "@esbuild/linux-arm": "0.28.0",
+ "@esbuild/linux-arm64": "0.28.0",
+ "@esbuild/linux-ia32": "0.28.0",
+ "@esbuild/linux-loong64": "0.28.0",
+ "@esbuild/linux-mips64el": "0.28.0",
+ "@esbuild/linux-ppc64": "0.28.0",
+ "@esbuild/linux-riscv64": "0.28.0",
+ "@esbuild/linux-s390x": "0.28.0",
+ "@esbuild/linux-x64": "0.28.0",
+ "@esbuild/netbsd-arm64": "0.28.0",
+ "@esbuild/netbsd-x64": "0.28.0",
+ "@esbuild/openbsd-arm64": "0.28.0",
+ "@esbuild/openbsd-x64": "0.28.0",
+ "@esbuild/openharmony-arm64": "0.28.0",
+ "@esbuild/sunos-x64": "0.28.0",
+ "@esbuild/win32-arm64": "0.28.0",
+ "@esbuild/win32-ia32": "0.28.0",
+ "@esbuild/win32-x64": "0.28.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz",
+ "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==",
+ "license": "(MIT OR CC0-1.0)",
+ "dependencies": {
+ "tagged-tag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/widest-line": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz",
+ "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^8.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
+ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yoga-layout": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
+ "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/cli/package.json b/cli/package.json
new file mode 100644
index 0000000..409a028
--- /dev/null
+++ b/cli/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "agents-cli",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "bin": {
+ "agents": "./dist/main.js",
+ "agents-cli": "./dist/main.js"
+ },
+ "scripts": {
+ "dev": "tsx src/main.tsx",
+ "build": "esbuild src/main.tsx --bundle --platform=node --format=esm --target=node20 --outfile=dist/main.js --banner:js='#!/usr/bin/env node' && chmod +x dist/main.js",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "ink": "^6.5.1",
+ "react": "^19.2.1",
+ "react-devtools-core": "^6.1.5"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "esbuild": "^0.27.0",
+ "tsx": "^4.20.6",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/cli/src/App.tsx b/cli/src/App.tsx
new file mode 100644
index 0000000..680f6fd
--- /dev/null
+++ b/cli/src/App.tsx
@@ -0,0 +1,680 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { Box, Text, useApp, useInput, useStdout } from 'ink';
+import { setTimeout as sleep } from 'node:timers/promises';
+import { slashCommands, filterCommands } from './commands.js';
+import { createWorkspaceForPath, findWorkspaceByPath, loadWorkspaceCatalog, repoRoot } from './workspaces.js';
+import type { CliStatus, ExecutionMode, ModelDefinition, PermissionMode, RunMode, SkillDefinition, SlashCommand, TimelineItem, Workspace, WorkspaceCatalog } from './types.js';
+import { nowSessionId, shortPath } from './format.js';
+import { Composer, HelpPanel, Picker, SkillHint, SlashMenu, StatusLine, StatusPanel, Timeline, WelcomePanel, WorkspacePrompt } from './components.js';
+import { ApiClient, createDefaultApiClient } from './api.js';
+import { createEventRenderState, reduceTaskEvent } from './eventMapper.js';
+
+type InputMode = 'workspace_prompt' | 'composer' | 'slash_menu' | 'picker' | 'status_panel' | 'help_panel' | 'approval';
+
+type PickerOption = {
+ label: string;
+ description?: string;
+ disabled?: boolean;
+ onSelect: () => void | Promise;
+};
+
+type ApprovalOption = {
+ label: string;
+ description?: string;
+ decision: 'approved' | 'rejected';
+};
+
+const cwd = process.cwd();
+const initialCatalog = loadWorkspaceCatalog();
+const matchedWorkspace = findWorkspaceByPath(initialCatalog, cwd);
+
+function id(): string {
+ return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
+}
+
+function initialStatus(workspace: Workspace): CliStatus {
+ return {
+ model: '连接中',
+ runMode: 'thinking',
+ directory: workspace.path,
+ workspace,
+ permissionMode: 'auto_approval',
+ executionMode: 'sandbox',
+ sessionId: nowSessionId(),
+ contextUsed: 0,
+ contextLimit: 0,
+ versioningEnabled: false,
+ backgroundAgents: 0,
+ backgroundCommands: 0,
+ };
+}
+
+function workspaceFromApi(item: any): Workspace {
+ return {
+ workspace_id: String(item.workspace_id || item.id || item.label || 'workspace'),
+ label: String(item.label || item.workspace_id || 'workspace'),
+ path: String(item.path || ''),
+ };
+}
+
+export default function App() {
+ const { exit } = useApp();
+ const { stdout } = useStdout();
+ const apiRef = useRef(null);
+ const runningTaskRef = useRef(null);
+ const currentConversationRef = useRef(undefined);
+ const bootstrappedRef = useRef(false);
+ const [catalog, setCatalog] = useState(initialCatalog);
+ const [mode, setMode] = useState(matchedWorkspace ? 'composer' : 'workspace_prompt');
+ const [workspacePromptIndex, setWorkspacePromptIndex] = useState(0);
+ const [input, setInput] = useState('');
+ const [slashIndex, setSlashIndex] = useState(0);
+ const [pickerTitle, setPickerTitle] = useState('');
+ const [pickerOptions, setPickerOptions] = useState([]);
+ const [pickerIndex, setPickerIndex] = useState(0);
+ const [approvalTitle, setApprovalTitle] = useState('');
+ const [approvalId, setApprovalId] = useState('');
+ const [approvalOptions] = useState([
+ { label: '允许一次', decision: 'approved' },
+ { label: '拒绝', decision: 'rejected' },
+ ]);
+ const [approvalIndex, setApprovalIndex] = useState(0);
+ const [timeline, setTimeline] = useState([]);
+ const [status, setStatus] = useState(() => initialStatus(matchedWorkspace || initialCatalog.workspaces[0]!));
+ const [connected, setConnected] = useState(false);
+ const [connecting, setConnecting] = useState(false);
+ const [running, setRunning] = useState(false);
+ const [runningStartedAt, setRunningStartedAt] = useState(null);
+ const [nowMs, setNowMs] = useState(Date.now());
+ const [workingTextTick, setWorkingTextTick] = useState(0);
+ const [workingDotTick, setWorkingDotTick] = useState(0);
+ const [timelineDotTick, setTimelineDotTick] = useState(0);
+ const [models, setModels] = useState([]);
+ const [skills, setSkills] = useState([]);
+ const [permissionOptions, setPermissionOptions] = useState([]);
+ const [executionOptions, setExecutionOptions] = useState([]);
+
+ const commandMatches = useMemo(() => filterCommands(input), [input]);
+ const runningSeconds = runningStartedAt ? Math.max(0, Math.floor((nowMs - runningStartedAt) / 1000)) : 0;
+ const hasRunningTimeline = useMemo(() => timeline.some((item) => item.status === 'running'), [timeline]);
+
+ useEffect(() => {
+ if (!running) return;
+ const timer = setInterval(() => {
+ setNowMs(Date.now());
+ }, 1000);
+ return () => clearInterval(timer);
+ }, [running]);
+
+ useEffect(() => {
+ if (!running) return;
+ const timer = setInterval(() => {
+ setWorkingTextTick((value) => value + 1);
+ }, 100);
+ return () => clearInterval(timer);
+ }, [running]);
+
+ useEffect(() => {
+ if (!running) return;
+ const timer = setInterval(() => {
+ setWorkingDotTick((value) => value + 1);
+ }, 800);
+ return () => clearInterval(timer);
+ }, [running]);
+
+ useEffect(() => {
+ if (!hasRunningTimeline) return;
+ let timer: ReturnType | undefined;
+ const delay = setTimeout(() => {
+ setTimelineDotTick((value) => value + 1);
+ timer = setInterval(() => {
+ setTimelineDotTick((value) => value + 1);
+ }, 800);
+ }, 400);
+ return () => {
+ clearTimeout(delay);
+ if (timer) clearInterval(timer);
+ };
+ }, [hasRunningTimeline]);
+
+ useEffect(() => {
+ if (mode !== 'composer' || bootstrappedRef.current) return;
+ bootstrappedRef.current = true;
+ getApi()
+ .then((api) => bootstrapConversation(api))
+ .catch((err) => addItem({ kind: 'tool', title: '初始化失败', status: 'failed', body: String(err.message || err) }));
+ }, [mode]);
+
+ function addItem(item: Omit) {
+ setTimeline((prev) => [...prev, { id: id(), ...item }]);
+ }
+
+ function patchStatusFromServer(raw: any, workspaceOverride?: Workspace) {
+ const execMode = typeof raw?.execution_mode === 'object' ? raw.execution_mode.mode : raw?.execution_mode;
+ const contextUsed = Number(raw?.context?.total_size || raw?.context_tokens || raw?.token_statistics?.current_context_tokens || 0);
+ setStatus((prev) => ({
+ ...prev,
+ model: raw?.model_key || prev.model,
+ runMode: raw?.run_mode || prev.runMode,
+ directory: raw?.project_path || workspaceOverride?.path || prev.directory,
+ workspace: workspaceOverride || prev.workspace,
+ permissionMode: raw?.permission_mode || prev.permissionMode,
+ executionMode: execMode || prev.executionMode,
+ sessionId: raw?.conversation?.current_id || prev.sessionId,
+ contextUsed: Number.isFinite(contextUsed) && contextUsed > 0 ? contextUsed : prev.contextUsed,
+ }));
+ if (raw?.conversation?.current_id) currentConversationRef.current = raw.conversation.current_id;
+ }
+
+ async function getApi(): Promise {
+ if (!apiRef.current) apiRef.current = createDefaultApiClient(cwd, repoRoot);
+ if (!connected && !connecting) {
+ setConnecting(true);
+ try {
+ await apiRef.current.ensureConnected();
+ setConnected(true);
+ } finally {
+ setConnecting(false);
+ }
+ } else if (!connected) {
+ while (!connected && connecting) await sleep(100);
+ }
+ return apiRef.current;
+ }
+
+ async function syncBackendState(api = apiRef.current) {
+ if (!api) return;
+ try {
+ const wsData = await api.listWorkspaces();
+ const workspaces = (wsData.workspaces || []).map(workspaceFromApi);
+ const current = workspaces.find((ws) => ws.workspace_id === wsData.current_workspace_id) || workspaces.find((ws) => ws.path === status.workspace.path) || status.workspace;
+ setCatalog({ default_workspace_id: wsData.default_workspace_id, workspaces });
+ const rawStatus = await api.getStatus();
+ patchStatusFromServer(rawStatus, current);
+ const conv = await api.getCurrentConversation().catch(() => null);
+ const convId = conv?.id || rawStatus?.conversation?.current_id;
+ if (convId) currentConversationRef.current = convId;
+ await syncRuntimeCatalogs(api, convId).catch(() => undefined);
+ } catch (err) {
+ addItem({ kind: 'tool', title: '同步后端状态失败', status: 'failed', body: String((err as Error).message || err) });
+ }
+ }
+
+ async function syncRuntimeCatalogs(api: ApiClient, conversationId?: string) {
+ const [modelItems, personalization, permission, execution, subAgents, backgroundCommands, tokens, versioning] = await Promise.all([
+ api.listModels().catch(() => []),
+ api.getPersonalization().catch(() => null),
+ api.getPermissionMode().catch(() => null),
+ api.getExecutionMode().catch(() => null),
+ api.listSubAgents().catch(() => []),
+ api.listBackgroundCommands().catch(() => []),
+ conversationId ? api.getConversationTokens(conversationId).catch(() => null) : Promise.resolve(null),
+ conversationId ? api.getConversationVersioning(conversationId).catch(() => null) : Promise.resolve(null),
+ ]);
+ if (modelItems.length) setModels(modelItems);
+ const enabled = new Set((personalization?.data?.enabled_skills || []).map(String));
+ const skillItems = (personalization?.skills_catalog || [])
+ .filter((skill: any) => !enabled.size || enabled.has(String(skill.id)))
+ .map((skill: any) => ({ id: String(skill.id), label: String(skill.label || skill.id), description: String(skill.description || '') }));
+ if (skillItems.length) setSkills(skillItems);
+ const rawCompressionLimit = Number(
+ personalization?.context_compression_settings?.context_window_tokens
+ || personalization?.context_compression_settings?.deep_trigger_tokens
+ || personalization?.data?.deep_compress_trigger_tokens
+ || 0,
+ );
+ const currentModel = modelItems.find((item) => item.model_key === status.model);
+ const modelWindow = Number(currentModel?.context_window || 0);
+ const compressionLimit = rawCompressionLimit > 0 && modelWindow > 0
+ ? Math.min(rawCompressionLimit, modelWindow)
+ : rawCompressionLimit || modelWindow;
+ if (Array.isArray(permission?.options)) setPermissionOptions(permission.options);
+ if (Array.isArray(execution?.options)) setExecutionOptions(execution.options);
+ const runningAgents = subAgents.filter((item: any) => !['completed', 'failed', 'cancelled', 'terminated', 'timeout'].includes(String(item.status || '').toLowerCase())).length;
+ const runningCommands = backgroundCommands.filter((item: any) => !['completed', 'failed', 'cancelled', 'timeout'].includes(String(item.status || '').toLowerCase())).length;
+ const tokenUsed = Number(tokens?.total_tokens || 0);
+ setStatus((prev) => ({
+ ...prev,
+ contextUsed: Number.isFinite(tokenUsed) && tokenUsed > 0 ? tokenUsed : prev.contextUsed,
+ contextLimit: Number.isFinite(compressionLimit) && compressionLimit > 0 ? compressionLimit : prev.contextLimit,
+ versioningEnabled: versioning ? Boolean(versioning.enabled) : prev.versioningEnabled,
+ backgroundAgents: runningAgents,
+ backgroundCommands: runningCommands,
+ }));
+ }
+
+ async function bootstrapConversation(api: ApiClient) {
+ const result = await api.createConversation();
+ const convId = result.conversation_id || result.data?.conversation_id || result.data?.id || result.id;
+ if (convId) {
+ currentConversationRef.current = String(convId);
+ setStatus((prev) => ({ ...prev, sessionId: String(convId), contextUsed: 0 }));
+ await syncBackendState(api);
+ await syncRuntimeCatalogs(api, String(convId)).catch(() => undefined);
+ }
+ }
+
+ function openPicker(title: string, options: PickerOption[]) {
+ setPickerTitle(title);
+ setPickerOptions(options);
+ setPickerIndex(0);
+ setMode('picker');
+ }
+
+ function closeOverlay() {
+ setMode('composer');
+ setInput('');
+ setSlashIndex(0);
+ setPickerIndex(0);
+ }
+
+ async function executeCommand(command: SlashCommand) {
+ const name = command.name;
+ try {
+ if (name === '/new') {
+ const api = await getApi();
+ const result = await api.createConversation();
+ const convId = result.conversation_id || result.data?.conversation_id;
+ currentConversationRef.current = convId;
+ setTimeline([]);
+ setStatus((prev) => ({ ...prev, sessionId: convId || nowSessionId(), contextUsed: 0 }));
+ if (convId) await syncRuntimeCatalogs(api, String(convId)).catch(() => undefined);
+ closeOverlay();
+ return;
+ }
+ if (name === '/resume') {
+ const api = await getApi();
+ const conversations = await api.listConversations(30);
+ openPicker('Resume conversation', conversations.map((conv: any) => ({
+ label: String(conv.title || conv.id || '未命名对话'),
+ description: String(conv.updated_at || conv.created_at || conv.id || ''),
+ onSelect: async () => {
+ await api.loadConversation(String(conv.id));
+ currentConversationRef.current = String(conv.id);
+ setStatus((prev) => ({ ...prev, sessionId: String(conv.id) }));
+ await syncRuntimeCatalogs(api, String(conv.id)).catch(() => undefined);
+ addItem({ kind: 'system', body: `已切换对话:${conv.title || conv.id}` });
+ closeOverlay();
+ },
+ })));
+ return;
+ }
+ if (name === '/model') {
+ const api = await getApi();
+ const modelItems = (await api.listModels().catch(() => models)).filter((item) => item.model_key);
+ if (modelItems.length) setModels(modelItems);
+ const currentRunMode = status.runMode;
+ openPicker('Model', modelItems.map((model) => ({
+ label: model.name || model.model_key,
+ description: `${model.model_key === status.model ? 'current · ' : ''}${model.description || model.model_key}`,
+ onSelect: async () => {
+ await api.setModel(model.model_key);
+ setStatus((prev) => ({ ...prev, model: model.model_key, contextLimit: Number(model.context_window) || prev.contextLimit }));
+ addItem({ kind: 'system', body: `已切换模型:${model.name || model.model_key}` });
+ const modes = getRunModesForModel(model);
+ openPicker('Run Mode', modes.map((runMode) => ({
+ label: runMode,
+ description: runMode === currentRunMode ? 'current' : '',
+ onSelect: async () => {
+ await api.setRunMode(runMode);
+ setStatus((prev) => ({ ...prev, runMode }));
+ await syncBackendState(api);
+ addItem({ kind: 'system', body: `已切换运行模式:${runMode}` });
+ closeOverlay();
+ },
+ })));
+ },
+ })));
+ return;
+ }
+ if (name === '/versioning') {
+ const api = await getApi();
+ const convId = currentConversationRef.current || status.sessionId;
+ const current = await api.getConversationVersioning(convId).catch(() => null);
+ const enabled = current ? Boolean(current.enabled) : status.versioningEnabled;
+ const trackingMode = current?.tracking_mode;
+ openPicker('Versioning', [
+ { label: enabled ? '关闭版本控制' : '开启版本控制', onSelect: async () => {
+ const result = await api.setConversationVersioning(convId, !enabled, trackingMode);
+ setStatus((prev) => ({ ...prev, versioningEnabled: Boolean(result.enabled) }));
+ addItem({ kind: 'system', body: `版本控制:${result.enabled ? '开' : '关'}` });
+ closeOverlay();
+ } },
+ ]);
+ return;
+ }
+ if (name === '/permission') {
+ const api = await getApi();
+ const current = await api.getPermissionMode().catch(() => null);
+ const modes: PermissionMode[] = Array.isArray(current?.options) && current.options.length ? current.options : (permissionOptions.length ? permissionOptions : [status.permissionMode]);
+ openPicker('Permission Mode', modes.map((permissionMode) => ({ label: permissionMode, description: permissionMode === status.permissionMode ? 'current' : '', onSelect: async () => {
+ await api.setPermissionMode(permissionMode);
+ setStatus((prev) => ({ ...prev, permissionMode }));
+ addItem({ kind: 'system', body: `权限模式:${permissionMode}` });
+ closeOverlay();
+ } })));
+ return;
+ }
+ if (name === '/execution') {
+ const api = await getApi();
+ const current = await api.getExecutionMode().catch(() => null);
+ const modes: ExecutionMode[] = Array.isArray(current?.options) && current.options.length ? current.options : (executionOptions.length ? executionOptions : [status.executionMode]);
+ openPicker('Execution Environment', modes.map((executionMode) => ({ label: executionMode, description: executionMode === status.executionMode ? 'current' : executionMode === 'direct' ? '高风险' : '', onSelect: async () => {
+ await api.setExecutionMode(executionMode);
+ setStatus((prev) => ({ ...prev, executionMode }));
+ addItem({ kind: 'system', body: `执行环境:${executionMode}` });
+ closeOverlay();
+ } })));
+ return;
+ }
+ if (name === '/workspace') {
+ const api = await getApi();
+ const wsData = await api.listWorkspaces();
+ const workspaces = (wsData.workspaces || []).map(workspaceFromApi);
+ setCatalog({ default_workspace_id: wsData.default_workspace_id, workspaces });
+ openPicker('Workspace', workspaces.map((workspace) => ({ label: workspace.label, description: `${shortPath(workspace.path)}${workspace.workspace_id === wsData.current_workspace_id ? ' current' : ''}`, onSelect: async () => {
+ await api.selectWorkspace(workspace.workspace_id);
+ setStatus((prev) => ({ ...prev, workspace, directory: workspace.path }));
+ addItem({ kind: 'system', body: `已切换工作区:${workspace.label}` });
+ await syncBackendState(api);
+ closeOverlay();
+ } })));
+ return;
+ }
+ if (name === '/compact') {
+ const api = await getApi();
+ const convId = currentConversationRef.current || status.sessionId;
+ const result = await api.compressConversation(convId);
+ addItem({ kind: 'tool', title: '对话已压缩', body: result.compact_file || '', status: 'success' });
+ if (result.compressed_conversation_id) {
+ currentConversationRef.current = result.compressed_conversation_id;
+ setStatus((prev) => ({ ...prev, sessionId: result.compressed_conversation_id }));
+ }
+ closeOverlay();
+ return;
+ }
+ if (name === '/tools') {
+ const api = await getApi();
+ const snapshot = await api.getToolSettings();
+ const categories = snapshot.categories || {};
+ openPicker('Tools', Object.entries(categories).map(([key, value]: [string, any]) => ({ label: `${value.enabled ? '[x]' : '[ ]'} ${key}`, description: value.label || '', onSelect: async () => {
+ await api.setToolCategory(key, !value.enabled);
+ addItem({ kind: 'system', body: `工具 ${key}:${!value.enabled ? '启用' : '禁用'}` });
+ closeOverlay();
+ } })));
+ return;
+ }
+ if (name === '/path') {
+ const api = await getApi();
+ const auth = await api.getPathAuthorization();
+ openPicker('Authorized Paths', [
+ { label: '当前可读写路径', description: (auth.writable_paths || []).join(', ') || '无', onSelect: () => closeOverlay() },
+ { label: '当前只读路径', description: (auth.readable_extra_paths || []).join(', ') || '无', onSelect: () => closeOverlay() },
+ ]);
+ return;
+ }
+ if (name === '/skill') {
+ const api = await getApi();
+ const personalization = await api.getPersonalization().catch(() => null);
+ const enabled = new Set((personalization?.data?.enabled_skills || []).map(String));
+ const skillItems = (personalization?.skills_catalog || skills)
+ .filter((skill: any) => !enabled.size || enabled.has(String(skill.id)))
+ .map((skill: any) => ({ id: String(skill.id), label: String(skill.label || skill.id), description: String(skill.description || '') }));
+ if (skillItems.length) setSkills(skillItems);
+ openPicker('Skill', skillItems.map((skill: SkillDefinition) => ({ label: skill.label, description: skill.description, onSelect: () => {
+ setStatus((prev) => ({ ...prev, activeSkill: skill }));
+ addItem({ kind: 'system', body: `下一条消息将使用 skill:${skill.label}` });
+ closeOverlay();
+ } })).concat(skillItems.length ? [] : [{ label: '暂无可用 skill', disabled: true, onSelect: () => closeOverlay() }]));
+ return;
+ }
+ if (name === '/status') {
+ await getApi().then((api) => syncBackendState(api)).catch(() => undefined);
+ setMode('status_panel');
+ setInput('');
+ return;
+ }
+ if (name === '/help') {
+ setMode('help_panel');
+ setInput('');
+ return;
+ }
+ if (name === '/clear') {
+ setTimeline([]);
+ closeOverlay();
+ return;
+ }
+ if (name === '/exit') {
+ if (runningTaskRef.current) await apiRef.current?.cancelTask(runningTaskRef.current).catch(() => undefined);
+ exit();
+ }
+ } catch (err) {
+ addItem({ kind: 'tool', title: `命令失败:${name}`, status: 'failed', body: String((err as Error).message || err) });
+ closeOverlay();
+ }
+ }
+
+ async function submitUserMessage(text: string) {
+ addItem({ kind: 'user', body: text });
+ const skill = status.activeSkill;
+ const message = skill ? `${text}\n\n先阅读 ${skill.label} skill` : text;
+ if (skill) addItem({ kind: 'user', body: `先阅读 ${skill.label} skill` });
+ setStatus((prev) => ({ ...prev, activeSkill: undefined }));
+ setRunning(true);
+ setRunningStartedAt(Date.now());
+ try {
+ const api = await getApi();
+ const task = await api.createTask({ message, conversation_id: currentConversationRef.current, model_key: status.model, run_mode: status.runMode });
+ runningTaskRef.current = task.task_id;
+ if (task.conversation_id) currentConversationRef.current = task.conversation_id;
+ await pollTask(api, task.task_id);
+ await syncBackendState(api);
+ } catch (err) {
+ addItem({ kind: 'tool', title: '发送失败', status: 'failed', body: String((err as Error).message || err) });
+ } finally {
+ runningTaskRef.current = null;
+ setRunning(false);
+ setRunningStartedAt(null);
+ }
+ }
+
+ async function pollTask(api: ApiClient, taskId: string) {
+ let offset = 0;
+ const renderState = createEventRenderState();
+ for (;;) {
+ const result = await api.pollTask(taskId, offset);
+ offset = result.next_offset;
+ for (const event of result.events || []) {
+ if (event.type === 'tool_approval_required') {
+ const approval = event.data?.approval || event.data;
+ if (approval?.approval_id) {
+ setApprovalId(String(approval.approval_id));
+ setApprovalTitle(`需要审批:${approval.tool_name || '工具调用'}`);
+ setApprovalIndex(0);
+ setMode('approval');
+ }
+ }
+ setTimeline((prev) => reduceTaskEvent(prev, renderState, event));
+ }
+ if (!['pending', 'running', 'cancel_requested'].includes(String(result.status))) break;
+ await sleep(700);
+ }
+ }
+
+ useInput((chunk, key) => {
+ void handleInput(chunk, key);
+ });
+
+ async function handleInput(chunk: string, key: any) {
+ if (mode === 'workspace_prompt') {
+ if (key.upArrow || chunk === 'k') setWorkspacePromptIndex(0);
+ else if (key.downArrow || chunk === 'j') setWorkspacePromptIndex(1);
+ else if (key.return) {
+ if (workspacePromptIndex === 0) {
+ const result = createWorkspaceForPath(catalog, cwd);
+ setCatalog(result.catalog);
+ setStatus((prev) => ({ ...prev, workspace: result.workspace, directory: result.workspace.path }));
+ bootstrappedRef.current = true;
+ setMode('composer');
+ getApi().then(async (api) => {
+ await api.createWorkspace(result.workspace.path, result.workspace.label).catch(() => undefined);
+ const wsData = await api.listWorkspaces().catch(() => null);
+ const backendWs = wsData?.workspaces?.find((ws: any) => String(ws.path) === result.workspace.path);
+ if (backendWs) await api.selectWorkspace(String(backendWs.workspace_id));
+ await bootstrapConversation(api);
+ }).catch((err) => addItem({ kind: 'tool', title: '连接后端失败', status: 'failed', body: String(err.message || err) }));
+ } else {
+ exit();
+ }
+ }
+ return;
+ }
+
+ if (mode === 'approval') {
+ if (key.upArrow) setApprovalIndex((prev) => Math.max(0, prev - 1));
+ else if (key.downArrow) setApprovalIndex((prev) => Math.min(approvalOptions.length - 1, prev + 1));
+ else if (key.return && approvalId) {
+ const selected = approvalOptions[approvalIndex]!;
+ await apiRef.current?.decideApproval(approvalId, selected.decision).catch((err) => addItem({ kind: 'tool', title: '审批提交失败', status: 'failed', body: String(err.message || err) }));
+ addItem({ kind: 'system', body: `审批已${selected.decision === 'approved' ? '允许' : '拒绝'}` });
+ setMode('composer');
+ }
+ return;
+ }
+
+ if (key.escape) {
+ if (runningTaskRef.current) {
+ await apiRef.current?.cancelTask(runningTaskRef.current).catch(() => undefined);
+ addItem({ kind: 'system', body: '已请求停止当前任务。' });
+ return;
+ }
+ closeOverlay();
+ return;
+ }
+
+ if (mode === 'status_panel' || mode === 'help_panel') {
+ if (key.return || chunk === 'q') closeOverlay();
+ return;
+ }
+
+ if (mode === 'picker') {
+ if (key.upArrow) setPickerIndex((prev) => Math.max(0, prev - 1));
+ else if (key.downArrow) setPickerIndex((prev) => Math.min(pickerOptions.length - 1, prev + 1));
+ else if (key.return) await pickerOptions[pickerIndex]?.onSelect();
+ return;
+ }
+
+ if (key.ctrl && chunk === 'c') {
+ exit();
+ return;
+ }
+
+ if (mode === 'slash_menu') {
+ if (key.upArrow) setSlashIndex((prev) => Math.max(0, prev - 1));
+ else if (key.downArrow) setSlashIndex((prev) => Math.min(commandMatches.length - 1, prev + 1));
+ else if (key.return) {
+ const command = commandMatches[slashIndex];
+ if (command) await executeCommand(command);
+ } else if (key.backspace || key.delete) {
+ setInput((prev) => {
+ const next = Array.from(prev).slice(0, -1).join('');
+ if (!next) setMode('composer');
+ return next;
+ });
+ setSlashIndex(0);
+ } else if (chunk && !key.ctrl && !key.meta) {
+ const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
+ setInput((prev) => prev + normalized);
+ setSlashIndex(0);
+ }
+ return;
+ }
+
+ if (key.return) {
+ const text = input.trim();
+ if (!text) return;
+ if (text.startsWith('/')) {
+ const command = filterCommands(text)[0];
+ if (command) await executeCommand(command);
+ return;
+ }
+ setInput('');
+ await submitUserMessage(text);
+ return;
+ }
+
+ if (key.backspace || key.delete) {
+ setInput((prev) => Array.from(prev).slice(0, -1).join(''));
+ return;
+ }
+
+ if (chunk && !key.ctrl && !key.meta) {
+ const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
+ setInput((prev) => {
+ const next = prev + normalized;
+ if (next.startsWith('/')) {
+ setMode('slash_menu');
+ setSlashIndex(0);
+ }
+ return next;
+ });
+ }
+ }
+
+ if (mode === 'workspace_prompt') {
+ return ;
+ }
+
+ return (
+
+
+ {timeline.length === 0 ? : }
+ {timeline.length > 0 && timeline[timeline.length - 1]?.kind === 'assistant' ? : null}
+
+
+ {running ? (
+
+
+ {workingDotTick % 2 === 0 ? '◦' : '•'}{' '}
+
+ {` (${runningSeconds}s • 按 Esc 停止)`}
+
+
+ ) : null}
+ {mode === 'slash_menu' ? : null}
+ {mode === 'picker' ? : null}
+ {mode === 'status_panel' ? : null}
+ {mode === 'help_panel' ? : null}
+ {mode === 'approval' ? : null}
+ {mode === 'composer' || mode === 'slash_menu' ? <>> : null}
+
+
+
+ );
+}
+
+function AnimatedSummary({ text, tick }: { text: string; tick: number }) {
+ const chars = Array.from(text || '正在执行...');
+ const cycle = chars.length + 8;
+ const head = tick % cycle;
+ return (
+ <>
+ {chars.map((char, index) => {
+ const distance = head - index;
+ const active = head < chars.length + 4 && distance >= 0 && distance < 5;
+ return (
+
+ {char === ' ' ? '\u00A0' : char}
+
+ );
+ })}
+ >
+ );
+}
+
+function getRunModesForModel(model?: ModelDefinition): RunMode[] {
+ if (!model) return ['fast', 'thinking', 'deep'];
+ if (model.deep_only) return ['deep'];
+ if (model.fast_only || !model.supports_thinking) return ['fast'];
+ return ['fast', 'thinking', 'deep'];
+}
diff --git a/cli/src/api.ts b/cli/src/api.ts
new file mode 100644
index 0000000..65964d6
--- /dev/null
+++ b/cli/src/api.ts
@@ -0,0 +1,313 @@
+import { spawn, type ChildProcess } from 'node:child_process';
+import { setTimeout as sleep } from 'node:timers/promises';
+import { basename } from 'node:path';
+import type { ExecutionMode, ModelDefinition, PermissionMode, RunMode, Workspace } from './types.js';
+
+export type ApiConfig = {
+ baseUrl: string;
+ repoRoot: string;
+ cwd: string;
+ port: number;
+};
+
+export type TaskPollResult = {
+ task_id: string;
+ status: string;
+ error?: string;
+ conversation_id?: string;
+ events: Array<{ idx: number; type: string; data: any; ts?: number }>;
+ next_offset: number;
+};
+
+export class ApiClient {
+ private cookie = '';
+ private csrfToken = '';
+ private serverProcess: ChildProcess | null = null;
+
+ constructor(private readonly config: ApiConfig) {}
+
+ get baseUrl(): string {
+ return this.config.baseUrl;
+ }
+
+ async ensureConnected(): Promise {
+ const ok = await this.tryFetchCsrf();
+ if (!ok) {
+ this.startServer();
+ await this.waitForServer();
+ await this.fetchCsrf();
+ }
+ await this.hostLogin();
+ await this.fetchCsrf();
+ }
+
+ private startServer(): void {
+ if (this.serverProcess) return;
+ const env = {
+ ...process.env,
+ TERMINAL_SANDBOX_MODE: 'host',
+ HOST_PROJECT_PATH: this.config.cwd,
+ WEB_SERVER_PORT: String(this.config.port),
+ AGENTS_CLI_STARTED_SERVER: '1',
+ };
+ this.serverProcess = spawn(
+ process.execPath.includes('node') ? 'python3' : 'python3',
+ ['-m', 'server.app', '--path', this.config.cwd, '--port', String(this.config.port), '--thinking-mode'],
+ {
+ cwd: this.config.repoRoot,
+ env,
+ detached: true,
+ stdio: 'ignore',
+ },
+ );
+ this.serverProcess.unref();
+ }
+
+ private async waitForServer(): Promise {
+ const deadline = Date.now() + 20_000;
+ let lastError: unknown;
+ while (Date.now() < deadline) {
+ const ok = await this.tryFetchCsrf().catch((err) => {
+ lastError = err;
+ return false;
+ });
+ if (ok) return;
+ await sleep(500);
+ }
+ throw new Error(`无法连接本地 Web 服务:${String(lastError || 'timeout')}`);
+ }
+
+ private async tryFetchCsrf(): Promise {
+ try {
+ await this.fetchCsrf();
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ async fetchCsrf(): Promise {
+ const data = await this.request<{ token: string }>('/api/csrf-token', { method: 'GET', skipCsrf: true, allowUnauthorized: true });
+ this.csrfToken = data.token;
+ return this.csrfToken;
+ }
+
+ async hostLogin(): Promise {
+ await this.request('/host-login', { method: 'POST' });
+ }
+
+ async getStatus(): Promise {
+ return this.request('/api/status', { method: 'GET' });
+ }
+
+ async listModels(): Promise {
+ const res = await this.request<{ items?: ModelDefinition[]; data?: any }>('/api/v1/models', { method: 'GET' });
+ return res.items || res.data?.items || [];
+ }
+
+ async getPersonalization(): Promise {
+ return this.request('/api/personalization', { method: 'GET' });
+ }
+
+ async listWorkspaces(): Promise<{ current_workspace_id: string; default_workspace_id: string; workspaces: Workspace[] }> {
+ const res = await this.request<{ data: any }>('/api/host/workspaces', { method: 'GET' });
+ return res.data;
+ }
+
+ async createWorkspace(path: string, label = basename(path)): Promise<{ workspace: Workspace; created: boolean }> {
+ const res = await this.request<{ data: any }>('/api/host/workspaces/create', {
+ method: 'POST',
+ body: { path, label },
+ });
+ return res.data;
+ }
+
+ async selectWorkspace(workspaceId: string): Promise {
+ await this.request('/api/host/workspaces/select', {
+ method: 'POST',
+ body: { workspace_id: workspaceId },
+ });
+ }
+
+ async createConversation(): Promise {
+ return this.request('/api/conversations', { method: 'POST', body: {} });
+ }
+
+ async listConversations(limit = 20): Promise {
+ const res = await this.request<{ data: any }>(`/api/conversations?limit=${limit}`, { method: 'GET' });
+ return res.data?.conversations || res.data || [];
+ }
+
+ async loadConversation(conversationId: string): Promise {
+ return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/load`, { method: 'PUT', body: {} });
+ }
+
+ async getCurrentConversation(): Promise {
+ const res = await this.request<{ data: any }>('/api/conversations/current', { method: 'GET' });
+ return res.data;
+ }
+
+ async compressConversation(conversationId: string): Promise {
+ return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/compress`, { method: 'POST', body: {} });
+ }
+
+ async setModel(modelKey: string): Promise {
+ return this.request('/api/model', { method: 'POST', body: { model_key: modelKey } });
+ }
+
+ async setRunMode(mode: RunMode): Promise {
+ return this.request('/api/thinking-mode', { method: 'POST', body: { mode } });
+ }
+
+ async getPermissionMode(): Promise {
+ return this.request('/api/permission-mode', { method: 'GET' });
+ }
+
+ async setPermissionMode(mode: PermissionMode): Promise {
+ return this.request('/api/permission-mode', { method: 'POST', body: { mode } });
+ }
+
+ async getExecutionMode(): Promise {
+ return this.request('/api/execution-mode', { method: 'GET' });
+ }
+
+ async getConversationVersioning(conversationId: string): Promise {
+ const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/versioning`, { method: 'GET' });
+ return res.data;
+ }
+
+ async setConversationVersioning(conversationId: string, enabled: boolean, trackingMode?: string): Promise {
+ const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/versioning`, {
+ method: 'POST',
+ body: { enabled, ...(trackingMode ? { tracking_mode: trackingMode } : {}) },
+ });
+ return res.data;
+ }
+
+ async getConversationTokens(conversationId: string): Promise {
+ const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/tokens`, { method: 'GET' });
+ return res.data;
+ }
+
+ async listSubAgents(): Promise {
+ const res = await this.request<{ data: any[] }>('/api/sub_agents', { method: 'GET' });
+ return Array.isArray(res.data) ? res.data : [];
+ }
+
+ async listBackgroundCommands(): Promise {
+ const res = await this.request<{ data: any[] }>('/api/background_commands', { method: 'GET' });
+ return Array.isArray(res.data) ? res.data : [];
+ }
+
+ async setExecutionMode(mode: ExecutionMode): Promise {
+ return this.request('/api/execution-mode', { method: 'POST', body: { mode } });
+ }
+
+ async getToolSettings(): Promise {
+ return this.request('/api/tool-settings', { method: 'GET' });
+ }
+
+ async setToolCategory(category: string, enabled: boolean): Promise {
+ return this.request('/api/tool-settings', { method: 'POST', body: { category, enabled } });
+ }
+
+ async getPathAuthorization(): Promise {
+ return this.request('/api/path-authorization', { method: 'GET' });
+ }
+
+ async setPathAuthorization(writablePaths: string[], readableExtraPaths: string[]): Promise {
+ return this.request('/api/path-authorization', {
+ method: 'POST',
+ body: { writable_paths: writablePaths, readable_extra_paths: readableExtraPaths },
+ });
+ }
+
+ async createTask(payload: { message: string; conversation_id?: string; model_key?: string; run_mode?: RunMode }): Promise {
+ const res = await this.request<{ data: any }>('/api/tasks', {
+ method: 'POST',
+ body: payload,
+ });
+ return res.data;
+ }
+
+ async pollTask(taskId: string, offset: number): Promise {
+ const res = await this.request<{ data: TaskPollResult }>(`/api/tasks/${encodeURIComponent(taskId)}?from=${offset}`, { method: 'GET' });
+ return res.data;
+ }
+
+ async cancelTask(taskId: string): Promise {
+ await this.request(`/api/tasks/${encodeURIComponent(taskId)}/cancel`, { method: 'POST', body: {} });
+ }
+
+ async decideApproval(approvalId: string, decision: 'approved' | 'rejected'): Promise {
+ return this.request(`/api/tool-approvals/${encodeURIComponent(approvalId)}/decision`, {
+ method: 'POST',
+ body: { decision },
+ });
+ }
+
+ private async request(path: string, options: { method: string; body?: any; skipCsrf?: boolean; allowUnauthorized?: boolean }): Promise {
+ const headers: Record = { Accept: 'application/json' };
+ if (this.cookie) headers.Cookie = this.cookie;
+ if (options.body !== undefined) headers['Content-Type'] = 'application/json';
+ if (!options.skipCsrf && options.method !== 'GET' && this.csrfToken) headers['X-CSRF-Token'] = this.csrfToken;
+
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
+ method: options.method,
+ headers,
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
+ redirect: 'manual',
+ });
+ this.captureCookies(response);
+
+ if (response.status === 401 && !options.allowUnauthorized) {
+ await this.fetchCsrf();
+ await this.hostLogin();
+ return this.request(path, { ...options, allowUnauthorized: true });
+ }
+
+ const text = await response.text();
+ let data: any = {};
+ if (text) {
+ try {
+ data = JSON.parse(text);
+ } catch {
+ data = { success: false, error: text };
+ }
+ }
+ if (!response.ok || data?.success === false) {
+ throw new Error(data?.error || data?.message || `HTTP ${response.status}`);
+ }
+ return data as T;
+ }
+
+ private captureCookies(response: Response): void {
+ const anyHeaders = response.headers as any;
+ const rawCookies: string[] = typeof anyHeaders.getSetCookie === 'function'
+ ? anyHeaders.getSetCookie()
+ : [];
+ const fallback = response.headers.get('set-cookie');
+ if (fallback) rawCookies.push(fallback);
+ if (!rawCookies.length) return;
+
+ const current = new Map();
+ for (const part of this.cookie.split(';')) {
+ const trimmed = part.trim();
+ if (!trimmed) continue;
+ const eq = trimmed.indexOf('=');
+ if (eq > 0) current.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
+ }
+ for (const raw of rawCookies) {
+ const cookiePair = raw.split(';', 1)[0];
+ const eq = cookiePair.indexOf('=');
+ if (eq > 0) current.set(cookiePair.slice(0, eq), cookiePair.slice(eq + 1));
+ }
+ this.cookie = Array.from(current.entries()).map(([key, value]) => `${key}=${value}`).join('; ');
+ }
+}
+
+export function createDefaultApiClient(cwd: string, repoRoot: string): ApiClient {
+ const port = Number(process.env.AGENTS_API_PORT || process.env.WEB_SERVER_PORT || 8091);
+ const baseUrl = process.env.AGENTS_API_BASE || `http://127.0.0.1:${port}`;
+ return new ApiClient({ baseUrl, repoRoot, cwd, port });
+}
diff --git a/cli/src/commands.ts b/cli/src/commands.ts
new file mode 100644
index 0000000..9f6c390
--- /dev/null
+++ b/cli/src/commands.ts
@@ -0,0 +1,28 @@
+import type { SlashCommand } from './types.js';
+
+export const slashCommands: SlashCommand[] = [
+ { name: '/new', description: '新建对话', kind: 'immediate' },
+ { name: '/resume', description: '查看对话列表并切换对话', kind: 'picker' },
+ { name: '/model', description: '切换模型与思考模式', kind: 'picker' },
+ { name: '/versioning', description: '管理版本控制', aliases: ['/version'], kind: 'picker' },
+ { name: '/permission', description: '切换权限模式', aliases: ['/perm'], kind: 'picker' },
+ { name: '/execution', description: '切换执行环境', aliases: ['/exec', '/env'], kind: 'picker' },
+ { name: '/workspace', description: '选择或切换工作区', kind: 'picker' },
+ { name: '/compact', description: '压缩当前对话上下文', kind: 'immediate' },
+ { name: '/tools', description: '启用或禁用工具', aliases: ['/tool'], kind: 'picker' },
+ { name: '/path', description: '管理路径授权', kind: 'form' },
+ { name: '/skill', description: '选择本轮要使用的 skill', kind: 'picker' },
+ { name: '/status', description: '查看当前状态', kind: 'panel' },
+ { name: '/help', description: '查看命令帮助', kind: 'panel' },
+ { name: '/clear', description: '清空当前 CLI 显示', kind: 'immediate' },
+ { name: '/exit', description: '退出 CLI', kind: 'immediate' },
+];
+
+export function filterCommands(query: string): SlashCommand[] {
+ const normalized = query.trim().toLowerCase();
+ if (!normalized || normalized === '/') return slashCommands;
+ return slashCommands.filter((command) => {
+ const candidates = [command.name, ...(command.aliases || [])];
+ return candidates.some((name) => name.toLowerCase().startsWith(normalized) || name.toLowerCase().includes(normalized));
+ });
+}
diff --git a/cli/src/components.tsx b/cli/src/components.tsx
new file mode 100644
index 0000000..bb7ab6c
--- /dev/null
+++ b/cli/src/components.tsx
@@ -0,0 +1,333 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Box, Text, useCursor, useStdout, type DOMElement } from 'ink';
+import type { CliStatus, SlashCommand, SkillDefinition, TimelineItem, Workspace } from './types.js';
+import { formatContext, shortPath } from './format.js';
+
+const SHOW_THINKING_DETAILS = false;
+
+export function StatusLine({ status }: { status: CliStatus }) {
+ const skill = status.activeSkill ? ` skill:${status.activeSkill.label}` : '';
+ const background = [
+ status.backgroundAgents > 0 ? `${status.backgroundAgents}后台智能体` : '',
+ status.backgroundCommands > 0 ? `${status.backgroundCommands}后台指令` : '',
+ ].filter(Boolean).join(' · ');
+ const left = `${status.model} · ${status.workspace.label} · ${status.permissionMode} · ${status.executionMode}${background ? ` · ${background}` : ''} · 版本控制:${status.versioningEnabled ? '开' : '关'}${skill}`;
+ const right = formatContext(status.contextUsed, status.contextLimit);
+ return (
+
+ {left}
+ {right}
+
+ );
+}
+
+export function Composer({ value, disabled, marginTop = 1 }: { value: string; disabled?: boolean; marginTop?: number }) {
+ const { stdout } = useStdout();
+ const { setCursorPosition } = useCursor();
+ const contentRef = useRef(null);
+ const [contentOrigin, setContentOrigin] = useState<{ x: number; y: number } | null>(null);
+ const lines = String(value || '').split(/\r?\n/);
+ const lastLine = lines[lines.length - 1] || '';
+ const fallbackOrigin = { x: 1, y: Math.max(0, (stdout.rows || 24) - 3) };
+ const origin = contentOrigin || fallbackOrigin;
+ setCursorPosition({
+ x: origin.x + 2 + displayWidth(lastLine),
+ y: origin.y + Math.max(0, lines.length - 1),
+ });
+
+ useEffect(() => {
+ const next = getAbsoluteOrigin(contentRef.current);
+ if (!next) return;
+ setContentOrigin((prev) => (prev && prev.x === next.x && prev.y === next.y ? prev : next));
+ });
+
+ return (
+
+
+
+ {lines.map((line, index) => (
+
+ {index === 0 ? › : }
+ {line}
+ {index === lines.length - 1 ? : null}
+
+ ))}
+
+
+
+ );
+}
+
+function getAbsoluteOrigin(node: DOMElement | null): { x: number; y: number } | null {
+ if (!node?.yogaNode) return null;
+ let x = 0;
+ let y = 0;
+ let current: DOMElement | undefined = node;
+ while (current?.parentNode) {
+ if (!current.yogaNode) return null;
+ x += current.yogaNode.getComputedLeft();
+ y += current.yogaNode.getComputedTop();
+ current = current.parentNode;
+ }
+ return { x, y };
+}
+
+function displayWidth(text: string): number {
+ let width = 0;
+ for (const char of Array.from(text)) {
+ const code = char.codePointAt(0) || 0;
+ if (code === 0) continue;
+ if (code < 32 || (code >= 0x7f && code < 0xa0)) continue;
+ width += isWideCodePoint(code) ? 2 : 1;
+ }
+ return width;
+}
+
+function isWideCodePoint(code: number): boolean {
+ return (
+ code >= 0x1100 && (
+ code <= 0x115f ||
+ code === 0x2329 ||
+ code === 0x232a ||
+ (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
+ (code >= 0xac00 && code <= 0xd7a3) ||
+ (code >= 0xf900 && code <= 0xfaff) ||
+ (code >= 0xfe10 && code <= 0xfe19) ||
+ (code >= 0xfe30 && code <= 0xfe6f) ||
+ (code >= 0xff00 && code <= 0xff60) ||
+ (code >= 0xffe0 && code <= 0xffe6) ||
+ (code >= 0x1f300 && code <= 0x1f64f) ||
+ (code >= 0x1f900 && code <= 0x1f9ff) ||
+ (code >= 0x20000 && code <= 0x3fffd)
+ )
+ );
+}
+
+export function WelcomePanel({ status }: { status: CliStatus }) {
+ return (
+
+ Agents
+
+ Welcome back!
+ {status.model} · {status.runMode}
+ {shortPath(status.directory)}
+
+
+ );
+}
+
+export function Timeline({ items, pulseTick = 0, width = 78 }: { items: TimelineItem[]; pulseTick?: number; width?: number }) {
+ return (
+
+ {items.map((item) => (
+
+ ))}
+
+ );
+}
+
+function TimelineRow({ item, pulseTick }: { item: TimelineItem; pulseTick: number }) {
+ const separator = useFullWidthSeparator();
+ if (item.kind === 'user') {
+ const lines = String(item.body || '').split(/\r?\n/);
+ return (
+
+
+ {lines.map((line, index) => (
+
+ {index === 0 ? › : }
+ {line}
+
+ ))}
+
+
+ );
+ }
+ if (item.kind === 'assistant') {
+ return (
+
+ {separator}
+
+ {wrapIndentedText(String(item.body || ''), 2, separator.length).map((line, index) => (
+ {line}
+ ))}
+ {item.status === 'success' ? <> {separator}> : null}
+
+ );
+ }
+ if (item.kind === 'system') {
+ return • {item.body};
+ }
+ const running = item.status === 'running';
+ const failed = item.status === 'failed';
+ const color = running ? (pulseTick % 2 === 0 ? 'gray' : undefined) : failed ? 'red' : item.kind === 'thinking' ? undefined : 'green';
+ const dot = running ? (pulseTick % 2 === 0 ? '◦' : '•') : '•';
+ const detailColor = item.kind === 'thinking' || item.kind === 'tool' || item.kind === 'sub_agent' ? 'gray' : undefined;
+ const detailWidth = separator.length;
+ const showDetails = item.kind === 'thinking' ? SHOW_THINKING_DETAILS : true;
+ return (
+
+ {dot} {item.title || item.kind}
+ {showDetails && !running && item.body ? wrapIndentedText(`└ ${item.body}`, 2, detailWidth).map((line, index) => (
+ {line}
+ )) : null}
+ {showDetails && (item.kind === 'thinking' || !running) && (item.lines || []).map((line, index) => (
+ wrapIndentedText(line, index === 0 && !item.body ? 2 : 4, detailWidth).map((wrapped, wrappedIndex) => (
+
+ {index === 0 && !item.body && wrappedIndex === 0 ? wrapped.replace(/^ /, ' └ ') : wrapped}
+
+ ))
+ ))}
+
+ );
+}
+
+function useFullWidthSeparator(): string {
+ const { stdout } = useStdout();
+ const width = Math.max(2, (stdout.columns || 80) - 2);
+ return '─'.repeat(width);
+}
+
+function wrapIndentedText(text: string, indent: number, width: number): string[] {
+ const prefix = ' '.repeat(indent);
+ const contentWidth = Math.max(10, width - indent);
+ const result: string[] = [];
+ for (const rawLine of text.split(/\r?\n/)) {
+ const line = rawLine || '';
+ if (displayWidth(line) <= contentWidth) {
+ result.push(`${prefix}${line}`);
+ continue;
+ }
+ let current = '';
+ let currentWidth = 0;
+ for (const char of Array.from(line)) {
+ const charWidth = displayWidth(char);
+ if (current && currentWidth + charWidth > contentWidth) {
+ result.push(`${prefix}${current}`);
+ current = char;
+ currentWidth = charWidth;
+ } else {
+ current += char;
+ currentWidth += charWidth;
+ }
+ }
+ result.push(`${prefix}${current}`);
+ }
+ return result.length ? result : [prefix];
+}
+
+export function SlashMenu({ query, commands, selectedIndex }: { query: string; commands: SlashCommand[]; selectedIndex: number }) {
+ const visibleCount = 5;
+ const start = Math.max(0, selectedIndex - 2);
+ const end = Math.min(commands.length, start + visibleCount);
+ const adjustedStart = Math.max(0, end - visibleCount);
+ const visibleCommands = commands.slice(adjustedStart, end);
+ return (
+
+
+ {visibleCommands.map((command, visibleIndex) => {
+ const index = adjustedStart + visibleIndex;
+ return (
+
+ {index === selectedIndex ? '› ' : ' '}{command.name.padEnd(13)} {command.description}
+
+ );
+ })}
+ {!commands.length ? 无匹配命令 : null}
+
+
+ );
+}
+
+export function Picker({ title, items, selectedIndex }: { title: string; items: Array<{ label: string; description?: string; disabled?: boolean }>; selectedIndex: number }) {
+ const visibleCount = 5;
+ const start = Math.max(0, selectedIndex - 2);
+ const end = Math.min(items.length, start + visibleCount);
+ const adjustedStart = Math.max(0, end - visibleCount);
+ const visibleItems = items.slice(adjustedStart, end);
+ return (
+
+ {title}
+
+ {visibleItems.map((item, visibleIndex) => {
+ const index = adjustedStart + visibleIndex;
+ return (
+
+ {index === selectedIndex ? '› ' : ' '}{item.label.padEnd(20)} {item.description || ''}
+
+ );
+ })}
+ {!items.length ? 暂无选项 : null}
+
+
+ );
+}
+
+export function WorkspacePrompt({ cwd, selectedIndex }: { cwd: string; selectedIndex: number }) {
+ const options = ['确认,添加并继续', '拒绝,退出'];
+ return (
+
+ 当前目录尚未添加为工作区:
+ {shortPath(cwd)}
+
+ Agent 将在此目录中读取文件、执行命令和修改内容。
+ 是否将此目录添加为工作区?
+
+
+ {options.map((label, index) => (
+
+ {index === selectedIndex ? '› ' : ' '}{index + 1}. {label}
+
+ ))}
+
+
+ );
+}
+
+export function StatusPanel({ status }: { status: CliStatus }) {
+ return (
+
+ {'>_ Agents CLI'}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function StatusField({ label, value }: { label: string; value: string }) {
+ return {label.padEnd(15)} {value};
+}
+
+export function HelpPanel({ commands }: { commands: SlashCommand[] }) {
+ return (
+
+ Commands
+
+ {commands.map((command) => (
+ {command.name.padEnd(13)} {command.description}
+ ))}
+
+
+ );
+}
+
+export function SkillHint({ skill }: { skill?: SkillDefinition }) {
+ if (!skill) return null;
+ return + 先阅读 {skill.label} skill;
+}
+
+export function WorkspaceList({ workspaces, selectedIndex }: { workspaces: Workspace[]; selectedIndex: number }) {
+ return (
+ ({ label: workspace.label, description: shortPath(workspace.path) }))}
+ />
+ );
+}
diff --git a/cli/src/eventMapper.ts b/cli/src/eventMapper.ts
new file mode 100644
index 0000000..deec0c0
--- /dev/null
+++ b/cli/src/eventMapper.ts
@@ -0,0 +1,281 @@
+import type { TimelineItem } from './types.js';
+import { lastLines } from './format.js';
+
+type ToolState = {
+ timelineId: string;
+ name: string;
+ args: any;
+ title: string;
+ summary: string;
+};
+
+export type EventRenderState = {
+ thinkingId?: string;
+ thinkingStartedAt?: number;
+ thinkingText: string;
+ thinkingLines: string[];
+ assistantId?: string;
+ assistantText: string;
+ tools: Map;
+};
+
+export function createEventRenderState(): EventRenderState {
+ return { thinkingText: '', thinkingLines: [], assistantText: '', tools: new Map() };
+}
+
+export function reduceTaskEvent(items: TimelineItem[], state: EventRenderState, event: { type: string; data: any }): TimelineItem[] {
+ const data = event.data || {};
+ switch (event.type) {
+ case 'thinking_start': {
+ const timelineId = newId();
+ state.thinkingId = timelineId;
+ state.thinkingStartedAt = Date.now();
+ state.thinkingText = '';
+ state.thinkingLines = [];
+ return [...items, { id: timelineId, kind: 'thinking', title: '思考中', status: 'running', lines: [] }];
+ }
+ case 'thinking_chunk': {
+ const content = String(data.content || '');
+ state.thinkingText += content;
+ const lines = state.thinkingText.split(/\r?\n/).filter((line) => line.length > 0);
+ state.thinkingLines = lines;
+ if (!state.thinkingId) {
+ state.thinkingId = newId();
+ state.thinkingStartedAt = Date.now();
+ state.thinkingText = content;
+ items = [...items, { id: state.thinkingId, kind: 'thinking', title: '思考中', status: 'running', lines: [] }];
+ }
+ return patchItem(items, state.thinkingId, { lines: lastLines(state.thinkingLines, 5) });
+ }
+ case 'thinking_end': {
+ if (!state.thinkingId) return items;
+ const elapsed = state.thinkingStartedAt ? `${((Date.now() - state.thinkingStartedAt) / 1000).toFixed(1)}s` : '';
+ const id = state.thinkingId;
+ state.thinkingId = undefined;
+ state.thinkingStartedAt = undefined;
+ state.thinkingText = '';
+ state.thinkingLines = [];
+ return patchItem(items, id, {
+ title: `思考完成 ${elapsed}`.trim(),
+ status: 'success',
+ body: undefined,
+ summary: undefined,
+ lines: [],
+ });
+ }
+ case 'text_start': {
+ const timelineId = newId();
+ state.assistantId = timelineId;
+ state.assistantText = '';
+ return [...items, { id: timelineId, kind: 'assistant', status: 'running', body: '' }];
+ }
+ case 'text_chunk': {
+ const content = String(data.content || '');
+ if (!state.assistantId) {
+ state.assistantId = newId();
+ state.assistantText = '';
+ items = [...items, { id: state.assistantId, kind: 'assistant', status: 'running', body: '' }];
+ }
+ state.assistantText += content;
+ return patchItem(items, state.assistantId, { body: state.assistantText });
+ }
+ case 'text_end': {
+ const full = String(data.full_content || state.assistantText || '');
+ if (!state.assistantId) return full ? [...items, { id: newId(), kind: 'assistant', title: '完成', status: 'success', body: full }] : items;
+ const id = state.assistantId;
+ state.assistantId = undefined;
+ state.assistantText = '';
+ return patchItem(items, id, { status: 'success', body: full });
+ }
+ case 'tool_preparing':
+ case 'tool_start': {
+ const toolId = String(data.id || data.execution_id || data.preparing_id || newId());
+ const name = String(data.name || 'tool');
+ const args = data.arguments || {};
+ const title = toolTitle(name, args, 'running');
+ const summary = toolSummaryPreview(data, name, args, 'running');
+ const existingKey = data.preparing_id ? String(data.preparing_id) : toolId;
+ const existing = state.tools.get(existingKey);
+ if (existing) {
+ state.tools.delete(existingKey);
+ state.tools.set(toolId, { ...existing, name, args, title, summary });
+ return patchItem(items, existing.timelineId, {
+ title,
+ summary,
+ status: 'running',
+ body: toolInitialBody(name, args),
+ lines: undefined,
+ });
+ }
+ const timelineId = newId();
+ state.tools.set(toolId, { timelineId, name, args, title, summary });
+ return [...items, { id: timelineId, kind: 'tool', title, summary, status: 'running', body: toolInitialBody(name, args) }];
+ }
+ case 'tool_update_action':
+ case 'update_action': {
+ const toolId = String(data.id || data.execution_id || data.preparing_id || '');
+ const tool = state.tools.get(toolId);
+ if (!tool) return items;
+ if (data.result !== undefined || data.status) {
+ const status = String(data.status || '').toLowerCase() === 'completed' ? 'success' : String(data.status || '').toLowerCase() === 'failed' ? 'failed' : 'running';
+ return patchItem(items, tool.timelineId, {
+ title: toolTitle(tool.name, tool.args, status),
+ summary: toolSummaryPreview(data, tool.name, tool.args, status),
+ status,
+ body: toolResultBody(tool.name, tool.args, data.result),
+ lines: toolResultLines(tool.name, tool.args, data.result),
+ });
+ }
+ return items;
+ }
+ case 'append_payload':
+ case 'modify_payload': {
+ return [...items, renderPayloadEvent(data)];
+ }
+ case 'tool_approval_required': {
+ const approval = data.approval || data;
+ return [...items, { id: newId(), kind: 'tool', title: `需要审批:${approval.tool_name || '工具调用'}`, status: 'running', body: approval.preview?.summary || '' }];
+ }
+ case 'tool_approval_resolved': {
+ return [...items, { id: newId(), kind: 'tool', title: `审批${data.decision === 'approved' ? '通过' : '拒绝'}`, status: data.decision === 'approved' ? 'success' : 'failed', body: data.reason || '' }];
+ }
+ case 'task_complete': {
+ return items;
+ }
+ case 'task_stopped': {
+ return [...items, { id: newId(), kind: 'tool', title: '任务已停止', status: 'cancelled', body: data.message || '' }];
+ }
+ case 'error': {
+ return [...items, { id: newId(), kind: 'tool', title: '任务失败', status: 'failed', body: data.message || data.error || '未知错误' }];
+ }
+ case 'token_update': {
+ return items;
+ }
+ default:
+ return items;
+ }
+}
+
+function toolTitle(name: string, args: any, status: string): string {
+ if (name === 'run_command') return `运行 ${args.command || ''}`.trim();
+ if (name === 'run_python') return '运行 Python';
+ if (name === 'write_file') return `Write ${args.file_path || args.path || ''} ${countWrite(args.content || '')}`.trim();
+ if (name === 'edit_file') return `Edited ${args.file_path || args.path || ''} ${countEdit(args)}`.trim();
+ if (name === 'read_file' || name === 'read_skill') return `阅读 ${args.path || args.file_path || ''}`.trim();
+ if (name === 'terminal_session') return `${status === 'running' ? '创建' : '终端'} ${args.session_name || args.name || ''}`.trim();
+ if (name === 'terminal_input') return `终端输入 ${args.command || args.input || ''}`.trim();
+ return name;
+}
+
+function toolSummaryPreview(data: any, name: string, args: any, status: string): string {
+ const intent = data?.intent_rendered || data?.intent_full || data?.intent || args?.intent_rendered || args?.intent_full || args?.intent || '';
+ if (intent) return truncateSummaryPreview(firstLine(String(intent)));
+ if (status === 'success') return '工具执行完成';
+ if (status === 'failed') return `${name || '工具'} 执行失败`;
+ if (data?.status === 'preparing') return `准备调用 ${name || '工具'}...`;
+ return `正在调用 ${name || '工具'}...`;
+}
+
+function firstLine(text: string): string {
+ return text.split(/\r?\n/)[0] || '';
+}
+
+function truncateSummaryPreview(text: string): string {
+ const value = String(text || '');
+ return value.length > 25 ? `${value.slice(0, 25)}...` : value;
+}
+
+function toolInitialBody(name: string, args: any): string {
+ if (name === 'write_file' || name === 'edit_file') return '';
+ return '';
+}
+
+function toolResultBody(name: string, args: any, result: any): string {
+ if (typeof result === 'string') return summarizeText(result);
+ if (!result) return '';
+ if (name === 'run_command' || name === 'terminal_input' || name === 'run_python') {
+ const exitCode = result.exit_code ?? result.returncode;
+ const text = String(result?.output || result?.stdout || result?.stderr || '').trim();
+ if (exitCode !== undefined && exitCode !== 0) return text ? `exit code ${exitCode}` : `exit code ${exitCode}\n(no output)`;
+ return text ? '' : '(no output)';
+ }
+ if (name === 'read_file' || name === 'read_skill') {
+ const content = result.content || result.text || '';
+ const count = String(content).split(/\r?\n/).filter(Boolean).length;
+ const type = result.type || 'read';
+ if (type === 'extract') return `提取了 ${count} 行`;
+ return `读取了 ${count} 行`;
+ }
+ if (name === 'write_file') return '';
+ if (name === 'edit_file') return '';
+ if (result.error) return String(result.error);
+ return result.success === false ? '失败' : '成功';
+}
+
+function toolResultLines(name: string, args: any, result: any): string[] | undefined {
+ if (name === 'write_file') {
+ return String(args.content || '').split('\n').map((line, idx) => `${idx + 1} +${line}`);
+ }
+ if (name === 'edit_file') {
+ return editLines(args);
+ }
+ const text = typeof result === 'string' ? result : String(result?.output || result?.stdout || result?.content || result?.text || '');
+ if (!text || name === 'read_file' || name === 'read_skill') return undefined;
+ return summarizeLines(text);
+}
+
+function renderPayloadEvent(data: any): TimelineItem {
+ const path = data.path || data.file_path || '';
+ const lines: string[] = [];
+ if (Array.isArray(data.old_lines)) lines.push(...data.old_lines.map((line: string, idx: number) => `${idx + 1} -${line}`));
+ if (Array.isArray(data.new_lines)) lines.push(...data.new_lines.map((line: string, idx: number) => `${idx + 1} +${line}`));
+ return { id: newId(), kind: 'tool', title: `Edited ${path}`, status: 'success', lines };
+}
+
+function countWrite(content: string): string {
+ const added = content ? content.split('\n').length : 0;
+ return `(+${added} -0)`;
+}
+
+function countEdit(args: any): string {
+ const lines = editLines(args);
+ const removed = lines.filter((line) => line.includes(' -')).length;
+ const added = lines.filter((line) => line.includes(' +')).length;
+ return `(+${added} -${removed})`;
+}
+
+function editLines(args: any): string[] {
+ const replacements = Array.isArray(args.replacements) ? args.replacements : [];
+ const result: string[] = [];
+ const emitPair = (oldText: string, newText: string) => {
+ const oldLines = oldText.replace(/\\n/g, '\n').split('\n');
+ const newLines = newText.replace(/\\n/g, '\n').split('\n');
+ oldLines.filter((line) => line.length > 0).forEach((line, idx) => result.push(`${idx + 1} -${line}`));
+ newLines.filter((line) => line.length > 0).forEach((line, idx) => result.push(`${idx + 1} +${line}`));
+ };
+ if (replacements.length) {
+ for (const rep of replacements) emitPair(String(rep.old_text || rep.old || ''), String(rep.new_text || rep.new || ''));
+ } else {
+ emitPair(String(args.old_string || ''), String(args.new_string || ''));
+ }
+ return result;
+}
+
+function summarizeText(text: string): string {
+ const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
+ return summarizeLines(lines.join('\n')).join('\n');
+}
+
+function summarizeLines(text: string): string[] {
+ const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
+ if (lines.length <= 5) return lines;
+ return [lines[0]!, lines[1]!, `… +${lines.length - 4} lines`, lines[lines.length - 2]!, lines[lines.length - 1]!];
+}
+
+function patchItem(items: TimelineItem[], id: string, patch: Partial): TimelineItem[] {
+ return items.map((item) => item.id === id ? { ...item, ...patch } : item);
+}
+
+function newId(): string {
+ return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
+}
diff --git a/cli/src/format.ts b/cli/src/format.ts
new file mode 100644
index 0000000..04b6262
--- /dev/null
+++ b/cli/src/format.ts
@@ -0,0 +1,29 @@
+import { homedir } from 'node:os';
+
+export function shortPath(path: string): string {
+ const home = homedir();
+ return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
+}
+
+export function formatContext(used: number, limit: number): string {
+ if (!limit || limit <= 0) return formatTokens(used);
+ const left = Math.max(0, limit - used);
+ const pct = limit > 0 ? Math.round((left / limit) * 100) : 0;
+ return `${pct}% left ${formatTokens(used)} / ${formatTokens(limit)}`;
+}
+
+export function formatTokens(value: number): string {
+ if (value >= 1000) {
+ const rounded = Math.round((value / 1000) * 10) / 10;
+ return `${rounded}K`;
+ }
+ return String(value);
+}
+
+export function nowSessionId(): string {
+ return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
+}
+
+export function lastLines(lines: string[], limit = 5): string[] {
+ return lines.slice(Math.max(0, lines.length - limit));
+}
diff --git a/cli/src/main.tsx b/cli/src/main.tsx
new file mode 100644
index 0000000..8916fb7
--- /dev/null
+++ b/cli/src/main.tsx
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+import React from 'react';
+import { render } from 'ink';
+import App from './App.js';
+
+process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
+render();
diff --git a/cli/src/types.ts b/cli/src/types.ts
new file mode 100644
index 0000000..d7a1104
--- /dev/null
+++ b/cli/src/types.ts
@@ -0,0 +1,65 @@
+export type PermissionMode = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted';
+export type ExecutionMode = 'sandbox' | 'direct';
+export type RunMode = 'fast' | 'thinking' | 'deep';
+
+export type Workspace = {
+ workspace_id: string;
+ label: string;
+ path: string;
+};
+
+export type WorkspaceCatalog = {
+ default_workspace_id: string;
+ workspaces: Workspace[];
+};
+
+export type CliStatus = {
+ model: string;
+ runMode: RunMode;
+ directory: string;
+ workspace: Workspace;
+ permissionMode: PermissionMode;
+ executionMode: ExecutionMode;
+ sessionId: string;
+ contextUsed: number;
+ contextLimit: number;
+ versioningEnabled: boolean;
+ backgroundAgents: number;
+ backgroundCommands: number;
+ activeSkill?: SkillDefinition;
+};
+
+export type SkillDefinition = {
+ id: string;
+ label: string;
+ description: string;
+};
+
+export type ModelDefinition = {
+ model_key: string;
+ name: string;
+ description?: string;
+ supports_thinking?: boolean;
+ fast_only?: boolean;
+ deep_only?: boolean;
+ context_window?: number;
+};
+
+export type TimelineItem = {
+ id: string;
+ kind: 'system' | 'user' | 'assistant' | 'tool' | 'thinking' | 'sub_agent';
+ title?: string;
+ summary?: string;
+ body?: string;
+ status?: 'running' | 'success' | 'failed' | 'cancelled';
+ lines?: string[];
+};
+
+export type SlashCommandKind = 'immediate' | 'picker' | 'panel' | 'form';
+
+export type SlashCommand = {
+ name: string;
+ description: string;
+ aliases?: string[];
+ kind: SlashCommandKind;
+};
diff --git a/cli/src/workspaces.ts b/cli/src/workspaces.ts
new file mode 100644
index 0000000..9cd9135
--- /dev/null
+++ b/cli/src/workspaces.ts
@@ -0,0 +1,97 @@
+import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname, resolve, basename } from 'node:path';
+import type { Workspace, WorkspaceCatalog } from './types.js';
+
+export const repoRoot = process.env.AGENTS_REPO_ROOT || resolve(new URL('../..', import.meta.url).pathname);
+const configPath = resolve(repoRoot, 'config/host_workspaces.json');
+
+function defaultCatalog(): WorkspaceCatalog {
+ const fallbackPath = resolve(repoRoot, 'project');
+ return {
+ default_workspace_id: 'default',
+ workspaces: [
+ {
+ workspace_id: 'default',
+ label: '默认工作区',
+ path: fallbackPath,
+ },
+ ],
+ };
+}
+
+function normalizePath(path: string): string {
+ return resolve(path);
+}
+
+function slugify(value: string): string {
+ const slug = value
+ .trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9._-]+/g, '-')
+ .replace(/-{2,}/g, '-')
+ .replace(/^[-._]+|[-._]+$/g, '');
+ return slug || 'workspace';
+}
+
+export function getHostWorkspacesConfigPath(): string {
+ return configPath;
+}
+
+export function loadWorkspaceCatalog(): WorkspaceCatalog {
+ try {
+ const raw = JSON.parse(readFileSync(configPath, 'utf8')) as Partial;
+ const workspaces = Array.isArray(raw.workspaces) ? raw.workspaces : [];
+ const normalized = workspaces
+ .filter((item): item is Workspace => Boolean(item && item.workspace_id && item.path))
+ .map((item) => ({
+ workspace_id: String(item.workspace_id),
+ label: String(item.label || item.workspace_id),
+ path: normalizePath(String(item.path)),
+ }));
+ if (!normalized.length) return defaultCatalog();
+ const defaultId = raw.default_workspace_id && normalized.some((ws) => ws.workspace_id === raw.default_workspace_id)
+ ? String(raw.default_workspace_id)
+ : normalized[0]!.workspace_id;
+ return { default_workspace_id: defaultId, workspaces: normalized };
+ } catch {
+ return defaultCatalog();
+ }
+}
+
+export function saveWorkspaceCatalog(catalog: WorkspaceCatalog): void {
+ mkdirSync(dirname(configPath), { recursive: true });
+ writeFileSync(configPath, JSON.stringify(catalog, null, 2), 'utf8');
+}
+
+export function findWorkspaceByPath(catalog: WorkspaceCatalog, cwd: string): Workspace | undefined {
+ const target = normalizePath(cwd);
+ return catalog.workspaces.find((ws) => normalizePath(ws.path) === target);
+}
+
+export function createWorkspaceForPath(catalog: WorkspaceCatalog, cwd: string): { catalog: WorkspaceCatalog; workspace: Workspace; created: boolean } {
+ const path = normalizePath(cwd);
+ const existing = findWorkspaceByPath(catalog, path);
+ if (existing) return { catalog, workspace: existing, created: false };
+
+ const baseLabel = basename(path) || 'workspace';
+ const baseId = slugify(baseLabel);
+ const used = new Set(catalog.workspaces.map((ws) => ws.workspace_id));
+ let workspaceId = baseId;
+ let suffix = 2;
+ while (used.has(workspaceId)) {
+ workspaceId = `${baseId}-${suffix}`;
+ suffix += 1;
+ }
+
+ const workspace: Workspace = {
+ workspace_id: workspaceId,
+ label: baseLabel,
+ path,
+ };
+ const nextCatalog: WorkspaceCatalog = {
+ default_workspace_id: catalog.default_workspace_id || workspaceId,
+ workspaces: [...catalog.workspaces, workspace],
+ };
+ saveWorkspaceCatalog(nextCatalog);
+ return { catalog: nextCatalog, workspace, created: true };
+}
diff --git a/cli/tsconfig.json b/cli/tsconfig.json
new file mode 100644
index 0000000..c08ab58
--- /dev/null
+++ b/cli/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "jsx": "react-jsx",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "types": ["node", "react"]
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/docs/cli_slash_commands_spec.md b/docs/cli_slash_commands_spec.md
new file mode 100644
index 0000000..541fbb8
--- /dev/null
+++ b/docs/cli_slash_commands_spec.md
@@ -0,0 +1,764 @@
+# CLI Slash 命令设计规范(草案)
+
+> 目标:定义 CLI 中 `/` 命令的命名、交互方式、显示方式与最小功能集。
+> 关联文档:`docs/cli_ui_display_spec.md`
+
+## 1. 总体原则
+
+1. **所有主动操作都从 `/` 命令进入**:避免额外快捷键过多导致记忆负担。
+2. **命令面板统一交互**:输入过滤、上下选择、Enter 确认。
+3. **没有必要的命令不做**:后台内部操作、配置文件管理、文件浏览等不暴露成命令。
+4. **审批不做独立命令**:有审批时直接占用输入栏显示审批交互。
+5. **命令只负责 UI 入口**:真正执行仍通过后端 API / CLI 适配层完成。
+
+---
+
+## 2. 启动命令与工作区初始化
+
+### 2.1 启动命令
+
+正式启动命令建议为:
+
+```bash
+agents
+```
+
+开发期或兼容别名可保留:
+
+```bash
+agents-cli
+```
+
+命名理由:
+
+- 和项目名一致
+- 短、好记
+- 类似 `codex` / `claude` 的产品级 CLI 命令
+- 比 `agent` 更不容易和泛称混淆
+
+### 2.2 默认工作区目录
+
+用户在任意目录执行:
+
+```bash
+cd /Users/jojo/Desktop/test
+agents
+```
+
+CLI 默认使用 `process.cwd()` 作为本次启动的工作区目录。
+
+### 2.3 当前目录已是工作区
+
+如果当前目录已经存在于工作区列表中:
+
+1. 自动选择该工作区
+2. 进入 CLI 主界面
+
+### 2.4 当前目录不是工作区
+
+如果当前目录不在工作区列表中,启动时先显示确认提示:
+
+```text
+当前目录尚未添加为工作区:
+
+ /Users/jojo/Desktop/test
+
+Agent 将在此目录中读取文件、执行命令和修改内容。
+是否将此目录添加为工作区?
+
+› 1. 确认,添加并继续
+ 2. 拒绝,退出
+```
+
+选择 `确认`:
+
+```text
+• 已添加工作区 test
+ └ /Users/jojo/Desktop/test
+```
+
+然后进入 CLI 主界面。
+
+选择 `拒绝`:
+
+```text
+已取消,退出 Agents CLI。
+```
+
+并退出程序。
+
+### 2.5 工作区匹配规则
+
+v1 只做精确路径匹配:
+
+- 当前目录路径与已有工作区路径完全一致:视为已有工作区
+- 当前目录是已有工作区的子目录:仍视为新目录,需要确认是否添加
+
+这样可以保证行为明确:
+
+> 在哪个目录启动,Agent 就在哪个目录工作。
+
+### 2.6 自动工作区名称
+
+自动创建工作区时:
+
+- 默认名称使用目录 basename
+- 如 `/Users/jojo/Desktop/test` → `test`
+- 如果名称重复,可自动追加序号,例如 `test-2`
+
+---
+
+## 3. 命令面板交互
+
+### 3.1 触发
+
+用户在输入栏输入 `/` 后打开命令面板。
+
+```text
+› /
+```
+
+输入更多字符后实时过滤:
+
+```text
+› /a
+
+ /compact 压缩当前对话上下文
+› /path 管理路径授权
+ /permission 切换权限模式
+```
+
+### 3.2 选择
+
+- `↑` / `↓` 切换当前选中项
+- 当前选中项使用高亮颜色
+- `Enter` 执行当前命令
+- `Esc` 关闭命令面板
+
+### 3.3 展示格式
+
+```text
+› /mo
+
+› /model 切换模型与思考模式
+```
+
+格式规则:
+
+- 左侧显示命令名
+- 右侧显示简短说明
+- 说明必须短,不超过一行
+
+---
+
+## 4. 最终命令清单
+
+| 命令 | 说明 | 类型 |
+|---|---|---|
+| `/new` | 新建对话 | 立即执行 / 可确认 |
+| `/resume` | 查看对话列表并切换对话 | 选择器 |
+| `/model` | 切换模型与思考模式 | 选择器 |
+| `/versioning` | 管理版本控制 | 选择器 |
+| `/permission` | 切换权限模式 | 选择器 |
+| `/execution` | 切换执行环境 | 选择器 |
+| `/workspace` | 选择或切换工作区 | 选择器 |
+| `/compact` | 压缩当前对话上下文 | 立即执行 / 可确认 |
+| `/tools` | 启用或禁用工具 | 选择器 |
+| `/path` | 管理路径授权 | 选择器 / 表单 |
+| `/skill` | 选择本轮要使用的 skill | 选择器 |
+| `/status` | 查看当前状态 | 信息面板 |
+| `/help` | 查看命令帮助 | 信息面板 |
+| `/clear` | 清空当前 CLI 显示 | 立即执行 |
+| `/exit` | 退出 CLI | 立即执行 / 可确认 |
+
+---
+
+## 5. 不做的命令
+
+以下命令不做:
+
+| 命令 | 不做原因 |
+|---|---|
+| `/approval` | 有待审批内容时直接在输入栏显示审批交互,无需手动进入 |
+| `/memory` | 属于后端/Agent 内部操作,不作为 CLI 常用入口 |
+| `/files` | CLI 不做文件浏览器,文件操作由 Agent 工具执行 |
+| `/tasks` | 后台任务数量放在底部状态栏;详细逻辑先不暴露 |
+| `/focus` | 暂不做额外显示模式 |
+
+---
+
+## 6. 各命令详细设计
+
+### 6.1 `/new`
+
+说明:
+
+```text
+/new 新建对话
+```
+
+行为:
+
+- 创建一个空白对话
+- 清空当前 CLI 消息区
+- 切换到底部输入待命态
+
+如果当前有未完成任务:
+
+```text
+当前任务仍在运行,是否新建对话?
+
+› 1. 新建并停止当前任务
+ 2. 等待当前任务完成
+ 3. 取消
+```
+
+---
+
+### 6.2 `/resume`
+
+说明:
+
+```text
+/resume 查看对话列表并切换对话
+```
+
+行为:
+
+- 打开历史对话选择器
+- 显示最近对话
+- 选择后切换当前会话
+
+显示示例:
+
+```text
+Resume conversation
+
+› 今天 14:32 修复 CLI 设计文档
+ 今天 13:10 调研 Claude Code CLI
+ 昨天 22:45 Android 发布联动
+```
+
+字段建议:
+
+- 时间
+- 对话标题 / 首条用户消息摘要
+- 当前工作区(可选)
+- 模型(可选)
+
+---
+
+### 6.3 `/model`
+
+说明:
+
+```text
+/model 切换模型与思考模式
+```
+
+行为:
+
+- 打开模型选择器
+- 支持模型和运行模式一起切换
+
+显示示例:
+
+```text
+Model
+
+› 1. kimi-k2.5 当前模型
+ 2. gpt-5.5 高质量编码
+ 3. gpt-5.4-mini 快速低成本
+
+Run mode
+
+› 1. thinking 思考模式
+ 2. fast 快速模式
+ 3. deep 深度思考
+```
+
+规则:
+
+- 当前选中项标记为 `current`
+- 被管理员禁用的模型显示为 disabled,不可选
+- fast-only / deep-only 模型需要显示提示
+
+---
+
+### 6.4 `/versioning`
+
+说明:
+
+```text
+/versioning 管理版本控制
+```
+
+为什么用 `versioning`:
+
+- 比 `/version` 更明确是“版本控制”
+- 避免和模型版本混淆
+
+行为:
+
+- 打开版本控制设置 / 恢复面板
+
+显示示例:
+
+```text
+Versioning
+
+› 1. 开启版本控制
+ 2. 关闭版本控制
+ 3. 查看版本记录
+ 4. 恢复到选定版本
+```
+
+字段建议:
+
+- 当前是否开启
+- 当前跟踪模式
+- 最近版本点
+- 是否与当前工作区匹配
+
+---
+
+### 6.5 `/permission`
+
+说明:
+
+```text
+/permission 切换权限模式
+```
+
+行为:
+
+- 打开权限模式选择器
+
+显示示例:
+
+```text
+Permission Mode
+
+› 1. Readonly 只读,不允许写入
+ 2. Approval 需要审批后执行写入/敏感操作
+ 3. Auto Approval 由自动审批智能体审核
+ 4. Unrestricted 不做权限拦截
+```
+
+规则:
+
+- 当前权限模式显示 `current`
+- 高风险项需要二次确认
+
+---
+
+### 6.6 `/execution`
+
+说明:
+
+```text
+/execution 切换执行环境
+```
+
+为什么用 `execution`:
+
+- 覆盖 `sandbox` 与 `direct`
+- 比 `/env` 更明确
+- 比 `/runtime` 更贴近用户动作
+
+行为:
+
+- 打开执行环境选择器
+
+显示示例:
+
+```text
+Execution Environment
+
+› 1. Sandbox 使用 OS 沙箱执行命令
+ 2. Direct 在宿主机直接执行命令
+```
+
+规则:
+
+- `Direct` 需要风险提示
+- 如果 direct 有 TTL,显示剩余时间
+
+---
+
+### 6.7 `/workspace`
+
+说明:
+
+```text
+/workspace 选择或切换工作区
+```
+
+行为:
+
+- 打开工作区列表
+- 支持选择已有工作区
+- 支持新建工作区
+
+显示示例:
+
+```text
+Workspace
+
+› 1. agents /Users/jojo/Desktop/agents/正在修复中/agents current
+ 2. test /Users/jojo/Desktop/test
+ 3. 新建工作区...
+```
+
+新建工作区时,输入栏切换成表单:
+
+```text
+Workspace path
+› /Users/jojo/Desktop/new-project
+```
+
+可选第二步:
+
+```text
+Workspace name
+› new-project
+```
+
+---
+
+### 6.8 `/compact`
+
+说明:
+
+```text
+/compact 压缩当前对话上下文
+```
+
+行为:
+
+- 触发当前对话压缩
+- 压缩前可确认
+
+显示示例:
+
+```text
+确认压缩当前对话上下文?
+
+› 1. 压缩
+ 2. 取消
+```
+
+完成后:
+
+```text
+• 对话已压缩
+ └ 当前上下文 24k/200k
+```
+
+---
+
+### 6.9 `/tools`
+
+说明:
+
+```text
+/tools 启用或禁用工具
+```
+
+行为:
+
+- 打开工具分类选择器
+- 支持启用/禁用工具分类
+
+显示示例:
+
+```text
+Tools
+
+› [x] terminal 终端命令
+ [x] file 文件读写
+ [ ] web 网络访问
+ [x] sub_agent 子智能体
+```
+
+规则:
+
+- 空格切换选中
+- Enter 保存
+- Esc 取消
+
+---
+
+### 6.10 `/path`
+
+说明:
+
+```text
+/path 管理路径授权
+```
+
+行为:
+
+- 查看已授权路径
+- 添加可读写路径
+- 添加只读路径
+- 删除授权路径
+
+显示示例:
+
+```text
+Authorized Paths
+
+› 1. 添加可读写路径
+ 2. 添加只读路径
+ 3. 删除授权路径
+
+Current
+ rw /Users/jojo/Desktop/agents
+ ro /Users/jojo/Downloads
+```
+
+添加路径表单:
+
+```text
+Path
+› /Users/jojo/Desktop/project
+```
+
+---
+
+### 6.11 `/skill`
+
+说明:
+
+```text
+/skill 选择本轮要使用的 skill
+```
+
+这是 CLI 新增能力,Web 端暂无对应入口。
+
+行为:
+
+- 打开 skill 列表
+- 用户选择一个 skill
+- 下一次发送用户消息时,CLI 在用户消息下方附加一条 user 消息:
+
+```text
+先阅读 docs skill
+```
+
+### 显示示例
+
+```text
+Skill
+
+› docs 阅读文档相关技能
+ frontend 前端开发相关技能
+ backend 后端开发相关技能
+```
+
+选择后输入栏状态栏显示:
+
+```text
+skill: docs
+```
+
+发送消息时:
+
+```text
+› 帮我整理一下 CLI 文档
+ + 先阅读 docs skill
+```
+
+规则:
+
+- v1 先做单选
+- 选择后只作用于下一条用户消息
+- 消息发送后清空当前 skill
+- 后续可扩展为多选
+
+---
+
+### 6.12 `/status`
+
+说明:
+
+```text
+/status 查看当前状态
+```
+
+行为:
+
+- 打开状态信息面板
+- 类似 Codex CLI 的状态卡
+
+显示示例:
+
+```text
+╭────────────────────────────────────────────────────────────────────╮
+│ >_ Agents CLI │
+│ │
+│ Model: kimi-k2.5 │
+│ Directory: ~/Desktop/agents/正在修复中/agents │
+│ Workspace: agents │
+│ Permissions: auto_approval │
+│ Execution: sandbox │
+│ Session: 019e253e-7310-7330-ac02-859338456535 │
+│ Context window: 99% left (14.6K used / 258K) │
+╰────────────────────────────────────────────────────────────────────╯
+```
+
+#### 字段说明
+
+| 字段 | 说明 |
+|---|---|
+| `Model` | 当前模型 |
+| `Directory` | 当前工作目录 |
+| `Workspace` | 当前工作区名称 |
+| `Permissions` | 当前权限模式 |
+| `Execution` | 当前执行环境 |
+| `Session` | 当前会话 ID |
+| `Context window` | 上下文窗口使用情况 |
+
+---
+
+### 6.13 `/help`
+
+说明:
+
+```text
+/help 查看命令帮助
+```
+
+显示命令列表:
+
+```text
+Commands
+
+ /new 新建对话
+ /resume 查看对话列表并切换对话
+ /model 切换模型与思考模式
+ /versioning 管理版本控制
+ /permission 切换权限模式
+ /execution 切换执行环境
+ /workspace 选择或切换工作区
+ /compact 压缩当前对话上下文
+ /tools 启用或禁用工具
+ /path 管理路径授权
+ /skill 选择本轮要使用的 skill
+ /status 查看当前状态
+ /clear 清空当前 CLI 显示
+ /exit 退出 CLI
+```
+
+---
+
+### 6.14 `/clear`
+
+说明:
+
+```text
+/clear 清空当前 CLI 显示
+```
+
+行为:
+
+- 只清空 CLI 当前显示区
+- 不删除对话历史
+- 不影响后端会话
+
+---
+
+### 6.15 `/exit`
+
+说明:
+
+```text
+/exit 退出 CLI
+```
+
+行为:
+
+- 如果当前无任务,直接退出
+- 如果有运行任务,显示确认:
+
+```text
+当前任务仍在运行,是否退出?
+
+› 1. 退出并停止当前任务
+ 2. 后台继续运行并退出
+ 3. 取消
+```
+
+---
+
+## 7. 审批交互与命令系统的关系
+
+审批不是 `/approval` 命令。
+
+当有待审批操作时,输入栏临时切换为审批态:
+
+```text
+需要批准:运行 rm -rf dist
+
+› 1. 允许一次
+ 2. 切换到无限制
+ 3. 拒绝
+```
+
+审批完成后恢复普通输入态。
+
+---
+
+## 8. 底部状态栏与 `/status` 的关系
+
+底部状态栏只显示短信息:
+
+```text
+5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k
+```
+
+`/status` 显示完整信息卡。
+
+---
+
+## 9. 实现建议
+
+### 9.1 命令定义结构
+
+```ts
+type SlashCommand = {
+ name: string;
+ aliases?: string[];
+ description: string;
+ kind: 'immediate' | 'picker' | 'panel' | 'form';
+ run: () => void | Promise;
+};
+```
+
+### 9.2 命令状态
+
+```ts
+type CliInputMode =
+ | 'composer'
+ | 'slash_menu'
+ | 'picker'
+ | 'approval'
+ | 'form'
+ | 'status_panel'
+ | 'help_panel';
+```
+
+### 9.3 命令过滤
+
+- 前缀匹配优先
+- 模糊匹配其次
+- 当前项默认选第一条
+
+---
+
+## 10. 一句话总结
+
+CLI 的 `/` 命令系统目标是:
+
+> 用一个统一命令入口覆盖 Web 对话页的主要直接操作,同时保持终端交互简单、可键盘驱动、低噪声。
diff --git a/docs/cli_ui_display_spec.md b/docs/cli_ui_display_spec.md
new file mode 100644
index 0000000..a78d970
--- /dev/null
+++ b/docs/cli_ui_display_spec.md
@@ -0,0 +1,523 @@
+# CLI UI 显示规范(草案)
+
+> 目标:把 Web 端已有的信息表达方式,压缩成一个低噪声、可扫描、适合终端的 CLI 交互层。
+> 适用范围:CLI 前端展示层,不定义 Agent Runtime 本身。
+
+## 1. 设计原则
+
+1. **事件流优先**:CLI 只消费结构化事件,不直接消费原始 API 响应。
+2. **极简优先**:正文少、状态清楚、动作明确。
+3. **固定符号**:统一用少量状态符号表达进度与结果。
+4. **完整展示变更**:文件编辑类内容不折叠,直接完整显示。
+5. **思考可见但不冗长**:只保留滚动中的短窗口,不保留完整历史。
+6. **交互独立**:审批、选择、Slash 命令都走独立输入态。
+
+---
+
+## 2. 总体布局
+
+```text
+[历史事件区]
+[当前思考 / 工具 / 子智能体输出]
+[最终结果]
+──
+[输入栏]
+[状态栏]
+```
+
+### 底部状态栏建议
+
+```text
+5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k
+```
+
+可扩展字段:
+
+- 模型名
+- 运行模式(thinking / fast 先不做切换也可保留显示位)
+- Git 开关
+- 自动审批状态
+- 沙箱/直连状态
+- 上下文剩余量
+
+---
+
+## 3. 状态符号
+
+| 状态 | 符号 | 颜色 | 说明 |
+|---|---|---|---|
+| 运行中 | `◦` / `•` 交替 | 黄/灰 | 闪烁 |
+| 完成 | `•` | 绿 | 静态 |
+| 失败 | `•` | 红 | 静态 |
+| 用户输入 | `›` | 白/青 | 静态 |
+| 审批中 | `◦` | 黄 | 慢闪 |
+| 已取消 | `•` | 灰 | 静态 |
+
+---
+
+## 4. 思考块显示
+
+思考块只保留一种样式:**5 行滚动窗口**。
+
+### 4.1 运行中
+
+```text
+◦ 思考中
+ └ 第一行
+ 第二行
+ 第三行
+ 第四行
+ 第五行
+```
+
+当加入第 6 行时,自动滚动:
+
+```text
+◦ 思考中
+ └ 第二行
+ 第三行
+ 第四行
+ 第五行
+ 第六行
+```
+
+### 4.2 完成后
+
+```text
+• 思考完成 4.2s
+```
+
+不做折叠展开,不保留完整思考历史。
+
+### 4.3 规则
+
+- 只显示最新 5 行
+- 只显示当前思考内容
+- 完成后只留完成状态
+- 不显示“思考全文”
+
+---
+
+## 5. 子智能体前台显示
+
+子智能体前台展示方式与思考块一致,也是 5 行滚动窗口。
+
+```text
+◦ 子智能体 review-agent...
+ └ 运行 ls -la && pwd
+ 搜索 python 新版本
+ 第三行
+ 第四行
+ 第五行
+```
+
+完成后:
+
+```text
+• 子智能体完成 8.1s
+```
+
+### 规则
+
+- 子智能体只显示前台进度,不显示全部历史
+- 只保留最新 5 行
+- 完成态只显示耗时和结果摘要
+
+---
+
+## 6. 工具块通用格式
+
+统一使用:
+
+```text
+• 动作摘要
+ └ 结果摘要
+```
+
+### 6.1 运行中
+
+```text
+◦ 运行 npm run build
+ └ 正在执行...
+```
+
+### 6.2 完成
+
+```text
+• 运行 npm run build
+ └ 成功,用时 8.2s
+```
+
+### 6.3 失败
+
+```text
+• 运行 npm run build
+ └ 失败,exit code 2
+```
+
+---
+
+## 7. 工具显示规范
+
+下面定义 `└` 后面的内容包含哪些字段、如何显示。
+
+### 7.1 运行命令类
+
+#### 适用工具
+
+- `run_command`
+- `terminal_input`
+- `terminal_session`
+- `terminal_snapshot`
+
+#### 显示字段
+
+- `command` / `input`
+- `timeout`
+- `exit_code`
+- `stdout` / `output`
+- `stderr`(若可分离)
+
+#### 显示规则
+
+```text
+• 运行 printf '' > test.txt && ls -l test.txt
+ └ -rw-r--r-- 1 jojo staff 0 May 14 14:48 test.txt
+```
+
+长输出:
+
+```text
+• 运行 ls -la && pwd
+ └ 第 1 行内容
+ 第 2 行内容
+ … +6 lines
+ 第 9 行内容
+ 第 10 行内容
+```
+
+#### 约束
+
+- 第一行优先显示结果中最有信息量的一行
+- 长文本可局部截断,但不要丢掉关键错误
+- 如果有 exit code,必须显示
+
+---
+
+### 7.2 文件编辑类
+
+#### 适用工具
+
+- `write_file`
+- `edit_file`
+
+#### 原则
+
+**必须完整显示,不折叠。**
+
+因为:
+
+- 这是文件内容本身,不是工具摘要
+- 单次替换通常不会太大
+- 终端里保留完整 + / - 最有价值
+
+#### 7.2.1 Write
+
+显示字段:
+
+- `path`
+- `mode`(append / overwrite)
+- `status`
+- `content`
+
+显示模板:
+
+```text
+• Write test.txt (+3 -0)
+ 1 +这是第一行测试内容。
+ 2 +这是第二行测试内容。
+ 3 +这是第三行测试内容。
+```
+
+#### 7.2.2 Edit
+
+显示字段:
+
+- `path`
+- `replace_all`
+- `found`
+- `replacements`
+- 每个 replacement 的 `old` / `new`
+
+显示模板:
+
+```text
+• Edited main.py (+2 -2)
+ 14 -old line 1
+ 15 -old line 2
+ 14 +new line 1
+ 15 +new line 2
+```
+
+#### 7.2.3 规则
+
+- 不折叠
+- 不显示上下文行
+- 只显示参数里已有的变更信息
+- 若存在多段替换,按替换顺序逐段输出
+
+---
+
+### 7.3 阅读类
+
+#### 适用工具
+
+- `read_file`
+- `read_skill`
+
+#### 显示字段
+
+- `path`
+- `status`
+- `size`
+- `read_type`
+- `content`
+- `search` / `extract` 子模式信息
+
+#### 显示模板
+
+普通读取:
+
+```text
+• 阅读 test.txt
+ └ 读取 3 行
+```
+
+提取模式:
+
+```text
+• 阅读 test.txt
+ └ 提取第 12-28 行
+```
+
+搜索模式(如果未来恢复):
+
+```text
+• 搜索 handle_task
+ └ 找到 12 处,位于 5 个文件
+```
+
+#### 规则
+
+- 正文尽量压缩成“读取了多少 / 提取了什么 / 找到了什么”
+- 需要看全文时再走展开命令
+
+---
+
+### 7.4 搜索类
+
+当前 CLI v1 **不做搜索工具**。
+
+如果未来恢复,显示方式参考阅读类:
+
+```text
+• 搜索 "xxx"
+ └ 找到 N 处,位于 M 个文件
+```
+
+---
+
+### 7.5 文件创建 / 删除 / 重命名
+
+当前 CLI v1 **不做这些工具块**,可直接忽略。
+
+---
+
+### 7.6 子智能体 / 后台任务类
+
+#### 显示字段
+
+- `name`
+- `status`
+- `latest_progress_lines`
+- `elapsed`
+
+#### 显示模板
+
+```text
+◦ 子智能体 review-agent...
+ └ 运行 ls -la && pwd
+ 搜索 python 新版本
+ 第三行
+ 第四行
+ 第五行
+```
+
+完成后:
+
+```text
+• 子智能体完成 8.1s
+```
+
+#### 规则
+
+- 只显示最新 5 行
+- 运行中用 `◦`
+- 完成后用 `•`
+
+---
+
+### 7.7 审批 / 交互类
+
+#### 适用场景
+
+- 权限审批
+- 需要用户确认的危险操作
+- 自定义选择
+- Slash 命令补全
+
+#### 显示方式
+
+使用**输入栏扩展 + 选项列表**,不是普通消息流。
+
+示例:
+
+```text
+┌────────────────────────────────────┐
+│ 需要批准:运行 rm -rf dist │
+│ 原因:删除构建目录 │
+│ │
+│ › 1. Allow once │
+│ 2. Always allow │
+│ 3. Deny │
+└────────────────────────────────────┘
+```
+
+#### 规则
+
+- 上下键切换
+- 当前行高亮
+- Enter 确认
+- Esc 或拒绝关闭
+
+---
+
+## 8. Slash 命令规范
+
+所有操作都通过 `/` 输入。
+
+### 8.1 触发方式
+
+输入 `/` 或 `/a` 后,打开命令建议列表。
+
+### 8.2 显示方式
+
+```text
+› /approve 简要说明
+ /allow 简要说明
+ /assist 简要说明
+```
+
+### 8.3 规则
+
+- 支持模糊过滤
+- 上下键选择
+- 当前项变色
+- Enter 执行
+
+---
+
+## 9. 最终输出区
+
+任务完成后,使用固定分隔结构:
+
+```text
+──
+
+• 已创建 test.txt 文件。
+
+ 1. 修改文件列表:test.txt
+ 2. 核心变更点:在 /Users/jojo/Desktop/test 下创建了一个空文本文件
+ 3. 验证结果:已执行 ls -l test.txt,文件存在。
+
+──
+```
+
+### 最终结果字段
+
+建议统一输出:
+
+1. **修改文件列表**
+2. **核心变更点**
+3. **验证结果**
+
+如果有失败:
+
+- 失败原因
+- 已做的尝试
+- 下一步建议
+
+---
+
+## 10. 信息优先级
+
+从高到低:
+
+1. 当前交互态(审批 / 命令选择)
+2. 当前思考 / 当前子智能体进度
+3. 当前工具输出
+4. 最终结论
+5. 低优先级元信息
+
+---
+
+## 11. 参考的 Web 端表现
+
+Web 端已经存在的对应参考:
+
+- `static/src/components/chat/actions/ThinkingAction.vue`
+ - 思考块
+ - 运行中 / 完成两态
+ - 可折叠
+- `static/src/components/chat/actions/ToolAction.vue`
+ - 工具块
+ - `renderEnhancedToolResult()`
+ - 文件编辑 / 读取 / 命令 / 图片等不同分支
+- `static/src/components/chat/StackedBlocks.vue`
+ - 思考 + 工具的堆叠展示
+- `static/src/components/chat/MinimalBlocks.vue`
+ - 摘要组展示
+ - 最终只保留当前步骤预览
+- `static/src/components/panels/ToolApprovalPanel.vue`
+ - 审批面板
+ - 编辑 diff、写入内容、命令预览
+
+CLI 的目标不是复制这些视觉细节,而是抽取其中最有信息价值的字段。
+
+---
+
+## 12. 推荐的 CLI 默认策略
+
+### 默认开启
+
+- 5 行思考滚动
+- 5 行子智能体滚动
+- 文件编辑完整展示
+- 命令输出摘要展示
+- Slash 命令补全
+- 审批选择弹层
+
+### 默认关闭
+
+- 完整思考历史
+- 文件创建 / 删除 / 重命名显示
+- 搜索工具
+- 折叠展开复杂交互
+
+---
+
+## 13. 一句话总结
+
+CLI 的目标是:
+
+> 用最少的符号,表达最多的有效信息;用固定的状态和结构,让用户能一眼看懂 Agent 在做什么。
diff --git a/package.json b/package.json
index c17df04..acd269d 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,10 @@
"build": "tsc --noEmit && vite build",
"lint": "eslint \"static/src/**/*.{ts,tsx,js,vue}\" --max-warnings=0",
"format": "prettier --write \"static/src/**/*.{ts,tsx,js,vue,css}\"",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "cli": "npm --prefix cli run dev",
+ "cli:build": "npm --prefix cli run build",
+ "cli:typecheck": "npm --prefix cli run typecheck"
},
"dependencies": {
"@types/html2canvas": "^0.5.35",
diff --git a/server/chat.py b/server/chat.py
index 4bc0157..83abcf6 100644
--- a/server/chat.py
+++ b/server/chat.py
@@ -15,6 +15,7 @@ import secrets
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
from modules.personalization_manager import (
load_personalization_config,
+ resolve_context_compression_settings,
save_personalization_config,
THINKING_INTERVAL_MIN,
THINKING_INTERVAL_MAX,
@@ -28,6 +29,7 @@ from modules.upload_security import UploadSecurityError
from modules.host_sandbox_policy import load_policy, save_policy
from modules.user_manager import UserWorkspace
from core.web_terminal import WebTerminal
+from config.model_profiles import get_model_context_window
from .auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record, get_current_username
from .context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
@@ -244,11 +246,23 @@ def get_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace
)
data_out = dict(data)
data_out["enabled_skills"] = enabled_skills
+ compression_settings = resolve_context_compression_settings(data_out)
+ try:
+ model_window = get_model_context_window(getattr(terminal, "model_key", None) or "kimi-k2.5")
+ except Exception:
+ model_window = None
return jsonify({
"success": True,
"data": data_out,
"tool_categories": terminal.get_tool_settings_snapshot(),
"skills_catalog": skills_catalog,
+ "context_compression_settings": {
+ **compression_settings,
+ "context_window_tokens": min(
+ int(compression_settings.get("deep_trigger_tokens") or 0),
+ int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
+ ),
+ },
"thinking_interval_default": THINKING_FAST_INTERVAL,
"thinking_interval_range": {
"min": THINKING_INTERVAL_MIN,
@@ -314,11 +328,23 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp
debug_log(f"应用个性化偏好失败: {exc}")
config_out = dict(config)
config_out["enabled_skills"] = enabled_skills
+ compression_settings = resolve_context_compression_settings(config_out)
+ try:
+ model_window = get_model_context_window(getattr(terminal, "model_key", None) or "kimi-k2.5")
+ except Exception:
+ model_window = None
return jsonify({
"success": True,
"data": config_out,
"tool_categories": terminal.get_tool_settings_snapshot(),
"skills_catalog": skills_catalog,
+ "context_compression_settings": {
+ **compression_settings,
+ "context_window_tokens": min(
+ int(compression_settings.get("deep_trigger_tokens") or 0),
+ int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
+ ),
+ },
"thinking_interval_default": THINKING_FAST_INTERVAL,
"thinking_interval_range": {
"min": THINKING_INTERVAL_MIN,