feat: 成本预算与失败通知第一批(G1/G2/G5/G6/无价上报)
- G1:修正 _check_quota docstring(Local popen 30 秒轮询掐断、 Docker/同步路径只有边界检查),README 配额节同步;followup 创建不查配额是已声明设计,保持不动。 - G2:notify_failure 对插值 shlex.quote,模板错误记日志不静默 吞,命令返回值/超时/异常记 host-run.log;dispatch 前置预算门 与 goal 预算中断补 budget-exceeded 通知(没配命令时只写日志)。 - G5/G6:重试与续跑的配额检查按任务创建者查(费用记创建者名下), 维护者替他人操作不再用错人的额度;续跑 API 侧补查,超限回 402 且不改状态,不再先 200 排队再悄悄 interrupted。 - 无价上报:配额自查/管理端点返回增加 unpriced_actual_tokens (actual 且 usd_micros 为 NULL,与 CostStore 口径一致),只读 上报、不计价、不拦任务;不给 gpt-5.6-terra 编造价格。 测试:host 68、cloud 760(4 跳过)、deploy 19 全过。
This commit is contained in:
1 parent
c2f82da5e0
commit
e4b5545a24
5 files changed
+304
-38
No files matched your search
@@ -48,6 +48,7 @@ import json
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
@@ -1375,20 +1376,59 @@ def budget_exceeded(state, cfg):
|
||||
def notify_failure(state_path, cfg, task_name, result):
|
||||
"""失败通知:先写 host-run.log(一定有),再跑用户配置的
|
||||
notify_command(本地 shell 命令,只来自用户自己的配置,
|
||||
不是外部输入;模板里可用 {task} {status})。"""
|
||||
text = (result or {}).get("text", "") or ""
|
||||
msg = f"FAILED {task_name}: {result.get('status')} {text[:200]}"
|
||||
不是外部输入;模板里可用 {task} {status} {text})。
|
||||
|
||||
插进命令串的值一律 shlex.quote:模型文本可能含 shell 元
|
||||
字符(引号、分号、$() 等),不加 quote 就等于把模型输出
|
||||
当命令执行。模板本身有问题(未知占位符、未成对的大括号)
|
||||
时不再静默吞掉:记一条 template error 到 host-run.log 并
|
||||
且不跑这条命令。命令的返回值 / 超时 / 异常同样记日志,
|
||||
否则通知丢了都无从发现(旧行为是异常全吞、无任何记录)。
|
||||
没有配 notify_command 时只写 host-run.log,不外发。"""
|
||||
result = result or {}
|
||||
text = result.get("text", "") or ""
|
||||
status = result.get("status", "") or ""
|
||||
msg = f"FAILED {task_name}: {status} {text[:200]}"
|
||||
try:
|
||||
log_event(state_path, msg)
|
||||
except Exception:
|
||||
pass
|
||||
cmd = (cfg or {}).get("notify_command")
|
||||
if cmd:
|
||||
if not cmd:
|
||||
return msg
|
||||
try:
|
||||
formatted = cmd.format(task=shlex.quote(str(task_name)),
|
||||
status=shlex.quote(str(status)),
|
||||
text=shlex.quote(str(text[:200])))
|
||||
except (KeyError, IndexError, ValueError) as exc:
|
||||
# 模板写错(例如 {unknown} 或单个 {)时 .format 抛错:
|
||||
# 明确记日志、不跑命令、不让调用方以为通知已发出。
|
||||
try:
|
||||
subprocess.run(cmd.format(task=task_name,
|
||||
status=result.get("status", ""),
|
||||
text=text[:200]),
|
||||
shell=True, timeout=30)
|
||||
log_event(state_path,
|
||||
"notify command template error: "
|
||||
f"{type(exc).__name__}: {exc}")
|
||||
except Exception:
|
||||
pass
|
||||
return msg
|
||||
try:
|
||||
proc = subprocess.run(formatted, shell=True, timeout=30)
|
||||
try:
|
||||
log_event(state_path,
|
||||
f"notify command rc={proc.returncode} "
|
||||
f"task={task_name}")
|
||||
except Exception:
|
||||
pass
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
log_event(state_path,
|
||||
f"notify command timeout task={task_name}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
try:
|
||||
log_event(state_path,
|
||||
"notify command failed: "
|
||||
f"{type(exc).__name__} task={task_name}")
|
||||
except Exception:
|
||||
pass
|
||||
return msg
|
||||
@@ -1969,6 +2009,11 @@ def run_goal_loop(server, state, state_path, task, goal_text, verify,
|
||||
if budget_stopped:
|
||||
task["status"] = "interrupted"
|
||||
task["goal_stopped_reason"] = "budget exceeded: max_daily_turns"
|
||||
# goal 预算中断以前只打印:与 dispatch 前置门同款,
|
||||
# 补一次 budget-exceeded 通知(没配命令时只写日志)。
|
||||
notify_failure(state_path, cfg, task.get("name") or "goal",
|
||||
{"status": "budget-exceeded",
|
||||
"text": "budget exceeded: max_daily_turns"})
|
||||
else:
|
||||
task["status"] = "goal-met" if ok else "goal-unmet"
|
||||
if task["status"] == "goal-unmet":
|
||||
@@ -2423,6 +2468,13 @@ def main():
|
||||
if args.cmd == "dispatch":
|
||||
if budget_exceeded(state, cfg):
|
||||
print("预算已用完(max_daily_turns),本次不派发,见 doctor 的 usage")
|
||||
# 前置预算门以前只打印:配了 notify_command 的
|
||||
# 用户收不到任何通知。补一次 budget-exceeded
|
||||
# 通知;没配命令时 notify_failure 只写日志,
|
||||
# 不会无配置刷屏。
|
||||
notify_failure(args.state, cfg, args.name,
|
||||
{"status": "budget-exceeded",
|
||||
"text": "budget exceeded: max_daily_turns"})
|
||||
return
|
||||
deps = [d.strip() for d in args.depends_on.split(",")
|
||||
if d.strip()] if args.depends_on else []
|
||||
|
||||
Reference in new issue
Block a user