commonswarm 0.1.41 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cswarm.cjs +258 -34
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -22943,7 +22943,7 @@ async function sendFileCommand(options, command2) {
22943
22943
  const body = await response.json().catch(() => null);
22944
22944
  if (!response.ok) {
22945
22945
  const code = typeof body?.error === "string" ? body.error : "http_error";
22946
- const message = typeof body?.message === "string" ? body.message : `file command failed (HTTP ${response.status})`;
22946
+ const message = typeof body?.message === "string" ? body.message : `file command failed (HTTP ${response.status}) DEBUGBODY=${JSON.stringify(body).slice(0, 300)}`;
22947
22947
  throw new FileCommandRefused(response.status, code, message);
22948
22948
  }
22949
22949
  if (!body || typeof body !== "object") {
@@ -23119,6 +23119,40 @@ async function listFilesAsHuman(target2, accessToken, workspaceId2, fetcher = fe
23119
23119
  return body;
23120
23120
  }
23121
23121
 
23122
+ // src/cloud/brain.ts
23123
+ var BRAIN_FILE_PREFIX = "brain--";
23124
+ var BRAIN_FILE_SUFFIX = ".md";
23125
+ var BRAIN_TOPIC_MAX_LENGTH = 255 - BRAIN_FILE_PREFIX.length - BRAIN_FILE_SUFFIX.length;
23126
+ var BRAIN_TOPIC_RE = /^[a-z0-9][a-z0-9._-]*$/;
23127
+ var BrainTopicError = class extends Error {
23128
+ name = "BrainTopicError";
23129
+ };
23130
+ function canonicalBrainTopic(value) {
23131
+ const topic = value.trim().toLowerCase();
23132
+ if (topic.length < 1 || topic.length > BRAIN_TOPIC_MAX_LENGTH || !BRAIN_TOPIC_RE.test(topic)) {
23133
+ throw new BrainTopicError(
23134
+ `brain topics use ${BRAIN_TOPIC_MAX_LENGTH} or fewer lowercase letters, numbers, dots, dashes, or underscores; start with a letter or number`
23135
+ );
23136
+ }
23137
+ return topic;
23138
+ }
23139
+ function brainFileName(value) {
23140
+ return `${BRAIN_FILE_PREFIX}${canonicalBrainTopic(value)}${BRAIN_FILE_SUFFIX}`;
23141
+ }
23142
+ function brainTopicFromFileName(name) {
23143
+ const lower = name.toLowerCase();
23144
+ if (!lower.startsWith(BRAIN_FILE_PREFIX) || !lower.endsWith(BRAIN_FILE_SUFFIX)) {
23145
+ return null;
23146
+ }
23147
+ const topic = lower.slice(BRAIN_FILE_PREFIX.length, -BRAIN_FILE_SUFFIX.length);
23148
+ try {
23149
+ return canonicalBrainTopic(topic);
23150
+ } catch (error) {
23151
+ if (error instanceof BrainTopicError) return null;
23152
+ throw error;
23153
+ }
23154
+ }
23155
+
23122
23156
  // src/cloud/feedback.ts
23123
23157
  var FeedbackTransportError = class extends Error {
23124
23158
  name = "FeedbackTransportError";
@@ -30034,6 +30068,12 @@ function parseDeliveryReceipt(value) {
30034
30068
  );
30035
30069
  }
30036
30070
  const row = value;
30071
+ if (Object.hasOwn(row, "recipient_user_id")) {
30072
+ return {
30073
+ recipient_user_id: uuid4(row.recipient_user_id, "recipient_user_id"),
30074
+ seen_at: nullableTimestamp2(row.seen_at, "seen_at")
30075
+ };
30076
+ }
30037
30077
  const ackedAt = nullableTimestamp2(row.acked_at, "acked_at");
30038
30078
  const ackOutcome = row.ack_outcome === null ? null : typeof row.ack_outcome === "string" && ACK_OUTCOMES.has(row.ack_outcome) ? row.ack_outcome : (() => {
30039
30079
  throw new DeliveryReceiptReadError(
@@ -30085,10 +30125,10 @@ function parseDeliveryReceiptResult(value) {
30085
30125
  );
30086
30126
  }
30087
30127
  const receipts = body.receipts.map(parseDeliveryReceipt);
30088
- if (body.addressed === false && receipts.length !== 0) {
30128
+ if (body.addressed === false && receipts.some((row) => "recipient_agent_principal_id" in row)) {
30089
30129
  throw new DeliveryReceiptReadError(
30090
30130
  "protocol",
30091
- "delivery receipt read returned recipients for a broadcast"
30131
+ "delivery receipt read returned an agent recipient for a broadcast"
30092
30132
  );
30093
30133
  }
30094
30134
  if (body.addressed === true && receipts.length === 0) {
@@ -30098,7 +30138,9 @@ function parseDeliveryReceiptResult(value) {
30098
30138
  );
30099
30139
  }
30100
30140
  const recipientIds = new Set(
30101
- receipts.map((row) => row.recipient_agent_principal_id)
30141
+ receipts.map(
30142
+ (row) => "recipient_agent_principal_id" in row ? `agent:${row.recipient_agent_principal_id}` : `human:${row.recipient_user_id}`
30143
+ )
30102
30144
  );
30103
30145
  if (recipientIds.size !== receipts.length) {
30104
30146
  throw new DeliveryReceiptReadError(
@@ -30197,6 +30239,9 @@ async function readAgentDeliveryReceipts(target2, token, workspaceId2, signalId,
30197
30239
  }
30198
30240
 
30199
30241
  // src/cloud/receipts.ts
30242
+ function humanReceipt(receipt) {
30243
+ return "recipient_user_id" in receipt;
30244
+ }
30200
30245
  function signalReceiptCliState(receipt, nowMs) {
30201
30246
  const state = deliveryReceiptState(receipt, nowMs);
30202
30247
  if (state === "enqueued") return "not_delivered";
@@ -30215,13 +30260,24 @@ function newAskCommand(report, receipt) {
30215
30260
  return `cswarm ask "<question>" --to ${receipt.recipient_agent_principal_id} --workspace-id ${report.workspaceId}`;
30216
30261
  }
30217
30262
  function renderSignalReceiptReport(report, nowMs = Date.now()) {
30263
+ const humanReceipts = report.receipts.filter(humanReceipt);
30264
+ const agentReceipts = report.receipts.filter(
30265
+ (receipt) => !humanReceipt(receipt)
30266
+ );
30267
+ const humanSections = humanReceipts.map(
30268
+ (receipt) => receipt.seen_at === null ? `Not seen yet \u2014 the member's browser reports seen state when the message is viewed.` : [
30269
+ `Seen by ${receipt.recipient_user_id} at ${receipt.seen_at}.`,
30270
+ "This is a browser proxy: the message row was in view while the document had focus."
30271
+ ].join("\n")
30272
+ );
30218
30273
  if (!report.addressed) {
30219
30274
  return [
30220
30275
  "This was a broadcast; no agent was addressed and none was woken.",
30276
+ ...humanSections,
30221
30277
  `To wake an agent, send a new ask with: cswarm ask "<text>" --to <agent> --workspace-id ${report.workspaceId}`
30222
30278
  ].join("\n");
30223
30279
  }
30224
- const sections = report.receipts.map((receipt) => {
30280
+ const sections = agentReceipts.map((receipt) => {
30225
30281
  const state = deliveryReceiptState(receipt, nowMs);
30226
30282
  if (state === "enqueued") {
30227
30283
  return [
@@ -30276,25 +30332,31 @@ function renderSignalReceiptReport(report, nowMs = Date.now()) {
30276
30332
  `Ask the agent's operator to check its listener with: ${listenerStatusCommand(report, receipt)}`
30277
30333
  ].join("\n");
30278
30334
  });
30279
- return sections.join("\n\n");
30335
+ return [...humanSections, ...sections].join("\n\n");
30280
30336
  }
30281
30337
  function signalReceiptJsonPayload(report, nowMs = Date.now()) {
30282
30338
  return {
30283
30339
  workspace_id: report.workspaceId,
30284
30340
  signal_id: report.signalId,
30285
30341
  broadcast: !report.addressed,
30286
- receipts: report.receipts.map((receipt) => ({
30287
- recipient_agent_principal_id: receipt.recipient_agent_principal_id,
30288
- state: signalReceiptCliState(receipt, nowMs),
30289
- outcome: receipt.ack_outcome,
30290
- enqueued_at: receipt.enqueued_at,
30291
- delivered_at: receipt.delivered_at,
30292
- leased_until: receipt.leased_until,
30293
- acked_at: receipt.acked_at,
30294
- attempt_count: receipt.attempt_count,
30295
- lease_expiry_count: receipt.lease_expiry_count,
30296
- last_error_code: receipt.last_error_code
30297
- }))
30342
+ receipts: report.receipts.map(
30343
+ (receipt) => humanReceipt(receipt) ? {
30344
+ recipient_user_id: receipt.recipient_user_id,
30345
+ state: receipt.seen_at === null ? "not_seen" : "seen",
30346
+ seen_at: receipt.seen_at
30347
+ } : {
30348
+ recipient_agent_principal_id: receipt.recipient_agent_principal_id,
30349
+ state: signalReceiptCliState(receipt, nowMs),
30350
+ outcome: receipt.ack_outcome,
30351
+ enqueued_at: receipt.enqueued_at,
30352
+ delivered_at: receipt.delivered_at,
30353
+ leased_until: receipt.leased_until,
30354
+ acked_at: receipt.acked_at,
30355
+ attempt_count: receipt.attempt_count,
30356
+ lease_expiry_count: receipt.lease_expiry_count,
30357
+ last_error_code: receipt.last_error_code
30358
+ }
30359
+ )
30298
30360
  };
30299
30361
  }
30300
30362
 
@@ -39209,8 +39271,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
39209
39271
  AGENT_CREDENTIAL_MESSAGE_D088
39210
39272
  ];
39211
39273
  function packageVersion() {
39212
- if ("0.1.41".length > 0) {
39213
- return "0.1.41";
39274
+ if ("0.1.43".length > 0) {
39275
+ return "0.1.43";
39214
39276
  }
39215
39277
  try {
39216
39278
  const value = JSON.parse(
@@ -39342,6 +39404,9 @@ Usage:
39342
39404
  cswarm file get <name|file-id> [--version <n>] [--out <local-path>] [--force] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39343
39405
  cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39344
39406
  cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39407
+ cswarm brain ls [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39408
+ cswarm brain get <topic> [--version <n>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39409
+ cswarm brain put <topic> [<markdown-path>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # without a path, reads Markdown from stdin
39345
39410
  cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
39346
39411
  cswarm listen start ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--foreground] [--json]
39347
39412
  cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
@@ -39387,7 +39452,8 @@ Credential selection for command/dogfood:
39387
39452
  signal command/read only -- either form
39388
39453
  receipt reads only -- either form
39389
39454
  inbox --notify persists a per-agent cursor -- needs principal_id
39390
- file put, file ls, file get, file rm, file restore
39455
+ file put, file ls, file get, file rm, file restore,
39456
+ brain ls, brain get, brain put
39391
39457
  read and command, nothing persisted -- either form
39392
39458
  feedback command only, nothing persisted -- either form
39393
39459
  command, dogfood
@@ -43085,17 +43151,7 @@ async function resolveFileSelector(context, selector) {
43085
43151
  }
43086
43152
  return match.file_id;
43087
43153
  }
43088
- async function runFilePut(args) {
43089
- const localPath = args.positionals[2];
43090
- if (!localPath) throw new UsageError("cswarm file put needs a local path");
43091
- const context = await fileContext(args, ["name"], 3);
43092
- let bytes;
43093
- try {
43094
- bytes = (0, import_node_fs7.readFileSync)(localPath);
43095
- } catch {
43096
- throw new Error(`could not read ${localPath}; check the path and permissions`);
43097
- }
43098
- const name = args.optional("name") ?? (0, import_node_path20.basename)(localPath);
43154
+ async function uploadNamedFile(context, name, bytes) {
43099
43155
  if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
43100
43156
  throw new Error(
43101
43157
  `this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
@@ -43128,13 +43184,26 @@ async function runFilePut(args) {
43128
43184
  await onceRetried(
43129
43185
  () => putObject(context.cloud, created.upload_path, bytes, contentType)
43130
43186
  );
43131
- const committed = await onceRetried(
43187
+ return await onceRetried(
43132
43188
  () => fileVersionCommit({ ...send, commandId: commitCommandId }, {
43133
43189
  fileId: created.file_id,
43134
43190
  versionId: created.version_id,
43135
43191
  sha256: sha256Hex(bytes)
43136
43192
  })
43137
43193
  );
43194
+ }
43195
+ async function runFilePut(args) {
43196
+ const localPath = args.positionals[2];
43197
+ if (!localPath) throw new UsageError("cswarm file put needs a local path");
43198
+ const context = await fileContext(args, ["name"], 3);
43199
+ let bytes;
43200
+ try {
43201
+ bytes = (0, import_node_fs7.readFileSync)(localPath);
43202
+ } catch {
43203
+ throw new Error(`could not read ${localPath}; check the path and permissions`);
43204
+ }
43205
+ const name = args.optional("name") ?? (0, import_node_path20.basename)(localPath);
43206
+ const committed = await uploadNamedFile(context, name, bytes);
43138
43207
  if (args.has("json")) {
43139
43208
  process.stdout.write(`${JSON.stringify(committed, null, 2)}
43140
43209
  `);
@@ -43260,6 +43329,157 @@ async function runFileRestore(args) {
43260
43329
  `
43261
43330
  );
43262
43331
  }
43332
+ async function brainRows(context) {
43333
+ const rows3 = await fileRows(context);
43334
+ return rows3.filter((row) => row.tombstoned_at === null).flatMap((file) => {
43335
+ const topic = brainTopicFromFileName(file.name);
43336
+ return topic === null ? [] : [{ topic, file }];
43337
+ }).sort((left, right) => left.topic.localeCompare(right.topic));
43338
+ }
43339
+ async function readBrainMarkdownFromStdin() {
43340
+ if (process.stdin.isTTY) {
43341
+ throw new UsageError(
43342
+ "cswarm brain put needs a Markdown path or piped Markdown on stdin"
43343
+ );
43344
+ }
43345
+ const chunks = [];
43346
+ let size2 = 0;
43347
+ for await (const chunk of process.stdin) {
43348
+ const bytes2 = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
43349
+ size2 += bytes2.byteLength;
43350
+ if (size2 > FILE_MAX_VERSION_BYTES) {
43351
+ throw new Error(
43352
+ `brain topic input is larger than ${formatFileSize(FILE_MAX_VERSION_BYTES)}; nothing was uploaded`
43353
+ );
43354
+ }
43355
+ chunks.push(bytes2);
43356
+ }
43357
+ const bytes = Buffer.concat(chunks);
43358
+ if (bytes.byteLength === 0) {
43359
+ throw new UsageError("cswarm brain put received empty Markdown; nothing was uploaded");
43360
+ }
43361
+ return bytes;
43362
+ }
43363
+ function decodeBrainMarkdown(bytes) {
43364
+ try {
43365
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
43366
+ } catch {
43367
+ throw new Error("the brain topic is not valid UTF-8 Markdown");
43368
+ }
43369
+ }
43370
+ async function runBrainLs(args) {
43371
+ const context = await fileContext(args, [], 2);
43372
+ const topics = await brainRows(context);
43373
+ if (args.has("json")) {
43374
+ process.stdout.write(
43375
+ `${JSON.stringify({
43376
+ workspace_id: context.selected.selectedWorkspace,
43377
+ topics: topics.map(({ topic, file }) => ({ topic, ...file }))
43378
+ }, null, 2)}
43379
+ `
43380
+ );
43381
+ return;
43382
+ }
43383
+ if (topics.length === 0) {
43384
+ process.stdout.write(
43385
+ "No brain topics yet. Add one with: cswarm brain put <topic> <markdown-path>.\n"
43386
+ );
43387
+ return;
43388
+ }
43389
+ process.stdout.write(`Brain topics (${topics.length}):
43390
+ `);
43391
+ for (const { topic, file } of topics) {
43392
+ const versions = `${file.current_version} ${file.current_version === 1 ? "version" : "versions"}`;
43393
+ const author = file.uploaded_by ? `${file.uploaded_by_kind ?? file.created_by_kind} ${file.uploaded_by.slice(0, 8)}` : file.created_by_kind;
43394
+ process.stdout.write(
43395
+ `- ${topic} \xB7 ${versions} \xB7 updated ${file.committed_at ?? file.created_at} \xB7 by ${author}
43396
+ `
43397
+ );
43398
+ }
43399
+ }
43400
+ async function runBrainGet(args) {
43401
+ const requestedTopic = args.positionals[2];
43402
+ if (!requestedTopic) throw new UsageError("cswarm brain get needs a topic");
43403
+ const topic = canonicalBrainTopic(requestedTopic);
43404
+ const context = await fileContext(args, ["version"], 3);
43405
+ const row = (await brainRows(context)).find((candidate) => candidate.topic === topic);
43406
+ if (!row) {
43407
+ throw new Error(
43408
+ `no brain topic named "${sanitizeDisplayLabel(topic, "that topic")}" exists; run cswarm brain ls to see the current topics`
43409
+ );
43410
+ }
43411
+ const versionN = args.has("version") ? integer2(args, "version", { minimum: 1 }) : null;
43412
+ const grant = await fileDownloadUrl({
43413
+ target: context.cloud,
43414
+ workspaceId: context.selected.selectedWorkspace,
43415
+ credential: context.selected.bearer
43416
+ }, { fileId: row.file.file_id, versionN });
43417
+ const content = decodeBrainMarkdown(
43418
+ await getObject(context.cloud, grant.download_path)
43419
+ );
43420
+ if (args.has("json")) {
43421
+ process.stdout.write(`${JSON.stringify({
43422
+ topic,
43423
+ file_id: grant.file_id,
43424
+ version_n: grant.version_n,
43425
+ updated_at: row.file.committed_at,
43426
+ updated_by_kind: row.file.uploaded_by_kind,
43427
+ updated_by: row.file.uploaded_by,
43428
+ content
43429
+ }, null, 2)}
43430
+ `);
43431
+ return;
43432
+ }
43433
+ process.stdout.write(content.endsWith("\n") ? content : `${content}
43434
+ `);
43435
+ }
43436
+ async function runBrainPut(args) {
43437
+ const requestedTopic = args.positionals[2];
43438
+ if (!requestedTopic) throw new UsageError("cswarm brain put needs a topic");
43439
+ if (args.positionals.length > 4) {
43440
+ throw new UsageError("cswarm brain put takes one topic and, optionally, one Markdown path");
43441
+ }
43442
+ const topic = canonicalBrainTopic(requestedTopic);
43443
+ const localPath = args.positionals[3];
43444
+ if (!localPath && args.has("agent-token-stdin")) {
43445
+ throw new UsageError(
43446
+ "cswarm brain put cannot read both the credential and Markdown from stdin; use --agent-token-file or pass a Markdown path"
43447
+ );
43448
+ }
43449
+ const context = await fileContext(args, [], args.positionals.length);
43450
+ let bytes;
43451
+ if (localPath) {
43452
+ try {
43453
+ bytes = (0, import_node_fs7.readFileSync)(localPath);
43454
+ } catch {
43455
+ throw new Error(`could not read ${localPath}; check the path and permissions`);
43456
+ }
43457
+ if (bytes.byteLength === 0) {
43458
+ throw new UsageError("cswarm brain put received an empty Markdown file; nothing was uploaded");
43459
+ }
43460
+ } else {
43461
+ bytes = await readBrainMarkdownFromStdin();
43462
+ }
43463
+ decodeBrainMarkdown(bytes);
43464
+ const committed = await uploadNamedFile(context, brainFileName(topic), bytes);
43465
+ if (args.has("json")) {
43466
+ process.stdout.write(`${JSON.stringify({ topic, ...committed }, null, 2)}
43467
+ `);
43468
+ return;
43469
+ }
43470
+ process.stdout.write(
43471
+ `Saved brain topic ${topic} as version ${committed.version_n}. It is now visible to everyone in this workspace.
43472
+ Read it with: cswarm brain get ${topic}
43473
+ `
43474
+ );
43475
+ }
43476
+ async function runBrain(args) {
43477
+ const action = args.positionals[1];
43478
+ if (action === "ls") return await runBrainLs(args);
43479
+ if (action === "get") return await runBrainGet(args);
43480
+ if (action === "put") return await runBrainPut(args);
43481
+ throw new UsageError("cswarm brain takes ls, get, or put");
43482
+ }
43263
43483
  async function runFeedback(args) {
43264
43484
  const body = args.positionals[1];
43265
43485
  if (!body) {
@@ -43577,6 +43797,10 @@ async function main() {
43577
43797
  await runFile(args);
43578
43798
  return;
43579
43799
  }
43800
+ if (verb === "brain") {
43801
+ await runBrain(args);
43802
+ return;
43803
+ }
43580
43804
  if (verb === "members") {
43581
43805
  await runMembers(args);
43582
43806
  return;
@@ -43669,7 +43893,7 @@ main().catch((error) => {
43669
43893
  if (error instanceof WorkspaceCliError) {
43670
43894
  const structured = error.structured();
43671
43895
  const verb = process.argv[2];
43672
- const json = process.argv.includes("--json") && (verb === "status" || verb === "workspaces" || verb === "use" || verb === "working-on" || verb === "note" || verb === "ask" || verb === "reply" || verb === "receipt" || verb === "feed" || verb === "inbox");
43896
+ const json = process.argv.includes("--json") && (verb === "status" || verb === "workspaces" || verb === "use" || verb === "working-on" || verb === "note" || verb === "ask" || verb === "reply" || verb === "receipt" || verb === "feed" || verb === "inbox" || verb === "file" || verb === "brain");
43673
43897
  if (json) {
43674
43898
  process.stdout.write(`${JSON.stringify(structured, null, 2)}
43675
43899
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "CommonSwarm CLI — coordination for teams where people and AI agents work side by side.",
5
5
  "bin": {
6
6
  "cswarm": "cswarm.cjs"