@lambdacurry/arbor 0.20.32 → 0.21.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 +137 -81
- package/package.json +1 -1
package/dist/arbor.js
CHANGED
|
@@ -17808,17 +17808,6 @@ var MCP_OUTPUT_SCHEMAS = {
|
|
|
17808
17808
|
connected: exports_external.boolean(),
|
|
17809
17809
|
inheritedBySiblingThreads: exports_external.boolean()
|
|
17810
17810
|
}),
|
|
17811
|
-
publish_topic_computer: exports_external.looseObject({
|
|
17812
|
-
published: exports_external.boolean(),
|
|
17813
|
-
snapshot
|
|
17814
|
-
}),
|
|
17815
|
-
checkpoint_computer: exports_external.looseObject({
|
|
17816
|
-
ok: exports_external.boolean(),
|
|
17817
|
-
idempotent: exports_external.boolean().optional(),
|
|
17818
|
-
snapshot: snapshot.optional(),
|
|
17819
|
-
reason: exports_external.string().optional(),
|
|
17820
|
-
currentSnapshotId: nullableString.optional()
|
|
17821
|
-
}),
|
|
17822
17811
|
transition_thread: exports_external.looseObject({ status: exports_external.string() }),
|
|
17823
17812
|
request: exports_external.looseObject({
|
|
17824
17813
|
requestId: id,
|
|
@@ -18597,8 +18586,6 @@ var MCP_TOOL_ANNOTATIONS = {
|
|
|
18597
18586
|
attach_computer: additive,
|
|
18598
18587
|
get_computer: readOnly,
|
|
18599
18588
|
computer_inventory: readOnly,
|
|
18600
|
-
publish_topic_computer: additive,
|
|
18601
|
-
checkpoint_computer: additive,
|
|
18602
18589
|
transition_thread: additive,
|
|
18603
18590
|
request: additive,
|
|
18604
18591
|
respond: additive,
|
|
@@ -18696,6 +18683,8 @@ var ORIENTATION = `Arbor is your team's deliberation room and shared memory —
|
|
|
18696
18683
|
|
|
18697
18684
|
Meaningful friction with Arbor itself can be privately contributed through \`contribute_arbor_feedback\`; the raw body is never posted into the current Thread or the shared Arbor Feedback forum. Browse the curated shared forum with \`feedback_tree\` → \`feedback_recall\` → \`feedback_thread_get\`.
|
|
18698
18685
|
|
|
18686
|
+
Arbor's MCP is the collaboration and durable-knowledge surface. Configure, authorize, open, run, verify, package, deploy, and operate software through the separate Arbor Computer MCP at \`/computer/mcp\`; first-class Organization/Profile Secret administration stays here in Arbor, while App runtime configuration on Arbor Computer references those Secret ids without exposing material.
|
|
18687
|
+
|
|
18699
18688
|
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. 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\`.
|
|
18700
18689
|
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.
|
|
18701
18690
|
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).
|
|
@@ -18754,23 +18743,23 @@ var ACTION_DEFINITIONS = [
|
|
|
18754
18743
|
{
|
|
18755
18744
|
name: "github_repositories",
|
|
18756
18745
|
title: "List installed GitHub repositories",
|
|
18757
|
-
description: "READ ONLY: List repositories covered by this organization's verified Arbor Computer GitHub App
|
|
18746
|
+
description: "READ ONLY: List repositories covered by this organization's verified Arbor Computer GitHub App, returning ids and metadata but never credentials. Use github_connect_repository to authorize one at an Arbor scope; listing alone grants no Computer access.",
|
|
18758
18747
|
inputSchema: { ...PAGINATION_INPUT },
|
|
18759
|
-
surfaces: ["mcp", "cli"],
|
|
18748
|
+
surfaces: ["computer-mcp", "cli"],
|
|
18760
18749
|
toolset: "projects",
|
|
18761
18750
|
run: forward("github.repositories")
|
|
18762
18751
|
},
|
|
18763
18752
|
{
|
|
18764
18753
|
name: "github_connect_repository",
|
|
18765
18754
|
title: "Connect a GitHub repository",
|
|
18766
|
-
description: "Connect
|
|
18755
|
+
description: "Connect or disconnect a listed GitHub repository at organization, Space, Topic, or Thread scope so matching descendant Computers may materialize it. The caller must manage that scope, GitHub installation coverage remains a separate gate, and get_computer shows the effective grant.",
|
|
18767
18756
|
inputSchema: {
|
|
18768
18757
|
repositoryId: exports_external.string().describe("installed repository id from github_repositories, ghr_…"),
|
|
18769
18758
|
scope: exports_external.enum(["organization", "space", "topic", "thread"]).describe("the Arbor scope that may use this repository's context; topic is the durable choice for a project every Thread under it develops"),
|
|
18770
18759
|
scopeId: exports_external.string().describe("id matching the selected scope"),
|
|
18771
18760
|
connected: exports_external.boolean().describe("true to connect; false to disconnect recoverably")
|
|
18772
18761
|
},
|
|
18773
|
-
surfaces: ["mcp", "cli"],
|
|
18762
|
+
surfaces: ["computer-mcp", "cli"],
|
|
18774
18763
|
toolset: "projects",
|
|
18775
18764
|
run: forward("github.set_repository_connection")
|
|
18776
18765
|
},
|
|
@@ -19151,7 +19140,7 @@ var ACTION_DEFINITIONS = [
|
|
|
19151
19140
|
setupScript: exports_external.string().nullable().optional().describe("optional strict Bash setup run after connected repositories materialize, or null to clear"),
|
|
19152
19141
|
repositories: exports_external.array(exports_external.string()).max(MAX_COMPUTER_REPOSITORIES).nullable().optional().describe("connected GitHub repositories to materialize into a fresh reusable base and allow for later Git calls; [] clears and null inherits")
|
|
19153
19142
|
},
|
|
19154
|
-
surfaces: ["mcp", "cli"],
|
|
19143
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19155
19144
|
toolset: "structure",
|
|
19156
19145
|
run: forward("computer_config.set_organization")
|
|
19157
19146
|
},
|
|
@@ -19166,7 +19155,7 @@ var ACTION_DEFINITIONS = [
|
|
|
19166
19155
|
setupScript: exports_external.string().nullable().optional().describe("optional strict Bash setup run after connected repositories materialize, or null to clear"),
|
|
19167
19156
|
repositories: exports_external.array(exports_external.string()).max(MAX_COMPUTER_REPOSITORIES).nullable().optional().describe("connected GitHub repositories to materialize into a fresh reusable base and allow for later Git calls; [] clears and null inherits")
|
|
19168
19157
|
},
|
|
19169
|
-
surfaces: ["mcp", "cli"],
|
|
19158
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19170
19159
|
toolset: "structure",
|
|
19171
19160
|
run: forward("computer_config.set_space")
|
|
19172
19161
|
},
|
|
@@ -19182,7 +19171,7 @@ var ACTION_DEFINITIONS = [
|
|
|
19182
19171
|
repositories: exports_external.array(exports_external.string()).max(MAX_COMPUTER_REPOSITORIES).nullable().optional().describe("connected GitHub repositories to materialize into a fresh reusable base and allow for later Git calls; [] clears and null inherits"),
|
|
19183
19172
|
...LIVE_PREVIEW_CONFIG_INPUT
|
|
19184
19173
|
},
|
|
19185
|
-
surfaces: ["mcp", "cli"],
|
|
19174
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19186
19175
|
toolset: "structure",
|
|
19187
19176
|
run: forward("computer_config.set_topic")
|
|
19188
19177
|
},
|
|
@@ -19198,18 +19187,18 @@ var ACTION_DEFINITIONS = [
|
|
|
19198
19187
|
repositories: exports_external.array(exports_external.string()).max(MAX_COMPUTER_REPOSITORIES).nullable().optional().describe("connected GitHub repositories to materialize into a fresh reusable base and allow for later Git calls; [] clears and null inherits"),
|
|
19199
19188
|
...LIVE_PREVIEW_CONFIG_INPUT
|
|
19200
19189
|
},
|
|
19201
|
-
surfaces: ["mcp", "cli"],
|
|
19190
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19202
19191
|
toolset: "structure",
|
|
19203
19192
|
run: forward("computer_config.set_thread")
|
|
19204
19193
|
},
|
|
19205
19194
|
{
|
|
19206
19195
|
name: "attach_computer",
|
|
19207
19196
|
title: "Attach a Thread computer",
|
|
19208
|
-
description: "
|
|
19197
|
+
description: "Create or reuse a Thread's stable Computer identity and mint an immutable collaborator receipt without starting provider runtime. Normally computer_open performs this attachment as part of opening; use this tool only when durable protocol identity is needed separately.",
|
|
19209
19198
|
inputSchema: {
|
|
19210
19199
|
threadId: exports_external.string().describe("the Thread whose computer you are attaching, thr_…")
|
|
19211
19200
|
},
|
|
19212
|
-
surfaces: ["mcp", "cli"],
|
|
19201
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19213
19202
|
toolset: "loop",
|
|
19214
19203
|
run: forward("computer.attach")
|
|
19215
19204
|
},
|
|
@@ -19235,7 +19224,7 @@ var ACTION_DEFINITIONS = [
|
|
|
19235
19224
|
idempotencyKey: exports_external.string().min(1).max(200).describe("stable key for this ONE replacement request; reuse it unchanged for retries"),
|
|
19236
19225
|
reason: exports_external.string().min(1).max(500).describe("concise auditable reason for replacing the active generation")
|
|
19237
19226
|
},
|
|
19238
|
-
surfaces: ["
|
|
19227
|
+
surfaces: ["computer-mcp", "computer-cli"],
|
|
19239
19228
|
toolset: "loop",
|
|
19240
19229
|
run: forward("computer_runtime.reprovision")
|
|
19241
19230
|
},
|
|
@@ -19554,50 +19543,23 @@ var ACTION_DEFINITIONS = [
|
|
|
19554
19543
|
threadId: exports_external.string().describe("the Thread, thr_…"),
|
|
19555
19544
|
lineageLimit: exports_external.number().int().min(1).max(100).optional().describe("recent receipts/lineage rows (default 20)")
|
|
19556
19545
|
},
|
|
19557
|
-
surfaces: ["mcp", "cli"],
|
|
19546
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19558
19547
|
toolset: "loop",
|
|
19559
19548
|
run: forward("computer.get")
|
|
19560
19549
|
},
|
|
19561
19550
|
{
|
|
19562
19551
|
name: "computer_inventory",
|
|
19563
19552
|
title: "Inventory organization Computers",
|
|
19564
|
-
description: "READ ONLY: Inventory materialized Thread Computers in your organization, optionally scoped to
|
|
19553
|
+
description: "READ ONLY: Inventory materialized Thread Computers in your organization, optionally scoped to a Topic, with active generation, compatibility, lifecycle counts, and factual anomaly flags. Organization owner/admin only; this is evidence, not a deletion recommendation.",
|
|
19565
19554
|
inputSchema: {
|
|
19566
19555
|
topicId: exports_external.string().optional().describe("optional Topic to scope the inventory"),
|
|
19567
19556
|
limit: exports_external.number().int().min(1).max(50).optional().describe("rows to return (default 25)"),
|
|
19568
19557
|
cursor: exports_external.string().optional().describe("active Computer id returned as nextCursor by the previous page")
|
|
19569
19558
|
},
|
|
19570
|
-
surfaces: ["mcp", "cli"],
|
|
19559
|
+
surfaces: ["computer-mcp", "cli"],
|
|
19571
19560
|
toolset: "admin",
|
|
19572
19561
|
run: forward("computer.inventory")
|
|
19573
19562
|
},
|
|
19574
|
-
{
|
|
19575
|
-
name: "publish_topic_computer",
|
|
19576
|
-
title: "Publish a Topic computer base",
|
|
19577
|
-
description: "After the first attached collaborator finishes the Topic setup recipe and Currybox checkpoints `/workspace`, publish that opaque restore handle as the reusable Topic base. Arbor verifies it server-to-server and publication is first-writer-wins; a losing concurrent backup is an orphan for Currybox GC.",
|
|
19578
|
-
inputSchema: {
|
|
19579
|
-
sessionId: exports_external.string().describe("the Arbor computer session that materialized the base, cms_…"),
|
|
19580
|
-
providerRef: exports_external.string().describe("the opaque restore handle returned by `currybox checkpoint`")
|
|
19581
|
-
},
|
|
19582
|
-
surfaces: ["mcp", "cli"],
|
|
19583
|
-
toolset: "loop",
|
|
19584
|
-
run: forward("computer.publish_topic_base")
|
|
19585
|
-
},
|
|
19586
|
-
{
|
|
19587
|
-
name: "checkpoint_computer",
|
|
19588
|
-
title: "Checkpoint a Thread computer",
|
|
19589
|
-
description: "After a complete unit of work, checkpoint `/workspace` through Currybox, then publish its opaque handle here to append immutable Thread lineage and advance the current pointer atomically. Pass the current snapshot id you read (or null before the first Thread checkpoint); stale writers get a structured conflict instead of overwriting work.",
|
|
19590
|
-
inputSchema: {
|
|
19591
|
-
computerId: exports_external.string().describe("the stable Thread computer, cmp_…"),
|
|
19592
|
-
sessionId: exports_external.string().optional().describe("the producing agent session, cms_…; Currybox service sweeps omit it"),
|
|
19593
|
-
providerRef: exports_external.string().describe("the opaque restore handle returned by `currybox checkpoint`"),
|
|
19594
|
-
expectedCurrentSnapshotId: exports_external.string().nullable().describe("the current Thread snapshot id you read, or null before its first checkpoint"),
|
|
19595
|
-
parentSnapshotId: exports_external.string().optional().describe("first checkpoint only: the Topic base parent when the session cannot supply it")
|
|
19596
|
-
},
|
|
19597
|
-
surfaces: ["mcp", "cli"],
|
|
19598
|
-
toolset: "loop",
|
|
19599
|
-
run: forward("computer.checkpoint")
|
|
19600
|
-
},
|
|
19601
19563
|
{
|
|
19602
19564
|
name: "set_ways_to_help",
|
|
19603
19565
|
title: "Set a thread's ways to help",
|
|
@@ -20592,7 +20554,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20592
20554
|
bindingId: exports_external.string().optional().describe("unbind: binding id, sbd_…")
|
|
20593
20555
|
},
|
|
20594
20556
|
surfaces: ["mcp"],
|
|
20595
|
-
toolset: "
|
|
20557
|
+
toolset: "secrets",
|
|
20596
20558
|
run: dispatch({
|
|
20597
20559
|
list: "secret.list",
|
|
20598
20560
|
get: "secret.get",
|
|
@@ -20746,7 +20708,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20746
20708
|
inputSchema: {
|
|
20747
20709
|
includeArchived: exports_external.boolean().optional().describe("include archived Apps you are authorized to inspect")
|
|
20748
20710
|
},
|
|
20749
|
-
surfaces: ["mcp"],
|
|
20711
|
+
surfaces: ["computer-mcp"],
|
|
20750
20712
|
toolset: "apps",
|
|
20751
20713
|
run: forward("app.list")
|
|
20752
20714
|
},
|
|
@@ -20755,7 +20717,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20755
20717
|
title: "Read an App",
|
|
20756
20718
|
description: "READ ONLY: Inspect one App's stable URL, active deployment, pending control intent, deployment history, provider health, deployment-aware redacted configuration readiness for the active release, and durable state generation. It cannot modify App state, deployment state, access, or durable SQLite; use its activeDeploymentId as app_activate's expectedActiveDeploymentId.",
|
|
20757
20719
|
inputSchema: { appId: exports_external.string().describe("the App, app_…") },
|
|
20758
|
-
surfaces: ["mcp", "cli"],
|
|
20720
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20759
20721
|
toolset: "apps",
|
|
20760
20722
|
run: forward("app.get")
|
|
20761
20723
|
},
|
|
@@ -20772,7 +20734,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20772
20734
|
discovery: exports_external.enum(["unlisted", "listed"]).nullable().optional().describe("public Apps only; default unlisted"),
|
|
20773
20735
|
stateMode: exports_external.enum(["none", "durable"]).describe("durable allocates Space-only isolated facet SQLite; deployment never resets it")
|
|
20774
20736
|
},
|
|
20775
|
-
surfaces: ["mcp", "cli"],
|
|
20737
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20776
20738
|
toolset: "apps",
|
|
20777
20739
|
run: forward("app.create")
|
|
20778
20740
|
},
|
|
@@ -20788,7 +20750,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20788
20750
|
])).describe("complete desired runtime config object keyed by environment variable name"),
|
|
20789
20751
|
dryRun: exports_external.boolean().optional().describe("validate and plan without mutating")
|
|
20790
20752
|
},
|
|
20791
|
-
surfaces: ["mcp", "cli"],
|
|
20753
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20792
20754
|
toolset: "apps",
|
|
20793
20755
|
run: forward("app.config_apply")
|
|
20794
20756
|
},
|
|
@@ -20801,7 +20763,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20801
20763
|
bundleId: exports_external.string().describe("verified immutable AppBundle, bnd_…"),
|
|
20802
20764
|
idempotencyKey: exports_external.string().min(1).max(200).describe("stable key for this one logical deployment attempt")
|
|
20803
20765
|
},
|
|
20804
|
-
surfaces: ["mcp", "cli"],
|
|
20766
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20805
20767
|
toolset: "apps",
|
|
20806
20768
|
run: forward("app.deploy")
|
|
20807
20769
|
},
|
|
@@ -20825,7 +20787,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20825
20787
|
appId: exports_external.string().describe("owning App, app_…"),
|
|
20826
20788
|
deploymentId: exports_external.string().describe("deployment, dep_…")
|
|
20827
20789
|
},
|
|
20828
|
-
surfaces: ["mcp"],
|
|
20790
|
+
surfaces: ["computer-mcp"],
|
|
20829
20791
|
toolset: "apps",
|
|
20830
20792
|
run: forward("app.deployment_get")
|
|
20831
20793
|
},
|
|
@@ -20838,7 +20800,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20838
20800
|
path: exports_external.string().min(1).max(2048).describe("relative App path beginning with /; may include a query string"),
|
|
20839
20801
|
method: exports_external.enum(["GET", "HEAD"]).optional().describe("safe read method; default GET")
|
|
20840
20802
|
},
|
|
20841
|
-
surfaces: ["mcp", "cli"],
|
|
20803
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20842
20804
|
toolset: "apps",
|
|
20843
20805
|
run: forward("app.fetch")
|
|
20844
20806
|
},
|
|
@@ -20854,7 +20816,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20854
20816
|
body: exports_external.string().optional().describe("UTF-8 request body; mutually exclusive with json"),
|
|
20855
20817
|
contentType: exports_external.string().max(160).optional().describe("body media type; allowed only with body, default text/plain; charset=utf-8")
|
|
20856
20818
|
},
|
|
20857
|
-
surfaces: ["mcp", "cli"],
|
|
20819
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20858
20820
|
toolset: "apps",
|
|
20859
20821
|
run: forward("app.request")
|
|
20860
20822
|
},
|
|
@@ -20867,7 +20829,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20867
20829
|
deploymentId: exports_external.string().describe("prepared candidate, dep_…"),
|
|
20868
20830
|
expectedActiveDeploymentId: exports_external.string().nullable().describe("active deployment from app_get, or null before first activation")
|
|
20869
20831
|
},
|
|
20870
|
-
surfaces: ["mcp", "cli"],
|
|
20832
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20871
20833
|
toolset: "apps",
|
|
20872
20834
|
run: forward("app.activate")
|
|
20873
20835
|
},
|
|
@@ -20880,7 +20842,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20880
20842
|
deploymentId: exports_external.string().describe("superseded deployment to restore, dep_…"),
|
|
20881
20843
|
expectedActiveDeploymentId: exports_external.string().describe("current active deployment from app_get")
|
|
20882
20844
|
},
|
|
20883
|
-
surfaces: ["mcp", "cli"],
|
|
20845
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20884
20846
|
toolset: "apps",
|
|
20885
20847
|
run: forward("app.rollback")
|
|
20886
20848
|
},
|
|
@@ -20906,7 +20868,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20906
20868
|
access: exports_external.enum(["space", "public"]).describe("new gateway access"),
|
|
20907
20869
|
discovery: exports_external.enum(["unlisted", "listed"]).nullable().optional().describe("required concept only for public Apps")
|
|
20908
20870
|
},
|
|
20909
|
-
surfaces: ["mcp", "cli"],
|
|
20871
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20910
20872
|
toolset: "apps",
|
|
20911
20873
|
run: forward("app.set_access")
|
|
20912
20874
|
},
|
|
@@ -20919,7 +20881,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20919
20881
|
expectedGeneration: exports_external.number().int().min(0).describe("state generation from app_get"),
|
|
20920
20882
|
idempotencyKey: exports_external.string().min(1).max(200).describe("stable key for this one deliberate reset")
|
|
20921
20883
|
},
|
|
20922
|
-
surfaces: ["mcp", "cli"],
|
|
20884
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20923
20885
|
toolset: "apps",
|
|
20924
20886
|
run: forward("app.reset_state")
|
|
20925
20887
|
},
|
|
@@ -20933,7 +20895,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20933
20895
|
expectedGeneration: exports_external.number().int().min(0).describe("state generation from app_get"),
|
|
20934
20896
|
idempotencyKey: exports_external.string().min(1).max(200).describe("stable key for this one deliberate recovery")
|
|
20935
20897
|
},
|
|
20936
|
-
surfaces: ["mcp", "cli"],
|
|
20898
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20937
20899
|
toolset: "apps",
|
|
20938
20900
|
run: forward("app.restore_state")
|
|
20939
20901
|
},
|
|
@@ -20942,7 +20904,7 @@ var ACTION_DEFINITIONS = [
|
|
|
20942
20904
|
title: "Archive an App",
|
|
20943
20905
|
description: "Remove an App from active serving while retaining its stable identity, deployment timeline, receipts, durable state, and release history for audit and recovery. Archive is a lifecycle-only transition.",
|
|
20944
20906
|
inputSchema: { appId: exports_external.string().describe("target App, app_…") },
|
|
20945
|
-
surfaces: ["mcp", "cli"],
|
|
20907
|
+
surfaces: ["computer-mcp", "cli"],
|
|
20946
20908
|
toolset: "apps",
|
|
20947
20909
|
run: forward("app.archive")
|
|
20948
20910
|
},
|
|
@@ -20993,11 +20955,70 @@ var ACTIONS = ACTION_DEFINITIONS.map((action) => {
|
|
|
20993
20955
|
});
|
|
20994
20956
|
|
|
20995
20957
|
// src/config.ts
|
|
20996
|
-
import {
|
|
20958
|
+
import {
|
|
20959
|
+
chmodSync,
|
|
20960
|
+
existsSync,
|
|
20961
|
+
mkdirSync,
|
|
20962
|
+
readFileSync,
|
|
20963
|
+
rmSync,
|
|
20964
|
+
statSync,
|
|
20965
|
+
writeFileSync
|
|
20966
|
+
} from "node:fs";
|
|
20997
20967
|
import { homedir } from "node:os";
|
|
20998
20968
|
import { dirname, join } from "node:path";
|
|
20999
20969
|
var CONFIG_PATH = process.env.ARBOR_CONFIG ?? join(homedir(), ".arbor", "config.json");
|
|
21000
20970
|
var DEFAULT_API_URL = process.env.ARBOR_API_URL ?? "https://arborthreads.com";
|
|
20971
|
+
var DEFAULT_CONFIG_PATH = join(homedir(), ".arbor", "config.json");
|
|
20972
|
+
var DEFAULT_TOKEN_OVERWRITE_REFUSAL = "refusing to overwrite ~/.arbor/config.json — a token already exists. Set ARBOR_CONFIG to an isolated path (e.g. ~/.arbor/<agent>.config.json) and retry.";
|
|
20973
|
+
var LOCK_TIMEOUT_MS = 5000;
|
|
20974
|
+
var LOCK_STALE_MS = 30000;
|
|
20975
|
+
var LOCK_POLL_MS = 20;
|
|
20976
|
+
function sleepSync(ms) {
|
|
20977
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
20978
|
+
}
|
|
20979
|
+
function acquireConfigLock(configPath) {
|
|
20980
|
+
const lockPath = `${configPath}.lock`;
|
|
20981
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
20982
|
+
for (;; ) {
|
|
20983
|
+
try {
|
|
20984
|
+
mkdirSync(lockPath);
|
|
20985
|
+
return () => {
|
|
20986
|
+
try {
|
|
20987
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
20988
|
+
} catch {}
|
|
20989
|
+
};
|
|
20990
|
+
} catch (err) {
|
|
20991
|
+
if (err?.code !== "EEXIST")
|
|
20992
|
+
throw err;
|
|
20993
|
+
try {
|
|
20994
|
+
if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
|
|
20995
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
20996
|
+
continue;
|
|
20997
|
+
}
|
|
20998
|
+
} catch {
|
|
20999
|
+
continue;
|
|
21000
|
+
}
|
|
21001
|
+
if (Date.now() >= deadline) {
|
|
21002
|
+
throw new Error(`timed out waiting for another arbor process to finish writing ${configPath}. If none is running, remove ${lockPath}.`);
|
|
21003
|
+
}
|
|
21004
|
+
sleepSync(LOCK_POLL_MS);
|
|
21005
|
+
}
|
|
21006
|
+
}
|
|
21007
|
+
}
|
|
21008
|
+
function defaultConfigOverwriteBlocked(opts) {
|
|
21009
|
+
const isolated = opts?.isolated ?? Boolean(process.env.ARBOR_CONFIG);
|
|
21010
|
+
const path = opts?.path ?? DEFAULT_CONFIG_PATH;
|
|
21011
|
+
if (isolated)
|
|
21012
|
+
return false;
|
|
21013
|
+
if (!existsSync(path))
|
|
21014
|
+
return false;
|
|
21015
|
+
try {
|
|
21016
|
+
const cfg = JSON.parse(readFileSync(path, "utf8"));
|
|
21017
|
+
return Boolean(cfg.token);
|
|
21018
|
+
} catch {
|
|
21019
|
+
return false;
|
|
21020
|
+
}
|
|
21021
|
+
}
|
|
21001
21022
|
function loadConfig() {
|
|
21002
21023
|
const envToken = process.env.ARBOR_TOKEN || undefined;
|
|
21003
21024
|
const envUrl = process.env.ARBOR_API_URL || undefined;
|
|
@@ -21009,15 +21030,32 @@ function loadConfig() {
|
|
|
21009
21030
|
}
|
|
21010
21031
|
return { apiUrl: envUrl ?? DEFAULT_API_URL, token: envToken };
|
|
21011
21032
|
}
|
|
21012
|
-
function
|
|
21013
|
-
mkdirSync(dirname(
|
|
21014
|
-
|
|
21015
|
-
|
|
21016
|
-
|
|
21033
|
+
function writeConfigGuarded(opts) {
|
|
21034
|
+
mkdirSync(dirname(opts.configPath), { recursive: true });
|
|
21035
|
+
const release = acquireConfigLock(opts.configPath);
|
|
21036
|
+
try {
|
|
21037
|
+
if (!opts.allowDefaultTokenOverwrite && defaultConfigOverwriteBlocked({ isolated: opts.isolated, path: opts.defaultPath })) {
|
|
21038
|
+
throw new Error(DEFAULT_TOKEN_OVERWRITE_REFUSAL);
|
|
21039
|
+
}
|
|
21040
|
+
writeFileSync(opts.configPath, `${JSON.stringify(opts.cfg, null, 2)}
|
|
21041
|
+
`, { mode: 384 });
|
|
21042
|
+
chmodSync(opts.configPath, 384);
|
|
21043
|
+
} finally {
|
|
21044
|
+
release();
|
|
21045
|
+
}
|
|
21046
|
+
}
|
|
21047
|
+
function saveConfig(cfg, opts) {
|
|
21048
|
+
writeConfigGuarded({
|
|
21049
|
+
cfg,
|
|
21050
|
+
configPath: CONFIG_PATH,
|
|
21051
|
+
defaultPath: DEFAULT_CONFIG_PATH,
|
|
21052
|
+
isolated: Boolean(process.env.ARBOR_CONFIG),
|
|
21053
|
+
allowDefaultTokenOverwrite: opts?.allowDefaultTokenOverwrite
|
|
21054
|
+
});
|
|
21017
21055
|
}
|
|
21018
21056
|
function clearToken() {
|
|
21019
21057
|
const cfg = loadConfig();
|
|
21020
|
-
saveConfig({ apiUrl: cfg.apiUrl });
|
|
21058
|
+
saveConfig({ apiUrl: cfg.apiUrl }, { allowDefaultTokenOverwrite: true });
|
|
21021
21059
|
}
|
|
21022
21060
|
|
|
21023
21061
|
// src/errors.ts
|
|
@@ -21747,20 +21785,38 @@ async function runObjectVerb(positionals, flags, ctx) {
|
|
|
21747
21785
|
// src/connect.ts
|
|
21748
21786
|
var DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
21749
21787
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21788
|
+
function bareConnectUrl(apiUrl, returnedUrl) {
|
|
21789
|
+
const fallback = `${apiUrl.replace(/\/$/, "")}/connect`;
|
|
21790
|
+
if (!returnedUrl)
|
|
21791
|
+
return fallback;
|
|
21792
|
+
try {
|
|
21793
|
+
return `${new URL(returnedUrl).origin}/connect`;
|
|
21794
|
+
} catch {
|
|
21795
|
+
return fallback;
|
|
21796
|
+
}
|
|
21797
|
+
}
|
|
21750
21798
|
async function connect(opts) {
|
|
21799
|
+
if (defaultConfigOverwriteBlocked()) {
|
|
21800
|
+
throw new Error(DEFAULT_TOKEN_OVERWRITE_REFUSAL);
|
|
21801
|
+
}
|
|
21751
21802
|
const apiUrl = opts.url ?? loadConfig().apiUrl;
|
|
21752
21803
|
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
21753
|
-
const body = JSON.stringify({ pairingKey: opts.pairingKey });
|
|
21754
21804
|
const headers = { "content-type": "application/json" };
|
|
21755
|
-
const
|
|
21805
|
+
const body = JSON.stringify({ pairingKey: opts.pairingKey });
|
|
21806
|
+
const initRes = await fetch(`${apiUrl}/api/agent/connect`, {
|
|
21807
|
+
method: "POST",
|
|
21808
|
+
headers,
|
|
21809
|
+
body
|
|
21810
|
+
});
|
|
21756
21811
|
const init = await initRes.json().catch(() => ({}));
|
|
21757
21812
|
if (!initRes.ok || !init.url || !init.userCode) {
|
|
21758
21813
|
throw new Error(`could not start pairing (${initRes.status}): ${init.error ?? "is the server reachable?"}`);
|
|
21759
21814
|
}
|
|
21815
|
+
const approvalUrl = bareConnectUrl(apiUrl, init.url);
|
|
21760
21816
|
process.stderr.write(`
|
|
21761
21817
|
Pairing against ${apiUrl}
|
|
21762
21818
|
` + ` Ask the person who will manage this agent${init.agentName ? ` (“${init.agentName}”)` : ""} to approve it:
|
|
21763
|
-
` + ` 1. open ${
|
|
21819
|
+
` + ` 1. open ${approvalUrl}
|
|
21764
21820
|
` + ` 2. enter the code: ${init.userCode}
|
|
21765
21821
|
|
|
21766
21822
|
` + ` Only approve an agent you intend to manage.
|
|
@@ -21835,7 +21891,7 @@ async function login(opts) {
|
|
|
21835
21891
|
});
|
|
21836
21892
|
const tok = await tokRes.json().catch(() => ({}));
|
|
21837
21893
|
if (tok.access_token) {
|
|
21838
|
-
saveConfig({ apiUrl, token: tok.access_token });
|
|
21894
|
+
saveConfig({ apiUrl, token: tok.access_token }, { allowDefaultTokenOverwrite: true });
|
|
21839
21895
|
return;
|
|
21840
21896
|
}
|
|
21841
21897
|
if (tok.error === "authorization_pending")
|
|
@@ -22037,7 +22093,7 @@ async function main() {
|
|
|
22037
22093
|
if (!token)
|
|
22038
22094
|
throw new UsageError("usage: arbor auth <token> [--url <api-url>]");
|
|
22039
22095
|
const url2 = stringFlag(flags.url, "url") ?? loadConfig().apiUrl;
|
|
22040
|
-
saveConfig({ apiUrl: url2, token });
|
|
22096
|
+
saveConfig({ apiUrl: url2, token }, { allowDefaultTokenOverwrite: true });
|
|
22041
22097
|
advise(` ✓ Token saved (${CONFIG_PATH}).
|
|
22042
22098
|
`, ctx);
|
|
22043
22099
|
await renderMe(ctx, "auth");
|
package/package.json
CHANGED