cc-ding 1.0.7 → 1.1.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 CHANGED
@@ -18,6 +18,16 @@
18
18
 
19
19
  将 Claude Code 接入钉钉,实现双向通信。支持多轮对话、任务队列、定时任务、图片识别,帮助团队以最低成本构建可私有化部署的 AI 助手。
20
20
 
21
+ ### 交流群
22
+
23
+ 扫码加入钉钉或微信交流群:
24
+
25
+ <p align="center">
26
+ <img src="assets/qrcode-dingtalk.jpg" alt="钉钉交流群二维码" width="250">
27
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
28
+ <img src="assets/qrcode-wechat.jpg" alt="微信交流群二维码" width="250">
29
+ </p>
30
+
21
31
  ### 目录
22
32
 
23
33
  - [快速开始](#快速开始)
@@ -161,6 +171,23 @@ pm2 start --name "cc-ding-{clientId}" npx -- -p cc-ding cc-ding run -ci {clientI
161
171
  | `permissionMode` | Claude 进程权限模式(默认 `acceptEdits`;`bypassPermissions` 需显式配置,启动时会告警),可选: `default`、`acceptEdits`、`plan`、`auto`、`bypassPermissions`、`dontAsk` |
162
172
  | `freedomMode` | 自由模式开关(默认 `false`,开启后跳过群用户白名单限制) |
163
173
  | `preBash` | 全局 `/bash` 前置命令 |
174
+ | `agent` | AI 执行引擎:`claude`(默认)或 `codex`(OpenAI Codex CLI) |
175
+
176
+ ### 多 Agent 架构(v1.1.0)
177
+
178
+ cc-ding 引入了多 Agent 抽象层,支持在不同 AI 执行引擎之间切换。通过在群配置中设置 `agent` 字段,可以为不同群指定不同的执行引擎:
179
+
180
+ ```json
181
+ {
182
+ "conversationId": "群ID",
183
+ "agent": "claude" // 或 "codex"
184
+ }
185
+ ```
186
+
187
+ - **`claude`**(默认):使用 Claude Code CLI,支持 `--resume` 会话恢复、API Key 轮换、上下文压缩等完整功能
188
+ - **`codex`**:使用 OpenAI Codex CLI,支持线程恢复(`thread_id`)、沙箱模式(`full-auto`)、自动超时恢复等
189
+
190
+ 所有命令(`/goon`、`/!`、`/new`、`/cc` 等)均支持多 Agent 路由,无需额外配置。
164
191
 
165
192
  #### 安全说明
166
193
 
@@ -174,7 +201,7 @@ pm2 start --name "cc-ding-{clientId}" npx -- -p cc-ding cc-ding run -ci {clientI
174
201
 
175
202
  | 类型 | 路径 |
176
203
  |------|------|
177
- | 会话 | `{MD5}/.sessions/{claudeSessionId}/session.{json,log}` |
204
+ | 会话 | `{MD5}/.sessions/{claudeSessionId|agentSessionId}/session.{json,log}` |
178
205
  | 任务 | `{MD5}/.tasks/{时间戳}/task.{json,log}` |
179
206
  | 定时任务 | `cron.json` |
180
207
  | 图片缓存 | `{MD5}/.images/` |
@@ -195,6 +222,16 @@ pnpm run build
195
222
 
196
223
  Connect Claude Code to DingTalk for bidirectional communication. Supports multi-turn conversations, task queues, scheduled jobs, and image recognition — helping teams build privately deployable AI assistants at minimal cost.
197
224
 
225
+ ### Community
226
+
227
+ Scan the QR code to join our DingTalk or WeChat group:
228
+
229
+ <p align="center">
230
+ <img src="assets/qrcode-dingtalk.jpg" alt="DingTalk Group QR Code" width="250">
231
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
232
+ <img src="assets/qrcode-wechat.jpg" alt="WeChat Group QR Code" width="250">
233
+ </p>
234
+
198
235
  ### Table of Contents
199
236
 
200
237
  - [Quick Start](#quick-start)
@@ -338,6 +375,23 @@ pm2 start --name "cc-ding-{clientId}" npx -- -p cc-ding cc-ding run -ci {clientI
338
375
  | `permissionMode` | Claude process permission mode (default `acceptEdits`; `bypassPermissions` must be set explicitly and warns at startup), options: `default`, `acceptEdits`, `plan`, `auto`, `bypassPermissions`, `dontAsk` |
339
376
  | `freedomMode` | Freedom mode toggle (default `false`; when enabled, skips group whitelist check) |
340
377
  | `preBash` | Global pre-bash command for `/bash` |
378
+ | `agent` | AI execution engine: `claude` (default) or `codex` (OpenAI Codex CLI) |
379
+
380
+ ### Multi-Agent Architecture (v1.1.0)
381
+
382
+ cc-ding introduces a multi-Agent abstraction layer, allowing different AI execution engines to be switched per group. Set the `agent` field in group config to choose the engine:
383
+
384
+ ```json
385
+ {
386
+ "conversationId": "group_id",
387
+ "agent": "claude" // or "codex"
388
+ }
389
+ ```
390
+
391
+ - **`claude`** (default): Uses Claude Code CLI, supports `--resume` session recovery, API Key rotation, context compaction, and full feature set
392
+ - **`codex`**: Uses OpenAI Codex CLI, supports thread recovery (`thread_id`), sandbox mode (`full-auto`), auto-timeout recovery, etc.
393
+
394
+ All commands (`/goon`, `/!`, `/new`, `/cc`, etc.) support multi-Agent routing without additional configuration.
341
395
 
342
396
  #### Security Notes
343
397
 
@@ -351,7 +405,7 @@ All data is stored under `~/.cc-ding/{clientId}/`:
351
405
 
352
406
  | Type | Path |
353
407
  |------|------|
354
- | Sessions | `{MD5}/.sessions/{claudeSessionId}/session.{json,log}` |
408
+ | Sessions | `{MD5}/.sessions/{claudeSessionId|agentSessionId}/session.{json,log}` |
355
409
  | Tasks | `{MD5}/.tasks/{timestamp}/task.{json,log}` |
356
410
  | Cron jobs | `cron.json` |
357
411
  | Image cache | `{MD5}/.images/` |
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var r=exports&&exports.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const a=require("../src/common"),g=require("commander"),t=require("../src/biz/session"),d=r(require("path")),m=require("../src/biz/cc-ding-cli"),I=require("../src/biz/lock"),u=require("../src/biz/doctor"),f=require("../src/biz/notify"),p=r(require("fs"));(0,a.loadEnv)(),process.removeAllListeners("warning"),process.setMaxListeners(0),process.on("uncaughtException",function(e){console.log("Caught exception: "+e),process.exit(1)});const o=new g.Command;o.addHelpText("before",`
2
+ "use strict";var g=exports&&exports.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const m=require("../src/common"),v=require("commander"),l=require("../src/biz/session"),a=g(require("path")),D=require("../src/biz/cc-ding-cli"),q=require("../src/biz/lock"),f=require("../src/biz/doctor"),_=require("../src/biz/notify"),y=require("../src/biz/send-queue"),p=g(require("fs"));(0,m.loadEnv)(),process.removeAllListeners("warning"),process.setMaxListeners(0),process.on("uncaughtException",function(e){console.log("Caught exception: "+e),process.exit(1)});const s=new v.Command;s.addHelpText("before",`
3
3
  cc-ding for connect ClaudeCode to DingDingRobot
4
4
  `).addHelpText("after",`
5
5
  Examples:
6
6
  $ cc-ding init -ci {clientId} -cs {clientSecret} -u {user} -dt {defaultDingToken}
7
7
  $ cc-ding run -ci {clientId}
8
- `).version((0,a.projUtil)().getPkgVersion()),o.command("init").description("\u521D\u59CB\u5316cc-ding\u914D\u7F6E\u6587\u4EF6, \u751F\u6210\u6700\u7B80config.json").requiredOption("-ci, --clientId <value>","clientId").requiredOption("-cs, --clientSecret <value>","clientSecret (\u9489\u9489Stream\u8FDE\u63A5\u5BC6\u94A5)").requiredOption("-u, --user <value>","user (\u81EA\u5DF1\u7684\u624B\u673A\u53F7\u6216\u5DE5\u53F7, \u81EA\u52A8\u8BBE\u4E3Aowner\u5E76\u52A0\u5165\u767D\u540D\u5355)").requiredOption("-dt, --defaultDingToken <value>","defaultDingToken (\u515C\u5E95\u9489\u9489\u673A\u5668\u4EBAToken)").option("-cn, --clientName <value>","clientName (\u673A\u5668\u4EBA\u540D\u79F0, \u53EF\u9009)").action(async e=>{const n=process.version.slice(1);parseInt(n.split(".")[0],10)<22&&(console.log(`
8
+ `).version((0,m.projUtil)().getPkgVersion()),s.command("init").description("\u521D\u59CB\u5316cc-ding\u914D\u7F6E\u6587\u4EF6, \u751F\u6210\u6700\u7B80config.json").requiredOption("-ci, --clientId <value>","clientId").requiredOption("-cs, --clientSecret <value>","clientSecret (\u9489\u9489Stream\u8FDE\u63A5\u5BC6\u94A5)").requiredOption("-u, --user <value>","user (\u81EA\u5DF1\u7684\u624B\u673A\u53F7\u6216\u5DE5\u53F7, \u81EA\u52A8\u8BBE\u4E3Aowner\u5E76\u52A0\u5165\u767D\u540D\u5355)").requiredOption("-dt, --defaultDingToken <value>","defaultDingToken (\u515C\u5E95\u9489\u9489\u673A\u5668\u4EBAToken)").option("-cn, --clientName <value>","clientName (\u673A\u5668\u4EBA\u540D\u79F0, \u53EF\u9009)").action(async e=>{const n=process.version.slice(1);parseInt(n.split(".")[0],10)<22&&(console.log(`
9
9
  \u274C Node \u7248\u672C\u8FC7\u4F4E\uFF0C\u65E0\u6CD5\u6267\u884C init \u547D\u4EE4`),console.log(` \u5F53\u524D\u7248\u672C\uFF1A${n}`),console.log(" \u8981\u6C42\uFF1ANode >= 22"),console.log(`
10
10
  \u{1F4A1} \u8BF7\u5347\u7EA7 Node \u7248\u672C\u540E\u91CD\u65B0\u8FD0\u884C
11
- `),process.exit(1));const i=`${`${(0,t.getHomeDir)()}/.cc-ding/${e.clientId}`}/config.json`;p.default.existsSync(i)&&(console.log(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: ${i}`),console.log("\u5982\u9700\u91CD\u65B0\u521D\u59CB\u5316, \u8BF7\u5148\u5220\u9664\u5DF2\u6709\u914D\u7F6E\u6587\u4EF6"),process.exit(1));const c={clientName:e.clientName||"cc\u52A9\u624B",owner:e.user,whiteUserList:[e.user],clientSecret:e.clientSecret,defaultDingToken:e.defaultDingToken,conversations:[],includeThinking:!1,resultOnly:!0,debug:!1,taskQueueSize:10,taskHandlerCount:1,sessionMaxConcurrency:20};(0,t.initClientDir)(e.clientId,c),console.log("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210:",i),console.log(""),console.log("\u540E\u7EED\u6B65\u9AA4:"),console.log(" 1. \u7F16\u8F91 config.json \u6DFB\u52A0 conversations \u914D\u7F6E(\u7FA4\u804A\u9700\u914D\u7F6EdingToken)"),console.log(" 2. \u542F\u52A8\u673A\u5668\u4EBA: cc-ding run -ci",e.clientId),console.log(" 3. \u63A8\u8350PM2\u542F\u52A8:"),console.log(` pm2 start --name "cc-ding-${e.clientId}" npx -- -p cc-ding cc-ding run -ci ${e.clientId}`)}),o.command("run").description(`
11
+ `),process.exit(1));const i=`${`${(0,l.getHomeDir)()}/.cc-ding/${e.clientId}`}/config.json`;p.default.existsSync(i)&&(console.log(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: ${i}`),console.log("\u5982\u9700\u91CD\u65B0\u521D\u59CB\u5316, \u8BF7\u5148\u5220\u9664\u5DF2\u6709\u914D\u7F6E\u6587\u4EF6"),process.exit(1));const o={clientName:e.clientName||"cc\u52A9\u624B",owner:e.user,whiteUserList:[e.user],clientSecret:e.clientSecret,defaultDingToken:e.defaultDingToken,conversations:[],includeThinking:!1,resultOnly:!0,debug:!1,taskQueueSize:10,taskHandlerCount:1,sessionMaxConcurrency:20};(0,l.initClientDir)(e.clientId,o),console.log("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210:",i),console.log(""),console.log("\u540E\u7EED\u6B65\u9AA4:"),console.log(" 1. \u7F16\u8F91 config.json \u6DFB\u52A0 conversations \u914D\u7F6E(\u7FA4\u804A\u9700\u914D\u7F6EdingToken)"),console.log(" 2. \u542F\u52A8\u673A\u5668\u4EBA: cc-ding run -ci",e.clientId),console.log(" 3. \u63A8\u8350PM2\u542F\u52A8:"),console.log(` pm2 start --name "cc-ding-${e.clientId}" npx -- -p cc-ding cc-ding run -ci ${e.clientId}`)}),s.command("run").description(`
12
12
  - \u529F\u80FD: \u9489\u9489\u673A\u5668\u4EBA\u5BF9\u63A5\u672C\u5730Claude, \u652F\u6301\u4F1A\u8BDD\u6A21\u5F0F\u548C\u4EFB\u52A1\u961F\u5217\u6A21\u5F0F
13
13
  - \u4F1A\u8BDD\u6570\u636E\u8DEF\u5F84: ~/.cc-ding/{clientId}/{MD5}/.sessions/{claudeSessionId}/session.{json|log}
14
14
  - \u4EFB\u52A1\u6570\u636E\u8DEF\u5F84: ~/.cc-ding/{clientId}/{MD5}/.tasks/{\u65F6\u95F4\u6233}/task.{json|log}
@@ -33,5 +33,5 @@ Examples:
33
33
  - 429\u81EA\u52A8\u5207\u6362: \u81EA\u52A8\u5207\u6362\u5230API Key\u6A21\u5F0F
34
34
  - Key\u8F6E\u6362: API Key\u9047\u5230429\u6216\u8FDE\u7EEDTPM\u4E0D\u7A33\u5B9A\u65F6\u81EA\u52A8\u6362Key
35
35
  - \u8DE8\u5929\u91CD\u7F6E: \u6BCF\u65E5\u81EA\u52A8\u91CD\u7F6EAPI Key\u72B6\u6001
36
- `).requiredOption("-ci, --clientId <value>","clientId").action(async e=>{(0,t.ensureClientDir)(e.clientId);const n=d.default.join((0,t.getHomeDir)(),".cc-ding",e.clientId);(0,I.acquirePidLock)(n,e.clientId),await new m.DingClaude(e.clientId).run()}),o.command("doctor").description("\u68C0\u67E5\u6307\u5B9Aclient\u7684\u914D\u7F6E\u6587\u4EF6schema\u5408\u6CD5\u6027\u548C\u6709\u6548\u6027").requiredOption("-ci, --clientId <value>","clientId").action(async e=>{const n=d.default.join((0,t.getHomeDir)(),".cc-ding",e.clientId),s=(0,u.runDoctor)(n);(0,u.printDoctorResults)(s)}),o.command("notify").description("\u901A\u8FC7\u9489\u9489\u673A\u5668\u4EBA\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u7FA4\u6216\u5355\u804A").requiredOption("-ci, --clientId <value>","clientId").requiredOption("-c, --conversations <value>","\u76EE\u6807\u4F1A\u8BDDID\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09").requiredOption("-m, --message <value>","\u6D88\u606F\u5185\u5BB9").option("-at, --atUserIds <value>","@ \u6307\u5B9A\u7528\u6237\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09").option("-mo, --mobile <value>","\u5355\u804A\u76EE\u6807\u624B\u673A\u53F7\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF0C\u4E0E conversations \u4E00\u4E00\u5BF9\u5E94\uFF09").option("-md, --markdown","\u4F7F\u7528 Markdown \u683C\u5F0F\u53D1\u9001",!1).action(async e=>{const n=e.conversations.split(",").map(c=>c.trim()).filter(Boolean),s=e.atUserIds?e.atUserIds.split(",").map(c=>c.trim()).filter(Boolean):[],l=e.mobile?e.mobile.split(",").map(c=>c.trim()).filter(Boolean):[];console.log(`\u{1F4E4} \u53D1\u9001\u6D88\u606F\u5230 ${n.length} \u4E2A\u4F1A\u8BDD...`);const i=await(0,f.sendNotify)({clientId:e.clientId,message:e.message,conversationIds:n,atUserIds:s,mobiles:l,markdown:e.markdown});console.log(`
37
- \u2705 \u6210\u529F: ${i.success}, \u274C \u5931\u8D25: ${i.fail}`),process.exit(i.fail>0?1:0)}),o.parse(process.argv);
36
+ `).requiredOption("-ci, --clientId <value>","clientId").action(async e=>{(0,l.ensureClientDir)(e.clientId);const n=a.default.join((0,l.getHomeDir)(),".cc-ding",e.clientId);(0,q.acquirePidLock)(n,e.clientId),await new D.DingClaude(e.clientId).run()}),s.command("doctor").description("\u68C0\u67E5\u6307\u5B9Aclient\u7684\u914D\u7F6E\u6587\u4EF6schema\u5408\u6CD5\u6027\u548C\u6709\u6548\u6027").requiredOption("-ci, --clientId <value>","clientId").action(async e=>{const n=a.default.join((0,l.getHomeDir)(),".cc-ding",e.clientId),c=(0,f.runDoctor)(n);(0,f.printDoctorResults)(c)}),s.command("notify").description("\u901A\u8FC7\u9489\u9489\u673A\u5668\u4EBA\u53D1\u9001\u6D88\u606F\u5230\u6307\u5B9A\u7FA4\u6216\u5355\u804A").requiredOption("-ci, --clientId <value>","clientId").requiredOption("-c, --conversations <value>","\u76EE\u6807\u4F1A\u8BDDID\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09").requiredOption("-m, --message <value>","\u6D88\u606F\u5185\u5BB9").option("-at, --atUserIds <value>","@ \u6307\u5B9A\u7528\u6237\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09").option("-mo, --mobile <value>","\u5355\u804A\u76EE\u6807\u624B\u673A\u53F7\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF0C\u4E0E conversations \u4E00\u4E00\u5BF9\u5E94\uFF09").option("-md, --markdown","\u4F7F\u7528 Markdown \u683C\u5F0F\u53D1\u9001",!1).action(async e=>{const n=e.conversations.split(",").map(o=>o.trim()).filter(Boolean),c=e.atUserIds?e.atUserIds.split(",").map(o=>o.trim()).filter(Boolean):[],t=e.mobile?e.mobile.split(",").map(o=>o.trim()).filter(Boolean):[];console.log(`\u{1F4E4} \u53D1\u9001\u6D88\u606F\u5230 ${n.length} \u4E2A\u4F1A\u8BDD...`);const i=await(0,_.sendNotify)({clientId:e.clientId,message:e.message,conversationIds:n,atUserIds:c,mobiles:t,markdown:e.markdown});console.log(`
37
+ \u2705 \u6210\u529F: ${i.success}, \u274C \u5931\u8D25: ${i.fail}`),process.exit(i.fail>0?1:0)}),s.command("push").description("\u4E3B\u52A8\u5411\u9489\u9489\u7FA4\u63A8\u9001\u56FE\u7247\u6216\u6587\u4EF6\uFF08\u901A\u8FC7\u6587\u4EF6\u4FE1\u53F7\u961F\u5217\u5F02\u6B65\u6295\u9012\uFF09").requiredOption("-ci, --clientId <value>","clientId").requiredOption("-c, --conversationId <value>","\u76EE\u6807\u4F1A\u8BDDID").option("-i, --image <path>","\u56FE\u7247\u6587\u4EF6\u8DEF\u5F84\uFF08\u4E0E --file \u4E8C\u9009\u4E00\u5FC5\u586B\uFF09").option("-f, --file <path>","\u6587\u4EF6\u8DEF\u5F84\uFF08\u4E0E --image \u4E8C\u9009\u4E00\u5FC5\u586B\uFF09").option("--caption <value>","\u9644\u52A0\u8BF4\u660E\u6587\u5B57").action(e=>{const{clientId:n,conversationId:c,image:t,file:i,caption:o}=e;!t&&!i&&(console.error("\u274C \u8BF7\u6307\u5B9A --image \u6216 --file \u53C2\u6570"),process.exit(1)),t&&i&&(console.error(" --image \u548C --file \u4E0D\u80FD\u540C\u65F6\u6307\u5B9A"),process.exit(1));const d=t||i,r=a.default.isAbsolute(d)?d:a.default.resolve(d);p.default.existsSync(r)||(console.error(`\u274C \u6587\u4EF6\u4E0D\u5B58\u5728: ${r}`),process.exit(1));const u=t?"image":"file",I={type:u,path:r,conversationId:c,caption:o,timestamp:Date.now()};(0,y.writeSendSignal)(n,I),console.log("\u2705 \u63A8\u9001\u4FE1\u53F7\u5DF2\u5199\u5165\uFF0C\u7B49\u5F85\u4E3B\u8FDB\u7A0B\u5904\u7406"),console.log(` \u7C7B\u578B: ${u}`),console.log(` \u4F1A\u8BDD: ${c}`),console.log(` \u6587\u4EF6: ${r}`),o&&console.log(` \u8BF4\u660E: ${o}`)}),s.parse(process.argv);
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createAgent=r,exports.listAgentTypes=u;const n=require("./claude-agent"),t=require("./codex-agent");function r(e){if(e==="claude")return new n.ClaudeAgent;if(e==="codex")return new t.CodexAgent;throw new Error(`Unknown agent type: ${e}`)}function u(){return["claude","codex"]}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});