session-mechanism: 修复钩子静默失效 + 3 处判据缺陷;禁「变相征询」
1) stop-dialog-guard: session_budget() 早退路径返回 2 值、末尾返回 3 值,调用方按 3 值解包
⇒ transcript > 64 MiB 时每轮 ValueError。因 fail-open(异常仍 exit 0),
宿主零报错、install.py --verify 只判 rc=0 ⇒ 假绿;实测 86 条 EXCEPTION,
死掉的是整条(水位/收口、接续机制起点、预算告警、门禁自检、路径自检)。
2) session-rules-check 三处判据:
· hook_reg 按旧文件名找 ⇒ 合并成 prompt-guards.py 后每轮假红 ⇒ 改为一组可接受名
· snap_sync 拿 mtime 当内容判据 ⇒ 连续 4 天假红 ⇒ 改为复用抽取器本体比对内容
(变异对照:截断快照能报 fail,非恒绿)
· mem_ptr 只查全局技能根 ⇒ 工作区自带技能被判悬空 ⇒ 改查「全局 ∪ 工作区」
3) pitfalls 新增 P0-95(改判据必须重跑变异对照;fail-open + 只看 rc=0 = 假绿温床)
4) 回复排版核心块新增「变相征询同样禁止」(先只报不动/等你发话/我倾向X你看呢
这类不带选项的待定清单,一律按待拍板项写:问题+说明+各候选优缺点+倾向)
This commit is contained in:
1 parent
19101acd65
commit
64dd82073b
21 files changed
+8444
-4523
No files matched your search
@@ -96,6 +96,7 @@ except Exception:
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
@@ -132,15 +133,57 @@ def _resolve_ws() -> Path:
|
||||
|
||||
|
||||
WS = _resolve_ws()
|
||||
CFG = HERE / "collabd.config.json"
|
||||
|
||||
|
||||
def _resolve_cfg() -> Path:
|
||||
"""🔴🔴 部署配置解析(2026-10-06 修)—— ⛔ **不许写死成技能目录**。
|
||||
|
||||
**病根**:原为 `CFG = HERE / "collabd.config.json"`(`HERE` = **技能包目录**),
|
||||
而按定则「**技能就是技能、程序就是程序,谁用产生的文件放在他自己那里**」,
|
||||
**技能目录里⛔ 不放生产配置** ⇒ 该文件恒不存在 ⇒ `_load(CFG,{})` 恒返回 `{}`。
|
||||
|
||||
**症状(不报错、不崩溃,只是判定那一路永远读不到)**:
|
||||
· `taskgraph` 取默认 `INBOX/taskgraph.json` ⇒ 目标换了图没换时**照旧踩**
|
||||
(vibe-product 2026-10-06 实测:配置里已改指 proto-board,而 `goals_open()`
|
||||
仍报 `bad=['任务图读不到(taskgraph.json)']` —— 即**改了配置不生效**);
|
||||
· 其余读 `CFG` 的命令(`wake_enable`/`targets` / 写回配置)**全部作用在一个不存在的文件上**。
|
||||
|
||||
✅ 与 `collabd._cfg_candidates()` **同款三级**(⛔ 不新造顺序,否则又是一处口径分叉):
|
||||
① 环境变量 `COLLABD_CONFIG`(钩子/启动器显式指定,首选)
|
||||
② `<工作区>/.workbuddy/collab/collabd.config.json`(使用方**标准落点**)
|
||||
③ 都不在 ⇒ 回落到旧的 `HERE/collabd.config.json` **并打醒目告警**(⛔ 不静默)。
|
||||
"""
|
||||
_env = os.environ.get("COLLABD_CONFIG")
|
||||
if _env and Path(_env).is_file():
|
||||
return Path(_env)
|
||||
_std = WS / ".workbuddy" / "collab" / "collabd.config.json"
|
||||
if _std.is_file():
|
||||
return _std
|
||||
_legacy = HERE / "collabd.config.json"
|
||||
sys.stderr.write(
|
||||
"⚠️ goalctl:没找到部署配置(`COLLABD_CONFIG` 未设、`%s` 不存在)"
|
||||
"⇒ 回落到 `%s`\n" % (_std, _legacy))
|
||||
return _legacy
|
||||
|
||||
|
||||
CFG = _resolve_cfg()
|
||||
INBOX = WS / "tmp" / "supervise-inbox"
|
||||
GOAL, TASKS = INBOX / "goal.json", INBOX / "tasks.json"
|
||||
# 🔴🔴 2026-10-05 **历史目标归档目录**(用户口径「一个工作区 历史的旧的目标会有很多个」)。
|
||||
# 判据:`goal.json` = **唯一当前目标**(同时只有一个);换目标时**旧的必须移到这里**,
|
||||
# ⛔ 不许原地覆盖丢掉(本目录此前**代码零实现** —— 那份 `机制自检-20261004.json`
|
||||
# 是手工造的 ⇒ 换目标 = 旧目标消失,只留用户手写的 `_前身` 一行字)。
|
||||
GOALS_DIR = INBOX / "goals"
|
||||
STOPPER = HERE / "stop-collab.py"
|
||||
HOSTDB = Path(os.environ.get("CODEBUDDY_CONFIG_DIR") or os.path.expanduser("~/.workbuddy")) / "workbuddy.db"
|
||||
BAK = WS / ".workbuddy" / "collab" / ("bak-goalctl-%s" % time.strftime("%Y%m%d"))
|
||||
|
||||
ARGS = [a for a in sys.argv[1:] if not a.startswith("-")]
|
||||
DO = "--yes" in sys.argv
|
||||
# 🔴🔴 2026-10-05 **「用户已确认切换目标」的显式开关**(用户口径:换目标**需要用户确认**)。
|
||||
# 判据:`--switch-goal` = **人**说"同意放弃当前目标";`--yes` = 只是"干跑转真写"。
|
||||
# ⛔ **两者不许合并** —— 合并就等于把"确认"降级成"顺手加个 yes",闸门失效。
|
||||
SWITCH_OK = "--switch-goal" in sys.argv
|
||||
CMD = (ARGS[0] if ARGS else "status")
|
||||
NAME = (ARGS[1] if len(ARGS) > 1 else "")
|
||||
|
||||
@@ -223,6 +266,69 @@ def read_automations() -> list:
|
||||
return out
|
||||
|
||||
|
||||
def _acc_is_pass(v) -> bool:
|
||||
"""🔴 一条验收判据「算不算过」—— 🔴 **本文件唯一实现,且⛔ 不自带词表**。
|
||||
|
||||
🔴🔴 **2026-10-06 立**(vibe-product 实测踩到,属"判据分叉"第 N 次复发):
|
||||
|
||||
**病根**:`board.py::acc_is_pass` 自称「**全库唯一实现**」,并在注释里点明
|
||||
三处判据(`board.py` / `collabd.py::_acc_is_pass` / `board.html::accIsPass`)
|
||||
**必须同款**。🔴 **实际是四处** —— 本文件 `goals_open()` 里那句
|
||||
`if str(acc[k]) != "pass":` **被漏掉了**,既不归一化、也不复用真身。
|
||||
|
||||
**症状(不报错、不崩溃,只少说一句真话)**:本机制的存值**真源就是中文**
|
||||
(`过(PID 3552 …)`/`已过|…`/`达(实测 7 个…)`)⇒ 死板比 `"pass"` ⇒
|
||||
**已通过的验收被全部计入 why** ⇒ 恒判「未完成」。
|
||||
实测(vibe-product 2026-10-06):四条验收**全过**(`V1=pass` 已归一化存值),
|
||||
而 `goalctl.py status` 仍会因历史中文值报「未完成」;用户原话
|
||||
「**为什么完成了还说没完成**」。⇒ 与 `collabd._acc_is_pass()` 的沿革逐字同族。
|
||||
|
||||
**正解=复用真身,⛔ 不复制词表**(复制就是下一次漂的来源):
|
||||
首选 `collabd._acc_is_pass()`(它自己又首选 `board.acc_is_pass()`);
|
||||
拿不到(副本被单独拷走 / 循环导入)⇒ **fail-closed 声明式兜底**:
|
||||
用与 `board.py::ACC_PASS_WORDS` **同款**的白名单判,并在 stderr 留一行。
|
||||
⛔ **绝不静默返回 True/False** —— 那会把"代码没同步"伪装成"判据没过/过了"。
|
||||
"""
|
||||
try:
|
||||
import sys as _sys
|
||||
_sd = str(HERE)
|
||||
if _sd not in _sys.path:
|
||||
_sys.path.insert(0, _sd)
|
||||
import collabd as _cd # ⚠️ 同目录 ⇒ 无副作用
|
||||
return bool(_cd._acc_is_pass(v))
|
||||
except Exception as _e:
|
||||
try:
|
||||
sys.stderr.write("⚠️ goalctl._acc_is_pass: 拿不到 collabd(%s)⇒ 走兜底白名单\n" % _e)
|
||||
except Exception:
|
||||
pass
|
||||
# ── fail-safe 兜底:与 `board.py::ACC_PASS_WORDS` / `ACC_ADV_PREFIX` **同款**
|
||||
# (⛔ 改了那边必须改这里 —— 两处都列出来,免得只改词表漏掉副词)
|
||||
s = str(v or "").strip()
|
||||
if not s:
|
||||
return False # 空 ⇒ 判不出来 ⇒ ⛔ 不算过(fail-closed)
|
||||
_head = re.split(r"[((]", s, maxsplit=1)[0] # 丢掉括注说明
|
||||
for _seg in re.split(r"[::]", _head): # ⛔ 逐段找,⛔ 不"只取最后一段"
|
||||
_t = _seg.strip().lstrip("*").strip().lower()
|
||||
for _w in ("pass", "过", "通过", "达", "达标", "合格", "完成"):
|
||||
if _t.startswith(_w):
|
||||
return True
|
||||
# 🔴 剥"完成副词"再比(与 `board.py::acc_is_pass` 同款)。
|
||||
# ⚠️ 否定副词(未/不/没/非/待)**不在**表里 ⇒ 剥不动 ⇒ 仍 fail-closed。
|
||||
_t2 = _t
|
||||
for _ in range(3):
|
||||
_cut = ""
|
||||
for _a in ("已", "已经", "均", "都", "经复核", "复核后"):
|
||||
if _t2.startswith(_a) and len(_a) > len(_cut):
|
||||
_cut = _a # 取**最长**匹配(`已经` 优先于 `已`)
|
||||
if not _cut:
|
||||
break
|
||||
_t2 = _t2[len(_cut):].strip().lstrip("*").strip()
|
||||
for _w in ("pass", "过", "通过", "达", "达标", "合格", "完成"):
|
||||
if _t2.startswith(_w):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def goals_open() -> dict:
|
||||
"""目标是否仍未完成 —— 三路并集(与 `collabd.goals_open()` 同判据,⛔ 不缩水)。
|
||||
|
||||
@@ -249,7 +355,10 @@ def goals_open() -> dict:
|
||||
acc = (read_goal().get("acceptance_state") or {})
|
||||
eff = [k for k in acc if not str(k).startswith("_")] # 🔴 **有效项**=键不以 `_` 开头
|
||||
for k in eff:
|
||||
if str(acc[k]) != "pass":
|
||||
# 🔴🔴 2026-10-06 **改走真身**(原为 `str(acc[k]) != "pass"`,死板比字面值)——
|
||||
# 存值真源是**中文**(`过(…)`/`已过|…`/`达(…)`)⇒ 死板比 ⇒ 已通过的
|
||||
# 验收被计入 why ⇒ 恒判「未完成」。详见 `_acc_is_pass()` 的 docstring。
|
||||
if not _acc_is_pass(acc[k]):
|
||||
why.append("验收 %s=%s" % (k, acc[k]))
|
||||
if not eff:
|
||||
# 🔴 2026-09-30 修(**"读不到"被当成了"全过"**):判据是"**有没有有效项**",
|
||||
@@ -464,7 +573,7 @@ def declare() -> int:
|
||||
⇒ 本命令是"说明"的**唯一落点**;⛔ 技能侧不许预设类别、⛔ 不许从文件名推。
|
||||
|
||||
用法:
|
||||
python goalctl.py declare --title "…" [--why "…"] [--topics "A,B"] [--kpi "V1=pass;V2=未过"]
|
||||
python goalctl.py declare --title "…" [--why "…"] [--topics "A,B"] [--kpi "V1=过;V2=未过"]
|
||||
python goalctl.py declare --title "…" --topics "" # 显式清空类别(回到单类别回落)
|
||||
⛔ 默认 **dry-run**;真写加 `--yes`(与 stop/start 同一个语义开关)。
|
||||
|
||||
@@ -486,7 +595,14 @@ def declare() -> int:
|
||||
· `--topics` 省略 ⇒ **不动**(⛔ 不拿旧值凑数);给了 ⇒ 整表替换,并记 `topics_declared_at/by`;
|
||||
· `--kpi` 省略 ⇒ **不动** `acceptance_state`;给了 ⇒ 整表重写,且**会点明丢掉了哪些说明行**。
|
||||
"""
|
||||
title = _opt("title") or NAME
|
||||
# ⚠️ **没给 `--title` ⇒ 保持现有标题**(⛔ 不回落成 `NAME`=工作区名)。
|
||||
# 🔴 2026-10-05 实测踩到:旧写法 `_opt("title") or NAME` 在"只想补 `--why`/`--kpi`"
|
||||
# (即**同一目标在细化**,标题根本没打算动)时,会把标题**悄悄改成工作区名**
|
||||
# ⇒ 触发换目标闸 ⇒ **把一个纯细化动作拦成"未确认换目标"**(放大成事故)。
|
||||
# 判据:`--title` 是**显式动作**;没给就是"不动它"(与 `--topics`/`--kpi` 同一规矩)。
|
||||
# ⚠️ 只有**一个目标都没有**(首次声明)时,才允许回落到 `NAME`。
|
||||
_t_given = _opt("title", _SURE)
|
||||
title = (_t_given if _t_given is not _SURE else "") or str(read_goal().get("title") or "") or NAME
|
||||
why = _opt("why", _SURE)
|
||||
topics = _opt("topics", _SURE)
|
||||
kpi = _opt("kpi", _SURE)
|
||||
@@ -498,10 +614,67 @@ def declare() -> int:
|
||||
return 2
|
||||
|
||||
g = read_goal()
|
||||
print(" 现有目标:%s" % (g.get("title") or "(无)"))
|
||||
old_title = str(g.get("title") or "")
|
||||
print(" 现有目标:%s" % (old_title or "(无)"))
|
||||
print(" 目标改为:%s" % title)
|
||||
|
||||
# 🔴🔴🔴 2026-10-05 **换目标的确认闸 + 旧目标自动归档**(用户口径,逐字:
|
||||
# 「**一个工作区 同时只能执行一个目标,如果要切换目标,需要用户确认,
|
||||
# 然后切换和关注检查切换后的目标**」)。
|
||||
# 🔴 **改前取证**:`declare` **直接覆盖 `title`**、**从不归档旧目标**
|
||||
# (全文搜 `goals/` 零命中)⇒ 换目标 = 旧目标当场消失,只剩人手写的 `_前身` 一行。
|
||||
# ⇒ 补两道:
|
||||
# ① **确认闸**:标题变了 ⇒ **默认拒绝**,必须带 `--switch-goal` 明示"用户已确认切换"
|
||||
# (⛔ 与 `--yes` 分开:`--yes` 是"干跑转真写",⛔ 不是"用户同意换目标")。
|
||||
# ② **归档**:确认后把旧 `goal.json` **整份**移到 `goals/<旧标题>__<时间>.json`,
|
||||
# 并在新目标里留 `_前身目标` 指向归档件(⛔ 不靠人手写)。
|
||||
# ⚠️ 只改措辞/补 why/补 kpi(标题**没变**)⇒ ⛔ **不触发本闸**(那是同一目标在细化)。
|
||||
_is_switch = bool(old_title) and old_title != title
|
||||
if _is_switch and not SWITCH_OK:
|
||||
print("\n⛔ **这是「切换目标」,需要用户确认 —— 已拒绝写入。**")
|
||||
print(" 当前目标:%s" % old_title)
|
||||
print(" 拟换为 :%s" % title)
|
||||
print()
|
||||
print(" 用户口径(2026-10-05 定案,逐字):")
|
||||
print(" 「一个工作区 同时只能执行一个目标,如果要切换目标,**需要用户确认**,")
|
||||
print(" 然后切换和关注检查切换后的目标」")
|
||||
print()
|
||||
print(" ⇒ 请**先向用户确认**「要放弃当前目标、换成新的吗」;")
|
||||
print(" 得到确认后,在原命令上补 `--switch-goal` 重跑:")
|
||||
print(" python goalctl.py declare --title \"%s\" --switch-goal --yes" % title[:40])
|
||||
print(" ⚠️ `--switch-goal` ⛔ **不等于** `--yes` —— 前者是「用户同意换目标」,")
|
||||
print(" 后者只是「干跑转真写」。⛔ 不确认就带上它 = 伪造用户授权。")
|
||||
print("\n⇒ 本轮**一个字节都没写**。")
|
||||
return 3
|
||||
|
||||
if _is_switch:
|
||||
print(" 🔴 换目标已确认(`--switch-goal`)⇒ 旧目标将**整份归档**到 `%s`" % GOALS_DIR)
|
||||
new = dict(g)
|
||||
new["title"] = title
|
||||
# 🔴🔴🔴 2026-10-05 **换目标 ⇒ 自动把 `lifecycle` 复位成「进行中」**(用户定案:
|
||||
# 「一个工作区 同时只能执行一个目标,如果要切换目标,需要用户确认,**然后切换和关注检查
|
||||
# 切换后的目标**」)。
|
||||
# **改前事实**:`declare` **从不碰 `lifecycle`**(全文只在上文末尾打一句
|
||||
# 「别忘了同步 `lifecycle`(⛔ `declare` 不碰它)」让人手补)。
|
||||
# **为什么必须自动做**(不是"顺手"):
|
||||
# ① 旧目标做完了 ⇒ `lifecycle` 停在「已完成」⇒ 换到新目标后**机制照旧不拉**
|
||||
# (判据见 `collabd.py`:`life != 进行中` 时只在"队列还有非 done 件"才拉)
|
||||
# ⇒ **新目标没人检查** —— 与用户"切换后要关注检查"的要求**方向相反**。
|
||||
# ② 靠人手补=**又一条"要记得"的待办**(同族:用户 2026-10-05 原话「最好是记录会话,
|
||||
# 这样就不用每次会话都去做声明了」——凡"要人记"的都会忘)。
|
||||
# ⚠️ **不是破坏性动作**:旧 `goal.json` **整份归档**(下面紧跟的归档步),旧值可回查;
|
||||
# 且只在**已确认换目标**(`--switch-goal`)时做,⛔ 纯细化(只改措辞/补 why/kpi)不触发。
|
||||
# ⚠️ **只动 `lifecycle` 三件套**,⛔ 不碰 `acceptance_state`(那是上一条目标的判据,
|
||||
# 另有静默陷阱告警处理;清它会毁证据 —— 见下面那段 `--kpi` 警告)。
|
||||
if _is_switch:
|
||||
_old_life = str(g.get("lifecycle") or "").strip()
|
||||
new["lifecycle"] = "进行中"
|
||||
new["lifecycle_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
new["lifecycle_by"] = "goalctl declare --switch-goal(换目标自动复位)"
|
||||
new["lifecycle_why"] = ("换目标 ⇒ 复位(改前=%s);旧目标整份归档,可在归档件里回查"
|
||||
% (_old_life or "未声明"))
|
||||
print(" 🔴 `lifecycle` 自动复位:%s ⇒ 进行中" % (_old_life or "(未声明)"))
|
||||
print(" 理由:不复位 ⇒ 新目标**不被检查**(机制只在『进行中』时才拉检查会话)")
|
||||
# 🔴 2026-10-01 加(**静默陷阱告警**):换目标名却不给 `--kpi` ⇒ 上一条目标的验收判据
|
||||
# 会**原样留着** ⇒ 新目标可能**一声明就被判「全过」**、机制完全不排队不投递(实测踩到:
|
||||
# 新目标零件零判据,常驻程序却报「目标三路全过」,digest 写「没有可推进的活」)。
|
||||
@@ -512,7 +685,7 @@ def declare() -> int:
|
||||
print(" 🔴 **注意(静默陷阱)**:你没给 `--kpi` ⇒ 上一条目标的验收判据会**原样留下** ——")
|
||||
print(" 现存判据:%s" % "、".join(_oldacc))
|
||||
print(" ⚠️ 新目标可能**一声明就被判『全过』**、机制不排队不投递(判据不绑目标)。")
|
||||
print(" 要换判据:加 `--kpi \"V1=pass;V2=未过\"`(留空表也请显式给 `--kpi \"\"`)。")
|
||||
print(" 要换判据:加 `--kpi \"V1=过;V2=未过\"`(留空表也请显式给 `--kpi \"\"`)。")
|
||||
if why is not _SURE:
|
||||
new["why"] = why
|
||||
print(" 为什么 :%s" % (why or "(清空)"))
|
||||
@@ -528,7 +701,9 @@ def declare() -> int:
|
||||
acc, dropped = {}, []
|
||||
for seg in [x for x in str(kpi).replace(";", ";").split(";") if x.strip()]:
|
||||
if "=" not in seg:
|
||||
print(" ⚠️ 判据 `%s` 没有 `=` ⇒ 跳过(要 `名字=pass` 或 `名字=未过`)" % seg.strip())
|
||||
print(" ⚠️ 判据 `%s` 没有 `=` ⇒ 跳过(要 `名字=值`;值可写 `pass`,"
|
||||
"也可写中文 `过`/`已过`/`达` —— 判定层会归一化,⛔ 不是只认 `pass`)"
|
||||
% seg.strip())
|
||||
continue
|
||||
k, v = seg.split("=", 1)
|
||||
acc[k.strip()] = v.strip()
|
||||
@@ -543,12 +718,42 @@ def declare() -> int:
|
||||
|
||||
if not DO:
|
||||
print("\n⇒ 这是干跑(⛔ 一个字节都没写)。要真写:末尾加 `--yes`")
|
||||
if _is_switch:
|
||||
print(" ⚠️ 本命令是**换目标** ⇒ 真写时还需要 `--switch-goal`(确认闸已过则不必再加)")
|
||||
return 0
|
||||
|
||||
# 🔴🔴 2026-10-05 **旧目标整份归档**(换目标时;⛔ 不许原地覆盖丢掉)。
|
||||
# 落点:`tmp/supervise-inbox/goals/<旧标题≤40字>__<YYYYMMDD-HHMM>.json`
|
||||
# ⛔ 文件名净化:去掉 `/ \ : * ? " < > |`(Windows 非法字符)+ 折叠空白。
|
||||
if _is_switch:
|
||||
try:
|
||||
GOALS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
_safe = re.sub(r'[\\/:*?"<>|\r\n\t]+', "-", old_title).strip()[:40] or "untitled"
|
||||
_dst = GOALS_DIR / ("%s__%s.json" % (_safe, time.strftime("%Y%m%d-%H%M")))
|
||||
_n = 1
|
||||
while _dst.exists():
|
||||
_dst = GOALS_DIR / ("%s__%s_%d.json" % (_safe, time.strftime("%Y%m%d-%H%M"), _n))
|
||||
_n += 1
|
||||
_dst.write_text(json.dumps(g, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8", newline="\n")
|
||||
new["_前身目标"] = ("%s(已于 %s 换目标时归档至 goals/%s)"
|
||||
% (old_title, time.strftime("%Y-%m-%dT%H:%M"), _dst.name))
|
||||
print(" ✅ 旧目标已归档:goals/%s(%d 字节)" % (_dst.name, _dst.stat().st_size))
|
||||
except Exception as e:
|
||||
# ⚠️ 归档失败 ⇒ **不阻断换目标**(用户已确认),但必须**喊出来**(⛔ 不许静默)
|
||||
print(" 🔴 **旧目标归档失败**:%s" % e)
|
||||
print(" ⚠️ 旧目标**未被保存** —— 若还要它,请先手工备份 `%s`" % GOAL)
|
||||
new["declared_at"] = time.strftime("%Y-%m-%dT%H:%M")
|
||||
new["declared_by"] = by
|
||||
_write_json(GOAL, new)
|
||||
print("\n⇒ 已登记(落点 `%s`)。" % GOAL)
|
||||
print(" 🔴 目标**不是固定的**:下次调用技能时在对话里再说一遍 ⇒ 用 `declare` 覆盖即可。")
|
||||
if _is_switch:
|
||||
print(" 🔴 **切换完成** —— 从现在起,机制**关注和检查的是新目标**:%s" % title[:60])
|
||||
print(" ✅ `lifecycle` 已**自动复位为「进行中」**(2026-10-05 起 `declare` 自己管它,⛔ 不用手补)")
|
||||
print(" ⚠️ 若新目标其实**还没开工**,再手动改成别的:")
|
||||
print(" python collabd.py --set-life 等待 --by \"主会话\"")
|
||||
else:
|
||||
print(" 🔴 目标**不是固定的**:下次调用技能时在对话里再说一遍 ⇒ 用 `declare` 覆盖即可。")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user