Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

MyCode Web

mycode-web 是独立于 MyCode Core 的 Web 与 deployment layer。它不 import、复制或修改 MyCode Python package,而是通过 MyCode Runtime JSONL Protocol v1 的 mycode runtime --jsonl --continue 驱动 Core。Web repo 与 MyCode Core repo 独立维护。

真实 Provider API Key 只由 Host/FastAPI 持有。Docker Sandbox 仅获得内部 Relay token、Relay URL、模型名,以及 Host 明确传入的 MyCode Runtime 参数;Provider 凭据不会进入 Sandbox。

核心能力

  • Multi-session:一个 Web User 可以创建、列出、切换和删除多个 Session。
  • Session isolation:每个 Session 独立拥有 MyCode state、Runtime、Permission、SSE 和 Console history;同一 Web User 的 Session 共享一个 Workspace。
  • Warm Runtime:进入 Session 后后台预热 Sandbox 与长期运行的 mycode runtime --jsonl --continue。
  • Workspace:支持上传、文件树、文本预览、文件/目录删除以及文件和完整 Workspace 下载。
  • Workspace 自动刷新:Agent 修改文件后,页面自动刷新文件树和当前 Preview。
  • Agent Console:同时提供实时增量输出和可持久化、可恢复的聚合历史。
  • Context inspection / Compact:通过 Runtime JSONL Protocol v1 查询 Context 统计,并串行执行手动 Compact。
  • Permission interaction:Browser 提供独立的 Allow/Reject UI,并通过结构化 permission_request / permission_response 完成同步决策。
  • Runtime Pool / FIFO Queue:在受控资源上管理并发 Session Runtime。
  • Project MCP Trust:Sandbox 启动期间通过 mcp_trust_request / mcp_trust_response 请求项目级 MCP 信任,不展示 secret value。
  • Session persistence:Runtime 回收后保留 Session 数据,后续通过 mycode runtime --jsonl --continue 恢复。
  • Sandbox compatibility smoke:Sandbox rebuild 后以无网络、临时状态执行 runtime_ready → close → runtime_closed 启动握手检查。

架构

