@shgroup/dsh-serenity-hooks 1.31.2 → 1.31.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dsh.plugin.json +1 -1
- package/lib/{access-dU_vG1q8.js → access-BqtkTl9M.js} +4 -1
- package/lib/handyman-ops.d.ts +1 -1
- package/lib/host/access.d.ts +10 -0
- package/lib/index.js +109 -10
- package/lib/msm-ops.d.ts +1 -1
- package/lib/{settings-section-CP4uMJf_.js → settings-section-BxoNXUqk.js} +1 -1
- package/lib/{skiff-debug-C5ulZKCa.js → skiff-debug-sdGryydE.js} +26 -10
- package/lib/tools/handyman.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
|
|
|
75
75
|
| `container_git` | git 操作:status / commit / push / log / pull / diff | 提交和推送代码;它绝不自动强推 |
|
|
76
76
|
| `msm` | 小工具执行入口:`msm("名字", ["参数"])`;名字记不全就给候选;`inspect=true` 看用法 | 调用工作区里注册的任何小工具 |
|
|
77
77
|
| `praxis` | 按需给 AI 注入三套"做事方法":输出自检(eap)、设计对齐(neat)、认知连续性(cce) | 要它把话说清楚 / 先对齐再动手时 |
|
|
78
|
-
| `handyman` |
|
|
78
|
+
| `handyman` | 杂工:派一个便宜模型的助手干活。**默认模式(foreground)**= 一次串行委派、拿回结果;**background 模式**= 循环干活直到完成(完成码校验 / 轮次上限 / 自动重启 / 进度文件),也可一次派多个并行 | 大批量、重复性的活(扫描几十个技能、逐用例回归) |
|
|
79
79
|
| `localstore` | 存密钥和配置(凭据、偏好两个命名空间) | API key、密码集中放一处,不进 git |
|
|
80
80
|
| `container_admin` | 机务舱:管理子角色、管理小工具注册表、查看全部配置 | 定义"子角色"、注册新小工具时 |
|
|
81
81
|
| `autopilot-trajectory` | 自动巡航:定时唤醒工作区、注入当前焦点,支持多个工作区各自独立 | 想让 AI 定时自己干活(见 §6.5) |
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.3",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1(deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
@@ -49,5 +49,8 @@ function hostSettings(ctx) {
|
|
|
49
49
|
function hostWeb(ctx) {
|
|
50
50
|
return hostInjected(ctx, "web");
|
|
51
51
|
}
|
|
52
|
+
function hostSubagents(ctx) {
|
|
53
|
+
return hostInjected(ctx, "subagents");
|
|
54
|
+
}
|
|
52
55
|
//#endregion
|
|
53
|
-
export { hostSettings as a, hostSessions as i, hostInjected as n,
|
|
56
|
+
export { hostSettings as a, hostWebServer as c, hostSessions as i, hostInjected as n, hostSubagents as o, hostService as r, hostWeb as s, hostAgents as t };
|
package/lib/handyman-ops.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export declare function buildRoundPrompt(opts: {
|
|
|
54
54
|
* 使用 handyman 前必须先加载 eap 设计方案;并行策略(jobs 编排);提示词规范(详尽固定 EAP);
|
|
55
55
|
* 阅读/文字编写类 handyman 内部也加载 eap。
|
|
56
56
|
*/
|
|
57
|
-
export declare const HANDYMAN_GUIDE = "# handyman \u2014 Scale-Up Usage Guide (guide)\n\n## \u26A0\uFE0F Before using: load eap and design the plan\nBefore calling handyman, load eap (acc-eap skill) and design the \"scale-up handyman plan\" based on the EAP framework.\n\n### 1. Task decomposition (E\u2191 Explicit)\n- Split large tasks into explicit subtasks: each subtask defines goal / input / boundaries (what to do, what not to do) / acceptance criteria\n- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel\n\n### 2. Prompt design (handyman's task parameter)\n- task must be detailed, fixed, and EAP-compliant: clear goal, drawn boundaries, decidable acceptance criteria\n- Anti-example \"handle this file\" \u2014 ambiguous; good example \"read <path>, extract all rows of the\u300C\u5173\u952E\u51B3\u7B56\u300Dtable,\n output a JSON array (fields id/conclusion/evidence), do not modify the original file\"\n- Reading/curating or text-writing work (extracting from files, summarizing, writing docs, generating text, etc.):\n the handyman-internal agent is also required to load eap and organize output per the EAP standard\n\n### 3. Model whitelist (CCC-configured, mandatory)\n- handyman only uses models listed in .opencode/serenity.json \"handyman.models\" \u2014 never arbitrary models\n-
|
|
57
|
+
export declare const HANDYMAN_GUIDE = "# handyman \u2014 Scale-Up Usage Guide (guide)\n\n## Two modes (v1.31.3; default = foreground)\n| | foreground (default) | background |\n|---|---|---|\n| What | one serial child agent runs the task ONCE and returns its final text | loop-validated worker: hard while-loop + random completion code |\n| Use when | you need exactly one result back now (per-case testers, single conversions) | long/unattended work needing anti-early-finish, resumability, or parallel jobs |\n| Loop / validation | none | stop-token is the ONLY completion condition; round cap (default 100); auto-restart (\u2264100) |\n| Progress file | none | AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes) |\n| Parallel jobs | no (call once per task) | yes (jobs=[...], handyman.maxParallel default 10) |\n| Mechanism | host delegation service ctx.subagents.start(\"spawn\") + agentOptions | ctx.agents.create + internal while-loop |\n| Model source | CCC handyman.models whitelist (same for both) | same |\n\n## \u26A0\uFE0F Before using: load eap and design the plan\nBefore calling handyman, load eap (acc-eap skill) and design the \"scale-up handyman plan\" based on the EAP framework.\n\n### 1. Task decomposition (E\u2191 Explicit)\n- Split large tasks into explicit subtasks: each subtask defines goal / input / boundaries (what to do, what not to do) / acceptance criteria\n- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel (background mode)\n\n### 2. Prompt design (handyman's task parameter)\n- task must be detailed, fixed, and EAP-compliant: clear goal, drawn boundaries, decidable acceptance criteria\n- foreground mode: the child does NOT share your conversation \u2014 restate everything it needs in task\n- Anti-example \"handle this file\" \u2014 ambiguous; good example \"read <path>, extract all rows of the\u300C\u5173\u952E\u51B3\u7B56\u300Dtable,\n output a JSON array (fields id/conclusion/evidence), do not modify the original file\"\n- Reading/curating or text-writing work (extracting from files, summarizing, writing docs, generating text, etc.):\n the handyman-internal agent is also required to load eap and organize output per the EAP standard\n\n### 3. Model whitelist (CCC-configured, mandatory)\n- handyman only uses models listed in .opencode/serenity.json \"handyman.models\" \u2014 never arbitrary models\n- The CCC is expected to point this whitelist at LOW-COST models: handyman is the bulk-execution channel\n- background mode: recursive subagents inside a worker inherit the worker's model automatically (DSH native)\n- foreground mode: the child is started through the host delegation service with the resolved model\n\n### 4. Parallel strategy (background mode only)\n- Independent subtasks can run in parallel via handyman(mode=\"background\", jobs=[...]): each job gets its own label + task + stop token + progress file\n- Concurrency safety guaranteed: unique sessionId (handyman-<label>-<uuid>), progress files isolated per label\n (AGENT_SESSIONS/handyman-<label>.json) \u2014 same label resumes, different labels never interfere\n- Parallel cap: handyman.maxParallel (default 10 \u2014 cheap models are cheap)\n- Aggregation: after each parallel job produces progress, the main agent merges (or spawns one aggregation handyman)\n- For programmable pipeline/phase orchestration at scale, use the platform's workflow tool instead\n\n## Completion criteria\n- foreground: the child's run settles; done=true only when stopReason=\"completed\" (otherwise read diagnostic)\n- background (osp loop standard): the only completion condition = the worker echoes this round's random verification\n code (stop token); dialogue round cap (default 100, osp fail-safe, forced stop beyond the cap, resumable);\n automatic restart on abnormal agent stop (\u2264100 restarts, anti-infinite-loop)\n\n## Waiting UI\n- The WebUI session-header Serenity detail card shows running background handymen's progress\n (label / round / last response), one line per parallel job, ~3s refresh (foreground mode writes no progress)\n";
|
|
58
58
|
/** handyman 运行状态(进度文件摘要;WebUI 等待界面数据源) */
|
|
59
59
|
export interface HandymanRunInfo {
|
|
60
60
|
label: string;
|
package/lib/host/access.d.ts
CHANGED
|
@@ -56,5 +56,15 @@ export declare function hostWebServer(ctx: unknown): HostWebServer | undefined;
|
|
|
56
56
|
export declare function hostSettings(ctx: unknown): HostSettings | undefined;
|
|
57
57
|
/** `ctx.web`(injected;v1.30.12 web_fetch provider 注册通道) */
|
|
58
58
|
export declare function hostWeb(ctx: unknown): HostWeb | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* `ctx.subagents`(lazy;v1.31.3:handyman foreground 模式的委派正门)。
|
|
61
|
+
*
|
|
62
|
+
* 形状对照宿主 rc.1:`SubagentRuntime.start(name, request)` → `SubagentRun`
|
|
63
|
+
* (`result` / `dispose` / `id`)。本模块只做形状收口;语义与错误处理归调用方。
|
|
64
|
+
*/
|
|
65
|
+
export interface HostSubagents {
|
|
66
|
+
start?: (name: string, request: unknown) => Promise<unknown>;
|
|
67
|
+
}
|
|
68
|
+
export declare function hostSubagents(ctx: unknown): HostSubagents | undefined;
|
|
59
69
|
/** 会话 cwd 列表(live 会话;形状不符时返回空数组而非抛错) */
|
|
60
70
|
export declare function hostSessionCwds(ctx: unknown): string[];
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
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-
|
|
5
|
-
import { i as hostSessions, n as hostInjected, o 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-sdGryydE.js";
|
|
5
|
+
import { c as hostWebServer, i as hostSessions, n as hostInjected, o as hostSubagents, r as hostService, s as hostWeb, t as hostAgents } from "./access-BqtkTl9M.js";
|
|
6
6
|
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-wWk4AIwV.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";
|
|
8
|
-
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-
|
|
8
|
+
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-BxoNXUqk.js";
|
|
9
9
|
import { a as markdownToPlainText, c as sendTyping, i as getUpdates, l as sniffImageExt, n as downloadMedia, o as sendFileMessage, r as getConfig, s as sendTextMessage, t as TypingStatus } from "./weixin-api-PjEhmjlZ.js";
|
|
10
10
|
import z from "@deepseek-ai/schemastery";
|
|
11
11
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -1221,6 +1221,17 @@ const HOST_SERVICES = [
|
|
|
1221
1221
|
impact: "handyman worker agent 无法装配 preset",
|
|
1222
1222
|
required: false
|
|
1223
1223
|
},
|
|
1224
|
+
{
|
|
1225
|
+
id: "subagents",
|
|
1226
|
+
name: "subagents",
|
|
1227
|
+
access: "lazy",
|
|
1228
|
+
members: [{
|
|
1229
|
+
name: "start",
|
|
1230
|
+
kind: "function"
|
|
1231
|
+
}],
|
|
1232
|
+
impact: "handyman foreground 模式不可用(响亮报错,不影响 background 模式)",
|
|
1233
|
+
required: false
|
|
1234
|
+
},
|
|
1224
1235
|
{
|
|
1225
1236
|
id: "sessionTitle",
|
|
1226
1237
|
name: "sessionTitle",
|
|
@@ -2020,6 +2031,15 @@ Below are all available configuration sections.
|
|
|
2020
2031
|
── 1. handyman.models ──
|
|
2021
2032
|
handyman(杂工)工具可用模型白名单(provider/model 列表);未配置时 handyman 报错要求配置。
|
|
2022
2033
|
缺省模型 = models[0](可用 handyman.defaultModel 指定,必须 ∈ models)。
|
|
2034
|
+
**CCC 应把该白名单指向低成本模型**——handyman 是批量执行通道(v1.31.3 双模式共用同一白名单)。
|
|
2035
|
+
|
|
2036
|
+
Modes(v1.31.3,缺省 foreground):
|
|
2037
|
+
mode="foreground"(缺省):一次前台串行委派(宿主委派服务 ctx.subagents.start("spawn") +
|
|
2038
|
+
agentOptions 注入模型)——不循环、不校验完成码、不写进度文件;返回子 agent 最终文本。
|
|
2039
|
+
适合"一次调用一次结果"(逐用例回归、单次转换)。
|
|
2040
|
+
mode="background":既有循环校验实现(内部 while 硬循环 + 随机完成码唯一判据 + 轮次上限
|
|
2041
|
+
默认 100 可续跑 + 异常自动重启 ≤100 + 进度文件 AGENT_SESSIONS/handyman-<label>.md/.json +
|
|
2042
|
+
jobs 并行 maxParallel 默认 10)。适合长任务/无人值守/需抗提前收工。
|
|
2023
2043
|
|
|
2024
2044
|
Config:
|
|
2025
2045
|
{ "handyman": { "models": ["provider/model-name"] } }
|
|
@@ -3031,6 +3051,58 @@ function parseJobs(raw) {
|
|
|
3031
3051
|
}
|
|
3032
3052
|
return jobs;
|
|
3033
3053
|
}
|
|
3054
|
+
/** 拼接 SubagentResult.output(ContentBlock[])中的文本块 */
|
|
3055
|
+
function subagentOutputText(output) {
|
|
3056
|
+
if (!Array.isArray(output)) return "";
|
|
3057
|
+
return output.filter((b) => typeof b === "object" && b !== null).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* foreground 模式:单次前台串行委派(用户裁决"本次所需的简单实现")。
|
|
3061
|
+
*
|
|
3062
|
+
* 与 background 的差别(R↓):
|
|
3063
|
+
* - 走宿主**委派正门** `ctx.subagents.start('spawn', …)`——深度上限、子 agent 所有权、
|
|
3064
|
+
* `subagent/start|end` 生命周期事件、`dispose()` 收尾全部沿用宿主实现(不重写 agent 创建);
|
|
3065
|
+
* - 模型经 `agentOptions` 注入(spawn 后端能力位 `agentOptions: true`),因此**不依赖**
|
|
3066
|
+
* 宿主未放开的 `subagent-model-selection` 设置;
|
|
3067
|
+
* - 不循环、不写进度文件、不做完成码校验——"一次调用一次结果"。
|
|
3068
|
+
*/
|
|
3069
|
+
async function runForegroundJob(ctx, opts) {
|
|
3070
|
+
const { task, label, model, models, parent, signal } = opts;
|
|
3071
|
+
requireWhitelistedModel(model, models);
|
|
3072
|
+
const subagents = hostSubagents(ctx);
|
|
3073
|
+
if (!subagents?.start) throw new Error("handyman foreground: host subagents service unavailable (ctx.subagents.start missing) — use mode=\"background\" (does not need this service) or check the host-contract report");
|
|
3074
|
+
if (!parent) throw new Error("handyman foreground: requires a calling agent (exec.agent was undefined)");
|
|
3075
|
+
const { provider, model: modelName } = splitModel(model);
|
|
3076
|
+
const run = await subagents.start("spawn", {
|
|
3077
|
+
...label === void 0 ? {} : { label },
|
|
3078
|
+
prompt: [{
|
|
3079
|
+
type: "text",
|
|
3080
|
+
text: task
|
|
3081
|
+
}],
|
|
3082
|
+
parent,
|
|
3083
|
+
signal: signal ?? new AbortController().signal,
|
|
3084
|
+
agentOptions: {
|
|
3085
|
+
...provider === void 0 ? {} : { provider },
|
|
3086
|
+
model: modelName
|
|
3087
|
+
},
|
|
3088
|
+
toolFilter: { deny: ["handyman"] }
|
|
3089
|
+
});
|
|
3090
|
+
try {
|
|
3091
|
+
const result = run.result === void 0 ? {} : await run.result;
|
|
3092
|
+
const stopReason = typeof result.stopReason === "string" ? result.stopReason : "error";
|
|
3093
|
+
return {
|
|
3094
|
+
mode: "foreground",
|
|
3095
|
+
done: stopReason === "completed",
|
|
3096
|
+
model,
|
|
3097
|
+
stopReason,
|
|
3098
|
+
output: subagentOutputText(result.output),
|
|
3099
|
+
...typeof run.id === "string" ? { childId: run.id } : {},
|
|
3100
|
+
...typeof result.diagnostic === "string" && result.diagnostic !== "" ? { diagnostic: result.diagnostic } : {}
|
|
3101
|
+
};
|
|
3102
|
+
} finally {
|
|
3103
|
+
if (run.dispose) await run.dispose().catch(() => {});
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3034
3106
|
/**
|
|
3035
3107
|
* 驱动单个 handyman worker:创建 agent → while 循环(stop-token 完成判据)→ dispose。
|
|
3036
3108
|
* 同步阻塞直到完成/保险阀。jobs 编排时多个 worker 并行(Promise.all)。
|
|
@@ -3160,15 +3232,19 @@ async function runHandymanJob(ctx, opts) {
|
|
|
3160
3232
|
function createHandymanTool(ctx) {
|
|
3161
3233
|
return defineTool({
|
|
3162
3234
|
name: "handyman",
|
|
3163
|
-
description: "The handyman (杂工): delegate a
|
|
3235
|
+
description: "The handyman (杂工): delegate work to a worker agent on a CCC-configured model. Two modes (v1.31.3).\nMode \"foreground\" (DEFAULT): one serial child agent runs the task once and returns its final text — no loop, no completion-code validation, no progress file; use it for one-shot serial delegation (e.g. per-case testers that just gather evidence). Usage: handyman(task, [label], [model]).\nMode \"background\": the loop-validated worker — internal hard while-loop whose ONLY completion condition is the worker echoing this round's random completion code (stop token, prevents low-intelligence models from finishing early), round cap (default 100, resumable), automatic restart on abnormal stop (≤100), progress file AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes), and parallel jobs (handyman.maxParallel, default 10). Usage: handyman(mode=\"background\", task, label, [model]) or handyman(mode=\"background\", jobs=[{task,label,model?},...]).\nChoosing: need exactly one result back now → foreground; need anti-early-finish guarantees, resumability or parallel jobs → background.\nModel: only models whitelisted in .opencode/serenity.json \"handyman.models\" (missing config = error); default reads handyman.defaultModel. Both modes share the same whitelist and default.\nRecursion: a worker's tool set excludes handyman itself (orchestration belongs to the main agent).\nGuide: handyman(guide=true) prints the scale-up usage guide.",
|
|
3164
3236
|
parameters: {
|
|
3237
|
+
mode: {
|
|
3238
|
+
type: "string",
|
|
3239
|
+
description: "Delegation mode: \"foreground\" (default — one serial child, returns its final text) or \"background\" (loop-validated worker with completion-code check, round cap, auto-restart, progress file, parallel jobs)"
|
|
3240
|
+
},
|
|
3165
3241
|
task: {
|
|
3166
3242
|
type: "string",
|
|
3167
|
-
description: "The task goal to complete (required
|
|
3243
|
+
description: "The task goal to complete (required in both modes; foreground needs a self-contained prompt — the child does not share this conversation)"
|
|
3168
3244
|
},
|
|
3169
3245
|
label: {
|
|
3170
3246
|
type: "string",
|
|
3171
|
-
description: "Task label (1-50 chars
|
|
3247
|
+
description: "Task label (background: 1-50 chars, names the progress file; foreground: optional child display name)"
|
|
3172
3248
|
},
|
|
3173
3249
|
session: {
|
|
3174
3250
|
type: "string",
|
|
@@ -3206,6 +3282,29 @@ function createHandymanTool(ctx) {
|
|
|
3206
3282
|
if (!root) throw new Error("No CCC found: no .serenity file from agent cwd");
|
|
3207
3283
|
const hc = readHandymanConfig(root, DEFAULT_SERENITY_CONFIG_PATHS);
|
|
3208
3284
|
if (hc === null) throw new Error("handyman requires a model whitelist: configure .opencode/serenity.json \"handyman.models\" (e.g. {\"handyman\": {\"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\"}})");
|
|
3285
|
+
const mode = args.mode === "background" ? "background" : "foreground";
|
|
3286
|
+
if (args.mode !== void 0 && args.mode !== "background" && args.mode !== "foreground") throw new Error("handyman: mode must be \"foreground\" (default) or \"background\"");
|
|
3287
|
+
if (mode === "foreground") {
|
|
3288
|
+
if (typeof args.task !== "string" || args.task.trim() === "") throw new Error("handyman foreground: task is required (and must be self-contained — the child does not share this conversation)");
|
|
3289
|
+
if (args.jobs !== void 0) throw new Error("handyman foreground: jobs is background-only — call foreground once per task, or use mode=\"background\" for parallel jobs");
|
|
3290
|
+
const model = typeof args.model === "string" && args.model !== "" ? args.model : hc.defaultModel;
|
|
3291
|
+
const res = await runForegroundJob(ctx, {
|
|
3292
|
+
task: args.task,
|
|
3293
|
+
...typeof args.label === "string" && args.label !== "" ? { label: args.label } : {},
|
|
3294
|
+
model,
|
|
3295
|
+
models: hc.models,
|
|
3296
|
+
parent: exec.agent,
|
|
3297
|
+
signal: exec.signal
|
|
3298
|
+
});
|
|
3299
|
+
return {
|
|
3300
|
+
...res,
|
|
3301
|
+
usage: {
|
|
3302
|
+
how: "foreground mode starts ONE serial child agent through the host delegation service (ctx.subagents.start \"spawn\") with the CCC-configured model; it awaits the run and returns the child's final text. No loop, no completion-code validation, no progress file.",
|
|
3303
|
+
model: "restricted to the CCC whitelist handyman.models; default reads handyman.defaultModel",
|
|
3304
|
+
next: res.done ? "Child completed; use its output directly" : `Child did not complete (stopReason=${res.stopReason}); inspect diagnostic/output and decide whether to retry, switch model, or use mode="background"`
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3209
3308
|
const jobs = args.jobs !== void 0 ? (() => {
|
|
3210
3309
|
const parsed = parseJobs(args.jobs);
|
|
3211
3310
|
if (parsed === null) throw new Error("handyman: jobs must be [{task, label, model?}, ...] (every job requires task and label)");
|
|
@@ -5899,7 +5998,7 @@ function toolsBlock() {
|
|
|
5899
5998
|
" container_git— git operations (status/commit/push/log)",
|
|
5900
5999
|
" msm — execute a registered CCC MSM: msm(name, args); partial name returns candidates; inspect=true shows usage",
|
|
5901
6000
|
" praxis — actionable theory injection: praxis (index) / praxis eap / praxis neat / praxis cce",
|
|
5902
|
-
" handyman — delegate a
|
|
6001
|
+
" handyman — delegate work to a worker agent on a CCC-whitelisted model; mode=\"foreground\" (default) = one serial child returns its final text; mode=\"background\" = loop-validated worker (completion code + round cap + restart + progress file), jobs=[] orchestrates parallel work",
|
|
5903
6002
|
" localstore — ACC local credential/config storage (CCC-root localstore.json; git policy localstore.gitTrack default deny)",
|
|
5904
6003
|
" container_admin — container administration (the maintenance bay): role (Skiff roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config",
|
|
5905
6004
|
" autopilot-trajectory — Autopilot Trajectory one-stop management (all/init/random/diag/doc/check/status/guide)",
|
|
@@ -6900,7 +6999,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6900
6999
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6901
7000
|
});
|
|
6902
7001
|
const root = findSerenityRoot(workspace) ?? "";
|
|
6903
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
7002
|
+
const { discoverCccs } = await import("./skiff-debug-sdGryydE.js").then((n) => n.i);
|
|
6904
7003
|
sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
6905
7004
|
} catch (err) {
|
|
6906
7005
|
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
@@ -6930,7 +7029,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6930
7029
|
return;
|
|
6931
7030
|
}
|
|
6932
7031
|
const settings = readAdvancedSettings();
|
|
6933
|
-
const { readSimpleSettings } = await import("./settings-section-
|
|
7032
|
+
const { readSimpleSettings } = await import("./settings-section-BxoNXUqk.js").then((n) => n.r);
|
|
6934
7033
|
const simple = readSimpleSettings();
|
|
6935
7034
|
const allowed = settings.publicAsk.allowed;
|
|
6936
7035
|
const port = simple.acpHttpPort ?? 3100;
|
|
@@ -10505,7 +10604,7 @@ function matchCcc(input, candidates) {
|
|
|
10505
10604
|
}
|
|
10506
10605
|
/** 组装候选列表(discoverCccs 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
10507
10606
|
async function collectCandidates(ctx) {
|
|
10508
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
10607
|
+
const { discoverCccs } = await import("./skiff-debug-sdGryydE.js").then((n) => n.i);
|
|
10509
10608
|
const entries = await discoverCccs(ctx, process.cwd());
|
|
10510
10609
|
const seen = /* @__PURE__ */ new Set();
|
|
10511
10610
|
const out = [];
|
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 / sessionKeeper.sessionMdMaxKB \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, \"sessionMdMaxKB\": 200 } }\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 \u25B8 sessionMdMaxKB\uFF08v1.31.1 \u65B0\u589E\uFF0Cv1.31.2 \u7F3A\u7701 100\u2192200\uFF09\uFF1A**\u6D3B\u8DC3 SESSION.md \u4F53\u79EF\u4E0A\u9650\uFF08KB\uFF0C\u7F3A\u7701 200\uFF1B0 = \u5173\u95ED\uFF09**\u3002\n \u8D85\u8FC7 \u2192 \u6CE8\u5165 [TRAJECTORY-ASSISTANT \u00B7 LOGBOOK COMPACTION] \u63D0\u9192\uFF1A\u6682\u505C\u5F53\u524D\u5DE5\u4F5C \u2192\n \u52A0\u8F7D eap\uFF08praxis eap\uFF09\u2192 \u6309 EAP \u5206\u5C42\u9AA8\u67B6\u91CD\u5199 SESSION.md\u3002\u56DB\u6761\u539F\u5219\uFF08ACC \u5185\u5D4C\uFF0C\u968F\u63D0\u9192\u7ED9\u51FA\uFF09\uFF1A\n \u2460 \u4FDD\u7559 EAP \u5206\u5C42\u9AA8\u67B6\uFF08\u4E0D\u538B\u6210\u65F6\u95F4\u6D41\u6C34\u8D26\uFF09\u2461 \u5185\u5BB9\u53EF\u5916\u79FB\u5230 references \u6587\u4EF6\n \uFF08AGENT_SESSIONS/<\u4F1A\u8BDD>/references/*.md\uFF0CSESSION.md \u7559\u94FE\u63A5\uFF09\u2462 \u5141\u8BB8\u6574\u5408/\u5220\u9664\u4E0D\u91CD\u8981\u4E8B\u9879\n \uFF08\u88AB\u53D6\u4EE3\u7684\u51B3\u7B56\u3001\u5DF2\u89E3\u51B3\u95EE\u9898\u3001\u4E2D\u95F4\u6001\uFF09\u2463 \u81EA\u4E3B\u88C1\u91CF\u6743\u5145\u5206\u5141\u8BB8\uFF08\u552F\u4E00\u786C\u8981\u6C42 = \u9AA8\u67B6 +\n \u672A\u51B3\u9879/\u51B3\u7B56\u7406\u7531/\u4E0B\u4E00\u6B65\u4ECD\u53EF\u91CD\u5EFA\uFF09\u3002\u8D85\u9650\u6BCF\u8F6E\u63D0\u9192\uFF1B\u8FDE\u7EED 3 \u8F6E\u672A\u91CD\u5199 \u2192 \u5347\u7EA7\u5F3A\u5236\u8BED\u6C14\uFF1B\n \u6587\u4EF6\u56DE\u5230\u9650\u5185 \u2192 \u81EA\u52A8\u505C\u6B62\uFF08\u81EA\u6108\uFF09\u3002\u8F68\u8FF9\u63D0\u9192**\u4E0D\u673A\u68B0\u963B\u65AD\u5DE5\u4F5C**\uFF08\u4E0E rebuild \u63D0\u9192\u540C\u65CF\uFF09\u3002\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.31.0\uFF1A**ACC \u5DE5\u5177 im-bridge**\uFF09\uFF1A\u8C03\n im-bridge({channel:\"weixin\", action:\"send\", user:\"<\u522B\u540D|id>\", text:\"<\u6587\u672C>\"}) \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\n **\u5DE5\u5177\u53EA\u80FD\u64CD\u4F5C\u672C\u4F1A\u8BDD CCC**\uFF08\u65E0 ccc \u53C2\u6570\uFF0C\u9632\u8BEF\u53D1\u4ED6\u5BB9\u5668\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002\u6587\u4EF6\u7528 action:\"send-file\" + file:\"<CCC \u5185\u8DEF\u5F84>\"\n \uFF08\u9003\u9038/\u7F3A\u5931/\u8D85 20MB \u62D2\u7EDD\uFF09+ \u53EF\u9009 caption\u3002**\u53EF\u89C1\u6027**\uFF1A\u672C CCC \u914D\u7F6E\u4E86 IM \u901A\u9053\n \uFF08weixin.enabled=true\uFF09\u624D\u51FA\u73B0\u5728\u6A21\u578B\u5DE5\u5177\u6E05\u5355\u91CC\uFF1B\u672A\u914D\u7F6E \u2192 \u7531 guards \u9010 agent\n tools.restrict({deny:['im-bridge']}) \u79FB\u9664\uFF08\u4E0D\u662F\"\u770B\u5F97\u5230\u4F46\u88AB\u62D2\"\uFF09\u3002skiff \u89D2\u8272\u53E6\u9700\u5728\n roles.<role>.tools \u767D\u540D\u5355\u5217\u51FA im-bridge\u3002\n \u5386\u53F2\uFF08v1.30.9~v1.30.x\uFF09\uFF1ACCC \u4FA7 MSM weixin-send\uFF08\u8D70 loopback HTTP \u5165\u53E3 3082\uFF09\u2014\u2014\n v1.31.0 \u5DF2\u9000\u5F79\uFF08\u811A\u672C\u5220\u9664 + \u6CE8\u518C\u8868\u6CE8\u9500\uFF09\uFF1B3082 \u5165\u53E3\u4FDD\u7559\u7ED9**\u975E agent \u8C03\u7528\u8005**\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 im-bridge \u8C03\u7528\n \uFF08\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 im-bridge\uFF08send / send-file\uFF1B\u517C\u5BB9\u65E7\n msm(\"weixin-send\") \u5F62\u6001\uFF09\u2192 agent \u88AB\u6253\u56DE\uFF08\u22642 \u6B21\uFF0C\u63D0\u793A = \u6807\u8BB0 + \u4E8B\u5B9E\uFF09\uFF1B\u8FBE\u4E0A\u9650\u653E\u5F03\u5E76\u54CD\u4EAE\u544A\u8B66\u3002\n \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 typing \u6307\u793A\u4E0E incoming hook \u4E0D\u53D7\u5F71\u54CD\u3002\n\n \u25B8 weixin.fallbackOnNoSend\uFF08v1.30.17\uFF0C\u624B\u52A8\u6A21\u5F0F\u7684**\u515C\u5E95**\uFF1B\u7F3A\u7701 false\uFF09\uFF1A\n \u4EC5\u5728 autoReplyWithLastMessage=false \u65F6\u751F\u6548\u3002true \u2192 \u4E00\u8F6E\u7ED3\u675F\u65F6\u82E5 agent **\u4E00\u6B21\u90FD\u6CA1\u6210\u529F**\n \u8C03\u7528\u8F93\u51FA\u5DE5\u5177\uFF08\u95F8\u95E8\u6253\u56DE\u7528\u5C3D\uFF09\uFF0C\u6865\u628A\u8BE5\u8F6E\u6700\u7EC8\u6587\u672C\u8F6C\u53D1\u7ED9\u7528\u6237\uFF08\u8BB0\u5F55 source=\"reply-fallback\"\uFF0C\n \u65E5\u5FD7\u7559\u54CD\u4EAE\u544A\u8B66\uFF09\u2014\u2014\u4FDD\u8BC1\"\u4E0D\u4E22\u6D88\u606F\"\u3002agent \u81EA\u5DF1\u53D1\u8FC7 \u2192 \u4E0D\u515C\u5E95\uFF08\u8F93\u51FA\u6743\u4ECD\u5728 agent\uFF09\u3002\n \u4E3A\u4EC0\u4E48\u9700\u8981\uFF08\u5B9E\u8BC1\uFF09\uFF1Av1.30.16 \u95F8\u95E8\u4F1A\u6253\u56DE \u22642 \u6B21\uFF0C\u4F46\u5B9E\u6D4B\u67D0\u6A21\u578B\u5728**\u5BD2\u6684\u7C7B\u6D88\u606F**\u4E0A\u8FDE\u7EED 4 \u8F6E\u3001\n \u8DE8 3 \u7248 CCC \u63D0\u793A\u8BCD\u4ECD\u4E0D\u8C03\u5DE5\u5177 \u2192 \u6253\u56DE\u7528\u5C3D\u540E\u7528\u6237\u4EC0\u4E48\u90FD\u6536\u4E0D\u5230\uFF1B\u63D0\u793A\u8BCD\u4E0E\u95F8\u95E8\u5747\u5DF2\u6392\u9664\u3002\n \u4EE3\u4EF7\uFF08\u663E\u5F0F\uFF09\uFF1A\u89D2\u8272\u5931\u53BB\"\u6545\u610F\u9759\u9ED8\"\u80FD\u529B\uFF08\u5B83\u603B\u4F1A\u4EA7\u51FA\u4E00\u6BB5\u6700\u7EC8\u6587\u672C\uFF09\u2192 \u9700\u4E25\u683C\u9759\u9ED8\u7684\u89D2\u8272\u4FDD\u6301\u7F3A\u7701\u3002\n \u4E09\u6001\uFF1A\u2460 autoReplyWithLastMessage=true\uFF08\u7F3A\u7701\uFF09\u6865\u603B\u662F\u56DE\u53D1\uFF1B\u2461 =false + fallbackOnNoSend=true\n = agent \u4F18\u5148\u3001\u6865\u515C\u5E95\uFF08\u63A8\u8350\uFF09\uFF1B\u2462 \u4E24\u8005\u7686 false\uFF08\u7F3A\u7701\uFF09= \u4E25\u683C\u9759\u9ED8\uFF08v1.30.10 \u8BED\u4E49\uFF09\u3002\n \u26A0\uFE0F \u515C\u5E95\u53EA\u5728**\u95F8\u95E8\u88C5\u914D\u6210\u529F**\u65F6\u751F\u6548\uFF08\u672A\u88C5\u914D\u5219\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u53D1\u9001\u8FC7 \u2192 \u4E0D\u5192\u91CD\u590D\u53D1\u9001\u98CE\u9669\uFF09\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"autoReplyWithLastMessage\": true,\n \"fallbackOnNoSend\": false,\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 im-bridge \u5DE5\u5177\uFF08v1.31.0\uFF1B\u65E7 weixin-send MSM \u5DF2\u9000\u5F79\uFF09\u2192 sendProactiveText\uFF09\uFF1B\n source=\"reply-fallback\"\uFF08v1.30.17\uFF09= \u624B\u52A8\u6A21\u5F0F\u4E0B agent \u672C\u8F6E\u672A\u53D1\u9001 \u2192 \u6865\u515C\u5E95\u8F6C\u53D1\u6700\u7EC8\u6587\u672C\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\" | \"reply-fallback\" }\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**CCC \u5E94\u628A\u8BE5\u767D\u540D\u5355\u6307\u5411\u4F4E\u6210\u672C\u6A21\u578B**\u2014\u2014handyman \u662F\u6279\u91CF\u6267\u884C\u901A\u9053\uFF08v1.31.3 \u53CC\u6A21\u5F0F\u5171\u7528\u540C\u4E00\u767D\u540D\u5355\uFF09\u3002\n\n Modes\uFF08v1.31.3\uFF0C\u7F3A\u7701 foreground\uFF09:\n mode=\"foreground\"\uFF08\u7F3A\u7701\uFF09\uFF1A\u4E00\u6B21\u524D\u53F0\u4E32\u884C\u59D4\u6D3E\uFF08\u5BBF\u4E3B\u59D4\u6D3E\u670D\u52A1 ctx.subagents.start(\"spawn\") +\n agentOptions \u6CE8\u5165\u6A21\u578B\uFF09\u2014\u2014\u4E0D\u5FAA\u73AF\u3001\u4E0D\u6821\u9A8C\u5B8C\u6210\u7801\u3001\u4E0D\u5199\u8FDB\u5EA6\u6587\u4EF6\uFF1B\u8FD4\u56DE\u5B50 agent \u6700\u7EC8\u6587\u672C\u3002\n \u9002\u5408\"\u4E00\u6B21\u8C03\u7528\u4E00\u6B21\u7ED3\u679C\"\uFF08\u9010\u7528\u4F8B\u56DE\u5F52\u3001\u5355\u6B21\u8F6C\u6362\uFF09\u3002\n mode=\"background\"\uFF1A\u65E2\u6709\u5FAA\u73AF\u6821\u9A8C\u5B9E\u73B0\uFF08\u5185\u90E8 while \u786C\u5FAA\u73AF + \u968F\u673A\u5B8C\u6210\u7801\u552F\u4E00\u5224\u636E + \u8F6E\u6B21\u4E0A\u9650\n \u9ED8\u8BA4 100 \u53EF\u7EED\u8DD1 + \u5F02\u5E38\u81EA\u52A8\u91CD\u542F \u2264100 + \u8FDB\u5EA6\u6587\u4EF6 AGENT_SESSIONS/handyman-<label>.md/.json +\n jobs \u5E76\u884C maxParallel \u9ED8\u8BA4 10\uFF09\u3002\u9002\u5408\u957F\u4EFB\u52A1/\u65E0\u4EBA\u503C\u5B88/\u9700\u6297\u63D0\u524D\u6536\u5DE5\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 / sessionKeeper.sessionMdMaxKB \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, \"sessionMdMaxKB\": 200 } }\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 \u25B8 sessionMdMaxKB\uFF08v1.31.1 \u65B0\u589E\uFF0Cv1.31.2 \u7F3A\u7701 100\u2192200\uFF09\uFF1A**\u6D3B\u8DC3 SESSION.md \u4F53\u79EF\u4E0A\u9650\uFF08KB\uFF0C\u7F3A\u7701 200\uFF1B0 = \u5173\u95ED\uFF09**\u3002\n \u8D85\u8FC7 \u2192 \u6CE8\u5165 [TRAJECTORY-ASSISTANT \u00B7 LOGBOOK COMPACTION] \u63D0\u9192\uFF1A\u6682\u505C\u5F53\u524D\u5DE5\u4F5C \u2192\n \u52A0\u8F7D eap\uFF08praxis eap\uFF09\u2192 \u6309 EAP \u5206\u5C42\u9AA8\u67B6\u91CD\u5199 SESSION.md\u3002\u56DB\u6761\u539F\u5219\uFF08ACC \u5185\u5D4C\uFF0C\u968F\u63D0\u9192\u7ED9\u51FA\uFF09\uFF1A\n \u2460 \u4FDD\u7559 EAP \u5206\u5C42\u9AA8\u67B6\uFF08\u4E0D\u538B\u6210\u65F6\u95F4\u6D41\u6C34\u8D26\uFF09\u2461 \u5185\u5BB9\u53EF\u5916\u79FB\u5230 references \u6587\u4EF6\n \uFF08AGENT_SESSIONS/<\u4F1A\u8BDD>/references/*.md\uFF0CSESSION.md \u7559\u94FE\u63A5\uFF09\u2462 \u5141\u8BB8\u6574\u5408/\u5220\u9664\u4E0D\u91CD\u8981\u4E8B\u9879\n \uFF08\u88AB\u53D6\u4EE3\u7684\u51B3\u7B56\u3001\u5DF2\u89E3\u51B3\u95EE\u9898\u3001\u4E2D\u95F4\u6001\uFF09\u2463 \u81EA\u4E3B\u88C1\u91CF\u6743\u5145\u5206\u5141\u8BB8\uFF08\u552F\u4E00\u786C\u8981\u6C42 = \u9AA8\u67B6 +\n \u672A\u51B3\u9879/\u51B3\u7B56\u7406\u7531/\u4E0B\u4E00\u6B65\u4ECD\u53EF\u91CD\u5EFA\uFF09\u3002\u8D85\u9650\u6BCF\u8F6E\u63D0\u9192\uFF1B\u8FDE\u7EED 3 \u8F6E\u672A\u91CD\u5199 \u2192 \u5347\u7EA7\u5F3A\u5236\u8BED\u6C14\uFF1B\n \u6587\u4EF6\u56DE\u5230\u9650\u5185 \u2192 \u81EA\u52A8\u505C\u6B62\uFF08\u81EA\u6108\uFF09\u3002\u8F68\u8FF9\u63D0\u9192**\u4E0D\u673A\u68B0\u963B\u65AD\u5DE5\u4F5C**\uFF08\u4E0E rebuild \u63D0\u9192\u540C\u65CF\uFF09\u3002\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.31.0\uFF1A**ACC \u5DE5\u5177 im-bridge**\uFF09\uFF1A\u8C03\n im-bridge({channel:\"weixin\", action:\"send\", user:\"<\u522B\u540D|id>\", text:\"<\u6587\u672C>\"}) \u7ED9\u7528\u6237\u53D1\u6D88\u606F\u2014\u2014\n **\u5DE5\u5177\u53EA\u80FD\u64CD\u4F5C\u672C\u4F1A\u8BDD CCC**\uFF08\u65E0 ccc \u53C2\u6570\uFF0C\u9632\u8BEF\u53D1\u4ED6\u5BB9\u5668\uFF09\u2192 weixin-bridge.sendProactiveText \u2192\n \u6210\u529F\u540E\u89E6\u53D1 outgoing hook\uFF08source=\"proactive\"\uFF09\u3002\u6587\u4EF6\u7528 action:\"send-file\" + file:\"<CCC \u5185\u8DEF\u5F84>\"\n \uFF08\u9003\u9038/\u7F3A\u5931/\u8D85 20MB \u62D2\u7EDD\uFF09+ \u53EF\u9009 caption\u3002**\u53EF\u89C1\u6027**\uFF1A\u672C CCC \u914D\u7F6E\u4E86 IM \u901A\u9053\n \uFF08weixin.enabled=true\uFF09\u624D\u51FA\u73B0\u5728\u6A21\u578B\u5DE5\u5177\u6E05\u5355\u91CC\uFF1B\u672A\u914D\u7F6E \u2192 \u7531 guards \u9010 agent\n tools.restrict({deny:['im-bridge']}) \u79FB\u9664\uFF08\u4E0D\u662F\"\u770B\u5F97\u5230\u4F46\u88AB\u62D2\"\uFF09\u3002skiff \u89D2\u8272\u53E6\u9700\u5728\n roles.<role>.tools \u767D\u540D\u5355\u5217\u51FA im-bridge\u3002\n \u5386\u53F2\uFF08v1.30.9~v1.30.x\uFF09\uFF1ACCC \u4FA7 MSM weixin-send\uFF08\u8D70 loopback HTTP \u5165\u53E3 3082\uFF09\u2014\u2014\n v1.31.0 \u5DF2\u9000\u5F79\uFF08\u811A\u672C\u5220\u9664 + \u6CE8\u518C\u8868\u6CE8\u9500\uFF09\uFF1B3082 \u5165\u53E3\u4FDD\u7559\u7ED9**\u975E agent \u8C03\u7528\u8005**\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 im-bridge \u8C03\u7528\n \uFF08\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 im-bridge\uFF08send / send-file\uFF1B\u517C\u5BB9\u65E7\n msm(\"weixin-send\") \u5F62\u6001\uFF09\u2192 agent \u88AB\u6253\u56DE\uFF08\u22642 \u6B21\uFF0C\u63D0\u793A = \u6807\u8BB0 + \u4E8B\u5B9E\uFF09\uFF1B\u8FBE\u4E0A\u9650\u653E\u5F03\u5E76\u54CD\u4EAE\u544A\u8B66\u3002\n \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 typing \u6307\u793A\u4E0E incoming hook \u4E0D\u53D7\u5F71\u54CD\u3002\n\n \u25B8 weixin.fallbackOnNoSend\uFF08v1.30.17\uFF0C\u624B\u52A8\u6A21\u5F0F\u7684**\u515C\u5E95**\uFF1B\u7F3A\u7701 false\uFF09\uFF1A\n \u4EC5\u5728 autoReplyWithLastMessage=false \u65F6\u751F\u6548\u3002true \u2192 \u4E00\u8F6E\u7ED3\u675F\u65F6\u82E5 agent **\u4E00\u6B21\u90FD\u6CA1\u6210\u529F**\n \u8C03\u7528\u8F93\u51FA\u5DE5\u5177\uFF08\u95F8\u95E8\u6253\u56DE\u7528\u5C3D\uFF09\uFF0C\u6865\u628A\u8BE5\u8F6E\u6700\u7EC8\u6587\u672C\u8F6C\u53D1\u7ED9\u7528\u6237\uFF08\u8BB0\u5F55 source=\"reply-fallback\"\uFF0C\n \u65E5\u5FD7\u7559\u54CD\u4EAE\u544A\u8B66\uFF09\u2014\u2014\u4FDD\u8BC1\"\u4E0D\u4E22\u6D88\u606F\"\u3002agent \u81EA\u5DF1\u53D1\u8FC7 \u2192 \u4E0D\u515C\u5E95\uFF08\u8F93\u51FA\u6743\u4ECD\u5728 agent\uFF09\u3002\n \u4E3A\u4EC0\u4E48\u9700\u8981\uFF08\u5B9E\u8BC1\uFF09\uFF1Av1.30.16 \u95F8\u95E8\u4F1A\u6253\u56DE \u22642 \u6B21\uFF0C\u4F46\u5B9E\u6D4B\u67D0\u6A21\u578B\u5728**\u5BD2\u6684\u7C7B\u6D88\u606F**\u4E0A\u8FDE\u7EED 4 \u8F6E\u3001\n \u8DE8 3 \u7248 CCC \u63D0\u793A\u8BCD\u4ECD\u4E0D\u8C03\u5DE5\u5177 \u2192 \u6253\u56DE\u7528\u5C3D\u540E\u7528\u6237\u4EC0\u4E48\u90FD\u6536\u4E0D\u5230\uFF1B\u63D0\u793A\u8BCD\u4E0E\u95F8\u95E8\u5747\u5DF2\u6392\u9664\u3002\n \u4EE3\u4EF7\uFF08\u663E\u5F0F\uFF09\uFF1A\u89D2\u8272\u5931\u53BB\"\u6545\u610F\u9759\u9ED8\"\u80FD\u529B\uFF08\u5B83\u603B\u4F1A\u4EA7\u51FA\u4E00\u6BB5\u6700\u7EC8\u6587\u672C\uFF09\u2192 \u9700\u4E25\u683C\u9759\u9ED8\u7684\u89D2\u8272\u4FDD\u6301\u7F3A\u7701\u3002\n \u4E09\u6001\uFF1A\u2460 autoReplyWithLastMessage=true\uFF08\u7F3A\u7701\uFF09\u6865\u603B\u662F\u56DE\u53D1\uFF1B\u2461 =false + fallbackOnNoSend=true\n = agent \u4F18\u5148\u3001\u6865\u515C\u5E95\uFF08\u63A8\u8350\uFF09\uFF1B\u2462 \u4E24\u8005\u7686 false\uFF08\u7F3A\u7701\uFF09= \u4E25\u683C\u9759\u9ED8\uFF08v1.30.10 \u8BED\u4E49\uFF09\u3002\n \u26A0\uFE0F \u515C\u5E95\u53EA\u5728**\u95F8\u95E8\u88C5\u914D\u6210\u529F**\u65F6\u751F\u6548\uFF08\u672A\u88C5\u914D\u5219\u65E0\u6CD5\u5224\u5B9A\u662F\u5426\u53D1\u9001\u8FC7 \u2192 \u4E0D\u5192\u91CD\u590D\u53D1\u9001\u98CE\u9669\uFF09\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\n \"autoReplyWithLastMessage\": true,\n \"fallbackOnNoSend\": false,\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 im-bridge \u5DE5\u5177\uFF08v1.31.0\uFF1B\u65E7 weixin-send MSM \u5DF2\u9000\u5F79\uFF09\u2192 sendProactiveText\uFF09\uFF1B\n source=\"reply-fallback\"\uFF08v1.30.17\uFF09= \u624B\u52A8\u6A21\u5F0F\u4E0B agent \u672C\u8F6E\u672A\u53D1\u9001 \u2192 \u6865\u515C\u5E95\u8F6C\u53D1\u6700\u7EC8\u6587\u672C\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\" | \"reply-fallback\" }\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,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { a as hostSettings } from "./access-
|
|
2
|
+
import { a as hostSettings } from "./access-BqtkTl9M.js";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
//#region src/settings-section.ts
|
|
5
5
|
var settings_section_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1,7 +1,7 @@
|
|
|
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
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
|
-
import { i as hostSessions, r as hostService, t as hostAgents } from "./access-
|
|
4
|
+
import { i as hostSessions, r as hostService, t as hostAgents } from "./access-BqtkTl9M.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";
|
|
7
7
|
import { basename, join } from "node:path";
|
|
@@ -153,15 +153,27 @@ If the task is complete, output only ${stopToken}.`;
|
|
|
153
153
|
*/
|
|
154
154
|
const HANDYMAN_GUIDE = `# handyman — Scale-Up Usage Guide (guide)
|
|
155
155
|
|
|
156
|
+
## Two modes (v1.31.3; default = foreground)
|
|
157
|
+
| | foreground (default) | background |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| What | one serial child agent runs the task ONCE and returns its final text | loop-validated worker: hard while-loop + random completion code |
|
|
160
|
+
| Use when | you need exactly one result back now (per-case testers, single conversions) | long/unattended work needing anti-early-finish, resumability, or parallel jobs |
|
|
161
|
+
| Loop / validation | none | stop-token is the ONLY completion condition; round cap (default 100); auto-restart (≤100) |
|
|
162
|
+
| Progress file | none | AGENT_SESSIONS/handyman-<label>.md/.json (same label resumes) |
|
|
163
|
+
| Parallel jobs | no (call once per task) | yes (jobs=[...], handyman.maxParallel default 10) |
|
|
164
|
+
| Mechanism | host delegation service ctx.subagents.start("spawn") + agentOptions | ctx.agents.create + internal while-loop |
|
|
165
|
+
| Model source | CCC handyman.models whitelist (same for both) | same |
|
|
166
|
+
|
|
156
167
|
## ⚠️ Before using: load eap and design the plan
|
|
157
168
|
Before calling handyman, load eap (acc-eap skill) and design the "scale-up handyman plan" based on the EAP framework.
|
|
158
169
|
|
|
159
170
|
### 1. Task decomposition (E↑ Explicit)
|
|
160
171
|
- Split large tasks into explicit subtasks: each subtask defines goal / input / boundaries (what to do, what not to do) / acceptance criteria
|
|
161
|
-
- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel
|
|
172
|
+
- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel (background mode)
|
|
162
173
|
|
|
163
174
|
### 2. Prompt design (handyman's task parameter)
|
|
164
175
|
- task must be detailed, fixed, and EAP-compliant: clear goal, drawn boundaries, decidable acceptance criteria
|
|
176
|
+
- foreground mode: the child does NOT share your conversation — restate everything it needs in task
|
|
165
177
|
- Anti-example "handle this file" — ambiguous; good example "read <path>, extract all rows of the「关键决策」table,
|
|
166
178
|
output a JSON array (fields id/conclusion/evidence), do not modify the original file"
|
|
167
179
|
- Reading/curating or text-writing work (extracting from files, summarizing, writing docs, generating text, etc.):
|
|
@@ -169,23 +181,27 @@ Before calling handyman, load eap (acc-eap skill) and design the "scale-up handy
|
|
|
169
181
|
|
|
170
182
|
### 3. Model whitelist (CCC-configured, mandatory)
|
|
171
183
|
- handyman only uses models listed in .opencode/serenity.json "handyman.models" — never arbitrary models
|
|
172
|
-
-
|
|
173
|
-
-
|
|
184
|
+
- The CCC is expected to point this whitelist at LOW-COST models: handyman is the bulk-execution channel
|
|
185
|
+
- background mode: recursive subagents inside a worker inherit the worker's model automatically (DSH native)
|
|
186
|
+
- foreground mode: the child is started through the host delegation service with the resolved model
|
|
174
187
|
|
|
175
|
-
### 4. Parallel strategy (
|
|
176
|
-
- Independent subtasks can run in parallel via handyman(jobs=[...]): each job gets its own label + task + stop token + progress file
|
|
188
|
+
### 4. Parallel strategy (background mode only)
|
|
189
|
+
- Independent subtasks can run in parallel via handyman(mode="background", jobs=[...]): each job gets its own label + task + stop token + progress file
|
|
177
190
|
- Concurrency safety guaranteed: unique sessionId (handyman-<label>-<uuid>), progress files isolated per label
|
|
178
191
|
(AGENT_SESSIONS/handyman-<label>.json) — same label resumes, different labels never interfere
|
|
179
192
|
- Parallel cap: handyman.maxParallel (default 10 — cheap models are cheap)
|
|
180
193
|
- Aggregation: after each parallel job produces progress, the main agent merges (or spawns one aggregation handyman)
|
|
181
194
|
- For programmable pipeline/phase orchestration at scale, use the platform's workflow tool instead
|
|
182
195
|
|
|
183
|
-
## Completion criteria
|
|
184
|
-
-
|
|
185
|
-
-
|
|
196
|
+
## Completion criteria
|
|
197
|
+
- foreground: the child's run settles; done=true only when stopReason="completed" (otherwise read diagnostic)
|
|
198
|
+
- background (osp loop standard): the only completion condition = the worker echoes this round's random verification
|
|
199
|
+
code (stop token); dialogue round cap (default 100, osp fail-safe, forced stop beyond the cap, resumable);
|
|
200
|
+
automatic restart on abnormal agent stop (≤100 restarts, anti-infinite-loop)
|
|
186
201
|
|
|
187
202
|
## Waiting UI
|
|
188
|
-
- The WebUI session-header Serenity detail card shows running handymen's progress
|
|
203
|
+
- The WebUI session-header Serenity detail card shows running background handymen's progress
|
|
204
|
+
(label / round / last response), one line per parallel job, ~3s refresh (foreground mode writes no progress)
|
|
189
205
|
`;
|
|
190
206
|
/** 列出 AGENT_SESSIONS/handyman-*.json 的全部进度(按 updated 倒序;坏文件跳过) */
|
|
191
207
|
function listActiveHandymen(root) {
|
package/lib/tools/handyman.d.ts
CHANGED
|
@@ -39,5 +39,15 @@ export interface HandymanJobResult {
|
|
|
39
39
|
lastResponse: string;
|
|
40
40
|
progressFile: string;
|
|
41
41
|
}
|
|
42
|
+
/** foreground 模式结果(一次调用一次结果;无循环、无进度文件) */
|
|
43
|
+
export interface HandymanForegroundResult {
|
|
44
|
+
mode: 'foreground';
|
|
45
|
+
done: boolean;
|
|
46
|
+
model: string;
|
|
47
|
+
stopReason: string;
|
|
48
|
+
output: string;
|
|
49
|
+
childId?: string;
|
|
50
|
+
diagnostic?: string;
|
|
51
|
+
}
|
|
42
52
|
/** 创建 handyman 工具(闭包捕获插件 ctx → 可访问 ctx.agents) */
|
|
43
53
|
export declare function createHandymanTool(ctx: Context): ToolDefinition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.3",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|