fix: TransferEncodingError on task extraction failure
- brain.py: unwrap JSON array responses from Claude (occasional array instead of object caused AttributeError → broken chunked stream) - main.py: wrap extract_task_fields() in try/except so any extraction failure gracefully falls back to regex — generator always completes - tools.py: _resolve_project() uses alphanumeric-stripped matching so "Generaladm" resolves to General/Admin via identifier prefix match. Word-overlap fallback added. Default changed from Agent Ecosystem to General/Admin (correct catch-all bucket) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,11 @@ async def extract_task_fields(message: str) -> tuple[dict, dict]:
|
|||||||
if content.startswith("json"):
|
if content.startswith("json"):
|
||||||
content = content[4:]
|
content = content[4:]
|
||||||
fields = json.loads(content.strip())
|
fields = json.loads(content.strip())
|
||||||
|
# Claude occasionally wraps the object in an array — unwrap it
|
||||||
|
if isinstance(fields, list):
|
||||||
|
fields = fields[0] if fields else {}
|
||||||
|
if not isinstance(fields, dict):
|
||||||
|
fields = {}
|
||||||
return fields, usage
|
return fields, usage
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"extract_task_fields failed: {e}")
|
logger.warning(f"extract_task_fields failed: {e}")
|
||||||
|
|||||||
@@ -686,8 +686,12 @@ async def chat_completions(req: ChatRequest):
|
|||||||
|
|
||||||
elif intent == "task":
|
elif intent == "task":
|
||||||
# LLM extraction — handles any natural language phrasing
|
# LLM extraction — handles any natural language phrasing
|
||||||
|
try:
|
||||||
fields, usage = await extract_task_fields(user_message)
|
fields, usage = await extract_task_fields(user_message)
|
||||||
log_usage("task_extract", usage["prompt_tokens"], usage["completion_tokens"])
|
log_usage("task_extract", usage["prompt_tokens"], usage["completion_tokens"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"extraction failed, falling back to regex: {e}")
|
||||||
|
fields = {}
|
||||||
|
|
||||||
title = fields.get("title") or extract_task_title(user_message)
|
title = fields.get("title") or extract_task_title(user_message)
|
||||||
project_hint = fields.get("project") or extract_project_name(user_message)
|
project_hint = fields.get("project") or extract_project_name(user_message)
|
||||||
|
|||||||
+22
-3
@@ -147,14 +147,33 @@ async def get_plane_issues(project_hint: str) -> tuple[str, list[dict]]:
|
|||||||
|
|
||||||
|
|
||||||
def _resolve_project(projects: list[dict], hint: str | None) -> dict:
|
def _resolve_project(projects: list[dict], hint: str | None) -> dict:
|
||||||
|
import re as _re
|
||||||
if hint:
|
if hint:
|
||||||
hint_lower = hint.lower()
|
hint_lower = hint.lower()
|
||||||
|
hint_alpha = _re.sub(r"[^a-z0-9]", "", hint_lower) # "generaladm" from "Generaladm"
|
||||||
|
|
||||||
for p in projects:
|
for p in projects:
|
||||||
if hint_lower in p["name"].lower():
|
name_lower = p["name"].lower()
|
||||||
|
ident_lower = p.get("identifier", "").lower()
|
||||||
|
name_alpha = _re.sub(r"[^a-z0-9]", "", name_lower) # "generaladmin"
|
||||||
|
# Exact or substring name match
|
||||||
|
if hint_lower in name_lower or name_lower in hint_lower:
|
||||||
return p
|
return p
|
||||||
# Default: Agent Ecosystem
|
# Identifier match (e.g. "generaladm" == "generaladm")
|
||||||
|
if hint_alpha == ident_lower or hint_alpha == _re.sub(r"[^a-z0-9]", "", ident_lower):
|
||||||
|
return p
|
||||||
|
# Stripped prefix match (e.g. "generaladm" starts "generaladmin")
|
||||||
|
if name_alpha.startswith(hint_alpha) or hint_alpha.startswith(name_alpha):
|
||||||
|
return p
|
||||||
|
# Word overlap fallback — any significant word of hint in project name
|
||||||
|
hint_words = {w for w in hint_lower.split() if len(w) > 2}
|
||||||
for p in projects:
|
for p in projects:
|
||||||
if "agent" in p["name"].lower():
|
if hint_words & set(p["name"].lower().split()):
|
||||||
|
return p
|
||||||
|
|
||||||
|
# Default: General / Admin (catch-all bucket)
|
||||||
|
for p in projects:
|
||||||
|
if "general" in p["name"].lower():
|
||||||
return p
|
return p
|
||||||
return projects[0]
|
return projects[0]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user