@lambdacurry/arbor 0.24.34 → 0.24.36

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 +174 -34
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -6955,6 +6955,7 @@ function json(params) {
6955
6955
  return jsonSchema;
6956
6956
  }
6957
6957
  // ../core/src/objects/types.ts
6958
+ var TOPIC_LIFECYCLES = ["planned", "open", "on-hold", "closed", "archived"];
6958
6959
  var IDENTITY_COLORS = [
6959
6960
  "indigo",
6960
6961
  "teal",
@@ -8562,6 +8563,7 @@ var topics = sqliteTable("topics", {
8562
8563
  workflowId: text("workflow_id"),
8563
8564
  visibility: text("visibility").$type().notNull().default("open"),
8564
8565
  ownerProfileId: text("owner_profile_id").references(() => profiles.id),
8566
+ lifecycle: text("lifecycle").$type().notNull().default("open"),
8565
8567
  archivedAt: ts("archived_at"),
8566
8568
  createdAt: ts("created_at").notNull()
8567
8569
  }, (t) => ({
@@ -10217,6 +10219,9 @@ var ORG_ADMIN_CONTAINMENT_PERMISSIONS = new Set([
10217
10219
  "revoke"
10218
10220
  ]);
10219
10221
 
10222
+ // ../core/src/ops/topic-lifecycle.ts
10223
+ var INITIAL_TOPIC_LIFECYCLES = ["planned", "open"];
10224
+
10220
10225
  // ../core/src/queries/artifact-base.ts
10221
10226
  var eventChronology = sql`_rowid_`;
10222
10227
  var SPACE_ARTIFACT_STATUSES = [
@@ -11121,13 +11126,17 @@ function buildOutputSchemas() {
11121
11126
  type: string2(),
11122
11127
  description: string2().optional()
11123
11128
  });
11129
+ const topicLifecycle = _enum(TOPIC_LIFECYCLES);
11130
+ const removedTopicStatus = never().optional();
11124
11131
  const topic = looseObject({
11125
11132
  id,
11126
11133
  spaceId: id.optional(),
11127
11134
  title: string2(),
11128
11135
  purpose: nullableString.optional(),
11129
11136
  url: string2().optional(),
11130
- workflowId: nullableString.optional()
11137
+ workflowId: nullableString.optional(),
11138
+ lifecycle: topicLifecycle.optional(),
11139
+ status: removedTopicStatus
11131
11140
  });
11132
11141
  const space = looseObject({
11133
11142
  id,
@@ -11285,7 +11294,8 @@ function buildOutputSchemas() {
11285
11294
  purpose: string2().optional(),
11286
11295
  threadCount: number2(),
11287
11296
  activeThreadCount: number2(),
11288
- status: _enum(["healthy", "attention", "stuck"]),
11297
+ lifecycle: topicLifecycle,
11298
+ status: removedTopicStatus,
11289
11299
  openRequestCount: number2().optional(),
11290
11300
  lastActivityAt: timestamp,
11291
11301
  lanesForYou: array(string2()).optional(),
@@ -11437,13 +11447,19 @@ function buildOutputSchemas() {
11437
11447
  reacted: boolean2(),
11438
11448
  reactions: array(reactionCount)
11439
11449
  }),
11440
- create_topic: looseObject({ topicId: id, url: string2().optional() }),
11450
+ create_topic: looseObject({
11451
+ topicId: id,
11452
+ url: string2().optional(),
11453
+ lifecycle: topicLifecycle.optional()
11454
+ }),
11441
11455
  topic_update: looseObject({
11442
11456
  id,
11443
11457
  title: string2().optional(),
11444
11458
  purpose: nullableString.optional(),
11445
11459
  workflowId: nullableString.optional(),
11446
- visibility: _enum(["open", "private"]).optional()
11460
+ visibility: _enum(["open", "private"]).optional(),
11461
+ lifecycle: topicLifecycle.optional(),
11462
+ archivedAt: nullableString.optional()
11447
11463
  }),
11448
11464
  create_thread: looseObject({ threadId: id, url: string2().optional() }),
11449
11465
  thread_update: looseObject({
@@ -11723,7 +11739,8 @@ function buildOutputSchemas() {
11723
11739
  }),
11724
11740
  space_get: looseObject({
11725
11741
  space,
11726
- topics: array(topic).optional(),
11742
+ topics: array(topic.extend({ lifecycle: topicLifecycle })).optional(),
11743
+ archivedTopics: array(looseObject({ id, title: string2(), lifecycle: literal("archived") })).optional(),
11727
11744
  members: array(member).optional(),
11728
11745
  total: number2().int().min(0),
11729
11746
  nextCursor: string2().optional(),
@@ -11732,7 +11749,8 @@ function buildOutputSchemas() {
11732
11749
  defaultWorkflow: workflowSummary.optional()
11733
11750
  }),
11734
11751
  topic_get: looseObject({
11735
- topic,
11752
+ topic: topic.extend({ lifecycle: topicLifecycle }),
11753
+ lifecycleChoices: array(topicLifecycle).optional(),
11736
11754
  space,
11737
11755
  workflow: workflowSummary.optional(),
11738
11756
  threads: array(thread).optional(),
@@ -12025,6 +12043,46 @@ function buildOutputSchemas() {
12025
12043
  serializedByteLimit: number2().int().positive()
12026
12044
  })
12027
12045
  }),
12046
+ presence: discriminatedUnion("status", [
12047
+ object({ status: literal("absent") }).strict(),
12048
+ object({ status: literal("unavailable") }).strict(),
12049
+ looseObject({
12050
+ status: literal("present"),
12051
+ sessions: number2().int().positive(),
12052
+ pageId: string2().nullable(),
12053
+ viewport: jsonObject.nullable(),
12054
+ lastActiveAt: timestamp.nullable(),
12055
+ selection: looseObject({
12056
+ shapeIds: array(string2()),
12057
+ shapes: array(jsonObject),
12058
+ missingShapeIds: array(string2()),
12059
+ clusterIds: array(string2()),
12060
+ bounds: jsonObject.nullable()
12061
+ })
12062
+ })
12063
+ ]).optional(),
12064
+ comments: looseObject({
12065
+ items: array(looseObject({
12066
+ contributionId: id,
12067
+ url: string2(),
12068
+ author: jsonObject,
12069
+ createdAt: timestamp,
12070
+ text: string2(),
12071
+ anchor: jsonObject,
12072
+ replyCount: number2().int().nonnegative(),
12073
+ replies: array(jsonObject)
12074
+ })),
12075
+ total: number2().int().nonnegative(),
12076
+ omitted: number2().int().nonnegative()
12077
+ }).optional(),
12078
+ screenshot: looseObject({
12079
+ mimeType: literal("image/png"),
12080
+ width: number2().int().positive(),
12081
+ height: number2().int().positive(),
12082
+ bounds: jsonObject,
12083
+ shapeCount: number2().int().positive(),
12084
+ data: string2().optional()
12085
+ }).optional(),
12028
12086
  preview: tldrawLatestPreview
12029
12087
  }),
12030
12088
  tldraw_exec: looseObject({
@@ -12783,7 +12841,8 @@ var OUTPUT_SHAPERS = {
12783
12841
  ]),
12784
12842
  create_topic: (result) => defined([
12785
12843
  ["topicId", result.id],
12786
- ["url", result.url]
12844
+ ["url", result.url],
12845
+ ["lifecycle", result.lifecycle]
12787
12846
  ]),
12788
12847
  create_thread: (result) => defined([
12789
12848
  ["threadId", result.id],
@@ -13016,6 +13075,9 @@ var OUTPUT_SHAPERS = {
13016
13075
  ["documentClock", result.documentClock],
13017
13076
  ["documentDigest", result.documentDigest],
13018
13077
  ["scene", result.scene],
13078
+ ["presence", result.presence],
13079
+ ["comments", result.comments],
13080
+ ["screenshot", result.screenshot],
13019
13081
  ["preview", compactTldrawPreview(result.preview)]
13020
13082
  ]),
13021
13083
  computer_write: (result) => defined([
@@ -13294,12 +13356,37 @@ var shapeId = TLDRAW_SHAPE_ID_SCHEMA.describe("stable tldraw shape id, shape:…
13294
13356
  var shapeType = _enum(["geo", "text", "note", "frame", "arrow", "line", "draw", "group"]).describe("built-in tldraw shape type; use code mode for custom or asset-backed shapes");
13295
13357
  var finite = number2().finite();
13296
13358
  var jsonRecord = record(string2(), unknown());
13359
+ var diagramKey = string2().min(1).max(80).describe("caller-meaningful node key; returned as the alias for its generated tldraw shape id");
13360
+ var pageIdFilter = string2().min(1).max(200).describe("optional page id to search");
13361
+ var textRef = object({
13362
+ text: string2().min(1).max(500),
13363
+ match: _enum(["exact", "contains"]).optional().describe("exact (default, case/whitespace-insensitive) or contains"),
13364
+ type: string2().min(1).max(64).optional().describe("optional shape type filter"),
13365
+ pageId: pageIdFilter.optional()
13366
+ }).strict().describe("the one existing shape whose text matches");
13367
+ var descriptionRef = object({ description: string2().min(1).max(200), pageId: pageIdFilter.optional() }).strict().describe(`the one existing shape a description like 'the blue box labelled "API" in the top right' names (quoted label, kind, colour, region are strict filters)`);
13368
+ var selectedRef = object({ selected: literal(true) }).strict().describe("your one currently selected shape in an open board tab");
13369
+ var createdRef = object({
13370
+ created: union([literal("last"), number2().int().min(0).max(TLDRAW_EXEC_MAX_OPERATIONS)])
13371
+ }).strict().describe('a shape created earlier in this batch: "last" or its 0-based create index');
13372
+ var keyRef = object({ key: diagramKey }).strict().describe("a diagram node created earlier in this batch, by its key");
13373
+ var TLDRAW_SHAPE_REF_SCHEMA = union([shapeId, textRef, descriptionRef, selectedRef, createdRef, keyRef]).describe("shape id, or { text } | { description } | { selected: true } | { created } | { key }; resolved before any edit, ambiguity refuses");
13374
+ var shapeRef = TLDRAW_SHAPE_REF_SCHEMA;
13375
+ var selectedAllRef = object({ selected: literal("all") }).strict().describe("every currently selected shape in your open board tab");
13376
+ var shapeRefs = array(union([shapeId, textRef, descriptionRef, selectedRef, createdRef, keyRef, selectedAllRef])).min(1).max(TLDRAW_EXEC_MAX_OPERATIONS);
13377
+ var place = object({
13378
+ relativeTo: shapeRef.describe("the existing or earlier-created shape to place beside"),
13379
+ side: _enum(["right", "left", "above", "below"]),
13380
+ gap: finite.min(0).max(1e4).optional().describe("edge-to-edge gap; defaults to 40"),
13381
+ align: _enum(["start", "center", "end"]).optional().describe("cross-axis alignment with the target; defaults to center")
13382
+ }).strict().describe("place the new shape beside another instead of giving x/y");
13297
13383
  var create = object({
13298
13384
  op: literal("create"),
13299
13385
  id: shapeId,
13300
13386
  type: shapeType,
13301
- x: finite,
13302
- y: finite,
13387
+ x: finite.optional().describe("page x; omit only when place is given"),
13388
+ y: finite.optional().describe("page y; omit only when place is given"),
13389
+ place: place.optional(),
13303
13390
  parentId: string2().min(1).max(200).optional(),
13304
13391
  rotation: finite.optional(),
13305
13392
  opacity: finite.min(0).max(1).optional(),
@@ -13309,8 +13396,8 @@ var create = object({
13309
13396
  }).strict();
13310
13397
  var update = object({
13311
13398
  op: literal("update"),
13312
- id: shapeId,
13313
- type: shapeType,
13399
+ id: shapeRef,
13400
+ type: shapeType.optional().describe("optional; read from the resolved shape when omitted"),
13314
13401
  x: finite.optional(),
13315
13402
  y: finite.optional(),
13316
13403
  parentId: string2().min(1).max(200).optional(),
@@ -13320,20 +13407,18 @@ var update = object({
13320
13407
  props: jsonRecord.optional().describe("partial shape properties"),
13321
13408
  meta: jsonRecord.optional()
13322
13409
  }).strict();
13323
- var shapeIds = array(shapeId).min(1).max(TLDRAW_EXEC_MAX_OPERATIONS);
13324
- var diagramKey = string2().min(1).max(80).describe("caller-meaningful node key; returned as the alias for its generated tldraw shape id");
13325
13410
  var diagramLabel = string2().min(1).max(160);
13326
13411
  var stack = object({
13327
13412
  op: literal("stack"),
13328
- ids: shapeIds.min(2).describe("native shapes in the exact stack order; first id is the anchor"),
13413
+ ids: shapeRefs.describe("shapes in the exact stack order; the first is the anchor (at least 2)"),
13329
13414
  axis: _enum(["horizontal", "vertical"]),
13330
13415
  gap: finite.min(0).describe("required edge-to-edge gap between adjacent shapes"),
13331
13416
  alignment: _enum(["start", "center", "end"]).optional().describe("optional cross-axis alignment relative to the first shape")
13332
13417
  }).strict();
13333
13418
  var connect = object({
13334
13419
  op: literal("connect"),
13335
- sourceId: shapeId.describe("existing native shape to bind the arrow start to"),
13336
- targetId: shapeId.describe("existing native shape to bind the arrow end to")
13420
+ sourceId: shapeRef.describe("existing native shape to bind the arrow start to"),
13421
+ targetId: shapeRef.describe("existing native shape to bind the arrow end to")
13337
13422
  }).strict();
13338
13423
  var diagram = object({
13339
13424
  op: literal("diagram"),
@@ -13351,26 +13436,32 @@ var diagram = object({
13351
13436
  var TLDRAW_EXEC_OPERATION_SCHEMA = discriminatedUnion("op", [
13352
13437
  create,
13353
13438
  update,
13354
- object({ op: literal("delete"), ids: shapeIds }).strict(),
13439
+ object({ op: literal("delete"), ids: shapeRefs }).strict(),
13355
13440
  object({
13356
13441
  op: literal("align"),
13357
- ids: shapeIds.min(2),
13442
+ ids: shapeRefs.describe("at least 2 shapes"),
13358
13443
  alignment: _enum(["bottom", "center-horizontal", "center-vertical", "left", "right", "top"])
13359
13444
  }).strict(),
13360
13445
  object({
13361
13446
  op: literal("distribute"),
13362
- ids: shapeIds.min(3),
13447
+ ids: shapeRefs.describe("at least 3 shapes"),
13363
13448
  axis: _enum(["horizontal", "vertical"])
13364
13449
  }).strict(),
13365
13450
  object({
13366
13451
  op: literal("group"),
13367
- ids: shapeIds.min(2),
13452
+ ids: shapeRefs.describe("at least 2 shapes"),
13368
13453
  groupId: shapeId.optional()
13369
13454
  }).strict(),
13370
13455
  stack,
13371
13456
  connect,
13372
13457
  diagram
13373
13458
  ]);
13459
+ var MIN_REFERENCE_COUNT = {
13460
+ align: 2,
13461
+ distribute: 3,
13462
+ group: 2,
13463
+ stack: 2
13464
+ };
13374
13465
  var TLDRAW_EXEC_OPERATIONS_SCHEMA = array(TLDRAW_EXEC_OPERATION_SCHEMA).min(1).max(TLDRAW_EXEC_MAX_OPERATIONS).superRefine((operations, ctx) => {
13375
13466
  const createdIds = new Set;
13376
13467
  const diagramKeys = new Set;
@@ -13387,20 +13478,43 @@ var TLDRAW_EXEC_OPERATIONS_SCHEMA = array(TLDRAW_EXEC_OPERATION_SCHEMA).min(1).m
13387
13478
  }
13388
13479
  createdIds.add(operation.id);
13389
13480
  }
13481
+ if (operation.op === "create") {
13482
+ const hasPoint = operation.x !== undefined || operation.y !== undefined;
13483
+ if (operation.place ? hasPoint : operation.x === undefined || operation.y === undefined) {
13484
+ ctx.addIssue({
13485
+ code: "custom",
13486
+ message: "create needs both x and y, or place instead of x/y",
13487
+ path: [index, operation.place ? "place" : "x"]
13488
+ });
13489
+ }
13490
+ }
13390
13491
  if ("ids" in operation) {
13391
13492
  const seen = new Set;
13392
13493
  operation.ids.forEach((id, idIndex) => {
13393
- if (seen.has(id)) {
13494
+ const key = typeof id === "string" ? id : JSON.stringify(id);
13495
+ if (seen.has(key)) {
13394
13496
  ctx.addIssue({
13395
13497
  code: "custom",
13396
- message: `duplicate shape id: ${id}`,
13498
+ message: `duplicate shape reference: ${key}`,
13397
13499
  path: [index, "ids", idIndex]
13398
13500
  });
13399
13501
  }
13400
- seen.add(id);
13502
+ seen.add(key);
13401
13503
  });
13504
+ const minimum = MIN_REFERENCE_COUNT[operation.op] ?? 1;
13505
+ const expands = operation.ids.some((id) => typeof id === "object" && ("selected" in id) && id.selected === "all");
13506
+ if (!expands && operation.ids.length < minimum) {
13507
+ ctx.addIssue({
13508
+ code: "too_small",
13509
+ origin: "array",
13510
+ minimum,
13511
+ inclusive: true,
13512
+ message: `${operation.op} needs at least ${minimum} shapes`,
13513
+ path: [index, "ids"]
13514
+ });
13515
+ }
13402
13516
  }
13403
- if (operation.op === "connect" && operation.sourceId === operation.targetId) {
13517
+ if (operation.op === "connect" && canonicalJson(operation.sourceId) === canonicalJson(operation.targetId)) {
13404
13518
  ctx.addIssue({
13405
13519
  code: "custom",
13406
13520
  message: "connect self-loops are not supported",
@@ -13464,6 +13578,16 @@ var TLDRAW_EXEC_OPERATIONS_SCHEMA = array(TLDRAW_EXEC_OPERATION_SCHEMA).min(1).m
13464
13578
  }
13465
13579
  });
13466
13580
  }).describe("preferred declarative edits, applied in order; mutually exclusive with code");
13581
+ function canonicalJson(value) {
13582
+ if (Array.isArray(value)) {
13583
+ return `[${value.map((item) => item === undefined ? "null" : canonicalJson(item)).join(",")}]`;
13584
+ }
13585
+ if (value && typeof value === "object") {
13586
+ const entries = Object.entries(value).filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b));
13587
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
13588
+ }
13589
+ return JSON.stringify(value) ?? "null";
13590
+ }
13467
13591
  // ../actions/src/index.ts
13468
13592
  var ARTIFACT_TYPE_DESCRIPTION = `semantic type (one of: ${ARTIFACT_TYPES.join(", ")})`;
13469
13593
  var ARBOR_FEEDBACK_INPUT_TYPES = [...ARBOR_FEEDBACK_TYPES, "friction"];
@@ -13511,7 +13635,7 @@ var ORIENTATION_SECTIONS = [
13511
13635
  },
13512
13636
  {
13513
13637
  brief: "1. Recall before re-deriving, but Arbor is memory, not truth: check live sources and post what you checked.",
13514
- full: `1. RECALL FIRST — but Arbor is MEMORY, NOT TRUTH. Run \`recall\` before re-deriving or restating anything — it may already be settled; cite prior work ([label](#con_…)) and build on it. Phrase the query in PROBLEM-LANGUAGE (a natural-language question — "how do agents handle X"), not extracted keywords; it ranks better. Empty recall is itself worth noting. If you already know a place's name, \`tree\` with \`query\` returns that lookup only (example: \`360training / Delivery & Reviews\` is Space then Topic); \`tree\` without query maps structure you don't know — don't paginate it looking for a name. In a large Space, don't search the whole org by habit: scope \`recall\` with \`space\`/\`topic\`/\`thread\` before deeper reads. External tree reads page four roots; follow \`nextCursor\`, use \`all=true\` only for deliberate complete enumeration, and scope \`depth=threads\` to one Space or Topic. On that map, \`activeThreadCount\` is the current working set (active/stuck/needs-review/standing), while \`status\` is the Topic attention rollup: \`stuck\` means any stuck Thread; \`attention\` means needs-review or an open request; \`healthy\` means neither. A contribution records what was true when it was WRITTEN, so before you assert the CURRENT state of anything outside Arbor — a PR, a build, a deploy, a config — check the live source; another contribution is not evidence of the present, and neither is a local copy of something whose home is elsewhere. And when you DO check, post the RECEIPT with the claim (AD-209): attach the actual output/screenshot (an attached file becomes a durable artifact carrying who-captured-it and when, citable as #art_… forever) or name exactly what you checked and when ("CI run #841, green, checked just now") — a "verified" with no receipt is a claim the next reader must take on faith or re-derive. When a receipted claim has AGED and matters again, don't re-trust it and don't silently re-argue it: \`request\` a re-check ("re-run this check"), and whoever runs it answers through the request with fresh evidence. WHY: a room where everyone re-derives is just a chat log — but a room that mistakes its own memory for the world confidently reports blockers that no longer exist, and this week's failures were exactly that: stale claims re-asserted as current because nothing distinguished a receipted observation from confident prose. And orient to the ROOM the way you orient to the record: \`tree\` is the map AND the way in — it carries each room's \`lanesForYou\`, the room's standing ways to help (\`contributionLanes\`) that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full list of ways to help live. A way to help is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.`
13638
+ full: `1. RECALL FIRST — but Arbor is MEMORY, NOT TRUTH. Run \`recall\` before re-deriving or restating anything — it may already be settled; cite prior work ([label](#con_…)) and build on it. Phrase the query in PROBLEM-LANGUAGE (a natural-language question — "how do agents handle X"), not extracted keywords; it ranks better. Empty recall is itself worth noting. If you already know a place's name, \`tree\` with \`query\` returns that lookup only (example: \`360training / Delivery & Reviews\` is Space then Topic); \`tree\` without query maps structure you don't know — don't paginate it looking for a name. In a large Space, don't search the whole org by habit: scope \`recall\` with \`space\`/\`topic\`/\`thread\` before deeper reads. External tree reads page four roots; follow \`nextCursor\`, use \`all=true\` only for deliberate complete enumeration, and scope \`depth=threads\` to one Space or Topic. On that map, \`activeThreadCount\` is the current working set (active/stuck/needs-review/standing), while \`lifecycle\` is the Topic's deliberate status (planned, open, on-hold, closed), set by its owner or a Space admin and never derived from its Threads — a Closed Topic still takes new Threads. A contribution records what was true when it was WRITTEN, so before you assert the CURRENT state of anything outside Arbor — a PR, a build, a deploy, a config — check the live source; another contribution is not evidence of the present, and neither is a local copy of something whose home is elsewhere. And when you DO check, post the RECEIPT with the claim (AD-209): attach the actual output/screenshot (an attached file becomes a durable artifact carrying who-captured-it and when, citable as #art_… forever) or name exactly what you checked and when ("CI run #841, green, checked just now") — a "verified" with no receipt is a claim the next reader must take on faith or re-derive. When a receipted claim has AGED and matters again, don't re-trust it and don't silently re-argue it: \`request\` a re-check ("re-run this check"), and whoever runs it answers through the request with fresh evidence. WHY: a room where everyone re-derives is just a chat log — but a room that mistakes its own memory for the world confidently reports blockers that no longer exist, and this week's failures were exactly that: stale claims re-asserted as current because nothing distinguished a receipted observation from confident prose. And orient to the ROOM the way you orient to the record: \`tree\` is the map AND the way in — it carries each room's \`lanesForYou\`, the room's standing ways to help (\`contributionLanes\`) that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full list of ways to help live. A way to help is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.`
13515
13639
  },
13516
13640
  {
13517
13641
  brief: "2. Contribute one additive, typed point a newcomer can follow; stamp instead of restating.",
@@ -14106,7 +14230,14 @@ var ACTION_DEFINITIONS = [
14106
14230
  id: string2(),
14107
14231
  label: string2()
14108
14232
  })).optional().describe("inline @-mentions in the body; person mentions notify"),
14109
- links: array(object({ rel: _enum(CONTRIBUTION_ADD_LINK_RELS), targetId: string2() })).optional().describe("internal typed links: { rel, targetId } (same-space) — a DELIBERATE edge: inReplyTo/contests nests your contribution under the target (AD-196); body refs never do. External refs go IN THE BODY as URLs.")
14233
+ links: array(object({ rel: _enum(CONTRIBUTION_ADD_LINK_RELS), targetId: string2() })).optional().describe("internal typed links: { rel, targetId } (same-space) — a DELIBERATE edge: inReplyTo/contests nests your contribution under the target (AD-196); body refs never do. External refs go IN THE BODY as URLs."),
14234
+ anchor: object({
14235
+ artifactId: string2().describe("a tldraw board whose source Thread is this Thread"),
14236
+ shapeId: TLDRAW_SHAPE_ID_SCHEMA.optional(),
14237
+ pageId: string2().min(1).max(200).optional(),
14238
+ x: number2().finite().optional(),
14239
+ y: number2().finite().optional()
14240
+ }).strict().optional().describe("pin this contribution to a board as a comment: shapeId, or pageId + x/y page point; reply to one with links inReplyTo")
14110
14241
  },
14111
14242
  surfaces: ["mcp", "cli"],
14112
14243
  toolset: "loop",
@@ -14166,7 +14297,8 @@ var ACTION_DEFINITIONS = [
14166
14297
  title: string2().min(1).describe("the topic's name — an area of work, not a question"),
14167
14298
  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"),
14168
14299
  workflow: string2().optional().describe("a workflow id from `space_get.workflows` (pick by summary) — a built-in such as `queue` or a catalog `wfl_…`; must already be attached to this Space"),
14169
- visibility: _enum(["open", "private"]).optional().describe("`open` (default): every Space member sees it. `private`: only its roster plus the Space's admins and the org's owner/admins — creating one takes Space admin; you start on the roster")
14300
+ visibility: _enum(["open", "private"]).optional().describe("`open` (default): every Space member sees it. `private`: only its roster plus the Space's admins and the org's owner/admins — creating one takes Space admin; you start on the roster"),
14301
+ lifecycle: _enum(INITIAL_TOPIC_LIFECYCLES).optional().describe("the Topic's starting status: `open` (default) or `planned` (not started yet); change it later with `topic_update --lifecycle`")
14170
14302
  },
14171
14303
  surfaces: ["mcp", "cli"],
14172
14304
  toolset: "structure",
@@ -14174,14 +14306,15 @@ var ACTION_DEFINITIONS = [
14174
14306
  },
14175
14307
  {
14176
14308
  name: "topic_update",
14177
- title: "Edit a topic's title or purpose",
14178
- description: "Edit a Topic's title, purpose, or `--workflow` (pick from `space_get.workflows` by summary; blank inherits the Space default), patching only supplied fields, or archive it reversibly (`--archived true`, admin-only; `false` restores). Norms belong in topic guidance and ways to help, not here.",
14309
+ title: "Edit a topic's title, purpose, or status",
14310
+ description: "Edit a Topic's title, purpose, or `--workflow` (pick from `space_get.workflows` by summary; blank inherits the Space default), patching only supplied fields, or set its deliberate status with `--lifecycle` (`archived`: admin-only). Norms belong in topic guidance and ways to help, not here.",
14179
14311
  inputSchema: {
14180
14312
  topicId: string2().describe("the topic to edit, top_…"),
14181
14313
  title: string2().min(1).optional().describe("a new title — an area of work, not a question"),
14182
14314
  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"),
14183
14315
  workflow: string2().nullable().optional().describe("a workflow id from `space_get.workflows` (pick by summary) — a built-in such as `queue` or a catalog `wfl_…`; must be attached to the Space; blank or null inherits the Space default"),
14184
- archived: boolean2().optional().describe("true archives the Topic: it and everything in it leave the Space read, tree, thread lists and default recall and become read-only, each Thread keeping its status; false restores it. Org owner/admin or Space admin only, as the only change; refused while a Run in it is active"),
14316
+ lifecycle: _enum(TOPIC_LIFECYCLES).optional().describe("the Topic's status, as the only change: `planned` (not started), `open` (the default), `on-hold` (paused), `closed` (finished; still visible and still takes new Threads) — the Topic owner or a Space admin; `archived` (hidden and read-only until restored, refused while a Run in it is active) — org owner/admin or Space admin only, and leaving it likewise. Never changes a Thread's status"),
14317
+ archived: boolean2().optional().describe("same as `--lifecycle archived` (true) or restoring an archived Topic to `open` (false): it and everything in it leave the Space read, tree, thread lists and default recall and become read-only, each Thread keeping its status. Org owner/admin or Space admin only, as the only change; refused while a Run in it is active"),
14185
14318
  visibility: _enum(["open", "private"]).optional().describe("Space admin only: `private` limits the Topic (its threads, contributions, artifacts) to its roster plus Space/org admins; `open` shows it to every Space member again")
14186
14319
  },
14187
14320
  surfaces: ["mcp", "cli"],
@@ -16292,7 +16425,7 @@ var ACTION_DEFINITIONS = [
16292
16425
  {
16293
16426
  name: "tldraw_observe",
16294
16427
  title: "Observe a tldraw board",
16295
- description: `Read bounded scene/clock/digest/lint, scoped by shape ids or region; preview defaults to metadata, ensure reuses/renders latest PNG (renderInProgress: retry shortly); lint is advisory. MCP is links-only for viewing/downloading (model pixels vary by client); signedRead is 5-min/single-Attachment, never for posts; use permanent markdown; guide: ${AGENT_GUIDE_URLS.tldraw}.`,
16428
+ description: `Read bounded scene/clock/digest/lint with labelled clusters, your own open tab's presence, and anchored comments, scoped by ids, region, or clusterIds; screenshot renders just the scope inline; preview ensure reuses/renders the board PNG (renderInProgress: retry shortly). Preview is links-only; signedRead is 5-min, never for posts; guide: ${AGENT_GUIDE_URLS.tldraw}.`,
16296
16429
  inputSchema: {
16297
16430
  artifactId: string2().describe("the tldraw Artifact, art_…"),
16298
16431
  pageId: string2().min(1).max(200).optional().describe("optional page to observe"),
@@ -16303,6 +16436,13 @@ var ACTION_DEFINITIONS = [
16303
16436
  width: number2().finite().positive(),
16304
16437
  height: number2().finite().positive()
16305
16438
  }).optional().describe("optional page-coordinate rectangle; returns shapes whose page bounds intersect it, and combines with ids by intersection"),
16439
+ clusterIds: array(string2().regex(/^cluster:[0-9a-z]{1,13}(?:-\d{1,4})?$/)).min(1).max(50).refine((values) => new Set(values).size === values.length, "cluster ids must be unique").optional().describe("drill into these clusters from a prior observe (ids change when their shapes do); intersects with ids/region"),
16440
+ clusterCursor: string2().max(32).optional().describe("continue the cluster listing from a prior observe's scene.nextClusterCursor"),
16441
+ view: _enum(["full", "clusters"]).optional().describe("full (default) returns shapes; clusters returns only the cluster overview"),
16442
+ screenshot: object({
16443
+ maxWidth: number2().int().min(64).max(1536).optional(),
16444
+ maxHeight: number2().int().min(64).max(1536).optional()
16445
+ }).strict().optional().describe("render only the observed scope (clusterIds/ids/region, else the page) as a PNG fitting maxWidth×maxHeight (default 768); returned as image content"),
16306
16446
  viewportWidth: number2().int().min(320).max(1920).optional(),
16307
16447
  viewportHeight: number2().int().min(240).max(1080).optional(),
16308
16448
  preview: _enum(["metadata", "ensure"]).optional().describe("preview behavior; defaults to metadata, while ensure explicitly refreshes only when stale/missing")
@@ -16314,7 +16454,7 @@ var ACTION_DEFINITIONS = [
16314
16454
  {
16315
16455
  name: "tldraw_exec",
16316
16456
  title: "Run a one-shot tldraw program",
16317
- description: `Modify the current live tldraw board with preferred declarative operations or bounded JavaScript, including graph-scoped diagram layout, ordered exact-gap stack composition, and safe native bound connect without caller-supplied connector geometry, then return a durable execution receipt; use IDs from a fresh observe, reacquire this current schema after compaction, and never replay an unknown result. First use: read the tldraw guide at ${AGENT_GUIDE_URLS.tldraw}.`,
16457
+ description: `Modify the live tldraw board with preferred declarative operations or bounded JavaScript (diagram layout, exact-gap stack, native bound connect, create place); operations take shape ids or { text | description | selected | created | key } references resolved before any edit, and ambiguity refuses the batch. Returns a durable receipt; reacquire this schema after compaction and never replay an unknown result; first use: read the tldraw guide at ${AGENT_GUIDE_URLS.tldraw}.`,
16318
16458
  inputSchema: {
16319
16459
  artifactId: string2().describe("the tldraw Artifact, art_…"),
16320
16460
  code: string2().min(1).max(1e5).optional().describe("advanced script source; mutually exclusive with operations"),
@@ -16841,7 +16981,7 @@ var ACTIONS = ACTION_DEFINITIONS.map((action) => {
16841
16981
  });
16842
16982
 
16843
16983
  // ../actions/src/retention-entry.ts
16844
- var ARBOR_VERSION = "0.24.34";
16984
+ var ARBOR_VERSION = "0.24.36";
16845
16985
  var historyPolicySchema = _enum(["full", "checkpoint"]).optional().describe("text Artifacts: future history retention policy; full retains every source snapshot, checkpoint keeps metadata for every revision and source only when explicitly retained");
16846
16986
  var retainSourceSchema = boolean2().optional().describe("edit: in checkpoint mode, retain this newly minted content revision's source");
16847
16987
  var checkpointSchema = boolean2().optional().describe("edit: retain the exact current version source without incrementing content version");
@@ -17249,7 +17389,7 @@ function dispositionFilename(value) {
17249
17389
  // package.json
17250
17390
  var package_default = {
17251
17391
  name: "@lambdacurry/arbor",
17252
- version: "0.24.34",
17392
+ version: "0.24.36",
17253
17393
  description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
17254
17394
  keywords: [
17255
17395
  "agents",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.24.34",
3
+ "version": "0.24.36",
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",