@shgroup/dsh-serenity-hooks 1.26.12 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.26.12",
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 root = findSerenityRoot(resolveAgentCwd(exec));
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);
@@ -7096,12 +6960,29 @@ const skiffAdminTool = defineTool({
7096
6960
  * 实现:薄封装——exec 包内静态脚本(npm files 含 experiments/),脚本是单一真相源。
7097
6961
  * 环境注入 SERENITY_ROOT(当前 CCC 根)供脚本定位;bun 优先(可直跑 TS)。
7098
6962
  */
7099
- /** 包内实验脚本(lib/tools/ → ../../experiments/...;npm files 分发) */
7100
- const EXP_SCRIPT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "experiments", "autotrajectory", "scripts", "autotrajectory-exp.ts");
6963
+ /**
6964
+ * 定位包内实验脚本(npm files 分发 experiments/autotrajectory/)。
6965
+ * 布局差异:tsdown bundle 后 import.meta.url 指向 lib/index.js(lib → 包根 1 层);
6966
+ * vitest 源码直跑时指向 src/tools/x.ts(src/tools → 包根 2 层)——逐级上溯查找,
6967
+ * 两种布局都稳(找到 experiments/autotrajectory/scripts/autotrajectory-exp.ts 即止)。
6968
+ */
6969
+ function findExpScript(startDir) {
6970
+ let cur = startDir;
6971
+ while (true) {
6972
+ const cand = join(cur, "experiments", "autotrajectory", "scripts", "autotrajectory-exp.ts");
6973
+ if (existsSync(cand)) return cand;
6974
+ const parent = dirname(cur);
6975
+ if (parent === cur) return null;
6976
+ cur = parent;
6977
+ }
6978
+ }
6979
+ /** 包内实验脚本(上溯查找;找不到 → execute 报错提示包完整性) */
6980
+ const EXP_SCRIPT = findExpScript(dirname(fileURLToPath(import.meta.url)));
7101
6981
  const AUTO_TRAJECTORY_EXP_ACTIONS = [
7102
6982
  "all",
7103
6983
  "init",
7104
6984
  "random",
6985
+ "diag",
7105
6986
  "doc",
7106
6987
  "check",
7107
6988
  "status",
@@ -7118,7 +6999,7 @@ function renderText(value) {
7118
6999
  }
7119
7000
  const autoTrajectoryExpTool = defineTool({
7120
7001
  name: "autotrajectory-exp",
7121
- description: "自主轨迹实验(Self-Sustaining Trajectory)一站式管理——实验提案 v1.26.12,默认关。无参/action=all:全报告(背景摘要 + 就绪检查 + 状态 + 下一步)——CCC agent 看一次即完整理解实验并知道怎么开始;init:初始化辅助(写配置 + 生成偏见提供者脚本模板);random:运行偏见提供者脚本输出当前偏见内容;doc:实验定义全文;check/status/guide:单项。实验是 CCC 的自选动作——dsp 只提供工具与知识,不自动安装任何东西。",
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 只提供工具与知识,不自动安装任何东西。",
7122
7003
  parameters: { action: {
7123
7004
  type: "string",
7124
7005
  enum: [...AUTO_TRAJECTORY_EXP_ACTIONS],
@@ -7132,13 +7013,18 @@ const autoTrajectoryExpTool = defineTool({
7132
7013
  async execute(args, exec) {
7133
7014
  const root = findSerenityRoot(agentCwd$1(exec));
7134
7015
  const result = {};
7135
- if (!existsSync(EXP_SCRIPT)) {
7136
- result.error = `autotrajectory-exp 脚本缺失(${EXP_SCRIPT})——实验包未随安装分发,请检查 npm 包完整性`;
7016
+ if (!EXP_SCRIPT) {
7017
+ result.error = "autotrajectory-exp 实验脚本未随安装分发(npm 包缺 experiments/autotrajectory/)——请检查包完整性";
7018
+ return result;
7019
+ }
7020
+ const script = EXP_SCRIPT;
7021
+ if (!existsSync(script)) {
7022
+ result.error = `autotrajectory-exp 脚本缺失(${script})——实验包未随安装分发,请检查 npm 包完整性`;
7137
7023
  return result;
7138
7024
  }
7139
7025
  const env = { ...process.env };
7140
7026
  if (root) env.SERENITY_ROOT = root;
7141
- const r = spawnSync("bun", [EXP_SCRIPT, args.action ?? "all"], {
7027
+ const r = spawnSync("bun", [script, args.action ?? "all"], {
7142
7028
  encoding: "utf-8",
7143
7029
  timeout: 6e5,
7144
7030
  env,
@@ -7266,7 +7152,8 @@ function registerKeeper(ctx, opts = {}) {
7266
7152
  };
7267
7153
  ctx.on("tools/post-execute", async (exec, _result, next) => {
7268
7154
  if (!exec.agent) return next();
7269
- const root = findSerenityRoot(exec.agent?.session?.header?.cwd ?? process.cwd());
7155
+ const cwd = exec.agent?.session?.header?.cwd ?? process.cwd();
7156
+ const root = findSerenityRoot(cwd);
7270
7157
  if (!root) return next();
7271
7158
  const sessionId = exec.agent?.session?.id;
7272
7159
  const skiffKeeper = skiffTrajectoryEnabled(root, sessionId, "keeper");
@@ -8376,7 +8263,8 @@ function registerContext(ctx, opts = {}) {
8376
8263
  const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
8377
8264
  const entrySkillMaxChars = opts.entrySkillMaxChars ?? 3e4;
8378
8265
  const seed = (agent) => {
8379
- const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
8266
+ const cwd = agent.session?.header?.cwd ?? process.cwd();
8267
+ const root = findSerenityRoot(cwd);
8380
8268
  if (!root) return;
8381
8269
  const key = agentKey(agent);
8382
8270
  if (isSkiffSessionId(key)) return;
@@ -8409,7 +8297,8 @@ function registerContext(ctx, opts = {}) {
8409
8297
  });
8410
8298
  if (opts.injectOnPrompt ?? true) ctx.on("agent/pre-step", async (payload, next) => {
8411
8299
  const { agent, messages } = payload;
8412
- const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
8300
+ const cwd = agent.session?.header?.cwd ?? process.cwd();
8301
+ const root = findSerenityRoot(cwd);
8413
8302
  const key = agentKey(agent);
8414
8303
  const downstream = await next();
8415
8304
  if (isSkiffSessionId(key)) return downstream;
@@ -8446,7 +8335,8 @@ function registerCompactRetention(ctx, opts = {}) {
8446
8335
  if (isSkiffSessionId(String(session.id ?? ""))) return;
8447
8336
  const agent = ctx.agents.get(session.id);
8448
8337
  if (!agent) return;
8449
- const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
8338
+ const cwd = agent.session?.header?.cwd ?? process.cwd();
8339
+ const root = findSerenityRoot(cwd);
8450
8340
  if (!root) return;
8451
8341
  try {
8452
8342
  agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
@@ -8462,6 +8352,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
8462
8352
  const CONFIG_PATH = "/serenity/config";
8463
8353
  const CCCS_PATH = "/serenity/cccs";
8464
8354
  const PUBLIC_ASK_PATH = "/serenity/public-ask";
8355
+ const AUTOTRAJECTORY_PATH = "/serenity/autotrajectory";
8465
8356
  /** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
8466
8357
  const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
8467
8358
  const IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
@@ -8600,10 +8491,11 @@ function registerStatusApi(ctx, opts = {}) {
8600
8491
  return;
8601
8492
  }
8602
8493
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
8603
- const root = findSerenityRoot(resolveWorkspace(ctx, {
8494
+ const workspace = resolveWorkspace(ctx, {
8604
8495
  sessionId: url.searchParams.get("sessionId") ?? void 0,
8605
8496
  workspace: url.searchParams.get("workspace") ?? void 0
8606
- }));
8497
+ });
8498
+ const root = findSerenityRoot(workspace);
8607
8499
  if (!root) {
8608
8500
  sendJson$2(res, 200, { handymen: [] });
8609
8501
  return;
@@ -8762,10 +8654,11 @@ function registerStatusApi(ctx, opts = {}) {
8762
8654
  return;
8763
8655
  }
8764
8656
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
8765
- const root = findSerenityRoot(resolveWorkspace(ctx, {
8657
+ const workspace = resolveWorkspace(ctx, {
8766
8658
  sessionId: url.searchParams.get("sessionId") ?? void 0,
8767
8659
  workspace: url.searchParams.get("workspace") ?? void 0
8768
- })) ?? "";
8660
+ });
8661
+ const root = findSerenityRoot(workspace) ?? "";
8769
8662
  const { discoverCccs } = await Promise.resolve().then(() => skiff_debug_exports);
8770
8663
  sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
8771
8664
  } catch (err) {
@@ -8818,6 +8711,62 @@ function registerStatusApi(ctx, opts = {}) {
8818
8711
  }
8819
8712
  }
8820
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
+ });
8821
8770
  }
8822
8771
  //#endregion
8823
8772
  //#region src/seams/env.ts
@@ -8911,7 +8860,8 @@ function registerOpencodeSkills(ctx) {
8911
8860
  const provider = {
8912
8861
  name: OPENCODE_PROVIDER,
8913
8862
  async list(options) {
8914
- const root = findSerenityRoot(options.cwd ?? process.cwd());
8863
+ const cwd = options.cwd ?? process.cwd();
8864
+ const root = findSerenityRoot(cwd);
8915
8865
  if (!root) return {
8916
8866
  candidates: [],
8917
8867
  complete: true
@@ -9894,7 +9844,8 @@ function registerOutputGuardHook(ctx) {
9894
9844
  const agent = payload?.agent;
9895
9845
  if (!agent) return;
9896
9846
  if (!isExternalFaceSession(String(agent.session.id ?? ""))) return;
9897
- const root = findSerenityRoot(agent.session.header?.cwd ?? process.cwd());
9847
+ const cwd = agent.session.header?.cwd ?? process.cwd();
9848
+ const root = findSerenityRoot(cwd);
9898
9849
  if (!root) return;
9899
9850
  const id = agent.id;
9900
9851
  const text = lastAssistantText(agent);
@@ -12817,10 +12768,31 @@ qInput.focus()
12817
12768
  }
12818
12769
  //#endregion
12819
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
+ });
12820
12790
  const PLUGIN_SOURCE = {
12821
12791
  kind: "plugin",
12822
12792
  plugin: "dsh-serenity-hooks"
12823
12793
  };
12794
+ /** 偏见内容提供者脚本缺省名(CCC 根目录下;用户拍板:tool 直接运行,缺失报错要求实现) */
12795
+ const DEFAULT_BIAS_PROVIDER = "autotrajectory-bias.ts";
12824
12796
  /** 自主轨迹会话目录后缀标志(用户拍板:后缀) */
12825
12797
  const AUTO_DIR_SUFFIX = "--auto";
12826
12798
  /** 调度 tick 周期(10min——验证形态"一个就够") */
@@ -12957,6 +12929,71 @@ function buildWakeMessage(opts) {
12957
12929
  lines.push("", "任务:执行一轮自主认知(探索/反事实检验),把产出写入 SESSION.md", "「自主探索日志」段,并预写「下一轮动机」段。完成后自然结束。");
12958
12930
  return lines.join("\n");
12959
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
+ }
12960
12997
  /**
12961
12998
  * 装配(index.ts apply 调用):仅当 CCC 配置 autotrajectory.enabled=true 时启动定时器;
12962
12999
  * 否则零资源占用。定时器 tick 检查唤起条件 → 注入活跃会话(agent.steer,前台可见)。
@@ -12970,36 +13007,12 @@ function registerAutoTrajectory(ctx) {
12970
13007
  if (running) return;
12971
13008
  const mdPath = resolveTargetMd(root, settings);
12972
13009
  if (!shouldWake(settings, mdPath, Date.now(), running)) return;
12973
- const agent = resolveTargetAgent(ctx, mdPath);
12974
- if (!agent) {
12975
- console.warn("[serenity-hooks] ✗ 自主轨迹唤起跳过:目标会话 agent 不可得(session use 激活后重试)");
12976
- return;
12977
- }
12978
13010
  running = true;
12979
13011
  (async () => {
12980
13012
  try {
12981
- const provider = settings.biasProvider?.trim() || "autotrajectory-bias.ts";
12982
- const biasRes = await fetchBiasContent(root, provider);
12983
- if (biasRes.error) {
12984
- console.error(`[serenity-hooks] ✗ 自主轨迹唤起中止(偏见内容缺失): ${biasRes.error}`);
12985
- return;
12986
- }
12987
- const motivation = readSelfGeneratedMotivation(mdPath);
12988
- const message = buildWakeMessage({
12989
- sessionName: basename(dirname(mdPath)),
12990
- mdPath,
12991
- intervalHours: settings.intervalHours ?? 12,
12992
- motivation,
12993
- biasContent: biasRes.text
12994
- });
12995
- agent.steer(createUserMessage({
12996
- content: [{
12997
- type: "text",
12998
- text: message
12999
- }],
13000
- source: PLUGIN_SOURCE
13001
- }));
13002
- 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}`);
13003
13016
  } catch (err) {
13004
13017
  console.warn(`[serenity-hooks] ✗ 自主轨迹唤起失败: ${String(err?.message ?? err)}`);
13005
13018
  } finally {
@@ -13036,6 +13049,45 @@ function resolveAutoTrajectoryRoot(ctx) {
13036
13049
  } catch {}
13037
13050
  return null;
13038
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
+ }
13039
13091
  //#endregion
13040
13092
  //#region src/index.ts
13041
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.12",
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": {