Files
microdao-daarion/crews/agromatrix_crew/operator_commands.py
Apple ef3473db21 snapshot: NODE1 production state 2026-02-09
Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.

Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles

Excluded from snapshot: venv/, .env, data/, backups, .tgz archives

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 08:46:46 -08:00

365 lines
11 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import re
import shlex
from agromatrix_tools import tool_dictionary_review as review
CATEGORIES = {"field","crop","operation","material","unit"}
def is_operator(user_id: str | None, chat_id: str | None) -> bool:
allowed_ids = [s.strip() for s in os.getenv('AGX_OPERATOR_IDS', '').split(',') if s.strip()]
allowed_chat = os.getenv('AGX_OPERATOR_CHAT_ID', '').strip()
if allowed_chat and chat_id and str(chat_id) != allowed_chat:
return False
if allowed_ids and user_id and str(user_id) in allowed_ids:
return True
return False
def parse_operator_command(text: str):
parts = shlex.split(text)
if not parts:
return None
cmd = parts[0].lstrip('/')
args = parts[1:]
return {"cmd": cmd, "args": args}
def _wrap(summary: str, details: dict | None = None):
return {
"status": "ok",
"summary": summary,
"artifacts": [],
"tool_calls": [],
"next_actions": [],
"details": details or {}
}
def _extract_ref(text: str) -> str | None:
m = re.search(r"pending\.jsonl:\d+", text)
return m.group(0) if m else None
def _extract_index(text: str) -> int | None:
m = re.search(r"(\d{1,3})", text)
if not m:
return None
try:
return int(m.group(1))
except Exception:
return None
def _extract_limit(text: str, default: int = 10) -> int:
m = re.search(r"(?:до|limit)\s*(\d{1,3})", text)
if m:
try:
return int(m.group(1))
except Exception:
return default
m = re.search(r"(\d{1,3})", text)
if m:
try:
return int(m.group(1))
except Exception:
return default
return default
def _extract_category(text: str) -> str | None:
t = text.lower()
if 'один' in t or 'unit' in t:
return 'unit'
if 'операц' in t or 'operation' in t:
return 'operation'
if 'культур' in t or 'crop' in t:
return 'crop'
if 'матеріал' in t or 'material' in t:
return 'material'
if 'поле' in t or 'field' in t:
return 'field'
return None
def _extract_canonical_id(text: str) -> str | None:
m = re.search(r"(?:як|as)\s+([\w\-:.]+)", text, flags=re.IGNORECASE)
if m:
return m.group(1)
return None
def _extract_reason(text: str) -> str:
if ':' in text:
return text.split(':', 1)[1].strip()
m = re.search(r"(?:бо|через)\s+(.+)$", text, flags=re.IGNORECASE)
if m:
return m.group(1).strip()
return ''
def _resolve_ref_by_index(last_list: list | None, idx: int | None) -> str | None:
if not last_list or not idx:
return None
if idx < 1 or idx > len(last_list):
return None
item = last_list[idx - 1]
return item.get('pending_ref') or item.get('ref')
def handle_whoami(user_id: str | None, chat_id: str | None):
summary = "user_id: {}\nchat_id: {}".format(user_id or '', chat_id or '')
return _wrap(summary)
def handle_pending(limit=10, category=None):
items = review.list_pending(limit=limit, category=category)
lines = []
pending_items = []
for item in items:
sug = item.get('suggestions', [])[:5]
sug_s = ', '.join([f"{s['id']}({s['score']:.2f})" for s in sug])
lines.append(f"{item['pending_ref']} | {item.get('category')} | {item.get('raw_term')} | {sug_s}")
pending_items.append({
'pending_ref': item.get('pending_ref'),
'pending_id': item.get('pending_id'),
'raw_term': item.get('raw_term'),
'category': item.get('category'),
'suggestions': [{"id": s.get('id'), "score": s.get('score')} for s in sug]
})
summary = "\n".join(lines) if lines else "Немає pending записів."
return _wrap(summary, {"count": len(items), "pending_items": pending_items})
def handle_pending_show(ref: str):
detail = review.get_pending_detail(ref)
if not detail:
return _wrap('Не знайдено pending запис')
lines = [
f"ref: {detail.get('ref')}",
f"category: {detail.get('category')}",
f"raw_term: {detail.get('raw_term')}",
f"ts: {detail.get('ts')}",
f"status: {detail.get('status')}"
]
if detail.get('decision'):
lines.append(f"decision: {detail.get('decision')}")
if detail.get('reason'):
lines.append(f"reason: {detail.get('reason')}")
lines.append('suggestions:')
suggestions = detail.get('suggestions') or []
if suggestions:
for s in suggestions:
score = s.get('score')
score_s = f"{score:.2f}" if isinstance(score, (int, float)) else "n/a"
lines.append(f"- {s.get('id')} ({score_s})")
else:
lines.append('- (none)')
return _wrap("\n".join(lines), detail)
def handle_approve(ref, map_to=None, create_new=None, category=None, name=None, id_=None, apply=False):
if map_to:
action = {"type": "map_to_existing", "canonical_id": map_to}
elif create_new:
action = {"type": "create_new_entry", "canonical_id": id_ or '', "canonical_name": name or ''}
else:
action = {"type": "add_synonym", "canonical_id": map_to}
res = review.approve_pending(ref, action)
if apply:
review.apply_resolutions()
return _wrap(f"approved {ref}", {"record": res, "applied": apply})
def handle_reject(ref, reason):
res = review.reject_pending(ref, reason)
return _wrap(f"rejected {ref}", {"record": res})
def handle_apply():
count = review.apply_resolutions()
return _wrap(f"applied {count}")
def handle_stats():
stats = review.stats()
return _wrap(f"open={stats['open']} approved={stats['approved']} rejected={stats['rejected']}", stats)
def route_operator_text(text: str, user_id: str | None, chat_id: str | None, last_pending_list: list | None = None):
if not is_operator(user_id, chat_id):
return {
"status": "error",
"summary": "Недостатньо прав",
"artifacts": [],
"tool_calls": [],
"next_actions": []
}
t = text.strip().lower()
# list pending
if any(k in t for k in ['покажи', 'показати', 'невпізнан', 'непізнан', 'pending', 'невідом']):
category = _extract_category(t)
limit = _extract_limit(t, default=10)
if limit < 1:
limit = 1
if limit > 100:
limit = 100
if category and category not in CATEGORIES:
return _wrap('unknown category')
return handle_pending(limit=limit, category=category)
# stats
if any(k in t for k in ['статист', 'скільки pending', 'pending stats']):
return handle_stats()
# apply
if any(k in t for k in ['застосуй', 'apply', 'застосувати', 'застосуй зміни']):
return handle_apply()
# details
if any(k in t for k in ['деталі', 'detail', 'подробиц', 'покажи деталі']):
ref = _extract_ref(t)
if not ref:
idx = _extract_index(t)
ref = _resolve_ref_by_index(last_pending_list, idx)
if not ref:
return _wrap('Немає ref або контексту для деталей')
return handle_pending_show(ref)
# approve
if any(k in t for k in ['підтверд', 'approve', 'схвали']):
ref = _extract_ref(t)
if not ref:
idx = _extract_index(t)
ref = _resolve_ref_by_index(last_pending_list, idx)
if not ref:
return _wrap('Немає ref або контексту для підтвердження')
canonical_id = _extract_canonical_id(t)
if not canonical_id:
return _wrap('Вкажіть canonical_id після "як"')
return handle_approve(ref, map_to=canonical_id)
# reject
if any(k in t for k in ['відхил', 'reject', 'забракуй']):
ref = _extract_ref(t)
if not ref:
idx = _extract_index(t)
ref = _resolve_ref_by_index(last_pending_list, idx)
if not ref:
return _wrap('Немає ref або контексту для відхилення')
reason = _extract_reason(text)
if not reason:
return _wrap('Вкажіть причину відхилення')
return handle_reject(ref, reason)
return None
def route_operator_command(text: str, user_id: str | None, chat_id: str | None):
parsed = parse_operator_command(text)
if not parsed:
return None
if not is_operator(user_id, chat_id):
return {
"status": "error",
"summary": "Недостатньо прав",
"artifacts": [],
"tool_calls": [],
"next_actions": []
}
cmd = parsed['cmd']
args = parsed['args']
if cmd == 'whoami':
return handle_whoami(user_id, chat_id)
if cmd == 'pending_show':
if not args:
return _wrap('Потрібен ref')
return handle_pending_show(args[0])
if cmd == 'pending':
limit = 10
category = None
i = 0
while i < len(args):
if args[i] == '--limit' and i + 1 < len(args):
try:
limit = int(args[i+1])
except Exception:
limit = 10
i += 2
continue
if args[i] == '--category' and i + 1 < len(args):
category = args[i+1]
i += 2
continue
i += 1
# clamp
if limit < 1:
limit = 1
if limit > 100:
limit = 100
if category and category not in CATEGORIES:
return _wrap('unknown category')
return handle_pending(limit=limit, category=category)
if cmd == 'approve':
if not args:
return _wrap('missing ref')
ref = args[0]
map_to = None
create_new = False
category = None
name = None
id_ = None
apply = False
i = 1
while i < len(args):
if args[i] == 'map_to':
map_to = args[i+1]
i += 2
elif args[i] == '--apply':
apply = True
i += 1
elif args[i] == 'create_new':
create_new = True
i += 1
elif args[i] == 'category':
category = args[i+1]
i += 2
elif args[i] == 'name':
name = args[i+1]
i += 2
elif args[i] == 'id':
id_ = args[i+1]
i += 2
else:
i += 1
if apply:
allow_apply = os.getenv('AGX_ALLOW_APPLY', '0') == '1' or os.getenv('AGX_OPS_MODE', '0') == '1'
if not allow_apply:
return _wrap('apply_not_allowed')
return handle_approve(ref, map_to=map_to, create_new=create_new, category=category, name=name, id_=id_, apply=apply)
if cmd == 'reject':
if len(args) < 2:
return _wrap('missing ref or reason')
ref = args[0]
reason = ' '.join(args[1:])
return handle_reject(ref, reason)
if cmd == 'apply_dict':
return handle_apply()
if cmd == 'pending_stats':
return handle_stats()
return _wrap('unknown command')