dsh-biocore 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/py/bridge.py +15 -7
- package/lib/py-bridge.js +4 -3
- package/package.json +3 -2
- package/python/biocore_memory/__init__.py +21 -0
- package/python/biocore_memory/adapters/__init__.py +2 -0
- package/python/biocore_memory/adapters/astrbot.py +24 -0
- package/python/biocore_memory/adapters/dsh_cli.py +335 -0
- package/python/biocore_memory/co_recall.py +324 -0
- package/python/biocore_memory/consolidate/__init__.py +11 -0
- package/python/biocore_memory/consolidate/gated.py +104 -0
- package/python/biocore_memory/consolidate/neighbors.py +68 -0
- package/python/biocore_memory/consolidate/reflection.py +45 -0
- package/python/biocore_memory/consolidate/tree.py +95 -0
- package/python/biocore_memory/consolidate/writeback.py +181 -0
- package/python/biocore_memory/core/__init__.py +4 -0
- package/python/biocore_memory/core/config.py +127 -0
- package/python/biocore_memory/core/permissions.py +69 -0
- package/python/biocore_memory/core/types.py +116 -0
- package/python/biocore_memory/core/util.py +26 -0
- package/python/biocore_memory/embedding_local.py +447 -0
- package/python/biocore_memory/embedding_mode.py +156 -0
- package/python/biocore_memory/engine.py +2476 -0
- package/python/biocore_memory/engine_helpers.py +266 -0
- package/python/biocore_memory/event/__init__.py +3 -0
- package/python/biocore_memory/event/_types.py +16 -0
- package/python/biocore_memory/event/consistency.py +117 -0
- package/python/biocore_memory/event/extract.py +48 -0
- package/python/biocore_memory/event/media.py +111 -0
- package/python/biocore_memory/event/notes.py +85 -0
- package/python/biocore_memory/event/notes_link.py +87 -0
- package/python/biocore_memory/event/vector.py +69 -0
- package/python/biocore_memory/evict/__init__.py +3 -0
- package/python/biocore_memory/evict/adaptive.py +73 -0
- package/python/biocore_memory/evict/consequence.py +58 -0
- package/python/biocore_memory/evict/heat.py +54 -0
- package/python/biocore_memory/evict/inhibition.py +105 -0
- package/python/biocore_memory/evict/policy.py +188 -0
- package/python/biocore_memory/evict/score.py +23 -0
- package/python/biocore_memory/identity/__init__.py +6 -0
- package/python/biocore_memory/identity/api.py +99 -0
- package/python/biocore_memory/identity/history.py +9 -0
- package/python/biocore_memory/identity/ledger.py +191 -0
- package/python/biocore_memory/identity/narrative.py +358 -0
- package/python/biocore_memory/identity/snapshot.py +13 -0
- package/python/biocore_memory/identity/writeback.py +61 -0
- package/python/biocore_memory/index/__init__.py +3 -0
- package/python/biocore_memory/index/cue_index.py +80 -0
- package/python/biocore_memory/index/entities.py +85 -0
- package/python/biocore_memory/index/entity_boost.py +104 -0
- package/python/biocore_memory/index/graph.py +172 -0
- package/python/biocore_memory/recall/__init__.py +5 -0
- package/python/biocore_memory/recall/api.py +65 -0
- package/python/biocore_memory/recall/bm25.py +118 -0
- package/python/biocore_memory/recall/channels.py +98 -0
- package/python/biocore_memory/recall/complexity.py +62 -0
- package/python/biocore_memory/recall/constraints.py +75 -0
- package/python/biocore_memory/recall/embedding.py +745 -0
- package/python/biocore_memory/recall/expand.py +26 -0
- package/python/biocore_memory/recall/filters.py +40 -0
- package/python/biocore_memory/recall/fusion.py +18 -0
- package/python/biocore_memory/recall/mmr.py +70 -0
- package/python/biocore_memory/recall/ppr.py +85 -0
- package/python/biocore_memory/recall/retrieval_practice.py +102 -0
- package/python/biocore_memory/relay/__init__.py +2 -0
- package/python/biocore_memory/relay/card.py +51 -0
- package/python/biocore_memory/slots/__init__.py +18 -0
- package/python/biocore_memory/slots/admission.py +64 -0
- package/python/biocore_memory/slots/boundary.py +128 -0
- package/python/biocore_memory/slots/evict_bridge.py +106 -0
- package/python/biocore_memory/slots/registry.py +135 -0
- package/python/biocore_memory/slots/switch.py +42 -0
- package/python/biocore_memory/store/__init__.py +4 -0
- package/python/biocore_memory/store/audit.py +74 -0
- package/python/biocore_memory/store/compress.py +81 -0
- package/python/biocore_memory/store/dedupe.py +178 -0
- package/python/biocore_memory/store/json_store.py +154 -0
- package/python/biocore_memory/store/media.py +145 -0
- package/python/biocore_memory/store/provenance.py +73 -0
- package/python/biocore_memory/store/rate_limit.py +93 -0
- package/python/biocore_memory/store/security.py +107 -0
- package/python/biocore_memory/store/source_consistency.py +123 -0
- package/python/biocore_memory/store/tiering.py +61 -0
- package/python/biocore_memory/store/tiering_exec.py +50 -0
- package/python/biocore_memory/store/tombstone.py +23 -0
- package/python/biocore_memory/store/versions.py +69 -0
- package/python/biocore_memory/thread/__init__.py +2 -0
- package/python/biocore_memory/thread/card.py +58 -0
- package/python/biocore_model/__init__.py +37 -0
- package/python/biocore_model/affective_labor.py +162 -0
- package/python/biocore_model/appraisal.py +257 -0
- package/python/biocore_model/appraisal_action.py +78 -0
- package/python/biocore_model/archive_queue.py +60 -0
- package/python/biocore_model/attachment_behavior.py +130 -0
- package/python/biocore_model/attachment_regulation.py +75 -0
- package/python/biocore_model/attachment_state.py +41 -0
- package/python/biocore_model/attribution.py +73 -0
- package/python/biocore_model/attributor.py +162 -0
- package/python/biocore_model/autonomic.py +91 -0
- package/python/biocore_model/autonomy_affect.py +51 -0
- package/python/biocore_model/autonomy_check.py +67 -0
- package/python/biocore_model/autonomy_loop.py +122 -0
- package/python/biocore_model/autonomy_mode.py +114 -0
- package/python/biocore_model/autonomy_model.py +733 -0
- package/python/biocore_model/autonomy_params.py +131 -0
- package/python/biocore_model/autonomy_reason.py +73 -0
- package/python/biocore_model/autonomy_tick.py +227 -0
- package/python/biocore_model/bio_appraisal.py +49 -0
- package/python/biocore_model/bio_autonomy_state.py +171 -0
- package/python/biocore_model/bio_config.py +221 -0
- package/python/biocore_model/bio_const.py +128 -0
- package/python/biocore_model/bio_daily.py +267 -0
- package/python/biocore_model/bio_emotion.py +538 -0
- package/python/biocore_model/bio_emotion2.py +214 -0
- package/python/biocore_model/bio_goal.py +220 -0
- package/python/biocore_model/bio_identity.py +82 -0
- package/python/biocore_model/bio_noise.py +119 -0
- package/python/biocore_model/bio_physiology2.py +114 -0
- package/python/biocore_model/bio_snapshot.py +324 -0
- package/python/biocore_model/bio_state.py +272 -0
- package/python/biocore_model/bio_tick.py +310 -0
- package/python/biocore_model/bio_util.py +82 -0
- package/python/biocore_model/blocks.py +264 -0
- package/python/biocore_model/boundary.py +251 -0
- package/python/biocore_model/boundary_gate.py +188 -0
- package/python/biocore_model/commitment_extractor.py +517 -0
- package/python/biocore_model/compassion_fatigue.py +84 -0
- package/python/biocore_model/connection_bids.py +120 -0
- package/python/biocore_model/core_bridge.py +177 -0
- package/python/biocore_model/core_client.py +127 -0
- package/python/biocore_model/core_commands.py +112 -0
- package/python/biocore_model/core_mode.py +104 -0
- package/python/biocore_model/core_mode_ui.mjs +104 -0
- package/python/biocore_model/criticality.py +81 -0
- package/python/biocore_model/defense_mechanisms.py +272 -0
- package/python/biocore_model/delta_budget.py +99 -0
- package/python/biocore_model/diag_trace.py +274 -0
- package/python/biocore_model/dissonance.py +134 -0
- package/python/biocore_model/emotion_geometry.py +108 -0
- package/python/biocore_model/emotion_probe.py +209 -0
- package/python/biocore_model/entity_graph.py +931 -0
- package/python/biocore_model/entity_resolve.py +260 -0
- package/python/biocore_model/envy.py +193 -0
- package/python/biocore_model/exit_or_silence.py +230 -0
- package/python/biocore_model/expr_guard.py +24 -0
- package/python/biocore_model/expr_intent.py +77 -0
- package/python/biocore_model/expr_state.py +204 -0
- package/python/biocore_model/expr_template.py +105 -0
- package/python/biocore_model/expr_tone.py +81 -0
- package/python/biocore_model/extreme_scenarios.py +205 -0
- package/python/biocore_model/feedback_flow.py +227 -0
- package/python/biocore_model/feedback_guard.py +129 -0
- package/python/biocore_model/feedback_llm.py +163 -0
- package/python/biocore_model/feedback_memory.py +96 -0
- package/python/biocore_model/feedback_user.py +93 -0
- package/python/biocore_model/frustration_curve.py +110 -0
- package/python/biocore_model/granularity.py +283 -0
- package/python/biocore_model/graph_extract.py +248 -0
- package/python/biocore_model/graph_recall.py +221 -0
- package/python/biocore_model/intent_render.py +91 -0
- package/python/biocore_model/intero_core.py +235 -0
- package/python/biocore_model/intero_style.py +116 -0
- package/python/biocore_model/layered_memory.py +297 -0
- package/python/biocore_model/learning_probe.py +120 -0
- package/python/biocore_model/memory_dual.py +224 -0
- package/python/biocore_model/memory_forgetting.py +125 -0
- package/python/biocore_model/memory_store.py +1399 -0
- package/python/biocore_model/metacog.py +153 -0
- package/python/biocore_model/migrate.py +733 -0
- package/python/biocore_model/migrate_store.py +337 -0
- package/python/biocore_model/mind_wander.py +111 -0
- package/python/biocore_model/mood.py +48 -0
- package/python/biocore_model/narrative.py +172 -0
- package/python/biocore_model/needs_esteem.py +116 -0
- package/python/biocore_model/needs_events.py +137 -0
- package/python/biocore_model/needs_sleep.py +72 -0
- package/python/biocore_model/noise_bank.py +95 -0
- package/python/biocore_model/persona_flow.mjs +25 -0
- package/python/biocore_model/pipeline.py +240 -0
- package/python/biocore_model/pipeline_api.py +200 -0
- package/python/biocore_model/plugin_config.py +83 -0
- package/python/biocore_model/plugin_routes.py +1435 -0
- package/python/biocore_model/policy_learning.py +132 -0
- package/python/biocore_model/possible_selves.py +110 -0
- package/python/biocore_model/predictor.py +178 -0
- package/python/biocore_model/preset.py +205 -0
- package/python/biocore_model/proactive_context.py +153 -0
- package/python/biocore_model/prompt_rules.py +90 -0
- package/python/biocore_model/prospective.py +183 -0
- package/python/biocore_model/provider_fallback.py +97 -0
- package/python/biocore_model/reconstruction.py +132 -0
- package/python/biocore_model/regulation_loop.py +179 -0
- package/python/biocore_model/regulation_strategies.py +294 -0
- package/python/biocore_model/relation_stage.py +130 -0
- package/python/biocore_model/relationship_stage.py +95 -0
- package/python/biocore_model/render.py +901 -0
- package/python/biocore_model/render_guard.mjs +34 -0
- package/python/biocore_model/repair_after_boundary.py +350 -0
- package/python/biocore_model/reply_limit.py +183 -0
- package/python/biocore_model/reply_split.py +203 -0
- package/python/biocore_model/rollback.py +143 -0
- package/python/biocore_model/runtime.py +479 -0
- package/python/biocore_model/sanitize.py +200 -0
- package/python/biocore_model/scheduler.py +123 -0
- package/python/biocore_model/schema.py +120 -0
- package/python/biocore_model/secrecy.py +145 -0
- package/python/biocore_model/self_esteem.py +50 -0
- package/python/biocore_model/self_intent.py +437 -0
- package/python/biocore_model/self_interest.py +263 -0
- package/python/biocore_model/self_verification.py +178 -0
- package/python/biocore_model/services.py +78 -0
- package/python/biocore_model/situation.py +254 -0
- package/python/biocore_model/sleep_circadian.py +116 -0
- package/python/biocore_model/social_pain.py +69 -0
- package/python/biocore_model/social_repair.py +111 -0
- package/python/biocore_model/social_theory.py +131 -0
- package/python/biocore_model/source_monitoring.py +292 -0
- package/python/biocore_model/stages.py +487 -0
- package/python/biocore_model/state_tag.py +231 -0
- package/python/biocore_model/switch_groups.mjs +201 -0
- package/python/biocore_model/tabs_state.mjs +37 -0
- package/python/biocore_model/template_expr.py +252 -0
- package/python/biocore_model/tools/caps_check.mjs +23 -0
- package/python/biocore_model/tools/init_symbols_check.mjs +11 -0
- package/python/biocore_model/tools/missing_ids.mjs +16 -0
- package/python/biocore_model/tools/scope_check.mjs +19 -0
- package/python/biocore_model/topic_control.py +268 -0
- package/python/biocore_model/turn_memory.py +152 -0
- package/python/biocore_model/ui_compat.mjs +48 -0
- package/python/biocore_model/ui_settings.py +266 -0
- package/python/biocore_model/ui_settings.py.bak_authority +108 -0
- package/python/biocore_model/value_order.py +186 -0
- package/python/biocore_model/view_mode.mjs +8 -0
- package/python/biocore_model/voice_bank.py +177 -0
- package/python/biocore_model/waiting_sense.py +229 -0
- package/python/biocore_model/x_directives.py +142 -0
- package/python/biocore_runtime/__init__.py +7 -0
- package/python/biocore_runtime/pipeline.py +1915 -0
- package/python/dsh_adapter/README.md +96 -0
- package/python/dsh_adapter/adapter.py +1077 -0
- package/python/dsh_adapter/cli.py +107 -0
- package/python/dsh_adapter/data/demo_state.json +164 -0
- package/python/dsh_adapter/data/verify_state.json +164 -0
- package/python/dsh_adapter/tests/__init__.py +1 -0
- package/python/dsh_adapter/tests/test_adapter.py +494 -0
- package/ui/panel-layout.js +3 -3
- package/ui/panel.css +12 -6
- package/ui/panel.html +7 -7
- package/ui/panel.js +9 -1
- 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
|
|
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
|
-
`<
|
|
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 (
|
|
27
|
-
_PLUGIN_DIR = Path(os.environ.get("BIOCORE_PLUGIN_DIR")
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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.
|
|
3
|
+
"version": "0.1.2",
|
|
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,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())
|