cli-claw-kit 0.0.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/LICENSE +21 -0
- package/README.md +245 -0
- package/config/default-groups.json +1 -0
- package/config/global-agents-md.template.md +37 -0
- package/config/mount-allowlist.json +11 -0
- package/container/Dockerfile +160 -0
- package/container/agent-runner/dist/.tsbuildinfo +1 -0
- package/container/agent-runner/dist/agent-definitions.js +22 -0
- package/container/agent-runner/dist/channel-prefixes.js +16 -0
- package/container/agent-runner/dist/codex-config.js +29 -0
- package/container/agent-runner/dist/image-detector.js +96 -0
- package/container/agent-runner/dist/index.js +2587 -0
- package/container/agent-runner/dist/mcp-tools.js +1076 -0
- package/container/agent-runner/dist/stream-event.types.js +5 -0
- package/container/agent-runner/dist/stream-processor.js +867 -0
- package/container/agent-runner/dist/types.js +6 -0
- package/container/agent-runner/dist/utils.js +115 -0
- package/container/agent-runner/package.json +36 -0
- package/container/agent-runner/prompts/security-rules.md +31 -0
- package/container/agent-runner/src/agent-definitions.ts +27 -0
- package/container/agent-runner/src/channel-prefixes.ts +16 -0
- package/container/agent-runner/src/codex-config.ts +40 -0
- package/container/agent-runner/src/image-detector.ts +116 -0
- package/container/agent-runner/src/index.ts +3107 -0
- package/container/agent-runner/src/mcp-tools.ts +1295 -0
- package/container/agent-runner/src/stream-event.types.ts +10 -0
- package/container/agent-runner/src/stream-processor.ts +932 -0
- package/container/agent-runner/src/types.ts +75 -0
- package/container/agent-runner/src/utils.ts +114 -0
- package/container/agent-runner/tsconfig.json +17 -0
- package/container/build.sh +28 -0
- package/container/entrypoint.sh +64 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/install-skill/SKILL.md +64 -0
- package/container/skills/post-test-cleanup/SKILL.md +121 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/agent-output-parser.js +459 -0
- package/dist/app-root.js +52 -0
- package/dist/assistant-meta-footer.js +1 -0
- package/dist/auth.js +91 -0
- package/dist/billing.js +694 -0
- package/dist/channel-prefixes.js +16 -0
- package/dist/cli.js +86 -0
- package/dist/commands.js +79 -0
- package/dist/config.js +120 -0
- package/dist/container-runner.js +981 -0
- package/dist/daily-summary.js +210 -0
- package/dist/db.js +3683 -0
- package/dist/dingtalk.js +1347 -0
- package/dist/feishu-markdown-style.js +97 -0
- package/dist/feishu-streaming-card.js +1875 -0
- package/dist/feishu.js +1628 -0
- package/dist/file-manager.js +270 -0
- package/dist/group-queue.js +1070 -0
- package/dist/group-runtime.js +35 -0
- package/dist/host-workspace-cwd.js +85 -0
- package/dist/im-channel.js +384 -0
- package/dist/im-command-utils.js +142 -0
- package/dist/im-downloader.js +45 -0
- package/dist/im-manager.js +527 -0
- package/dist/im-utils.js +53 -0
- package/dist/image-detector.js +96 -0
- package/dist/index.js +5828 -0
- package/dist/logger.js +22 -0
- package/dist/mcp-utils.js +66 -0
- package/dist/message-attachments.js +69 -0
- package/dist/message-notifier.js +36 -0
- package/dist/middleware/auth.js +85 -0
- package/dist/mount-security.js +315 -0
- package/dist/permissions.js +67 -0
- package/dist/project-memory.js +6 -0
- package/dist/provider-pool.js +189 -0
- package/dist/qq.js +826 -0
- package/dist/reset-admin.js +42 -0
- package/dist/routes/admin.js +543 -0
- package/dist/routes/agent-definitions.js +241 -0
- package/dist/routes/agents.js +533 -0
- package/dist/routes/auth.js +675 -0
- package/dist/routes/billing.js +490 -0
- package/dist/routes/browse.js +210 -0
- package/dist/routes/bug-report.js +387 -0
- package/dist/routes/config.js +1868 -0
- package/dist/routes/files.js +671 -0
- package/dist/routes/groups.js +1367 -0
- package/dist/routes/mcp-servers.js +320 -0
- package/dist/routes/memory.js +523 -0
- package/dist/routes/monitor.js +307 -0
- package/dist/routes/skills.js +777 -0
- package/dist/routes/tasks.js +509 -0
- package/dist/routes/usage.js +64 -0
- package/dist/routes/workspace-config.js +458 -0
- package/dist/runtime-build.js +112 -0
- package/dist/runtime-command-handler.js +189 -0
- package/dist/runtime-command-registry.js +1 -0
- package/dist/runtime-config.js +1777 -0
- package/dist/runtime-identity.js +52 -0
- package/dist/schemas.js +590 -0
- package/dist/script-runner.js +64 -0
- package/dist/sdk-query.js +82 -0
- package/dist/skill-utils.js +145 -0
- package/dist/sqlite-compat.js +19 -0
- package/dist/stream-event.types.js +5 -0
- package/dist/streaming-runtime-meta.js +29 -0
- package/dist/task-scheduler.js +695 -0
- package/dist/task-utils.js +13 -0
- package/dist/telegram-pairing.js +59 -0
- package/dist/telegram.js +897 -0
- package/dist/terminal-manager.js +307 -0
- package/dist/tool-step-display.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +85 -0
- package/dist/web-context.js +161 -0
- package/dist/web.js +1377 -0
- package/dist/wechat-crypto.js +182 -0
- package/dist/wechat.js +589 -0
- package/dist/workspace-runtime-reset.js +35 -0
- package/package.json +107 -0
- package/shared/assistant-meta-footer.ts +127 -0
- package/shared/channel-prefixes.ts +16 -0
- package/shared/dist/assistant-meta-footer.d.ts +29 -0
- package/shared/dist/assistant-meta-footer.js +85 -0
- package/shared/dist/channel-prefixes.d.ts +4 -0
- package/shared/dist/channel-prefixes.js +16 -0
- package/shared/dist/image-detector.d.ts +20 -0
- package/shared/dist/image-detector.js +96 -0
- package/shared/dist/runtime-command-registry.d.ts +38 -0
- package/shared/dist/runtime-command-registry.js +185 -0
- package/shared/dist/stream-event.d.ts +65 -0
- package/shared/dist/stream-event.js +8 -0
- package/shared/dist/tool-step-display.d.ts +4 -0
- package/shared/dist/tool-step-display.js +11 -0
- package/shared/image-detector.ts +116 -0
- package/shared/runtime-command-registry.ts +252 -0
- package/shared/stream-event.ts +67 -0
- package/shared/tool-step-display.ts +21 -0
- package/shared/tsconfig.json +24 -0
- package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
- package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
- package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
- package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
- package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
- package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
- package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
- package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
- package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
- package/web/dist/assets/band-CquvqAHh.js +1 -0
- package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
- package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
- package/web/dist/assets/channel-BOVj73LR.js +1 -0
- package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
- package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
- package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
- package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
- package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
- package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
- package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
- package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
- package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
- package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
- package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
- package/web/dist/assets/clone-BmaCesfa.js +1 -0
- package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
- package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
- package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
- package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
- package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
- package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
- package/web/dist/assets/error-CGD5mp5f.js +1 -0
- package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
- package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
- package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
- package/web/dist/assets/graph-CeAEckur.js +1 -0
- package/web/dist/assets/index-CPnL1_qC.js +768 -0
- package/web/dist/assets/index-DVevCbcO.css +10 -0
- package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
- package/web/dist/assets/init-Dmth1JHB.js +1 -0
- package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
- package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
- package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
- package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
- package/web/dist/assets/linear-DiaJloY5.js +1 -0
- package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
- package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
- package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
- package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
- package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
- package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
- package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
- package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
- package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
- package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
- package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
- package/web/dist/assets/square-0CqMX1Q3.js +11 -0
- package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
- package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
- package/web/dist/assets/step-D51IIHGA.js +1 -0
- package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
- package/web/dist/assets/time-O8zIGux3.js +1 -0
- package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
- package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
- package/web/dist/assets/utils-KGAn0XTg.js +11 -0
- package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
- package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
- package/web/dist/assets/zap-_hKJYy7J.js +6 -0
- package/web/dist/favicon.svg +332 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin.woff2 +0 -0
- package/web/dist/icons/README.md +20 -0
- package/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/web/dist/icons/icon-128.png +0 -0
- package/web/dist/icons/icon-144.png +0 -0
- package/web/dist/icons/icon-152.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-192.svg +332 -0
- package/web/dist/icons/icon-384.png +0 -0
- package/web/dist/icons/icon-48.png +0 -0
- package/web/dist/icons/icon-512-maskable.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-512.svg +332 -0
- package/web/dist/icons/icon-72.png +0 -0
- package/web/dist/icons/icon-96.png +0 -0
- package/web/dist/icons/loading-logo.svg +332 -0
- package/web/dist/icons/logo-1024.png +0 -0
- package/web/dist/icons/logo-icon.svg +332 -0
- package/web/dist/icons/logo-text.svg +332 -0
- package/web/dist/index.html +30 -0
- package/web/dist/manifest.webmanifest +1 -0
- package/web/dist/registerSW.js +1 -0
- package/web/dist/sw.js +1 -0
- package/web/dist/workbox-08d6266a.js +1 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { getWebDeps } from '../web-context.js';
|
|
6
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
7
|
+
import { canAccessGroup } from '../web-context.js';
|
|
8
|
+
import { getRegisteredGroup, getAllRegisteredGroups, listAgentsByJid, getAgent, deleteAgent, updateAgentStatus, createAgent, ensureChatExists, deleteMessagesForChatJid, deleteSession, getGroupsByTargetAgent, setRegisteredGroup, getJidsByFolder, updateAgentLastImJid, updateAgentInfo, updateChatName, } from '../db.js';
|
|
9
|
+
import { DATA_DIR } from '../config.js';
|
|
10
|
+
import { logger } from '../logger.js';
|
|
11
|
+
import { getChannelType, extractChatId } from '../im-channel.js';
|
|
12
|
+
import { ensureAgentDirectories } from '../utils.js';
|
|
13
|
+
const router = new Hono();
|
|
14
|
+
// GET /api/groups/:jid/agents — list all agents for a group
|
|
15
|
+
router.get('/:jid/agents', authMiddleware, async (c) => {
|
|
16
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
17
|
+
const user = c.get('user');
|
|
18
|
+
const group = getRegisteredGroup(jid);
|
|
19
|
+
if (!group) {
|
|
20
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
21
|
+
}
|
|
22
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
23
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
24
|
+
}
|
|
25
|
+
const agents = listAgentsByJid(jid);
|
|
26
|
+
return c.json({
|
|
27
|
+
agents: agents.map((a) => {
|
|
28
|
+
const base = {
|
|
29
|
+
id: a.id,
|
|
30
|
+
name: a.name,
|
|
31
|
+
prompt: a.prompt,
|
|
32
|
+
status: a.status,
|
|
33
|
+
kind: a.kind,
|
|
34
|
+
created_at: a.created_at,
|
|
35
|
+
completed_at: a.completed_at,
|
|
36
|
+
result_summary: a.result_summary,
|
|
37
|
+
};
|
|
38
|
+
if (a.kind === 'conversation') {
|
|
39
|
+
const linked = getGroupsByTargetAgent(a.id);
|
|
40
|
+
return {
|
|
41
|
+
...base,
|
|
42
|
+
linked_im_groups: linked.map((l) => ({
|
|
43
|
+
jid: l.jid,
|
|
44
|
+
name: l.group.name,
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return base;
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
// POST /api/groups/:jid/agents — create a user conversation
|
|
53
|
+
router.post('/:jid/agents', authMiddleware, async (c) => {
|
|
54
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
55
|
+
const user = c.get('user');
|
|
56
|
+
const group = getRegisteredGroup(jid);
|
|
57
|
+
if (!group) {
|
|
58
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
59
|
+
}
|
|
60
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
61
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
62
|
+
}
|
|
63
|
+
const body = await c.req.json().catch(() => ({}));
|
|
64
|
+
const name = typeof body.name === 'string' ? body.name.trim() : '';
|
|
65
|
+
if (!name || name.length > 40) {
|
|
66
|
+
return c.json({ error: 'Name is required (max 40 chars)' }, 400);
|
|
67
|
+
}
|
|
68
|
+
const description = typeof body.description === 'string' ? body.description.trim() : '';
|
|
69
|
+
const agentId = crypto.randomUUID();
|
|
70
|
+
const now = new Date().toISOString();
|
|
71
|
+
const agent = {
|
|
72
|
+
id: agentId,
|
|
73
|
+
group_folder: group.folder,
|
|
74
|
+
chat_jid: jid,
|
|
75
|
+
name,
|
|
76
|
+
prompt: description,
|
|
77
|
+
status: 'idle',
|
|
78
|
+
kind: 'conversation',
|
|
79
|
+
created_by: user.id,
|
|
80
|
+
created_at: now,
|
|
81
|
+
completed_at: null,
|
|
82
|
+
result_summary: null,
|
|
83
|
+
last_im_jid: null,
|
|
84
|
+
spawned_from_jid: null,
|
|
85
|
+
};
|
|
86
|
+
createAgent(agent);
|
|
87
|
+
// Create IPC + session directories
|
|
88
|
+
ensureAgentDirectories(group.folder, agentId);
|
|
89
|
+
// Create virtual chat record for this agent's messages
|
|
90
|
+
const virtualChatJid = `${jid}#agent:${agentId}`;
|
|
91
|
+
ensureChatExists(virtualChatJid);
|
|
92
|
+
// Broadcast agent_status (idle) via WebSocket
|
|
93
|
+
// Import dynamically to avoid circular deps
|
|
94
|
+
const { broadcastAgentStatus } = await import('../web.js');
|
|
95
|
+
broadcastAgentStatus(jid, agentId, 'idle', name, description);
|
|
96
|
+
logger.info({ agentId, jid, name, userId: user.id }, 'User conversation created');
|
|
97
|
+
return c.json({
|
|
98
|
+
agent: {
|
|
99
|
+
id: agent.id,
|
|
100
|
+
name: agent.name,
|
|
101
|
+
prompt: agent.prompt,
|
|
102
|
+
status: agent.status,
|
|
103
|
+
kind: agent.kind,
|
|
104
|
+
created_at: agent.created_at,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// PATCH /api/groups/:jid/agents/:agentId — rename a conversation agent
|
|
109
|
+
router.patch('/:jid/agents/:agentId', authMiddleware, async (c) => {
|
|
110
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
111
|
+
const agentId = c.req.param('agentId');
|
|
112
|
+
const user = c.get('user');
|
|
113
|
+
const group = getRegisteredGroup(jid);
|
|
114
|
+
if (!group) {
|
|
115
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
116
|
+
}
|
|
117
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
118
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
119
|
+
}
|
|
120
|
+
const agent = getAgent(agentId);
|
|
121
|
+
if (!agent || agent.chat_jid !== jid) {
|
|
122
|
+
return c.json({ error: 'Agent not found' }, 404);
|
|
123
|
+
}
|
|
124
|
+
const body = await c.req.json().catch(() => ({}));
|
|
125
|
+
const name = typeof body.name === 'string' ? body.name.trim() : '';
|
|
126
|
+
if (!name || name.length > 40) {
|
|
127
|
+
return c.json({ error: 'Name is required (max 40 chars)' }, 400);
|
|
128
|
+
}
|
|
129
|
+
// Update agent name in DB
|
|
130
|
+
updateAgentInfo(agentId, name, agent.prompt);
|
|
131
|
+
// Update virtual chat name
|
|
132
|
+
const virtualChatJid = `${jid}#agent:${agentId}`;
|
|
133
|
+
updateChatName(virtualChatJid, name);
|
|
134
|
+
// Broadcast update via WebSocket
|
|
135
|
+
const { broadcastAgentStatus } = await import('../web.js');
|
|
136
|
+
broadcastAgentStatus(jid, agentId, agent.status, name, agent.prompt);
|
|
137
|
+
logger.info({ agentId, jid, name, userId: user.id }, 'Agent renamed');
|
|
138
|
+
return c.json({ success: true });
|
|
139
|
+
});
|
|
140
|
+
// DELETE /api/groups/:jid/agents/:agentId — stop and delete an agent
|
|
141
|
+
router.delete('/:jid/agents/:agentId', authMiddleware, async (c) => {
|
|
142
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
143
|
+
const agentId = c.req.param('agentId');
|
|
144
|
+
const user = c.get('user');
|
|
145
|
+
const group = getRegisteredGroup(jid);
|
|
146
|
+
if (!group) {
|
|
147
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
148
|
+
}
|
|
149
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
150
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
151
|
+
}
|
|
152
|
+
const agent = getAgent(agentId);
|
|
153
|
+
if (!agent || agent.chat_jid !== jid) {
|
|
154
|
+
return c.json({ error: 'Agent not found' }, 404);
|
|
155
|
+
}
|
|
156
|
+
// Block deletion if conversation agent has active IM bindings
|
|
157
|
+
if (agent.kind === 'conversation') {
|
|
158
|
+
const linkedImGroups = getGroupsByTargetAgent(agentId);
|
|
159
|
+
if (linkedImGroups.length > 0) {
|
|
160
|
+
return c.json({
|
|
161
|
+
error: 'Agent has active IM bindings. Unbind all IM groups before deleting.',
|
|
162
|
+
linked_im_groups: linkedImGroups.map(({ jid: imJid, group: imGroup }) => ({
|
|
163
|
+
jid: imJid,
|
|
164
|
+
name: imGroup.name,
|
|
165
|
+
})),
|
|
166
|
+
}, 409);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// If the agent is still running or idle, stop the process
|
|
170
|
+
if (agent.status === 'running' || agent.status === 'idle') {
|
|
171
|
+
updateAgentStatus(agentId, 'error', '用户手动停止');
|
|
172
|
+
// Stop running process via queue
|
|
173
|
+
const deps = getWebDeps();
|
|
174
|
+
if (deps) {
|
|
175
|
+
const virtualJid = `${jid}#agent:${agentId}`;
|
|
176
|
+
deps.queue.stopGroup(virtualJid);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Clean up IPC/session directories
|
|
180
|
+
const agentIpcDir = path.join(DATA_DIR, 'ipc', group.folder, 'agents', agentId);
|
|
181
|
+
try {
|
|
182
|
+
fs.rmSync(agentIpcDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* ignore */
|
|
186
|
+
}
|
|
187
|
+
const agentSessionDir = path.join(DATA_DIR, 'sessions', group.folder, 'agents', agentId);
|
|
188
|
+
try {
|
|
189
|
+
fs.rmSync(agentSessionDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
/* ignore */
|
|
193
|
+
}
|
|
194
|
+
// Delete virtual chat messages for conversation agents
|
|
195
|
+
if (agent.kind === 'conversation') {
|
|
196
|
+
const virtualChatJid = `${jid}#agent:${agentId}`;
|
|
197
|
+
deleteMessagesForChatJid(virtualChatJid);
|
|
198
|
+
// Note: IM bindings are checked above and block deletion if present.
|
|
199
|
+
// No auto-clear here — user must unbind explicitly before deleting.
|
|
200
|
+
}
|
|
201
|
+
// Delete session records
|
|
202
|
+
deleteSession(group.folder, agentId);
|
|
203
|
+
deleteAgent(agentId);
|
|
204
|
+
// Broadcast removal
|
|
205
|
+
const { broadcastAgentStatus } = await import('../web.js');
|
|
206
|
+
broadcastAgentStatus(jid, agentId, 'error', agent.name, agent.prompt, '__removed__');
|
|
207
|
+
logger.info({ agentId, jid, userId: user.id }, 'Agent deleted by user');
|
|
208
|
+
return c.json({ success: true });
|
|
209
|
+
});
|
|
210
|
+
// Helper: check if a Telegram JID is a private/P2P chat
|
|
211
|
+
function isTelegramPrivateChat(jid) {
|
|
212
|
+
if (!jid.startsWith('telegram:'))
|
|
213
|
+
return false;
|
|
214
|
+
const id = jid.slice('telegram:'.length);
|
|
215
|
+
return !id.startsWith('-');
|
|
216
|
+
}
|
|
217
|
+
// GET /api/groups/:jid/im-groups — list available IM group chats for this folder
|
|
218
|
+
router.get('/:jid/im-groups', authMiddleware, async (c) => {
|
|
219
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
220
|
+
const user = c.get('user');
|
|
221
|
+
const group = getRegisteredGroup(jid);
|
|
222
|
+
if (!group) {
|
|
223
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
224
|
+
}
|
|
225
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
226
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
227
|
+
}
|
|
228
|
+
// Find all IM groups this user can access (across all folders).
|
|
229
|
+
const allGroups = getAllRegisteredGroups();
|
|
230
|
+
const imJids = Object.keys(allGroups)
|
|
231
|
+
.filter((j) => {
|
|
232
|
+
if (j.startsWith('web:'))
|
|
233
|
+
return false;
|
|
234
|
+
return canAccessGroup(user, { ...allGroups[j], jid: j });
|
|
235
|
+
})
|
|
236
|
+
.filter((j) => !isTelegramPrivateChat(j));
|
|
237
|
+
const candidates = [];
|
|
238
|
+
for (const j of imJids) {
|
|
239
|
+
const g = allGroups[j];
|
|
240
|
+
// Resolve bound target name for display
|
|
241
|
+
let boundTargetName = null;
|
|
242
|
+
let boundWorkspaceName = null;
|
|
243
|
+
if (g.target_agent_id) {
|
|
244
|
+
const boundAgent = getAgent(g.target_agent_id);
|
|
245
|
+
if (boundAgent) {
|
|
246
|
+
boundTargetName = boundAgent.name;
|
|
247
|
+
const ownerGroup = getRegisteredGroup(boundAgent.chat_jid);
|
|
248
|
+
if (ownerGroup)
|
|
249
|
+
boundWorkspaceName = ownerGroup.name;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (g.target_main_jid) {
|
|
253
|
+
let boundGroup = getRegisteredGroup(g.target_main_jid);
|
|
254
|
+
// Legacy fallback: old bindings stored web:${folder} instead of actual JID
|
|
255
|
+
if (!boundGroup && g.target_main_jid.startsWith('web:')) {
|
|
256
|
+
const folder = g.target_main_jid.slice(4);
|
|
257
|
+
const jids = getJidsByFolder(folder);
|
|
258
|
+
for (const fj of jids) {
|
|
259
|
+
if (fj.startsWith('web:')) {
|
|
260
|
+
boundGroup = getRegisteredGroup(fj);
|
|
261
|
+
if (boundGroup)
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (boundGroup)
|
|
267
|
+
boundTargetName = boundGroup.name;
|
|
268
|
+
}
|
|
269
|
+
candidates.push({
|
|
270
|
+
jid: j,
|
|
271
|
+
name: g.name,
|
|
272
|
+
bound_agent_id: g.target_agent_id ?? null,
|
|
273
|
+
bound_main_jid: g.target_main_jid ?? null,
|
|
274
|
+
reply_policy: g.reply_policy === 'mirror' ? 'mirror' : 'source_only',
|
|
275
|
+
bound_target_name: boundTargetName,
|
|
276
|
+
bound_workspace_name: boundWorkspaceName,
|
|
277
|
+
channel_type: getChannelType(j) ?? 'unknown',
|
|
278
|
+
activation_mode: g.activation_mode,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// Enrich Feishu groups with avatar, member count, and chat_mode
|
|
282
|
+
const deps = getWebDeps();
|
|
283
|
+
if (deps?.getFeishuChatInfo) {
|
|
284
|
+
const feishuCandidates = candidates.filter((g) => g.channel_type === 'feishu');
|
|
285
|
+
const chatInfoPromises = feishuCandidates.map(async (g) => {
|
|
286
|
+
const chatId = extractChatId(g.jid);
|
|
287
|
+
const info = await deps.getFeishuChatInfo(user.id, chatId);
|
|
288
|
+
if (info) {
|
|
289
|
+
g.avatar = info.avatar;
|
|
290
|
+
g.chat_mode = info.chat_mode;
|
|
291
|
+
if (info.user_count != null) {
|
|
292
|
+
const count = parseInt(info.user_count, 10);
|
|
293
|
+
if (!isNaN(count))
|
|
294
|
+
g.member_count = count;
|
|
295
|
+
}
|
|
296
|
+
if (info.name && info.name !== g.name)
|
|
297
|
+
g.name = info.name;
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await Promise.allSettled(chatInfoPromises);
|
|
301
|
+
}
|
|
302
|
+
// Feishu chat_mode: 'group' = group chat, 'p2p' = private chat
|
|
303
|
+
// If chat_mode is available, use it directly. When API data is completely
|
|
304
|
+
// missing (permissions not enabled), default to keeping the group rather
|
|
305
|
+
// than filtering it out. Only filter when chat_mode is explicitly 'p2p'.
|
|
306
|
+
const imGroups = candidates
|
|
307
|
+
.filter((g) => {
|
|
308
|
+
if (g.channel_type === 'feishu') {
|
|
309
|
+
if (g.chat_mode === 'p2p')
|
|
310
|
+
return false;
|
|
311
|
+
// Exclude groups with only the bot (user_count=0 means no real users, just bot)
|
|
312
|
+
if (g.member_count !== undefined && g.member_count < 1)
|
|
313
|
+
return false;
|
|
314
|
+
// chat_mode is 'group' or API data completely missing — keep the group
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
})
|
|
319
|
+
.map(({ chat_mode: _, ...rest }) => rest);
|
|
320
|
+
return c.json({ imGroups });
|
|
321
|
+
});
|
|
322
|
+
// PUT /api/groups/:jid/agents/:agentId/im-binding — bind an IM group to this agent
|
|
323
|
+
router.put('/:jid/agents/:agentId/im-binding', authMiddleware, async (c) => {
|
|
324
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
325
|
+
const agentId = c.req.param('agentId');
|
|
326
|
+
const user = c.get('user');
|
|
327
|
+
const group = getRegisteredGroup(jid);
|
|
328
|
+
if (!group) {
|
|
329
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
330
|
+
}
|
|
331
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
332
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
333
|
+
}
|
|
334
|
+
const agent = getAgent(agentId);
|
|
335
|
+
if (!agent || agent.chat_jid !== jid) {
|
|
336
|
+
return c.json({ error: 'Agent not found' }, 404);
|
|
337
|
+
}
|
|
338
|
+
if (agent.kind !== 'conversation') {
|
|
339
|
+
return c.json({ error: 'Only conversation agents can bind IM groups' }, 400);
|
|
340
|
+
}
|
|
341
|
+
const body = await c.req.json().catch(() => ({}));
|
|
342
|
+
const imJid = typeof body.im_jid === 'string' ? body.im_jid.trim() : '';
|
|
343
|
+
if (!imJid) {
|
|
344
|
+
return c.json({ error: 'im_jid is required' }, 400);
|
|
345
|
+
}
|
|
346
|
+
const imGroup = getRegisteredGroup(imJid);
|
|
347
|
+
if (!imGroup) {
|
|
348
|
+
return c.json({ error: 'IM group not found' }, 404);
|
|
349
|
+
}
|
|
350
|
+
if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
|
|
351
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
352
|
+
}
|
|
353
|
+
const force = body.force === true;
|
|
354
|
+
const replyPolicy = body.reply_policy === 'mirror' ? 'mirror' : 'source_only';
|
|
355
|
+
const hasConflict = (imGroup.target_agent_id && imGroup.target_agent_id !== agentId) ||
|
|
356
|
+
!!imGroup.target_main_jid;
|
|
357
|
+
if (hasConflict && !force) {
|
|
358
|
+
return c.json({ error: 'IM group is already bound elsewhere' }, 409);
|
|
359
|
+
}
|
|
360
|
+
// Update DB + in-memory cache — clear target_main_jid to avoid conflicts
|
|
361
|
+
const updated = {
|
|
362
|
+
...imGroup,
|
|
363
|
+
target_agent_id: agentId,
|
|
364
|
+
target_main_jid: undefined,
|
|
365
|
+
reply_policy: replyPolicy,
|
|
366
|
+
};
|
|
367
|
+
setRegisteredGroup(imJid, updated);
|
|
368
|
+
const deps = getWebDeps();
|
|
369
|
+
if (deps) {
|
|
370
|
+
const groups = deps.getRegisteredGroups();
|
|
371
|
+
if (groups[imJid])
|
|
372
|
+
groups[imJid] = updated;
|
|
373
|
+
}
|
|
374
|
+
logger.info({ imJid, agentId, userId: user.id }, 'IM group bound to agent');
|
|
375
|
+
return c.json({ success: true });
|
|
376
|
+
});
|
|
377
|
+
// DELETE /api/groups/:jid/agents/:agentId/im-binding/:imJid — unbind an IM group
|
|
378
|
+
router.delete('/:jid/agents/:agentId/im-binding/:imJid', authMiddleware, async (c) => {
|
|
379
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
380
|
+
const agentId = c.req.param('agentId');
|
|
381
|
+
const imJid = decodeURIComponent(c.req.param('imJid'));
|
|
382
|
+
const user = c.get('user');
|
|
383
|
+
const group = getRegisteredGroup(jid);
|
|
384
|
+
if (!group) {
|
|
385
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
386
|
+
}
|
|
387
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
388
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
389
|
+
}
|
|
390
|
+
const agent = getAgent(agentId);
|
|
391
|
+
if (!agent || agent.chat_jid !== jid) {
|
|
392
|
+
return c.json({ error: 'Agent not found' }, 404);
|
|
393
|
+
}
|
|
394
|
+
const imGroup = getRegisteredGroup(imJid);
|
|
395
|
+
if (!imGroup) {
|
|
396
|
+
return c.json({ error: 'IM group not found' }, 404);
|
|
397
|
+
}
|
|
398
|
+
if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
|
|
399
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
400
|
+
}
|
|
401
|
+
if (imGroup.target_agent_id !== agentId) {
|
|
402
|
+
return c.json({ error: 'IM group is not bound to this agent' }, 400);
|
|
403
|
+
}
|
|
404
|
+
// Update DB + in-memory cache
|
|
405
|
+
const updated = { ...imGroup, target_agent_id: undefined };
|
|
406
|
+
setRegisteredGroup(imJid, updated);
|
|
407
|
+
const deps = getWebDeps();
|
|
408
|
+
if (deps) {
|
|
409
|
+
const groups = deps.getRegisteredGroups();
|
|
410
|
+
if (groups[imJid])
|
|
411
|
+
groups[imJid] = updated;
|
|
412
|
+
}
|
|
413
|
+
// Clear persisted IM routing so restart won't route to unbound channel (#225)
|
|
414
|
+
updateAgentLastImJid(agentId, null);
|
|
415
|
+
logger.info({ imJid, agentId, userId: user.id }, 'IM group unbound from agent');
|
|
416
|
+
return c.json({ success: true });
|
|
417
|
+
});
|
|
418
|
+
// PUT /api/groups/:jid/im-binding — bind an IM group to this workspace's main conversation
|
|
419
|
+
router.put('/:jid/im-binding', authMiddleware, async (c) => {
|
|
420
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
421
|
+
const user = c.get('user');
|
|
422
|
+
const group = getRegisteredGroup(jid);
|
|
423
|
+
if (!group) {
|
|
424
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
425
|
+
}
|
|
426
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
427
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
428
|
+
}
|
|
429
|
+
if (group.is_home) {
|
|
430
|
+
return c.json({ error: 'Home workspace main conversation uses default IM routing' }, 400);
|
|
431
|
+
}
|
|
432
|
+
const body = await c.req.json().catch(() => ({}));
|
|
433
|
+
const imJid = typeof body.im_jid === 'string' ? body.im_jid.trim() : '';
|
|
434
|
+
if (!imJid) {
|
|
435
|
+
return c.json({ error: 'im_jid is required' }, 400);
|
|
436
|
+
}
|
|
437
|
+
const imGroup = getRegisteredGroup(imJid);
|
|
438
|
+
if (!imGroup) {
|
|
439
|
+
return c.json({ error: 'IM group not found' }, 404);
|
|
440
|
+
}
|
|
441
|
+
if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
|
|
442
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
443
|
+
}
|
|
444
|
+
const targetMainJid = jid; // Use actual registered JID (not folder-based)
|
|
445
|
+
const legacyMainJid = `web:${group.folder}`;
|
|
446
|
+
const force = body.force === true;
|
|
447
|
+
// Only update reply_policy if explicitly provided; otherwise preserve existing value
|
|
448
|
+
const replyPolicy = body.reply_policy === 'mirror'
|
|
449
|
+
? 'mirror'
|
|
450
|
+
: body.reply_policy === 'source_only'
|
|
451
|
+
? 'source_only'
|
|
452
|
+
: undefined;
|
|
453
|
+
const hasConflict = !!imGroup.target_agent_id ||
|
|
454
|
+
(imGroup.target_main_jid &&
|
|
455
|
+
imGroup.target_main_jid !== targetMainJid &&
|
|
456
|
+
imGroup.target_main_jid !== legacyMainJid);
|
|
457
|
+
if (hasConflict && !force) {
|
|
458
|
+
return c.json({ error: 'IM group is already bound elsewhere' }, 409);
|
|
459
|
+
}
|
|
460
|
+
// Parse activation_mode from request body
|
|
461
|
+
const validActivationModes = [
|
|
462
|
+
'always',
|
|
463
|
+
'when_mentioned',
|
|
464
|
+
'auto',
|
|
465
|
+
'disabled',
|
|
466
|
+
];
|
|
467
|
+
const rawActivationMode = body.activation_mode;
|
|
468
|
+
const activationMode = typeof rawActivationMode === 'string' &&
|
|
469
|
+
validActivationModes.includes(rawActivationMode)
|
|
470
|
+
? rawActivationMode
|
|
471
|
+
: undefined;
|
|
472
|
+
// Update DB + in-memory cache — clear target_agent_id to avoid conflicts
|
|
473
|
+
const updated = {
|
|
474
|
+
...imGroup,
|
|
475
|
+
target_main_jid: targetMainJid,
|
|
476
|
+
target_agent_id: undefined,
|
|
477
|
+
...(replyPolicy !== undefined ? { reply_policy: replyPolicy } : {}),
|
|
478
|
+
...(activationMode !== undefined
|
|
479
|
+
? { activation_mode: activationMode }
|
|
480
|
+
: {}),
|
|
481
|
+
};
|
|
482
|
+
setRegisteredGroup(imJid, updated);
|
|
483
|
+
const deps = getWebDeps();
|
|
484
|
+
if (deps) {
|
|
485
|
+
const groups = deps.getRegisteredGroups();
|
|
486
|
+
if (groups[imJid])
|
|
487
|
+
groups[imJid] = updated;
|
|
488
|
+
}
|
|
489
|
+
logger.info({ imJid, targetMainJid, activationMode, userId: user.id }, 'IM group bound to workspace main conversation');
|
|
490
|
+
return c.json({ success: true });
|
|
491
|
+
});
|
|
492
|
+
// DELETE /api/groups/:jid/im-binding/:imJid — unbind an IM group from this workspace's main conversation
|
|
493
|
+
router.delete('/:jid/im-binding/:imJid', authMiddleware, async (c) => {
|
|
494
|
+
const jid = decodeURIComponent(c.req.param('jid'));
|
|
495
|
+
const imJid = decodeURIComponent(c.req.param('imJid'));
|
|
496
|
+
const user = c.get('user');
|
|
497
|
+
const group = getRegisteredGroup(jid);
|
|
498
|
+
if (!group) {
|
|
499
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
500
|
+
}
|
|
501
|
+
if (!canAccessGroup(user, { ...group, jid })) {
|
|
502
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
503
|
+
}
|
|
504
|
+
const imGroup = getRegisteredGroup(imJid);
|
|
505
|
+
if (!imGroup) {
|
|
506
|
+
return c.json({ error: 'IM group not found' }, 404);
|
|
507
|
+
}
|
|
508
|
+
if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
|
|
509
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
510
|
+
}
|
|
511
|
+
const targetMainJid = jid; // Use actual registered JID (not folder-based)
|
|
512
|
+
const legacyMainJid = `web:${group.folder}`;
|
|
513
|
+
if (imGroup.target_main_jid !== targetMainJid &&
|
|
514
|
+
imGroup.target_main_jid !== legacyMainJid) {
|
|
515
|
+
return c.json({ error: 'IM group is not bound to this workspace' }, 400);
|
|
516
|
+
}
|
|
517
|
+
// Update DB + in-memory cache — reset activation_mode to 'auto' on unbind
|
|
518
|
+
const updated = {
|
|
519
|
+
...imGroup,
|
|
520
|
+
target_main_jid: undefined,
|
|
521
|
+
activation_mode: 'auto',
|
|
522
|
+
};
|
|
523
|
+
setRegisteredGroup(imJid, updated);
|
|
524
|
+
const deps = getWebDeps();
|
|
525
|
+
if (deps) {
|
|
526
|
+
const groups = deps.getRegisteredGroups();
|
|
527
|
+
if (groups[imJid])
|
|
528
|
+
groups[imJid] = updated;
|
|
529
|
+
}
|
|
530
|
+
logger.info({ imJid, targetMainJid, userId: user.id }, 'IM group unbound from workspace main conversation');
|
|
531
|
+
return c.json({ success: true });
|
|
532
|
+
});
|
|
533
|
+
export default router;
|