chore(hooks): 输出门禁三脚本更新 + 新增技能加载闸门 skill-load-guard.py
This commit is contained in:
1 parent
c8b514832f
commit
bda9b10161
4 files changed
+385
-10
No files matched your search
@@ -245,14 +245,77 @@ CONTEXT = (
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 会话预算(2026-09-15 用户选 A 案):到 ~15 万 token / ~120 次工具调用 ⇒ 提醒开新会话
|
||||
# 会话预算(2026-09-15 用户选 A 案 · 提交 03c8363):到 **12 万 token / 80 次工具调用** ⇒ 提醒开新会话
|
||||
# ⚠️ 2026-09-16 更正:本行原写「~15 万 token / ~120 次工具调用」,与代码值(`BUDGET_TOKENS=120000` /
|
||||
# `BUDGET_TOOLS=80`)**不符** —— 提交 `03c8363` 的 message 里就写的是 `120000/80`,代码从未用过 15 万/120
|
||||
# ⇒ 判定为注释笔误,已按代码更正(避免后人据注释误判告警点)。
|
||||
# 数值本身由 AI 在实现层自定(属 §1「性能与资源调参」= 边界内);**方案(A 案四招)才是用户 09-15 拍板的**。
|
||||
# 为什么加进本脚本、而不新装一个 hook:`settings.json` 的 hooks 是**应用启动时快照**(新增条目要重启),
|
||||
# 而**脚本内容每次调用现读** ⇒ 改这里即刻生效。数据源 = 转录 `type=function_call` → `message.usage.input_tokens`
|
||||
# (最近一条 usage 即"当前上下文体量",精确,不靠估算)。
|
||||
# 依据(实测某会话):上下文 5.2 万 → 59.2 万;累计 input 1.93 亿 / output 51.9 万(**371:1**);
|
||||
# 其中 33 次缓存失效,每次都把 ~50 万 token **按全价**重算 ⇒ 会话越长,单次失效越贵。
|
||||
BUDGET_TOKENS = 120000
|
||||
# ★ 2026-09-16 改为**分级 + 去重**(原为单一阈值 120000/80,超了之后每轮都报 ⇒ 变成"狼来了")
|
||||
# 设计要点:**同一级别只报一次** ⇒ 跨级才再提醒,既不麻木也不失警。
|
||||
BUDGET_TOKENS = 120000 # 一级:轻提示(保留原名,兼容既有引用)
|
||||
BUDGET_STRONG = 200000 # 二级:建议收口
|
||||
BUDGET_FORCE = 300000 # 三级:**强制收口**(先落盘、出接续包,再开新会话)
|
||||
BUDGET_TOOLS = 80
|
||||
ALERT_LEVEL_REL = os.path.join('.workbuddy', '.budget-alert-level')
|
||||
|
||||
# 为什么三级不是"自动开新会话"(2026-09-16 与用户讨论后定):
|
||||
# ① **技术上做不到** —— hook 只有 `additionalContext`(注入)与 `permissionDecision`(拦工具)两种输出,
|
||||
# 事件只有 SessionStart / PreToolUse / UserPromptSubmit / Stop,**没有"创建/切换会话"的能力**;
|
||||
# AI 自身也只能在会话内行动。⇒ "自动开"这一半无法实现。
|
||||
# ② **设计上不该做** —— 新会话 = 上下文清零 ⇒ **在途状态全丢**;而"该带走什么"只有 AI 判断得了
|
||||
# ⇒ 顺序必须是 **先收口(落盘 + 接续包)→ 再由用户开新会话**,反了就是"突然失忆"。
|
||||
# ③ 因此本级**不做"自动开"**,做**"强制收口"**:把状态固化成文档,让下一个会话能无损接上。
|
||||
LV_PREFIX = {
|
||||
1: '',
|
||||
2: '⚠️ 上下文已过 **20 万**,本轮收口后建议开新会话。\n',
|
||||
3: ('🔴 **已过 30 万 ⇒ 进入强制收口模式**(先落盘、再接续):\n'
|
||||
' ① 把在途状态写进 `.workbuddy/memory/`(今日日志 + 必要的 MEMORY.md 条目);\n'
|
||||
' ② 产出**接续包**:目标 / 已完成 / 在途 / 未完成 / 下一步 / 关键决定 / 回滚点;\n'
|
||||
' ③ 然后明确告知用户「请开新会话,接续点在 X」,**由用户开**(钩子无法自动创建会话)。\n'
|
||||
' ⛔ 不要在本会话继续开新任务 —— 每多跑一轮,成本按当前水位线性放大。\n'),
|
||||
}
|
||||
|
||||
|
||||
def _alert_level(tokens, ncalls):
|
||||
"""0=未超 1=轻 2=建议收口 3=强制收口"""
|
||||
t = tokens or 0
|
||||
if t >= BUDGET_FORCE:
|
||||
return 3
|
||||
if t >= BUDGET_STRONG:
|
||||
return 2
|
||||
if t >= BUDGET_TOKENS:
|
||||
return 1
|
||||
if ncalls is not None and ncalls >= BUDGET_TOOLS:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _last_level(root, sid):
|
||||
"""读「本会话」已报到的级别。**按 sid 区分** —— 否则新会话会被上一会话的状态挡住 ⇒ **漏报**。"""
|
||||
try:
|
||||
with io.open(os.path.join(root, ALERT_LEVEL_REL), encoding='utf-8') as f:
|
||||
s = (f.read() or '').strip()
|
||||
ps = s.split('\t')
|
||||
if len(ps) == 2 and ps[0] == sid:
|
||||
return int(ps[1] or 0)
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
def _set_level(root, sid, lv):
|
||||
try:
|
||||
p = os.path.join(root, ALERT_LEVEL_REL)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||
with io.open(p, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write('%s\t%d' % (sid, lv))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def session_budget(path):
|
||||
@@ -286,8 +349,15 @@ def session_budget(path):
|
||||
return last_in, n_calls, prev_in
|
||||
|
||||
|
||||
def budget_note(tokens, ncalls, prev=None):
|
||||
"""超预算 ⇒ 返回注入用的提醒串;未超 ⇒ 但增量异常也提醒(<1 万 token 的增量不打扰)。"""
|
||||
def budget_note(tokens, ncalls, prev=None, root='', sid=''):
|
||||
"""分级提醒;**同一级别只注入一次**(跨级才再报)—— 治"超阈值后每轮都报"导致的麻木。
|
||||
未超阈值时,单轮增量异常(≥4 万 token)仍提醒。"""
|
||||
# ★ 分级 + 去重(2026-09-16):本级已报过 ⇒ 静默(**按会话 id 区分**,新会话重新计数)
|
||||
lv = _alert_level(tokens, ncalls)
|
||||
if lv and lv <= _last_level(root, sid):
|
||||
return ''
|
||||
if lv:
|
||||
_set_level(root, sid, lv)
|
||||
over_t = tokens is not None and tokens >= BUDGET_TOKENS
|
||||
over_n = ncalls is not None and ncalls >= BUDGET_TOOLS
|
||||
grew = (tokens is not None and prev is not None and tokens - prev >= 40000) if not (over_t or over_n) else False
|
||||
@@ -296,7 +366,8 @@ def budget_note(tokens, ncalls, prev=None):
|
||||
which = ('上下文 %s token' % tokens) if over_t else (
|
||||
'工具调用 %s 次' % ncalls) if over_n else ('**上一轮新增 %s token**' % (tokens - prev))
|
||||
return (
|
||||
'💰 【会话预算告警】本会话已到 **%s**(阈值 %s token / %s 次工具调用)。'
|
||||
LV_PREFIX.get(lv, '') +
|
||||
'💰 【会话预算告警】本会话已到 **%s**(一级阈值 %s token / %s 次工具调用)。'
|
||||
'代价机制(2026-09-15 实测):对话历史是**追加式**的 —— 一次工具调用的输出(`function_call_result`)'
|
||||
'**永久留在历史里、每轮全量重发**(某会话 553 轮 × 平均 42 万 = **2.33 亿 input**,output 仅 0.35%%)。'
|
||||
'**处置:本轮收口后开新会话**(新会话起点约 5 万 ⇒ 每轮降到 1/12);'
|
||||
@@ -404,13 +475,17 @@ def user_prompt_mode(payload):
|
||||
tail = tail_lines(text, 1) if text else ''
|
||||
hit = bool(tail) and not RE_QUOTE.search(tail) and bool(RE_BAN.search(tail))
|
||||
toks, ncalls, prev = session_budget(tp)
|
||||
note = budget_note(toks, ncalls, prev)
|
||||
note = budget_note(toks, ncalls, prev, root0 or '.', str(payload.get('session_id') or ''))
|
||||
gh = guard_health(root0 or '.')
|
||||
ph = path_health(root0 or '.')
|
||||
log(root0 or '.', 'invoked(user-prompt)|mode=%s|上轮收尾=征询句:%s|上下文=%s tok(+%s)|工具=%s 次|预算告警=%s|门禁自检=%s|路径自检=%s|%s'
|
||||
% (mode, hit, toks, (toks - prev) if (toks and prev) else '-', ncalls, bool(note), bool(gh), bool(ph),
|
||||
(tail.replace('\n', ' ')[:60] if tail else '(取不到上一轮文本)')))
|
||||
if mode == 'inject' and (hit or note or gh or ph):
|
||||
# ⚠️ 2026-09-15 用户选 C:**取消**「会话预算」注入 —— 当时的症结是"每轮都报、太吵"。
|
||||
# ★ 2026-09-16 恢复**分级注入**:`budget_note` 已改为「分级 + 同会话跨级才报一次」(见其 docstring),
|
||||
# 噪音症结已消解;且用户当日提出「上下文超 30 万要不要自动收口」⇒
|
||||
# **AI 必须先知道水位才谈得上收口** ⇒ 恢复注入(一级静默无关、二级建议、三级强制收口)。
|
||||
if mode == 'inject' and (hit or gh or ph or note):
|
||||
ctx = ''
|
||||
for seg in (CONTEXT if hit else '', note, gh, ph):
|
||||
if seg:
|
||||
|
||||
Reference in new issue
Block a user