commonswarm 0.1.30 → 0.1.31

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 +69 -16
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -37864,7 +37864,17 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
37864
37864
  instanceDirectory
37865
37865
  );
37866
37866
  const statusIsLive = storedStatus === null ? null : await isListenerLive(storedStatus);
37867
- if (statusIsLive === false) continue;
37867
+ if (statusIsLive === false) {
37868
+ contexts.push({
37869
+ instanceDirectory,
37870
+ paths: storedStatus.paths,
37871
+ status: storedStatus.status,
37872
+ listenerLive: false,
37873
+ credential: null,
37874
+ credentialReadFailed: false
37875
+ });
37876
+ continue;
37877
+ }
37868
37878
  await deleteSecureJsonFile(
37869
37879
  (0, import_node_path18.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
37870
37880
  ).catch(() => void 0);
@@ -37874,6 +37884,7 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
37874
37884
  instanceDirectory,
37875
37885
  paths: storedStatus?.paths ?? null,
37876
37886
  status: storedStatus?.status ?? null,
37887
+ listenerLive: statusIsLive,
37877
37888
  credential,
37878
37889
  credentialReadFailed: credential === null && storedStatus !== null
37879
37890
  });
@@ -37883,6 +37894,7 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
37883
37894
  instanceDirectory,
37884
37895
  paths: storedStatus.paths,
37885
37896
  status: storedStatus.status,
37897
+ listenerLive: statusIsLive,
37886
37898
  credential: null,
37887
37899
  credentialReadFailed: true
37888
37900
  });
@@ -37923,6 +37935,24 @@ function renderHookSignal(item) {
37923
37935
  `${replyLabel} cswarm reply ${item.signalId} "<answer>" --workspace-id ${item.workspaceId}`
37924
37936
  ].join("\n");
37925
37937
  }
