Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6cf5833
docs: add TypeScript+Bun migration plan
hetaoBackend Jun 12, 2026
733ef9d
feat(backend): scaffold Bun+TS workspace with core types/util/log
hetaoBackend Jun 12, 2026
7378f35
feat(backend): port MessageBus/Channel/UIChannel to TypeScript with t…
hetaoBackend Jun 12, 2026
7dedf73
feat(electron): convert app to TypeScript (main/preload/renderer, bun…
hetaoBackend Jun 12, 2026
62b1adc
feat(backend): port TaskDB to bun:sqlite with full test suite
hetaoBackend Jun 12, 2026
b489997
build(electron): replace Vite/npm toolchain with Bun build pipeline
hetaoBackend Jun 12, 2026
3d67c0d
build: switch Makefile and CI to Bun toolchain
hetaoBackend Jun 12, 2026
b3af3d7
style(backend): apply prettier formatting
hetaoBackend Jun 12, 2026
80562a1
docs: update AGENTS.md for TypeScript+Bun architecture
hetaoBackend Jun 12, 2026
68bea63
feat(skills): port agentforge skill CLI to TypeScript/Bun
hetaoBackend Jun 12, 2026
5cf99b5
docs: update READMEs for TypeScript+Bun toolchain
hetaoBackend Jun 12, 2026
e76c098
feat(backend): port channel agent/dir utils to TypeScript
hetaoBackend Jun 12, 2026
535f64e
docs: update install troubleshooting and keep_running.sh for Bun
hetaoBackend Jun 12, 2026
651aafc
wip(backend): executor/skills/scheduler port and weixin bridge conver…
hetaoBackend Jun 12, 2026
95cfd0d
feat(backend): port Slack channel to TypeScript with tests
hetaoBackend Jun 12, 2026
14a6380
feat(backend): port Telegram channel to TypeScript with tests
hetaoBackend Jun 12, 2026
1b99b12
test(backend): port executor/scheduler/skills test suites (164 tests)
hetaoBackend Jun 12, 2026
518871c
Complete Bun TypeScript migration
hetaoBackend Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,24 @@ jobs:
backend-quality:
name: Backend Quality
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v5
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
bun-version: latest

- name: Install dependencies
run: uv sync --dev
run: bun install --frozen-lockfile

- name: Run backend quality gate
run: make check
run: bun run check

frontend-quality:
name: Frontend Quality
Expand All @@ -47,24 +45,25 @@ jobs:
- name: Check out repository
uses: actions/checkout@v5

- name: Set up Node.js
uses: actions/setup-node@v5
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
node-version: "22"
cache: npm
cache-dependency-path: taskboard-electron/package-lock.json
bun-version: latest

- name: Install dependencies
run: npm ci
run: bun install --frozen-lockfile

- name: Typecheck (tsc)
run: bun run typecheck

- name: Lint (ESLint)
run: npm run lint
run: bun run lint

- name: Format check (Prettier)
run: npm run format:check
run: bun run format:check

- name: Unit tests
run: npm test
run: bun run test

- name: Renderer build check
run: npm run build:check
- name: Renderer/main build check (Bun.build)
run: bun run build:check
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Python-generated files
__pycache__/
*.py[oc]
.pytest_cache/
.ruff_cache/
.python-version
build/
dist/
wheels/
*.egg-info

# JS/Bun dependencies
node_modules/

# Virtual environments
.venv
.coverage
Expand Down
1 change: 0 additions & 1 deletion .python-version

This file was deleted.

104 changes: 55 additions & 49 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,89 +1,95 @@
## Project Overview

AgentForge is a macOS desktop app (Electron + Python) that provides a kanban-style task board for orchestrating AI coding agents (**Claude Code or OpenAI Codex CLI**). The Python backend manages task scheduling, execution, and persistence; the React frontend renders the board and streams live output. Tasks can also be created/monitored from chat channels (Telegram / Slack / Feishu / WeChat), and a **Skill Library** distills recurring task patterns into reusable Claude Code skills.
AgentForge is a macOS desktop app (Electron + Bun/TypeScript) that provides a kanban-style task board for orchestrating AI coding agents (**Claude Code or OpenAI Codex CLI**). The TypeScript backend (running on Bun) manages task scheduling, execution, and persistence; the React frontend renders the board and streams live output. Tasks can also be created/monitored from chat channels (Telegram / Slack / Feishu / WeChat), and a **Skill Library** distills recurring task patterns into reusable Claude Code skills.

The entire project is TypeScript-only and uses **Bun** to build, run, test, and compile (no Python, no Node toolchain — Electron's bundled Node runs the packaged app, but all tooling is Bun).

## Commands

### Python Backend
### Backend (`backend/`)
```bash
# Run backend directly (dev)
uv run taskboard.py

# Run with explicit port (default 9712)
uv run taskboard.py # listens on 127.0.0.1:9712 (loopback only — not network-exposed)
# Run backend directly (dev) — listens on 127.0.0.1:9712 (loopback only)
cd backend && bun taskboard.ts

# Verify health
curl http://127.0.0.1:9712/api/health

# Build PyInstaller binary (must run from project root)
uv run pyinstaller --onefile --name taskboard \
--distpath taskboard-electron/resources \
--hidden-import croniter --hidden-import dateutil --hidden-import pytz \
taskboard.py
# Compile single-file binary for packaging (replaces PyInstaller)
cd taskboard-electron && bun scripts/build-backend.ts
# (equivalent: cd backend && bun run compile)
```

### Electron App
```bash
# Dev: starts Electron + Vite dev server (spawns uv run taskboard.py automatically)
cd taskboard-electron && npm start
# Dev: Bun-builds main/preload/renderer, launches Electron with watch + reload
# (spawns `bun backend/taskboard.ts` automatically)
cd taskboard-electron && bun run start

# Build distributable DMG (arm64)
cd taskboard-electron && npm run make
cd taskboard-electron && bun run make
# Output: taskboard-electron/out/make/AgentForge-1.0.0-arm64.dmg
```

### Tests & Quality
```bash
# Backend (⭐ backend-quality CI job runs `make check`)
make check # ruff lint + ruff format --check + pytest (coverage, fail_under=90)
make test # pytest only
make lint # ruff check ONLY (no format-check, no tests — NOT the CI gate)
make format # apply ruff formatting
uv run pytest -q # run the Python suite directly
make check # = cd backend && bun run check (tsc --noEmit + prettier --check + bun test --coverage)
make test # bun test only
make lint # tsc typecheck ONLY (no format-check, no tests — NOT the CI gate)
make format # apply prettier formatting
cd backend && bun test # run the suite directly

# Frontend (⭐ frontend-quality CI job runs all four, from taskboard-electron/)
# Frontend (⭐ frontend-quality CI job runs all five, from taskboard-electron/)
cd taskboard-electron
npm run lint # ESLint (flat config, eslint.config.mjs)
npm run format:check # Prettier --check (npm run format to apply)
npm test # node --test (pins TZ=Asia/Shanghai — date tests assert local wall time)
npm run build:check # vite renderer build — catches compile/import errors
bun run typecheck # tsc over renderer + main/preload tsconfigs
bun run lint # ESLint (flat config, typescript-eslint, eslint.config.mjs)
bun run format:check # Prettier --check (bun run format to apply)
bun run test # bun test (pins TZ=Asia/Shanghai — date tests assert local wall time)
bun run build:check # Bun.build of main/preload/renderer — catches compile/import errors
```
CI (`.github/workflows/ci.yml`) runs two jobs: **backend-quality** (`make check`)
and **frontend-quality** (lint + format check + tests + build). The workflow uses
`concurrency` to cancel superseded runs on the same ref. There are 34 pytest files
under `tests/` (backend coverage gate is 90%) plus `.test.mjs` files beside the renderer.
CI (`.github/workflows/ci.yml`) runs two jobs: **backend-quality** (`bun run check` in `backend/`)
and **frontend-quality** (typecheck + lint + format check + tests + build). The workflow uses
`concurrency` to cancel superseded runs on the same ref. Backend tests live in `backend/tests/*.test.ts`
(bun:test), frontend tests beside the renderer sources as `*.test.ts`.

## Architecture

### Two-process model
The Electron main process (`taskboard-electron/src/main.js`) spawns the Python backend on startup and kills it on quit. The React renderer communicates with the backend exclusively via HTTP on `127.0.0.1:9712` (loopback only). There is no WebSocket or IPC for data — the renderer polls the REST API with `fetch()`.
The Electron main process (`taskboard-electron/src/main.ts`) spawns the Bun backend on startup and kills it on quit. The React renderer communicates with the backend exclusively via HTTP on `127.0.0.1:9712` (loopback only). There is no WebSocket or IPC for data — the renderer polls the REST API with `fetch()`.

### Python backend (`taskboard.py`)
Single-file HTTP server (`BaseHTTPRequestHandler`) with:
- **`TaskDB`** — SQLite layer at `~/.agentforge/tasks.db`. Thread-safe with a lock. Stores tasks, run history, and streaming output events.
- **`AgentExecutor`** — Runs the agent CLI: `claude -p … --output-format stream-json --verbose --permission-mode bypassPermissions`, or `codex exec --json …`. Parses the NDJSON stream and persists each event to `task_output_events`.
- **`TaskScheduler`** — Background thread that polls every 2 seconds for due tasks. Supports four schedule types:
### Bun backend (`backend/`)
TypeScript modules served by `Bun.serve` (entry `backend/taskboard.ts`):
- **`src/db.ts` — `TaskDB`** — SQLite layer (bun:sqlite) at `~/.agentforge/tasks.db`. Stores tasks, run history, and streaming output events. Method names keep the original Python snake_case spelling (they double as API JSON keys).
- **`src/executor.ts` — `AgentExecutor`** — Runs the agent CLI: `claude -p … --output-format stream-json --verbose --permission-mode bypassPermissions`, or `codex exec --json …`. Parses the NDJSON stream and persists each event to `task_output_events`.
- **`src/scheduler.ts` — `TaskScheduler`** — Polls every 2 seconds for due tasks. Supports four schedule types:
- `immediate`: runs as soon as scheduled
- `delayed`: runs after N seconds (relative time)
- `scheduled_at`: runs once at a specific datetime (absolute time)
- `cron`: recurring schedule using croniter for cron expression evaluation
- **`Heartbeat`** — Background watcher: on a cron/interval it runs a `check_prompt` via an agent that returns a JSON decision (idle/trigger/resume/notify) and may auto-create tasks.
- **Skill Library** — `TaskScheduler.run_skill_sweep` periodically (or via the manual "Scan" button) asks an agent to detect recurring patterns across completed runs (`skill_patterns` table), distills candidates into standard Claude Code `SKILL.md` files using the vendored `vendor/skill-creator`, and on approval writes them to `~/.agentforge/skills` symlinked into both `~/.claude/skills` and `~/.agents/skills`. Off by default (`skill_library_enabled` setting).
- **Channels** (`channels/`) — Optional Telegram / Slack / Feishu / WeChat bridges (a `MessageBus` in `taskboard_bus.py` decouples them from the scheduler). Feishu uses a lark WebSocket long-connection.
- **REST API** — Endpoints under `/api/tasks*`, `/api/heartbeats*`, `/api/skill-patterns`, `/api/skills*`, `/api/settings`, `/api/channels/*`, `/api/health`.

### Electron main process (`taskboard-electron/src/main.js`)
- In **dev mode**: `app.getAppPath()` returns `taskboard-electron/`, so `path.join(app.getAppPath(), '..')` resolves to project root for `uv run taskboard.py`. The `cwd` option must point to project root when spawning.
- In **packaged mode**: uses the binary at `resources/taskboard` bundled inside the `.app`.
- `cron`: recurring schedule using cron-parser for cron expression evaluation
- **Heartbeats** — Background watcher: on a cron/interval it runs a `check_prompt` via an agent that returns a JSON decision (idle/trigger/resume/notify) and may auto-create tasks.
- **Skill Library** (`src/skills.ts` + scheduler) — `TaskScheduler.run_skill_sweep` periodically (or via the manual "Scan" button) asks an agent to detect recurring patterns across completed runs (`skill_patterns` table), distills candidates into standard Claude Code `SKILL.md` files using the vendored `vendor/skill-creator`, and on approval writes them to `~/.agentforge/skills` symlinked into both `~/.claude/skills` and `~/.agents/skills`. Off by default (`skill_library_enabled` setting).
- **Channels** (`src/channels/`) — Optional Telegram / Slack / Feishu / WeChat bridges (a `MessageBus` in `src/bus.ts` decouples them from the scheduler). Feishu uses a lark WebSocket long-connection (`@larksuiteoapi/node-sdk`).
- **REST API** (`src/api.ts`, `src/server.ts`) — Endpoints under `/api/tasks*`, `/api/heartbeats*`, `/api/skill-patterns`, `/api/skills*`, `/api/settings`, `/api/channels/*`, `/api/health`.

### Electron main process (`taskboard-electron/src/main.ts`)
- In **dev mode**: `app.getAppPath()` returns `taskboard-electron/`, so `path.join(app.getAppPath(), '..')` resolves to project root for `bun backend/taskboard.ts`. The `cwd` option must point to project root when spawning.
- In **packaged mode**: uses the `bun build --compile` binary at `resources/taskboard` bundled inside the `.app`.
- Polls `/api/health` (15s timeout) before loading the UI.
- Exposes `window.electronAPI.selectDirectory()` to renderer via context bridge for native directory picker.

### React frontend (`taskboard-electron/src/renderer/App.jsx`)
Single large component (~4200 lines). Key design points:
### Build pipeline (`taskboard-electron/scripts/`)
- `build.ts` — `Bun.build` bundles main (CJS, electron external), preload (CJS), and renderer (HTML entrypoint → `.bun/renderer/`). Replaces the old Vite plugin.
- `dev.ts` — watch-rebuild + Electron launcher; renderer rebuilds trigger window reload (main.ts watches `.bun/renderer`), backend `.ts` changes restart the backend.
- `build-backend.ts` — `bun build --compile` of `backend/taskboard.ts` into `resources/taskboard`.
- electron-forge is retained only for packaging/DMG (`bunx electron-forge package|make`); `forge.config.js` ships `.bun/` output via packager ignore rules.

### React frontend (`taskboard-electron/src/renderer/App.tsx`)
Single large component (~6200 lines). Key design points:
- `API` constant hardcoded to `http://127.0.0.1:9712/api`.
- Three top-level views (tab switch): **Tasks** (kanban), **Heartbeats**, **Skills**.
- Kanban columns: **Queue** (pending/scheduled/blocked) → **Running** → **Done** (completed/failed/cancelled).
- `FormattedOutput` component parses stream-json events (type: `user`/`assistant`/`result`/`error`) and renders colorized output; trace/event aggregation helpers live in `traceSteps.mjs` (tested by `traceSteps.test.mjs`).
- `FormattedOutput` component parses stream-json events (type: `user`/`assistant`/`result`/`error`) and renders colorized output; trace/event aggregation helpers live in `traceSteps.ts` (tested by `traceSteps.test.ts`).
- Backend payload interfaces (snake_case) live in `src/renderer/types.ts`.
- Task creation supports four schedule types:
- `immediate`: run immediately
- `delayed`: run after N seconds
Expand All @@ -100,6 +106,6 @@ Single large component (~4200 lines). Key design points:
## Workflow Rules

### Always run `make check` after changing code
- After any change to Python code (or before pushing / reporting done), run **`make check`** — not `make lint`. `make lint` only runs `ruff check`; it skips `ruff format --check` and the tests, so it will pass while CI still fails on formatting or a broken test.
- After any change to backend code (or before pushing / reporting done), run **`make check`** — not `make lint`. `make lint` only runs the tsc typecheck; it skips `prettier --check` and the tests, so it will pass while CI still fails on formatting or a broken test.
- If `make check` reports formatting diffs, run `make format` to fix them, then re-run `make check`.
- For frontend-only changes, run the frontend gate from `taskboard-electron/`: `npm run lint && npm run format:check && npm test && npm run build:check` (this is exactly what the frontend-quality CI job runs). If `format:check` fails, run `npm run format` to fix.
- For frontend-only changes, run the frontend gate from `taskboard-electron/`: `bun run typecheck && bun run lint && bun run format:check && bun run test && bun run build:check` (this is exactly what the frontend-quality CI job runs). If `format:check` fails, run `bun run format` to fix.
59 changes: 27 additions & 32 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,50 +1,45 @@
.PHONY: help build-backend build-electron package-dmg clean install-deps lint format format-check test test-cov check
.PHONY: help build-backend build-electron package-dmg clean install-deps lint format format-check test test-cov check dev-backend dev-electron check-backend check-dmg

# 项目配置
PROJECT_NAME = AgentForge
BACKEND_SRC = taskboard.py
BACKEND_DIR = backend
BACKEND_BINARY = taskboard-electron/resources/taskboard
ELECTRON_DIR = taskboard-electron
DMG_OUTPUT = $(ELECTRON_DIR)/out/make/$(PROJECT_NAME)-1.0.0-arm64.dmg

help:
@echo "AgentForge 打包工具"
@echo "AgentForge 打包工具 (Bun + TypeScript)"
@echo ""
@echo "可用命令:"
@echo " make help - 显示此帮助信息"
@echo " make install-deps - 安装项目依赖"
@echo " make build-backend - 构建Python后端二进制文件"
@echo " make install-deps - 安装项目依赖 (bun install)"
@echo " make build-backend - 编译后端单文件二进制 (bun build --compile)"
@echo " make build-electron - 构建Electron应用"
@echo " make package-dmg - 打包为DMG文件(包含所有步骤)"
@echo " make clean - 清理构建文件"
@echo ""
@echo "快速打包: make package-dmg"

install-deps:
@echo "安装Python依赖..."
uv add pyinstaller croniter python-dateutil pytz
@echo "安装后端依赖..."
cd $(BACKEND_DIR) && bun install
@echo "安装Electron依赖..."
cd $(ELECTRON_DIR) && npm install
cd $(ELECTRON_DIR) && bun install

build-backend:
@echo "构建Python后端二进制文件..."
uv run pyinstaller --onefile --name taskboard \
--distpath $(ELECTRON_DIR)/resources \
--hidden-import croniter --hidden-import dateutil --hidden-import pytz \
--add-data vendor/skill-creator:vendor/skill-creator \
--add-data channels/weixin_bridge:channels/weixin_bridge \
$(BACKEND_SRC)
@echo "编译后端单文件二进制 (bun build --compile)..."
cd $(ELECTRON_DIR) && bun scripts/build-backend.ts
@echo "后端二进制文件位置: $(BACKEND_BINARY)"
@ls -lh $(BACKEND_BINARY)

build-electron:
@echo "构建Electron应用..."
cd $(ELECTRON_DIR) && SKIP_BACKEND_BUILD=1 npm run package
cd $(ELECTRON_DIR) && bun scripts/build.ts && SKIP_BACKEND_BUILD=1 bunx electron-forge package
@echo "Electron应用构建完成"

package-dmg: build-backend build-electron
package-dmg: build-backend
@echo "打包DMG文件..."
cd $(ELECTRON_DIR) && SKIP_BACKEND_BUILD=1 npm run make
cd $(ELECTRON_DIR) && bun scripts/build.ts && SKIP_BACKEND_BUILD=1 bunx electron-forge make
@if [ -f "$(DMG_OUTPUT)" ]; then \
echo "DMG文件生成成功: $(DMG_OUTPUT)"; \
ls -lh "$(DMG_OUTPUT)"; \
Expand All @@ -55,20 +50,19 @@ package-dmg: build-backend build-electron

clean:
@echo "清理构建文件..."
rm -rf build/
rm -rf $(ELECTRON_DIR)/out/
rm -rf $(ELECTRON_DIR)/.vite/
rm -rf $(ELECTRON_DIR)/.bun/
rm -f $(BACKEND_BINARY)
@echo "清理完成"

# 开发相关命令
dev-backend:
@echo "启动后端开发服务器..."
uv run python $(BACKEND_SRC)
cd $(BACKEND_DIR) && bun taskboard.ts

dev-electron:
@echo "启动Electron开发模式..."
cd $(ELECTRON_DIR) && npm start
cd $(ELECTRON_DIR) && bun run start

# 检查命令
check-backend:
Expand All @@ -84,23 +78,24 @@ check-dmg:
fi

lint:
@echo "运行 Ruff lint..."
uv run ruff check .
@echo "运行 TypeScript 类型检查..."
cd $(BACKEND_DIR) && bun run typecheck

format:
@echo "运行 Ruff format..."
uv run ruff format .
@echo "运行 Prettier format..."
cd $(BACKEND_DIR) && bun run format

format-check:
@echo "检查代码格式..."
uv run ruff format --check .
cd $(BACKEND_DIR) && bun run format:check

test:
@echo "运行 Python 测试..."
uv run pytest
@echo "运行后端测试..."
cd $(BACKEND_DIR) && bun test

test-cov:
@echo "运行 Python 测试并检查覆盖率..."
uv run pytest --cov --cov-report=term-missing
@echo "运行后端测试并检查覆盖率..."
cd $(BACKEND_DIR) && bun test --coverage

check: lint format-check test-cov
check:
cd $(BACKEND_DIR) && bun run check
Loading