@shgroup/dsh-serenity-hooks 1.31.13 → 1.32.0
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 +8 -5
- package/dsh.plugin.json +3 -3
- package/experiments/autopilot-trajectory/SKILL.md +23 -21
- package/lib/autopilot-trajectory.d.ts +15 -5
- package/lib/ccc.d.ts +10 -0
- package/lib/client.js +16 -4
- package/lib/index.js +635 -49
- package/lib/invariant.d.ts +1 -1
- package/lib/invariant.js +1 -1
- package/lib/msm-ops.d.ts +2 -2
- package/lib/{session-bound-BpTayaIS.js → session-bound-NHozeLFc.js} +57 -1
- package/lib/session-bound.d.ts +30 -0
- package/lib/{settings-section-01rTVq6v.js → settings-section-Bm-xG-NR.js} +6 -3
- package/lib/settings-section.d.ts +5 -0
- package/lib/{skiff-debug-w2c3UqQk.js → skiff-debug-K2k1JnGj.js} +1 -1
- package/lib/tools/autopilot-trajectory.d.ts +2 -2
- package/lib/wake-registry.d.ts +146 -0
- package/lib/wake-scheduler.d.ts +76 -0
- package/package.json +2 -2
package/lib/invariant.d.ts
CHANGED
|
@@ -32,4 +32,4 @@ export declare function verifyToolConsistency(manifestPath: string, registeredTo
|
|
|
32
32
|
* `verifyToolConsistency` 零问题(**陈旧的双侧一致 = 静默通过**),而代码实际注册 11 个。
|
|
33
33
|
* 教训:工具面变更必须同时改三处(tools/*.ts 注册 / 本常量 / dsh.plugin.json)。
|
|
34
34
|
*/
|
|
35
|
-
export declare const REGISTERED_TOOLS: readonly ["container_fs", "logbook", "dashboard", "container_git", "msm", "praxis", "handyman", "localstore", "container_admin", "
|
|
35
|
+
export declare const REGISTERED_TOOLS: readonly ["container_fs", "logbook", "dashboard", "container_git", "msm", "praxis", "handyman", "localstore", "container_admin", "trajectory", "im-bridge"];
|
package/lib/invariant.js
CHANGED
package/lib/msm-ops.d.ts
CHANGED
|
@@ -16,10 +16,10 @@ export declare const MSM_ACTIONS: readonly MsmAction[];
|
|
|
16
16
|
* (避免 container_admin/weixin-doctor 等 guide 全文重复 → 多真相源 → 失同步)。
|
|
17
17
|
* 后续新增功能只需在此加一行(低熵可演进)。
|
|
18
18
|
*/
|
|
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
|
|
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 trajectory\uFF08\u8F68\u8FF9\u4E00\u7AD9\u5F0F\uFF1Aall/init/random/diag/doc/check/status/guide + \u5524\u9192\u6CE8\u518C\u8868 wake-add/wake-list/wake-rm\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/trajectory/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**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";
|
|
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. trajectory\uFF08\u8F68\u8FF9\uFF1Aautopilot \u81EA\u4E3B\u5DE1\u822A + \u5524\u9192\u6CE8\u518C\u8868\uFF0CD58\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**autopilot \u662F trajectory \u7684\u5B50\u96C6**\uFF1A\u5468\u671F\u81EA\u5524\u9192\u7684\u7279\u4F8B\uFF1Btrajectory \u672C\u8EAB\u662F\u4E00\u7B49\u6982\u5FF5\uFF08\u4E00\u4E2A CCC \u5185\u53EF\u591A\u6761\u5E76\u884C\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\uFF1Atrajectory <all|check|status|diag>\uFF08trajectory({action:\"all\"})\uFF09\u3002\n\u5524\u9192\u6CE8\u518C\u8868\uFF08wake-add/wake-list/wake-rm\uFF09\uFF1A\u5524\u9192 = \u672A\u6765\u65F6\u523B + \u4E00\u6761 message\uFF0C\u53EF\u5524\u9192\u81EA\u5DF1\u6216\u522B\u7684 trajectory\uFF1B\n\u843D\u70B9 AGENT_SESSIONS/wake-registry.json\uFF0C\u4E2D\u5FC3\u8C03\u5EA6\u5668\u5230\u70B9\u6295\u9012\uFF08fire-and-forget\uFF09\u3002\n\n Config:\n { \"trajectory\": {\n \"autopilot\": { // \u539F autopilotTrajectory \u6BB5\uFF0C\u5B57\u6BB5\u4E0D\u53D8\uFF08\u65E7\u952E\u4ECD\u56DE\u9000\u8BFB\uFF09\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;
|
|
@@ -241,6 +241,9 @@ let lastActive = null;
|
|
|
241
241
|
function getActiveSessionInfo(scope) {
|
|
242
242
|
return activeStore.get(scope) ?? null;
|
|
243
243
|
}
|
|
244
|
+
function getLastActiveSessionInfo() {
|
|
245
|
+
return lastActive;
|
|
246
|
+
}
|
|
244
247
|
function setActiveSessionInfo(scope, info) {
|
|
245
248
|
activeStore.set(scope, info);
|
|
246
249
|
lastActive = info;
|
|
@@ -734,6 +737,59 @@ function readLastBound(session) {
|
|
|
734
737
|
}
|
|
735
738
|
return readLegacyBoundFromEvents(session);
|
|
736
739
|
}
|
|
740
|
+
/** 目录名 → 轨迹标签(`--S###--` 命中取 `S###`;未命中原样返回——编码无关 U4) */
|
|
741
|
+
function trajectoryLabel(dirName) {
|
|
742
|
+
const m = dirName.match(/--S(\d{3,})--/);
|
|
743
|
+
return m ? `S${m[1]}` : dirName;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* **调用方自身**所属 trajectory 的标签(审计字段 `createdBy` 的唯一正解)。
|
|
747
|
+
*
|
|
748
|
+
* 三级来源,逐级回退(R↓:为什么不用单一来源):
|
|
749
|
+
* 1. `readLastBound(session)` —— **持久 bound**,权威(重启存活、不受别的会话激活污染)
|
|
750
|
+
* 2. `getActiveSessionInfo(scope)` —— 本 dsh 会话的**内存**激活(按 scope 隔离)
|
|
751
|
+
* 3. `getLastActiveSessionInfo()` —— **全局**最近激活指针(无绑定的会话兼容回退)
|
|
752
|
+
*
|
|
753
|
+
* ⚠️ 为何单列成本函数(S142 §30.12.1 实测缺陷):唤醒注册表的 `createdBy` 原先直接取第 3 级
|
|
754
|
+
* 全局指针,而它记录的是"**进程内最近一次被激活的 trajectory**"——多会话并发时必然张冠李戴
|
|
755
|
+
* (实测:S142 登记唤醒被写成 `by=S060`)。`createdBy` 是 D60「以可审计替代入口栅」的**唯一凭证**,
|
|
756
|
+
* 失真即替代控制失效 ⇒ 必须以"调用方自己是谁"为准,全局指针只作最后兜底。
|
|
757
|
+
*
|
|
758
|
+
* @param session 调用方 dsh 会话对象(需 `header.id` + `header.cwd`)
|
|
759
|
+
* @param scope 调用方 dsh 会话 id(取不到传空串)
|
|
760
|
+
* @returns 轨迹标签(`S###` 或完整目录名);三级全落空 → 空串(渲染为 `?`)
|
|
761
|
+
*/
|
|
762
|
+
function resolveSessionTrajectoryLabel(session, scope) {
|
|
763
|
+
const bound = session ? readLastBound(session) : null;
|
|
764
|
+
if (bound?.dirName) return trajectoryLabel(bound.dirName);
|
|
765
|
+
const scoped = scope !== "" ? getActiveSessionInfo(scope) : null;
|
|
766
|
+
if (scoped) return scoped.sessionId;
|
|
767
|
+
return getLastActiveSessionInfo()?.sessionId ?? "";
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* 反向查:绑定到某 trajectory 目录名的 dsh 会话 id(**按绑定时间倒序,最新在前**)。
|
|
771
|
+
*
|
|
772
|
+
* 用途(wake-scheduler 冷唤醒):.bindings.json 以 **sessionId 为键**,而唤醒条目的
|
|
773
|
+
* target 是**目录名**——需要值侧扫描做反向索引。冷会话被唤醒时必须先知道"哪个 dsh
|
|
774
|
+
* 会话承载这条轨迹",这是唯一的权威来源(标题只作回退,见 v1.29.2 R2)。
|
|
775
|
+
*
|
|
776
|
+
* @param root CCC 根
|
|
777
|
+
* @param dirName AGENT_SESSIONS 目录名(完整名,如 `2026-09-01--S151--x--auto`)
|
|
778
|
+
* @returns 会话 id 列表(最新绑定在前);无绑定 → `[]`
|
|
779
|
+
*/
|
|
780
|
+
function listBoundSessionIds(root, dirName) {
|
|
781
|
+
const file = readBindingsFile(join(root, BINDINGS_REL_PATH));
|
|
782
|
+
const hits = [];
|
|
783
|
+
for (const [id, raw] of Object.entries(file.sessions)) {
|
|
784
|
+
const rec = asBoundRecord(raw);
|
|
785
|
+
if (!rec || rec.dirName !== dirName) continue;
|
|
786
|
+
hits.push({
|
|
787
|
+
id,
|
|
788
|
+
at: typeof rec.at === "number" ? rec.at : 0
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
return hits.sort((a, b) => b.at - a.at).map((h) => h.id);
|
|
792
|
+
}
|
|
737
793
|
/**
|
|
738
794
|
* 写入绑定(latest-wins 覆盖该会话记录)。
|
|
739
795
|
*
|
|
@@ -763,4 +819,4 @@ function appendBound(session, action, rec) {
|
|
|
763
819
|
}
|
|
764
820
|
}
|
|
765
821
|
//#endregion
|
|
766
|
-
export {
|
|
822
|
+
export { showSession as C, setActiveSessionInfo as S, useSession as T, qaCheck as _, DEFAULT_SESSION_SCOPE as a, sessionEvents as b, clearActiveSessionInfo as c, extractSessionMdPathFromText as d, findSession as f, parseSessionContextFromEvents as g, listSessions as h, resolveSessionTrajectoryLabel as i, closeSession as l, healthCheck as m, listBoundSessionIds as n, SESSION_ACTIONS as o, getActiveSessionInfo as p, readLastBound as r, archiveSessions as s, appendBound as t, createSession as u, readActiveSessionMd as v, summarize as w, sessionsRoot as x, resolveSessionByTitle as y };
|
package/lib/session-bound.d.ts
CHANGED
|
@@ -45,6 +45,36 @@ export declare function bindingsPathFor(session: unknown): string | null;
|
|
|
45
45
|
export declare function readLastBound(session: unknown): SessionBoundRecord | null;
|
|
46
46
|
/** 判定会话是否已有绑定(供 reconcile 判定:无绑定才标题升级)。 */
|
|
47
47
|
export declare function hasAnyBound(session: unknown): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* **调用方自身**所属 trajectory 的标签(审计字段 `createdBy` 的唯一正解)。
|
|
50
|
+
*
|
|
51
|
+
* 三级来源,逐级回退(R↓:为什么不用单一来源):
|
|
52
|
+
* 1. `readLastBound(session)` —— **持久 bound**,权威(重启存活、不受别的会话激活污染)
|
|
53
|
+
* 2. `getActiveSessionInfo(scope)` —— 本 dsh 会话的**内存**激活(按 scope 隔离)
|
|
54
|
+
* 3. `getLastActiveSessionInfo()` —— **全局**最近激活指针(无绑定的会话兼容回退)
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ 为何单列成本函数(S142 §30.12.1 实测缺陷):唤醒注册表的 `createdBy` 原先直接取第 3 级
|
|
57
|
+
* 全局指针,而它记录的是"**进程内最近一次被激活的 trajectory**"——多会话并发时必然张冠李戴
|
|
58
|
+
* (实测:S142 登记唤醒被写成 `by=S060`)。`createdBy` 是 D60「以可审计替代入口栅」的**唯一凭证**,
|
|
59
|
+
* 失真即替代控制失效 ⇒ 必须以"调用方自己是谁"为准,全局指针只作最后兜底。
|
|
60
|
+
*
|
|
61
|
+
* @param session 调用方 dsh 会话对象(需 `header.id` + `header.cwd`)
|
|
62
|
+
* @param scope 调用方 dsh 会话 id(取不到传空串)
|
|
63
|
+
* @returns 轨迹标签(`S###` 或完整目录名);三级全落空 → 空串(渲染为 `?`)
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveSessionTrajectoryLabel(session: unknown, scope: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* 反向查:绑定到某 trajectory 目录名的 dsh 会话 id(**按绑定时间倒序,最新在前**)。
|
|
68
|
+
*
|
|
69
|
+
* 用途(wake-scheduler 冷唤醒):.bindings.json 以 **sessionId 为键**,而唤醒条目的
|
|
70
|
+
* target 是**目录名**——需要值侧扫描做反向索引。冷会话被唤醒时必须先知道"哪个 dsh
|
|
71
|
+
* 会话承载这条轨迹",这是唯一的权威来源(标题只作回退,见 v1.29.2 R2)。
|
|
72
|
+
*
|
|
73
|
+
* @param root CCC 根
|
|
74
|
+
* @param dirName AGENT_SESSIONS 目录名(完整名,如 `2026-09-01--S151--x--auto`)
|
|
75
|
+
* @returns 会话 id 列表(最新绑定在前);无绑定 → `[]`
|
|
76
|
+
*/
|
|
77
|
+
export declare function listBoundSessionIds(root: string, dirName: string): string[];
|
|
48
78
|
/**
|
|
49
79
|
* 写入绑定(latest-wins 覆盖该会话记录)。
|
|
50
80
|
*
|
|
@@ -22,7 +22,8 @@ const simpleSettingsSchema = z.object({
|
|
|
22
22
|
acpEnabled: z.boolean().default(false),
|
|
23
23
|
acpHttpPort: z.number().min(1024).max(65535).default(3100),
|
|
24
24
|
publicAskEnabled: z.boolean().default(false),
|
|
25
|
-
autopilotEnabled: z.boolean().default(false)
|
|
25
|
+
autopilotEnabled: z.boolean().default(false),
|
|
26
|
+
trajectoryEnabled: z.boolean().default(false)
|
|
26
27
|
});
|
|
27
28
|
/** 从插件 Config 提取 entry 默认(settings base 层) */
|
|
28
29
|
function entryDefaults(config) {
|
|
@@ -35,7 +36,8 @@ function entryDefaults(config) {
|
|
|
35
36
|
acpEnabled: config.acp?.enabled ?? false,
|
|
36
37
|
acpHttpPort: config.acp?.httpPort ?? 3100,
|
|
37
38
|
publicAskEnabled: config.publicAsk?.enabled ?? false,
|
|
38
|
-
autopilotEnabled: false
|
|
39
|
+
autopilotEnabled: false,
|
|
40
|
+
trajectoryEnabled: false
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
/** 运行时源(installSettingsSection 注入:settings scope 或 entry fallback) */
|
|
@@ -53,7 +55,8 @@ function defaultSimpleSettings() {
|
|
|
53
55
|
acpEnabled: false,
|
|
54
56
|
acpHttpPort: 3100,
|
|
55
57
|
publicAskEnabled: false,
|
|
56
|
-
autopilotEnabled: false
|
|
58
|
+
autopilotEnabled: false,
|
|
59
|
+
trajectoryEnabled: false
|
|
57
60
|
};
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
@@ -67,6 +67,9 @@ export interface SerenitySimpleSettings {
|
|
|
67
67
|
/** Autopilot Trajectory 全局总开关(v1.27.9,默认关——只在指定电脑开启;
|
|
68
68
|
* 关了即使 CCC 配置 enabled=true 也不启动定时器;CCC 级 enabled 仍为必要条件) */
|
|
69
69
|
autopilotEnabled: boolean;
|
|
70
|
+
/** trajectory 全局总开关(D58/M-1,S142 2026-09-13):**autopilot 与唤醒调度器共用**。
|
|
71
|
+
* 读取顺序:`trajectoryEnabled` → 旧键 `autopilotEnabled`(逐级回退,不回写)。 */
|
|
72
|
+
trajectoryEnabled: boolean;
|
|
70
73
|
}
|
|
71
74
|
/** schemastery schema(与 DSH 各插件 Config 同款) */
|
|
72
75
|
export declare const simpleSettingsSchema: z<Schemastery.ObjectS<{
|
|
@@ -79,6 +82,7 @@ export declare const simpleSettingsSchema: z<Schemastery.ObjectS<{
|
|
|
79
82
|
acpHttpPort: z<number, number>;
|
|
80
83
|
publicAskEnabled: z<boolean, boolean>;
|
|
81
84
|
autopilotEnabled: z<boolean, boolean>;
|
|
85
|
+
trajectoryEnabled: z<boolean, boolean>;
|
|
82
86
|
}>, Schemastery.ObjectT<{
|
|
83
87
|
gatewayEnabled: z<boolean, boolean>;
|
|
84
88
|
rebuildEnabled: z<boolean, boolean>;
|
|
@@ -89,6 +93,7 @@ export declare const simpleSettingsSchema: z<Schemastery.ObjectS<{
|
|
|
89
93
|
acpHttpPort: z<number, number>;
|
|
90
94
|
publicAskEnabled: z<boolean, boolean>;
|
|
91
95
|
autopilotEnabled: z<boolean, boolean>;
|
|
96
|
+
trajectoryEnabled: z<boolean, boolean>;
|
|
92
97
|
}>>;
|
|
93
98
|
/** 从插件 Config 提取 entry 默认(settings base 层) */
|
|
94
99
|
export declare function entryDefaults(config: SimpleConfigFragment): SerenitySimpleSettings;
|
|
@@ -2,7 +2,7 @@ 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
4
|
import { i as hostSessions, r as hostService, t as hostAgents } from "./access-fiehjxV6.js";
|
|
5
|
-
import {
|
|
5
|
+
import { S as setActiveSessionInfo, p as getActiveSessionInfo, r as readLastBound, t as appendBound, u as createSession } from "./session-bound-NHozeLFc.js";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { basename, join } from "node:path";
|
|
8
8
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
@@ -20,6 +20,6 @@ import type { Context } from 'cordis';
|
|
|
20
20
|
* 两种布局都稳(找到 experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts 即止)。
|
|
21
21
|
*/
|
|
22
22
|
export declare function findExpScript(startDir: string): string | null;
|
|
23
|
-
export declare const AUTOPILOT_ACTIONS: readonly ["all", "init", "random", "diag", "doc", "check", "status", "guide", "diag-live"];
|
|
24
|
-
/** 创建
|
|
23
|
+
export declare const AUTOPILOT_ACTIONS: readonly ["all", "init", "random", "diag", "doc", "check", "status", "guide", "diag-live", "wake-add", "wake-list", "wake-rm"];
|
|
24
|
+
/** 创建 trajectory 工具(原 autopilot-trajectory;D58 更名 + 唤醒注册表三动作) */
|
|
25
25
|
export declare function createAutopilotTool(ctx: Context): ReturnType<typeof defineTool>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wake-registry.ts — trajectory 唤醒注册表(D58,S142 2026-09-13)
|
|
3
|
+
*
|
|
4
|
+
* 需求(用户原话):一个 CCC 内允许多个 trajectory 任意形态并行;某个 trajectory 可以
|
|
5
|
+
* **预定未来唤醒自己或别人**,本质上是一次「未来时刻 + 一条 message」的投递;autopilot
|
|
6
|
+
* 是「周期自唤醒」的特例(独立循环,D59)。协作边界:互相可见、**无阻塞无等待**。
|
|
7
|
+
*
|
|
8
|
+
* 本模块只负责**注册表本身**(纯数据层,无宿主依赖,可单测):
|
|
9
|
+
* - 落点:CCC 内 `AGENT_SESSIONS/wake-registry.json`(随 CCC git,人可读可审计)
|
|
10
|
+
* - 状态机:`pending` →(投递成功)`delivered`|(超补跑窗口)`missed`|(`wake rm` 物理移除)
|
|
11
|
+
* - 投递即终结:一次性,不重投(无回执语义——发起方不 await,见设计文档 §2)
|
|
12
|
+
*
|
|
13
|
+
* 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
|
|
14
|
+
*/
|
|
15
|
+
/** 注册表相对 CCC 根的路径(与 `.bindings.json` 同居 AGENT_SESSIONS/) */
|
|
16
|
+
export declare const WAKE_REGISTRY_REL_PATH = "AGENT_SESSIONS/wake-registry.json";
|
|
17
|
+
/** 注册表格式版本(结构变更时 +1;读取端只接受本版本) */
|
|
18
|
+
export declare const WAKE_REGISTRY_VERSION = 1;
|
|
19
|
+
/**
|
|
20
|
+
* 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
|
|
21
|
+
* 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
|
|
22
|
+
*/
|
|
23
|
+
export declare const WAKE_CATCH_UP_MS: number;
|
|
24
|
+
/** 条目状态:一次性唤醒只有这四种归宿 */
|
|
25
|
+
export type WakeState = 'pending' | 'delivered' | 'missed' | 'cancelled';
|
|
26
|
+
/** 一条唤醒条目 */
|
|
27
|
+
export interface WakeEntry {
|
|
28
|
+
/** 稳定 id(不复用;`w-<UTC 到分钟>-<4 hex>`) */
|
|
29
|
+
id: string;
|
|
30
|
+
/** 目标 trajectory:`S###` 或完整 AGENT_SESSIONS 目录名(编码无关,与绑定锚一致) */
|
|
31
|
+
target: string;
|
|
32
|
+
/** 绝对时刻(UTC RFC3339,如 `2026-09-14T01:00:00.000Z`) */
|
|
33
|
+
at: string;
|
|
34
|
+
/** 唤醒 message(投递给目标 trajectory 的正文) */
|
|
35
|
+
message: string;
|
|
36
|
+
/** 状态 */
|
|
37
|
+
state: WakeState;
|
|
38
|
+
/** 发起者(轨迹标识,如 `S142`;可见性/审计用) */
|
|
39
|
+
createdBy: string;
|
|
40
|
+
/** 登记时刻(UTC RFC3339) */
|
|
41
|
+
createdAt: string;
|
|
42
|
+
/** 已尝试投递次数 */
|
|
43
|
+
attempts: number;
|
|
44
|
+
/** 最近一次投递结果(人读;失败必须留痕——禁止静默) */
|
|
45
|
+
lastResult: string | null;
|
|
46
|
+
/** 投递成功时刻(UTC RFC3339) */
|
|
47
|
+
deliveredAt: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** 注册表文件形态 */
|
|
50
|
+
export interface WakeRegistry {
|
|
51
|
+
version: number;
|
|
52
|
+
entries: WakeEntry[];
|
|
53
|
+
}
|
|
54
|
+
/** 注册表绝对路径 */
|
|
55
|
+
export declare function wakeRegistryPath(root: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* 读注册表。文件缺失 → 空表;损坏 → 空表 + error(不抛错,调用方决定是否告警)。
|
|
58
|
+
* @param root CCC 根
|
|
59
|
+
* @returns 注册表与读取错误(无错为 null)
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadWakeRegistry(root: string): {
|
|
62
|
+
registry: WakeRegistry;
|
|
63
|
+
error: string | null;
|
|
64
|
+
};
|
|
65
|
+
/** 原子写(tmp + rename;与 .bindings.json 同款形态) */
|
|
66
|
+
export declare function saveWakeRegistry(root: string, registry: WakeRegistry): {
|
|
67
|
+
ok: boolean;
|
|
68
|
+
error: string | null;
|
|
69
|
+
};
|
|
70
|
+
/** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
|
|
71
|
+
export declare function newWakeId(nowMs: number, rand?: () => number): string;
|
|
72
|
+
/**
|
|
73
|
+
* 解析唤醒时刻。接受两种写法(设计文档 §7):
|
|
74
|
+
* - 绝对:RFC3339(含时区偏移,如 `2026-09-14T09:00:00+08:00`)
|
|
75
|
+
* - 相对:`+30m` / `+2h`(便于 agent 直接登记"半小时后")
|
|
76
|
+
* @param input 用户/agent 传入的时刻串
|
|
77
|
+
* @param nowMs 当前时刻(相对写法基准)
|
|
78
|
+
* @returns `{ ok, ms }` 或 `{ ok: false, error }`
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseWakeAt(input: string, nowMs: number): {
|
|
81
|
+
ok: true;
|
|
82
|
+
ms: number;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
error: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 登记一条唤醒。
|
|
89
|
+
* @param root CCC 根
|
|
90
|
+
* @param input 目标 trajectory / 时刻串 / message / 发起者 / 当前时刻
|
|
91
|
+
* @returns 成功返回条目,失败返回稳定错误文案(不抛错)
|
|
92
|
+
*/
|
|
93
|
+
export declare function addWake(root: string, input: {
|
|
94
|
+
target: string;
|
|
95
|
+
at: string;
|
|
96
|
+
message: string;
|
|
97
|
+
createdBy: string;
|
|
98
|
+
nowMs: number;
|
|
99
|
+
}): {
|
|
100
|
+
ok: true;
|
|
101
|
+
entry: WakeEntry;
|
|
102
|
+
} | {
|
|
103
|
+
ok: false;
|
|
104
|
+
error: string;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* 物理移除一条(`wake rm`)。审计由 SESSION.md 与工具输出承担——注册表只保留在办项。
|
|
108
|
+
* @param root CCC 根
|
|
109
|
+
* @param id 条目 id
|
|
110
|
+
* @returns 移除结果(不存在 → 稳定错误码文案)
|
|
111
|
+
*/
|
|
112
|
+
export declare function removeWake(root: string, id: string): {
|
|
113
|
+
ok: boolean;
|
|
114
|
+
error: string | null;
|
|
115
|
+
removed: WakeEntry | null;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* 到期切分(纯函数,可单测):
|
|
119
|
+
* - `due`:`state === 'pending'` 且 `now >= at`(在补跑窗口内)→ 应投递
|
|
120
|
+
* - `expired`:`state === 'pending'` 且 `now - at > catchUpMs` → 应置 missed(不投递)
|
|
121
|
+
* @param registry 注册表
|
|
122
|
+
* @param nowMs 当前时刻
|
|
123
|
+
* @param catchUpMs 补跑窗口
|
|
124
|
+
*/
|
|
125
|
+
export declare function splitDueWakes(registry: WakeRegistry, nowMs: number, catchUpMs?: number): {
|
|
126
|
+
due: WakeEntry[];
|
|
127
|
+
expired: WakeEntry[];
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* 更新一条(状态推进;调用方给最终状态与结果文案)。
|
|
131
|
+
* @param root CCC 根
|
|
132
|
+
* @param id 条目 id
|
|
133
|
+
* @param patch 要写入的字段
|
|
134
|
+
* @returns 写入结果
|
|
135
|
+
*/
|
|
136
|
+
export declare function updateWake(root: string, id: string, patch: Partial<Pick<WakeEntry, 'state' | 'attempts' | 'lastResult' | 'deliveredAt'>>): {
|
|
137
|
+
ok: boolean;
|
|
138
|
+
error: string | null;
|
|
139
|
+
};
|
|
140
|
+
/** 列条目(默认全量;`pendingOnly` 只列在办项——面板/工具用) */
|
|
141
|
+
export declare function listWakes(root: string, opts?: {
|
|
142
|
+
pendingOnly?: boolean;
|
|
143
|
+
}): {
|
|
144
|
+
entries: WakeEntry[];
|
|
145
|
+
error: string | null;
|
|
146
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wake-scheduler.ts — trajectory 唤醒调度器(D58/D59/D60;S142 2026-09-13)
|
|
3
|
+
*
|
|
4
|
+
* 一个**中心调度器**(5min tick,复用 autopilot 的节律常量),处理 CCC 唤醒注册表里
|
|
5
|
+
* 到期的条目:把「未来时刻 + 一条 message」投递给目标 trajectory,**fire-and-forget**
|
|
6
|
+
* (无阻塞、无等待、无回执——发起方不 await,调度器不替模型结果负责)。
|
|
7
|
+
*
|
|
8
|
+
* 与 autopilot 的关系(D59):**两个 tick 各自独立**。autopilot 是每 CCC 单例的
|
|
9
|
+
* 周期自唤醒,逻辑一行不碰;本模块只处理注册表的一次性唤醒条目。
|
|
10
|
+
*
|
|
11
|
+
* 投递路径(P-1 证据链见 SESSION.md §30.6):
|
|
12
|
+
* 1. target(S### / 目录名)→ 目录名 + SESSION.md
|
|
13
|
+
* 2. 目标 dsh 会话 id:`.bindings.json` 里绑定该目录名的会话(按绑定时间倒序,权威)
|
|
14
|
+
* → 回退:live 会话标题匹配
|
|
15
|
+
* 3. agent:**live 优先**(`ctx.agents.get`)→ 冷会话走 `ctx.sessionController.resolveAgent`
|
|
16
|
+
* (宿主实现:live 优先 + resume 去重 + 由会话元数据恢复 preset)
|
|
17
|
+
* → `sessionController` 缺席(headless profile)⇒ 仅投递 live 目标,**不静默**
|
|
18
|
+
* 4. 投递:`agent.followup(...)`("Queue an ordinary follow-up turn and wake the driver")
|
|
19
|
+
*
|
|
20
|
+
* 守卫(I-4 默认):全局 `trajectoryEnabled`(旧键 `autopilotEnabled` 回退)|
|
|
21
|
+
* 补跑窗口 2h(超窗置 missed 留痕)|同 tick 串行|唤起后维持 live(人类可介入)。
|
|
22
|
+
*/
|
|
23
|
+
import type { Context } from 'cordis';
|
|
24
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
25
|
+
import { type WakeEntry } from './wake-registry.js';
|
|
26
|
+
/** 投递结果(写回条目的 lastResult;人读) */
|
|
27
|
+
export interface WakeDeliveryResult {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
detail: string;
|
|
30
|
+
}
|
|
31
|
+
/** 目标定位:目录名 + SESSION.md 绝对路径 */
|
|
32
|
+
interface WakeTarget {
|
|
33
|
+
dirName: string;
|
|
34
|
+
mdPath: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 解析条目的 target(`S###` 或完整目录名)→ 目录名 + SESSION.md 路径。
|
|
38
|
+
* @param root CCC 根
|
|
39
|
+
* @param target 条目里的目标标识
|
|
40
|
+
* @returns 命中返回目录信息,未命中 null
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveWakeTarget(root: string, target: string): WakeTarget | null;
|
|
43
|
+
/** 全局闸:`trajectoryEnabled` 优先,旧键 `autopilotEnabled` 回退(设置服务不可用 → 关) */
|
|
44
|
+
export declare function wakeSchedulerEnabled(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* 取目标 agent:live 优先 → 冷会话走 sessionController.resolveAgent → 标题回退。
|
|
47
|
+
* @param ctx 插件上下文
|
|
48
|
+
* @param root CCC 根
|
|
49
|
+
* @param dirName 目标 trajectory 目录名
|
|
50
|
+
* @returns 命中返回 agent 与取得方式(how 进 lastResult,便于诊断);未命中返回原因
|
|
51
|
+
*/
|
|
52
|
+
export declare function acquireWakeAgent(ctx: Context, root: string, dirName: string): Promise<{
|
|
53
|
+
agent: Agent;
|
|
54
|
+
how: string;
|
|
55
|
+
} | {
|
|
56
|
+
error: string;
|
|
57
|
+
}>;
|
|
58
|
+
/** 唤醒消息(唤起即投递正文:身份锚定 + 唤醒信息 + 任务) */
|
|
59
|
+
export declare function buildWakeText(entry: WakeEntry, target: WakeTarget): string;
|
|
60
|
+
/**
|
|
61
|
+
* 投递一条唤醒(fire-and-forget:`followup` 入队即返回,不等模型结果)。
|
|
62
|
+
* @param ctx 插件上下文
|
|
63
|
+
* @param root CCC 根
|
|
64
|
+
* @param entry 唤醒条目
|
|
65
|
+
* @returns 投递结果(写回 lastResult)
|
|
66
|
+
*/
|
|
67
|
+
export declare function deliverWake(ctx: Context, root: string, entry: WakeEntry): Promise<WakeDeliveryResult>;
|
|
68
|
+
/** 一次调度 tick:遍历 live CCC → 到期项串行投递 / 超窗项置 missed;返回人读摘要 */
|
|
69
|
+
export declare function runWakeTick(ctx: Context): Promise<string[]>;
|
|
70
|
+
/**
|
|
71
|
+
* 装配调度器(index.ts apply 调用):5min tick + 生命周期 disposer +
|
|
72
|
+
* 会话出现/settings 变化时热启动(与 autopilot 同款触发面,互不干扰)。
|
|
73
|
+
* @param ctx 插件上下文
|
|
74
|
+
*/
|
|
75
|
+
export declare function registerWakeScheduler(ctx: Context): void;
|
|
76
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/
|
|
3
|
+
"version": "1.32.0",
|
|
4
|
+
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|