@shgroup/dsh-serenity-hooks 1.28.2 → 1.29.1
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/client.js +281 -76
- package/lib/index.js +340 -49
- package/lib/msm-ops.d.ts +1 -1
- package/lib/seams/context.d.ts +2 -0
- package/lib/seams/keeper.d.ts +1 -1
- package/lib/seams/system-prompt.d.ts +4 -1
- package/lib/session-bound.d.ts +80 -0
- package/lib/session-cleanup-PahSNvjI.js +113 -0
- package/lib/session-cleanup.d.ts +60 -0
- package/lib/session-ops.d.ts +13 -0
- package/lib/trajectory-assistant.d.ts +54 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1428,7 +1428,7 @@ handyman(杂工)工具可用模型白名单(provider/model 列表);未
|
|
|
1428
1428
|
{ "handyman": { "models": ["minimax-cn-coding-plan/MiniMax-M3"], "defaultModel": "minimax-cn-coding-plan/MiniMax-M3", "maxRounds": 100, "maxParallel": 10 } }
|
|
1429
1429
|
|
|
1430
1430
|
── 2. sessionKeeper.threshold ──
|
|
1431
|
-
|
|
1431
|
+
trajectory-assistant(TRAJECTORY-ASSISTANT · CHECKPOINT)提醒机制的积分阈值(非 headless 主 agent)。
|
|
1432
1432
|
按工具调用加权 + 耗时计分;达到阈值注入提醒,要求模型回复 ACK 码。
|
|
1433
1433
|
|
|
1434
1434
|
Config:
|
|
@@ -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
|
|
@@ -3824,6 +3956,49 @@ function registerBootstrap(ctx) {
|
|
|
3824
3956
|
}, { prepend: true });
|
|
3825
3957
|
}
|
|
3826
3958
|
//#endregion
|
|
3959
|
+
//#region src/trajectory-assistant.ts
|
|
3960
|
+
/**
|
|
3961
|
+
* trajectory-assistant.ts — 关卡化注入的统一命名与风格门面(v0.3,S142)
|
|
3962
|
+
*
|
|
3963
|
+
* 概念(用户拍板 2026-09-05):dsp 全部"过程中动态提示注入"统一命名为
|
|
3964
|
+
* trajectory-assistant(轨迹助航员)。关卡设计思想塑造**结构与时机**
|
|
3965
|
+
* (何时/何地注入),**不用于提示词用词**(D8 词法原则)——纯游戏黑话
|
|
3966
|
+
* (BOSS/XP/level-up)禁止出现在提示词文本;CHECKPOINT/LIMIT/TUTORIAL 等
|
|
3967
|
+
* 跨领域自然通用词允许。
|
|
3968
|
+
*
|
|
3969
|
+
* 本模块 = token 常量 + level-event 词汇表的**单一真相源**(避免前缀散落
|
|
3970
|
+
* 各文件字符串字面量)+ 风格门面(plain 默认 / metaphor 星舰变体)。
|
|
3971
|
+
* 仅当 style=metaphor 时前缀包装措辞变化;可行动正文不变 → 零行为漂移。
|
|
3972
|
+
*
|
|
3973
|
+
* 设计简写(L0 环境 / L1 教程 / L2 目标 / L3 检查点 / L4 极限 / L5 守卫 /
|
|
3974
|
+
* L6 结算)仅在本模块注释与内部文档出现,永不浮现在提示词文本。
|
|
3975
|
+
*
|
|
3976
|
+
* 结算(settlement)挂点:D6 用户拍板——若 CCC 有标准 SESSION 复盘仪式,
|
|
3977
|
+
* 结算视图归 trajectory-assistant。当前 close/archive 无复盘摘要 → 机制
|
|
3978
|
+
* 延后,仅留 onSettlement 导出 seam(OP-1,无调用者)。
|
|
3979
|
+
*/
|
|
3980
|
+
/** 可见提示词前缀用词(跨领域自然词,D8:无游戏黑话) */
|
|
3981
|
+
const EVENT_LABEL = {
|
|
3982
|
+
/** 计分同步提醒(原 TRAJECTORY-STEWARD) */
|
|
3983
|
+
checkpoint: "CHECKPOINT",
|
|
3984
|
+
/** 上下文极限重建提醒(原 TRAJECTORY;LIMIT 替代被否的 BOSS——自然词) */
|
|
3985
|
+
limit: "LIMIT",
|
|
3986
|
+
/** 极限强制升级(原 TRAJECTORY-ESCALATED) */
|
|
3987
|
+
limitMandatory: "LIMIT · MANDATORY",
|
|
3988
|
+
/** 重建锚点头部(原 TRAJECTORY-REBUILD) */
|
|
3989
|
+
rebuild: "REBUILD",
|
|
3990
|
+
/** 敏感输出边界守卫(原 SERENITY OUTPUT GUARD) */
|
|
3991
|
+
guard: "BOUNDARY GUARD"
|
|
3992
|
+
};
|
|
3993
|
+
/** 家族标识(所有动态注入统一前缀) */
|
|
3994
|
+
const ASSISTANT_PREFIX = "TRAJECTORY-ASSISTANT";
|
|
3995
|
+
/** 完整 token:`[TRAJECTORY-ASSISTANT · <LABEL>]` */
|
|
3996
|
+
function eventToken(event) {
|
|
3997
|
+
return `[${ASSISTANT_PREFIX} · ${EVENT_LABEL[event]}]`;
|
|
3998
|
+
}
|
|
3999
|
+
/** ACK 确认码前缀(recorded/skipped 语义不变,仅家族名更新) */
|
|
4000
|
+
const ACK_PREFIX = `${ASSISTANT_PREFIX}-recorded`;
|
|
4001
|
+
//#endregion
|
|
3827
4002
|
//#region src/rebuild.ts
|
|
3828
4003
|
const PLUGIN_SOURCE$4 = {
|
|
3829
4004
|
kind: "plugin",
|
|
@@ -3850,7 +4025,7 @@ function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DE
|
|
|
3850
4025
|
const sessionDir = basename(dirname(activeMdPath));
|
|
3851
4026
|
const sessionLine = sessionDir !== "AGENT_SESSIONS" ? `- Serenity session: ${sessionName !== "" ? `${sessionName} (${sessionDir})` : sessionDir}` : null;
|
|
3852
4027
|
return [
|
|
3853
|
-
"
|
|
4028
|
+
`${eventToken("rebuild")} The conversation has been cleared and rebuilt (Ship of Theseus: the carrier is replaced, the trajectory continues).`,
|
|
3854
4029
|
"",
|
|
3855
4030
|
...anchorMessages.flatMap((text) => [stripAckSuffix(text), ""]),
|
|
3856
4031
|
sessionName !== "" ? `Continue the work of ${sessionName}.` : "Continue the current work.",
|
|
@@ -3945,7 +4120,14 @@ async function queueRebuild(ctx, opts) {
|
|
|
3945
4120
|
if (!session) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
|
|
3946
4121
|
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
3947
4122
|
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.");
|
|
3948
|
-
const
|
|
4123
|
+
const sessionName = getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath);
|
|
4124
|
+
const anchor = buildRebuildAnchor(root, sessionName, mdPath);
|
|
4125
|
+
appendBound(session, "rebuild", {
|
|
4126
|
+
dirName: basename(dirname(mdPath)),
|
|
4127
|
+
mdPath,
|
|
4128
|
+
sessionId: sessionName.startsWith("S") ? sessionName : void 0,
|
|
4129
|
+
note: "rebuild queued"
|
|
4130
|
+
});
|
|
3949
4131
|
pendingRebuilds.set(dshSessionId, {
|
|
3950
4132
|
anchor,
|
|
3951
4133
|
summary,
|
|
@@ -4075,7 +4257,7 @@ function registerRebuildTurnHook(ctx) {
|
|
|
4075
4257
|
agent.steer(createUserMessage({
|
|
4076
4258
|
content: [{
|
|
4077
4259
|
type: "text",
|
|
4078
|
-
text: "
|
|
4260
|
+
text: `${eventToken("rebuild")} The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent trajectory (SESSION.md) and continue the work automatically from the last checkpoint.`
|
|
4079
4261
|
}],
|
|
4080
4262
|
source: PLUGIN_SOURCE$4
|
|
4081
4263
|
}));
|
|
@@ -4137,7 +4319,7 @@ function renderText$3(value) {
|
|
|
4137
4319
|
function createRebuildTool(ctx) {
|
|
4138
4320
|
return defineTool({
|
|
4139
4321
|
name: "session_rebuild",
|
|
4140
|
-
description: "Trajectory-tracker overflow rebuild (Ship of Theseus): this session is the rebuildable carrier of a trajectory — the current conversation is discarded and rebuilt in place at the end of this turn with the anchor \"continue the work of {SESSION name}\" (first-anchor protocol body included), then auto-continues. SESSION.md (the trajectory's persistent body) stays in place; identity continues from it. Use when you receive a
|
|
4322
|
+
description: "Trajectory-tracker overflow rebuild (Ship of Theseus): this session is the rebuildable carrier of a trajectory — the current conversation is discarded and rebuilt in place at the end of this turn with the anchor \"continue the work of {SESSION name}\" (first-anchor protocol body included), then auto-continues. SESSION.md (the trajectory's persistent body) stays in place; identity continues from it. Use when you receive a " + eventToken("limit") + " reminder (context above threshold), at a natural pause point. After triggering, resume from SESSION.md when this turn ends.",
|
|
4141
4323
|
parameters: {
|
|
4142
4324
|
note: {
|
|
4143
4325
|
type: "string",
|
|
@@ -4600,7 +4782,7 @@ function buildWakeMessage(opts) {
|
|
|
4600
4782
|
lines.push(`[轨迹焦点] ${opts.topPrompt}`);
|
|
4601
4783
|
lines.push("");
|
|
4602
4784
|
}
|
|
4603
|
-
lines.push(`[Autopilot Trajectory 唤起] — 距上次轨迹活动已满 ${intervalLabel},自动继续。`, "", `身份锚定:继续 ${opts.sessionName} 的 trajectory(SESSION.md: ${opts.mdPath})。`, "先验偏见:");
|
|
4785
|
+
lines.push(`[Autopilot Trajectory · 唤起] — 距上次轨迹活动已满 ${intervalLabel},自动继续。`, "", `身份锚定:继续 ${opts.sessionName} 的 trajectory(SESSION.md: ${opts.mdPath})。`, "先验偏见:");
|
|
4604
4786
|
if (opts.motivation) lines.push(` · 自生动机:${opts.motivation}`);
|
|
4605
4787
|
if (opts.biasContent) lines.push(` · 偏见内容:${opts.biasContent}`);
|
|
4606
4788
|
if (!opts.motivation && !opts.biasContent) lines.push(" · (无——本轮纯自主探索)");
|
|
@@ -5128,7 +5310,7 @@ var KeeperTracker = class {
|
|
|
5128
5310
|
}
|
|
5129
5311
|
};
|
|
5130
5312
|
function reminderText(code, score) {
|
|
5131
|
-
return
|
|
5313
|
+
return `${eventToken("checkpoint")} Score threshold reached (${score}). Please acknowledge with [${ACK_PREFIX}-${code}] once progress is synced to the working session (acc-session show). No need to interrupt your work — just acknowledge inline and keep going.`;
|
|
5132
5314
|
}
|
|
5133
5315
|
/**
|
|
5134
5316
|
* F2 rebuild 提示(v1.21;v1.22.1 对齐"轨迹跟踪器"概念;v1.23.0 英化 + 载体关系;
|
|
@@ -5147,8 +5329,8 @@ function reminderText(code, score) {
|
|
|
5147
5329
|
* thresholdK = 配置阈值(千 token);文案 `Context usage at NNNK (threshold NNNK)`。
|
|
5148
5330
|
*/
|
|
5149
5331
|
function rebuildReminderText(tokensK, thresholdK, escalated = false) {
|
|
5150
|
-
if (escalated) return
|
|
5151
|
-
return
|
|
5332
|
+
if (escalated) return `${eventToken("limitMandatory")} Context usage at ${Math.round(tokensK)}K (threshold ${Math.round(thresholdK)}K) — you have been reminded repeatedly and have NOT called session_rebuild. This is now mandatory: STOP at the current task step, preserve valuable cognition into the CCC skills (or write a new-skill proposal into SESSION.md), then call the session_rebuild tool immediately, passing --summary "<content summary ≤20 chars>" (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild). The conversation will be cleared and rebuilt in place; SESSION.md is the persistent trajectory and stays in place — identity continues from it. Do not continue working without rebuilding; this reminder persists until you call session_rebuild.`;
|
|
5333
|
+
return `${eventToken("limit")} Context usage at ${Math.round(tokensK)}K (threshold ${Math.round(thresholdK)}K). This session is the rebuildable carrier of the trajectory: SESSION.md is the persistent body, this conversation is only a temporary work copy. Before rebuilding: if this conversation produced valuable cognition, revise the relevant existing skill of this CCC (structure it with eap); if a new skill is warranted, write a short proposal into SESSION.md for the user to review — do not create it yourself. ACT NOW: at the next natural pause (end of the current task step), call the session_rebuild tool — passing --summary "<content summary ≤20 chars>" describing the next work phase (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild) — to clear and rebuild this conversation: the current copy is discarded, identity continues from SESSION.md. If you are in the middle of an unbreakable step, continue it, then rebuild at its end. Do not ignore this; rebuild is the expected action, not an option.`;
|
|
5152
5334
|
}
|
|
5153
5335
|
/** 读取会话 contextPressure 投影(sessionProjections 可选服务;未装配返回 null) */
|
|
5154
5336
|
function readContextPressure(ctx, session) {
|
|
@@ -5905,22 +6087,25 @@ function eapBlock() {
|
|
|
5905
6087
|
].join("\n");
|
|
5906
6088
|
}
|
|
5907
6089
|
/**
|
|
5908
|
-
* Metaphor 强化块(v1.19.6,S142
|
|
6090
|
+
* Metaphor 强化块(v1.19.6,S142;v1.29 星舰意象升级,S142 用户拍板):
|
|
6091
|
+
* 宁静号宇宙隐喻域(星舰/深空/航程/船员)。海船→星舰意象:one starship, one voyage;
|
|
6092
|
+
* deep space / launch / docking clamp;Departure Inspection(原 Harbor Inspection)。
|
|
5909
6093
|
* 隐喻是记忆钩子——形象唤起约束,前述块保持规则精确。每条隐喻 = 一个不可违背的
|
|
5910
6094
|
* 物理事实 + 行为判据(verdict)。全英文(与 CCE/Constraints 一致);独立块可
|
|
5911
6095
|
* 回退;无 osp 对应(dsp 扩展,不进对齐断言参照)。
|
|
6096
|
+
* 结构约束(docs/metaphor-domain.md M-1~M-4):三层骨架不变 + 每条映射约束不变。
|
|
5912
6097
|
*/
|
|
5913
6098
|
function metaphorBlock() {
|
|
5914
6099
|
return [
|
|
5915
6100
|
"",
|
|
5916
6101
|
"=== Serenity Metaphor ===",
|
|
5917
|
-
"The Serenity Universe — one
|
|
6102
|
+
"The Serenity Universe — one starship, one voyage. Metaphors are memory hooks:",
|
|
5918
6103
|
"they make constraints vivid, while the rules above stay precise. Each",
|
|
5919
6104
|
"metaphor is an unbreakable physical fact; violating one is a behavioral",
|
|
5920
6105
|
"violation. The universe is structured in three layers — the Ship (the",
|
|
5921
6106
|
"container itself), the Voyage (the cognitive lifecycle), the Crew",
|
|
5922
6107
|
"(multi-agent collaboration); every metaphor maps to one protocol",
|
|
5923
|
-
"constraint.
|
|
6108
|
+
"constraint. Deep space has no mistakes — only stars you have not yet mapped.",
|
|
5924
6109
|
"",
|
|
5925
6110
|
"THE SHIP — the container itself",
|
|
5926
6111
|
"",
|
|
@@ -5930,8 +6115,8 @@ function metaphorBlock() {
|
|
|
5930
6115
|
" container = overload.",
|
|
5931
6116
|
"",
|
|
5932
6117
|
"2. Deck Order → Entropy (H_op). Clutter on deck raises the cost of",
|
|
5933
|
-
" finding things. H_op ≤ H_critical = the ship stays
|
|
5934
|
-
" Verdict: disorganized output =
|
|
6118
|
+
" finding things. H_op ≤ H_critical = the ship stays flight-worthy.",
|
|
6119
|
+
" Verdict: disorganized output = debris in the hold.",
|
|
5935
6120
|
"",
|
|
5936
6121
|
"3. Engineering Drawings → EAP. Every part dimensioned (E↑), the",
|
|
5937
6122
|
" drawings rebuild the whole machine (R↓), the drawings are reusable",
|
|
@@ -5947,14 +6132,14 @@ function metaphorBlock() {
|
|
|
5947
6132
|
" is on the manifest (mech-registry); there is exactly one manifest.",
|
|
5948
6133
|
" An MSM self-describes (--help/--schema) — the manifest is the only",
|
|
5949
6134
|
" key. Verdict: duplicating a tool's usage in documents = two",
|
|
5950
|
-
" contradictory charts.",
|
|
6135
|
+
" contradictory star charts.",
|
|
5951
6136
|
"",
|
|
5952
6137
|
"THE VOYAGE — the cognitive lifecycle",
|
|
5953
6138
|
"",
|
|
5954
|
-
"6.
|
|
5955
|
-
"
|
|
5956
|
-
" ballast (constraints) before setting
|
|
5957
|
-
"
|
|
6139
|
+
"6. Departure Inspection → First Anchor. The departure inspection = pre-",
|
|
6140
|
+
" launch checklist: confirm identity (ACC manifesto), logbook (SESSION),",
|
|
6141
|
+
" ballast (constraints) before setting course. Verdict: skipping the",
|
|
6142
|
+
" inspection and launching directly = flying uninspected.",
|
|
5958
6143
|
"",
|
|
5959
6144
|
"7. The Logbook → Session Tracking. SESSION.md is the trajectory's logbook —",
|
|
5960
6145
|
" the persistent body of the voyage; sessions are rebuildable carriers of",
|
|
@@ -6090,12 +6275,12 @@ function sessionBlock(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
6090
6275
|
"This preserves the session context across todo updates.",
|
|
6091
6276
|
"Do NOT remove or reorder this item — keep it at position 0.",
|
|
6092
6277
|
"",
|
|
6093
|
-
"TRAJECTORY-
|
|
6278
|
+
"TRAJECTORY-ASSISTANT: a background tracker scores your tool use (write/edit=3,",
|
|
6094
6279
|
"task=10, read/grep/glob/msm=1, +1 per minute) and reminds you with a",
|
|
6095
|
-
"[TRAJECTORY-
|
|
6280
|
+
"[TRAJECTORY-ASSISTANT · CHECKPOINT] message when the threshold is reached. On every such",
|
|
6096
6281
|
"reminder you MUST reply with the exact ACK code:",
|
|
6097
|
-
" [TRAJECTORY-
|
|
6098
|
-
" [TRAJECTORY-
|
|
6282
|
+
" [TRAJECTORY-ASSISTANT-recorded-{code}] — if you recorded progress to SESSION.md",
|
|
6283
|
+
" [TRAJECTORY-ASSISTANT-skipped-{code}] — if nothing to record this round",
|
|
6099
6284
|
"Do not ignore the reminder; do not stop ongoing work. Codes are single-use;",
|
|
6100
6285
|
"never reuse a prior code.",
|
|
6101
6286
|
""
|
|
@@ -6235,6 +6420,17 @@ function registerEntrySkillSection(agent, root) {
|
|
|
6235
6420
|
//#endregion
|
|
6236
6421
|
//#region src/seams/context.ts
|
|
6237
6422
|
const DEFAULT_ENTRY_SKILL_MAX_CHARS = 3e4;
|
|
6423
|
+
/** 从 dsh 会话日志读标题(latest-wins `session/title` 事件;无返回 null)——供标题 reconcile(U3) */
|
|
6424
|
+
function readDshSessionTitle(session) {
|
|
6425
|
+
try {
|
|
6426
|
+
const events = sessionEvents(session);
|
|
6427
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
6428
|
+
const e = events[i];
|
|
6429
|
+
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
6430
|
+
}
|
|
6431
|
+
} catch {}
|
|
6432
|
+
return null;
|
|
6433
|
+
}
|
|
6238
6434
|
function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entrySkillMaxChars = DEFAULT_ENTRY_SKILL_MAX_CHARS) {
|
|
6239
6435
|
loadSerenityConfig(root, configPaths);
|
|
6240
6436
|
const defaultModel = readHandymanConfig(root, configPaths)?.defaultModel;
|
|
@@ -6322,17 +6518,56 @@ function registerContext(ctx, opts = {}) {
|
|
|
6322
6518
|
const scope = agentScope(agent);
|
|
6323
6519
|
if (shouldRestoreActive(agent) && getActiveSessionInfo(scope) === null) try {
|
|
6324
6520
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
6325
|
-
const
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6521
|
+
const dshSession = agent.session;
|
|
6522
|
+
let restored = null;
|
|
6523
|
+
const bound = readLastBound(dshSession);
|
|
6524
|
+
if (bound) {
|
|
6525
|
+
const abs = bound.mdPath.startsWith(root) ? bound.mdPath : resolve(root, bound.mdPath);
|
|
6526
|
+
if (existsSync(abs)) restored = {
|
|
6527
|
+
dirName: bound.dirName,
|
|
6528
|
+
mdPath: abs,
|
|
6529
|
+
sessionId: bound.sessionId
|
|
6530
|
+
};
|
|
6531
|
+
}
|
|
6532
|
+
if (!restored) {
|
|
6533
|
+
const info = parseSessionContextFromEvents(sessionEvents(dshSession));
|
|
6534
|
+
if (info) {
|
|
6535
|
+
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
6536
|
+
if (existsSync(abs)) restored = {
|
|
6537
|
+
dirName: info.dirName,
|
|
6538
|
+
mdPath: abs,
|
|
6539
|
+
sessionId: info.sessionId
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
if (!restored) {
|
|
6544
|
+
const title = readDshSessionTitle(dshSession);
|
|
6545
|
+
if (title) {
|
|
6546
|
+
const md = resolveSessionByTitle(title, sessionsRoot(root));
|
|
6547
|
+
if (md && existsSync(md)) {
|
|
6548
|
+
const dirName = basename(dirname(md));
|
|
6549
|
+
restored = {
|
|
6550
|
+
dirName,
|
|
6551
|
+
mdPath: md,
|
|
6552
|
+
sessionId: void 0
|
|
6553
|
+
};
|
|
6554
|
+
appendBound(dshSession, "reconcile", {
|
|
6555
|
+
dirName,
|
|
6556
|
+
mdPath: md,
|
|
6557
|
+
note: "auto from title"
|
|
6558
|
+
});
|
|
6559
|
+
console.log(`[serenity-hooks] ↻ 标题兼容持久化绑定: ${title} → ${dirName}`);
|
|
6560
|
+
}
|
|
6334
6561
|
}
|
|
6335
6562
|
}
|
|
6563
|
+
if (restored) {
|
|
6564
|
+
const derived = restored.sessionId ?? (restored.dirName.match(/--([^--]+)--/) ? restored.dirName.match(/--([^--]+)--/)[1] : void 0) ?? restored.dirName;
|
|
6565
|
+
setActiveSessionInfo(scope, {
|
|
6566
|
+
...restored,
|
|
6567
|
+
sessionId: derived
|
|
6568
|
+
});
|
|
6569
|
+
console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${restored.dirName}`);
|
|
6570
|
+
}
|
|
6336
6571
|
}
|
|
6337
6572
|
} catch {}
|
|
6338
6573
|
if (injected.has(key)) return;
|
|
@@ -7025,6 +7260,58 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7025
7260
|
}
|
|
7026
7261
|
}
|
|
7027
7262
|
});
|
|
7263
|
+
ctx.webServer.register({
|
|
7264
|
+
kind: "exact",
|
|
7265
|
+
path: "/serenity/session-cleanup",
|
|
7266
|
+
handler: async (req, res) => {
|
|
7267
|
+
try {
|
|
7268
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
7269
|
+
const daysParam = Number(url.searchParams.get("olderThanDays") ?? "30");
|
|
7270
|
+
const days = Number.isFinite(daysParam) && daysParam >= 1 ? daysParam : 30;
|
|
7271
|
+
const sessions = ctx.get("sessions");
|
|
7272
|
+
const liveIds = /* @__PURE__ */ new Set();
|
|
7273
|
+
if (sessions?.list) for (const s of sessions.list()) {
|
|
7274
|
+
const id = s.id ?? s.header?.id;
|
|
7275
|
+
if (id) liveIds.add(id);
|
|
7276
|
+
}
|
|
7277
|
+
const { sessionsRootDir, collectEligibleSessions, performCleanup, cutoffDaysAgo } = await import("./session-cleanup-PahSNvjI.js");
|
|
7278
|
+
const root = sessionsRootDir();
|
|
7279
|
+
if (req.method === "GET") {
|
|
7280
|
+
const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
|
|
7281
|
+
sendJson$1(res, 200, {
|
|
7282
|
+
root,
|
|
7283
|
+
olderThanDays: days,
|
|
7284
|
+
dryRun: true,
|
|
7285
|
+
count: candidates.length,
|
|
7286
|
+
candidates: candidates.map((c) => ({
|
|
7287
|
+
id: c.id,
|
|
7288
|
+
project: c.project,
|
|
7289
|
+
lastActive: new Date(c.lastActiveMs).toISOString()
|
|
7290
|
+
}))
|
|
7291
|
+
});
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
if (req.method !== "POST") {
|
|
7295
|
+
sendJson$1(res, 405, { error: "method not allowed" });
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
if (req.headers["x-serenity-ui"] !== "1") {
|
|
7299
|
+
sendJson$1(res, 403, { error: "会话清理仅限 WebUI(client 专用)" });
|
|
7300
|
+
return;
|
|
7301
|
+
}
|
|
7302
|
+
const { result } = performCleanup(root, cutoffDaysAgo(days), liveIds);
|
|
7303
|
+
sendJson$1(res, 200, {
|
|
7304
|
+
root,
|
|
7305
|
+
olderThanDays: days,
|
|
7306
|
+
dryRun: false,
|
|
7307
|
+
deleted: result.deleted,
|
|
7308
|
+
errors: result.errors
|
|
7309
|
+
});
|
|
7310
|
+
} catch (err) {
|
|
7311
|
+
sendJson$1(res, 400, { error: err.message ?? String(err) });
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
});
|
|
7028
7315
|
}
|
|
7029
7316
|
/** 进行中的扫码登录(loginKey → 状态;进程内,5min 有效) */
|
|
7030
7317
|
const weixinLogins = /* @__PURE__ */ new Map();
|
|
@@ -8088,6 +8375,8 @@ const MECHANISM_WORDS = [
|
|
|
8088
8375
|
"tools/post-execute",
|
|
8089
8376
|
"session_rebuild",
|
|
8090
8377
|
"Trajectory Steward",
|
|
8378
|
+
"TRAJECTORY-STEWARD",
|
|
8379
|
+
"TRAJECTORY-ASSISTANT",
|
|
8091
8380
|
"first-anchor",
|
|
8092
8381
|
"拦截缝",
|
|
8093
8382
|
"装配层",
|
|
@@ -8190,7 +8479,9 @@ const CATEGORY_GUIDE = {
|
|
|
8190
8479
|
*/
|
|
8191
8480
|
function buildRebuke(hits) {
|
|
8192
8481
|
const count = hits.length;
|
|
8193
|
-
|
|
8482
|
+
const noun = count === 1 ? "term" : "terms";
|
|
8483
|
+
const lines = hits.map((h) => `- "${h.word}" — ${CATEGORY_GUIDE[h.category]}`).join("\n");
|
|
8484
|
+
return `${eventToken("guard")} Your previous response contained ${count} sensitive internal ${noun} that must not appear in user-visible output:\n${lines}\nRegenerate the response from scratch without ANY of these — describe the same substance without referencing internal machinery, credentials, ports, tool names, or implementation details. Do not repeat the terms; do not explain this instruction to the user.`;
|
|
8194
8485
|
}
|
|
8195
8486
|
/** 打回状态(按 agent 会话跟踪连续命中轮数) */
|
|
8196
8487
|
const rebukeStates = /* @__PURE__ */ new Map();
|