Skip to content

Commit efe1311

Browse files
committed
Merge branch 'refactor/thin-channel-thick-runner' into fork-main-tmp
2 parents f731897 + 042879b commit efe1311

325 files changed

Lines changed: 36465 additions & 15419 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ APP_ENV=production
1010
DEBUG=false
1111
SECRET_KEY=change-me-in-production-use-long-random-string
1212

13+
# Fleet 鉴权(ADR-0005):所有 /api 路由的静态 Bearer Token。
14+
# 留空 = 关闭鉴权(开发姿态,启动时仅允许绑定 localhost);
15+
# 非 localhost 绑定(如 Docker 的 0.0.0.0)必须设置,否则启动直接拒绝。
16+
# 前端取值:构建期 VITE_API_AUTH_TOKEN,或浏览器 localStorage 'apiAuthToken'(优先)。
17+
# MCP server / CLI(backend/mcp_server.py、backend/cli.py)读同名环境变量。
18+
# 一旦设置此值,边缘 Agent(backend/agent_server.py)也必须设置同值的 AGENT_API_TOKEN
19+
# (或直接复用 API_AUTH_TOKEN)——覆盖 HTTP 注册(/api/v1/nodes/register)和
20+
# WS 反向通道(/api/v1/nodes/ws、/api/v1/browsers/agents/ws)两条握手路径。
21+
API_AUTH_TOKEN=
22+
1323
# 服务端口(两种启动模式均生效)
1424
API_PORT=8031 # API 服务对外端口
1525
FRONTEND_PORT=8030 # 前端页面对外端口
@@ -39,6 +49,32 @@ TASK_EXECUTOR=local
3949
# ODP_INGEST_URL=http://odp-ingest:8040
4050
# III_URL=ws://iii-engine:49134
4151

52+
# ── ODP 数据面(Python 侧:backend/pipeline/odp_client.py)───────────────────
53+
# ODP ingest(Rust 热路径)的 HTTP 地址。留空 = 不转发(走 sqlite legacy 路径)。
54+
# ODP_INGEST_URL=http://odp-ingest:8040
55+
# ODP HTTP 请求超时(秒)。默认 10。
56+
# ODP_INGEST_TIMEOUT=10
57+
# ODP 转发失败时的语义(backend/pipeline/storer.py):
58+
# unset/false(默认,fail-open)— 转发异常仅 log warning,继续走 sqlite legacy 路径,不中断采集
59+
# true/1/yes(fail-closed) — 转发异常直接 raise,中断该次采集任务
60+
# ODP_INGEST_REQUIRED=false
61+
62+
# ── ODP 数据面(Rust 侧:odp-rs/crates/*)──────────────────────────────────
63+
# odp-store 的 Postgres 连接串;未设置时回退读 DATABASE_URL(见 odp-store/src/main.rs)。
64+
# ODP_DATABASE_URL=postgresql://opencli:opencli_secret@postgres:5432/opencli_admin
65+
# odp-store 每批写入 Postgres 的最大行数。默认 500。
66+
# ODP_STORE_BATCH_SIZE=500
67+
# Redis Stream 消费者组名(odp-bus)。默认 "odp-store"。
68+
# ODP_BUS_GROUP=odp-store
69+
# Redis Stream 消费者实例名(odp-bus)。默认 "<hostname>-<pid>",同组内建议每实例唯一。
70+
# ODP_BUS_CONSUMER=
71+
# odp-ingest 允许无 Redis bus 启动的显式开关(commit d1f33d9)。
72+
# 默认关闭 = fail-fast:没有可用的 Redis bus(ODP_REDIS_URL/REDIS_URL 未设置或不可达)时,
73+
# odp-ingest 直接拒绝启动 —— 没有 bus 就没有持久化,装作 202 accepted 是在撒谎。
74+
# =1 才是显式的 dev/test-only 例外:仅在内存里跑,事件全部当作未持久化处理(见 handlers.rs),
75+
# 绝不能在生产/NAS 部署中打开。
76+
# ODP_INGEST_ALLOW_NO_BUS=0
77+
4278
# 采集模式
4379
# local — 默认,API 直接驱动本地 Docker 网络内的采集器(Chrome、爬虫等)
4480
# agent — 分布式边缘采集;采集任务分发到远端 Agent 节点执行
@@ -67,6 +103,11 @@ CELERY_RESULT_BACKEND=redis://redis:6379/1
67103
API_KEY_ENABLED=false
68104
API_KEY=
69105

