evolcore 0.0.1 → 0.0.2

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.
Files changed (371) hide show
  1. package/CHANGELOG.md +813 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/assets/brand/evolcore/README.md +19 -0
  8. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  9. package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
  10. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  11. package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
  12. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  13. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  14. package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
  15. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  16. package/assets/brand/evolcore/evolcore-logo.svg +14 -0
  17. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  18. package/assets/brand/evolcore/evolcore-mark.svg +10 -0
  19. package/bin/ec-safe-output.js +161 -0
  20. package/bin/ec.js +29 -0
  21. package/dist/agents/baseagent.js +163 -0
  22. package/dist/agents/claude-runner.js +2385 -0
  23. package/dist/agents/codex-app-server-client.js +448 -0
  24. package/dist/agents/codex-runner.js +2639 -0
  25. package/dist/agents/gemini-runner.js +666 -0
  26. package/dist/agents/runner-types.js +75 -0
  27. package/dist/aun/aid/agentmd.js +216 -0
  28. package/dist/aun/aid/client.js +132 -0
  29. package/dist/aun/aid/control-aid.js +91 -0
  30. package/dist/aun/aid/identity.js +518 -0
  31. package/dist/aun/aid/index.js +4 -0
  32. package/dist/aun/aid/store.js +74 -0
  33. package/dist/aun/aid/types.js +1 -0
  34. package/dist/aun/aid/validation.js +21 -0
  35. package/dist/aun/group-identity.js +10 -0
  36. package/dist/aun/msg/group-index.js +6 -0
  37. package/dist/aun/msg/group.js +1231 -0
  38. package/dist/aun/msg/history.js +123 -0
  39. package/dist/aun/msg/index.js +5 -0
  40. package/dist/aun/msg/p2p.js +393 -0
  41. package/dist/aun/msg/payload-type.js +27 -0
  42. package/dist/aun/msg/upload.js +137 -0
  43. package/dist/aun/outbox.js +160 -0
  44. package/dist/aun/rpc/caller.js +42 -0
  45. package/dist/aun/rpc/connection.js +25 -0
  46. package/dist/aun/rpc/index.js +2 -0
  47. package/dist/aun/service-proxy.js +225 -0
  48. package/dist/aun/storage/download.js +29 -0
  49. package/dist/aun/storage/index.js +3 -0
  50. package/dist/aun/storage/manage.js +10 -0
  51. package/dist/aun/storage/upload.js +68 -0
  52. package/dist/channels/aun.js +4147 -0
  53. package/dist/channels/daemon.js +422 -0
  54. package/dist/channels/dingtalk.js +649 -0
  55. package/dist/channels/feishu.js +1789 -0
  56. package/dist/channels/qqbot.js +409 -0
  57. package/dist/channels/wechat.js +817 -0
  58. package/dist/channels/wecom.js +565 -0
  59. package/dist/cli/agent-command.js +641 -0
  60. package/dist/cli/agent.js +1059 -0
  61. package/dist/cli/aun-commands.js +1948 -0
  62. package/dist/cli/bench.js +1228 -0
  63. package/dist/cli/cli-argv.js +66 -0
  64. package/dist/cli/code-stats.js +329 -0
  65. package/dist/cli/command-log.js +82 -0
  66. package/dist/cli/config-selector.js +69 -0
  67. package/dist/cli/config.js +261 -0
  68. package/dist/cli/ctl-command.js +62 -0
  69. package/dist/cli/daemon-commands.js +2887 -0
  70. package/dist/cli/fs-command.js +1447 -0
  71. package/dist/cli/handoff-command.js +302 -0
  72. package/dist/cli/help.js +31 -0
  73. package/dist/cli/index.js +358 -0
  74. package/dist/cli/init-channel.js +1377 -0
  75. package/dist/cli/init.js +553 -0
  76. package/dist/cli/link-rules.js +240 -0
  77. package/dist/cli/model.js +590 -0
  78. package/dist/cli/net-check.js +723 -0
  79. package/dist/cli/queue-command.js +126 -0
  80. package/dist/cli/response.js +345 -0
  81. package/dist/cli/restart-monitor.js +456 -0
  82. package/dist/cli/stats.js +607 -0
  83. package/dist/cli/task-context.js +80 -0
  84. package/dist/cli/trigger-command.js +505 -0
  85. package/dist/cli/version.js +88 -0
  86. package/dist/cli/watch-logs.js +33 -0
  87. package/dist/cli/watch-msg.js +673 -0
  88. package/dist/config/boot-log.js +266 -0
  89. package/dist/config/builtin-role-templates.js +30 -0
  90. package/dist/config/builtin-roles.js +85 -0
  91. package/dist/config/config-batch-get.js +11 -0
  92. package/dist/config/config-field-policy.js +261 -0
  93. package/dist/config/config-manager.js +916 -0
  94. package/dist/config/config-operation-service.js +384 -0
  95. package/dist/config/contact-book.js +371 -0
  96. package/dist/config/gateway-config.js +858 -0
  97. package/dist/config/lifecycle.js +17 -0
  98. package/dist/config/mention-mode.js +27 -0
  99. package/dist/config/merge.js +161 -0
  100. package/dist/config/owner-policy.js +4 -0
  101. package/dist/config/peer-role-resolver.js +139 -0
  102. package/dist/config/resolved-config-op.js +483 -0
  103. package/dist/config/role-config-v4-startup.js +32 -0
  104. package/dist/config/role-config-v5-startup.js +27 -0
  105. package/dist/config/role-schema.js +105 -0
  106. package/dist/config/role-service.js +159 -0
  107. package/dist/config/role-store.js +204 -0
  108. package/dist/config/roles.js +55 -0
  109. package/dist/config/schema-registry.js +154 -0
  110. package/dist/config/snapshot.js +598 -0
  111. package/dist/config-store.js +503 -0
  112. package/dist/core/auth/agent-delegation.js +111 -0
  113. package/dist/core/auth/auth-gateway.js +166 -0
  114. package/dist/core/auth/authenticated-actor.js +6 -0
  115. package/dist/core/auth/authorization-audit.js +110 -0
  116. package/dist/core/auth/operation-authorizer.js +718 -0
  117. package/dist/core/auth/operation-catalog.js +675 -0
  118. package/dist/core/baseagent-loader.js +54 -0
  119. package/dist/core/bootstrap-service.js +175 -0
  120. package/dist/core/capability/capability-manager.js +316 -0
  121. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  122. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  123. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  124. package/dist/core/capability/types.js +27 -0
  125. package/dist/core/causation/audit.js +103 -0
  126. package/dist/core/causation/aun-association.js +111 -0
  127. package/dist/core/causation/context.js +93 -0
  128. package/dist/core/causation/index.js +4 -0
  129. package/dist/core/causation/types.js +2 -0
  130. package/dist/core/channel-loader.js +277 -0
  131. package/dist/core/command/agent-control.js +616 -0
  132. package/dist/core/command/cli-intent-parser.js +225 -0
  133. package/dist/core/command/command-handler.js +1638 -0
  134. package/dist/core/command/menu-handler.js +3354 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1524 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3056 -0
  139. package/dist/core/daemon-file-cache.js +216 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +740 -0
  142. package/dist/core/evolagent-registry.js +546 -0
  143. package/dist/core/evolagent.js +331 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +657 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +803 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1436 -0
  161. package/dist/core/message/message-utils.js +70 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +3776 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/stream-debouncer.js +130 -0
  168. package/dist/core/message/stream-idle-monitor.js +124 -0
  169. package/dist/core/model/config-scope.js +162 -0
  170. package/dist/core/model/field-scope.js +78 -0
  171. package/dist/core/model/model-catalog.js +227 -0
  172. package/dist/core/model/model-diagnostics.js +182 -0
  173. package/dist/core/model/model-permission.js +90 -0
  174. package/dist/core/permission/approval-gateway.js +1017 -0
  175. package/dist/core/permission/ec-command-parser.js +347 -0
  176. package/dist/core/permission/execution-sandbox.js +16 -0
  177. package/dist/core/permission/index.js +6 -0
  178. package/dist/core/permission/mode.js +24 -0
  179. package/dist/core/permission/sandbox-runtime.js +265 -0
  180. package/dist/core/permission/tool-policy.js +987 -0
  181. package/dist/core/permission/unix-socket-policy.js +99 -0
  182. package/dist/core/protected-paths.js +330 -0
  183. package/dist/core/relation/peer-identity.js +222 -0
  184. package/dist/core/relation/peer-key.js +1 -0
  185. package/dist/core/role/runtime-policy.js +141 -0
  186. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  187. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  188. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  189. package/dist/core/session/session-file-adapter.js +7 -0
  190. package/dist/core/session/session-file-health.js +45 -0
  191. package/dist/core/session/session-fs-store.js +232 -0
  192. package/dist/core/session/session-key.js +24 -0
  193. package/dist/core/session/session-manager.js +1587 -0
  194. package/dist/core/session/session-mapper.js +100 -0
  195. package/dist/core/session/session-renew.js +314 -0
  196. package/dist/core/session/session-title.js +128 -0
  197. package/dist/core/session/session-turn-coordinator.js +205 -0
  198. package/dist/core/session/session-turns.js +67 -0
  199. package/dist/core/system-channels.js +29 -0
  200. package/dist/eck/baseagent-caps.js +18 -0
  201. package/dist/eck/detect.js +47 -0
  202. package/dist/eck/group-rules-sync.js +345 -0
  203. package/dist/eck/init.js +77 -0
  204. package/dist/eck/kit-renderer.js +359 -0
  205. package/dist/eck/manifest-engine.js +446 -0
  206. package/dist/eck/message-renderer.js +199 -0
  207. package/dist/eck/rules-loader.js +28 -0
  208. package/dist/index.js +2870 -4
  209. package/dist/ipc.js +748 -0
  210. package/dist/paths.js +262 -0
  211. package/dist/product.js +18 -0
  212. package/dist/response-system/context-builder.js +71 -0
  213. package/dist/response-system/coordinator.js +117 -0
  214. package/dist/response-system/decision-executor.js +86 -0
  215. package/dist/response-system/engines/v1/index.js +21 -0
  216. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  217. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  218. package/dist/response-system/engines/v1/types.js +1 -0
  219. package/dist/response-system/extensions.js +41 -0
  220. package/dist/response-system/index.js +6 -0
  221. package/dist/response-system/modes/index.js +7 -0
  222. package/dist/response-system/modes/single-session/index.js +72 -0
  223. package/dist/response-system/queues/fifo-queue.js +44 -0
  224. package/dist/response-system/queues/index.js +6 -0
  225. package/dist/response-system/queues/lifo-queue.js +42 -0
  226. package/dist/response-system/queues/priority-queue.js +63 -0
  227. package/dist/response-system/registry.js +97 -0
  228. package/dist/response-system/resolver.js +37 -0
  229. package/dist/response-system/selector.js +23 -0
  230. package/dist/response-system/types.js +7 -0
  231. package/dist/stats/billing.js +151 -0
  232. package/dist/stats/budget.js +93 -0
  233. package/dist/stats/db.js +403 -0
  234. package/dist/stats/eck-vars.js +89 -0
  235. package/dist/stats/index.js +11 -0
  236. package/dist/stats/normalizer.js +80 -0
  237. package/dist/stats/price-resolver.js +138 -0
  238. package/dist/stats/query.js +763 -0
  239. package/dist/stats/role-budget.js +168 -0
  240. package/dist/stats/writer.js +151 -0
  241. package/dist/trigger/anomaly-store.js +258 -0
  242. package/dist/trigger/audit.js +152 -0
  243. package/dist/trigger/event-source.js +119 -0
  244. package/dist/trigger/feedback.js +685 -0
  245. package/dist/trigger/history.js +290 -0
  246. package/dist/trigger/manager.js +291 -0
  247. package/dist/trigger/parser.js +520 -0
  248. package/dist/trigger/patch.js +148 -0
  249. package/dist/trigger/scheduler.js +1600 -0
  250. package/dist/trigger/script-executor.js +155 -0
  251. package/dist/trigger/state.js +145 -0
  252. package/dist/trigger/types.js +1 -0
  253. package/dist/trigger/validation.js +621 -0
  254. package/dist/types.js +12 -0
  255. package/dist/utils/aid-bind.js +299 -0
  256. package/dist/utils/atomic-write.js +95 -0
  257. package/dist/utils/avatar-upload.js +123 -0
  258. package/dist/utils/cross-platform.js +297 -0
  259. package/dist/utils/ecweb-utils.js +73 -0
  260. package/dist/utils/error-dict.json +153 -0
  261. package/dist/utils/error-utils.js +349 -0
  262. package/dist/utils/instance-registry.js +437 -0
  263. package/dist/utils/locale.js +21 -0
  264. package/dist/utils/log-writer.js +224 -0
  265. package/dist/utils/logger.js +89 -0
  266. package/dist/utils/markdown-to-plain-text.js +20 -0
  267. package/dist/utils/media-cache.js +271 -0
  268. package/dist/utils/model-prices.jsonl +17 -0
  269. package/dist/utils/npm-ops.js +210 -0
  270. package/dist/utils/process-introspect.js +133 -0
  271. package/dist/utils/process-tree-stats.js +271 -0
  272. package/dist/utils/project-path.js +74 -0
  273. package/dist/utils/stats.js +410 -0
  274. package/dist/utils/tool-summary.js +284 -0
  275. package/dist/utils/welcome.js +268 -0
  276. package/kits/docs/GUIDE.md +20 -0
  277. package/kits/docs/INDEX.md +65 -0
  278. package/kits/docs/aun/CHEATSHEET.md +19 -0
  279. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  280. package/kits/docs/channels/aun.md +65 -0
  281. package/kits/docs/channels/feishu.md +56 -0
  282. package/kits/docs/context-assembly.md +366 -0
  283. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  284. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  285. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  286. package/kits/docs/eck_templates/runtime.template.md +19 -0
  287. package/kits/docs/evolcore/INDEX.md +66 -0
  288. package/kits/docs/evolcore/agent.md +69 -0
  289. package/kits/docs/evolcore/aid.md +49 -0
  290. package/kits/docs/evolcore/config.md +149 -0
  291. package/kits/docs/evolcore/ctl.md +46 -0
  292. package/kits/docs/evolcore/event.md +216 -0
  293. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  294. package/kits/docs/evolcore/fs.md +101 -0
  295. package/kits/docs/evolcore/group-fs.md +17 -0
  296. package/kits/docs/evolcore/group-rules.md +226 -0
  297. package/kits/docs/evolcore/group.md +141 -0
  298. package/kits/docs/evolcore/model.md +47 -0
  299. package/kits/docs/evolcore/msg.md +130 -0
  300. package/kits/docs/evolcore/response.md +80 -0
  301. package/kits/docs/evolcore/rpc.md +35 -0
  302. package/kits/docs/evolcore/self-summary.md +29 -0
  303. package/kits/docs/evolcore/stats.md +70 -0
  304. package/kits/docs/evolcore/storage.md +49 -0
  305. package/kits/docs/evolcore/trigger.md +524 -0
  306. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  307. package/kits/docs/identity/PATH_OPS.md +16 -0
  308. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  309. package/kits/docs/identity/identity-tools.md +26 -0
  310. package/kits/docs/path-registry.md +43 -0
  311. package/kits/docs/prompt-loading-architecture.md +266 -0
  312. package/kits/docs/venues/aun-group.md +45 -0
  313. package/kits/docs/venues/aun-private.md +10 -0
  314. package/kits/docs/venues/client-desktop.md +10 -0
  315. package/kits/docs/venues/client-mobile.md +10 -0
  316. package/kits/docs/venues/feishu-group.md +13 -0
  317. package/kits/docs/venues/feishu-private.md +9 -0
  318. package/kits/docs/venues/group.md +25 -0
  319. package/kits/docs/venues/private.md +10 -0
  320. package/kits/eck_manifest.auxiliary.json +43 -0
  321. package/kits/eck_manifest.json +191 -0
  322. package/kits/eck_message_manifest.json +63 -0
  323. package/kits/migrations/README-role-config-v4.md +32 -0
  324. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  325. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  326. package/kits/migrations/rename-config-file.mjs +99 -0
  327. package/kits/rules/01-overview.md +142 -0
  328. package/kits/rules/02-navigation.md +76 -0
  329. package/kits/rules/03-identity.md +34 -0
  330. package/kits/rules/04-relation.md +59 -0
  331. package/kits/rules/05-venue.md +44 -0
  332. package/kits/rules/06-channel.md +59 -0
  333. package/kits/schemas/_meta.json +29 -0
  334. package/kits/schemas/agent-config.schema.1.json +177 -0
  335. package/kits/schemas/agent-config.schema.2.json +239 -0
  336. package/kits/schemas/agent-config.schema.3.json +119 -0
  337. package/kits/schemas/agent-config.schema.4.json +208 -0
  338. package/kits/schemas/agent-config.schema.5.json +326 -0
  339. package/kits/schemas/contact-book.schema.1.json +36 -0
  340. package/kits/schemas/daemon.schema.1.json +90 -0
  341. package/kits/schemas/defaults.schema.1.json +81 -0
  342. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  343. package/kits/schemas/migrations/README.md +28 -0
  344. package/kits/schemas/relation-config.schema.1.json +158 -0
  345. package/kits/schemas/relation-config.schema.2.json +73 -0
  346. package/kits/schemas/relation-config.schema.3.json +50 -0
  347. package/kits/schemas/relation-config.schema.4.json +47 -0
  348. package/kits/schemas/role-config.schema.1.json +200 -0
  349. package/kits/schemas/role-registry.schema.1.json +35 -0
  350. package/kits/schemas/single-session.schema.1.json +31 -0
  351. package/kits/templates/bootstrap-welcome.md +15 -0
  352. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  353. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  354. package/kits/templates/message-fragments/inject-default.md +2 -0
  355. package/kits/templates/message-fragments/item.md +2 -0
  356. package/kits/templates/roles/admin.json +8 -0
  357. package/kits/templates/roles/member.json +40 -0
  358. package/kits/templates/roles/owner.json +8 -0
  359. package/kits/templates/roles/visitor.json +39 -0
  360. package/kits/templates/system-fragments/baseagent.md +14 -0
  361. package/kits/templates/system-fragments/bootstrap.md +16 -0
  362. package/kits/templates/system-fragments/channel.md +48 -0
  363. package/kits/templates/system-fragments/commands.md +26 -0
  364. package/kits/templates/system-fragments/identity.md +11 -0
  365. package/kits/templates/system-fragments/relation.md +19 -0
  366. package/kits/templates/system-fragments/session.md +53 -0
  367. package/kits/templates/system-fragments/venue.md +31 -0
  368. package/package.json +50 -15
  369. package/dist/index.d.ts +0 -7
  370. package/dist/index.d.ts.map +0 -1
  371. package/dist/index.js.map +0 -1
