@furongjun1999/dsh-memory 0.7.4 → 0.7.5

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.
Files changed (30) hide show
  1. package/README.md +631 -630
  2. package/codebuddy/CODEBUDDY.md +5 -5
  3. package/docs/discipline/templates/zcode-user.md.tmpl +4 -1
  4. package/docs/eval//345/217/221/345/270/20312_/344/270/226/347/225/214/346/250/241/345/236/213/345/212/237/350/203/275/347/253/257_v1.0.md +1 -1
  5. package/docs/eval//345/217/221/345/270/20313_/344/270/212/344/270/213/346/226/207/350/207/252/347/256/241/347/220/206/346/234/272/345/210/266_v1.0.md +114 -0
  6. package/docs/mdcg//347/235/241/347/234/240/345/221/250/346/234/237_/350/277/220/347/273/264/345/211/215/346/217/220/344/270/216/347/273/264/346/212/244/346/214/207/345/215/227_v1.0.md +80 -1
  7. package/docs/plans//347/201/265/346/236/242/350/272/253/344/275/223/303/227/350/204/221_/344/270/226/347/225/214/346/250/241/345/236/213/345/257/271/346/216/245/350/256/276/350/256/241_v0.1.md +76 -0
  8. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +461 -461
  9. package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.2.md +121 -0
  10. package/docs//350/256/260/345/277/206/347/263/273/347/273/237/345/215/263/347/231/275/347/256/261/345/214/226/346/231/272/350/203/275_/346/236/266/346/236/204/345/257/271/347/205/247_v0.3.md +117 -0
  11. package/lib/hooks.d.ts +34 -0
  12. package/lib/hooks.js +165 -0
  13. package/lib/index.js +22 -1
  14. package/lib/lib/mdcg_client.d.ts +37 -0
  15. package/lib/lib/mdcg_client.js +44 -0
  16. package/md_cg/audit.py +7 -1
  17. package/md_cg/bench_governance.py +4 -1
  18. package/md_cg/bench_locomo_zh.py +12 -2
  19. package/md_cg/bench_zh_mad.py +12 -2
  20. package/md_cg/interop.py +25 -9
  21. package/md_cg/sleep.py +223 -103
  22. package/md_cg/sustain.py +150 -4
  23. package/md_cg/test_sustain_bounded.py +624 -0
  24. package/md_cg/units.py +4 -1
  25. package/package.json +1 -1
  26. package/skills/plugin.json +1 -1
  27. package/src/hooks.ts +177 -0
  28. package/src/index.ts +22 -1
  29. package/src/lib/mdcg_client.ts +48 -0
  30. package/zcode/AGENTS.md +5 -5
@@ -19,6 +19,8 @@
19
19
  * 外部裁决回填 → MdcgClient.verify() → MCP cg(op=verify)
20
20
  * 最近记忆时间线 → MdcgClient.timeline() → MCP stg(op=timeline)
21
21
  * 近期事件窗口 → MdcgClient.recent() → MCP cg(op=recent)
22
+ * 窗口追加事件 → MdcgClient.recentAdd() → MCP cg(op=recent, action=add)
23
+ * 会话续接包 → MdcgClient.sessionRecall() → MCP cg(op=session, action=recall)
22
24
  * 身份读取 → MdcgClient.identity() → MCP cg(op=identity)
23
25
  * 白箱能力验证 → MdcgClient.whitebox() → MCP cg(op=whitebox)
24
26
  * 服务信息 → MdcgClient.serviceInfo()→ MCP cg(op=info)
