@master0071/dsh-systemone 0.4.3 → 0.4.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.
package/lib/auto.js CHANGED
@@ -64,6 +64,8 @@ export function installAutoDecide(ctx, deps) {
64
64
  if (!readBoolean(config, 'autoDecide', false)) return () => {}
65
65
 
66
66
  const disposers = []
67
+ /** agentId → 该 agent 的 per-agent 清理函数;agent 销毁时一并调用并从表中移除,避免无界增长。 */
68
+ const agentDisposers = new Map()
67
69
  /** agentId → { stateKey, text, scenarioId, source, headline, at }:供 context 通道渲染。 */
68
70
  const perAgent = new Map()
69
71
  /** agentId → 上次注入的决策文本:同一决策只注入一次,避免每步重复污染历史。 */
@@ -84,12 +86,23 @@ export function installAutoDecide(ctx, deps) {
84
86
  }
85
87
  }
86
88
 
87
- // agent 销毁时清理状态,避免长会话内存泄漏
89
+ // agent 销毁时清理状态与 per-agent 监听器,避免长会话内存泄漏
88
90
  disposers.push(ctx.on('agent/disposed', (payload) => {
89
91
  const id = payload?.agent?.id
90
92
  if (id !== undefined) {
91
93
  perAgent.delete(id)
92
94
  lastInjected.delete(id)
95
+ const list = agentDisposers.get(id)
96
+ if (list) {
97
+ for (const dispose of list) {
98
+ try {
99
+ dispose()
100
+ } catch {
101
+ // 卸载竞态
102
+ }
103
+ }
104
+ agentDisposers.delete(id)
105
+ }
93
106
  }
94
107
  }))
95
108
 
@@ -101,10 +114,13 @@ export function installAutoDecide(ctx, deps) {
101
114
  const agent = payload?.agent
102
115
  const agentCtx = agent?.ctx
103
116
  if (!agentCtx) return
117
+ const agentId = agent?.id
118
+ // 该 agent 的监听器与动态上下文注册统一记到这里,随 agent 销毁一起移除
119
+ const perAgentDisposers = []
104
120
  // ① 消息一入箱就异步预计算,让决策尽可能在 assembly 之前就绪
105
121
  if (typeof agentCtx.on === 'function') {
106
122
  try {
107
- disposers.push(agentCtx.on('agent/inbox/inserted', (event) => {
123
+ const dispose = agentCtx.on('agent/inbox/inserted', (event) => {
108
124
  if (readString(config, 'autoInject', 'message') !== 'context') return
109
125
  const newText = extractUserText([event?.message])
110
126
  if (!newText) return
@@ -112,7 +128,8 @@ export function installAutoDecide(ctx, deps) {
112
128
  if (isTrivialInput(newText, readNumber(config, 'autoMinChars', AUTO_DEFAULTS.autoMinChars))) return
113
129
  // 返回 promise 便于测试等待;宿主事件总线忽略返回值
114
130
  return computeAndStore(agent, newText)
115
- }))
131
+ })
132
+ if (typeof dispose === 'function') perAgentDisposers.push(dispose)
116
133
  } catch (error) {
117
134
  log('warn', `注册入箱预计算失败:${messageOf(error)}`)
118
135
  }
@@ -120,17 +137,36 @@ export function installAutoDecide(ctx, deps) {
120
137
  // ② 把最新决策渲染进作用域动态上下文
121
138
  if (agentCtx?.systemPrompt?.context) {
122
139
  try {
123
- disposers.push(agentCtx.systemPrompt.context({
140
+ const dispose = agentCtx.systemPrompt.context({
124
141
  name: 'systemone-auto-decision',
125
142
  order: 900,
126
143
  text: () => (readString(config, 'autoInject', 'message') === 'context')
127
144
  ? (perAgent.get(agent?.id)?.text || '')
128
145
  : '',
129
- }))
146
+ })
147
+ if (typeof dispose === 'function') perAgentDisposers.push(dispose)
130
148
  } catch (error) {
131
149
  log('warn', `注册动态上下文失败:${messageOf(error)}`)
132
150
  }
133
151
  }
152
+ // 记入 per-agent 表:agent 销毁时移除;同一 agent 重复创建时先清理旧条目。
153
+ // agentId 缺失时退回插件级 disposers,随插件卸载统一清理。
154
+ if (perAgentDisposers.length === 0) return
155
+ if (agentId === undefined) {
156
+ disposers.push(...perAgentDisposers)
157
+ return
158
+ }
159
+ const existing = agentDisposers.get(agentId)
160
+ if (existing) {
161
+ for (const dispose of existing) {
162
+ try {
163
+ dispose()
164
+ } catch {
165
+ // 重复创建竞态
166
+ }
167
+ }
168
+ }
169
+ agentDisposers.set(agentId, perAgentDisposers)
134
170
  }))
135
171
 
136
172
  // ── pre-step:同一步生效(仅 message 通道) ──
@@ -243,6 +279,17 @@ export function installAutoDecide(ctx, deps) {
243
279
  // 卸载竞态
244
280
  }
245
281
  }
