feat: dual execution model (SSH CLI + HTTP pull)

- ExecutionMode enum: SshCli (orchestrator dispatches) | HttpPull (agent pulls)
- SSH CLI executor: spawn remote agents via ssh + CLI template
- Local subprocess as SSH special case (localhost)
- HostConfig with capability matching and load-based selection
- Dispatch loop: scan created tasks → select host → execute → update
- CliAdapterConfig: CLI templates for Codex and Claude Code
- Structured prompt construction (Issue → goal/constraints/validation)
- Output parsers: Codex JSON, Claude Code JSON, raw fallback
- TaskStatus::ReviewPending + review_count loop limit
- Forgejo webhook: pull_request (opened→review_pending, merged→completed)
- Forgejo webhook: push events (task/* branch → last_activity_at)
- HTTP API: dequeue only returns http_pull tasks
- HTTP API: status update only for http_pull mode
- Token auth config for http_pull agents
- Adapter module rewritten: AgentAdapter trait removed → config-driven CLI templates
- New fields: execution_mode, assigned_host, branch_name, pr_title, last_activity_at, review_count
- 30/30 tests pass
This commit is contained in:
Zer4tul 2026-05-12 14:07:56 +08:00
parent 1bc7580ecc
commit e39a16498c
34 changed files with 2541 additions and 1555 deletions

View file

@ -0,0 +1,72 @@
## MODIFIED Requirements
### Requirement: Dual execution mode
Orchestrator SHALL 支持两种任务执行模式:
1. **ssh_cli**Orchestrator 主动通过 SSH 在远程主机执行 Agent CLI。控制权在 Orchestrator上下文由 Orchestrator 构造并传入。
2. **http_pull**Agent 自主通过 HTTP API 拉取任务、执行、提交 receipt。控制权在 Agent适用于外部 AgentOpenClaw/Jeeves、Hermes 等)。
每个任务 SHALL 有 `execution_mode` 字段,由任务来源决定(默认 `ssh_cli`)。
#### Scenario: Forgejo Issue → ssh_cli task
- **WHEN** 任务从 Forgejo Issue 创建
- **THEN** execution_mode SHALL 为 `ssh_cli`
- **AND** Orchestrator SHALL 选择主机并主动执行
#### Scenario: External Agent using http_pull
- **WHEN** 外部 AgentJeeves需要执行任务
- **THEN** 任务 execution_mode SHALL 为 `http_pull`
- **AND** Agent 通过 `POST /api/v1/tasks/dequeue` 拉取并自行执行
### Requirement: Agent task dequeue (pull model — for http_pull mode only)
`POST /api/v1/tasks/dequeue` SHALL 仅适用于 `http_pull` 模式的任务。`ssh_cli` 模式的任务 SHALL 由 Orchestrator 直接调度,不经过 dequeue。
#### Scenario: Agent dequeues an http_pull task
- **WHEN** Agent 发送 `POST /api/v1/tasks/dequeue`
- **THEN** SHALL 仅返回 execution_mode = `http_pull` 的任务
- **AND** 排除已被 Orchestrator 调度的 ssh_cli 任务
### Requirement: Agent task status update (for http_pull mode)
`POST /api/v1/tasks/{task_id}/status` SHALL 适用于 `http_pull` 模式。`ssh_cli` 模式的状态 SHALL 由 Orchestrator 直接管理。
#### Scenario: ssh_cli task status managed by Orchestrator
- **WHEN** 任务 execution_mode = `ssh_cli`
- **THEN** 状态变更 SHALL 由 Orchestrator 在 SSH 执行过程中自动更新
- **AND** Agent 不需要调用 status update API
### Requirement: Single task detail query
Orchestrator SHALL 提供 `GET /api/v1/tasks/{task_id}` 返回单个任务详情,两种执行模式通用。
#### Scenario: Query task detail
- **WHEN** 发送 `GET /api/v1/tasks/org/repo#42`
- **THEN** 返回任务完整信息 JSON包含 execution_mode、assigned_hostssh_cli或 assigned_agent_idhttp_pull
### Requirement: Agent authentication (http_pull mode)
`http_pull` 模式的 Agent 调用 API 时 SHALL 携带 token。`ssh_cli` 模式不需要 Agent 认证(由 Orchestrator 直接管理)。
#### Scenario: ssh_cli mode — no Agent auth needed
- **WHEN** 任务 execution_mode = `ssh_cli`
- **THEN** Agent 不参与 API 调用,无需认证
#### Scenario: http_pull mode — token required
- **WHEN** 任务 execution_mode = `http_pull`
- **THEN** Agent SHALL 携带有效 token 调用 API
### Requirement: Non-PR task completion endpoint
对于不产生 PR 的任务research、review 等),无论哪种执行模式,都 SHALL 可通过 `POST /api/v1/tasks/{task_id}/complete` 显式完成。
#### Scenario: ssh_cli mode — auto-complete from CLI output
- **WHEN** 任务 execution_mode = `ssh_cli` 且 Agent CLI 输出包含成功 receipt
- **THEN** Orchestrator SHALL 自动解析输出并完成任务
#### Scenario: http_pull mode — Agent calls complete
- **WHEN** 任务 execution_mode = `http_pull`
- **THEN** Agent SHALL 调用 `POST /api/v1/tasks/{task_id}/complete` + receipt
### Requirement: Review loop limit
无论哪种执行模式,任务的 review 循环 SHALL 有最大次数限制。
#### Scenario: ssh_cli review loop
- **WHEN** ssh_cli 模式下 review 不通过
- **THEN** Orchestrator SHALL 自动重新调度 Agent 修复
- **AND** 超过 max_retries 时标记 failed