jefrichat-mcp 0.49.52 → 0.49.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/dist/index.js +22 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ from gateway.platforms.event import MessageEvent, MessageType, ProcessingOutcome
75
75
  from gateway.platforms._shared import extra_or_secret, get_scoped_secret
76
76
  from gateway.status import acquire_scoped_lock, release_scoped_lock
77
77
 
78
- from .native_state import NativeState, read_config, private_write
78
+ from .native_state import NativeState, read_config
79
79
  from .sdk_client import JefriClient
80
80
  from .recovery import HistoryRecovery
81
81
  from .delivery import deliver, delivery_code, reply_chunks
@@ -132,6 +132,7 @@ class JefriAdapter(BasePlatformAdapter):
132
132
  self.waiter = None
133
133
  self.current = None
134
134
  self.last_error = None
135
+ self.delivery_error = None
135
136
  self.stopping = False
136
137
  self.send_index = 0
137
138
  self.sent_keys = {}
@@ -191,6 +192,8 @@ class JefriAdapter(BasePlatformAdapter):
191
192
  self.observed_revision = read_config(self.connection_file).get("controlRevision")
192
193
  self.recovery = HistoryRecovery()
193
194
  self.recovery_error = self.connection_error = self.last_error = None
195
+ if self.state.recovered_uncertain:
196
+ self.last_error = "Interrupted native tasks need attention and will not be replayed; the owner's ON/OFF setting is unchanged"
194
197
  self.next_recovery = self.next_connection_check = self.retry_work_after = 0
195
198
  try:
196
199
  self.version = json.loads((Path(self.connection_file).parent / "runtime/package.json").read_text())["version"]
@@ -510,9 +513,8 @@ class JefriAdapter(BasePlatformAdapter):
510
513
  return
511
514
  self.last_error = detail
512
515
  await self._notice(message, "This task needs attention: execution or reply delivery could not be confirmed. I will not repeat its actions automatically.", "attention")
513
- current = read_config(self.connection_file)
514
- if self.state.owner() and current.get("controlRevision") == self.turn_revision:
515
- private_write(self.connection_file, {**current, "enabled": False})
516
+ # This ID is now fenced against execution. Keep the owner's intent and
517
+ # the rest of the queue intact, including if a notice races an OFF/ON.
516
518
 
517
519
  async def _work(self):
518
520
  while not self.stopping:
@@ -536,6 +538,7 @@ class JefriAdapter(BasePlatformAdapter):
536
538
  self.started_at = time.monotonic()
537
539
  self.next_notice = self.started_at + self.notice_delay
538
540
  self.send_index = 0
541
+ self.delivery_error = None
539
542
  self.sent_keys = {}
540
543
  self.send_receipts = {}
541
544
  self.edits = {}
@@ -563,7 +566,7 @@ class JefriAdapter(BasePlatformAdapter):
563
566
  self.last_error = None
564
567
  self.last_processed = int(time.time() * 1000)
565
568
  else:
566
- await self._uncertain(item, m, "No confirmed final delivery; inspect native session and Jefri history")
569
+ await self._uncertain(item, m, self.delivery_error or "Native completion unconfirmed; inspect native session and Jefri history. This task will not be replayed; other eligible messages can continue")
567
570
  self._warning("delivery_uncertain")
568
571
  except Exception as error:
569
572
  if not dispatched:
@@ -571,7 +574,7 @@ class JefriAdapter(BasePlatformAdapter):
571
574
  self.retry_work_after = time.monotonic() + 30
572
575
  self._warning("preflight_retry")
573
576
  continue
574
- await self._uncertain(item, m, "Native execution/delivery unconfirmed (" + delivery_code(error) + "); inspect session before retrying")
577
+ await self._uncertain(item, m, "Native execution unconfirmed (" + delivery_code(error) + "); inspect session before retrying. This task will not be replayed; other eligible messages can continue")
575
578
  self._warning("execution_uncertain")
576
579
  finally:
577
580
  self._retire_questions()
@@ -588,6 +591,7 @@ class JefriAdapter(BasePlatformAdapter):
588
591
  self.waiter.set_result(outcome)
