commonswarm 0.1.52 → 0.1.53

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 +300 -200
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -13530,6 +13530,188 @@ var import_node_os10 = require("node:os");
13530
13530
  var import_node_path21 = require("node:path");
13531
13531
  var import_promises13 = require("node:readline/promises");
13532
13532
 
13533
+ // src/protocol/events.ts
13534
+ var SCHEMA_VERSION = 1;
13535
+ var EVENT_TYPES = [
13536
+ "TaskCreated",
13537
+ "LeaseAcquired",
13538
+ "LeaseRenewed",
13539
+ "LeaseHandedOff",
13540
+ "LeaseTakenOver",
13541
+ "TaskSubmitted",
13542
+ "TaskClosed",
13543
+ "TaskReopened",
13544
+ "CommandRejected"
13545
+ ];
13546
+
13547
+ // src/protocol/reducer.ts
13548
+ function req(payload, keys, type, seq) {
13549
+ if (!payload || typeof payload !== "object") throw new StreamIntegrityError(`event "${type}" at seq ${seq} has a non-object payload`);
13550
+ for (const k of keys) {
13551
+ if (payload[k] === void 0) {
13552
+ throw new StreamIntegrityError(`event "${type}" at seq ${seq} is missing payload field "${String(k)}"`);
13553
+ }
13554
+ }
13555
+ return payload;
13556
+ }
13557
+ var UnknownEventTypeError = class extends Error {
13558
+ constructor(type, seq) {
13559
+ super(`unknown authoritative event type "${type}" at seq ${seq}; halting`);
13560
+ this.type = type;
13561
+ this.seq = seq;
13562
+ this.name = "UnknownEventTypeError";
13563
+ }
13564
+ type;
13565
+ seq;
13566
+ };
13567
+ var StreamIntegrityError = class extends Error {
13568
+ constructor(message) {
13569
+ super(message);
13570
+ this.name = "StreamIntegrityError";
13571
+ }
13572
+ };
13573
+ function reduceTask(prev, env) {
13574
+ if (!EVENT_TYPES.includes(env.type)) {
13575
+ throw new UnknownEventTypeError(env.type, env.seq);
13576
+ }
13577
+ if (env.schema_version !== SCHEMA_VERSION) {
13578
+ throw new StreamIntegrityError(`event "${env.type}" at seq ${env.seq} is schema v${env.schema_version}, expected v${SCHEMA_VERSION} (upcast before reduce)`);
13579
+ }
13580
+ if (env.type === "CommandRejected") {
13581
+ if (!prev) throw new StreamIntegrityError(`CommandRejected before task exists (seq ${env.seq})`);
13582
+ req(env.payload, ["task_id", "command", "reason", "detail"], env.type, env.seq);
13583
+ return prev;
13584
+ }
13585
+ if (env.type === "TaskCreated") {
13586
+ if (prev) throw new StreamIntegrityError(`TaskCreated for an already-existing task (seq ${env.seq})`);
13587
+ const p = req(env.payload, ["task_id", "slug"], env.type, env.seq);
13588
+ return {
13589
+ task_id: p.task_id,
13590
+ slug: p.slug,
13591
+ lifecycle: "open",
13592
+ version: 1,
13593
+ epoch: 0,
13594
+ owner: null,
13595
+ lease_expiry: null,
13596
+ submission: null,
13597
+ closed_disposition: null
13598
+ };
13599
+ }
13600
+ if (!prev) throw new StreamIntegrityError(`event "${env.type}" before TaskCreated (seq ${env.seq})`);
13601
+ const s = prev;
13602
+ function assertEpochIncrease(ep) {
13603
+ if (typeof ep !== "number" || ep <= s.epoch) {
13604
+ throw new StreamIntegrityError(`${env.type} at seq ${env.seq} has non-increasing epoch ${ep} (current ${s.epoch})`);
13605
+ }
13606
+ }
13607
+ const leaseLifecycle = s.submission ? "awaiting_review" : "active";
13608
+ switch (env.type) {
13609
+ case "LeaseAcquired": {
13610
+ const p = req(env.payload, ["task_id", "epoch", "owner", "lease_expiry"], env.type, env.seq);
13611
+ assertEpochIncrease(p.epoch);
13612
+ return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.owner, lease_expiry: p.lease_expiry };
13613
+ }
13614
+ case "LeaseRenewed": {
13615
+ const p = req(env.payload, ["task_id", "epoch", "lease_expiry"], env.type, env.seq);
13616
+ if (p.epoch !== s.epoch) throw new StreamIntegrityError(`LeaseRenewed at seq ${env.seq} epoch ${p.epoch} != current ${s.epoch}`);
13617
+ return { ...s, lease_expiry: p.lease_expiry };
13618
+ }
13619
+ case "LeaseHandedOff": {
13620
+ const p = req(env.payload, ["task_id", "epoch", "from_owner", "to_owner", "lease_expiry"], env.type, env.seq);
13621
+ assertEpochIncrease(p.epoch);
13622
+ return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.to_owner, lease_expiry: p.lease_expiry };
13623
+ }
13624
+ case "LeaseTakenOver": {
13625
+ const p = req(env.payload, ["task_id", "epoch", "owner", "lease_expiry", "grant_id"], env.type, env.seq);
13626
+ assertEpochIncrease(p.epoch);
13627
+ return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.owner, lease_expiry: p.lease_expiry };
13628
+ }
13629
+ case "TaskSubmitted": {
13630
+ const p = req(env.payload, ["task_id", "epoch", "branch", "head_sha", "evidence_set"], env.type, env.seq);
13631
+ return {
13632
+ ...s,
13633
+ lifecycle: "awaiting_review",
13634
+ submission: { epoch: p.epoch, branch: p.branch, head_sha: p.head_sha, evidence_set: [...p.evidence_set] }
13635
+ };
13636
+ }
13637
+ case "TaskClosed": {
13638
+ const p = req(env.payload, ["task_id", "epoch", "disposition", "grant_id"], env.type, env.seq);
13639
+ return { ...s, lifecycle: "done", closed_disposition: p.disposition };
13640
+ }
13641
+ case "TaskReopened": {
13642
+ const p = req(env.payload, ["task_id", "version"], env.type, env.seq);
13643
+ if (p.version <= s.version) throw new StreamIntegrityError(`TaskReopened at seq ${env.seq} version ${p.version} not > current ${s.version}`);
13644
+ return { ...s, lifecycle: "reopened", version: p.version, submission: null, owner: null, lease_expiry: null };
13645
+ }
13646
+ default: {
13647
+ throw new UnknownEventTypeError(env.type, env.seq);
13648
+ }
13649
+ }
13650
+ }
13651
+
13652
+ // src/protocol/idempotency.ts
13653
+ function canonicalJson(value) {
13654
+ return JSON.stringify(sortValue(value));
13655
+ }
13656
+ function sortValue(v) {
13657
+ if (Array.isArray(v)) return v.map(sortValue);
13658
+ if (v && typeof v === "object") {
13659
+ const out = {};
13660
+ for (const k of Object.keys(v).sort()) {
13661
+ out[k] = sortValue(v[k]);
13662
+ }
13663
+ return out;
13664
+ }
13665
+ return v;
13666
+ }
13667
+
13668
+ // src/protocol/upcasters.ts
13669
+ var registry = /* @__PURE__ */ new Map();
13670
+ function key(type, fromVersion) {
13671
+ return `${type}:${fromVersion}`;
13672
+ }
13673
+ function registerUpcaster(type, fromVersion, fn) {
13674
+ registry.set(key(type, fromVersion), fn);
13675
+ }
13676
+ var UpcastError = class extends Error {
13677
+ constructor(message) {
13678
+ super(message);
13679
+ this.name = "UpcastError";
13680
+ }
13681
+ };
13682
+ function upcastPayload(type, fromVersion, payload) {
13683
+ let v = fromVersion;
13684
+ let p = payload;
13685
+ if (v > SCHEMA_VERSION) {
13686
+ throw new UpcastError(`event "${type}" is schema v${v}, newer than supported v${SCHEMA_VERSION}; halting`);
13687
+ }
13688
+ while (v < SCHEMA_VERSION) {
13689
+ const fn = registry.get(key(type, v));
13690
+ if (!fn) throw new UpcastError(`no upcaster for "${type}" v${v}\u2192v${v + 1}`);
13691
+ p = fn(p);
13692
+ v += 1;
13693
+ }
13694
+ return { payload: p, schema_version: SCHEMA_VERSION };
13695
+ }
13696
+ function upcastEnvelope(raw) {
13697
+ const { payload, schema_version } = upcastPayload(raw.type, raw.schema_version, raw.payload);
13698
+ return { ...raw, payload, schema_version };
13699
+ }
13700
+ registerUpcaster("TaskCreated", 0, (p) => ({ task_id: p.id, slug: p.name }));
13701
+
13702
+ // src/protocol/workspace-commands.ts
13703
+ var INVITATION_MAX_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13704
+ var AGENT_TOKEN_DEFAULT_TTL_MS = 60 * 60 * 1e3;
13705
+ var AGENT_TOKEN_MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
13706
+ var RENEWAL_HORIZON_DEFAULT_MS = 30 * 24 * 60 * 60 * 1e3;
13707
+ var RENEWAL_HORIZON_MAX_MS = 90 * 24 * 60 * 60 * 1e3;
13708
+
13709
+ // src/protocol/brain-version-window.ts
13710
+ var BRAIN_FILE_PREFIX = "brain--";
13711
+ var BRAIN_FILE_SUFFIX = ".md";
13712
+ var BRAIN_TOPIC_MAX_LENGTH = 255 - BRAIN_FILE_PREFIX.length - BRAIN_FILE_SUFFIX.length;
13713
+ var FILE_VERSION_PRECONDITION_FAILED = "file_version_precondition_failed";
13714
+
13533
13715
  // src/cloud/auth.ts
