fix(generate): הפקת מסמכים דרך issue-ילד משויך-ל-CEO — לא מבוטלת בהמתנה-ליו"ר (#227 ב) #398
@@ -226,7 +226,7 @@ Paperclip חוסם אוטומטית כל issue ב-`in_progress` שאין לו ru
|
||||
### שלב 0: בדוק למה התעוררת
|
||||
|
||||
**לפני כל דבר אחר** — בדוק את סיבת ההתעוררות (`$PAPERCLIP_WAKE_REASON`):
|
||||
- **פעולות סטרוקטורליות (כפתורי-UI, בלי פענוח-טקסט):** אם `$PAPERCLIP_WAKE_PAYLOAD_JSON` מכיל שדה `action` → נתב דטרמיניסטית לפי הערך: `action == "interim_draft"` → **שלב H** (טיוטת ביניים); `action == "party_claims_summary"` → **שלב H2** (סיכום מנהלים). אלו side-quests — אל תסרוק תיקים אחרים, טפל רק בתיק שב-payload (`case_number`/`issueId`).
|
||||
- **פעולות סטרוקטורליות (כפתורי-UI, בלי פענוח-טקסט):** אם `$PAPERCLIP_WAKE_PAYLOAD_JSON` מכיל שדה `action` → נתב דטרמיניסטית לפי הערך: `action == "interim_draft"` → **שלב H** (טיוטת ביניים); `action == "party_claims_summary"` → **שלב H2** (סיכום מנהלים). אלו side-quests — אל תסרוק תיקים אחרים, טפל רק בתיק שב-payload (`case_number`/`issueId`). ה-`issueId` שב-payload הוא **issue-ילד ייעודי שמשויך אליך** (נוצר ע"י המערכת כדי שהריצה לא תבוטל כשה-issue הראשי ממתין-ליו"ר; #227) — הרץ את הפעולה, פרסם comment עם התוצאה, ו**סגור את ה-issue-ילד הזה כ-done** בסיום. אל תיגע ב-issue הראשי של התיק.
|
||||
- אם ה-reason מכיל `user_commented` **ו**-`$PAPERCLIP_WAKE_PAYLOAD_JSON` כולל `issueId` → **דלג ישירות לסעיף "טיפול בתגובות חדשות מחיים"** עם ה-issue הזה. אל תסרוק תיקים אחרים. **טפל רק בתגובה.**
|
||||
- אם ה-reason מכיל `agent_completion` → דלג לשלב E/F בהתאם לסוכן שסיים
|
||||
- אם ה-reason מכיל `precedent_extraction_` → **דלג לסעיף "חילוץ פסיקה אוטומטי"**. אל תיגע בתיקים — זו עבודת ספרייה.
|
||||
|
||||
@@ -1258,25 +1258,58 @@ async def wake_ceo_agent(issue_id: str, case_number: str, company_id: str = "")
|
||||
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(
|
||||
case_number: str,
|
||||
action: str,
|
||||
company_id: str = "",
|
||||
) -> 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
|
||||
``payload.action`` and routes deterministically to its שלב H / שלב H2
|
||||
side-quests (legal-ceo.md §"פעולות סטרוקטורליות") — no free-text parsing.
|
||||
Drives the UI "הפקת מסמכים" buttons:
|
||||
action="party_claims_summary" → summarize_party_claims (שלב H2)
|
||||
action="interim_draft" → write_interim_draft + export_interim_draft (שלב H)
|
||||
|
||||
action="party_claims_summary" → שלב H2 (summarize_party_claims)
|
||||
action="interim_draft" → שלב H (write_interim_draft + export_interim_draft)
|
||||
A **child issue assigned to the CEO** is created under the case's main issue
|
||||
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
|
||||
issue is created (the CEO must not change ``cases.status`` or spawn sub-agents).
|
||||
Wakeup goes through the Paperclip API (``POST /api/agents/{id}/wakeup``), never a
|
||||
direct DB insert (CLAUDE.md hard rule). Returns
|
||||
``{"status": "ok"|"skipped", ...}``.
|
||||
Wakeup goes through the Paperclip API (``POST /api/agents/{id}/wakeup``), never
|
||||
a direct DB insert (CLAUDE.md hard rule). Returns
|
||||
``{"status": "ok", "issue_id"(=child), "ceo_id", ...}`` or
|
||||
``{"status": "skipped", "reason": ...}``.
|
||||
"""
|
||||
if not PAPERCLIP_BOARD_API_KEY:
|
||||
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)
|
||||
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 = (
|
||||
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 issues[0]
|
||||
)
|
||||
main_issue_id = main_issue["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(
|
||||
"POST",
|
||||
f"/api/agents/{ceo_id}/wakeup",
|
||||
@@ -1306,7 +1364,7 @@ async def wake_ceo_for_action(
|
||||
"triggerDetail": "manual",
|
||||
"reason": f"generate_{action}_{case_number}",
|
||||
"payload": {
|
||||
"issueId": main_issue_id,
|
||||
"issueId": sub_issue_id,
|
||||
"action": action,
|
||||
"case_number": case_number,
|
||||
},
|
||||
@@ -1314,13 +1372,14 @@ async def wake_ceo_for_action(
|
||||
raise_on_error=True,
|
||||
)
|
||||
logger.info(
|
||||
"CEO action wakeup for case %s: action=%s issue=%s ceo=%s status=%s",
|
||||
case_number, action, main_issue_id, ceo_id, wake_resp.status_code,
|
||||
"CEO action wakeup for case %s: action=%s child_issue=%s ceo=%s status=%s",
|
||||
case_number, action, sub_issue_id, ceo_id, wake_resp.status_code,
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"action": action,
|
||||
"issue_id": main_issue_id,
|
||||
"issue_id": sub_issue_id,
|
||||
"main_issue_id": main_issue_id,
|
||||
"ceo_id": ceo_id,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user