busabase-sdk 0.16.1 → 0.17.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.
package/dist/index.js CHANGED
@@ -557,7 +557,13 @@ var htmlNodeType = {
557
557
  label: "HTML",
558
558
  icon: "code-xml",
559
559
  capabilities: { hasDetail: true, creatable: true },
560
- operations: []
560
+ operations: [
561
+ {
562
+ kind: "html_document_update",
563
+ label: "Update HTML",
564
+ tone: "border-blue-200 bg-blue-50 text-blue-800"
565
+ }
566
+ ]
561
567
  };
562
568
 
563
569
  // ../../packages/busabase-contract/src/domains/skill/definition.ts
@@ -572,7 +578,13 @@ var whiteboardNodeType = {
572
578
  label: "Whiteboard",
573
579
  icon: "pen-tool",
574
580
  capabilities: { hasDetail: true, creatable: true },
575
- operations: []
581
+ operations: [
582
+ {
583
+ kind: "whiteboard_document_update",
584
+ label: "Update whiteboard",
585
+ tone: "border-blue-200 bg-blue-50 text-blue-800"
586
+ }
587
+ ]
576
588
  };
577
589
 
578
590
  // ../../packages/busabase-contract/src/domains/workflow/definition.ts
@@ -581,7 +593,13 @@ var workflowNodeType = {
581
593
  label: "Workflow",
582
594
  icon: "workflow",
583
595
  capabilities: { hasDetail: true, creatable: true },
584
- operations: []
596
+ operations: [
597
+ {
598
+ kind: "workflow_document_update",
599
+ label: "Update workflow",
600
+ tone: "border-blue-200 bg-blue-50 text-blue-800"
601
+ }
602
+ ]
585
603
  };
586
604
 
587
605
  // ../../packages/busabase-contract/src/domains/registry.ts
