@lambdacurry/arbor 0.21.59 → 0.21.61

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 +96 -2
  2. package/package.json +1 -1
package/dist/arbor.js CHANGED
@@ -16,7 +16,7 @@ var __export = (target, all) => {
16
16
  // package.json
17
17
  var package_default = {
18
18
  name: "@lambdacurry/arbor",
19
- version: "0.21.59",
19
+ version: "0.21.61",
20
20
  description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
21
21
  keywords: [
22
22
  "agents",
@@ -17144,6 +17144,16 @@ var watches = sqliteTable("watches", {
17144
17144
  watchUq: uniqueIndex("watches_watcher_target_uniq").on(t.watcherProfileId, t.targetType, t.targetId),
17145
17145
  watchTargetIdx: index("watches_target_idx").on(t.targetType, t.targetId)
17146
17146
  }));
17147
+ var threadAssignments = sqliteTable("thread_assignments", {
17148
+ id: text("id").primaryKey(),
17149
+ threadId: text("thread_id").notNull().references(() => threads.id),
17150
+ profileId: text("profile_id").notNull().references(() => profiles.id),
17151
+ assignedByProfileId: text("assigned_by_profile_id").notNull().references(() => profiles.id),
17152
+ createdAt: ts("created_at").notNull()
17153
+ }, (t) => ({
17154
+ assigneeUq: uniqueIndex("thread_assignments_thread_profile_uniq").on(t.threadId, t.profileId),
17155
+ profileIdx: index("thread_assignments_profile_idx").on(t.profileId, t.createdAt)
17156
+ }));
17147
17157
  // ../core/src/ops/secret.ts
17148
17158
  var SECRET_PERMISSIONS = [
17149
17159
  "create",
@@ -17206,6 +17216,17 @@ var EMOJI_RE = new RegExp("^\\p{RGI_Emoji}$", "v");
17206
17216
  var BASE_MS = Date.parse("2026-07-20T15:00:00.000Z");
17207
17217
  // ../core/src/ops/agent-pairing.ts
17208
17218
  var PAIRING_TTL_MS = 15 * 60 * 1000;
17219
+ // ../core/src/queries/thread-assignment.ts
17220
+ var assigneeColumns = {
17221
+ threadId: threadAssignments.threadId,
17222
+ assignedAt: threadAssignments.createdAt,
17223
+ assignedByProfileId: threadAssignments.assignedByProfileId,
17224
+ id: profiles.id,
17225
+ kind: profiles.kind,
17226
+ displayName: profiles.displayName,
17227
+ color: profiles.color,
17228
+ emoji: profiles.emoji
17229
+ };
17209
17230
  // ../core/src/ops/computer-recovery.ts
17210
17231
  var COMPUTER_RECOVERY_GRANT_MAX_TTL_MS = 24 * 60 * 60 * 1000;
17211
17232
  // ../core/src/ops/preview.ts
@@ -17791,6 +17812,10 @@ var threadAuthor = exports_external.looseObject({
17791
17812
  color: nullableString,
17792
17813
  emoji: nullableString
17793
17814
  });
17815
+ var threadAssignee = threadAuthor.extend({
17816
+ assignedAt: exports_external.string(),
17817
+ assignedByProfileId: id
17818
+ });
17794
17819
  var threadContribution = exports_external.looseObject({
17795
17820
  id,
17796
17821
  computerSessionId: nullableString,
@@ -18161,6 +18186,7 @@ var MCP_OUTPUT_SCHEMAS = {
18161
18186
  openRequests: exports_external.array(threadOpenRequest).optional(),
18162
18187
  scheduledRequests: exports_external.array(threadScheduledRequest).optional(),
18163
18188
  participants: exports_external.array(threadAuthor).optional(),
18189
+ assignees: exports_external.array(threadAssignee).optional(),
18164
18190
  artifacts: exports_external.array(artifact4).optional(),
18165
18191
  initiatives: exports_external.array(initiative3).optional(),
18166
18192
  distillate: exports_external.array(jsonObject).optional(),
@@ -18168,6 +18194,33 @@ var MCP_OUTPUT_SCHEMAS = {
18168
18194
  cursor: exports_external.string().optional(),
18169
18195
  unchanged: exports_external.boolean().optional()
18170
18196
  }),
18197
+ thread_assign: exports_external.looseObject({
18198
+ threadId: id,
18199
+ profileId: id,
18200
+ assigned: exports_external.boolean(),
18201
+ created: exports_external.boolean().optional(),
18202
+ removed: exports_external.boolean().optional(),
18203
+ assignees: exports_external.array(threadAssignee)
18204
+ }),
18205
+ thread_list: exports_external.looseObject({
18206
+ items: exports_external.array(exports_external.looseObject({
18207
+ id,
18208
+ title: exports_external.string(),
18209
+ objective: exports_external.string(),
18210
+ status: exports_external.string(),
18211
+ spaceId: id,
18212
+ spaceTitle: exports_external.string(),
18213
+ topicId: id,
18214
+ topicTitle: exports_external.string(),
18215
+ createdAt: exports_external.string(),
18216
+ lastActivityAt: exports_external.string(),
18217
+ assignees: exports_external.array(threadAssignee),
18218
+ url: exports_external.string().optional()
18219
+ })),
18220
+ nextCursor: nullableString,
18221
+ total: exports_external.number().int().min(0),
18222
+ status: exports_external.string().optional()
18223
+ }),
18171
18224
  tree: exports_external.looseObject({
18172
18225
  spaces: exports_external.array(treeSpace),
18173
18226
  pageUnit: exports_external.enum(["spaces", "topics", "threads"]).optional(),
@@ -19052,6 +19105,8 @@ var MCP_TOOL_ANNOTATIONS = {
19052
19105
  topic_update: additive,
19053
19106
  create_thread: additive,
19054
19107
  thread_update: additive,
19108
+ thread_assign: idempotent,
19109
+ thread_list: readOnly,
19055
19110
  set_organization_computer: destructiveIdempotent,
19056
19111
  set_space_computer: destructiveIdempotent,
19057
19112
  set_topic_computer: destructiveIdempotent,
@@ -19167,7 +19222,7 @@ Arbor's MCP is the collaboration and durable-knowledge surface. Configure, autho
19167
19222
 
19168
19223
  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. In a large Space, don't search the whole org by habit: use \`tree\` to choose the room, then 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 durable contribution lanes that match what you said you do; follow one into \`space_get\`/\`topic_get\`, where the room's purpose, guidance, goals, and full lane list live. A lane is an invitation, never an obligation: what you OWE is only ever in \`inbox\`.
19169
19224
  2. CONTRIBUTE typed points — but ADD ONLY WHAT'S ADDITIVE (AD-205). Ask what the most additive move is, not whether to say something: if your reaction to a point already on the record fits in one line — agree OR disagree — STAMP it (vouch, or push back with a one-line why), don't restate it; if you'd only echo consensus, reviewing IS the contribution and staying out is fine. When you DO contribute, it's ONE point, with the type that names your move (proposal / critique / question / evidence / risk / correction / assertion / decision). Markdown welcome; put references IN your prose (a URL or [label](#con_…) becomes a navigable reference). Prose refs are CITATIONS — they never move your contribution in the thread, so cite freely; to REPLY under a specific contribution, pass links: [{rel: 'inReplyTo', targetId}] (AD-196). WHY: a thread where every agent restates the consensus is noise — the record is strongest when each point appears ONCE and gets vouched (or contested) with a stamp, not re-said; and one typed point is reviewable on its own, so a synthesis citing five points should read as the most top-level thing in the thread, not as a reply to the first one it mentions.
19170
- 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).
19225
+ 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).
19171
19226
  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").
19172
19227
  5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
19173
19228
  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.
@@ -19778,6 +19833,45 @@ var ACTION_DEFINITIONS = [
19778
19833
  toolset: "loop",
19779
19834
  run: forward("thread.setConfig")
19780
19835
  },
19836
+ {
19837
+ name: "thread_assign",
19838
+ title: "Assign or unassign a thread",
19839
+ description: "Make a Space member (yourself when profileId is omitted) responsible for moving a Thread, or remove that responsibility; assignment is immediate, idempotent, grants no permissions, and creates no Inbox item — a specific owed action is still a `request`. Assignees stay attached through every lifecycle move, so transfer responsibility by assigning the new owner and unassigning the old.",
19840
+ inputSchema: {
19841
+ verb: exports_external.enum(["assign", "unassign"]).describe("add or remove responsibility"),
19842
+ threadId: exports_external.string().describe("the thread, thr_…"),
19843
+ profileId: exports_external.string().optional().describe("the member to (un)assign, prof_…; omit for yourself")
19844
+ },
19845
+ surfaces: ["mcp", "cli"],
19846
+ toolset: "loop",
19847
+ run: dispatch({ assign: "thread.assign", unassign: "thread.unassign" })
19848
+ },
19849
+ {
19850
+ name: "thread_list",
19851
+ title: "List threads by responsibility",
19852
+ description: "List the Threads you are responsible for (default), those assigned to another profile, or the unassigned ones inside one Space or Topic — current work only (active, needs-review, stuck) unless status widens it. Read it to decide what to move forward; `inbox` remains the list of specific actions you owe.",
19853
+ inputSchema: {
19854
+ assignee: exports_external.string().optional().describe("`me` (default) or a profile id, prof_…"),
19855
+ unassigned: exports_external.boolean().optional().describe("threads with no assignee — requires spaceId or topicId"),
19856
+ spaceId: exports_external.string().optional().describe("narrow to one space, spc_…"),
19857
+ topicId: exports_external.string().optional().describe("narrow to one topic, top_…"),
19858
+ status: exports_external.enum([
19859
+ "current",
19860
+ "all",
19861
+ "active",
19862
+ "needs-review",
19863
+ "stuck",
19864
+ "standing",
19865
+ "resolved",
19866
+ "archived"
19867
+ ]).optional().describe("default current = active + needs-review + stuck; all includes standing/resolved/archived"),
19868
+ limit: exports_external.number().int().min(1).max(100).optional().describe("page size (default 20, max 100)"),
19869
+ cursor: exports_external.string().optional().describe("keyset cursor from a prior page's nextCursor")
19870
+ },
19871
+ surfaces: ["mcp", "cli"],
19872
+ toolset: "loop",
19873
+ run: forward("thread.list")
19874
+ },
19781
19875
  {
19782
19876
  name: "set_organization_computer",
19783
19877
  title: "Replace organization computer defaults",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambdacurry/arbor",
3
- "version": "0.21.59",
3
+ "version": "0.21.61",
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",