37938
+ function listenerRestartCommand(status) {
37939
+ const routeMode = status.routeMode ?? "worker";
37940
+ return [
37941
+ "cswarm listen start",
37942
+ "--agent-token-stdin",
37943
+ `--workspace-id ${status.workspaceId}`,
37944
+ `--provider ${status.provider}`,
37945
+ ...status.permissionMode ? [`--permissions ${status.permissionMode}`] : [],
37946
+ `--route ${routeMode}`,
37947
+ ...routeMode === "split" && status.deferOverChars !== null && status.deferOverChars !== void 0 ? [`--defer-over ${status.deferOverChars}`] : []
37948
+ ].join(" ");
37949
+ }
37950
+ function renderStrandedQueue(context, count2) {
37951
+ const status = context.status;
37952
+ const noun = count2 === 1 ? "message" : "messages";
37953
+ const verb = count2 === 1 ? "was" : "were";
37954
+ return `[CommonSwarm] ${count2} ${noun} ${verb} waiting for agent ${status.principalId} in listener ${status.instanceId}, but that listener is no longer running. Restart it by piping the same agent credential into: ` + listenerRestartCommand(status);
37955
+ }
37926
37956
  async function inboxItems(context, options) {
37927
37957
  const stored = context.credential;
37928
37958
  const target2 = cloudTarget(stored.targetUrl, stored.anonKey);
@@ -37982,11 +38012,11 @@ async function checkListenerHooks(options) {
37982
38012
  options.isListenerLive ?? listenerIsLive
37983
38013
  );
37984
38014
  if (contexts.length === 0) return "";
37985
- const networkAllowed = await reserveCheck(
38015
+ const networkAllowed = contexts.some((context) => context.listenerLive !== false) ? await reserveCheck(
37986
38016
  stateDirectory2,
37987
38017
  cooldownSeconds * 1e3,
37988
38018
  now()
37989
- );
38019
+ ) : false;
37990
38020
  const checks = await Promise.all(contexts.map(async (context) => {
37991
38021
  const queue = new FilePendingMainQueue(context.instanceDirectory);
37992
38022
  const pending = await queue.read();
@@ -38011,7 +38041,7 @@ async function checkListenerHooks(options) {
38011
38041
  context,
38012
38042
  queue,
38013
38043
  pending,
38014
- droppedCount: stats.droppedCount,
38044
+ droppedCount: context.listenerLive === false ? 0 : stats.droppedCount,
38015
38045
  network,
38016
38046
  credentialFailure,
38017
38047
  credentialHealthy
@@ -38027,6 +38057,11 @@ async function checkListenerHooks(options) {
38027
38057
  check.droppedCount
38028
38058
  );
38029
38059
  blocks.push(...staged.unseen.map(renderHookSignal));
38060
+ const pendingSignalIds = new Set(check.pending.map((entry) => entry.signalId));
38061
+ const unseenPending = staged.unseen.filter((item) => pendingSignalIds.has(item.signalId));
38062
+ if (check.context.listenerLive === false && unseenPending.length > 0) {
38063
+ blocks.push(renderStrandedQueue(check.context, unseenPending.length));
38064
+ }
38030
38065
  const reportDrops = staged.droppedSinceLastCheck > 0;
38031
38066
  if (reportDrops) blocks.push(renderDroppedAsks(staged.droppedSinceLastCheck));
38032
38067
  const reportCredentialFailure = check.credentialFailure !== null && !staged.credentialFailureReported;
@@ -38037,12 +38072,14 @@ async function checkListenerHooks(options) {
38037
38072
  blocks.push(warning);
38038
38073
  }
38039
38074
  }
38040
- const pendingSignalIds = new Set(check.pending.map((entry) => entry.signalId));
38041
38075
  commits.push({
38042
38076
  check,
38043
38077
  store: store2,
38044
38078
  signalIds: staged.unseen.map((item) => item.signalId),
38045
- printedPendingSignalIds: staged.unseen.map((item) => item.signalId).filter((signalId) => pendingSignalIds.has(signalId)),
38079
+ // Every staged queue entry is either written by this run or was committed
38080
+ // after an earlier successful write. Removing both keeps the queue bounded
38081
+ // without re-printing entries below the exactly-once high-water.
38082
+ settledPendingSignalIds: check.pending.map((item) => item.signalId),
38046
38083
  reportDrops,
38047
38084
  reportCredentialFailure
38048
38085
  });
@@ -38056,7 +38093,7 @@ async function checkListenerHooks(options) {
38056
38093
  ...commit.reportCredentialFailure ? { credentialFailureReported: true } : commit.check.credentialHealthy ? { credentialFailureReported: false } : {}
38057
38094
  });
38058
38095
  const remainingCount = await commit.check.queue.remove(
38059
- new Set(commit.printedPendingSignalIds),
38096
+ new Set(commit.settledPendingSignalIds),
38060
38097
  HOOK_LOCK_TIMEOUT_MS
38061
38098
  );
38062
38099
  if (commit.check.context.paths !== null && commit.check.context.status !== null) {
@@ -38189,8 +38226,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
38189
38226
  AGENT_CREDENTIAL_MESSAGE_D088
38190
38227
  ];
38191
38228
  function packageVersion() {
38192
- if ("0.1.30".length > 0) {
38193
- return "0.1.30";
38229
+ if ("0.1.31".length > 0) {
38230
+ return "0.1.31";
38194
38231
  }
38195
38232
  try {
38196
38233
  const value = JSON.parse(
@@ -40741,7 +40778,7 @@ function renderListenerStatus(status) {
40741
40778
  }
40742
40779
  if (pendingForMainCount > 0) {
40743
40780
  lines.push(
40744
- `${pendingForMainCount} asks waiting for this session; they surface at your next prompt, or run cswarm hook check.`
40781
+ status.state === "stopped" || status.state === "failed" ? `${pendingForMainCount} asks are stranded because this listener is not running. Restart it by piping the same agent credential into: ${listenerRestartCommand(status)}` : `${pendingForMainCount} asks waiting for this session; they surface at your next prompt, or run cswarm hook check.`
40745
40782
  );
40746
40783
  }
40747
40784
  }
@@ -40757,6 +40794,20 @@ function renderListenerStatus(status) {
40757
40794
  }
40758
40795
  return lines.join("\n");
40759
40796
  }
40797
+ async function unsurfacedPendingMainStats(instanceDirectory, fallback) {
40798
+ try {
40799
+ const queue = new FilePendingMainQueue(instanceDirectory);
40800
+ const pending = await queue.read();
40801
+ const stats = await queue.stats();
40802
+ const staged = await new FileHookSurfaceStore(instanceDirectory).stage(
40803
+ pending,
40804
+ stats.droppedCount
40805
+ );
40806
+ return { count: staged.unseen.length, droppedCount: stats.droppedCount };
40807
+ } catch {
40808
+ return fallback;
40809
+ }
40810
+ }
40760
40811
  function listenerFailureMessage(code, provider) {
40761
40812
  if (code === "version_below_floor") {
40762
40813
  if (provider === "codex") {
@@ -41231,9 +41282,10 @@ async function runListenStart(args) {
41231
41282
  if ((status.routeMode ?? "worker") !== "worker") {
41232
41283
  const recordedPending = status.pendingForMainCount ?? 0;
41233
41284
  const recordedDropped = status.droppedForMainCount ?? 0;
41234
- const queueStats = await new FilePendingMainQueue(
41235
- paths.instanceDirectory
41236
- ).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
41285
+ const queueStats = await unsurfacedPendingMainStats(
41286
+ paths.instanceDirectory,
41287
+ { count: recordedPending, droppedCount: recordedDropped }
41288
+ );
41237
41289
  status = {
41238
41290
  ...status,
41239
41291
  pendingForMainCount: queueStats.count,
@@ -41348,9 +41400,10 @@ async function runListenStatusOrStop(args, command2) {
41348
41400
  if ((status.routeMode ?? "worker") !== "worker") {
41349
41401
  const recordedPending = status.pendingForMainCount ?? 0;
41350
41402
  const recordedDropped = status.droppedForMainCount ?? 0;
41351
- const queueStats = await new FilePendingMainQueue(
41352
- paths.instanceDirectory
41353
- ).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
41403
+ const queueStats = await unsurfacedPendingMainStats(
41404
+ paths.instanceDirectory,
41405
+ { count: recordedPending, droppedCount: recordedDropped }
41406
+ );
41354
41407
  status = {
41355
41408
  ...status,
41356
41409
  pendingForMainCount: queueStats.count,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
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"