feat(queue,mcp): отмена пакета сообщает о команде у агента; подсказка про labels (#13)
cancel_batch возвращает still_claimed — команды пакета в статусе claimed, которые отмена не останавливает. MCP batch_cancel выводит строку на каждую такую команду с подсказкой про POST /commands/N/cancel с force. Описание batch_create предупреждает, что ensure_issue нужны labels с базовой меткой AWPCA, иначе задача выпадет из базовой JQL и из зеркала. M-11 (hub#12), T3.7 (agent#4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
19aa032a25
commit
bd8c769e75
@ -91,6 +91,9 @@ def fmt_batch(b) -> str:
|
||||
for c in b.get("commands", []):
|
||||
lines.append(f" {c['seq']}. #{c['id']} {c['type']} {json.dumps(c['params'], ensure_ascii=False)} → {c['status']}"
|
||||
+ (f" ({c['error']})" if c.get("error") else "") + (f" {json.dumps(c['result'], ensure_ascii=False)}" if c.get("result") else ""))
|
||||
for s in b.get("still_claimed") or []:
|
||||
lines.append(f" команда #{s['id']} уже у агента и будет выполнена; остановить — "
|
||||
f"POST /commands/{s['id']}/cancel с force: true")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@ -142,7 +145,9 @@ def notes_set(key: str, fact_status: str | None = None, work_kind: str | None =
|
||||
@guard
|
||||
def batch_create(title: str, commands: list[dict]) -> str:
|
||||
"""Поставить пакет команд. Каждая команда — {"type": ensure_issue|ensure_fields|ensure_comment|ensure_status|
|
||||
ensure_link|ensure_sprint|refresh, "params": {...}} по контракту specs/commands. Хвостовой refresh хаб добавит сам."""
|
||||
ensure_link|ensure_sprint|refresh, "params": {...}} по контракту specs/commands. Хвостовой refresh хаб добавит сам.
|
||||
ensure_issue передавайте с labels, включающими базовую метку проекта (AWPCA): хаб и агент метки сами не добавляют,
|
||||
задача без неё выпадет из базовой JQL и из зеркала."""
|
||||
b = _client().post("/batches", {"title": title, "author": AUTHOR, "commands": commands})
|
||||
return f"пакет #{b['id']} поставлен: {b['count']} команд, оценка ≈ {round(b['estimate_seconds'] / 60)} мин"
|
||||
|
||||
@ -186,7 +191,8 @@ def command_retry(id: int) -> str:
|
||||
@mcp.tool()
|
||||
@guard
|
||||
def batch_cancel(id: int) -> str:
|
||||
"""Отменить пакет (только команды в очереди; взятую агентом — command_cancel с force через API)."""
|
||||
"""Отменить пакет: команды в очереди отменяются; взятую агентом команду отмена не останавливает —
|
||||
в ответе подсказка (POST /commands/N/cancel с force)."""
|
||||
return fmt_batch(_client().post(f"/batches/{id}/cancel", {}))
|
||||
|
||||
|
||||
|
||||
@ -287,7 +287,10 @@ def cancel_batch(conn, batch_id) -> dict:
|
||||
now = db.now()
|
||||
conn.execute("UPDATE batch SET cancelled_at = ? WHERE id = ? AND cancelled_at IS NULL", (now, batch_id))
|
||||
conn.execute("UPDATE command SET status = 'cancelled', finished_at = ? WHERE batch_id = ? AND status = 'queued'", (now, batch_id))
|
||||
return batch_view(conn, batch_id)
|
||||
view = batch_view(conn, batch_id)
|
||||
# взятые агентом команды отмена пакета не останавливает: они будут выполнены (M-11 #12)
|
||||
view["still_claimed"] = [{"id": c["id"], "type": c["type"]} for c in view["commands"] if c["status"] == "claimed"]
|
||||
return view
|
||||
|
||||
|
||||
def cancel_command(conn, command_id, force=False) -> dict:
|
||||
|
||||
@ -189,3 +189,16 @@ def test_batch_wait_does_not_block_event_loop(monkeypatch):
|
||||
|
||||
out, ticks = asyncio.run(scenario())
|
||||
assert "done" in out and len(ticks) == 5
|
||||
|
||||
|
||||
def test_fmt_batch_warns_about_claimed():
|
||||
b = {"id": 9, "title": "x", "author": "claude", "created_at": "2026-10-14T10:00:00+00:00", "status": "running",
|
||||
"remaining_seconds": 120, "counts": {"claimed": 1, "cancelled": 1}, "commands": [],
|
||||
"still_claimed": [{"id": 30, "type": "ensure_fields"}]}
|
||||
text = mcp_server.fmt_batch(b)
|
||||
assert "команда #30 уже у агента и будет выполнена" in text and "POST /commands/30/cancel" in text
|
||||
|
||||
|
||||
def test_batch_create_mentions_labels():
|
||||
doc = mcp_server.batch_create.__doc__ or ""
|
||||
assert "labels" in doc and "AWPCA" in doc
|
||||
|
||||
@ -456,3 +456,12 @@ def test_refresh_failed_by_limit_finishes_sync_run():
|
||||
queue.claim(conn)
|
||||
assert queue.get_command(conn, r["id"])["status"] == "failed"
|
||||
assert conn.execute("SELECT finished_at FROM sync_run WHERE command_id = ?", (r["id"],)).fetchone()["finished_at"]
|
||||
|
||||
|
||||
def test_cancel_batch_reports_still_claimed():
|
||||
conn = fresh()
|
||||
b = queue.create_batch(conn, "x", "claude", [fields("A-1"), fields("A-2")])
|
||||
c = queue.claim(conn)
|
||||
view = queue.cancel_batch(conn, b["id"])
|
||||
assert view["still_claimed"] == [{"id": c["id"], "type": "ensure_fields"}]
|
||||
assert queue.get_command(conn, c["id"])["status"] == "claimed"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user