@shgroup/dsh-serenity-hooks 1.25.9 → 1.25.11
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 +177 -48
- 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.25.
|
|
3
|
+
"version": "1.25.11",
|
|
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
|
@@ -647,7 +647,8 @@ function readSkiffRoles(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
647
647
|
keeper: role.trajectory.keeper === true,
|
|
648
648
|
rebuild: role.trajectory.rebuild === true
|
|
649
649
|
} : void 0,
|
|
650
|
-
systemPrompt: typeof role.systemPrompt === "string" ? role.systemPrompt : void 0
|
|
650
|
+
systemPrompt: typeof role.systemPrompt === "string" ? role.systemPrompt : void 0,
|
|
651
|
+
systemPromptFile: typeof role.systemPromptFile === "string" ? role.systemPromptFile : void 0
|
|
651
652
|
});
|
|
652
653
|
}
|
|
653
654
|
} catch {}
|
|
@@ -672,6 +673,32 @@ function roleMsmWhitelist(role) {
|
|
|
672
673
|
return new Set(role?.msms ?? []);
|
|
673
674
|
}
|
|
674
675
|
/**
|
|
676
|
+
* 解析角色的系统提示词全文(v1.25.10,S142 用户:超长提示词 JSON 内嵌不可读):
|
|
677
|
+
* ① `systemPromptFile` 存在 → 读取文件内容(**推荐配置方法**;相对 CCC 根,
|
|
678
|
+
* 路径逃逸拒绝(resolveInside)+ BOM 剥除(readUtf8)+ 存在性校验)
|
|
679
|
+
* ② 否则 → 内嵌 `systemPrompt`(兼容旧配置)
|
|
680
|
+
* ③ 都无 → 空字符串
|
|
681
|
+
* 文件缺失/逃逸 → 抛错(调用方 catch 降级 + validate 报 issue)。
|
|
682
|
+
* 懒读取:readSkiffRoles 不读文件(guards/seams 每次工具调用查询的热路径零 IO),
|
|
683
|
+
* 仅在本函数(创建 agent / validate / list 时)读取。
|
|
684
|
+
*/
|
|
685
|
+
function resolveRoleSystemPrompt(root, role) {
|
|
686
|
+
if (!role) return "";
|
|
687
|
+
const file = role.systemPromptFile?.trim();
|
|
688
|
+
if (file) {
|
|
689
|
+
const abs = resolveInside(root, file);
|
|
690
|
+
if (!existsSync(abs)) throw new Error(`skiff role "${role.systemPrompt ?? "(unnamed)"}": systemPromptFile "${file}" not found (resolved: ${abs})`);
|
|
691
|
+
return readUtf8(abs).trim();
|
|
692
|
+
}
|
|
693
|
+
return role.systemPrompt ?? "";
|
|
694
|
+
}
|
|
695
|
+
/** 角色系统提示词来源(validate/list 展示用) */
|
|
696
|
+
function systemPromptSource(role) {
|
|
697
|
+
if (role?.systemPromptFile?.trim()) return "file";
|
|
698
|
+
if (role?.systemPrompt?.trim()) return "inline";
|
|
699
|
+
return "none";
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
675
702
|
* Skiff 基础提示词(dsp 只给这部分;CCC 的 systemPrompt 段由调用方拼接):
|
|
676
703
|
* 身份 + 可用 MSM/工具清单 + 调用协议 + 边界声明。动态生成(清单来自角色白名单)。
|
|
677
704
|
*/
|
|
@@ -694,22 +721,20 @@ function buildSkiffBasePrompt(roleName, role) {
|
|
|
694
721
|
}
|
|
695
722
|
//#endregion
|
|
696
723
|
//#region src/skiff-registry.ts
|
|
697
|
-
/**
|
|
698
|
-
* skiff-registry.ts — Skiff 会话注册表(sessionId → role;零 DSH 依赖,可被任何 seams 安全 import)
|
|
699
|
-
*
|
|
700
|
-
* F4b 会话映射:Skiff agent 创建时注册(skiff-core),guards/seams 按 sessionId 查角色。
|
|
701
|
-
* 独立成模块的原因:guards.ts 等拦截缝需要查角色而不引入 skiff-core 的运行时依赖
|
|
702
|
-
* (skiff-core 依赖 @deepseek-ai/dsh-llm,测试/装配级联成本高)——注册表本身纯内存 Map。
|
|
703
|
-
*
|
|
704
|
-
* 生命周期:进程内存态;连接/页面关闭时 unregister;进程重启自然清空(skiff 会话不复存在)。
|
|
705
|
-
*/
|
|
706
724
|
const skiffSessions = /* @__PURE__ */ new Map();
|
|
707
|
-
/** 查 sessionId 的 Skiff 角色名(无 → null
|
|
725
|
+
/** 查 sessionId 的 Skiff 角色名(无 → null)——向后兼容(guards/seams 依赖) */
|
|
708
726
|
function skiffRoleFor$1(sessionId) {
|
|
727
|
+
return skiffSessions.get(sessionId)?.role ?? null;
|
|
728
|
+
}
|
|
729
|
+
/** 查 sessionId 的完整绑定(role + ccc;无 → null)——v1.25.10 会话追问校验 */
|
|
730
|
+
function skiffSessionInfo$1(sessionId) {
|
|
709
731
|
return skiffSessions.get(sessionId) ?? null;
|
|
710
732
|
}
|
|
711
|
-
function registerSkiffSession$1(sessionId, role) {
|
|
712
|
-
skiffSessions.set(sessionId,
|
|
733
|
+
function registerSkiffSession$1(sessionId, role, ccc) {
|
|
734
|
+
skiffSessions.set(sessionId, {
|
|
735
|
+
role,
|
|
736
|
+
ccc
|
|
737
|
+
});
|
|
713
738
|
}
|
|
714
739
|
//#endregion
|
|
715
740
|
//#region src/seams/guards.ts
|
|
@@ -2357,9 +2382,17 @@ const skiffAgents = /* @__PURE__ */ new Map();
|
|
|
2357
2382
|
function skiffRoleFor(sessionId) {
|
|
2358
2383
|
return skiffRoleFor$1(sessionId);
|
|
2359
2384
|
}
|
|
2360
|
-
|
|
2385
|
+
/** 查 sessionId 的会话绑定(role + ccc;无 → null)——v1.25.10 追问校验 */
|
|
2386
|
+
function skiffSessionInfo(sessionId) {
|
|
2387
|
+
return skiffSessionInfo$1(sessionId);
|
|
2388
|
+
}
|
|
2389
|
+
/** 查 sessionId 的活体 agent(进程内会话延续用;未注册/已清理 → undefined) */
|
|
2390
|
+
function getSkiffAgent(sessionId) {
|
|
2391
|
+
return skiffAgents.get(sessionId);
|
|
2392
|
+
}
|
|
2393
|
+
function registerSkiffSession(sessionId, role, ccc, agent) {
|
|
2361
2394
|
skiffAgents.set(sessionId, agent);
|
|
2362
|
-
registerSkiffSession$1(sessionId, role);
|
|
2395
|
+
registerSkiffSession$1(sessionId, role, ccc);
|
|
2363
2396
|
}
|
|
2364
2397
|
/** Skiff agent 挂载的 DSH preset(v1.25.3 修复:read/grep/glob 等平台工具由 preset 决定工具面;
|
|
2365
2398
|
* handyman 经 composeFrom 继承父、skiff 无父上下文——直接挂 DSH 默认 standard preset;
|
|
@@ -2387,16 +2420,22 @@ async function createSkiffAgent(ctx, root, roleName, role, defaultModel) {
|
|
|
2387
2420
|
...model ? { agentOptions: splitModel(model) } : {}
|
|
2388
2421
|
});
|
|
2389
2422
|
const agent = handle.agent;
|
|
2423
|
+
let cccPrompt = "";
|
|
2424
|
+
try {
|
|
2425
|
+
cccPrompt = resolveRoleSystemPrompt(root, role);
|
|
2426
|
+
} catch (err) {
|
|
2427
|
+
console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 系统提示词解析失败(回退仅基础段): ${String(err?.message ?? err)}`);
|
|
2428
|
+
}
|
|
2390
2429
|
try {
|
|
2391
2430
|
agent.ctx.systemPrompt.section({
|
|
2392
2431
|
name: "serenity-skiff",
|
|
2393
2432
|
order: -60,
|
|
2394
|
-
text: () => [buildSkiffBasePrompt(roleName, role),
|
|
2433
|
+
text: () => [buildSkiffBasePrompt(roleName, role), cccPrompt].filter(Boolean).join("\n")
|
|
2395
2434
|
});
|
|
2396
2435
|
} catch (err) {
|
|
2397
2436
|
console.warn(`[serenity-hooks] skiff 系统提示词注册失败: ${String(err?.message ?? err)}`);
|
|
2398
2437
|
}
|
|
2399
|
-
registerSkiffSession(sessionId, roleName, agent);
|
|
2438
|
+
registerSkiffSession(sessionId, roleName, root, agent);
|
|
2400
2439
|
return {
|
|
2401
2440
|
handle,
|
|
2402
2441
|
agent,
|
|
@@ -2478,11 +2517,12 @@ function truncate(s, n) {
|
|
|
2478
2517
|
return s.length > n ? `${s.slice(0, n)}…` : s;
|
|
2479
2518
|
}
|
|
2480
2519
|
/**
|
|
2481
|
-
* 提问一轮:followup → 等 idle → 读答案 +
|
|
2482
|
-
* @param eventsStart
|
|
2520
|
+
* 提问一轮:followup → 等 idle → 读答案 + 轨迹。
|
|
2521
|
+
* @param eventsStart 轨迹起点:显式传 0 = 全量轨迹(会话追问时页面重绘完整时间线,
|
|
2522
|
+
* v1.25.10 用户拍板);不传(undefined)= 本轮增量(followup 前 events 之后)
|
|
2483
2523
|
*/
|
|
2484
|
-
async function askSkiff(ctx, agent, question, eventsStart
|
|
2485
|
-
const before = eventsStart
|
|
2524
|
+
async function askSkiff(ctx, agent, question, eventsStart) {
|
|
2525
|
+
const before = eventsStart === void 0 ? agent.session.events.length : eventsStart;
|
|
2486
2526
|
agent.followup(createUserMessage({
|
|
2487
2527
|
content: [{
|
|
2488
2528
|
type: "text",
|
|
@@ -5944,6 +5984,43 @@ function renameDshSessionOnUse(deps, session, titles, active) {
|
|
|
5944
5984
|
};
|
|
5945
5985
|
}
|
|
5946
5986
|
}
|
|
5987
|
+
/**
|
|
5988
|
+
* 从 create 结果构造命名用 ActiveSessionInfo(v1.25.11,S142 用户:create 也要命名):
|
|
5989
|
+
* createSession 返回的 result 已含 sessionId(S###/issue)与 dirName——无需等待 use 激活,
|
|
5990
|
+
* 直接构造(mdPath = sessionPath/SESSION.md,与 use 时一致)即可驱动 renameDshSessionOnUse。
|
|
5991
|
+
*/
|
|
5992
|
+
function activeInfoFromCreate(result) {
|
|
5993
|
+
return {
|
|
5994
|
+
sessionId: result.sessionId,
|
|
5995
|
+
dirName: result.dirName,
|
|
5996
|
+
mdPath: join(result.sessionPath, "SESSION.md")
|
|
5997
|
+
};
|
|
5998
|
+
}
|
|
5999
|
+
/**
|
|
6000
|
+
* 把当前 dsh 会话重命名为指定 SESSION 的命名标题(use/create 共用;v1.25.11)。
|
|
6001
|
+
* 门控/失败可见性与 renameDshSessionOnUse 一致(不静默:成功 log / 失败 warn)。
|
|
6002
|
+
* 调用点(use 分支原内联逻辑提取,create 分支复用):
|
|
6003
|
+
* - use:激活后从 activeStore 取 info
|
|
6004
|
+
* - create:createSession 结果经 activeInfoFromCreate 构造 info
|
|
6005
|
+
*/
|
|
6006
|
+
function renameDshSessionForActive(ctx, exec, info) {
|
|
6007
|
+
try {
|
|
6008
|
+
const titles = ctx.get?.("sessionTitle");
|
|
6009
|
+
const dshSession = exec.agent?.session;
|
|
6010
|
+
if (!dshSession) {
|
|
6011
|
+
console.warn(`[serenity-hooks] dsh 会话重命名未执行: 缺少 agent session(info: ${info.sessionId})`);
|
|
6012
|
+
return;
|
|
6013
|
+
}
|
|
6014
|
+
const result = renameDshSessionOnUse({
|
|
6015
|
+
namingEnabled: readSimpleSettings().namingEnabled,
|
|
6016
|
+
sessionTitleAvailable: true
|
|
6017
|
+
}, dshSession, titles, info);
|
|
6018
|
+
if (result.ok) console.log(`[serenity-hooks] dsh 会话已重命名: ${String(dshSession.id ?? "?")} → ${result.title}`);
|
|
6019
|
+
else console.warn(`[serenity-hooks] dsh 会话重命名未执行: ${result.reason}`);
|
|
6020
|
+
} catch (err) {
|
|
6021
|
+
console.warn(`[serenity-hooks] dsh 会话重命名异常: ${String(err?.message ?? err)}`);
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
5947
6024
|
function renderText$3(value) {
|
|
5948
6025
|
return [{
|
|
5949
6026
|
type: "text",
|
|
@@ -6102,6 +6179,7 @@ function createSessionTool(ctx) {
|
|
|
6102
6179
|
dryRun: args.dryRun ?? false
|
|
6103
6180
|
});
|
|
6104
6181
|
let message = result.message;
|
|
6182
|
+
if (!(args.dryRun ?? false)) renameDshSessionForActive(ctx, exec, activeInfoFromCreate(result));
|
|
6105
6183
|
if (!(args.dryRun ?? false) && cccHooks.includes("create-transform")) try {
|
|
6106
6184
|
const hookResult = await runMsmAsync(root, {
|
|
6107
6185
|
action: "exec",
|
|
@@ -6119,23 +6197,8 @@ function createSessionTool(ctx) {
|
|
|
6119
6197
|
if (!args.name) throw new Error("use requires name (S### or directory name)");
|
|
6120
6198
|
const scope = agentScope$2(exec);
|
|
6121
6199
|
const active = useSession(root, args.name, scope);
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
if (info) {
|
|
6125
|
-
const titles = ctx.get?.("sessionTitle");
|
|
6126
|
-
const dshSession = exec.agent?.session;
|
|
6127
|
-
if (dshSession) {
|
|
6128
|
-
const result = renameDshSessionOnUse({
|
|
6129
|
-
namingEnabled: readSimpleSettings().namingEnabled,
|
|
6130
|
-
sessionTitleAvailable: true
|
|
6131
|
-
}, dshSession, titles, info);
|
|
6132
|
-
if (result.ok) console.log(`[serenity-hooks] dsh 会话已重命名: ${String(dshSession.id ?? "?")} → ${result.title}`);
|
|
6133
|
-
else console.warn(`[serenity-hooks] dsh 会话重命名未执行: ${result.reason}`);
|
|
6134
|
-
} else console.warn(`[serenity-hooks] dsh 会话重命名未执行: 缺少 agent session`);
|
|
6135
|
-
}
|
|
6136
|
-
} catch (err) {
|
|
6137
|
-
console.warn(`[serenity-hooks] dsh 会话重命名异常: ${String(err?.message ?? err)}`);
|
|
6138
|
-
}
|
|
6200
|
+
const info = getActiveSessionInfo(scope);
|
|
6201
|
+
if (info) renameDshSessionForActive(ctx, exec, info);
|
|
6139
6202
|
return active;
|
|
6140
6203
|
}
|
|
6141
6204
|
case "close":
|
|
@@ -6806,7 +6869,9 @@ dsp 只提供机制(双白名单强制 + 基础提示词 + 调试问答页)
|
|
|
6806
6869
|
"msms": ["msm-a", "msm-b"], // MSM 白名单(独立):acc_msm exec 只能跑这些;register/deregister 必拒;list 只显示这些
|
|
6807
6870
|
"tools": ["read","grep","glob",...], // 非 MSM 工具白名单(独立):白名单外工具一律不可用(guard 强制)
|
|
6808
6871
|
"trajectory": { "session": false, "keeper": false, "rebuild": false }, // 轨迹纪律子集(缺省全关 = 完全独立)
|
|
6809
|
-
|
|
6872
|
+
// 角色人格/认知边界/风格(CCC 完整定义;dsp 只给基础提示词)
|
|
6873
|
+
"systemPromptFile": ".opencode/skiff/<role>.md" // ★ 推荐:引用 md 文件(相对 CCC 根;超长提示词在 JSON 内嵌不可读)
|
|
6874
|
+
// "systemPrompt": "..." // 兼容:内嵌字符串仍可用;两者都设时 systemPromptFile 优先
|
|
6810
6875
|
}
|
|
6811
6876
|
} } }
|
|
6812
6877
|
|
|
@@ -6837,6 +6902,8 @@ dsp 只提供机制(双白名单强制 + 基础提示词 + 调试问答页)
|
|
|
6837
6902
|
启停 = 人工(设置面板开关,不随插件加载自动启动);未开启零资源占用
|
|
6838
6903
|
生效机制:角色配置**实时读取**(改 .opencode/serenity.json → 刷新调试页即生效);
|
|
6839
6904
|
改配置后用 skiff_admin apply 做显式校验 + 应用确认(绑定 CCC + 角色清单)
|
|
6905
|
+
会话追问(v1.25.10):调试页持有当前会话,追问自动续接(同会话上下文延续);
|
|
6906
|
+
「新对话」按钮开新会话;进程重启后旧会话不可续(WebUI 仍可见历史)
|
|
6840
6907
|
注意:skill 加载对 skiff **恒可用**(不设白名单——读知识面,无写能力)`;
|
|
6841
6908
|
/** 校验当前 CCC 的 skiff 配置:roles schema 合法 / msms 均已注册 / model ∈ handyman.models / systemPrompt 非空 */
|
|
6842
6909
|
function validateSkiffConfig(root) {
|
|
@@ -6852,7 +6919,11 @@ function validateSkiffConfig(root) {
|
|
|
6852
6919
|
const hc = readHandymanConfig(root);
|
|
6853
6920
|
const allowedModels = hc ? new Set(hc.models) : null;
|
|
6854
6921
|
for (const [name, role] of roles) {
|
|
6855
|
-
|
|
6922
|
+
try {
|
|
6923
|
+
if (!resolveRoleSystemPrompt(root, role).trim()) issues.push(`role "${name}": system prompt is empty (define systemPromptFile (recommended) or systemPrompt)`);
|
|
6924
|
+
} catch (err) {
|
|
6925
|
+
issues.push(`role "${name}": ${String(err?.message ?? err)}`);
|
|
6926
|
+
}
|
|
6856
6927
|
for (const m of role.msms ?? []) if (!registered.has(m)) issues.push(`role "${name}": msms "${m}" is not registered in mech-registry.json`);
|
|
6857
6928
|
if (role.model && allowedModels && !allowedModels.has(role.model)) issues.push(`role "${name}": model "${role.model}" not in handyman.models (${[...allowedModels].join(", ") || "(none)"})`);
|
|
6858
6929
|
}
|
|
@@ -6879,7 +6950,8 @@ function listSkiffRoles(root) {
|
|
|
6879
6950
|
keeper: role.trajectory?.keeper === true,
|
|
6880
6951
|
rebuild: role.trajectory?.rebuild === true
|
|
6881
6952
|
},
|
|
6882
|
-
|
|
6953
|
+
promptSource: systemPromptSource(role),
|
|
6954
|
+
...role.systemPromptFile ? { systemPromptFile: role.systemPromptFile } : {}
|
|
6883
6955
|
}));
|
|
6884
6956
|
return {
|
|
6885
6957
|
roles: items,
|
|
@@ -6914,7 +6986,7 @@ function applySkiffConfig(root) {
|
|
|
6914
6986
|
}
|
|
6915
6987
|
const skiffAdminTool = defineTool({
|
|
6916
6988
|
name: "skiff_admin",
|
|
6917
|
-
description: "Skiff (F4, experimental): CCC cognitive-subset roles (subsets of the full serenity trajectory). guide: definition tutorial (concept/role schema/cognitive MSM writing/dual whitelist/trajectory subset/examples); validate: check the CCC skiff config (roles schema legal / msms registered / model in handyman.models / systemPrompt non-empty); apply: validate then confirm the config is live (bound CCC + role list; roles are read live on every request); list: role summary (name/model/msms/tools/trajectory). Roles are defined by the CCC in .opencode/serenity.json skiff.roles; skill loading is always available to skiff sessions (not whitelisted).",
|
|
6989
|
+
description: "Skiff (F4, experimental): CCC cognitive-subset roles (subsets of the full serenity trajectory). guide: definition tutorial (concept/role schema/cognitive MSM writing/dual whitelist/trajectory subset/examples); validate: check the CCC skiff config (roles schema legal / msms registered / model in handyman.models / system prompt resolvable — systemPromptFile (recommended) or systemPrompt non-empty); apply: validate then confirm the config is live (bound CCC + role list; roles are read live on every request); list: role summary (name/model/msms/tools/trajectory/prompt source). Roles are defined by the CCC in .opencode/serenity.json skiff.roles; skill loading is always available to skiff sessions (not whitelisted).",
|
|
6918
6990
|
parameters: { action: {
|
|
6919
6991
|
type: "string",
|
|
6920
6992
|
enum: [
|
|
@@ -11084,6 +11156,13 @@ function skiffDebugPage(cccs, defaultRoot, webPort) {
|
|
|
11084
11156
|
.sub { opacity: .65; font-size: 13px; margin-bottom: 16px; }
|
|
11085
11157
|
.ccc { display: inline-block; padding: 3px 10px; border-radius: 999px; background: rgba(11,168,117,.12); color: #0ba875; font-size: 12px; font-weight: 600; margin-bottom: 12px; word-break: break-all; }
|
|
11086
11158
|
@media (prefers-color-scheme: dark) { .ccc { color: #3ddc9a; } }
|
|
11159
|
+
.sessionbar { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
|
|
11160
|
+
.session-badge { font-family: ui-monospace, SFMono-Regular, monospace; font-size: 12px; padding: 3px 10px; border-radius: 999px; background: rgba(127,127,127,.12); word-break: break-all; }
|
|
11161
|
+
.session-badge.continued { background: rgba(11,168,117,.12); color: #0ba875; }
|
|
11162
|
+
@media (prefers-color-scheme: dark) { .session-badge.continued { color: #3ddc9a; } }
|
|
11163
|
+
button.ghost { background: transparent; color: #57606a; border: 1px solid #d0d7de; padding: 4px 12px; font-size: 12px; border-radius: 8px; cursor: pointer; margin-top: 0; }
|
|
11164
|
+
@media (prefers-color-scheme: dark) { button.ghost { color: #8b949e; border-color: #3a3d42; } }
|
|
11165
|
+
button.ghost:hover { background: rgba(127,127,127,.1); }
|
|
11087
11166
|
label { font-size: 13px; font-weight: 600; display: block; margin: 12px 0 4px; }
|
|
11088
11167
|
select, textarea { width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px; border: 1px solid #d0d7de; background: #fff; color: inherit; font-size: 14px; }
|
|
11089
11168
|
@media (prefers-color-scheme: dark) { select, textarea { background: #26282c; border-color: #3a3d42; } }
|
|
@@ -11120,14 +11199,18 @@ function skiffDebugPage(cccs, defaultRoot, webPort) {
|
|
|
11120
11199
|
<body>
|
|
11121
11200
|
<main>
|
|
11122
11201
|
<h1>Skiff Debug</h1>
|
|
11123
|
-
<div class="sub">宁静号 trajectory 子集角色问答页(v1.25.
|
|
11202
|
+
<div class="sub">宁静号 trajectory 子集角色问答页(v1.25.10 实验性)— 多 CCC 可切换,同会话可追问(新对话按钮开新会话),回答 marked 服务端渲染 + think 折叠</div>
|
|
11124
11203
|
<div class="ccc" id="cccBadge"></div>
|
|
11204
|
+
<div class="sessionbar">
|
|
11205
|
+
<span id="sessionBadge" class="session-badge muted">(新会话)</span>
|
|
11206
|
+
<button id="newChat" type="button" class="ghost">新对话</button>
|
|
11207
|
+
</div>
|
|
11125
11208
|
<label for="ccc">认知容器</label>
|
|
11126
11209
|
<select id="ccc"></select>
|
|
11127
11210
|
<label for="role">角色</label>
|
|
11128
11211
|
<select id="role"></select>
|
|
11129
11212
|
<label for="q">问题</label>
|
|
11130
|
-
<textarea id="q" placeholder="
|
|
11213
|
+
<textarea id="q" placeholder="向该角色提问…(追问直接继续输入)"></textarea>
|
|
11131
11214
|
<button id="ask">提问</button>
|
|
11132
11215
|
<div id="answer" class="muted">等待提问…</div>
|
|
11133
11216
|
<div id="trajectory"></div>
|
|
@@ -11140,10 +11223,26 @@ const defaultRoot = document.getElementById('skiff-data').dataset.default
|
|
|
11140
11223
|
const cccSel = document.getElementById('ccc')
|
|
11141
11224
|
const roleSel = document.getElementById('role')
|
|
11142
11225
|
const badge = document.getElementById('cccBadge')
|
|
11226
|
+
const sessionBadge = document.getElementById('sessionBadge')
|
|
11227
|
+
const newChatBtn = document.getElementById('newChat')
|
|
11143
11228
|
const btn = document.getElementById('ask')
|
|
11144
11229
|
const answer = document.getElementById('answer')
|
|
11145
11230
|
const traj = document.getElementById('trajectory')
|
|
11146
11231
|
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))
|
|
11232
|
+
let sessionId = ''
|
|
11233
|
+
function shortId(id) { return id.length > 24 ? id.slice(0, 18) + '…' + id.slice(-6) : id }
|
|
11234
|
+
function renderSessionBadge() {
|
|
11235
|
+
sessionBadge.textContent = sessionId ? '会话 ' + shortId(sessionId) + '(追问续接)' : '(新会话)'
|
|
11236
|
+
sessionBadge.className = 'session-badge' + (sessionId ? ' continued' : '')
|
|
11237
|
+
}
|
|
11238
|
+
function newConversation() {
|
|
11239
|
+
sessionId = ''
|
|
11240
|
+
renderSessionBadge()
|
|
11241
|
+
answer.className = 'muted'
|
|
11242
|
+
answer.textContent = '已开启新对话'
|
|
11243
|
+
traj.innerHTML = ''
|
|
11244
|
+
document.getElementById('q').value = ''
|
|
11245
|
+
}
|
|
11147
11246
|
function currentCcc() {
|
|
11148
11247
|
return CCCS.find((c) => c.root === cccSel.value) || (CCCS[0] || { root: '', name: '', roles: [] })
|
|
11149
11248
|
}
|
|
@@ -11162,8 +11261,11 @@ function fillRoles() {
|
|
|
11162
11261
|
? c.roles.map((r) => '<option value="' + esc(r) + '">' + esc(r) + '</option>').join('')
|
|
11163
11262
|
: '<option value="">(未配置角色)</option>'
|
|
11164
11263
|
}
|
|
11165
|
-
cccSel.addEventListener('change', fillRoles)
|
|
11264
|
+
cccSel.addEventListener('change', () => { fillRoles(); newConversation() })
|
|
11265
|
+
roleSel.addEventListener('change', () => newConversation())
|
|
11266
|
+
newChatBtn.addEventListener('click', newConversation)
|
|
11166
11267
|
fillCccs()
|
|
11268
|
+
renderSessionBadge()
|
|
11167
11269
|
btn.addEventListener('click', async () => {
|
|
11168
11270
|
const c = currentCcc()
|
|
11169
11271
|
const role = roleSel.value
|
|
@@ -11174,11 +11276,18 @@ btn.addEventListener('click', async () => {
|
|
|
11174
11276
|
answer.textContent = '运行中…'
|
|
11175
11277
|
traj.innerHTML = ''
|
|
11176
11278
|
try {
|
|
11177
|
-
const res = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ccc: c.root, role, question: q }) })
|
|
11279
|
+
const res = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ccc: c.root, role, question: q, sessionId: sessionId || undefined }) })
|
|
11178
11280
|
const data = await res.json()
|
|
11179
|
-
if (!res.ok)
|
|
11281
|
+
if (!res.ok) {
|
|
11282
|
+
// 会话不可恢复/绑定不匹配 → 前端清空 sessionId 重建(用户拍板:切换即新对话)
|
|
11283
|
+
sessionId = ''
|
|
11284
|
+
renderSessionBadge()
|
|
11285
|
+
throw new Error(data.error || ('HTTP ' + res.status))
|
|
11286
|
+
}
|
|
11180
11287
|
answer.className = ''
|
|
11181
11288
|
answer.innerHTML = data.answer_html || esc(data.answer || '') || '(空回答)'
|
|
11289
|
+
sessionId = data.sessionId || sessionId
|
|
11290
|
+
renderSessionBadge()
|
|
11182
11291
|
renderTrajectory(data.trajectory || [], data.sessionId || '')
|
|
11183
11292
|
} catch (err) {
|
|
11184
11293
|
answer.className = 'err'
|
|
@@ -11288,6 +11397,7 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
|
|
|
11288
11397
|
const ccc = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : defaultRoot;
|
|
11289
11398
|
const roleName = typeof body.role === "string" ? body.role : "";
|
|
11290
11399
|
const question = typeof body.question === "string" ? body.question : "";
|
|
11400
|
+
const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
|
|
11291
11401
|
const role = readSkiffRoles(ccc).get(roleName);
|
|
11292
11402
|
if (!roleName || !role) {
|
|
11293
11403
|
sendJson(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
|
|
@@ -11297,11 +11407,30 @@ async function handle(ctx, defaultRoot, webPort, req, res) {
|
|
|
11297
11407
|
sendJson(res, 400, { error: "empty question" });
|
|
11298
11408
|
return;
|
|
11299
11409
|
}
|
|
11300
|
-
|
|
11410
|
+
let agent;
|
|
11411
|
+
let continued = false;
|
|
11412
|
+
if (sessionId) {
|
|
11413
|
+
const info = skiffSessionInfo(sessionId);
|
|
11414
|
+
const live = getSkiffAgent(sessionId);
|
|
11415
|
+
if (!info || !live) {
|
|
11416
|
+
sendJson(res, 400, { error: "session is not recoverable (process restarted or session unknown) — start a new conversation" });
|
|
11417
|
+
return;
|
|
11418
|
+
}
|
|
11419
|
+
if (info.role !== roleName || info.ccc !== ccc) {
|
|
11420
|
+
sendJson(res, 400, { error: `session "${sessionId}" belongs to role "${info.role}" in another CCC — start a new conversation` });
|
|
11421
|
+
return;
|
|
11422
|
+
}
|
|
11423
|
+
agent = live;
|
|
11424
|
+
continued = true;
|
|
11425
|
+
}
|
|
11426
|
+
const hc = readHandymanConfig(ccc);
|
|
11427
|
+
if (!agent) agent = (await createSkiffAgent(ctx, ccc, roleName, role, hc?.defaultModel)).agent;
|
|
11428
|
+
const result = await askSkiff(ctx, agent, question, 0);
|
|
11301
11429
|
sendJson(res, 200, {
|
|
11302
11430
|
answer: result.answer,
|
|
11303
11431
|
answer_html: renderSkiffMarkdown(result.answer),
|
|
11304
11432
|
sessionId: result.sessionId,
|
|
11433
|
+
continued,
|
|
11305
11434
|
trajectory: result.trajectory
|
|
11306
11435
|
});
|
|
11307
11436
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.11",
|
|
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": {
|