@volter/supercode-teams 0.3.91 → 0.3.92
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/connector/index.mjs +24 -13
- package/live-sessions.mjs +44 -10
- package/machine.mjs +3 -1
- package/mail/send.mjs +18 -3
- package/package.json +1 -1
package/connector/index.mjs
CHANGED
|
@@ -19,8 +19,22 @@ import { NativeEffects } from './native-effects.mjs';
|
|
|
19
19
|
import { consumeAgentClosures } from './agent-closures.mjs';
|
|
20
20
|
|
|
21
21
|
const SYNC_PROBLEM_LIMIT=20;
|
|
22
|
-
// How
|
|
23
|
-
const
|
|
22
|
+
// How long after a session is first seen a change has its launch agent read again (a launch record written late).
|
|
23
|
+
const LAUNCH_AGENT_WINDOW_MS=120000;
|
|
24
|
+
/**
|
|
25
|
+
* The agent variables a session's launch recorded in its harness home (`<home>/supercode/launches/<session id>.json`,
|
|
26
|
+
* crates/harness/src/launch_agent.rs `read`), read here with no request: a Codex rollout's home is the folder above its
|
|
27
|
+
* `sessions/`, a Claude transcript's the folder above its `projects/`. Null when its launch named no agent.
|
|
28
|
+
*/
|
|
29
|
+
export function readLaunchAgent(locator){
|
|
30
|
+
const id=locator?.session_id,path=locator?.storage?.kind==='file'?locator.storage.path:null;
|
|
31
|
+
const store={codex:'sessions','claude-code':'projects'}[locator?.harness];
|
|
32
|
+
if(typeof path!=='string'||!store||typeof id!=='string'||!/^[A-Za-z0-9_-]+$/.test(id))return null;
|
|
33
|
+
let home=null;for(let dir=dirname(path);dirname(dir)!==dir;dir=dirname(dir))if(basename(dir)===store){home=dirname(dir);break;}
|
|
34
|
+
if(!home)return null;
|
|
35
|
+
try{const environment=JSON.parse(readFileSync(join(home,'supercode','launches',`${id}.json`),'utf8'))?.environment;return environment&&typeof environment==='object'&&!Array.isArray(environment)?environment:null;}
|
|
36
|
+
catch{return null;}
|
|
37
|
+
}
|
|
24
38
|
const CAPACITY_CODE='SUBSCRIPTION_OPEN_CAPACITY';
|
|
25
39
|
// Whether a root covers a session's folder: the root's own, or (unless the root is exact-only) one under it, by real path.
|
|
26
40
|
const realOr=path=>{try{return realpathSync(path);}catch{return path;}};
|
|
@@ -640,12 +654,11 @@ export class TeamsConnector {
|
|
|
640
654
|
// the binding is written only while this work is still the session's (checked again after its awaits)
|
|
641
655
|
const bindingRead=this.clock.nowMs();
|
|
642
656
|
try{await this.nativeEffects.discover(descriptor,ruleId,()=>this.sessionLive(key,ruleId,epoch)&&!((this.runtimeEnded?.get(key)??-Infinity)>=bindingRead));}catch(error){this.lastError=error;}
|
|
643
|
-
// The agent this session's launch named, read when first seen here, and
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if(captured&&!this.state.agent_sessions?.[key]&&asked<LAUNCH_AGENT_ASKS&&(asked===0||changed)){(this.launchChecked??=new Map()).set(key,asked+1);await this.noteLaunchAgent(locator,()=>this.sessionLive(key,ruleId,epoch));}
|
|
657
|
+
// The agent this session's launch named, read (in this process, one file) when first seen here, and on a change in
|
|
658
|
+
// the LAUNCH_AGENT_WINDOW_MS after (a launch record written late), until queued for binding. A launch's agent is
|
|
659
|
+
// fixed at its launch: read on every change it was a call into harness serve's loop per transcript write.
|
|
660
|
+
const firstSeen=this.launchChecked?.get(key),now=this.clock.nowMs();
|
|
661
|
+
if(captured&&!this.state.agent_sessions?.[key]&&(firstSeen===undefined||(changed&&now-firstSeen<LAUNCH_AGENT_WINDOW_MS))){if(firstSeen===undefined)(this.launchChecked??=new Map()).set(key,now);this.noteLaunchAgent(locator,()=>this.sessionLive(key,ruleId,epoch));}
|
|
649
662
|
// A session seen with its native bundle still owed (from before this start) waits for its file to go quiet; one
|
|
650
663
|
// captured now has its wait armed by the commit that records it (flushOutbox).
|
|
651
664
|
if(!changed)this.armNativeIdle(key);
|
|
@@ -1215,11 +1228,9 @@ export class TeamsConnector {
|
|
|
1215
1228
|
if(error.status!==409&&error.status!==404)throw error;
|
|
1216
1229
|
}
|
|
1217
1230
|
}
|
|
1218
|
-
/** The agent a session's launch named, as its home recorded it (
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
let environment;
|
|
1222
|
-
try{environment=await this.daemon.launchAgent(locator);}catch(error){this.lastError=error;return;}
|
|
1231
|
+
/** The agent a session's launch named, as its home recorded it (read here, readLaunchAgent): queued for binding. */
|
|
1232
|
+
noteLaunchAgent(locator,live=()=>true){
|
|
1233
|
+
const environment=readLaunchAgent(locator);
|
|
1223
1234
|
if(!live())return;
|
|
1224
1235
|
if(environment?.SUPERCODE_AGENT_ID)this.queueAgentSession(`${locator.harness}:${locator.session_id}`,environment);
|
|
1225
1236
|
}
|
package/live-sessions.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// - Messaging tools: the marker a `message mcp` server writes for its session process, with the server alive.
|
|
15
15
|
import { EventEmitter } from 'node:events';
|
|
16
16
|
import { execFile } from 'node:child_process';
|
|
17
|
-
import { mkdirSync, readFileSync, readdirSync, renameSync, watch, writeFileSync } from 'node:fs';
|
|
17
|
+
import { mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, watch, writeFileSync } from 'node:fs';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { FrontendClient } from '@volter/supercode-frontend';
|
|
@@ -30,6 +30,9 @@ const HISTORY_KEPT = 64;
|
|
|
30
30
|
/** The argument a Codex hook file carries when supercode's mail hook is installed. */
|
|
31
31
|
const CODEX_HOOK = 'message hook codex';
|
|
32
32
|
|
|
33
|
+
/** How long the runtime probes of one claim may take in all (a host stalled by load answers late, not never). */
|
|
34
|
+
const CLAIM_PROBE_MS = 10_000;
|
|
35
|
+
|
|
33
36
|
const alive = (pid) => {
|
|
34
37
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
35
38
|
try { process.kill(pid, 0); return true; } catch (error) { return error.code === 'EPERM'; }
|
|
@@ -50,6 +53,9 @@ function linuxStart(pid) {
|
|
|
50
53
|
} catch { return null; }
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
/** `path` with its symlinks resolved, or null when it cannot be (gone, or not a string). */
|
|
57
|
+
function realPath(path) { try { return typeof path === 'string' ? realpathSync(path) : null; } catch { return null; } }
|
|
58
|
+
|
|
53
59
|
/** When the process on `pid` started, in ms (Linux's /proc, or one `ps` on macOS), or null when it cannot be read. */
|
|
54
60
|
function startedMs(pid) {
|
|
55
61
|
if (process.platform === 'linux') { const started = linuxStart(pid); return Promise.resolve(started ? Date.parse(`${started.date} UTC`) : null); }
|
|
@@ -99,6 +105,9 @@ export class LiveSessions extends EventEmitter {
|
|
|
99
105
|
this.runtimes = new Map(); // receipt id → { pid, harness, sessionId, runtimeSessionId, cwd }
|
|
100
106
|
this.markers = new Map(); // session pid → server pid
|
|
101
107
|
this.codexRows = new Map(); // session id → { cwd, updatedAt }
|
|
108
|
+
// whether the Codex sessions are known yet: the index's first answer read, then their activity followed once after
|
|
109
|
+
// it (until then a Codex session missing from the map is not yet known, never not running)
|
|
110
|
+
this.codexIndexRead = false; this.codexKnown = false; this.codexWaiters = [];
|
|
102
111
|
this.codexRunning = new Map(); // session id → turn (from activity)
|
|
103
112
|
this.codexPaneOf = new Map(); // session id → pane
|
|
104
113
|
this.projectHooks = new Map(); // directory → whether its .codex/hooks.json carries the hook
|
|
@@ -320,7 +329,8 @@ export class LiveSessions extends EventEmitter {
|
|
|
320
329
|
const source = receipt.source;
|
|
321
330
|
const entry = { pid: receipt.pid, harness: source.harness, sessionId: source.session_id, runtimeSessionId: typeof receipt.runtime_session_id === 'string' ? receipt.runtime_session_id : null, cwd: typeof source.workspace === 'string' ? source.workspace : null,
|
|
322
331
|
// what a later check of the same pid needs: when the receipt was written, and how to ask the runtime itself
|
|
323
|
-
|
|
332
|
+
// its workspace as a real path too, so a relay folder named through a symlinked home still matches it
|
|
333
|
+
realCwd: realPath(source.workspace), created: Number(receipt.created_at_ms), probe: { base_url: receipt.base_url, token: receipt.token, receipt_id: id, runtime_session_id: receipt.runtime_session_id } };
|
|
324
334
|
const known = this.runtimes.get(id);
|
|
325
335
|
if (known && known.pid === entry.pid && known.startChecked) entry.startChecked = true;
|
|
326
336
|
this.runtimes.set(id, entry);
|
|
@@ -418,6 +428,26 @@ export class LiveSessions extends EventEmitter {
|
|
|
418
428
|
|
|
419
429
|
#codexChanged() { this.emit('codex-followed'); }
|
|
420
430
|
|
|
431
|
+
/** The session index's first answer is read: its Codex sessions' activity is followed next (even when it has none). */
|
|
432
|
+
codexIndexed() { this.codexIndexRead = true; this.#codexChanged(); }
|
|
433
|
+
|
|
434
|
+
/** The Codex sessions' activity was followed: after the index's first answer, the Codex sessions are known. */
|
|
435
|
+
codexFollowedNow() {
|
|
436
|
+
if (!this.codexIndexRead || this.codexKnown) return;
|
|
437
|
+
this.codexKnown = true;
|
|
438
|
+
for (const resolve of this.codexWaiters.splice(0)) resolve(true);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** Resolves true once the Codex sessions are known, or false when `boundMs` passes first. */
|
|
442
|
+
whenCodexKnown(boundMs) {
|
|
443
|
+
if (this.codexKnown) return Promise.resolve(true);
|
|
444
|
+
return new Promise((resolve) => {
|
|
445
|
+
const timer = setTimeout(() => { this.codexWaiters = this.codexWaiters.filter((waiter) => waiter !== done); resolve(false); }, boundMs);
|
|
446
|
+
const done = (value) => { clearTimeout(timer); resolve(value); };
|
|
447
|
+
this.codexWaiters.push(done);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
421
451
|
#changed() { this.emit('changed'); }
|
|
422
452
|
|
|
423
453
|
// ---- what it holds ------------------------------------------------------------------------------------------------
|
|
@@ -469,7 +499,8 @@ export class LiveSessions extends EventEmitter {
|
|
|
469
499
|
*/
|
|
470
500
|
/** The live runtimes of `harness` whose workspace is `cwd`, each by the ids it may be closed by (in-memory, no read). */
|
|
471
501
|
runtimesIn(harness, cwd) {
|
|
472
|
-
|
|
502
|
+
const real = realPath(cwd);
|
|
503
|
+
return [...this.runtimes.values()].filter((runtime) => runtime.harness === harness && (runtime.cwd === cwd || (real && runtime.realCwd === real)) && alive(runtime.pid))
|
|
473
504
|
.map((runtime) => [...new Set([runtime.sessionId, runtime.runtimeSessionId].filter((id) => typeof id === 'string' && id))]);
|
|
474
505
|
}
|
|
475
506
|
|
|
@@ -517,12 +548,14 @@ export class LiveSessions extends EventEmitter {
|
|
|
517
548
|
|
|
518
549
|
/** The session a client runs in, from its claim: its parent's current session, else its environment's id through the map. */
|
|
519
550
|
async #sessionOfClaim({ pid, ppid, session, runtime }) {
|
|
551
|
+
// the probes of one claim share one bound (CLAIM_PROBE_MS), however many of its processes are runtimes
|
|
552
|
+
const deadline = Date.now() + CLAIM_PROBE_MS;
|
|
520
553
|
// a runtime supercode hosts (the native agent) says so in its claim: it is named by its own process first, never by
|
|
521
554
|
// the session whose shell started it; one with no receipt is named by the rest of its claim, as any client is
|
|
522
555
|
if (runtime === true) {
|
|
523
556
|
// a pid several receipts name is no one runtime: the rest of the claim names it, never a refusal here
|
|
524
557
|
let own = null;
|
|
525
|
-
try { own = await this.#runtimeOfPid(Number(pid)); } catch (error) { if (!(error instanceof CallerRefused) || error.unconfirmed) throw error; }
|
|
558
|
+
try { own = await this.#runtimeOfPid(Number(pid), deadline); } catch (error) { if (!(error instanceof CallerRefused) || error.unconfirmed) throw error; }
|
|
526
559
|
if (own) return own;
|
|
527
560
|
}
|
|
528
561
|
const parent = this.claude.get(Number(ppid));
|
|
@@ -536,26 +569,27 @@ export class LiveSessions extends EventEmitter {
|
|
|
536
569
|
// only when exactly one live runtime's receipt names that process. A process several receipts name (harness serve
|
|
537
570
|
// records its own pid on each runtime it hosts with no child of its own) is no one runtime, and is refused.
|
|
538
571
|
for (const candidate of [Number(pid), Number(ppid)]) {
|
|
539
|
-
const found = await this.#runtimeOfPid(candidate);
|
|
572
|
+
const found = await this.#runtimeOfPid(candidate, deadline);
|
|
540
573
|
if (found) return found;
|
|
541
574
|
}
|
|
542
575
|
return null;
|
|
543
576
|
}
|
|
544
577
|
|
|
545
578
|
/** The runtime whose live receipt alone names process `pid`, or null; several receipts naming it are no one runtime, refused. */
|
|
546
|
-
async #runtimeOfPid(pid) {
|
|
579
|
+
async #runtimeOfPid(pid, deadline = Date.now() + CLAIM_PROBE_MS) {
|
|
547
580
|
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
548
581
|
const held = [...this.runtimes.values()].filter((runtime) => runtime.pid === pid && alive(runtime.pid));
|
|
549
582
|
if (held.length > 1) throw new CallerRefused(`process ${pid} hosts ${held.length} runtimes, so which one is calling is not known; nothing was done`);
|
|
550
583
|
if (held.length !== 1) return null;
|
|
551
584
|
// a caller's identity: checked again now, with no process (the runtime asked at its own address), as a runtime that
|
|
552
585
|
// ended may have left its pid to another process before the sweep lets its receipt go
|
|
553
|
-
// a host stalled by load answers late, not never: one that did not answer in 2 s is asked once more, for
|
|
554
|
-
|
|
555
|
-
|
|
586
|
+
// a host stalled by load answers late, not never: one that did not answer in 2 s is asked once more, for what is left
|
|
587
|
+
// of the claim's bound
|
|
588
|
+
let answered = await answersAs(held[0].probe, Math.max(1, Math.min(2000, deadline - Date.now())));
|
|
589
|
+
if (answered === null && deadline - Date.now() > 250) answered = await answersAs(held[0].probe, deadline - Date.now());
|
|
556
590
|
// one that did not answer then is not known either way: refused, never taken for its parent's session or its
|
|
557
591
|
// environment's (which would name another caller)
|
|
558
|
-
if (answered === null) throw Object.assign(new CallerRefused(`process ${pid} is a runtime that did not answer within
|
|
592
|
+
if (answered === null) throw Object.assign(new CallerRefused(`process ${pid} is a runtime that did not answer within ${CLAIM_PROBE_MS / 1000} s, so which session is calling was not confirmed; nothing was done`), { unconfirmed: true });
|
|
559
593
|
if (!answered) return null;
|
|
560
594
|
return this.#address(held[0].harness, held[0].sessionId);
|
|
561
595
|
}
|
package/machine.mjs
CHANGED
|
@@ -594,7 +594,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
594
594
|
const followNow = async () => {
|
|
595
595
|
const locators = this.live.codexFollowed();
|
|
596
596
|
const key = locators.map((locator) => locator.session_id).sort().join('\n');
|
|
597
|
-
if (key === followed) return;
|
|
597
|
+
if (key === followed) { this.live.codexFollowedNow(); return; }
|
|
598
598
|
const previous = this.liveActivity;
|
|
599
599
|
try {
|
|
600
600
|
if (locators.length) {
|
|
@@ -603,6 +603,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
603
603
|
this.live.codexActivity(opened?.initial ?? [], { replace: true });
|
|
604
604
|
} else { this.liveActivity = null; this.live.codexActivity([], { replace: true }); }
|
|
605
605
|
followed = key; backoffMs = 0;
|
|
606
|
+
this.live.codexFollowedNow();
|
|
606
607
|
if (previous) void this.#serveRequest('harness.v1.sessions.activity.unsubscribe', { subscription: previous }, 30_000).catch(() => {});
|
|
607
608
|
} catch (error) {
|
|
608
609
|
backoffMs = Math.min(300_000, Math.max(2000, (backoffMs || 1000) * 2));
|
|
@@ -795,6 +796,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
795
796
|
this.sessionDescriptors?.clear();
|
|
796
797
|
for (const descriptor of answer?.initial ?? []) if (descriptor?.locator) { locators.set(`${descriptor.locator.harness}:${descriptor.locator.session_id}`, descriptor.locator); this.live?.codexIndex({ descriptor }); this.sessionDescriptors?.set(`sc:${machineName(this.env)}:${descriptor.locator.harness}:${descriptor.locator.session_id}`, descriptor); }
|
|
797
798
|
producer.note('sessions_index', null); noteIndexParts(answer?.failed, null); both();
|
|
799
|
+
this.live?.codexIndexed();
|
|
798
800
|
}, (error) => {
|
|
799
801
|
indexBackoff = backoff(indexBackoff);
|
|
800
802
|
const why = `the session index could not be subscribed: ${error.message}; sessions and panes change on pane and mail events only until it is`;
|
package/mail/send.mjs
CHANGED
|
@@ -23,6 +23,9 @@ export function unansweredText(where, detail, id) {
|
|
|
23
23
|
return `Unanswered: ${where} took the message, but its answer was lost (${detail}), so whether it was delivered is unknown. Message id ${shortId(id)}. Don't send it again as a new message; supercode message show ${shortId(id)} says where it stands.`;
|
|
24
24
|
}
|
|
25
25
|
|
|
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
|
+
const CODEX_KNOWN_WAIT_MS = 20_000;
|
|
28
|
+
|
|
26
29
|
/** Replies in one unbroken chain between two sessions beyond which another is refused. */
|
|
27
30
|
const REPLY_CHAIN_LIMIT = 32;
|
|
28
31
|
/** How long a name no running session carries still reaches the session that last carried it. */
|
|
@@ -148,7 +151,7 @@ export class MailSend {
|
|
|
148
151
|
}
|
|
149
152
|
if (parsed) {
|
|
150
153
|
const session = sessions.find((row) => row.address === parsed.address);
|
|
151
|
-
if (!session) return { outcome: outcome(EXIT_UNKNOWN, `${to} is no longer running. Nothing was sent. Run supercode message list for the live sessions.`) };
|
|
154
|
+
if (!session) return { outcome: outcome(EXIT_UNKNOWN, this.#notYetKnown(to) ?? `${to} is no longer running. Nothing was sent. Run supercode message list for the live sessions.`) };
|
|
152
155
|
return { address: session.address, name: session.name, session };
|
|
153
156
|
}
|
|
154
157
|
// a name: unique among the sessions here, else the session it last named (the names ledger)
|
|
@@ -176,7 +179,17 @@ export class MailSend {
|
|
|
176
179
|
}).slice(0, 3).map((row) => `${row.name} (${row.address.split(':')[2]}, ${row.status})`);
|
|
177
180
|
if (near.length) hint = ` Did you mean: ${near.join(', ')}?`;
|
|
178
181
|
}
|
|
179
|
-
return { outcome: outcome(EXIT_UNKNOWN, `No session named "${wanted}" is reachable.${hint} Run supercode message list. Nothing was sent.`) };
|
|
182
|
+
return { outcome: outcome(EXIT_UNKNOWN, this.#notYetKnown(wanted) ?? `No session named "${wanted}" is reachable.${hint} Run supercode message list. Nothing was sent.`) };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** In a daemon's first moments the Codex sessions are not known yet (its session index and their activity still being
|
|
186
|
+
* read): a send waits for them, at most CODEX_KNOWN_WAIT_MS. */
|
|
187
|
+
async #codexKnown() { if (!this.live.codexKnown) await this.live.whenCodexKnown?.(CODEX_KNOWN_WAIT_MS); }
|
|
188
|
+
|
|
189
|
+
/** What a receiver not found is, while the Codex sessions are not known yet: not known yet, never not running. */
|
|
190
|
+
#notYetKnown(what) {
|
|
191
|
+
if (this.live.codexKnown !== false) return null;
|
|
192
|
+
return `${what} is not known yet: this machine's daemon started a moment ago and is still reading its sessions (its Codex sessions among them). Nothing was sent. Send it again in a minute.`;
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
/** The door that reaches `address` here, from one read of the live sessions (mail_route.rs `door_in`): null when no
|
|
@@ -195,6 +208,7 @@ export class MailSend {
|
|
|
195
208
|
|
|
196
209
|
/** Deliver from `caller` to `to` on this machine (mail_send.rs `deliver`). */
|
|
197
210
|
async #deliver(caller, to, body, { subject, in_reply_to, notify_when_idle, queue, id, notice, thread: carried, chosen }, link) {
|
|
211
|
+
await this.#codexKnown();
|
|
198
212
|
const sessions = this.live.sessions();
|
|
199
213
|
const receiver = this.#receiver(to, sessions);
|
|
200
214
|
if (receiver.outcome) return receiver.outcome;
|
|
@@ -226,7 +240,7 @@ export class MailSend {
|
|
|
226
240
|
if (plan.plan) return this.#deliverPlanned(caller, sent, plan.plan, { queue, notify_when_idle });
|
|
227
241
|
// the thread this message is in, kept in the send log: a later reply to it continues that thread
|
|
228
242
|
const sentIn = threadOf(sent);
|
|
229
|
-
if (!door || door.kind === 'other_machine') return outcome(EXIT_UNKNOWN, `${name} is no longer running. Nothing was sent. Run supercode message list for the live sessions.`);
|
|
243
|
+
if (!door || door.kind === 'other_machine') return outcome(EXIT_UNKNOWN, this.#notYetKnown(name) ?? `${name} is no longer running. Nothing was sent. Run supercode message list for the live sessions.`);
|
|
230
244
|
const idleNote = notify_when_idle && cameThroughLink(sent)
|
|
231
245
|
? ' No idle notice: this message came through a link, so its return address is as claimed, and nothing is sent to it on its own (D200); a reply is the receiver\'s own send.'
|
|
232
246
|
: notify_when_idle ? ' Subscribed: one idle notice reaches you when its next turn ends.' : '';
|
|
@@ -444,6 +458,7 @@ export class MailSend {
|
|
|
444
458
|
const receiver = typeof harness === 'string' && typeof sessionId === 'string' ? makeAddress(this.machine, harness, sessionId) : null;
|
|
445
459
|
if (!receiver) return refused('delivery_failed', `\`sc:${this.machine}:${harness}:${sessionId}\` is not a session address; addresses look like sc:<machine>:<harness>:<session-id>`);
|
|
446
460
|
if (params.voice_for != null && params.voice_for !== receiver) return refused('invalid_sender', 'a voice front must represent the receiving session');
|
|
461
|
+
await this.#codexKnown();
|
|
447
462
|
const sessions = this.live.sessions();
|
|
448
463
|
// a Claude session's registry name must still name only that session; a holder the mailbox launched may have stopped
|
|
449
464
|
if (harness === 'claude-code' && !(await this.agents.resumable(receiver)) && !this.live.runtimeReceipt('claude-code', sessionId)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.92",
|
|
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",
|