dsh-biocore 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +2 -2
  2. package/lib/py/bridge.py +15 -7
  3. package/lib/py-bridge.js +4 -3
  4. package/package.json +3 -2
  5. package/python/biocore_memory/__init__.py +21 -0
  6. package/python/biocore_memory/adapters/__init__.py +2 -0
  7. package/python/biocore_memory/adapters/astrbot.py +24 -0
  8. package/python/biocore_memory/adapters/dsh_cli.py +335 -0
  9. package/python/biocore_memory/co_recall.py +324 -0
  10. package/python/biocore_memory/consolidate/__init__.py +11 -0
  11. package/python/biocore_memory/consolidate/gated.py +104 -0
  12. package/python/biocore_memory/consolidate/neighbors.py +68 -0
  13. package/python/biocore_memory/consolidate/reflection.py +45 -0
  14. package/python/biocore_memory/consolidate/tree.py +95 -0
  15. package/python/biocore_memory/consolidate/writeback.py +181 -0
  16. package/python/biocore_memory/core/__init__.py +4 -0
  17. package/python/biocore_memory/core/config.py +127 -0
  18. package/python/biocore_memory/core/permissions.py +69 -0
  19. package/python/biocore_memory/core/types.py +116 -0
  20. package/python/biocore_memory/core/util.py +26 -0
  21. package/python/biocore_memory/embedding_local.py +447 -0
  22. package/python/biocore_memory/embedding_mode.py +156 -0
  23. package/python/biocore_memory/engine.py +2476 -0
  24. package/python/biocore_memory/engine_helpers.py +266 -0
  25. package/python/biocore_memory/event/__init__.py +3 -0
  26. package/python/biocore_memory/event/_types.py +16 -0
  27. package/python/biocore_memory/event/consistency.py +117 -0
  28. package/python/biocore_memory/event/extract.py +48 -0
  29. package/python/biocore_memory/event/media.py +111 -0
  30. package/python/biocore_memory/event/notes.py +85 -0
  31. package/python/biocore_memory/event/notes_link.py +87 -0
  32. package/python/biocore_memory/event/vector.py +69 -0
  33. package/python/biocore_memory/evict/__init__.py +3 -0
  34. package/python/biocore_memory/evict/adaptive.py +73 -0
  35. package/python/biocore_memory/evict/consequence.py +58 -0
  36. package/python/biocore_memory/evict/heat.py +54 -0
  37. package/python/biocore_memory/evict/inhibition.py +105 -0
  38. package/python/biocore_memory/evict/policy.py +188 -0
  39. package/python/biocore_memory/evict/score.py +23 -0
  40. package/python/biocore_memory/identity/__init__.py +6 -0
  41. package/python/biocore_memory/identity/api.py +99 -0
  42. package/python/biocore_memory/identity/history.py +9 -0
  43. package/python/biocore_memory/identity/ledger.py +191 -0
  44. package/python/biocore_memory/identity/narrative.py +358 -0
  45. package/python/biocore_memory/identity/snapshot.py +13 -0
  46. package/python/biocore_memory/identity/writeback.py +61 -0
  47. package/python/biocore_memory/index/__init__.py +3 -0
  48. package/python/biocore_memory/index/cue_index.py +80 -0
  49. package/python/biocore_memory/index/entities.py +85 -0
  50. package/python/biocore_memory/index/entity_boost.py +104 -0
  51. package/python/biocore_memory/index/graph.py +172 -0
  52. package/python/biocore_memory/recall/__init__.py +5 -0
  53. package/python/biocore_memory/recall/api.py +65 -0
  54. package/python/biocore_memory/recall/bm25.py +118 -0
  55. package/python/biocore_memory/recall/channels.py +98 -0
  56. package/python/biocore_memory/recall/complexity.py +62 -0
  57. package/python/biocore_memory/recall/constraints.py +75 -0
  58. package/python/biocore_memory/recall/embedding.py +745 -0
  59. package/python/biocore_memory/recall/expand.py +26 -0
  60. package/python/biocore_memory/recall/filters.py +40 -0
  61. package/python/biocore_memory/recall/fusion.py +18 -0
  62. package/python/biocore_memory/recall/mmr.py +70 -0
  63. package/python/biocore_memory/recall/ppr.py +85 -0
  64. package/python/biocore_memory/recall/retrieval_practice.py +102 -0
  65. package/python/biocore_memory/relay/__init__.py +2 -0
  66. package/python/biocore_memory/relay/card.py +51 -0
  67. package/python/biocore_memory/slots/__init__.py +18 -0
  68. package/python/biocore_memory/slots/admission.py +64 -0
  69. package/python/biocore_memory/slots/boundary.py +128 -0
  70. package/python/biocore_memory/slots/evict_bridge.py +106 -0
  71. package/python/biocore_memory/slots/registry.py +135 -0
  72. package/python/biocore_memory/slots/switch.py +42 -0
  73. package/python/biocore_memory/store/__init__.py +4 -0
  74. package/python/biocore_memory/store/audit.py +74 -0
  75. package/python/biocore_memory/store/compress.py +81 -0
  76. package/python/biocore_memory/store/dedupe.py +178 -0
  77. package/python/biocore_memory/store/json_store.py +154 -0
  78. package/python/biocore_memory/store/media.py +145 -0
  79. package/python/biocore_memory/store/provenance.py +73 -0
  80. package/python/biocore_memory/store/rate_limit.py +93 -0
  81. package/python/biocore_memory/store/security.py +107 -0
  82. package/python/biocore_memory/store/source_consistency.py +123 -0
  83. package/python/biocore_memory/store/tiering.py +61 -0
  84. package/python/biocore_memory/store/tiering_exec.py +50 -0
  85. package/python/biocore_memory/store/tombstone.py +23 -0
  86. package/python/biocore_memory/store/versions.py +69 -0
  87. package/python/biocore_memory/thread/__init__.py +2 -0
  88. package/python/biocore_memory/thread/card.py +58 -0
  89. package/python/biocore_model/__init__.py +37 -0
  90. package/python/biocore_model/affective_labor.py +162 -0
  91. package/python/biocore_model/appraisal.py +257 -0
  92. package/python/biocore_model/appraisal_action.py +78 -0
  93. package/python/biocore_model/archive_queue.py +60 -0
  94. package/python/biocore_model/attachment_behavior.py +130 -0
  95. package/python/biocore_model/attachment_regulation.py +75 -0
  96. package/python/biocore_model/attachment_state.py +41 -0
  97. package/python/biocore_model/attribution.py +73 -0
  98. package/python/biocore_model/attributor.py +162 -0
  99. package/python/biocore_model/autonomic.py +91 -0
  100. package/python/biocore_model/autonomy_affect.py +51 -0
  101. package/python/biocore_model/autonomy_check.py +67 -0
  102. package/python/biocore_model/autonomy_loop.py +122 -0
  103. package/python/biocore_model/autonomy_mode.py +114 -0
  104. package/python/biocore_model/autonomy_model.py +733 -0
  105. package/python/biocore_model/autonomy_params.py +131 -0
  106. package/python/biocore_model/autonomy_reason.py +73 -0
  107. package/python/biocore_model/autonomy_tick.py +227 -0
  108. package/python/biocore_model/bio_appraisal.py +49 -0
  109. package/python/biocore_model/bio_autonomy_state.py +171 -0
  110. package/python/biocore_model/bio_config.py +221 -0
  111. package/python/biocore_model/bio_const.py +128 -0
  112. package/python/biocore_model/bio_daily.py +267 -0
  113. package/python/biocore_model/bio_emotion.py +538 -0
  114. package/python/biocore_model/bio_emotion2.py +214 -0
  115. package/python/biocore_model/bio_goal.py +220 -0
  116. package/python/biocore_model/bio_identity.py +82 -0
  117. package/python/biocore_model/bio_noise.py +119 -0
  118. package/python/biocore_model/bio_physiology2.py +114 -0
  119. package/python/biocore_model/bio_snapshot.py +324 -0
  120. package/python/biocore_model/bio_state.py +272 -0
  121. package/python/biocore_model/bio_tick.py +310 -0
  122. package/python/biocore_model/bio_util.py +82 -0
  123. package/python/biocore_model/blocks.py +264 -0
  124. package/python/biocore_model/boundary.py +251 -0
  125. package/python/biocore_model/boundary_gate.py +188 -0
  126. package/python/biocore_model/commitment_extractor.py +517 -0
  127. package/python/biocore_model/compassion_fatigue.py +84 -0
  128. package/python/biocore_model/connection_bids.py +120 -0
  129. package/python/biocore_model/core_bridge.py +177 -0
  130. package/python/biocore_model/core_client.py +127 -0
  131. package/python/biocore_model/core_commands.py +112 -0
  132. package/python/biocore_model/core_mode.py +104 -0
  133. package/python/biocore_model/core_mode_ui.mjs +104 -0
  134. package/python/biocore_model/criticality.py +81 -0
  135. package/python/biocore_model/defense_mechanisms.py +272 -0
  136. package/python/biocore_model/delta_budget.py +99 -0
  137. package/python/biocore_model/diag_trace.py +274 -0
  138. package/python/biocore_model/dissonance.py +134 -0
  139. package/python/biocore_model/emotion_geometry.py +108 -0
  140. package/python/biocore_model/emotion_probe.py +209 -0
  141. package/python/biocore_model/entity_graph.py +931 -0
  142. package/python/biocore_model/entity_resolve.py +260 -0
  143. package/python/biocore_model/envy.py +193 -0
  144. package/python/biocore_model/exit_or_silence.py +230 -0
  145. package/python/biocore_model/expr_guard.py +24 -0
  146. package/python/biocore_model/expr_intent.py +77 -0
  147. package/python/biocore_model/expr_state.py +204 -0
  148. package/python/biocore_model/expr_template.py +105 -0
  149. package/python/biocore_model/expr_tone.py +81 -0
  150. package/python/biocore_model/extreme_scenarios.py +205 -0
  151. package/python/biocore_model/feedback_flow.py +227 -0
  152. package/python/biocore_model/feedback_guard.py +129 -0
  153. package/python/biocore_model/feedback_llm.py +163 -0
  154. package/python/biocore_model/feedback_memory.py +96 -0
  155. package/python/biocore_model/feedback_user.py +93 -0
  156. package/python/biocore_model/frustration_curve.py +110 -0
  157. package/python/biocore_model/granularity.py +283 -0
  158. package/python/biocore_model/graph_extract.py +248 -0
  159. package/python/biocore_model/graph_recall.py +221 -0
  160. package/python/biocore_model/intent_render.py +91 -0
  161. package/python/biocore_model/intero_core.py +235 -0
  162. package/python/biocore_model/intero_style.py +116 -0
  163. package/python/biocore_model/layered_memory.py +297 -0
  164. package/python/biocore_model/learning_probe.py +120 -0
  165. package/python/biocore_model/memory_dual.py +224 -0
  166. package/python/biocore_model/memory_forgetting.py +125 -0
  167. package/python/biocore_model/memory_store.py +1399 -0
  168. package/python/biocore_model/metacog.py +153 -0
  169. package/python/biocore_model/migrate.py +733 -0
  170. package/python/biocore_model/migrate_store.py +337 -0
  171. package/python/biocore_model/mind_wander.py +111 -0
  172. package/python/biocore_model/mood.py +48 -0
  173. package/python/biocore_model/narrative.py +172 -0
  174. package/python/biocore_model/needs_esteem.py +116 -0
  175. package/python/biocore_model/needs_events.py +137 -0
  176. package/python/biocore_model/needs_sleep.py +72 -0
  177. package/python/biocore_model/noise_bank.py +95 -0
  178. package/python/biocore_model/persona_flow.mjs +25 -0
  179. package/python/biocore_model/pipeline.py +240 -0
  180. package/python/biocore_model/pipeline_api.py +200 -0
  181. package/python/biocore_model/plugin_config.py +83 -0
  182. package/python/biocore_model/plugin_routes.py +1435 -0
  183. package/python/biocore_model/policy_learning.py +132 -0
  184. package/python/biocore_model/possible_selves.py +110 -0
  185. package/python/biocore_model/predictor.py +178 -0
  186. package/python/biocore_model/preset.py +205 -0
  187. package/python/biocore_model/proactive_context.py +153 -0
  188. package/python/biocore_model/prompt_rules.py +90 -0
  189. package/python/biocore_model/prospective.py +183 -0
  190. package/python/biocore_model/provider_fallback.py +97 -0
  191. package/python/biocore_model/reconstruction.py +132 -0
  192. package/python/biocore_model/regulation_loop.py +179 -0
  193. package/python/biocore_model/regulation_strategies.py +294 -0
  194. package/python/biocore_model/relation_stage.py +130 -0
  195. package/python/biocore_model/relationship_stage.py +95 -0
  196. package/python/biocore_model/render.py +901 -0
  197. package/python/biocore_model/render_guard.mjs +34 -0
  198. package/python/biocore_model/repair_after_boundary.py +350 -0
  199. package/python/biocore_model/reply_limit.py +183 -0
  200. package/python/biocore_model/reply_split.py +203 -0
  201. package/python/biocore_model/rollback.py +143 -0
  202. package/python/biocore_model/runtime.py +479 -0
  203. package/python/biocore_model/sanitize.py +200 -0
  204. package/python/biocore_model/scheduler.py +123 -0
  205. package/python/biocore_model/schema.py +120 -0
  206. package/python/biocore_model/secrecy.py +145 -0
  207. package/python/biocore_model/self_esteem.py +50 -0
  208. package/python/biocore_model/self_intent.py +437 -0
  209. package/python/biocore_model/self_interest.py +263 -0
  210. package/python/biocore_model/self_verification.py +178 -0
  211. package/python/biocore_model/services.py +78 -0
  212. package/python/biocore_model/situation.py +254 -0
  213. package/python/biocore_model/sleep_circadian.py +116 -0
  214. package/python/biocore_model/social_pain.py +69 -0
  215. package/python/biocore_model/social_repair.py +111 -0
  216. package/python/biocore_model/social_theory.py +131 -0
  217. package/python/biocore_model/source_monitoring.py +292 -0
  218. package/python/biocore_model/stages.py +487 -0
  219. package/python/biocore_model/state_tag.py +231 -0
  220. package/python/biocore_model/switch_groups.mjs +201 -0
  221. package/python/biocore_model/tabs_state.mjs +37 -0
  222. package/python/biocore_model/template_expr.py +252 -0
  223. package/python/biocore_model/tools/caps_check.mjs +23 -0
  224. package/python/biocore_model/tools/init_symbols_check.mjs +11 -0
  225. package/python/biocore_model/tools/missing_ids.mjs +16 -0
  226. package/python/biocore_model/tools/scope_check.mjs +19 -0
  227. package/python/biocore_model/topic_control.py +268 -0
  228. package/python/biocore_model/turn_memory.py +152 -0
  229. package/python/biocore_model/ui_compat.mjs +48 -0
  230. package/python/biocore_model/ui_settings.py +266 -0
  231. package/python/biocore_model/ui_settings.py.bak_authority +108 -0
  232. package/python/biocore_model/value_order.py +186 -0
  233. package/python/biocore_model/view_mode.mjs +8 -0
  234. package/python/biocore_model/voice_bank.py +177 -0
  235. package/python/biocore_model/waiting_sense.py +229 -0
  236. package/python/biocore_model/x_directives.py +142 -0
  237. package/python/biocore_runtime/__init__.py +7 -0
  238. package/python/biocore_runtime/pipeline.py +1915 -0
  239. package/python/dsh_adapter/README.md +96 -0
  240. package/python/dsh_adapter/adapter.py +1077 -0
  241. package/python/dsh_adapter/cli.py +107 -0
  242. package/python/dsh_adapter/data/demo_state.json +164 -0
  243. package/python/dsh_adapter/data/verify_state.json +164 -0
  244. package/python/dsh_adapter/tests/__init__.py +1 -0
  245. package/python/dsh_adapter/tests/test_adapter.py +494 -0
  246. package/ui/panel-layout.js +3 -3
  247. package/ui/panel.css +9 -4
  248. package/ui/panel.html +7 -7
  249. package/ui/panel.js +9 -1
  250. package/lib/py/__pycache__/bridge.cpython-310.pyc +0 -0
