dsh-biocore 0.1.3 → 0.1.4

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 (210) hide show
  1. package/lib/index.js +170 -27
  2. package/package.json +1 -1
  3. package/python/biocore_runtime/pipeline.py +42 -0
  4. package/python/biocore_runtime/rerank.py +201 -0
  5. package/python/mindbase/__init__.py +18 -0
  6. package/python/mindbase/__main__.py +3 -0
  7. package/python/mindbase/actions.py +56 -0
  8. package/python/mindbase/active_inference.py +54 -0
  9. package/python/mindbase/addressing.py +69 -0
  10. package/python/mindbase/affect.py +86 -0
  11. package/python/mindbase/affective.py +111 -0
  12. package/python/mindbase/allostasis.py +28 -0
  13. package/python/mindbase/appraisal.py +101 -0
  14. package/python/mindbase/attachment.py +49 -0
  15. package/python/mindbase/attention.py +30 -0
  16. package/python/mindbase/autonomy.py +81 -0
  17. package/python/mindbase/autonomy_bench.py +251 -0
  18. package/python/mindbase/autonomy_evolve.py +342 -0
  19. package/python/mindbase/autonomy_loop.py +65 -0
  20. package/python/mindbase/autonomy_meta.py +91 -0
  21. package/python/mindbase/base_only.py +28 -0
  22. package/python/mindbase/benchmark.py +61 -0
  23. package/python/mindbase/bio_adaptation.py +274 -0
  24. package/python/mindbase/bio_noise.py +39 -0
  25. package/python/mindbase/bio_semantic.py +138 -0
  26. package/python/mindbase/bm25.py +46 -0
  27. package/python/mindbase/bootstrap.py +256 -0
  28. package/python/mindbase/bootstrap_real.py +231 -0
  29. package/python/mindbase/brain.py +178 -0
  30. package/python/mindbase/care.py +78 -0
  31. package/python/mindbase/chaos_search.py +79 -0
  32. package/python/mindbase/character_profile.py +33 -0
  33. package/python/mindbase/circadian.py +38 -0
  34. package/python/mindbase/circuit.py +235 -0
  35. package/python/mindbase/clock.py +53 -0
  36. package/python/mindbase/companion_runtime.py +366 -0
  37. package/python/mindbase/companion_train.py +126 -0
  38. package/python/mindbase/config.py +66 -0
  39. package/python/mindbase/connection_state.py +76 -0
  40. package/python/mindbase/connectome_prior.py +35 -0
  41. package/python/mindbase/connectome_world.py +158 -0
  42. package/python/mindbase/constructs.py +88 -0
  43. package/python/mindbase/context_builder.py +58 -0
  44. package/python/mindbase/critical.py +36 -0
  45. package/python/mindbase/criticality.py +123 -0
  46. package/python/mindbase/defaults.py +27 -0
  47. package/python/mindbase/demo.py +163 -0
  48. package/python/mindbase/diary.py +116 -0
  49. package/python/mindbase/diary_service.py +40 -0
  50. package/python/mindbase/digital_prior.py +37 -0
  51. package/python/mindbase/diversity.py +110 -0
  52. package/python/mindbase/drives.py +106 -0
  53. package/python/mindbase/embedding.py +132 -0
  54. package/python/mindbase/embedding_memory.py +74 -0
  55. package/python/mindbase/embodiment.py +83 -0
  56. package/python/mindbase/emotion_calibration.py +39 -0
  57. package/python/mindbase/emotion_calibrator.py +50 -0
  58. package/python/mindbase/emotion_core.py +113 -0
  59. package/python/mindbase/emotion_dynamics.py +104 -0
  60. package/python/mindbase/emotion_intent.py +164 -0
  61. package/python/mindbase/emotion_layer.py +200 -0
  62. package/python/mindbase/emotion_schema.py +92 -0
  63. package/python/mindbase/engine.py +781 -0
  64. package/python/mindbase/episode_log.py +90 -0
  65. package/python/mindbase/evaluation.py +493 -0
  66. package/python/mindbase/event_appraisal.py +84 -0
  67. package/python/mindbase/evolution.py +77 -0
  68. package/python/mindbase/evolve_offline.py +232 -0
  69. package/python/mindbase/experiments.py +358 -0
  70. package/python/mindbase/expression.py +54 -0
  71. package/python/mindbase/external_brain.py +157 -0
  72. package/python/mindbase/fly_mb.py +159 -0
  73. package/python/mindbase/fly_tasks.py +45 -0
  74. package/python/mindbase/forage.py +179 -0
  75. package/python/mindbase/goal_generator.py +98 -0
  76. package/python/mindbase/goals.py +153 -0
  77. package/python/mindbase/grid_world.py +123 -0
  78. package/python/mindbase/group_autonomy.py +92 -0
  79. package/python/mindbase/group_context.py +67 -0
  80. package/python/mindbase/homeostatic.py +71 -0
  81. package/python/mindbase/homeostatic_action.py +38 -0
  82. package/python/mindbase/human_standard.py +53 -0
  83. package/python/mindbase/identity_suite.py +50 -0
  84. package/python/mindbase/image_context.py +27 -0
  85. package/python/mindbase/impression.py +119 -0
  86. package/python/mindbase/initiative_quota.py +145 -0
  87. package/python/mindbase/insight.py +51 -0
  88. package/python/mindbase/intelligence_search.py +108 -0
  89. package/python/mindbase/intention.py +76 -0
  90. package/python/mindbase/interaction_log.py +30 -0
  91. package/python/mindbase/interoception.py +67 -0
  92. package/python/mindbase/invariants.py +53 -0
  93. package/python/mindbase/iteration.py +29 -0
  94. package/python/mindbase/layered_memory.py +170 -0
  95. package/python/mindbase/layers/__init__.py +18 -0
  96. package/python/mindbase/layers/l0_substrate.py +39 -0
  97. package/python/mindbase/layers/l1_homeostasis.py +6 -0
  98. package/python/mindbase/layers/l2_affect.py +6 -0
  99. package/python/mindbase/layers/l3_attention.py +6 -0
  100. package/python/mindbase/layers/l4_learning.py +17 -0
  101. package/python/mindbase/layers/l5_self_social.py +35 -0
  102. package/python/mindbase/layers/l6_expression.py +23 -0
  103. package/python/mindbase/layers/l7_evaluation.py +45 -0
  104. package/python/mindbase/learner.py +196 -0
  105. package/python/mindbase/lexicon.py +33 -0
  106. package/python/mindbase/life_state.py +169 -0
  107. package/python/mindbase/lifecycle.py +87 -0
  108. package/python/mindbase/liquid_readout.py +48 -0
  109. package/python/mindbase/llm_adapter.py +233 -0
  110. package/python/mindbase/llm_cache.py +77 -0
  111. package/python/mindbase/llm_gate.py +85 -0
  112. package/python/mindbase/llm_openai.py +130 -0
  113. package/python/mindbase/llm_setup.py +99 -0
  114. package/python/mindbase/longrun.py +83 -0
  115. package/python/mindbase/longterm_memory.py +606 -0
  116. package/python/mindbase/mb/__init__.py +14 -0
  117. package/python/mindbase/mb/apl.py +22 -0
  118. package/python/mindbase/mb/dan.py +14 -0
  119. package/python/mindbase/mb/habituation.py +27 -0
  120. package/python/mindbase/mb/kc.py +28 -0
  121. package/python/mindbase/mb/mbon.py +23 -0
  122. package/python/mindbase/mb/mushroom_body.py +116 -0
  123. package/python/mindbase/memory.py +201 -0
  124. package/python/mindbase/memory_framework.py +158 -0
  125. package/python/mindbase/memory_retriever.py +110 -0
  126. package/python/mindbase/memory_summarizer.py +177 -0
  127. package/python/mindbase/memory_task.py +32 -0
  128. package/python/mindbase/memory_trace.py +46 -0
  129. package/python/mindbase/meta.py +32 -0
  130. package/python/mindbase/motivation.py +31 -0
  131. package/python/mindbase/multi_session.py +19 -0
  132. package/python/mindbase/narrative.py +87 -0
  133. package/python/mindbase/needs.py +77 -0
  134. package/python/mindbase/neural.py +45 -0
  135. package/python/mindbase/neuron_models.py +134 -0
  136. package/python/mindbase/noise.py +309 -0
  137. package/python/mindbase/one_on_one.py +60 -0
  138. package/python/mindbase/online_iteration.py +63 -0
  139. package/python/mindbase/ostracism.py +35 -0
  140. package/python/mindbase/parallel_eval.py +60 -0
  141. package/python/mindbase/parallel_qd.py +64 -0
  142. package/python/mindbase/persona.py +221 -0
  143. package/python/mindbase/persona_ab.py +64 -0
  144. package/python/mindbase/persona_bridge.py +129 -0
  145. package/python/mindbase/persona_compile.py +34 -0
  146. package/python/mindbase/persona_dsh.py +120 -0
  147. package/python/mindbase/persona_learner.py +157 -0
  148. package/python/mindbase/physiology.py +197 -0
  149. package/python/mindbase/population_lifecycle.py +83 -0
  150. package/python/mindbase/preferences.py +50 -0
  151. package/python/mindbase/proactive_agent.py +274 -0
  152. package/python/mindbase/proactive_gate.py +53 -0
  153. package/python/mindbase/proactive_group.py +119 -0
  154. package/python/mindbase/proactive_policy.py +422 -0
  155. package/python/mindbase/proactive_profile.py +196 -0
  156. package/python/mindbase/proactive_timing.py +28 -0
  157. package/python/mindbase/pyramid_stages.py +61 -0
  158. package/python/mindbase/quantum.py +132 -0
  159. package/python/mindbase/quantum_chaos.py +98 -0
  160. package/python/mindbase/reflection.py +68 -0
  161. package/python/mindbase/regulation_posture.py +50 -0
  162. package/python/mindbase/relationship_session.py +431 -0
  163. package/python/mindbase/relationship_suite.py +45 -0
  164. package/python/mindbase/relationships.py +70 -0
  165. package/python/mindbase/replay.py +22 -0
  166. package/python/mindbase/reply_diversity.py +53 -0
  167. package/python/mindbase/reply_gate.py +52 -0
  168. package/python/mindbase/rerank.py +41 -0
  169. package/python/mindbase/rerank_local.py +46 -0
  170. package/python/mindbase/ripple.py +187 -0
  171. package/python/mindbase/robust_search.py +68 -0
  172. package/python/mindbase/runtime.py +113 -0
  173. package/python/mindbase/scoped_identity.py +116 -0
  174. package/python/mindbase/self_insight.py +51 -0
  175. package/python/mindbase/self_model.py +116 -0
  176. package/python/mindbase/selftest.py +118 -0
  177. package/python/mindbase/semantic_memory.py +108 -0
  178. package/python/mindbase/session_store.py +175 -0
  179. package/python/mindbase/session_window.py +31 -0
  180. package/python/mindbase/shadow_eval.py +42 -0
  181. package/python/mindbase/sim_env.py +86 -0
  182. package/python/mindbase/social.py +138 -0
  183. package/python/mindbase/social_constraint.py +141 -0
  184. package/python/mindbase/social_feedback.py +95 -0
  185. package/python/mindbase/social_norm.py +225 -0
  186. package/python/mindbase/spiking_reservoir.py +204 -0
  187. package/python/mindbase/spontaneity.py +40 -0
  188. package/python/mindbase/story_loop.py +203 -0
  189. package/python/mindbase/survival_gauntlet.py +128 -0
  190. package/python/mindbase/symbolic.py +170 -0
  191. package/python/mindbase/teacher.py +244 -0
  192. package/python/mindbase/theory.py +42 -0
  193. package/python/mindbase/theory_of_mind.py +98 -0
  194. package/python/mindbase/translation_layer.py +80 -0
  195. package/python/mindbase/turn_eval.py +52 -0
  196. package/python/mindbase/types.py +78 -0
  197. package/python/mindbase/uncertainty_field.py +61 -0
  198. package/python/mindbase/user_loop.py +45 -0
  199. package/python/mindbase/user_profile.py +73 -0
  200. package/python/mindbase/value_model.py +64 -0
  201. package/python/mindbase/values.py +102 -0
  202. package/python/mindbase/vector_store.py +78 -0
  203. package/python/mindbase/viability.py +41 -0
  204. package/python/mindbase/virtual_body.py +90 -0
  205. package/python/mindbase/will.py +60 -0
  206. package/python/mindbase/working_memory.py +32 -0
  207. package/python/mindbase/workspace.py +55 -0
  208. package/ui/panel.css +13 -1
  209. package/ui/panel.html +25 -1
  210. package/ui/panel.js +57 -15
