busabase-sdk 0.9.3 → 0.9.5

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 (3) hide show
  1. package/dist/index.d.ts +12567 -5595
  2. package/dist/index.js +1046 -313
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -4,145 +4,6 @@ import { oc, eventIterator } from '@orpc/contract';
4
4
  import { z } from 'zod';
5
5
 
6
6
  // src/client.ts
7
- var AttachmentMetadataSchema = z.record(z.string(), z.unknown());
8
- z.object({
9
- id: z.string(),
10
- url: z.string(),
11
- fileName: z.string(),
12
- mimeType: z.string(),
13
- size: z.number().int().nonnegative()
14
- });
15
- var RequestUploadUrlInputSchema = z.object({
16
- fileName: z.string().min(1).max(255),
17
- mimeType: z.string().min(1),
18
- sizeBytes: z.number().int().positive(),
19
- spaceId: z.string().optional(),
20
- context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
21
- /** Content fingerprint (e.g. "sha256:<hex>") for dedup; computed client-side. */
22
- contentHash: z.string().max(80).optional()
23
- });
24
- var ConfirmUploadInputSchema = z.object({
25
- storageKey: z.string().min(1),
26
- fileName: z.string().min(1).max(255),
27
- mimeType: z.string().min(1),
28
- sizeBytes: z.number().int().positive(),
29
- spaceId: z.string().optional(),
30
- context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
31
- metadata: AttachmentMetadataSchema.optional(),
32
- /** Content fingerprint (e.g. "sha256:<hex>") persisted for dedup. */
33
- contentHash: z.string().max(80).optional()
34
- });
35
- var RequestUploadUrlVOSchema = z.object({
36
- uploadUrl: z.string(),
37
- storageKey: z.string(),
38
- publicUrl: z.string(),
39
- expiresIn: z.number(),
40
- /**
41
- * True when an identical file (same contentHash, same scope) already exists.
42
- * The client should SKIP the byte upload and the confirm step, and use
43
- * `attachmentId`/`publicUrl` directly. `uploadUrl` is empty in this case.
44
- */
45
- duplicate: z.boolean().optional(),
46
- /** Existing attachment id — present only when `duplicate` is true. */
47
- attachmentId: z.string().optional(),
48
- /** Host-specific logical asset id, when the app maps uploads into an Asset library. */
49
- assetId: z.string().optional()
50
- });
51
- var ConfirmUploadVOSchema = z.object({
52
- success: z.boolean(),
53
- attachmentId: z.string(),
54
- /** Host-specific logical asset id, when the app maps uploads into an Asset library. */
55
- assetId: z.string().optional(),
56
- storageKey: z.string(),
57
- publicUrl: z.string()
58
- });
59
- var AssetVOSchema = z.object({
60
- id: z.string(),
61
- attachmentId: z.string(),
62
- name: z.string(),
63
- contentKind: z.enum(["text", "binary"]),
64
- metadata: z.record(z.string(), z.unknown()).default({}),
65
- fileName: z.string(),
66
- mimeType: z.string(),
67
- size: z.number().int().nonnegative(),
68
- url: z.string(),
69
- contentHash: z.string().nullable(),
70
- /** How many places reference this asset (Base records + Doc bodies). */
71
- usageCount: z.number().int().nonnegative(),
72
- createdAt: z.string()
73
- });
74
- var AssetUsageVOSchema = z.object({
75
- ownerType: z.enum(["drive", "skill", "base", "doc", "file_node"]),
76
- nodeId: z.string(),
77
- nodeName: z.string(),
78
- nodeType: z.string(),
79
- /** Node slug, so the UI can link to the owning Base/Doc (`/{type}/{slug}`). */
80
- nodeSlug: z.string(),
81
- /** Drive/Skill mounted path, or null when the usage is not path-based. */
82
- path: z.string().nullable(),
83
- /** Base record id, or null for whole-node usages (e.g. a Doc body). */
84
- recordId: z.string().nullable(),
85
- /** Attachment field slug, or null for whole-node usages. */
86
- fieldSlug: z.string().nullable(),
87
- /** Doc block id, or null when the usage is not block-based. */
88
- blockId: z.string().nullable(),
89
- createdAt: z.string()
90
- });
91
- var AssetDetailVOSchema = z.object({
92
- asset: AssetVOSchema,
93
- usages: z.array(AssetUsageVOSchema)
94
- });
95
-
96
- // ../../packages/busabase-contract/src/domains/assets/contract.ts
97
- var UpdateAssetMetadataInputSchema = z.object({
98
- assetId: z.string(),
99
- metadata: z.record(z.string(), z.unknown()),
100
- mode: z.enum(["merge", "replace"]).optional().default("merge")
101
- });
102
- var assetsContract = {
103
- createUploadUrl: oc.route({
104
- method: "POST",
105
- path: "/assets/upload-urls",
106
- tags: ["Assets"],
107
- summary: "Request asset upload URL",
108
- successDescription: "Presigned (or dev) upload URL plus the public URL and asset id when identical bytes are already in the library."
109
- }).input(RequestUploadUrlInputSchema).output(RequestUploadUrlVOSchema),
110
- confirm: oc.route({
111
- method: "POST",
112
- path: "/assets/confirmations",
113
- tags: ["Assets"],
114
- summary: "Confirm asset upload",
115
- successDescription: "Recorded the file and ensured its Busabase Asset library entry."
116
- }).input(ConfirmUploadInputSchema).output(ConfirmUploadVOSchema),
117
- list: oc.route({
118
- method: "GET",
119
- path: "/assets",
120
- tags: ["Assets"],
121
- summary: "List assets",
122
- successDescription: "Every asset in the space, with file metadata and usage counts."
123
- }).output(z.array(AssetVOSchema)),
124
- get: oc.route({
125
- method: "GET",
126
- path: "/assets/{assetId}",
127
- tags: ["Assets"],
128
- summary: "Get asset detail",
129
- successDescription: "Asset metadata plus every place it is referenced (where-used)."
130
- }).input(z.object({ assetId: z.string() })).output(AssetDetailVOSchema),
131
- updateMetadata: oc.route({
132
- method: "PATCH",
133
- path: "/assets/{assetId}/metadata",
134
- tags: ["Assets"],
135
- summary: "Update asset metadata",
136
- successDescription: "Updated AI-readable metadata for a file, such as summary, extracted text, tags, source URL, or schema-specific hints."
137
- }).input(UpdateAssetMetadataInputSchema).output(AssetDetailVOSchema),
138
- delete: oc.route({
139
- method: "DELETE",
140
- path: "/assets/{assetId}",
141
- tags: ["Assets"],
142
- summary: "Delete asset",
143
- successDescription: "Removed the asset and, if no other row references its bytes, the stored object. Refused while the asset is still referenced (where-used)."
144
- }).input(z.object({ assetId: z.string() })).output(z.object({ deleted: z.boolean() }))
145
- };
146
7
  var i18n = {
147
8
  locales: ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr", "es", "pt"]};
148
9
  var LocaleSchema = z.enum(i18n.locales);
@@ -263,7 +124,12 @@ var createBaseInputSchema = z.object({
263
124
  required: z.boolean().default(false),
264
125
  options: fieldOptionsSchema.optional().default({})
265
126
  })
266
- ).default([])
127
+ ).default([]),
128
+ // Review-first by default: without `autoMerge: true`, this proposes the Base
129
+ // as a pending ChangeRequest (status "in_review") instead of creating it
130
+ // immediately. Pass `autoMerge: true` only for callers that don't need human
131
+ // review (seed/migration scripts, an explicit no-review agent task).
132
+ autoMerge: z.boolean().optional().default(false)
267
133
  });
