# -*- coding: utf-8 -*- """角色 agent 实例注册(2026-09-01):项目 × 角色 → 稳定实例 id。 背景:poller 派单时 agent_id 是 `poller-{role}` 拼出来的字符串—— 1. 不是身份,审计/落库查不到「哪个 agent 实例干的」; 2. `poller-agent.bid_writer_technical` 达 33 字符,超过 pipeline_deliverables.created_by varchar(32) → 1406 连环失败, 技术章节交付件全部落不了库(商务写者恰好 32 字符没事)。 规则(角色规范名约定:处理方 = role + agentid): - 每个项目 × 角色第一次派单时注册一个实例,id 形如 `ag.{12位}`(15 字符, 任何 32 宽字段都放得下),此后复用; - `created_by`/审计 `agent_id` 一律存实例 id; - pm/qc poller 同走此解析(角色 agent.pm / agent.qc)。 """ import logging logger = logging.getLogger("pipeline.agent_instance") async def resolve_agent_instance(project_id, role): """解析(必要时注册)项目 × 角色的 agent 实例 id。幂等。 失败兜底返回 `agent.{role}`(规范角色名,≤32 字符),保证调用方不炸。 """ role = (role or "").strip() if not role: return "" fallback = ("agent." + role) if not role.startswith("agent.") else role try: from sqlor.dbpools import DBPools from appPublic.uniqueID import getID db = DBPools() async with db.sqlorContext("pipeline") as sor: recs = await sor.sqlExe( "SELECT agent_id FROM pipeline_agent_instances " "WHERE project_id=${p}$ AND role=${r}$ AND status='active' LIMIT 1", {"p": project_id or "", "r": role}) await sor.sqlExe("COMMIT", {}) if recs: aid = getattr(recs[0], "agent_id", "") or "" if aid: return aid aid = "ag." + getID()[:12] await sor.C("pipeline_agent_instances", { "id": getID(), "project_id": project_id or "", "role": role, "agent_id": aid, "status": "active", }) await sor.sqlExe("COMMIT", {}) logger.info("agent instance registered: project=%s role=%s id=%s", project_id, role, aid) return aid except Exception as e: # 并发注册撞唯一键 → 重查一次;仍失败用规范角色名兜底 try: from sqlor.dbpools import DBPools db = DBPools() async with db.sqlorContext("pipeline") as sor: recs = await sor.sqlExe( "SELECT agent_id FROM pipeline_agent_instances " "WHERE project_id=${p}$ AND role=${r}$ AND status='active' LIMIT 1", {"p": project_id or "", "r": role}) await sor.sqlExe("COMMIT", {}) if recs and (getattr(recs[0], "agent_id", "") or ""): return recs[0].agent_id except Exception: pass logger.warning("resolve_agent_instance fallback role=%s err=%s", role, str(e)[:120]) return fallback[:32]