@shgroup/dsh-serenity-hooks 1.57.0 → 1.57.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 CHANGED
@@ -100,7 +100,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
100
100
  | **工作区围墙** | 墙内可用,墙外一律拒绝(路径无法解析) | AI 不应接触工作区之外的内容 |
101
101
  | **黑名单 / 治理文件** | 黑名单可配置;`.serenity` 等治理文件禁止 AI 写入 | 防止 AI 破坏自身所在的"地基" |
102
102
  | **密钥文件守卫** | `localstore.json` 对**所有**工具拒绝(包括 read/grep/glob) | 密钥值在结构上无法外流 |
103
- | **对外输出守卫** | 对外向会话(子角色 / ACP / 重建会话)若答出敏感词,会被打回重答,并告知命中的词与修改方向 | 外部使用者不应看到内部机制 |
103
+ | **对外输出守卫** | 对外向会话(子角色 / 重建会话)若答出敏感词,会被打回重答,并告知命中的词与修改方向 | 外部使用者不应看到内部机制 |
104
104
  | **轨迹提醒** | 持续一段时间后会提醒 AI"把进度写回工作日志",并要求它回一个确认码 | 提醒是机制,不依赖自觉 |
105
105
  | **工作日志体积提醒** | 工作日志(SESSION.md)超过 200KB 时提醒 AI"停下重写",并附四条重写原则(保留骨架 / 细节移至附件文件 / 可合并的合并 / 其余自行判断) | 日志持续增厚则无人读得完;重写成本低于堆积。阈值按工作区可调(`sessionKeeper.sessionMdMaxKB`,0 = 关) |
106
106
  | **重建前交接** | 重建上下文前,要求 AI 先把"手头做到哪了 / 还差什么 / 下一步做什么"写入**本轨迹目录下的 `REBUILD-TODO.md`**(固定文件名);重建后的 AI 首先读取该文件并继续 | 上下文被清空,但在办的事不能丢。该便签是**只用一次的交界材料**:无格式义务、随时可丢;超过 10 KB 会提醒清理(`SCRATCH PRUNE`,整份删除亦可) |
@@ -113,10 +113,15 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
113
113
  | **网页登录入口** | 3081 | 外部/手机访问完整界面:登录后反向代理到 3080,可配工作区白名单 |
114
114
  | **微信主动发送入口** | 3082(只绑 127.0.0.1) | 工作区外的脚本/集成用它给微信发消息(公网不可达,所以不需要密钥)。工作区内的 AI 不走这个端口,直接使用 `im-bridge` 工具 |
115
115
  | **子角色调试页** | 3099(只绑 127.0.0.1) | 调试"子角色"时使用,可切换工作区、查看对话轨迹 |
116
- | **ACP + 对外问答页** | 3100(只绑 127.0.0.1) | 程序化接入(JSON-RPC)+ 给别人用的问答页(key 认证,只返回答案,不返回内部轨迹) |
117
116
  | **微信桥** | 无需端口(出站长轮询) | 家人在微信里直接和 AI 说话 |
118
117
 
119
- > 3081 / 3099 / 3100 **默认关**(须自行开启);3082 **默认开**(微信桥启用时才有意义)。默认只监听 127.0.0.1 的入口,如需暴露到公网,由使用者自行决定方式(隧道 / 反代 / 端口映射都行),插件不绑定任何特定做法。
118
+ > 3081 / 3099 **默认关**(须自行开启);3082 **默认开**(微信桥启用时才有意义)。默认只监听 127.0.0.1 的入口,如需暴露到公网,由使用者自行决定方式(隧道 / 反代 / 端口映射都行),插件不绑定任何特定做法。
119
+
120
+ > 🔴 **已退役:原「ACP + 对外问答页」(3100)**。该面是本插件早期自造的一套私有 HTTP+HTML 协议外壳
121
+ > (无客户端生态),已于 2026-10-15 **整体移除**(连同 `acp-core` / `acp-http` 与相关开关、问答页 key)。
122
+ > **要程序化或从手机/远程接入,走宿主自带的官方 ACP server**:`dsh --profile acp` 起一个纯 stdio、
123
+ > 零认证的 ACP 服务,再由任一现成 ACP 客户端(编辑器如 Zed;手机端如 Agmente、ACP UI)连接。
124
+ > 认证落在**接入层**(隧道 / 反代 / Cloudflare Access),不再由本插件发放 key。
120
125
 
121
126
  ### 3.4 自动配置:用 opencode 的模型
122
127
 
