|
|
|
@@ -1258,25 +1258,58 @@ async def wake_ceo_agent(issue_id: str, case_number: str, company_id: str = "")
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ceo_action_brief(action: str, case_number: str) -> tuple[str, str]:
|
|
|
|
|
|
|
|
"""(child-issue title, description) for a deterministic CEO generation action.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The instruction is self-contained in the description (like
|
|
|
|
|
|
|
|
``wake_analyst_for_argument_aggregation``) so the run does not depend on the
|
|
|
|
|
|
|
|
CEO parsing ``payload.action`` — it just runs the named tool and closes the
|
|
|
|
|
|
|
|
child. Both actions run host-side (claude_session → local claude CLI).
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if action == "party_claims_summary":
|
|
|
|
|
|
|
|
title = f"[ערר {case_number}] הפקת סיכום-מנהלים לטענות הצדדים"
|
|
|
|
|
|
|
|
description = (
|
|
|
|
|
|
|
|
f"חיים ביקש הפקת סיכום-מנהלים לטענות הצדדים בתיק {case_number}.\n\n"
|
|
|
|
|
|
|
|
f"הרץ `mcp__legal-ai__summarize_party_claims(case_number=\"{case_number}\")`, "
|
|
|
|
|
|
|
|
f"כתוב comment קצר בעברית עם תמצית התוצאה, וסגור issue זה כ-done. "
|
|
|
|
|
|
|
|
f"אם חסרים נתונים (אין טענות מחולצות) — דווח ב-comment מה חסר וסגור כ-blocked."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return title, description
|
|
|
|
|
|
|
|
# default: interim_draft
|
|
|
|
|
|
|
|
title = f"[ערר {case_number}] הפקת טיוטת טענות-הצדדים (טיוטת-ביניים)"
|
|
|
|
|
|
|
|
description = (
|
|
|
|
|
|
|
|
f"חיים ביקש הפקת טיוטת טענות-הצדדים (טיוטת-ביניים) בתיק {case_number}.\n\n"
|
|
|
|
|
|
|
|
f"הרץ `mcp__legal-ai__write_interim_draft(case_number=\"{case_number}\")` ואז "
|
|
|
|
|
|
|
|
f"`mcp__legal-ai__export_interim_draft(case_number=\"{case_number}\")`, כתוב comment "
|
|
|
|
|
|
|
|
f"קצר בעברית עם שם הקובץ שנוצר, וסגור issue זה כ-done. אם חסרים נתונים — דווח "
|
|
|
|
|
|
|
|
f"ב-comment וסגור כ-blocked."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return title, description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def wake_ceo_for_action(
|
|
|
|
async def wake_ceo_for_action(
|
|
|
|
case_number: str,
|
|
|
|
case_number: str,
|
|
|
|
action: str,
|
|
|
|
action: str,
|
|
|
|
company_id: str = "",
|
|
|
|
company_id: str = "",
|
|
|
|
) -> dict:
|
|
|
|
) -> dict:
|
|
|
|
"""Wake the CEO with a deterministic *structured action* on the case's MAIN issue.
|
|
|
|
"""Wake the CEO to run a deterministic generation action (TaskMaster #214).
|
|
|
|
|
|
|
|
|
|
|
|
Drives the UI "הפקת מסמכים" buttons (TaskMaster #214): the CEO reads
|
|
|
|
Drives the UI "הפקת מסמכים" buttons:
|
|
|
|
``payload.action`` and routes deterministically to its שלב H / שלב H2
|
|
|
|
action="party_claims_summary" → summarize_party_claims (שלב H2)
|
|
|
|
side-quests (legal-ceo.md §"פעולות סטרוקטורליות") — no free-text parsing.
|
|
|
|
action="interim_draft" → write_interim_draft + export_interim_draft (שלב H)
|
|
|
|
|
|
|
|
|
|
|
|
action="party_claims_summary" → שלב H2 (summarize_party_claims)
|
|
|
|
A **child issue assigned to the CEO** is created under the case's main issue
|
|
|
|
action="interim_draft" → שלב H (write_interim_draft + export_interim_draft)
|
|
|
|
and the wakeup targets that child — NOT the main issue directly (#227). The
|
|
|
|
|
|
|
|
main issue is usually ``in_review`` assigned to the chair (human) while a case
|
|
|
|
|
|
|
|
waits; waking the CEO *on* a human-owned issue makes Paperclip cancel the run
|
|
|
|
|
|
|
|
immediately (``issue_assignee_changed``) — the exact silent failure this
|
|
|
|
|
|
|
|
fixes. The CEO owns the child, so its run is never cancelled. Same delegation
|
|
|
|
|
|
|
|
shape as ``wake_analyst_for_argument_aggregation``.
|
|
|
|
|
|
|
|
|
|
|
|
These are side-quests on the EXISTING ``[ערר {case_number}]`` issue — no child
|
|
|
|
Wakeup goes through the Paperclip API (``POST /api/agents/{id}/wakeup``), never
|
|
|
|
issue is created (the CEO must not change ``cases.status`` or spawn sub-agents).
|
|
|
|
a direct DB insert (CLAUDE.md hard rule). Returns
|
|
|
|
Wakeup goes through the Paperclip API (``POST /api/agents/{id}/wakeup``), never a
|
|
|
|
``{"status": "ok", "issue_id"(=child), "ceo_id", ...}`` or
|
|
|
|
direct DB insert (CLAUDE.md hard rule). Returns
|
|
|
|
``{"status": "skipped", "reason": ...}``.
|
|
|
|
``{"status": "ok"|"skipped", ...}``.
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if not PAPERCLIP_BOARD_API_KEY:
|
|
|
|
if not PAPERCLIP_BOARD_API_KEY:
|
|
|
|
logger.warning("PAPERCLIP_BOARD_API_KEY not set — skipping CEO action wakeup")
|
|
|
|
logger.warning("PAPERCLIP_BOARD_API_KEY not set — skipping CEO action wakeup")
|
|
|
|
@@ -1287,17 +1320,42 @@ async def wake_ceo_for_action(
|
|
|
|
logger.warning("No Paperclip issues found for case %s — skipping CEO action", case_number)
|
|
|
|
logger.warning("No Paperclip issues found for case %s — skipping CEO action", case_number)
|
|
|
|
return {"status": "skipped", "reason": "no_issue"}
|
|
|
|
return {"status": "skipped", "reason": "no_issue"}
|
|
|
|
|
|
|
|
|
|
|
|
# The main case issue — prefer the in-progress one, else the canonical
|
|
|
|
|
|
|
|
# "[ערר {case_number}]" issue, else the oldest. (Same selection spirit as
|
|
|
|
|
|
|
|
# wake_curator_for_final, but we never spawn a child — it's a side-quest.)
|
|
|
|
|
|
|
|
main_issue = (
|
|
|
|
main_issue = (
|
|
|
|
next((i for i in issues if i.get("status") == "in_progress"), None)
|
|
|
|
next((i for i in issues if i.get("status") == "in_progress"), None)
|
|
|
|
or next((i for i in issues if f"[ערר {case_number}]" in (i.get("title") or "")), None)
|
|
|
|
or next((i for i in issues if f"[ערר {case_number}]" in (i.get("title") or "")), None)
|
|
|
|
or issues[0]
|
|
|
|
or issues[0]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
main_issue_id = main_issue["id"]
|
|
|
|
main_issue_id = main_issue["id"]
|
|
|
|
|
|
|
|
|
|
|
|
ceo_id = CEO_AGENTS.get(company_id, CEO_AGENT_ID)
|
|
|
|
ceo_id = CEO_AGENTS.get(company_id, CEO_AGENT_ID)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Child issue assigned to the CEO — so the queued run is owned by the CEO and
|
|
|
|
|
|
|
|
# not cancelled by the human assignee on the parent (#227).
|
|
|
|
|
|
|
|
title, description = _ceo_action_brief(action, case_number)
|
|
|
|
|
|
|
|
child_resp = await pc_request(
|
|
|
|
|
|
|
|
"POST",
|
|
|
|
|
|
|
|
f"/api/issues/{main_issue_id}/children",
|
|
|
|
|
|
|
|
json={
|
|
|
|
|
|
|
|
"title": title,
|
|
|
|
|
|
|
|
"description": description,
|
|
|
|
|
|
|
|
"status": "in_progress",
|
|
|
|
|
|
|
|
"priority": "medium",
|
|
|
|
|
|
|
|
"assigneeAgentId": ceo_id,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
raise_on_error=True,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
sub_issue = child_resp.json()
|
|
|
|
|
|
|
|
sub_issue_id = sub_issue["id"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Tag plugin_state so the case page surfaces this sub-issue too.
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
conn = await asyncpg.connect(PAPERCLIP_DB_URL)
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
await _link_case_to_issue(conn, sub_issue_id, case_number)
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
await conn.close()
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
logger.warning("plugin_state link failed for sub_issue=%s: %s", sub_issue_id, e)
|
|
|
|
|
|
|
|
|
|
|
|
wake_resp = await pc_request(
|
|
|
|
wake_resp = await pc_request(
|
|
|
|
"POST",
|
|
|
|
"POST",
|
|
|
|
f"/api/agents/{ceo_id}/wakeup",
|
|
|
|
f"/api/agents/{ceo_id}/wakeup",
|
|
|
|
@@ -1306,7 +1364,7 @@ async def wake_ceo_for_action(
|
|
|
|
"triggerDetail": "manual",
|
|
|
|
"triggerDetail": "manual",
|
|
|
|
"reason": f"generate_{action}_{case_number}",
|
|
|
|
"reason": f"generate_{action}_{case_number}",
|
|
|
|
"payload": {
|
|
|
|
"payload": {
|
|
|
|
"issueId": main_issue_id,
|
|
|
|
"issueId": sub_issue_id,
|
|
|
|
"action": action,
|
|
|
|
"action": action,
|
|
|
|
"case_number": case_number,
|
|
|
|
"case_number": case_number,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -1314,13 +1372,14 @@ async def wake_ceo_for_action(
|
|
|
|
raise_on_error=True,
|
|
|
|
raise_on_error=True,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
logger.info(
|
|
|
|
logger.info(
|
|
|
|
"CEO action wakeup for case %s: action=%s issue=%s ceo=%s status=%s",
|
|
|
|
"CEO action wakeup for case %s: action=%s child_issue=%s ceo=%s status=%s",
|
|
|
|
case_number, action, main_issue_id, ceo_id, wake_resp.status_code,
|
|
|
|
case_number, action, sub_issue_id, ceo_id, wake_resp.status_code,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
"status": "ok",
|
|
|
|
"status": "ok",
|
|
|
|
"action": action,
|
|
|
|
"action": action,
|
|
|
|
"issue_id": main_issue_id,
|
|
|
|
"issue_id": sub_issue_id,
|
|
|
|
|
|
|
|
"main_issue_id": main_issue_id,
|
|
|
|
"ceo_id": ceo_id,
|
|
|
|
"ceo_id": ceo_id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|