@volter/supercode-teams 0.3.125 → 0.3.127
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/bin/teams.mjs +8 -2
- package/codex-startup.mjs +10 -1
- package/machine.mjs +28 -8
- package/mail/relay.mjs +12 -0
- package/mail/send.mjs +65 -14
- package/package.json +1 -1
package/bin/teams.mjs
CHANGED
|
@@ -55,7 +55,7 @@ import { APP_DOOR_PATH, appDoorFile, appDoorLink, normalOrigin, readAppDoor, rea
|
|
|
55
55
|
|
|
56
56
|
const MACHINE_VERBS = new Set(['machine', 'slow', 'describe', 'records', 'panes', 'input', 'suspend', 'resume', 'files', 'ports', 'port', 'browser', 'stream', 'jobs', 'workspace', 'title', 'attention', 'watch', 'rpc', 'launches', 'log', 'mail', 'board', 'health', 'app-door']);
|
|
57
57
|
// Every flag a machine verb reads; any other is refused rather than silently ignored.
|
|
58
|
-
const KNOWN_FLAGS = new Set(['home', 'since', 'machine', 'context', 'floor', 'supercode', 'tmux-socket', 'verbose', 'lines', 'input', 'name', 'for', 'urgency', 'reason', 'interrupt', 'query', 'out', 'in', 'interval', 'no-enter', 'when-composer-empty', 'key', 'listen', 'list', 'from', 'to', 'path', 'to-path', 'repo', 'branch', 'solo', 'card', 'create', 'presence-only', 'processes', 'panes', 'clear-draft', 'message-ids', 'harness', 'observe', 'attach-context', 'json', 'all', 'origin', 'port', 'remove-origin', 'open']);
|
|
58
|
+
const KNOWN_FLAGS = new Set(['home', 'since', 'machine', 'context', 'floor', 'supercode', 'tmux-socket', 'verbose', 'lines', 'input', 'name', 'for', 'urgency', 'reason', 'interrupt', 'query', 'out', 'in', 'interval', 'no-enter', 'when-composer-empty', 'key', 'listen', 'list', 'from', 'to', 'path', 'to-path', 'repo', 'branch', 'solo', 'card', 'create', 'presence-only', 'processes', 'panes', 'clear-draft', 'message-ids', 'harness', 'observe', 'attach-context', 'json', 'all', 'origin', 'port', 'remove-origin', 'open', 'foreign']);
|
|
59
59
|
|
|
60
60
|
const argv = process.argv.slice(2);
|
|
61
61
|
const flags = {};
|
|
@@ -247,7 +247,13 @@ async function main() {
|
|
|
247
247
|
flags.processes ? 'panes.processes' : flags['presence-only'] ? 'panes.present' : 'panes.list',
|
|
248
248
|
flags['presence-only'] && typeof flags.panes === 'string' ? { panes: flags.panes.split(',').filter(Boolean) } : {})));
|
|
249
249
|
if (sub === 'capture') return withNode(async (rpc) => out((await rpc.call('panes.capture', { pane: rest[0], lines: Number(flags.lines ?? 200) })).text));
|
|
250
|
-
|
|
250
|
+
// One pane, named: `--foreign` closes a pane another session started, and is said for that pane alone.
|
|
251
|
+
if (sub === 'close') {
|
|
252
|
+
// the flag parser takes `--foreign p_…` as the flag's value: that pane is the one named
|
|
253
|
+
const named = typeof flags.foreign === 'string' ? [flags.foreign, ...rest] : rest;
|
|
254
|
+
if (named.length !== 1) die('panes close <pane> [--foreign]: one pane per close');
|
|
255
|
+
return withNode(async (rpc) => out(await rpc.call('panes.close', { pane: named[0], foreign: flags.foreign !== undefined, ...(await acting()) })));
|
|
256
|
+
}
|
|
251
257
|
// `window <pane> [machine]`: a terminal window on this machine's screen showing the pane, this machine's or, named,
|
|
252
258
|
// another machine's (attached through Teams, in that machine's Teams context `--attach-context`); `--observe` shows it
|
|
253
259
|
// read-only.
|
package/codex-startup.mjs
CHANGED
|
@@ -42,7 +42,16 @@ export function codexStartupArguments(args = []) {
|
|
|
42
42
|
}
|
|
43
43
|
normalized.push(argument);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
// Codex 0.160's `resume` does not take this override from before the subcommand: a resumed TUI with it there still
|
|
46
|
+
// stopped on "Update available" once another Codex had cached a newer version, and `codex resume <id> -c …` did not
|
|
47
|
+
// (yuerans, 2026-10-06). It goes after the subcommand and its session id (readers of the process take the word after
|
|
48
|
+
// `resume` as the id: codex_peer.rs `resumed_session_in`), in front when there is no subcommand. `fork` is placed the
|
|
49
|
+
// same way by extension; only `resume` was measured.
|
|
50
|
+
const subcommand = normalized.findIndex((argument, index) => ['resume', 'fork'].includes(argument)
|
|
51
|
+
&& !['-c', '--config'].includes(normalized[index - 1]) && !normalized.slice(0, index).includes('--'));
|
|
52
|
+
const id = subcommand >= 0 && normalized[subcommand + 1] !== undefined && normalized[subcommand + 1] !== '--' && !normalized[subcommand + 1].startsWith('-');
|
|
53
|
+
const at = subcommand < 0 ? 0 : subcommand + (id ? 2 : 1);
|
|
54
|
+
return [...normalized.slice(0, at), '-c', 'check_for_update_on_startup=false', ...normalized.slice(at)];
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export function codexUpdatePrompt(screen) {
|
package/machine.mjs
CHANGED
|
@@ -2079,16 +2079,22 @@ export class TeamsMachine extends MachineCore {
|
|
|
2079
2079
|
}
|
|
2080
2080
|
|
|
2081
2081
|
#refuseForeign(pane, caller) {
|
|
2082
|
-
|
|
2082
|
+
const starter = this.#foreignStarter(pane, caller);
|
|
2083
|
+
if (starter) throw Object.assign(new Error(`pane ${pane} ${starter}, not by ${caller}: a session acts only on the panes it started. Nothing was done; reach that session through its own doors (supercode message send)`), { code: 403 });
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/** Who started `pane`, said, when `caller` is a session that did not start it; null when the caller may act on it (a
|
|
2087
|
+
* person, a board, its starter). */
|
|
2088
|
+
#foreignStarter(pane, caller) {
|
|
2089
|
+
if (typeof caller !== 'string' || !caller || /^sc:[^:]+:board:/.test(caller)) return null;
|
|
2083
2090
|
const launch = this.launches.all().find((entry) => entry.pane === pane);
|
|
2084
2091
|
const by = launch?.by ?? null;
|
|
2085
|
-
if (by === caller) return;
|
|
2092
|
+
if (by === caller) return null;
|
|
2086
2093
|
// A pane that resumes a session is that session's as well: its first starter acts on it as before the resume
|
|
2087
2094
|
// (t_09a795fd item 18). The starter is the oldest harness launch whose own transcript confirmed it started that
|
|
2088
2095
|
// session, never a launch row's claim (any caller can launch a program with --session-id S).
|
|
2089
|
-
if (launch?.resumes && this.#sessionOrigin(launch.resumes)?.by === caller) return;
|
|
2090
|
-
|
|
2091
|
-
throw Object.assign(new Error(`pane ${pane} ${starter}, not by ${caller}: a session acts only on the panes it started. Nothing was done; reach that session through its own doors (supercode message send)`), { code: 403 });
|
|
2096
|
+
if (launch?.resumes && this.#sessionOrigin(launch.resumes)?.by === caller) return null;
|
|
2097
|
+
return by ? `was started by ${by}` : 'was not started by a session (a person or an earlier daemon started it)';
|
|
2092
2098
|
}
|
|
2093
2099
|
|
|
2094
2100
|
/** The launch that started native Claude session `session`: the oldest journal row of a Claude launch whose own
|
|
@@ -3307,7 +3313,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
3307
3313
|
}
|
|
3308
3314
|
const contextKey = params.pane ?? `p_${randomBytes(9).toString('base64url')}`;
|
|
3309
3315
|
// Who started the pane: the launching session's (or board's) address, null for a person. A session's later
|
|
3310
|
-
// input on the pane is refused unless it is this one (#refuseForeign); close
|
|
3316
|
+
// input on the pane is refused unless it is this one (#refuseForeign); its close needs `foreign` (panes.close).
|
|
3311
3317
|
// Who to tell when the pane ends (`open --new … --notify-when-ended`): a session's mail address, and this
|
|
3312
3318
|
// machine's name as the address a notice from its pane comes from.
|
|
3313
3319
|
if (params.notify !== undefined && !(params.notify && typeof params.notify.to === 'string' && /^sc:[^:\s]+:[^:\s]+:\S+$/.test(params.notify.to) && typeof params.notify.machine === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(params.notify.machine))) throw Object.assign(new Error('launch notify is { to: <sc: address>, machine: <this machine\'s mail name> }'), { code: 400 });
|
|
@@ -3459,14 +3465,28 @@ export class TeamsMachine extends MachineCore {
|
|
|
3459
3465
|
case 'harness.v1.panes.close':
|
|
3460
3466
|
case 'panes.close': {
|
|
3461
3467
|
need('control', `pane:${params.pane}`);
|
|
3462
|
-
|
|
3468
|
+
// A session closes the panes it started and the one it runs in. Another's pane is closed only when the caller
|
|
3469
|
+
// says so (`foreign`), one pane at a time, by name: a listing piped into close is refused pane by pane rather
|
|
3470
|
+
// than ending every session on the machine (docs/adr/0004-pane-close-authority.md).
|
|
3471
|
+
const starting = [...this.pendingStartups.values()].filter((pending) => pending.pane === params.pane);
|
|
3472
|
+
// only a pane here (open, or still starting) is anyone's: one this machine lacks is answered as absent, before
|
|
3473
|
+
// the caller is asked for
|
|
3474
|
+
const here = starting.length > 0 || Boolean(await this.#paneByContext(params.pane));
|
|
3475
|
+
const closeBy = here ? await this.#callerOf(entry, params) : null;
|
|
3476
|
+
const starter = params.foreign === true || !here ? null : this.#foreignStarter(params.pane, closeBy);
|
|
3477
|
+
// the pane the caller runs in: a Claude row names it as its tmux locator (`p_…:@window.%pane`), the others as the pane
|
|
3478
|
+
if (starter && !this.live.sessions().some((live) => live.address === closeBy && String(live.pane ?? '').split(':')[0] === params.pane)) {
|
|
3479
|
+
act('close', `pane:${params.pane}`, 'refused', `foreign: ${starter}`);
|
|
3480
|
+
throw Object.assign(new Error(`pane ${params.pane} ${starter}, not by ${closeBy}. Nothing was closed. A session closes the panes it started; closing another's is deliberate: read what the pane holds, settle it with whoever started it, then close that one pane with --foreign`), { code: 403 });
|
|
3481
|
+
}
|
|
3482
|
+
for (const pending of starting) pending.abort.abort();
|
|
3463
3483
|
const session = await this.#paneByContext(params.pane);
|
|
3464
3484
|
if (!session) {
|
|
3465
3485
|
act('close', `pane:${params.pane}`, 'ok', 'already absent');
|
|
3466
3486
|
return { closed: params.pane, alreadyAbsent: true };
|
|
3467
3487
|
}
|
|
3468
3488
|
await this.host.closeSession(session.id);
|
|
3469
|
-
act('close', `pane:${params.pane}`);
|
|
3489
|
+
act('close', `pane:${params.pane}`, 'ok', params.foreign === true ? `foreign, by ${closeBy ?? 'a person'}` : null);
|
|
3470
3490
|
this.#panesChanged();
|
|
3471
3491
|
return { closed: params.pane };
|
|
3472
3492
|
}
|
package/mail/relay.mjs
CHANGED
|
@@ -371,6 +371,18 @@ export class Relays {
|
|
|
371
371
|
* (claude_relay.rs `send_through_relay`): the receiver is named by its own socket (`uds:<path>`), unique to it, never
|
|
372
372
|
* by its name. One send at a time per relay; a relay whose process is proven dead is started again once.
|
|
373
373
|
*/
|
|
374
|
+
/** Whether `sender`'s relay still holds a send queued (its queue file, written within SEND_TIMEOUT_MS twice over): a
|
|
375
|
+
* relay runtime outlives a daemon that ended mid-send and may still make it. */
|
|
376
|
+
sending(sender) {
|
|
377
|
+
try { return Date.now() - statSync(this.spec(sender).queue).mtimeMs < 2 * SEND_TIMEOUT_MS; } catch { return false; }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Whether message `messageId` from `sender` landed in Claude session `sessionId`'s transcript, as a relay send reads
|
|
381
|
+
* an unconfirmed outcome (send.mjs handOver asks it of a hand-over the daemon did not live to settle). */
|
|
382
|
+
async landed(sessionId, messageId, sender) {
|
|
383
|
+
return this.arrived(sessionId, `cross-session-message id=\\"${shortId(messageId)}\\" from=\\"${sender}\\"`);
|
|
384
|
+
}
|
|
385
|
+
|
|
374
386
|
async send(sender, senderName, receiver, message, messageId) {
|
|
375
387
|
const spec = this.spec(sender, senderName);
|
|
376
388
|
const asked = performance.now();
|
package/mail/send.mjs
CHANGED
|
@@ -25,6 +25,12 @@ export function unansweredText(where, detail, id) {
|
|
|
25
25
|
|
|
26
26
|
/** How long a send waits, in a daemon's first moments, for its Codex sessions to be known before it resolves a receiver. */
|
|
27
27
|
const CODEX_KNOWN_WAIT_MS = 20_000;
|
|
28
|
+
// A relayed message's wake is due this long after its relay send begins: a send still running here holds it (handOver),
|
|
29
|
+
// so it fires only for a send the daemon did not live to finish (t_b0f9a2a0)
|
|
30
|
+
const RELAY_GRACE_MS = 30_000;
|
|
31
|
+
/** The kinds of delivery that hand nothing over (#deliverWoken's `how`): the copy waits filed. An operator's mailbox,
|
|
32
|
+
* which its operator reads, and a session's hook, which shows it, are deliveries. */
|
|
33
|
+
const LEFT_FILED = new Set(['queued', 'stored', 'pane_held', 'pane_waits', 'not_running']);
|
|
28
34
|
|
|
29
35
|
/** Replies in one unbroken chain between two sessions beyond which another is refused. */
|
|
30
36
|
const REPLY_CHAIN_LIMIT = 32;
|
|
@@ -53,6 +59,7 @@ export class MailSend {
|
|
|
53
59
|
constructor({ machine, mailRoot, live, store, agents, relays, askMachine, deliverRuntime, codexAnswer, typeTurn, log = () => {} }) {
|
|
54
60
|
Object.assign(this, { machine, mailRoot, live, store, agents, relays, askMachine, deliverRuntime, codexAnswer, typeTurn, log });
|
|
55
61
|
this.handing = new Set(); // `<address>\n<id>` of each hand-over running here (handOver)
|
|
62
|
+
this.relaying = new Set(); // `<address>\n<id>` of each relay send running here (route): its wake waits for it
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
// ---- one send ----------------------------------------------------------------------------------------------------
|
|
@@ -319,11 +326,21 @@ export class MailSend {
|
|
|
319
326
|
const handed = sent.reply_via?.mode === 'command' && door.session.tools ? { ...sent, reply_via: { mode: 'tool' } } : sent;
|
|
320
327
|
const text = render(handed);
|
|
321
328
|
if (Buffer.byteLength(text) > MAX_RELAYED_BYTES) throw new Refused('too_long', Buffer.byteLength(text));
|
|
322
|
-
// filed before it is handed over
|
|
323
|
-
//
|
|
329
|
+
// filed before it is handed over, unread, with a wake due RELAY_GRACE_MS from now: a session that reads its mailbox
|
|
330
|
+
// at its first tool call finds it, and a daemon that ends before the relay answers (an upgrade's restart, a crash)
|
|
331
|
+
// leaves it for the carrier, which hands it over again (t_b0f9a2a0: filed read before, such a send was lost). It is
|
|
332
|
+
// read once the relay has made it; a relay that fails takes back the copy this delivery filed
|
|
324
333
|
const filedHere = this.store.findIn(to, handed.id) == null;
|
|
325
|
-
const filed = this.store.
|
|
326
|
-
|
|
334
|
+
const filed = this.store.deliver(to, handed);
|
|
335
|
+
if (wake && !this.store.hasWake(to, handed.id)) {
|
|
336
|
+
// its hand-over begins now: a daemon that restarts reads the receiver's transcript before sending it again
|
|
337
|
+
try { this.store.requestWake(to, handed.id); this.store.setWakeState(to, handed.id, { attempts: 0, next_at_ms: Date.now() + RELAY_GRACE_MS, handover_at_ms: Date.now() }); } catch { /* the relay's own outcome stands */ }
|
|
338
|
+
}
|
|
339
|
+
const key = `${to}\n${handed.id}`;
|
|
340
|
+
this.relaying.add(key);
|
|
341
|
+
let relayed;
|
|
342
|
+
try { relayed = await this.relays.send(handed.from, handed.from_name, { sessionId: door.session.address.split(':').slice(3).join(':'), name: door.session.name.split('@')[0], socket: door.session.socket }, text, handed.id); }
|
|
343
|
+
finally { this.relaying.delete(key); }
|
|
327
344
|
// a send the relay may have made keeps its copy and is unanswered, never taken back and said not sent; only one the
|
|
328
345
|
// relay is known not to have made (refused, never handed in) takes its copy back. One its receiver read from its
|
|
329
346
|
// mailbox meanwhile (its recipient claim) has reached it. Otherwise the relay can no longer make it (its queue is
|
|
@@ -331,7 +348,8 @@ export class MailSend {
|
|
|
331
348
|
// goes back unread, for the receiver's own mailbox read and the mailbox's wake, which hands it over again
|
|
332
349
|
const received = this.store.claimOf(to, handed.id)?.recipient === true;
|
|
333
350
|
if (!relayed.delivered && relayed.unknown && !received) {
|
|
334
|
-
|
|
351
|
+
// kept unread, its wake due now: the carrier hands it over again after its backoff
|
|
352
|
+
if (filedHere && wake) { try { this.store.requestWake(to, handed.id); } catch { /* the wake filed above stands */ } }
|
|
335
353
|
throw Object.assign(new Error(relayed.detail), { unanswered: true });
|
|
336
354
|
}
|
|
337
355
|
if (!relayed.delivered && !received) { if (filedHere) { try { unlinkSync(filed); } catch { /* gone */ } } throw new Error(relayed.detail); }
|
|
@@ -378,10 +396,19 @@ export class MailSend {
|
|
|
378
396
|
const reached = [], copied = [], failed = [];
|
|
379
397
|
let recorded = false;
|
|
380
398
|
const held = []; // recipients whose own copy is held unread with its wake (an unanswered relay)
|
|
399
|
+
let agentKey = null;
|
|
381
400
|
if (plan.agent) {
|
|
382
|
-
// the agent's own mailbox keeps the record of what was addressed to it
|
|
383
|
-
|
|
401
|
+
// the agent's own mailbox keeps the record of what was addressed to it: unread, with a wake due RELAY_GRACE_MS on,
|
|
402
|
+
// until a recipient has it, so a daemon that ends while the plan runs (a resume, a relay send, another machine
|
|
403
|
+
// asked) leaves it for the carrier to plan again (#1264 review P1). While this plan runs its wake waits for it
|
|
404
|
+
try {
|
|
405
|
+
const fresh = this.store.findIn(plan.agent, sent.id) == null;
|
|
406
|
+
this.store.deliver(plan.agent, sent); recorded = true;
|
|
407
|
+
if (fresh && !this.store.hasWake(plan.agent, sent.id)) { this.store.requestWake(plan.agent, sent.id); this.store.setWakeState(plan.agent, sent.id, { attempts: 0, next_at_ms: Date.now() + RELAY_GRACE_MS }); }
|
|
408
|
+
agentKey = `${plan.agent}\n${sent.id}`; this.relaying.add(agentKey);
|
|
409
|
+
} catch (error) { failed.push(`the agent's mailbox (${error.message})`); }
|
|
384
410
|
}
|
|
411
|
+
try {
|
|
385
412
|
const sessions = this.live.sessions();
|
|
386
413
|
for (const recipient of plan.recipients.filter((item) => item.address !== caller.address)) {
|
|
387
414
|
const label = this.#label(sessions, recipient.address);
|
|
@@ -390,7 +417,16 @@ export class MailSend {
|
|
|
390
417
|
try { this.#fileUnread(recipient.address, sent); copied.push(label); } catch (error) { failed.push(`${label} (${error.message})`); }
|
|
391
418
|
continue;
|
|
392
419
|
}
|
|
393
|
-
try {
|
|
420
|
+
try {
|
|
421
|
+
const { how, text: said } = await this.#deliverWoken(sent, recipient.address, sessions, queue, notify_when_idle);
|
|
422
|
+
// only a hand-over is a reach: a copy left filed with nothing to carry it (queued, stored, held in the daemon's
|
|
423
|
+
// memory for a pane, or for a session not running) is held when its own wake carries it, and otherwise the
|
|
424
|
+
// agent's copy keeps its retry (t_b0f9a2a0: such a send was counted reached, and nothing carried it). One the
|
|
425
|
+
// sender asked to queue (--queue) is held as asked: its receiver reads it at its next turn
|
|
426
|
+
if (!LEFT_FILED.has(how)) reached.push(`${label} (${said})`);
|
|
427
|
+
else if (this.store.hasWake(recipient.address, sent.id) || (queue && how === 'queued')) held.push(`${label} (${said})`);
|
|
428
|
+
else failed.push(`${label} (${said}; handed over again when it can be)`);
|
|
429
|
+
}
|
|
394
430
|
catch (error) {
|
|
395
431
|
// held only when its copy is here, unread: its wake hands it over, or (queued, no wake) the receiver's own read
|
|
396
432
|
// does. An unanswered request to another machine's door holds nothing here, so it is a failure, and the agent's
|
|
@@ -400,6 +436,7 @@ export class MailSend {
|
|
|
400
436
|
else failed.push(error.unanswered ? `${label} (unanswered, whether it arrived is unknown: ${error.message})` : `${label} (${error.message})`);
|
|
401
437
|
}
|
|
402
438
|
}
|
|
439
|
+
} finally { if (agentKey) this.relaying.delete(agentKey); }
|
|
403
440
|
const thread = plan.thread ? ` Thread ${shortId(plan.thread.id)}.` : '';
|
|
404
441
|
// agent mail that reached nobody is never left only as a read record: its copy in the agent's mailbox goes back
|
|
405
442
|
// unread with a wake, and the carrier hands it over again (handOver plans it anew) after its backoff, until a
|
|
@@ -450,18 +487,18 @@ export class MailSend {
|
|
|
450
487
|
if (cameThroughLink(sent)) throw new Error(`not carried on to ${door.machine}: a message that came to this machine through a link is for its own sessions only`);
|
|
451
488
|
const answer = await this.askMachine(door.machine, { op: 'file', to, envelope: sent });
|
|
452
489
|
if (answer?.code !== 0) throw new Error(typeof answer?.text === 'string' ? answer.text : 'the other machine refused the message');
|
|
453
|
-
return 'filed on its machine';
|
|
490
|
+
return { how: 'other_machine', text: 'filed on its machine' };
|
|
454
491
|
}
|
|
455
492
|
if (!door) {
|
|
456
493
|
// a reply to a closed session resumes it; only one the mailbox launched, the way it was launched
|
|
457
494
|
this.store.deliver(to, sent);
|
|
458
|
-
if (!(await this.agents.resumable(to))) return 'stopped; not resumed, since the mailbox did not launch it: the message waits in its mailbox';
|
|
495
|
+
if (!(await this.agents.resumable(to))) return { how: 'not_running', text: 'stopped; not resumed, since the mailbox did not launch it: the message waits in its mailbox' };
|
|
459
496
|
try { await this.agents.resume(to); } catch (error) { throw new Error(`not resumed: ${error.message}`); }
|
|
460
497
|
const started = Date.now();
|
|
461
498
|
for (;;) {
|
|
462
499
|
door = this.#door(to, this.live.sessions());
|
|
463
500
|
if (door) break;
|
|
464
|
-
if (Date.now() - started > RESUME_WAIT_MS) return 'resumed; the message waits in its mailbox';
|
|
501
|
+
if (Date.now() - started > RESUME_WAIT_MS) return { how: 'not_running', text: 'resumed; the message waits in its mailbox' };
|
|
465
502
|
await sleep(1000);
|
|
466
503
|
}
|
|
467
504
|
}
|
|
@@ -472,7 +509,7 @@ export class MailSend {
|
|
|
472
509
|
if (error instanceof Refused) throw new Error(`${error.bytes} bytes; the limit is ${MAX_RELAYED_BYTES}`);
|
|
473
510
|
throw error;
|
|
474
511
|
}
|
|
475
|
-
return { steered: 'steered into its running turn', started: 'started a turn', native_busy: 'read at its next tool call', 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];
|
|
512
|
+
return { how, text: { steered: 'steered into its running turn', started: 'started a turn', native_busy: 'read at its next tool call', 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] };
|
|
476
513
|
}
|
|
477
514
|
|
|
478
515
|
// ---- a filing --------------------------------------------------------------------------------------------------
|
|
@@ -724,6 +761,8 @@ export class MailSend {
|
|
|
724
761
|
this.store.markRead(address, found.path);
|
|
725
762
|
return { outcome: 'filed', detail: 'already handed over' };
|
|
726
763
|
}
|
|
764
|
+
// a plan or a relay send still running here holds its wake (#1264 review P1)
|
|
765
|
+
if (this.relaying.has(`${address}\n${id}`)) return { outcome: 'later', detail: 'its delivery is still in progress' };
|
|
727
766
|
const parsed = parseAddress(address);
|
|
728
767
|
if (parsed?.harness === AGENT_HARNESS) {
|
|
729
768
|
const plan = await this.#plan(sent, address, {});
|
|
@@ -743,8 +782,20 @@ export class MailSend {
|
|
|
743
782
|
// one still running here (its carrier gave up on its answer, past its bound) is waited for: the wake stays and is
|
|
744
783
|
// tried again after its backoff, settled by that hand-over's own outcome
|
|
745
784
|
const key = `${address}\n${id}`;
|
|
746
|
-
if (this.handing.has(key)) return { outcome: 'later', detail: 'its hand-over is still in progress' };
|
|
747
|
-
|
|
785
|
+
if (this.handing.has(key) || this.relaying.has(key)) return { outcome: 'later', detail: 'its hand-over is still in progress' };
|
|
786
|
+
// an earlier hand-over never settled (the daemon ended mid-way): one its receiver's transcript holds landed, and is
|
|
787
|
+
// read; any other is handed over again, never given up (t_b0f9a2a0)
|
|
788
|
+
if (state.handover_at_ms != null && door.kind === 'native') {
|
|
789
|
+
// a relay that outlived its daemon may still be making that send (its queue still there, fresh): waited for, never
|
|
790
|
+
// sent beside it (#1264 review P2a)
|
|
791
|
+
if (this.relays.sending?.(sent.from, id)) return { outcome: 'later', detail: 'its relay may still be making it' };
|
|
792
|
+
let landed = false;
|
|
793
|
+
try { landed = await this.relays.landed(door.session.address.split(':').slice(3).join(':'), id, sent.from); } catch { /* unread: hand it over */ }
|
|
794
|
+
if (landed) {
|
|
795
|
+
try { this.store.claimFor(address, id, { pid: process.pid, via: 'door', caller: null, recipient: true, at_ms: Date.now() }); this.store.markRead(address, found.path); } catch { /* moved meanwhile */ }
|
|
796
|
+
return { outcome: 'filed', detail: 'an earlier hand-over landed' };
|
|
797
|
+
}
|
|
798
|
+
}
|
|
748
799
|
this.store.setWakeState(address, id, { ...state, handover_at_ms: Date.now() });
|
|
749
800
|
this.handing.add(key);
|
|
750
801
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.127",
|
|
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",
|