@@ -0,0 +1,649 @@
1
+ import { randomInt } from 'crypto';
2
+ import { logger } from '../utils/logger.js';
3
+ import { requireOptional } from '../utils/npm-ops.js';
4
+ import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
5
+ import { formatItemsAsText } from '../core/message/items-formatter.js';
6
+ import { initWelcomeManager, sendWelcomeIfNeeded } from '../utils/welcome.js';
7
+ import { isValidAid } from '../aun/aid/validation.js';
8
+ import { bindContactAlias } from '../config/contact-book.js';
9
+ export const DINGTALK_BIND_CODE_TTL_MS = 10 * 60 * 1000;
10
+ export const DINGTALK_BIND_MAX_FAILED_ATTEMPTS = 5;
11
+ const pendingContactBinds = new Map();
12
+ export function registerPendingDingtalkContactBind(req) {
13
+ const selfAid = String(req.selfAid || '').trim();
14
+ const channelName = String(req.channelName || '').trim();
15
+ const primaryId = String(req.primaryId || '').trim();
16
+ const now = req.now ?? Date.now();
17
+ const code = req.code ? String(req.code).trim() : generateDingtalkBindCode();
18
+ if (!isValidAid(selfAid))
19
+ return { ok: false, error: `invalid selfAid: ${req.selfAid}` };
20
+ if (!channelName)
21
+ return { ok: false, error: 'missing channelName' };
22
+ if (!isValidAid(primaryId))
23
+ return { ok: false, error: `invalid primaryId: ${req.primaryId}` };
24
+ if (!/^\d{6}$/.test(code))
25
+ return { ok: false, error: 'binding code must be 6 digits' };
26
+ const key = pendingBindKey(selfAid, channelName);
27
+ const replaced = pendingContactBinds.has(key);
28
+ const item = {
29
+ selfAid,
30
+ channelName,
31
+ primaryId,
32
+ code,
33
+ createdAt: now,
34
+ expiresAt: now + DINGTALK_BIND_CODE_TTL_MS,
35
+ failedAttempts: 0,
36
+ maxFailedAttempts: DINGTALK_BIND_MAX_FAILED_ATTEMPTS,
37
+ };
38
+ pendingContactBinds.set(key, item);
39
+ return { ok: true, code, expiresAt: item.expiresAt, replaced };
40
+ }
41
+ export function handlePendingDingtalkContactBindMessage(ctx) {
42
+ if (ctx.channelType !== 'dingtalk')
43
+ return { handled: false };
44
+ const selfAid = String(ctx.selfAid || '').trim();
45
+ const channelName = String(ctx.channelName || '').trim();
46
+ if (!selfAid || !channelName)
47
+ return { handled: false };
48
+ const key = pendingBindKey(selfAid, channelName);
49
+ const item = pendingContactBinds.get(key);
50
+ if (!item)
51
+ return { handled: false };
52
+ const now = ctx.now ?? Date.now();
53
+ if (now >= item.expiresAt) {
54
+ pendingContactBinds.delete(key);
55
+ return {
56
+ handled: true,
57
+ status: 'expired',
58
+ remainingAttempts: 0,
59
+ reply: '钉钉身份绑定码已超时,本次绑定失败。请重新执行 ec init dingtalk 并再次扫码绑定。',
60
+ };
61
+ }
62
+ if (ctx.chatType !== 'private')
63
+ return { handled: false };
64
+ const input = String(ctx.content ?? '').trim();
65
+ if (!/^\d+$/.test(input) || !/^\d{6}$/.test(input)) {
66
+ return {
67
+ handled: true,
68
+ status: 'format',
69
+ reply: '请直接发送 6 位数字绑定码。格式错误不计入错误次数。',
70
+ remainingAttempts: item.maxFailedAttempts - item.failedAttempts,
71
+ };
72
+ }
73
+ if (input !== item.code) {
74
+ item.failedAttempts += 1;
75
+ const remaining = Math.max(0, item.maxFailedAttempts - item.failedAttempts);
76
+ if (remaining === 0) {
77
+ pendingContactBinds.delete(key);
78
+ return {
79
+ handled: true,
80
+ status: 'failed',
81
+ remainingAttempts: 0,
82
+ reply: '绑定码错误次数已达上限,本次钉钉身份绑定失败。请重新执行 ec init dingtalk 并再次扫码绑定。',
83
+ };
84
+ }
85
+ return {
86
+ handled: true,
87
+ status: 'wrong-code',
88
+ remainingAttempts: remaining,
89
+ reply: `绑定码错误,请重新发送 6 位数字绑定码。剩余 ${remaining} 次机会。`,
90
+ };
91
+ }
92
+ const actorId = String(ctx.actorId || '').trim();
93
+ if (!actorId) {
94
+ return {
95
+ handled: true,
96
+ status: 'missing-actor',
97
+ remainingAttempts: item.maxFailedAttempts - item.failedAttempts,
98
+ reply: '无法识别当前钉钉发送者身份,未建立绑定。请重新发送绑定码或重新执行绑定流程。',
99
+ };
100
+ }
101
+ try {
102
+ bindContactAlias(item.selfAid, item.primaryId, 'dingtalk', actorId, item.channelName);
103
+ pendingContactBinds.delete(key);
104
+ return {
105
+ handled: true,
106
+ status: 'bound',
107
+ reply: `钉钉身份绑定成功:dingtalk:${actorId} -> ${item.primaryId}`,
108
+ };
109
+ }
110
+ catch (error) {
111
+ const message = error instanceof Error ? error.message : String(error);
112
+ return {
113
+ handled: true,
114
+ status: 'write-failed',
115
+ remainingAttempts: item.maxFailedAttempts - item.failedAttempts,
116
+ reply: `钉钉身份绑定写入失败:${message}`,
117
+ };
118
+ }
119
+ }
120
+ export function getPendingDingtalkContactBind(selfAid, channelName) {
121
+ const item = pendingContactBinds.get(pendingBindKey(selfAid, channelName));
122
+ return item ? { ...item } : null;
123
+ }
124
+ export function clearPendingDingtalkContactBinds() {
125
+ pendingContactBinds.clear();
126
+ }
127
+ function generateDingtalkBindCode() {
128
+ return String(randomInt(0, 1_000_000)).padStart(6, '0');
129
+ }
130
+ function pendingBindKey(selfAid, channelName) {
131
+ return `${String(selfAid || '').trim()}\u0000${String(channelName || '').trim()}`;
132
+ }
133
+ // ── Webhook SSRF validation ────────────────────────────────────────────────────
134
+ const WEBHOOK_RE = /^https:\/\/(api|oapi)\.dingtalk\.com\//;
135
+ // ── DingtalkChannel ────────────────────────────────────────────────────────────
136
+ export class DingtalkChannel {
137
+ agentAid;
138
+ channelName;
139
+ config;
140
+ client = null;
141
+ connected = false;
142
+ messageHandler = null;
143
+ recallHandler;
144
+ webhookCache = new Map();
145
+ conversationIdCache = new Map();
146
+ senderStaffIdCache = new Map();
147
+ seenMessages = new Map();
148
+ cleanupInterval = null;
149
+ projectPathProvider = null;
150
+ // Welcome message manager
151
+ welcomeManager;
152
+ constructor(config, agentAid, channelName) {
153
+ this.agentAid = agentAid;
154
+ this.channelName = channelName;
155
+ this.config = config;
156
+ // 初始化 welcomeManager(使用共享帮助函数)
157
+ if (agentAid && channelName) {
158
+ this.welcomeManager = initWelcomeManager('dingtalk', agentAid, channelName);
159
+ }
160
+ }
161
+ // ── Public helpers (testable) ──────────────────────────────────────────────
162
+ isValidWebhook(url) {
163
+ if (!url)
164
+ return false;
165
+ return WEBHOOK_RE.test(url);
166
+ }
167
+ isDuplicate(msgId) {
168
+ if (this.seenMessages.has(msgId))
169
+ return true;
170
+ this.seenMessages.set(msgId, Date.now());
171
+ return false;
172
+ }
173
+ resolveChatId(conversationType, conversationId, senderId) {
174
+ return conversationType === '2' ? conversationId : senderId;
175
+ }
176
+ shouldProcessGroupMessage(conversationId, isInAtList) {
177
+ if (this.config.requireMention === false)
178
+ return true;
179
+ if (this.config.freeResponseChats?.includes(conversationId))
180
+ return true;
181
+ return isInAtList;
182
+ }
183
+ extractText(content) {
184
+ if (!content)
185
+ return '';
186
+ const text = content.text;
187
+ if (typeof text === 'string')
188
+ return text.trim();
189
+ if (text && typeof text === 'object' && typeof text.content === 'string')
190
+ return text.content.trim();
191
+ return '';
192
+ }
193
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
194
+ async connect() {
195
+ const { clientId, clientSecret } = this.config;
196
+ if (!clientId || !clientSecret || clientId.includes('your-') || clientSecret.includes('your-')) {
197
+ throw new Error('DingTalk clientId/clientSecret not configured');
198
+ }
199
+ const { DWClient, TOPIC_ROBOT } = await requireOptional('dingtalk-stream');
200
+ this.client = new DWClient({ clientId, clientSecret });
201
+ this.client.registerCallbackListener(TOPIC_ROBOT, async (msg) => {
202
+ await this.handleIncoming(msg);
203
+ });
204
+ await this.client.connect();
205
+ this.connected = true;
206
+ // Hourly cleanup of old dedup entries
207
+ this.cleanupInterval = setInterval(() => {
208
+ const cutoff = Date.now() - 24 * 60 * 60 * 1000;
209
+ for (const [id, ts] of this.seenMessages) {
210
+ if (ts < cutoff)
211
+ this.seenMessages.delete(id);
212
+ }
213
+ }, 60 * 60 * 1000);
214
+ logger.info('[DingTalk] Connected via Stream Mode');
215
+ }
216
+ async disconnect() {
217
+ this.connected = false;
218
+ if (this.cleanupInterval) {
219
+ clearInterval(this.cleanupInterval);
220
+ this.cleanupInterval = null;
221
+ }
222
+ if (this.client) {
223
+ try {
224
+ this.client.disconnect();
225
+ }
226
+ catch { /* ignore */ }
227
+ this.client = null;
228
+ }
229
+ logger.info('[DingTalk] Disconnected');
230
+ }
231
+ onMessage(handler) {
232
+ this.messageHandler = handler;
233
+ }
234
+ onRecall(handler) {
235
+ this.recallHandler = handler;
236
+ }
237
+ // ── Inbound message handling ───────────────────────────────────────────────
238
+ async handleIncoming(msg) {
239
+ try {
240
+ const data = typeof msg.data === 'string' ? JSON.parse(msg.data) : msg.data;
241
+ const msgId = data.msgId;
242
+ const conversationType = data.conversationType;
243
+ const conversationId = data.conversationId;
244
+ const senderId = data.senderStaffId || data.senderId;
245
+ const senderNick = data.senderNick;
246
+ const sessionWebhook = data.sessionWebhook;
247
+ const msgtype = data.msgtype;
248
+ // Dedup
249
+ if (msgId && this.isDuplicate(msgId)) {
250
+ logger.debug(`[DingTalk] Duplicate message skipped: ${msgId}`);
251
+ return;
252
+ }
253
+ const chatId = this.resolveChatId(conversationType, conversationId, senderId);
254
+ const chatType = conversationType === '2' ? 'group' : 'private';
255
+ // Cache sender info for Open API sends
256
+ if (senderId)
257
+ this.senderStaffIdCache.set(chatId, senderId);
258
+ if (conversationId)
259
+ this.conversationIdCache.set(chatId, conversationId);
260
+ // Group gate
261
+ if (conversationType === '2') {
262
+ const isInAtList = !!(data.isInAtList || (data.atUsers && data.atUsers.length > 0));
263
+ if (!this.shouldProcessGroupMessage(conversationId, isInAtList)) {
264
+ logger.debug(`[DingTalk] Group message ignored (not mentioned): ${msgId}`);
265
+ return;
266
+ }
267
+ }
268
+ // Webhook cache (SSRF validated)
269
+ if (sessionWebhook && this.isValidWebhook(sessionWebhook)) {
270
+ this.webhookCache.set(chatId, sessionWebhook);
271
+ }
272
+ // ACK to prevent 60s retry
273
+ if (this.client && msg.headers?.messageId) {
274
+ try {
275
+ this.client.socketCallBackResponse(msg.headers.messageId, { response: JSON.stringify({ status: 'OK' }) });
276
+ }
277
+ catch (e) {
278
+ logger.warn('[DingTalk] ACK failed:', e);
279
+ }
280
+ }
281
+ // Dispatch by msgtype
282
+ if (!this.messageHandler)
283
+ return;
284
+ // 首次交互欢迎消息(使用共享帮助函数)
285
+ await sendWelcomeIfNeeded(this.welcomeManager, senderId, chatId, (id, text) => this.sendMessage(id, text), 'DingTalk');
286
+ if (msgtype === 'text' || !msgtype) {
287
+ const text = this.extractText(data);
288
+ if (!text)
289
+ return;
290
+ await this.messageHandler({
291
+ channelId: chatId, content: text, chatType,
292
+ peerId: senderId || '', peerName: senderNick, messageId: msgId,
293
+ });
294
+ }
295
+ else if (msgtype === 'picture' || msgtype === 'image') {
296
+ await this.handleImageMessage(data, chatId, chatType, senderId, senderNick, msgId);
297
+ }
298
+ else if (msgtype === 'file') {
299
+ await this.handleFileMessage(data, chatId, chatType, senderId, senderNick, msgId);
300
+ }
301
+ else if (msgtype === 'richText') {
302
+ await this.handleRichTextMessage(data, chatId, chatType, senderId, senderNick, msgId);
303
+ }
304
+ else {
305
+ await this.messageHandler({
306
+ channelId: chatId,
307
+ content: `[不支持的消息类型: ${msgtype}]`,
308
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
309
+ });
310
+ }
311
+ }
312
+ catch (error) {
313
+ logger.error('[DingTalk] Failed to process incoming message:', error);
314
+ }
315
+ }
316
+ // ── Inbound media handling ─────────────────────────────────────────────────
317
+ async handleImageMessage(data, chatId, chatType, senderId, senderNick, msgId) {
318
+ const content = typeof data.content === 'string' ? JSON.parse(data.content) : (data.content || {});
319
+ const downloadUrl = content.downloadUrl || content.downloadCode;
320
+ if (!downloadUrl) {
321
+ logger.warn('[DingTalk] Image message without downloadUrl');
322
+ await this.messageHandler({
323
+ channelId: chatId, content: '[图片下载失败:缺少下载链接]',
324
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
325
+ });
326
+ return;
327
+ }
328
+ try {
329
+ const { safeFetch, validateImage } = await import('../utils/media-cache.js');
330
+ const buffer = await safeFetch(downloadUrl, { skipSsrfCheck: true });
331
+ const result = await validateImage(buffer);
332
+ if (result.mime) {
333
+ await this.messageHandler({
334
+ channelId: chatId,
335
+ content: '用户发送了一张图片,请分析这张图片的内容。',
336
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
337
+ images: [{ data: buffer.toString('base64'), mimeType: result.mime }],
338
+ });
339
+ }
340
+ else {
341
+ logger.warn(`[DingTalk] Image validation failed: ${!result.mime && 'reason' in result ? result.reason : 'unknown'}`);
342
+ await this.messageHandler({
343
+ channelId: chatId, content: '[图片验证失败]',
344
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
345
+ });
346
+ }
347
+ }
348
+ catch (error) {
349
+ logger.error('[DingTalk] Failed to download image:', error);
350
+ await this.messageHandler({
351
+ channelId: chatId, content: '[图片下载失败]',
352
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
353
+ });
354
+ }
355
+ }
356
+ async handleFileMessage(data, chatId, chatType, senderId, senderNick, msgId) {
357
+ const content = typeof data.content === 'string' ? JSON.parse(data.content) : (data.content || {});
358
+ const downloadUrl = content.downloadUrl || content.downloadCode;
359
+ const fileName = content.fileName || 'unknown';
360
+ if (!downloadUrl) {
361
+ logger.warn('[DingTalk] File message without downloadUrl');
362
+ await this.messageHandler({
363
+ channelId: chatId, content: `[文件下载失败:缺少下载链接] ${fileName}`,
364
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
365
+ });
366
+ return;
367
+ }
368
+ try {
369
+ const { safeFetch, saveToUploads, sanitizeFileName } = await import('../utils/media-cache.js');
370
+ const projectPath = this.projectPathProvider
371
+ ? await this.projectPathProvider(chatId)
372
+ : process.cwd();
373
+ const buffer = await safeFetch(downloadUrl, { skipSsrfCheck: true });
374
+ const { filePath } = saveToUploads(buffer, sanitizeFileName(fileName), projectPath);
375
+ await this.messageHandler({
376
+ channelId: chatId,
377
+ content: `用户发送了文件:${fileName}\n文件已保存到:${filePath}\n请使用 Read 工具读取并分析文件内容。`,
378
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
379
+ });
380
+ }
381
+ catch (error) {
382
+ logger.error('[DingTalk] Failed to download file:', error);
383
+ await this.messageHandler({
384
+ channelId: chatId, content: `[文件下载失败] ${fileName}`,
385
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
386
+ });
387
+ }
388
+ }
389
+ async handleRichTextMessage(data, chatId, chatType, senderId, senderNick, msgId) {
390
+ const content = typeof data.content === 'string' ? JSON.parse(data.content) : (data.content || {});
391
+ const richText = content.richText;
392
+ if (!Array.isArray(richText)) {
393
+ await this.messageHandler({
394
+ channelId: chatId, content: '[不支持的富文本格式]',
395
+ chatType, peerId: senderId || '', peerName: senderNick, messageId: msgId,
396
+ });
397
+ return;
398
+ }
399
+ let text = '';
400
+ const images = [];
401
+ for (const item of richText) {
402
+ if (item.type === 'text' && item.text) {
403
+ text += item.text;
404
+ }
405
+ else if (item.type === 'picture' && item.downloadUrl) {
406
+ try {
407
+ const { safeFetch, validateImage } = await import('../utils/media-cache.js');
408
+ const buffer = await safeFetch(item.downloadUrl, { skipSsrfCheck: true });
409
+ const result = await validateImage(buffer);
410
+ if (result.mime) {
411
+ images.push({ data: buffer.toString('base64'), mimeType: result.mime });
412
+ }
413
+ }
414
+ catch (error) {
415
+ logger.warn('[DingTalk] Failed to download richText image:', error);
416
+ }
417
+ }
418
+ }
419
+ const prompt = text.trim() || (images.length > 0 ? '用户发送了一张图片,请分析这张图片的内容。' : '[空消息]');
420
+ await this.messageHandler({
421
+ channelId: chatId, content: prompt, chatType,
422
+ peerId: senderId || '', peerName: senderNick, messageId: msgId,
423
+ images: images.length > 0 ? images : undefined,
424
+ });
425
+ }
426
+ // ── Outbound: text via sessionWebhook ──────────────────────────────────────
427
+ async sendMessage(chatId, content) {
428
+ const webhook = this.webhookCache.get(chatId);
429
+ if (!webhook) {
430
+ logger.warn(`[DingTalk] No webhook cached for chatId: ${chatId}, message dropped`);
431
+ return;
432
+ }
433
+ try {
434
+ const token = await this.client?.getAccessToken();
435
+ const response = await fetch(webhook, {
436
+ method: 'POST',
437
+ headers: {
438
+ 'Content-Type': 'application/json',
439
+ 'x-acs-dingtalk-access-token': token || '',
440
+ },
441
+ body: JSON.stringify({
442
+ msgtype: 'markdown',
443
+ markdown: { title: 'Bot', text: content },
444
+ }),
445
+ signal: AbortSignal.timeout(15_000),
446
+ });
447
+ if (!response.ok) {
448
+ const body = await response.text().catch(() => '');
449
+ logger.error(`[DingTalk] sendMessage failed for ${chatId}: ${response.status} ${body}`);
450
+ }
451
+ }
452
+ catch (error) {
453
+ logger.error(`[DingTalk] sendMessage failed for ${chatId}:`, error.message);
454
+ }
455
+ }
456
+ // ── Outbound: image via Open API ───────────────────────────────────────────
457
+ async sendImage(chatId, png) {
458
+ try {
459
+ const token = await this.client?.getAccessToken();
460
+ if (!token) {
461
+ logger.warn('[DingTalk] No access token for sendImage');
462
+ return;
463
+ }
464
+ // Step 1: Upload media
465
+ const FormData = (await requireOptional('form-data')).default;
466
+ const form = new FormData();
467
+ form.append('type', 'image');
468
+ form.append('media', png, { filename: 'image.png', contentType: 'image/png' });
469
+ const uploadRes = await fetch(`https://oapi.dingtalk.com/media/upload?access_token=${token}`, { method: 'POST', body: form, signal: AbortSignal.timeout(30_000) });
470
+ const uploadData = await uploadRes.json();
471
+ const mediaId = uploadData?.media_id;
472
+ if (!mediaId) {
473
+ logger.error('[DingTalk] Media upload failed:', uploadData);
474
+ return;
475
+ }
476
+ // Step 2: Send via robot API
477
+ await this.sendRobotMessage(chatId, token, 'sampleImageMsg', JSON.stringify({ photoURL: `@${mediaId}` }));
478
+ }
479
+ catch (error) {
480
+ logger.error(`[DingTalk] sendImage failed for ${chatId}:`, error.message);
481
+ }
482
+ }
483
+ // ── Outbound: file via Open API ────────────────────────────────────────────
484
+ async sendFile(chatId, filePath) {
485
+ try {
486
+ // Detect image files → route to sendImage (same pattern as Feishu)
487
+ const fs = await import('fs');
488
+ const path = await import('path');
489
+ const header = Buffer.alloc(12);
490
+ const fd = fs.openSync(filePath, 'r');
491
+ fs.readSync(fd, header, 0, 12, 0);
492
+ fs.closeSync(fd);
493
+ const { fileTypeFromBuffer } = await import('file-type');
494
+ const ftype = await fileTypeFromBuffer(header);
495
+ if (ftype && ftype.mime.startsWith('image/')) {
496
+ const buf = fs.readFileSync(filePath);
497
+ return this.sendImage(chatId, buf);
498
+ }
499
+ const token = await this.client?.getAccessToken();
500
+ if (!token) {
501
+ logger.warn('[DingTalk] No access token for sendFile');
502
+ return;
503
+ }
504
+ // Step 1: Upload media
505
+ const FormData = (await requireOptional('form-data')).default;
506
+ const form = new FormData();
507
+ form.append('type', 'file');
508
+ form.append('media', fs.createReadStream(filePath), { filename: path.basename(filePath) });
509
+ const uploadRes = await fetch(`https://oapi.dingtalk.com/media/upload?access_token=${token}`, { method: 'POST', body: form, signal: AbortSignal.timeout(60_000) });
510
+ const uploadData = await uploadRes.json();
511
+ const mediaId = uploadData?.media_id;
512
+ if (!mediaId) {
513
+ logger.error('[DingTalk] File upload failed:', uploadData);
514
+ return;
515
+ }
516
+ // Step 2: Send via robot API
517
+ const fileName = path.basename(filePath);
518
+ const fileType = path.extname(filePath).replace('.', '') || 'file';
519
+ await this.sendRobotMessage(chatId, token, 'sampleFile', JSON.stringify({ mediaId: `@${mediaId}`, fileName, fileType }));
520
+ }
521
+ catch (error) {
522
+ logger.error(`[DingTalk] sendFile failed for ${chatId}:`, error.message);
523
+ }
524
+ }
525
+ // ── Robot message send helper (group vs DM) ────────────────────────────────
526
+ async sendRobotMessage(chatId, token, msgKey, msgParam) {
527
+ const headers = { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' };
528
+ const { clientId } = this.config;
529
+ // Group chatId = conversationId, DM chatId = senderId
530
+ const cachedConvId = this.conversationIdCache.get(chatId);
531
+ const staffId = this.senderStaffIdCache.get(chatId);
532
+ if (cachedConvId === chatId) {
533
+ // Group: chatId is the conversationId
534
+ const res = await fetch('https://api.dingtalk.com/v1.0/robot/groupMessages/send', {
535
+ method: 'POST', headers,
536
+ body: JSON.stringify({ msgKey, msgParam, openConversationId: chatId, robotCode: clientId }),
537
+ signal: AbortSignal.timeout(15_000),
538
+ });
539
+ if (!res.ok)
540
+ logger.error(`[DingTalk] Group robot send failed: ${res.status}`);
541
+ }
542
+ else if (staffId) {
543
+ // DM: use senderStaffId
544
+ const res = await fetch('https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend', {
545
+ method: 'POST', headers,
546
+ body: JSON.stringify({ msgKey, msgParam, userIds: [staffId], robotCode: clientId }),
547
+ signal: AbortSignal.timeout(15_000),
548
+ });
549
+ if (!res.ok)
550
+ logger.error(`[DingTalk] DM robot send failed: ${res.status}`);
551
+ }
552
+ else {
553
+ logger.warn(`[DingTalk] Cannot send robot message: no conversation/staff ID cached for ${chatId}`);
554
+ }
555
+ }
556
+ }
557
+ // ── Plugin ─────────────────────────────────────────────────────────────────────
558
+ function isValidCredential(value) {
559
+ return !!value && !value.includes('your-') && !value.includes('placeholder');
560
+ }
561
+ export class DingtalkChannelPlugin {
562
+ name = 'dingtalk';
563
+ async createInstance(inst, ctx) {
564
+ if (inst.enabled === false)
565
+ return null;
566
+ if (!isValidCredential(inst.clientId) || !isValidCredential(inst.clientSecret))
567
+ return null;
568
+ const channel = new DingtalkChannel({
569
+ clientId: inst.clientId,
570
+ clientSecret: inst.clientSecret,
571
+ requireMention: inst.requireMention,
572
+ freeResponseChats: inst.freeResponseChats,
573
+ }, ctx.agentName, inst.name);
574
+ const mode = resolveShowActivities(inst);
575
+ const adapter = {
576
+ channelName: inst.name,
577
+ channelKey: inst.name,
578
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false, authenticatedApproval: true },
579
+ send: async (envelope, payload) => {
580
+ const channelId = envelope.channelId;
581
+ switch (payload.kind) {
582
+ case 'result.text':
583
+ case 'command.result':
584
+ case 'command.error':
585
+ case 'system.notice':
586
+ case 'system.error':
587
+ case 'result.error':
588
+ await channel.sendMessage(channelId, payload.text);
589
+ return;
590
+ case 'result.file':
591
+ await channel.sendFile(channelId, payload.filePath);
592
+ return;
593
+ case 'result.image':
594
+ await channel.sendImage(channelId, payload.data);
595
+ return;
596
+ case 'activity.batch': {
597
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
598
+ const text = formatItemsAsText(filtered);
599
+ if (text)
600
+ await channel.sendMessage(channelId, text);
601
+ return;
602
+ }
603
+ case 'status.requires_action':
604
+ await channel.sendMessage(channelId, '等待 owner 审批');
605
+ return;
606
+ case 'status.error':
607
+ if (payload.metadata?.message)
608
+ await channel.sendMessage(channelId, payload.metadata.message);
609
+ return;
610
+ case 'interaction':
611
+ if (payload.fallbackText)
612
+ await channel.sendMessage(channelId, payload.fallbackText);
613
+ return;
614
+ default: return;
615
+ }
616
+ },
617
+ };
618
+ const policy = {
619
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
620
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
621
+ canCreateSession: () => true,
622
+ canDeleteSession: () => true,
623
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
624
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
625
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
626
+ middleOutputMode: (chatType, identity) => middleOutputModePolicy(mode, chatType, identity),
627
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
628
+ accumulateErrors: () => true,
629
+ };
630
+ return {
631
+ channelType: 'dingtalk', adapter, channel,
632
+ policy,
633
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
634
+ connect: () => channel.connect(),
635
+ disconnect: () => channel.disconnect(),
636
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
637
+ registerBridge(bridge, channelType) {
638
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
639
+ await handler({
640
+ channel: adapter.channelName, channelType, channelId: event.channelId,
641
+ selfAID: ctx.agentName, content: event.content, images: event.images,
642
+ chatType: event.chatType || 'private', peerId: event.peerId || '',
643
+ peerName: event.peerName, messageId: event.messageId,
644
+ });
645
+ }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
646
+ },
647
+ };
648
+ }
649
+ }