@shgroup/dsh-serenity-hooks 1.28.0 → 1.28.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 +12 -37
- package/lib/config-ops.d.ts +13 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +74 -62
- package/lib/seams/bootstrap.d.ts +9 -0
- package/lib/session-ops.d.ts +11 -0
- package/lib/settings-section-BfVxgCZy.js +91 -0
- package/lib/settings-section.d.ts +5 -1
- package/lib/{skiff-debug-DIf_Uenr.js → skiff-debug-CBU6T2F_.js} +14 -3
- package/package.json +18 -16
- package/lib/settings-section-DnX9TJxt.js +0 -2124
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.1",
|
|
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/client.js
CHANGED
|
@@ -3206,44 +3206,18 @@ window.__ModuleLoader__.load({
|
|
|
3206
3206
|
return null;
|
|
3207
3207
|
}
|
|
3208
3208
|
const CONFIG_PATH = "/serenity/config";
|
|
3209
|
-
/**
|
|
3210
|
-
* 获取已有工作区列表(需求 1:白名单从已有工作区选择而非手输)。
|
|
3211
|
-
* 走 DSH 原生 RPC `POST /api/workspace.list`(JSON RPC 形态)。
|
|
3212
|
-
* 信封必须是完整 ClientRequest:`{ type: 'client-request', rpcId, method, payload }`
|
|
3213
|
-
* (api/rpc.ts wire 契约——缺 type/method → clientRequestSchema 校验失败 → bad-request)。
|
|
3214
|
-
* 返回 { path, title }[];失败返回空数组(面板显示"暂无可选工作区")。
|
|
3215
|
-
*/
|
|
3216
|
-
async function fetchWorkspaces() {
|
|
3217
|
-
try {
|
|
3218
|
-
const res = await fetch("/api/workspace.list", {
|
|
3219
|
-
method: "POST",
|
|
3220
|
-
headers: { "content-type": "application/json" },
|
|
3221
|
-
body: JSON.stringify({
|
|
3222
|
-
type: "client-request",
|
|
3223
|
-
rpcId: `ws-${Date.now().toString(36)}`,
|
|
3224
|
-
method: "workspace.list",
|
|
3225
|
-
payload: {}
|
|
3226
|
-
})
|
|
3227
|
-
});
|
|
3228
|
-
if (!res.ok) return [];
|
|
3229
|
-
const items = (await res.json())?.result?.value?.items;
|
|
3230
|
-
if (!Array.isArray(items)) return [];
|
|
3231
|
-
return items.filter((i) => typeof i.path === "string" && i.path !== "").map((i) => ({
|
|
3232
|
-
path: i.path,
|
|
3233
|
-
title: typeof i.title === "string" && i.title !== "" ? i.title : i.path
|
|
3234
|
-
}));
|
|
3235
|
-
} catch {
|
|
3236
|
-
return [];
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
/** GET 配置(含账号列表;无 hash) */
|
|
3209
|
+
/** GET 配置(含账号列表 + knownWorkspaces;无 hash) */
|
|
3240
3210
|
async function fetchConfig() {
|
|
3241
3211
|
const res = await fetch(CONFIG_PATH, { headers: {
|
|
3242
3212
|
accept: "application/json",
|
|
3243
3213
|
"x-serenity-ui": "1"
|
|
3244
3214
|
} });
|
|
3245
3215
|
if (!res.ok) return null;
|
|
3246
|
-
|
|
3216
|
+
const body = await res.json();
|
|
3217
|
+
return {
|
|
3218
|
+
config: body.config ?? null,
|
|
3219
|
+
knownWorkspaces: body.knownWorkspaces ?? []
|
|
3220
|
+
};
|
|
3247
3221
|
}
|
|
3248
3222
|
/** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋 + 开放容器白名单)→ 返回保存后的 wire */
|
|
3249
3223
|
async function saveConfig(patch) {
|
|
@@ -3303,9 +3277,10 @@ window.__ModuleLoader__.load({
|
|
|
3303
3277
|
const [saved, setSaved] = (0, react.useState)(false);
|
|
3304
3278
|
(0, react.useEffect)(() => {
|
|
3305
3279
|
let alive = true;
|
|
3306
|
-
|
|
3280
|
+
fetchConfig().then((resp) => {
|
|
3307
3281
|
if (!alive) return;
|
|
3308
|
-
|
|
3282
|
+
const cfg = resp?.config ?? null;
|
|
3283
|
+
setKnownWorkspaces(resp?.knownWorkspaces ?? []);
|
|
3309
3284
|
if (cfg) {
|
|
3310
3285
|
setConfig(cfg);
|
|
3311
3286
|
setHost(cfg.gateway.host);
|
|
@@ -3734,8 +3709,8 @@ Instruction-following style:
|
|
|
3734
3709
|
let alive = true;
|
|
3735
3710
|
fetchConfig().then((cfg) => {
|
|
3736
3711
|
if (!alive || !cfg) return;
|
|
3737
|
-
setMode(cfg.persona?.mode ?? "");
|
|
3738
|
-
setOverrideText(cfg.persona?.overrideText ?? "");
|
|
3712
|
+
setMode(cfg.config?.persona?.mode ?? "");
|
|
3713
|
+
setOverrideText(cfg.config?.persona?.overrideText ?? "");
|
|
3739
3714
|
setLoaded(true);
|
|
3740
3715
|
});
|
|
3741
3716
|
return () => {
|
|
@@ -3893,7 +3868,7 @@ Instruction-following style:
|
|
|
3893
3868
|
try {
|
|
3894
3869
|
const cfg = await fetchConfig();
|
|
3895
3870
|
if (!alive) return;
|
|
3896
|
-
setAllowed(cfg?.publicAsk?.allowed ?? []);
|
|
3871
|
+
setAllowed(cfg?.config?.publicAsk?.allowed ?? []);
|
|
3897
3872
|
} catch {}
|
|
3898
3873
|
})();
|
|
3899
3874
|
return () => {
|
package/lib/config-ops.d.ts
CHANGED
|
@@ -150,6 +150,19 @@ export interface AdvancedSettingsWire {
|
|
|
150
150
|
}
|
|
151
151
|
/** 持久化 → wire(剥离 passHash/totpSecret;只留布尔) */
|
|
152
152
|
export declare function toWire(settings: AdvancedSettings): AdvancedSettingsWire;
|
|
153
|
+
/**
|
|
154
|
+
* 已知工作区投影(v1.28.0 适配 0.1.2-rc.1 A2 方案 A′):
|
|
155
|
+
* rc.1 workspace.list unary 删除 → AccountsEditor 白名单下拉的数据源改走 gateway 自有
|
|
156
|
+
* /serenity/config 的 knownWorkspaces(host workspaceRegistry.list() 投影,白名单过滤)。
|
|
157
|
+
* 纯函数可单测。allowPrefixes 空 = 全部放行(向后兼容默认)。
|
|
158
|
+
*/
|
|
159
|
+
export declare function projectKnownWorkspaces(workspaces: Array<{
|
|
160
|
+
path?: string;
|
|
161
|
+
title?: string;
|
|
162
|
+
}>, allowPrefixes: readonly string[]): Array<{
|
|
163
|
+
path: string;
|
|
164
|
+
title: string;
|
|
165
|
+
}>;
|
|
153
166
|
/**
|
|
154
167
|
* wire → 持久化(面板 PUT 用):
|
|
155
168
|
* accounts 元素可选带 `pass`:非空 → 重新 hash;空/缺省 → 保留现有 hash(按 id 匹配)。
|
package/lib/index.d.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import type { Context } from 'cordis';
|
|
15
15
|
import z from '@deepseek-ai/schemastery';
|
|
16
16
|
export declare const name = "dsh-serenity-hooks";
|
|
17
|
-
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
17
|
+
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
18
|
+
* (v1.28.0 适配 0.1.2-rc.1:+ 'settings'——B4 settings 服务由 provider 插件加载后才有) */
|
|
18
19
|
export declare const inject: string[];
|
|
19
20
|
/** 插件配置(cordis.yml 提供;进程级) */
|
|
20
21
|
export interface Config {
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$2, f as readHandymanConfig, i as findGitRoot, l as pathInside, m as resolveInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-CX48YNUL.js";
|
|
3
3
|
import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-BTdBHyOQ.js";
|
|
4
|
-
import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-
|
|
4
|
+
import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-CBU6T2F_.js";
|
|
5
5
|
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-DFkRf9ou.js";
|
|
6
|
-
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-
|
|
6
|
+
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-BfVxgCZy.js";
|
|
7
7
|
import { a as markdownToPlainText, c as sniffImageExt, i as getUpdates, n as downloadMedia, o as sendTextMessage, r as getConfig, s as sendTyping, t as TypingStatus } from "./weixin-api-OUCuw0Ws.js";
|
|
8
8
|
import z from "@deepseek-ai/schemastery";
|
|
9
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -2301,7 +2301,8 @@ function waitIdle(ctx, agent) {
|
|
|
2301
2301
|
}
|
|
2302
2302
|
/** 读取会话最后一个 assistant/message 文本 */
|
|
2303
2303
|
function lastAssistantText$1(agent) {
|
|
2304
|
-
const
|
|
2304
|
+
const s = agent.session;
|
|
2305
|
+
const events = typeof s.snapshotEvents === "function" ? s.snapshotEvents() : s.events ?? [];
|
|
2305
2306
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2306
2307
|
const e = events[i];
|
|
2307
2308
|
if (e && e.type === "assistant/message") {
|
|
@@ -2567,6 +2568,24 @@ const SESSION_ACTIONS = [
|
|
|
2567
2568
|
"summary",
|
|
2568
2569
|
"hook-develop-guide"
|
|
2569
2570
|
];
|
|
2571
|
+
/**
|
|
2572
|
+
* 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
|
|
2573
|
+
* 移除 `.events` 属性 → `snapshotEvents()` 方法(dsh-session/src/session.ts:
|
|
2574
|
+
* `snapshotEvents(fromSeq, toSeqExclusive)`)。插件早期代码多处裸读 `.events`
|
|
2575
|
+
* (经 `as unknown as { events? }` 断言绕过 typecheck),运行时静默 undefined——
|
|
2576
|
+
* 造成 first-anchor 每轮重插 / SESSION 激活恢复失效 / rebuild 定位错乱。
|
|
2577
|
+
* 统一收敛到本 helper:snapshotEvents() 优先(rc.1 真实形态),`.events` 兜底
|
|
2578
|
+
* (测试替身/旧运行时)。所有消费方一律经此读取,禁止再裸读 `.events`。
|
|
2579
|
+
* 泛型 T:调用方按需声明事件形状(如 `SessionEvent`),unknown 默认。
|
|
2580
|
+
*/
|
|
2581
|
+
function sessionEvents(session) {
|
|
2582
|
+
const s = session;
|
|
2583
|
+
if (!s) return [];
|
|
2584
|
+
if (typeof s.snapshotEvents === "function") try {
|
|
2585
|
+
return s.snapshotEvents() ?? [];
|
|
2586
|
+
} catch {}
|
|
2587
|
+
return s.events ?? [];
|
|
2588
|
+
}
|
|
2570
2589
|
const SESSION_MD = "SESSION.md";
|
|
2571
2590
|
const ARCHIVE_DIR_NAME = "_archived";
|
|
2572
2591
|
const HEALTH_STALE_DAYS = 7;
|
|
@@ -3524,6 +3543,17 @@ const DEFAULT_COMPACTION_TOOLS = [
|
|
|
3524
3543
|
*/
|
|
3525
3544
|
const DEFAULT_ANCHOR_MESSAGES = ["You are the operator agent of Serenity — a cognitive container governed by the Abstract Cognitive Container (ACC) protocol.\nWe operate under the Explicit Abstraction Principle (EAP): the functional value of a thought equals its external reconstructability — every output we produce must be explicit (E↑), reconstructable (R↓), and stable (S↑).\nThe personal pronoun is us/we.\nWe anchor first, then act: the abstract layer precedes the concrete.\nPlease simply reply \"acknowledge\" — no action needed.", "Before we proceed, align on how we work:\n1. We read before we write — every decision grounds in what already exists in the container.\n2. Every output records its reasoning (R↓): decisions carry reasons and alternatives.\n3. We never jump levels — abstract layer first, then specifics.\n4. Every artifact we create is a durable cognitive anchor for the work that follows.\n5. We keep the container's state coherent (SESSION.md) as we advance.\nPlease simply reply \"acknowledge\" — no action needed."];
|
|
3526
3545
|
/**
|
|
3546
|
+
* 根会话锚定重入判定(v1.28.1 提取导出以便单测):会话是否已有真实用户消息。
|
|
3547
|
+
* resume/续跑的会话已有对话历史 → **不重锚**(first-anchor 只注入一次)。
|
|
3548
|
+
*
|
|
3549
|
+
* 0.1.2-rc.1 适配教训:裸读 `session.events`(经类型断言)恒 undefined →
|
|
3550
|
+
* `!undefined` 恒 true → 有历史也永不跳过 → 任何情况发消息都重插 first-anchor。
|
|
3551
|
+
* 统一经 sessionEvents() 读取(snapshotEvents() 优先,.events 兜底)。
|
|
3552
|
+
*/
|
|
3553
|
+
function hasUserMessageHistory(session) {
|
|
3554
|
+
return sessionEvents(session).some((event) => event.type === "user/message");
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3527
3557
|
* 构建一个 epoch 感知晋升跟踪器(纯逻辑,可单测)。
|
|
3528
3558
|
* requiredSignals:晋升所需信号数(boundary 后累计;默认 1)。
|
|
3529
3559
|
* 多轮锚定(v4):两轮递进锚定时 requiredSignals = 锚定轮数——
|
|
@@ -3552,8 +3582,8 @@ function createEpochPromotion(promoteEvents, requiredSignals = 1, maxRoundsFallb
|
|
|
3552
3582
|
let boundary = -1;
|
|
3553
3583
|
let signalCount = 0;
|
|
3554
3584
|
let rounds = 0;
|
|
3555
|
-
const events = session
|
|
3556
|
-
if (
|
|
3585
|
+
const events = sessionEvents(session);
|
|
3586
|
+
if (events.length > 0) for (const event of events) {
|
|
3557
3587
|
const e = event;
|
|
3558
3588
|
const seq = typeof e.seq === "number" ? e.seq : 0;
|
|
3559
3589
|
if (e.type === "compaction/end") {
|
|
@@ -3695,7 +3725,7 @@ function registerBootstrap(ctx) {
|
|
|
3695
3725
|
const sid = typeof session?.id === "string" ? session.id : void 0;
|
|
3696
3726
|
if (sid !== void 0 && (sid.startsWith("handyman-") || isSkiffSessionId(sid))) return;
|
|
3697
3727
|
if (depth === 0) {
|
|
3698
|
-
if (session
|
|
3728
|
+
if (hasUserMessageHistory(session)) return;
|
|
3699
3729
|
} else if (sid !== void 0 && anchoredSessions.has(sid)) return;
|
|
3700
3730
|
if (message?.source?.kind === "plugin") return;
|
|
3701
3731
|
const inbox = agent.inbox;
|
|
@@ -3834,8 +3864,8 @@ function parseAnchorMdPath(session) {
|
|
|
3834
3864
|
try {
|
|
3835
3865
|
const nodes = [...session.surface.nodes];
|
|
3836
3866
|
if (nodes.length === 0) return null;
|
|
3837
|
-
const events = session
|
|
3838
|
-
if (
|
|
3867
|
+
const events = sessionEvents(session);
|
|
3868
|
+
if (events.length === 0) return null;
|
|
3839
3869
|
const event = events[nodes[0]];
|
|
3840
3870
|
if (!event) return null;
|
|
3841
3871
|
const message = deriveEventMessage(event);
|
|
@@ -3860,8 +3890,8 @@ function sessionNameFromMdPath(mdPath) {
|
|
|
3860
3890
|
function resolveSessionMdPath(root, scope, session) {
|
|
3861
3891
|
const candidates = [];
|
|
3862
3892
|
candidates.push(getActiveSessionInfo(scope)?.mdPath ?? null);
|
|
3863
|
-
const events = session
|
|
3864
|
-
if (
|
|
3893
|
+
const events = sessionEvents(session);
|
|
3894
|
+
if (events.length > 0) candidates.push(parseSessionContextFromEvents(events)?.mdPath ?? null);
|
|
3865
3895
|
candidates.push(parseAnchorMdPath(session));
|
|
3866
3896
|
candidates.push(findLatestActiveSessionMd(root));
|
|
3867
3897
|
for (const c of candidates) {
|
|
@@ -3953,8 +3983,9 @@ function performRebuild(session, pending, meter) {
|
|
|
3953
3983
|
if (nodes.length === 0) return false;
|
|
3954
3984
|
if (meter) {
|
|
3955
3985
|
let shadowedTokenCount = 0;
|
|
3986
|
+
const events = sessionEvents(session);
|
|
3956
3987
|
for (const seq of nodes) {
|
|
3957
|
-
const event =
|
|
3988
|
+
const event = events[seq];
|
|
3958
3989
|
if (!event) continue;
|
|
3959
3990
|
const message = deriveEventMessage(event);
|
|
3960
3991
|
if (message) shadowedTokenCount += meter.estimateMessage(message);
|
|
@@ -4765,8 +4796,7 @@ function resolveTargetAgent(ctx, mdPath) {
|
|
|
4765
4796
|
*/
|
|
4766
4797
|
function readSessionTitle(session) {
|
|
4767
4798
|
try {
|
|
4768
|
-
const events = session
|
|
4769
|
-
if (!Array.isArray(events)) return null;
|
|
4799
|
+
const events = sessionEvents(session);
|
|
4770
4800
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
4771
4801
|
const e = events[i];
|
|
4772
4802
|
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
@@ -5603,6 +5633,18 @@ function toWire(settings) {
|
|
|
5603
5633
|
};
|
|
5604
5634
|
}
|
|
5605
5635
|
/**
|
|
5636
|
+
* 已知工作区投影(v1.28.0 适配 0.1.2-rc.1 A2 方案 A′):
|
|
5637
|
+
* rc.1 workspace.list unary 删除 → AccountsEditor 白名单下拉的数据源改走 gateway 自有
|
|
5638
|
+
* /serenity/config 的 knownWorkspaces(host workspaceRegistry.list() 投影,白名单过滤)。
|
|
5639
|
+
* 纯函数可单测。allowPrefixes 空 = 全部放行(向后兼容默认)。
|
|
5640
|
+
*/
|
|
5641
|
+
function projectKnownWorkspaces(workspaces, allowPrefixes) {
|
|
5642
|
+
return workspaces.filter((w) => typeof w.path === "string" && w.path !== "").filter((w) => allowPrefixes.length === 0 || allowPrefixes.some((p) => w.path.startsWith(p))).map((w) => ({
|
|
5643
|
+
path: w.path,
|
|
5644
|
+
title: typeof w.title === "string" && w.title !== "" ? w.title : w.path
|
|
5645
|
+
}));
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5606
5648
|
* wire → 持久化(面板 PUT 用):
|
|
5607
5649
|
* accounts 元素可选带 `pass`:非空 → 重新 hash;空/缺省 → 保留现有 hash(按 id 匹配)。
|
|
5608
5650
|
* 新账号(id 不在现有)必须带非空 pass,否则抛错(无法生成 hash)。
|
|
@@ -6264,8 +6306,7 @@ function shouldAutoRestore(agent) {
|
|
|
6264
6306
|
*/
|
|
6265
6307
|
function shouldRestoreActive(agent) {
|
|
6266
6308
|
if (!shouldAutoRestore(agent)) return false;
|
|
6267
|
-
|
|
6268
|
-
return Array.isArray(events) && events.length > 0;
|
|
6309
|
+
return sessionEvents(agent.session).length > 0;
|
|
6269
6310
|
}
|
|
6270
6311
|
function registerContext(ctx, opts = {}) {
|
|
6271
6312
|
const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
|
|
@@ -6280,7 +6321,7 @@ function registerContext(ctx, opts = {}) {
|
|
|
6280
6321
|
const scope = agentScope(agent);
|
|
6281
6322
|
if (shouldRestoreActive(agent) && getActiveSessionInfo(scope) === null) try {
|
|
6282
6323
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
6283
|
-
const info = parseSessionContextFromEvents(agent.session
|
|
6324
|
+
const info = parseSessionContextFromEvents(sessionEvents(agent.session));
|
|
6284
6325
|
if (info) {
|
|
6285
6326
|
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
6286
6327
|
if (existsSync(abs)) {
|
|
@@ -6635,7 +6676,16 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6635
6676
|
return;
|
|
6636
6677
|
}
|
|
6637
6678
|
if (req.method === "GET") {
|
|
6638
|
-
|
|
6679
|
+
const settings = readAdvancedSettings();
|
|
6680
|
+
let known = [];
|
|
6681
|
+
try {
|
|
6682
|
+
const registry = ctx.get?.("workspaceRegistry");
|
|
6683
|
+
known = projectKnownWorkspaces(registry?.list?.() ?? [], settings.gateway.workspaces);
|
|
6684
|
+
} catch {}
|
|
6685
|
+
sendJson$1(res, 200, {
|
|
6686
|
+
config: toWire(settings),
|
|
6687
|
+
knownWorkspaces: known
|
|
6688
|
+
});
|
|
6639
6689
|
return;
|
|
6640
6690
|
}
|
|
6641
6691
|
if (req.method === "PUT") {
|
|
@@ -6668,7 +6718,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6668
6718
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6669
6719
|
});
|
|
6670
6720
|
const root = findSerenityRoot(workspace) ?? "";
|
|
6671
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
6721
|
+
const { discoverCccs } = await import("./skiff-debug-CBU6T2F_.js").then((n) => n.i);
|
|
6672
6722
|
sendJson$1(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
6673
6723
|
} catch (err) {
|
|
6674
6724
|
sendJson$1(res, 400, { error: err.message ?? String(err) });
|
|
@@ -6698,7 +6748,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6698
6748
|
return;
|
|
6699
6749
|
}
|
|
6700
6750
|
const settings = readAdvancedSettings();
|
|
6701
|
-
const { readSimpleSettings } = await import("./settings-section-
|
|
6751
|
+
const { readSimpleSettings } = await import("./settings-section-BfVxgCZy.js").then((n) => n.r);
|
|
6702
6752
|
const simple = readSimpleSettings();
|
|
6703
6753
|
const allowed = settings.publicAsk.allowed;
|
|
6704
6754
|
const port = simple.acpHttpPort ?? 3100;
|
|
@@ -7381,25 +7431,6 @@ const RANDOM_UUID_POLYFILL = `<script>
|
|
|
7381
7431
|
/** 注入标记(幂等:已注入的 HTML 不重复注入) */
|
|
7382
7432
|
const POLYFILL_MARKER = "data-sp-randomuuid-polyfill";
|
|
7383
7433
|
/**
|
|
7384
|
-
* workspace.list 响应过滤(v1.22 白名单):
|
|
7385
|
-
* DSH client→server RPC 全部走 HTTP JSON(`POST /api/workspace.list`,WS 仅下行推送)。
|
|
7386
|
-
* 白名单(workspaces 路径前缀)非空 → 只保留匹配前缀的 items;
|
|
7387
|
-
* 空 = 全部允许(默认,向后兼容)。
|
|
7388
|
-
*/
|
|
7389
|
-
function filterWorkspaceList(body, allowPrefixes) {
|
|
7390
|
-
if (allowPrefixes.length === 0) return body;
|
|
7391
|
-
try {
|
|
7392
|
-
const parsed = JSON.parse(body);
|
|
7393
|
-
const value = parsed?.result?.value;
|
|
7394
|
-
if (parsed?.result?.ok !== true || !value || !Array.isArray(value.items)) return body;
|
|
7395
|
-
const keep = (path) => typeof path === "string" && allowPrefixes.some((p) => path.startsWith(p));
|
|
7396
|
-
value.items = value.items.filter((item) => keep(item.path));
|
|
7397
|
-
return JSON.stringify(parsed);
|
|
7398
|
-
} catch {
|
|
7399
|
-
return body;
|
|
7400
|
-
}
|
|
7401
|
-
}
|
|
7402
|
-
/**
|
|
7403
7434
|
* 校验 workspace.create 请求路径是否在白名单内(v1.22):
|
|
7404
7435
|
* 白名单非空且路径不匹配 → 拒绝(由调用方构造 403 RPC 响应)。
|
|
7405
7436
|
*/
|
|
@@ -7485,8 +7516,6 @@ function startGateway(config, getAccounts) {
|
|
|
7485
7516
|
* @param bodyOverride - workspace.create 已读 body 时的重放(白名单检查后转发)
|
|
7486
7517
|
*/
|
|
7487
7518
|
const proxy = (req, res, bodyOverride) => {
|
|
7488
|
-
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
7489
|
-
const method = req.method === "POST" && url.pathname.startsWith("/api/") ? url.pathname.slice(5) : null;
|
|
7490
7519
|
const headers = buildProxyHeaders(req.headers, mainPort, bodyOverride);
|
|
7491
7520
|
const target = request({
|
|
7492
7521
|
host: "127.0.0.1",
|
|
@@ -7516,25 +7545,6 @@ function startGateway(config, getAccounts) {
|
|
|
7516
7545
|
});
|
|
7517
7546
|
return;
|
|
7518
7547
|
}
|
|
7519
|
-
if (method === "workspace.list" && status === 200 && ct.includes("application/json")) {
|
|
7520
|
-
const chunks = [];
|
|
7521
|
-
upstream.on("data", (c) => chunks.push(c));
|
|
7522
|
-
upstream.on("end", () => {
|
|
7523
|
-
const transformed = filterWorkspaceList(Buffer.concat(chunks).toString("utf-8"), allowWorkspaces);
|
|
7524
|
-
const out = {
|
|
7525
|
-
...upstream.headers,
|
|
7526
|
-
"content-length": Buffer.byteLength(transformed)
|
|
7527
|
-
};
|
|
7528
|
-
res.writeHead(status, out);
|
|
7529
|
-
res.end(transformed);
|
|
7530
|
-
});
|
|
7531
|
-
upstream.on("error", () => {
|
|
7532
|
-
try {
|
|
7533
|
-
res.destroy();
|
|
7534
|
-
} catch {}
|
|
7535
|
-
});
|
|
7536
|
-
return;
|
|
7537
|
-
}
|
|
7538
7548
|
res.writeHead(status, upstream.headers);
|
|
7539
7549
|
upstream.pipe(res);
|
|
7540
7550
|
upstream.on("error", () => {
|
|
@@ -8055,7 +8065,7 @@ function isExternalFaceSession(sessionId) {
|
|
|
8055
8065
|
* 否则 think 内提及内部机制词(思考过程必然推演机制)会误打回。复用 stripThink
|
|
8056
8066
|
* (v1.26.8 状态机,弃正则——同 v1.27.1 微信桥回复链路)。 */
|
|
8057
8067
|
function lastAssistantText(agent) {
|
|
8058
|
-
const events = agent.session
|
|
8068
|
+
const events = sessionEvents(agent.session);
|
|
8059
8069
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
8060
8070
|
const e = events[i];
|
|
8061
8071
|
if (e && e.type === "assistant/message") {
|
|
@@ -9332,7 +9342,8 @@ function registerWeixinBridge(ctx) {
|
|
|
9332
9342
|
//#endregion
|
|
9333
9343
|
//#region src/index.ts
|
|
9334
9344
|
const name = "dsh-serenity-hooks";
|
|
9335
|
-
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
9345
|
+
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
9346
|
+
* (v1.28.0 适配 0.1.2-rc.1:+ 'settings'——B4 settings 服务由 provider 插件加载后才有) */
|
|
9336
9347
|
const inject = [
|
|
9337
9348
|
"tools",
|
|
9338
9349
|
"webServer",
|
|
@@ -9342,7 +9353,8 @@ const inject = [
|
|
|
9342
9353
|
"agentLoop",
|
|
9343
9354
|
"agents",
|
|
9344
9355
|
"systemPrompt",
|
|
9345
|
-
"sessionProjections"
|
|
9356
|
+
"sessionProjections",
|
|
9357
|
+
"settings"
|
|
9346
9358
|
];
|
|
9347
9359
|
const Config = z.object({
|
|
9348
9360
|
serenityConfigPaths: z.array(z.string()).default([...DEFAULT_SERENITY_CONFIG_PATHS]),
|
package/lib/seams/bootstrap.d.ts
CHANGED
|
@@ -50,6 +50,15 @@ export declare const DEFAULT_COMPACTION_TOOLS: string[];
|
|
|
50
50
|
* 文本 = 原 home-serenity CCC 配置(anchorMessages),随零配置化固化进代码。
|
|
51
51
|
*/
|
|
52
52
|
export declare const DEFAULT_ANCHOR_MESSAGES: string[];
|
|
53
|
+
/**
|
|
54
|
+
* 根会话锚定重入判定(v1.28.1 提取导出以便单测):会话是否已有真实用户消息。
|
|
55
|
+
* resume/续跑的会话已有对话历史 → **不重锚**(first-anchor 只注入一次)。
|
|
56
|
+
*
|
|
57
|
+
* 0.1.2-rc.1 适配教训:裸读 `session.events`(经类型断言)恒 undefined →
|
|
58
|
+
* `!undefined` 恒 true → 有历史也永不跳过 → 任何情况发消息都重插 first-anchor。
|
|
59
|
+
* 统一经 sessionEvents() 读取(snapshotEvents() 优先,.events 兜底)。
|
|
60
|
+
*/
|
|
61
|
+
export declare function hasUserMessageHistory(session: unknown): boolean;
|
|
53
62
|
export interface PromotionStatus {
|
|
54
63
|
/** 最后一次 compaction/end 的 seq(-1 = 未压缩过) */
|
|
55
64
|
boundary: number;
|
package/lib/session-ops.d.ts
CHANGED
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
import type { JsonValue } from './json.js';
|
|
13
13
|
export type SessionAction = 'list' | 'show' | 'create' | 'use' | 'close' | 'health' | 'qa' | 'archive' | 'summary' | 'hook-develop-guide';
|
|
14
14
|
export declare const SESSION_ACTIONS: readonly SessionAction[];
|
|
15
|
+
/**
|
|
16
|
+
* 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
|
|
17
|
+
* 移除 `.events` 属性 → `snapshotEvents()` 方法(dsh-session/src/session.ts:
|
|
18
|
+
* `snapshotEvents(fromSeq, toSeqExclusive)`)。插件早期代码多处裸读 `.events`
|
|
19
|
+
* (经 `as unknown as { events? }` 断言绕过 typecheck),运行时静默 undefined——
|
|
20
|
+
* 造成 first-anchor 每轮重插 / SESSION 激活恢复失效 / rebuild 定位错乱。
|
|
21
|
+
* 统一收敛到本 helper:snapshotEvents() 优先(rc.1 真实形态),`.events` 兜底
|
|
22
|
+
* (测试替身/旧运行时)。所有消费方一律经此读取,禁止再裸读 `.events`。
|
|
23
|
+
* 泛型 T:调用方按需声明事件形状(如 `SessionEvent`),unknown 默认。
|
|
24
|
+
*/
|
|
25
|
+
export declare function sessionEvents<T = unknown>(session: unknown): readonly T[];
|
|
15
26
|
export declare function sessionsRoot(root: string): string;
|
|
16
27
|
interface SessionStatus {
|
|
17
28
|
hasSessionMd: boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
//#region src/settings-section.ts
|
|
4
|
+
var settings_section_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
SERENITY_SETTINGS_NS: () => SERENITY_SETTINGS_NS,
|
|
6
|
+
defaultSimpleSettings: () => defaultSimpleSettings,
|
|
7
|
+
entryDefaults: () => entryDefaults,
|
|
8
|
+
readSimpleSettings: () => readSimpleSettings,
|
|
9
|
+
registerSettingsSection: () => registerSettingsSection,
|
|
10
|
+
simpleSettingsSchema: () => simpleSettingsSchema
|
|
11
|
+
});
|
|
12
|
+
/** 简单配置命名空间(dsh 设置面板的 section id / settings.yaml section) */
|
|
13
|
+
const SERENITY_SETTINGS_NS = "serenity-hooks";
|
|
14
|
+
/** schemastery schema(与 DSH 各插件 Config 同款) */
|
|
15
|
+
const simpleSettingsSchema = z.object({
|
|
16
|
+
gatewayEnabled: z.boolean().default(false),
|
|
17
|
+
rebuildEnabled: z.boolean().default(true),
|
|
18
|
+
rebuildThresholdK: z.number().min(50).max(4e3).default(400),
|
|
19
|
+
skiffEnabled: z.boolean().default(false),
|
|
20
|
+
skiffDebugPort: z.number().min(1024).max(65535).default(3099),
|
|
21
|
+
acpEnabled: z.boolean().default(false),
|
|
22
|
+
acpHttpPort: z.number().min(1024).max(65535).default(3100),
|
|
23
|
+
publicAskEnabled: z.boolean().default(false),
|
|
24
|
+
autopilotEnabled: z.boolean().default(false)
|
|
25
|
+
});
|
|
26
|
+
/** 从插件 Config 提取 entry 默认(settings base 层) */
|
|
27
|
+
function entryDefaults(config) {
|
|
28
|
+
return {
|
|
29
|
+
gatewayEnabled: config.gateway?.enabled ?? false,
|
|
30
|
+
rebuildEnabled: config.rebuild?.enabled ?? true,
|
|
31
|
+
rebuildThresholdK: config.rebuild?.thresholdK ?? 400,
|
|
32
|
+
skiffEnabled: config.skiff?.enabled ?? false,
|
|
33
|
+
skiffDebugPort: config.skiff?.debugPort ?? 3099,
|
|
34
|
+
acpEnabled: config.acp?.enabled ?? false,
|
|
35
|
+
acpHttpPort: config.acp?.httpPort ?? 3100,
|
|
36
|
+
publicAskEnabled: config.publicAsk?.enabled ?? false,
|
|
37
|
+
autopilotEnabled: false
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** 运行时源(installSettingsSection 注入:settings scope 或 entry fallback) */
|
|
41
|
+
let simpleSource = null;
|
|
42
|
+
/** 进程级默认(无 settings 服务时的兜底) */
|
|
43
|
+
function defaultSimpleSettings() {
|
|
44
|
+
return {
|
|
45
|
+
gatewayEnabled: false,
|
|
46
|
+
rebuildEnabled: true,
|
|
47
|
+
rebuildThresholdK: 400,
|
|
48
|
+
skiffEnabled: false,
|
|
49
|
+
skiffDebugPort: 3099,
|
|
50
|
+
acpEnabled: false,
|
|
51
|
+
acpHttpPort: 3100,
|
|
52
|
+
publicAskEnabled: false,
|
|
53
|
+
autopilotEnabled: false
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 读取当前简单配置(settings 解析值;无 provider/未注册 → entry 默认)。
|
|
58
|
+
* 各功能(gateway/rebuild/naming)启动与运行时判断开关都经此函数。
|
|
59
|
+
*/
|
|
60
|
+
function readSimpleSettings() {
|
|
61
|
+
return simpleSource ? simpleSource() : defaultSimpleSettings();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 注册简单配置到 DSH settings(零改 DSH;settings.yaml 持久化 + 原生面板渲染)。
|
|
65
|
+
* 注:插件 Config(cordis.yml 组合层)作为 base;用户文档层叠加其上。
|
|
66
|
+
* 运行时读取简单配置统一经 `readSimpleSettings()`。
|
|
67
|
+
* v1.28.0 适配 0.1.2-rc.1(B4):`installSettingsSection`/`settingsNamespace` 便捷函数在
|
|
68
|
+
* rc.1 消失 → 改 `SettingsProvider.installSection(owner, ns, schema, entry, hooks)`
|
|
69
|
+
* (owner = consumer 插件 ctx;ns 直接字符串)。
|
|
70
|
+
*/
|
|
71
|
+
function registerSettingsSection(ctx, config) {
|
|
72
|
+
const hooks = {
|
|
73
|
+
setSource: (get) => {
|
|
74
|
+
simpleSource = get;
|
|
75
|
+
},
|
|
76
|
+
onChange: () => {
|
|
77
|
+
try {
|
|
78
|
+
ctx.emit?.("serenity/settings-changed");
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const settingsAny = ctx.settings;
|
|
83
|
+
if (settingsAny) {
|
|
84
|
+
settingsAny.installSection.call(settingsAny, ctx, SERENITY_SETTINGS_NS, simpleSettingsSchema, entryDefaults(config), hooks);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
hooks.setSource(() => ({}));
|
|
88
|
+
hooks.onChange();
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { registerSettingsSection as n, settings_section_exports as r, readSimpleSettings as t };
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* 分层决策(S142):**简单配置(开关/阈值)→ dsh 原生设置面板**;
|
|
5
5
|
* **复杂配置(账号列表)→ 宁静号高级面板**(localstore + /serenity/config)。
|
|
6
6
|
*
|
|
7
|
-
* 本模块承载简单配置层:`
|
|
7
|
+
* 本模块承载简单配置层:`SettingsProvider.installSection(ctx, 'serenity-hooks', schema, entry)`
|
|
8
8
|
* 注册 `serenity-hooks` namespace——三功能总开关 + F2 阈值。
|
|
9
|
+
* (v1.28.0 适配 0.1.2-rc.1:installSettingsSection/settingsNamespace 便捷函数消失 → 方法调用)
|
|
9
10
|
*
|
|
10
11
|
* 运行时降级守卫(版本鲁棒性):旧 RC(staging 架构)api-proxy 有
|
|
11
12
|
* `WEB_SETTINGS_NAMESPACES` 静态白名单,第三方 ns 会收到 settings-not-exposed
|
|
@@ -107,5 +108,8 @@ export declare function __setSimpleSourceForTest(source: (() => SerenitySimpleSe
|
|
|
107
108
|
* 注册简单配置到 DSH settings(零改 DSH;settings.yaml 持久化 + 原生面板渲染)。
|
|
108
109
|
* 注:插件 Config(cordis.yml 组合层)作为 base;用户文档层叠加其上。
|
|
109
110
|
* 运行时读取简单配置统一经 `readSimpleSettings()`。
|
|
111
|
+
* v1.28.0 适配 0.1.2-rc.1(B4):`installSettingsSection`/`settingsNamespace` 便捷函数在
|
|
112
|
+
* rc.1 消失 → 改 `SettingsProvider.installSection(owner, ns, schema, entry, hooks)`
|
|
113
|
+
* (owner = consumer 插件 ctx;ns 直接字符串)。
|
|
110
114
|
*/
|
|
111
115
|
export declare function registerSettingsSection(ctx: Context, config: SimpleConfigFragment): void;
|
|
@@ -396,9 +396,20 @@ function waitIdle(ctx, agent) {
|
|
|
396
396
|
});
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* 会话事件读取(v1.28.0 适配 0.1.2-rc.1):rc.1 Session 移除 `.events` 属性 → `snapshotEvents()` 方法。
|
|
401
|
+
* 兼容双形态(运行时 rc.1 snapshotEvents;测试替身 events)——同一函数两处语义,测试注入零侵入。
|
|
402
|
+
* 注:与 session-ops.ts 导出的共享 `sessionEvents` 同构(v1.28.1 收敛基准)。本模块保留本地副本
|
|
403
|
+
* 以维持独立 import 面(零 DSH 依赖测试替身形态一致);若未来重构可统一 import 共享版。
|
|
404
|
+
*/
|
|
405
|
+
function sessionEvents(session) {
|
|
406
|
+
const s = session;
|
|
407
|
+
if (typeof s.snapshotEvents === "function") return s.snapshotEvents();
|
|
408
|
+
return s.events ?? [];
|
|
409
|
+
}
|
|
399
410
|
/** 读会话最后一个 assistant/message 文本(handyman 同款) */
|
|
400
411
|
function lastAssistantText(agent) {
|
|
401
|
-
const events = agent.session
|
|
412
|
+
const events = sessionEvents(agent.session);
|
|
402
413
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
403
414
|
const e = events[i];
|
|
404
415
|
if (e && e.type === "assistant/message") {
|
|
@@ -462,7 +473,7 @@ function truncate(s, n) {
|
|
|
462
473
|
* 公开问答页 / ACP JSON-RPC 不返回 trajectory,传 false 跳过计算;3099 调试页默认 true 保留)
|
|
463
474
|
*/
|
|
464
475
|
async function askSkiff(ctx, agent, question, eventsStart, options) {
|
|
465
|
-
const before = eventsStart === void 0 ? agent.session.
|
|
476
|
+
const before = eventsStart === void 0 ? sessionEvents(agent.session).length : eventsStart;
|
|
466
477
|
agent.followup(createUserMessage({
|
|
467
478
|
content: [{
|
|
468
479
|
type: "text",
|
|
@@ -472,7 +483,7 @@ async function askSkiff(ctx, agent, question, eventsStart, options) {
|
|
|
472
483
|
}));
|
|
473
484
|
await waitIdle(ctx, agent);
|
|
474
485
|
const answer = lastAssistantText(agent);
|
|
475
|
-
const trajectory = options?.includeTrajectory === false ? [] : eventsToTrajectory(agent.session.
|
|
486
|
+
const trajectory = options?.includeTrajectory === false ? [] : eventsToTrajectory(sessionEvents(agent.session).slice(before));
|
|
476
487
|
return {
|
|
477
488
|
answer,
|
|
478
489
|
sessionId: String(agent.session.id ?? ""),
|