@master0071/dsh-systemone 0.7.2 → 0.8.0
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 +6 -2
- package/lib/client.js +17 -3
- package/lib/index.d.ts +6 -1
- package/lib/index.js +10 -2
- package/lib/provider.js +68 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -365,7 +365,8 @@ const decision = await waterfall('agent/pre-step', { // ③ 再跑瀑布
|
|
|
365
365
|
| `model` | `u2-decision` | 热更新 | 决策模型名 |
|
|
366
366
|
| `timeoutMs` | `30000` | 热更新 | 请求超时(毫秒) |
|
|
367
367
|
| `minConfidence` | `0.6` | 热更新 | **次要**复核信号:置信度低于该值时也标记需复核。主要判据是分布平坦度,见「复核判定」 |
|
|
368
|
-
| `redact` | `false` | 热更新 | 发送前对 `state` 脱敏:手机号/身份证/邮箱/银行卡号替换为 `[手机号]`
|
|
368
|
+
| `redact` | `false` | 热更新 | 发送前对 `state` **与 `questions`** 脱敏:手机号/身份证/邮箱/银行卡号替换为 `[手机号]` 等类型标签(判断语义保留;`questions` 只脱敏 `instructions`/`label`,不动 qid 与 criteria 的键。处理含隐私数据的内容时建议开启) |
|
|
369
|
+
| `allowInsecureHttp` | `false` | 热更新 | 是否允许向 **非 https** 端点发请求。带 `Bearer` 凭据的请求走明文等于把密钥发到网络上,所以默认只放行 `https` 与本机回环(`127.0.0.1` / `localhost` / `[::1]`);连内网明文网关时才显式打开 |
|
|
369
370
|
| `customScenarios` | 空 | 热更新 | 自定义场景 JSON 数组(见下)。保存后**场景库立即重建**,无需重启 |
|
|
370
371
|
| `autoDecide` | `false` | 需重启 | 是否开启自动决策(挂载 `agent/pre-step`) |
|
|
371
372
|
| `auto*` | 见「自动决策」 | 热更新 | 自动决策的运行参数(钩子挂载后实时生效) |
|
|
@@ -580,6 +581,7 @@ SystemOne 是「结构化问题 → 概率分布」的决策模型,**不是开
|
|
|
580
581
|
插件与厂商解耦:只要目标服务实现相同协议(`POST {endpoint}`,请求体 `{ model, state, questions }`,响应 `{ answers: { [id]: { type, ... } } }`)即可切换。
|
|
581
582
|
|
|
582
583
|
- **换一家云厂商**:配置 `provider: "http"` + `endpoint` 指向该厂商或自建网关,`apiKey` 填对应凭证。
|
|
584
|
+
**端点必须是 `https`**(或本机回环):请求带 `Authorization: Bearer <key>`,明文 `http` 会把密钥发到网络上,插件会直接拒绝;连内网明文网关需显式设置 `allowInsecureHttp: true`。
|
|
583
585
|
- **本地联调**:`provider: "mock"`,无需 Key,确定性输出。
|
|
584
586
|
- **编程扩展**:参考 `lib/provider.js`,实现 `{ name, decide() }` 后注册到 `createProvider()`。
|
|
585
587
|
|
|
@@ -587,10 +589,12 @@ SystemOne 是「结构化问题 → 概率分布」的决策模型,**不是开
|
|
|
587
589
|
|
|
588
590
|
```powershell
|
|
589
591
|
npm run check # 9 个 lib/*.js 语法检查通过
|
|
590
|
-
npm run test #
|
|
592
|
+
npm run test # 206 项测试:
|
|
591
593
|
# - 场景库完整性、11 大场景端到端(有断言强制 sample 覆盖全部内置场景)、
|
|
592
594
|
# 自定义场景合并/覆盖/patch/非法跳过
|
|
593
595
|
# - params 覆盖语义、确定性、失败路径(无网络、无 Key)
|
|
596
|
+
# - provider 端点安全(非 https 拒绝 / 回环放行 / allowInsecureHttp 逃生口)
|
|
597
|
+
# 与 state+questions 脱敏
|
|
594
598
|
# - 插件入口装配(临时桩实例化:工具注册、降级、卸载)
|
|
595
599
|
# - 自动决策(开关、注入形状与自有 source.kind、fail-open、硬超时、
|
|
596
600
|
# 缓存与"改配置即失效"、去重、跳过规则、历史捕获、自动路由、
|
package/lib/client.js
CHANGED
|
@@ -78,9 +78,13 @@ window.__ModuleLoader__.load({
|
|
|
78
78
|
minConfidence: '置信度阈值',
|
|
79
79
|
minConfidenceHint: '任一答案低于该值时,结果会标记为建议人工复核(0~1)',
|
|
80
80
|
redact: '发送前脱敏',
|
|
81
|
-
redactHint: '开启后把 state 里的手机号/身份证/邮箱/银行卡号替换为 [类型] 标签再发给决策服务,判断语义保留',
|
|
81
|
+
redactHint: '开启后把 state 与 questions 里的手机号/身份证/邮箱/银行卡号替换为 [类型] 标签再发给决策服务,判断语义保留',
|
|
82
82
|
redactOn: '开启(含隐私数据时建议)',
|
|
83
83
|
redactOff: '关闭',
|
|
84
|
+
allowInsecureHttp: '允许明文 HTTP 端点',
|
|
85
|
+
allowInsecureHttpHint: '默认拒绝向非 https、非本机的端点发请求(Authorization 里的 API Key 会明文过网)。仅在本机/内网联调且明确知险时开启',
|
|
86
|
+
allowInsecureOn: '允许(不推荐)',
|
|
87
|
+
allowInsecureOff: '拒绝(默认)',
|
|
84
88
|
autoScenario: '固定场景(可选)',
|
|
85
89
|
autoScenarioHint: '点右侧箭头可从 11 个内置场景里选,也可手填场景 id 或别名(如 software_dev / 开发);留空则每次自动选场景(多一次路由请求)',
|
|
86
90
|
autoInject: '注入通道',
|
|
@@ -173,9 +177,13 @@ window.__ModuleLoader__.load({
|
|
|
173
177
|
minConfidence: 'Confidence threshold',
|
|
174
178
|
minConfidenceHint: 'Below this value a result is flagged for human review (0~1)',
|
|
175
179
|
redact: 'Redact before send',
|
|
176
|
-
redactHint: 'Replace phone/ID-card/email/bank-card numbers in state with [type] tags before sending to the decision service; semantics preserved',
|
|
180
|
+
redactHint: 'Replace phone/ID-card/email/bank-card numbers in state and questions with [type] tags before sending to the decision service; semantics preserved',
|
|
177
181
|
redactOn: 'On (recommended with PII)',
|
|
178
182
|
redactOff: 'Off',
|
|
183
|
+
allowInsecureHttp: 'Allow plain HTTP endpoints',
|
|
184
|
+
allowInsecureHttpHint: 'By default requests to non-https, non-loopback endpoints are refused (the API key in Authorization would travel in cleartext). Enable only for trusted local/LAN testing',
|
|
185
|
+
allowInsecureOn: 'Allow (not recommended)',
|
|
186
|
+
allowInsecureOff: 'Refuse (default)',
|
|
179
187
|
autoScenario: 'Fixed scenario (optional)',
|
|
180
188
|
autoScenarioHint: 'Pick one of the 11 built-in scenarios from the dropdown, or type a scenario id/alias (e.g. software_dev); empty routes automatically on every step (one extra request)',
|
|
181
189
|
autoInject: 'Injection channel',
|
|
@@ -348,6 +356,8 @@ window.__ModuleLoader__.load({
|
|
|
348
356
|
{ key: 'timeoutMs', kind: 'number', min: 1000 },
|
|
349
357
|
{ key: 'minConfidence', kind: 'number', min: 0, max: 1 },
|
|
350
358
|
{ key: 'redact', kind: 'select', options: ['false', 'true'] },
|
|
359
|
+
// 明文端点默认拒绝(API Key 会明文过网);只有内网联调才该开
|
|
360
|
+
{ key: 'allowInsecureHttp', kind: 'select', options: ['false', 'true'] },
|
|
351
361
|
// 台账路径也是 volatile:保存后宿主重建台账(新文件从当天记录恢复累计)
|
|
352
362
|
{ key: 'usageLogPath', kind: 'text' },
|
|
353
363
|
]
|
|
@@ -492,6 +502,9 @@ window.__ModuleLoader__.load({
|
|
|
492
502
|
if (key === 'redact') {
|
|
493
503
|
return value === 'true' ? t('redactOn') : t('redactOff')
|
|
494
504
|
}
|
|
505
|
+
if (key === 'allowInsecureHttp') {
|
|
506
|
+
return value === 'true' ? t('allowInsecureOn') : t('allowInsecureOff')
|
|
507
|
+
}
|
|
495
508
|
return value
|
|
496
509
|
}
|
|
497
510
|
|
|
@@ -577,7 +590,8 @@ window.__ModuleLoader__.load({
|
|
|
577
590
|
if (!Number.isFinite(parsed)) continue
|
|
578
591
|
next = parsed
|
|
579
592
|
}
|
|
580
|
-
|
|
593
|
+
// 布尔字段:下拉在草稿里是字符串,schema 要的是布尔
|
|
594
|
+
if (key === 'redact' || key === 'allowInsecureHttp') next = value === 'true'
|
|
581
595
|
ops.push({ op: 'set', path: [key], value: next })
|
|
582
596
|
}
|
|
583
597
|
if (cardKey === 'service' && clearApiKey) ops.push({ op: 'unset', path: ['apiKey'] })
|
package/lib/index.d.ts
CHANGED
|
@@ -27,8 +27,13 @@ export interface Config {
|
|
|
27
27
|
* 主要判据是概率分布平坦度(见 `reviewAssessment` 语义),因为这类概率未标定、跨后端不可比。
|
|
28
28
|
*/
|
|
29
29
|
minConfidence?: number
|
|
30
|
-
/** 发送前对 state 做内置脱敏(手机号/身份证/邮箱/银行卡号替换为类型标签) */
|
|
30
|
+
/** 发送前对 state 与 questions 做内置脱敏(手机号/身份证/邮箱/银行卡号替换为类型标签) */
|
|
31
31
|
redact?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* 允许向非 https 端点发送请求(默认 false)。请求带 Bearer 凭据,明文 http
|
|
34
|
+
* 会让密钥过网;默认只放行 https 与本机回环地址。
|
|
35
|
+
*/
|
|
36
|
+
allowInsecureHttp?: boolean
|
|
32
37
|
/** 自定义场景:JSON 数组字符串 */
|
|
33
38
|
customScenarios?: string
|
|
34
39
|
/** 用量台账文件路径;留空则用 `$DSH_HOME/dsh-systemone/usage.jsonl` */
|
package/lib/index.js
CHANGED
|
@@ -101,11 +101,19 @@ const Config = z.object({
|
|
|
101
101
|
/** 置信度阈值:任一答案低于该值则建议人工复核 */
|
|
102
102
|
minConfidence: z.number().min(0).max(1).default(0.6).volatile(),
|
|
103
103
|
/**
|
|
104
|
-
* 发送前对 state 做内置脱敏:手机号/身份证/邮箱/银行卡号替换为
|
|
105
|
-
* `[手机号]`
|
|
104
|
+
* 发送前对 state 与 questions 做内置脱敏:手机号/身份证/邮箱/银行卡号替换为
|
|
105
|
+
* `[手机号]` 等类型标签(判断语义保留)。这两部分都会发给上游决策服务,
|
|
106
106
|
* 处理含隐私数据的场景(尤其 data_governance)时建议开启。
|
|
107
107
|
*/
|
|
108
108
|
redact: z.boolean().default(false).volatile(),
|
|
109
|
+
/**
|
|
110
|
+
* 允许向非 https 端点发送请求(默认 false)。
|
|
111
|
+
*
|
|
112
|
+
* 所有请求都带 `Authorization: Bearer <key>`,走明文 http 等于把密钥发到网络上,
|
|
113
|
+
* 所以默认只允许 https(以及本机回环 127.0.0.1/localhost)。只有明确知道自己在
|
|
114
|
+
* 连内网明文网关时才应打开。
|
|
115
|
+
*/
|
|
116
|
+
allowInsecureHttp: z.boolean().default(false).volatile(),
|
|
109
117
|
|
|
110
118
|
/* ── 结构性字段:不在配置页里,改动需要重启 ───────────────────────────────── */
|
|
111
119
|
|
package/lib/provider.js
CHANGED
|
@@ -54,10 +54,10 @@ export class HttpProvider {
|
|
|
54
54
|
if (!ep) {
|
|
55
55
|
throw new Error('SystemOne: provider=http 需要配置 endpoint 或 baseUrl')
|
|
56
56
|
}
|
|
57
|
-
return assertValidUrl(ep)
|
|
57
|
+
return assertValidUrl(ep, this.config)
|
|
58
58
|
}
|
|
59
59
|
const base = String(baseUrl || DEFAULT_BASE_URL).trim().replace(/\/+$/, '')
|
|
60
|
-
return assertValidUrl(`${base}/systemone
|
|
60
|
+
return assertValidUrl(`${base}/systemone`, this.config)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -93,10 +93,13 @@ export class HttpProvider {
|
|
|
93
93
|
const endpoint = this.resolveEndpoint()
|
|
94
94
|
const apiKey = this.resolveApiKey()
|
|
95
95
|
const timeoutMs = readNumber(this.config, 'timeoutMs', 30000) || 30000
|
|
96
|
+
const redact = readBoolean(this.config, 'redact', false)
|
|
96
97
|
const body = JSON.stringify({
|
|
97
98
|
model: model || readString(this.config, 'model', DEFAULT_MODEL) || DEFAULT_MODEL,
|
|
98
|
-
state: redactValue(state,
|
|
99
|
-
questions
|
|
99
|
+
state: redactValue(state, redact),
|
|
100
|
+
// questions 里的 instructions 同样是自由文本(systemone_decide 的 params 也并进来),
|
|
101
|
+
// 只脱敏 state 会让这条路径漏掉 PII。
|
|
102
|
+
questions: redactQuestions(questions, redact),
|
|
100
103
|
})
|
|
101
104
|
|
|
102
105
|
const controller = new AbortController()
|
|
@@ -322,6 +325,35 @@ function redactValue(value, enabled) {
|
|
|
322
325
|
return value
|
|
323
326
|
}
|
|
324
327
|
|
|
328
|
+
/**
|
|
329
|
+
* 递归脱敏 questions。
|
|
330
|
+
*
|
|
331
|
+
* 只处理**自由文本**(`instructions` / `label`),**不动 qid 与 criteria 的键**——
|
|
332
|
+
* 键是决策结果回填到场景的索引,改了就映射不回去了。`systemone_decide` 的
|
|
333
|
+
* `params.instructions` 覆盖会在发请求前合并进 questions,因此也一并覆盖。
|
|
334
|
+
*
|
|
335
|
+
* @param {object} questions - 问题定义(`{ [qid]: { type, instructions, ... } }`)
|
|
336
|
+
* @param {boolean} enabled - 是否开启脱敏
|
|
337
|
+
* @returns {object} 脱敏后的副本(未开启时原样返回)
|
|
338
|
+
*/
|
|
339
|
+
function redactQuestions(questions, enabled) {
|
|
340
|
+
if (!enabled || !questions || typeof questions !== 'object' || Array.isArray(questions)) {
|
|
341
|
+
return questions
|
|
342
|
+
}
|
|
343
|
+
const out = {}
|
|
344
|
+
for (const [qid, question] of Object.entries(questions)) {
|
|
345
|
+
if (!question || typeof question !== 'object' || Array.isArray(question)) {
|
|
346
|
+
out[qid] = question
|
|
347
|
+
continue
|
|
348
|
+
}
|
|
349
|
+
const next = { ...question }
|
|
350
|
+
if (typeof next.instructions === 'string') next.instructions = redactString(next.instructions)
|
|
351
|
+
if (typeof next.label === 'string') next.label = redactString(next.label)
|
|
352
|
+
out[qid] = next
|
|
353
|
+
}
|
|
354
|
+
return out
|
|
355
|
+
}
|
|
356
|
+
|
|
325
357
|
/* ─── DSH 凭证缝读取 ───────────────────────────────────────────────────────── */
|
|
326
358
|
|
|
327
359
|
/** DSH 主目录:与用量台账同源(usage.js 导出)。 */
|
|
@@ -411,14 +443,42 @@ export function redactRuleSummary() {
|
|
|
411
443
|
|
|
412
444
|
/* ─── 通用内部工具 ─────────────────────────────────────────────────────────── */
|
|
413
445
|
|
|
414
|
-
/**
|
|
415
|
-
function
|
|
446
|
+
/** 本机回环地址:http 只在这里被放行(凭据不出网卡)。 */
|
|
447
|
+
function isLoopbackHost(hostname) {
|
|
448
|
+
const host = String(hostname || '').toLowerCase()
|
|
449
|
+
return host === 'localhost' || host === '::1' || host === '[::1]' || /^127\./.test(host)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 发请求前的端点体检。
|
|
454
|
+
*
|
|
455
|
+
* 除了"是不是合法 URL",还要挡住**明文传输凭据**:本插件所有请求都带
|
|
456
|
+
* `Authorization: Bearer <key>`,走 http 等于把密钥明文发到网络上。因此默认
|
|
457
|
+
* 只允许 https;http 仅对本机回环放行(本地联调 / mock 网关),其他明文端点
|
|
458
|
+
* 必须显式配置 `allowInsecureHttp: true`(给内网自建网关留的逃生口)。
|
|
459
|
+
*
|
|
460
|
+
* @param {string} url - 已解析出的完整端点
|
|
461
|
+
* @param {object} [config] - 插件配置(读取 allowInsecureHttp)
|
|
462
|
+
*/
|
|
463
|
+
function assertValidUrl(url, config) {
|
|
464
|
+
let parsed
|
|
416
465
|
try {
|
|
417
|
-
new URL(url)
|
|
466
|
+
parsed = new URL(url)
|
|
418
467
|
} catch {
|
|
419
468
|
throw new Error(`SystemOne: 端点不是合法 URL:${url}(需以 http:// 或 https:// 开头)`)
|
|
420
469
|
}
|
|
421
|
-
return url
|
|
470
|
+
if (parsed.protocol === 'https:') return url
|
|
471
|
+
if (parsed.protocol !== 'http:') {
|
|
472
|
+
throw new Error(`SystemOne: 不支持的端点协议 ${parsed.protocol}:${url}(只支持 http/https)`)
|
|
473
|
+
}
|
|
474
|
+
if (isLoopbackHost(parsed.hostname)) return url
|
|
475
|
+
if (readBoolean(config, 'allowInsecureHttp', false)) return url
|
|
476
|
+
throw new Error(
|
|
477
|
+
`SystemOne: 拒绝向非 https 端点发送请求:${url}`
|
|
478
|
+
+ '(Authorization 里的 API Key 会明文过网)。'
|
|
479
|
+
+ '本机联调请用 http://127.0.0.1 或 http://localhost;'
|
|
480
|
+
+ '确需连接明文内网网关时,请在配置里显式设置 allowInsecureHttp: true。',
|
|
481
|
+
)
|
|
422
482
|
}
|
|
423
483
|
|
|
424
484
|
/** 仅临时故障值得重试:5xx、429 限流、网络层错误(fetch 失败抛 TypeError)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@master0071/dsh-systemone",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "DeepSeek Harness 插件:将 SystemOne 决策模型(默认云知声 u2-decision,可切换提供商)接入软件开发、需求预检、客服、内容审核、路由、销售线索、风控、招聘、数据治理、教育、需求变更等 11 大业务场景,为 Agent 返回带概率分布的决策与处置建议。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|