@volter/supercode-teams 0.3.92 → 0.3.93

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 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,36 @@ 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
+ }
437
+
431
438
  /** 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(); }
439
+ codexIndexed() {
440
+ if (!this.codexIndexRead) this.codexIndexedAt = Date.now();
441
+ this.codexIndexRead = true; this.#codexChanged();
442
+ }
433
443
 
434
444
  /** The Codex sessions' activity was followed: after the index's first answer, the Codex sessions are known. */
435
445
  codexFollowedNow() {
436
446
  if (!this.codexIndexRead || this.codexKnown) return;
437
- this.codexKnown = true;
447
+ this.codexKnown = true; this.codexWaitedOut = false;
448
+ // how long a daemon's start took to know its Codex sessions, and in which part (said once)
449
+ const now = Date.now();
450
+ 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
451
  for (const resolve of this.codexWaiters.splice(0)) resolve(true);
439
452
  }
440
453
 
441
- /** Resolves true once the Codex sessions are known, or false when `boundMs` passes first. */
454
+ /** Resolves true once the Codex sessions are known, or false when `boundMs` passes first. Once a wait has run out,
455
+ * later ones answer false at once until they are known: a harness serve that does not answer holds no send twice. */
442
456
  whenCodexKnown(boundMs) {
443
457
  if (this.codexKnown) return Promise.resolve(true);
458
+ if (this.codexWaitedOut) return Promise.resolve(false);
444
459
  return new Promise((resolve) => {
445
- const timer = setTimeout(() => { this.codexWaiters = this.codexWaiters.filter((waiter) => waiter !== done); resolve(false); }, boundMs);
460
+ const timer = setTimeout(() => { this.codexWaiters = this.codexWaiters.filter((waiter) => waiter !== done); this.codexWaitedOut = true; resolve(false); }, boundMs);
446
461
  const done = (value) => { clearTimeout(timer); resolve(value); };
447
462
  this.codexWaiters.push(done);
448
463
  });
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
- this.live.on('codex-followed', () => { if (!pending) { pending = setTimeout(follow, 2000); pending.unref?.(); } });
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 `${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.`;
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
- await this.#codexKnown();
212
- const sessions = this.live.sessions();
213
- const receiver = this.#receiver(to, sessions);
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
- await this.#codexKnown();
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.92",
3
+ "version": "0.3.93",
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",