@volter/supercode-teams 0.3.195 → 0.3.197
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 +70 -42
- package/package.json +1 -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 };
|
|
@@ -428,7 +427,8 @@ export class MailSend {
|
|
|
428
427
|
const [code, what] = {
|
|
429
428
|
steered: [0, 'steered into its running turn'],
|
|
430
429
|
started: [0, 'it was idle, so the message started a turn'],
|
|
431
|
-
|
|
430
|
+
native_busy: [0, 'it will read it at its next tool call'],
|
|
431
|
+
native_waits: [EXIT_STORED, 'Stored: its relay could not deliver it and its session is busy, so its pane fallback waits unread until it is idle. Do not resend'],
|
|
432
432
|
native_idle: [0, 'it was idle, so the message starts its next turn'],
|
|
433
433
|
hooked: [0, 'queued in its mailbox; terminal delivery waits for native transcript confirmation'],
|
|
434
434
|
hook_woken: [0, 'it was idle, so its pane was told to read its mailbox; its hook shows it the message in that turn'],
|
|
@@ -441,12 +441,13 @@ export class MailSend {
|
|
|
441
441
|
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
442
|
}[delivered];
|
|
443
443
|
this.#recordSend(caller.address, address, messageId, sentIn, receiver.continuation);
|
|
444
|
+
const viaPane = terminal || (door.kind === 'native' && ['typed', 'pane_held', 'pane_waits'].includes(delivered));
|
|
444
445
|
const harness = parseAddress(address).harness;
|
|
445
446
|
const text = code === 0
|
|
446
447
|
? `sent to ${name} (${harness}, ${door.kind}): ${what}. Message id ${shortId(messageId)}.${receiver.alias ?? ''}${idleNote} Don't poll; carry on.`
|
|
447
448
|
: `${name} (${harness}): ${what}. Message id ${shortId(messageId)}.${idleNote}`;
|
|
448
|
-
if ((code === 0 || (
|
|
449
|
-
return outcome(code, text, code === 0 ||
|
|
449
|
+
if ((code === 0 || (viaPane && code === EXIT_STORED)) && id != null) { try { this.store.setSentMarker(caller.address, messageId, text); } catch { /* the marker is best kept */ } }
|
|
450
|
+
return outcome(code, text, code === 0 || viaPane ? this.#deliveryReceipt(address, messageId, viaPane) : null);
|
|
450
451
|
}
|
|
451
452
|
|
|
452
453
|
/** An agent's plan for `sent` (its thread store decides), or an outcome when the plan refused it. */
|
|
@@ -485,17 +486,20 @@ export class MailSend {
|
|
|
485
486
|
if (how === 'not_woken') { this.store.deliver(to, sent); delivered = 'queued'; }
|
|
486
487
|
else { this.store.deliverRead(to, handed); finalReply = answers; delivered = how === 'steered' ? 'steered' : 'started'; }
|
|
487
488
|
} else if (door.kind === 'native') {
|
|
489
|
+
// one its pane fallback already holds (typed, or queued for its composer) is never handed to the relay as well
|
|
490
|
+
const held = door.pane ? terminalReceipt(this.store, to, sent.id) : null;
|
|
491
|
+
if (held?.delivered) {
|
|
492
|
+
attempt?.begin('existing-native-delivery');
|
|
493
|
+
try { this.store.claimFor(to, sent.id, { pid: process.pid, via: 'door', caller: null, recipient: true, at_ms: Date.now() }); } catch { /* best kept */ }
|
|
494
|
+
const now = this.store.findIn(to, sent.id);
|
|
495
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
496
|
+
return 'already';
|
|
497
|
+
}
|
|
498
|
+
if (held?.queued) return 'pane_held';
|
|
488
499
|
const busy = door.session.status !== 'idle' || door.session.panes_current === false;
|
|
489
500
|
if (!wake && !busy) throw new Refused('queue');
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.store.deliver(to, sent);
|
|
493
|
-
this.store.requestWake(to, sent.id, { keep: true });
|
|
494
|
-
const state = this.store.wakeState(to, sent.id);
|
|
495
|
-
this.store.setWakeState(to, sent.id, { ...state, waiting_for_idle: true, next_at_ms: Date.now() + 300_000 });
|
|
496
|
-
if (notifyWhenIdle && !cameThroughLink(sent)) this.store.subscribeIdle(to, { message_id: sent.id, subscriber: sent.from, notice: true, final_reply: false });
|
|
497
|
-
return 'native_waits';
|
|
498
|
-
}
|
|
501
|
+
// a busy session is handed it at once, as an idle one is: Claude Code's own cross-session message surfaces at the
|
|
502
|
+
// receiver's next tool call (D391). Only the pane fallback below waits for idle, since a busy pane is never typed into
|
|
499
503
|
// its reply line names the door this session really has: its tools when it runs supercode's MCP server
|
|
500
504
|
const handed = sent.reply_via?.mode === 'command' && door.session.tools ? { ...sent, reply_via: { mode: 'tool' } } : sent;
|
|
501
505
|
const text = render(handed);
|
|
@@ -543,43 +547,43 @@ export class MailSend {
|
|
|
543
547
|
// gone, so its gate refuses a late send) and it is not in the receiver's transcript: a copy this delivery filed
|
|
544
548
|
// goes back unread, for the receiver's own mailbox read and the mailbox's wake, which hands it over again
|
|
545
549
|
const received = this.store.claimOf(to, handed.id)?.recipient === true;
|
|
546
|
-
|
|
550
|
+
// the relay produced no receipt and cannot send it late (known not made, or not in the receiver's transcript after
|
|
551
|
+
// its bound, its queue gone): a session with a daemon pane has the same copy typed there instead
|
|
552
|
+
if (!relayed.delivered && !received && wake && door.pane && (!relayed.unknown || relayed.landed === false) && busy) {
|
|
553
|
+
// its pane is typed into only once its turn is idle: the copy waits unread, its wake resumed when it goes idle
|
|
554
|
+
// (carrier.mjs), which hands it to the relay again first
|
|
555
|
+
this.log(`relay could not deliver ${shortId(handed.id)} to busy ${to} (${relayed.detail}); its pane fallback waits for idle`);
|
|
556
|
+
try { this.store.requestWake(to, handed.id, { keep: true }); } catch { /* the wake filed above stands */ }
|
|
557
|
+
const state = this.store.wakeState(to, handed.id);
|
|
558
|
+
this.store.setWakeState(to, handed.id, { ...state, waiting_for_idle: true, next_at_ms: Date.now() + 300_000 });
|
|
559
|
+
delivered = 'native_waits';
|
|
560
|
+
} else if (!relayed.delivered && !received && wake && door.pane && (!relayed.unknown || relayed.landed === false)) {
|
|
561
|
+
this.log(`relay could not deliver ${shortId(handed.id)} to ${to} (${relayed.detail}); typing it into its pane ${door.pane}`);
|
|
562
|
+
this.relaying.add(key);
|
|
563
|
+
try { delivered = await this.#typeIntoPane(handed, to, door.pane, wake, attempt); }
|
|
564
|
+
finally { this.relaying.delete(key); }
|
|
565
|
+
} else if (!relayed.delivered && relayed.unknown && !received) {
|
|
547
566
|
// kept unread, its wake due now: the carrier hands it over again after its backoff
|
|
548
567
|
if (filedHere && wake) { try { this.store.requestWake(to, handed.id); } catch { /* the wake filed above stands */ } }
|
|
549
568
|
throw Object.assign(new Error(relayed.detail), { unanswered: true });
|
|
550
569
|
}
|
|
551
|
-
if (!relayed.delivered && !received) { if (filedHere) { try { this.store.remove(to, filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
570
|
+
else if (!relayed.delivered && !received) { if (filedHere) { try { this.store.remove(to, filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
552
571
|
// relayed: a copy filed unread before this delivery (an earlier unanswered or queued one, held for the receiver's
|
|
553
572
|
// own read) is moved to cur/, so that read never hands it over a second time
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
573
|
+
else {
|
|
574
|
+
if (relayed.delivered && !received) {
|
|
575
|
+
const now = this.store.findIn(to, handed.id);
|
|
576
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
577
|
+
}
|
|
578
|
+
delivered = busy ? 'native_busy' : 'native_idle';
|
|
557
579
|
}
|
|
558
|
-
delivered = 'native_idle';
|
|
559
580
|
} else if (door.kind === 'hook') {
|
|
560
581
|
attempt?.begin('hook-mailbox');
|
|
561
582
|
this.store.deliver(to, sent);
|
|
562
583
|
if (wake) this.store.requestWake(to, sent.id);
|
|
563
584
|
delivered = 'hooked';
|
|
564
585
|
} 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
|
-
}
|
|
586
|
+
delivered = await this.#typeIntoPane(sent, to, door.pane, wake, attempt);
|
|
583
587
|
} else if (door.kind === 'stored') { attempt?.begin('stored-mailbox'); this.store.deliver(to, sent); delivered = 'stored'; }
|
|
584
588
|
else { attempt?.begin('operator-mailbox'); this.store.deliver(to, sent); delivered = 'operator'; }
|
|
585
589
|
// a hand-over to the recipient's own door is its delivery, recorded as such
|
|
@@ -592,6 +596,30 @@ export class MailSend {
|
|
|
592
596
|
return delivered;
|
|
593
597
|
}
|
|
594
598
|
|
|
599
|
+
/** Type `sent` into `pane` as its next turn, only into an empty composer (machine.mjs #paneTurn); otherwise it waits
|
|
600
|
+
* in its mailbox. Answers 'typed', 'pane_held', 'pane_waits' or 'queued'. */
|
|
601
|
+
async #typeIntoPane(sent, to, pane, wake, attempt) {
|
|
602
|
+
let typed = null;
|
|
603
|
+
attempt?.begin(wake ? 'terminal-input' : 'terminal-mailbox');
|
|
604
|
+
if (wake) { try { typed = await this.typeTurn(pane, parseAddress(to).harness, render(sent), [sent.id], to); } catch (error) { typed = { reason: error.message }; } }
|
|
605
|
+
if (typed?.delivered) {
|
|
606
|
+
this.store.deliverRead(to, sent);
|
|
607
|
+
const now = this.store.findIn(to, sent.id);
|
|
608
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
609
|
+
return 'typed';
|
|
610
|
+
}
|
|
611
|
+
// held by the daemon's queue for the pane, typed once its composer frees: not typed yet, so kept unread
|
|
612
|
+
if (typed?.queued) { this.store.deliver(to, sent); if (wake) this.store.requestWake(to, sent.id, { keep: true }); return 'pane_held'; }
|
|
613
|
+
// not typed (its composer held a draft or a turn ran): it waits unread with a wake, so the carrier tries it again once
|
|
614
|
+
// the composer is empty (mail/carrier.mjs), never left for a read a session with no hook never makes
|
|
615
|
+
this.store.deliver(to, sent);
|
|
616
|
+
if (!wake) return 'queued';
|
|
617
|
+
this.store.requestWake(to, sent.id, { keep: true });
|
|
618
|
+
const state = this.store.wakeState(to, sent.id);
|
|
619
|
+
this.store.setWakeState(to, sent.id, { ...state, next_at_ms: Date.now() + 2_000, last_error: paneWaitReason(typed) });
|
|
620
|
+
return 'pane_waits';
|
|
621
|
+
}
|
|
622
|
+
|
|
595
623
|
/**
|
|
596
624
|
* Deliver `sent` to every receiver `plan` names: a `to` receiver through its door (a stopped holder the mailbox
|
|
597
625
|
* launched is resumed first), a `cc` receiver filed unread and not woken (mail_send.rs `deliver_planned`).
|
|
@@ -719,7 +747,7 @@ export class MailSend {
|
|
|
719
747
|
if (error instanceof Refused) throw new Error(`${error.bytes} bytes; the limit is ${MAX_RELAYED_BYTES}`);
|
|
720
748
|
throw error;
|
|
721
749
|
}
|
|
722
|
-
return { how, text: { steered: 'steered into its running turn', started: 'started a turn', native_waits: 'waits unread until its native turn is idle', native_idle: 'starts its next turn', hooked: 'in its mailbox, its hook shows it', queued: 'waits in its mailbox', operator: 'filed', already: 'it already had it', stored: 'stored; it has no delivery door', typed: 'typed into its pane', pane_held: 'held for its pane until its composer is free', pane_waits: 'waits in its mailbox (its composer was not empty)' }[how] };
|
|
750
|
+
return { how, text: { steered: 'steered into its running turn', started: 'started a turn', native_busy: 'read at its next tool call', native_waits: 'waits unread until its native turn is idle', native_idle: 'starts its next turn', hooked: 'in its mailbox, its hook shows it', queued: 'waits in its mailbox', operator: 'filed', already: 'it already had it', stored: 'stored; it has no delivery door', typed: 'typed into its pane', pane_held: 'held for its pane until its composer is free', pane_waits: 'waits in its mailbox (its composer was not empty)' }[how] };
|
|
723
751
|
}
|
|
724
752
|
|
|
725
753
|
// ---- a filing --------------------------------------------------------------------------------------------------
|
|
@@ -822,7 +850,7 @@ export class MailSend {
|
|
|
822
850
|
return {
|
|
823
851
|
delivered_to_bus: door.kind !== 'stored' && !['pane_waits', 'pane_held', 'native_waits'].includes(how), message_id: sent.id, reply_to: sender,
|
|
824
852
|
target: { harness, session_id: sessionId, name: door.kind === 'native' ? door.session.name.split('@')[0] : null },
|
|
825
|
-
delivery: { door: door.kind, how: { steered: 'steered', started: 'started', native_waits: 'waiting_for_idle', native_idle: 'started', hooked: 'hook', queued: 'queued', stored: 'stored', operator: 'filed', already: 'already', typed: 'typed', pane_held: 'held', pane_waits: 'stored' }[how] },
|
|
853
|
+
delivery: { door: door.kind, how: { steered: 'steered', started: 'started', native_busy: 'next_tool_call', native_waits: 'waiting_for_idle', native_idle: 'started', hooked: 'hook', queued: 'queued', stored: 'stored', operator: 'filed', already: 'already', typed: 'typed', pane_held: 'held', pane_waits: 'stored' }[how] },
|
|
826
854
|
};
|
|
827
855
|
}
|
|
828
856
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.197",
|
|
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",
|