@xdevops/issue-auto-finish 1.0.1 → 1.0.3
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/dist/KnowledgeAnalyzer-EZSJT2MJ.js +13 -0
- package/dist/KnowledgeAnalyzer-EZSJT2MJ.js.map +1 -0
- package/dist/KnowledgeStore-4ROC6F56.js +10 -0
- package/dist/KnowledgeStore-4ROC6F56.js.map +1 -0
- package/dist/ai-runner/AIRunner.d.ts +2 -0
- package/dist/ai-runner/AIRunner.d.ts.map +1 -1
- package/dist/ai-runner/BaseAIRunner.d.ts +9 -0
- package/dist/ai-runner/BaseAIRunner.d.ts.map +1 -1
- package/dist/ai-runner-RGAJPOOW.js +16 -0
- package/dist/ai-runner-RGAJPOOW.js.map +1 -0
- package/dist/analyze-I7UOJB4F.js +72 -0
- package/dist/analyze-I7UOJB4F.js.map +1 -0
- package/dist/chunk-3JUHZGX5.js +171 -0
- package/dist/chunk-3JUHZGX5.js.map +1 -0
- package/dist/chunk-5JYCGAU3.js +318 -0
- package/dist/chunk-5JYCGAU3.js.map +1 -0
- package/dist/chunk-5VUB3UUK.js +643 -0
- package/dist/chunk-5VUB3UUK.js.map +1 -0
- package/dist/{chunk-IDUKWCC2.js → chunk-C6ZJVIPZ.js} +1151 -80
- package/dist/chunk-C6ZJVIPZ.js.map +1 -0
- package/dist/{chunk-OWVT3Z34.js → chunk-JFYAXNNS.js} +121 -31
- package/dist/chunk-JFYAXNNS.js.map +1 -0
- package/dist/chunk-KISVPNSV.js +188 -0
- package/dist/chunk-KISVPNSV.js.map +1 -0
- package/dist/{chunk-I3T573SU.js → chunk-LEQYGOMJ.js} +65 -2
- package/dist/chunk-LEQYGOMJ.js.map +1 -0
- package/dist/{chunk-TBIEB3JY.js → chunk-N5YK6YVI.js} +592 -767
- package/dist/chunk-N5YK6YVI.js.map +1 -0
- package/dist/{chunk-RIUI4ROA.js → chunk-PECYMYAK.js} +2 -2
- package/dist/chunk-SWG2Y7YX.js +410 -0
- package/dist/chunk-SWG2Y7YX.js.map +1 -0
- package/dist/chunk-TZ6C7HL5.js +59 -0
- package/dist/chunk-TZ6C7HL5.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +8 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli.js +67 -3
- package/dist/cli.js.map +1 -1
- package/dist/clients/GongfengClient.d.ts +5 -0
- package/dist/clients/GongfengClient.d.ts.map +1 -1
- package/dist/config-RI7NLDXI.js +7 -0
- package/dist/config-RI7NLDXI.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/{doctor-B26Q6JWI.js → doctor-ZPGIBA5N.js} +3 -3
- package/dist/events/EventBus.d.ts +1 -1
- package/dist/events/EventBus.d.ts.map +1 -1
- package/dist/git/GitOperations.d.ts +12 -0
- package/dist/git/GitOperations.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/{init-L3VIWCOV.js → init-LZGCIHE7.js} +8 -4
- package/dist/{init-L3VIWCOV.js.map → init-LZGCIHE7.js.map} +1 -1
- package/dist/knowledge/KnowledgeAnalyzer.d.ts +31 -0
- package/dist/knowledge/KnowledgeAnalyzer.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeDefaults.d.ts +7 -0
- package/dist/knowledge/KnowledgeDefaults.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeEntry.d.ts +30 -0
- package/dist/knowledge/KnowledgeEntry.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeLoader.d.ts +18 -0
- package/dist/knowledge/KnowledgeLoader.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeStore.d.ts +35 -0
- package/dist/knowledge/KnowledgeStore.d.ts.map +1 -0
- package/dist/knowledge/ProjectKnowledge.d.ts +79 -0
- package/dist/knowledge/ProjectKnowledge.d.ts.map +1 -0
- package/dist/knowledge/analyze-prompt.d.ts +2 -0
- package/dist/knowledge/analyze-prompt.d.ts.map +1 -0
- package/dist/knowledge/importers/GongfengExtractor.d.ts +27 -0
- package/dist/knowledge/importers/GongfengExtractor.d.ts.map +1 -0
- package/dist/knowledge/importers/IwikiImporter.d.ts +21 -0
- package/dist/knowledge/importers/IwikiImporter.d.ts.map +1 -0
- package/dist/knowledge/index.d.ts +12 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/lib.js +19 -10
- package/dist/orchestrator/PipelineOrchestrator.d.ts +5 -1
- package/dist/orchestrator/PipelineOrchestrator.d.ts.map +1 -1
- package/dist/phases/BasePhase.d.ts.map +1 -1
- package/dist/poller/IssuePoller.d.ts +5 -0
- package/dist/poller/IssuePoller.d.ts.map +1 -1
- package/dist/prompts/chat-templates.d.ts +4 -0
- package/dist/prompts/chat-templates.d.ts.map +1 -0
- package/dist/prompts/templates.d.ts +11 -0
- package/dist/prompts/templates.d.ts.map +1 -1
- package/dist/rules/RuleResolver.d.ts +4 -0
- package/dist/rules/RuleResolver.d.ts.map +1 -1
- package/dist/run.js +11 -5
- package/dist/run.js.map +1 -1
- package/dist/services/ChatService.d.ts +39 -0
- package/dist/services/ChatService.d.ts.map +1 -0
- package/dist/shutdown/ShutdownSignal.d.ts +3 -0
- package/dist/shutdown/ShutdownSignal.d.ts.map +1 -0
- package/dist/{start-TVN4SS6E.js → start-NMQHUKGF.js} +1 -1
- package/dist/tracker/IssueState.d.ts +1 -0
- package/dist/tracker/IssueState.d.ts.map +1 -1
- package/dist/tracker/IssueTracker.d.ts +2 -0
- package/dist/tracker/IssueTracker.d.ts.map +1 -1
- package/dist/updater/AutoUpdater.d.ts +33 -0
- package/dist/updater/AutoUpdater.d.ts.map +1 -0
- package/dist/updater/UpdateExecutor.d.ts +7 -0
- package/dist/updater/UpdateExecutor.d.ts.map +1 -0
- package/dist/updater/VersionChecker.d.ts +22 -0
- package/dist/updater/VersionChecker.d.ts.map +1 -0
- package/dist/web/WebServer.d.ts +4 -0
- package/dist/web/WebServer.d.ts.map +1 -1
- package/dist/web/routes/api.d.ts +4 -0
- package/dist/web/routes/api.d.ts.map +1 -1
- package/dist/web/routes/chat.d.ts +7 -0
- package/dist/web/routes/chat.d.ts.map +1 -0
- package/dist/web/routes/knowledge.d.ts +13 -0
- package/dist/web/routes/knowledge.d.ts.map +1 -0
- package/dist/web/routes/setup.d.ts.map +1 -1
- package/dist/webhook/CommandExecutor.d.ts +4 -0
- package/dist/webhook/CommandExecutor.d.ts.map +1 -1
- package/dist/webhook/CommandParser.d.ts +2 -2
- package/dist/webhook/CommandParser.d.ts.map +1 -1
- package/dist/webhook/WebhookHandler.d.ts +8 -0
- package/dist/webhook/WebhookHandler.d.ts.map +1 -1
- package/dist/webhook/WebhookServer.d.ts +2 -0
- package/dist/webhook/WebhookServer.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/web/frontend/dist/assets/index-AcJ0lPIv.js +67 -0
- package/src/web/frontend/dist/assets/index-BbRt5BAr.css +1 -0
- package/src/web/frontend/dist/index.html +2 -2
- package/dist/chunk-I3T573SU.js.map +0 -1
- package/dist/chunk-IDUKWCC2.js.map +0 -1
- package/dist/chunk-OWVT3Z34.js.map +0 -1
- package/dist/chunk-TBIEB3JY.js.map +0 -1
- package/src/web/frontend/dist/assets/index-CQdlU9PE.js +0 -65
- package/src/web/frontend/dist/assets/index-CgMEkyZJ.css +0 -1
- /package/dist/{chunk-RIUI4ROA.js.map → chunk-PECYMYAK.js.map} +0 -0
- /package/dist/{doctor-B26Q6JWI.js.map → doctor-ZPGIBA5N.js.map} +0 -0
- /package/dist/{start-TVN4SS6E.js.map → start-NMQHUKGF.js.map} +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSetupRouter
|
|
3
|
+
} from "./chunk-LEQYGOMJ.js";
|
|
1
4
|
import {
|
|
2
5
|
BrainstormService,
|
|
3
6
|
CLASSIC_PIPELINE,
|
|
@@ -8,25 +11,38 @@ import {
|
|
|
8
11
|
PipelineOrchestrator,
|
|
9
12
|
PlanPersistence,
|
|
10
13
|
collectStateLabels,
|
|
11
|
-
createAIRunner,
|
|
12
14
|
eventBus,
|
|
13
15
|
getE2eEnabled,
|
|
14
16
|
getNoteSyncEnabled,
|
|
15
17
|
getPipelineDef,
|
|
16
18
|
isNoteSyncEnabledForIssue,
|
|
17
|
-
loadConfig,
|
|
18
|
-
logger,
|
|
19
19
|
setE2eOverride,
|
|
20
20
|
setNoteSyncOverride,
|
|
21
21
|
validatePhaseRegistry
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-N5YK6YVI.js";
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
IwikiImporter,
|
|
25
|
+
getProjectKnowledge,
|
|
26
|
+
loadKnowledge
|
|
27
|
+
} from "./chunk-3JUHZGX5.js";
|
|
26
28
|
import {
|
|
27
29
|
setLocale,
|
|
28
30
|
t
|
|
29
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JFYAXNNS.js";
|
|
32
|
+
import {
|
|
33
|
+
KnowledgeStore
|
|
34
|
+
} from "./chunk-5JYCGAU3.js";
|
|
35
|
+
import {
|
|
36
|
+
loadConfig
|
|
37
|
+
} from "./chunk-KISVPNSV.js";
|
|
38
|
+
import {
|
|
39
|
+
createAIRunner,
|
|
40
|
+
isShuttingDown,
|
|
41
|
+
setShuttingDown
|
|
42
|
+
} from "./chunk-SWG2Y7YX.js";
|
|
43
|
+
import {
|
|
44
|
+
logger
|
|
45
|
+
} from "./chunk-TZ6C7HL5.js";
|
|
30
46
|
|
|
31
47
|
// src/supplement/SupplementStore.ts
|
|
32
48
|
import fs from "fs";
|
|
@@ -125,6 +141,7 @@ var IssuePoller = class {
|
|
|
125
141
|
driveTimer = null;
|
|
126
142
|
activeIssues = /* @__PURE__ */ new Set();
|
|
127
143
|
lastAutoApproveCheckMs = 0;
|
|
144
|
+
discoveryPaused = false;
|
|
128
145
|
constructor(config, gongfeng, tracker, orchestrator) {
|
|
129
146
|
this.config = config;
|
|
130
147
|
this.gongfeng = gongfeng;
|
|
@@ -153,7 +170,28 @@ var IssuePoller = class {
|
|
|
153
170
|
getActiveIssueIids() {
|
|
154
171
|
return [...this.activeIssues];
|
|
155
172
|
}
|
|
173
|
+
getActiveCount() {
|
|
174
|
+
return this.activeIssues.size;
|
|
175
|
+
}
|
|
176
|
+
forceReleaseIssue(issueIid) {
|
|
177
|
+
const had = this.activeIssues.has(issueIid);
|
|
178
|
+
if (had) {
|
|
179
|
+
this.activeIssues.delete(issueIid);
|
|
180
|
+
logger3.info("Force-released issue from activeIssues", { issueIid });
|
|
181
|
+
}
|
|
182
|
+
return had;
|
|
183
|
+
}
|
|
184
|
+
pauseDiscovery() {
|
|
185
|
+
this.discoveryPaused = true;
|
|
186
|
+
logger3.info("Discovery paused (auto-update drain)");
|
|
187
|
+
}
|
|
188
|
+
resumeDiscovery() {
|
|
189
|
+
this.discoveryPaused = false;
|
|
190
|
+
logger3.info("Discovery resumed");
|
|
191
|
+
}
|
|
156
192
|
async discover() {
|
|
193
|
+
if (isShuttingDown()) return;
|
|
194
|
+
if (this.discoveryPaused) return;
|
|
157
195
|
try {
|
|
158
196
|
logger3.debug("Discovering new issues...");
|
|
159
197
|
const issues = await this.gongfeng.listIssues("opened", AUTO_FINISH_LABEL);
|
|
@@ -177,6 +215,7 @@ var IssuePoller = class {
|
|
|
177
215
|
}
|
|
178
216
|
}
|
|
179
217
|
drive() {
|
|
218
|
+
if (isShuttingDown()) return;
|
|
180
219
|
this.maybeAutoApproveWaiting();
|
|
181
220
|
const maxConcurrent = this.config.poll.maxConcurrent;
|
|
182
221
|
const available = maxConcurrent - this.activeIssues.size;
|
|
@@ -308,6 +347,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
|
|
|
308
347
|
let git;
|
|
309
348
|
let gongfeng;
|
|
310
349
|
let supplementStore;
|
|
350
|
+
let autoUpdater;
|
|
351
|
+
let poller;
|
|
311
352
|
if (config !== void 0) {
|
|
312
353
|
tracker = trackerOrDeps;
|
|
313
354
|
cfg = config;
|
|
@@ -323,6 +364,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
|
|
|
323
364
|
git = deps.mainGit;
|
|
324
365
|
gongfeng = deps.gongfeng;
|
|
325
366
|
supplementStore = deps.supplementStore;
|
|
367
|
+
autoUpdater = deps.autoUpdater;
|
|
368
|
+
poller = deps.poller;
|
|
326
369
|
}
|
|
327
370
|
const router = Router();
|
|
328
371
|
router.get("/api/issues", (_req, res) => {
|
|
@@ -395,6 +438,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
|
|
|
395
438
|
router.post("/api/issues/:iid/restart", async (req, res) => {
|
|
396
439
|
const iid = parseInt(req.params.iid, 10);
|
|
397
440
|
try {
|
|
441
|
+
poller?.forceReleaseIssue(iid);
|
|
398
442
|
await orch.restartIssue(iid);
|
|
399
443
|
res.json({ success: true, message: `Issue #${iid} restarted` });
|
|
400
444
|
} catch (err) {
|
|
@@ -802,6 +846,43 @@ data: ${JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
|
802
846
|
const htmlBody = await marked(content);
|
|
803
847
|
res.type("html").send(renderDocPage(iid, title, htmlBody, filename));
|
|
804
848
|
});
|
|
849
|
+
router.get("/api/system/update-status", (_req, res) => {
|
|
850
|
+
if (!autoUpdater) {
|
|
851
|
+
res.status(501).json({ error: "Auto-updater not available" });
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
res.json(autoUpdater.getStatus());
|
|
855
|
+
});
|
|
856
|
+
router.post("/api/system/update-check", async (_req, res) => {
|
|
857
|
+
if (!autoUpdater) {
|
|
858
|
+
res.status(501).json({ error: "Auto-updater not available" });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const result = await autoUpdater.checkForUpdate();
|
|
863
|
+
res.json(result);
|
|
864
|
+
} catch (err) {
|
|
865
|
+
const msg = err.message;
|
|
866
|
+
logger4.error("Manual update check failed", { error: msg });
|
|
867
|
+
res.status(500).json({ error: msg });
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
router.post("/api/system/update", async (_req, res) => {
|
|
871
|
+
if (!autoUpdater) {
|
|
872
|
+
res.status(501).json({ error: "Auto-updater not available" });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
res.json({ success: true, message: "Update triggered" });
|
|
877
|
+
autoUpdater.triggerUpdate().catch((err) => {
|
|
878
|
+
logger4.error("Manual update failed", { error: err.message });
|
|
879
|
+
});
|
|
880
|
+
} catch (err) {
|
|
881
|
+
const msg = err.message;
|
|
882
|
+
logger4.error("Failed to trigger update", { error: msg });
|
|
883
|
+
res.status(500).json({ error: msg });
|
|
884
|
+
}
|
|
885
|
+
});
|
|
805
886
|
return router;
|
|
806
887
|
}
|
|
807
888
|
var DOC_LABELS_FUNC = (filename) => t(`docLabel.${filename}`) || filename;
|
|
@@ -1063,9 +1144,553 @@ function extractTitle(sdd) {
|
|
|
1063
1144
|
return "\u8111\u66B4\u751F\u6210\u7684\u9700\u6C42";
|
|
1064
1145
|
}
|
|
1065
1146
|
|
|
1147
|
+
// src/web/routes/chat.ts
|
|
1148
|
+
import { Router as Router3 } from "express";
|
|
1149
|
+
var logger6 = logger.child("ChatRoutes");
|
|
1150
|
+
function sseWriter2(res) {
|
|
1151
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1152
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1153
|
+
res.setHeader("Connection", "keep-alive");
|
|
1154
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
1155
|
+
res.flushHeaders();
|
|
1156
|
+
return (event) => {
|
|
1157
|
+
try {
|
|
1158
|
+
res.write(`event: ${event.type}
|
|
1159
|
+
data: ${JSON.stringify(event)}
|
|
1160
|
+
|
|
1161
|
+
`);
|
|
1162
|
+
} catch {
|
|
1163
|
+
logger6.warn("Failed to write SSE chat event");
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function createChatRouter(deps) {
|
|
1168
|
+
const router = Router3();
|
|
1169
|
+
const { chatService } = deps;
|
|
1170
|
+
router.post("/api/chat/sessions", (req, res) => {
|
|
1171
|
+
try {
|
|
1172
|
+
const { title } = req.body;
|
|
1173
|
+
const session = chatService.createSession(title?.trim());
|
|
1174
|
+
res.json({ success: true, session });
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
logger6.error("Failed to create chat session", { error: err.message });
|
|
1177
|
+
res.status(500).json({ error: err.message });
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
router.get("/api/chat/sessions", (_req, res) => {
|
|
1181
|
+
try {
|
|
1182
|
+
const sessions = chatService.listSessions();
|
|
1183
|
+
res.json({ success: true, sessions });
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
logger6.error("Failed to list chat sessions", { error: err.message });
|
|
1186
|
+
res.status(500).json({ error: err.message });
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
router.get("/api/chat/sessions/:id", (req, res) => {
|
|
1190
|
+
const session = chatService.getSession(req.params.id);
|
|
1191
|
+
if (!session) {
|
|
1192
|
+
res.status(404).json({ error: "Session not found" });
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
res.json({ success: true, session });
|
|
1196
|
+
});
|
|
1197
|
+
router.delete("/api/chat/sessions/:id", (req, res) => {
|
|
1198
|
+
const deleted = chatService.deleteSession(req.params.id);
|
|
1199
|
+
if (!deleted) {
|
|
1200
|
+
res.status(404).json({ error: "Session not found" });
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
res.json({ success: true });
|
|
1204
|
+
});
|
|
1205
|
+
router.post("/api/chat/sessions/:id/messages", async (req, res) => {
|
|
1206
|
+
const session = chatService.getSession(req.params.id);
|
|
1207
|
+
if (!session) {
|
|
1208
|
+
res.status(404).json({ error: "Session not found" });
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const { content } = req.body;
|
|
1212
|
+
if (!content?.trim()) {
|
|
1213
|
+
res.status(400).json({ error: "content is required" });
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const write = sseWriter2(res);
|
|
1217
|
+
try {
|
|
1218
|
+
await chatService.sendMessage(req.params.id, content.trim(), write);
|
|
1219
|
+
res.write(`event: done
|
|
1220
|
+
data: ${JSON.stringify({ type: "done" })}
|
|
1221
|
+
|
|
1222
|
+
`);
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
write({ type: "error", data: { message: err.message } });
|
|
1225
|
+
} finally {
|
|
1226
|
+
res.end();
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
return router;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/web/routes/knowledge.ts
|
|
1233
|
+
import { Router as Router4 } from "express";
|
|
1234
|
+
var logger7 = logger.child("KnowledgeRoutes");
|
|
1235
|
+
function createKnowledgeRouter(deps) {
|
|
1236
|
+
const router = Router4();
|
|
1237
|
+
const { knowledgeStore, iwikiImporter, gongfengExtractor, config } = deps;
|
|
1238
|
+
router.get("/api/knowledge/entries", (req, res) => {
|
|
1239
|
+
try {
|
|
1240
|
+
const type = req.query.type;
|
|
1241
|
+
const q = req.query.q;
|
|
1242
|
+
let entries;
|
|
1243
|
+
if (q) {
|
|
1244
|
+
entries = knowledgeStore.search(q);
|
|
1245
|
+
} else {
|
|
1246
|
+
entries = knowledgeStore.list(type || void 0);
|
|
1247
|
+
}
|
|
1248
|
+
res.json({ success: true, entries });
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
logger7.error("Failed to list knowledge entries", { error: err.message });
|
|
1251
|
+
res.status(500).json({ error: err.message });
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
router.get("/api/knowledge/entries/:id", (req, res) => {
|
|
1255
|
+
try {
|
|
1256
|
+
const entry = knowledgeStore.get(req.params.id);
|
|
1257
|
+
if (!entry) {
|
|
1258
|
+
res.status(404).json({ error: "Entry not found" });
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
res.json({ success: true, entry });
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
logger7.error("Failed to get knowledge entry", { error: err.message });
|
|
1264
|
+
res.status(500).json({ error: err.message });
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
router.post("/api/knowledge/entries", (req, res) => {
|
|
1268
|
+
try {
|
|
1269
|
+
const { title, content, tags, type } = req.body;
|
|
1270
|
+
if (!title?.trim() || !content?.trim()) {
|
|
1271
|
+
res.status(400).json({ error: "title and content are required" });
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const entry = knowledgeStore.create({
|
|
1275
|
+
type: type || "custom",
|
|
1276
|
+
title: title.trim(),
|
|
1277
|
+
content: content.trim(),
|
|
1278
|
+
tags: tags || []
|
|
1279
|
+
});
|
|
1280
|
+
res.json({ success: true, entry });
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
logger7.error("Failed to create knowledge entry", { error: err.message });
|
|
1283
|
+
res.status(500).json({ error: err.message });
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
router.put("/api/knowledge/entries/:id", (req, res) => {
|
|
1287
|
+
try {
|
|
1288
|
+
const { title, content, tags } = req.body;
|
|
1289
|
+
const entry = knowledgeStore.update(req.params.id, { title, content, tags });
|
|
1290
|
+
if (!entry) {
|
|
1291
|
+
res.status(404).json({ error: "Entry not found" });
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
res.json({ success: true, entry });
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
logger7.error("Failed to update knowledge entry", { error: err.message });
|
|
1297
|
+
res.status(500).json({ error: err.message });
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
router.delete("/api/knowledge/entries/:id", (req, res) => {
|
|
1301
|
+
try {
|
|
1302
|
+
const deleted = knowledgeStore.delete(req.params.id);
|
|
1303
|
+
if (!deleted) {
|
|
1304
|
+
res.status(404).json({ error: "Entry not found" });
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
res.json({ success: true });
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
logger7.error("Failed to delete knowledge entry", { error: err.message });
|
|
1310
|
+
res.status(500).json({ error: err.message });
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
router.post("/api/knowledge/analyze", (req, res) => {
|
|
1314
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1315
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1316
|
+
res.setHeader("Connection", "keep-alive");
|
|
1317
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
1318
|
+
res.flushHeaders();
|
|
1319
|
+
const workDir = config.project.workDir;
|
|
1320
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1321
|
+
const total = 4;
|
|
1322
|
+
let heartbeat;
|
|
1323
|
+
let closed = false;
|
|
1324
|
+
function sendProgress(data) {
|
|
1325
|
+
if (closed) return;
|
|
1326
|
+
try {
|
|
1327
|
+
res.write("data: " + JSON.stringify({ ...data, startedAt }) + "\n\n");
|
|
1328
|
+
} catch {
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
(async () => {
|
|
1332
|
+
try {
|
|
1333
|
+
sendProgress({ step: "collecting", current: 1, total, message: "\u6536\u96C6\u9879\u76EE\u4FE1\u606F..." });
|
|
1334
|
+
const { collectStaticInfo } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
|
|
1335
|
+
await collectStaticInfo(workDir);
|
|
1336
|
+
sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D..." });
|
|
1337
|
+
const aiStart = Date.now();
|
|
1338
|
+
heartbeat = setInterval(() => {
|
|
1339
|
+
sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
|
|
1340
|
+
}, 3e3);
|
|
1341
|
+
const { createAIRunner: createAIRunner2 } = await import("./ai-runner-RGAJPOOW.js");
|
|
1342
|
+
const runner = createAIRunner2(config.ai);
|
|
1343
|
+
const { analyze } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
|
|
1344
|
+
const knowledge = await analyze({ workDir, aiRunner: runner });
|
|
1345
|
+
clearInterval(heartbeat);
|
|
1346
|
+
heartbeat = void 0;
|
|
1347
|
+
sendProgress({ step: "saving", current: 3, total, message: "\u4FDD\u5B58\u5206\u6790\u7ED3\u679C..." });
|
|
1348
|
+
const { projectKnowledgeToMarkdown } = await import("./KnowledgeStore-4ROC6F56.js");
|
|
1349
|
+
const markdown = projectKnowledgeToMarkdown(knowledge);
|
|
1350
|
+
knowledgeStore.upsertProjectMeta(markdown, knowledge);
|
|
1351
|
+
knowledgeStore.setLastAnalyzedAt(knowledge.generatedAt);
|
|
1352
|
+
sendProgress({ step: "done", current: 4, total, message: "\u5206\u6790\u5B8C\u6210" });
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
1355
|
+
sendProgress({ step: "error", error: err.message });
|
|
1356
|
+
} finally {
|
|
1357
|
+
res.end();
|
|
1358
|
+
}
|
|
1359
|
+
})();
|
|
1360
|
+
req.on("close", () => {
|
|
1361
|
+
closed = true;
|
|
1362
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
1363
|
+
res.end();
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1366
|
+
router.post("/api/knowledge/analyze-incremental", (req, res) => {
|
|
1367
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1368
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1369
|
+
res.setHeader("Connection", "keep-alive");
|
|
1370
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
1371
|
+
res.flushHeaders();
|
|
1372
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1373
|
+
const total = 3;
|
|
1374
|
+
let heartbeat;
|
|
1375
|
+
let closed = false;
|
|
1376
|
+
function sendProgress(data) {
|
|
1377
|
+
if (closed) return;
|
|
1378
|
+
try {
|
|
1379
|
+
res.write("data: " + JSON.stringify({ ...data, startedAt }) + "\n\n");
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
(async () => {
|
|
1384
|
+
try {
|
|
1385
|
+
sendProgress({ step: "collecting", current: 1, total, message: "\u68C0\u6D4B\u53D8\u66F4\u5E76\u6536\u96C6\u4FE1\u606F..." });
|
|
1386
|
+
const { createAIRunner: createAIRunner2 } = await import("./ai-runner-RGAJPOOW.js");
|
|
1387
|
+
const { analyzeIncremental } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
|
|
1388
|
+
const runner = createAIRunner2(config.ai);
|
|
1389
|
+
sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D..." });
|
|
1390
|
+
const aiStart = Date.now();
|
|
1391
|
+
heartbeat = setInterval(() => {
|
|
1392
|
+
sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
|
|
1393
|
+
}, 3e3);
|
|
1394
|
+
const result = await analyzeIncremental({
|
|
1395
|
+
workDir: config.project.workDir,
|
|
1396
|
+
aiRunner: runner,
|
|
1397
|
+
store: knowledgeStore
|
|
1398
|
+
});
|
|
1399
|
+
clearInterval(heartbeat);
|
|
1400
|
+
heartbeat = void 0;
|
|
1401
|
+
if (!result) {
|
|
1402
|
+
sendProgress({ step: "no_changes", current: total, total, message: "\u81EA\u4E0A\u6B21\u5206\u6790\u4EE5\u6765\u65E0\u53D8\u66F4" });
|
|
1403
|
+
} else {
|
|
1404
|
+
sendProgress({ step: "done", current: 3, total, message: "\u589E\u91CF\u5206\u6790\u5B8C\u6210", changedFiles: result.changedFiles });
|
|
1405
|
+
}
|
|
1406
|
+
} catch (err) {
|
|
1407
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
1408
|
+
sendProgress({ step: "error", error: err.message });
|
|
1409
|
+
} finally {
|
|
1410
|
+
res.end();
|
|
1411
|
+
}
|
|
1412
|
+
})();
|
|
1413
|
+
req.on("close", () => {
|
|
1414
|
+
closed = true;
|
|
1415
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
1416
|
+
res.end();
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
router.post("/api/knowledge/import/iwiki", async (req, res) => {
|
|
1420
|
+
try {
|
|
1421
|
+
const { url } = req.body;
|
|
1422
|
+
if (!url?.trim()) {
|
|
1423
|
+
res.status(400).json({ error: "url is required" });
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const entry = await iwikiImporter.importFromUrl(url.trim());
|
|
1427
|
+
res.json({ success: true, entry });
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
logger7.error("Failed to import iwiki page", { error: err.message });
|
|
1430
|
+
res.status(500).json({ error: err.message });
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
router.post("/api/knowledge/import/gongfeng", async (req, res) => {
|
|
1434
|
+
try {
|
|
1435
|
+
if (!gongfengExtractor) {
|
|
1436
|
+
res.status(400).json({ error: "Gongfeng extractor not available" });
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const { type, iid } = req.body;
|
|
1440
|
+
if (!type || !iid) {
|
|
1441
|
+
res.status(400).json({ error: "type and iid are required" });
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const workDir = config.project.workDir;
|
|
1445
|
+
const entry = type === "issue" ? await gongfengExtractor.extractFromIssue(iid, workDir) : await gongfengExtractor.extractFromMR(iid, workDir);
|
|
1446
|
+
res.json({ success: true, entry });
|
|
1447
|
+
} catch (err) {
|
|
1448
|
+
logger7.error("Failed to extract from gongfeng", { error: err.message });
|
|
1449
|
+
res.status(500).json({ error: err.message });
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
router.post("/api/knowledge/entries/:id/sync", async (req, res) => {
|
|
1453
|
+
try {
|
|
1454
|
+
const entry = knowledgeStore.get(req.params.id);
|
|
1455
|
+
if (!entry) {
|
|
1456
|
+
res.status(404).json({ error: "Entry not found" });
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (entry.type === "iwiki") {
|
|
1460
|
+
const updated = await iwikiImporter.sync(req.params.id);
|
|
1461
|
+
res.json({ success: true, entry: updated });
|
|
1462
|
+
} else {
|
|
1463
|
+
res.status(400).json({ error: "Only iwiki entries support sync" });
|
|
1464
|
+
}
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
logger7.error("Failed to sync entry", { error: err.message });
|
|
1467
|
+
res.status(500).json({ error: err.message });
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
router.get("/api/knowledge/stats", (_req, res) => {
|
|
1471
|
+
try {
|
|
1472
|
+
const stats = knowledgeStore.getStats();
|
|
1473
|
+
res.json({ success: true, stats });
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
logger7.error("Failed to get knowledge stats", { error: err.message });
|
|
1476
|
+
res.status(500).json({ error: err.message });
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
return router;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/services/ChatService.ts
|
|
1483
|
+
import { randomUUID } from "crypto";
|
|
1484
|
+
|
|
1485
|
+
// src/prompts/chat-templates.ts
|
|
1486
|
+
var SUMMARY_MAX_CHARS = 500;
|
|
1487
|
+
function buildChatSystemPrompt(knowledge, knowledgeEntries) {
|
|
1488
|
+
let knowledgeSection = "";
|
|
1489
|
+
if (knowledge) {
|
|
1490
|
+
const parts = [];
|
|
1491
|
+
if (knowledge.structure) {
|
|
1492
|
+
parts.push(`- \u4E3B\u8981\u8BED\u8A00\uFF1A${knowledge.structure.primaryLanguage}`);
|
|
1493
|
+
if (knowledge.structure.frameworks.length > 0) {
|
|
1494
|
+
parts.push(`- \u6846\u67B6\uFF1A${knowledge.structure.frameworks.join(", ")}`);
|
|
1495
|
+
}
|
|
1496
|
+
if (knowledge.structure.description) {
|
|
1497
|
+
parts.push(`- \u63CF\u8FF0\uFF1A${knowledge.structure.description}`);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (knowledge.toolchain) {
|
|
1501
|
+
parts.push(`- \u5305\u7BA1\u7406\u5668\uFF1A${knowledge.toolchain.packageManager}`);
|
|
1502
|
+
}
|
|
1503
|
+
if (parts.length > 0) {
|
|
1504
|
+
knowledgeSection = `
|
|
1505
|
+
**\u9879\u76EE\u77E5\u8BC6\u5E93\uFF1A**
|
|
1506
|
+
${parts.join("\n")}
|
|
1507
|
+
`;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
let entriesSection = "";
|
|
1511
|
+
if (knowledgeEntries && knowledgeEntries.length > 0) {
|
|
1512
|
+
const entryParts = knowledgeEntries.map((e) => {
|
|
1513
|
+
const typeLabel = { "project-meta": "\u9879\u76EE\u5206\u6790", custom: "\u81EA\u5B9A\u4E49", iwiki: "iwiki\u6587\u6863", gongfeng: "\u5DE5\u8702\u63D0\u70BC" }[e.type] || e.type;
|
|
1514
|
+
const summary = e.content.length > SUMMARY_MAX_CHARS ? e.content.slice(0, SUMMARY_MAX_CHARS) + "..." : e.content;
|
|
1515
|
+
return `### [${typeLabel}] ${e.title}
|
|
1516
|
+
${summary}`;
|
|
1517
|
+
});
|
|
1518
|
+
entriesSection = `
|
|
1519
|
+
**\u77E5\u8BC6\u6761\u76EE\uFF08\u5171 ${knowledgeEntries.length} \u6761\uFF09\uFF1A**
|
|
1520
|
+
|
|
1521
|
+
${entryParts.join("\n\n")}
|
|
1522
|
+
`;
|
|
1523
|
+
}
|
|
1524
|
+
return `\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\uFF0C\u4E13\u6CE8\u4E8E\u4E3A\u5F00\u53D1\u8005\u63D0\u4F9B\u6280\u672F\u54A8\u8BE2\u548C\u9879\u76EE\u5E2E\u52A9\u3002
|
|
1525
|
+
|
|
1526
|
+
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
1527
|
+
1. \u89E3\u7B54\u6280\u672F\u95EE\u9898\uFF08\u7F16\u7A0B\u8BED\u8A00\u3001\u6846\u67B6\u3001\u5DE5\u5177\u94FE\u7B49\uFF09
|
|
1528
|
+
2. \u63D0\u4F9B\u9879\u76EE\u76F8\u5173\u7684\u4F7F\u7528\u6307\u5357\u548C\u6700\u4F73\u5B9E\u8DF5
|
|
1529
|
+
3. \u5E2E\u52A9\u5206\u6790\u548C\u89E3\u51B3\u5F00\u53D1\u4E2D\u9047\u5230\u7684\u95EE\u9898
|
|
1530
|
+
4. \u7ED9\u51FA\u4EE3\u7801\u793A\u4F8B\u548C\u5B9E\u73B0\u5EFA\u8BAE
|
|
1531
|
+
|
|
1532
|
+
\u56DE\u590D\u539F\u5219\uFF1A
|
|
1533
|
+
- \u4F7F\u7528\u6E05\u6670\u7B80\u6D01\u7684\u8BED\u8A00
|
|
1534
|
+
- \u7ED9\u51FA\u5177\u4F53\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE
|
|
1535
|
+
- \u5FC5\u8981\u65F6\u63D0\u4F9B\u4EE3\u7801\u793A\u4F8B
|
|
1536
|
+
- \u5982\u679C\u4E0D\u786E\u5B9A\uFF0C\u660E\u786E\u8BF4\u660E\u5E76\u7ED9\u51FA\u53EF\u80FD\u7684\u65B9\u5411
|
|
1537
|
+
- \u4F7F\u7528 Markdown \u683C\u5F0F\u7EC4\u7EC7\u56DE\u590D
|
|
1538
|
+
${knowledgeSection}${entriesSection}
|
|
1539
|
+
\u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u7684\u95EE\u9898\uFF0C\u4E0D\u8981\u8F93\u51FA\u591A\u4F59\u7684\u5BD2\u6684\u3002`;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/services/ChatService.ts
|
|
1543
|
+
var logger8 = logger.child("Chat");
|
|
1544
|
+
function agentConfigToAIConfig(agentCfg, timeoutMs) {
|
|
1545
|
+
return {
|
|
1546
|
+
mode: agentCfg.mode,
|
|
1547
|
+
binary: agentCfg.binary,
|
|
1548
|
+
phaseTimeoutMs: timeoutMs,
|
|
1549
|
+
nvmNodeVersion: agentCfg.nvmNodeVersion,
|
|
1550
|
+
model: agentCfg.model
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
var ChatService = class {
|
|
1554
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1555
|
+
runner;
|
|
1556
|
+
config;
|
|
1557
|
+
workDir;
|
|
1558
|
+
knowledgeStore;
|
|
1559
|
+
constructor(config, knowledgeStore) {
|
|
1560
|
+
this.config = config.chat;
|
|
1561
|
+
this.workDir = config.project.workDir;
|
|
1562
|
+
this.knowledgeStore = knowledgeStore;
|
|
1563
|
+
this.runner = createAIRunner(
|
|
1564
|
+
agentConfigToAIConfig(this.config.agent, this.config.timeoutMs)
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
createSession(title) {
|
|
1568
|
+
const session = {
|
|
1569
|
+
id: randomUUID(),
|
|
1570
|
+
title: title || "\u65B0\u5BF9\u8BDD",
|
|
1571
|
+
messages: [],
|
|
1572
|
+
status: "idle",
|
|
1573
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1574
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1575
|
+
};
|
|
1576
|
+
this.sessions.set(session.id, session);
|
|
1577
|
+
logger8.info("Created chat session", { sessionId: session.id });
|
|
1578
|
+
return session;
|
|
1579
|
+
}
|
|
1580
|
+
getSession(id) {
|
|
1581
|
+
return this.sessions.get(id);
|
|
1582
|
+
}
|
|
1583
|
+
listSessions() {
|
|
1584
|
+
return Array.from(this.sessions.values()).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1585
|
+
}
|
|
1586
|
+
deleteSession(id) {
|
|
1587
|
+
return this.sessions.delete(id);
|
|
1588
|
+
}
|
|
1589
|
+
async sendMessage(sessionId, content, onEvent) {
|
|
1590
|
+
const session = this.requireSession(sessionId);
|
|
1591
|
+
if (session.messages.length >= this.config.maxSessionMessages) {
|
|
1592
|
+
throw new Error(`Session message limit reached (${this.config.maxSessionMessages})`);
|
|
1593
|
+
}
|
|
1594
|
+
const userMessage = {
|
|
1595
|
+
id: randomUUID(),
|
|
1596
|
+
role: "user",
|
|
1597
|
+
content,
|
|
1598
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1599
|
+
};
|
|
1600
|
+
session.messages.push(userMessage);
|
|
1601
|
+
session.status = "thinking";
|
|
1602
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1603
|
+
const assistantMessageId = randomUUID();
|
|
1604
|
+
let assistantContent = "";
|
|
1605
|
+
try {
|
|
1606
|
+
onEvent?.({
|
|
1607
|
+
type: "message:thinking",
|
|
1608
|
+
data: { status: "connecting" },
|
|
1609
|
+
messageId: assistantMessageId
|
|
1610
|
+
});
|
|
1611
|
+
const isFirstMessage = session.messages.length === 1;
|
|
1612
|
+
const knowledgeEntries = this.knowledgeStore?.getAllEntries();
|
|
1613
|
+
const prompt = isFirstMessage ? `${buildChatSystemPrompt(getProjectKnowledge(), knowledgeEntries)}
|
|
1614
|
+
|
|
1615
|
+
\u7528\u6237\u95EE\u9898\uFF1A${content}` : content;
|
|
1616
|
+
const result = await this.runner.run({
|
|
1617
|
+
prompt,
|
|
1618
|
+
workDir: this.workDir,
|
|
1619
|
+
timeoutMs: this.config.timeoutMs,
|
|
1620
|
+
sessionId: session.aiSessionId,
|
|
1621
|
+
continueSession: !!session.aiSessionId,
|
|
1622
|
+
onStreamEvent: (evt) => {
|
|
1623
|
+
if (evt.content && typeof evt.content === "object") {
|
|
1624
|
+
const content2 = evt.content;
|
|
1625
|
+
if (content2.type === "assistant") {
|
|
1626
|
+
const message = content2.message;
|
|
1627
|
+
if (message?.content && Array.isArray(message.content)) {
|
|
1628
|
+
for (const block of message.content) {
|
|
1629
|
+
if (block.type === "text" && block.text) {
|
|
1630
|
+
assistantContent += block.text;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
} else if (evt.content && typeof evt.content === "string") {
|
|
1636
|
+
if (evt.type === "partial" || evt.type === "raw") {
|
|
1637
|
+
assistantContent += evt.content;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
onEvent?.({
|
|
1641
|
+
type: "message:chunk",
|
|
1642
|
+
data: evt,
|
|
1643
|
+
messageId: assistantMessageId
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
const finalContent = assistantContent.trim() ? assistantContent : result.success ? result.output : result.output || "AI \u672A\u8FD4\u56DE\u5185\u5BB9";
|
|
1648
|
+
const assistantMessage = {
|
|
1649
|
+
id: assistantMessageId,
|
|
1650
|
+
role: "assistant",
|
|
1651
|
+
content: finalContent,
|
|
1652
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1653
|
+
};
|
|
1654
|
+
session.messages.push(assistantMessage);
|
|
1655
|
+
session.aiSessionId = result.sessionId ?? session.aiSessionId;
|
|
1656
|
+
session.status = result.success ? "idle" : "error";
|
|
1657
|
+
if (!result.success) {
|
|
1658
|
+
session.error = result.output;
|
|
1659
|
+
}
|
|
1660
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1661
|
+
if (session.title === "\u65B0\u5BF9\u8BDD" && session.messages.length >= 2) {
|
|
1662
|
+
session.title = content.slice(0, 50) + (content.length > 50 ? "..." : "");
|
|
1663
|
+
}
|
|
1664
|
+
onEvent?.({
|
|
1665
|
+
type: "message:complete",
|
|
1666
|
+
data: { message: assistantMessage },
|
|
1667
|
+
messageId: assistantMessageId
|
|
1668
|
+
});
|
|
1669
|
+
return assistantMessage;
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
session.status = "error";
|
|
1672
|
+
session.error = err.message;
|
|
1673
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1674
|
+
onEvent?.({
|
|
1675
|
+
type: "error",
|
|
1676
|
+
data: { message: err.message },
|
|
1677
|
+
messageId: assistantMessageId
|
|
1678
|
+
});
|
|
1679
|
+
throw err;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
requireSession(id) {
|
|
1683
|
+
const session = this.sessions.get(id);
|
|
1684
|
+
if (!session) {
|
|
1685
|
+
throw new Error(`Chat session not found: ${id}`);
|
|
1686
|
+
}
|
|
1687
|
+
return session;
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1066
1691
|
// src/web/WebServer.ts
|
|
1067
1692
|
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
1068
|
-
var
|
|
1693
|
+
var logger9 = logger.child("WebServer");
|
|
1069
1694
|
var WebServer = class {
|
|
1070
1695
|
app;
|
|
1071
1696
|
server = null;
|
|
@@ -1093,7 +1718,9 @@ var WebServer = class {
|
|
|
1093
1718
|
orchestrator: deps.orchestrator,
|
|
1094
1719
|
mainGit: deps.mainGit,
|
|
1095
1720
|
gongfeng: deps.gongfeng,
|
|
1096
|
-
supplementStore: deps.supplementStore
|
|
1721
|
+
supplementStore: deps.supplementStore,
|
|
1722
|
+
autoUpdater: deps.autoUpdater,
|
|
1723
|
+
poller: deps.poller
|
|
1097
1724
|
};
|
|
1098
1725
|
}
|
|
1099
1726
|
this.app = express();
|
|
@@ -1110,6 +1737,25 @@ var WebServer = class {
|
|
|
1110
1737
|
});
|
|
1111
1738
|
this.app.use(brainstormRouter);
|
|
1112
1739
|
}
|
|
1740
|
+
const knowledgeDataDir = path3.join(apiDeps.config.project.workDir, "data", "knowledge");
|
|
1741
|
+
const knowledgeStore = new KnowledgeStore(knowledgeDataDir);
|
|
1742
|
+
if (apiDeps.config.chat.enabled) {
|
|
1743
|
+
const chatService = new ChatService(apiDeps.config, knowledgeStore);
|
|
1744
|
+
const chatRouter = createChatRouter({ chatService });
|
|
1745
|
+
this.app.use(chatRouter);
|
|
1746
|
+
}
|
|
1747
|
+
const iwikiImporter = new IwikiImporter(knowledgeStore, {
|
|
1748
|
+
authCookie: apiDeps.config.iwiki?.authCookie,
|
|
1749
|
+
authToken: apiDeps.config.iwiki?.authToken
|
|
1750
|
+
});
|
|
1751
|
+
const legacyKnowledgePath = path3.join(apiDeps.config.project.workDir, "knowledge.json");
|
|
1752
|
+
knowledgeStore.migrateFromLegacy(legacyKnowledgePath);
|
|
1753
|
+
const knowledgeRouter = createKnowledgeRouter({
|
|
1754
|
+
knowledgeStore,
|
|
1755
|
+
iwikiImporter,
|
|
1756
|
+
config: apiDeps.config
|
|
1757
|
+
});
|
|
1758
|
+
this.app.use(knowledgeRouter);
|
|
1113
1759
|
const publicDir = apiDeps.config.web.frontendDistDir;
|
|
1114
1760
|
this.app.use(express.static(publicDir));
|
|
1115
1761
|
this.app.get("{*path}", (_req, res) => {
|
|
@@ -1119,7 +1765,7 @@ var WebServer = class {
|
|
|
1119
1765
|
start() {
|
|
1120
1766
|
return new Promise((resolve) => {
|
|
1121
1767
|
this.server = this.app.listen(this.port, () => {
|
|
1122
|
-
|
|
1768
|
+
logger9.info(`Web UI available at http://localhost:${this.port}`);
|
|
1123
1769
|
resolve();
|
|
1124
1770
|
});
|
|
1125
1771
|
});
|
|
@@ -1128,7 +1774,7 @@ var WebServer = class {
|
|
|
1128
1774
|
if (this.server) {
|
|
1129
1775
|
this.server.close();
|
|
1130
1776
|
this.server = null;
|
|
1131
|
-
|
|
1777
|
+
logger9.info("Web server stopped");
|
|
1132
1778
|
}
|
|
1133
1779
|
}
|
|
1134
1780
|
};
|
|
@@ -1137,10 +1783,10 @@ var WebServer = class {
|
|
|
1137
1783
|
import express2 from "express";
|
|
1138
1784
|
|
|
1139
1785
|
// src/webhook/WebhookHandler.ts
|
|
1140
|
-
import { Router as
|
|
1786
|
+
import { Router as Router5 } from "express";
|
|
1141
1787
|
|
|
1142
1788
|
// src/webhook/CommandParser.ts
|
|
1143
|
-
var
|
|
1789
|
+
var TRIGGERS = ["@issue-auto", "@iaf"];
|
|
1144
1790
|
var PHASE_ALIAS = {
|
|
1145
1791
|
"\u5206\u6790": "analysis",
|
|
1146
1792
|
"\u9700\u6C42\u5206\u6790": "analysis",
|
|
@@ -1238,15 +1884,27 @@ var EXACT_PATTERNS = [
|
|
|
1238
1884
|
{
|
|
1239
1885
|
regex: /(?:清理评论|清除评论|删除评论)/,
|
|
1240
1886
|
build: () => ({ intent: "clean-notes" })
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
regex: /fix-conflict\b/i,
|
|
1890
|
+
build: () => ({ intent: "fix-conflict" })
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
regex: /(?:修复冲突|解决冲突|处理冲突|冲突修复|fix\s*冲突)/,
|
|
1894
|
+
build: () => ({ intent: "fix-conflict" })
|
|
1241
1895
|
}
|
|
1242
1896
|
];
|
|
1243
1897
|
function containsTrigger(text) {
|
|
1244
|
-
|
|
1898
|
+
const lower = text.toLowerCase();
|
|
1899
|
+
return TRIGGERS.some((t2) => lower.includes(t2));
|
|
1245
1900
|
}
|
|
1246
1901
|
function extractCommandText(text) {
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1902
|
+
const lower = text.toLowerCase();
|
|
1903
|
+
for (const trigger of TRIGGERS) {
|
|
1904
|
+
const idx = lower.indexOf(trigger);
|
|
1905
|
+
if (idx >= 0) return text.slice(idx + trigger.length).trim();
|
|
1906
|
+
}
|
|
1907
|
+
return null;
|
|
1250
1908
|
}
|
|
1251
1909
|
function parseExact(commandText) {
|
|
1252
1910
|
for (const { regex, build } of EXACT_PATTERNS) {
|
|
@@ -1259,28 +1917,30 @@ function parseExact(commandText) {
|
|
|
1259
1917
|
// src/webhook/CommandExecutor.ts
|
|
1260
1918
|
import path4 from "path";
|
|
1261
1919
|
import fs3 from "fs";
|
|
1262
|
-
var
|
|
1920
|
+
var logger10 = logger.child("CommandExecutor");
|
|
1263
1921
|
var CommandExecutor = class {
|
|
1264
1922
|
tracker;
|
|
1265
1923
|
orchestrator;
|
|
1266
1924
|
supplementStore;
|
|
1267
1925
|
gongfeng;
|
|
1268
1926
|
config;
|
|
1927
|
+
poller;
|
|
1269
1928
|
constructor(deps) {
|
|
1270
1929
|
this.tracker = deps.tracker;
|
|
1271
1930
|
this.orchestrator = deps.orchestrator;
|
|
1272
1931
|
this.supplementStore = deps.supplementStore;
|
|
1273
1932
|
this.gongfeng = deps.gongfeng;
|
|
1274
1933
|
this.config = deps.config;
|
|
1934
|
+
this.poller = deps.poller;
|
|
1275
1935
|
}
|
|
1276
1936
|
async execute(issueIid, issueId, command) {
|
|
1277
|
-
|
|
1937
|
+
logger10.info("Executing webhook command", { issueIid, command });
|
|
1278
1938
|
let result;
|
|
1279
1939
|
try {
|
|
1280
1940
|
result = await this.dispatch(issueIid, command);
|
|
1281
1941
|
} catch (err) {
|
|
1282
1942
|
const msg = err.message;
|
|
1283
|
-
|
|
1943
|
+
logger10.error("Webhook command failed", { issueIid, error: msg });
|
|
1284
1944
|
result = { success: false, message: msg };
|
|
1285
1945
|
}
|
|
1286
1946
|
await this.replyToIssue(issueId, result);
|
|
@@ -1308,6 +1968,8 @@ var CommandExecutor = class {
|
|
|
1308
1968
|
return this.handleStopPreview(iid);
|
|
1309
1969
|
case "clean-notes":
|
|
1310
1970
|
return this.handleCleanNotes(iid);
|
|
1971
|
+
case "fix-conflict":
|
|
1972
|
+
return this.handleFixConflict(iid);
|
|
1311
1973
|
default:
|
|
1312
1974
|
return { success: false, message: "Unknown command" };
|
|
1313
1975
|
}
|
|
@@ -1399,6 +2061,7 @@ var CommandExecutor = class {
|
|
|
1399
2061
|
}
|
|
1400
2062
|
async handleRestart(iid) {
|
|
1401
2063
|
try {
|
|
2064
|
+
this.poller?.forceReleaseIssue(iid);
|
|
1402
2065
|
await this.orchestrator.restartIssue(iid);
|
|
1403
2066
|
} catch (err) {
|
|
1404
2067
|
return this.fail(err.message);
|
|
@@ -1452,6 +2115,22 @@ var CommandExecutor = class {
|
|
|
1452
2115
|
return this.fail(err.message);
|
|
1453
2116
|
}
|
|
1454
2117
|
}
|
|
2118
|
+
handleFixConflict(iid) {
|
|
2119
|
+
const record = this.tracker.get(iid);
|
|
2120
|
+
if (!record) return this.notTracked(iid);
|
|
2121
|
+
const isCompleted = record.state === "completed" /* Completed */;
|
|
2122
|
+
const isConflictRetry = record.state === "failed" /* Failed */ && record.failedAtState === "resolving_conflict" /* ResolvingConflict */;
|
|
2123
|
+
if (!isCompleted && !isConflictRetry) {
|
|
2124
|
+
return this.fail(t("conflict.invalidState", { state: record.state }));
|
|
2125
|
+
}
|
|
2126
|
+
if (!record.mrUrl) {
|
|
2127
|
+
return this.fail(t("conflict.noMr", { iid }));
|
|
2128
|
+
}
|
|
2129
|
+
this.orchestrator.resolveConflict(iid).catch((err) => {
|
|
2130
|
+
logger10.error("Async conflict resolution failed", { iid, error: err.message });
|
|
2131
|
+
});
|
|
2132
|
+
return this.ok(t("conflict.startedMsg"));
|
|
2133
|
+
}
|
|
1455
2134
|
saveSupplement(iid, context) {
|
|
1456
2135
|
const existing = this.supplementStore.get(iid);
|
|
1457
2136
|
const prev = existing?.freeText?.trim() ?? "";
|
|
@@ -1474,7 +2153,7 @@ ${context}` : context;
|
|
|
1474
2153
|
const prefix = result.success ? "" : "> **Failed**\n>\n> ";
|
|
1475
2154
|
await this.gongfeng.createIssueNote(issueId, `${prefix}${result.message}`);
|
|
1476
2155
|
} catch (err) {
|
|
1477
|
-
|
|
2156
|
+
logger10.warn("Failed to reply", { issueId, error: err.message });
|
|
1478
2157
|
}
|
|
1479
2158
|
}
|
|
1480
2159
|
ok(message) {
|
|
@@ -1530,9 +2209,9 @@ var NoteDeduplicator = class {
|
|
|
1530
2209
|
};
|
|
1531
2210
|
|
|
1532
2211
|
// src/webhook/WebhookHandler.ts
|
|
1533
|
-
var
|
|
2212
|
+
var logger11 = logger.child("WebhookHandler");
|
|
1534
2213
|
function createWebhookRouter(deps) {
|
|
1535
|
-
const router =
|
|
2214
|
+
const router = Router5();
|
|
1536
2215
|
const executor = new CommandExecutor(deps);
|
|
1537
2216
|
const dedup = new NoteDeduplicator();
|
|
1538
2217
|
const { config, intentRecognizer } = deps;
|
|
@@ -1545,19 +2224,15 @@ function createWebhookRouter(deps) {
|
|
|
1545
2224
|
if (config.webhook.secret) {
|
|
1546
2225
|
const token = req.headers["x-gitlab-token"];
|
|
1547
2226
|
if (token !== config.webhook.secret) {
|
|
1548
|
-
|
|
2227
|
+
logger11.warn("Webhook token mismatch");
|
|
1549
2228
|
res.status(401).json({ error: "Invalid token" });
|
|
1550
2229
|
return;
|
|
1551
2230
|
}
|
|
1552
2231
|
}
|
|
1553
2232
|
const event = req.body;
|
|
1554
|
-
if (!isNoteOnIssue(event)) {
|
|
1555
|
-
res.json({ ignored: true, reason: "not a note on issue" });
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
2233
|
const noteBody = event.object_attributes.note;
|
|
1559
2234
|
if (!containsTrigger(noteBody)) {
|
|
1560
|
-
res.json({ ignored: true, reason: "no
|
|
2235
|
+
res.json({ ignored: true, reason: "no trigger found" });
|
|
1561
2236
|
return;
|
|
1562
2237
|
}
|
|
1563
2238
|
if (isSelfNote(event, selfToken)) {
|
|
@@ -1566,32 +2241,46 @@ function createWebhookRouter(deps) {
|
|
|
1566
2241
|
}
|
|
1567
2242
|
const noteId = event.object_attributes.id;
|
|
1568
2243
|
if (dedup.isDuplicate(noteId)) {
|
|
1569
|
-
|
|
2244
|
+
logger11.debug("Duplicate note, skipping", { noteId });
|
|
1570
2245
|
res.json({ ignored: true, reason: "duplicate" });
|
|
1571
2246
|
return;
|
|
1572
2247
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
2248
|
+
if (isNoteOnIssue(event)) {
|
|
2249
|
+
const issueIid = event.issue?.iid;
|
|
2250
|
+
const issueId = event.issue?.id;
|
|
2251
|
+
if (!issueIid) {
|
|
2252
|
+
res.status(400).json({ error: "Missing issue iid in webhook payload" });
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
if (!issueId) {
|
|
2256
|
+
logger11.info("Webhook received with null issue id (likely a test ping)", { issueIid });
|
|
2257
|
+
res.json({ accepted: true, noteId, issueIid, test: true });
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
res.json({ accepted: true, noteId, issueIid });
|
|
2261
|
+
processCommandAsync(
|
|
2262
|
+
noteBody,
|
|
2263
|
+
issueIid,
|
|
2264
|
+
issueId,
|
|
2265
|
+
executor,
|
|
2266
|
+
intentRecognizer,
|
|
2267
|
+
config,
|
|
2268
|
+
deps.tracker,
|
|
2269
|
+
deps.gongfeng
|
|
2270
|
+
);
|
|
2271
|
+
} else if (isNoteOnMergeRequest(event)) {
|
|
2272
|
+
const mr = event.merge_request;
|
|
2273
|
+
res.json({ accepted: true, noteId, mrIid: mr.iid });
|
|
2274
|
+
processMRCommandAsync(
|
|
2275
|
+
noteBody,
|
|
2276
|
+
mr,
|
|
2277
|
+
executor,
|
|
2278
|
+
deps.tracker,
|
|
2279
|
+
deps.gongfeng
|
|
2280
|
+
);
|
|
2281
|
+
} else {
|
|
2282
|
+
res.json({ ignored: true, reason: "not a note on issue or MR" });
|
|
1583
2283
|
}
|
|
1584
|
-
res.json({ accepted: true, noteId, issueIid });
|
|
1585
|
-
processCommandAsync(
|
|
1586
|
-
noteBody,
|
|
1587
|
-
issueIid,
|
|
1588
|
-
issueId,
|
|
1589
|
-
executor,
|
|
1590
|
-
intentRecognizer,
|
|
1591
|
-
config,
|
|
1592
|
-
deps.tracker,
|
|
1593
|
-
deps.gongfeng
|
|
1594
|
-
);
|
|
1595
2284
|
});
|
|
1596
2285
|
return router;
|
|
1597
2286
|
}
|
|
@@ -1604,19 +2293,19 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
|
|
|
1604
2293
|
if (!command && intentRecognizer && config.webhook.llmFallback) {
|
|
1605
2294
|
const record = tracker.get(issueIid);
|
|
1606
2295
|
const state = record?.state;
|
|
1607
|
-
|
|
2296
|
+
logger11.info("Falling back to LLM intent recognition", { issueIid });
|
|
1608
2297
|
command = await intentRecognizer.recognize(cmdText, state);
|
|
1609
2298
|
}
|
|
1610
2299
|
if (!command) {
|
|
1611
|
-
|
|
2300
|
+
logger11.info("Could not parse command from note", { issueIid, text: cmdText.slice(0, 100) });
|
|
1612
2301
|
await gongfeng.createIssueNote(issueId, HELP_TEXT_FUNC()).catch(
|
|
1613
|
-
(e) =>
|
|
2302
|
+
(e) => logger11.warn("Failed to reply help text", { error: e.message })
|
|
1614
2303
|
);
|
|
1615
2304
|
return;
|
|
1616
2305
|
}
|
|
1617
2306
|
await executor.execute(issueIid, issueId, command);
|
|
1618
2307
|
} catch (err) {
|
|
1619
|
-
|
|
2308
|
+
logger11.error("Failed to process webhook command", {
|
|
1620
2309
|
issueIid,
|
|
1621
2310
|
error: err.message
|
|
1622
2311
|
});
|
|
@@ -1625,13 +2314,62 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
|
|
|
1625
2314
|
function isNoteOnIssue(event) {
|
|
1626
2315
|
return event.object_kind === "note" && event.object_attributes?.noteable_type?.toLowerCase() === "issue" && !!event.issue;
|
|
1627
2316
|
}
|
|
2317
|
+
function isNoteOnMergeRequest(event) {
|
|
2318
|
+
return event.object_kind === "note" && (event.object_attributes?.noteable_type?.toLowerCase() === "review" || event.object_attributes?.noteable_type?.toLowerCase() === "mergerequest") && !!event.merge_request;
|
|
2319
|
+
}
|
|
2320
|
+
var MR_SUPPORTED_INTENTS = /* @__PURE__ */ new Set(["fix-conflict", "status"]);
|
|
2321
|
+
async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng) {
|
|
2322
|
+
try {
|
|
2323
|
+
const cmdText = extractCommandText(noteBody);
|
|
2324
|
+
if (!cmdText) return;
|
|
2325
|
+
const command = parseExact(cmdText);
|
|
2326
|
+
if (!command) {
|
|
2327
|
+
await gongfeng.createMergeRequestNote(mr.iid, HELP_TEXT_FUNC()).catch(
|
|
2328
|
+
(e) => logger11.warn("Failed to reply help text on MR", { error: e.message })
|
|
2329
|
+
);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
if (!MR_SUPPORTED_INTENTS.has(command.intent)) {
|
|
2333
|
+
await gongfeng.createMergeRequestNote(
|
|
2334
|
+
mr.iid,
|
|
2335
|
+
`\`${command.intent}\` command is not supported on MR comments. Please use Issue comments instead.`
|
|
2336
|
+
).catch(
|
|
2337
|
+
(e) => logger11.warn("Failed to reply on MR", { error: e.message })
|
|
2338
|
+
);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
const allRecords = tracker.getAll();
|
|
2342
|
+
const record = allRecords.find((r) => r.branchName === mr.source_branch);
|
|
2343
|
+
if (!record) {
|
|
2344
|
+
await gongfeng.createMergeRequestNote(
|
|
2345
|
+
mr.iid,
|
|
2346
|
+
`Could not find a tracked issue for branch \`${mr.source_branch}\`.`
|
|
2347
|
+
).catch(
|
|
2348
|
+
(e) => logger11.warn("Failed to reply on MR", { error: e.message })
|
|
2349
|
+
);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
const result = await executor.execute(record.issueIid, record.issueId, command);
|
|
2353
|
+
try {
|
|
2354
|
+
const prefix = result.success ? "" : "> **Failed**\n>\n> ";
|
|
2355
|
+
await gongfeng.createMergeRequestNote(mr.iid, `${prefix}${result.message}`);
|
|
2356
|
+
} catch (err) {
|
|
2357
|
+
logger11.warn("Failed to reply on MR", { mrIid: mr.iid, error: err.message });
|
|
2358
|
+
}
|
|
2359
|
+
} catch (err) {
|
|
2360
|
+
logger11.error("Failed to process MR webhook command", {
|
|
2361
|
+
mrIid: mr.iid,
|
|
2362
|
+
error: err.message
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
1628
2366
|
function isSelfNote(_event, _selfToken) {
|
|
1629
2367
|
return false;
|
|
1630
2368
|
}
|
|
1631
2369
|
|
|
1632
2370
|
// src/webhook/IntentRecognizer.ts
|
|
1633
2371
|
import { spawn } from "child_process";
|
|
1634
|
-
var
|
|
2372
|
+
var logger12 = logger.child("IntentRecognizer");
|
|
1635
2373
|
var VALID_INTENTS = [
|
|
1636
2374
|
"retry",
|
|
1637
2375
|
"retry-from",
|
|
@@ -1639,7 +2377,8 @@ var VALID_INTENTS = [
|
|
|
1639
2377
|
"approve",
|
|
1640
2378
|
"reject",
|
|
1641
2379
|
"status",
|
|
1642
|
-
"restart"
|
|
2380
|
+
"restart",
|
|
2381
|
+
"fix-conflict"
|
|
1643
2382
|
];
|
|
1644
2383
|
var VALID_PHASES = [
|
|
1645
2384
|
"analysis",
|
|
@@ -1672,7 +2411,7 @@ ${userComment}`;
|
|
|
1672
2411
|
const rawOutput = await this.runLLM(userPrompt);
|
|
1673
2412
|
return this.parseResponse(rawOutput);
|
|
1674
2413
|
} catch (err) {
|
|
1675
|
-
|
|
2414
|
+
logger12.error("Intent recognition failed", { error: err.message });
|
|
1676
2415
|
return null;
|
|
1677
2416
|
}
|
|
1678
2417
|
}
|
|
@@ -1680,20 +2419,20 @@ ${userComment}`;
|
|
|
1680
2419
|
parseResponse(raw) {
|
|
1681
2420
|
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
1682
2421
|
if (!jsonMatch) {
|
|
1683
|
-
|
|
2422
|
+
logger12.warn("No JSON found in LLM response", { raw: raw.slice(0, 200) });
|
|
1684
2423
|
return null;
|
|
1685
2424
|
}
|
|
1686
2425
|
let parsed;
|
|
1687
2426
|
try {
|
|
1688
2427
|
parsed = JSON.parse(jsonMatch[0]);
|
|
1689
2428
|
} catch {
|
|
1690
|
-
|
|
2429
|
+
logger12.warn("Failed to parse JSON from LLM", { raw: jsonMatch[0].slice(0, 200) });
|
|
1691
2430
|
return null;
|
|
1692
2431
|
}
|
|
1693
2432
|
if (parsed.intent === null || parsed.intent === "null") return null;
|
|
1694
2433
|
const intent = String(parsed.intent);
|
|
1695
2434
|
if (!VALID_INTENTS.includes(intent)) {
|
|
1696
|
-
|
|
2435
|
+
logger12.warn("Invalid intent from LLM", { intent });
|
|
1697
2436
|
return null;
|
|
1698
2437
|
}
|
|
1699
2438
|
const result = { intent };
|
|
@@ -1735,7 +2474,7 @@ ${userComment}`;
|
|
|
1735
2474
|
child.on("close", (code) => {
|
|
1736
2475
|
clearTimeout(timer);
|
|
1737
2476
|
if (code !== 0) {
|
|
1738
|
-
|
|
2477
|
+
logger12.warn("LLM process exited with non-zero code", { code, stderr: stderr.slice(0, 300) });
|
|
1739
2478
|
}
|
|
1740
2479
|
resolve(stdout);
|
|
1741
2480
|
});
|
|
@@ -1750,7 +2489,7 @@ ${userComment}`;
|
|
|
1750
2489
|
};
|
|
1751
2490
|
|
|
1752
2491
|
// src/webhook/WebhookServer.ts
|
|
1753
|
-
var
|
|
2492
|
+
var logger13 = logger.child("WebhookServer");
|
|
1754
2493
|
var WebhookServer = class {
|
|
1755
2494
|
app;
|
|
1756
2495
|
server = null;
|
|
@@ -1768,7 +2507,8 @@ var WebhookServer = class {
|
|
|
1768
2507
|
supplementStore: deps.supplementStore,
|
|
1769
2508
|
gongfeng: deps.gongfeng,
|
|
1770
2509
|
config: deps.config,
|
|
1771
|
-
intentRecognizer
|
|
2510
|
+
intentRecognizer,
|
|
2511
|
+
poller: deps.poller
|
|
1772
2512
|
};
|
|
1773
2513
|
this.app = express2();
|
|
1774
2514
|
this.app.use(express2.json());
|
|
@@ -1780,7 +2520,7 @@ var WebhookServer = class {
|
|
|
1780
2520
|
start() {
|
|
1781
2521
|
return new Promise((resolve) => {
|
|
1782
2522
|
this.server = this.app.listen(this.port, () => {
|
|
1783
|
-
|
|
2523
|
+
logger13.info(`Webhook server listening on port ${this.port}`);
|
|
1784
2524
|
resolve();
|
|
1785
2525
|
});
|
|
1786
2526
|
});
|
|
@@ -1789,7 +2529,7 @@ var WebhookServer = class {
|
|
|
1789
2529
|
if (this.server) {
|
|
1790
2530
|
this.server.close();
|
|
1791
2531
|
this.server = null;
|
|
1792
|
-
|
|
2532
|
+
logger13.info("Webhook server stopped");
|
|
1793
2533
|
}
|
|
1794
2534
|
}
|
|
1795
2535
|
};
|
|
@@ -1797,7 +2537,7 @@ var WebhookServer = class {
|
|
|
1797
2537
|
// src/web/AgentLogStore.ts
|
|
1798
2538
|
import fs4 from "fs";
|
|
1799
2539
|
import path5 from "path";
|
|
1800
|
-
var
|
|
2540
|
+
var logger14 = logger.child("AgentLogStore");
|
|
1801
2541
|
var MAX_LOGS_PER_ISSUE = 2e4;
|
|
1802
2542
|
var DEBUG_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1803
2543
|
"thinking",
|
|
@@ -1824,7 +2564,7 @@ var AgentLogStore = class {
|
|
|
1824
2564
|
eventBus.on("pipeline:progress", (payload) => {
|
|
1825
2565
|
this.handlePipelineProgress(payload);
|
|
1826
2566
|
});
|
|
1827
|
-
|
|
2567
|
+
logger14.info("AgentLogStore listening for events");
|
|
1828
2568
|
}
|
|
1829
2569
|
getLogs(issueIid) {
|
|
1830
2570
|
const filePath = this.logFilePath(issueIid);
|
|
@@ -1834,7 +2574,7 @@ var AgentLogStore = class {
|
|
|
1834
2574
|
if (!raw) return [];
|
|
1835
2575
|
return raw.split("\n").map((line) => JSON.parse(line));
|
|
1836
2576
|
} catch (err) {
|
|
1837
|
-
|
|
2577
|
+
logger14.warn("Failed to read agent logs", { issueIid, error: err.message });
|
|
1838
2578
|
return [];
|
|
1839
2579
|
}
|
|
1840
2580
|
}
|
|
@@ -1854,7 +2594,7 @@ var AgentLogStore = class {
|
|
|
1854
2594
|
`, "utf-8");
|
|
1855
2595
|
this.trimIfNeeded(issueIid, filePath);
|
|
1856
2596
|
} catch (err) {
|
|
1857
|
-
|
|
2597
|
+
logger14.warn("Failed to write agent log", { issueIid, error: err.message });
|
|
1858
2598
|
}
|
|
1859
2599
|
}
|
|
1860
2600
|
trimIfNeeded(issueIid, filePath) {
|
|
@@ -1866,7 +2606,7 @@ var AgentLogStore = class {
|
|
|
1866
2606
|
const trimmed = lines.slice(-MAX_LOGS_PER_ISSUE);
|
|
1867
2607
|
fs4.writeFileSync(filePath, `${trimmed.join("\n")}
|
|
1868
2608
|
`, "utf-8");
|
|
1869
|
-
|
|
2609
|
+
logger14.info("Agent logs trimmed", { issueIid, from: lines.length, to: trimmed.length });
|
|
1870
2610
|
}
|
|
1871
2611
|
} catch {
|
|
1872
2612
|
}
|
|
@@ -1936,14 +2676,317 @@ var AgentLogStore = class {
|
|
|
1936
2676
|
}
|
|
1937
2677
|
};
|
|
1938
2678
|
|
|
1939
|
-
// src/
|
|
2679
|
+
// src/updater/VersionChecker.ts
|
|
2680
|
+
import { readFileSync, existsSync } from "fs";
|
|
1940
2681
|
import path6 from "path";
|
|
1941
2682
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1942
2683
|
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
2684
|
+
var logger15 = logger.child("VersionChecker");
|
|
2685
|
+
function compareSemver(a, b) {
|
|
2686
|
+
const pa = a.split(".").map(Number);
|
|
2687
|
+
const pb = b.split(".").map(Number);
|
|
2688
|
+
for (let i = 0; i < 3; i++) {
|
|
2689
|
+
const va = pa[i] ?? 0;
|
|
2690
|
+
const vb = pb[i] ?? 0;
|
|
2691
|
+
if (va > vb) return 1;
|
|
2692
|
+
if (va < vb) return -1;
|
|
2693
|
+
}
|
|
2694
|
+
return 0;
|
|
2695
|
+
}
|
|
2696
|
+
var VersionChecker = class {
|
|
2697
|
+
registry;
|
|
2698
|
+
packageName;
|
|
2699
|
+
currentVersion;
|
|
2700
|
+
constructor(registry) {
|
|
2701
|
+
this.registry = registry.replace(/\/$/, "");
|
|
2702
|
+
const pkg = this.readPackageJson();
|
|
2703
|
+
this.packageName = pkg.name;
|
|
2704
|
+
this.currentVersion = pkg.version;
|
|
2705
|
+
}
|
|
2706
|
+
getCurrentVersion() {
|
|
2707
|
+
return this.currentVersion;
|
|
2708
|
+
}
|
|
2709
|
+
getPackageName() {
|
|
2710
|
+
return this.packageName;
|
|
2711
|
+
}
|
|
2712
|
+
async check() {
|
|
2713
|
+
const latestVersion = await this.fetchLatestVersion();
|
|
2714
|
+
const hasUpdate = compareSemver(latestVersion, this.currentVersion) > 0;
|
|
2715
|
+
logger15.info("Version check completed", {
|
|
2716
|
+
current: this.currentVersion,
|
|
2717
|
+
latest: latestVersion,
|
|
2718
|
+
hasUpdate
|
|
2719
|
+
});
|
|
2720
|
+
return {
|
|
2721
|
+
currentVersion: this.currentVersion,
|
|
2722
|
+
latestVersion,
|
|
2723
|
+
hasUpdate
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
async fetchLatestVersion() {
|
|
2727
|
+
const url = `${this.registry}/${encodeURIComponent(this.packageName)}`;
|
|
2728
|
+
const response = await fetch(url, {
|
|
2729
|
+
signal: AbortSignal.timeout(15e3),
|
|
2730
|
+
headers: { Accept: "application/json" }
|
|
2731
|
+
});
|
|
2732
|
+
if (!response.ok) {
|
|
2733
|
+
throw new Error(`Registry returned HTTP ${response.status} for ${this.packageName}`);
|
|
2734
|
+
}
|
|
2735
|
+
const data = await response.json();
|
|
2736
|
+
const latest = data["dist-tags"]?.latest;
|
|
2737
|
+
if (!latest) {
|
|
2738
|
+
throw new Error(`No latest version found in dist-tags for ${this.packageName}`);
|
|
2739
|
+
}
|
|
2740
|
+
return latest;
|
|
2741
|
+
}
|
|
2742
|
+
readPackageJson() {
|
|
2743
|
+
for (let dir = __dirname2; dir !== path6.dirname(dir); dir = path6.dirname(dir)) {
|
|
2744
|
+
const candidate = path6.join(dir, "package.json");
|
|
2745
|
+
if (existsSync(candidate)) {
|
|
2746
|
+
try {
|
|
2747
|
+
const content = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
2748
|
+
if (content.name && content.version) return content;
|
|
2749
|
+
} catch {
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
throw new Error("Could not find package.json");
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
|
|
2757
|
+
// src/updater/UpdateExecutor.ts
|
|
2758
|
+
import { execFile } from "child_process";
|
|
2759
|
+
import { cpSync, existsSync as existsSync2, rmSync } from "fs";
|
|
2760
|
+
import path7 from "path";
|
|
2761
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2762
|
+
var __dirname3 = path7.dirname(fileURLToPath3(import.meta.url));
|
|
2763
|
+
var logger16 = logger.child("UpdateExecutor");
|
|
2764
|
+
function findProjectRoot() {
|
|
2765
|
+
for (let dir = __dirname3; dir !== path7.dirname(dir); dir = path7.dirname(dir)) {
|
|
2766
|
+
if (existsSync2(path7.join(dir, "package.json"))) {
|
|
2767
|
+
return dir;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
throw new Error("Could not find project root");
|
|
2771
|
+
}
|
|
2772
|
+
function run(cmd, args, cwd, timeoutMs) {
|
|
2773
|
+
return new Promise((resolve, reject) => {
|
|
2774
|
+
execFile(cmd, args, { cwd, timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
2775
|
+
if (err) {
|
|
2776
|
+
reject(new Error(`${cmd} ${args.join(" ")} failed: ${stderr || err.message}`));
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
resolve(stdout);
|
|
2780
|
+
});
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
var UpdateExecutor = class {
|
|
2784
|
+
packageName;
|
|
2785
|
+
projectRoot;
|
|
2786
|
+
constructor(packageName) {
|
|
2787
|
+
this.packageName = packageName;
|
|
2788
|
+
this.projectRoot = findProjectRoot();
|
|
2789
|
+
}
|
|
2790
|
+
async execute(targetVersion) {
|
|
2791
|
+
const distDir = path7.join(this.projectRoot, "dist");
|
|
2792
|
+
const backupDir = path7.join(this.projectRoot, "dist.backup");
|
|
2793
|
+
logger16.info("Backing up dist directory...");
|
|
2794
|
+
if (existsSync2(backupDir)) {
|
|
2795
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
2796
|
+
}
|
|
2797
|
+
if (existsSync2(distDir)) {
|
|
2798
|
+
cpSync(distDir, backupDir, { recursive: true });
|
|
2799
|
+
}
|
|
2800
|
+
try {
|
|
2801
|
+
logger16.info("Running pnpm update...", { package: this.packageName, targetVersion });
|
|
2802
|
+
await run("pnpm", ["update", this.packageName], this.projectRoot, 12e4);
|
|
2803
|
+
logger16.info("Running pnpm build...");
|
|
2804
|
+
await run("pnpm", ["build"], this.projectRoot, 12e4);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
logger16.error("Update failed, rolling back dist...", { error: err.message });
|
|
2807
|
+
if (existsSync2(backupDir)) {
|
|
2808
|
+
if (existsSync2(distDir)) {
|
|
2809
|
+
rmSync(distDir, { recursive: true, force: true });
|
|
2810
|
+
}
|
|
2811
|
+
cpSync(backupDir, distDir, { recursive: true });
|
|
2812
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
2813
|
+
}
|
|
2814
|
+
throw err;
|
|
2815
|
+
}
|
|
2816
|
+
logger16.info("Reloading via PM2...");
|
|
2817
|
+
try {
|
|
2818
|
+
await run("pm2", ["reload", "issue-auto-finish"], this.projectRoot, 3e4);
|
|
2819
|
+
} catch (err) {
|
|
2820
|
+
logger16.warn("PM2 reload failed, the process may need manual restart", {
|
|
2821
|
+
error: err.message
|
|
2822
|
+
});
|
|
2823
|
+
throw err;
|
|
2824
|
+
}
|
|
2825
|
+
if (existsSync2(backupDir)) {
|
|
2826
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
2827
|
+
}
|
|
2828
|
+
logger16.info("Update completed successfully", { version: targetVersion });
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
|
|
2832
|
+
// src/updater/AutoUpdater.ts
|
|
2833
|
+
var logger17 = logger.child("AutoUpdater");
|
|
2834
|
+
var INITIAL_DELAY_MS = 3e4;
|
|
2835
|
+
var AutoUpdater = class {
|
|
2836
|
+
config;
|
|
2837
|
+
poller;
|
|
2838
|
+
versionChecker;
|
|
2839
|
+
updateExecutor;
|
|
2840
|
+
state = "idle";
|
|
2841
|
+
latestVersion = null;
|
|
2842
|
+
lastCheckAt = null;
|
|
2843
|
+
lastError = null;
|
|
2844
|
+
updateInProgress = false;
|
|
2845
|
+
checkTimer = null;
|
|
2846
|
+
initialTimer = null;
|
|
2847
|
+
constructor(config, poller) {
|
|
2848
|
+
this.config = config;
|
|
2849
|
+
this.poller = poller;
|
|
2850
|
+
this.versionChecker = new VersionChecker(config.autoUpdate.registry);
|
|
2851
|
+
this.updateExecutor = new UpdateExecutor(this.versionChecker.getPackageName());
|
|
2852
|
+
}
|
|
2853
|
+
start() {
|
|
2854
|
+
if (!this.config.autoUpdate.enabled) {
|
|
2855
|
+
logger17.info("Auto-update is disabled");
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
logger17.info("Auto-updater starting", {
|
|
2859
|
+
intervalMs: this.config.autoUpdate.intervalMs,
|
|
2860
|
+
registry: this.config.autoUpdate.registry
|
|
2861
|
+
});
|
|
2862
|
+
this.initialTimer = setTimeout(() => {
|
|
2863
|
+
this.initialTimer = null;
|
|
2864
|
+
this.performCheck();
|
|
2865
|
+
this.checkTimer = setInterval(() => this.performCheck(), this.config.autoUpdate.intervalMs);
|
|
2866
|
+
}, INITIAL_DELAY_MS);
|
|
2867
|
+
}
|
|
2868
|
+
stop() {
|
|
2869
|
+
if (this.initialTimer) {
|
|
2870
|
+
clearTimeout(this.initialTimer);
|
|
2871
|
+
this.initialTimer = null;
|
|
2872
|
+
}
|
|
2873
|
+
if (this.checkTimer) {
|
|
2874
|
+
clearInterval(this.checkTimer);
|
|
2875
|
+
this.checkTimer = null;
|
|
2876
|
+
}
|
|
2877
|
+
logger17.info("Auto-updater stopped");
|
|
2878
|
+
}
|
|
2879
|
+
getStatus() {
|
|
2880
|
+
return {
|
|
2881
|
+
state: this.state,
|
|
2882
|
+
enabled: this.config.autoUpdate.enabled,
|
|
2883
|
+
currentVersion: this.versionChecker.getCurrentVersion(),
|
|
2884
|
+
latestVersion: this.latestVersion,
|
|
2885
|
+
lastCheckAt: this.lastCheckAt,
|
|
2886
|
+
lastError: this.lastError
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
async checkForUpdate() {
|
|
2890
|
+
this.state = "checking";
|
|
2891
|
+
eventBus.emitTyped("update:checking", {});
|
|
2892
|
+
try {
|
|
2893
|
+
const result = await this.versionChecker.check();
|
|
2894
|
+
this.latestVersion = result.latestVersion;
|
|
2895
|
+
this.lastCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2896
|
+
this.lastError = null;
|
|
2897
|
+
if (result.hasUpdate) {
|
|
2898
|
+
this.state = "update-available";
|
|
2899
|
+
eventBus.emitTyped("update:available", {
|
|
2900
|
+
currentVersion: result.currentVersion,
|
|
2901
|
+
latestVersion: result.latestVersion
|
|
2902
|
+
});
|
|
2903
|
+
} else {
|
|
2904
|
+
this.state = "idle";
|
|
2905
|
+
}
|
|
2906
|
+
return result;
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
const message = err.message;
|
|
2909
|
+
this.lastError = message;
|
|
2910
|
+
this.state = "failed";
|
|
2911
|
+
eventBus.emitTyped("update:failed", { error: message });
|
|
2912
|
+
throw err;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
async triggerUpdate() {
|
|
2916
|
+
if (this.updateInProgress) {
|
|
2917
|
+
logger17.warn("Update already in progress, skipping");
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
this.updateInProgress = true;
|
|
2921
|
+
try {
|
|
2922
|
+
if (!this.latestVersion) {
|
|
2923
|
+
const result = await this.checkForUpdate();
|
|
2924
|
+
if (!result.hasUpdate) {
|
|
2925
|
+
logger17.info("No update available");
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
const targetVersion = this.latestVersion;
|
|
2930
|
+
this.state = "draining";
|
|
2931
|
+
logger17.info("Draining active issues before update...");
|
|
2932
|
+
this.poller.pauseDiscovery();
|
|
2933
|
+
const drainStart = Date.now();
|
|
2934
|
+
const drainTimeout = this.config.autoUpdate.drainTimeoutMs;
|
|
2935
|
+
while (this.poller.getActiveCount() > 0 && Date.now() - drainStart < drainTimeout) {
|
|
2936
|
+
logger17.info("Waiting for active issues to complete...", {
|
|
2937
|
+
active: this.poller.getActiveCount()
|
|
2938
|
+
});
|
|
2939
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
2940
|
+
}
|
|
2941
|
+
if (this.poller.getActiveCount() > 0) {
|
|
2942
|
+
logger17.warn("Drain timeout reached, proceeding with update", {
|
|
2943
|
+
active: this.poller.getActiveCount()
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
this.state = "updating";
|
|
2947
|
+
eventBus.emitTyped("update:downloading", { version: targetVersion });
|
|
2948
|
+
logger17.info("Executing update...", { targetVersion });
|
|
2949
|
+
await this.updateExecutor.execute(targetVersion);
|
|
2950
|
+
this.state = "completed";
|
|
2951
|
+
eventBus.emitTyped("update:completed", { version: targetVersion });
|
|
2952
|
+
logger17.info("Update completed", { version: targetVersion });
|
|
2953
|
+
} catch (err) {
|
|
2954
|
+
const message = err.message;
|
|
2955
|
+
this.lastError = message;
|
|
2956
|
+
this.state = "failed";
|
|
2957
|
+
eventBus.emitTyped("update:failed", { error: message });
|
|
2958
|
+
logger17.error("Update failed", { error: message });
|
|
2959
|
+
this.poller.resumeDiscovery();
|
|
2960
|
+
} finally {
|
|
2961
|
+
this.updateInProgress = false;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
async performCheck() {
|
|
2965
|
+
if (this.updateInProgress) return;
|
|
2966
|
+
try {
|
|
2967
|
+
const result = await this.checkForUpdate();
|
|
2968
|
+
if (result.hasUpdate) {
|
|
2969
|
+
logger17.info("New version available, starting update", {
|
|
2970
|
+
current: result.currentVersion,
|
|
2971
|
+
latest: result.latestVersion
|
|
2972
|
+
});
|
|
2973
|
+
await this.triggerUpdate();
|
|
2974
|
+
}
|
|
2975
|
+
} catch (err) {
|
|
2976
|
+
logger17.warn("Periodic version check failed", { error: err.message });
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
|
|
2981
|
+
// src/index.ts
|
|
2982
|
+
import path8 from "path";
|
|
2983
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2984
|
+
var __dirname4 = path8.dirname(fileURLToPath4(import.meta.url));
|
|
1943
2985
|
async function main() {
|
|
1944
2986
|
logger.info("Issue Auto-Finish service starting...");
|
|
1945
2987
|
const config = loadConfig();
|
|
1946
2988
|
setLocale(config.locale);
|
|
2989
|
+
loadKnowledge(config.knowledgePath);
|
|
1947
2990
|
const allAiPhaseNames = [
|
|
1948
2991
|
...CLASSIC_PIPELINE.phases.filter((p) => p.kind === "ai").map((p) => p.name),
|
|
1949
2992
|
...PLAN_MODE_PIPELINE.phases.filter((p) => p.kind === "ai").map((p) => p.name)
|
|
@@ -1952,7 +2995,7 @@ async function main() {
|
|
|
1952
2995
|
const gongfeng = new GongfengClient(config.gongfeng);
|
|
1953
2996
|
const git = new GitOperations(config.project.gitRootDir);
|
|
1954
2997
|
const aiRunner = createAIRunner(config.ai);
|
|
1955
|
-
const dataDir =
|
|
2998
|
+
const dataDir = path8.resolve(__dirname4, "../data");
|
|
1956
2999
|
const tracker = new IssueTracker(dataDir);
|
|
1957
3000
|
const supplementStore = new SupplementStore(dataDir);
|
|
1958
3001
|
const agentLogStore = new AgentLogStore(dataDir);
|
|
@@ -1960,25 +3003,53 @@ async function main() {
|
|
|
1960
3003
|
const orchestrator = new PipelineOrchestrator(config, gongfeng, git, aiRunner, tracker, supplementStore);
|
|
1961
3004
|
const poller = new IssuePoller(config, gongfeng, tracker, orchestrator);
|
|
1962
3005
|
let webServer = null;
|
|
3006
|
+
const autoUpdater = new AutoUpdater(config, poller);
|
|
1963
3007
|
if (config.web.enabled) {
|
|
1964
|
-
webServer = new WebServer({ tracker, config, agentLogStore, orchestrator, gongfeng, supplementStore, mainGit: git });
|
|
3008
|
+
webServer = new WebServer({ tracker, config, agentLogStore, orchestrator, gongfeng, supplementStore, mainGit: git, autoUpdater, poller });
|
|
1965
3009
|
await webServer.start();
|
|
1966
3010
|
}
|
|
1967
3011
|
let webhookServer = null;
|
|
1968
3012
|
if (config.webhook.enabled) {
|
|
1969
|
-
webhookServer = new WebhookServer({ tracker, config, orchestrator, gongfeng, supplementStore });
|
|
3013
|
+
webhookServer = new WebhookServer({ tracker, config, orchestrator, gongfeng, supplementStore, poller });
|
|
1970
3014
|
await webhookServer.start();
|
|
1971
3015
|
}
|
|
1972
|
-
const
|
|
1973
|
-
|
|
3016
|
+
const recoveredCount = tracker.recoverInterruptedIssues();
|
|
3017
|
+
if (recoveredCount > 0) {
|
|
3018
|
+
logger.info("Recovered interrupted issues on startup", { count: recoveredCount });
|
|
3019
|
+
}
|
|
3020
|
+
await orchestrator.cleanupStaleState();
|
|
3021
|
+
let shutdownInProgress = false;
|
|
3022
|
+
const shutdown = async () => {
|
|
3023
|
+
if (shutdownInProgress) return;
|
|
3024
|
+
shutdownInProgress = true;
|
|
3025
|
+
logger.info("Graceful shutdown initiated");
|
|
3026
|
+
setShuttingDown();
|
|
3027
|
+
autoUpdater.stop();
|
|
1974
3028
|
poller.stop();
|
|
3029
|
+
aiRunner.killAll();
|
|
3030
|
+
orchestrator.getDevServerManager().stopAll();
|
|
3031
|
+
const drainStart = Date.now();
|
|
3032
|
+
const DRAIN_TIMEOUT_MS = 5e3;
|
|
3033
|
+
while (poller.getActiveCount() > 0 && Date.now() - drainStart < DRAIN_TIMEOUT_MS) {
|
|
3034
|
+
logger.info("Waiting for active issues to drain...", { active: poller.getActiveCount() });
|
|
3035
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3036
|
+
}
|
|
3037
|
+
if (poller.getActiveCount() > 0) {
|
|
3038
|
+
logger.warn("Drain timeout reached, proceeding with shutdown", { active: poller.getActiveCount() });
|
|
3039
|
+
}
|
|
1975
3040
|
webServer?.stop();
|
|
1976
3041
|
webhookServer?.stop();
|
|
3042
|
+
logger.info("Graceful shutdown complete");
|
|
1977
3043
|
process.exit(0);
|
|
1978
3044
|
};
|
|
1979
|
-
process.on("SIGINT",
|
|
1980
|
-
|
|
3045
|
+
process.on("SIGINT", () => {
|
|
3046
|
+
shutdown();
|
|
3047
|
+
});
|
|
3048
|
+
process.on("SIGTERM", () => {
|
|
3049
|
+
shutdown();
|
|
3050
|
+
});
|
|
1981
3051
|
poller.start();
|
|
3052
|
+
autoUpdater.start();
|
|
1982
3053
|
logger.info("Issue Auto-Finish service started", {
|
|
1983
3054
|
projectPath: config.gongfeng.projectPath,
|
|
1984
3055
|
workDir: config.project.workDir,
|
|
@@ -1992,4 +3063,4 @@ async function main() {
|
|
|
1992
3063
|
export {
|
|
1993
3064
|
main
|
|
1994
3065
|
};
|
|
1995
|
-
//# sourceMappingURL=chunk-
|
|
3066
|
+
//# sourceMappingURL=chunk-C6ZJVIPZ.js.map
|