@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.
- package/README.md +417 -0
- package/bin/mesh-master.sh +439 -0
- package/bin/rotom +29 -0
- package/bin/rotom-link.sh +136 -0
- package/bin/rotom-send-with-status +57 -0
- package/bin/rotom-up.sh +428 -0
- package/dist/cli/ask.js +62 -0
- package/dist/cli/common.js +321 -0
- package/dist/cli/config.js +65 -0
- package/dist/cli/directory.js +17 -0
- package/dist/cli/executor.js +58 -0
- package/dist/cli/fed.js +91 -0
- package/dist/cli/group.js +273 -0
- package/dist/cli/identity.js +62 -0
- package/dist/cli/init.js +268 -0
- package/dist/cli/issue.js +202 -0
- package/dist/cli/join.js +170 -0
- package/dist/cli/link.js +47 -0
- package/dist/cli/master.js +51 -0
- package/dist/cli/memory.js +307 -0
- package/dist/cli/note.js +68 -0
- package/dist/cli/repo.js +77 -0
- package/dist/cli/rotom.js +277 -0
- package/dist/cli/routes.js +118 -0
- package/dist/cli/run.js +45 -0
- package/dist/cli/schedule.js +237 -0
- package/dist/cli/skill.js +173 -0
- package/dist/cli/team.js +106 -0
- package/dist/executor/claude-code-hook.cjs +80 -0
- package/dist/executor/cli-executor.js +8 -0
- package/dist/executor/executors/claude-code.js +780 -0
- package/dist/executor/executors/codex.js +719 -0
- package/dist/executor/executors/hermes-cli.js +855 -0
- package/dist/executor/executors/openclaw.js +467 -0
- package/dist/executor/executors/pi.js +514 -0
- package/dist/executor/index.js +269 -0
- package/dist/executor/jsonrpc-transport.js +125 -0
- package/dist/executor/process-runner.js +101 -0
- package/dist/executor/reasoning-status.js +83 -0
- package/dist/executor/repo-cache.js +502 -0
- package/dist/executor/session-store.js +188 -0
- package/dist/executor/worker-chat.js +257 -0
- package/dist/executor/worker-connection.js +89 -0
- package/dist/executor/worker-issue.js +264 -0
- package/dist/executor/worker.js +877 -0
- package/dist/link/pending-requests.js +72 -0
- package/dist/link/server.js +233 -0
- package/dist/link/visibility-store.js +58 -0
- package/dist/master/api/agents.js +333 -0
- package/dist/master/api/artifacts.js +271 -0
- package/dist/master/api/domains.js +64 -0
- package/dist/master/api/groups.js +635 -0
- package/dist/master/api/guidance-templates.js +147 -0
- package/dist/master/api/index.js +89 -0
- package/dist/master/api/issues-patrol.js +172 -0
- package/dist/master/api/issues.js +663 -0
- package/dist/master/api/links-patrol.js +168 -0
- package/dist/master/api/links.js +114 -0
- package/dist/master/api/memory.js +259 -0
- package/dist/master/api/messages.js +157 -0
- package/dist/master/api/notes.js +77 -0
- package/dist/master/api/schedule-patterns.js +133 -0
- package/dist/master/api/schedules.js +272 -0
- package/dist/master/api/sessions.js +158 -0
- package/dist/master/api/share.js +269 -0
- package/dist/master/api/skills.js +190 -0
- package/dist/master/api/teams.js +122 -0
- package/dist/master/api/uploads.js +245 -0
- package/dist/master/auth.js +134 -0
- package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
- package/dist/master/dashboard/animations/calico-error.apng +0 -0
- package/dist/master/dashboard/animations/calico-happy.apng +0 -0
- package/dist/master/dashboard/animations/calico-notification.apng +0 -0
- package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
- package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
- package/dist/master/dashboard/animations/calico-waking.apng +0 -0
- package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
- package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
- package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
- package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
- package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
- package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
- package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
- package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
- package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
- package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
- package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
- package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
- package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
- package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
- package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
- package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
- package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
- package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
- package/dist/master/dashboard/debug-auth.html +197 -0
- package/dist/master/dashboard/favicon.ico +0 -0
- package/dist/master/dashboard/index.html +20 -0
- package/dist/master/dashboard/rotom-avatar.png +0 -0
- package/dist/master/db/agent-sessions.js +60 -0
- package/dist/master/db/agent-visibility.js +64 -0
- package/dist/master/db/agents.js +119 -0
- package/dist/master/db/ask-bridges.js +157 -0
- package/dist/master/db/build-update.js +59 -0
- package/dist/master/db/core.js +82 -0
- package/dist/master/db/domains.js +80 -0
- package/dist/master/db/groups.js +316 -0
- package/dist/master/db/guidance-templates.js +58 -0
- package/dist/master/db/index.js +12 -0
- package/dist/master/db/internal.js +45 -0
- package/dist/master/db/issues-patrol.js +81 -0
- package/dist/master/db/issues.js +373 -0
- package/dist/master/db/links.js +221 -0
- package/dist/master/db/master-node.js +43 -0
- package/dist/master/db/memory.js +272 -0
- package/dist/master/db/messages.js +210 -0
- package/dist/master/db/notes.js +55 -0
- package/dist/master/db/schedule-patterns.js +56 -0
- package/dist/master/db/schedules.js +135 -0
- package/dist/master/db/skills.js +144 -0
- package/dist/master/db/team.js +88 -0
- package/dist/master/db/types.js +10 -0
- package/dist/master/db.js +12 -0
- package/dist/master/embedded.js +133 -0
- package/dist/master/federation/client.js +283 -0
- package/dist/master/federation/identity.js +133 -0
- package/dist/master/federation/manager.js +267 -0
- package/dist/master/federation/publisher.js +87 -0
- package/dist/master/federation/self-publisher.js +69 -0
- package/dist/master/federation/server.js +487 -0
- package/dist/master/group-paths.js +208 -0
- package/dist/master/offline-queue.js +38 -0
- package/dist/master/opc-bootstrap.js +245 -0
- package/dist/master/patrol-terminal.js +275 -0
- package/dist/master/repo-scan.js +188 -0
- package/dist/master/router.js +214 -0
- package/dist/master/scheduler-handlers.js +510 -0
- package/dist/master/scheduler.js +201 -0
- package/dist/master/server.js +203 -0
- package/dist/master/services/link-collector.js +82 -0
- package/dist/master/services/link-patrol-bootstrap.js +50 -0
- package/dist/master/services/memory-extract-prompt.js +34 -0
- package/dist/master/services/patrol-bootstrap.js +63 -0
- package/dist/master/share-tokens.js +56 -0
- package/dist/master/terminal-hub.js +300 -0
- package/dist/master/uploads.js +108 -0
- package/dist/master/util/fs.js +100 -0
- package/dist/master/util/paths.js +50 -0
- package/dist/master/util/persona.js +10 -0
- package/dist/master/ws-hub/connection.js +928 -0
- package/dist/master/ws-hub/conversation.js +290 -0
- package/dist/master/ws-hub/directory.js +70 -0
- package/dist/master/ws-hub/dispatch-enrich.js +34 -0
- package/dist/master/ws-hub/hub.js +136 -0
- package/dist/master/ws-hub/index.js +9 -0
- package/dist/master/ws-hub/internal.js +35 -0
- package/dist/master/ws-hub/routing.js +295 -0
- package/dist/master/ws-hub/sessions.js +130 -0
- package/dist/master/ws-hub.js +11 -0
- package/dist/shared/agent-profile.js +44 -0
- package/dist/shared/constants.js +55 -0
- package/dist/shared/dedup.js +33 -0
- package/dist/shared/group-context.js +62 -0
- package/dist/shared/json-codec.js +33 -0
- package/dist/shared/logger.js +136 -0
- package/dist/shared/mention.js +22 -0
- package/dist/shared/network.js +40 -0
- package/dist/shared/parse.js +18 -0
- package/dist/shared/prompt-composer.js +171 -0
- package/dist/shared/protocol/client-messages.js +8 -0
- package/dist/shared/protocol/enums.js +6 -0
- package/dist/shared/protocol/federation.js +62 -0
- package/dist/shared/protocol/guards.js +87 -0
- package/dist/shared/protocol/server-messages.js +8 -0
- package/dist/shared/protocol/types.js +8 -0
- package/dist/shared/protocol.js +19 -0
- package/dist/shared/readonly-allowlist.js +122 -0
- package/dist/shared/rotom-cli-prompt.js +23 -0
- package/dist/shared/skill-context.js +19 -0
- package/dist/shared/skill-md.js +43 -0
- package/dist/shared/slash-commands.js +50 -0
- package/dist/shared/time.js +80 -0
- package/dist/shared/title.js +46 -0
- package/dist/shared/url-extractor.js +99 -0
- package/migrations/001-schema.sql +942 -0
- package/package.json +68 -0
- package/scripts/fix-node-pty-perms.mjs +46 -0
- package/skill/rotom-a2a-communicate/SKILL.md +257 -0
- package/skill/rotom-bus-host/SKILL.md +78 -0
- 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
|
+
}
|