@konglx/rotom 2.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +417 -0
  2. package/bin/mesh-master.sh +439 -0
  3. package/bin/rotom +29 -0
  4. package/bin/rotom-link.sh +136 -0
  5. package/bin/rotom-send-with-status +57 -0
  6. package/bin/rotom-up.sh +428 -0
  7. package/dist/cli/ask.js +62 -0
  8. package/dist/cli/common.js +321 -0
  9. package/dist/cli/config.js +65 -0
  10. package/dist/cli/directory.js +17 -0
  11. package/dist/cli/executor.js +58 -0
  12. package/dist/cli/fed.js +91 -0
  13. package/dist/cli/group.js +273 -0
  14. package/dist/cli/identity.js +62 -0
  15. package/dist/cli/init.js +268 -0
  16. package/dist/cli/issue.js +202 -0
  17. package/dist/cli/join.js +170 -0
  18. package/dist/cli/link.js +47 -0
  19. package/dist/cli/master.js +51 -0
  20. package/dist/cli/memory.js +307 -0
  21. package/dist/cli/note.js +68 -0
  22. package/dist/cli/repo.js +77 -0
  23. package/dist/cli/rotom.js +277 -0
  24. package/dist/cli/routes.js +118 -0
  25. package/dist/cli/run.js +45 -0
  26. package/dist/cli/schedule.js +237 -0
  27. package/dist/cli/skill.js +173 -0
  28. package/dist/cli/team.js +106 -0
  29. package/dist/executor/claude-code-hook.cjs +80 -0
  30. package/dist/executor/cli-executor.js +8 -0
  31. package/dist/executor/executors/claude-code.js +780 -0
  32. package/dist/executor/executors/codex.js +719 -0
  33. package/dist/executor/executors/hermes-cli.js +855 -0
  34. package/dist/executor/executors/openclaw.js +467 -0
  35. package/dist/executor/executors/pi.js +514 -0
  36. package/dist/executor/index.js +269 -0
  37. package/dist/executor/jsonrpc-transport.js +125 -0
  38. package/dist/executor/process-runner.js +101 -0
  39. package/dist/executor/reasoning-status.js +83 -0
  40. package/dist/executor/repo-cache.js +502 -0
  41. package/dist/executor/session-store.js +188 -0
  42. package/dist/executor/worker-chat.js +257 -0
  43. package/dist/executor/worker-connection.js +89 -0
  44. package/dist/executor/worker-issue.js +264 -0
  45. package/dist/executor/worker.js +877 -0
  46. package/dist/link/pending-requests.js +72 -0
  47. package/dist/link/server.js +233 -0
  48. package/dist/link/visibility-store.js +58 -0
  49. package/dist/master/api/agents.js +333 -0
  50. package/dist/master/api/artifacts.js +271 -0
  51. package/dist/master/api/domains.js +64 -0
  52. package/dist/master/api/groups.js +635 -0
  53. package/dist/master/api/guidance-templates.js +147 -0
  54. package/dist/master/api/index.js +89 -0
  55. package/dist/master/api/issues-patrol.js +172 -0
  56. package/dist/master/api/issues.js +663 -0
  57. package/dist/master/api/links-patrol.js +168 -0
  58. package/dist/master/api/links.js +114 -0
  59. package/dist/master/api/memory.js +259 -0
  60. package/dist/master/api/messages.js +157 -0
  61. package/dist/master/api/notes.js +77 -0
  62. package/dist/master/api/schedule-patterns.js +133 -0
  63. package/dist/master/api/schedules.js +272 -0
  64. package/dist/master/api/sessions.js +158 -0
  65. package/dist/master/api/share.js +269 -0
  66. package/dist/master/api/skills.js +190 -0
  67. package/dist/master/api/teams.js +122 -0
  68. package/dist/master/api/uploads.js +245 -0
  69. package/dist/master/auth.js +134 -0
  70. package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
  71. package/dist/master/dashboard/animations/calico-error.apng +0 -0
  72. package/dist/master/dashboard/animations/calico-happy.apng +0 -0
  73. package/dist/master/dashboard/animations/calico-notification.apng +0 -0
  74. package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
  75. package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
  76. package/dist/master/dashboard/animations/calico-waking.apng +0 -0
  77. package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
  78. package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
  79. package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
  80. package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
  81. package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
  82. package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
  83. package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
  84. package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
  85. package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
  86. package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
  87. package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
  88. package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
  89. package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
  90. package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
  91. package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
  92. package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
  93. package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
  94. package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
  95. package/dist/master/dashboard/debug-auth.html +197 -0
  96. package/dist/master/dashboard/favicon.ico +0 -0
  97. package/dist/master/dashboard/index.html +20 -0
  98. package/dist/master/dashboard/rotom-avatar.png +0 -0
  99. package/dist/master/db/agent-sessions.js +60 -0
  100. package/dist/master/db/agent-visibility.js +64 -0
  101. package/dist/master/db/agents.js +119 -0
  102. package/dist/master/db/ask-bridges.js +157 -0
  103. package/dist/master/db/build-update.js +59 -0
  104. package/dist/master/db/core.js +82 -0
  105. package/dist/master/db/domains.js +80 -0
  106. package/dist/master/db/groups.js +316 -0
  107. package/dist/master/db/guidance-templates.js +58 -0
  108. package/dist/master/db/index.js +12 -0
  109. package/dist/master/db/internal.js +45 -0
  110. package/dist/master/db/issues-patrol.js +81 -0
  111. package/dist/master/db/issues.js +373 -0
  112. package/dist/master/db/links.js +221 -0
  113. package/dist/master/db/master-node.js +43 -0
  114. package/dist/master/db/memory.js +272 -0
  115. package/dist/master/db/messages.js +210 -0
  116. package/dist/master/db/notes.js +55 -0
  117. package/dist/master/db/schedule-patterns.js +56 -0
  118. package/dist/master/db/schedules.js +135 -0
  119. package/dist/master/db/skills.js +144 -0
  120. package/dist/master/db/team.js +88 -0
  121. package/dist/master/db/types.js +10 -0
  122. package/dist/master/db.js +12 -0
  123. package/dist/master/embedded.js +133 -0
  124. package/dist/master/federation/client.js +283 -0
  125. package/dist/master/federation/identity.js +133 -0
  126. package/dist/master/federation/manager.js +267 -0
  127. package/dist/master/federation/publisher.js +87 -0
  128. package/dist/master/federation/self-publisher.js +69 -0
  129. package/dist/master/federation/server.js +487 -0
  130. package/dist/master/group-paths.js +208 -0
  131. package/dist/master/offline-queue.js +38 -0
  132. package/dist/master/opc-bootstrap.js +245 -0
  133. package/dist/master/patrol-terminal.js +275 -0
  134. package/dist/master/repo-scan.js +188 -0
  135. package/dist/master/router.js +214 -0
  136. package/dist/master/scheduler-handlers.js +510 -0
  137. package/dist/master/scheduler.js +201 -0
  138. package/dist/master/server.js +203 -0
  139. package/dist/master/services/link-collector.js +82 -0
  140. package/dist/master/services/link-patrol-bootstrap.js +50 -0
  141. package/dist/master/services/memory-extract-prompt.js +34 -0
  142. package/dist/master/services/patrol-bootstrap.js +63 -0
  143. package/dist/master/share-tokens.js +56 -0
  144. package/dist/master/terminal-hub.js +300 -0
  145. package/dist/master/uploads.js +108 -0
  146. package/dist/master/util/fs.js +100 -0
  147. package/dist/master/util/paths.js +50 -0
  148. package/dist/master/util/persona.js +10 -0
  149. package/dist/master/ws-hub/connection.js +928 -0
  150. package/dist/master/ws-hub/conversation.js +290 -0
  151. package/dist/master/ws-hub/directory.js +70 -0
  152. package/dist/master/ws-hub/dispatch-enrich.js +34 -0
  153. package/dist/master/ws-hub/hub.js +136 -0
  154. package/dist/master/ws-hub/index.js +9 -0
  155. package/dist/master/ws-hub/internal.js +35 -0
  156. package/dist/master/ws-hub/routing.js +295 -0
  157. package/dist/master/ws-hub/sessions.js +130 -0
  158. package/dist/master/ws-hub.js +11 -0
  159. package/dist/shared/agent-profile.js +44 -0
  160. package/dist/shared/constants.js +55 -0
  161. package/dist/shared/dedup.js +33 -0
  162. package/dist/shared/group-context.js +62 -0
  163. package/dist/shared/json-codec.js +33 -0
  164. package/dist/shared/logger.js +136 -0
  165. package/dist/shared/mention.js +22 -0
  166. package/dist/shared/network.js +40 -0
  167. package/dist/shared/parse.js +18 -0
  168. package/dist/shared/prompt-composer.js +171 -0
  169. package/dist/shared/protocol/client-messages.js +8 -0
  170. package/dist/shared/protocol/enums.js +6 -0
  171. package/dist/shared/protocol/federation.js +62 -0
  172. package/dist/shared/protocol/guards.js +87 -0
  173. package/dist/shared/protocol/server-messages.js +8 -0
  174. package/dist/shared/protocol/types.js +8 -0
  175. package/dist/shared/protocol.js +19 -0
  176. package/dist/shared/readonly-allowlist.js +122 -0
  177. package/dist/shared/rotom-cli-prompt.js +23 -0
  178. package/dist/shared/skill-context.js +19 -0
  179. package/dist/shared/skill-md.js +43 -0
  180. package/dist/shared/slash-commands.js +50 -0
  181. package/dist/shared/time.js +80 -0
  182. package/dist/shared/title.js +46 -0
  183. package/dist/shared/url-extractor.js +99 -0
  184. package/migrations/001-schema.sql +942 -0
  185. package/package.json +68 -0
  186. package/scripts/fix-node-pty-perms.mjs +46 -0
  187. package/skill/rotom-a2a-communicate/SKILL.md +257 -0
  188. package/skill/rotom-bus-host/SKILL.md +78 -0
  189. package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * /api/guidance-templates — 群指导 prompt 模板库 CRUD
