@shgroup/dsh-serenity-hooks 1.57.0 → 1.57.2

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/lib/index.js CHANGED
@@ -1,12 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { a as hostSessions, c as hostWeb, i as hostService, l as hostWebServer, n as hostInjected, o as hostSettings, r as hostLlm, s as hostSubagents, t as hostAgents } from "./access-CNccNtgf.js";
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 { i as cccRootForExec, n as agentCwdFor, o as listCccs, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD } from "./ccc-roots-BC8mn8iM.js";
5
- 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, t as ACC_MESSAGE_KIND, u as TRAJECTORY_ACTIONS, v as parseSessionContextFromEvents, w as showSession, x as sessionEvents$1, y as readActiveSessionMd } from "./message-source-BcCRsEOh.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, t as ACC_MESSAGE_KIND, u as TRAJECTORY_ACTIONS, v as parseSessionContextFromEvents, w as showSession, x as sessionEvents$1, y as readActiveSessionMd } from "./message-source-B4P3Uw6D.js";
6
5
  import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, 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-B9gU1YXz.js";
7
- import { a as unregisterSkiffSession$1, i as skiffSessionSnapshot$1, n as skiffRoleFor$1, o as ACC_VERSION, r as skiffSessionInfo$1, t as registerSkiffSession$1 } from "./skiff-registry-BjIX_KD1.js";
6
+ import { a as GATEWAY_PORT, c as SKIFF_DEBUG_PORT, d as skiffRoleFor$1, f as skiffSessionInfo$1, i as FACE_PORTS, l as WEIXIN_SEND_PORT, m as ACC_VERSION, n as readSimpleSettings, o as MAIN_WEB_PORT, p as unregisterSkiffSession$1, r as registerSettingsSection, s as MECHANISM_PORTS, u as registerSkiffSession$1 } from "./settings-section-DQIVsTqE.js";
8
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";
9
- import { a as ACP_HTTP_PORT, c as MAIN_WEB_PORT, d as WEIXIN_SEND_PORT, l as MECHANISM_PORTS, n as readSimpleSettings, o as FACE_PORTS, r as registerSettingsSection, s as GATEWAY_PORT, u as SKIFF_DEBUG_PORT } from "./settings-section-xHABl8nk.js";
10
8
  import { REGISTERED_TOOLS } from "./invariant.js";
11
9
  import { a as markdownToPlainText, c as sendTyping, i as getUpdates, l as sniffImageExt, n as downloadMedia, o as sendFileMessage, r as getConfig, s as sendTextMessage, t as TypingStatus } from "./weixin-api-BrRcPiQs.js";
12
10
  import { createRequire } from "node:module";
