commonswarm 0.1.33 → 0.1.34

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 +132 -112
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -33068,7 +33068,7 @@ function parseV2Record(row) {
33068
33068
  throw new Error("stored listener effect is malformed");
33069
33069
  }
33070
33070
  if (signalKind2 === "note") {
33071
- if (typeof row.commandId !== "string" || row.commandId !== "" || row.state !== "observed" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
33071
+ if (typeof row.commandId !== "string" || row.commandId !== "" || row.state !== "observed" && row.state !== "routed_main" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
33072
33072
  throw new Error("stored listener effect is malformed");
33073
33073
  }
33074
33074
  } else if (row.state === "routed_main") {
@@ -33137,11 +33137,11 @@ function newObservedNoteRecord(input) {
33137
33137
  updatedAt: input.updatedAt
33138
33138
  };
33139
33139
  }
33140
- function newRoutedMainAskRecord(input) {
33140
+ function newRoutedMainRecord(input) {
33141
33141
  const base = newObservedNoteRecord(input);
33142
33142
  return {
33143
33143
  ...base,
33144
- signalKind: "ask",
33144
+ signalKind: input.signalKind,
33145
33145
  state: "routed_main"
33146
33146
  };
33147
33147
  }
@@ -35076,8 +35076,8 @@ var FilePendingMainQueue = class {
35076
35076
  }
35077
35077
  };
35078
35078
  function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
35079
- if (signal.kind !== "ask") {
35080
- throw new Error("only directed asks can enter the pending-for-main queue");
35079
+ if (signal.kind !== "ask" && signal.kind !== "note") {
35080
+ throw new Error("only directed asks and notes can enter the pending-for-main queue");
35081
35081
  }
35082
35082
  return parseEntry({
35083
35083
  signalId: signal.id,
@@ -35085,6 +35085,7 @@ function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
35085
35085
  principalId,
35086
35086
  fromId: signal.from,
35087
35087
  fromKind: signal.from_kind,
35088
+ kind: signal.kind,
35088
35089
  senderName: provenance.senderName,
35089
35090
  body: signal.body,
35090
35091
  createdAt: signal.created_at,
@@ -35182,7 +35183,7 @@ function ackForTerminalEffect(record, now) {
35182
35183
  if (record.state === "observed" && record.signalKind === "note") {
35183
35184
  return { outcome: "observed", lastErrorCode: null };
35184
35185
  }
35185
- if (record.state === "routed_main" && record.signalKind === "ask") {
35186
+ if (record.state === "routed_main") {
35186
35187
  return { outcome: "queued", lastErrorCode: null };
35187
35188
  }
35188
35189
  if (record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now()) {
@@ -35204,7 +35205,7 @@ function ackForTerminalEffect(record, now) {
35204
35205
  return { outcome: "failed_terminal", lastErrorCode: "local_effect_failed" };
35205
35206
  }
35206
35207
  function isAckableTerminalEffect(record, now) {
35207
- return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "routed_main" && record.signalKind === "ask" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
35208
+ return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "routed_main" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
35208
35209
  }
35209
35210
  function effectPhaseBudget(record) {
35210
35211
  if (record === null || record.state === "received" || record.state === "prompting") {
@@ -35215,6 +35216,9 @@ function effectPhaseBudget(record) {
35215
35216
  }
35216
35217
  return LISTENER_ACK_ONLY_MINIMUM_MS;
35217
35218
  }
35219
+ function observedNoteNeedsMainRoute(record, routeMode, deferOverChars) {
35220
+ return record?.state === "observed" && record.signalKind === "note" && decideListenerRoute(routeMode, deferOverChars, record.askBody.length) === "main";
35221
+ }
35218
35222
  function verifyPreparedAckEffect(record, active, now) {
35219
35223
  if (record === null || record.signalId !== active.signalId || active.ack === null) {
35220
35224
  throw new Error("prepared delivery ACK has no matching terminal effect");
@@ -35450,7 +35454,11 @@ async function runListenerRuntime(options) {
35450
35454
  ...options.resolveSenderProvenance === void 0 ? {} : { resolveSenderProvenance: options.resolveSenderProvenance },
35451
35455
  isCredentialFailure: isCredentialLoss
35452
35456
  });
35453
- const routeAskToMain = async (signal) => {
35457
+ const routeSignalToMain = async (signal) => {
35458
+ if (signal.kind !== "ask" && signal.kind !== "note") {
35459
+ throw new Error("only directed asks and notes can route to the main session");
35460
+ }
35461
+ const signalKind2 = signal.kind;
35454
35462
  let provenance = {
35455
35463
  senderName: null,
35456
35464
  operatorId: null,
@@ -35480,13 +35488,19 @@ async function runListenerRuntime(options) {
35480
35488
  });
35481
35489
  const existing = await options.store.read(signal.id);
35482
35490
  if (existing !== null) {
35483
- if (!sameEffectSignal(existing, signal) || existing.state !== "routed_main") {
35484
- throw new Error("stored listener effect does not match the main-routed ask");
35491
+ if (!sameEffectSignal(existing, signal)) {
35492
+ throw new Error("stored listener effect does not match the main-routed message");
35493
+ }
35494
+ if (existing.state === "routed_main") {
35495
+ return existing;
35496
+ }
35497
+ if (!(existing.signalKind === "note" && existing.state === "observed")) {
35498
+ throw new Error("stored listener effect does not match the main-routed message");
35485
35499
  }
35486
- return existing;
35487
35500
  }
35488
- await options.store.write(newRoutedMainAskRecord({
35501
+ await options.store.write(newRoutedMainRecord({
35489
35502
  signalId: signal.id,
35503
+ signalKind: signalKind2,
35490
35504
  body: signal.body,
35491
35505
  until: signal.until,
35492
35506
  senderOwnerRelation: signal.sender_owner_relation ?? "unknown",
@@ -35494,7 +35508,7 @@ async function runListenerRuntime(options) {
35494
35508
  }));
35495
35509
  const persisted = await options.store.read(signal.id);
35496
35510
  if (persisted === null || !sameEffectSignal(persisted, signal) || persisted.state !== "routed_main") {
35497
- throw new Error("main-routed listener effect could not be verified");
35511
+ throw new Error("main-routed message effect could not be verified");
35498
35512
  }
35499
35513
  return persisted;
35500
35514
  };
@@ -35716,7 +35730,20 @@ async function runListenerRuntime(options) {
35716
35730
  const recovery = currentJournalRecord?.active ?? null;
35717
35731
  if (recovery?.phase === "ack_pending") {
35718
35732
  const horizon = Date.parse(recovery.leasedUntil) + LISTENER_DELIVERY_SAFETY_MARGIN_MS;
35719
- if (page.capabilities.deliveryAck && now() < horizon) {
35733
+ let preparedNeedsMainRoute = false;
35734
+ if (recovery.signalId !== null) {
35735
+ try {
35736
+ preparedNeedsMainRoute = observedNoteNeedsMainRoute(
35737
+ await options.store.read(recovery.signalId),
35738
+ routeMode,
35739
+ deferOverChars
35740
+ );
35741
+ } catch (error) {
35742
+ stop = { reason: "fatal", error: asError2(error) };
35743
+ break;
35744
+ }
35745
+ }
35746
+ if (page.capabilities.deliveryAck && now() < horizon && !preparedNeedsMainRoute) {
35720
35747
  const ackStop = await sendPreparedAck(recovery);
35721
35748
  if (ackStop !== null) {
35722
35749
  stop = ackStop;
@@ -35755,7 +35782,7 @@ async function runListenerRuntime(options) {
35755
35782
  terminal = null;
35756
35783
  }
35757
35784
  }
35758
- if (terminal !== null && sameRecoveredEffect(recovery, terminal) && isAckableTerminalEffect(terminal, now)) {
35785
+ if (terminal !== null && sameRecoveredEffect(recovery, terminal) && !observedNoteNeedsMainRoute(terminal, routeMode, deferOverChars) && isAckableTerminalEffect(terminal, now)) {
35759
35786
  try {
35760
35787
  const mapped = ackForTerminalEffect(terminal, now);
35761
35788
  await options.deliveryJournal.prepareAck({
@@ -35949,7 +35976,33 @@ async function runListenerRuntime(options) {
35949
35976
  if (existing !== null && !sameEffectSignal(existing, signal)) {
35950
35977
  throw new Error("stored listener effect does not match the authoritative delivery");
35951
35978
  }
35952
- if (signal.kind === "note") {
35979
+ if (signal.kind !== "ask" && signal.kind !== "note") {
35980
+ throw new Error("claimed delivery has an unsupported signal kind");
35981
+ }
35982
+ const decision = decideListenerRoute(
35983
+ routeMode,
35984
+ deferOverChars,
35985
+ signal.body.length
35986
+ );
35987
+ options.onEvent?.({
35988
+ type: "routing_decision",
35989
+ signalId: signal.id,
35990
+ routeMode,
35991
+ decision,
35992
+ threshold: deferOverChars,
35993
+ bodyLength: signal.body.length,
35994
+ ts: eventTime(now)
35995
+ });
35996
+ if (decision === "main") {
35997
+ terminal = await routeSignalToMain(signal);
35998
+ options.onEvent?.({
35999
+ type: "effect",
36000
+ signalId: signal.id,
36001
+ status: "routed_main",
36002
+ failureCode: null,
36003
+ ts: eventTime(now)
36004
+ });
36005
+ } else if (signal.kind === "note") {
35953
36006
  if (existing === null) {
35954
36007
  await options.store.write(newObservedNoteRecord({
35955
36008
  signalId: signal.id,
@@ -35970,85 +36023,58 @@ async function runListenerRuntime(options) {
35970
36023
  failureCode: null,
35971
36024
  ts: eventTime(now)
35972
36025
  });
35973
- } else if (signal.kind === "ask") {
35974
- const decision = decideListenerRoute(
35975
- routeMode,
35976
- deferOverChars,
35977
- signal.body.length
35978
- );
35979
- options.onEvent?.({
35980
- type: "routing_decision",
35981
- signalId: signal.id,
35982
- routeMode,
35983
- decision,
35984
- threshold: deferOverChars,
35985
- bodyLength: signal.body.length,
35986
- ts: eventTime(now)
35987
- });
35988
- if (decision === "main") {
35989
- terminal = await routeAskToMain(signal);
36026
+ } else {
36027
+ let processAttempt = 0;
36028
+ while (terminal === null) {
36029
+ const before = await options.store.read(signal.id);
36030
+ if (before !== null && !sameEffectSignal(before, signal)) {
36031
+ throw new Error("stored listener effect does not match the authoritative delivery");
36032
+ }
36033
+ const requiredBudget = effectPhaseBudget(before);
36034
+ if (leasedUntilMs <= now() + requiredBudget) {
36035
+ await sleep2(
36036
+ Math.max(
36037
+ 0,
36038
+ leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS - now()
36039
+ ),
36040
+ abort
36041
+ );
36042
+ if (abort?.aborted) {
36043
+ stop = { reason: "cancelled" };
36044
+ break;
36045
+ }
36046
+ if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
36047
+ await journal.clearActive(eventTime(now));
36048
+ after = null;
36049
+ }
36050
+ break;
36051
+ }
36052
+ const processed = await engine.process(signal);
36053
+ const effect = "record" in processed ? processed.record : null;
35990
36054
  options.onEvent?.({
35991
36055
  type: "effect",
35992
36056
  signalId: signal.id,
35993
- status: "routed_main",
35994
- failureCode: null,
36057
+ status: processed.status,
36058
+ failureCode: effect?.failureCode ?? null,
35995
36059
  ts: eventTime(now)
35996
36060
  });
35997
- } else {
35998
- let processAttempt = 0;
35999
- while (terminal === null) {
36000
- const before = await options.store.read(signal.id);
36001
- if (before !== null && !sameEffectSignal(before, signal)) {
36002
- throw new Error("stored listener effect does not match the authoritative delivery");
36003
- }
36004
- const requiredBudget = effectPhaseBudget(before);
36005
- if (leasedUntilMs <= now() + requiredBudget) {
36006
- await sleep2(
36007
- Math.max(
36008
- 0,
36009
- leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS - now()
36010
- ),
36011
- abort
36012
- );
36013
- if (abort?.aborted) {
36014
- stop = { reason: "cancelled" };
36015
- break;
36016
- }
36017
- if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
36018
- await journal.clearActive(eventTime(now));
36019
- after = null;
36020
- }
36061
+ if (processed.status === "ignored") {
36062
+ throw new Error("claimed delivery was ignored by the listener engine");
36063
+ }
36064
+ if (processed.status === "retry_pending") {
36065
+ processAttempt += 1;
36066
+ await sleep2(
36067
+ deliveryRetryDelay(processAttempt, null, random),
36068
+ abort
36069
+ );
36070
+ if (abort?.aborted) {
36071
+ stop = { reason: "cancelled" };
36021
36072
  break;
36022
36073
  }
36023
- const processed = await engine.process(signal);
36024
- const effect = "record" in processed ? processed.record : null;
36025
- options.onEvent?.({
36026
- type: "effect",
36027
- signalId: signal.id,
36028
- status: processed.status,
36029
- failureCode: effect?.failureCode ?? null,
36030
- ts: eventTime(now)
36031
- });
36032
- if (processed.status === "ignored") {
36033
- throw new Error("claimed delivery was ignored by the listener engine");
36034
- }
36035
- if (processed.status === "retry_pending") {
36036
- processAttempt += 1;
36037
- await sleep2(
36038
- deliveryRetryDelay(processAttempt, null, random),
36039
- abort
36040
- );
36041
- if (abort?.aborted) {
36042
- stop = { reason: "cancelled" };
36043
- break;
36044
- }
36045
- continue;
36046
- }
36047
- terminal = processed.record;
36074
+ continue;
36048
36075
  }
36076
+ terminal = processed.record;
36049
36077
  }
36050
- } else {
36051
- throw new Error("claimed delivery has an unsupported signal kind");
36052
36078
  }
36053
36079
  } catch (error) {
36054
36080
  if (abort?.aborted) {
@@ -36097,24 +36123,7 @@ async function runListenerRuntime(options) {
36097
36123
  stop = { reason: "cancelled" };
36098
36124
  break;
36099
36125
  }
36100
- if (signal.kind === "note") {
36101
- try {
36102
- await readOrReplaceUnreadableEffect(options.store, signal, now);
36103
- const record2 = await observeFallbackNote(options.store, signal, now);
36104
- options.onEvent?.({
36105
- type: "effect",
36106
- signalId: signal.id,
36107
- status: "observed",
36108
- failureCode: record2.failureCode,
36109
- ts: eventTime(now)
36110
- });
36111
- } catch (error) {
36112
- stop = { reason: "fatal", error: asError2(error) };
36113
- break;
36114
- }
36115
- continue;
36116
- }
36117
- if (signal.kind !== "ask") continue;
36126
+ if (signal.kind !== "ask" && signal.kind !== "note") continue;
36118
36127
  let result;
36119
36128
  try {
36120
36129
  await readOrReplaceUnreadableEffect(options.store, signal, now);
@@ -36133,7 +36142,7 @@ async function runListenerRuntime(options) {
36133
36142
  ts: eventTime(now)
36134
36143
  });
36135
36144
  if (decision === "main") {
36136
- const record2 = await routeAskToMain(signal);
36145
+ await routeSignalToMain(signal);
36137
36146
  options.onEvent?.({
36138
36147
  type: "effect",
36139
36148
  signalId: signal.id,
@@ -36143,6 +36152,17 @@ async function runListenerRuntime(options) {
36143
36152
  });
36144
36153
  continue;
36145
36154
  }
36155
+ if (signal.kind === "note") {
36156
+ const record2 = await observeFallbackNote(options.store, signal, now);
36157
+ options.onEvent?.({
36158
+ type: "effect",
36159
+ signalId: signal.id,
36160
+ status: "observed",
36161
+ failureCode: record2.failureCode,
36162
+ ts: eventTime(now)
36163
+ });
36164
+ continue;
36165
+ }
36146
36166
  result = await engine.process(signal);
36147
36167
  } catch (error) {
36148
36168
  if (abort?.aborted) {
@@ -38620,8 +38640,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
38620
38640
  AGENT_CREDENTIAL_MESSAGE_D088
38621
38641
  ];
38622
38642
  function packageVersion() {
38623
- if ("0.1.33".length > 0) {
38624
- return "0.1.33";
38643
+ if ("0.1.34".length > 0) {
38644
+ return "0.1.34";
38625
38645
  }
38626
38646
  try {
38627
38647
  const value = JSON.parse(
@@ -38820,11 +38840,11 @@ TTL); a turn that lands just before a rotation can be clamped to the ~5m
38820
38840
  renewal lead, and if it times out there, durable delivery retries it on the
38821
38841
  fresh credential.
38822
38842
 
38823
- listen start --route worker|main|split chooses where directed asks go. worker
38824
- is the unchanged default. main queues every ask for the interactive session.
38825
- split queues asks whose body is longer than --defer-over <chars>; the bound is
38826
- 1..10000 and an equal-length ask stays on the worker path. Run cswarm hook check
38827
- to surface queued asks. hook check has its own 3s ceiling, exits 0 on every
38843
+ listen start --route worker|main|split chooses where directed messages go. worker
38844
+ is the unchanged default. main queues every ask or note for the interactive session.
38845
+ split queues messages whose body is longer than --defer-over <chars>; the bound is
38846
+ 1..10000 and an equal-length message stays on the worker path. Run cswarm hook check
38847
+ to surface queued messages. hook check has its own 3s ceiling, exits 0 on every
38828
38848
  outcome, and skips network checks made within --cooldown seconds (default 30).
38829
38849
  hook install claude prints the UserPromptSubmit JSON by default and changes the
38830
38850
  project's .claude/settings.json only with --write; uninstall also requires --write.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
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"