@lambdacurry/arbor 0.6.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arbor.js +113 -26
- package/package.json +1 -1
package/dist/arbor.js
CHANGED
|
@@ -1345,6 +1345,7 @@ var profiles = sqliteTable("profiles", {
|
|
|
1345
1345
|
color: text("color").$type(),
|
|
1346
1346
|
emoji: text("emoji"),
|
|
1347
1347
|
description: text("description"),
|
|
1348
|
+
notificationPrefs: text("notification_prefs", { mode: "json" }).$type(),
|
|
1348
1349
|
createdAt: ts("created_at").notNull()
|
|
1349
1350
|
}, (t) => ({ orgIdx: index("profiles_org_idx").on(t.orgId) }));
|
|
1350
1351
|
var clients = sqliteTable("clients", {
|
|
@@ -1560,12 +1561,28 @@ var artifacts = sqliteTable("artifacts", {
|
|
|
1560
1561
|
supersedes: text("supersedes", { mode: "json" }).$type(),
|
|
1561
1562
|
supersededBy: text("superseded_by", { mode: "json" }).$type(),
|
|
1562
1563
|
reviewAfter: ts("review_after"),
|
|
1563
|
-
createdAt: ts("created_at").notNull()
|
|
1564
|
+
createdAt: ts("created_at").notNull(),
|
|
1565
|
+
kind: text("kind").$type().notNull().default("file"),
|
|
1566
|
+
source: text("source"),
|
|
1567
|
+
version: integer("version").notNull().default(0),
|
|
1568
|
+
editPolicy: text("edit_policy").$type().notNull().default("members")
|
|
1564
1569
|
}, (t) => ({
|
|
1565
1570
|
statusIdx: index("artifacts_status_idx").on(t.status),
|
|
1566
1571
|
tierIdx: index("artifacts_tier_idx").on(t.retrievalTier),
|
|
1567
1572
|
ownerIdx: index("artifacts_owner_idx").on(t.ownerProfileId)
|
|
1568
1573
|
}));
|
|
1574
|
+
var artifactVersions = sqliteTable("artifact_versions", {
|
|
1575
|
+
id: text("id").primaryKey(),
|
|
1576
|
+
artifactId: text("artifact_id").notNull().references(() => artifacts.id),
|
|
1577
|
+
version: integer("version").notNull(),
|
|
1578
|
+
source: text("source").notNull(),
|
|
1579
|
+
editorProfileId: text("editor_profile_id").notNull().references(() => profiles.id),
|
|
1580
|
+
executionContextId: text("execution_context_id").references(() => executionContexts.id),
|
|
1581
|
+
note: text("note"),
|
|
1582
|
+
createdAt: ts("created_at").notNull()
|
|
1583
|
+
}, (t) => ({
|
|
1584
|
+
artifactVersionUq: uniqueIndex("artifact_versions_artifact_version_uq").on(t.artifactId, t.version)
|
|
1585
|
+
}));
|
|
1569
1586
|
var requests = sqliteTable("requests", {
|
|
1570
1587
|
id: text("id").primaryKey(),
|
|
1571
1588
|
threadId: text("thread_id").notNull().references(() => threads.id),
|
|
@@ -1624,9 +1641,11 @@ var notifications = sqliteTable("notifications", {
|
|
|
1624
1641
|
groupKey: text("group_key"),
|
|
1625
1642
|
actors: text("actors", { mode: "json" }).$type().notNull().default([]),
|
|
1626
1643
|
readAt: ts("read_at"),
|
|
1644
|
+
emailedAt: ts("emailed_at"),
|
|
1627
1645
|
createdAt: ts("created_at").notNull()
|
|
1628
1646
|
}, (t) => ({
|
|
1629
1647
|
recipientIdx: index("notifications_recipient_idx").on(t.recipientProfileId, t.readAt),
|
|
1648
|
+
emailIdx: index("notifications_emailed_idx").on(t.emailedAt),
|
|
1630
1649
|
groupIdx: uniqueIndex("notifications_group_key_uniq").on(t.groupKey)
|
|
1631
1650
|
}));
|
|
1632
1651
|
var notificationCursors = sqliteTable("notification_cursors", {
|
|
@@ -1729,6 +1748,7 @@ var INDEX_ON = new Set([
|
|
|
1729
1748
|
"contribution.added",
|
|
1730
1749
|
"contribution.edited",
|
|
1731
1750
|
"artifact.created",
|
|
1751
|
+
"artifact.edited",
|
|
1732
1752
|
"review.added",
|
|
1733
1753
|
"thread.created",
|
|
1734
1754
|
"topic.created"
|
|
@@ -16012,24 +16032,25 @@ config(en_default());
|
|
|
16012
16032
|
// ../actions/src/index.ts
|
|
16013
16033
|
var ORIENTATION = `Arbor is your team's deliberation room and shared memory — people and agents settle typed work here, and Arbor remembers what's decided. To work well:
|
|
16014
16034
|
|
|
16015
|
-
1. RECALL FIRST. 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. WHY: a room where everyone re-derives is just a chat log.
|
|
16016
|
-
2. CONTRIBUTE typed points — ONE point per contribution, 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 —
|
|
16035
|
+
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. 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. 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.
|
|
16036
|
+
2. CONTRIBUTE typed points — ONE point per contribution, 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: one typed point is reviewable on its own, and 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.
|
|
16017
16037
|
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).
|
|
16018
|
-
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️). WHY: promotion is curation, not applause — and it's about an OUTPUT, never a whole thread (a thread RESOLVES; it is never "promoted").
|
|
16038
|
+
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").
|
|
16019
16039
|
5. EDIT, don't repost. \`edit\` your own contribution to sharpen it; the record stays audited. A genuinely different point is a NEW contribution.
|
|
16020
|
-
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 a thread reaches its conclusion, \`transition_thread --to resolved\`; opened in the wrong place → \`--to archived\` and reopen where it belongs. 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.
|
|
16040
|
+
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 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.
|
|
16041
|
+
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.
|
|
16021
16042
|
|
|
16022
16043
|
Run \`arbor help\` (or read the MCP tool list) for the exact command/flags — those stay generated from the live action surface, so they're always current.`;
|
|
16023
16044
|
function forward(operation) {
|
|
16024
16045
|
return (ex, input) => ex.call(operation, input);
|
|
16025
16046
|
}
|
|
16026
|
-
function dispatch(map2) {
|
|
16047
|
+
function dispatch(map2, reshape) {
|
|
16027
16048
|
return (ex, input) => {
|
|
16028
16049
|
const { verb, ...rest } = input;
|
|
16029
16050
|
const op = verb ? map2[verb] : undefined;
|
|
16030
16051
|
if (!op)
|
|
16031
16052
|
return Promise.reject(new Error(`unknown verb "${verb ?? ""}" — expected one of: ${Object.keys(map2).join(" | ")}`));
|
|
16032
|
-
return ex.call(op, rest);
|
|
16053
|
+
return ex.call(op, reshape && verb ? reshape(verb, rest) : rest);
|
|
16033
16054
|
};
|
|
16034
16055
|
}
|
|
16035
16056
|
var PAGINATION_INPUT = {
|
|
@@ -16056,7 +16077,7 @@ var ACTIONS = [
|
|
|
16056
16077
|
{
|
|
16057
16078
|
name: "contribute",
|
|
16058
16079
|
title: "Contribute to a thread",
|
|
16059
|
-
description: "Add your own typed contribution (proposal/critique/question/evidence/…) — ONE point per contribution, on the record. RECALL FIRST (recalling beats re-deriving), and put references IN THE BODY: URLs and inline [label](#con_…) refs become typed reference edges + smart pills automatically.",
|
|
16080
|
+
description: "Add your own typed contribution (proposal/critique/question/evidence/…) — ONE point per contribution, on the record. RECALL FIRST (recalling beats re-deriving), and put references IN THE BODY: URLs and inline [label](#con_…) refs become typed reference edges + smart pills automatically. Body refs are CITATIONS (AD-196) — they never change where your contribution sits in the thread, so cite freely. To thread your contribution UNDER a specific one (a reply), pass links: [{rel: 'inReplyTo', targetId}] — that deliberate edge is what nests it.",
|
|
16060
16081
|
inputSchema: {
|
|
16061
16082
|
threadId: exports_external.string().describe("the thread id, e.g. thr_…"),
|
|
16062
16083
|
type: exports_external.enum(CONTRIBUTION_TYPES).describe("the contribution type (AD-066 taxonomy)"),
|
|
@@ -16068,7 +16089,7 @@ var ACTIONS = [
|
|
|
16068
16089
|
id: exports_external.string(),
|
|
16069
16090
|
label: exports_external.string()
|
|
16070
16091
|
})).optional().describe("inline @-mentions in the body; person mentions notify"),
|
|
16071
|
-
links: exports_external.array(exports_external.object({ rel: exports_external.enum(CONTRIBUTION_LINK_RELS), targetId: exports_external.string() })).optional().describe("internal typed links: { rel, targetId } (same-space). External refs go IN THE BODY as URLs.")
|
|
16092
|
+
links: exports_external.array(exports_external.object({ rel: exports_external.enum(CONTRIBUTION_LINK_RELS), targetId: exports_external.string() })).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.")
|
|
16072
16093
|
},
|
|
16073
16094
|
surfaces: ["mcp", "cli"],
|
|
16074
16095
|
toolset: "loop",
|
|
@@ -16077,7 +16098,7 @@ var ACTIONS = [
|
|
|
16077
16098
|
{
|
|
16078
16099
|
name: "edit",
|
|
16079
16100
|
title: "Edit your contribution",
|
|
16080
|
-
description: "Edit the BODY of your own contribution (AD-132 — editable-but-audited: the displayed text changes, an 'edited' marker appears, and the event log keeps the previous body). Author-only; the type and links are fixed — a different point is a NEW contribution, not an edit.",
|
|
16101
|
+
description: "Edit the BODY of your own contribution (AD-132 — editable-but-audited: the displayed text changes, an 'edited' marker appears, and the event log keeps the previous body). Author-only; the type and DELIBERATE links (reply edges, fulfills) are fixed — body-borne prose refs follow the edited body: a [label](#con_…) you edit in gains its citation edge, one you edit out drops it (AD-196). A different point is a NEW contribution, not an edit.",
|
|
16081
16102
|
inputSchema: {
|
|
16082
16103
|
contributionId: exports_external.string().describe("your contribution's id, con_…"),
|
|
16083
16104
|
body: exports_external.string().min(1).describe("the replacement markdown body"),
|
|
@@ -16173,10 +16194,10 @@ var ACTIONS = [
|
|
|
16173
16194
|
{
|
|
16174
16195
|
name: "transition_thread",
|
|
16175
16196
|
title: "Move a thread's lifecycle",
|
|
16176
|
-
description: "Move a thread along its lifecycle (AD-068/159): `resolved` = the deliberation reached its conclusion; `archived` = retired / wrong place (both recoverable to `active`); `stuck`/`needs-review` flag an open thread. Threads RESOLVE — standout outputs get promoted via `curation`; there is no promote-thread. Illegal jumps are rejected",
|
|
16197
|
+
description: "Move a thread along its lifecycle (AD-068/159/200): `resolved` = the deliberation reached its conclusion; `archived` = retired / wrong place (both recoverable to `active`); `stuck`/`needs-review` flag an open thread; `standing` = DESIGNED to stay open (a journal, ops log, or standing lane — never owed a conclusion; distill with promotions instead of closing). Threads RESOLVE — standout outputs get promoted via `curation`; there is no promote-thread. Illegal jumps are rejected",
|
|
16177
16198
|
inputSchema: {
|
|
16178
16199
|
threadId: exports_external.string().describe("the thread to move, thr_…"),
|
|
16179
|
-
to: exports_external.string().describe("target status: active|needs-review|stuck|resolved|archived"),
|
|
16200
|
+
to: exports_external.string().describe("target status: active|needs-review|stuck|standing|resolved|archived"),
|
|
16180
16201
|
reason: exports_external.string().optional().describe("optional one-line why, recorded on the event")
|
|
16181
16202
|
},
|
|
16182
16203
|
surfaces: ["mcp", "cli"],
|
|
@@ -16186,11 +16207,14 @@ var ACTIONS = [
|
|
|
16186
16207
|
{
|
|
16187
16208
|
name: "create_artifact",
|
|
16188
16209
|
title: "Create an artifact",
|
|
16189
|
-
description: "Promote durable output — a decision, summary, plan, or research brief — into a first-class artifact you own. Reach for this when a thread has produced something worth retaining and citing beyond the conversation
|
|
16210
|
+
description: "Promote durable output — a decision, summary, plan, or research brief — into a first-class artifact you own. Text kinds (doc/table/diagram/html) are living documents: pass `source` (born at v1, editable in place via edit_artifact); kind=file passes a contentRef instead. Reach for this when a thread has produced something worth retaining and citing beyond the conversation.",
|
|
16190
16211
|
inputSchema: {
|
|
16191
|
-
type: exports_external.string().describe("
|
|
16212
|
+
type: exports_external.string().describe("semantic type, e.g. decision|summary|plan|markdown|table|research-brief"),
|
|
16213
|
+
kind: exports_external.enum(["doc", "table", "diagram", "html", "file"]).optional().describe("structural kind (default file). doc/table/diagram/html are editable, versioned text"),
|
|
16192
16214
|
title: exports_external.string().min(1),
|
|
16193
|
-
|
|
16215
|
+
source: exports_external.string().optional().describe("text kinds: the initial content (v1)"),
|
|
16216
|
+
contentRef: exports_external.string().optional().describe("kind=file: where the content lives, e.g. inline://… or a URL"),
|
|
16217
|
+
editPolicy: exports_external.enum(["owner", "members", "anyone"]).optional().describe("who may edit (default members)"),
|
|
16194
16218
|
summary: exports_external.string().optional(),
|
|
16195
16219
|
sourceThreadId: exports_external.string().optional(),
|
|
16196
16220
|
sourceContributionIds: exports_external.array(exports_external.string()).optional()
|
|
@@ -16199,6 +16223,36 @@ var ACTIONS = [
|
|
|
16199
16223
|
toolset: "artifacts",
|
|
16200
16224
|
run: forward("artifact.create")
|
|
16201
16225
|
},
|
|
16226
|
+
{
|
|
16227
|
+
name: "get_artifact",
|
|
16228
|
+
title: "Read an artifact",
|
|
16229
|
+
description: "Read an artifact: current source + version + history (who edited, via what). READ BEFORE YOU EDIT — the version you read is the baseVersion your edit must name. Pass --version to read an old snapshot.",
|
|
16230
|
+
inputSchema: {
|
|
16231
|
+
artifactId: exports_external.string().describe("the artifact, art_…"),
|
|
16232
|
+
version: exports_external.number().optional().describe("read this old snapshot instead of the current")
|
|
16233
|
+
},
|
|
16234
|
+
surfaces: ["cli"],
|
|
16235
|
+
toolset: "artifacts",
|
|
16236
|
+
run: forward("artifact.get")
|
|
16237
|
+
},
|
|
16238
|
+
{
|
|
16239
|
+
name: "edit_artifact",
|
|
16240
|
+
title: "Edit a text artifact",
|
|
16241
|
+
description: `Edit a text-backed artifact (doc/table/diagram/html) in place. Read it first (get_artifact) and pass baseVersion = the version you read. Either --source (full replace) or --patches '[{"old":…,"new":…}]' (exact-match hunks, applied in order, ATOMIC — all land or none; one call mints ONE version, so batch related row-edits into one call). A stale baseVersion returns ok:false with the current source — reconcile and retry; a failed patch names the culprit index.`,
|
|
16242
|
+
inputSchema: {
|
|
16243
|
+
artifactId: exports_external.string().describe("the artifact, art_…"),
|
|
16244
|
+
baseVersion: exports_external.number().describe("the version you READ (get_artifact) — stale is rejected"),
|
|
16245
|
+
source: exports_external.string().optional().describe("full replace: the complete new content"),
|
|
16246
|
+
patches: exports_external.array(exports_external.object({ old: exports_external.string(), new: exports_external.string() })).optional().describe("exact-match hunks applied in order; each old must match exactly once"),
|
|
16247
|
+
note: exports_external.string().optional().describe("one-line summary of what changed")
|
|
16248
|
+
},
|
|
16249
|
+
surfaces: ["cli"],
|
|
16250
|
+
toolset: "artifacts",
|
|
16251
|
+
run: (ex, input) => ex.call("artifact.edit", {
|
|
16252
|
+
...input,
|
|
16253
|
+
verb: Array.isArray(input.patches) ? "patch" : "replace"
|
|
16254
|
+
})
|
|
16255
|
+
},
|
|
16202
16256
|
{
|
|
16203
16257
|
name: "transition_artifact",
|
|
16204
16258
|
title: "Transition an artifact's lifecycle",
|
|
@@ -16227,7 +16281,7 @@ var ACTIONS = [
|
|
|
16227
16281
|
{
|
|
16228
16282
|
name: "promote_contribution",
|
|
16229
16283
|
title: "Promote a contribution to a \uD83C\uDF96️ standout",
|
|
16230
|
-
description: "Mark a contribution a \uD83C\uDF96️ standout — recognition that boosts it in recall and flags it as one of the thread's outputs (AD-140: promotion is a MARK on the contribution, not a separate artifact). Reach for this when a contribution is worth recognizing. MANY contributions in a thread can be standouts (there's no single 'answer'); idempotent — re-promoting is a quiet no-op.",
|
|
16284
|
+
description: "Mark a contribution a \uD83C\uDF96️ standout — recognition that boosts it in recall and flags it as one of the thread's outputs (AD-140: promotion is a MARK on the contribution, not a separate artifact). Reach for this when a contribution is worth recognizing — and routinely in a STANDING thread, where promoting the periodic distilling synthesis is the lane's compression mechanism (AD-200). MANY contributions in a thread can be standouts (there's no single 'answer'); idempotent — re-promoting is a quiet no-op.",
|
|
16231
16285
|
inputSchema: {
|
|
16232
16286
|
contributionId: exports_external.string().describe("the contribution to promote, con_… (its thread + author are derived)")
|
|
16233
16287
|
},
|
|
@@ -16427,7 +16481,7 @@ var ACTIONS = [
|
|
|
16427
16481
|
{
|
|
16428
16482
|
name: "tree",
|
|
16429
16483
|
title: "Navigate the workspace tree",
|
|
16430
|
-
description: 'Map the workspace — a GLANCEABLE structure map, not a dump. Depths: `spaces` | `topics` (default: spaces → topics + light roster) | `threads` (adds each topic\'s threads with status
|
|
16484
|
+
description: 'Map the workspace — a GLANCEABLE structure map, not a dump. Depths: `spaces` | `topics` (default: spaces → topics + light roster) | `threads` (adds each topic\'s threads with status, open-request counts — `openRequestsForYou` answers "does this space need me?" (AD-182) — plus `lastActivityAt` + `standouts` (AD-198): a quiet-for-days ACTIVE thread with a standout is worth OPENING). These are TRIAGE signals — where to look, never what to do: a quiet thread may be closeable, waiting on a human, or a monitor doing its job, and reading it IS the cheap check before any resolve. Scope with space/topic; drill down with space_get/topic_get/thread_get instead of inflating the tree',
|
|
16431
16485
|
inputSchema: {
|
|
16432
16486
|
space: exports_external.string().optional().describe("scope to one space id (spc_…)"),
|
|
16433
16487
|
topic: exports_external.string().optional().describe("scope to one topic id (top_…)"),
|
|
@@ -16823,7 +16877,7 @@ var ACTIONS = [
|
|
|
16823
16877
|
{
|
|
16824
16878
|
name: "curation",
|
|
16825
16879
|
title: "Curate the record",
|
|
16826
|
-
description: "Curation moves on a contribution (all take contributionId). verb=promote (mark it a \uD83C\uDF96️ standout — sparingly: promotion is curation, not applause, AD-140), demote (remove the mark), delete (soft-remove), restore (bring a removed one back).",
|
|
16880
|
+
description: "Curation moves on a contribution (all take contributionId). verb=promote (mark it a \uD83C\uDF96️ standout — sparingly in deliberations: promotion is curation, not applause, AD-140. In a STANDING thread the calculus flips: promoting a periodic distilling synthesis is the lane's compression mechanism, AD-200 — do it, including on your own synthesis), demote (remove the mark), delete (soft-remove), restore (bring a removed one back).",
|
|
16827
16881
|
inputSchema: {
|
|
16828
16882
|
verb: exports_external.enum(["promote", "demote", "delete", "restore"]).describe("which curation move"),
|
|
16829
16883
|
contributionId: exports_external.string().describe("the contribution, con_…")
|
|
@@ -16840,15 +16894,23 @@ var ACTIONS = [
|
|
|
16840
16894
|
{
|
|
16841
16895
|
name: "artifact",
|
|
16842
16896
|
title: "Durable artifacts",
|
|
16843
|
-
description: "Durable,
|
|
16897
|
+
description: "Durable, versioned artifacts — the team's living documents (AD-201). Kinds: doc (markdown), table (CSV), diagram (Mermaid), html — all versioned text you edit in place — plus file (a contentRef: URL/inline/blob). " + "verb=create (kind+type+title, source for text kinds / contentRef for file; a text artifact is born at v1). " + "verb=get (artifactId — the current source + version + history; pass version to read an old snapshot). " + "verb=edit — READ FIRST via get, then pass baseVersion (the version you read): either source (full replace) or patches [{old,new},…] (exact-match hunks, applied in order, ATOMIC — all land or none, and one call mints ONE version, so batch related row-edits into one call). A stale baseVersion returns {ok:false, currentSource} — reconcile and retry against the returned source; a failed patch names the culprit index. " + "verb=transition (artifactId + to: active|promoted|needs-review|superseded|deprecated|archived|deleted), promote_attachment (attachmentId — lift an uploaded file into an artifact).",
|
|
16844
16898
|
inputSchema: {
|
|
16845
|
-
verb: exports_external.enum(["create", "transition", "promote_attachment"]).describe("which artifact move"),
|
|
16899
|
+
verb: exports_external.enum(["create", "get", "edit", "transition", "promote_attachment"]).describe("which artifact move"),
|
|
16846
16900
|
title: exports_external.string().optional().describe("create: the artifact's title"),
|
|
16847
|
-
|
|
16901
|
+
kind: exports_external.enum(["doc", "table", "diagram", "html", "file"]).optional().describe("create: structural kind (default file). doc/table/diagram/html are editable text"),
|
|
16902
|
+
type: exports_external.string().optional().describe("create: semantic type, e.g. decision|summary|plan|markdown|table|research-brief"),
|
|
16903
|
+
source: exports_external.string().optional().describe("create (text kinds): the initial content · edit verb=replace: the full new content"),
|
|
16904
|
+
contentRef: exports_external.string().optional().describe("create (kind=file): where the content lives, e.g. inline://… or a URL"),
|
|
16905
|
+
editPolicy: exports_external.enum(["owner", "members", "anyone"]).optional().describe("create: who may edit (default members)"),
|
|
16848
16906
|
summary: exports_external.string().optional().describe("create: a short summary"),
|
|
16849
16907
|
sourceThreadId: exports_external.string().optional().describe("create: the thread it came from"),
|
|
16850
16908
|
sourceContributionIds: exports_external.array(exports_external.string()).optional().describe("create: contributions it distills"),
|
|
16851
|
-
artifactId: exports_external.string().optional().describe("transition: the artifact, art_…"),
|
|
16909
|
+
artifactId: exports_external.string().optional().describe("get/edit/transition: the artifact, art_…"),
|
|
16910
|
+
version: exports_external.number().optional().describe("get: read this old snapshot instead of the current"),
|
|
16911
|
+
baseVersion: exports_external.number().optional().describe("edit: the version you READ (from get/create) — stale base is rejected with the current source"),
|
|
16912
|
+
patches: exports_external.array(exports_external.object({ old: exports_external.string(), new: exports_external.string() })).optional().describe("edit: exact-match hunks applied in order, atomically; each old must match exactly once"),
|
|
16913
|
+
note: exports_external.string().optional().describe("edit: one-line summary of what changed"),
|
|
16852
16914
|
to: exports_external.string().optional().describe("transition: the target status"),
|
|
16853
16915
|
reason: exports_external.string().optional().describe("transition: why"),
|
|
16854
16916
|
attachmentId: exports_external.string().optional().describe("promote_attachment: the upload, att_…")
|
|
@@ -16857,9 +16919,11 @@ var ACTIONS = [
|
|
|
16857
16919
|
toolset: "artifacts",
|
|
16858
16920
|
run: dispatch({
|
|
16859
16921
|
create: "artifact.create",
|
|
16922
|
+
get: "artifact.get",
|
|
16923
|
+
edit: "artifact.edit",
|
|
16860
16924
|
transition: "artifact.transition",
|
|
16861
16925
|
promote_attachment: "attachment.promote"
|
|
16862
|
-
})
|
|
16926
|
+
}, (verb, input) => verb === "edit" ? { ...input, verb: Array.isArray(input.patches) ? "patch" : "replace" } : input)
|
|
16863
16927
|
},
|
|
16864
16928
|
{
|
|
16865
16929
|
name: "charter",
|
|
@@ -16984,6 +17048,25 @@ class UsageError extends Error {
|
|
|
16984
17048
|
}
|
|
16985
17049
|
}
|
|
16986
17050
|
|
|
17051
|
+
// src/source.ts
|
|
17052
|
+
var SNIFF = [
|
|
17053
|
+
["CLAUDECODE", "Claude Code"],
|
|
17054
|
+
["CLAUDE_CODE_ENTRYPOINT", "Claude Code"],
|
|
17055
|
+
["OPENCLAW_HOME", "openclaw"],
|
|
17056
|
+
["CODEX_SANDBOX", "Codex"],
|
|
17057
|
+
["CURSOR_TRACE_ID", "Cursor"]
|
|
17058
|
+
];
|
|
17059
|
+
function detectSource(env = process.env) {
|
|
17060
|
+
const explicit = env.ARBOR_SOURCE?.trim();
|
|
17061
|
+
if (explicit)
|
|
17062
|
+
return explicit;
|
|
17063
|
+
for (const [envVar, tool] of SNIFF) {
|
|
17064
|
+
if (env[envVar]?.trim())
|
|
17065
|
+
return tool;
|
|
17066
|
+
}
|
|
17067
|
+
return;
|
|
17068
|
+
}
|
|
17069
|
+
|
|
16987
17070
|
// src/client.ts
|
|
16988
17071
|
class NotLoggedInError extends Error {
|
|
16989
17072
|
code = "UNAUTHORIZED";
|
|
@@ -16998,6 +17081,7 @@ function authHeader() {
|
|
|
16998
17081
|
throw new NotLoggedInError;
|
|
16999
17082
|
return { authorization: `Bearer ${cfg.token}`, apiUrl: cfg.apiUrl };
|
|
17000
17083
|
}
|
|
17084
|
+
var source = detectSource();
|
|
17001
17085
|
var httpExecutor = {
|
|
17002
17086
|
async call(name, input) {
|
|
17003
17087
|
const { authorization, apiUrl } = authHeader();
|
|
@@ -17008,7 +17092,11 @@ var httpExecutor = {
|
|
|
17008
17092
|
try {
|
|
17009
17093
|
res = await fetch(`${apiUrl}/api`, {
|
|
17010
17094
|
method: "POST",
|
|
17011
|
-
headers: {
|
|
17095
|
+
headers: {
|
|
17096
|
+
"content-type": "application/json",
|
|
17097
|
+
authorization,
|
|
17098
|
+
...source ? { "x-arbor-source": source } : {}
|
|
17099
|
+
},
|
|
17012
17100
|
body: JSON.stringify({ object: object2, verb, input })
|
|
17013
17101
|
});
|
|
17014
17102
|
} catch (cause) {
|
|
@@ -17030,7 +17118,6 @@ var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
|
17030
17118
|
"json",
|
|
17031
17119
|
"quiet",
|
|
17032
17120
|
"no-quiet",
|
|
17033
|
-
"version",
|
|
17034
17121
|
"help"
|
|
17035
17122
|
]);
|
|
17036
17123
|
var RESERVED_FLAGS = new Set([
|
|
@@ -17634,7 +17721,7 @@ async function main() {
|
|
|
17634
17721
|
const ctx = resolveOutput(flags);
|
|
17635
17722
|
const [cmd] = positionals;
|
|
17636
17723
|
try {
|
|
17637
|
-
if (
|
|
17724
|
+
if (flags.version !== undefined && positionals.length === 0 || cmd === "version") {
|
|
17638
17725
|
renderVersion(ctx);
|
|
17639
17726
|
return;
|
|
17640
17727
|
}
|
package/package.json
CHANGED