plugin-build-guide-block 1.1.4 → 1.1.6
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/client/index.js +2 -2
- package/dist/externalVersion.js +7 -7
- package/dist/node_modules/sanitize-html/index.js +1 -1
- package/dist/node_modules/sanitize-html/package.json +1 -1
- package/dist/server/actions/build.js +682 -83
- package/dist/server/collections/ai-build-guide-spaces.js +20 -0
- package/dist/server/plugin.js +21 -19
- package/dist/server/tools/search-guides.js +41 -30
- package/package.json +2 -2
- package/src/client/components/BuildButton.tsx +20 -9
- package/src/server/actions/build.ts +768 -86
- package/src/server/collections/ai-build-guide-spaces.ts +77 -57
- package/src/server/plugin.ts +170 -163
- package/src/server/tools/search-guides.ts +113 -95
- package/dist/client/UserGuideBlock.d.ts +0 -2
- package/dist/client/UserGuideBlockInitializer.d.ts +0 -2
- package/dist/client/UserGuideBlockProvider.d.ts +0 -2
- package/dist/client/UserGuideManager.d.ts +0 -2
- package/dist/client/components/BuildButton.d.ts +0 -2
- package/dist/client/components/LLMServiceSelect.d.ts +0 -2
- package/dist/client/components/ModelSelect.d.ts +0 -2
- package/dist/client/components/SpaceSelect.d.ts +0 -2
- package/dist/client/components/StatusTag.d.ts +0 -2
- package/dist/client/index.d.ts +0 -1
- package/dist/client/locale.d.ts +0 -3
- package/dist/client/models/UserGuideBlockModel.d.ts +0 -9
- package/dist/client/models/index.d.ts +0 -9
- package/dist/client/plugin.d.ts +0 -5
- package/dist/client/schemaSettings.d.ts +0 -2
- package/dist/client/schemas/spacesSchema.d.ts +0 -437
- package/dist/index.d.ts +0 -2
- package/dist/locale/namespace.d.ts +0 -6
- package/dist/server/actions/build.d.ts +0 -2
- package/dist/server/actions/getHtml.d.ts +0 -2
- package/dist/server/actions/getMarkdown.d.ts +0 -2
- package/dist/server/collections/ai-build-guide-pages.d.ts +0 -2
- package/dist/server/collections/ai-build-guide-spaces.d.ts +0 -2
- package/dist/server/index.d.ts +0 -2
- package/dist/server/plugin.d.ts +0 -16
- package/dist/server/tools/index.d.ts +0 -1
- package/dist/server/tools/search-guides.d.ts +0 -28
|
@@ -36,7 +36,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var build_exports = {};
|
|
38
38
|
__export(build_exports, {
|
|
39
|
-
|
|
39
|
+
WORKER_JOB_BUILD_GUIDE_PROCESS: () => WORKER_JOB_BUILD_GUIDE_PROCESS,
|
|
40
|
+
build: () => build,
|
|
41
|
+
recoverInterruptedBuilds: () => recoverInterruptedBuilds,
|
|
42
|
+
registerBuildGuideQueue: () => registerBuildGuideQueue,
|
|
43
|
+
unregisterBuildGuideQueue: () => unregisterBuildGuideQueue
|
|
40
44
|
});
|
|
41
45
|
module.exports = __toCommonJS(build_exports);
|
|
42
46
|
var import_sanitize_html = __toESM(require("sanitize-html"));
|
|
@@ -50,6 +54,66 @@ const MAX_SOURCE_CHARS = 9e4;
|
|
|
50
54
|
const MIN_CHAPTERS = 1;
|
|
51
55
|
const MAX_CHAPTERS = 12;
|
|
52
56
|
const DEFAULT_TARGET_CHAPTERS = 5;
|
|
57
|
+
const WORKER_JOB_BUILD_GUIDE_PROCESS = "build-guide:process";
|
|
58
|
+
const BUILD_GUIDE_QUEUE_CHANNEL = "plugin-build-guide-block.build";
|
|
59
|
+
const BUILD_GUIDE_QUEUE_CONCURRENCY = Math.max(
|
|
60
|
+
1,
|
|
61
|
+
Number.parseInt(process.env.BUILD_GUIDE_QUEUE_CONCURRENCY || process.env.BUILD_GUIDE_MAX_CONCURRENCY || "1", 10) || 1
|
|
62
|
+
);
|
|
63
|
+
const BUILD_GUIDE_QUEUE_TIMEOUT_MS = Math.max(
|
|
64
|
+
6e4,
|
|
65
|
+
Number.parseInt(process.env.BUILD_GUIDE_QUEUE_TIMEOUT_MS || "", 10) || 30 * 60 * 1e3
|
|
66
|
+
);
|
|
67
|
+
const BUILD_GUIDE_QUEUE_POLL_INTERVAL_MS = Math.max(
|
|
68
|
+
1e3,
|
|
69
|
+
Number.parseInt(process.env.BUILD_GUIDE_QUEUE_POLL_INTERVAL_MS || "", 10) || 5e3
|
|
70
|
+
);
|
|
71
|
+
const BUILD_GUIDE_QUEUE_WAKE_CHANNEL = "plugin-build-guide-block.build.wake";
|
|
72
|
+
const BUILD_GUIDE_QUEUE_REDIS_CONNECTION = "plugin-build-guide-block.build.queue";
|
|
73
|
+
const BUILD_TRIGGER_LOCK_TTL_MS = 3e4;
|
|
74
|
+
const BUILD_RUN_LOCK_TTL_MS = Math.max(
|
|
75
|
+
6e4,
|
|
76
|
+
Number.parseInt(process.env.BUILD_GUIDE_RUN_LOCK_TTL_MS || "", 10) || 24 * 60 * 60 * 1e3
|
|
77
|
+
);
|
|
78
|
+
const BUILD_HEARTBEAT_INTERVAL_MS = Math.max(
|
|
79
|
+
5e3,
|
|
80
|
+
Number.parseInt(process.env.BUILD_GUIDE_HEARTBEAT_MS || "", 10) || 3e4
|
|
81
|
+
);
|
|
82
|
+
const BUILD_STALE_MS = Math.max(
|
|
83
|
+
BUILD_HEARTBEAT_INTERVAL_MS * 2,
|
|
84
|
+
Number.parseInt(process.env.BUILD_GUIDE_STALE_MS || "", 10) || 12e4
|
|
85
|
+
);
|
|
86
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
87
|
+
".txt",
|
|
88
|
+
".md",
|
|
89
|
+
".markdown",
|
|
90
|
+
".csv",
|
|
91
|
+
".tsv",
|
|
92
|
+
".json",
|
|
93
|
+
".xml",
|
|
94
|
+
".html",
|
|
95
|
+
".htm",
|
|
96
|
+
".yaml",
|
|
97
|
+
".yml",
|
|
98
|
+
".log",
|
|
99
|
+
".sql",
|
|
100
|
+
".js",
|
|
101
|
+
".jsx",
|
|
102
|
+
".ts",
|
|
103
|
+
".tsx",
|
|
104
|
+
".css",
|
|
105
|
+
".scss",
|
|
106
|
+
".less"
|
|
107
|
+
]);
|
|
108
|
+
const TEXT_MIMETYPES = /* @__PURE__ */ new Set([
|
|
109
|
+
"application/json",
|
|
110
|
+
"application/xml",
|
|
111
|
+
"application/yaml",
|
|
112
|
+
"application/x-yaml",
|
|
113
|
+
"application/javascript",
|
|
114
|
+
"application/typescript",
|
|
115
|
+
"image/svg+xml"
|
|
116
|
+
]);
|
|
53
117
|
const SANITIZE_OPTIONS = {
|
|
54
118
|
allowedTags: [
|
|
55
119
|
"div",
|
|
@@ -94,12 +158,93 @@ const SANITIZE_OPTIONS = {
|
|
|
94
158
|
}
|
|
95
159
|
}
|
|
96
160
|
};
|
|
161
|
+
let buildQueueTimer = null;
|
|
162
|
+
let buildQueueKickTimer = null;
|
|
163
|
+
let buildQueueProcessing = false;
|
|
164
|
+
let buildQueueWakeHandler = null;
|
|
165
|
+
class StaleBuildRunError extends Error {
|
|
166
|
+
constructor(spaceId, runId) {
|
|
167
|
+
super(`Build run ${runId} for space ${spaceId} is no longer current`);
|
|
168
|
+
this.name = "StaleBuildRunError";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
97
171
|
function clampChapterCount(value) {
|
|
98
172
|
const count = Number(value);
|
|
99
173
|
if (!Number.isFinite(count)) return DEFAULT_TARGET_CHAPTERS;
|
|
100
174
|
return Math.max(MIN_CHAPTERS, Math.min(MAX_CHAPTERS, Math.round(count)));
|
|
101
175
|
}
|
|
102
|
-
|
|
176
|
+
function resolveExtname(file) {
|
|
177
|
+
const explicit = file == null ? void 0 : file.extname;
|
|
178
|
+
if (typeof explicit === "string" && explicit) return explicit.toLowerCase();
|
|
179
|
+
const name = (file == null ? void 0 : file.filename) || (file == null ? void 0 : file.name) || "";
|
|
180
|
+
const index = String(name).lastIndexOf(".");
|
|
181
|
+
return index >= 0 ? String(name).slice(index).toLowerCase() : "";
|
|
182
|
+
}
|
|
183
|
+
function isTextDocument(file) {
|
|
184
|
+
const mimetype = String((file == null ? void 0 : file.mimetype) || "").toLowerCase();
|
|
185
|
+
if (mimetype.startsWith("text/")) return true;
|
|
186
|
+
if (TEXT_MIMETYPES.has(mimetype)) return true;
|
|
187
|
+
return TEXT_EXTENSIONS.has(resolveExtname(file));
|
|
188
|
+
}
|
|
189
|
+
function createParserContext(app) {
|
|
190
|
+
const headers = { "x-timezone": "+00:00", "x-locale": "en-US" };
|
|
191
|
+
return {
|
|
192
|
+
app,
|
|
193
|
+
db: app.db,
|
|
194
|
+
log: app.log || app.logger || console,
|
|
195
|
+
logger: app.logger || app.log || console,
|
|
196
|
+
state: {},
|
|
197
|
+
auth: {},
|
|
198
|
+
req: { headers },
|
|
199
|
+
request: { headers },
|
|
200
|
+
get(name) {
|
|
201
|
+
return headers[String(name).toLowerCase()] || "";
|
|
202
|
+
},
|
|
203
|
+
getCurrentLocale() {
|
|
204
|
+
return "en-US";
|
|
205
|
+
},
|
|
206
|
+
t(key) {
|
|
207
|
+
return key;
|
|
208
|
+
},
|
|
209
|
+
i18n: {
|
|
210
|
+
t(key) {
|
|
211
|
+
return key;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function extractParsedText(value) {
|
|
217
|
+
if (!value) return "";
|
|
218
|
+
if (typeof value === "string") return value;
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
return value.map(extractParsedText).filter(Boolean).join("\n");
|
|
221
|
+
}
|
|
222
|
+
if (typeof value === "object") {
|
|
223
|
+
if (typeof value.text === "string") return value.text;
|
|
224
|
+
if (typeof value.content === "string") return value.content;
|
|
225
|
+
if (value.content) return extractParsedText(value.content);
|
|
226
|
+
if (value.message) return extractParsedText(value.message);
|
|
227
|
+
}
|
|
228
|
+
return "";
|
|
229
|
+
}
|
|
230
|
+
function getDocumentParserPlugin(app) {
|
|
231
|
+
var _a, _b, _c, _d;
|
|
232
|
+
return ((_b = (_a = app.pm) == null ? void 0 : _a.get) == null ? void 0 : _b.call(_a, "@nocobase/plugin-document-parser")) || ((_d = (_c = app.pm) == null ? void 0 : _c.get) == null ? void 0 : _d.call(_c, "plugin-document-parser")) || null;
|
|
233
|
+
}
|
|
234
|
+
function unsupportedDocumentMessage(file) {
|
|
235
|
+
const filename = (file == null ? void 0 : file.filename) || (file == null ? void 0 : file.name) || (file == null ? void 0 : file.id) || "document";
|
|
236
|
+
const type = (file == null ? void 0 : file.mimetype) || resolveExtname(file) || "unknown type";
|
|
237
|
+
return `[Unsupported document type: ${filename} (${type}). Install or enable plugin-document-parser/MarkItDown to extract this file.]`;
|
|
238
|
+
}
|
|
239
|
+
async function fetchTextFileContent(app, file) {
|
|
240
|
+
if (!isTextDocument(file)) {
|
|
241
|
+
return unsupportedDocumentMessage(file);
|
|
242
|
+
}
|
|
243
|
+
const docParserPlugin = getDocumentParserPlugin(app);
|
|
244
|
+
if (docParserPlugin == null ? void 0 : docParserPlugin.fetchFileBuffer) {
|
|
245
|
+
const { buffer } = await docParserPlugin.fetchFileBuffer(createParserContext(app), file);
|
|
246
|
+
return buffer.toString("utf8");
|
|
247
|
+
}
|
|
103
248
|
const fileManager = app.pm.get("file-manager");
|
|
104
249
|
if (!fileManager) return "";
|
|
105
250
|
const url = await fileManager.getFileURL(file);
|
|
@@ -119,6 +264,42 @@ async function fetchFileContent(app, file) {
|
|
|
119
264
|
return `[Failed to read document: ${file.filename}]`;
|
|
120
265
|
}
|
|
121
266
|
}
|
|
267
|
+
async function parseWithDocumentParser(app, file) {
|
|
268
|
+
var _a, _b, _c, _d, _e;
|
|
269
|
+
const docParserPlugin = getDocumentParserPlugin(app);
|
|
270
|
+
if (!docParserPlugin) return "";
|
|
271
|
+
const parserCtx = createParserContext(app);
|
|
272
|
+
const defaultParser = async () => ({
|
|
273
|
+
placement: "contentBlocks",
|
|
274
|
+
content: {
|
|
275
|
+
type: "text",
|
|
276
|
+
text: await fetchTextFileContent(app, file)
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
try {
|
|
280
|
+
if ((_a = docParserPlugin.parseRouter) == null ? void 0 : _a.route) {
|
|
281
|
+
const result = await docParserPlugin.parseRouter.route(parserCtx, file, defaultParser);
|
|
282
|
+
const text = extractParsedText(result == null ? void 0 : result.content);
|
|
283
|
+
if (text && !text.startsWith("[Unsupported document type:")) {
|
|
284
|
+
return text;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if ((_b = docParserPlugin.internalParserRegistry) == null ? void 0 : _b.parse) {
|
|
288
|
+
const result = await docParserPlugin.internalParserRegistry.parse(file, parserCtx);
|
|
289
|
+
if ((result == null ? void 0 : result.handled) && ((_c = result == null ? void 0 : result.text) == null ? void 0 : _c.trim())) {
|
|
290
|
+
return result.text;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} catch (err) {
|
|
294
|
+
(_e = (_d = app.log) == null ? void 0 : _d.warn) == null ? void 0 : _e.call(_d, `[plugin-build-guide-block] Document parser failed for ${(file == null ? void 0 : file.filename) || (file == null ? void 0 : file.id)}`, err);
|
|
295
|
+
}
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
async function fetchFileContent(app, file) {
|
|
299
|
+
const parsedText = await parseWithDocumentParser(app, file);
|
|
300
|
+
if (parsedText) return parsedText;
|
|
301
|
+
return fetchTextFileContent(app, file);
|
|
302
|
+
}
|
|
122
303
|
function toPlainText(value) {
|
|
123
304
|
if (typeof value === "string") return value;
|
|
124
305
|
if (Array.isArray(value)) {
|
|
@@ -319,59 +500,114 @@ ${content}
|
|
|
319
500
|
);
|
|
320
501
|
return texts.join("\n");
|
|
321
502
|
}
|
|
322
|
-
|
|
503
|
+
function getSpaceModel(app) {
|
|
504
|
+
return app.db.getModel("aiBuildGuideSpaces");
|
|
505
|
+
}
|
|
506
|
+
async function updateSpaceForRun(app, run, values, optional = false) {
|
|
507
|
+
const SpaceModel = getSpaceModel(app);
|
|
508
|
+
const [affected] = await SpaceModel.update(values, {
|
|
509
|
+
where: {
|
|
510
|
+
id: run.spaceId,
|
|
511
|
+
buildRunId: run.runId
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
if (!affected && !optional) {
|
|
515
|
+
throw new StaleBuildRunError(run.spaceId, run.runId);
|
|
516
|
+
}
|
|
517
|
+
return affected > 0;
|
|
518
|
+
}
|
|
519
|
+
async function claimBuildRun(app, run, workerId) {
|
|
520
|
+
const now = /* @__PURE__ */ new Date();
|
|
521
|
+
const SpaceModel = getSpaceModel(app);
|
|
522
|
+
const [affected] = await SpaceModel.update(
|
|
523
|
+
{
|
|
524
|
+
buildPhase: "running",
|
|
525
|
+
buildStartedAt: now,
|
|
526
|
+
buildHeartbeatAt: now,
|
|
527
|
+
buildWorkerId: workerId
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
where: {
|
|
531
|
+
id: run.spaceId,
|
|
532
|
+
status: "building",
|
|
533
|
+
buildPhase: "queued",
|
|
534
|
+
buildRunId: run.runId
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
return affected > 0;
|
|
539
|
+
}
|
|
540
|
+
function getBuildWorkerId(app) {
|
|
541
|
+
return [
|
|
542
|
+
process.env.HOSTNAME || process.env.COMPUTERNAME || "worker",
|
|
543
|
+
app.name || "app",
|
|
544
|
+
app.instanceId || "0",
|
|
545
|
+
process.pid
|
|
546
|
+
].join(":");
|
|
547
|
+
}
|
|
548
|
+
function startBuildHeartbeat(app, run) {
|
|
549
|
+
const timer = setInterval(() => {
|
|
550
|
+
updateSpaceForRun(
|
|
551
|
+
app,
|
|
552
|
+
run,
|
|
553
|
+
{
|
|
554
|
+
buildHeartbeatAt: /* @__PURE__ */ new Date()
|
|
555
|
+
},
|
|
556
|
+
true
|
|
557
|
+
).catch((error) => {
|
|
558
|
+
var _a, _b;
|
|
559
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, `[plugin-build-guide-block] Failed to update heartbeat for build ${run.runId}`, error);
|
|
560
|
+
});
|
|
561
|
+
}, BUILD_HEARTBEAT_INTERVAL_MS);
|
|
562
|
+
return () => clearInterval(timer);
|
|
563
|
+
}
|
|
564
|
+
async function runBuild(app, db, run) {
|
|
323
565
|
const spaceRepo = db.getRepository("aiBuildGuideSpaces");
|
|
324
566
|
const pageRepo = db.getRepository("aiBuildGuidePages");
|
|
325
|
-
const space = await spaceRepo.findById(
|
|
567
|
+
const space = await spaceRepo.findById(run.spaceId);
|
|
326
568
|
if (!space) {
|
|
327
569
|
throw new Error("Space not found");
|
|
328
570
|
}
|
|
571
|
+
if (space.get("buildRunId") !== run.runId) {
|
|
572
|
+
throw new StaleBuildRunError(run.spaceId, run.runId);
|
|
573
|
+
}
|
|
329
574
|
const { llmService, model } = space.get();
|
|
330
575
|
if (!llmService || !model) {
|
|
331
576
|
throw new Error("LLM Service or model is missing in space configuration");
|
|
332
577
|
}
|
|
333
578
|
await pageRepo.destroy({
|
|
334
579
|
filter: {
|
|
335
|
-
spaceId:
|
|
580
|
+
spaceId: run.spaceId
|
|
336
581
|
}
|
|
337
582
|
});
|
|
338
|
-
await
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
planJson: null,
|
|
346
|
-
pageCount: 0
|
|
347
|
-
}
|
|
583
|
+
await updateSpaceForRun(app, run, {
|
|
584
|
+
buildPhase: "reading",
|
|
585
|
+
buildLog: "Reading source documents",
|
|
586
|
+
generatedHtml: null,
|
|
587
|
+
generatedMarkdown: null,
|
|
588
|
+
planJson: null,
|
|
589
|
+
pageCount: 0
|
|
348
590
|
});
|
|
349
591
|
const documentsText = await readDocuments(app, space);
|
|
350
592
|
const sourceHash = import_crypto.default.createHash("sha256").update(documentsText).digest("hex");
|
|
351
593
|
const provider = await getLLMProvider(app, llmService, model);
|
|
352
|
-
await
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
buildLog: "Creating guide breakdown plan",
|
|
357
|
-
sourceHash
|
|
358
|
-
}
|
|
594
|
+
await updateSpaceForRun(app, run, {
|
|
595
|
+
buildPhase: "planning",
|
|
596
|
+
buildLog: "Creating guide breakdown plan",
|
|
597
|
+
sourceHash
|
|
359
598
|
});
|
|
360
599
|
const plan = await buildPlan(provider, space, documentsText);
|
|
361
|
-
await
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
buildPhase: "building_pages",
|
|
367
|
-
buildLog: `Plan created with ${plan.chapters.length} chapters`
|
|
368
|
-
}
|
|
600
|
+
await updateSpaceForRun(app, run, {
|
|
601
|
+
planJson: plan,
|
|
602
|
+
pageCount: plan.chapters.length,
|
|
603
|
+
buildPhase: "building_pages",
|
|
604
|
+
buildLog: `Plan created with ${plan.chapters.length} chapters`
|
|
369
605
|
});
|
|
370
606
|
const pageRecords = [];
|
|
371
607
|
for (const [index, chapter] of plan.chapters.entries()) {
|
|
372
608
|
const page = await pageRepo.create({
|
|
373
609
|
values: {
|
|
374
|
-
spaceId:
|
|
610
|
+
spaceId: run.spaceId,
|
|
375
611
|
sort: index + 1,
|
|
376
612
|
title: chapter.title,
|
|
377
613
|
slug: slugify(chapter.title, `chapter-${index + 1}`),
|
|
@@ -392,12 +628,9 @@ async function runBuild(app, db, filterByTk) {
|
|
|
392
628
|
buildLog: "Building chapter with LLM"
|
|
393
629
|
}
|
|
394
630
|
});
|
|
395
|
-
await
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
buildPhase: "building_pages",
|
|
399
|
-
buildLog: `Building chapter ${index + 1}/${pageRecords.length}: ${chapter.title}`
|
|
400
|
-
}
|
|
631
|
+
await updateSpaceForRun(app, run, {
|
|
632
|
+
buildPhase: "building_pages",
|
|
633
|
+
buildLog: `Building chapter ${index + 1}/${pageRecords.length}: ${chapter.title}`
|
|
401
634
|
});
|
|
402
635
|
try {
|
|
403
636
|
const markdown = await buildPageMarkdown(provider, space, plan, chapter, documentsText);
|
|
@@ -424,76 +657,442 @@ async function runBuild(app, db, filterByTk) {
|
|
|
424
657
|
}
|
|
425
658
|
const completedPages = await pageRepo.find({
|
|
426
659
|
filter: {
|
|
427
|
-
spaceId:
|
|
660
|
+
spaceId: run.spaceId,
|
|
428
661
|
status: "completed"
|
|
429
662
|
},
|
|
430
663
|
sort: ["sort"]
|
|
431
664
|
});
|
|
432
665
|
const combinedMarkdown = completedPages.map((page) => page.get("generatedMarkdown")).filter(Boolean).join("\n\n---\n\n");
|
|
433
666
|
const combinedHtml = await markdownToCleanHtml(combinedMarkdown);
|
|
434
|
-
await
|
|
435
|
-
|
|
667
|
+
await updateSpaceForRun(app, run, {
|
|
668
|
+
status: "completed",
|
|
669
|
+
buildPhase: "completed",
|
|
670
|
+
buildLog: `Built ${completedPages.length} chapters successfully`,
|
|
671
|
+
generatedMarkdown: combinedMarkdown,
|
|
672
|
+
generatedHtml: combinedHtml,
|
|
673
|
+
buildHeartbeatAt: /* @__PURE__ */ new Date()
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
function isBuildGuideWorker(app) {
|
|
677
|
+
const workerMode = process.env.WORKER_MODE || "";
|
|
678
|
+
return app.serving(WORKER_JOB_BUILD_GUIDE_PROCESS) || workerMode === "worker" || workerMode === "task" || process.env.APP_ROLE === "worker";
|
|
679
|
+
}
|
|
680
|
+
function clearLocalBuildMemoryQueue(app) {
|
|
681
|
+
var _a, _b, _c, _d, _e;
|
|
682
|
+
const eventQueue = app.eventQueue;
|
|
683
|
+
const adapter = eventQueue == null ? void 0 : eventQueue.adapter;
|
|
684
|
+
const fullChannel = (_a = eventQueue == null ? void 0 : eventQueue.getFullChannel) == null ? void 0 : _a.call(eventQueue, BUILD_GUIDE_QUEUE_CHANNEL);
|
|
685
|
+
const queue = fullChannel ? (_c = (_b = adapter == null ? void 0 : adapter.queues) == null ? void 0 : _b.get) == null ? void 0 : _c.call(_b, fullChannel) : null;
|
|
686
|
+
if (!(queue == null ? void 0 : queue.length)) return;
|
|
687
|
+
adapter.queues.set(fullChannel, []);
|
|
688
|
+
(_e = (_d = app.log) == null ? void 0 : _d.warn) == null ? void 0 : _e.call(
|
|
689
|
+
_d,
|
|
690
|
+
`[plugin-build-guide-block] Cleared ${queue.length} stale local memory message(s) on non-worker node; queued DB builds will be picked up by workers`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function getBuildQueueRedisKey(app) {
|
|
694
|
+
const appName = app.name || process.env.APP_NAME || "main";
|
|
695
|
+
return `${appName}:plugin-build-guide-block:build:queue`;
|
|
696
|
+
}
|
|
697
|
+
async function getBuildQueueRedis(app) {
|
|
698
|
+
var _a, _b;
|
|
699
|
+
const manager = app.redisConnectionManager;
|
|
700
|
+
if (!(manager == null ? void 0 : manager.getConnectionSync)) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
const connectionString = process.env.QUEUE_ADAPTER_REDIS_URL || process.env.REDIS_URL;
|
|
705
|
+
return await manager.getConnectionSync(
|
|
706
|
+
BUILD_GUIDE_QUEUE_REDIS_CONNECTION,
|
|
707
|
+
connectionString ? { connectionString } : void 0
|
|
708
|
+
);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(
|
|
711
|
+
_a,
|
|
712
|
+
`[plugin-build-guide-block] Redis queue unavailable; DB polling fallback active: ${(error == null ? void 0 : error.message) || error}`
|
|
713
|
+
);
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function enqueueBuildToRedis(app, message) {
|
|
718
|
+
var _a, _b, _c, _d;
|
|
719
|
+
const redis = await getBuildQueueRedis(app);
|
|
720
|
+
if (!redis) return false;
|
|
721
|
+
try {
|
|
722
|
+
await redis.sendCommand(["RPUSH", getBuildQueueRedisKey(app), JSON.stringify(message)]);
|
|
723
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(
|
|
724
|
+
_a,
|
|
725
|
+
`[plugin-build-guide-block] Enqueued build ${message.runId} for space "${message.spaceId}" to Redis`
|
|
726
|
+
);
|
|
727
|
+
return true;
|
|
728
|
+
} catch (error) {
|
|
729
|
+
(_d = (_c = app.log) == null ? void 0 : _c.warn) == null ? void 0 : _d.call(_c, `[plugin-build-guide-block] Failed to enqueue build to Redis; DB polling fallback active`, error);
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function publishBuildQueueWake(app, message) {
|
|
734
|
+
var _a, _b, _c, _d;
|
|
735
|
+
try {
|
|
736
|
+
await ((_b = (_a = app.pubSubManager) == null ? void 0 : _a.publish) == null ? void 0 : _b.call(
|
|
737
|
+
_a,
|
|
738
|
+
BUILD_GUIDE_QUEUE_WAKE_CHANNEL,
|
|
739
|
+
{ spaceId: message == null ? void 0 : message.spaceId, runId: message == null ? void 0 : message.runId },
|
|
740
|
+
{ skipSelf: !isBuildGuideWorker(app) }
|
|
741
|
+
));
|
|
742
|
+
} catch (error) {
|
|
743
|
+
(_d = (_c = app.log) == null ? void 0 : _c.debug) == null ? void 0 : _d.call(_c, `[plugin-build-guide-block] Wake publish skipped: ${(error == null ? void 0 : error.message) || error}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function startBuildGuideQueueProcessor(app) {
|
|
747
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
748
|
+
if (!isBuildGuideWorker(app)) {
|
|
749
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, "[plugin-build-guide-block] Build queue processor disabled on non-worker node");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (buildQueueTimer) return;
|
|
753
|
+
buildQueueWakeHandler = async () => {
|
|
754
|
+
scheduleBuildQueueTick(app, 0);
|
|
755
|
+
};
|
|
756
|
+
const subscribe = (_d = (_c = app.pubSubManager) == null ? void 0 : _c.subscribe) == null ? void 0 : _d.call(_c, BUILD_GUIDE_QUEUE_WAKE_CHANNEL, buildQueueWakeHandler);
|
|
757
|
+
if (subscribe == null ? void 0 : subscribe.catch) {
|
|
758
|
+
subscribe.catch((error) => {
|
|
759
|
+
var _a2, _b2;
|
|
760
|
+
(_b2 = (_a2 = app.log) == null ? void 0 : _a2.debug) == null ? void 0 : _b2.call(_a2, `[plugin-build-guide-block] Wake subscribe skipped: ${(error == null ? void 0 : error.message) || error}`);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
buildQueueTimer = setInterval(() => scheduleBuildQueueTick(app, 0), BUILD_GUIDE_QUEUE_POLL_INTERVAL_MS);
|
|
764
|
+
(_e = buildQueueTimer.unref) == null ? void 0 : _e.call(buildQueueTimer);
|
|
765
|
+
scheduleBuildQueueTick(app, 1e3);
|
|
766
|
+
(_g = (_f = app.log) == null ? void 0 : _f.info) == null ? void 0 : _g.call(
|
|
767
|
+
_f,
|
|
768
|
+
`[plugin-build-guide-block] Build queue processor started (interval ${BUILD_GUIDE_QUEUE_POLL_INTERVAL_MS}ms)`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
function stopBuildGuideQueueProcessor(app) {
|
|
772
|
+
var _a, _b;
|
|
773
|
+
if (buildQueueTimer) {
|
|
774
|
+
clearInterval(buildQueueTimer);
|
|
775
|
+
buildQueueTimer = null;
|
|
776
|
+
}
|
|
777
|
+
if (buildQueueKickTimer) {
|
|
778
|
+
clearTimeout(buildQueueKickTimer);
|
|
779
|
+
buildQueueKickTimer = null;
|
|
780
|
+
}
|
|
781
|
+
if (buildQueueWakeHandler) {
|
|
782
|
+
const unsubscribe = (_b = (_a = app.pubSubManager) == null ? void 0 : _a.unsubscribe) == null ? void 0 : _b.call(
|
|
783
|
+
_a,
|
|
784
|
+
BUILD_GUIDE_QUEUE_WAKE_CHANNEL,
|
|
785
|
+
buildQueueWakeHandler
|
|
786
|
+
);
|
|
787
|
+
if (unsubscribe == null ? void 0 : unsubscribe.catch) {
|
|
788
|
+
unsubscribe.catch(() => void 0);
|
|
789
|
+
}
|
|
790
|
+
buildQueueWakeHandler = null;
|
|
791
|
+
}
|
|
792
|
+
buildQueueProcessing = false;
|
|
793
|
+
}
|
|
794
|
+
function scheduleBuildQueueTick(app, delayMs) {
|
|
795
|
+
var _a;
|
|
796
|
+
if (buildQueueKickTimer) return;
|
|
797
|
+
buildQueueKickTimer = setTimeout(() => {
|
|
798
|
+
buildQueueKickTimer = null;
|
|
799
|
+
runBuildQueueTick(app).catch((error) => {
|
|
800
|
+
var _a2, _b;
|
|
801
|
+
(_b = (_a2 = app.log) == null ? void 0 : _a2.error) == null ? void 0 : _b.call(_a2, "[plugin-build-guide-block] Build queue tick failed", error);
|
|
802
|
+
});
|
|
803
|
+
}, delayMs);
|
|
804
|
+
(_a = buildQueueKickTimer.unref) == null ? void 0 : _a.call(buildQueueKickTimer);
|
|
805
|
+
}
|
|
806
|
+
async function runBuildQueueTick(app) {
|
|
807
|
+
if (buildQueueProcessing || !isBuildGuideWorker(app)) return;
|
|
808
|
+
buildQueueProcessing = true;
|
|
809
|
+
try {
|
|
810
|
+
const redisMessages = await drainRedisBuildQueue(app, BUILD_GUIDE_QUEUE_CONCURRENCY);
|
|
811
|
+
await processBuildQueueMessages(app, redisMessages);
|
|
812
|
+
const remaining = Math.max(1, BUILD_GUIDE_QUEUE_CONCURRENCY - redisMessages.length);
|
|
813
|
+
await processQueuedBuildsFromDb(app, remaining);
|
|
814
|
+
} finally {
|
|
815
|
+
buildQueueProcessing = false;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function drainRedisBuildQueue(app, count) {
|
|
819
|
+
var _a, _b;
|
|
820
|
+
const redis = await getBuildQueueRedis(app);
|
|
821
|
+
if (!redis) return [];
|
|
822
|
+
const key = getBuildQueueRedisKey(app);
|
|
823
|
+
const messages = [];
|
|
824
|
+
for (let i = 0; i < count; i += 1) {
|
|
825
|
+
const raw = await redis.sendCommand(["LPOP", key]);
|
|
826
|
+
if (!raw) break;
|
|
827
|
+
try {
|
|
828
|
+
messages.push(JSON.parse(String(raw)));
|
|
829
|
+
} catch (error) {
|
|
830
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, `[plugin-build-guide-block] Dropped invalid Redis build message: ${(error == null ? void 0 : error.message) || error}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return messages;
|
|
834
|
+
}
|
|
835
|
+
function createBuildQueueMessageFromSpace(space) {
|
|
836
|
+
const runId = space.get("buildRunId");
|
|
837
|
+
if (!runId) return null;
|
|
838
|
+
return {
|
|
839
|
+
spaceId: String(space.get("id")),
|
|
840
|
+
runId: String(runId),
|
|
841
|
+
queuedAt: space.get("buildQueuedAt") ? new Date(space.get("buildQueuedAt")).toISOString() : void 0
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
async function processQueuedBuildsFromDb(app, count) {
|
|
845
|
+
const spaceRepo = app.db.getRepository("aiBuildGuideSpaces");
|
|
846
|
+
const spaces = await spaceRepo.find({
|
|
847
|
+
filter: {
|
|
848
|
+
status: "building",
|
|
849
|
+
buildPhase: "queued"
|
|
850
|
+
},
|
|
851
|
+
sort: ["buildQueuedAt"],
|
|
852
|
+
limit: count
|
|
853
|
+
});
|
|
854
|
+
const messages = spaces.map(createBuildQueueMessageFromSpace).filter(Boolean);
|
|
855
|
+
await processBuildQueueMessages(app, messages);
|
|
856
|
+
}
|
|
857
|
+
async function processBuildQueueMessages(app, messages) {
|
|
858
|
+
if (!messages.length) return;
|
|
859
|
+
await Promise.all(messages.map((message) => processQueuedBuild(app, message)));
|
|
860
|
+
}
|
|
861
|
+
async function markBuildError(app, spaceId, runId, error) {
|
|
862
|
+
const buildLog = (error == null ? void 0 : error.message) || String(error);
|
|
863
|
+
let updated = false;
|
|
864
|
+
if (runId) {
|
|
865
|
+
updated = await updateSpaceForRun(
|
|
866
|
+
app,
|
|
867
|
+
{ spaceId, runId },
|
|
868
|
+
{
|
|
869
|
+
status: "error",
|
|
870
|
+
buildPhase: "error",
|
|
871
|
+
buildLog,
|
|
872
|
+
buildHeartbeatAt: /* @__PURE__ */ new Date()
|
|
873
|
+
},
|
|
874
|
+
true
|
|
875
|
+
);
|
|
876
|
+
} else {
|
|
877
|
+
await app.db.getRepository("aiBuildGuideSpaces").update({
|
|
878
|
+
filterByTk: spaceId,
|
|
879
|
+
values: {
|
|
880
|
+
status: "error",
|
|
881
|
+
buildPhase: "error",
|
|
882
|
+
buildLog
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
updated = true;
|
|
886
|
+
}
|
|
887
|
+
if (!updated) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
await app.db.getRepository("aiBuildGuidePages").update({
|
|
891
|
+
filter: {
|
|
892
|
+
spaceId,
|
|
893
|
+
status: "building"
|
|
894
|
+
},
|
|
436
895
|
values: {
|
|
437
|
-
status: "
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
896
|
+
status: "error",
|
|
897
|
+
buildLog
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
async function enqueueBuild(app, message) {
|
|
902
|
+
var _a, _b;
|
|
903
|
+
try {
|
|
904
|
+
const queuedInRedis = await enqueueBuildToRedis(app, message);
|
|
905
|
+
if (queuedInRedis) {
|
|
906
|
+
await publishBuildQueueWake(app, message);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
await publishBuildQueueWake(app, message);
|
|
910
|
+
if (isBuildGuideWorker(app)) {
|
|
911
|
+
await app.eventQueue.publish(BUILD_GUIDE_QUEUE_CHANNEL, message, {
|
|
912
|
+
timeout: BUILD_GUIDE_QUEUE_TIMEOUT_MS,
|
|
913
|
+
maxRetries: 0
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(
|
|
918
|
+
_a,
|
|
919
|
+
`[plugin-build-guide-block] Redis queue is unavailable; build ${message.runId} for space "${message.spaceId}" will remain queued until a worker DB poller picks it up`
|
|
920
|
+
);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
await markBuildError(app, message.spaceId, message.runId, error);
|
|
923
|
+
throw error;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async function processQueuedBuild(app, message) {
|
|
927
|
+
var _a, _b;
|
|
928
|
+
const spaceId = message == null ? void 0 : message.spaceId;
|
|
929
|
+
const runId = message == null ? void 0 : message.runId;
|
|
930
|
+
if (!spaceId || !runId) {
|
|
931
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "[plugin-build-guide-block] Build queue message missing spaceId or runId");
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
await withBuildRunLock(app, spaceId, async () => {
|
|
935
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
936
|
+
const run = { spaceId, runId };
|
|
937
|
+
const workerId = getBuildWorkerId(app);
|
|
938
|
+
const claimed = await claimBuildRun(app, run, workerId);
|
|
939
|
+
if (!claimed) {
|
|
940
|
+
(_b2 = (_a2 = app.log) == null ? void 0 : _a2.info) == null ? void 0 : _b2.call(_a2, `[plugin-build-guide-block] Build ${runId} for space "${spaceId}" was already claimed or stale`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const spaceRepo = app.db.getRepository("aiBuildGuideSpaces");
|
|
944
|
+
const space = await spaceRepo.findById(spaceId);
|
|
945
|
+
if (!space) {
|
|
946
|
+
(_d = (_c = app.log) == null ? void 0 : _c.warn) == null ? void 0 : _d.call(_c, `[plugin-build-guide-block] Build space "${spaceId}" not found; skipping queued build`);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (space.get("status") !== "building") {
|
|
950
|
+
(_f = (_e = app.log) == null ? void 0 : _e.info) == null ? void 0 : _f.call(
|
|
951
|
+
_e,
|
|
952
|
+
`[plugin-build-guide-block] Build space "${spaceId}" is ${space.get("status")}; skipping queued build`
|
|
953
|
+
);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const stopHeartbeat = startBuildHeartbeat(app, run);
|
|
957
|
+
try {
|
|
958
|
+
await runBuild(app, app.db, run);
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (error instanceof StaleBuildRunError) {
|
|
961
|
+
(_h = (_g = app.log) == null ? void 0 : _g.info) == null ? void 0 : _h.call(_g, `[plugin-build-guide-block] ${error.message}`);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
(_j = (_i = app.log) == null ? void 0 : _i.error) == null ? void 0 : _j.call(_i, "Build Guide Worker Error", error);
|
|
965
|
+
await markBuildError(app, spaceId, runId, error);
|
|
966
|
+
} finally {
|
|
967
|
+
stopHeartbeat();
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
function registerBuildGuideQueue(app) {
|
|
972
|
+
app.eventQueue.subscribe(BUILD_GUIDE_QUEUE_CHANNEL, {
|
|
973
|
+
concurrency: BUILD_GUIDE_QUEUE_CONCURRENCY,
|
|
974
|
+
idle: () => isBuildGuideWorker(app),
|
|
975
|
+
process: async (message) => {
|
|
976
|
+
await processQueuedBuild(app, message);
|
|
442
977
|
}
|
|
443
978
|
});
|
|
979
|
+
if (!isBuildGuideWorker(app)) {
|
|
980
|
+
app.on("afterStart", () => clearLocalBuildMemoryQueue(app));
|
|
981
|
+
}
|
|
982
|
+
startBuildGuideQueueProcessor(app);
|
|
983
|
+
}
|
|
984
|
+
function unregisterBuildGuideQueue(app) {
|
|
985
|
+
app.eventQueue.unsubscribe(BUILD_GUIDE_QUEUE_CHANNEL);
|
|
986
|
+
stopBuildGuideQueueProcessor(app);
|
|
987
|
+
}
|
|
988
|
+
async function withBuildTriggerLock(app, spaceId, fn) {
|
|
989
|
+
return app.lockManager.runExclusive(`build-guide:trigger:${spaceId}`, fn, BUILD_TRIGGER_LOCK_TTL_MS);
|
|
990
|
+
}
|
|
991
|
+
async function withBuildRunLock(app, spaceId, fn) {
|
|
992
|
+
return app.lockManager.runExclusive(`build-guide:run:${spaceId}`, fn, BUILD_RUN_LOCK_TTL_MS);
|
|
993
|
+
}
|
|
994
|
+
async function recoverInterruptedBuilds(app) {
|
|
995
|
+
var _a, _b;
|
|
996
|
+
const spaceRepo = app.db.getRepository("aiBuildGuideSpaces");
|
|
997
|
+
const pageRepo = app.db.getRepository("aiBuildGuidePages");
|
|
998
|
+
const staleBefore = new Date(Date.now() - BUILD_STALE_MS);
|
|
999
|
+
const spaces = await spaceRepo.find({
|
|
1000
|
+
filter: {
|
|
1001
|
+
status: "building",
|
|
1002
|
+
$or: [{ buildHeartbeatAt: null }, { buildHeartbeatAt: { $lt: staleBefore } }]
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
for (const space of spaces) {
|
|
1006
|
+
const spaceId = String(space.get("id"));
|
|
1007
|
+
const runId = String(space.get("buildRunId") || import_crypto.default.randomUUID());
|
|
1008
|
+
const SpaceModel = getSpaceModel(app);
|
|
1009
|
+
const [affected] = await SpaceModel.update(
|
|
1010
|
+
{
|
|
1011
|
+
buildPhase: "queued",
|
|
1012
|
+
buildLog: "Build re-queued after worker restart",
|
|
1013
|
+
buildRunId: runId,
|
|
1014
|
+
buildQueuedAt: /* @__PURE__ */ new Date(),
|
|
1015
|
+
buildStartedAt: null,
|
|
1016
|
+
buildHeartbeatAt: null,
|
|
1017
|
+
buildWorkerId: null
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
where: {
|
|
1021
|
+
id: spaceId,
|
|
1022
|
+
status: "building",
|
|
1023
|
+
buildRunId: space.get("buildRunId") || null
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
if (!affected) {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
await pageRepo.update({
|
|
1031
|
+
filter: {
|
|
1032
|
+
spaceId,
|
|
1033
|
+
status: "building"
|
|
1034
|
+
},
|
|
1035
|
+
values: {
|
|
1036
|
+
status: "pending",
|
|
1037
|
+
buildLog: "Build re-queued after worker restart"
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
await enqueueBuild(app, {
|
|
1041
|
+
spaceId,
|
|
1042
|
+
runId,
|
|
1043
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (spaces.length) {
|
|
1047
|
+
(_b = (_a = app.log) == null ? void 0 : _a.info) == null ? void 0 : _b.call(_a, `[plugin-build-guide-block] Re-queued ${spaces.length} interrupted build(s)`);
|
|
1048
|
+
}
|
|
444
1049
|
}
|
|
445
1050
|
async function build(ctx, next) {
|
|
446
1051
|
const { filterByTk } = ctx.action.params;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (!space) {
|
|
450
|
-
ctx.throw(404, "Space not found");
|
|
451
|
-
}
|
|
452
|
-
if (space.get("status") === "building") {
|
|
453
|
-
ctx.throw(409, "A build is already in progress for this space");
|
|
1052
|
+
if (!filterByTk) {
|
|
1053
|
+
ctx.throw(400, "Space id is required");
|
|
454
1054
|
}
|
|
455
1055
|
const app = ctx.app;
|
|
456
|
-
const
|
|
457
|
-
|
|
1056
|
+
const repository = ctx.db.getRepository("aiBuildGuideSpaces");
|
|
1057
|
+
const body = await withBuildTriggerLock(app, String(filterByTk), async () => {
|
|
1058
|
+
var _a, _b;
|
|
1059
|
+
const runId = import_crypto.default.randomUUID();
|
|
1060
|
+
const space = await repository.findById(filterByTk);
|
|
1061
|
+
if (!space) {
|
|
1062
|
+
ctx.throw(404, "Space not found");
|
|
1063
|
+
}
|
|
1064
|
+
if (space.get("status") === "building") {
|
|
1065
|
+
ctx.throw(409, "A build is already in progress for this space");
|
|
1066
|
+
}
|
|
458
1067
|
await repository.update({
|
|
459
1068
|
filterByTk,
|
|
460
1069
|
values: {
|
|
461
1070
|
status: "building",
|
|
462
1071
|
buildPhase: "queued",
|
|
463
|
-
buildLog: "Build queued"
|
|
1072
|
+
buildLog: "Build queued",
|
|
1073
|
+
buildRunId: runId,
|
|
1074
|
+
buildQueuedAt: /* @__PURE__ */ new Date(),
|
|
1075
|
+
buildStartedAt: null,
|
|
1076
|
+
buildHeartbeatAt: null,
|
|
1077
|
+
buildWorkerId: null
|
|
464
1078
|
}
|
|
465
1079
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
values: {
|
|
472
|
-
status: "error",
|
|
473
|
-
buildPhase: "error",
|
|
474
|
-
buildLog: error.message || String(error)
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
} catch (updateErr) {
|
|
478
|
-
app.log.error("Failed to persist build error status", updateErr);
|
|
479
|
-
}
|
|
1080
|
+
await enqueueBuild(app, {
|
|
1081
|
+
spaceId: String(filterByTk),
|
|
1082
|
+
runId,
|
|
1083
|
+
userId: ((_b = (_a = ctx.state) == null ? void 0 : _a.currentUser) == null ? void 0 : _b.id) ?? null,
|
|
1084
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
480
1085
|
});
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
await repository.update({
|
|
485
|
-
filterByTk,
|
|
486
|
-
values: {
|
|
487
|
-
status: "error",
|
|
488
|
-
buildPhase: "error",
|
|
489
|
-
buildLog: error.message || String(error)
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
ctx.throw(500, error.message || "Error occurred during build");
|
|
493
|
-
}
|
|
1086
|
+
return { status: "building" };
|
|
1087
|
+
});
|
|
1088
|
+
ctx.body = body;
|
|
494
1089
|
await next();
|
|
495
1090
|
}
|
|
496
1091
|
// Annotate the CommonJS export names for ESM import in node:
|
|
497
1092
|
0 && (module.exports = {
|
|
498
|
-
|
|
1093
|
+
WORKER_JOB_BUILD_GUIDE_PROCESS,
|
|
1094
|
+
build,
|
|
1095
|
+
recoverInterruptedBuilds,
|
|
1096
|
+
registerBuildGuideQueue,
|
|
1097
|
+
unregisterBuildGuideQueue
|
|
499
1098
|
});
|