@shgroup/dsh-serenity-hooks 1.17.5 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dsh.plugin.json +1 -1
- package/lib/index.js +254 -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.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/loop + 拦截缝机械约束(safe-mode/路径守卫/会话落盘)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。私有(dsh-external 组织)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -3504,6 +3504,257 @@ const localstoreTool = defineTool({
|
|
|
3504
3504
|
}
|
|
3505
3505
|
});
|
|
3506
3506
|
//#endregion
|
|
3507
|
+
//#region src/seams/bootstrap.ts
|
|
3508
|
+
/** 默认首请求工具集:dsp 平台 Minimal 等价核心(anchored 是 bash+str_replace_editor) */
|
|
3509
|
+
const DEFAULT_BOOTSTRAP_TOOLS = [
|
|
3510
|
+
"read",
|
|
3511
|
+
"write",
|
|
3512
|
+
"edit",
|
|
3513
|
+
"glob",
|
|
3514
|
+
"grep"
|
|
3515
|
+
];
|
|
3516
|
+
/** 默认剥离的自动注入源(anchored 默认:可用技能目录提醒 + 工作区指令摘要) */
|
|
3517
|
+
const DEFAULT_SUPPRESSED_SOURCES = ["skill-catalog", "agent-instructions"];
|
|
3518
|
+
/** 默认 compaction 恢复工作集(anchored 默认 read/write/edit/glob/grep/todo_write/ask_user_question) */
|
|
3519
|
+
const DEFAULT_COMPACTION_TOOLS = [
|
|
3520
|
+
"read",
|
|
3521
|
+
"write",
|
|
3522
|
+
"edit",
|
|
3523
|
+
"glob",
|
|
3524
|
+
"grep",
|
|
3525
|
+
"todo_write"
|
|
3526
|
+
];
|
|
3527
|
+
/** 默认首轮锚定问题(用户指定:介绍宁静号,200 字以内) */
|
|
3528
|
+
const DEFAULT_ANCHOR_MESSAGE = "请介绍当前宁静号,它是什么,为了什么,200字以内回答";
|
|
3529
|
+
/** 构建一个 epoch 感知晋升跟踪器(纯逻辑,可单测) */
|
|
3530
|
+
function createEpochPromotion(promoteEvents) {
|
|
3531
|
+
/** sessionId -> { boundary, promoted } */
|
|
3532
|
+
const state = /* @__PURE__ */ new Map();
|
|
3533
|
+
const sessionIdOf = (session) => {
|
|
3534
|
+
if (session && typeof session === "object") {
|
|
3535
|
+
const id = session.id;
|
|
3536
|
+
if (typeof id === "string") return id;
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
const scan = (session) => {
|
|
3540
|
+
let boundary = -1;
|
|
3541
|
+
let promoted = false;
|
|
3542
|
+
const events = session?.events;
|
|
3543
|
+
if (Array.isArray(events)) for (const event of events) {
|
|
3544
|
+
const e = event;
|
|
3545
|
+
const seq = typeof e.seq === "number" ? e.seq : 0;
|
|
3546
|
+
if (e.type === "compaction/end") {
|
|
3547
|
+
boundary = seq;
|
|
3548
|
+
promoted = false;
|
|
3549
|
+
continue;
|
|
3550
|
+
}
|
|
3551
|
+
if (promoteEvents.has(e.type) && seq > boundary) promoted = true;
|
|
3552
|
+
}
|
|
3553
|
+
const entry = {
|
|
3554
|
+
boundary,
|
|
3555
|
+
promoted
|
|
3556
|
+
};
|
|
3557
|
+
const sid = sessionIdOf(session);
|
|
3558
|
+
if (sid) state.set(sid, entry);
|
|
3559
|
+
return entry;
|
|
3560
|
+
};
|
|
3561
|
+
return {
|
|
3562
|
+
status(agent) {
|
|
3563
|
+
if (agent === void 0) return {
|
|
3564
|
+
boundary: -1,
|
|
3565
|
+
promoted: true
|
|
3566
|
+
};
|
|
3567
|
+
const session = agent.session;
|
|
3568
|
+
if (session === void 0) return {
|
|
3569
|
+
boundary: -1,
|
|
3570
|
+
promoted: true
|
|
3571
|
+
};
|
|
3572
|
+
if ((session.header?.delegationDepth ?? 0) > 0) return {
|
|
3573
|
+
boundary: -1,
|
|
3574
|
+
promoted: true
|
|
3575
|
+
};
|
|
3576
|
+
const sid = sessionIdOf(session);
|
|
3577
|
+
if (sid === void 0) return {
|
|
3578
|
+
boundary: -1,
|
|
3579
|
+
promoted: true
|
|
3580
|
+
};
|
|
3581
|
+
return state.get(sid) ?? scan(session);
|
|
3582
|
+
},
|
|
3583
|
+
observe(session, event) {
|
|
3584
|
+
const sid = sessionIdOf(session);
|
|
3585
|
+
if (sid === void 0) return;
|
|
3586
|
+
const entry = state.get(sid);
|
|
3587
|
+
if (entry === void 0) return;
|
|
3588
|
+
const e = event;
|
|
3589
|
+
const seq = typeof e.seq === "number" ? e.seq : 0;
|
|
3590
|
+
if (e.type === "compaction/end") {
|
|
3591
|
+
state.set(sid, {
|
|
3592
|
+
boundary: seq,
|
|
3593
|
+
promoted: false
|
|
3594
|
+
});
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (promoteEvents.has(e.type) && seq > entry.boundary && !entry.promoted) state.set(sid, {
|
|
3598
|
+
...entry,
|
|
3599
|
+
promoted: true
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const PROMOTE_EVENTS = {
|
|
3605
|
+
"tool-call": /* @__PURE__ */ new Set(["tool/call"]),
|
|
3606
|
+
"assistant-message": /* @__PURE__ */ new Set(["assistant/message"]),
|
|
3607
|
+
either: /* @__PURE__ */ new Set(["tool/call", "assistant/message"])
|
|
3608
|
+
};
|
|
3609
|
+
function stringList(value, field, fallback) {
|
|
3610
|
+
if (value === void 0) return [...fallback];
|
|
3611
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new TypeError(`bootstrap: ${field} must be a non-empty array of non-empty strings`);
|
|
3612
|
+
return [...new Set(value)];
|
|
3613
|
+
}
|
|
3614
|
+
function sourceList(value, field, fallback) {
|
|
3615
|
+
if (value === void 0) return new Set(fallback);
|
|
3616
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0)) throw new TypeError(`bootstrap: ${field} must be an array of non-empty strings`);
|
|
3617
|
+
return new Set(value);
|
|
3618
|
+
}
|
|
3619
|
+
function resolveBootstrapSettings(opts) {
|
|
3620
|
+
const promoteOn = opts.promoteOn ?? "either";
|
|
3621
|
+
if (promoteOn !== "either" && promoteOn !== "tool-call" && promoteOn !== "assistant-message") throw new TypeError(`bootstrap: promoteOn must be one of "tool-call", "assistant-message", "either"; got ${JSON.stringify(promoteOn)}`);
|
|
3622
|
+
return {
|
|
3623
|
+
bootstrapTools: stringList(opts.bootstrapTools, "bootstrapTools", DEFAULT_BOOTSTRAP_TOOLS),
|
|
3624
|
+
promoteEvents: PROMOTE_EVENTS[promoteOn],
|
|
3625
|
+
suppressedSources: sourceList(opts.suppressedContextSources, "suppressedContextSources", DEFAULT_SUPPRESSED_SOURCES),
|
|
3626
|
+
compactionTools: stringList(opts.compactionTools, "compactionTools", DEFAULT_COMPACTION_TOOLS),
|
|
3627
|
+
anchorMessage: typeof opts.anchorMessage === "string" && opts.anchorMessage.length > 0 ? opts.anchorMessage : DEFAULT_ANCHOR_MESSAGE
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
/**
|
|
3631
|
+
* 注册 Anchored bootstrap 机制(按 agent cwd 动态解析 CCC 配置——多 CCC 架构,与 guards 同模式):
|
|
3632
|
+
* 1. session/event 观察晋升信号(tool/call + assistant/message + compaction/end)
|
|
3633
|
+
* 2. system-prompt/assemble:bootstrap 阶段目录窄化到 bootstrapTools(+compaction 后 compactionTools)
|
|
3634
|
+
* 3. agent/pre-step:bootstrap 阶段剥离 suppressedSources 注入消息(降级保留全部)
|
|
3635
|
+
*
|
|
3636
|
+
* CCC 的 serenity.json 配置 `bootstrap.enabled: true` 才生效(否则零影响);
|
|
3637
|
+
* 摘除 = 删除 index.ts 中本注册行(独立模块)。
|
|
3638
|
+
*/
|
|
3639
|
+
/** 从 agent 解析 CCC 根(宽松;无 agent/无 CCC 返回 null) */
|
|
3640
|
+
function agentRoot(agent) {
|
|
3641
|
+
const cwd = agent?.session?.header?.cwd;
|
|
3642
|
+
if (typeof cwd !== "string") return null;
|
|
3643
|
+
return findSerenityRoot(cwd);
|
|
3644
|
+
}
|
|
3645
|
+
/** 按 agent cwd 解析 bootstrap 配置;未开启返回 null */
|
|
3646
|
+
function readBootstrapConfig(agent) {
|
|
3647
|
+
const root = agentRoot(agent);
|
|
3648
|
+
if (!root) return null;
|
|
3649
|
+
const b = loadSerenityConfig(root).bootstrap;
|
|
3650
|
+
if (!b?.enabled) return null;
|
|
3651
|
+
return resolveBootstrapSettings({
|
|
3652
|
+
bootstrapTools: b.bootstrapTools,
|
|
3653
|
+
promoteOn: b.promoteOn,
|
|
3654
|
+
suppressedContextSources: b.suppressedContextSources,
|
|
3655
|
+
compactionTools: b.compactionTools,
|
|
3656
|
+
anchorMessage: b.anchorMessage
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3659
|
+
function registerBootstrap(ctx) {
|
|
3660
|
+
const settingsByRoot = /* @__PURE__ */ new Map();
|
|
3661
|
+
const tracker = createEpochPromotion(/* @__PURE__ */ new Set(["tool/call", "assistant/message"]));
|
|
3662
|
+
ctx.on("session/event", (session, event) => {
|
|
3663
|
+
try {
|
|
3664
|
+
tracker.observe(session, event);
|
|
3665
|
+
} catch {}
|
|
3666
|
+
});
|
|
3667
|
+
ctx.on("agent/inbox/inserted", ({ agent, message }) => {
|
|
3668
|
+
try {
|
|
3669
|
+
const root = agentRoot(agent);
|
|
3670
|
+
if (!root) return;
|
|
3671
|
+
const settings = settingsByRoot.get(root) ?? readBootstrapConfig(agent);
|
|
3672
|
+
if (!settings) return;
|
|
3673
|
+
const session = agent.session;
|
|
3674
|
+
if ((session?.header?.delegationDepth ?? 0) > 0) return;
|
|
3675
|
+
if (session?.events?.some((event) => event.type === "user/message")) return;
|
|
3676
|
+
if (message?.source?.kind === "plugin") return;
|
|
3677
|
+
const inbox = agent.inbox;
|
|
3678
|
+
if (!inbox?.prepend) return;
|
|
3679
|
+
inbox.prepend("next-turn", {
|
|
3680
|
+
id: `bootstrap-anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
3681
|
+
role: "user",
|
|
3682
|
+
content: [{
|
|
3683
|
+
type: "text",
|
|
3684
|
+
text: settings.anchorMessage
|
|
3685
|
+
}],
|
|
3686
|
+
source: {
|
|
3687
|
+
kind: "plugin",
|
|
3688
|
+
plugin: "dsh-serenity-hooks",
|
|
3689
|
+
form: "notice",
|
|
3690
|
+
summary: "bootstrap anchor turn"
|
|
3691
|
+
}
|
|
3692
|
+
});
|
|
3693
|
+
warnOnce(`anchor turn injected: "${settings.anchorMessage.slice(0, 40)}…"`);
|
|
3694
|
+
} catch {}
|
|
3695
|
+
});
|
|
3696
|
+
let warned = false;
|
|
3697
|
+
const warnOnce = (message) => {
|
|
3698
|
+
if (warned) return;
|
|
3699
|
+
warned = true;
|
|
3700
|
+
try {
|
|
3701
|
+
console.warn(`[serenity-hooks] bootstrap: ${message}`);
|
|
3702
|
+
} catch {}
|
|
3703
|
+
};
|
|
3704
|
+
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
3705
|
+
const assembled = await next();
|
|
3706
|
+
try {
|
|
3707
|
+
const agent = context.agent;
|
|
3708
|
+
const root = agentRoot(agent);
|
|
3709
|
+
if (!root) return assembled;
|
|
3710
|
+
const settings = settingsByRoot.get(root) ?? readBootstrapConfig(agent);
|
|
3711
|
+
if (!settings) return assembled;
|
|
3712
|
+
settingsByRoot.set(root, settings);
|
|
3713
|
+
const status = tracker.status(agent);
|
|
3714
|
+
if (status.promoted) return assembled;
|
|
3715
|
+
const keep = new Set(settings.bootstrapTools);
|
|
3716
|
+
if (status.boundary >= 0) for (const toolName of settings.compactionTools) keep.add(toolName);
|
|
3717
|
+
const tools = assembled.tools;
|
|
3718
|
+
if (!Array.isArray(tools)) return assembled;
|
|
3719
|
+
const available = new Set(tools.map((tool) => tool.name).filter((n) => typeof n === "string"));
|
|
3720
|
+
const missing = [...keep].filter((name) => !available.has(name));
|
|
3721
|
+
if (missing.length > 0) {
|
|
3722
|
+
warnOnce(`expected bootstrap tools missing=${JSON.stringify(missing)} — bootstrap disabled, full catalog exposed`);
|
|
3723
|
+
return assembled;
|
|
3724
|
+
}
|
|
3725
|
+
return {
|
|
3726
|
+
...assembled,
|
|
3727
|
+
tools: tools.filter((tool) => typeof tool.name === "string" && keep.has(tool.name))
|
|
3728
|
+
};
|
|
3729
|
+
} catch (error) {
|
|
3730
|
+
warnOnce(`assemble filter failed, exposing the full catalog: ${String(error?.message ?? error)}`);
|
|
3731
|
+
return assembled;
|
|
3732
|
+
}
|
|
3733
|
+
});
|
|
3734
|
+
ctx.on("agent/pre-step", async ({ agent }, next) => {
|
|
3735
|
+
const decision = await next();
|
|
3736
|
+
if (decision.kind === "reject") return decision;
|
|
3737
|
+
try {
|
|
3738
|
+
const settings = readBootstrapConfig(agent);
|
|
3739
|
+
if (!settings) return decision;
|
|
3740
|
+
if (tracker.status(agent).promoted || settings.suppressedSources.size === 0) return decision;
|
|
3741
|
+
const messages = decision.messages;
|
|
3742
|
+
if (!Array.isArray(messages)) return decision;
|
|
3743
|
+
const kept = messages.filter((message) => {
|
|
3744
|
+
const kind = message?.source?.kind;
|
|
3745
|
+
return typeof kind !== "string" || !settings.suppressedSources.has(kind);
|
|
3746
|
+
});
|
|
3747
|
+
return kept.length === messages.length ? decision : {
|
|
3748
|
+
...decision,
|
|
3749
|
+
messages: kept
|
|
3750
|
+
};
|
|
3751
|
+
} catch (error) {
|
|
3752
|
+
warnOnce(`pre-step context filter failed, keeping injected context: ${String(error?.message ?? error)}`);
|
|
3753
|
+
return decision;
|
|
3754
|
+
}
|
|
3755
|
+
}, { prepend: true });
|
|
3756
|
+
}
|
|
3757
|
+
//#endregion
|
|
3507
3758
|
//#region src/seams/loop.ts
|
|
3508
3759
|
/** 解析当前 dsh 会话 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
|
|
3509
3760
|
function resolveActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
@@ -4435,7 +4686,8 @@ const Config = z.object({
|
|
|
4435
4686
|
api: z.boolean().default(true),
|
|
4436
4687
|
entrySkillMaxChars: z.number().default(3e4),
|
|
4437
4688
|
env: z.boolean().default(true),
|
|
4438
|
-
opencodeSkills: z.boolean().default(true)
|
|
4689
|
+
opencodeSkills: z.boolean().default(true),
|
|
4690
|
+
bootstrap: z.boolean().default(false)
|
|
4439
4691
|
});
|
|
4440
4692
|
function apply(ctx, config) {
|
|
4441
4693
|
if (config.tools) {
|
|
@@ -4468,6 +4720,7 @@ function apply(ctx, config) {
|
|
|
4468
4720
|
if (config.api) registerStatusApi(ctx, { configPaths: config.serenityConfigPaths });
|
|
4469
4721
|
if (config.env) registerEnv(ctx);
|
|
4470
4722
|
if (config.opencodeSkills) registerOpencodeSkills(ctx);
|
|
4723
|
+
if (config.bootstrap) registerBootstrap(ctx);
|
|
4471
4724
|
}
|
|
4472
4725
|
//#endregion
|
|
4473
4726
|
export { Config, apply, inject, name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
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": {
|