Files
cowork-local/core/agent_command.py
T
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## Summary

epic r04 - begin refactor

## Change Type

- [x] Cowork feature
- [ ] Bug fix
- [ ] Core AI contribution
- [ ] Test / hardening
- [ ] Performance
- [ ] Documentation

## Related Work

Cowork Task:

Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets

Core AI Issue:

Core Task:

Related PR:

## Scope

What is intentionally included?

What is intentionally NOT included?

## Validation

- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual verification
- [ ] Regression check

Commands / evidence:

## Security Impact

Permission / credential / network / customer data impact:

## Compatibility

- [ ] No breaking change
- [ ] Breaking change documented

## Reviewer Notes

Anything Cowork reviewers should pay attention to.

---------

Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com>
Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com>
Co-authored-by: Vu Dam Tuan <vudt15@fpt.com>
Co-authored-by: Hiep Ha Van <hiephv3@fpt.com>
Co-authored-by: Lam Hoang Van <lamhv7@fpt.com>
Reviewed-on: #7
Co-authored-by: Duy Le Huu <duylh19@fpt.com>
2026-08-31 05:15:13 +00:00

123 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
"""Parse a ``/agent`` command typed in a chat box (Cowork parity with Co4E).
An *agent* directive applies a named agent PERSONA to the turn: its role +
instructions are prepended to the request as a system-style prefix, exactly the
way ``skills.parse_skill_command`` applies a skill. Only the WORK agents a user
composes with are callable here:
* built-in Co4E personas (``co4e_builtins.BUILTIN_AGENTS``) — includes the
delivery-lifecycle + Security agents;
* per-user custom Co4E agents (``co4e.list_custom_agents``).
Admin-defined agents (Monitoring → Agents Admin) are DELIBERATELY excluded:
they are system-management presets (help agent, task executors, …), not agents
to pick in Cowork/Co4E — so they never appear in the ``/agent`` list or the
Agent picker.
Only the persona (prompt) is applied — NOT a per-turn model/provider switch:
a Cowork panel runs several turns concurrently, so a shared model override would
race between them; the Agent picker remains the way to pin a model/provider.
Pure logic (no Qt) so it's unit-testable.
"""
from __future__ import annotations
import re
from typing import List, Optional, Tuple
# Same shape as skills.parse_skill_command: the command may sit mid-sentence, so
# trailing punctuation must not break recognition.
_CMD = re.compile(r"(?<!\S)/agent(?::([\w\-.]+))?(?=$|[\s.,;:!?)\]}»”’'\"、。」])",
re.DOTALL)
def _slug(name: str) -> str:
"""Định danh an toàn suy từ tên agent (dùng chung hàm với Co4E)."""
from .co4e import slugify
return slugify(name)
def collect_agents(shared_dir: str = "") -> List[dict]:
"""Every callable WORK agent as ``{slug, name, desc, persona, source}``,
de-duped by slug (built-in > custom). Admin agents are intentionally NOT
included — they are system-management presets, not Cowork/Co4E choices (see
the module docstring). ``shared_dir`` is kept for signature stability. Never
raises — a broken source is skipped so the command still works."""
out: List[dict] = []
seen: set[str] = set()
def _add(slug: str, name: str, desc: str, persona: str, source: str) -> None:
"""Thêm một agent vào danh sách gộp; bỏ qua nếu trùng slug hoặc thiếu persona.
Agent không có persona thì không dùng được — thêm vào chỉ làm bảng gợi ý dài
ra mà chọn vào lại không chạy.
"""
if not slug or slug in seen or not persona.strip():
return
seen.add(slug)
out.append({"slug": slug, "name": name, "desc": desc,
"persona": persona.strip(), "source": source})
try:
from .co4e_builtins import BUILTIN_AGENTS
for a in BUILTIN_AGENTS:
_add(a.slug, a.name, a.role,
f"You are the {a.role} agent — {a.name}.\n{a.instructions}", "builtin")
except Exception: # noqa: BLE001
pass
try:
from . import co4e
for ca in co4e.list_custom_agents():
_add(_slug(ca.name), ca.name, ca.role,
f"You are the {ca.role} agent — {ca.name}.\n{ca.instructions}", "custom")
except Exception: # noqa: BLE001
pass
return out
def _persona_block(agent: dict) -> str:
"""Khối prompt mô tả một agent, chèn vào đầu lượt chat khi người dùng gõ ``/agent:``."""
return f"## Agent: {agent['name']}\n{agent['persona']}"
def parse_agent_command(text: str, shared_dir: str = "") -> Tuple[str, str, Optional[str]]:
"""Parse a ``/agent`` command anywhere in ``text``.
Returns ``(prefix, request, info)``:
* ``prefix`` – agent persona to prepend to the agent prompt ('' if none)
* ``request`` – the message with the command stripped
* ``info`` – when not None, answer this inline (no agent turn)
Forms: ``/agent`` (list) · ``/agent:<name> <req>`` (apply that agent).
"""
raw = (text or "").strip()
m = _CMD.search(raw)
if not m:
return "", text, None
slug = m.group(1)
if slug:
slug = slug.rstrip(".") # "/agent:name." — the dot was sentence punctuation
rest = (raw[:m.start()] + " " + raw[m.end():]).strip()
rest = re.sub(r"[ \t]{2,}", " ", rest)
agents = collect_agents(shared_dir)
if slug:
low = slug.lower()
match = next((a for a in agents if a["slug"] == low or a["name"].lower() == low), None)
if match is None:
return "", text, (f"Agent `{slug}` not found. "
"Type `/agent` to see the available agents.")
if not rest:
return "", text, (f"Agent **{match['name']}** selected — add your request, e.g. "
f"`/agent:{match['slug']} review this file`.")
return _persona_block(match), rest, None
# Bare /agent → list what's available.
if not agents:
return "", text, ("No agents found. Add one in Agents Admin, or a custom Flow agent.")
listing = "\n".join(
f"- `/agent:{a['slug']}` — **{a['name']}**"
+ (f" _({a['desc']})_" if a.get("desc") else "")
for a in agents)
return "", text, ("**Available agents**\n" + listing
+ "\n\nApply one with `/agent:<name> <your request>`.")