dsh-biocore 0.2.0 → 0.3.0

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 (245) hide show
  1. package/README.md +11 -0
  2. package/lib/core-worker.js +268 -0
  3. package/lib/index.js +160 -70
  4. package/lib/modules/autonomy.js +125 -0
  5. package/lib/modules/cache-stats.js +48 -0
  6. package/lib/modules/core-turn.js +24 -6
  7. package/lib/modules/expression-inject.js +0 -10
  8. package/lib/modules/expression-refresh.js +0 -9
  9. package/lib/modules/memory-capture.js +2 -1
  10. package/lib/modules/memory-inject.js +50 -5
  11. package/lib/modules/memory-tools.js +7 -4
  12. package/lib/modules/python-env.js +46 -0
  13. package/lib/modules/safety-gate.js +0 -3
  14. package/lib/modules/series.js +54 -0
  15. package/lib/modules/switch.js +16 -0
  16. package/lib/py-bridge.js +81 -15
  17. package/package.json +1 -1
  18. package/python/biocore_memory/core/config.py +138 -127
  19. package/python/biocore_memory/engine.py +2653 -2476
  20. package/python/biocore_memory/recall/embedding.py +129 -0
  21. package/python/biocore_memory/recall/feedback.py +202 -0
  22. package/python/biocore_model/preference_drift.py +255 -0
  23. package/python/biocore_model/tests/__init__.py +1 -0
  24. package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
  25. package/python/biocore_model/tests/core_status_check.mjs +29 -0
  26. package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
  27. package/python/biocore_model/tests/leak_samples_run.py +18 -0
  28. package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
  29. package/python/biocore_model/tests/panel_html_check.mjs +63 -0
  30. package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
  31. package/python/biocore_model/tests/render_guard_check.mjs +19 -0
  32. package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
  33. package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
  34. package/python/biocore_model/tests/test_affective_labor.py +102 -0
  35. package/python/biocore_model/tests/test_appraisal.py +137 -0
  36. package/python/biocore_model/tests/test_appraisal_action.py +55 -0
  37. package/python/biocore_model/tests/test_arch_convergence.py +35 -0
  38. package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
  39. package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
  40. package/python/biocore_model/tests/test_attachment_state.py +29 -0
  41. package/python/biocore_model/tests/test_attribution.py +56 -0
  42. package/python/biocore_model/tests/test_attributor.py +108 -0
  43. package/python/biocore_model/tests/test_autonomic.py +69 -0
  44. package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
  45. package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
  46. package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
  47. package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
  48. package/python/biocore_model/tests/test_autonomy_check.py +40 -0
  49. package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
  50. package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
  51. package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
  52. package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
  53. package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
  54. package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
  55. package/python/biocore_model/tests/test_autonomy_model.py +60 -0
  56. package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
  57. package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
  58. package/python/biocore_model/tests/test_autonomy_params.py +163 -0
  59. package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
  60. package/python/biocore_model/tests/test_autonomy_self.py +153 -0
  61. package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
  62. package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
  63. package/python/biocore_model/tests/test_autonomy_status.py +35 -0
  64. package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
  65. package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
  66. package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
  67. package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
  68. package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
  69. package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
  70. package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
  71. package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
  72. package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
  73. package/python/biocore_model/tests/test_bio_tick.py +202 -0
  74. package/python/biocore_model/tests/test_boundary.py +183 -0
  75. package/python/biocore_model/tests/test_boundary_gate.py +295 -0
  76. package/python/biocore_model/tests/test_brain_split.py +99 -0
  77. package/python/biocore_model/tests/test_co_recall.py +350 -0
  78. package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
  79. package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
  80. package/python/biocore_model/tests/test_connection_bids.py +69 -0
  81. package/python/biocore_model/tests/test_core_bridge.py +88 -0
  82. package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
  83. package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
  84. package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
  85. package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
  86. package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
  87. package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
  88. package/python/biocore_model/tests/test_core_client.py +156 -0
  89. package/python/biocore_model/tests/test_core_commands.py +146 -0
  90. package/python/biocore_model/tests/test_core_mode.py +161 -0
  91. package/python/biocore_model/tests/test_core_state.py +240 -0
  92. package/python/biocore_model/tests/test_criticality.py +61 -0
  93. package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
  94. package/python/biocore_model/tests/test_debug_values.py +169 -0
  95. package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
  96. package/python/biocore_model/tests/test_delta_budget.py +157 -0
  97. package/python/biocore_model/tests/test_diag_trace.py +201 -0
  98. package/python/biocore_model/tests/test_directives_compat.py +698 -0
  99. package/python/biocore_model/tests/test_dissonance.py +97 -0
  100. package/python/biocore_model/tests/test_drift_sign.py +87 -0
  101. package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
  102. package/python/biocore_model/tests/test_embedding_local.py +279 -0
  103. package/python/biocore_model/tests/test_embedding_mode.py +175 -0
  104. package/python/biocore_model/tests/test_embedding_modes.py +342 -0
  105. package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
  106. package/python/biocore_model/tests/test_emotion_probe.py +214 -0
  107. package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
  108. package/python/biocore_model/tests/test_entity_graph.py +198 -0
  109. package/python/biocore_model/tests/test_entity_resolve.py +446 -0
  110. package/python/biocore_model/tests/test_envy.py +173 -0
  111. package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
  112. package/python/biocore_model/tests/test_expr_guard.py +1 -0
  113. package/python/biocore_model/tests/test_expr_intent.py +66 -0
  114. package/python/biocore_model/tests/test_expr_state.py +66 -0
  115. package/python/biocore_model/tests/test_expr_template.py +71 -0
  116. package/python/biocore_model/tests/test_expr_tone.py +41 -0
  117. package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
  118. package/python/biocore_model/tests/test_feedback_flow.py +185 -0
  119. package/python/biocore_model/tests/test_feedback_guard.py +65 -0
  120. package/python/biocore_model/tests/test_feedback_llm.py +59 -0
  121. package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
  122. package/python/biocore_model/tests/test_feedback_memory.py +63 -0
  123. package/python/biocore_model/tests/test_feedback_user.py +61 -0
  124. package/python/biocore_model/tests/test_frustration_curve.py +66 -0
  125. package/python/biocore_model/tests/test_global_bio_state.py +116 -0
  126. package/python/biocore_model/tests/test_governance.py +181 -0
  127. package/python/biocore_model/tests/test_granularity.py +133 -0
  128. package/python/biocore_model/tests/test_graph_extract.py +224 -0
  129. package/python/biocore_model/tests/test_graph_recall.py +226 -0
  130. package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
  131. package/python/biocore_model/tests/test_growth.py +301 -0
  132. package/python/biocore_model/tests/test_intent_render.py +60 -0
  133. package/python/biocore_model/tests/test_intero_regulation.py +224 -0
  134. package/python/biocore_model/tests/test_intero_style.py +146 -0
  135. package/python/biocore_model/tests/test_item3_numeric.py +105 -0
  136. package/python/biocore_model/tests/test_item6_numeric.py +116 -0
  137. package/python/biocore_model/tests/test_layered_memory.py +174 -0
  138. package/python/biocore_model/tests/test_leak_guard.py +75 -0
  139. package/python/biocore_model/tests/test_learning_probe.py +93 -0
  140. package/python/biocore_model/tests/test_life_loop.py +222 -0
  141. package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
  142. package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
  143. package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
  144. package/python/biocore_model/tests/test_memory_backend.py +332 -0
  145. package/python/biocore_model/tests/test_memory_dedup.py +316 -0
  146. package/python/biocore_model/tests/test_memory_dual.py +219 -0
  147. package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
  148. package/python/biocore_model/tests/test_memory_feedback.py +241 -0
  149. package/python/biocore_model/tests/test_memory_fixes.py +84 -0
  150. package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
  151. package/python/biocore_model/tests/test_memory_inject.py +211 -0
  152. package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
  153. package/python/biocore_model/tests/test_memory_store.py +372 -0
  154. package/python/biocore_model/tests/test_metacog.py +103 -0
  155. package/python/biocore_model/tests/test_migrate.py +245 -0
  156. package/python/biocore_model/tests/test_migrate_store.py +194 -0
  157. package/python/biocore_model/tests/test_mind_wander.py +65 -0
  158. package/python/biocore_model/tests/test_mood.py +35 -0
  159. package/python/biocore_model/tests/test_narrative.py +114 -0
  160. package/python/biocore_model/tests/test_needs_esteem.py +51 -0
  161. package/python/biocore_model/tests/test_needs_events.py +205 -0
  162. package/python/biocore_model/tests/test_needs_sleep.py +54 -0
  163. package/python/biocore_model/tests/test_noise_bank.py +54 -0
  164. package/python/biocore_model/tests/test_numeric_schema.py +98 -0
  165. package/python/biocore_model/tests/test_output_protocol.py +65 -0
  166. package/python/biocore_model/tests/test_page_debug.py +147 -0
  167. package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
  168. package/python/biocore_model/tests/test_pipeline.py +345 -0
  169. package/python/biocore_model/tests/test_pipeline_api.py +192 -0
  170. package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
  171. package/python/biocore_model/tests/test_plugin_config.py +85 -0
  172. package/python/biocore_model/tests/test_policy_learning.py +116 -0
  173. package/python/biocore_model/tests/test_possible_selves.py +88 -0
  174. package/python/biocore_model/tests/test_predictor.py +227 -0
  175. package/python/biocore_model/tests/test_preference_drift.py +142 -0
  176. package/python/biocore_model/tests/test_preset.py +142 -0
  177. package/python/biocore_model/tests/test_proactive_context.py +70 -0
  178. package/python/biocore_model/tests/test_proactive_history.py +90 -0
  179. package/python/biocore_model/tests/test_proactive_memory.py +141 -0
  180. package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
  181. package/python/biocore_model/tests/test_probe_contract.py +71 -0
  182. package/python/biocore_model/tests/test_prospective.py +154 -0
  183. package/python/biocore_model/tests/test_provider_fallback.py +89 -0
  184. package/python/biocore_model/tests/test_reconstruction.py +111 -0
  185. package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
  186. package/python/biocore_model/tests/test_relation_stage.py +114 -0
  187. package/python/biocore_model/tests/test_relationship_stage.py +59 -0
  188. package/python/biocore_model/tests/test_release_140.py +55 -0
  189. package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
  190. package/python/biocore_model/tests/test_reply_limit.py +86 -0
  191. package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
  192. package/python/biocore_model/tests/test_reply_split.py +52 -0
  193. package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
  194. package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
  195. package/python/biocore_model/tests/test_rerank.py +229 -0
  196. package/python/biocore_model/tests/test_rollback.py +175 -0
  197. package/python/biocore_model/tests/test_runtime.py +175 -0
  198. package/python/biocore_model/tests/test_runtime_active.py +205 -0
  199. package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
  200. package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
  201. package/python/biocore_model/tests/test_scheduler.py +79 -0
  202. package/python/biocore_model/tests/test_secrecy.py +105 -0
  203. package/python/biocore_model/tests/test_self_esteem.py +45 -0
  204. package/python/biocore_model/tests/test_self_intent.py +347 -0
  205. package/python/biocore_model/tests/test_self_interest.py +183 -0
  206. package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
  207. package/python/biocore_model/tests/test_self_verification.py +113 -0
  208. package/python/biocore_model/tests/test_situation.py +142 -0
  209. package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
  210. package/python/biocore_model/tests/test_social_pain.py +49 -0
  211. package/python/biocore_model/tests/test_social_repair.py +67 -0
  212. package/python/biocore_model/tests/test_social_theory.py +78 -0
  213. package/python/biocore_model/tests/test_source_monitoring.py +204 -0
  214. package/python/biocore_model/tests/test_speak_like_group.py +36 -0
  215. package/python/biocore_model/tests/test_stages.py +226 -0
  216. package/python/biocore_model/tests/test_state_tag.py +86 -0
  217. package/python/biocore_model/tests/test_template_expr.py +65 -0
  218. package/python/biocore_model/tests/test_text_inference.py +70 -0
  219. package/python/biocore_model/tests/test_theta_params.py +115 -0
  220. package/python/biocore_model/tests/test_topic_control.py +200 -0
  221. package/python/biocore_model/tests/test_turn_memory.py +105 -0
  222. package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
  223. package/python/biocore_model/tests/test_ui_settings.py +84 -0
  224. package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
  225. package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
  226. package/python/biocore_model/tests/test_user_classify.py +122 -0
  227. package/python/biocore_model/tests/test_value_order.py +117 -0
  228. package/python/biocore_model/tests/test_voice_bank.py +105 -0
  229. package/python/biocore_model/tests/test_waiting_sense.py +216 -0
  230. package/python/biocore_model/tests/test_x_directives.py +101 -0
  231. package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
  232. package/python/biocore_model/tests/view_mode_check.mjs +17 -0
  233. package/python/biocore_runtime/autonomy.py +208 -0
  234. package/python/biocore_runtime/core_state.py +302 -0
  235. package/python/biocore_runtime/governance.py +495 -0
  236. package/python/biocore_runtime/growth.py +528 -0
  237. package/python/biocore_runtime/pipeline.py +850 -23
  238. package/python/biocore_runtime/relationship_state.py +214 -0
  239. package/python/biocore_runtime/self_layer.py +103 -0
  240. package/python/biocore_runtime/service.py +162 -0
  241. package/python/biocore_runtime/standby.py +472 -0
  242. package/ui/panel-layout.js +83 -39
  243. package/ui/panel.css +885 -743
  244. package/ui/panel.html +356 -296
  245. package/ui/panel.js +739 -865
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Safe template engine: substitution, conditionals, loops, no code execution."""
3
+ import unittest
4
+
5
+ from biocore_model import template_expr as t
6
+ from biocore_model import expr_template as et
7
+
8
+
9
+ class RenderTest(unittest.TestCase):
10
+ def test_plain_text_untouched(self):
11
+ self.assertEqual(t.render("你好", {}), "你好")
12
+
13
+ def test_substitution(self):
14
+ self.assertEqual(t.render("你好{{name}}", {"name": "老板"}), "你好老板")
15
+
16
+ def test_missing_variable_is_empty(self):
17
+ self.assertEqual(t.render("[{{missing}}]", {}), "[]")
18
+
19
+ def test_if_else(self):
20
+ self.assertEqual(t.render("{% if trust > 0.5 %}亲近{% else %}疏远{% endif %}",
21
+ {"trust": 0.7}), "亲近")
22
+ self.assertEqual(t.render("{% if trust > 0.5 %}亲近{% else %}疏远{% endif %}",
23
+ {"trust": 0.1}), "疏远")
24
+
25
+ def test_elif_chain(self):
26
+ v = {"trust": 0.3}
27
+ self.assertEqual(t.render("{% if trust > 0.5 %}a{% elif trust > 0.2 %}b{% else %}c{% endif %}", v), "b")
28
+
29
+ def test_for_loop(self):
30
+ self.assertEqual(t.render("{% for x in tags %}[{{x}}]{% endfor %}", {"tags": ["a", "b"]}), "[a][b]")
31
+
32
+ def test_nested_if(self):
33
+ tpl = "{% if a %}A{% if b %}B{% endif %}{% endif %}"
34
+ self.assertEqual(t.render(tpl, {"a": 1, "b": 1}), "AB")
35
+ self.assertEqual(t.render(tpl, {"a": 1, "b": 0}), "A")
36
+
37
+ def test_arithmetic_and_helpers(self):
38
+ self.assertEqual(t.render("{{ len(tags) }}", {"tags": [1, 2, 3]}), "3")
39
+ self.assertEqual(t.render("{{ min(a, b) }}", {"a": 2, "b": 5}), "2")
40
+
41
+ def test_no_code_execution(self):
42
+ out = t.render('{{ __import__("os").system("echo pwn") }}', {})
43
+ self.assertEqual(out, "")
44
+ self.assertEqual(t.render("{{ ().__class__ }}", {}), "")
45
+
46
+ def test_bad_syntax_is_safe(self):
47
+ self.assertEqual(t.render("{% if %}x{% endif %}", {}), "")
48
+
49
+
50
+ class WiringTest(unittest.TestCase):
51
+ def test_build_reply_renders_template_vars(self):
52
+ built = et.build_reply({}, relationship={},
53
+ state_ctx={"state_block": "{% if trust > 0.5 %}亲近{% else %}疏远{% endif %}"},
54
+ tone="", guard="", opts={"template_vars": {"trust": 0.8}})
55
+ self.assertIn("亲近", built["user"])
56
+ self.assertNotIn("{%", built["user"])
57
+
58
+ def test_bulit_reply_without_vars_keeps_text(self):
59
+ built = et.build_reply({}, relationship={}, state_ctx={"state_block": "情绪:平静"},
60
+ tone="", guard="", opts={})
61
+ self.assertIn("情绪:平静", built["user"])
62
+
63
+
64
+ if __name__ == "__main__":
65
+ unittest.main()
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ """item-5: lexical inference is disabled; the probe owns semantics."""
3
+ import unittest
4
+
5
+ import interpretation as it
6
+
7
+
8
+ class DisabledInferenceTest(unittest.TestCase):
9
+ def test_flag_is_off_by_default(self):
10
+ self.assertIs(it.TEXT_INFERENCE_ENABLED, False)
11
+ self.assertIs(it.USER_CLASSIFY_ENABLED, False)
12
+
13
+ def test_negative_text_is_neutral(self):
14
+ out = it.infer_from_text("我今天特别难受,别烦我")
15
+ self.assertEqual(out["emotion"], "neutral")
16
+ self.assertEqual(out["confidence"], 0.0)
17
+
18
+ def test_positive_text_is_neutral(self):
19
+ self.assertEqual(it.infer_from_text("今天特别开心!")["emotion"],
20
+ "neutral")
21
+
22
+ def test_no_lexical_tables_remain(self):
23
+ for name in ("_INFER_WORDS", "_INFER_EMOJI", "_SARCASM_MARKERS",
24
+ "_USER_WORDS", "_NEGATION_PREFIX_WORDS", "_negated"):
25
+ self.assertFalse(hasattr(it, name), name)
26
+
27
+ def test_parse_response_does_not_infer(self):
28
+ for text in ("我今天特别难受,别烦我", "今天特别开心!", "你可真行啊",
29
+ "最近压力好大,晚上总睡不着。"):
30
+ visible, update = it.parse_response(text)
31
+ self.assertIsNone(update, text)
32
+ self.assertEqual(visible, text)
33
+
34
+ def test_empty_input_is_safe(self):
35
+ self.assertEqual(it.infer_from_text("")["emotion"], "neutral")
36
+ self.assertIsNone(it.parse_response("")[1])
37
+
38
+ def test_ranges_are_bounded(self):
39
+ for text in ("开心", "难受", "压力大", "在吗", ""):
40
+ out = it.infer_from_text(text)
41
+ self.assertGreaterEqual(out["intensity"], 0.0)
42
+ self.assertLessEqual(out["intensity"], 1.0)
43
+ self.assertGreaterEqual(out["confidence"], 0.0)
44
+ self.assertLessEqual(out["confidence"], 1.0)
45
+
46
+ def test_classify_is_neutral_noop(self):
47
+ out = it.classify_user_msg("你真废物,闭嘴")
48
+ self.assertEqual(out["kind"], "neutral")
49
+ self.assertLess(out["confidence"], 0.4)
50
+
51
+
52
+ class ParseContractTest(unittest.TestCase):
53
+ def test_tag_path_still_works(self):
54
+ visible, update = it.parse_response(
55
+ '<bc>{"e":"joy","i":0.4}</bc> 我今天特别难受')
56
+ self.assertNotIn("<bc>", visible)
57
+ self.assertEqual(update["emotion"], "joy")
58
+ self.assertNotEqual(update.get("source"), "inferred")
59
+
60
+ def test_invalid_tag_does_not_fall_back(self):
61
+ _visible, update = it.parse_response("嗯。<bc>not json</bc>")
62
+ self.assertIsNone(update)
63
+
64
+ def test_update_shape_unchanged_for_tag_path(self):
65
+ _visible, update = it.parse_response('<bc>{"e":"anger","i":0.6}</bc>')
66
+ self.assertIn("valence_delta", update)
67
+
68
+
69
+ if __name__ == "__main__":
70
+ unittest.main()
@@ -0,0 +1,115 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Persona theta overwrite tests: save path and startup write."""
3
+ import ast
4
+ import tempfile
5
+ import types
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+ from biocore_model import ui_settings
10
+
11
+ MAIN = Path(__file__).resolve().parents[2] / "main.py"
12
+ SOURCE = MAIN.read_text(encoding="utf-8")
13
+
14
+
15
+ def _load_methods():
16
+ tree = ast.parse(SOURCE)
17
+ cls = next(n for n in tree.body
18
+ if isinstance(n, ast.ClassDef) and n.name == "BioCorePlugin")
19
+ wanted = ("_apply_autonomy_params", "_write_autonomy_theta_once")
20
+ ns = {}
21
+ fns = {}
22
+ for node in cls.body:
23
+ if isinstance(node, ast.FunctionDef) and node.name in wanted:
24
+ clone = ast.FunctionDef(name=node.name, args=node.args, body=node.body,
25
+ decorator_list=[], returns=node.returns,
26
+ type_comment=None, lineno=node.lineno,
27
+ col_offset=node.col_offset,
28
+ end_lineno=node.end_lineno,
29
+ end_col_offset=node.end_col_offset)
30
+ exec(compile(ast.Module(body=[clone], type_ignores=[]), "<theta>", "exec"),
31
+ ns)
32
+ fns[node.name] = ns[node.name]
33
+ return fns
34
+
35
+
36
+ FNS = _load_methods()
37
+
38
+
39
+ class FakePlugin:
40
+ def __init__(self, data_dir):
41
+ self.data_dir = str(data_dir)
42
+ self.autonomy_params = {"theta": 0.5239, "cooldown_s": 1625,
43
+ "daily_cap": 3}
44
+ self._autonomy_theta_written = False
45
+ self._apply_autonomy_params = types.MethodType(
46
+ FNS["_apply_autonomy_params"], self)
47
+ self._write_autonomy_theta_once = types.MethodType(
48
+ FNS["_write_autonomy_theta_once"], self)
49
+
50
+ def _autonomy_params(self):
51
+ return dict(self.autonomy_params)
52
+
53
+
54
+ class ParamsMergeTest(unittest.TestCase):
55
+ def test_theta_overwrites_stale_value(self):
56
+ plugin = FakePlugin(tempfile.gettempdir())
57
+ out = plugin._apply_autonomy_params({"theta": 0.55}, {"theta": 0.5239})
58
+ self.assertAlmostEqual(out["theta"], 0.5239, places=4)
59
+
60
+ def test_cooldown_and_cap_always_forced(self):
61
+ plugin = FakePlugin(tempfile.gettempdir())
62
+ out = plugin._apply_autonomy_params(
63
+ {"theta": 0.55, "cooldown_seconds": 1800, "daily_cap": 5, "V": 0.1},
64
+ {"theta": 0.52, "cooldown_s": 1625, "daily_cap": 3})
65
+ self.assertEqual(out["cooldown_seconds"], 1625)
66
+ self.assertEqual(out["daily_cap"], 3)
67
+ self.assertEqual(out["V"], 0.1)
68
+ lean = plugin._apply_autonomy_params({"theta": 0.55}, {"theta": 0.52})
69
+ self.assertEqual(lean["cooldown_seconds"], 1800)
70
+ self.assertEqual(lean["daily_cap"], 5)
71
+
72
+ def test_params_unavailable_keeps_old_theta(self):
73
+ plugin = FakePlugin(tempfile.gettempdir())
74
+ out = plugin._apply_autonomy_params({"theta": 0.55}, {})
75
+ self.assertAlmostEqual(out["theta"], 0.55, places=4)
76
+
77
+
78
+ class StartupWriteTest(unittest.TestCase):
79
+ def test_startup_write_once(self):
80
+ with tempfile.TemporaryDirectory() as tmp:
81
+ plugin = FakePlugin(tmp)
82
+ ui_settings.save(plugin, {"autonomy": {"accumulator": {
83
+ "theta": 0.55, "V": 0.15, "cooldown_seconds": 1800,
84
+ "daily_cap": 5}}})
85
+ plugin._write_autonomy_theta_once()
86
+ saved = ((ui_settings.load(plugin).get("autonomy") or {})
87
+ .get("accumulator") or {})
88
+ self.assertAlmostEqual(saved["theta"], 0.5239, places=4)
89
+ self.assertEqual(saved["cooldown_seconds"], 1625)
90
+ self.assertEqual(saved["daily_cap"], 3)
91
+ self.assertEqual(saved["V"], 0.15)
92
+ # Second call is a no-op (startup write once).
93
+ plugin.autonomy_params = {"theta": 0.9, "cooldown_s": 9999,
94
+ "daily_cap": 8}
95
+ plugin._write_autonomy_theta_once()
96
+ again = ((ui_settings.load(plugin).get("autonomy") or {})
97
+ .get("accumulator") or {})
98
+ self.assertAlmostEqual(again["theta"], 0.5239, places=4)
99
+
100
+ def test_save_path_overwrites_after_one_tick(self):
101
+ plugin = FakePlugin(tempfile.gettempdir())
102
+ payload = {"theta": 0.55, "V": 0.1}
103
+ out = plugin._apply_autonomy_params(payload, plugin._autonomy_params())
104
+ self.assertAlmostEqual(out["theta"], 0.5239, places=4)
105
+
106
+
107
+ class WiringStaticTest(unittest.TestCase):
108
+ def test_log_has_theta_and_save_uses_params(self):
109
+ self.assertIn("autonomy tick engine=module V=%.3f theta=%.3f", SOURCE)
110
+ self.assertIn("self._apply_autonomy_params(payload, params)", SOURCE)
111
+ self.assertIn("_write_autonomy_theta_once()", SOURCE)
112
+
113
+
114
+ if __name__ == "__main__":
115
+ unittest.main()
@@ -0,0 +1,200 @@
1
+ # -*- coding: utf-8 -*-
2
+ """task-181 话题主导权单测:让出/接住/另起/收尾、主导度、关闭、畸形输入。
3
+
4
+ 只依赖 biocore_model.topic_control 的纯函数;不导入 main.py。
5
+ """
6
+ import unittest
7
+
8
+ from biocore_model import topic_control as tc
9
+
10
+ CATCH_TURNS = [{"by": "you", "kind": "initiate"},
11
+ {"by": "me", "kind": "keep"}]
12
+ YIELD_TURNS = [{"by": "me", "kind": "initiate"},
13
+ {"by": "me", "kind": "switch"},
14
+ {"by": "me", "kind": "keep"}]
15
+ STALE_TURNS = [{"by": "you", "kind": "keep"},
16
+ {"by": "me", "kind": "keep"},
17
+ {"by": "you", "kind": "keep"},
18
+ {"by": "me", "kind": "keep"}]
19
+ CLOSE_TURNS = [{"by": "you", "kind": "initiate"},
20
+ {"by": "you", "kind": "close"}]
21
+
22
+
23
+ class ContractTest(unittest.TestCase):
24
+ def test_result_keys(self):
25
+ out = tc.topic_control(CATCH_TURNS)
26
+ for key in ("enabled", "holder", "dominance", "tendency", "reason",
27
+ "counts", "window", "last_by", "scores"):
28
+ self.assertIn(key, out)
29
+
30
+ def test_constants(self):
31
+ self.assertEqual(tc.HOLDERS, ("me", "you", "shared"))
32
+ self.assertEqual(tc.TENDENCIES, ("让出", "接住", "另起", "收尾"))
33
+ self.assertEqual(tc.KINDS, ("initiate", "switch", "close", "keep"))
34
+ self.assertEqual(set(tc.REASONS), set(tc.TENDENCIES))
35
+
36
+ def test_values_bounded(self):
37
+ for turns in (CATCH_TURNS, YIELD_TURNS, STALE_TURNS, CLOSE_TURNS, []):
38
+ out = tc.topic_control(turns)
39
+ self.assertGreaterEqual(out["dominance"], 0.0)
40
+ self.assertLessEqual(out["dominance"], 1.0)
41
+ self.assertIn(out["holder"], tc.HOLDERS)
42
+ self.assertIn(out["tendency"], tc.TENDENCIES)
43
+ for value in out["scores"].values():
44
+ self.assertGreaterEqual(value, 0.0)
45
+ self.assertLessEqual(value, 1.0)
46
+
47
+
48
+ class TendencyTest(unittest.TestCase):
49
+ def test_catch_your_topic(self):
50
+ out = tc.topic_control(CATCH_TURNS, state={"energy": 0.6},
51
+ relationship={"closeness": 0.6})
52
+ self.assertEqual(out["tendency"], "接住")
53
+ self.assertEqual(out["holder"], "you")
54
+
55
+ def test_yield_when_she_has_been_steering(self):
56
+ out = tc.topic_control(YIELD_TURNS, state={"energy": 0.3},
57
+ relationship={"closeness": 0.7})
58
+ self.assertEqual(out["tendency"], "让出")
59
+ self.assertEqual(out["holder"], "me")
60
+
61
+ def test_start_when_topic_is_stale(self):
62
+ out = tc.topic_control(STALE_TURNS, state={"energy": 0.8},
63
+ relationship={"closeness": 0.3})
64
+ self.assertEqual(out["tendency"], "另起")
65
+ self.assertTrue(tc.metrics(STALE_TURNS)["stale"])
66
+
67
+ def test_close_when_topic_is_wrapping_up(self):
68
+ out = tc.topic_control(CLOSE_TURNS, state={"energy": 0.3},
69
+ relationship={"conflict": 0.2}, hour=23)
70
+ self.assertEqual(out["tendency"], "收尾")
71
+ self.assertTrue(tc.metrics(CLOSE_TURNS)["closing"])
72
+
73
+ def test_closing_flag_forces_close(self):
74
+ out = tc.topic_control([{"by": "you", "kind": "keep"}],
75
+ state={"energy": 0.2},
76
+ flags={"closing": True})
77
+ self.assertEqual(out["tendency"], "收尾")
78
+
79
+ def test_empty_history_defaults_to_catch(self):
80
+ out = tc.topic_control([])
81
+ self.assertEqual(out["tendency"], "接住")
82
+ self.assertEqual(out["holder"], "shared")
83
+ self.assertEqual(out["dominance"], 0.5)
84
+
85
+
86
+ class HolderTest(unittest.TestCase):
87
+ def test_holder_me(self):
88
+ out = tc.topic_control([{"by": "me", "kind": "initiate"}])
89
+ self.assertEqual(out["holder"], "me")
90
+ self.assertEqual(out["dominance"], 1.0)
91
+
92
+ def test_holder_you(self):
93
+ out = tc.topic_control([{"by": "you", "kind": "switch"}])
94
+ self.assertEqual(out["holder"], "you")
95
+ self.assertEqual(out["dominance"], 0.0)
96
+
97
+ def test_holder_shared(self):
98
+ out = tc.topic_control([{"by": "me", "kind": "initiate"},
99
+ {"by": "you", "kind": "initiate"}])
100
+ self.assertEqual(out["holder"], "shared")
101
+
102
+ def test_recency_weights(self):
103
+ turns = [{"by": "you", "kind": "initiate"},
104
+ {"by": "me", "kind": "switch"}]
105
+ row = tc.metrics(turns)
106
+ self.assertGreater(row["dominance"], 0.5)
107
+ self.assertLess(row["dominance"], 1.0)
108
+ self.assertGreater(row["my_control"], 0.0)
109
+ self.assertGreater(row["your_control"], 0.0)
110
+
111
+ def test_window_limits_history(self):
112
+ turns = [{"by": "me", "kind": "initiate"},
113
+ {"by": "me", "kind": "initiate"},
114
+ {"by": "me", "kind": "initiate"},
115
+ {"by": "you", "kind": "initiate"}]
116
+ wide = tc.metrics(turns, window=4)
117
+ narrow = tc.metrics(turns, window=2)
118
+ self.assertEqual(narrow["count"], 2)
119
+ self.assertEqual(narrow["holder"], "shared")
120
+ self.assertEqual(wide["holder"], "me")
121
+
122
+ def test_counts_consistent(self):
123
+ out = tc.topic_control(YIELD_TURNS)
124
+ counts = out["counts"]
125
+ self.assertGreaterEqual(counts["my_control"], 0.0)
126
+ self.assertGreaterEqual(counts["your_control"], 0.0)
127
+ self.assertGreaterEqual(counts["engaged"], 0.0)
128
+ self.assertLessEqual(counts["engaged"], 1.0)
129
+
130
+
131
+ class EnergyTest(unittest.TestCase):
132
+ def test_energy_raises_start_score(self):
133
+ low = tc.topic_control(STALE_TURNS, state={"energy": 0.0})
134
+ high = tc.topic_control(STALE_TURNS, state={"energy": 1.0})
135
+ self.assertGreater(high["scores"]["另起"], low["scores"]["另起"])
136
+
137
+ def test_low_energy_raises_close_score(self):
138
+ low = tc.topic_control(CLOSE_TURNS, state={"energy": 0.1})
139
+ high = tc.topic_control(CLOSE_TURNS, state={"energy": 0.9})
140
+ self.assertGreater(low["scores"]["收尾"], high["scores"]["收尾"])
141
+
142
+ def test_low_energy_with_closing_stops_keeping_topic(self):
143
+ out = tc.topic_control(YIELD_TURNS, state={"energy": 0.1},
144
+ flags={"closing": True})
145
+ self.assertIn(out["tendency"], ("让出", "收尾"))
146
+
147
+
148
+ class SwitchTest(unittest.TestCase):
149
+ def test_disabled_is_neutral(self):
150
+ out = tc.topic_control(CATCH_TURNS, flags={"enabled": False})
151
+ self.assertFalse(out["enabled"])
152
+ self.assertEqual(out["holder"], "shared")
153
+ self.assertEqual(out["dominance"], 0.5)
154
+ self.assertEqual(out["tendency"], "接住")
155
+ self.assertIn("关闭", out["reason"])
156
+
157
+ def test_directives_mapping(self):
158
+ out = tc.directives(tc.topic_control(YIELD_TURNS))
159
+ self.assertEqual(out["tendency"], "让出")
160
+ self.assertEqual(out["holder"], "me")
161
+ self.assertLessEqual(out["initiative"], 0.2)
162
+ self.assertTrue(out["tone"])
163
+ neutral = tc.directives(tc.topic_control(flags={"enabled": False}))
164
+ self.assertEqual(neutral["tendency"], "接住")
165
+ self.assertEqual(neutral["tone"], "")
166
+
167
+
168
+ class RobustnessTest(unittest.TestCase):
169
+ def test_odd_inputs(self):
170
+ for turns in (None, "x", 3, [], [{"by": "what", "kind": "??"}],
171
+ ["bad"], "me:initiate", {"turns": CATCH_TURNS}):
172
+ out = tc.topic_control(turns)
173
+ self.assertIn(out["tendency"], tc.TENDENCIES)
174
+ self.assertIn(out["holder"], tc.HOLDERS)
175
+ self.assertTrue(out["reason"])
176
+
177
+ def test_string_turns_are_parsed(self):
178
+ row = tc.metrics(["me:initiate", "me:switch"])
179
+ self.assertEqual(row["holder"], "me")
180
+ self.assertEqual(row["dominance"], 1.0)
181
+
182
+ def test_aliases(self):
183
+ row = tc.metrics([{"by": "her", "kind": "new"},
184
+ {"by": "user", "kind": "change"}])
185
+ self.assertGreater(row["my_control"], 0.0)
186
+ self.assertGreater(row["your_control"], 0.0)
187
+
188
+ def test_deterministic(self):
189
+ first = tc.topic_control(YIELD_TURNS, state={"energy": 0.3})
190
+ second = tc.topic_control(YIELD_TURNS, state={"energy": 0.3})
191
+ self.assertEqual(first, second)
192
+
193
+ def test_bad_hour_and_state(self):
194
+ out = tc.topic_control(CATCH_TURNS, state="x", relationship="y",
195
+ hour="zzz")
196
+ self.assertIn(out["tendency"], tc.TENDENCIES)
197
+
198
+
199
+ if __name__ == "__main__":
200
+ unittest.main()
@@ -0,0 +1,105 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+
4
+ from biocore_model import turn_memory as tm
5
+
6
+
7
+ def _trace_row(rank, eid, text, score=0.6, source="direct", **extra):
8
+ row = {"entry_id": eid, "rank": rank, "score": score, "source": source,
9
+ "text": text, "cosine": 0.55, "importance": 0.5,
10
+ "components": {"lexical": 0.5, "recency": 0.3}}
11
+ row.update(extra)
12
+ return row
13
+
14
+
15
+ class TurnMemoryTest(unittest.TestCase):
16
+ def test_build_gives_four_rows_with_source_and_score(self):
17
+ trace = [_trace_row(i, "id%d" % i, "记忆 %d" % i, score=0.5 + i * 0.05)
18
+ for i in range(1, 6)]
19
+ rows = tm.build(trace)
20
+ self.assertEqual(len(rows), 4)
21
+ for row in rows:
22
+ self.assertEqual(row["source"], "direct")
23
+ self.assertGreater(row["score"], 0)
24
+ self.assertTrue(row["reason"])
25
+ self.assertEqual([row["rank"] for row in rows], [1, 2, 3, 4])
26
+
27
+ def test_build_limit_is_clamped(self):
28
+ trace = [_trace_row(i, "id%d" % i, "记忆 %d" % i) for i in range(1, 9)]
29
+ self.assertEqual(len(tm.build(trace, limit=2)), 2)
30
+ self.assertEqual(tm.build(trace, limit=0), [])
31
+ self.assertEqual(len(tm.build(trace, limit=99)), tm.HARD_ROWS)
32
+ self.assertEqual(len(tm.build(trace)), tm.MAX_ROWS)
33
+
34
+ def test_reason_uses_top_two_components(self):
35
+ reason = tm.reason_text({"lexical": 0.58, "recency": 0.22,
36
+ "retention": 0.10})
37
+ self.assertIn("关键词 0.58", reason)
38
+ self.assertIn("近期 0.22", reason)
39
+ self.assertNotIn("留存", reason)
40
+
41
+ def test_reason_falls_back_to_similarity_and_importance(self):
42
+ reason = tm.reason_text(None, cosine=0.61, importance=0.5)
43
+ self.assertIn("相似度 0.61", reason)
44
+ self.assertIn("重要度 0.50", reason)
45
+
46
+ def test_reason_defaults_to_rule_score(self):
47
+ self.assertEqual(tm.reason_text(), "规则打分")
48
+
49
+ def test_build_skips_unusable_rows(self):
50
+ rows = tm.build([None, {}, {"entry_id": ""}, "x",
51
+ {"entry_id": "e1", "text": "保留", "score": 0.7}])
52
+ self.assertEqual(len(rows), 1)
53
+ self.assertEqual(rows[0]["text"], "保留")
54
+ self.assertEqual(rows[0]["source"], "unknown")
55
+ self.assertEqual(rows[0]["rank"], 1)
56
+
57
+ def test_build_record_fields(self):
58
+ rows = tm.build([_trace_row(1, "e1", "记忆")])
59
+ record = tm.build_record(rows, reply="回" * 300, ts=1700000000)
60
+ self.assertEqual(record["count"], 1)
61
+ self.assertEqual(record["ts"], 1700000000.0)
62
+ self.assertLessEqual(len(record["reply"]), 121)
63
+ self.assertIn("…", record["reply"])
64
+
65
+ def test_render_receipt_lists_rows(self):
66
+ trace = [_trace_row(i, "e%d" % i, "记忆内容 %d" % i)
67
+ for i in range(1, 5)]
68
+ record = tm.build_record(tm.build(trace), ts=1700000000)
69
+ text = tm.render_receipt(record)
70
+ lines = text.splitlines()
71
+ self.assertEqual(lines[0], "本轮记忆:用了 4 条")
72
+ self.assertEqual(len(lines), 5)
73
+ for line in lines[1:]:
74
+ self.assertIn("[direct]", line)
75
+ self.assertIn("score", line)
76
+ self.assertIn("原因:关键词", line)
77
+
78
+ def test_render_receipt_empty(self):
79
+ self.assertIn("暂无", tm.render_receipt(None))
80
+ self.assertIn("暂无", tm.render_receipt({"rows": []}))
81
+
82
+ def test_merge_history_is_bounded_newest_first(self):
83
+ old = [{"ts": 1, "rows": []}, {"ts": 2, "rows": []}]
84
+ new = {"ts": 3, "rows": []}
85
+ merged = tm.merge_history(old, new)
86
+ self.assertEqual([item["ts"] for item in merged], [3, 1, 2])
87
+ long = [{"ts": i} for i in range(6)]
88
+ self.assertEqual(len(tm.merge_history(long, {"ts": 9})),
89
+ tm.HISTORY_LIMIT)
90
+ self.assertIsInstance(tm.merge_history(None, {"ts": 9}), list)
91
+ self.assertEqual(len(tm.merge_history(None, {"ts": 9})), 1)
92
+ self.assertEqual(tm.merge_history(old, None), old)
93
+
94
+ def test_text_trim_and_receipt_budget(self):
95
+ rows = tm.build([{"entry_id": "e1", "text": "长" * 300,
96
+ "score": 0.5, "components": {}}])
97
+ self.assertLessEqual(len(rows[0]["text"]), tm.MAX_TEXT + 1)
98
+ self.assertTrue(rows[0]["text"].endswith("…"))
99
+ record = tm.build_record(rows)
100
+ self.assertLessEqual(len(tm.render_receipt(record)),
101
+ tm.RECEIPT_MAX_CHARS)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ unittest.main()