13534
13716
  var import_node_crypto2 = require("node:crypto");
13535
13717
  var import_node_http = require("node:http");
@@ -21959,189 +22141,6 @@ async function logout(target2, store2, scope = "local", options = {}) {
21959
22141
 
21960
22142
  // src/cloud/command-client.ts
21961
22143
  var import_node_crypto3 = require("node:crypto");
21962
-
21963
- // src/protocol/events.ts
21964
- var SCHEMA_VERSION = 1;
21965
- var EVENT_TYPES = [
21966
- "TaskCreated",
21967
- "LeaseAcquired",
21968
- "LeaseRenewed",
21969
- "LeaseHandedOff",
21970
- "LeaseTakenOver",
21971
- "TaskSubmitted",
21972
- "TaskClosed",
21973
- "TaskReopened",
21974
- "CommandRejected"
21975
- ];
21976
-
21977
- // src/protocol/reducer.ts
21978
- function req(payload, keys, type, seq) {
21979
- if (!payload || typeof payload !== "object") throw new StreamIntegrityError(`event "${type}" at seq ${seq} has a non-object payload`);
21980
- for (const k of keys) {
21981
- if (payload[k] === void 0) {
21982
- throw new StreamIntegrityError(`event "${type}" at seq ${seq} is missing payload field "${String(k)}"`);
21983
- }
21984
- }
21985
- return payload;
21986
- }
21987
- var UnknownEventTypeError = class extends Error {
21988
- constructor(type, seq) {
21989
- super(`unknown authoritative event type "${type}" at seq ${seq}; halting`);
21990
- this.type = type;
21991
- this.seq = seq;
21992
- this.name = "UnknownEventTypeError";
21993
- }
21994
- type;
21995
- seq;
21996
- };
21997
- var StreamIntegrityError = class extends Error {
21998
- constructor(message) {
21999
- super(message);
22000
- this.name = "StreamIntegrityError";
22001
- }
22002
- };
22003
- function reduceTask(prev, env) {
22004
- if (!EVENT_TYPES.includes(env.type)) {
22005
- throw new UnknownEventTypeError(env.type, env.seq);
22006
- }
22007
- if (env.schema_version !== SCHEMA_VERSION) {
22008
- throw new StreamIntegrityError(`event "${env.type}" at seq ${env.seq} is schema v${env.schema_version}, expected v${SCHEMA_VERSION} (upcast before reduce)`);
22009
- }
22010
- if (env.type === "CommandRejected") {
22011
- if (!prev) throw new StreamIntegrityError(`CommandRejected before task exists (seq ${env.seq})`);
22012
- req(env.payload, ["task_id", "command", "reason", "detail"], env.type, env.seq);
22013
- return prev;
22014
- }
22015
- if (env.type === "TaskCreated") {
22016
- if (prev) throw new StreamIntegrityError(`TaskCreated for an already-existing task (seq ${env.seq})`);
22017
- const p = req(env.payload, ["task_id", "slug"], env.type, env.seq);
22018
- return {
22019
- task_id: p.task_id,
22020
- slug: p.slug,
22021
- lifecycle: "open",
22022
- version: 1,
22023
- epoch: 0,
22024
- owner: null,
22025
- lease_expiry: null,
22026
- submission: null,
22027
- closed_disposition: null
22028
- };
22029
- }
22030
- if (!prev) throw new StreamIntegrityError(`event "${env.type}" before TaskCreated (seq ${env.seq})`);
22031
- const s = prev;
22032
- function assertEpochIncrease(ep) {
22033
- if (typeof ep !== "number" || ep <= s.epoch) {
22034
- throw new StreamIntegrityError(`${env.type} at seq ${env.seq} has non-increasing epoch ${ep} (current ${s.epoch})`);
22035
- }
22036
- }
22037
- const leaseLifecycle = s.submission ? "awaiting_review" : "active";
22038
- switch (env.type) {
22039
- case "LeaseAcquired": {
22040
- const p = req(env.payload, ["task_id", "epoch", "owner", "lease_expiry"], env.type, env.seq);
22041
- assertEpochIncrease(p.epoch);
22042
- return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.owner, lease_expiry: p.lease_expiry };
22043
- }
22044
- case "LeaseRenewed": {
22045
- const p = req(env.payload, ["task_id", "epoch", "lease_expiry"], env.type, env.seq);
22046
- if (p.epoch !== s.epoch) throw new StreamIntegrityError(`LeaseRenewed at seq ${env.seq} epoch ${p.epoch} != current ${s.epoch}`);
22047
- return { ...s, lease_expiry: p.lease_expiry };
22048
- }
22049
- case "LeaseHandedOff": {
22050
- const p = req(env.payload, ["task_id", "epoch", "from_owner", "to_owner", "lease_expiry"], env.type, env.seq);
22051
- assertEpochIncrease(p.epoch);
22052
- return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.to_owner, lease_expiry: p.lease_expiry };
22053
- }
22054
- case "LeaseTakenOver": {
22055
- const p = req(env.payload, ["task_id", "epoch", "owner", "lease_expiry", "grant_id"], env.type, env.seq);
22056
- assertEpochIncrease(p.epoch);
22057
- return { ...s, lifecycle: leaseLifecycle, epoch: p.epoch, owner: p.owner, lease_expiry: p.lease_expiry };
22058
- }
22059
- case "TaskSubmitted": {
22060
- const p = req(env.payload, ["task_id", "epoch", "branch", "head_sha", "evidence_set"], env.type, env.seq);
22061
- return {
22062
- ...s,
22063
- lifecycle: "awaiting_review",
22064
- submission: { epoch: p.epoch, branch: p.branch, head_sha: p.head_sha, evidence_set: [...p.evidence_set] }
22065
- };
22066
- }
22067
- case "TaskClosed": {
22068
- const p = req(env.payload, ["task_id", "epoch", "disposition", "grant_id"], env.type, env.seq);
22069
- return { ...s, lifecycle: "done", closed_disposition: p.disposition };
22070
- }
22071
- case "TaskReopened": {
22072
- const p = req(env.payload, ["task_id", "version"], env.type, env.seq);
22073
- if (p.version <= s.version) throw new StreamIntegrityError(`TaskReopened at seq ${env.seq} version ${p.version} not > current ${s.version}`);
22074
- return { ...s, lifecycle: "reopened", version: p.version, submission: null, owner: null, lease_expiry: null };
22075
- }
22076
- default: {
22077
- throw new UnknownEventTypeError(env.type, env.seq);
22078
- }
22079
- }
22080
- }
22081
-
22082
- // src/protocol/idempotency.ts
22083
- function canonicalJson(value) {
22084
- return JSON.stringify(sortValue(value));
22085
- }
22086
- function sortValue(v) {
22087
- if (Array.isArray(v)) return v.map(sortValue);
22088
- if (v && typeof v === "object") {
22089
- const out = {};
22090
- for (const k of Object.keys(v).sort()) {
22091
- out[k] = sortValue(v[k]);
22092
- }
22093
- return out;
22094
- }
22095
- return v;
22096
- }
22097
-
22098
- // src/protocol/upcasters.ts
22099
- var registry = /* @__PURE__ */ new Map();
22100
- function key(type, fromVersion) {
22101
- return `${type}:${fromVersion}`;
22102
- }
22103
- function registerUpcaster(type, fromVersion, fn) {
22104
- registry.set(key(type, fromVersion), fn);
22105
- }
22106
- var UpcastError = class extends Error {
22107
- constructor(message) {
22108
- super(message);
22109
- this.name = "UpcastError";
22110
- }
22111
- };
22112
- function upcastPayload(type, fromVersion, payload) {
22113
- let v = fromVersion;
22114
- let p = payload;
22115
- if (v > SCHEMA_VERSION) {
22116
- throw new UpcastError(`event "${type}" is schema v${v}, newer than supported v${SCHEMA_VERSION}; halting`);
22117
- }
22118
- while (v < SCHEMA_VERSION) {
22119
- const fn = registry.get(key(type, v));
22120
- if (!fn) throw new UpcastError(`no upcaster for "${type}" v${v}\u2192v${v + 1}`);
22121
- p = fn(p);
22122
- v += 1;
22123
- }
22124
- return { payload: p, schema_version: SCHEMA_VERSION };
22125
- }
22126
- function upcastEnvelope(raw) {
22127
- const { payload, schema_version } = upcastPayload(raw.type, raw.schema_version, raw.payload);
22128
- return { ...raw, payload, schema_version };
22129
- }
22130
- registerUpcaster("TaskCreated", 0, (p) => ({ task_id: p.id, slug: p.name }));
22131
-
22132
- // src/protocol/workspace-commands.ts
22133
- var INVITATION_MAX_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
22134
- var AGENT_TOKEN_DEFAULT_TTL_MS = 60 * 60 * 1e3;
22135
- var AGENT_TOKEN_MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
22136
- var RENEWAL_HORIZON_DEFAULT_MS = 30 * 24 * 60 * 60 * 1e3;
22137
- var RENEWAL_HORIZON_MAX_MS = 90 * 24 * 60 * 60 * 1e3;
22138
-
22139
- // src/protocol/brain-version-window.ts
22140
- var BRAIN_FILE_PREFIX = "brain--";
22141
- var BRAIN_FILE_SUFFIX = ".md";
22142
- var BRAIN_TOPIC_MAX_LENGTH = 255 - BRAIN_FILE_PREFIX.length - BRAIN_FILE_SUFFIX.length;
22143
-
22144
- // src/cloud/command-client.ts
22145
22144
  var AGENT_TOKEN_RE = /^swm_agt_[A-Za-z0-9_-]{43}$/;
