@shgroup/dsh-serenity-hooks 1.26.13 → 1.26.14
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/dsh.plugin.json +1 -1
- package/experiments/autotrajectory/scripts/autotrajectory-exp.ts +123 -1
- package/lib/client.js +117 -1
- package/lib/index.js +208 -177
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.14",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* acc_msm exec autotrajectory-exp → 一站式全报告(背景 + 就绪 + 状态 + 下一步)
|
|
7
7
|
* acc_msm exec autotrajectory-exp init → 初始化实验(写配置 + 生成偏见提供者脚本模板)
|
|
8
8
|
* acc_msm exec autotrajectory-exp random → 运行偏见提供者脚本,输出当前偏见内容(验证)
|
|
9
|
+
* acc_msm exec autotrajectory-exp diag → 唤起条件链诊断(--ccc <path> 指定;无参递归扫描 /home/yh 两层)
|
|
9
10
|
* acc_msm exec autotrajectory-exp doc → 实验定义说明全文(SKILL.md)
|
|
10
11
|
* acc_msm exec autotrajectory-exp check → 仅就绪度检查
|
|
11
12
|
* acc_msm exec autotrajectory-exp status → 仅当前状态
|
|
@@ -15,6 +16,9 @@
|
|
|
15
16
|
* stdout 输出本轮唤起要注入的偏见内容(反事实方向/探索动机等)。tool 直接运行它;
|
|
16
17
|
* 脚本缺失 → 报错要求实现(不再经 mech-registry 注册 MSM)。
|
|
17
18
|
*
|
|
19
|
+
* 状态查看:WebUI 设置面板「自主轨迹」区块(GET /serenity/autotrajectory)+「立即唤起」按钮
|
|
20
|
+
* (POST /serenity/autotrajectory {action:'wake'},调试语义跳过窗口/间隔)——由插件提供,脚本不重复。
|
|
21
|
+
*
|
|
18
22
|
* 零 DSH 依赖,任何 CCC 可运行。环境:SERENITY_ROOT(acc_msm exec 注入)或 cwd 上溯 .serenity。
|
|
19
23
|
*/
|
|
20
24
|
|
|
@@ -343,6 +347,121 @@ function all(root: string): string {
|
|
|
343
347
|
return [BACKGROUND, ready, '', stat, '', next.join('\n'), '', guide()].join('\n')
|
|
344
348
|
}
|
|
345
349
|
|
|
350
|
+
/** 唤起条件链诊断(单 CCC)——逐条件当前值 + 阻断点 + 修复建议 */
|
|
351
|
+
function diagCcc(root: string): string {
|
|
352
|
+
const now = Date.now()
|
|
353
|
+
const lines: string[] = [`═══ 自主轨迹唤起诊断:${basename(root)}(${root})═══`]
|
|
354
|
+
const cfg = loadConfig(root)
|
|
355
|
+
const blocks: string[] = []
|
|
356
|
+
const suggest: string[] = []
|
|
357
|
+
|
|
358
|
+
// ① enabled(定时器是否启动)
|
|
359
|
+
if (!cfg) {
|
|
360
|
+
blocks.push('✗ autotrajectory 未配置(定时器不启动)')
|
|
361
|
+
suggest.push('配置:.opencode/serenity.json 加 autotrajectory 段(autotrajectory-exp init 可生成)')
|
|
362
|
+
} else if (!cfg.enabled) {
|
|
363
|
+
blocks.push('✗ enabled = false(定时器不启动)')
|
|
364
|
+
suggest.push('置 enabled: true')
|
|
365
|
+
} else {
|
|
366
|
+
blocks.push('✓ enabled = true')
|
|
367
|
+
}
|
|
368
|
+
if (cfg) {
|
|
369
|
+
blocks.push(` 配置: intervalHours=${cfg.intervalHours ?? 12} | biasProvider=${cfg.biasProvider?.trim() || DEFAULT_BIAS_PROVIDER}${cfg.session ? ` | session=${cfg.session}` : ' | session=未配置'}`)
|
|
370
|
+
const avoid = cfg.avoidWakeHours
|
|
371
|
+
blocks.push(` 窗口避开北京 ${avoid?.start ?? 8}~${avoid?.end ?? 18} 点`)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ② session(必填——不默认任何会话)
|
|
375
|
+
const md = resolveTargetMd(root, cfg ?? {})
|
|
376
|
+
if (!cfg?.session) {
|
|
377
|
+
blocks.push('✗ session 未配置(必填——自动唤起不默认任何会话)')
|
|
378
|
+
suggest.push('配置 session: "S###"(目标会话 S###/目录名)')
|
|
379
|
+
} else if (!md) {
|
|
380
|
+
blocks.push(`✗ session=${cfg.session} 未命中(AGENT_SESSIONS 无匹配目录)`)
|
|
381
|
+
suggest.push('确认目标会话存在(目录名含该关键字)')
|
|
382
|
+
} else {
|
|
383
|
+
blocks.push(`✓ session=${cfg.session} 命中(${basename(dirname(md))})`)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ③ --auto 会话标志
|
|
387
|
+
if (md) {
|
|
388
|
+
const flag = basename(dirname(md)).endsWith(AUTO_SUFFIX)
|
|
389
|
+
if (flag) {
|
|
390
|
+
blocks.push('✓ 会话目录带 --auto 标志')
|
|
391
|
+
} else {
|
|
392
|
+
blocks.push(`✗ 会话目录无 --auto 标志(${basename(dirname(md))})`)
|
|
393
|
+
suggest.push('目录名加 --auto 后缀:AGENT_SESSIONS/<date>--<desc>--auto/')
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ④ mtime 间隔(无人类活动时长)
|
|
398
|
+
if (md) {
|
|
399
|
+
const mtime = statSync(md).mtimeMs
|
|
400
|
+
const idle = (now - mtime) / 3600_000
|
|
401
|
+
const interval = Math.max(1, cfg?.intervalHours ?? 12)
|
|
402
|
+
if (idle >= interval) blocks.push(`✓ 距上次轨迹活动 ${idle.toFixed(1)}h ≥ 阈值 ${interval}h`)
|
|
403
|
+
else blocks.push(`⏸ 距上次轨迹活动 ${idle.toFixed(1)}h < 阈值 ${interval}h(等待中——不满足不唤起)`)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ⑤ 北京时间唤起窗口
|
|
407
|
+
const h = beijingHour(now)
|
|
408
|
+
const inWin = inAllowedWindow(now, cfg?.avoidWakeHours)
|
|
409
|
+
blocks.push(`${inWin ? '✓' : '⏸'} 当前北京 ${h} 点——${inWin ? '在唤起窗口内' : '高峰避开中(缺省 8~18,用量峰谷省钱)'}`)
|
|
410
|
+
|
|
411
|
+
// ⑥ 偏见内容提供者脚本
|
|
412
|
+
if (cfg?.enabled) {
|
|
413
|
+
const provider = cfg.biasProvider?.trim() || DEFAULT_BIAS_PROVIDER
|
|
414
|
+
const bias = runBiasProvider(root, provider)
|
|
415
|
+
if (bias.error) {
|
|
416
|
+
blocks.push(`✗ 偏见脚本: ${bias.error}`)
|
|
417
|
+
suggest.push('实现偏见脚本(autotrajectory-exp init 生成模板后编辑)')
|
|
418
|
+
} else {
|
|
419
|
+
blocks.push(`✓ 偏见脚本可运行(输出: ${bias.text ?? '(空)'})`)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
lines.push(...blocks.map((b) => ` ${b}`))
|
|
424
|
+
lines.push('')
|
|
425
|
+
const bad = blocks.filter((b) => b.startsWith('✗'))
|
|
426
|
+
if (bad.length === 0) {
|
|
427
|
+
lines.push('✅ 唤起条件全部满足——等待下一个 10min tick 自动唤起(前台可见)')
|
|
428
|
+
} else {
|
|
429
|
+
lines.push(`⚠️ 阻断点 ${bad.length} 项(全部满足才唤起):`)
|
|
430
|
+
for (const s of suggest) lines.push(` → ${s}`)
|
|
431
|
+
}
|
|
432
|
+
return lines.join('\n')
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** diag:--ccc <path> 指定目标 CCC;无参扫描常见 CCC 根(配置了 autotrajectory 的优先) */
|
|
436
|
+
function diag(): string {
|
|
437
|
+
const argIdx = process.argv.indexOf('--ccc')
|
|
438
|
+
if (argIdx >= 0 && process.argv[argIdx + 1]) {
|
|
439
|
+
return diagCcc(resolve(process.argv[argIdx + 1]!))
|
|
440
|
+
}
|
|
441
|
+
const roots = new Set<string>()
|
|
442
|
+
const envRoot = process.env.SERENITY_ROOT
|
|
443
|
+
if (envRoot && existsSync(join(envRoot, '.serenity'))) roots.add(envRoot)
|
|
444
|
+
// 递归扫描 /home/yh 两层(覆盖 /home/yh/home/*、/home/yh/our-home/* 及任意实验 CCC 位置)
|
|
445
|
+
for (const base of ['/home/yh']) {
|
|
446
|
+
collectCccs(base, 0, roots, 2)
|
|
447
|
+
}
|
|
448
|
+
const list = [...roots]
|
|
449
|
+
if (list.length === 0) return '[autotrajectory-exp diag] 未发现 CCC(用 --ccc <path> 指定目标)'
|
|
450
|
+
const ordered = [...list.filter((r) => loadConfig(r)?.enabled), ...list.filter((r) => !loadConfig(r)?.enabled)]
|
|
451
|
+
return ordered.map(diagCcc).join('\n\n')
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** 递归收集 .serenity 标记目录(跳过隐藏目录;maxDepth 层内) */
|
|
455
|
+
function collectCccs(dir: string, depth: number, out: Set<string>, maxDepth: number): void {
|
|
456
|
+
if (depth > maxDepth || !existsSync(dir)) return
|
|
457
|
+
for (const d of readdirSync(dir, { withFileTypes: true })) {
|
|
458
|
+
if (!d.isDirectory() || d.name.startsWith('.')) continue
|
|
459
|
+
const r = join(dir, d.name)
|
|
460
|
+
if (existsSync(join(r, '.serenity'))) out.add(r)
|
|
461
|
+
collectCccs(r, depth + 1, out, maxDepth)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
346
465
|
function main(): void {
|
|
347
466
|
const cmd = process.argv[2] ?? 'all'
|
|
348
467
|
const root = findRoot()
|
|
@@ -360,6 +479,9 @@ function main(): void {
|
|
|
360
479
|
case 'random':
|
|
361
480
|
console.log(random(root))
|
|
362
481
|
break
|
|
482
|
+
case 'diag':
|
|
483
|
+
console.log(diag())
|
|
484
|
+
break
|
|
363
485
|
case 'doc':
|
|
364
486
|
console.log(doc(root))
|
|
365
487
|
break
|
|
@@ -373,7 +495,7 @@ function main(): void {
|
|
|
373
495
|
console.log(guide())
|
|
374
496
|
break
|
|
375
497
|
default:
|
|
376
|
-
console.error(`[autotrajectory-exp] 未知子命令: ${cmd}(可用: 无参一站式 / init / random / doc / check / status / guide)`)
|
|
498
|
+
console.error(`[autotrajectory-exp] 未知子命令: ${cmd}(可用: 无参一站式 / init / random / diag / doc / check / status / guide)`)
|
|
377
499
|
process.exit(2)
|
|
378
500
|
}
|
|
379
501
|
}
|
package/lib/client.js
CHANGED
|
@@ -4099,7 +4099,7 @@ Instruction-following style:
|
|
|
4099
4099
|
}
|
|
4100
4100
|
//#endregion
|
|
4101
4101
|
//#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
|
|
4102
|
-
const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n";
|
|
4102
|
+
const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.3));\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary, #eee);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-fill-l1, rgba(128, 128, 128, 0.08));\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n";
|
|
4103
4103
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
|
|
4104
4104
|
const tag = document.createElement("style");
|
|
4105
4105
|
tag.setAttribute("data-sp-css", "SettingsSection");
|
|
@@ -4339,10 +4339,126 @@ Instruction-following style:
|
|
|
4339
4339
|
className: "ss-groupTitle",
|
|
4340
4340
|
children: "彩蛋模式"
|
|
4341
4341
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PersonaEditor, {})]
|
|
4342
|
+
}),
|
|
4343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4344
|
+
className: "ss-group",
|
|
4345
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
4346
|
+
className: "ss-groupTitle",
|
|
4347
|
+
children: "自主轨迹"
|
|
4348
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTrajectoryStatusBlock, {})]
|
|
4342
4349
|
})
|
|
4343
4350
|
]
|
|
4344
4351
|
});
|
|
4345
4352
|
}
|
|
4353
|
+
/** 「自主轨迹」只读状态区块(v1.26.14):展示当前工作区 CCC 的实验状态;实验配置改走 CCC 配置文件 */
|
|
4354
|
+
function AutoTrajectoryStatusBlock() {
|
|
4355
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
4356
|
+
const [unavailable, setUnavailable] = (0, react.useState)(false);
|
|
4357
|
+
const [waking, setWaking] = (0, react.useState)(false);
|
|
4358
|
+
const [wakeResult, setWakeResult] = (0, react.useState)(null);
|
|
4359
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
4360
|
+
try {
|
|
4361
|
+
const res = await fetch("/serenity/autotrajectory", { headers: { accept: "application/json" } });
|
|
4362
|
+
if (!res.ok) return;
|
|
4363
|
+
const body = await res.json();
|
|
4364
|
+
if (body.status) {
|
|
4365
|
+
setStatus(body.status);
|
|
4366
|
+
setUnavailable(false);
|
|
4367
|
+
} else {
|
|
4368
|
+
setStatus(null);
|
|
4369
|
+
setUnavailable(true);
|
|
4370
|
+
}
|
|
4371
|
+
} catch {}
|
|
4372
|
+
}, []);
|
|
4373
|
+
(0, react.useEffect)(() => {
|
|
4374
|
+
refresh();
|
|
4375
|
+
return () => {};
|
|
4376
|
+
}, [refresh]);
|
|
4377
|
+
const wakeNow = async () => {
|
|
4378
|
+
if (waking) return;
|
|
4379
|
+
setWaking(true);
|
|
4380
|
+
setWakeResult(null);
|
|
4381
|
+
try {
|
|
4382
|
+
const res = await fetch("/serenity/autotrajectory", {
|
|
4383
|
+
method: "POST",
|
|
4384
|
+
headers: {
|
|
4385
|
+
"content-type": "application/json",
|
|
4386
|
+
"x-serenity-ui": "1"
|
|
4387
|
+
},
|
|
4388
|
+
body: JSON.stringify({ action: "wake" })
|
|
4389
|
+
});
|
|
4390
|
+
const body = await res.json();
|
|
4391
|
+
setWakeResult(body.detail ?? body.error ?? `HTTP ${res.status}`);
|
|
4392
|
+
refresh();
|
|
4393
|
+
} catch (err) {
|
|
4394
|
+
setWakeResult(err instanceof Error ? err.message : String(err));
|
|
4395
|
+
} finally {
|
|
4396
|
+
setWaking(false);
|
|
4397
|
+
}
|
|
4398
|
+
};
|
|
4399
|
+
if (unavailable) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4400
|
+
className: "ss-rows",
|
|
4401
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4402
|
+
title: "未激活",
|
|
4403
|
+
desc: "当前工作区不在 CCC 内(无 .serenity)——先在工作区打开一个 CCC 会话",
|
|
4404
|
+
control: null
|
|
4405
|
+
}) })
|
|
4406
|
+
});
|
|
4407
|
+
if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4408
|
+
className: "ss-rows",
|
|
4409
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4410
|
+
title: "加载中…",
|
|
4411
|
+
desc: "读取自主轨迹实验状态",
|
|
4412
|
+
control: null
|
|
4413
|
+
}) })
|
|
4414
|
+
});
|
|
4415
|
+
const target = status.target;
|
|
4416
|
+
const stateText = !status.configured ? "未配置(实验未开始)" : !status.enabled ? "已配置,未启用(enabled=false,零资源占用)" : "已启用 — 时钟唤起等待中";
|
|
4417
|
+
const targetText = target ? `${target.dirName}${target.autoFlag ? "(--auto ✓)" : "(无 --auto 标志)"} · 空闲 ${target.idleHours.toFixed(1)}h / 阈值 ${status.intervalHours}h` : status.session ? `会话 ${status.session} 未命中(AGENT_SESSIONS 无匹配)` : "未配置目标会话(不唤起)";
|
|
4418
|
+
const wakeReady = status.enabled && !!target && target.autoFlag;
|
|
4419
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
4420
|
+
className: "ss-rows",
|
|
4421
|
+
children: [
|
|
4422
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4423
|
+
title: "实验状态",
|
|
4424
|
+
desc: stateText,
|
|
4425
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4426
|
+
className: "ss-value",
|
|
4427
|
+
children: status.enabled ? "● 运行中" : status.configured ? "○ 待启" : "—"
|
|
4428
|
+
})
|
|
4429
|
+
}) }),
|
|
4430
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4431
|
+
title: "目标会话",
|
|
4432
|
+
desc: targetText,
|
|
4433
|
+
control: target?.wakeable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4434
|
+
className: "ss-value",
|
|
4435
|
+
children: "可唤起"
|
|
4436
|
+
}) : null
|
|
4437
|
+
}) }),
|
|
4438
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4439
|
+
title: "唤起窗口",
|
|
4440
|
+
desc: `当前北京 ${status.beijingHour} 点 — ${status.windowAllowed ? "允许唤起" : `高峰避开中(${status.avoidWakeHours.start}~${status.avoidWakeHours.end})`}`,
|
|
4441
|
+
control: null
|
|
4442
|
+
}) }),
|
|
4443
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4444
|
+
title: "偏见提供者",
|
|
4445
|
+
desc: status.biasProvider,
|
|
4446
|
+
control: null
|
|
4447
|
+
}) }),
|
|
4448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
|
|
4449
|
+
title: "立即唤起",
|
|
4450
|
+
desc: wakeResult ?? "调试:手动触发一次唤起(跳过窗口/间隔,仍校验配置与偏见脚本)",
|
|
4451
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4452
|
+
type: "button",
|
|
4453
|
+
className: "ss-wakeBtn",
|
|
4454
|
+
disabled: !wakeReady || waking,
|
|
4455
|
+
onClick: () => void wakeNow(),
|
|
4456
|
+
children: waking ? "唤起中…" : "立即唤起"
|
|
4457
|
+
})
|
|
4458
|
+
}) })
|
|
4459
|
+
]
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4346
4462
|
//#endregion
|
|
4347
4463
|
//#region src/client/index.ts
|
|
4348
4464
|
const inject = [
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as findSerenityRoot, c as matchBlacklist, d as readHandymanConfig, f as readUtf8, i as findGitRoot, l as pathInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as resolveInside, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CfDrlfA7.js";
|
|
1
2
|
import z from "@deepseek-ai/schemastery";
|
|
2
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
4
|
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
@@ -23,144 +24,6 @@ var __exportAll = (all, no_symbols) => {
|
|
|
23
24
|
return target;
|
|
24
25
|
};
|
|
25
26
|
//#endregion
|
|
26
|
-
//#region src/ccc.ts
|
|
27
|
-
/**
|
|
28
|
-
* ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
|
|
29
|
-
*
|
|
30
|
-
* 职责:CCC 根检测(P1)、git 检测(P2)、serenity.json 配置读取(.opencode 规范位置,.dsh 回退)、
|
|
31
|
-
* 路径守卫(P3 语义)、安全模式黑名单匹配。
|
|
32
|
-
*
|
|
33
|
-
* 由 tools/ 与 seams/ 复用;逻辑移植自 dsh-serenity-plugin v0.1-v0.2
|
|
34
|
-
* runner(本项目自有代码,非 opencode-serenity-plugin 源码)。
|
|
35
|
-
*/
|
|
36
|
-
function findSerenityRoot(cwd) {
|
|
37
|
-
let current = resolve(cwd);
|
|
38
|
-
while (true) {
|
|
39
|
-
const marker = resolve(current, ".serenity");
|
|
40
|
-
if (existsSync(marker) && statSync(marker).isFile()) return current;
|
|
41
|
-
const parent = dirname(current);
|
|
42
|
-
if (parent === current) return null;
|
|
43
|
-
current = parent;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function findGitRoot(cwd) {
|
|
47
|
-
let current = resolve(cwd);
|
|
48
|
-
while (true) {
|
|
49
|
-
if (existsSync(resolve(current, ".git"))) return current;
|
|
50
|
-
const parent = dirname(current);
|
|
51
|
-
if (parent === current) return null;
|
|
52
|
-
current = parent;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 前缀判定:abs 是否位于 rootAbs 之内。
|
|
57
|
-
* caseInsensitive(Windows 盘符/路径大小写不敏感)由调用方按平台传入;
|
|
58
|
-
* 边界必须是路径分隔符(`\` 或 `/` 任一)——不依赖平台 sep,跨平台语义一致:
|
|
59
|
-
* - 跨盘符绝对路径(root 在 D:\、target 在 C:\)前缀不匹配 → outside
|
|
60
|
-
* - 兄弟目录前缀陷阱(home vs home2)边界非分隔符 → outside
|
|
61
|
-
* (旧实现用 path.relative().startsWith('..')——跨盘时 relative 返回绝对路径原文,
|
|
62
|
-
* 不以 `..` 开头 → 漏判放行,见 Windows 兼容审计问题 1。)
|
|
63
|
-
*/
|
|
64
|
-
function pathInside(rootAbs, abs, caseInsensitive = process.platform === "win32") {
|
|
65
|
-
const r = caseInsensitive ? rootAbs.toLowerCase() : rootAbs;
|
|
66
|
-
const a = caseInsensitive ? abs.toLowerCase() : abs;
|
|
67
|
-
if (a === r) return true;
|
|
68
|
-
if (!a.startsWith(r)) return false;
|
|
69
|
-
const next = a[r.length];
|
|
70
|
-
return next === "\\" || next === "/";
|
|
71
|
-
}
|
|
72
|
-
function classifyPath(p, root) {
|
|
73
|
-
const rootAbs = resolve(root);
|
|
74
|
-
const abs = resolve(p);
|
|
75
|
-
const ci = process.platform === "win32";
|
|
76
|
-
if (ci ? abs.toLowerCase() === rootAbs.toLowerCase() : abs === rootAbs) return "same";
|
|
77
|
-
return pathInside(rootAbs, abs, ci) ? "inside" : "outside";
|
|
78
|
-
}
|
|
79
|
-
function resolveInside(root, p) {
|
|
80
|
-
const abs = resolve(root, p);
|
|
81
|
-
if (classifyPath(abs, root) === "outside") throw new Error(`Path escape blocked: "${p}" resolves outside "${root}"`);
|
|
82
|
-
return abs;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 解析 handyman 配置(v1.24.0:取代 loop.defaultModel;不兼容旧 loop 配置——用户拍板)。
|
|
86
|
-
* @returns 白名单 + 缺省模型 + 上限;models 未配置返回 null(工具应报错要求配置)
|
|
87
|
-
*/
|
|
88
|
-
function readHandymanConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
89
|
-
const cfg = loadSerenityConfig(root, paths);
|
|
90
|
-
const models = cfg.handyman?.models;
|
|
91
|
-
if (!Array.isArray(models) || models.length === 0) return null;
|
|
92
|
-
const clean = models.map((m) => m.trim()).filter(Boolean);
|
|
93
|
-
if (clean.length === 0) return null;
|
|
94
|
-
const defaultModel = cfg.handyman?.defaultModel?.trim();
|
|
95
|
-
return {
|
|
96
|
-
models: clean,
|
|
97
|
-
defaultModel: defaultModel !== void 0 && clean.includes(defaultModel) ? defaultModel : clean[0],
|
|
98
|
-
maxRounds: cfg.handyman?.maxRounds ?? 100,
|
|
99
|
-
maxParallel: cfg.handyman?.maxParallel ?? 10
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* ACC 依赖的 CCC 配置路径(S134 修正:兼容历史——历史在 .opencode,规范位置 = .opencode;
|
|
104
|
-
* .dsh 仅作 dsh 运行时回退,不优先)。
|
|
105
|
-
*/
|
|
106
|
-
const DEFAULT_SERENITY_CONFIG_PATHS = [".opencode/serenity.json", ".dsh/serenity.json"];
|
|
107
|
-
/**
|
|
108
|
-
* 读取 UTF-8 文件并剥离 BOM(Windows 审计问题 16):PowerShell/Windows 编辑器
|
|
109
|
-
* 写出的 BOM(\uFEFF)会让 JSON.parse 抛错(配置静默变空)或 frontmatter 检测失败(技能被丢弃)。
|
|
110
|
-
*/
|
|
111
|
-
function readUtf8(path) {
|
|
112
|
-
return readFileSync(path, "utf-8").replace(/^\uFEFF/, "");
|
|
113
|
-
}
|
|
114
|
-
function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
115
|
-
for (const candidate of paths) {
|
|
116
|
-
const p = resolve(root, candidate);
|
|
117
|
-
if (!existsSync(p)) continue;
|
|
118
|
-
try {
|
|
119
|
-
return JSON.parse(readUtf8(p));
|
|
120
|
-
} catch {
|
|
121
|
-
return {};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return {};
|
|
125
|
-
}
|
|
126
|
-
const SAFE_MODE_MARKER = ".serenity-safe-on";
|
|
127
|
-
function isSafeModeOn(root) {
|
|
128
|
-
return existsSync(resolve(root, SAFE_MODE_MARKER));
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* 读取黑名单(对齐 osp readBlacklist):支持两种条目格式——
|
|
132
|
-
* string:".secrets/" 或 "regex:^..."(前缀 / 正则)
|
|
133
|
-
* object:{ "pattern": ".secrets/", "message": "自定义提示" }
|
|
134
|
-
* dsp v1.17.4 前只支持 string(对象会被 String() 成 "[object Object]" → 规则失效,不拦截)。
|
|
135
|
-
*/
|
|
136
|
-
function readBlacklist(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
137
|
-
const rules = loadSerenityConfig(root, paths).safeMode?.blacklist;
|
|
138
|
-
if (!Array.isArray(rules)) return [];
|
|
139
|
-
const out = [];
|
|
140
|
-
for (const item of rules) if (typeof item === "string") {
|
|
141
|
-
if (item) out.push({ pattern: item });
|
|
142
|
-
} else if (item && typeof item === "object" && typeof item.pattern === "string") {
|
|
143
|
-
const obj = item;
|
|
144
|
-
const p = obj.pattern;
|
|
145
|
-
if (p) out.push({
|
|
146
|
-
pattern: p,
|
|
147
|
-
message: typeof obj.message === "string" ? obj.message : void 0
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
/** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp)。Windows:规则反斜杠归一化为正斜杠与 rel 对齐 */
|
|
153
|
-
function matchBlacklist(relPath, rules) {
|
|
154
|
-
for (const rule of rules) if (rule.pattern.startsWith("regex:")) try {
|
|
155
|
-
if (new RegExp(rule.pattern.slice(6)).test(relPath)) return rule;
|
|
156
|
-
} catch {}
|
|
157
|
-
else {
|
|
158
|
-
const pat = rule.pattern.split("\\").join("/");
|
|
159
|
-
if (relPath.startsWith(pat)) return rule;
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
//#endregion
|
|
164
27
|
//#region src/fs-ops.ts
|
|
165
28
|
/**
|
|
166
29
|
* fs-ops.ts — cc_fs 纯操作层(零 DSH 依赖,可独立单测)
|
|
@@ -947,7 +810,8 @@ function extractAction(exec) {
|
|
|
947
810
|
function registerGuards(ctx, opts = {}) {
|
|
948
811
|
const configPaths = opts.configPaths;
|
|
949
812
|
const evaluate = (exec) => {
|
|
950
|
-
const
|
|
813
|
+
const cwd = resolveAgentCwd(exec);
|
|
814
|
+
const root = findSerenityRoot(cwd);
|
|
951
815
|
if (!root) return { kind: "allow" };
|
|
952
816
|
const safeModeOn = isSafeModeOn(root);
|
|
953
817
|
const blacklist = readBlacklist(root, configPaths);
|
|
@@ -7118,6 +6982,7 @@ const AUTO_TRAJECTORY_EXP_ACTIONS = [
|
|
|
7118
6982
|
"all",
|
|
7119
6983
|
"init",
|
|
7120
6984
|
"random",
|
|
6985
|
+
"diag",
|
|
7121
6986
|
"doc",
|
|
7122
6987
|
"check",
|
|
7123
6988
|
"status",
|
|
@@ -7134,7 +6999,7 @@ function renderText(value) {
|
|
|
7134
6999
|
}
|
|
7135
7000
|
const autoTrajectoryExpTool = defineTool({
|
|
7136
7001
|
name: "autotrajectory-exp",
|
|
7137
|
-
description: "自主轨迹实验(Self-Sustaining Trajectory)一站式管理——实验提案 v1.26.
|
|
7002
|
+
description: "自主轨迹实验(Self-Sustaining Trajectory)一站式管理——实验提案 v1.26.14,默认关。无参/action=all:全报告(背景摘要 + 就绪检查 + 状态 + 下一步)——CCC agent 看一次即完整理解实验并知道怎么开始;init:初始化辅助(写配置 + 生成偏见提供者脚本模板);random:运行偏见提供者脚本输出当前偏见内容;diag:唤起条件链诊断(--ccc <path> 指定 CCC,无参递归扫描 /home/yh 两层——覆盖任意实验 CCC 位置;逐条件输出 + 阻断点 + 修复建议);doc:实验定义全文;check/status/guide:单项。实验是 CCC 的自选动作——dsp 只提供工具与知识,不自动安装任何东西。",
|
|
7138
7003
|
parameters: { action: {
|
|
7139
7004
|
type: "string",
|
|
7140
7005
|
enum: [...AUTO_TRAJECTORY_EXP_ACTIONS],
|
|
@@ -7287,7 +7152,8 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
7287
7152
|
};
|
|
7288
7153
|
ctx.on("tools/post-execute", async (exec, _result, next) => {
|
|
7289
7154
|
if (!exec.agent) return next();
|
|
7290
|
-
const
|
|
7155
|
+
const cwd = exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
7156
|
+
const root = findSerenityRoot(cwd);
|
|
7291
7157
|
if (!root) return next();
|
|
7292
7158
|
const sessionId = exec.agent?.session?.id;
|
|
7293
7159
|
const skiffKeeper = skiffTrajectoryEnabled(root, sessionId, "keeper");
|
|
@@ -8397,7 +8263,8 @@ function registerContext(ctx, opts = {}) {
|
|
|
8397
8263
|
const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
|
|
8398
8264
|
const entrySkillMaxChars = opts.entrySkillMaxChars ?? 3e4;
|
|
8399
8265
|
const seed = (agent) => {
|
|
8400
|
-
const
|
|
8266
|
+
const cwd = agent.session?.header?.cwd ?? process.cwd();
|
|
8267
|
+
const root = findSerenityRoot(cwd);
|
|
8401
8268
|
if (!root) return;
|
|
8402
8269
|
const key = agentKey(agent);
|
|
8403
8270
|
if (isSkiffSessionId(key)) return;
|
|
@@ -8430,7 +8297,8 @@ function registerContext(ctx, opts = {}) {
|
|
|
8430
8297
|
});
|
|
8431
8298
|
if (opts.injectOnPrompt ?? true) ctx.on("agent/pre-step", async (payload, next) => {
|
|
8432
8299
|
const { agent, messages } = payload;
|
|
8433
|
-
const
|
|
8300
|
+
const cwd = agent.session?.header?.cwd ?? process.cwd();
|
|
8301
|
+
const root = findSerenityRoot(cwd);
|
|
8434
8302
|
const key = agentKey(agent);
|
|
8435
8303
|
const downstream = await next();
|
|
8436
8304
|
if (isSkiffSessionId(key)) return downstream;
|
|
@@ -8467,7 +8335,8 @@ function registerCompactRetention(ctx, opts = {}) {
|
|
|
8467
8335
|
if (isSkiffSessionId(String(session.id ?? ""))) return;
|
|
8468
8336
|
const agent = ctx.agents.get(session.id);
|
|
8469
8337
|
if (!agent) return;
|
|
8470
|
-
const
|
|
8338
|
+
const cwd = agent.session?.header?.cwd ?? process.cwd();
|
|
8339
|
+
const root = findSerenityRoot(cwd);
|
|
8471
8340
|
if (!root) return;
|
|
8472
8341
|
try {
|
|
8473
8342
|
agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
|
|
@@ -8483,6 +8352,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
|
|
|
8483
8352
|
const CONFIG_PATH = "/serenity/config";
|
|
8484
8353
|
const CCCS_PATH = "/serenity/cccs";
|
|
8485
8354
|
const PUBLIC_ASK_PATH = "/serenity/public-ask";
|
|
8355
|
+
const AUTOTRAJECTORY_PATH = "/serenity/autotrajectory";
|
|
8486
8356
|
/** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
|
|
8487
8357
|
const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
|
|
8488
8358
|
const IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -8621,10 +8491,11 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
8621
8491
|
return;
|
|
8622
8492
|
}
|
|
8623
8493
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8624
|
-
const
|
|
8494
|
+
const workspace = resolveWorkspace(ctx, {
|
|
8625
8495
|
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
8626
8496
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
8627
|
-
})
|
|
8497
|
+
});
|
|
8498
|
+
const root = findSerenityRoot(workspace);
|
|
8628
8499
|
if (!root) {
|
|
8629
8500
|
sendJson$2(res, 200, { handymen: [] });
|
|
8630
8501
|
return;
|
|
@@ -8783,10 +8654,11 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
8783
8654
|
return;
|
|
8784
8655
|
}
|
|
8785
8656
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8786
|
-
const
|
|
8657
|
+
const workspace = resolveWorkspace(ctx, {
|
|
8787
8658
|
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
8788
8659
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
8789
|
-
})
|
|
8660
|
+
});
|
|
8661
|
+
const root = findSerenityRoot(workspace) ?? "";
|
|
8790
8662
|
const { discoverCccs } = await Promise.resolve().then(() => skiff_debug_exports);
|
|
8791
8663
|
sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
8792
8664
|
} catch (err) {
|
|
@@ -8839,6 +8711,62 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
8839
8711
|
}
|
|
8840
8712
|
}
|
|
8841
8713
|
});
|
|
8714
|
+
ctx.webServer.register({
|
|
8715
|
+
kind: "exact",
|
|
8716
|
+
path: AUTOTRAJECTORY_PATH,
|
|
8717
|
+
handler: async (req, res) => {
|
|
8718
|
+
try {
|
|
8719
|
+
if (req.method === "POST") {
|
|
8720
|
+
if (req.headers["x-serenity-ui"] !== "1") {
|
|
8721
|
+
sendJson$2(res, 403, { error: "立即唤起仅限 WebUI(agent 不可自行唤起)" });
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
const raw = await readBody$3(req, 16384);
|
|
8725
|
+
const body = JSON.parse(raw);
|
|
8726
|
+
if (body.action !== "wake") {
|
|
8727
|
+
sendJson$2(res, 400, { error: "unsupported action (expected \"wake\")" });
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
const workspace = resolveWorkspace(ctx, {
|
|
8731
|
+
sessionId: body.sessionId,
|
|
8732
|
+
workspace: body.workspace
|
|
8733
|
+
});
|
|
8734
|
+
const root = findSerenityRoot(workspace);
|
|
8735
|
+
if (!root) {
|
|
8736
|
+
sendJson$2(res, 404, { error: `no CCC found from workspace: ${workspace}` });
|
|
8737
|
+
return;
|
|
8738
|
+
}
|
|
8739
|
+
const { performAutoTrajectoryWake, readAutoTrajectorySettings } = await Promise.resolve().then(() => autotrajectory_exports);
|
|
8740
|
+
const settings = readAutoTrajectorySettings(root);
|
|
8741
|
+
if (!settings) {
|
|
8742
|
+
sendJson$2(res, 400, { error: "autotrajectory 未配置(.opencode/serenity.json 缺段)" });
|
|
8743
|
+
return;
|
|
8744
|
+
}
|
|
8745
|
+
const result = await performAutoTrajectoryWake(ctx, root, settings, { force: true });
|
|
8746
|
+
sendJson$2(res, result.ok ? 200 : 400, result);
|
|
8747
|
+
return;
|
|
8748
|
+
}
|
|
8749
|
+
if (req.method !== "GET") {
|
|
8750
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
8751
|
+
return;
|
|
8752
|
+
}
|
|
8753
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8754
|
+
const workspace = resolveWorkspace(ctx, {
|
|
8755
|
+
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
8756
|
+
workspace: url.searchParams.get("workspace") ?? void 0
|
|
8757
|
+
});
|
|
8758
|
+
const root = findSerenityRoot(workspace);
|
|
8759
|
+
if (!root) {
|
|
8760
|
+
sendJson$2(res, 200, { status: null });
|
|
8761
|
+
return;
|
|
8762
|
+
}
|
|
8763
|
+
const { getAutoTrajectoryStatus } = await Promise.resolve().then(() => autotrajectory_exports);
|
|
8764
|
+
sendJson$2(res, 200, { status: getAutoTrajectoryStatus(root) });
|
|
8765
|
+
} catch (err) {
|
|
8766
|
+
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
8767
|
+
}
|
|
8768
|
+
}
|
|
8769
|
+
});
|
|
8842
8770
|
}
|
|
8843
8771
|
//#endregion
|
|
8844
8772
|
//#region src/seams/env.ts
|
|
@@ -8932,7 +8860,8 @@ function registerOpencodeSkills(ctx) {
|
|
|
8932
8860
|
const provider = {
|
|
8933
8861
|
name: OPENCODE_PROVIDER,
|
|
8934
8862
|
async list(options) {
|
|
8935
|
-
const
|
|
8863
|
+
const cwd = options.cwd ?? process.cwd();
|
|
8864
|
+
const root = findSerenityRoot(cwd);
|
|
8936
8865
|
if (!root) return {
|
|
8937
8866
|
candidates: [],
|
|
8938
8867
|
complete: true
|
|
@@ -9915,7 +9844,8 @@ function registerOutputGuardHook(ctx) {
|
|
|
9915
9844
|
const agent = payload?.agent;
|
|
9916
9845
|
if (!agent) return;
|
|
9917
9846
|
if (!isExternalFaceSession(String(agent.session.id ?? ""))) return;
|
|
9918
|
-
const
|
|
9847
|
+
const cwd = agent.session.header?.cwd ?? process.cwd();
|
|
9848
|
+
const root = findSerenityRoot(cwd);
|
|
9919
9849
|
if (!root) return;
|
|
9920
9850
|
const id = agent.id;
|
|
9921
9851
|
const text = lastAssistantText(agent);
|
|
@@ -12838,10 +12768,31 @@ qInput.focus()
|
|
|
12838
12768
|
}
|
|
12839
12769
|
//#endregion
|
|
12840
12770
|
//#region src/autotrajectory.ts
|
|
12771
|
+
var autotrajectory_exports = /* @__PURE__ */ __exportAll({
|
|
12772
|
+
AUTO_DIR_SUFFIX: () => AUTO_DIR_SUFFIX,
|
|
12773
|
+
DEFAULT_AVOID_HOURS: () => DEFAULT_AVOID_HOURS,
|
|
12774
|
+
DEFAULT_BIAS_PROVIDER: () => DEFAULT_BIAS_PROVIDER,
|
|
12775
|
+
MOTIVATION_MARKER: () => MOTIVATION_MARKER,
|
|
12776
|
+
TICK_MS: () => TICK_MS,
|
|
12777
|
+
beijingHour: () => beijingHour,
|
|
12778
|
+
buildWakeMessage: () => buildWakeMessage,
|
|
12779
|
+
fetchBiasContent: () => fetchBiasContent,
|
|
12780
|
+
getAutoTrajectoryStatus: () => getAutoTrajectoryStatus,
|
|
12781
|
+
inAllowedWakeWindow: () => inAllowedWakeWindow,
|
|
12782
|
+
isAutoTrajectorySession: () => isAutoTrajectorySession,
|
|
12783
|
+
performAutoTrajectoryWake: () => performAutoTrajectoryWake,
|
|
12784
|
+
readAutoTrajectorySettings: () => readAutoTrajectorySettings,
|
|
12785
|
+
readSelfGeneratedMotivation: () => readSelfGeneratedMotivation,
|
|
12786
|
+
registerAutoTrajectory: () => registerAutoTrajectory,
|
|
12787
|
+
resolveTargetMd: () => resolveTargetMd,
|
|
12788
|
+
shouldWake: () => shouldWake
|
|
12789
|
+
});
|
|
12841
12790
|
const PLUGIN_SOURCE = {
|
|
12842
12791
|
kind: "plugin",
|
|
12843
12792
|
plugin: "dsh-serenity-hooks"
|
|
12844
12793
|
};
|
|
12794
|
+
/** 偏见内容提供者脚本缺省名(CCC 根目录下;用户拍板:tool 直接运行,缺失报错要求实现) */
|
|
12795
|
+
const DEFAULT_BIAS_PROVIDER = "autotrajectory-bias.ts";
|
|
12845
12796
|
/** 自主轨迹会话目录后缀标志(用户拍板:后缀) */
|
|
12846
12797
|
const AUTO_DIR_SUFFIX = "--auto";
|
|
12847
12798
|
/** 调度 tick 周期(10min——验证形态"一个就够") */
|
|
@@ -12978,6 +12929,71 @@ function buildWakeMessage(opts) {
|
|
|
12978
12929
|
lines.push("", "任务:执行一轮自主认知(探索/反事实检验),把产出写入 SESSION.md", "「自主探索日志」段,并预写「下一轮动机」段。完成后自然结束。");
|
|
12979
12930
|
return lines.join("\n");
|
|
12980
12931
|
}
|
|
12932
|
+
async function performAutoTrajectoryWake(ctx, root, settings, opts = {}) {
|
|
12933
|
+
if (!settings?.enabled) return {
|
|
12934
|
+
ok: false,
|
|
12935
|
+
detail: "autotrajectory 未启用(enabled=false)"
|
|
12936
|
+
};
|
|
12937
|
+
const mdPath = resolveTargetMd(root, settings);
|
|
12938
|
+
if (!mdPath) return {
|
|
12939
|
+
ok: false,
|
|
12940
|
+
detail: "目标会话未命中(session 未配置或 AGENT_SESSIONS 无匹配)"
|
|
12941
|
+
};
|
|
12942
|
+
if (!isAutoTrajectorySession(mdPath)) return {
|
|
12943
|
+
ok: false,
|
|
12944
|
+
detail: "目标会话目录无 --auto 标志(AGENT_SESSIONS/<date>--<desc>--auto/)"
|
|
12945
|
+
};
|
|
12946
|
+
if (!opts.force) {
|
|
12947
|
+
const now = Date.now();
|
|
12948
|
+
if (!inAllowedWakeWindow(now, settings.avoidWakeHours)) return {
|
|
12949
|
+
ok: false,
|
|
12950
|
+
detail: "当前在北京高峰避开窗口内(不唤起)"
|
|
12951
|
+
};
|
|
12952
|
+
try {
|
|
12953
|
+
const mtime = statSync(mdPath).mtimeMs;
|
|
12954
|
+
const hours = Math.max(1, settings.intervalHours ?? 12);
|
|
12955
|
+
if (now - mtime < hours * 36e5) return {
|
|
12956
|
+
ok: false,
|
|
12957
|
+
detail: `距上次轨迹活动不足 ${hours}h(等待中)`
|
|
12958
|
+
};
|
|
12959
|
+
} catch {
|
|
12960
|
+
return {
|
|
12961
|
+
ok: false,
|
|
12962
|
+
detail: "SESSION.md 读取失败(statSync)"
|
|
12963
|
+
};
|
|
12964
|
+
}
|
|
12965
|
+
}
|
|
12966
|
+
const agent = resolveTargetAgent(ctx, mdPath);
|
|
12967
|
+
if (!agent) return {
|
|
12968
|
+
ok: false,
|
|
12969
|
+
detail: "目标会话 agent 不可得(先在会话列表激活目标会话后重试)"
|
|
12970
|
+
};
|
|
12971
|
+
const provider = settings.biasProvider?.trim() || "autotrajectory-bias.ts";
|
|
12972
|
+
const biasRes = await fetchBiasContent(root, provider);
|
|
12973
|
+
if (biasRes.error) return {
|
|
12974
|
+
ok: false,
|
|
12975
|
+
detail: `偏见内容缺失:${biasRes.error}`
|
|
12976
|
+
};
|
|
12977
|
+
const motivation = readSelfGeneratedMotivation(mdPath);
|
|
12978
|
+
const message = buildWakeMessage({
|
|
12979
|
+
sessionName: basename(dirname(mdPath)),
|
|
12980
|
+
mdPath,
|
|
12981
|
+
intervalHours: Math.max(1, settings.intervalHours ?? 12),
|
|
12982
|
+
motivation,
|
|
12983
|
+
biasContent: biasRes.text
|
|
12984
|
+
});
|
|
12985
|
+
agent.steer(createUserMessage({
|
|
12986
|
+
content: [{
|
|
12987
|
+
type: "text",
|
|
12988
|
+
text: message
|
|
12989
|
+
}],
|
|
12990
|
+
source: PLUGIN_SOURCE
|
|
12991
|
+
}));
|
|
12992
|
+
return {
|
|
12993
|
+
ok: true,
|
|
12994
|
+
detail: `已唤起 ${basename(dirname(mdPath))}(偏见提供者 ${provider})`
|
|
12995
|
+
};
|
|
12996
|
+
}
|
|
12981
12997
|
/**
|
|
12982
12998
|
* 装配(index.ts apply 调用):仅当 CCC 配置 autotrajectory.enabled=true 时启动定时器;
|
|
12983
12999
|
* 否则零资源占用。定时器 tick 检查唤起条件 → 注入活跃会话(agent.steer,前台可见)。
|
|
@@ -12991,36 +13007,12 @@ function registerAutoTrajectory(ctx) {
|
|
|
12991
13007
|
if (running) return;
|
|
12992
13008
|
const mdPath = resolveTargetMd(root, settings);
|
|
12993
13009
|
if (!shouldWake(settings, mdPath, Date.now(), running)) return;
|
|
12994
|
-
const agent = resolveTargetAgent(ctx, mdPath);
|
|
12995
|
-
if (!agent) {
|
|
12996
|
-
console.warn("[serenity-hooks] ✗ 自主轨迹唤起跳过:目标会话 agent 不可得(session use 激活后重试)");
|
|
12997
|
-
return;
|
|
12998
|
-
}
|
|
12999
13010
|
running = true;
|
|
13000
13011
|
(async () => {
|
|
13001
13012
|
try {
|
|
13002
|
-
const
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
console.error(`[serenity-hooks] ✗ 自主轨迹唤起中止(偏见内容缺失): ${biasRes.error}`);
|
|
13006
|
-
return;
|
|
13007
|
-
}
|
|
13008
|
-
const motivation = readSelfGeneratedMotivation(mdPath);
|
|
13009
|
-
const message = buildWakeMessage({
|
|
13010
|
-
sessionName: basename(dirname(mdPath)),
|
|
13011
|
-
mdPath,
|
|
13012
|
-
intervalHours: settings.intervalHours ?? 12,
|
|
13013
|
-
motivation,
|
|
13014
|
-
biasContent: biasRes.text
|
|
13015
|
-
});
|
|
13016
|
-
agent.steer(createUserMessage({
|
|
13017
|
-
content: [{
|
|
13018
|
-
type: "text",
|
|
13019
|
-
text: message
|
|
13020
|
-
}],
|
|
13021
|
-
source: PLUGIN_SOURCE
|
|
13022
|
-
}));
|
|
13023
|
-
console.log(`[serenity-hooks] ✓ 自主轨迹唤起(${basename(dirname(mdPath))},偏见提供者 ${provider})`);
|
|
13013
|
+
const res = await performAutoTrajectoryWake(ctx, root, settings, { force: false });
|
|
13014
|
+
if (res.ok) console.log(`[serenity-hooks] ✓ 自主轨迹唤起(${res.detail})`);
|
|
13015
|
+
else console.warn(`[serenity-hooks] ✗ 自主轨迹唤起跳过:${res.detail}`);
|
|
13024
13016
|
} catch (err) {
|
|
13025
13017
|
console.warn(`[serenity-hooks] ✗ 自主轨迹唤起失败: ${String(err?.message ?? err)}`);
|
|
13026
13018
|
} finally {
|
|
@@ -13057,6 +13049,45 @@ function resolveAutoTrajectoryRoot(ctx) {
|
|
|
13057
13049
|
} catch {}
|
|
13058
13050
|
return null;
|
|
13059
13051
|
}
|
|
13052
|
+
function getAutoTrajectoryStatus(root) {
|
|
13053
|
+
const cfg = readAutoTrajectorySettings(root);
|
|
13054
|
+
const now = Date.now();
|
|
13055
|
+
const base = {
|
|
13056
|
+
configured: cfg !== null,
|
|
13057
|
+
enabled: cfg?.enabled ?? false,
|
|
13058
|
+
intervalHours: Math.max(1, cfg?.intervalHours ?? 12),
|
|
13059
|
+
biasProvider: cfg?.biasProvider?.trim() || "autotrajectory-bias.ts",
|
|
13060
|
+
session: cfg?.session ?? null,
|
|
13061
|
+
avoidWakeHours: {
|
|
13062
|
+
start: cfg?.avoidWakeHours?.start ?? DEFAULT_AVOID_HOURS.start,
|
|
13063
|
+
end: cfg?.avoidWakeHours?.end ?? DEFAULT_AVOID_HOURS.end
|
|
13064
|
+
},
|
|
13065
|
+
beijingHour: beijingHour(now),
|
|
13066
|
+
windowAllowed: inAllowedWakeWindow(now, cfg?.avoidWakeHours)
|
|
13067
|
+
};
|
|
13068
|
+
if (!cfg) return {
|
|
13069
|
+
...base,
|
|
13070
|
+
target: null
|
|
13071
|
+
};
|
|
13072
|
+
const mdPath = resolveTargetMd(root, cfg);
|
|
13073
|
+
if (!mdPath) return {
|
|
13074
|
+
...base,
|
|
13075
|
+
target: null
|
|
13076
|
+
};
|
|
13077
|
+
let idleHours = 0;
|
|
13078
|
+
try {
|
|
13079
|
+
idleHours = (now - statSync(mdPath).mtimeMs) / 36e5;
|
|
13080
|
+
} catch {}
|
|
13081
|
+
return {
|
|
13082
|
+
...base,
|
|
13083
|
+
target: {
|
|
13084
|
+
dirName: basename(dirname(mdPath)),
|
|
13085
|
+
autoFlag: isAutoTrajectorySession(mdPath),
|
|
13086
|
+
idleHours: Math.max(0, idleHours),
|
|
13087
|
+
wakeable: shouldWake(cfg, mdPath, now, false)
|
|
13088
|
+
}
|
|
13089
|
+
};
|
|
13090
|
+
}
|
|
13060
13091
|
//#endregion
|
|
13061
13092
|
//#region src/index.ts
|
|
13062
13093
|
const name = "dsh-serenity-hooks";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.14",
|
|
4
4
|
"description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|