wiki-graph-core 0.3.1
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/LICENSE +201 -0
- package/data/editor/clue_reviewer.jinja +410 -0
- package/data/editor/clue_reviewer_generator.jinja +428 -0
- package/data/editor/revision_feedback.jinja +5 -0
- package/data/editor/text_compressor.jinja +127 -0
- package/data/guaranteed/response_intent_classifier.jinja +8 -0
- package/data/help/commands/archive/next.jinja +29 -0
- package/data/help/commands/gc.jinja +38 -0
- package/data/help/commands/legacy/migrate.jinja +20 -0
- package/data/help/commands/legacy.jinja +19 -0
- package/data/help/commands/maintenance/chapter/add.jinja +41 -0
- package/data/help/commands/maintenance/chapter/list.jinja +33 -0
- package/data/help/commands/maintenance/chapter/move.jinja +36 -0
- package/data/help/commands/maintenance/chapter/remove.jinja +26 -0
- package/data/help/commands/maintenance/chapter/reset.jinja +29 -0
- package/data/help/commands/maintenance/chapter/set-source.jinja +34 -0
- package/data/help/commands/maintenance/chapter/set-summary.jinja +31 -0
- package/data/help/commands/maintenance/chapter/set-title.jinja +29 -0
- package/data/help/commands/maintenance/chapter/tree.jinja +58 -0
- package/data/help/commands/maintenance/chapter.jinja +59 -0
- package/data/help/commands/maintenance/cover.jinja +28 -0
- package/data/help/commands/maintenance/meta.jinja +55 -0
- package/data/help/commands/predicate.jinja +460 -0
- package/data/help/commands/root.jinja +83 -0
- package/data/help/commands/transform.jinja +34 -0
- package/data/help/commands/uri.jinja +274 -0
- package/data/help/topics/config.jinja +88 -0
- package/data/help/topics/format.jinja +72 -0
- package/data/help/topics/readiness.jinja +99 -0
- package/data/help/topics/recipe.jinja +151 -0
- package/data/help/topics/runtime.jinja +62 -0
- package/data/help/topics/uri.jinja +178 -0
- package/data/output/epub/cover.xhtml.jinja +13 -0
- package/data/output/epub/nav.xhtml.jinja +12 -0
- package/data/output/epub/package.opf.xml.jinja +39 -0
- package/data/output/epub/section.xhtml.jinja +12 -0
- package/data/topologization/book_coherence_extraction.jinja +223 -0
- package/data/topologization/chunk_extraction.jinja +233 -0
- package/data/topologization/evidence_choice.jinja +38 -0
- package/data/topologization/translate_chunks.jinja +138 -0
- package/data/topologization/user_focused_extraction.jinja +378 -0
- package/dist/archive/query/archive-view.d.ts +26 -0
- package/dist/archive/query/archive-view.js +4540 -0
- package/dist/archive/query/archive-view.js.map +1 -0
- package/dist/archive/query/continuation-cursor.d.ts +61 -0
- package/dist/archive/query/continuation-cursor.js +363 -0
- package/dist/archive/query/continuation-cursor.js.map +1 -0
- package/dist/archive/query/index.d.ts +29 -0
- package/dist/archive/query/index.js +63 -0
- package/dist/archive/query/index.js.map +1 -0
- package/dist/archive/query/lexical-search.d.ts +22 -0
- package/dist/archive/query/lexical-search.js +137 -0
- package/dist/archive/query/lexical-search.js.map +1 -0
- package/dist/archive/query/search-cache.d.ts +192 -0
- package/dist/archive/query/search-cache.js +1248 -0
- package/dist/archive/query/search-cache.js.map +1 -0
- package/dist/archive/search-index/index.d.ts +26 -0
- package/dist/archive/search-index/index.js +29 -0
- package/dist/archive/search-index/index.js.map +1 -0
- package/dist/archive/search-index/search-index.d.ts +26 -0
- package/dist/archive/search-index/search-index.js +555 -0
- package/dist/archive/search-index/search-index.js.map +1 -0
- package/dist/archive/search-index/search-tokenizer.d.ts +16 -0
- package/dist/archive/search-index/search-tokenizer.js +225 -0
- package/dist/archive/search-index/search-tokenizer.js.map +1 -0
- package/dist/common/cli-command.d.ts +4 -0
- package/dist/common/cli-command.js +7 -0
- package/dist/common/cli-command.js.map +1 -0
- package/dist/common/data-dir.d.ts +3 -0
- package/dist/common/data-dir.js +48 -0
- package/dist/common/data-dir.js.map +1 -0
- package/dist/common/language.d.ts +55 -0
- package/dist/common/language.js +208 -0
- package/dist/common/language.js.map +1 -0
- package/dist/common/llm-scope.d.ts +19 -0
- package/dist/common/llm-scope.js +31 -0
- package/dist/common/llm-scope.js.map +1 -0
- package/dist/common/logging.d.ts +22 -0
- package/dist/common/logging.js +141 -0
- package/dist/common/logging.js.map +1 -0
- package/dist/common/template.d.ts +8 -0
- package/dist/common/template.js +54 -0
- package/dist/common/template.js.map +1 -0
- package/dist/common/tinyld-language.d.ts +6 -0
- package/dist/common/tinyld-language.js +34 -0
- package/dist/common/tinyld-language.js.map +1 -0
- package/dist/common/wiki-graph-dir.d.ts +10 -0
- package/dist/common/wiki-graph-dir.js +41 -0
- package/dist/common/wiki-graph-dir.js.map +1 -0
- package/dist/common/wiki-graph-temp.d.ts +6 -0
- package/dist/common/wiki-graph-temp.js +21 -0
- package/dist/common/wiki-graph-temp.js.map +1 -0
- package/dist/common/wiki-graph-uri.d.ts +28 -0
- package/dist/common/wiki-graph-uri.js +171 -0
- package/dist/common/wiki-graph-uri.js.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +13 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/task-context.d.ts +41 -0
- package/dist/context/task-context.js +111 -0
- package/dist/context/task-context.js.map +1 -0
- package/dist/document/database.d.ts +30 -0
- package/dist/document/database.js +273 -0
- package/dist/document/database.js.map +1 -0
- package/dist/document/document.d.ts +153 -0
- package/dist/document/document.js +615 -0
- package/dist/document/document.js.map +1 -0
- package/dist/document/fragments.d.ts +63 -0
- package/dist/document/fragments.js +371 -0
- package/dist/document/fragments.js.map +1 -0
- package/dist/document/index.d.ts +13 -0
- package/dist/document/index.js +62 -0
- package/dist/document/index.js.map +1 -0
- package/dist/document/schema.d.ts +8 -0
- package/dist/document/schema.js +472 -0
- package/dist/document/schema.js.map +1 -0
- package/dist/document/shared-state-database.d.ts +9 -0
- package/dist/document/shared-state-database.js +193 -0
- package/dist/document/shared-state-database.js.map +1 -0
- package/dist/document/stores.d.ts +203 -0
- package/dist/document/stores.js +1678 -0
- package/dist/document/stores.js.map +1 -0
- package/dist/document/text-streams.d.ts +86 -0
- package/dist/document/text-streams.js +515 -0
- package/dist/document/text-streams.js.map +1 -0
- package/dist/document/types.d.ts +148 -0
- package/dist/document/types.js +69 -0
- package/dist/document/types.js.map +1 -0
- package/dist/editor/clue.d.ts +27 -0
- package/dist/editor/clue.js +158 -0
- package/dist/editor/clue.js.map +1 -0
- package/dist/editor/compressor.d.ts +18 -0
- package/dist/editor/compressor.js +68 -0
- package/dist/editor/compressor.js.map +1 -0
- package/dist/editor/editor.d.ts +39 -0
- package/dist/editor/editor.js +253 -0
- package/dist/editor/editor.js.map +1 -0
- package/dist/editor/feedback.d.ts +16 -0
- package/dist/editor/feedback.js +104 -0
- package/dist/editor/feedback.js.map +1 -0
- package/dist/editor/index.d.ts +17 -0
- package/dist/editor/index.js +5 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/language-review.d.ts +14 -0
- package/dist/editor/language-review.js +35 -0
- package/dist/editor/language-review.js.map +1 -0
- package/dist/editor/log.d.ts +37 -0
- package/dist/editor/log.js +202 -0
- package/dist/editor/log.js.map +1 -0
- package/dist/editor/markup.d.ts +20 -0
- package/dist/editor/markup.js +294 -0
- package/dist/editor/markup.js.map +1 -0
- package/dist/editor/prompt-templates.d.ts +6 -0
- package/dist/editor/prompt-templates.js +11 -0
- package/dist/editor/prompt-templates.js.map +1 -0
- package/dist/editor/response.d.ts +5 -0
- package/dist/editor/response.js +31 -0
- package/dist/editor/response.js.map +1 -0
- package/dist/editor/review.d.ts +34 -0
- package/dist/editor/review.js +189 -0
- package/dist/editor/review.js.map +1 -0
- package/dist/editor/types.d.ts +31 -0
- package/dist/editor/types.js +21 -0
- package/dist/editor/types.js.map +1 -0
- package/dist/evidence-selection/anchor-resolver.d.ts +43 -0
- package/dist/evidence-selection/anchor-resolver.js +783 -0
- package/dist/evidence-selection/anchor-resolver.js.map +1 -0
- package/dist/evidence-selection/anchor-types.d.ts +27 -0
- package/dist/evidence-selection/anchor-types.js +1 -0
- package/dist/evidence-selection/anchor-types.js.map +1 -0
- package/dist/evidence-selection/index.d.ts +8 -0
- package/dist/evidence-selection/index.js +32 -0
- package/dist/evidence-selection/index.js.map +1 -0
- package/dist/evidence-selection/prompt.d.ts +18 -0
- package/dist/evidence-selection/prompt.js +54 -0
- package/dist/evidence-selection/prompt.js.map +1 -0
- package/dist/evidence-selection/quote-score.d.ts +29 -0
- package/dist/evidence-selection/quote-score.js +331 -0
- package/dist/evidence-selection/quote-score.js.map +1 -0
- package/dist/evidence-selection/selection-resolver.d.ts +19 -0
- package/dist/evidence-selection/selection-resolver.js +303 -0
- package/dist/evidence-selection/selection-resolver.js.map +1 -0
- package/dist/evidence-selection/text.d.ts +4 -0
- package/dist/evidence-selection/text.js +66 -0
- package/dist/evidence-selection/text.js.map +1 -0
- package/dist/evidence-selection/types.d.ts +36 -0
- package/dist/evidence-selection/types.js +1 -0
- package/dist/evidence-selection/types.js.map +1 -0
- package/dist/facade/app.d.ts +75 -0
- package/dist/facade/app.js +141 -0
- package/dist/facade/app.js.map +1 -0
- package/dist/facade/build-queue.d.ts +181 -0
- package/dist/facade/build-queue.js +1442 -0
- package/dist/facade/build-queue.js.map +1 -0
- package/dist/facade/chapter-build.d.ts +60 -0
- package/dist/facade/chapter-build.js +1020 -0
- package/dist/facade/chapter-build.js.map +1 -0
- package/dist/facade/chapter.d.ts +168 -0
- package/dist/facade/chapter.js +944 -0
- package/dist/facade/chapter.js.map +1 -0
- package/dist/facade/digest.d.ts +55 -0
- package/dist/facade/digest.js +168 -0
- package/dist/facade/digest.js.map +1 -0
- package/dist/facade/graph.d.ts +60 -0
- package/dist/facade/graph.js +203 -0
- package/dist/facade/graph.js.map +1 -0
- package/dist/facade/import.d.ts +49 -0
- package/dist/facade/import.js +262 -0
- package/dist/facade/import.js.map +1 -0
- package/dist/facade/index.d.ts +39 -0
- package/dist/facade/index.js +171 -0
- package/dist/facade/index.js.map +1 -0
- package/dist/facade/knowledge-graph-build.d.ts +75 -0
- package/dist/facade/knowledge-graph-build.js +897 -0
- package/dist/facade/knowledge-graph-build.js.map +1 -0
- package/dist/facade/llm-sampling.d.ts +11 -0
- package/dist/facade/llm-sampling.js +58 -0
- package/dist/facade/llm-sampling.js.map +1 -0
- package/dist/facade/types.d.ts +8 -0
- package/dist/facade/types.js +1 -0
- package/dist/facade/types.js.map +1 -0
- package/dist/facade/wiki-graph-archive.d.ts +41 -0
- package/dist/facade/wiki-graph-archive.js +130 -0
- package/dist/facade/wiki-graph-archive.js.map +1 -0
- package/dist/gc/files.d.ts +15 -0
- package/dist/gc/files.js +141 -0
- package/dist/gc/files.js.map +1 -0
- package/dist/gc/index.d.ts +2 -0
- package/dist/gc/index.js +5 -0
- package/dist/gc/index.js.map +1 -0
- package/dist/gc/lock.d.ts +3 -0
- package/dist/gc/lock.js +137 -0
- package/dist/gc/lock.js.map +1 -0
- package/dist/gc/runner.d.ts +9 -0
- package/dist/gc/runner.js +129 -0
- package/dist/gc/runner.js.map +1 -0
- package/dist/gc/tmp-gc.d.ts +5 -0
- package/dist/gc/tmp-gc.js +57 -0
- package/dist/gc/tmp-gc.js.map +1 -0
- package/dist/gc/types.d.ts +24 -0
- package/dist/gc/types.js +1 -0
- package/dist/gc/types.js.map +1 -0
- package/dist/gc.cjs +10041 -0
- package/dist/gc.cjs.map +1 -0
- package/dist/gc.d.cts +10 -0
- package/dist/gc.d.ts +2 -0
- package/dist/gc.js +5 -0
- package/dist/gc.js.map +1 -0
- package/dist/guaranteed/classifier.d.ts +10 -0
- package/dist/guaranteed/classifier.js +88 -0
- package/dist/guaranteed/classifier.js.map +1 -0
- package/dist/guaranteed/errors.d.ts +38 -0
- package/dist/guaranteed/errors.js +73 -0
- package/dist/guaranteed/errors.js.map +1 -0
- package/dist/guaranteed/index.d.ts +7 -0
- package/dist/guaranteed/index.js +21 -0
- package/dist/guaranteed/index.js.map +1 -0
- package/dist/guaranteed/request.d.ts +8 -0
- package/dist/guaranteed/request.js +206 -0
- package/dist/guaranteed/request.js.map +1 -0
- package/dist/guaranteed/response.d.ts +12 -0
- package/dist/guaranteed/response.js +98 -0
- package/dist/guaranteed/response.js.map +1 -0
- package/dist/guaranteed/types.d.ts +20 -0
- package/dist/guaranteed/types.js +1 -0
- package/dist/guaranteed/types.js.map +1 -0
- package/dist/index-B8NNnQ8P.d.ts +424 -0
- package/dist/index.cjs +34774 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2462 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +168 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy-sdpub/upgrade.d.ts +7 -0
- package/dist/legacy-sdpub/upgrade.js +795 -0
- package/dist/legacy-sdpub/upgrade.js.map +1 -0
- package/dist/llm/cache.d.ts +13 -0
- package/dist/llm/cache.js +35 -0
- package/dist/llm/cache.js.map +1 -0
- package/dist/llm/client.d.ts +26 -0
- package/dist/llm/client.js +572 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/context.d.ts +22 -0
- package/dist/llm/context.js +78 -0
- package/dist/llm/context.js.map +1 -0
- package/dist/llm/errors.d.ts +7 -0
- package/dist/llm/errors.js +12 -0
- package/dist/llm/errors.js.map +1 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/index.js +17 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/request-log.d.ts +9 -0
- package/dist/llm/request-log.js +35 -0
- package/dist/llm/request-log.js.map +1 -0
- package/dist/llm/sampling.d.ts +11 -0
- package/dist/llm/sampling.js +67 -0
- package/dist/llm/sampling.js.map +1 -0
- package/dist/llm/types.d.ts +50 -0
- package/dist/llm/types.js +1 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/output/epub/archive.d.ts +9 -0
- package/dist/output/epub/archive.js +81 -0
- package/dist/output/epub/archive.js.map +1 -0
- package/dist/output/epub/book.d.ts +15 -0
- package/dist/output/epub/book.js +76 -0
- package/dist/output/epub/book.js.map +1 -0
- package/dist/output/epub/content.d.ts +9 -0
- package/dist/output/epub/content.js +35 -0
- package/dist/output/epub/content.js.map +1 -0
- package/dist/output/epub/index.d.ts +20 -0
- package/dist/output/epub/index.js +14 -0
- package/dist/output/epub/index.js.map +1 -0
- package/dist/output/epub/model.d.ts +24 -0
- package/dist/output/epub/model.js +1 -0
- package/dist/output/epub/model.js.map +1 -0
- package/dist/output/epub/navigation.d.ts +10 -0
- package/dist/output/epub/navigation.js +39 -0
- package/dist/output/epub/navigation.js.map +1 -0
- package/dist/output/epub/package.d.ts +17 -0
- package/dist/output/epub/package.js +29 -0
- package/dist/output/epub/package.js.map +1 -0
- package/dist/output/epub/shared.d.ts +4 -0
- package/dist/output/epub/shared.js +12 -0
- package/dist/output/epub/shared.js.map +1 -0
- package/dist/output/epub/templates.d.ts +35 -0
- package/dist/output/epub/templates.js +26 -0
- package/dist/output/epub/templates.js.map +1 -0
- package/dist/output/index.d.ts +14 -0
- package/dist/output/index.js +11 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/plain-text.d.ts +18 -0
- package/dist/output/plain-text.js +56 -0
- package/dist/output/plain-text.js.map +1 -0
- package/dist/progress/index.d.ts +3 -0
- package/dist/progress/index.js +14 -0
- package/dist/progress/index.js.map +1 -0
- package/dist/progress/reporter.d.ts +10 -0
- package/dist/progress/reporter.js +79 -0
- package/dist/progress/reporter.js.map +1 -0
- package/dist/progress/tracker.d.ts +37 -0
- package/dist/progress/tracker.js +119 -0
- package/dist/progress/tracker.js.map +1 -0
- package/dist/progress/types.d.ts +28 -0
- package/dist/progress/types.js +1 -0
- package/dist/progress/types.js.map +1 -0
- package/dist/reader/attention/attention.d.ts +39 -0
- package/dist/reader/attention/attention.js +123 -0
- package/dist/reader/attention/attention.js.map +1 -0
- package/dist/reader/attention/index.d.ts +9 -0
- package/dist/reader/attention/index.js +5 -0
- package/dist/reader/attention/index.js.map +1 -0
- package/dist/reader/attention/wave-reflection.d.ts +21 -0
- package/dist/reader/attention/wave-reflection.js +153 -0
- package/dist/reader/attention/wave-reflection.js.map +1 -0
- package/dist/reader/attention/working-memory.d.ts +28 -0
- package/dist/reader/attention/working-memory.js +62 -0
- package/dist/reader/attention/working-memory.js.map +1 -0
- package/dist/reader/chunk-batch/extract.d.ts +13 -0
- package/dist/reader/chunk-batch/extract.js +14 -0
- package/dist/reader/chunk-batch/extract.js.map +1 -0
- package/dist/reader/chunk-batch/extractor.d.ts +17 -0
- package/dist/reader/chunk-batch/extractor.js +337 -0
- package/dist/reader/chunk-batch/extractor.js.map +1 -0
- package/dist/reader/chunk-batch/fragment-projection.d.ts +34 -0
- package/dist/reader/chunk-batch/fragment-projection.js +94 -0
- package/dist/reader/chunk-batch/fragment-projection.js.map +1 -0
- package/dist/reader/chunk-batch/index.d.ts +12 -0
- package/dist/reader/chunk-batch/index.js +13 -0
- package/dist/reader/chunk-batch/index.js.map +1 -0
- package/dist/reader/chunk-batch/language.d.ts +9 -0
- package/dist/reader/chunk-batch/language.js +23 -0
- package/dist/reader/chunk-batch/language.js.map +1 -0
- package/dist/reader/chunk-batch/parser.d.ts +94 -0
- package/dist/reader/chunk-batch/parser.js +640 -0
- package/dist/reader/chunk-batch/parser.js.map +1 -0
- package/dist/reader/chunk-batch/prompt-templates.d.ts +6 -0
- package/dist/reader/chunk-batch/prompt-templates.js +11 -0
- package/dist/reader/chunk-batch/prompt-templates.js.map +1 -0
- package/dist/reader/chunk-batch/types.d.ts +85 -0
- package/dist/reader/chunk-batch/types.js +1 -0
- package/dist/reader/chunk-batch/types.js.map +1 -0
- package/dist/reader/index.d.ts +13 -0
- package/dist/reader/index.js +7 -0
- package/dist/reader/index.js.map +1 -0
- package/dist/reader/reader.d.ts +37 -0
- package/dist/reader/reader.js +82 -0
- package/dist/reader/reader.js.map +1 -0
- package/dist/reader/segment/index.d.ts +2 -0
- package/dist/reader/segment/index.js +5 -0
- package/dist/reader/segment/index.js.map +1 -0
- package/dist/reader/segment/intl-segmenter.d.ts +5 -0
- package/dist/reader/segment/intl-segmenter.js +102 -0
- package/dist/reader/segment/intl-segmenter.js.map +1 -0
- package/dist/reader/segment/segment.d.ts +5 -0
- package/dist/reader/segment/segment.js +9 -0
- package/dist/reader/segment/segment.js.map +1 -0
- package/dist/reader/segment/types.d.ts +14 -0
- package/dist/reader/segment/types.js +1 -0
- package/dist/reader/segment/types.js.map +1 -0
- package/dist/reader/types.d.ts +29 -0
- package/dist/reader/types.js +1 -0
- package/dist/reader/types.js.map +1 -0
- package/dist/serial.d.ts +89 -0
- package/dist/serial.js +476 -0
- package/dist/serial.js.map +1 -0
- package/dist/source/adapter.d.ts +15 -0
- package/dist/source/adapter.js +1 -0
- package/dist/source/adapter.js.map +1 -0
- package/dist/source/epub/archive.d.ts +27 -0
- package/dist/source/epub/archive.js +189 -0
- package/dist/source/epub/archive.js.map +1 -0
- package/dist/source/epub/content.d.ts +22 -0
- package/dist/source/epub/content.js +183 -0
- package/dist/source/epub/content.js.map +1 -0
- package/dist/source/epub/document.d.ts +55 -0
- package/dist/source/epub/document.js +179 -0
- package/dist/source/epub/document.js.map +1 -0
- package/dist/source/epub/index.d.ts +10 -0
- package/dist/source/epub/index.js +6 -0
- package/dist/source/epub/index.js.map +1 -0
- package/dist/source/epub/navigation.d.ts +16 -0
- package/dist/source/epub/navigation.js +215 -0
- package/dist/source/epub/navigation.js.map +1 -0
- package/dist/source/epub/package.d.ts +33 -0
- package/dist/source/epub/package.js +237 -0
- package/dist/source/epub/package.js.map +1 -0
- package/dist/source/epub/xml.d.ts +16 -0
- package/dist/source/epub/xml.js +111 -0
- package/dist/source/epub/xml.js.map +1 -0
- package/dist/source/index.d.ts +12 -0
- package/dist/source/index.js +32 -0
- package/dist/source/index.js.map +1 -0
- package/dist/source/meta.d.ts +30 -0
- package/dist/source/meta.js +22 -0
- package/dist/source/meta.js.map +1 -0
- package/dist/source/plain-text.d.ts +16 -0
- package/dist/source/plain-text.js +86 -0
- package/dist/source/plain-text.js.map +1 -0
- package/dist/source/toc.d.ts +16 -0
- package/dist/source/toc.js +17 -0
- package/dist/source/toc.js.map +1 -0
- package/dist/source/types.d.ts +16 -0
- package/dist/source/types.js +1 -0
- package/dist/source/types.js.map +1 -0
- package/dist/topology/grouping.d.ts +15 -0
- package/dist/topology/grouping.js +18 -0
- package/dist/topology/grouping.js.map +1 -0
- package/dist/topology/index.d.ts +21 -0
- package/dist/topology/index.js +5 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/topology/resource-segmentation.d.ts +13 -0
- package/dist/topology/resource-segmentation.js +111 -0
- package/dist/topology/resource-segmentation.js.map +1 -0
- package/dist/topology/segment-incision.d.ts +18 -0
- package/dist/topology/segment-incision.js +197 -0
- package/dist/topology/segment-incision.js.map +1 -0
- package/dist/topology/snake-detector.d.ts +13 -0
- package/dist/topology/snake-detector.js +520 -0
- package/dist/topology/snake-detector.js.map +1 -0
- package/dist/topology/snake-graph-builder.d.ts +14 -0
- package/dist/topology/snake-graph-builder.js +76 -0
- package/dist/topology/snake-graph-builder.js.map +1 -0
- package/dist/topology/topology.d.ts +29 -0
- package/dist/topology/topology.js +348 -0
- package/dist/topology/topology.js.map +1 -0
- package/dist/topology/weights.d.ts +10 -0
- package/dist/topology/weights.js +84 -0
- package/dist/topology/weights.js.map +1 -0
- package/dist/types-FVIqiLC9.d.cts +24 -0
- package/dist/utils/async-semaphore.d.ts +7 -0
- package/dist/utils/async-semaphore.js +40 -0
- package/dist/utils/async-semaphore.js.map +1 -0
- package/dist/utils/enum.d.ts +5 -0
- package/dist/utils/enum.js +18 -0
- package/dist/utils/enum.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.js +132 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/node-error.d.ts +4 -0
- package/dist/utils/node-error.js +59 -0
- package/dist/utils/node-error.js.map +1 -0
- package/dist/wikg/archive-uri.d.ts +1 -0
- package/dist/wikg/archive-uri.js +37 -0
- package/dist/wikg/archive-uri.js.map +1 -0
- package/dist/wikg/archive.d.ts +28 -0
- package/dist/wikg/archive.js +496 -0
- package/dist/wikg/archive.js.map +1 -0
- package/dist/wikg/index.d.ts +31 -0
- package/dist/wikg/index.js +60 -0
- package/dist/wikg/index.js.map +1 -0
- package/dist/wikg/wikg-coordinator.d.ts +45 -0
- package/dist/wikg/wikg-coordinator.js +1687 -0
- package/dist/wikg/wikg-coordinator.js.map +1 -0
- package/dist/wikg/wiki-graph-archive-file.d.ts +42 -0
- package/dist/wikg/wiki-graph-archive-file.js +78 -0
- package/dist/wikg/wiki-graph-archive-file.js.map +1 -0
- package/dist/wikilink/index.d.ts +10 -0
- package/dist/wikilink/index.js +7 -0
- package/dist/wikilink/index.js.map +1 -0
- package/dist/wikilink/relation-discovery.d.ts +31 -0
- package/dist/wikilink/relation-discovery.js +477 -0
- package/dist/wikilink/relation-discovery.js.map +1 -0
- package/dist/wikilink/types.d.ts +23 -0
- package/dist/wikilink/types.js +1 -0
- package/dist/wikilink/types.js.map +1 -0
- package/dist/wikilink/window.d.ts +7 -0
- package/dist/wikilink/window.js +53 -0
- package/dist/wikilink/window.js.map +1 -0
- package/dist/wikimatch/enrichment.d.ts +10 -0
- package/dist/wikimatch/enrichment.js +55 -0
- package/dist/wikimatch/enrichment.js.map +1 -0
- package/dist/wikimatch/index.d.ts +16 -0
- package/dist/wikimatch/index.js +66 -0
- package/dist/wikimatch/index.js.map +1 -0
- package/dist/wikimatch/option-narrowing.d.ts +17 -0
- package/dist/wikimatch/option-narrowing.js +208 -0
- package/dist/wikimatch/option-narrowing.js.map +1 -0
- package/dist/wikimatch/options.d.ts +14 -0
- package/dist/wikimatch/options.js +155 -0
- package/dist/wikimatch/options.js.map +1 -0
- package/dist/wikimatch/policy-judge.d.ts +17 -0
- package/dist/wikimatch/policy-judge.js +430 -0
- package/dist/wikimatch/policy-judge.js.map +1 -0
- package/dist/wikimatch/range-suppression.d.ts +9 -0
- package/dist/wikimatch/range-suppression.js +27 -0
- package/dist/wikimatch/range-suppression.js.map +1 -0
- package/dist/wikimatch/surface-screening.d.ts +16 -0
- package/dist/wikimatch/surface-screening.js +168 -0
- package/dist/wikimatch/surface-screening.js.map +1 -0
- package/dist/wikimatch/surface-window.d.ts +6 -0
- package/dist/wikimatch/surface-window.js +44 -0
- package/dist/wikimatch/surface-window.js.map +1 -0
- package/dist/wikimatch/types.d.ts +151 -0
- package/dist/wikimatch/types.js +1 -0
- package/dist/wikimatch/types.js.map +1 -0
- package/dist/wikimatch/wikispine.d.ts +43 -0
- package/dist/wikimatch/wikispine.js +399 -0
- package/dist/wikimatch/wikispine.js.map +1 -0
- package/dist/wikimatch/window.d.ts +6 -0
- package/dist/wikimatch/window.js +83 -0
- package/dist/wikimatch/window.js.map +1 -0
- package/dist/wikimatch/words.d.ts +16 -0
- package/dist/wikimatch/words.js +66 -0
- package/dist/wikimatch/words.js.map +1 -0
- package/dist/wikipage/cache.d.ts +18 -0
- package/dist/wikipage/cache.js +404 -0
- package/dist/wikipage/cache.js.map +1 -0
- package/dist/wikipage/fetch-log.d.ts +17 -0
- package/dist/wikipage/fetch-log.js +143 -0
- package/dist/wikipage/fetch-log.js.map +1 -0
- package/dist/wikipage/index.d.ts +14 -0
- package/dist/wikipage/index.js +17 -0
- package/dist/wikipage/index.js.map +1 -0
- package/dist/wikipage/normalizer.d.ts +14 -0
- package/dist/wikipage/normalizer.js +147 -0
- package/dist/wikipage/normalizer.js.map +1 -0
- package/dist/wikipage/rate-limiter.d.ts +13 -0
- package/dist/wikipage/rate-limiter.js +84 -0
- package/dist/wikipage/rate-limiter.js.map +1 -0
- package/dist/wikipage/resolver.d.ts +26 -0
- package/dist/wikipage/resolver.js +325 -0
- package/dist/wikipage/resolver.js.map +1 -0
- package/dist/wikipage/types.d.ts +96 -0
- package/dist/wikipage/types.js +1 -0
- package/dist/wikipage/types.js.map +1 -0
- package/dist/wikipage/wikimedia-client.d.ts +48 -0
- package/dist/wikipage/wikimedia-client.js +397 -0
- package/dist/wikipage/wikimedia-client.js.map +1 -0
- package/dist/worker-BgQ0Xl8T.d.cts +181 -0
- package/dist/worker.cjs +5259 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +5 -0
- package/dist/worker.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,1442 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "crypto";
|
|
2
|
+
import { appendFile, mkdir, readFile, rm } from "fs/promises";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
|
+
import { resolveWikiGraphJobsDirectoryPath } from "../common/wiki-graph-dir.js";
|
|
5
|
+
import { openSharedStateDatabase } from "../document/index.js";
|
|
6
|
+
import { readPathSize, removeDisposableChildDirectories } from "../gc/files.js";
|
|
7
|
+
const BUILD_JOB_STATES = [
|
|
8
|
+
"queued",
|
|
9
|
+
"running",
|
|
10
|
+
"canceling",
|
|
11
|
+
"paused",
|
|
12
|
+
"succeeded",
|
|
13
|
+
"failed",
|
|
14
|
+
"canceled"
|
|
15
|
+
];
|
|
16
|
+
const BUILD_QUEUE_SCHEMA_SQL = `
|
|
17
|
+
CREATE TABLE IF NOT EXISTS build_jobs (
|
|
18
|
+
job_id TEXT PRIMARY KEY,
|
|
19
|
+
archive_key TEXT NOT NULL,
|
|
20
|
+
archive_path TEXT NOT NULL,
|
|
21
|
+
chapter_id INTEGER NOT NULL,
|
|
22
|
+
target TEXT NOT NULL,
|
|
23
|
+
current_step TEXT,
|
|
24
|
+
state TEXT NOT NULL,
|
|
25
|
+
queue_rank INTEGER NOT NULL,
|
|
26
|
+
workspace_path TEXT NOT NULL,
|
|
27
|
+
cache_path TEXT NOT NULL,
|
|
28
|
+
log_path TEXT NOT NULL,
|
|
29
|
+
events_path TEXT NOT NULL,
|
|
30
|
+
input_revision INTEGER,
|
|
31
|
+
llm_json TEXT,
|
|
32
|
+
prompt TEXT,
|
|
33
|
+
owner_id TEXT,
|
|
34
|
+
owner_pid INTEGER,
|
|
35
|
+
reading_summary_started_at INTEGER,
|
|
36
|
+
created_at INTEGER NOT NULL,
|
|
37
|
+
updated_at INTEGER NOT NULL,
|
|
38
|
+
finished_at INTEGER,
|
|
39
|
+
error_json TEXT
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
DROP INDEX IF EXISTS idx_build_jobs_active_chapter;
|
|
43
|
+
|
|
44
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_build_jobs_active_knowledge_chapter
|
|
45
|
+
ON build_jobs(archive_key, chapter_id)
|
|
46
|
+
WHERE target = 'knowledge-graph'
|
|
47
|
+
AND state IN ('queued', 'running', 'canceling', 'paused');
|
|
48
|
+
|
|
49
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_build_jobs_active_reading_chapter
|
|
50
|
+
ON build_jobs(archive_key, chapter_id)
|
|
51
|
+
WHERE target IN ('reading-graph', 'reading-summary')
|
|
52
|
+
AND state IN ('queued', 'running', 'canceling', 'paused');
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_build_jobs_queue
|
|
55
|
+
ON build_jobs(state, queue_rank, updated_at);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS build_worker_lease (
|
|
58
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
59
|
+
owner_id TEXT,
|
|
60
|
+
owner_pid INTEGER,
|
|
61
|
+
heartbeat_at INTEGER
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
INSERT OR IGNORE INTO build_worker_lease (id)
|
|
65
|
+
VALUES (1);
|
|
66
|
+
`;
|
|
67
|
+
const ACTIVE_JOB_STATES = /* @__PURE__ */ new Set([
|
|
68
|
+
"queued",
|
|
69
|
+
"running",
|
|
70
|
+
"canceling",
|
|
71
|
+
"paused"
|
|
72
|
+
]);
|
|
73
|
+
const WORKER_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
74
|
+
async function addBuildJob(options) {
|
|
75
|
+
const state = await openBuildQueueDatabase();
|
|
76
|
+
try {
|
|
77
|
+
await recoverStaleBuildJobs(state);
|
|
78
|
+
return await state.transaction(async () => {
|
|
79
|
+
const archivePath = resolve(options.archivePath);
|
|
80
|
+
const archiveKey = createArchiveKey(archivePath);
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const existing = await findActiveBuildJobInLane(state, {
|
|
83
|
+
archiveKey,
|
|
84
|
+
chapterId: options.chapterId,
|
|
85
|
+
target: options.target
|
|
86
|
+
});
|
|
87
|
+
if (existing !== void 0) {
|
|
88
|
+
return await mergeActiveBuildJob(state, existing, options, now);
|
|
89
|
+
}
|
|
90
|
+
const jobId = options.jobId ?? randomUUID();
|
|
91
|
+
const workspacePath = await createJobWorkspacePath(jobId);
|
|
92
|
+
const cachePath = await createJobCachePath(jobId);
|
|
93
|
+
const logPath = await createJobLogPath(jobId);
|
|
94
|
+
const eventsPath = await createJobEventsPath(jobId);
|
|
95
|
+
const queueRank = options.boost === true ? await readMinQueueRank(state) - 1 : await readMaxQueueRank(state) + 1;
|
|
96
|
+
await state.run(
|
|
97
|
+
`
|
|
98
|
+
INSERT INTO build_jobs (
|
|
99
|
+
job_id, archive_key, archive_path, chapter_id, target, state, queue_rank,
|
|
100
|
+
workspace_path, cache_path, log_path, events_path, llm_json, prompt,
|
|
101
|
+
created_at, updated_at
|
|
102
|
+
) VALUES (?, ?, ?, ?, ?, 'queued', ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
103
|
+
`,
|
|
104
|
+
[
|
|
105
|
+
jobId,
|
|
106
|
+
archiveKey,
|
|
107
|
+
archivePath,
|
|
108
|
+
options.chapterId,
|
|
109
|
+
options.target,
|
|
110
|
+
queueRank,
|
|
111
|
+
workspacePath,
|
|
112
|
+
cachePath,
|
|
113
|
+
logPath,
|
|
114
|
+
eventsPath,
|
|
115
|
+
options.llmJSON ?? null,
|
|
116
|
+
options.prompt ?? null,
|
|
117
|
+
now,
|
|
118
|
+
now
|
|
119
|
+
]
|
|
120
|
+
);
|
|
121
|
+
const job = await requireBuildJobById(state, jobId);
|
|
122
|
+
await appendBuildJobEvent(job, {
|
|
123
|
+
at: now,
|
|
124
|
+
jobId,
|
|
125
|
+
seq: 0,
|
|
126
|
+
state: "queued",
|
|
127
|
+
type: "created"
|
|
128
|
+
});
|
|
129
|
+
return job;
|
|
130
|
+
});
|
|
131
|
+
} finally {
|
|
132
|
+
await state.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function recordBuildJobInputRevision(input) {
|
|
136
|
+
const state = await openBuildQueueDatabase();
|
|
137
|
+
try {
|
|
138
|
+
await state.transaction(async () => {
|
|
139
|
+
await state.run(
|
|
140
|
+
`
|
|
141
|
+
UPDATE build_jobs
|
|
142
|
+
SET input_revision = ?,
|
|
143
|
+
updated_at = ?
|
|
144
|
+
WHERE job_id = ?
|
|
145
|
+
AND owner_id = ?
|
|
146
|
+
AND state = 'running'
|
|
147
|
+
`,
|
|
148
|
+
[input.currentRevision, Date.now(), input.jobId, input.ownerId]
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
return await requireBuildJobById(state, input.jobId);
|
|
152
|
+
} finally {
|
|
153
|
+
await state.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function assertBuildJobInputRevision(input) {
|
|
157
|
+
const job = await getBuildJob(input.jobId);
|
|
158
|
+
if (job.state !== "running" || job.ownerId !== input.ownerId) {
|
|
159
|
+
throw new BuildJobStoppedError(
|
|
160
|
+
`Job ${input.jobId} is ${job.state}. Stop current worker execution.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (job.inputRevision === void 0) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Job ${input.jobId} has no recorded chapter input revision. Requeue the job before committing build output.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (job.inputRevision === input.currentRevision) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Chapter ${job.chapterId} changed while job ${job.jobId} was running. Requeue the job before committing build output.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
async function listBuildJobs(options = {}) {
|
|
176
|
+
const state = await openBuildQueueDatabase();
|
|
177
|
+
try {
|
|
178
|
+
await recoverStaleBuildJobs(state);
|
|
179
|
+
const params = [];
|
|
180
|
+
const filters = [];
|
|
181
|
+
if (options.archivePath !== void 0) {
|
|
182
|
+
params.push(createArchiveKey(resolve(options.archivePath)));
|
|
183
|
+
filters.push("archive_key = ?");
|
|
184
|
+
}
|
|
185
|
+
if (options.activeOnly === true || options.all !== true) {
|
|
186
|
+
filters.push("state IN ('queued', 'running', 'canceling', 'paused')");
|
|
187
|
+
}
|
|
188
|
+
return await state.queryAll(
|
|
189
|
+
`
|
|
190
|
+
SELECT *
|
|
191
|
+
FROM build_jobs
|
|
192
|
+
${filters.length === 0 ? "" : `WHERE ${filters.join(" AND ")}`}
|
|
193
|
+
ORDER BY
|
|
194
|
+
CASE state
|
|
195
|
+
WHEN 'running' THEN 0
|
|
196
|
+
WHEN 'canceling' THEN 1
|
|
197
|
+
WHEN 'queued' THEN 2
|
|
198
|
+
WHEN 'paused' THEN 3
|
|
199
|
+
ELSE 4
|
|
200
|
+
END,
|
|
201
|
+
queue_rank ASC,
|
|
202
|
+
updated_at DESC
|
|
203
|
+
`,
|
|
204
|
+
params,
|
|
205
|
+
mapBuildJob
|
|
206
|
+
);
|
|
207
|
+
} finally {
|
|
208
|
+
await state.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function findActiveBuildJobInLane(state, input) {
|
|
212
|
+
const laneFilter = input.target === "knowledge-graph" ? "target = 'knowledge-graph'" : "target IN ('reading-graph', 'reading-summary')";
|
|
213
|
+
return await state.queryOne(
|
|
214
|
+
`
|
|
215
|
+
SELECT *
|
|
216
|
+
FROM build_jobs
|
|
217
|
+
WHERE archive_key = ?
|
|
218
|
+
AND chapter_id = ?
|
|
219
|
+
AND state IN ('queued', 'running', 'canceling', 'paused')
|
|
220
|
+
AND ${laneFilter}
|
|
221
|
+
ORDER BY updated_at DESC
|
|
222
|
+
LIMIT 1
|
|
223
|
+
`,
|
|
224
|
+
[input.archiveKey, input.chapterId],
|
|
225
|
+
mapBuildJob
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
async function mergeActiveBuildJob(state, job, options, now) {
|
|
229
|
+
const nextTarget = job.target === "reading-graph" && options.target === "reading-summary" ? "reading-summary" : job.target;
|
|
230
|
+
const nextQueueRank = options.boost === true && job.state === "queued" ? await readMinQueueRank(state) - 1 : job.queueRank;
|
|
231
|
+
if (nextTarget === job.target && nextQueueRank === job.queueRank) {
|
|
232
|
+
return job;
|
|
233
|
+
}
|
|
234
|
+
await state.run(
|
|
235
|
+
`
|
|
236
|
+
UPDATE build_jobs
|
|
237
|
+
SET target = ?, queue_rank = ?, updated_at = ?
|
|
238
|
+
WHERE job_id = ?
|
|
239
|
+
`,
|
|
240
|
+
[nextTarget, nextQueueRank, now, job.jobId]
|
|
241
|
+
);
|
|
242
|
+
const updated = await requireBuildJobById(state, job.jobId);
|
|
243
|
+
if (nextTarget !== job.target) {
|
|
244
|
+
await appendBuildJobEvent(updated, {
|
|
245
|
+
at: now,
|
|
246
|
+
from: job.target,
|
|
247
|
+
jobId: job.jobId,
|
|
248
|
+
seq: 0,
|
|
249
|
+
to: nextTarget,
|
|
250
|
+
type: "target_changed"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (nextQueueRank !== job.queueRank) {
|
|
254
|
+
await appendBuildJobEvent(updated, {
|
|
255
|
+
at: now,
|
|
256
|
+
jobId: job.jobId,
|
|
257
|
+
seq: 0,
|
|
258
|
+
state: updated.state,
|
|
259
|
+
type: "boosted"
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return updated;
|
|
263
|
+
}
|
|
264
|
+
async function getBuildJob(jobId) {
|
|
265
|
+
const state = await openBuildQueueDatabase();
|
|
266
|
+
try {
|
|
267
|
+
await recoverStaleBuildJobs(state);
|
|
268
|
+
return await requireBuildJobById(state, jobId);
|
|
269
|
+
} finally {
|
|
270
|
+
await state.close();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function readBuildJobForStopCheck(jobId) {
|
|
274
|
+
const state = await openReadonlyBuildQueueDatabase();
|
|
275
|
+
try {
|
|
276
|
+
return await requireBuildJobById(state, jobId);
|
|
277
|
+
} finally {
|
|
278
|
+
await state.close();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function resolveBuildJobId(jobIdPrefix) {
|
|
282
|
+
const state = await openBuildQueueDatabase();
|
|
283
|
+
try {
|
|
284
|
+
await recoverStaleBuildJobs(state);
|
|
285
|
+
return await resolveBuildJobIdInState(state, jobIdPrefix);
|
|
286
|
+
} finally {
|
|
287
|
+
await state.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function pauseBuildJob(jobId) {
|
|
291
|
+
return await updateBuildJobState(jobId, "paused", "paused", {
|
|
292
|
+
allowedStates: ["queued", "running"]
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
async function resumeBuildJob(jobId) {
|
|
296
|
+
return await updateBuildJobState(jobId, "queued", "resumed", {
|
|
297
|
+
allowedStates: ["paused"],
|
|
298
|
+
clearOwner: true
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async function cancelBuildJob(jobId) {
|
|
302
|
+
const state = await openBuildQueueDatabase();
|
|
303
|
+
try {
|
|
304
|
+
await recoverStaleBuildJobs(state);
|
|
305
|
+
return await state.transaction(async () => {
|
|
306
|
+
const job = await requireBuildJobById(state, jobId);
|
|
307
|
+
if (job.state === "running") {
|
|
308
|
+
return await markBuildJobCanceling(state, job);
|
|
309
|
+
}
|
|
310
|
+
if (job.state === "queued" || job.state === "paused") {
|
|
311
|
+
return await markBuildJobCanceled(state, job);
|
|
312
|
+
}
|
|
313
|
+
throw new Error(`Cannot cancel ${job.state} job ${jobId}.`);
|
|
314
|
+
});
|
|
315
|
+
} finally {
|
|
316
|
+
await state.close();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function boostBuildJob(jobId) {
|
|
320
|
+
const state = await openBuildQueueDatabase();
|
|
321
|
+
try {
|
|
322
|
+
await recoverStaleBuildJobs(state);
|
|
323
|
+
return await state.transaction(async () => {
|
|
324
|
+
const job = await requireBuildJobById(state, jobId);
|
|
325
|
+
if (job.state !== "queued") {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Only queued jobs can be boosted. Job ${jobId} is ${job.state}.`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
await state.run(
|
|
332
|
+
`
|
|
333
|
+
UPDATE build_jobs
|
|
334
|
+
SET queue_rank = ?, updated_at = ?
|
|
335
|
+
WHERE job_id = ?
|
|
336
|
+
`,
|
|
337
|
+
[await readMinQueueRank(state) - 1, now, jobId]
|
|
338
|
+
);
|
|
339
|
+
const updated = await requireBuildJobById(state, jobId);
|
|
340
|
+
await appendBuildJobEvent(updated, {
|
|
341
|
+
at: now,
|
|
342
|
+
jobId,
|
|
343
|
+
seq: 0,
|
|
344
|
+
state: "queued",
|
|
345
|
+
type: "boosted"
|
|
346
|
+
});
|
|
347
|
+
return updated;
|
|
348
|
+
});
|
|
349
|
+
} finally {
|
|
350
|
+
await state.close();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function updateBuildJobTarget(jobId, target) {
|
|
354
|
+
const state = await openBuildQueueDatabase();
|
|
355
|
+
try {
|
|
356
|
+
await recoverStaleBuildJobs(state);
|
|
357
|
+
return await state.transaction(async () => {
|
|
358
|
+
const job = await requireBuildJobById(state, jobId);
|
|
359
|
+
if (!ACTIVE_JOB_STATES.has(job.state)) {
|
|
360
|
+
throw new Error(`Cannot change target for ${job.state} job ${jobId}.`);
|
|
361
|
+
}
|
|
362
|
+
if (job.target === target) {
|
|
363
|
+
return job;
|
|
364
|
+
}
|
|
365
|
+
const existing = await findActiveBuildJobInLane(state, {
|
|
366
|
+
archiveKey: job.archiveKey,
|
|
367
|
+
chapterId: job.chapterId,
|
|
368
|
+
target
|
|
369
|
+
});
|
|
370
|
+
if (existing !== void 0 && existing.jobId !== job.jobId) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Chapter ${job.chapterId} already has active ${formatBuildJobLane(target)} job ${existing.jobId}.`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (job.target === "reading-summary" && target === "reading-graph") {
|
|
376
|
+
if (job.readingSummaryStartedAt !== void 0 || job.currentStep === "reading-summary") {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`Cannot downgrade job ${jobId} after summary has started. Cancel it explicitly instead.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
await state.run(
|
|
384
|
+
`
|
|
385
|
+
UPDATE build_jobs
|
|
386
|
+
SET target = ?, updated_at = ?
|
|
387
|
+
WHERE job_id = ?
|
|
388
|
+
`,
|
|
389
|
+
[target, now, jobId]
|
|
390
|
+
);
|
|
391
|
+
const updated = await requireBuildJobById(state, jobId);
|
|
392
|
+
await appendBuildJobEvent(updated, {
|
|
393
|
+
at: now,
|
|
394
|
+
from: job.target,
|
|
395
|
+
jobId,
|
|
396
|
+
seq: 0,
|
|
397
|
+
to: target,
|
|
398
|
+
type: "target_changed"
|
|
399
|
+
});
|
|
400
|
+
return updated;
|
|
401
|
+
});
|
|
402
|
+
} finally {
|
|
403
|
+
await state.close();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function assertNoActiveBuildJobs(input) {
|
|
407
|
+
await assertNoActiveBuildJobConflicts({
|
|
408
|
+
archivePath: input.archivePath,
|
|
409
|
+
operation: input.operation,
|
|
410
|
+
...input.requiresTarget === void 0 ? {} : { requiresTarget: input.requiresTarget },
|
|
411
|
+
scope: { chapterIds: input.chapterIds, kind: "chapter" }
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
async function assertNoActiveBuildJobConflicts(input) {
|
|
415
|
+
if (input.scope.kind === "chapter" && input.scope.chapterIds.length === 0) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const state = await openBuildQueueDatabase();
|
|
419
|
+
try {
|
|
420
|
+
await recoverStaleBuildJobs(state);
|
|
421
|
+
const archiveKey = createArchiveKey(resolve(input.archivePath));
|
|
422
|
+
const targetFilter = input.requiresTarget === void 0 ? "" : "AND target = ?";
|
|
423
|
+
const params = [archiveKey];
|
|
424
|
+
let scopeFilter = "";
|
|
425
|
+
if (input.scope.kind === "chapter") {
|
|
426
|
+
const placeholders = input.scope.chapterIds.map(() => "?").join(", ");
|
|
427
|
+
scopeFilter = `AND chapter_id IN (${placeholders})`;
|
|
428
|
+
params.push(...input.scope.chapterIds);
|
|
429
|
+
}
|
|
430
|
+
if (input.requiresTarget !== void 0) {
|
|
431
|
+
params.push(input.requiresTarget);
|
|
432
|
+
}
|
|
433
|
+
const job = await state.queryOne(
|
|
434
|
+
`
|
|
435
|
+
SELECT *
|
|
436
|
+
FROM build_jobs
|
|
437
|
+
WHERE archive_key = ?
|
|
438
|
+
${scopeFilter}
|
|
439
|
+
AND state IN ('queued', 'running', 'canceling', 'paused')
|
|
440
|
+
${targetFilter}
|
|
441
|
+
ORDER BY updated_at DESC
|
|
442
|
+
LIMIT 1
|
|
443
|
+
`,
|
|
444
|
+
params,
|
|
445
|
+
mapBuildJob
|
|
446
|
+
);
|
|
447
|
+
if (job === void 0) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Chapter ${job.chapterId} has active ${job.target} job ${job.jobId}. ${input.operation} is blocked until the job is paused/canceled or completed.`
|
|
452
|
+
);
|
|
453
|
+
} finally {
|
|
454
|
+
await state.close();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function runBuildJobWorker(options) {
|
|
458
|
+
const state = await openBuildQueueDatabase();
|
|
459
|
+
const ownerId = `${process.pid}-${randomUUID()}`;
|
|
460
|
+
const concurrency = Math.max(1, options.concurrency);
|
|
461
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? 1e4;
|
|
462
|
+
let stopping = false;
|
|
463
|
+
let busySlotCount = 0;
|
|
464
|
+
let idleSince = Date.now();
|
|
465
|
+
const stop = (_signal) => {
|
|
466
|
+
stopping = true;
|
|
467
|
+
};
|
|
468
|
+
process.once("SIGINT", stop);
|
|
469
|
+
process.once("SIGTERM", stop);
|
|
470
|
+
const heartbeat = setInterval(() => {
|
|
471
|
+
void heartbeatBuildWorker(ownerId).catch(() => void 0);
|
|
472
|
+
}, WORKER_HEARTBEAT_INTERVAL_MS);
|
|
473
|
+
try {
|
|
474
|
+
const acquired = await acquireBuildWorkerLease(state, ownerId);
|
|
475
|
+
if (!acquired) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const runSlot = async () => {
|
|
479
|
+
while (!stopping) {
|
|
480
|
+
await heartbeatBuildWorker(ownerId, state);
|
|
481
|
+
await recoverStaleBuildJobs(state);
|
|
482
|
+
busySlotCount += 1;
|
|
483
|
+
let job;
|
|
484
|
+
try {
|
|
485
|
+
job = await claimQueuedBuildJob(state, ownerId);
|
|
486
|
+
} finally {
|
|
487
|
+
if (job === void 0) {
|
|
488
|
+
busySlotCount -= 1;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (job === void 0) {
|
|
492
|
+
if (busySlotCount === 0 && Date.now() - idleSince >= idleTimeoutMs) {
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
await delay(500);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
idleSince = Date.now();
|
|
499
|
+
try {
|
|
500
|
+
await executeClaimedBuildJob(job, ownerId, options);
|
|
501
|
+
} finally {
|
|
502
|
+
busySlotCount -= 1;
|
|
503
|
+
if (busySlotCount === 0) {
|
|
504
|
+
idleSince = Date.now();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
await Promise.all(
|
|
510
|
+
Array.from({ length: concurrency }, async () => await runSlot())
|
|
511
|
+
);
|
|
512
|
+
} finally {
|
|
513
|
+
clearInterval(heartbeat);
|
|
514
|
+
process.removeListener("SIGINT", stop);
|
|
515
|
+
process.removeListener("SIGTERM", stop);
|
|
516
|
+
await releaseBuildWorkerLease(state, ownerId);
|
|
517
|
+
await state.close();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function executeClaimedBuildJob(job, ownerId, options) {
|
|
521
|
+
const reporter = new BuildJobProgressAccumulator(job, ownerId);
|
|
522
|
+
const abortController = new AbortController();
|
|
523
|
+
const stopWatcher = setInterval(() => {
|
|
524
|
+
void abortJobWhenStopped(job, ownerId, abortController).catch(
|
|
525
|
+
() => void 0
|
|
526
|
+
);
|
|
527
|
+
}, 500);
|
|
528
|
+
try {
|
|
529
|
+
await appendBuildJobEvent(job, {
|
|
530
|
+
at: Date.now(),
|
|
531
|
+
jobId: job.jobId,
|
|
532
|
+
seq: 0,
|
|
533
|
+
state: "running",
|
|
534
|
+
type: "started"
|
|
535
|
+
});
|
|
536
|
+
await options.executeJob(job, reporter, {
|
|
537
|
+
signal: abortController.signal
|
|
538
|
+
});
|
|
539
|
+
await reporter.throwIfStopped();
|
|
540
|
+
await markBuildJobSucceeded(job.jobId, ownerId);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (error instanceof BuildJobStoppedError || abortController.signal.aborted) {
|
|
543
|
+
await markBuildJobStopped(job.jobId, ownerId);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
await markBuildJobFailed(job.jobId, ownerId, error);
|
|
547
|
+
} finally {
|
|
548
|
+
clearInterval(stopWatcher);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function abortJobWhenStopped(job, ownerId, abortController) {
|
|
552
|
+
if (abortController.signal.aborted) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const latest = await readBuildJobForStopCheck(job.jobId);
|
|
556
|
+
if (latest.state === "running" && latest.ownerId === ownerId) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
abortController.abort(
|
|
560
|
+
new BuildJobStoppedError(
|
|
561
|
+
`Job ${job.jobId} is ${latest.state}. Stop current worker execution.`
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
async function readBuildJobEvents(job) {
|
|
566
|
+
let content;
|
|
567
|
+
try {
|
|
568
|
+
content = await readFile(job.eventsPath, "utf8");
|
|
569
|
+
} catch {
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
return content.split("\n").filter((line) => line.trim() !== "").map((line) => JSON.parse(line));
|
|
573
|
+
}
|
|
574
|
+
async function cleanBuildJobs(options = {}) {
|
|
575
|
+
const state = await openBuildQueueDatabase();
|
|
576
|
+
const cutoff = Date.now() - (options.olderThanMs ?? 7 * 24 * 60 * 60 * 1e3);
|
|
577
|
+
const states = options.states ?? ["succeeded", "failed", "canceled"];
|
|
578
|
+
try {
|
|
579
|
+
const placeholders = states.map(() => "?").join(", ");
|
|
580
|
+
const jobs = await state.queryAll(
|
|
581
|
+
`
|
|
582
|
+
SELECT *
|
|
583
|
+
FROM build_jobs
|
|
584
|
+
WHERE state IN (${placeholders})
|
|
585
|
+
AND updated_at <= ?
|
|
586
|
+
`,
|
|
587
|
+
[...states, cutoff],
|
|
588
|
+
mapBuildJob
|
|
589
|
+
);
|
|
590
|
+
for (const job of jobs) {
|
|
591
|
+
await rm(job.workspacePath, { force: true, recursive: true });
|
|
592
|
+
await rm(job.cachePath, { force: true, recursive: true });
|
|
593
|
+
await rm(job.logPath, { force: true, recursive: true });
|
|
594
|
+
await rm(job.eventsPath, { force: true });
|
|
595
|
+
await state.run("DELETE FROM build_jobs WHERE job_id = ?", [job.jobId]);
|
|
596
|
+
}
|
|
597
|
+
return jobs.length;
|
|
598
|
+
} finally {
|
|
599
|
+
await state.close();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async function runBuildQueueGc(context) {
|
|
603
|
+
const state = await openBuildQueueDatabase();
|
|
604
|
+
const cutoff = context.force ? context.now : context.now - 7 * 24 * 60 * 60 * 1e3;
|
|
605
|
+
try {
|
|
606
|
+
const scanned = await state.queryOne(
|
|
607
|
+
"SELECT COUNT(*) AS count FROM build_jobs",
|
|
608
|
+
void 0,
|
|
609
|
+
(row) => getNumber(row, "count")
|
|
610
|
+
);
|
|
611
|
+
const jobs = await state.queryAll(
|
|
612
|
+
`
|
|
613
|
+
SELECT *
|
|
614
|
+
FROM build_jobs
|
|
615
|
+
WHERE state IN ('succeeded', 'failed', 'canceled')
|
|
616
|
+
AND updated_at <= ?
|
|
617
|
+
`,
|
|
618
|
+
[cutoff],
|
|
619
|
+
mapBuildJob
|
|
620
|
+
);
|
|
621
|
+
const childDirectories = context.dryRun ? { freedBytes: 0, removed: 0, scanned: 0 } : await removeDisposableChildDirectories(getBuildJobWorkspaceRootPath());
|
|
622
|
+
let freedBytes = 0;
|
|
623
|
+
for (const job of jobs) {
|
|
624
|
+
freedBytes += await readPathSize(job.workspacePath);
|
|
625
|
+
freedBytes += await readPathSize(job.cachePath);
|
|
626
|
+
freedBytes += await readPathSize(job.logPath);
|
|
627
|
+
freedBytes += await readPathSize(job.eventsPath);
|
|
628
|
+
if (!context.dryRun) {
|
|
629
|
+
await rm(job.workspacePath, { force: true, recursive: true });
|
|
630
|
+
await rm(job.cachePath, { force: true, recursive: true });
|
|
631
|
+
await rm(job.logPath, { force: true, recursive: true });
|
|
632
|
+
await rm(job.eventsPath, { force: true });
|
|
633
|
+
await state.run("DELETE FROM build_jobs WHERE job_id = ?", [job.jobId]);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
freedBytes: freedBytes + childDirectories.freedBytes,
|
|
638
|
+
removed: jobs.length + childDirectories.removed,
|
|
639
|
+
scanned: (scanned ?? 0) + childDirectories.scanned
|
|
640
|
+
};
|
|
641
|
+
} finally {
|
|
642
|
+
await state.close();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function updateBuildJobState(jobId, stateName, eventType, options) {
|
|
646
|
+
const state = await openBuildQueueDatabase();
|
|
647
|
+
try {
|
|
648
|
+
await recoverStaleBuildJobs(state);
|
|
649
|
+
return await state.transaction(async () => {
|
|
650
|
+
const job = await requireBuildJobById(state, jobId);
|
|
651
|
+
if (!options.allowedStates.includes(job.state)) {
|
|
652
|
+
throw new Error(`Cannot ${eventType} ${job.state} job ${jobId}.`);
|
|
653
|
+
}
|
|
654
|
+
const now = Date.now();
|
|
655
|
+
await state.run(
|
|
656
|
+
`
|
|
657
|
+
UPDATE build_jobs
|
|
658
|
+
SET state = ?, updated_at = ?, finished_at = ?,
|
|
659
|
+
owner_id = CASE WHEN ? = 1 THEN NULL ELSE owner_id END,
|
|
660
|
+
owner_pid = CASE WHEN ? = 1 THEN NULL ELSE owner_pid END
|
|
661
|
+
WHERE job_id = ?
|
|
662
|
+
`,
|
|
663
|
+
[
|
|
664
|
+
stateName,
|
|
665
|
+
now,
|
|
666
|
+
options.finished === true ? now : null,
|
|
667
|
+
options.clearOwner === true || options.finished === true ? 1 : 0,
|
|
668
|
+
options.clearOwner === true || options.finished === true ? 1 : 0,
|
|
669
|
+
jobId
|
|
670
|
+
]
|
|
671
|
+
);
|
|
672
|
+
const updated = await requireBuildJobById(state, jobId);
|
|
673
|
+
await appendBuildJobEvent(updated, {
|
|
674
|
+
at: now,
|
|
675
|
+
jobId,
|
|
676
|
+
seq: 0,
|
|
677
|
+
state: stateName,
|
|
678
|
+
type: eventType
|
|
679
|
+
});
|
|
680
|
+
return updated;
|
|
681
|
+
});
|
|
682
|
+
} finally {
|
|
683
|
+
await state.close();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function markBuildJobCanceling(state, job) {
|
|
687
|
+
const now = Date.now();
|
|
688
|
+
await state.run(
|
|
689
|
+
`
|
|
690
|
+
UPDATE build_jobs
|
|
691
|
+
SET state = 'canceling', updated_at = ?
|
|
692
|
+
WHERE job_id = ? AND state = 'running'
|
|
693
|
+
`,
|
|
694
|
+
[now, job.jobId]
|
|
695
|
+
);
|
|
696
|
+
const updated = await requireBuildJobById(state, job.jobId);
|
|
697
|
+
await appendBuildJobEvent(updated, {
|
|
698
|
+
at: now,
|
|
699
|
+
jobId: job.jobId,
|
|
700
|
+
seq: 0,
|
|
701
|
+
state: "canceling",
|
|
702
|
+
type: "canceling"
|
|
703
|
+
});
|
|
704
|
+
return updated;
|
|
705
|
+
}
|
|
706
|
+
async function markBuildJobCanceled(state, job) {
|
|
707
|
+
const now = Date.now();
|
|
708
|
+
await state.run(
|
|
709
|
+
`
|
|
710
|
+
UPDATE build_jobs
|
|
711
|
+
SET state = 'canceled', owner_id = NULL, owner_pid = NULL,
|
|
712
|
+
current_step = NULL, finished_at = ?, updated_at = ?
|
|
713
|
+
WHERE job_id = ?
|
|
714
|
+
`,
|
|
715
|
+
[now, now, job.jobId]
|
|
716
|
+
);
|
|
717
|
+
const updated = await requireBuildJobById(state, job.jobId);
|
|
718
|
+
await appendBuildJobEvent(updated, {
|
|
719
|
+
at: now,
|
|
720
|
+
jobId: job.jobId,
|
|
721
|
+
seq: 0,
|
|
722
|
+
state: "canceled",
|
|
723
|
+
type: "canceled"
|
|
724
|
+
});
|
|
725
|
+
return updated;
|
|
726
|
+
}
|
|
727
|
+
async function markBuildJobFailedInState(state, job, error) {
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
const formattedError = formatErrorEvent(error);
|
|
730
|
+
const errorJSON = JSON.stringify(formattedError);
|
|
731
|
+
await state.run(
|
|
732
|
+
`
|
|
733
|
+
UPDATE build_jobs
|
|
734
|
+
SET state = 'failed', owner_id = NULL, owner_pid = NULL,
|
|
735
|
+
current_step = NULL, finished_at = ?, updated_at = ?, error_json = ?
|
|
736
|
+
WHERE job_id = ?
|
|
737
|
+
`,
|
|
738
|
+
[now, now, errorJSON, job.jobId]
|
|
739
|
+
);
|
|
740
|
+
await appendBuildJobEvent(job, {
|
|
741
|
+
at: now,
|
|
742
|
+
error: formattedError,
|
|
743
|
+
jobId: job.jobId,
|
|
744
|
+
seq: 0,
|
|
745
|
+
state: "failed",
|
|
746
|
+
type: "failed"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
async function markBuildJobSucceeded(jobId, ownerId) {
|
|
750
|
+
const state = await openBuildQueueDatabase();
|
|
751
|
+
try {
|
|
752
|
+
await state.transaction(async () => {
|
|
753
|
+
const job = await requireBuildJobById(state, jobId);
|
|
754
|
+
if (job.ownerId !== ownerId || job.state !== "running") {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const now = Date.now();
|
|
758
|
+
await state.run(
|
|
759
|
+
`
|
|
760
|
+
UPDATE build_jobs
|
|
761
|
+
SET state = 'succeeded', owner_id = NULL, owner_pid = NULL,
|
|
762
|
+
current_step = NULL, finished_at = ?, updated_at = ?
|
|
763
|
+
WHERE job_id = ?
|
|
764
|
+
`,
|
|
765
|
+
[now, now, jobId]
|
|
766
|
+
);
|
|
767
|
+
await appendBuildJobEvent(job, {
|
|
768
|
+
at: now,
|
|
769
|
+
jobId,
|
|
770
|
+
seq: 0,
|
|
771
|
+
state: "succeeded",
|
|
772
|
+
type: "succeeded"
|
|
773
|
+
});
|
|
774
|
+
await rm(job.workspacePath, { force: true, recursive: true });
|
|
775
|
+
});
|
|
776
|
+
} finally {
|
|
777
|
+
await state.close();
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function markBuildJobFailed(jobId, ownerId, error) {
|
|
781
|
+
const state = await openBuildQueueDatabase();
|
|
782
|
+
try {
|
|
783
|
+
await state.transaction(async () => {
|
|
784
|
+
const job = await requireBuildJobById(state, jobId);
|
|
785
|
+
if (job.ownerId !== ownerId || job.state !== "running") {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
await markBuildJobFailedInState(state, job, error);
|
|
789
|
+
});
|
|
790
|
+
} finally {
|
|
791
|
+
await state.close();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function markBuildJobStopped(jobId, ownerId) {
|
|
795
|
+
const state = await openBuildQueueDatabase();
|
|
796
|
+
try {
|
|
797
|
+
await state.transaction(async () => {
|
|
798
|
+
const job = await requireBuildJobById(state, jobId);
|
|
799
|
+
if (job.ownerId !== ownerId || job.state !== "canceling") {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
await markBuildJobCanceled(state, job);
|
|
803
|
+
});
|
|
804
|
+
} finally {
|
|
805
|
+
await state.close();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async function claimQueuedBuildJob(state, ownerId) {
|
|
809
|
+
return await state.transaction(async () => {
|
|
810
|
+
const job = await state.queryOne(
|
|
811
|
+
`
|
|
812
|
+
SELECT *
|
|
813
|
+
FROM build_jobs
|
|
814
|
+
WHERE state = 'queued'
|
|
815
|
+
ORDER BY queue_rank ASC, created_at ASC
|
|
816
|
+
LIMIT 1
|
|
817
|
+
`,
|
|
818
|
+
void 0,
|
|
819
|
+
mapBuildJob
|
|
820
|
+
);
|
|
821
|
+
if (job === void 0) {
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
const now = Date.now();
|
|
825
|
+
await state.run(
|
|
826
|
+
`
|
|
827
|
+
UPDATE build_jobs
|
|
828
|
+
SET state = 'running', owner_id = ?, owner_pid = ?, updated_at = ?
|
|
829
|
+
WHERE job_id = ? AND state = 'queued'
|
|
830
|
+
`,
|
|
831
|
+
[ownerId, process.pid, now, job.jobId]
|
|
832
|
+
);
|
|
833
|
+
return await requireBuildJobById(state, job.jobId);
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
async function recoverStaleBuildJobs(state) {
|
|
837
|
+
const workspacePathsToDelete = [];
|
|
838
|
+
await state.transaction(async () => {
|
|
839
|
+
const jobs = await state.queryAll(
|
|
840
|
+
`
|
|
841
|
+
SELECT *
|
|
842
|
+
FROM build_jobs
|
|
843
|
+
WHERE state IN ('running', 'canceling')
|
|
844
|
+
AND owner_pid IS NOT NULL
|
|
845
|
+
`,
|
|
846
|
+
void 0,
|
|
847
|
+
mapBuildJob
|
|
848
|
+
);
|
|
849
|
+
for (const job of jobs) {
|
|
850
|
+
if (job.ownerPid !== void 0 && isProcessAlive(job.ownerPid)) {
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
if (job.state === "canceling") {
|
|
854
|
+
await markBuildJobCanceled(state, job);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
await markBuildJobFailedInState(state, job, {
|
|
858
|
+
message: "Build worker process disappeared before finishing the job.",
|
|
859
|
+
name: "BuildJobWorkerLost"
|
|
860
|
+
});
|
|
861
|
+
workspacePathsToDelete.push(job.workspacePath);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
for (const workspacePath of workspacePathsToDelete) {
|
|
865
|
+
await rm(workspacePath, { force: true, recursive: true });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
async function acquireBuildWorkerLease(state, ownerId) {
|
|
869
|
+
await recoverStaleBuildWorkerLease(state);
|
|
870
|
+
return await state.transaction(async () => {
|
|
871
|
+
const lease = await state.queryOne(
|
|
872
|
+
`
|
|
873
|
+
SELECT owner_pid
|
|
874
|
+
FROM build_worker_lease
|
|
875
|
+
WHERE id = 1
|
|
876
|
+
`,
|
|
877
|
+
void 0,
|
|
878
|
+
(row) => ({
|
|
879
|
+
ownerPid: row.owner_pid === null ? void 0 : getNumber(row, "owner_pid")
|
|
880
|
+
})
|
|
881
|
+
);
|
|
882
|
+
if (lease?.ownerPid !== void 0 && isProcessAlive(lease.ownerPid)) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
await state.run(
|
|
886
|
+
`
|
|
887
|
+
UPDATE build_worker_lease
|
|
888
|
+
SET owner_id = ?, owner_pid = ?, heartbeat_at = ?
|
|
889
|
+
WHERE id = 1
|
|
890
|
+
`,
|
|
891
|
+
[ownerId, process.pid, Date.now()]
|
|
892
|
+
);
|
|
893
|
+
return true;
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
async function heartbeatBuildWorker(ownerId, existingState) {
|
|
897
|
+
const state = existingState ?? await openBuildQueueDatabase();
|
|
898
|
+
try {
|
|
899
|
+
await state.run(
|
|
900
|
+
`
|
|
901
|
+
UPDATE build_worker_lease
|
|
902
|
+
SET heartbeat_at = ?
|
|
903
|
+
WHERE id = 1 AND owner_id = ?
|
|
904
|
+
`,
|
|
905
|
+
[Date.now(), ownerId]
|
|
906
|
+
);
|
|
907
|
+
} finally {
|
|
908
|
+
if (existingState === void 0) {
|
|
909
|
+
await state.close();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
async function releaseBuildWorkerLease(state, ownerId) {
|
|
914
|
+
await state.run(
|
|
915
|
+
`
|
|
916
|
+
UPDATE build_worker_lease
|
|
917
|
+
SET owner_id = NULL, owner_pid = NULL, heartbeat_at = NULL
|
|
918
|
+
WHERE id = 1 AND owner_id = ?
|
|
919
|
+
`,
|
|
920
|
+
[ownerId]
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
async function recoverStaleBuildWorkerLease(state) {
|
|
924
|
+
const lease = await state.queryOne(
|
|
925
|
+
`
|
|
926
|
+
SELECT owner_id, owner_pid
|
|
927
|
+
FROM build_worker_lease
|
|
928
|
+
WHERE id = 1
|
|
929
|
+
`,
|
|
930
|
+
void 0,
|
|
931
|
+
(row) => ({
|
|
932
|
+
ownerId: getOptionalString(row, "owner_id"),
|
|
933
|
+
ownerPid: row.owner_pid === null ? void 0 : getNumber(row, "owner_pid")
|
|
934
|
+
})
|
|
935
|
+
);
|
|
936
|
+
if (lease?.ownerPid === void 0 || isProcessAlive(lease.ownerPid)) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
await state.run(
|
|
940
|
+
`
|
|
941
|
+
UPDATE build_worker_lease
|
|
942
|
+
SET owner_id = NULL, owner_pid = NULL, heartbeat_at = NULL
|
|
943
|
+
WHERE id = 1
|
|
944
|
+
`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
async function requireBuildJobById(state, jobId) {
|
|
948
|
+
const job = await state.queryOne(
|
|
949
|
+
"SELECT * FROM build_jobs WHERE job_id = ?",
|
|
950
|
+
[jobId],
|
|
951
|
+
mapBuildJob
|
|
952
|
+
);
|
|
953
|
+
if (job === void 0) {
|
|
954
|
+
throw new Error(`Build job not found: ${jobId}`);
|
|
955
|
+
}
|
|
956
|
+
return job;
|
|
957
|
+
}
|
|
958
|
+
async function resolveBuildJobIdInState(state, jobIdPrefix) {
|
|
959
|
+
const normalizedPrefix = jobIdPrefix.trim();
|
|
960
|
+
if (normalizedPrefix === "") {
|
|
961
|
+
throw new Error("Build job id is empty.");
|
|
962
|
+
}
|
|
963
|
+
const jobs = await state.queryAll(
|
|
964
|
+
`
|
|
965
|
+
SELECT job_id
|
|
966
|
+
FROM build_jobs
|
|
967
|
+
WHERE substr(job_id, 1, ?) = ?
|
|
968
|
+
ORDER BY created_at DESC
|
|
969
|
+
`,
|
|
970
|
+
[normalizedPrefix.length, normalizedPrefix],
|
|
971
|
+
(row) => getString(row, "job_id")
|
|
972
|
+
);
|
|
973
|
+
if (jobs.length === 0) {
|
|
974
|
+
throw new Error(`Build job not found: ${jobIdPrefix}`);
|
|
975
|
+
}
|
|
976
|
+
if (jobs.length > 1) {
|
|
977
|
+
throw new Error(
|
|
978
|
+
`Build job id prefix is ambiguous: ${jobIdPrefix}. Matches: ${jobs.join(
|
|
979
|
+
", "
|
|
980
|
+
)}`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
return jobs[0];
|
|
984
|
+
}
|
|
985
|
+
async function appendBuildJobEvent(job, event) {
|
|
986
|
+
const seq = await readLastBuildJobEventSeq(job) + 1;
|
|
987
|
+
const nextEvent = {
|
|
988
|
+
...event,
|
|
989
|
+
jobId: job.jobId,
|
|
990
|
+
seq
|
|
991
|
+
};
|
|
992
|
+
await appendFile(job.eventsPath, `${JSON.stringify(nextEvent)}
|
|
993
|
+
`, "utf8");
|
|
994
|
+
}
|
|
995
|
+
async function readLastBuildJobEventSeq(job) {
|
|
996
|
+
const events = await readBuildJobEvents(job);
|
|
997
|
+
return events.at(-1)?.seq ?? 0;
|
|
998
|
+
}
|
|
999
|
+
async function readMaxQueueRank(state) {
|
|
1000
|
+
return await state.queryOne(
|
|
1001
|
+
"SELECT COALESCE(MAX(queue_rank), 0) AS rank FROM build_jobs",
|
|
1002
|
+
void 0,
|
|
1003
|
+
(row) => getNumber(row, "rank")
|
|
1004
|
+
) ?? 0;
|
|
1005
|
+
}
|
|
1006
|
+
async function readMinQueueRank(state) {
|
|
1007
|
+
return await state.queryOne(
|
|
1008
|
+
"SELECT COALESCE(MIN(queue_rank), 0) AS rank FROM build_jobs",
|
|
1009
|
+
void 0,
|
|
1010
|
+
(row) => getNumber(row, "rank")
|
|
1011
|
+
) ?? 0;
|
|
1012
|
+
}
|
|
1013
|
+
async function openBuildQueueDatabase() {
|
|
1014
|
+
return await openSharedStateDatabase(
|
|
1015
|
+
getBuildQueueDatabasePath(),
|
|
1016
|
+
BUILD_QUEUE_SCHEMA_SQL
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
async function openReadonlyBuildQueueDatabase() {
|
|
1020
|
+
return await openBuildQueueDatabase();
|
|
1021
|
+
}
|
|
1022
|
+
function getBuildQueueDatabasePath() {
|
|
1023
|
+
return join(getBuildQueueStateDirectoryPath(), "job.sqlite");
|
|
1024
|
+
}
|
|
1025
|
+
async function createJobWorkspacePath(jobId) {
|
|
1026
|
+
const workspacePath = join(getBuildJobWorkspaceRootPath(), jobId);
|
|
1027
|
+
await mkdir(workspacePath, { recursive: true });
|
|
1028
|
+
return workspacePath;
|
|
1029
|
+
}
|
|
1030
|
+
function getBuildJobWorkspaceRootPath() {
|
|
1031
|
+
return join(getBuildQueueStateDirectoryPath(), "work");
|
|
1032
|
+
}
|
|
1033
|
+
async function createJobCachePath(jobId) {
|
|
1034
|
+
const cachePath = join(getBuildJobCacheRootPath(), jobId);
|
|
1035
|
+
await mkdir(cachePath, { recursive: true });
|
|
1036
|
+
return cachePath;
|
|
1037
|
+
}
|
|
1038
|
+
function getBuildJobCacheRootPath() {
|
|
1039
|
+
return join(getBuildQueueStateDirectoryPath(), "cache");
|
|
1040
|
+
}
|
|
1041
|
+
async function createJobLogPath(jobId) {
|
|
1042
|
+
const logPath = join(getBuildJobLogRootPath(), jobId);
|
|
1043
|
+
await mkdir(logPath, { recursive: true });
|
|
1044
|
+
return logPath;
|
|
1045
|
+
}
|
|
1046
|
+
function getBuildJobLogRootPath() {
|
|
1047
|
+
return join(getBuildQueueStateDirectoryPath(), "logs");
|
|
1048
|
+
}
|
|
1049
|
+
async function createJobEventsPath(jobId) {
|
|
1050
|
+
const rootPath = join(getBuildQueueStateDirectoryPath(), "events");
|
|
1051
|
+
await mkdir(rootPath, { recursive: true });
|
|
1052
|
+
return join(rootPath, `${jobId}.ndjson`);
|
|
1053
|
+
}
|
|
1054
|
+
function getBuildQueueStateDirectoryPath() {
|
|
1055
|
+
return resolveWikiGraphJobsDirectoryPath();
|
|
1056
|
+
}
|
|
1057
|
+
function mapBuildJob(row) {
|
|
1058
|
+
const currentStep = parseOptionalBuildJobTarget(
|
|
1059
|
+
getOptionalString(row, "current_step"),
|
|
1060
|
+
"current_step"
|
|
1061
|
+
);
|
|
1062
|
+
const ownerId = getOptionalString(row, "owner_id");
|
|
1063
|
+
const ownerPid = row.owner_pid === null ? void 0 : getNumber(row, "owner_pid");
|
|
1064
|
+
const readingSummaryStartedAt = row.reading_summary_started_at === null ? void 0 : getNumber(row, "reading_summary_started_at");
|
|
1065
|
+
const finishedAt = row.finished_at === null ? void 0 : getNumber(row, "finished_at");
|
|
1066
|
+
const errorJSON = getOptionalString(row, "error_json");
|
|
1067
|
+
const inputRevision = row.input_revision === null ? void 0 : getNumber(row, "input_revision");
|
|
1068
|
+
const llmJSON = getOptionalString(row, "llm_json");
|
|
1069
|
+
const prompt = getOptionalString(row, "prompt");
|
|
1070
|
+
return {
|
|
1071
|
+
archiveKey: getString(row, "archive_key"),
|
|
1072
|
+
archivePath: getString(row, "archive_path"),
|
|
1073
|
+
cachePath: getString(row, "cache_path"),
|
|
1074
|
+
chapterId: getNumber(row, "chapter_id"),
|
|
1075
|
+
createdAt: getNumber(row, "created_at"),
|
|
1076
|
+
...currentStep === void 0 ? {} : { currentStep },
|
|
1077
|
+
...errorJSON === void 0 ? {} : { errorJSON },
|
|
1078
|
+
eventsPath: getString(row, "events_path"),
|
|
1079
|
+
...finishedAt === void 0 ? {} : { finishedAt },
|
|
1080
|
+
jobId: getString(row, "job_id"),
|
|
1081
|
+
...inputRevision === void 0 ? {} : { inputRevision },
|
|
1082
|
+
logPath: getString(row, "log_path"),
|
|
1083
|
+
...llmJSON === void 0 ? {} : { llmJSON },
|
|
1084
|
+
...ownerId === void 0 ? {} : { ownerId },
|
|
1085
|
+
...ownerPid === void 0 ? {} : { ownerPid },
|
|
1086
|
+
...prompt === void 0 ? {} : { prompt },
|
|
1087
|
+
queueRank: getNumber(row, "queue_rank"),
|
|
1088
|
+
state: parseBuildJobState(getString(row, "state")),
|
|
1089
|
+
...readingSummaryStartedAt === void 0 ? {} : { readingSummaryStartedAt },
|
|
1090
|
+
target: parseBuildJobTarget(getString(row, "target"), "target"),
|
|
1091
|
+
updatedAt: getNumber(row, "updated_at"),
|
|
1092
|
+
workspacePath: getString(row, "workspace_path")
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function parseBuildJobState(value) {
|
|
1096
|
+
if (BUILD_JOB_STATES.includes(value)) {
|
|
1097
|
+
return value;
|
|
1098
|
+
}
|
|
1099
|
+
throw new Error(`Invalid build job state: ${value}`);
|
|
1100
|
+
}
|
|
1101
|
+
function parseBuildJobTarget(value, field) {
|
|
1102
|
+
if (value === "reading-graph" || value === "knowledge-graph" || value === "reading-summary") {
|
|
1103
|
+
return value;
|
|
1104
|
+
}
|
|
1105
|
+
throw new Error(`Invalid ${field}: ${value}`);
|
|
1106
|
+
}
|
|
1107
|
+
function parseOptionalBuildJobTarget(value, field) {
|
|
1108
|
+
return value === void 0 ? void 0 : parseBuildJobTarget(value, field);
|
|
1109
|
+
}
|
|
1110
|
+
function formatBuildJobLane(target) {
|
|
1111
|
+
return target === "knowledge-graph" ? "knowledge-graph" : "reading";
|
|
1112
|
+
}
|
|
1113
|
+
function getString(row, key) {
|
|
1114
|
+
const value = row[key];
|
|
1115
|
+
if (typeof value !== "string") {
|
|
1116
|
+
throw new TypeError(`Expected ${key} to be a string`);
|
|
1117
|
+
}
|
|
1118
|
+
return value;
|
|
1119
|
+
}
|
|
1120
|
+
function getNumber(row, key) {
|
|
1121
|
+
const value = row[key];
|
|
1122
|
+
if (typeof value !== "number") {
|
|
1123
|
+
throw new TypeError(`Expected ${key} to be a number`);
|
|
1124
|
+
}
|
|
1125
|
+
return value;
|
|
1126
|
+
}
|
|
1127
|
+
function getOptionalString(row, key) {
|
|
1128
|
+
const value = row[key];
|
|
1129
|
+
if (value === null || value === void 0) {
|
|
1130
|
+
return void 0;
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof value !== "string") {
|
|
1133
|
+
throw new TypeError(`Expected ${key} to be a string`);
|
|
1134
|
+
}
|
|
1135
|
+
return value;
|
|
1136
|
+
}
|
|
1137
|
+
function createArchiveKey(archivePath) {
|
|
1138
|
+
return createHash("sha256").update(resolve(archivePath)).digest("hex");
|
|
1139
|
+
}
|
|
1140
|
+
function isProcessAlive(pid) {
|
|
1141
|
+
try {
|
|
1142
|
+
process.kill(pid, 0);
|
|
1143
|
+
return true;
|
|
1144
|
+
} catch {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function formatErrorEvent(error) {
|
|
1149
|
+
if (error instanceof Error) {
|
|
1150
|
+
return {
|
|
1151
|
+
message: error.message,
|
|
1152
|
+
name: error.name,
|
|
1153
|
+
stack: error.stack
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
return error;
|
|
1157
|
+
}
|
|
1158
|
+
async function delay(ms) {
|
|
1159
|
+
await new Promise((resolveDelay) => {
|
|
1160
|
+
setTimeout(resolveDelay, ms);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
class BuildJobProgressAccumulator {
|
|
1164
|
+
#job;
|
|
1165
|
+
#ownerId;
|
|
1166
|
+
#outputCharactersPerToken = 4;
|
|
1167
|
+
#refreshIntervalMs = 5e3;
|
|
1168
|
+
#graphWords = 0;
|
|
1169
|
+
#lastSnapshotAt = 0;
|
|
1170
|
+
#outputCharacters = 0;
|
|
1171
|
+
#phase;
|
|
1172
|
+
#phaseCounters = /* @__PURE__ */ new Map();
|
|
1173
|
+
#step;
|
|
1174
|
+
#tokenUsage = {};
|
|
1175
|
+
#readingSummaryWords = 0;
|
|
1176
|
+
#stopCheckQueue = Promise.resolve();
|
|
1177
|
+
#totalGraphWords = 0;
|
|
1178
|
+
#totalReadingSummaryWords = 0;
|
|
1179
|
+
#writeQueue = Promise.resolve();
|
|
1180
|
+
constructor(job, ownerId) {
|
|
1181
|
+
this.#job = job;
|
|
1182
|
+
this.#ownerId = ownerId;
|
|
1183
|
+
}
|
|
1184
|
+
async addOutputCharacters(characters) {
|
|
1185
|
+
await this.#enqueue(async () => {
|
|
1186
|
+
await this.throwIfStopped();
|
|
1187
|
+
this.#outputCharacters += characters;
|
|
1188
|
+
await this.#snapshot();
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
async addTokenUsage(usage) {
|
|
1192
|
+
await this.#enqueue(async () => {
|
|
1193
|
+
await this.throwIfStopped();
|
|
1194
|
+
this.#tokenUsage = {
|
|
1195
|
+
...formatOptionalTokenUsage(
|
|
1196
|
+
"cacheReadTokens",
|
|
1197
|
+
addOptionalNumbers(
|
|
1198
|
+
this.#tokenUsage.cacheReadTokens,
|
|
1199
|
+
usage.cacheReadTokens
|
|
1200
|
+
)
|
|
1201
|
+
),
|
|
1202
|
+
...formatOptionalTokenUsage(
|
|
1203
|
+
"inputTokens",
|
|
1204
|
+
addOptionalNumbers(this.#tokenUsage.inputTokens, usage.inputTokens)
|
|
1205
|
+
),
|
|
1206
|
+
...formatOptionalTokenUsage(
|
|
1207
|
+
"outputTokens",
|
|
1208
|
+
addOptionalNumbers(this.#tokenUsage.outputTokens, usage.outputTokens)
|
|
1209
|
+
)
|
|
1210
|
+
};
|
|
1211
|
+
await this.#snapshot(true);
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
async setTotals(input) {
|
|
1215
|
+
await this.#enqueue(async () => {
|
|
1216
|
+
await this.throwIfStopped();
|
|
1217
|
+
this.#totalGraphWords = input.totalGraphWords ?? this.#totalGraphWords;
|
|
1218
|
+
this.#totalReadingSummaryWords = input.totalReadingSummaryWords ?? this.#totalReadingSummaryWords;
|
|
1219
|
+
await this.#snapshot(true);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
async stepStarted(step) {
|
|
1223
|
+
await this.#enqueue(async () => {
|
|
1224
|
+
await this.throwIfStopped();
|
|
1225
|
+
this.#step = step;
|
|
1226
|
+
this.#phase = void 0;
|
|
1227
|
+
this.#phaseCounters.clear();
|
|
1228
|
+
await markBuildJobStep(this.#job.jobId, step);
|
|
1229
|
+
await appendBuildJobEvent(this.#job, {
|
|
1230
|
+
at: Date.now(),
|
|
1231
|
+
jobId: this.#job.jobId,
|
|
1232
|
+
seq: 0,
|
|
1233
|
+
step,
|
|
1234
|
+
type: "step_started"
|
|
1235
|
+
});
|
|
1236
|
+
await this.#snapshot(true);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
async stepCompleted(step) {
|
|
1240
|
+
await this.#enqueue(async () => {
|
|
1241
|
+
await this.throwIfStopped();
|
|
1242
|
+
this.#step = step;
|
|
1243
|
+
this.#phase = void 0;
|
|
1244
|
+
this.#phaseCounters.clear();
|
|
1245
|
+
await appendBuildJobEvent(this.#job, {
|
|
1246
|
+
at: Date.now(),
|
|
1247
|
+
jobId: this.#job.jobId,
|
|
1248
|
+
seq: 0,
|
|
1249
|
+
step,
|
|
1250
|
+
type: "step_completed"
|
|
1251
|
+
});
|
|
1252
|
+
await this.#snapshot(true);
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
async updateWords(input) {
|
|
1256
|
+
await this.#enqueue(async () => {
|
|
1257
|
+
await this.throwIfStopped();
|
|
1258
|
+
this.#graphWords = input.graphWords === void 0 ? this.#graphWords : clampProgressWords(input.graphWords, this.#totalGraphWords);
|
|
1259
|
+
this.#readingSummaryWords = input.readingSummaryWords === void 0 ? this.#readingSummaryWords : clampProgressWords(
|
|
1260
|
+
input.readingSummaryWords,
|
|
1261
|
+
this.#totalReadingSummaryWords
|
|
1262
|
+
);
|
|
1263
|
+
await this.#snapshot();
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
async updatePhase(input) {
|
|
1267
|
+
await this.#enqueue(async () => {
|
|
1268
|
+
await this.throwIfStopped();
|
|
1269
|
+
if (this.#phase !== input.phase) {
|
|
1270
|
+
this.#phaseCounters.clear();
|
|
1271
|
+
}
|
|
1272
|
+
this.#phase = input.phase;
|
|
1273
|
+
this.#phaseCounters.set(formatProgressCounterKey(input), {
|
|
1274
|
+
done: clampProgressWords(input.done, input.total),
|
|
1275
|
+
name: formatProgressCounterName(input),
|
|
1276
|
+
total: Math.max(0, input.total),
|
|
1277
|
+
unit: input.unit
|
|
1278
|
+
});
|
|
1279
|
+
await this.#snapshot(input.force ?? true);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
async throwIfStopped() {
|
|
1283
|
+
const queued = this.#stopCheckQueue.then(async () => {
|
|
1284
|
+
const job = await readBuildJobForStopCheck(this.#job.jobId);
|
|
1285
|
+
if (job.state === "running" && job.ownerId === this.#ownerId) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
throw new BuildJobStoppedError(
|
|
1289
|
+
`Job ${this.#job.jobId} is ${job.state}. Stop current worker execution.`
|
|
1290
|
+
);
|
|
1291
|
+
});
|
|
1292
|
+
this.#stopCheckQueue = queued.catch(() => void 0);
|
|
1293
|
+
await queued;
|
|
1294
|
+
}
|
|
1295
|
+
async #enqueue(operation) {
|
|
1296
|
+
const queued = this.#writeQueue.then(operation, operation);
|
|
1297
|
+
this.#writeQueue = queued.catch(() => void 0);
|
|
1298
|
+
await queued;
|
|
1299
|
+
}
|
|
1300
|
+
async #snapshot(force = false) {
|
|
1301
|
+
const now = Date.now();
|
|
1302
|
+
if (!force && now - this.#lastSnapshotAt < this.#refreshIntervalMs) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
this.#lastSnapshotAt = now;
|
|
1306
|
+
const tokens = this.#formatTokenUsage();
|
|
1307
|
+
await appendBuildJobEvent(this.#job, {
|
|
1308
|
+
at: now,
|
|
1309
|
+
counters: this.#createCounters(),
|
|
1310
|
+
jobId: this.#job.jobId,
|
|
1311
|
+
...this.#phase === void 0 ? {} : { phase: this.#phase },
|
|
1312
|
+
seq: 0,
|
|
1313
|
+
...this.#step === void 0 ? {} : { step: this.#step },
|
|
1314
|
+
...tokens === void 0 ? {} : { tokens },
|
|
1315
|
+
type: "status_snapshot"
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
#createCounters() {
|
|
1319
|
+
const wordCounter = this.#createWordCounter();
|
|
1320
|
+
return [
|
|
1321
|
+
...wordCounter === void 0 ? [] : [wordCounter],
|
|
1322
|
+
...this.#phaseCounters.values()
|
|
1323
|
+
];
|
|
1324
|
+
}
|
|
1325
|
+
#createWordCounter() {
|
|
1326
|
+
switch (this.#step) {
|
|
1327
|
+
case "reading-graph":
|
|
1328
|
+
return this.#totalGraphWords <= 0 ? void 0 : {
|
|
1329
|
+
done: this.#graphWords,
|
|
1330
|
+
name: "words",
|
|
1331
|
+
total: this.#totalGraphWords,
|
|
1332
|
+
unit: "word"
|
|
1333
|
+
};
|
|
1334
|
+
case "reading-summary":
|
|
1335
|
+
return this.#totalReadingSummaryWords <= 0 ? void 0 : {
|
|
1336
|
+
done: this.#readingSummaryWords,
|
|
1337
|
+
name: "words",
|
|
1338
|
+
total: this.#totalReadingSummaryWords,
|
|
1339
|
+
unit: "word"
|
|
1340
|
+
};
|
|
1341
|
+
case void 0:
|
|
1342
|
+
return void 0;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
#formatTokenUsage() {
|
|
1346
|
+
const outputTokens = this.#tokenUsage.outputTokens ?? (this.#outputCharacters === 0 ? void 0 : Math.floor(this.#outputCharacters / this.#outputCharactersPerToken));
|
|
1347
|
+
const usage = {
|
|
1348
|
+
...this.#tokenUsage.cacheReadTokens === void 0 ? {} : { cacheReadTokens: this.#tokenUsage.cacheReadTokens },
|
|
1349
|
+
...this.#tokenUsage.inputTokens === void 0 ? {} : { inputTokens: this.#tokenUsage.inputTokens },
|
|
1350
|
+
...outputTokens === void 0 ? {} : { outputTokens }
|
|
1351
|
+
};
|
|
1352
|
+
return Object.keys(usage).length === 0 ? void 0 : usage;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function formatOptionalTokenUsage(key, value) {
|
|
1356
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1357
|
+
}
|
|
1358
|
+
function addOptionalNumbers(left, right) {
|
|
1359
|
+
if (left === void 0 && right === void 0) {
|
|
1360
|
+
return void 0;
|
|
1361
|
+
}
|
|
1362
|
+
return (left ?? 0) + (right ?? 0);
|
|
1363
|
+
}
|
|
1364
|
+
function formatProgressCounterKey(input) {
|
|
1365
|
+
return input.phaseDetail ?? input.unit;
|
|
1366
|
+
}
|
|
1367
|
+
function formatProgressCounterName(input) {
|
|
1368
|
+
if (input.phaseDetail !== void 0) {
|
|
1369
|
+
return input.phaseDetail;
|
|
1370
|
+
}
|
|
1371
|
+
switch (input.unit) {
|
|
1372
|
+
case "candidate":
|
|
1373
|
+
return "candidates";
|
|
1374
|
+
case "char":
|
|
1375
|
+
return "chars";
|
|
1376
|
+
case "item":
|
|
1377
|
+
return "items";
|
|
1378
|
+
case "page":
|
|
1379
|
+
return "page";
|
|
1380
|
+
case "qid":
|
|
1381
|
+
return "qids";
|
|
1382
|
+
case "record":
|
|
1383
|
+
return "records";
|
|
1384
|
+
case "sentence":
|
|
1385
|
+
return "sentences";
|
|
1386
|
+
case "window":
|
|
1387
|
+
return "windows";
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
class BuildJobStoppedError extends Error {
|
|
1391
|
+
constructor(message) {
|
|
1392
|
+
super(message);
|
|
1393
|
+
this.name = "BuildJobStoppedError";
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function clampProgressWords(words, totalWords) {
|
|
1397
|
+
if (totalWords <= 0) {
|
|
1398
|
+
return Math.max(0, words);
|
|
1399
|
+
}
|
|
1400
|
+
return Math.min(totalWords, Math.max(0, words));
|
|
1401
|
+
}
|
|
1402
|
+
async function markBuildJobStep(jobId, step) {
|
|
1403
|
+
const state = await openBuildQueueDatabase();
|
|
1404
|
+
try {
|
|
1405
|
+
await state.run(
|
|
1406
|
+
`
|
|
1407
|
+
UPDATE build_jobs
|
|
1408
|
+
SET current_step = ?,
|
|
1409
|
+
reading_summary_started_at = CASE
|
|
1410
|
+
WHEN ? = 'reading-summary' AND reading_summary_started_at IS NULL THEN ?
|
|
1411
|
+
ELSE reading_summary_started_at
|
|
1412
|
+
END,
|
|
1413
|
+
updated_at = ?
|
|
1414
|
+
WHERE job_id = ?
|
|
1415
|
+
`,
|
|
1416
|
+
[step, step, Date.now(), Date.now(), jobId]
|
|
1417
|
+
);
|
|
1418
|
+
} finally {
|
|
1419
|
+
await state.close();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
export {
|
|
1423
|
+
BUILD_JOB_STATES,
|
|
1424
|
+
addBuildJob,
|
|
1425
|
+
assertBuildJobInputRevision,
|
|
1426
|
+
assertNoActiveBuildJobConflicts,
|
|
1427
|
+
assertNoActiveBuildJobs,
|
|
1428
|
+
boostBuildJob,
|
|
1429
|
+
cancelBuildJob,
|
|
1430
|
+
cleanBuildJobs,
|
|
1431
|
+
getBuildJob,
|
|
1432
|
+
listBuildJobs,
|
|
1433
|
+
pauseBuildJob,
|
|
1434
|
+
readBuildJobEvents,
|
|
1435
|
+
recordBuildJobInputRevision,
|
|
1436
|
+
resolveBuildJobId,
|
|
1437
|
+
resumeBuildJob,
|
|
1438
|
+
runBuildJobWorker,
|
|
1439
|
+
runBuildQueueGc,
|
|
1440
|
+
updateBuildJobTarget
|
|
1441
|
+
};
|
|
1442
|
+
//# sourceMappingURL=build-queue.js.map
|