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
|
@@ -69,6 +69,8 @@ if _PLUGIN_ROOT not in sys.path:
|
|
|
69
69
|
|
|
70
70
|
MAX_CHARS = 420
|
|
71
71
|
TOP_K = 5
|
|
72
|
+
INJECT_MAX_K = 10 # inject_block may ask for more when rerank prunes
|
|
73
|
+
INJECT_MODES = ("query", "recent")
|
|
72
74
|
HOURLY_CAP = 20
|
|
73
75
|
MIN_CHARS = 8
|
|
74
76
|
MAX_TEXT_CHARS = 2000
|
|
@@ -96,6 +98,43 @@ try:
|
|
|
96
98
|
except Exception: # pragma: no cover
|
|
97
99
|
_rerank_mod = None
|
|
98
100
|
|
|
101
|
+
try: # task-221: one global core state shared by every session
|
|
102
|
+
from . import core_state as _core_state
|
|
103
|
+
except Exception: # pragma: no cover
|
|
104
|
+
try:
|
|
105
|
+
from biocore_runtime import core_state as _core_state
|
|
106
|
+
except Exception:
|
|
107
|
+
_core_state = None
|
|
108
|
+
|
|
109
|
+
try: # task-223: memory governance + relationship accumulation
|
|
110
|
+
from . import governance as _governance
|
|
111
|
+
except Exception: # pragma: no cover
|
|
112
|
+
try:
|
|
113
|
+
from biocore_runtime import governance as _governance
|
|
114
|
+
except Exception:
|
|
115
|
+
_governance = None
|
|
116
|
+
try:
|
|
117
|
+
from . import relationship_state as _relationship_state
|
|
118
|
+
except Exception: # pragma: no cover
|
|
119
|
+
try:
|
|
120
|
+
from biocore_runtime import relationship_state as _relationship_state
|
|
121
|
+
except Exception:
|
|
122
|
+
_relationship_state = None
|
|
123
|
+
try: # task-222: self layer + autonomy
|
|
124
|
+
from . import self_layer as _self_layer
|
|
125
|
+
except Exception: # pragma: no cover
|
|
126
|
+
try:
|
|
127
|
+
from biocore_runtime import self_layer as _self_layer
|
|
128
|
+
except Exception:
|
|
129
|
+
_self_layer = None
|
|
130
|
+
try:
|
|
131
|
+
from . import autonomy as _autonomy
|
|
132
|
+
except Exception: # pragma: no cover
|
|
133
|
+
try:
|
|
134
|
+
from biocore_runtime import autonomy as _autonomy
|
|
135
|
+
except Exception:
|
|
136
|
+
_autonomy = None
|
|
137
|
+
|
|
99
138
|
try:
|
|
100
139
|
from biocore_model import sanitize as _sanitize
|
|
101
140
|
except Exception: # pragma: no cover
|
|
@@ -378,16 +417,272 @@ def capture(session_id, role, text, store, config=None):
|
|
|
378
417
|
return {"ok": True, "decision": decision, "entry": entry}
|
|
379
418
|
|
|
380
419
|
|
|
420
|
+
# ---------------------------------------------------------------- governance
|
|
421
|
+
def _concepts_of(text):
|
|
422
|
+
if _governance is None:
|
|
423
|
+
return []
|
|
424
|
+
try:
|
|
425
|
+
return _governance.extract_concepts(text)
|
|
426
|
+
except Exception:
|
|
427
|
+
return []
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _concept_filter(entries, query):
|
|
431
|
+
"""Keep entries whose concepts intersect the query (fallback: all)."""
|
|
432
|
+
rows = [entry for entry in (entries or []) if isinstance(entry, dict)]
|
|
433
|
+
wanted = set(_concepts_of(query))
|
|
434
|
+
if not wanted:
|
|
435
|
+
return rows
|
|
436
|
+
narrowed = [entry for entry in rows
|
|
437
|
+
if wanted & set(entry.get("concepts") or [])]
|
|
438
|
+
return narrowed or rows
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _governance_tick(store, now=None):
|
|
442
|
+
"""Decay/index/graph/aggregate sidecars; never deletes a memory."""
|
|
443
|
+
if _governance is None or not store:
|
|
444
|
+
return None
|
|
445
|
+
try:
|
|
446
|
+
return _governance.run(store, now=now)
|
|
447
|
+
except Exception:
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _relationship_tick(store, user_text, now=None):
|
|
452
|
+
"""Bounded trust/closeness/conflict accumulation + attachment/repair."""
|
|
453
|
+
if _relationship_state is None or not store:
|
|
454
|
+
return None
|
|
455
|
+
try:
|
|
456
|
+
if not _relationship_state.enabled(store):
|
|
457
|
+
return {"enabled": False, "skipped": True}
|
|
458
|
+
before = relationship_read(store)["relationship"]
|
|
459
|
+
signal = _relationship_state.signals(user_text)
|
|
460
|
+
deltas = signal.get("deltas") or {}
|
|
461
|
+
if deltas:
|
|
462
|
+
relationship_update(store, deltas=deltas, now=now)
|
|
463
|
+
after = relationship_read(store)["relationship"]
|
|
464
|
+
summary = _relationship_state.summary(store, after)
|
|
465
|
+
style = summary.get("attachment_style")
|
|
466
|
+
if style and style != after.get("attachment_style"):
|
|
467
|
+
relationship_update(store, attachment_style=style, now=now)
|
|
468
|
+
after = relationship_read(store)["relationship"]
|
|
469
|
+
_relationship_state.record_event(store, signal.get("kind"),
|
|
470
|
+
user_text, deltas, now=now)
|
|
471
|
+
return {"kind": signal.get("kind"), "deltas": deltas,
|
|
472
|
+
"trust": round(_num(after.get("trust"), 0.5), 4),
|
|
473
|
+
"closeness": round(_num(after.get("closeness"), 0.5), 4),
|
|
474
|
+
"conflict": round(_num(after.get("conflict"), 0.0), 4),
|
|
475
|
+
"delta_trust": round(_num(after.get("trust"), 0.5)
|
|
476
|
+
- _num(before.get("trust"), 0.5), 4),
|
|
477
|
+
"delta_closeness": round(_num(after.get("closeness"), 0.5)
|
|
478
|
+
- _num(before.get("closeness"), 0.5), 4),
|
|
479
|
+
"attachment_style": str(after.get("attachment_style") or style),
|
|
480
|
+
"summary": summary}
|
|
481
|
+
except Exception:
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _identity_tick(store, narrative=None, now=None):
|
|
486
|
+
if _self_layer is None or not store:
|
|
487
|
+
return None
|
|
488
|
+
try:
|
|
489
|
+
cfg = _load_runtime_config(store)
|
|
490
|
+
persona_hash = _self_layer.hash_persona(cfg.get("persona_text"))
|
|
491
|
+
return _self_layer.update(store, persona_hash=persona_hash,
|
|
492
|
+
narrative=narrative, now=now)
|
|
493
|
+
except Exception:
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _autonomy_tick(store, data=None, now=None):
|
|
498
|
+
if _autonomy is None or not store:
|
|
499
|
+
return None
|
|
500
|
+
try:
|
|
501
|
+
payload = data if isinstance(data, dict) else {}
|
|
502
|
+
switches = _effective_switches(store, payload.get("switches"))
|
|
503
|
+
if not switches.get("core_enabled", True) or not switches.get(
|
|
504
|
+
"autonomy", True):
|
|
505
|
+
return {"ok": True, "action": None, "reason": "disabled",
|
|
506
|
+
"metrics": _autonomy.metrics(store, now),
|
|
507
|
+
"limits": _autonomy.limits(store)}
|
|
508
|
+
core_path = _core_state_path(payload)
|
|
509
|
+
state = {}
|
|
510
|
+
if _core_state is not None and _core_state.exists(core_path):
|
|
511
|
+
doc = _core_state.load(core_path)
|
|
512
|
+
state = {"core": _core_state.core_of(doc),
|
|
513
|
+
"intero": doc.get("intero")}
|
|
514
|
+
elif isinstance(payload.get("state"), dict):
|
|
515
|
+
state = payload["state"]
|
|
516
|
+
return _autonomy.decide(
|
|
517
|
+
store, now=now,
|
|
518
|
+
session_id=str(payload.get("session_id") or "autonomy"),
|
|
519
|
+
state=state,
|
|
520
|
+
last_human_ts=_num(payload.get("last_human_ts"), 0.0),
|
|
521
|
+
silence_minutes=_num(payload.get("silence_minutes"), 0.0))
|
|
522
|
+
except Exception:
|
|
523
|
+
return None
|
|
524
|
+
|
|
525
|
+
|
|
381
526
|
# ---------------------------------------------------------------- inject
|
|
382
|
-
def
|
|
383
|
-
|
|
527
|
+
def _store_data_dir(store):
|
|
528
|
+
root = _store_root(store)
|
|
529
|
+
if root is None:
|
|
530
|
+
return None
|
|
531
|
+
return root.parent if root.name == "memory" else root
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _data_flags(store):
|
|
535
|
+
"""<dataDir>/flags.json booleans (learning switches live next to memory/).
|
|
536
|
+
|
|
537
|
+
task-221口径: DSH learning switches default ON; an explicit file value wins.
|
|
538
|
+
"""
|
|
539
|
+
merged = {"mood_recall": True, "affect_encoding": True,
|
|
540
|
+
"memory_feedback": True, "preference_drift": True}
|
|
541
|
+
data_dir = _store_data_dir(store)
|
|
542
|
+
if data_dir is None:
|
|
543
|
+
return merged
|
|
544
|
+
raw = _load_json(data_dir / "flags.json", {})
|
|
545
|
+
for key, value in raw.items():
|
|
546
|
+
if isinstance(value, bool):
|
|
547
|
+
merged[str(key)] = value
|
|
548
|
+
cfg = _load_runtime_config(store)
|
|
549
|
+
switches = cfg.get("switches") if isinstance(cfg.get("switches"), dict) else {}
|
|
550
|
+
for key, value in switches.items():
|
|
551
|
+
if isinstance(value, bool):
|
|
552
|
+
merged[str(key)] = value
|
|
553
|
+
return merged
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _inject_engine(store, entries):
|
|
557
|
+
"""MemoryEngine over this session's entries, with the env embedder."""
|
|
558
|
+
try:
|
|
559
|
+
from biocore_memory.engine import MemoryEngine
|
|
560
|
+
from biocore_memory.recall.embedding import load_provider
|
|
561
|
+
from biocore_memory.store.json_store import MemoryStore
|
|
562
|
+
except Exception:
|
|
563
|
+
return None
|
|
564
|
+
embedder = None
|
|
565
|
+
try:
|
|
566
|
+
cfg = _load_runtime_config(store)
|
|
567
|
+
emb = cfg.get("embedding") if isinstance(cfg.get("embedding"), dict) else {}
|
|
568
|
+
provider_cfg = {}
|
|
569
|
+
if emb:
|
|
570
|
+
provider_cfg = {
|
|
571
|
+
"embedding_mode": emb.get("mode") or "auto",
|
|
572
|
+
"embedding_base_url": emb.get("base_url") or "",
|
|
573
|
+
"embedding_model": emb.get("model") or "",
|
|
574
|
+
"embedding_api_key": emb.get("api_key") or "",
|
|
575
|
+
}
|
|
576
|
+
embedder = load_provider(provider_cfg)
|
|
577
|
+
except Exception:
|
|
578
|
+
embedder = None
|
|
579
|
+
try:
|
|
580
|
+
memory = MemoryStore(doc={"entries": [dict(item) for item in entries
|
|
581
|
+
if isinstance(item, dict)]})
|
|
582
|
+
return MemoryEngine(store=memory, embedder=embedder,
|
|
583
|
+
config={"flags": _data_flags(store)})
|
|
584
|
+
except Exception:
|
|
585
|
+
return None
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _rerank_entries(query, entries, rerank_config):
|
|
589
|
+
"""Reorder candidates by the optional rerank API; None keeps local order."""
|
|
590
|
+
if _rerank_mod is None or not isinstance(rerank_config, dict):
|
|
591
|
+
return None
|
|
592
|
+
if not rerank_config.get("rerank_enabled"):
|
|
593
|
+
return None
|
|
594
|
+
try:
|
|
595
|
+
texts = [str(entry.get("text") or "") for entry in entries]
|
|
596
|
+
ranked = _rerank_mod.rerank_documents(query, texts, rerank_config)
|
|
597
|
+
except Exception:
|
|
598
|
+
return None
|
|
599
|
+
if not ranked:
|
|
600
|
+
return None
|
|
601
|
+
ordered = []
|
|
602
|
+
for item in ranked:
|
|
603
|
+
try:
|
|
604
|
+
index = int(item.get("index", -1))
|
|
605
|
+
except Exception:
|
|
606
|
+
continue
|
|
607
|
+
if 0 <= index < len(entries) and entries[index] not in ordered:
|
|
608
|
+
ordered.append(entries[index])
|
|
609
|
+
for entry in entries:
|
|
610
|
+
if entry not in ordered:
|
|
611
|
+
ordered.append(entry)
|
|
612
|
+
return ordered or None
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _query_entries(store, session_id, query, top_k, rerank_config):
|
|
616
|
+
"""engine.recall(query) over the injected candidates; None means fallback."""
|
|
617
|
+
entries = _visible(load_memory(store, session_id).get("entries"))
|
|
618
|
+
if not entries:
|
|
619
|
+
return None
|
|
620
|
+
engine = _inject_engine(store, entries)
|
|
621
|
+
if engine is None:
|
|
622
|
+
return None
|
|
623
|
+
rows = engine.recall(str(query)[:MAX_TEXT_CHARS], budget=top_k)
|
|
624
|
+
if not isinstance(rows, list) or not rows:
|
|
625
|
+
return None
|
|
626
|
+
picked = [row.get("entry") for row in rows
|
|
627
|
+
if isinstance(row, dict) and isinstance(row.get("entry"), dict)]
|
|
628
|
+
if not picked:
|
|
629
|
+
return None
|
|
630
|
+
# task-223: concept filter (entries written with concepts only).
|
|
631
|
+
narrowed = _concept_filter(picked, query)
|
|
632
|
+
if 0 < len(narrowed) < len(picked):
|
|
633
|
+
picked = narrowed
|
|
634
|
+
if _governance is not None:
|
|
635
|
+
try:
|
|
636
|
+
_governance.note_concept_filter(store, len(picked))
|
|
637
|
+
except Exception:
|
|
638
|
+
pass
|
|
639
|
+
if len(picked) > 1:
|
|
640
|
+
reordered = _rerank_entries(query, picked, rerank_config)
|
|
641
|
+
if reordered:
|
|
642
|
+
picked = reordered
|
|
643
|
+
# task-223: index hit + decay weight (cold memories sink, never deleted).
|
|
644
|
+
if _governance is not None:
|
|
645
|
+
try:
|
|
646
|
+
hits = set(_governance.lookup(store, query))
|
|
647
|
+
if hits:
|
|
648
|
+
_governance.note_index_hit(store)
|
|
649
|
+
if hits:
|
|
650
|
+
picked.sort(key=lambda entry: (
|
|
651
|
+
1 if str(entry.get("id") or "") in hits else 0,
|
|
652
|
+
_governance.weight_of(store, str(entry.get("id") or "")),
|
|
653
|
+
_num(entry.get("importance"), 0.5)), reverse=True)
|
|
654
|
+
except Exception:
|
|
655
|
+
pass
|
|
656
|
+
return picked[:top_k]
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def inject_block(session_id, store, max_chars=MAX_CHARS, top_k=TOP_K,
|
|
660
|
+
query="", mode="query", rerank_config=None):
|
|
661
|
+
"""Top-k session memories as one bounded Chinese prompt block.
|
|
662
|
+
|
|
663
|
+
mode="query" recall this turn's words (engine.recall + env embedder,
|
|
664
|
+
optional rerank); any failure falls back to recent.
|
|
665
|
+
mode="recent" keep the pin+ts ordering.
|
|
666
|
+
"""
|
|
384
667
|
session_id = sanitize_session_id(session_id)
|
|
385
668
|
budget = _int_param(max_chars, MAX_CHARS, 0, MAX_CHARS)
|
|
386
|
-
limit = _int_param(top_k, TOP_K, 1,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
669
|
+
limit = _int_param(top_k, TOP_K, 1, INJECT_MAX_K)
|
|
670
|
+
text_query = clean_text(query, MAX_TEXT_CHARS)
|
|
671
|
+
chosen = str(mode or "query").strip().lower()
|
|
672
|
+
entries = []
|
|
673
|
+
if chosen == "query" and text_query:
|
|
674
|
+
try:
|
|
675
|
+
picked = _query_entries(store, session_id, text_query, limit,
|
|
676
|
+
rerank_config)
|
|
677
|
+
except Exception:
|
|
678
|
+
picked = None
|
|
679
|
+
if picked:
|
|
680
|
+
entries = list(picked)
|
|
681
|
+
if not entries:
|
|
682
|
+
entries = sorted(_visible(load_memory(store, session_id).get("entries")),
|
|
683
|
+
key=lambda entry: (1 if entry.get("pin") else 0,
|
|
684
|
+
_num(entry.get("ts"), 0.0)),
|
|
685
|
+
reverse=True)[:limit]
|
|
391
686
|
header = "你记得这些事:"
|
|
392
687
|
ids = []
|
|
393
688
|
if not entries or budget <= len(header) + 3:
|
|
@@ -591,6 +886,7 @@ def _new_entry(text, role, tags, now):
|
|
|
591
886
|
"pin": False,
|
|
592
887
|
"importance": 0.5,
|
|
593
888
|
"hits": 1,
|
|
889
|
+
"concepts": _concepts_of(text),
|
|
594
890
|
}
|
|
595
891
|
|
|
596
892
|
|
|
@@ -1558,6 +1854,365 @@ def apply_persona_to_session(session_id, payload):
|
|
|
1558
1854
|
return state
|
|
1559
1855
|
|
|
1560
1856
|
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
# ---------------------------------------------------------------- core state
|
|
1860
|
+
def _core_state_path(payload):
|
|
1861
|
+
"""<dataDir>/core_state.json for any payload (store = <dataDir>/memory)."""
|
|
1862
|
+
if _core_state is None:
|
|
1863
|
+
return None
|
|
1864
|
+
data = payload if isinstance(payload, dict) else {}
|
|
1865
|
+
try:
|
|
1866
|
+
return _core_state.path_for(data.get("store"),
|
|
1867
|
+
data.get("data_dir") or data.get("dataDir"))
|
|
1868
|
+
except Exception:
|
|
1869
|
+
return None
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
def _intero_from_core(doc, prev, dt_hours, now):
|
|
1873
|
+
"""Recompute intero from the global body, using its own intero as prev."""
|
|
1874
|
+
body = dict(doc) if isinstance(doc, dict) else {}
|
|
1875
|
+
merged = {"core": body}
|
|
1876
|
+
if isinstance(prev, dict) and prev:
|
|
1877
|
+
merged["core"] = dict(body, intero=prev)
|
|
1878
|
+
merged["intero"] = dict(prev)
|
|
1879
|
+
return compute_intero(merged, dt_hours=dt_hours or 0.0, now=now)
|
|
1880
|
+
|
|
1881
|
+
|
|
1882
|
+
def _state_deltas(payload):
|
|
1883
|
+
"""This turn's numeric deltas: direct dict, or diagnostics before/after."""
|
|
1884
|
+
data = payload if isinstance(payload, dict) else {}
|
|
1885
|
+
out = {}
|
|
1886
|
+
|
|
1887
|
+
def add(key, value):
|
|
1888
|
+
if key not in _CORE_FIELDS:
|
|
1889
|
+
return
|
|
1890
|
+
try:
|
|
1891
|
+
out[key] = out.get(key, 0.0) + float(value)
|
|
1892
|
+
except Exception:
|
|
1893
|
+
pass
|
|
1894
|
+
|
|
1895
|
+
for source_key in ("delta", "deltas", "state_delta"):
|
|
1896
|
+
raw = data.get(source_key)
|
|
1897
|
+
if isinstance(raw, dict):
|
|
1898
|
+
for key, value in raw.items():
|
|
1899
|
+
add(str(key), value)
|
|
1900
|
+
elif isinstance(raw, list):
|
|
1901
|
+
for item in raw:
|
|
1902
|
+
if not isinstance(item, dict):
|
|
1903
|
+
continue
|
|
1904
|
+
add(str(item.get("field") or item.get("key") or ""),
|
|
1905
|
+
_num(item.get("after"), 0.0) - _num(item.get("before"), 0.0))
|
|
1906
|
+
return {key: round(value, 4) for key, value in out.items()
|
|
1907
|
+
if abs(value) > 1e-9}
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
_CORE_FIELDS = ("v", "a", "stress", "energy", "social", "social_pain",
|
|
1911
|
+
"trust", "closeness", "conflict")
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
def _recall_count(store, session_id):
|
|
1915
|
+
"""Recalled memory hits for this session (0 when traces are empty)."""
|
|
1916
|
+
try:
|
|
1917
|
+
total = 0
|
|
1918
|
+
for row in trace_read(store, session_id, TRACE_KEEP):
|
|
1919
|
+
memory = row.get("memory") if isinstance(row.get("memory"), dict) else {}
|
|
1920
|
+
ids = memory.get("recalled_ids")
|
|
1921
|
+
if isinstance(ids, list):
|
|
1922
|
+
total += len(ids)
|
|
1923
|
+
return total
|
|
1924
|
+
except Exception:
|
|
1925
|
+
return 0
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
def _proactive_sent_today(store):
|
|
1929
|
+
"""Messages delivered by the proactive path today (0 when none)."""
|
|
1930
|
+
try:
|
|
1931
|
+
root = _store_root(store)
|
|
1932
|
+
if root is None:
|
|
1933
|
+
return 0
|
|
1934
|
+
data = _load_json(root / "proactive.json", {})
|
|
1935
|
+
sent = data.get("sent") if isinstance(data.get("sent"), list) else []
|
|
1936
|
+
now = time.time()
|
|
1937
|
+
start = time.mktime(time.localtime(now)[:3] + (0, 0, 0, 0, 0, -1))
|
|
1938
|
+
return sum(1 for item in sent if _num(item, 0.0) / 1000.0 >= start)
|
|
1939
|
+
except Exception:
|
|
1940
|
+
return 0
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
def _series_payload(store, payload=None):
|
|
1944
|
+
"""Newest <=24 points from the global history + growth_history.jsonl."""
|
|
1945
|
+
data = payload if isinstance(payload, dict) else {}
|
|
1946
|
+
limit = _int_param(data.get("limit"), 24, 1, 288)
|
|
1947
|
+
rows = []
|
|
1948
|
+
try:
|
|
1949
|
+
rows = _core_state.points(_core_state_path(data), limit) if _core_state else []
|
|
1950
|
+
except Exception:
|
|
1951
|
+
rows = []
|
|
1952
|
+
growth = []
|
|
1953
|
+
try:
|
|
1954
|
+
root = _store_root(store)
|
|
1955
|
+
path = (root / "growth_history.jsonl") if root is not None else None
|
|
1956
|
+
if path is not None and path.exists():
|
|
1957
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
1958
|
+
line = line.strip()
|
|
1959
|
+
if not line:
|
|
1960
|
+
continue
|
|
1961
|
+
try:
|
|
1962
|
+
item = json.loads(line)
|
|
1963
|
+
except Exception:
|
|
1964
|
+
continue
|
|
1965
|
+
if isinstance(item, dict) and _num(item.get("ts"), 0.0) > 0:
|
|
1966
|
+
growth.append({"ts": round(_num(item.get("ts")), 3),
|
|
1967
|
+
"trust": round(_num(item.get("trust"), 0.5), 4),
|
|
1968
|
+
"closeness": round(_num(item.get("closeness"),
|
|
1969
|
+
0.5), 4)})
|
|
1970
|
+
except Exception:
|
|
1971
|
+
growth = []
|
|
1972
|
+
if growth:
|
|
1973
|
+
known = {round(_num(row.get("ts"), 0.0), 0) for row in rows}
|
|
1974
|
+
for item in growth[-limit:]:
|
|
1975
|
+
if round(_num(item.get("ts"), 0.0), 0) not in known:
|
|
1976
|
+
rows.append(item)
|
|
1977
|
+
rows.sort(key=lambda row: _num(row.get("ts"), 0.0))
|
|
1978
|
+
rows = rows[-limit:]
|
|
1979
|
+
series = {key: [row.get(key) for row in rows
|
|
1980
|
+
if isinstance(row, dict) and row.get(key) is not None]
|
|
1981
|
+
for key in ("ts", "v", "tension", "warmth", "heaviness",
|
|
1982
|
+
"clarity", "trust", "closeness")}
|
|
1983
|
+
return {"ok": True, "series": series, "points": rows,
|
|
1984
|
+
"source": "core_state" if _core_state else "unavailable"}
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
_RUNTIME_CONFIG = {}
|
|
1988
|
+
CORE_CONFIG_FILE = "core_config.json"
|
|
1989
|
+
CORE_DEFAULTS = {
|
|
1990
|
+
# contract: everything core-side is on; only external/irreversible off.
|
|
1991
|
+
"core_enabled": True,
|
|
1992
|
+
"mood_recall": True, "affect_encoding": True,
|
|
1993
|
+
"memory_feedback": True, "preference_drift": True,
|
|
1994
|
+
"governance": True, "index": True, "consolidate": True,
|
|
1995
|
+
"evict": True, "evict_delete": False,
|
|
1996
|
+
"relationship_governance": True,
|
|
1997
|
+
"self_layer": True, "motivation": True,
|
|
1998
|
+
"autonomy": True, "standby": True,
|
|
1999
|
+
"safety_enabled": False, "boundary_enabled": False,
|
|
2000
|
+
"crisis_safety_enabled": False,
|
|
2001
|
+
"proactive_send": True,
|
|
2002
|
+
"recall_mode": "query",
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
|
|
2006
|
+
def _load_runtime_config(store):
|
|
2007
|
+
data_dir = _store_data_dir(store)
|
|
2008
|
+
doc = {}
|
|
2009
|
+
if data_dir is not None:
|
|
2010
|
+
doc = _load_json(data_dir / CORE_CONFIG_FILE, {})
|
|
2011
|
+
if not isinstance(doc, dict) or not doc:
|
|
2012
|
+
doc = _RUNTIME_CONFIG
|
|
2013
|
+
return doc if isinstance(doc, dict) else {}
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
def configure(payload):
|
|
2017
|
+
"""task-223: whole config arrives once; the core owns every decision."""
|
|
2018
|
+
data = payload if isinstance(payload, dict) else {}
|
|
2019
|
+
store = data.get("store")
|
|
2020
|
+
data_dir = data.get("data_dir")
|
|
2021
|
+
if not data_dir:
|
|
2022
|
+
data_dir = _store_data_dir(store) if store else None
|
|
2023
|
+
doc = {key: value for key, value in data.items()
|
|
2024
|
+
if key not in ("store", "data_dir")}
|
|
2025
|
+
doc["updated_at"] = round(time.time(), 3)
|
|
2026
|
+
if data_dir is not None:
|
|
2027
|
+
try:
|
|
2028
|
+
_atomic_write(Path(data_dir) / CORE_CONFIG_FILE, doc)
|
|
2029
|
+
except Exception:
|
|
2030
|
+
pass
|
|
2031
|
+
_RUNTIME_CONFIG.clear()
|
|
2032
|
+
_RUNTIME_CONFIG.update(doc)
|
|
2033
|
+
return {"ok": True, "configured": True,
|
|
2034
|
+
"switches": doc.get("switches") if isinstance(
|
|
2035
|
+
doc.get("switches"), dict) else {}}
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
def _effective_switches(store, overrides=None):
|
|
2039
|
+
merged = dict(CORE_DEFAULTS)
|
|
2040
|
+
data_dir = _store_data_dir(store)
|
|
2041
|
+
if data_dir is not None:
|
|
2042
|
+
flags = _load_json(data_dir / "flags.json", {})
|
|
2043
|
+
for key, value in flags.items():
|
|
2044
|
+
if isinstance(value, bool):
|
|
2045
|
+
merged[str(key)] = value
|
|
2046
|
+
cfg = _load_runtime_config(store)
|
|
2047
|
+
switches = cfg.get("switches") if isinstance(cfg.get("switches"), dict) else {}
|
|
2048
|
+
for key, value in switches.items():
|
|
2049
|
+
if isinstance(value, bool):
|
|
2050
|
+
merged[str(key)] = value
|
|
2051
|
+
if isinstance(overrides, dict):
|
|
2052
|
+
for key, value in overrides.items():
|
|
2053
|
+
if isinstance(value, bool):
|
|
2054
|
+
merged[str(key)] = value
|
|
2055
|
+
return merged
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
def _rerank_config_from(raw):
|
|
2059
|
+
"""Accept either the adapter's rerank payload or config.json's rerank block."""
|
|
2060
|
+
if not isinstance(raw, dict):
|
|
2061
|
+
return None
|
|
2062
|
+
if "rerank_enabled" in raw:
|
|
2063
|
+
return raw
|
|
2064
|
+
return {"rerank_enabled": raw.get("enabled") is True,
|
|
2065
|
+
"rerank_base_url": str(raw.get("base_url") or ""),
|
|
2066
|
+
"rerank_model": str(raw.get("model") or ""),
|
|
2067
|
+
"rerank_api_key": str(raw.get("api_key") or ""),
|
|
2068
|
+
"rerank_top_n": int(raw.get("top_n") or 20),
|
|
2069
|
+
"rerank_timeout": 8}
|
|
2070
|
+
|
|
2071
|
+
|
|
2072
|
+
def _recall_mode(store, requested=None):
|
|
2073
|
+
mode = str(requested or "").strip().lower()
|
|
2074
|
+
if mode in INJECT_MODES:
|
|
2075
|
+
return mode
|
|
2076
|
+
cfg = _load_runtime_config(store)
|
|
2077
|
+
mode = str(cfg.get("recall_mode") or "").strip().lower()
|
|
2078
|
+
return mode if mode in INJECT_MODES else "query"
|
|
2079
|
+
|
|
2080
|
+
|
|
2081
|
+
MOTIVATIONS = {
|
|
2082
|
+
"hold": "想先听着,不急着推进",
|
|
2083
|
+
"approach": "想靠近一点,再问一句",
|
|
2084
|
+
"withdraw": "想退半步,留点空间",
|
|
2085
|
+
"probe": "想探一探,但还不确定",
|
|
2086
|
+
"repair": "想把话圆回来",
|
|
2087
|
+
"rest": "有点累,想安静待着",
|
|
2088
|
+
"share": "想说说自己这边的事",
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
def _ui_state_body(store, session_id, data):
|
|
2093
|
+
"""task-223: the /state payload is assembled by the core, JS forwards it."""
|
|
2094
|
+
entries = _visible(load_memory(store, session_id).get("entries"))
|
|
2095
|
+
stored_state = _load_session_state(store, session_id)
|
|
2096
|
+
core_path = _core_state_path(data)
|
|
2097
|
+
core_state_ts = 0.0
|
|
2098
|
+
if _core_state is not None and _core_state.exists(core_path):
|
|
2099
|
+
doc = _core_state.load(core_path)
|
|
2100
|
+
core = _core_state.core_of(doc)
|
|
2101
|
+
intero = doc.get("intero")
|
|
2102
|
+
if not isinstance(intero, dict):
|
|
2103
|
+
intero = compute_intero({"core": core})
|
|
2104
|
+
core["intero"] = intero
|
|
2105
|
+
core_state_ts = doc.get("ts", 0.0)
|
|
2106
|
+
history = list(doc.get("history") or [])[-120:]
|
|
2107
|
+
else:
|
|
2108
|
+
merged_state = apply_intero_to_state(stored_state, data.get("state"))
|
|
2109
|
+
intero = merged_state.get("intero") or {}
|
|
2110
|
+
core = dict(merged_state.get("core") or {})
|
|
2111
|
+
core["intero"] = intero
|
|
2112
|
+
history = list(stored_state.get("history") or [])[-120:]
|
|
2113
|
+
relationship = (relationship_read(store)["relationship"]
|
|
2114
|
+
if _store_root(store) is not None else {})
|
|
2115
|
+
recent = []
|
|
2116
|
+
for entry in sorted(entries, key=lambda item: _num(item.get("ts"), 0.0),
|
|
2117
|
+
reverse=True)[:10]:
|
|
2118
|
+
text = clean_text(entry.get("text"), 500)
|
|
2119
|
+
if not text:
|
|
2120
|
+
continue
|
|
2121
|
+
recent.append({"ts": round(_num(entry.get("ts"), 0.0) * 1000, 0),
|
|
2122
|
+
"text": text[:80],
|
|
2123
|
+
"source": str(entry.get("memory_source") or "direct"),
|
|
2124
|
+
"kind": str(entry.get("kind") or "")})
|
|
2125
|
+
rel_metrics = {"closeness": round(_num(relationship.get("closeness"), 0.5), 2),
|
|
2126
|
+
"trust": round(_num(relationship.get("trust"), 0.5), 2),
|
|
2127
|
+
"conflict": round(_num(relationship.get("conflict"), 0.0), 2),
|
|
2128
|
+
"bids": round(_num(relationship.get("bids"), 0.0), 2)}
|
|
2129
|
+
body = {"self_esteem": round(_num(core.get("self_esteem"), 0.0), 2),
|
|
2130
|
+
"social_pain": round(_num(core.get("social_pain"), 0.0), 2),
|
|
2131
|
+
"energy": round(_num(core.get("energy"), 0.5), 2),
|
|
2132
|
+
"social": round(_num(core.get("social"), 0.5), 2)}
|
|
2133
|
+
trace_rows = trace_read(store, session_id, 1)
|
|
2134
|
+
last_trace = trace_rows[-1] if trace_rows else {}
|
|
2135
|
+
last_turn = last_trace.get("intent") if isinstance(last_trace, dict) else {}
|
|
2136
|
+
intent = str((last_turn or {}).get("kind") or "")[:24]
|
|
2137
|
+
governance_summary = None
|
|
2138
|
+
if _governance is not None:
|
|
2139
|
+
try:
|
|
2140
|
+
governance_summary = _governance.summary(store)
|
|
2141
|
+
except Exception:
|
|
2142
|
+
governance_summary = None
|
|
2143
|
+
relationship_summary = None
|
|
2144
|
+
if _relationship_state is not None:
|
|
2145
|
+
try:
|
|
2146
|
+
relationship_summary = _relationship_state.summary(store, relationship)
|
|
2147
|
+
except Exception:
|
|
2148
|
+
relationship_summary = None
|
|
2149
|
+
identity_summary = None
|
|
2150
|
+
if _self_layer is not None:
|
|
2151
|
+
try:
|
|
2152
|
+
identity_summary = _self_layer.summary(store)
|
|
2153
|
+
except Exception:
|
|
2154
|
+
identity_summary = None
|
|
2155
|
+
autonomy_view = None
|
|
2156
|
+
if _autonomy is not None:
|
|
2157
|
+
try:
|
|
2158
|
+
switches = _effective_switches(store)
|
|
2159
|
+
metrics = _autonomy.metrics(store)
|
|
2160
|
+
autonomy_view = {
|
|
2161
|
+
"enabled": bool(switches.get("autonomy", True)),
|
|
2162
|
+
"actions_today": metrics["actions_today"],
|
|
2163
|
+
"sends_today": metrics["sends_today"],
|
|
2164
|
+
"actions_hour": metrics["actions_hour"],
|
|
2165
|
+
"sends_hour": metrics["sends_hour"],
|
|
2166
|
+
"limits": _autonomy.limits(store)}
|
|
2167
|
+
except Exception:
|
|
2168
|
+
autonomy_view = None
|
|
2169
|
+
identity = {"v": round(_num(core.get("v"), 0.0), 2),
|
|
2170
|
+
"intero": intero,
|
|
2171
|
+
"governance": governance_summary,
|
|
2172
|
+
"relationship_summary": relationship_summary,
|
|
2173
|
+
"identity": identity_summary,
|
|
2174
|
+
"available": True,
|
|
2175
|
+
"session_id": sanitize_session_id(session_id),
|
|
2176
|
+
"memory_count": len(entries),
|
|
2177
|
+
"memory": {"count": len(entries)},
|
|
2178
|
+
"memory_recent": recent,
|
|
2179
|
+
"relationship_metrics": rel_metrics,
|
|
2180
|
+
"body_metrics": body,
|
|
2181
|
+
"narrative": {"intent": intent, "length_factor": 1.0,
|
|
2182
|
+
"initiative": 0.5, "metacog_band": "",
|
|
2183
|
+
"allow_correction": False},
|
|
2184
|
+
"motivation": MOTIVATIONS.get(intent, "安静待着,等你开口"),
|
|
2185
|
+
"tone": {"label": ""},
|
|
2186
|
+
"relation": {"stage": ""},
|
|
2187
|
+
"autonomy": {"state": "静", **((autonomy_view or {}))},
|
|
2188
|
+
"history": history,
|
|
2189
|
+
"core_state_ts": core_state_ts,
|
|
2190
|
+
"recall_count": _recall_count(store, session_id),
|
|
2191
|
+
"governance_numbers": (governance_summary or {}),
|
|
2192
|
+
"proactive_sent_today": _proactive_sent_today(store),
|
|
2193
|
+
"core": core,
|
|
2194
|
+
"source": "core"}
|
|
2195
|
+
return identity
|
|
2196
|
+
|
|
2197
|
+
|
|
2198
|
+
def _embed_stats_snapshot():
|
|
2199
|
+
"""Embedding counters: persisted file (spawn-per-call) wins over process."""
|
|
2200
|
+
data = {}
|
|
2201
|
+
try:
|
|
2202
|
+
from biocore_memory.recall.embedding import embed_stats as _embed_stats
|
|
2203
|
+
data = dict(_embed_stats())
|
|
2204
|
+
except Exception:
|
|
2205
|
+
data = {}
|
|
2206
|
+
path = str(os.environ.get("BIOCORE_EMBED_STATS_FILE") or "").strip()
|
|
2207
|
+
if path and os.path.isfile(path):
|
|
2208
|
+
persisted = _load_json(Path(path), {})
|
|
2209
|
+
if isinstance(persisted, dict) and persisted:
|
|
2210
|
+
data = {key: int(persisted.get(key, data.get(key, 0)) or 0)
|
|
2211
|
+
for key in ("hit", "disk_hit", "miss", "remote",
|
|
2212
|
+
"local", "fail")}
|
|
2213
|
+
return data
|
|
2214
|
+
|
|
2215
|
+
|
|
1561
2216
|
# ---------------------------------------------------------------- turn trace
|
|
1562
2217
|
TRACE_DIR = "traces"
|
|
1563
2218
|
TRACE_KEEP = 50
|
|
@@ -1773,11 +2428,52 @@ def dispatch(action, payload=None):
|
|
|
1773
2428
|
if "user_text" not in data:
|
|
1774
2429
|
return bad("missing user_text")
|
|
1775
2430
|
user_text = clean_text(data.get("user_text"))
|
|
2431
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2432
|
+
data["switches"] = switches
|
|
2433
|
+
if not switches.get("core_enabled", True):
|
|
2434
|
+
return {"ok": True, "prompt_block": "", "state": {},
|
|
2435
|
+
"disabled": True, "reason": "core_disabled"}
|
|
1776
2436
|
merge_shared_relationship_into_state(session_id, data)
|
|
1777
2437
|
session_state = apply_persona_to_session(session_id, data)
|
|
1778
2438
|
session_state = session_state if isinstance(session_state, dict) else {}
|
|
1779
|
-
session_state = apply_intero_to_state(session_state, data.get("state"))
|
|
1780
2439
|
state_store = _relationship_store(data)
|
|
2440
|
+
core_path = _core_state_path(data)
|
|
2441
|
+
if _core_state is not None and core_path is not None:
|
|
2442
|
+
payload_state = (data.get("state")
|
|
2443
|
+
if isinstance(data.get("state"), dict) else {})
|
|
2444
|
+
payload_core = (payload_state.get("core")
|
|
2445
|
+
if isinstance(payload_state.get("core"), dict)
|
|
2446
|
+
else {})
|
|
2447
|
+
legacy_core = dict(payload_core)
|
|
2448
|
+
session_core = session_state.get("core")
|
|
2449
|
+
if isinstance(session_core, dict):
|
|
2450
|
+
legacy_core.update(session_core)
|
|
2451
|
+
if _core_state.exists(core_path):
|
|
2452
|
+
_core_state.migrate_session(core_path, legacy_core,
|
|
2453
|
+
session_id, now=time.time())
|
|
2454
|
+
else:
|
|
2455
|
+
passed = {key: legacy_core[key] for key in _CORE_FIELDS
|
|
2456
|
+
if key in legacy_core}
|
|
2457
|
+
_core_state.apply(core_path, absolute=passed,
|
|
2458
|
+
now=time.time(), session_id=session_id,
|
|
2459
|
+
intero_fn=_intero_from_core)
|
|
2460
|
+
doc = _core_state.load(core_path)
|
|
2461
|
+
if doc.get("intero") is None:
|
|
2462
|
+
_core_state.apply(core_path, now=time.time(),
|
|
2463
|
+
session_id=session_id,
|
|
2464
|
+
intero_fn=_intero_from_core)
|
|
2465
|
+
doc = _core_state.load(core_path)
|
|
2466
|
+
core = dict(legacy_core)
|
|
2467
|
+
core.update(_core_state.core_of(doc))
|
|
2468
|
+
if isinstance(doc.get("intero"), dict):
|
|
2469
|
+
core["intero"] = dict(doc["intero"])
|
|
2470
|
+
session_state["intero"] = dict(doc["intero"])
|
|
2471
|
+
session_state["core"] = core
|
|
2472
|
+
session_state["core_delta"] = {}
|
|
2473
|
+
session_state["core_state_ts"] = doc.get("ts", 0.0)
|
|
2474
|
+
else:
|
|
2475
|
+
session_state = apply_intero_to_state(session_state,
|
|
2476
|
+
data.get("state"))
|
|
1781
2477
|
if state_store is not None:
|
|
1782
2478
|
_save_session_state(state_store, session_id, session_state)
|
|
1783
2479
|
capture_enabled = _as_bool(data.get("capture"), True)
|
|
@@ -1818,6 +2514,15 @@ def dispatch(action, payload=None):
|
|
|
1818
2514
|
return bad("missing session_id")
|
|
1819
2515
|
if "assistant_text" not in data:
|
|
1820
2516
|
return bad("missing assistant_text")
|
|
2517
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2518
|
+
data["switches"] = switches
|
|
2519
|
+
if not switches.get("core_enabled", True):
|
|
2520
|
+
return {"ok": True, "gate": {"blocked": False, "action": "pass",
|
|
2521
|
+
"text": str(data.get("assistant_text") or ""),
|
|
2522
|
+
"rule": "", "label": ""},
|
|
2523
|
+
"captured": None, "clean_text": str(data.get("assistant_text") or ""),
|
|
2524
|
+
"trace": None, "trace_file": "", "disabled": True,
|
|
2525
|
+
"reason": "core_disabled"}
|
|
1821
2526
|
raw_assistant = clean_text(data.get("assistant_text"))
|
|
1822
2527
|
do_sanitize = _as_bool(data.get("sanitize_reply"), True)
|
|
1823
2528
|
cleaned = raw_assistant
|
|
@@ -1864,6 +2569,50 @@ def dispatch(action, payload=None):
|
|
|
1864
2569
|
"reason": "disabled" if not capture_enabled else "no_store",
|
|
1865
2570
|
}, "entry": None}
|
|
1866
2571
|
merge_state_relationship_into_shared(session_id, data)
|
|
2572
|
+
core_snapshot = None
|
|
2573
|
+
core_path = _core_state_path(data)
|
|
2574
|
+
if _core_state is not None and core_path is not None:
|
|
2575
|
+
deltas = _state_deltas(data)
|
|
2576
|
+
session_doc = (_load_session_state(store, session_id)
|
|
2577
|
+
if store is not None else {})
|
|
2578
|
+
legacy_core = session_doc.get("core")
|
|
2579
|
+
legacy_core = legacy_core if isinstance(legacy_core, dict) else {}
|
|
2580
|
+
if not _core_state.exists(core_path):
|
|
2581
|
+
passed = {key: legacy_core[key] for key in _CORE_FIELDS
|
|
2582
|
+
if key in legacy_core}
|
|
2583
|
+
_core_state.apply(core_path, absolute=passed,
|
|
2584
|
+
now=time.time(), session_id=session_id,
|
|
2585
|
+
intero_fn=_intero_from_core)
|
|
2586
|
+
shared_rel = (relationship_read(store)["relationship"]
|
|
2587
|
+
if store is not None else {})
|
|
2588
|
+
fixed = {key: shared_rel[key]
|
|
2589
|
+
for key in ("trust", "closeness", "conflict")
|
|
2590
|
+
if isinstance(shared_rel.get(key), (int, float))}
|
|
2591
|
+
doc = _core_state.apply(core_path, deltas=deltas,
|
|
2592
|
+
absolute=fixed, now=time.time(),
|
|
2593
|
+
session_id=session_id,
|
|
2594
|
+
intero_fn=_intero_from_core)
|
|
2595
|
+
if doc:
|
|
2596
|
+
core = dict(legacy_core)
|
|
2597
|
+
core.update(_core_state.core_of(doc))
|
|
2598
|
+
if isinstance(doc.get("intero"), dict):
|
|
2599
|
+
core["intero"] = dict(doc["intero"])
|
|
2600
|
+
session_doc["intero"] = dict(doc["intero"])
|
|
2601
|
+
session_doc["core"] = core
|
|
2602
|
+
session_doc["core_delta"] = dict(deltas)
|
|
2603
|
+
session_doc["core_state_ts"] = doc.get("ts", 0.0)
|
|
2604
|
+
if store is not None:
|
|
2605
|
+
_save_session_state(store, session_id, session_doc)
|
|
2606
|
+
core_snapshot = _core_state.snapshot(core_path)
|
|
2607
|
+
governance_info = _governance_tick(store, now=time.time())
|
|
2608
|
+
relationship_info = _relationship_tick(
|
|
2609
|
+
store, data.get("user_text"), now=time.time())
|
|
2610
|
+
identity_info = None
|
|
2611
|
+
if relationship_info:
|
|
2612
|
+
identity_info = _identity_tick(
|
|
2613
|
+
store, narrative={"kind": relationship_info.get("kind"),
|
|
2614
|
+
"text": str(data.get("user_text") or "")[:60]},
|
|
2615
|
+
now=time.time())
|
|
1867
2616
|
decision = captured.get("decision") or {}
|
|
1868
2617
|
entry = captured.get("entry") or {}
|
|
1869
2618
|
trace = build_trace(session_id, data, gate, {"entry_id": entry.get("id")})
|
|
@@ -1881,27 +2630,93 @@ def dispatch(action, payload=None):
|
|
|
1881
2630
|
"removed_tags": int(removed_tags),
|
|
1882
2631
|
"trace": trace,
|
|
1883
2632
|
"trace_file": trace_file,
|
|
2633
|
+
"core_state": core_snapshot,
|
|
2634
|
+
"governance": governance_info,
|
|
2635
|
+
"relationship": relationship_info,
|
|
2636
|
+
"identity": identity_info,
|
|
1884
2637
|
}
|
|
2638
|
+
if action == "stats":
|
|
2639
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2640
|
+
if not switches.get("core_enabled", True):
|
|
2641
|
+
return {"ok": True, "disabled": True, "reason": "core_disabled",
|
|
2642
|
+
"embed": {key: 0 for key in ("hit", "disk_hit", "miss",
|
|
2643
|
+
"remote", "local", "fail")},
|
|
2644
|
+
"switches": {}, "store": str(store)}
|
|
2645
|
+
embed = _embed_stats_snapshot()
|
|
2646
|
+
names = ("mood_recall", "affect_encoding", "memory_feedback",
|
|
2647
|
+
"preference_drift")
|
|
2648
|
+
out = {name: {"enabled": bool(switches.get(name)), "source": "core"}
|
|
2649
|
+
for name in names}
|
|
2650
|
+
out["core_enabled"] = {"enabled": True, "source": "core"}
|
|
2651
|
+
governance_info = None
|
|
2652
|
+
if _governance is not None:
|
|
2653
|
+
try:
|
|
2654
|
+
governance_info = _governance.summary(store)
|
|
2655
|
+
except Exception:
|
|
2656
|
+
governance_info = None
|
|
2657
|
+
relationship_info = None
|
|
2658
|
+
if _relationship_state is not None:
|
|
2659
|
+
try:
|
|
2660
|
+
relationship_info = _relationship_state.summary(
|
|
2661
|
+
store, relationship_read(store)["relationship"])
|
|
2662
|
+
except Exception:
|
|
2663
|
+
relationship_info = None
|
|
2664
|
+
identity_info = None
|
|
2665
|
+
if _self_layer is not None:
|
|
2666
|
+
try:
|
|
2667
|
+
identity_info = _self_layer.summary(store)
|
|
2668
|
+
except Exception:
|
|
2669
|
+
identity_info = None
|
|
2670
|
+
autonomy_info = None
|
|
2671
|
+
if _autonomy is not None:
|
|
2672
|
+
try:
|
|
2673
|
+
metrics = _autonomy.metrics(store)
|
|
2674
|
+
autonomy_info = {"enabled": bool(switches.get("autonomy", True)),
|
|
2675
|
+
"actions_today": metrics["actions_today"],
|
|
2676
|
+
"sends_today": metrics["sends_today"],
|
|
2677
|
+
"limits": _autonomy.limits(store)}
|
|
2678
|
+
except Exception:
|
|
2679
|
+
autonomy_info = None
|
|
2680
|
+
return {"ok": True, "embed": embed, "switches": out,
|
|
2681
|
+
"governance": governance_info,
|
|
2682
|
+
"relationship": relationship_info,
|
|
2683
|
+
"identity": identity_info,
|
|
2684
|
+
"autonomy": autonomy_info,
|
|
2685
|
+
"store": str(store)}
|
|
2686
|
+
if action == "autonomy_tick":
|
|
2687
|
+
return _autonomy_tick(store, data) or {
|
|
2688
|
+
"ok": False, "reason": "autonomy_unavailable"}
|
|
1885
2689
|
if action == "state":
|
|
1886
2690
|
session_id = str(data.get("session_id") or "").strip()
|
|
1887
2691
|
if not session_id:
|
|
1888
2692
|
return bad("missing session_id")
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2693
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2694
|
+
if not switches.get("core_enabled", True):
|
|
2695
|
+
return {"ok": True, "state": {"available": False,
|
|
2696
|
+
"reason": "core_disabled",
|
|
2697
|
+
"session_id": sanitize_session_id(session_id)}}
|
|
2698
|
+
body = _ui_state_body(store, session_id, data)
|
|
1892
2699
|
stored_state = _load_session_state(store, session_id)
|
|
1893
|
-
merged_state = apply_intero_to_state(stored_state, data.get("state"))
|
|
1894
|
-
intero = merged_state["intero"]
|
|
1895
|
-
state_payload["intero"] = intero
|
|
1896
|
-
if isinstance(merged_state.get("core"), dict):
|
|
1897
|
-
state_payload["core"] = merged_state["core"]
|
|
1898
2700
|
if stored_state.get("persona_version"):
|
|
1899
|
-
|
|
2701
|
+
body["persona_version"] = str(
|
|
1900
2702
|
stored_state.get("persona_version") or "")
|
|
1901
2703
|
if _store_root(store) is not None:
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2704
|
+
body["relationship"] = relationship_read(store)["relationship"]
|
|
2705
|
+
body["switches"] = {key: bool(switches.get(key)) for key in
|
|
2706
|
+
("core_enabled", "mood_recall",
|
|
2707
|
+
"affect_encoding", "memory_feedback",
|
|
2708
|
+
"preference_drift")}
|
|
2709
|
+
return {"ok": True, "state": body,
|
|
2710
|
+
"ui_payload": {"intero": body.get("intero") or {}}}
|
|
2711
|
+
if action == "series":
|
|
2712
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2713
|
+
if not switches.get("core_enabled", True):
|
|
2714
|
+
return {"ok": True, "series": {key: [] for key in
|
|
2715
|
+
("ts", "v", "tension", "warmth",
|
|
2716
|
+
"heaviness", "clarity", "trust",
|
|
2717
|
+
"closeness")},
|
|
2718
|
+
"points": [], "source": "disabled", "disabled": True}
|
|
2719
|
+
return _series_payload(store, data)
|
|
1905
2720
|
if action == "switches_get":
|
|
1906
2721
|
current = normalize_switches(data.get("switches"))
|
|
1907
2722
|
key = str(data.get("key") or "").strip()
|
|
@@ -1980,10 +2795,22 @@ def dispatch(action, payload=None):
|
|
|
1980
2795
|
store, now=data.get("now"), state_file=data.get("state_file"))
|
|
1981
2796
|
if action in ("memory_inject", "memory"):
|
|
1982
2797
|
session_id = data.get("session_id") or data.get("session") or "default"
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2798
|
+
switches = _effective_switches(store, data.get("switches"))
|
|
2799
|
+
if not switches.get("core_enabled", True):
|
|
2800
|
+
return {"ok": True, "text": "", "chars": 0, "ids": [],
|
|
2801
|
+
"disabled": True, "reason": "core_disabled"}
|
|
2802
|
+
result = inject_block(
|
|
2803
|
+
session_id, store,
|
|
2804
|
+
max_chars=data.get("max_chars", MAX_CHARS),
|
|
2805
|
+
top_k=data.get("top_k", TOP_K),
|
|
2806
|
+
query=data.get("query") or data.get("user_text") or "",
|
|
2807
|
+
mode=_recall_mode(store, data.get("mode")),
|
|
2808
|
+
rerank_config=(data.get("rerank_config")
|
|
2809
|
+
or _rerank_config_from(
|
|
2810
|
+
_load_runtime_config(store).get("rerank"))))
|
|
1986
2811
|
return {"ok": True, **result}
|
|
2812
|
+
if action == "configure":
|
|
2813
|
+
return configure(data)
|
|
1987
2814
|
return {"ok": False, "reason": "unknown_action", "action": action}
|
|
1988
2815
|
except Exception as exc: # CLI must always answer one JSON line
|
|
1989
2816
|
return {"ok": False, "reason": "pipeline_error",
|