@shgroup/dsh-serenity-hooks 1.30.14 → 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 +1 -1
- package/lib/ccc.d.ts +11 -1
- package/lib/index.js +152 -39
- package/lib/msm-ops.d.ts +1 -1
- package/lib/{skiff-debug-DYXDdzu3.js → skiff-debug-C5ulZKCa.js} +14 -9
- package/lib/{skiff-role-DlrbHPLD.js → skiff-role-BYvNnwv1.js} +64 -2
- package/lib/skiff-role.d.ts +24 -0
- package/lib/weixin-bridge.d.ts +16 -7
- package/lib/weixin-output-guard.d.ts +50 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.30.
|
|
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/ccc.d.ts
CHANGED
|
@@ -178,6 +178,15 @@ export interface WeixinRouteConfig {
|
|
|
178
178
|
export interface SkiffRoleConfig {
|
|
179
179
|
/** 角色模型(provider/model;CCC 直接指定,无白名单校验——用户拍板) */
|
|
180
180
|
model?: string;
|
|
181
|
+
/**
|
|
182
|
+
* 角色类型(v1.30.15,S142 用户拍板"skiff 应该区分的类型是临时和长期"):
|
|
183
|
+
* - `temporary`:临时 skiff——不绑定 trajectory(无 SESSION.md 工作台),
|
|
184
|
+
* 提示词按创建时快照注入("注入发生一次")。
|
|
185
|
+
* - `persistent`:长期 skiff——绑定 trajectory(自动建/恢复 SESSION.md 工作台),
|
|
186
|
+
* 提示词**每轮动态重读文件**(改 `systemPromptFile` 即生效,无需重启)。
|
|
187
|
+
* 缺省(未写)= 隐式推断:调用方传入稳定 sessionId → persistent,否则 temporary。
|
|
188
|
+
*/
|
|
189
|
+
kind?: 'temporary' | 'persistent';
|
|
181
190
|
/** MSM 白名单(独立):该角色可 exec 的 MSM 名列表;缺省空 = 无 MSM 通道 */
|
|
182
191
|
msms?: string[];
|
|
183
192
|
/** 非 MSM 工具白名单(独立):平台/ACC 工具名列表;缺省空 = 仅 MSM 通道 */
|
|
@@ -191,7 +200,8 @@ export interface SkiffRoleConfig {
|
|
|
191
200
|
/** 角色系统提示词(CCC 完整定义:人格/认知边界/回答风格) */
|
|
192
201
|
systemPrompt?: string;
|
|
193
202
|
/** 角色系统提示词文件(推荐:相对 CCC 根引用 md 文件——超长提示词在 JSON 内嵌不可读;
|
|
194
|
-
* 优先于 systemPrompt;文件缺失/逃逸 → resolveRoleSystemPrompt
|
|
203
|
+
* 优先于 systemPrompt;文件缺失/逃逸 → resolveRoleSystemPrompt 抛错。
|
|
204
|
+
* persistent 型每轮重读(mtime 缓存),改文件即生效) */
|
|
195
205
|
systemPromptFile?: string;
|
|
196
206
|
}
|
|
197
207
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$2, f as readHandymanConfig, i as findGitRoot, l as pathInside, m as resolveInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DAsSHsub.js";
|
|
3
|
-
import { a as
|
|
4
|
-
import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-
|
|
3
|
+
import { a as readSkiffRoles, d as systemPromptSource, i as isSkiffSessionId, l as roleToolWhitelist, o as resolveRoleSystemPrompt } from "./skiff-role-BYvNnwv1.js";
|
|
4
|
+
import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-C5ulZKCa.js";
|
|
5
5
|
import { i as hostSessions, n as hostInjected, o as hostWeb, r as hostService, s as hostWebServer, t as hostAgents } from "./access-dU_vG1q8.js";
|
|
6
6
|
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, 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-BD_yYAZz.js";
|
|
7
7
|
import { C as useSession, S as summarize, _ as resolveSessionByTitle, a as archiveSessions, b as setActiveSessionInfo, c as createSession, d as getActiveSessionInfo, f as healthCheck, g as readActiveSessionMd, h as qaCheck, i as SESSION_ACTIONS, l as extractSessionMdPathFromText, m as parseSessionContextFromEvents, n as readLastBound, o as clearActiveSessionInfo, p as listSessions, r as DEFAULT_SESSION_SCOPE, s as closeSession, t as appendBound, u as findSession, v as sessionEvents, x as showSession, y as sessionsRoot } from "./session-bound-BpTayaIS.js";
|
|
@@ -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
|
|
1855
|
-
|
|
1856
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
6000
|
+
source: PLUGIN_SOURCE$3
|
|
5997
6001
|
});
|
|
5998
6002
|
}
|
|
5999
6003
|
/** 每 agent 是否已注入过(进程内存态) */
|
|
@@ -6478,7 +6482,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6478
6482
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6479
6483
|
});
|
|
6480
6484
|
const root = findSerenityRoot(workspace) ?? "";
|
|
6481
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
6485
|
+
const { discoverCccs } = await import("./skiff-debug-C5ulZKCa.js").then((n) => n.i);
|
|
6482
6486
|
sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
6483
6487
|
} catch (err) {
|
|
6484
6488
|
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
@@ -6675,7 +6679,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6675
6679
|
return;
|
|
6676
6680
|
}
|
|
6677
6681
|
const { saveWeixinRoutes } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
|
|
6678
|
-
const { readSkiffRoles } = await import("./skiff-role-
|
|
6682
|
+
const { readSkiffRoles } = await import("./skiff-role-BYvNnwv1.js").then((n) => n.u);
|
|
6679
6683
|
const roles = readSkiffRoles(root);
|
|
6680
6684
|
for (const r of routes) if (!roles.has(r.role)) {
|
|
6681
6685
|
sendJson$2(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
|
|
@@ -7884,7 +7888,7 @@ var rebuild_exports = /* @__PURE__ */ __exportAll({
|
|
|
7884
7888
|
sanitizeFocusLine: () => sanitizeFocusLine,
|
|
7885
7889
|
stripAckSuffix: () => stripAckSuffix
|
|
7886
7890
|
});
|
|
7887
|
-
const PLUGIN_SOURCE$
|
|
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$
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
9428
|
-
*
|
|
9429
|
-
*
|
|
9536
|
+
* 为什么不用 ACC 写措辞(历史教训):v1.30.10~v1.30.15 由 ACC 内嵌一段纪律文案,用户实测
|
|
9537
|
+
* "约束不够,LLM 不听"——且措辞迭代要动插件代码 + 发版 + 重启;把措辞放 CCC 才能快速迭代,
|
|
9538
|
+
* 也符合"ACC 管机制、CCC 管内容"的归属二分。
|
|
9430
9539
|
*
|
|
9431
|
-
*
|
|
9432
|
-
*
|
|
9540
|
+
* 标记格式(稳定,供 CCC 提示词引用):
|
|
9541
|
+
* ```
|
|
9542
|
+
* [serenity:weixin-manual-output]
|
|
9543
|
+
* msm("weixin-send", ["send", "--ccc", "<root>", "--account", "<account>", "--user", "<user>", "<回复>"])
|
|
9544
|
+
* ```
|
|
9433
9545
|
*/
|
|
9434
|
-
function
|
|
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({
|
|
@@ -9897,7 +10006,7 @@ function matchCcc(input, candidates) {
|
|
|
9897
10006
|
}
|
|
9898
10007
|
/** 组装候选列表(discoverCccs 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
9899
10008
|
async function collectCandidates(ctx) {
|
|
9900
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
10009
|
+
const { discoverCccs } = await import("./skiff-debug-C5ulZKCa.js").then((n) => n.i);
|
|
9901
10010
|
const entries = await discoverCccs(ctx, process.cwd());
|
|
9902
10011
|
const seen = /* @__PURE__ */ new Set();
|
|
9903
10012
|
const out = [];
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { a as findSerenityRoot, f as readHandymanConfig } from "./ccc-DAsSHsub.js";
|
|
3
|
-
import { a as
|
|
3
|
+
import { a as readSkiffRoles, c as roleMsmWhitelist, f as trajectorySubset, i as isSkiffSessionId, n as buildSkiffBasePrompt, o as resolveRoleSystemPrompt, r as createRolePromptReader, s as resolveSkiffKind } from "./skiff-role-BYvNnwv1.js";
|
|
4
4
|
import { i as hostSessions, r as hostService, t as hostAgents } from "./access-dU_vG1q8.js";
|
|
5
5
|
import { b as setActiveSessionInfo, c as createSession, d as getActiveSessionInfo, n as readLastBound, t as appendBound } from "./session-bound-BpTayaIS.js";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
@@ -458,15 +458,20 @@ async function createSkiffAgent(ctx, root, roleName, role, defaultModel, session
|
|
|
458
458
|
};
|
|
459
459
|
const handle = await createOrResumeAgent(ctx, id, root, model, setup, sessionId !== void 0);
|
|
460
460
|
const agent = handle.agent;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
461
|
+
const kind = resolveSkiffKind(role, sessionId !== void 0);
|
|
462
|
+
const rolePrompt = kind === "persistent" ? createRolePromptReader(root, role) : (() => {
|
|
463
|
+
let snapshot = "";
|
|
464
|
+
try {
|
|
465
|
+
snapshot = resolveRoleSystemPrompt(root, role);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 系统提示词解析失败(回退仅基础段): ${String(err?.message ?? err)}`);
|
|
468
|
+
}
|
|
469
|
+
return () => snapshot;
|
|
470
|
+
})();
|
|
471
|
+
rolePrompt();
|
|
467
472
|
let workspaceMdPath = null;
|
|
468
473
|
try {
|
|
469
|
-
workspaceMdPath = ensureSkiffSession(root, agent, roleName, role,
|
|
474
|
+
workspaceMdPath = ensureSkiffSession(root, agent, roleName, role, kind === "persistent");
|
|
470
475
|
} catch (err) {
|
|
471
476
|
console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 专属 SESSION 处理失败(不影响 agent 创建): ${String(err?.message ?? err)}`);
|
|
472
477
|
}
|
|
@@ -474,7 +479,7 @@ async function createSkiffAgent(ctx, root, roleName, role, defaultModel, session
|
|
|
474
479
|
agent.ctx.systemPrompt.section({
|
|
475
480
|
name: "serenity-skiff",
|
|
476
481
|
order: -60,
|
|
477
|
-
text: () => [buildSkiffBasePrompt(roleName, role),
|
|
482
|
+
text: () => [buildSkiffBasePrompt(roleName, role), rolePrompt()].filter(Boolean).join("\n")
|
|
478
483
|
});
|
|
479
484
|
} catch (err) {
|
|
480
485
|
console.warn(`[serenity-hooks] skiff 系统提示词注册失败: ${String(err?.message ?? err)}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { m as resolveInside, p as readUtf8, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-DAsSHsub.js";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
//#region src/skiff-role.ts
|
|
5
5
|
/**
|
|
6
6
|
* skiff-role.ts — Skiff(F4,v1.25.0 实验性)角色层纯逻辑(零 DSH 依赖,可独立单测)
|
|
@@ -15,9 +15,11 @@ import { existsSync } from "node:fs";
|
|
|
15
15
|
var skiff_role_exports = /* @__PURE__ */ __exportAll({
|
|
16
16
|
SKIFF_SESSION_PREFIX: () => SKIFF_SESSION_PREFIX,
|
|
17
17
|
buildSkiffBasePrompt: () => buildSkiffBasePrompt,
|
|
18
|
+
createRolePromptReader: () => createRolePromptReader,
|
|
18
19
|
isSkiffSessionId: () => isSkiffSessionId,
|
|
19
20
|
readSkiffRoles: () => readSkiffRoles,
|
|
20
21
|
resolveRoleSystemPrompt: () => resolveRoleSystemPrompt,
|
|
22
|
+
resolveSkiffKind: () => resolveSkiffKind,
|
|
21
23
|
roleMsmWhitelist: () => roleMsmWhitelist,
|
|
22
24
|
roleToolWhitelist: () => roleToolWhitelist,
|
|
23
25
|
systemPromptSource: () => systemPromptSource,
|
|
@@ -43,6 +45,7 @@ function readSkiffRoles(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
43
45
|
if (name.trim() === "") continue;
|
|
44
46
|
out.set(name.trim(), {
|
|
45
47
|
model: typeof role.model === "string" ? role.model : void 0,
|
|
48
|
+
kind: role.kind === "temporary" || role.kind === "persistent" ? role.kind : void 0,
|
|
46
49
|
msms: Array.isArray(role.msms) ? role.msms.filter((m) => typeof m === "string") : void 0,
|
|
47
50
|
tools: Array.isArray(role.tools) ? role.tools.filter((t) => typeof t === "string") : void 0,
|
|
48
51
|
trajectory: role.trajectory && typeof role.trajectory === "object" ? {
|
|
@@ -103,6 +106,65 @@ function systemPromptSource(role) {
|
|
|
103
106
|
if (role?.systemPrompt?.trim()) return "inline";
|
|
104
107
|
return "none";
|
|
105
108
|
}
|
|
109
|
+
function resolveSkiffKind(role, hasStableId) {
|
|
110
|
+
const k = role?.kind;
|
|
111
|
+
if (k === "temporary" || k === "persistent") return k;
|
|
112
|
+
return hasStableId ? "persistent" : "temporary";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 角色提示词**读取器**(v1.30.15,方案 A:长期型热更新)。
|
|
116
|
+
*
|
|
117
|
+
* 为什么是函数而不是字符串(R↓):旧实现 `createSkiffAgent` 在创建时把
|
|
118
|
+
* `resolveRoleSystemPrompt(...)` 的结果**快照**进系统提示词段闭包 → CCC 改了
|
|
119
|
+
* `zhaocai.md` 必须重启 dsh web 才生效(本轮实证:改完 18KB 提示词,live agent 仍用旧文本)。
|
|
120
|
+
* 现形态返回一个读取函数,由系统提示词段的 `text()` **每轮调用**:
|
|
121
|
+
* - `systemPromptFile`:按 **mtime + size** 缓存重读(命中缓存零 IO;未变不解析)
|
|
122
|
+
* → 改文件即生效,**无需重启**;变更时打一行 info 日志(可观测,便于确认热更生效)
|
|
123
|
+
* - 内嵌 `systemPrompt`:静态(JSON 配置改动仍需重启/重载配置)
|
|
124
|
+
* - 读取失败:返回**上次成功内容**(绝不返回空——空提示词会让角色失去人格与纪律),
|
|
125
|
+
* 并按标签去重告警一次
|
|
126
|
+
* @param root CCC 根
|
|
127
|
+
* @param role 角色配置
|
|
128
|
+
*/
|
|
129
|
+
function createRolePromptReader(root, role) {
|
|
130
|
+
const file = role?.systemPromptFile?.trim();
|
|
131
|
+
if (!file) {
|
|
132
|
+
const inline = role?.systemPrompt ?? "";
|
|
133
|
+
return () => inline;
|
|
134
|
+
}
|
|
135
|
+
let abs = null;
|
|
136
|
+
try {
|
|
137
|
+
abs = resolveInside(root, file);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.warn(`[serenity-hooks] ✗ skiff 角色提示词路径非法(${file}): ${String(err?.message ?? err)}`);
|
|
140
|
+
}
|
|
141
|
+
if (abs === null) return () => "";
|
|
142
|
+
const absPath = abs;
|
|
143
|
+
let cachedMtime = -1;
|
|
144
|
+
let cachedSize = -1;
|
|
145
|
+
let cachedText = "";
|
|
146
|
+
let warned = false;
|
|
147
|
+
return () => {
|
|
148
|
+
try {
|
|
149
|
+
const st = statSync(absPath);
|
|
150
|
+
if (st.mtimeMs === cachedMtime && st.size === cachedSize) return cachedText;
|
|
151
|
+
const text = readUtf8(absPath).trim();
|
|
152
|
+
const isReload = cachedMtime !== -1;
|
|
153
|
+
cachedMtime = st.mtimeMs;
|
|
154
|
+
cachedSize = st.size;
|
|
155
|
+
cachedText = text;
|
|
156
|
+
warned = false;
|
|
157
|
+
if (isReload) console.info(`[serenity-hooks] ↻ skiff 角色提示词已热重载(${file},${text.length} 字符)`);
|
|
158
|
+
return text;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (!warned) {
|
|
161
|
+
warned = true;
|
|
162
|
+
console.warn(`[serenity-hooks] ✗ skiff 角色提示词读取失败(${file})——沿用上次成功内容: ${String(err?.message ?? err)}`);
|
|
163
|
+
}
|
|
164
|
+
return cachedText;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
106
168
|
/**
|
|
107
169
|
* Skiff 基础提示词(dsp 只给这部分;CCC 的 systemPrompt 段由调用方拼接):
|
|
108
170
|
* 身份 + 可用 MSM/工具清单 + 调用协议 + 边界声明。动态生成(清单来自角色白名单)。
|
|
@@ -125,4 +187,4 @@ function buildSkiffBasePrompt(roleName, role) {
|
|
|
125
187
|
return lines.join("\n");
|
|
126
188
|
}
|
|
127
189
|
//#endregion
|
|
128
|
-
export {
|
|
190
|
+
export { readSkiffRoles as a, roleMsmWhitelist as c, systemPromptSource as d, trajectorySubset as f, isSkiffSessionId as i, roleToolWhitelist as l, buildSkiffBasePrompt as n, resolveRoleSystemPrompt as o, createRolePromptReader as r, resolveSkiffKind as s, SKIFF_SESSION_PREFIX as t, skiff_role_exports as u };
|
package/lib/skiff-role.d.ts
CHANGED
|
@@ -43,6 +43,30 @@ export declare function roleMsmWhitelist(role: SkiffRoleConfig | undefined): Set
|
|
|
43
43
|
export declare function resolveRoleSystemPrompt(root: string, role: SkiffRoleConfig | undefined): string;
|
|
44
44
|
/** 角色系统提示词来源(validate/list 展示用) */
|
|
45
45
|
export declare function systemPromptSource(role: SkiffRoleConfig | undefined): 'file' | 'inline' | 'none';
|
|
46
|
+
/**
|
|
47
|
+
* 角色类型(v1.30.15,S142 用户拍板):
|
|
48
|
+
* - 显式 `role.kind` 优先(`temporary` / `persistent`)
|
|
49
|
+
* - 缺省 = 隐式推断:调用方给出**稳定 sessionId** → persistent;否则 temporary
|
|
50
|
+
* (存量配置零迁移;微信桥传固定会话 id → persistent,ACP/调试页随机 id → temporary)
|
|
51
|
+
*/
|
|
52
|
+
export type SkiffKind = 'temporary' | 'persistent';
|
|
53
|
+
export declare function resolveSkiffKind(role: SkiffRoleConfig | undefined, hasStableId: boolean): SkiffKind;
|
|
54
|
+
/**
|
|
55
|
+
* 角色提示词**读取器**(v1.30.15,方案 A:长期型热更新)。
|
|
56
|
+
*
|
|
57
|
+
* 为什么是函数而不是字符串(R↓):旧实现 `createSkiffAgent` 在创建时把
|
|
58
|
+
* `resolveRoleSystemPrompt(...)` 的结果**快照**进系统提示词段闭包 → CCC 改了
|
|
59
|
+
* `zhaocai.md` 必须重启 dsh web 才生效(本轮实证:改完 18KB 提示词,live agent 仍用旧文本)。
|
|
60
|
+
* 现形态返回一个读取函数,由系统提示词段的 `text()` **每轮调用**:
|
|
61
|
+
* - `systemPromptFile`:按 **mtime + size** 缓存重读(命中缓存零 IO;未变不解析)
|
|
62
|
+
* → 改文件即生效,**无需重启**;变更时打一行 info 日志(可观测,便于确认热更生效)
|
|
63
|
+
* - 内嵌 `systemPrompt`:静态(JSON 配置改动仍需重启/重载配置)
|
|
64
|
+
* - 读取失败:返回**上次成功内容**(绝不返回空——空提示词会让角色失去人格与纪律),
|
|
65
|
+
* 并按标签去重告警一次
|
|
66
|
+
* @param root CCC 根
|
|
67
|
+
* @param role 角色配置
|
|
68
|
+
*/
|
|
69
|
+
export declare function createRolePromptReader(root: string, role: SkiffRoleConfig | undefined): () => string;
|
|
46
70
|
/**
|
|
47
71
|
* Skiff 基础提示词(dsp 只给这部分;CCC 的 systemPrompt 段由调用方拼接):
|
|
48
72
|
* 身份 + 可用 MSM/工具清单 + 调用协议 + 边界声明。动态生成(清单来自角色白名单)。
|
package/lib/weixin-bridge.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
21
|
+
* 手动输出模式的**机制标记**(v1.30.10 引入;v1.30.16 按 S142 用户"这个词不能让 ACC 定义,
|
|
22
|
+
* 要让 CCC 定义"重构)。
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* 边界(R↓):ACC 只提供**机制与数据**——"桥不转发最终文本"这个事实 + 三个已填好的参数值
|
|
25
|
+
* (CCC 根 / 账号 / 用户 id)。**纪律措辞归 CCC**:怎么写、必须怎么做、后果是什么,全部写在
|
|
26
|
+
* CCC 的角色提示词文件里(如 `.opencode/skiff/zhaocai.md` 的「输出通道」节),CCC 可随时改,
|
|
27
|
+
* 且 v1.30.15 起角色提示词热重载——改完立即生效,无需 ACC 发版。
|
|
26
28
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
|
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.
|
|
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": {
|