dsh-biocore 0.1.5 → 0.1.6

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.
@@ -94,6 +94,17 @@ export function diagnosticsFromState(state, userText) {
94
94
  }
95
95
  }
96
96
 
97
+ /** Final assistant text: core clean_text wins, JS sanitizer is the fallback.
98
+ * sanitizeEnabled=false keeps the original (user disabled the switch). */
99
+ export function chooseCleanReply(original, coreCleanText, options) {
100
+ const opts = options || {}
101
+ const source = String(original == null ? '' : original)
102
+ if (opts.sanitizeEnabled === false) return source
103
+ if (typeof coreCleanText === 'string') return coreCleanText
104
+ const sanitize = typeof opts.sanitize === 'function' ? opts.sanitize : sanitizeReply
105
+ return String(sanitize(source))
106
+ }
107
+
97
108
  export function turnStartPayload(options) {
98
109
  const opts = options || {}
99
110
  const expression = opts.expression && typeof opts.expression === 'object'
@@ -218,6 +229,14 @@ export function makeCoreSessionListener(options) {
218
229
  })
219
230
  const result = await runAction('turn_end', payload)
220
231
  if (!result || result.ok !== true) return
232
+ const reply = result.reply && typeof result.reply === 'object' ? result.reply : {}
233
+ const coreCleanText = typeof reply.clean_text === 'string'
234
+ ? reply.clean_text
235
+ : (typeof result.clean_text === 'string' ? result.clean_text : null)
236
+ const finalText = chooseCleanReply(original, coreCleanText, {
237
+ sanitizeEnabled,
238
+ sanitize: sanitizeReply,
239
+ })
221
240
  const gate = result.gate && typeof result.gate === 'object' ? result.gate : {}
222
241
  const action = stringId(gate.action || 'pass')
223
242
  if ((action === 'replace' || action === 'truncate')
@@ -228,9 +247,9 @@ export function makeCoreSessionListener(options) {
228
247
  + ' blocked=' + (gate.blocked === true ? '1' : '0')
229
248
  + ' rule=' + stringId(gate.rule || '')
230
249
  + ' chars=' + original.length + '->' + gate.text.length)
231
- } else if (cleaned !== original) {
232
- // No gate rewrite: still write the sanitized text back.
233
- applyGateText(event, cleaned)
250
+ } else if (finalText !== original) {
251
+ // No gate rewrite: write the core-cleaned / JS-fallback text back.
252
+ applyGateText(event, finalText)
234
253
  }
235
254
  if (result.trace_file) {
236
255
  logLine(log, CORE_TRACE_PREFIX + ' session=' + sessionId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-biocore",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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",
@@ -96,6 +96,11 @@ try:
96
96
  except Exception: # pragma: no cover
97
97
  _rerank_mod = None
98
98
 
99
+ try:
100
+ from biocore_model import sanitize as _sanitize
101
+ except Exception: # pragma: no cover
102
+ _sanitize = None
103
+
99
104
  try: # self-layer modules (all pure; optional so a stripped checkout still runs)
100
105
  from biocore_model import x_directives as _x_directives
101
106
  from biocore_model import self_intent as _self_intent
@@ -1794,10 +1799,35 @@ def dispatch(action, payload=None):
1794
1799
  return bad("missing session_id")
1795
1800
  if "assistant_text" not in data:
1796
1801
  return bad("missing assistant_text")
1797
- assistant_text = clean_text(data.get("assistant_text"))
1802
+ raw_assistant = clean_text(data.get("assistant_text"))
1803
+ do_sanitize = _as_bool(data.get("sanitize_reply"), True)
1804
+ cleaned = raw_assistant
1805
+ sanitized = False
1806
+ removed_tags = 0
1807
+ if do_sanitize and _sanitize is not None:
1808
+ stats = getattr(_sanitize, "STATS", {}) or {}
1809
+ before = (int(stats.get("tag_stripped", 0) or 0),
1810
+ int(stats.get("json_stripped", 0) or 0),
1811
+ int(stats.get("fenced_stripped", 0) or 0))
1812
+ try:
1813
+ cleaned = _sanitize.clean(raw_assistant)
1814
+ except Exception:
1815
+ cleaned = raw_assistant
1816
+ after = (int(stats.get("tag_stripped", 0) or 0),
1817
+ int(stats.get("json_stripped", 0) or 0),
1818
+ int(stats.get("fenced_stripped", 0) or 0))
1819
+ removed_tags = max(0, sum(after) - sum(before))
1820
+ sanitized = cleaned != raw_assistant or removed_tags > 0
1821
+ assistant_text = cleaned
1798
1822
  switches = normalize_switches(data.get("switches"))
1799
1823
  gate = gate_reply(assistant_text, {
1800
1824
  "switches": switches, "user_text": data.get("user_text", "")})
1825
+ if do_sanitize and _sanitize is not None:
1826
+ try:
1827
+ gate = dict(gate)
1828
+ gate["text"] = _sanitize.clean(gate.get("text") or "")
1829
+ except Exception:
1830
+ pass
1801
1831
  captured = capture(session_id, "assistant", assistant_text, store, {
1802
1832
  "enabled": _as_bool(data.get("memoryEnabled",
1803
1833
  switches.get("memoryEnabled", True)),
@@ -1818,6 +1848,9 @@ def dispatch(action, payload=None):
1818
1848
  "captured": {"captured": bool(decision.get("capture")),
1819
1849
  "entry_id": str(entry.get("id") or ""),
1820
1850
  "reason": str(decision.get("reason") or "")},
1851
+ "clean_text": cleaned,
1852
+ "sanitized": bool(sanitized),
1853
+ "removed_tags": int(removed_tags),
1821
1854
  "trace": trace,
1822
1855
  "trace_file": trace_file,
1823
1856
  }