106+
# CLI 渠道二进制白名单(ADR-0005,审计 P0-4)。cli 渠道会执行任意二进制,
107+
# 只有列在这里的二进制才允许运行;逗号分隔完整路径/名称,留空(默认)= 全部拒绝。
108+
# 例:CLI_CHANNEL_ALLOWED_BINARIES=/usr/bin/mycli,C:\tools\other.exe
109+
CLI_CHANNEL_ALLOWED_BINARIES=
110+
70111
# 邮件发送(通知渠道使用)
71112
SMTP_HOST=
72113
SMTP_PORT=587

.github/workflows/ci.yml

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ jobs:
3030
- name: Install dependencies
3131
run: npm ci --legacy-peer-deps
3232

33+
- name: Typecheck
34+
run: npm run typecheck --if-present
35+
3336
- name: Test
3437
run: npm run test
3538

39+
- name: Lint (design token gate)
40+
run: npm run lint
41+
3642
- name: Build
3743
run: npm run build
3844

@@ -63,6 +69,9 @@ jobs:
6369
- name: Install dependencies
6470
run: npm ci
6571

72+
- name: Typecheck
73+
run: npm run typecheck
74+
6675
- name: Build
6776
run: npm run build
6877

@@ -93,5 +102,112 @@ jobs:
93102
- name: Backend syntax check
94103
run: python -m compileall backend
95104

96-
- name: Unit tests
97-
run: pytest tests/unit -m "not live" --no-cov
105+
# Coverage visibility, not a gate: pyproject.toml bakes in
106+
# `--cov-fail-under=80` via [tool.pytest.ini_options].addopts, but the
107+
# current suite sits at ~70% total. Enforcing 80% here would turn CI red
108+
# immediately for pre-existing gaps, not for anything this PR changes.
109+
# `--cov-fail-under=0` overrides the pyproject default (last CLI value
110+
# wins) so the job stays green while the report is printed every run.
111+
# TODO(ratchet): once real coverage is measured for a few weeks, raise
112+
# this floor incrementally (e.g. 70 -> 75 -> 80) instead of jumping
113+
# straight to the pyproject target.
114+
- name: Unit tests (with coverage report)
115+
run: pytest tests/unit -m "not live" --cov=backend --cov-report=term-missing --cov-fail-under=0
116+
117+
migrations:
118+
runs-on: ubuntu-latest
119+
name: Alembic Migrations
120+
services:
121+
postgres:
122+
image: postgres:16-alpine
123+
env:
124+
POSTGRES_DB: opencli_admin
125+
POSTGRES_USER: opencli
126+
POSTGRES_PASSWORD: opencli_secret
127+
ports:
128+
- 5432:5432
129+
options: >-
130+
--health-cmd "pg_isready -U opencli"
131+
--health-interval 5s
132+
--health-timeout 3s
133+
--health-retries 10
134+
env:
135+
# backend/config.py Settings.database_url (case-insensitive env,
136+
# see backend/migrations/env.py which overrides alembic.ini's
137+
# sqlalchemy.url from settings.database_url at runtime).
138+
DATABASE_URL: postgresql+asyncpg://opencli:opencli_secret@localhost:5432/opencli_admin
139+
steps:
140+
- name: Checkout
141+
uses: actions/checkout@v4
142+
143+
- name: Setup Python
144+
uses: actions/setup-python@v5
145+
with:
146+
python-version: "3.11"
147+
148+
- name: Install dependencies
149+
run: |
150+
python -m pip install --upgrade pip
151+
python -m pip install -e .[dev]
152+
153+
- name: Wait for Postgres
154+
run: python -c "import time; time.sleep(2)"
155+
156+
- name: alembic upgrade head
157+
run: alembic upgrade head
158+
159+
# Catches broken downgrade() bodies / non-reversible migrations before
160+
# merge: a chain that only ever gets tested via upgrade() can silently
161+
# rot (dropped columns with no re-add, wrong op order, etc.).
162+
- name: alembic downgrade/upgrade smoke test
163+
run: |
164+
alembic downgrade -1
165+
alembic upgrade head
166+
167+
# AUDIT follow-up (c): the cursor concurrency tests run on SQLite in the
168+
# backend job, where `SELECT ... FOR UPDATE` is a silent no-op — they
169+
# prove the code path but not that the lock serializes writers. Re-run the
170+
# Postgres-gated variant here (schema already at head above, DATABASE_URL
171+
# is Postgres so the test's skip-gate activates), where the row lock is
172+
# actually enforced, so a regression in cursor_store's per-source locking
173+
# is caught. --no-cov: this is a targeted single-test step, not a coverage
174+
# run (pyproject's addopts would otherwise fail it under the 80% gate).
175+
- name: cursor FOR UPDATE locking (Postgres)
176+
run: pytest tests/unit/pipeline/test_db_cursor_store.py -k postgres --no-cov -p no:cacheprovider
177+
178+
cargo:
179+
runs-on: ubuntu-latest
180+
name: ODP Rust (odp-rs)
181+
defaults:
182+
run:
183+
working-directory: odp-rs
184+
steps:
185+
- name: Checkout
186+
uses: actions/checkout@v4
187+
188+
- name: Setup Rust toolchain
189+
uses: dtolnay/rust-toolchain@stable
190+
with:
191+
components: clippy
192+
193+
- name: Cache cargo registry + target
194+
uses: actions/cache@v4
195+
with:
196+
path: |
197+
~/.cargo/registry
198+
~/.cargo/git
199+
odp-rs/target
200+
key: ${{ runner.os }}-cargo-odp-rs-${{ hashFiles('odp-rs/Cargo.lock', 'odp-rs/**/Cargo.toml') }}
201+
restore-keys: |
202+
${{ runner.os }}-cargo-odp-rs-
203+
204+
# No live Redis/Postgres here: odp-bus/odp-ingest/odp-store tests are
205+
# unit-level (dedup logic, contract (de)serialization, stream naming).
206+
# If integration tests that need a real broker/DB are added later, gate
207+
# them behind a feature flag or #[ignore] + a service-container job,
208+
# the same way `pytest -m live` is deselected in the backend job.
209+
- name: cargo test --workspace
210+
run: cargo test --workspace
211+
212+
- name: cargo clippy --workspace -- -D warnings
213+
run: cargo clippy --workspace --all-targets -- -D warnings

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# opencli-admin
2+
3+
## 前端样式 — 必读
4+
5+
`frontend/src` 下任何 UI 前, 先读 [docs/DESIGN_SYSTEM.md](docs/DESIGN_SYSTEM.md)(锁定版设计系统, 顶部有 30 秒速查表)。
6+
7+
硬规则速记:
8+
- 背景只用 `bg-ops-black/panel/raised` 三级, 禁手写近黑 hex
9+
- 灰阶只用 zinc; 交互色只用 primary; 状态色 emerald/amber/red/sky/violet
10+
- 字号禁 `text-[Npx]` 任意值, 用 `text-2xs`(11px)/`text-3xs`(10px)
11+
- 圆角: 控件 `rounded-xs`, 面板 `rounded-md`; 阴影用 `shadow-panel/overlay/drag`
12+
- 缺 ui 原语用 `npx shadcn@latest add <x>` 拉, 禁手搓

