@shgroup/dsh-serenity-hooks 1.31.13 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +14 -11
  2. package/dsh.plugin.json +5 -5
  3. package/experiments/autopilot-trajectory/SKILL.md +23 -21
  4. package/experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts +69 -45
  5. package/lib/autopilot-script.d.ts +40 -0
  6. package/lib/autopilot-trajectory.d.ts +44 -5
  7. package/lib/{ccc-DAsSHsub.js → ccc-eu_IPF94.js} +17 -1
  8. package/lib/ccc.d.ts +30 -0
  9. package/lib/client.js +43 -11
  10. package/lib/diag-ops.d.ts +61 -0
  11. package/lib/index.d.ts +1 -1
  12. package/lib/index.js +4802 -4206
  13. package/lib/invariant.d.ts +5 -1
  14. package/lib/invariant.js +7 -3
  15. package/lib/json.d.ts +1 -1
  16. package/lib/msm-ops.d.ts +2 -2
  17. package/lib/output-guard-seam.d.ts +1 -1
  18. package/lib/rebuild.d.ts +4 -3
  19. package/lib/seams/guards.d.ts +27 -0
  20. package/lib/seams/keeper.d.ts +3 -3
  21. package/lib/{settings-section-01rTVq6v.js → settings-section-BuPGQv_H.js} +5 -3
  22. package/lib/settings-section.d.ts +15 -3
  23. package/lib/skiff-core.d.ts +1 -1
  24. package/lib/{skiff-debug-w2c3UqQk.js → skiff-debug-D83-Njy7.js} +7 -7
  25. package/lib/{skiff-role-BYvNnwv1.js → skiff-role-DAfPb6YQ.js} +1 -1
  26. package/lib/tools/acc-diag.d.ts +22 -0
  27. package/lib/tools/container-admin.d.ts +1 -1
  28. package/lib/tools/{session.d.ts → trajectory.d.ts} +32 -16
  29. package/lib/{session-bound-BpTayaIS.js → trajectory-bound-C8DUxi6_.js} +77 -276
  30. package/lib/{session-bound.d.ts → trajectory-bound.d.ts} +32 -2
  31. package/lib/{session-ops.d.ts → trajectory-ops.d.ts} +24 -28
  32. package/lib/wake-registry-D0Vj0WXb.js +270 -0
  33. package/lib/wake-registry.d.ts +146 -0
  34. package/lib/wake-scheduler.d.ts +115 -0
  35. package/lib/{weixin-route-wWk4AIwV.js → weixin-route-D1ND823E.js} +1 -1
  36. package/package.json +2 -2
  37. package/lib/tools/autopilot-trajectory.d.ts +0 -25
@@ -1,30 +1,27 @@
1
- import { a as findSerenityRoot } from "./ccc-DAsSHsub.js";
1
+ import { a as findSerenityRoot } from "./ccc-eu_IPF94.js";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join } from "node:path";
4
- //#region src/session-ops.ts
4
+ //#region src/trajectory-ops.ts
5
5
  /**
6
- * session-ops.ts — session 工具纯操作层(零 DSH 依赖,可独立单测)
6
+ * trajectory-ops.ts — 轨迹工具纯操作层(零 DSH 依赖,可独立单测)
7
7
  *
8
- * 行为对齐 osp(opencode-serenity-plugin/src/session/lib.ts)——osp 是 ACC 工具 spec
9
- * - create:--desc / --issue 二选一(互斥、缺省报错);issue 模式目录 YYYY-MM-DD--<issue>
10
- * (无 S###,sessionId=issue);desc 模式 YYYY-MM-DD--S###--<desc>;goal 写入目标段;dry-run 预览
11
- * - close:需 name + confirm=true;标记 [x] 已完成+已关闭 + 进度记录"关闭"
12
- * - archive:name 缺省 批量归档(completed + ≥7 → 移动 _archived/);单会话需 completed + grace
13
- * - list/show/health/qa/summary:文本输出格式与 osp 一致
14
- * 保留 dsp S134 活跃会话机制(内存 Map + events 恢复,不落盘)——osp 同为内存 active-state。
8
+ * 行为对齐 osp(opencode-serenity-plugin/src/session/lib.ts)——osp 是 ACC 工具 spec
9
+ *
10
+ * v1.33 收敛(S142 §32):本文件只服务 `container_trajectory` 的 6 个动作
11
+ * (list / show / create / use / rebuild / wake-later)。旧 `logbook` 面里
12
+ * **close / archive / health / qa 四个动作已删**(用户裁决「功能上废弃,实际上没用」),
13
+ * 其函数(closeSession / archiveSessions / healthCheck / qaCheck)**同批删除**——
14
+ * 保留下来的只有:create(--desc / --issue 二选一、dry-run 预览)、
15
+ * list(含 summarize 仪表盘)、show、use(激活 + 绑定)、以及供 list 用的 summarize。
16
+ * 「完成」不再由 close 写入,而是从 SESSION.md 的 `[x]` 推导;归档走 `container_fs mv`。
15
17
  */