@@ -268,10 +273,18 @@ my-workspace/ ← 工作区根目录(放一个 .serenity
268
273
  - 调试页(3099)可以切工作区、看轨迹;回答用 markdown 渲染,思考过程折叠
269
274
  - `container_admin role validate` 校验配置,`apply` 才真正生效
270
275
 
271
- ### 6.4 对外问答页(3100)
276
+ ### 6.4 对外接入(官方 ACP)
277
+
278
+ 本插件**不自造** ACP 面。要程序化或远程使用,用宿主自带的官方 ACP server:
279
+
280
+ | 步 | 做什么 |
281
+ |---|---|
282
+ | 1 | `dsh --profile acp` —— 起官方 ACP 服务(**纯 stdio、零认证**;首次运行自动建 profile) |
283
+ | 2 | 用任一 ACP 客户端连它(编辑器内直接起子进程;手机/远程经桥 + 隧道) |
284
+ | 3 | **认证放在接入层**(隧道 / 反代 / Cloudflare Access)——官方 server 自身不做认证 |
272
285
 
273
- - **key 认证**(常量时间比较 + 失败 IP 锁定 + 可轮换)+ 容器白名单(留空 = 全部开放)
274
- - **只给答案**:响应里只有 answer / answer_html / sessionId——内部轨迹、工具结果、机制信息都不出去
286
+ > **为什么不再自造**:原 3100 面是一个**没有客户端生态**的私有外壳,而官方件已发布并随宿主同版本演进。
287
+ > 详细接法与手机端配置见宁静号 CCC 的接入指南。
275
288
 
276
289
  ### 6.5 轨迹与定时唤醒(trajectory)
277
290
 
@@ -332,8 +345,8 @@ my-workspace/ ← 工作区根目录(放一个 .serenity
332
345
 
333
346
  | 层 | 位置 | 放什么 |
334
347
  |---|---|---|
335
- | DSH 原生设置 | DSH 的 settings.yaml(即设置面板那几项) | 开关与阈值:网关总开关、重建阈值、子角色开关与调试端口、ACP 与问答页开关、**CRO 总闸(`croEnabled`,默认为开)**、**无人值守代理回复(`unattendedEnabled`,默认为关)**、**图工具总闸(默认关)** |
336
- | 插件全局文件 | `~/.dsh/serenity-hooks.json`(权限 0600) | 网关账号(scrypt + TOTP)、监听地址与端口、工作区白名单、cookie 安全开关、问答页 key |
348
+ | DSH 原生设置 | DSH 的 settings.yaml(即设置面板那几项) | 开关与阈值:网关总开关、重建阈值、子角色开关与调试端口、**CRO 总闸(`croEnabled`,默认为开)**、**无人值守代理回复(`unattendedEnabled`,默认为关)**、**图工具总闸(默认关)** |
349
+ | 插件全局文件 | `~/.dsh/serenity-hooks.json`(权限 0600) | 网关账号(scrypt + TOTP)、监听地址与端口、工作区白名单、cookie 安全开关 |
337
350
  | 工作区配置 | `.opencode/serenity.json` | 助手模型白名单、日志阈值、安全模式黑名单、子角色、**轨迹级 skill 供给(`trajectory.skills`)**、微信(账号/路由/开关) |
338
351
  | 工作区凭据 | `localstore.json` | 密钥与本地偏好;微信机器人 token 也在这一层 |
339
352
 
@@ -408,7 +421,8 @@ pnpm build # 打包(lib/index.js + client.js)
408
421
  先让 AI 把进度写回 SESSION.md,然后按提示调用 `container_trajectory rebuild`。轨迹会自动接续,不用手动开新会话。
409
422
 
410
423
  **Q:对外问答页会返回内部信息吗?**
411
- 不会。只返回答案本身,内部轨迹和工具结果都不出去。
424
+ 该面已于 2026-10-15 移除(改用官方 ACP,见 §6.4)。走官方 ACP 时,**认证与暴露范围由接入层决定**——
425
+ 官方 server 只跑在 stdio 上、不认识网络,把它接到哪里、谁能连,取决于你怎么架隧道。
412
426
 
413
427
  **Q:微信桥里,AI 的回复是怎么发出去的?**
414
428
  默认由插件自动把它的最后一段话转发给用户。如果配了 `autoReplyWithLastMessage: false`,插件就不转了,改由 AI 自己发——所以这时候它如果没发,用户就收不到消息(除非另外开了 `fallbackOnNoSend` 让插件兜底)。
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.57.0",
3
+ "version": "1.57.1",
4
4
  "main": "lib/index.js",
5
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(双档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
@@ -1,7 +1,84 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { a as hostSessions, i as hostService } from "./access-CNccNtgf.js";
3
2
  import { a as findSerenityRoot } from "./ccc-B0ZGL9lx.js";
4
3
  import { basename } from "node:path";
4
+ //#region src/host/access.ts
5
+ /**
6
+ * host/access.ts — 宿主访问收口(S142 review F-06)
7
+ *
8
+ * 为什么存在:dsp 此前在 30+ 个文件里各自用 `(ctx as unknown as {…})` 断言读取宿主
9
+ * 服务——契约表达分散、失败模式各异(多数静默 undefined)。本模块是**唯一**读取宿主
10
+ * 的入口:所有 `ctx.get` / `ctx.<service>` 的形状断言集中在此,调用点只拿类型化结果。
11
+ *
12
+ * 约定:
13
+ * - 只读、无副作用;服务缺失一律返回 `undefined`(调用方决定降级策略),**不抛错**
14
+ * (宿主对插件 apply 抛错 = 整个 dsh 启动失败,访问层不能成为单点)。
15
+ * - 形状以 `AI_LAB/dsh-harness-public` @ 0.1.2-rc.1 为准;成员缺失由
16
+ * `host/contract.ts` 的探针在装载时与 `dashboard health` 中报告。
17
+ * - 迁移是渐进的:新代码必须走本模块;旧调用点逐轮收敛(F-06 轮次记录见 CHANGELOG)。
18
+ */
19
+ /** 通用读取:`ctx.get(name)`(含异常吞掉——服务 getter 抛错视为不可用) */
20
+ function hostService(ctx, name) {
21
+ const c = ctx;
22
+ if (typeof c?.get !== "function") return void 0;
23
+ try {
24
+ return c.get(name);
25
+ } catch {
26
+ return;
27
+ }
28
+ }
29
+ /**
30
+ * 直接属性读取(injected 服务:`ctx.<name>`),属性缺失**或读取抛错**时回落 `ctx.get`。
31
+ *
32
+ * v1.31.4 修复(R↓,实证 v1.31.3 真机首测):cordis 的 Context 是 Proxy,
33
+ * `ReflectService.handler.get`(`vendor/cordis/src/reflect.ts`)对**未经 `inject` 声明**
34
+ * 的服务名会沿 fiber 链查找,找不到时**抛错**而非返回 undefined:
35
+ *
36
+ * cannot get property "<name>" without inject
37
+ *
38
+ * 于是"先直接属性读、失败回落 ctx.get"的写法在**真实 cordis** 下永远走不到回落分支——
39
+ * 异常直接逃逸(`hostService` 的 try/catch 被绕过)。v1.31.3 的 `hostSubagents`
40
+ * 正是这样炸的:`subagents` 是 lazy 服务(不在插件 `inject` 列表内)。
41
+ * 现有 fake ctx 单测(普通对象无 getter)复现不了 → 真 cordis 用例见
42
+ * `tests/host/cordis-access.test.ts`。
43
+ *
44
+ * 为什么吞掉异常而不是让它冒泡:本模块的契约是"服务缺失一律返回 undefined,**不抛错**"
45
+ * (宿主对插件 apply 抛错 = 整个 dsh 启动失败)。读取失败 = 服务不可用,等价于缺失。
46
+ */
47
+ function hostInjected(ctx, name) {
48
+ const c = ctx;
49
+ try {
50
+ const direct = c?.[name];
51
+ if (direct !== void 0) return direct;
52
+ } catch {}
53
+ return hostService(ctx, name);
54
+ }
55
+ /** `ctx.sessions`(injected) */
56
+ function hostSessions(ctx) {
57
+ return hostInjected(ctx, "sessions");
58
+ }
59
+ /** `ctx.agents`(injected) */
60
+ function hostAgents(ctx) {
61
+ return hostInjected(ctx, "agents");
62
+ }
63
+ /** `ctx.webServer`(injected) */
64
+ function hostWebServer(ctx) {
65
+ return hostInjected(ctx, "webServer");
66
+ }
67
+ /** `ctx.settings`(injected;提供 settings 面板装配通道) */
68
+ function hostSettings(ctx) {
69
+ return hostInjected(ctx, "settings");
70
+ }
71
+ /** `ctx.web`(injected;v1.30.12 web_fetch provider 注册通道) */
72
+ function hostWeb(ctx) {
73
+ return hostInjected(ctx, "web");
74
+ }
75
+ function hostSubagents(ctx) {
76
+ return hostInjected(ctx, "subagents");
77
+ }
78
+ function hostLlm(ctx) {
79
+ return hostInjected(ctx, "llm");
80
+ }
81
+ //#endregion
5
82
  //#region src/ccc-roots.ts
6
83
  /**
7
84
  * ccc-roots.ts — CCC 根解析的**唯一判断层**(L1 单根解析 + L2 全量枚举)
@@ -160,4 +237,4 @@ async function listCccs(ctx, opts = {}) {
160
237
  }));
161
238
  }
162
239
  //#endregion
163
- export { ccc_roots_exports as a, cccRootForExec as i, agentCwdFor as n, listCccs as o, cccRootForCwd as r, NO_CCC_FROM_AGENT_CWD as t };
240
+ export { ccc_roots_exports as a, hostInjected as c, hostSessions as d, hostSettings as f, hostWebServer as h, cccRootForExec as i, hostLlm as l, hostWeb as m, agentCwdFor as n, listCccs as o, hostSubagents as p, cccRootForCwd as r, hostAgents as s, NO_CCC_FROM_AGENT_CWD as t, hostService as u };
@@ -75,7 +75,8 @@ export declare function cccRootForExec(execOrAgent: ExecOrAgent): string | null;
75
75
  * **文案逐字不变**(外在行为不变)。
76
76
  */
77
77
  export declare const NO_CCC_FROM_AGENT_CWD = "No CCC found: no .serenity file from agent cwd";
78
- /** 候选 CCC 条目(调试页 CCC 切换器 / 微信桥 / ACP 问答页 / `/serenity/cccs` 共用数据形状) */
78
+ /** 候选 CCC 条目(调试页 CCC 切换器 / 微信桥 / `/serenity/cccs` 共用数据形状)
79
+ * ⚠️ 2026-10-15:原同批还有「ACP 问答页」,其面已退役 ⇒ 消费方少一个(形状不变)。 */
79
80
  export interface CccEntry {
80
81
  /** CCC 根(绝对路径) */
81
82
  root: string;
package/lib/client.js CHANGED
@@ -3207,7 +3207,7 @@ window.__ModuleLoader__.load({
3207
3207
  knownWorkspaces: body.knownWorkspaces ?? []
3208
3208
  };
3209
3209
  }
3210
- /** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋 + 开放容器白名单)→ 返回保存后的 wire */
3210
+ /** PUT 配置(账号 patch + 工作区白名单 + persona 彩蛋)→ 返回保存后的 wire */
3211
3211
  async function saveConfig(patch) {
3212
3212
  const res = await fetch(CONFIG_PATH, {
3213
3213
  method: "PUT",
@@ -3799,286 +3799,6 @@ Instruction-following style:
3799
3799
  });
3800
3800
  }
3801
3801
  //#endregion
3802
- //#region src/client/PublicAskEditor.tsx
3803
- /**
3804
- * PublicAskEditor.tsx — 建议问答页配置(v1.26.2,S142 用户需求)
3805
- *
3806
- * 功能:
3807
- * - **开放容器白名单**:从 /serenity/cccs 拉取候选容器,chips 选择哪些开放;
3808
- * 空白名单 = 全部容器开放(向后兼容 v1.26.1 全局开放语义)。
3809
- * - **key + 地址展示**:经 /serenity/public-ask 获取访问 key 与各开放容器 URL
3810
- * (用户:配置处需能获取 key 和地址——管理员复制分享给使用者)。
3811
- *
3812
- * 数据通道(plugin 全局):
3813
- * - GET /serenity/cccs 候选容器(node half listCccs)
3814
- * - GET /serenity/public-ask key + 地址(x-serenity-ui 头)
3815
- * - PUT /serenity/config publicAsk.allowed 白名单保存(accounts-api saveConfig)
3816
- */
3817
- function PublicAskEditor(props) {
3818
- const { publicAskOn } = props;
3819
- const [candidates, setCandidates] = (0, react.useState)([]);
3820
- const [allowed, setAllowed] = (0, react.useState)(null);
3821
- const [info, setInfo] = (0, react.useState)(null);
3822
- const [busy, setBusy] = (0, react.useState)(false);
3823
- const [error, setError] = (0, react.useState)(null);
3824
- const [saved, setSaved] = (0, react.useState)(false);
3825
- const [rotating, setRotating] = (0, react.useState)(false);
3826
- const rotateKey = async () => {
3827
- if (!window.confirm("重新生成访问 Key?旧 Key 将立即失效,需要重新分享给使用者。")) return;
3828
- setRotating(true);
3829
- setError(null);
3830
- try {
3831
- const res = await fetch("/serenity/public-ask", {
3832
- method: "PUT",
3833
- headers: {
3834
- "content-type": "application/json",
3835
- "x-serenity-ui": "1"
3836
- },
3837
- body: JSON.stringify({ action: "rotate" })
3838
- });
3839
- if (!res.ok) throw new Error(`轮换失败(${res.status})`);
3840
- const body = await res.json();
3841
- setInfo((cur) => cur ? {
3842
- ...cur,
3843
- key: body.key
3844
- } : cur);
3845
- setSaved(true);
3846
- setTimeout(() => setSaved(false), 2500);
3847
- } catch (err) {
3848
- setError(err instanceof Error ? err.message : String(err));
3849
- } finally {
3850
- setRotating(false);
3851
- }
3852
- };
3853
- (0, react.useEffect)(() => {
3854
- let alive = true;
3855
- (async () => {
3856
- try {
3857
- const cfg = await fetchConfig();
3858
- if (!alive) return;
3859
- setAllowed(cfg?.config?.publicAsk?.allowed ?? []);
3860
- } catch {}
3861
- })();
3862
- return () => {
3863
- alive = false;
3864
- };
3865
- }, []);
3866
- (0, react.useEffect)(() => {
3867
- let alive = true;
3868
- (async () => {
3869
- try {
3870
- const res = await fetch("/serenity/cccs", { headers: { accept: "application/json" } });
3871
- if (!res.ok) return;
3872
- const body = await res.json();
3873
- if (alive) setCandidates(body?.cccs ?? []);
3874
- } catch {}
3875
- })();
3876
- return () => {
3877
- alive = false;
3878
- };
3879
- }, []);
3880
- (0, react.useEffect)(() => {
3881
- if (!publicAskOn) return;
3882
- let alive = true;
3883
- (async () => {
3884
- try {
3885
- const res = await fetch("/serenity/public-ask", { headers: {
3886
- accept: "application/json",
3887
- "x-serenity-ui": "1"
3888
- } });
3889
- if (!res.ok) return;
3890
- const body = await res.json();
3891
- if (alive) setInfo(body);
3892
- } catch {}
3893
- })();
3894
- return () => {
3895
- alive = false;
3896
- };
3897
- }, [publicAskOn]);
3898
- const toggleAllowed = (name) => {
3899
- setAllowed((cur) => {
3900
- const base = cur ?? [];
3901
- return base.includes(name) ? base.filter((n) => n !== name) : [...base, name];
3902
- });
3903
- };
3904
- const save = async () => {
3905
- if (allowed === null) return;
3906
- setBusy(true);
3907
- setError(null);
3908
- setSaved(false);
3909
- try {
3910
- const cfg = await saveConfig({ publicAsk: { allowed } });
3911
- if (cfg) {
3912
- setAllowed(cfg.publicAsk.allowed ?? []);
3913
- setSaved(true);
3914
- setTimeout(() => setSaved(false), 2e3);
3915
- }
3916
- } catch (err) {
3917
- setError(err instanceof Error ? err.message : String(err));
3918
- } finally {
3919
- setBusy(false);
3920
- }
3921
- };
3922
- const allOpen = (allowed ?? []).length === 0;
3923
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3924
- className: "ae-root",
3925
- children: [
3926
- !publicAskOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3927
- className: "ae-warn",
3928
- children: "⚠️ Skiff 问答页当前关闭——先开启上方「Skiff 问答页」开关,外部访问才生效。"
3929
- }),
3930
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3931
- className: "ae-block",
3932
- children: [
3933
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3934
- className: "ae-title",
3935
- children: "开放容器"
3936
- }),
3937
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3938
- className: "ae-desc",
3939
- children: [
3940
- "允许外部问答的认知容器(URL:/c/<容器名>)。",
3941
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "未选择 = 全部容器开放" }),
3942
- "; 选择后仅白名单内容器可访问。"
3943
- ]
3944
- }),
3945
- candidates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3946
- className: "ae-note",
3947
- children: "暂无可选容器(/serenity/cccs 未返回条目——先在工作区打开一个 CCC)"
3948
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3949
- className: "ae-chips",
3950
- children: candidates.map((c) => {
3951
- const on = (allowed ?? []).includes(c.name);
3952
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3953
- type: "button",
3954
- className: `ae-chip${on ? " ae-chipOn" : ""}`,
3955
- onClick: () => toggleAllowed(c.name),
3956
- title: `${c.root}(${c.roles.length} 角色)`,
3957
- children: [c.name, on ? " ✓" : ""]
3958
- }, c.name);
3959
- })
3960
- }),
3961
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3962
- className: "ae-actions",
3963
- children: [
3964
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3965
- type: "button",
3966
- className: "ae-save",
3967
- disabled: busy || allowed === null,
3968
- onClick: () => void save(),
3969
- children: busy ? "保存中…" : "保存白名单"
3970
- }),
3971
- saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3972
- className: "ae-saved",
3973
- children: "✓ 已保存"
3974
- }),
3975
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3976
- className: "ae-err",
3977
- children: error
3978
- })
3979
- ]
3980
- }),
3981
- allOpen && allowed !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3982
- className: "ae-note",
3983
- children: "当前为「全部开放」——所有发现到的认知容器都可问答。"
3984
- })
3985
- ]
3986
- }),
3987
- publicAskOn && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3988
- className: "ae-block",
3989
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3990
- className: "ae-title",
3991
- children: "访问 Key 与地址"
3992
- }), info ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3993
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3994
- className: "ae-desc",
3995
- children: "把下面的 key 与地址分享给使用者;key 已自动保存在其浏览器(localStorage)。"
3996
- }),
3997
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3998
- className: "ae-row",
3999
- children: [
4000
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4001
- className: "ae-label",
4002
- children: "访问 Key"
4003
- }),
4004
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
4005
- className: "ae-key",
4006
- children: info.key
4007
- }),
4008
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4009
- type: "button",
4010
- className: "ae-copy",
4011
- onClick: () => {
4012
- navigator.clipboard?.writeText(info.key);
4013
- },
4014
- children: "复制"
4015
- }),
4016
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4017
- type: "button",
4018
- className: "ae-copy ae-rotate",
4019
- disabled: rotating,
4020
- onClick: () => void rotateKey(),
4021
- children: rotating ? "生成中…" : "重新生成"
4022
- })
4023
- ]
4024
- }),
4025
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4026
- className: "ae-row",
4027
- children: [
4028
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4029
- className: "ae-label",
4030
- children: "容器列表页"
4031
- }),
4032
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
4033
- className: "ae-key",
4034
- children: info.listUrl
4035
- }),
4036
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4037
- type: "button",
4038
- className: "ae-copy",
4039
- onClick: () => {
4040
- navigator.clipboard?.writeText(info.listUrl);
4041
- },
4042
- children: "复制"
4043
- })
4044
- ]
4045
- }),
4046
- info.urls.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4047
- className: "ae-rows",
4048
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4049
- className: "ae-label",
4050
- children: "各容器地址"
4051
- }), info.urls.map((u) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4052
- className: "ae-row",
4053
- children: [
4054
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4055
- className: "ae-label ae-labelDim",
4056
- children: u.name
4057
- }),
4058
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
4059
- className: "ae-key",
4060
- children: u.url
4061
- }),
4062
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4063
- type: "button",
4064
- className: "ae-copy",
4065
- onClick: () => {
4066
- navigator.clipboard?.writeText(u.url);
4067
- },
4068
- children: "复制"
4069
- })
4070
- ]
4071
- }, u.name))]
4072
- })
4073
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4074
- className: "ae-note",
4075
- children: "开启后此处显示访问 key 与地址…"
4076
- })]
4077
- })
4078
- ]
4079
- });
4080
- }
4081
- //#endregion
4082
3802
  //#region src/client/WeixinBridgeEditor.tsx
