@shgroup/dsh-serenity-hooks 1.107.0 → 1.109.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/client.js +151 -0
- package/lib/index.js +39 -3
- package/lib/{service-xvjwsX3p.js → service-CCzsVeN_.js} +12 -2
- package/lib/{subscriptions-CyrfNpcG.js → subscriptions-DBR99OVm.js} +70 -22
- package/lib/tools/diag/service.d.ts +11 -0
- package/lib/trajectory/subscriptions.d.ts +27 -0
- 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.109.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -4754,10 +4754,161 @@ Instruction-following style:
|
|
|
4754
4754
|
title: "会话清理",
|
|
4755
4755
|
desc: "删除 N 天前无活动的 DSH 会话(手动,不可恢复)",
|
|
4756
4756
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionCleanupBlock, {})
|
|
4757
|
+
}),
|
|
4758
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapse, {
|
|
4759
|
+
title: "轨迹订阅",
|
|
4760
|
+
desc: "观察者盯一组成员,任一轮结束即通知(只读;配置在 serenity.json)",
|
|
4761
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsBlock, {})
|
|
4757
4762
|
})
|
|
4758
4763
|
]
|
|
4759
4764
|
});
|
|
4760
4765
|
}
|
|
4766
|
+
/**
|
|
4767
|
+
* 「轨迹订阅」块(v1.108.0,owner 2026-11-01 P3)。
|
|
4768
|
+
*
|
|
4769
|
+
* 🔴 **只读**,且刻意分两栏显示「意图」与「实际」:
|
|
4770
|
+
* · **意图栏** = 配置里写了谁盯谁;**实际栏** = 真的发生过投递的键。
|
|
4771
|
+
* · 两者**必须分开看**:一个配了却不出现在实际栏的订阅 = **还没触发过**(不是"在工作");
|
|
4772
|
+
* 出现在实际栏且 `failed>0` = **触发了但送不到**(那才是要修的)。
|
|
4773
|
+
* 把这两件事合成一句话,人就分不出来了(这正是 diag ⑤ 段同一个理由)。
|
|
4774
|
+
*
|
|
4775
|
+
* ⚠️ 计数是**进程内**的(重启清零)—— UI 上必须写明,否则"0"会被读成"从来没配过"。
|
|
4776
|
+
*/
|
|
4777
|
+
function SubscriptionsBlock() {
|
|
4778
|
+
const [data, setData] = (0, react.useState)(null);
|
|
4779
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
4780
|
+
const [error, setError] = (0, react.useState)(null);
|
|
4781
|
+
const load = async () => {
|
|
4782
|
+
setBusy(true);
|
|
4783
|
+
setError(null);
|
|
4784
|
+
try {
|
|
4785
|
+
const res = await fetch("/serenity/subscriptions", { headers: {
|
|
4786
|
+
accept: "application/json",
|
|
4787
|
+
"x-serenity-ui": "1"
|
|
4788
|
+
} });
|
|
4789
|
+
if (!res.ok) {
|
|
4790
|
+
const body = await res.json().catch(() => null);
|
|
4791
|
+
setError(body?.error ?? `HTTP ${res.status}`);
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
setData(await res.json());
|
|
4795
|
+
} catch (err) {
|
|
4796
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4797
|
+
} finally {
|
|
4798
|
+
setBusy(false);
|
|
4799
|
+
}
|
|
4800
|
+
};
|
|
4801
|
+
(0, react.useEffect)(() => {
|
|
4802
|
+
load();
|
|
4803
|
+
}, []);
|
|
4804
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4805
|
+
className: "ss-field",
|
|
4806
|
+
children: [
|
|
4807
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4808
|
+
className: "ss-row",
|
|
4809
|
+
style: {
|
|
4810
|
+
display: "flex",
|
|
4811
|
+
alignItems: "center",
|
|
4812
|
+
gap: 8
|
|
4813
|
+
},
|
|
4814
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4815
|
+
type: "button",
|
|
4816
|
+
className: "ss-btn",
|
|
4817
|
+
disabled: busy,
|
|
4818
|
+
onClick: () => void load(),
|
|
4819
|
+
children: busy ? "读取中…" : "刷新读数"
|
|
4820
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4821
|
+
style: {
|
|
4822
|
+
opacity: .7,
|
|
4823
|
+
fontSize: 12
|
|
4824
|
+
},
|
|
4825
|
+
children: "投递计数只在宿主进程内有效(重启清零);配置全表在 .opencode/serenity.json"
|
|
4826
|
+
})]
|
|
4827
|
+
}),
|
|
4828
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4829
|
+
className: "ss-error",
|
|
4830
|
+
children: ["⚠ ", error]
|
|
4831
|
+
}),
|
|
4832
|
+
data !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4833
|
+
data.problems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4834
|
+
className: "ss-error",
|
|
4835
|
+
children: [
|
|
4836
|
+
"⚠ 配置坏项 ",
|
|
4837
|
+
data.problems.length,
|
|
4838
|
+
" 条(配了但不生效):",
|
|
4839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4840
|
+
style: { margin: "4px 0 0 16px" },
|
|
4841
|
+
children: data.problems.map((p, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: p }, i))
|
|
4842
|
+
})
|
|
4843
|
+
]
|
|
4844
|
+
}),
|
|
4845
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
4846
|
+
style: { margin: "12px 0 4px" },
|
|
4847
|
+
children: "配置(意图:谁盯谁)"
|
|
4848
|
+
}),
|
|
4849
|
+
data.subscriptions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4850
|
+
style: { opacity: .7 },
|
|
4851
|
+
children: "(未配置任何订阅 —— 在 .opencode/serenity.json 里写 trajectorySubscriptions)"
|
|
4852
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4853
|
+
style: { margin: "0 0 0 16px" },
|
|
4854
|
+
children: data.subscriptions.map((s, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
4855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: s.watcher }),
|
|
4856
|
+
" 盯 ",
|
|
4857
|
+
s.members.join("、"),
|
|
4858
|
+
s.note !== null ? `(${s.note})` : ""
|
|
4859
|
+
] }, i))
|
|
4860
|
+
}),
|
|
4861
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
4862
|
+
style: { margin: "12px 0 4px" },
|
|
4863
|
+
children: "投递(实际:发生了没有)"
|
|
4864
|
+
}),
|
|
4865
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4866
|
+
style: {
|
|
4867
|
+
opacity: .75,
|
|
4868
|
+
fontSize: 12,
|
|
4869
|
+
marginBottom: 4
|
|
4870
|
+
},
|
|
4871
|
+
children: [
|
|
4872
|
+
"机制计数:收到 agent/status ",
|
|
4873
|
+
data.eval.statusEvents,
|
|
4874
|
+
" 次 | running ",
|
|
4875
|
+
data.eval.runningSeen,
|
|
4876
|
+
" / idle",
|
|
4877
|
+
" ",
|
|
4878
|
+
data.eval.idleSeen,
|
|
4879
|
+
" | 判成一轮结束 ",
|
|
4880
|
+
data.eval.turnFinished,
|
|
4881
|
+
Object.keys(data.eval.skips).length > 0 && ` | 跳过 ${Object.entries(data.eval.skips).map(([k, v]) => `${k}=${v}`).join("、")}`
|
|
4882
|
+
]
|
|
4883
|
+
}),
|
|
4884
|
+
data.eval.statusEvents === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4885
|
+
style: {
|
|
4886
|
+
opacity: .7,
|
|
4887
|
+
fontSize: 12
|
|
4888
|
+
},
|
|
4889
|
+
children: "⚠ 一次 agent/status 都没收到 —— 可能此刻没人跑轮次,也可能订阅未装配;两者在读数上同形。"
|
|
4890
|
+
}),
|
|
4891
|
+
data.deliveries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4892
|
+
style: { opacity: .7 },
|
|
4893
|
+
children: "(本进程内还没有任何投递发生 —— 配了但没出现 = 还没触发过,不等于在工作)"
|
|
4894
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4895
|
+
style: { margin: "0 0 0 16px" },
|
|
4896
|
+
children: data.deliveries.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
4897
|
+
d.key,
|
|
4898
|
+
":成功 ",
|
|
4899
|
+
d.ok,
|
|
4900
|
+
" / 失败 ",
|
|
4901
|
+
d.failed,
|
|
4902
|
+
d.failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4903
|
+
className: "ss-error",
|
|
4904
|
+
children: [" ⚠ ", d.lastError ?? "(无说明)"]
|
|
4905
|
+
})
|
|
4906
|
+
] }, i))
|
|
4907
|
+
})
|
|
4908
|
+
] })
|
|
4909
|
+
]
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4761
4912
|
/** 「会话清理」块(v1.29 需求③,S142 用户拍板:lastActive + 手动按钮 + 直接物理删):
|
|
4762
4913
|
* 天数输入 → GET 预览(dryRun 列表)→ 确认 → POST 执行。live 会话由服务端保护跳过。 */
|
|
4763
4914
|
function SessionCleanupBlock() {
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
2
|
import { c as hostAgents, d as hostSessions, f as hostSettings, i as cccRootForExec, m as hostWebServer, n as agentCwdFor, o as listCccs, p as hostWeb, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-DPMwBywc.js";
|
|
3
|
-
import { a as findSerenityRoot$1, d as readCccName, g as resolveInside, l as pathInside, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-BmpE3cQz.js";
|
|
3
|
+
import { a as findSerenityRoot$1, d as readCccName, g as resolveInside, l as pathInside, m as readTrajectorySubscriptions, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-BmpE3cQz.js";
|
|
4
4
|
import { A as useSession, C as resolveSessionByTitle, D as showSession, E as setActiveSessionInfo, F as isoLocal, I as localFileStamp, M as TRAJECTORY_LEGACY_FILENAMES, O as summarize, S as readTrajectoryUid, T as sessionsRoot, _ as listSessions, b as parseTrajectoryCode, c as UID_FILENAME, d as createSession, f as extractSessionMdPathFromText, j as TRAJECTORY_FILENAME, l as archiveSession, m as getActiveSessionInfo, o as COGNITIVE_TRAJECTORY_ACTIONS, p as findSession, u as clearActiveSessionInfo, v as namingTitleFor, w as sessionEvents, y as parseSessionContextFromEvents } from "./bind-file-B0TuOP89.js";
|
|
5
5
|
import { n as checkLocalstoreGitCompliance, o as readStore, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./service-Z3aSR1cn.js";
|
|
6
6
|
import { d as findPackageRoot, i as constraintSystemPromptSource, l as resolveConstraintSystemPrompt, r as constraintCognitiveTrajectory, t as TRAJECTORY_CONSTRAINT_HINT, u as ACC_VERSION } from "./rules-BLkYTPUy.js";
|
|
@@ -13,7 +13,7 @@ import { i as sessionsRootDir } from "./cleanup-CPl1SSaK.js";
|
|
|
13
13
|
import { n as readSimpleSettings, r as registerSettingsSection } from "./settings-DY4_DYYY.js";
|
|
14
14
|
import { a as readFeishuCredential, c as readWeixinSettings, d as weixinInboundDir, f as weixinSessionIdForTrajectory, i as matchWeixinRoute, n as extractWeixinText, r as hasVoiceItem, s as readWeixinCredential, t as extractWeixinMedia, u as sanitizeFileName$1 } from "./route-reyv8qcB.js";
|
|
15
15
|
import { i as runImBridge, n as isImWiredTrajectory, r as registerImChannel, t as imChannelIds } from "./channels-Dty_NIl_.js";
|
|
16
|
-
import { n as registerTrajectorySubscriptions, t as defaultSubscriptionDeps } from "./subscriptions-
|
|
16
|
+
import { i as subscriptionStats, n as registerTrajectorySubscriptions, r as subscriptionEval, t as defaultSubscriptionDeps } from "./subscriptions-DBR99OVm.js";
|
|
17
17
|
import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-CsZ-f64q.js";
|
|
18
18
|
import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
|
|
19
19
|
import { createRequire } from "node:module";
|
|
@@ -569,7 +569,7 @@ async function runKit(root, args, hostCtx) {
|
|
|
569
569
|
}
|
|
570
570
|
case "diag": {
|
|
571
571
|
if (hostCtx === void 0) throw new Error("diag requires the plugin context (live session data unavailable without it)");
|
|
572
|
-
const { runDiag, renderDiag } = await import("./service-
|
|
572
|
+
const { runDiag, renderDiag } = await import("./service-CCzsVeN_.js");
|
|
573
573
|
return renderDiag(await runDiag(hostCtx, root ?? ""));
|
|
574
574
|
}
|
|
575
575
|
default: throw new Error(`Unknown action: ${args.action}`);
|
|
@@ -9184,6 +9184,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
|
|
|
9184
9184
|
const CONFIG_PATH = "/serenity/config";
|
|
9185
9185
|
const CCCS_PATH = "/serenity/cccs";
|
|
9186
9186
|
const WEIXIN_PATH = "/serenity/weixin";
|
|
9187
|
+
const SUBSCRIPTIONS_PATH = "/serenity/subscriptions";
|
|
9187
9188
|
/** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
|
|
9188
9189
|
const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
|
|
9189
9190
|
const IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -9519,6 +9520,41 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
9519
9520
|
}
|
|
9520
9521
|
}
|
|
9521
9522
|
});
|
|
9523
|
+
ctx.webServer.register({
|
|
9524
|
+
kind: "exact",
|
|
9525
|
+
path: SUBSCRIPTIONS_PATH,
|
|
9526
|
+
handler: async (req, res) => {
|
|
9527
|
+
try {
|
|
9528
|
+
if (req.method !== "GET") {
|
|
9529
|
+
sendJson$2(res, 405, { error: "method not allowed" });
|
|
9530
|
+
return;
|
|
9531
|
+
}
|
|
9532
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
9533
|
+
const workspace = resolveWorkspace(ctx, {
|
|
9534
|
+
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
9535
|
+
workspace: url.searchParams.get("workspace") ?? void 0
|
|
9536
|
+
});
|
|
9537
|
+
const root = cccRootForCwd(workspace) ?? "";
|
|
9538
|
+
const { subs, problems } = root ? readTrajectorySubscriptions(root) : {
|
|
9539
|
+
subs: [],
|
|
9540
|
+
problems: []
|
|
9541
|
+
};
|
|
9542
|
+
sendJson$2(res, 200, {
|
|
9543
|
+
root,
|
|
9544
|
+
subscriptions: subs.map((s) => ({
|
|
9545
|
+
watcher: s.watcher,
|
|
9546
|
+
members: s.members,
|
|
9547
|
+
note: s.note ?? null
|
|
9548
|
+
})),
|
|
9549
|
+
problems,
|
|
9550
|
+
deliveries: subscriptionStats(),
|
|
9551
|
+
eval: subscriptionEval()
|
|
9552
|
+
});
|
|
9553
|
+
} catch (err) {
|
|
9554
|
+
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
});
|
|
9522
9558
|
ctx.webServer.register({
|
|
9523
9559
|
kind: "exact",
|
|
9524
9560
|
path: WEIXIN_PATH,
|
|
@@ -3,7 +3,7 @@ import { m as readTrajectorySubscriptions } from "./ccc-BmpE3cQz.js";
|
|
|
3
3
|
import { F as isoLocal, w as sessionEvents } from "./bind-file-B0TuOP89.js";
|
|
4
4
|
import { m as WAKE_CATCH_UP_MS, p as listCroCognitiveTrajectories } from "./scheduler-B0pB9mxI.js";
|
|
5
5
|
import { n as readSimpleSettings } from "./settings-DY4_DYYY.js";
|
|
6
|
-
import {
|
|
6
|
+
import { i as subscriptionStats, r as subscriptionEval } from "./subscriptions-DBR99OVm.js";
|
|
7
7
|
import { containerClocks, containerWakes } from "./observation-BWpkA865.js";
|
|
8
8
|
//#region src/trajectory/live.ts
|
|
9
9
|
/**
|
|
@@ -187,7 +187,8 @@ async function runDiag(ctx, root, nowMs = Date.now()) {
|
|
|
187
187
|
},
|
|
188
188
|
nowMs,
|
|
189
189
|
subscriptions: subscriptionStats(),
|
|
190
|
-
subscriptionProblems: readTrajectorySubscriptions(root).problems
|
|
190
|
+
subscriptionProblems: readTrajectorySubscriptions(root).problems,
|
|
191
|
+
subscriptionEval: subscriptionEval()
|
|
191
192
|
};
|
|
192
193
|
}
|
|
193
194
|
/** 人读渲染一行唤醒条目 */
|
|
@@ -234,6 +235,15 @@ function renderDiag(r) {
|
|
|
234
235
|
}
|
|
235
236
|
lines.push("");
|
|
236
237
|
lines.push(`── ⑤ 轨迹消息订阅(投递读数;只列发生过的投递,配置全表见 serenity.json)──`);
|
|
238
|
+
{
|
|
239
|
+
const e = r.subscriptionEval;
|
|
240
|
+
const skipText = Object.keys(e.skips).length === 0 ? "无" : Object.entries(e.skips).map(([k, v]) => `${k}=${v}`).join(" / ");
|
|
241
|
+
lines.push(`机制计数: 收到 agent/status ${e.statusEvents} 次|running ${e.runningSeen}|idle ${e.idleSeen}|判成一轮结束 ${e.turnFinished}|跳过: ${skipText}`);
|
|
242
|
+
if (e.statusEvents === 0) {
|
|
243
|
+
lines.push(" ⚠ 一次 agent/status 都没收到 ⇒ 要么此刻真的没人在跑轮次,要么订阅未装配。");
|
|
244
|
+
lines.push(" 两者在读数上同形 —— 判\"未装配\"要看宿主日志里有没有「轨迹消息订阅未装配」。");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
237
247
|
if (r.subscriptionProblems.length > 0) {
|
|
238
248
|
lines.push(`⚠ 配置坏项 ${r.subscriptionProblems.length} 条(**配了但不生效**):`);
|
|
239
249
|
for (const p of r.subscriptionProblems) lines.push(` · ${p}`);
|
|
@@ -57,6 +57,34 @@ import { d as registerDisposer } from "./scheduler-B0pB9mxI.js";
|
|
|
57
57
|
* 它有上界(键 = 订阅数),且随插件卸载清空({@link registerTrajectorySubscriptions} 的拆卸)。
|
|
58
58
|
*/
|
|
59
59
|
const stats = /* @__PURE__ */ new Map();
|
|
60
|
+
const evalStats = {
|
|
61
|
+
statusEvents: 0,
|
|
62
|
+
runningSeen: 0,
|
|
63
|
+
idleSeen: 0,
|
|
64
|
+
turnFinished: 0,
|
|
65
|
+
skips: {}
|
|
66
|
+
};
|
|
67
|
+
/** 评估计数快照(诊断/面板读) */
|
|
68
|
+
function subscriptionEval() {
|
|
69
|
+
return {
|
|
70
|
+
...evalStats,
|
|
71
|
+
skips: { ...evalStats.skips }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function bump(key) {
|
|
75
|
+
evalStats[key] += 1;
|
|
76
|
+
}
|
|
77
|
+
function bumpSkip(reason) {
|
|
78
|
+
evalStats.skips[reason] = (evalStats.skips[reason] ?? 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
/** 测试用:评估计数复位 */
|
|
81
|
+
function __resetSubscriptionEvalForTest() {
|
|
82
|
+
evalStats.statusEvents = 0;
|
|
83
|
+
evalStats.runningSeen = 0;
|
|
84
|
+
evalStats.idleSeen = 0;
|
|
85
|
+
evalStats.turnFinished = 0;
|
|
86
|
+
evalStats.skips = {};
|
|
87
|
+
}
|
|
60
88
|
function statOf(key) {
|
|
61
89
|
const cur = stats.get(key);
|
|
62
90
|
if (cur) return cur;
|
|
@@ -120,30 +148,42 @@ function buildSubscriptionText(memberDir, sub, atMs, iso) {
|
|
|
120
148
|
*/
|
|
121
149
|
async function onTurnFinished(deps, sessionId, nowMs) {
|
|
122
150
|
const root = deps.rootForSession(sessionId);
|
|
123
|
-
if (root === null)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
151
|
+
if (root === null) {
|
|
152
|
+
bumpSkip("no-ccc");
|
|
153
|
+
return {
|
|
154
|
+
skipped: "no-ccc",
|
|
155
|
+
delivered: 0,
|
|
156
|
+
failed: 0
|
|
157
|
+
};
|
|
158
|
+
}
|
|
128
159
|
const { subs, problems } = deps.readSubs(root);
|
|
129
160
|
for (const p of problems) deps.warn(`[serenity-hooks] ✗ 轨迹订阅配置有坏项(${root}):${p}`);
|
|
130
|
-
if (subs.length === 0)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
if (subs.length === 0) {
|
|
162
|
+
bumpSkip("no-subscriptions");
|
|
163
|
+
return {
|
|
164
|
+
skipped: "no-subscriptions",
|
|
165
|
+
delivered: 0,
|
|
166
|
+
failed: 0
|
|
167
|
+
};
|
|
168
|
+
}
|
|
135
169
|
const memberDir = deps.dirForSession(root, sessionId);
|
|
136
|
-
if (memberDir === null)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
170
|
+
if (memberDir === null) {
|
|
171
|
+
bumpSkip("unbound-member");
|
|
172
|
+
return {
|
|
173
|
+
skipped: "unbound-member",
|
|
174
|
+
delivered: 0,
|
|
175
|
+
failed: 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
141
178
|
const hits = pickSubscriptionsFor(subs, memberDir);
|
|
142
|
-
if (hits.length === 0)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
if (hits.length === 0) {
|
|
180
|
+
bumpSkip("not-in-any-members");
|
|
181
|
+
return {
|
|
182
|
+
skipped: "unbound-member",
|
|
183
|
+
delivered: 0,
|
|
184
|
+
failed: 0
|
|
185
|
+
};
|
|
186
|
+
}
|
|
147
187
|
let delivered = 0;
|
|
148
188
|
let failed = 0;
|
|
149
189
|
for (const sub of hits) {
|
|
@@ -192,11 +232,13 @@ function registerTrajectorySubscriptions(ctx, deps) {
|
|
|
192
232
|
const lastStatus = /* @__PURE__ */ new Map();
|
|
193
233
|
try {
|
|
194
234
|
ctx.on("agent/status", (payload) => {
|
|
235
|
+
bump("statusEvents");
|
|
195
236
|
const p = payload;
|
|
196
237
|
const sessionId = (p?.agent)?.session?.id;
|
|
197
238
|
if (typeof sessionId !== "string" || sessionId === "") return;
|
|
198
239
|
const status = typeof p.status === "string" ? p.status : "";
|
|
199
240
|
if (status === "running") {
|
|
241
|
+
bump("runningSeen");
|
|
200
242
|
lastStatus.set(sessionId, "running");
|
|
201
243
|
return;
|
|
202
244
|
}
|
|
@@ -204,9 +246,14 @@ function registerTrajectorySubscriptions(ctx, deps) {
|
|
|
204
246
|
lastStatus.delete(sessionId);
|
|
205
247
|
return;
|
|
206
248
|
}
|
|
249
|
+
bump("idleSeen");
|
|
207
250
|
const prev = lastStatus.get(sessionId);
|
|
208
251
|
lastStatus.delete(sessionId);
|
|
209
|
-
if (prev !== "running")
|
|
252
|
+
if (prev !== "running") {
|
|
253
|
+
bumpSkip("idle-without-running");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
bump("turnFinished");
|
|
210
257
|
onTurnFinished(deps, sessionId, Date.now()).catch((err) => {
|
|
211
258
|
deps.warn(`[serenity-hooks] ✗ 轨迹订阅处理异常(会话 ${sessionId}):${String(err?.message ?? err)}`);
|
|
212
259
|
});
|
|
@@ -217,6 +264,7 @@ function registerTrajectorySubscriptions(ctx, deps) {
|
|
|
217
264
|
registerDisposer(ctx, "trajectory subscriptions", () => {
|
|
218
265
|
lastStatus.clear();
|
|
219
266
|
stats.clear();
|
|
267
|
+
__resetSubscriptionEvalForTest();
|
|
220
268
|
});
|
|
221
269
|
}
|
|
222
270
|
/**
|
|
@@ -270,4 +318,4 @@ function defaultSubscriptionDeps(ctx) {
|
|
|
270
318
|
};
|
|
271
319
|
}
|
|
272
320
|
//#endregion
|
|
273
|
-
export { registerTrajectorySubscriptions as n,
|
|
321
|
+
export { subscriptionStats as i, registerTrajectorySubscriptions as n, subscriptionEval as r, defaultSubscriptionDeps as t };
|
|
@@ -112,6 +112,17 @@ export interface DiagReport {
|
|
|
112
112
|
}>;
|
|
113
113
|
/** 订阅配置本身的**坏项**(配了但不生效的条目 —— 必须显形,否则用户以为它在工作) */
|
|
114
114
|
subscriptionProblems: string[];
|
|
115
|
+
/**
|
|
116
|
+
* 订阅机制的**评估计数**(v1.108.0):收到多少 `agent/status`、认出多少次一轮结束、各因何跳过。
|
|
117
|
+
* 🔴 它回答的是「**机制在跑吗**」—— 与「送出过东西吗」是两个问题;缺了它两者同形(都是空)。
|
|
118
|
+
*/
|
|
119
|
+
subscriptionEval: {
|
|
120
|
+
statusEvents: number;
|
|
121
|
+
runningSeen: number;
|
|
122
|
+
idleSeen: number;
|
|
123
|
+
turnFinished: number;
|
|
124
|
+
skips: Record<string, number>;
|
|
125
|
+
};
|
|
115
126
|
}
|
|
116
127
|
/**
|
|
117
128
|
* 装配报告(一次调用即全报告)。
|
|
@@ -63,6 +63,33 @@ interface DeliveryStat {
|
|
|
63
63
|
/** 最后一次投递尝试的时刻 */
|
|
64
64
|
lastAt: number | null;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 评估计数(**"这个机制到底有没有在跑"的唯一读数**,v1.108.0)。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 **为什么必须有它**(我自己在第一版里漏掉了):只有 `stats`(投递计数)时,
|
|
70
|
+
* **「机制没装配」与「装配了但从未命中」在面板上完全同形** —— 两者都是空表。
|
|
71
|
+
* 而那正是本文件头批评过的失败形态("配了但不生效"与"没人干活"分不开)。
|
|
72
|
+
* ⇒ 本计数把**事件流**也记下来:收到多少 `agent/status`、认出多少次 running→idle、
|
|
73
|
+
* 每次因何跳过。有它才能回答「它在跑吗」,而不只是「它送出过东西吗」。
|
|
74
|
+
*
|
|
75
|
+
* ⚠️ 上界:固定字段(不是无界 Map)⇒ 结构上不可能"只增不清"。
|
|
76
|
+
*/
|
|
77
|
+
export interface EvalStats {
|
|
78
|
+
/** 收到的 `agent/status` 事件数(含非 running/idle 的) */
|
|
79
|
+
statusEvents: number;
|
|
80
|
+
/** 其中被认成 running 的 */
|
|
81
|
+
runningSeen: number;
|
|
82
|
+
/** 其中被认成 idle 的 */
|
|
83
|
+
idleSeen: number;
|
|
84
|
+
/** 真被判成「一轮结束」(= 有前置 running 的 idle)的次数 */
|
|
85
|
+
turnFinished: number;
|
|
86
|
+
/** 跳过原因计数(键 = `no-ccc` / `no-subscriptions` / `unbound-member`) */
|
|
87
|
+
skips: Record<string, number>;
|
|
88
|
+
}
|
|
89
|
+
/** 评估计数快照(诊断/面板读) */
|
|
90
|
+
export declare function subscriptionEval(): EvalStats;
|
|
91
|
+
/** 测试用:评估计数复位 */
|
|
92
|
+
export declare function __resetSubscriptionEvalForTest(): void;
|
|
66
93
|
/** 投递统计快照(`dashboard diag` 与测试读;空 = 从未有投递尝试) */
|
|
67
94
|
export declare function subscriptionStats(): Array<{
|
|
68
95
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.109.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|