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.
- package/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-222: relationship governance for DSH (accumulation / memory / attachment).
|
|
3
|
+
|
|
4
|
+
The shared relationship file already stores trust/closeness/conflict. This
|
|
5
|
+
module adds the missing P0 pieces:
|
|
6
|
+
|
|
7
|
+
accumulation per-message signals -> bounded deltas (warmth, disclosure,
|
|
8
|
+
conflict, repair), applied by pipeline.relationship_update
|
|
9
|
+
memory <store>/relationship_memory.json keeps the last N notable
|
|
10
|
+
events (kind/ts/text preview/delta), append-only
|
|
11
|
+
attachment style + activation derived with biocore_model
|
|
12
|
+
attachment_state / attachment_regulation (pure, optional)
|
|
13
|
+
repair apology-style turns lower conflict and count as repairs
|
|
14
|
+
|
|
15
|
+
Never deletes relationship history and never writes memory entries.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from biocore_model import attachment_regulation as _attach_reg
|
|
25
|
+
except Exception: # pragma: no cover - stripped checkout
|
|
26
|
+
_attach_reg = None
|
|
27
|
+
try:
|
|
28
|
+
from biocore_model import attachment_state as _attach_state
|
|
29
|
+
except Exception: # pragma: no cover
|
|
30
|
+
_attach_state = None
|
|
31
|
+
|
|
32
|
+
MEMORY_FILE = "relationship_memory.json"
|
|
33
|
+
EVENT_LIMIT = 50
|
|
34
|
+
SCHEMA = 1
|
|
35
|
+
|
|
36
|
+
_CONFLICT = ("生气", "讨厌", "闭嘴", "滚", "烦", "失望", "吵架", "别说了",
|
|
37
|
+
"你错了", "没用", "不想理")
|
|
38
|
+
_REPAIR = ("对不起", "抱歉", "是我不好", "别生气", "原谅", "我错了",
|
|
39
|
+
"不好意思", "不是那个意思")
|
|
40
|
+
_WARMTH = ("谢谢", "喜欢", "想你", "抱抱", "辛苦", "开心", "爱你", "陪我",
|
|
41
|
+
"在吗", "晚安")
|
|
42
|
+
_TRUST = ("相信", "告诉你", "秘密", "说实话", "只跟你说", "坦白")
|
|
43
|
+
_DISCLOSE = ("我其实", "我觉得", "我害怕", "我担心", "我一直")
|
|
44
|
+
|
|
45
|
+
DELTAS = {
|
|
46
|
+
"warmth": {"trust": 0.004, "closeness": 0.010},
|
|
47
|
+
"conflict": {"trust": -0.012, "closeness": -0.004, "conflict": 0.020},
|
|
48
|
+
"repair": {"trust": 0.008, "closeness": 0.004, "conflict": -0.020},
|
|
49
|
+
"trust": {"trust": 0.008, "closeness": 0.002},
|
|
50
|
+
"disclose": {"trust": 0.006, "closeness": 0.006},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _num(value, default=0.0):
|
|
55
|
+
try:
|
|
56
|
+
number = float(value)
|
|
57
|
+
except Exception:
|
|
58
|
+
return default
|
|
59
|
+
return number if number == number else default
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _root(store):
|
|
63
|
+
if isinstance(store, dict):
|
|
64
|
+
raw = store.get("root") or store.get("store") or store.get("path")
|
|
65
|
+
else:
|
|
66
|
+
raw = store
|
|
67
|
+
text = str(raw or "").strip()
|
|
68
|
+
return Path(text) if text else None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _data_dir(store):
|
|
72
|
+
root = _root(store)
|
|
73
|
+
if root is None:
|
|
74
|
+
return None
|
|
75
|
+
return root.parent if root.name == "memory" else root
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def enabled(store):
|
|
79
|
+
data_dir = _data_dir(store)
|
|
80
|
+
raw = {}
|
|
81
|
+
if data_dir is not None:
|
|
82
|
+
try:
|
|
83
|
+
raw = json.loads((data_dir / "flags.json").read_text(encoding="utf-8"))
|
|
84
|
+
except Exception:
|
|
85
|
+
raw = {}
|
|
86
|
+
if not isinstance(raw, dict):
|
|
87
|
+
raw = {}
|
|
88
|
+
return raw.get("relationship_governance", True) is not False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def signals(text):
|
|
92
|
+
"""One user message -> at most one kind plus its bounded deltas."""
|
|
93
|
+
value = str(text or "")
|
|
94
|
+
if any(word in value for word in _REPAIR):
|
|
95
|
+
kind = "repair"
|
|
96
|
+
elif any(word in value for word in _CONFLICT):
|
|
97
|
+
kind = "conflict"
|
|
98
|
+
elif any(word in value for word in _WARMTH):
|
|
99
|
+
kind = "warmth"
|
|
100
|
+
elif any(word in value for word in _TRUST):
|
|
101
|
+
kind = "trust"
|
|
102
|
+
elif any(word in value for word in _DISCLOSE):
|
|
103
|
+
kind = "disclose"
|
|
104
|
+
else:
|
|
105
|
+
return {"kind": "neutral", "deltas": {}}
|
|
106
|
+
return {"kind": kind, "deltas": dict(DELTAS[kind])}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def style_for(trust, closeness, conflict, repairs=0):
|
|
110
|
+
trust = _num(trust, 0.5)
|
|
111
|
+
closeness = _num(closeness, 0.5)
|
|
112
|
+
conflict = _num(conflict, 0.0)
|
|
113
|
+
if conflict >= 0.45 and trust < 0.55:
|
|
114
|
+
return "anxious"
|
|
115
|
+
if trust < 0.35 and closeness < 0.40:
|
|
116
|
+
return "avoidant"
|
|
117
|
+
if trust >= 0.55 and conflict < 0.25:
|
|
118
|
+
return "secure"
|
|
119
|
+
return "mixed"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def activation_for(style, conflict=0.0, current=0.0, responded=False, hours=0.0):
|
|
123
|
+
"""Attachment activation via the pure model modules (safe fallback)."""
|
|
124
|
+
style = style if style in ("secure", "anxious", "avoidant") else "secure"
|
|
125
|
+
try:
|
|
126
|
+
if _attach_reg is not None:
|
|
127
|
+
return dict(_attach_reg.activation(
|
|
128
|
+
style, threat=min(1.0, max(0.0, _num(conflict))),
|
|
129
|
+
current=_num(current), responded=bool(responded),
|
|
130
|
+
hours=max(0.0, _num(hours))))
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
return {"style": style, "activation": round(min(1.0, max(0.0, _num(current))), 4),
|
|
134
|
+
"mode": "comfort", "drive_mult": 1.0, "tone": "seek_comfort"}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def memory_path(store):
|
|
138
|
+
root = _root(store)
|
|
139
|
+
return (root / MEMORY_FILE) if root is not None else None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def read_memory(store):
|
|
143
|
+
path = memory_path(store)
|
|
144
|
+
try:
|
|
145
|
+
if path is None or not path.exists():
|
|
146
|
+
return {"schema": SCHEMA, "events": []}
|
|
147
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
148
|
+
if not isinstance(data, dict):
|
|
149
|
+
return {"schema": SCHEMA, "events": []}
|
|
150
|
+
events = [row for row in (data.get("events") or []) if isinstance(row, dict)]
|
|
151
|
+
return {"schema": SCHEMA, "events": events[-EVENT_LIMIT:]}
|
|
152
|
+
except Exception:
|
|
153
|
+
return {"schema": SCHEMA, "events": []}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _write_memory(store, doc):
|
|
157
|
+
path = memory_path(store)
|
|
158
|
+
if path is None:
|
|
159
|
+
return False
|
|
160
|
+
try:
|
|
161
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
tmp = path.with_name(path.name + ".tmp")
|
|
163
|
+
tmp.write_text(json.dumps(doc, ensure_ascii=False, indent=2),
|
|
164
|
+
encoding="utf-8")
|
|
165
|
+
tmp.replace(path)
|
|
166
|
+
return True
|
|
167
|
+
except Exception:
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def record_event(store, kind, text, deltas, now=None):
|
|
172
|
+
"""Append one notable relationship event (no-op for neutral turns)."""
|
|
173
|
+
if kind in ("", "neutral"):
|
|
174
|
+
return None
|
|
175
|
+
doc = read_memory(store)
|
|
176
|
+
row = {"ts": round(_num(now, time.time()), 3),
|
|
177
|
+
"kind": str(kind)[:16],
|
|
178
|
+
"text": str(text or "")[:40],
|
|
179
|
+
"delta": {key: round(_num(value, 0.0), 4)
|
|
180
|
+
for key, value in (deltas or {}).items()}}
|
|
181
|
+
events = (doc.get("events") or []) + [row]
|
|
182
|
+
doc["events"] = events[-EVENT_LIMIT:]
|
|
183
|
+
_write_memory(store, doc)
|
|
184
|
+
return row
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def summary(store, relationship=None):
|
|
188
|
+
"""Counters for /state: accumulation totals + attachment view."""
|
|
189
|
+
doc = read_memory(store)
|
|
190
|
+
events = doc.get("events") or []
|
|
191
|
+
rel = relationship if isinstance(relationship, dict) else {}
|
|
192
|
+
counts = {}
|
|
193
|
+
for row in events:
|
|
194
|
+
kind = str(row.get("kind") or "")
|
|
195
|
+
if kind:
|
|
196
|
+
counts[kind] = counts.get(kind, 0) + 1
|
|
197
|
+
style = str(rel.get("attachment_style") or "")
|
|
198
|
+
if not style:
|
|
199
|
+
style = style_for(rel.get("trust"), rel.get("closeness"),
|
|
200
|
+
rel.get("conflict"), counts.get("repair", 0))
|
|
201
|
+
view = activation_for(style, _num(rel.get("conflict"), 0.0))
|
|
202
|
+
last = events[-1] if events else {}
|
|
203
|
+
return {
|
|
204
|
+
"events": len(events),
|
|
205
|
+
"repairs": int(counts.get("repair", 0)),
|
|
206
|
+
"warmth": int(counts.get("warmth", 0)),
|
|
207
|
+
"conflicts": int(counts.get("conflict", 0)),
|
|
208
|
+
"attachment_style": view.get("style") or style,
|
|
209
|
+
"attachment_activation": round(_num(view.get("activation"), 0.0), 4),
|
|
210
|
+
"last_event": {"kind": str(last.get("kind") or ""),
|
|
211
|
+
"ts": round(_num(last.get("ts"), 0.0), 3),
|
|
212
|
+
"text": str(last.get("text") or "")},
|
|
213
|
+
"enabled": enabled(store),
|
|
214
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-222: self layer (persona lock / narrative / drift) for the DSH core."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
IDENTITY_FILE = "identity.json"
|
|
11
|
+
NARRATIVE_LIMIT = 30
|
|
12
|
+
SCHEMA = 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _root(store):
|
|
16
|
+
if isinstance(store, dict):
|
|
17
|
+
raw = store.get("root") or store.get("store") or store.get("path")
|
|
18
|
+
else:
|
|
19
|
+
raw = store
|
|
20
|
+
text = str(raw or "").strip()
|
|
21
|
+
return Path(text) if text else None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _data_dir(store):
|
|
25
|
+
root = _root(store)
|
|
26
|
+
if root is None:
|
|
27
|
+
return None
|
|
28
|
+
return root.parent if root.name == "memory" else root
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def path_for(store):
|
|
32
|
+
data_dir = _data_dir(store)
|
|
33
|
+
return (data_dir / IDENTITY_FILE) if data_dir is not None else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read(path):
|
|
37
|
+
try:
|
|
38
|
+
if path is None or not path.exists():
|
|
39
|
+
return {}
|
|
40
|
+
doc = json.loads(path.read_text(encoding="utf-8"))
|
|
41
|
+
return doc if isinstance(doc, dict) else {}
|
|
42
|
+
except Exception:
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _write(path, doc):
|
|
47
|
+
try:
|
|
48
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
tmp = path.with_name(path.name + ".tmp")
|
|
50
|
+
tmp.write_text(json.dumps(doc, ensure_ascii=False, indent=2),
|
|
51
|
+
encoding="utf-8")
|
|
52
|
+
tmp.replace(path)
|
|
53
|
+
return True
|
|
54
|
+
except Exception:
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def hash_persona(text):
|
|
59
|
+
raw = str(text or "").strip()
|
|
60
|
+
if not raw:
|
|
61
|
+
return ""
|
|
62
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def update(store, persona_hash="", narrative=None, now=None):
|
|
66
|
+
"""Lock the persona hash, append one narrative line, detect drift."""
|
|
67
|
+
path = path_for(store)
|
|
68
|
+
if path is None:
|
|
69
|
+
return None
|
|
70
|
+
stamp = round(float(now or time.time()), 3)
|
|
71
|
+
doc = _read(path)
|
|
72
|
+
locked = str(doc.get("persona_hash") or "")
|
|
73
|
+
incoming = str(persona_hash or "").strip()
|
|
74
|
+
drift = bool(locked and incoming and locked != incoming)
|
|
75
|
+
if incoming and not locked:
|
|
76
|
+
locked = incoming
|
|
77
|
+
if incoming and drift:
|
|
78
|
+
history = [row for row in (doc.get("drift") or []) if isinstance(row, dict)]
|
|
79
|
+
history.append({"ts": stamp, "from": locked, "to": incoming})
|
|
80
|
+
doc["drift"] = history[-10:]
|
|
81
|
+
locked = incoming
|
|
82
|
+
lines = [row for row in (doc.get("narrative") or []) if isinstance(row, dict)]
|
|
83
|
+
if isinstance(narrative, dict) and str(narrative.get("text") or "").strip():
|
|
84
|
+
lines.append({"ts": stamp, "kind": str(narrative.get("kind") or "note")[:16],
|
|
85
|
+
"text": str(narrative.get("text"))[:60]})
|
|
86
|
+
doc.update({"schema": SCHEMA, "persona_hash": locked,
|
|
87
|
+
"persona_locked": bool(locked), "narrative": lines[-NARRATIVE_LIMIT:],
|
|
88
|
+
"updated_at": stamp, "drift_count": len(doc.get("drift") or [])})
|
|
89
|
+
_write(path, doc)
|
|
90
|
+
return doc
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def summary(store):
|
|
94
|
+
doc = _read(path_for(store))
|
|
95
|
+
lines = [row for row in (doc.get("narrative") or []) if isinstance(row, dict)]
|
|
96
|
+
return {
|
|
97
|
+
"persona_locked": bool(doc.get("persona_locked")),
|
|
98
|
+
"persona_hash": str(doc.get("persona_hash") or "")[:12],
|
|
99
|
+
"drift_count": int(doc.get("drift_count") or 0),
|
|
100
|
+
"narrative_count": len(lines),
|
|
101
|
+
"last_narrative": (lines[-1].get("text") if lines else ""),
|
|
102
|
+
"updated_at": float(doc.get("updated_at") or 0.0),
|
|
103
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""task-223: persistent BioCore service (one process per DSH plugin lifetime).
|
|
4
|
+
|
|
5
|
+
Protocol: one JSON object per line on stdin, one JSON reply per line on stdout.
|
|
6
|
+
|
|
7
|
+
request {"id": <str>, "action": <str>, "payload": {...}}
|
|
8
|
+
reply {"id": <str>, "ok": true, "result": {...}}
|
|
9
|
+
{"id": <str>, "ok": false, "reason": "...", "detail": "..."}
|
|
10
|
+
|
|
11
|
+
The action is handed to biocore_runtime.pipeline.dispatch, so every policy
|
|
12
|
+
stays in the existing module. A bad request or an exception is answered and
|
|
13
|
+
the loop keeps running; SIGTERM/SIGINT stop it cleanly. No third-party deps.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import signal
|
|
21
|
+
import sys
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
import traceback
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
_PLUGIN_ROOT = str(Path(__file__).resolve().parents[1])
|
|
28
|
+
if _PLUGIN_ROOT not in sys.path:
|
|
29
|
+
sys.path.insert(0, _PLUGIN_ROOT)
|
|
30
|
+
_ADAPTER_DIR = str(os.environ.get("BIOCORE_ADAPTER_DIR") or "").strip()
|
|
31
|
+
if _ADAPTER_DIR and _ADAPTER_DIR not in sys.path:
|
|
32
|
+
sys.path.insert(0, _ADAPTER_DIR)
|
|
33
|
+
|
|
34
|
+
from biocore_runtime import pipeline # noqa: E402
|
|
35
|
+
|
|
36
|
+
STOP = False
|
|
37
|
+
_IDLE_EVENT = threading.Event()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _install_signals():
|
|
41
|
+
def _stop(signum, frame):
|
|
42
|
+
global STOP
|
|
43
|
+
STOP = True
|
|
44
|
+
_IDLE_EVENT.set()
|
|
45
|
+
for name in ("SIGTERM", "SIGINT"):
|
|
46
|
+
handler = getattr(signal, name, None)
|
|
47
|
+
if handler is None:
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
signal.signal(handler, _stop)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _reply(stream, req_id, ok, result=None, reason="", detail=""):
|
|
56
|
+
payload = {"id": req_id, "ok": bool(ok)}
|
|
57
|
+
if ok:
|
|
58
|
+
payload["result"] = result if result is not None else {}
|
|
59
|
+
else:
|
|
60
|
+
payload["reason"] = str(reason or "service_error")
|
|
61
|
+
if detail:
|
|
62
|
+
payload["detail"] = str(detail)[:300]
|
|
63
|
+
try:
|
|
64
|
+
stream.write(json.dumps(payload, ensure_ascii=False,
|
|
65
|
+
separators=(",", ":")) + "\n")
|
|
66
|
+
stream.flush()
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def handle(req):
|
|
72
|
+
"""One request dict -> (ok, result, reason, detail); never raises."""
|
|
73
|
+
if not isinstance(req, dict):
|
|
74
|
+
return False, None, "bad_request", "request must be an object"
|
|
75
|
+
req_id = req.get("id")
|
|
76
|
+
action = str(req.get("action") or "").strip()
|
|
77
|
+
payload = req.get("payload")
|
|
78
|
+
payload = payload if isinstance(payload, dict) else {}
|
|
79
|
+
if not action:
|
|
80
|
+
return False, None, "bad_request", "missing action"
|
|
81
|
+
if action == "ping":
|
|
82
|
+
return True, {"pong": True, "echo_len": len(json.dumps(payload)),
|
|
83
|
+
"pid": os.getpid()}, "", ""
|
|
84
|
+
if action == "sleep":
|
|
85
|
+
try:
|
|
86
|
+
ms = max(0, min(5000, int(payload.get("ms") or 0)))
|
|
87
|
+
except Exception:
|
|
88
|
+
ms = 0
|
|
89
|
+
time.sleep(ms / 1000.0)
|
|
90
|
+
return True, {"slept": ms}, "", ""
|
|
91
|
+
try:
|
|
92
|
+
result = pipeline.dispatch(action, payload)
|
|
93
|
+
return True, result, "", ""
|
|
94
|
+
except Exception as error: # keep the service alive
|
|
95
|
+
detail = "%s: %s" % (type(error).__name__, str(error)[:200])
|
|
96
|
+
try:
|
|
97
|
+
traceback.print_exc(file=sys.stderr)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
return False, None, "service_error", detail
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _watchdog(idle_timeout):
|
|
104
|
+
"""Exit when no request arrived for idle_timeout seconds (0 = never)."""
|
|
105
|
+
if not idle_timeout or idle_timeout <= 0:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
def _run():
|
|
109
|
+
while not STOP:
|
|
110
|
+
fired = _IDLE_EVENT.wait(timeout=idle_timeout)
|
|
111
|
+
if fired:
|
|
112
|
+
return
|
|
113
|
+
if STOP:
|
|
114
|
+
return
|
|
115
|
+
try:
|
|
116
|
+
sys.stderr.write("__bioService idle_timeout\n")
|
|
117
|
+
sys.stderr.flush()
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
os._exit(0)
|
|
121
|
+
|
|
122
|
+
thread = threading.Thread(target=_run, daemon=True)
|
|
123
|
+
thread.start()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def serve(stream_in=None, stream_out=None, idle_timeout=0.0):
|
|
127
|
+
global STOP
|
|
128
|
+
stdin = stream_in or sys.stdin
|
|
129
|
+
stdout = stream_out or sys.stdout
|
|
130
|
+
_watchdog(float(idle_timeout or 0.0))
|
|
131
|
+
for line in stdin:
|
|
132
|
+
if STOP:
|
|
133
|
+
break
|
|
134
|
+
line = str(line or "").strip()
|
|
135
|
+
if not line:
|
|
136
|
+
continue
|
|
137
|
+
_IDLE_EVENT.set()
|
|
138
|
+
_IDLE_EVENT.clear()
|
|
139
|
+
req_id = None
|
|
140
|
+
try:
|
|
141
|
+
request = json.loads(line)
|
|
142
|
+
if isinstance(request, dict):
|
|
143
|
+
req_id = request.get("id")
|
|
144
|
+
ok, result, reason, detail = handle(request)
|
|
145
|
+
except Exception as error:
|
|
146
|
+
ok, result, reason, detail = (False, None, "bad_json",
|
|
147
|
+
"%s: %s" % (type(error).__name__,
|
|
148
|
+
str(error)[:200]))
|
|
149
|
+
_reply(stdout, req_id, ok, result, reason, detail)
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main(argv=None):
|
|
154
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
155
|
+
parser.add_argument("--idle-timeout", type=float, default=0.0)
|
|
156
|
+
args, _unknown = parser.parse_known_args(list(argv or sys.argv[1:]))
|
|
157
|
+
_install_signals()
|
|
158
|
+
return serve(idle_timeout=args.idle_timeout)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
raise SystemExit(main())
|