282
+ // 兜底:即使宿主未触发 agent/disposed,也清掉所有 per-agent 监听器
283
+ for (const list of agentDisposers.values()) {
284
+ for (const dispose of list) {
285
+ try {
286
+ dispose()
287
+ } catch {
288
+ // 卸载竞态
289
+ }
290
+ }
291
+ }
292
+ agentDisposers.clear()
246
293
  perAgent.clear()
247
294
  lastInjected.clear()
248
295
  cache.clear()
package/lib/index.d.ts CHANGED
@@ -43,6 +43,9 @@ export interface Config {
43
43
  autoMinChars?: number
44
44
  }
45
45
 
46
+ /** 运行时导出的配置 schema(schemastery 对象),与上面的接口同名。 */
47
+ export const Config: any
48
+
46
49
  /** 编程式调用 SystemOne 的参数。 */
47
50
  export interface DecideOptions {
48
51
  state: unknown
package/lib/provider.js CHANGED
@@ -84,7 +84,11 @@ export class HttpProvider {
84
84
  timer.unref?.()
85
85
  const onOuterAbort = () => controller.abort()
86
86
  if (signal && typeof signal.addEventListener === 'function') {
87
- signal.addEventListener('abort', onOuterAbort, { once: true })
87
+ if (signal.aborted) {
88
+ controller.abort()
89
+ } else {
90
+ signal.addEventListener('abort', onOuterAbort, { once: true })
91
+ }
88
92
  }
89
93
 
90
94
  try {
package/lib/tools.js CHANGED
@@ -296,9 +296,12 @@ function applyParams(questions, params) {
296
296
  // criteria 整体替换;addCriteria 在替换结果之上追加/覆盖单个选项
297
297
  if (override.criteria) {
298
298
  if (question.type === 'choice' && !Array.isArray(override.criteria)) {
299
- next.criteria = Object.fromEntries(
300
- Object.entries(override.criteria).map(([k, v]) => [String(k), String(v)]),
301
- )
299
+ const entries = Object.entries(override.criteria)
300
+ if (entries.length < 2) {
301
+ ignored.push(`${qid}.criteria(choice 至少需要 2 个选项)`)
302
+ } else {
303
+ next.criteria = Object.fromEntries(entries.map(([k, v]) => [String(k), String(v)]))
304
+ }
302
305
  } else if (question.type === 'score' && Array.isArray(override.criteria) && override.criteria.length >= 2) {
303
306
  next.criteria = override.criteria.map((v) => String(v))
304
307
  } else {
package/lib/volatile.js CHANGED
@@ -36,9 +36,11 @@ export function readString(config, key, fallback = '') {
36
36
  return text === '' ? fallback : text
37
37
  }
38
38
 
39
- /** 读取数值配置(去壳 + 非法值兜底)。 */
39
+ /** 读取数值配置(去壳 + 空值/非法值兜底)。 */
40
40
  export function readNumber(config, key, fallback) {
41
- const value = Number(unwrap(config?.[key]))
41
+ const raw = unwrap(config?.[key])
42
+ if (raw === undefined || raw === null || raw === '') return fallback
43
+ const value = Number(raw)
42
44
  return Number.isFinite(value) ? value : fallback
43
45
  }
44
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@master0071/dsh-systemone",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "DeepSeek Harness 插件:将 SystemOne 决策模型(默认云知声 u2-decision,可切换提供商)接入软件开发、客服、内容审核、路由、销售线索、风控、招聘、数据治理、教育、需求变更等 10 大业务场景,为 Agent 返回带概率分布的决策与处置建议。",
5
5
  "license": "MIT",
6
6
  "type": "module",