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,307 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
/**
|
|
8
|
+
* Standalone Node.js script that wraps node-pty.
|
|
9
|
+
* Spawned as `node pty-worker.cjs <json-args>`, communicates via JSON lines
|
|
10
|
+
* over stdin/stdout. This sidesteps Bun's incompatibility with node-pty's
|
|
11
|
+
* native addon.
|
|
12
|
+
*/
|
|
13
|
+
const PTY_WORKER_PATH = path.resolve(__dirname, '..', 'src', 'pty-worker.cjs');
|
|
14
|
+
export class TerminalManager {
|
|
15
|
+
sessions = new Map();
|
|
16
|
+
/** Set to true when node-pty is detected as broken (e.g. Node.js version incompatibility) */
|
|
17
|
+
ptyDisabled = false;
|
|
18
|
+
start(groupJid, containerName, cols, rows, onData, onExit) {
|
|
19
|
+
// 如果已有会话,先关闭
|
|
20
|
+
if (this.sessions.has(groupJid)) {
|
|
21
|
+
this.stop(groupJid);
|
|
22
|
+
}
|
|
23
|
+
logger.info({ groupJid, containerName, cols, rows }, 'Starting terminal session');
|
|
24
|
+
const shellBootstrap = 'export TERM="${TERM:-xterm-256color}"; stty erase "^?" 2>/dev/null; ' +
|
|
25
|
+
'if command -v zsh >/dev/null 2>&1; then exec zsh -il; ' +
|
|
26
|
+
'elif command -v bash >/dev/null 2>&1; then exec bash -il; ' +
|
|
27
|
+
'else exec sh -i; fi';
|
|
28
|
+
// Try PTY mode via node subprocess (Bun can't load node-pty natively)
|
|
29
|
+
if (!this.ptyDisabled && fs.existsSync(PTY_WORKER_PATH)) {
|
|
30
|
+
try {
|
|
31
|
+
const workerArgs = JSON.stringify({
|
|
32
|
+
file: 'docker',
|
|
33
|
+
args: [
|
|
34
|
+
'exec',
|
|
35
|
+
'-it',
|
|
36
|
+
'-u',
|
|
37
|
+
'node',
|
|
38
|
+
containerName,
|
|
39
|
+
'/bin/sh',
|
|
40
|
+
'-c',
|
|
41
|
+
shellBootstrap,
|
|
42
|
+
],
|
|
43
|
+
name: 'xterm-256color',
|
|
44
|
+
cols,
|
|
45
|
+
rows,
|
|
46
|
+
});
|
|
47
|
+
const proc = spawn('node', [PTY_WORKER_PATH, workerArgs], {
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
env: process.env,
|
|
50
|
+
});
|
|
51
|
+
const session = {
|
|
52
|
+
mode: 'pty',
|
|
53
|
+
process: proc,
|
|
54
|
+
containerName,
|
|
55
|
+
groupJid,
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
stoppedManually: false,
|
|
58
|
+
};
|
|
59
|
+
// Track whether PTY worker has been alive long enough to be considered healthy
|
|
60
|
+
let ptyHealthy = false;
|
|
61
|
+
const ptyHealthTimer = setTimeout(() => {
|
|
62
|
+
ptyHealthy = true;
|
|
63
|
+
}, 2000);
|
|
64
|
+
// Parse JSON-line messages from the worker
|
|
65
|
+
let buffer = '';
|
|
66
|
+
proc.stdout?.on('data', (chunk) => {
|
|
67
|
+
buffer += chunk.toString();
|
|
68
|
+
let newlineIdx;
|
|
69
|
+
while ((newlineIdx = buffer.indexOf('\n')) !== -1) {
|
|
70
|
+
const line = buffer.slice(0, newlineIdx);
|
|
71
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
72
|
+
try {
|
|
73
|
+
const msg = JSON.parse(line);
|
|
74
|
+
if (msg.type === 'data') {
|
|
75
|
+
onData(msg.data);
|
|
76
|
+
}
|
|
77
|
+
else if (msg.type === 'exit') {
|
|
78
|
+
if (!session.stoppedManually) {
|
|
79
|
+
logger.info({ groupJid, exitCode: msg.exitCode, signal: msg.signal }, 'Terminal session exited');
|
|
80
|
+
this.sessions.delete(groupJid);
|
|
81
|
+
onExit(msg.exitCode, msg.signal);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Not JSON — forward as raw output
|
|
87
|
+
onData(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
proc.stderr?.on('data', (chunk) => {
|
|
92
|
+
logger.warn({ groupJid, stderr: chunk.toString().trim() }, 'PTY worker stderr');
|
|
93
|
+
});
|
|
94
|
+
proc.on('close', (exitCode) => {
|
|
95
|
+
clearTimeout(ptyHealthTimer);
|
|
96
|
+
if (session.stoppedManually || !this.sessions.has(groupJid))
|
|
97
|
+
return;
|
|
98
|
+
// If PTY worker crashes quickly (< 2s), node-pty is broken — fall back to pipe mode permanently
|
|
99
|
+
if (!ptyHealthy) {
|
|
100
|
+
logger.warn({ groupJid, exitCode }, 'PTY worker crashed on startup, disabling PTY and falling back to pipe mode');
|
|
101
|
+
this.ptyDisabled = true;
|
|
102
|
+
this.sessions.delete(groupJid);
|
|
103
|
+
this.startPipeMode(groupJid, containerName, shellBootstrap, onData, onExit);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
logger.info({ groupJid, exitCode }, 'PTY worker process closed');
|
|
107
|
+
this.sessions.delete(groupJid);
|
|
108
|
+
onExit(exitCode ?? 1);
|
|
109
|
+
});
|
|
110
|
+
proc.on('error', (err) => {
|
|
111
|
+
clearTimeout(ptyHealthTimer);
|
|
112
|
+
logger.warn({ err, groupJid }, 'PTY worker spawn error');
|
|
113
|
+
if (!session.stoppedManually && this.sessions.has(groupJid)) {
|
|
114
|
+
this.sessions.delete(groupJid);
|
|
115
|
+
// Disable PTY and fall back to pipe mode
|
|
116
|
+
this.ptyDisabled = true;
|
|
117
|
+
this.startPipeMode(groupJid, containerName, shellBootstrap, onData, onExit);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
this.sessions.set(groupJid, session);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
logger.warn({ err, groupJid, containerName }, 'PTY worker spawn failed, falling back to pipe terminal');
|
|
125
|
+
this.ptyDisabled = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (!fs.existsSync(PTY_WORKER_PATH)) {
|
|
129
|
+
logger.warn({ path: PTY_WORKER_PATH }, 'PTY worker script not found, falling back to pipe terminal');
|
|
130
|
+
}
|
|
131
|
+
this.startPipeMode(groupJid, containerName, shellBootstrap, onData, onExit);
|
|
132
|
+
}
|
|
133
|
+
/** Pipe mode fallback (no PTY, line-based input) */
|
|
134
|
+
startPipeMode(groupJid, containerName, shellBootstrap, onData, onExit) {
|
|
135
|
+
const proc = spawn('docker', [
|
|
136
|
+
'exec',
|
|
137
|
+
'-i',
|
|
138
|
+
'-u',
|
|
139
|
+
'node',
|
|
140
|
+
containerName,
|
|
141
|
+
'/bin/sh',
|
|
142
|
+
'-c',
|
|
143
|
+
shellBootstrap,
|
|
144
|
+
], {
|
|
145
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
146
|
+
env: {
|
|
147
|
+
...process.env,
|
|
148
|
+
TERM: process.env.TERM || 'xterm-256color',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
const session = {
|
|
152
|
+
mode: 'pipe',
|
|
153
|
+
process: proc,
|
|
154
|
+
onData,
|
|
155
|
+
lineBuffer: '',
|
|
156
|
+
containerName,
|
|
157
|
+
groupJid,
|
|
158
|
+
createdAt: Date.now(),
|
|
159
|
+
stoppedManually: false,
|
|
160
|
+
};
|
|
161
|
+
let exited = false;
|
|
162
|
+
const finalizeExit = (exitCode) => {
|
|
163
|
+
if (exited || session.stoppedManually)
|
|
164
|
+
return;
|
|
165
|
+
exited = true;
|
|
166
|
+
logger.info({ groupJid, exitCode }, 'Pipe terminal session exited');
|
|
167
|
+
this.sessions.delete(groupJid);
|
|
168
|
+
onExit(exitCode);
|
|
169
|
+
};
|
|
170
|
+
proc.stdout?.on('data', (chunk) => {
|
|
171
|
+
onData(chunk.toString());
|
|
172
|
+
});
|
|
173
|
+
proc.stderr?.on('data', (chunk) => {
|
|
174
|
+
onData(chunk.toString());
|
|
175
|
+
});
|
|
176
|
+
proc.on('error', (err) => {
|
|
177
|
+
onData(`\r\n[terminal process error: ${err.message}]\r\n`);
|
|
178
|
+
finalizeExit(1);
|
|
179
|
+
});
|
|
180
|
+
proc.on('close', (exitCode) => {
|
|
181
|
+
finalizeExit(exitCode ?? 0);
|
|
182
|
+
});
|
|
183
|
+
this.sessions.set(groupJid, session);
|
|
184
|
+
onData('\r\n[terminal compatibility mode: no PTY available]\r\n' +
|
|
185
|
+
'[input is line-based; press Enter to execute]\r\n');
|
|
186
|
+
}
|
|
187
|
+
write(groupJid, data) {
|
|
188
|
+
const session = this.sessions.get(groupJid);
|
|
189
|
+
if (!session)
|
|
190
|
+
return;
|
|
191
|
+
if (session.mode === 'pty') {
|
|
192
|
+
// Send write command to PTY worker via JSON line
|
|
193
|
+
session.process.stdin?.write(JSON.stringify({ type: 'write', data }) + '\n');
|
|
194
|
+
}
|
|
195
|
+
else if (session.process.stdin?.writable) {
|
|
196
|
+
// Pipe mode: local line buffer with editing support.
|
|
197
|
+
// Characters are buffered locally; only the final edited line is sent
|
|
198
|
+
// to the shell on Enter. This is necessary because pipe stdin has no
|
|
199
|
+
// PTY line discipline to process backspace / Ctrl-U / etc.
|
|
200
|
+
for (let i = 0; i < data.length; i++) {
|
|
201
|
+
const ch = data[i];
|
|
202
|
+
if (ch === '\r' || ch === '\n') {
|
|
203
|
+
// Skip \n following \r (Windows CRLF from paste)
|
|
204
|
+
if (ch === '\r' && i + 1 < data.length && data[i + 1] === '\n')
|
|
205
|
+
i++;
|
|
206
|
+
// Send the clean, edited line to the shell
|
|
207
|
+
session.process.stdin.write(session.lineBuffer + '\n');
|
|
208
|
+
session.onData('\r\n');
|
|
209
|
+
session.lineBuffer = '';
|
|
210
|
+
}
|
|
211
|
+
else if (ch === '\x7f' || ch === '\b') {
|
|
212
|
+
// Backspace / DEL: remove last character
|
|
213
|
+
if (session.lineBuffer.length > 0) {
|
|
214
|
+
session.lineBuffer = session.lineBuffer.slice(0, -1);
|
|
215
|
+
session.onData('\b \b');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (ch === '\x15') {
|
|
219
|
+
// Ctrl-U: kill entire line
|
|
220
|
+
const len = session.lineBuffer.length;
|
|
221
|
+
if (len > 0) {
|
|
222
|
+
session.onData('\b \b'.repeat(len));
|
|
223
|
+
session.lineBuffer = '';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (ch === '\x17') {
|
|
227
|
+
// Ctrl-W: delete last word
|
|
228
|
+
const buf = session.lineBuffer;
|
|
229
|
+
const trimmed = buf.replace(/\s+$/, '');
|
|
230
|
+
const lastSpace = trimmed.lastIndexOf(' ');
|
|
231
|
+
const newBuf = lastSpace >= 0 ? buf.slice(0, lastSpace + 1) : '';
|
|
232
|
+
const removed = buf.length - newBuf.length;
|
|
233
|
+
if (removed > 0) {
|
|
234
|
+
session.onData('\b \b'.repeat(removed));
|
|
235
|
+
session.lineBuffer = newBuf;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (ch === '\x03') {
|
|
239
|
+
// Ctrl-C: discard current line
|
|
240
|
+
session.onData('^C\r\n');
|
|
241
|
+
session.lineBuffer = '';
|
|
242
|
+
}
|
|
243
|
+
else if (ch === '\x04') {
|
|
244
|
+
// Ctrl-D: send EOF if line is empty, otherwise ignore
|
|
245
|
+
if (session.lineBuffer.length === 0) {
|
|
246
|
+
session.process.stdin.end();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (ch === '\t') {
|
|
250
|
+
// Tab: insert literal tab (no completion in pipe mode)
|
|
251
|
+
session.lineBuffer += ch;
|
|
252
|
+
session.onData(ch);
|
|
253
|
+
}
|
|
254
|
+
else if (ch >= ' ') {
|
|
255
|
+
// Printable character
|
|
256
|
+
session.lineBuffer += ch;
|
|
257
|
+
session.onData(ch);
|
|
258
|
+
}
|
|
259
|
+
// Ignore other control characters (arrow keys, etc.)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
resize(groupJid, cols, rows) {
|
|
264
|
+
const session = this.sessions.get(groupJid);
|
|
265
|
+
if (session?.mode === 'pty') {
|
|
266
|
+
try {
|
|
267
|
+
session.process.stdin?.write(JSON.stringify({ type: 'resize', cols, rows }) + '\n');
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Worker may already be dead — ignore
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
stop(groupJid) {
|
|
275
|
+
const session = this.sessions.get(groupJid);
|
|
276
|
+
if (session) {
|
|
277
|
+
logger.info({ groupJid }, 'Stopping terminal session');
|
|
278
|
+
session.stoppedManually = true;
|
|
279
|
+
this.sessions.delete(groupJid);
|
|
280
|
+
try {
|
|
281
|
+
if (session.mode === 'pty') {
|
|
282
|
+
session.process.stdin?.write(JSON.stringify({ type: 'kill' }) + '\n');
|
|
283
|
+
setTimeout(() => {
|
|
284
|
+
try {
|
|
285
|
+
session.process.kill();
|
|
286
|
+
}
|
|
287
|
+
catch { }
|
|
288
|
+
}, 500);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
session.process.kill();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// ignore - process may already be dead
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
has(groupJid) {
|
|
300
|
+
return this.sessions.has(groupJid);
|
|
301
|
+
}
|
|
302
|
+
shutdown() {
|
|
303
|
+
for (const [groupJid] of this.sessions) {
|
|
304
|
+
this.stop(groupJid);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { formatToolStepLine } from '../shared/dist/tool-step-display.js';
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Utility functions
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { DATA_DIR, TRUST_PROXY } from './config.js';
|
|
5
|
+
/**
|
|
6
|
+
* Strip agent-internal XML tags from output text.
|
|
7
|
+
* Removes `<internal>...</internal>` and `<process>...</process>` blocks
|
|
8
|
+
* that the agent uses for internal reasoning / process tracking.
|
|
9
|
+
*/
|
|
10
|
+
export function stripAgentInternalTags(text) {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/<internal>[\s\S]*?<\/internal>/g, '')
|
|
13
|
+
.replace(/<process>[\s\S]*?<\/process>/g, '')
|
|
14
|
+
.trim();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detect whether an agent output is system-maintenance noise that should
|
|
18
|
+
* be suppressed from IM delivery when sourceKind is 'auto_continue'.
|
|
19
|
+
*
|
|
20
|
+
* These are short acknowledgements that the agent generates when its session
|
|
21
|
+
* transcript contains memory-flush / AGENTS.md-update context from the
|
|
22
|
+
* compaction pipeline (issue #275). Substantive user-facing continuations
|
|
23
|
+
* (task resumption, actual replies) are NOT noise and must pass through.
|
|
24
|
+
*
|
|
25
|
+
* Heuristic: text is "noise" if it is short (<= 30 chars) AND matches a
|
|
26
|
+
* known system-acknowledgement pattern after normalisation.
|
|
27
|
+
*/
|
|
28
|
+
const NOISE_PATTERNS = [
|
|
29
|
+
/^ok[.。!!]?$/,
|
|
30
|
+
/^好的[.。!!]?$/,
|
|
31
|
+
/^已更新/,
|
|
32
|
+
/^已完成/,
|
|
33
|
+
/^已刷新/,
|
|
34
|
+
/^记忆已/,
|
|
35
|
+
/^agents\.md\s*已/,
|
|
36
|
+
/^memory\s*(flush|updated)/i,
|
|
37
|
+
];
|
|
38
|
+
export function isSystemMaintenanceNoise(text) {
|
|
39
|
+
const normalized = text.trim().toLowerCase();
|
|
40
|
+
if (!normalized)
|
|
41
|
+
return true;
|
|
42
|
+
if (normalized.length > 30)
|
|
43
|
+
return false;
|
|
44
|
+
return NOISE_PATTERNS.some((p) => p.test(normalized));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Strip virtual JID suffixes (#task:xxx, #agent:xxx) to get the base JID.
|
|
48
|
+
*/
|
|
49
|
+
export function stripVirtualJidSuffix(jid) {
|
|
50
|
+
const taskSep = jid.indexOf('#task:');
|
|
51
|
+
if (taskSep >= 0)
|
|
52
|
+
return jid.slice(0, taskSep);
|
|
53
|
+
const agentSep = jid.indexOf('#agent:');
|
|
54
|
+
if (agentSep >= 0)
|
|
55
|
+
return jid.slice(0, agentSep);
|
|
56
|
+
return jid;
|
|
57
|
+
}
|
|
58
|
+
export function getClientIp(c) {
|
|
59
|
+
if (TRUST_PROXY) {
|
|
60
|
+
const xff = c.req.header('x-forwarded-for');
|
|
61
|
+
if (xff) {
|
|
62
|
+
const firstIp = xff.split(',')[0]?.trim();
|
|
63
|
+
if (firstIp)
|
|
64
|
+
return firstIp;
|
|
65
|
+
}
|
|
66
|
+
const realIp = c.req.header('x-real-ip');
|
|
67
|
+
if (realIp)
|
|
68
|
+
return realIp;
|
|
69
|
+
}
|
|
70
|
+
// Fallback: connection remote address (Hono + Node.js adapter)
|
|
71
|
+
// Hono Node.js adapter 将 IncomingMessage 存于 c.env.incoming
|
|
72
|
+
const connInfo = c.env?.incoming?.socket?.remoteAddress ||
|
|
73
|
+
c.env?.remoteAddr ||
|
|
74
|
+
c.req.raw?.socket?.remoteAddress;
|
|
75
|
+
return connInfo || 'unknown';
|
|
76
|
+
}
|
|
77
|
+
/** Create IPC + session directories for an agent. */
|
|
78
|
+
export function ensureAgentDirectories(folder, agentId) {
|
|
79
|
+
const agentIpcDir = path.join(DATA_DIR, 'ipc', folder, 'agents', agentId);
|
|
80
|
+
fs.mkdirSync(path.join(agentIpcDir, 'input'), { recursive: true });
|
|
81
|
+
fs.mkdirSync(path.join(agentIpcDir, 'messages'), { recursive: true });
|
|
82
|
+
fs.mkdirSync(path.join(agentIpcDir, 'tasks'), { recursive: true });
|
|
83
|
+
fs.mkdirSync(path.join(DATA_DIR, 'sessions', folder, 'agents', agentId, '.claude'), { recursive: true });
|
|
84
|
+
return agentIpcDir;
|
|
85
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Shared state and utilities for web server
|
|
2
|
+
import { getJidsByFolder, getRegisteredGroup, getGroupMemberRole, getSessionWithUser, } from './db.js';
|
|
3
|
+
let deps = null;
|
|
4
|
+
export const wsClients = new Map();
|
|
5
|
+
export const MAX_GROUP_NAME_LEN = 40;
|
|
6
|
+
export function setWebDeps(d) {
|
|
7
|
+
deps = d;
|
|
8
|
+
}
|
|
9
|
+
export function getWebDeps() {
|
|
10
|
+
return deps;
|
|
11
|
+
}
|
|
12
|
+
// lastActiveCache - 5 min debounce for session activity tracking
|
|
13
|
+
export const lastActiveCache = new Map();
|
|
14
|
+
export const LAST_ACTIVE_DEBOUNCE_MS = 5 * 60 * 1000;
|
|
15
|
+
const LAST_ACTIVE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
const lastActiveCleanupTimer = setInterval(() => {
|
|
17
|
+
const cutoff = Date.now() - LAST_ACTIVE_CACHE_TTL_MS;
|
|
18
|
+
for (const [sessionId, touchedAt] of lastActiveCache.entries()) {
|
|
19
|
+
if (touchedAt < cutoff)
|
|
20
|
+
lastActiveCache.delete(sessionId);
|
|
21
|
+
}
|
|
22
|
+
}, 60 * 60 * 1000);
|
|
23
|
+
lastActiveCleanupTimer.unref?.();
|
|
24
|
+
// Session data cache — 30s TTL, avoids DB query on every request
|
|
25
|
+
const SESSION_CACHE_TTL_MS = 30 * 1000;
|
|
26
|
+
const sessionCache = new Map();
|
|
27
|
+
export function getCachedSessionWithUser(sessionId) {
|
|
28
|
+
const cached = sessionCache.get(sessionId);
|
|
29
|
+
if (cached && cached.expiry > Date.now()) {
|
|
30
|
+
return cached.data;
|
|
31
|
+
}
|
|
32
|
+
sessionCache.delete(sessionId);
|
|
33
|
+
const data = getSessionWithUser(sessionId);
|
|
34
|
+
if (data) {
|
|
35
|
+
sessionCache.set(sessionId, {
|
|
36
|
+
data,
|
|
37
|
+
expiry: Date.now() + SESSION_CACHE_TTL_MS,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
export function invalidateSessionCache(sessionId) {
|
|
43
|
+
sessionCache.delete(sessionId);
|
|
44
|
+
lastActiveCache.delete(sessionId);
|
|
45
|
+
}
|
|
46
|
+
export function invalidateUserSessions(userId) {
|
|
47
|
+
for (const [sid, entry] of sessionCache.entries()) {
|
|
48
|
+
if (entry.data.user_id === userId) {
|
|
49
|
+
sessionCache.delete(sid);
|
|
50
|
+
lastActiveCache.delete(sid);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const sessionCacheCleanupTimer = setInterval(() => {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const [sid, entry] of sessionCache.entries()) {
|
|
57
|
+
if (entry.expiry < now)
|
|
58
|
+
sessionCache.delete(sid);
|
|
59
|
+
}
|
|
60
|
+
}, 5 * 60 * 1000);
|
|
61
|
+
sessionCacheCleanupTimer.unref?.();
|
|
62
|
+
// Cookie parser - used by middleware and WebSocket
|
|
63
|
+
export function parseCookie(cookieHeader) {
|
|
64
|
+
if (!cookieHeader)
|
|
65
|
+
return {};
|
|
66
|
+
const cookies = {};
|
|
67
|
+
for (const cookie of cookieHeader.split(';')) {
|
|
68
|
+
const pair = cookie.trim();
|
|
69
|
+
const eqIndex = pair.indexOf('=');
|
|
70
|
+
if (eqIndex === -1)
|
|
71
|
+
continue;
|
|
72
|
+
const key = pair.slice(0, eqIndex).trim();
|
|
73
|
+
const value = pair.slice(eqIndex + 1).trim();
|
|
74
|
+
if (key)
|
|
75
|
+
cookies[key] = value;
|
|
76
|
+
}
|
|
77
|
+
return cookies;
|
|
78
|
+
}
|
|
79
|
+
// Host execution helpers
|
|
80
|
+
export function isHostExecutionGroup(group) {
|
|
81
|
+
return (group.executionMode || 'container') === 'host';
|
|
82
|
+
}
|
|
83
|
+
export function hasHostExecutionPermission(user) {
|
|
84
|
+
return user.role === 'admin';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a user can access (view messages, send messages to) a group.
|
|
88
|
+
* All users (including admin) follow the same visibility rules:
|
|
89
|
+
* - is_home groups → only the owner (created_by) can access
|
|
90
|
+
* - IM groups (jid does not start with 'web:') → owner or group_members
|
|
91
|
+
* - folder === 'main' → only the admin who owns it
|
|
92
|
+
* - Web groups → created_by matches user.id, or user is in group_members
|
|
93
|
+
*/
|
|
94
|
+
export function canAccessGroup(user, group) {
|
|
95
|
+
if (group.is_home)
|
|
96
|
+
return group.created_by === user.id;
|
|
97
|
+
// IM groups: check ownership if created_by is set.
|
|
98
|
+
// For legacy rows without created_by, resolve owner from sibling home group.
|
|
99
|
+
if (!group.jid.startsWith('web:')) {
|
|
100
|
+
if (group.created_by === user.id)
|
|
101
|
+
return true;
|
|
102
|
+
// Check membership for IM groups sharing a non-home folder
|
|
103
|
+
if (getGroupMemberRole(group.folder, user.id) !== null)
|
|
104
|
+
return true;
|
|
105
|
+
if (group.created_by)
|
|
106
|
+
return false;
|
|
107
|
+
const siblingJids = getJidsByFolder(group.folder);
|
|
108
|
+
for (const jid of siblingJids) {
|
|
109
|
+
if (jid === group.jid)
|
|
110
|
+
continue;
|
|
111
|
+
const sibling = getRegisteredGroup(jid);
|
|
112
|
+
if (sibling?.is_home && sibling.created_by) {
|
|
113
|
+
return sibling.created_by === user.id;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Ownership cannot be resolved for this IM group → deny by default.
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// folder === 'main': only accessible by the admin who owns it (via created_by or group_members)
|
|
120
|
+
if (group.folder === 'main') {
|
|
121
|
+
if (group.created_by === user.id)
|
|
122
|
+
return true;
|
|
123
|
+
return getGroupMemberRole(group.folder, user.id) !== null;
|
|
124
|
+
}
|
|
125
|
+
if (group.created_by === user.id)
|
|
126
|
+
return true;
|
|
127
|
+
// Check group_members table for shared workspaces
|
|
128
|
+
return getGroupMemberRole(group.folder, user.id) !== null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if a user can modify (rename, reset) a group.
|
|
132
|
+
* - Users can modify their own home group.
|
|
133
|
+
* - Users can modify web groups they created.
|
|
134
|
+
* - IM groups can be modified by their owner (created_by).
|
|
135
|
+
*/
|
|
136
|
+
export function canModifyGroup(user, group) {
|
|
137
|
+
if (group.is_home)
|
|
138
|
+
return group.created_by === user.id;
|
|
139
|
+
if (!group.jid.startsWith('web:'))
|
|
140
|
+
return group.created_by === user.id;
|
|
141
|
+
return group.created_by === user.id;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a user can manage members (add/remove) of a group.
|
|
145
|
+
* - Home groups cannot have members managed.
|
|
146
|
+
* - Only the group creator (owner) can manage members.
|
|
147
|
+
*/
|
|
148
|
+
export function canManageGroupMembers(user, group) {
|
|
149
|
+
if (group.is_home)
|
|
150
|
+
return false;
|
|
151
|
+
return group.created_by === user.id;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check if a user can delete a group.
|
|
155
|
+
* - is_home groups cannot be deleted by anyone.
|
|
156
|
+
*/
|
|
157
|
+
export function canDeleteGroup(user, group) {
|
|
158
|
+
if (group.is_home)
|
|
159
|
+
return false;
|
|
160
|
+
return canModifyGroup(user, group);
|
|
161
|
+
}
|