@volter/supercode-teams 0.3.128 → 0.3.130
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/live-sessions.mjs +16 -8
- package/machine.mjs +2 -2
- package/mail/relay.mjs +2 -2
- package/mail/send.mjs +17 -14
- package/native-codex-pane.mjs +4 -1
- package/native-questions.mjs +3 -1
- package/package.json +1 -1
package/live-sessions.mjs
CHANGED
|
@@ -147,7 +147,7 @@ export class LiveSessions extends EventEmitter {
|
|
|
147
147
|
// session is live while its process is; the daemon pane it runs in, when it does, from the panes the records read
|
|
148
148
|
this.grokHome = env.GROK_HOME || join(homedir(), '.grok');
|
|
149
149
|
this.grok = new Map(); // session id → { pid, ppid, cwd, openedAt, pane }
|
|
150
|
-
this.
|
|
150
|
+
this.paneRoots = [];
|
|
151
151
|
this.projectHooks = new Map(); // directory → whether its .codex/hooks.json carries the hook
|
|
152
152
|
this.userHook = false;
|
|
153
153
|
this.watchers = [];
|
|
@@ -468,15 +468,23 @@ export class LiveSessions extends EventEmitter {
|
|
|
468
468
|
|
|
469
469
|
/** The daemon pane a Grok session runs in: the pane whose process it is, or whose process started it. */
|
|
470
470
|
#grokPane(entry) {
|
|
471
|
-
entry.pane = this.
|
|
471
|
+
entry.pane = this.paneRoots.find((root) => root.pid === entry.pid || (entry.ppid != null && root.pid === entry.ppid))?.pane ?? null;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
/** The daemon
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
474
|
+
/** The daemon pane a Claude session runs in: the tmux pane its registry names, else the pane whose process it is.
|
|
475
|
+
* Claude names its pane only from tmux's environment, so on Windows (no tmux) only the pane's process says it. */
|
|
476
|
+
#claudePane(entry) {
|
|
477
|
+
return entry.tmux ?? this.paneRoots.find((root) => root.pid === entry.pid)?.pane ?? null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** The daemon's live panes and their root processes (`[{ pane, pid }]`), as the records read them: the panes of the
|
|
481
|
+
* sessions whose own records name none (Grok's; Claude's on Windows). */
|
|
482
|
+
panes(roots) {
|
|
483
|
+
const placed = () => JSON.stringify([...[...this.grok].map(([id, entry]) => [id, entry.pane]), ...[...this.claude.values()].map((entry) => [entry.sessionId, this.#claudePane(entry)])]);
|
|
484
|
+
const before = placed();
|
|
485
|
+
this.paneRoots = (roots ?? []).filter((root) => root?.pane && Number.isInteger(root.pid));
|
|
478
486
|
for (const entry of this.grok.values()) this.#grokPane(entry);
|
|
479
|
-
if (
|
|
487
|
+
if (placed() !== before) this.#changed();
|
|
480
488
|
}
|
|
481
489
|
|
|
482
490
|
/** The messaging tools' markers kept here, and those servers from before wrote into the mail folder, taken in once
|
|
@@ -647,7 +655,7 @@ export class LiveSessions extends EventEmitter {
|
|
|
647
655
|
if (entry.name.startsWith(RELAY_PREFIX) || !alive(entry.pid)) continue;
|
|
648
656
|
const address = this.#address('claude-code', entry.sessionId);
|
|
649
657
|
if (seen.has(address)) continue; seen.add(address);
|
|
650
|
-
out.push({ address, name: `${entry.name}@${this.machine}`, status: entry.status, door: 'native', pid: entry.pid, cwd: entry.cwd, pane: entry
|
|
658
|
+
out.push({ address, name: `${entry.name}@${this.machine}`, status: entry.status, door: 'native', pid: entry.pid, cwd: entry.cwd, pane: this.#claudePane(entry), tools: this.hasTools(entry.pid), socket: entry.socket ?? null });
|
|
651
659
|
}
|
|
652
660
|
const codex = new Set([...this.codexRunning.keys(), ...this.codexPaneOf.keys()]);
|
|
653
661
|
for (const sessionId of codex) {
|
package/machine.mjs
CHANGED
|
@@ -922,8 +922,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
922
922
|
const titles = readJson(this.p.titles, { v: 1, titles: {} }).titles;
|
|
923
923
|
const panes = rows.filter((row) => row.session?.id).map(({ session: s }) => ({ pane: s.contextKey, dead: Boolean(s.dead), pid: s.rootPid ?? null, label: s.label ?? null, title: titles[`pane:${s.contextKey}`] ?? s.label ?? null, cwd: s.cwd ?? null }));
|
|
924
924
|
const live = panes.filter((pane) => !pane.dead && pane.pid);
|
|
925
|
-
// the pane each
|
|
926
|
-
this.live?.
|
|
925
|
+
// the pane each session whose record names none runs in (Grok's; Claude's on Windows): the one rooted at it
|
|
926
|
+
this.live?.panes(live.map((pane) => ({ pane: pane.pane, pid: pane.pid })));
|
|
927
927
|
// the harness session under each live pane, as the board reads it: unread is said, never no session
|
|
928
928
|
let activities = [], sessions_unread = null;
|
|
929
929
|
if (live.length) {
|
package/mail/relay.mjs
CHANGED
|
@@ -373,8 +373,8 @@ export class Relays {
|
|
|
373
373
|
*/
|
|
374
374
|
/** Whether `sender`'s relay still holds a send queued (its queue file, written within SEND_TIMEOUT_MS twice over): a
|
|
375
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; }
|
|
376
|
+
sending(sender, senderName) {
|
|
377
|
+
try { return Date.now() - statSync(this.spec(sender, senderName).queue).mtimeMs < 2 * SEND_TIMEOUT_MS; } catch { return false; }
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
/** Whether message `messageId` from `sender` landed in Claude session `sessionId`'s transcript, as a relay send reads
|
package/mail/send.mjs
CHANGED
|
@@ -330,13 +330,29 @@ export class MailSend {
|
|
|
330
330
|
// at its first tool call finds it, and a daemon that ends before the relay answers (an upgrade's restart, a crash)
|
|
331
331
|
// leaves it for the carrier, which hands it over again (t_b0f9a2a0: filed read before, such a send was lost). It is
|
|
332
332
|
// read once the relay has made it; a relay that fails takes back the copy this delivery filed
|
|
333
|
+
// never beside another hand-over of the same message: one running here, one a relay that outlived its daemon may
|
|
334
|
+
// still be making, or one an earlier daemon made that landed (every path: a wake's handOver, an agent's re-plan)
|
|
335
|
+
const key = `${to}\n${handed.id}`;
|
|
336
|
+
const unanswered = (detail) => Object.assign(new Error(detail), { unanswered: true });
|
|
337
|
+
if (this.relaying.has(key)) throw unanswered('its relay send is still running here');
|
|
338
|
+
const earlier = this.store.hasWake(to, handed.id) ? this.store.wakeState(to, handed.id).handover_at_ms : null;
|
|
339
|
+
if (earlier != null) {
|
|
340
|
+
if (this.relays.sending?.(handed.from, handed.from_name)) throw unanswered('its relay may still be making it');
|
|
341
|
+
let landed = false;
|
|
342
|
+
try { landed = await this.relays.landed(door.session.address.split(':').slice(3).join(':'), handed.id, handed.from); } catch { /* unread: hand it over */ }
|
|
343
|
+
if (landed) {
|
|
344
|
+
try { this.store.claimFor(to, handed.id, { pid: process.pid, via: 'door', caller: null, recipient: true, at_ms: Date.now() }); } catch { /* best kept */ }
|
|
345
|
+
const now = this.store.findIn(to, handed.id);
|
|
346
|
+
if (now?.state === 'unread') { try { this.store.markRead(to, now.path); } catch { /* moved meanwhile */ } }
|
|
347
|
+
return 'already';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
333
350
|
const filedHere = this.store.findIn(to, handed.id) == null;
|
|
334
351
|
const filed = this.store.deliver(to, handed);
|
|
335
352
|
if (wake && !this.store.hasWake(to, handed.id)) {
|
|
336
353
|
// its hand-over begins now: a daemon that restarts reads the receiver's transcript before sending it again
|
|
337
354
|
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
355
|
}
|
|
339
|
-
const key = `${to}\n${handed.id}`;
|
|
340
356
|
this.relaying.add(key);
|
|
341
357
|
let relayed;
|
|
342
358
|
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); }
|
|
@@ -783,19 +799,6 @@ export class MailSend {
|
|
|
783
799
|
// tried again after its backoff, settled by that hand-over's own outcome
|
|
784
800
|
const key = `${address}\n${id}`;
|
|
785
801
|
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
|
-
}
|
|
799
802
|
this.store.setWakeState(address, id, { ...state, handover_at_ms: Date.now() });
|
|
800
803
|
this.handing.add(key);
|
|
801
804
|
try {
|
package/native-codex-pane.mjs
CHANGED
|
@@ -8,7 +8,10 @@ import { basename, dirname, join } from 'node:path';
|
|
|
8
8
|
|
|
9
9
|
const path = process.argv[2];
|
|
10
10
|
const seed = JSON.parse(readFileSync(path, 'utf8'));
|
|
11
|
-
|
|
11
|
+
// A local socket's path holds at most 104 bytes (macOS; 108 on Linux): under a long TMPDIR (a scratch home's) the
|
|
12
|
+
// endpoint goes under /tmp, private to this user as mkdtemp makes it, or neither side could open it.
|
|
13
|
+
const base = process.platform !== 'win32' && Buffer.byteLength(join(tmpdir(), 'sc-codex-XXXXXX', 'native.sock')) > 103 ? '/tmp' : tmpdir();
|
|
14
|
+
const directory = mkdtempSync(join(base, 'sc-codex-'));
|
|
12
15
|
const socketPath = join(directory, 'native.sock');
|
|
13
16
|
const endpoint = `unix://${socketPath}`;
|
|
14
17
|
// Configuration is evaluated by the native server. Remote resume/fork refuses
|
package/native-questions.mjs
CHANGED
|
@@ -26,7 +26,9 @@ export async function codexQuestions({ threadId, threadIds, answer, socketPath,
|
|
|
26
26
|
const targets = new Set(threadIds ?? (threadId ? [threadId] : []));
|
|
27
27
|
if ((!targets.size && !(discover && socketPath)) || [...targets].some((id) => typeof id !== 'string' || !id) || (answer && targets.size !== 1)) throw failure('invalid-request', 'Native thread ids are required; answer only one thread at a time.');
|
|
28
28
|
if (!socketPath) throw failure('native-question-unavailable', 'This launch has no native question endpoint.');
|
|
29
|
-
|
|
29
|
+
// The connection's own failure (a gone or unopenable endpoint) can be emitted before the request listens to it: it
|
|
30
|
+
// is the WebSocket's error then (the handshake times out), never an unhandled one that ends the machine daemon.
|
|
31
|
+
const socket = new WebSocket('ws://localhost/', { perMessageDeflate: false, createConnection: () => connect(socketPath).on('error', () => {}), handshakeTimeout: timeoutMs });
|
|
30
32
|
let nextId = 0;
|
|
31
33
|
const pending = new Map();
|
|
32
34
|
const questions = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.130",
|
|
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",
|