@volter/supercode-teams 0.3.99 → 0.3.100

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.
package/machine.mjs CHANGED
@@ -3419,7 +3419,8 @@ export class TeamsMachine extends MachineCore {
3419
3419
  case 'harness.v1.mail.handover': {
3420
3420
  if (entry.principal !== 'local' || entry.appDoor) throw Object.assign(new Error('mail is handed over here by this machine\'s own processes'), { code: 403 });
3421
3421
  if (typeof params.address !== 'string' || typeof params.id !== 'string' || !parseAddress(params.address)) throw Object.assign(new Error('a hand-over names an address and a message id'), { code: 400 });
3422
- return this.mailSend.handOver(params.address, params.id, { markRead: params.mark_read === true, wake: params.wake === true });
3422
+ // a hand-over is moved to cur/ once delivered whatever `mark_read` says (MailSend.handOver)
3423
+ return this.mailSend.handOver(params.address, params.id, { wake: params.wake === true });
3423
3424
  }
3424
3425
  // harness serve's `sessions.message` (its mail to a session or an agent, not its `as_user` turn), asked here by
3425
3426
  // harness serve itself: one implementation of a send, whatever the entry.
package/mail/send.mjs CHANGED
@@ -52,6 +52,7 @@ export class MailSend {
52
52
  */
53
53
  constructor({ machine, mailRoot, live, store, agents, relays, askMachine, deliverRuntime, codexAnswer, typeTurn, log = () => {} }) {
54
54
  Object.assign(this, { machine, mailRoot, live, store, agents, relays, askMachine, deliverRuntime, codexAnswer, typeTurn, log });
55
+ this.handing = new Set(); // `<address>\n<id>` of each hand-over running here (handOver)
55
56
  }
56
57
 
57
58
  // ---- one send ----------------------------------------------------------------------------------------------------
@@ -665,16 +666,27 @@ export class MailSend {
665
666
  /**
666
667
  * A message filed unread in `address`'s mailbox handed to its session's door, once (mail_watch.rs `carry` and message.rs
667
668
  * `push`, and the native watch's notices): only a door that hands it to the session (a relay, a runtime) takes it, a
668
- * hook's is woken when `wake`, and an agent's mail is planned; `markRead` moves it to `cur/` once handed over. The
669
- * hand-over is recorded on its wake before it starts, so one interrupted mid-way is never handed over again. Answers
669
+ * hook's is woken when `wake`, and an agent's mail is planned. One handed over is moved to `cur/` here, whoever asked:
670
+ * its receiver has it, so its next inbox read never shows it again (the carrier marks it read only when its own call
671
+ * answers delivered, which a hand-over past its bound never does). The
672
+ * hand-over is recorded on its wake before it starts, so one interrupted mid-way (its daemon ended) is never handed over
673
+ * again; one still running here, its carrier past its bound, answers 'later' until it settles. Answers
670
674
  * `{ outcome: 'delivered' | 'filed' | 'later', detail }`.
671
675
  */
672
- async handOver(address, id, { markRead = false, wake = false } = {}) {
676
+ async handOver(address, id, { wake = false } = {}) {
673
677
  const found = this.store.findIn(address, id);
674
678
  if (!found) return { outcome: 'filed', detail: 'not in its mailbox' };
675
679
  const sent = readJson(found.path);
676
680
  if (!sent) return { outcome: 'later', detail: 'its envelope could not be read' };
677
681
  if (found.state !== 'unread') return { outcome: 'filed', detail: 'already read' };
682
+ // one already handed to its receiver's door (that delivery's claim, the door's or the carrier's: a daemon that ended
683
+ // between the delivery and moving the copy left it unread) is moved to `cur/`, never handed over again. A reader's
684
+ // claim is not one: a reader that ended before reading put the copy back unread, and it is handed over
685
+ const claim = this.store.claimOf(address, id);
686
+ if (claim?.recipient === true && (claim.via === 'door' || claim.via === 'carrier')) {
687
+ this.store.markRead(address, found.path);
688
+ return { outcome: 'filed', detail: 'already handed over' };
689
+ }
678
690
  const parsed = parseAddress(address);
679
691
  if (parsed?.harness === AGENT_HARNESS) {
680
692
  const plan = await this.#plan(sent, address, {});
@@ -691,11 +703,18 @@ export class MailSend {
691
703
  return { outcome: 'filed', detail: `its door is ${door.kind}` };
692
704
  }
693
705
  const state = this.store.wakeState(address, id);
706
+ // one still running here (its carrier gave up on its answer, past its bound) is waited for: the wake stays and is
707
+ // tried again after its backoff, settled by that hand-over's own outcome
708
+ const key = `${address}\n${id}`;
709
+ if (this.handing.has(key)) return { outcome: 'later', detail: 'its hand-over is still in progress' };
694
710
  if (state.handover_at_ms != null) return { outcome: 'filed', detail: 'an earlier hand-over never settled' };
695
711
  this.store.setWakeState(address, id, { ...state, handover_at_ms: Date.now() });
712
+ this.handing.add(key);
696
713
  try {
697
- await this.route(sent, address, door, true, false);
698
- if (markRead) { const now = this.store.findIn(address, id); if (now?.state === 'unread') this.store.markRead(address, now.path); }
714
+ const how = await this.route(sent, address, door, true, false);
715
+ // handed to the session (or already its): moved to cur/ (one only queued for a runtime not woken would stay unread)
716
+ const now = this.store.findIn(address, id);
717
+ if (how !== 'queued' && now?.state === 'unread') this.store.markRead(address, now.path);
699
718
  return { outcome: 'delivered', detail: null };
700
719
  } catch (error) {
701
720
  // a message its door can never take (too long for a relay) stays filed for the session to read
@@ -704,6 +723,7 @@ export class MailSend {
704
723
  if (error.unanswered) return { outcome: 'later', detail: `unanswered: ${error.message}` };
705
724
  return error instanceof Refused ? { outcome: 'filed', detail: error.kind } : { outcome: 'later', detail: error.message };
706
725
  } finally {
726
+ this.handing.delete(key);
707
727
  const after = this.store.wakeState(address, id);
708
728
  this.store.setWakeState(address, id, { ...after, handover_at_ms: null });
709
729
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.99",
3
+ "version": "0.3.100",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",