@shgroup/dsh-serenity-hooks 1.59.5 → 1.63.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/README.md +3 -3
- package/assets/serenity-voyage.html +1241 -1073
- package/dsh.plugin.json +2 -2
- package/lib/host/contract.d.ts +11 -0
- package/lib/index.js +250 -49
- package/lib/seams/bootstrap.d.ts +64 -3
- package/package.json +2 -2
package/dsh.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.63.1",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
|
-
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1
|
|
5
|
+
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
7
7
|
"dsh": ">=0.1.7-rc.1"
|
|
8
8
|
},
|
package/lib/host/contract.d.ts
CHANGED
|
@@ -23,6 +23,17 @@ type HostAccess = 'injected' | 'lazy';
|
|
|
23
23
|
interface HostMember {
|
|
24
24
|
name: string;
|
|
25
25
|
kind: 'function' | 'value';
|
|
26
|
+
/**
|
|
27
|
+
* 成员所属的「互斥族」标签(可选)。**同族成员满足任一即可**。
|
|
28
|
+
*
|
|
29
|
+
* 🔴 为什么需要(v1.61,S151 2026-10-17 报告):
|
|
30
|
+
* 宿主会**给同一个能力改名**并保留旧名作回退。`subagents` 就是实例:
|
|
31
|
+
* `0.2.1-rc.*+` 的正门叫 `startActivation`,`start` 仅作旧宿主回退
|
|
32
|
+
* (`tools/cheap-worker.ts` 的 `resolveSubagentStarter()` 是**双路**的)。
|
|
33
|
+
* 若契约把某一个具体名字钉成"必须有",宿主改名后 ⇒ **探针报假故障,而功能是好的**。
|
|
34
|
+
* ⇒ 判据必须是「**这一族里至少有一个能用**」,不是「这个名字必须在」。
|
|
35
|
+
*/
|
|
36
|
+
anyOf?: string;
|
|
26
37
|
}
|
|
27
38
|
interface HostServiceContract {
|
|
28
39
|
/** 稳定标识(报告用) */
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-GIJ3A_tw.js";
|
|
3
3
|
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, h as readTrajectorySkills, i as findGitRoot, l as pathInside, m as readSafeModeHiddenTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readExclusiveTools, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-B0ZGL9lx.js";
|
|
4
|
-
import { A as localIdStamp, C as setActiveSessionInfo, D as isoLocal, E as useSession, O as localFileStamp, S as sessionsRoot, T as summarize, _ as namingTitleFor, a as pruneMissingBindings, b as resolveSessionByTitle, c as setBindingStore, d as clearActiveSessionInfo, f as createSession, g as listSessions, h as getActiveSessionInfo, i as listBoundSessionIds, k as localHuman, l as DEFAULT_SESSION_SCOPE, m as findSession, n as PLUGIN_SOURCE, o as readLastBound, p as extractSessionMdPathFromText, r as appendBound, s as resolveSessionTrajectoryLabel,
|
|
4
|
+
import { A as localIdStamp, C as setActiveSessionInfo, D as isoLocal, E as useSession, O as localFileStamp, S as sessionsRoot, T as summarize, _ as namingTitleFor, a as pruneMissingBindings, b as resolveSessionByTitle, c as setBindingStore, d as clearActiveSessionInfo, f as createSession, g as listSessions, h as getActiveSessionInfo, i as listBoundSessionIds, k as localHuman, l as DEFAULT_SESSION_SCOPE, m as findSession, n as PLUGIN_SOURCE, o as readLastBound, p as extractSessionMdPathFromText, r as appendBound, s as resolveSessionTrajectoryLabel, u as TRAJECTORY_ACTIONS, v as parseSessionContextFromEvents, w as showSession, x as sessionEvents$1, y as readActiveSessionMd } from "./message-source-B4P3Uw6D.js";
|
|
5
5
|
import { a as readGitTrack, c as unsetEntry, i as localstorePath, l as writeEntry, n as checkLocalstoreGitCompliance, o as readStore, r as getEntry, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./localstore-ops-DMs_ovHo.js";
|
|
6
6
|
import { a as FACE_PORTS, c as MECHANISM_PORTS, d as registerSkiffSession$1, f as skiffRoleFor$1, h as ACC_VERSION, i as ACP_PORT, l as SKIFF_DEBUG_PORT, m as unregisterSkiffSession$1, n as readSimpleSettings, o as GATEWAY_PORT, p as skiffSessionInfo$1, r as registerSettingsSection, s as MAIN_WEB_PORT, u as WEIXIN_SEND_PORT } from "./settings-section-DGUQZo5j.js";
|
|
7
7
|
import { a as readSkiffRoles, c as roleMsmWhitelist, d as systemPromptSource, f as trajectorySubset, i as isSkiffSessionId, l as roleToolWhitelist, n as buildSkiffBasePrompt, o as resolveRoleSystemPrompt, r as createRolePromptReader, s as resolveSkiffKind } from "./skiff-role-B615L9MV.js";
|
|
@@ -11518,17 +11518,39 @@ async function sendToTrajectory(ctx, root, target, message, sender) {
|
|
|
11518
11518
|
*
|
|
11519
11519
|
* 何时用:**只有** `send-now` 在 `acquireWakeAgent` 报「无绑定会话记录」时(见该处的判据说明)。
|
|
11520
11520
|
*
|
|
11521
|
-
*
|
|
11522
|
-
*
|
|
11523
|
-
*
|
|
11521
|
+
* ## 🔴 2026-10-18 根治:改成**和用户操作完全同一个调用形态**
|
|
11522
|
+
*
|
|
11523
|
+
* owner 原话:「应当和用户操作唤起的会话完全一样才行」。
|
|
11524
|
+
*
|
|
11525
|
+
* 上一版(v1.62.1)传的是 `{ cwd: root }`,然后**事后补** `attachSession()` 去挂工作区。
|
|
11526
|
+
* 那是**补救**,不是"一样" —— 宿主里那条路径(`sessionController.create` 的
|
|
11527
|
+
* `if (workspace !== void 0) await workspace.attachSession(sessionId)`)**只在 `workspaceId` 分支执行**:
|
|
11528
|
+
*
|
|
11529
|
+
* ```js
|
|
11530
|
+
* if (request.workspaceId !== void 0) workspace = registry.get(request.workspaceId)
|
|
11531
|
+
* const cwd = workspace?.path ?? request.cwd ?? this.defaultCwd
|
|
11532
|
+
* ...
|
|
11533
|
+
* if (workspace !== void 0) await workspace.attachSession(sessionId) // ← 只有这条分支
|
|
11534
|
+
* ```
|
|
11535
|
+
*
|
|
11536
|
+
* 而**用户在 UI 里新建会话传的正是 `workspaceId`**(见 `client/sessions/service.d.ts` 的
|
|
11537
|
+
* `create({ workspaceId?, cwd?, ... })`)⇒ 用户那条路天然会走到 `attachSession`。
|
|
11538
|
+
*
|
|
11539
|
+
* ⇒ 现在与用户**同一条分支**:先由 CCC 根查出 `workspaceId`,再用 `{ workspaceId }` 建。
|
|
11540
|
+
* **`attachToWorkspace` 降级为兜底**(只在"该目录还没有工作区登记"时才会用上),
|
|
11541
|
+
* 不再是主路径 —— 因为主路径已经由宿主自己做完。
|
|
11542
|
+
*
|
|
11543
|
+
* ## 三步(顺序不可换)
|
|
11544
|
+
* ① `sessionController.create({ workspaceId })` —— 与用户同一形态;宿主替我们挂工作区。
|
|
11545
|
+
* **idempotently adopt** ⇒ 重复调用不会建出第二条(不必自造"先查再建"的竞态窗口)。
|
|
11524
11546
|
* ② `resolveAgent(sessionId)` —— 拿到**真 agent**。`create` 只返回 id,不给 agent。
|
|
11525
11547
|
* ③ `appendBound(...)` —— 落绑定。**必须由本函数写**:`listBoundSessionIds` 是
|
|
11526
11548
|
* `acquireWakeAgent` 的唯一选人依据,不写绑定则下一条消息**还是**找不到它
|
|
11527
11549
|
* (那就成了"建了但永远用不上",比不建更坏)。
|
|
11528
11550
|
*
|
|
11529
|
-
* 🔴
|
|
11551
|
+
* 🔴 **为什么定位到 CCC 根这一级、不是轨迹目录**:会话的 cwd 决定它落在哪个 CCC
|
|
11530
11552
|
* (`cccRootForCwd` 上溯 `.serenity`)。目标轨迹本就住在该 CCC 的 `AGENT_SESSIONS/` 下
|
|
11531
|
-
* ⇒
|
|
11553
|
+
* ⇒ 落到 CCC 根才让它**进同一个 CCC**;落到轨迹目录会让它变成一个**不同 CCC**的会话。
|
|
11532
11554
|
*
|
|
11533
11555
|
* @returns 命中返回 agent 与取得方式;任一步失败返回**带原因**的 error(不静默、不误报成功)
|
|
11534
11556
|
*/
|
|
@@ -11539,9 +11561,11 @@ async function createAndBindTarget(ctx, root, dirName) {
|
|
|
11539
11561
|
error: "目标无活跃 dsh 会话,且 sessionController.create 不可用(绑定 id: 无)——无法就地创建载体;请先对该轨迹执行一次 container_trajectory use",
|
|
11540
11562
|
notReady: true
|
|
11541
11563
|
};
|
|
11564
|
+
const ws = findWorkspaceByPath(listWorkspaces(ctx) ?? [], root);
|
|
11565
|
+
const workspaceId = typeof ws?.id === "string" && ws.id !== "" ? ws.id : void 0;
|
|
11542
11566
|
let sessionId = "";
|
|
11543
11567
|
try {
|
|
11544
|
-
const out = await create.call(sc, { cwd: root });
|
|
11568
|
+
const out = await create.call(sc, workspaceId !== void 0 ? { workspaceId } : { cwd: root });
|
|
11545
11569
|
sessionId = String(out?.sessionId ?? "");
|
|
11546
11570
|
} catch (err) {
|
|
11547
11571
|
return { error: `就地创建会话失败(${dirName}): ${String(err?.message ?? err)}` };
|
|
@@ -11568,13 +11592,100 @@ async function createAndBindTarget(ctx, root, dirName) {
|
|
|
11568
11592
|
mdPath: join(root, "AGENT_SESSIONS", dirName, "SESSION.md"),
|
|
11569
11593
|
note: "auto-created by send-now"
|
|
11570
11594
|
});
|
|
11571
|
-
const
|
|
11595
|
+
const wsNote = workspaceId !== void 0 ? "" : await attachToWorkspace(ctx, root, sessionId);
|
|
11596
|
+
const how = `auto-create(${sessionId}${bound ? ", bound" : ", ⚠️ 绑定写入失败"}${workspaceId !== void 0 ? ", via-workspaceId(=用户同一形态)" : ", via-cwd+attach"}${wsNote})`;
|
|
11572
11597
|
return {
|
|
11573
11598
|
agent,
|
|
11574
11599
|
how
|
|
11575
11600
|
};
|
|
11576
11601
|
}
|
|
11577
11602
|
/**
|
|
11603
|
+
* 把刚建的会话挂进 **cwd 所属的那个工作区**(2026-10-17 缺陷修复)。
|
|
11604
|
+
*
|
|
11605
|
+
* 🔴 **为什么必须我们自己做(宿主源码取证,不是推断)**:
|
|
11606
|
+
* `sessionController.create()` 里 `attachSession` **只在 `workspaceId` 分支执行** ——
|
|
11607
|
+
* `dsh-api-session-controller/lib/index.js` 的 `create()`:
|
|
11608
|
+
* ```
|
|
11609
|
+
* if (request.workspaceId !== void 0) workspace = registry.get(request.workspaceId)
|
|
11610
|
+
* const cwd = workspace?.path ?? request.cwd ?? this.defaultCwd
|
|
11611
|
+
* ...
|
|
11612
|
+
* if (workspace !== void 0) await workspace.attachSession(sessionId) // ← 只有这条分支挂工作区
|
|
11613
|
+
* ```
|
|
11614
|
+
* 我们传的是 `{ cwd }`(**必须如此**:`workspaceId` 与 `cwd` 互斥,而工作区 id 是宿主的内部 uuid,
|
|
11615
|
+
* 我们又得按"这条轨迹住在哪个 CCC"来建会话)⇒ **建出来的会话从不登记进任何工作区**。
|
|
11616
|
+
*
|
|
11617
|
+
* 🔴 **症状(现场实证,本机真实数据)**:`send-now` 自动建出的那条会话
|
|
11618
|
+
* (`note` = `auto-created by send-now`)header `cwd` 确实是某个 CCC 根,
|
|
11619
|
+
* **却不在**该工作区的 `sessionIds` 里
|
|
11620
|
+
* (同一个 CCC 的另外两条会话都在)⇒ WebUI 里它不属于那个工作区。
|
|
11621
|
+
*
|
|
11622
|
+
* 🔴 **为什么按 `path` 找而不是"拿第一个"**:多个 CCC 并存(本机实有 5 个工作区)
|
|
11623
|
+
* ⇒ 拿第一个必然挂错。判据 = `workspace.path === root`(宿主 `Workspace.path` 是
|
|
11624
|
+
* **realpath 规范化**后的规范路径;`root` 已是 `cccRootForCwd` 上溯出来的真实目录,
|
|
11625
|
+
* 两侧同一来源,故可直接比)。
|
|
11626
|
+
*
|
|
11627
|
+
* ⚠️ **失败不阻断投递**:工作区归属影响的是"在 WebUI 里归哪个工作区",
|
|
11628
|
+
* 而消息投递已经完成 ⇒ 把原因**如实写进回执**,不吞、也不把成功改成失败。
|
|
11629
|
+
*
|
|
11630
|
+
* @param ctx 插件上下文
|
|
11631
|
+
* @param root CCC 根(= 该会话的 cwd)
|
|
11632
|
+
* @param sessionId 刚建出来的会话 id
|
|
11633
|
+
* @returns 追加到回执里的短注记(成功/跳过为空串,失败带原因)
|
|
11634
|
+
*/
|
|
11635
|
+
async function attachToWorkspace(ctx, root, sessionId) {
|
|
11636
|
+
const workspaces = listWorkspaces(ctx);
|
|
11637
|
+
if (workspaces === void 0) return ", ⚠️ 工作区未挂载(workspaceRegistry 不可用)";
|
|
11638
|
+
const match = findWorkspaceByPath(workspaces, root);
|
|
11639
|
+
if (match === void 0) return "";
|
|
11640
|
+
if (typeof match.attachSession !== "function") return ", ⚠️ 工作区未挂载(attachSession 不可用)";
|
|
11641
|
+
if (Array.isArray(match.sessionIds) && match.sessionIds.includes(sessionId)) return "";
|
|
11642
|
+
try {
|
|
11643
|
+
await match.attachSession(sessionId);
|
|
11644
|
+
return ", workspace-attached";
|
|
11645
|
+
} catch (err) {
|
|
11646
|
+
return `, ⚠️ 工作区挂载失败: ${String(err?.message ?? err)}`;
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11649
|
+
/** `workspaceRegistry.list()` 的读取收口(不可用返回 `undefined`,**与"空列表"区分**) */
|
|
11650
|
+
function listWorkspaces(ctx) {
|
|
11651
|
+
const reg = hostService(ctx, "workspaceRegistry");
|
|
11652
|
+
if (typeof reg?.list !== "function") return void 0;
|
|
11653
|
+
try {
|
|
11654
|
+
return reg.list() ?? [];
|
|
11655
|
+
} catch {
|
|
11656
|
+
return;
|
|
11657
|
+
}
|
|
11658
|
+
}
|
|
11659
|
+
/**
|
|
11660
|
+
* 在 `workspaceRegistry.list()` 里找 **path 等于该 CCC 根** 的那一个。
|
|
11661
|
+
*
|
|
11662
|
+
* 🔴 **为什么要 realpath 归一(不是画蛇添足)**:宿主的 `Workspace.path` 是
|
|
11663
|
+
* **`fs.realpath` 规范化**过的(`entity.d.ts` 逐字:*"Canonical directory path: the
|
|
11664
|
+
* `fs.realpath` of the path given at create time (trailing slashes, `..`, and symlinks
|
|
11665
|
+
* all resolved)"*)。而 `root` 来自 `cccRootForCwd` 的上溯——**上溯用的是字面路径**。
|
|
11666
|
+
* ⇒ 当 CCC 根是一个**符号链接**(部署上常见:家目录/项目目录被链到别处),
|
|
11667
|
+
* 两侧**逐字不等**,只比字符串会**挂错或挂不上**。
|
|
11668
|
+
*
|
|
11669
|
+
* ⚠️ 归一失败(路径不存在等)时**回落字面比较** —— 不改既有语义,只是多给一次机会。
|
|
11670
|
+
*
|
|
11671
|
+
* @param workspaces registry.list() 的返回
|
|
11672
|
+
* @param root CCC 根
|
|
11673
|
+
* @returns 命中的工作区,无则 undefined
|
|
11674
|
+
*/
|
|
11675
|
+
function findWorkspaceByPath(workspaces, root) {
|
|
11676
|
+
const direct = workspaces.find((ws) => ws?.path === root);
|
|
11677
|
+
if (direct !== void 0) return direct;
|
|
11678
|
+
const canonical = (p) => {
|
|
11679
|
+
try {
|
|
11680
|
+
return realpathSync(p);
|
|
11681
|
+
} catch {
|
|
11682
|
+
return p;
|
|
11683
|
+
}
|
|
11684
|
+
};
|
|
11685
|
+
const want = canonical(root);
|
|
11686
|
+
return workspaces.find((ws) => typeof ws?.path === "string" && canonical(ws.path) === want);
|
|
11687
|
+
}
|
|
11688
|
+
/**
|
|
11578
11689
|
* 本 tick 要扫的 CCC 根集合(**并集**)。
|
|
11579
11690
|
*
|
|
11580
11691
|
* C2(S142 2026-09-15 Q5):原实现是 `collectLiveCccs`——**只认 live 会话** ⇒
|
|
@@ -14091,15 +14202,77 @@ const DEFAULT_COMPACTION_TOOLS = [
|
|
|
14091
14202
|
*/
|
|
14092
14203
|
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."];
|
|
14093
14204
|
/**
|
|
14094
|
-
*
|
|
14095
|
-
*
|
|
14205
|
+
* 会话里是否已有模型回复(= 已经不是首轮)。
|
|
14206
|
+
*
|
|
14207
|
+
* 🔴 这是锚定的**形状判据**(v1.62):锚定只能在**首轮**造。
|
|
14208
|
+
* ⛔ 不能用 `hasUserMessageHistory`:宿主在 system 之后、真人 prompt 之前就写了
|
|
14209
|
+
* ACC 横幅,而锚定实际执行点(`setTimeout(0)`)更晚 ⇒ 真人历史**必然已存在** ⇒ 恒真。
|
|
14210
|
+
* 而 `assistant/message` 只在**模型真的答过一轮之后**才出现 ⇒ 它在首轮时必然为空。
|
|
14211
|
+
*/
|
|
14212
|
+
function hasAssistantHistory(session) {
|
|
14213
|
+
return sessionEvents$1(session).some((event) => event.type === "assistant/message");
|
|
14214
|
+
}
|
|
14215
|
+
/**
|
|
14216
|
+
* 把 first-anchor 的锚定话语**直接写进会话日志**,取代「真跑两轮」。
|
|
14096
14217
|
*
|
|
14097
|
-
*
|
|
14098
|
-
*
|
|
14099
|
-
*
|
|
14218
|
+
* ## 🔴 两条硬约束(宿主 v4 格式校验器,实测逐条踩过)
|
|
14219
|
+
*
|
|
14220
|
+
* **约束 A — 只能写 `user/message`**:`system/message` 与 `assistant/message` 都在
|
|
14221
|
+
* `STEP_EVENT_TYPES` 里 ⇒ 必须过 `requireStep()`(`data.turn`/`data.step` 精确匹配
|
|
14222
|
+
* **当前打开的** turn 与 step,且为 null 时同样拒绝)。而本函数在 `agent/pre-step`
|
|
14223
|
+
* 前后被调用时 **turn/step 都还没打开**(`turn/start` 在 preStep 之前,`step/start`
|
|
14224
|
+
* 在 preStep 之后)⇒ 代写它们**结构性非法**。
|
|
14225
|
+
* ⚠️ 症状:写通道不校验 ⇒ **首轮正常**;**重新进入**时重放才抛
|
|
14226
|
+
* `system/message does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
|
|
14227
|
+
* ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案因此作废**。
|
|
14228
|
+
*
|
|
14229
|
+
* **约束 B — 必须写在宿主提交 `system/message` 之后**:格式层有 `protectedHead`
|
|
14230
|
+
* 机制 —— `foldSurface()` 里 `system/message` 若在 **surface 非空且尚无 protectedHead**
|
|
14231
|
+
* 时到来,直接抛 `system/message requires a protected first surface head`。
|
|
14232
|
+
* 也就是说:**surface 的第一个节点必须是 system**。
|
|
14233
|
+
* ⇒ 若我们先写 user 消息,宿主随后的 system 就落不到首位 ⇒ 重放同样判 corrupt。
|
|
14234
|
+
* ⚠️ 这条是**第一次修复后仍然翻车**的原因(同一会话、不同报错),
|
|
14235
|
+
* 判据仍是「重启宿主后重新进入该会话」。
|
|
14236
|
+
*
|
|
14237
|
+
* ⇒ 因此调用时机 = **`session/event` 里看到该会话第一条 `system/message` 之后**(见下方注册处),
|
|
14238
|
+
* 而不是 `agent/inbox/inserted`(那太早)。
|
|
14239
|
+
*
|
|
14240
|
+
* ## 为什么去掉 assistant 那半不损失什么
|
|
14241
|
+
*
|
|
14242
|
+
* 锚定话语的**唯一目的**是让模型认为自己已经同意过协作协议。
|
|
14243
|
+
* 保留 **2 条 `user/message`** 即可达成;丢掉的只是"模型自己的 ack 回复"——
|
|
14244
|
+
* 而那原本也是**我们伪造的**,不是模型真说过的。
|
|
14245
|
+
*
|
|
14246
|
+
* ## 判据(改这个函数前后都必须跑)
|
|
14247
|
+
*
|
|
14248
|
+
* ① 单测钉形状(只许 user/message);
|
|
14249
|
+
* ② **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
|
|
14250
|
+
* (前两次修复都是"单测全绿 + 首轮正常"却仍然 corrupt)。
|
|
14251
|
+
* ③ 直接调宿主自己的 `assertReleasedV4Relationships()` 判形状,**不要自写复刻**。
|
|
14252
|
+
*
|
|
14253
|
+
* @param session 会话(需有 append)
|
|
14254
|
+
* @param anchorMessages 锚定话语序列(缺省协议固有两条)
|
|
14255
|
+
* @returns 是否成功写入
|
|
14100
14256
|
*/
|
|
14101
|
-
function
|
|
14102
|
-
|
|
14257
|
+
function appendAnchorHistory(session, anchorMessages = DEFAULT_ANCHOR_MESSAGES) {
|
|
14258
|
+
const target = session;
|
|
14259
|
+
if (typeof target?.append !== "function") return false;
|
|
14260
|
+
const append = target.append.bind(target);
|
|
14261
|
+
const rid = (prefix) => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
14262
|
+
try {
|
|
14263
|
+
for (let i = 0; i < anchorMessages.length; i++) append("user/message", {
|
|
14264
|
+
id: rid(`anchor-user-${i + 1}`),
|
|
14265
|
+
role: "user",
|
|
14266
|
+
content: [{
|
|
14267
|
+
type: "text",
|
|
14268
|
+
text: anchorMessages[i]
|
|
14269
|
+
}],
|
|
14270
|
+
source: { kind: "user" }
|
|
14271
|
+
}, { surfaceOp: "append" });
|
|
14272
|
+
return anchorMessages.length > 0;
|
|
14273
|
+
} catch {
|
|
14274
|
+
return false;
|
|
14275
|
+
}
|
|
14103
14276
|
}
|
|
14104
14277
|
/**
|
|
14105
14278
|
* 构建一个 epoch 感知晋升跟踪器(纯逻辑,可单测)。
|
|
@@ -14213,9 +14386,18 @@ const PROMOTE_EVENTS = {
|
|
|
14213
14386
|
"assistant-message": /* @__PURE__ */ new Set(["assistant/message"]),
|
|
14214
14387
|
either: /* @__PURE__ */ new Set(["tool/call", "assistant/message"])
|
|
14215
14388
|
};
|
|
14216
|
-
/** 协议固有 bootstrap
|
|
14217
|
-
*
|
|
14218
|
-
*
|
|
14389
|
+
/** 协议固有 bootstrap 设置(v1.62 / D140)。
|
|
14390
|
+
*
|
|
14391
|
+
* 🔴 **zeroTools 必须为 false**:v1.62 起锚定历史**只造 `user/message`**(不含伪造的
|
|
14392
|
+
* `assistant/message`)⇒ **首请求时晋升信号尚未满足** ⇒ 若仍 `zeroTools: true`,
|
|
14393
|
+
* 首请求会真的退化成「0 工具」,模型看不到任何 ACC 工具。
|
|
14394
|
+
*
|
|
14395
|
+
* 🔴 **为什么不改回去伪造一条 assistant**:宿主 v4 校验要求 `assistant/message` 落在
|
|
14396
|
+
* 已打开的 turn+step 内(见 `appendAnchorHistory` 头注释),而锚定写入时 turn/step
|
|
14397
|
+
* 都还没打开 ⇒ 伪造它**结构性非法**,代价是「重新进入会话即 corrupt」。
|
|
14398
|
+
*
|
|
14399
|
+
* ⇒ 取舍:**首请求就给完整工具目录**(放弃「首请求窄目录」这个 cache 断点优化),
|
|
14400
|
+
* 换来**日志合法**。`bootstrapTools` 因此只是历史字段,`boundary < 0` 分支已让位。 */
|
|
14219
14401
|
function resolveBootstrapSettings() {
|
|
14220
14402
|
return {
|
|
14221
14403
|
bootstrapTools: [...DEFAULT_BOOTSTRAP_TOOLS],
|
|
@@ -14224,7 +14406,7 @@ function resolveBootstrapSettings() {
|
|
|
14224
14406
|
suppressedSources: new Set(DEFAULT_SUPPRESSED_SOURCES),
|
|
14225
14407
|
compactionTools: [...DEFAULT_COMPACTION_TOOLS],
|
|
14226
14408
|
anchorMessages: [...DEFAULT_ANCHOR_MESSAGES],
|
|
14227
|
-
zeroTools:
|
|
14409
|
+
zeroTools: false
|
|
14228
14410
|
};
|
|
14229
14411
|
}
|
|
14230
14412
|
/**
|
|
@@ -14263,36 +14445,32 @@ function registerBootstrap(ctx) {
|
|
|
14263
14445
|
const root = cccRootForCwd(cwd);
|
|
14264
14446
|
if (!root) return;
|
|
14265
14447
|
trackerFor(root).observe(session, event);
|
|
14448
|
+
if (event.type === "system/message") setTimeout(() => {
|
|
14449
|
+
try {
|
|
14450
|
+
maybeAnchor(session, root);
|
|
14451
|
+
} catch {}
|
|
14452
|
+
}, 0);
|
|
14266
14453
|
} catch {}
|
|
14267
14454
|
});
|
|
14455
|
+
/** 造锚定历史(幂等;防重靠 `anchoredSessions`)。 */
|
|
14456
|
+
const maybeAnchor = (session, _root) => {
|
|
14457
|
+
const s = session;
|
|
14458
|
+
s?.header?.delegationDepth;
|
|
14459
|
+
const sid = typeof s?.id === "string" ? s.id : void 0;
|
|
14460
|
+
if (sid !== void 0 && anchoredSessions.has(sid)) return;
|
|
14461
|
+
if (sid !== void 0 && (sid.startsWith("cheap-worker-") || isSkiffSessionId(sid))) return;
|
|
14462
|
+
if (hasAssistantHistory(session)) return;
|
|
14463
|
+
const wrote = appendAnchorHistory(session, SETTINGS.anchorMessages);
|
|
14464
|
+
if (wrote && sid !== void 0) anchoredSessions.add(sid);
|
|
14465
|
+
if (wrote) warnOnce(`anchor history fabricated: ${SETTINGS.anchorMessages.length} 条 user 消息(不再真跑)`);
|
|
14466
|
+
};
|
|
14268
14467
|
ctx.on("agent/inbox/inserted", ({ agent, message }) => {
|
|
14269
14468
|
try {
|
|
14270
14469
|
if (!agentRoot(agent)) return;
|
|
14271
14470
|
const session = agent.session;
|
|
14272
|
-
const depth = session?.header?.delegationDepth ?? 0;
|
|
14273
14471
|
const sid = typeof session?.id === "string" ? session.id : void 0;
|
|
14274
14472
|
if (sid !== void 0 && (sid.startsWith("cheap-worker-") || isSkiffSessionId(sid))) return;
|
|
14275
|
-
if (depth === 0) {
|
|
14276
|
-
if (hasUserMessageHistory(session)) return;
|
|
14277
|
-
} else if (sid !== void 0 && anchoredSessions.has(sid)) return;
|
|
14278
14473
|
if (message?.source?.kind === "plugin:dsh-serenity-hooks") return;
|
|
14279
|
-
const inbox = agent.inbox;
|
|
14280
|
-
if (!inbox?.prepend) return;
|
|
14281
|
-
for (let i = SETTINGS.anchorMessages.length - 1; i >= 0; i--) inbox.prepend("next-turn", {
|
|
14282
|
-
id: `bootstrap-anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}-${i}`,
|
|
14283
|
-
role: "user",
|
|
14284
|
-
content: [{
|
|
14285
|
-
type: "text",
|
|
14286
|
-
text: SETTINGS.anchorMessages[i]
|
|
14287
|
-
}],
|
|
14288
|
-
source: {
|
|
14289
|
-
kind: ACC_MESSAGE_KIND,
|
|
14290
|
-
form: "notice",
|
|
14291
|
-
summary: "bootstrap anchor turn"
|
|
14292
|
-
}
|
|
14293
|
-
});
|
|
14294
|
-
if (sid !== void 0) anchoredSessions.add(sid);
|
|
14295
|
-
warnOnce(`anchor turns injected: ${SETTINGS.anchorMessages.length} 条(${SETTINGS.anchorMessages[0].slice(0, 40)}…)`);
|
|
14296
14474
|
} catch {}
|
|
14297
14475
|
});
|
|
14298
14476
|
let warned = false;
|
|
@@ -14330,8 +14508,9 @@ function registerBootstrap(ctx) {
|
|
|
14330
14508
|
tools: tools.filter((tool) => typeof tool.name === "string" && keep.has(tool.name))
|
|
14331
14509
|
};
|
|
14332
14510
|
}
|
|
14511
|
+
if (status.boundary < 0) return assembled;
|
|
14333
14512
|
const keep = new Set(SETTINGS.bootstrapTools);
|
|
14334
|
-
|
|
14513
|
+
for (const toolName of SETTINGS.compactionTools) keep.add(toolName);
|
|
14335
14514
|
const missing = [...keep].filter((name) => !available.has(name));
|
|
14336
14515
|
if (missing.length > 0) {
|
|
14337
14516
|
warnOnce(`expected bootstrap tools missing=${JSON.stringify(missing)} — bootstrap disabled, full catalog exposed`);
|
|
@@ -16141,8 +16320,13 @@ const HOST_SERVICES = [
|
|
|
16141
16320
|
name: "subagents",
|
|
16142
16321
|
access: "lazy",
|
|
16143
16322
|
members: [{
|
|
16323
|
+
name: "startActivation",
|
|
16324
|
+
kind: "function",
|
|
16325
|
+
anyOf: "spawn-starter"
|
|
16326
|
+
}, {
|
|
16144
16327
|
name: "start",
|
|
16145
|
-
kind: "function"
|
|
16328
|
+
kind: "function",
|
|
16329
|
+
anyOf: "spawn-starter"
|
|
16146
16330
|
}],
|
|
16147
16331
|
impact: "cheap-worker 委派不可用(前台委派的正门,响亮报错)",
|
|
16148
16332
|
required: false
|
|
@@ -16388,13 +16572,30 @@ function probeHostContract(ctx, hostVersion = null) {
|
|
|
16388
16572
|
for (const member of svc.members) {
|
|
16389
16573
|
checked += 1;
|
|
16390
16574
|
const actual = value[member.name];
|
|
16391
|
-
if (!(member.kind === "function" ? typeof actual === "function" : actual !== void 0))
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16575
|
+
if (!(member.kind === "function" ? typeof actual === "function" : actual !== void 0)) {
|
|
16576
|
+
if (member.anyOf !== void 0) {
|
|
16577
|
+
const siblings = svc.members.filter((m) => m.anyOf === member.anyOf);
|
|
16578
|
+
if (siblings.some((m) => {
|
|
16579
|
+
const v = value[m.name];
|
|
16580
|
+
return m.kind === "function" ? typeof v === "function" : v !== void 0;
|
|
16581
|
+
})) continue;
|
|
16582
|
+
issues.push({
|
|
16583
|
+
id: `${svc.id}.${member.anyOf}`,
|
|
16584
|
+
kind: "member",
|
|
16585
|
+
detail: `service "${svc.name}" is missing every ${member.kind} of "${member.anyOf}" (${siblings.map((m) => m.name).join(" | ")})`,
|
|
16586
|
+
impact: svc.impact,
|
|
16587
|
+
required: svc.required
|
|
16588
|
+
});
|
|
16589
|
+
continue;
|
|
16590
|
+
}
|
|
16591
|
+
issues.push({
|
|
16592
|
+
id: `${svc.id}.${member.name}`,
|
|
16593
|
+
kind: "member",
|
|
16594
|
+
detail: `service "${svc.name}" is missing ${member.kind} "${member.name}"`,
|
|
16595
|
+
impact: svc.impact,
|
|
16596
|
+
required: svc.required
|
|
16597
|
+
});
|
|
16598
|
+
}
|
|
16398
16599
|
}
|
|
16399
16600
|
}
|
|
16400
16601
|
const version = checkHostVersion(hostVersion);
|
package/lib/seams/bootstrap.d.ts
CHANGED
|
@@ -59,6 +59,58 @@ export declare const DEFAULT_ANCHOR_MESSAGES: string[];
|
|
|
59
59
|
* 统一经 sessionEvents() 读取(snapshotEvents() 优先,.events 兜底)。
|
|
60
60
|
*/
|
|
61
61
|
export declare function hasUserMessageHistory(session: unknown): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 会话里是否已有模型回复(= 已经不是首轮)。
|
|
64
|
+
*
|
|
65
|
+
* 🔴 这是锚定的**形状判据**(v1.62):锚定只能在**首轮**造。
|
|
66
|
+
* ⛔ 不能用 `hasUserMessageHistory`:宿主在 system 之后、真人 prompt 之前就写了
|
|
67
|
+
* ACC 横幅,而锚定实际执行点(`setTimeout(0)`)更晚 ⇒ 真人历史**必然已存在** ⇒ 恒真。
|
|
68
|
+
* 而 `assistant/message` 只在**模型真的答过一轮之后**才出现 ⇒ 它在首轮时必然为空。
|
|
69
|
+
*/
|
|
70
|
+
export declare function hasAssistantHistory(session: unknown): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* 把 first-anchor 的锚定话语**直接写进会话日志**,取代「真跑两轮」。
|
|
73
|
+
*
|
|
74
|
+
* ## 🔴 两条硬约束(宿主 v4 格式校验器,实测逐条踩过)
|
|
75
|
+
*
|
|
76
|
+
* **约束 A — 只能写 `user/message`**:`system/message` 与 `assistant/message` 都在
|
|
77
|
+
* `STEP_EVENT_TYPES` 里 ⇒ 必须过 `requireStep()`(`data.turn`/`data.step` 精确匹配
|
|
78
|
+
* **当前打开的** turn 与 step,且为 null 时同样拒绝)。而本函数在 `agent/pre-step`
|
|
79
|
+
* 前后被调用时 **turn/step 都还没打开**(`turn/start` 在 preStep 之前,`step/start`
|
|
80
|
+
* 在 preStep 之后)⇒ 代写它们**结构性非法**。
|
|
81
|
+
* ⚠️ 症状:写通道不校验 ⇒ **首轮正常**;**重新进入**时重放才抛
|
|
82
|
+
* `system/message does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
|
|
83
|
+
* ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案因此作废**。
|
|
84
|
+
*
|
|
85
|
+
* **约束 B — 必须写在宿主提交 `system/message` 之后**:格式层有 `protectedHead`
|
|
86
|
+
* 机制 —— `foldSurface()` 里 `system/message` 若在 **surface 非空且尚无 protectedHead**
|
|
87
|
+
* 时到来,直接抛 `system/message requires a protected first surface head`。
|
|
88
|
+
* 也就是说:**surface 的第一个节点必须是 system**。
|
|
89
|
+
* ⇒ 若我们先写 user 消息,宿主随后的 system 就落不到首位 ⇒ 重放同样判 corrupt。
|
|
90
|
+
* ⚠️ 这条是**第一次修复后仍然翻车**的原因(同一会话、不同报错),
|
|
91
|
+
* 判据仍是「重启宿主后重新进入该会话」。
|
|
92
|
+
*
|
|
93
|
+
* ⇒ 因此调用时机 = **`session/event` 里看到该会话第一条 `system/message` 之后**(见下方注册处),
|
|
94
|
+
* 而不是 `agent/inbox/inserted`(那太早)。
|
|
95
|
+
*
|
|
96
|
+
* ## 为什么去掉 assistant 那半不损失什么
|
|
97
|
+
*
|
|
98
|
+
* 锚定话语的**唯一目的**是让模型认为自己已经同意过协作协议。
|
|
99
|
+
* 保留 **2 条 `user/message`** 即可达成;丢掉的只是"模型自己的 ack 回复"——
|
|
100
|
+
* 而那原本也是**我们伪造的**,不是模型真说过的。
|
|
101
|
+
*
|
|
102
|
+
* ## 判据(改这个函数前后都必须跑)
|
|
103
|
+
*
|
|
104
|
+
* ① 单测钉形状(只许 user/message);
|
|
105
|
+
* ② **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
|
|
106
|
+
* (前两次修复都是"单测全绿 + 首轮正常"却仍然 corrupt)。
|
|
107
|
+
* ③ 直接调宿主自己的 `assertReleasedV4Relationships()` 判形状,**不要自写复刻**。
|
|
108
|
+
*
|
|
109
|
+
* @param session 会话(需有 append)
|
|
110
|
+
* @param anchorMessages 锚定话语序列(缺省协议固有两条)
|
|
111
|
+
* @returns 是否成功写入
|
|
112
|
+
*/
|
|
113
|
+
export declare function appendAnchorHistory(session: unknown, anchorMessages?: string[]): boolean;
|
|
62
114
|
interface PromotionStatus {
|
|
63
115
|
/** 最后一次 compaction/end 的 seq(-1 = 未压缩过) */
|
|
64
116
|
boundary: number;
|
|
@@ -85,9 +137,18 @@ interface PromotionTracker {
|
|
|
85
137
|
* 通常 ≤ 2 < 默认兜底 3,不受影响。
|
|
86
138
|
*/
|
|
87
139
|
export declare function createEpochPromotion(promoteEvents: Set<'tool/call' | 'assistant/message'>, requiredSignals?: number, maxRoundsFallback?: number): PromotionTracker;
|
|
88
|
-
/** 协议固有 bootstrap
|
|
89
|
-
*
|
|
90
|
-
*
|
|
140
|
+
/** 协议固有 bootstrap 设置(v1.62 / D140)。
|
|
141
|
+
*
|
|
142
|
+
* 🔴 **zeroTools 必须为 false**:v1.62 起锚定历史**只造 `user/message`**(不含伪造的
|
|
143
|
+
* `assistant/message`)⇒ **首请求时晋升信号尚未满足** ⇒ 若仍 `zeroTools: true`,
|
|
144
|
+
* 首请求会真的退化成「0 工具」,模型看不到任何 ACC 工具。
|
|
145
|
+
*
|
|
146
|
+
* 🔴 **为什么不改回去伪造一条 assistant**:宿主 v4 校验要求 `assistant/message` 落在
|
|
147
|
+
* 已打开的 turn+step 内(见 `appendAnchorHistory` 头注释),而锚定写入时 turn/step
|
|
148
|
+
* 都还没打开 ⇒ 伪造它**结构性非法**,代价是「重新进入会话即 corrupt」。
|
|
149
|
+
*
|
|
150
|
+
* ⇒ 取舍:**首请求就给完整工具目录**(放弃「首请求窄目录」这个 cache 断点优化),
|
|
151
|
+
* 换来**日志合法**。`bootstrapTools` 因此只是历史字段,`boundary < 0` 分支已让位。 */
|
|
91
152
|
export declare function resolveBootstrapSettings(): BootstrapSettings;
|
|
92
153
|
export declare function registerBootstrap(ctx: Context): void;
|
|
93
154
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1
|
|
3
|
+
"version": "1.63.1",
|
|
4
|
+
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|