fix(routing): הוראת-יו"ר תעבוד בדרך הטבעית — הערה פותחת הרצת-CEO
**התסמין:** היו"ר כותב הוראה על תיק והסוכן "מתעלם". ההרצה מדווחת succeeded/exit 0 ואפילו עולה כסף — ולכן זה שרד חודשים בלי שמישהו שם לב. **שתי סיבות-שורש בלתי-תלויות, שתיהן מאומתות:** 1. ה-wakeup הישיר על ה-issue של התיק מבוטל תמיד: תיק שממתין ליו"ר הוא `in_review` ומשויך *לאדם*, ו-Paperclip עונה `issue_assignee_changed` → "אעיר את הבעלים החדש" → הבעלים אדם → אף סוכן לא מתעורר. 2. רשת-הביטחון בפלאגין מוסרת את ההוראה כ-`payload.prompt`, וה-runner קורא רק `payload.issueId` — הפרומפט נבלע. 18/18 מדידה ב-DB: 0% מסירה, אי-פעם. **התיקון:** המסלול הטבעי (הערה בטאב-הסוכנים) מפרסם את ההערה בשרשור כרגיל **וגם** פותח הרצת-CEO עם ההוראה, דרך `pc_open_ceo_run` — הפרימיטיב היחיד שאומת כמגיע לסוכן (הוכח היום על 1043-02-26: 3,212→7,697 מילים). אותו תיקון ל-interaction-response, שסובל מאותה תקלה (החצי השני של #228). **G2 — הסרת מסלול מקביל:** ה-wakeup הנדון-לביטול הוסר מ-`post_comment`; הוא היה המסלול השני, זה ששותק. כעת מסלול-מסירה קנוני אחד. `mark_comment_routed` מסמן את ההערה כמנותבת כדי שה-sweep לא יירה הרצת-סרק על מה שכבר טופל. **G12 — שער-הפלטפורמה:** המגע החדש (`mark_comment_routed`) נחשף דרך `agent_platform_port.py` בלבד; leak-guard עובר. התיקון יושב ב-backend (REST) ולא בפלאגין, ולכן אינו חשוף לבאג ה-invocation-scope שמפיל את ה-sweep. תועד ב-docs/paperclip-quirks.md §7 עם המלכודות שנתקלנו בהן. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
web/app.py
52
web/app.py
@@ -91,6 +91,7 @@ from web.agent_platform_port import (
|
||||
pc_wake_ceo,
|
||||
pc_wake_ceo_for_action,
|
||||
pc_open_ceo_run,
|
||||
pc_mark_comment_routed,
|
||||
pc_wake_ceo_for_feedback_fold,
|
||||
pc_wake_curator_for_final,
|
||||
pc_wake_for_precedent_extraction,
|
||||
@@ -4660,8 +4661,9 @@ async def api_case_agents(case_number: str):
|
||||
class AgentCommentRequest(BaseModel):
|
||||
body: str
|
||||
issue_id: str | None = None
|
||||
# "פתח הרצה חדשה" — open a fresh CEO-owned run for this instruction instead of
|
||||
# appending to an existing issue (agents-tab target selector). Overrides issue_id.
|
||||
# "פתח הרצה חדשה" — skip the comment entirely and only open a CEO run. Kept for
|
||||
# the agents-tab target selector; the default path below now opens a run too, so
|
||||
# this only differs in *not* recording the instruction on the parent thread.
|
||||
new_run: bool = False
|
||||
|
||||
|
||||
@@ -4705,10 +4707,31 @@ async def api_post_agent_comment(case_number: str, req: AgentCommentRequest):
|
||||
|
||||
result = await pc_post_comment(target["id"], target["company_id"], req.body)
|
||||
|
||||
# The comment alone reaches nobody: a case issue awaiting the chair is
|
||||
# `in_review` and owned by a human, so Paperclip cancels any wakeup on it
|
||||
# (`issue_assignee_changed` → "wake the new owner" → the owner is a person).
|
||||
# The plugin sweep that was meant to catch this delivers the instruction as
|
||||
# `payload.prompt`, which the runner drops. So the comment is recorded for the
|
||||
# thread, and the instruction is carried by a CEO-owned child run — the one
|
||||
# path verified to reach the agent (TaskMaster #228; see paperclip-quirks.md).
|
||||
run = await pc_open_ceo_run(case_number, req.body)
|
||||
if run.get("status") != "ok":
|
||||
raise HTTPException(
|
||||
502,
|
||||
f"ההערה נשמרה אך פתיחת ההרצה נכשלה — הסוכן לא יטפל בה: "
|
||||
f"{run.get('reason', 'unknown')}",
|
||||
)
|
||||
# Claim the comment so the sweep does not re-route it: the CEO answers on the
|
||||
# child issue, leaving the parent with no agent reply, which the sweep reads
|
||||
# as "still pending" forever.
|
||||
await pc_mark_comment_routed(target["id"], result.get("comment_id", ""))
|
||||
|
||||
# Echo the resolved target so the UI can show where it landed and flag it
|
||||
# when the chair explicitly targeted a closed issue (whose wakeup is skipped).
|
||||
result["issue_identifier"] = target.get("identifier", "")
|
||||
result["issue_status"] = target.get("status", "")
|
||||
result["run_issue_id"] = run["issue_id"]
|
||||
result["run_issue_identifier"] = run.get("identifier", "")
|
||||
return result
|
||||
|
||||
|
||||
@@ -4725,8 +4748,10 @@ async def api_post_interaction_response(
|
||||
):
|
||||
"""Submit a user's answer to a Paperclip issue-thread interaction.
|
||||
|
||||
Routes to /respond | /accept | /reject based on `action`. Paperclip
|
||||
auto-wakes the issue assignee after a successful submission.
|
||||
Routes to /respond | /accept | /reject based on `action`, then opens a CEO run
|
||||
carrying the answer. Paperclip's own `wake_assignee` cannot deliver it: the
|
||||
issue is owned by the chair while it waits for her, so the "assignee" it wakes
|
||||
is a person and no agent ever runs (TaskMaster #228).
|
||||
"""
|
||||
issues = await pc_get_case_issues(case_number)
|
||||
if not any(i["id"] == req.issue_id for i in issues):
|
||||
@@ -4738,7 +4763,7 @@ async def api_post_interaction_response(
|
||||
"reject": pc_reject_interaction,
|
||||
}
|
||||
try:
|
||||
return await handlers[req.action](
|
||||
result = await handlers[req.action](
|
||||
req.issue_id, req.interaction_id, req.payload,
|
||||
)
|
||||
except httpx.HTTPStatusError as e:
|
||||
@@ -4747,6 +4772,23 @@ async def api_post_interaction_response(
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"שגיאת Paperclip: {e}")
|
||||
|
||||
answer = json.dumps(req.payload, ensure_ascii=False)
|
||||
run = await pc_open_ceo_run(
|
||||
case_number,
|
||||
f'תשובת היו"ר לשאלה שהוצגה לה ({req.action}):\n\n{answer}\n\n'
|
||||
f"קרא את ה-interaction המקורי על ה-issue והמשך משם.",
|
||||
)
|
||||
if run.get("status") != "ok":
|
||||
raise HTTPException(
|
||||
502,
|
||||
f"התשובה נשמרה אך פתיחת ההרצה נכשלה — הסוכן לא ימשיך: "
|
||||
f"{run.get('reason', 'unknown')}",
|
||||
)
|
||||
if isinstance(result, dict):
|
||||
result["run_issue_id"] = run["issue_id"]
|
||||
result["run_issue_identifier"] = run.get("identifier", "")
|
||||
return result
|
||||
|
||||
|
||||
class InteractionDismissRequest(BaseModel):
|
||||
issue_id: str
|
||||
|
||||
Reference in New Issue
Block a user