268
134
  var createBaseFieldInputSchema = z.object({
269
135
  name: fieldNameSchema,
@@ -330,6 +196,43 @@ var restoreFieldChangeRequestInputSchema = z.object({
330
196
  submittedBy: z.string().optional().default("local-editor")
331
197
  });
332
198
 
199
+ // ../../packages/busabase-contract/src/domains/filetree/definition.ts
200
+ var fileTreeOperations = (type) => [
201
+ {
202
+ kind: `${type}_file_create`,
203
+ label: "Create file",
204
+ tone: "border-emerald-200 bg-emerald-50 text-emerald-800"
205
+ },
206
+ {
207
+ kind: `${type}_file_update`,
208
+ label: "Update file",
209
+ tone: "border-blue-200 bg-blue-50 text-blue-800"
210
+ },
211
+ {
212
+ kind: `${type}_file_delete`,
213
+ label: "Delete file",
214
+ tone: "border-rose-200 bg-rose-50 text-rose-800"
215
+ },
216
+ {
217
+ kind: `${type}_metadata_update`,
218
+ label: `Update ${type}`,
219
+ tone: "border-violet-200 bg-violet-50 text-violet-800"
220
+ }
221
+ ];
222
+ var makeFileTreeNodeType = (config) => ({
223
+ type: config.type,
224
+ label: config.label,
225
+ icon: config.icon,
226
+ capabilities: { hasDetail: true, creatable: true },
227
+ operations: fileTreeOperations(config.type)
228
+ });
229
+
230
+ // ../../packages/busabase-contract/src/domains/airapp/definition.ts
231
+ var airappNodeType = makeFileTreeNodeType({
232
+ type: "airapp",
233
+ label: "AirApp",
234
+ icon: "app-window"});
235
+
333
236
  // ../../packages/busabase-contract/src/domains/base/definition.ts
334
237
  var baseNodeType = {
335
238
  type: "base",
@@ -424,37 +327,6 @@ var docNodeType = {
424
327
  ]
425
328
  };
426
329
 
427
- // ../../packages/busabase-contract/src/domains/filetree/definition.ts
428
- var fileTreeOperations = (type) => [
429
- {
430
- kind: `${type}_file_create`,
431
- label: "Create file",
432
- tone: "border-emerald-200 bg-emerald-50 text-emerald-800"
433
- },
434
- {
435
- kind: `${type}_file_update`,
436
- label: "Update file",
437
- tone: "border-blue-200 bg-blue-50 text-blue-800"
438
- },
439
- {
440
- kind: `${type}_file_delete`,
441
- label: "Delete file",
442
- tone: "border-rose-200 bg-rose-50 text-rose-800"
443
- },
444
- {
445
- kind: `${type}_metadata_update`,
446
- label: `Update ${type}`,
447
- tone: "border-violet-200 bg-violet-50 text-violet-800"
448
- }
449
- ];
450
- var makeFileTreeNodeType = (config) => ({
451
- type: config.type,
452
- label: config.label,
453
- icon: config.icon,
454
- capabilities: { hasDetail: true, creatable: true },
455
- operations: fileTreeOperations(config.type)
456
- });
457
-
458
330
  // ../../packages/busabase-contract/src/domains/drive/definition.ts
459
331
  var driveNodeType = makeFileTreeNodeType({
460
332
  type: "drive",
@@ -510,6 +382,7 @@ var BUILTIN_NODE_TYPES = [
510
382
  baseNodeType,
511
383
  skillNodeType,
512
384
  driveNodeType,
385
+ airappNodeType,
513
386
  fileNodeType,
514
387
  docNodeType
515
388
  ];
@@ -695,7 +568,14 @@ var liveEventSchema = z.object({
695
568
  "change_request.updated",
696
569
  "change_request.deleted",
697
570
  "change_request.reviewed",
698
- "change_request.merged"
571
+ "change_request.merged",
572
+ // Fired only when a CONTENT change request freshly enters human review
573
+ // (record_* ops created via record-ops.ts) — never for structural ops
574
+ // that auto-merge instantly (those still fire "change_request.created"
575
+ // via the audit funnel, but nothing needs reviewing). Consumed by
576
+ // `use-live-sync.ts` to pop a desktop Notification, and by
577
+ // busabase-cloud's host hook to persist an inbox notification row.
578
+ "change_request.pending_review"
699
579
  ]),
700
580
  spaceId: z.string(),
701
581
  actorId: z.string(),
@@ -723,8 +603,11 @@ var auditActionSchema = z.enum([
723
603
  "file.created",
724
604
  "skill.created",
725
605
  "drive.created",
606
+ "airapp.created",
726
607
  "asset.deleted",
727
608
  "asset.metadata_updated",
609
+ "asset.text_written",
610
+ "asset.text_marked_none",
728
611
  "node.purged"
729
612
  ]);
730
613
  var auditEventSchema = z.object({
@@ -810,6 +693,13 @@ var createNodeChangeRequestInputSchema = z.object({
810
693
  ),
811
694
  operations: z.array(nodeOperationInputSchema).min(1)
812
695
  });
696
+ var moveNodeInputSchema = z.object({
697
+ nodeId: z.string(),
698
+ parentNodeId: z.string().optional().describe("New parent folder node id. Omit to keep the current parent and only reorder."),
699
+ position: z.number().int().optional().describe("New position among the target parent's children."),
700
+ message: z.string().optional().describe("Reviewer-facing Change Request message."),
701
+ submittedBy: z.string().optional()
702
+ });
813
703
  var createDeleteChangeRequestInputSchema = z.object({
814
704
  message: z.string().optional().default("Delete record").describe(
815
705
  'Explanation shown to the human reviewer. Say what is being removed and why, e.g. "Archive duplicate contact \u2014 merged into Acme Corp".'
@@ -898,6 +788,477 @@ var authInfoSchema = z.object({
898
788
  */
899
789
  spaces: z.array(authSpaceSchema)
900
790
  });
791
+
792
+ // ../../packages/busabase-contract/src/domains/filetree/contract.ts
793
+ var fileTreeFileSchema = z.object({
794
+ path: z.string(),
795
+ name: z.string(),
796
+ size: z.number(),
797
+ updatedAt: z.string().nullable(),
798
+ mimeType: z.string().nullable(),
799
+ assetId: z.string(),
800
+ displayName: z.string().nullable()
801
+ });
802
+ var assetFileInputSchema = z.object({
803
+ path: z.string().min(1),
804
+ assetId: z.string().min(1),
805
+ displayName: z.string().optional(),
806
+ mimeType: z.string().optional()
807
+ }).strict();
808
+ var textFileInputSchema = z.object({
809
+ path: z.string().min(1),
810
+ content: z.string().default(""),
811
+ mimeType: z.string().optional()
812
+ }).strict();
813
+ var assetFileOperationInputSchema = z.object({
814
+ kind: z.enum(["create", "update"]),
815
+ path: z.string().min(1),
816
+ assetId: z.string().min(1),
817
+ displayName: z.string().optional(),
818
+ mimeType: z.string().optional(),
819
+ baseContentHash: z.string().optional()
820
+ }).strict();
821
+ var textFileOperationInputSchema = z.object({
822
+ kind: z.enum(["create", "update"]),
823
+ path: z.string().min(1),
824
+ content: z.string(),
825
+ mimeType: z.string().optional(),
826
+ baseContentHash: z.string().optional()
827
+ }).strict();
828
+ var fileTreeNodeSchema = z.object({
829
+ node: nodeSchema,
830
+ entryFile: z.string(),
831
+ visibility: z.enum(["private", "workspace", "public"]),
832
+ version: z.string(),
833
+ files: z.array(fileTreeFileSchema)
834
+ });
835
+ var createFileTreeInputSchema = z.object({
836
+ parentNodeId: z.string().optional(),
837
+ slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
838
+ name: z.string().min(1),
839
+ description: z.string().optional().default(""),
840
+ visibility: z.enum(["private", "workspace", "public"]).optional().default("private"),
841
+ version: z.string().optional().default("0.1.0"),
842
+ files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([]),
843
+ // Review-first by default: without `autoMerge: true`, this proposes the node
844
+ // as a pending ChangeRequest (status "in_review") instead of creating it
845
+ // immediately. Pass `autoMerge: true` only for callers that don't need human
846
+ // review (seed/migration scripts, an explicit no-review agent task).
847
+ autoMerge: z.boolean().optional().default(false),
848
+ // "merge" (default): `files` is layered on top of the config's default seed
849
+ // files by path — a caller supplying just a couple of extra files (e.g. a
850
+ // Skill's own reference doc) still gets the default scaffold (SKILL.md,
851
+ // skill.json, ...) for any path they didn't provide themselves. "replace":
852
+ // `files` replaces the defaults entirely — for a caller handing over a
853
+ // complete, different-shaped project (e.g. an AirApp seeded with a Vite
854
+ // project instead of the default Hono template) who does NOT want leftover
855
+ // default files with unrelated content mixed in.
856
+ mergeMode: z.enum(["merge", "replace"]).optional().default("merge")
857
+ });
858
+ var fileTreeFileOperationInputSchema = z.union([
859
+ assetFileOperationInputSchema,
860
+ textFileOperationInputSchema,
861
+ z.object({
862
+ kind: z.literal("delete"),
863
+ path: z.string().min(1),
864
+ baseContentHash: z.string().optional()
865
+ }).strict(),
866
+ z.object({
867
+ kind: z.literal("metadata_update"),
868
+ metadata: z.object({
869
+ entryFile: z.string().optional(),
870
+ visibility: z.enum(["private", "workspace", "public"]).optional(),
871
+ version: z.string().optional()
872
+ }).default({})
873
+ }).strict()
874
+ ]);
875
+ var createFileTreeChangeRequestInputSchema = z.object({
876
+ message: z.string().optional().default("Update file tree").describe(
877
+ 'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Rewrite README.md quickstart for the new auth flow".'
878
+ ),
879
+ submittedBy: z.string().optional().default("local-producer"),
880
+ operations: z.array(fileTreeFileOperationInputSchema).min(1)
881
+ });
882
+ var makeFileTreeContract = (routeBase, tag) => {
883
+ const label = tag.endsWith("s") ? tag.slice(0, -1) : tag;
884
+ const basePath = `/${routeBase}`;
885
+ return {
886
+ list: oc.route({
887
+ method: "GET",
888
+ path: basePath,
889
+ tags: [tag],
890
+ summary: `List ${label} nodes`,
891
+ successDescription: `${label} nodes with their Asset-backed file trees.`
892
+ }).output(z.array(fileTreeNodeSchema)),
893
+ create: oc.route({
894
+ method: "POST",
895
+ path: basePath,
896
+ tags: [tag],
897
+ summary: `Create ${label} node`,
898
+ successDescription: `Review-first by default: a pending ChangeRequest proposing the ${label} node. Returns the materialized ${label} node instead when \`autoMerge: true\` is passed.`
899
+ }).input(createFileTreeInputSchema).output(z.union([fileTreeNodeSchema, changeRequestSchema])),
900
+ get: oc.route({
901
+ method: "GET",
902
+ path: `${basePath}/{nodeId}`,
903
+ tags: [tag],
904
+ summary: `Get ${label} node`,
905
+ successDescription: `${label} node detail and file tree.`
906
+ }).input(z.object({ nodeId: z.string() })).output(fileTreeNodeSchema),
907
+ listFiles: oc.route({
908
+ method: "GET",
909
+ path: `${basePath}/{nodeId}/files`,
910
+ tags: [tag],
911
+ summary: `List ${label} files`,
912
+ successDescription: `Asset-backed files mounted under the ${label} node.`
913
+ }).input(z.object({ nodeId: z.string() })).output(z.array(fileTreeFileSchema)),
914
+ readFile: oc.route({
915
+ method: "GET",
916
+ path: `${basePath}/{nodeId}/files/{+filePath}`,
917
+ tags: [tag],
918
+ summary: `Read ${label} file`,
919
+ successDescription: `${label} file content and content hash.`
920
+ }).input(z.object({ nodeId: z.string(), filePath: z.string() })).output(
921
+ z.object({
922
+ nodeId: z.string(),
923
+ path: z.string(),
924
+ encoding: z.enum(["utf8", "url"]),
925
+ content: z.string(),
926
+ mimeType: z.string(),
927
+ assetId: z.string(),
928
+ displayName: z.string().nullable(),
929
+ assetUrl: z.string().nullable(),
930
+ contentHash: z.string()
931
+ })
932
+ ),
933
+ createChangeRequest: oc.route({
934
+ method: "POST",
935
+ path: `${basePath}/{nodeId}/change-requests`,
936
+ tags: [tag, "Change Requests"],
937
+ summary: `Create ${label} change request`,
938
+ successDescription: `Created file-tree change request for a ${label} node.`
939
+ }).input(createFileTreeChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
940
+ };
941
+ };
942
+
943
+ // ../../packages/busabase-contract/src/domains/airapp/contract.ts
944
+ var airappContract = makeFileTreeContract("airapps", "AirApps");
945
+ var AttachmentMetadataSchema = z.record(z.string(), z.unknown());
946
+ z.object({
947
+ id: z.string(),
948
+ url: z.string(),
949
+ fileName: z.string(),
950
+ mimeType: z.string(),
951
+ size: z.number().int().nonnegative()
952
+ });
953
+ var RequestUploadUrlInputSchema = z.object({
954
+ fileName: z.string().min(1).max(255),
955
+ mimeType: z.string().min(1),
956
+ sizeBytes: z.number().int().positive(),
957
+ spaceId: z.string().optional(),
958
+ context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
959
+ /** Content fingerprint (e.g. "sha256:<hex>") for dedup; computed client-side. */
960
+ contentHash: z.string().max(80).optional()
961
+ });
962
+ var ConfirmUploadInputSchema = z.object({
963
+ storageKey: z.string().min(1),
964
+ fileName: z.string().min(1).max(255),
965
+ mimeType: z.string().min(1),
966
+ sizeBytes: z.number().int().positive(),
967
+ spaceId: z.string().optional(),
968
+ context: z.string().regex(/^[a-zA-Z0-9_/-]+$/).max(100).optional(),
969
+ metadata: AttachmentMetadataSchema.optional(),
970
+ /** Content fingerprint (e.g. "sha256:<hex>") persisted for dedup. */
971
+ contentHash: z.string().max(80).optional()
972
+ });
973
+ var RequestUploadUrlVOSchema = z.object({
974
+ uploadUrl: z.string(),
975
+ storageKey: z.string(),
976
+ publicUrl: z.string(),
977
+ expiresIn: z.number(),
978
+ /**
979
+ * True when an identical file (same contentHash, same scope) already exists.
980
+ * The client should SKIP the byte upload and the confirm step, and use
981
+ * `attachmentId`/`publicUrl` directly. `uploadUrl` is empty in this case.
982
+ */
983
+ duplicate: z.boolean().optional(),
984
+ /** Existing attachment id — present only when `duplicate` is true. */
985
+ attachmentId: z.string().optional(),
986
+ /** Host-specific logical asset id, when the app maps uploads into an Asset library. */
987
+ assetId: z.string().optional()
988
+ });
989
+ var ConfirmUploadVOSchema = z.object({
990
+ success: z.boolean(),
991
+ attachmentId: z.string(),
992
+ /** Host-specific logical asset id, when the app maps uploads into an Asset library. */
993
+ assetId: z.string().optional(),
994
+ storageKey: z.string(),
995
+ publicUrl: z.string()
996
+ });
997
+ var AssetTextStatusSchema = z.enum(["missing", "present", "none", "stale"]);
998
+ var AssetVOSchema = z.object({
999
+ id: z.string(),
1000
+ attachmentId: z.string(),
1001
+ name: z.string(),
1002
+ contentKind: z.enum(["text", "binary"]),
1003
+ metadata: z.record(z.string(), z.unknown()).default({}),
1004
+ fileName: z.string(),
1005
+ mimeType: z.string(),
1006
+ size: z.number().int().nonnegative(),
1007
+ url: z.string(),
1008
+ contentHash: z.string().nullable(),
1009
+ /** How many places reference this asset (Base records + Doc bodies). */
1010
+ usageCount: z.number().int().nonnegative(),
1011
+ /** Drive Grep Retrieval text-slot status — see {@link AssetTextStatusSchema}. */
1012
+ textStatus: AssetTextStatusSchema,
1013
+ createdAt: z.string()
1014
+ });
1015
+ var AssetUsageVOSchema = z.object({
1016
+ ownerType: z.enum(["drive", "skill", "airapp", "base", "doc", "file_node"]),
1017
+ nodeId: z.string(),
1018
+ nodeName: z.string(),
1019
+ nodeType: z.string(),
1020
+ /** Node slug, so the UI can link to the owning Base/Doc (`/{type}/{slug}`). */
1021
+ nodeSlug: z.string(),
1022
+ /** Drive/Skill mounted path, or null when the usage is not path-based. */
1023
+ path: z.string().nullable(),
1024
+ /** Base record id, or null for whole-node usages (e.g. a Doc body). */
1025
+ recordId: z.string().nullable(),
1026
+ /** Attachment field slug, or null for whole-node usages. */
1027
+ fieldSlug: z.string().nullable(),
1028
+ /** Doc block id, or null when the usage is not block-based. */
1029
+ blockId: z.string().nullable(),
1030
+ createdAt: z.string()
1031
+ });
1032
+ var AssetDetailVOSchema = z.object({
1033
+ asset: AssetVOSchema,
1034
+ usages: z.array(AssetUsageVOSchema)
1035
+ });
1036
+ var PutTextInputSchema = z.object({
1037
+ assetId: z.string(),
1038
+ /** Inline text body, ≤ 1 MB. For larger text, use `createTextUploadUrl` + bind by `storageKey`. */
1039
+ text: z.string().optional(),
1040
+ /** Bind a presigned-uploaded text object (a temp `asset-texts/pending/*.txt` key). */
1041
+ storageKey: z.string().optional(),
1042
+ /**
1043
+ * Claimed content hash for the `storageKey` bind path (`sha256:<hex>`, echoing
1044
+ * `createTextUploadUrl`'s input like `open-domains/attachments`' confirm step).
1045
+ * The server always computes the ACTUAL hash from the bytes during the
1046
+ * confirm scan and rejects a mismatch (hash-poisoning defense) — this field
1047
+ * is only an optional early-mismatch check, never trusted for addressing.
1048
+ */
1049
+ contentHash: z.string().optional(),
1050
+ /** Mark as having no extractable text (e.g. a scanned, image-only PDF). */
1051
+ none: z.boolean().optional()
1052
+ });
1053
+ var AssetTextVOSchema = z.object({
1054
+ assetId: z.string(),
1055
+ textStatus: AssetTextStatusSchema,
1056
+ lineCount: z.number().int().nonnegative(),
1057
+ charCount: z.number().int().nonnegative(),
1058
+ byteCount: z.number().int().nonnegative()
1059
+ });
1060
+ var CreateTextUploadUrlInputSchema = z.object({
1061
+ assetId: z.string(),
1062
+ sizeBytes: z.number().int().positive(),
1063
+ /** Optional claim, mirrors `RequestUploadUrlDTO.contentHash` (never trusted for addressing). */
1064
+ contentHash: z.string().optional()
1065
+ });
1066
+ var CreateTextUploadUrlVOSchema = z.object({
1067
+ uploadUrl: z.string(),
1068
+ storageKey: z.string(),
1069
+ expiresIn: z.number().int().nonnegative()
1070
+ });
1071
+ var GrepScopeSchema = z.object({
1072
+ assetIds: z.array(z.string()).optional(),
1073
+ /** Drive/Skill mounted path prefix (matches `busabase_asset_usages.path`). */
1074
+ drivePath: z.string().optional(),
1075
+ mimeTypes: z.array(z.string()).optional()
1076
+ });
1077
+ var GREP_DEFAULT_MAX_MATCHES = 100;
1078
+ var GREP_HARD_MAX_MATCHES = 1e3;
1079
+ var GREP_DEFAULT_CONTEXT_LINES = 0;
1080
+ var GREP_MAX_CONTEXT_LINES = 10;
1081
+ var GrepInputSchema = z.object({
1082
+ pattern: z.string().min(1),
1083
+ /** JS RegExp flags, e.g. `"i"` for case-insensitive. `g`/`y` are ignored (grep always scans every match per line). */
1084
+ flags: z.string().optional().default(""),
1085
+ scope: GrepScopeSchema.optional(),
1086
+ maxMatches: z.coerce.number().int().min(1).max(GREP_HARD_MAX_MATCHES).optional().default(GREP_DEFAULT_MAX_MATCHES),
1087
+ contextLines: z.coerce.number().int().min(0).max(GREP_MAX_CONTEXT_LINES).optional().default(GREP_DEFAULT_CONTEXT_LINES)
1088
+ });
1089
+ var GrepMatchVOSchema = z.object({
1090
+ assetId: z.string(),
1091
+ fileName: z.string(),
1092
+ /** Drive/Skill mounted path, or "" when the asset isn't path-mounted (e.g. a File node). */
1093
+ drivePath: z.string(),
1094
+ line: z.number().int().positive(),
1095
+ /** 1-based character column (not byte offset) of the match start within the line. */
1096
+ column: z.number().int().positive(),
1097
+ /** The matching line, truncated if it exceeds the long-line guard. */
1098
+ text: z.string(),
1099
+ before: z.array(z.string()),
1100
+ after: z.array(z.string())
1101
+ });
1102
+ var GrepResultVOSchema = z.object({
1103
+ matches: z.array(GrepMatchVOSchema),
1104
+ filesScanned: z.number().int().nonnegative(),
1105
+ /** Asset ids in scope with no text yet (contentKind text-or-writable-binary, no row). */
1106
+ missing: z.array(z.string()),
1107
+ /** Asset ids in scope whose derived text is stale (source replaced since it was written). */
1108
+ stale: z.array(z.string()),
1109
+ /** Count of assets in scope explicitly marked `none` (no extractable text). */
1110
+ unsearchable: z.number().int().nonnegative(),
1111
+ /**
1112
+ * Asset ids whose scan was attempted but failed (storage error, corrupt
1113
+ * cache file, object deleted mid-flight) — NOT counted in `filesScanned`.
1114
+ * Honest coverage: these were not actually searched, so a caller must not
1115
+ * treat their absence from `matches` as a clean "no match".
1116
+ */
1117
+ errored: z.array(z.string()),
1118
+ /**
1119
+ * Count of in-scope, present-and-searchable assets the scan never even
1120
+ * reached because the deadline or `maxMatches` budget ran out first. Only
1121
+ * nonzero when `truncated` is true.
1122
+ */
1123
+ notReached: z.number().int().nonnegative(),
1124
+ truncated: z.boolean()
1125
+ });
1126
+ var ReadTextLinesInputSchema = z.object({
1127
+ assetId: z.string(),
1128
+ startLine: z.coerce.number().int().min(1),
1129
+ endLine: z.coerce.number().int().min(1)
1130
+ });
1131
+ var ReadLinesVOSchema = z.object({
1132
+ lines: z.array(z.string()),
1133
+ startLine: z.number().int().positive(),
1134
+ endLine: z.number().int().positive(),
1135
+ totalLines: z.number().int().nonnegative(),
1136
+ truncated: z.boolean()
1137
+ });
1138
+ var AssetContentEditSchema = z.object({
1139
+ oldString: z.string().min(1),
1140
+ newString: z.string(),
1141
+ /** Replace every occurrence instead of requiring a single unique match. */
1142
+ replaceAll: z.boolean().optional().default(false)
1143
+ }).strict();
1144
+ var EditAssetContentInputSchema = z.object({
1145
+ assetId: z.string(),
1146
+ edits: z.array(AssetContentEditSchema).min(1),
1147
+ message: z.string().optional().default("Edit file content").describe(
1148
+ 'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Fix typo in setup instructions".'
1149
+ ),
1150
+ submittedBy: z.string().optional().default("agent")
1151
+ });
1152
+ var AssetDownloadInputSchema = z.object({ assetId: z.string() });
1153
+ var AssetDownloadVOSchema = z.object({
1154
+ assetId: z.string(),
1155
+ downloadUrl: z.string(),
1156
+ fileName: z.string(),
1157
+ mimeType: z.string(),
1158
+ size: z.number().int().nonnegative(),
1159
+ contentHash: z.string().nullable()
1160
+ });
1161
+
1162
+ // ../../packages/busabase-contract/src/domains/assets/contract.ts
1163
+ var UpdateAssetMetadataInputSchema = z.object({
1164
+ assetId: z.string(),
1165
+ metadata: z.record(z.string(), z.unknown()),
1166
+ mode: z.enum(["merge", "replace"]).optional().default("merge")
1167
+ });
1168
+ var assetsContract = {
1169
+ createUploadUrl: oc.route({
1170
+ method: "POST",
1171
+ path: "/assets/upload-urls",
1172
+ tags: ["Assets"],
1173
+ summary: "Request asset upload URL",
1174
+ successDescription: "Presigned (or dev) upload URL plus the public URL and asset id when identical bytes are already in the library."
1175
+ }).input(RequestUploadUrlInputSchema).output(RequestUploadUrlVOSchema),
1176
+ confirm: oc.route({
1177
+ method: "POST",
1178
+ path: "/assets/confirmations",
1179
+ tags: ["Assets"],
1180
+ summary: "Confirm asset upload",
1181
+ successDescription: "Recorded the file and ensured its Busabase Asset library entry."
1182
+ }).input(ConfirmUploadInputSchema).output(ConfirmUploadVOSchema),
1183
+ list: oc.route({
1184
+ method: "GET",
1185
+ path: "/assets",
1186
+ tags: ["Assets"],
1187
+ summary: "List assets",
1188
+ successDescription: "Every asset in the space, with file metadata and usage counts."
1189
+ }).output(z.array(AssetVOSchema)),
1190
+ get: oc.route({
1191
+ method: "GET",
1192
+ path: "/assets/{assetId}",
1193
+ tags: ["Assets"],
1194
+ summary: "Get asset detail",
1195
+ successDescription: "Asset metadata plus every place it is referenced (where-used)."
1196
+ }).input(z.object({ assetId: z.string() })).output(AssetDetailVOSchema),
1197
+ updateMetadata: oc.route({
1198
+ method: "PATCH",
1199
+ path: "/assets/{assetId}/metadata",
1200
+ tags: ["Assets"],
1201
+ summary: "Update asset metadata",
1202
+ successDescription: "Updated AI-readable metadata for a file, such as summary, tags, source URL, or schema-specific hints. Large text does not live here \u2014 see putText / grep / readTextLines."
1203
+ }).input(UpdateAssetMetadataInputSchema).output(AssetDetailVOSchema),
1204
+ delete: oc.route({
1205
+ method: "DELETE",
1206
+ path: "/assets/{assetId}",
1207
+ tags: ["Assets"],
1208
+ summary: "Delete asset",
1209
+ successDescription: "Removed the asset and, if no other row references its bytes, the stored object. Refused while the asset is still referenced (where-used)."
1210
+ }).input(z.object({ assetId: z.string() })).output(z.object({ deleted: z.boolean() })),
1211
+ download: oc.route({
1212
+ method: "GET",
1213
+ path: "/assets/{assetId}/content",
1214
+ tags: ["Assets"],
1215
+ summary: "Get an asset's binary content download URL",
1216
+ successDescription: "A resolved, time-bounded download URL for the asset's raw bytes (local dev: the existing static /uploads route; cloud/S3: a presigned URL) plus its file metadata \u2014 see AssetDownloadVOSchema for why this returns a URL, not a raw-binary oRPC response."
1217
+ }).input(AssetDownloadInputSchema).output(AssetDownloadVOSchema),
1218
+ // ── Drive Grep Retrieval ─────────────────────────────────────────────────
1219
+ // Busabase stores, indexes, and searches text; it never generates it. Text
1220
+ // always arrives via putText — an agent's own extractor, or (future) an
1221
+ // Outgoing-Hook-triggered service — never a bundled parser/OCR library.
1222
+ putText: oc.route({
1223
+ method: "PUT",
1224
+ path: "/assets/{assetId}/text",
1225
+ tags: ["Assets"],
1226
+ summary: "Write (or mark none) an asset's text slot",
1227
+ successDescription: "Text slot updated: inline body (\u22641MB), or bound from a presigned upload (server-verified content hash, hash-poisoning-safe), or marked `none` for files with no extractable text. Direct write, audit-logged, not ChangeRequest-gated."
1228
+ }).input(PutTextInputSchema).output(AssetTextVOSchema),
1229
+ createTextUploadUrl: oc.route({
1230
+ method: "POST",
1231
+ path: "/assets/text/upload-urls",
1232
+ tags: ["Assets"],
1233
+ summary: "Request a presigned upload URL for large text",
1234
+ successDescription: "Presigned (or dev) upload URL for a temporary text object; PUT the bytes there, then call putText with the returned storageKey to bind, verify, and content-address it."
1235
+ }).input(CreateTextUploadUrlInputSchema).output(CreateTextUploadUrlVOSchema),
1236
+ grep: oc.route({
1237
+ method: "POST",
1238
+ path: "/assets/grep",
1239
+ tags: ["Assets"],
1240
+ summary: "Search every text-bearing asset in scope",
1241
+ successDescription: "Streaming regex/literal matches with real file + line + column numbers and context, across every asset with text \u2014 any size, no 256KB cap. Honest coverage: missing/stale/unsearchable/errored name assets that were not (fully or successfully) searched, notReached counts present assets the scan never got to, and truncated flags a capped response."
1242
+ }).input(GrepInputSchema).output(GrepResultVOSchema),
1243
+ readTextLines: oc.route({
1244
+ method: "GET",
1245
+ path: "/assets/{assetId}/text/lines",
1246
+ tags: ["Assets"],
1247
+ summary: "Read an exact line range from an asset's text",
1248
+ successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) read via a storage byte-range request \u2014 the server never loads the whole object, even for a multi-GB file."
1249
+ }).input(ReadTextLinesInputSchema).output(ReadLinesVOSchema),
1250
+ // ── editContent — edit an asset's REAL file content via ChangeRequest ──────
1251
+ // Unlike putText (derived/extracted text, direct write, disposable), this
1252
+ // edits the asset's actual mounted Drive/Skill file bytes and always goes
1253
+ // through the existing filetree ChangeRequest pipeline — never auto-merged.
1254
+ editContent: oc.route({
1255
+ method: "POST",
1256
+ path: "/assets/{assetId}/edit-content",
1257
+ tags: ["Assets", "Change Requests"],
1258
+ summary: "Edit an asset's file content via string-replace edits, as a ChangeRequest",
1259
+ successDescription: `Applied the string-replace edits (coding-agent Edit-tool semantics: unique-match or replaceAll) to the asset's current mounted Drive/Skill file content and proposed the result as a ChangeRequest (status "in_review") for human review. Reuses the existing filetree update-via-CR pipeline end to end, including baseContentHash optimistic-concurrency conflict protection at merge time. Requires the asset to be mounted in exactly one editable Drive/Skill location.`
1260
+ }).input(EditAssetContentInputSchema).output(changeRequestSchema)
1261
+ };
901
1262
  var viewFilterOperatorSchema = z.enum([
902
1263
  "contains",
903
1264
  "equals",
@@ -1003,6 +1364,12 @@ var listRecordsResponseSchema = z.object({
1003
1364
  records: z.array(recordSchema),
1004
1365
  nextCursor: z.string().nullable()
1005
1366
  });
1367
+ var listArchivedRecordsPagedInputSchema = z.object({
1368
+ baseId: z.string(),
1369
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50),
1370
+ /** Opaque base64 cursor (createdAt-keyed) for keyset pagination. */
1371
+ cursor: z.string().optional()
1372
+ });
1006
1373
  var countRecordsInputSchema = z.object({
1007
1374
  baseId: z.string().optional()
1008
1375
  }).optional().default({});
@@ -1101,13 +1468,20 @@ var baseContract = {
1101
1468
  summary: "List archived records for a Base",
1102
1469
  successDescription: "Records that have been archived (soft-deleted) from a Base."
1103
1470
  }).input(z.object({ baseId: z.string() })).output(z.array(recordSchema)),
1471
+ listArchivedRecordsPaged: oc.route({
1472
+ method: "GET",
1473
+ path: "/bases/{baseId}/records/archived/paged",
1474
+ tags: ["Records"],
1475
+ summary: "List archived records for a Base with keyset pagination",
1476
+ successDescription: "A page of archived (soft-deleted) records plus an opaque nextCursor (null at the end)."
1477
+ }).input(listArchivedRecordsPagedInputSchema).output(listRecordsResponseSchema),
1104
1478
  create: oc.route({
1105
1479
  method: "POST",
1106
1480
  path: "/bases",
1107
1481
  tags: ["Bases"],
1108
1482
  summary: "Create Base",
1109
- successDescription: "Created Base."
1110
- }).input(createBaseInputSchema).output(baseSchema),
1483
+ successDescription: "Review-first by default: a pending ChangeRequest proposing the Base. Returns the materialized Base instead when `autoMerge: true` is passed."
1484
+ }).input(createBaseInputSchema).output(z.union([baseSchema, changeRequestSchema])),
1111
1485
  createChangeRequest: oc.route({
1112
1486
  method: "POST",
1113
1487
  path: "/bases/{baseId}/change-requests",
@@ -1295,6 +1669,13 @@ var viewContract = {
1295
1669
  successDescription: "Created change request that restores an archived View."
1296
1670
  }).input(restoreViewInputSchema.extend({ viewId: z.string() })).output(changeRequestSchema)
1297
1671
  };
1672
+ var ReadDocLinesInputSchema = z.object({
1673
+ nodeId: z.string(),
1674
+ startLine: z.coerce.number().int().min(1),
1675
+ endLine: z.coerce.number().int().min(1)
1676
+ });
1677
+
1678
+ // ../../packages/busabase-contract/src/domains/doc/contract.ts
1298
1679
  var docSchema = z.object({
1299
1680
  node: nodeSchema,
1300
1681
  storagePrefix: z.string(),
@@ -1305,7 +1686,12 @@ var createDocInputSchema = z.object({
1305
1686
  slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
1306
1687
  name: z.string().min(1),
1307
1688
  description: z.string().optional().default(""),
1308
- body: z.string().optional().default("")
1689
+ body: z.string().optional().default(""),
1690
+ // Review-first by default: without `autoMerge: true`, this proposes the Doc
1691
+ // as a pending ChangeRequest (status "in_review") instead of creating it
1692
+ // immediately. Pass `autoMerge: true` only for callers that don't need human
1693
+ // review (seed/migration scripts, an explicit no-review agent task).
1694
+ autoMerge: z.boolean().optional().default(false)
1309
1695
  });
1310
1696
  var updateDocInputSchema = z.object({
1311
1697
  body: z.string()
@@ -1330,8 +1716,8 @@ var docContract = {
1330
1716
  path: "/docs",
1331
1717
  tags: ["Docs"],
1332
1718
  summary: "Create Doc node",
1333
- successDescription: "Created Doc node and initialized its body."
1334
- }).input(createDocInputSchema).output(docSchema),
1719
+ successDescription: "Review-first by default: a pending ChangeRequest proposing the Doc. Returns the materialized Doc node instead when `autoMerge: true` is passed."
1720
+ }).input(createDocInputSchema).output(z.union([docSchema, changeRequestSchema])),
1335
1721
  get: oc.route({
1336
1722
  method: "GET",
1337
1723
  path: "/docs/{nodeId}",
@@ -1339,6 +1725,13 @@ var docContract = {
1339
1725
  summary: "Get Doc node",
1340
1726
  successDescription: "Doc node detail and body."
1341
1727
  }).input(z.object({ nodeId: z.string() })).output(docSchema),
1728
+ readLines: oc.route({
1729
+ method: "GET",
1730
+ path: "/docs/{nodeId}/lines",
1731
+ tags: ["Docs"],
1732
+ summary: "Read an exact line range from a Doc body",
1733
+ successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) sliced from the Doc's full body \u2014 Docs are KB-scale, so the whole body is read in memory; no byte-range/checkpoint machinery like assets.readTextLines uses for potentially multi-GB files. The Doc-domain follow-up to a Unified Grep match with `source: \"docs\"`, so an agent can read just the lines around a match instead of `get`'s entire body."
1734
+ }).input(ReadDocLinesInputSchema).output(ReadLinesVOSchema),
1342
1735
  updateBody: oc.route({
1343
1736
  method: "PUT",
1344
1737
  path: "/docs/{nodeId}/body",
@@ -1354,144 +1747,100 @@ var docContract = {
1354
1747
  successDescription: "Created a change request that proposes a new Doc body."
1355
1748
  }).input(createDocChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
1356
1749
  };
1357
- var fileTreeFileSchema = z.object({
1358
- path: z.string(),
1359
- name: z.string(),
1360
- size: z.number(),
1361
- updatedAt: z.string().nullable(),
1362
- mimeType: z.string().nullable(),
1363
- assetId: z.string(),
1364
- displayName: z.string().nullable()
1750
+
1751
+ // ../../packages/busabase-contract/src/domains/drive/contract.ts
1752
+ var driveContract = makeFileTreeContract("drives", "Drives");
1753
+ var DumpTableSchema = z.enum([
1754
+ "nodes",
1755
+ "bases",
1756
+ "baseFields",
1757
+ "views",
1758
+ "records",
1759
+ "fieldValues",
1760
+ "recordLinks",
1761
+ /** The physical bytes registry (open-domains/attachments) an Asset's `attachmentId` FKs into. */
1762
+ "attachments",
1763
+ "assets",
1764
+ "assetUsages",
1765
+ "assetTexts",
1766
+ "commits",
1767
+ "changeRequests",
1768
+ "operations",
1769
+ "comments",
1770
+ "reviews",
1771
+ "auditEvents"
1772
+ ]);
1773
+ var ExportTablesInputSchema = z.object({
1774
+ table: DumpTableSchema,
1775
+ /** Opaque pagination cursor from a previous page's `nextCursor`; omit for the first page. */
1776
+ cursor: z.string().optional(),
1777
+ /** Page size, default 500, capped at 2000. */
1778
+ limit: z.number().int().positive().max(2e3).optional().default(500)
1365
1779
  });
1366
- var assetFileInputSchema = z.object({
1367
- path: z.string().min(1),
1368
- assetId: z.string().min(1),
1369
- displayName: z.string().optional(),
1370
- mimeType: z.string().optional()
1371
- }).strict();
1372
- var textFileInputSchema = z.object({
1373
- path: z.string().min(1),
1374
- content: z.string().default(""),
1375
- mimeType: z.string().optional()
1376
- }).strict();
1377
- var assetFileOperationInputSchema = z.object({
1378
- kind: z.enum(["create", "update"]),
1379
- path: z.string().min(1),
1380
- assetId: z.string().min(1),
1381
- displayName: z.string().optional(),
1382
- mimeType: z.string().optional(),
1383
- baseContentHash: z.string().optional()
1384
- }).strict();
1385
- var textFileOperationInputSchema = z.object({
1386
- kind: z.enum(["create", "update"]),
1387
- path: z.string().min(1),
1388
- content: z.string(),
1389
- mimeType: z.string().optional(),
1390
- baseContentHash: z.string().optional()
1391
- }).strict();
1392
- var fileTreeNodeSchema = z.object({
1393
- node: nodeSchema,
1394
- entryFile: z.string(),
1395
- visibility: z.enum(["private", "workspace", "public"]),
1396
- version: z.string(),
1397
- files: z.array(fileTreeFileSchema)
1780
+ var ExportTablesVOSchema = z.object({
1781
+ rows: z.array(z.record(z.string(), z.unknown())),
1782
+ nextCursor: z.string().nullable()
1398
1783
  });
1399
- var createFileTreeInputSchema = z.object({
1400
- parentNodeId: z.string().optional(),
1401
- slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
1402
- name: z.string().min(1),
1403
- description: z.string().optional().default(""),
1404
- visibility: z.enum(["private", "workspace", "public"]).optional().default("private"),
1405
- version: z.string().optional().default("0.1.0"),
1406
- files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([])
1784
+ var ImportBeginVOSchema = z.object({
1785
+ sessionId: z.string()
1407
1786
  });
1408
- var fileTreeFileOperationInputSchema = z.union([
1409
- assetFileOperationInputSchema,
1410
- textFileOperationInputSchema,
1411
- z.object({
1412
- kind: z.literal("delete"),
1413
- path: z.string().min(1),
1414
- baseContentHash: z.string().optional()
1415
- }).strict(),
1416
- z.object({
1417
- kind: z.literal("metadata_update"),
1418
- metadata: z.object({
1419
- entryFile: z.string().optional(),
1420
- visibility: z.enum(["private", "workspace", "public"]).optional(),
1421
- version: z.string().optional()
1422
- }).default({})
1423
- }).strict()
1424
- ]);
1425
- var createFileTreeChangeRequestInputSchema = z.object({
1426
- message: z.string().optional().default("Update file tree").describe(
1427
- 'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Rewrite README.md quickstart for the new auth flow".'
1428
- ),
1429
- submittedBy: z.string().optional().default("local-producer"),
1430
- operations: z.array(fileTreeFileOperationInputSchema).min(1)
1787
+ var ImportTablesInputSchema = z.object({
1788
+ sessionId: z.string(),
1789
+ table: z.union([DumpTableSchema, z.literal("docBodies"), z.literal("attachmentBlobs")]),
1790
+ rows: z.array(z.record(z.string(), z.unknown()))
1791
+ });
1792
+ var ImportTablesVOSchema = z.object({
1793
+ inserted: z.number().int().nonnegative()
1794
+ });
1795
+ var ImportSessionInputSchema = z.object({
1796
+ sessionId: z.string()
1797
+ });
1798
+ var ImportCommitVOSchema = z.object({
1799
+ ok: z.boolean(),
1800
+ warnings: z.array(z.string())
1801
+ });
1802
+ var ImportAbortVOSchema = z.object({
1803
+ ok: z.boolean()
1431
1804
  });
1432
- var makeFileTreeContract = (routeBase, tag) => {
1433
- const label = tag.endsWith("s") ? tag.slice(0, -1) : tag;
1434
- const basePath = `/${routeBase}`;
1435
- return {
1436
- list: oc.route({
1437
- method: "GET",
1438
- path: basePath,
1439
- tags: [tag],
1440
- summary: `List ${label} nodes`,
1441
- successDescription: `${label} nodes with their Asset-backed file trees.`
1442
- }).output(z.array(fileTreeNodeSchema)),
1443
- create: oc.route({
1444
- method: "POST",
1445
- path: basePath,
1446
- tags: [tag],
1447
- summary: `Create ${label} node`,
1448
- successDescription: `Created ${label} node and initialized file tree.`
1449
- }).input(createFileTreeInputSchema).output(fileTreeNodeSchema),
1450
- get: oc.route({
1451
- method: "GET",
1452
- path: `${basePath}/{nodeId}`,
1453
- tags: [tag],
1454
- summary: `Get ${label} node`,
1455
- successDescription: `${label} node detail and file tree.`
1456
- }).input(z.object({ nodeId: z.string() })).output(fileTreeNodeSchema),
1457
- listFiles: oc.route({
1458
- method: "GET",
1459
- path: `${basePath}/{nodeId}/files`,
1460
- tags: [tag],
1461
- summary: `List ${label} files`,
1462
- successDescription: `Asset-backed files mounted under the ${label} node.`
1463
- }).input(z.object({ nodeId: z.string() })).output(z.array(fileTreeFileSchema)),
1464
- readFile: oc.route({
1465
- method: "GET",
1466
- path: `${basePath}/{nodeId}/files/{+filePath}`,
1467
- tags: [tag],
1468
- summary: `Read ${label} file`,
1469
- successDescription: `${label} file content and content hash.`
1470
- }).input(z.object({ nodeId: z.string(), filePath: z.string() })).output(
1471
- z.object({
1472
- nodeId: z.string(),
1473
- path: z.string(),
1474
- encoding: z.enum(["utf8", "url"]),
1475
- content: z.string(),
1476
- mimeType: z.string(),
1477
- assetId: z.string(),
1478
- displayName: z.string().nullable(),
1479
- assetUrl: z.string().nullable(),
1480
- contentHash: z.string()
1481
- })
1482
- ),
1483
- createChangeRequest: oc.route({
1484
- method: "POST",
1485
- path: `${basePath}/{nodeId}/change-requests`,
1486
- tags: [tag, "Change Requests"],
1487
- summary: `Create ${label} change request`,
1488
- successDescription: `Created file-tree change request for a ${label} node.`
1489
- }).input(createFileTreeChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
1490
- };
1491
- };
1492
1805
 
1493
- // ../../packages/busabase-contract/src/domains/drive/contract.ts
1494
- var driveContract = makeFileTreeContract("drives", "Drives");
1806
+ // ../../packages/busabase-contract/src/domains/dump/contract.ts
1807
+ var dumpContract = {
1808
+ exportTables: oc.route({
1809
+ method: "POST",
1810
+ path: "/dump/export/tables",
1811
+ tags: ["Dump"],
1812
+ summary: "Export raw rows for one table (cursor-paginated)",
1813
+ successDescription: "A page of raw rows for the requested table (id-ordered), plus an opaque nextCursor (null at the end). Vault items and webhook secrets are never exportable through this endpoint."
1814
+ }).input(ExportTablesInputSchema).output(ExportTablesVOSchema),
1815
+ importBegin: oc.route({
1816
+ method: "POST",
1817
+ path: "/dump/import/begin",
1818
+ tags: ["Dump"],
1819
+ summary: "Begin a full-fidelity import session",
1820
+ successDescription: "Created an import session for the current space. Refused unless the space's node tree is empty (full-fidelity import preserves original ids and cannot merge into existing data)."
1821
+ }).output(ImportBeginVOSchema),
1822
+ importTables: oc.route({
1823
+ method: "POST",
1824
+ path: "/dump/import/tables",
1825
+ tags: ["Dump"],
1826
+ summary: "Import a batch of raw rows into an open session",
1827
+ successDescription: "Inserted rows preserving their original ids. `docBodies` is a pseudo-table ({nodeId, markdown}[]) written directly to object storage (doc bodies are not a DB row)."
1828
+ }).input(ImportTablesInputSchema).output(ImportTablesVOSchema),
1829
+ importCommit: oc.route({
1830
+ method: "POST",
1831
+ path: "/dump/import/commit",
1832
+ tags: ["Dump"],
1833
+ summary: "Finalize an import session",
1834
+ successDescription: "Ran integrity checks (FK orphans, missing blobs) and closed the session. `ok:false` or a non-empty `warnings` array means the import completed but may be incomplete \u2014 inspect before relying on the space."
1835
+ }).input(ImportSessionInputSchema).output(ImportCommitVOSchema),
1836
+ importAbort: oc.route({
1837
+ method: "POST",
1838
+ path: "/dump/import/abort",
1839
+ tags: ["Dump"],
1840
+ summary: "Abort an import session",
1841
+ successDescription: "Best-effort cleanup of everything written so far in this session. Only ever touches the target space validated empty at `importBegin` time."
1842
+ }).input(ImportSessionInputSchema).output(ImportAbortVOSchema)
1843
+ };
1495
1844
  z.object({
1496
1845
  assetId: z.string()
1497
1846
  });
@@ -1506,7 +1855,12 @@ var createFileNodeInputSchema = z.object({
1506
1855
  slug: z.string().min(1).regex(/^[a-z0-9-]+$/),
1507
1856
  name: z.string().min(1),
1508
1857
  description: z.string().optional().default(""),
1509
- assetId: z.string().min(1)
1858
+ assetId: z.string().min(1),
1859
+ // Review-first by default: without `autoMerge: true`, this proposes the File
1860
+ // node as a pending ChangeRequest (status "in_review") instead of creating it
1861
+ // immediately. Pass `autoMerge: true` only for callers that don't need human
1862
+ // review (seed/migration scripts, an explicit no-review agent task).
1863
+ autoMerge: z.boolean().optional().default(false)
1510
1864
  });
1511
1865
  var fileContract = {
1512
1866
  list: oc.route({
@@ -1521,8 +1875,8 @@ var fileContract = {
1521
1875
  path: "/files",
1522
1876
  tags: ["Files"],
1523
1877
  summary: "Create File node",
1524
- successDescription: "Created a first-class File node that references an Asset."
1525
- }).input(createFileNodeInputSchema).output(FileNodeVOSchema),
1878
+ successDescription: "Review-first by default: a pending ChangeRequest proposing the File node. Returns the materialized File node instead when `autoMerge: true` is passed."
1879
+ }).input(createFileNodeInputSchema).output(z.union([FileNodeVOSchema, changeRequestSchema])),
1526
1880
  get: oc.route({
1527
1881
  method: "GET",
1528
1882
  path: "/files/{nodeId}",
@@ -1630,6 +1984,303 @@ var vaultContract = {
1630
1984
  successDescription: "Removed local Vault secrets and variables."
1631
1985
  }).output(VaultSuccessSchema)
1632
1986
  };
1987
+ var WebhookEventTypeSchema = z.enum([
1988
+ "record.created",
1989
+ "ai_mention",
1990
+ "changes_requested",
1991
+ "asset.uploaded"
1992
+ ]);
1993
+ z.enum(["webhook", "notify_agent", "run_function"]);
1994
+ var WebhookDeliveryStatusSchema = z.enum(["success", "failed", "skipped"]);
1995
+ var WebhookHttpConfigSchema = z.object({
1996
+ targetUrl: z.string().url(),
1997
+ secret: z.string().min(1).max(256).optional(),
1998
+ headers: z.record(z.string(), z.string()).optional()
1999
+ });
2000
+ var WebhookFunctionConfigSchema = z.object({
2001
+ code: z.string().min(1).max(2e4),
2002
+ timeoutMs: z.number().int().min(100).max(5e3).default(2e3)
2003
+ });
2004
+ var WebhookHttpConfigVOSchema = z.object({
2005
+ targetUrl: z.string().url(),
2006
+ hasSecret: z.boolean(),
2007
+ headers: z.record(z.string(), z.string()).optional()
2008
+ });
2009
+ var WebhookFunctionConfigVOSchema = WebhookFunctionConfigSchema;
2010
+ var webhookRuleCommonInputFields = {
2011
+ name: z.string().min(1).max(200),
2012
+ eventType: WebhookEventTypeSchema,
2013
+ baseId: z.string().nullable().optional(),
2014
+ enabled: z.boolean().default(true)
2015
+ };
2016
+ var WebhookRuleInputSchema = z.discriminatedUnion("actionKind", [
2017
+ z.object({
2018
+ ...webhookRuleCommonInputFields,
2019
+ actionKind: z.literal("webhook"),
2020
+ config: WebhookHttpConfigSchema
2021
+ }),
2022
+ z.object({
2023
+ ...webhookRuleCommonInputFields,
2024
+ actionKind: z.literal("notify_agent"),
2025
+ config: WebhookHttpConfigSchema
2026
+ }),
2027
+ z.object({
2028
+ ...webhookRuleCommonInputFields,
2029
+ actionKind: z.literal("run_function"),
2030
+ config: WebhookFunctionConfigSchema
2031
+ })
2032
+ ]);
2033
+ var WebhookRuleUpdateInputSchema = z.discriminatedUnion("actionKind", [
2034
+ z.object({
2035
+ id: z.string(),
2036
+ ...webhookRuleCommonInputFields,
2037
+ actionKind: z.literal("webhook"),
2038
+ config: WebhookHttpConfigSchema
2039
+ }),
2040
+ z.object({
2041
+ id: z.string(),
2042
+ ...webhookRuleCommonInputFields,
2043
+ actionKind: z.literal("notify_agent"),
2044
+ config: WebhookHttpConfigSchema
2045
+ }),
2046
+ z.object({
2047
+ id: z.string(),
2048
+ ...webhookRuleCommonInputFields,
2049
+ actionKind: z.literal("run_function"),
2050
+ config: WebhookFunctionConfigSchema
2051
+ })
2052
+ ]);
2053
+ var webhookRuleVOCommonFields = {
2054
+ id: z.string(),
2055
+ spaceId: z.string(),
2056
+ baseId: z.string().nullable(),
2057
+ name: z.string(),
2058
+ eventType: WebhookEventTypeSchema,
2059
+ enabled: z.boolean(),
2060
+ createdBy: z.string(),
2061
+ createdAt: z.string(),
2062
+ updatedAt: z.string(),
2063
+ lastTriggeredAt: z.string().nullable(),
2064
+ lastStatus: WebhookDeliveryStatusSchema.nullable()
2065
+ };
2066
+ var WebhookRuleVOSchema = z.discriminatedUnion("actionKind", [
2067
+ z.object({
2068
+ ...webhookRuleVOCommonFields,
2069
+ actionKind: z.literal("webhook"),
2070
+ config: WebhookHttpConfigVOSchema
2071
+ }),
2072
+ z.object({
2073
+ ...webhookRuleVOCommonFields,
2074
+ actionKind: z.literal("notify_agent"),
2075
+ config: WebhookHttpConfigVOSchema
2076
+ }),
2077
+ z.object({
2078
+ ...webhookRuleVOCommonFields,
2079
+ actionKind: z.literal("run_function"),
2080
+ config: WebhookFunctionConfigVOSchema
2081
+ })
2082
+ ]);
2083
+ var WebhookDeliveryVOSchema = z.object({
2084
+ id: z.string(),
2085
+ ruleId: z.string(),
2086
+ eventType: WebhookEventTypeSchema,
2087
+ status: WebhookDeliveryStatusSchema,
2088
+ httpStatus: z.number().nullable(),
2089
+ detail: z.string().nullable(),
2090
+ durationMs: z.number().nullable(),
2091
+ createdAt: z.string()
2092
+ });
2093
+ z.object({}).optional().default({});
2094
+ var ListWebhookDeliveriesInputSchema = z.object({
2095
+ ruleId: z.string(),
2096
+ limit: z.coerce.number().int().min(1).max(100).default(20)
2097
+ });
2098
+
2099
+ // ../../packages/busabase-contract/src/domains/webhook/contract.ts
2100
+ var webhookContract = {
2101
+ list: oc.route({
2102
+ method: "GET",
2103
+ path: "/webhooks",
2104
+ tags: ["Webhooks"],
2105
+ summary: "List webhook automation rules",
2106
+ successDescription: "Configured webhook automation rules for this space."
2107
+ }).output(WebhookRuleVOSchema.array()),
2108
+ get: oc.route({
2109
+ method: "GET",
2110
+ path: "/webhooks/{id}",
2111
+ tags: ["Webhooks"],
2112
+ summary: "Get webhook automation rule",
2113
+ successDescription: "A single webhook automation rule."
2114
+ }).input(z.object({ id: z.string() })).output(WebhookRuleVOSchema),
2115
+ create: oc.route({
2116
+ method: "POST",
2117
+ path: "/webhooks",
2118
+ tags: ["Webhooks"],
2119
+ summary: "Create webhook automation rule",
2120
+ successDescription: "Created webhook automation rule. Dispatches on the configured event via an HTTP webhook, an agent notification, or a sandboxed function."
2121
+ }).input(WebhookRuleInputSchema).output(WebhookRuleVOSchema),
2122
+ update: oc.route({
2123
+ method: "PUT",
2124
+ path: "/webhooks/{id}",
2125
+ tags: ["Webhooks"],
2126
+ summary: "Update webhook automation rule",
2127
+ successDescription: "Updated webhook automation rule."
2128
+ }).input(WebhookRuleUpdateInputSchema).output(WebhookRuleVOSchema),
2129
+ delete: oc.route({
2130
+ method: "DELETE",
2131
+ path: "/webhooks/{id}",
2132
+ tags: ["Webhooks"],
2133
+ summary: "Delete webhook automation rule",
2134
+ successDescription: "Removed the webhook automation rule."
2135
+ }).input(z.object({ id: z.string() })).output(z.object({ success: z.boolean() })),
2136
+ deliveries: oc.route({
2137
+ method: "GET",
2138
+ path: "/webhooks/{ruleId}/deliveries",
2139
+ tags: ["Webhooks"],
2140
+ summary: "List webhook rule delivery attempts",
2141
+ successDescription: "Recent delivery attempts for a webhook rule, newest first."
2142
+ }).input(ListWebhookDeliveriesInputSchema).output(WebhookDeliveryVOSchema.array()),
2143
+ testFire: oc.route({
2144
+ method: "POST",
2145
+ path: "/webhooks/{id}/test-fire",
2146
+ tags: ["Webhooks"],
2147
+ summary: "Test-fire a webhook automation rule",
2148
+ successDescription: "The delivery record produced by firing this rule right now with a synthetic payload \u2014 runs regardless of the rule's enabled state or its real trigger."
2149
+ }).input(z.object({ id: z.string() })).output(WebhookDeliveryVOSchema)
2150
+ };
2151
+ var activityItemSchema = z.discriminatedUnion("kind", [
2152
+ z.object({
2153
+ kind: z.literal("change_request"),
2154
+ timestamp: z.string(),
2155
+ changeRequest: changeRequestSchema
2156
+ }),
2157
+ z.object({
2158
+ kind: z.literal("operation"),
2159
+ timestamp: z.string(),
2160
+ operationId: z.string(),
2161
+ changeRequest: changeRequestSchema
2162
+ }),
2163
+ z.object({
2164
+ kind: z.literal("record"),
2165
+ timestamp: z.string(),
2166
+ record: recordSchema
2167
+ }),
2168
+ z.object({
2169
+ kind: z.literal("audit"),
2170
+ timestamp: z.string(),
2171
+ auditEvent: auditEventSchema,
2172
+ record: recordSchema.nullable()
2173
+ })
2174
+ ]);
2175
+ var listActivityPagedInputSchema = z.object({
2176
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50),
2177
+ cursor: z.string().optional()
2178
+ }).optional().default({ limit: 50 });
2179
+ var listActivityResponseSchema = z.object({
2180
+ items: z.array(activityItemSchema),
2181
+ nextCursor: z.string().nullable()
2182
+ });
2183
+ var GrepSourceSchema = z.enum(["files", "docs", "records"]);
2184
+ var UnifiedGrepFilesScopeSchema = z.object({
2185
+ assetIds: z.array(z.string()).optional(),
2186
+ /** Drive/Skill mounted path prefix (matches `busabase_asset_usages.path`). */
2187
+ drivePath: z.string().optional(),
2188
+ mimeTypes: z.array(z.string()).optional()
2189
+ });
2190
+ var UnifiedGrepDocsScopeSchema = z.object({
2191
+ nodeIds: z.array(z.string()).optional()
2192
+ });
2193
+ var UnifiedGrepRecordsScopeSchema = z.object({
2194
+ baseIds: z.array(z.string()).optional(),
2195
+ baseSlugs: z.array(z.string()).optional()
2196
+ });
2197
+ var UnifiedGrepScopeSchema = z.object({
2198
+ files: UnifiedGrepFilesScopeSchema.optional(),
2199
+ docs: UnifiedGrepDocsScopeSchema.optional(),
2200
+ records: UnifiedGrepRecordsScopeSchema.optional()
2201
+ });
2202
+ var UnifiedGrepInputSchema = z.object({
2203
+ pattern: z.string().min(1),
2204
+ /** JS RegExp flags, e.g. `"i"` for case-insensitive — same language as `assets.grep`. */
2205
+ flags: z.string().optional().default(""),
2206
+ /** Which sources to scan. Omitted = all three (`files`, `docs`, `records`). */
2207
+ sources: z.array(GrepSourceSchema).optional(),
2208
+ scope: UnifiedGrepScopeSchema.optional(),
2209
+ /** Shared across every scanned source — files run to completion first, then docs, then whatever remains goes to records. */
2210
+ maxMatches: z.coerce.number().int().min(1).max(GREP_HARD_MAX_MATCHES).optional().default(GREP_DEFAULT_MAX_MATCHES),
2211
+ contextLines: z.coerce.number().int().min(0).max(GREP_MAX_CONTEXT_LINES).optional().default(GREP_DEFAULT_CONTEXT_LINES)
2212
+ });
2213
+ var grepHitFields = {
2214
+ line: z.number().int().positive(),
2215
+ /** 1-based character column (not byte offset) of the match start within the line. */
2216
+ column: z.number().int().positive(),
2217
+ /** The matching line, truncated if it exceeds the long-line guard. */
2218
+ text: z.string(),
2219
+ before: z.array(z.string()),
2220
+ after: z.array(z.string())
2221
+ };
2222
+ var UnifiedGrepFileMatchVOSchema = z.object({
2223
+ source: z.literal("files"),
2224
+ assetId: z.string(),
2225
+ fileName: z.string(),
2226
+ /** Drive/Skill mounted path, or "" when the asset isn't path-mounted (e.g. a File node). */
2227
+ drivePath: z.string(),
2228
+ ...grepHitFields
2229
+ });
2230
+ var UnifiedGrepDocMatchVOSchema = z.object({
2231
+ source: z.literal("docs"),
2232
+ nodeId: z.string(),
2233
+ slug: z.string(),
2234
+ name: z.string(),
2235
+ ...grepHitFields
2236
+ });
2237
+ var UnifiedGrepRecordMatchVOSchema = z.object({
2238
+ source: z.literal("records"),
2239
+ baseId: z.string(),
2240
+ baseSlug: z.string(),
2241
+ recordId: z.string(),
2242
+ fieldSlug: z.string(),
2243
+ ...grepHitFields
2244
+ });
2245
+ var UnifiedGrepMatchVOSchema = z.discriminatedUnion("source", [
2246
+ UnifiedGrepFileMatchVOSchema,
2247
+ UnifiedGrepDocMatchVOSchema,
2248
+ UnifiedGrepRecordMatchVOSchema
2249
+ ]);
2250
+ var UnifiedGrepFilesCoverageSchema = z.object({
2251
+ scanned: z.number().int().nonnegative(),
2252
+ missing: z.array(z.string()),
2253
+ stale: z.array(z.string()),
2254
+ unsearchable: z.number().int().nonnegative(),
2255
+ errored: z.array(z.string()),
2256
+ notReached: z.number().int().nonnegative()
2257
+ });
2258
+ var UnifiedGrepDocsCoverageSchema = z.object({
2259
+ scanned: z.number().int().nonnegative(),
2260
+ /** Doc node ids whose body read/scan was attempted but failed — NOT a clean "scanned, no match". */
2261
+ errored: z.array(z.string()),
2262
+ /** Count of in-scope docs the scan never reached because the deadline/maxMatches budget ran out first. */
2263
+ notReached: z.number().int().nonnegative()
2264
+ });
2265
+ var UnifiedGrepRecordsCoverageSchema = z.object({
2266
+ scanned: z.number().int().nonnegative(),
2267
+ /** Record ids whose commit-fields read/flatten/scan was attempted but failed — NOT a clean "scanned, no match". */
2268
+ errored: z.array(z.string()),
2269
+ /** Count of in-scope records the scan never reached because the deadline/maxMatches budget ran out first. */
2270
+ notReached: z.number().int().nonnegative()
2271
+ });
2272
+ var UnifiedGrepCoverageSchema = z.object({
2273
+ files: UnifiedGrepFilesCoverageSchema,
2274
+ docs: UnifiedGrepDocsCoverageSchema,
2275
+ records: UnifiedGrepRecordsCoverageSchema
2276
+ });
2277
+ var UnifiedGrepResultVOSchema = z.object({
2278
+ /** Deterministic order: every `files` match, then every `docs` match, then every `records` match. */
2279
+ matches: z.array(UnifiedGrepMatchVOSchema),
2280
+ coverage: UnifiedGrepCoverageSchema,
2281
+ /** True when any source truncated, or any source has `notReached > 0`. */
2282
+ truncated: z.boolean()
2283
+ });
1633
2284
 
1634
2285
  // ../../packages/busabase-contract/src/contract/busabase.ts
1635
2286
  var changeRequestBatchResultSchema = z.object({
@@ -1659,6 +2310,18 @@ var busabaseContractRoutes = {
1659
2310
  summary: "Search Busabase",
1660
2311
  successDescription: "Paginated search results across records, change requests, Bases, File nodes, and Assets."
1661
2312
  }).input(searchInputSchema).output(searchResponseSchema),
2313
+ // Unified Grep (P2a files+docs, P2b records) — top-level, cross-source
2314
+ // superset of `assets.grep`. See apps/busabase/content/spec/unified-grep.md.
2315
+ // Composes `logic/grep.ts`; `assets.grep` (files-only specialist) is
2316
+ // unchanged and stays the dedicated endpoint for its fuller
2317
+ // missing/stale/unsearchable reporting.
2318
+ grep: oc.route({
2319
+ method: "POST",
2320
+ path: "/grep",
2321
+ tags: ["Search"],
2322
+ summary: "Search files, Docs, and Base records with one pattern (unified grep)",
2323
+ successDescription: "Streaming regex/literal matches across every in-scope source \u2014 Drive/Skill files, Doc bodies, and Base records (canonical headCommit.fields, never the truncated search projection) \u2014 with one shared pattern, one shared maxMatches/deadline budget (files scanned first, then docs, then whatever budget remains goes to records), and a per-source honest coverage report (files keeps its existing missing/stale/unsearchable/errored/notReached; docs and records report scanned/errored/notReached). truncated is set when any source truncated or has notReached > 0."
2324
+ }).input(UnifiedGrepInputSchema).output(UnifiedGrepResultVOSchema),
1662
2325
  nodes: {
1663
2326
  list: oc.route({
1664
2327
  method: "GET",
@@ -1681,6 +2344,13 @@ var busabaseContractRoutes = {
1681
2344
  summary: "Create Node tree change request",
1682
2345
  successDescription: "Created change request for folder or node tree changes."
1683
2346
  }).input(createNodeChangeRequestInputSchema).output(changeRequestSchema),
2347
+ move: oc.route({
2348
+ method: "POST",
2349
+ path: "/nodes/{nodeId}/move",
2350
+ tags: ["Nodes"],
2351
+ summary: "Move or reorder a node",
2352
+ successDescription: "Merged change request that repositioned the node under its (optionally new) parent. Applied immediately (auto-merged) since reordering is a low-risk structural tweak, not a review-worthy content change."
2353
+ }).input(moveNodeInputSchema).output(changeRequestSchema),
1684
2354
  purge: oc.route({
1685
2355
  method: "DELETE",
1686
2356
  path: "/nodes/{nodeId}",
@@ -1705,6 +2375,15 @@ var busabaseContractRoutes = {
1705
2375
  successDescription: "Recorded audit event."
1706
2376
  }).input(createAuditEventInputSchema).output(auditEventSchema)
1707
2377
  },
2378
+ activity: {
2379
+ listPaged: oc.route({
2380
+ method: "GET",
2381
+ path: "/activity/paged",
2382
+ tags: ["Activity"],
2383
+ summary: "List the activity feed with keyset pagination",
2384
+ successDescription: "A page of activity items (change requests, operations, records and audit events merged, newest first) plus an opaque nextCursor (null at the end)."
2385
+ }).input(listActivityPagedInputSchema).output(listActivityResponseSchema)
2386
+ },
1708
2387
  comments: {
1709
2388
  list: oc.route({
1710
2389
  method: "GET",
@@ -1738,11 +2417,14 @@ var busabaseContractRoutes = {
1738
2417
  bases: baseContract,
1739
2418
  skills: skillContract,
1740
2419
  drives: driveContract,
2420
+ airapps: airappContract,
1741
2421
  files: fileContract,
1742
2422
  docs: docContract,
1743
2423
  folders: folderContract,
1744
2424
  assets: assetsContract,
1745
2425
  vault: vaultContract,
2426
+ webhooks: webhookContract,
2427
+ dump: dumpContract,
1746
2428
  changeRequests: {
1747
2429
  list: oc.route({
1748
2430
  method: "GET",
@@ -2069,10 +2751,61 @@ var Busabase = class {
2069
2751
  get agentTasks() {
2070
2752
  return this.client.agentTasks;
2071
2753
  }
2754
+ get webhooks() {
2755
+ return this.client.webhooks;
2756
+ }
2072
2757
  /** Full-text search across records, change requests, and Bases. */
2073
2758
  search(input) {
2074
2759
  return this.client.search(input);
2075
2760
  }
2761
+ /**
2762
+ * Unified grep — one regex/literal pattern scanned across every in-scope
2763
+ * source (Drive/Skill files, Doc bodies, and Base records — records read
2764
+ * the canonical `headCommit.fields`, never the truncated search
2765
+ * projection), with a shared `maxMatches`/deadline budget and per-source
2766
+ * honest coverage. Use this when the answer could live anywhere; use
2767
+ * `client.assets.grep` directly instead when you specifically only care
2768
+ * about files and want its fuller `missing`/`stale`/`unsearchable`
2769
+ * file-only reporting.
2770
+ */
2771
+ grep(input) {
2772
+ return this.client.grep(input);
2773
+ }
2774
+ /**
2775
+ * Supply text for an Asset's Drive Grep Retrieval text slot in one call —
2776
+ * inline for small text, a presigned upload for large text — so callers
2777
+ * never see the underlying three-step flow
2778
+ * (`createTextUploadUrl` → PUT bytes → `putText({ storageKey })`).
2779
+ *
2780
+ * @example
2781
+ * ```ts
2782
+ * await bb.putText(assetId, extractedText); // picks inline vs presigned by size
2783
+ * ```
2784
+ */
2785
+ async putText(assetId, text) {
2786
+ const INLINE_TEXT_MAX_BYTES = 1024 * 1024;
2787
+ const byteLength = typeof Buffer !== "undefined" ? Buffer.byteLength(text, "utf8") : new Blob([text]).size;
2788
+ if (byteLength <= INLINE_TEXT_MAX_BYTES) {
2789
+ return this.client.assets.putText({ assetId, text });
2790
+ }
2791
+ const upload = await this.client.assets.createTextUploadUrl({
2792
+ assetId,
2793
+ sizeBytes: byteLength
2794
+ });
2795
+ const doFetch = this.config.fetch ?? fetch;
2796
+ const response = await doFetch(upload.uploadUrl, {
2797
+ method: "PUT",
2798
+ headers: { "content-type": "text/plain; charset=utf-8" },
2799
+ body: text
2800
+ });
2801
+ if (!response.ok) {
2802
+ const detail = await response.text().catch(() => "");
2803
+ throw new Error(
2804
+ `putText: presigned upload failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
2805
+ );
2806
+ }
2807
+ return this.client.assets.putText({ assetId, storageKey: upload.storageKey });
2808
+ }
2076
2809
  /** Service health — reaches the server without requiring auth. */
2077
2810
  health() {
2078
2811
  return this.client.system.health();