Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AGENTS.md

## Project

NodeShell is a Wails v2 desktop SSH client: Go backend, React/TypeScript frontend.
`main.go` has two entries: the GUI (default) and a native MCP stdio server (`--mcp`). `internal/app/app.go` is the Wails binding facade; domain logic lives in the other `internal/*` packages.

Toolchain: Go 1.26, Node 22 (CI), Wails v2.13. The Wails CLI is not an npm dependency.

## Setup and build order

- `npm ci && go mod download`
- `npm run build:wails` emits `frontend/dist/`, which `main.go` embeds with `//go:embed all:frontend/dist`. On a clean checkout, `go test .` and `go vet ./...` fail until this exists; `wails build` creates it automatically. `internal/*` packages can be tested without it.
- `npm run dev:wails` only starts Vite on port 5173; use `wails dev` for the desktop app.
- Build with `wails build -skipbindings`. Install the CLI with `go install github.com/wailsapp/wails/v2/cmd/wails@v2.13.0`.
- Ubuntu 24.04/CI has only webkit2gtk 4.1: add `-tags webkit2_41` (e.g. `-platform linux/amd64 -tags webkit2_41`). Windows release/MCP builds use `-windowsconsole` so `--mcp` stdio works under redirected handles; do not drop it.

## Verify

- Full check: `npm run typecheck`, `npm test`, `npm run lint`, `npm run build:wails`, `go vet ./...`, then Go tests.
- Single frontend test: `npx vitest run tests/ui/settings.test.tsx`.
- Single Go test: `go test ./internal/mcpcli -run TestName -count=1`. Root-package tests use `go test . -run TestName -count=1` and require `frontend/dist`.
- With `node_modules` present, filter it from Go package lists like CI: `go test $(go list ./... | grep -v /node_modules/)`. CI runs one package at a time with `-timeout 90s`; keep test waits bounded.
- CI keeps `-race` off on every platform (matrix entries set `go-test-race: ''`). Local race runs have a history of hanging on Linux/macOS.

## Architecture and API contract

- Root-level `main.go`, `console_*.go`, and `windowsize.go` are the desktop entry package: `//go:embed frontend/dist` is root-relative and the Wails `Bind` call lives there. The App facade and its tests are in `internal/app`; keep Go tests beside their package.
- Keep domain packages (`internal/*` except `internal/app`) free of Wails imports. Services receive dependencies and an `EventSink`; `internal/app/wails_sink.go` adapts events to the runtime. This is what keeps them headless-testable.
- Add a bound API in three places: an exported `<Group><Method>` method on `*App` in `internal/app/app.go`, a typed call in `src/renderer/src/api/adapter.ts`, and shared DTOs/event constants in `src/shared/types.ts`. The frontend calls `window.go.app.App` directly.
- App lifecycle is driven by package functions `app.Start`/`app.Stop`; do not turn them into methods, or Wails binds them as frontend-callable APIs.
- `wailsjs/` is generated and gitignored; do not edit or commit it. `frontend/dist/` is build output.
- The frontend intentionally keeps the old `window.api` / `ElectronApi` names even though the runtime is Wails; do not rename the contract.
- Vite root is `src/renderer` and output is `frontend/dist`. Vitest only includes `tests/**/*.test.{ts,tsx}`.
- Go errors crossing IPC implement `ErrorCode() string` using codes from `internal/apperror`; the frontend whitelist is `SSH_CODES` in `src/shared/ipc-error.ts`. Keep both lists in sync, or codes collapse to `UNKNOWN`.
- MCP exposes exactly 10 tools. The count is asserted in `internal/mcpcli/runmcp_test.go` and both `scripts/mcp-smoke.*`; update all of them (and the README) when changing tools. `--mcp` must write protocol only to stdout and nothing to stderr during the smoke test.
- The product is Wails-only: `tests/runtime-dependencies.test.ts` rejects Electron/`ssh2`/MCP SDK dependencies and generic `dev`/`build` scripts. Do not reintroduce them.

## Security and data invariants

- SSH host keys are verified before authentication; do not bypass `internal/knownhosts`.
- Passwords and agent API keys live in the OS keyring, not JSON. Never write secrets into `hosts.json` or `settings.json`.
- Local upload/download, private-key, and MCP file paths must pass `internal/localpathguard` and stay under the user home. Remote SFTP paths use POSIX normalization. Do not add permissive path handling.
- Data lives in `os.UserConfigDir()/nodeshell`, intentionally compatible with the Electron 2.0.0 layout. The old `credentials.json` must remain untouched.

