dsh-biocore 0.1.4 → 0.1.5
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/lib/index.js +7 -1
- package/lib/modules/core-turn.js +17 -2
- package/lib/modules/sanitize-reply.js +60 -0
- package/package.json +1 -1
- package/ui/panel.html +1 -0
- package/ui/panel.js +1 -0
package/lib/index.js
CHANGED
|
@@ -167,6 +167,7 @@ export function readFullConfig(dataDir) {
|
|
|
167
167
|
embedding: storedEmbedding(source),
|
|
168
168
|
rerank: normalizeRerank(source.rerank),
|
|
169
169
|
expression: normalizeExpression(source.expression),
|
|
170
|
+
sanitize_reply: source.sanitize_reply !== false,
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
|
|
@@ -176,6 +177,7 @@ function writeFullConfig(file, config) {
|
|
|
176
177
|
embedding: config.embedding,
|
|
177
178
|
rerank: config.rerank,
|
|
178
179
|
expression: config.expression,
|
|
180
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
179
181
|
}, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
|
|
180
182
|
try { chmodSync(file, 0o600) } catch { /* Windows may ignore POSIX modes */ }
|
|
181
183
|
}
|
|
@@ -191,6 +193,7 @@ export async function handleSettings(req, res, dataDir) {
|
|
|
191
193
|
embedding: maskedEmbedding(config.embedding),
|
|
192
194
|
rerank: maskedRerank(config.rerank),
|
|
193
195
|
expression: config.expression,
|
|
196
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
194
197
|
restart_required: false,
|
|
195
198
|
})
|
|
196
199
|
}
|
|
@@ -213,7 +216,8 @@ export async function handleSettings(req, res, dataDir) {
|
|
|
213
216
|
const hasEmbedding = body.embedding && typeof body.embedding === 'object'
|
|
214
217
|
const hasRerank = body.rerank && typeof body.rerank === 'object'
|
|
215
218
|
const hasExpression = body.expression && typeof body.expression === 'object'
|
|
216
|
-
|
|
219
|
+
const hasSanitize = typeof body.sanitize_reply === 'boolean'
|
|
220
|
+
if (!hasEmbedding && !hasRerank && !hasExpression && !hasSanitize) {
|
|
217
221
|
return sendJson(res, 400, { ok: false, reason: 'no_settings' })
|
|
218
222
|
}
|
|
219
223
|
if (hasEmbedding) {
|
|
@@ -236,12 +240,14 @@ export async function handleSettings(req, res, dataDir) {
|
|
|
236
240
|
config.rerank = rerank
|
|
237
241
|
}
|
|
238
242
|
if (hasExpression) config.expression = normalizeExpression(body.expression)
|
|
243
|
+
if (hasSanitize) config.sanitize_reply = body.sanitize_reply === true
|
|
239
244
|
writeFullConfig(paths.file, config)
|
|
240
245
|
return sendJson(res, 200, {
|
|
241
246
|
ok: true,
|
|
242
247
|
embedding: maskedEmbedding(config.embedding),
|
|
243
248
|
rerank: maskedRerank(config.rerank),
|
|
244
249
|
expression: config.expression,
|
|
250
|
+
sanitize_reply: config.sanitize_reply !== false,
|
|
245
251
|
restart_required: true,
|
|
246
252
|
hint: '重启 BioCore 生效',
|
|
247
253
|
})
|
package/lib/modules/core-turn.js
CHANGED
|
@@ -26,6 +26,11 @@ import { applyGateText, assistantTextOf } from './safety-gate.js'
|
|
|
26
26
|
import { defaultStoreDir } from './trace.js'
|
|
27
27
|
import { runtimeAction, runTurn } from '../py-bridge.js'
|
|
28
28
|
import { EXPRESSION_DEFAULTS, readExpressionSettings } from './expression-config.js'
|
|
29
|
+
import {
|
|
30
|
+
readSanitizeReplySetting,
|
|
31
|
+
sanitizeReply,
|
|
32
|
+
sanitizeReplyEnabled,
|
|
33
|
+
} from './sanitize-reply.js'
|
|
29
34
|
|
|
30
35
|
export const TURN_CONTEXT_NAME = 'biocore-expression-turn'
|
|
31
36
|
export const TURN_CONTEXT_ORDER = -190
|
|
@@ -118,6 +123,7 @@ export function turnEndPayload(options) {
|
|
|
118
123
|
store: opts.store || defaultStoreDir(),
|
|
119
124
|
capture: false,
|
|
120
125
|
memoryEnabled: false,
|
|
126
|
+
sanitize_reply: opts.sanitizeReply !== false,
|
|
121
127
|
diagnostics,
|
|
122
128
|
}, diagnostics, {
|
|
123
129
|
modules_trace: diagnostics.modules,
|
|
@@ -135,6 +141,9 @@ export function makeCoreSessionListener(options) {
|
|
|
135
141
|
const getExpression = typeof opts.getExpression === 'function'
|
|
136
142
|
? opts.getExpression
|
|
137
143
|
: () => readExpressionSettings(stateDir)
|
|
144
|
+
const getSanitizeReply = typeof opts.getSanitizeReply === 'function'
|
|
145
|
+
? opts.getSanitizeReply
|
|
146
|
+
: () => readSanitizeReplySetting(stateDir)
|
|
138
147
|
const store = opts.store || defaultStoreDir()
|
|
139
148
|
const log = typeof opts.log === 'function' ? opts.log : (line) => {
|
|
140
149
|
try { console.error(line) } catch { /* noop */ }
|
|
@@ -186,9 +195,11 @@ export function makeCoreSessionListener(options) {
|
|
|
186
195
|
}).catch(() => null)
|
|
187
196
|
return
|
|
188
197
|
}
|
|
189
|
-
// assistant/message: gate + trace via one turn_end call.
|
|
198
|
+
// assistant/message: sanitize -> gate + trace via one turn_end call.
|
|
190
199
|
const original = assistantTextOf(event)
|
|
191
200
|
if (!original) return
|
|
201
|
+
const sanitizeEnabled = sanitizeReplyEnabled(getSanitizeReply())
|
|
202
|
+
const cleaned = sanitizeEnabled ? sanitizeReply(original) : original
|
|
192
203
|
const userText = lastUser.get(sessionId) || ''
|
|
193
204
|
void (async () => {
|
|
194
205
|
try {
|
|
@@ -198,11 +209,12 @@ export function makeCoreSessionListener(options) {
|
|
|
198
209
|
const state = read && !read.error && !read.missing ? read.state : {}
|
|
199
210
|
const payload = turnEndPayload({
|
|
200
211
|
sessionId,
|
|
201
|
-
assistantText:
|
|
212
|
+
assistantText: cleaned,
|
|
202
213
|
userText,
|
|
203
214
|
switches,
|
|
204
215
|
store,
|
|
205
216
|
state,
|
|
217
|
+
sanitizeReply: sanitizeEnabled,
|
|
206
218
|
})
|
|
207
219
|
const result = await runAction('turn_end', payload)
|
|
208
220
|
if (!result || result.ok !== true) return
|
|
@@ -216,6 +228,9 @@ export function makeCoreSessionListener(options) {
|
|
|
216
228
|
+ ' blocked=' + (gate.blocked === true ? '1' : '0')
|
|
217
229
|
+ ' rule=' + stringId(gate.rule || '')
|
|
218
230
|
+ ' 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)
|
|
219
234
|
}
|
|
220
235
|
if (result.trace_file) {
|
|
221
236
|
logLine(log, CORE_TRACE_PREFIX + ' session=' + sessionId
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Strip BioCore output markers from assistant replies (task-223).
|
|
2
|
+
*
|
|
3
|
+
* Removes:
|
|
4
|
+
* - <bc>...</bc> and <biocore>...</biocore> blocks (also unclosed to EOL/EOS)
|
|
5
|
+
* - bare compact JSON objects that only carry e/i[/n] keys
|
|
6
|
+
* - extra whitespace; empty result stays empty (never falls back to input)
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_SANITIZE_REPLY = true
|
|
12
|
+
|
|
13
|
+
const TAG_RE = /<(bc|biocore)\b[^>]*>/gi
|
|
14
|
+
const BARE_JSON_RE = /\{\s*"e"\s*:\s*"[^"\\]*(?:\\.[^"\\]*)*"\s*,\s*"i"\s*:\s*-?\d+(?:\.\d+)?(?:\s*,\s*"n"\s*:\s*-?\d+(?:\.\d+)?)?\s*\}/g
|
|
15
|
+
|
|
16
|
+
function stripClosedTags(text) {
|
|
17
|
+
return String(text)
|
|
18
|
+
.replace(/<bc\b[^>]*>[\s\S]*?<\/bc\s*>/gi, '')
|
|
19
|
+
.replace(/<biocore\b[^>]*>[\s\S]*?<\/biocore\s*>/gi, '')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stripUnclosedTags(text) {
|
|
23
|
+
// Until line end or a Chinese/ASCII sentence terminator; the terminator stays.
|
|
24
|
+
return String(text)
|
|
25
|
+
.replace(/<bc\b[^>]*>[\s\S]*?(?:[\n。!?!?]|$)/gi, '')
|
|
26
|
+
.replace(/<biocore\b[^>]*>[\s\S]*?(?:[\n。!?!?]|$)/gi, '')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeWhitespace(text) {
|
|
30
|
+
return String(text)
|
|
31
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
32
|
+
.replace(/[ \t]*\n[ \t]*/g, '\n')
|
|
33
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
34
|
+
.trim()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Main entry: returns the cleaned reply (may be ''); never reverts to input. */
|
|
38
|
+
export function sanitizeReply(text, options) {
|
|
39
|
+
const opts = options || {}
|
|
40
|
+
if (opts.enabled === false) return String(text == null ? '' : text)
|
|
41
|
+
const source = String(text == null ? '' : text)
|
|
42
|
+
let out = stripClosedTags(source)
|
|
43
|
+
out = stripUnclosedTags(out)
|
|
44
|
+
out = out.replace(BARE_JSON_RE, '')
|
|
45
|
+
return normalizeWhitespace(out)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Config.json top-level flag: only an explicit false disables cleaning. */
|
|
49
|
+
export function readSanitizeReplySetting(dataDir) {
|
|
50
|
+
try {
|
|
51
|
+
const doc = JSON.parse(readFileSync(join(String(dataDir || '').trim(), 'config.json'), 'utf8'))
|
|
52
|
+
if (doc && typeof doc === 'object' && doc.sanitize_reply === false) return false
|
|
53
|
+
} catch { /* missing/bad config -> default on */ }
|
|
54
|
+
return DEFAULT_SANITIZE_REPLY
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Payload helper: sanitize_reply defaults to true unless explicitly false. */
|
|
58
|
+
export function sanitizeReplyEnabled(value) {
|
|
59
|
+
return value !== false
|
|
60
|
+
}
|
package/package.json
CHANGED
package/ui/panel.html
CHANGED
|
@@ -247,6 +247,7 @@
|
|
|
247
247
|
<dl class="advanced-info">
|
|
248
248
|
<div><dt>数据目录</dt><dd id="dataDirText">宿主托管</dd></div>
|
|
249
249
|
<div><dt>插件版本</dt><dd id="pluginVersion">0.1.0</dd></div>
|
|
250
|
+
<div><dt>出口清洗</dt><dd id="sanitizeReplyText">开</dd></div>
|
|
250
251
|
</dl>
|
|
251
252
|
<div class="advanced-actions">
|
|
252
253
|
<button id="resetLayout" class="advanced-button" type="button">恢复默认布局</button>
|