P1 security batch: quota execution checks, cost accounting, goal/coordinator semantics, PR channel, orphan signature, control-plane and console hardening
- Docker quota watchdog + fail-closed quota checks, system tasks need real owner - max_cost_usd actually accumulated, cancel records cost, estimated rows excluded from unpriced cap, cache_write declared non-billed - goal-resume keeps original verify on breakpoint resume, goal retry falls back to resume, goal_max_rounds capped at 100 at create and execution - coordinator children inherit parent model/effort, orphans marked interrupted with audit - PR jobs on docker workers return explicit needs_local_worker instead of silent empty - worker code signature covers 6 files and is checked before claim, fail-closed - sub2api lazy provisioning with rotation, backup whitelist + 0600, push SSRF block, repos/workers desensitized, key/project audits and limits, console buttons/quota/status wording, coordinator/automation quota reserve
This commit is contained in:
1 parent
d254240d84
commit
1402dc67fd
20 files changed
+2616
-179
No files matched your search
+83
-36
@@ -423,6 +423,15 @@ class LocalExecutor(Executor):
|
||||
rounds = task.get("goal_max_rounds")
|
||||
if rounds is None:
|
||||
rounds = 3
|
||||
# P1 轮次上限(与 worker 的 GOAL_MAX_ROUNDS_CAP 同值 100):
|
||||
# 有限轮次超限直接 loud fail,不静默截断;goal_mode 的 0
|
||||
#(不限轮次)放行,上界是配额;旧验收形态的 0 同样拒绝
|
||||
#(0 在旧形态里表示不限轮次,P1 已禁用无上限验收循环)。
|
||||
if rounds is not None and rounds > 100:
|
||||
raise ValueError("goal_max_rounds 超过上限 100 轮")
|
||||
if rounds == 0 and verify and not task.get("goal_mode"):
|
||||
raise ValueError(
|
||||
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
|
||||
cmd = self._base_command(model=task.get("model"),
|
||||
effort=task.get("effort"),
|
||||
approval=task.get("approval"),
|
||||
@@ -440,26 +449,35 @@ class LocalExecutor(Executor):
|
||||
return self._goal_result_from_runner(
|
||||
task["id"], self._run_local(cmd, extra_env))
|
||||
|
||||
def goal_resume_task(self, task, goal_text, extra_env=None,
|
||||
model_provider=None) -> dict:
|
||||
"""调 host.py goal-resume --goal-text:在旧线程上开新目标
|
||||
循环(旧任务接续 goal 模式,云端 /continue 带 goal_mode=1
|
||||
时走这里)。命令:host.py goal-resume --task <host 名>
|
||||
--goal-text <新目标> --max-rounds 0(新语义不限轮次)。
|
||||
def goal_resume_task(self, task, goal_text=None, extra_env=None,
|
||||
model_provider=None, max_rounds=None) -> dict:
|
||||
"""调 host.py goal-resume,分两种调用(P1,与 worker、
|
||||
host 三处一致,绝不能混用):
|
||||
- 断点续跑:goal_text=None(worker 的 _resume_kind=
|
||||
"breakpoint" 时传 None)。命令不带 --goal-text、默认
|
||||
也不带 --max-rounds:host 沿用原 goal + 原 verify_command
|
||||
+ 原 goal_max_rounds,从断点轮次续跑。传了 --goal-text
|
||||
就会走 host 的开新目标分支、把原 verify 覆盖成 None,
|
||||
所以断点调用绝不能顺手把续跑消息塞进 goal_text。
|
||||
- 开新目标:goal_text 为新目标文本(云端 /continue 带
|
||||
goal_mode=1 时走这里)。命令带 --goal-text 与
|
||||
--max-rounds(默认 0,新语义不限轮次,上界是配额)。
|
||||
不传 --repo:goal-resume 操作已有 host 任务,不新建
|
||||
worktree。结论解析与 goal_task 同款。
|
||||
"""
|
||||
if not isinstance(task, dict) or task.get("id") is None:
|
||||
raise ValueError("任务缺少 id")
|
||||
if not goal_text:
|
||||
raise ValueError("goal-resume 缺少目标文本")
|
||||
cmd = self._base_command(model=task.get("model"),
|
||||
effort=task.get("effort"),
|
||||
approval=task.get("approval"),
|
||||
model_provider=model_provider)
|
||||
cmd += ["goal-resume", "--task", self._host_name(task),
|
||||
"--goal-text", goal_text,
|
||||
"--max-rounds", "0"]
|
||||
cmd += ["goal-resume", "--task", self._host_name(task)]
|
||||
if goal_text:
|
||||
cmd += ["--goal-text", goal_text,
|
||||
"--max-rounds",
|
||||
str(max_rounds if max_rounds is not None else 0)]
|
||||
elif max_rounds is not None:
|
||||
cmd += ["--max-rounds", str(max_rounds)]
|
||||
return self._goal_result_from_runner(
|
||||
task["id"], self._run_local(cmd, extra_env))
|
||||
|
||||
@@ -469,7 +487,9 @@ class LocalExecutor(Executor):
|
||||
解析用完整 stdout(结论在末尾),存库的 stdout 才截尾;
|
||||
returncode 非 0 一律 failed;为 0 时无结论也判 failed,
|
||||
绝不把“没打印结论”当成达标。host 任务已存在时(同任务
|
||||
/retry 撞名,goal 没有 run 续跑语义)错误写明请新建任务。
|
||||
/retry 撞名,goal 没有 run 续跑语义)错误写明“goal 失败
|
||||
请用继续而非重试”(P1:重试在已存在 host-state 上必失败,
|
||||
断点续跑才是正确路径,见 worker 的回落逻辑)。
|
||||
"""
|
||||
returncode = getattr(result, "returncode", 1)
|
||||
stdout_full = getattr(result, "stdout", "") or ""
|
||||
@@ -478,8 +498,10 @@ class LocalExecutor(Executor):
|
||||
combined = stdout_full + stderr
|
||||
if "已存在" in combined:
|
||||
met, goal_error = None, (
|
||||
"host 任务已存在:goal 任务不支持同任务重试,"
|
||||
"请新建任务")
|
||||
"host 任务已存在:goal 失败请用继续而非重试——"
|
||||
"同任务重试会撞已存在 host-state,worker 会先"
|
||||
"尝试断点续跑,断点续跑仍失败时请用继续(不要"
|
||||
"重排同一任务)")
|
||||
status = "failed"
|
||||
elif returncode != 0:
|
||||
status = "failed"
|
||||
@@ -516,9 +538,11 @@ class LocalExecutor(Executor):
|
||||
killpg 才能连同子孙一起杀掉。注意:只有默认 runner(真实
|
||||
subprocess)时才用本方法;单测注入的假 runner 走同步路径,
|
||||
不走这里。kind=goal 时建 goal 命令、kind=goal-resume 时建
|
||||
goal-resume 命令(旧任务接续 goal 模式,目标文本取本轮
|
||||
prompt;结论解析由 worker 的 _execute_via_process 按 kind
|
||||
分发到 _goal_result_from_runner)。goal 语义与同步 goal_task
|
||||
goal-resume 命令(结论解析由 worker 的 _execute_via_process
|
||||
按 kind 分发到 _goal_result_from_runner)。goal-resume 分流
|
||||
(P1):_resume_kind="breakpoint" 时不传 --goal-text(断点
|
||||
续跑,沿用原 goal+verify),其它情况才把本轮 prompt 当
|
||||
--goal-text(开新目标)。goal 语义与同步 goal_task
|
||||
一致:新形态(goal_mode)无验收命令时 --verify 不传、
|
||||
--max-rounds 传 0(0 不能用 or 兜底成 3);旧形态仍传验收
|
||||
命令与轮次。seed_dir 只对 dispatch/goal 追加(run 续跑与
|
||||
@@ -545,6 +569,11 @@ class LocalExecutor(Executor):
|
||||
rounds = task.get("goal_max_rounds")
|
||||
if rounds is None:
|
||||
rounds = 3
|
||||
if rounds is not None and rounds > 100:
|
||||
raise ValueError("goal_max_rounds 超过上限 100 轮")
|
||||
if rounds == 0 and verify and not task.get("goal_mode"):
|
||||
raise ValueError(
|
||||
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
|
||||
cmd += ["goal", "--name", self._host_name(task),
|
||||
"--task", goal_text]
|
||||
if verify:
|
||||
@@ -556,12 +585,15 @@ class LocalExecutor(Executor):
|
||||
if seed_dir:
|
||||
cmd += ["--seed-dir", seed_dir]
|
||||
elif kind == "goal-resume":
|
||||
# 旧任务接续 goal:host 侧用 --goal-text 在旧线程上开
|
||||
# 新目标循环(新语义,不限轮次);task["prompt"] 在续跑
|
||||
# 时已被 worker 换成续跑消息。
|
||||
cmd += ["goal-resume", "--task", self._host_name(task),
|
||||
"--goal-text", task["prompt"],
|
||||
"--max-rounds", "0"]
|
||||
# P1 分流(与 goal_resume_task 同款契约):
|
||||
# _resume_kind="breakpoint" 时不传 --goal-text(host
|
||||
# 沿用原 goal+verify 断点续跑,绝不能用续跑消息覆盖
|
||||
# 原 verify);其它情况(开新目标)才用 task["prompt"]
|
||||
#(worker 已换成续跑消息)当 --goal-text,--max-rounds 0。
|
||||
cmd += ["goal-resume", "--task", self._host_name(task)]
|
||||
if task.get("_resume_kind") != "breakpoint":
|
||||
cmd += ["--goal-text", task["prompt"],
|
||||
"--max-rounds", "0"]
|
||||
else:
|
||||
cmd += ["dispatch", "--name", self._host_name(task),
|
||||
"--task", task["prompt"]]
|
||||
@@ -1101,6 +1133,12 @@ class DockerExecutor(Executor):
|
||||
rounds = task.get("goal_max_rounds")
|
||||
if rounds is None:
|
||||
rounds = 3
|
||||
# P1 轮次上限(与 Local goal_task 同款,不静默截断)。
|
||||
if rounds is not None and rounds > 100:
|
||||
raise ValueError("goal_max_rounds 超过上限 100 轮")
|
||||
if rounds == 0 and verify and not task.get("goal_mode"):
|
||||
raise ValueError(
|
||||
"旧验收形态 goal_max_rounds=0 表示不限轮次,已禁用")
|
||||
inner_args = ["goal", "--name", self._host_name(task),
|
||||
"--task", goal_text]
|
||||
if verify:
|
||||
@@ -1150,8 +1188,10 @@ class DockerExecutor(Executor):
|
||||
combined = stdout_full + stderr
|
||||
if "已存在" in combined:
|
||||
met, goal_error = None, (
|
||||
"host 任务已存在:goal 任务不支持同任务重试,"
|
||||
"请新建任务")
|
||||
"host 任务已存在:goal 失败请用继续而非重试——"
|
||||
"同任务重试会撞已存在 host-state,worker 会先"
|
||||
"尝试断点续跑,断点续跑仍失败时请用继续(不要"
|
||||
"重排同一任务)")
|
||||
status = "failed"
|
||||
elif returncode != 0:
|
||||
status = "failed"
|
||||
@@ -1181,22 +1221,29 @@ class DockerExecutor(Executor):
|
||||
"usage": _parse_goal_usage(stdout_full),
|
||||
}
|
||||
|
||||
def goal_resume_task(self, task, goal_text, timeout=None,
|
||||
def goal_resume_task(self, task, goal_text=None, timeout=None,
|
||||
extra_mounts=None, network=None,
|
||||
extra_env=None, model_provider=None) -> dict:
|
||||
"""容器内调 host.py goal-resume --goal-text:在旧线程上开
|
||||
新目标循环(旧任务接续 goal 模式)。解析与 goal_task 同款
|
||||
(完整 stdout 结论、存库截尾),但没有“已存在”分支:
|
||||
goal-resume 本来就是操作已有 host 任务,不存在 dispatch
|
||||
撞名问题;returncode 非 0 或无结论一律 failed。
|
||||
extra_env=None, model_provider=None,
|
||||
max_rounds=None) -> dict:
|
||||
"""容器内调 host.py goal-resume,分两种调用(P1,与 Local
|
||||
goal_resume_task 同款契约):断点续跑传 goal_text=None
|
||||
(不带 --goal-text,host 沿用原 goal+verify+轮次),开新
|
||||
目标才传 goal_text(带 --goal-text 与 --max-rounds,默认 0)。
|
||||
解析与 goal_task 同款(完整 stdout 结论、存库截尾),但
|
||||
没有“已存在”分支:goal-resume 本来就是操作已有 host 任务,
|
||||
不存在 dispatch 撞名问题;returncode 非 0 或无结论一律 failed。
|
||||
"""
|
||||
if not isinstance(task, dict) or task.get("id") is None:
|
||||
raise ValueError("任务缺少 id")
|
||||
if not goal_text:
|
||||
raise ValueError("goal-resume 缺少目标文本")
|
||||
_resume_args = ["goal-resume", "--task", self._host_name(task)]
|
||||
if goal_text:
|
||||
_resume_args += ["--goal-text", goal_text, "--max-rounds",
|
||||
str(max_rounds
|
||||
if max_rounds is not None else 0)]
|
||||
elif max_rounds is not None:
|
||||
_resume_args += ["--max-rounds", str(max_rounds)]
|
||||
inner = self._inner_host_command(
|
||||
"goal-resume", "--task", self._host_name(task),
|
||||
"--goal-text", goal_text, "--max-rounds", "0",
|
||||
*_resume_args,
|
||||
model=task.get("model"), effort=task.get("effort"),
|
||||
approval=task.get("approval"),
|
||||
model_provider=model_provider)
|
||||
|
||||
Reference in new issue
Block a user