4083
3803
  /**
4084
3804
  * WeixinBridgeEditor.tsx — 「微信桥」配置区块(F4c-3,v1.27.0 实验性)
@@ -4712,24 +4432,16 @@ Instruction-following style:
4712
4432
  const setSkiffPort = (v) => {
4713
4433
  set("skiffDebugPort", Math.min(65535, Math.max(1024, Math.round(v))));
4714
4434
  };
4715
- const setAcpPort = (v) => {
4716
- set("acpHttpPort", Math.min(65535, Math.max(1024, Math.round(v))));
4717
- };
4718
4435
  const gatewayOn = value?.gatewayEnabled ?? false;
4719
4436
  const thresholdK = value?.rebuildThresholdK ?? 400;
4720
4437
  const skiffOn = value?.skiffEnabled ?? false;
4721
4438
  const skiffPort = value?.skiffDebugPort ?? 3099;
4722
- const acpOn = value?.acpEnabled ?? false;
4723
- const acpPort = value?.acpHttpPort ?? 3100;
4724
- const publicAskOn = value?.publicAskEnabled ?? false;
4725
4439
  const croOn = value?.croEnabled ?? true;
4726
4440
  const unattendedOn = value?.unattendedEnabled ?? false;
4727
4441
  const diagramOn = value?.diagramEnabled ?? false;
4728
4442
  const [openGateway, setOpenGateway] = (0, react.useState)(false);
4729
4443
  const [openRebuild, setOpenRebuild] = (0, react.useState)(false);
4730
4444
  const [openSkiff, setOpenSkiff] = (0, react.useState)(false);
4731
- const [openAcp, setOpenAcp] = (0, react.useState)(false);
4732
- const [openPublicAsk, setOpenPublicAsk] = (0, react.useState)(false);
4733
4445
  const [openCro, setOpenCro] = (0, react.useState)(false);
4734
4446
  const [openUnattended, setOpenUnattended] = (0, react.useState)(false);
4735
4447
  const [openDiagram, setOpenDiagram] = (0, react.useState)(false);
@@ -4821,98 +4533,50 @@ Instruction-following style:
4821
4533
  }),
4822
4534
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Group, {
4823
4535
  title: "外部能力",
4824
- children: [
4825
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4826
- title: "Skiff 调试",
4827
- desc: "Skiff 认知子集问答页(端口 3099,实验性,127.0.0.1)",
4828
- expandable: true,
4829
- open: openSkiff,
4830
- onToggle: setOpenSkiff,
4831
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4832
- className: "ss-switchRow",
4833
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4834
- checked: skiffOn,
4835
- onChange: (on) => toggle("skiffEnabled", on)
4836
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4837
- className: "ss-portInput",
4838
- type: "number",
4839
- min: 1024,
4840
- max: 65535,
4841
- value: skiffPort,
4842
- disabled: !skiffOn,
4843
- title: "Skiff 调试端口(默认 3099)",
4844
- onChange: (e) => setSkiffPort(Number(e.target.value))
4845
- })]
4846
- }),
4847
- detail: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4848
- className: "ss-detailIntro",
4849
- children: "Skiff 认知子集角色调试服务(端口 3099):\n· 概念:Skiff = 完整宁静号 trajectory 的任意子集(认知子集角色)\n· 服务:角色问答页(调试用),仅监听 127.0.0.1(本地)\n· 使用:配置 CCC 的 skiff.roles 角色后,可经此页以角色身份问答\n· 实验性:人工启停,默认关闭;对外问答请用「Skiff 问答页」"
4850
- })
4851
- }) }),
4852
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4853
- title: "ACP JSON-RPC",
4854
- desc: "程序化调用 Skiff 角色(端口 3100,实验性,127.0.0.1)",
4855
- expandable: true,
4856
- open: openAcp,
4857
- onToggle: setOpenAcp,
4858
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4859
- className: "ss-switchRow",
4860
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4861
- checked: acpOn,
4862
- onChange: (on) => toggle("acpEnabled", on)
4863
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4864
- className: "ss-portInput",
4865
- type: "number",
4866
- min: 1024,
4867
- max: 65535,
4868
- value: acpPort,
4869
- disabled: !acpOn && !publicAskOn,
4870
- title: "ACP + Skiff 问答共用端口(默认 3100)",
4871
- onChange: (e) => setAcpPort(Number(e.target.value))
4872
- })]
4873
- }),
4874
- detail: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4537
+ title: "Skiff 调试",
4538
+ desc: "Skiff 认知子集问答页(端口 3099,实验性,127.0.0.1)",
4539
+ expandable: true,
4540
+ open: openSkiff,
4541
+ onToggle: setOpenSkiff,
4542
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4543
+ className: "ss-switchRow",
4544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4545
+ checked: skiffOn,
4546
+ onChange: (on) => toggle("skiffEnabled", on)
4547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4548
+ className: "ss-portInput",
4549
+ type: "number",
4550
+ min: 1024,
4551
+ max: 65535,
4552
+ value: skiffPort,
4553
+ disabled: !skiffOn,
4554
+ title: "Skiff 调试端口(默认 3099)",
4555
+ onChange: (e) => setSkiffPort(Number(e.target.value))
4556
+ })]
4557
+ }),
4558
+ detail: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4559
+ className: "ss-detailIntro",
4560
+ children: "Skiff 认知子集角色调试服务(端口 3099):\n· 概念:Skiff = 完整宁静号 trajectory 的任意子集(认知子集角色)\n· 服务:角色问答页(调试用),仅监听 127.0.0.1(本地)\n· 使用:配置 CCC 的 skiff.roles 角色后,可经此页以角色身份问答\n· 实验性:人工启停,默认关闭;对外问答请用「Skiff 问答页」"
4561
+ })
4562
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4563
+ title: "CRO(轨迹自编程唤起)",
4564
+ desc: "让轨迹用自己的一段程序决定何时唤起(全局开关,默认开)",
4565
+ expandable: true,
4566
+ open: openCro,
4567
+ onToggle: setOpenCro,
4568
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4569
+ checked: croOn,
4570
+ onChange: (on) => toggle("croEnabled", on)
4571
+ }),
4572
+ detail: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4573
+ className: "ss-detailStack",
4574
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4875
4575
  className: "ss-detailIntro",
4876
- children: "ACP(Agent Client Protocol)HTTP JSON-RPC 端点(端口 3100):\n· 作用:程序化调用 Skiff 角色(指定 CCC + 角色 + 会话)\n· 形态:HTTP JSON-RPC 服务,仅监听 127.0.0.1(本地)\n· 场景:IM 桥(如微信桥)、脚本、自动化接线\n· 实验性:人工启停,默认关闭;与「Skiff 问答页」共用端口"
4877
- })
4878
- }) }),
4879
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4880
- title: "Skiff 问答页",
4881
- desc: "按认知容器暴露问答页(端口 3100,需 key)",
4882
- expandable: true,
4883
- open: openPublicAsk,
4884
- onToggle: setOpenPublicAsk,
4885
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4886
- checked: publicAskOn,
4887
- onChange: (on) => toggle("publicAskEnabled", on)
4888
- }),
4889
- detail: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4890
- className: "ss-detailStack",
4891
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4892
- className: "ss-detailIntro",
4893
- children: "Skiff 问答页(对外问答,端口 3100,key 认证):\n· 作用:让外部用户在浏览器中与指定 CCC 的 Skiff 角色对话\n· 认证:访问 key(首次开启自动生成)\n· 授权:开放容器白名单(未选 = 全部开放)\n· 外部接入:需经部署方自选方案暴露(隧道/反代/端口映射)\n· 安全:key 可轮换(旧 key 立即失效)"
4894
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublicAskEditor, { publicAskOn })]
4576
+ children: "CRO = Continuous Re-Occurrence(轨迹自编程唤起)。\n· 作用:轨迹目录下放一个 continuous-re-occurrence.ts,ACC 的 5min tick 会 spawn 它,\n 由这段程序自己决定“要不要唤、何时唤、唤起时说什么”\n· 怎么启用:程序文件在即启用(无需在此配置);本开关是**总闸**\n· 关掉它的后果:**只有 CRO 阶段停跑** —— 定时唤醒(send-later)、即时投递(send-now)\n 与唤醒表投递**照常工作**\n· 默认开:已在生产稳定运行,且“没有程序文件的轨迹”本来就不参与(默认存在感为零)\n· 与它同时废止的开关:原“唤醒调度器”闸(2026-09-21 所有者令砍掉)——\n 唤醒调度器现**恒开**,不再有可被误关的开关"
4895
4577
  })
4896
- }) }),
4897
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4898
- title: "CRO(轨迹自编程唤起)",
4899
- desc: "让轨迹用自己的一段程序决定何时唤起(全局开关,默认开)",
4900
- expandable: true,
4901
- open: openCro,
4902
- onToggle: setOpenCro,
4903
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toggle, {
4904
- checked: croOn,
4905
- onChange: (on) => toggle("croEnabled", on)
4906
- }),
4907
- detail: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4908
- className: "ss-detailStack",
4909
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4910
- className: "ss-detailIntro",
4911
- children: "CRO = Continuous Re-Occurrence(轨迹自编程唤起)。\n· 作用:轨迹目录下放一个 continuous-re-occurrence.ts,ACC 的 5min tick 会 spawn 它,\n 由这段程序自己决定“要不要唤、何时唤、唤起时说什么”\n· 怎么启用:程序文件在即启用(无需在此配置);本开关是**总闸**\n· 关掉它的后果:**只有 CRO 阶段停跑** —— 定时唤醒(send-later)、即时投递(send-now)\n 与唤醒表投递**照常工作**\n· 默认开:已在生产稳定运行,且“没有程序文件的轨迹”本来就不参与(默认存在感为零)\n· 与它同时废止的开关:原“唤醒调度器”闸(2026-09-21 所有者令砍掉)——\n 唤醒调度器现**恒开**,不再有可被误关的开关"
4912
- })
4913
- })
4914
- }) })
4915
- ]
4578
+ })
4579
+ }) })]
4916
4580
  }),
4917
4581
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Group, {
4918
4582
  title: "工具",