@master0071/dsh-systemone 0.0.0-stage → 0.4.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/lib/tools.js ADDED
@@ -0,0 +1,421 @@
1
+ /**
2
+ * tools.js — SystemOne 决策工具定义(统一调度形态)。
3
+ *
4
+ * 只注册 2 个工具:
5
+ * 1. systemone_scenario — action: list / describe / run,覆盖全部内置与自定义场景
6
+ * 2. systemone_decide — 直接提交自定义 questions 的低层入口
7
+ *
8
+ * 场景全部来自 scenarios.js 的场景库(内置 10 个 + 配置追加),新增场景无需改代码。
9
+ */
10
+ import {
11
+ computeNeedsHumanReview,
12
+ deriveFields,
13
+ formatAnswerLines,
14
+ formatSummary,
15
+ normalizeAnswers,
16
+ renderTemplate,
17
+ } from './format.js'
18
+ import { findScenario } from './scenarios.js'
19
+ import { readNumber, readString } from './volatile.js'
20
+
21
+ const DEFAULT_MIN_CONFIDENCE = 0.6
22
+
23
+ /** 创建全部工具定义。 */
24
+ export function createTools(provider, config, scenarios) {
25
+ return [
26
+ scenarioTool(provider, config, scenarios),
27
+ decideTool(provider, config),
28
+ ]
29
+ }
30
+
31
+ /* ─── 1. 场景调度工具 ─────────────────────────────────────────────────────── */
32
+
33
+ function scenarioTool(provider, config, scenarios) {
34
+ return {
35
+ name: 'systemone_scenario',
36
+ description: [
37
+ 'SystemOne 场景决策:把业务状态与预置的结构化问题一次性提交给决策模型,返回带概率分布的判断与处置建议。',
38
+ 'action=list 列出全部场景;action=describe 查看某个场景的问题定义与可覆盖参数;action=run 执行决策。',
39
+ '内置 10 大业务场景,可用 params 覆盖问题定义。',
40
+ ].join(' '),
41
+ parameters: {
42
+ type: 'object',
43
+ properties: {
44
+ action: {
45
+ type: 'string',
46
+ enum: ['list', 'describe', 'run'],
47
+ description: '操作类型:list=列出场景,describe=查看场景问题定义,run=执行决策。',
48
+ },
49
+ scenario: {
50
+ type: 'string',
51
+ description: '场景 id 或别名,如 customer_service / content_moderation / risk_control(action=describe/run 时必填)。',
52
+ },
53
+ state: {
54
+ type: ['string', 'object', 'array'],
55
+ description: '业务上下文:工单文本、对话数组或结构化对象(action=run 时必填)。',
56
+ },
57
+ params: {
58
+ type: 'object',
59
+ additionalProperties: { type: 'object' },
60
+ description: '可选:按问题 id 覆盖问题定义。criteria 整体替换选项(choice 传对象,score 传标签数组),addCriteria 在现有选项上追加/覆盖单个 key,也可覆盖 instructions 与 label。',
61
+ },
62
+ keyword: {
63
+ type: 'string',
64
+ description: '可选:action=list 时按关键词过滤场景(匹配 id/标题/别名)。',
65
+ },
66
+ },
67
+ required: ['action'],
68
+ },
69
+ output: {
70
+ schema: { type: 'object', additionalProperties: true },
71
+ render: (_args, value) => [{ type: 'text', text: value?.summary || JSON.stringify(value, null, 2) }],
72
+ },
73
+ async execute(args, exec) {
74
+ const action = args.action || 'run'
75
+
76
+ if (action === 'list') {
77
+ return listScenarios(scenarios, args.keyword)
78
+ }
79
+ if (action === 'describe') {
80
+ return describeScenario(scenarios, args.scenario)
81
+ }
82
+ return runScenario({ provider, config, scenarios, args, exec })
83
+ },
84
+ presentCall: (args) => ({
85
+ card: 'generic',
86
+ title: `SystemOne 场景:${args?.action || 'run'}${args?.scenario ? ` · ${args.scenario}` : ''}`,
87
+ kind: 'other',
88
+ rawInput: args,
89
+ }),
90
+ }
91
+ }
92
+
93
+ function listScenarios(scenarios, keyword) {
94
+ const needle = typeof keyword === 'string' ? keyword.trim().toLowerCase() : ''
95
+ const items = scenarios
96
+ .filter((s) => {
97
+ if (!needle) return true
98
+ return [s.id, s.title, s.description, ...(s.aliases || [])]
99
+ .join(' ')
100
+ .toLowerCase()
101
+ .includes(needle)
102
+ })
103
+ .map((s) => ({
104
+ id: s.id,
105
+ title: s.title,
106
+ description: s.description,
107
+ source: s.source,
108
+ question_count: Object.keys(s.questions).length,
109
+ questions: Object.entries(s.questions).map(([qid, q]) => `${qid}:${q.type}`),
110
+ }))
111
+
112
+ const summary = [
113
+ `## SystemOne 场景库(${items.length} 个)`,
114
+ ...items.map((s) => `- **${s.id}**:${s.title} — ${s.question_count} 个问题(${s.questions.join(',')})`),
115
+ '',
116
+ '用 action=describe 查看某场景的问题定义,用 action=run 执行决策。',
117
+ ].join('\n')
118
+
119
+ return { ok: true, action: 'list', count: items.length, scenarios: items, summary }
120
+ }
121
+
122
+ function describeScenario(scenarios, id) {
123
+ const scenario = findScenario(scenarios, id)
124
+ if (!scenario) {
125
+ return {
126
+ ok: false,
127
+ error: `未知场景 "${id}"`,
128
+ available: scenarios.map((s) => s.id),
129
+ summary: `## SystemOne 场景\n\n**未找到场景**:${id}\n\n可用场景:${scenarios.map((s) => s.id).join('、')}`,
130
+ }
131
+ }
132
+ const questions = Object.entries(scenario.questions).map(([qid, q]) => ({
133
+ id: qid,
134
+ type: q.type,
135
+ label: q.label,
136
+ instructions: q.instructions,
137
+ criteria: q.criteria ?? null,
138
+ }))
139
+ const summary = [
140
+ `## ${scenario.title}`,
141
+ `- **场景 id**:${scenario.id}${scenario.aliases?.length ? `(别名:${scenario.aliases.join('、')})` : ''}`,
142
+ scenario.description ? `- **说明**:${scenario.description}` : null,
143
+ ...questions.map((q) => {
144
+ const criteria = q.type === 'choice'
145
+ ? Object.entries(q.criteria || {}).map(([k, v]) => `${k}=${v}`).join(';')
146
+ : q.type === 'score'
147
+ ? (q.criteria || []).map((v, i) => `${i}=${v}`).join(';')
148
+ : '(0~1 概率)'
149
+ return `- **${q.label}** \`${q.id}\`(${q.type}):${q.instructions}\n - 选项:${criteria}`
150
+ }).filter(Boolean),
151
+ scenario.recommendation ? `- **建议模板**:${scenario.recommendation}` : null,
152
+ '',
153
+ '用 action=run 执行,可用 params 覆盖上面任意问题的 criteria。',
154
+ ].filter(Boolean).join('\n')
155
+
156
+ return { ok: true, action: 'describe', scenario: scenario.id, title: scenario.title, questions, summary }
157
+ }
158
+
159
+ async function runScenario({ provider, config, scenarios, args, exec }) {
160
+ return runScenarioDecision({
161
+ provider,
162
+ config,
163
+ scenarios,
164
+ scenario: args.scenario,
165
+ state: args.state,
166
+ params: args.params,
167
+ exec,
168
+ })
169
+ }
170
+
171
+ /**
172
+ * 执行一次场景决策(工具与自动决策共用的核心入口)。
173
+ *
174
+ * @param {object} options
175
+ * @param {object} options.provider - 提供商
176
+ * @param {object} options.config - 插件配置
177
+ * @param {object[]} options.scenarios - 场景库
178
+ * @param {string} options.scenario - 场景 id 或别名
179
+ * @param {unknown} options.state - 业务上下文
180
+ * @param {object} [options.params] - 按问题 id 覆盖问题定义
181
+ * @param {object} [options.exec] - 工具执行上下文(取 signal)
182
+ * @param {string} [options.model] - 覆盖模型
183
+ * @param {number} [options.minConfidence] - 覆盖置信度阈值
184
+ * @returns {Promise<object>} 与工具输出同构的决策结果
185
+ */
186
+ export async function runScenarioDecision({
187
+ provider,
188
+ config,
189
+ scenarios,
190
+ scenario: scenarioRef,
191
+ state,
192
+ params,
193
+ exec,
194
+ model,
195
+ minConfidence,
196
+ }) {
197
+ const scenario = findScenario(scenarios, scenarioRef)
198
+ if (!scenario) {
199
+ return {
200
+ ok: false,
201
+ error: `未知场景 "${scenarioRef ?? ''}"`,
202
+ available: scenarios.map((s) => s.id),
203
+ summary: `## SystemOne 场景决策\n\n**未找到场景**:${scenarioRef ?? '(未提供)'}\n\n可用场景:${scenarios.map((s) => s.id).join('、')}`,
204
+ }
205
+ }
206
+ if (state === undefined || state === null || state === '') {
207
+ return {
208
+ ok: false,
209
+ error: '缺少 state',
210
+ scenario: scenario.id,
211
+ summary: `## ${scenario.title}\n\n**缺少 state**:请提供需要判断的业务上下文。`,
212
+ }
213
+ }
214
+
215
+ const questions = applyParams(scenario.questions, params)
216
+ const configThreshold = readNumber(config, 'minConfidence', NaN)
217
+ const threshold = Number.isFinite(Number(minConfidence))
218
+ ? Number(minConfidence)
219
+ : Number.isFinite(Number(scenario.minConfidence))
220
+ ? Number(scenario.minConfidence)
221
+ : Number.isFinite(configThreshold)
222
+ ? configThreshold
223
+ : DEFAULT_MIN_CONFIDENCE
224
+
225
+ return executeDecision({
226
+ title: scenario.title,
227
+ scenarioId: scenario.id,
228
+ state,
229
+ questions,
230
+ provider,
231
+ config,
232
+ minConfidence: threshold,
233
+ exec,
234
+ model,
235
+ finalize(answers) {
236
+ const { decision, labels, confidences } = normalizeAnswers(answers, questions)
237
+ const derived = deriveFields(scenario.derive, decision)
238
+ const recommendation = scenario.recommendation
239
+ ? renderTemplate(scenario.recommendation, { labels, decision, confidences, derived })
240
+ : ''
241
+ return { decision, labels, derived, confidences, recommendation }
242
+ },
243
+ })
244
+ }
245
+
246
+ /** 按 params 覆盖问题定义。 */
247
+ function applyParams(questions, params) {
248
+ if (!params || typeof params !== 'object') return questions
249
+ const merged = {}
250
+ for (const [qid, question] of Object.entries(questions)) {
251
+ const override = params[qid]
252
+ if (!override || typeof override !== 'object') {
253
+ merged[qid] = question
254
+ continue
255
+ }
256
+ const next = { ...question }
257
+ if (typeof override.instructions === 'string' && override.instructions.trim()) {
258
+ next.instructions = override.instructions
259
+ }
260
+ if (typeof override.label === 'string' && override.label.trim()) {
261
+ next.label = override.label
262
+ }
263
+ // criteria 整体替换;addCriteria 在替换结果之上追加/覆盖单个选项
264
+ if (override.criteria) {
265
+ if (question.type === 'choice' && !Array.isArray(override.criteria)) {
266
+ next.criteria = Object.fromEntries(
267
+ Object.entries(override.criteria).map(([k, v]) => [String(k), String(v)]),
268
+ )
269
+ } else if (question.type === 'score' && Array.isArray(override.criteria) && override.criteria.length >= 2) {
270
+ next.criteria = override.criteria.map((v) => String(v))
271
+ }
272
+ }
273
+ if (override.addCriteria && question.type === 'choice' && !Array.isArray(override.addCriteria)) {
274
+ next.criteria = { ...(next.criteria || {}), ...override.addCriteria }
275
+ }
276
+ merged[qid] = next
277
+ }
278
+ return merged
279
+ }
280
+
281
+ /* ─── 2. 通用决策工具 ─────────────────────────────────────────────────────── */
282
+
283
+ function decideTool(provider, config) {
284
+ return {
285
+ name: 'systemone_decide',
286
+ description: [
287
+ 'SystemOne 通用决策:直接提交业务状态与自定义问题映射(choice/noul/score)到决策模型,返回原始概率化答案。',
288
+ '适合场景库中没有的临时判断。',
289
+ ].join(' '),
290
+ parameters: {
291
+ type: 'object',
292
+ properties: {
293
+ state: {
294
+ type: ['string', 'object', 'array'],
295
+ description: '业务上下文:字符串、结构化对象或数组。非字符串会被序列化后推理。',
296
+ },
297
+ questions: {
298
+ type: 'object',
299
+ additionalProperties: {
300
+ type: 'object',
301
+ properties: {
302
+ type: { type: 'string', enum: ['choice', 'noul', 'score'], description: '问题类型。' },
303
+ instructions: { type: 'string', description: '问题描述。' },
304
+ criteria: {
305
+ description: 'choice 为选项映射对象(key=选项标识,value=说明);score 为分级标签数组。',
306
+ },
307
+ },
308
+ required: ['type', 'instructions'],
309
+ },
310
+ description: '问题映射:键为问题 ID,值为问题对象。推荐不超过 16 个问题。',
311
+ },
312
+ model: { type: 'string', description: '可选:模型名,默认取插件配置的 model。' },
313
+ },
314
+ required: ['state', 'questions'],
315
+ },
316
+ output: {
317
+ schema: { type: 'object', additionalProperties: true },
318
+ render: (_args, value) => [{ type: 'text', text: value?.summary || JSON.stringify(value, null, 2) }],
319
+ },
320
+ async execute(args, exec) {
321
+ const minConfidence = readNumber(config, 'minConfidence', DEFAULT_MIN_CONFIDENCE)
322
+ return executeDecision({
323
+ title: 'SystemOne 通用决策',
324
+ scenarioId: null,
325
+ state: args.state,
326
+ questions: args.questions || {},
327
+ provider,
328
+ config,
329
+ minConfidence,
330
+ exec,
331
+ model: args.model,
332
+ finalize(answers) {
333
+ const { decision, labels, confidences } = normalizeAnswers(answers, args.questions || {})
334
+ return { decision, labels, derived: {}, confidences, recommendation: '' }
335
+ },
336
+ })
337
+ },
338
+ presentCall: (args) => ({
339
+ card: 'generic',
340
+ title: 'SystemOne 通用决策',
341
+ kind: 'other',
342
+ rawInput: args,
343
+ }),
344
+ }
345
+ }
346
+
347
+ /* ─── 公共执行逻辑 ─────────────────────────────────────────────────────────── */
348
+
349
+ async function executeDecision({
350
+ title,
351
+ scenarioId,
352
+ state,
353
+ questions,
354
+ provider,
355
+ config,
356
+ minConfidence,
357
+ exec,
358
+ model,
359
+ finalize,
360
+ }) {
361
+ try {
362
+ const started = Date.now()
363
+ const configuredModel = readString(config, 'model', '')
364
+ const response = await provider.decide({
365
+ state,
366
+ questions,
367
+ model: model || configuredModel,
368
+ signal: exec?.signal,
369
+ })
370
+ const answers = response.answers || {}
371
+ const { decision, labels, derived, confidences, recommendation } = finalize(answers)
372
+ const needsHumanReview = computeNeedsHumanReview(answers, minConfidence)
373
+ const lines = formatAnswerLines(answers, questions)
374
+ const latencyMs = Number.isFinite(Number(response.latency_ms))
375
+ ? Number(response.latency_ms)
376
+ : Date.now() - started
377
+ const modelName = response.model || configuredModel || 'unknown'
378
+ const summary = formatSummary({
379
+ title,
380
+ provider: provider.name,
381
+ model: modelName,
382
+ requestId: response.request_id || '',
383
+ latencyMs,
384
+ lines,
385
+ recommendation,
386
+ needsHumanReview,
387
+ })
388
+ return {
389
+ ok: true,
390
+ scenario: scenarioId,
391
+ provider: provider.name,
392
+ model: modelName,
393
+ request_id: response.request_id || '',
394
+ latency_ms: latencyMs,
395
+ answers,
396
+ decision,
397
+ labels,
398
+ derived,
399
+ confidences,
400
+ needs_human_review: needsHumanReview,
401
+ recommendation,
402
+ summary,
403
+ }
404
+ } catch (err) {
405
+ const msg = messageOf(err)
406
+ return {
407
+ ok: false,
408
+ scenario: scenarioId,
409
+ provider: provider.name,
410
+ error: msg,
411
+ summary: `## ${title}\n\n**SystemOne 调用失败**:${msg}`,
412
+ }
413
+ }
414
+ }
415
+
416
+ function messageOf(err) {
417
+ if (err !== null && typeof err === 'object' && typeof err.message === 'string') return err.message
418
+ if (typeof err === 'string') return err
419
+ return String(err)
420
+ }
421
+
@@ -0,0 +1,50 @@
1
+ /**
2
+ * volatile.js — 配置读取辅助。
3
+ *
4
+ * 被 `.volatile()` 标注的 Config 字段,schemastery 会把它解析成一个「引用对象」
5
+ * (cosmokit 的 volatile),热更新时**原地替换内部值**,插件无需重启即可生效。
6
+ *
7
+ * 代价是读取时必须先脱壳:直接 `config.model` 拿到的是引用对象而不是字符串。
8
+ * 所有配置读取都应经过这里的 `unwrap()`:
9
+ * - 非 volatile 字段 / 单元测试里的普通对象:原样返回,行为不变;
10
+ * - volatile 字段:返回当前最新值,配置页保存后立即生效。
11
+ *
12
+ * 识别方式是 cosmokit 的全局注册符号 `Symbol.for("cosmokit.volatile.write")`
13
+ * (见 cosmokit `createVolatile` / `isVolatile`)。用全局符号而不是 import,
14
+ * 是为了让本插件保持零依赖:宿主里可能同时存在多份 cosmokit 副本(ESM/CJS),
15
+ * 而全局符号在各副本间是同一个。
16
+ */
17
+
18
+ /** cosmokit 标记 volatile 引用的全局符号。 */
19
+ const VOLATILE_WRITE = Symbol.for('cosmokit.volatile.write')
20
+
21
+ /** 判断一个值是否是 volatile 引用对象。 */
22
+ function isVolatileRef(value) {
23
+ return typeof value === 'object' && value !== null && VOLATILE_WRITE in value
24
+ }
25
+
26
+ /** 取出 volatile 引用里的当前值;非 volatile 原样返回。 */
27
+ export function unwrap(value) {
28
+ return isVolatileRef(value) ? value.get() : value
29
+ }
30
+
31
+ /** 读取字符串配置(去壳 + 空值兜底)。 */
32
+ export function readString(config, key, fallback = '') {
33
+ const value = unwrap(config?.[key])
34
+ if (value === undefined || value === null) return fallback
35
+ const text = String(value)
36
+ return text === '' ? fallback : text
37
+ }
38
+
39
+ /** 读取数值配置(去壳 + 非法值兜底)。 */
40
+ export function readNumber(config, key, fallback) {
41
+ const value = Number(unwrap(config?.[key]))
42
+ return Number.isFinite(value) ? value : fallback
43
+ }
44
+
45
+ /** 读取布尔配置(去壳 + 默认值兜底)。 */
46
+ export function readBoolean(config, key, fallback = false) {
47
+ const value = unwrap(config?.[key])
48
+ if (value === undefined || value === null) return fallback
49
+ return Boolean(value)
50
+ }
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@master0071/dsh-systemone",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.0",
4
+ "description": "DeepSeek Harness 插件:将 SystemOne 决策模型(默认云知声 u2-decision,可切换提供商)接入软件开发、客服、内容审核、路由、销售线索、风控、招聘、数据治理、教育、需求变更等 10 大业务场景,为 Agent 返回带概率分布的决策与处置建议。",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/zerosloney/dsh-system-one.git"
10
+ },
11
+ "homepage": "https://github.com/zerosloney/dsh-system-one#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/zerosloney/dsh-system-one/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "lib/",
20
+ "cordis.patch.yml",
21
+ "README.md"
22
+ ],
23
+ "main": "./lib/index.js",
24
+ "types": "./lib/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./lib/index.d.ts",
28
+ "default": "./lib/index.js"
29
+ },
30
+ "./client": "./lib/client.js",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "dsh": {
34
+ "bundle": {
35
+ "patch": "cordis.patch.yml"
36
+ },
37
+ "client": {
38
+ "platform": "web",
39
+ "inject": [
40
+ "@deepseek-ai/dsh-client-ui-renderer",
41
+ "@deepseek-ai/dsh-client-locale"
42
+ ]
43
+ }
44
+ },
45
+ "engines": {
46
+ "node": ">=22.19"
47
+ },
48
+ "scripts": {
49
+ "check": "node --check lib/index.js && node --check lib/provider.js && node --check lib/tools.js && node --check lib/format.js && node --check lib/scenarios.js && node --check lib/auto.js && node --check lib/volatile.js && node --check lib/client.js",
50
+ "test": "node --test \"test/**/*.test.mjs\""
51
+ },
52
+ "dependencies": {
53
+ "@deepseek-ai/schemastery": "^3.18.1"
54
+ },
55
+ "peerDependencies": {
56
+ "@deepseek-ai/cordis": "^4.0.1",
57
+ "@deepseek-ai/schemastery": "^3.18.1"
58
+ }
59
+ }