@lambdacurry/arbor 0.24.2 → 0.24.4
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/README.md +8 -0
- package/dist/arbor.js +299 -79
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -101,6 +101,14 @@ and `attachment list` (`attachment_list`, `list_attachments`) recover receipts;
|
|
|
101
101
|
pages with `--cursor`. Finalized receipts replay with the same authentic token even after expiry.
|
|
102
102
|
Duplicate SHA-256 warnings on the same Thread are best-effort; concurrent uploads may not warn.
|
|
103
103
|
|
|
104
|
+
Several files work in one command: `arbor attachment upload a.png b.png --thread-id thr_… --description "A" --description "B"`.
|
|
105
|
+
Repeated `--description` flags pair with files by order (none = filename defaults, otherwise exactly one per
|
|
106
|
+
file; write a value that starts with `--` as `--description=<value>`; values are literal, so a leading `[` is text). A blank description or a path listed twice is an error. The whole batch is checked before the
|
|
107
|
+
first request and uploads one file at a time; a mid-batch failure prints what uploaded, what failed and what
|
|
108
|
+
was not attempted, and exits non-zero. `--dry-run` prints the plan and makes no network call. Every receipt
|
|
109
|
+
carries a `nextStep` (unbound until bound to `contribute`/`respond`, max 10 per contribution; descriptions are
|
|
110
|
+
permanent) and may warn when a description is reused for a different file on the same Thread.
|
|
111
|
+
|
|
104
112
|
MCP exposes **one** tool, `attachment`, with `upload_url` / `attach` / `get` / `list` verbs.
|
|
105
113
|
Refresh a stale client tool catalog if it still shows the retired individual tools, or use
|
|
106
114
|
`arbor attachment upload`. `thread get --view full` retains body/Attachment read-back.
|
package/dist/arbor.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "@lambdacurry/arbor",
|
|
5
|
-
version: "0.24.
|
|
5
|
+
version: "0.24.4",
|
|
6
6
|
description: "The Arbor CLI — a shared workspace for people and agents. The human + headless-agent write path over Arbor's guarded operation surface.",
|
|
7
7
|
keywords: [
|
|
8
8
|
"agents",
|
|
@@ -29,7 +29,7 @@ var package_default = {
|
|
|
29
29
|
access: "public"
|
|
30
30
|
},
|
|
31
31
|
scripts: {
|
|
32
|
-
arbor: "
|
|
32
|
+
arbor: "vpx src/bin.ts",
|
|
33
33
|
build: "bun build ./src/bin.ts --target=node --outfile=dist/arbor.js",
|
|
34
34
|
test: "vp test run --passWithNoTests",
|
|
35
35
|
typecheck: "tsc -b",
|
|
@@ -40,7 +40,6 @@ var package_default = {
|
|
|
40
40
|
"@arbor/core": "workspace:*",
|
|
41
41
|
"@types/node": "^26.2.0",
|
|
42
42
|
"just-bash": "3.4.2",
|
|
43
|
-
tsx: "^4.20.6",
|
|
44
43
|
typescript: "^5.8.0",
|
|
45
44
|
"vite-plus": "catalog:",
|
|
46
45
|
zod: "^4.4.3"
|
|
@@ -5162,6 +5161,7 @@ var ARTIFACT_TYPES = [
|
|
|
5162
5161
|
];
|
|
5163
5162
|
// ../core/src/blob/attachment-upload.ts
|
|
5164
5163
|
var MAX_ATTACHMENT_BYTES = 1e8;
|
|
5164
|
+
var ATTACHMENT_ATTACH_NEXT_STEP = "Unbound until you pass attachmentIds (max 10 per contribution) to contribute or respond. The description is permanent (it becomes the Artifact summary): to fix it before binding, re-upload and bind only the new id. Paste attachments[].markdown verbatim; don't rebuild it from the description.";
|
|
5165
5165
|
var AGENT_ATTACHMENT_MIME_TYPES = [
|
|
5166
5166
|
"image/png",
|
|
5167
5167
|
"image/jpeg",
|
|
@@ -6579,10 +6579,20 @@ var spaces = sqliteTable("spaces", {
|
|
|
6579
6579
|
defaultReviewDimensions: text("default_review_dimensions", { mode: "json" }).$type().notNull().default([]),
|
|
6580
6580
|
stage: text("stage").$type().notNull().default("exploration"),
|
|
6581
6581
|
systemKind: text("system_kind"),
|
|
6582
|
+
key: text("key"),
|
|
6582
6583
|
createdAt: ts("created_at").notNull()
|
|
6583
6584
|
}, (t) => ({
|
|
6584
6585
|
orgIdx: index("spaces_org_idx").on(t.orgId),
|
|
6585
|
-
systemKindUq: uniqueIndex("spaces_system_kind_uq").on(t.systemKind)
|
|
6586
|
+
systemKindUq: uniqueIndex("spaces_system_kind_uq").on(t.systemKind),
|
|
6587
|
+
orgKeyUq: uniqueIndex("spaces_org_key_uq").on(t.orgId, t.key)
|
|
6588
|
+
}));
|
|
6589
|
+
var spaceRetiredKeys = sqliteTable("space_retired_keys", {
|
|
6590
|
+
orgId: text("org_id").notNull().references(() => orgs.id),
|
|
6591
|
+
key: text("key").notNull(),
|
|
6592
|
+
spaceId: text("space_id").notNull().references(() => spaces.id),
|
|
6593
|
+
retiredAt: ts("retired_at").notNull()
|
|
6594
|
+
}, (t) => ({
|
|
6595
|
+
orgKeyUq: uniqueIndex("space_retired_keys_org_key_uq").on(t.orgId, t.key)
|
|
6586
6596
|
}));
|
|
6587
6597
|
var spaceMemberships = sqliteTable("space_memberships", {
|
|
6588
6598
|
id: text("id").primaryKey(),
|
|
@@ -6712,8 +6722,10 @@ var threads = sqliteTable("threads", {
|
|
|
6712
6722
|
waysToHelp: text("ways_to_help", { mode: "json" }).$type().notNull().default([]),
|
|
6713
6723
|
openQuestions: text("open_questions", { mode: "json" }).$type().notNull().default([]),
|
|
6714
6724
|
expectedOutput: text("expected_output").notNull().default(""),
|
|
6725
|
+
number: integer2("number"),
|
|
6715
6726
|
createdAt: ts("created_at").notNull()
|
|
6716
6727
|
}, (t) => ({
|
|
6728
|
+
spaceNumberUq: uniqueIndex("threads_space_number_uq").on(t.spaceId, t.number),
|
|
6717
6729
|
topicIdx: index("threads_topic_idx").on(t.topicId),
|
|
6718
6730
|
spaceIdx: index("threads_space_idx").on(t.spaceId),
|
|
6719
6731
|
lifecycleTypeIdx: index("threads_lifecycle_type_idx").on(t.lifecycleType),
|
|
@@ -8217,6 +8229,36 @@ var ORG_ADMIN_CONTAINMENT_PERMISSIONS = new Set([
|
|
|
8217
8229
|
"revoke"
|
|
8218
8230
|
]);
|
|
8219
8231
|
|
|
8232
|
+
// ../core/src/queries/artifact-base.ts
|
|
8233
|
+
var eventChronology = sql`_rowid_`;
|
|
8234
|
+
var SPACE_ARTIFACT_STATUSES = [
|
|
8235
|
+
"draft",
|
|
8236
|
+
"proposed",
|
|
8237
|
+
"active",
|
|
8238
|
+
"promoted",
|
|
8239
|
+
"needs-review",
|
|
8240
|
+
"superseded",
|
|
8241
|
+
"deprecated",
|
|
8242
|
+
"archived"
|
|
8243
|
+
];
|
|
8244
|
+
var artifactUpdatedAt = sql`coalesce((select max(${events.createdAt}) from ${events} where ${events.objectType} = 'artifact' and ${events.objectId} = ${artifacts.id}), ${artifacts.createdAt})`;
|
|
8245
|
+
// ../core/src/queries/thread-assignment.ts
|
|
8246
|
+
var assigneeColumns = {
|
|
8247
|
+
threadId: threadAssignments.threadId,
|
|
8248
|
+
assignedAt: threadAssignments.createdAt,
|
|
8249
|
+
assignedByProfileId: threadAssignments.assignedByProfileId,
|
|
8250
|
+
id: profiles.id,
|
|
8251
|
+
kind: profiles.kind,
|
|
8252
|
+
displayName: profiles.displayName,
|
|
8253
|
+
color: profiles.color,
|
|
8254
|
+
emoji: profiles.emoji
|
|
8255
|
+
};
|
|
8256
|
+
var lastActivityExpr = sql`coalesce(
|
|
8257
|
+
(select max(${contributions.createdAt}) from ${contributions}
|
|
8258
|
+
where ${contributions.threadId} = ${threads.id}),
|
|
8259
|
+
${threads.createdAt}
|
|
8260
|
+
)`;
|
|
8261
|
+
|
|
8220
8262
|
// ../core/src/ops/space.ts
|
|
8221
8263
|
var GUIDANCE_LIMITS = {
|
|
8222
8264
|
maxItems: 12,
|
|
@@ -8235,6 +8277,11 @@ var MAX_WAY_LEN = WAYS_TO_HELP_LIMITS.maxItemLength;
|
|
|
8235
8277
|
|
|
8236
8278
|
// ../core/src/ops/computer-output.ts
|
|
8237
8279
|
var COMPUTER_OUTPUT_DEFAULT_STALE_MS = 24 * 60 * 60 * 1000;
|
|
8280
|
+
var OPERATION_LOSS_REASONS = new Set([
|
|
8281
|
+
"process_provider_lost",
|
|
8282
|
+
"workspace_expired",
|
|
8283
|
+
"run_runtime_lost"
|
|
8284
|
+
]);
|
|
8238
8285
|
|
|
8239
8286
|
// ../core/src/ops/computer-run-retention.ts
|
|
8240
8287
|
var COMPUTER_RUN_RETENTION_WARNING_MS = 24 * 60 * 60 * 1000;
|
|
@@ -8284,20 +8331,6 @@ var COMPUTER_WORKSPACE_RECOVERY_ATTACH_DEADLINE_MS = 31 * 60 * 1000;
|
|
|
8284
8331
|
var COMPUTER_WORKSPACE_RECOVERY_HEALTH_DEADLINE_MS = 2 * 60 * 1000;
|
|
8285
8332
|
var COMPUTER_WORKSPACE_RECOVERY_HEADROOM_MS = 2 * 60 * 1000;
|
|
8286
8333
|
var COMPUTER_WORKSPACE_RECOVERY_LEASE_MS = COMPUTER_WORKSPACE_RECOVERY_CHECKPOINT_DEADLINE_MS + COMPUTER_WORKSPACE_RECOVERY_ATTACH_DEADLINE_MS + COMPUTER_WORKSPACE_RECOVERY_HEALTH_DEADLINE_MS + COMPUTER_WORKSPACE_RECOVERY_HEADROOM_MS;
|
|
8287
|
-
|
|
8288
|
-
// ../core/src/queries/artifact-base.ts
|
|
8289
|
-
var eventChronology = sql`_rowid_`;
|
|
8290
|
-
var SPACE_ARTIFACT_STATUSES = [
|
|
8291
|
-
"draft",
|
|
8292
|
-
"proposed",
|
|
8293
|
-
"active",
|
|
8294
|
-
"promoted",
|
|
8295
|
-
"needs-review",
|
|
8296
|
-
"superseded",
|
|
8297
|
-
"deprecated",
|
|
8298
|
-
"archived"
|
|
8299
|
-
];
|
|
8300
|
-
var artifactUpdatedAt = sql`coalesce((select max(${events.createdAt}) from ${events} where ${events.objectType} = 'artifact' and ${events.objectId} = ${artifacts.id}), ${artifacts.createdAt})`;
|
|
8301
8334
|
// ../core/src/ops/artifact-git-intent.ts
|
|
8302
8335
|
var RETRYABLE_REPAIR = new Set([
|
|
8303
8336
|
"pending_push_unconfirmed",
|
|
@@ -8315,22 +8348,6 @@ var EMOJI_RE = new RegExp("^\\p{RGI_Emoji}$", "v");
|
|
|
8315
8348
|
var BASE_MS = Date.parse("2026-07-20T15:00:00.000Z");
|
|
8316
8349
|
// ../core/src/ops/agent-pairing.ts
|
|
8317
8350
|
var PAIRING_TTL_MS = 15 * 60 * 1000;
|
|
8318
|
-
// ../core/src/queries/thread-assignment.ts
|
|
8319
|
-
var assigneeColumns = {
|
|
8320
|
-
threadId: threadAssignments.threadId,
|
|
8321
|
-
assignedAt: threadAssignments.createdAt,
|
|
8322
|
-
assignedByProfileId: threadAssignments.assignedByProfileId,
|
|
8323
|
-
id: profiles.id,
|
|
8324
|
-
kind: profiles.kind,
|
|
8325
|
-
displayName: profiles.displayName,
|
|
8326
|
-
color: profiles.color,
|
|
8327
|
-
emoji: profiles.emoji
|
|
8328
|
-
};
|
|
8329
|
-
var lastActivityExpr = sql`coalesce(
|
|
8330
|
-
(select max(${contributions.createdAt}) from ${contributions}
|
|
8331
|
-
where ${contributions.threadId} = ${threads.id}),
|
|
8332
|
-
${threads.createdAt}
|
|
8333
|
-
)`;
|
|
8334
8351
|
// ../core/src/ops/package-cache.ts
|
|
8335
8352
|
var PACKAGE_CACHE_PENDING_TTL_MS = 15 * 60 * 1000;
|
|
8336
8353
|
var PACKAGE_CACHE_FINAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -8504,6 +8521,7 @@ var recallHit = looseObject({
|
|
|
8504
8521
|
excerpt: string2(),
|
|
8505
8522
|
space: spaceRef.nullable(),
|
|
8506
8523
|
threadId: nullableString,
|
|
8524
|
+
shortId: nullableString.optional(),
|
|
8507
8525
|
url: nullableString,
|
|
8508
8526
|
createdAt: timestamp
|
|
8509
8527
|
});
|
|
@@ -8989,6 +9007,7 @@ var orgMember = looseObject({
|
|
|
8989
9007
|
});
|
|
8990
9008
|
var thread3 = looseObject({
|
|
8991
9009
|
id,
|
|
9010
|
+
shortId: nullableString.optional(),
|
|
8992
9011
|
title: string2(),
|
|
8993
9012
|
objective: string2(),
|
|
8994
9013
|
status: string2().optional(),
|
|
@@ -9027,6 +9046,7 @@ var topic2 = looseObject({
|
|
|
9027
9046
|
var space3 = looseObject({
|
|
9028
9047
|
id,
|
|
9029
9048
|
title: string2(),
|
|
9049
|
+
key: nullableString.optional(),
|
|
9030
9050
|
purpose: nullableString.optional(),
|
|
9031
9051
|
stage: string2().optional(),
|
|
9032
9052
|
url: string2().optional()
|
|
@@ -10328,8 +10348,19 @@ var MCP_OUTPUT_SCHEMAS = {
|
|
|
10328
10348
|
uploadedByProfileId: id,
|
|
10329
10349
|
createdAt: timestamp,
|
|
10330
10350
|
attachments: array(attachment2),
|
|
10351
|
+
replayed: boolean2().optional(),
|
|
10352
|
+
nextStep: string2().optional(),
|
|
10331
10353
|
warnings: array(string2()).optional()
|
|
10332
10354
|
}),
|
|
10355
|
+
attachment_upload: looseObject({
|
|
10356
|
+
attachmentId: id.optional(),
|
|
10357
|
+
attachmentIds: array(id),
|
|
10358
|
+
attachments: array(attachment2),
|
|
10359
|
+
nextStep: string2().optional(),
|
|
10360
|
+
warnings: array(string2()).optional(),
|
|
10361
|
+
failed: array(looseObject({ file: string2(), error: string2() })).optional(),
|
|
10362
|
+
notAttempted: array(string2()).optional()
|
|
10363
|
+
}),
|
|
10333
10364
|
computer_export: looseObject({
|
|
10334
10365
|
computerSessionId: id,
|
|
10335
10366
|
replayed: boolean2(),
|
|
@@ -10421,8 +10452,6 @@ var MCP_OUTPUT_SCHEMAS = {
|
|
|
10421
10452
|
})
|
|
10422
10453
|
};
|
|
10423
10454
|
function outputSchemaFor(actionName) {
|
|
10424
|
-
if (actionName === "attachment_upload")
|
|
10425
|
-
actionName = "attachment_attach";
|
|
10426
10455
|
return MCP_OUTPUT_SCHEMAS[actionName];
|
|
10427
10456
|
}
|
|
10428
10457
|
|
|
@@ -11146,6 +11175,7 @@ var TLDRAW_EXEC_OPERATIONS_SCHEMA = array(TLDRAW_EXEC_OPERATION_SCHEMA).min(1).m
|
|
|
11146
11175
|
// ../actions/src/index.ts
|
|
11147
11176
|
var ARTIFACT_TYPE_DESCRIPTION = `semantic type (one of: ${ARTIFACT_TYPES.join(", ")})`;
|
|
11148
11177
|
var ARBOR_FEEDBACK_INPUT_TYPES = [...ARBOR_FEEDBACK_TYPES, "friction"];
|
|
11178
|
+
var SPACE_KEY_INPUT = string2().min(1).refine((key) => key.trim().length > 0, "space key cannot be blank");
|
|
11149
11179
|
var CONTRIBUTION_ADD_LINK_RELS = [
|
|
11150
11180
|
"cites",
|
|
11151
11181
|
"contests",
|
|
@@ -11287,6 +11317,17 @@ function proseList(description) {
|
|
|
11287
11317
|
function isProseList(schema) {
|
|
11288
11318
|
return PROSE_LISTS.has(schema);
|
|
11289
11319
|
}
|
|
11320
|
+
var VERBATIM_LISTS = registry();
|
|
11321
|
+
function verbatimList(description) {
|
|
11322
|
+
const schema = array(string2()).describe(description);
|
|
11323
|
+
PROSE_LISTS.add(schema, { literal: true });
|
|
11324
|
+
VERBATIM_LISTS.add(schema, { verbatim: true });
|
|
11325
|
+
return schema;
|
|
11326
|
+
}
|
|
11327
|
+
function isVerbatimList(schema) {
|
|
11328
|
+
return VERBATIM_LISTS.has(schema);
|
|
11329
|
+
}
|
|
11330
|
+
var ATTACHMENT_DESCRIPTION_HINT = "durable summary and default alt text; defaults to the filename when omitted; permanent once bound; 1-500 chars; give each file its own";
|
|
11290
11331
|
function forward(operation) {
|
|
11291
11332
|
const run = (ex, input) => ex.call(operation, input);
|
|
11292
11333
|
run.operation = operation;
|
|
@@ -11721,7 +11762,7 @@ var ACTION_DEFINITIONS = [
|
|
|
11721
11762
|
title: "Contribute to a thread",
|
|
11722
11763
|
description: "Add one additive point to a Thread; use stamp for agreement, respond/mark_as_response for requests, and links for deliberate reply/contest edges. Pass idempotencyKey (CLI: --idempotency-key) for safe retries.",
|
|
11723
11764
|
inputSchema: {
|
|
11724
|
-
threadId: string2().describe("the thread id, e.g. thr_
|
|
11765
|
+
threadId: string2().describe("the thread id, e.g. thr_…, or its short id, e.g. BUD-34"),
|
|
11725
11766
|
type: _enum(CONTRIBUTION_TYPES).describe("the contribution type (AD-066 taxonomy)"),
|
|
11726
11767
|
body: string2().min(1).describe(`the contribution markdown. ${CONTRIBUTION_BODY_GUIDANCE}`),
|
|
11727
11768
|
idempotencyKey: string2().min(1).max(200).optional().describe("stable key for this ONE logical contribution (≤200 chars), e.g. cron-run/work-item + output slot; safe retries return the original contribution. Reusing it with different content is a conflict."),
|
|
@@ -11880,7 +11921,7 @@ var ACTION_DEFINITIONS = [
|
|
|
11880
11921
|
title: "Edit a thread's title, objective, or topic",
|
|
11881
11922
|
description: "Correct a Thread's title or objective, or move it under another Topic of the same Space, patching only supplied fields; use it to make done clear or refile misfiled work instead of archiving and reopening. Status moves stay with `transition_thread`, column order with `--rank`, and the temporary help list with `charter` ways_to_help.",
|
|
11882
11923
|
inputSchema: {
|
|
11883
|
-
threadId: string2().describe("the thread to edit, thr_…"),
|
|
11924
|
+
threadId: string2().describe("the thread to edit, thr_… or a short id like BUD-34"),
|
|
11884
11925
|
title: string2().min(1).optional().describe("a new title — the question, outcome, investigation, instance, or standing concern"),
|
|
11885
11926
|
objective: string2().min(1).optional().describe("a new objective — what this Thread should accomplish; for finite work, what done means"),
|
|
11886
11927
|
topicId: string2().optional().describe("move the thread under this topic of the same space, top_…"),
|
|
@@ -11896,7 +11937,7 @@ var ACTION_DEFINITIONS = [
|
|
|
11896
11937
|
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.",
|
|
11897
11938
|
inputSchema: {
|
|
11898
11939
|
verb: _enum(["assign", "unassign"]).describe("add or remove responsibility"),
|
|
11899
|
-
threadId: string2().describe("the thread, thr_…"),
|
|
11940
|
+
threadId: string2().describe("the thread, thr_… or a short id like BUD-34"),
|
|
11900
11941
|
profileId: string2().optional().describe("the member to (un)assign, prof_…; omit for yourself")
|
|
11901
11942
|
},
|
|
11902
11943
|
surfaces: ["mcp", "cli"],
|
|
@@ -11997,7 +12038,7 @@ var ACTION_DEFINITIONS = [
|
|
|
11997
12038
|
title: "Replace a Thread computer override",
|
|
11998
12039
|
description: "DESTRUCTIVE REPLACE: Replace this Thread's entire sparse Computer override; omitted fields are cleared from the Thread layer and inherit Topic/Space/organization policy. This is config only and allocates no runtime; an existing Computer cannot switch provider, and repeating the same full layer is idempotent.",
|
|
11999
12040
|
inputSchema: {
|
|
12000
|
-
threadId: string2().describe("the Thread, thr_…"),
|
|
12041
|
+
threadId: string2().describe("the Thread, thr_… or a short id like BUD-34"),
|
|
12001
12042
|
backend: _enum(["worker-shell", "container"]).nullable().optional().describe("override execution tier, or null to inherit"),
|
|
12002
12043
|
containerProfile: string2().nullable().optional().describe("logical container profile; bun-typescript-v1 guarantees Bash, Git, GitHub CLI (gh), Bun 1.4.2, bunx, Node 26.5.1, pnpm 10.33.0, Python 3.13.15 (python/python3), jq, and ripgrep (rg), or null to clear inherited profile"),
|
|
12003
12044
|
setupScript: string2().nullable().optional().describe("optional strict Bash setup for a fresh base after repositories materialize; install scoped tools in /workspace/.local/bin so exec finds them and snapshots retain them"),
|
|
@@ -12013,7 +12054,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12013
12054
|
title: "Attach a Thread computer",
|
|
12014
12055
|
description: "Deprecated by AD-306: computer_run_start(threadId) attaches durable identity itself, so use this only to mint protocol identity with no Run at all. Creates or reuses the Thread's stable Computer identity and an immutable collaborator receipt without starting provider runtime.",
|
|
12015
12056
|
inputSchema: {
|
|
12016
|
-
threadId: string2().describe("the Thread whose computer you are attaching, thr_…")
|
|
12057
|
+
threadId: string2().describe("the Thread whose computer you are attaching, thr_… or a short id like BUD-34")
|
|
12017
12058
|
},
|
|
12018
12059
|
surfaces: ["cli"],
|
|
12019
12060
|
toolset: "loop",
|
|
@@ -12024,7 +12065,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12024
12065
|
title: "Open a Thread computer",
|
|
12025
12066
|
description: "Deprecated by AD-306: start work with computer_run_start(threadId, mode) instead; this alias remains one release and boots the parent Thread Computer, returning a computerSessionId. An optional owned active runId records a successful session attach as Run retention activity. Use it only for a legacy client that still addresses the parent by cms_ id; unsafe recovery is refused and unresolved newer workspace state stays fail-closed.",
|
|
12026
12067
|
inputSchema: {
|
|
12027
|
-
threadId: string2().describe("the Arbor Thread whose stable computer to open, thr_…"),
|
|
12068
|
+
threadId: string2().describe("the Arbor Thread whose stable computer to open, thr_… or a short id like BUD-34"),
|
|
12028
12069
|
runId: string2().optional().describe("optional owned active Run; successful open renews its retention deadline"),
|
|
12029
12070
|
idempotencyKey: string2().trim().min(1).max(200).optional().describe("optional explicit key for ONE distinct logical open; omission reuses the durable profile/Thread/recipe identity; changed options require a distinct intent; retry unchanged after response loss and never mint a new key to bypass unresolved work"),
|
|
12030
12071
|
vcpus: number2().int().min(1).max(4).optional().describe("optional container vCPU request"),
|
|
@@ -12039,7 +12080,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12039
12080
|
title: "Replace a Thread computer",
|
|
12040
12081
|
description: "DESTRUCTIVE RECOVERY: Replace an incompatible active Computer generation from the Thread's effective recipe when Arbor reports structural recipe incompatibility. Preserves the old generation unless the successor restores supported state and passes health.",
|
|
12041
12082
|
inputSchema: {
|
|
12042
|
-
threadId: string2().describe("the Thread whose active Computer generation is replaced, thr_…"),
|
|
12083
|
+
threadId: string2().describe("the Thread whose active Computer generation is replaced, thr_… or a short id like BUD-34"),
|
|
12043
12084
|
idempotencyKey: string2().min(1).max(200).describe("stable key for this ONE replacement request; reuse it unchanged for retries"),
|
|
12044
12085
|
reason: string2().min(1).max(500).describe("concise auditable reason for replacing the active generation")
|
|
12045
12086
|
},
|
|
@@ -12052,7 +12093,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12052
12093
|
title: "Start a run",
|
|
12053
12094
|
description: `Start an isolated write Run or shared/read Run with one idempotencyKey; pending admission returns runStartId for receipt observation while the lifecycle owner continues independently. Save work outside the temporary Computer and finish the Run when done; first use: read the Computer guide at ${AGENT_GUIDE_URLS.computer}.`,
|
|
12054
12095
|
inputSchema: {
|
|
12055
|
-
threadId: string2().optional().describe("the Thread whose Computer this Run works in, thr_
|
|
12096
|
+
threadId: string2().optional().describe("the Thread whose Computer this Run works in, thr_… or a short id like BUD-34; the canonical Run-first front door"),
|
|
12056
12097
|
computerSessionId: string2().optional().describe("legacy compatibility alternative to threadId: an existing parent computerSessionId, cms_…"),
|
|
12057
12098
|
mode: _enum(["read", "write"]).describe("write for mutable work; read for shared inspection"),
|
|
12058
12099
|
execution: _enum(["shared", "isolated"]).optional().describe("write Run placement; defaults to isolated. Use shared only for intentional live collaboration on the Thread Computer"),
|
|
@@ -12234,7 +12275,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12234
12275
|
title: "Capture public visual evidence",
|
|
12235
12276
|
description: "Capture a 1280×720 screenshot of one public HTTPS URL as Thread evidence without opening the project runtime. Returns capture metadata plus ready-to-place attachments[].markdown; put that Markdown exactly where the image belongs and bind the matching attachmentIds, while localhost and authenticated pages remain unavailable and the capture does not judge correctness.",
|
|
12236
12277
|
inputSchema: {
|
|
12237
|
-
threadId: string2().describe("the Arbor Thread that will own the evidence, thr_…"),
|
|
12278
|
+
threadId: string2().describe("the Arbor Thread that will own the evidence, thr_… or a short id like BUD-34"),
|
|
12238
12279
|
targetUrl: url().refine((value) => value.startsWith("https://"), "targetUrl must use HTTPS").describe("one public HTTPS URL to capture")
|
|
12239
12280
|
},
|
|
12240
12281
|
surfaces: ["computer-mcp", "computer-cli"],
|
|
@@ -12321,7 +12362,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12321
12362
|
filename: string2().min(1).max(1000).optional().describe("upload_url: original filename"),
|
|
12322
12363
|
mimeType: _enum(AGENT_ATTACHMENT_MIME_TYPES).optional().describe("upload_url: supported MIME matching the bytes"),
|
|
12323
12364
|
size: number2().optional().describe("upload_url: exact file size, at most 100000000 bytes; compress, split, or link larger files"),
|
|
12324
|
-
description: string2().min(1).max(500).optional(),
|
|
12365
|
+
description: string2().min(1).max(500).optional().describe(`upload_url: ${ATTACHMENT_DESCRIPTION_HINT}`),
|
|
12325
12366
|
sha256: string2().regex(/^[a-f0-9]{64}$/).optional().describe("upload_url: optional SHA-256 hex; also signed into the PUT checksum header"),
|
|
12326
12367
|
token: string2().optional().describe("attach: token from upload_url; first attach before attachExpiresAt, replay finalized receipts even after expiry"),
|
|
12327
12368
|
attachmentId: string2().optional().describe("get: attachment id, att_…"),
|
|
@@ -12345,7 +12386,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12345
12386
|
filename: string2().min(1).max(1000),
|
|
12346
12387
|
mimeType: _enum(AGENT_ATTACHMENT_MIME_TYPES),
|
|
12347
12388
|
size: number2(),
|
|
12348
|
-
description: string2().min(1).max(500).optional(),
|
|
12389
|
+
description: string2().min(1).max(500).optional().describe(ATTACHMENT_DESCRIPTION_HINT),
|
|
12349
12390
|
sha256: string2().regex(/^[a-f0-9]{64}$/).optional()
|
|
12350
12391
|
},
|
|
12351
12392
|
surfaces: ["cli"],
|
|
@@ -12381,11 +12422,13 @@ var ACTION_DEFINITIONS = [
|
|
|
12381
12422
|
},
|
|
12382
12423
|
{
|
|
12383
12424
|
name: "attachment_upload",
|
|
12384
|
-
title: "Upload
|
|
12385
|
-
description: "Upload
|
|
12425
|
+
title: "Upload local Thread files",
|
|
12426
|
+
description: "Upload local files with Node or Bun (SHA-256 → presigned PUT → attach, 100 MB cap)",
|
|
12386
12427
|
cliHelpLead: "Requires Node or Bun.",
|
|
12387
12428
|
cliHelpNotes: [
|
|
12388
12429
|
"Upload with SHA-256 via upload_url → presigned PUT → attach (100 MB cap); compress, split, or link larger files.",
|
|
12430
|
+
'Several files: `arbor attachment upload a.png b.png --thread-id thr_… --description "A" --description "B"`. Repeated --description flags pair with the files by ORDER: pass none (each defaults to its filename) or exactly one per file; a value that starts with `--` must be written --description=<value>. The whole batch is checked before the first request, then uploaded one by one; if one fails the output lists what uploaded and what failed (exit non-zero). --dry-run prints the plan and makes no network call.',
|
|
12431
|
+
`Description: ${ATTACHMENT_DESCRIPTION_HINT}. An upload is unbound until you pass its attachmentIds to contribute or respond (max 10 per contribution); to fix a description before binding, re-upload and bind only the new id. Paste attachments[].markdown verbatim; don't rebuild it from the description.`,
|
|
12389
12432
|
"PUT links expire after five minutes; first attach is allowed until one hour after PUT expiry. Finalized receipts replay after expiry.",
|
|
12390
12433
|
"JSON syntax checks stop at 10 MB; larger JSON gets UTF-8/control checks only, so validate syntax locally.",
|
|
12391
12434
|
"js-exec lacks file descriptors, node:crypto, and binary-safe fetch; run this command with Node or Bun.",
|
|
@@ -12395,8 +12438,9 @@ var ACTION_DEFINITIONS = [
|
|
|
12395
12438
|
`),
|
|
12396
12439
|
inputSchema: {
|
|
12397
12440
|
threadId: string2().min(1),
|
|
12398
|
-
file:
|
|
12399
|
-
description:
|
|
12441
|
+
file: verbatimList("local file path(s), or positional files; uploaded in order; requires Node or Bun"),
|
|
12442
|
+
description: verbatimList(`${ATTACHMENT_DESCRIPTION_HINT}; repeat --description once per file, paired by order (none = filenames)`).optional(),
|
|
12443
|
+
dryRun: boolean2().optional().describe("print the upload plan (file, mime, size, sha256, description) with no network call")
|
|
12400
12444
|
},
|
|
12401
12445
|
surfaces: ["cli"],
|
|
12402
12446
|
toolset: "loop",
|
|
@@ -12515,7 +12559,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12515
12559
|
title: "Read a Thread computer",
|
|
12516
12560
|
description: "Inspect a Thread's provider-free durable Computer recipe/materialization state, compatibility, recoverySafety, lineage, recent receipts, Runs, and reprovision outcomes; setup content and credentials stay redacted. This read never contacts execution; use computer_status for live readiness.",
|
|
12517
12561
|
inputSchema: {
|
|
12518
|
-
threadId: string2().describe("the Thread, thr_…"),
|
|
12562
|
+
threadId: string2().describe("the Thread, thr_… or a short id like BUD-34"),
|
|
12519
12563
|
lineageLimit: number2().int().min(1).max(100).optional().describe("recent receipts/lineage rows (default 20)")
|
|
12520
12564
|
},
|
|
12521
12565
|
surfaces: ["computer-mcp", "cli"],
|
|
@@ -12552,7 +12596,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12552
12596
|
title: "Set a thread's ways to help",
|
|
12553
12597
|
description: "Set a thread's READ-ONLY 'ways to help' (AD-038) — a short, curated list of perspectives, skills, or concrete tasks orienting someone on how they could pitch in with THIS thread as it stands (e.g. 'a benchmark on cold-start latency'). It is thread-local and temporary: it is read inside `thread_get`, and it never rolls up into a space/topic orientation surface — a standing statement about what a room wants is the room's standing ways to help (`contributionLanes`, via `set_space_lanes`/`set_topic_lanes`, AD-222). Guidance you READ, NOT an obligation — that's `request`. Replaces the whole list; pass [] to clear.",
|
|
12554
12598
|
inputSchema: {
|
|
12555
|
-
threadId: string2().describe("the thread, thr_…"),
|
|
12599
|
+
threadId: string2().describe("the thread, thr_… or a short id like BUD-34"),
|
|
12556
12600
|
waysToHelp: proseList("the full ways-to-help list — replaces the existing one; [] clears it")
|
|
12557
12601
|
},
|
|
12558
12602
|
surfaces: ["cli"],
|
|
@@ -12564,7 +12608,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12564
12608
|
title: "Move a thread's lifecycle",
|
|
12565
12609
|
description: transitionThreadActionDescription(),
|
|
12566
12610
|
inputSchema: {
|
|
12567
|
-
threadId: string2().describe("the thread to move, thr_…"),
|
|
12611
|
+
threadId: string2().describe("the thread to move, thr_… or a short id like BUD-34"),
|
|
12568
12612
|
to: string2().min(1).describe("label key, case-insensitive label name, or bare type (`resolved` still works as a type name)"),
|
|
12569
12613
|
reason: string2().optional().describe("optional one-line why, recorded on the event"),
|
|
12570
12614
|
rank: string2().max(THREAD_RANK_MAX).regex(THREAD_RANK_PATTERN).nullable().optional().describe("LexoRank in the destination column; omit to leave unchanged, null to restore recency. Combined with `--to` this is one atomic write — a bad rank moves nothing")
|
|
@@ -12650,7 +12694,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12650
12694
|
description: "Bind one Artifact to an existing same-Space Thread Live Preview, making the Artifact the durable entry point for that executable surface. Omit --thread-id to clear the binding; this changes no Artifact source/version and does not create or reconfigure a Computer.",
|
|
12651
12695
|
inputSchema: {
|
|
12652
12696
|
artifactId: string2().describe("the Artifact, art_…"),
|
|
12653
|
-
threadId: string2().optional().describe("the configured Thread Live Preview to bind, thr_
|
|
12697
|
+
threadId: string2().optional().describe("the configured Thread Live Preview to bind, thr_… or a short id like BUD-34; omit to clear")
|
|
12654
12698
|
},
|
|
12655
12699
|
surfaces: ["cli"],
|
|
12656
12700
|
toolset: "artifacts",
|
|
@@ -12730,7 +12774,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12730
12774
|
title: "Request a contribution or review",
|
|
12731
12775
|
description: "Request a contribution or review on a Thread: omit aboutContributionId for a contribution answered via respond, or pass it for a review answered only by stamp; explicitly target yourself for a normal Inbox obligation, target other people, or leave it open. Choose completion/deadline and optionally opensAt/opensIn to keep it dormant until scheduled delivery; fulfill a self-target through respond or use cancel_request to retire it.",
|
|
12732
12776
|
inputSchema: {
|
|
12733
|
-
threadId: string2().describe("the thread the request is in, thr_…"),
|
|
12777
|
+
threadId: string2().describe("the thread the request is in, thr_… or a short id like BUD-34"),
|
|
12734
12778
|
requestedAction: string2().optional().describe("a one-line 'what you're requesting'"),
|
|
12735
12779
|
requestedType: _enum(CONTRIBUTION_TYPES).optional().describe("the contribution type you're HOPING for (e.g. evidence, decision) — a soft default on the responder's picker, never a constraint; omit for 'any contribution welcome'. Contribution asks only"),
|
|
12736
12780
|
aboutContributionId: string2().optional().describe("PASS to request a REVIEW of this contribution (con_…, in the thread; met by a stamp, badges that card). OMIT to request a CONTRIBUTION on the thread (met by a respond)."),
|
|
@@ -12891,7 +12935,7 @@ var ACTION_DEFINITIONS = [
|
|
|
12891
12935
|
title: "Read a thread",
|
|
12892
12936
|
description: "Read one Thread as compact current context (distillate, requests, artifacts, recent tail, cursored unchanged checks) — not the durable transcript; pass view=full to read contribution bodies and attachments (including Markdown and authenticated URLs) and page history (contribLimit default 50) or all=true for the complete transcript. Read it before contributing so you can answer open requests correctly.",
|
|
12893
12937
|
inputSchema: {
|
|
12894
|
-
threadId: string2().describe("the thread id, e.g. thr_
|
|
12938
|
+
threadId: string2().describe("the thread id, e.g. thr_…, or its short id, e.g. BUD-34"),
|
|
12895
12939
|
view: _enum(["full", "compact"]).optional().describe("MCP/CLI/API default compact = bounded decision-ready context; full = transcript page"),
|
|
12896
12940
|
cursor: string2().optional().describe("compact reads only: cursor from the previous compact response; if context is unchanged the response collapses to a tiny unchanged envelope"),
|
|
12897
12941
|
recentLimit: number2().int().min(1).max(20).optional().describe("compact reads only: latest contributions to include (default 6, max 20)"),
|
|
@@ -13062,7 +13106,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13062
13106
|
title: "Link a thread to a goal",
|
|
13063
13107
|
description: "Record that a thread contributes to a goal — the many-to-many link that surfaces the goal on the thread's space and lists the thread under the goal. Idempotent. Reach for this once a thread's work clearly serves a goal; use `list_goals` to find the goal id.",
|
|
13064
13108
|
inputSchema: {
|
|
13065
|
-
threadId: string2().describe("the contributing thread, thr_…"),
|
|
13109
|
+
threadId: string2().describe("the contributing thread, thr_… or a short id like BUD-34"),
|
|
13066
13110
|
initiativeId: string2().describe("the goal it serves, ini_…")
|
|
13067
13111
|
},
|
|
13068
13112
|
surfaces: ["cli"],
|
|
@@ -13074,7 +13118,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13074
13118
|
title: "Unlink a thread from a goal",
|
|
13075
13119
|
description: "Remove the link between a thread and a goal (the deliberate inverse of `link_thread_to_goal`). Idempotent. Reach for this when a thread no longer serves the goal it was attached to — the thread and the goal both stay; only the link is dropped.",
|
|
13076
13120
|
inputSchema: {
|
|
13077
|
-
threadId: string2().describe("the thread to unlink, thr_…"),
|
|
13121
|
+
threadId: string2().describe("the thread to unlink, thr_… or a short id like BUD-34"),
|
|
13078
13122
|
initiativeId: string2().describe("the goal to unlink it from, ini_…")
|
|
13079
13123
|
},
|
|
13080
13124
|
surfaces: ["cli"],
|
|
@@ -13099,7 +13143,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13099
13143
|
description: "List compact goals attached to one Space, or a Thread's linked goals plus its available pick list. Space lists are compact cards; Thread lists page only the available `all` list and keep `linked` complete; view=summary returns 3 active goals; status pages named lifecycles before the page.",
|
|
13100
13144
|
inputSchema: {
|
|
13101
13145
|
spaceId: string2().optional().describe("a Space whose attached goals to list, spc_…"),
|
|
13102
|
-
threadId: string2().optional().describe("a Thread whose linked and available goals to list, thr_…"),
|
|
13146
|
+
threadId: string2().optional().describe("a Thread whose linked and available goals to list, thr_… or a short id like BUD-34"),
|
|
13103
13147
|
view: literal("summary").optional().describe("summary: Topics home — 3 most recently active goals (title + in-space linked Thread count). Ignores status. Omit for the attached list."),
|
|
13104
13148
|
status: union([
|
|
13105
13149
|
_enum(["active", "paused", "completed", "archived"]),
|
|
@@ -13153,6 +13197,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13153
13197
|
description: "Create a Space when work needs its own durable collaboration/governance boundary — its own membership, visibility, shared norms, and multiple durable areas; you become its first admin (org owner/admins always can, other members only when enabled), with open/private visibility and optional seeded members. If the work can live under an existing Space with the same people and governance, use a Topic instead.",
|
|
13154
13198
|
inputSchema: {
|
|
13155
13199
|
title: string2().min(1).describe("the space title"),
|
|
13200
|
+
key: SPACE_KEY_INPUT.optional().describe("thread short-id prefix (BUD in BUD-34): 1-10 letters/digits, starting with a letter, unique in the org; default: the title's first three letters"),
|
|
13156
13201
|
purpose: string2().optional().describe("why this boundary exists and what it is around — commonly a Product/System, Program/Function, Portfolio/Network, or Practice/Community — plus its dominant work modes (Build, Operate, Coordinate, Discuss, Share) when that helps; plain prose, not metadata syntax"),
|
|
13157
13202
|
visibility: _enum(["open", "private"]).optional().describe("open (default) or private (invite-only)"),
|
|
13158
13203
|
memberProfileIds: array(string2()).optional().describe("org profile ids to seed as members — agents added directly, humans get a pending invite")
|
|
@@ -13169,6 +13214,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13169
13214
|
spaceId: string2().describe("the space, spc_…"),
|
|
13170
13215
|
purpose: string2().optional().describe("why this boundary exists and what it is around — commonly a Product/System, Program/Function, Portfolio/Network, or Practice/Community — plus its dominant work modes (Build, Operate, Coordinate, Discuss, Share) when that helps; plain prose, not metadata syntax"),
|
|
13171
13216
|
title: string2().optional().describe("a new title for the space"),
|
|
13217
|
+
key: SPACE_KEY_INPUT.optional().describe("a new thread short-id prefix (BUD in BUD-34), unique in the org; every thread's short id changes, the old ones stop resolving, and the old key stays reserved to this Space"),
|
|
13172
13218
|
visibility: _enum(["open", "private"]).optional().describe("open or private (invite-only)"),
|
|
13173
13219
|
stage: _enum(["exploration", "design", "implementation", "maintenance", "retired"]).optional().describe("set the lifecycle stage; retired archives the Space reversibly and restores with an active stage, but only when no Run is active/suspended and no App is serving"),
|
|
13174
13220
|
attachWorkflow: string2().optional().describe("add a workflow to this Space's list: a built-in id (`standard`, `queue`, `content`, `experiment`, `funnel`) or a catalog `wfl_…` from `workflow_list`"),
|
|
@@ -13379,7 +13425,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13379
13425
|
title: "Inspect a thread (superadmin)",
|
|
13380
13426
|
description: "Platform-operator only: read any thread by id across orgs (its contributions + authors), bypassing the per-org membership boundary for oversight.",
|
|
13381
13427
|
inputSchema: {
|
|
13382
|
-
threadId: string2().min(1).describe("the thread id, e.g. thr_…")
|
|
13428
|
+
threadId: string2().min(1).describe("the canonical thread id, e.g. thr_… (admin reads are cross-org)")
|
|
13383
13429
|
},
|
|
13384
13430
|
surfaces: ["cli"],
|
|
13385
13431
|
toolset: "admin",
|
|
@@ -13400,7 +13446,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13400
13446
|
description: "List compact goals attached to one Space, or the linked and available goals for one Thread. Space pages omit relatedThreads; Thread pages keep linked complete and page only the available `all` list; view=summary returns 3 active goals; status pages named lifecycles before the page; it cannot mutate a goal.",
|
|
13401
13447
|
inputSchema: {
|
|
13402
13448
|
spaceId: string2().optional().describe("a Space whose attached goals to list, spc_…"),
|
|
13403
|
-
threadId: string2().optional().describe("a Thread whose linked and available goals to list, thr_…"),
|
|
13449
|
+
threadId: string2().optional().describe("a Thread whose linked and available goals to list, thr_… or a short id like BUD-34"),
|
|
13404
13450
|
view: literal("summary").optional().describe("summary: Topics home — 3 most recently active goals (title + in-space linked Thread count). Ignores status. Omit for the attached list."),
|
|
13405
13451
|
status: union([
|
|
13406
13452
|
_enum(["active", "paused", "completed", "archived"]),
|
|
@@ -13432,7 +13478,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13432
13478
|
threadIds: array(string2()).optional().describe("create: threads to link at birth"),
|
|
13433
13479
|
id: string2().optional().describe("update/set_status: the goal, ini_…"),
|
|
13434
13480
|
status: _enum(["active", "paused", "completed", "archived"]).optional().describe("set_status: the new lifecycle state"),
|
|
13435
|
-
threadId: string2().optional().describe("link_thread/unlink_thread: the thread, thr_…"),
|
|
13481
|
+
threadId: string2().optional().describe("link_thread/unlink_thread: the thread, thr_… or a short id like BUD-34"),
|
|
13436
13482
|
initiativeId: string2().optional().describe("link/unlink/attach/detach: the goal, ini_…")
|
|
13437
13483
|
},
|
|
13438
13484
|
surfaces: ["mcp"],
|
|
@@ -13616,7 +13662,7 @@ var ACTION_DEFINITIONS = [
|
|
|
13616
13662
|
computerSessionId: string2().optional().describe("create: the attached computer session that produced it, cms_…"),
|
|
13617
13663
|
sourceContributionIds: array(string2()).optional().describe("create: contributions it distills"),
|
|
13618
13664
|
artifactId: string2().optional().describe("edit/set_live_preview/set_snapshot: the Artifact, art_…"),
|
|
13619
|
-
threadId: string2().optional().describe("set_live_preview: the configured Thread Live Preview, thr_
|
|
13665
|
+
threadId: string2().optional().describe("set_live_preview: the configured Thread Live Preview, thr_… or a short id like BUD-34; omit to clear the binding"),
|
|
13620
13666
|
baseVersion: number2().optional().describe("edit: the version you READ (from get/create) — stale base is rejected with the current source"),
|
|
13621
13667
|
patches: array(object({ old: string2(), new: string2() })).optional().describe("edit: exact-match hunks applied in order, atomically; each old must match exactly once"),
|
|
13622
13668
|
note: string2().optional().describe("edit: one-line summary of what changed"),
|
|
@@ -14291,7 +14337,7 @@ var ACTION_DEFINITIONS = [
|
|
|
14291
14337
|
topicId: string2().optional().describe("topic_guidance/topic_lanes: the topic, top_…"),
|
|
14292
14338
|
guidance: proseList("space_guidance/topic_guidance: full list, replaces; [] clears").optional(),
|
|
14293
14339
|
contributionLanes: proseList("space_lanes/topic_lanes: full list of ways to help, replaces; [] clears").optional(),
|
|
14294
|
-
threadId: string2().optional().describe("ways_to_help: the thread, thr_…"),
|
|
14340
|
+
threadId: string2().optional().describe("ways_to_help: the thread, thr_… or a short id like BUD-34"),
|
|
14295
14341
|
waysToHelp: proseList("ways_to_help: full list, replaces; [] clears").optional()
|
|
14296
14342
|
},
|
|
14297
14343
|
surfaces: ["mcp"],
|
|
@@ -14677,6 +14723,19 @@ class UsageError extends Error {
|
|
|
14677
14723
|
}
|
|
14678
14724
|
}
|
|
14679
14725
|
|
|
14726
|
+
class PartialUploadError extends Error {
|
|
14727
|
+
partialResult;
|
|
14728
|
+
code;
|
|
14729
|
+
details;
|
|
14730
|
+
constructor(message, cause, partialResult) {
|
|
14731
|
+
super(message);
|
|
14732
|
+
this.partialResult = partialResult;
|
|
14733
|
+
this.name = "PartialUploadError";
|
|
14734
|
+
this.code = codeForError(cause);
|
|
14735
|
+
this.details = detailsForError(cause);
|
|
14736
|
+
}
|
|
14737
|
+
}
|
|
14738
|
+
|
|
14680
14739
|
// src/config.ts
|
|
14681
14740
|
var PROD_API_URL = "https://arborthreads.com";
|
|
14682
14741
|
var PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -15051,7 +15110,7 @@ var httpExecutor = {
|
|
|
15051
15110
|
};
|
|
15052
15111
|
|
|
15053
15112
|
// src/attachment-upload.ts
|
|
15054
|
-
import { basename } from "node:path";
|
|
15113
|
+
import { basename, resolve } from "node:path";
|
|
15055
15114
|
function checkFile(stat) {
|
|
15056
15115
|
const isFile = typeof stat.isFile === "function" ? stat.isFile() : stat.isFile;
|
|
15057
15116
|
if (!isFile)
|
|
@@ -15061,8 +15120,7 @@ function checkFile(stat) {
|
|
|
15061
15120
|
if (!stat.size)
|
|
15062
15121
|
throw new UsageError("Attachment file is empty (0 bytes); choose a non-empty file");
|
|
15063
15122
|
}
|
|
15064
|
-
async function
|
|
15065
|
-
const file = String(input.file);
|
|
15123
|
+
async function preflightFile(file) {
|
|
15066
15124
|
const filename = basename(file);
|
|
15067
15125
|
const fs = await import("node:fs");
|
|
15068
15126
|
const initialStat = fs.statSync(file);
|
|
@@ -15072,6 +15130,9 @@ async function uploadLocalAttachment(executor, input, fetcher = fetch) {
|
|
|
15072
15130
|
throw new UsageError(`Unsupported file extension for ${initialStat.size} bytes (limit ${MAX_ATTACHMENT_BYTES} bytes). Use PNG/JPEG/GIF/WebP/PDF/TXT/JSON/CSV, or link the file.`);
|
|
15073
15131
|
if ([fs.openSync, fs.fstatSync, fs.readSync, fs.closeSync].some((api) => typeof api !== "function"))
|
|
15074
15132
|
throw new UsageError("Attachment upload requires Node or Bun: js-exec lacks file descriptors, node:crypto, and binary-safe fetch. Run `arbor attachment upload <file> --thread-id <thread>` with Node or Bun, or use MCP attachment upload_url → curl PUT → attach.");
|
|
15133
|
+
return { fs, local: { file, filename, mimeType, size: initialStat.size } };
|
|
15134
|
+
}
|
|
15135
|
+
async function putLocalAttachment(executor, fs, { file, filename, mimeType }, input, fetcher) {
|
|
15075
15136
|
const descriptor = fs.openSync(file, "r");
|
|
15076
15137
|
let bytes;
|
|
15077
15138
|
try {
|
|
@@ -15117,6 +15178,135 @@ async function uploadLocalAttachment(executor, input, fetcher = fetch) {
|
|
|
15117
15178
|
throw new ApiError(`Attachment PUT failed (${response.status}) for ${bytes.length} bytes (limit ${MAX_ATTACHMENT_BYTES} bytes). Request a fresh upload_url if expired, then retry; compress, split, or link files above the limit.`, response.status);
|
|
15118
15179
|
return executor.call("attachment.attach", { token: link.token });
|
|
15119
15180
|
}
|
|
15181
|
+
function normalizeDescription(value) {
|
|
15182
|
+
return value.trim().replace(/\s+/g, " ");
|
|
15183
|
+
}
|
|
15184
|
+
function asList(value) {
|
|
15185
|
+
if (value === undefined)
|
|
15186
|
+
return [];
|
|
15187
|
+
return (Array.isArray(value) ? value : [value]).map(String);
|
|
15188
|
+
}
|
|
15189
|
+
async function planBatch(input) {
|
|
15190
|
+
const files = asList(input.file);
|
|
15191
|
+
const descriptions = asList(input.description);
|
|
15192
|
+
if (files.length === 0)
|
|
15193
|
+
throw new UsageError("missing required flag: --file");
|
|
15194
|
+
if (descriptions.length > 0 && descriptions.length !== files.length) {
|
|
15195
|
+
throw new UsageError(files.length > 1 && descriptions.length === 1 ? `One --description for ${files.length} files would give every file the same permanent description. Pass one --description per file in file order, or none to default each to its filename.` : `Got ${descriptions.length} --description flag${descriptions.length === 1 ? "" : "s"} for ${files.length} file${files.length === 1 ? "" : "s"}; descriptions pair with files by order, so pass none or exactly one per file.`, { reason: "validation.invalid_value", field: "description" });
|
|
15196
|
+
}
|
|
15197
|
+
for (const description of descriptions) {
|
|
15198
|
+
const length = normalizeDescription(description).length;
|
|
15199
|
+
if (length < 1)
|
|
15200
|
+
throw new UsageError("--description is blank; give each file a real description, or omit --description to default it to the filename", { reason: "validation.invalid_value", field: "description" });
|
|
15201
|
+
if (length > 500)
|
|
15202
|
+
throw new UsageError("--description must be 1–500 characters", {
|
|
15203
|
+
reason: "validation.invalid_value",
|
|
15204
|
+
field: "description"
|
|
15205
|
+
});
|
|
15206
|
+
}
|
|
15207
|
+
const paths = new Map;
|
|
15208
|
+
for (const file of files) {
|
|
15209
|
+
if (!file.trim())
|
|
15210
|
+
throw new UsageError("--file is blank; pass a path to a local file");
|
|
15211
|
+
const key = resolve(file);
|
|
15212
|
+
const first = paths.get(key);
|
|
15213
|
+
if (first !== undefined)
|
|
15214
|
+
throw new UsageError(`${file} is listed twice${first === file ? "" : ` (as ${first})`}; each file uploads once, so list it once.`, { reason: "validation.invalid_value", field: "file" });
|
|
15215
|
+
paths.set(key, file);
|
|
15216
|
+
}
|
|
15217
|
+
let fs;
|
|
15218
|
+
const plan = [];
|
|
15219
|
+
for (const [index, file] of files.entries()) {
|
|
15220
|
+
const prepared = await preflightFile(file);
|
|
15221
|
+
fs = prepared.fs;
|
|
15222
|
+
plan.push({ local: prepared.local, description: descriptions[index] });
|
|
15223
|
+
}
|
|
15224
|
+
const seen = new Map;
|
|
15225
|
+
for (const { local, description } of plan) {
|
|
15226
|
+
if (description === undefined)
|
|
15227
|
+
continue;
|
|
15228
|
+
const key = normalizeDescription(description);
|
|
15229
|
+
const first = seen.get(key);
|
|
15230
|
+
if (first !== undefined)
|
|
15231
|
+
throw new UsageError(`Duplicate --description "${key}" for ${first} and ${local.file}; descriptions are permanent, so give each file its own.`, { reason: "validation.invalid_value", field: "description" });
|
|
15232
|
+
seen.set(key, local.file);
|
|
15233
|
+
}
|
|
15234
|
+
return { fs, plan };
|
|
15235
|
+
}
|
|
15236
|
+
function sha256File(fs, file, createHash) {
|
|
15237
|
+
const hash = createHash("sha256");
|
|
15238
|
+
const chunk = Buffer.alloc(1048576);
|
|
15239
|
+
const descriptor = fs.openSync(file, "r");
|
|
15240
|
+
try {
|
|
15241
|
+
for (;; ) {
|
|
15242
|
+
const count = fs.readSync(descriptor, chunk, 0, chunk.length, null);
|
|
15243
|
+
if (!count)
|
|
15244
|
+
break;
|
|
15245
|
+
hash.update(chunk.subarray(0, count));
|
|
15246
|
+
}
|
|
15247
|
+
} finally {
|
|
15248
|
+
fs.closeSync(descriptor);
|
|
15249
|
+
}
|
|
15250
|
+
return hash.digest("hex");
|
|
15251
|
+
}
|
|
15252
|
+
async function dryRunPlan(fs, threadId, plan) {
|
|
15253
|
+
const { createHash } = await import("node:crypto");
|
|
15254
|
+
return {
|
|
15255
|
+
dryRun: true,
|
|
15256
|
+
threadId,
|
|
15257
|
+
files: plan.map(({ local, description }) => ({
|
|
15258
|
+
file: local.file,
|
|
15259
|
+
filename: local.filename,
|
|
15260
|
+
mimeType: local.mimeType,
|
|
15261
|
+
size: local.size,
|
|
15262
|
+
sha256: sha256File(fs, local.file, createHash),
|
|
15263
|
+
description: description ?? local.filename,
|
|
15264
|
+
descriptionSource: description === undefined ? "filename default" : "explicit",
|
|
15265
|
+
status: "unbound until attached"
|
|
15266
|
+
}))
|
|
15267
|
+
};
|
|
15268
|
+
}
|
|
15269
|
+
function record3(value) {
|
|
15270
|
+
return value !== null && typeof value === "object" ? value : {};
|
|
15271
|
+
}
|
|
15272
|
+
function aggregateReceipt(uploaded, extra = {}) {
|
|
15273
|
+
const warnings = uploaded.flatMap(({ local, receipt }) => asList(record3(receipt).warnings).map((warning) => `${local.filename}: ${warning}`));
|
|
15274
|
+
return {
|
|
15275
|
+
attachmentIds: uploaded.flatMap(({ receipt }) => {
|
|
15276
|
+
const r = record3(receipt);
|
|
15277
|
+
return Array.isArray(r.attachmentIds) ? r.attachmentIds : typeof r.attachmentId === "string" ? [r.attachmentId] : [];
|
|
15278
|
+
}),
|
|
15279
|
+
attachments: uploaded.flatMap(({ receipt }) => {
|
|
15280
|
+
const attachments = record3(receipt).attachments;
|
|
15281
|
+
return Array.isArray(attachments) ? attachments : [];
|
|
15282
|
+
}),
|
|
15283
|
+
nextStep: ATTACHMENT_ATTACH_NEXT_STEP,
|
|
15284
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
15285
|
+
...extra
|
|
15286
|
+
};
|
|
15287
|
+
}
|
|
15288
|
+
async function uploadLocalAttachments(executor, input, fetcher = fetch) {
|
|
15289
|
+
const { fs, plan } = await planBatch(input);
|
|
15290
|
+
if (input.dryRun === true)
|
|
15291
|
+
return dryRunPlan(fs, String(input.threadId), plan);
|
|
15292
|
+
const upload = ({ local, description }) => putLocalAttachment(executor, fs, local, { threadId: input.threadId, ...description !== undefined ? { description } : {} }, fetcher);
|
|
15293
|
+
if (plan.length === 1)
|
|
15294
|
+
return upload(plan[0]);
|
|
15295
|
+
const uploaded = [];
|
|
15296
|
+
for (const [index, item] of plan.entries()) {
|
|
15297
|
+
try {
|
|
15298
|
+
uploaded.push({ local: item.local, receipt: await upload(item) });
|
|
15299
|
+
} catch (cause) {
|
|
15300
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
15301
|
+
const notAttempted = plan.slice(index + 1).map(({ local }) => local.file);
|
|
15302
|
+
throw new PartialUploadError(`Uploaded ${uploaded.length} of ${plan.length} files; ${item.local.file} failed: ${reason.replace(/\.$/, "")}. Uploaded files stay valid but unbound; re-run only the failed and not-attempted files.`, cause, aggregateReceipt(uploaded, {
|
|
15303
|
+
failed: [{ file: item.local.file, error: reason }],
|
|
15304
|
+
notAttempted
|
|
15305
|
+
}));
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15308
|
+
return aggregateReceipt(uploaded);
|
|
15309
|
+
}
|
|
15120
15310
|
|
|
15121
15311
|
// src/output.ts
|
|
15122
15312
|
function writeOutput(value, stream, fallback) {
|
|
@@ -15208,15 +15398,20 @@ function emitData(data, _action, ctx) {
|
|
|
15208
15398
|
function emitError(err, _action, ctx) {
|
|
15209
15399
|
const code = codeForError(err);
|
|
15210
15400
|
const message = err instanceof Error ? err.message : String(err);
|
|
15401
|
+
const partial = err instanceof PartialUploadError ? err.partialResult : undefined;
|
|
15211
15402
|
if (ctx.json) {
|
|
15212
15403
|
const envelope = {
|
|
15213
15404
|
ok: false,
|
|
15214
|
-
error: { code, message, ...detailsForError(err) }
|
|
15405
|
+
error: { code, message, ...detailsForError(err) },
|
|
15406
|
+
...partial !== undefined ? { data: partial } : {}
|
|
15215
15407
|
};
|
|
15216
15408
|
writeStdout(`${JSON.stringify(envelope)}
|
|
15217
15409
|
`);
|
|
15218
15410
|
process.exitCode = exitCodeForError(code);
|
|
15219
15411
|
} else {
|
|
15412
|
+
if (partial !== undefined)
|
|
15413
|
+
writeStdout(`${JSON.stringify(partial, null, 2)}
|
|
15414
|
+
`);
|
|
15220
15415
|
writeStderr(`✗ ${message}
|
|
15221
15416
|
`);
|
|
15222
15417
|
process.exitCode = 1;
|
|
@@ -15238,7 +15433,7 @@ var DEFAULT_PROCESS_DEADLINE_MS = 1800000;
|
|
|
15238
15433
|
var PROCESS_OUTPUT_PAGE_BYTES = 4096;
|
|
15239
15434
|
var POLL_MS = 400;
|
|
15240
15435
|
var processRead = ACTIONS3.find((action) => action.name === "computer_process_read");
|
|
15241
|
-
function
|
|
15436
|
+
function record4(value) {
|
|
15242
15437
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15243
15438
|
}
|
|
15244
15439
|
function isRunning(value) {
|
|
@@ -15266,7 +15461,7 @@ function sleep(ms) {
|
|
|
15266
15461
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15267
15462
|
}
|
|
15268
15463
|
function isComputerExecHandle(value) {
|
|
15269
|
-
const rec =
|
|
15464
|
+
const rec = record4(value);
|
|
15270
15465
|
return Boolean(rec && typeof rec.processId === "string" && (isRunning(rec) || rec.truncated === true));
|
|
15271
15466
|
}
|
|
15272
15467
|
async function waitForComputerExec(executor, input, value, ctx) {
|
|
@@ -15281,7 +15476,7 @@ async function waitForComputerExec(executor, input, value, ctx) {
|
|
|
15281
15476
|
`, ctx);
|
|
15282
15477
|
while (isRunning(latest) && Date.now() < deadline) {
|
|
15283
15478
|
await sleep(POLL_MS);
|
|
15284
|
-
const read =
|
|
15479
|
+
const read = record4(await processRead.run(executor, {
|
|
15285
15480
|
processId: latest.processId,
|
|
15286
15481
|
maxBytes: PROCESS_OUTPUT_PAGE_BYTES,
|
|
15287
15482
|
...cursor ? { cursor } : {},
|
|
@@ -15355,6 +15550,7 @@ function kindOf(node) {
|
|
|
15355
15550
|
return "string";
|
|
15356
15551
|
}
|
|
15357
15552
|
}
|
|
15553
|
+
var BARE_VALUE_HINT = (flag) => ` (a value that starts with "--" must be written --${flag}=<value>)`;
|
|
15358
15554
|
function acceptedFlags(inputSchema) {
|
|
15359
15555
|
const out = new Set;
|
|
15360
15556
|
for (const spec of flagsForSchema(inputSchema)) {
|
|
@@ -15387,7 +15583,8 @@ function flagsForSchema(inputSchema) {
|
|
|
15387
15583
|
required: !optional,
|
|
15388
15584
|
nullable,
|
|
15389
15585
|
description: description(inner) ?? description(raw),
|
|
15390
|
-
literal: isProseList(inner)
|
|
15586
|
+
literal: isProseList(inner),
|
|
15587
|
+
verbatim: isVerbatimList(inner)
|
|
15391
15588
|
};
|
|
15392
15589
|
});
|
|
15393
15590
|
}
|
|
@@ -15428,7 +15625,7 @@ function buildInput(inputSchema, flags, command) {
|
|
|
15428
15625
|
continue;
|
|
15429
15626
|
}
|
|
15430
15627
|
if (spec.kind !== "boolean" && raw === true) {
|
|
15431
|
-
throw new UsageError(`--${spec.flag} expects a value`, {
|
|
15628
|
+
throw new UsageError(`--${spec.flag} expects a value${BARE_VALUE_HINT(spec.flag)}`, {
|
|
15432
15629
|
reason: "validation.missing_field",
|
|
15433
15630
|
field: spec.field
|
|
15434
15631
|
});
|
|
@@ -15451,6 +15648,10 @@ function buildInput(inputSchema, flags, command) {
|
|
|
15451
15648
|
case "array": {
|
|
15452
15649
|
const repeated = Array.isArray(raw);
|
|
15453
15650
|
const tokens = repeated ? raw : [String(raw)];
|
|
15651
|
+
if (spec.verbatim) {
|
|
15652
|
+
input[spec.field] = tokens.map(String);
|
|
15653
|
+
break;
|
|
15654
|
+
}
|
|
15454
15655
|
const out = [];
|
|
15455
15656
|
for (const token of tokens) {
|
|
15456
15657
|
const text = String(token).trim();
|
|
@@ -15706,6 +15907,7 @@ var POSITIONAL_FIELDS = {
|
|
|
15706
15907
|
code_implementations: ["namePath", "path"],
|
|
15707
15908
|
code_diagnostics: ["path"]
|
|
15708
15909
|
};
|
|
15910
|
+
var VARIADIC_LAST_POSITIONAL = new Set(["attachment_upload"]);
|
|
15709
15911
|
function positionalSpecs(action) {
|
|
15710
15912
|
const fields = POSITIONAL_FIELDS[action.name];
|
|
15711
15913
|
if (!fields)
|
|
@@ -15793,7 +15995,11 @@ function toolNameAliases(action) {
|
|
|
15793
15995
|
function helpLine(action) {
|
|
15794
15996
|
const positionals = positionalSpecs(action);
|
|
15795
15997
|
const positionalFlags = new Set(positionals.map((spec) => spec.flag));
|
|
15796
|
-
const args = positionals.map((spec) =>
|
|
15998
|
+
const args = positionals.map((spec, index) => {
|
|
15999
|
+
const variadic = VARIADIC_LAST_POSITIONAL.has(action.name) && index === positionals.length - 1;
|
|
16000
|
+
const name = `${spec.field}${variadic ? ">..." : ">"}`;
|
|
16001
|
+
return spec.required ? ` <${name}` : ` [<${name}]`;
|
|
16002
|
+
}).join("");
|
|
15797
16003
|
const flags = flagsForSchema(action.inputSchema).filter((spec) => !positionalFlags.has(spec.flag)).map((f) => {
|
|
15798
16004
|
const token = f.kind === "boolean" ? `--${f.flag}` : `--${f.flag} <>`;
|
|
15799
16005
|
return f.required ? token : `[${token}]`;
|
|
@@ -15819,7 +16025,11 @@ function commandHelp(positionals) {
|
|
|
15819
16025
|
const token = f.kind === "boolean" ? `--${f.flag}` : `--${f.flag} <${f.kind}>`;
|
|
15820
16026
|
return f.required ? token : `[${token}]`;
|
|
15821
16027
|
}).join(" ");
|
|
15822
|
-
const usageArgs = helpPositionals.map((spec) =>
|
|
16028
|
+
const usageArgs = helpPositionals.map((spec, index) => {
|
|
16029
|
+
const variadic = VARIADIC_LAST_POSITIONAL.has(action.name) && index === helpPositionals.length - 1;
|
|
16030
|
+
const name = `${spec.field}${variadic ? ">..." : ">"}`;
|
|
16031
|
+
return spec.required ? ` <${name}` : ` [<${name}]`;
|
|
16032
|
+
}).join("");
|
|
15823
16033
|
const usage = `Usage: arbor ${commandWords(action)}${usageArgs}${usageFlags ? ` ${usageFlags}` : ""}`;
|
|
15824
16034
|
const lines = specs.map((f) => {
|
|
15825
16035
|
const positional = helpPositionals.find((spec) => spec.flag === f.flag);
|
|
@@ -15901,10 +16111,12 @@ function applyPositionals(action, extras, flags) {
|
|
|
15901
16111
|
if (positionalTargets.length === 0) {
|
|
15902
16112
|
throw new UsageError(`unexpected argument: ${extras[0]} (\`${commandWords(action)}\` takes no positional — use flags)`);
|
|
15903
16113
|
}
|
|
15904
|
-
|
|
16114
|
+
const fixed = positionalTargets.length - 1;
|
|
16115
|
+
const values = VARIADIC_LAST_POSITIONAL.has(action.name) && extras.length > fixed ? [...extras.slice(0, fixed), extras.slice(fixed)] : extras;
|
|
16116
|
+
if (values.length > positionalTargets.length) {
|
|
15905
16117
|
throw new UsageError(`too many arguments: \`${commandWords(action)}\` takes ${positionalTargets.length === 1 ? "one positional" : `${positionalTargets.length} positionals`}; pass the rest as flags`);
|
|
15906
16118
|
}
|
|
15907
|
-
for (const [index, value] of
|
|
16119
|
+
for (const [index, value] of values.entries()) {
|
|
15908
16120
|
const target = positionalTargets[index];
|
|
15909
16121
|
if (flags[target.flag] !== undefined || flags[`${target.flag}-file`] !== undefined) {
|
|
15910
16122
|
throw new UsageError(`pass --${target.flag} either as a flag or as the positional argument, not both`);
|
|
@@ -15942,8 +16154,16 @@ async function runObjectVerb(positionals, flags, ctx) {
|
|
|
15942
16154
|
const unknown = Object.keys(flags).filter((f) => !accepted.has(f) && !RESERVED_FLAGS.has(f));
|
|
15943
16155
|
if (unknown.length > 0) {
|
|
15944
16156
|
const hints = unknown.map((f) => listFileHint(action, f)).filter(Boolean);
|
|
16157
|
+
const bare = flagsForSchema(action.inputSchema).find((spec) => spec.kind !== "boolean" && flags[spec.flag] === true);
|
|
16158
|
+
if (bare)
|
|
16159
|
+
hints.push(`--${bare.flag} has no value${BARE_VALUE_HINT(bare.flag)}`);
|
|
15945
16160
|
throw new UsageError(`unknown flag${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => `--${f}`).join(", ")}${hints.length > 0 ? ` — ${hints.join("; ")}` : ""}`);
|
|
15946
16161
|
}
|
|
16162
|
+
if (action.name === "attachment_upload") {
|
|
16163
|
+
const dryRun = lastValue(flags["dry-run"]);
|
|
16164
|
+
if (typeof dryRun === "string" && dryRun !== "true" && dryRun !== "false")
|
|
16165
|
+
throw new UsageError(`--dry-run takes no value but swallowed "${dryRun}"; put --dry-run after the files or write --dry-run=true`, { reason: "validation.invalid_value", field: "dryRun" });
|
|
16166
|
+
}
|
|
15947
16167
|
const inputFlags = { ...flags };
|
|
15948
16168
|
if (action.name === "app_request" && lastValue(inputFlags.json) === true) {
|
|
15949
16169
|
delete inputFlags.json;
|
|
@@ -15980,7 +16200,7 @@ async function runObjectVerb(positionals, flags, ctx) {
|
|
|
15980
16200
|
advise(`Agent registered. Save its apiKey now — it is shown only once:
|
|
15981
16201
|
`, ctx);
|
|
15982
16202
|
}
|
|
15983
|
-
let value = action.name === "attachment_upload" ? await
|
|
16203
|
+
let value = action.name === "attachment_upload" ? await uploadLocalAttachments(httpExecutor, input) : await action.run(httpExecutor, input);
|
|
15984
16204
|
if (action.name === "computer_exec" && !truthyFlag(flags["no-wait"]) && input.waitForTerminalMs === undefined && input.background !== true) {
|
|
15985
16205
|
value = await waitForComputerExec(httpExecutor, input, value, ctx);
|
|
15986
16206
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lambdacurry/arbor",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.4",
|
|
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",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"arbor": "
|
|
30
|
+
"arbor": "vpx src/bin.ts",
|
|
31
31
|
"build": "bun build ./src/bin.ts --target=node --outfile=dist/arbor.js",
|
|
32
32
|
"test": "vp test run --passWithNoTests",
|
|
33
33
|
"typecheck": "tsc -b",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"@arbor/core": "workspace:*",
|
|
39
39
|
"@types/node": "^26.2.0",
|
|
40
40
|
"just-bash": "3.4.2",
|
|
41
|
-
"tsx": "^4.20.6",
|
|
42
41
|
"typescript": "^5.8.0",
|
|
43
42
|
"vite-plus": "catalog:",
|
|
44
43
|
"zod": "^4.4.3"
|