@shgroup/dsh-serenity-hooks 1.47.3 → 1.48.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.
- package/dsh.plugin.json +1 -1
- package/lib/agent-idle.d.ts +17 -3
- package/lib/api.d.ts +0 -10
- package/lib/{ccc-roots-NpTWo0-c.js → ccc-roots-Duk89a-h.js} +11 -0
- package/lib/ccc-roots.d.ts +12 -2
- package/lib/clock-runtime.d.ts +12 -7
- package/lib/cro-turns.d.ts +19 -6
- package/lib/cro.d.ts +0 -2
- package/lib/index.js +2373 -58
- package/lib/live-sessions.d.ts +14 -4
- package/lib/{message-source-aR-FJ38L.js → message-source-D72jte0u.js} +46 -5
- package/lib/msm-ops.d.ts +3 -1
- package/lib/opencode-provider.d.ts +53 -0
- package/lib/tools/trajectory.d.ts +0 -22
- package/lib/trajectory-assistant.d.ts +3 -8
- package/lib/trajectory-bound.d.ts +0 -17
- package/lib/trajectory-ops.d.ts +29 -2
- package/lib/trajectory-skills.d.ts +0 -2
- package/lib/wake-scheduler.d.ts +4 -5
- package/package.json +1 -1
- package/lib/session-cleanup-BtkSmry8.js +0 -213
- package/lib/wake-scheduler-BR4hD8Vj.js +0 -1888
package/lib/live-sessions.d.ts
CHANGED
|
@@ -15,8 +15,18 @@
|
|
|
15
15
|
* 留下的 = **进程 cwd/CCC + live 会话清单**(`acc-diag` ① 段仍需要)。
|
|
16
16
|
*
|
|
17
17
|
* 判据纪律:本模块**不得**反向依赖 autopilot 判据层(那会让退场半途而废)。
|
|
18
|
+
*
|
|
19
|
+
* ── 🔴 2026-09-25 I2 收敛(约束文档 §2.7 违反清单,L1 违反之一)────────────────
|
|
20
|
+
* **本模块的 `ctx` 参数类型从 `Context` 改为 `unknown`** —— 消除本文件唯一的 `W2` 接触
|
|
21
|
+
* (直接 import 宿主 `cordis` 的类型)。
|
|
22
|
+
*
|
|
23
|
+
* **为什么这是"真解耦"而不是"把类型藏起来"(判据):** `ctx` 在本模块**从不被解引用** ——
|
|
24
|
+
* 它只被**原样透传**给 `hostSessions(ctx)`,而后者(`host/access.ts`,**L0**)的参数
|
|
25
|
+
* 本就声明为 `ctx: unknown` ⇒ `Context` 这个 import **在语义上是多余的**,
|
|
26
|
+
* 它没有参与任何类型检查,只贡献了一处 L1→宿主 的接触点。
|
|
27
|
+
* 🔵 **证据**:grep 本文件 `ctx` 全部 5 处命中 —— 3 处在参数/调用位、2 处在注释,
|
|
28
|
+
* **零处**属性访问(`ctx.xxx`)。
|
|
18
29
|
*/
|
|
19
|
-
import type { Context } from 'cordis';
|
|
20
30
|
/** live 会话条目(诊断用;标题从 events 读) */
|
|
21
31
|
export interface LiveSessionEntry {
|
|
22
32
|
id: string;
|
|
@@ -36,10 +46,10 @@ export declare function readSessionTitle(session: unknown): string | null;
|
|
|
36
46
|
* CCC 枚举("本机有哪些 CCC")**不在这里**,归 `ccc-roots.listCccs`(并集语义);
|
|
37
47
|
* 本函数保留的是它**独有**的那部分:会话维度 + cwd 归属 + 标题 + id。
|
|
38
48
|
*
|
|
39
|
-
* @param ctx
|
|
49
|
+
* @param ctx 插件上下文(**只透传**给 L0 取数口,本模块不解引用 ⇒ 类型为 `unknown`)
|
|
40
50
|
* @returns 逐条 live 会话
|
|
41
51
|
*/
|
|
42
|
-
export declare function listLiveSessions(ctx:
|
|
52
|
+
export declare function listLiveSessions(ctx: unknown): LiveSessionEntry[];
|
|
43
53
|
/**
|
|
44
54
|
* 进程内诊断(`acc-diag` ① 段数据源)——输出当前实例的进程归属 + live 会话清单。
|
|
45
55
|
*
|
|
@@ -51,4 +61,4 @@ export interface DiagLiveReport {
|
|
|
51
61
|
processCcc: string | null;
|
|
52
62
|
liveSessions: LiveSessionEntry[];
|
|
53
63
|
}
|
|
54
|
-
export declare function diagLive(ctx:
|
|
64
|
+
export declare function diagLive(ctx: unknown): DiagLiveReport;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as cccRootForCwd } from "./ccc-roots-
|
|
1
|
+
import { r as cccRootForCwd } from "./ccc-roots-Duk89a-h.js";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
//#region src/time.ts
|
|
@@ -227,10 +227,17 @@ function showSession(root, key) {
|
|
|
227
227
|
const content = readFileSync(mdPath, "utf-8");
|
|
228
228
|
return `# ${session.dirName}\n\n${content}`;
|
|
229
229
|
}
|
|
230
|
-
/**
|
|
230
|
+
/**
|
|
231
|
+
* 生成 SESSION.md 模板(固定格式,owner 2026-09-25 裁决;时间戳 YYYY-MM-DD HH:mm)。
|
|
232
|
+
*
|
|
233
|
+
* 🔴 **状态行是被解析的机器契约**:`completed` 的判据 = SESSION.md 里出现 `[x]`(见 `parseSessionMd`)
|
|
234
|
+
* ⇒ 状态行必须带标记 token:未完成 `- 状态: [ ] <label>`/已完成 `- 状态: [x] <label>`。
|
|
235
|
+
* ⚠️ 改模板时**两件事必须同时成立**:① 状态行保留 `[ ]`(且全模板不得出现任何 `[x]`——否则新会话
|
|
236
|
+
* 一出生就被判 completed)② 七个固定段的名字与顺序不得动(契约钉在 `tests/trajectory-ops.test.ts`)。
|
|
237
|
+
*/
|
|
231
238
|
function sessionMdTemplate(title, id, goal, now) {
|
|
232
239
|
const ts = localDateTimeMinutes(now);
|
|
233
|
-
return `# SESSION: ${title}\n- ID: ${id}\n\n##
|
|
240
|
+
return `# SESSION: ${title}\n- ID: ${id}\n- 状态: [ ] 进行中\n\n## 身份与边界\n${goal ?? "(待补充)"}\n\n## 权威锚点\n- (指针:仓库 / 工具 / 单一真相源位置)\n\n## 工作纪律\n- (只放本轨迹特有的;通用项外推到 skill)\n\n## 决策\n| # | 决策 | 理由 / 否决项 |\n|---|------|------|\n| 1 | | |\n\n## 日志\n- ${ts} — 创建(里程碑 + 证据指针;执行细节归 commit message)\n\n## 当前状态\n- 在办:\n- 断点:\n\n## 待 owner 裁决\n- \n`;
|
|
234
241
|
}
|
|
235
242
|
/** create 子命令(对齐 osp createSession:--desc/--issue 二选一 + dry-run + 长度限制) */
|
|
236
243
|
/** 目录名脱敏(Windows 审计问题 10):非法字符 → '-', 去尾点/空格, 保留名(CON/NUL 等)加前缀 */
|
|
@@ -306,6 +313,39 @@ function createSession(opts) {
|
|
|
306
313
|
const DEFAULT_SESSION_SCOPE = "default";
|
|
307
314
|
/** [SESSION CONTEXT] 恢复标记(use 时注入 events 历史;进程重启后从 events 解析) */
|
|
308
315
|
const SESSION_CONTEXT_MARKER = "[SESSION CONTEXT] Activated:";
|
|
316
|
+
/**
|
|
317
|
+
* 概括清洗(标题用;需求② S142 用户拍板:编号日期后加 ≤20 字内容概括)。
|
|
318
|
+
* 规则(服务端统一,不信任 LLM 输入):
|
|
319
|
+
* - 去控制字符/换行/回车/制表(防标题注入/多行污染)
|
|
320
|
+
* - trim(去首尾空白)
|
|
321
|
+
* - 截断 ≤20 字符(按 Unicode 码点——中英混排统一;emoji 等代理对按码点保留)
|
|
322
|
+
* - 去 `/`(防标题被误读为路径分隔)
|
|
323
|
+
* @returns 清洗后的概括(空输入 → 空串;调用方决定是否允许空)
|
|
324
|
+
*/
|
|
325
|
+
function sanitizeSessionSummary(summary) {
|
|
326
|
+
return [...summary.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\//g, "").trim()].slice(0, 20).join("");
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 从激活会话派生命名标题(v1.22.9 格式修正 + 需求② 概括):
|
|
330
|
+
* F3 原始需求是 **`S###-日期`**(如 `S143-2026-08-26`)——从 `sessionId` 派生,
|
|
331
|
+
* 而非完整目录名(`2026-08-24--S142--...` 超长 + 中文,不符合用户拍板格式)。
|
|
332
|
+
* 需求②(S142 用户拍板):编号日期后加 ≤20 字内容概括 → `S###-YYYY-MM-DD-<概括>`
|
|
333
|
+
* ——概括来自显式 summary 参数(服务端截断/清洗,可靠不靠猜);编号日期仍固定派生。
|
|
334
|
+
* 无 S### 编号(issue 会话等)→ 回退目录名。
|
|
335
|
+
* @param active 激活会话信息(sessionId + dirName)
|
|
336
|
+
* @param summary 内容概括(≤20 字,服务端清洗截断;空 → 不带概括的 `S###-日期`)
|
|
337
|
+
* @returns `S143-2026-08-26-概括` / `S143-2026-08-26` / 原目录名
|
|
338
|
+
*/
|
|
339
|
+
function namingTitleFor(active, summary) {
|
|
340
|
+
const sid = active.sessionId;
|
|
341
|
+
if (typeof sid === "string" && /^S\d+$/.test(sid)) {
|
|
342
|
+
const date = active.dirName.match(/^(\d{4}-\d{2}-\d{2})--/)?.[1] ?? "";
|
|
343
|
+
const cleaned = summary ? sanitizeSessionSummary(summary) : "";
|
|
344
|
+
const base = date ? `${sid}-${date}` : sid;
|
|
345
|
+
return cleaned ? `${base}-${cleaned}` : base;
|
|
346
|
+
}
|
|
347
|
+
return active.dirName;
|
|
348
|
+
}
|
|
309
349
|
/** 内存活跃会话:scope(dsh 会话 id)→ 会话信息(不落盘;并行多会话各自 key 隔离) */
|
|
310
350
|
const activeStore = /* @__PURE__ */ new Map();
|
|
311
351
|
/** 全局最近活跃(对齐 osp lastActive;供无 scope 上下文使用) */
|
|
@@ -358,7 +398,8 @@ function useSession(root, key, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
358
398
|
``,
|
|
359
399
|
`→ All subsequent work should refer back to this session.`,
|
|
360
400
|
` Use "session show ${sessionId}" to check current progress.`,
|
|
361
|
-
`
|
|
401
|
+
` When a milestone lands or a decision is made: overwrite "当前状态" and append ONE`,
|
|
402
|
+
` milestone line to "日志" (one line + an evidence pointer — details go to the commit message).`,
|
|
362
403
|
``,
|
|
363
404
|
`→ BEFORE responding to the user, you MUST call todowrite immediately`,
|
|
364
405
|
` with the session todo list. The first item MUST be:`,
|
|
@@ -924,4 +965,4 @@ const ACC_MESSAGE_KIND = "plugin:dsh-serenity-hooks";
|
|
|
924
965
|
/** ACC 注入消息的统一来源值(各缝共用;需要 `form` 时按 `ContextFormed` 另加字段)。 */
|
|
925
966
|
const PLUGIN_SOURCE = { kind: ACC_MESSAGE_KIND };
|
|
926
967
|
//#endregion
|
|
927
|
-
export {
|
|
968
|
+
export { localIdStamp as A, setActiveSessionInfo as C, isoLocal as D, useSession as E, localFileStamp as O, sessionsRoot as S, summarize as T, namingTitleFor as _, pruneMissingBindings as a, resolveSessionByTitle as b, setBindingStore as c, clearActiveSessionInfo as d, createSession as f, listSessions as g, getActiveSessionInfo as h, listBoundSessionIds as i, localHuman as k, DEFAULT_SESSION_SCOPE as l, findSession as m, PLUGIN_SOURCE as n, readLastBound as o, extractSessionMdPathFromText as p, appendBound as r, resolveSessionTrajectoryLabel as s, ACC_MESSAGE_KIND as t, TRAJECTORY_ACTIONS as u, parseSessionContextFromEvents as v, showSession as w, sessionEvents as x, readActiveSessionMd as y };
|
package/lib/msm-ops.d.ts
CHANGED
|
@@ -24,7 +24,9 @@ export interface MsmEntry {
|
|
|
24
24
|
usage?: string;
|
|
25
25
|
flags?: MsmFlag[];
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
/** 🔴 2026-09-25(S142 第 1 批):本接口**必须导出** —— `runMsm`/`runMsmAsync` 的**公开签名**引用它
|
|
28
|
+
* (此前未导出 ⇒ 外部调用方看不到该类型,只能传结构等价字面量 = 契约不完整)。 */
|
|
29
|
+
export interface MsmArgs {
|
|
28
30
|
action: MsmAction;
|
|
29
31
|
name?: string;
|
|
30
32
|
args?: string[];
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
* - **L2 建路由**:环境里有 `OPENCODE_API_KEY` 且没有任何 opencode 路由 → 建 `opencode-go` 并带全头。
|
|
17
17
|
* “有 key”就是用户意图的最强证据 → 不需要的人(没 key)零影响(不会平白多出一组模型)。
|
|
18
18
|
*
|
|
19
|
+
* ## L3 补模型(v1.48.0;S142 owner 具名令「做成自动吧」)
|
|
20
|
+
*
|
|
21
|
+
* pi-ai 目录**收录不全**(如 `deepseek-v4.1-flash` 已在官方端点表、目录里却没有)⇒
|
|
22
|
+
* 面板不显示;手加也不带推理(目录外模型 `base` 为 undefined ⇒ `reasoning` 恒 false,
|
|
23
|
+
* 见 {@link OPENCODE_EXTRA_MODELS} 的注释)⇒ **"能用但没 thinking"**。
|
|
24
|
+
* 本层把 {@link OPENCODE_EXTRA_MODELS} 里的模型补进该路由的 `models` 列表。
|
|
25
|
+
*
|
|
26
|
+
* 🔴 **三条拒绝条件**(每条都对应一个会把配置弄坏的真实形态,宁可不动):
|
|
27
|
+
* ① 路由**没有显式 `models` 列表** —— 补 `models` 会顶掉整个 pi-ai 目录(宿主是"整体替换"语义)
|
|
28
|
+
* ② 列表里含**已知异协议**模型 —— 设路由级 `api` 会覆盖它们的 `base.api`
|
|
29
|
+
* ③ 路由已显式设了**别的 `api`** —— 同上,且那是用户的显式选择
|
|
30
|
+
* 判据与来源见 `planExtraModels` / `OPENCODE_GO_NON_COMPLETIONS`。
|
|
31
|
+
*
|
|
19
32
|
* ## 注入通道(唯一可用且是官方的)
|
|
20
33
|
*
|
|
21
34
|
* `ctx.settings.update('llm-pi-ai', patch)` —— `dsh-settings` 的官方写入面:
|
|
@@ -68,6 +81,37 @@ export declare const OPENCODE_API_KEY_ENV = "OPENCODE_API_KEY";
|
|
|
68
81
|
* 头名大小写不敏感是 HTTP 语义,但**名字不同就是两个头**,故都要。
|
|
69
82
|
*/
|
|
70
83
|
export declare function opencodeRouteHeaders(): Record<string, string>;
|
|
84
|
+
/**
|
|
85
|
+
* 一个「pi-ai 目录尚未收录、但我们已知怎么配」的 opencode-go 模型。
|
|
86
|
+
*
|
|
87
|
+
* 🔴 **为什么必须有 `reasoningEfforts`**:`dsh-llm-pi-ai` 的 `resolveModelReasoning` 是
|
|
88
|
+
* `if (efforts === void 0) return { reasoning: base?.reasoning ?? false }` ——
|
|
89
|
+
* **目录里没有的模型 `base` 为 undefined ⇒ 一律判成非推理模型**("能用但没 thinking"的唯一成因)。
|
|
90
|
+
* 已收录的兄弟模型(如 `deepseek-v4-flash`)从目录的 `base` 拿到 `reasoning`/`thinkingLevelMap`,
|
|
91
|
+
* 故本表**只需收录目录没有的那些**。
|
|
92
|
+
*/
|
|
93
|
+
export interface OpencodeExtraModel {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
/** 该模型说的 wire 协议;见 {@link OPENCODE_GO_NON_COMPLETIONS} 的安全闸 */
|
|
96
|
+
readonly api: string;
|
|
97
|
+
readonly contextWindow: number;
|
|
98
|
+
readonly maxTokens: number;
|
|
99
|
+
readonly input: readonly string[];
|
|
100
|
+
/** pi-ai 推理档 → wire 取值(除 `off` 外都必须非空;空值会被宿主 `invalid` 拒绝) */
|
|
101
|
+
readonly reasoningEfforts: Readonly<Record<string, string>>;
|
|
102
|
+
/** 该模型族的 compat(推理 wire 格式在这里;值取自目录里同族兄弟模型) */
|
|
103
|
+
readonly compat: Readonly<Record<string, unknown>>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 目录补丁表(**唯一真相源 = pi-ai 的 `dist/providers/data/opencode-go.json`**)。
|
|
107
|
+
*
|
|
108
|
+
* 录取判据:① 官方文档 <https://opencode.ai/docs/zh-cn/go/> 的端点表列了它;
|
|
109
|
+
* ② pi-ai 目录里**没有**它 ⇒ 面板不显示、手加也没 thinking。
|
|
110
|
+
*
|
|
111
|
+
* `deepseek-v4.1-flash` 的各字段**逐字取自目录里 `deepseek-v4-flash`**(同族兄弟,
|
|
112
|
+
* 同 `chat/completions` 端点 + 同 `thinkingFormat: deepseek`)——不自行发明规格。
|
|
113
|
+
*/
|
|
114
|
+
export declare const OPENCODE_EXTRA_MODELS: Readonly<Record<string, OpencodeExtraModel>>;
|
|
71
115
|
interface OpencodeAutoConfigInput {
|
|
72
116
|
/** `llm-pi-ai` 命名空间**解析后**的值(0.1.7 读面 `settings.describe()` 里按 `ns` 挑出的 `value`;undefined = 命名空间未注册) */
|
|
73
117
|
resolved: unknown;
|
|
@@ -86,6 +130,15 @@ type OpencodeAutoConfigAction = {
|
|
|
86
130
|
kind: 'skip-route';
|
|
87
131
|
route: string;
|
|
88
132
|
reason: 'headers-complete';
|
|
133
|
+
} | {
|
|
134
|
+
kind: 'add-models';
|
|
135
|
+
route: string;
|
|
136
|
+
ids: readonly string[];
|
|
137
|
+
api: string | null;
|
|
138
|
+
} | {
|
|
139
|
+
kind: 'skip-models';
|
|
140
|
+
route: string;
|
|
141
|
+
reason: 'catalog-served' | 'mixed-protocol' | 'api-conflict';
|
|
89
142
|
} | {
|
|
90
143
|
kind: 'idle';
|
|
91
144
|
reason: 'namespace-unregistered' | 'no-route';
|
|
@@ -44,28 +44,6 @@ interface RenameOnUseDeps {
|
|
|
44
44
|
/** sessionTitle 服务可用性 */
|
|
45
45
|
sessionTitleAvailable: boolean;
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
48
|
-
* 清洗 + 截断会话概括(需求② S142 用户拍板:编号日期后加 ≤20 字内容概括)。
|
|
49
|
-
* 规则(服务端统一,不信任 LLM 输入):
|
|
50
|
-
* - 去控制字符/换行/回车/制表(防标题注入/多行污染)
|
|
51
|
-
* - trim(去首尾空白)
|
|
52
|
-
* - 截断 ≤20 字符(按 Unicode 码点——中英混排统一;emoji 等代理对按码点保留)
|
|
53
|
-
* - 去 `/`(防标题被误读为路径分隔)
|
|
54
|
-
* @returns 清洗后的概括(空输入 → 空串;调用方决定是否允许空)
|
|
55
|
-
*/
|
|
56
|
-
export declare function sanitizeSessionSummary(summary: string): string;
|
|
57
|
-
/**
|
|
58
|
-
* 从激活会话派生命名标题(v1.22.9 格式修正 + 需求② 概括):
|
|
59
|
-
* F3 原始需求是 **`S###-日期`**(如 `S143-2026-08-26`)——从 `sessionId` 派生,
|
|
60
|
-
* 而非完整目录名(`2026-08-24--S142--...` 超长 + 中文,不符合用户拍板格式)。
|
|
61
|
-
* 需求②(S142 用户拍板):编号日期后加 ≤20 字内容概括 → `S###-YYYY-MM-DD-<概括>`
|
|
62
|
-
* ——概括来自显式 summary 参数(服务端截断/清洗,可靠不靠猜);编号日期仍固定派生。
|
|
63
|
-
* 无 S### 编号(issue 会话等)→ 回退目录名。
|
|
64
|
-
* @param active 激活会话信息(sessionId + dirName)
|
|
65
|
-
* @param summary 内容概括(≤20 字,服务端清洗截断;空 → 不带概括的 `S###-日期`)
|
|
66
|
-
* @returns `S143-2026-08-26-概括` / `S143-2026-08-26` / 原目录名
|
|
67
|
-
*/
|
|
68
|
-
export declare function namingTitleFor(active: ActiveSessionInfo, summary?: string): string;
|
|
69
47
|
/**
|
|
70
48
|
* use 激活宁静号会话后,把当前 dsh 会话重命名为命名标题(`S###-日期[-概括]`)。
|
|
71
49
|
* v1.27.1:**永远开启**(不再有 naming.enabled 门控)——仅 sessionTitle 服务
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* L6 结算)仅在本模块注释与内部文档出现,永不浮现在提示词文本。
|
|
16
16
|
*
|
|
17
17
|
* 结算(settlement)挂点:D6 用户拍板——若 CCC 有标准 SESSION 复盘仪式,
|
|
18
|
-
* 结算视图归 trajectory-assistant。当前 close/archive 无复盘摘要
|
|
19
|
-
*
|
|
18
|
+
* 结算视图归 trajectory-assistant。当前 close/archive 无复盘摘要 ⇒ 机制延后。
|
|
19
|
+
* 🔴 2026-09-25(S142 分批清死代码·第 1 批):原先那个 `onSettlement(cb)` **空 seam 已删**
|
|
20
|
+
* (零调用者、无触发器、空实现)——仪式真落地时按当时的信号重新设计,**不再靠空占位**。
|
|
20
21
|
*/
|
|
21
22
|
/** 可见提示词前缀用词(跨领域自然词,D8:无游戏黑话) */
|
|
22
23
|
export declare const EVENT_LABEL: {
|
|
@@ -59,10 +60,4 @@ type TrajectoryStyle = 'plain' | 'metaphor';
|
|
|
59
60
|
* "context limit/deck check" 类,不强制替换;扩展点留给未来实验)。
|
|
60
61
|
*/
|
|
61
62
|
export declare function styledToken(event: keyof typeof EVENT_LABEL, style?: TrajectoryStyle): string;
|
|
62
|
-
/**
|
|
63
|
-
* 结算 seam(OP-1/D6):未来标准 SESSION 复盘仪式接入点。当前无调用者,
|
|
64
|
-
* 仅导出契约:onSettlement(cb) 在"工作完成且被用户认可"时触发——该信号
|
|
65
|
-
* 尚无可靠自动检测(D2 用户拍板记录为未解问题),实现留待仪式落地。
|
|
66
|
-
*/
|
|
67
|
-
export declare function onSettlement(_cb: (sessionId: string) => void): void;
|
|
68
63
|
export {};
|
|
@@ -51,8 +51,6 @@ export declare const BINDINGS_REL_PATH = "AGENT_SESSIONS/.bindings.json";
|
|
|
51
51
|
* 幂等:重复调用以后者为准(HMR / 重载场景)。
|
|
52
52
|
*/
|
|
53
53
|
export declare function setBindingStore(store: BindingStore | null): void;
|
|
54
|
-
/** 当前域句柄(诊断用;`null` = 未接线/不可用) */
|
|
55
|
-
export declare function getBindingStore(): BindingStore | null;
|
|
56
54
|
/**
|
|
57
55
|
* §0L **一次性迁移**:把某个 CCC 的旧 `.bindings.json` 全量灌入宿主存储域。
|
|
58
56
|
*
|
|
@@ -72,21 +70,6 @@ export declare function migrateBindingsToDomain(root: string): {
|
|
|
72
70
|
skipped: number;
|
|
73
71
|
failed: number;
|
|
74
72
|
};
|
|
75
|
-
/**
|
|
76
|
-
* **按 CCC 惰性补迁移**(§0L 修正,S142 2026-09-19)。
|
|
77
|
-
*
|
|
78
|
-
* 🔴 为什么需要它(实测缺陷,R↓):
|
|
79
|
-
* 装载期那次迁移用的是 `process.cwd()` —— 而 dsh **服务进程的 cwd 是 `/home/yh`**,
|
|
80
|
-
* **不是 CCC 根**(实测 `acc-diag`:`进程 cwd: /home/yh | 进程 CCC: (无)`)。
|
|
81
|
-
* ⇒ 迁移读的是一个**不存在的文件**,静默迁移 0 条
|
|
82
|
-
* (症状:发布后 `~/.dsh/storages/serenity_bindings.json` **没出现**)。
|
|
83
|
-
*
|
|
84
|
-
* 现在改为:**任何一次能定位到 CCC 根的路径**都顺手补一次迁移(幂等 + 进程内去重)。
|
|
85
|
-
* 这样无论服务从哪个 cwd 启动,只要那条 CCC 有会话活动,它的绑定就会被带上。
|
|
86
|
-
*
|
|
87
|
-
* @param root CCC 根
|
|
88
|
-
*/
|
|
89
|
-
export declare function ensureBindingsMigrated(root: string): void;
|
|
90
73
|
/** 绑定文件绝对路径(由会话 cwd 上溯 .serenity 定位 CCC 根);无法定位返回 null */
|
|
91
74
|
export declare function bindingsPathFor(session: unknown): string | null;
|
|
92
75
|
/**
|
package/lib/trajectory-ops.d.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* `container_trajectory` 工具的动作集合(v1.33,S142 §32 用户裁决后的收敛结果)。
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* 为什么收敛到这 8 个(R↓;v1.33 收敛时为 **6 个** —— 其后追加 `send-now`(09-16)
|
|
18
|
+
* 与 `cro-guide`(09-20),🔴 2026-09-25 更正计数,勿再写成"6 个"):旧面是 `logbook`(11)+ `trajectory`(12)两个工具,用户裁决
|
|
18
19
|
* 「都合并成 trajectory,废除 logbook 这个词」并逐条删并 ——
|
|
19
20
|
* summary 并进 list|health/qa 淘汰(判据=旧模板,EAP 已取代;仅"空壳/长期无活动"并入 use)|
|
|
20
21
|
* close/archive 删(completed 由 SESSION.md 的 `[x]` 推导;归档走 container_fs mv)|
|
|
@@ -52,7 +53,11 @@ export declare function sessionEvents<T = unknown>(session: unknown): readonly T
|
|
|
52
53
|
export declare function sessionsRoot(root: string): string;
|
|
53
54
|
interface SessionStatus {
|
|
54
55
|
hasSessionMd: boolean;
|
|
55
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* 「完成」的判据 = SESSION.md 里**出现 `[x]`**(v1.33 起不再由 close 动作写入)。
|
|
58
|
+
* 🔴 这是**全文匹配**、两代格式通用:新格式的状态行 `- 状态: [x] …` 与旧格式的
|
|
59
|
+
* `## 状态` 段内 `- [x] 已完成` 都能被识别 ⇒ **不可改成锚定某一行的正则**(会让旧会话永远"未完成")。
|
|
60
|
+
*/
|
|
56
61
|
completed: boolean;
|
|
57
62
|
}
|
|
58
63
|
interface SessionEntry {
|
|
@@ -101,6 +106,28 @@ export interface ActiveSessionInfo {
|
|
|
101
106
|
dirName: string;
|
|
102
107
|
mdPath: string;
|
|
103
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* 概括清洗(标题用;需求② S142 用户拍板:编号日期后加 ≤20 字内容概括)。
|
|
111
|
+
* 规则(服务端统一,不信任 LLM 输入):
|
|
112
|
+
* - 去控制字符/换行/回车/制表(防标题注入/多行污染)
|
|
113
|
+
* - trim(去首尾空白)
|
|
114
|
+
* - 截断 ≤20 字符(按 Unicode 码点——中英混排统一;emoji 等代理对按码点保留)
|
|
115
|
+
* - 去 `/`(防标题被误读为路径分隔)
|
|
116
|
+
* @returns 清洗后的概括(空输入 → 空串;调用方决定是否允许空)
|
|
117
|
+
*/
|
|
118
|
+
export declare function sanitizeSessionSummary(summary: string): string;
|
|
119
|
+
/**
|
|
120
|
+
* 从激活会话派生命名标题(v1.22.9 格式修正 + 需求② 概括):
|
|
121
|
+
* F3 原始需求是 **`S###-日期`**(如 `S143-2026-08-26`)——从 `sessionId` 派生,
|
|
122
|
+
* 而非完整目录名(`2026-08-24--S142--...` 超长 + 中文,不符合用户拍板格式)。
|
|
123
|
+
* 需求②(S142 用户拍板):编号日期后加 ≤20 字内容概括 → `S###-YYYY-MM-DD-<概括>`
|
|
124
|
+
* ——概括来自显式 summary 参数(服务端截断/清洗,可靠不靠猜);编号日期仍固定派生。
|
|
125
|
+
* 无 S### 编号(issue 会话等)→ 回退目录名。
|
|
126
|
+
* @param active 激活会话信息(sessionId + dirName)
|
|
127
|
+
* @param summary 内容概括(≤20 字,服务端清洗截断;空 → 不带概括的 `S###-日期`)
|
|
128
|
+
* @returns `S143-2026-08-26-概括` / `S143-2026-08-26` / 原目录名
|
|
129
|
+
*/
|
|
130
|
+
export declare function namingTitleFor(active: ActiveSessionInfo, summary?: string): string;
|
|
104
131
|
export declare function getActiveSessionInfo(scope: string): ActiveSessionInfo | null;
|
|
105
132
|
export declare function getLastActiveSessionInfo(): ActiveSessionInfo | null;
|
|
106
133
|
export declare function setActiveSessionInfo(scope: string, info: ActiveSessionInfo): void;
|
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/** 全文注入总长上限(字符数;规格 §3「缺省 32 KB」——防上下文爆炸) */
|
|
17
17
|
export declare const TRAJECTORY_SKILLS_MAX_CHARS: number;
|
|
18
|
-
/** 缺失标记(规格 §3/§6-3 的验收锚点:任何"拿不到全文"的情况都必须带它,绝不静默) */
|
|
19
|
-
export declare const SKILL_MISSING_MARK = "[\u7F3A\u5931]";
|
|
20
18
|
/**
|
|
21
19
|
* 抽取文件顶部 YAML frontmatter 区(规格 §2.1)。
|
|
22
20
|
*
|
package/lib/wake-scheduler.d.ts
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* CRO 阶段另有**独立**总闸 `croEnabled`(只停 CRO,**不影响**本注册表投递)|
|
|
24
24
|
* 补跑窗口 2h(超窗置 missed 留痕)|同 tick 串行|唤起后维持 live(人类可介入)。
|
|
25
25
|
*/
|
|
26
|
-
import type { Context } from 'cordis';
|
|
27
26
|
import { type ClockRuntime } from './clock-runtime.js';
|
|
28
27
|
import { type WakeEntry } from './wake-registry.js';
|
|
29
28
|
/** 投递结果(写回条目的 lastResult;人读) */
|
|
@@ -54,7 +53,7 @@ interface WakeDeliveryResult {
|
|
|
54
53
|
* C6b(P2):字段由 {@link ClockRuntime} 提供(**工厂那份**,字段名与语义一个字未改);
|
|
55
54
|
* 此处只把 `lastTickLog` 收为**必需**(本钟独有,autopilot 没有它——现状稿 §2.4)。
|
|
56
55
|
*/
|
|
57
|
-
|
|
56
|
+
type WakeSchedulerRuntime = ClockRuntime & {
|
|
58
57
|
lastTickLog: string[];
|
|
59
58
|
};
|
|
60
59
|
/** 目标定位:目录名 + SESSION.md 绝对路径 */
|
|
@@ -86,7 +85,7 @@ export declare function buildWakeText(entry: WakeEntry, target: WakeTarget): str
|
|
|
86
85
|
* @param entry 唤醒条目
|
|
87
86
|
* @returns 投递结果(写回 lastResult)
|
|
88
87
|
*/
|
|
89
|
-
export declare function deliverWake(ctx:
|
|
88
|
+
export declare function deliverWake(ctx: unknown, root: string, entry: WakeEntry): Promise<WakeDeliveryResult>;
|
|
90
89
|
/**
|
|
91
90
|
* 即时消息正文(**刻意不复用** {@link buildWakeText}):唤醒文本以「**到点**(登记于…)」开头,
|
|
92
91
|
* 对即时投递那是**错的事实**。本函数显式区分"这是即时消息、不是到点唤醒"。
|
|
@@ -138,7 +137,7 @@ export declare function buildSendText(target: WakeTarget, message: string, sende
|
|
|
138
137
|
* @param sender 发起者轨迹标识
|
|
139
138
|
* @returns 投递结果(**同步**返回给调用方)
|
|
140
139
|
*/
|
|
141
|
-
export declare function sendToTrajectory(ctx:
|
|
140
|
+
export declare function sendToTrajectory(ctx: unknown, root: string, target: string, message: string, sender: string): Promise<WakeDeliveryResult>;
|
|
142
141
|
/**
|
|
143
142
|
* CRO 唤起消息正文(**刻意不复用** {@link buildWakeText} / {@link buildSendText})。
|
|
144
143
|
*
|
|
@@ -163,5 +162,5 @@ export declare function buildCroWakeText(dirName: string, mdPath: string, prompt
|
|
|
163
162
|
* 会话出现/settings 变化时热启动(与 autopilot 同款触发面,互不干扰)。
|
|
164
163
|
* @param ctx 插件上下文
|
|
165
164
|
*/
|
|
166
|
-
export declare function registerWakeScheduler(ctx:
|
|
165
|
+
export declare function registerWakeScheduler(ctx: unknown): void;
|
|
167
166
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
4
|
-
//#region src/session-cleanup.ts
|
|
5
|
-
/**
|
|
6
|
-
* session-cleanup.ts — DSH 平台旧会话自动清理(v1.29,S142 需求③)
|
|
7
|
-
*
|
|
8
|
-
* 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
|
|
9
|
-
* base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
|
|
10
|
-
* <root>/--<project-slug>--/<encoded-session-id>/session[.vN].jsonl(.zstd)
|
|
11
|
-
* **世代(v1.31.13 补)**:宿主自 DSH 0.1.5 起按世代写文件,且迁移**保留源文件**
|
|
12
|
-
* ⇒ 一个会话目录常见 v0 与 v3 两个文件并存。本模块用 `sessionLogArtifacts()` 统一枚举世代。
|
|
13
|
-
* DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
|
|
14
|
-
* 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
|
|
15
|
-
* 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
|
|
16
|
-
* 物理删后 list 不再返回;WorkspaceRegistry 下次启动重建 header index 自动消失。
|
|
17
|
-
*
|
|
18
|
-
* 方案(S142 用户拍板):lastActive 基准 + 手动触发 + 不过滤不归档直接物理删;
|
|
19
|
-
* 安全底线 = live 会话保护跳过(删正在运行的会话 = 灾难)。
|
|
20
|
-
*
|
|
21
|
-
* 本模块纯逻辑(零 ctx 依赖):传入 sessionsRoot + liveIds,可单测。
|
|
22
|
-
*/
|
|
23
|
-
var session_cleanup_exports = /* @__PURE__ */ __exportAll({
|
|
24
|
-
collectEligibleSessions: () => collectEligibleSessions,
|
|
25
|
-
cutoffDaysAgo: () => cutoffDaysAgo,
|
|
26
|
-
findSessionLog: () => findSessionLog,
|
|
27
|
-
hasSessionLogById: () => hasSessionLogById,
|
|
28
|
-
performCleanup: () => performCleanup,
|
|
29
|
-
sessionLogArtifacts: () => sessionLogArtifacts,
|
|
30
|
-
sessionLogBytesById: () => sessionLogBytesById,
|
|
31
|
-
sessionsRootDir: () => sessionsRootDir
|
|
32
|
-
});
|
|
33
|
-
/** DSH 会话 root:env DSH_HOME → ~/.dsh(config-ops globalConfigPath 同款推导) */
|
|
34
|
-
function sessionsRootDir() {
|
|
35
|
-
const dshHome = process.env.DSH_HOME ?? join(process.env.HOME ?? "", ".dsh");
|
|
36
|
-
return join(dshHome, "sessions");
|
|
37
|
-
}
|
|
38
|
-
/** 一天的毫秒数 */
|
|
39
|
-
const DAY_MS = 864e5;
|
|
40
|
-
/**
|
|
41
|
-
* 扫描 sessions root,列出可清理候选:
|
|
42
|
-
* - 递归 `<root>/<project>/<session-id>/session.jsonl(.zstd)`(任意深度下找 *session* 文件)
|
|
43
|
-
* - 过滤 live(liveIds 集合)——安全底线
|
|
44
|
-
* - 过滤 lastActive >= cutoffMs(最后活动未达阈值 → 保留)
|
|
45
|
-
* root 不存在/为空 → 返回 [](非错误)。
|
|
46
|
-
*/
|
|
47
|
-
function collectEligibleSessions(root, cutoffMs, liveIds = /* @__PURE__ */ new Set()) {
|
|
48
|
-
if (!existsSync(root)) return [];
|
|
49
|
-
const out = [];
|
|
50
|
-
for (const project of readdirSync(root, { withFileTypes: true })) {
|
|
51
|
-
if (!project.isDirectory()) continue;
|
|
52
|
-
const projectPath = join(root, project.name);
|
|
53
|
-
for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
|
|
54
|
-
if (!entry.isDirectory()) continue;
|
|
55
|
-
const dirPath = join(projectPath, entry.name);
|
|
56
|
-
const artifacts = sessionLogArtifacts(dirPath);
|
|
57
|
-
if (artifacts.length === 0) continue;
|
|
58
|
-
const log = findSessionLog(dirPath);
|
|
59
|
-
if (log === null) continue;
|
|
60
|
-
if (liveIds.has(entry.name)) continue;
|
|
61
|
-
let mtime = Number.NEGATIVE_INFINITY;
|
|
62
|
-
for (const artifact of artifacts) try {
|
|
63
|
-
mtime = Math.max(mtime, statSync(artifact.path).mtimeMs);
|
|
64
|
-
} catch {}
|
|
65
|
-
if (!Number.isFinite(mtime)) continue;
|
|
66
|
-
if (mtime >= cutoffMs) continue;
|
|
67
|
-
out.push({
|
|
68
|
-
id: entry.name,
|
|
69
|
-
project: project.name,
|
|
70
|
-
logPath: log,
|
|
71
|
-
lastActiveMs: mtime
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* 会话日志文件名识别:`session.jsonl` / `session.jsonl.zstd`(= **v0 世代**)/
|
|
79
|
-
* `session.v<N>.jsonl[.zstd]`(= 第 N 世代,DSH 0.1.5 起按世代写文件)。
|
|
80
|
-
* 出处:`@deepseek-ai/dsh-session-persistence-jsonl` 的 `parseSessionFormatLogFilename` 同款形态。
|
|
81
|
-
*/
|
|
82
|
-
const SESSION_LOG_RE = /^session(?:\.v(\d+))?\.jsonl(\.zstd)?$/;
|
|
83
|
-
/**
|
|
84
|
-
* 列出会话目录下的**全部日志世代**,按代递增排序(同代内 `.jsonl` 在前、`.zstd` 在后 = 后者更代表"当前书写格式")。
|
|
85
|
-
*
|
|
86
|
-
* 为什么需要(R↓,v1.31.13 修复):宿主自 0.1.5 起改成**按世代写文件**,且迁移**保留源文件**
|
|
87
|
-
* ⇒ 同一会话常见 `session.jsonl.zstd`(v0)与 `session.v3.jsonl.zstd`(v3)**并存**。
|
|
88
|
-
* 旧实现只认前两个名字 → 新格式会话对本模块**完全不可见**(静默 no-op:清理永远不生效、磁盘只涨)。
|
|
89
|
-
*/
|
|
90
|
-
function sessionLogArtifacts(sessionDirPath) {
|
|
91
|
-
let names;
|
|
92
|
-
try {
|
|
93
|
-
names = readdirSync(sessionDirPath);
|
|
94
|
-
} catch {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
const out = [];
|
|
98
|
-
for (const name of names) {
|
|
99
|
-
const m = SESSION_LOG_RE.exec(name);
|
|
100
|
-
if (m === null) continue;
|
|
101
|
-
out.push({
|
|
102
|
-
name,
|
|
103
|
-
generation: m[1] === void 0 ? 0 : Number(m[1]),
|
|
104
|
-
path: join(sessionDirPath, name)
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return out.sort((a, b) => a.generation - b.generation || (a.name.endsWith(".zstd") ? 1 : 0) - (b.name.endsWith(".zstd") ? 1 : 0));
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* 在会话目录下找 session 日志文件;无 → null。
|
|
111
|
-
* 多世代并存时返回**最高世代**(= 宿主 `findLog` 选中的当前世代;旧行为对单文件目录完全一致)。
|
|
112
|
-
*/
|
|
113
|
-
function findSessionLog(sessionDirPath) {
|
|
114
|
-
const artifacts = sessionLogArtifacts(sessionDirPath);
|
|
115
|
-
const latest = artifacts[artifacts.length - 1];
|
|
116
|
-
return latest === void 0 ? null : latest.path;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* 执行清理(用户拍板:直接物理删,不过滤不归档;dryRun 预览不删)。
|
|
120
|
-
* 删除 = rmSync 整个会话目录(recursive + force)。失败逐个记 errors 不中断。
|
|
121
|
-
*/
|
|
122
|
-
function performCleanup(root, cutoffMs, liveIds = /* @__PURE__ */ new Set(), opts = {}) {
|
|
123
|
-
const candidates = collectEligibleSessions(root, cutoffMs, liveIds);
|
|
124
|
-
if (opts.dryRun) return {
|
|
125
|
-
candidates,
|
|
126
|
-
result: null
|
|
127
|
-
};
|
|
128
|
-
const result = {
|
|
129
|
-
deleted: [],
|
|
130
|
-
errors: []
|
|
131
|
-
};
|
|
132
|
-
for (const c of candidates) {
|
|
133
|
-
const dir = dirOf(c.logPath);
|
|
134
|
-
try {
|
|
135
|
-
rmSync(dir, {
|
|
136
|
-
recursive: true,
|
|
137
|
-
force: true
|
|
138
|
-
});
|
|
139
|
-
result.deleted.push(c.id);
|
|
140
|
-
} catch (e) {
|
|
141
|
-
result.errors.push({
|
|
142
|
-
id: c.id,
|
|
143
|
-
reason: e.message
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
candidates,
|
|
149
|
-
result
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
/** 从日志路径取会话目录(父目录) */
|
|
153
|
-
function dirOf(logPath) {
|
|
154
|
-
return logPath.slice(0, logPath.lastIndexOf("/")) || logPath;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* 某一 dsh 会话(目录名 / 会话 id)的**当前世代日志体积**(字节)。
|
|
158
|
-
*
|
|
159
|
-
* 为什么要它(S142 §0y「旧会话累积 ⇒ dsh 内存崩溃」,所有者令):
|
|
160
|
-
* · 宿主恢复/继续一条会话必须**重放全部事件**(`scanLog` → `materializeAppendBatch`)
|
|
161
|
-
* ⇒ **代价 ∝ 日志体积**;
|
|
162
|
-
* · 实测(`_tmp/measure-session-load.mjs`,照宿主 `scanZstdFrames` 复刻):
|
|
163
|
-
* 一条 **72.2 MB**(zstd)的日志 = 35 559 个 frame / 252.6 MB 文本 / 62 680 条事件,
|
|
164
|
-
* **光是读一遍峰值 RSS +1 175 MB**(探针什么都不保留);
|
|
165
|
-
* · 而宿主的解析缓存 `COLD_LOG_MEMO_MAX_ENTRIES = 2` 是**按条数限、不按字节限**的
|
|
166
|
-
* (`dsh-session-persistence-jsonl/lib/index.js:2175`)⇒ 两条巨日志 = 上限内。
|
|
167
|
-
* 🔴 **在本函数之前,ACC 侧没有任何地方知道"绑定的这条会话日志多大"——这个信号根本不存在。**
|
|
168
|
-
* 这是"靠 agent 自觉 rebuild 不够"的机制层原因(keeper 的 K 量的是**模型上下文**,压缩后回落;
|
|
169
|
-
* 而**日志文件只增不减** ⇒ rebuild 提醒永远不会因为"日志 72 MB"触发)。
|
|
170
|
-
*
|
|
171
|
-
* 只认会话目录(含任意世代日志);返回**最高世代**的日志(= 宿主 `findLog` 选中的当前世代)。
|
|
172
|
-
* 未命中 / 不可读 → null(非错误,调用方按"未知"处理)。
|
|
173
|
-
* @param root - DSH 会话 root({@link sessionsRootDir})
|
|
174
|
-
* @param sessionId - 会话目录名(`.bindings.json` 的键,含 `session-` 前缀)或裸 id
|
|
175
|
-
* @returns 字节数 + 日志绝对路径;未命中 → null
|
|
176
|
-
*/
|
|
177
|
-
function sessionLogBytesById(root, sessionId) {
|
|
178
|
-
if (sessionId === "" || !existsSync(root)) return null;
|
|
179
|
-
const dirName = sessionId.startsWith("session-") ? sessionId : `session-${sessionId}`;
|
|
180
|
-
for (const project of readdirSync(root, { withFileTypes: true })) {
|
|
181
|
-
if (!project.isDirectory()) continue;
|
|
182
|
-
const log = findSessionLog(join(root, project.name, dirName));
|
|
183
|
-
if (log === null) continue;
|
|
184
|
-
try {
|
|
185
|
-
return {
|
|
186
|
-
bytes: statSync(log).size,
|
|
187
|
-
path: log
|
|
188
|
-
};
|
|
189
|
-
} catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* **(c) 绑定一致性兜底**的判据:某会话的日志**是否仍在磁盘上**。
|
|
197
|
-
*
|
|
198
|
-
* 🔴 **fail-closed**:sessions root 本身不存在时**一律返回 true(当作"在")**——
|
|
199
|
-
* 否则换 `DSH_HOME` / 换机器会让"全部绑定看起来都悬空",进而误清整张绑定表。
|
|
200
|
-
* 只有"root 在、而这个会话目录确实不在"才算悬空。
|
|
201
|
-
* @param sessionRoot - DSH 会话 root({@link sessionsRootDir})
|
|
202
|
-
* @param sessionId - 会话目录名(`.bindings.json` 的键)或裸 id
|
|
203
|
-
*/
|
|
204
|
-
function hasSessionLogById(sessionRoot, sessionId) {
|
|
205
|
-
if (!existsSync(sessionRoot)) return true;
|
|
206
|
-
return sessionLogBytesById(sessionRoot, sessionId) !== null;
|
|
207
|
-
}
|
|
208
|
-
/** 便捷:默认 N 天前为 cutoff(供 API/调用方) */
|
|
209
|
-
function cutoffDaysAgo(days, nowMs = Date.now()) {
|
|
210
|
-
return nowMs - days * DAY_MS;
|
|
211
|
-
}
|
|
212
|
-
//#endregion
|
|
213
|
-
export { session_cleanup_exports as n, sessionsRootDir as r, hasSessionLogById as t };
|