面向基于 VSCode + Cline 开源代码二次开发的 AI 编程 IDE,将 evolving-programming-agent 集成为核心 skill。
关键约束:
- 运行时完全离线 — 用户机器无网络,所有资源必须随 IDE 安装包分发
- 集成只在 build 期 — 联网下载、版本检测、资源拉取仅在 IDE 构建流水线中执行
- 完整代码集成 — IDE build 期下载本 skill 完整代码 + 便携 Python + 离线 wheel,全部打包进安装包
─────────────────────────────────────────────────────────────────────────────
[本项目 Release Pipeline] 打 tag → 多平台预构建 → 上传 GitHub Release
▼
─────────────────────────────────────────────────────────────────────────────
[IDE Build Pipeline] (在线) ① 拉取 skill 完整代码 (git/release)
② 拉取便携 Python (python-build-standalone)
③ 拉取离线 wheel (vendor/)
④ 调用 pack_for_ide.py 校验+组装
⑤ 产物固化进 IDE 安装包
▼
─────────────────────────────────────────────────────────────────────────────
[用户机器] (离线) ⑥ 安装 IDE
⑦ 首次启动:bootstrap.py --resolve(纯本地)
⑧ Skill 注入 system prompt + Tool 硬约束
⑨ 全程无网络
─────────────────────────────────────────────────────────────────────────────
┌─────────────────────────────────────────────────────────────┐
│ IDE 项目(集成层) │
│ Build 期: │
│ - 拉取本项目 release(含完整代码 + Python + vendor) │
│ - 校验 cli_protocol 兼容性 │
│ - 资源固化进安装包 │
│ 运行时(离线): │
│ - System Prompt 注入 │
│ - Tool 硬约束(evolving_agent + 编辑工具拦截) │
│ - UI(状态栏、任务面板) │
└──────────────────────┬──────────────────────────────────────┘
│ Build 期契约:pack-for-ide 资源包
│ Runtime 契约:CLI Protocol v1(纯离线)
▼
┌─────────────────────────────────────────────────────────────┐
│ 本项目(运行时层 - evolving-agent) │
│ Release 期: │
│ - GitHub Actions 多平台预构建 │
│ - 产出:evolving-agent-<ver>-<platform>.tar.gz │
│ 对 IDE build 暴露: │
│ - pack_for_ide.py:一键资源组装工具 │
│ - manifest.json:完整性校验 │
│ 对运行时暴露: │
│ - bootstrap.py(detect-only,无下载逻辑) │
│ - run.py:CLI 入口(version / meta / task / knowledge) │
└─────────────────────────────────────────────────────────────┘
由于运行时离线,「迅速」不再是「重启即生效」,而是「IDE build pipeline 跟上本项目最新版的成本极低」。
| 本项目变更类型 | IDE 跟进流程 | 时效 |
|---|---|---|
| SKILL.md 文字 / agents 文档 | IDE 重新 build → 发新 IDE 版本 → 用户更新 | 下个 IDE release 周期 |
| 新增 run.py 子命令(兼容) | 同上,IDE 代码可不动 | 下个 IDE release 周期 |
| 破坏性变更(bump cli_protocol) | IDE 代码适配 + 发新版 | 1-2 天 + 下个 IDE release |
| IDE build 拉取本项目新版的人力成本 | 改一行 release 版本号 | 5 分钟 |
关键:本项目必须把「IDE build 拉取 + 集成」做成一行命令,这样 IDE pipeline 升级 skill 几乎零成本。
这一节定下两个仓库各自负责什么、谁不该碰什么。后续所有改动应在这个边界内进行。 历史背景:v1.0.0 时期 IDE 侧曾用正则做 prompt adapter,在 v1.1.0 通过
--mode ide把适配责任收回到本仓库,适配只在一个地方写。
| 仓库 | 角色 | 维护内容 |
|---|---|---|
evolving-programming-agent(本项目) |
唯一适配源 | 维护 multi-agent + IDE 两份 SKILL 变体(SKILL.md + SKILL.ide.md),通过 run.py meta --mode {default,ide} 同时服务多种集成方。所有内容/语法适配都在本仓库做完,不要让下游做转写。 |
tiantacode(IDE 侧) |
纯消费方 | 通过 git submodule 引用本项目,运行 npm run skill:prepare 拉资源、bridge.exec(['meta','--skill-content','--mode','ide']) 取内容。禁止做正则转写、内容魔改、维护"自家版本"的 SKILL。 |
skill 升级到新版本时,正确的端到端流程:
# ① 本仓库(evolving-programming-agent)
# 完成改动 → CHANGELOG → bump runtime.json 版本 → tag → push
git tag -a vX.Y.Z -m "..."
git push origin main vX.Y.Z
# ② IDE 仓库(tiantacode),单条命令拉新版
cd extensions/huashan-ai-code/vendor/evolving-agent
git fetch && git checkout vX.Y.Z
cd ../../../..
git add extensions/huashan-ai-code/vendor/evolving-agent
git commit -m "chore: bump evolving-agent submodule to vX.Y.Z"
# ③ 重新 prepare 资源(自动校验 cli_protocol 兼容)
npm run skill:prepare
# ④ build → 发版
npm run build:desktop只要本项目守住 cli_protocol 兼容(patch / minor 不破坏),IDE 这条路径不需要碰任何 TS 代码。
-
evolving-programming-agent是单一适配源- 任何针对特定集成方的内容改写(如把
调度 @coder改写为 IDE tool 调用)都在本仓库新增 / 修改对应 mode 的 skill 变体(如SKILL.ide.md),而不是在下游做。 - 新增第三种集成方(如未来某个新 IDE)时:本仓库新增
SKILL.<mode>.md+ 在run.py meta --mode的 choices 加新值,下游永远只是读取。
- 任何针对特定集成方的内容改写(如把
-
tiantacode不修改 skill 内容- PR 评审时如果发现 IDE 侧出现「正则替换 skill 文本」「魔改 SKILL.md 复制版」这类逻辑,应当 reject 并要求把改动反推到本项目。
- IDE 唯一允许的「适配」是包装层(如 system prompt 的
<EXTREMELY_IMPORTANT>外层标签),但不能修改 skill 主体内容。
-
跟进 = 一行 submodule update
- 只要本项目 release 时守 semver + cli_protocol 兼容承诺,IDE 升级 skill 就只是「改 submodule pointer + commit + 重新 prepare」,零代码改动。
- 例外(破坏性变更)必须 bump 到 major / 改 cli_protocol,且本项目 CHANGELOG 中明确列出 IDE 侧需要的对应改动——这时 IDE 才需要写代码。
evolving-programming-agent/
├── runtime.json # 【新增】运行时声明
├── scripts/
│ ├── bootstrap.py # 【新增】纯 detect,无下载
│ ├── pack_for_ide.py # 【新增】IDE build 期一键打包工具
│ └── run.py # 已有,新增 meta / version 子命令
├── .github/workflows/
│ └── release.yml # 【新增】多平台预构建
└── docs/
└── IDE-INTEGRATION.md # 当前文档
{
"skill": {
"name": "evolving-agent",
"version": "1.2.0",
"skill_md": "SKILL.md",
"agents_dir": "agents/",
"workflows_dir": "workflows/"
},
"python": {
"min_version": "3.8",
"recommended_version": "3.11",
"portable": {
"source": "python-build-standalone",
"release_tag": "20240415",
"downloads": {
"linux-x86_64": "https://github.com/astral-sh/python-build-standalone/releases/download/20240415/cpython-3.11.9+20240415-x86_64-unknown-linux-gnu-install_only.tar.gz",
"linux-aarch64": "https://github.com/astral-sh/python-build-standalone/releases/download/20240415/cpython-3.11.9+20240415-aarch64-unknown-linux-gnu-install_only.tar.gz",
"darwin-x86_64": "https://github.com/astral-sh/python-build-standalone/releases/download/20240415/cpython-3.11.9+20240415-x86_64-apple-darwin-install_only.tar.gz",
"darwin-arm64": "https://github.com/astral-sh/python-build-standalone/releases/download/20240415/cpython-3.11.9+20240415-aarch64-apple-darwin-install_only.tar.gz",
"windows-x86_64": "https://github.com/astral-sh/python-build-standalone/releases/download/20240415/cpython-3.11.9+20240415-x86_64-pc-windows-msvc-install_only.tar.gz"
},
"checksums": {
"linux-x86_64": "sha256:...",
"linux-aarch64": "sha256:...",
"darwin-x86_64": "sha256:...",
"darwin-arm64": "sha256:...",
"windows-x86_64": "sha256:..."
}
}
},
"dependencies": {
"required": ["PyYAML>=6.0,<7.0"],
"optional": ["jieba>=0.42,<1.0"]
},
"cli_protocol": {
"version": "v1",
"min_ide_version": "0.5.0"
}
}设计变更:删除原方案中的 download_portable_python 逻辑。运行时只能 detect 已经被 IDE build 期打包好的资源。
"""
bootstrap.py — 运行时 Python 解析器(纯离线,无下载)
调用方式:
python3 bootstrap.py --resolve # 解析 Python 路径,输出 JSON
python3 bootstrap.py --doctor # 诊断当前环境
输出:
{"status":"ok","python":"/path","python_version":"3.11.9","source":"portable","skill_root":"/path"}
"""
import json, os, sys, subprocess
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RUNTIME_JSON = os.path.join(PROJECT_ROOT, "runtime.json")
def detect_portable_python(skill_root):
"""检测 IDE build 期打包的便携 Python(与 skill 资源同目录)"""
bin_paths = [
os.path.join(skill_root, ".runtime/python/bin/python3"), # mac/linux
os.path.join(skill_root, ".runtime/python/python.exe"), # windows
]
for p in bin_paths:
if os.path.exists(p):
try:
out = subprocess.check_output(
[p, "-c", "import sys; print('.'.join(map(str, sys.version_info[:3])))"],
timeout=5
).decode().strip()
return {"path": p, "version": out, "source": "portable"}
except Exception:
pass
return None
def detect_system_python(min_version="3.8"):
"""方案 B fallback:用户系统 Python(仅当 IDE 未打包便携 Python 时)"""
candidates = ["python3.13", "python3.12", "python3.11", "python3.10",
"python3.9", "python3.8", "python3", "python"]
for cmd in candidates:
try:
out = subprocess.check_output(
[cmd, "-c", "import sys; print('.'.join(map(str, sys.version_info[:3])))"],
stderr=subprocess.DEVNULL, timeout=5
).decode().strip()
if tuple(map(int, out.split("."))) >= tuple(map(int, min_version.split("."))):
return {"path": cmd, "version": out, "source": "system"}
except Exception:
continue
return None
def resolve():
cfg = json.load(open(RUNTIME_JSON))
# 优先级:IDE 打包的便携 Python > 系统 Python(兜底)
py = detect_portable_python(PROJECT_ROOT) \
or detect_system_python(cfg["python"]["min_version"])
if not py:
return {
"status": "error",
"code": "PYTHON_NOT_FOUND",
"message": "No Python found. IDE installation may be corrupted."
}
return {
"status": "ok",
"python": py["path"],
"python_version": py["version"],
"source": py["source"],
"skill_root": PROJECT_ROOT,
}
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--resolve", action="store_true")
ap.add_argument("--doctor", action="store_true")
args = ap.parse_args()
print(json.dumps(resolve()))
sys.exit(0 if resolve().get("status") == "ok" else 1)这是本项目对 IDE build pipeline 暴露的核心工具。一行命令完成「下载便携 Python + 准备 vendor + 复制 skill 代码 + 生成 manifest」。
"""
pack_for_ide.py — 为 IDE build pipeline 准备完整离线资源包
仅在 IDE build 期(联网环境)调用。
调用方式:
python pack_for_ide.py \
--output ./build-output/evolving-agent \
--platform darwin-arm64 \
--include-vendor \
--include-portable-python
产出目录结构:
<output>/
├── runtime.json # 资源清单
├── SKILL.md
├── agents/
├── workflows/
├── scripts/ # 含 bootstrap.py / run.py
├── .runtime/
│ └── python/ # 便携 Python(已展开)
├── vendor/ # 离线 wheel
│ ├── PyYAML-*.whl
│ └── jieba-*.whl
└── manifest.json # 完整性校验 + 版本元数据
"""
import json, os, sys, shutil, hashlib, urllib.request, tarfile, subprocess
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
def copy_skill_code(output: Path):
"""复制 skill 源码到输出目录"""
for item in ["SKILL.md", "runtime.json", "requirements.txt",
"requirements-optional.txt", "agents", "workflows",
"scripts", "references", "skills"]:
src = PROJECT_ROOT / item
if not src.exists():
continue
dst = output / item
if src.is_dir():
shutil.copytree(src, dst, dirs_exist_ok=True,
ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
else:
shutil.copy2(src, dst)
def download_portable_python(output: Path, platform: str):
"""下载并解压便携 Python 到 .runtime/python/"""
cfg = json.loads((PROJECT_ROOT / "runtime.json").read_text())
url = cfg["python"]["portable"]["downloads"].get(platform)
if not url:
raise RuntimeError(f"Unsupported platform: {platform}")
target = output / ".runtime"
target.mkdir(parents=True, exist_ok=True)
archive = target / "python.tar.gz"
print(f"[pack] Downloading Python for {platform}...", file=sys.stderr)
urllib.request.urlretrieve(url, archive)
# 校验 sha256
expected = cfg["python"]["portable"].get("checksums", {}).get(platform, "")
if expected.startswith("sha256:"):
h = hashlib.sha256()
with open(archive, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
assert h.hexdigest() == expected[7:], f"Checksum mismatch for {platform}"
print(f"[pack] Extracting...", file=sys.stderr)
with tarfile.open(archive) as tar:
tar.extractall(target)
archive.unlink()
def download_vendor_wheels(output: Path, platform: str):
"""下载离线 wheel 包(多平台兼容)"""
vendor = output / "vendor"
vendor.mkdir(parents=True, exist_ok=True)
requirements = PROJECT_ROOT / "requirements.txt"
# 平台名映射到 pip --platform
pip_platform_map = {
"linux-x86_64": "manylinux2014_x86_64",
"linux-aarch64": "manylinux2014_aarch64",
"darwin-x86_64": "macosx_10_9_x86_64",
"darwin-arm64": "macosx_11_0_arm64",
"windows-x86_64": "win_amd64",
}
pip_plat = pip_platform_map[platform]
print(f"[pack] Downloading wheels for {pip_plat}...", file=sys.stderr)
subprocess.check_call([
sys.executable, "-m", "pip", "download",
"--dest", str(vendor),
"--platform", pip_plat,
"--only-binary=:all:",
"--no-deps",
"-r", str(requirements),
])
def generate_manifest(output: Path, platform: str):
"""生成 manifest.json:版本 + 文件清单 + checksum"""
cfg = json.loads((PROJECT_ROOT / "runtime.json").read_text())
files = []
for root, _, names in os.walk(output):
for n in names:
if n == "manifest.json":
continue
p = Path(root) / n
rel = p.relative_to(output)
sha = hashlib.sha256(p.read_bytes()).hexdigest()
files.append({"path": str(rel), "sha256": sha, "size": p.stat().st_size})
manifest = {
"skill_version": cfg["skill"]["version"],
"cli_protocol": cfg["cli_protocol"]["version"],
"min_ide_version": cfg["cli_protocol"]["min_ide_version"],
"platform": platform,
"build_time": subprocess.check_output(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"]).decode().strip(),
"files": files,
}
(output / "manifest.json").write_text(json.dumps(manifest, indent=2))
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--output", required=True)
ap.add_argument("--platform", required=True,
choices=["linux-x86_64","linux-aarch64",
"darwin-x86_64","darwin-arm64","windows-x86_64"])
ap.add_argument("--include-vendor", action="store_true", default=True)
ap.add_argument("--include-portable-python", action="store_true", default=True)
args = ap.parse_args()
output = Path(args.output).resolve()
if output.exists():
shutil.rmtree(output)
output.mkdir(parents=True)
copy_skill_code(output)
if args.include_vendor:
download_vendor_wheels(output, args.platform)
if args.include_portable_python:
download_portable_python(output, args.platform)
generate_manifest(output, args.platform)
print(f"[pack] OK: {output}", file=sys.stderr)
print(json.dumps({"status":"ok","output":str(output),
"platform":args.platform}))
if __name__ == "__main__":
main()def cmd_version(args):
"""供 IDE build 期校验兼容性"""
cfg = json.load(open(os.path.join(PROJECT_ROOT, "runtime.json")))
print(json.dumps({
"skill_version": cfg["skill"]["version"],
"cli_protocol": cfg["cli_protocol"]["version"],
"min_ide_version": cfg["cli_protocol"]["min_ide_version"],
}))
def cmd_meta(args):
"""暴露 skill 内容(供 IDE build 期固化进安装包)"""
cfg = json.load(open(os.path.join(PROJECT_ROOT, "runtime.json")))
if args.skill_content:
result = {
"skill_md": open(os.path.join(PROJECT_ROOT, cfg["skill"]["skill_md"])).read(),
"agents": {f.name: f.read_text()
for f in Path(PROJECT_ROOT, "agents").glob("*.md")},
"workflows": {f.name: f.read_text()
for f in Path(PROJECT_ROOT, "workflows").glob("*.md")},
}
else:
result = cfg
print(json.dumps(result, ensure_ascii=False))
def cmd_verify(args):
"""运行时校验 manifest.json 完整性(防止安装包损坏)"""
manifest = json.load(open(os.path.join(PROJECT_ROOT, "manifest.json")))
errors = []
for f in manifest["files"]:
p = os.path.join(PROJECT_ROOT, f["path"])
if not os.path.exists(p):
errors.append(f"missing: {f['path']}")
continue
actual = hashlib.sha256(open(p,"rb").read()).hexdigest()
if actual != f["sha256"]:
errors.append(f"checksum mismatch: {f['path']}")
print(json.dumps({"status":"ok" if not errors else "error","errors":errors}))让本项目每次打 tag 时自动产出 5 个平台的预打包资源,IDE build pipeline 直接下载 release 资源即可,不必自己跑 pack_for_ide.py。
name: Release
on:
push:
tags: ['v*']
jobs:
build-package:
strategy:
matrix:
include:
- platform: linux-x86_64
runner: ubuntu-latest
- platform: linux-aarch64
runner: ubuntu-latest
- platform: darwin-x86_64
runner: macos-13
- platform: darwin-arm64
runner: macos-14
- platform: windows-x86_64
runner: windows-latest
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- name: Pack for IDE
run: |
python scripts/pack_for_ide.py \
--output ./dist/evolving-agent-${{ matrix.platform }} \
--platform ${{ matrix.platform }}
- name: Compress
run: |
tar -czf evolving-agent-${{ matrix.platform }}.tar.gz \
-C ./dist evolving-agent-${{ matrix.platform }}
- uses: softprops/action-gh-release@v2
with:
files: evolving-agent-${{ matrix.platform }}.tar.gz对 IDE 的承诺:每个 release 都有 5 个 evolving-agent-<platform>.tar.gz,开箱即用。
| 命令 | 调用时机 | 是否联网 | 用途 |
|---|---|---|---|
pack_for_ide.py --output ... --platform ... |
IDE build 期 | ✅ 需要 | 一键打包资源 |
run.py version |
IDE build 期 + 运行时 | ❌ 离线 | 版本兼容校验 |
run.py verify |
运行时启动 | ❌ 离线 | manifest 完整性校验 |
bootstrap.py --resolve |
运行时启动 | ❌ 离线 | 解析 Python 路径 |
run.py meta --skill-content |
IDE build 期 | ❌ 离线 | 固化 skill 内容到安装包 |
run.py task status --json |
运行时 | ❌ 离线 | 任务状态机查询 |
run.py task transition |
运行时 | ❌ 离线 | 状态变更 |
run.py knowledge trigger |
运行时 | ❌ 离线 | 知识检索 |
目标:在 IDE 构建流水线中一次性拉取本项目所有资源、校验、固化。
<your-ide>/
├── package.json
├── build/
│ ├── fetch-skill.sh # 【关键】Build 期资源拉取脚本
│ ├── verify-skill.sh # Build 期版本校验
│ └── pack-installer.sh # 最终打包安装器
├── resources/
│ └── evolving-agent/ # ← Build 期由 fetch-skill.sh 填充,git ignore
│ ├── runtime.json
│ ├── SKILL.md
│ ├── agents/
│ ├── workflows/
│ ├── scripts/
│ ├── .runtime/python/ # 便携 Python
│ ├── vendor/ # 离线 wheel
│ └── manifest.json
└── src/
├── runtime/PythonBridge.ts
├── skill/SkillLoader.ts
├── prompts/system.ts
├── tools/EvolvingAgentTool.ts
└── tools/EditToolGuard.ts
resources/evolving-agent/ 列入 .gitignore — 它是 build 产物,不进 IDE 仓库。
#!/usr/bin/env sh
# 在 IDE build pipeline 中(联网环境)执行
# 用法: ./build/fetch-skill.sh <skill-version> <platform>
# e.g. ./build/fetch-skill.sh v1.2.0 darwin-arm64
set -e
SKILL_VERSION="${1:-v1.2.0}"
PLATFORM="${2:-$(uname -s | tr A-Z a-z)-$(uname -m)}"
TARGET="resources/evolving-agent"
URL="https://github.com/YOUR_ORG/evolving-programming-agent/releases/download/${SKILL_VERSION}/evolving-agent-${PLATFORM}.tar.gz"
echo "[ide-build] Fetching skill ${SKILL_VERSION} for ${PLATFORM}..."
mkdir -p "$TARGET"
curl -fsSL "$URL" | tar -xz --strip-components=1 -C "$TARGET"
echo "[ide-build] Verifying manifest..."
SKILL_VER=$(node -e "console.log(require('./$TARGET/manifest.json').skill_version)")
CLI_PROTO=$(node -e "console.log(require('./$TARGET/manifest.json').cli_protocol)")
MIN_IDE=$(node -e "console.log(require('./$TARGET/manifest.json').min_ide_version)")
echo "[ide-build] OK: skill=$SKILL_VER cli=$CLI_PROTO min_ide=$MIN_IDE"执行一次完成:①下载 release 包 ②解压到 resources/ ③读 manifest 打印版本。
#!/usr/bin/env sh
# 在 IDE pipeline 的 verify 阶段执行
# 不通过则 build 失败
set -e
TARGET="resources/evolving-agent"
IDE_VERSION=$(node -e "console.log(require('./package.json').version)")
REQUIRED_CLI="v1"
ACTUAL_CLI=$(node -e "console.log(require('./$TARGET/manifest.json').cli_protocol)")
MIN_IDE=$(node -e "console.log(require('./$TARGET/manifest.json').min_ide_version)")
if [ "$ACTUAL_CLI" != "$REQUIRED_CLI" ]; then
echo "ERROR: skill cli_protocol=$ACTUAL_CLI, IDE 仅支持 $REQUIRED_CLI"
exit 1
fi
if ! node -e "
const semver = require('semver');
if (semver.lt('$IDE_VERSION', '$MIN_IDE')) process.exit(1);
"; then
echo "ERROR: IDE $IDE_VERSION < skill 要求的 $MIN_IDE"
exit 1
fi
echo "[ide-build] Compatibility OK"{
"scripts": {
"skill:fetch": "./build/fetch-skill.sh v1.2.0 ${PLATFORM:-darwin-arm64}",
"skill:verify": "./build/verify-skill.sh",
"build:prepare": "npm run skill:fetch && npm run skill:verify",
"build": "npm run build:prepare && electron-builder",
"build:all-platforms": "for p in linux-x86_64 darwin-arm64 windows-x86_64; do PLATFORM=$p npm run build; done"
}
}- "skill:fetch": "./build/fetch-skill.sh v1.2.0 ..."
+ "skill:fetch": "./build/fetch-skill.sh v1.3.0 ..."改一行 + 重新 build = 升级完成。这是 IDE 跟进本项目最新版的全部成本。
IDE 启动(用户机器,离线)
├─→ PythonBridge.init()
│ ├─→ spawn bootstrap.py --resolve (从安装包内的 .runtime/python 解析)
│ └─→ 缓存 pythonBin / skillRoot
│
├─→ SkillLoader.load()
│ ├─→ run.py verify 校验 manifest 完整性(防安装包损坏)
│ ├─→ run.py version 读本地 version(不联网)
│ ├─→ run.py meta 读取 SKILL.md/agents/workflows
│ └─→ 缓存 skillContent
│
├─→ 注册 evolving_agent tool
├─→ Hook 编辑工具的 pre-execute
└─→ UI 初始化(状态栏、任务面板)
用户提问
├─→ buildSystemPrompt(skill, userInstructions)
├─→ LLM 调用工具
│ ├─ evolving_agent → PythonBridge.exec(['task','status','--json'])
│ └─ write_file → preEditCheck() → exec(['task','status']) → 通过则放行
└─→ 全程无网络
// src/runtime/PythonBridge.ts
import { spawn, spawnSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
export class PythonBridge {
private pythonBin!: string;
private skillRoot!: string;
/** 启动时调用一次。skillResourceDir 指向 IDE 安装包内的 resources/evolving-agent */
async init(skillResourceDir: string) {
const bootstrap = path.join(skillResourceDir, 'scripts/bootstrap.py');
// 用安装包内的便携 Python 启动 bootstrap
const portablePython = this.findPortablePython(skillResourceDir);
const result = spawnSync(portablePython, [bootstrap, '--resolve'],
{ encoding: 'utf8' });
const info = JSON.parse(result.stdout);
if (info.status !== 'ok') {
throw new Error(`Skill bootstrap failed: ${info.message} (code=${info.code})`);
}
this.pythonBin = info.python;
this.skillRoot = info.skill_root;
// 启动时校验完整性(防安装包被篡改/损坏)
const verify = await this.exec(['verify']);
if (verify.status !== 'ok') {
console.warn('[skill] integrity check failed:', verify.errors);
}
}
async exec(args: string[]): Promise<any> {
return new Promise((resolve, reject) => {
const runPy = path.join(this.skillRoot, 'scripts/run.py');
const proc = spawn(this.pythonBin, [runPy, ...args]);
let out = '', err = '';
proc.stdout.on('data', d => out += d);
proc.stderr.on('data', d => err += d);
proc.on('close', code => {
if (code !== 0) return reject(new Error(err || `exit ${code}`));
try { resolve(JSON.parse(out)); } catch { resolve(out); }
});
});
}
private findPortablePython(skillResourceDir: string): string {
const candidates = [
path.join(skillResourceDir, '.runtime/python/bin/python3'),
path.join(skillResourceDir, '.runtime/python/python.exe'),
];
for (const p of candidates) if (fs.existsSync(p)) return p;
throw new Error('Portable Python not found. IDE installation corrupted.');
}
}// src/skill/SkillLoader.ts
export class SkillLoader {
skillContent!: { skill_md: string; agents: Record<string,string>; workflows: Record<string,string> };
version!: { skill_version: string; cli_protocol: string; min_ide_version: string };
async load(bridge: PythonBridge, ideVersion: string) {
// 1. 读本地 version(不联网,因为 build 期已校验过,运行时只是显示)
this.version = await bridge.exec(['version']);
// 2. 防御性校验:如果用户绕过 IDE 升级机制替换了 skill 资源,运行时再校验一次
if (this.version.cli_protocol !== 'v1') {
throw new Error(`Skill cli_protocol=${this.version.cli_protocol} 不兼容 IDE`);
}
// 3. 一次性加载 skill 文档(缓存到内存)
this.skillContent = await bridge.exec(['meta', '--skill-content']);
}
}// src/prompts/system.ts
export function buildSystemPrompt(skill: SkillLoader, userInstructions: string): string {
return `
<EXTREMELY_IMPORTANT>
You have a built-in core skill: evolving-agent (v${skill.version.skill_version}).
This skill is MANDATORY for ANY coding task and CANNOT be skipped.
To execute tasks, use the \`evolving_agent\` tool.
Direct shell calls to python/run.py are FORBIDDEN — always go through the tool.
</EXTREMELY_IMPORTANT>
# Skill Document
${skill.skillContent.skill_md}
# Agent Roles
${Object.entries(skill.skillContent.agents).map(([k,v]) => `## ${k}\n${v}`).join('\n\n')}
---
${userInstructions}
`;
}// src/tools/EvolvingAgentTool.ts —— LLM 操作 skill 的唯一入口
export const evolvingAgentTool = {
name: 'evolving_agent',
description: 'MANDATORY entry for all coding tasks. Manages task state machine and knowledge base.',
input_schema: {
type: 'object',
properties: {
action: { type: 'string',
enum: ['task_status','task_create','task_transition',
'knowledge_query','knowledge_store','meta'] },
args: { type: 'object' }
},
required: ['action']
},
async execute(input: any, ctx: { bridge: PythonBridge }) {
const argv = mapActionToCli(input.action, input.args || {});
return await ctx.bridge.exec(argv);
}
};
function mapActionToCli(action: string, args: any): string[] {
switch (action) {
case 'task_status': return ['task','status','--json'];
case 'task_create': return ['task','create','--description', args.description];
case 'task_transition': return ['task','transition',
'--task-id', args.task_id,
'--status', args.status,
'--actor', args.actor || 'coder'];
case 'knowledge_query': return ['knowledge','trigger',
'--input', args.input,
'--format', args.format || 'context'];
default: throw new Error(`Unknown action: ${action}`);
}
}// src/tools/EditToolGuard.ts —— 拦截编辑工具,物理硬约束
export async function preEditCheck(filePath: string, ctx: { bridge: PythonBridge }) {
const status = await ctx.bridge.exec(['task','status','--json']);
if (!status.has_active_session) {
throw new ToolError(
`No active task session. Call evolving_agent(action='task_create') first.`
);
}
if (!status.current_task || status.current_task.status !== 'in_progress') {
throw new ToolError(
`Current task not in_progress. Call evolving_agent(action='task_transition', ` +
`args={task_id:'${status.current?.id}', status:'in_progress'}) first.`
);
}
}
// 在 Cline 的工具注册处挂钩
['write_file','apply_diff','replace_in_file'].forEach(name => {
const orig = tools[name].execute;
tools[name].execute = async (input, ctx) => {
await preEditCheck(input.path, ctx);
return orig(input, ctx);
};
});效果:LLM 跳过 evolving_agent 直接调 write_file → ToolError → LLM 看到错误 → 被迫先走状态机。离线 + 物理硬约束双重保障。
实际落地(v1.1.0+)采用 git submodule 方式直接引用,取代了早期设想的 「IDE 从 GitHub Release 下载平台 tar.gz」流程。Submodule 锁定 commit hash, tag 只是辅助参考;这样不依赖 release artifact 在线可用,且对私有部署更友好。
[本项目维护者] evolving-programming-agent
① 改代码 + 更新 CHANGELOG(三栏分类)
② bump runtime.json 中的 skill.version
③ git tag -a vX.Y.Z + git push origin main vX.Y.Z
▼
[IDE 维护者] tiantacode
④ 看本项目 CHANGELOG,决定升级
⑤ submodule 一行升级:
cd extensions/huashan-ai-code/vendor/evolving-agent
git fetch && git checkout vX.Y.Z
⑥ 主仓库 commit submodule pointer:
git add <submodule path>
git commit -m "chore: bump evolving-agent to vX.Y.Z"
⑦ npm run skill:prepare(自动跑 verify,cli_protocol 不兼容则 build red)
⑧ npm run build:desktop → 出 IDE 安装包
▼
[最终用户]
⑨ IDE 自动更新(或手动下载)
⑩ 离线启动,新 skill 立即生效
⭐ 黄金路径:第 ⑤⑥⑦ 步合计 5 分钟人工时间。如果 cli_protocol 没破坏,IDE 侧不需要改任何 TS 代码。
升级 submodule pointer 后 push 主仓库前,确认 tag 已 push 到本项目远端:
git ls-remote --tags origin | grep vX.Y.Z
# 必须有命中;否则其他 clone 主仓库的人执行 git submodule update --init 会失败CI 中可加一步 git ls-remote 校验作为 release blocker。
为了让 IDE 安心升级,本项目必须保证:
- Semver 严格执行
1.2.xpatch:仅修 bug,IDE 可盲升1.x.0minor:兼容增强(增字段/子命令),IDE 可盲升2.0.0major:破坏 cli_protocol,IDE 必须适配
- 每个 tag 自动产出 5 平台 release 资源(GitHub Actions 已配置)
- CHANGELOG.md 强制三栏分类:
Skill changes:影响 SKILL.md / agents 文档(IDE 只需重新 build)CLI changes:run.py 子命令变化(IDE 可能需要更新 mapActionToCli)Breaking:必须 bump cli_protocol
- CI 兼容性守护:PR 修改 run.py 接口未 bump cli_protocol → CI 红
- runtime.json + manifest.json 是双层事实源:源仓库(runtime.json)+ 打包后(manifest.json)
| 本项目变更类型 | IDE 跟进动作 | 人工时间 | 端到端 |
|---|---|---|---|
| SKILL.md / SKILL.ide.md / agents 文档调整 | git submodule update --remote + commit + npm run skill:prepare |
5 分钟 | + 1 个 IDE release 周期 |
| 新增 run.py 子命令 / 字段(兼容) | 同上;如需用新功能再加 mapActionToCli 一行 | 5–30 分钟 | + 1 个 IDE release 周期 |
| 破坏性变更(cli_protocol bump) | IDE 代码适配 + 重新发版 | 1–2 天 | + 1 个 IDE release 周期 |
历史对照:v1.0.0 时期 IDE 侧维护着一份
SkillLoader.adaptForIDE()用正则把 「调度 @coder」转写成 tool 调用,约 80 行代码 + 每次本项目改 SKILL.md 句法都可能 漏抓。v1.1.0 引入SKILL.ide.md+meta --mode ide后,IDE 侧 -54 行,适配负担 完全转移到本项目,跟进时间从「半小时盯改正则」变成「git submodule update」。
-
runtime.json创建,含python.portable.downloads+cli_protocol+ checksums -
scripts/bootstrap.py实现--resolve / --doctor(纯 detect,无下载) -
scripts/pack_for_ide.py实现一键打包(IDE build 期专用) -
run.py新增version/meta/verify子命令 -
.github/workflows/release.yml多平台预构建(5 个平台) - CI 增加 cli_protocol 兼容性检查
- CHANGELOG 维护规范(三栏分类)
- git submodule 引用本项目(
extensions/huashan-ai-code/vendor/evolving-agent),URL 指向正式 git remote 而非 file:// -
scripts-build/prepare-skill.sh:调用 submodule 内pack_for_ide.py产出资源 + 内嵌 verify(cli_protocol + min_ide_version 兼容性校验) -
package.json增加skill:prepare脚本(不再分 fetch / verify) -
resources/evolving-agent/加入.gitignore,submodule 路径不要 ignore - esbuild plugin 把资源固化到
dist/evolving-agent/,注意fs.cpSync({verbatimSymlinks: true})保留便携 Python 的相对 symlink -
PythonBridge:仅 detect,不下载 -
SkillLoader:启动时 verify + 调bridge.exec(['meta','--skill-content','--mode','ide'])一行加载 IDE 适配版(不要自己写正则转写) -
system.ts把 SKILL.ide.md 内容注入 prompt,外层简单<EXTREMELY_IMPORTANT>标签即可(SKILL.ide.md 自带<EVOLVING_AGENT_IDE_MODE>banner) -
EvolvingAgentTool+EditToolGuard实现 Tool 硬约束 -
verify-pack-layout.sh防回归:确保便携 Python 不进任何 asar archive
- 物理断网测试:拔网线后 IDE 启动 + 完整跑通编程任务
- 故意删除便携 Python:bootstrap.py 应报错
PYTHON_NOT_FOUND - 故意篡改 skill 文件:
run.py verify应检测到 checksum 不一致 - LLM 跳过 evolving_agent:直接 write_file 应被拦截并返回 ToolError
- 本项目 bump 到 vX.Y.Z:submodule update + commit →
npm run skill:prepare自动校验 → build 通过 - 故意降低 IDE 版本:低于 min_ide_version 时
prepare-skill.sh内嵌的 verify 应失败
| 决策点 | 选择 | 原因 |
|---|---|---|
| 运行时是否允许联网 | 否 | 用户场景明确要求 |
| 便携 Python 在哪一层下载 | IDE build 期 | 运行时离线 |
| 资源拉取方式 | git submodule(实际落地) | 锁 commit hash 比 tag 更确定;不依赖 release artifact 在线可用;私有部署友好。早期设计的 GitHub Release tar.gz 路径已不再使用 |
| skill 文档同步速度 | 跟随 IDE release 周期 | 运行时无网络,无法热更 |
| 集成升级成本目标 | 一行 git submodule update |
通过 prepare-skill.sh + cli_protocol 兼容承诺实现 |
| 多平台 skill 适配源 | 本项目唯一 | v1.1.0 起新增 SKILL.ide.md + meta --mode ide;IDE 侧零正则转写。新增集成方时也在本项目加 mode,下游永远只读取 |
| Tool 拦截层级 | IDE 主进程 | 物理硬约束,LLM 无法绕过 |
| 完整性校验 | manifest.json + run.py verify | 防止安装包损坏/篡改 |
| Symlink 处理 | 本项目 shutil.copytree(symlinks=True) + IDE fs.cpSync({verbatimSymlinks: true}) |
python-build-standalone 含相对 symlink 链;默认行为会解引用为绝对路径,导致下游重复 copy 时 EINVAL |
| 阶段 | 任务 | 估算 |
|---|---|---|
| 本项目 | runtime.json + bootstrap.py(detect-only) | 0.5 天 |
| pack_for_ide.py | 1 天 | |
| run.py 新增 version/meta/verify | 0.5 天 | |
| GitHub Actions release.yml | 0.5 天 | |
| 文档 + 测试 | 1 天 | |
| 小计 | 3.5 天 | |
| IDE 侧 | build/fetch-skill.sh + verify-skill.sh | 0.5 天 |
| PythonBridge + SkillLoader | 1 天 | |
| System prompt 注入 | 0.5 天 | |
| EvolvingAgentTool + EditToolGuard | 1 天 | |
| UI(状态栏 + 任务面板) | 1.5 天 | |
| 联调 + 离线测试 | 1.5 天 | |
| 小计 | 6 天 | |
| 总计 | ~10 人天 |
- python-build-standalone(Astral 维护) — 便携 Python 发行版
- pip download 离线打包 — vendor wheel 准备
- Cline 自定义指令文档 — IDE 注入参考
- GitHub Actions Release Workflow — 多平台预构建
- 本项目入口:
scripts/run.py,子命令清单见.opencode/references/commands.md