Browser /web/
  -> Vue 3 + Vite
  -> FastAPI /web/api/*
  -> RuntimeManager -> JsonlRuntimeAdapter
  -> Docker Sandbox: `mycode runtime --jsonl --continue`
  -> FastAPI OpenAI-compatible Relay
  -> Provider

用户 Workspace 与 Session 私有数据分别位于:

data/users/<user_id>/workspace/
data/sessions/<session_id>/mycode_state/

Sandbox 挂载当前 Web User 的 Workspace 和当前 Session 的 mycode_state。不同 User 的 Workspace 路径彼此隔离;删除 Session 只删除该 Session 的私有数据,不删除 User Workspace。

Session 与 Runtime 生命周期

HttpOnly Cookie 只标识 Web User。一个 User 可以拥有多个 Session,并通过 /web/?session=<session_id> 选择当前 Session。首次访问没有 Session 时,前端自动创建一个;URL 未指定 Session 时进入最近活跃的 Session。

Session 支持 create、list、switch 和 delete。进入或切换 Session 后,前端立即调用 activate,并在后台启动或复用 Sandbox 与 mycode runtime --jsonl --continue。浏览器刷新不会停止 Runtime,也不会创建重复 Agent process。

Runtime 状态包括:starting、idle、running、waiting_permission、waiting_mcp_trust、queued、stopped 和 error。

Runtime Pool、FIFO Queue 和调度锁都位于单个 FastAPI 进程内。最大 active Sandbox、单个 Web User 的 active Sandbox 配额、Queue 上限和 idle TTL 由配置决定,admission 遵循以下规则:

  1. 全局 capacity 和该 Web User 的 active Sandbox 配额都未满时,为 Session activate Runtime;
  2. 全局 capacity 已满但存在 idle Runtime,且用户配额允许时,驱逐最久未活动的 idle Runtime;
  3. 没有可立即启动的 Runtime 时,新请求进入有界 FIFO Queue;队列 handoff 会跳过当前用户配额已满的候选,避免队首阻塞。

starting、idle、running、waiting_permission、waiting_mcp_trust 和 stopping 都占用 Sandbox capacity。关闭期间即使本地进程已经退出,只要容器回收或 generation teardown 尚未完成,slot 仍被保留。idle、等待 Permission 和等待 MCP Trust 都受 inactivity TTL 约束;等待交互超时会清除 pending request、停止 Runtime,确认回收后才释放 slot。Runtime 也可因 capacity eviction、explicit stop、process exit 或 lifecycle cleanup 停止,但这些操作不会删除 Workspace、mycode_state 或 SQLite Session metadata。再次 activate 时会启动新 Sandbox,并通过 mycode runtime --jsonl --continue 恢复 Session。

Queue Handoff 和 idle eviction 的目标预留绑定来源 Session 及其 Runtime generation:来源尚未释放时,全局名额只计一次,目标必须等待来源回收成功后才能启动。跨用户交接同时保留旧用户占用和新用户预留,避免绕过任一用户的配额。异步启动任务也绑定目标 generation,取消后的旧交接不能启动同一 Session 后来提交的新任务或发送旧内容。

Runtime resource ownership contract:Web Session 持有 Session metadata、Workspace、Console/EventHub、turn reservation,以及 Browser Terminal connection/terminal demand;这些不属于某个 Runtime generation。每个 generation 独占 Sandbox process/container、JSONL reader、relay token、watcher ownership 和 Terminal PTY shell,并且 generation teardown 完成前同一 Session 禁止启动下一 generation。Agent turn 的 active_turn_id、busy 和 pending Permission 属于 turn;MCP Trust 是可发生在 runtime_ready 之前的 startup interaction。Generation final status 只在 process/reader/token/watcher/terminal cleanup 完成后发布。

Runtime 正常关闭先发送 JSONL close,在 5 秒内等待 runtime_closed 和进程退出;关闭不可用或超时后使用 terminate/kill 兜底,每次进程退出等待最多 5 秒。Docker 命令最多等待 10 秒;Web 按 managed label、Session label 和精确容器名查询并回收对应容器,再次查询确认容器消失。中间步骤失败但最终回收成功不会产生 runtime_cleanup_failed。若进程仍存活或容器回收无法确认,则保留清理目标和 capacity 占用,禁止同一 Session 重启;后续 activate、发送消息、stop 或 delete 会先重试回收,确认成功后才继续原操作。回收仍失败时 delete 返回 503 并保留 Session 数据,定时清理跳过该 Session、下轮再试。

turn_finished 只有携带非空且与当前执行 Turn 完全一致的 turn_id 才能完成该 Turn、刷新 Console buffer 和发布 idle;缺失、错配、重复以及 finalizing 期间的完成事件都被忽略。每个 generation 保留最近 128 个已完成 Turn ID 用于重复识别,已知重复与关闭期间的迟到事件只记 debug;缺失/非法 ID、未知 ID 错配、无活动 Turn 和尚未执行的 Turn 则记录带原因、Session、generation、收到及当前 Turn ID 的协议 warning。异常不会被当作成功,也不会终止当前任务;这些诊断仅写服务端日志。Session 级 Runtime 警告使用独立提示展示,不归入最近一轮的执行区块;历史 runtime_cleanup_failed 记录也按 Session 提示恢复。

Workspace watcher 跟随 active Runtime 生命周期,而不是永久附着在保留的 Session 上。Runtime starting/active 时 watcher 启动;Runtime 被回收或停止时 watcher 停止;Session 再次 activate 时 watcher 重启;Session delete 和 application shutdown 会清理 watcher。

页面初始化

进入 Session 后,Workspace tree 和 activate 可以与 bootstrap 请求并行执行。Runtime metadata、Console history 和 fresh SSE 使用明确的 bootstrap cursor 协议:

  1. 前端先请求 GET /sessions/{id};
  2. FastAPI 先捕获该 Session 的 EventHub latest_id 作为 event_cursor,再读取 Runtime snapshot:runtime_status、active_turn_id、pending_permission 和 pending_mcp_trust;
  3. 前端使用 bootstrap response 的 event_cursor 作为 /events?after=<event_cursor> 的 fresh SSE 起点;
  4. 前端独立请求 /console,它只返回 SQLite stable Console history,不再返回或维护 SSE cursor;
  5. bootstrap cursor 之后产生的 replayable event 由 SSE replay/live stream 补齐。

cursor 在 Runtime snapshot 之前捕获,因此 Permission / MCP Trust 请求要么已经存在于 bootstrap snapshot,要么会作为 cursor 之后的 replayable SSE event 到达。两者短暂重复是允许的,但事件不能丢失。

网络断线重连时,浏览器携带的 Last-Event-ID 用于补发错过的 replayable stable events;Last-Event-ID 的优先级高于 query parameter after。

Workspace 与安全边界

Workspace 支持 upload、file tree、preview、download、delete file 和 delete directory。Host 使用 watchfiles 监听真实 Workspace;发生变化时发布该 Session 的 workspace_changed SSE,前端 debounce 后刷新文件树和当前 Preview。

Symlink/junction 安全语义:

  • tree/stats 可以识别并显示 link,但不会 follow link target;
  • symlink 和 dangling symlink 可以删除,删除只作用于 link 本身;
  • 删除包含 symlink 的普通目录时,不会递归进入 link target;
  • Workspace 外部 target 不会因 Workspace 删除操作被修改;
  • read、preview 和 download 拒绝通过 symlink 访问 target;
  • ZIP upload 拒绝 archive 中的 symlink entry。

JSONL Runtime、Console 与 SSE

Web 与 Core 之间使用正式的 JSONL machine protocol:

  • Turn 输入为 {"version":1,"type":"turn","turn_id":"...","content":"..."}。content 保留原始多行消息,不再折叠成 CLI 单行输入。
  • Permission 使用 permission_request ↔ permission_response;Web 的 deny 映射为 wire decision reject,once、task、session 保持同名。
  • Project MCP Trust 使用 mcp_trust_request ↔ mcp_trust_response。请求在 runtime ready 之前也可能出现,Web 会保持 waiting_mcp_trust 并等待 Browser Allow/Reject;展示时只使用 Core 提供的 alias、transport、command/args、env_keys、url_template、destination 和 header_keys 等安全字段。
  • Core stdout 只由 JSONL adapter 解析;Core stderr 由独立 reader 记录为 bounded diagnostic,不作为 protocol event。

Core agent_event 由 RuntimeManager 投影为 Browser 使用的结构化事件:text_delta 用于 assistant streaming,tool_call / tool_result 用于 execution group,其他支持事件保留结构化字段。console_live 与 raw agent_event 都是 live-only,不进入 EventHub replay history;稳定的 console_event 由 ConsoleRecorder 写入 SQLite,负责刷新后的历史恢复。过大的 tool result 在 Web projection 层做有界摘要,不改变 Core wire message。

SSE replay buffer 位于 FastAPI 进程内。runtime_status、Permission/MCP Trust 生命周期事件、runtime_warning、runtime_error 和 console_event 等稳定事件可以 replay;raw agent_event 和 console_live 只发送给当前 live subscriber。Fresh connection 使用 Session bootstrap event_cursor,reconnect 使用 Last-Event-ID,且 Last-Event-ID 优先于 after。服务重启后 replay buffer 不恢复;Console 历史恢复依赖 SQLite,而不是 SSE history。

环境要求

  • Python 3.11+
  • uv
  • Docker Desktop 或支持 BuildKit named contexts 的 Docker
  • Node.js 20.19+ 或 22.12+ 与 npm
  • OpenAI Chat Completions compatible Provider

配置与启动 FastAPI

全新本地安装时,在 mycode-web 根目录执行:

Copy-Item .\.env.example .\.env
uv sync
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --env-file .env

Copy-Item 仅适用于全新本地安装;升级已有服务器时不要用 .env.example 覆盖生产 .env。

.env 至少设置:

MYCODE_PROVIDER_API_KEY=真实密钥
MYCODE_PROVIDER_BASE_URL=https://provider.example.com/v1
MYCODE_MODEL=模型名

以下是 repo default 示例值,不是固定架构参数,production 可以通过 .env 覆盖:

SANDBOX_MAX_ACTIVE=2
SANDBOX_QUEUE_MAX=20
SANDBOX_MAX_ACTIVE_PER_USER=5
SANDBOX_MEMORY_LIMIT=640m
SANDBOX_MEMORY_SWAP_LIMIT=1g
SANDBOX_CPUS=1.0
SANDBOX_PIDS_LIMIT=256
SANDBOX_IDLE_TTL_SECONDS=7200
RUNTIME_SWEEP_INTERVAL_SECONDS=60
SESSION_RETENTION_SECONDS=1209600
SESSION_CLEANUP_INTERVAL_SECONDS=3600

SANDBOX_IDLE_TTL_SECONDS 只回收 Runtime 进程/容器;SESSION_RETENTION_SECONDS 根据 User/Session 的 SQLite last_active_at 清理过期 Session 数据和 metadata,并仅在 User 已无 Session 且达到 retention 时删除其 Workspace。不要提交 .env。

Runtime Pool、FIFO Queue、Runtime locks 和 SSE replay state 都是单 FastAPI 进程内状态,因此当前部署要求 --workers 1:

uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1

多 Worker 部署需要外置共享 runtime coordination、Queue 和 event state;当前实现不依赖 Redis 等共享协调服务。

验证 FastAPI:

Invoke-RestMethod http://127.0.0.1:8000/web/api/health

构建 Sandbox Image

Dockerfile 使用名为 mycode 的 BuildKit context,只从外部 MyCode source 复制 README.md、pyproject.toml、uv.lock 和 mycode/。Web repo 不保存 MyCode source 副本;MyCode project dependencies 和 uv.lock 是 Sandbox dependency source of truth。

Windows:

.\scripts\build-sandbox.ps1 -MyCodeSource ..\mycode-project

Linux:

./scripts/build-sandbox.sh ../mycode

也可直接执行:

docker buildx build --load `
  --build-context mycode=..\mycode-project `
  --file .\docker\Dockerfile.sandbox `
  --tag mycode-sandbox:dev .

MyCode source path 由命令参数传入,不写死本机路径。Dockerfile 按 Python/uv -> MyCode lock dependencies -> system tools -> MyCode source 分层,源码变化不会使 dependency layer 失效。

安装并启动 Vue

Set-Location .\frontend
npm install
npm run dev

浏览器打开 http://localhost:5173/web/。Vite base 固定为 /web/,并把 /web/api/* 代理到 http://127.0.0.1:8000。

Production Deployment

Production topology:

Browser
  -> Nginx /web/
  -> Vue static assets / FastAPI /web/api/*
  -> Docker Sandbox
  -> mycode runtime --jsonl --continue
  -> FastAPI internal Relay
  -> Provider

服务器 Nginx 模板位于 deploy/nginx/mycode.conf,最终公网入口为 https://mycode.icu/web/。该模板包含 HTTP→HTTPS、SPA fallback、SSE 无缓冲、WebSocket Upgrade,以及旧 /mycode 路径到 /web 的兼容跳转。Linux Docker、Browser 和真实 Provider E2E 仍需按部署 checklist 单独执行,不以本地 fake tests 代替。

HTTPS 部署与 Phase 2 升级

首次 HTTPS bootstrap 必须先确保 ACME challenge 可用;证书不存在时不要先安装引用证书文件的最终 443 配置:

  1. 将 mycode.icu 的 DNS A record 指向服务器公网 IP,并确认 TCP 80/443 对外开放。

  2. 保持现有可工作的 HTTP-only Nginx server(如果服务器已有适合 Certbot 的 HTTP server,直接复用)。

  3. 使用 Docker Certbot + webroot 先申请证书(挂载 /etc/letsencrypt 和 /var/www/certbot):

    docker run --rm \
      -v /etc/letsencrypt:/etc/letsencrypt \
      -v /var/www/certbot:/var/www/certbot \
      certbot/certbot certonly --webroot \
      --webroot-path /var/www/certbot -d mycode.icu
  4. 确认 /etc/letsencrypt/live/mycode.icu/fullchain.pem 和 /etc/letsencrypt/live/mycode.icu/privkey.pem 均已存在。

  5. 再安装 deploy/nginx/mycode.conf,执行 sudo nginx -t,然后 sudo systemctl reload nginx。

  6. 确认自动续期 timer/cron 调用同一 Docker Certbot 挂载,并执行:

    docker run --rm \
      -v /etc/letsencrypt:/etc/letsencrypt \
      -v /var/www/certbot:/var/www/certbot \
      certbot/certbot renew --dry-run
  7. 验证 https://mycode.icu/web/ 和 https://mycode.icu/web/api/health。

生产 .env 升级

Phase 2 升级时编辑服务器上已有的 /opt/mycode-web/.env,不要执行 cp .env.example .env 或用 .env.example 覆盖生产配置。保留现有 Provider、Sandbox pool、queue、memory 和 TTL 配置;重点确认或增加:

MYCODE_COOKIE_SECURE=true
MYCODE_RELAY_BASE_URL_FOR_SANDBOX=http://host.docker.internal:8000/web/api/relay/v1

如果没有显式设置 Relay base URL,则使用当前代码中的新默认值。.env.example 只用于全新安装参考。

旧 Web 数据

旧 /mycode 的 Cookie 使用 Path=/mycode,不会随 /web/ 请求发送,因此首次打开 /web/ 会创建新的 Web User。旧测试数据不做 cookie、User 或 Session migration;上线时可按需清理,不增加迁移代码。

清理前先按当前源码解析真实路径,不要凭空假设数据目录:

cd /opt/mycode-web
uv run --env-file .env python -c 'from app.config import ServerSettings; s=ServerSettings(); print("data_dir=", s.data_dir); print("database=", s.database_path); print("users=", s.users_dir); print("sessions=", s.sessions_dir)'

当前代码语义是 MYCODE_SERVER_DATA_DIR(未设置时为服务工作目录下的 ./data)、数据库 <data_dir>/web-v2.sqlite3、用户 Workspace <data_dir>/users/<user_id>/workspace,以及 Session 私有数据 <data_dir>/sessions/<session_id>/mycode_state。Session 删除只删除私有数据,不删除用户 Workspace;Workspace retention 也按 User 维度处理。确认打印路径确实是旧 Web 数据后,再在服务器执行:

# 将下面两项替换为上一步确认过的真实输出路径。
DATA_DIR=/path/from-the-command
DB="$DATA_DIR/web-v2.sqlite3"
USERS="$DATA_DIR/users"
SESSIONS="$DATA_DIR/sessions"
test -f "$DB" && test -d "$SESSIONS" && test -d "$USERS"

sudo systemctl stop mycode-web
# 可选但建议:先备份 "$DB"、"$SESSIONS" 和 "$USERS"。
sudo cp -a "$DB" "$DB.phase2-backup"
sudo cp -a "$USERS" "$USERS.phase2-backup"
sudo rm -f "$DB"
sudo rm -rf "$SESSIONS"
sudo rm -rf "$USERS"
sudo systemctl start mycode-web

只在确认目标是旧 Web 测试数据后执行删除;FastAPI 会在启动时自动创建新 schema 和新 Session 目录。不要在本机 Windows 环境执行上述生产数据清理;证书、私钥和生产 .env 也不提交到仓库。

测试

uv sync
.\.venv\Scripts\python.exe -m pytest .\tests -q
Set-Location .\frontend
npm run build

Server tests 使用临时 SQLite/Workspace 和 fake Sandbox,不访问真实 Provider,也不启动 Docker。Linux production 与 Browser E2E 属于独立的部署验证,不应由本地 fake tests 替代。

当前限制

  • 身份只有随机 HttpOnly Cookie,没有正式账号或登录系统。
  • Runtime Pool、FIFO Queue、scheduler locks 和 SSE replay buffer 是单 FastAPI 进程内状态。
  • 服务重启后 Queue 和 SSE replay history 不恢复;Console history 由 SQLite 恢复。
  • Core runtime 使用正式的结构化 JSONL machine protocol;stdout 是 protocol,stderr 是 diagnostic。
  • FastAPI 不接管 crash 前遗留的旧 Sandbox process;startup 只按 mycode-web.managed=true label 清理 orphan Sandbox。
  • Orphan cleanup 不删除 Workspace、mycode_state 或 SQLite Session;后续可通过 mycode runtime --jsonl --continue 恢复。

设计原因见 docs/adr/0001-web-demo-cli-sandbox-relay.md,实现和验证记录见 docs/实施记录.md。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages