commonswarm 0.1.51 → 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 +490 -321
  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) {
@@ -26624,6 +26628,138 @@ function parseAgentCredentialInput(value, source) {
26624
26628
 
26625
26629
  // src/cloud/renewal.ts
26626
26630
  var import_node_crypto9 = require("node:crypto");
26631
+
26632
+ // src/cloud/renewal-grants.ts
26633
+ var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26634
+ function nullableString(value, field) {
26635
+ if (value === null) return null;
26636
+ if (typeof value !== "string") {
26637
+ throw new Error(`renewal grant read returned malformed ${field}`);
26638
+ }
26639
+ return value;
26640
+ }
26641
+ function nullableTimestamp(value, field) {
26642
+ const text = nullableString(value, field);
26643
+ if (text !== null && !Number.isFinite(Date.parse(text))) {
26644
+ throw new Error(`renewal grant read returned malformed ${field}`);
26645
+ }
26646
+ return text;
26647
+ }
26648
+ function uuid2(value, field) {
26649
+ if (typeof value !== "string" || !UUID_RE5.test(value)) {
26650
+ throw new Error(`renewal grant read returned malformed ${field}`);
26651
+ }
26652
+ return value.toLowerCase();
26653
+ }
26654
+ function nullableUuid(value, field) {
26655
+ return value === null ? null : uuid2(value, field);
26656
+ }
26657
+ function parseGrant(value) {
26658
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
26659
+ throw new Error("renewal grant read returned a malformed row");
26660
+ }
26661
+ const row = value;
26662
+ if (row.kind !== "timeboxed" && row.kind !== "standing") {
26663
+ throw new Error("renewal grant read returned malformed kind");
26664
+ }
26665
+ const horizon = nullableTimestamp(
26666
+ row.horizon_expires_at,
26667
+ "horizon_expires_at"
26668
+ );
26669
+ if (row.kind === "standing" && horizon !== null || row.kind === "timeboxed" && horizon === null) {
26670
+ throw new Error("renewal grant read returned an invalid kind/horizon pair");
26671
+ }
26672
+ return {
26673
+ renewal_grant_id: uuid2(row.renewal_grant_id, "renewal_grant_id"),
26674
+ principal_id: uuid2(row.principal_id, "principal_id"),
26675
+ kind: row.kind,
26676
+ horizon_expires_at: horizon,
26677
+ bound_device_id: nullableUuid(row.bound_device_id, "bound_device_id"),
26678
+ last_used_at: nullableTimestamp(row.last_used_at, "last_used_at"),
26679
+ last_used_device_id: nullableUuid(
26680
+ row.last_used_device_id,
26681
+ "last_used_device_id"
26682
+ ),
26683
+ last_used_from: nullableString(row.last_used_from, "last_used_from"),
26684
+ new_host_at: nullableTimestamp(row.new_host_at, "new_host_at"),
26685
+ suspended_at: nullableTimestamp(row.suspended_at, "suspended_at"),
26686
+ revoked_at: nullableTimestamp(row.revoked_at, "revoked_at"),
26687
+ token_id: nullableUuid(row.token_id, "token_id"),
26688
+ issued_at: nullableTimestamp(row.issued_at, "issued_at"),
26689
+ token_expires_at: nullableTimestamp(
26690
+ row.token_expires_at,
26691
+ "token_expires_at"
26692
+ ),
26693
+ token_revoked_at: nullableTimestamp(
26694
+ row.token_revoked_at,
26695
+ "token_revoked_at"
26696
+ )
26697
+ };
26698
+ }
26699
+ async function readRenewalGrants(target2, credential, workspaceId2, fetcher = fetch) {
26700
+ const response = await fetcher(readEndpoint(target2), {
26701
+ method: "POST",
26702
+ headers: {
26703
+ authorization: `Bearer ${credential}`,
26704
+ apikey: target2.anonKey,
26705
+ "content-type": "application/json"
26706
+ },
26707
+ body: JSON.stringify({
26708
+ resource: "renewal_grants",
26709
+ workspace_id: workspaceId2
26710
+ }),
26711
+ signal: AbortSignal.timeout(15e3)
26712
+ });
26713
+ if (!response.ok) {
26714
+ throw new Error(`renewal grant read failed (HTTP ${response.status})`);
26715
+ }
26716
+ const body = await response.json().catch(() => null);
26717
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
26718
+ throw new Error("renewal grant read returned malformed JSON");
26719
+ }
26720
+ const grants = body.grants;
26721
+ if (!Array.isArray(grants)) {
26722
+ throw new Error("renewal grant read returned no grants array");
26723
+ }
26724
+ return grants.map(parseGrant);
26725
+ }
26726
+ var STANDING_IDLE_PAUSE_DAYS = 14;
26727
+ var STANDING_RESUME_ACTORS = [
26728
+ "a workspace owner",
26729
+ "an admin",
26730
+ "the member who added the agent"
26731
+ ];
26732
+ function orList(items) {
26733
+ if (items.length === 0) return "";
26734
+ if (items.length === 1) return items[0];
26735
+ return `${items.slice(0, -1).join(", ")}, or ${items[items.length - 1]}`;
26736
+ }
26737
+ var STANDING_RESUME_ACTORS_SENTENCE = orList(STANDING_RESUME_ACTORS);
26738
+ var STANDING_GRANT_RULES = [
26739
+ "Access does not expire.",
26740
+ `${STANDING_IDLE_PAUSE_DAYS} days with no use pauses it; ${STANDING_RESUME_ACTORS_SENTENCE} can resume it.`,
26741
+ "Revoking it is the only permanent stop."
26742
+ ];
26743
+ function standingPausedRenewalMessage(idle) {
26744
+ const cause = idle ? `This standing grant went ${STANDING_IDLE_PAUSE_DAYS} days with no use, so CommonSwarm paused it and refused renewal.` : "This renewal grant is paused, so CommonSwarm refused renewal.";
26745
+ return `${cause} It is not revoked and this agent is not gone. Next step: ${STANDING_RESUME_ACTORS_SENTENCE} runs cswarm grant resume, then this agent continues.`;
26746
+ }
26747
+ function describeRenewalGrant(grant) {
26748
+ const lines = grant.kind === "standing" ? [`Grant: standing \u2014 ${STANDING_GRANT_RULES.join(" ")}`] : [`Grant: timeboxed \u2014 renewal horizon ${grant.horizon_expires_at}.`];
26749
+ if (grant.suspended_at !== null) {
26750
+ lines.push(
26751
+ `PAUSED since ${grant.suspended_at} after ${STANDING_IDLE_PAUSE_DAYS} days with no use. This is not revoked and the agent is not gone. Next step: ${orList(STANDING_RESUME_ACTORS)} runs cswarm grant resume --renewal-grant-id ${grant.renewal_grant_id}`
26752
+ );
26753
+ }
26754
+ if (grant.revoked_at !== null) {
26755
+ lines.push(
26756
+ `REVOKED since ${grant.revoked_at}. This is permanent and cannot be resumed. Next step: mint a new grant if this agent should continue.`
26757
+ );
26758
+ }
26759
+ return lines;
26760
+ }
26761
+
26762
+ // src/cloud/renewal.ts
26627
26763
  var AGENT_TOKEN_DEFAULT_TTL_MS2 = 60 * 60 * 1e3;
26628
26764
  var AGENT_TOKEN_MAX_TTL_MS2 = 8 * 60 * 60 * 1e3;
26629
26765
  var RENEWAL_HORIZON_DEFAULT_MS2 = 30 * 24 * 60 * 60 * 1e3;
@@ -26633,7 +26769,8 @@ function describeMintRenewal(hasExpiry, horizonDays, kind = "timeboxed") {
26633
26769
  return "This credential does not renew itself; re-issue one by hand when it expires.\n";
26634
26770
  }
26635
26771
  if (kind === "standing") {
26636
- return "Standing grant created. This does not expire. Revoke is the only kill switch. The bearer credential still rotates before expiry while a cswarm process remains running and secure local state is available.\n";
26772
+ return `Standing grant created. ${STANDING_GRANT_RULES.join(" ")} The bearer credential still rotates before expiry while a cswarm process remains running and secure local state is available.
26773
+ `;
26637
26774
  }
26638
26775
  const days = Number.isFinite(horizonDays) && horizonDays > 0 ? Math.round(horizonDays) : Math.round(RENEWAL_HORIZON_DEFAULT_MS2 / 864e5);
26639
26776
  return `While a cswarm process remains running and secure local state is available, this credential rotates before expiry. A person is asked to authorise it again in ${days} days. A stopped or idle CLI cannot renew it.
@@ -26644,7 +26781,7 @@ var RENEWAL_LEAD_FLOOR_MS = 5 * 6e4;
26644
26781
  var RENEWAL_LEAD_CEILING_MS = 15 * 6e4;
26645
26782
  var RENEWAL_PENDING_RECOVERY_MS = 60 * 6e4;
26646
26783
  var RENEW_TIMEOUT_MS = 3e4;
26647
- var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26784
+ var UUID_RE6 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26648
26785
  var AGENT_TOKEN_RE4 = /^swm_agt_[A-Za-z0-9_-]{43}$/;
26649
26786
  function renewalDueAt(issuedAt, expiresAt) {
26650
26787
  const lifetime = Math.max(0, expiresAt - issuedAt);
@@ -26803,7 +26940,7 @@ async function requestSuccessor(options) {
26803
26940
  } catch {
26804
26941
  body = {};
26805
26942
  }
26806
- const principalId = typeof body.principal_id === "string" && UUID_RE5.test(body.principal_id) ? body.principal_id.toLowerCase() : null;
26943
+ const principalId = typeof body.principal_id === "string" && UUID_RE6.test(body.principal_id) ? body.principal_id.toLowerCase() : null;
26807
26944
  if (response.status === 400 || response.status === 404) {
26808
26945
  throw new RenewalUnsupported(
26809
26946
  "this deployment does not offer credential renewal yet, so a credential here still has to be re-issued by hand when it expires"
@@ -26838,7 +26975,7 @@ async function requestSuccessor(options) {
26838
26975
  if (reason === "renewal_idle_suspended" || reason === "renewal_grant_suspended") {
26839
26976
  throw new RenewalSuspended(
26840
26977
  reason,
26841
- reason === "renewal_idle_suspended" ? "This standing grant was idle for more than 14 days, so CommonSwarm suspended it and refused renewal. Ask a workspace owner to revoke this grant and mint a new credential before this agent continues." : "This renewal grant is suspended, so CommonSwarm refused renewal. Ask a workspace owner to revoke this grant and mint a new credential before this agent continues."
26978
+ standingPausedRenewalMessage(reason === "renewal_idle_suspended")
26842
26979
  );
26843
26980
  }
26844
26981
  if (reason === "renewal_horizon_reached") {
@@ -26900,7 +27037,7 @@ async function requestSuccessor(options) {
26900
27037
  }
26901
27038
  const tokenId = typeof body.token_id === "string" ? body.token_id : "";
26902
27039
  const runId = typeof body.run_id === "string" ? body.run_id : "";
26903
- if (!UUID_RE5.test(tokenId) || !UUID_RE5.test(runId) || principalId === null) {
27040
+ if (!UUID_RE6.test(tokenId) || !UUID_RE6.test(runId) || principalId === null) {
26904
27041
  throw new RenewalRefused(
26905
27042
  response.status,
26906
27043
  "incomplete_successor",
@@ -27409,7 +27546,7 @@ var MAX_LINK_PAYLOAD_BYTES = 8 * 1024;
27409
27546
  var MAX_LABEL_INPUT_LENGTH = 1024;
27410
27547
  var CONTROL_GLOBAL_RE = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;
27411
27548
  var ANSI_ESCAPE_GLOBAL_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
27412
- var UUID_RE6 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
27549
+ var UUID_RE7 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
27413
27550
  var STRICT_BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
27414
27551
  var RAW_BASE64_PAYLOAD_CANDIDATE_RE = /^[A-Za-z0-9+/_=-]+$/;
27415
27552
  var CURRENT_INVITE_SCHEME = "cswarm://accept/";
@@ -27460,7 +27597,7 @@ function validatedPayload(value) {
27460
27597
  throw new Error("invite link target is malformed");
27461
27598
  }
27462
27599
  cloudTarget(value.url, value.anon_key);
27463
- if (typeof value.workspace_id !== "string" || !UUID_RE6.test(value.workspace_id)) {
27600
+ if (typeof value.workspace_id !== "string" || !UUID_RE7.test(value.workspace_id)) {
27464
27601
  throw new Error("invite link workspace_id must be a UUID");
27465
27602
  }
27466
27603
  if (typeof value.invitation_token !== "string") {
@@ -27470,7 +27607,7 @@ function validatedPayload(value) {
27470
27607
  if (typeof value.workspace_name !== "string" || typeof value.inviter_display_name !== "string" || value.workspace_name.length > MAX_LABEL_INPUT_LENGTH || value.inviter_display_name.length > MAX_LABEL_INPUT_LENGTH) {
27471
27608
  throw new Error("invite link display labels are malformed");
27472
27609
  }
27473
- if (value.inviter_user_id !== void 0 && (typeof value.inviter_user_id !== "string" || !UUID_RE6.test(value.inviter_user_id))) {
27610
+ if (value.inviter_user_id !== void 0 && (typeof value.inviter_user_id !== "string" || !UUID_RE7.test(value.inviter_user_id))) {
27474
27611
  throw new Error("invite link inviter_user_id must be a UUID");
27475
27612
  }
27476
27613
  return value;
@@ -27670,7 +27807,7 @@ function acceptedResponse(result) {
27670
27807
  }
27671
27808
  return result.response;
27672
27809
  }
27673
- function uuid2(value, field) {
27810
+ function uuid3(value, field) {
27674
27811
  if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
27675
27812
  throw new Error(`server returned a malformed ${field}`);
27676
27813
  }
@@ -28014,7 +28151,7 @@ function cloudAcceptOperations(target2, store2, fetcher = fetch) {
28014
28151
  );
28015
28152
  return {
28016
28153
  status: "accepted",
28017
- workspaceId: uuid2(response.workspace_id, "workspace_id")
28154
+ workspaceId: uuid3(response.workspace_id, "workspace_id")
28018
28155
  };
28019
28156
  } catch (error) {
28020
28157
  if (error instanceof CommandHttpError && error.status === 403) {
@@ -28033,7 +28170,7 @@ function cloudAcceptOperations(target2, store2, fetcher = fetch) {
28033
28170
  if (result.response.status === "accepted") {
28034
28171
  return {
28035
28172
  status: "accepted",
28036
- principalId: uuid2(result.response.principal_id, "principal_id")
28173
+ principalId: uuid3(result.response.principal_id, "principal_id")
28037
28174
  };
28038
28175
  }
28039
28176
  if (String(result.response.reason) === "principal_name_taken") {
@@ -28127,113 +28264,6 @@ function renderCapabilityRevoke(capabilityId, revokedAt) {
28127
28264
  return `Capability link ${capabilityId} was revoked at ${revokedAt}. Anyone who still holds it now gets the same answer as someone holding a link that never existed. Links you have not revoked are unaffected.`;
28128
28265
  }
28129
28266
 
28130
- // src/cloud/renewal-grants.ts
28131
- var UUID_RE7 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
28132
- function nullableString(value, field) {
28133
- if (value === null) return null;
28134
- if (typeof value !== "string") {
28135
- throw new Error(`renewal grant read returned malformed ${field}`);
28136
- }
28137
- return value;
28138
- }
28139
- function nullableTimestamp(value, field) {
28140
- const text = nullableString(value, field);
28141
- if (text !== null && !Number.isFinite(Date.parse(text))) {
28142
- throw new Error(`renewal grant read returned malformed ${field}`);
28143
- }
28144
- return text;
28145
- }
28146
- function uuid3(value, field) {
28147
- if (typeof value !== "string" || !UUID_RE7.test(value)) {
28148
- throw new Error(`renewal grant read returned malformed ${field}`);
28149
- }
28150
- return value.toLowerCase();
28151
- }
28152
- function nullableUuid(value, field) {
28153
- return value === null ? null : uuid3(value, field);
28154
- }
28155
- function parseGrant(value) {
28156
- if (!value || typeof value !== "object" || Array.isArray(value)) {
28157
- throw new Error("renewal grant read returned a malformed row");
28158
- }
28159
- const row = value;
28160
- if (row.kind !== "timeboxed" && row.kind !== "standing") {
28161
- throw new Error("renewal grant read returned malformed kind");
28162
- }
28163
- const horizon = nullableTimestamp(
28164
- row.horizon_expires_at,
28165
- "horizon_expires_at"
28166
- );
28167
- if (row.kind === "standing" && horizon !== null || row.kind === "timeboxed" && horizon === null) {
28168
- throw new Error("renewal grant read returned an invalid kind/horizon pair");
28169
- }
28170
- return {
28171
- renewal_grant_id: uuid3(row.renewal_grant_id, "renewal_grant_id"),
28172
- principal_id: uuid3(row.principal_id, "principal_id"),
28173
- kind: row.kind,
28174
- horizon_expires_at: horizon,
28175
- bound_device_id: nullableUuid(row.bound_device_id, "bound_device_id"),
28176
- last_used_at: nullableTimestamp(row.last_used_at, "last_used_at"),
28177
- last_used_device_id: nullableUuid(
28178
- row.last_used_device_id,
28179
- "last_used_device_id"
28180
- ),
28181
- last_used_from: nullableString(row.last_used_from, "last_used_from"),
28182
- new_host_at: nullableTimestamp(row.new_host_at, "new_host_at"),
28183
- suspended_at: nullableTimestamp(row.suspended_at, "suspended_at"),
28184
- revoked_at: nullableTimestamp(row.revoked_at, "revoked_at"),
28185
- token_id: nullableUuid(row.token_id, "token_id"),
28186
- issued_at: nullableTimestamp(row.issued_at, "issued_at"),
28187
- token_expires_at: nullableTimestamp(
28188
- row.token_expires_at,
28189
- "token_expires_at"
28190
- ),
28191
- token_revoked_at: nullableTimestamp(
28192
- row.token_revoked_at,
28193
- "token_revoked_at"
28194
- )
28195
- };
28196
- }
28197
- async function readRenewalGrants(target2, credential, workspaceId2, fetcher = fetch) {
28198
- const response = await fetcher(readEndpoint(target2), {
28199
- method: "POST",
28200
- headers: {
28201
- authorization: `Bearer ${credential}`,
28202
- apikey: target2.anonKey,
28203
- "content-type": "application/json"
28204
- },
28205
- body: JSON.stringify({
28206
- resource: "renewal_grants",
28207
- workspace_id: workspaceId2
28208
- }),
28209
- signal: AbortSignal.timeout(15e3)
28210
- });
28211
- if (!response.ok) {
28212
- throw new Error(`renewal grant read failed (HTTP ${response.status})`);
28213
- }
28214
- const body = await response.json().catch(() => null);
28215
- if (!body || typeof body !== "object" || Array.isArray(body)) {
28216
- throw new Error("renewal grant read returned malformed JSON");
28217
- }
28218
- const grants = body.grants;
28219
- if (!Array.isArray(grants)) {
28220
- throw new Error("renewal grant read returned no grants array");
28221
- }
28222
- return grants.map(parseGrant);
28223
- }
28224
- function describeRenewalGrant(grant) {
28225
- const lines = grant.kind === "standing" ? ["Grant: standing \u2014 does not expire; revoke is the only kill switch."] : [`Grant: timeboxed \u2014 renewal horizon ${grant.horizon_expires_at}.`];
28226
- if (grant.suspended_at !== null) {
28227
- lines.push(
28228
- `SUSPENDED since ${grant.suspended_at}. Next step: ask a workspace owner to revoke this grant and mint a new credential.`
28229
- );
28230
- }
28231
- if (grant.revoked_at !== null) {
28232
- lines.push(`REVOKED since ${grant.revoked_at}. Next step: mint a new grant if this agent should continue.`);
28233
- }
28234
- return lines;
28235
- }
28236
-
28237
28267
  // src/cloud/workspaces.ts
28238
28268
  var UUID_RE8 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
28239
28269
  var ROLES = /* @__PURE__ */ new Set(["owner", "admin", "member"]);
@@ -31102,7 +31132,7 @@ var DELIVERY_HANDLED_OUTCOMES = new Set(
31102
31132
  var DELIVERY_PROVIDER_PROVEN_OUTCOMES = new Set(
31103
31133
  [...DELIVERY_ACK_OUTCOMES].filter((outcome) => outcome === "replied")
31104
31134
  );
31105
- function orList(values2) {
31135
+ function orList2(values2) {
31106
31136
  return values2.length <= 1 ? values2.join("") : `${values2.slice(0, -1).join(", ")}, or ${values2[values2.length - 1]}`;
31107
31137
  }
31108
31138
  var DELIVERY_REQUEST_TIMEOUT_MS = 3e4;
@@ -31448,13 +31478,13 @@ function assertAckRequest(request) {
31448
31478
  checkedUuidRequest(request.listenerInstanceId, "listenerInstanceId");
31449
31479
  if (!DELIVERY_ACK_OUTCOMES.has(request.outcome)) {
31450
31480
  throw new Error(
31451
- `a delivery outcome must be ${orList([...DELIVERY_ACK_OUTCOMES])}`
31481
+ `a delivery outcome must be ${orList2([...DELIVERY_ACK_OUTCOMES])}`
31452
31482
  );
31453
31483
  }
31454
31484
  if (request.outcome === "failed_terminal") {
31455
31485
  if (typeof request.lastErrorCode !== "string" || !FAILED_TERMINAL_CODES_SET.has(request.lastErrorCode)) {
31456
31486
  throw new Error(
31457
- `a failed_terminal acknowledgement requires one of ${orList([...FAILED_TERMINAL_CODES_SET])}`
31487
+ `a failed_terminal acknowledgement requires one of ${orList2([...FAILED_TERMINAL_CODES_SET])}`
31458
31488
  );
31459
31489
  }
31460
31490
  } else if (request.lastErrorCode !== null) {
@@ -41622,6 +41652,23 @@ var ListenerHttpClient = class {
41622
41652
 
41623
41653
  // src/resume.ts
41624
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
+ };
41625
41672
  function execFileText(file, args) {
41626
41673
  return new Promise((resolve3, reject) => {
41627
41674
  (0, import_node_child_process8.execFile)(file, [...args], {
@@ -41633,15 +41680,76 @@ function execFileText(file, args) {
41633
41680
  });
41634
41681
  });
41635
41682
  }
41636
- 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);
41637
41694
  return {
41638
- async list() {
41639
- const output = await execFileText("ps", ["-axo", "pid=,command="]);
41640
- return output.split("\n").flatMap((line) => {
41641
- const match = /^\s*(\d+)\s+(.*)$/.exec(line);
41642
- if (!match) return [];
41643
- const pid = Number(match[1]);
41644
- 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
+ });
41645
41753
  });
41646
41754
  }
41647
41755
  };
@@ -41691,7 +41799,10 @@ function isNotifyCommand(command2) {
41691
41799
  return /(?:^|\s)inbox(?:\s|$)/.test(command2) && /(?:^|\s)--notify(?:\s|$)/.test(command2);
41692
41800
  }
41693
41801
  async function findNotifyWatchers(options) {
41694
- const processTable = options.processTable ?? systemProcessTable();
41802
+ const processTable = options.processTable ?? systemProcessTable({
41803
+ retain: isNotifyCommand,
41804
+ ...options.processTableCommand ? { command: options.processTableCommand } : {}
41805
+ });
41695
41806
  const stdoutConsumer = options.stdoutConsumer ?? lsofStdoutConsumer();
41696
41807
  const rows3 = await processTable.list();
41697
41808
  const matches = rows3.flatMap((row) => {
@@ -41959,6 +42070,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
41959
42070
  "grok-executable",
41960
42071
  "head-sha",
41961
42072
  "help",
42073
+ "if-version",
41962
42074
  "include-stale",
41963
42075
  "include-tombstoned",
41964
42076
  "invitation-id",
@@ -41978,6 +42090,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
41978
42090
  "permissions",
41979
42091
  "principal-id",
41980
42092
  "provider",
42093
+ "renewal-grant-id",
41981
42094
  "repo",
41982
42095
  "reveal-anon-key",
41983
42096
  "route",
@@ -42029,8 +42142,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
42029
42142
  ]);
42030
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;
42031
42144
  function packageVersion() {
42032
- if ("0.1.51".length > 0) {
42033
- return "0.1.51";
42145
+ if ("0.1.53".length > 0) {
42146
+ return "0.1.53";
42034
42147
  }
42035
42148
  try {
42036
42149
  const value = JSON.parse(
@@ -42165,7 +42278,7 @@ Usage:
42165
42278
  cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42166
42279
  cswarm brain ls [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42167
42280
  cswarm brain get <topic>[@<version>] [--version <n>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42168
- 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>
42169
42282
  cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42170
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]
42171
42284
  cswarm listen canary ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--state-dir <path>] [--wait <seconds>] [--json]
@@ -42191,6 +42304,7 @@ Usage:
42191
42304
  cswarm token mint [--url <url> --anon-key <key>] [--workspace-id <uuid>] --principal-id <uuid> --run-id <uuid> --task-id <uuid> --epoch <n> [--ttl-ms <ms>] [--renewal-horizon-days <1..90> | --standing --confirm-standing]
42192
42305
  cswarm token revoke [--url <url> --anon-key <key>] [--workspace-id <uuid>] --token-id <uuid>
42193
42306
  cswarm token revoke ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--token-id <uuid>]
42307
+ cswarm grant resume [--url <url> --anon-key <key>] [--workspace-id <uuid>] --renewal-grant-id <uuid> [--json] # lifts an idle pause; a REVOKED grant is refused
42194
42308
  cswarm link new [--url <url> --anon-key <key>] [--workspace-id <uuid>] --task-id <uuid> [--ttl-ms <ms>] [--site <origin>] [--json]
42195
42309
  cswarm link revoke [--url <url> --anon-key <key>] [--workspace-id <uuid>] --capability-id <uuid> [--json]
42196
42310
  cswarm command <kind> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [command fields]
@@ -43333,6 +43447,43 @@ async function runToken(args) {
43333
43447
  expiresAt
43334
43448
  }));
43335
43449
  }
43450
+ async function runGrant(args) {
43451
+ const action = args.positionals[1];
43452
+ args.assertShape(
43453
+ [
43454
+ ...TARGET_FLAGS,
43455
+ "workspace-id",
43456
+ "renewal-grant-id",
43457
+ /* `--json` accepted, no effect — see the note on `runInvite`. D-064. */
43458
+ "json"
43459
+ ],
43460
+ 2
43461
+ );
43462
+ if (action !== "resume") {
43463
+ throw new UsageError(`unknown grant command: ${action ?? "(missing)"}`);
43464
+ }
43465
+ const renewalGrantId = args.required("renewal-grant-id");
43466
+ const cloud = await target(args);
43467
+ const human = await humanCredential(args, cloud);
43468
+ const workspace = await workspaceId(args, cloud, human);
43469
+ const response = acceptedConnect(
43470
+ "grant resume",
43471
+ await sendConnectWithPending(
43472
+ new ThinCommandClient(cloud),
43473
+ human,
43474
+ workspace,
43475
+ { kind: "resume_renewal_grant", renewal_grant_id: renewalGrantId }
43476
+ )
43477
+ );
43478
+ const resumedAt = response.resumed_at ?? null;
43479
+ process.stdout.write(
43480
+ `Grant resumed${resumedAt === null ? "" : ` at ${resumedAt}`}.
43481
+ Renewal is allowed again. Nothing has reached the agent yet: it starts renewing when its own cswarm process next tries, so start that process if it is not running.
43482
+ The idle clock restarts now \u2014 another ${STANDING_IDLE_PAUSE_DAYS} days with no use pauses it again.
43483
+ Confirm with: cswarm whoami --agent-token-file <path>
43484
+ `
43485
+ );
43486
+ }
43336
43487
  async function runTokenRevoke(args) {
43337
43488
  if (hasAgentCredential(args)) {
43338
43489
  args.assertShape(
@@ -46731,7 +46882,7 @@ async function resolveFileSelector(context, selector) {
46731
46882
  }
46732
46883
  return match.file_id;
46733
46884
  }
46734
- async function uploadNamedFile(context, name, bytes) {
46885
+ async function uploadNamedFile(context, name, bytes, options = {}) {
46735
46886
  if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
46736
46887
  throw new Error(
46737
46888
  `this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
@@ -46758,7 +46909,8 @@ async function uploadNamedFile(context, name, bytes) {
46758
46909
  versionId,
46759
46910
  name,
46760
46911
  declaredSizeBytes: bytes.byteLength,
46761
- contentType
46912
+ contentType,
46913
+ ...options.ifVersion === void 0 ? {} : { ifVersion: options.ifVersion }
46762
46914
  })
46763
46915
  );
46764
46916
  await onceRetried(
@@ -47052,7 +47204,8 @@ async function runBrainPut(args) {
47052
47204
  "cswarm brain put cannot read both the credential and Markdown from stdin; use --agent-token-file or pass a Markdown path"
47053
47205
  );
47054
47206
  }
47055
- 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 });
47056
47209
  let bytes;
47057
47210
  if (localPath) {
47058
47211
  try {
@@ -47067,7 +47220,19 @@ async function runBrainPut(args) {
47067
47220
  bytes = await readBrainMarkdownFromStdin();
47068
47221
  }
47069
47222
  decodeBrainMarkdown(bytes);
47070
- 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
+ });
47071
47236
  if (args.has("json")) {
47072
47237
  process.stdout.write(`${JSON.stringify({ topic, ...committed }, null, 2)}
47073
47238
  `);
@@ -47463,6 +47628,10 @@ async function main() {
47463
47628
  await runToken(args);
47464
47629
  return;
47465
47630
  }
47631
+ if (verb === "grant") {
47632
+ await runGrant(args);
47633
+ return;
47634
+ }
47466
47635
  if (verb === "link") {
47467
47636
  await runLink(args);
47468
47637
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.51",
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"