"""主线 /goal 命令解析:发给主线会话的一句话里自动建 goal。 背景:此前主线不会因为收到“请在这条主线开启一个持久 /goal” 这样的自然语言就自己变成 goal 任务——goal 只在建任务时由 调用方用 goal_mode=1 指定,或在 /continue 时显式勾 goal 模式。 本模块把“/goal”当成显式命令标记,让发给主线的消息本身就能 触发转换,调用方不用再知道任务当前是进行中还是已终态。 解析规则(刻意收紧,防误触发): - 文本里必须出现字面 "/goal"(全角 / 也认,大小写不敏感)。 - 光出现还不够,必须是命令形状,二者居一: 1. 文本以 /goal 开头(最显式的命令); 2. /goal 后面紧跟分隔符(空格、,、:、、)或“目标”标记, 例如“/goal 收xxx”“/goal,目标是收xxx”; 3. /goal 前面有开启类动词(开启/建立/创建/添加/设为/设置/ 开一个),例如“请开启一个持久/goal收xxx”。 只是在讨论里提到“/goal 的用法”这种不算命令。 - 目标取 /goal 之后的部分:先去分隔符,再去“目标是/目标为/ 目标:/目标:”或“持久目标是/为/:/:”这类前缀,最后去尾句号。 - 返回值:不是命令返回 None;是命令但没给目标返回 ""(调用方 据此回 400“目标不能为空”,而不是当成普通消息悄悄排队); 否则返回目标文本。 本模块只做纯文本解析,不碰数据库、不发请求,server 与 worker 共用同一份解析,避免两端对“是不是 /goal 命令”判断不一致。 """ import re _GOAL_RE = re.compile(r"/goal(?![A-Za-z])", re.IGNORECASE) _SEPARATORS = " \t\r\n::,,、" _TARGET_MARKERS = ( "持久目标是", "持久目标为", "持久目标:", "持久目标:", "目标是", "目标为", "目标:", "目标:", ) _OPEN_VERBS = ("开启", "建立", "创建", "添加", "设为", "设置", "开一个") def _strip_target(rest): """去掉目标前的分隔符与“目标是”类前缀、尾句号。""" rest = rest.lstrip(_SEPARATORS) for marker in _TARGET_MARKERS: if rest.startswith(marker): rest = rest[len(marker):].lstrip(_SEPARATORS) break rest = rest.strip() while rest.endswith("。"): rest = rest[:-1].rstrip() return rest def parse_goal_command(text): """解析主线消息里的 /goal 命令,返回目标或 None/""(见模块说明)。""" if not isinstance(text, str): return None s = text.strip().replace("/", "/") if not s: return None match = _GOAL_RE.search(s) if match is None: return None before = s[:match.start()] rest = s[match.end():] stripped = s.lstrip() is_command = False if stripped.lower().startswith("/goal"): is_command = True elif rest and rest[0] in _SEPARATORS: is_command = True elif rest.startswith("目标"): is_command = True elif any(verb in before for verb in _OPEN_VERBS): is_command = True elif not rest: # 单独一句“/goal”:是命令,但没目标,调用方回 400。 is_command = True if not is_command: return None return _strip_target(rest)