16
- const SESSION_ACTIONS = [
18
+ const TRAJECTORY_ACTIONS = [
17
19
  "list",
18
20
  "show",
19
21
  "create",
20
22
  "use",
21
- "close",
22
- "health",
23
- "qa",
24
- "archive",
25
- "summary",
26
- "hook-develop-guide",
27
- "rebuild"
23
+ "rebuild",
24
+ "wake-later"
28
25
  ];
29
26
  /**
30
27
  * 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
@@ -45,11 +42,7 @@ function sessionEvents(session) {
45
42
  return s.events ?? [];
46
43
  }
47
44
  const SESSION_MD = "SESSION.md";
48
- const ARCHIVE_DIR_NAME = "_archived";
49
45
  const HEALTH_STALE_DAYS = 7;
50
- const HEALTH_STALLED_PCT = 30;
51
- const HEALTH_STALLED_DAYS = 3;
52
- const HEALTH_GHOST_DAYS = 2;
53
46
  const DAY = 864e5;
54
47
  function sessionsRoot(root) {
55
48
  return join(root, "AGENT_SESSIONS");
@@ -60,18 +53,12 @@ function parseSessionMd(filePath) {
60
53
  const content = readFileSync(filePath, "utf-8");
61
54
  return {
62
55
  hasSessionMd: true,
63
- completed: /\[\s*x\s*\]/i.test(content),
64
- completedCount: (content.match(/\[\s*x\s*\]/gi) ?? []).length,
65
- pendingCount: (content.match(/\[\s*[ \t]\s*\]/g) ?? []).length,
66
- unresolvedCount: (content.match(/(未解决|open|question|TODO)/gi) ?? []).length
56
+ completed: /\[\s*x\s*\]/i.test(content)
67
57
  };
68
58
  } catch {
69
59
  return {
70
60
  hasSessionMd: false,
71
- completed: false,
72
- completedCount: 0,
73
- pendingCount: 0,
74
- unresolvedCount: 0
61
+ completed: false
75
62
  };
76
63
  }
77
64
  }
@@ -83,10 +70,7 @@ function readSessionEntry(dirPath) {
83
70
  const mdPath = join(dirPath, SESSION_MD);
84
71
  const status = existsSync(mdPath) ? parseSessionMd(mdPath) : {
85
72
  hasSessionMd: false,
86
- completed: false,
87
- completedCount: 0,
88
- pendingCount: 0,
89
- unresolvedCount: 0
73
+ completed: false
90
74
  };
91
75
  return {
92
76
  dirName,
@@ -241,6 +225,9 @@ let lastActive = null;
241
225
  function getActiveSessionInfo(scope) {
242
226
  return activeStore.get(scope) ?? null;
243
227
  }
228
+ function getLastActiveSessionInfo() {
229
+ return lastActive;
230
+ }
244
231
  function setActiveSessionInfo(scope, info) {
245
232
  activeStore.set(scope, info);
246
233
  lastActive = info;
@@ -295,26 +282,6 @@ function useSession(root, key, scope = DEFAULT_SESSION_SCOPE) {
295
282
  };
296
283
  }
297
284
  /**
298
- * close 子命令(对齐 osp closeSession):需 name + confirm=true;
299
- * 标记 SESSION.md 为 [x] 已完成 + [x] 已关闭 + 进度记录"关闭";清除该会话的活跃状态。
300
- */
301
- function closeSession(root, key, confirm, scope = DEFAULT_SESSION_SCOPE) {
302
- if (!confirm) return "⚠ Close requires explicit confirmation.\n Re-run with --confirm to confirm closing this session.";
303
- const session = findSession(sessionsRoot(root), key);
304
- if (!session) throw new Error(`Session not found: "${key}". Use "list" to see available sessions.`);
305
- if (session.status.completed) return `Session "${session.dirName}" is already completed.`;
306
- const mdPath = join(session.path, SESSION_MD);
307
- if (!existsSync(mdPath)) throw new Error(`Session "${session.dirName}" has no SESSION.md — nothing to close.`);
308
- let content = readFileSync(mdPath, "utf-8");
309
- content = content.replace(/\r\n/g, "\n");
310
- content = content.replace(/## 状态\n\n?- \[ \] 进行中/, "## 状态\n- [x] 已完成\n- [x] 已关闭");
311
- const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
312
- if (!content.includes("-- 关闭")) content = content.replace(/(## 进度记录\n)/, `$1- ${now} — 关闭\n`);
313
- writeFileSync(mdPath, content, "utf-8");
314
- clearActiveSessionInfo(scope);
315
- return `Session "${session.dirName}" closed and marked as completed.`;
316
- }
317
- /**
318
285
  * 从会话历史(events)解析会话身份(进程重启恢复;只扫**当前会话**自己的历史——无跨会话串台)。
319
286
  *
320
287
  * v1.24.11 稳固化(S142 用户需求:重建后新会话必须准确知道从哪个 SESSION 恢复):
@@ -412,76 +379,7 @@ function resolveSessionByTitle(title, sessionsDir) {
412
379
  if (fuzzy.length === 1) return join(fuzzy[0].path, SESSION_MD);
413
380
  return null;
414
381
  }
415
- /** health 子命令(对齐 osp healthCheck:stale/stalled/ghost/drift 四类检查,文本输出) */
416
- function healthCheck(root) {
417
- const sessions = readAllSessions(sessionsRoot(root));
418
- if (sessions.length === 0) return "No sessions found — nothing to check.";
419
- const now = Date.now();
420
- const issues = [];
421
- for (const s of sessions) {
422
- const ageDays = (now - s.mtime.getTime()) / DAY;
423
- const st = s.status;
424
- if (ageDays > HEALTH_STALE_DAYS && !st.completed) issues.push({
425
- dirName: s.dirName,
426
- issue: `No activity for ${Math.floor(ageDays)}d`,
427
- severity: "stale"
428
- });
429
- const totalTasks = st.completedCount + st.pendingCount;
430
- if (totalTasks > 0) {
431
- const pct = Math.round(st.completedCount / totalTasks * 100);
432
- if (pct < HEALTH_STALLED_PCT && ageDays > HEALTH_STALLED_DAYS && !st.completed) issues.push({
433
- dirName: s.dirName,
434
- issue: `Only ${pct}% done after ${Math.floor(ageDays)}d`,
435
- severity: "stalled"
436
- });
437
- }
438
- if (!st.hasSessionMd && ageDays > HEALTH_GHOST_DAYS) issues.push({
439
- dirName: s.dirName,
440
- issue: "No SESSION.md (ghost directory)",
441
- severity: "ghost"
442
- });
443
- if (st.unresolvedCount > 3 && !st.completed) issues.push({
444
- dirName: s.dirName,
445
- issue: `${st.unresolvedCount} unresolved items`,
446
- severity: "drift"
447
- });
448
- }
449
- if (issues.length === 0) return "All sessions healthy — no issues found.";
450
- const lines = issues.map((i) => `[${i.severity.toUpperCase()}] ${i.dirName}: ${i.issue}`);
451
- return `${issues.length} issue(s) found:\n` + lines.join("\n");
452
- }
453
- /** archive 子命令(对齐 osp archiveSessions:移动 _archived/;name 缺省批量) */
454
- function archiveSessions(root, opts) {
455
- const { name, dryRun } = opts;
456
- const sessionsDir = sessionsRoot(root);
457
- const now = Date.now();
458
- const archiveDir = join(sessionsDir, ARCHIVE_DIR_NAME);
459
- if (name) {
460
- const session = findSession(sessionsDir, name);
461
- if (!session) throw new Error(`Session not found: "${name}"`);
462
- if (!session.status.completed) return `Session "${session.dirName}" is not completed — skipping.`;
463
- const ageDays = (now - session.mtime.getTime()) / DAY;
464
- if (ageDays < 7) return `Session "${session.dirName}" completed ${Math.floor(ageDays)}d ago — needs ${7 - Math.floor(ageDays)} more days before archiving.`;
465
- if (dryRun) return `[dry-run] Would archive: ${session.dirName} → ${ARCHIVE_DIR_NAME}/`;
466
- if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
467
- renameSync(session.path, join(archiveDir, session.dirName));
468
- return `Archived: ${session.dirName} → _archived/`;
469
- }
470
- const toArchive = readAllSessions(sessionsDir).filter((s) => {
471
- if (!s.status.completed) return false;
472
- return (now - s.mtime.getTime()) / DAY >= 7;
473
- });
474
- if (toArchive.length === 0) return "No sessions eligible for archiving.";
475
- if (dryRun) return `[dry-run] Would archive ${toArchive.length} session(s):\n` + toArchive.map((s) => ` ${s.dirName}`).join("\n");
476
- if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
477
- let count = 0;
478
- for (const s of toArchive) {
479
- renameSync(s.path, join(archiveDir, s.dirName));
480
- count++;
481
- }
482
- return `Archived ${count} session(s) → _archived/`;
483
- }
484
- /** summary 子命令(对齐 osp sessionSummary 文本仪表盘) */
382
+ /** summary 仪表盘(供 `list` 动作拼接;v1.33 起独立 `summary` 动作已撤,并入 list) */
485
383
  function summarize(root) {
486
384
  const sessions = readAllSessions(sessionsRoot(root));
487
385
  if (sessions.length === 0) return "AGENT_SESSIONS/ is empty.";
@@ -506,157 +404,7 @@ function summarize(root) {
506
404
  return ` ${s.status.completed ? "✓" : "○"} ${s.dirName} (${age}d ago)`;
507
405
  })
508
406
  ];
509
- if (stale > 0) lines.push("", "⚠ Warning: Stale sessions foundrun \"session health\" for details.");
510
- return lines.join("\n");
511
- }
512
- /** 事实核对:SESSION.md 声明 vs 实际情况(结构/一致性/新鲜度/决策质量/产出物) */
513
- function qaCheck(root, key) {
514
- const session = findSession(sessionsRoot(root), key);
515
- if (!session) throw new Error(`Session not found: "${key}". Use "list" to see available sessions.`);
516
- const mdPath = join(session.path, SESSION_MD);
517
- if (!existsSync(mdPath)) return `[ERROR] Session "${session.dirName}" has no SESSION.md — nothing to verify.`;
518
- const content = readFileSync(mdPath, "utf-8");
519
- const issues = [];
520
- for (const section of [
521
- {
522
- heading: "目标",
523
- label: "目标 (goal)"
524
- },
525
- {
526
- heading: "状态",
527
- label: "状态 (status)"
528
- },
529
- {
530
- heading: "关键决策",
531
- label: "关键决策 (key decisions)"
532
- },
533
- {
534
- heading: "进度记录",
535
- label: "进度记录 (progress)"
536
- },
537
- {
538
- heading: "产出物",
539
- label: "产出物 (outputs)"
540
- },
541
- {
542
- heading: "未解决的问题",
543
- label: "未解决的问题 (unresolved)"
544
- }
545
- ]) {
546
- const headingRegex = new RegExp(`^##\\s*${section.heading}[\\s\\S]*?(?=^##|(?![\\s\\S]))`, "m");
547
- const match = content.match(headingRegex);
548
- if (!match) {
549
- issues.push({
550
- severity: "warning",
551
- category: "structure",
552
- message: `Missing section: ${section.label}`
553
- });
554
- continue;
555
- }
556
- const headingLineRegex = new RegExp(`^##\\s*${section.heading}\\s*$`, "m");
557
- const body = match[0].replace(headingLineRegex, "").trim();
558
- if (!body || /^[-*]\s*$/.test(body)) issues.push({
559
- severity: "warning",
560
- category: "structure",
561
- message: `Section "${section.label}" is empty (only placeholder)`
562
- });
563
- }
564
- const completedTasks = (content.match(/\[\s*x\s*\]/gi) ?? []).length;
565
- const pendingTasks = (content.match(/\[\s*[ \t]\s*\]/gi) ?? []).length;
566
- const statusSection = content.match(/^##\s*状态[\s\S]*?(?=^##|(?![^]))/im);
567
- const statusBody = statusSection ? statusSection[0].replace(/^##\s*状态.*$/m, "").trim() : "";
568
- const hasCompletionMark = statusBody ? /#+\s*(?:完成|done|completed|closed)\b/i.test(statusBody) || /(?:全部完成|已全部完成|所有.*任务.*完成|任务.*全部完成|已完成.*所有)/i.test(statusBody) : false;
569
- const unresolvedSection = content.match(/^##\s*未解决的问题[\s\S]*?(?=^##|(?![^]))/im);
570
- const unresolvedBody = unresolvedSection ? unresolvedSection[0].replace(/^##\s*未解决的问题.*$/m, "").trim() : "";
571
- const unresolvedCount = unresolvedBody ? (unresolvedBody.match(/(?:未解决|open|question|TODO)/gi) ?? []).length : 0;
572
- if (hasCompletionMark && pendingTasks > 0) issues.push({
573
- severity: "error",
574
- category: "consistency",
575
- message: `Session marked as completed but has ${pendingTasks} pending task(s)`
576
- });
577
- if (hasCompletionMark && unresolvedCount > 0) issues.push({
578
- severity: "warning",
579
- category: "consistency",
580
- message: `Session marked as completed but has ${unresolvedCount} unresolved item(s)`
581
- });
582
- if (completedTasks > 0 && pendingTasks === 0 && !hasCompletionMark) issues.push({
583
- severity: "info",
584
- category: "consistency",
585
- message: `All ${completedTasks} task(s) completed but session not marked complete`
586
- });
587
- const progressSection = content.match(/##\s*进度记录[\s\S]*?(?=^##|\z)/m);
588
- if (progressSection) {
589
- const dateMatches = progressSection[0].match(/\b(\d{4}-\d{2}-\d{2})\b/g);
590
- if (dateMatches && dateMatches.length > 0) {
591
- const lastDateStr = dateMatches[dateMatches.length - 1];
592
- const lastDate = new Date(lastDateStr);
593
- const daysSince = Math.floor((Date.now() - lastDate.getTime()) / DAY);
594
- if (daysSince > HEALTH_STALE_DAYS && pendingTasks > 0) issues.push({
595
- severity: "warning",
596
- category: "stale",
597
- message: `No progress entry for ${daysSince} days (last: ${lastDateStr}), session still has ${pendingTasks} pending task(s)`
598
- });
599
- }
600
- }
601
- const decisionSection = content.match(/##\s*关键决策[\s\S]*?(?=^##|\z)/m);
602
- if (decisionSection) {
603
- const decisionLines = decisionSection[0].split("\n").filter((l) => /^\|\s*\d+\s*\|/.test(l));
604
- if (decisionLines.length > 0) {
605
- const emptyDecisions = decisionLines.filter((l) => {
606
- const cells = l.split("|").map((c) => c.trim());
607
- return cells.length >= 4 && (!cells[2] || !cells[3] || cells[2] === "-" || cells[3] === "-");
608
- });
609
- if (emptyDecisions.length > 0) issues.push({
610
- severity: "info",
611
- category: "quality",
612
- message: `${emptyDecisions.length} decision(s) have empty reason — consider filling gaps`
613
- });
614
- } else if (!hasCompletionMark) issues.push({
615
- severity: "info",
616
- category: "quality",
617
- message: "No decisions recorded yet — add key decisions as the session progresses"
618
- });
619
- }
620
- const outputSection = content.match(/##\s*产出物[\s\S]*?(?=^##|\z)/m);
621
- if (outputSection) {
622
- const outputLines = outputSection[0].split("\n").filter((l) => /^\s*[-*]\s/.test(l));
623
- const fileRefs = [];
624
- for (const line of outputLines) {
625
- const refs = line.match(/`[^`]+`/g) ?? [];
626
- fileRefs.push(...refs.map((r) => r.replace(/`/g, "")));
627
- const inlineRefs = line.match(/\b[\w./-]+\.[a-zA-Z]{1,5}\b/g) ?? [];
628
- fileRefs.push(...inlineRefs.filter((r) => r.includes("/") || r.includes(".")));
629
- }
630
- if (fileRefs.length > 0) {
631
- const missing = fileRefs.filter((ref) => !existsSync(join(root, ref)));
632
- if (missing.length > 0 && completedTasks > 0) issues.push({
633
- severity: "warning",
634
- category: "outputs",
635
- message: `${missing.length} referenced file(s) not found: ${missing.slice(0, 3).join(", ")}${missing.length > 3 ? `... (+${missing.length - 3} more)` : ""}`
636
- });
637
- }
638
- }
639
- const errorCount = issues.filter((i) => i.severity === "error").length;
640
- const warningCount = issues.filter((i) => i.severity === "warning").length;
641
- const infoCount = issues.filter((i) => i.severity === "info").length;
642
- const verified = errorCount === 0 && warningCount === 0;
643
- const lines = [
644
- `QA Report: ${session.dirName}`,
645
- `────────────────${"─".repeat(session.dirName.length)}`,
646
- `Summary: ${issues.length} issue(s) found (${errorCount} error, ${warningCount} warning, ${infoCount} info)`,
647
- `Status: ${verified ? "✓ Verified" : "⚠ Issues found"}`
648
- ];
649
- if (issues.length > 0) {
650
- lines.push("");
651
- for (const issue of issues) {
652
- const tag = issue.severity === "error" ? "ERR" : issue.severity === "warning" ? "WRN" : "INF";
653
- lines.push(` [${tag}:${issue.category}] ${issue.message}`);
654
- }
655
- }
656
- lines.push("", "Recommendations:");
657
- if (errorCount > 0) lines.push(" • Fix errors before closing the session (status vs content mismatch)");
658
- if (warningCount > 0) lines.push(" • Review warnings — they may indicate incomplete or outdated information");
659
- if (verified) lines.push(" • Session looks clean — no issues detected");
407
+ if (stale > 0) lines.push("", "⚠ Warning: stale trajectories present (no activity > 7d) see the list above for detail.");
660
408
  return lines.join("\n");
661
409
  }
662
410
  /** 绑定文件相对 CCC 根的路径 */
@@ -734,6 +482,59 @@ function readLastBound(session) {
734
482
  }
735
483
  return readLegacyBoundFromEvents(session);
736
484
  }
485
+ /** 目录名 → 轨迹标签(`--S###--` 命中取 `S###`;未命中原样返回——编码无关 U4) */
486
+ function trajectoryLabel(dirName) {
487
+ const m = dirName.match(/--S(\d{3,})--/);
488
+ return m ? `S${m[1]}` : dirName;
489
+ }
490
+ /**
491
+ * **调用方自身**所属 trajectory 的标签(审计字段 `createdBy` 的唯一正解)。
492
+ *
493
+ * 三级来源,逐级回退(R↓:为什么不用单一来源):
494
+ * 1. `readLastBound(session)` —— **持久 bound**,权威(重启存活、不受别的会话激活污染)
495
+ * 2. `getActiveSessionInfo(scope)` —— 本 dsh 会话的**内存**激活(按 scope 隔离)
496
+ * 3. `getLastActiveSessionInfo()` —— **全局**最近激活指针(无绑定的会话兼容回退)
497
+ *
498
+ * ⚠️ 为何单列成本函数(S142 §30.12.1 实测缺陷):唤醒注册表的 `createdBy` 原先直接取第 3 级
499
+ * 全局指针,而它记录的是"**进程内最近一次被激活的 trajectory**"——多会话并发时必然张冠李戴
500
+ * (实测:S142 登记唤醒被写成 `by=S060`)。`createdBy` 是 D60「以可审计替代入口栅」的**唯一凭证**,
501
+ * 失真即替代控制失效 ⇒ 必须以"调用方自己是谁"为准,全局指针只作最后兜底。
502
+ *
503
+ * @param session 调用方 dsh 会话对象(需 `header.id` + `header.cwd`)
504
+ * @param scope 调用方 dsh 会话 id(取不到传空串)
505
+ * @returns 轨迹标签(`S###` 或完整目录名);三级全落空 → 空串(渲染为 `?`)
506
+ */
507
+ function resolveSessionTrajectoryLabel(session, scope) {
508
+ const bound = session ? readLastBound(session) : null;
509
+ if (bound?.dirName) return trajectoryLabel(bound.dirName);
510
+ const scoped = scope !== "" ? getActiveSessionInfo(scope) : null;
511
+ if (scoped) return scoped.sessionId;
512
+ return getLastActiveSessionInfo()?.sessionId ?? "";
513
+ }
514
+ /**
515
+ * 反向查:绑定到某 trajectory 目录名的 dsh 会话 id(**按绑定时间倒序,最新在前**)。
516
+ *
517
+ * 用途(wake-scheduler 冷唤醒):.bindings.json 以 **sessionId 为键**,而唤醒条目的
518
+ * target 是**目录名**——需要值侧扫描做反向索引。冷会话被唤醒时必须先知道"哪个 dsh
519
+ * 会话承载这条轨迹",这是唯一的权威来源(标题只作回退,见 v1.29.2 R2)。
520
+ *
521
+ * @param root CCC 根
522
+ * @param dirName AGENT_SESSIONS 目录名(完整名,如 `2026-09-01--S151--x--auto`)
523
+ * @returns 会话 id 列表(最新绑定在前);无绑定 → `[]`
524
+ */
525
+ function listBoundSessionIds(root, dirName) {
526
+ const file = readBindingsFile(join(root, BINDINGS_REL_PATH));
527
+ const hits = [];
528
+ for (const [id, raw] of Object.entries(file.sessions)) {
529
+ const rec = asBoundRecord(raw);
530
+ if (!rec || rec.dirName !== dirName) continue;
531
+ hits.push({
532
+ id,
533
+ at: typeof rec.at === "number" ? rec.at : 0
534
+ });
535
+ }
536
+ return hits.sort((a, b) => b.at - a.at).map((h) => h.id);
537
+ }
737
538
  /**
738
539
  * 写入绑定(latest-wins 覆盖该会话记录)。
739
540
  *
@@ -763,4 +564,4 @@ function appendBound(session, action, rec) {
763
564
  }
764
565
  }
765
566
  //#endregion
766
- export { useSession as C, summarize as S, resolveSessionByTitle as _, archiveSessions as a, setActiveSessionInfo as b, createSession as c, getActiveSessionInfo as d, healthCheck as f, readActiveSessionMd as g, qaCheck as h, SESSION_ACTIONS as i, extractSessionMdPathFromText as l, parseSessionContextFromEvents as m, readLastBound as n, clearActiveSessionInfo as o, listSessions as p, DEFAULT_SESSION_SCOPE as r, closeSession as s, appendBound as t, findSession as u, sessionEvents as v, showSession as x, sessionsRoot as y };
567
+ export { sessionsRoot as _, DEFAULT_SESSION_SCOPE as a, summarize as b, createSession as c, getActiveSessionInfo as d, listSessions as f, sessionEvents as g, resolveSessionByTitle as h, resolveSessionTrajectoryLabel as i, extractSessionMdPathFromText as l, readActiveSessionMd as m, listBoundSessionIds as n, TRAJECTORY_ACTIONS as o, parseSessionContextFromEvents as p, readLastBound as r, clearActiveSessionInfo as s, appendBound as t, findSession as u, setActiveSessionInfo as v, useSession as x, showSession as y };
@@ -1,8 +1,8 @@
1
1
  /**
2
- * session-bound.ts — SESSION 绑定持久化(CCC 内 `AGENT_SESSIONS/.bindings.json`)
2
+ * trajectory-bound.ts — SESSION 绑定持久化(CCC 内 `AGENT_SESSIONS/.bindings.json`)
3
3
  *
4
4
  * 目标(S142 用户拍板,方案 v1.0):让「dsh 会话(载体)↔ SESSION(宁静号轨迹)」
5
- * 的绑定坚固——LLM 不能因智力因素(幻觉/理解偏差)在过程中或 logbook rebuild 后
5
+ * 的绑定坚固——LLM 不能因智力因素(幻觉/理解偏差)在过程中或 container_trajectory rebuild 后
6
6
  * 自行更换 SESSION。
7
7
  *
8
8
  * 存储形态(v1.30.6 起,S142 review F-01):绑定**不再写入 dsh 会话日志**。
@@ -45,6 +45,36 @@ export declare function bindingsPathFor(session: unknown): string | null;
45
45
  export declare function readLastBound(session: unknown): SessionBoundRecord | null;
46
46
  /** 判定会话是否已有绑定(供 reconcile 判定:无绑定才标题升级)。 */
47
47
  export declare function hasAnyBound(session: unknown): boolean;
48
+ /**
49
+ * **调用方自身**所属 trajectory 的标签(审计字段 `createdBy` 的唯一正解)。
50
+ *
51
+ * 三级来源,逐级回退(R↓:为什么不用单一来源):
52
+ * 1. `readLastBound(session)` —— **持久 bound**,权威(重启存活、不受别的会话激活污染)
53
+ * 2. `getActiveSessionInfo(scope)` —— 本 dsh 会话的**内存**激活(按 scope 隔离)
54
+ * 3. `getLastActiveSessionInfo()` —— **全局**最近激活指针(无绑定的会话兼容回退)
55
+ *
56
+ * ⚠️ 为何单列成本函数(S142 §30.12.1 实测缺陷):唤醒注册表的 `createdBy` 原先直接取第 3 级
57
+ * 全局指针,而它记录的是"**进程内最近一次被激活的 trajectory**"——多会话并发时必然张冠李戴
58
+ * (实测:S142 登记唤醒被写成 `by=S060`)。`createdBy` 是 D60「以可审计替代入口栅」的**唯一凭证**,
59
+ * 失真即替代控制失效 ⇒ 必须以"调用方自己是谁"为准,全局指针只作最后兜底。
60
+ *
61
+ * @param session 调用方 dsh 会话对象(需 `header.id` + `header.cwd`)
62
+ * @param scope 调用方 dsh 会话 id(取不到传空串)
63
+ * @returns 轨迹标签(`S###` 或完整目录名);三级全落空 → 空串(渲染为 `?`)
64
+ */
65
+ export declare function resolveSessionTrajectoryLabel(session: unknown, scope: string): string;
66
+ /**
67
+ * 反向查:绑定到某 trajectory 目录名的 dsh 会话 id(**按绑定时间倒序,最新在前**)。
68
+ *
69
+ * 用途(wake-scheduler 冷唤醒):.bindings.json 以 **sessionId 为键**,而唤醒条目的
70
+ * target 是**目录名**——需要值侧扫描做反向索引。冷会话被唤醒时必须先知道"哪个 dsh
71
+ * 会话承载这条轨迹",这是唯一的权威来源(标题只作回退,见 v1.29.2 R2)。
72
+ *
73
+ * @param root CCC 根
74
+ * @param dirName AGENT_SESSIONS 目录名(完整名,如 `2026-09-01--S151--x--auto`)
75
+ * @returns 会话 id 列表(最新绑定在前);无绑定 → `[]`
76
+ */
77
+ export declare function listBoundSessionIds(root: string, dirName: string): string[];
48
78
  /**
49
79
  * 写入绑定(latest-wins 覆盖该会话记录)。
50
80
  *
@@ -1,17 +1,29 @@
1
1
  /**
2
- * session-ops.ts — session 工具纯操作层(零 DSH 依赖,可独立单测)
2
+ * trajectory-ops.ts — 轨迹工具纯操作层(零 DSH 依赖,可独立单测)
3
3
  *
4
- * 行为对齐 osp(opencode-serenity-plugin/src/session/lib.ts)——osp 是 ACC 工具 spec
5
- * - create:--desc / --issue 二选一(互斥、缺省报错);issue 模式目录 YYYY-MM-DD--<issue>
6
- * (无 S###,sessionId=issue);desc 模式 YYYY-MM-DD--S###--<desc>;goal 写入目标段;dry-run 预览
7
- * - close:需 name + confirm=true;标记 [x] 已完成+已关闭 + 进度记录"关闭"
8
- * - archive:name 缺省 批量归档(completed + ≥7 → 移动 _archived/);单会话需 completed + grace
9
- * - list/show/health/qa/summary:文本输出格式与 osp 一致
10
- * 保留 dsp S134 活跃会话机制(内存 Map + events 恢复,不落盘)——osp 同为内存 active-state。
4
+ * 行为对齐 osp(opencode-serenity-plugin/src/session/lib.ts)——osp 是 ACC 工具 spec
5
+ *
6
+ * v1.33 收敛(S142 §32):本文件只服务 `container_trajectory` 的 6 个动作
7
+ * (list / show / create / use / rebuild / wake-later)。旧 `logbook` 面里
8
+ * **close / archive / health / qa 四个动作已删**(用户裁决「功能上废弃,实际上没用」),
9
+ * 其函数(closeSession / archiveSessions / healthCheck / qaCheck)**同批删除**——
10
+ * 保留下来的只有:create(--desc / --issue 二选一、dry-run 预览)、
11
+ * list(含 summarize 仪表盘)、show、use(激活 + 绑定)、以及供 list 用的 summarize。
12
+ * 「完成」不再由 close 写入,而是从 SESSION.md 的 `[x]` 推导;归档走 `container_fs mv`。
11
13
  */
12
14
  import type { JsonValue } from './json.js';
13
- export type SessionAction = 'list' | 'show' | 'create' | 'use' | 'close' | 'health' | 'qa' | 'archive' | 'summary' | 'hook-develop-guide' | 'rebuild';
14
- export declare const SESSION_ACTIONS: readonly SessionAction[];
15
+ /**
16
+ * `container_trajectory` 工具的动作集合(v1.33,S142 §32 用户裁决后的收敛结果)。
17
+ *
18
+ * 为什么只有 6 个(R↓):旧面是 `logbook`(11)+ `trajectory`(12)两个工具,用户裁决
19
+ * 「都合并成 trajectory,废除 logbook 这个词」并逐条删并 ——
20
+ * summary 并进 list|health/qa 淘汰(判据=旧模板,EAP 已取代;仅"空壳/长期无活动"并入 use)|
21
+ * close/archive 删(completed 由 SESSION.md 的 `[x]` 推导;归档走 container_fs mv)|
22
+ * hook-develop-guide 并进 container_admin msm guide|autopilot 面归 container_admin 的 autopilot 域|
23
+ * wake-add/list/rm 收敛为 wake-later。
24
+ */
25
+ export type TrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'wake-later';
26
+ export declare const TRAJECTORY_ACTIONS: readonly TrajectoryAction[];
15
27
  /**
16
28
  * 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
17
29
  * 移除 `.events` 属性 → `snapshotEvents()` 方法(dsh-session/src/session.ts:
@@ -26,10 +38,8 @@ export declare function sessionEvents<T = unknown>(session: unknown): readonly T
26
38
  export declare function sessionsRoot(root: string): string;
27
39
  interface SessionStatus {
28
40
  hasSessionMd: boolean;
41
+ /** 「完成」的判据 = SESSION.md 里出现 `[x]`(v1.33 起不再由 close 动作写入) */
29
42
  completed: boolean;
30
- completedCount: number;
31
- pendingCount: number;
32
- unresolvedCount: number;
33
43
  }
34
44
  export interface SessionEntry {
35
45
  dirName: string;
@@ -98,11 +108,6 @@ export declare function useSession(root: string, key: string, scope?: string): {
98
108
  mdPath: string;
99
109
  context: string;
100
110
  };
101
- /**
102
- * close 子命令(对齐 osp closeSession):需 name + confirm=true;
103
- * 标记 SESSION.md 为 [x] 已完成 + [x] 已关闭 + 进度记录"关闭";清除该会话的活跃状态。
104
- */
105
- export declare function closeSession(root: string, key: string, confirm: boolean, scope?: string): string;
106
111
  /**
107
112
  * 从会话历史(events)解析会话身份(进程重启恢复;只扫**当前会话**自己的历史——无跨会话串台)。
108
113
  *
@@ -136,16 +141,7 @@ export declare function resolveSessionByTitle(title: string, sessionsDir: string
136
141
  * 只作最后手段(内存/events/锚点全缺时),保证重建锚点至少指向一个真实存在的轨迹。
137
142
  */
138
143
  export declare function findLatestActiveSessionMd(root: string): string | null;
139
- /** health 子命令(对齐 osp healthCheck:stale/stalled/ghost/drift 四类检查,文本输出) */
140
- export declare function healthCheck(root: string): string;
141
- /** archive 子命令(对齐 osp archiveSessions:移动 _archived/;name 缺省批量) */
142
- export declare function archiveSessions(root: string, opts: {
143
- name?: string;
144
- dryRun: boolean;
145
- }): string;
146
- /** summary 子命令(对齐 osp sessionSummary 文本仪表盘) */
144
+ /** summary 仪表盘(供 `list` 动作拼接;v1.33 起独立 `summary` 动作已撤,并入 list) */
147
145
  export declare function summarize(root: string): string;
148
- /** 事实核对:SESSION.md 声明 vs 实际情况(结构/一致性/新鲜度/决策质量/产出物) */
149
- export declare function qaCheck(root: string, key: string): string;
150
146
  export type SessionInfo = SessionEntry;
151
147
  export type { JsonValue };