@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/SKILL.md +147 -0
  3. package/cordis.patch.yml +5 -0
  4. package/i18n/zh.json +10 -0
  5. package/lib/client.js +18308 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +6005 -0
  8. package/lib/types/api.d.ts +620 -0
  9. package/lib/types/client/KnowledgeAudit.d.ts +39 -0
  10. package/lib/types/client/KnowledgeContextMenu.d.ts +121 -0
  11. package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
  12. package/lib/types/client/KnowledgeFileTree.d.ts +117 -0
  13. package/lib/types/client/KnowledgeHome.d.ts +102 -0
  14. package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
  15. package/lib/types/client/KnowledgeListing.d.ts +174 -0
  16. package/lib/types/client/KnowledgeLoading.d.ts +41 -0
  17. package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
  18. package/lib/types/client/KnowledgePanel.d.ts +58 -0
  19. package/lib/types/client/KnowledgePermissionOverview.d.ts +147 -0
  20. package/lib/types/client/KnowledgePermissions.d.ts +47 -0
  21. package/lib/types/client/KnowledgeProgressPanel.d.ts +53 -0
  22. package/lib/types/client/KnowledgeReferencePicker.d.ts +81 -0
  23. package/lib/types/client/KnowledgeSearch.d.ts +83 -0
  24. package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
  25. package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
  26. package/lib/types/client/KnowledgeSettings.d.ts +96 -0
  27. package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
  28. package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
  29. package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
  30. package/lib/types/client/KnowledgeTrash.d.ts +62 -0
  31. package/lib/types/client/MenuGlyphs.d.ts +37 -0
  32. package/lib/types/client/auditPaging.d.ts +40 -0
  33. package/lib/types/client/departmentFolders.d.ts +57 -0
  34. package/lib/types/client/deployTarget.d.ts +34 -0
  35. package/lib/types/client/index.d.ts +40 -0
  36. package/lib/types/client/insertReadiness.d.ts +60 -0
  37. package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
  38. package/lib/types/client/locales.d.ts +505 -0
  39. package/lib/types/client/openInSidebar.d.ts +62 -0
  40. package/lib/types/client/ownerLabel.d.ts +39 -0
  41. package/lib/types/client/paging.d.ts +69 -0
  42. package/lib/types/client/permissionRows.d.ts +265 -0
  43. package/lib/types/client/progressRules.d.ts +54 -0
  44. package/lib/types/client/referenceTargets.d.ts +60 -0
  45. package/lib/types/client/roleSummary.d.ts +47 -0
  46. package/lib/types/client/saveAudit.d.ts +87 -0
  47. package/lib/types/client/shareRelations.d.ts +81 -0
  48. package/lib/types/client/useFavorites.d.ts +49 -0
  49. package/lib/types/client/useFileDrop.d.ts +171 -0
  50. package/lib/types/client/useFileDropHook.d.ts +64 -0
  51. package/lib/types/client/usePinnedPaths.d.ts +39 -0
  52. package/lib/types/client/useTreeState.d.ts +46 -0
  53. package/lib/types/client/useViewMode.d.ts +39 -0
  54. package/lib/types/deploy.d.ts +209 -0
  55. package/lib/types/extract.d.ts +144 -0
  56. package/lib/types/extractionQueue.d.ts +154 -0
  57. package/lib/types/index.d.ts +899 -0
  58. package/lib/types/permissions.d.ts +237 -0
  59. package/lib/types/protocol.d.ts +229 -0
  60. package/lib/types/remote.d.ts +105 -0
  61. package/lib/types/trash.d.ts +142 -0
  62. package/lib/types/viewMetadata.d.ts +20 -0
  63. package/lib/types/views.d.ts +923 -0
  64. package/package.json +109 -4
  65. package/skills/kb-organize/SKILL.md +365 -0
  66. package/skills/kb-organize/i18n/zh.json +10 -0
  67. package/README.md +0 -3
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Idempotent deployment recipes for the knowledge base.
3
+ *
4
+ * Every step here is a *plan*: a named sequence of shell commands plus the
5
+ * predicate that decides whether the step already ran. The Settings panel shows
6
+ * each step, runs it over the {@link RemoteRunner} seam, and reports the output.
7
+ *
8
+ * Rules these recipes must satisfy:
9
+ * - Re-runnable. Clicking twice must not install twice, start two servers, or
10
+ * fail because something already exists.
11
+ * - Read-only probes first. A status step never mutates anything.
12
+ * - The commands are quoted for a POSIX shell and never interpolate untrusted
13
+ * input without quoting.
14
+ *
15
+ * @module @sparkelf/dsh-knowledge-base/deploy
16
+ */
17
+ /**
18
+ * MinerU deployment tiers this plugin can prepare.
19
+ *
20
+ * MinerU 4.x recognises exactly these two (`mineru/types.py`: `DEPLOYMENT_TIERS`), and
21
+ * `models download --tier` validates against that tuple. The `flash` name that also exists in
22
+ * MinerU is a *quality* tier for the parsers that need no local weights, not a deployment
23
+ * tier, so it cannot be prepared and is not offered here.
24
+ */
25
+ export type MineruTier = 'basic' | 'standard';
26
+ /** Small-model backend for MinerU. */
27
+ export type MineruBackend = 'onnx' | 'torch';
28
+ /** One step of a deployment plan. */
29
+ export interface DeployStep {
30
+ /** Stable id used by the UI. */
31
+ id: string;
32
+ /** Human-facing label key. */
33
+ label: string;
34
+ /**
35
+ * Commands to run in order. Each is a complete shell line.
36
+ * `{{...}}` placeholders are substituted from {@link DeployPlanContext}.
37
+ */
38
+ commands: string[];
39
+ /** How long this step may take; model downloads are slow. */
40
+ timeoutMs: number;
41
+ }
42
+ /** Values substituted into command templates. */
43
+ export interface DeployPlanContext {
44
+ /** Absolute path of the authoritative knowledge tree. */
45
+ actualRoot: string;
46
+ /** Absolute path of the per-user view tree. */
47
+ viewRoot: string;
48
+ /** Tier to deploy. */
49
+ tier: MineruTier;
50
+ /** Small-model backend. */
51
+ backend: MineruBackend;
52
+ /** TCP port the MinerU API listens on. */
53
+ port: number;
54
+ /** Client hosts allowed to mount the NFS export, as an exports spec. */
55
+ permitted: string;
56
+ /** Host serving the export, as `address` or `address:/path` (the pseudo root). */
57
+ server: string;
58
+ /** Where the client mounts the export, i.e. the browsable knowledge tree. */
59
+ mountPoint: string;
60
+ /** Memory ceiling in MiB for the MinerU service; clamped to half the host's RAM. */
61
+ memoryMaxMiB?: number;
62
+ /**
63
+ * How a remote deployment reaches its share.
64
+ *
65
+ * Decides which transport steps {@link deploymentPlan} emits. Absent means `nfs`, the
66
+ * shape this plan had before the protocol existed.
67
+ */
68
+ protocol?: 'nfs' | 'sshfs';
69
+ /** SSHFS: remote host. */
70
+ sshHost?: string;
71
+ /** SSHFS: login user on the remote host. */
72
+ sshUser?: string;
73
+ /** SSHFS: TCP port the SSH daemon listens on. */
74
+ sshPort?: number;
75
+ /**
76
+ * SSHFS: path of the private key on the HOST, when the credential store resolves to a
77
+ * file. Optional: an ssh-agent-managed key needs no `-i`.
78
+ */
79
+ sshKeyPath?: string;
80
+ /** SSHFS: absolute path of the shared directory on the remote host. */
81
+ sshRemotePath?: string;
82
+ /** SSHFS: extra `-o` options, appended to the ones the step requires. */
83
+ sshMountOptions?: string;
84
+ }
85
+ /**
86
+ * Memory ceiling for the MinerU service when the config names none.
87
+ *
88
+ * The `basic` tier peaked at 1.86 GB resident while parsing a one-page PDF (measured
89
+ * 2026-10-15), so a ceiling near the steady-state size leaves `MemoryHigh` no headroom. On a
90
+ * host without swap that reads as a stalled parse rather than a slow one, because throttling
91
+ * has nowhere to spill. The service step clamps this to half of RAM, so the number only has to
92
+ * be generous on a roomy machine.
93
+ */
94
+ export declare const DEFAULT_MINERU_MEMORY_MAX_MIB = 4096;
95
+ /** Rotate a single-quoted shell string safely. */
96
+ export declare function shquote(value: string): string;
97
+ /** Substitute `{{name}}` placeholders; unknown names are left intact. */
98
+ export declare function renderCommand(template: string, context: DeployPlanContext): string;
99
+ /** Marker the image keeps so a redeploy can prove which tier is present. */
100
+ export declare const KB_STATE_FILE = "/var/lib/dataops-kb/state.json";
101
+ /**
102
+ * Read-only environment probe.
103
+ *
104
+ * Establishes which install path is available so the UI can disable the steps
105
+ * that cannot work: a host without Python cannot take the pip path.
106
+ * @returns the probe step.
107
+ */
108
+ export declare function probeStep(): DeployStep;
109
+ /**
110
+ * Install the MinerU package and download the selected tier's models.
111
+ *
112
+ * The base package (`mineru>=4.0,<5`) is the CPU path: it brings ONNX and
113
+ * llama.cpp but not vLLM, which needs a GPU this environment does not have.
114
+ * @param context - deployment values.
115
+ * @returns the install step.
116
+ */
117
+ export declare function mineruInstallStep(context: DeployPlanContext): DeployStep;
118
+ /**
119
+ * Install a systemd unit that serves the MinerU HTTP API.
120
+ *
121
+ * A unit rather than a bare background process so the service survives logout
122
+ * and can be restarted by the same click.
123
+ * @param context - deployment values.
124
+ * @returns the service step.
125
+ */
126
+ export declare function mineruServiceStep(context: DeployPlanContext): DeployStep;
127
+ /**
128
+ * Verify the MinerU API actually answers.
129
+ *
130
+ * This is the check that catches the parameter-compatibility question: the
131
+ * plugin posts multipart form fields while MinerU 4.x reads a request body, so
132
+ * a pass here is the evidence that the two agree.
133
+ * @param context - deployment values.
134
+ * @returns the verification step.
135
+ */
136
+ export declare function mineruVerifyStep(context: DeployPlanContext): DeployStep;
137
+ /**
138
+ * Deploy the NFS export of the knowledge tree.
139
+ *
140
+ * Uses the container image rather than host packages: Docker is already present
141
+ * on the target, the image carries the userspace tools, and its default export
142
+ * template already includes `no_root_squash`, which hardlinking across NFS
143
+ * requires.
144
+ * @param context - deployment values.
145
+ * @returns the NFS step.
146
+ */
147
+ export declare function nfsDeployStep(context: DeployPlanContext): DeployStep;
148
+ /**
149
+ * Prove hardlinks work across the NFS export.
150
+ *
151
+ * Creating a hard link between the authoritative tree and a view directory is
152
+ * the single operation the whole permission model depends on, and it is also
153
+ * the operation most likely to fail (wrong export scope, `root_squash`, split
154
+ * filesystems). A pass here is the acceptance test for the storage side.
155
+ * @param context - deployment values.
156
+ * @returns the storage verification step.
157
+ */
158
+ export declare function nfsVerifyStep(context: DeployPlanContext): DeployStep;
159
+ /**
160
+ * Mount the exported share at the mount point, with the option this deployment needs.
161
+ *
162
+ * `noresvport` is not decorative here. This host runs WSL2 with
163
+ * `networkingMode=mirrored`, where an NFS mount without it hangs until timeout
164
+ * (`mount.nfs: Connection timed out`) while `rpcinfo`, `showmount -e` and a plain TCP
165
+ * connect to 2049 all still succeed. Measured 2026-10-16 against a server that mounts
166
+ * fine from its own kernel, and A/B verified: without the option the mount fails, with
167
+ * it the mount succeeds, repeatably. Microsoft documents the same failure for mirrored
168
+ * networking (WSL issue 12508), which is still open.
169
+ *
170
+ * The NFS client normally opts into a reserved source port, which requires a reply the
171
+ * mirrored networking stack does not deliver; `noresvport` drops that requirement.
172
+ *
173
+ * @param context - Deployment paths and the export address.
174
+ * @returns The mount step.
175
+ */
176
+ export declare function nfsMountStep(context: DeployPlanContext): DeployStep;
177
+ /**
178
+ * Mount a remote directory over SSHFS on the HOST.
179
+ *
180
+ * The same shape as {@link nfsMountStep}, for a host that cannot mount NFS: the container
181
+ * has no `CAP_SYS_ADMIN` and cannot mount anything itself, so this runs on the Host and the
182
+ * resulting directory is bound in at `mountPoint` like any other.
183
+ *
184
+ * Two details are not incidental:
185
+ * - `StrictHostKeyChecking=accept-new` is limited to this invocation. Accepting an unknown
186
+ * host key globally would silence the one warning that catches a swapped host, so the
187
+ * trust-on-first-use decision is scoped to the connection whose key the operator is
188
+ * establishing.
189
+ * - The reconnect options are not optional here. An SSHFS mount whose connection drops
190
+ * returns EIO to every reader until someone unmounts it; `reconnect` plus a keepalive is
191
+ * what makes a transient network blip a stall instead of a broken knowledge base.
192
+ * @param context - transport and target paths from the deployment config.
193
+ * @returns the mount step.
194
+ */
195
+ export declare function sshfsMountStep(context: DeployPlanContext): DeployStep;
196
+ /**
197
+ * Verify the SSHFS mount is present, readable, and actually a FUSE mount.
198
+ *
199
+ * Separate from the mount step because "the command exited 0" and "the reader will see
200
+ * files" are different claims: a stale empty directory satisfies the first and not the
201
+ * second, and the filesystem type is what tells them apart.
202
+ * @param context - transport and target paths from the deployment config.
203
+ * @returns the verification step.
204
+ */
205
+ export declare function sshfsVerifyStep(context: DeployPlanContext): DeployStep;
206
+ /** The full ordered plan the Settings panel presents. */
207
+ export declare function deploymentPlan(context: DeployPlanContext): DeployStep[];
208
+ /** Look up one step by id. */
209
+ export declare function findStep(id: string, context: DeployPlanContext): DeployStep | undefined;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Convert an uploaded document into Markdown so its text becomes greppable.
3
+ *
4
+ * The knowledge tree doubles as a document store and as what an agent greps. A
5
+ * `.docx` on disk is neither: its bytes answer no query. Every upload therefore
6
+ * lands twice — the original, byte for byte, plus a `.md` sibling holding the
7
+ * extracted text — so a reader can still open the real file while a search sees
8
+ * the words inside it.
9
+ *
10
+ * Extraction is delegated rather than implemented here. Three authorities
11
+ * already exist in this deployment and each owns one format family:
12
+ *
13
+ * - MinerU (`/file_parse`) parses PDF and office documents with layout models.
14
+ * It is the best answer for a scanned PDF and the one authority that can
15
+ * recognise tables inside one.
16
+ * - LibreOffice converts the legacy binary formats (`.doc`, `.xls`, `.ppt`) that
17
+ * MinerU and the Python readers refuse.
18
+ * - `python-docx` / `openpyxl` / `python-pptx` / `pypdf` read the modern formats
19
+ * directly, without a service round trip.
20
+ *
21
+ * None of them is reimplemented in TypeScript: a `.docx` is a ZIP of XML and a
22
+ * second parser would only disagree with the first. This module decides WHICH
23
+ * authority to ask and what to do when it cannot answer, which is the part that
24
+ * is specific to this plugin.
25
+ *
26
+ * A failed extraction never fails the upload. The original is already written and
27
+ * remains the source of truth; the caller learns that no `.md` was produced, and
28
+ * the file stays readable through the original.
29
+ *
30
+ * @module @sparkelf/dsh-knowledge-base/extract
31
+ */
32
+ /** Formats whose text this module knows how to extract. */
33
+ export declare const EXTRACTABLE_EXTENSIONS: readonly [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp", ".rtf"];
34
+ /** One extractable extension. */
35
+ export type ExtractableExtension = typeof EXTRACTABLE_EXTENSIONS[number];
36
+ /** How one request to the extraction authorities ended. */
37
+ export interface ExtractResult {
38
+ /** Markdown text, when extraction produced any. */
39
+ markdown?: string;
40
+ /**
41
+ * Which authority answered. Reported so a bad conversion can be traced.
42
+ *
43
+ * `mineru-ocr` means the first pass classified the document as text and came back
44
+ * too thin to search, so the same parser was asked again with OCR forced. It is a
45
+ * separate value because the two passes can disagree, and a reader debugging a
46
+ * missing line needs to know which one produced the file.
47
+ */
48
+ via?: 'mineru' | 'mineru-ocr' | 'libreoffice' | 'python' | 'native';
49
+ /** Why no markdown was produced. Present whenever {@link markdown} is absent. */
50
+ reason?: string;
51
+ }
52
+ /**
53
+ * Whether this path names a document worth extracting.
54
+ *
55
+ * Compared case-insensitively because a browser reports the extension as the
56
+ * user typed it, and a `.PDF` is the same format as a `.pdf`.
57
+ *
58
+ * @param relPath - the uploaded path, relative to the knowledge root.
59
+ * @returns true when {@link extractDocument} has an authority for it.
60
+ */
61
+ export declare function isExtractable(relPath: string): boolean;
62
+ /**
63
+ * The Markdown sibling an extracted document lands beside.
64
+ *
65
+ * Appending rather than replacing the extension keeps two same-named uploads
66
+ * from colliding: `报告.docx` becomes `报告.docx.md`, so it cannot overwrite a
67
+ * `报告.md` the user wrote by hand.
68
+ *
69
+ * @param relPath - the uploaded document's path.
70
+ * @returns the markdown path to write.
71
+ */
72
+ export declare function markdownSiblingOf(relPath: string): string;
73
+ /**
74
+ * Whether MinerU should be asked before the local readers.
75
+ *
76
+ * PDF only: a PDF carries no recoverable structure beyond its layout, which is
77
+ * exactly what MinerU models. An office document still has its XML, and the
78
+ * direct readers keep more of it (a spreadsheet's cell grid survives
79
+ * `openpyxl` and is flattened by a layout parser).
80
+ *
81
+ * @param ext - lowercased extension including the dot.
82
+ * @returns true when the remote parser is the better authority.
83
+ */
84
+ export declare function prefersRemoteParser(ext: string): boolean;
85
+ /**
86
+ * Ask MinerU to parse one file and return its Markdown.
87
+ *
88
+ * The service answers with a task handle for large files and the body directly
89
+ * for small ones, so both shapes are accepted. A non-200 answer is a miss, not
90
+ * a failure of the upload.
91
+ *
92
+ * @param baseUrl - MinerU service base URL, without a trailing slash.
93
+ * @param fullPath - absolute path of the document on this host.
94
+ * @param fileName - name to report to the service.
95
+ * @returns the Markdown, or undefined when the service could not parse it.
96
+ */
97
+ /**
98
+ * Whether an extraction came back too small to be worth searching.
99
+ *
100
+ * Not a judgement about document quality: a 200-character memo is a real document,
101
+ * and treating it as a failure would re-run OCR over something already correct. The
102
+ * test targets the specific shape of a mis-classified scan -- pages of content that
103
+ * came back as a title and a page number -- so it counts what is left after markup
104
+ * rather than trusting a single threshold on the whole answer.
105
+ *
106
+ * @param markdown - what the parser returned.
107
+ * @returns true when the answer is short enough that a classifier mistake is likely.
108
+ */
109
+ export declare function looksTooThinToSearch(markdown: string): boolean;
110
+ /** Services and commands extraction may use, as configured for this deployment. */
111
+ export interface ExtractOptions {
112
+ /** MinerU base URL. Empty disables the remote parser. */
113
+ mineruUrl?: string;
114
+ }
115
+ /**
116
+ * Extract one document to Markdown.
117
+ *
118
+ * Authorities are tried in an order that puts the best answer first for each
119
+ * format, and every step degrades to the next rather than failing: a PDF whose
120
+ * MinerU call times out still gets `pypdf`'s text layer.
121
+ *
122
+ * @param fullPath - absolute path of the written document.
123
+ * @param relPath - the same document's knowledge-relative path, for the title.
124
+ * @param options - services this deployment exposes.
125
+ * @returns the Markdown and the authority that produced it, or the reason none did.
126
+ */
127
+ export declare function extractDocument(fullPath: string, relPath: string, options?: ExtractOptions): Promise<ExtractResult>;
128
+ /**
129
+ * Whether a file exists and is a regular file.
130
+ *
131
+ * @param path - absolute path to test.
132
+ * @returns true when it is there.
133
+ */
134
+ export declare function fileExists(path: string): Promise<boolean>;
135
+ /**
136
+ * Write extracted markdown beside its document.
137
+ *
138
+ * Kept separate from {@link extractDocument} so the write is testable without an
139
+ * extraction service.
140
+ *
141
+ * @param markdownPath - absolute path of the `.md` sibling.
142
+ * @param markdown - the text to write.
143
+ */
144
+ export declare function writeExtractedMarkdown(markdownPath: string, markdown: string): Promise<void>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Background extraction of a document's Markdown sibling.
3
+ *
4
+ * Upload and extraction are two different promises. The bytes are the uploader's
5
+ * deliverable and must be visible the moment the write lands; the `.md` sibling is
6
+ * derived text that may take a minute (a scanned PDF goes through MinerU, a legacy
7
+ * `.doc` through LibreOffice). Doing both inside the upload request made a slow
8
+ * parse look like a slow upload -- and a failed parse look like a failed upload.
9
+ *
10
+ * This queue separates them. `enqueue` never blocks and never throws: a failed
11
+ * extraction is a status, not an upload error. The queue lives in this process only
12
+ * (see {@link MAX_FINISHED_TASKS}) because the file itself is already durable; a
13
+ * restart loses the bookkeeping, and the manual `extract.retry` route is the recovery
14
+ * path. Persisting a queue would add a second source of truth about files that the
15
+ * filesystem already answers for.
16
+ *
17
+ * @module @sparkelf/dsh-knowledge-base/extractionQueue
18
+ */
19
+ /** Lifecycle of one extraction task. */
20
+ export type ExtractionState = 'pending' | 'running' | 'succeeded' | 'failed' | 'unsupported';
21
+ /** One extraction task, as a reader observes it. */
22
+ export interface ExtractionTask {
23
+ /** Knowledge-relative path of the SOURCE document. */
24
+ relPath: string;
25
+ /** Where the task is in its lifecycle. */
26
+ state: ExtractionState;
27
+ /** When the task was accepted, ISO-8601. */
28
+ queuedAt: string;
29
+ /** When the run started, ISO-8601. */
30
+ startedAt?: string;
31
+ /** When the run reached a terminal state, ISO-8601. */
32
+ finishedAt?: string;
33
+ /** The Markdown sibling that was written, when one was. */
34
+ markdown?: string;
35
+ /** Which authority produced the text (`mineru`, `libreoffice`, `python`, `native`). */
36
+ via?: string;
37
+ /** Why the extraction produced nothing, verbatim from the extractor. */
38
+ reason?: string;
39
+ }
40
+ /** What one run reports back to the queue. */
41
+ export interface ExtractionOutcome {
42
+ /** Terminal state for this run. */
43
+ state: Exclude<ExtractionState, 'pending' | 'running'>;
44
+ /** The written sibling, when the run produced one. */
45
+ markdown?: string;
46
+ /** The authority that produced the text. */
47
+ via?: string;
48
+ /** Why nothing was produced. */
49
+ reason?: string;
50
+ }
51
+ /**
52
+ * How many extractions may run at once.
53
+ *
54
+ * One, deliberately. Every authority this drives is expensive in a way a second
55
+ * concurrent run makes worse: LibreOffice spawns a headless process per conversion and
56
+ * MinerU parses on the same small box the workspace already shares. Serializing also
57
+ * makes the observable order the submission order, which is what a reader retrying one
58
+ * file expects.
59
+ */
60
+ export declare const MAX_CONCURRENT_EXTRACTIONS = 1;
61
+ /**
62
+ * Most finished tasks remembered for status reads.
63
+ *
64
+ * The map is bookkeeping, not storage: an evicted entry loses only its status line,
65
+ * never a file. Bounded so a long-lived process does not accumulate one record per
66
+ * upload it ever saw. Running and pending tasks are never evicted.
67
+ */
68
+ export declare const MAX_FINISHED_TASKS = 200;
69
+ /** What the queue needs from the surrounding plugin, injected so it stays testable. */
70
+ export interface ExtractionQueueOptions {
71
+ /**
72
+ * Run one extraction.
73
+ *
74
+ * Supplied by the plugin so this module owns scheduling only, not the extraction
75
+ * itself: the caller keeps the permission-checked `extractBeside` path in one place.
76
+ * @param relPath - the source document.
77
+ * @param mineruBaseUrl - the caller's own parser address, when set.
78
+ * @returns the terminal state of the run.
79
+ */
80
+ run: (relPath: string, mineruBaseUrl: string | undefined) => Promise<ExtractionOutcome>;
81
+ /**
82
+ * Whether this path can be extracted at all.
83
+ *
84
+ * Asked before queueing so an unsupported file answers immediately rather than
85
+ * occupying the single worker slot to discover it has no reader.
86
+ */
87
+ isExtractable: (relPath: string) => boolean;
88
+ }
89
+ /** One extraction queue. One instance per plugin registration. */
90
+ export interface ExtractionQueue {
91
+ /**
92
+ * Accept a source document for background extraction.
93
+ *
94
+ * Never throws and never waits: the upload that calls this has already written its
95
+ * bytes, and nothing about a parse may change that answer. A repeat for the same
96
+ * path while one is still pending or running returns the existing task instead of
97
+ * queueing a second run.
98
+ * @param relPath - the source document, relative to the knowledge root.
99
+ * @param mineruBaseUrl - the caller's own parser address, when set.
100
+ * @returns the task as it stands right after the call.
101
+ */
102
+ enqueue: (relPath: string, mineruBaseUrl?: string) => ExtractionTask;
103
+ /**
104
+ * Read one task.
105
+ * @param relPath - the source document.
106
+ * @returns the task, or undefined when this process never saw it.
107
+ */
108
+ get: (relPath: string) => ExtractionTask | undefined;
109
+ /**
110
+ * Snapshot the queue for a status surface.
111
+ * @returns every remembered task plus what is running.
112
+ */
113
+ snapshot: () => {
114
+ running: string | undefined;
115
+ size: number;
116
+ tasks: ExtractionTask[];
117
+ };
118
+ /**
119
+ * Wait for one path's task to reach a terminal state.
120
+ *
121
+ * The manual-retry route waits on its own document, and it must wait on the TASK
122
+ * rather than on `drain()`. Two reasons, both about there being one truth:
123
+ *
124
+ * 1. `drain()` also waits for unrelated uploads queued ahead, so a retry would
125
+ * report a delay that has nothing to do with this file.
126
+ * 2. The retry must NOT run its own extraction and then enqueue a second copy to
127
+ * keep the status surface current. That is two runs of one document, and an
128
+ * answer built from one run while `extract.status` reports the other -- the
129
+ * exact split this method exists to remove.
130
+ *
131
+ * Resolves with the terminal task, or immediately with `undefined` when this
132
+ * process never queued that path (a restart, or a path it has not seen). Never
133
+ * rejects: the caller is a route, and the queue is not allowed to become an error
134
+ * path for an upload.
135
+ * @param relPath - the source document.
136
+ * @returns the terminal task, or undefined when the path was never queued.
137
+ */
138
+ settled: (relPath: string) => Promise<ExtractionTask | undefined>;
139
+ /**
140
+ * Wait for the queue to go idle.
141
+ *
142
+ * For tests and for a caller that asked to wait on one specific run; a reader never
143
+ * needs it. Resolves when nothing is pending or running.
144
+ * @returns when the worker has drained.
145
+ */
146
+ drain: () => Promise<void>;
147
+ }
148
+ /**
149
+ * Create an extraction queue.
150
+ *
151
+ * @param options - the runner and the predicate that decides whether a path has a reader.
152
+ * @returns a queue that runs at most {@link MAX_CONCURRENT_EXTRACTIONS} task at a time.
153
+ */
154
+ export declare function createExtractionQueue(options: ExtractionQueueOptions): ExtractionQueue;