补入本地今日改动:session-mechanism + product-planning(按用户令改推本仓库)
背景:旧远端 work.alotbuy.com 今天一直连不上(222/22 端口都不通)⇒ 今天本地累积多条提交推不上去; 用户令改推本仓库 [email protected]:admin/workbuddy_skills.git(只推这 5 个技能: browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)。 本次改动(对照本机已安装的技能源逐文件 md5 比对得出,⛔ 是「补差异」不是「整体覆盖」): · session-mechanism:含今天两条钩子改动(派活收工闸、点名加载收工闸)、 rules.md §9「只写正向范围」、检查程序静默阈值 10→6、检查排期补 workspace_scope(修未分组)、 以及今天修的三处陈旧指针(旧技能名 dsh-decision / agent-operating-rules 与旧路径编号)。 · product-planning:第③段「随段样式风格库」并入 design-system-tiaoyue、竞品分析方法论重写等。 · humanizer / humanizer-zh:已一致,零改动。 · browser-harness:比对出的 7 个「本地独有」全是**它自己 .gitignore 里就排除的**构建产物 (src/*.egg-info/ 与 uv.lock)⇒ 按该技能自己的规矩不推(用户口径「不要环境」)。 仓库级配置(与旧仓一致):core.autocrlf=false;身份 maogeigei <[email protected]>。 ⚠️ 克隆时仓库默认 autocrlf=true(会把行尾转成 CRLF)⇒ 已改回 false 并重新暂存, 核对索引 blob 均为 LF、真实差异 23 个(⛔ 不是把几百个文件一起改掉)。
This commit is contained in:
1 parent
0abd33b3a0
commit
61962f1726
23 files changed
+741
-250
No files matched your search
@@ -20,7 +20,9 @@
|
||||
🔴 三条设计判据(本需求的关键,⛔ 都别改)
|
||||
──────────────────────────────────────────
|
||||
1. **真相源在技能里**(不在本文件、也不在某个工作区):
|
||||
`<技能库>/agent-operating-rules/references/回复排版-核心块.md`
|
||||
`<技能库>/session-mechanism/references/03-回复排版-核心块.md`
|
||||
⚠️ 2026-10-07 订正:原写 `<技能库>/agent-operating-rules/references/回复排版-核心块.md` ——
|
||||
该技能**已于 2026-10-06 整包并入 `session-mechanism`**,旧路径已不存在(属「指针陈旧」族)。
|
||||
⇒ 技能是**跨工作区**的 ⇒ 一次改动,**所有会话**都跟着变。
|
||||
⛔ **绝不在本文件里写死规则文本** —— 写死就变成第二真相源,两边必然漂。
|
||||
2. **每轮都注入,⛔ 不设冷却**:这条规矩的价值就在"紧贴用户消息、每轮重述"。
|
||||
|
||||
@@ -116,6 +116,7 @@ COOLDOWN = 300 # 同会话注入冷却(秒
|
||||
# 起因(用户令逐字):「**重点问题是 会话接收到 调用执行会话完成目标 没有去调用对应技能去执行**」——
|
||||
# 本钩子原来**只注入提醒**,AI 照不照做没人管;实测会话 `b232218f` 里用户连说三遍,一次都没拉起执行会话。
|
||||
DISPATCH_REL = os.path.join('.workbuddy', '.dispatch-pending')
|
||||
LOAD_REL = os.path.join('.workbuddy', '.load-pending')
|
||||
_DISPATCH_WORDS = ('使用执行会话', '使用任务会话', '执行会话完成', '任务会话完成',
|
||||
'调用执行会话', '调用任务会话', '派任务会话', '建任务会话')
|
||||
|
||||
@@ -156,13 +157,15 @@ TRIGGERS = (
|
||||
# 命中词 → 该加载哪个技能(2026-09-22 加:从"只会推决策技能"扩为按命中词分流)
|
||||
# 🔴 2026-10-02 修:本行原写 `dsh-decision-method`,而它**已于 2026-09-28 合并退役**
|
||||
# ⇒ 注入文本让人去加载一个**不存在的技能**(找得到才怪 ⇒ 规则仍不进上下文;属静默失效族)。
|
||||
# 现名 `dsh-decision`,两档全文:`references/00-决策方法论.md`(原 decision-method)
|
||||
# / `references/01-功能优先协作协议.md`(原 feature-first,含 §5.1 结论骨架 + §5.4 排版硬约束)。
|
||||
# 🔴 2026-10-07 再修:`dsh-decision` **已于 2026-10-06 整份删除**(实体早已搬入本包),
|
||||
# 且上面那句写的路径编号也错了 ⇒ 两档实体**现在就在本包**:
|
||||
# `references/04-决策方法论.md`(原 decision-method)
|
||||
# / `references/02-功能优先协作协议.md`(原 feature-first,含 §5.1 结论骨架 + §5.4 排版硬约束)。
|
||||
_LOAD_RULES = ('按规则来', '按规则做', '按作业规则', '遵守规则', '按规矩来',
|
||||
'回复排版', '执行结果排版', '回复格式', '怎么回复', '排版')
|
||||
|
||||
# ── 分组 3 的词 → 加载 `session-mechanism`(2026-10-05 加)─────────────────────
|
||||
# 与 `_LOAD_RULES`(→ agent-operating-rules)并列的第三条路由。
|
||||
# 与 `_LOAD_RULES`(该技能已于 2026-10-06 整包并入本包)并列的第三条路由。
|
||||
# 🔴 为什么单列一族:这三族**目标技能不同**,⛔ 不能混在一个 if 里 ——
|
||||
# 命中"使用任务会话完成目标"的人,要的是**会话机制本体**(怎么建排期/怎么派活/
|
||||
# 自决策白名单在 `references/02-功能优先协作协议.md`),⛔ 不是决策方法论。
|
||||
@@ -171,6 +174,17 @@ _LOAD_SESSION = ('使用任务会话', '使用执行会话', '使用协作会话
|
||||
'会话技能', '会话机制', '创建任务会话', '建任务会话',
|
||||
'派任务会话', '任务会话', '执行会话', '协作会话', '多会话')
|
||||
|
||||
# 🔴🔴 2026-10-07 「点名族」= TRIGGERS 里**除派活族外的全部**(决策方法族 + 作业规则/排版族)。
|
||||
# 用途:命中任意一个 ⇒ 写 `<ws>/.workbuddy/.load-pending` 待办,交给 `stop-dialog-guard.py`
|
||||
# 做**收工前自检**(本会话到底有没有真的 `Skill` 加载过本技能)。
|
||||
# 起因(用户令逐字):「**问题是 会话没有加载决策方法,之前的优化改动无效**」——
|
||||
# 实测(`content_marketing_agent` 那条会话的闸门日志):
|
||||
# `20:09:45 HIT … hits=决策方法` **注入确实发出了**,可会话**仍未去加载**;
|
||||
# ⇒ 根因=**本钩子只"注入提醒",没有任何东西核对 Skill 到底调没调**(与"派活触发句"同族洞)。
|
||||
# ⛔ **不许再抄一份词表** —— 从 `TRIGGERS` 里**减**出来,否则加词时两处必然漂移。
|
||||
_LOAD_WORDS = tuple(w for w in TRIGGERS
|
||||
if w not in _LOAD_SESSION and w not in _DISPATCH_WORDS)
|
||||
|
||||
|
||||
def _read_stdin():
|
||||
try:
|
||||
@@ -270,6 +284,33 @@ def _mark_dispatch(workdir, sid, prompt, hits):
|
||||
pass
|
||||
|
||||
|
||||
def _is_load(prompt):
|
||||
"""本轮是不是**点名族**(点名方法/规则/排版)。与 `_is_dispatch` 并列,⛔ 别各写一遍。"""
|
||||
try:
|
||||
seg = re.sub(r'\s+', '', prompt or '')
|
||||
return any(w in seg for w in _LOAD_WORDS)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _mark_load(workdir, sid, prompt, hits):
|
||||
"""命中「点名族」⇒ 写一条待办(TSV:会话 id / 时间戳 / 命中词),交给收工闸核。
|
||||
|
||||
判据(收工闸会核):**本会话有没有真的 `Skill` 加载过 `session-mechanism`**。
|
||||
⚠️ 与 `_mark_dispatch` 同规矩:**写在冷却判定之前**(重复点名常被冷却吞掉,写完才有痕迹)。
|
||||
⛔ 全程 fail-open。
|
||||
"""
|
||||
try:
|
||||
if not _is_load(prompt):
|
||||
return
|
||||
with io.open(os.path.join(_norm_path(workdir), LOAD_REL),
|
||||
'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write('%s\t%.3f\t%s\n' % (sid, time.time(), ','.join(hits)))
|
||||
_log(workdir, 'LOAD_PENDING sid=%s hits=%s' % (sid[:8], ','.join(hits)))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _cooling(workdir, sid):
|
||||
"""同一会话 COOLDOWN 秒内不重复注入。"""
|
||||
try:
|
||||
@@ -326,14 +367,15 @@ def main():
|
||||
|
||||
# 🔴 先留待办(⛔ 必须在冷却之前,见 `_mark_dispatch` 注释)
|
||||
_mark_dispatch(workdir, sid, prompt, hits)
|
||||
_mark_load(workdir, sid, prompt, hits)
|
||||
|
||||
# 🔴🔴 2026-10-07 两处改(用户问「有什么影响 该如何处理」时落的):
|
||||
# ① **派活族显式触发句不受冷却** —— 那是最明确的一类"要动作",被 300 s 吞掉最亏。
|
||||
# ① **派活族 + 点名族都不受冷却** —— 它们都是"用户明确点名"的一类,被 300 s 吞掉最亏。
|
||||
# 实测:会话 `b232218f` 里 01:32:00 命中后,01:33:46(105 s,最明确的那句)
|
||||
# 被冷却吞掉、**连日志都没有**。
|
||||
# ② **被冷却跳过也要留一行**(`COOL_SKIP`)—— 否则"没命中"与"被吞"在日志上分不出来,
|
||||
# 排查时只能靠状态文件时间戳旁证反推(上一次就是这么反推的)。
|
||||
if _cooling(workdir, sid) and not _is_dispatch(prompt):
|
||||
# 被冷却吞掉、**连日志都没有**;同型复现在 `content_marketing_agent` 的「决策方法」上
|
||||
# (20:09:45 注入 → 20:09:54 重复点名被 COOL_SKIP)。
|
||||
# ② **被冷却跳过也要留一行**(`COOL_SKIP`)—— 否则"没命中"与"被吞"在日志上分不出来。
|
||||
if _cooling(workdir, sid) and not (_is_dispatch(prompt) or _is_load(prompt)):
|
||||
_log(workdir, 'COOL_SKIP sid=%s hits=%s(冷却中,本轮不注入)'
|
||||
% (sid[:8], ','.join(hits)))
|
||||
return
|
||||
|
||||
@@ -710,6 +710,95 @@ def _dispatch_gate(payload):
|
||||
return True
|
||||
|
||||
|
||||
# ── 🔴🔴 2026-10-07 「点名了机制却没加载」闸 ────────────────────────────────────
|
||||
# 起因(用户令逐字):「**问题是 会话没有加载决策方法,之前的优化改动无效**」。
|
||||
# 实测(`content_marketing_agent` 那条会话的闸门日志):
|
||||
# `20:09:45 HIT … hits=决策方法` ⇒ **注入确实发出了**;可会话**仍未去加载**
|
||||
# ⇒ 根因=`skill-load-guard.py` **只"注入提醒",没有任何东西核对 `Skill` 到底调没调**。
|
||||
# 形态:命中「点名族」时它写 `<ws>/.workbuddy/.load-pending`(会话/时间/命中词),
|
||||
# 本闸在**收工前**核一次:**同会话 + 未过期 + 转录尾部里没有"真的 `Skill` 加载过本技能"** ⇒ 拦一次。
|
||||
# ⛔ 三条安全底线:① 全程 fail-open(读不到转录 = 判"不确定" ⇒ **放行**);② 只拦一次(复用 10 分钟限流);
|
||||
# ③ 待办 15 分钟过期。⛔ 只读转录**尾部**(1.5 MB),绝不整读(有 200 MB 级的转录)。
|
||||
LOAD_REL = os.path.join('.workbuddy', '.load-pending')
|
||||
LOAD_GRACE = 60 # 命中后给 60 秒去加载(⛔ 不拦"刚说完、还没轮到")
|
||||
LOAD_EXPIRE = 900 # 待办最长存活 15 分钟
|
||||
LOAD_TAIL_BYTES = 1500000 # 只看转录最后 1.5 MB(足够覆盖"刚被点名"的那几轮)
|
||||
LOAD_REASON = (
|
||||
'⛔ **收工前自检:用户点名了机制问题(决策方法/作业规则/排版),但这条会话没有真的加载过它。**\n'
|
||||
'你点名的那些判据**不在你的记忆里**,就在技能包里 —— 先调用 Skill 工具加载 `session-mechanism`,再收工。\n'
|
||||
'⚠️ **提醒 ≠ 加载**:本机制的钩子只会"注入一句提醒",⛔ 它拦不住你直接干活;\n'
|
||||
' 实测栽过(`content_marketing_agent`):20:09:45 已注入「决策方法」提醒,会话却始终没去加载\n'
|
||||
' ⇒ 用户原话「**会话没有加载决策方法,之前的优化改动无效**」。'
|
||||
)
|
||||
|
||||
|
||||
def _has_skill_load(tp):
|
||||
"""转录尾部里有没有『**真的** Skill 加载过本技能』。
|
||||
|
||||
返回 True/False/**None**(读不到 ⇒ 不确定 ⇒ 调用方放行,fail-open)。
|
||||
⚠️ 转录里技能名被**转义引号**包着:`"arguments":"{\\"skill\\": \\"session-mechanism\\"}"`
|
||||
(⛔ 按普通引号匹配必然全不命中 —— 本项目 2026-10-07 实测踩过这个假阴性)。
|
||||
"""
|
||||
if not tp or not os.path.isfile(tp):
|
||||
return None
|
||||
try:
|
||||
with open(tp, "rb") as f:
|
||||
try:
|
||||
f.seek(0, 2)
|
||||
size = f.tell()
|
||||
f.seek(max(0, size - LOAD_TAIL_BYTES))
|
||||
except OSError:
|
||||
f.seek(0)
|
||||
raw = f.read().decode("utf-8", "replace")
|
||||
except Exception:
|
||||
return None
|
||||
for pat in (r'\\"skill\\":\s*\\"session-mechanism\\"',
|
||||
r'\\"command\\":\s*\\"session-mechanism\\"',
|
||||
r'"skill":\s*"session-mechanism"',
|
||||
r'"command":\s*"session-mechanism"'):
|
||||
if re.search(pat, raw):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _load_gate(payload):
|
||||
"""见上面那段注释。返回 True = 已发出阻拦 ⇒ 调用方**直接 return**。"""
|
||||
try:
|
||||
ws = str(payload.get('cwd') or '') or \
|
||||
(os.environ.get('CODEBUDDY_PROJECT_DIR') or os.environ.get('DSH_WORKSPACE') or '')
|
||||
if not ws:
|
||||
return False
|
||||
p = os.path.join(_norm_path(ws), LOAD_REL)
|
||||
if not os.path.isfile(p):
|
||||
return False
|
||||
parts = (io.open(p, encoding='utf-8').read().strip().split('\t') + ['', '', ''])[:3]
|
||||
sid_m, ts_s, hits = parts
|
||||
sid_now = str(payload.get('session_id') or '')
|
||||
if not sid_now or sid_m != sid_now: # ⛔ 不是本会话的待办 ⇒ 不碰
|
||||
return False
|
||||
ts = float(ts_s or 0)
|
||||
age = time.time() - ts
|
||||
if age > LOAD_EXPIRE: # 过期 ⇒ 作废
|
||||
os.remove(p)
|
||||
return False
|
||||
if age < LOAD_GRACE: # 刚说完、还没轮到 ⇒ 放行
|
||||
return False
|
||||
got = _has_skill_load(str(payload.get('transcript_path') or ''))
|
||||
if got is None: # 读不到转录 ⇒ 不确定 ⇒ 放行
|
||||
return False
|
||||
if got: # 已经加载过 ⇒ 待办销账
|
||||
os.remove(p)
|
||||
return False
|
||||
except Exception:
|
||||
return False # ⛔ fail-open
|
||||
root = _norm_path(ws)
|
||||
if _rate_limited(root, sid_now): # 同一会话 10 分钟最多拦 1 次
|
||||
return False
|
||||
log(root, 'load-gate 命中:点名了机制但本会话未加载(hits=%s|待办 %.0fs)' % (hits, age))
|
||||
_emit({'continue': False, 'reason': LOAD_REASON})
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
raw = _read_stdin_text() # ⚠️ 必须走 buffer:`-E` 下 sys.stdin 是 cp936(见 _read_stdin_text 注释)
|
||||
payload = None
|
||||
@@ -729,6 +818,9 @@ def main():
|
||||
# ⛔ 它只在"本工作区确实有本会话的派活待办"时才可能生效,其余情形一次都不触发。
|
||||
if _dispatch_gate(payload):
|
||||
return
|
||||
# 🔴🔴 2026-10-07 「点名了机制却没加载」闸 —— 同样放在自作用域判定之前(同上理由)。
|
||||
if _load_gate(payload):
|
||||
return
|
||||
if not _in_scope(tp): # 作用域外 → 放行
|
||||
return
|
||||
if os.environ.get('DSH_STOP_GUARD_OFF'): # 急停(环境变量)→ 放行
|
||||
|
||||
Reference in new issue
Block a user