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,509 @@
|
|
|
1
|
+
// Task management routes
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
5
|
+
import { sdkQuery } from '../sdk-query.js';
|
|
6
|
+
import { removeFlowArtifacts } from '../file-manager.js';
|
|
7
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
8
|
+
import { TaskCreateSchema, TaskPatchSchema } from '../schemas.js';
|
|
9
|
+
import { logger } from '../logger.js';
|
|
10
|
+
import { getAllTasks, getTaskById, createTask, updateTask, deleteTask, getTaskRunLogs, getRegisteredGroup, getAllRegisteredGroups, getUserHomeGroup, deleteGroupData, } from '../db.js';
|
|
11
|
+
import { TIMEZONE } from '../config.js';
|
|
12
|
+
import { isHostExecutionGroup, hasHostExecutionPermission, canAccessGroup, getWebDeps, } from '../web-context.js';
|
|
13
|
+
import { getRunningTaskIds } from '../task-scheduler.js';
|
|
14
|
+
const tasksRoutes = new Hono();
|
|
15
|
+
// --- Routes ---
|
|
16
|
+
tasksRoutes.get('/', authMiddleware, (c) => {
|
|
17
|
+
const authUser = c.get('user');
|
|
18
|
+
const allGroups = getAllRegisteredGroups();
|
|
19
|
+
const tasks = getAllTasks().filter((task) => {
|
|
20
|
+
// Host-mode tasks are only visible to admin
|
|
21
|
+
if (task.execution_mode === 'host' && authUser.role !== 'admin') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const group = allGroups[task.chat_jid];
|
|
25
|
+
// Conservative: if group can't be resolved, only admin can see (may be orphaned task)
|
|
26
|
+
if (!group)
|
|
27
|
+
return authUser.role === 'admin';
|
|
28
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, { ...group, jid: task.chat_jid }))
|
|
29
|
+
return false;
|
|
30
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser))
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
const visibleTaskIds = new Set(tasks.map((t) => t.id));
|
|
35
|
+
const filteredRunningIds = getRunningTaskIds().filter((id) => visibleTaskIds.has(id));
|
|
36
|
+
return c.json({ tasks, runningTaskIds: filteredRunningIds });
|
|
37
|
+
});
|
|
38
|
+
tasksRoutes.post('/', authMiddleware, async (c) => {
|
|
39
|
+
const body = await c.req.json().catch(() => ({}));
|
|
40
|
+
const validation = TaskCreateSchema.safeParse(body);
|
|
41
|
+
if (!validation.success) {
|
|
42
|
+
return c.json({ error: 'Invalid request body', details: validation.error.format() }, 400);
|
|
43
|
+
}
|
|
44
|
+
const { prompt, schedule_type, schedule_value, execution_type, script_command, notify_channels, } = validation.data;
|
|
45
|
+
const authUser = c.get('user');
|
|
46
|
+
// Auto-resolve group_folder/chat_jid from user's home group if not provided
|
|
47
|
+
let groupFolder = validation.data.group_folder;
|
|
48
|
+
let chatJid = validation.data.chat_jid;
|
|
49
|
+
if (!groupFolder || !chatJid) {
|
|
50
|
+
const homeGroup = getUserHomeGroup(authUser.id);
|
|
51
|
+
if (!homeGroup) {
|
|
52
|
+
return c.json({ error: 'User has no home group' }, 400);
|
|
53
|
+
}
|
|
54
|
+
groupFolder = groupFolder || homeGroup.folder;
|
|
55
|
+
chatJid = chatJid || homeGroup.jid;
|
|
56
|
+
}
|
|
57
|
+
const group = getRegisteredGroup(chatJid);
|
|
58
|
+
if (!group)
|
|
59
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
60
|
+
if (group.folder !== groupFolder) {
|
|
61
|
+
return c.json({ error: 'group_folder does not match chat_jid group folder' }, 400);
|
|
62
|
+
}
|
|
63
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, group)) {
|
|
64
|
+
return c.json({ error: 'Group not found' }, 404);
|
|
65
|
+
}
|
|
66
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser)) {
|
|
67
|
+
return c.json({ error: 'Insufficient permissions for host execution mode' }, 403);
|
|
68
|
+
}
|
|
69
|
+
// Only admin can create script tasks
|
|
70
|
+
const execType = execution_type || 'agent';
|
|
71
|
+
if (execType === 'script' && authUser.role !== 'admin') {
|
|
72
|
+
return c.json({ error: '只有管理员可以创建脚本类型任务' }, 403);
|
|
73
|
+
}
|
|
74
|
+
// Determine execution_mode
|
|
75
|
+
let taskExecutionMode;
|
|
76
|
+
if (authUser.role === 'admin') {
|
|
77
|
+
taskExecutionMode = validation.data.execution_mode || 'host';
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (validation.data.execution_mode === 'host') {
|
|
81
|
+
return c.json({ error: '只有管理员可以创建宿主机任务' }, 403);
|
|
82
|
+
}
|
|
83
|
+
taskExecutionMode = 'container';
|
|
84
|
+
}
|
|
85
|
+
const taskId = crypto.randomUUID();
|
|
86
|
+
const now = new Date().toISOString();
|
|
87
|
+
let nextRun;
|
|
88
|
+
if (schedule_type === 'cron') {
|
|
89
|
+
try {
|
|
90
|
+
const cronNext = CronExpressionParser.parse(schedule_value, {
|
|
91
|
+
tz: TIMEZONE,
|
|
92
|
+
})
|
|
93
|
+
.next()
|
|
94
|
+
.toISOString();
|
|
95
|
+
if (!cronNext) {
|
|
96
|
+
return c.json({ error: 'Cron expression produced no next run time' }, 400);
|
|
97
|
+
}
|
|
98
|
+
nextRun = cronNext;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return c.json({ error: 'Invalid cron expression' }, 400);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (schedule_type === 'interval') {
|
|
105
|
+
nextRun = new Date(Date.now() + Number(schedule_value)).toISOString();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// once — use the target time from schedule_value
|
|
109
|
+
nextRun = new Date(schedule_value).toISOString();
|
|
110
|
+
}
|
|
111
|
+
createTask({
|
|
112
|
+
id: taskId,
|
|
113
|
+
group_folder: groupFolder,
|
|
114
|
+
chat_jid: chatJid,
|
|
115
|
+
prompt: prompt || '',
|
|
116
|
+
schedule_type,
|
|
117
|
+
schedule_value,
|
|
118
|
+
context_mode: validation.data.context_mode || 'group',
|
|
119
|
+
execution_type: execType,
|
|
120
|
+
execution_mode: taskExecutionMode,
|
|
121
|
+
script_command: script_command ?? null,
|
|
122
|
+
next_run: nextRun,
|
|
123
|
+
status: 'active',
|
|
124
|
+
created_at: now,
|
|
125
|
+
created_by: authUser.id,
|
|
126
|
+
notify_channels: notify_channels ?? null,
|
|
127
|
+
});
|
|
128
|
+
return c.json({ success: true, taskId });
|
|
129
|
+
});
|
|
130
|
+
tasksRoutes.patch('/:id', authMiddleware, async (c) => {
|
|
131
|
+
const id = c.req.param('id');
|
|
132
|
+
const existing = getTaskById(id);
|
|
133
|
+
if (!existing)
|
|
134
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
135
|
+
const authUser = c.get('user');
|
|
136
|
+
const group = getRegisteredGroup(existing.chat_jid);
|
|
137
|
+
if (!group) {
|
|
138
|
+
if (authUser.role !== 'admin')
|
|
139
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, group)) {
|
|
143
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
144
|
+
}
|
|
145
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser)) {
|
|
146
|
+
return c.json({ error: 'Insufficient permissions for host execution mode' }, 403);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const body = await c.req.json().catch(() => ({}));
|
|
150
|
+
const validation = TaskPatchSchema.safeParse(body);
|
|
151
|
+
if (!validation.success) {
|
|
152
|
+
return c.json({ error: 'Invalid request body', details: validation.error.format() }, 400);
|
|
153
|
+
}
|
|
154
|
+
// Only admin can create/modify script tasks
|
|
155
|
+
const isScriptTask = validation.data.execution_type === 'script' ||
|
|
156
|
+
(existing.execution_type === 'script' &&
|
|
157
|
+
validation.data.script_command !== undefined);
|
|
158
|
+
if (isScriptTask && authUser.role !== 'admin') {
|
|
159
|
+
return c.json({ error: '只有管理员可以创建或修改脚本类型任务' }, 403);
|
|
160
|
+
}
|
|
161
|
+
// Only admin can set execution_mode to 'host'
|
|
162
|
+
if (validation.data.execution_mode === 'host' && authUser.role !== 'admin') {
|
|
163
|
+
return c.json({ error: '只有管理员可以设置宿主机执行模式' }, 403);
|
|
164
|
+
}
|
|
165
|
+
// Auto-recalculate next_run when schedule changes (avoid pulling cron-parser into frontend)
|
|
166
|
+
const patchData = { ...validation.data };
|
|
167
|
+
if (patchData.schedule_type !== undefined ||
|
|
168
|
+
patchData.schedule_value !== undefined) {
|
|
169
|
+
const schedType = patchData.schedule_type ?? existing.schedule_type;
|
|
170
|
+
const schedValue = patchData.schedule_value ?? existing.schedule_value;
|
|
171
|
+
try {
|
|
172
|
+
if (schedType === 'cron') {
|
|
173
|
+
patchData.next_run =
|
|
174
|
+
CronExpressionParser.parse(schedValue, { tz: TIMEZONE })
|
|
175
|
+
.next()
|
|
176
|
+
.toISOString() || new Date().toISOString();
|
|
177
|
+
}
|
|
178
|
+
else if (schedType === 'interval') {
|
|
179
|
+
const ms = parseInt(schedValue, 10);
|
|
180
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
181
|
+
return c.json({ error: 'Invalid interval value' }, 400);
|
|
182
|
+
}
|
|
183
|
+
patchData.next_run = new Date(Date.now() + ms).toISOString();
|
|
184
|
+
}
|
|
185
|
+
else if (schedType === 'once') {
|
|
186
|
+
const ts = Date.parse(schedValue);
|
|
187
|
+
if (isNaN(ts)) {
|
|
188
|
+
return c.json({ error: 'Invalid once schedule value' }, 400);
|
|
189
|
+
}
|
|
190
|
+
patchData.next_run = new Date(ts).toISOString();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return c.json({ error: 'Invalid schedule value for the given schedule type' }, 400);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
updateTask(id, patchData);
|
|
198
|
+
return c.json({ success: true });
|
|
199
|
+
});
|
|
200
|
+
tasksRoutes.delete('/:id', authMiddleware, (c) => {
|
|
201
|
+
const id = c.req.param('id');
|
|
202
|
+
const existing = getTaskById(id);
|
|
203
|
+
if (!existing)
|
|
204
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
205
|
+
const authUser = c.get('user');
|
|
206
|
+
const group = getRegisteredGroup(existing.chat_jid);
|
|
207
|
+
if (!group) {
|
|
208
|
+
if (authUser.role !== 'admin')
|
|
209
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, group)) {
|
|
213
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
214
|
+
}
|
|
215
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser)) {
|
|
216
|
+
return c.json({ error: 'Insufficient permissions for host execution mode' }, 403);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Only admin can delete script tasks
|
|
220
|
+
if (existing.execution_type === 'script' && authUser.role !== 'admin') {
|
|
221
|
+
return c.json({ error: '只有管理员可以删除脚本类型任务' }, 403);
|
|
222
|
+
}
|
|
223
|
+
// Prevent deleting a running task
|
|
224
|
+
if (getRunningTaskIds().includes(id)) {
|
|
225
|
+
return c.json({ error: '任务正在运行中,请先等待完成或停止任务' }, 409);
|
|
226
|
+
}
|
|
227
|
+
// Clean up dedicated workspace if exists
|
|
228
|
+
if (existing.workspace_jid && existing.workspace_folder) {
|
|
229
|
+
const wsGroup = getRegisteredGroup(existing.workspace_jid);
|
|
230
|
+
if (wsGroup) {
|
|
231
|
+
deleteGroupData(existing.workspace_jid, existing.workspace_folder);
|
|
232
|
+
}
|
|
233
|
+
// Remove all flow artifacts (groups/, sessions/, ipc/, env/, memory/)
|
|
234
|
+
removeFlowArtifacts(existing.workspace_folder);
|
|
235
|
+
const deps = getWebDeps();
|
|
236
|
+
if (deps) {
|
|
237
|
+
delete deps.getRegisteredGroups()[existing.workspace_jid];
|
|
238
|
+
delete deps.getSessions()[existing.workspace_folder];
|
|
239
|
+
}
|
|
240
|
+
logger.info({
|
|
241
|
+
taskId: id,
|
|
242
|
+
workspaceJid: existing.workspace_jid,
|
|
243
|
+
workspaceFolder: existing.workspace_folder,
|
|
244
|
+
}, 'Task workspace deleted');
|
|
245
|
+
}
|
|
246
|
+
deleteTask(id);
|
|
247
|
+
return c.json({ success: true });
|
|
248
|
+
});
|
|
249
|
+
tasksRoutes.post('/:id/run', authMiddleware, (c) => {
|
|
250
|
+
const id = c.req.param('id');
|
|
251
|
+
const existing = getTaskById(id);
|
|
252
|
+
if (!existing)
|
|
253
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
254
|
+
const authUser = c.get('user');
|
|
255
|
+
const group = getRegisteredGroup(existing.chat_jid);
|
|
256
|
+
if (!group) {
|
|
257
|
+
if (authUser.role !== 'admin')
|
|
258
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, group)) {
|
|
262
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
263
|
+
}
|
|
264
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser)) {
|
|
265
|
+
return c.json({ error: 'Insufficient permissions for host execution mode' }, 403);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Only admin can run script tasks
|
|
269
|
+
if (existing.execution_type === 'script' && authUser.role !== 'admin') {
|
|
270
|
+
return c.json({ error: '只有管理员可以运行脚本类型任务' }, 403);
|
|
271
|
+
}
|
|
272
|
+
const deps = getWebDeps();
|
|
273
|
+
if (!deps?.triggerTaskRun)
|
|
274
|
+
return c.json({ error: 'Scheduler not available' }, 503);
|
|
275
|
+
const result = deps.triggerTaskRun(id);
|
|
276
|
+
if (!result.success)
|
|
277
|
+
return c.json({ error: result.error }, 409);
|
|
278
|
+
return c.json({ success: true });
|
|
279
|
+
});
|
|
280
|
+
tasksRoutes.get('/:id/logs', authMiddleware, (c) => {
|
|
281
|
+
const id = c.req.param('id');
|
|
282
|
+
const existing = getTaskById(id);
|
|
283
|
+
if (!existing)
|
|
284
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
285
|
+
const authUser = c.get('user');
|
|
286
|
+
const group = getRegisteredGroup(existing.chat_jid);
|
|
287
|
+
if (!group) {
|
|
288
|
+
if (authUser.role !== 'admin')
|
|
289
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
if (!canAccessGroup({ id: authUser.id, role: authUser.role }, group)) {
|
|
293
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
294
|
+
}
|
|
295
|
+
if (isHostExecutionGroup(group) && !hasHostExecutionPermission(authUser)) {
|
|
296
|
+
return c.json({ error: 'Insufficient permissions for host execution mode' }, 403);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const limitRaw = parseInt(c.req.query('limit') || '20', 10);
|
|
300
|
+
const limit = Math.min(Number.isFinite(limitRaw) ? Math.max(1, limitRaw) : 20, 200);
|
|
301
|
+
const logs = getTaskRunLogs(id, limit);
|
|
302
|
+
return c.json({ logs });
|
|
303
|
+
});
|
|
304
|
+
/** Build the AI parse prompt for a task description */
|
|
305
|
+
function buildParsePrompt(description) {
|
|
306
|
+
const now = new Date();
|
|
307
|
+
return `你是一个任务调度解析器。用户会用自然语言描述他们想要创建的定时任务,你需要解析出结构化的任务参数。
|
|
308
|
+
|
|
309
|
+
当前时间: ${now.toISOString()}
|
|
310
|
+
当前时区: ${TIMEZONE}
|
|
311
|
+
|
|
312
|
+
用户描述: "${description}"
|
|
313
|
+
|
|
314
|
+
请返回一个 JSON 对象(不要包含任何其他文字),包含以下字段:
|
|
315
|
+
- "prompt": string — 任务要执行的 prompt(精炼用户的意图,作为 Agent 的指令)
|
|
316
|
+
- "schedule_type": "cron" | "interval" | "once" — 调度类型
|
|
317
|
+
- "schedule_value": string — 调度值:
|
|
318
|
+
- cron 类型: cron 表达式(推荐 5 段:分 时 日 月 周,也支持 6 段含秒)
|
|
319
|
+
- interval 类型: 毫秒数字符串(如 "3600000" 表示 1 小时)
|
|
320
|
+
- once 类型: ISO 8601 日期时间字符串
|
|
321
|
+
- "context_mode": "group" | "isolated" — 上下文模式(大多数情况推荐 "group")
|
|
322
|
+
- "summary": string — 用一句话解释你的理解(中文)
|
|
323
|
+
|
|
324
|
+
注意:
|
|
325
|
+
- cron 表达式中的时间为北京时间(UTC+8)
|
|
326
|
+
- 推荐使用 5 段格式:分 时 日 月 星期
|
|
327
|
+
- 支持特殊字符:*/n(步长)、a-b(范围)、a,b,c(列表)、L(最后)、W(工作日)、#(第N个)
|
|
328
|
+
- 支持预定义表达式:@daily, @hourly, @weekly, @monthly, @yearly
|
|
329
|
+
- "每天早上 9 点" → cron "0 9 * * *"
|
|
330
|
+
- "每小时" → interval "3600000"
|
|
331
|
+
- "每 30 分钟" → interval "1800000"
|
|
332
|
+
- "明天下午 3 点" → once,计算出具体的 ISO 时间
|
|
333
|
+
- "每周一早上 10 点" → cron "0 10 * * 1"
|
|
334
|
+
- "每月最后一天" → cron "0 0 L * *"
|
|
335
|
+
- "每 5 分钟" → cron "*/5 * * * *"
|
|
336
|
+
|
|
337
|
+
只返回 JSON,不要返回其他任何内容。`;
|
|
338
|
+
}
|
|
339
|
+
/** Parse AI response text into structured task params */
|
|
340
|
+
function parseAiResult(result, description) {
|
|
341
|
+
try {
|
|
342
|
+
let jsonStr = result;
|
|
343
|
+
const fenced = result.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
344
|
+
if (fenced)
|
|
345
|
+
jsonStr = fenced[1].trim();
|
|
346
|
+
const parsed = JSON.parse(jsonStr);
|
|
347
|
+
return {
|
|
348
|
+
prompt: parsed.prompt || description,
|
|
349
|
+
schedule_type: parsed.schedule_type || 'cron',
|
|
350
|
+
schedule_value: parsed.schedule_value || '',
|
|
351
|
+
summary: parsed.summary || '',
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* AI create: immediately create task in 'parsing' status, resolve schedule in background.
|
|
360
|
+
*/
|
|
361
|
+
tasksRoutes.post('/ai', authMiddleware, async (c) => {
|
|
362
|
+
const authUser = c.get('user');
|
|
363
|
+
const body = await c.req.json().catch(() => ({}));
|
|
364
|
+
const description = typeof body.description === 'string' ? body.description.trim() : '';
|
|
365
|
+
if (!description) {
|
|
366
|
+
return c.json({ error: '请输入任务描述' }, 400);
|
|
367
|
+
}
|
|
368
|
+
const notifyChannels = body.notify_channels ?? null;
|
|
369
|
+
// Resolve home group
|
|
370
|
+
const homeGroup = getUserHomeGroup(authUser.id);
|
|
371
|
+
if (!homeGroup)
|
|
372
|
+
return c.json({ error: 'Home group not found' }, 400);
|
|
373
|
+
const taskId = crypto.randomUUID();
|
|
374
|
+
const now = new Date().toISOString();
|
|
375
|
+
// Determine execution_mode
|
|
376
|
+
const taskExecutionMode = authUser.role === 'admin' ? 'host' : 'container';
|
|
377
|
+
// Create task immediately with 'parsing' status and description as prompt
|
|
378
|
+
createTask({
|
|
379
|
+
id: taskId,
|
|
380
|
+
group_folder: homeGroup.folder,
|
|
381
|
+
chat_jid: homeGroup.jid,
|
|
382
|
+
prompt: description,
|
|
383
|
+
schedule_type: 'cron',
|
|
384
|
+
schedule_value: '0 0 * * *', // placeholder, will be updated after parsing
|
|
385
|
+
context_mode: 'group',
|
|
386
|
+
execution_type: 'agent',
|
|
387
|
+
execution_mode: taskExecutionMode,
|
|
388
|
+
script_command: null,
|
|
389
|
+
next_run: null,
|
|
390
|
+
status: 'parsing',
|
|
391
|
+
created_at: now,
|
|
392
|
+
created_by: authUser.id,
|
|
393
|
+
notify_channels: notifyChannels,
|
|
394
|
+
});
|
|
395
|
+
logger.info({ taskId, description: description.slice(0, 80) }, 'AI task created, parsing in background');
|
|
396
|
+
// Background: parse with SDK and update task
|
|
397
|
+
void (async () => {
|
|
398
|
+
try {
|
|
399
|
+
const parsePrompt = buildParsePrompt(description);
|
|
400
|
+
const model = process.env.RECALL_MODEL || undefined;
|
|
401
|
+
const result = await sdkQuery(parsePrompt, { model, timeout: 60_000 });
|
|
402
|
+
if (!result) {
|
|
403
|
+
const cur = getTaskById(taskId);
|
|
404
|
+
if (!cur || cur.status !== 'parsing')
|
|
405
|
+
return;
|
|
406
|
+
updateTask(taskId, {
|
|
407
|
+
status: 'paused',
|
|
408
|
+
prompt: description,
|
|
409
|
+
});
|
|
410
|
+
logger.warn({ taskId }, 'AI parse returned null, task paused');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const parsed = parseAiResult(result, description);
|
|
414
|
+
if (!parsed || !parsed.schedule_value) {
|
|
415
|
+
const cur = getTaskById(taskId);
|
|
416
|
+
if (!cur || cur.status !== 'parsing')
|
|
417
|
+
return;
|
|
418
|
+
updateTask(taskId, {
|
|
419
|
+
status: 'paused',
|
|
420
|
+
prompt: description,
|
|
421
|
+
});
|
|
422
|
+
logger.warn({ taskId }, 'AI parse result invalid, task paused');
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// Compute next_run from parsed schedule
|
|
426
|
+
let nextRun = null;
|
|
427
|
+
try {
|
|
428
|
+
if (parsed.schedule_type === 'cron') {
|
|
429
|
+
nextRun = CronExpressionParser.parse(parsed.schedule_value, {
|
|
430
|
+
tz: TIMEZONE,
|
|
431
|
+
})
|
|
432
|
+
.next()
|
|
433
|
+
.toISOString();
|
|
434
|
+
}
|
|
435
|
+
else if (parsed.schedule_type === 'interval') {
|
|
436
|
+
nextRun = new Date(Date.now() + Number(parsed.schedule_value)).toISOString();
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
nextRun = new Date(parsed.schedule_value).toISOString();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
// Invalid schedule, keep paused
|
|
444
|
+
const cur = getTaskById(taskId);
|
|
445
|
+
if (!cur || cur.status !== 'parsing')
|
|
446
|
+
return;
|
|
447
|
+
updateTask(taskId, {
|
|
448
|
+
status: 'paused',
|
|
449
|
+
prompt: parsed.prompt,
|
|
450
|
+
});
|
|
451
|
+
logger.warn({ taskId, scheduleValue: parsed.schedule_value }, 'AI parsed schedule invalid, task paused');
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const cur = getTaskById(taskId);
|
|
455
|
+
if (!cur || cur.status !== 'parsing')
|
|
456
|
+
return;
|
|
457
|
+
updateTask(taskId, {
|
|
458
|
+
prompt: parsed.prompt,
|
|
459
|
+
schedule_type: parsed.schedule_type,
|
|
460
|
+
schedule_value: parsed.schedule_value,
|
|
461
|
+
next_run: nextRun,
|
|
462
|
+
status: 'active',
|
|
463
|
+
});
|
|
464
|
+
logger.info({
|
|
465
|
+
taskId,
|
|
466
|
+
scheduleType: parsed.schedule_type,
|
|
467
|
+
scheduleValue: parsed.schedule_value,
|
|
468
|
+
}, 'AI task parse complete, activated');
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
logger.error({ taskId, err }, 'AI task background parse failed');
|
|
472
|
+
const cur = getTaskById(taskId);
|
|
473
|
+
if (cur && cur.status === 'parsing') {
|
|
474
|
+
updateTask(taskId, { status: 'paused' });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
})().catch((err) => logger.error({ taskId, err }, 'Unhandled AI task parse error'));
|
|
478
|
+
return c.json({ success: true, taskId });
|
|
479
|
+
});
|
|
480
|
+
/**
|
|
481
|
+
* Parse natural language task description (synchronous, kept for backward compat).
|
|
482
|
+
*/
|
|
483
|
+
tasksRoutes.post('/parse', authMiddleware, async (c) => {
|
|
484
|
+
const body = await c.req.json().catch(() => ({}));
|
|
485
|
+
const description = typeof body.description === 'string' ? body.description.trim() : '';
|
|
486
|
+
if (!description) {
|
|
487
|
+
return c.json({ error: '请输入任务描述' }, 400);
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const model = process.env.RECALL_MODEL || undefined;
|
|
491
|
+
const result = await sdkQuery(buildParsePrompt(description), {
|
|
492
|
+
model,
|
|
493
|
+
timeout: 30_000,
|
|
494
|
+
});
|
|
495
|
+
if (!result) {
|
|
496
|
+
return c.json({ error: 'AI 解析失败,请重试或切换到手动模式' }, 502);
|
|
497
|
+
}
|
|
498
|
+
const parsed = parseAiResult(result, description);
|
|
499
|
+
if (!parsed) {
|
|
500
|
+
return c.json({ error: 'AI 返回格式异常,请重试或切换到手动模式' }, 502);
|
|
501
|
+
}
|
|
502
|
+
return c.json({ success: true, parsed });
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
logger.warn({ err }, 'task-parse: failed to parse AI response');
|
|
506
|
+
return c.json({ error: 'AI 返回格式异常,请重试或切换到手动模式' }, 502);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
export default tasksRoutes;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
3
|
+
import { getUsageDailyStats, getUsageDailySummary, getUsageModels, getUsageUsers, } from '../db.js';
|
|
4
|
+
const usage = new Hono();
|
|
5
|
+
usage.use('*', authMiddleware);
|
|
6
|
+
/**
|
|
7
|
+
* Resolve userId for queries:
|
|
8
|
+
* - Admin can filter by any userId or see all (undefined = all)
|
|
9
|
+
* - Member always sees only their own data
|
|
10
|
+
*/
|
|
11
|
+
function resolveUserId(user, requestedUserId) {
|
|
12
|
+
if (user.role === 'admin') {
|
|
13
|
+
return requestedUserId || undefined; // undefined = all users
|
|
14
|
+
}
|
|
15
|
+
return user.id; // member always sees only own data
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* GET /api/usage/stats?days=7&userId=&model=
|
|
19
|
+
* Returns aggregated token usage statistics from usage_daily_summary.
|
|
20
|
+
* Fixes: token KPI (uses modelUsage data) + timezone (local date grouping).
|
|
21
|
+
*/
|
|
22
|
+
usage.get('/stats', (c) => {
|
|
23
|
+
const user = c.get('user');
|
|
24
|
+
const daysParam = c.req.query('days');
|
|
25
|
+
const days = daysParam
|
|
26
|
+
? Math.min(Math.max(parseInt(daysParam, 10) || 7, 1), 365)
|
|
27
|
+
: 7;
|
|
28
|
+
const userId = resolveUserId(user, c.req.query('userId') || undefined);
|
|
29
|
+
const model = c.req.query('model') || undefined;
|
|
30
|
+
const summary = getUsageDailySummary(days, userId, model);
|
|
31
|
+
const breakdown = getUsageDailyStats(days, userId, model);
|
|
32
|
+
// Compute actual data range for frontend display
|
|
33
|
+
const dates = breakdown.map((r) => r.date);
|
|
34
|
+
const uniqueDates = [...new Set(dates)].sort();
|
|
35
|
+
const dataRange = uniqueDates.length > 0
|
|
36
|
+
? {
|
|
37
|
+
from: uniqueDates[0],
|
|
38
|
+
to: uniqueDates[uniqueDates.length - 1],
|
|
39
|
+
activeDays: uniqueDates.length,
|
|
40
|
+
}
|
|
41
|
+
: null;
|
|
42
|
+
return c.json({ summary, breakdown, days, dataRange });
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* GET /api/usage/models
|
|
46
|
+
* Returns list of all models that have usage data.
|
|
47
|
+
*/
|
|
48
|
+
usage.get('/models', (c) => {
|
|
49
|
+
const models = getUsageModels();
|
|
50
|
+
return c.json({ models });
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* GET /api/usage/users
|
|
54
|
+
* Returns list of users that have usage data. Admin only.
|
|
55
|
+
*/
|
|
56
|
+
usage.get('/users', (c) => {
|
|
57
|
+
const user = c.get('user');
|
|
58
|
+
if (user.role !== 'admin') {
|
|
59
|
+
return c.json({ users: [{ id: user.id, username: user.username }] });
|
|
60
|
+
}
|
|
61
|
+
const users = getUsageUsers();
|
|
62
|
+
return c.json({ users });
|
|
63
|
+
});
|
|
64
|
+
export { usage };
|