feat(agents-tab): הוראה למעלה + פעילות מקופלת + בורר-יעד עם "הרצה חדשה"
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

טאב הסוכנים בדף-התיק — מימוש מוקאפ X17 מאושר (18i3), פותר את התלונה החוזרת
שהיו"ר נחת בתחתית העמוד, שדה-ההוראה למטה, והטיוטה החדשה נבלעה בגלילה.

Frontend (web-ui/agent-activity-feed.tsx):
- **שדה-ההוראה עלה לראש הטאב** (Composer), מעל הפעילות — הכי זמין, בלי גלילה.
  בוטל ה-auto-scroll-to-bottom (endRef/useEffect הוסרו).
- **כל הפעילות מקופלת כברירת-מחדל** — קבוצות-המשימה וגם "ממתין לתשובתך"
  (defaultOpen=false); היו"ר פותח כדי להסתכל. סדר החדש-למעלה (תג "החדש למעלה").
- **בורר-יעד** (TargetSelector): מציג לאיזו משימה ההוראה תלך, בוחר משימה פעילה,
  מסמן משימות סגורות כמנוטרלות ("שליחה אליהן לא תעיר סוכן"), אזהרת יעד-סגור
  עם כפתור-שליחה מנוטרל, ואופציית **"פתח הרצה חדשה"**. מחשב את יעד-ברירת-המחדל
  בצד-לקוח במראָה ל-pick_default_comment_target.

Backend:
- `open_ceo_run` (paperclip_client) — פותח הרצת-CEO טרייה דרך פרימיטיב ה-CEO-child
  (#227): יוצר issue-ילד בבעלות CEO ומעיר עליו, ועוקף את issue_assignee_changed
  על issue בבעלות-אדם (הבאג של #228). נחשף דרך ה-Port כ-`pc_open_ceo_run`.
- `/agents/comment` מקבל `new_run:bool`; `PaperclipIssue` מחזיר `parent_id`
  (לבורר בצד-לקוח).

Invariants: G1 (נרמול-במקור), G2 (אין מסלול-ניתוב מקביל), G12 (מגע-Paperclip דרך ה-Port).
מקדם TaskMaster #228 (מנתב הרצה-חדשה דרך פרימיטיב ה-CEO-child).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 15:48:26 +00:00
parent 0a5be942ba
commit 9e7d98a47a
5 changed files with 457 additions and 80 deletions

View File

@@ -1739,6 +1739,93 @@ async def wake_ceo_for_action(
}
async def open_ceo_run(
case_number: str,
instruction: str,
company_id: str = "",
) -> dict:
"""Open a FRESH CEO-owned run carrying a free-form chair instruction.
Drives the "פתח הרצה חדשה" option of the agents-tab target selector: instead
of appending the instruction to an existing (possibly human-owned or closed)
issue, a **child issue assigned to the CEO** is created under the case's main
issue and the CEO is woken on it. This is the same CEO-child primitive as
:func:`wake_ceo_for_action` (#227) — it sidesteps the ``issue_assignee_changed``
cancellation that fires when an agent is woken on a human-owned parent (the
exact silent failure the chair hit on 1027-04-26; see TaskMaster #228).
Wakeup goes through the Paperclip API, never a direct DB insert. Returns
``{"status":"ok", "issue_id"(=child), "identifier", ...}`` or
``{"status":"skipped", "reason": ...}``.
"""
if not PAPERCLIP_BOARD_API_KEY:
logger.warning("PAPERCLIP_BOARD_API_KEY not set — skipping CEO new-run")
return {"status": "skipped", "reason": "no_api_key"}
issues = await get_case_issues(case_number)
if not issues:
logger.warning("No Paperclip issues for case %s — skipping CEO new-run", case_number)
return {"status": "skipped", "reason": "no_issue"}
# Parent the new run under the live main issue (never a closed child).
main_issue = pick_default_comment_target(issues)
main_issue_id = main_issue["id"]
if not company_id:
company_id = main_issue.get("company_id", "")
ceo_id = CEO_AGENTS.get(company_id, CEO_AGENT_ID)
title = f'[ערר {case_number}] הוראת יו"ר — הרצה חדשה'
description = (
'הוראת יו"ר שנשלחה כ"הרצה חדשה" מטאב הסוכנים. קרא את מצב התיק, טפל בהוראה '
"בסגנון דפנה, ומסור disposition תקין בסיום.\n\n---\n\n" + instruction
)
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"]
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 new-run sub_issue=%s: %s", sub_issue_id, e)
await pc_request(
"POST",
f"/api/agents/{ceo_id}/wakeup",
json={
"source": "on_demand",
"triggerDetail": "manual",
"reason": f"chair_new_run_{case_number}",
"payload": {"issueId": sub_issue_id, "case_number": case_number},
},
raise_on_error=True,
)
logger.info(
"CEO new-run for case %s: child_issue=%s ceo=%s", case_number, sub_issue_id, ceo_id,
)
return {
"status": "ok",
"issue_id": sub_issue_id,
"identifier": sub_issue.get("identifier", ""),
"main_issue_id": main_issue_id,
"ceo_id": ceo_id,
}
# Substring of the generation child-issue title per action (see _ceo_action_brief).
GENERATION_TITLE_SUBSTR = {
"party_claims_summary": "סיכום-מנהלים",