package/lib/index.js CHANGED
@@ -106,6 +106,27 @@ function normalizeEmbedding(value) {
106
106
  }
107
107
  }
108
108
 
109
+ export const RERANK_DEFAULTS = Object.freeze({
110
+ enabled: false,
111
+ base_url: 'https://api.jina.ai/v1',
112
+ model: 'jina-reranker-v2-base-multilingual',
113
+ api_key: '',
114
+ top_n: 20,
115
+ })
116
+
117
+ function normalizeRerank(value) {
118
+ const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {}
119
+ const rawTop = Number(source.top_n)
120
+ const top = Number.isFinite(rawTop) ? Math.max(1, Math.min(100, Math.round(rawTop))) : RERANK_DEFAULTS.top_n
121
+ return {
122
+ enabled: source.enabled === true,
123
+ base_url: String(source.base_url || RERANK_DEFAULTS.base_url).trim().slice(0, 2000),
124
+ model: String(source.model || RERANK_DEFAULTS.model).trim().slice(0, 200),
125
+ api_key: typeof source.api_key === 'string' ? source.api_key.trim().slice(0, 4000) : '',
126
+ top_n: top,
127
+ }
128
+ }
129
+
109
130
  /** GET must never leak the plaintext key. */
110
131
  export function maskApiKey(key) {
111
132
  const text = String(key || '')
@@ -114,23 +135,14 @@ export function maskApiKey(key) {
114
135
  return text.slice(0, 2) + '••••••' + text.slice(-2)
115
136
  }
116
137
 
117
- function readStoredSettings(dataDir) {
118
- return readConfig(dataDir)
119
- }
120
-
121
- function writeSettingsFile(file, embedding, expression) {
122
- mkdirSync(dirname(file), { recursive: true })
123
- writeFileSync(file, JSON.stringify({ embedding, expression }, null, 2) + '\n', {
124
- encoding: 'utf8',
125
- mode: 0o600,
126
- })
127
- try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
128
- }
129
-
130
138
  function maskedEmbedding(embedding) {
131
139
  return Object.assign({}, embedding, { api_key: maskApiKey(embedding.api_key) })
132
140
  }
133
141
 
142
+ function maskedRerank(rerank) {
143
+ return Object.assign({}, rerank, { api_key: maskApiKey(rerank.api_key) })
144
+ }
145
+
134
146
  function defaultEmbedding() {
135
147
  const embedding = emptyEmbedding()
136
148
  embedding.mode = 'auto'
@@ -144,16 +156,41 @@ function storedEmbedding(stored) {
144
156
  return embedding
145
157
  }
146
158
 
147
- /** GET/POST /api/biocore/settings: embedding + expression. */
159
+ /** Full config.json projection with defaults for all three sections. */
160
+ export function readFullConfig(dataDir) {
161
+ let doc = null
162
+ try {
163
+ doc = JSON.parse(readFileSync(join(String(dataDir || '').trim(), SETTINGS_FILE_NAME), 'utf8'))
164
+ } catch { doc = null }
165
+ const source = doc && typeof doc === 'object' && !Array.isArray(doc) ? doc : {}
166
+ return {
167
+ embedding: storedEmbedding(source),
168
+ rerank: normalizeRerank(source.rerank),
169
+ expression: normalizeExpression(source.expression),
170
+ }
171
+ }
172
+
173
+ function writeFullConfig(file, config) {
174
+ mkdirSync(dirname(file), { recursive: true })
175
+ writeFileSync(file, JSON.stringify({
176
+ embedding: config.embedding,
177
+ rerank: config.rerank,
178
+ expression: config.expression,
179
+ }, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
180
+ try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
181
+ }
182
+
183
+ /** GET/POST /api/biocore/settings: embedding + rerank + expression. */
148
184
  export async function handleSettings(req, res, dataDir) {
149
185
  const paths = settingsPaths(dataDir)
150
186
  const method = String((req && req.method) || 'GET').toUpperCase()
151
187
  if (method === 'GET') {
152
- const stored = readStoredSettings(paths.dir)
188
+ const config = readFullConfig(paths.dir)
153
189
  return sendJson(res, 200, {
154
190
  ok: true,
155
- embedding: maskedEmbedding(storedEmbedding(stored)),
156
- expression: Object.assign({}, EXPRESSION_DEFAULTS, stored.expression || {}),
191
+ embedding: maskedEmbedding(config.embedding),
192
+ rerank: maskedRerank(config.rerank),
193
+ expression: config.expression,
157
194
  restart_required: false,
158
195
  })
159
196
  }
@@ -172,33 +209,133 @@ export async function handleSettings(req, res, dataDir) {
172
209
  if (body.confirm !== true) {
173
210
  return sendJson(res, 400, { ok: false, reason: 'confirm_required' })
174
211
  }
175
- const stored = readStoredSettings(paths.dir)
176
- let embedding = storedEmbedding(stored)
177
- let expression = Object.assign({}, EXPRESSION_DEFAULTS, stored.expression || {})
212
+ const config = readFullConfig(paths.dir)
178
213
  const hasEmbedding = body.embedding && typeof body.embedding === 'object'
214
+ const hasRerank = body.rerank && typeof body.rerank === 'object'
179
215
  const hasExpression = body.expression && typeof body.expression === 'object'
180
- if (!hasEmbedding && !hasExpression) {
216
+ if (!hasEmbedding && !hasRerank && !hasExpression) {
181
217
  return sendJson(res, 400, { ok: false, reason: 'no_settings' })
182
218
  }
183
219
  if (hasEmbedding) {
184
220
  const raw = body.embedding
185
- embedding = normalizeEmbedding(raw)
221
+ const embedding = normalizeEmbedding(raw)
186
222
  if (!embedding.mode) {
187
223
  return sendJson(res, 400, { ok: false, reason: 'bad_mode', allowed: Array.from(EMBEDDING_MODES) })
188
224
  }
189
- if (!('api_key' in raw)) embedding.api_key = storedEmbedding(stored).api_key
225
+ if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
226
+ embedding.api_key = config.embedding.api_key
227
+ }
228
+ config.embedding = embedding
190
229
  }
191
- if (hasExpression) expression = normalizeExpression(body.expression)
192
- writeSettingsFile(paths.file, embedding, expression)
230
+ if (hasRerank) {
231
+ const raw = body.rerank
232
+ const rerank = normalizeRerank(raw)
233
+ if (!('api_key' in raw) || String(raw.api_key || '').startsWith('••')) {
234
+ rerank.api_key = config.rerank.api_key
235
+ }
236
+ config.rerank = rerank
237
+ }
238
+ if (hasExpression) config.expression = normalizeExpression(body.expression)
239
+ writeFullConfig(paths.file, config)
193
240
  return sendJson(res, 200, {
194
241
  ok: true,
195
- embedding: maskedEmbedding(embedding),
196
- expression,
242
+ embedding: maskedEmbedding(config.embedding),
243
+ rerank: maskedRerank(config.rerank),
244
+ expression: config.expression,
197
245
  restart_required: true,
198
246
  hint: '重启 BioCore 生效',
199
247
  })
200
248
  }
201
249
 
250
+ function notConfigured(kind, detail) {
251
+ return { ok: false, kind, reason: 'not_configured', detail }
252
+ }
253
+
254
+ /** One minimal API call; never returns or logs the plaintext key. */
255
+ export async function testApiConnection(kind, config, options) {
256
+ const opts = options || {}
257
+ const fetchImpl = typeof opts.fetchImpl === 'function' ? opts.fetchImpl : globalThis.fetch
258
+ const timeoutMs = Number(opts.timeoutMs) || 6000
259
+ if (typeof fetchImpl !== 'function') return { ok: false, kind, reason: 'fetch_unavailable' }
260
+ let url = ''
261
+ let payload = null
262
+ if (kind === 'embedding') {
263
+ const embedding = config && config.embedding ? config.embedding : {}
264
+ if (embedding.mode === 'off' || embedding.mode === 'local') {
265
+ return notConfigured(kind, 'embedding mode is ' + embedding.mode)
266
+ }
267
+ if (!embedding.base_url || !embedding.model || !embedding.api_key) {
268
+ return notConfigured(kind, 'embedding base_url/model/api_key missing')
269
+ }
270
+ url = String(embedding.base_url).replace(/\/+$/, '') + '/embeddings'
271
+ payload = { model: embedding.model, input: 'ping' }
272
+ } else if (kind === 'rerank') {
273
+ const rerank = config && config.rerank ? config.rerank : {}
274
+ if (rerank.enabled !== true) return notConfigured(kind, 'rerank disabled')
275
+ if (!rerank.base_url || !rerank.model || !rerank.api_key) {
276
+ return notConfigured(kind, 'rerank base_url/model/api_key missing')
277
+ }
278
+ url = String(rerank.base_url).replace(/\/+$/, '') + '/rerank'
279
+ payload = {
280
+ model: rerank.model,
281
+ query: 'ping',
282
+ documents: ['ping'],
283
+ top_n: 1,
284
+ }
285
+ } else {
286
+ return { ok: false, kind: String(kind || ''), reason: 'bad_kind' }
287
+ }
288
+ const key = kind === 'embedding'
289
+ ? String(config.embedding.api_key || '')
290
+ : String(config.rerank.api_key || '')
291
+ const controller = typeof AbortController === 'function' ? new AbortController() : null
292
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null
293
+ const started = Date.now()
294
+ try {
295
+ const response = await fetchImpl(url, {
296
+ method: 'POST',
297
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + key },
298
+ body: JSON.stringify(payload),
299
+ signal: controller ? controller.signal : undefined,
300
+ })
301
+ const status = Number(response && response.status) || 0
302
+ const elapsed = Date.now() - started
303
+ if (response && response.ok) {
304
+ return { ok: true, kind, status_code: status, elapsed_ms: elapsed }
305
+ }
306
+ return { ok: false, kind, reason: 'http_error', status_code: status, elapsed_ms: elapsed }
307
+ } catch (error) {
308
+ const name = error && error.name ? String(error.name) : ''
309
+ return {
310
+ ok: false,
311
+ kind,
312
+ reason: name === 'AbortError' ? 'timeout' : 'network_error',
313
+ elapsed_ms: Date.now() - started,
314
+ }
315
+ } finally {
316
+ if (timer) clearTimeout(timer)
317
+ }
318
+ }
319
+
320
+ /** POST /api/biocore/settings/test {kind:'embedding'|'rerank'}. */
321
+ export async function handleSettingsTest(req, res, options) {
322
+ const opts = options || {}
323
+ const dataDir = opts.dataDir
324
+ const method = String((req && req.method) || 'POST').toUpperCase()
325
+ if (method !== 'POST') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' })
326
+ let body = null
327
+ try { body = await readJsonBody(req) } catch (error) {
328
+ return sendJson(res, 400, { ok: false, reason: (error && error.reason) || 'bad_payload' })
329
+ }
330
+ const kind = String(body && body.kind || '').trim()
331
+ if (kind !== 'embedding' && kind !== 'rerank') {
332
+ return sendJson(res, 400, { ok: false, reason: 'bad_kind', allowed: ['embedding', 'rerank'] })
333
+ }
334
+ const config = readFullConfig(dataDir)
335
+ const result = await testApiConnection(kind, config, { fetchImpl: opts.fetchImpl })
336
+ return sendJson(res, 200, result)
337
+ }
338
+
202
339
  const PERSONA_PREVIEW_FIELDS = [
203
340
  ['E', '外向', 2],
204
341
  ['A', '宜人', 2],
@@ -692,6 +829,12 @@ export function apply(ctx, config) {
692
829
  path: SETTINGS_ROUTE_PATH,
693
830
  handler: (req, res) => handleSettings(req, res, dataDir),
694
831
  }))
832
+ // API connection test (embedding / rerank); never logs the key.
833
+ keep(webServer.register({
834
+ kind: 'exact',
835
+ path: SETTINGS_ROUTE_PATH + '/test',
836
+ handler: (req, res) => handleSettingsTest(req, res, { dataDir }),
837
+ }))
695
838
  })
696
839
  }