@@ -215,6 +217,48 @@ export class MdcgClient {
215
217
  recent(limit = 20) {
216
218
  return this.cg({ op: 'recent', limit });
217
219
  }
220
+ /** 追加一条**近期事件窗口**记录(`cg(op=recent, action=add)` →
221
+ * `md_cg` 的 `remember_event(role, text, tags, meta, window)`)。
222
+ *
223
+ * 这是「滑动窗口」写侧:事件落 `_recent.jsonl`(**运行态面**),按窗口滚动
224
+ * 淘汰(服务端缺省 200 条,`mdcg.py:91 DEFAULT_RECENT_WINDOW`)——它**不是
225
+ * 知识节点**:不占 knowledge 层、不进检索正排,与 `remember()` 的知识面沉淀
226
+ * 是两条独立的轨道(见 src/hooks.ts 文件头的 contextWindow 头注)。
227
+ *
228
+ * ⚠️ **不注入 as_unit**(与 `write()` 同款理由的反面):本调用不写任何层的
229
+ * 节点(服务端 `remember_event` 不做层白名单校验),收窄单元无收益且可能压低
230
+ * 事件密级(缺省 internal);`meta.session` 由调用方显式给出(沿本文件
231
+ * `remember()` 的 sessionTag 口径)。
232
+ *
233
+ * ⚠️ 服务端对 `meta` 做 `setdefault`(tenant/session/harness/unit,见
234
+ * `mdcos.py:5057 remember_event`):调用方已写的键**不被覆盖**。 */
235
+ recentAdd(role, text, meta = {}, tags = []) {
236
+ return this.cg({ op: 'recent', action: 'add', role, text, meta, tags });
237
+ }
238
+ /** **会话续接包**(`cg(op=session, action=recall)` → `md_cg` 的
239
+ * `session_recall`):一次取回 notes / goals / tasks / **recent 事件窗口** /
240
+ * unresolved / self_state,按 `budget_tokens` 整包裁剪(服务端
241
+ * `mdcos.py:3351-3362`:交替丢 recent / notes 尾部)。
242
+ *
243
+ * 本插件消费其中的 `recent` 段——滑动窗口的**注入面**(见 src/hooks.ts)。
244
+ * `recent_limit` 语义 = 近期事件**条数**(非「轮数」);服务端读取时会按
245
+ * `max(1, recent_limit or 10)` 归一(`mdcos.py:3309`)。
246
+ *
247
+ * ⚠️ 两条服务端事实(决定注入侧的可达性,勿据本方法名臆测):
248
+ * ① `recent` 段取 `recent_events(limit=recent_limit)` —— **不按 session
249
+ * 过滤**(近期事件是运行态滚动窗口,会话归属只写在每条事件的 meta 里);
250
+ * ② budget 是**整包**预算:库内 notes/tasks/self_state 占位越多,同样
251
+ * budget 下 recent 段被裁得越短(实测:空库 + 10 条窗口条目,
252
+ * budget_tokens=600 → recent 8 条;1200 → 10 条)。
253
+ *
254
+ * 只读调用,不注入 as_unit(同其它读路径:读无副作用,收窄只会压低 owner
255
+ * 的 private 读能力)。 */
256
+ sessionRecall(session, recentLimit, budgetTokens) {
257
+ return this.cg({
258
+ op: 'session', action: 'recall',
259
+ session, recent_limit: recentLimit, budget_tokens: budgetTokens,
260
+ });
261
+ }
218
262
  /** 最近记忆**时间线**(AEIS `timeline` 的对应物):`stg(op=timeline)` →
219
263
  * `{count, limit, items:[{id, layer, start, end, preview}]}`,按时间倒序。
220
264
  *
package/md_cg/audit.py CHANGED
@@ -456,11 +456,17 @@ def _verify_code(payload, ctx):
456
456
  # 被测命令只要输出非 gbk 字节,读取线程就抛 UnicodeDecodeError →
457
457
  # p.stdout/p.stderr 可能为空 → 下一行的失败证据丢失,
458
458
  # 「实测失败」会退化成一句没有依据的 REJECT(对齐 whitebox.py 的写法)。
459
+ # stdin=subprocess.DEVNULL(issue #63 同批加固):不指定 stdin 时子进程
460
+ # 继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道),读 stdin 的测试
461
+ # 命令会悬挂到 timeout 才被 kill——形态对齐 `md_cg/run_tests.py:105`。
459
462
  p = subprocess.run(argv, cwd=ctx.get("cwd"), capture_output=True,
460
463
  text=True, encoding="utf-8", errors="replace",
461
- shell=False,
464
+ shell=False, stdin=subprocess.DEVNULL,
462
465
  timeout=int(os.environ.get("MDCG_CODE_TEST_TIMEOUT", "60")))
463
466
  except (OSError, subprocess.SubprocessError) as exc:
467
+ # TimeoutExpired(SubprocessError 子类)也走这里:DEFER + 文案带异常
468
+ # 名与原文(如 "TimeoutExpired: Command '…' timed out after 60 seconds")
469
+ # ——超时**不吞**(issue #63)。
464
470
  return _verdict(DEFER, "code", f"测试无法执行:{type(exc).__name__}: {exc}")
465
471
  if p.returncode == 0:
466
472
  return _verdict(ACCEPT, "code", f"实测通过:{cmd}")
@@ -315,9 +315,12 @@ def run(root=None, keep=False):
315
315
 
316
316
  def _git_head():
317
317
  try:
318
+ # timeout=10 既有;stdin=subprocess.DEVNULL 为 issue #63 同批加固——
319
+ # 不指定 stdin 时子进程继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
318
320
  out = subprocess.run(["git", "rev-parse", "--short", "HEAD"],
319
321
  capture_output=True, text=True, encoding="utf-8",
320
- errors="replace", shell=False, timeout=10)
322
+ errors="replace", shell=False, timeout=10,
323
+ stdin=subprocess.DEVNULL)
321
324
  return (out.stdout or "").strip() or "unknown"
322
325
  except Exception: # noqa: BLE001
323
326
  return "unknown"
@@ -303,6 +303,9 @@ ROW_RE = re.compile(
303
303
  r"([\d.]+)%\s+([\d.]+)%\s+([\d.]+)\s*$", re.M)
304
304
  GATE_RE = re.compile(r"拒答率:([\d.]+)%")
305
305
  LINE_RE = re.compile(r"拒答线(正例 hit@1 题 Top-1 分 p10):([\d.]+)")
306
+ #: 单臂评测子进程的超时(秒)——issue #63 同批加固:评测跑批比 git 长,按
307
+ #: 「一臂为分钟级」给 1800s(30min)为安全上界;超时即 kill(不再是无限等待)。
308
+ _EVAL_TIMEOUT_S = 1800
306
309
 
307
310
 
308
311
  # 生效条件:RUST_BIN 经 os.path.exists 为假时抛 SystemExit;否则以 argv 列表 [RUST_BIN, "--dataset", "lc", "--tag", name, "--lib", lib, "--qfile", qfile](extra 为真值时追加其元素)执行 subprocess.run,返回码非 0 或 stdout 未匹配 ROW_RE 时抛 SystemExit,成功时返回 (got, neg, line),其中 GATE_RE/LINE_RE 未命中时对应值为 None。
@@ -320,8 +323,15 @@ def run_one(name, lib, qfile, extra=None):
320
323
  if extra:
321
324
  argv += list(extra)
322
325
  env = dict(os.environ, PYTHONUTF8="1")
323
- p = subprocess.run(argv, capture_output=True, text=True,
324
- encoding="utf-8", errors="replace", env=env, cwd=HERE)
326
+ try:
327
+ # timeout + stdin=DEVNULL(issue #63 同批加固):评测跑批有界化,
328
+ # 且不继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
329
+ p = subprocess.run(argv, capture_output=True, text=True,
330
+ encoding="utf-8", errors="replace", env=env, cwd=HERE,
331
+ timeout=_EVAL_TIMEOUT_S,
332
+ stdin=subprocess.DEVNULL)
333
+ except subprocess.TimeoutExpired:
334
+ raise SystemExit(f"[失败] {name}:评测超时(>{_EVAL_TIMEOUT_S}s)已被 kill")
325
335
  if p.returncode != 0:
326
336
  raise SystemExit(f"[失败] {name}:{(p.stderr or '')[-900:]}")
327
337
  got = {}
@@ -481,6 +481,9 @@ ROW_RE = re.compile(
481
481
  r"^\s*(precise|temporal|interference|reference)\s+(\d+)\s+"
482
482
  r"([\d.]+)%\s+([\d.]+)%\s+([\d.]+)\s*$", re.M)
483
483
  GROUPS = ["precise", "temporal", "interference", "reference"]
484
+ #: 单臂评测子进程的超时(秒)——issue #63 同批加固:评测跑批比 git 长,按
485
+ #: 「一臂为分钟级」给 1800s(30min)为安全上界;超时即 kill(不再是无限等待)。
486
+ _EVAL_TIMEOUT_S = 1800
484
487
 
485
488
 
486
489
  # 生效条件:os.path.exists(RUST_BIN) 为真时以 argv=[RUST_BIN,"--dataset","mad","--tag",name,"--lib",lib](extra 为真值时追加 list(extra))执行 subprocess,返回码非 0 或 ROW_RE 在 stdout 未匹配到任何组时 raise SystemExit,否则返回 {组:(n,hit@1,hit@5,MRR)}。
@@ -498,8 +501,15 @@ def run_one(name, lib, extra=None):
498
501
  if extra:
499
502
  argv += list(extra)
500
503
  env = dict(os.environ, PYTHONUTF8="1")
501
- p = subprocess.run(argv, capture_output=True, text=True,
502
- encoding="utf-8", errors="replace", env=env, cwd=HERE)
504
+ try:
505
+ # timeout + stdin=DEVNULL(issue #63 同批加固):评测跑批有界化,
506
+ # 且不继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道)。
507
+ p = subprocess.run(argv, capture_output=True, text=True,
508
+ encoding="utf-8", errors="replace", env=env, cwd=HERE,
509
+ timeout=_EVAL_TIMEOUT_S,
510
+ stdin=subprocess.DEVNULL)
511
+ except subprocess.TimeoutExpired:
512
+ raise SystemExit(f"[失败] {name}:评测超时(>{_EVAL_TIMEOUT_S}s)已被 kill")
503
513
  if p.returncode != 0:
504
514
  raise SystemExit(f"[失败] {name}:{(p.stderr or '')[-900:]}")
505
515
  got = {}
package/md_cg/interop.py CHANGED
@@ -47,6 +47,11 @@ _FORBIDDEN_RES = [
47
47
  ]
48
48
  _FORBIDDEN_KEYS = {"prompt", "content", "spec", "api_key", "base", "model",
49
49
  "system_prompt", "user_prompt", "content_head"}
50
+ #: git add/commit 子进程的超时(秒)——issue #63 同批加固:本机 git 为秒级
51
+ #: 操作,120s 为安全上界;超时即 kill。**stdin=subprocess.DEVNULL 同加**:
52
+ #: 不指定 stdin 时子进程继承父进程 stdin(常驻宿主下是 JSON-RPC 活管道),
53
+ #: 读 stdin 的子进程会悬挂(先例形态 `md_cg/run_tests.py:105`)。
54
+ _GIT_TIMEOUT_S = 120
50
55
 
51
56
 
52
57
  class InteropSanityError(ValueError):
@@ -323,15 +328,26 @@ def write_verdict_to_repo(verdict: dict, repo: str = HERE,
323
328
  out = {"ok": True, "path": fp}
324
329
  if do_commit:
325
330
  import subprocess
326
- r1 = subprocess.run(["git", "add", os.path.relpath(fp, repo)],
327
- cwd=repo, capture_output=True, text=True,
328
- encoding="utf-8", errors="replace")
329
- r2 = subprocess.run(
330
- ["git", "commit", "-q", "-m",
331
- f"interop(verifier): iter={it} verdict={verdict.get('verdict')} "
332
- f"passed={verdict.get('passed')} failed={verdict.get('failed')}"],
333
- cwd=repo, capture_output=True, text=True,
334
- encoding="utf-8", errors="replace")
331
+ try:
332
+ r1 = subprocess.run(["git", "add", os.path.relpath(fp, repo)],
333
+ cwd=repo, capture_output=True, text=True,
334
+ encoding="utf-8", errors="replace",
335
+ timeout=_GIT_TIMEOUT_S,
336
+ stdin=subprocess.DEVNULL)
337
+ r2 = subprocess.run(
338
+ ["git", "commit", "-q", "-m",
339
+ f"interop(verifier): iter={it} verdict={verdict.get('verdict')} "
340
+ f"passed={verdict.get('passed')} failed={verdict.get('failed')}"],
341
+ cwd=repo, capture_output=True, text=True,
342
+ encoding="utf-8", errors="replace",
343
+ timeout=_GIT_TIMEOUT_S, stdin=subprocess.DEVNULL)
344
+ except subprocess.TimeoutExpired:
345
+ # 超时**不吞**:如实记进既有错误面(与 rc≠0 同一条 commit_err),
346
+ # 不假装提交成功(issue #63)。
347
+ out["committed"] = False
348
+ out["commit_err"] = ("git 超时(%ss)已被 kill:%s" %
349
+ (_GIT_TIMEOUT_S, out_dir))
350
+ return out
335
351
  out["committed"] = r2.returncode == 0
336
352
  if r2.returncode != 0:
337
353
  out["commit_err"] = (r2.stderr or r1.stderr)[:200]