@ai-matrx/agents 0.17.4 → 0.18.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.
@@ -0,0 +1,959 @@
1
+ import { SourceRef, SourceManifestEntry, SourceDelivery, SourceRefOptions, SourceManifestSegment, SourceManifest, SourceSet, SourceManifestForm, ResolvedSourceSet } from '../index.cjs';
2
+
3
+ /**
4
+ * The Source input's runtime types — what a list of picked Sources IS while a
5
+ * person is still choosing, on top of the frozen v1 wire contract
6
+ * (`@ai-matrx/agents/sources`), which is never re-declared here.
7
+ *
8
+ * UI-free: no React, no framework, no store library. Every client (the web
9
+ * app, the browser extension, the desktop app, mobile) holds this same shape
10
+ * in its own store through a `SourceSetStore` adapter.
11
+ *
12
+ * Contract of record: common-docs `projects/unified-source-input/DESIGN.md`
13
+ * § "One core, many screens".
14
+ */
15
+
16
+ /** Every tile a host can allow: the Add new doors, plus "existing" (Use existing + search). */
17
+ type SourceTileId = "upload" | "paste" | "web" | "youtube" | "audio" | "image" | "topic" | "existing";
18
+ /**
19
+ * What a picked Source is while the person chooses: the tile it came through,
20
+ * or — for something they already had — "files", "notes", "your_sources"
21
+ * (a Source screen) or "records" (any other registry kind).
22
+ */
23
+ type SourceKindId = SourceTileId | "your_sources" | "files" | "notes" | "records";
24
+ /** The input behind a Source that is still landing — enough to land it again. */
25
+ interface SourceIntakeInput {
26
+ /** Pasted text (paste). */
27
+ text?: string | undefined;
28
+ /** The name the person typed for pasted text. */
29
+ name?: string | undefined;
30
+ /** A web page or YouTube link (web, youtube). */
31
+ url?: string | undefined;
32
+ /** A recording that already finished uploading (audio) — transcribe from here. */
33
+ fileId?: string | undefined;
34
+ }
35
+ /**
36
+ * One picked Source while the person is still choosing. JSON only — it is
37
+ * persisted as a draft so a refresh never loses a pick.
38
+ *
39
+ * `ref` is null only while a NEW Source is still landing (reading a page,
40
+ * transcribing, uploading); it becomes the pointer the moment the door
41
+ * answers. Nothing is ever sent to the server as a blob.
42
+ */
43
+ interface SourceDraft {
44
+ kind: SourceKindId;
45
+ /** What the person sees: a file name, a page title, the first line of a paste. */
46
+ label: string;
47
+ ref: SourceRef | null;
48
+ /** Where it came from, in plain words (a web address, "Pasted text"). */
49
+ origin?: string | undefined;
50
+ /** The Source screen id when known (a landed Source) — every card opens. */
51
+ processedDocumentId?: string | undefined;
52
+ /**
53
+ * How a reused Source was captured (`processed_documents.source_kind`), so
54
+ * the card and the review say "Transcript" or "Web page" — never "Document".
55
+ */
56
+ sourceKind?: string | undefined;
57
+ /** The stored file behind it, when it is one (the form chooser reads its family). */
58
+ fileId?: string | undefined;
59
+ /** Every stand-in announces itself: a reader fallback, a door notice. */
60
+ notes?: string[] | undefined;
61
+ /** The person asked to wait for the clean version before anything runs. */
62
+ waitForClean?: boolean | undefined;
63
+ /**
64
+ * What the person handed over, kept ONLY while the Source is still landing
65
+ * (or failed) so a reload or a failure never loses it: the pasted text, the
66
+ * link, or an already-uploaded recording's file. Never file bytes. Cleared
67
+ * the moment the Source settles.
68
+ */
69
+ input?: SourceIntakeInput | undefined;
70
+ }
71
+ type SourceCardStatus = "pending" | "resolving" | "ready" | "error";
72
+ /** One card: the draft, its lifecycle, and what the server measured. */
73
+ interface SourceCardModel {
74
+ id: string;
75
+ draft: SourceDraft;
76
+ status: SourceCardStatus;
77
+ /** A sentence with its remedy — never a code. */
78
+ error: string | null;
79
+ /** The server's measurement (sizes, forms, parts, state). Null until read. */
80
+ manifest: SourceManifestEntry | null;
81
+ }
82
+ /** The entity the Sources are being used for — passed to the door as `attach_to`. */
83
+ interface SourceAttachTo {
84
+ entityType: string;
85
+ entityId: string;
86
+ label?: string | undefined;
87
+ }
88
+ /** The whole state of one Source input: its cards (in the order picked) and the topic. */
89
+ interface SourceSetState {
90
+ cards: readonly SourceCardModel[];
91
+ topic: string;
92
+ }
93
+ /** What a draft store keeps for a reload: the cards (without measurements) and the topic. */
94
+ interface PersistedSourceCard {
95
+ id: string;
96
+ draft: SourceDraft;
97
+ status: SourceCardStatus;
98
+ error: string | null;
99
+ }
100
+ interface PersistedSourceInput {
101
+ sources: PersistedSourceCard[];
102
+ topic: string;
103
+ }
104
+
105
+ /**
106
+ * How the AI gets a Source — THE one source of truth for delivery.
107
+ *
108
+ * The frozen contract (`SourceRef.delivery`, `@ai-matrx/agents/sources`) has
109
+ * two values and a default: absent / "direct" = the text is handed to the AI
110
+ * with the request; "context" = nothing is sent up front and the AI opens the
111
+ * Source while it works. The server (aidream `source_resolution.py`) reads
112
+ * exactly this field and nothing else — `promote` / `exclude` are NOT read on
113
+ * the Source path.
114
+ *
115
+ * Every screen that shows or changes delivery (the Source card, "Review what
116
+ * goes in", the planner) reads it through `sourceDelivery` and says it with
117
+ * `DELIVERY_WORDS`, so a card can never say one thing while the review and the
118
+ * request say another (V1-A: the card said "Nothing is copied in" — chat's
119
+ * attachment editor's wording — while the review said "Include the text" and
120
+ * the request carried the text).
121
+ */
122
+
123
+ /** The delivery a pointer asks for — absent means the contract default, "direct". */
124
+ declare function sourceDelivery(ref: Pick<SourceRef, "delivery"> | null | undefined): SourceDelivery;
125
+ /**
126
+ * The patch that sets a delivery. "direct" is the default, so it clears the
127
+ * field (the wire stays minimal, exactly as the review has always written it).
128
+ * Looking a Source up sends no text, so parts and a size limit no longer apply.
129
+ */
130
+ declare function deliveryPatch(delivery: SourceDelivery): Pick<SourceRefOptions, "delivery" | "include_segments" | "max_chars">;
131
+ interface DeliveryWords {
132
+ /** The choice's name on a control. */
133
+ label: string;
134
+ /** One sentence under the control: what happens, in plain words. */
135
+ hint: string;
136
+ /** The short phrase a summary line uses. */
137
+ summary: string;
138
+ }
139
+ declare const DELIVERY_WORDS: Record<SourceDelivery, DeliveryWords>;
140
+ /** The choices, in the order every control shows them. */
141
+ declare const DELIVERY_CHOICES: ReadonlyArray<{
142
+ value: SourceDelivery;
143
+ } & DeliveryWords>;
144
+ /**
145
+ * What a HOST can use. Not every host can take every delivery: a generator
146
+ * that needs the text up front (flashcards' segmented generator reads the
147
+ * resolved text and nothing else) gets NOTHING from a Source set to "look it
148
+ * up" — V2 verifier shots 06/26: the request went out with `delivery:
149
+ * "context"` and the page said "None of the Sources had any text", which was
150
+ * false. So a host declares the deliveries it can use (`SourceInputProps.
151
+ * deliveries`); the card and the review offer only those, and a Source already
152
+ * set to something the host cannot use is switched back — with a note on it.
153
+ * Omitted = both (a host whose AI can open Sources while it works).
154
+ */
155
+ declare function allowedDeliveries(deliveries: readonly SourceDelivery[] | undefined): readonly SourceDelivery[];
156
+ /** The choices one host offers, in the order every control shows them. */
157
+ declare function deliveryChoicesFor(deliveries: readonly SourceDelivery[] | undefined): ReadonlyArray<{
158
+ value: SourceDelivery;
159
+ } & DeliveryWords>;
160
+ /** The sentence a switched-back Source carries — every intervention announces itself. */
161
+ declare function deliverySwitchedNote(to: SourceDelivery): string;
162
+ /**
163
+ * Fit a pointer to what the host can use. Returns the patch to apply (null
164
+ * when it already fits) and the delivery it lands on.
165
+ */
166
+ declare function fitDelivery(ref: Pick<SourceRef, "delivery"> | null | undefined, deliveries: readonly SourceDelivery[] | undefined): {
167
+ patch: ReturnType<typeof deliveryPatch>;
168
+ to: SourceDelivery;
169
+ } | null;
170
+
171
+ /**
172
+ * Finding a part of a Source — THE one matcher, used by the Source card's
173
+ * "Choose parts" and by "Review what goes in".
174
+ *
175
+ * A query is one of:
176
+ * - a page number ("12") — the part that covers that page;
177
+ * - a page range ("3-10", "3–10", "3 to 10") — every part inside it;
178
+ * - words — every word must appear in the part's label or its preview (the
179
+ * opening words the manifest carries, contract amendment A5), matched here;
180
+ * or the part is one the server found holding every word in its full text
181
+ * (`POST /sources/parts/search`, via `useSourcePartsSearch`).
182
+ *
183
+ * A part spanning several pages ("Pages 22–24") covers each of them: the
184
+ * manifest's numeric `page` is its first page and the label names the span.
185
+ *
186
+ * V1-A: the search promised "words" but matched only labels, so
187
+ * "photosynthesis" found nothing in a biology course.
188
+ */
189
+
190
+ /** A manifest part, with the A5 preview (optional on the wire). */
191
+ type SourcePart = SourceManifestSegment & {
192
+ preview?: string;
193
+ };
194
+ /** The ids of parts the server found holding every word (`useSourcePartsSearch`). */
195
+ type PartMatchIds = ReadonlySet<string>;
196
+ /** The pages a part covers: its label's span ("Pages 22–24") or its one page. */
197
+ declare function partPages(part: SourcePart): [number, number] | null;
198
+ /** True when a query is words (not a page or a range) — only then is the text searched. */
199
+ declare function isWordQuery(query: string): boolean;
200
+ declare function matchesPart(part: SourcePart, query: string, serverMatches?: PartMatchIds): boolean;
201
+ /** The parts a query shows, in the Source's own order. */
202
+ declare function findParts<T extends SourcePart>(parts: readonly T[], query: string, serverMatches?: PartMatchIds): T[];
203
+
204
+ /**
205
+ * What a Source that was still landing becomes when the page reloads.
206
+ *
207
+ * Never lose input (table stakes): the pasted text, the link, or an
208
+ * already-uploaded recording is kept in the draft (`SourceDraft.input`), so
209
+ * the input picks it up again by itself — the landing door dedupes by content
210
+ * hash, so landing it a second time reuses the same Source, never a copy.
211
+ * A file that was mid-upload cannot come back (its bytes lived only in the
212
+ * browser tab); the card says so honestly and keeps the file's name so the
213
+ * person can choose it again in one click.
214
+ */
215
+
216
+ /** Shown while a kept input is being landed again after a reload. */
217
+ declare const RELOADED_RESUMING = "The page reloaded while this was being added \u2014 picking it up again.";
218
+ /** A kept input exists but has not been re-landed (it will be, or the person can press Try again). */
219
+ declare const RELOADED_WHILE_ADDING = "This was still being added when the page reloaded, so it did not finish. Add it again.";
220
+ /** The kinds whose landing can be repeated from what the draft kept. */
221
+ declare function resumableInput(draft: SourceDraft): SourceIntakeInput | null;
222
+ /** The sentence for a file whose upload the reload cut off. */
223
+ declare function uploadCutOffSentence(draft: SourceDraft): string;
224
+ interface ReloadedCard {
225
+ /** "resume" = land the kept input again now; "reoffer" = the person must re-choose a file. */
226
+ action: "resume" | "reoffer" | "lost";
227
+ sentence: string;
228
+ }
229
+ /**
230
+ * The one decision: a card that was `pending`/`resolving` when the page
231
+ * unloaded. Cards in any other state are restored unchanged (returns null).
232
+ */
233
+ declare function reloadedCard(draft: SourceDraft, status: "pending" | "resolving" | "ready" | "error"): ReloadedCard | null;
234
+ /**
235
+ * The knob for the largest pasted text kept in the device draft (it would
236
+ * crowd the draft store). Read by the intake; while unread nothing is kept and
237
+ * the card says so.
238
+ */
239
+ declare const MAX_KEPT_TEXT_KNOB: {
240
+ readonly feature: "sources";
241
+ readonly key: "max_kept_draft_chars";
242
+ };
243
+ /** Said on a pasted card whose text is too large (or the limit unreadable) to keep for a reload. */
244
+ declare const NOT_KEPT_FOR_RELOAD = "Too large to keep on this device, so it will not come back if the page reloads before it is added.";
245
+ /**
246
+ * A landing stopped only because no organization is chosen yet. The card
247
+ * waits; the moment one is set the intake lands every waiting card again from
248
+ * what its draft kept (state, never an in-memory queue — it survives a reload).
249
+ */
250
+ declare const WAITING_FOR_ORGANIZATION = "Waiting for an organization \u2014 choose one and this continues by itself.";
251
+ /** A read Source whose keep waits for an organization; kept again once one is set. */
252
+ declare const KEEP_WAITING_FOR_ORGANIZATION = "Read. Waiting for an organization to keep it \u2014 choose one and it is kept by itself.";
253
+
254
+ /**
255
+ * fileSource — how a stored file on a Source card becomes its Source, decided
256
+ * from the SERVER's state alone (USI-3e). Pure: the one read
257
+ * (`/files/{id}/rag-status`) and the file's own organization are INJECTED by
258
+ * the host (`SourceFileState` below), never imported.
259
+ *
260
+ * The server owns reading a file: every upload's finalize already starts the
261
+ * one processing run (the file adapters → a `processed_documents` row), and
262
+ * `/files/{id}/rag-status` merges that run's lifecycle row with the Source it
263
+ * made. So a card never starts its own run while one is live, a reload simply
264
+ * reads the state again (re-attach, never a second job), and a file someone
265
+ * already had ("use the one you already have", or picked from Files) is kept
266
+ * the moment its existing Source is seen.
267
+ */
268
+
269
+ /** The part of the server's `/files/{id}/rag-status` answer the decision reads. */
270
+ interface FileSourceStatus {
271
+ state: string;
272
+ processed_document_id?: string | null | undefined;
273
+ error?: {
274
+ message?: string | null | undefined;
275
+ [detail: string]: unknown;
276
+ } | null | undefined;
277
+ }
278
+ type FileSourceStep =
279
+ /** The Source exists: keep it and file it against the thing being made. */
280
+ {
281
+ do: "keep";
282
+ processedDocumentId: string;
283
+ }
284
+ /** A run is reading it now (server-side or this tab's own). Look again soon. */
285
+ | {
286
+ do: "wait";
287
+ }
288
+ /** Nothing is reading it and it has no Source yet: start the one run. */
289
+ | {
290
+ do: "start";
291
+ }
292
+ /** It cannot become a Source; say why, and stop looking. */
293
+ | {
294
+ do: "unreadable";
295
+ reason: string;
296
+ };
297
+ interface FileSourceContext {
298
+ /** This card already started a run in this page's life. */
299
+ started: boolean;
300
+ /** This tab's processing runner has a live run for the file. */
301
+ localRunning: boolean;
302
+ }
303
+ /** The one decision. `status` is the server's `/files/{id}/rag-status` answer. */
304
+ declare function nextFileStep(status: FileSourceStatus | null, ctx: FileSourceContext): FileSourceStep;
305
+ /** How long to wait before looking again: quick at first, then spaced out. */
306
+ declare function fileSourcePollDelayMs(round: number): number;
307
+ /** A stored file on a card whose Source is not known yet — the recovery reads its state. */
308
+ declare function isWaitingFileCard(card: SourceCardModel): boolean;
309
+ /**
310
+ * True while a waiting file card cannot be asked about yet: `/files/{id}/
311
+ * rag-status` answers only with an organization (the file's own, when the
312
+ * host knows it, or the one the person picked). Without one every read was a
313
+ * refused 400 — every ~3 s, piling up as page errors (V2-F #3). So the card
314
+ * is HELD: it says it is waiting for an organization, offers the one picker,
315
+ * and nothing is asked until one is known.
316
+ *
317
+ * `fileOrganizationId` is the host's lookup of a file's own organization
318
+ * (omitted = the host does not know it).
319
+ */
320
+ declare function fileCardHeldForOrganization(card: SourceCardModel, activeOrganizationId: string | null | undefined, fileOrganizationId?: (fileId: string) => string | null | undefined): boolean;
321
+
322
+ /**
323
+ * The token estimate the planner uses by default — the SAME measured ratios as
324
+ * the web app's one estimator (matrx-frontend `lib/tokens/estimate.ts`) and the
325
+ * server's `CHARS_PER_TOKEN_MEASURED` (aidream, 2026-09-28): 2.9 characters per
326
+ * token for prose with markup, 2.4 for structured payloads. Measured, not
327
+ * assumed — the textbook 4.0 under-counted a real run by 23%.
328
+ *
329
+ * A host that owns its own estimator passes it to `planSourceReview`
330
+ * (`PlanInput.estimateTokens`) so a preview and a run never disagree.
331
+ */
332
+ declare const SOURCE_CHARS_PER_TOKEN: {
333
+ readonly prose: 2.9;
334
+ readonly structured: 2.4;
335
+ };
336
+ /** Estimated tokens for `chars` characters of prose. */
337
+ declare function estimateSourceTokens(chars: number): number;
338
+
339
+ /** How close to the window counts as "Getting heavy" (the champion's 70%). */
340
+ declare const HEAVY_SHARE = 0.7;
341
+ type SourcePlanStatus =
342
+ /** Its text goes in. */
343
+ "included"
344
+ /** The AI looks it up on demand — no text is sent up front. */
345
+ | "on_demand"
346
+ /** Does not fit in what is left of the window — removed from the result. */
347
+ | "left_out"
348
+ /** Cannot be used at all (no access, missing, failed). Kept so the host can show it. */
349
+ | "unusable";
350
+ interface SourcePlanEntry {
351
+ index: number;
352
+ ref: SourceRef;
353
+ entry: SourceManifestEntry;
354
+ status: SourcePlanStatus;
355
+ /** Label of the form in use. */
356
+ formLabel: string;
357
+ /** Characters of the chosen form before parts/cap. */
358
+ formChars: number;
359
+ /** Characters the model receives for this Source (grounding headers included). */
360
+ sentChars: number;
361
+ sentTokens: number;
362
+ /** True when parts are known, so `sentChars` is exact; false = the text body only. */
363
+ exact: boolean;
364
+ /** Number of parts that go in, when parts are known. */
365
+ partsSent: number | null;
366
+ partsTotal: number | null;
367
+ /** The per-Source cap cut something. */
368
+ capped: boolean;
369
+ }
370
+ type BudgetVerdict = "empty" | "fine" | "heavy" | "too_much";
371
+ interface SourcePlan {
372
+ entries: SourcePlanEntry[];
373
+ sentChars: number;
374
+ sentTokens: number;
375
+ /** Window the verdict is judged against. */
376
+ windowTokens: number;
377
+ /** True when the window is the labelled default, not the model's own. */
378
+ windowIsFallback: boolean;
379
+ verdict: BudgetVerdict;
380
+ /** Share of the window used, 0..∞. */
381
+ share: number;
382
+ leftOut: SourcePlanEntry[];
383
+ /** Exactly what the review returns. */
384
+ sourceSet: SourceSet;
385
+ }
386
+ /** Length of one rendered grounding block: `### Chunk <id>[ (page N)]\n<text>`. */
387
+ declare function renderedPartChars(segment: {
388
+ id: string;
389
+ page?: number | undefined;
390
+ }, chars: number): number;
391
+ declare function chosenForm(entry: SourceManifestEntry, ref: SourceRef): SourceManifestForm | null;
392
+ interface PlanInput {
393
+ manifest: SourceManifest;
394
+ /** The person's current choices, index-aligned with `manifest.sources`. */
395
+ refs: SourceRef[];
396
+ /** The original set — carries topic / grounding / retrieve_query. */
397
+ base: SourceSet;
398
+ /** The model's window, or null when unknown. */
399
+ modelWindowTokens: number | null;
400
+ /** The labelled default used when the model is unknown. */
401
+ fallbackWindowTokens: number;
402
+ targetModelId?: string | undefined;
403
+ /** The host's one token estimator (default: the measured `estimateSourceTokens`). */
404
+ estimateTokens?: ((chars: number) => number) | undefined;
405
+ }
406
+ declare function planSourceReview(input: PlanInput): SourcePlan;
407
+
408
+ /**
409
+ * THE one client for the Source doors on the server (aidream
410
+ * `api/routers/source_sets.py`):
411
+ *
412
+ * - `POST /sources/manifest` — sizes, states, forms and parts (never bodies);
413
+ * - `POST /sources/resolve` — the full grounded text a generator reads;
414
+ * - `POST /sources/parts/search` — the ids of one Source's parts that hold
415
+ * every word of a query (the manifest's own part ids; never a body).
416
+ *
417
+ * All three are READS decided by access, never by the selected organization:
418
+ * the server names them in `BODY_CARRIED_READS` (aidream `api/read_by_access.py`),
419
+ * so every call goes out marked `bodyCarriedRead` — with an organization it is
420
+ * still named, with none it is sent naming none instead of being refused on
421
+ * the client (V1-A: "Select an organization before sending this request" was a
422
+ * dead end the server never asked for).
423
+ *
424
+ * The TRANSPORT is injected: auth, base URL and error shape belong to the
425
+ * host. A web app binds its typed API client; any other client can use
426
+ * `createFetchSourcesTransport` below.
427
+ */
428
+
429
+ /** Options every Source door call carries. */
430
+ interface SourceCallOptions {
431
+ organizationId?: string | undefined;
432
+ signal?: AbortSignal | undefined;
433
+ }
434
+ /** What the transport receives: the call options plus the read marker. */
435
+ interface SourceTransportOptions extends SourceCallOptions {
436
+ /** Always true for the Source doors — send without requiring an organization. */
437
+ bodyCarriedRead: true;
438
+ }
439
+ /** The server's answer to a part search. */
440
+ interface SourcePartsSearchResult {
441
+ segment_ids?: string[] | null | undefined;
442
+ truncated?: boolean | null | undefined;
443
+ /** Set when the Source cannot be searched ("no_access", …). */
444
+ unavailable?: string | null | undefined;
445
+ /** What happened and what to do, in the server's words. */
446
+ detail?: string | null | undefined;
447
+ }
448
+ /** The three door paths, typed by body and answer. */
449
+ interface SourceDoorCalls {
450
+ "/sources/manifest": {
451
+ body: {
452
+ source_set: SourceSet;
453
+ };
454
+ answer: SourceManifest;
455
+ };
456
+ "/sources/resolve": {
457
+ body: {
458
+ source_set: SourceSet;
459
+ };
460
+ answer: ResolvedSourceSet;
461
+ };
462
+ "/sources/parts/search": {
463
+ body: {
464
+ source_ref: SourceRef;
465
+ query: string;
466
+ };
467
+ answer: SourcePartsSearchResult;
468
+ };
469
+ }
470
+ type SourceDoorPath = keyof SourceDoorCalls;
471
+ /** The host's way to POST to the server (auth + base URL + error shape are its own). */
472
+ interface SourcesTransport {
473
+ post<P extends SourceDoorPath>(path: P, body: SourceDoorCalls[P]["body"], options: SourceTransportOptions): Promise<SourceDoorCalls[P]["answer"]>;
474
+ }
475
+ interface SourcesClient {
476
+ manifest(sourceSet: SourceSet, options?: SourceCallOptions): Promise<SourceManifest>;
477
+ resolve(sourceSet: SourceSet, options?: SourceCallOptions): Promise<ResolvedSourceSet>;
478
+ /** The ids of `ref`'s parts whose label or text holds every word of `query`. */
479
+ searchParts(ref: SourceRef, query: string, options?: SourceCallOptions): Promise<SourcePartsSearchResult>;
480
+ }
481
+ declare function createSourcesClient(transport: SourcesTransport): SourcesClient;
482
+ /** Thrown by the fetch transport for a non-2xx answer — carries the server's words. */
483
+ declare class SourceDoorError extends Error {
484
+ readonly status: number;
485
+ readonly body: unknown;
486
+ constructor(status: number, message: string, body: unknown);
487
+ }
488
+ interface FetchSourcesTransportOptions {
489
+ /** The server's base URL ("https://server.app.matrxserver.com"). */
490
+ baseUrl: string;
491
+ /** Headers for each call — the host's auth (a bearer token) goes here. */
492
+ headers?: (() => Record<string, string> | Promise<Record<string, string>>) | undefined;
493
+ /** The header that names the organization (default "X-Organization-Id"). */
494
+ organizationHeader?: string | undefined;
495
+ fetch?: typeof fetch | undefined;
496
+ }
497
+ /** A plain `fetch` transport for clients without their own API layer. */
498
+ declare function createFetchSourcesTransport(options: FetchSourcesTransportOptions): SourcesTransport;
499
+
500
+ /**
501
+ * The source-list state machine — pure reducers and selectors over
502
+ * `SourceSetState`. Every function returns a NEW state (or the same one when
503
+ * nothing changed) and never touches a store, a network or a clock; the
504
+ * controller (`controller.ts`) applies them through the host's store adapter.
505
+ *
506
+ * The rules here are the ones the web app's Source input has always kept:
507
+ * - landing the same Source twice keeps ONE card and says so on it;
508
+ * - a settled card drops the input it kept for a reload;
509
+ * - a card cut off mid-landing by a reload is restored as an error the
510
+ * input resolves (`interrupted.ts`), never a spinner forever;
511
+ * - a new Source starts on the host's default form.
512
+ */
513
+
514
+ declare const EMPTY_SOURCE_SET_STATE: SourceSetState;
515
+ /** Said on the one card kept when the same Source lands twice. */
516
+ declare const SAME_SOURCE_AGAIN = "You added this again \u2014 it is the same Source, so it is listed once.";
517
+ declare function isSourceDraft(value: unknown): value is SourceDraft;
518
+ declare function sameSourceRef(a: Pick<SourceRef, "resource_type" | "resource_id">, b: Pick<SourceRef, "resource_type" | "resource_id">): boolean;
519
+ /** "<resource_type>:<resource_id>" — how a Source is keyed across screens. */
520
+ declare function sourceKey(ref: {
521
+ resource_type: string;
522
+ resource_id: string;
523
+ }): string;
524
+ /** The draft kind a registry token is picked as. */
525
+ declare function draftKindForToken(token: string): SourceDraft["kind"];
526
+ /** A new pointer starts on the host's default form (never over one already chosen). */
527
+ declare function withDefaultForm(ref: SourceRef, defaultForm: string | undefined): SourceRef;
528
+ /** Add a Source that is already a pointer (a stored record, a landed Source). */
529
+ declare function addReadyCard(state: SourceSetState, id: string, draft: SourceDraft, options?: {
530
+ defaultForm?: string | undefined;
531
+ }): SourceSetState;
532
+ /** Add a Source that is still landing; finish it with `settleCard` or `failCard`. */
533
+ declare function addPendingCard(state: SourceSetState, id: string, draft: Omit<SourceDraft, "ref">): SourceSetState;
534
+ /**
535
+ * A landing answered. The door dedupes by content: landing the same thing
536
+ * twice returns the Source already picked — keep ONE card and say so on it.
537
+ */
538
+ declare function settleCard(state: SourceSetState, id: string, patch: Partial<SourceDraft> & {
539
+ ref: SourceRef;
540
+ }, options?: {
541
+ defaultForm?: string | undefined;
542
+ }): SourceSetState;
543
+ declare function failCard(state: SourceSetState, id: string, sentence: string): SourceSetState;
544
+ /** Back to landing (after a reload or a failure). */
545
+ declare function restartCard(state: SourceSetState, id: string): SourceSetState;
546
+ /** Change what a card says or keeps (label, input, fileId, notes, processedDocumentId). */
547
+ declare function updateCardDraft(state: SourceSetState, id: string, patch: Partial<Omit<SourceDraft, "ref">>): SourceSetState;
548
+ declare function removeCard(state: SourceSetState, id: string): SourceSetState;
549
+ /** Change the pointer's choices (form, parts, cap, delivery). */
550
+ declare function updateCardRef(state: SourceSetState, id: string, options: SourceRefOptions): SourceSetState;
551
+ declare function setCardWaitForClean(state: SourceSetState, id: string, wait: boolean): SourceSetState;
552
+ declare function setSourceTopic(state: SourceSetState, topic: string): SourceSetState;
553
+ /** Apply a set the review returned (forms, parts, caps, delivery) — and its topic. */
554
+ declare function applyReviewedSourceSet(state: SourceSetState, set: SourceSet): SourceSetState;
555
+ /** The server measured the ready Sources: each ready card takes its entry. */
556
+ declare function applyManifest(state: SourceSetState, manifest: SourceManifest): SourceSetState;
557
+ /**
558
+ * Only what the host can use (V2-F #1): a Source set to a delivery this surface
559
+ * cannot use (picked elsewhere, restored from a draft, handed in by a link) is
560
+ * switched back, and its card says so. Never a choice that cannot work.
561
+ */
562
+ declare function fitCardDeliveries(state: SourceSetState, deliveries: readonly SourceDelivery[] | undefined): SourceSetState;
563
+ /** Read whatever a draft store handed back; anything malformed is dropped. */
564
+ declare function readPersistedSourceInput(data: unknown): PersistedSourceInput;
565
+ declare function toPersistedSourceInput(state: SourceSetState): PersistedSourceInput;
566
+ /**
567
+ * The cards a reload brings back. A card cut off mid-landing becomes an error
568
+ * the input resolves (it re-lands the kept input, or asks for the file again)
569
+ * — never a spinner forever. A landing held for an organization lost its hold
570
+ * with the page: it is picked up again like one cut off mid-landing.
571
+ */
572
+ declare function restoredCards(persisted: PersistedSourceInput): SourceCardModel[];
573
+ declare function readySourceRefs(state: SourceSetState): SourceRef[];
574
+ declare function hasSourceRef(state: SourceSetState, resourceType: string, resourceId: string): boolean;
575
+ /** The frozen v1 payload, built from the ready Sources. */
576
+ declare function selectSourceSet(state: SourceSetState, options?: {
577
+ targetModelId?: string | undefined;
578
+ }): SourceSet;
579
+ /**
580
+ * THE one size rule (the contract's `totalChars`): the chosen form, or the
581
+ * picked parts, capped — measured against each card's CURRENT pointer.
582
+ */
583
+ declare function selectTotalChars(state: SourceSetState): number;
584
+ /** Every Source finished landing (nothing pending) and none failed. */
585
+ declare function selectSettled(state: SourceSetState): boolean;
586
+ /** How many more Sources fit under the host's max (Infinity when there is none). */
587
+ declare function sourceRoomLeft(state: SourceSetState, max: number | undefined): number;
588
+ /**
589
+ * Every generator names a Source the way the person saw it on its card — the
590
+ * file name, the name they gave pasted text — never the stored document's own
591
+ * title (V2-F #5). The server's label stays only for a Source the input does
592
+ * not hold.
593
+ */
594
+ declare function withDisplayNames(resolved: ResolvedSourceSet, cards: readonly Pick<SourceCardModel, "draft">[]): ResolvedSourceSet;
595
+
596
+ /**
597
+ * The Source-set controller — the imperative face of the state machine, with
598
+ * every outside dependency injected through one adapter:
599
+ *
600
+ * - `store` where the cards live (a Redux slice, a Zustand store, the
601
+ * in-memory store below — the host's choice);
602
+ * - `persistence` the draft store a reload reads back (optional);
603
+ * - `client` the Source doors (`createSourcesClient(transport)`);
604
+ * - `config` the host's choices: default form, organization, the
605
+ * deliveries it can use, the most Sources it takes.
606
+ *
607
+ * Every screen (the Studio grid, a compact strip, chat's "+", the review)
608
+ * drives the SAME controller; `@ai-matrx/agents/sources/react` binds it to
609
+ * React with `useSourceSet(adapter)`.
610
+ */
611
+
612
+ /** Where the cards live. `getState` must return the SAME object until something changes. */
613
+ interface SourceSetStore {
614
+ getState(): SourceSetState;
615
+ setState(next: SourceSetState): void;
616
+ subscribe(listener: () => void): () => void;
617
+ /** Called once when a screen mounts (a store that registers entries lazily). */
618
+ init?: (() => void) | undefined;
619
+ }
620
+ /** The draft store a reload reads back. `load` returns the raw saved value (stable until it changes). */
621
+ interface SourceDraftStorage {
622
+ load(): unknown;
623
+ save(value: PersistedSourceInput): void;
624
+ subscribe?: ((listener: () => void) => () => void) | undefined;
625
+ }
626
+ interface SourceSetConfig {
627
+ /** The form each new Source starts on ("clean" | "raw"). Omitted = the server's choice. */
628
+ defaultForm?: string | undefined;
629
+ /** The organization named on the door calls (they answer without one too). */
630
+ organizationId?: string | null | undefined;
631
+ /** How the AI may get the Sources on this surface. Omitted = both. */
632
+ deliveries?: readonly SourceDelivery[] | undefined;
633
+ /** Most Sources the person may pick. Omitted = no limit. */
634
+ max?: number | undefined;
635
+ }
636
+ interface SourceSetAdapter {
637
+ store: SourceSetStore;
638
+ persistence?: SourceDraftStorage | undefined;
639
+ /** Needed for `manifest()` and `resolve()`. */
640
+ client?: SourcesClient | undefined;
641
+ config?: SourceSetConfig | undefined;
642
+ /** A new card's id (default: a random UUID). */
643
+ createId?: (() => string) | undefined;
644
+ /** A failed call as one sentence with its remedy (default: the error's message). */
645
+ describeError?: ((error: unknown) => string) | undefined;
646
+ }
647
+ /** The measurement's own state (not persisted, not shared across controllers). */
648
+ interface SourceSetMeta {
649
+ measuring: boolean;
650
+ /** Why the measurement failed, with its remedy. */
651
+ manifestError: string | null;
652
+ }
653
+ /** The mutations every intake and screen drives. */
654
+ interface SourceSetActions {
655
+ /** Add a Source that is already a pointer (a stored record, a landed Source). */
656
+ addReady(draft: SourceDraft): string;
657
+ /** Add a Source that is still landing; finish it with `settle` or `fail`. */
658
+ addPending(draft: Omit<SourceDraft, "ref">): string;
659
+ settle(id: string, patch: Partial<SourceDraft> & {
660
+ ref: SourceRef;
661
+ }): void;
662
+ fail(id: string, sentence: string): void;
663
+ /** Back to landing. False when the card is gone (removed) — nothing to land. */
664
+ restart(id: string): boolean;
665
+ /** Change what a card that is still landing says or keeps (label, input, fileId, notes). */
666
+ updateDraft(id: string, patch: Partial<Omit<SourceDraft, "ref">>): void;
667
+ remove(id: string): void;
668
+ /** Change the pointer's choices (form, parts, cap, delivery). */
669
+ updateRef(id: string, options: SourceRefOptions): void;
670
+ setWaitForClean(id: string, wait: boolean): void;
671
+ setTopic(topic: string): void;
672
+ /** True when this pointer is already picked. */
673
+ hasRef(resourceType: string, resourceId: string): boolean;
674
+ /** How many Sources are picked RIGHT NOW (read from the store, never a stale render). */
675
+ liveCount(): number;
676
+ /** POST /sources/manifest for the ready Sources; cards update with it. */
677
+ manifest(): Promise<SourceManifest | null>;
678
+ }
679
+ interface SourceSetController extends SourceSetActions {
680
+ getState(): SourceSetState;
681
+ subscribe(listener: () => void): () => void;
682
+ getMeta(): SourceSetMeta;
683
+ subscribeMeta(listener: () => void): () => void;
684
+ /** Replace the host's choices (called on every render by a React binding). */
685
+ configure(config: SourceSetConfig): void;
686
+ readonly config: SourceSetConfig;
687
+ /** Bring back a saved draft once — only into an empty input. True when it restored. */
688
+ restore(saved: unknown): boolean;
689
+ /** The frozen v1 payload, built from the ready Sources. */
690
+ toSourceSet(options?: {
691
+ targetModelId?: string | undefined;
692
+ }): SourceSet;
693
+ /** Apply a set the review returned (forms, parts, caps, delivery). */
694
+ applySourceSet(set: SourceSet): void;
695
+ /** POST /sources/resolve — the grounded text for a generator, named as the cards name them. */
696
+ resolve(options?: {
697
+ targetModelId?: string | undefined;
698
+ }): Promise<ResolvedSourceSet>;
699
+ /** Switch back any Source set to a delivery the host cannot use (with a note). */
700
+ fitDeliveries(deliveries?: readonly SourceDelivery[]): void;
701
+ /** How many more Sources fit under the host's max. */
702
+ roomLeft(): number;
703
+ totalChars(): number;
704
+ settled(): boolean;
705
+ }
706
+ /** An in-memory store — for clients without their own, and for tests. */
707
+ declare function createMemorySourceSetStore(initial?: SourceSetState): SourceSetStore;
708
+ /** An in-memory draft store (a stand-in for device storage in tests and previews). */
709
+ declare function createMemorySourceDraftStorage(initial?: unknown): SourceDraftStorage & {
710
+ current(): unknown;
711
+ };
712
+ declare function createSourceSetController(adapter: SourceSetAdapter): SourceSetController;
713
+
714
+ /**
715
+ * Source intake — every way NEW material becomes a Source, through the doors
716
+ * that already exist on the server (DESIGN.md amendment A3). Each call adds a
717
+ * card at once (so the person sees it working), then settles it to a pointer
718
+ * or fails it with a sentence and a remedy. Nothing is ever sent onward as a
719
+ * blob.
720
+ *
721
+ * pasted text → `POST /sources/land`, kept.
722
+ * a web page → the host's scraper lands it (`addScrapedPage`), or a link
723
+ * typed before a reload is scraped again (`addWebPage`);
724
+ * `POST /sources/{id}/keep` files it against the thing being made.
725
+ * a file/image → the host's uploader hands over file ids (`addUploaded`); the
726
+ * file is filed against `attachTo` in the direction the
727
+ * association registry declares, and its Source is kept once
728
+ * reading makes one (`fileLanded`, driven by the recovery).
729
+ * existing → a registry item (`addExisting`).
730
+ * YouTube/audio → the transcript comes from the host's readers and lands
731
+ * through `POST /sources/land`.
732
+ *
733
+ * Never lose input: every landing keeps what the person handed over in the
734
+ * draft (`SourceDraft.input` — text, link, uploaded recording; never bytes)
735
+ * until it settles, so `resume` can land it again after a reload, a failure,
736
+ * or once an organization is chosen (the recovery replays every card waiting
737
+ * for one — from state, never an in-memory queue). Landing twice is safe: the
738
+ * door dedupes by (organization, identity, content hash).
739
+ *
740
+ * Every outside dependency is INJECTED (`SourceIntakeDeps`): the doors, the
741
+ * organization gate, the signed-in person, the failure wording.
742
+ */
743
+
744
+ /** A door's notice: a sentence, and sometimes a remedy code ("save_it…"). */
745
+ interface SourceLandingNotice {
746
+ message: string;
747
+ remedy?: string | null | undefined;
748
+ }
749
+ /** The target a door files a Source against (`attach_to`). */
750
+ interface SourceAttachTarget {
751
+ entity_type: string;
752
+ entity_id: string;
753
+ label: string | null;
754
+ signal: boolean;
755
+ }
756
+ /** The part of a landing body the intake reads or sets — the rest is the host's door shape. */
757
+ interface SourceLandingBodyBase {
758
+ name: string;
759
+ canonical_identity?: string | null | undefined;
760
+ provenance?: Record<string, unknown> | null | undefined;
761
+ }
762
+ /** A landed (or kept) Source's answer. */
763
+ interface SourceLandingAnswer {
764
+ processed_document_id: string;
765
+ notices?: SourceLandingNotice[] | null | undefined;
766
+ }
767
+ /** What the host's web reader returns for a link. Null = the page could not be read. */
768
+ interface ScrapedSourcePage {
769
+ processedDocumentId: string | null | undefined;
770
+ sourceNotices: SourceLandingNotice[];
771
+ pageTitle?: string | null | undefined;
772
+ }
773
+ /** What the host's YouTube reader returns. `source` = the server landed a timed transcript Source. */
774
+ interface YouTubeTranscriptResult {
775
+ text: string | null | undefined;
776
+ note?: string | null | undefined;
777
+ source?: {
778
+ processed_document_id: string;
779
+ title?: string | null | undefined;
780
+ } | null | undefined;
781
+ }
782
+ /** One file the host's uploader already uploaded. */
783
+ interface UploadedSourceFile {
784
+ fileId: string;
785
+ name: string;
786
+ }
787
+ /** The association write (the host's one chokepoint). */
788
+ interface SourceAssociationEdge {
789
+ sourceType: string;
790
+ sourceId: string;
791
+ targetType: string;
792
+ targetId: string;
793
+ orgId: string;
794
+ }
795
+ type SourceAssociationResult = {
796
+ ok: true;
797
+ } | {
798
+ ok: false;
799
+ error: {
800
+ message: string;
801
+ };
802
+ };
803
+ interface SourceIntakeDoors<Body extends SourceLandingBodyBase = SourceLandingBodyBase> {
804
+ /** `POST /sources/land` with the body as given (keep + attach_to set by the intake). */
805
+ land(body: Body & {
806
+ keep: true;
807
+ attach_to: SourceAttachTarget[];
808
+ }): Promise<SourceLandingAnswer>;
809
+ /** `POST /sources/{id}/keep`, filed against the targets. */
810
+ keep(processedDocumentId: string, options: {
811
+ attachTo: SourceAttachTarget[];
812
+ organizationId: string;
813
+ }): Promise<{
814
+ notices?: SourceLandingNotice[] | null | undefined;
815
+ }>;
816
+ /** Build the landing body for text the person handed over. */
817
+ buildTextLanding(input: {
818
+ text: string;
819
+ name?: string | undefined;
820
+ organizationId: string;
821
+ userId: string;
822
+ }): Promise<Body>;
823
+ /** Read a web page (the scraper lands it). Null = it could not be read. */
824
+ scrapeUrl(url: string): Promise<ScrapedSourcePage | null>;
825
+ /** Read a YouTube video's transcript. */
826
+ fetchYouTubeTranscript(url: string): Promise<YouTubeTranscriptResult>;
827
+ /** Write out a recording that is already stored. */
828
+ transcribeFile(input: {
829
+ fileId: string;
830
+ organizationId: string;
831
+ }): Promise<{
832
+ text?: string | null | undefined;
833
+ }>;
834
+ /** File one edge through the host's association chokepoint. */
835
+ associate(edge: SourceAssociationEdge): Promise<SourceAssociationResult>;
836
+ }
837
+ interface SourceIntakeDeps<Body extends SourceLandingBodyBase = SourceLandingBodyBase> {
838
+ doors: SourceIntakeDoors<Body>;
839
+ /** The organization to land in — asks the person when none is chosen (the host's gate). */
840
+ ensureOrganization(): Promise<string>;
841
+ /** Run a landing the person asked for so the gate can carry the click intent. Default: run it. */
842
+ holdIntent?: (<T>(run: () => Promise<T>) => Promise<T | undefined>) | undefined;
843
+ /** True when a landing stopped only because no organization is chosen yet. */
844
+ waitsForOrganization(error: unknown): boolean;
845
+ /** A failed landing as one sentence with its remedy. */
846
+ failureSentence(error: unknown): string;
847
+ /** The signed-in person (null while sign-in is still loading). */
848
+ userId(): string | null | undefined;
849
+ /** The YouTube video id in a link, or null. */
850
+ youtubeId(url: string): string | null | undefined;
851
+ /** The largest pasted text kept in the draft for a reload (null = unknown — nothing kept). */
852
+ maxKeptChars(): number | null;
853
+ /** The thing being made — new Sources are filed against it and kept. */
854
+ attachTo?: SourceAttachTo | undefined;
855
+ }
856
+ interface SourceIntake {
857
+ addPastedText(text: string, name?: string): Promise<void>;
858
+ addWebPage(url: string): Promise<void>;
859
+ /** A page the host's web picker already read (and the scraper landed); null id = land its text instead. */
860
+ addScrapedPage(page: {
861
+ url: string;
862
+ title: string;
863
+ text: string;
864
+ processedDocumentId: string | null;
865
+ }): Promise<void>;
866
+ /** Files the host's uploader already uploaded. */
867
+ addUploaded(files: readonly UploadedSourceFile[], kind: SourceKindId): Promise<void>;
868
+ /** A recording already uploaded — written out, then landed. */
869
+ addUploadedRecording(file: UploadedSourceFile): Promise<void>;
870
+ /** Something the person already has (Use existing / search). Returns false when already picked. */
871
+ addExisting(item: {
872
+ token: string;
873
+ id: string;
874
+ title: string;
875
+ }): boolean;
876
+ addYouTube(url: string): Promise<void>;
877
+ /** Land a card's kept input again (after a reload or a failure). False when nothing was kept. */
878
+ resume(card: SourceCardModel): boolean;
879
+ /** The person chose the file again (uploaded) for a card whose upload was cut off. */
880
+ retryFile(card: SourceCardModel, file: UploadedSourceFile): Promise<void>;
881
+ /** Reading an uploaded file made its Source: keep it and file it against `attachTo`. */
882
+ fileLanded(card: SourceCardModel, processedDocumentId: string): Promise<void>;
883
+ }
884
+ declare function sourceAttachTargets(attachTo: SourceAttachTo | undefined): SourceAttachTarget[];
885
+ /**
886
+ * Which way a file ↔ `targetType` edge is registered — read from the package's
887
+ * generated association registry (`@ai-matrx/associations`) — or null when it
888
+ * is not registered.
889
+ */
890
+ declare function registeredFileEdge(targetType: string): "file_to_target" | "target_to_file" | null;
891
+ declare function createSourceIntake<Body extends SourceLandingBodyBase>(set: SourceSetActions, deps: SourceIntakeDeps<Body>): SourceIntake;
892
+
893
+ /**
894
+ * Recovery — the UI-free half of "never lose input" (USI-3b), as decisions and
895
+ * one step function. A React binding (`@ai-matrx/agents/sources/react`
896
+ * `useSourceRecovery`) schedules them; any other client can call them itself.
897
+ *
898
+ * 1. A Source a reload cut off while it was landing, whose input the draft
899
+ * kept (`interrupted.ts`), is handed back to the door once — the door
900
+ * dedupes by content hash, so a second landing reuses the same Source.
901
+ * 2. A landing that waited for an organization is landed again the moment one
902
+ * is set — from the card's kept input, or, for a read Source whose keep
903
+ * waited, by keeping it again. State, never an in-memory queue.
904
+ * 3. A stored file's Source (new upload, reused copy, or picked file) is read
905
+ * from the SERVER's state (`fileSource.ts`): kept and filed once it exists,
906
+ * the one run started only when nothing is reading it, re-attached after a
907
+ * reload — never a duplicate run.
908
+ *
909
+ * The once-per-card guards live on `globalThis` (one registry however many
910
+ * loader graphs import this module), so two inputs on one surface, or a
911
+ * re-mount, never land the same card twice.
912
+ */
913
+
914
+ interface RecoveryGuards {
915
+ resumedCards: Set<string>;
916
+ keptFileSources: Set<string>;
917
+ startedFileRuns: Set<string>;
918
+ }
919
+ declare function sourceRecoveryGuards(): RecoveryGuards;
920
+ /** Cards a reload cut off whose kept input lands again now. */
921
+ declare function cardsToResume(cards: readonly SourceCardModel[]): SourceCardModel[];
922
+ /** Resume each cut-off card once (per card id, across every input). */
923
+ declare function resumeInterrupted(cards: readonly SourceCardModel[], intake: Pick<SourceIntake, "resume">): void;
924
+ /** Cards that waited for an organization (a landing, or a read Source's keep). */
925
+ declare function cardsWaitingForOrganization(cards: readonly SourceCardModel[]): SourceCardModel[];
926
+ /** An organization is known now: land or keep every card that waited for one. */
927
+ declare function continueAfterOrganization(cards: readonly SourceCardModel[], intake: Pick<SourceIntake, "resume" | "fileLanded">): void;
928
+ /** The host's processing runner, as far as the recovery needs it. */
929
+ interface SourceFileRunner {
930
+ jobs: ReadonlyArray<{
931
+ cldFileId?: string | null | undefined;
932
+ status: string;
933
+ processedDocumentId?: string | null | undefined;
934
+ }>;
935
+ runForCldFile(fileId: string, label: string, reason: string): Promise<unknown>;
936
+ }
937
+ interface FileRecoveryDeps {
938
+ /** The organization the person picked (null = none yet). */
939
+ organizationId: string | null | undefined;
940
+ /** The server's `/files/{id}/rag-status` read. */
941
+ readFileState(fileId: string, signal?: AbortSignal): Promise<FileSourceStatus>;
942
+ /** The host's lookup of a file's own organization (omitted = unknown). */
943
+ fileOrganizationId?: ((fileId: string) => string | null | undefined) | undefined;
944
+ }
945
+ /** A file card the recovery may ask about now (waiting, and not held for an organization). */
946
+ declare function isAskableFileCard(card: SourceCardModel, deps: Pick<FileRecoveryDeps, "organizationId" | "fileOrganizationId">): boolean;
947
+ /**
948
+ * One look at every askable file card: a Source → keep and file it; a live run
949
+ * → look again; nothing reading it → start the one run (once); unreadable →
950
+ * say so. Returns true while any card still waits (look again later).
951
+ */
952
+ declare function lookAtFileCards(cards: readonly SourceCardModel[], ctx: {
953
+ set: Pick<SourceSetActions, "fail" | "manifest">;
954
+ intake: Pick<SourceIntake, "fileLanded">;
955
+ runner: SourceFileRunner;
956
+ signal: AbortSignal;
957
+ } & FileRecoveryDeps): Promise<boolean>;
958
+
959
+ export { type BudgetVerdict, DELIVERY_CHOICES, DELIVERY_WORDS, type DeliveryWords, EMPTY_SOURCE_SET_STATE, type FetchSourcesTransportOptions, type FileRecoveryDeps, type FileSourceContext, type FileSourceStatus, type FileSourceStep, HEAVY_SHARE, KEEP_WAITING_FOR_ORGANIZATION, MAX_KEPT_TEXT_KNOB, NOT_KEPT_FOR_RELOAD, type PartMatchIds, type PersistedSourceCard, type PersistedSourceInput, type PlanInput, RELOADED_RESUMING, RELOADED_WHILE_ADDING, type ReloadedCard, SAME_SOURCE_AGAIN, SOURCE_CHARS_PER_TOKEN, type ScrapedSourcePage, type SourceAssociationEdge, type SourceAssociationResult, type SourceAttachTarget, type SourceAttachTo, type SourceCallOptions, type SourceCardModel, type SourceCardStatus, SourceDelivery, type SourceDoorCalls, SourceDoorError, type SourceDoorPath, type SourceDraft, type SourceDraftStorage, type SourceFileRunner, type SourceIntake, type SourceIntakeDeps, type SourceIntakeDoors, type SourceIntakeInput, type SourceKindId, type SourceLandingAnswer, type SourceLandingBodyBase, type SourceLandingNotice, type SourcePart, type SourcePartsSearchResult, type SourcePlan, type SourcePlanEntry, type SourcePlanStatus, type SourceSetActions, type SourceSetAdapter, type SourceSetConfig, type SourceSetController, type SourceSetMeta, type SourceSetState, type SourceSetStore, type SourceTileId, type SourceTransportOptions, type SourcesClient, type SourcesTransport, type UploadedSourceFile, WAITING_FOR_ORGANIZATION, type YouTubeTranscriptResult, addPendingCard, addReadyCard, allowedDeliveries, applyManifest, applyReviewedSourceSet, cardsToResume, cardsWaitingForOrganization, chosenForm, continueAfterOrganization, createFetchSourcesTransport, createMemorySourceDraftStorage, createMemorySourceSetStore, createSourceIntake, createSourceSetController, createSourcesClient, deliveryChoicesFor, deliveryPatch, deliverySwitchedNote, draftKindForToken, estimateSourceTokens, failCard, fileCardHeldForOrganization, fileSourcePollDelayMs, findParts, fitCardDeliveries, fitDelivery, hasSourceRef, isAskableFileCard, isSourceDraft, isWaitingFileCard, isWordQuery, lookAtFileCards, matchesPart, nextFileStep, partPages, planSourceReview, readPersistedSourceInput, readySourceRefs, registeredFileEdge, reloadedCard, removeCard, renderedPartChars, restartCard, restoredCards, resumableInput, resumeInterrupted, sameSourceRef, selectSettled, selectSourceSet, selectTotalChars, setCardWaitForClean, setSourceTopic, settleCard, sourceAttachTargets, sourceDelivery, sourceKey, sourceRecoveryGuards, sourceRoomLeft, toPersistedSourceInput, updateCardDraft, updateCardRef, uploadCutOffSentence, withDefaultForm, withDisplayNames };