dsh-superinput 0.7.0 → 0.12.1

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.
@@ -0,0 +1,413 @@
1
+ /* 由 scripts/build-suite.mjs 从 dsh-compaction-control/index.js 机械生成;勿手改,重跑脚本再生成。 */
2
+ /**
3
+ * dsh-compaction-control — 上下文压缩控制中心(DeepSeek Harness / dsh)
4
+ *
5
+ * 三件事:
6
+ * 1. 手动压缩:把官方 compaction 服务的 ctx.compaction.compactNow(agent, signal)
7
+ * 暴露成 HTTP 路由,客户端面板一键触发(等价 /compact,免打字)。
8
+ * 2. 调节自动压缩:把 {auto, thresholdRatio, retainRatio} 写进每个挂载了
9
+ * 本插件的 profile 的用户补丁层(~/.dsh/profiles/<name>/cordis.patch.yml,
10
+ * id 定向覆盖 compaction-basic 的 config;applyEntryPatches 后写者胜)。
11
+ * 补丁层是 initProfile 官方创建的用户文件,插件管理器不会回收它;
12
+ * 但 loader 只在启动时读取——保存后需重启 dsh 生效。
13
+ * 3. 状态源:~/.dsh/compaction-control.json 是唯一事实源(含 hideBuiltIn
14
+ * 等 UI 偏好),补丁文件由它派生,可随时重生成。
15
+ *
16
+ * 已核对的 dsh API(dsh-reference 源码对照):
17
+ * - ctx.agents.get(sessionId) → Agent(core/agent AgentRegistry,实现
18
+ * runMaintenance,满足 compaction 的 ManualCompactAgentContext)
19
+ * - ctx.compaction.compactNow(agent, signal) → CompactionResult |
20
+ * null;错误带 code: busy/cancelled/changed/summary/commit/persistence
21
+ * - BasicCompactionConfig 键集(compaction-basic/src/config.ts):
22
+ * auto/thresholdRatio/retainRatio/headroomTokens/maxTokens/
23
+ * summarizationProvider/summarizationModel/compactionRetries/
24
+ * maxOverflowRetries/modelPolicies —— 只写前三个,其余走默认
25
+ * - applyEntryPatches(vendor/include):{id, name, config} 直接整体
26
+ * 替换目标条目的 config;未知键会让 compaction-basic 加载即抛错,
27
+ * 所以这里的 config 永远是完整合法子集
28
+ */
29
+
30
+ import Schema from '@deepseek-ai/schemastery'
31
+ import { createUserMessage } from '@deepseek-ai/dsh-llm'
32
+ import { createRequire } from 'node:module'
33
+ import { appendFile, mkdir, readFile, readdir, writeFile } from 'node:fs/promises'
34
+ import { homedir } from 'node:os'
35
+ import { dirname, join } from 'node:path'
36
+
37
+ const require = createRequire(import.meta.url)
38
+
39
+ const name = 'compaction-control'
40
+
41
+ const DSH_DIR = join(homedir(), '.dsh')
42
+ const PROFILES_DIR = join(DSH_DIR, 'profiles')
43
+ const STATE_FILE = join(DSH_DIR, 'compaction-control.json')
44
+ const DIAG_LOG = join(DSH_DIR, 'compaction-control-debug.log')
45
+
46
+ /** 与 compaction-basic 的默认对齐(thresholdRatio 0.8 / retainRatio 0.16 / auto)。 */
47
+ const DEFAULT_STATE = { auto: true, thresholdRatio: 0.8, retainRatio: 0.16, hideBuiltIn: true, checkpoint: true }
48
+
49
+ const MANAGED_BEGIN = '# BEGIN dsh-compaction-control (managed)'
50
+ const MANAGED_END = '# END dsh-compaction-control'
51
+
52
+ /** 补丁文件语法校验用;运行时没带 yaml 就跳过校验(写入逻辑本身保守)。 */
53
+ let Yaml = null
54
+ try {
55
+ const mod = require('yaml')
56
+ if (typeof mod?.parse === 'function') Yaml = mod
57
+ } catch { Yaml = null }
58
+
59
+ export const Config = Schema.object({
60
+ /** 关闭后客户端不再注册圆环与面板(压缩行为恢复 compaction-basic 默认)。 */
61
+ enabled: Schema.boolean().default(true).description('是否启用上下文压缩控制'),
62
+ })
63
+
64
+ /** 校验并归一化一份状态;不合法直接抛中文错误给面板显示。 */
65
+ function sanitizeState(input) {
66
+ const ratio = (value, fallback, lo, hi, label) => {
67
+ const n = Number(value ?? fallback)
68
+ if (!Number.isFinite(n) || n < lo || n > hi) {
69
+ throw new Error(`${label} 需在 ${lo}~${hi} 之间`)
70
+ }
71
+ return Math.round(n * 1000) / 1000
72
+ }
73
+ const state = {
74
+ auto: input?.auto === undefined ? DEFAULT_STATE.auto : Boolean(input.auto),
75
+ thresholdRatio: ratio(input?.thresholdRatio, DEFAULT_STATE.thresholdRatio, 0.3, 0.99, '触发阈值'),
76
+ retainRatio: ratio(input?.retainRatio, DEFAULT_STATE.retainRatio, 0.02, 0.5, '保留比例'),
77
+ hideBuiltIn: input?.hideBuiltIn === undefined ? DEFAULT_STATE.hideBuiltIn : Boolean(input.hideBuiltIn),
78
+ checkpoint: input?.checkpoint === undefined ? DEFAULT_STATE.checkpoint : Boolean(input.checkpoint),
79
+ }
80
+ // compaction-basic 加载期硬约束:retainRatio >= thresholdRatio 直接抛错。
81
+ if (state.retainRatio >= state.thresholdRatio) {
82
+ throw new Error(`保留比例 (${state.retainRatio}) 必须小于触发阈值 (${state.thresholdRatio})`)
83
+ }
84
+ return state
85
+ }
86
+
87
+ async function readState() {
88
+ try {
89
+ return sanitizeState(JSON.parse(await readFile(STATE_FILE, 'utf8')))
90
+ } catch {
91
+ return { ...DEFAULT_STATE }
92
+ }
93
+ }
94
+
95
+ /** 列出挂载了本插件的 profile 名(manifest 里出现包名即算)。 */
96
+ async function listMountedProfiles() {
97
+ let entries = []
98
+ try { entries = await readdir(PROFILES_DIR, { withFileTypes: true }) } catch { return [] }
99
+ const mounted = []
100
+ for (const entry of entries) {
101
+ if (!entry.isDirectory()) continue
102
+ try {
103
+ const raw = await readFile(join(PROFILES_DIR, entry.name, 'package.json'), 'utf8')
104
+ if (raw.includes('dsh-compaction-control')) mounted.push(entry.name)
105
+ } catch { /* 无 manifest 的目录不是 profile */ }
106
+ }
107
+ return mounted.sort()
108
+ }
109
+
110
+ function managedBlock(state) {
111
+ return [
112
+ MANAGED_BEGIN,
113
+ '- id: compaction-basic',
114
+ " name: '@deepseek-ai/dsh-compaction-basic'",
115
+ ' config:',
116
+ ` auto: ${state.auto}`,
117
+ ` thresholdRatio: ${state.thresholdRatio}`,
118
+ ` retainRatio: ${state.retainRatio}`,
119
+ MANAGED_END,
120
+ ].join('\n')
121
+ }
122
+
123
+ /** 纯文本手术:由现有内容与目标状态算出下一版补丁文件内容(不落盘)。 */
124
+ function composePatch(raw, state) {
125
+ const begin = raw.indexOf(MANAGED_BEGIN)
126
+ const end = raw.indexOf(MANAGED_END)
127
+ if (begin !== -1 && end !== -1 && end > begin) {
128
+ return raw.slice(0, begin) + managedBlock(state) + raw.slice(end + MANAGED_END.length)
129
+ }
130
+ const stripped = raw
131
+ .split('\n')
132
+ .filter((line) => line.trim() !== '' && !line.trim().startsWith('#'))
133
+ .join('')
134
+ .trim()
135
+ if (stripped === '[]') {
136
+ return raw.replace('[]', managedBlock(state))
137
+ }
138
+ if (stripped === '') {
139
+ return `${managedBlock(state)}\n`
140
+ }
141
+ const base = raw.replace(/\s+$/, '')
142
+ return `${base}\n${managedBlock(state)}\n`
143
+ }
144
+
145
+ /** 移除受管块后的剩余内容;没有受管块时返回 null(无需写回)。 */
146
+ function stripManaged(raw) {
147
+ const begin = raw.indexOf(MANAGED_BEGIN)
148
+ const end = raw.indexOf(MANAGED_END)
149
+ if (begin === -1 || end === -1 || end < begin) return null
150
+ return raw.slice(0, begin) + raw.slice(end + MANAGED_END.length)
151
+ }
152
+
153
+ /** 官方 initProfile 的用户补丁层模板(移除后文件为空时回填)。 */
154
+ const PATCH_TEMPLATE = '# Your patch layer for this dsh profile, applied after every bundle layer:\n'
155
+ + '# a top-level YAML array of loader patch entries (id-targeted config\n'
156
+ + '# overrides, disables, and insert lists; `!!js` expressions allowed).\n'
157
+ + '[]\n'
158
+
159
+ /** 恢复默认:从 profile 补丁层移除受管块,让 compaction-basic 回落自身默认。 */
160
+ async function clearProfilePatch(profileName, report) {
161
+ const file = join(PROFILES_DIR, profileName, 'cordis.patch.yml')
162
+ let raw = ''
163
+ try { raw = await readFile(file, 'utf8') } catch { return }
164
+ const strippedManaged = stripManaged(raw)
165
+ if (strippedManaged === null) return
166
+ const stripped = strippedManaged
167
+ .split('\n')
168
+ .filter((line) => line.trim() !== '' && !line.trim().startsWith('#'))
169
+ .join('')
170
+ .trim()
171
+ const next = stripped === ''
172
+ ? PATCH_TEMPLATE
173
+ : `${strippedManaged.replace(/\s+$/, '')}\n`
174
+ if (Yaml) {
175
+ try {
176
+ const parsed = Yaml.parse(next)
177
+ if (!Array.isArray(parsed)) throw new Error('not an array')
178
+ } catch (error) {
179
+ throw new Error(`${profileName}/cordis.patch.yml 语法校验失败,未写入:${String(error?.message ?? error)}`)
180
+ }
181
+ } else {
182
+ report(`yaml lib unavailable, rewrote ${profileName}/cordis.patch.yml without validation`)
183
+ }
184
+ await writeFile(file, next, 'utf8')
185
+ return file
186
+ }
187
+
188
+ /**
189
+ * 把受管块写入一个 profile 的 cordis.patch.yml。
190
+ * 纯文本手术:只动 BEGIN/END 标记之间的内容,用户自己写的其他条目原样保留;
191
+ * 全新文件(官方模板是注释 + `[]`)用受管块替换 `[]`。写前用 yaml 库校验
192
+ * (如果运行时带了这个库),校验失败不落盘。
193
+ */
194
+ async function syncProfilePatch(profileName, state, report) {
195
+ const file = join(PROFILES_DIR, profileName, 'cordis.patch.yml')
196
+ let raw = ''
197
+ try { raw = await readFile(file, 'utf8') } catch { raw = '' }
198
+ const next = composePatch(raw, state)
199
+
200
+ if (Yaml) {
201
+ try {
202
+ const parsed = Yaml.parse(next)
203
+ if (!Array.isArray(parsed)) throw new Error('not an array')
204
+ } catch (error) {
205
+ throw new Error(`${profileName}/cordis.patch.yml 语法校验失败,未写入:${String(error?.message ?? error)}`)
206
+ }
207
+ } else {
208
+ report(`yaml lib unavailable, wrote ${profileName}/cordis.patch.yml without validation`)
209
+ }
210
+
211
+ await writeFile(file, next, 'utf8')
212
+ return file
213
+ }
214
+
215
+ /** 把状态同步进所有挂载 profile;返回写入的 profile 名单。 */
216
+ async function syncAllProfiles(state, report) {
217
+ const profiles = await listMountedProfiles()
218
+ const written = []
219
+ for (const profileName of profiles) {
220
+ await syncProfilePatch(profileName, state, report)
221
+ written.push(profileName)
222
+ }
223
+ return written
224
+ }
225
+
226
+ export function apply(ctx, config) {
227
+ const say = (message) => {
228
+ try { console.info(`[dsh-compaction-control] ${message}`) } catch { /* ignore */ }
229
+ }
230
+ const report = async (line) => {
231
+ try {
232
+ await mkdir(dirname(DIAG_LOG), { recursive: true })
233
+ await appendFile(DIAG_LOG, `${new Date().toISOString()} ${line}\n`, 'utf8')
234
+ } catch { /* ignore */ }
235
+ }
236
+
237
+ say(`server boot, enabled=${config.enabled !== false}`)
238
+ void report(`server boot enabled=${config.enabled !== false}`)
239
+
240
+ // 压缩前自动落档:客户端圆环在占用率上穿「阈值-5%」时布防,本进程在
241
+ // 该会话下一次模型调用前往消息尾追加落档指令——agent 用自己的文件工具
242
+ // 把项目状态写进工作区 checkpoint.md,随后 compaction-basic 照常压缩。
243
+ // 只注入一次(注入即解除布防;占用回落后客户端重新布防)。
244
+ const armedSessions = new Set()
245
+ const CHECKPOINT_MARKER = 'dsh-auto-checkpoint'
246
+ const CHECKPOINT_TEXT = [
247
+ '【自动落档】上下文即将压缩。请先把当前任务状态写入工作区文件 `checkpoint.md`(覆盖旧内容):',
248
+ '任务目标、已完成事项、待办事项、关键决定与约束、涉及的重要文件路径。',
249
+ '写完落档后,再回答上一条消息的问题。(dsh-auto-checkpoint)',
250
+ ].join('\n')
251
+
252
+ ctx.on('agent/pre-step', async ({ agent, signal }, next) => {
253
+ const decision = await next()
254
+ try {
255
+ if (!decision || decision.kind !== 'enter' || !Array.isArray(decision.messages)) return decision
256
+ const sessionId = String(agent?.sessionId ?? '')
257
+ if (!sessionId || !armedSessions.has(sessionId)) return decision
258
+ const state = await readState()
259
+ if (state.checkpoint === false) { armedSessions.delete(sessionId); return decision }
260
+ const tail = decision.messages.slice(-3).map((m) => JSON.stringify(m?.content ?? '')).join('')
261
+ armedSessions.delete(sessionId)
262
+ if (tail.includes(CHECKPOINT_MARKER)) return decision
263
+ const message = createUserMessage({ content: [{ type: 'text', text: CHECKPOINT_TEXT }] })
264
+ await report(`checkpoint injected session=${sessionId} msgs=${decision.messages.length}`)
265
+ return { ...decision, messages: [...decision.messages, message] }
266
+ } catch (error) {
267
+ await report(`checkpoint pre-step failed: ${String(error?.message ?? error).slice(0, 200)}`)
268
+ return decision
269
+ }
270
+ })
271
+
272
+ // compaction/agents 属于可选依赖:base bundle 没带它们时路由仍注册,
273
+ // /compact 返回 503,面板其余功能(状态展示、参数保存)不受影响。
274
+ let runtime = null
275
+
276
+ ctx.inject(['webServer'], (webCtx) => {
277
+ webCtx.effect(() => webCtx.webServer.register({
278
+ kind: 'prefix',
279
+ path: '/api/dsh-compaction-control',
280
+ handler: async (req, res) => {
281
+ const send = (status, body) => {
282
+ res.statusCode = status
283
+ res.setHeader('content-type', 'application/json')
284
+ res.end(JSON.stringify(body))
285
+ }
286
+ const readBody = async () => {
287
+ let raw = ''
288
+ for await (const chunk of req) raw += chunk
289
+ try { return raw ? JSON.parse(raw) : {} } catch { return null }
290
+ }
291
+ const path = (req.url ?? '').split('?')[0].replace(/^\/api\/dsh-compaction-control/, '')
292
+
293
+ if (path === '/enabled' && req.method === 'GET') {
294
+ send(200, { enabled: config.enabled !== false, name })
295
+ return
296
+ }
297
+
298
+ if (path === '/diag' && req.method === 'POST') {
299
+ await report(String((await readBody())?.line ?? '').slice(0, 2000))
300
+ send(200, { ok: true })
301
+ return
302
+ }
303
+
304
+ // 压缩前落档布防:客户端占用率上穿阈值-5% 时 arm=true(升沿只发一次),
305
+ // 占用回落后 arm=false。服务端只记标记,注入发生在下一次 pre-step。
306
+ if (path === '/checkpoint-arm' && req.method === 'POST') {
307
+ const body = await readBody()
308
+ const sessionId = String(body?.sessionId ?? '')
309
+ if (!sessionId) { send(400, { error: '缺少 sessionId' }); return }
310
+ const arm = body?.arm === true
311
+ const state = await readState()
312
+ if (arm && state.checkpoint === false) { send(200, { ok: true, armed: false, reason: 'checkpoint-disabled' }); return }
313
+ if (arm) armedSessions.add(sessionId)
314
+ else armedSessions.delete(sessionId)
315
+ await report(`checkpoint ${arm ? 'armed' : 'disarmed'} session=${sessionId}`)
316
+ send(200, { ok: true, armed })
317
+ return
318
+ }
319
+
320
+ if (path === '/state' && req.method === 'GET') {
321
+ try {
322
+ const [state, profiles] = await Promise.all([readState(), listMountedProfiles()])
323
+ send(200, { ok: true, state, defaults: DEFAULT_STATE, profiles, version: '0.1.0' })
324
+ } catch (error) {
325
+ send(500, { error: String(error?.message ?? error) })
326
+ }
327
+ return
328
+ }
329
+
330
+ if (path === '/state' && req.method === 'POST') {
331
+ const body = await readBody()
332
+ if (body === null) { send(400, { error: '请求体不是合法 JSON' }); return }
333
+ try {
334
+ const state = sanitizeState(body)
335
+ await mkdir(DSH_DIR, { recursive: true })
336
+ await writeFile(STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
337
+ const profiles = await syncAllProfiles(state, report)
338
+ await report(`state saved ${JSON.stringify(state)} profiles=${profiles.join(',') || 'none'}`)
339
+ send(200, { ok: true, state, profiles, restartRequired: true })
340
+ } catch (error) {
341
+ send(400, { error: String(error?.message ?? error) })
342
+ }
343
+ return
344
+ }
345
+
346
+ if (path === '/reset' && req.method === 'POST') {
347
+ try {
348
+ const state = { ...DEFAULT_STATE }
349
+ await mkdir(DSH_DIR, { recursive: true })
350
+ await writeFile(STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
351
+ const profiles = await listMountedProfiles()
352
+ for (const profileName of profiles) await clearProfilePatch(profileName, report)
353
+ await report(`state reset to defaults, managed block removed from: ${profiles.join(',') || 'none'}`)
354
+ send(200, { ok: true, state, profiles, restartRequired: true })
355
+ } catch (error) {
356
+ send(400, { error: String(error?.message ?? error) })
357
+ }
358
+ return
359
+ }
360
+
361
+ if (path === '/compact' && req.method === 'POST') {
362
+ if (!runtime) { send(503, { error: '压缩服务未就绪(本 profile 未挂载 compaction/agents)' }); return }
363
+ const body = await readBody()
364
+ const sessionId = body?.sessionId
365
+ if (!sessionId) { send(400, { error: '缺少 sessionId' }); return }
366
+ const agent = runtime.agents.get(sessionId)
367
+ if (!agent) { send(409, { error: '未找到该会话的 agent' }); return }
368
+ const controller = new AbortController()
369
+ try {
370
+ const result = await runtime.compaction.compactNow(agent, controller.signal)
371
+ if (result === null) {
372
+ send(200, { ok: true, empty: true, message: '暂无可压缩的历史' })
373
+ } else {
374
+ await report(`manual compact ok: ${result.shadowedSeqs.length} items ~${result.shadowedTokenCount} tokens`)
375
+ send(200, {
376
+ ok: true,
377
+ shadowed: result.shadowedSeqs.length,
378
+ tokens: result.shadowedTokenCount,
379
+ })
380
+ }
381
+ } catch (error) {
382
+ const code = typeof error?.code === 'string' ? error.code.toLowerCase() : ''
383
+ const messages = {
384
+ busy: '会话正忙(运行中或已有压缩任务),请在空闲时重试',
385
+ cancelled: '压缩已取消',
386
+ changed: '待压缩历史在提交前发生了变化,本次已放弃并记录',
387
+ summary: '未能生成有效摘要,本次已记录',
388
+ commit: '压缩未干净完成,请检查会话状态后再试',
389
+ persistence: '压缩完成,但会话保存失败',
390
+ }
391
+ await report(`manual compact failed: code=${code || 'unknown'} ${String(error?.message ?? error).slice(0, 300)}`)
392
+ if (messages[code]) { send(409, { error: messages[code], code }); return }
393
+ send(500, { error: String(error?.message ?? error) })
394
+ }
395
+ return
396
+ }
397
+
398
+ send(404, { error: 'not-found' })
399
+ },
400
+ }), 'dsh-compaction-control: /api/dsh-compaction-control/*')
401
+
402
+ webCtx.inject(['compaction', 'agents'], (deep) => {
403
+ runtime = { compaction: deep.compaction, agents: deep.agents }
404
+ say('compaction + agents ready')
405
+ void report('runtime ready (compaction + agents)')
406
+ return () => { runtime = null }
407
+ })
408
+ })
409
+ }
410
+
411
+
412
+ /** 仅供离线自测(scripts/ 与 CI 可直接 import),运行时不使用。 */
413
+ export const __internals = { sanitizeState, managedBlock, composePatch, stripManaged, PATCH_TEMPLATE, DEFAULT_STATE }