feat(t3code): thread events as master injects, hook job without dedupe
This commit is contained in:
@@ -70,6 +70,8 @@ BEAVERGRAM_MCP=
|
|||||||
# На каждой машине: `t3 auth session issue --token-only --ttl 365d --label beaver`
|
# На каждой машине: `t3 auth session issue --token-only --ttl 365d --label beaver`
|
||||||
T3_MAC_TOKEN=
|
T3_MAC_TOKEN=
|
||||||
T3_DELL_TOKEN=
|
T3_DELL_TOKEN=
|
||||||
|
# Токен gateway со scope `api` (admin → Tokens) - им t3code-mcp шлёт события в /hooks/t3code
|
||||||
|
T3CODE_MCP_GATEWAY_TOKEN=
|
||||||
# Ветка/тег t3code-mcp, из которого собирается образ
|
# Ветка/тег t3code-mcp, из которого собирается образ
|
||||||
T3CODE_MCP_REF=main
|
T3CODE_MCP_REF=main
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from datetime import UTC, date, datetime, timedelta
|
from datetime import UTC, date, datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from beaver_gateway.agents.base import BaseAgent, ExposedMcp
|
from beaver_gateway.agents.base import BaseAgent, ExposedMcp
|
||||||
@@ -589,6 +590,51 @@ async def memory(run: JobRun) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
T3CODE_QUESTION_HINT = (
|
||||||
|
"Если ответ следует из задания - ответь сам: t3_answer(thread_id, "
|
||||||
|
"{id вопроса: label}). Не знаешь - спроси Бобра через say своими словами, "
|
||||||
|
"потом t3_answer. Тред стоит, пока не ответят."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def t3code_text(p: dict[str, Any]) -> str:
|
||||||
|
head = (
|
||||||
|
f"t3code: тред «{p.get('title')}» ({p.get('machine')}, {p.get('project')}, "
|
||||||
|
f"thread_id {p.get('thread_id')})"
|
||||||
|
)
|
||||||
|
event = p.get("event")
|
||||||
|
if event in ("question", "approval"):
|
||||||
|
lines = [f"{head} - спрашивает."]
|
||||||
|
for q in (p.get("question") or {}).get("questions", []):
|
||||||
|
multi = " (можно несколько)" if q.get("multi_select") else ""
|
||||||
|
lines.append(
|
||||||
|
f"- [{q['id']}] {q.get('header') or ''}: {q['question']}{multi}"
|
||||||
|
)
|
||||||
|
lines.extend(
|
||||||
|
f" · {o['label']}"
|
||||||
|
+ (f" - {o['description']}" if o.get("description") else "")
|
||||||
|
for o in q.get("options", [])
|
||||||
|
)
|
||||||
|
lines.append(T3CODE_QUESTION_HINT)
|
||||||
|
return "\n".join(lines)
|
||||||
|
if event == "completed":
|
||||||
|
lines = [f"{head} - завершён."]
|
||||||
|
elif event == "interrupted":
|
||||||
|
lines = [f"{head} - прерван."]
|
||||||
|
else:
|
||||||
|
lines = [f"{head} - упал: {p.get('error') or 'без текста ошибки'}."]
|
||||||
|
if p.get("text"):
|
||||||
|
lines.append(f"Ответ треда:\n{p['text']}")
|
||||||
|
if p.get("files"):
|
||||||
|
lines.append("Файлы: " + ", ".join(f["path"] for f in p["files"]))
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
async def t3code_event(run: JobRun) -> None:
|
||||||
|
"""§5, §8.6: t3code-mcp следит за тредами по WS и присылает сюда события."""
|
||||||
|
await run.inject_master(t3code_text(run.payload), urgency="urgent", origin="t3code")
|
||||||
|
|
||||||
|
|
||||||
async def komodo_alert(run: JobRun) -> None:
|
async def komodo_alert(run: JobRun) -> None:
|
||||||
payload = run.payload
|
payload = run.payload
|
||||||
summary = payload.get("raw") or {k: v for k, v in payload.items() if k != "trigger"}
|
summary = payload.get("raw") or {k: v for k, v in payload.items() if k != "trigger"}
|
||||||
@@ -602,6 +648,8 @@ jobs = [
|
|||||||
Job("память", memory, cron="30 4 * * 0", critical=False),
|
Job("память", memory, cron="30 4 * * 0", critical=False),
|
||||||
Job("deploy", deploy, webhook=True),
|
Job("deploy", deploy, webhook=True),
|
||||||
Job("komodo", komodo_alert, webhook=True),
|
Job("komodo", komodo_alert, webhook=True),
|
||||||
|
# события идут подряд (вопрос и завершение) - схлопывать нельзя
|
||||||
|
Job("t3code", t3code_event, webhook=True, dedupe=False),
|
||||||
]
|
]
|
||||||
|
|
||||||
gateway = Gateway(
|
gateway = Gateway(
|
||||||
|
|||||||
@@ -97,10 +97,16 @@ services:
|
|||||||
profiles: [t3]
|
profiles: [t3]
|
||||||
<<: *restart
|
<<: *restart
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
# события тредов (завершён / упал / спрашивает) → инжект в мастер (§4.5)
|
||||||
|
T3CODE_MCP_HOOK_URL: http://gateway:62990/hooks/t3code
|
||||||
|
T3CODE_MCP_STATE: /data/t3code-mcp.json
|
||||||
volumes:
|
volumes:
|
||||||
- ./t3code.toml:/config/t3code.toml:ro
|
- ./t3code.toml:/config/t3code.toml:ro
|
||||||
|
- t3code-state:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
t3code-state:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
vault:
|
vault:
|
||||||
obsidian-config:
|
obsidian-config:
|
||||||
|
|||||||
Reference in New Issue
Block a user