@volter/supercode-teams 0.3.101 → 0.3.102

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
@@ -302,8 +302,8 @@ function instructionsFile({ harness, scope = 'user', cwd } = {}) {
302
302
  const BOARD_ANSWER_PIECE = 512 * 1024;
303
303
  // What an inbox answer carries at most: the channel's frame is 8 MiB (channel.mjs MAX_FRAME), and an answer holds each
304
304
  // message three ways (rendered, plain, its row). A mailbox's whole history did not fit (yuerans, 2026-10-06: 6,496 read
305
- // envelopes, a 43 MB frame refused); the newest that fit are listed, its claimed (unread) ones always, and a first line
306
- // says how many older ones are not.
305
+ // envelopes, a 43 MB frame refused); the newest that fit are listed, its unread ones always, and a first line says how
306
+ // many older read ones are not.
307
307
  const INBOX_ANSWER_BYTES = 4 * 1024 * 1024;
308
308
 
309
309
  /** The indexes of `count` messages kept in an answer, newest first until `sizeOf` passes the bound; `always` kept. */
@@ -317,7 +317,7 @@ function inboxKept(count, sizeOf, always = () => false) {
317
317
  }
318
318
  return kept.reverse();
319
319
  }
320
- const inboxNote = (left) => `(${left} older message${left === 1 ? ' is' : 's are'} not listed: an answer holds at most ${INBOX_ANSWER_BYTES / 1024 / 1024} MiB. --recent <N> lists the newest N.)`;
320
+ const inboxNote = (left) => `(${left} older read message${left === 1 ? ' is' : 's are'} not listed: an answer holds at most ${INBOX_ANSWER_BYTES / 1024 / 1024} MiB. --recent <N> lists the newest N.)`;
321
321
 
322
322
 
323
323
  export class TeamsMachine extends MachineCore {
@@ -3492,7 +3492,8 @@ export class TeamsMachine extends MachineCore {
3492
3492
  const machine = address.split(':')[1];
3493
3493
  if (machine === machineName(this.env)) {
3494
3494
  const rows = await this.mail.inboxRows(address, last);
3495
- const kept = inboxKept(rows.length, (index) => Buffer.byteLength(JSON.stringify(rows[index])));
3495
+ // an unread message to it is always listed, as its own inbox lists its claimed ones
3496
+ const kept = inboxKept(rows.length, (index) => Buffer.byteLength(JSON.stringify(rows[index])), (index) => rows[index].direction === 'in' && rows[index].state === 'unread');
3496
3497
  const left = rows.length - kept.length;
3497
3498
  return { code: 0, address, rows: [...(left ? [{ direction: 'note', omitted: left, rendered: inboxNote(left) }] : []), ...kept.map((index) => rows[index])] };
3498
3499
  }
package/mail/send.mjs CHANGED
@@ -335,6 +335,12 @@ export class MailSend {
335
335
  throw Object.assign(new Error(relayed.detail), { unanswered: true });
336
336
  }
337
337
  if (!relayed.delivered && !received) { if (filedHere) { try { unlinkSync(filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
338
+ // relayed: a copy filed unread before this delivery (an earlier unanswered or queued one, held for the receiver's
339
+ // own read) is moved to cur/, so that read never hands it over a second time
340
+ if (relayed.delivered && !received) {
341
+ const now = this.store.findIn(to, handed.id);
342
+ if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
343
+ }
338
344
  delivered = busy ? 'native_busy' : 'native_idle';
339
345
  } else if (door.kind === 'hook') {
340
346
  this.store.deliver(to, sent);
@@ -368,7 +374,8 @@ export class MailSend {
368
374
 
369
375
  async #deliverPlanned(caller, sent, plan, { queue, notify_when_idle }) {
370
376
  const reached = [], copied = [], failed = [];
371
- let recorded = false, held = false;
377
+ let recorded = false;
378
+ const held = []; // recipients whose own copy is held unread with its wake (an unanswered relay)
372
379
  if (plan.agent) {
373
380
  // the agent's own mailbox keeps the record of what was addressed to it
374
381
  try { this.store.deliverRead(plan.agent, sent); recorded = true; } catch (error) { failed.push(`the agent's mailbox (${error.message})`); }
@@ -383,8 +390,12 @@ export class MailSend {
383
390
  }
384
391
  try { reached.push(`${label} (${await this.#deliverWoken(sent, recipient.address, sessions, queue, notify_when_idle)})`); }
385
392
  catch (error) {
386
- held ||= error.unanswered === true;
387
- failed.push(error.unanswered ? `${label} (unanswered, whether it arrived is unknown: ${error.message})` : `${label} (${error.message})`);
393
+ // held only when its copy is here, unread: its wake hands it over, or (queued, no wake) the receiver's own read
394
+ // does. An unanswered request to another machine's door holds nothing here, so it is a failure, and the agent's
395
+ // own copy waits with its wake (filing there again is filing the same id, which files nothing new)
396
+ const kept = this.store.findIn(recipient.address, sent.id);
397
+ if (error.unanswered && kept?.state === 'unread') held.push(`${label} (unanswered: ${error.message}; ${this.store.hasWake(recipient.address, sent.id) ? 'its wake hands it over' : 'queued: its own mailbox read gives it'})`);
398
+ else failed.push(error.unanswered ? `${label} (unanswered, whether it arrived is unknown: ${error.message})` : `${label} (${error.message})`);
388
399
  }
389
400
  }
390
401
  const thread = plan.thread ? ` Thread ${shortId(plan.thread.id)}.` : '';
@@ -393,10 +404,11 @@ export class MailSend {
393
404
  // recipient has it or the wake expires. Not when a recipient's own copy is held unread with its wake (an unanswered
394
405
  // relay), which hands it over itself: two wakes would deliver it twice
395
406
  let waiting = false;
396
- if (recorded && (reached.length || copied.length)) {
397
- // reached: its record is read (one a retry handed over was unread)
407
+ if (recorded && (reached.length || copied.length || held.length)) {
408
+ // reached, or held by a recipient's own wake (which hands it over, once): its record is read (one a retry handed
409
+ // over was unread), so its own wake, if any, is settled and never hands it over beside the recipient's
398
410
  try { const record = this.store.findIn(plan.agent, sent.id); if (record?.state === 'unread') this.store.markRead(plan.agent, record.path); } catch { /* kept unread */ }
399
- } else if (recorded && !held) {
411
+ } else if (recorded) {
400
412
  try {
401
413
  const record = this.store.findIn(plan.agent, sent.id);
402
414
  if (record?.state === 'read') this.store.markUnread(plan.agent, record.path);
@@ -405,12 +417,14 @@ export class MailSend {
405
417
  } catch (error) { failed.push(`its retry (${error.message})`); }
406
418
  }
407
419
  let text;
420
+ // a held copy is not said not sent: it is handed over by its wake, so a sender that sent it again would duplicate it
421
+ const heldText = held.length ? ` Held, unanswered, unread in ${held.length === 1 ? 'its receiver\'s' : 'their receivers\''} mailbox, which gives it to the receiver once (don't send it again): ${held.join('; ')}.` : '';
408
422
  if (waiting) text = `not sent yet: nobody in its thread could be reached, so it waits unread in ${plan.agent}'s mailbox and is handed over again when one can be. Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
409
- else if (!reached.length && !copied.length) text = `Not sent: nobody in its thread could be reached.${thread}`;
410
- else text = `sent to ${reached.length ? reached.join(', ') : 'no one woken'}.${copied.length ? ` CC (filed, not woken): ${copied.join(', ')}.` : ''} Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
423
+ else if (!reached.length && !copied.length && !held.length) text = `Not sent: nobody in its thread could be reached.${thread}`;
424
+ else text = `sent to ${reached.length ? reached.join(', ') : 'no one woken yet'}.${copied.length ? ` CC (filed, not woken): ${copied.join(', ')}.` : ''}${heldText} Message id ${shortId(sent.id)}.${thread} Don't poll; carry on.`;
411
425
  if (failed.length) text += ` Not delivered to: ${failed.join('; ')}.`;
412
- const code = reached.length || copied.length || waiting ? 0 : EXIT_FAILED;
413
- return { ...outcome(code, text, code === 0 ? { delivered: true, message_ids: [sent.id], receipt: 'agent-mailbox', ...(waiting ? { waiting } : {}) } : null), waiting };
426
+ const code = reached.length || copied.length || held.length || waiting ? 0 : EXIT_FAILED;
427
+ return { ...outcome(code, text, code === 0 ? { delivered: true, message_ids: [sent.id], receipt: 'agent-mailbox', ...(waiting ? { waiting } : {}), ...(held.length && !reached.length && !copied.length ? { unanswered: true } : {}) } : null), waiting };
414
428
  }
415
429
 
416
430
  /** `sent` filed unread in `address`'s mailbox on this machine's mail, without waking it (mail_agent.rs `file_unread`):
package/mail/store.mjs CHANGED
@@ -413,6 +413,12 @@ export class MailStore extends EventEmitter {
413
413
  catch (error) { if (!keep || error.code !== 'EEXIST') throw error; }
414
414
  }
415
415
 
416
+ /** Whether a wake is on file for `id` in `address`'s mailbox. */
417
+ hasWake(address, id) {
418
+ if (!/^[A-Za-z0-9_-]+$/.test(id)) return false;
419
+ return existsSync(join(this.open(address).dir, 'wake', id));
420
+ }
421
+
416
422
  /** A wake's delivery so far (mailbox.rs `wake_state`): an empty or absent file is a fresh wake. */
417
423
  wakeState(address, id) {
418
424
  const box = this.open(address);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.101",
3
+ "version": "0.3.102",
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",