@ai-matrx/media 0.8.0 → 0.9.0

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 (185) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/files/engine/api/assets.d.ts +223 -0
  3. package/dist/files/engine/api/assets.js +124 -0
  4. package/dist/files/engine/api/assets.js.map +1 -0
  5. package/dist/files/engine/api/direct.d.ts +43 -0
  6. package/dist/files/engine/api/direct.js +73 -0
  7. package/dist/files/engine/api/direct.js.map +1 -0
  8. package/dist/files/engine/api/fileOrganization.d.ts +34 -0
  9. package/dist/files/engine/api/fileOrganization.js +38 -0
  10. package/dist/files/engine/api/fileOrganization.js.map +1 -0
  11. package/dist/files/engine/api/files.d.ts +249 -0
  12. package/dist/files/engine/api/files.js +231 -0
  13. package/dist/files/engine/api/files.js.map +1 -0
  14. package/dist/files/engine/api/folders.d.ts +63 -0
  15. package/dist/files/engine/api/folders.js +56 -0
  16. package/dist/files/engine/api/folders.js.map +1 -0
  17. package/dist/files/engine/api/office.d.ts +27 -0
  18. package/dist/files/engine/api/office.js +19 -0
  19. package/dist/files/engine/api/office.js.map +1 -0
  20. package/dist/files/engine/api/permissions.d.ts +37 -0
  21. package/dist/files/engine/api/permissions.js +109 -0
  22. package/dist/files/engine/api/permissions.js.map +1 -0
  23. package/dist/files/engine/api/versions.d.ts +26 -0
  24. package/dist/files/engine/api/versions.js +37 -0
  25. package/dist/files/engine/api/versions.js.map +1 -0
  26. package/dist/files/engine/cache/idb-store.d.ts +95 -0
  27. package/dist/files/engine/cache/idb-store.js +172 -0
  28. package/dist/files/engine/cache/idb-store.js.map +1 -0
  29. package/dist/files/engine/cache/policy.d.ts +14 -0
  30. package/dist/files/engine/cache/policy.js +40 -0
  31. package/dist/files/engine/cache/policy.js.map +1 -0
  32. package/dist/files/engine/cache/register-service-worker.d.ts +57 -0
  33. package/dist/files/engine/cache/register-service-worker.js +80 -0
  34. package/dist/files/engine/cache/register-service-worker.js.map +1 -0
  35. package/dist/files/engine/db-types.d.ts +48865 -0
  36. package/dist/files/engine/db-types.js +1 -0
  37. package/dist/files/engine/db-types.js.map +1 -0
  38. package/dist/files/engine/filesDb.d.ts +1913 -0
  39. package/dist/files/engine/filesDb.js +28 -0
  40. package/dist/files/engine/filesDb.js.map +1 -0
  41. package/dist/files/engine/handler/handler.d.ts +82 -0
  42. package/dist/files/engine/handler/handler.js +124 -0
  43. package/dist/files/engine/handler/handler.js.map +1 -0
  44. package/dist/files/engine/handler/hooks/useFile.d.ts +17 -0
  45. package/dist/files/engine/handler/hooks/useFile.js +75 -0
  46. package/dist/files/engine/handler/hooks/useFile.js.map +1 -0
  47. package/dist/files/engine/handler/hooks/useFileUpload.d.ts +76 -0
  48. package/dist/files/engine/handler/hooks/useFileUpload.js +67 -0
  49. package/dist/files/engine/handler/hooks/useFileUpload.js.map +1 -0
  50. package/dist/files/engine/handler/input/normalize.d.ts +14 -0
  51. package/dist/files/engine/handler/input/normalize.js +365 -0
  52. package/dist/files/engine/handler/input/normalize.js.map +1 -0
  53. package/dist/files/engine/handler/intelligence/access.d.ts +35 -0
  54. package/dist/files/engine/handler/intelligence/access.js +85 -0
  55. package/dist/files/engine/handler/intelligence/access.js.map +1 -0
  56. package/dist/files/engine/handler/intelligence/magic-bytes.d.ts +21 -0
  57. package/dist/files/engine/handler/intelligence/magic-bytes.js +67 -0
  58. package/dist/files/engine/handler/intelligence/magic-bytes.js.map +1 -0
  59. package/dist/files/engine/handler/output/target.d.ts +19 -0
  60. package/dist/files/engine/handler/output/target.js +222 -0
  61. package/dist/files/engine/handler/output/target.js.map +1 -0
  62. package/dist/files/engine/handler/resolver.d.ts +34 -0
  63. package/dist/files/engine/handler/resolver.js +133 -0
  64. package/dist/files/engine/handler/resolver.js.map +1 -0
  65. package/dist/files/engine/handler/types.d.ts +367 -0
  66. package/dist/files/engine/handler/types.js +1 -0
  67. package/dist/files/engine/handler/types.js.map +1 -0
  68. package/dist/files/engine/handler/upload.d.ts +32 -0
  69. package/dist/files/engine/handler/upload.js +295 -0
  70. package/dist/files/engine/handler/upload.js.map +1 -0
  71. package/dist/files/engine/handler/utils/classify.d.ts +14 -0
  72. package/dist/files/engine/handler/utils/classify.js +27 -0
  73. package/dist/files/engine/handler/utils/classify.js.map +1 -0
  74. package/dist/files/engine/handler/utils/prefer-locator.d.ts +32 -0
  75. package/dist/files/engine/handler/utils/prefer-locator.js +34 -0
  76. package/dist/files/engine/handler/utils/prefer-locator.js.map +1 -0
  77. package/dist/files/engine/handler/utils/python-base.d.ts +129 -0
  78. package/dist/files/engine/handler/utils/python-base.js +60 -0
  79. package/dist/files/engine/handler/utils/python-base.js.map +1 -0
  80. package/dist/files/engine/hooks/blob-cache.d.ts +130 -0
  81. package/dist/files/engine/hooks/blob-cache.js +162 -0
  82. package/dist/files/engine/hooks/blob-cache.js.map +1 -0
  83. package/dist/files/engine/hooks/office-extraction-cache.d.ts +28 -0
  84. package/dist/files/engine/hooks/office-extraction-cache.js +67 -0
  85. package/dist/files/engine/hooks/office-extraction-cache.js.map +1 -0
  86. package/dist/files/engine/host/configure.d.ts +166 -0
  87. package/dist/files/engine/host/configure.js +30 -0
  88. package/dist/files/engine/host/configure.js.map +1 -0
  89. package/dist/files/engine/host/org.d.ts +8 -0
  90. package/dist/files/engine/host/org.js +16 -0
  91. package/dist/files/engine/host/org.js.map +1 -0
  92. package/dist/files/engine/host/python-client.d.ts +60 -0
  93. package/dist/files/engine/host/python-client.js +46 -0
  94. package/dist/files/engine/host/python-client.js.map +1 -0
  95. package/dist/files/engine/host/share-links.d.ts +20 -0
  96. package/dist/files/engine/host/share-links.js +32 -0
  97. package/dist/files/engine/host/share-links.js.map +1 -0
  98. package/dist/files/engine/host/store.d.ts +24 -0
  99. package/dist/files/engine/host/store.js +23 -0
  100. package/dist/files/engine/host/store.js.map +1 -0
  101. package/dist/files/engine/host/supabase.d.ts +3808 -0
  102. package/dist/files/engine/host/supabase.js +33 -0
  103. package/dist/files/engine/host/supabase.js.map +1 -0
  104. package/dist/files/engine/host/toast.d.ts +14 -0
  105. package/dist/files/engine/host/toast.js +20 -0
  106. package/dist/files/engine/host/toast.js.map +1 -0
  107. package/dist/files/engine/host/typed-client.d.ts +136 -0
  108. package/dist/files/engine/host/typed-client.js +66 -0
  109. package/dist/files/engine/host/typed-client.js.map +1 -0
  110. package/dist/files/engine/index.d.ts +22 -0
  111. package/dist/files/engine/index.js +49 -0
  112. package/dist/files/engine/index.js.map +1 -0
  113. package/dist/files/engine/media/our-file-sources.d.ts +39 -0
  114. package/dist/files/engine/media/our-file-sources.js +83 -0
  115. package/dist/files/engine/media/our-file-sources.js.map +1 -0
  116. package/dist/files/engine/media/signed-url.d.ts +1 -0
  117. package/dist/files/engine/media/signed-url.js +6 -0
  118. package/dist/files/engine/media/signed-url.js.map +1 -0
  119. package/dist/files/engine/redux/converters.d.ts +73 -0
  120. package/dist/files/engine/redux/converters.js +286 -0
  121. package/dist/files/engine/redux/converters.js.map +1 -0
  122. package/dist/files/engine/redux/file-hydration.d.ts +15 -0
  123. package/dist/files/engine/redux/file-hydration.js +46 -0
  124. package/dist/files/engine/redux/file-hydration.js.map +1 -0
  125. package/dist/files/engine/redux/file-tree-auth-boundary.d.ts +5 -0
  126. package/dist/files/engine/redux/file-tree-auth-boundary.js +28 -0
  127. package/dist/files/engine/redux/file-tree-auth-boundary.js.map +1 -0
  128. package/dist/files/engine/redux/file-tree-timeout.d.ts +32 -0
  129. package/dist/files/engine/redux/file-tree-timeout.js +73 -0
  130. package/dist/files/engine/redux/file-tree-timeout.js.map +1 -0
  131. package/dist/files/engine/redux/mutation-toast-middleware.d.ts +27 -0
  132. package/dist/files/engine/redux/mutation-toast-middleware.js +103 -0
  133. package/dist/files/engine/redux/mutation-toast-middleware.js.map +1 -0
  134. package/dist/files/engine/redux/realtime-middleware.d.ts +61 -0
  135. package/dist/files/engine/redux/realtime-middleware.js +431 -0
  136. package/dist/files/engine/redux/realtime-middleware.js.map +1 -0
  137. package/dist/files/engine/redux/request-ledger.d.ts +68 -0
  138. package/dist/files/engine/redux/request-ledger.js +66 -0
  139. package/dist/files/engine/redux/request-ledger.js.map +1 -0
  140. package/dist/files/engine/redux/selectors.d.ts +3254 -0
  141. package/dist/files/engine/redux/selectors.js +428 -0
  142. package/dist/files/engine/redux/selectors.js.map +1 -0
  143. package/dist/files/engine/redux/slice.d.ts +157 -0
  144. package/dist/files/engine/redux/slice.js +836 -0
  145. package/dist/files/engine/redux/slice.js.map +1 -0
  146. package/dist/files/engine/redux/thunks.d.ts +449 -0
  147. package/dist/files/engine/redux/thunks.js +1684 -0
  148. package/dist/files/engine/redux/thunks.js.map +1 -0
  149. package/dist/files/engine/redux/tree-utils.d.ts +90 -0
  150. package/dist/files/engine/redux/tree-utils.js +200 -0
  151. package/dist/files/engine/redux/tree-utils.js.map +1 -0
  152. package/dist/files/engine/support/claimsUser.d.ts +77 -0
  153. package/dist/files/engine/support/claimsUser.js +48 -0
  154. package/dist/files/engine/support/claimsUser.js.map +1 -0
  155. package/dist/files/engine/support/datetime.d.ts +6 -0
  156. package/dist/files/engine/support/datetime.js +15 -0
  157. package/dist/files/engine/support/datetime.js.map +1 -0
  158. package/dist/files/engine/support/document-visibility.d.ts +14 -0
  159. package/dist/files/engine/support/document-visibility.js +28 -0
  160. package/dist/files/engine/support/document-visibility.js.map +1 -0
  161. package/dist/files/engine/support/logger.d.ts +28 -0
  162. package/dist/files/engine/support/logger.js +36 -0
  163. package/dist/files/engine/support/logger.js.map +1 -0
  164. package/dist/files/engine/types.d.ts +1081 -0
  165. package/dist/files/engine/types.js +30 -0
  166. package/dist/files/engine/types.js.map +1 -0
  167. package/dist/files/engine/upload/cloudUpload.d.ts +182 -0
  168. package/dist/files/engine/upload/cloudUpload.js +418 -0
  169. package/dist/files/engine/upload/cloudUpload.js.map +1 -0
  170. package/dist/files/engine/upload/tusUpload.d.ts +100 -0
  171. package/dist/files/engine/upload/tusUpload.js +258 -0
  172. package/dist/files/engine/upload/tusUpload.js.map +1 -0
  173. package/dist/files/engine/upload/uploadDedupGuard.d.ts +58 -0
  174. package/dist/files/engine/upload/uploadDedupGuard.js +41 -0
  175. package/dist/files/engine/upload/uploadDedupGuard.js.map +1 -0
  176. package/dist/files/engine/upload/uploadGuardOpeners.d.ts +70 -0
  177. package/dist/files/engine/upload/uploadGuardOpeners.js +54 -0
  178. package/dist/files/engine/upload/uploadGuardOpeners.js.map +1 -0
  179. package/dist/files/engine/utils/file-types.d.ts +243 -0
  180. package/dist/files/engine/utils/file-types.js +1722 -0
  181. package/dist/files/engine/utils/file-types.js.map +1 -0
  182. package/dist/files/engine/utils/user-visible.d.ts +133 -0
  183. package/dist/files/engine/utils/user-visible.js +115 -0
  184. package/dist/files/engine/utils/user-visible.js.map +1 -0
  185. package/package.json +393 -9