package/README.md CHANGED
@@ -206,7 +206,7 @@ DSH `session/event` hook 每轮调用一次 `one_turn`,另有 `/bio sync <一
206
206
 
207
207
  ```powershell
208
208
  cd C:\Users\Administrator\Desktop\deepseek
209
- dsh plugin --profile web add file:C:/Users/Administrator/Desktop/deepseek/dsh-biocore-ui
209
+ dsh plugin --profile web add file:<path-to-dsh-biocore>
210
210
  # 再把 "dsh-biocore-ui" 追加进 profile 的 package.json dsh.profile.bundles
211
211
  dsh --profile web --port 3080
212
212
  ```
@@ -250,7 +250,7 @@ dsh --profile web-test --port 3082 --no-open
250
250
  - `<dataDir>/flags.json` 四个开关
251
251
 
252
252
  其它环境变量:`BIOCORE_ADAPTER_DIR`(adapter 目录,默认
253
- `<home>/Desktop/deepseek/dsh_adapter`)、`BIOCORE_PYTHON`(默认 `python`)。
253
+ `<pkg>/python/dsh_adapter`)、`BIOCORE_PYTHON`(默认 `python`)。
254
254
  端口/启停仍由 profile 管理。
255
255
 
256
256
  部署(`scripts\deploy_dsh_ui.ps1` 会同步 lib/package/cordis 并跑 selftest):
package/lib/py/bridge.py CHANGED
@@ -22,14 +22,19 @@ import sys
22
22
  import time
23
23
  from pathlib import Path
24
24
 
25
+ # Package root: <pkg>/lib/py/bridge.py -> <pkg>; the python core ships inside
26
+ # <pkg>/python so an npm install works without the AstrBot checkout.
27
+ _PACKAGE_ROOT = Path(__file__).resolve().parents[2]
28
+ _BUNDLED_PY = _PACKAGE_ROOT / "python"
25
29
  _ADAPTER_DIR = Path(os.environ.get("BIOCORE_ADAPTER_DIR")
26
- or (Path.home() / "Desktop" / "deepseek" / "dsh_adapter"))
27
- _PLUGIN_DIR = Path(os.environ.get("BIOCORE_PLUGIN_DIR")
28
- or (Path.home() / "Desktop" / "deepseek"
29
- / "astrbot_plugin_biocore_standalone"))
30
- _MIND_DIR = Path(os.environ.get("BIOCORE_MIND_DIR")
31
- or (Path.home() / "Desktop" / "deepseek"))
30
+ or (_BUNDLED_PY / "dsh_adapter"))
31
+ _PLUGIN_DIR = Path(os.environ.get("BIOCORE_PLUGIN_DIR") or _BUNDLED_PY)
32
+ _MIND_DIR = Path(os.environ.get("BIOCORE_MIND_DIR") or _BUNDLED_PY)
32
33
  sys.path.insert(0, str(_ADAPTER_DIR))
34
+ if str(_PLUGIN_DIR) not in sys.path:
35
+ sys.path.insert(0, str(_PLUGIN_DIR))
36
+ if str(_MIND_DIR) not in sys.path:
37
+ sys.path.insert(0, str(_MIND_DIR))
33
38
 
34
39
  PERSONA_TEXT_LIMIT = 20000
35
40
  PERSONA_FIELDS = (
@@ -139,7 +144,10 @@ def _persist_persona(state_file, profile, version):
139
144
  def _compile_profile(text):
140
145
  if str(_MIND_DIR) not in sys.path:
141
146
  sys.path.insert(0, str(_MIND_DIR))
142
- from mindbase.persona_compile import compile_persona # noqa: E402
147
+ try: # optional: bundled builds ship the deterministic compiler only
148
+ from mindbase.persona_compile import compile_persona # noqa: E402
149
+ except Exception: # pragma: no cover - persona compile degrades
150
+ compile_persona = None
143
151
  from mindbase.persona_bridge import normalize # noqa: E402
144
152
  persona = compile_persona(text)
145
153
  return normalize(persona.numbers())
package/lib/py-bridge.js CHANGED
@@ -5,7 +5,9 @@ import { homedir } from 'node:os'
5
5
  import { join } from 'node:path'
6
6
 
7
7
  export const BRIDGE_PATH = fileURLToPath(new URL('./py/bridge.py', import.meta.url))
8
- export const DEFAULT_ADAPTER_DIR = join(homedir(), 'Desktop', 'deepseek', 'dsh_adapter')
8
+ export const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url))
9
+ export const BUNDLED_PYTHON_DIR = join(PACKAGE_ROOT, 'python')
10
+ export const DEFAULT_ADAPTER_DIR = join(BUNDLED_PYTHON_DIR, 'dsh_adapter')
9
11
  export const BRIDGE_TIMEOUT_MS = 20000
10
12
 
11
13
  export function adapterDir() {
@@ -13,8 +15,7 @@ export function adapterDir() {
13
15
  }
14
16
 
15
17
  export function pluginDir() {
16
- return process.env.BIOCORE_PLUGIN_DIR || join(
17
- homedir(), 'Desktop', 'deepseek', 'astrbot_plugin_biocore_standalone')
18
+ return process.env.BIOCORE_PLUGIN_DIR || BUNDLED_PYTHON_DIR
18
19
  }
19
20
 
20
21
  export function pythonCommand() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-biocore",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "BioCore DSH UI: per-session state tab, standalone instrument panel, /api/biocore/state, session/event one_turn updates, /bio sync.",
6
6
  "main": "lib/index.js",
@@ -8,7 +8,8 @@
8
8
  "lib",
9
9
  "cordis.patch.yml",
10
10
  "README.md",
11
- "ui"
11
+ "ui",
12
+ "python"
12
13
  ],
13
14
  "dsh": {
14
15
  "bundle": {
@@ -0,0 +1,21 @@
1
+ # OWNER: engine-core
2
+ # -*- coding: utf-8 -*-
3
+ """BioCore memory layer (pure Python; no astrbot, no prompt text).
4
+
5
+ Public API: MemoryEngine, MemoryStore, MemoryEvent, recall_entries,
6
+ eviction_score, identity_snapshot, relay_card, thread_card.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from .engine import MemoryEngine
11
+ from .store import MemoryStore
12
+ from .event._types import MemoryEvent
13
+ from .recall import recall_entries
14
+ from .evict import eviction_score
15
+ from .identity import identity_snapshot
16
+ from .relay import relay_card
17
+ from .thread import thread_card
18
+ from . import identity, index # noqa: F401 (compatibility for callers)
19
+
20
+ __all__ = ["MemoryEngine", "MemoryStore", "MemoryEvent", "recall_entries",
21
+ "eviction_score", "identity_snapshot", "relay_card", "thread_card"]
@@ -0,0 +1,2 @@
1
+ # OWNER: legacy
2
+ from .astrbot import AstrBotMemoryAdapter # noqa
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Thin AstrBot adapter (callbacks only; never imports astrbot)."""
3
+ from __future__ import annotations
4
+
5
+
6
+ class AstrBotMemoryAdapter:
7
+ def __init__(self, engine, session="", embed=None, now=None):
8
+ self.engine = engine
9
+ self.session = str(session or "")
10
+ self.embed = embed
11
+ self.now = now
12
+
13
+ def remember(self, text, role="user", vector=None, importance=0.5):
14
+ from ..event.extract import from_message
15
+ event = from_message(text, role=role, session=self.session,
16
+ ts=self.now() if callable(self.now) else self.now,
17
+ vector=vector, importance=importance)
18
+ return self.engine.remember(event)
19
+
20
+ def recall(self, query, budget=3, vector=None):
21
+ return self.engine.recall(query, budget=budget, vector=vector)
22
+
23
+ def embed_text(self, text):
24
+ return self.embed(text) if callable(self.embed) else None
@@ -0,0 +1,335 @@
1
+ # -*- coding: utf-8 -*-
2
+ """DSH adapter CLI for the host-neutral memory engine.
3
+
4
+ Standard library only; never imports astrbot, never renders a prompt.
5
+ Run with: python -m biocore_memory.adapters.dsh_cli <command> ...
6
+ All output is JSON. Global --store selects the data directory
7
+ (default ./data/memory_store, engine file <store>/memory.json).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import re
14
+ import sys
15
+ import time
16
+ from pathlib import Path
17
+
18
+
19
+ def _engine_class():
20
+ try:
21
+ from ..engine import MemoryEngine # package import
22
+ except (ImportError, ValueError):
23
+ root = Path(__file__).resolve().parents[2]
24
+ if str(root) not in sys.path:
25
+ sys.path.insert(0, str(root))
26
+ from biocore_memory.engine import MemoryEngine
27
+ return MemoryEngine
28
+
29
+
30
+ def _emit(payload, code=0):
31
+ print(json.dumps(payload, ensure_ascii=False, indent=1))
32
+ return code
33
+
34
+
35
+ def _parse_since(value):
36
+ """Parse 24h/7d/30m/3600s to seconds; None when absent/invalid."""
37
+ if not value:
38
+ return None
39
+ m = re.fullmatch(r"\s*(\d+)([smhd])\s*", str(value).lower())
40
+ if not m:
41
+ return None
42
+ unit = {"s": 1, "m": 60, "h": 3600, "d": 86400}[m.group(2)]
43
+ return int(m.group(1)) * unit
44
+
45
+
46
+ def dispatch(engine, action, **kwargs):
47
+ """Compatibility dispatcher shared by DSH callers (JSON-safe results)."""
48
+ if action == "remember":
49
+ return engine.remember(kwargs.get("event") or kwargs)
50
+ if action == "recall":
51
+ return engine.recall(kwargs.get("query", ""),
52
+ budget=kwargs.get("budget", 3),
53
+ vector=kwargs.get("vector"))
54
+ if action == "forget":
55
+ return engine.forget(kwargs.get("entry_id", ""))
56
+ if action == "pin":
57
+ return engine.pin(kwargs.get("entry_id", ""),
58
+ bool(kwargs.get("pinned", True)))
59
+ return {"error": "unknown_action"}
60
+
61
+
62
+ def _compact(entry):
63
+ return {k: entry.get(k) for k in ("id", "text", "role", "ts", "hits",
64
+ "importance", "pin", "tags", "source")}
65
+
66
+
67
+ def cmd_remember(args):
68
+ engine = _engine_class()(data_dir=args.store)
69
+ tags = [t.strip() for t in str(args.tags or "").split(",") if t.strip()]
70
+ worth = {}
71
+ if getattr(args, "worth", None):
72
+ try:
73
+ raw = str(args.worth).strip()
74
+ try:
75
+ worth = json.loads(raw)
76
+ except Exception:
77
+ worth = json.loads(raw.replace("'", '"'))
78
+ if not isinstance(worth, dict):
79
+ raise ValueError("worth must be a JSON object")
80
+ if not isinstance(worth, dict):
81
+ raise ValueError("worth must be a JSON object")
82
+ except Exception as exc:
83
+ return _emit({"ok": False, "command": "remember",
84
+ "error": "invalid_worth", "detail": str(exc)}, 2)
85
+ for name, key in (("importance", "importance"), ("self_relevance", "self_relevance"),
86
+ ("mainline", "mainline")):
87
+ value = getattr(args, name, None)
88
+ if value is not None:
89
+ worth[key] = float(value)
90
+ meta = {"tags": tags, "source": str(args.source or "user")}
91
+ for key in ("mainline", "routine", "self_defining"):
92
+ if key in worth:
93
+ meta[key] = worth[key]
94
+ event = {"text": args.text, "role": "user", "session": "", "metadata": meta}
95
+ if "importance" in worth:
96
+ try:
97
+ event["importance"] = max(0.0, min(1.0, float(worth["importance"])))
98
+ except Exception:
99
+ pass
100
+ if "self_relevance" in worth:
101
+ try:
102
+ event["self_relevance"] = max(0.0, min(1.0, float(worth["self_relevance"])))
103
+ except Exception:
104
+ pass
105
+ entry = engine.remember(event)
106
+ engine.save()
107
+ if not entry:
108
+ return _emit({"ok": False, "command": "remember",
109
+ "error": "text_too_short_or_invalid"}, 2)
110
+ return _emit({"ok": True, "command": "remember", "entry": _compact(entry)})
111
+
112
+
113
+ def cmd_recall(args):
114
+ engine = _engine_class()(data_dir=args.store)
115
+ rows = engine.recall(args.query, budget=max(1, int(args.limit or 3)),
116
+ mode=getattr(args, 'mode', 'now') or 'now')
117
+ try:
118
+ from ..core.config import MID_TIER
119
+ except ImportError:
120
+ MID_TIER = 0.35
121
+ out = []
122
+ for row in rows or []:
123
+ row = row if isinstance(row, dict) else {}
124
+ entry = row.get("entry") if isinstance(row.get("entry"), dict) else {}
125
+ score = float(row.get("score") or 0.0)
126
+ if score < float(MID_TIER):
127
+ continue
128
+ out.append({"id": entry.get("id"), "text": entry.get("text"),
129
+ "score": score, "mode": row.get("mode"),
130
+ "tier": "expand" if score >= 0.55 else "hint",
131
+ "role": entry.get("role"), "ts": entry.get("ts")})
132
+ return _emit({"ok": True, "command": "recall", "query": args.query,
133
+ "count": len(out), "results": out,
134
+ "hint": "no_cue" if not out else ""})
135
+
136
+
137
+ def cmd_forget(args):
138
+ engine = _engine_class()(data_dir=args.store)
139
+ result = engine.forget(args.entry_id)
140
+ engine.save()
141
+ return _emit({"ok": result is not None, "command": "forget",
142
+ "id": args.entry_id, "result": result})
143
+
144
+
145
+ def cmd_pin(args, pinned):
146
+ engine = _engine_class()(data_dir=args.store)
147
+ result = engine.pin(args.entry_id, pinned)
148
+ engine.save()
149
+ return _emit({"ok": result is not None, "command": "pin" if pinned else "unpin",
150
+ "id": args.entry_id, "pinned": pinned, "result": result})
151
+
152
+
153
+ def cmd_consolidate(args):
154
+ since = _parse_since(args.since)
155
+ return _emit({"ok": True, "command": "consolidate", "noop": True,
156
+ "since_seconds": since,
157
+ "reason": "engine has no consolidate pass; CLI degraded to no-op",
158
+ "hint": "use tick/forget policies or implement engine.consolidate"})
159
+
160
+
161
+ def cmd_checkup(args):
162
+ engine = _engine_class()(data_dir=args.store)
163
+ entries = [e for e in engine.store.entries if isinstance(e, dict)]
164
+ index = engine.build_index()
165
+ now = time.time()
166
+ recent = sorted(entries, key=lambda e: (-float(e.get("ts", 0) or 0),
167
+ -int(e.get("hits", 0) or 0)))[:5]
168
+ actions = []
169
+ if not entries:
170
+ actions.append({"action": "remember", "why": "store is empty", "count": 0})
171
+ if entries and not index.get("entities"):
172
+ actions.append({"action": "rebuild_index", "why": "entity index empty",
173
+ "count": len(entries)})
174
+ singles = [e for e in entries if int(e.get("hits", 1) or 1) <= 1]
175
+ if singles:
176
+ actions.append({"action": "revisit_or_forget",
177
+ "why": "single-hit entries", "count": len(singles)})
178
+ old = [e for e in entries if now - float(e.get("ts", 0) or 0) > 90 * 86400]
179
+ if old:
180
+ actions.append({"action": "eviction_snapshot",
181
+ "why": "entries older than 90d", "count": len(old)})
182
+ report = {"ok": not actions, "command": "checkup",
183
+ "store": str(Path(args.store).resolve() / "memory.json"),
184
+ "entries": len(entries),
185
+ "index": {k: len(v) for k, v in index.items()},
186
+ "recent": [{"id": e.get("id"), "ts": e.get("ts"),
187
+ "hits": e.get("hits")} for e in recent],
188
+ "top_actions": actions[:5]}
189
+ return _emit(report, 1 if actions else 0)
190
+
191
+
192
+ def _flags_path(store):
193
+ return Path(store) / "flags.json"
194
+
195
+
196
+ def cmd_flags(args):
197
+ path = _flags_path(args.store)
198
+ data = {}
199
+ if path.exists():
200
+ try:
201
+ data = json.loads(path.read_text(encoding="utf-8"))
202
+ except Exception:
203
+ data = {}
204
+ if getattr(args, "set", ""):
205
+ for item in str(args.set).split(","):
206
+ if "=" not in item:
207
+ continue
208
+ key, value = item.split("=", 1)
209
+ key, value = key.strip(), value.strip().lower()
210
+ data[key] = value in ("1", "true", "on", "yes")
211
+ path.parent.mkdir(parents=True, exist_ok=True)
212
+ path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
213
+ return _emit({"ok": True, "command": "flags", "path": str(path), "flags": data})
214
+
215
+
216
+ def cmd_import(args):
217
+ engine = _engine_class()(data_dir=args.store)
218
+ try:
219
+ payload = json.loads(Path(args.file).read_text(encoding="utf-8"))
220
+ except Exception as exc:
221
+ return _emit({"ok": False, "command": "import", "error": str(exc)[:120]}, 2)
222
+ result = engine.import_data(payload)
223
+ return _emit({"ok": bool(result.get("ok")), "command": "import", "result": result})
224
+
225
+
226
+ def cmd_export(args):
227
+ engine = _engine_class()(data_dir=args.store)
228
+ payload = engine.export()
229
+ if getattr(args, "out", ""):
230
+ Path(args.out).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
231
+ return _emit({"ok": True, "command": "export", "out": args.out,
232
+ "entries": len(payload.get("entries") or [])})
233
+ return _emit({"ok": True, "command": "export", "payload": payload})
234
+
235
+
236
+ def cmd_stats(args):
237
+ engine = _engine_class()(data_dir=args.store)
238
+ return _emit({"ok": True, "command": "stats", "monitor": engine.monitor()})
239
+
240
+
241
+ def cmd_snapshot(args):
242
+ """Write the read-only UI snapshot and echo it; store is not modified."""
243
+ engine = _engine_class()(data_dir=args.store)
244
+ out = str(getattr(args, "out", "") or "") or str(Path(args.store) / "snapshot.json")
245
+ target = engine.write_snapshot(out)
246
+ if target is None:
247
+ return _emit({"ok": False, "command": "snapshot", "path": out,
248
+ "error": "snapshot_write_failed"}, 2)
249
+ payload = {}
250
+ try:
251
+ payload = json.loads(Path(out).read_text(encoding="utf-8"))
252
+ except Exception:
253
+ payload = {}
254
+ result = {"ok": True, "command": "snapshot", "path": out}
255
+ if isinstance(payload, dict):
256
+ result.update(payload)
257
+ try:
258
+ print(json.dumps(result, ensure_ascii=True, indent=1))
259
+ except Exception:
260
+ print(json.dumps({"ok": True, "command": "snapshot", "path": out}, ensure_ascii=True))
261
+ return 0
262
+
263
+
264
+ def cmd_consolidate_run(args):
265
+ engine = _engine_class()(data_dir=args.store)
266
+ return _emit({"ok": True, "command": "consolidate", "result": engine.consolidate(level=1)})
267
+
268
+
269
+ def build_parser():
270
+ p = argparse.ArgumentParser(prog="biocore-memory",
271
+ description="DSH adapters for BioCore memory")
272
+ p.add_argument("--store", default="./data/memory_store",
273
+ help="memory data directory (default ./data/memory_store)")
274
+ sp = p.add_subparsers(dest="command", required=True)
275
+ r = sp.add_parser("remember", help="store one memory")
276
+ r.add_argument("text")
277
+ r.add_argument("--tags", default="")
278
+ r.add_argument("--source", default="user")
279
+ r.add_argument("--worth", default="",
280
+ help='memory-value JSON from the agent: {"importance":0.7,"self_relevance":0.5,"mainline":0.3}')
281
+ r.add_argument("--importance", type=float, default=None)
282
+ r.add_argument("--self-relevance", dest="self_relevance", type=float, default=None)
283
+ r.add_argument("--mainline", type=float, default=None)
284
+ r.set_defaults(func=cmd_remember)
285
+ r = sp.add_parser("recall", help="query memories")
286
+ r.add_argument("query")
287
+ r.add_argument("--limit", type=int, default=3)
288
+ r.add_argument("--mode", default="now", choices=("now", "then"))
289
+ r.set_defaults(func=cmd_recall)
290
+ r = sp.add_parser("forget", help="delete by id")
291
+ r.add_argument("entry_id")
292
+ r.set_defaults(func=cmd_forget)
293
+ r = sp.add_parser("pin", help="pin by id")
294
+ r.add_argument("entry_id")
295
+ r.set_defaults(func=lambda a: cmd_pin(a, True))
296
+ r = sp.add_parser("unpin", help="unpin by id")
297
+ r.add_argument("entry_id")
298
+ r.set_defaults(func=lambda a: cmd_pin(a, False))
299
+ r = sp.add_parser("consolidate", help="degraded no-op report")
300
+ r.add_argument("--since", default="24h")
301
+ r.set_defaults(func=cmd_consolidate)
302
+ r = sp.add_parser("checkup", help="health report; exit!=0 if actions")
303
+ r.set_defaults(func=cmd_checkup)
304
+ r = sp.add_parser("flags", help="list or set module flags (flags.json)")
305
+ r.add_argument("--set", default="")
306
+ r.set_defaults(func=cmd_flags)
307
+ r = sp.add_parser("import", help="import entries/tombstones from JSON")
308
+ r.add_argument("--file", required=True)
309
+ r.set_defaults(func=cmd_import)
310
+ r = sp.add_parser("export", help="export store JSON")
311
+ r.add_argument("--out", default="")
312
+ r.set_defaults(func=cmd_export)
313
+ r = sp.add_parser("stats", help="engine monitor counters")
314
+ r.set_defaults(func=cmd_stats)
315
+ r = sp.add_parser("snapshot",
316
+ help="write <store>/snapshot.json and echo it (read-only)")
317
+ r.add_argument("--out", default="",
318
+ help="snapshot path (default <store>/snapshot.json)")
319
+ r.set_defaults(func=cmd_snapshot)
320
+ r = sp.add_parser("consolidate-run", help="run gated consolidation")
321
+ r.set_defaults(func=cmd_consolidate_run)
322
+ return p
323
+
324
+
325
+ def main(argv=None):
326
+ args = build_parser().parse_args(argv)
327
+ try:
328
+ return int(args.func(args) or 0)
329
+ except Exception as exc: # JSON error, never traceback for CLI consumers
330
+ return _emit({"ok": False, "command": args.command,
331
+ "error": str(exc)[:400]}, 2)
332
+
333
+
334
+ if __name__ == "__main__":
335
+ raise SystemExit(main())