@shgroup/dsh-serenity-hooks 1.30.15 → 1.30.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.30.15",
3
+ "version": "1.30.16",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/index.js CHANGED
@@ -1851,9 +1851,13 @@ CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC
1851
1851
 
1852
1852
  ▸ weixin.autoReplyWithLastMessage(v1.30.10,关闭桥的自动输出):
1853
1853
  缺省 true = 现状:桥在 agent 一轮结束后把**最终 assistant 文本**自动回发用户(source="reply")。
1854
- 显式 false = 关闭:桥**不再转发最终文本**,输出权交给 agent——每轮 question 前缀注入
1855
- 「Reply Output (manual mode)」纪律块(含可直接照抄的 weixin-send 命令,CCC 根/账号/用户 id
1856
- 已填好),agent 自己决定发什么、发几条(全部记 source="proactive")。
1854
+ 显式 false = 关闭:桥**不再转发最终文本**,输出权交给 agent——每轮在用户消息**末尾**追加
1855
+ **机制标记** [serenity:weixin-manual-output] + 一行已填好参数的 weixin-send 命令
1856
+ (CCC 根/账号/用户 id),agent 自己决定发什么、发几条(全部记 source="proactive")。
1857
+ **ACC 只给机制与数据,纪律措辞归 CCC**(v1.30.16):怎么写/必须怎么做/后果是什么写在
1858
+ CCC 角色提示词文件里(如 .opencode/skiff/<role>.md),可热改(v1.30.15 起角色提示词热重载)。
1859
+ 机械闸门(v1.30.16):本轮 turn 结束仍未成功调用 weixin-send → agent 被打回(≤2 次,
1860
+ 提示 = 标记 + 事实);达上限放弃并响亮告警。失败调用不算已送达。
1857
1861
  语义边界(用户拍板):只关最终文本;系统类消息(新对话通知 / 语音无法解析提示)保留;
1858
1862
  **agent 一轮未输出 → 静默不兜底**(否则开关失去意义);typing 指示与 incoming hook 不受影响。
1859
1863
 
@@ -3747,7 +3751,7 @@ var autopilot_trajectory_exports = /* @__PURE__ */ __exportAll({
3747
3751
  shouldWake: () => shouldWake,
3748
3752
  wakeHistoryFor: () => wakeHistoryFor
3749
3753
  });
3750
- const PLUGIN_SOURCE$4 = {
3754
+ const PLUGIN_SOURCE$5 = {
3751
3755
  kind: "plugin",
3752
3756
  plugin: "dsh-serenity-hooks"
3753
3757
  };
@@ -3985,7 +3989,7 @@ async function performAutopilotWake(ctx, root, settings, opts = {}) {
3985
3989
  type: "text",
3986
3990
  text: message
3987
3991
  }],
3988
- source: PLUGIN_SOURCE$4
3992
+ source: PLUGIN_SOURCE$5
3989
3993
  }));
3990
3994
  return {
3991
3995
  ok: true,
@@ -4864,7 +4868,7 @@ function readContextPressure(ctx, session) {
4864
4868
  return null;
4865
4869
  }
4866
4870
  }
4867
- const PLUGIN_SOURCE$3 = {
4871
+ const PLUGIN_SOURCE$4 = {
4868
4872
  kind: "plugin",
4869
4873
  plugin: "dsh-serenity-hooks"
4870
4874
  };
@@ -4928,7 +4932,7 @@ function registerKeeper(ctx, opts = {}) {
4928
4932
  if (blocks.length === 0) return downstream;
4929
4933
  const reminderAll = createUserMessage({
4930
4934
  content: blocks,
4931
- source: PLUGIN_SOURCE$3
4935
+ source: PLUGIN_SOURCE$4
4932
4936
  });
4933
4937
  if (downstream.kind === "block") return {
4934
4938
  kind: "block",
@@ -5976,7 +5980,7 @@ function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entr
5976
5980
  }
5977
5981
  return lines.join("\n");
5978
5982
  }
5979
- const PLUGIN_SOURCE$2 = {
5983
+ const PLUGIN_SOURCE$3 = {
5980
5984
  kind: "plugin",
5981
5985
  plugin: "dsh-serenity-hooks"
5982
5986
  };
@@ -5993,7 +5997,7 @@ function accMessage(root, configPaths, entrySkillMaxChars) {
5993
5997
  }];
5994
5998
  return createUserMessage({
5995
5999
  content,
5996
- source: PLUGIN_SOURCE$2
6000
+ source: PLUGIN_SOURCE$3
5997
6001
  });
5998
6002
  }
5999
6003
  /** 每 agent 是否已注入过(进程内存态) */
@@ -7884,7 +7888,7 @@ var rebuild_exports = /* @__PURE__ */ __exportAll({
7884
7888
  sanitizeFocusLine: () => sanitizeFocusLine,
7885
7889
  stripAckSuffix: () => stripAckSuffix
7886
7890
  });
7887
- const PLUGIN_SOURCE$1 = {
7891
+ const PLUGIN_SOURCE$2 = {
7888
7892
  kind: "plugin",
7889
7893
  plugin: "dsh-serenity-hooks"
7890
7894
  };
@@ -8174,7 +8178,7 @@ function registerRebuildTurnHook(ctx) {
8174
8178
  type: "text",
8175
8179
  text: `${eventToken("rebuild")} The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent trajectory (SESSION.md) and continue the work automatically from the last checkpoint.`
8176
8180
  }],
8177
- source: PLUGIN_SOURCE$1
8181
+ source: PLUGIN_SOURCE$2
8178
8182
  }));
8179
8183
  writeRebuildDiag(resolveSerenityRootFor(agent), {
8180
8184
  sessionId: id,
@@ -8348,7 +8352,7 @@ function buildRebuke(hits) {
8348
8352
  const rebukeStates = /* @__PURE__ */ new Map();
8349
8353
  //#endregion
8350
8354
  //#region src/output-guard-seam.ts
8351
- const PLUGIN_SOURCE = {
8355
+ const PLUGIN_SOURCE$1 = {
8352
8356
  kind: "plugin",
8353
8357
  plugin: "dsh-serenity-hooks"
8354
8358
  };
@@ -8409,7 +8413,7 @@ function registerOutputGuardHook(ctx) {
8409
8413
  type: "text",
8410
8414
  text: buildRebuke(hits)
8411
8415
  }],
8412
- source: PLUGIN_SOURCE
8416
+ source: PLUGIN_SOURCE$1
8413
8417
  }));