@@ -0,0 +1,1081 @@
1
+ /**
2
+ * features/files/types.ts
3
+ *
4
+ * Single source of truth for all cloud-files types. Import from
5
+ * `@/features/files/types` — never duplicate types in feature subfolders.
6
+ *
7
+ * LAYERS
8
+ * ------
9
+ * 1. Domain types — camelCase, what components and Redux work with.
10
+ * 2. DB row types — snake_case, derived from Supabase-generated `Database`.
11
+ * 3. API types — from Python OpenAPI-generated `components["schemas"]`.
12
+ * 4. Runtime records — domain types + dirty/loading/error metadata for Redux.
13
+ * 5. Tree & UI types — normalized structure + UI state shapes.
14
+ * 6. Upload types — upload orchestrator state.
15
+ * 7. Error types — re-export of BackendApiError, plus files-specific codes.
16
+ *
17
+ * Do NOT duplicate or re-declare any type from here in feature subfolders.
18
+ */
19
+ import type { components } from "@ai-matrx/agents/generated/api-types";
20
+ import type { Database } from "./db-types.js";
21
+ import type { FieldFlags } from "@ai-matrx/agents/field-flags";
22
+ import type { readFileRowById } from "./filesDb.js";
23
+ /**
24
+ * THE canonical `platform.visibility` enum, in enum order:
25
+ * `personal < internal < link < public`. Identical on the server
26
+ * (`matrx_utils.visibility.VisibilityLiteral`) and in the DB. One vocabulary,
27
+ * no per-domain dialect.
28
+ *
29
+ * Two bugs are fossilized here; do not reintroduce either.
30
+ *
31
+ * 1. `internal` was folded into `personal` on read until 2026-07-26, so a file
32
+ * readable by an entire organization was labelled "Only you" everywhere.
33
+ * Never collapse a level to shrink this union — they are different facts.
34
+ *
35
+ * 2. This domain used to call `link` "shared". That is NOT a synonym: `shared`
36
+ * was RETIRED from the DB enum on 2026-07-21, and the server's legacy map
37
+ * reconciles it to `personal`. So the old code read `link` as `"shared"`
38
+ * and, on any write-back, silently DOWNGRADED the file to `personal`.
39
+ * Never send `shared` or `private` to the server.
40
+ */
41
+ import type { Visibility, PermissionLevel, MediaRef, FileIdentityHint } from "../../files.js";
42
+ export type { Visibility, PermissionLevel, MediaRef, FileIdentityHint };
43
+ export type ResourceType = "file" | "folder";
44
+ /**
45
+ * Who a grant targets. Mirrors the canonical `iam.permissions` three-way
46
+ * mutual exclusion (CHECK `user_or_org_or_public`): exactly one of a user, an
47
+ * organization, or the public sentinel per row.
48
+ * - `"user"` — `granted_to_user_id` set; `granteeId` is that user id.
49
+ * - `"group"` — `granted_to_organization_id` set; `granteeId` is that org id.
50
+ * - `"public"` — `is_public = true`; there is NO grantee id, so `granteeId`
51
+ * carries the permission row's own id (never `""`). A public
52
+ * grant is "anyone with access", not a member — member/avatar
53
+ * UIs must exclude it, and it is revoked by flipping the
54
+ * resource's visibility (see `features/sharing/FEATURE.md`),
55
+ * not via the by-grantee-id REST endpoint.
56
+ */
57
+ export type GranteeType = "user" | "group" | "public";
58
+ /**
59
+ * Metadata a caller may already know when all it has is a durable file id.
60
+ * These values seed the canonical Redux record before field hydration runs;
61
+ * omitted keys remain genuinely unloaded and are fetched on demand.
62
+ */
63
+ type FilesTables = Database["files"]["Tables"];
64
+ type IamTables = Database["iam"]["Tables"];
65
+ export type CloudFileRow = FilesTables["files"]["Row"];
66
+ export type CloudFileInsert = FilesTables["files"]["Insert"];
67
+ export type CloudFileUpdate = FilesTables["files"]["Update"];
68
+ /**
69
+ * What the client is actually ALLOWED to read from `files.files` — DERIVED from
70
+ * `FILES_TABLE_COLUMNS` (the ONE canonical select, in [filesDb.ts](./filesDb.ts))
71
+ * rather than declared beside it, so the two can never disagree.
72
+ *
73
+ * `files.files` grants `authenticated` NO table-level SELECT: every readable
74
+ * column carries its own column grant, which is how the server-only native
75
+ * storage location stays server-only. So a column ADDED to the table is
76
+ * readable by nobody until a migration grants it, and a column list that names
77
+ * an ungranted column fails the WHOLE read with "permission denied for table
78
+ * files". Subtracting one name from the generated Row type therefore described
79
+ * a row the client cannot actually read: on 2026-09-13 the table gained
80
+ * `origin_device_id` and `client_modified_at` (folder-sync 028, no client
81
+ * grant then) and every selected row stopped matching this type. Deriving from
82
+ * the query makes the select list the single truth. (2026-09-24: `authenticated`
83
+ * now holds SELECT on `origin_device_id` and `artifact_kind`, verified live, and
84
+ * both are in the select list — Recents needs the first.)
85
+ *
86
+ * Same deal for `file_versions` via `FILE_VERSIONS_TABLE_COLUMNS`.
87
+ */
88
+ export type CloudFileReadRow = NonNullable<Awaited<ReturnType<typeof readFileRowById>>>;
89
+ export type CloudFileVersionReadRow = Omit<FilesTables["file_versions"]["Row"], "storage_uri" | "custom_fields">;
90
+ export type CloudFolderRow = FilesTables["folders"]["Row"];
91
+ export type CloudFolderInsert = FilesTables["folders"]["Insert"];
92
+ export type CloudFolderUpdate = FilesTables["folders"]["Update"];
93
+ export type CloudFileVersionRow = FilesTables["file_versions"]["Row"];
94
+ /**
95
+ * File-permission grants live in the CANONICAL grant store `iam.permissions`
96
+ * (resource_type='file'), NOT in the legacy cld_ file-permission duplicate
97
+ * (deprecated in the 2026 DB cutover — see docs/db_rebuild/03-app-agent-cutover-instructions.md §1a).
98
+ */
99
+ export type CloudFilePermissionRow = IamTables["permissions"]["Row"];
100
+ /**
101
+ * Summary JSON returned by `iam.fn_list_resource_permissions` and
102
+ * `iam.fn_grant_resource_permission` — NOT a full `iam.permissions` row.
103
+ * Maps grantee/org columns into `grantee_id` + `grantee_type` and file-level
104
+ * `read|write|admin` permission strings.
105
+ */
106
+ export interface IamResourcePermissionRpcRow {
107
+ resource_id: string;
108
+ resource_type: string;
109
+ grantee_id: string;
110
+ grantee_type: string;
111
+ permission_level: string;
112
+ granted_by: string | null;
113
+ expires_at: string | null;
114
+ }
115
+ export type FileRecordApi = components["schemas"]["FileRecord"];
116
+ export type FileUploadResponse = components["schemas"]["FileUploadResponse"];
117
+ export type FilePatchRequest = components["schemas"]["FilePatchRequest"];
118
+ export type GrantPermissionRequest = components["schemas"]["GrantPermissionRequest"];
119
+ /**
120
+ * Discriminator on every cloud-file record. Real records are bytes in S3;
121
+ * virtual records are Postgres rows surfaced by a `VirtualSourceAdapter`
122
+ * (Notes, Agent Apps, Tool UIs, code-files snippets, etc.) — see
123
+ * [features/files/virtual-sources/types.ts](./virtual-sources/types.ts).
124
+ *
125
+ * The `source` field defaults to `{ kind: "real" }` everywhere so existing
126
+ * callers compile unchanged. Synthetic ids of shape
127
+ * `vfs:<adapterId>:<virtualId>[:<fieldId>]` keep the cloud-files Redux
128
+ * `filesById` / `foldersById` maps a single keyspace.
129
+ */
130
+ export type FileSource = {
131
+ kind: "real";
132
+ } | {
133
+ kind: "virtual";
134
+ adapterId: string;
135
+ virtualId: string;
136
+ fieldId?: string;
137
+ };
138
+ export interface CloudFile {
139
+ id: string;
140
+ ownerId: string;
141
+ /** Canonical owning workspace from `files.files.organization_id`. */
142
+ organizationId?: string | null;
143
+ filePath: string;
144
+ fileName: string;
145
+ mimeType: string | null;
146
+ fileSize: number | null;
147
+ checksum: string | null;
148
+ visibility: Visibility;
149
+ currentVersion: number;
150
+ parentFolderId: string | null;
151
+ metadata: Record<string, unknown>;
152
+ createdAt: string;
153
+ updatedAt: string;
154
+ deletedAt: string | null;
155
+ /**
156
+ * Permanent CDN URL (Cloudflare-fronted) when the file is public AND
157
+ * the server has the CDN feature enabled. ``null`` otherwise — callers
158
+ * should fall back to ``useFileSrc({ kind: "file_id", fileId })`` for the durable URL.
159
+ *
160
+ * Carries a ``?v=<checksum[:8]>`` cache-buster so a content change
161
+ * invalidates the cache instantly. **Do not strip the query string.**
162
+ *
163
+ * Populated by the API converter (``apiFileRecordToCloudFile``);
164
+ * always ``null`` for rows that came in via the direct DB read path
165
+ * because the DB has no ``public_url`` column — it's computed
166
+ * server-side from visibility + storage location + checksum. For DB-sourced
167
+ * rows, fall back to ``useFileSrc({ kind: "file_id", fileId })`` to fetch the canonical
168
+ * URL (which the server returns as a CDN URL when applicable).
169
+ */
170
+ publicUrl: string | null;
171
+ /**
172
+ * The DURABLE URL envelope the REST `FileRecord` carries. `url` is the
173
+ * server's canonical always-renderable pick (CDN for public, the durable
174
+ * `/files/{id}/download?inline=1` route for private — authenticated by
175
+ * the `mx_files_session` cookie). `cdnUrl` is public-only and permanent.
176
+ * `downloadUrl` carries attachment disposition. None of them expire.
177
+ *
178
+ * These are populated by `apiFileRecordToCloudFile` from the REST
179
+ * response. They are `null` on the direct-DB read path (the `cld_files`
180
+ * table has no computed-URL columns) — DB-sourced rows build the durable
181
+ * URL from the file id via the resolver.
182
+ */
183
+ url: string | null;
184
+ cdnUrl: string | null;
185
+ downloadUrl: string | null;
186
+ /**
187
+ * Backend-rendered thumbnail URL (Phase 1b universal thumbnails). Set
188
+ * for **every** uploaded file regardless of MIME — Python now renders
189
+ * SOCIAL_BASELINE variants (og_url / thumbnail_url / tiny_url) for
190
+ * images, PDFs (page 1), videos (10%-mark frame), audio (waveform),
191
+ * and even archives / text / unknown mimes (mime-family icon PNGs).
192
+ *
193
+ * Populated by `apiFileRecordToCloudFile` from `FileRecord.thumbnail_url`
194
+ * — the REST response field. The server resolves this from the
195
+ * variants store at request time (post-Phase-1b the legacy
196
+ * `cld_files.thumbnail_url` column is dropped).
197
+ *
198
+ * `null` for rows coming in via the direct Supabase read path — the
199
+ * row doesn't carry resolved URLs. Those callers should fall back to
200
+ * `useFileAsset(fileId)` and read `asset.variants["thumbnail_url"].url`,
201
+ * or `MediaThumbnail` will fall back to the category icon.
202
+ *
203
+ * Phase 1c: PDFs additionally get `Asset.variants["page1_url"]`
204
+ * (page 1 at 150 DPI ~1200×1700) for full-page detail views, and
205
+ * videos get `Asset.variants["poster_url"]` (native-res frame) for
206
+ * the HTML5 `<video poster>` attribute. Those are separate from this
207
+ * `thumbnailUrl` field and require the asset fetch.
208
+ */
209
+ thumbnailUrl: string | null;
210
+ /** Real S3-backed bytes vs. virtual Postgres-backed adapter row. */
211
+ source: FileSource;
212
+ /**
213
+ * Binary lineage — points to the cld_files row this one was derived
214
+ * from (e.g. "extracted text from this PDF" or "page range 5–10 of
215
+ * the parent PDF"). Set by Phase 4A migration `0006_cld_files_lineage`.
216
+ * Null when the file was uploaded directly with no derivation.
217
+ * Optional on the FE because it is null for nearly every existing
218
+ * file and was added to the API after the initial schema landed.
219
+ */
220
+ parentFileId?: string | null;
221
+ /**
222
+ * Free-form classifier set by the deriving system: "pdf_text_extract",
223
+ * "page_range_5_10", "ocr_re_run", "merge", … Used by lineage chips
224
+ * to label *how* the parent relates to this file.
225
+ */
226
+ derivationKind?: string | null;
227
+ derivationMetadata?: Record<string, unknown> | null;
228
+ /**
229
+ * The registered device (`public.app_instances.id`) that wrote this row — a
230
+ * desktop sync client — or null for an in-app write. A device's write is the
231
+ * person's file but never their recent activity: Recents keys on this via
232
+ * `isRecentActivityFile` (mirror of `files.is_recent_activity`).
233
+ */
234
+ originDeviceId?: string | null;
235
+ /**
236
+ * When set, this row is a deliberate parallel copy of `duplicateOfFileId`
237
+ * (the keeper). Set by:
238
+ * - the dedup consolidation script (soft-deletes the duplicate row +
239
+ * stamps the keeper's id here so refs to the dup still resolve), or
240
+ * - `intent: "force_new_copy"` uploads (user explicitly asked for a
241
+ * second copy of identical content).
242
+ * Null for every freshly-uploaded file.
243
+ *
244
+ * UI: surface a "duplicate of <keeper>" chip on rows where this is set.
245
+ * Refs that hit a `deletedAt != null` row should `useFile(duplicateOfFileId)`
246
+ * to follow the chain to the live keeper.
247
+ */
248
+ duplicateOfFileId?: string | null;
249
+ /**
250
+ * Points at the "official" `processed_documents.id` for this file —
251
+ * the canonical text-extraction row out of potentially many re_extract /
252
+ * re_clean variants. UIs that show "extracted text" or feed Knowledge should
253
+ * use this column to find THE extract, not the freshest one.
254
+ */
255
+ canonicalProcessedDocumentId?: string | null;
256
+ }
257
+ export interface CloudFolder {
258
+ id: string;
259
+ ownerId: string;
260
+ folderPath: string;
261
+ folderName: string;
262
+ parentId: string | null;
263
+ visibility: Visibility;
264
+ metadata: Record<string, unknown>;
265
+ createdAt: string;
266
+ updatedAt: string;
267
+ deletedAt: string | null;
268
+ /** Real cloud-folder vs. virtual adapter root / nested adapter folder. */
269
+ source: FileSource;
270
+ }
271
+ export interface CloudFileVersion {
272
+ id: string;
273
+ fileId: string;
274
+ versionNumber: number;
275
+ fileSize: number | null;
276
+ checksum: string | null;
277
+ createdBy: string | null;
278
+ createdAt: string;
279
+ changeSummary: string | null;
280
+ }
281
+ export interface CloudFilePermission {
282
+ id: string;
283
+ resourceId: string;
284
+ resourceType: ResourceType;
285
+ granteeId: string;
286
+ granteeType: GranteeType;
287
+ permissionLevel: PermissionLevel;
288
+ grantedBy: string | null;
289
+ grantedAt: string;
290
+ expiresAt: string | null;
291
+ }
292
+ /**
293
+ * A canonical share link (`platform.share_links`) scoped to a file or folder.
294
+ * Minted/listed/revoked via the canonical RPC family (`create_share_link` /
295
+ * `list_share_links` / `revoke_share_link` — see `utils/permissions/shareLinks.ts`).
296
+ */
297
+ export interface CloudShareLink {
298
+ id: string;
299
+ resourceId: string;
300
+ resourceType: ResourceType;
301
+ shareToken: string;
302
+ permissionLevel: "viewer" | "editor";
303
+ label: string | null;
304
+ createdAt: string | null;
305
+ expiresAt: string | null;
306
+ maxUses: number | null;
307
+ useCount: number;
308
+ isActive: boolean;
309
+ }
310
+ export interface CloudTreeFileRow {
311
+ kind: "file";
312
+ id: string;
313
+ file_path: string;
314
+ file_name: string;
315
+ parent_folder_id: string | null;
316
+ mime_type: string | null;
317
+ /**
318
+ * File size in bytes. Renamed from `file_size` in Phase 0 (see
319
+ * docs/PYTHON_UPDATES.md §3). The RPC `tree_for_owner` (and friends)
320
+ * now returns `size_bytes`; converters read both names defensively
321
+ * during the transition. Consumers should always read `size_bytes`.
322
+ */
323
+ size_bytes: number | null;
324
+ visibility: Visibility;
325
+ current_version: number;
326
+ effective_permission: PermissionLevel | null;
327
+ owner_id: string;
328
+ created_at: string;
329
+ updated_at: string;
330
+ deleted_at: string | null;
331
+ /** Device that wrote the row (desktop sync), null for an in-app write. */
332
+ origin_device_id: string | null;
333
+ /**
334
+ * The row's metadata as the RPC returns it — carries `system_artifact`, the
335
+ * marker every file list reads through `isListedFile` (2026-09-29; the tree
336
+ * used to drop it, so system files could not be told apart in the store).
337
+ */
338
+ metadata: Record<string, unknown>;
339
+ }
340
+ export interface CloudTreeFolderRow {
341
+ kind: "folder";
342
+ id: string;
343
+ folder_path: string;
344
+ folder_name: string;
345
+ parent_id: string | null;
346
+ visibility: Visibility;
347
+ effective_permission: PermissionLevel | null;
348
+ owner_id: string;
349
+ created_at: string;
350
+ updated_at: string;
351
+ deleted_at: string | null;
352
+ metadata: Record<string, unknown>;
353
+ }
354
+ export type CloudTreeRow = CloudTreeFileRow | CloudTreeFolderRow;
355
+ export interface RuntimeMetadata<K extends string> {
356
+ _dirty: boolean;
357
+ _dirtyFields: FieldFlags<K>;
358
+ _loadedFields: FieldFlags<K>;
359
+ _loading: boolean;
360
+ _error: string | null;
361
+ _pendingRequestIds: string[];
362
+ }
363
+ export type CloudFileFieldSnapshot = Partial<Pick<CloudFile, "fileName" | "filePath" | "visibility" | "parentFolderId" | "metadata" | "deletedAt">>;
364
+ export interface CloudFileRecord extends CloudFile, RuntimeMetadata<keyof CloudFile> {
365
+ _fieldHistory: CloudFileFieldSnapshot;
366
+ }
367
+ export type CloudFolderFieldSnapshot = Partial<Pick<CloudFolder, "folderName" | "folderPath" | "parentId" | "visibility" | "metadata">>;
368
+ export interface CloudFolderRecord extends CloudFolder, RuntimeMetadata<keyof CloudFolder> {
369
+ _fieldHistory: CloudFolderFieldSnapshot;
370
+ }
371
+ export interface TreeChildren {
372
+ folderIds: string[];
373
+ fileIds: string[];
374
+ }
375
+ export interface TreeState {
376
+ rootFolderIds: string[];
377
+ rootFileIds: string[];
378
+ childrenByFolderId: Record<string, TreeChildren>;
379
+ fullyLoadedFolderIds: Record<string, true>;
380
+ status: "idle" | "loading" | "loaded" | "error";
381
+ error: string | null;
382
+ lastReconciledAt: number | null;
383
+ }
384
+ export type ViewMode = "list" | "grid" | "columns";
385
+ /**
386
+ * Sticky filter chips above the file table. Mirrors the `FilterChips`
387
+ * component's union — re-declared here (not imported) so the slice
388
+ * stays component-cycle-free. Keep in sync with
389
+ * `features/files/components/surfaces/desktop/FilterChips.tsx`.
390
+ */
391
+ export type ChipFilter = "recents" | "starred";
392
+ /**
393
+ * Column sort keys. Mirror the file-table columns so users can sort by any
394
+ * column they're looking at. Folders always group ahead of files regardless
395
+ * of the active key (Box / Drive / Dropbox convention) — see `compareNodes`.
396
+ */
397
+ export type SortBy = "name" | "type" | "extension" | "mime" | "path" | "owner" | "size" | "version" | "updated_at" | "created_at";
398
+ export type SortDirection = "asc" | "desc";
399
+ /** Whether the file table shows files only, folders only, or both. */
400
+ export type KindFilter = "all" | "files" | "folders";
401
+ /** Optional details surface (extension, mime, dimensions, etc.) on rows. */
402
+ export type DetailsLevel = "compact" | "extended";
403
+ /** Modified-date preset filter — "today", "week" = last 7d, "month" = last 30d. */
404
+ export type ModifiedFilter = "any" | "today" | "week" | "month";
405
+ /** Size preset filter — buckets familiar to users. */
406
+ export type SizeFilter = "any" | "small" | "medium" | "large" | "huge";
407
+ /**
408
+ * Access (visibility) filter. One option per `Visibility` level plus "any" —
409
+ * a level with no option is a level whose rows can never be filtered to.
410
+ */
411
+ export type AccessFilter = "any" | Visibility;
412
+ /**
413
+ * Type filter — multi-select set of file categories (CODE, DOCUMENT, IMAGE,
414
+ * VIDEO, …). Empty array = "any type". Modeled as `string[]` (not the
415
+ * `FileCategory` enum from `utils/file-types.ts`) to keep the slice type
416
+ * import-cycle-free; the selectors / pickers cast at the boundary.
417
+ */
418
+ export type TypeFilter = string[];
419
+ /** Owner filter — multi-select set of owner user ids. Empty = "any owner". */
420
+ export type OwnerFilter = string[];
421
+ /**
422
+ * Per-file Knowledge indexing status.
423
+ *
424
+ * - `indexed` — backend has a `processed_documents` row for this file
425
+ * - `not_indexed` — file exists but no doc row (`/files/{id}/document` 404)
426
+ * - `pending` — request is in flight
427
+ * - `unknown` — endpoint returned a transient error or hasn't been
428
+ * called yet for this file
429
+ *
430
+ * The pending state matters because the user toggles "Show Knowledge status"
431
+ * across hundreds of files at once — the column needs to show "Checking…"
432
+ * for the rows still in flight rather than flickering "Not indexed".
433
+ */
434
+ export type RagStatus = "indexed" | "not_indexed" | "pending" | "unknown";
435
+ /**
436
+ * Knowledge filter — multi-select of statuses. Empty array = "any status".
437
+ * Modeled as `string[]` (not `RagStatus[]`) to mirror `TypeFilter` /
438
+ * `OwnerFilter` and keep the slice type import-cycle-free.
439
+ */
440
+ export type RagFilter = string[];
441
+ /** Per-column filters surfaced through the column-header dropdowns. */
442
+ export interface ColumnFilters {
443
+ /** Name "contains" — column-scoped text filter, distinct from the
444
+ * global search box. */
445
+ name: string;
446
+ /** File category multi-select (Image / Video / Code / …). */
447
+ type: TypeFilter;
448
+ /** Extension "contains" — e.g. "pdf", "jp" matches jpg & jpeg. */
449
+ extension: string;
450
+ /** MIME "contains" — e.g. "image/" matches every image. */
451
+ mime: string;
452
+ /** Folder path "contains" — useful in tree-wide search results. */
453
+ path: string;
454
+ /** Owner user-id multi-select. */
455
+ owner: OwnerFilter;
456
+ modified: ModifiedFilter;
457
+ /** Same preset semantics as `modified`, applied to `createdAt`. */
458
+ created: ModifiedFilter;
459
+ size: SizeFilter;
460
+ access: AccessFilter;
461
+ /**
462
+ * Knowledge indexing status multi-select. Only meaningful when the user has
463
+ * fetched Knowledge statuses (via the Knowledge column toggle in column-settings or
464
+ * the column-header refresh button) — folders never pass this filter
465
+ * since Knowledge indexing is a file-only concept.
466
+ */
467
+ rag: RagFilter;
468
+ }
469
+ /**
470
+ * Stable ids for every optional / required column rendered in the file
471
+ * table. Hidden vs. visible columns are tracked in
472
+ * `UiState.visibleColumns` — a Box.com / Google-Drive-style "Choose
473
+ * columns" panel toggles each on/off. `name` and `access` are conceptually
474
+ * always present but are still included here so the type remains the
475
+ * single source of truth.
476
+ */
477
+ export type ColumnId = "name" | "type" | "extension" | "mime" | "path" | "owner" | "size" | "version" | "updated_at" | "created_at" | "access" | "rag_status" | "context";
478
+ export type VisibleColumns = Record<ColumnId, boolean>;
479
+ /**
480
+ * Default column set on first load. Tuned to match what users coming from
481
+ * Box.com / Google Drive expect to see by default — Type is shown
482
+ * because "what kind of file is this" is the second-most-important
483
+ * question after "what's its name", and the previous shipping default
484
+ * (just Name / Modified / Size / Access) silently hid that signal.
485
+ *
486
+ * `rag_status` is OFF by default because populating it requires a per-file
487
+ * network probe that the user explicitly opts into.
488
+ */
489
+ export declare const DEFAULT_VISIBLE_COLUMNS: VisibleColumns;
490
+ export interface UiState {
491
+ viewMode: ViewMode;
492
+ sortBy: SortBy;
493
+ sortDir: SortDirection;
494
+ /** Files-only / folders-only / both. Default = "all". */
495
+ kindFilter: KindFilter;
496
+ /** Whether to show extra detail columns (Extension, Type, Owner, …). */
497
+ detailsLevel: DetailsLevel;
498
+ /** Per-column filter values driven by the column-header dropdowns. */
499
+ columnFilters: ColumnFilters;
500
+ /** Which optional columns are mounted in the file table. */
501
+ visibleColumns: VisibleColumns;
502
+ /**
503
+ * Tree-wide search box value. Lives in Redux (not local component state)
504
+ * so the URL-sync layer can reflect it as `?q=…` and so cross-component
505
+ * reads (e.g. clearing search from a chip) don't need callbacks.
506
+ */
507
+ searchQuery: string;
508
+ /**
509
+ * Sticky filter chip currently active (Recents / Starred). Independent
510
+ * of `kindFilter` — chips apply preset semantics on top of any column
511
+ * filters. `null` = no chip.
512
+ */
513
+ chipFilter: ChipFilter | null;
514
+ activeFileId: string | null;
515
+ activeFolderId: string | null;
516
+ /**
517
+ * The single item (file or folder id) that has "keyboard/visual focus" — the
518
+ * highlighted row in the Google Drive sense. Set after create/upload so the
519
+ * newly-created item is immediately highlighted and scrolled into view.
520
+ * Clicking any row also moves focus to that row.
521
+ */
522
+ focusedId: string | null;
523
+ }
524
+ export interface SelectionState {
525
+ selectedIds: string[];
526
+ anchorId: string | null;
527
+ }
528
+ export type UploadStatus = "pending" | "uploading" | "success" | "error" | "cancelled";
529
+ export interface UploadState {
530
+ requestId: string;
531
+ fileName: string;
532
+ fileSize: number;
533
+ parentFolderId: string | null;
534
+ /**
535
+ * The logical `folderPath` this upload targeted (`UploadFilesArg.folderPath`),
536
+ * verbatim — `null` when the caller used `parentFolderId` instead.
537
+ *
538
+ * This is the ONE correlation channel a container-scoped surface has for
539
+ * finding its own failed uploads: `state.uploads` is a flat, app-wide map
540
+ * (every uploader shares it — the Files page, chat attachments, the
541
+ * Rulebook Resources card…), so a surface that wants "MY failed uploads,
542
+ * not anyone else's" gives itself a folder path nothing else uses and reads
543
+ * back entries matching it exactly (see
544
+ * `RulebookSourcesPanel.tsx`'s `sourcesFolderPath`).
545
+ */
546
+ folderPath: string | null;
547
+ status: UploadStatus;
548
+ bytesUploaded: number;
549
+ startedAt: number;
550
+ completedAt: number | null;
551
+ error: string | null;
552
+ retries: number;
553
+ /** Populated on success; null until then. */
554
+ fileId: string | null;
555
+ }
556
+ /**
557
+ * Per-file Knowledge indexing status, hydrated lazily by the
558
+ * `prefetchRagStatusesForFiles` thunk. The thunk de-duplicates against
559
+ * `byFileId` so toggling the Knowledge column on/off doesn't re-fetch already
560
+ * known answers (use the column header's refresh action to force).
561
+ */
562
+ export interface RagStatusState {
563
+ byFileId: Record<string, RagStatus>;
564
+ /** True while a batch fetch is in flight. */
565
+ isFetching: boolean;
566
+ /** ms timestamp of the most-recent successful batch (any source). */
567
+ lastFetchedAt: number | null;
568
+ }
569
+ export interface CloudFilesState {
570
+ filesById: Record<string, CloudFileRecord>;
571
+ foldersById: Record<string, CloudFolderRecord>;
572
+ versionsByFileId: Record<string, CloudFileVersion[]>;
573
+ permissionsByResourceId: Record<string, CloudFilePermission[]>;
574
+ shareLinksByResourceId: Record<string, CloudShareLink[]>;
575
+ tree: TreeState;
576
+ selection: SelectionState;
577
+ ui: UiState;
578
+ uploads: Record<string, UploadState>;
579
+ /** Per-file Knowledge indexing status, populated on demand. */
580
+ ragStatus: RagStatusState;
581
+ /**
582
+ * Realtime attachment status. Mirrors the supabase Channel lifecycle.
583
+ */
584
+ realtime: {
585
+ status: "detached" | "connecting" | "subscribed" | "errored" | "closed";
586
+ userId: string | null;
587
+ lastEventAt: number | null;
588
+ error: string | null;
589
+ };
590
+ }
591
+ export interface CreateFolderArg {
592
+ folderName: string;
593
+ parentId: string | null;
594
+ visibility?: Visibility;
595
+ metadata?: Record<string, unknown>;
596
+ }
597
+ export interface DeleteFolderArg {
598
+ folderId: string;
599
+ }
600
+ export interface EnsureFolderPathArg {
601
+ /**
602
+ * A folder path like "Images/2026/Q1". Each segment is created if missing.
603
+ * Returns the leaf folder's id.
604
+ */
605
+ folderPath: string;
606
+ visibility?: Visibility;
607
+ }
608
+ export interface UploadFilesArg {
609
+ files: File[];
610
+ /**
611
+ * Existing folder id (rare — only when you've already created/loaded the
612
+ * folder via realtime or the tree RPC). Prefer `folderPath` for new
613
+ * uploads — the backend auto-creates folders so the browser doesn't
614
+ * need to query `cld_folders` (which can recurse on RLS until the
615
+ * SECURITY DEFINER policy fix lands; see HANDOFF.md).
616
+ */
617
+ parentFolderId?: string | null;
618
+ /**
619
+ * Logical folder path (e.g. "Images/Chat" or "Debug Uploads"). The
620
+ * Python backend creates any missing folders during upload. This is
621
+ * the recommended option for new uploads — it doesn't trigger any
622
+ * supabase-js queries on `cld_folders` from the browser.
623
+ */
624
+ folderPath?: string | null;
625
+ visibility?: Visibility;
626
+ shareWith?: string[];
627
+ shareLevel?: PermissionLevel;
628
+ changeSummary?: string;
629
+ metadata?: Record<string, unknown>;
630
+ /**
631
+ * Per-upload options forwarded to the backend `options_json` field.
632
+ * Notably `rag.trigger_now` to run Knowledge immediately on upload instead of
633
+ * waiting for the scheduled auto-Knowledge sweep. Only menu/explicit uploads set
634
+ * this — drag-drop leaves it unset (scheduled sweep still runs).
635
+ */
636
+ options?: {
637
+ rag?: {
638
+ trigger_now?: boolean;
639
+ };
640
+ };
641
+ /** Parallel upload ceiling. Defaults to 3. */
642
+ concurrency?: number;
643
+ /**
644
+ * Per-file path overrides. Keyed by index into `files` (string
645
+ * because Records use string keys). When set for a file, the
646
+ * upload uses this exact path (relative to the parent prefix)
647
+ * instead of the file's own name — i.e. it can target an EXISTING
648
+ * file's path so the backend version-bumps it.
649
+ *
650
+ * The auto " (1)" / " (2)" rename in `uploadFiles` is bypassed
651
+ * for any index that has an override, since the override is the
652
+ * user's explicit choice (typically "Overwrite" from the
653
+ * duplicate-upload dialog).
654
+ *
655
+ * Indices not present here use the default name resolution.
656
+ */
657
+ filenameOverrides?: Record<number, string>;
658
+ /**
659
+ * Indices to drop from the upload entirely. Used by the
660
+ * duplicate-upload dialog's "Skip" action — we want to keep the
661
+ * batch shape stable for telemetry, but not actually upload
662
+ * those files. Indices reference the original `files` array.
663
+ */
664
+ skipIndices?: number[];
665
+ /**
666
+ * Indices whose duplicate-dialog decision was explicitly "Make a copy".
667
+ * The upload keeps its unique display name and sends the backend's strict
668
+ * `force_new_copy` intent so checksum-identical bytes create a distinct,
669
+ * durable row linked through `duplicate_of_file_id`.
670
+ */
671
+ forceNewCopyIndices?: number[];
672
+ }
673
+ export interface RenameFileArg {
674
+ fileId: string;
675
+ newName: string;
676
+ }
677
+ export interface MoveFileArg {
678
+ fileId: string;
679
+ newParentFolderId: string | null;
680
+ }
681
+ export interface UpdateFileMetadataArg {
682
+ fileId: string;
683
+ patch: {
684
+ visibility?: Visibility;
685
+ metadata?: Record<string, unknown>;
686
+ };
687
+ }
688
+ export interface DeleteFileArg {
689
+ fileId: string;
690
+ }
691
+ /**
692
+ * Save new content as the next version of an EXISTING file (edit-in-place).
693
+ * `content` is the whole new body; the file keeps its id, name and folder.
694
+ */
695
+ export interface SaveFileNewVersionArg {
696
+ fileId: string;
697
+ content: string | Blob;
698
+ changeSummary?: string;
699
+ }
700
+ /** What a successful save wrote: the same file id at its new version. */
701
+ export interface SaveFileNewVersionResult {
702
+ fileId: string;
703
+ versionNumber: number;
704
+ }
705
+ export interface RestoreVersionArg {
706
+ fileId: string;
707
+ versionNumber: number;
708
+ }
709
+ /**
710
+ * Grant/revoke via the by-grantee REST path. `granteeType` here is a `user` or
711
+ * `group` grant only — `"public"` is not a by-grantee grant (it is toggled on
712
+ * the resource's visibility), and the grant/revoke thunks throw if it is passed
713
+ * (see `requireByGranteeType` in `redux/thunks.ts`). Defaults to `"user"`.
714
+ */
715
+ export interface GrantPermissionArg {
716
+ resourceId: string;
717
+ resourceType: ResourceType;
718
+ granteeId: string;
719
+ granteeType?: GranteeType;
720
+ level: PermissionLevel;
721
+ expiresAt?: string;
722
+ }
723
+ export interface RevokePermissionArg {
724
+ resourceId: string;
725
+ resourceType: ResourceType;
726
+ granteeId: string;
727
+ granteeType?: GranteeType;
728
+ }
729
+ export interface CreateShareLinkArg {
730
+ resourceId: string;
731
+ resourceType: ResourceType;
732
+ permissionLevel: "viewer" | "editor";
733
+ expiresAt?: string;
734
+ maxUses?: number;
735
+ }
736
+ export interface RevokeShareLinkArg {
737
+ /** `platform.share_links.id` — the canonical revoke key. */
738
+ linkId: string;
739
+ }
740
+ /**
741
+ * Request body for `POST /folders` — create a folder. The Python team
742
+ * accepts a logical path (e.g. "Images/Chat") OR an explicit name + parentId.
743
+ * Path-style is preferred because the backend creates intermediate folders
744
+ * atomically and idempotently, matching upload's auto-create semantics.
745
+ */
746
+ /**
747
+ * Body for `POST /folders`. Path-style ONLY — the backend creates any missing
748
+ * segments and REJECTS `{folder_name, parent_id}` (`validation_error`).
749
+ * DERIVED from the contract so the phantom name/parent fields can't be sent.
750
+ */
751
+ export type CreateFolderRequest = components["schemas"]["CreateFolderRequest"];
752
+ /**
753
+ * Body for `PATCH /folders/{id}`. Rename AND move are expressed as the target
754
+ * `folder_path` — the server renames, reparents, and cascades descendants from
755
+ * it. The backend SILENTLY IGNORES `folder_name`/`parent_id` (they are not on
756
+ * the model), so sending them made rename/move no-op server-side. DERIVED from
757
+ * the contract to make that mistake a compile error.
758
+ */
759
+ export type FolderPatchRequest = components["schemas"]["PatchFolderRequest"];
760
+ /** Body for `DELETE /files/bulk`. */
761
+ export interface BulkDeleteFilesRequest {
762
+ file_ids: string[];
763
+ }
764
+ /** Body for `POST /files/bulk/move`. */
765
+ export interface BulkMoveFilesRequest {
766
+ file_ids: string[];
767
+ /** Target parent folder id, or null to move to root. */
768
+ new_parent_folder_id: string | null;
769
+ }
770
+ /** Body for `POST /folders/bulk/move`. */
771
+ export interface BulkMoveFoldersRequest {
772
+ folder_ids: string[];
773
+ /** Target parent folder id, or null to move to root. */
774
+ new_parent_id: string | null;
775
+ }
776
+ /**
777
+ * Per-item outcome inside a bulk response. Matches the backend
778
+ * `BulkResultItem` shape: `{ id, ok, error }` per item plus the
779
+ * aggregate counters returned alongside.
780
+ */
781
+ export interface BulkResultItem {
782
+ id: string;
783
+ ok: boolean;
784
+ /** Error code/message string when `ok` is false; null on success. */
785
+ error: string | null;
786
+ }
787
+ /**
788
+ * Aggregate envelope returned by every bulk endpoint:
789
+ * - `DELETE /files/bulk`
790
+ * - `POST /files/bulk/move`
791
+ * - `POST /folders/bulk/move`
792
+ *
793
+ * The aggregate `succeeded` and `failed` are NUMBERS (counts), not
794
+ * arrays. Per-item detail lives in `results`.
795
+ */
796
+ export interface BulkResponse {
797
+ results: BulkResultItem[];
798
+ succeeded: number;
799
+ failed: number;
800
+ }
801
+ /**
802
+ * Response body of `GET /files/usage`. Drives the storage indicator,
803
+ * the tier badge, and feature gating in the UI. `null` on a numeric
804
+ * field means "no cap" (typically Enterprise tier).
805
+ */
806
+ export interface StorageUsageResponse {
807
+ tier_id: string;
808
+ tier_name: string;
809
+ is_blocked: boolean;
810
+ blocked_reason: string | null;
811
+ bytes_used: number;
812
+ files_count: number;
813
+ daily_upload_count: number;
814
+ daily_upload_bytes: number;
815
+ max_storage_bytes: number | null;
816
+ max_file_size_bytes: number | null;
817
+ max_files: number | null;
818
+ max_versions_per_file: number | null;
819
+ max_daily_uploads: number | null;
820
+ max_daily_upload_bytes: number | null;
821
+ max_bulk_items: number | null;
822
+ rate_limit_uploads_per_min: number | null;
823
+ rate_limit_downloads_per_min: number | null;
824
+ features: Record<string, unknown>;
825
+ /**
826
+ * TRUE when `files.user_storage_usage` actually holds a row for this user.
827
+ *
828
+ * `get_usage_status` synthesizes `{bytes_used: 0, files_count: 0, …}` when
829
+ * the ledger row is ABSENT, which on screen is indistinguishable from a
830
+ * genuinely empty account — so the UI would say "0 bytes of 5 GB" about an
831
+ * account whose usage nobody has measured. The flattener detects the
832
+ * synthesized shape (it carries no `user_id`) and says so here, and the
833
+ * meter renders "usage being recalculated" instead of a number it does not
834
+ * have. folder-sync SPEC-SERVER §8: metering only just landed on the
835
+ * standalone service, and FS-L6 still has to rebuild and re-grain the
836
+ * ledger — an unbuilt row is the expected state, not an error.
837
+ */
838
+ ledger_measured: boolean;
839
+ /** When the ledger row was last written, or null when there is no row. */
840
+ ledger_measured_at: string | null;
841
+ }
842
+ /**
843
+ * Response body of `GET /files/trash`. Soft-deleted files + folders for
844
+ * the authenticated user (or guest fingerprint).
845
+ */
846
+ export interface TrashListResponse {
847
+ files: FileRecordApi[];
848
+ folders: CloudFolderRow[];
849
+ }
850
+ /**
851
+ * Response body of `GET /files/search?q=&mime_prefix=&limit=&offset=`.
852
+ * The backend matches against filename + path substring; `mime_prefix`
853
+ * filters by `mime_type LIKE 'prefix%'`.
854
+ */
855
+ export interface SearchFilesResponse {
856
+ results: FileRecordApi[];
857
+ query: string;
858
+ total_returned: number;
859
+ }
860
+ export interface SearchFilesParams {
861
+ q: string;
862
+ mimePrefix?: string;
863
+ limit?: number;
864
+ offset?: number;
865
+ }
866
+ export interface RenameFileRequest {
867
+ /** Full new logical path including filename. Backend auto-creates parents. */
868
+ new_path: string;
869
+ }
870
+ export interface CopyFileRequest {
871
+ /** Full target logical path including filename. Backend auto-creates parents. */
872
+ target_path: string;
873
+ /** Default false. When false, conflicts return `409 file_already_exists`. */
874
+ overwrite?: boolean;
875
+ }
876
+ export interface BulkDeleteFilesArg {
877
+ fileIds: string[];
878
+ }
879
+ export interface BulkMoveFilesArg {
880
+ fileIds: string[];
881
+ newParentFolderId: string | null;
882
+ }
883
+ export interface BulkMoveFoldersArg {
884
+ folderIds: string[];
885
+ newParentId: string | null;
886
+ }
887
+ export interface UpdateFolderArg {
888
+ folderId: string;
889
+ patch: {
890
+ folderName?: string;
891
+ parentId?: string | null;
892
+ visibility?: Visibility;
893
+ metadata?: Record<string, unknown>;
894
+ };
895
+ }
896
+ export interface RenameFileToPathArg {
897
+ fileId: string;
898
+ /** Full new logical path including filename. */
899
+ newPath: string;
900
+ }
901
+ export interface CopyFileArg {
902
+ fileId: string;
903
+ /** Full target logical path including filename. */
904
+ targetPath: string;
905
+ overwrite?: boolean;
906
+ }
907
+ export interface SearchFilesArg {
908
+ q: string;
909
+ mimePrefix?: string;
910
+ limit?: number;
911
+ offset?: number;
912
+ }
913
+ export type RequestKind = "upload" | "update" | "delete" | "move" | "rename" | "restore-version" | "grant-permission" | "revoke-permission" | "create-share-link" | "revoke-share-link" | "folder-create" | "folder-update" | "folder-delete" | "bulk-delete-files" | "file-rename-path" | "file-copy" | "file-restore" | "bulk-move-files" | "bulk-move-folders";
914
+ export interface LedgerEntry {
915
+ requestId: string;
916
+ kind: RequestKind;
917
+ resourceId: string | null;
918
+ resourceType: ResourceType | null;
919
+ createdAt: number;
920
+ }
921
+ export type { BackendApiError } from "@ai-matrx/agents/matrx";
922
+ /**
923
+ * Files-specific error codes.
924
+ *
925
+ * Aligned with the Python backend's error envelope. The retry posture
926
+ * column is the FE contract — every caller must respect it:
927
+ *
928
+ * | Code | HTTP | Retry? | UX hint |
929
+ * |---------------------------|------|---------|--------------------------|
930
+ * | invalid_request | 400 | no | fix the request |
931
+ * | invalid_path | 400 | no | fix the path |
932
+ * | invalid_metadata | 400 | no | fix the patch |
933
+ * | fingerprint_required | 400 | no | guest header missing |
934
+ * | auth_required | 401 | no | sign in |
935
+ * | permission_denied | 403 | no | request access |
936
+ * | guest_id_mismatch | 403 | no | re-auth |
937
+ * | not_found | 404 | no | resource gone |
938
+ * | conflict | 409 | no | overwrite=true to force |
939
+ * | file_already_exists | 409 | no | overwrite=true to force |
940
+ * | guest_locked | 409 | no | already migrated |
941
+ * | file_too_large | 413 | no | upgrade tier |
942
+ * | storage_quota_exceeded | 413 | no | upgrade tier |
943
+ * | file_count_exceeded | 413 | no | upgrade tier |
944
+ * | daily_uploads_exceeded | 413 | no | wait until tomorrow |
945
+ * | daily_bytes_exceeded | 413 | no | wait until tomorrow |
946
+ * | bulk_too_large | 413 | no | smaller batch |
947
+ * | rate_limited | 429 | YES (b) | exponential backoff |
948
+ * | account_blocked | 423 | no | contact support |
949
+ * | share_link_invalid | 410 | no | link revoked / expired |
950
+ * | internal | 5xx | YES (b) | exponential backoff |
951
+ * | cld_sync_unavailable | 503 | YES (b) | exponential backoff |
952
+ */
953
+ export type CloudFilesErrorCode = "invalid_request" | "invalid_path" | "invalid_metadata" | "fingerprint_required" | "auth_required" | "permission_denied" | "guest_id_mismatch" | "not_found" | "conflict" | "file_already_exists" | "guest_locked" | "file_too_large" | "storage_quota_exceeded" | "file_count_exceeded" | "daily_uploads_exceeded" | "daily_bytes_exceeded" | "bulk_too_large" | "rate_limited" | "account_blocked" | "share_link_invalid" | "internal" | "cld_sync_unavailable";
954
+ export declare function isCloudTreeFileRow(row: CloudTreeRow): row is CloudTreeFileRow;
955
+ export declare function isCloudTreeFolderRow(row: CloudTreeRow): row is CloudTreeFolderRow;
956
+ /**
957
+ * Preset name accepted by `POST /assets` and `POST /assets/{id}/variants`.
958
+ * Adding a new preset on the backend is a coordinated change — extend
959
+ * this union in lockstep.
960
+ */
961
+ export type AssetPreset = "raw" | "podcast" | "social" | "web" | "email" | "logo" | "avatar" | "favicon";
962
+ /**
963
+ * One rendered variant inside an `Asset`. Each variant is a distinct
964
+ * `cld_files` row (so it shows up in the file tree and has its own
965
+ * permissions) but the `Asset` envelope groups them under a single
966
+ * `primary_key`-rooted record.
967
+ *
968
+ * URL fields (all durable — none expire):
969
+ * - `url` canonical inline-renderable URL. CDN for public,
970
+ * the durable download route for private/shared.
971
+ * **Use this** for `<img src>` / `<video src>` /
972
+ * `<audio src>`.
973
+ * - `cdn_url` permanent CDN URL when public + CDN configured;
974
+ * null otherwise.
975
+ * - `download_url` durable URL with `Content-Disposition: attachment`
976
+ * — forces a download dialog.
977
+ */
978
+ export interface AssetVariant {
979
+ key: string;
980
+ file_id: string;
981
+ file_path: string;
982
+ width: number | null;
983
+ height: number | null;
984
+ mime_type: string | null;
985
+ /**
986
+ * File size in bytes. Renamed from `file_size` in Phase 0 (see
987
+ * docs/PYTHON_UPDATES.md §3). Old servers may still send `file_size`
988
+ * during the transition — consumers should fall back to the legacy
989
+ * field name when reading older responses.
990
+ */
991
+ size_bytes: number | null;
992
+ /**
993
+ * Legacy alias for `size_bytes`. Populated by older servers during
994
+ * the Phase 0 transition window. Read `size_bytes` first; fall back
995
+ * to this only when re-hydrating older persisted responses.
996
+ *
997
+ * @deprecated Use `size_bytes`.
998
+ */
999
+ file_size?: number | null;
1000
+ url: string | null;
1001
+ cdn_url: string | null;
1002
+ download_url: string | null;
1003
+ metadata: Record<string, unknown>;
1004
+ }
1005
+ /**
1006
+ * Canonical envelope returned by every `/assets/*` and
1007
+ * `/files/{id}/asset` endpoint.
1008
+ *
1009
+ * - `file_id` master file id (the upload's "original" row).
1010
+ * - `primary_key` which variant the FE should render by default
1011
+ * (e.g. `"cover_url"` for podcast, `"original"` for raw).
1012
+ * - `primary_url` shorthand for `variants[primary_key].url`.
1013
+ * - `variants` always includes `"original"` plus zero or more
1014
+ * preset-driven derivatives.
1015
+ */
1016
+ export interface Asset {
1017
+ file_id: string;
1018
+ visibility: Visibility;
1019
+ folder: string;
1020
+ preset: string | null;
1021
+ primary_key: string;
1022
+ primary_url: string | null;
1023
+ variants: Record<string, AssetVariant>;
1024
+ metadata: Record<string, unknown>;
1025
+ }
1026
+ /**
1027
+ * Request body for `POST /assets/{id}/variants`. Sends additional
1028
+ * variants for an already-uploaded file. Idempotent on `(file_id, key)`.
1029
+ */
1030
+ export interface AddAssetVariantsRequest {
1031
+ preset?: AssetPreset;
1032
+ custom_variants?: ReadonlyArray<{
1033
+ key: string;
1034
+ suffix?: string;
1035
+ width?: number;
1036
+ height?: number;
1037
+ quality?: number;
1038
+ format?: string;
1039
+ }>;
1040
+ include_social_baseline?: boolean;
1041
+ }
1042
+ /**
1043
+ * Request body for `PATCH /assets/{id}`. Sparse — any field omitted is
1044
+ * left unchanged. `metadata` is MERGED server-side (matches the
1045
+ * `/files/{id}` PATCH semantics).
1046
+ */
1047
+ export interface AssetPatchRequest {
1048
+ visibility?: Visibility;
1049
+ /** Comma-separated user IDs OR an explicit list. */
1050
+ share_with?: string | ReadonlyArray<string>;
1051
+ share_level?: PermissionLevel;
1052
+ metadata?: Record<string, unknown>;
1053
+ }
1054
+ /**
1055
+ * One row in the `GET /assets/presets` response — describes a single
1056
+ * variant a preset will render.
1057
+ */
1058
+ export interface AssetPresetVariantDescriptor {
1059
+ key: string;
1060
+ width: number | null;
1061
+ height: number | null;
1062
+ format: string | null;
1063
+ }
1064
+ /**
1065
+ * One preset row in the `GET /assets/presets` response.
1066
+ */
1067
+ export interface AssetPresetDescriptor {
1068
+ name: AssetPreset;
1069
+ primary_key: string;
1070
+ include_baseline: boolean;
1071
+ variants: ReadonlyArray<AssetPresetVariantDescriptor>;
1072
+ }
1073
+ /**
1074
+ * Response body for `GET /assets/presets`. Drives the picker UI in
1075
+ * admin tools and lets agents enumerate every server-known preset
1076
+ * without hard-coding the list.
1077
+ */
1078
+ export interface PresetsRegistryResponse {
1079
+ presets: ReadonlyArray<AssetPresetDescriptor>;
1080
+ social_baseline: ReadonlyArray<AssetPresetVariantDescriptor>;
1081
+ }