589
592
 
590
593
  async def send(self, chat_id, content, reply_to=None, metadata=None):
594
+ turn_id = None
591
595
  try:
592
596
  if isinstance(metadata, dict) and metadata.get("expect_edits"):
593
597
  # Some Hermes proxy/older paths still construct a stream for
@@ -618,6 +622,8 @@ class JefriAdapter(BasePlatformAdapter):
618
622
  return SendResult(success=True, message_id=self.send_receipts[chunk_key])
619
623
  except Exception as error:
620
624
  self._warning("send_unconfirmed")
625
+ if turn_id and self.current and self.current["id"] == turn_id and native_turn.get() == turn_id:
626
+ self.delivery_error = "Native reply delivery unconfirmed (" + delivery_code(error) + "); inspect Jefri history. This task will not be replayed; other eligible messages can continue"
621
627
  # Suppress Hermes's differently formatted fallback after an unknown
622
628
  # result: changing the payload/key could duplicate an accepted send.
623
629
  return SendResult(success=False, error="Jefri send timed out or is unconfirmed (" + delivery_code(error) + "); inspect history before retrying")
@@ -757,10 +763,9 @@ class NativeState:
757
763
  self.db.execute("CREATE TABLE IF NOT EXISTS turns (id TEXT PRIMARY KEY, chat TEXT NOT NULL, message TEXT, state TEXT NOT NULL, at INTEGER NOT NULL, reply TEXT, sent INTEGER NOT NULL DEFAULT 0, detail TEXT)")
758
764
  self.db.execute("CREATE TABLE IF NOT EXISTS sibling_admissions (id TEXT PRIMARY KEY, sender TEXT NOT NULL, at INTEGER NOT NULL)")
759
765
  self.db.execute("CREATE TABLE IF NOT EXISTS group_rearm (id TEXT PRIMARY KEY, cutoff TEXT NOT NULL)")
760
- recovered = self.db.execute("UPDATE turns SET state='uncertain',detail='Gateway stopped mid-turn; inspect native session and Jefri history before retrying' WHERE state IN ('running','sending')").rowcount
761
- if recovered:
762
- private_write(self.file, {**read_config(self.file), "enabled": False})
763
- self.cancel_queued()
766
+ self.recovered_uncertain = self.db.execute("UPDATE turns SET state='uncertain',detail='Gateway stopped mid-turn; inspect native session and Jefri history before retrying' WHERE state IN ('running','sending')").rowcount
767
+ # Fence interrupted IDs, not the whole responder. A restart must not
768
+ # change the owner's saved choice or discard undispatched queued work.
764
769
 
765
770
  @staticmethod
766
771
  def _db(file):
