@volter/supercode-teams 0.3.92 → 0.3.94
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 +1 -0
- package/bin/workspace.mjs +1 -0
- package/live-sessions.mjs +25 -5
- package/machine-records.mjs +6 -2
- package/machine.mjs +4 -2
- package/mail/send.mjs +9 -5
- package/package.json +1 -1
package/bin/teams.mjs
CHANGED
|
@@ -175,6 +175,7 @@ async function main() {
|
|
|
175
175
|
machine.on('door', (d) => { if (flags.verbose) process.stderr.write(`door ${d.method} ${d.outcome} ${d.ms}ms${d.detail ? ` ${d.detail}` : ''}\n`); });
|
|
176
176
|
machine.on('serve-stderr', (t) => process.stderr.write(t));
|
|
177
177
|
machine.on('mail-log', (line) => process.stderr.write(`mail: ${line}\n`));
|
|
178
|
+
machine.on('records-log', (line) => process.stderr.write(`records: ${line}\n`));
|
|
178
179
|
machine.on('serve-exit', (e) => process.stderr.write(`harness serve exited: ${JSON.stringify(e)}\n`));
|
|
179
180
|
machine.on('unrouted-notification', (m) => { if (flags.verbose) process.stderr.write(`unrouted: ${m.method}\n`); });
|
|
180
181
|
const socket = await machine.start();
|
package/bin/workspace.mjs
CHANGED
|
@@ -1136,6 +1136,7 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
|
|
|
1136
1136
|
machine.on('serve-exit', (exit) => process.stderr.write(`supercode teams: harness serve exited: ${JSON.stringify(exit)}\n`));
|
|
1137
1137
|
machine.on('serve-stderr', (text) => process.stderr.write(`supercode teams: harness serve: ${String(text).trimEnd().slice(-2000)}\n`));
|
|
1138
1138
|
machine.on('mail-log', (line) => process.stderr.write(`supercode teams: mail: ${line}\n`));
|
|
1139
|
+
machine.on('records-log', (line) => process.stderr.write(`supercode teams: records: ${line}\n`));
|
|
1139
1140
|
// its mail goes through the daemon's own send, in this process
|
|
1140
1141
|
machineEvents.send = (request) => machine.sendOwn(request);
|
|
1141
1142
|
// the Claude worker homes the roots declare: their live sessions are this machine's too (the live-session map)
|
package/live-sessions.mjs
CHANGED
|
@@ -100,7 +100,7 @@ export class LiveSessions extends EventEmitter {
|
|
|
100
100
|
this.homesFile = join(supercodeHome, 'live-claude-homes.json');
|
|
101
101
|
this.markersDir = join(supercodeHome, 'mail', 'message-mcp');
|
|
102
102
|
this.codexHooks = join(env.CODEX_HOME || join(homedir(), '.codex'), 'hooks.json');
|
|
103
|
-
this.boardOwner = boardOwner; this.log = log;
|
|
103
|
+
this.boardOwner = boardOwner; this.log = log; this.startedAt = Date.now();
|
|
104
104
|
this.claude = new Map(); // pid → { pid, dir, sessionId, name, status, cwd, tmux, procStart, history: [ids, oldest first] }
|
|
105
105
|
this.runtimes = new Map(); // receipt id → { pid, harness, sessionId, runtimeSessionId, cwd }
|
|
106
106
|
this.markers = new Map(); // session pid → server pid
|
|
@@ -428,21 +428,41 @@ export class LiveSessions extends EventEmitter {
|
|
|
428
428
|
|
|
429
429
|
#codexChanged() { this.emit('codex-followed'); }
|
|
430
430
|
|
|
431
|
+
/** harness serve ended: its index and activity subscriptions went with it, so the Codex sessions are not known again
|
|
432
|
+
* until it answers anew (a session missing meanwhile is not known yet, never not running). */
|
|
433
|
+
codexForget() {
|
|
434
|
+
if (!this.codexIndexRead && !this.codexKnown) return;
|
|
435
|
+
this.codexIndexRead = false; this.codexKnown = false; this.codexWaitedOut = false; this.startedAt = Date.now();
|
|
436
|
+
// what the ended serve said is not kept as what is true now: a Codex session that ended in the gap would be filed for
|
|
437
|
+
// a door it no longer has; until serve answers anew, a Codex session is not known yet (a send to it waits)
|
|
438
|
+
const had = this.codexRunning.size + this.codexPaneOf.size;
|
|
439
|
+
this.codexRunning.clear(); this.codexPaneOf = new Map();
|
|
440
|
+
if (had) this.#changed();
|
|
441
|
+
}
|
|
442
|
+
|
|
431
443
|
/** The session index's first answer is read: its Codex sessions' activity is followed next (even when it has none). */
|
|
432
|
-
codexIndexed() {
|
|
444
|
+
codexIndexed() {
|
|
445
|
+
if (!this.codexIndexRead) this.codexIndexedAt = Date.now();
|
|
446
|
+
this.codexIndexRead = true; this.#codexChanged();
|
|
447
|
+
}
|
|
433
448
|
|
|
434
449
|
/** The Codex sessions' activity was followed: after the index's first answer, the Codex sessions are known. */
|
|
435
450
|
codexFollowedNow() {
|
|
436
451
|
if (!this.codexIndexRead || this.codexKnown) return;
|
|
437
|
-
this.codexKnown = true;
|
|
452
|
+
this.codexKnown = true; this.codexWaitedOut = false;
|
|
453
|
+
// how long a daemon's start took to know its Codex sessions, and in which part (said once)
|
|
454
|
+
const now = Date.now();
|
|
455
|
+
this.log(`live sessions: Codex sessions known ${Math.round((now - this.startedAt) / 100) / 10} s after start (the session index answered at ${Math.round((this.codexIndexedAt - this.startedAt) / 100) / 10} s; their activity followed ${Math.round((now - this.codexIndexedAt) / 100) / 10} s later, ${this.codexFollowed().length} followed)`);
|
|
438
456
|
for (const resolve of this.codexWaiters.splice(0)) resolve(true);
|
|
439
457
|
}
|
|
440
458
|
|
|
441
|
-
/** Resolves true once the Codex sessions are known, or false when `boundMs` passes first.
|
|
459
|
+
/** Resolves true once the Codex sessions are known, or false when `boundMs` passes first. Once a wait has run out,
|
|
460
|
+
* later ones answer false at once until they are known: a harness serve that does not answer holds no send twice. */
|
|
442
461
|
whenCodexKnown(boundMs) {
|
|
443
462
|
if (this.codexKnown) return Promise.resolve(true);
|
|
463
|
+
if (this.codexWaitedOut) return Promise.resolve(false);
|
|
444
464
|
return new Promise((resolve) => {
|
|
445
|
-
const timer = setTimeout(() => { this.codexWaiters = this.codexWaiters.filter((waiter) => waiter !== done); resolve(false); }, boundMs);
|
|
465
|
+
const timer = setTimeout(() => { this.codexWaiters = this.codexWaiters.filter((waiter) => waiter !== done); this.codexWaitedOut = true; resolve(false); }, boundMs);
|
|
446
466
|
const done = (value) => { clearTimeout(timer); resolve(value); };
|
|
447
467
|
this.codexWaiters.push(done);
|
|
448
468
|
});
|
package/machine-records.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const MIN_READ_MS = 2000;
|
|
|
21
21
|
export const BEAT_MS = 15_000;
|
|
22
22
|
export const STALE_MS = 45_000;
|
|
23
23
|
const backoff = (previous) => Math.min(300_000, Math.max(2000, (previous || 1000) * 2));
|
|
24
|
+
/** How often a machine whose daemon is too old to serve records is asked again (its update is what answers it). */
|
|
25
|
+
const TOO_OLD_RETRY_MS = 600_000;
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* The producer: `readers[section](detail)` reads one section (async), `detail` naming what changed when the event knows
|
|
@@ -231,8 +233,10 @@ export class RecordHolder {
|
|
|
231
233
|
held.attempt = null;
|
|
232
234
|
Object.assign(held, { stream: null, state, since: Date.now(), why: String(why ?? 'the stream closed').slice(0, 300) });
|
|
233
235
|
try { stream?.close?.(); } catch { /* closing */ }
|
|
234
|
-
// opened again after a backoff, 2 s doubling to 5 min: an obligation of this machine, never a sweep
|
|
235
|
-
|
|
236
|
+
// opened again after a backoff, 2 s doubling to 5 min: an obligation of this machine, never a sweep. A daemon too
|
|
237
|
+
// old to serve records is asked again every TOO_OLD_RETRY_MS: only its update answers it (asked every 8-16 s, it
|
|
238
|
+
// filled the log)
|
|
239
|
+
held.backoff = state === 'too-old' ? TOO_OLD_RETRY_MS : backoff(held.backoff);
|
|
236
240
|
if (this.closed) return;
|
|
237
241
|
held.retry = setTimeout(() => { held.retry = null; if ((held.state === 'closed' || held.state === 'too-old') && !this.closed) this.subscribe(machine); }, held.backoff);
|
|
238
242
|
held.retry.unref?.();
|
package/machine.mjs
CHANGED
|
@@ -611,12 +611,14 @@ export class TeamsMachine extends MachineCore {
|
|
|
611
611
|
pending = setTimeout(follow, backoffMs); pending.unref?.();
|
|
612
612
|
}
|
|
613
613
|
};
|
|
614
|
-
|
|
614
|
+
// a change is followed after 2 s (changes come in bursts); the first follow after the index's first answer at once,
|
|
615
|
+
// as sends wait on it (the Codex sessions are not known until it is done)
|
|
616
|
+
this.live.on('codex-followed', () => { if (!pending) { pending = setTimeout(follow, this.live.codexKnown ? 2000 : 0); pending.unref?.(); } });
|
|
615
617
|
this.on('internal-notification', (message) => {
|
|
616
618
|
const params = message.params ?? {};
|
|
617
619
|
if (params.subscription && params.subscription === this.liveActivity) this.live.codexActivity(params.activities);
|
|
618
620
|
});
|
|
619
|
-
this.on('serve-exit', () => { this.liveActivity = null; followed = ''; this.live.emit('codex-followed'); });
|
|
621
|
+
this.on('serve-exit', () => { this.liveActivity = null; followed = ''; this.live.codexForget(); this.live.emit('codex-followed'); });
|
|
620
622
|
}
|
|
621
623
|
|
|
622
624
|
// ---- this machine's mail (mail/; docs/architecture/overview.md, "The mail operations") ---------------------------
|
package/mail/send.mjs
CHANGED
|
@@ -189,7 +189,9 @@ export class MailSend {
|
|
|
189
189
|
/** What a receiver not found is, while the Codex sessions are not known yet: not known yet, never not running. */
|
|
190
190
|
#notYetKnown(what) {
|
|
191
191
|
if (this.live.codexKnown !== false) return null;
|
|
192
|
-
return
|
|
192
|
+
return this.live.codexWaitedOut
|
|
193
|
+
? `${what} is not known: this machine's harness serve has not reported its Codex sessions yet (it may be starting again, or not answering; the daemon's log says which). Nothing was sent.`
|
|
194
|
+
: `${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.`;
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
/** The door that reaches `address` here, from one read of the live sessions (mail_route.rs `door_in`): null when no
|
|
@@ -208,9 +210,10 @@ export class MailSend {
|
|
|
208
210
|
|
|
209
211
|
/** Deliver from `caller` to `to` on this machine (mail_send.rs `deliver`). */
|
|
210
212
|
async #deliver(caller, to, body, { subject, in_reply_to, notify_when_idle, queue, id, notice, thread: carried, chosen }, link) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
let sessions = this.live.sessions();
|
|
214
|
+
let receiver = this.#receiver(to, sessions);
|
|
215
|
+
// only a receiver not found waits for the Codex sessions to be known (one the map holds is reached at once)
|
|
216
|
+
if (receiver.outcome && !this.live.codexKnown) { await this.#codexKnown(); sessions = this.live.sessions(); receiver = this.#receiver(to, sessions); }
|
|
214
217
|
if (receiver.outcome) return receiver.outcome;
|
|
215
218
|
const { address, name } = receiver;
|
|
216
219
|
if (address === caller.address) return outcome(EXIT_REFUSED, 'Not sent: that address is your own session.');
|
|
@@ -458,7 +461,8 @@ export class MailSend {
|
|
|
458
461
|
const receiver = typeof harness === 'string' && typeof sessionId === 'string' ? makeAddress(this.machine, harness, sessionId) : null;
|
|
459
462
|
if (!receiver) return refused('delivery_failed', `\`sc:${this.machine}:${harness}:${sessionId}\` is not a session address; addresses look like sc:<machine>:<harness>:<session-id>`);
|
|
460
463
|
if (params.voice_for != null && params.voice_for !== receiver) return refused('invalid_sender', 'a voice front must represent the receiving session');
|
|
461
|
-
|
|
464
|
+
// only a receiver the map does not hold waits for the Codex sessions to be known
|
|
465
|
+
if (!this.live.codexKnown && !this.live.sessions().some((row) => row.address === receiver)) await this.#codexKnown();
|
|
462
466
|
const sessions = this.live.sessions();
|
|
463
467
|
// a Claude session's registry name must still name only that session; a holder the mailbox launched may have stopped
|
|
464
468
|
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.94",
|
|
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",
|