CONTEXT.md

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ OpenCLI Admin is an operations console for collection work that needs browser se
44

55
## Language
66

7-
**Collection Operations Console**: The primary operator surface for turning collection work into captured, triaged, owned, stateful, and closed work. It is the product shape that contains Run Inbox, Data Sources, Live Collection View, and Diagnostic Canvas without making any one visualization the whole product.
8-
_Avoid_: Dashboard wall, canvas-first app
7+
**Collection Operations Console**: The primary operator surface for turning collection work into captured, triaged, owned, stateful, and closed work. It is the product shape that contains Run Inbox, Data Sources, Live Collection View, and the Collection Canvas.
8+
_Avoid_: Dashboard wall
99

10-
**Collection Operations**: The operator-facing domain for deciding what should be collected, when collection should run, what recently happened, and which actions are currently safe. It groups Data Sources, Collection Plans, Recent Runs, and Node Actions without making a canvas the primary operating surface.
11-
_Avoid_: Source Workflow Workbench, canvas-first operations
10+
**Collection Operations**: The operator-facing domain for deciding what should be collected, when collection should run, what recently happened, and which actions are currently safe. It groups Data Sources, Collection Plans, Recent Runs, and Node Actions.
11+
_Avoid_: Source Workflow Workbench
1212

13-
**Diagnostic Canvas**: A secondary view for understanding relationships among collection entities when troubleshooting or explaining system state. It is not the default place to configure routine collection work.
14-
_Avoid_: Main workflow, primary operating surface
13+
**Collection Canvas**: The primary authoring surface for collection logic — the graph IS the program. Defining and editing what a source collects happens on the canvas; forms survive only as the inspector panel of a selected node. Absorbs the old Diagnostic Canvas's troubleshooting role.
14+
_Avoid_: Diagnostic Canvas (superseded 2026-07-02: canvas promoted from secondary diagnostic view to primary authoring surface), form-first configuration
1515

