codebee 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +601 -589
- package/LICENSE +21 -21
- package/README.md +508 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/aiflavor.py +197 -197
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +95 -95
- package/app/core/board.py +209 -209
- package/app/core/bookmeta.py +935 -935
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1680 -1673
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +190 -190
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/evalbench.py +836 -720
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flows.py +8 -8
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +374 -374
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +25 -1
- package/app/core/manager.py +2208 -2143
- package/app/core/market.py +90 -4
- package/app/core/market_remote.py +16 -0
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +3 -2
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +189 -189
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -138
- package/app/core/pipeline.py +102 -30
- package/app/core/planner.py +2 -0
- package/app/core/plugins.py +34 -0
- package/app/core/portguard.py +111 -111
- package/app/core/preview.py +293 -293
- package/app/core/prompt_guard.py +78 -0
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +396 -367
- package/app/core/publish/browser.py +655 -655
- package/app/core/publish/fanqie.py +165 -165
- package/app/core/publish/flow.py +640 -589
- package/app/core/publish/flows-fanqie-calibrated.json +336 -336
- package/app/core/publish/ledger.py +307 -229
- package/app/core/publish/manager.py +1118 -824
- package/app/core/publish/qimao.py +176 -146
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -0
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2142 -2142
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings_schema.py +270 -270
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -122
- package/app/core/skillpacks/fanqie-novel.md +98 -80
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/skillpacks/qimao-signing.md +70 -59
- package/app/core/skills.py +58 -8
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +51 -0
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tracing.py +144 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1035 -1035
- package/app/core/volumes.py +389 -389
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2692 -2614
- package/app/main.py +110 -39
- package/app/mcp_server.py +175 -175
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +509 -114
- package/app/ui/artdiff.js +74 -74
- package/app/ui/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +41 -2
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +8 -3
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +74 -26
- package/bin/tutti.js +147 -147
- package/package.json +48 -47
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +0 -18
package/app/core/skills.py
CHANGED
|
@@ -23,6 +23,7 @@ import json
|
|
|
23
23
|
import re
|
|
24
24
|
import threading
|
|
25
25
|
import time
|
|
26
|
+
from datetime import datetime
|
|
26
27
|
from pathlib import Path
|
|
27
28
|
|
|
28
29
|
from . import paths, revisions
|
|
@@ -338,6 +339,52 @@ def _karma(item):
|
|
|
338
339
|
return (hits - lost + _KARMA_PRIOR) / (hits + 2 * _KARMA_PRIOR)
|
|
339
340
|
|
|
340
341
|
|
|
342
|
+
_DECAY_GRACE_DAYS = 30 # 宽限期:30 天内不衰减(新条目与常用条目都不受影响)
|
|
343
|
+
_DECAY_RAMP_DAYS = 60 # 宽限期后线性爬满(再 60 天衰减到底)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _surplus_decay(item, now=None):
|
|
347
|
+
"""衰减后的「信任盈余」(ds, lost):won==0 且闲置超宽限期的条目,盈余
|
|
348
|
+
(hits−lost)⁺ 随时长线性衰减到 0——过去的注入证据会过期(hippo-memory 借鉴,
|
|
349
|
+
066 班深挖);lost 是失守证据,粘滞不衰减。won≥1 有结局归因背书不衰减;
|
|
350
|
+
无时间戳的条目不衰减(无从判旧不臆测)。updated_at 随每次 upsert 刷新,
|
|
351
|
+
被再次触及即重置。只压排序,不删除、不改数据。"""
|
|
352
|
+
hits = max(0, int(item.get("hits") or 0))
|
|
353
|
+
lost = min(max(0, int(item.get("lost") or 0)), hits)
|
|
354
|
+
if int(item.get("won") or 0) > 0:
|
|
355
|
+
return hits - lost, lost
|
|
356
|
+
stamp = item.get("updated_at") or item.get("created_at")
|
|
357
|
+
if not stamp:
|
|
358
|
+
return hits - lost, lost
|
|
359
|
+
try:
|
|
360
|
+
then = datetime.strptime(str(stamp)[:19], "%Y-%m-%d %H:%M:%S")
|
|
361
|
+
except ValueError:
|
|
362
|
+
return hits - lost, lost
|
|
363
|
+
now = now or datetime.now()
|
|
364
|
+
idle = (now - then).days
|
|
365
|
+
if idle <= _DECAY_GRACE_DAYS:
|
|
366
|
+
return hits - lost, lost
|
|
367
|
+
ramp = min(1.0, (idle - _DECAY_GRACE_DAYS) / float(_DECAY_RAMP_DAYS))
|
|
368
|
+
return int(round((hits - lost) * (1.0 - ramp))), lost
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _eff_karma(item, now=None):
|
|
372
|
+
"""有效可信度:以衰减后盈余重算 karma。ramp=0 时与 _karma 完全等价
|
|
373
|
+
((hits−lost+1)/(hits+2) 恒等变形),满衰减时未归因条目回中性 0.5——
|
|
374
|
+
与新鲜条目同位,先后交给「新者优先」尾键;失守者因 lost 粘滞继续下沉。"""
|
|
375
|
+
ds, lost = _surplus_decay(item, now=now)
|
|
376
|
+
return (ds + _KARMA_PRIOR) / (ds + lost + 2 * _KARMA_PRIOR)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _ts(item):
|
|
380
|
+
"""created_at → epoch 秒(排序用,可取负实现新者优先);缺失/坏值记 0。"""
|
|
381
|
+
stamp = str(item.get("created_at") or "")[:19]
|
|
382
|
+
try:
|
|
383
|
+
return datetime.strptime(stamp, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
384
|
+
except ValueError:
|
|
385
|
+
return 0.0
|
|
386
|
+
|
|
387
|
+
|
|
341
388
|
def list_lessons(scope=None, only_enabled=False, category=None):
|
|
342
389
|
with _LOCK:
|
|
343
390
|
items = list((_load().get("lessons") or []))
|
|
@@ -349,11 +396,13 @@ def list_lessons(scope=None, only_enabled=False, category=None):
|
|
|
349
396
|
if only_enabled:
|
|
350
397
|
items = [x for x in items if x.get("enabled", True)]
|
|
351
398
|
# karma 感知排序(UI 列表用;注入排序在 relevance_top 不走这里):
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
399
|
+
# 首键是有效可信度(未归因条目的信任盈余随闲置衰减——证据会过期;won 背书与
|
|
400
|
+
# 失守证据都粘滞),被点名归因过成功的(won)作次键提权,再按衰减后盈余;
|
|
401
|
+
# 失守多的自然沉底(不隐藏——降权不删除)。衰减到中性的同分者按新者优先
|
|
402
|
+
# (-_ts 尾键)——「先试新的」是衰减语义的一部分。
|
|
403
|
+
items.sort(key=lambda x: (-_eff_karma(x), -int(x.get("won") or 0),
|
|
404
|
+
-_surplus_decay(x)[0], -int(x.get("seen") or 1),
|
|
405
|
+
-_ts(x), x.get("id") or ""))
|
|
357
406
|
return items
|
|
358
407
|
|
|
359
408
|
|
|
@@ -495,12 +544,13 @@ def relevance_top(lessons, task, limit):
|
|
|
495
544
|
return lessons[:limit]
|
|
496
545
|
|
|
497
546
|
# 使用反馈闭环(pmb「量化记忆真实帮助」+ tradememory「按结局加权召回」):
|
|
498
|
-
#
|
|
547
|
+
# 相关性优先;同分比有效可信度(未归因盈余随闲置衰减),再比归因成功的次数,
|
|
548
|
+
# 最后比衰减后盈余;同到中性的按新者优先。
|
|
499
549
|
def rank(x):
|
|
500
550
|
grams = _text_bigrams(x.get("title")) | _text_bigrams(x.get("content"))
|
|
501
551
|
overlap = -len(probe & grams)
|
|
502
|
-
return (overlap, -
|
|
503
|
-
-
|
|
552
|
+
return (overlap, -_eff_karma(x), -int(x.get("won") or 0),
|
|
553
|
+
-_surplus_decay(x)[0], -_ts(x), x.get("id") or "")
|
|
504
554
|
|
|
505
555
|
return sorted(lessons, key=rank)[:limit]
|
|
506
556
|
|
package/app/core/step_runner.py
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""Step 执行器:压缩触发的重试守门(复用已渲染 prompt,不重复组装)。
|
|
3
|
-
|
|
4
|
-
设计稿:docs/migration/02-context-compaction.md §1D。
|
|
5
|
-
参考 dsh packages/core/agent-loop/src/agent.ts:307-378:
|
|
6
|
-
失败重试只在「前进 surface generation」时返回 {kind:'retry'},
|
|
7
|
-
复用已渲染 PromptAssembly,不重跑 pre-step / 组装 / 用户消息准入。
|
|
8
|
-
|
|
9
|
-
Tutti 语境:runner.run_agent 自带 prompt(已渲染);重试的风险不是重复渲染
|
|
10
|
-
(prompt 是同一个字符串),而是「不压缩直接重试」必然再次撑爆。
|
|
11
|
-
本模块的守门:仅当 maybe_compact 真正前进了 surface generation 才重试一次。
|
|
12
|
-
"""
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import logging
|
|
16
|
-
|
|
17
|
-
from .compaction import maybe_compact, DEFAULT_PRESSURE_THRESHOLD
|
|
18
|
-
from .error_codes import ErrorCode
|
|
19
|
-
|
|
20
|
-
log = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
# 触发「压缩后重试」的错误码
|
|
23
|
-
_OVERFLOW_CODES = {ErrorCode.CONTEXT_OVERFLOW, ErrorCode.MAX_TOKENS}
|
|
24
|
-
|
|
25
|
-
# 事前预检阈值:最近一次调用的上下文锚点已占本步模型容量九成 → 第一个请求
|
|
26
|
-
# 大概率被供应商拒。只拦「大概率必死」,不提前压缩平时的高压(used() 是累计
|
|
27
|
-
# 口径,比真实上下文偏大;0.8 的响应式阈值语义与此不同,勿混用)。
|
|
28
|
-
_PRECHECK_RATIO = 0.9
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _v2_compaction_tuning():
|
|
32
|
-
"""settings_v2 orchestrator.compaction 微调 → (threshold|None, retain|None)。
|
|
33
|
-
|
|
34
|
-
None = 未配置(走 maybe_compact 的模块默认);读取/注册失败静默回落。
|
|
35
|
-
retain 允许 0(尾部不保留),与 None(未配置)语义不同,勿合并。"""
|
|
36
|
-
try:
|
|
37
|
-
from .settings_schema import get as ss_get, register_default_namespaces
|
|
38
|
-
register_default_namespaces()
|
|
39
|
-
c = (ss_get("orchestrator") or {}).get("compaction") or {}
|
|
40
|
-
th = c.get("pressure_threshold")
|
|
41
|
-
rt = c.get("retain_tail_tokens")
|
|
42
|
-
return (float(th) if th else None,
|
|
43
|
-
int(rt) if rt is not None else None)
|
|
44
|
-
except Exception:
|
|
45
|
-
return None, None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
49
|
-
llm_caller=None, retain_tail_tokens=None, **kwargs):
|
|
50
|
-
"""执行一次 step;撑爆时压缩并守门重试一次。
|
|
51
|
-
|
|
52
|
-
事前预检(借鉴 freebuff base-chat 每步容量重估):换将切到小窗口模型后,
|
|
53
|
-
响应式路径要等供应商报 CONTEXT_OVERFLOW 才压缩——报得不干净就直接卡死。
|
|
54
|
-
先用「最近一次调用上下文 vs 本步模型容量」判一次,超阈先压缩再发。
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
session: session_log.Session(记录 replace_generation)
|
|
58
|
-
run_agent_fn: runner.run_agent(或测试替身),签名 (prompt, **kwargs) -> dict
|
|
59
|
-
prompt: 已渲染的提示词(重试时原样复用,不重新渲染)
|
|
60
|
-
model: 本步将要使用的模型名(容量与压力估算都按它算)
|
|
61
|
-
llm_caller: 压缩摘要 LLM(None = 不压缩,永不重试)
|
|
62
|
-
**kwargs: 透传 run_agent_fn
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
(result, retried: bool)
|
|
66
|
-
"""
|
|
67
|
-
v2_th, v2_rt = _v2_compaction_tuning()
|
|
68
|
-
compact_kwargs = {}
|
|
69
|
-
if retain_tail_tokens is not None:
|
|
70
|
-
compact_kwargs["retain_tail_tokens"] = retain_tail_tokens
|
|
71
|
-
elif v2_rt is not None:
|
|
72
|
-
compact_kwargs["retain_tail_tokens"] = v2_rt
|
|
73
|
-
if v2_th is not None:
|
|
74
|
-
compact_kwargs["threshold"] = v2_th
|
|
75
|
-
if llm_caller is not None and model:
|
|
76
|
-
try:
|
|
77
|
-
from .token_meter import token_meter
|
|
78
|
-
ctx = token_meter.last_context(session.run_id)
|
|
79
|
-
cap = token_meter.capacity(model)
|
|
80
|
-
if ctx > 0 and cap > 0 and ctx > cap * _PRECHECK_RATIO:
|
|
81
|
-
gen_p0 = session.replace_generation()
|
|
82
|
-
if maybe_compact(session, model=model, llm_caller=llm_caller,
|
|
83
|
-
run_id=session.run_id, **compact_kwargs) \
|
|
84
|
-
and session.replace_generation() > gen_p0:
|
|
85
|
-
log.info("precheck: last ctx~%d > %.0f%% of cap(%s)=%d, "
|
|
86
|
-
"compacted before step", ctx, _PRECHECK_RATIO * 100,
|
|
87
|
-
model, cap)
|
|
88
|
-
except Exception:
|
|
89
|
-
log.debug("precheck skipped", exc_info=True)
|
|
90
|
-
gen0 = session.replace_generation()
|
|
91
|
-
result = run_agent_fn(prompt, **kwargs)
|
|
92
|
-
code = result.get("error_code") or ""
|
|
93
|
-
if code not in _OVERFLOW_CODES or llm_caller is None:
|
|
94
|
-
return result, False
|
|
95
|
-
# 撑爆 → 尝试压缩
|
|
96
|
-
compacted = maybe_compact(session, model=model, llm_caller=llm_caller,
|
|
97
|
-
run_id=session.run_id, **compact_kwargs)
|
|
98
|
-
gen1 = session.replace_generation()
|
|
99
|
-
if not compacted or gen1 <= gen0:
|
|
100
|
-
# 压缩未发生或未前进 generation:重试必然再次撑爆,不重试
|
|
101
|
-
log.info("overflow but no compaction progress (gen %s->%s), no retry",
|
|
102
|
-
gen0, gen1)
|
|
103
|
-
return result, False
|
|
104
|
-
# 压缩生效:surface 已折叠。**真正替换 CLI 请求上下文**(2026-09-21
|
|
105
|
-
# 清最老待深挖项:此前重试仍发原 prompt + 续旧 resume 会话——摘要只进
|
|
106
|
-
# 审计日志,CLI 看到的上下文一点没小,重试必然再爆):
|
|
107
|
-
# ①重试 prompt 改用 session 派生转录(折叠摘要 + 近尾消息,体量骤降);
|
|
108
|
-
# ②弃 resume(旧会话本体仍是膨胀态,续上就前功尽弃)。
|
|
109
|
-
msgs = []
|
|
110
|
-
try:
|
|
111
|
-
msgs = session.derive_messages()
|
|
112
|
-
except Exception:
|
|
113
|
-
log.debug("derive_messages failed on retry", exc_info=True)
|
|
114
|
-
kwargs2 = dict(kwargs)
|
|
115
|
-
if msgs:
|
|
116
|
-
transcript = "\n\n".join("[%s] %s" % (m.get("role"), m.get("content"))
|
|
117
|
-
for m in msgs)
|
|
118
|
-
if len(transcript) > 24_000:
|
|
119
|
-
transcript = transcript[-24_000:]
|
|
120
|
-
retry_prompt = transcript
|
|
121
|
-
# 本步指令必须在场:被折叠/截断掉就显式补在尾部(缺失指令比重复严重)
|
|
122
|
-
tail = transcript[-2000:]
|
|
123
|
-
if prompt[:400] not in tail:
|
|
124
|
-
retry_prompt = transcript + "\n\n" + prompt
|
|
125
|
-
kwargs2.pop("resume", None)
|
|
126
|
-
log.info("compaction advanced gen %s->%s, retrying with derived "
|
|
127
|
-
"context (%d msgs, resume dropped)", gen0, gen1, len(msgs))
|
|
128
|
-
result2 = run_agent_fn(retry_prompt, **kwargs2)
|
|
129
|
-
else:
|
|
130
|
-
log.info("compaction advanced gen %s->%s but no surface derived, "
|
|
131
|
-
"retrying same prompt", gen0, gen1)
|
|
132
|
-
result2 = run_agent_fn(prompt, **kwargs)
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Step 执行器:压缩触发的重试守门(复用已渲染 prompt,不重复组装)。
|
|
3
|
+
|
|
4
|
+
设计稿:docs/migration/02-context-compaction.md §1D。
|
|
5
|
+
参考 dsh packages/core/agent-loop/src/agent.ts:307-378:
|
|
6
|
+
失败重试只在「前进 surface generation」时返回 {kind:'retry'},
|
|
7
|
+
复用已渲染 PromptAssembly,不重跑 pre-step / 组装 / 用户消息准入。
|
|
8
|
+
|
|
9
|
+
Tutti 语境:runner.run_agent 自带 prompt(已渲染);重试的风险不是重复渲染
|
|
10
|
+
(prompt 是同一个字符串),而是「不压缩直接重试」必然再次撑爆。
|
|
11
|
+
本模块的守门:仅当 maybe_compact 真正前进了 surface generation 才重试一次。
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from .compaction import maybe_compact, DEFAULT_PRESSURE_THRESHOLD
|
|
18
|
+
from .error_codes import ErrorCode
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# 触发「压缩后重试」的错误码
|
|
23
|
+
_OVERFLOW_CODES = {ErrorCode.CONTEXT_OVERFLOW, ErrorCode.MAX_TOKENS}
|
|
24
|
+
|
|
25
|
+
# 事前预检阈值:最近一次调用的上下文锚点已占本步模型容量九成 → 第一个请求
|
|
26
|
+
# 大概率被供应商拒。只拦「大概率必死」,不提前压缩平时的高压(used() 是累计
|
|
27
|
+
# 口径,比真实上下文偏大;0.8 的响应式阈值语义与此不同,勿混用)。
|
|
28
|
+
_PRECHECK_RATIO = 0.9
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _v2_compaction_tuning():
|
|
32
|
+
"""settings_v2 orchestrator.compaction 微调 → (threshold|None, retain|None)。
|
|
33
|
+
|
|
34
|
+
None = 未配置(走 maybe_compact 的模块默认);读取/注册失败静默回落。
|
|
35
|
+
retain 允许 0(尾部不保留),与 None(未配置)语义不同,勿合并。"""
|
|
36
|
+
try:
|
|
37
|
+
from .settings_schema import get as ss_get, register_default_namespaces
|
|
38
|
+
register_default_namespaces()
|
|
39
|
+
c = (ss_get("orchestrator") or {}).get("compaction") or {}
|
|
40
|
+
th = c.get("pressure_threshold")
|
|
41
|
+
rt = c.get("retain_tail_tokens")
|
|
42
|
+
return (float(th) if th else None,
|
|
43
|
+
int(rt) if rt is not None else None)
|
|
44
|
+
except Exception:
|
|
45
|
+
return None, None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
49
|
+
llm_caller=None, retain_tail_tokens=None, **kwargs):
|
|
50
|
+
"""执行一次 step;撑爆时压缩并守门重试一次。
|
|
51
|
+
|
|
52
|
+
事前预检(借鉴 freebuff base-chat 每步容量重估):换将切到小窗口模型后,
|
|
53
|
+
响应式路径要等供应商报 CONTEXT_OVERFLOW 才压缩——报得不干净就直接卡死。
|
|
54
|
+
先用「最近一次调用上下文 vs 本步模型容量」判一次,超阈先压缩再发。
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
session: session_log.Session(记录 replace_generation)
|
|
58
|
+
run_agent_fn: runner.run_agent(或测试替身),签名 (prompt, **kwargs) -> dict
|
|
59
|
+
prompt: 已渲染的提示词(重试时原样复用,不重新渲染)
|
|
60
|
+
model: 本步将要使用的模型名(容量与压力估算都按它算)
|
|
61
|
+
llm_caller: 压缩摘要 LLM(None = 不压缩,永不重试)
|
|
62
|
+
**kwargs: 透传 run_agent_fn
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
(result, retried: bool)
|
|
66
|
+
"""
|
|
67
|
+
v2_th, v2_rt = _v2_compaction_tuning()
|
|
68
|
+
compact_kwargs = {}
|
|
69
|
+
if retain_tail_tokens is not None:
|
|
70
|
+
compact_kwargs["retain_tail_tokens"] = retain_tail_tokens
|
|
71
|
+
elif v2_rt is not None:
|
|
72
|
+
compact_kwargs["retain_tail_tokens"] = v2_rt
|
|
73
|
+
if v2_th is not None:
|
|
74
|
+
compact_kwargs["threshold"] = v2_th
|
|
75
|
+
if llm_caller is not None and model:
|
|
76
|
+
try:
|
|
77
|
+
from .token_meter import token_meter
|
|
78
|
+
ctx = token_meter.last_context(session.run_id)
|
|
79
|
+
cap = token_meter.capacity(model)
|
|
80
|
+
if ctx > 0 and cap > 0 and ctx > cap * _PRECHECK_RATIO:
|
|
81
|
+
gen_p0 = session.replace_generation()
|
|
82
|
+
if maybe_compact(session, model=model, llm_caller=llm_caller,
|
|
83
|
+
run_id=session.run_id, **compact_kwargs) \
|
|
84
|
+
and session.replace_generation() > gen_p0:
|
|
85
|
+
log.info("precheck: last ctx~%d > %.0f%% of cap(%s)=%d, "
|
|
86
|
+
"compacted before step", ctx, _PRECHECK_RATIO * 100,
|
|
87
|
+
model, cap)
|
|
88
|
+
except Exception:
|
|
89
|
+
log.debug("precheck skipped", exc_info=True)
|
|
90
|
+
gen0 = session.replace_generation()
|
|
91
|
+
result = run_agent_fn(prompt, **kwargs)
|
|
92
|
+
code = result.get("error_code") or ""
|
|
93
|
+
if code not in _OVERFLOW_CODES or llm_caller is None:
|
|
94
|
+
return result, False
|
|
95
|
+
# 撑爆 → 尝试压缩
|
|
96
|
+
compacted = maybe_compact(session, model=model, llm_caller=llm_caller,
|
|
97
|
+
run_id=session.run_id, **compact_kwargs)
|
|
98
|
+
gen1 = session.replace_generation()
|
|
99
|
+
if not compacted or gen1 <= gen0:
|
|
100
|
+
# 压缩未发生或未前进 generation:重试必然再次撑爆,不重试
|
|
101
|
+
log.info("overflow but no compaction progress (gen %s->%s), no retry",
|
|
102
|
+
gen0, gen1)
|
|
103
|
+
return result, False
|
|
104
|
+
# 压缩生效:surface 已折叠。**真正替换 CLI 请求上下文**(2026-09-21
|
|
105
|
+
# 清最老待深挖项:此前重试仍发原 prompt + 续旧 resume 会话——摘要只进
|
|
106
|
+
# 审计日志,CLI 看到的上下文一点没小,重试必然再爆):
|
|
107
|
+
# ①重试 prompt 改用 session 派生转录(折叠摘要 + 近尾消息,体量骤降);
|
|
108
|
+
# ②弃 resume(旧会话本体仍是膨胀态,续上就前功尽弃)。
|
|
109
|
+
msgs = []
|
|
110
|
+
try:
|
|
111
|
+
msgs = session.derive_messages()
|
|
112
|
+
except Exception:
|
|
113
|
+
log.debug("derive_messages failed on retry", exc_info=True)
|
|
114
|
+
kwargs2 = dict(kwargs)
|
|
115
|
+
if msgs:
|
|
116
|
+
transcript = "\n\n".join("[%s] %s" % (m.get("role"), m.get("content"))
|
|
117
|
+
for m in msgs)
|
|
118
|
+
if len(transcript) > 24_000:
|
|
119
|
+
transcript = transcript[-24_000:]
|
|
120
|
+
retry_prompt = transcript
|
|
121
|
+
# 本步指令必须在场:被折叠/截断掉就显式补在尾部(缺失指令比重复严重)
|
|
122
|
+
tail = transcript[-2000:]
|
|
123
|
+
if prompt[:400] not in tail:
|
|
124
|
+
retry_prompt = transcript + "\n\n" + prompt
|
|
125
|
+
kwargs2.pop("resume", None)
|
|
126
|
+
log.info("compaction advanced gen %s->%s, retrying with derived "
|
|
127
|
+
"context (%d msgs, resume dropped)", gen0, gen1, len(msgs))
|
|
128
|
+
result2 = run_agent_fn(retry_prompt, **kwargs2)
|
|
129
|
+
else:
|
|
130
|
+
log.info("compaction advanced gen %s->%s but no surface derived, "
|
|
131
|
+
"retrying same prompt", gen0, gen1)
|
|
132
|
+
result2 = run_agent_fn(prompt, **kwargs)
|
|
133
133
|
return result2, True
|
package/app/core/store.py
CHANGED
|
@@ -502,6 +502,57 @@ def list_tasks(limit=None, archived=None):
|
|
|
502
502
|
return out
|
|
503
503
|
|
|
504
504
|
|
|
505
|
+
def serial_chain_ids(task_id):
|
|
506
|
+
"""连载链条全部任务 id,根任务在前(一本书一条链:开书资料/发布绑定沿链共享)。
|
|
507
|
+
|
|
508
|
+
含祖先(沿 serial.continues 上溯)与后代(全量扫 continues 指向链条成员);
|
|
509
|
+
非连载/未知任务回退 [task_id]。环与超深链安全(上限 50)。"""
|
|
510
|
+
task_id = str(task_id or "")
|
|
511
|
+
if not task_id:
|
|
512
|
+
return []
|
|
513
|
+
with LOCK:
|
|
514
|
+
by_id = {str(t.get("id")): t for t in _TASKS.values()}
|
|
515
|
+
if task_id not in by_id:
|
|
516
|
+
return [task_id]
|
|
517
|
+
order, seen = [], set()
|
|
518
|
+
cur = task_id
|
|
519
|
+
while cur and cur in by_id and cur not in seen and len(seen) < 50:
|
|
520
|
+
seen.add(cur)
|
|
521
|
+
order.append(cur)
|
|
522
|
+
cur = str(((by_id[cur].get("serial") or {}).get("continues")) or "") or ""
|
|
523
|
+
order.reverse() # 根在前
|
|
524
|
+
in_chain, grew = set(order), True
|
|
525
|
+
while grew: # 后代收进链条,迭代到收敛
|
|
526
|
+
grew = False
|
|
527
|
+
for tid, t in by_id.items():
|
|
528
|
+
if tid in in_chain:
|
|
529
|
+
continue
|
|
530
|
+
parent = str(((t.get("serial") or {}).get("continues")) or "")
|
|
531
|
+
if parent and parent in in_chain:
|
|
532
|
+
order.append(tid)
|
|
533
|
+
in_chain.add(tid)
|
|
534
|
+
grew = True
|
|
535
|
+
return order
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def inherited_book_meta(task, platform):
|
|
539
|
+
"""任务在某平台的作品信息:连载链上「生成完成」的条目整链共用(都是用第一个)。
|
|
540
|
+
|
|
541
|
+
根在前找第一个 status=done 且有 data 的条目;整链都没有则返回任务自己的
|
|
542
|
+
条目(可能为空——调用方按未生成口径处理)。"""
|
|
543
|
+
task = task or {}
|
|
544
|
+
platform = str(platform)
|
|
545
|
+
own = ((task.get("book_meta") or {}).get(platform)) or {}
|
|
546
|
+
for tid in serial_chain_ids(task.get("id") or ""):
|
|
547
|
+
t = task if tid == str(task.get("id")) else get_task(tid)
|
|
548
|
+
if not t:
|
|
549
|
+
continue
|
|
550
|
+
e = ((t.get("book_meta") or {}).get(platform)) or {}
|
|
551
|
+
if e.get("status") == "done" and e.get("data"):
|
|
552
|
+
return e
|
|
553
|
+
return own
|
|
554
|
+
|
|
555
|
+
|
|
505
556
|
def migrate_task_workdirs(old_root, new_root):
|
|
506
557
|
"""把「旧默认保存路径」下的任务目录搬到新默认路径下,并更新任务记录。
|
|
507
558
|
|