@shgroup/dsh-serenity-hooks 1.29.1 → 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 +84 -23
- package/lib/rebuild.d.ts +11 -1
- 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
|
@@ -4015,15 +4015,19 @@ function stripAckSuffix(text) {
|
|
|
4015
4015
|
/**
|
|
4016
4016
|
* 构建重建锚点消息(v1.22.4 定稿语义 + v1.22.5 保留 first-anchor 正文):
|
|
4017
4017
|
* 「[TRAJECTORY-REBUILD] + first-anchor 协议正文(去 acknowledge 尾句)+ 继续 {SESSION 名} 的工作」。
|
|
4018
|
+
* v1.29.2(R1):可选 task focus 段——简短任务焦点文字传入重建后会话(不含历史,
|
|
4019
|
+
* 历史完整在 SESSION.md;让重建后的自己第一时间理解本轮焦点,降低冷启动认知成本)。
|
|
4018
4020
|
* @param root - CCC 根
|
|
4019
4021
|
* @param sessionName - 当前 use 的宁静号 SESSION 名(如 S142);无激活则通用指令
|
|
4020
4022
|
* @param activeMdPath - 持久轨迹 SESSION.md 绝对路径(保持原位)
|
|
4021
4023
|
* @param anchorMessages - first-anchor 协议消息序列(缺省 DEFAULT_ANCHOR_MESSAGES;可注入测试)
|
|
4024
|
+
* @param focus - 可选任务焦点(单行化 + ≤200 字消毒;无则不输出 focus 段——向后兼容)
|
|
4022
4025
|
*/
|
|
4023
|
-
function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DEFAULT_ANCHOR_MESSAGES) {
|
|
4026
|
+
function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DEFAULT_ANCHOR_MESSAGES, focus) {
|
|
4024
4027
|
const rel = activeMdPath.startsWith(root) ? activeMdPath.slice(root.length + 1) : activeMdPath;
|
|
4025
4028
|
const sessionDir = basename(dirname(activeMdPath));
|
|
4026
4029
|
const sessionLine = sessionDir !== "AGENT_SESSIONS" ? `- Serenity session: ${sessionName !== "" ? `${sessionName} (${sessionDir})` : sessionDir}` : null;
|
|
4030
|
+
const focusLine = sanitizeFocusLine(focus);
|
|
4027
4031
|
return [
|
|
4028
4032
|
`${eventToken("rebuild")} The conversation has been cleared and rebuilt (Ship of Theseus: the carrier is replaced, the trajectory continues).`,
|
|
4029
4033
|
"",
|
|
@@ -4032,9 +4036,22 @@ function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DE
|
|
|
4032
4036
|
...sessionLine ? [sessionLine] : [],
|
|
4033
4037
|
`- Persistent trajectory — SESSION.md path: ${rel}`,
|
|
4034
4038
|
` (the trajectory's persistent body — stays in place through rebuilds)`,
|
|
4035
|
-
`- 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}`] : []
|
|
4036
4041
|
].join("\n");
|
|
4037
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;
|
|
4038
4055
|
/** 从会话 surface 首条 user 消息(重建锚点)解析 SESSION.md 路径(events 异常/缺失时的兜底) */
|
|
4039
4056
|
function parseAnchorMdPath(session) {
|
|
4040
4057
|
try {
|
|
@@ -4121,7 +4138,8 @@ async function queueRebuild(ctx, opts) {
|
|
|
4121
4138
|
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
4122
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.");
|
|
4123
4140
|
const sessionName = getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath);
|
|
4124
|
-
const
|
|
4141
|
+
const focus = note && note.trim() !== "" ? note : void 0;
|
|
4142
|
+
const anchor = buildRebuildAnchor(root, sessionName, mdPath, DEFAULT_ANCHOR_MESSAGES, focus);
|
|
4125
4143
|
appendBound(session, "rebuild", {
|
|
4126
4144
|
dirName: basename(dirname(mdPath)),
|
|
4127
4145
|
mdPath,
|
|
@@ -4132,6 +4150,7 @@ async function queueRebuild(ctx, opts) {
|
|
|
4132
4150
|
anchor,
|
|
4133
4151
|
summary,
|
|
4134
4152
|
mdPath,
|
|
4153
|
+
focus,
|
|
4135
4154
|
queuedAt: Date.now()
|
|
4136
4155
|
});
|
|
4137
4156
|
writeRebuildDiag(root, {
|
|
@@ -4323,7 +4342,7 @@ function createRebuildTool(ctx) {
|
|
|
4323
4342
|
parameters: {
|
|
4324
4343
|
note: {
|
|
4325
4344
|
type: "string",
|
|
4326
|
-
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."
|
|
4327
4346
|
},
|
|
4328
4347
|
summary: {
|
|
4329
4348
|
type: "string",
|
|
@@ -4951,25 +4970,60 @@ function registerAutopilot(ctx) {
|
|
|
4951
4970
|
ctx.on("serenity/settings-changed", () => startTimer());
|
|
4952
4971
|
} catch {}
|
|
4953
4972
|
}
|
|
4954
|
-
/**
|
|
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
|
+
*/
|
|
4955
4986
|
function resolveTargetAgent(ctx, mdPath) {
|
|
4956
|
-
const
|
|
4987
|
+
const dirName = basename(dirname(mdPath));
|
|
4988
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
4957
4989
|
const sid = idMatch ? `S${idMatch[1]}` : null;
|
|
4958
4990
|
const targetRoot = findSerenityRoot(mdPath);
|
|
4991
|
+
const sessions = ctx.sessions;
|
|
4992
|
+
const agents = ctx.agents;
|
|
4993
|
+
const candidates = [];
|
|
4959
4994
|
try {
|
|
4960
|
-
const sessions = ctx.sessions;
|
|
4961
4995
|
for (const s of sessions?.list?.() ?? []) {
|
|
4962
|
-
const
|
|
4996
|
+
const sess = s;
|
|
4997
|
+
const cwd = sess?.header?.cwd ?? "";
|
|
4963
4998
|
if (targetRoot) {
|
|
4964
4999
|
if (findSerenityRoot(cwd) !== targetRoot) continue;
|
|
4965
5000
|
} else if (cwd !== "" && !mdPath.startsWith(cwd.endsWith("/") ? cwd : cwd + "/")) continue;
|
|
4966
|
-
const
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
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
|
+
});
|
|
4971
5015
|
}
|
|
4972
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
|
+
}
|
|
4973
5027
|
return null;
|
|
4974
5028
|
}
|
|
4975
5029
|
/**
|
|
@@ -4989,31 +5043,38 @@ function readSessionTitle(session) {
|
|
|
4989
5043
|
}
|
|
4990
5044
|
/**
|
|
4991
5045
|
* 诊断:目标会话 agent 为何不可得(供 performAutopilotWake 失败信息——
|
|
4992
|
-
* 区分"live
|
|
5046
|
+
* 区分"无 live 会话" / "标题不匹配" / "bound 或标题命中但 agent 未加载")。
|
|
5047
|
+
* v1.29.2(R2):bound 命中但 agent 未加载也归入「已匹配未加载」(原只查标题)。
|
|
5048
|
+
* 返回诊断文本(无则 null)。
|
|
4993
5049
|
*/
|
|
4994
5050
|
function diagnoseTargetUnavailable(ctx, mdPath) {
|
|
4995
|
-
const
|
|
5051
|
+
const dirName = basename(dirname(mdPath));
|
|
5052
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
4996
5053
|
const sid = idMatch ? `S${idMatch[1]}` : null;
|
|
4997
5054
|
const targetRoot = findSerenityRoot(mdPath);
|
|
4998
5055
|
const sameCccTitles = [];
|
|
4999
|
-
const agentMissing = {
|
|
5056
|
+
const agentMissing = { matched: false };
|
|
5000
5057
|
try {
|
|
5001
5058
|
const sessions = ctx.sessions;
|
|
5002
5059
|
for (const s of sessions?.list?.() ?? []) {
|
|
5003
|
-
const
|
|
5060
|
+
const sess = s;
|
|
5061
|
+
const cwd = sess?.header?.cwd ?? "";
|
|
5004
5062
|
if (targetRoot) {
|
|
5005
5063
|
if (findSerenityRoot(cwd) !== targetRoot) continue;
|
|
5006
5064
|
} else if (cwd !== "" && !mdPath.startsWith(cwd.endsWith("/") ? cwd : cwd + "/")) continue;
|
|
5007
|
-
const
|
|
5065
|
+
const bound = readLastBound(sess);
|
|
5066
|
+
const title = readSessionTitle(sess);
|
|
5008
5067
|
if (title) sameCccTitles.push(title);
|
|
5009
|
-
|
|
5010
|
-
|
|
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;
|
|
5011
5072
|
}
|
|
5012
5073
|
}
|
|
5013
5074
|
} catch {}
|
|
5014
|
-
if (agentMissing.
|
|
5015
|
-
if (sameCccTitles.length > 0) return `目标 CCC 内 live 会话标题: [${sameCccTitles.join(", ")}]——均不匹配 ${sid}(目标会话未在 WebUI
|
|
5016
|
-
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} 会话后重试)`;
|
|
5017
5078
|
}
|
|
5018
5079
|
/** Autopilot 绑定的回退 CCC 根:进程 cwd 上溯 .serenity 优先,回退任一 live 会话 root */
|
|
5019
5080
|
function resolveAutopilotRoot(ctx) {
|
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/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": {
|