## Testing conventions

- Vitest defaults to the `node` environment; DOM tests need the `// @vitest-environment jsdom` docblock.
- Use `tests/ui/helpers.tsx` (`installFakeApi`, `renderWithI18n`, event emitters) instead of hardcoding translations or mocking component behavior.
- Go integration tests use the real in-process SSH server in `internal/sshtest`; tests must not depend on the public network or an external SSH daemon. Root-package tests use `testApp` from `app_test.go` with a fake keyring.
- UI tests subscribe through the fake `window.api`; event names must match the Go constants.

## CI and release

- `.github/workflows/ci.yml` is the source of truth for packaging flags and platform dependencies; the README is a summary.
- Windows, macOS, and Linux jobs build with Wails and run `scripts/mcp-smoke.*`. Packaging must run on native runners; do not assume local cross-platform Wails builds work.
- `tests/ci-workflow-structure.test.ts` asserts CI structure and version handling; update it when intentionally changing the workflow.
- `package.json` is the version source for CI (`APP_VERSION`). Keep `wails.json` `productVersion` and the README version in sync. Tags matching `v*` produce a draft GitHub release.
- Secrets are mapped to job-level env vars; never reference `secrets.*` in a step-level `if:` (GitHub rejects the workflow).

## Docs

`README.md` is the user-facing document (Chinese). `docs/design/wails-rebuild.md` and `docs/reports/` are historical design/acceptance notes, grouped by domain; they are not current authority, so verify claims against code and CI. Start at `docs/README.md` for the index. Put new design notes under `docs/design/` and reports under `docs/reports/<domain>/`, not at the repository root.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ CI 会在 Windows、macOS 和 Linux 原生 runner 上执行类型检查、前端

```text
.
├── app.go # Wails 应用 facade 与领域服务绑定
├── main.go # GUI / --mcp 双入口
├── main.go # GUI / --mcp 双入口与 Wails 窗口启动
├── internal/
│ ├── app/ # Wails 应用 facade 与领域服务绑定
│ ├── agent/ # 内置 Agent 服务
│ ├── credentials/ # 凭据与 Keyring 抽象
│ ├── hosts/ # 主机配置存储
Expand All @@ -310,7 +310,9 @@ CI 会在 Windows、macOS 和 Linux 原生 runner 上执行类型检查、前端
│ └── ...
├── scripts/ # MCP smoke test 和平台打包脚本
├── tests/ # 前端和适配层测试
├── docs/ # 设计与重构文档
├── docs/
│ ├── design/ # 重构设计文档
│ └── reports/ # 历史实施与验收报告(按功能域分类)
└── wails.json # Wails 构建配置
```

Expand Down
21 changes: 21 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 文档索引

本目录保存 NodeShell 的设计文档与历史实施报告。当前行为以根目录 `README.md`、代码和 `.github/workflows/ci.yml` 为准。

## 设计文档

- [Wails 重构设计](design/wails-rebuild.md)

## 历史报告

报告按功能域归档,文件名保持不变;报告之间的相互链接已改为相对路径。

| 目录 | 内容 |
| --- | --- |
| [reports/agent](reports/agent/) | Agent 供应商、模型与工具调用 |
| [reports/mcp](reports/mcp/) | MCP 权限与外部客户端集成 |
| [reports/terminal](reports/terminal/) | 多会话终端与 xterm.js 优化 |
| [reports/transfer](reports/transfer/) | SFTP、文件传输任务中心与拖放上传 |
| [reports/ui](reports/ui/) | 侧栏、端口映射、设置与主机管理界面 |

这些报告记录当时的方案、实施过程和验收结果,不代表当前实现;引用前请核对代码。
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# NodeShell Agent 供应商模型 ID 编辑器美化:实施与验收报告

