@shgroup/dsh-serenity-hooks 1.28.2 → 1.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/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 session tool\uFF08list/show/create/use/close/health/qa/archive/summary/hook-develop-guide\uFF09\n session_rebuild\uFF08\u8D85\u9650\u91CD\u5EFA\uFF1B\u9608\u503C K \u89C1 ccc-config / \u8BBE\u7F6E\u9762\u677F\u91CD\u5EFA\u9608\u503CK\uFF09\n \u2461 \u8BA4\u77E5\u8D28\u91CF\u6846\u67B6 \u2192 eap\uFF08E\u2191/R\u2193/S\u2191\uFF09/ neat\uFF08Neat \u534F\u8BAE\uFF09/ cce\uFF08\u8BA4\u77E5\u8FDE\u7EED\u6027\u5DE5\u7A0B\uFF09\u2014\u2014\u6E10\u8FDB\u62AB\u9732\uFF0C\u65E0\u53C2\u5373\u5168\u6587\n \u2462 \u5DE5\u5177\u4E0E\u6267\u884C \u2192 cc_fs\uFF08\u6587\u4EF6\u7CFB\u7EDF 15 \u5B50\u547D\u4EE4\uFF09/ cc_git\uFF08git\uFF09/ acc_kit\uFF08health \u542B\u6CE8\u518C\u8868\u68C0\u67E5/time/wait\uFF09\n acc_msm\uFF08MSM \u6846\u67B6\uFF1A\u672C\u5DE5\u5177 list/exec/register/deregister/check/guide/ccc-config/catalog\uFF09\n handyman\uFF08\u6742\u5DE5 agent \u7F16\u6392\u2014\u2014guide \u5B50\u547D\u4EE4\uFF1Aacc_msm exec handyman guide\uFF09\n \u2463 \u89D2\u8272\u4E0E\u5BF9\u5916\u9762 \u2192 skiff_admin\uFF08Skiff \u8BA4\u77E5\u5B50\u96C6\u89D2\u8272\uFF1Aguide/validate/apply/list\u2014\u2014acc_msm exec skiff_admin guide\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\uFF1Aacc_msm exec weixin-doctor guide\uFF09\n \u2465 CCC \u914D\u7F6E\u603B\u89C8 \u2192 acc_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 acc_msm register/deregister \u7BA1\u7406\uFF08\u5199\u4FDD\u62A4\u2014\u2014\u4E0D\u53EF\u76F4\u63A5\u7F16\u8F91\uFF09\n acc_kit 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 (acc_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. acc_msm 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 acc_msm register --flags '<json>' (the tool currently parses the name style)\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 (acc_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)\nacc_msm exec <name> --list \u2014 list all MSMs\nacc_msm exec <name> --schema <n> \u2014 view a specific MSM's parameter schema\nacc_msm exec <name> --format=json \u2014 JSON output mode (remaining args passed through losslessly)\n";
21
21
  /** CCC 配置参考(对齐 osp ccc-config action) */
22
- export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\nSESSION-KEEPER \u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccc_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\uFF1Aacc_msm exec skiff_admin <guide|validate|apply|list>\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\uFF1Aacc_msm exec autopilot-trajectory <all|check|status|diag>\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\uFF1Bacc_msm exec weixin-doctor\n<status|diag|verify> \u6392\u67E5\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\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\u56DE\u590D\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\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\" }\n\n \u793A\u4F8B\u811A\u672C\uFF08\u8FFD\u52A0\u5230\u6309\u65E5\u6587\u4EF6\u2014\u2014\u6301\u4E45\u5316\u5F52 CCC \u81EA\u9009\uFF1A\u6587\u4EF6/DB/\u8FDC\u7AEF\u5747\u53EF\uFF09\uFF1A\n const fs = require('node:fs'); const p = '/path/ccc/AGENT_SESSIONS/_weixin-log.jsonl';\n fs.appendFileSync(p, JSON.stringify(ev) + '\\n');\n\n \u6267\u884C\u8BED\u4E49\uFF08\u65C1\u8DEF\u5BB9\u5FCD\uFF09\uFF1A\u5F02\u6B65 fire-and-forget + 15s \u8D85\u65F6 kill + \u5931\u8D25\u4EC5\u65E5\u5FD7\u2014\u2014\n \u5FAE\u4FE1\u6865\u6D88\u606F\u5904\u7406/\u56DE\u590D\u4E0D\u53D7 hook \u5F71\u54CD\uFF1B\u811A\u672C\u987B\u5728 CCC \u6839\u5185\uFF08\u8DEF\u5F84\u9003\u9038\u62D2\u7EDD\uFF09\u3002\n \u5A92\u4F53 relPath \u6307\u5411 _tmp/weixin-inbound/\uFF08\u4E34\u65F6\u76EE\u5F55\uFF09\u2014\u2014\u9700\u6301\u4E45\u4FDD\u5B58\u5A92\u4F53\u6587\u4EF6\u8BF7\u81EA\u884C copy\u3002\n";
22
+ export declare const CCC_CONFIG_REFERENCE = "\u2550\u2550\u2550 CCC Configuration Reference \u2550\u2550\u2550\n\nCCC-level features are configured in .opencode/serenity.json.\nBelow are all available configuration sections.\n\n\u2500\u2500 1. handyman.models \u2500\u2500\nhandyman\uFF08\u6742\u5DE5\uFF09\u5DE5\u5177\u53EF\u7528\u6A21\u578B\u767D\u540D\u5355\uFF08provider/model \u5217\u8868\uFF09\uFF1B\u672A\u914D\u7F6E\u65F6 handyman \u62A5\u9519\u8981\u6C42\u914D\u7F6E\u3002\n\u7F3A\u7701\u6A21\u578B = models[0]\uFF08\u53EF\u7528 handyman.defaultModel \u6307\u5B9A\uFF0C\u5FC5\u987B \u2208 models\uFF09\u3002\n\n Config:\n { \"handyman\": { \"models\": [\"provider/model-name\"] } }\n\n Example:\n { \"handyman\": { \"models\": [\"minimax-cn-coding-plan/MiniMax-M3\"], \"defaultModel\": \"minimax-cn-coding-plan/MiniMax-M3\", \"maxRounds\": 100, \"maxParallel\": 10 } }\n\n\u2500\u2500 2. sessionKeeper.threshold \u2500\u2500\ntrajectory-assistant\uFF08TRAJECTORY-ASSISTANT \u00B7 CHECKPOINT\uFF09\u63D0\u9192\u673A\u5236\u7684\u79EF\u5206\u9608\u503C\uFF08\u975E headless \u4E3B agent\uFF09\u3002\n\u6309\u5DE5\u5177\u8C03\u7528\u52A0\u6743 + \u8017\u65F6\u8BA1\u5206\uFF1B\u8FBE\u5230\u9608\u503C\u6CE8\u5165\u63D0\u9192\uFF0C\u8981\u6C42\u6A21\u578B\u56DE\u590D ACK \u7801\u3002\n\n Config:\n { \"sessionKeeper\": { \"threshold\": 150 } }\n\n \u8BA1\u5206\uFF1Awrite/edit = 3\uFF0Ctask = 10\uFF0Cread/grep/glob/msm \u7B49 = 1\uFF0C\u65F6\u95F4 = 1/\u5206\u949F\n \u9ED8\u8BA4\uFF1A150\n\n\u2500\u2500 3. localstore.gitTrack \u2500\u2500\nlocalstore.json \u7684 git \u7B56\u7565\uFF1Aallow \u53EF\u63D0\u4EA4 / deny \u7981\u63D0\u4EA4\uFF08\u9ED8\u8BA4 deny\uFF09\u3002\ndeny \u65F6\u5199\u5165\u81EA\u52A8\u786E\u4FDD .gitignore \u542B\u8BE5\u6587\u4EF6\uFF08\u7269\u7406\u4FDD\u8BC1\uFF09\uFF0Ccc_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\uFF1Aacc_msm exec skiff_admin <guide|validate|apply|list>\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\uFF1Aacc_msm exec autopilot-trajectory <all|check|status|diag>\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\uFF1Bacc_msm exec weixin-doctor\n<status|diag|verify> \u6392\u67E5\u3002\u51ED\u636E\u4E3B\u52A8\u67E5\u770B\uFF1Alocalstore get WEIXIN_<ACCOUNT>_TOKEN\u3002\n\n Config:\n { \"weixin\": {\n \"enabled\": true,\n \"hook\": \"scripts/weixin-message-hook.ts\",\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\u56DE\u590D\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\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\" }\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;
@@ -13,6 +13,8 @@ import type { Context } from 'cordis';
13
13
  import type { Agent } from '@deepseek-ai/dsh-agent';
14
14
  import type { UserMessage } from '@deepseek-ai/dsh-session';
15
15
  export declare const DEFAULT_ENTRY_SKILL_MAX_CHARS = 30000;
16
+ /** 从 dsh 会话日志读标题(latest-wins `session/title` 事件;无返回 null)——供标题 reconcile(U3) */
17
+ export declare function readDshSessionTitle(session: unknown): string | null;
16
18
  export declare function accIdentityText(root: string, configPaths?: string[], entrySkillMaxChars?: number): string;
17
19
  /**
18
20
  * ACC 注入消息(S134 去重):**只含简短身份锚点**([ACC] 已激活 + CCC 根 + 约束 + handyman 模型 + Phase 2)。
@@ -7,7 +7,7 @@
7
7
  * 计分(对齐 opencode-serenity-plugin session-keeper):
8
8
  * write/edit=3, task=10, read/grep/glob/msm=1, 经过时间 +1 分/分钟
9
9
  * 阈值:.opencode/serenity.json sessionKeeper.threshold(规范位置,.dsh 回退;缺省 150)
10
- * 提醒要求模型回应 [SESSION-KEEPER-recorded-{code}] 确认(DCP 模式)。
10
+ * 提醒要求模型回应 [TRAJECTORY-ASSISTANT-recorded-{code}] 确认(DCP 模式)。
11
11
  */
12
12
  import type { Context } from 'cordis';
13
13
  export declare function scoreTool(toolName: string): number;
@@ -61,10 +61,13 @@ export declare function personaBlock(mode: string, overrideText: string): string
61
61
  */
62
62
  export declare function eapBlock(): string;
63
63
  /**
64
- * Metaphor 强化块(v1.19.6,S142):宁静号宇宙隐喻域(船/海/罗盘/日志/船员)。
64
+ * Metaphor 强化块(v1.19.6,S142;v1.29 星舰意象升级,S142 用户拍板):
65
+ * 宁静号宇宙隐喻域(星舰/深空/航程/船员)。海船→星舰意象:one starship, one voyage;
66
+ * deep space / launch / docking clamp;Departure Inspection(原 Harbor Inspection)。
65
67
  * 隐喻是记忆钩子——形象唤起约束,前述块保持规则精确。每条隐喻 = 一个不可违背的
66
68
  * 物理事实 + 行为判据(verdict)。全英文(与 CCE/Constraints 一致);独立块可
67
69
  * 回退;无 osp 对应(dsp 扩展,不进对齐断言参照)。
70
+ * 结构约束(docs/metaphor-domain.md M-1~M-4):三层骨架不变 + 每条映射约束不变。
68
71
  */
69
72
  export declare function metaphorBlock(): string;
70
73
  /**
@@ -0,0 +1,80 @@
1
+ /**
2
+ * session-bound.ts — SESSION 绑定持久化(serenity/bound 会话事件)
3
+ *
4
+ * 目标(S142 用户拍板,方案 v1.0):让「dsh 会话(载体)↔ SESSION(宁静号轨迹)」
5
+ * 的绑定坚固——LLM 不能因智力因素(幻觉/理解偏差)在过程中或 session_rebuild 后
6
+ * 自行更换 SESSION。
7
+ *
8
+ * 机制(DSH 原生,零改 harness):
9
+ * `SessionEventMap` 是 merge-extensible(先例:dsh-compaction 扩展 compaction/*;
10
+ * dsp 已在 append compaction/prune)——插件可 declare module 自定义事件,
11
+ * `Session.append(type, data)` 将其写入会话 append-only 日志,随 session.jsonl
12
+ * 持久化落盘,进程重启后 snapshotEvents() 可完整读取。
13
+ *
14
+ * 权威绑定 = 会话日志中**最后一条** `serenity/bound`(append-only latest-wins)。
15
+ * 本事件是 log-only 元数据(无 surfaceOp——永不上模型可见面,不进系统提示词)。
16
+ *
17
+ * 编码无关(U4):绑定锚 = **完整 AGENT_SESSIONS 目录名**(磁盘唯一存在),
18
+ * sessionId(S###/apaas-xxx/自定义)仅是派生展示字段——绝不假设 S 前缀。
19
+ */
20
+ import type { Session } from '@deepseek-ai/dsh-session';
21
+ import type { SessionEventMap } from '@deepseek-ai/dsh-session/types';
22
+ declare module '@deepseek-ai/dsh-session/types' {
23
+ interface SessionEventMap {
24
+ /**
25
+ * Serenity SESSION binding — authoritative; appended on every binding change.
26
+ * Code-agnostic (keyed by full dir name), log-only (no surfaceOp).
27
+ */
28
+ 'serenity/bound': {
29
+ /** Full AGENT_SESSIONS dir name — the ONLY hard identity. */
30
+ dirName: string;
31
+ /** SESSION.md absolute path. */
32
+ mdPath: string;
33
+ /** Display code when parseable (S142 / apaas-26116 / custom); informational. */
34
+ sessionId?: string;
35
+ /** Why this binding record was written. */
36
+ action: 'activate' | 'switch' | 'create' | 'rebuild' | 'reconcile' | 'release';
37
+ /** Epoch ms. */
38
+ at: number;
39
+ /** Optional reason (e.g. 'auto from title'). */
40
+ note?: string;
41
+ };
42
+ }
43
+ }
44
+ export type SessionBoundAction = SessionEventMap['serenity/bound']['action'];
45
+ export interface SessionBoundRecord {
46
+ dirName: string;
47
+ mdPath: string;
48
+ sessionId?: string;
49
+ action: SessionBoundAction;
50
+ at: number;
51
+ note?: string;
52
+ }
53
+ /** 事件 type 常量(声明 + 读取共用) */
54
+ export declare const SESSION_BOUND_EVENT: "serenity/bound";
55
+ /**
56
+ * 读取会话日志中**最后一条** `serenity/bound`(权威绑定,latest-wins)。
57
+ * 尾到头扫描(时间序最新在后);无 bound 事件返回 null。
58
+ * 纯逻辑零 DSH 依赖(经 sessionEvents helper 读 snapshotEvents/events 兜底)。
59
+ */
60
+ export declare function readLastBound(session: unknown): SessionBoundRecord | null;
61
+ /**
62
+ * 判定会话日志是否已有任何 bound 事件(供 reconcile 判定:无绑定才标题升级)。
63
+ */
64
+ export declare function hasAnyBound(session: unknown): boolean;
65
+ /**
66
+ * append 一条 `serenity/bound` 绑定事件(log-only,无 surfaceOp——元数据不进模型可见面)。
67
+ * @param session 目标 dsh 会话(真实 Session;append 泛型经内部断言兼容——类型由
68
+ * `serenity/bound` 声明面保证,append 接受已声明事件)
69
+ * @param action 绑定动作
70
+ * @param rec 绑定记录(dirName + mdPath 必填;sessionId 可选展示码)
71
+ * @returns 是否成功(append 抛错/会话不可用时 false——绑定失败不阻断主流程)
72
+ */
73
+ export declare function appendBound(session: Session | {
74
+ append: (type: string, data: unknown) => unknown;
75
+ } | null | undefined, action: SessionBoundAction, rec: {
76
+ dirName: string;
77
+ mdPath: string;
78
+ sessionId?: string;
79
+ note?: string;
80
+ }): boolean;
@@ -0,0 +1,113 @@
1
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ //#region src/session-cleanup.ts
4
+ /**
5
+ * session-cleanup.ts — DSH 平台旧会话自动清理(v1.29,S142 需求③)
6
+ *
7
+ * 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
8
+ * base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
9
+ * <root>/--<project-slug>--/<encoded-session-id>/session.jsonl(.zstd)
10
+ * DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
11
+ * 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
12
+ * 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
13
+ * 物理删后 list 不再返回;WorkspaceRegistry 下次启动重建 header index 自动消失。
14
+ *
15
+ * 方案(S142 用户拍板):lastActive 基准 + 手动触发 + 不过滤不归档直接物理删;
16
+ * 安全底线 = live 会话保护跳过(删正在运行的会话 = 灾难)。
17
+ *
18
+ * 本模块纯逻辑(零 ctx 依赖):传入 sessionsRoot + liveIds,可单测。
19
+ */
20
+ /** DSH 会话 root:env DSH_HOME → ~/.dsh(config-ops globalConfigPath 同款推导) */
21
+ function sessionsRootDir() {
22
+ const dshHome = process.env.DSH_HOME ?? join(process.env.HOME ?? "", ".dsh");
23
+ return join(dshHome, "sessions");
24
+ }
25
+ /** 一天的毫秒数 */
26
+ const DAY_MS = 864e5;
27
+ /**
28
+ * 扫描 sessions root,列出可清理候选:
29
+ * - 递归 `<root>/<project>/<session-id>/session.jsonl(.zstd)`(任意深度下找 *session* 文件)
30
+ * - 过滤 live(liveIds 集合)——安全底线
31
+ * - 过滤 lastActive >= cutoffMs(最后活动未达阈值 → 保留)
32
+ * root 不存在/为空 → 返回 [](非错误)。
33
+ */
34
+ function collectEligibleSessions(root, cutoffMs, liveIds = /* @__PURE__ */ new Set()) {
35
+ if (!existsSync(root)) return [];
36
+ const out = [];
37
+ for (const project of readdirSync(root, { withFileTypes: true })) {
38
+ if (!project.isDirectory()) continue;
39
+ const projectPath = join(root, project.name);
40
+ for (const entry of readdirSync(projectPath, { withFileTypes: true })) {
41
+ if (!entry.isDirectory()) continue;
42
+ const log = findSessionLog(join(projectPath, entry.name));
43
+ if (log === null) continue;
44
+ if (liveIds.has(entry.name)) continue;
45
+ let mtime;
46
+ try {
47
+ mtime = statSync(log).mtimeMs;
48
+ } catch {
49
+ continue;
50
+ }
51
+ if (mtime >= cutoffMs) continue;
52
+ out.push({
53
+ id: entry.name,
54
+ project: project.name,
55
+ logPath: log,
56
+ lastActiveMs: mtime
57
+ });
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+ /** 在会话目录下找 session 日志文件(.jsonl / .jsonl.zstd);无 → null */
63
+ function findSessionLog(sessionDirPath) {
64
+ for (const name of ["session.jsonl", "session.jsonl.zstd"]) {
65
+ const p = join(sessionDirPath, name);
66
+ if (existsSync(p)) return p;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * 执行清理(用户拍板:直接物理删,不过滤不归档;dryRun 预览不删)。
72
+ * 删除 = rmSync 整个会话目录(recursive + force)。失败逐个记 errors 不中断。
73
+ */
74
+ function performCleanup(root, cutoffMs, liveIds = /* @__PURE__ */ new Set(), opts = {}) {
75
+ const candidates = collectEligibleSessions(root, cutoffMs, liveIds);
76
+ if (opts.dryRun) return {
77
+ candidates,
78
+ result: null
79
+ };
80
+ const result = {
81
+ deleted: [],
82
+ errors: []
83
+ };
84
+ for (const c of candidates) {
85
+ const dir = dirOf(c.logPath);
86
+ try {
87
+ rmSync(dir, {
88
+ recursive: true,
89
+ force: true
90
+ });
91
+ result.deleted.push(c.id);
92
+ } catch (e) {
93
+ result.errors.push({
94
+ id: c.id,
95
+ reason: e.message
96
+ });
97
+ }
98
+ }
99
+ return {
100
+ candidates,
101
+ result
102
+ };
103
+ }
104
+ /** 从日志路径取会话目录(父目录) */
105
+ function dirOf(logPath) {
106
+ return logPath.slice(0, logPath.lastIndexOf("/")) || logPath;
107
+ }
108
+ /** 便捷:默认 N 天前为 cutoff(供 API/调用方) */
109
+ function cutoffDaysAgo(days, nowMs = Date.now()) {
110
+ return nowMs - days * DAY_MS;
111
+ }
112
+ //#endregion
113
+ export { cutoffDaysAgo, performCleanup, sessionsRootDir };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * session-cleanup.ts — DSH 平台旧会话自动清理(v1.29,S142 需求③)
3
+ *
4
+ * 背景(实证):DSH 会话物理存储 = `$DSH_HOME/sessions/`(缺省 ~/.dsh/sessions;
5
+ * base bundle cordis.patch.yml root: dshHomePath('sessions'))。磁盘布局:
6
+ * <root>/--<project-slug>--/<encoded-session-id>/session.jsonl(.zstd)
7
+ * DSH **无删除会话 API**(PersistenceCoordinator 仅 create/append/load/inspect/borrow,
8
+ * 无 delete/purge;workspace archiveSession 只加归档集不删文件)——物理删除只能直接
9
+ * 删文件。删除后一致性(利好):sessionPersistence.list() = readdir 扫描磁盘现存 →
10
+ * 物理删后 list 不再返回;WorkspaceRegistry 下次启动重建 header index 自动消失。
11
+ *
12
+ * 方案(S142 用户拍板):lastActive 基准 + 手动触发 + 不过滤不归档直接物理删;
13
+ * 安全底线 = live 会话保护跳过(删正在运行的会话 = 灾难)。
14
+ *
15
+ * 本模块纯逻辑(零 ctx 依赖):传入 sessionsRoot + liveIds,可单测。
16
+ */
17
+ /** DSH 会话 root:env DSH_HOME → ~/.dsh(config-ops globalConfigPath 同款推导) */
18
+ export declare function sessionsRootDir(): string;
19
+ /** 一个候选会话(删除前预览信息) */
20
+ export interface CandidateSession {
21
+ /** 会话 id(目录名) */
22
+ id: string;
23
+ /** 项目目录(父目录名,如 --home-yh-home-home-serenity--) */
24
+ project: string;
25
+ /** 会话日志文件绝对路径 */
26
+ logPath: string;
27
+ /** 最后活动(mtime 退化——日志只追加,mtime ≈ 最后写) */
28
+ lastActiveMs: number;
29
+ }
30
+ /**
31
+ * 扫描 sessions root,列出可清理候选:
32
+ * - 递归 `<root>/<project>/<session-id>/session.jsonl(.zstd)`(任意深度下找 *session* 文件)
33
+ * - 过滤 live(liveIds 集合)——安全底线
34
+ * - 过滤 lastActive >= cutoffMs(最后活动未达阈值 → 保留)
35
+ * root 不存在/为空 → 返回 [](非错误)。
36
+ */
37
+ export declare function collectEligibleSessions(root: string, cutoffMs: number, liveIds?: ReadonlySet<string>): CandidateSession[];
38
+ /** 在会话目录下找 session 日志文件(.jsonl / .jsonl.zstd);无 → null */
39
+ export declare function findSessionLog(sessionDirPath: string): string | null;
40
+ /** 删除执行结果 */
41
+ export interface CleanupResult {
42
+ deleted: string[];
43
+ /** 尝试删除但失败的会话目录(权限/竞态) */
44
+ errors: Array<{
45
+ id: string;
46
+ reason: string;
47
+ }>;
48
+ }
49
+ /**
50
+ * 执行清理(用户拍板:直接物理删,不过滤不归档;dryRun 预览不删)。
51
+ * 删除 = rmSync 整个会话目录(recursive + force)。失败逐个记 errors 不中断。
52
+ */
53
+ export declare function performCleanup(root: string, cutoffMs: number, liveIds?: ReadonlySet<string>, opts?: {
54
+ dryRun?: boolean;
55
+ }): {
56
+ candidates: CandidateSession[];
57
+ result: CleanupResult | null;
58
+ };
59
+ /** 便捷:默认 N 天前为 cutoff(供 API/调用方) */
60
+ export declare function cutoffDaysAgo(days: number, nowMs?: number): number;
@@ -117,6 +117,19 @@ export declare function parseSessionContextFromEvents(events: readonly unknown[]
117
117
  /** 从文本提取 SESSION.md 路径(use 上下文 / 重建锚点规范行通用;无匹配返回 null)。
118
118
  * 同行已知尾注(如系统提示词 Session 块的 persistent-body 注释)剥除——规范行只取路径本体。 */
119
119
  export declare function extractSessionMdPathFromText(text: string): string | null;
120
+ /**
121
+ * 从 dsh 会话标题解析 SESSION 目录(编码无关 best-match,U3/U4)——
122
+ * 标题不假设 S### 前缀(CCC 可自定义编码:apaas-xxx / P### / 完整目录名等)。
123
+ * 匹配优先级(全部对 AGENT_SESSIONS 现有目录 best-match,不猜):
124
+ * ① 标题即完整目录名(含日期前缀 `YYYY-MM-DD--`)→ 精确命中
125
+ * ② 标题含 `--<code>--` 段(如完整目录名被截断为 `<code>-日期-概括` 前段)→
126
+ * 按 code 段匹配:取标题首 token(`-` 前),与各目录 `--<code>--`/`--<code>` 尾段比对
127
+ * ③ 唯一模糊子串匹配(多个则返回 null 防误猜)
128
+ * @param title dsh 会话标题(如 `S142-2026-08-24-概括` / `apaas-26116-…` / 完整目录名)
129
+ * @param sessionsDir AGENT_SESSIONS 绝对路径
130
+ * @returns 命中目录的绝对路径(SESSION.md);无/歧义返回 null
131
+ */
132
+ export declare function resolveSessionByTitle(title: string, sessionsDir: string): string | null;
120
133
  /**
121
134
  * 约定回退(v1.24.11):AGENT_SESSIONS 下最新修改的**未完成**会话的 SESSION.md。
122
135
  * readAllSessions 已按「未完成优先 + mtime 降序」排序 → 首个未完成且含 SESSION.md 即最新活动。
@@ -0,0 +1,54 @@
1
+ /**
2
+ * trajectory-assistant.ts — 关卡化注入的统一命名与风格门面(v0.3,S142)
3
+ *
4
+ * 概念(用户拍板 2026-09-05):dsp 全部"过程中动态提示注入"统一命名为
5
+ * trajectory-assistant(轨迹助航员)。关卡设计思想塑造**结构与时机**
6
+ * (何时/何地注入),**不用于提示词用词**(D8 词法原则)——纯游戏黑话
7
+ * (BOSS/XP/level-up)禁止出现在提示词文本;CHECKPOINT/LIMIT/TUTORIAL 等
8
+ * 跨领域自然通用词允许。
9
+ *
10
+ * 本模块 = token 常量 + level-event 词汇表的**单一真相源**(避免前缀散落
11
+ * 各文件字符串字面量)+ 风格门面(plain 默认 / metaphor 星舰变体)。
12
+ * 仅当 style=metaphor 时前缀包装措辞变化;可行动正文不变 → 零行为漂移。
13
+ *
14
+ * 设计简写(L0 环境 / L1 教程 / L2 目标 / L3 检查点 / L4 极限 / L5 守卫 /
15
+ * L6 结算)仅在本模块注释与内部文档出现,永不浮现在提示词文本。
16
+ *
17
+ * 结算(settlement)挂点:D6 用户拍板——若 CCC 有标准 SESSION 复盘仪式,
18
+ * 结算视图归 trajectory-assistant。当前 close/archive 无复盘摘要 → 机制
19
+ * 延后,仅留 onSettlement 导出 seam(OP-1,无调用者)。
20
+ */
21
+ /** 可见提示词前缀用词(跨领域自然词,D8:无游戏黑话) */
22
+ export declare const EVENT_LABEL: {
23
+ /** 计分同步提醒(原 TRAJECTORY-STEWARD) */
24
+ readonly checkpoint: "CHECKPOINT";
25
+ /** 上下文极限重建提醒(原 TRAJECTORY;LIMIT 替代被否的 BOSS——自然词) */
26
+ readonly limit: "LIMIT";
27
+ /** 极限强制升级(原 TRAJECTORY-ESCALATED) */
28
+ readonly limitMandatory: "LIMIT · MANDATORY";
29
+ /** 重建锚点头部(原 TRAJECTORY-REBUILD) */
30
+ readonly rebuild: "REBUILD";
31
+ /** 敏感输出边界守卫(原 SERENITY OUTPUT GUARD) */
32
+ readonly guard: "BOUNDARY GUARD";
33
+ };
34
+ /** 家族标识(所有动态注入统一前缀) */
35
+ export declare const ASSISTANT_PREFIX = "TRAJECTORY-ASSISTANT";
36
+ /** 完整 token:`[TRAJECTORY-ASSISTANT · <LABEL>]` */
37
+ export declare function eventToken(event: keyof typeof EVENT_LABEL): string;
38
+ /** ACK 确认码前缀(recorded/skipped 语义不变,仅家族名更新) */
39
+ export declare const ACK_PREFIX = "TRAJECTORY-ASSISTANT-recorded";
40
+ export declare const ACK_SKIP_PREFIX = "TRAJECTORY-ASSISTANT-skipped";
41
+ /** 风格档位:plain(默认,精确文本)/ metaphor(借星舰词——产品隐喻非游戏词) */
42
+ export type TrajectoryStyle = 'plain' | 'metaphor';
43
+ /**
44
+ * 按风格生成前缀 token。plain = eventToken()(原样);metaphor 仅当有
45
+ * 星舰化变体时替换用词(当前等价保留——星舰词库隐喻域的既有措辞即
46
+ * "context limit/deck check" 类,不强制替换;扩展点留给未来实验)。
47
+ */
48
+ export declare function styledToken(event: keyof typeof EVENT_LABEL, style?: TrajectoryStyle): string;
49
+ /**
50
+ * 结算 seam(OP-1/D6):未来标准 SESSION 复盘仪式接入点。当前无调用者,
51
+ * 仅导出契约:onSettlement(cb) 在"工作完成且被用户认可"时触发——该信号
52
+ * 尚无可靠自动检测(D2 用户拍板记录为未解问题),实现留待仪式落地。
53
+ */
54
+ export declare function onSettlement(_cb: (sessionId: string) => void): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.28.2",
3
+ "version": "1.29.1",
4
4
  "description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
5
5
  "license": "MIT",
6
6
  "repository": {