@@ -650,12 +668,8 @@ var nodeSchema = z.lazy(
650
668
  slug: z.string(),
651
669
  name: z.string(),
652
670
  description: z.string(),
653
- metadata: z.object({
654
- entryFile: z.string().optional(),
655
- visibility: z.enum(["private", "workspace", "public"]).optional(),
656
- version: z.string().optional(),
657
- assetId: z.string().optional()
658
- }).catchall(z.unknown()).default({}),
671
+ metadata: z.object({ version: z.string().optional() }).catchall(z.unknown()).default({}),
672
+ explicitVisibility: z.enum(["private", "workspace", "public"]).nullable().default(null),
659
673
  position: z.number(),
660
674
  createdAt: z.string(),
661
675
  updatedAt: z.string(),
@@ -762,7 +776,17 @@ var commitSchema = z.object({
762
776
  nodeId: z.string().nullable(),
763
777
  operationId: z.string().nullable(),
764
778
  parentCommitId: z.string().nullable(),
765
- fields: z.record(z.string(), z.unknown()),
779
+ // Deliberately loose (`z.record`), NOT a discriminated union keyed on `operation`.
780
+ //
781
+ // Do not "tighten" this. Commits already in the database were written before
782
+ // per-operation payload validation existed, so their shapes carry no guarantee.
783
+ // The write path (`insertCommit`) and the merge path (`parseCommitPayload`) are
784
+ // strict precisely because they only ever touch freshly-written payloads; this VO
785
+ // is also used to render *history* and approval detail pages, which read arbitrarily
786
+ // old commits. Making it strict would turn any legacy-shaped row into a 500 on a
787
+ // read-only screen. This is not a compatibility shim — it is the requirement not to
788
+ // break reading data that already exists.
789
+ payload: z.record(z.string(), z.unknown()),
766
790
  operation: z.enum(OPERATION_KINDS),
767
791
  message: z.string(),
768
792
  author: z.string(),
@@ -1333,6 +1357,14 @@ var airAppRunLocalNodeInputSchema = z.object({
1333
1357
  /** Text files to mount into the sandbox workdir before installing, keyed by
1334
1358
  * path (same shape `RunPanel` already assembles for `NodepodRunner.mount`). */
1335
1359
  files: z.record(z.string(), z.string()),
1360
+ /** Binary (asset-backed) files — images, fonts, sample data — keyed by the
1361
+ * same path, base64-encoded because this input crosses a JSON boundary that
1362
+ * `Uint8Array` cannot. Separate from `files` rather than a tagged union so
1363
+ * an older client that sends only `files` keeps working unchanged.
1364
+ *
1365
+ * The in-browser Nodepod engine never uses this field: it hands raw bytes to
1366
+ * `Nodepod.boot({ files })` directly and skips the base64 round trip. */
1367
+ binaryFiles: z.record(z.string(), z.string()).optional().default({}),
1336
1368
  /** Server-side execution mode. `"local-node"` spawns a bare host Node.js
1337
1369
  * process (previewable, data bridge via reverse proxy, NOT OS-isolated);
1338
1370
  * `"srt"` wraps the same commands in the OS sandbox (isolated execution,
@@ -2167,21 +2199,6 @@ var createDocInputSchema = z.object({
2167
2199
  // force review even with write access.
2168
2200
  autoMerge: z.boolean().optional()
2169
2201
  });
2170
- var updateDocInputSchema = z.object({
2171
- body: z.string()
2172
- });
2173
- var createDocChangeRequestInputSchema = z.object({
2174
- body: z.string(),
2175
- message: z.string().optional().default("Update doc").describe(
2176
- 'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Add rollback steps to the deploy runbook".'
2177
- ),
2178
- submittedBy: z.string().optional().default("local-producer"),
2179
- // A Doc body update is the Doc-domain twin of a record `update`, which has taken
2180
- // the permission-aware default since #5712 — and this node type already has a
2181
- // direct-write bypass (`PUT /docs/{nodeId}/body`), so review-first here was never
2182
- // an actual guarantee, just a slower path to the same place.
2183
- autoMerge: z.boolean().optional()
2184
- });
2185
2202
  var docContract = {
2186
2203
  create: oc.route({
2187
2204
  method: "POST",
@@ -2201,21 +2218,7 @@ var docContract = {
2201
2218
  tags: ["Docs"],
2202
2219
  summary: "Read an exact line range from a Doc body",
2203
2220
  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 `nodes.get`'s entire body."
2204
- }).input(ReadDocLinesInputSchema).output(ReadLinesVOSchema),
2205
- updateBody: oc.route({
2206
- method: "PUT",
2207
- path: "/docs/{nodeId}/body",
2208
- tags: ["Docs"],
2209
- summary: "Update Doc body",
2210
- successDescription: "Updated the Doc body."
2211
- }).input(updateDocInputSchema.extend({ nodeId: z.string() })).output(docSchema),
2212
- createChangeRequest: oc.route({
2213
- method: "POST",
2214
- path: "/docs/{nodeId}/change-requests",
2215
- tags: ["Docs", "Change Requests"],
2216
- summary: "Create Doc change request",
2217
- successDescription: "Created a change request that proposes a new Doc body."
2218
- }).input(createDocChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
2221
+ }).input(ReadDocLinesInputSchema).output(ReadLinesVOSchema)
2219
2222
  };
2220
2223
  var DumpTableSchema = z.enum([
2221
2224
  "nodes",
@@ -3039,6 +3042,125 @@ var UnifiedGrepResultVOSchema = z.object({
3039
3042
  /** True when any source truncated, or any source has `notReached > 0`. */
3040
3043
  truncated: z.boolean()
3041
3044
  });
3045
+ var positionSchema = z.object({
3046
+ x: z.number().finite(),
3047
+ y: z.number().finite()
3048
+ });
3049
+ var WhiteboardDocumentSchema = z.object({
3050
+ version: z.literal(1),
3051
+ elements: z.array(z.unknown()).default([]),
3052
+ appState: z.record(z.string(), z.unknown()).default({})
3053
+ });
3054
+ var workflowNodeBase = {
3055
+ id: z.string().min(1),
3056
+ position: positionSchema,
3057
+ label: z.string().min(1).max(120),
3058
+ description: z.string().max(500).default("")
3059
+ };
3060
+ var WorkflowNodeSchema = z.discriminatedUnion("kind", [
3061
+ z.object({
3062
+ ...workflowNodeBase,
3063
+ kind: z.literal("trigger"),
3064
+ eventName: z.string().max(160).default("manual")
3065
+ }),
3066
+ z.object({
3067
+ ...workflowNodeBase,
3068
+ kind: z.literal("webhook"),
3069
+ method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("POST"),
3070
+ url: z.string().max(2e3).default("")
3071
+ }),
3072
+ z.object({
3073
+ ...workflowNodeBase,
3074
+ kind: z.literal("function"),
3075
+ webhookRuleId: z.string().max(160).default(""),
3076
+ functionName: z.string().max(160).default("")
3077
+ }),
3078
+ z.object({
3079
+ ...workflowNodeBase,
3080
+ kind: z.literal("condition"),
3081
+ expression: z.string().max(2e3).default("")
3082
+ }),
3083
+ z.object({
3084
+ ...workflowNodeBase,
3085
+ kind: z.literal("wait"),
3086
+ duration: z.number().int().min(0).max(525600).default(1),
3087
+ unit: z.enum(["minutes", "hours", "days"]).default("hours")
3088
+ }),
3089
+ z.object({
3090
+ ...workflowNodeBase,
3091
+ kind: z.literal("approval"),
3092
+ approver: z.string().max(160).default("")
3093
+ }),
3094
+ z.object({
3095
+ ...workflowNodeBase,
3096
+ kind: z.literal("action"),
3097
+ actionName: z.string().max(160).default("")
3098
+ }),
3099
+ z.object({
3100
+ ...workflowNodeBase,
3101
+ kind: z.literal("end"),
3102
+ outcome: z.string().max(160).default("completed")
3103
+ })
3104
+ ]);
3105
+ var GraphEdgeSchema = z.object({
3106
+ id: z.string().min(1),
3107
+ source: z.string().min(1),
3108
+ target: z.string().min(1)
3109
+ });
3110
+ var WorkflowEdgeSchema = GraphEdgeSchema.extend({
3111
+ label: z.string().max(120).default(""),
3112
+ outcome: z.string().max(120).default("default")
3113
+ });
3114
+ var WorkflowSettingsSchema = z.object({
3115
+ executionMode: z.enum(["manual", "event"]).default("manual"),
3116
+ concurrency: z.number().int().min(1).max(50).default(1),
3117
+ timeoutMs: z.number().int().min(1e3).max(3e5).default(3e4),
3118
+ errorPolicy: z.enum(["stop", "continue"]).default("stop")
3119
+ });
3120
+ var WorkflowDocumentSchema = z.object({
3121
+ version: z.literal(2),
3122
+ nodes: z.array(WorkflowNodeSchema).default([]),
3123
+ edges: z.array(WorkflowEdgeSchema).default([]),
3124
+ settings: WorkflowSettingsSchema.default({
3125
+ executionMode: "manual",
3126
+ concurrency: 1,
3127
+ timeoutMs: 3e4,
3128
+ errorPolicy: "stop"
3129
+ })
3130
+ });
3131
+ var HtmlDocumentSchema = z.object({
3132
+ version: z.literal(1),
3133
+ source: z.string().max(5e5)
3134
+ });
3135
+
3136
+ // ../../packages/busabase-contract/src/contract/node-content-schemas.ts
3137
+ var NODE_CONTENT_VARIANTS = {
3138
+ doc: z.object({ kind: z.literal("doc"), body: z.string() }),
3139
+ whiteboard: z.object({ kind: z.literal("whiteboard"), document: WhiteboardDocumentSchema }),
3140
+ workflow: z.object({ kind: z.literal("workflow"), document: WorkflowDocumentSchema }),
3141
+ html: z.object({ kind: z.literal("html"), document: HtmlDocumentSchema })
3142
+ };
3143
+ var nodeContentInputSchema = z.discriminatedUnion("kind", [
3144
+ NODE_CONTENT_VARIANTS.doc,
3145
+ NODE_CONTENT_VARIANTS.whiteboard,
3146
+ NODE_CONTENT_VARIANTS.workflow,
3147
+ NODE_CONTENT_VARIANTS.html
3148
+ ]);
3149
+ var updateNodeContentInputSchema = z.object({
3150
+ nodeId: z.string(),
3151
+ content: nodeContentInputSchema,
3152
+ message: z.string().optional().default("Update content").describe(
3153
+ 'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Add rollback steps to the deploy runbook".'
3154
+ ),
3155
+ submittedBy: z.string().optional().default("local-producer"),
3156
+ // Server-decided, not client-decided: `shouldAutoMerge(autoMerge, hasWrite) =
3157
+ // autoMerge !== false && hasWrite`. Omitted/true merges immediately IF the
3158
+ // actor holds `write` on the node; otherwise (or with explicit `false`) the
3159
+ // content lands as a pending ChangeRequest instead. A `changeRequest`-level
3160
+ // caller passing `autoMerge: true` still lands in review — the server never
3161
+ // lets the client escalate past its own permission.
3162
+ autoMerge: z.boolean().optional()
3163
+ });
3042
3164
  var folderSchema = z.object({
3043
3165
  node: nodeSchema,
3044
3166
  children: z.array(nodeSchema)
@@ -3049,6 +3171,11 @@ var genericNodeDetailSchema = (type) => z.object({
3049
3171
  type: z.literal(type),
3050
3172
  node: nodeSchema
3051
3173
  });
3174
+ var richNodeDetailSchema = (type, documentSchema) => z.object({
3175
+ type: z.literal(type),
3176
+ node: nodeSchema,
3177
+ document: documentSchema
3178
+ });
3052
3179
  var NODE_DETAIL_VARIANTS = {
3053
3180
  folder: folderSchema.extend({ type: z.literal("folder") }),
3054
3181
  doc: docSchema.extend({ type: z.literal("doc") }),
@@ -3061,9 +3188,9 @@ var NODE_DETAIL_VARIANTS = {
3061
3188
  airapp: fileTreeNodeSchema.extend({ type: z.literal("airapp") }),
3062
3189
  base: genericNodeDetailSchema("base"),
3063
3190
  form: genericNodeDetailSchema("form"),
3064
- whiteboard: genericNodeDetailSchema("whiteboard"),
3065
- workflow: genericNodeDetailSchema("workflow"),
3066
- html: genericNodeDetailSchema("html")
3191
+ whiteboard: richNodeDetailSchema("whiteboard", WhiteboardDocumentSchema),
3192
+ workflow: richNodeDetailSchema("workflow", WorkflowDocumentSchema),
3193
+ html: richNodeDetailSchema("html", HtmlDocumentSchema)
3067
3194
  };
3068
3195
  var NodeDetailVOSchema = z.discriminatedUnion("type", [
3069
3196
  NODE_DETAIL_VARIANTS.folder,
@@ -3146,7 +3273,7 @@ var busabaseContractRoutes = {
3146
3273
  path: "/grep",
3147
3274
  tags: ["Search"],
3148
3275
  summary: "Search files, Docs, and Base records with one pattern (unified grep)",
3149
- 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."
3276
+ successDescription: "Streaming regex/literal matches across every in-scope source \u2014 Drive/Skill files, Doc bodies, and Base records (canonical headCommit.payload, 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."
3150
3277
  }).input(UnifiedGrepInputSchema).output(UnifiedGrepResultVOSchema),
3151
3278
  nodes: {
3152
3279
  list: oc.route({
@@ -3189,8 +3316,15 @@ var busabaseContractRoutes = {
3189
3316
  path: "/nodes/{nodeId}/metadata",
3190
3317
  tags: ["Nodes"],
3191
3318
  summary: "Update node metadata",
3192
- successDescription: "Shallow-merged the supplied top-level keys into the active node's existing metadata. Requires write access on the node."
3319
+ successDescription: "Shallow-merged the supplied top-level keys into the active node's existing metadata. Requires write access on the node. Node CONTENT (a Doc body, or a whiteboard/workflow/html document) does not go through here \u2014 use PUT /nodes/{nodeId}/content instead."
3193
3320
  }).input(updateNodeMetadataInputSchema).output(nodeSchema),
3321
+ updateContent: oc.route({
3322
+ method: "PUT",
3323
+ path: "/nodes/{nodeId}/content",
3324
+ tags: ["Nodes", "Change Requests"],
3325
+ summary: "Update node content",
3326
+ successDescription: "ChangeRequest carrying the proposed content. Merged immediately when the actor holds write access on the node and `autoMerge` was not explicitly `false`; otherwise left `in_review` for a human. Accepts doc, whiteboard, workflow, and html nodes \u2014 the types that own exactly one document."
3327
+ }).input(updateNodeContentInputSchema).output(changeRequestSchema),
3194
3328
  purge: oc.route({
3195
3329
  method: "DELETE",
3196
3330
  path: "/nodes/{nodeId}",
@@ -3930,7 +4064,7 @@ var Busabase = class {
3930
4064
  /**
3931
4065
  * Unified grep — one regex/literal pattern scanned across every in-scope
3932
4066
  * source (Drive/Skill files, Doc bodies, and Base records — records read
3933
- * the canonical `headCommit.fields`, never the truncated search
4067
+ * the canonical `headCommit.payload`, never the truncated search
3934
4068
  * projection), with a shared `maxMatches`/deadline budget and per-source
3935
4069
  * honest coverage. `bb.assets.grep` remains available as a files-only SDK
3936
4070
  * convenience and delegates here with `sources: ["files"]`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Typed TypeScript/JavaScript SDK for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server` (or Busabase Cloud).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-sdk",
@@ -63,9 +63,9 @@
63
63
  "tsx": "^4.20.5",
64
64
  "typescript": "^5.9.3",
65
65
  "vitest": "^2.1.8",
66
- "busabase-contract": "0.16.1",
66
+ "open-domains": "0.0.2",
67
67
  "openlib": "0.1.1",
68
- "open-domains": "0.0.2"
68
+ "busabase-contract": "0.17.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=24.18.0"