@shgroup/dsh-serenity-hooks 1.29.0 → 1.29.2
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/index.js +298 -48
- package/lib/rebuild.d.ts +11 -1
- package/lib/seams/context.d.ts +2 -0
- package/lib/session-bound.d.ts +80 -0
- package/lib/session-ops.d.ts +13 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.2",
|
|
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": {
|
package/lib/index.js
CHANGED
|
@@ -2919,6 +2919,43 @@ function isSessionDirName(dirName) {
|
|
|
2919
2919
|
return /^\d{4}-\d{2}-\d{2}--/.test(dirName) && dirName.length > 11;
|
|
2920
2920
|
}
|
|
2921
2921
|
/**
|
|
2922
|
+
* 从 dsh 会话标题解析 SESSION 目录(编码无关 best-match,U3/U4)——
|
|
2923
|
+
* 标题不假设 S### 前缀(CCC 可自定义编码:apaas-xxx / P### / 完整目录名等)。
|
|
2924
|
+
* 匹配优先级(全部对 AGENT_SESSIONS 现有目录 best-match,不猜):
|
|
2925
|
+
* ① 标题即完整目录名(含日期前缀 `YYYY-MM-DD--`)→ 精确命中
|
|
2926
|
+
* ② 标题含 `--<code>--` 段(如完整目录名被截断为 `<code>-日期-概括` 前段)→
|
|
2927
|
+
* 按 code 段匹配:取标题首 token(`-` 前),与各目录 `--<code>--`/`--<code>` 尾段比对
|
|
2928
|
+
* ③ 唯一模糊子串匹配(多个则返回 null 防误猜)
|
|
2929
|
+
* @param title dsh 会话标题(如 `S142-2026-08-24-概括` / `apaas-26116-…` / 完整目录名)
|
|
2930
|
+
* @param sessionsDir AGENT_SESSIONS 绝对路径
|
|
2931
|
+
* @returns 命中目录的绝对路径(SESSION.md);无/歧义返回 null
|
|
2932
|
+
*/
|
|
2933
|
+
function resolveSessionByTitle(title, sessionsDir) {
|
|
2934
|
+
const t = (title ?? "").trim();
|
|
2935
|
+
if (!t) return null;
|
|
2936
|
+
const all = readAllSessions(sessionsDir);
|
|
2937
|
+
if (all.length === 0) return null;
|
|
2938
|
+
if (isSessionDirName(t)) {
|
|
2939
|
+
const exact = all.find((s) => s.dirName === t);
|
|
2940
|
+
if (exact) return join(exact.path, SESSION_MD);
|
|
2941
|
+
}
|
|
2942
|
+
const codeToken = t.split("-")[0]?.trim() ?? "";
|
|
2943
|
+
if (codeToken) {
|
|
2944
|
+
const byCode = all.filter((s) => {
|
|
2945
|
+
const m = s.dirName.match(/--([^--]+)--/);
|
|
2946
|
+
const code = m ? m[1] : null;
|
|
2947
|
+
const tailMatch = s.dirName.match(/--([^--]+)$/);
|
|
2948
|
+
const tailCode = tailMatch && !s.dirName.includes("--", s.dirName.lastIndexOf("--") + 3) ? tailMatch[1] : null;
|
|
2949
|
+
return code === codeToken || tailCode === codeToken || s.dirName === codeToken || s.dirName.includes(`--${codeToken}`);
|
|
2950
|
+
});
|
|
2951
|
+
if (byCode.length === 1) return join(byCode[0].path, SESSION_MD);
|
|
2952
|
+
if (byCode.length > 1) return null;
|
|
2953
|
+
}
|
|
2954
|
+
const fuzzy = all.filter((s) => s.dirName.toLowerCase().includes(t.toLowerCase()));
|
|
2955
|
+
if (fuzzy.length === 1) return join(fuzzy[0].path, SESSION_MD);
|
|
2956
|
+
return null;
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2922
2959
|
* 约定回退(v1.24.11):AGENT_SESSIONS 下最新修改的**未完成**会话的 SESSION.md。
|
|
2923
2960
|
* readAllSessions 已按「未完成优先 + mtime 降序」排序 → 首个未完成且含 SESSION.md 即最新活动。
|
|
2924
2961
|
* 只作最后手段(内存/events/锚点全缺时),保证重建锚点至少指向一个真实存在的轨迹。
|
|
@@ -3179,6 +3216,56 @@ function qaCheck(root, key) {
|
|
|
3179
3216
|
return lines.join("\n");
|
|
3180
3217
|
}
|
|
3181
3218
|
//#endregion
|
|
3219
|
+
//#region src/session-bound.ts
|
|
3220
|
+
/** 事件 type 常量(声明 + 读取共用) */
|
|
3221
|
+
const SESSION_BOUND_EVENT = "serenity/bound";
|
|
3222
|
+
/** 事件形状归一(运行时数据经 session.append 深冻结,此处仅类型收窄) */
|
|
3223
|
+
function asBoundEvent(e) {
|
|
3224
|
+
const ev = e;
|
|
3225
|
+
if (!ev || ev.type !== "serenity/bound") return null;
|
|
3226
|
+
const d = ev.data;
|
|
3227
|
+
if (!d || typeof d.dirName !== "string" || typeof d.mdPath !== "string") return null;
|
|
3228
|
+
return d;
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* 读取会话日志中**最后一条** `serenity/bound`(权威绑定,latest-wins)。
|
|
3232
|
+
* 尾到头扫描(时间序最新在后);无 bound 事件返回 null。
|
|
3233
|
+
* 纯逻辑零 DSH 依赖(经 sessionEvents helper 读 snapshotEvents/events 兜底)。
|
|
3234
|
+
*/
|
|
3235
|
+
function readLastBound(session) {
|
|
3236
|
+
const events = sessionEvents(session);
|
|
3237
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
3238
|
+
const b = asBoundEvent(events[i]);
|
|
3239
|
+
if (b) return b;
|
|
3240
|
+
}
|
|
3241
|
+
return null;
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* append 一条 `serenity/bound` 绑定事件(log-only,无 surfaceOp——元数据不进模型可见面)。
|
|
3245
|
+
* @param session 目标 dsh 会话(真实 Session;append 泛型经内部断言兼容——类型由
|
|
3246
|
+
* `serenity/bound` 声明面保证,append 接受已声明事件)
|
|
3247
|
+
* @param action 绑定动作
|
|
3248
|
+
* @param rec 绑定记录(dirName + mdPath 必填;sessionId 可选展示码)
|
|
3249
|
+
* @returns 是否成功(append 抛错/会话不可用时 false——绑定失败不阻断主流程)
|
|
3250
|
+
*/
|
|
3251
|
+
function appendBound(session, action, rec) {
|
|
3252
|
+
if (!session || typeof session.append !== "function") return false;
|
|
3253
|
+
try {
|
|
3254
|
+
const append = session.append;
|
|
3255
|
+
append(SESSION_BOUND_EVENT, {
|
|
3256
|
+
dirName: rec.dirName,
|
|
3257
|
+
mdPath: rec.mdPath,
|
|
3258
|
+
...rec.sessionId ? { sessionId: rec.sessionId } : {},
|
|
3259
|
+
action,
|
|
3260
|
+
at: Date.now(),
|
|
3261
|
+
...rec.note ? { note: rec.note } : {}
|
|
3262
|
+
});
|
|
3263
|
+
return true;
|
|
3264
|
+
} catch {
|
|
3265
|
+
return false;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
//#endregion
|
|
3182
3269
|
//#region src/tools/session.ts
|
|
3183
3270
|
/**
|
|
3184
3271
|
* session.ts — session 真实 DSH 工具定义(defineTool)
|
|
@@ -3203,6 +3290,21 @@ function agentCwd$5(exec) {
|
|
|
3203
3290
|
function agentScope$2(exec) {
|
|
3204
3291
|
return exec.agent?.session?.id ?? "default";
|
|
3205
3292
|
}
|
|
3293
|
+
/** 当前 dsh 会话对象(append bound 用);无 → null */
|
|
3294
|
+
function agentDshSession(exec) {
|
|
3295
|
+
const s = exec.agent?.session;
|
|
3296
|
+
return s && typeof s.append === "function" ? { append: s.append } : null;
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* 当前绑定的权威 dirName(优先持久化 bound——防内存被误 use 污染;无 bound 回退内存 active)。
|
|
3300
|
+
* 供 G1 守卫对比目标会话是否切换。
|
|
3301
|
+
*/
|
|
3302
|
+
function currentBoundDirName(exec, scope) {
|
|
3303
|
+
const dsh = agentDshSession(exec);
|
|
3304
|
+
const bound = dsh ? readLastBound(dsh) : null;
|
|
3305
|
+
if (bound) return bound.dirName;
|
|
3306
|
+
return getActiveSessionInfo(scope)?.dirName ?? null;
|
|
3307
|
+
}
|
|
3206
3308
|
/**
|
|
3207
3309
|
* 清洗 + 截断会话概括(需求② S142 用户拍板:编号日期后加 ≤20 字内容概括)。
|
|
3208
3310
|
* 规则(服务端统一,不信任 LLM 输入):
|
|
@@ -3275,18 +3377,6 @@ function renameDshSessionOnUse(deps, session, titles, active, summary) {
|
|
|
3275
3377
|
}
|
|
3276
3378
|
}
|
|
3277
3379
|
/**
|
|
3278
|
-
* 从 create 结果构造命名用 ActiveSessionInfo(v1.25.11,S142 用户:create 也要命名):
|
|
3279
|
-
* createSession 返回的 result 已含 sessionId(S###/issue)与 dirName——无需等待 use 激活,
|
|
3280
|
-
* 直接构造(mdPath = sessionPath/SESSION.md,与 use 时一致)即可驱动 renameDshSessionOnUse。
|
|
3281
|
-
*/
|
|
3282
|
-
function activeInfoFromCreate(result) {
|
|
3283
|
-
return {
|
|
3284
|
-
sessionId: result.sessionId,
|
|
3285
|
-
dirName: result.dirName,
|
|
3286
|
-
mdPath: join(result.sessionPath, "SESSION.md")
|
|
3287
|
-
};
|
|
3288
|
-
}
|
|
3289
|
-
/**
|
|
3290
3380
|
* 把当前 dsh 会话重命名为指定 SESSION 的命名标题(use/create 共用;v1.25.11)。
|
|
3291
3381
|
* 需求②:summary 参数(≤20 字概括)透传——标题带概括(编号日期固定派生)。
|
|
3292
3382
|
* 门控/失败可见性与 renameDshSessionOnUse 一致(不静默:成功 log / 失败 warn)。
|
|
@@ -3440,6 +3530,10 @@ function createSessionTool(ctx) {
|
|
|
3440
3530
|
type: "boolean",
|
|
3441
3531
|
description: "close must be true (prevents accidental close)"
|
|
3442
3532
|
},
|
|
3533
|
+
force: {
|
|
3534
|
+
type: "boolean",
|
|
3535
|
+
description: "use: allow switching away from the currently-bound session (binding guard override)"
|
|
3536
|
+
},
|
|
3443
3537
|
dryRun: {
|
|
3444
3538
|
type: "boolean",
|
|
3445
3539
|
description: "create/archive preview mode (no actual changes)"
|
|
@@ -3474,7 +3568,12 @@ function createSessionTool(ctx) {
|
|
|
3474
3568
|
dryRun: isDryRun
|
|
3475
3569
|
});
|
|
3476
3570
|
let message = result.message;
|
|
3477
|
-
if (!isDryRun)
|
|
3571
|
+
if (!isDryRun) appendBound(agentDshSession(exec), "create", {
|
|
3572
|
+
dirName: result.dirName,
|
|
3573
|
+
mdPath: join(result.sessionPath, "SESSION.md"),
|
|
3574
|
+
sessionId: result.sessionId,
|
|
3575
|
+
note: "created (binding unchanged until explicit use)"
|
|
3576
|
+
});
|
|
3478
3577
|
if (!isDryRun && cccHooks.includes("create-transform")) try {
|
|
3479
3578
|
const hookResult = await runMsmAsync(root, {
|
|
3480
3579
|
action: "exec",
|
|
@@ -3492,14 +3591,47 @@ function createSessionTool(ctx) {
|
|
|
3492
3591
|
if (!args.name) throw new Error("use requires name (S### or directory name)");
|
|
3493
3592
|
if (!args.summary || args.summary.trim() === "") throw new Error("use requires --summary <content summary ≤20 chars> (appended to the dsh session title as S###-YYYY-MM-DD-<summary>; id and date stay server-derived)");
|
|
3494
3593
|
const scope = agentScope$2(exec);
|
|
3594
|
+
const targetEntry = findSession(join(root, "AGENT_SESSIONS"), args.name);
|
|
3595
|
+
if (!targetEntry) throw new Error(`Session not found: "${args.name}". Use "list" to see available sessions.`);
|
|
3596
|
+
const targetDirName = targetEntry.dirName;
|
|
3597
|
+
const currentDir = currentBoundDirName(exec, scope);
|
|
3598
|
+
const switching = currentDir !== null && targetDirName !== currentDir;
|
|
3599
|
+
const force = args.force === true;
|
|
3600
|
+
if (switching && !force) throw new Error(`Session is bound to ${currentDir}. Switching to ${targetDirName} would orphan the current trajectory. Re-run with --force to switch (or close the current session first).`);
|
|
3495
3601
|
const active = useSession(root, args.name, scope);
|
|
3496
3602
|
const info = getActiveSessionInfo(scope);
|
|
3497
3603
|
if (info) renameDshSessionForActive(ctx, exec, info, args.summary);
|
|
3604
|
+
const dsh = agentDshSession(exec);
|
|
3605
|
+
if (dsh && info) {
|
|
3606
|
+
const prevBound = readLastBound(dsh);
|
|
3607
|
+
appendBound(dsh, switching && prevBound ? "switch" : "activate", {
|
|
3608
|
+
dirName: info.dirName,
|
|
3609
|
+
mdPath: info.mdPath,
|
|
3610
|
+
sessionId: info.sessionId,
|
|
3611
|
+
...switching ? { note: "forced switch" } : {}
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3498
3614
|
return active;
|
|
3499
3615
|
}
|
|
3500
|
-
case "close":
|
|
3501
|
-
|
|
3502
|
-
|
|
3616
|
+
case "close": {
|
|
3617
|
+
const scope = agentScope$2(exec);
|
|
3618
|
+
const requested = args.name?.trim() ?? "";
|
|
3619
|
+
const boundDir = currentBoundDirName(exec, scope);
|
|
3620
|
+
if (boundDir && requested) {
|
|
3621
|
+
if ((findSession(join(root, "AGENT_SESSIONS"), requested)?.dirName ?? requested) !== boundDir) throw new Error(`Session is bound to ${boundDir}. close targets the bound session — pass the bound session (or omit name) to close it; closing ${requested} while bound to another session is not allowed.`);
|
|
3622
|
+
}
|
|
3623
|
+
if (!boundDir && !requested) throw new Error("close requires name (no active binding to close) — pass the session to close.");
|
|
3624
|
+
const closed = closeSession(root, boundDir ? boundDir : requested, args.confirm ?? false, scope);
|
|
3625
|
+
const dsh = agentDshSession(exec);
|
|
3626
|
+
const bound = dsh ? readLastBound(dsh) : null;
|
|
3627
|
+
if (bound) appendBound(dsh, "release", {
|
|
3628
|
+
dirName: bound.dirName,
|
|
3629
|
+
mdPath: bound.mdPath,
|
|
3630
|
+
sessionId: bound.sessionId,
|
|
3631
|
+
note: "session closed"
|
|
3632
|
+
});
|
|
3633
|
+
return closed;
|
|
3634
|
+
}
|
|
3503
3635
|
case "archive": return archiveSessions(root, {
|
|
3504
3636
|
name: args.name,
|
|
3505
3637
|
dryRun: args.dryRun ?? false
|
|
@@ -3883,15 +4015,19 @@ function stripAckSuffix(text) {
|
|
|
3883
4015
|
/**
|
|
3884
4016
|
* 构建重建锚点消息(v1.22.4 定稿语义 + v1.22.5 保留 first-anchor 正文):
|
|
3885
4017
|
* 「[TRAJECTORY-REBUILD] + first-anchor 协议正文(去 acknowledge 尾句)+ 继续 {SESSION 名} 的工作」。
|
|
4018
|
+
* v1.29.2(R1):可选 task focus 段——简短任务焦点文字传入重建后会话(不含历史,
|
|
4019
|
+
* 历史完整在 SESSION.md;让重建后的自己第一时间理解本轮焦点,降低冷启动认知成本)。
|
|
3886
4020
|
* @param root - CCC 根
|
|
3887
4021
|
* @param sessionName - 当前 use 的宁静号 SESSION 名(如 S142);无激活则通用指令
|
|
3888
4022
|
* @param activeMdPath - 持久轨迹 SESSION.md 绝对路径(保持原位)
|
|
3889
4023
|
* @param anchorMessages - first-anchor 协议消息序列(缺省 DEFAULT_ANCHOR_MESSAGES;可注入测试)
|
|
4024
|
+
* @param focus - 可选任务焦点(单行化 + ≤200 字消毒;无则不输出 focus 段——向后兼容)
|
|
3890
4025
|
*/
|
|
3891
|
-
function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DEFAULT_ANCHOR_MESSAGES) {
|
|
4026
|
+
function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DEFAULT_ANCHOR_MESSAGES, focus) {
|
|
3892
4027
|
const rel = activeMdPath.startsWith(root) ? activeMdPath.slice(root.length + 1) : activeMdPath;
|
|
3893
4028
|
const sessionDir = basename(dirname(activeMdPath));
|
|
3894
4029
|
const sessionLine = sessionDir !== "AGENT_SESSIONS" ? `- Serenity session: ${sessionName !== "" ? `${sessionName} (${sessionDir})` : sessionDir}` : null;
|
|
4030
|
+
const focusLine = sanitizeFocusLine(focus);
|
|
3895
4031
|
return [
|
|
3896
4032
|
`${eventToken("rebuild")} The conversation has been cleared and rebuilt (Ship of Theseus: the carrier is replaced, the trajectory continues).`,
|
|
3897
4033
|
"",
|
|
@@ -3900,9 +4036,22 @@ function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DE
|
|
|
3900
4036
|
...sessionLine ? [sessionLine] : [],
|
|
3901
4037
|
`- Persistent trajectory — SESSION.md path: ${rel}`,
|
|
3902
4038
|
` (the trajectory's persistent body — stays in place through rebuilds)`,
|
|
3903
|
-
`- Read that SESSION.md first (goal/decisions/progress/unresolved), then continue from the last checkpoint
|
|
4039
|
+
`- Read that SESSION.md first (goal/decisions/progress/unresolved), then continue from the last checkpoint.`,
|
|
4040
|
+
...focusLine ? [`- Task focus: ${focusLine}`] : []
|
|
3904
4041
|
].join("\n");
|
|
3905
4042
|
}
|
|
4043
|
+
/**
|
|
4044
|
+
* 任务焦点行消毒(纯函数可单测):单行化(换行/回车 → 空格)+ 控制字符清除 +
|
|
4045
|
+
* ≤200 字截断。空/纯空白 → null(不输出 focus 段)。防 LLM 传多行注入伪造锚点结构。
|
|
4046
|
+
*/
|
|
4047
|
+
function sanitizeFocusLine(focus) {
|
|
4048
|
+
if (!focus) return null;
|
|
4049
|
+
const single = focus.replace(/[\r\n\t]+/g, " ").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").trim();
|
|
4050
|
+
if (single === "") return null;
|
|
4051
|
+
return single.length <= FOCUS_MAX_CHARS ? single : single.slice(0, FOCUS_MAX_CHARS);
|
|
4052
|
+
}
|
|
4053
|
+
/** 任务焦点最大长度(字/码点;200 足够一句话任务焦点——非历史) */
|
|
4054
|
+
const FOCUS_MAX_CHARS = 200;
|
|
3906
4055
|
/** 从会话 surface 首条 user 消息(重建锚点)解析 SESSION.md 路径(events 异常/缺失时的兜底) */
|
|
3907
4056
|
function parseAnchorMdPath(session) {
|
|
3908
4057
|
try {
|
|
@@ -3988,11 +4137,20 @@ async function queueRebuild(ctx, opts) {
|
|
|
3988
4137
|
if (!session) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
|
|
3989
4138
|
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
3990
4139
|
if (!mdPath) throw new Error("Unable to determine the active SESSION.md — no session context found in this conversation. Run \"session use <S###> --summary <内容概括 ≤20 字>\" first to activate the trajectory to resume, then retry session_rebuild.");
|
|
3991
|
-
const
|
|
4140
|
+
const sessionName = getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath);
|
|
4141
|
+
const focus = note && note.trim() !== "" ? note : void 0;
|
|
4142
|
+
const anchor = buildRebuildAnchor(root, sessionName, mdPath, DEFAULT_ANCHOR_MESSAGES, focus);
|
|
4143
|
+
appendBound(session, "rebuild", {
|
|
4144
|
+
dirName: basename(dirname(mdPath)),
|
|
4145
|
+
mdPath,
|
|
4146
|
+
sessionId: sessionName.startsWith("S") ? sessionName : void 0,
|
|
4147
|
+
note: "rebuild queued"
|
|
4148
|
+
});
|
|
3992
4149
|
pendingRebuilds.set(dshSessionId, {
|
|
3993
4150
|
anchor,
|
|
3994
4151
|
summary,
|
|
3995
4152
|
mdPath,
|
|
4153
|
+
focus,
|
|
3996
4154
|
queuedAt: Date.now()
|
|
3997
4155
|
});
|
|
3998
4156
|
writeRebuildDiag(root, {
|
|
@@ -4184,7 +4342,7 @@ function createRebuildTool(ctx) {
|
|
|
4184
4342
|
parameters: {
|
|
4185
4343
|
note: {
|
|
4186
4344
|
type: "string",
|
|
4187
|
-
description: "Optional:
|
|
4345
|
+
description: "Optional: task focus ≤200 chars for the rebuilt self — what to work on next (short, no history; SESSION.md holds the full history). Injected as \"- Task focus: …\" into the rebuild anchor."
|
|
4188
4346
|
},
|
|
4189
4347
|
summary: {
|
|
4190
4348
|
type: "string",
|
|
@@ -4812,25 +4970,60 @@ function registerAutopilot(ctx) {
|
|
|
4812
4970
|
ctx.on("serenity/settings-changed", () => startTimer());
|
|
4813
4971
|
} catch {}
|
|
4814
4972
|
}
|
|
4815
|
-
/**
|
|
4973
|
+
/**
|
|
4974
|
+
* 目标 agent:从 SESSION.md 反向定位 dsh 会话(cwd 归属校验 + **bound 精确匹配优先**,
|
|
4975
|
+
* 标题 F3 命名回退);不可得 → null。
|
|
4976
|
+
*
|
|
4977
|
+
* v1.29.2(R2,用户"autopilot 会话绑定应当更稳固——唤起的时候能唤起最新的、
|
|
4978
|
+
* 绑定 autopilot SESSION 的会话"):v1.29.1 `serenity/bound` 事件是权威绑定
|
|
4979
|
+
* (session use 激活时 append,dirName = 完整 AGENT_SESSIONS 目录名,编码无关 U4)。
|
|
4980
|
+
* 旧实现只按标题猜(=== sid / startsWith(sid-))——若绑定该 SESSION 的 dsh 会话
|
|
4981
|
+
* 标题不是 S###- 前缀(LLM 改过/重建后 rename 异常),绑定明明在却唤起失败。
|
|
4982
|
+
* 加强:**bound.dirName 精确匹配优先**(权威证据),标题匹配降级为回退(存量
|
|
4983
|
+
* 无 bound 的 live 会话兼容);多候选(罕见:同 SESSION 绑定多个 live 会话)取
|
|
4984
|
+
* **绑定最新**(bound.at 最大——最近 use 过 = 最可能当前在用)。
|
|
4985
|
+
*/
|
|
4816
4986
|
function resolveTargetAgent(ctx, mdPath) {
|
|
4817
|
-
const
|
|
4987
|
+
const dirName = basename(dirname(mdPath));
|
|
4988
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
4818
4989
|
const sid = idMatch ? `S${idMatch[1]}` : null;
|
|
4819
4990
|
const targetRoot = findSerenityRoot(mdPath);
|
|
4991
|
+
const sessions = ctx.sessions;
|
|
4992
|
+
const agents = ctx.agents;
|
|
4993
|
+
const candidates = [];
|
|
4820
4994
|
try {
|
|
4821
|
-
const sessions = ctx.sessions;
|
|
4822
4995
|
for (const s of sessions?.list?.() ?? []) {
|
|
4823
|
-
const
|
|
4996
|
+
const sess = s;
|
|
4997
|
+
const cwd = sess?.header?.cwd ?? "";
|
|
4824
4998
|
if (targetRoot) {
|
|
4825
4999
|
if (findSerenityRoot(cwd) !== targetRoot) continue;
|
|
4826
5000
|
} else if (cwd !== "" && !mdPath.startsWith(cwd.endsWith("/") ? cwd : cwd + "/")) continue;
|
|
4827
|
-
const
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
5001
|
+
const bound = readLastBound(sess);
|
|
5002
|
+
const title = readSessionTitle(sess);
|
|
5003
|
+
const boundMatched = bound !== null && bound.dirName === dirName;
|
|
5004
|
+
const titleMatched = sid !== null && title !== null && (title === sid || title.startsWith(`${sid}-`));
|
|
5005
|
+
if (boundMatched) candidates.push({
|
|
5006
|
+
session: sess,
|
|
5007
|
+
boundAt: bound?.at ?? null,
|
|
5008
|
+
titleMatched
|
|
5009
|
+
});
|
|
5010
|
+
else if (titleMatched) candidates.push({
|
|
5011
|
+
session: sess,
|
|
5012
|
+
boundAt: null,
|
|
5013
|
+
titleMatched
|
|
5014
|
+
});
|
|
4832
5015
|
}
|
|
4833
5016
|
} catch {}
|
|
5017
|
+
if (candidates.length === 0) return null;
|
|
5018
|
+
candidates.sort((a, b) => {
|
|
5019
|
+
const aBound = a.boundAt !== null;
|
|
5020
|
+
if (aBound !== (b.boundAt !== null)) return aBound ? -1 : 1;
|
|
5021
|
+
return (b.boundAt ?? 0) - (a.boundAt ?? 0);
|
|
5022
|
+
});
|
|
5023
|
+
for (const c of candidates) {
|
|
5024
|
+
const agent = agents?.get?.(c.session.id ?? "");
|
|
5025
|
+
if (agent) return agent;
|
|
5026
|
+
}
|
|
4834
5027
|
return null;
|
|
4835
5028
|
}
|
|
4836
5029
|
/**
|
|
@@ -4850,31 +5043,38 @@ function readSessionTitle(session) {
|
|
|
4850
5043
|
}
|
|
4851
5044
|
/**
|
|
4852
5045
|
* 诊断:目标会话 agent 为何不可得(供 performAutopilotWake 失败信息——
|
|
4853
|
-
* 区分"live
|
|
5046
|
+
* 区分"无 live 会话" / "标题不匹配" / "bound 或标题命中但 agent 未加载")。
|
|
5047
|
+
* v1.29.2(R2):bound 命中但 agent 未加载也归入「已匹配未加载」(原只查标题)。
|
|
5048
|
+
* 返回诊断文本(无则 null)。
|
|
4854
5049
|
*/
|
|
4855
5050
|
function diagnoseTargetUnavailable(ctx, mdPath) {
|
|
4856
|
-
const
|
|
5051
|
+
const dirName = basename(dirname(mdPath));
|
|
5052
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
4857
5053
|
const sid = idMatch ? `S${idMatch[1]}` : null;
|
|
4858
5054
|
const targetRoot = findSerenityRoot(mdPath);
|
|
4859
5055
|
const sameCccTitles = [];
|
|
4860
|
-
const agentMissing = {
|
|
5056
|
+
const agentMissing = { matched: false };
|
|
4861
5057
|
try {
|
|
4862
5058
|
const sessions = ctx.sessions;
|
|
4863
5059
|
for (const s of sessions?.list?.() ?? []) {
|
|
4864
|
-
const
|
|
5060
|
+
const sess = s;
|
|
5061
|
+
const cwd = sess?.header?.cwd ?? "";
|
|
4865
5062
|
if (targetRoot) {
|
|
4866
5063
|
if (findSerenityRoot(cwd) !== targetRoot) continue;
|
|
4867
5064
|
} else if (cwd !== "" && !mdPath.startsWith(cwd.endsWith("/") ? cwd : cwd + "/")) continue;
|
|
4868
|
-
const
|
|
5065
|
+
const bound = readLastBound(sess);
|
|
5066
|
+
const title = readSessionTitle(sess);
|
|
4869
5067
|
if (title) sameCccTitles.push(title);
|
|
4870
|
-
|
|
4871
|
-
|
|
5068
|
+
const boundMatched = bound !== null && bound.dirName === dirName;
|
|
5069
|
+
const titleMatched = sid !== null && title !== null && (title === sid || title.startsWith(`${sid}-`));
|
|
5070
|
+
if (boundMatched || titleMatched) {
|
|
5071
|
+
if (!ctx.agents?.get?.(sess.id ?? "")) agentMissing.matched = true;
|
|
4872
5072
|
}
|
|
4873
5073
|
}
|
|
4874
5074
|
} catch {}
|
|
4875
|
-
if (agentMissing.
|
|
4876
|
-
if (sameCccTitles.length > 0) return `目标 CCC 内 live 会话标题: [${sameCccTitles.join(", ")}]——均不匹配 ${sid}(目标会话未在 WebUI
|
|
4877
|
-
return `目标 CCC 内无 live 会话(先在 WebUI 打开 ${sid} 会话后重试)`;
|
|
5075
|
+
if (agentMissing.matched) return `会话已绑定/匹配 ${sid ?? dirName} 但 agent 未加载(会话可能刚创建/正在恢复——稍后重试)`;
|
|
5076
|
+
if (sameCccTitles.length > 0) return `目标 CCC 内 live 会话标题: [${sameCccTitles.join(", ")}]——均不匹配 ${sid ?? dirName}(目标会话未在 WebUI 打开,或绑定/命名未生效)`;
|
|
5077
|
+
return `目标 CCC 内无 live 会话(先在 WebUI 打开 ${sid ?? dirName} 会话后重试)`;
|
|
4878
5078
|
}
|
|
4879
5079
|
/** Autopilot 绑定的回退 CCC 根:进程 cwd 上溯 .serenity 优先,回退任一 live 会话 root */
|
|
4880
5080
|
function resolveAutopilotRoot(ctx) {
|
|
@@ -6281,6 +6481,17 @@ function registerEntrySkillSection(agent, root) {
|
|
|
6281
6481
|
//#endregion
|
|
6282
6482
|
//#region src/seams/context.ts
|
|
6283
6483
|
const DEFAULT_ENTRY_SKILL_MAX_CHARS = 3e4;
|
|
6484
|
+
/** 从 dsh 会话日志读标题(latest-wins `session/title` 事件;无返回 null)——供标题 reconcile(U3) */
|
|
6485
|
+
function readDshSessionTitle(session) {
|
|
6486
|
+
try {
|
|
6487
|
+
const events = sessionEvents(session);
|
|
6488
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
6489
|
+
const e = events[i];
|
|
6490
|
+
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
6491
|
+
}
|
|
6492
|
+
} catch {}
|
|
6493
|
+
return null;
|
|
6494
|
+
}
|
|
6284
6495
|
function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entrySkillMaxChars = DEFAULT_ENTRY_SKILL_MAX_CHARS) {
|
|
6285
6496
|
loadSerenityConfig(root, configPaths);
|
|
6286
6497
|
const defaultModel = readHandymanConfig(root, configPaths)?.defaultModel;
|
|
@@ -6368,17 +6579,56 @@ function registerContext(ctx, opts = {}) {
|
|
|
6368
6579
|
const scope = agentScope(agent);
|
|
6369
6580
|
if (shouldRestoreActive(agent) && getActiveSessionInfo(scope) === null) try {
|
|
6370
6581
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
6371
|
-
const
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6582
|
+
const dshSession = agent.session;
|
|
6583
|
+
let restored = null;
|
|
6584
|
+
const bound = readLastBound(dshSession);
|
|
6585
|
+
if (bound) {
|
|
6586
|
+
const abs = bound.mdPath.startsWith(root) ? bound.mdPath : resolve(root, bound.mdPath);
|
|
6587
|
+
if (existsSync(abs)) restored = {
|
|
6588
|
+
dirName: bound.dirName,
|
|
6589
|
+
mdPath: abs,
|
|
6590
|
+
sessionId: bound.sessionId
|
|
6591
|
+
};
|
|
6592
|
+
}
|
|
6593
|
+
if (!restored) {
|
|
6594
|
+
const info = parseSessionContextFromEvents(sessionEvents(dshSession));
|
|
6595
|
+
if (info) {
|
|
6596
|
+
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
6597
|
+
if (existsSync(abs)) restored = {
|
|
6598
|
+
dirName: info.dirName,
|
|
6599
|
+
mdPath: abs,
|
|
6600
|
+
sessionId: info.sessionId
|
|
6601
|
+
};
|
|
6380
6602
|
}
|
|
6381
6603
|
}
|
|
6604
|
+
if (!restored) {
|
|
6605
|
+
const title = readDshSessionTitle(dshSession);
|
|
6606
|
+
if (title) {
|
|
6607
|
+
const md = resolveSessionByTitle(title, sessionsRoot(root));
|
|
6608
|
+
if (md && existsSync(md)) {
|
|
6609
|
+
const dirName = basename(dirname(md));
|
|
6610
|
+
restored = {
|
|
6611
|
+
dirName,
|
|
6612
|
+
mdPath: md,
|
|
6613
|
+
sessionId: void 0
|
|
6614
|
+
};
|
|
6615
|
+
appendBound(dshSession, "reconcile", {
|
|
6616
|
+
dirName,
|
|
6617
|
+
mdPath: md,
|
|
6618
|
+
note: "auto from title"
|
|
6619
|
+
});
|
|
6620
|
+
console.log(`[serenity-hooks] ↻ 标题兼容持久化绑定: ${title} → ${dirName}`);
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
if (restored) {
|
|
6625
|
+
const derived = restored.sessionId ?? (restored.dirName.match(/--([^--]+)--/) ? restored.dirName.match(/--([^--]+)--/)[1] : void 0) ?? restored.dirName;
|
|
6626
|
+
setActiveSessionInfo(scope, {
|
|
6627
|
+
...restored,
|
|
6628
|
+
sessionId: derived
|
|
6629
|
+
});
|
|
6630
|
+
console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${restored.dirName}`);
|
|
6631
|
+
}
|
|
6382
6632
|
}
|
|
6383
6633
|
} catch {}
|
|
6384
6634
|
if (injected.has(key)) return;
|
package/lib/rebuild.d.ts
CHANGED
|
@@ -45,12 +45,20 @@ export declare function stripAckSuffix(text: string): string;
|
|
|
45
45
|
/**
|
|
46
46
|
* 构建重建锚点消息(v1.22.4 定稿语义 + v1.22.5 保留 first-anchor 正文):
|
|
47
47
|
* 「[TRAJECTORY-REBUILD] + first-anchor 协议正文(去 acknowledge 尾句)+ 继续 {SESSION 名} 的工作」。
|
|
48
|
+
* v1.29.2(R1):可选 task focus 段——简短任务焦点文字传入重建后会话(不含历史,
|
|
49
|
+
* 历史完整在 SESSION.md;让重建后的自己第一时间理解本轮焦点,降低冷启动认知成本)。
|
|
48
50
|
* @param root - CCC 根
|
|
49
51
|
* @param sessionName - 当前 use 的宁静号 SESSION 名(如 S142);无激活则通用指令
|
|
50
52
|
* @param activeMdPath - 持久轨迹 SESSION.md 绝对路径(保持原位)
|
|
51
53
|
* @param anchorMessages - first-anchor 协议消息序列(缺省 DEFAULT_ANCHOR_MESSAGES;可注入测试)
|
|
54
|
+
* @param focus - 可选任务焦点(单行化 + ≤200 字消毒;无则不输出 focus 段——向后兼容)
|
|
52
55
|
*/
|
|
53
|
-
export declare function buildRebuildAnchor(root: string, sessionName: string, activeMdPath: string, anchorMessages?: string[]): string;
|
|
56
|
+
export declare function buildRebuildAnchor(root: string, sessionName: string, activeMdPath: string, anchorMessages?: string[], focus?: string | null): string;
|
|
57
|
+
/**
|
|
58
|
+
* 任务焦点行消毒(纯函数可单测):单行化(换行/回车 → 空格)+ 控制字符清除 +
|
|
59
|
+
* ≤200 字截断。空/纯空白 → null(不输出 focus 段)。防 LLM 传多行注入伪造锚点结构。
|
|
60
|
+
*/
|
|
61
|
+
export declare function sanitizeFocusLine(focus: string | null | undefined): string | null;
|
|
54
62
|
export interface RebuildResult {
|
|
55
63
|
/** 是否成功排队(turn 结束时执行清空重建) */
|
|
56
64
|
queued: boolean;
|
|
@@ -73,6 +81,8 @@ interface PendingRebuild {
|
|
|
73
81
|
summary: string;
|
|
74
82
|
/** 持久轨迹 SESSION.md(重建后重命名标题的编号/日期来源) */
|
|
75
83
|
mdPath: string;
|
|
84
|
+
/** v1.29.2(R1):任务焦点(可选——重建后会话理解当前任务;不含历史) */
|
|
85
|
+
focus?: string;
|
|
76
86
|
/** 排队时间(防陈旧队列误清空——超时丢弃) */
|
|
77
87
|
queuedAt: number;
|
|
78
88
|
}
|
package/lib/seams/context.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type { Context } from 'cordis';
|
|
|
13
13
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
14
14
|
import type { UserMessage } from '@deepseek-ai/dsh-session';
|
|
15
15
|
export declare const DEFAULT_ENTRY_SKILL_MAX_CHARS = 30000;
|
|
16
|
+
/** 从 dsh 会话日志读标题(latest-wins `session/title` 事件;无返回 null)——供标题 reconcile(U3) */
|
|
17
|
+
export declare function readDshSessionTitle(session: unknown): string | null;
|
|
16
18
|
export declare function accIdentityText(root: string, configPaths?: string[], entrySkillMaxChars?: number): string;
|
|
17
19
|
/**
|
|
18
20
|
* ACC 注入消息(S134 去重):**只含简短身份锚点**([ACC] 已激活 + CCC 根 + 约束 + handyman 模型 + Phase 2)。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-bound.ts — SESSION 绑定持久化(serenity/bound 会话事件)
|
|
3
|
+
*
|
|
4
|
+
* 目标(S142 用户拍板,方案 v1.0):让「dsh 会话(载体)↔ SESSION(宁静号轨迹)」
|
|
5
|
+
* 的绑定坚固——LLM 不能因智力因素(幻觉/理解偏差)在过程中或 session_rebuild 后
|
|
6
|
+
* 自行更换 SESSION。
|
|
7
|
+
*
|
|
8
|
+
* 机制(DSH 原生,零改 harness):
|
|
9
|
+
* `SessionEventMap` 是 merge-extensible(先例:dsh-compaction 扩展 compaction/*;
|
|
10
|
+
* dsp 已在 append compaction/prune)——插件可 declare module 自定义事件,
|
|
11
|
+
* `Session.append(type, data)` 将其写入会话 append-only 日志,随 session.jsonl
|
|
12
|
+
* 持久化落盘,进程重启后 snapshotEvents() 可完整读取。
|
|
13
|
+
*
|
|
14
|
+
* 权威绑定 = 会话日志中**最后一条** `serenity/bound`(append-only latest-wins)。
|
|
15
|
+
* 本事件是 log-only 元数据(无 surfaceOp——永不上模型可见面,不进系统提示词)。
|
|
16
|
+
*
|
|
17
|
+
* 编码无关(U4):绑定锚 = **完整 AGENT_SESSIONS 目录名**(磁盘唯一存在),
|
|
18
|
+
* sessionId(S###/apaas-xxx/自定义)仅是派生展示字段——绝不假设 S 前缀。
|
|
19
|
+
*/
|
|
20
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
21
|
+
import type { SessionEventMap } from '@deepseek-ai/dsh-session/types';
|
|
22
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
23
|
+
interface SessionEventMap {
|
|
24
|
+
/**
|
|
25
|
+
* Serenity SESSION binding — authoritative; appended on every binding change.
|
|
26
|
+
* Code-agnostic (keyed by full dir name), log-only (no surfaceOp).
|
|
27
|
+
*/
|
|
28
|
+
'serenity/bound': {
|
|
29
|
+
/** Full AGENT_SESSIONS dir name — the ONLY hard identity. */
|
|
30
|
+
dirName: string;
|
|
31
|
+
/** SESSION.md absolute path. */
|
|
32
|
+
mdPath: string;
|
|
33
|
+
/** Display code when parseable (S142 / apaas-26116 / custom); informational. */
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
/** Why this binding record was written. */
|
|
36
|
+
action: 'activate' | 'switch' | 'create' | 'rebuild' | 'reconcile' | 'release';
|
|
37
|
+
/** Epoch ms. */
|
|
38
|
+
at: number;
|
|
39
|
+
/** Optional reason (e.g. 'auto from title'). */
|
|
40
|
+
note?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export type SessionBoundAction = SessionEventMap['serenity/bound']['action'];
|
|
45
|
+
export interface SessionBoundRecord {
|
|
46
|
+
dirName: string;
|
|
47
|
+
mdPath: string;
|
|
48
|
+
sessionId?: string;
|
|
49
|
+
action: SessionBoundAction;
|
|
50
|
+
at: number;
|
|
51
|
+
note?: string;
|
|
52
|
+
}
|
|
53
|
+
/** 事件 type 常量(声明 + 读取共用) */
|
|
54
|
+
export declare const SESSION_BOUND_EVENT: "serenity/bound";
|
|
55
|
+
/**
|
|
56
|
+
* 读取会话日志中**最后一条** `serenity/bound`(权威绑定,latest-wins)。
|
|
57
|
+
* 尾到头扫描(时间序最新在后);无 bound 事件返回 null。
|
|
58
|
+
* 纯逻辑零 DSH 依赖(经 sessionEvents helper 读 snapshotEvents/events 兜底)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function readLastBound(session: unknown): SessionBoundRecord | null;
|
|
61
|
+
/**
|
|
62
|
+
* 判定会话日志是否已有任何 bound 事件(供 reconcile 判定:无绑定才标题升级)。
|
|
63
|
+
*/
|
|
64
|
+
export declare function hasAnyBound(session: unknown): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* append 一条 `serenity/bound` 绑定事件(log-only,无 surfaceOp——元数据不进模型可见面)。
|
|
67
|
+
* @param session 目标 dsh 会话(真实 Session;append 泛型经内部断言兼容——类型由
|
|
68
|
+
* `serenity/bound` 声明面保证,append 接受已声明事件)
|
|
69
|
+
* @param action 绑定动作
|
|
70
|
+
* @param rec 绑定记录(dirName + mdPath 必填;sessionId 可选展示码)
|
|
71
|
+
* @returns 是否成功(append 抛错/会话不可用时 false——绑定失败不阻断主流程)
|
|
72
|
+
*/
|
|
73
|
+
export declare function appendBound(session: Session | {
|
|
74
|
+
append: (type: string, data: unknown) => unknown;
|
|
75
|
+
} | null | undefined, action: SessionBoundAction, rec: {
|
|
76
|
+
dirName: string;
|
|
77
|
+
mdPath: string;
|
|
78
|
+
sessionId?: string;
|
|
79
|
+
note?: string;
|
|
80
|
+
}): boolean;
|
package/lib/session-ops.d.ts
CHANGED
|
@@ -117,6 +117,19 @@ export declare function parseSessionContextFromEvents(events: readonly unknown[]
|
|
|
117
117
|
/** 从文本提取 SESSION.md 路径(use 上下文 / 重建锚点规范行通用;无匹配返回 null)。
|
|
118
118
|
* 同行已知尾注(如系统提示词 Session 块的 persistent-body 注释)剥除——规范行只取路径本体。 */
|
|
119
119
|
export declare function extractSessionMdPathFromText(text: string): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* 从 dsh 会话标题解析 SESSION 目录(编码无关 best-match,U3/U4)——
|
|
122
|
+
* 标题不假设 S### 前缀(CCC 可自定义编码:apaas-xxx / P### / 完整目录名等)。
|
|
123
|
+
* 匹配优先级(全部对 AGENT_SESSIONS 现有目录 best-match,不猜):
|
|
124
|
+
* ① 标题即完整目录名(含日期前缀 `YYYY-MM-DD--`)→ 精确命中
|
|
125
|
+
* ② 标题含 `--<code>--` 段(如完整目录名被截断为 `<code>-日期-概括` 前段)→
|
|
126
|
+
* 按 code 段匹配:取标题首 token(`-` 前),与各目录 `--<code>--`/`--<code>` 尾段比对
|
|
127
|
+
* ③ 唯一模糊子串匹配(多个则返回 null 防误猜)
|
|
128
|
+
* @param title dsh 会话标题(如 `S142-2026-08-24-概括` / `apaas-26116-…` / 完整目录名)
|
|
129
|
+
* @param sessionsDir AGENT_SESSIONS 绝对路径
|
|
130
|
+
* @returns 命中目录的绝对路径(SESSION.md);无/歧义返回 null
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveSessionByTitle(title: string, sessionsDir: string): string | null;
|
|
120
133
|
/**
|
|
121
134
|
* 约定回退(v1.24.11):AGENT_SESSIONS 下最新修改的**未完成**会话的 SESSION.md。
|
|
122
135
|
* readAllSessions 已按「未完成优先 + mtime 降序」排序 → 首个未完成且含 SESSION.md 即最新活动。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.2",
|
|
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": {
|