8414
8418
  } catch (error) {
8415
8419
  console.warn(`[serenity-hooks] output-guard steer failed: ${String(error?.message ?? error)}`);
@@ -9175,6 +9179,105 @@ qInput.focus()
9175
9179
  </html>`;
9176
9180
  }
9177
9181
  //#endregion
9182
+ //#region src/weixin-output-guard.ts
9183
+ const PLUGIN_SOURCE = {
9184
+ kind: "plugin",
9185
+ plugin: "dsh-serenity-hooks"
9186
+ };
9187
+ const manualSessions = /* @__PURE__ */ new Map();
9188
+ /** 本轮已成功发送的会话(turn-stopping 时结算并清空) */
9189
+ const sentThisTurn = /* @__PURE__ */ new Set();
9190
+ /** 连续打回计数(成功发送即清零) */
9191
+ const rebukeCounts = /* @__PURE__ */ new Map();
9192
+ /** 桥:登记手动模式会话(每轮 incoming 都调用,幂等覆盖) */
9193
+ function noteManualOutputSession(sessionId, session) {
9194
+ if (typeof sessionId !== "string" || sessionId === "") return;
9195
+ manualSessions.set(sessionId, session);
9196
+ }
9197
+ /** 桥:非手动模式 / 会话失效 → 注销(闸门随之失效,零干预) */
9198
+ function forgetManualOutputSession(sessionId) {
9199
+ manualSessions.delete(sessionId);
9200
+ sentThisTurn.delete(sessionId);
9201
+ rebukeCounts.delete(sessionId);
9202
+ }
9203
+ /** 从 agent / 会话对象取会话 id(形状宽容) */
9204
+ function sessionIdOf$1(value) {
9205
+ const v = value;
9206
+ const id = v?.session?.id ?? v?.id;
9207
+ return typeof id === "string" && id !== "" ? id : null;
9208
+ }
9209
+ /** 工具调用是否为「成功的 weixin-send」 */
9210
+ function isSuccessfulWeixinSend(exec, result) {
9211
+ const e = exec;
9212
+ if (e?.name !== "msm") return false;
9213
+ if (e.arguments?.name !== "weixin-send") return false;
9214
+ return result?.isError !== true;
9215
+ }
9216
+ /** 打回消息(机制事实 + 标记;怎么做由 CCC 角色提示词定义) */
9217
+ function buildManualOutputRebuke(session) {
9218
+ return [
9219
+ "[serenity:weixin-manual-output] no send detected in this turn — the user has received NOTHING.",
9220
+ `ccc=${session.root} account=${session.accountId} user=${session.userId}`,
9221
+ `msm("weixin-send", ["send", "--ccc", "${session.root}", "--account", "${session.accountId}", "--user", "${session.userId}", "<回复>"])`
9222
+ ].join("\n");
9223
+ }
9224
+ /**
9225
+ * 装配闸门(index.ts apply 调用)。
9226
+ * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff/ACP 临时会话)零干预。
9227
+ */
9228
+ function registerWeixinOutputGuard(ctx) {
9229
+ try {
9230
+ ctx.on("tools/post-execute", async (exec, result, next) => {
9231
+ const downstream = await next();
9232
+ try {
9233
+ const sessionId = sessionIdOf$1(exec?.agent);
9234
+ if (sessionId && manualSessions.has(sessionId) && isSuccessfulWeixinSend(exec, result)) {
9235
+ sentThisTurn.add(sessionId);
9236
+ rebukeCounts.delete(sessionId);
9237
+ }
9238
+ } catch {}
9239
+ return downstream;
9240
+ });
9241
+ } catch {
9242
+ console.warn("[serenity-hooks] ✗ weixin 输出闸门未装配:tools/post-execute 不可用");
9243
+ return;
9244
+ }
9245
+ try {
9246
+ ctx.on("agent/turn-stopping", (payload) => {
9247
+ const agent = payload?.agent;
9248
+ if (!agent) return;
9249
+ const sessionId = sessionIdOf$1(agent);
9250
+ if (!sessionId) return;
9251
+ const session = manualSessions.get(sessionId);
9252
+ if (!session) return;
9253
+ const sent = sentThisTurn.has(sessionId);
9254
+ sentThisTurn.delete(sessionId);
9255
+ if (sent) return;
9256
+ const count = (rebukeCounts.get(sessionId) ?? 0) + 1;
9257
+ if (count > 2) {
9258
+ rebukeCounts.delete(sessionId);
9259
+ console.warn(`[serenity-hooks] ✗ weixin 输出闸门:连续 2 次打回后仍未发送(session=${sessionId}, role=${session.role})——本轮用户将收不到任何回复`);
9260
+ return;
9261
+ }
9262
+ rebukeCounts.set(sessionId, count);
9263
+ try {
9264
+ agent.steer(createUserMessage({
9265
+ content: [{
9266
+ type: "text",
9267
+ text: buildManualOutputRebuke(session)
9268
+ }],
9269
+ source: PLUGIN_SOURCE
9270
+ }));
9271
+ console.log(`[serenity-hooks] weixin 输出闸门:打回 ${count}/2(session=${sessionId}, turn=${payload.turn ?? "?"})`);
9272
+ } catch (error) {
9273
+ console.warn(`[serenity-hooks] ✗ weixin 输出闸门 steer 失败: ${String(error?.message ?? error)}`);
9274
+ }
9275
+ });
9276
+ } catch {
9277
+ console.warn("[serenity-hooks] ✗ weixin 输出闸门未装配:agent/turn-stopping 不可用");
9278
+ }
9279
+ }
9280
+ //#endregion
9178
9281
  //#region src/weixin-hook.ts
9179
9282
  /**
9180
9283
  * weixin-hook.ts — 微信桥消息记录 hook(F4c-3 扩展,v1.27.13 用户需求)
@@ -9342,7 +9445,7 @@ var weixin_bridge_exports = /* @__PURE__ */ __exportAll({
9342
9445
  stopCccBridge: () => stopCccBridge,
9343
9446
  syncCccBridge: () => syncCccBridge,
9344
9447
  weixinBridgeStatus: () => weixinBridgeStatus,
9345
- weixinManualOutputLine: () => weixinManualOutputLine
9448
+ weixinManualOutputMarker: () => weixinManualOutputMarker
9346
9449
  });
9347
9450
  const bridges = /* @__PURE__ */ new Map();
9348
9451
  /** 轮询间隔(getupdates 失败后重试延迟;成功 = 立即续轮询) */
@@ -9422,28 +9525,26 @@ async function runAccountLoop(ctx, root, accountId, cred, loop) {
9422
9525
  }
9423
9526
  }
9424
9527
  /**
9425
- * 手动输出模式的**输出纪律块**(v1.30.10,`weixin.autoReplyWithLastMessage: false` 时注入)。
9528
+ * 手动输出模式的**机制标记**(v1.30.10 引入;v1.30.16 S142 用户"这个词不能让 ACC 定义,
9529
+ * 要让 CCC 定义"重构)。
9530
+ *
9531
+ * 边界(R↓):ACC 只提供**机制与数据**——"桥不转发最终文本"这个事实 + 三个已填好的参数值
9532
+ * (CCC 根 / 账号 / 用户 id)。**纪律措辞归 CCC**:怎么写、必须怎么做、后果是什么,全部写在
9533
+ * CCC 的角色提示词文件里(如 `.opencode/skiff/zhaocai.md` 的「输出通道」节),CCC 可随时改,
9534
+ * 且 v1.30.15 起角色提示词热重载——改完立即生效,无需 ACC 发版。
9426
9535
  *
9427
- * 为什么必须注入(R↓):关掉自动回发后,agent 若不知道"桥不会替你转发",用户就收不到任何回复
9428
- * ——机制变更必须伴随可执行的行动指引。因此这里给的是**可直接照抄的完整命令**(CCC + 账号 +
9429
- * 用户 id 全部填好),而不是抽象提示(E↑)。
9536
+ * 为什么不用 ACC 写措辞(历史教训):v1.30.10~v1.30.15 由 ACC 内嵌一段纪律文案,用户实测
9537
+ * "约束不够,LLM 不听"——且措辞迭代要动插件代码 + 发版 + 重启;把措辞放 CCC 才能快速迭代,
9538
+ * 也符合"ACC 管机制、CCC 管内容"的归属二分。
9430
9539
  *
9431
- * 参数全部显式:`--ccc`(必填,无隐式当前 CCC)、`--account`(沿用收到消息的账号,避免多账号下
9432
- * 用错 bot 身份回话)、`--user`(该用户 iLink id,免去别名解析)。
9540
+ * 标记格式(稳定,供 CCC 提示词引用):
9541
+ * ```
9542
+ * [serenity:weixin-manual-output]
9543
+ * msm("weixin-send", ["send", "--ccc", "<root>", "--account", "<account>", "--user", "<user>", "<回复>"])
9544
+ * ```
9433
9545
  */
9434
- function weixinManualOutputLine(root, accountId, userId) {
9435
- return [
9436
- "── Reply Output (manual mode) ──",
9437
- "The bridge will NOT forward your final message to the user for this CCC.",
9438
- "Send everything the user should see yourself, e.g.:",
9439
- ` msm("weixin-send", ["send", "--ccc", "${root}", "--account", "${accountId}", "--user", "${userId}", "<your reply>"])`,
9440
- "Rules:",
9441
- " 1. Send your reply before the turn ends — if you send nothing, the user sees nothing (no fallback).",
9442
- " 2. You may send several messages (progress / final answer); split only when it helps the reader.",
9443
- " 3. Plain text only — WeChat renders no markdown.",
9444
- " 4. Each send is recorded automatically (source=proactive); do not repeat the same content.",
9445
- "── ──"
9446
- ].join("\n");
9546
+ function weixinManualOutputMarker(root, accountId, userId) {
9547
+ return ["[serenity:weixin-manual-output]", `msm("weixin-send", ["send", "--ccc", "${root}", "--account", "${accountId}", "--user", "${userId}", "<回复>"])`].join("\n");
9447
9548
  }
9448
9549
  /**
9449
9550
  * 处理单条微信消息:路由 → skiff 会话(固定 id 创建/延续)→ 提问 → 回复回写。
@@ -9562,9 +9663,17 @@ async function handleIncoming(ctx, root, accountId, cred, msg) {
9562
9663
  if (!ensureWorkspacePromptSection(ref.agent, sessionId)) parts.push(workspaceTrajectoryLine(activeWorkspace.mdPath));
9563
9664
  }
9564
9665
  const manualOutput = settings.autoReplyWithLastMessage === false;
9565
- if (manualOutput) parts.push(weixinManualOutputLine(root, accountId, fromUserId));
9566
9666
  if (text) parts.push(text);
9567
9667
  parts.push(...mediaNotes, ...degradedNotes);
9668
+ if (manualOutput) {
9669
+ noteManualOutputSession(sessionId, {
9670
+ root,
9671
+ accountId,
9672
+ userId: fromUserId,
9673
+ role: roleName
9674
+ });
9675
+ parts.push(weixinManualOutputMarker(root, accountId, fromUserId));
9676
+ } else forgetManualOutputSession(sessionId);
9568
9677
  const question = parts.join("\n");
9569
9678
  const hookRel = settings.hook;
9570
9679
  if (hookRel) invokeWeixinHook(root, hookRel, buildIncomingHookEvent({
@@ -10138,6 +10247,9 @@ function cleanupSessionState(sessionId) {
10138
10247
  try {
10139
10248
  clearActiveSessionInfo(sessionId);
10140
10249
  } catch {}
10250
+ try {
10251
+ forgetManualOutputSession(sessionId);
10252
+ } catch {}
10141
10253
  }
10142
10254
  /**
10143
10255
  * 装配生命周期订阅与资源拆卸。
@@ -10449,6 +10561,7 @@ function apply(ctx, config) {
10449
10561
  registerAutopilot(ctx);
10450
10562
  registerWeixinBridge(ctx);
10451
10563
  registerWeixinSendApi(ctx);
10564
+ registerWeixinOutputGuard(ctx);
10452
10565
  registerLifecycle(ctx);
10453
10566
  if (config.webFetch?.enabled !== false) registerWebFetchProvider(ctx);
10454
10567
  }
package/lib/msm-ops.d.ts CHANGED
@@ -19,7 +19,7 @@ export declare const MSM_ACTIONS: readonly MsmAction[];
19
19
  export declare const ACC_CATALOG = "\u2550\u2550\u2550 ACC Usage Catalog \u2550\u2550\u2550\nDirectory of ACC capabilities \u2014 each area lists where to go for details (guides live with their feature; this catalog only points).\n\n \u2460 \u4F1A\u8BDD\u4E0E\u8F68\u8FF9 \u2192 logbook\uFF08list/show/create/use/close/health/qa/archive/summary/hook-develop-guide + rebuild \u8D85\u9650\u91CD\u5EFA\uFF09\n session_rebuild \u5DF2\u5E76\u5165 logbook rebuild\uFF08\u8D85\u9650\u91CD\u5EFA\uFF1B\u9608\u503C K \u89C1 container_admin ccc-config / \u8BBE\u7F6E\u9762\u677F\u91CD\u5EFA\u9608\u503CK\uFF09\n \u2461 \u8BA4\u77E5\u8D28\u91CF\u6846\u67B6 \u2192 praxis\uFF08eap/neat/cce \u4E09\u5408\u4E00\u2014\u2014\u53EF\u5B9E\u8DF5\u7406\u8BBA\u6CE8\u5165\uFF0Csection \u6E10\u8FDB\u62AB\u9732\uFF0C\u65E0\u53C2\u5373\u76EE\u5F55\uFF09\n \u2462 \u5DE5\u5177\u4E0E\u6267\u884C \u2192 container_fs\uFF08\u5BB9\u5668\u6587\u4EF6\u7CFB\u7EDF 15 \u5B50\u547D\u4EE4\uFF09/ container_git\uFF08git\uFF09/ dashboard\uFF08health \u542B\u6CE8\u518C\u8868\u68C0\u67E5/time/wait\uFF09\n acc_msm \u5DF2\u5E76\u5165 msm\uFF08\u6267\u884C+\u53D1\u73B0\uFF09\u4E0E container_admin\uFF08\u7BA1\u7406\uFF09\u2014\u2014msm(\"<name>\", [\"<args>\"])\uFF1Binspect=true \u67E5\u7528\u6CD5\n handyman\uFF08\u6742\u5DE5 agent \u7F16\u6392\u2014\u2014guide \u5B50\u547D\u4EE4\uFF1Ahandyman guide\uFF09\n \u2463 \u89D2\u8272\u4E0E\u5BF9\u5916\u9762 \u2192 container_admin role\uFF08Skiff \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF1Aguide/validate/apply/list\u2014\u2014skiff_admin \u5DF2\u5E76\u5165\uFF09\n ACP\uFF08\u7A0B\u5E8F\u5316 JSON-RPC 3100\uFF09/ Skiff \u95EE\u7B54\u9875\uFF08\u516C\u7F51 ask\uFF09\u2014\u2014\u9762\u677F\u300C\u5916\u90E8\u80FD\u529B\u300D\u7EC4\n \u2464 \u81EA\u4E3B\u4E0E\u63A5\u5165 \u2192 autopilot-trajectory\uFF08Autopilot \u4E00\u7AD9\u5F0F\uFF1Aall/init/random/diag/doc/check/status/guide\uFF09\n weixin \u5FAE\u4FE1\u6865\uFF08\u914D\u7F6E/\u626B\u7801/\u8DEF\u7531/\u6D88\u606F hook\u2014\u2014CCC \u4FA7 weixin-doctor MSM\uFF1Amsm(\"weixin-doctor\", [\"guide\"])\uFF09\n \u2465 CCC \u914D\u7F6E\u603B\u89C8 \u2192 container_admin msm ccc-config\uFF088 \u6BB5\uFF1Ahandyman/sessionKeeper/localstore/hooks/safeMode/skiff/autopilotTrajectory/weixin\uFF09\n \u2466 \u6CE8\u518C\u8868\u4E0E\u5B89\u5168 \u2192 mech-registry.json \u7531 container_admin msm register/deregister \u7BA1\u7406\uFF08\u5199\u4FDD\u62A4\u2014\u2014\u4E0D\u53EF\u76F4\u63A5\u7F16\u8F91\uFF09\n dashboard health \u8F93\u51FA registry \u5B8C\u6574\u6027\u68C0\u67E5\uFF1B\u635F\u574F\u6062\u590D\u6307\u5F15\u89C1 health registry.issues\n\n\u6BCF\u533A\uFF1A\u4E00\u53E5\u8BDD\u5B9A\u4F4D + \u8BE6\u7EC6\u5165\u53E3\uFF08\u5DE5\u5177\u540D / guide \u5B50\u547D\u4EE4\uFF09\u3002\u8BE6\u60C5\u6C38\u8FDC\u4EE5\u5BF9\u5E94\u5DE5\u5177\u7684 guide/ccc-config \u4E3A\u5355\u4E00\u771F\u76F8\u6E90\u3002\n";
20
20
  export declare const MSM_GUIDE = "MSM Development Manual (Mech & Semi-Mech framework)\n\n## What it is\nMSM = the executable-unit layer. Mech is pure TS with zero LLM reasoning; Semi-Mech is a TS framework + LLM decision points.\nMSM is ACC's deterministic executable-unit layer \u2014 all shell/exec operations go through MSM and cannot be bypassed.\n\n## Registering a new MSM (container_admin msm register)\n1. Write the script under <skill>/scripts/ (runnable via tsx; must include a main() CLI guard with an import.meta.url check)\n2. container_admin msm register --domain msm --action register <name> --skill <s> --path <script path relative to root> --category <mech|semi-mech> --description <desc>\n3. Auto-writes mech-registry.json (preserving the original format) + git commit (commits only the registry file)\n4. Validation: path must be inside root, script must exist, name must be globally unique\n\n## flag schema (v1)\nflags is a new-style object array for parameter validation and path-escape guarding:\n [{\"name\":\"output\",\"type\":\"string\",\"description\":\"output path\"},\n {\"name\":\"target\",\"type\":\"path\",\"description\":\"operation target (type:path enables in-root validation + symlink defense)\"},\n {\"name\":\"force\",\"type\":\"boolean\",\"description\":\"force mode\",\"default\":false}]\n- {name, type} format \u2014 new style, type:\"path\" enables the path-escape guard\n- {flag, description} format \u2014 old style, CLI flag description string\n- On registration, flags are passed via container_admin msm register --flags '<json>'\n\n## Script conventions\n- Top-of-file documentation: purpose / usage / exit codes\n- Exit codes: 0 success / 1 user / 2 system / 3 operator (per ACC protocol classification)\n- main() CLI guard (DC-M2): the script must start with\n if (import.meta.url === `file://${process.argv[1]}`) { main() }\n or an equivalent isMain / require.main === check \u2014 so vitest imports never trigger top-level code\n- A paired .test.ts or .spec.ts (DC-M1, vitest)\n- Business subprocess environment: SERENITY_ROOT / SERENITY_CCC / SERENITY_VERSION injected\n\n## Interaction & confirmation conventions (no blocking confirmation)\n- MSMs run in **user-less subprocesses** (spawn/execFile, 600s timeout) \u2014 **never** use\n readline / prompt / process.stdin etc. to block waiting for user input (it would hang until killed by the timeout)\n- When a second confirmation is needed: **directly return the confirmation request** (list the operation to perform and its impact + how to retry with the confirmation flag),\n returning a non-zero exit code (or an explicit hint)\n- After the agent confirms, **re-invoke the MSM with the confirmation flag** (e.g. --confirm / --yes / --force) to retry\n- Standard mode (two-phase):\n 1. First call: destructive/confirmation-requiring operation detected and no confirmation flag present \u2192 output the confirmation request (list operation/impact/rollback),\n exit non-zero (e.g. 1 user), **perform no changes**\n 2. After the agent evaluates, re-invoke: with the confirmation flag \u2192 execute and output the result\n- Applies to: delete/overwrite/push/batch operations and other irreversible or wide-impact operations\n- Anti-example (forbidden): readline waiting for user input, process.stdin.on('data') blocking waits\n (MSMs have no stdin interaction channel \u2014 they hang until the 600s timeout)\n\n## Quality checks (container_admin msm check, DC-M1~M4)\nDC-M1 has .test.ts/.spec.ts; DC-M2 has a main() guard (function main( / isMain / require.main === / import.meta.url);\nDC-M3 bidirectional: scripts unregistered + registry references missing scripts; DC-M4 path-type flags marked type:\"path\"\n\n## Self-description (protocol flags, first argument only)\nmsm(\"<name>\", [\"--list\"]) \u2014 list all MSMs\nmsm(\"<name>\", [\"--schema\", \"<n>\"]) \u2014 view a specific MSM's parameter schema\nmsm(\"<name>\", [\"--format=json\"]) \u2014 JSON output mode (remaining args passed through losslessly)\n";
21
21
  /** CCC 配置参考(对齐 osp ccc-config action) */
22
- export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\ntrajectory-assistant\uFF08TRAJECTORY-ASSISTANT \u00B7 CHECKPOINT\uFF09\u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccontainer_git commit \u4F1A\u68C0\u67E5\u62D2\u7EDD\u3002\n\n Config:\n { \"localstore\": { \"gitTrack\": \"allow\" } }\n\n\u2500\u2500 4. hooks.autoRestoreSession \u2500\u2500\n\u4F1A\u8BDD\u81EA\u52A8\u6062\u590D\uFF08\u9ED8\u8BA4 true\uFF1B\u53D7 events \u95E8\u63A7\u2014\u2014\u4EC5\u6839\u4F1A\u8BDD + \u5DF2\u6709\u5BF9\u8BDD\u5386\u53F2\u624D\u6062\u590D\uFF09\u3002\n\n Config:\n { \"hooks\": { \"autoRestoreSession\": false } }\n\n\u2500\u2500 5. safeMode / blacklist \u2500\u2500\nsafe-mode \u7531 WebUI \u5F00\u5173\u63A7\u5236\uFF08\u5199 .serenity-safe-on \u6807\u8BB0\uFF09\uFF1B\u9ED1\u540D\u5355\u8DEF\u5F84\u53D7 guards seam \u62E6\u622A\u3002\n\n Config:\n { \"safeMode\": { \"blacklist\": [\".secrets/\"] } }\n\n\u2500\u2500 6. skiff.roles\uFF08F4 \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF09\u2500\u2500\nSkiff \u89D2\u8272 = \u5168\u77E5\u5168\u80FD trajectory \u7684\u4EFB\u610F\u5B50\u96C6\uFF08CCC \u5B9A\u4E49\uFF09\u3002\u6BCF\u89D2\u8272\u72EC\u7ACB\u6A21\u578B + \u53CC\u767D\u540D\u5355\n\uFF08MSM \u767D\u540D\u5355 msms[] \u4E0E\u975E MSM \u5DE5\u5177\u767D\u540D\u5355 tools[]\uFF0C\u767D\u540D\u5355\u5916\u5168\u9690\u85CF\uFF1Bskill \u52A0\u8F7D\u6052\u53EF\u7528\uFF09\u3002\ntrajectory \u7EAA\u5F8B\u5B50\u96C6\uFF08session/keeper/rebuild \u53C2\u4E0E\u9879\uFF09\u9ED8\u8BA4\u5168\u5173 = \u5B8C\u5168\u72EC\u7ACB\u3002\nsystemPrompt \u5185\u8054 \u6216 systemPromptFile\uFF08.md \u5F15\u7528\uFF0C\u63A8\u8350\uFF09\u2014\u2014\u89D2\u8272\u4F1A\u8BDD\u7684\u5B8C\u6574\u4EBA\u683C/\u8FB9\u754C\u63D0\u793A\u8BCD\u3002\nvalidate \u6821\u9A8C / apply \u751F\u6548 / list \u67E5\u770B\uFF1Acontainer_admin role <guide|validate|apply|list>\uFF08skiff_admin \u5DF2\u5E76\u5165\uFF09\u3002\n\n Config:\n { \"skiff\": { \"roles\": {\n \"qa\": {\n \"model\": \"provider/model\",\n \"msms\": [\"web-search\", \"vlm-describe\"],\n \"tools\": [\"read\", \"grep\", \"glob\"],\n \"systemPromptFile\": \".opencode/skiff/qa.md\"\n } } } }\n\n\u2500\u2500 7. autopilotTrajectory\uFF08\u81EA\u52A8\u5DE1\u822A\u8F68\u8FF9\uFF09\u2500\u2500\nCCC \u5B9A\u4E49\u7684\u4E00\u6761\u81EA\u4E3B trajectory\u2014\u2014\u65F6\u949F\u5230\u70B9\u81EA\u52A8\u5524\u8D77\uFF08\u524D\u53F0\u6CE8\u5165\uFF0C\u7528\u6237\u53EF\u89C1\u53EF\u4ECB\u5165\uFF09\u3002\n\u672A\u914D\u7F6E\u6216 enabled=false \u2192 \u5B8C\u5168\u4E0D\u542F\u52A8\uFF08\u96F6\u8D44\u6E90\u5360\u7528\uFF09\u3002\u591A CCC \u72EC\u7ACB\uFF1A\u6BCF CCC \u81EA\u5DF1\u7684\u914D\u7F6E\u3002\n\u5524\u8D77\u6D88\u606F\u56DB\u6BB5\u5F0F\uFF1A\u8F68\u8FF9\u7126\u70B9 topPrompt\uFF08\u6700\u5148\u6CE8\u5165\uFF0C\u7A33\u5B9A\u951A\uFF09\u2192 \u8EAB\u4EFD\u951A\u5B9A \u2192 \u5148\u9A8C\u504F\u89C1\n\uFF08CCC \u6839\u811A\u672C biasProvider \u8F93\u51FA\uFF09\u2192 \u4EFB\u52A1\u3002\u76EE\u6807\u4F1A\u8BDD session \u5FC5\u586B\uFF08\u76EE\u5F55\u987B\u5E26 --auto \u540E\u7F00\uFF09\u3002\n\u8BCA\u65AD/\u72B6\u6001/\u7ACB\u5373\u5524\u8D77\uFF1Aautopilot-trajectory <all|check|status|diag>\uFF08msm(\"autopilot-trajectory\", [\"all\"])\uFF09\u3002\n\n Config:\n { \"autopilotTrajectory\": {\n \"enabled\": true,\n \"intervalHours\": 2,\n \"session\": \"S151\",\n \"biasProvider\": \"autopilot-bias.ts\",\n \"topPrompt\": \"\u672C\u8F68\u8FF9\u6838\u5FC3\u76EE\u6807/\u7EAA\u5F8B/\u8D28\u91CF\u8981\u6C42\uFF08CCC \u81EA\u586B\uFF0C\u9632\u6F02\u79FB\uFF09\",\n \"avoidWakeHours\": { \"start\": 8, \"end\": 18 }\n } }\n\n\u2500\u2500 8. weixin\uFF08\u5FAE\u4FE1\u6865 F4c-3\uFF0C\u542B\u6D88\u606F\u8BB0\u5F55 hook\uFF09\u2500\u2500\nCCC \u7EA7\u5FAE\u4FE1\u4E2A\u4EBA\u53F7\u63A5\u5165\uFF08iLink \u534F\u8BAE\uFF09\uFF1Adsh \u4E00\u8FDB\u7A0B\u591A CCC\uFF0C\u6BCF CCC \u72EC\u7ACB\u5BF9\u63A5\u5FAE\u4FE1\u6865\u3002\n\u8D26\u53F7/\u8DEF\u7531/\u5F00\u5173\u5728\u6B64\u6587\u4EF6\uFF1B**bot_token \u51ED\u636E\u5728 CCC localstore credential scope**\n\uFF08\u626B\u7801\u7ED1\u5B9A\u540E\u81EA\u52A8\u5199\u5165\uFF0C\u6C38\u4E0D\u8FDB git \u660E\u6587\u9762\uFF09\u3002\n\u8DEF\u7531 user \u2192 role\uFF1Aexact \u4F18\u5148\uFF0C* \u901A\u914D\u515C\u5E95\uFF1Brole \u5FC5\u987B \u2208 \u8BE5 CCC skiff.roles\u3002\n\u9762\u677F\uFF08WebUI \u8BBE\u7F6E \u2192 \u5FAE\u4FE1\u6865\uFF09\u53EF\u626B\u7801\u7ED1\u5B9A/\u79FB\u9664\u8D26\u53F7/\u7F16\u8F91\u8DEF\u7531\uFF1Bmsm(\"weixin-doctor\", [\"status\"|\"diag\"|\"verify\"|\"guide\"]) \u6392\u67E5 + \u5927\u800C\u5168\u6307\u5357\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n \u25B8 \u4E3B\u52A8\u53D1\u9001\uFF08v1.30.9\uFF09\uFF1ACCC \u7528 msm(\"weixin-send\", [\"send\", \"--ccc\", \"<CCC>\", \"--user\", \"<\u522B\u540D|id>\", \"<\u6587\u672C>\"])\n \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\u7ECF dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3\uFF08**\u53EA\u7ED1 127.0.0.1**\uFF0Cplugin \u5168\u5C40\u914D\u7F6E ~/.dsh/serenity-hooks.json\n \u7684 weixinApi.port\uFF0C\u9ED8\u8BA4 3082\uFF1B\u516C\u7F51\u4E0D\u53EF\u8FBE\u6545\u65E0\u5BC6\u94A5\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002send-file \u4ECD\u7531 MSM \u76F4\u8FDE iLink \u4E0A\u4F20\u5E76\u81EA\u884C\u8865\u8BB0\u3002\n\n \u25B8 weixin.autoReplyWithLastMessage\uFF08v1.30.10\uFF0C\u5173\u95ED\u6865\u7684\u81EA\u52A8\u8F93\u51FA\uFF09\uFF1A\n \u7F3A\u7701 true = \u73B0\u72B6\uFF1A\u6865\u5728 agent \u4E00\u8F6E\u7ED3\u675F\u540E\u628A**\u6700\u7EC8 assistant \u6587\u672C**\u81EA\u52A8\u56DE\u53D1\u7528\u6237\uFF08source=\"reply\"\uFF09\u3002\n \u663E\u5F0F false = \u5173\u95ED\uFF1A\u6865**\u4E0D\u518D\u8F6C\u53D1\u6700\u7EC8\u6587\u672C**\uFF0C\u8F93\u51FA\u6743\u4EA4\u7ED9 agent\u2014\u2014\u6BCF\u8F6E question \u524D\u7F00\u6CE8\u5165\n \u300CReply Output (manual mode)\u300D\u7EAA\u5F8B\u5757\uFF08\u542B\u53EF\u76F4\u63A5\u7167\u6284\u7684 weixin-send \u547D\u4EE4\uFF0CCCC \u6839/\u8D26\u53F7/\u7528\u6237 id\n \u5DF2\u586B\u597D\uFF09\uFF0Cagent \u81EA\u5DF1\u51B3\u5B9A\u53D1\u4EC0\u4E48\u3001\u53D1\u51E0\u6761\uFF08\u5168\u90E8\u8BB0 source=\"proactive\"\uFF09\u3002\n \u8BED\u4E49\u8FB9\u754C\uFF08\u7528\u6237\u62CD\u677F\uFF09\uFF1A\u53EA\u5173\u6700\u7EC8\u6587\u672C\uFF1B\u7CFB\u7EDF\u7C7B\u6D88\u606F\uFF08\u65B0\u5BF9\u8BDD\u901A\u77E5 / \u8BED\u97F3\u65E0\u6CD5\u89E3\u6790\u63D0\u793A\uFF09\u4FDD\u7559\uFF1B\n **agent \u4E00\u8F6E\u672A\u8F93\u51FA \u2192 \u9759\u9ED8\u4E0D\u515C\u5E95**\uFF08\u5426\u5219\u5F00\u5173\u5931\u53BB\u610F\u4E49\uFF09\uFF1Btyping \u6307\u793A\u4E0E incoming hook \u4E0D\u53D7\u5F71\u54CD\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"autoReplyWithLastMessage\": true,\n \"accounts\": [{ \"accountId\": \"wechat-1\", \"name\": \"\u5BB6\u5EAD\u52A9\u624B\", \"enabled\": true }],\n \"routes\": [{ \"user\": \"*\", \"role\": \"zhaocai\" }]\n } }\n\n \u25B8 weixin.hook\uFF08\u6D88\u606F\u8BB0\u5F55 hook\uFF0Cv1.27.13\uFF09\uFF1A\n \u5FAE\u4FE1\u6865\u6BCF\u6536/\u53D1\u4E00\u6761\u6D88\u606F\u89E6\u53D1\u4E00\u6B21 CCC \u81EA\u5199\u811A\u672C\uFF0C\u7531 CCC \u81EA\u884C\u6301\u4E45\u5316\u4FDD\u5B58\uFF08\u5B58\u54EA/\u5B58\u6210\u4EC0\u4E48\n /\u662F\u5426\u5165\u5E93\u5168\u5F52 CCC\u2014\u2014ACC \u4E0D\u7ED1\u5B9A\u5B58\u50A8\uFF09\u3002\u672A\u914D\u7F6E hook \u2192 \u96F6\u53D8\u5316\uFF08\u4E0D\u89E6\u53D1\uFF09\u3002\n\n \u89E6\u53D1\uFF08\u53CC\u5411\uFF09\uFF1A\n incoming = \u7528\u6237 \u2192 bot\uFF1A\u8DEF\u7531\u547D\u4E2D\u540E\u3001\u5A92\u4F53\u843D\u76D8\u540E\u89E6\u53D1\uFF08\u6587\u672C\u542B\u8BED\u97F3\u8F6C\u5199\uFF1B\u5A92\u4F53\u5E26\u843D\u76D8 relPath\uFF09\n outgoing = bot \u2192 \u7528\u6237\uFF1A\u53D1\u9001\u6210\u529F\u540E\u89E6\u53D1\uFF08reply = \u7528\u6237\u5B9E\u9645\u6536\u5230\u7684\u7EAF\u6587\u672C\uFF0C\u5DF2\u5265\u79BB think\uFF09\n \u00B7 source=\"reply\"\uFF08\u7F3A\u7701\uFF09= \u56DE\u590D\u7528\u6237\u6D88\u606F\uFF1Bsource=\"proactive\"\uFF08v1.30.9\uFF09= bot \u4E3B\u52A8\u53D1\u8D77\n \uFF08CCC \u7ECF weixin-send MSM \u2192 dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3 \u2192 sendProactiveText\uFF09\n \u00B7 file\uFF08\u4EC5 send-file \u8865\u8BB0\uFF09\uFF1A{ \"name\": \"\u62A5\u544A.pdf\", \"size\": 12345, \"caption\": \"\u53EF\u9009\" }\n\n \u811A\u672C\u7EA6\u5B9A\uFF08\u4E8B\u4EF6 JSON \u5355\u884C\u7ECF stdin \u4F20\u5165\uFF1Bbun \u4F18\u5148 node \u515C\u5E95\uFF09\uFF1A\n #!/usr/bin/env bun \u6216 node script.js \u2014\u2014 \u8BFB process.stdin \u6574\u6BB5 JSON.parse\n const ev = JSON.parse(await new Response(process.stdin).text())\n ev.event === 'incoming' | 'outgoing'\n\n \u4E8B\u4EF6 schema\uFF08**\u4E0D\u542B\u4EFB\u4F55\u4F1A\u8BDD\u51ED\u636E**\u2014\u2014context_token/bot_token/token/aes_key \u5747\u4E0D\u51FA\u73B0\uFF09\uFF1A\n { \"event\": \"incoming\", \"ts\": 1788359941488, \"cccRoot\": \"/path/ccc\",\n \"accountId\": \"wechat-1\", \"userId\": \"u1@im.wechat\",\n \"sessionId\": \"skiff-weixin-xxx\", \"role\": \"zhaocai\",\n \"message\": { \"text\": \"\u4F60\u597D\", \"media\": [{ \"kind\": \"image\", \"relPath\": \"_tmp/weixin-inbound/<hash>/img_x.jpg\" }] } }\n { \"event\": \"outgoing\", \"ts\": ..., \"cccRoot\": ..., \"accountId\": ...,\n \"userId\": ..., \"sessionId\": ..., \"role\": ...,\n \"reply\": \"\u5DF2\u8BB0\u5F55\uFF08\u7EAF\u6587\u672C\uFF09\", \"source\": \"reply\" | \"proactive\" }\n\n \u793A\u4F8B\u811A\u672C\uFF08\u8FFD\u52A0\u5230\u6309\u65E5\u6587\u4EF6\u2014\u2014\u6301\u4E45\u5316\u5F52 CCC \u81EA\u9009\uFF1A\u6587\u4EF6/DB/\u8FDC\u7AEF\u5747\u53EF\uFF09\uFF1A\n const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';\n fs.appendFileSync(p, JSON.stringify(ev) + '\\n');\n\n \u6267\u884C\u8BED\u4E49\uFF08\u65C1\u8DEF\u5BB9\u5FCD\uFF09\uFF1A\u5F02\u6B65 fire-and-forget + 15s \u8D85\u65F6 kill + \u5931\u8D25\u4EC5\u65E5\u5FD7\u2014\u2014\n \u5FAE\u4FE1\u6865\u6D88\u606F\u5904\u7406/\u56DE\u590D\u4E0D\u53D7 hook \u5F71\u54CD\uFF1B\u811A\u672C\u987B\u5728 CCC \u6839\u5185\uFF08\u8DEF\u5F84\u9003\u9038\u62D2\u7EDD\uFF09\u3002\n \u5A92\u4F53 relPath \u6307\u5411 _tmp/weixin-inbound/\uFF08\u4E34\u65F6\u76EE\u5F55\uFF09\u2014\u2014\u9700\u6301\u4E45\u4FDD\u5B58\u5A92\u4F53\u6587\u4EF6\u8BF7\u81EA\u884C copy\u3002\n";
22
+ export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\ntrajectory-assistant\uFF08TRAJECTORY-ASSISTANT \u00B7 CHECKPOINT\uFF09\u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccontainer_git commit \u4F1A\u68C0\u67E5\u62D2\u7EDD\u3002\n\n Config:\n { \"localstore\": { \"gitTrack\": \"allow\" } }\n\n\u2500\u2500 4. hooks.autoRestoreSession \u2500\u2500\n\u4F1A\u8BDD\u81EA\u52A8\u6062\u590D\uFF08\u9ED8\u8BA4 true\uFF1B\u53D7 events \u95E8\u63A7\u2014\u2014\u4EC5\u6839\u4F1A\u8BDD + \u5DF2\u6709\u5BF9\u8BDD\u5386\u53F2\u624D\u6062\u590D\uFF09\u3002\n\n Config:\n { \"hooks\": { \"autoRestoreSession\": false } }\n\n\u2500\u2500 5. safeMode / blacklist \u2500\u2500\nsafe-mode \u7531 WebUI \u5F00\u5173\u63A7\u5236\uFF08\u5199 .serenity-safe-on \u6807\u8BB0\uFF09\uFF1B\u9ED1\u540D\u5355\u8DEF\u5F84\u53D7 guards seam \u62E6\u622A\u3002\n\n Config:\n { \"safeMode\": { \"blacklist\": [\".secrets/\"] } }\n\n\u2500\u2500 6. skiff.roles\uFF08F4 \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF09\u2500\u2500\nSkiff \u89D2\u8272 = \u5168\u77E5\u5168\u80FD trajectory \u7684\u4EFB\u610F\u5B50\u96C6\uFF08CCC \u5B9A\u4E49\uFF09\u3002\u6BCF\u89D2\u8272\u72EC\u7ACB\u6A21\u578B + \u53CC\u767D\u540D\u5355\n\uFF08MSM \u767D\u540D\u5355 msms[] \u4E0E\u975E MSM \u5DE5\u5177\u767D\u540D\u5355 tools[]\uFF0C\u767D\u540D\u5355\u5916\u5168\u9690\u85CF\uFF1Bskill \u52A0\u8F7D\u6052\u53EF\u7528\uFF09\u3002\ntrajectory \u7EAA\u5F8B\u5B50\u96C6\uFF08session/keeper/rebuild \u53C2\u4E0E\u9879\uFF09\u9ED8\u8BA4\u5168\u5173 = \u5B8C\u5168\u72EC\u7ACB\u3002\nsystemPrompt \u5185\u8054 \u6216 systemPromptFile\uFF08.md \u5F15\u7528\uFF0C\u63A8\u8350\uFF09\u2014\u2014\u89D2\u8272\u4F1A\u8BDD\u7684\u5B8C\u6574\u4EBA\u683C/\u8FB9\u754C\u63D0\u793A\u8BCD\u3002\nvalidate \u6821\u9A8C / apply \u751F\u6548 / list \u67E5\u770B\uFF1Acontainer_admin role <guide|validate|apply|list>\uFF08skiff_admin \u5DF2\u5E76\u5165\uFF09\u3002\n\n Config:\n { \"skiff\": { \"roles\": {\n \"qa\": {\n \"model\": \"provider/model\",\n \"msms\": [\"web-search\", \"vlm-describe\"],\n \"tools\": [\"read\", \"grep\", \"glob\"],\n \"systemPromptFile\": \".opencode/skiff/qa.md\"\n } } } }\n\n\u2500\u2500 7. autopilotTrajectory\uFF08\u81EA\u52A8\u5DE1\u822A\u8F68\u8FF9\uFF09\u2500\u2500\nCCC \u5B9A\u4E49\u7684\u4E00\u6761\u81EA\u4E3B trajectory\u2014\u2014\u65F6\u949F\u5230\u70B9\u81EA\u52A8\u5524\u8D77\uFF08\u524D\u53F0\u6CE8\u5165\uFF0C\u7528\u6237\u53EF\u89C1\u53EF\u4ECB\u5165\uFF09\u3002\n\u672A\u914D\u7F6E\u6216 enabled=false \u2192 \u5B8C\u5168\u4E0D\u542F\u52A8\uFF08\u96F6\u8D44\u6E90\u5360\u7528\uFF09\u3002\u591A CCC \u72EC\u7ACB\uFF1A\u6BCF CCC \u81EA\u5DF1\u7684\u914D\u7F6E\u3002\n\u5524\u8D77\u6D88\u606F\u56DB\u6BB5\u5F0F\uFF1A\u8F68\u8FF9\u7126\u70B9 topPrompt\uFF08\u6700\u5148\u6CE8\u5165\uFF0C\u7A33\u5B9A\u951A\uFF09\u2192 \u8EAB\u4EFD\u951A\u5B9A \u2192 \u5148\u9A8C\u504F\u89C1\n\uFF08CCC \u6839\u811A\u672C biasProvider \u8F93\u51FA\uFF09\u2192 \u4EFB\u52A1\u3002\u76EE\u6807\u4F1A\u8BDD session \u5FC5\u586B\uFF08\u76EE\u5F55\u987B\u5E26 --auto \u540E\u7F00\uFF09\u3002\n\u8BCA\u65AD/\u72B6\u6001/\u7ACB\u5373\u5524\u8D77\uFF1Aautopilot-trajectory <all|check|status|diag>\uFF08msm(\"autopilot-trajectory\", [\"all\"])\uFF09\u3002\n\n Config:\n { \"autopilotTrajectory\": {\n \"enabled\": true,\n \"intervalHours\": 2,\n \"session\": \"S151\",\n \"biasProvider\": \"autopilot-bias.ts\",\n \"topPrompt\": \"\u672C\u8F68\u8FF9\u6838\u5FC3\u76EE\u6807/\u7EAA\u5F8B/\u8D28\u91CF\u8981\u6C42\uFF08CCC \u81EA\u586B\uFF0C\u9632\u6F02\u79FB\uFF09\",\n \"avoidWakeHours\": { \"start\": 8, \"end\": 18 }\n } }\n\n\u2500\u2500 8. weixin\uFF08\u5FAE\u4FE1\u6865 F4c-3\uFF0C\u542B\u6D88\u606F\u8BB0\u5F55 hook\uFF09\u2500\u2500\nCCC \u7EA7\u5FAE\u4FE1\u4E2A\u4EBA\u53F7\u63A5\u5165\uFF08iLink \u534F\u8BAE\uFF09\uFF1Adsh \u4E00\u8FDB\u7A0B\u591A CCC\uFF0C\u6BCF CCC \u72EC\u7ACB\u5BF9\u63A5\u5FAE\u4FE1\u6865\u3002\n\u8D26\u53F7/\u8DEF\u7531/\u5F00\u5173\u5728\u6B64\u6587\u4EF6\uFF1B**bot_token \u51ED\u636E\u5728 CCC localstore credential scope**\n\uFF08\u626B\u7801\u7ED1\u5B9A\u540E\u81EA\u52A8\u5199\u5165\uFF0C\u6C38\u4E0D\u8FDB git \u660E\u6587\u9762\uFF09\u3002\n\u8DEF\u7531 user \u2192 role\uFF1Aexact \u4F18\u5148\uFF0C* \u901A\u914D\u515C\u5E95\uFF1Brole \u5FC5\u987B \u2208 \u8BE5 CCC skiff.roles\u3002\n\u9762\u677F\uFF08WebUI \u8BBE\u7F6E \u2192 \u5FAE\u4FE1\u6865\uFF09\u53EF\u626B\u7801\u7ED1\u5B9A/\u79FB\u9664\u8D26\u53F7/\u7F16\u8F91\u8DEF\u7531\uFF1Bmsm(\"weixin-doctor\", [\"status\"|\"diag\"|\"verify\"|\"guide\"]) \u6392\u67E5 + \u5927\u800C\u5168\u6307\u5357\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n \u25B8 \u4E3B\u52A8\u53D1\u9001\uFF08v1.30.9\uFF09\uFF1ACCC \u7528 msm(\"weixin-send\", [\"send\", \"--ccc\", \"<CCC>\", \"--user\", \"<\u522B\u540D|id>\", \"<\u6587\u672C>\"])\n \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\u7ECF dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3\uFF08**\u53EA\u7ED1 127.0.0.1**\uFF0Cplugin \u5168\u5C40\u914D\u7F6E ~/.dsh/serenity-hooks.json\n \u7684 weixinApi.port\uFF0C\u9ED8\u8BA4 3082\uFF1B\u516C\u7F51\u4E0D\u53EF\u8FBE\u6545\u65E0\u5BC6\u94A5\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002send-file \u4ECD\u7531 MSM \u76F4\u8FDE iLink \u4E0A\u4F20\u5E76\u81EA\u884C\u8865\u8BB0\u3002\n\n \u25B8 weixin.autoReplyWithLastMessage\uFF08v1.30.10\uFF0C\u5173\u95ED\u6865\u7684\u81EA\u52A8\u8F93\u51FA\uFF09\uFF1A\n \u7F3A\u7701 true = \u73B0\u72B6\uFF1A\u6865\u5728 agent \u4E00\u8F6E\u7ED3\u675F\u540E\u628A**\u6700\u7EC8 assistant \u6587\u672C**\u81EA\u52A8\u56DE\u53D1\u7528\u6237\uFF08source=\"reply\"\uFF09\u3002\n \u663E\u5F0F false = \u5173\u95ED\uFF1A\u6865**\u4E0D\u518D\u8F6C\u53D1\u6700\u7EC8\u6587\u672C**\uFF0C\u8F93\u51FA\u6743\u4EA4\u7ED9 agent\u2014\u2014\u6BCF\u8F6E\u5728\u7528\u6237\u6D88\u606F**\u672B\u5C3E**\u8FFD\u52A0\n **\u673A\u5236\u6807\u8BB0** [serenity:weixin-manual-output] + \u4E00\u884C\u5DF2\u586B\u597D\u53C2\u6570\u7684 weixin-send \u547D\u4EE4\n \uFF08CCC \u6839/\u8D26\u53F7/\u7528\u6237 id\uFF09\uFF0Cagent \u81EA\u5DF1\u51B3\u5B9A\u53D1\u4EC0\u4E48\u3001\u53D1\u51E0\u6761\uFF08\u5168\u90E8\u8BB0 source=\"proactive\"\uFF09\u3002\n **ACC \u53EA\u7ED9\u673A\u5236\u4E0E\u6570\u636E\uFF0C\u7EAA\u5F8B\u63AA\u8F9E\u5F52 CCC**\uFF08v1.30.16\uFF09\uFF1A\u600E\u4E48\u5199/\u5FC5\u987B\u600E\u4E48\u505A/\u540E\u679C\u662F\u4EC0\u4E48\u5199\u5728\n CCC \u89D2\u8272\u63D0\u793A\u8BCD\u6587\u4EF6\u91CC\uFF08\u5982 .opencode/skiff/<role>.md\uFF09\uFF0C\u53EF\u70ED\u6539\uFF08v1.30.15 \u8D77\u89D2\u8272\u63D0\u793A\u8BCD\u70ED\u91CD\u8F7D\uFF09\u3002\n \u673A\u68B0\u95F8\u95E8\uFF08v1.30.16\uFF09\uFF1A\u672C\u8F6E turn \u7ED3\u675F\u4ECD\u672A\u6210\u529F\u8C03\u7528 weixin-send \u2192 agent \u88AB\u6253\u56DE\uFF08\u22642 \u6B21\uFF0C\n \u63D0\u793A = \u6807\u8BB0 + \u4E8B\u5B9E\uFF09\uFF1B\u8FBE\u4E0A\u9650\u653E\u5F03\u5E76\u54CD\u4EAE\u544A\u8B66\u3002\u5931\u8D25\u8C03\u7528\u4E0D\u7B97\u5DF2\u9001\u8FBE\u3002\n \u8BED\u4E49\u8FB9\u754C\uFF08\u7528\u6237\u62CD\u677F\uFF09\uFF1A\u53EA\u5173\u6700\u7EC8\u6587\u672C\uFF1B\u7CFB\u7EDF\u7C7B\u6D88\u606F\uFF08\u65B0\u5BF9\u8BDD\u901A\u77E5 / \u8BED\u97F3\u65E0\u6CD5\u89E3\u6790\u63D0\u793A\uFF09\u4FDD\u7559\uFF1B\n **agent \u4E00\u8F6E\u672A\u8F93\u51FA \u2192 \u9759\u9ED8\u4E0D\u515C\u5E95**\uFF08\u5426\u5219\u5F00\u5173\u5931\u53BB\u610F\u4E49\uFF09\uFF1Btyping \u6307\u793A\u4E0E incoming hook \u4E0D\u53D7\u5F71\u54CD\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"autoReplyWithLastMessage\": true,\n \"accounts\": [{ \"accountId\": \"wechat-1\", \"name\": \"\u5BB6\u5EAD\u52A9\u624B\", \"enabled\": true }],\n \"routes\": [{ \"user\": \"*\", \"role\": \"zhaocai\" }]\n } }\n\n \u25B8 weixin.hook\uFF08\u6D88\u606F\u8BB0\u5F55 hook\uFF0Cv1.27.13\uFF09\uFF1A\n \u5FAE\u4FE1\u6865\u6BCF\u6536/\u53D1\u4E00\u6761\u6D88\u606F\u89E6\u53D1\u4E00\u6B21 CCC \u81EA\u5199\u811A\u672C\uFF0C\u7531 CCC \u81EA\u884C\u6301\u4E45\u5316\u4FDD\u5B58\uFF08\u5B58\u54EA/\u5B58\u6210\u4EC0\u4E48\n /\u662F\u5426\u5165\u5E93\u5168\u5F52 CCC\u2014\u2014ACC \u4E0D\u7ED1\u5B9A\u5B58\u50A8\uFF09\u3002\u672A\u914D\u7F6E hook \u2192 \u96F6\u53D8\u5316\uFF08\u4E0D\u89E6\u53D1\uFF09\u3002\n\n \u89E6\u53D1\uFF08\u53CC\u5411\uFF09\uFF1A\n incoming = \u7528\u6237 \u2192 bot\uFF1A\u8DEF\u7531\u547D\u4E2D\u540E\u3001\u5A92\u4F53\u843D\u76D8\u540E\u89E6\u53D1\uFF08\u6587\u672C\u542B\u8BED\u97F3\u8F6C\u5199\uFF1B\u5A92\u4F53\u5E26\u843D\u76D8 relPath\uFF09\n outgoing = bot \u2192 \u7528\u6237\uFF1A\u53D1\u9001\u6210\u529F\u540E\u89E6\u53D1\uFF08reply = \u7528\u6237\u5B9E\u9645\u6536\u5230\u7684\u7EAF\u6587\u672C\uFF0C\u5DF2\u5265\u79BB think\uFF09\n \u00B7 source=\"reply\"\uFF08\u7F3A\u7701\uFF09= \u56DE\u590D\u7528\u6237\u6D88\u606F\uFF1Bsource=\"proactive\"\uFF08v1.30.9\uFF09= bot \u4E3B\u52A8\u53D1\u8D77\n \uFF08CCC \u7ECF weixin-send MSM \u2192 dsp \u4E3B\u52A8\u53D1\u9001\u5165\u53E3 \u2192 sendProactiveText\uFF09\n \u00B7 file\uFF08\u4EC5 send-file \u8865\u8BB0\uFF09\uFF1A{ \"name\": \"\u62A5\u544A.pdf\", \"size\": 12345, \"caption\": \"\u53EF\u9009\" }\n\n \u811A\u672C\u7EA6\u5B9A\uFF08\u4E8B\u4EF6 JSON \u5355\u884C\u7ECF stdin \u4F20\u5165\uFF1Bbun \u4F18\u5148 node \u515C\u5E95\uFF09\uFF1A\n #!/usr/bin/env bun \u6216 node script.js \u2014\u2014 \u8BFB process.stdin \u6574\u6BB5 JSON.parse\n const ev = JSON.parse(await new Response(process.stdin).text())\n ev.event === 'incoming' | 'outgoing'\n\n \u4E8B\u4EF6 schema\uFF08**\u4E0D\u542B\u4EFB\u4F55\u4F1A\u8BDD\u51ED\u636E**\u2014\u2014context_token/bot_token/token/aes_key \u5747\u4E0D\u51FA\u73B0\uFF09\uFF1A\n { \"event\": \"incoming\", \"ts\": 1788359941488, \"cccRoot\": \"/path/ccc\",\n \"accountId\": \"wechat-1\", \"userId\": \"u1@im.wechat\",\n \"sessionId\": \"skiff-weixin-xxx\", \"role\": \"zhaocai\",\n \"message\": { \"text\": \"\u4F60\u597D\", \"media\": [{ \"kind\": \"image\", \"relPath\": \"_tmp/weixin-inbound/<hash>/img_x.jpg\" }] } }\n { \"event\": \"outgoing\", \"ts\": ..., \"cccRoot\": ..., \"accountId\": ...,\n \"userId\": ..., \"sessionId\": ..., \"role\": ...,\n \"reply\": \"\u5DF2\u8BB0\u5F55\uFF08\u7EAF\u6587\u672C\uFF09\", \"source\": \"reply\" | \"proactive\" }\n\n \u793A\u4F8B\u811A\u672C\uFF08\u8FFD\u52A0\u5230\u6309\u65E5\u6587\u4EF6\u2014\u2014\u6301\u4E45\u5316\u5F52 CCC \u81EA\u9009\uFF1A\u6587\u4EF6/DB/\u8FDC\u7AEF\u5747\u53EF\uFF09\uFF1A\n const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';\n fs.appendFileSync(p, JSON.stringify(ev) + '\\n');\n\n \u6267\u884C\u8BED\u4E49\uFF08\u65C1\u8DEF\u5BB9\u5FCD\uFF09\uFF1A\u5F02\u6B65 fire-and-forget + 15s \u8D85\u65F6 kill + \u5931\u8D25\u4EC5\u65E5\u5FD7\u2014\u2014\n \u5FAE\u4FE1\u6865\u6D88\u606F\u5904\u7406/\u56DE\u590D\u4E0D\u53D7 hook \u5F71\u54CD\uFF1B\u811A\u672C\u987B\u5728 CCC \u6839\u5185\uFF08\u8DEF\u5F84\u9003\u9038\u62D2\u7EDD\uFF09\u3002\n \u5A92\u4F53 relPath \u6307\u5411 _tmp/weixin-inbound/\uFF08\u4E34\u65F6\u76EE\u5F55\uFF09\u2014\u2014\u9700\u6301\u4E45\u4FDD\u5B58\u5A92\u4F53\u6587\u4EF6\u8BF7\u81EA\u884C copy\u3002\n";
23
23
  export interface MsmFlag {
24
24
  name: string;
25
25
  type?: string;
@@ -18,16 +18,25 @@ import { type WeixinMessage } from './weixin-api.js';
18
18
  /** 测试辅助:清空 typing_ticket 缓存(生产零调用) */
19
19
  export declare function resetWeixinTypingCache(): void;
20
20
  /**
21
- * 手动输出模式的**输出纪律块**(v1.30.10,`weixin.autoReplyWithLastMessage: false` 时注入)。
21
+ * 手动输出模式的**机制标记**(v1.30.10 引入;v1.30.16 S142 用户"这个词不能让 ACC 定义,
22
+ * 要让 CCC 定义"重构)。
22
23
  *
23
- * 为什么必须注入(R↓):关掉自动回发后,agent 若不知道"桥不会替你转发",用户就收不到任何回复
24
- * ——机制变更必须伴随可执行的行动指引。因此这里给的是**可直接照抄的完整命令**(CCC 根 + 账号 +
25
- * 用户 id 全部填好),而不是抽象提示(E↑)。
24
+ * 边界(R↓):ACC 只提供**机制与数据**——"桥不转发最终文本"这个事实 + 三个已填好的参数值
25
+ * CCC 根 / 账号 / 用户 id)。**纪律措辞归 CCC**:怎么写、必须怎么做、后果是什么,全部写在
26
+ * CCC 的角色提示词文件里(如 `.opencode/skiff/zhaocai.md` 的「输出通道」节),CCC 可随时改,
27
+ * 且 v1.30.15 起角色提示词热重载——改完立即生效,无需 ACC 发版。
26
28
  *
27
- * 参数全部显式:`--ccc`(必填,无隐式当前 CCC)、`--account`(沿用收到消息的账号,避免多账号下
28
- * 用错 bot 身份回话)、`--user`(该用户 iLink id,免去别名解析)。
29
+ * 为什么不用 ACC 写措辞(历史教训):v1.30.10~v1.30.15 由 ACC 内嵌一段纪律文案,用户实测
30
+ * "约束不够,LLM 不听"——且措辞迭代要动插件代码 + 发版 + 重启;把措辞放 CCC 才能快速迭代,
31
+ * 也符合"ACC 管机制、CCC 管内容"的归属二分。
32
+ *
33
+ * 标记格式(稳定,供 CCC 提示词引用):
34
+ * ```
35
+ * [serenity:weixin-manual-output]
36
+ * msm("weixin-send", ["send", "--ccc", "<root>", "--account", "<account>", "--user", "<user>", "<回复>"])
37
+ * ```
29
38
  */
30
- export declare function weixinManualOutputLine(root: string, accountId: string, userId: string): string;
39
+ export declare function weixinManualOutputMarker(root: string, accountId: string, userId: string): string;
31
40
  /**
32
41
  * 处理单条微信消息:路由 → skiff 会话(固定 id 创建/延续)→ 提问 → 回复回写。
33
42
  *
@@ -0,0 +1,50 @@
1
+ /**
2
+ * weixin-output-guard.ts — 手动输出模式的**机械闸门**(v1.30.16,S142 用户"LLM 不听")
3
+ *
4
+ * 问题(实证):`weixin.autoReplyWithLastMessage: false` 时桥不回发最终文本,输出权归 agent
5
+ * (必须自己调 `msm("weixin-send", …)`)。提示词层面已说明,但用户实测 **LLM 仍经常不发**——
6
+ * 结果用户什么都收不到(静默)。这类"提示词软约束失效"正是 ACC 该用机械守卫兜底的场景
7
+ * (与 output-guard 同一套思路:turn-stopping + agent.steer)。
8
+ *
9
+ * 本模块做三件事(全部是机制,不含任何纪律措辞——措辞归 CCC 角色提示词):
10
+ * ① `noteManualOutputSession`:桥每轮把手动模式会话登记进来(含 root/account/user/role)
11
+ * ② 订阅 `tools/post-execute`:观察本轮是否真的调用了 `msm` 且首个参数为 `weixin-send`
12
+ * (**只在执行成功时**计入——失败调用不算"已送达")
13
+ * ③ 订阅 `agent/turn-stopping`:本轮结束仍没有成功发送 → `agent.steer` 打回一次(≤2 次),
14
+ * 提示内容 = **标记 + 事实**("本轮未发送,用户什么都没收到"),具体怎么做由 CCC 角色
15
+ * 提示词定义(与 per-message 标记同名 `[serenity:weixin-manual-output]`,模型能对上)。
16
+ *
17
+ * 为什么不直接替 agent 发送(R↓):用户拍板的语义是"输出权归 agent"——代发会让 agent 误以为
18
+ * 自己发过、也掩盖了"模型没遵守"这一事实;打回让模型自己完成,且留下可观测的日志。
19
+ *
20
+ * 达上限后放弃并**响亮告警**(不静默失败):宁可让日志里出现一条明确记录,也不要假装正常。
21
+ */
22
+ import type { Context } from 'cordis';
23
+ /** 打回上限(超过则放弃并告警——避免与模型无限拉扯) */
24
+ export declare const WEIXIN_OUTPUT_REBUKE_MAX = 2;
25
+ /** 手动模式会话上下文(桥登记) */
26
+ export interface ManualOutputSession {
27
+ root: string;
28
+ accountId: string;
29
+ userId: string;
30
+ role: string;
31
+ }
32
+ /** 桥:登记手动模式会话(每轮 incoming 都调用,幂等覆盖) */
33
+ export declare function noteManualOutputSession(sessionId: string, session: ManualOutputSession): void;
34
+ /** 桥:非手动模式 / 会话失效 → 注销(闸门随之失效,零干预) */
35
+ export declare function forgetManualOutputSession(sessionId: string): void;
36
+ /** 是否手动模式会话(测试/诊断用) */
37
+ export declare function isManualOutputSession(sessionId: string): boolean;
38
+ /** 本轮是否已成功发送(测试/诊断用) */
39
+ export declare function hasSentThisTurn(sessionId: string): boolean;
40
+ /** 工具调用是否为「成功的 weixin-send」 */
41
+ export declare function isSuccessfulWeixinSend(exec: unknown, result: unknown): boolean;
42
+ /** 打回消息(机制事实 + 标记;怎么做由 CCC 角色提示词定义) */
43
+ export declare function buildManualOutputRebuke(session: ManualOutputSession): string;
44
+ /** 测试辅助:清空全部状态 */
45
+ export declare function __resetWeixinOutputGuardForTest(): void;
46
+ /**
47
+ * 装配闸门(index.ts apply 调用)。
48
+ * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff/ACP 临时会话)零干预。
49
+ */
50
+ export declare function registerWeixinOutputGuard(ctx: Context): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.30.15",
3
+ "version": "1.30.16",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {