@volter/supercode-teams 0.3.194 → 0.3.196
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/mail/send.mjs +56 -30
- package/package.json +1 -1
- package/terminal-mail.mjs +2 -1
package/mail/send.mjs
CHANGED
|
@@ -357,10 +357,9 @@ export class MailSend {
|
|
|
357
357
|
const session = sessions.find((row) => row.address === address);
|
|
358
358
|
if (!session) return null;
|
|
359
359
|
if (session.door === 'runtime') return { kind: 'runtime', session };
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
360
|
+
// a native Claude session is reached by its relay; its daemon pane, when it has one, is the fallback for a message
|
|
361
|
+
// the relay could not deliver
|
|
362
|
+
if (session.door === 'native') return { kind: 'native', session, pane: paneOf(session) };
|
|
364
363
|
if (session.door === 'hook') return { kind: 'hook', session, pane: session.pane ?? null, idle: session.status === 'idle' };
|
|
365
364
|
// a session with no hook and no relay (Grok) in a daemon pane: its mail is typed there, into an empty composer
|
|
366
365
|
if (session.door === 'pane' && session.pane) return { kind: 'pane', session, pane: session.pane };
|
|
@@ -441,12 +440,13 @@ export class MailSend {
|
|
|
441
440
|
stored: [EXIT_STORED, `Stored (not failed): it has no delivery door, so it sees this only if it runs supercode message inbox${parseAddress(address).harness === 'codex' ? ' (a Codex session gets one with: supercode message setup codex)' : ''}. Don't resend, and don't wait for a reply`],
|
|
442
441
|
}[delivered];
|
|
443
442
|
this.#recordSend(caller.address, address, messageId, sentIn, receiver.continuation);
|
|
443
|
+
const viaPane = terminal || (door.kind === 'native' && ['typed', 'pane_held', 'pane_waits'].includes(delivered));
|
|
444
444
|
const harness = parseAddress(address).harness;
|
|
445
445
|
const text = code === 0
|
|
446
446
|
? `sent to ${name} (${harness}, ${door.kind}): ${what}. Message id ${shortId(messageId)}.${receiver.alias ?? ''}${idleNote} Don't poll; carry on.`
|
|
447
447
|
: `${name} (${harness}): ${what}. Message id ${shortId(messageId)}.${idleNote}`;
|
|
448
|
-
if ((code === 0 || (
|
|
449
|
-
return outcome(code, text, code === 0 ||
|
|
448
|
+
if ((code === 0 || (viaPane && code === EXIT_STORED)) && id != null) { try { this.store.setSentMarker(caller.address, messageId, text); } catch { /* the marker is best kept */ } }
|
|
449
|
+
return outcome(code, text, code === 0 || viaPane ? this.#deliveryReceipt(address, messageId, viaPane) : null);
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
/** An agent's plan for `sent` (its thread store decides), or an outcome when the plan refused it. */
|
|
@@ -485,6 +485,16 @@ export class MailSend {
|
|
|
485
485
|
if (how === 'not_woken') { this.store.deliver(to, sent); delivered = 'queued'; }
|
|
486
486
|
else { this.store.deliverRead(to, handed); finalReply = answers; delivered = how === 'steered' ? 'steered' : 'started'; }
|
|
487
487
|
} else if (door.kind === 'native') {
|
|
488
|
+
// one its pane fallback already holds (typed, or queued for its composer) is never handed to the relay as well
|
|
489
|
+
const held = door.pane ? terminalReceipt(this.store, to, sent.id) : null;
|
|
490
|
+
if (held?.delivered) {
|
|
491
|
+
attempt?.begin('existing-native-delivery');
|
|
492
|
+
try { this.store.claimFor(to, sent.id, { pid: process.pid, via: 'door', caller: null, recipient: true, at_ms: Date.now() }); } catch { /* best kept */ }
|
|
493
|
+
const now = this.store.findIn(to, sent.id);
|
|
494
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
495
|
+
return 'already';
|
|
496
|
+
}
|
|
497
|
+
if (held?.queued) return 'pane_held';
|
|
488
498
|
const busy = door.session.status !== 'idle' || door.session.panes_current === false;
|
|
489
499
|
if (!wake && !busy) throw new Refused('queue');
|
|
490
500
|
if (busy) {
|
|
@@ -543,43 +553,35 @@ export class MailSend {
|
|
|
543
553
|
// gone, so its gate refuses a late send) and it is not in the receiver's transcript: a copy this delivery filed
|
|
544
554
|
// goes back unread, for the receiver's own mailbox read and the mailbox's wake, which hands it over again
|
|
545
555
|
const received = this.store.claimOf(to, handed.id)?.recipient === true;
|
|
546
|
-
|
|
556
|
+
// the relay produced no receipt and cannot send it late (known not made, or not in the receiver's transcript after
|
|
557
|
+
// its bound, its queue gone): a session with a daemon pane has the same copy typed there instead
|
|
558
|
+
if (!relayed.delivered && !received && wake && door.pane && (!relayed.unknown || relayed.landed === false)) {
|
|
559
|
+
this.log(`relay could not deliver ${shortId(handed.id)} to ${to} (${relayed.detail}); typing it into its pane ${door.pane}`);
|
|
560
|
+
this.relaying.add(key);
|
|
561
|
+
try { delivered = await this.#typeIntoPane(handed, to, door.pane, wake, attempt); }
|
|
562
|
+
finally { this.relaying.delete(key); }
|
|
563
|
+
} else if (!relayed.delivered && relayed.unknown && !received) {
|
|
547
564
|
// kept unread, its wake due now: the carrier hands it over again after its backoff
|
|
548
565
|
if (filedHere && wake) { try { this.store.requestWake(to, handed.id); } catch { /* the wake filed above stands */ } }
|
|
549
566
|
throw Object.assign(new Error(relayed.detail), { unanswered: true });
|
|
550
567
|
}
|
|
551
|
-
if (!relayed.delivered && !received) { if (filedHere) { try { this.store.remove(to, filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
568
|
+
else if (!relayed.delivered && !received) { if (filedHere) { try { this.store.remove(to, filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
552
569
|
// relayed: a copy filed unread before this delivery (an earlier unanswered or queued one, held for the receiver's
|
|
553
570
|
// own read) is moved to cur/, so that read never hands it over a second time
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
571
|
+
else {
|
|
572
|
+
if (relayed.delivered && !received) {
|
|
573
|
+
const now = this.store.findIn(to, handed.id);
|
|
574
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
575
|
+
}
|
|
576
|
+
delivered = 'native_idle';
|
|
557
577
|
}
|
|
558
|
-
delivered = 'native_idle';
|
|
559
578
|
} else if (door.kind === 'hook') {
|
|
560
579
|
attempt?.begin('hook-mailbox');
|
|
561
580
|
this.store.deliver(to, sent);
|
|
562
581
|
if (wake) this.store.requestWake(to, sent.id);
|
|
563
582
|
delivered = 'hooked';
|
|
564
583
|
} else if (door.kind === 'pane') {
|
|
565
|
-
|
|
566
|
-
let typed = null;
|
|
567
|
-
attempt?.begin(wake ? 'terminal-input' : 'terminal-mailbox');
|
|
568
|
-
if (wake) { try { typed = await this.typeTurn(door.pane, parseAddress(to).harness, render(sent), [sent.id], to); } catch (error) { typed = { reason: error.message }; } }
|
|
569
|
-
if (typed?.delivered) { this.store.deliverRead(to, sent); delivered = 'typed'; }
|
|
570
|
-
// held by the daemon's queue for the pane, typed once its composer frees: not typed yet, so kept unread
|
|
571
|
-
else if (typed?.queued) { this.store.deliver(to, sent); if (wake) this.store.requestWake(to, sent.id, { keep: true }); delivered = 'pane_held'; }
|
|
572
|
-
// not typed (its composer held a draft or a turn ran): it waits unread with a wake, so the carrier types it once the
|
|
573
|
-
// composer is empty (mail/carrier.mjs), never left for a read a session with no hook never makes
|
|
574
|
-
else {
|
|
575
|
-
this.store.deliver(to, sent);
|
|
576
|
-
if (wake) {
|
|
577
|
-
this.store.requestWake(to, sent.id, { keep: true });
|
|
578
|
-
const state = this.store.wakeState(to, sent.id);
|
|
579
|
-
this.store.setWakeState(to, sent.id, { ...state, next_at_ms: Date.now() + 2_000, last_error: paneWaitReason(typed) });
|
|
580
|
-
}
|
|
581
|
-
delivered = wake ? 'pane_waits' : 'queued';
|
|
582
|
-
}
|
|
584
|
+
delivered = await this.#typeIntoPane(sent, to, door.pane, wake, attempt);
|
|
583
585
|
} else if (door.kind === 'stored') { attempt?.begin('stored-mailbox'); this.store.deliver(to, sent); delivered = 'stored'; }
|
|
584
586
|
else { attempt?.begin('operator-mailbox'); this.store.deliver(to, sent); delivered = 'operator'; }
|
|
585
587
|
// a hand-over to the recipient's own door is its delivery, recorded as such
|
|
@@ -592,6 +594,30 @@ export class MailSend {
|
|
|
592
594
|
return delivered;
|
|
593
595
|
}
|
|
594
596
|
|
|
597
|
+
/** Type `sent` into `pane` as its next turn, only into an empty composer (machine.mjs #paneTurn); otherwise it waits
|
|
598
|
+
* in its mailbox. Answers 'typed', 'pane_held', 'pane_waits' or 'queued'. */
|
|
599
|
+
async #typeIntoPane(sent, to, pane, wake, attempt) {
|
|
600
|
+
let typed = null;
|
|
601
|
+
attempt?.begin(wake ? 'terminal-input' : 'terminal-mailbox');
|
|
602
|
+
if (wake) { try { typed = await this.typeTurn(pane, parseAddress(to).harness, render(sent), [sent.id], to); } catch (error) { typed = { reason: error.message }; } }
|
|
603
|
+
if (typed?.delivered) {
|
|
604
|
+
this.store.deliverRead(to, sent);
|
|
605
|
+
const now = this.store.findIn(to, sent.id);
|
|
606
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
607
|
+
return 'typed';
|
|
608
|
+
}
|
|
609
|
+
// held by the daemon's queue for the pane, typed once its composer frees: not typed yet, so kept unread
|
|
610
|
+
if (typed?.queued) { this.store.deliver(to, sent); if (wake) this.store.requestWake(to, sent.id, { keep: true }); return 'pane_held'; }
|
|
611
|
+
// not typed (its composer held a draft or a turn ran): it waits unread with a wake, so the carrier tries it again once
|
|
612
|
+
// the composer is empty (mail/carrier.mjs), never left for a read a session with no hook never makes
|
|
613
|
+
this.store.deliver(to, sent);
|
|
614
|
+
if (!wake) return 'queued';
|
|
615
|
+
this.store.requestWake(to, sent.id, { keep: true });
|
|
616
|
+
const state = this.store.wakeState(to, sent.id);
|
|
617
|
+
this.store.setWakeState(to, sent.id, { ...state, next_at_ms: Date.now() + 2_000, last_error: paneWaitReason(typed) });
|
|
618
|
+
return 'pane_waits';
|
|
619
|
+
}
|
|
620
|
+
|
|
595
621
|
/**
|
|
596
622
|
* Deliver `sent` to every receiver `plan` names: a `to` receiver through its door (a stopped holder the mailbox
|
|
597
623
|
* launched is resumed first), a `cc` receiver filed unread and not woken (mail_send.rs `deliver_planned`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.196",
|
|
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",
|
package/terminal-mail.mjs
CHANGED
|
@@ -284,7 +284,8 @@ export class TerminalMailbox {
|
|
|
284
284
|
let lost = true;
|
|
285
285
|
for (let check = 0; lost && check < 2; check++) {
|
|
286
286
|
const screen = await this.host.capture(this.session.id, { lines: 200, preserveEscapes: true });
|
|
287
|
-
|
|
287
|
+
// Codex paints its hint as `\x1b[0;1mesc\x1b[0;2m to interrupt`: a running turn is read with the styling removed.
|
|
288
|
+
lost = composerText(batch.kind, screen) === '' && !/\besc to (?:interrupt|cancel)\b/iu.test(String(screen).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, ''));
|
|
288
289
|
if (lost && check === 0) await pause(200);
|
|
289
290
|
}
|
|
290
291
|
if (lost && !confirmed(batch)) {
|