- **报告日期**:2026-08-26
- **关联计划**:[Agent供应商模型ID编辑器美化_设计与实施计划.md](file:///e:/Projects/NodeShell/reports/Agent%E4%BE%9B%E5%BA%94%E5%95%86%E6%A8%A1%E5%9E%8BID%E7%BC%96%E8%BE%91%E5%99%A8%E7%BE%8E%E5%8C%96_%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E6%96%BD%E8%AE%A1%E5%88%92.md)
- **关联计划**:[Agent供应商模型ID编辑器美化_设计与实施计划.md](Agent供应商模型ID编辑器美化_设计与实施计划.md)
- **实施状态**:编辑器核心功能验收通过;整体验收为**有条件通过**(全量测试存在 2 项非编辑器失败,视觉实机项待执行)
- **影响范围**:设置页(SettingsModal)-> Agent -> 供应商配置 -> 模型 ID 录入与编辑区域

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# NodeShell Agent 单次 Prompt 工具调用不限:修复与验收报告

- **报告日期**:2026-08-26
- **关联计划**:[Agent单次Prompt工具调用不限_实现说明与修改计划.md](file:///e:/Projects/NodeShell/reports/Agent%E5%8D%95%E6%AC%A1Prompt%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E4%B8%8D%E9%99%90_%E5%AE%9E%E7%8E%B0%E8%AF%B4%E6%98%8E%E4%B8%8E%E4%BF%AE%E6%94%B9%E8%AE%A1%E5%88%92.md)
- **关联计划**:[Agent单次Prompt工具调用不限_实现说明与修改计划.md](Agent单次Prompt工具调用不限_实现说明与修改计划.md)
- **修复状态**:已完成并通过全套前端与后端自动化测试验证(100% PASS)
- **影响组件**:后端 Agent 服务循环(`internal/agent`)

Expand All @@ -24,7 +24,7 @@ Agent stopped after 8 steps without finishing

## 2. 修复方案与代码变更

### 2.1 取消生产环境 `DefaultMaxTurns = 8` 硬编码限制 ([`internal/agent/agent.go`](file:///e:/Projects/NodeShell/internal/agent/agent.go))
### 2.1 取消生产环境 `DefaultMaxTurns = 8` 硬编码限制 ([`internal/agent/agent.go`](../../../internal/agent/agent.go))

1. **移除常量**:彻底删除了 `DefaultMaxTurns = 8`。
2. **重定义 `MaxTurns` 语义**:
Expand All @@ -50,7 +50,7 @@ Agent stopped after 8 steps without finishing

---

### 2.2 新增与调整后端测试 ([`internal/agent/agent_test.go`](file:///e:/Projects/NodeShell/internal/agent/agent_test.go))
### 2.2 新增与调整后端测试 ([`internal/agent/agent_test.go`](../../../internal/agent/agent_test.go))

1. **新增长任务测试 `TestDefaultRunCanExceedEightToolTurnsAndFinish`**:
- 构造连续 12 轮工具调用 + 第 13 轮返回最终文本的 SSE 序列;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ GUI 与 MCP 是两个进程。MCP 不读取 GUI 内存中的 allow-session;loc
| `src/renderer/src/components/SettingsModal.tsx` | MCP 页独立选项;GUI 权限文案拆开 |
| `src/renderer/src/i18n/locales/{en,zh}.json` | 中英文案 |
| `tests/ui/settings.test.tsx` | 默认 external、切换 local、GUI 权限文案 |
| `reports/MCP权限外置化_实施与验收报告.md` | 本报告 |
| `docs/reports/mcp/MCP权限外置化_实施与验收报告.md` | 本报告 |

未改 P2 终端相关未提交文件。

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# xterm.js P0 稳定性与性能优化:实施与验收报告

- **实施日期**:2026-08-30
- **关联计划**:[xterm.js_P0优化_编程Agent实施计划.md](file:///E:/Projects/NodeShell/reports/xterm.js_P0优化_编程Agent实施计划.md)
- **关联计划**:[xterm.js_P0优化_编程Agent实施计划.md](xterm.js_P0优化_编程Agent实施计划.md)
- **状态**:**正式验收全部通过 (PASS)**

---
Expand All @@ -11,7 +11,7 @@
针对最新代码评审提出的晚连接路径 worker 泄漏防范、有界队列背压控制与文档/测试规范,完成了全面闭环:

### 1.1 晚连接路径 Batcher Worker 泄漏修复 (P1 修复)
- 在 [`internal/sessions/sessions.go`](file:///E:/Projects/NodeShell/internal/sessions/sessions.go) 的 `Connect()` 方法中,当在连接握手完成后检测到 `m.closing == true`(即应用已执行 `DisposeAll` 正在退出)时:
- 在 [`internal/sessions/sessions.go`](../../../internal/sessions/sessions.go) 的 `Connect()` 方法中,当在连接握手完成后检测到 `m.closing == true`(即应用已执行 `DisposeAll` 正在退出)时:
```go
m.mu.Lock()
if m.closing {
Expand All @@ -26,7 +26,7 @@
- **测试覆盖**:在 `internal/sessions/sessions_test.go` 中新增 `TestConnectDuringDisposeAllWorkerExits`,验证在 DisposeAll 竞态下返回 Cancelled 错误且 batcher worker 正常退出。

### 1.2 有界背压队列与内存上限控制 (P1/P2 优化)
- 在 [`internal/sessions/batcher.go`](file:///E:/Projects/NodeShell/internal/sessions/batcher.go) 中引入常量 `maxQueueBatches = 64`(单会话最大缓冲约 3MB 批次):
- 在 [`internal/sessions/batcher.go`](../../../internal/sessions/batcher.go) 中引入常量 `maxQueueBatches = 64`(单会话最大缓冲约 3MB 批次):
- 当高速输出(如 `cat` 大文件)导致 `queue` 达到容量上限时,`Add()` 阻塞在 `b.cond.Wait()` 上;
- 从而自然暂停 SSH 输出泵(`conn.Stdout().Read()`),通过 TCP 窗口滑动反压向远程服务端限速,彻底防止前端慢消费场景下的内存无界膨胀;
- 当 worker 消费批次或执行 `Close()` / `Discard()` 时广播 `b.cond.Broadcast()`,解除阻塞;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ go test -bench=BenchmarkBatcherSustainedHighRate -benchtime=5m ./internal/sessio
| `tests/ui/terminal-stress.ts` | 新增 | 前端压力语料 |
| `tests/ui/terminal-view-resize.test.tsx` | 修改 | 大块分帧、WebGL 不重试、后台写入、8 会话、指标 |
| `tests/ui/session-terminal-persistence.test.tsx` | 修改 | 重连替换实例、重复关闭幂等 |
| `reports/xterm.js_P2优化_性能基线.md` | 新增 | 基准数字 |
| `reports/xterm.js_P2优化_实施与验收报告.md` | 新增 | 本报告 |
| `docs/reports/terminal/xterm.js_P2优化_性能基线.md` | 新增 | 基准数字 |
| `docs/reports/terminal/xterm.js_P2优化_实施与验收报告.md` | 新增 | 本报告 |

未修改 SSH/PTY 类型、`sessions.write` / `sessions.resize` / `session:data` 契约、SFTP、Agent、端口映射。

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# NodeShell 多会话切换终端清屏:修复与验收报告

- **报告日期**:2026-08-26
- **关联问题**:多会话切换导致终端清屏与状态丢失([多会话切换终端清屏_bug定位与修复建议.md](file:///e:/Projects/NodeShell/reports/%E5%A4%9A%E4%BC%9A%E8%AF%9D%E5%88%87%E6%8D%A2%E7%BB%88%E7%AB%AF%E6%B8%85%E5%B1%8F_bug%E5%AE%9A%E4%BD%8D%E4%B8%8E%E4%BF%AE%E5%A4%8D%E5%BB%BA%E8%AE%AE.md))
- **关联问题**:多会话切换导致终端清屏与状态丢失([多会话切换终端清屏_bug定位与修复建议.md](多会话切换终端清屏_bug定位与修复建议.md))
- **修复状态**:已完成并通过全套前端与后端自动化测试验证(100% PASS)
- **影响组件**:前端 React 会话标签与终端视图渲染层、SSH 客户端测试套件

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

- **报告日期**:2026-08-26
- **关联计划**:
- [文件传输任务中心_完整优化实施计划.md](file:///e:/Projects/NodeShell/reports/文件传输任务中心_完整优化实施计划.md)
- [文件拖放上传与传输中心不可用_原因定位与修复计划.md](file:///e:/Projects/NodeShell/reports/文件拖放上传与传输中心不可用_原因定位与修复计划.md)
- [文件传输任务中心_验收问题修复报告.md](file:///e:/Projects/NodeShell/reports/文件传输任务中心_验收问题修复报告.md)
- [文件传输任务中心_完整优化实施计划.md](文件传输任务中心_完整优化实施计划.md)
- [文件拖放上传与传输中心不可用_原因定位与修复计划.md](文件拖放上传与传输中心不可用_原因定位与修复计划.md)
- [文件传输任务中心_验收问题修复报告.md](文件传输任务中心_验收问题修复报告.md)
- **实施状态**:**全部通过 (PASS)**
- **影响范围**:
- 后端:`internal/sftpservice`(`TransferManager` 调度状态机、事件顺序保障、`UploadWithContext`/`DownloadWithContext` 提交前中断检测与 CWD 解耦、`sync.WaitGroup` 优雅关闭)、`internal/sessions`(会话元数据解析)、`app.go`(Wails 绑定、绝对远端路径解析与生命周期)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@

### 3.1 问题位置

- [internal/sftpservice/transfer_manager.go:238](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:238)
- [internal/sftpservice/transfer_manager.go:241](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:241)
- [internal/sftpservice/transfer_manager.go:462](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:462)
- [src/renderer/src/hooks/useTransferTasks.ts:73](E:/Projects/NodeShell/src/renderer/src/hooks/useTransferTasks.ts:73)
- [internal/sftpservice/transfer_manager.go:238](../../../internal/sftpservice/transfer_manager.go)
- [internal/sftpservice/transfer_manager.go:241](../../../internal/sftpservice/transfer_manager.go)
- [internal/sftpservice/transfer_manager.go:462](../../../internal/sftpservice/transfer_manager.go)
- [src/renderer/src/hooks/useTransferTasks.ts:73](../../../src/renderer/src/hooks/useTransferTasks.ts)

### 3.2 根因

Expand Down Expand Up @@ -121,9 +121,9 @@ running -> succeeded -> queued

### 4.1 问题位置

- [src/renderer/src/components/SftpPanel.tsx:262-269](E:/Projects/NodeShell/src/renderer/src/components/SftpPanel.tsx:262)
- [internal/sftpservice/transfer_manager.go:249-288](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:249)
- [internal/sftpservice/sftpservice.go:1129](E:/Projects/NodeShell/internal/sftpservice/sftpservice.go:1129)
- [src/renderer/src/components/SftpPanel.tsx:262-269](../../../src/renderer/src/components/SftpPanel.tsx)
- [internal/sftpservice/transfer_manager.go:249-288](../../../internal/sftpservice/transfer_manager.go)
- [internal/sftpservice/sftpservice.go:1129](../../../internal/sftpservice/sftpservice.go)

### 4.2 根因

Expand Down Expand Up @@ -188,10 +188,10 @@ type Task struct {

### 5.1 问题位置

- [src/renderer/src/components/TransferCenter.tsx:277-293](E:/Projects/NodeShell/src/renderer/src/components/TransferCenter.tsx:277)
- [internal/sftpservice/sftpservice.go:1096-1104](E:/Projects/NodeShell/internal/sftpservice/sftpservice.go:1096)
- [internal/sftpservice/sftpservice.go:1210-1222](E:/Projects/NodeShell/internal/sftpservice/sftpservice.go:1210)
- [internal/sftpservice/transfer_manager.go:503-513](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:503)
- [src/renderer/src/components/TransferCenter.tsx:277-293](../../../src/renderer/src/components/TransferCenter.tsx)
- [internal/sftpservice/sftpservice.go:1096-1104](../../../internal/sftpservice/sftpservice.go)
- [internal/sftpservice/sftpservice.go:1210-1222](../../../internal/sftpservice/sftpservice.go)
- [internal/sftpservice/transfer_manager.go:503-513](../../../internal/sftpservice/transfer_manager.go)

### 5.2 根因

Expand Down Expand Up @@ -269,8 +269,8 @@ if runErr == nil {

### 6.1 问题位置

- [internal/sftpservice/transfer_manager.go:399-426](E:/Projects/NodeShell/internal/sftpservice/transfer_manager.go:399)
- [app.go:377-403](E:/Projects/NodeShell/app.go:377)
- [internal/sftpservice/transfer_manager.go:399-426](../../../internal/sftpservice/transfer_manager.go)
- [app.go:377-403](../../../internal/app/app.go)

### 6.2 根因

Expand Down
Loading
Loading