1616
**Live Collection View**: The operator-facing view of an active collection run as it happens, including streamed progress, rendered browser or pipeline state, and run-specific artifacts. It is anchored to a Recent Run, not to the default configuration surface.
1717
_Avoid_: Static task log, canvas-only monitoring
@@ -21,3 +21,46 @@ _Avoid_: Clock shop, always-on dashboard wall
2121

2222
**Run Inbox**: The operator-facing queue of collection runs that need observation, review, retry, acknowledgement, or dismissal. It treats a run as work to triage and close, not as a passive row in a log table.
2323
_Avoid_: Recent tasks table, static run history
24+
25+
### Control
26+
27+
**Advisory Mode**: The control-loop operating mode in which suggested actions are surfaced to the operator and recorded as evidence, but never executed.
28+
_Avoid_: dry-run mode, suggestion mode
29+
30+
**Automatic Mode**: The control-loop operating mode in which the Actuator may execute suggestions itself, opened per state class only when accumulated evidence justifies it.
31+
_Avoid_: autopilot, self-healing mode
32+
33+
**Actuator**: The component that carries out control actions against the collection system. It executes only whitelisted safe actions; everything else it downgrades.
34+
_Avoid_: executor, auto-fixer
35+
36+
**Evidence Ledger**: The durable record of every control suggestion and execution, together with the outcome later judged from post-decision measurements.
37+
_Avoid_: action log, audit trail
38+
39+
**Recovery Rate**: The share of judged suggestions whose triggering state later cleared. It is the quantified basis for opening Automatic Mode.
40+
_Avoid_: success rate, fix rate
41+
42+
**Require-Review Downgrade**: The policy that suggestions too dangerous to automate are executed only as "flag the source for human review", never as the suggested action itself.
43+
_Avoid_: blocked action, action rejection
44+
45+
**Control Cycle**: The background loop that periodically measures every source, decides, and — in Automatic Mode — acts. It runs regardless of whether any UI is open.
46+
_Avoid_: polling-driven control, frontend-triggered control
47+
48+
### Plan
49+
50+
**Plan**: A free multi-source graph on the Collection Canvas — any number of source nodes, transforms, merges, and sinks in one graph. The Plan is the program; a Data Source's legacy config is the degenerate single-node Plan.
51+
_Avoid_: per-source pipeline (rejected 2026-07-02 in favor of free graphs), workflow (overloaded)
52+
53+
**Two-Tier Attribution**: The observability contract for Plans. A source node is a real Data Source and its collection segment keeps per-source measurement unchanged (the control kernel is untouched); everything downstream of a merge belongs to Plan Health, and a shared-segment failure is never written into any source's state.
54+
_Avoid_: blaming all upstream sources, plan-only attribution
55+
56+
**Plan Health**: The health of a Plan's shared (post-merge) segments, measured per plan node, kept as its own dimension beside per-source measurement. A dedupe node failing does not make its upstream sources DEGRADED.
57+
_Avoid_: folding plan failures into source state
58+
59+
**Preset**: A packaged, one-click node configuration (e.g. an opencli site + command + format bundled as "雪球·热帖") registered in the node library and searchable from the palette. Presets are fed from backend adapter metadata, never hardcoded in the frontend. The advanced inspector still exposes raw parameters.
60+
_Avoid_: raw site/command dropdowns as the default UI
61+
62+
**Draft Source Node**: A source node placed on the Collection Canvas that does not yet reference a real Data Source. It renders visibly unmaterialized, cannot run, and does not enter the control loop until it is materialized into an entity.
63+
_Avoid_: fake canvas-only nodes that look real
64+
65+
**Dry-Run Preview**: The in-browser execution of a Plan on fixture data, explicitly labeled as a preview. It never produces collection results — the backend Plan executor is the only authoritative execution.
66+
_Avoid_: browser-side "real" runs, split-brain execution

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
## v0.4 前端基线
88

9-
- `frontend/` 是唯一生产前端主线:React + Vite。
9+
- `frontend/` 是唯一生产前端主线:React + Vite。改 UI 前先读 [docs/DESIGN_SYSTEM.md](docs/DESIGN_SYSTEM.md)(锁定版设计系统, 顶部 30 秒速查表)。
1010
- `experiments/next-web/` 只是 Next.js 实验壳,不接入默认 Docker、CI 或导航。
1111
- 默认 `docker compose up --build` 会构建仓库内的 `frontend/`,不会拉取旧的上游前端镜像。
1212
- 拓扑画布属于实验能力;设置 `VITE_ENABLE_TOPOLOGY_LAB=true` 后才开放 `/labs/topology`

backend/agent_runtimes/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Agent-runtime adapter package. Import registry to trigger auto-registration."""

0 commit comments

Comments
 (0)