@@ -63539,17 +63544,14 @@ async function startOpenClawBridge(file, timing = { heartbeatMs: 1e3, recoveryMs
63539
63544
  }
63540
63545
  const journal = new NativeJournal(path11.join(path11.dirname(file), "turns.db"));
63541
63546
  const questions = new NativeQuestions(path11.join(path11.dirname(file), "questions.db"));
63542
- if (journal.recoveredUncertain) {
63543
- setNativeEnabled(file, false);
63544
- journal.cancelQueued();
63545
- }
63546
63547
  let socket, active, busy = false, stopping = false, closed = false;
63547
63548
  let activeGroup;
63548
63549
  let activeMessage, activeStartedAt = 0, nextNoticeAt = 0;
63549
63550
  let noticeTask;
63550
63551
  const notices = /* @__PURE__ */ new Set();
63551
63552
  let activeRevision;
63552
- let turnError = null, admissionError = null, recoveryError = null;
63553
+ let turnError = journal.recoveredUncertain ? "Interrupted native tasks need attention and will not be replayed; the owner's ON/OFF setting is unchanged." : null;
63554
+ let admissionError = null, recoveryError = null;
63553
63555
  let pumpAfter = 0, lastInboundAt = null, lastProcessedAt = null;
63554
63556
  let observedRevision = config2.controlRevision;
63555
63557
  let recoveryTask, recoveryAbort, nextRecoveryAt = 0;
@@ -63626,6 +63628,7 @@ async function startOpenClawBridge(file, timing = { heartbeatMs: 1e3, recoveryMs
63626
63628
  while (enabled() && socket?.online && (next = journal.next())) {
63627
63629
  const { id: id5, chat, message } = next;
63628
63630
  let dispatched = false;
63631
+ let phase = "execution";
63629
63632
  const revision = readNativeConfig(file).controlRevision;
63630
63633
  const turnAllowed = async () => revision === readNativeConfig(file).controlRevision && await check2(message) && revision === readNativeConfig(file).controlRevision;
63631
63634
  try {
@@ -63648,8 +63651,10 @@ async function startOpenClawBridge(file, timing = { heartbeatMs: 1e3, recoveryMs
63648
63651
  const answer = questions.match(message, revision ?? null);
63649
63652
  const modelMessage = answer ? { ...message, content: `Answer to your question ${JSON.stringify(answer.pending.question.question)}: ${answer.label}` } : message;
63650
63653
  const reply = await runOpenClawTurn(config2, chat, modelMessage, signal);
63654
+ phase = "reply authorization";
63651
63655
  if (!await turnAllowed()) throw Error("Native admission or group eligibility changed while working; reply withheld.");
63652
63656
  lock2.withOwnership(() => journal.reply(id5, reply));
63657
+ phase = "reply delivery";
63653
63658
  for (const [part, text6] of splitNativeReply(reply).entries()) {
63654
63659
  if (!await turnAllowed()) throw Error("Reply stopped because eligibility changed.");
63655
63660
  const result = await deliverNative(() => send(text6, part), turnAllowed);
@@ -63669,15 +63674,11 @@ async function startOpenClawBridge(file, timing = { heartbeatMs: 1e3, recoveryMs
63669
63674
  pumpAfter = Date.now() + timing.recoveryMs;
63670
63675
  break;
63671
63676
  }
63672
- turnError = e?.name === "AbortError" || e?.name === "TimeoutError" ? "Native turn interrupted; platform actions may still finish." : `Native execution/delivery uncertain (${nativeDeliveryCode(e)}). Inspect gateway session and Jefri history; no automatic retry.`;
63677
+ turnError = e?.name === "AbortError" || e?.name === "TimeoutError" ? "Native turn interrupted; platform actions may still finish. This task will not be replayed; the owner's ON/OFF setting is unchanged." : `Native ${phase} uncertain (${nativeDeliveryCode(e)}). Inspect gateway session and Jefri history; this task will not be replayed. Other eligible messages can continue.`;
63673
63678
  if (lock2.isOwner()) {
63674
63679
  lock2.withOwnership(() => journal.mark(id5, "uncertain", turnError));
63675
63680
  if (readNativeConfig(file).controlRevision === revision) {
63676
63681
  await notice(message, "This task needs attention: execution or reply delivery could not be confirmed. I will not repeat its actions automatically.", "attention");
63677
- if (lock2.isOwner() && readNativeConfig(file).controlRevision === revision) {
63678
- setNativeEnabled(file, false);
63679
- journal.cancelQueued();
63680
- }
63681
63682
  } else turnError = null;
63682
63683
  }
63683
63684
  } finally {
@@ -63790,7 +63791,7 @@ async function startOpenClawBridge(file, timing = { heartbeatMs: 1e3, recoveryMs
63790
63791
  await Promise.allSettled([...admissions]);
63791
63792
  while (busy) await new Promise((resolve) => setTimeout(resolve, 50));
63792
63793
  await Promise.allSettled([...notices]);
63793
- if (lock2.isOwner()) journal.cancelQueued();
63794
+ if (lock2.isOwner() && !readNativeConfig(file).enabled) journal.cancelQueued();
63794
63795
  lock2.release();
63795
63796
  status();
63796
63797
  closed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.52",
3
+ "version": "0.49.53",
4
4
  "engines": {
5
5
  "node": ">=22.13"
6
6
  },