@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.2

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 (258) hide show
  1. package/INSTALL.md +25 -0
  2. package/PANEL.md +92 -0
  3. package/README.md +286 -2
  4. package/RELEASE-NOTES.md +732 -0
  5. package/bin/bc-acceptance-full.mjs +91 -0
  6. package/bin/bc-acceptance-tight.mjs +66 -0
  7. package/bin/bc-acceptance.mjs +22 -0
  8. package/bin/bc-add-keywords.mjs +21 -0
  9. package/bin/bc-demo.mjs +47 -0
  10. package/bin/bc-expand-hint.mjs +179 -0
  11. package/bin/bc-ingest-index.mjs +35 -0
  12. package/bin/bc-ingest.mjs +31 -0
  13. package/bin/bc-label-demo.mjs +18 -0
  14. package/bin/bc-relabel.mjs +150 -0
  15. package/bin/bc-sessionize.mjs +59 -0
  16. package/bin/coref_pipeline.py +47 -0
  17. package/bin/coref_service.py +57 -0
  18. package/bin/debug_ckip.py +16 -0
  19. package/bin/debug_ner_zh.py +18 -0
  20. package/bin/doctor.mjs +275 -0
  21. package/bin/en_ner_error_audit.py +33 -0
  22. package/bin/eval-label-rules.mjs +342 -0
  23. package/bin/export-turns.py +68 -0
  24. package/bin/extract-user-paragraph.mjs +14 -0
  25. package/bin/extraction-composite-smoke.mjs +28 -0
  26. package/bin/extraction-latency.mjs +58 -0
  27. package/bin/extraction-user-smoke.mjs +28 -0
  28. package/bin/extraction_service.py +164 -0
  29. package/bin/facet-active-experiment.mjs +110 -0
  30. package/bin/facet-covid.mjs +8 -0
  31. package/bin/facet-curve.mjs +27 -0
  32. package/bin/facet-embedding-train.mjs +20 -0
  33. package/bin/facet-embedding-validate.mjs +83 -0
  34. package/bin/facet-eval-roleplay.mjs +42 -0
  35. package/bin/facet-eval.mjs +38 -0
  36. package/bin/facet-fold.mjs +31 -0
  37. package/bin/facet-load.mjs +16 -0
  38. package/bin/facet-mlp-scan.mjs +45 -0
  39. package/bin/facet-mvp.mjs +34 -0
  40. package/bin/facet-online-experiments.mjs +78 -0
  41. package/bin/facet-online-race.mjs +99 -0
  42. package/bin/facet-online-validate.mjs +51 -0
  43. package/bin/facet-panel.mjs +22 -0
  44. package/bin/facet-passage.mjs +41 -0
  45. package/bin/facet-random-validate.mjs +48 -0
  46. package/bin/facet-roleplay.mjs +33 -0
  47. package/bin/facet-scan.mjs +56 -0
  48. package/bin/facet-segment.mjs +34 -0
  49. package/bin/facet-train.mjs +19 -0
  50. package/bin/facet-validate.mjs +103 -0
  51. package/bin/facet-zeroshot.mjs +14 -0
  52. package/bin/install-templates.mjs +72 -0
  53. package/bin/memory-bench.mjs +53 -0
  54. package/bin/memory-build-summary.mjs +43 -0
  55. package/bin/memory-build-vector.mjs +31 -0
  56. package/bin/memory-build.mjs +41 -0
  57. package/bin/memory-calibration.mjs +70 -0
  58. package/bin/memory-closed-loop.mjs +119 -0
  59. package/bin/memory-dry-run.mjs +70 -0
  60. package/bin/memory-eval.mjs +104 -0
  61. package/bin/memory-event-alias-test.mjs +89 -0
  62. package/bin/memory-extraction-bench.mjs +58 -0
  63. package/bin/memory-load-gate-replay.mjs +137 -0
  64. package/bin/memory-loop-test.mjs +90 -0
  65. package/bin/memory-pre-turn-harness.mjs +63 -0
  66. package/bin/memory-query.mjs +39 -0
  67. package/bin/memory-replay-hints.mjs +84 -0
  68. package/bin/memory-scale.mjs +58 -0
  69. package/bin/memory-scenario-test.mjs +146 -0
  70. package/bin/memory-user-loop.mjs +95 -0
  71. package/bin/ner_bench_tiny_zh.py +59 -0
  72. package/bin/ner_bench_zh.py +60 -0
  73. package/bin/ner_conll_en.py +139 -0
  74. package/bin/ner_direct_en.py +123 -0
  75. package/bin/ner_direct_zh.py +100 -0
  76. package/bin/ner_pipeline.py +208 -0
  77. package/bin/noise-selfcheck.mjs +50 -0
  78. package/bin/pack-integrity.mjs +47 -0
  79. package/bin/panel-check.mjs +57 -0
  80. package/bin/plugin-integrity.mjs +74 -0
  81. package/bin/preview-live.mjs +13 -0
  82. package/bin/public-install-check.mjs +222 -0
  83. package/bin/relabel-apply.mjs +272 -0
  84. package/bin/relabel-dry-run.mjs +381 -0
  85. package/bin/relik_pipeline.py +39 -0
  86. package/bin/relik_service.py +52 -0
  87. package/bin/srl_pipeline.py +55 -0
  88. package/bin/srl_service.py +68 -0
  89. package/bin/summary-index.mjs +151 -0
  90. package/bin/summary-query.mjs +79 -0
  91. package/bin/vec-check.mjs +27 -0
  92. package/bin/zh_ner_error_audit.py +36 -0
  93. package/bin/zh_ner_slot_eval.py +38 -0
  94. package/client.js +760 -0
  95. package/cordis.patch.yml +378 -0
  96. package/docs/API-NOTES.md +1743 -0
  97. package/docs/ARCHITECTURE.md +355 -0
  98. package/docs/CACHE.md +253 -0
  99. package/docs/CONDUCT.md +30 -0
  100. package/docs/CREATOR-HANDOFF.md +102 -0
  101. package/docs/EXPERIMENT.md +136 -0
  102. package/docs/FACET-MVP-REPORT.md +473 -0
  103. package/docs/GUIDE-0.4.66.md +195 -0
  104. package/docs/GUIDE.md +127 -0
  105. package/docs/MECHANISM.md +243 -0
  106. package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
  107. package/docs/README.md +37 -0
  108. package/docs/SESSION-LAYOUT.md +151 -0
  109. package/docs/history-map.md +249 -0
  110. package/docs/relabel-migration-wiring.md +366 -0
  111. package/guides/00-READ-FIRST.md +206 -0
  112. package/guides/01-INSTALL.md +207 -0
  113. package/guides/02-TABLES.md +225 -0
  114. package/guides/03-AUDIT.md +201 -0
  115. package/guides/04-REBUILD.md +306 -0
  116. package/guides/05-FIRST-RUN.md +191 -0
  117. package/guides/06-EMBEDDING.md +146 -0
  118. package/guides/07-PTC-AND-ASSETS.md +221 -0
  119. package/icon.svg +7 -0
  120. package/locale/en.json +6 -0
  121. package/locale/zh.json +6 -0
  122. package/models/README.md +13 -0
  123. package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
  124. package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  125. package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  126. package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  127. package/package.json +139 -4
  128. package/src/asset-tool.mjs +237 -0
  129. package/src/assets.mjs +244 -0
  130. package/src/backfill.mjs +74 -0
  131. package/src/backup.mjs +80 -0
  132. package/src/deadend.mjs +95 -0
  133. package/src/deleted-store.mjs +35 -0
  134. package/src/engine.mjs +3831 -0
  135. package/src/facets/classifier.mjs +146 -0
  136. package/src/facets/embedding-service.mjs +53 -0
  137. package/src/facets/embedding-worker.mjs +27 -0
  138. package/src/facets/embedding.mjs +163 -0
  139. package/src/facets/extract.mjs +80 -0
  140. package/src/facets/fold-plan.mjs +84 -0
  141. package/src/facets/hybrid.mjs +29 -0
  142. package/src/facets/index.mjs +113 -0
  143. package/src/facets/linear-probe.mjs +43 -0
  144. package/src/facets/load.mjs +37 -0
  145. package/src/facets/mlp-probe.mjs +54 -0
  146. package/src/facets/mlp-service.mjs +53 -0
  147. package/src/facets/mlp-worker.mjs +26 -0
  148. package/src/facets/online-knn.mjs +58 -0
  149. package/src/facets/online-models.mjs +227 -0
  150. package/src/facets/panel-state.mjs +23 -0
  151. package/src/facets/panel.mjs +73 -0
  152. package/src/facets/random.mjs +26 -0
  153. package/src/facets/scanner.mjs +35 -0
  154. package/src/facets/schema.mjs +75 -0
  155. package/src/facets/seed-examples.mjs +58 -0
  156. package/src/facets/sensitivity.mjs +98 -0
  157. package/src/facets/service.mjs +53 -0
  158. package/src/facets/store.mjs +39 -0
  159. package/src/facets/worker.mjs +23 -0
  160. package/src/folds-store.mjs +109 -0
  161. package/src/groups.mjs +167 -0
  162. package/src/handoff-tool.mjs +51 -0
  163. package/src/handoff.mjs +127 -0
  164. package/src/index.mjs +649 -0
  165. package/src/inject-policy.mjs +17 -0
  166. package/src/learning.mjs +88 -0
  167. package/src/memory/alias-proposal.mjs +40 -0
  168. package/src/memory/bc-audit.mjs +245 -0
  169. package/src/memory/bc-index-updater.mjs +404 -0
  170. package/src/memory/bc-index.mjs +48 -0
  171. package/src/memory/bc-label.mjs +220 -0
  172. package/src/memory/bc-panel-state.mjs +107 -0
  173. package/src/memory/bc-pre-step.mjs +463 -0
  174. package/src/memory/bc-recall.mjs +470 -0
  175. package/src/memory/bc-shift.mjs +123 -0
  176. package/src/memory/bc-store.mjs +41 -0
  177. package/src/memory/bm25.mjs +112 -0
  178. package/src/memory/calibration.mjs +54 -0
  179. package/src/memory/chatter.mjs +29 -0
  180. package/src/memory/cmap-file.mjs +107 -0
  181. package/src/memory/concepts-file.mjs +41 -0
  182. package/src/memory/coref-slots.mjs +40 -0
  183. package/src/memory/event-alias-ledger.mjs +87 -0
  184. package/src/memory/event-log.mjs +95 -0
  185. package/src/memory/event-model.mjs +132 -0
  186. package/src/memory/event-segmenter.mjs +36 -0
  187. package/src/memory/event-summary-tree.mjs +122 -0
  188. package/src/memory/extraction-adapter.mjs +76 -0
  189. package/src/memory/extraction-composite.mjs +75 -0
  190. package/src/memory/extraction-core.mjs +76 -0
  191. package/src/memory/extraction-pre-step.mjs +161 -0
  192. package/src/memory/extraction-provider.mjs +300 -0
  193. package/src/memory/extraction-score.mjs +36 -0
  194. package/src/memory/facet-stabilizer.mjs +59 -0
  195. package/src/memory/history-map.mjs +139 -0
  196. package/src/memory/language-router.mjs +30 -0
  197. package/src/memory/load-gate.mjs +161 -0
  198. package/src/memory/mechanical-note.mjs +104 -0
  199. package/src/memory/onboarding.mjs +163 -0
  200. package/src/memory/persona.mjs +78 -0
  201. package/src/memory/planner.mjs +249 -0
  202. package/src/memory/pre-turn.mjs +80 -0
  203. package/src/memory/query-aliases.mjs +39 -0
  204. package/src/memory/recall-cue.mjs +43 -0
  205. package/src/memory/recall.mjs +65 -0
  206. package/src/memory/referent-candidates.mjs +55 -0
  207. package/src/memory/referent-ledger.mjs +86 -0
  208. package/src/memory/relabel-migrate.mjs +376 -0
  209. package/src/memory/relik-adapter.mjs +25 -0
  210. package/src/memory/special-keywords.mjs +60 -0
  211. package/src/memory/structured-adapters.mjs +75 -0
  212. package/src/memory/summary-index.mjs +335 -0
  213. package/src/memory/summary-tree.mjs +110 -0
  214. package/src/memory/term-extractor.mjs +44 -0
  215. package/src/memory/tokenize.mjs +9 -0
  216. package/src/memory/vector-index.mjs +54 -0
  217. package/src/memory/window-summary.mjs +133 -0
  218. package/src/mood.mjs +133 -0
  219. package/src/peer-tool.mjs +63 -0
  220. package/src/peer.mjs +144 -0
  221. package/src/policy.mjs +170 -0
  222. package/src/preflight.mjs +113 -0
  223. package/src/provenance.mjs +94 -0
  224. package/src/range.mjs +75 -0
  225. package/src/recall-exact-tool.mjs +267 -0
  226. package/src/recall-tool.mjs +521 -0
  227. package/src/recheck.mjs +99 -0
  228. package/src/save-start.mjs +29 -0
  229. package/src/session-guard.mjs +140 -0
  230. package/src/session-log-tool.mjs +322 -0
  231. package/src/session-log.mjs +606 -0
  232. package/src/skills.mjs +111 -0
  233. package/src/split-range.mjs +115 -0
  234. package/src/state.mjs +198 -0
  235. package/src/t0.mjs +149 -0
  236. package/src/tag-confirm.mjs +78 -0
  237. package/src/tag-detect.mjs +42 -0
  238. package/src/tag-maintenance.mjs +285 -0
  239. package/src/tag-reject.mjs +73 -0
  240. package/src/tags-store.mjs +94 -0
  241. package/src/tags.mjs +692 -0
  242. package/src/threads-backend.mjs +26 -0
  243. package/src/threads-core.mjs +1887 -0
  244. package/src/threads-store.mjs +207 -0
  245. package/src/threads-tool.mjs +522 -0
  246. package/src/threads.mjs +929 -0
  247. package/src/tool-preview.mjs +427 -0
  248. package/src/topic.mjs +63 -0
  249. package/src/training.mjs +75 -0
  250. package/src/turn-tag.mjs +62 -0
  251. package/src/user-turn.mjs +39 -0
  252. package/templates/cmap.md +11 -0
  253. package/templates/concepts.md +12 -0
  254. package/templates/habits.md +40 -0
  255. package/templates/keywords.md +6 -0
  256. package/templates/rebuild.md +134 -0
  257. package/templates/rules-safe.md +184 -0
  258. package/templates/rules.md +177 -0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Direct token-classification inference, no transformers pipeline."""
4
+ import json
5
+ import sys
6
+ import time
7
+
8
+ import torch
9
+ from datasets import load_dataset
10
+ from transformers import AutoModelForTokenClassification, AutoTokenizer
11
+
12
+ MODEL = "uer/roberta-base-finetuned-cluener2020-chinese"
13
+ DATASET = "xusenlin/clue-ner"
14
+ LIMIT = int(sys.argv[1]) if len(sys.argv) > 1 else 20
15
+ OUT = sys.argv[2] if len(sys.argv) > 2 else None
16
+ MAX_LEN = 256
17
+
18
+ torch.set_num_threads(1)
19
+ print("load tokenizer", flush=True)
20
+ tok = AutoTokenizer.from_pretrained(MODEL)
21
+ print("fast", tok.is_fast, flush=True)
22
+ model = AutoModelForTokenClassification.from_pretrained(MODEL)
23
+ model.eval()
24
+ id2label = model.config.id2label
25
+ print("load data", flush=True)
26
+ ds = load_dataset(DATASET, split="validation")
27
+
28
+
29
+ def predict(text):
30
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=MAX_LEN, return_offsets_mapping=tok.is_fast)
31
+ offsets = enc.pop("offset_mapping")[0].tolist()
32
+ with torch.no_grad():
33
+ logits = model(**enc).logits[0]
34
+ ids = logits.argmax(-1).tolist()
35
+ entities = []
36
+ current = None
37
+ for idx, label_id in enumerate(ids):
38
+ label = id2label[int(label_id)]
39
+ start, end = offsets[idx]
40
+ if start == end:
41
+ continue
42
+ if label == "O":
43
+ if current is not None:
44
+ entities.append(current)
45
+ current = None
46
+ continue
47
+ prefix, _, kind = label.partition("-")
48
+ if prefix == "S":
49
+ if current is not None:
50
+ entities.append(current)
51
+ current = None
52
+ entities.append({"text": text[start:end], "label": kind, "start": start, "end": end})
53
+ elif prefix == "B":
54
+ if current is not None:
55
+ entities.append(current)
56
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
57
+ elif prefix in ("I", "E") and current is not None:
58
+ current["text"] += text[start:end]
59
+ current["end"] = end
60
+ if prefix == "E":
61
+ entities.append(current)
62
+ current = None
63
+ if current is not None:
64
+ entities.append(current)
65
+ return entities
66
+
67
+
68
+ def score(pred, gold):
69
+ gold_keys = {(g["entity"], g["label"]) for g in gold}
70
+ pred_keys = {(p["text"].replace(" ", ""), p["label"]) for p in pred}
71
+ tp = len(gold_keys & pred_keys)
72
+ fp = len(pred_keys - gold_keys)
73
+ fn = len(gold_keys - pred_keys)
74
+ precision = 0 if not pred_keys else tp / len(pred_keys)
75
+ recall = 0 if not gold_keys else tp / len(gold_keys)
76
+ f1 = 0 if precision + recall == 0 else 2 * precision * recall / (precision + recall)
77
+ return {"tp": tp, "fp": fp, "fn": fn, "precision": round(precision, 4), "recall": round(recall, 4), "f1": round(f1, 4)}
78
+
79
+
80
+ rows = []
81
+ for i in range(min(LIMIT, len(ds))):
82
+ print("sample", i, flush=True)
83
+ item = ds[i]
84
+ t0 = time.time()
85
+ pred = predict(item["text"])
86
+ gold = [{"entity": e["entity"], "label": e["label"]} for e in item["entities"]]
87
+ rows.append({"text": item["text"], "pred": pred, "gold": gold})
88
+ print("done", i, round(time.time() - t0, 2), pred[:3], flush=True)
89
+
90
+ result = {
91
+ "model": MODEL,
92
+ "dataset": DATASET,
93
+ "n": len(rows),
94
+ "score": score([p for r in rows for p in r["pred"]], [g for r in rows for g in r["gold"]]),
95
+ "rows": rows,
96
+ }
97
+ if OUT:
98
+ with open(OUT, "w", encoding="utf-8") as f:
99
+ json.dump(result, f, ensure_ascii=False, indent=2)
100
+ print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2), flush=True)
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """Bilingual NER pipeline over local JSONL messages. No training, no live IO."""
4
+ import json
5
+ import re
6
+ import sys
7
+
8
+ import torch
9
+ from transformers import AutoModelForTokenClassification, AutoTokenizer
10
+
11
+ ZH_MODEL = "uer/roberta-base-finetuned-cluener2020-chinese"
12
+ EN_MODEL = "elastic/distilbert-base-uncased-finetuned-conll03-english"
13
+ MAX_LEN = 256
14
+
15
+ ZH_SLOT = {
16
+ "name": "characters",
17
+ "address": "location",
18
+ "scene": "location",
19
+ "company": "objects",
20
+ "organization": "objects",
21
+ "government": "objects",
22
+ "book": "objects",
23
+ "movie": "objects",
24
+ "game": "objects",
25
+ }
26
+ EN_SLOT = {"PER": "characters", "LOC": "location", "ORG": "objects", "MISC": "objects", "PROPER": "objects"}
27
+ PROPER_STOP = {
28
+ "The", "This", "That", "These", "Those", "If", "Then", "There", "Here",
29
+ "We", "You", "I", "It", "He", "She", "They", "But", "And", "Or", "So",
30
+ "DSH", "TAG", "CPU", "GPU", "API", "JSON", "HTML", "HTTP", "HTTPS",
31
+ }
32
+
33
+ torch.set_num_threads(1)
34
+ print("load zh", flush=True)
35
+ zh_tok = AutoTokenizer.from_pretrained(ZH_MODEL)
36
+ zh_model = AutoModelForTokenClassification.from_pretrained(ZH_MODEL)
37
+ zh_model.eval()
38
+ print("load en", flush=True)
39
+ en_tok = AutoTokenizer.from_pretrained(EN_MODEL)
40
+ en_model = AutoModelForTokenClassification.from_pretrained(EN_MODEL)
41
+ en_model.eval()
42
+ print("models ready", flush=True)
43
+
44
+
45
+ def route(text):
46
+ cjk = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
47
+ latin = sum(1 for c in text if c.isascii() and c.isalpha())
48
+ return "en" if cjk == 0 and latin > 0 else "zh"
49
+
50
+
51
+ def decode(text, offsets, ids, id2label, mode):
52
+ entities = []
53
+ current = None
54
+ for idx, label_id in enumerate(ids):
55
+ label = id2label[int(label_id)]
56
+ start, end = offsets[idx]
57
+ if start == end:
58
+ continue
59
+ if label == "O":
60
+ if current is not None:
61
+ entities.append(current)
62
+ current = None
63
+ continue
64
+ prefix, _, kind = label.partition("-")
65
+ if mode == "zh":
66
+ if prefix == "S":
67
+ if current is not None:
68
+ entities.append(current)
69
+ entities.append({"text": text[start:end], "label": kind, "start": start, "end": end})
70
+ current = None
71
+ elif prefix == "B":
72
+ if current is not None:
73
+ entities.append(current)
74
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
75
+ elif prefix in ("I", "E") and current is not None:
76
+ current["text"] += text[start:end]
77
+ current["end"] = end
78
+ if prefix == "E":
79
+ entities.append(current)
80
+ current = None
81
+ else:
82
+ if prefix == "B":
83
+ if current is not None:
84
+ entities.append(current)
85
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
86
+ elif prefix == "I" and current is not None and current["label"] == kind:
87
+ current["text"] += text[start:end]
88
+ current["end"] = end
89
+ else:
90
+ if current is not None:
91
+ entities.append(current)
92
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
93
+ if current is not None:
94
+ entities.append(current)
95
+ if mode == "en":
96
+ merged = []
97
+ for entity in entities:
98
+ last = merged[-1] if merged else None
99
+ if last is not None and last["label"] == entity["label"] and entity["start"] - last["end"] <= 1:
100
+ last["text"] += entity["text"]
101
+ last["end"] = entity["end"]
102
+ else:
103
+ merged.append(entity)
104
+ entities = merged
105
+ return entities
106
+
107
+
108
+ def infer(tok, model, text, mode):
109
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=MAX_LEN, return_offsets_mapping=True)
110
+ offsets = enc.pop("offset_mapping")[0].tolist()
111
+ with torch.no_grad():
112
+ logits = model(**enc).logits[0]
113
+ return decode(text, offsets, logits.argmax(-1).tolist(), model.config.id2label, mode)
114
+
115
+
116
+ def extract(text):
117
+ lang = route(text)
118
+ zh_entities = infer(zh_tok, zh_model, text, "zh")
119
+ en_entities = infer(en_tok, en_model, text, "en")
120
+ combined = []
121
+ seen = set()
122
+ for entity in en_entities:
123
+ entity_text = entity["text"]
124
+ if any(c.isascii() and c.isalpha() for c in entity_text):
125
+ key = (entity["start"], entity["end"], entity_text)
126
+ if key not in seen:
127
+ seen.add(key)
128
+ combined.append(entity)
129
+ for entity in zh_entities:
130
+ entity_text = entity["text"]
131
+ if not any(c.isascii() and c.isalpha() for c in entity_text):
132
+ key = (entity["start"], entity["end"], entity_text)
133
+ if key not in seen:
134
+ seen.add(key)
135
+ combined.append(entity)
136
+ combined.sort(key=lambda item: (item["start"], item["end"]))
137
+ merged = []
138
+ for entity in combined:
139
+ last = merged[-1] if merged else None
140
+ if last is not None and entity["start"] <= last["end"] + 1:
141
+ between = text[last["end"]:entity["start"]]
142
+ both_latin = all(any(c.isascii() and c.isalpha() for c in item["text"]) for item in (last, entity))
143
+ if both_latin and between.strip() == "":
144
+ last["text"] = text[last["start"]:entity["end"]]
145
+ last["end"] = entity["end"]
146
+ if last["label"] not in ("PER", "PERSON", "name"):
147
+ last["label"] = entity["label"] if entity["label"] in ("PER", "PERSON", "name") else last["label"]
148
+ continue
149
+ merged.append(entity)
150
+ combined = merged
151
+ existing_spans = [(item["start"], item["end"], item) for item in combined]
152
+ for match in re.finditer(r"(?<![A-Za-z0-9_])[A-Z][A-Za-z0-9_-]{1,20}(?![A-Za-z0-9_])", text):
153
+ token = match.group(0)
154
+ if token in PROPER_STOP:
155
+ continue
156
+ span = (match.start(), match.end())
157
+ overlap = next(((start, end, item) for start, end, item in existing_spans if start < span[1] and span[0] < end), None)
158
+ if overlap is not None:
159
+ if overlap[0] == span[0] and len(token) > len(overlap[2]["text"]):
160
+ combined.remove(overlap[2])
161
+ combined.append({"text": token, "label": "PROPER", "start": span[0], "end": span[1]})
162
+ existing_spans = [(item["start"], item["end"], item) for item in combined]
163
+ continue
164
+ combined.append({"text": token, "label": "PROPER", "start": span[0], "end": span[1]})
165
+ existing_spans.append((span[0], span[1], combined[-1]))
166
+ combined.sort(key=lambda item: (item["start"], item["end"]))
167
+ ANIMATE = re.compile(r"(?:说|问|答|看|听|想|做|装|退|关|备份|运行|她|他|你|我)")
168
+ PRODUCT = re.compile(r"(?:插件|版本|系统|模型|工具|平台|接口|文件|世界|规则|框架|协议|功能)")
169
+ for entity in combined:
170
+ if entity["label"] != "PROPER":
171
+ continue
172
+ context = text[max(0, entity["start"] - 10):min(len(text), entity["end"] + 10)]
173
+ if ANIMATE.search(context) and not PRODUCT.search(context):
174
+ entity["label"] = "PER"
175
+ slot_map = {}
176
+ slot_map.update(ZH_SLOT)
177
+ slot_map.update(EN_SLOT)
178
+ slots = {"characters": [], "location": None, "objects": []}
179
+ for entity in combined:
180
+ slot = slot_map.get(entity["label"])
181
+ if slot == "characters":
182
+ slots["characters"].append(entity["text"])
183
+ elif slot == "location" and slots["location"] is None:
184
+ slots["location"] = entity["text"]
185
+ elif slot == "objects":
186
+ slots["objects"].append(entity["text"])
187
+ slots["characters"] = list(dict.fromkeys(slots["characters"]))
188
+ slots["objects"] = list(dict.fromkeys(slots["objects"]))
189
+ slots["objects"] = [item for item in slots["objects"] if item not in slots["characters"]]
190
+ return {"lang": lang, "entities": combined, "slots": slots}
191
+
192
+
193
+ src, out, limit = sys.argv[1], sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 100
194
+ rows = []
195
+ for line in open(src, encoding="utf-8"):
196
+ if len(rows) >= limit:
197
+ break
198
+ item = json.loads(line)
199
+ text = item.get("text", "")
200
+ if not text.strip():
201
+ continue
202
+ rows.append({"turn": item.get("turn"), "text": text, **extract(text)})
203
+ if len(rows) % 10 == 0:
204
+ print("done", len(rows), flush=True)
205
+
206
+ with open(out, "w", encoding="utf-8") as f:
207
+ json.dump({"n": len(rows), "rows": rows}, f, ensure_ascii=False, indent=2)
208
+ print(json.dumps({"n": len(rows), "langs": {k: sum(1 for r in rows if r["lang"] == k) for k in ["zh", "en"]}, "with_entities": sum(1 for r in rows if r["entities"])}, ensure_ascii=False, indent=2), flush=True)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import { needsSkillInjection } from '../src/inject-policy.mjs';
5
+ import { selectTopic } from '../src/topic.mjs';
6
+
7
+ const rows = [
8
+ { id: 'a', alias: 'A', label: 'A', keywords: ['普通词'] },
9
+ { id: 'b', alias: 'B', label: 'B', keywords: ['专名甲', '专名乙'] },
10
+ ];
11
+ const generic = new Set(['普通词']);
12
+
13
+ // 泛词单独出现:静默。
14
+ assert.equal(selectTopic({ rows, text: '普通词', genericWords: generic, activeId: 'b' }).blocked, true);
15
+ // 两个非泛词:可提示。
16
+ assert.equal(selectTopic({ rows, text: '专名甲 专名乙', genericWords: generic, activeId: 'a' }).blocked, false);
17
+ // 一个专属词:可提示。
18
+ assert.equal(selectTopic({
19
+ rows,
20
+ text: '专名甲',
21
+ genericWords: generic,
22
+ wordStats: new Map([['专名甲', { precision: 0.6, hits: 6 }]]),
23
+ activeId: 'a',
24
+ }).blocked, false);
25
+
26
+ // 技能正文只跟集合变化走;载体被折叠不再重灌整份正文。
27
+ assert.equal(needsSkillInjection({ keyChanged: true }), true);
28
+ assert.equal(needsSkillInjection({ keyChanged: false }), false);
29
+
30
+ // 静态断言:新标签和存提醒的阈值仍在源码里。
31
+ const engine = readFileSync(new URL('../src/engine.mjs', import.meta.url), 'utf8');
32
+ assert.match(engine, /snippet\.length >= 40/);
33
+ assert.match(engine, /cands\.length === 0 && snippet\.length >= 40/);
34
+ assert.match(engine, /currentTurn - Number\(last\?\.turn.*>= 20/);
35
+ assert.match(engine, /contentAfter >= 8 && gap >= 20/);
36
+ assert.match(engine, /this\.saveAsked/);
37
+ assert.match(engine, /TAG: <T 别名/);
38
+ assert.match(engine, /可选短语/);
39
+ assert.match(engine, /tagLearnShown/);
40
+ assert.match(engine, /turn-gate\.jsonl/);
41
+ assert.match(engine, /auditGate-failed/);
42
+ assert.match(engine, /habits\.md/);
43
+ assert.match(engine, /splitNewToken/);
44
+ assert.match(engine, /tagIdOf\(made\)/);
45
+ assert.match(engine, /gate-steer\.jsonl/);
46
+ assert.match(engine, /snapshotEvents/);
47
+ assert.match(engine, /bumpTagCount/);
48
+ assert.match(engine, /闸·线/);
49
+
50
+ console.log('noise-selfcheck PASS');
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pack integrity check: compare selected files inside a .tgz with the dev tree.
4
+ *
5
+ * node bin/pack-integrity.mjs --tgz path/to/pkg.tgz --root . \
6
+ * --files src/memory/relabel-migrate.mjs,src/memory/bc-pre-step.mjs
7
+ *
8
+ * Exits 1 on any mismatch. This catches the "packaged an old public copy"
9
+ * failure mode without trusting the version number.
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { readFileSync } from 'node:fs';
13
+ import { spawnSync } from 'node:child_process';
14
+
15
+ function arg(name, fallback = null) {
16
+ const index = process.argv.indexOf(`--${name}`);
17
+ return index >= 0 ? process.argv[index + 1] ?? fallback : fallback;
18
+ }
19
+
20
+ const tgz = arg('tgz');
21
+ const root = arg('root', '.');
22
+ const files = String(arg('files', '')).split(',').map((file) => file.trim()).filter(Boolean);
23
+ if (!tgz || files.length === 0) {
24
+ console.error('usage: node bin/pack-integrity.mjs --tgz <file.tgz> --root <dir> --files a,b');
25
+ process.exit(2);
26
+ }
27
+
28
+ const sha = (buffer) => createHash('sha256').update(buffer).digest('hex');
29
+ let failed = 0;
30
+ for (const file of files) {
31
+ const local = `${root.replace(/[\\/]+$/, '')}/${file}`;
32
+ const expected = sha(readFileSync(local));
33
+ const result = spawnSync('tar', ['-xOf', tgz, `package/${file}`]);
34
+ if (result.status !== 0) {
35
+ console.error(`MISSING ${file}: ${result.stderr?.toString().trim() || 'tar failed'}`);
36
+ failed += 1;
37
+ continue;
38
+ }
39
+ const actual = sha(result.stdout);
40
+ if (actual !== expected) {
41
+ console.error(`MISMATCH ${file}: dev ${expected.slice(0, 12)} vs tgz ${actual.slice(0, 12)}`);
42
+ failed += 1;
43
+ } else {
44
+ console.log(`OK ${file} ${actual.slice(0, 12)}`);
45
+ }
46
+ }
47
+ process.exit(failed === 0 ? 0 : 1);
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Diagnose the A/B/C/K panel installation.
4
+ *
5
+ * Run from the installed package:
6
+ * node bin/panel-check.mjs --url http://127.0.0.1:3095
7
+ * node bin/panel-check.mjs --url http://127.0.0.1:3095 --session session-...
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ function arg(name, fallback = null) {
14
+ const index = process.argv.indexOf(`--${name}`);
15
+ return index >= 0 ? process.argv[index + 1] ?? fallback : fallback;
16
+ }
17
+
18
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
20
+ const client = path.join(root, 'client.js');
21
+ const url = arg('url', process.env.DSH_WEB_URL ?? null);
22
+ const session = arg('session', null);
23
+
24
+ console.log(`package: ${pkg.name}@${pkg.version}`);
25
+ console.log(`client.js: ${fs.existsSync(client) ? 'present' : 'MISSING'}`);
26
+ console.log(`dsh.client: ${pkg.dsh?.client ? JSON.stringify(pkg.dsh.client) : 'MISSING'}`);
27
+ if (pkg.dsh?.client?.platform !== 'web') {
28
+ console.log('warning: dsh.client.platform is not "web"; the browser UI will not load');
29
+ }
30
+ if (!url) {
31
+ console.log('\nNo --url given. Pass the current DSH Web URL, e.g. --url http://127.0.0.1:3095');
32
+ process.exit(0);
33
+ }
34
+ const base = url.replace(/\/+$/, '');
35
+ async function get(pathname) {
36
+ const response = await fetch(`${base}${pathname}`, { headers: { accept: 'application/json' } });
37
+ return { status: response.status, text: await response.text() };
38
+ }
39
+ try {
40
+ const state = await get('/dsh-continuity/state.json');
41
+ console.log(`state.json: HTTP ${state.status}`);
42
+ console.log(state.text.slice(0, 300));
43
+ } catch (error) {
44
+ console.log(`state.json: FAILED ${error.message} (is this the DSH Web URL and is the plugin loaded?)`);
45
+ }
46
+ if (session) {
47
+ try {
48
+ const bc = await get(`/dsh-continuity/bc.json?sessionId=${encodeURIComponent(session)}`);
49
+ console.log(`bc.json: HTTP ${bc.status}`);
50
+ console.log(bc.text.slice(0, 500));
51
+ } catch (error) {
52
+ console.log(`bc.json: FAILED ${error.message}`);
53
+ }
54
+ }
55
+ console.log('\nIf state.json works but the sidebar has no Continuity entry, the client bundle is not mounted.');
56
+ console.log('If the left panel works but the right tab is absent, this DSH lacks sidebarRight/sidebarRightTabs.');
57
+ console.log('If the tab opens with empty tables, send one message; A/C/K start empty by design.');
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Plugin integrity manifest.
3
+ *
4
+ * Detects modifications to the installed plugin. Detection is not prevention;
5
+ * the installed copy must also be read-only / ACL-protected.
6
+ */
7
+ import crypto from 'node:crypto';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
11
+
12
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.npm-cache', 'test', 'tests', 'backups']);
13
+
14
+ function walk(root, current = root, out = []) {
15
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
16
+ if (entry.isDirectory()) {
17
+ if (SKIP_DIRS.has(entry.name)) continue;
18
+ walk(root, path.join(current, entry.name), out);
19
+ } else if (entry.isFile()) {
20
+ out.push(path.relative(root, path.join(current, entry.name)).replace(/\\/g, '/'));
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+
26
+ export function snapshotPlugin(root) {
27
+ const files = {};
28
+ for (const relative of walk(root).sort()) {
29
+ const full = path.join(root, relative);
30
+ files[relative] = crypto.createHash('sha256').update(fs.readFileSync(full)).digest('hex');
31
+ }
32
+ return { version: 1, algorithm: 'sha256', root: path.basename(root), files };
33
+ }
34
+
35
+ export function verifyPlugin(root, manifest) {
36
+ const current = snapshotPlugin(root);
37
+ const changed = [];
38
+ const missing = [];
39
+ const extra = [];
40
+ for (const [file, hash] of Object.entries(manifest.files ?? {})) {
41
+ if (!(file in current.files)) missing.push(file);
42
+ else if (current.files[file] !== hash) changed.push(file);
43
+ }
44
+ for (const file of Object.keys(current.files)) {
45
+ if (!(file in (manifest.files ?? {}))) extra.push(file);
46
+ }
47
+ return { ok: changed.length === 0 && missing.length === 0, changed, missing, extra };
48
+ }
49
+
50
+ export function readManifest(file) {
51
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
52
+ }
53
+
54
+ export function writeManifest(file, manifest) {
55
+ fs.mkdirSync(path.dirname(file), { recursive: true });
56
+ fs.writeFileSync(file, JSON.stringify(manifest, null, 2), 'utf8');
57
+ return file;
58
+ }
59
+
60
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
61
+ const [command, root, manifestFile] = process.argv.slice(2);
62
+ if (command === 'snapshot' && root && manifestFile) {
63
+ const manifest = snapshotPlugin(path.resolve(root));
64
+ writeManifest(path.resolve(manifestFile), manifest);
65
+ console.log(JSON.stringify({ ok: true, files: Object.keys(manifest.files).length }, null, 2));
66
+ } else if (command === 'verify' && root && manifestFile) {
67
+ const result = verifyPlugin(path.resolve(root), readManifest(path.resolve(manifestFile)));
68
+ console.log(JSON.stringify(result, null, 2));
69
+ if (!result.ok) process.exitCode = 1;
70
+ } else {
71
+ console.log('usage: plugin-integrity snapshot <root> <manifest> | verify <root> <manifest>');
72
+ process.exitCode = 2;
73
+ }
74
+ }
@@ -0,0 +1,13 @@
1
+ import fs from 'node:fs';
2
+ import { createExtractionProviderFromEnv } from '../src/memory/extraction-provider.mjs';
3
+ import { createExtractionPreStep } from '../src/memory/extraction-pre-step.mjs';
4
+
5
+ const config = 'C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/extraction-live.json';
6
+ const provider = createExtractionProviderFromEnv({ DSH_EXTRACTION_MODE: 'model', DSH_EXTRACTION_CONFIG: config });
7
+ const handler = createExtractionPreStep({ provider, mode: 'model' });
8
+ const text = 'Coo在灯室拿起钥匙,准备去找宁。';
9
+ const decision = { kind: 'accept', messages: [{ role: 'user', content: [{ type: 'text', text }] }] };
10
+ const result = await handler({ step: 1 }, async () => decision);
11
+ fs.writeFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a/LIVE-PREVIEW.json', JSON.stringify({ text, messages: result.messages, extraction: result.extraction }, null, 2));
12
+ provider.close?.();
13
+ console.log(JSON.stringify({ messages: result.messages.length, ok: result.extraction?.ok, slots: result.extraction?.data?.slots }, null, 2));