3
+ *
4
+ * - 列表: GET /api/guidance-templates
5
+ * - 新建: POST /api/guidance-templates
6
+ * - 改: PATCH /api/guidance-templates/:id
7
+ * - 删: DELETE /api/guidance-templates/:id (种子模板 is_default=1 返回 400)
8
+ *
9
+ * 身份验证沿用 api/index.ts 的 permissive middleware。
10
+ */
11
+ import { createLogger } from "../../shared/logger.js";
12
+ const log = createLogger("mesh-api-guidance-templates");
13
+ function asString(v) {
14
+ return typeof v === "string" && v.trim() ? v.trim() : undefined;
15
+ }
16
+ function asInt(v) {
17
+ if (typeof v === "number" && Number.isFinite(v))
18
+ return Math.trunc(v);
19
+ if (typeof v === "string" && /^-?\d+$/.test(v.trim()))
20
+ return parseInt(v.trim(), 10);
21
+ return undefined;
22
+ }
23
+ /**
24
+ * schedule_config 接受对象或 JSON 字符串;null/空串/缺省=不设置(返回 null)。
25
+ * 返回 undefined 表示「格式非法」,由调用方返回 400。
26
+ */
27
+ function normalizeScheduleConfig(v) {
28
+ if (v === null || v === undefined)
29
+ return null;
30
+ if (typeof v === "string") {
31
+ if (v.trim() === "")
32
+ return null;
33
+ try {
34
+ JSON.parse(v);
35
+ return v;
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
41
+ if (typeof v === "object") {
42
+ return JSON.stringify(v);
43
+ }
44
+ return undefined;
45
+ }
46
+ export function registerGuidanceTemplateRoutes(apiRouter, db) {
47
+ // ── 列表 ─────────────────────────────────────────────────────────────────
48
+ apiRouter.get("/guidance-templates", (_req, res) => {
49
+ res.json(db.listGuidanceTemplates());
50
+ });
51
+ // ── 新建 ─────────────────────────────────────────────────────────────────
52
+ apiRouter.post("/guidance-templates", (req, res) => {
53
+ const b = req.body;
54
+ const name = asString(b.name);
55
+ const promptText = asString(b.prompt_text);
56
+ if (!name) {
57
+ res.status(400).json({ error: "name is required" });
58
+ return;
59
+ }
60
+ if (!promptText) {
61
+ res.status(400).json({ error: "prompt_text is required" });
62
+ return;
63
+ }
64
+ const scheduleConfig = normalizeScheduleConfig(b.schedule_config);
65
+ if (scheduleConfig === undefined) {
66
+ res.status(400).json({ error: "schedule_config must be a JSON object or JSON string" });
67
+ return;
68
+ }
69
+ const tpl = db.createGuidanceTemplate({
70
+ name,
71
+ description: typeof b.description === "string" ? b.description : "",
72
+ prompt_text: promptText,
73
+ schedule_config: scheduleConfig,
74
+ sort_order: asInt(b.sort_order) ?? 0,
75
+ });
76
+ log.info(`Guidance template created: #${tpl.id} "${tpl.name}"`);
77
+ res.status(201).json(tpl);
78
+ });
79
+ // ── 改 ───────────────────────────────────────────────────────────────────
80
+ apiRouter.patch("/guidance-templates/:id", (req, res) => {
81
+ const id = asInt(req.params.id);
82
+ if (id === undefined) {
83
+ res.status(400).json({ error: "id must be an integer" });
84
+ return;
85
+ }
86
+ const existing = db.getGuidanceTemplate(id);
87
+ if (!existing) {
88
+ res.status(404).json({ error: "Template not found" });
89
+ return;
90
+ }
91
+ const b = req.body;
92
+ const patch = {};
93
+ if (b.name !== undefined) {
94
+ const s = asString(b.name);
95
+ if (!s) {
96
+ res.status(400).json({ error: "name cannot be empty" });
97
+ return;
98
+ }
99
+ patch.name = s;
100
+ }
101
+ if (b.description !== undefined) {
102
+ patch.description = typeof b.description === "string" ? b.description : "";
103
+ }
104
+ if (b.prompt_text !== undefined) {
105
+ const s = asString(b.prompt_text);
106
+ if (!s) {
107
+ res.status(400).json({ error: "prompt_text cannot be empty" });
108
+ return;
109
+ }
110
+ patch.prompt_text = s;
111
+ }
112
+ if (b.schedule_config !== undefined) {
113
+ const v = normalizeScheduleConfig(b.schedule_config);
114
+ if (v === undefined) {
115
+ res.status(400).json({ error: "schedule_config must be a JSON object or JSON string" });
116
+ return;
117
+ }
118
+ patch.schedule_config = v;
119
+ }
120
+ if (b.sort_order !== undefined) {
121
+ patch.sort_order = asInt(b.sort_order) ?? 0;
122
+ }
123
+ const tpl = db.updateGuidanceTemplate(id, patch);
124
+ log.info(`Guidance template updated: #${id}`);
125
+ res.json(tpl);
126
+ });
127
+ // ── 删 ───────────────────────────────────────────────────────────────────
128
+ apiRouter.delete("/guidance-templates/:id", (req, res) => {
129
+ const id = asInt(req.params.id);
130
+ if (id === undefined) {
131
+ res.status(400).json({ error: "id must be an integer" });
132
+ return;
133
+ }
134
+ const existing = db.getGuidanceTemplate(id);
135
+ if (!existing) {
136
+ res.status(404).json({ error: "Template not found" });
137
+ return;
138
+ }
139
+ if (existing.is_default === 1) {
140
+ res.status(400).json({ error: "Cannot delete default template" });
141
+ return;
142
+ }
143
+ db.deleteGuidanceTemplate(id);
144
+ log.info(`Guidance template deleted: #${id}`);
145
+ res.json({ ok: true });
146
+ });
147
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Digital Employee Mesh — REST API
3
+ *
4
+ * Dashboard endpoints are open (no login). Agent-token endpoints expect a
5
+ * `Bearer mesh_xxx` header and reject anonymous calls inline.
6
+ */
7
+ import { Router as ExpressRouter } from "express";
8
+ import { AuthService, hashToken } from "../auth.js";
9
+ import { ShareTokenStore } from "../share-tokens.js";
10
+ import { createLogger } from "../../shared/logger.js";
11
+ import { registerAgentRoutes } from "./agents.js";
12
+ import { registerDomainRoutes } from "./domains.js";
13
+ import { registerTeamRoutes } from "./teams.js";
14
+ import { registerMessageRoutes } from "./messages.js";
15
+ import { registerGroupRoutes } from "./groups.js";
16
+ import { registerIssueRoutes } from "./issues.js";
17
+ import { registerNoteRoutes } from "./notes.js";
18
+ import { registerMemoryRoutes } from "./memory.js";
19
+ import { registerSkillRoutes } from "./skills.js";
20
+ import { registerArtifactRoutes } from "./artifacts.js";
21
+ import { registerUploadRoutes } from "./uploads.js";
22
+ import { registerSessionRoutes } from "./sessions.js";
23
+ import { registerScheduleRoutes } from "./schedules.js";
24
+ import { registerShareRoutes } from "./share.js";
25
+ import { registerGuidanceTemplateRoutes } from "./guidance-templates.js";
26
+ import { registerSchedulePatternRoutes } from "./schedule-patterns.js";
27
+ import { registerIssuePatrolRoutes } from "./issues-patrol.js";
28
+ import { registerLinkPatrolRoutes } from "./links-patrol.js";
29
+ import { registerLinkRoutes } from "./links.js";
30
+ const log = createLogger("mesh-api");
31
+ // ---------------------------------------------------------------------------
32
+ // Create API router
33
+ // ---------------------------------------------------------------------------
34
+ export function createApi(db, sharedAuth, hub, router, serverPort, shareStore) {
35
+ const apiRouter = ExpressRouter();
36
+ const auth = sharedAuth ?? new AuthService(db);
37
+ const shareTokens = shareStore ?? new ShareTokenStore();
38
+ // ── Request logging middleware ──────────────────────────────────────────
39
+ apiRouter.use((req, res, next) => {
40
+ const start = Date.now();
41
+ res.on("finish", () => {
42
+ const ms = Date.now() - start;
43
+ log.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${ms}ms`);
44
+ });
45
+ next();
46
+ });
47
+ // ── Permissive auth middleware ──────────────────────────────────────────
48
+ apiRouter.use((req, res, next) => {
49
+ if (req.method === "OPTIONS") {
50
+ res.setHeader("Access-Control-Allow-Origin", "*");
51
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
52
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
53
+ res.status(200).end();
54
+ return;
55
+ }
56
+ const header = req.headers.authorization;
57
+ if (header && header.startsWith("Bearer ")) {
58
+ const token = header.slice(7);
59
+ if (token.startsWith("mesh_")) {
60
+ const agent = db.getAgentByTokenHash(hashToken(token));
61
+ if (agent) {
62
+ req.agentAuth = { name: agent.name, id: agent.id };
63
+ }
64
+ }
65
+ }
66
+ next();
67
+ });
68
+ // ── Register route modules ─────────────────────────────────────────────
69
+ registerAgentRoutes(apiRouter, db, auth, hub, serverPort);
70
+ registerDomainRoutes(apiRouter, db);
71
+ registerTeamRoutes(apiRouter, db);
72
+ registerMessageRoutes(apiRouter, db, auth, hub, router);
73
+ registerGroupRoutes(apiRouter, db, auth, hub);
74
+ registerIssueRoutes(apiRouter, db, auth, hub);
75
+ registerNoteRoutes(apiRouter, db);
76
+ registerMemoryRoutes(apiRouter, db);
77
+ registerSkillRoutes(apiRouter, db);
78
+ registerArtifactRoutes(apiRouter, db);
79
+ registerUploadRoutes(apiRouter, db);
80
+ registerSessionRoutes(apiRouter, db, auth, hub);
81
+ registerScheduleRoutes(apiRouter, db);
82
+ registerShareRoutes(apiRouter, db, shareTokens);
83
+ registerGuidanceTemplateRoutes(apiRouter, db);
84
+ registerSchedulePatternRoutes(apiRouter, db);
85
+ registerIssuePatrolRoutes(apiRouter, db);
86
+ registerLinkPatrolRoutes(apiRouter, db);
87
+ registerLinkRoutes(apiRouter, db);
88
+ return apiRouter;
89
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Issue 巡检 REST —— 工具箱 Issue 巡检 tab 用。
3
+ *
4
+ * - GET /api/issues-patrol/state — 当前 patrol 群 + 其 scheduled_task 配置
5
+ * - PATCH /api/issues-patrol/config — 改 enabled / intervalSec / 节流参数
6
+ * - GET /api/issues-patrol/runs — 最近 runs
7
+ * - GET /api/issues-patrol/runs/:runId/logs — 单轮日志
8
+ * - GET /api/issues-patrol/logs — 全局日志(可按 verdict / candidateGroupId 过滤)
9
+ */
10
+ import { createLogger } from "../../shared/logger.js";
11
+ const log = createLogger("mesh-api-issues-patrol");
12
+ function parsePayload(raw) {
13
+ if (!raw)
14
+ return {};
15
+ try {
16
+ return JSON.parse(raw);
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ }
22
+ function findPatrolGroup(db) {
23
+ const groups = db.listGroupsByType("patrol").filter((g) => g.archived_at == null);
24
+ if (groups.length === 0)
25
+ return null;
26
+ const group = groups[0];
27
+ const members = db.getGroupMembers(group.id);
28
+ // patrol 群成员 = creator(人) + 巡检员(1 个 AI agent)。优先取 scheduled_task.agent_name,
29
+ // 否则取第一个非空成员。
30
+ const task = db.listScheduledTasks({ groupId: group.id }).find((t) => t.handler_key === "issue-patrol");
31
+ const agentName = task?.agent_name ?? members[0]?.agent_name ?? "";
32
+ return { groupId: group.id, groupName: group.name, agentName };
33
+ }
34
+ export function registerIssuePatrolRoutes(apiRouter, db) {
35
+ // ── state ────────────────────────────────────────────────────────────────
36
+ apiRouter.get("/issues-patrol/state", (_req, res) => {
37
+ const patrol = findPatrolGroup(db);
38
+ if (!patrol) {
39
+ res.json({ enabled: false, hasPatrolGroup: false });
40
+ return;
41
+ }
42
+ const task = db.listScheduledTasks({ groupId: patrol.groupId }).find((t) => t.handler_key === "issue-patrol");
43
+ if (!task) {
44
+ res.json({
45
+ hasPatrolGroup: true,
46
+ patrolGroupId: patrol.groupId,
47
+ patrolGroupName: patrol.groupName,
48
+ patrolAgentName: patrol.agentName,
49
+ enabled: false,
50
+ });
51
+ return;
52
+ }
53
+ const payload = parsePayload(task.handler_payload);
54
+ res.json({
55
+ hasPatrolGroup: true,
56
+ patrolGroupId: patrol.groupId,
57
+ patrolGroupName: patrol.groupName,
58
+ patrolAgentName: patrol.agentName,
59
+ taskId: task.id,
60
+ enabled: task.enabled === 1,
61
+ intervalSec: task.interval_sec,
62
+ nextRunAt: task.next_run_at,
63
+ lastRunAt: task.last_run_at,
64
+ lastStatus: task.last_status,
65
+ lastError: task.last_error,
66
+ throughputCap: payload.throughputCap ?? 3,
67
+ candidateCap: payload.candidateCap ?? 3,
68
+ scanBatch: payload.scanBatch ?? 10,
69
+ });
70
+ });
71
+ // ── config ────────────────────────────────────────────────────────────────
72
+ apiRouter.patch("/issues-patrol/config", (req, res) => {
73
+ const patrol = findPatrolGroup(db);
74
+ if (!patrol) {
75
+ res.status(400).json({ error: "未创建巡检群,请先建一个 type=patrol 的群" });
76
+ return;
77
+ }
78
+ const task = db.listScheduledTasks({ groupId: patrol.groupId }).find((t) => t.handler_key === "issue-patrol");
79
+ if (!task) {
80
+ res.status(404).json({ error: "巡检定时任务不存在" });
81
+ return;
82
+ }
83
+ const body = req.body ?? {};
84
+ const patch = {};
85
+ const payload = parsePayload(task.handler_payload);
86
+ if (typeof body.enabled === "boolean") {
87
+ patch.enabled = body.enabled;
88
+ }
89
+ if (typeof body.intervalSec === "number") {
90
+ if (body.intervalSec < 60) {
91
+ res.status(400).json({ error: "intervalSec 必须 >= 60" });
92
+ return;
93
+ }
94
+ patch.intervalSec = Math.floor(body.intervalSec);
95
+ patch.scheduleKind = "interval";
96
+ }
97
+ if (typeof body.throughputCap === "number") {
98
+ if (body.throughputCap < 1 || body.throughputCap > 20) {
99
+ res.status(400).json({ error: "throughputCap 取值 1-20" });
100
+ return;
101
+ }
102
+ payload.throughputCap = Math.floor(body.throughputCap);
103
+ }
104
+ if (typeof body.candidateCap === "number") {
105
+ if (body.candidateCap < 1 || body.candidateCap > 20) {
106
+ res.status(400).json({ error: "candidateCap 取值 1-20" });
107
+ return;
108
+ }
109
+ payload.candidateCap = Math.floor(body.candidateCap);
110
+ }
111
+ if (typeof body.scanBatch === "number") {
112
+ if (body.scanBatch < 1 || body.scanBatch > 50) {
113
+ res.status(400).json({ error: "scanBatch 取值 1-50" });
114
+ return;
115
+ }
116
+ payload.scanBatch = Math.floor(body.scanBatch);
117
+ }
118
+ // patrolGroupId / patrolAgentName 不可改,从原 payload 继承
119
+ payload.patrolGroupId = payload.patrolGroupId ?? patrol.groupId;
120
+ payload.patrolAgentName = payload.patrolAgentName ?? patrol.agentName;
121
+ patch.handlerPayload = JSON.stringify(payload);
122
+ const updated = db.updateScheduledTask(task.id, patch);
123
+ if (!updated) {
124
+ res.status(500).json({ error: "更新失败" });
125
+ return;
126
+ }
127
+ log.info(`Patrol config updated (task #${task.id}): ${JSON.stringify(patch)}`);
128
+ res.json({
129
+ ok: true,
130
+ enabled: updated.enabled === 1,
131
+ intervalSec: updated.interval_sec,
132
+ throughputCap: payload.throughputCap,
133
+ candidateCap: payload.candidateCap,
134
+ scanBatch: payload.scanBatch,
135
+ nextRunAt: updated.next_run_at,
136
+ });
137
+ });
138
+ // ── runs ──────────────────────────────────────────────────────────────────
139
+ apiRouter.get("/issues-patrol/runs", (req, res) => {
140
+ const limit = parseLimit(req.query.limit, 50);
141
+ const patrol = findPatrolGroup(db);
142
+ const runs = db.listPatrolRuns({
143
+ patrolGroupId: patrol?.groupId,
144
+ limit,
145
+ });
146
+ res.json({ runs });
147
+ });
148
+ apiRouter.get("/issues-patrol/runs/:runId/logs", (req, res) => {
149
+ const logs = db.listPatrolLogsForRun(req.params.runId);
150
+ res.json({ logs });
151
+ });
152
+ // ── logs ──────────────────────────────────────────────────────────────────
153
+ apiRouter.get("/issues-patrol/logs", (req, res) => {
154
+ const limit = parseLimit(req.query.limit, 200);
155
+ const verdict = typeof req.query.verdict === "string" ? req.query.verdict : undefined;
156
+ const candidateGroupId = typeof req.query.candidateGroupId === "string" ? req.query.candidateGroupId : undefined;
157
+ const patrol = findPatrolGroup(db);
158
+ const logs = db.listPatrolLogs({
159
+ patrolGroupId: patrol?.groupId,
160
+ verdict: verdict,
161
+ candidateGroupId,
162
+ limit,
163
+ });
164
+ res.json({ logs });
165
+ });
166
+ }
167
+ function parseLimit(v, fallback) {
168
+ const n = typeof v === "string" ? parseInt(v, 10) : Number(v);
169
+ if (!Number.isFinite(n) || n <= 0)
170
+ return fallback;
171
+ return Math.min(n, 1000);
172
+ }