697
840
  } catch { /* headless */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-biocore",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",
@@ -91,6 +91,11 @@ try:
91
91
  except Exception: # pragma: no cover
92
92
  _intero_core = None
93
93
 
94
+ try:
95
+ from biocore_runtime import rerank as _rerank_mod
96
+ except Exception: # pragma: no cover
97
+ _rerank_mod = None
98
+
94
99
  try: # self-layer modules (all pure; optional so a stripped checkout still runs)
95
100
  from biocore_model import x_directives as _x_directives
96
101
  from biocore_model import self_intent as _self_intent
@@ -634,6 +639,26 @@ def tool_call(name, args, store, session_id=None):
634
639
  row["score"] = score
635
640
  rows.append(row)
636
641
  rows.sort(key=lambda row: (-row["score"], -_num(row.get("ts"), 0.0)))
642
+ rerank_config = args.get("rerank_config")
643
+ if (_rerank_mod is not None and isinstance(rerank_config, dict)
644
+ and rerank_config.get("rerank_enabled")):
645
+ try:
646
+ reranked = _rerank_mod.rerank_documents(
647
+ query, [row.get("text", "") for row in rows], rerank_config)
648
+ if reranked:
649
+ seen = set()
650
+ ordered = []
651
+ for item in reranked:
652
+ index = int(item.get("index", -1))
653
+ if 0 <= index < len(rows) and index not in seen:
654
+ seen.add(index)
655
+ ordered.append(rows[index])
656
+ for index, row in enumerate(rows):
657
+ if index not in seen:
658
+ ordered.append(row)
659
+ rows = ordered
660
+ except Exception:
661
+ pass # rerank is optional; recall keeps the local order
637
662
  return {"ok": True, "count": min(limit, len(rows)),
638
663
  "results": rows[:limit]}
639
664
  target = str(args.get("id") or args.get("query") or "").strip()
@@ -1835,6 +1860,23 @@ def dispatch(action, payload=None):
1835
1860
  current = normalize_switches(data.get("switches"))
1836
1861
  current[key] = bool(data.get("value"))
1837
1862
  return {"ok": True, "switches": current}
1863
+ if action == "recall":
1864
+ query = str(data.get("query") or "").strip()
1865
+ documents = data.get("documents")
1866
+ if not isinstance(documents, list):
1867
+ documents = data.get("docs") if isinstance(data.get("docs"), list) else []
1868
+ fallback = [{"index": index,
1869
+ "document": str((item.get("text") or item.get("document"))
1870
+ if isinstance(item, dict) else (item or "")).strip()}
1871
+ for index, item in enumerate(documents)]
1872
+ if _rerank_mod is None:
1873
+ return {"ok": True, "reranked": False, "results": fallback,
1874
+ "reason": "unavailable"}
1875
+ config = data.get("config") if isinstance(data.get("config"), dict) else data
1876
+ result = _rerank_mod.rerank_documents(query, documents, config)
1877
+ if not result:
1878
+ return {"ok": True, "reranked": False, "results": fallback}
1879
+ return {"ok": True, "reranked": True, "results": result}
1838
1880
  if action == "trace":
1839
1881
  session_id = str(data.get("session_id") or "").strip()
1840
1882
  if not session_id:
@@ -0,0 +1,201 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Optional remote cross-encoder rerank (stdlib only, silent degradation).
3
+
4
+ Config keys (all optional, defaults shown):
5
+ rerank_enabled False
6
+ rerank_base_url ""
7
+ rerank_model ""
8
+ rerank_api_key ""
9
+ rerank_timeout 8.0 seconds
10
+ rerank_top_n 20
11
+
12
+ Two common wire protocols are tried automatically:
13
+ POST {base_url}/rerank {"model", "query", "documents", "top_n"}
14
+ POST {base_url}/v1/rerank same payload
15
+
16
+ A disabled call, missing config, timeout, non-2xx response, bad JSON or an
17
+ invalid result list returns None and records a short reason; callers keep the
18
+ original ordering. Only stdlib urllib is used.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ import math
25
+ import socket
26
+ import urllib.error
27
+ import urllib.request
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ DEFAULTS = {
32
+ "rerank_enabled": False,
33
+ "rerank_base_url": "",
34
+ "rerank_model": "",
35
+ "rerank_api_key": "",
36
+ "rerank_timeout": 8.0,
37
+ "rerank_top_n": 20,
38
+ }
39
+
40
+
41
+ def _num(value, default=0.0):
42
+ try:
43
+ number = float(value)
44
+ except Exception:
45
+ return float(default)
46
+ return number if math.isfinite(number) else float(default)
47
+
48
+
49
+ def _as_bool(value, default=False):
50
+ if isinstance(value, bool):
51
+ return value
52
+ if isinstance(value, (int, float)):
53
+ return bool(value)
54
+ if isinstance(value, str):
55
+ lowered = value.strip().lower()
56
+ if lowered in ("1", "true", "yes", "on"):
57
+ return True
58
+ if lowered in ("0", "false", "no", "off", ""):
59
+ return False
60
+ return bool(default)
61
+
62
+
63
+ def normalize_config(config=None):
64
+ """Merge caller config with defaults and clamp timeout/top_n."""
65
+ data = config if isinstance(config, dict) else {}
66
+ out = dict(DEFAULTS)
67
+ out["rerank_enabled"] = _as_bool(data.get("rerank_enabled"), False)
68
+ for key in ("rerank_base_url", "rerank_model", "rerank_api_key"):
69
+ out[key] = str(data.get(key) or "").strip()
70
+ timeout = _num(data.get("rerank_timeout"), DEFAULTS["rerank_timeout"])
71
+ out["rerank_timeout"] = max(0.1, min(60.0, timeout))
72
+ top_n = int(_num(data.get("rerank_top_n"), DEFAULTS["rerank_top_n"]))
73
+ out["rerank_top_n"] = max(1, min(1000, top_n))
74
+ return out
75
+
76
+
77
+ def _document_text(item):
78
+ if isinstance(item, dict):
79
+ return str(item.get("text") or item.get("document") or "").strip()
80
+ return str(item or "").strip()
81
+
82
+
83
+ def _candidate_urls(base_url):
84
+ base = str(base_url or "").strip().rstrip("/")
85
+ if not base:
86
+ return []
87
+ if base.endswith("/rerank"):
88
+ return [base]
89
+ urls = [base + "/rerank"]
90
+ if not base.endswith("/v1"):
91
+ urls.append(base + "/v1/rerank")
92
+ return urls
93
+
94
+
95
+ def _urllib_transport(url, payload, timeout, headers):
96
+ request = urllib.request.Request(
97
+ url, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
98
+ headers=headers, method="POST")
99
+ with urllib.request.urlopen(request, timeout=timeout) as response:
100
+ status = int(getattr(response, "status", 200) or 200)
101
+ body = response.read()
102
+ return status, body
103
+
104
+
105
+ def _notify(callback, reason):
106
+ try:
107
+ logger.info("[rerank] fallback: %s", reason)
108
+ except Exception:
109
+ pass
110
+ if callable(callback):
111
+ try:
112
+ callback(reason)
113
+ except Exception:
114
+ pass
115
+
116
+
117
+ def rerank_documents(query, documents, config=None, transport=None,
118
+ on_reason=None):
119
+ """Return [{index, document, relevance_score}] sorted/truncated, or None."""
120
+ cfg = normalize_config(config)
121
+ if not cfg["rerank_enabled"]:
122
+ _notify(on_reason, "disabled")
123
+ return None
124
+ if not cfg["rerank_base_url"] or not cfg["rerank_model"]:
125
+ _notify(on_reason, "missing_config")
126
+ return None
127
+ text = str(query or "").strip()
128
+ if not text:
129
+ _notify(on_reason, "empty_query")
130
+ return None
131
+ text_docs = []
132
+ for index, item in enumerate(documents or []):
133
+ content = _document_text(item)
134
+ if content:
135
+ text_docs.append((index, content))
136
+ if not text_docs:
137
+ _notify(on_reason, "empty_documents")
138
+ return None
139
+ top_n = min(cfg["rerank_top_n"], len(text_docs))
140
+ payload = {
141
+ "model": cfg["rerank_model"],
142
+ "query": text,
143
+ "documents": [content for _index, content in text_docs],
144
+ "top_n": top_n,
145
+ }
146
+ headers = {"Content-Type": "application/json"}
147
+ if cfg["rerank_api_key"]:
148
+ headers["Authorization"] = "Bearer " + cfg["rerank_api_key"]
149
+ call = transport or _urllib_transport
150
+ reason = "failed"
151
+ for url in _candidate_urls(cfg["rerank_base_url"]):
152
+ try:
153
+ status, body = call(url, payload, cfg["rerank_timeout"], headers)
154
+ except (TimeoutError, socket.timeout):
155
+ _notify(on_reason, "timeout")
156
+ return None
157
+ except urllib.error.HTTPError as error:
158
+ reason = "http_%s" % getattr(error, "code", "error")
159
+ continue
160
+ except Exception:
161
+ _notify(on_reason, "request_failed")
162
+ return None
163
+ try:
164
+ status = int(status)
165
+ except Exception:
166
+ status = 0
167
+ if status < 200 or status >= 300:
168
+ reason = "http_%s" % status
169
+ continue
170
+ try:
171
+ if isinstance(body, bytes):
172
+ body = body.decode("utf-8", "replace")
173
+ data = json.loads(body)
174
+ except Exception:
175
+ reason = "bad_json"
176
+ continue
177
+ results = data.get("results") if isinstance(data, dict) else None
178
+ if not isinstance(results, list):
179
+ reason = "bad_results"
180
+ continue
181
+ parsed = []
182
+ by_index = dict(text_docs)
183
+ for item in results:
184
+ if not isinstance(item, dict):
185
+ continue
186
+ try:
187
+ index = int(item.get("index"))
188
+ score = float(item.get("relevance_score"))
189
+ except Exception:
190
+ continue
191
+ if index not in by_index or not math.isfinite(score):
192
+ continue
193
+ parsed.append({"index": index, "document": by_index[index],
194
+ "relevance_score": round(score, 6)})
195
+ if not parsed:
196
+ reason = "empty_results"
197
+ continue
198
+ parsed.sort(key=lambda row: (-row["relevance_score"], row["index"]))
199
+ return parsed[:top_n]
200
+ _notify(on_reason, reason)
201
+ return None
@@ -0,0 +1,18 @@
1
+ """mindbase: a layered, paper-grounded base for a human-like agent.
2
+
3
+ Layers:
4
+ L0 substrate/noise -> noise.py
5
+ L1 drives/allostasis -> drives.py, clock.py
6
+ L2 affect/appraisal -> appraisal.py, affect.py
7
+ L3 attention/WM -> attention.py, working_memory.py
8
+ L4 memory -> memory.py
9
+ L5 social/persona/self/meta/autonomy -> social.py, persona.py, self_model.py, meta.py, autonomy.py
10
+ L6 expression -> (not implemented; LLM adapter later)
11
+ """
12
+
13
+ from .config import MindConfig
14
+ from .actions import Action
15
+ from .types import Event, Appraisal, Outcome, Feedback, LLMRequest
16
+ from .engine import MindBase
17
+
18
+ __all__ = ["MindConfig", "Event", "Appraisal", "Outcome", "LLMRequest", "Feedback", "Action", "MindBase"]
@@ -0,0 +1,3 @@
1
+ from .demo import main
2
+
3
+ main()
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ ACTION_KINDS = (
6
+ "communicate",
7
+ "approach",
8
+ "avoid",
9
+ "explore",
10
+ "rest",
11
+ "repair",
12
+ "protect",
13
+ "seek_help",
14
+ "give_help",
15
+ "withdraw",
16
+ "appraise",
17
+ "forage",
18
+ "drink",
19
+ "heal",
20
+ )
21
+ MODALITIES = ("text", "voice", "gesture", "none")
22
+
23
+ STYLE_TO_KIND = {
24
+ "speak": "communicate",
25
+ "silence": "withdraw",
26
+ "ask": "seek_help",
27
+ "repair": "repair",
28
+ "avoid": "avoid",
29
+ "approach": "approach",
30
+ "appraise": "appraise",
31
+ "forage": "forage",
32
+ "rest": "rest",
33
+ "socialize": "approach",
34
+ "drink": "drink",
35
+ "heal": "heal",
36
+ }
37
+
38
+
39
+ def kind_for_style(style: str) -> str:
40
+ return STYLE_TO_KIND.get(str(style or "").lower(), "communicate")
41
+
42
+
43
+ @dataclass
44
+ class Action:
45
+ """A domain-general action, not a chat token.
46
+
47
+ kind: what the base does
48
+ modality: how it is expressed (chat is only communicate/text)
49
+ style: optional sub-style for language (speak/repair/avoid...)
50
+ """
51
+ kind: str = "communicate"
52
+ modality: str = "text"
53
+ style: str = ""
54
+
55
+ def as_dict(self) -> dict:
56
+ return {"kind": self.kind, "modality": self.modality, "style": self.style}
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def expected_free_energy(load: float, pred_err: float, social: float, energy: float, action: str, activity: float = 0.0) -> float:
7
+ """Expected free energy for a binary action (speak/silence).
8
+
9
+ risk = expected allostatic deviation after action
10
+ ambiguity= self-prediction uncertainty
11
+ cost = action cost
12
+ """
13
+ if action == "speak":
14
+ risk = max(0.0, load - 0.10 * social)
15
+ cost = 0.05 + 0.05 * max(0.0, 0.3 - energy) + 0.10 * max(0.0, float(activity))
16
+ else:
17
+ risk = load + 0.08 * social
18
+ cost = 0.02
19
+ ambiguity = pred_err * pred_err
20
+ return risk + 0.5 * ambiguity + cost
21
+
22
+
23
+ def choose(load: float, pred_err: float, social: float, energy: float) -> Dict[str, object]:
24
+ speak = expected_free_energy(load, pred_err, social, energy, "speak")
25
+ silence = expected_free_energy(load, pred_err, social, energy, "silence")
26
+ action = "speak" if speak < silence else "silence"
27
+ return {"action": action, "efe_speak": round(speak, 4), "efe_silence": round(silence, 4)}
28
+
29
+
30
+ class ActiveInference:
31
+ def __init__(self):
32
+ self.w_risk = 1.0
33
+ self.w_amb = 0.5
34
+ self.w_cost = 1.0
35
+ self.lr = 0.005
36
+
37
+ def choose(self, load: float, pred_err: float, social: float, energy: float, activity: float = 0.0, valence: float = 0.0):
38
+ def efe(action: str) -> float:
39
+ if action == "speak":
40
+ risk = max(0.0, load - 0.10 * social - 0.15 * float(valence))
41
+ cost = 0.05 + 0.05 * max(0.0, 0.3 - energy) + 0.10 * max(0.0, float(activity))
42
+ else:
43
+ risk = load + 0.08 * social + 0.10 * float(valence)
44
+ cost = 0.02
45
+ return self.w_risk * risk + self.w_amb * pred_err * pred_err + self.w_cost * cost
46
+ s, q = efe("speak"), efe("silence")
47
+ return {"action": "speak" if s < q else "silence", "efe_speak": round(s, 4), "efe_silence": round(q, 4)}
48
+
49
+ def learn(self, rpe: float, load: float, pred_err: float) -> None:
50
+ self.w_risk = max(0.1, min(3.0, self.w_risk - self.lr * float(rpe) * float(load)))
51
+ self.w_amb = max(0.05, min(3.0, self.w_amb - self.lr * float(rpe) * float(pred_err) ** 2))
52
+
53
+ def snapshot(self) -> dict:
54
+ return {"w_risk": round(self.w_risk, 4), "w_amb": round(self.w_amb, 4), "w_cost": self.w_cost}