@lambdacurry/arbor 0.22.40 → 0.23.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 (2) hide show
  1. package/dist/arbor.js +443 -47
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "@lambdacurry/arbor",
5
- version: "0.22.40",
5
+ version: "0.23.0",
6
6
  description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
7
7
  keywords: [
8
8
  "agents",
@@ -4921,6 +4921,30 @@ var IDENTITY_COLORS = [
4921
4921
  "rose",
4922
4922
  "cyan"
4923
4923
  ];
4924
+ var LIFECYCLE_TYPES = [
4925
+ "backlog",
4926
+ "active",
4927
+ "needs-review",
4928
+ "stuck",
4929
+ "standing",
4930
+ "resolved",
4931
+ "canceled",
4932
+ "archived"
4933
+ ];
4934
+ var LIFECYCLE_TYPE_DESCRIPTIONS = {
4935
+ backlog: "accepted, not committed",
4936
+ active: "committed and in flight",
4937
+ "needs-review": "waiting on a read",
4938
+ stuck: "in flight, blocked",
4939
+ standing: "open-forever; never owed a conclusion (AD-200)",
4940
+ resolved: "the objective was met",
4941
+ canceled: "we decided not to",
4942
+ archived: "left the record; refile with `thread_update`"
4943
+ };
4944
+ function transitionThreadActionDescription() {
4945
+ const types = LIFECYCLE_TYPES.map((type) => `\`${type}\` ${LIFECYCLE_TYPE_DESCRIPTIONS[type]}`).join("; ");
4946
+ return `Move a Thread by key, case-insensitive name, or type (\`--to resolved\` still works as a bare type); illegal jumps rejected. ${types}; live menu is \`thread_get.transitionsFrom\`.`;
4947
+ }
4924
4948
  var CONTRIBUTION_TYPES = [
4925
4949
  "comment",
4926
4950
  "question",
@@ -6427,6 +6451,24 @@ var agentPairings = sqliteTable("agent_pairings", {
6427
6451
  userCodeIdx: uniqueIndex("agent_pairings_user_code_idx").on(t.userCode),
6428
6452
  orgStatusIdx: index("agent_pairings_org_status_idx").on(t.orgId, t.status)
6429
6453
  }));
6454
+ var workflows = sqliteTable("workflows", {
6455
+ id: text("id").primaryKey(),
6456
+ orgId: text("org_id").notNull().references(() => orgs.id),
6457
+ name: text("name").notNull(),
6458
+ summary: text("summary").notNull(),
6459
+ labels: text("labels", { mode: "json" }).$type().notNull(),
6460
+ createdAt: ts("created_at").notNull(),
6461
+ updatedAt: ts("updated_at").notNull()
6462
+ }, (t) => ({ orgIdx: index("workflows_org_idx").on(t.orgId) }));
6463
+ var spaceWorkflows = sqliteTable("space_workflows", {
6464
+ spaceId: text("space_id").notNull().references(() => spaces.id),
6465
+ workflowId: text("workflow_id").notNull().references(() => workflows.id),
6466
+ isDefault: integer2("is_default", { mode: "boolean" }).notNull().default(false)
6467
+ }, (t) => ({
6468
+ pk: primaryKey({ columns: [t.spaceId, t.workflowId] }),
6469
+ workflowIdx: index("space_workflows_workflow_idx").on(t.workflowId),
6470
+ oneDefaultUq: uniqueIndex("space_workflows_one_default_uq").on(t.spaceId).where(sql`${t.isDefault} = 1`)
6471
+ }));
6430
6472
  var topics = sqliteTable("topics", {
6431
6473
  id: text("id").primaryKey(),
6432
6474
  spaceId: text("space_id").notNull().references(() => spaces.id),
@@ -6434,9 +6476,13 @@ var topics = sqliteTable("topics", {
6434
6476
  purpose: text("purpose"),
6435
6477
  guidance: text("guidance", { mode: "json" }).$type().notNull().default([]),
6436
6478
  contributionLanes: text("contribution_lanes", { mode: "json" }).$type().notNull().default([]),
6479
+ workflowId: text("workflow_id").references(() => workflows.id),
6437
6480
  archivedAt: ts("archived_at"),
6438
6481
  createdAt: ts("created_at").notNull()
6439
- }, (t) => ({ spaceIdx: index("topics_space_idx").on(t.spaceId) }));
6482
+ }, (t) => ({
6483
+ spaceIdx: index("topics_space_idx").on(t.spaceId),
6484
+ workflowIdx: index("topics_workflow_idx").on(t.workflowId)
6485
+ }));
6440
6486
  var initiatives = sqliteTable("initiatives", {
6441
6487
  id: text("id").primaryKey(),
6442
6488
  orgId: text("org_id").notNull().references(() => orgs.id),
@@ -6464,13 +6510,16 @@ var threads = sqliteTable("threads", {
6464
6510
  title: text("title").notNull(),
6465
6511
  objective: text("objective").notNull(),
6466
6512
  status: text("status").$type().notNull().default("active"),
6513
+ lifecycleType: text("lifecycle_type").$type().notNull().default("active"),
6514
+ lifecycleLabel: text("lifecycle_label").notNull().default("active"),
6467
6515
  waysToHelp: text("ways_to_help", { mode: "json" }).$type().notNull().default([]),
6468
6516
  openQuestions: text("open_questions", { mode: "json" }).$type().notNull().default([]),
6469
6517
  expectedOutput: text("expected_output").notNull().default(""),
6470
6518
  createdAt: ts("created_at").notNull()
6471
6519
  }, (t) => ({
6472
6520
  topicIdx: index("threads_topic_idx").on(t.topicId),
6473
- spaceIdx: index("threads_space_idx").on(t.spaceId)
6521
+ spaceIdx: index("threads_space_idx").on(t.spaceId),
6522
+ lifecycleTypeIdx: index("threads_lifecycle_type_idx").on(t.lifecycleType)
6474
6523
  }));
6475
6524
  var computerConfigs = sqliteTable("computer_configs", {
6476
6525
  id: text("id").primaryKey(),
@@ -6699,6 +6748,60 @@ var computerRuns = sqliteTable("computer_runs", {
6699
6748
  statusIdx: index("computer_runs_status_idx").on(t.computerId, t.status),
6700
6749
  runtimeIdx: index("computer_runs_runtime_idx").on(t.computerId, t.runtimeState)
6701
6750
  }));
6751
+ var packageCacheObjects = sqliteTable("package_cache_objects", {
6752
+ key: text("key").primaryKey(),
6753
+ orgId: text("org_id").notNull().references(() => orgs.id),
6754
+ project: text("project").notNull(),
6755
+ ecosystem: text("ecosystem").notNull(),
6756
+ originId: text("origin_id").notNull(),
6757
+ integrityAlgorithm: text("integrity_algorithm").notNull(),
6758
+ digest: text("digest").notNull(),
6759
+ sri: text("sri").notNull(),
6760
+ byteLength: integer2("byte_length").notNull(),
6761
+ status: text("status").$type().notNull(),
6762
+ uploadId: text("upload_id").notNull(),
6763
+ producerRunId: text("producer_run_id").notNull().references(() => computerRuns.id),
6764
+ producerTrust: text("producer_trust").$type().notNull(),
6765
+ createdAt: ts("created_at").notNull(),
6766
+ readyAt: ts("ready_at"),
6767
+ expiresAt: ts("expires_at").notNull()
6768
+ }, (t) => ({
6769
+ uploadUq: uniqueIndex("package_cache_objects_upload_uniq").on(t.uploadId),
6770
+ projectStatusExpiryIdx: index("package_cache_objects_project_status_expiry_idx").on(t.orgId, t.project, t.status, t.expiresAt)
6771
+ }));
6772
+ var packageCacheProjectUsage = sqliteTable("package_cache_project_usage", {
6773
+ id: text("id").primaryKey(),
6774
+ orgId: text("org_id").notNull().references(() => orgs.id),
6775
+ project: text("project").notNull(),
6776
+ maxStoredBytes: integer2("max_stored_bytes").notNull(),
6777
+ maxObjects: integer2("max_objects").notNull(),
6778
+ maxWriteBytesPerDay: integer2("max_write_bytes_per_day").notNull(),
6779
+ writePeriod: text("write_period").notNull(),
6780
+ writeBytesToday: integer2("write_bytes_today").notNull().default(0),
6781
+ storedBytes: integer2("stored_bytes").notNull().default(0),
6782
+ objectCount: integer2("object_count").notNull().default(0),
6783
+ reservedBytes: integer2("reserved_bytes").notNull().default(0),
6784
+ reservedObjects: integer2("reserved_objects").notNull().default(0),
6785
+ writeBytes: integer2("write_bytes").notNull().default(0),
6786
+ readBytes: integer2("read_bytes").notNull().default(0),
6787
+ hits: integer2("hits").notNull().default(0),
6788
+ misses: integer2("misses").notNull().default(0),
6789
+ rejections: integer2("rejections").notNull().default(0),
6790
+ classAOps: integer2("class_a_ops").notNull().default(0),
6791
+ classBOps: integer2("class_b_ops").notNull().default(0),
6792
+ publicationsNew: integer2("publications_new").notNull().default(0),
6793
+ publicationsDuplicate: integer2("publications_duplicate").notNull().default(0),
6794
+ publicationsConverged: integer2("publications_converged").notNull().default(0),
6795
+ publicationsRejected: integer2("publications_rejected").notNull().default(0),
6796
+ fallbacks: integer2("fallbacks").notNull().default(0),
6797
+ updatedAt: ts("updated_at").notNull()
6798
+ }, (t) => ({
6799
+ projectUq: uniqueIndex("package_cache_project_usage_project_uniq").on(t.orgId, t.project),
6800
+ nonnegative: check("package_cache_project_usage_nonnegative", sql`${t.storedBytes} >= 0 AND ${t.objectCount} >= 0 AND ${t.reservedBytes} >= 0 AND ${t.reservedObjects} >= 0`),
6801
+ byteBudget: check("package_cache_project_usage_byte_budget", sql`${t.storedBytes} + ${t.reservedBytes} <= ${t.maxStoredBytes}`),
6802
+ objectBudget: check("package_cache_project_usage_object_budget", sql`${t.objectCount} + ${t.reservedObjects} <= ${t.maxObjects}`),
6803
+ dailyWriteBudget: check("package_cache_project_usage_daily_write_budget", sql`${t.writeBytesToday} + ${t.reservedBytes} <= ${t.maxWriteBytesPerDay}`)
6804
+ }));
6702
6805
  var computerSnapshots = sqliteTable("computer_snapshots", {
6703
6806
  id: text("id").primaryKey(),
6704
6807
  ownerScope: text("owner_scope").$type().notNull(),
@@ -7064,6 +7167,22 @@ var artifactTldrawBoards = sqliteTable("artifact_tldraw_boards", {
7064
7167
  }, (t) => ({
7065
7168
  createKeyUq: uniqueIndex("artifact_tldraw_boards_create_key_uq").on(t.createActorProfileId, t.createThreadId, t.createIdempotencyKey)
7066
7169
  }));
7170
+ var artifactTldrawLatestPreviews = sqliteTable("artifact_tldraw_latest_previews", {
7171
+ artifactId: text("artifact_id").primaryKey().references(() => artifacts.id, { onDelete: "cascade" }),
7172
+ attachmentId: text("attachment_id").notNull().references(() => attachments.id),
7173
+ documentDigest: text("document_digest").notNull(),
7174
+ documentClock: integer2("document_clock").notNull(),
7175
+ roomGeneration: integer2("room_generation").notNull(),
7176
+ width: integer2("width").notNull(),
7177
+ height: integer2("height").notNull(),
7178
+ generatedAt: ts("generated_at").notNull(),
7179
+ revision: integer2("revision").notNull().default(1),
7180
+ storageSlot: integer2("storage_slot").notNull(),
7181
+ retiredAttachmentId: text("retired_attachment_id"),
7182
+ retiredStorageSlot: integer2("retired_storage_slot")
7183
+ }, (t) => ({
7184
+ attachmentIdx: uniqueIndex("artifact_tldraw_latest_previews_attachment_uniq").on(t.attachmentId)
7185
+ }));
7067
7186
  var artifactTldrawCheckpoints = sqliteTable("artifact_tldraw_checkpoints", {
7068
7187
  id: text("id").primaryKey(),
7069
7188
  artifactId: text("artifact_id").notNull().references(() => artifacts.id, { onDelete: "cascade" }),
@@ -7589,6 +7708,163 @@ var threadAssignments = sqliteTable("thread_assignments", {
7589
7708
  assigneeUq: uniqueIndex("thread_assignments_thread_profile_uniq").on(t.threadId, t.profileId),
7590
7709
  profileIdx: index("thread_assignments_profile_idx").on(t.profileId, t.createdAt)
7591
7710
  }));
7711
+ // ../core/src/db/tldraw-preview-candidate-schema.ts
7712
+ var ts2 = (name) => text(name);
7713
+ var artifactTldrawPreviewCandidates = sqliteTable("artifact_tldraw_preview_candidates", {
7714
+ artifactId: text("artifact_id").primaryKey().references(() => artifacts.id, { onDelete: "cascade" }),
7715
+ generationToken: text("generation_token").notNull(),
7716
+ slot: integer2("slot").notNull(),
7717
+ attachmentId: text("attachment_id").notNull(),
7718
+ r2Key: text("r2_key").notNull(),
7719
+ targetDocumentDigest: text("target_document_digest").notNull(),
7720
+ leaseExpiresAt: ts2("lease_expires_at").notNull(),
7721
+ createdAt: ts2("created_at").notNull()
7722
+ }, (t) => ({
7723
+ generationIdx: uniqueIndex("artifact_tldraw_preview_candidates_generation_uniq").on(t.generationToken),
7724
+ attachmentIdx: uniqueIndex("artifact_tldraw_preview_candidates_attachment_uniq").on(t.attachmentId),
7725
+ r2KeyIdx: uniqueIndex("artifact_tldraw_preview_candidates_r2_key_uniq").on(t.r2Key)
7726
+ }));
7727
+ // ../core/src/lifecycle/thread.ts
7728
+ var STATUS_OPENNESS = {
7729
+ backlog: { working: false, currentWork: false, closed: false },
7730
+ active: { working: true, currentWork: true, closed: false },
7731
+ "needs-review": { working: true, currentWork: true, closed: false },
7732
+ stuck: { working: true, currentWork: true, closed: false },
7733
+ standing: { working: true, currentWork: false, closed: false },
7734
+ resolved: { working: false, currentWork: false, closed: true },
7735
+ canceled: { working: false, currentWork: false, closed: true },
7736
+ archived: { working: false, currentWork: false, closed: true }
7737
+ };
7738
+ var typesWhere = (sense) => Object.keys(STATUS_OPENNESS).filter((s) => STATUS_OPENNESS[s][sense]);
7739
+ var WORKING_THREAD_STATUSES = typesWhere("working");
7740
+ var CURRENT_WORK_THREAD_STATUSES = typesWhere("currentWork");
7741
+ // ../core/src/lifecycle/workflow.ts
7742
+ var WORKFLOW_SUMMARY_MAX = 500;
7743
+ var WORKFLOW_LABEL_DESCRIPTION_MAX = 200;
7744
+ var LIFECYCLE_TYPE_SET = new Set(LIFECYCLE_TYPES);
7745
+ // ../core/src/lifecycle/builtins.ts
7746
+ var BUILTIN_WORKFLOW_IDS = [
7747
+ "standard",
7748
+ "queue",
7749
+ "content",
7750
+ "experiment",
7751
+ "funnel"
7752
+ ];
7753
+ var col = (key, name, type, order, extra) => ({ key, name, type, order, ...extra });
7754
+ var STANDARD = {
7755
+ id: "standard",
7756
+ name: "Standard",
7757
+ summary: "One label per type using the protocol's own words — the zero-config default and the only all-eight workflow.",
7758
+ labels: [
7759
+ col("backlog", "Backlog", "backlog", 0),
7760
+ col("active", "Active", "active", 1),
7761
+ col("needs-review", "Needs review", "needs-review", 2),
7762
+ col("stuck", "Stuck", "stuck", 3),
7763
+ col("standing", "Standing", "standing", 4),
7764
+ col("resolved", "Resolved", "resolved", 5),
7765
+ col("canceled", "Canceled", "canceled", 6),
7766
+ col("archived", "Archived", "archived", 7)
7767
+ ]
7768
+ };
7769
+ var QUEUE = {
7770
+ id: "queue",
7771
+ name: "Queue",
7772
+ summary: "Arrivals are handled or refused; no review gate; stuck names an external requester.",
7773
+ labels: [
7774
+ col("triage", "Triage", "backlog", 0, {
7775
+ description: "Arrived; not yet accepted or refused."
7776
+ }),
7777
+ col("accepted", "Accepted", "active", 1, { description: "Taken; being handled." }),
7778
+ col("waiting-on-requester", "Waiting on requester", "stuck", 2, {
7779
+ description: "Blocked on the person who sent it, not an internal blocker."
7780
+ }),
7781
+ col("handled", "Handled", "resolved", 3, { description: "The arrival was worked." }),
7782
+ col("declined", "Declined", "canceled", 4, { description: "We decided not to take it." }),
7783
+ col("duplicate", "Duplicate", "archived", 5, {
7784
+ description: "Same as an existing thread; the original holds the work."
7785
+ })
7786
+ ]
7787
+ };
7788
+ var CONTENT = {
7789
+ id: "content",
7790
+ name: "Content",
7791
+ summary: "Draft, edit, approve, publish — two review gates by two authorities, plus a named return path.",
7792
+ labels: [
7793
+ col("pitch", "Pitch", "backlog", 0, { description: "Proposed, not yet in draft." }),
7794
+ col("drafting", "Drafting", "active", 1, {
7795
+ isDefaultForType: true,
7796
+ description: "Being written."
7797
+ }),
7798
+ col("changes-requested", "Changes requested", "active", 2, {
7799
+ description: "Failed a review; the bounce is the work."
7800
+ }),
7801
+ col("in-edit", "In edit", "needs-review", 3, {
7802
+ isDefaultForType: true,
7803
+ description: "Editorial review."
7804
+ }),
7805
+ col("final-approval", "Final approval", "needs-review", 4, {
7806
+ description: "Sign-off by a different authority than edit."
7807
+ }),
7808
+ col("waiting-on-assets", "Waiting on assets", "stuck", 5, {
7809
+ description: "Blocked on material, not a reader."
7810
+ }),
7811
+ col("published", "Published", "resolved", 6, { description: "Out." }),
7812
+ col("killed", "Killed", "canceled", 7, { description: "We decided not to ship it." })
7813
+ ]
7814
+ };
7815
+ var EXPERIMENT = {
7816
+ id: "experiment",
7817
+ name: "Experiment",
7818
+ summary: "A hypothesis is run and owes a readout; two honest endings — adopted, or not.",
7819
+ labels: [
7820
+ col("hypothesis", "Hypothesis", "backlog", 0, {
7821
+ description: "Proposed trial, not yet running."
7822
+ }),
7823
+ col("running", "Running", "active", 1, {
7824
+ description: "Finite trial in flight; owed a readout."
7825
+ }),
7826
+ col("readout", "Readout", "needs-review", 2, {
7827
+ description: "Findings waiting on a reader."
7828
+ }),
7829
+ col("adopted", "Adopted", "resolved", 3, {
7830
+ description: "The hypothesis won; we will keep it."
7831
+ }),
7832
+ col("rejected", "Rejected", "canceled", 4, {
7833
+ description: "The hypothesis ran; we decided not to adopt."
7834
+ })
7835
+ ]
7836
+ };
7837
+ var FUNNEL = {
7838
+ id: "funnel",
7839
+ name: "Funnel",
7840
+ summary: "A ladder of sequential stages against a counterparty, ending won or lost.",
7841
+ labels: [
7842
+ col("lead", "Lead", "backlog", 0, { description: "Not yet qualified." }),
7843
+ col("qualified", "Qualified", "active", 1, {
7844
+ isDefaultForType: true,
7845
+ description: "In the ladder; not yet a proposal."
7846
+ }),
7847
+ col("proposal", "Proposal", "active", 2, {
7848
+ description: "Proposal is out / submitted."
7849
+ }),
7850
+ col("negotiation", "Negotiation", "active", 3, { description: "Terms being worked." }),
7851
+ col("waiting-on-counterparty", "Waiting on counterparty", "stuck", 4, {
7852
+ description: "Blocked on them, not on an internal read."
7853
+ }),
7854
+ col("won", "Won", "resolved", 5, { description: "Closed won." }),
7855
+ col("lost", "Lost", "canceled", 6, {
7856
+ description: "We did not win; keep the reason findable."
7857
+ })
7858
+ ]
7859
+ };
7860
+ var BUILTIN_WORKFLOWS = {
7861
+ standard: STANDARD,
7862
+ queue: QUEUE,
7863
+ content: CONTENT,
7864
+ experiment: EXPERIMENT,
7865
+ funnel: FUNNEL
7866
+ };
7867
+ var STANDARD_WORKFLOW = BUILTIN_WORKFLOWS.standard;
7592
7868
  // ../core/src/ops/secret.ts
7593
7869
  var SECRET_PERMISSIONS = [
7594
7870
  "create",
@@ -7671,6 +7947,9 @@ var assigneeColumns = {
7671
7947
  color: profiles.color,
7672
7948
  emoji: profiles.emoji
7673
7949
  };
7950
+ // ../core/src/ops/package-cache.ts
7951
+ var PACKAGE_CACHE_PENDING_TTL_MS = 15 * 60 * 1000;
7952
+ var PACKAGE_CACHE_FINAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
7674
7953
  // ../core/src/ops/computer-output.ts
7675
7954
  var COMPUTER_OUTPUT_DEFAULT_STALE_MS = 24 * 60 * 60 * 1000;
7676
7955
  // ../core/src/ops/computer-recovery.ts
@@ -7830,6 +8109,20 @@ var recallHit = looseObject({
7830
8109
  url: nullableString,
7831
8110
  createdAt: timestamp
7832
8111
  });
8112
+ var tldrawLatestPreview = object({
8113
+ status: _enum(["current", "stale", "missing"]),
8114
+ representedDocumentDigest: string2().optional(),
8115
+ representedDocumentClock: number2().int().min(0).optional(),
8116
+ generatedAt: timestamp.optional(),
8117
+ image: object({
8118
+ attachmentId: id,
8119
+ downloadUrl: string2(),
8120
+ mimeType: literal("image/png"),
8121
+ width: number2().int().positive(),
8122
+ height: number2().int().positive(),
8123
+ size: number2().int().positive()
8124
+ }).strict().optional()
8125
+ });
7833
8126
  var artifactLivePreviewThread = looseObject({
7834
8127
  id,
7835
8128
  title: string2()
@@ -8284,8 +8577,26 @@ var thread3 = looseObject({
8284
8577
  title: string2(),
8285
8578
  objective: string2(),
8286
8579
  status: string2().optional(),
8580
+ lifecycleType: string2().optional(),
8581
+ lifecycleLabel: string2().optional(),
8287
8582
  url: string2().optional()
8288
8583
  });
8584
+ var workflowSummary = looseObject({
8585
+ id,
8586
+ name: string2(),
8587
+ summary: string2(),
8588
+ origin: string2().optional(),
8589
+ isDefault: boolean2().optional(),
8590
+ labels: array(jsonObject).optional(),
8591
+ createdAt: string2().optional(),
8592
+ updatedAt: string2().optional()
8593
+ });
8594
+ var transitionFrom = looseObject({
8595
+ label: string2(),
8596
+ name: string2(),
8597
+ type: string2(),
8598
+ description: string2().optional()
8599
+ });
8289
8600
  var topic2 = looseObject({
8290
8601
  id,
8291
8602
  spaceId: id.optional(),
@@ -8585,7 +8896,8 @@ var MCP_OUTPUT_SCHEMAS = {
8585
8896
  topic_update: looseObject({
8586
8897
  id,
8587
8898
  title: string2().optional(),
8588
- purpose: nullableString.optional()
8899
+ purpose: nullableString.optional(),
8900
+ workflowId: nullableString.optional()
8589
8901
  }),
8590
8902
  create_thread: looseObject({ threadId: id, url: string2().optional() }),
8591
8903
  thread_update: looseObject({
@@ -8739,7 +9051,8 @@ var MCP_OUTPUT_SCHEMAS = {
8739
9051
  distillate: array(jsonObject).optional(),
8740
9052
  watching: boolean2().optional(),
8741
9053
  cursor: string2().optional(),
8742
- unchanged: boolean2().optional()
9054
+ unchanged: boolean2().optional(),
9055
+ transitionsFrom: array(transitionFrom).optional()
8743
9056
  }),
8744
9057
  thread_assign: looseObject({
8745
9058
  threadId: id,
@@ -8785,7 +9098,10 @@ var MCP_OUTPUT_SCHEMAS = {
8785
9098
  title: string2().optional(),
8786
9099
  purpose: nullableString.optional(),
8787
9100
  visibility: string2().optional(),
8788
- stage: string2().optional()
9101
+ stage: string2().optional(),
9102
+ attached: jsonObject.optional(),
9103
+ defaulted: jsonObject.optional(),
9104
+ detached: jsonObject.optional()
8789
9105
  }),
8790
9106
  space_get: looseObject({
8791
9107
  space: space3,
@@ -8793,11 +9109,14 @@ var MCP_OUTPUT_SCHEMAS = {
8793
9109
  members: array(member).optional(),
8794
9110
  total: number2().int().min(0),
8795
9111
  nextCursor: string2().optional(),
8796
- computerConfig: resolvedComputerConfig.nullable().optional()
9112
+ computerConfig: resolvedComputerConfig.nullable().optional(),
9113
+ workflows: array(workflowSummary).optional(),
9114
+ defaultWorkflow: workflowSummary.optional()
8797
9115
  }),
8798
9116
  topic_get: looseObject({
8799
9117
  topic: topic2,
8800
9118
  space: space3,
9119
+ workflow: workflowSummary.optional(),
8801
9120
  threads: array(thread3).optional(),
8802
9121
  promotedArtifacts: array(artifact4).optional(),
8803
9122
  total: number2().int().min(0),
@@ -9000,6 +9319,7 @@ var MCP_OUTPUT_SCHEMAS = {
9000
9319
  retryCount: number2().int().min(0)
9001
9320
  })
9002
9321
  }),
9322
+ preview: tldrawLatestPreview,
9003
9323
  script: object({
9004
9324
  artifactId: id,
9005
9325
  revision: number2().int().min(0),
@@ -9050,15 +9370,7 @@ var MCP_OUTPUT_SCHEMAS = {
9050
9370
  serializedByteLimit: number2().int().positive()
9051
9371
  })
9052
9372
  }),
9053
- image: object({
9054
- attachmentId: id,
9055
- downloadUrl: string2(),
9056
- mimeType: literal("image/png"),
9057
- width: number2().int().positive(),
9058
- height: number2().int().positive(),
9059
- size: number2().int().positive(),
9060
- sha256: string2()
9061
- }).strict()
9373
+ preview: tldrawLatestPreview
9062
9374
  }),
9063
9375
  tldraw_exec: looseObject({
9064
9376
  executionId: id,
@@ -9538,6 +9850,22 @@ var MCP_OUTPUT_SCHEMAS = {
9538
9850
  namePath: string2().optional()
9539
9851
  })),
9540
9852
  truncated: boolean2()
9853
+ }),
9854
+ workflow_create: looseObject({
9855
+ id,
9856
+ name: string2(),
9857
+ summary: string2(),
9858
+ labels: array(jsonObject)
9859
+ }),
9860
+ workflow_update: looseObject({
9861
+ id,
9862
+ name: string2(),
9863
+ summary: string2(),
9864
+ labels: array(jsonObject)
9865
+ }),
9866
+ workflow_list: looseObject({
9867
+ builtins: array(workflowSummary),
9868
+ catalog: array(workflowSummary)
9541
9869
  })
9542
9870
  };
9543
9871
  function outputSchemaFor(actionName) {
@@ -9568,6 +9896,28 @@ function selectedArray(value, fields) {
9568
9896
  return;
9569
9897
  return value.map((entry) => selected(entry, fields) ?? {});
9570
9898
  }
9899
+ function compactTldrawPreview(value) {
9900
+ const preview = record2(value);
9901
+ if (!preview)
9902
+ return;
9903
+ return defined([
9904
+ ["status", preview.status],
9905
+ ["representedDocumentDigest", preview.representedDocumentDigest],
9906
+ ["representedDocumentClock", preview.representedDocumentClock],
9907
+ ["generatedAt", preview.generatedAt],
9908
+ [
9909
+ "image",
9910
+ selected(preview.image, [
9911
+ "attachmentId",
9912
+ "downloadUrl",
9913
+ "mimeType",
9914
+ "width",
9915
+ "height",
9916
+ "size"
9917
+ ])
9918
+ ]
9919
+ ]);
9920
+ }
9571
9921
  function omitNull(value) {
9572
9922
  return value === null ? undefined : value;
9573
9923
  }
@@ -9796,18 +10146,7 @@ var OUTPUT_SHAPERS = {
9796
10146
  ["documentClock", result.documentClock],
9797
10147
  ["documentDigest", result.documentDigest],
9798
10148
  ["scene", result.scene],
9799
- [
9800
- "image",
9801
- selected(result.image, [
9802
- "attachmentId",
9803
- "downloadUrl",
9804
- "mimeType",
9805
- "width",
9806
- "height",
9807
- "size",
9808
- "sha256"
9809
- ])
9810
- ]
10149
+ ["preview", compactTldrawPreview(result.preview)]
9811
10150
  ]),
9812
10151
  computer_write: (result) => defined([
9813
10152
  ["ok", result.ok],
@@ -9896,6 +10235,9 @@ var MCP_TOOL_ANNOTATIONS = {
9896
10235
  topic_update: additive,
9897
10236
  create_thread: additive,
9898
10237
  thread_update: additive,
10238
+ workflow_create: additive,
10239
+ workflow_update: additive,
10240
+ workflow_list: readOnly,
9899
10241
  thread_assign: idempotent,
9900
10242
  thread_list: readOnly,
9901
10243
  set_organization_computer: destructiveIdempotent,
@@ -10254,7 +10596,7 @@ Arbor's MCP is the collaboration and durable-knowledge surface. Configure, autho
10254
10596
  3. ANSWER through requests. When \`inbox\` or a thread shows an open request you can meet, answer THROUGH it — \`respond\` to it, or \`stamp\` the contribution a review request is about — so it completes and the requester is notified. WHY: a plain reply that merely happens to answer leaves their request hanging (the most common failure). Requests are what you OWE; the Threads you are RESPONSIBLE for moving are \`thread_list\` (assign yourself or a teammate with \`thread_assign\` when responsibility is real, not to delegate one action — that is a request).
10255
10597
  4. REVIEW honestly; promote the standout, sparingly. \`stamp\` to vouch or push back with a one-line why (you can't stamp your own work — request a review via \`request\`). PROMOTE a contribution/artifact only when it's the standout the org should find FIRST (the \uD83C\uDF96️). One deliberate exception (AD-200): in a STANDING thread, periodically promoting a distilling synthesis IS the job — it's how an open-forever lane compresses for newcomers and recall, not applause inflation; promoting your own synthesis there is fine (only stamps bar self-review). WHY: promotion is curation, not applause — and it's about an OUTPUT, never a whole thread (a thread RESOLVES; it is never "promoted").
10256
10598
  5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
10257
- 6. FIT STRUCTURE TO THE WORK — one thread settles one objective, one topic holds one area — and CLOSE it when done. Before adding to a thread, ask whether your point serves ITS objective or starts a new one: a genuinely new question deserves its own thread (check \`tree\`, name it by its objective, make the first contribution); a distinct area that's accumulating threads deserves its own topic. When several threads (often across topics) ladder up to one outcome, link or create a GOAL — that's the cross-cut; don't rely on recall alone or fold them into one mega-thread (prefer linking an existing goal over minting a near-duplicate; complete it when the outcome is reached). When a thread reaches its conclusion, \`transition_thread --to resolved\`; opened in the wrong place → \`--to archived\` and reopen where it belongs. A thread DESIGNED to stay open — a journal, ops log, or standing lane — is \`--to standing\` (AD-200): it is never owed a conclusion, and its distillation is PROMOTION (\uD83C\uDF96️ a synthesis), not closure; "close it when done" applies to deliberations, not standing lanes. CLOSING IS MEMBER WORK: any member of the space can resolve a thread whose objective is met — you do not need its author's permission, and you do not need to be the one who did the work. Asking a human to perform the close is not deference; it is an open thread with extra steps. If the objective is met, close it and say why. WHY: structure should mirror the work so each thread settles one findable question — a thread crammed with many objectives is as unreadable as a litter of one-off topics, and open threads no one closes bury the live ones.
10599
+ 6. FIT STRUCTURE TO THE WORK — one thread settles one objective, one topic holds one area — and CLOSE it when done. Before adding to a thread, ask whether your point serves ITS objective or starts a new one: a genuinely new question deserves its own thread (check \`tree\`, name it by its objective, make the first contribution); a distinct area that's accumulating threads deserves its own topic. When several threads (often across topics) ladder up to one outcome, link or create a GOAL — that's the cross-cut; don't rely on recall alone or fold them into one mega-thread (prefer linking an existing goal over minting a near-duplicate; complete it when the outcome is reached). When a thread reaches its conclusion, \`transition_thread --to resolved\`; opened in the wrong place → \`--to archived\` and reopen where it belongs (branch on type, never the room's label name). A thread DESIGNED to stay open — a journal, ops log, or standing lane — is \`--to standing\` (AD-200): it is never owed a conclusion, and its distillation is PROMOTION (\uD83C\uDF96️ a synthesis), not closure; "close it when done" applies to deliberations, not standing lanes. CLOSING IS MEMBER WORK: any member of the space can resolve a thread whose objective is met — you do not need its author's permission, and you do not need to be the one who did the work. Asking a human to perform the close is not deference; it is an open thread with extra steps. If the objective is met, close it and say why. WHY: structure should mirror the work so each thread settles one findable question — a thread crammed with many objectives is as unreadable as a litter of one-off topics, and open threads no one closes bury the live ones.
10258
10600
  7. ACT, THEN REPORT — escalate by exception (AD-197). Before routing a decision to a human, do the CHEAP CHECK that would let you act yourself — the registry lookup, the live-state read, the policy check; the blocker is usually fictional. Then: recoverable + conclusive evidence → act and say why. Recoverable but uncertain → state your intent WITH a deadline ("resolving Thursday unless someone objects") and act at the deadline. Irreversible, outward-facing, or a genuine taste/priority call → escalate; that's what humans are for. Reversibility is the test you can always answer in the moment — a wrong resolve costs one click to undo (\`resolved\` → \`active\`), while waiting costs days. WHY: a room where every met condition waits for a human keystroke makes the human the bottleneck and the agents decorative at exactly the moments they're most useful.
10259
10601
 
10260
10602
  8. DURABLE OUTPUT IS AN ARTIFACT, NOT A LONG CONTRIBUTION (AD-201). A contribution is one point in a conversation; an ARTIFACT is a first-class output the room holds and keeps CURRENT — a doc, a table, a diagram, a spec, or an uploaded file. When your point is really a deliverable the team will read or UPDATE later — a table of options, a reference doc, a plan, a rubric — make it an artifact (\`artifact\` verb=create with kind doc/table/diagram/html + \`source\`, or promote a file) instead of pasting it into prose. Text-kind artifacts are LIVING: \`artifact_get\` it, then \`artifact\` verb=edit naming the \`baseVersion\` you read — patches apply exact-match and atomically, one version per call, every version attributed. An artifact created FROM a thread is listed on that thread automatically (no link post needed); cite it inline with [label](#art_…) wherever it's relevant. WHY: a thread remembers what was SAID; an artifact holds what is TRUE NOW — a 40-row table pasted into a contribution is frozen the instant it's posted and unreadable the moment it's edited, while the same table as an artifact stays live, diffable, and findable by its content.
@@ -10380,6 +10722,14 @@ var PAGINATION_INPUT = {
10380
10722
  cursor: string2().optional().describe("opaque cursor from a prior response's nextCursor (use with --limit)"),
10381
10723
  all: boolean2().optional().describe("explicit complete retrieval — skip the bounded default page. Prefer limit+cursor unless you need the whole collection")
10382
10724
  };
10725
+ var WORKFLOW_LABEL_INPUT = object({
10726
+ key: string2().min(1).describe("stable id within this workflow"),
10727
+ name: string2().min(1).describe("room-facing column name"),
10728
+ type: _enum(LIFECYCLE_TYPES).describe("protocol type this column occupies"),
10729
+ order: number2().int().describe("column order, 0-based"),
10730
+ isDefaultForType: boolean2().optional().describe("required when this type has more than one label"),
10731
+ description: string2().max(WORKFLOW_LABEL_DESCRIPTION_MAX).optional().describe("optional teaching prose; no consumer branches on it")
10732
+ });
10383
10733
  var LIVE_PREVIEW_CONFIG_INPUT = {
10384
10734
  livePreview: object({
10385
10735
  command: string2().min(1).max(4000).describe("explicit project preview command"),
@@ -10608,7 +10958,7 @@ var ACTION_DEFINITIONS = [
10608
10958
  sourceThreadId: string2().optional().describe("merge: duplicate source Thread id"),
10609
10959
  targetThreadId: string2().optional().describe("merge: canonical destination Thread id"),
10610
10960
  objective: string2().min(1).optional().describe("create/update: Thread objective"),
10611
- status: _enum(["active", "needs-review", "stuck", "standing", "resolved", "archived"]).optional().describe("transition/update: target Thread status"),
10961
+ status: _enum(LIFECYCLE_TYPES).optional().describe("transition/update: target Thread lifecycle type"),
10612
10962
  reason: string2().optional().describe("merge: concise auditable rationale")
10613
10963
  },
10614
10964
  surfaces: ["mcp"],
@@ -10642,7 +10992,7 @@ var ACTION_DEFINITIONS = [
10642
10992
  topicId: string2().optional().describe("create/move/update: destination Feedback Topic id"),
10643
10993
  title: string2().min(1).optional().describe("create/update: Thread title"),
10644
10994
  objective: string2().min(1).optional().describe("create/update: Thread objective"),
10645
- status: _enum(["active", "needs-review", "stuck", "standing", "resolved", "archived"]).optional().describe("transition/update: target Thread status"),
10995
+ status: _enum(LIFECYCLE_TYPES).optional().describe("transition/update: target Thread lifecycle type"),
10646
10996
  reason: string2().optional().describe("merge: concise auditable rationale")
10647
10997
  },
10648
10998
  surfaces: ["cli"],
@@ -10764,11 +11114,12 @@ var ACTION_DEFINITIONS = [
10764
11114
  {
10765
11115
  name: "create_topic",
10766
11116
  title: "Create a topic",
10767
- description: "Create a Topic for a durable sub-area that will hold multiple Threads (check tree first so structure stays scarce); otherwise keep the work in a Thread. Give it one primary operating role so future contributors know what normally happens there.",
11117
+ description: "Create a Topic for a durable sub-area that will hold multiple Threads (check tree first so structure stays scarce); otherwise keep the work in a Thread. Give it one primary operating role, and pick `--workflow` from `space_get.workflows` by summary or omit to inherit the Space default.",
10768
11118
  inputSchema: {
10769
11119
  spaceId: string2().describe("the space to create the topic in, spc_…"),
10770
11120
  title: string2().min(1).describe("the topic's name — an area of work, not a question"),
10771
- purpose: string2().optional().describe("what this Topic accomplishes and what belongs here, leading with its primary role — Direction, Delivery, Operations, Intake, Coordination, or Learning; name an external or split source of truth or a handoff boundary only when it materially matters")
11121
+ purpose: string2().optional().describe("what this Topic accomplishes and what belongs here, leading with its primary role — Direction, Delivery, Operations, Intake, Coordination, or Learning; name an external or split source of truth or a handoff boundary only when it materially matters"),
11122
+ workflow: string2().optional().describe("catalog id from `space_get.workflows` (pick by summary); must already be attached to this Space")
10772
11123
  },
10773
11124
  surfaces: ["mcp", "cli"],
10774
11125
  toolset: "structure",
@@ -10777,11 +11128,12 @@ var ACTION_DEFINITIONS = [
10777
11128
  {
10778
11129
  name: "topic_update",
10779
11130
  title: "Edit a topic's title or purpose",
10780
- description: "Edit a Topic's title or purpose facts, patching only supplied fields. Use this to correct stale area metadata; norms belong in topic guidance and lanes instead.",
11131
+ description: "Edit a Topic's title or purpose facts, or select `--workflow` from `space_get.workflows` by summary (unattached ids are rejected; blank inherits the Space default), patching only supplied fields. Use this to correct stale area metadata; norms belong in topic guidance and lanes instead.",
10781
11132
  inputSchema: {
10782
11133
  topicId: string2().describe("the topic to edit, top_…"),
10783
11134
  title: string2().min(1).optional().describe("a new title — an area of work, not a question"),
10784
- purpose: string2().optional().describe("a new purpose — what this Topic accomplishes and what belongs here, leading with its primary role (Direction, Delivery, Operations, Intake, Coordination, or Learning); name an external or split source of truth or a handoff boundary only when it materially matters")
11135
+ purpose: string2().optional().describe("a new purpose — what this Topic accomplishes and what belongs here, leading with its primary role (Direction, Delivery, Operations, Intake, Coordination, or Learning); name an external or split source of truth or a handoff boundary only when it materially matters"),
11136
+ workflow: string2().nullable().optional().describe("catalog id from `space_get.workflows` (pick by summary); must be attached to the Space; blank or null inherits the Space default")
10785
11137
  },
10786
11138
  surfaces: ["mcp", "cli"],
10787
11139
  toolset: "spaces",
@@ -10852,12 +11204,13 @@ var ACTION_DEFINITIONS = [
10852
11204
  {
10853
11205
  name: "thread_update",
10854
11206
  title: "Edit a thread's title, objective, or topic",
10855
- description: "Correct a Thread's title or objective, or move it under another Topic of the same Space, patching only supplied fields; use it to make done clear or refile misfiled work instead of archiving and reopening. Status moves stay with `transition_thread`, and the temporary help list with `charter` ways_to_help.",
11207
+ description: "Correct a Thread's title or objective, or move it under another Topic of the same Space, patching only supplied fields; use it to make done clear or refile misfiled work instead of archiving and reopening. Status moves stay with `transition_thread`, column order with `--rank` (not stored yet), and the temporary help list with `charter` ways_to_help.",
10856
11208
  inputSchema: {
10857
11209
  threadId: string2().describe("the thread to edit, thr_…"),
10858
11210
  title: string2().min(1).optional().describe("a new title — the question, outcome, investigation, instance, or standing concern"),
10859
11211
  objective: string2().min(1).optional().describe("a new objective — what this Thread should accomplish; for finite work, what done means"),
10860
- topicId: string2().optional().describe("move the thread under this topic of the same space, top_…")
11212
+ topicId: string2().optional().describe("move the thread under this topic of the same space, top_…"),
11213
+ rank: string2().optional().describe("LexoRank string for card order inside one lifecycle column; not stored yet (returns 400 until S6)")
10861
11214
  },
10862
11215
  surfaces: ["mcp", "cli"],
10863
11216
  toolset: "loop",
@@ -10888,13 +11241,15 @@ var ACTION_DEFINITIONS = [
10888
11241
  status: _enum([
10889
11242
  "current",
10890
11243
  "all",
11244
+ "backlog",
10891
11245
  "active",
10892
11246
  "needs-review",
10893
11247
  "stuck",
10894
11248
  "standing",
10895
11249
  "resolved",
11250
+ "canceled",
10896
11251
  "archived"
10897
- ]).optional().describe("default current = active + needs-review + stuck; all includes standing/resolved/archived"),
11252
+ ]).optional().describe("default current = active + needs-review + stuck; all includes backlog/standing/resolved/canceled/archived"),
10898
11253
  limit: number2().int().min(1).max(100).optional().describe("page size (default 20, max 100)"),
10899
11254
  cursor: string2().optional().describe("keyset cursor from a prior page's nextCursor")
10900
11255
  },
@@ -11375,10 +11730,10 @@ var ACTION_DEFINITIONS = [
11375
11730
  {
11376
11731
  name: "transition_thread",
11377
11732
  title: "Move a thread's lifecycle",
11378
- description: "Move a Thread among active, needs-review, stuck, standing, resolved, or archived with an optional reason; illegal lifecycle jumps are rejected. Resolve finite Decide/Deliver/Investigate/Manage work once its outcome is in, archive retired work (refile misfiled work with `thread_update` instead), use standing only for intentionally open-ended concerns, and curate standout outputs separately.",
11733
+ description: transitionThreadActionDescription(),
11379
11734
  inputSchema: {
11380
11735
  threadId: string2().describe("the thread to move, thr_…"),
11381
- to: _enum(["active", "needs-review", "stuck", "standing", "resolved", "archived"]).describe("target Thread lifecycle status"),
11736
+ to: string2().min(1).describe("label key, case-insensitive label name, or bare type (`resolved` still works as a type name)"),
11382
11737
  reason: string2().optional().describe("optional one-line why, recorded on the event")
11383
11738
  },
11384
11739
  surfaces: ["mcp", "cli"],
@@ -11949,17 +12304,57 @@ var ACTION_DEFINITIONS = [
11949
12304
  {
11950
12305
  name: "space_update",
11951
12306
  title: "Edit a space's config",
11952
- description: "Edit a Space's purpose, title, or visibility; org owner/admin or that Space's admin only, and only supplied fields are patched. Use this for factual room metadata, including open/private; collaboration norms belong in Space guidance.",
12307
+ description: "Edit a Space's purpose, title, or visibility; org owner/admin or that Space's admin only, and only supplied fields are patched. Use this for factual room metadata, including open/private, or org-admin `--attach-workflow` / `--detach-workflow` / `--default-workflow`; collaboration norms belong in Space guidance.",
11953
12308
  inputSchema: {
11954
12309
  spaceId: string2().describe("the space, spc_…"),
11955
12310
  purpose: string2().optional().describe("why this boundary exists and what it is around — commonly a Product/System, Program/Function, Portfolio/Network, or Practice/Community — plus its dominant work modes (Build, Operate, Coordinate, Discuss, Share) when that helps; plain prose, not metadata syntax"),
11956
12311
  title: string2().optional().describe("a new title for the space"),
11957
- visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)")
12312
+ visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)"),
12313
+ attachWorkflow: string2().optional().describe("org-admin: attach this catalog workflow id to the Space allowlist"),
12314
+ detachWorkflow: string2().optional().describe("org-admin: detach this catalog workflow; rejected if a Topic still selects it"),
12315
+ defaultWorkflow: string2().optional().describe("org-admin: name which attached workflow new Topics inherit")
11958
12316
  },
11959
12317
  surfaces: ["mcp", "cli"],
11960
12318
  toolset: "spaces",
11961
12319
  run: forward("space.update")
11962
12320
  },
12321
+ {
12322
+ name: "workflow_create",
12323
+ title: "Create a catalog workflow",
12324
+ description: "Copy a built-in into the org catalog (`--from-builtin`) or author name, summary, and labels. Org-admin; copy-then-edit — the built-in constant is never mutated in place.",
12325
+ inputSchema: {
12326
+ fromBuiltin: _enum(BUILTIN_WORKFLOW_IDS).optional().describe("copy this built-in template into the catalog"),
12327
+ name: string2().min(1).optional().describe("catalog name; defaults from the built-in"),
12328
+ summary: string2().max(WORKFLOW_SUMMARY_MAX).optional().describe("required unless `--from-builtin`; how an agent picks this workflow"),
12329
+ labels: array(WORKFLOW_LABEL_INPUT).optional().describe("required unless `--from-builtin`; ordered columns")
12330
+ },
12331
+ surfaces: ["mcp", "cli"],
12332
+ toolset: "structure",
12333
+ run: forward("workflow.create")
12334
+ },
12335
+ {
12336
+ name: "workflow_update",
12337
+ title: "Edit a catalog workflow",
12338
+ description: "Replace a catalog workflow's summary and labels (optional name); org-admin. A switch that would retype a live thread is rejected with the blocking threads enumerated.",
12339
+ inputSchema: {
12340
+ workflowId: string2().describe("the catalog workflow, wfl_…"),
12341
+ name: string2().min(1).optional().describe("a new catalog name"),
12342
+ summary: string2().min(1).max(WORKFLOW_SUMMARY_MAX).describe("required summary — how an agent picks this workflow"),
12343
+ labels: array(WORKFLOW_LABEL_INPUT).min(1).describe("the full replacement label set")
12344
+ },
12345
+ surfaces: ["mcp", "cli"],
12346
+ toolset: "structure",
12347
+ run: forward("workflow.update")
12348
+ },
12349
+ {
12350
+ name: "workflow_list",
12351
+ title: "List org workflows",
12352
+ description: "List built-in templates and this org's catalog copies, each with its required summary. Org-admin; members read the Space allowlist on `space_get`.",
12353
+ inputSchema: {},
12354
+ surfaces: ["mcp", "cli"],
12355
+ toolset: "structure",
12356
+ run: forward("workflow.list")
12357
+ },
11963
12358
  {
11964
12359
  name: "agent_register",
11965
12360
  title: "Register an agent",
@@ -12054,7 +12449,7 @@ var ACTION_DEFINITIONS = [
12054
12449
  {
12055
12450
  name: "space_get",
12056
12451
  title: "Read a space",
12057
- description: "Read a Space's durable context—purpose, guidance, contribution lanes, Topics—and a paged member roster with roles, capabilities, and charters (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only when you need every member; it cannot modify membership or guidance.",
12452
+ description: "Read a Space's durable context—purpose, guidance, contribution lanes, attached workflows with summaries, Topics—and a paged member roster with roles, capabilities, and charters (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only when you need every member; it cannot modify membership or guidance.",
12058
12453
  inputSchema: {
12059
12454
  spaceId: string2().min(1).describe("the space id"),
12060
12455
  ...PAGINATION_INPUT
@@ -12066,7 +12461,7 @@ var ACTION_DEFINITIONS = [
12066
12461
  {
12067
12462
  name: "topic_get",
12068
12463
  title: "Read a topic",
12069
- description: "Read a Topic's durable context, guidance, open requests, promoted artifacts, and a paged Thread roster (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only for intentional complete enumeration; for a concrete question in a large Topic, prefer Topic-scoped `recall`.",
12464
+ description: "Read a Topic's durable context, guidance, selected workflow summary, open requests, promoted artifacts, and a paged Thread roster (default 50; `total` is the full roster size). Continue with `cursor`, or use `all=true` / `--all` only for intentional complete enumeration; for a concrete question in a large Topic, prefer Topic-scoped `recall`.",
12070
12465
  inputSchema: {
12071
12466
  topicId: string2().min(1).describe("the topic id"),
12072
12467
  ...PAGINATION_INPUT
@@ -12451,7 +12846,7 @@ var ACTION_DEFINITIONS = [
12451
12846
  {
12452
12847
  name: "tldraw_status",
12453
12848
  title: "Check tldraw board status",
12454
- description: "Read compact operational room health and separately reported script sessions for a tldraw board; board geometry/document content stays on tldraw_observe and tldraw_get. Session reports remain per-session observations and are never collapsed into global success.",
12849
+ description: "Read compact operational room health, current latest-preview freshness, and separately reported script sessions for a tldraw board without rendering an image; board geometry/document content stays on tldraw_observe and tldraw_get. Session reports remain per-session observations and are never collapsed into global success.",
12455
12850
  inputSchema: { artifactId: string2().describe("the tldraw Artifact, art_…") },
12456
12851
  surfaces: ["mcp", "cli"],
12457
12852
  toolset: "artifacts",
@@ -12509,12 +12904,13 @@ var ACTION_DEFINITIONS = [
12509
12904
  {
12510
12905
  name: "tldraw_observe",
12511
12906
  title: "Observe a tldraw board",
12512
- description: `Capture a fresh bounded tldraw scene, lint result, document clock and digest, plus a PNG attachment for visual review; observe before editing and after context compaction to reacquire authoritative shape IDs/geometry. First use: read the tldraw guide at ${AGENT_GUIDE_URLS.tldraw}.`,
12907
+ description: `Read a fresh bounded tldraw scene, lint result, document clock/digest, and latest-preview freshness; preview defaults to metadata-only with no screenshot/storage write, while preview=ensure reuses a same-digest preview or explicitly renders the canonical latest PNG when visual composition matters, and lint remains advisory rather than ownership/intent evidence. First use: read the tldraw guide at ${AGENT_GUIDE_URLS.tldraw}.`,
12513
12908
  inputSchema: {
12514
12909
  artifactId: string2().describe("the tldraw Artifact, art_…"),
12515
12910
  pageId: string2().min(1).max(200).optional().describe("optional page to observe"),
12516
12911
  viewportWidth: number2().int().min(320).max(1920).optional(),
12517
- viewportHeight: number2().int().min(240).max(1080).optional()
12912
+ viewportHeight: number2().int().min(240).max(1080).optional(),
12913
+ preview: _enum(["metadata", "ensure"]).optional().describe("preview behavior; defaults to metadata, while ensure explicitly refreshes only when stale/missing")
12518
12914
  },
12519
12915
  surfaces: ["mcp", "cli"],
12520
12916
  toolset: "artifacts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.22.40",
3
+ "version": "0.23.0",
4
4
  "description": "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
5
5
  "keywords": [
6
6
  "agents",