@@ -1086,7 +1084,8 @@ Directory of ACC capabilities — each area lists where to go for details (guide
1086
1084
  cheap-worker(杂工 agent 编排——guide 子命令:cheap-worker guide)
1087
1085
  专属工具(**默认对所有 CCC 隐藏**,只有在自己 exclusiveTools 里点名的 CCC 可见)→ acc-diag(运行态诊断)
1088
1086
  ④ 角色与对外面 → container_admin role(Skiff 认知子集角色:guide/validate/apply/list——skiff_admin 已并入)
1089
- ACP(程序化 JSON-RPC 3100)/ Skiff 问答页(公网 ask)——面板「外部能力」组
1087
+ 3099 Skiff 调试问答页(人工调试面)。🔴 2026-10-15:自造 ACP 面已退役
1088
+ ⇒ 外部程序化接入改走官方 dsh --profile acp + 现成 ACP 客户端
1090
1089
  ⑤ 自主与接入 → container_trajectory send-now(跨轨迹的**即时**递送,**不排队**:live 目标在跑就当场注入当前轮、空闲则立即起轮;非 live 冷载入——等效于直接 wake;**有同步回执**)
1091
1090
  container_trajectory send-later(跨轨迹的"未来时刻 + 一条 message";fire-and-forget,无回执无回收)
1092
1091
  weixin 微信桥(配置/扫码/路由/消息 hook——CCC 侧 weixin-doctor MSM:msm("weixin-doctor", ["guide"]))
@@ -2943,10 +2942,6 @@ function defaultAdvancedSettings() {
2943
2942
  mode: "",
2944
2943
  overrideText: ""
2945
2944
  },
2946
- publicAsk: {
2947
- key: "",
2948
- allowed: []
2949
- },
2950
2945
  weixinApi: {
2951
2946
  enabled: true,
2952
2947
  port: WEIXIN_SEND_PORT
@@ -2999,7 +2994,6 @@ function mergeWithDefaults(raw) {
2999
2994
  const o = raw;
3000
2995
  const gateway = o.gateway ?? {};
3001
2996
  const persona = o.persona ?? {};
3002
- const publicAsk = o.publicAsk ?? {};
3003
2997
  const weixinApi = o.weixinApi ?? {};
3004
2998
  return {
3005
2999
  gateway: {
@@ -3016,10 +3010,6 @@ function mergeWithDefaults(raw) {
3016
3010
  mode: typeof persona.mode === "string" ? persona.mode : def.persona.mode,
3017
3011
  overrideText: typeof persona.overrideText === "string" ? persona.overrideText : def.persona.overrideText
3018
3012
  },
3019
- publicAsk: {
3020
- key: typeof publicAsk.key === "string" ? publicAsk.key : def.publicAsk.key,
3021
- allowed: Array.isArray(publicAsk.allowed) ? publicAsk.allowed.filter((n) => typeof n === "string" && n !== "") : def.publicAsk.allowed
3022
- },
3023
3013
  weixinApi: {
3024
3014
  enabled: typeof weixinApi.enabled === "boolean" ? weixinApi.enabled : def.weixinApi.enabled,
3025
3015
  port: typeof weixinApi.port === "number" && Number.isInteger(weixinApi.port) && weixinApi.port >= 0 ? weixinApi.port : def.weixinApi.port
@@ -3057,10 +3047,6 @@ function updateAdvancedSettings(patch) {
3057
3047
  mode: typeof ps.mode === "string" ? ps.mode : current.persona.mode,
3058
3048
  overrideText: typeof ps.overrideText === "string" ? ps.overrideText : current.persona.overrideText
3059
3049
  } : current.persona,
3060
- publicAsk: patch.publicAsk !== void 0 ? {
3061
- key: typeof patch.publicAsk.key === "string" ? patch.publicAsk.key : current.publicAsk.key,
3062
- allowed: Array.isArray(patch.publicAsk.allowed) ? patch.publicAsk.allowed.filter((n) => typeof n === "string" && n !== "") : current.publicAsk.allowed
3063
- } : current.publicAsk,
3064
3050
  weixinApi: patch.weixinApi !== void 0 ? {
3065
3051
  enabled: typeof patch.weixinApi.enabled === "boolean" ? patch.weixinApi.enabled : current.weixinApi.enabled,
3066
3052
  port: typeof patch.weixinApi.port === "number" && Number.isInteger(patch.weixinApi.port) && patch.weixinApi.port >= 0 ? patch.weixinApi.port : current.weixinApi.port
@@ -3070,87 +3056,6 @@ function updateAdvancedSettings(patch) {
3070
3056
  return next;
3071
3057
  }
3072
3058
  /**
3073
- * 确保问答页访问 key 存在(首次启用自动生成 + 写回固定,用户拍板"配置时生成"):
3074
- * 读取全局配置 publicAsk.key;为空 → 生成 32 字节随机 hex(64 字符)→ 写回持久化。
3075
- * @returns 当前固定 key(已生成则返回既有值——**幂等**,不会覆盖用户手改的 key)
3076
- */
3077
- function ensurePublicAskKey() {
3078
- const current = readAdvancedSettings();
3079
- if (current.publicAsk.key !== "") return current.publicAsk.key;
3080
- const key = randomBytes(32).toString("hex");
3081
- updateAdvancedSettings({ publicAsk: {
3082
- key,
3083
- allowed: current.publicAsk.allowed
3084
- } });
3085
- return key;
3086
- }
3087
- /** 校验问答页访问 key(timing-safe;key 未生成 → 恒 false——没有 key 不工作) */
3088
- function verifyPublicAskKey(provided) {
3089
- const expected = readAdvancedSettings().publicAsk.key;
3090
- if (!expected || typeof provided !== "string" || provided === "") return false;
3091
- const a = Buffer.from(provided);
3092
- const b = Buffer.from(expected);
3093
- return a.length === b.length && timingSafeEqual(a, b);
3094
- }
3095
- /**
3096
- * 轮换问答页访问 key(v1.26.5,S142 用户:公网开放 + 不好说要换 key 呢):
3097
- * 生成新 32 字节随机 hex(64 字符)→ 覆盖写回(**强制替换**——旧 key 立即失效)。
3098
- * @returns 新 key(面板展示给管理员,重新分享给使用者)
3099
- */
3100
- function rotatePublicAskKey() {
3101
- const current = readAdvancedSettings();
3102
- const key = randomBytes(32).toString("hex");
3103
- updateAdvancedSettings({ publicAsk: {
3104
- key,
3105
- allowed: current.publicAsk.allowed
3106
- } });
3107
- return key;
3108
- }
3109
- /** 失败锁定阈值(连续失败次数) */
3110
- const PUBLIC_ASK_FAIL_THRESHOLD = 5;
3111
- /** 首次锁定基础时长(指数退避底数) */
3112
- const PUBLIC_ASK_LOCK_BASE_MS = 9e5;
3113
- /** 锁定上限 */
3114
- const PUBLIC_ASK_LOCK_MAX_MS = 144e5;
3115
- const ipFailStates = /* @__PURE__ */ new Map();
3116
- /** 重置 IP 失败状态(测试/管理员解封) */
3117
- function resetPublicAskIpFail(ip) {
3118
- ipFailStates.delete(ip);
3119
- }
3120
- /** IP 当前是否锁定(到期自动解锁) */
3121
- function isPublicAskIpLocked(ip) {
3122
- const st = ipFailStates.get(ip);
3123
- if (!st || st.lockedUntil === 0) return false;
3124
- if (Date.now() >= st.lockedUntil) {
3125
- st.lockedUntil = 0;
3126
- st.count = 0;
3127
- return false;
3128
- }
3129
- return true;
3130
- }
3131
- /** 记录一次 key 校验失败;达到阈值 → 锁定(指数退避)。返回锁定剩余毫秒(0 = 未锁定) */
3132
- function recordPublicAskFail(ip) {
3133
- let st = ipFailStates.get(ip);
3134
- if (!st) {
3135
- st = {
3136
- count: 0,
3137
- lockedUntil: 0,
3138
- lockRound: 0
3139
- };
3140
- ipFailStates.set(ip, st);
3141
- }
3142
- if (st.lockedUntil > 0 && Date.now() < st.lockedUntil) return st.lockedUntil - Date.now();
3143
- st.count += 1;
3144
- if (st.count >= PUBLIC_ASK_FAIL_THRESHOLD) {
3145
- const base = PUBLIC_ASK_LOCK_BASE_MS * 2 ** st.lockRound;
3146
- st.lockedUntil = Date.now() + Math.min(base, PUBLIC_ASK_LOCK_MAX_MS);
3147
- st.lockRound += 1;
3148
- st.count = 0;
3149
- return st.lockedUntil - Date.now();
3150
- }
3151
- return 0;
3152
- }
3153
- /**
3154
3059
  * 一次性迁移(v1.21.x → v1.22):旧版把 `serenityAdvanced` 存在 CCC localstore.json;
3155
3060
  * 新版归 plugin 全局文件。全局文件已存在 → 跳过(幂等);localstore 无旧节 → 跳过。
3156
3061
  * @param root - CCC 根(localstore.json 所在目录)
@@ -3183,8 +3088,7 @@ function toWire(settings) {
3183
3088
  allowWorkspaceCreate: settings.gateway.allowWorkspaceCreate,
3184
3089
  totpEnabled: settings.gateway.totpEnabled
3185
3090
  },
3186
- persona: settings.persona,
3187
- publicAsk: { allowed: [...settings.publicAsk.allowed] }
3091
+ persona: settings.persona
3188
3092
  };
3189
3093
  }
3190
3094
  /**
@@ -3253,11 +3157,6 @@ function applyWirePatch(wire) {
3253
3157
  if (typeof wire.persona.overrideText === "string") psPatch.overrideText = wire.persona.overrideText;
3254
3158
  patch.persona = psPatch;
3255
3159
  }
3256
- if (wire.publicAsk !== void 0 && Array.isArray(wire.publicAsk.allowed)) {
3257
- const paPatch = { ...current.publicAsk };
3258
- paPatch.allowed = wire.publicAsk.allowed.filter((n) => typeof n === "string" && n !== "");
3259
- patch.publicAsk = paPatch;
3260
- }
3261
3160
  return updateAdvancedSettings(patch);
3262
3161
  }
3263
3162
  //#endregion
@@ -3877,15 +3776,6 @@ function unregisterSkiffSession(sessionId) {
3877
3776
  skiffAgents.delete(sessionId);
3878
3777
  unregisterSkiffSession$1(sessionId);
3879
3778
  }
3880
- /** 测试/调试:注册表快照(sessionId → {role} 兼容展示) */
3881
- function skiffSessionSnapshot() {
3882
- const out = /* @__PURE__ */ new Map();
3883
- for (const [id, b] of skiffSessionSnapshot$1()) out.set(id, {
3884
- role: b.role,
3885
- ccc: b.ccc
3886
- });
3887
- return out;
3888
- }
3889
3779
  /** 自动创建的 bound 标记(readLastBound 恢复判定——只恢复本机制建的;手工 use 的旧 bound 不认) */
3890
3780
  const AUTO_BOUND_NOTE_PREFIX = "auto-created for skiff role";
3891
3781
  /**
@@ -3974,7 +3864,7 @@ function ensureWorkspacePromptSection(agent, scope) {
3974
3864
  *
3975
3865
  * 判定链:
3976
3866
  * ⓪ **临时身份不建工作台**(v1.30.9,S142 用户报"SESSION 爆炸"根治):`persistent=false`
3977
- * (ACP/调试页/问答页等**无固定会话 id**的临时 agent)→ null。理由(R↓):这些 agent 每次
3867
+ * (调试页等**无固定会话 id**的临时 agent)→ null。理由(R↓):这些 agent 每次
3978
3868
  * 调用都是新 id、无身份延续,为其建 SESSION = 每次问答留一个永久空目录(无界熵增);
3979
3869
  * 用户模型「skiff 本质是临时会话机制」——工作台属于**持久身份**,不属于单次调用。
3980
3870
  * ① 角色未启用 session 能力(trajectory.session !== true)→ null(现状零变化)
@@ -4043,10 +3933,10 @@ const SKIFF_PRESET = "standard";
4043
3933
  * 指定 sessionId 且磁盘已有持久化 log → `ctx.agents.resume`(DSH 持久化语义:
4044
3934
  * sessionId 即身份,已持久化会话必须 resume——历史 + turn 续号延续,同用户长期记忆保留);
4045
3935
  * 无 log(首次)/持久化未配置 → `ctx.agents.create`(新建)。不指定 sessionId →
4046
- * 随机 id 恒 create(ACP/调试页默认路径)。
3936
+ * 随机 id 恒 create(调试页默认路径)。
4047
3937
  *
4048
3938
  * @param sessionId 指定会话 id(可选;微信桥等外部面用固定 id 实现用户↔会话长期映射——
4049
- * 不传则随机生成 skiff-<role>-<uuid>,ACP/调试页默认路径)
3939
+ * 不传则随机生成 skiff-<role>-<uuid>,调试页默认路径)
4050
3940
  */
4051
3941
  async function createSkiffAgent(ctx, root, roleName, role, defaultModel, sessionId) {
4052
3942
  if (!ctx.agents) throw new Error("skiff: ctx.agents unavailable");
@@ -4257,8 +4147,9 @@ function truncate(s, n) {
4257
4147
  * 提问一轮:followup → 等 idle → 读答案 + 轨迹。
4258
4148
  * @param eventsStart 轨迹起点:显式传 0 = 全量轨迹(会话追问时页面重绘完整时间线,
4259
4149
  * v1.25.10 用户拍板);不传(undefined)= 本轮增量(followup 前 events 之后)
4260
- * @param options.includeTrajectory 是否计算轨迹(v1.26.10:**3100 对外只提供问答**——
4261
- * 公开问答页 / ACP JSON-RPC 不返回 trajectory,传 false 跳过计算;3099 调试页默认 true 保留)
4150
+ * @param options.includeTrajectory 是否计算轨迹(v1.26.10 起供**对外面**传 false 跳过计算——
4151
+ * 原消费方 = 3100 公开问答页 / ACP JSON-RPC,**该面已于 2026-10-15 退役** ⇒ 现无生产调用方传 false;
4152
+ * 3099 调试页默认 true 保留)
4262
4153
  */
4263
4154
  async function askSkiff(ctx, agent, question, eventsStart, options) {
4264
4155
  const before = eventsStart === void 0 ? sessionEvents(agent.session).length : eventsStart;
@@ -4411,36 +4302,69 @@ const msmTool = defineTool({
4411
4302
  //#endregion
4412
4303
  //#region src/tools/eap.ts
4413
4304
  /**
4414
- * eap.ts — EAP 内容源(认知质量框架;v1.30 三工具合并为 praxis 后只保留内容常量)
4305
+ * eap.ts — EAP 内容源(**完整论文全文**,v1.30 三工具合并为 praxis 后只保留内容源)
4306
+ *
4307
+ * 🔴 本文件的形态由一条 owner 令决定(2026-10-15,S142 续 125):
4308
+ * 原实现内嵌一份约 1.5 KB 的「简易说明」(三变量表 + 自检清单),而 `praxis eap`
4309
+ * 的职责是**注入 EAP 论文全文**。owner 原话:「praxis · eap 放的不是 eap 完整论文全文
4310
+ * 而是简易说明,这是不行的,要求改成全文」。
4311
+ *
4312
+ * ⇒ 正文**不再内嵌**(58 KB 塞进 TS 模板字符串还要转义论文里 4 处 ```mermaid 围栏——
4313
+ * 本容器为此踩过四次),改为**随包的静态资产 + 运行时读取**:
4314
+ * 资产 = `assets/eap-paper.md`(**逐字**来自上游论文,未经改写)
4315
+ * 读取 = `loadEapPaper()`(进程内缓存一次;同 `voyage-page.ts` 的既有先例)
4316
+ *
4317
+ * 🔴 **单一真相源**:正文只在 `assets/eap-paper.md` 一个地方。本文件负责**定位与读取**,
4318
+ * `praxis.ts` 只负责**路由**。二者都不得内嵌任何论文片段——否则又是"第二份会漂的正文"。
4415
4319
  *
4416
- * 内嵌框架内容(自包含,不依赖已安装技能)。
4320
+ * 上游:https://github.com/tellmewhattodo/theory-eap(MIT,许可证随包见
4321
+ * `assets/eap-paper-LICENSE.txt`;同 `assets/diagram/*.ttf` 的既有做法)。
4417
4322
  */
4418
- const EAP_CONTENT = `# EAP Cognitive Quality Framework (Explicit Abstraction Principle)
4419
-
4420
- > "The functional value of a thought is proportional to its external reconstructability."
4421
-
4422
- ## Three Variables
4423
- | Variable | Meaning | How to improve |
4424
- |----------|---------|----------------|
4425
- | E↑ Explicitness | Degree to which variables/entities/relations are clearly defined | Define variables, state relationship direction & cardinality, draw boundaries |
4426
- | R↓ Reconstructability | Cost of rebuilding the original reasoning later | Record decision rationale, context, constraints, alternatives |
4427
- | S↑ Stability | Degree to which the same input repeatedly produces consistent output | Fix structures, protocolize, avoid relying on implicit context |
4428
-
4429
- ## Pre-Output Self-Check Checklist
4430
- - [ ] Variables/entities clearly defined (E↑)
4431
- - [ ] Relationships state direction/cardinality (E↑)
4432
- - [ ] Boundaries drawn — what is in scope / what is not (E↑)
4433
- - [ ] No ambiguous words: "handle" "optimize" "problem" → be specific (E↑)
4434
- - [ ] Key decisions record rationale and alternatives (R↓)
4435
- - [ ] No level-skipping — align the upper layer before descending (R↓)
4436
- - [ ] Structures can be regenerated repeatably (S↑)
4437
-
4438
- ## Relationship with ACC
4439
- ACC (plugin/template) encodes structure as code (E↑); generating a CCC from ACC is deterministic (R↓); consistent across multiple CCCs (S↑).
4440
- CCC (home-serenity etc.) encodes cognitive content as skills/SESSIONs/design docs. Use this checklist to self-check outputs.
4441
-
4442
- ## Reference
4443
- https://github.com/tellmewhattodo/theory-eap`;
4323
+ /**
4324
+ * 资产相对包根的路径。
4325
+ * 🔒 它**必须**同时出现在两处,否则发布包会缺文件、`praxis eap` 在**运行态**才报错:
4326
+ * ① `package.json` 的 `files` 白名单;
4327
+ * ② `scripts/dsh-develop.ts` 的 `verifyTarball` / `deploy` 的 `required` 数组。
4328
+ * 同 v1.26.15 事故形态(`files` 白名单漏运行时必需文件)。
4329
+ */
4330
+ const EAP_PAPER_ASSET = "assets/eap-paper.md";
4331
+ /**
4332
+ * 包根 = **向上找到含 `package.json` 的那一级**。
4333
+ *
4334
+ * 🔴 为什么不写 `join(__dirname, '..')`:本模块在 `src/tools/` 里(发布态 `lib/tools/`),
4335
+ * 一次 `..` 只到 `src/`,于是会去找 `src/assets/eap-paper.md` —— **实测 ENOENT**,
4336
+ * 而症状("论文不在")与真因(路径层级算错)长得完全不同。
4337
+ * 按 `package.json` 上溯对**开发态(vitest 直跑 TS)与发布态(lib/)都成立**。
4338
+ * 与 `src/diagram/rasterize.ts` 的 `packageRootPath()` 同一形态(该处已由实测固化)。
4339
+ *
4340
+ * @returns 包根绝对路径;四层内找不到 `package.json` 时回落"上一级"(不抛——定位失败由调用方响亮报错)
4341
+ */
4342
+ function packageRootPath$2() {
4343
+ const start = dirname(fileURLToPath(import.meta.url));
4344
+ let dir = start;
4345
+ for (let i = 0; i < 4; i++) {
4346
+ if (existsSync(join(dir, "package.json"))) return dir;
4347
+ dir = dirname(dir);
4348
+ }
4349
+ return join(start, "..");
4350
+ }
4351
+ /** 论文资产的绝对路径(导出以便测试与 pack-check 共用同一判据来源) */
4352
+ function eapPaperPath() {
4353
+ return join(packageRootPath$2(), EAP_PAPER_ASSET);
4354
+ }
4355
+ /** 缓存(58 KB;`praxis eap` 可能被反复调用——进程内只读一次) */
4356
+ let cachedPaper = null;
4357
+ /**
4358
+ * 读取论文全文。**读不到即抛错** —— 绝不静默返回空串或退回一份"简易说明":
4359
+ * 那种降级会把"资产缺失"伪装成"调用成功",而本工具的全部价值就是那份正文。
4360
+ */
4361
+ function loadEapPaper(assetPath = eapPaperPath()) {
4362
+ if (assetPath === eapPaperPath()) {
4363
+ if (cachedPaper === null) cachedPaper = readFileSync(assetPath, "utf-8");
4364
+ return cachedPaper;
4365
+ }
4366
+ return readFileSync(assetPath, "utf-8");
4367
+ }
4444
4368
  //#endregion
4445
4369
  //#region src/tools/neat.ts
4446
4370
  /**
@@ -4843,12 +4767,29 @@ Both OEPE review stages run in an isolated context. Three rules make them work:
4843
4767
  * Metaphor 映射:Engineering Drawings(图纸 = 可实践理论的编码,第 3 条隐喻)。
4844
4768
  *
4845
4769
  * section 参数渐进披露:不传 = 返回精简目录;传 = 返回对应框架全文/章节。
4846
- * 每个框架一个内容文件(单一真相源):eap.ts / neat.ts / cce.ts / ontology.ts。
4770
+ * 每个框架一个内容源(单一真相源):neat.ts / cce.ts / ontology.ts;
4771
+ * 🔴 **eap 的内容源是随包资产 `assets/eap-paper.md`**(论文全文,58 KB)——
4772
+ * 读它在 `tools/eap.ts`(本文件只路由,不内嵌任何正文;见该文件的成因注释)。
4847
4773
  */
4848
- /** 知识源:与各原工具内容保持一致(单一真相源——eap/neat/cce 各自源文件已并入此处) */
4774
+ /** 知识源:与各原工具内容保持一致(单一真相源——neat/cce/ontology 各自源文件已并入此处) */
4775
+ /**
4776
+ * 读 EAP 论文全文。
4777
+ *
4778
+ * 🔴 **绝不降级**:缺资产时抛错(带解析到的绝对路径),不返回空串、不退回"简易说明"——
4779
+ * 那种降级会把"资产缺失"伪装成"调用成功",而本工具的全部价值就是那份正文
4780
+ * (这正是 2026-10-15 owner 令要修的缺陷形态)。
4781
+ */
4782
+ function eapPaper() {
4783
+ try {
4784
+ return loadEapPaper();
4785
+ } catch (err) {
4786
+ const msg = err instanceof Error ? err.message : String(err);
4787
+ throw new Error(`EAP paper asset unavailable (${EAP_PAPER_ASSET}): ${msg} — resolved: ${eapPaperPath()}`);
4788
+ }
4789
+ }
4849
4790
  /** 框架清单(无参调用时返回的精简目录) */
4850
4791
  const PRAXIS_INDEX = `praxis — 可实践理论注入(认知框架,按需加载)
4851
- 选择框架:praxis eap(认知质量——E↑/R↓/S↑ 三变量 + 输出前自检)
4792
+ 选择框架:praxis eap(认知质量——EAP 论文全文)
4852
4793
  praxis neat(Neat 协作协议——小步对齐/显式决策/文档驱动)
4853
4794
  praxis cce(认知连续性工程——容器 5 行为约束 + H_op 操作熵)
4854
4795
  praxis ontology(本体论工程方法群——触发/编码/四组判据 M·L·D·V/OEPE 循环/公理/反模式)
@@ -4862,7 +4803,7 @@ function renderText$7(value) {
4862
4803
  }
4863
4804
  const praxisTool = defineTool({
4864
4805
  name: "praxis",
4865
- description: "Praxis — actionable theory injection (cognitive frameworks, loaded on demand): eap (cognitive quality: E↑ explicitness / R↓ reconstructability / S↑ stability + self-check checklist) / neat (Neat design collaboration protocol: small-step alignment, explicit decisions, document-driven) / cce (Cognitive Continuity Engineering: container identity/accessibility/evolution under bounded resources) / ontology (ontology engineering method family: when to model, Turtle encoding, four judgement sets M1-M10 structural / L1-L5 semantic hints / D1-D8 semantic review incl. OntoClean / V1-V4 runtime compliance, the OEPE audit loop, workflows, universal axioms, anti-patterns). No section returns the index; pass a section to load that framework.",
4806
+ description: "Praxis — actionable theory injection (cognitive frameworks, loaded on demand): eap (the EXPLICIT ABSTRACTION PRINCIPLE — the full paper: motivation, definitions, the three variables E↑/R↓/S↑, language-as-interface, empirical cases, theoretical corollaries, formal proof) / neat (Neat design collaboration protocol: small-step alignment, explicit decisions, document-driven) / cce (Cognitive Continuity Engineering: container identity/accessibility/evolution under bounded resources) / ontology (ontology engineering method family: when to model, Turtle encoding, four judgement sets M1-M10 structural / L1-L5 semantic hints / D1-D8 semantic review incl. OntoClean / V1-V4 runtime compliance, the OEPE audit loop, workflows, universal axioms, anti-patterns). No section returns the index; pass a section to load that framework.",
4866
4807
  parameters: { section: {
4867
4808
  type: "string",
4868
4809
  enum: [
@@ -4878,7 +4819,7 @@ const praxisTool = defineTool({
4878
4819
  render: (_args, value) => renderText$7(value)
4879
4820
  },
4880
4821
  async execute(args) {
4881
- if (args.section === "eap") return EAP_CONTENT;
4822
+ if (args.section === "eap") return eapPaper();
4882
4823
  if (args.section === "neat") return NEAT_CONTENT;
4883
4824
  if (args.section === "cce") return CCE_CONTENT;
4884
4825
  if (args.section === "ontology") return ONTOLOGY_CONTENT;
@@ -12275,11 +12216,10 @@ dsp 只提供机制(双白名单强制 + 基础提示词 + 调试问答页)
12275
12216
 
12276
12217
  ── 运行 ──
12277
12218
  调试:设置面板「Serenity」页 Skiff 区块开启 → http://127.0.0.1:<debugPort> 问答页实测
12278
- ACP 程序化面(v1.26.0):设置面板「ACP」区块开启 → HTTP JSON-RPC 端点(默认 3100)——
12279
- POST / {jsonrpc, id, method, params}
12280
- session/new {ccc, role, sessionId?} → 创建/延续会话(sessionId 可选=进程内追问)
12281
- session/prompt {sessionId, question} → {answer, trajectory}
12282
- session/cancel / session/close / session/list / request_permission(恒 allow)
12219
+ 🔴 外部程序化接入(2026-10-15 起改走官方):本插件自造的 ACP 面(HTTP JSON-RPC 3100 /
12220
+ 建议问答页)**已整体退役**(owner 裁「砍掉外部问答」)。现在要程序化/远程接入,走宿主
12221
+ 自带的官方 ACP server:dsh --profile acp(纯 stdio、零认证)+ 任一 ACP 客户端
12222
+ (手机端如 Agmente / ACP UI)经隧道接入。详见 S142 的接入指南文档。
12283
12223
  启停 = 人工(设置面板开关,不随插件加载自动启动);未开启零资源占用
12284
12224
  生效机制:角色配置**实时读取**(改 .opencode/serenity.json → 刷新调试页即生效);
12285
12225
  改配置后用 container_admin role apply 做显式校验 + 应用确认(绑定 CCC + 角色清单)
@@ -12660,7 +12600,9 @@ function claimsUserNotification(text) {
12660
12600
  /**
12661
12601
  * **不许开**的会话(设计 §3 的"建议写死",本版**确实写死**):
12662
12602
  * `skiff-`(F4 问答 / **微信桥会话也是这个前缀** —— 桥那侧的"用户"是**家人**,
12663
- * 代理替家人说话 = **冒充**)| `acp-`(对外程序化面)| `cheap-worker-`(自主 worker,已有自己的完成码循环)。
12603
+ * 代理替家人说话 = **冒充**)| `acp-`(对外程序化面 —— ⚠️ **2026-10-15 起已成死判据**:
12604
+ * 该前缀从无生产者且面 E 已退役;理由与"为何保留"见 `output-guard-seam.ts` 文件头)|
12605
+ * `cheap-worker-`(自主 worker,已有自己的完成码循环)。
12664
12606
  *
12665
12607
  * ⚠️ **`rebuild-` 不在排除面**(与 `output-guard-seam.ts` 的取向**刻意不同**):重建会话是
12666
12608
  * **同一轨迹的载体**(内部维护面),不是对外面;把它排除会让"重建后就没人推了"。
@@ -14733,7 +14675,6 @@ const UPLOAD_PATH = "/serenity/image-upload";
14733
14675
  const FILE_UPLOAD_PATH = "/serenity/file-upload";
14734
14676
  const CONFIG_PATH = "/serenity/config";
14735
14677
  const CCCS_PATH = "/serenity/cccs";
14736
- const PUBLIC_ASK_PATH = "/serenity/public-ask";
14737
14678
  const WEIXIN_PATH = "/serenity/weixin";
14738
14679
  /** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
14739
14680
  const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
@@ -14817,7 +14758,7 @@ function saveFileToTmp(root, fileName, data) {
14817
14758
  writeFileSync(join(dir, filename), bytes);
14818
14759
  return `${FILE_UPLOAD_DIR}/${filename}`;
14819
14760
  }
14820
- function readBody$4(req, maxBytes) {
14761
+ function readBody$3(req, maxBytes) {
14821
14762
  return new Promise((resolve, reject) => {
14822
14763
  let data = "";
14823
14764
  req.on("data", (chunk) => {
@@ -14831,7 +14772,7 @@ function readBody$4(req, maxBytes) {
14831
14772
  req.on("error", reject);
14832
14773
  });
14833
14774
  }
14834
- function sendJson$4(res, code, body) {
14775
+ function sendJson$3(res, code, body) {
14835
14776
  const payload = JSON.stringify(body);
14836
14777
  res.writeHead(code, {
14837
14778
  "content-type": "application/json; charset=utf-8",
@@ -14856,7 +14797,7 @@ function senderIsWebUi(req) {
14856
14797
  */
14857
14798
  function requireWebUi(req, res, message) {
14858
14799
  if (senderIsWebUi(req)) return true;
14859
- sendJson$4(res, 403, { error: message });
14800
+ sendJson$3(res, 403, { error: message });
14860
14801
  return false;
14861
14802
  }
14862
14803
  /**
@@ -14889,11 +14830,11 @@ function registerStatusApi(ctx, opts = {}) {
14889
14830
  handler: async (req, res) => {
14890
14831
  try {
14891
14832
  if (req.method !== "POST") {
14892
- sendJson$4(res, 405, { error: "method not allowed" });
14833
+ sendJson$3(res, 405, { error: "method not allowed" });
14893
14834
  return;
14894
14835
  }
14895
14836
  if (!requireWebUi(req, res, "图片落盘仅限 WebUI(client 专用)")) return;
14896
- const raw = await readBody$4(req, 20971520);
14837
+ const raw = await readBody$3(req, 20971520);
14897
14838
  const body = JSON.parse(raw);
14898
14839
  const workspace = resolveWorkspace(ctx, {
14899
14840
  sessionId: body.sessionId,
@@ -14901,12 +14842,12 @@ function registerStatusApi(ctx, opts = {}) {
14901
14842
  });
14902
14843
  const root = cccRootForCwd(workspace);
14903
14844
  if (!root) {
14904
- sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14845
+ sendJson$3(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14905
14846
  return;
14906
14847
  }
14907
- sendJson$4(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
14848
+ sendJson$3(res, 200, { path: saveImageToTmp(root, body.mediaType ?? "", body.data ?? "") });
14908
14849
  } catch (err) {
14909
- sendJson$4(res, 400, { error: err.message ?? String(err) });
14850
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
14910
14851
  }
14911
14852
  }
14912
14853
  });
@@ -14916,11 +14857,11 @@ function registerStatusApi(ctx, opts = {}) {
14916
14857
  handler: async (req, res) => {
14917
14858
  try {
14918
14859
  if (req.method !== "POST") {
14919
- sendJson$4(res, 405, { error: "method not allowed" });
14860
+ sendJson$3(res, 405, { error: "method not allowed" });
14920
14861
  return;
14921
14862
  }
14922
14863
  if (!requireWebUi(req, res, "文件落盘仅限 WebUI(client 专用)")) return;
14923
- const raw = await readBody$4(req, 20971520);
14864
+ const raw = await readBody$3(req, 20971520);
14924
14865
  const body = JSON.parse(raw);
14925
14866
  const workspace = resolveWorkspace(ctx, {
14926
14867
  sessionId: body.sessionId,
@@ -14928,12 +14869,12 @@ function registerStatusApi(ctx, opts = {}) {
14928
14869
  });
14929
14870
  const root = cccRootForCwd(workspace);
14930
14871
  if (!root) {
14931
- sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14872
+ sendJson$3(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14932
14873
  return;
14933
14874
  }
14934
- sendJson$4(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
14875
+ sendJson$3(res, 200, { path: saveFileToTmp(root, body.name ?? "", body.data ?? "") });
14935
14876
  } catch (err) {
14936
- sendJson$4(res, 400, { error: err.message ?? String(err) });
14877
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
14937
14878
  }
14938
14879
  }
14939
14880
  });
@@ -14949,7 +14890,7 @@ function registerStatusApi(ctx, opts = {}) {
14949
14890
  workspace: url.searchParams.get("workspace") ?? void 0
14950
14891
  }), configPaths);
14951
14892
  const runtime = ctx.get("codeRuntime");
14952
- sendJson$4(res, 200, {
14893
+ sendJson$3(res, 200, {
14953
14894
  ...status,
14954
14895
  ...runtime === void 0 ? { codeRuntime: null } : { codeRuntime: {
14955
14896
  language: runtime.language,
@@ -14960,7 +14901,7 @@ function registerStatusApi(ctx, opts = {}) {
14960
14901
  }
14961
14902
  if (req.method === "POST") {
14962
14903
  if (!requireWebUi(req, res, "safe-mode 切换仅限 WebUI(agent 不可自行开关)")) return;
14963
- const raw = await readBody$4(req, 65536);
14904
+ const raw = await readBody$3(req, 65536);
14964
14905
  const body = JSON.parse(raw);
14965
14906
  const workspace = resolveWorkspace(ctx, {
14966
14907
  sessionId: body.sessionId,
@@ -14968,19 +14909,19 @@ function registerStatusApi(ctx, opts = {}) {
14968
14909
  });
14969
14910
  const root = cccRootForCwd(workspace);
14970
14911
  if (!root) {
14971
- sendJson$4(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14912
+ sendJson$3(res, 404, { error: `no CCC found from workspace: ${workspace}` });
14972
14913
  return;
14973
14914
  }
14974
14915
  const result = setSafeMode(root, body.on === true);
14975
- sendJson$4(res, 200, {
14916
+ sendJson$3(res, 200, {
14976
14917
  ...getStatus(workspace, configPaths),
14977
14918
  ...result
14978
14919
  });
14979
14920
  return;
14980
14921
  }
14981
- sendJson$4(res, 405, { error: "method not allowed" });
14922
+ sendJson$3(res, 405, { error: "method not allowed" });
14982
14923
  } catch (err) {
14983
- sendJson$4(res, 400, { error: err.message ?? String(err) });
14924
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
14984
14925
  }
14985
14926
  }
14986
14927
  });
@@ -14996,24 +14937,24 @@ function registerStatusApi(ctx, opts = {}) {
14996
14937
  try {
14997
14938
  known = projectKnownWorkspaces(hostService(ctx, "workspaceRegistry")?.list?.() ?? [], settings.gateway.workspaces);
14998
14939
  } catch {}
14999
- sendJson$4(res, 200, {
14940
+ sendJson$3(res, 200, {
15000
14941
  config: toWire(settings),
15001
14942
  knownWorkspaces: known
15002
14943
  });
15003
14944
  return;
15004
14945
  }
15005
14946
  if (req.method === "PUT") {
15006
- const raw = await readBody$4(req, 131072);
14947
+ const raw = await readBody$3(req, 131072);
15007
14948
  const saved = applyWirePatch(JSON.parse(raw).config ?? {});
15008
14949
  try {
15009
14950
  ctx.emit?.("serenity/config-updated");
15010
14951
  } catch {}
15011
- sendJson$4(res, 200, { config: toWire(saved) });
14952
+ sendJson$3(res, 200, { config: toWire(saved) });
15012
14953
  return;
15013
14954
  }
15014
- sendJson$4(res, 405, { error: "method not allowed" });
14955
+ sendJson$3(res, 405, { error: "method not allowed" });
15015
14956
  } catch (err) {
15016
- sendJson$4(res, 400, { error: err.message ?? String(err) });
14957
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
15017
14958
  }
15018
14959
  }
15019
14960
  });
@@ -15023,7 +14964,7 @@ function registerStatusApi(ctx, opts = {}) {
15023
14964
  handler: async (req, res) => {
15024
14965
  try {
15025
14966
  if (req.method !== "GET") {
15026
- sendJson$4(res, 405, { error: "method not allowed" });
14967
+ sendJson$3(res, 405, { error: "method not allowed" });
15027
14968
  return;
15028
14969
  }
15029
14970
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -15032,54 +14973,12 @@ function registerStatusApi(ctx, opts = {}) {
15032
14973
  workspace: url.searchParams.get("workspace") ?? void 0
15033
14974
  });
15034
14975
  const root = cccRootForCwd(workspace) ?? "";
15035
- sendJson$4(res, 200, { cccs: await listCccs(ctx, {
14976
+ sendJson$3(res, 200, { cccs: await listCccs(ctx, {
15036
14977
  defaultRoot: root,
15037
14978
  withRoles: true
15038
14979
  }) });
15039
14980
  } catch (err) {
15040
- sendJson$4(res, 400, { error: err.message ?? String(err) });
15041
- }
15042
- }
15043
- });
15044
- ctx.webServer.register({
15045
- kind: "exact",
15046
- path: PUBLIC_ASK_PATH,
15047
- handler: async (req, res) => {
15048
- try {
15049
- if (!requireWebUi(req, res, "仅限 WebUI(key 属敏感凭据)")) return;
15050
- if (req.method === "PUT") {
15051
- const raw = await readBody$4(req, 16384);
15052
- if (JSON.parse(raw).action !== "rotate") {
15053
- sendJson$4(res, 400, { error: "unsupported action (expected \"rotate\")" });
15054
- return;
15055
- }
15056
- sendJson$4(res, 200, { key: rotatePublicAskKey() });
15057
- return;
15058
- }
15059
- if (req.method !== "GET") {
15060
- sendJson$4(res, 405, { error: "method not allowed" });
15061
- return;
15062
- }
15063
- const settings = readAdvancedSettings();
15064
- const { readSimpleSettings } = await import("./settings-section-xHABl8nk.js").then((n) => n.i);
15065
- const simple = readSimpleSettings();
15066
- const allowed = settings.publicAsk.allowed;
15067
- const port = simple.acpHttpPort ?? ACP_HTTP_PORT;
15068
- const base = `http://127.0.0.1:${port}`;
15069
- const containerUrls = allowed.map((name) => ({
15070
- name,
15071
- url: `${base}/c/${encodeURIComponent(name)}`
15072
- }));
15073
- sendJson$4(res, 200, {
15074
- enabled: simple.publicAskEnabled ?? false,
15075
- port,
15076
- key: ensurePublicAskKey(),
15077
- allowed,
15078
- urls: containerUrls,
15079
- listUrl: `${base}/`
15080
- });
15081
- } catch (err) {
15082
- sendJson$4(res, 400, { error: err.message ?? String(err) });
14981
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
15083
14982
  }
15084
14983
  }
15085
14984
  });
@@ -15093,7 +14992,7 @@ function registerStatusApi(ctx, opts = {}) {
15093
14992
  if (req.method === "GET") {
15094
14993
  const chk = requireCcc(url.searchParams.get("ccc") ?? void 0);
15095
14994
  if (!chk.ok) {
15096
- sendJson$4(res, 400, { error: chk.error });
14995
+ sendJson$3(res, 400, { error: chk.error });
15097
14996
  return;
15098
14997
  }
15099
14998
  const { readWeixinSettings } = await import("./weixin-route-B9gU1YXz.js").then((n) => n.u);
@@ -15107,7 +15006,7 @@ function registerStatusApi(ctx, opts = {}) {
15107
15006
  enabled: a.enabled !== false,
15108
15007
  bound: readWeixinCredential(chk.root, a.accountId) !== null
15109
15008
  }));
15110
- sendJson$4(res, 200, {
15009
+ sendJson$3(res, 200, {
15111
15010
  enabled: settings.enabled === true,
15112
15011
  botType: settings.botType ?? void 0,
15113
15012
  accounts,
@@ -15117,14 +15016,14 @@ function registerStatusApi(ctx, opts = {}) {
15117
15016
  return;
15118
15017
  }
15119
15018
  if (req.method !== "POST") {
15120
- sendJson$4(res, 405, { error: "method not allowed" });
15019
+ sendJson$3(res, 405, { error: "method not allowed" });
15121
15020
  return;
15122
15021
  }
15123
- const raw = await readBody$4(req, 131072);
15022
+ const raw = await readBody$3(req, 131072);
15124
15023
  const body = JSON.parse(raw);
15125
15024
  const chk = requireCcc(body.ccc);
15126
15025
  if (!chk.ok) {
15127
- sendJson$4(res, 400, { error: chk.error });
15026
+ sendJson$3(res, 400, { error: chk.error });
15128
15027
  return;
15129
15028
  }
15130
15029
  const root = chk.root;
@@ -15141,7 +15040,7 @@ function registerStatusApi(ctx, opts = {}) {
15141
15040
  startedAt: Date.now(),
15142
15041
  polling: false
15143
15042
  });
15144
- sendJson$4(res, 200, {
15043
+ sendJson$3(res, 200, {
15145
15044
  qrcode: qr.qrcode,
15146
15045
  qrcode_img_content: qr.qrcode_img_content,
15147
15046
  loginKey
@@ -15150,31 +15049,31 @@ function registerStatusApi(ctx, opts = {}) {
15150
15049
  }
15151
15050
  if (body.action === "remove-account") {
15152
15051
  if (!body.accountId) {
15153
- sendJson$4(res, 400, { error: "missing accountId" });
15052
+ sendJson$3(res, 400, { error: "missing accountId" });
15154
15053
  return;
15155
15054
  }
15156
15055
  const { removeWeixinAccount } = await import("./weixin-route-B9gU1YXz.js").then((n) => n.u);
15157
15056
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
15158
15057
  removeWeixinAccount(root, body.accountId);
15159
15058
  syncCccBridge(ctx, root);
15160
- sendJson$4(res, 200, { removed: body.accountId });
15059
+ sendJson$3(res, 200, { removed: body.accountId });
15161
15060
  return;
15162
15061
  }
15163
15062
  if (body.action === "save-routes") {
15164
15063
  const routes = body.routes ?? [];
15165
15064
  if (!Array.isArray(routes) || routes.some((r) => typeof r?.user !== "string" || typeof r?.role !== "string" || r.user === "" || r.role === "")) {
15166
- sendJson$4(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
15065
+ sendJson$3(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
15167
15066
  return;
15168
15067
  }
15169
15068
  const { saveWeixinRoutes } = await import("./weixin-route-B9gU1YXz.js").then((n) => n.u);
15170
15069
  const { readSkiffRoles } = await import("./skiff-role-B615L9MV.js").then((n) => n.u);
15171
15070
  const roles = readSkiffRoles(root);
15172
15071
  for (const r of routes) if (!roles.has(r.role)) {
15173
- sendJson$4(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
15072
+ sendJson$3(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
15174
15073
  return;
15175
15074
  }
15176
15075
  saveWeixinRoutes(root, routes);
15177
- sendJson$4(res, 200, { saved: routes.length });
15076
+ sendJson$3(res, 200, { saved: routes.length });
15178
15077
  return;
15179
15078
  }
15180
15079
  if (body.action === "set-enabled") {
@@ -15183,7 +15082,7 @@ function registerStatusApi(ctx, opts = {}) {
15183
15082
  const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
15184
15083
  const settings = readWeixinSettings(root);
15185
15084
  if (enabled && (settings.accounts ?? []).length === 0) {
15186
- sendJson$4(res, 400, { error: "启用前请先扫码绑定至少一个账号" });
15085
+ sendJson$3(res, 400, { error: "启用前请先扫码绑定至少一个账号" });
15187
15086
  return;
15188
15087
  }
15189
15088
  setWeixinEnabled(root, enabled);
@@ -15192,12 +15091,12 @@ function registerStatusApi(ctx, opts = {}) {
15192
15091
  const { stopCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
15193
15092
  stopCccBridge(root);
15194
15093
  }
15195
- sendJson$4(res, 200, { enabled });
15094
+ sendJson$3(res, 200, { enabled });
15196
15095
  return;
15197
15096
  }
15198
- sendJson$4(res, 400, { error: `unsupported action: ${body.action ?? ""}` });
15097
+ sendJson$3(res, 400, { error: `unsupported action: ${body.action ?? ""}` });
15199
15098
  } catch (err) {
15200
- sendJson$4(res, 400, { error: err.message ?? String(err) });
15099
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
15201
15100
  }
15202
15101
  }
15203
15102
  });
@@ -15208,18 +15107,18 @@ function registerStatusApi(ctx, opts = {}) {
15208
15107
  try {
15209
15108
  if (!requireWebUi(req, res, "微信桥配置仅限 WebUI(client 专用)")) return;
15210
15109
  if (req.method !== "GET") {
15211
- sendJson$4(res, 405, { error: "method not allowed" });
15110
+ sendJson$3(res, 405, { error: "method not allowed" });
15212
15111
  return;
15213
15112
  }
15214
15113
  const key = new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("key") ?? "";
15215
15114
  const login = weixinLogins.get(key);
15216
15115
  if (!login) {
15217
- sendJson$4(res, 404, { error: "login not found or expired" });
15116
+ sendJson$3(res, 404, { error: "login not found or expired" });
15218
15117
  return;
15219
15118
  }
15220
15119
  if (Date.now() - login.startedAt > WEIXIN_LOGIN_TTL_MS) {
15221
15120
  weixinLogins.delete(key);
15222
- sendJson$4(res, 200, { status: "expired" });
15121
+ sendJson$3(res, 200, { status: "expired" });
15223
15122
  return;
15224
15123
  }
15225
15124
  const { pollQRStatus } = await import("./weixin-api-BrRcPiQs.js").then((n) => n.u);
@@ -15232,7 +15131,7 @@ function registerStatusApi(ctx, opts = {}) {
15232
15131
  switch (status.status) {
15233
15132
  case "confirmed": {
15234
15133
  if (!status.bot_token) {
15235
- sendJson$4(res, 200, {
15134
+ sendJson$3(res, 200, {
15236
15135
  status: "error",
15237
15136
  error: "confirmed but no bot_token"
15238
15137
  });
@@ -15253,7 +15152,7 @@ function registerStatusApi(ctx, opts = {}) {
15253
15152
  });
15254
15153
  syncCccBridge(ctx, login.root);
15255
15154
  weixinLogins.delete(key);
15256
- sendJson$4(res, 200, {
15155
+ sendJson$3(res, 200, {
15257
15156
  status: "confirmed",
15258
15157
  accountId,
15259
15158
  tokenSaved: true
@@ -15262,14 +15161,14 @@ function registerStatusApi(ctx, opts = {}) {
15262
15161
  }
15263
15162
  case "expired":
15264
15163
  weixinLogins.delete(key);
15265
- sendJson$4(res, 200, { status: "expired" });
15164
+ sendJson$3(res, 200, { status: "expired" });
15266
15165
  return;
15267
15166
  default:
15268
- sendJson$4(res, 200, { status: status.status ?? "wait" });
15167
+ sendJson$3(res, 200, { status: status.status ?? "wait" });
15269
15168
  return;
15270
15169
  }
15271
15170
  } catch (err) {
15272
- sendJson$4(res, 400, { error: err.message ?? String(err) });
15171
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
15273
15172
  }
15274
15173
  }
15275
15174
  });
@@ -15291,7 +15190,7 @@ function registerStatusApi(ctx, opts = {}) {
15291
15190
  const root = sessionsRootDir();
15292
15191
  if (req.method === "GET") {
15293
15192
  const { candidates } = performCleanup(root, cutoffDaysAgo(days), liveIds, { dryRun: true });
15294
- sendJson$4(res, 200, {
15193
+ sendJson$3(res, 200, {
15295
15194
  root,
15296
15195
  olderThanDays: days,
15297
15196
  dryRun: true,
@@ -15305,12 +15204,12 @@ function registerStatusApi(ctx, opts = {}) {
15305
15204
  return;
15306
15205
  }
15307
15206
  if (req.method !== "POST") {
15308
- sendJson$4(res, 405, { error: "method not allowed" });
15207
+ sendJson$3(res, 405, { error: "method not allowed" });
15309
15208
  return;
15310
15209
  }
15311
15210
  if (!requireWebUi(req, res, "会话清理仅限 WebUI(client 专用)")) return;
15312
15211
  const { result } = performCleanup(root, cutoffDaysAgo(days), liveIds);
15313
- sendJson$4(res, 200, {
15212
+ sendJson$3(res, 200, {
15314
15213
  root,
15315
15214
  olderThanDays: days,
15316
15215
  dryRun: false,
@@ -15318,7 +15217,7 @@ function registerStatusApi(ctx, opts = {}) {
15318
15217
  errors: result.errors
15319
15218
  });
15320
15219
  } catch (err) {
15321
- sendJson$4(res, 400, { error: err.message ?? String(err) });
15220
+ sendJson$3(res, 400, { error: err.message ?? String(err) });
15322
15221
  }
15323
15222
  }
15324
15223
  });
@@ -15371,7 +15270,7 @@ function loadVoyageHtml(assetPath = voyageAssetPath()) {
15371
15270
  return readFileSync(assetPath, "utf-8");
15372
15271
  }
15373
15272
  /** 发 JSON(错误分支用;与 api.ts 的 sendJson 同形,但本模块不依赖它,保持零耦合) */
15374
- function sendJson$3(res, status, body) {
15273
+ function sendJson$2(res, status, body) {
15375
15274
  res.statusCode = status;
15376
15275
  res.setHeader("content-type", "application/json; charset=utf-8");
15377
15276
  res.end(JSON.stringify(body));
@@ -15386,14 +15285,14 @@ function registerVoyageApi(ctx, opts = {}) {
15386
15285
  path: VOYAGE_PATH,
15387
15286
  handler: (req, res) => {
15388
15287
  if (req.method !== "GET") {
15389
- sendJson$3(res, 405, { error: "method not allowed" });
15288
+ sendJson$2(res, 405, { error: "method not allowed" });
15390
15289
  return;
15391
15290
  }
15392
15291
  let html;
15393
15292
  try {
15394
15293
  html = loadVoyageHtml(opts.assetPath ?? voyageAssetPath());
15395
15294
  } catch (err) {
15396
- sendJson$3(res, 500, { error: `voyage asset unavailable: ${err?.message ?? String(err)}` });
15295
+ sendJson$2(res, 500, { error: `voyage asset unavailable: ${err?.message ?? String(err)}` });
15397
15296
  return;
15398
15297
  }
15399
15298
  res.statusCode = 200;
@@ -15988,14 +15887,16 @@ function summarizeHostContract(report) {
15988
15887
  *
15989
15888
  * ## 现状 → 本模块要收掉什么(取证:`…/acc-c4c5-current-state.md` §①/§③ 3.1)
15990
15889
  *
15991
- * 插件自己 spawn 了**四个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099 Skiff 调试
15992
- * / E 3100 ACP+问答)。此前四处各写一份同款样板:`createServer` + `server.once('error')` +
15890
+ * 插件自己 spawn 了**三个** node:http 监听器(B 3081 网关 / C 3082 微信发送 / D 3099 Skiff 调试)。
15891
+ * ⚠️ **2026-10-15 变更**:原为四个(含 **E 3100 ACP+问答**),E 已随 owner「砍掉外部问答」令**整面退役**
15892
+ * ⇒ 仍保有面 B/C/D 三个;本段保留"当年是四个"的记述,因为下方"两套拆卸"的病史由它解释。
15893
+ * 此前四处各写一份同款样板:`createServer` + `server.once('error')` +
15993
15894
  * `listen(port, host)` + 模块级 `active` 变量 + `close()`;拆卸也分裂成**两套**——
15994
15895
  * `seams/lifecycle.ts` 有一个聚合 disposer,而 `gateway.ts` 与 `weixin-send-api.ts`
15995
15896
  * **各自另注册**一个(D/E 干脆没有自己的 disposer)。
15996
15897
  *
15997
15898
  * 本模块把"listener 生命周期"收成一处:**active 表 + 统一 listen/close + 统一错误与端口占用
15998
- * 处理**。四个面各自只提供 `{ name, port, host, enabled, handler }`。
15899
+ * 处理**。各面各自只提供 `{ name, port, host, enabled, handler }`。
15999
15900
  *
16000
15901
  * ## 🔴 共享的是「生命周期与样板」,**绝不是**「路由 / 监听器 / 鉴权」
16001
15902
  *
@@ -16003,12 +15904,12 @@ function summarizeHostContract(report) {
16003
15904
  * · **A ↔ C 解耦**(`weixin-send-api.ts:4-7`):3080 会被 3081 原样反代(连请求头),
16004
15905
  * 把 3082 的端点挂到 3080 上 = 已登录的外部家庭账号即可冒充 bot 发消息
16005
15906
  * ⇒ 本模块**不提供**"把面挂到别的面"的能力,也不共享路由表;
16006
- * · **E ↔ D** 共用会话核心(acp-core → skiff-core)但各自独立 listener + 独立路由/HTML;
15907
+ * · (原 **E ↔ D** 共用会话核心但各自独立 listener 一条,**随 E 退役删除**);
16007
15908
  * · **B 是 A 的代理宿主**(把请求打到 `127.0.0.1:<主端口>`),不是另一个路由面。
16008
15909
  *
16009
15910
  * ## 面 A(3080)不在本模块之列
16010
15911
  * 它是**宿主 DSH 主 WebUI**,插件只往上挂 `/serenity/*` 路由(`ctx.webServer.register`),
16011
- * **插件不起这个 listener** ⇒ 面宿主只持有 B/C/D/E 四个自起面。
15912
+ * **插件不起这个 listener** ⇒ 面宿主只持有 B/C/D 三个自起面。
16012
15913
  *
16013
15914
  * ## 两个入口的分工(这是本模块唯一的"抽象决策",别混)
16014
15915
  * · {@link startFace} —— **机械启动**:把一个 handler 绑到 `host:port` 上。同名面若已在监听,
@@ -16633,7 +16534,7 @@ function createDshCookieProvider(connection, authority) {
16633
16534
  /** 面名(C4 块 A:listener 生命周期归 `face-host`) */
16634
16535
  const FACE_GATEWAY = "gateway";
16635
16536
  /** 读取请求体(≤ maxBytes;超限 reject) */
16636
- function readBody$3(req, maxBytes) {
16537
+ function readBody$2(req, maxBytes) {
16637
16538
  return new Promise((resolve, reject) => {
16638
16539
  let data = "";
16639
16540
  req.on("data", (chunk) => {
@@ -16760,7 +16661,7 @@ async function startGateway(config, getAccounts, getDshCookie) {
16760
16661
  const session = authed(req);
16761
16662
  if (session !== void 0) {
16762
16663
  if (req.method === "POST" && isWorkspaceCreatePath(url.pathname)) {
16763
- readBody$3(req, 131072).then((body) => {
16664
+ readBody$2(req, 131072).then((body) => {
16764
16665
  const { rpcId, path } = parseWorkspaceCreateBody(body);
16765
16666
  if (!allowWorkspaceCreate) {
16766
16667
  res.writeHead(403, { "content-type": "application/json" });
@@ -19090,7 +18991,7 @@ const SKIFF_DEBUG_HOST = FACE_PORTS.skiffDebug.host ?? "127.0.0.1";
19090
18991
  function skiffDebugActive() {
19091
18992
  return faceActive(FACE_SKIFF_DEBUG);
19092
18993
  }
19093
- function readBody$2(req) {
18994
+ function readBody$1(req) {
19094
18995
  return new Promise((resolve, reject) => {
19095
18996
  const chunks = [];
19096
18997
  req.on("data", (c) => chunks.push(c));
@@ -19098,7 +18999,7 @@ function readBody$2(req) {
19098
18999
  req.on("error", reject);
19099
19000
  });
19100
19001
  }
19101
- function sendJson$2(res, status, payload) {
19002
+ function sendJson$1(res, status, payload) {
19102
19003
  const body = JSON.stringify(payload);
19103
19004
  res.writeHead(status, {
19104
19005
  "Content-Type": "application/json; charset=utf-8",
@@ -19106,7 +19007,7 @@ function sendJson$2(res, status, payload) {
19106
19007
  });
19107
19008
  res.end(body);
19108
19009
  }
19109
- function sendHtml$1(res, html) {
19010
+ function sendHtml(res, html) {
19110
19011
  res.writeHead(200, {
19111
19012
  "Content-Type": "text/html; charset=utf-8",
19112
19013
  "Cache-Control": "no-store"
@@ -19303,8 +19204,8 @@ function escapeHtml(s) {
19303
19204
  * 在 JSON **文本层**把它们替换为 `\uXXXX` 转义序列:JSON.parse 后语义完全一致(还原原字符),
19304
19205
  * 且 JSC 正则看到的是常规 ASCII 转义(与 JSON.stringify 对控制字符的输出同形态,安全)。
19305
19206
  *
19306
- * 3100 问答页客户端 `await res.json()`(acp-http.ts)与页面内嵌 JSON(skiff-debug/acp-http)
19307
- * 均需此兼容层——iOS Safari 用户实测"复杂回答"触发。
19207
+ * 3099 调试页客户端 `await res.json()` 与页面内嵌 JSON 均需此兼容层
19208
+ * ——iOS Safari 用户实测"复杂回答"触发。(原同批还列了 3100 问答页,该面已退役,2026-10-15)
19308
19209
  *
19309
19210
  * @param jsonText JSON.stringify 的输出文本;原地等价替换,返回 JSC 安全文本
19310
19211
  */
@@ -19432,7 +19333,7 @@ function skiffDebugSpec(ctx, root, port, webPort) {
19432
19333
  port,
19433
19334
  host: SKIFF_DEBUG_HOST,
19434
19335
  enabled: () => readSimpleSettings().skiffEnabled,
19435
- handler: (req, res) => handle$1(ctx, root, webPort, req, res)
19336
+ handler: (req, res) => handle(ctx, root, webPort, req, res)
19436
19337
  };
19437
19338
  }
19438
19339
  /**
@@ -19456,11 +19357,11 @@ async function startSkiffDebugServer(ctx, root, port, webPort) {
19456
19357
  function stopSkiffDebugServer() {
19457
19358
  stopFace(FACE_SKIFF_DEBUG);
19458
19359
  }
19459
- async function handle$1(ctx, defaultRoot, webPort, req, res) {
19360
+ async function handle(ctx, defaultRoot, webPort, req, res) {
19460
19361
  try {
19461
19362
  const url = (req.url ?? "/").split("?")[0] ?? "/";
19462
19363
  if (req.method === "GET" && url === "/") {
19463
- sendHtml$1(res, skiffDebugPage(await listCccs(ctx, {
19364
+ sendHtml(res, skiffDebugPage(await listCccs(ctx, {
19464
19365
  defaultRoot,
19465
19366
  withRoles: true
19466
19367
  }), defaultRoot, webPort));
@@ -19469,11 +19370,11 @@ async function handle$1(ctx, defaultRoot, webPort, req, res) {
19469
19370
  if (req.method === "POST" && url === "/ask") {
19470
19371
  let body;
19471
19372
  try {
19472
- const parsed = JSON.parse(await readBody$2(req));
19373
+ const parsed = JSON.parse(await readBody$1(req));
19473
19374
  if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
19474
19375
  body = parsed;
19475
19376
  } catch {
19476
- sendJson$2(res, 400, { error: "invalid JSON body" });
19377
+ sendJson$1(res, 400, { error: "invalid JSON body" });
19477
19378
  return;
19478
19379
  }
19479
19380
  const ccc = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : defaultRoot;
@@ -19482,11 +19383,11 @@ async function handle$1(ctx, defaultRoot, webPort, req, res) {
19482
19383
  const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
19483
19384
  const role = readSkiffRoles(ccc).get(roleName);
19484
19385
  if (!roleName || !role) {
19485
- sendJson$2(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
19386
+ sendJson$1(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
19486
19387
  return;
19487
19388
  }
19488
19389
  if (!question.trim()) {
19489
- sendJson$2(res, 400, { error: "empty question" });
19390
+ sendJson$1(res, 400, { error: "empty question" });
19490
19391
  return;
19491
19392
  }
19492
19393
  let agent;
@@ -19495,11 +19396,11 @@ async function handle$1(ctx, defaultRoot, webPort, req, res) {
19495
19396
  const info = skiffSessionInfo(sessionId);
19496
19397
  const live = getSkiffAgent(sessionId);
19497
19398
  if (!info || !live) {
19498
- sendJson$2(res, 400, { error: "session is not recoverable (process restarted or session unknown) — start a new conversation" });
19399
+ sendJson$1(res, 400, { error: "session is not recoverable (process restarted or session unknown) — start a new conversation" });
19499
19400
  return;
19500
19401
  }
19501
19402
  if (info.role !== roleName || info.ccc !== ccc) {
19502
- sendJson$2(res, 400, { error: `session "${sessionId}" belongs to role "${info.role}" in another CCC — start a new conversation` });
19403
+ sendJson$1(res, 400, { error: `session "${sessionId}" belongs to role "${info.role}" in another CCC — start a new conversation` });
19503
19404
  return;
19504
19405
  }
19505
19406
  agent = live;
@@ -19508,7 +19409,7 @@ async function handle$1(ctx, defaultRoot, webPort, req, res) {
19508
19409
  const hc = readCheapWorkerConfig(ccc);
19509
19410
  if (!agent) agent = (await createSkiffAgent(ctx, ccc, roleName, role, hc?.defaultModel)).agent;
19510
19411
  const result = await askSkiff(ctx, agent, question, 0);
19511
- sendJson$2(res, 200, {
19412
+ sendJson$1(res, 200, {
19512
19413
  answer: result.answer,
19513
19414
  answer_html: renderSkiffMarkdown(result.answer),
19514
19415
  sessionId: result.sessionId,
@@ -19517,9 +19418,9 @@ async function handle$1(ctx, defaultRoot, webPort, req, res) {
19517
19418
  });
19518
19419
  return;
19519
19420
  }
19520
- sendJson$2(res, 404, { error: "not found" });
19421
+ sendJson$1(res, 404, { error: "not found" });
19521
19422
  } catch (err) {
19522
- sendJson$2(res, 500, { error: err?.message ?? String(err) });
19423
+ sendJson$1(res, 500, { error: err?.message ?? String(err) });
19523
19424
  }
19524
19425
  }
19525
19426
  //#endregion
@@ -19663,7 +19564,9 @@ const rebukeStates = /* @__PURE__ */ new Map();
19663
19564
  //#endregion
19664
19565
  //#region src/output-guard-seam.ts
19665
19566
  /** 外部面会话判定(v1.26.3 用户拍板:仅外部面检测,本地维护会话豁免):
19666
- * skiff-(F4 问答)/ acp-(F4c 程序化)/ rebuild-(v1.22.4 重建会话)前缀 */
19567
+ * skiff-(F4 问答)/ rebuild-(v1.22.4 重建会话)前缀。
19568
+ * 🔴 `acp-` 分支保留但**已是死判据**(无生产者;面 E 已于 2026-10-15 退役)——
19569
+ * 理由与"为何不擅自删"见文件头注释。 */
19667
19570
  function isExternalFaceSession(sessionId) {
19668
19571
  if (!sessionId) return false;
19669
19572
  return isSkiffSessionId(sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
@@ -19958,760 +19861,6 @@ function registerUnattendedSeam(ctx) {
19958
19861
  });
19959
19862
  }
19960
19863
  //#endregion
19961
- //#region src/acp-core.ts
19962
- /** JSON-RPC 错误码(对齐标准) */
19963
- const RPC_ERROR = {
19964
- PARSE: -32700,
19965
- INVALID_REQUEST: -32600,
19966
- METHOD_NOT_FOUND: -32601,
19967
- INVALID_PARAMS: -32602,
19968
- INTERNAL: -32603
19969
- };
19970
- /**
19971
- * ACP 会话管理器:持有 skiff agent 会话(与 skiff-core 注册表同源——
19972
- * 创建走 createSkiffAgent(自动注册),关闭走 unregisterSkiffSession)。
19973
- * 传输无关:HTTP 端点/企业微信桥/未来 stdio 都调 handle(method, params)。
19974
- */
19975
- var AcpServer = class {
19976
- ctx;
19977
- constructor(ctx) {
19978
- this.ctx = ctx;
19979
- }
19980
- /** 实现级方法调度(返回 result 或抛 JSON-RPC 语义错误) */
19981
- async handle(method, params) {
19982
- switch (method) {
19983
- case "initialize": return this.initialize();
19984
- case "authenticate": return {};
19985
- case "session/new": return this.newSession(params);
19986
- case "session/list": return this.listSessions();
19987
- case "session/prompt": return this.prompt(params);
19988
- case "session/cancel": return this.cancel(params);
19989
- case "session/close": return this.closeSession(params);
19990
- case "session/request_permission": return { allow: true };
19991
- default: throw new RpcMethodError(`method not found: ${method}`);
19992
- }
19993
- }
19994
- initialize() {
19995
- return {
19996
- protocolVersion: 1,
19997
- capabilities: {
19998
- sessionId: true,
19999
- ccc: true,
20000
- role: true
20001
- },
20002
- serverInfo: {
20003
- name: "dsh-serenity-hooks-acp",
20004
- version: "1.26.0"
20005
- }
20006
- };
20007
- }
20008
- /**
20009
- * session/new:{ ccc, role, sessionId? } → 创建或延续 skiff 会话。
20010
- * 无 sessionId → createSkiffAgent(新会话,continued:false);
20011
- * 有 → 进程内延续(getSkiffAgent 命中 + role/ccc 绑定校验;未命中 → 错误)。
20012
- */
20013
- async newSession(params) {
20014
- const ccc = typeof params?.ccc === "string" && params.ccc !== "" ? params.ccc : void 0;
20015
- const roleName = typeof params?.role === "string" ? params.role : void 0;
20016
- const sessionId = typeof params?.sessionId === "string" && params.sessionId !== "" ? params.sessionId : void 0;
20017
- if (!ccc) throw new RpcInvalidParams("session/new requires ccc (CCC root path)");
20018
- if (!roleName) throw new RpcInvalidParams("session/new requires role");
20019
- const role = readSkiffRoles(ccc).get(roleName);
20020
- if (!role) throw new RpcInvalidParams(`unknown role: ${roleName} (ccc: ${ccc})`);
20021
- if (sessionId) {
20022
- const info = skiffSessionInfo(sessionId);
20023
- const live = getSkiffAgent(sessionId);
20024
- if (!info || !live) throw new RpcInvalidParams("session is not recoverable (process restarted or session unknown) — start a new conversation");
20025
- if (info.role !== roleName || info.ccc !== ccc) throw new RpcInvalidParams(`session "${sessionId}" belongs to role "${info.role}" in another CCC`);
20026
- return {
20027
- sessionId,
20028
- role: roleName,
20029
- ccc,
20030
- continued: true
20031
- };
20032
- }
20033
- const hc = readCheapWorkerConfig(ccc);
20034
- return {
20035
- sessionId: (await createSkiffAgent(this.ctx, ccc, roleName, role, hc?.defaultModel)).sessionId,
20036
- role: roleName,
20037
- ccc,
20038
- continued: false
20039
- };
20040
- }
20041
- listSessions() {
20042
- const sessions = [];
20043
- for (const [id, b] of skiffSessionSnapshot()) sessions.push({
20044
- sessionId: id,
20045
- role: b.role,
20046
- ccc: b.ccc
20047
- });
20048
- return { sessions };
20049
- }
20050
- /**
20051
- * session/prompt:{ sessionId, question } → 答案(**v1.26.10:不返回 trajectory**——
20052
- * 3100 对外只提供问答,轨迹含工具结果等内部信息;3099 调试页另走 /ask 保留)
20053
- */
20054
- async prompt(params) {
20055
- const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
20056
- const question = typeof params?.question === "string" ? params.question : void 0;
20057
- if (!sessionId) throw new RpcInvalidParams("session/prompt requires sessionId");
20058
- if (!question?.trim()) throw new RpcInvalidParams("session/prompt requires question");
20059
- const agent = getSkiffAgent(sessionId);
20060
- if (!agent) throw new RpcInvalidParams(`unknown session: ${sessionId} (not recoverable — process restarted?)`);
20061
- const result = await askSkiff(this.ctx, agent, question, void 0, { includeTrajectory: false });
20062
- return {
20063
- answer: result.answer,
20064
- sessionId: result.sessionId
20065
- };
20066
- }
20067
- /** session/cancel:{ sessionId } → 取消该会话的当前活动(Agent.cancel,宿主 rc.1 契约) */
20068
- cancel(params) {
20069
- const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
20070
- if (!sessionId) return { cancelled: false };
20071
- const agent = getSkiffAgent(sessionId);
20072
- if (!agent) return { cancelled: false };
20073
- try {
20074
- agent.cancel({ kind: "user" });
20075
- } catch {}
20076
- return { cancelled: true };
20077
- }
20078
- /** session/close:{ sessionId } → 释放 agent + 注册表清理(进程内) */
20079
- closeSession(params) {
20080
- const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
20081
- if (!sessionId) return { closed: false };
20082
- if (!getSkiffAgent(sessionId)) return { closed: false };
20083
- unregisterSkiffSession(sessionId);
20084
- return { closed: true };
20085
- }
20086
- };
20087
- /** 方法不存在错误(JSON-RPC -32601) */
20088
- var RpcMethodError = class extends Error {
20089
- constructor(message) {
20090
- super(message);
20091
- this.name = "RpcMethodError";
20092
- }
20093
- };
20094
- /** 参数错误(JSON-RPC -32602) */
20095
- var RpcInvalidParams = class extends Error {
20096
- constructor(message) {
20097
- super(message);
20098
- this.name = "RpcInvalidParams";
20099
- }
20100
- };
20101
- /**
20102
- * JSON-RPC 2.0 批处理(单帧):解析请求/通知 → 调 AcpServer.handle → 响应。
20103
- * 传输无关:acp-http 端点 / 未来 stdio 共用。
20104
- * @returns 响应帧数组(请求有响应;通知无;解析失败 → 单错误响应)
20105
- */
20106
- async function dispatchRpc(server, frame) {
20107
- if (frame === null || typeof frame !== "object") return [rpcError(null, RPC_ERROR.INVALID_REQUEST, "invalid request")];
20108
- const req = frame;
20109
- if (req.jsonrpc !== "2.0" || typeof req.method !== "string") return [rpcError(req.id ?? null, RPC_ERROR.INVALID_REQUEST, "invalid request")];
20110
- if (req.id === void 0) {
20111
- try {
20112
- await server.handle(req.method, req.params);
20113
- } catch {}
20114
- return [];
20115
- }
20116
- try {
20117
- const result = await server.handle(req.method, req.params);
20118
- return [{
20119
- jsonrpc: "2.0",
20120
- id: req.id,
20121
- result
20122
- }];
20123
- } catch (error) {
20124
- return [rpcError(req.id, rpcCodeOf(error), error instanceof Error ? error.message : String(error))];
20125
- }
20126
- }
20127
- function rpcCodeOf(error) {
20128
- if (error instanceof RpcMethodError) return RPC_ERROR.METHOD_NOT_FOUND;
20129
- if (error instanceof RpcInvalidParams) return RPC_ERROR.INVALID_PARAMS;
20130
- return RPC_ERROR.INTERNAL;
20131
- }
20132
- function rpcError(id, code, message) {
20133
- return {
20134
- jsonrpc: "2.0",
20135
- id,
20136
- error: {
20137
- code,
20138
- message
20139
- }
20140
- };
20141
- }
20142
- //#endregion
20143
- //#region src/acp-http.ts
20144
- /** 面名(C4 块 A) */
20145
- const FACE_ACP_HTTP = "acp-http";
20146
- /** 监听地址:**只绑 loopback**(JSON-RPC 子面无认证——仅靠 loopback;问答页另有 key 认证) */
20147
- const ACP_HTTP_HOST = FACE_PORTS.acpHttp.host ?? "127.0.0.1";
20148
- function acpHttpActive() {
20149
- return faceActive(FACE_ACP_HTTP);
20150
- }
20151
- function readBody$1(req) {
20152
- return new Promise((resolve, reject) => {
20153
- const chunks = [];
20154
- req.on("data", (c) => chunks.push(c));
20155
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
20156
- req.on("error", reject);
20157
- });
20158
- }
20159
- function sendJson$1(res, status, payload) {
20160
- const body = jscSafeJsonText(JSON.stringify(payload));
20161
- res.writeHead(status, {
20162
- "Content-Type": "application/json; charset=utf-8",
20163
- "Cache-Control": "no-store"
20164
- });
20165
- res.end(body);
20166
- }
20167
- function sendHtml(res, html) {
20168
- res.writeHead(200, {
20169
- "Content-Type": "text/html; charset=utf-8",
20170
- "Cache-Control": "no-store"
20171
- });
20172
- res.end(html);
20173
- }
20174
- /**
20175
- * 本面的规格(C4 块 A/④:**只提供** name/port/host/enabled/handler)。
20176
- * 两个子面(ACP JSON-RPC / 对外问答页)的路由与鉴权仍全部归 {@link handle}——
20177
- * 面宿主不介入面语义,也**不合并** D/E 两面(两者共用会话核心但各有独立 listener/路由/HTML)。
20178
- */
20179
- function acpHttpSpec(ctx, port, defaultRoot) {
20180
- return {
20181
- name: FACE_ACP_HTTP,
20182
- port,
20183
- host: ACP_HTTP_HOST,
20184
- enabled: () => {
20185
- const s = readSimpleSettings();
20186
- return s.acpEnabled || s.publicAskEnabled;
20187
- },
20188
- handler: (req, res) => handle(ctx, defaultRoot ?? "", req, res)
20189
- };
20190
- }
20191
- /**
20192
- * 启动 ACP HTTP JSON-RPC + 建议问答页服务(单实例;重复启动幂等返回既有实例)。
20193
- * C4 块 A:listener 的创建/监听/错误/端口占用/关闭归 `face-host`。
20194
- * @param ctx 插件上下文(AcpServer 内部经 skiff-core 创建 agent)
20195
- * @param port 监听端口(仅 127.0.0.1)
20196
- * @param defaultRoot 默认 CCC 根(问答页候选发现兜底;resolveSkiffRoot 传入)
20197
- */
20198
- async function startAcpHttpServer(ctx, port, defaultRoot) {
20199
- if (acpHttpActive()) return;
20200
- const handle = await startFace(acpHttpSpec(ctx, port, defaultRoot));
20201
- const s = readSimpleSettings();
20202
- const faces = [];
20203
- if (s.acpEnabled) faces.push("JSON-RPC");
20204
- if (s.publicAskEnabled) faces.push("问答页(key 认证)");
20205
- console.log(`[serenity-hooks] ✓ ACP HTTP: http://127.0.0.1:${handle.port}(${faces.join(" + ") || "未开启任何面"};session/new 支持 {ccc, role, sessionId?})`);
20206
- }
20207
- function stopAcpHttpServer() {
20208
- stopFace(FACE_ACP_HTTP);
20209
- }
20210
- async function handle(ctx, defaultRoot, req, res) {
20211
- try {
20212
- const url = (req.url ?? "/").split("?")[0] ?? "/";
20213
- const s = readSimpleSettings();
20214
- const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "unknown";
20215
- if (req.method === "POST" && url === "/") {
20216
- if (!s.acpEnabled) {
20217
- sendJson$1(res, 403, { error: "ACP JSON-RPC disabled (enable in Serenity settings)" });
20218
- return;
20219
- }
20220
- let body;
20221
- try {
20222
- body = JSON.parse(await readBody$1(req));
20223
- } catch {
20224
- sendJson$1(res, 400, [{
20225
- jsonrpc: "2.0",
20226
- id: null,
20227
- error: {
20228
- code: -32700,
20229
- message: "parse error"
20230
- }
20231
- }]);
20232
- return;
20233
- }
20234
- sendJson$1(res, 200, await dispatchRpc(new AcpServer(ctx), body));
20235
- return;
20236
- }
20237
- if (!s.publicAskEnabled) {
20238
- if (req.method === "GET" && (url === "/" || url.startsWith("/c/"))) {
20239
- sendHtml(res, publicAskDisabledHtml());
20240
- return;
20241
- }
20242
- if (req.method === "POST" && (url === "/ask" || url.startsWith("/c/"))) {
20243
- sendJson$1(res, 403, { error: "public ask disabled (enable in Serenity settings)" });
20244
- return;
20245
- }
20246
- sendJson$1(res, 404, { error: "not found" });
20247
- return;
20248
- }
20249
- const containerAllowed = (name) => {
20250
- const allowed = readAdvancedSettings().publicAsk.allowed;
20251
- return allowed.length === 0 || allowed.includes(name);
20252
- };
20253
- let cccsCache = null;
20254
- const cccsOf = async () => {
20255
- if (cccsCache === null) cccsCache = await listCccs(ctx, {
20256
- defaultRoot: defaultRoot || (cccRootForCwd(process.cwd()) ?? ""),
20257
- withRoles: true
20258
- });
20259
- return cccsCache;
20260
- };
20261
- const cMatch = /^\/c\/([^/]+)$/.exec(url);
20262
- if (req.method === "GET" && cMatch) {
20263
- const name = decodeURIComponent(cMatch[1] ?? "");
20264
- const ccc = (await cccsOf()).find((c) => c.name === name);
20265
- if (!ccc) {
20266
- sendHtml(res, publicAskContainerUnknownHtml(name));
20267
- return;
20268
- }
20269
- if (!containerAllowed(name)) {
20270
- sendHtml(res, publicAskContainerClosedHtml(name));
20271
- return;
20272
- }
20273
- sendHtml(res, publicAskContainerPage(ccc));
20274
- return;
20275
- }
20276
- if (req.method === "GET" && url === "/") {
20277
- sendHtml(res, publicAskListPage((await cccsOf()).filter((c) => containerAllowed(c.name))));
20278
- return;
20279
- }
20280
- const aMatch = /^\/c\/([^/]+)\/ask$/.exec(url);
20281
- if (req.method === "POST" && aMatch) {
20282
- const name = decodeURIComponent(aMatch[1] ?? "");
20283
- const ccc = (await cccsOf()).find((c) => c.name === name);
20284
- if (!ccc || !containerAllowed(name)) {
20285
- sendJson$1(res, 403, { error: `container "${name}" is not open for public ask` });
20286
- return;
20287
- }
20288
- let body;
20289
- try {
20290
- const parsed = JSON.parse(await readBody$1(req));
20291
- if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
20292
- body = parsed;
20293
- } catch {
20294
- sendJson$1(res, 400, { error: "invalid JSON body" });
20295
- return;
20296
- }
20297
- await handleAskParsed(ctx, ccc, body, res, ip);
20298
- return;
20299
- }
20300
- if (req.method === "POST" && url === "/ask") {
20301
- let body;
20302
- try {
20303
- const parsed = JSON.parse(await readBody$1(req));
20304
- if (parsed === null || typeof parsed !== "object") throw new Error("not an object");
20305
- body = parsed;
20306
- } catch {
20307
- sendJson$1(res, 400, { error: "invalid JSON body" });
20308
- return;
20309
- }
20310
- const cccValue = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : void 0;
20311
- const nameValue = typeof body.name === "string" && body.name !== "" ? body.name : void 0;
20312
- let ccc;
20313
- if (nameValue) ccc = (await cccsOf()).find((c) => c.name === nameValue);
20314
- else if (cccValue) {
20315
- ccc = (await cccsOf()).find((c) => c.root === cccValue);
20316
- if (!ccc && cccValue !== "") ccc = {
20317
- root: cccValue,
20318
- name: cccValue.split("/").filter(Boolean).pop() ?? cccValue,
20319
- roles: [...readSkiffRoles(cccValue).keys()]
20320
- };
20321
- }
20322
- if (!ccc || !containerAllowed(ccc.name)) {
20323
- sendJson$1(res, 403, { error: "container is not open for public ask" });
20324
- return;
20325
- }
20326
- await handleAskParsed(ctx, ccc, body, res, ip);
20327
- return;
20328
- }
20329
- sendJson$1(res, 404, { error: "not found" });
20330
- } catch (err) {
20331
- sendJson$1(res, 500, { error: err?.message ?? String(err) });
20332
- }
20333
- }
20334
- /**
20335
- * 单容器问答核心(/c/<name>/ask 与 /ask 共用):
20336
- * body { key, role?, question, sessionId? } → key 校验(401 + IP 失败锁定)→ 角色缺省取 CCC 第一个
20337
- * → 会话延续(v1.25.10 语义,静默新建)→ askSkiff(0 全量轨迹) → { answer, answer_html, sessionId, continued, trajectory }
20338
- * @param ip 请求来源 IP(v1.26.5 公网 key 失败锁定:X-Forwarded-For 优先——公网反代,回退 remoteAddress)
20339
- */
20340
- async function handleAskParsed(ctx, ccc, body, res, ip) {
20341
- const key = typeof body.key === "string" ? body.key : void 0;
20342
- if (isPublicAskIpLocked(ip)) {
20343
- sendJson$1(res, 429, { error: "too many failed attempts — temporarily locked (retry later)" });
20344
- return;
20345
- }
20346
- if (!verifyPublicAskKey(key)) {
20347
- const remaining = recordPublicAskFail(ip);
20348
- sendJson$1(res, remaining > 0 ? 429 : 401, { error: remaining > 0 ? `too many failed attempts — locked for ${Math.ceil(remaining / 6e4)} min` : "invalid or missing key" });
20349
- return;
20350
- }
20351
- resetPublicAskIpFail(ip);
20352
- const root = ccc.root;
20353
- const roleName = typeof body.role === "string" && body.role !== "" ? body.role : void 0;
20354
- const question = typeof body.question === "string" ? body.question : "";
20355
- const sessionId = typeof body.sessionId === "string" && body.sessionId !== "" ? body.sessionId : void 0;
20356
- if (!question.trim()) {
20357
- sendJson$1(res, 400, { error: "empty question" });
20358
- return;
20359
- }
20360
- const roles = readSkiffRoles(root);
20361
- const explicit = roleName ? roles.get(roleName) : void 0;
20362
- const effectiveRole = roleName && explicit ? roleName : roles.keys().next().value;
20363
- const effectiveCfg = (roleName && explicit ? explicit : effectiveRole ? roles.get(effectiveRole) : void 0) ?? void 0;
20364
- if (!effectiveRole || !effectiveCfg) {
20365
- sendJson$1(res, 400, { error: `no skiff role available in ccc: ${root}` });
20366
- return;
20367
- }
20368
- let agent;
20369
- let continued = false;
20370
- if (sessionId) {
20371
- const info = skiffSessionInfo(sessionId);
20372
- const live = getSkiffAgent(sessionId);
20373
- if (info && live && info.role === effectiveRole && info.ccc === root) {
20374
- agent = live;
20375
- continued = true;
20376
- }
20377
- }
20378
- if (!agent) agent = (await createSkiffAgent(ctx, root, effectiveRole, effectiveCfg, readCheapWorkerConfig(root)?.defaultModel)).agent;
20379
- const result = await askSkiff(ctx, agent, question, void 0, { includeTrajectory: false });
20380
- sendJson$1(res, 200, {
20381
- answer: result.answer,
20382
- answer_html: renderSkiffMarkdown(result.answer, true),
20383
- sessionId: result.sessionId,
20384
- continued
20385
- });
20386
- }
20387
- /** 问答页未启用提示页 */
20388
- function publicAskDisabledHtml() {
20389
- return `<!DOCTYPE html>
20390
- <html lang="zh"><head><meta charset="utf-8"><title>Serenity Public Ask</title></head>
20391
- <body style="font-family:system-ui;padding:48px;text-align:center;color:#57606a">
20392
- <h2>Serenity Public Ask</h2>
20393
- <p>问答页未启用(请在 dsh 设置面板「Serenity」页开启「建议问答」)。</p>
20394
- </body></html>`;
20395
- }
20396
- /** 容器不存在提示页(URL 容器名不在已发现 CCC 中) */
20397
- function publicAskContainerUnknownHtml(name) {
20398
- return `<!DOCTYPE html>
20399
- <html lang="zh"><head><meta charset="utf-8"><title>Serenity Public Ask</title></head>
20400
- <body style="font-family:system-ui;padding:48px;text-align:center;color:#57606a">
20401
- <h2>Serenity Public Ask</h2>
20402
- <p>认知容器「${name}」不存在。</p>
20403
- <p style="font-size:13px"><a href="/" style="color:#0ba875">返回容器列表</a></p>
20404
- </body></html>`;
20405
- }
20406
- /** 容器未开放提示页(白名单外) */
20407
- function publicAskContainerClosedHtml(name) {
20408
- return `<!DOCTYPE html>
20409
- <html lang="zh"><head><meta charset="utf-8"><title>Serenity Public Ask</title></head>
20410
- <body style="font-family:system-ui;padding:48px;text-align:center;color:#57606a">
20411
- <h2>Serenity Public Ask</h2>
20412
- <p>认知容器「${name}」当前未开放问答(管理员在设置面板「开放容器」中配置)。</p>
20413
- <p style="font-size:13px"><a href="/" style="color:#0ba875">返回容器列表</a></p>
20414
- </body></html>`;
20415
- }
20416
- /** 页面基础样式(列表页 + 单容器页共用;v1.26.4 聊天 UI) */
20417
- const ASK_CSS = `
20418
- :root { color-scheme: light dark; --sp-green: #0ba875; --sp-green-dim: #059669; }
20419
- * { box-sizing: border-box; }
20420
- html, body { height: 100%; }
20421
- body { font-family: system-ui, -apple-system, sans-serif; margin: 0; background: #f6f7f9; color: #1f2328; }
20422
- @media (prefers-color-scheme: dark) { body { background: #16181c; color: #e6e6e6; } }
20423
- main { max-width: 720px; margin: 0 auto; }
20424
- h1 { font-size: 18px; margin: 0 0 4px; }
20425
- .sub { opacity: .65; font-size: 13px; margin-bottom: 16px; }
20426
- .card { background: #fff; border: 1px solid #d0d7de; border-radius: 10px; padding: 14px 16px; margin-bottom: 10px; display: block; text-decoration: none; color: inherit; }
20427
- @media (prefers-color-scheme: dark) { .card { background: #26282c; border-color: #3a3d42; } }
20428
- .card:hover { border-color: var(--sp-green); }
20429
- .card .cname { font-weight: 600; font-size: 15px; }
20430
- .card .cmeta { opacity: .6; font-size: 12px; margin-top: 2px; }
20431
- .empty { text-align: center; opacity: .7; padding: 24px 0; }
20432
- .ccname { font-weight: 600; color: var(--sp-green); }
20433
- /* key 门(v1.26.6:选容器前必填) */
20434
- .keyGate { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; background: rgba(127,127,127,.06); border: 1px solid rgba(127,127,127,.18); border-radius: 10px; padding: 10px 14px; }
20435
- .keyGate label { font-size: 12px; opacity: .7; white-space: nowrap; }
20436
- .keyGate input { flex: 1; border: 1px solid rgba(127,127,127,.25); border-radius: 8px; background: transparent; color: inherit; padding: 8px 12px; font-size: 14px; }
20437
- .keyGate input:focus { outline: none; border-color: var(--sp-green); }
20438
-
20439
- /* ── 聊天布局(v1.26.4 体验升级:参考 ChatGPT/Claude 会话形态)── */
20440
- .chat { display: flex; flex-direction: column; height: 100vh; max-width: 720px; margin: 0 auto; }
20441
- .chatHead { display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-bottom: 1px solid rgba(127,127,127,.18); background: rgba(127,127,127,.04); position: sticky; top: 0; z-index: 5; }
20442
- .chatHead .back { color: var(--sp-green); text-decoration: none; font-size: 13px; white-space: nowrap; }
20443
- .chatHead .title { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
20444
- .chatHead .spacer { flex: 1; }
20445
- .chatHead select { border: 1px solid rgba(127,127,127,.25); border-radius: 8px; background: transparent; color: inherit; padding: 4px 8px; font-size: 12px; }
20446
- .chatHead .newBtn { border: 1px solid rgba(127,127,127,.25); border-radius: 8px; background: transparent; color: inherit; padding: 4px 10px; font-size: 12px; cursor: pointer; white-space: nowrap; }
20447
- .chatHead .newBtn:hover { border-color: var(--sp-green); color: var(--sp-green); }
20448
- /* 消息流 */
20449
- .msgList { flex: 1; overflow-y: auto; padding: 18px 16px; display: flex; flex-direction: column; gap: 16px; }
20450
- .msg { display: flex; flex-direction: column; max-width: 88%; }
20451
- .msg.user { align-self: flex-end; align-items: flex-end; }
20452
- .msg.assistant { align-self: flex-start; align-items: flex-start; }
20453
- .msg .who { font-size: 11px; opacity: .55; margin-bottom: 4px; padding: 0 4px; }
20454
- .msg .bubble { padding: 10px 14px; border-radius: 14px; font-size: 14px; line-height: 1.65; word-break: break-word; white-space: normal; }
20455
- .msg.user .bubble { background: var(--sp-green); color: #fff; border-bottom-right-radius: 4px; }
20456
- .msg.assistant .bubble { background: #fff; border: 1px solid #d0d7de; border-bottom-left-radius: 4px; }
20457
- @media (prefers-color-scheme: dark) { .msg.assistant .bubble { background: #26282c; border-color: #3a3d42; } }
20458
- .bubble p { margin: 6px 0; }
20459
- .bubble p:first-child { margin-top: 0; }
20460
- .bubble p:last-child { margin-bottom: 0; }
20461
- .bubble h1, .bubble h2, .bubble h3, .bubble h4 { margin: 12px 0 6px; font-size: 15px; line-height: 1.4; }
20462
- .bubble ul, .bubble ol { margin: 6px 0; padding-left: 22px; }
20463
- .bubble code { background: rgba(127,127,127,.14); border-radius: 4px; padding: 1px 5px; font-size: 13px; font-family: ui-monospace, monospace; }
20464
- .bubble pre { background: rgba(127,127,127,.1); border-radius: 8px; padding: 10px 12px; overflow-x: auto; margin: 8px 0; }
20465
- .bubble pre code { background: none; padding: 0; }
20466
- .bubble blockquote { margin: 6px 0; padding: 2px 12px; border-left: 3px solid rgba(127,127,127,.3); opacity: .85; }
20467
- .bubble a { color: var(--sp-green); }
20468
- .bubble table { border-collapse: collapse; margin: 8px 0; }
20469
- .bubble th, .bubble td { border: 1px solid rgba(127,127,127,.25); padding: 4px 10px; font-size: 13px; }
20470
- .bubble img { max-width: 100%; border-radius: 8px; }
20471
- .bubble hr { border: 0; border-top: 1px solid rgba(127,127,127,.2); margin: 10px 0; }
20472
- .typing { display: inline-flex; align-items: center; gap: 4px; padding: 4px 2px; }
20473
- .typing i { width: 6px; height: 6px; border-radius: 50%; background: rgba(127,127,127,.5); animation: blink 1.2s infinite; }
20474
- .typing i:nth-child(2) { animation-delay: .2s; }
20475
- .typing i:nth-child(3) { animation-delay: .4s; }
20476
- @keyframes blink { 0%, 80%, 100% { opacity: .3 } 40% { opacity: 1 } }
20477
- .err { color: #cf222e; white-space: pre-wrap; font-size: 13px; }
20478
- .msg .err { padding: 0 4px; }
20479
- /* 输入区 */
20480
- .chatInput { border-top: 1px solid rgba(127,127,127,.18); padding: 10px 16px calc(12px + env(safe-area-inset-bottom)); background: rgba(127,127,127,.04); }
20481
- .inputRow { display: flex; align-items: flex-end; gap: 8px; }
20482
- .inputRow textarea { flex: 1; border: 1px solid rgba(127,127,127,.25); border-radius: 12px; background: #fff; color: inherit; padding: 10px 12px; font-size: 14px; resize: none; min-height: 44px; max-height: 140px; line-height: 1.5; }
20483
- @media (prefers-color-scheme: dark) { .inputRow textarea { background: #26282c; border-color: #3a3d42; } }
20484
- .inputRow textarea:focus { outline: none; border-color: var(--sp-green); }
20485
- .inputRow .sendBtn { border: 0; border-radius: 12px; background: var(--sp-green); color: #fff; padding: 10px 18px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; }
20486
- .inputRow .sendBtn:disabled { opacity: .5; cursor: wait; }
20487
- .inputRow .sendBtn:hover:not(:disabled) { background: var(--sp-green-dim); }
20488
- .hint { text-align: center; font-size: 12px; opacity: .55; padding: 20px 0; }
20489
-
20490
- /* ── 移动端适配(v1.26.6:手机/平板——iOS 防放大 16px / 触控 ≥44px / 100dvh / safe-area)── */
20491
- @media (max-width: 640px) {
20492
- main { padding: 20px 16px calc(28px + env(safe-area-inset-bottom)); }
20493
- .sub { font-size: 14px; margin-bottom: 14px; }
20494
- .keyGate { flex-wrap: wrap; gap: 6px; }
20495
- .keyGate input { flex: 1 1 100%; font-size: 16px; padding: 12px 14px; min-height: 48px; } /* 16px 防 iOS 聚焦放大 + 触控 ≥44px */
20496
- .card { padding: 16px; border-radius: 12px; }
20497
- .card .cname { font-size: 16px; }
20498
- .card .cmeta { font-size: 13px; }
20499
- .chat { height: 100vh; height: 100dvh; } /* dvh:iOS 地址栏收起/展开不跳动 */
20500
- .chatHead { gap: 8px; padding: 8px 12px calc(8px + env(safe-area-inset-top)); }
20501
- .chatHead .back { font-size: 14px; padding: 10px 4px; } /* 触控目标 ≥44px */
20502
- .chatHead .title { font-size: 14px; }
20503
- .chatHead select { font-size: 16px; padding: 8px 6px; min-height: 44px; } /* 16px 防放大 + 触控 */
20504
- .chatHead .newBtn { font-size: 14px; padding: 10px 12px; min-height: 44px; }
20505
- .msgList { padding: 14px 12px calc(20px + env(safe-area-inset-bottom)); gap: 14px; }
20506
- .msg { max-width: 94%; }
20507
- .msg .bubble { font-size: 15px; padding: 12px 14px; }
20508
- .msg .who { font-size: 12px; }
20509
- .chatInput { padding: 8px 12px calc(10px + env(safe-area-inset-bottom)); }
20510
- .inputRow textarea { font-size: 16px; padding: 12px 14px; min-height: 48px; } /* 16px 防放大 + 触控 */
20511
- .inputRow .sendBtn { font-size: 16px; padding: 12px 20px; min-height: 48px; }
20512
- }
20513
- `;
20514
- /** 容器列表页(v1.26.2 → v1.26.6):key 在选容器前填写(v1.26.6 用户拍板:不填 key 不让选容器) */
20515
- function publicAskListPage(cccs) {
20516
- const cards = cccs.map((c) => `
20517
- <a class="card" data-name="${c.name}" href="/c/${encodeURIComponent(c.name)}">
20518
- <div class="cname">${c.name}</div>
20519
- <div class="cmeta">${c.roles.length} 个问答角色 · 点击进入</div>
20520
- </a>`).join("") || "<div class=\"empty\">暂无可问答的认知容器(管理员尚未开放)</div>";
20521
- return `<!DOCTYPE html>
20522
- <html lang="zh">
20523
- <head>
20524
- <meta charset="utf-8">
20525
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
20526
- <title>Serenity Public Ask</title>
20527
- <style>${ASK_CSS}</style>
20528
- </head>
20529
- <body>
20530
- <main>
20531
- <h1>Serenity Public Ask</h1>
20532
- <div class="sub">先填写访问 key,再选择认知容器开始提问</div>
20533
- <div class="keyGate">
20534
- <label for="gateKey">访问 Key</label>
20535
- <input id="gateKey" type="password" placeholder="请输入访问 key(仅本次浏览器记忆)" autocomplete="off" autocapitalize="none" enterkeyhint="done">
20536
- </div>
20537
- <div id="cards">${cards}</div>
20538
- </main>
20539
- <script>
20540
- const KEY_STORE = 'serenity-public-ask-key'
20541
- const gateKey = document.getElementById('gateKey')
20542
- const cardList = document.getElementById('cards')
20543
- // 容器卡片(key 未填时禁用——v1.26.6 用户拍板:key 在选容器前填写)
20544
- function applyGate() {
20545
- const ok = gateKey.value.trim().length > 0
20546
- for (const card of cardList.querySelectorAll('a.card')) {
20547
- card.classList.toggle('locked', !ok)
20548
- card.setAttribute('aria-disabled', ok ? 'false' : 'true')
20549
- if (!ok) { card.removeAttribute('href'); card.style.pointerEvents = 'none'; card.style.opacity = '.4' }
20550
- else { card.setAttribute('href', '/c/' + encodeURIComponent(card.dataset.name)); card.style.pointerEvents = ''; card.style.opacity = '' }
20551
- }
20552
- }
20553
- gateKey.addEventListener('input', () => {
20554
- // 即存 localStorage(v1.26.7 修复:v1.26.6 漏掉保存——填一次就记录,对话页读取)
20555
- try { localStorage.setItem(KEY_STORE, gateKey.value.trim()) } catch {}
20556
- applyGate()
20557
- })
20558
- // key 自动从 localStorage 恢复
20559
- let hasSavedKey = false
20560
- try { const saved = localStorage.getItem(KEY_STORE); if (saved) { gateKey.value = saved; hasSavedKey = true } } catch {}
20561
- if (hasSavedKey) gateKey.placeholder = '访问 Key(已记忆,可修改)'
20562
- applyGate()
20563
- gateKey.focus()
20564
- <\/script>
20565
- </body>
20566
- </html>`;
20567
- }
20568
- /** 单容器问答页(v1.26.2→v1.26.6):URL 锁定容器;聊天 UI——消息流 + 连续对话 + key 从列表页记忆(v1.26.6 用户拍板:对话页不再填 key) */
20569
- function publicAskContainerPage(ccc) {
20570
- const data = jscSafeJsonText(JSON.stringify({
20571
- name: ccc.name,
20572
- root: ccc.root,
20573
- roles: ccc.roles
20574
- }).replace(/</g, "\\u003c"));
20575
- return `<!DOCTYPE html>
20576
- <html lang="zh">
20577
- <head>
20578
- <meta charset="utf-8">
20579
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
20580
- <title>Serenity · ${ccc.name}</title>
20581
- <style>${ASK_CSS}</style>
20582
- </head>
20583
- <body>
20584
- <div class="chat">
20585
- <div class="chatHead">
20586
- <a class="back" href="/">← 容器</a>
20587
- <span class="title">Serenity · <span class="ccname">${ccc.name}</span></span>
20588
- <span class="spacer"></span>
20589
- <select id="role" title="问答角色"></select>
20590
- <button id="newChat" class="newBtn" title="清空当前对话(不存储,刷新即失)">新对话</button>
20591
- </div>
20592
-
20593
- <div id="msgs" class="msgList"></div>
20594
-
20595
- <div class="chatInput">
20596
- <div class="inputRow">
20597
- <textarea id="q" placeholder="向 ${ccc.name} 提问…(Enter 发送,Shift+Enter 换行)" rows="1" enterkeyhint="send" autocapitalize="sentences"></textarea>
20598
- <button id="ask" class="sendBtn">发送</button>
20599
- </div>
20600
- </div>
20601
- </div>
20602
- <script id="ask-data" type="application/json">${data}<\/script>
20603
- <script>
20604
- const CCC = JSON.parse(document.getElementById('ask-data').textContent)
20605
- const KEY_STORE = 'serenity-public-ask-key'
20606
- const roleSel = document.getElementById('role')
20607
- const btn = document.getElementById('ask')
20608
- const msgs = document.getElementById('msgs')
20609
- const qInput = document.getElementById('q')
20610
- let sessionId = ''
20611
- let busy = false
20612
-
20613
- // 角色下拉
20614
- roleSel.innerHTML = (CCC.roles || []).map((r) => '<option value="' + r + '">' + r + '</option>').join('') || '<option value="">(无角色)</option>'
20615
- // key 从 localStorage 读取(列表页填写时记忆;对话页不填——v1.26.6 用户拍板)
20616
- function getStoredKey() {
20617
- try { return (localStorage.getItem(KEY_STORE) || '').trim() } catch { return '' }
20618
- }
20619
-
20620
- /** 追加一条消息气泡(role: user|assistant) */
20621
- function addMsg(role, htmlOrText, isHtml) {
20622
- const m = document.createElement('div')
20623
- m.className = 'msg ' + role
20624
- const who = document.createElement('div')
20625
- who.className = 'who'
20626
- who.textContent = role === 'user' ? '你' : CCC.name
20627
- const bubble = document.createElement('div')
20628
- bubble.className = 'bubble'
20629
- if (isHtml) bubble.innerHTML = htmlOrText
20630
- else bubble.textContent = htmlOrText
20631
- m.appendChild(who)
20632
- m.appendChild(bubble)
20633
- msgs.appendChild(m)
20634
- msgs.scrollTop = msgs.scrollHeight
20635
- return bubble
20636
- }
20637
-
20638
- /** 打字指示器(等待回答时显示) */
20639
- function addTyping() {
20640
- const m = document.createElement('div')
20641
- m.className = 'msg assistant'
20642
- const who = document.createElement('div')
20643
- who.className = 'who'
20644
- who.textContent = CCC.name
20645
- const bubble = document.createElement('div')
20646
- bubble.className = 'bubble'
20647
- bubble.innerHTML = '<span class="typing"><i></i><i></i><i></i></span>'
20648
- m.appendChild(who)
20649
- m.appendChild(bubble)
20650
- msgs.appendChild(m)
20651
- msgs.scrollTop = msgs.scrollHeight
20652
- return bubble
20653
- }
20654
-
20655
- async function ask() {
20656
- const q = qInput.value.trim()
20657
- const key = getStoredKey()
20658
- const role = roleSel.value || undefined
20659
- if (!q) return
20660
- if (!key) { addMsg('assistant', '⚠️ 未找到访问 key,请先回到 <a href="/">容器列表页</a> 填写 key。', true); return }
20661
- if (busy) return
20662
- // 追加用户消息 + 打字指示
20663
- addMsg('user', q, false)
20664
- qInput.value = ''
20665
- autoGrow()
20666
- const typing = addTyping()
20667
- busy = true
20668
- btn.disabled = true
20669
- try {
20670
- const res = await fetch('/c/' + encodeURIComponent(CCC.name) + '/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, role, question: q, sessionId: sessionId || undefined }) })
20671
- const data = await res.json()
20672
- typing.remove() // 移除打字指示,替换为真实回答
20673
- if (!res.ok) {
20674
- sessionId = ''
20675
- addMsg('assistant', '⚠️ ' + (data.error || ('HTTP ' + res.status)), true)
20676
- return
20677
- }
20678
- addMsg('assistant', data.answer_html || data.answer || '(空回答)', true)
20679
- sessionId = data.sessionId || ''
20680
- } catch (err) {
20681
- typing.remove()
20682
- addMsg('assistant', '⚠️ ' + String(err.message || err), true)
20683
- } finally {
20684
- busy = false
20685
- btn.disabled = false
20686
- qInput.focus()
20687
- }
20688
- }
20689
-
20690
- function autoGrow() {
20691
- qInput.style.height = 'auto'
20692
- qInput.style.height = Math.min(qInput.scrollHeight, 140) + 'px'
20693
- }
20694
-
20695
- btn.addEventListener('click', () => void ask())
20696
- qInput.addEventListener('keydown', (e) => {
20697
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); void ask() }
20698
- })
20699
- qInput.addEventListener('input', autoGrow)
20700
- // 新对话:重置 sessionId + 清空消息流(前端内存,不存储——刷新即失)
20701
- document.getElementById('newChat').addEventListener('click', () => {
20702
- sessionId = ''
20703
- msgs.innerHTML = ''
20704
- qInput.focus()
20705
- })
20706
-
20707
- // 初始欢迎消息(外层模板求值注入容器名;textContent 渲染防注入)
20708
- addMsg('assistant', '你好,我是「${ccc.name}」的认知助手。有什么可以帮你的?(本对话仅当前页面有效,刷新即失)', false)
20709
- qInput.focus()
20710
- <\/script>
20711
- </body>
20712
- </html>`;
20713
- }
20714
- //#endregion
20715
19864
  //#region src/human-channel.ts
20716
19865
  /**
20717
19866
  * human-channel.ts — 人机信道的**机制层**(机器级;2026-09-26 owner 定案 ⇒ 方案 §2.6)
@@ -21033,7 +20182,7 @@ function buildManualOutputRebuke(session) {
21033
20182
  }
21034
20183
  /**
21035
20184
  * 装配闸门(index.ts apply 调用)。
21036
- * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff/ACP 临时会话)零干预。
20185
+ * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff 会话)零干预。
21037
20186
  */
21038
20187
  function registerWeixinOutputGuard(ctx) {
21039
20188
  try {
@@ -21394,7 +20543,7 @@ function manualOutputFallbackNeeded(input) {
21394
20543
  */
21395
20544
  async function resolveCccRootByAlias(ctx, alias) {
21396
20545
  try {
21397
- const { listCccs } = await import("./ccc-roots-BC8mn8iM.js").then((n) => n.a);
20546
+ const { listCccs } = await import("./ccc-roots-GIJ3A_tw.js").then((n) => n.a);
21398
20547
  const entries = await listCccs(ctx);
21399
20548
  const byName = entries.find((e) => e.name === alias);
21400
20549
  if (byName) return byName.root;
@@ -21893,7 +21042,7 @@ function syncHumanChannel(ctx) {
21893
21042
  (async () => {
21894
21043
  try {
21895
21044
  stopMachineLoops();
21896
- const { listCccs } = await import("./ccc-roots-BC8mn8iM.js").then((n) => n.a);
21045
+ const { listCccs } = await import("./ccc-roots-GIJ3A_tw.js").then((n) => n.a);
21897
21046
  for (const entry of await listCccs(ctx)) syncCccBridge(ctx, entry.root);
21898
21047
  } catch {}
21899
21048
  })();
@@ -22129,7 +21278,7 @@ function matchCcc(input, candidates) {
22129
21278
  }
22130
21279
  /** 组装候选列表(`ccc-roots.listCccs` 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
22131
21280
  async function collectCandidates(ctx) {
22132
- const { listCccs } = await import("./ccc-roots-BC8mn8iM.js").then((n) => n.a);
21281
+ const { listCccs } = await import("./ccc-roots-GIJ3A_tw.js").then((n) => n.a);
22133
21282
  const entries = await listCccs(ctx, { defaultRoot: process.cwd() });
22134
21283
  const seen = /* @__PURE__ */ new Set();
22135
21284
  const out = [];
@@ -22395,7 +21544,7 @@ function registerLifecycle(ctx) {
22395
21544
  stopAllBridges();
22396
21545
  } catch {}
22397
21546
  };
22398
- registerDisposer(ctx, "self-started resources (gateway/weixin/skiff-debug/acp faces + weixin bridges)", disposeAll);
21547
+ registerDisposer(ctx, "self-started resources (gateway/weixin/skiff-debug faces + weixin bridges)", disposeAll);
22399
21548
  }
22400
21549
  //#endregion
22401
21550
  //#region src/web-fetch-provider.ts
@@ -24597,7 +23746,7 @@ function renderDsl(model) {
24597
23746
  * 🔴 **为什么 PNG 是必需的**:宿主的对话内图片通路**白名单只有光栅格式**
24598
23747
  * (`image/png|jpeg|webp|gif`,见 `dsh-attachment/types.d.ts`)⇒ **SVG 进不去**。
24599
23748
  *
24600
- * 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 skiff/acp 口径。
23749
+ * 🔴 **开关**:由 `index.ts` 门控(**关时不注册** ⇒ 工具根本不进模型工具清单),同 skiff 口径。
24601
23750
  *
24602
23751
  * ## 🔴 两条**宿主契约**(2026-09-29 现场取证;第 ① 条是本文件第一版写错的地方)
24603
23752
  *
@@ -24971,10 +24120,6 @@ const Config = z.object({
24971
24120
  enabled: z.boolean().default(false),
24972
24121
  debugPort: z.number().min(1024).max(65535).default(SKIFF_DEBUG_PORT)
24973
24122
  }),
24974
- acp: z.object({
24975
- enabled: z.boolean().default(false),
24976
- httpPort: z.number().min(1024).max(65535).default(ACP_HTTP_PORT)
24977
- }),
24978
24123
  webFetch: z.object({ enabled: z.boolean().default(true) }),
24979
24124
  opencodeProvider: z.object({ autoConfigure: z.boolean().default(true) }),
24980
24125
  visionPatch: z.object({ enabled: z.boolean().default(true) }),
@@ -24989,9 +24134,6 @@ const Config = z.object({
24989
24134
  rebuildThresholdK: z.number().min(50).max(4e3).volatile(),
24990
24135
  skiffEnabled: z.boolean().volatile(),
24991
24136
  skiffDebugPort: z.number().min(1024).max(65535).volatile(),
24992
- acpEnabled: z.boolean().volatile(),
24993
- acpHttpPort: z.number().min(1024).max(65535).volatile(),
24994
- publicAskEnabled: z.boolean().volatile(),
24995
24137
  croEnabled: z.boolean().volatile(),
24996
24138
  unattendedEnabled: z.boolean().volatile(),
24997
24139
  diagramEnabled: z.boolean().volatile()
@@ -25054,7 +24196,6 @@ function apply(ctx, config) {
25054
24196
  if (config.opencodeSkills) registerOpencodeSkills(ctx);
25055
24197
  registerBootstrap(ctx);
25056
24198
  registerSkiff(ctx);
25057
- registerAcp(ctx);
25058
24199
  registerCroTurnTracking(ctx);
25059
24200
  registerWakeScheduler(ctx);
25060
24201
  registerWeixinBridge(ctx);
@@ -25078,7 +24219,7 @@ function apply(ctx, config) {
25078
24219
  * ① 听宿主的**真事件** `settings/document-updated`(ns = 我们那张设置页)——顺带说明为什么不用
25079
24220
  * 我们自己的 `serenity/settings-changed`:那个事件**全仓没有任何 `emit` 点**(只有 `ctx.on` 侧
25080
24221
  * 在用)=**死通道**,照着它接线等于接了个永远不响的铃;
25081
- * ② 兜底再挂 `agent/created` / `session/created`(与 skiff/acp 同款做法,那两条在实测里确实会 fire)
24222
+ * ② 兜底再挂 `agent/created` / `session/created`(与 skiff 同款做法,那两条在实测里确实会 fire)
25082
24223
  * —— 宿主事件面若换名,至少还有这一条能自愈;
25083
24224
  * ③ 启动时同步一次(apply 时按当前配置定初始态)。
25084
24225
  * 🔴 **关的时候要注销**(不是"留着但不响应"):与"关时不注册"同一口径 —— 工具不出现在模型清单里。
@@ -25125,7 +24266,8 @@ function registerDiagramTool(ctx) {
25125
24266
  * 同步一次——此刻通常还没有 live 会话,进程 cwd(服务启动目录)也不在 CCC 内 →
25126
24267
  * `resolveSkiffRoot` 返回 null → 打印一行警告后**永不重试**(实证:重启日志
25127
24268
  * `✗ Skiff 调试服务未启动:无法定位 CCC root`,`ss -ltn` 无 3099;同批 ACP 因容忍
25128
- * `root ?? undefined` 而正常启动)。
24269
+ * `root ?? undefined` 而正常启动 —— ⚠️ **该对照已随面 E 退役而失效**(2026-10-15),
24270
+ * 但本条记录的**病因与修法不受影响**(Skiff 侧仍是"首次未定位 → 曾永不重试")。
25129
24271
  *
25130
24272
  * v1.30.14(重启日志实证后的收敛):**并发启动守卫** + 首次未定位降级为信息级日志。
25131
24273
  * - 实证(v1.30.13 重启日志):定时触发与"会话就绪"事件几乎同时到达 → 两次 sync 都
@@ -25249,29 +24391,5 @@ function readWebPort(ctx) {
25249
24391
  return MAIN_WEB_PORT;
25250
24392
  }
25251
24393
  }
25252
- /**
25253
- * F4c ACP HTTP + F4d 建议问答页装配(v1.26.x 实验性):启停 = 人工(设置面板
25254
- * 「Serenity」页 ACP / 问答页开关,settings 持久化)。任一面开启即启动服务
25255
- * (同一端口:POST / = JSON-RPC 需 acpEnabled;GET / + /ask = 问答页需 publicAskEnabled)。
25256
- * 会话创建/延续走 acp-core → skiff-core;仅监听 127.0.0.1;未开启零资源占用。
25257
- */
25258
- function registerAcp(ctx) {
25259
- const sync = async () => {
25260
- const s = readSimpleSettings();
25261
- const anyFace = s.acpEnabled || s.publicAskEnabled;
25262
- if (anyFace && !acpHttpActive()) {
25263
- const root = await resolveSkiffRoot(ctx);
25264
- return startAcpHttpServer(ctx, s.acpHttpPort, root ?? void 0).catch((err) => {
25265
- console.error(`[serenity-hooks] ✗ ACP HTTP 服务启动失败: ${String(err?.message ?? err)}`);
25266
- });
25267
- } else if (!anyFace && acpHttpActive()) stopAcpHttpServer();
25268
- };
25269
- try {
25270
- ctx.on("serenity/settings-changed", () => {
25271
- sync();
25272
- });
25273
- } catch {}
25274
- sync();
25275
- }
25276
24394
  //#endregion
25277
24395
  export { Config, apply, humanChannelAccountSchema, humanChannelSubscriptionSchema, inject, name };