@volter/supercode-teams 0.3.181 → 0.3.182

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.
Files changed (2) hide show
  1. package/live-sessions.mjs +50 -7
  2. package/package.json +1 -1
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 { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, unlinkSync, watch, writeFileSync } from 'node:fs';
17
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, unlinkSync, 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';
@@ -773,7 +773,11 @@ export class LiveSessions extends EventEmitter {
773
773
  if (claude && alive(claude.pid)) return this.#address('claude-code', claude.sessionId);
774
774
  const hosted = await this.#runtimeOfPid(at, deadline);
775
775
  if (hosted) return hosted;
776
- if (/(^|\/)codex$/.test(command) && typeof session === 'string' && /^[0-9a-f-]{8,64}$/.test(session) && await holdsRollout(at, session, left())) return this.#address('codex', session);
776
+ if (/(^|\/)codex$/.test(command) && typeof session === 'string' && /^[0-9a-f-]{8,64}$/.test(session)) {
777
+ // the session that process is: the thread the command names, or the one whose subagent it is
778
+ const held = await rolloutSession(at, session, left());
779
+ if (held) return this.#address('codex', held);
780
+ }
777
781
  }
778
782
  return null;
779
783
  }
@@ -835,12 +839,51 @@ async function ancestryOf(pid, timeout = 5000) {
835
839
  return chain;
836
840
  }
837
841
 
838
- /** Whether Codex process `pid` holds thread `thread`'s rollout open (its file is named by the thread's id), and no
839
- * other's: a process serving several threads (an app-server) names none of them, so a child cannot name a sibling. */
840
- async function holdsRollout(pid, thread, timeout = 5000) {
842
+ /**
843
+ * The session Codex process `pid` is, for a command that says it runs in thread `thread`: the one thread whose rollout
844
+ * the process holds open (its file is named by the thread's id), or, when it holds several, the thread they all
845
+ * descend from. A Codex session that spawns subagents keeps each subagent's rollout open beside its own, and every one
846
+ * of them says in its first line which thread spawned it: such a family is one session, its root, and a command of the
847
+ * root or of any subagent acts as it. A process serving threads that are not one family (an app-server) names none of
848
+ * them, so a child cannot name a sibling. Null when `thread`'s rollout is not among them.
849
+ * (2026-10-07: "exactly one rollout" left a session that had spawned subagents unable to read or send its own mail.)
850
+ */
851
+ async function rolloutSession(pid, thread, timeout = 5000) {
841
852
  const out = await new Promise((resolve) => execFile(tool('lsof'), ['-a', '-p', String(pid), '-Fn'], { timeout, maxBuffer: 16 * 1024 * 1024 }, (_error, stdout) => resolve(String(stdout ?? ''))));
842
- const rollouts = new Set(out.split('\n').filter((line) => line.startsWith('n') && /rollout-[^/]*\.jsonl$/.test(line)).map((line) => line.slice(1)));
843
- return rollouts.size === 1 && [...rollouts][0].includes(thread);
853
+ const rollouts = [...new Set(out.split('\n').filter((line) => line.startsWith('n') && /rollout-[^/]*\.jsonl$/.test(line)).map((line) => line.slice(1)))];
854
+ if (!rollouts.some((path) => path.includes(thread))) return null;
855
+ if (rollouts.length === 1) return thread;
856
+ // each thread held, with the thread that spawned it (none for one a person started)
857
+ const parentOf = new Map();
858
+ for (const path of rollouts) {
859
+ const id = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/.exec(path)?.[1];
860
+ const first = rolloutHead(path);
861
+ if (!id || first === null) return null;
862
+ parentOf.set(id, /"parent_thread_id"\s*:\s*"([0-9a-f-]{8,64})"/.exec(first)?.[1] ?? null);
863
+ }
864
+ // one family: exactly one thread nobody held here spawned, and every other one reaches it through threads held here
865
+ const roots = [...parentOf].filter(([, parent]) => parent === null).map(([id]) => id);
866
+ if (roots.length !== 1) return null;
867
+ for (const id of parentOf.keys()) {
868
+ let at = id;
869
+ for (let depth = 0; at !== roots[0]; depth++) {
870
+ if (depth > 16 || !parentOf.has(at)) return null;
871
+ at = parentOf.get(at);
872
+ }
873
+ }
874
+ return roots[0];
875
+ }
876
+
877
+ /** A rollout's first line (its session's own record), read from the file's head; null when it cannot be read. */
878
+ function rolloutHead(path) {
879
+ let file = null;
880
+ try {
881
+ file = openSync(path, 'r');
882
+ const head = Buffer.alloc(256 * 1024);
883
+ const text = head.toString('utf8', 0, readSync(file, head, 0, head.length, 0));
884
+ const end = text.indexOf('\n');
885
+ return end < 0 ? text : text.slice(0, end);
886
+ } catch { return null; } finally { if (file !== null) try { closeSync(file); } catch { /* closed */ } }
844
887
  }
845
888
 
846
889
  /** A live-runtime receipt file, when it is one a delivery may use: its schema, and a loopback HTTP address. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.181",
3
+ "version": "0.3.182",
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",