22146
22145
  var INVITATION_TOKEN_RE = /^swm_inv_[A-Za-z0-9_-]{43}$/;
22147
22146
  var CAPABILITY_TOKEN_RE = /^swm_cap_[A-Za-z0-9_-]{43}$/;
@@ -22978,13 +22977,18 @@ async function sendFileCommand(options, command2) {
22978
22977
  return body;
22979
22978
  }
22980
22979
  function fileVersionCreate(options, input) {
22980
+ const ifVersion = input.ifVersion ?? null;
22981
22981
  return sendFileCommand(options, {
22982
22982
  kind: "file_version_create",
22983
22983
  file_id: input.fileId,
22984
22984
  version_id: input.versionId,
22985
22985
  name: input.name,
22986
22986
  declared_size_bytes: input.declaredSizeBytes,
22987
- content_type: input.contentType
22987
+ content_type: input.contentType,
22988
+ /* The server validates an exact key set, so the key is sent only when a
22989
+ * precondition was asked for. An unconditional write is byte-identical to
22990
+ * what every earlier client sent. */
22991
+ ...ifVersion === null ? {} : { if_version: ifVersion }
22988
22992
  });
22989
22993
  }
22990
22994
  function fileVersionCommit(options, input) {
@@ -41648,6 +41652,23 @@ var ListenerHttpClient = class {
41648
41652
 
41649
41653
  // src/resume.ts
41650
41654
  var import_node_child_process8 = require("node:child_process");
41655
+ var DEFAULT_PROCESS_TABLE_COMMAND = {
41656
+ file: "ps",
41657
+ args: ["-axo", "pid=,command="]
41658
+ };
41659
+ var ProcessTableError = class extends Error {
41660
+ constructor(command2, detail) {
41661
+ super(
41662
+ `could not read the host process table with ${command2.file}: ${detail}`
41663
+ );
41664
+ this.command = command2;
41665
+ this.detail = detail;
41666
+ this.name = "ProcessTableError";
41667
+ }
41668
+ command;
41669
+ detail;
41670
+ code = "process_table_unavailable";
41671
+ };
41651
41672
  function execFileText(file, args) {
41652
41673
  return new Promise((resolve3, reject) => {
41653
41674
  (0, import_node_child_process8.execFile)(file, [...args], {
@@ -41659,15 +41680,76 @@ function execFileText(file, args) {
41659
41680
  });
41660
41681
  });
41661
41682
  }
41662
- function systemProcessTable() {
41683
+ function parseProcessRow(line) {
41684
+ const match = /^\s*(\d+)\s+(.*)$/.exec(line);
41685
+ if (!match) return null;
41686
+ const pid = Number(match[1]);
41687
+ if (!Number.isSafeInteger(pid) || pid <= 0) return null;
41688
+ return { pid, command: match[2] };
41689
+ }
41690
+ var PROCESS_TABLE_STDERR_MAX_CHARS = 2e3;
41691
+ function systemProcessTable(options = {}) {
41692
+ const command2 = options.command ?? DEFAULT_PROCESS_TABLE_COMMAND;
41693
+ const retain = options.retain ?? (() => true);
41663
41694
  return {
41664
- async list() {
41665
- const output = await execFileText("ps", ["-axo", "pid=,command="]);
41666
- return output.split("\n").flatMap((line) => {
41667
- const match = /^\s*(\d+)\s+(.*)$/.exec(line);
41668
- if (!match) return [];
41669
- const pid = Number(match[1]);
41670
- return Number.isSafeInteger(pid) && pid > 0 ? [{ pid, command: match[2] }] : [];
41695
+ list() {
41696
+ return new Promise((resolve3, reject) => {
41697
+ const child = (0, import_node_child_process8.spawn)(command2.file, [...command2.args], {
41698
+ stdio: ["ignore", "pipe", "pipe"]
41699
+ });
41700
+ const rows3 = [];
41701
+ let pending = "";
41702
+ let stderr = "";
41703
+ let settled = false;
41704
+ const fail = (detail) => {
41705
+ if (settled) return;
41706
+ settled = true;
41707
+ child.stdout.destroy();
41708
+ reject(new ProcessTableError(command2, detail));
41709
+ };
41710
+ const take = (line) => {
41711
+ const row = parseProcessRow(line);
41712
+ if (row === null) return true;
41713
+ try {
41714
+ if (retain(row.command)) rows3.push(row);
41715
+ } catch {
41716
+ fail("its row filter threw");
41717
+ return false;
41718
+ }
41719
+ return true;
41720
+ };
41721
+ child.stdout.setEncoding("utf8");
41722
+ child.stdout.on("data", (chunk) => {
41723
+ if (settled) return;
41724
+ const lines = (pending + chunk).split("\n");
41725
+ pending = lines.pop() ?? "";
41726
+ for (const line of lines) {
41727
+ if (!take(line)) return;
41728
+ }
41729
+ });
41730
+ child.stderr.setEncoding("utf8");
41731
+ child.stderr.on("data", (chunk) => {
41732
+ const room = PROCESS_TABLE_STDERR_MAX_CHARS - stderr.length;
41733
+ if (room > 0) stderr += chunk.slice(0, room);
41734
+ });
41735
+ child.stdout.on("error", () => fail("its output stream failed"));
41736
+ child.stderr.on("error", () => fail("its error stream failed"));
41737
+ child.on("error", (error) => fail(error.name));
41738
+ child.on("close", (code, signal) => {
41739
+ if (settled) return;
41740
+ if (pending.length > 0 && !take(pending)) return;
41741
+ if (signal !== null) {
41742
+ fail(`it was stopped by ${signal}`);
41743
+ return;
41744
+ }
41745
+ if (code !== 0) {
41746
+ const trailer = stderr.trim().length > 0 ? `: ${stderr.trim().slice(0, PROCESS_TABLE_STDERR_MAX_CHARS)}` : "";
41747
+ fail(`it exited ${code}${trailer}`);
41748
+ return;
41749
+ }
41750
+ settled = true;
41751
+ resolve3(rows3);
41752
+ });
41671
41753
  });
41672
41754
  }
41673
41755
  };
@@ -41717,7 +41799,10 @@ function isNotifyCommand(command2) {
41717
41799
  return /(?:^|\s)inbox(?:\s|$)/.test(command2) && /(?:^|\s)--notify(?:\s|$)/.test(command2);
41718
41800
  }
41719
41801
  async function findNotifyWatchers(options) {
41720
- const processTable = options.processTable ?? systemProcessTable();
41802
+ const processTable = options.processTable ?? systemProcessTable({
41803
+ retain: isNotifyCommand,
41804
+ ...options.processTableCommand ? { command: options.processTableCommand } : {}
41805
+ });
41721
41806
  const stdoutConsumer = options.stdoutConsumer ?? lsofStdoutConsumer();
41722
41807
  const rows3 = await processTable.list();
41723
41808
  const matches = rows3.flatMap((row) => {
@@ -41985,6 +42070,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
41985
42070
  "grok-executable",
41986
42071
  "head-sha",
41987
42072
  "help",
42073
+ "if-version",
41988
42074
  "include-stale",
41989
42075
  "include-tombstoned",
41990
42076
  "invitation-id",
@@ -42056,8 +42142,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
42056
42142
  ]);
42057
42143
  var UUID_RE23 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42058
42144
  function packageVersion() {
42059
- if ("0.1.52".length > 0) {
42060
- return "0.1.52";
42145
+ if ("0.1.53".length > 0) {
42146
+ return "0.1.53";
42061
42147
  }
42062
42148
  try {
42063
42149
  const value = JSON.parse(
@@ -42192,7 +42278,7 @@ Usage:
42192
42278
  cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42193
42279
  cswarm brain ls [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42194
42280
  cswarm brain get <topic>[@<version>] [--version <n>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42195
- cswarm brain put <topic> [<markdown-path>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # without a path, reads Markdown from stdin
42281
+ cswarm brain put <topic> [<markdown-path>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--if-version <n>] [--json] # without a path, reads Markdown from stdin; --if-version refuses the write unless the live version is still <n>
42196
42282
  cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42197
42283
  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>] [--allow-unattended] [--foreground] [--json]
42198
42284
  cswarm listen canary ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--state-dir <path>] [--wait <seconds>] [--json]
@@ -46796,7 +46882,7 @@ async function resolveFileSelector(context, selector) {
46796
46882
  }
46797
46883
  return match.file_id;
46798
46884
  }
46799
- async function uploadNamedFile(context, name, bytes) {
46885
+ async function uploadNamedFile(context, name, bytes, options = {}) {
46800
46886
  if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
46801
46887
  throw new Error(
46802
46888
  `this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
@@ -46823,7 +46909,8 @@ async function uploadNamedFile(context, name, bytes) {
46823
46909
  versionId,
46824
46910
  name,
46825
46911
  declaredSizeBytes: bytes.byteLength,
46826
- contentType
46912
+ contentType,
46913
+ ...options.ifVersion === void 0 ? {} : { ifVersion: options.ifVersion }
46827
46914
  })
46828
46915
  );
46829
46916
  await onceRetried(
@@ -47117,7 +47204,8 @@ async function runBrainPut(args) {
47117
47204
  "cswarm brain put cannot read both the credential and Markdown from stdin; use --agent-token-file or pass a Markdown path"
47118
47205
  );
47119
47206
  }
47120
- const context = await fileContext(args, [], args.positionals.length);
47207
+ const context = await fileContext(args, ["if-version"], args.positionals.length);
47208
+ const ifVersion = args.optional("if-version") === void 0 ? void 0 : integer2(args, "if-version", { minimum: 0 });
47121
47209
  let bytes;
47122
47210
  if (localPath) {
47123
47211
  try {
@@ -47132,7 +47220,19 @@ async function runBrainPut(args) {
47132
47220
  bytes = await readBrainMarkdownFromStdin();
47133
47221
  }
47134
47222
  decodeBrainMarkdown(bytes);
47135
- const committed = await uploadNamedFile(context, brainFileName(topic), bytes);
47223
+ const committed = await uploadNamedFile(
47224
+ context,
47225
+ brainFileName(topic),
47226
+ bytes,
47227
+ ifVersion === void 0 ? {} : { ifVersion }
47228
+ ).catch((error) => {
47229
+ if (error instanceof FileCommandRefused && error.code === FILE_VERSION_PRECONDITION_FAILED) {
47230
+ throw new Error(
47231
+ `${error.message}. Someone saved a new version after you read this topic. Re-read it, apply your change to that copy, then put it again: cswarm brain get ${topic}`
47232
+ );
47233
+ }
47234
+ throw error;
47235
+ });
47136
47236
  if (args.has("json")) {
47137
47237
  process.stdout.write(`${JSON.stringify({ topic, ...committed }, null, 2)}
47138
47238
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
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"