@biaoo/tiangong-wiki 0.3.2 → 0.3.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/core/db.js +1 -0
- package/dist/core/vault-processing.js +393 -173
- package/dist/core/vault.js +10 -0
- package/dist/daemon/server.js +1 -0
- package/dist/operations/dashboard.js +5 -0
- package/package.json +1 -1
- package/references/troubleshooting.md +3 -1
package/dist/core/db.js
CHANGED
|
@@ -9,8 +9,12 @@ import { buildVaultWorkflowPrompt, ensureWorkflowArtifactSet, getWorkflowArtifac
|
|
|
9
9
|
import { readWorkflowResult } from "./workflow-result.js";
|
|
10
10
|
import { AppError } from "../utils/errors.js";
|
|
11
11
|
import { readTextFileSync } from "../utils/fs.js";
|
|
12
|
-
import { toOffsetIso } from "../utils/time.js";
|
|
12
|
+
import { addSeconds, toOffsetIso } from "../utils/time.js";
|
|
13
13
|
const INLINE_WORKFLOW_ATTEMPTS = 2;
|
|
14
|
+
const MAX_QUEUE_ERROR_RETRIES = 3;
|
|
15
|
+
const QUEUE_FULL_RETRY_DELAY_SECONDS = 300;
|
|
16
|
+
const WORKFLOW_TIMEOUT_RETRY_DELAY_SECONDS = 120;
|
|
17
|
+
const NON_RETRYABLE_QUEUE_ERROR_CODES = new Set(["config_error", "invalid_request"]);
|
|
14
18
|
function buildFileIdFilterClause(filterFileIds) {
|
|
15
19
|
if (!filterFileIds || filterFileIds.length === 0) {
|
|
16
20
|
return { clause: "", params: [] };
|
|
@@ -20,6 +24,16 @@ function buildFileIdFilterClause(filterFileIds) {
|
|
|
20
24
|
params: filterFileIds,
|
|
21
25
|
};
|
|
22
26
|
}
|
|
27
|
+
function buildExcludedFileIdClause(excludedFileIds) {
|
|
28
|
+
const params = Array.from(excludedFileIds ?? []).filter((value) => value.trim().length > 0);
|
|
29
|
+
if (params.length === 0) {
|
|
30
|
+
return { clause: "", params: [] };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
clause: ` AND vault_processing_queue.file_id NOT IN (${params.map(() => "?").join(", ")})`,
|
|
34
|
+
params,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
23
37
|
function parseOptionalStringArray(value) {
|
|
24
38
|
if (Array.isArray(value)) {
|
|
25
39
|
return value
|
|
@@ -43,9 +57,11 @@ function parseOptionalStringArray(value) {
|
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
function mapQueueRow(row) {
|
|
60
|
+
const attempts = Number(row.attempts ?? 0);
|
|
61
|
+
const status = row.status;
|
|
46
62
|
return {
|
|
47
63
|
fileId: String(row.fileId),
|
|
48
|
-
status
|
|
64
|
+
status,
|
|
49
65
|
priority: Number(row.priority ?? 0),
|
|
50
66
|
queuedAt: String(row.queuedAt),
|
|
51
67
|
claimedAt: typeof row.claimedAt === "string" ? row.claimedAt : null,
|
|
@@ -53,13 +69,15 @@ function mapQueueRow(row) {
|
|
|
53
69
|
processedAt: typeof row.processedAt === "string" ? row.processedAt : null,
|
|
54
70
|
resultPageId: typeof row.resultPageId === "string" ? row.resultPageId : null,
|
|
55
71
|
errorMessage: typeof row.errorMessage === "string" ? row.errorMessage : null,
|
|
56
|
-
attempts
|
|
72
|
+
attempts,
|
|
57
73
|
threadId: typeof row.threadId === "string" ? row.threadId : null,
|
|
58
74
|
workflowVersion: typeof row.workflowVersion === "string" ? row.workflowVersion : null,
|
|
59
75
|
decision: typeof row.decision === "string" ? row.decision : null,
|
|
60
76
|
resultManifestPath: typeof row.resultManifestPath === "string" ? row.resultManifestPath : null,
|
|
61
77
|
lastErrorAt: typeof row.lastErrorAt === "string" ? row.lastErrorAt : null,
|
|
78
|
+
lastErrorCode: typeof row.lastErrorCode === "string" ? row.lastErrorCode : null,
|
|
62
79
|
retryAfter: typeof row.retryAfter === "string" ? row.retryAfter : null,
|
|
80
|
+
autoRetryExhausted: status === "error" && attempts > MAX_QUEUE_ERROR_RETRIES,
|
|
63
81
|
createdPageIds: parseOptionalStringArray(row.createdPageIds),
|
|
64
82
|
updatedPageIds: parseOptionalStringArray(row.updatedPageIds),
|
|
65
83
|
appliedTypeNames: parseOptionalStringArray(row.appliedTypeNames),
|
|
@@ -72,8 +90,20 @@ function mapQueueRow(row) {
|
|
|
72
90
|
filePath: typeof row.filePath === "string" ? row.filePath : undefined,
|
|
73
91
|
};
|
|
74
92
|
}
|
|
75
|
-
function claimQueueItems(db, limit,
|
|
76
|
-
const filter = buildFileIdFilterClause(filterFileIds);
|
|
93
|
+
function claimQueueItems(db, limit, options = {}) {
|
|
94
|
+
const filter = buildFileIdFilterClause(options.filterFileIds);
|
|
95
|
+
const exclude = buildExcludedFileIdClause(options.excludeFileIds);
|
|
96
|
+
const manualClaim = Boolean(options.filterFileIds && options.filterFileIds.length > 0);
|
|
97
|
+
const errorEligibility = manualClaim
|
|
98
|
+
? "vault_processing_queue.status = 'error'"
|
|
99
|
+
: [
|
|
100
|
+
"vault_processing_queue.status = 'error'",
|
|
101
|
+
`vault_processing_queue.attempts <= ${MAX_QUEUE_ERROR_RETRIES}`,
|
|
102
|
+
`COALESCE(vault_processing_queue.last_error_code, '') NOT IN (${Array.from(NON_RETRYABLE_QUEUE_ERROR_CODES)
|
|
103
|
+
.map((code) => `'${code}'`)
|
|
104
|
+
.join(", ")})`,
|
|
105
|
+
"(vault_processing_queue.retry_after IS NULL OR julianday(vault_processing_queue.retry_after) <= julianday(?))",
|
|
106
|
+
].join("\n AND ");
|
|
77
107
|
const select = db.prepare(`
|
|
78
108
|
SELECT
|
|
79
109
|
file_id AS fileId,
|
|
@@ -91,6 +121,7 @@ function claimQueueItems(db, limit, filterFileIds) {
|
|
|
91
121
|
decision,
|
|
92
122
|
result_manifest_path AS resultManifestPath,
|
|
93
123
|
last_error_at AS lastErrorAt,
|
|
124
|
+
last_error_code AS lastErrorCode,
|
|
94
125
|
retry_after AS retryAfter,
|
|
95
126
|
created_page_ids AS createdPageIds,
|
|
96
127
|
updated_page_ids AS updatedPageIds,
|
|
@@ -104,7 +135,12 @@ function claimQueueItems(db, limit, filterFileIds) {
|
|
|
104
135
|
vault_files.file_path AS filePath
|
|
105
136
|
FROM vault_processing_queue
|
|
106
137
|
LEFT JOIN vault_files ON vault_files.id = vault_processing_queue.file_id
|
|
107
|
-
WHERE
|
|
138
|
+
WHERE (
|
|
139
|
+
vault_processing_queue.status = 'pending'
|
|
140
|
+
OR (
|
|
141
|
+
${errorEligibility}
|
|
142
|
+
)
|
|
143
|
+
)${filter.clause}${exclude.clause}
|
|
108
144
|
ORDER BY priority DESC, queued_at ASC
|
|
109
145
|
LIMIT ?
|
|
110
146
|
`);
|
|
@@ -114,12 +150,16 @@ function claimQueueItems(db, limit, filterFileIds) {
|
|
|
114
150
|
status = 'processing',
|
|
115
151
|
claimed_at = @claimed_at,
|
|
116
152
|
started_at = @started_at,
|
|
117
|
-
error_message = NULL
|
|
153
|
+
error_message = NULL,
|
|
154
|
+
retry_after = NULL
|
|
118
155
|
WHERE file_id = @file_id AND status IN ('pending', 'error')
|
|
119
156
|
`);
|
|
120
157
|
return db.transaction((claimLimit, claimFilterParams) => {
|
|
121
158
|
const startedAt = toOffsetIso();
|
|
122
|
-
const
|
|
159
|
+
const selectParams = manualClaim
|
|
160
|
+
? [...claimFilterParams, claimLimit]
|
|
161
|
+
: [startedAt, ...claimFilterParams, claimLimit];
|
|
162
|
+
const items = select.all(...selectParams).map(mapQueueRow);
|
|
123
163
|
for (const item of items) {
|
|
124
164
|
markProcessing.run({
|
|
125
165
|
file_id: item.fileId,
|
|
@@ -132,7 +172,7 @@ function claimQueueItems(db, limit, filterFileIds) {
|
|
|
132
172
|
claimedAt: startedAt,
|
|
133
173
|
startedAt,
|
|
134
174
|
}));
|
|
135
|
-
})(limit, filter.params);
|
|
175
|
+
})(limit, [...filter.params, ...exclude.params]);
|
|
136
176
|
}
|
|
137
177
|
function fetchQueueItemsByStatus(db, status) {
|
|
138
178
|
const rows = db.prepare(`
|
|
@@ -152,6 +192,7 @@ function fetchQueueItemsByStatus(db, status) {
|
|
|
152
192
|
decision,
|
|
153
193
|
result_manifest_path AS resultManifestPath,
|
|
154
194
|
last_error_at AS lastErrorAt,
|
|
195
|
+
last_error_code AS lastErrorCode,
|
|
155
196
|
retry_after AS retryAfter,
|
|
156
197
|
created_page_ids AS createdPageIds,
|
|
157
198
|
updated_page_ids AS updatedPageIds,
|
|
@@ -188,6 +229,7 @@ function fetchQueueItemByFileId(db, fileId) {
|
|
|
188
229
|
decision,
|
|
189
230
|
result_manifest_path AS resultManifestPath,
|
|
190
231
|
last_error_at AS lastErrorAt,
|
|
232
|
+
last_error_code AS lastErrorCode,
|
|
191
233
|
retry_after AS retryAfter,
|
|
192
234
|
created_page_ids AS createdPageIds,
|
|
193
235
|
updated_page_ids AS updatedPageIds,
|
|
@@ -262,10 +304,112 @@ function serializeArray(value) {
|
|
|
262
304
|
function formatManifestLogFields(manifest) {
|
|
263
305
|
return `decision=${manifest.decision} skills=${manifest.skillsUsed.join(",") || "-"} created=${manifest.createdPageIds.join(",") || "-"} updated=${manifest.updatedPageIds.join(",") || "-"} proposed=${manifest.proposedTypes.map((item) => item.name).join(",") || "-"}`;
|
|
264
306
|
}
|
|
265
|
-
function
|
|
307
|
+
function extractErrorDetailsCode(error) {
|
|
308
|
+
if (!(error instanceof AppError)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
if (typeof error.details !== "object" || error.details === null || Array.isArray(error.details)) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
const code = error.details.code;
|
|
315
|
+
return typeof code === "string" && code.trim() ? code.trim() : null;
|
|
316
|
+
}
|
|
317
|
+
function inferWorkflowErrorCode(message) {
|
|
318
|
+
const normalized = message.toLowerCase();
|
|
319
|
+
if (normalized.includes("queue_full") || normalized.includes("write queue is full")) {
|
|
320
|
+
return "queue_full";
|
|
321
|
+
}
|
|
322
|
+
if (normalized.includes("timed out")) {
|
|
323
|
+
return "workflow_timeout";
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
function buildRetryAfter(seconds) {
|
|
328
|
+
return toOffsetIso(addSeconds(new Date(), seconds));
|
|
329
|
+
}
|
|
330
|
+
function buildQueueFailureState(message, options = {}) {
|
|
331
|
+
const inferredCode = inferWorkflowErrorCode(message);
|
|
332
|
+
const errorCode = inferredCode ?? options.explicitCode ?? (options.errorType === "config" ? "config_error" : null);
|
|
333
|
+
if (errorCode && NON_RETRYABLE_QUEUE_ERROR_CODES.has(errorCode)) {
|
|
334
|
+
return {
|
|
335
|
+
errorCode,
|
|
336
|
+
retryAfter: null,
|
|
337
|
+
autoRetryEligible: false,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (errorCode === "queue_full") {
|
|
341
|
+
return {
|
|
342
|
+
errorCode,
|
|
343
|
+
retryAfter: buildRetryAfter(QUEUE_FULL_RETRY_DELAY_SECONDS),
|
|
344
|
+
autoRetryEligible: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (errorCode === "workflow_timeout") {
|
|
348
|
+
return {
|
|
349
|
+
errorCode,
|
|
350
|
+
retryAfter: buildRetryAfter(WORKFLOW_TIMEOUT_RETRY_DELAY_SECONDS),
|
|
351
|
+
autoRetryEligible: true,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
errorCode,
|
|
356
|
+
retryAfter: null,
|
|
357
|
+
autoRetryEligible: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function formatQueueErrorMessage(message, autoRetryExhausted) {
|
|
361
|
+
const autoRetrySuffix = autoRetryExhausted
|
|
362
|
+
? ` Auto retry limit reached after ${MAX_QUEUE_ERROR_RETRIES} retries; use manual retry or requeue after the vault file changes.`
|
|
363
|
+
: "";
|
|
364
|
+
return `${message}${autoRetrySuffix}`.slice(0, 1_000);
|
|
365
|
+
}
|
|
366
|
+
function applyWorkflowManifest(db, fileId, manifest, resultManifestPath, currentAttempts) {
|
|
266
367
|
const resultPageId = manifest.createdPageIds[0] ?? manifest.updatedPageIds[0] ?? null;
|
|
267
368
|
const status = manifest.status;
|
|
268
369
|
const processedAt = toOffsetIso();
|
|
370
|
+
if (status === "error") {
|
|
371
|
+
const failureState = buildQueueFailureState(manifest.reason);
|
|
372
|
+
const nextAttempts = currentAttempts + 1;
|
|
373
|
+
const autoRetryExhausted = failureState.autoRetryEligible && nextAttempts > MAX_QUEUE_ERROR_RETRIES;
|
|
374
|
+
db.prepare(`
|
|
375
|
+
UPDATE vault_processing_queue
|
|
376
|
+
SET
|
|
377
|
+
status = 'error',
|
|
378
|
+
processed_at = @processed_at,
|
|
379
|
+
result_page_id = @result_page_id,
|
|
380
|
+
error_message = @error_message,
|
|
381
|
+
attempts = attempts + 1,
|
|
382
|
+
workflow_version = @workflow_version,
|
|
383
|
+
decision = @decision,
|
|
384
|
+
result_manifest_path = @result_manifest_path,
|
|
385
|
+
last_error_at = @last_error_at,
|
|
386
|
+
last_error_code = @last_error_code,
|
|
387
|
+
retry_after = @retry_after,
|
|
388
|
+
created_page_ids = @created_page_ids,
|
|
389
|
+
updated_page_ids = @updated_page_ids,
|
|
390
|
+
applied_type_names = @applied_type_names,
|
|
391
|
+
proposed_type_names = @proposed_type_names,
|
|
392
|
+
skills_used = @skills_used
|
|
393
|
+
WHERE file_id = @file_id
|
|
394
|
+
`).run({
|
|
395
|
+
file_id: fileId,
|
|
396
|
+
processed_at: processedAt,
|
|
397
|
+
result_page_id: resultPageId,
|
|
398
|
+
error_message: formatQueueErrorMessage(manifest.reason, autoRetryExhausted),
|
|
399
|
+
workflow_version: CODEX_WORKFLOW_VERSION,
|
|
400
|
+
decision: manifest.decision,
|
|
401
|
+
result_manifest_path: resultManifestPath,
|
|
402
|
+
last_error_at: processedAt,
|
|
403
|
+
last_error_code: failureState.errorCode,
|
|
404
|
+
retry_after: autoRetryExhausted ? null : failureState.retryAfter,
|
|
405
|
+
created_page_ids: serializeArray(manifest.createdPageIds),
|
|
406
|
+
updated_page_ids: serializeArray(manifest.updatedPageIds),
|
|
407
|
+
applied_type_names: serializeArray(manifest.appliedTypeNames),
|
|
408
|
+
proposed_type_names: serializeArray(manifest.proposedTypes.map((item) => item.name)),
|
|
409
|
+
skills_used: serializeArray(manifest.skillsUsed),
|
|
410
|
+
});
|
|
411
|
+
return { status, pageId: resultPageId };
|
|
412
|
+
}
|
|
269
413
|
db.prepare(`
|
|
270
414
|
UPDATE vault_processing_queue
|
|
271
415
|
SET
|
|
@@ -277,6 +421,7 @@ function applyWorkflowManifest(db, fileId, manifest, resultManifestPath) {
|
|
|
277
421
|
decision = @decision,
|
|
278
422
|
result_manifest_path = @result_manifest_path,
|
|
279
423
|
last_error_at = NULL,
|
|
424
|
+
last_error_code = NULL,
|
|
280
425
|
retry_after = NULL,
|
|
281
426
|
created_page_ids = @created_page_ids,
|
|
282
427
|
updated_page_ids = @updated_page_ids,
|
|
@@ -425,16 +570,20 @@ function updateQueueWorkflowError(db, fileId, payload) {
|
|
|
425
570
|
thread_id = COALESCE(@thread_id, thread_id),
|
|
426
571
|
workflow_version = @workflow_version,
|
|
427
572
|
result_manifest_path = COALESCE(@result_manifest_path, result_manifest_path),
|
|
428
|
-
last_error_at = @last_error_at
|
|
573
|
+
last_error_at = @last_error_at,
|
|
574
|
+
last_error_code = @last_error_code,
|
|
575
|
+
retry_after = @retry_after
|
|
429
576
|
WHERE file_id = @file_id
|
|
430
577
|
`).run({
|
|
431
578
|
file_id: fileId,
|
|
432
579
|
processed_at: processedAt,
|
|
433
|
-
error_message: payload.errorMessage
|
|
580
|
+
error_message: formatQueueErrorMessage(payload.errorMessage, payload.autoRetryExhausted === true),
|
|
434
581
|
thread_id: payload.threadId ?? null,
|
|
435
582
|
workflow_version: CODEX_WORKFLOW_VERSION,
|
|
436
583
|
result_manifest_path: payload.resultManifestPath ?? null,
|
|
437
584
|
last_error_at: processedAt,
|
|
585
|
+
last_error_code: payload.errorCode ?? null,
|
|
586
|
+
retry_after: payload.autoRetryExhausted ? null : payload.retryAfter ?? null,
|
|
438
587
|
});
|
|
439
588
|
}
|
|
440
589
|
function prepareCodexWorkflowInput(paths, item, file, localFilePath, env, allowTemplateEvolution) {
|
|
@@ -484,6 +633,185 @@ function prepareCodexWorkflowInput(paths, item, file, localFilePath, env, allowT
|
|
|
484
633
|
},
|
|
485
634
|
};
|
|
486
635
|
}
|
|
636
|
+
async function processClaimedQueueItem(input) {
|
|
637
|
+
const { db, env, paths, item, workflowRunner, templateEvolution, maxWorkflowAttempts, workflowTimeoutMs } = input;
|
|
638
|
+
input.log?.(`${item.fileId}: start processing attempt=${item.attempts + 1} queuedAt=${item.queuedAt} thread=${item.threadId ?? "-"}`);
|
|
639
|
+
const file = fetchVaultFile(db, item.fileId);
|
|
640
|
+
if (!file) {
|
|
641
|
+
updateQueueStatus(db, item.fileId, {
|
|
642
|
+
status: "error",
|
|
643
|
+
processedAt: toOffsetIso(),
|
|
644
|
+
errorMessage: `Vault file missing from index: ${item.fileId}`,
|
|
645
|
+
incrementAttempts: true,
|
|
646
|
+
});
|
|
647
|
+
input.log?.(`${item.fileId}: error thread=- result=- message=Vault file missing from index`);
|
|
648
|
+
return {
|
|
649
|
+
status: "error",
|
|
650
|
+
item: {
|
|
651
|
+
fileId: item.fileId,
|
|
652
|
+
status: "error",
|
|
653
|
+
reason: "Vault file missing from index",
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
let threadId = item.threadId ?? null;
|
|
658
|
+
let resultManifestPath = null;
|
|
659
|
+
try {
|
|
660
|
+
const localFilePath = await ensureLocalVaultFile(file, paths.vaultPath, env);
|
|
661
|
+
const { artifacts, input: workflowInput } = prepareCodexWorkflowInput(paths, item, file, localFilePath, env, templateEvolution.canApply);
|
|
662
|
+
resultManifestPath = artifacts.resultPath;
|
|
663
|
+
let finalOutcome = null;
|
|
664
|
+
let lastWorkflowError;
|
|
665
|
+
for (let attempt = 1; attempt <= maxWorkflowAttempts; attempt += 1) {
|
|
666
|
+
try {
|
|
667
|
+
const mode = threadId ? "resume" : "start";
|
|
668
|
+
const workflowController = new AbortController();
|
|
669
|
+
let loggedStartedThreadId = null;
|
|
670
|
+
const attemptInput = {
|
|
671
|
+
...workflowInput,
|
|
672
|
+
signal: workflowController.signal,
|
|
673
|
+
onThreadStarted: (startedThreadId) => {
|
|
674
|
+
if (loggedStartedThreadId === startedThreadId) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
loggedStartedThreadId = startedThreadId;
|
|
678
|
+
threadId = startedThreadId;
|
|
679
|
+
updateQueueWorkflowTracking(db, item.fileId, {
|
|
680
|
+
threadId: startedThreadId,
|
|
681
|
+
resultManifestPath: artifacts.resultPath,
|
|
682
|
+
});
|
|
683
|
+
input.log?.(`${item.fileId}: workflow started mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} thread=${startedThreadId} result=${artifacts.resultPath}`);
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
input.log?.(`${item.fileId}: launching workflow mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} timeout=${Math.ceil(workflowTimeoutMs / 1000)}s result=${artifacts.resultPath}`);
|
|
687
|
+
const handle = threadId
|
|
688
|
+
? await runWithWorkflowTimeout("resumeWorkflow", workflowTimeoutMs, workflowController, () => workflowRunner.resumeWorkflow(threadId, attemptInput))
|
|
689
|
+
: await runWithWorkflowTimeout("startWorkflow", workflowTimeoutMs, workflowController, () => workflowRunner.startWorkflow(attemptInput));
|
|
690
|
+
threadId = handle.threadId;
|
|
691
|
+
if (loggedStartedThreadId !== handle.threadId) {
|
|
692
|
+
loggedStartedThreadId = handle.threadId;
|
|
693
|
+
input.log?.(`${item.fileId}: workflow started mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} thread=${handle.threadId} result=${artifacts.resultPath}`);
|
|
694
|
+
}
|
|
695
|
+
updateQueueWorkflowTracking(db, item.fileId, {
|
|
696
|
+
threadId: handle.threadId,
|
|
697
|
+
resultManifestPath: artifacts.resultPath,
|
|
698
|
+
});
|
|
699
|
+
input.log?.(`${item.fileId}: waiting for workflow result thread=${handle.threadId} attempt=${attempt}/${maxWorkflowAttempts} result=${artifacts.resultPath}`);
|
|
700
|
+
const collectController = new AbortController();
|
|
701
|
+
const manifest = await runWithWorkflowTimeout("collectResult", workflowTimeoutMs, collectController, () => workflowRunner.collectResult(handle, {
|
|
702
|
+
...workflowInput,
|
|
703
|
+
signal: collectController.signal,
|
|
704
|
+
}));
|
|
705
|
+
assertTemplateEvolutionAllowed(manifest, templateEvolution);
|
|
706
|
+
finalOutcome = {
|
|
707
|
+
outcome: applyWorkflowManifest(db, item.fileId, manifest, artifacts.resultPath, item.attempts),
|
|
708
|
+
manifest,
|
|
709
|
+
handleThreadId: handle.threadId,
|
|
710
|
+
};
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
lastWorkflowError = error;
|
|
715
|
+
threadId = readPersistedWorkflowThreadId(artifacts.queueItemPath) ?? threadId;
|
|
716
|
+
if (threadId) {
|
|
717
|
+
updateQueueWorkflowTracking(db, item.fileId, {
|
|
718
|
+
threadId,
|
|
719
|
+
resultManifestPath: artifacts.resultPath,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const recoveredManifest = shouldAttemptManifestRecovery(error)
|
|
723
|
+
? readRecoverableWorkflowResult(artifacts.resultPath, threadId)
|
|
724
|
+
: null;
|
|
725
|
+
if (recoveredManifest) {
|
|
726
|
+
assertTemplateEvolutionAllowed(recoveredManifest, templateEvolution);
|
|
727
|
+
finalOutcome = {
|
|
728
|
+
outcome: applyWorkflowManifest(db, item.fileId, recoveredManifest, artifacts.resultPath, item.attempts),
|
|
729
|
+
manifest: recoveredManifest,
|
|
730
|
+
handleThreadId: recoveredManifest.threadId,
|
|
731
|
+
};
|
|
732
|
+
input.log?.(`${item.fileId}: recovered persisted workflow result status=${recoveredManifest.status} thread=${recoveredManifest.threadId} ${formatManifestLogFields(recoveredManifest)} result=${artifacts.resultPath} message=${formatWorkflowError(error)}`);
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
if (!shouldRetryWorkflowAttempt(error, attempt, maxWorkflowAttempts)) {
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
input.log?.(`${item.fileId}: retrying workflow attempt ${attempt + 1}/${maxWorkflowAttempts} thread=${threadId ?? "-"} result=${artifacts.resultPath} message=${formatWorkflowError(error)}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (!finalOutcome) {
|
|
742
|
+
throw (lastWorkflowError ?? new AppError("Workflow completed without a result", "runtime"));
|
|
743
|
+
}
|
|
744
|
+
input.log?.(`${item.fileId}: ${finalOutcome.outcome.status} thread=${finalOutcome.handleThreadId} ${formatManifestLogFields(finalOutcome.manifest)} result=${artifacts.resultPath}`);
|
|
745
|
+
return {
|
|
746
|
+
status: finalOutcome.outcome.status,
|
|
747
|
+
item: {
|
|
748
|
+
fileId: item.fileId,
|
|
749
|
+
status: finalOutcome.outcome.status,
|
|
750
|
+
pageId: finalOutcome.outcome.pageId,
|
|
751
|
+
reason: finalOutcome.manifest.reason,
|
|
752
|
+
threadId: finalOutcome.handleThreadId,
|
|
753
|
+
decision: finalOutcome.manifest.decision,
|
|
754
|
+
skillsUsed: finalOutcome.manifest.skillsUsed,
|
|
755
|
+
createdPageIds: finalOutcome.manifest.createdPageIds,
|
|
756
|
+
updatedPageIds: finalOutcome.manifest.updatedPageIds,
|
|
757
|
+
proposedTypeNames: finalOutcome.manifest.proposedTypes.map((entry) => entry.name),
|
|
758
|
+
resultManifestPath: artifacts.resultPath,
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
const recoveredManifest = shouldAttemptManifestRecovery(error)
|
|
764
|
+
? readRecoverableWorkflowResult(resultManifestPath, threadId)
|
|
765
|
+
: null;
|
|
766
|
+
if (recoveredManifest && resultManifestPath) {
|
|
767
|
+
assertTemplateEvolutionAllowed(recoveredManifest, templateEvolution);
|
|
768
|
+
const recoveredOutcome = applyWorkflowManifest(db, item.fileId, recoveredManifest, resultManifestPath, item.attempts);
|
|
769
|
+
input.log?.(`${item.fileId}: recovered persisted workflow result after terminal failure status=${recoveredOutcome.status} thread=${recoveredManifest.threadId} ${formatManifestLogFields(recoveredManifest)} result=${resultManifestPath} message=${formatWorkflowError(error)}`);
|
|
770
|
+
return {
|
|
771
|
+
status: recoveredOutcome.status,
|
|
772
|
+
item: {
|
|
773
|
+
fileId: item.fileId,
|
|
774
|
+
status: recoveredOutcome.status,
|
|
775
|
+
pageId: recoveredOutcome.pageId,
|
|
776
|
+
reason: recoveredManifest.reason,
|
|
777
|
+
threadId: recoveredManifest.threadId,
|
|
778
|
+
decision: recoveredManifest.decision,
|
|
779
|
+
skillsUsed: recoveredManifest.skillsUsed,
|
|
780
|
+
createdPageIds: recoveredManifest.createdPageIds,
|
|
781
|
+
updatedPageIds: recoveredManifest.updatedPageIds,
|
|
782
|
+
proposedTypeNames: recoveredManifest.proposedTypes.map((entry) => entry.name),
|
|
783
|
+
resultManifestPath,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
const message = formatWorkflowError(error);
|
|
788
|
+
const failureState = buildQueueFailureState(message, {
|
|
789
|
+
explicitCode: extractErrorDetailsCode(error),
|
|
790
|
+
errorType: error instanceof AppError ? error.type : null,
|
|
791
|
+
});
|
|
792
|
+
const autoRetryExhausted = failureState.autoRetryEligible && item.attempts >= MAX_QUEUE_ERROR_RETRIES;
|
|
793
|
+
updateQueueWorkflowError(db, item.fileId, {
|
|
794
|
+
errorMessage: message,
|
|
795
|
+
errorCode: failureState.errorCode,
|
|
796
|
+
retryAfter: failureState.retryAfter,
|
|
797
|
+
threadId,
|
|
798
|
+
resultManifestPath,
|
|
799
|
+
autoRetryExhausted,
|
|
800
|
+
});
|
|
801
|
+
input.log?.(`${item.fileId}: error thread=${threadId ?? "-"} result=${resultManifestPath ?? "-"} message=${message}${autoRetryExhausted ? ` autoRetryLimit=${MAX_QUEUE_ERROR_RETRIES}` : ""}`);
|
|
802
|
+
return {
|
|
803
|
+
status: "error",
|
|
804
|
+
item: {
|
|
805
|
+
fileId: item.fileId,
|
|
806
|
+
status: "error",
|
|
807
|
+
pageId: item.resultPageId ?? null,
|
|
808
|
+
reason: message,
|
|
809
|
+
threadId,
|
|
810
|
+
resultManifestPath,
|
|
811
|
+
},
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
487
815
|
export function getVaultQueueSnapshot(env = process.env, status) {
|
|
488
816
|
const paths = resolveRuntimePaths(env);
|
|
489
817
|
const config = loadConfig(paths.configPath);
|
|
@@ -539,7 +867,6 @@ export async function processVaultQueueBatch(env = process.env, options = {}) {
|
|
|
539
867
|
const config = loadConfig(paths.configPath);
|
|
540
868
|
const { db } = openDb(paths.dbPath, config, Number.parseInt(env.EMBEDDING_DIMENSIONS ?? "384", 10) || 384);
|
|
541
869
|
try {
|
|
542
|
-
const items = claimQueueItems(db, options.maxItems ?? agentSettings.batchSize, options.filterFileIds);
|
|
543
870
|
const result = {
|
|
544
871
|
enabled: true,
|
|
545
872
|
processed: 0,
|
|
@@ -552,9 +879,10 @@ export async function processVaultQueueBatch(env = process.env, options = {}) {
|
|
|
552
879
|
const templateEvolution = resolveTemplateEvolutionSettings(env);
|
|
553
880
|
const maxWorkflowAttempts = isInlineRetryCapable(workflowRunner) ? INLINE_WORKFLOW_ATTEMPTS : 1;
|
|
554
881
|
const workflowTimeoutMs = agentSettings.workflowTimeoutSeconds * 1000;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
882
|
+
const workerSlots = Math.max(0, options.maxItems ?? agentSettings.batchSize);
|
|
883
|
+
const attemptedFileIds = new Set();
|
|
884
|
+
const orderedItems = [];
|
|
885
|
+
let nextSequence = 0;
|
|
558
886
|
const countOutcome = (status) => {
|
|
559
887
|
if (status === "done") {
|
|
560
888
|
result.done += 1;
|
|
@@ -567,169 +895,61 @@ export async function processVaultQueueBatch(env = process.env, options = {}) {
|
|
|
567
895
|
}
|
|
568
896
|
result.processed += 1;
|
|
569
897
|
};
|
|
570
|
-
|
|
571
|
-
options.
|
|
572
|
-
|
|
573
|
-
if (!file) {
|
|
574
|
-
updateQueueStatus(db, item.fileId, {
|
|
575
|
-
status: "error",
|
|
576
|
-
processedAt: toOffsetIso(),
|
|
577
|
-
errorMessage: `Vault file missing from index: ${item.fileId}`,
|
|
578
|
-
incrementAttempts: true,
|
|
579
|
-
});
|
|
580
|
-
countOutcome("error");
|
|
581
|
-
options.log?.(`${item.fileId}: error thread=- result=- message=Vault file missing from index`);
|
|
582
|
-
result.items.push({
|
|
583
|
-
fileId: item.fileId,
|
|
584
|
-
status: "error",
|
|
585
|
-
reason: "Vault file missing from index",
|
|
586
|
-
});
|
|
587
|
-
continue;
|
|
898
|
+
const claimNextQueueItem = () => {
|
|
899
|
+
if (options.shouldStop?.() === true) {
|
|
900
|
+
return null;
|
|
588
901
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const localFilePath = await ensureLocalVaultFile(file, paths.vaultPath, env);
|
|
593
|
-
const { artifacts, input } = prepareCodexWorkflowInput(paths, item, file, localFilePath, env, templateEvolution.canApply);
|
|
594
|
-
resultManifestPath = artifacts.resultPath;
|
|
595
|
-
let finalOutcome = null;
|
|
596
|
-
let lastWorkflowError;
|
|
597
|
-
for (let attempt = 1; attempt <= maxWorkflowAttempts; attempt += 1) {
|
|
598
|
-
try {
|
|
599
|
-
const mode = threadId ? "resume" : "start";
|
|
600
|
-
const workflowController = new AbortController();
|
|
601
|
-
let loggedStartedThreadId = null;
|
|
602
|
-
const attemptInput = {
|
|
603
|
-
...input,
|
|
604
|
-
signal: workflowController.signal,
|
|
605
|
-
onThreadStarted: (startedThreadId) => {
|
|
606
|
-
if (loggedStartedThreadId === startedThreadId) {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
loggedStartedThreadId = startedThreadId;
|
|
610
|
-
threadId = startedThreadId;
|
|
611
|
-
updateQueueWorkflowTracking(db, item.fileId, {
|
|
612
|
-
threadId: startedThreadId,
|
|
613
|
-
resultManifestPath: artifacts.resultPath,
|
|
614
|
-
});
|
|
615
|
-
options.log?.(`${item.fileId}: workflow started mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} thread=${startedThreadId} result=${artifacts.resultPath}`);
|
|
616
|
-
},
|
|
617
|
-
};
|
|
618
|
-
options.log?.(`${item.fileId}: launching workflow mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} timeout=${agentSettings.workflowTimeoutSeconds}s result=${artifacts.resultPath}`);
|
|
619
|
-
const handle = threadId
|
|
620
|
-
? await runWithWorkflowTimeout("resumeWorkflow", workflowTimeoutMs, workflowController, () => workflowRunner.resumeWorkflow(threadId, attemptInput))
|
|
621
|
-
: await runWithWorkflowTimeout("startWorkflow", workflowTimeoutMs, workflowController, () => workflowRunner.startWorkflow(attemptInput));
|
|
622
|
-
threadId = handle.threadId;
|
|
623
|
-
if (loggedStartedThreadId !== handle.threadId) {
|
|
624
|
-
loggedStartedThreadId = handle.threadId;
|
|
625
|
-
options.log?.(`${item.fileId}: workflow started mode=${mode} attempt=${attempt}/${maxWorkflowAttempts} thread=${handle.threadId} result=${artifacts.resultPath}`);
|
|
626
|
-
}
|
|
627
|
-
updateQueueWorkflowTracking(db, item.fileId, {
|
|
628
|
-
threadId: handle.threadId,
|
|
629
|
-
resultManifestPath: artifacts.resultPath,
|
|
630
|
-
});
|
|
631
|
-
options.log?.(`${item.fileId}: waiting for workflow result thread=${handle.threadId} attempt=${attempt}/${maxWorkflowAttempts} result=${artifacts.resultPath}`);
|
|
632
|
-
const collectController = new AbortController();
|
|
633
|
-
const manifest = await runWithWorkflowTimeout("collectResult", workflowTimeoutMs, collectController, () => workflowRunner.collectResult(handle, {
|
|
634
|
-
...input,
|
|
635
|
-
signal: collectController.signal,
|
|
636
|
-
}));
|
|
637
|
-
assertTemplateEvolutionAllowed(manifest, templateEvolution);
|
|
638
|
-
finalOutcome = {
|
|
639
|
-
outcome: applyWorkflowManifest(db, item.fileId, manifest, artifacts.resultPath),
|
|
640
|
-
manifest,
|
|
641
|
-
handleThreadId: handle.threadId,
|
|
642
|
-
};
|
|
643
|
-
break;
|
|
644
|
-
}
|
|
645
|
-
catch (error) {
|
|
646
|
-
lastWorkflowError = error;
|
|
647
|
-
threadId = readPersistedWorkflowThreadId(artifacts.queueItemPath) ?? threadId;
|
|
648
|
-
if (threadId) {
|
|
649
|
-
updateQueueWorkflowTracking(db, item.fileId, {
|
|
650
|
-
threadId,
|
|
651
|
-
resultManifestPath: artifacts.resultPath,
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
const recoveredManifest = shouldAttemptManifestRecovery(error)
|
|
655
|
-
? readRecoverableWorkflowResult(artifacts.resultPath, threadId)
|
|
656
|
-
: null;
|
|
657
|
-
if (recoveredManifest) {
|
|
658
|
-
assertTemplateEvolutionAllowed(recoveredManifest, templateEvolution);
|
|
659
|
-
finalOutcome = {
|
|
660
|
-
outcome: applyWorkflowManifest(db, item.fileId, recoveredManifest, artifacts.resultPath),
|
|
661
|
-
manifest: recoveredManifest,
|
|
662
|
-
handleThreadId: recoveredManifest.threadId,
|
|
663
|
-
};
|
|
664
|
-
options.log?.(`${item.fileId}: recovered persisted workflow result status=${recoveredManifest.status} thread=${recoveredManifest.threadId} ${formatManifestLogFields(recoveredManifest)} result=${artifacts.resultPath} message=${formatWorkflowError(error)}`);
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
if (!shouldRetryWorkflowAttempt(error, attempt, maxWorkflowAttempts)) {
|
|
668
|
-
throw error;
|
|
669
|
-
}
|
|
670
|
-
options.log?.(`${item.fileId}: retrying workflow attempt ${attempt + 1}/${maxWorkflowAttempts} thread=${threadId ?? "-"} result=${artifacts.resultPath} message=${formatWorkflowError(error)}`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (!finalOutcome) {
|
|
674
|
-
throw (lastWorkflowError ?? new AppError("Workflow completed without a result", "runtime"));
|
|
675
|
-
}
|
|
676
|
-
options.log?.(`${item.fileId}: ${finalOutcome.outcome.status} thread=${finalOutcome.handleThreadId} ${formatManifestLogFields(finalOutcome.manifest)} result=${artifacts.resultPath}`);
|
|
677
|
-
countOutcome(finalOutcome.outcome.status);
|
|
678
|
-
result.items.push({
|
|
679
|
-
fileId: item.fileId,
|
|
680
|
-
status: finalOutcome.outcome.status,
|
|
681
|
-
pageId: finalOutcome.outcome.pageId,
|
|
682
|
-
reason: finalOutcome.manifest.reason,
|
|
683
|
-
threadId: finalOutcome.handleThreadId,
|
|
684
|
-
decision: finalOutcome.manifest.decision,
|
|
685
|
-
skillsUsed: finalOutcome.manifest.skillsUsed,
|
|
686
|
-
createdPageIds: finalOutcome.manifest.createdPageIds,
|
|
687
|
-
updatedPageIds: finalOutcome.manifest.updatedPageIds,
|
|
688
|
-
proposedTypeNames: finalOutcome.manifest.proposedTypes.map((entry) => entry.name),
|
|
689
|
-
resultManifestPath: artifacts.resultPath,
|
|
690
|
-
});
|
|
902
|
+
const remainingFilterFileIds = options.filterFileIds?.filter((fileId) => !attemptedFileIds.has(fileId));
|
|
903
|
+
if (options.filterFileIds && remainingFilterFileIds?.length === 0) {
|
|
904
|
+
return null;
|
|
691
905
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
continue;
|
|
906
|
+
const item = claimQueueItems(db, 1, {
|
|
907
|
+
filterFileIds: remainingFilterFileIds,
|
|
908
|
+
excludeFileIds: attemptedFileIds,
|
|
909
|
+
})[0];
|
|
910
|
+
if (!item) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
attemptedFileIds.add(item.fileId);
|
|
914
|
+
options.log?.(`claimed 1 items: ${item.fileId}`);
|
|
915
|
+
return {
|
|
916
|
+
sequence: nextSequence++,
|
|
917
|
+
item,
|
|
918
|
+
};
|
|
919
|
+
};
|
|
920
|
+
const workerCount = options.filterFileIds
|
|
921
|
+
? Math.min(workerSlots, options.filterFileIds.length)
|
|
922
|
+
: workerSlots;
|
|
923
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
924
|
+
while (true) {
|
|
925
|
+
const claimed = claimNextQueueItem();
|
|
926
|
+
if (!claimed) {
|
|
927
|
+
return;
|
|
715
928
|
}
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
929
|
+
const processed = await processClaimedQueueItem({
|
|
930
|
+
db,
|
|
931
|
+
env,
|
|
932
|
+
paths,
|
|
933
|
+
item: claimed.item,
|
|
934
|
+
log: options.log,
|
|
935
|
+
workflowRunner,
|
|
936
|
+
templateEvolution,
|
|
937
|
+
maxWorkflowAttempts,
|
|
938
|
+
workflowTimeoutMs,
|
|
721
939
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
status: "error",
|
|
727
|
-
pageId: item.resultPageId ?? null,
|
|
728
|
-
reason: message,
|
|
729
|
-
threadId,
|
|
730
|
-
resultManifestPath,
|
|
940
|
+
countOutcome(processed.status);
|
|
941
|
+
orderedItems.push({
|
|
942
|
+
sequence: claimed.sequence,
|
|
943
|
+
item: processed.item,
|
|
731
944
|
});
|
|
732
945
|
}
|
|
946
|
+
});
|
|
947
|
+
await Promise.all(workers);
|
|
948
|
+
result.items = orderedItems
|
|
949
|
+
.sort((left, right) => left.sequence - right.sequence)
|
|
950
|
+
.map((entry) => entry.item);
|
|
951
|
+
if (result.items.length > 0) {
|
|
952
|
+
options.log?.(`processed ${result.items.length} queue items with workerPool=${workerCount}`);
|
|
733
953
|
}
|
|
734
954
|
return result;
|
|
735
955
|
}
|
package/dist/core/vault.js
CHANGED
|
@@ -441,6 +441,7 @@ export function syncVaultIndex(db, currentFiles, syncId) {
|
|
|
441
441
|
decision,
|
|
442
442
|
result_manifest_path,
|
|
443
443
|
last_error_at,
|
|
444
|
+
last_error_code,
|
|
444
445
|
retry_after,
|
|
445
446
|
created_page_ids,
|
|
446
447
|
updated_page_ids,
|
|
@@ -468,6 +469,7 @@ export function syncVaultIndex(db, currentFiles, syncId) {
|
|
|
468
469
|
NULL,
|
|
469
470
|
NULL,
|
|
470
471
|
NULL,
|
|
472
|
+
NULL,
|
|
471
473
|
NULL
|
|
472
474
|
)
|
|
473
475
|
ON CONFLICT(file_id) DO UPDATE SET
|
|
@@ -493,6 +495,10 @@ export function syncVaultIndex(db, currentFiles, syncId) {
|
|
|
493
495
|
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.error_message
|
|
494
496
|
ELSE NULL
|
|
495
497
|
END,
|
|
498
|
+
attempts = CASE
|
|
499
|
+
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.attempts
|
|
500
|
+
ELSE 0
|
|
501
|
+
END,
|
|
496
502
|
thread_id = CASE
|
|
497
503
|
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.thread_id
|
|
498
504
|
ELSE NULL
|
|
@@ -513,6 +519,10 @@ export function syncVaultIndex(db, currentFiles, syncId) {
|
|
|
513
519
|
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.last_error_at
|
|
514
520
|
ELSE NULL
|
|
515
521
|
END,
|
|
522
|
+
last_error_code = CASE
|
|
523
|
+
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.last_error_code
|
|
524
|
+
ELSE NULL
|
|
525
|
+
END,
|
|
516
526
|
retry_after = CASE
|
|
517
527
|
WHEN vault_processing_queue.status = 'processing' THEN vault_processing_queue.retry_after
|
|
518
528
|
ELSE NULL
|
package/dist/daemon/server.js
CHANGED
|
@@ -511,6 +511,7 @@ export async function runDaemonServer(options) {
|
|
|
511
511
|
while (!stopping) {
|
|
512
512
|
const batchResult = await processVaultQueueBatch(env, {
|
|
513
513
|
log: (message) => logInfo(`queue ${message}`),
|
|
514
|
+
shouldStop: () => stopping,
|
|
514
515
|
});
|
|
515
516
|
if (!batchResult.enabled) {
|
|
516
517
|
break;
|
|
@@ -254,6 +254,7 @@ function buildQueueTiming(item) {
|
|
|
254
254
|
startedAt: item.startedAt ?? null,
|
|
255
255
|
processedAt: item.processedAt,
|
|
256
256
|
lastErrorAt: item.lastErrorAt ?? null,
|
|
257
|
+
lastErrorCode: item.lastErrorCode ?? null,
|
|
257
258
|
retryAfter: item.retryAfter ?? null,
|
|
258
259
|
queueAgeMs: Number.isFinite(queuedAt) ? now - queuedAt : null,
|
|
259
260
|
waitDurationMs: Number.isFinite(claimedAt) && Number.isFinite(queuedAt) ? claimedAt - queuedAt : null,
|
|
@@ -271,8 +272,10 @@ function buildQueueListItem(item) {
|
|
|
271
272
|
status: item.status,
|
|
272
273
|
priority: item.priority,
|
|
273
274
|
attempts: item.attempts,
|
|
275
|
+
autoRetryExhausted: item.autoRetryExhausted ?? false,
|
|
274
276
|
resultPageId: item.resultPageId,
|
|
275
277
|
errorMessage: item.errorMessage,
|
|
278
|
+
lastErrorCode: item.lastErrorCode ?? null,
|
|
276
279
|
threadId: item.threadId ?? null,
|
|
277
280
|
decision: item.decision ?? null,
|
|
278
281
|
workflowVersion: item.workflowVersion ?? null,
|
|
@@ -790,11 +793,13 @@ export function retryDashboardQueueItem(env = process.env, fileId) {
|
|
|
790
793
|
processed_at = NULL,
|
|
791
794
|
result_page_id = NULL,
|
|
792
795
|
error_message = NULL,
|
|
796
|
+
attempts = 0,
|
|
793
797
|
thread_id = NULL,
|
|
794
798
|
workflow_version = NULL,
|
|
795
799
|
decision = NULL,
|
|
796
800
|
result_manifest_path = NULL,
|
|
797
801
|
last_error_at = NULL,
|
|
802
|
+
last_error_code = NULL,
|
|
798
803
|
retry_after = NULL,
|
|
799
804
|
created_page_ids = NULL,
|
|
800
805
|
updated_page_ids = NULL,
|
package/package.json
CHANGED
|
@@ -87,12 +87,14 @@ The agent uses [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk) to p
|
|
|
87
87
|
| `WIKI_AGENT_BASE_URL` | No | LLM API base URL (e.g. `https://api.openai.com/v1`). When set, overrides global Codex config |
|
|
88
88
|
| `WIKI_AGENT_API_KEY` | If enabled | API key for the LLM provider |
|
|
89
89
|
| `WIKI_AGENT_MODEL` | No | Model name (e.g. `gpt-5.4`, `Qwen/Qwen3.5-397B-A17B-GPTQ-Int4`) |
|
|
90
|
-
| `WIKI_AGENT_BATCH_SIZE` | No | Max concurrent vault
|
|
90
|
+
| `WIKI_AGENT_BATCH_SIZE` | No | Max concurrent vault queue workers per cycle (default: `5`) |
|
|
91
91
|
| `WIKI_AGENT_SANDBOX_MODE` | No | Codex sandbox mode: `danger-full-access` (default) or `workspace-write` |
|
|
92
92
|
| `WIKI_PARSER_SKILLS` | No | Comma-separated parser skill list (e.g. `pdf,docx,pptx,xlsx`) |
|
|
93
93
|
|
|
94
94
|
`tiangong-wiki setup` now prompts for `WIKI_AGENT_SANDBOX_MODE` when automatic vault processing is enabled. The default is `danger-full-access`, and the setup wizard highlights that this mode grants full runtime access.
|
|
95
95
|
|
|
96
|
+
Queue items that fail workflow execution are auto-retried up to 3 times. After that they remain in `error` until you manually retry them from the dashboard / queue tooling, or a later vault sync requeues the file because the source changed.
|
|
97
|
+
|
|
96
98
|
---
|
|
97
99
|
|
|
98
100
|
## Common Issues
|