@volter/supercode-teams 0.3.127 → 0.3.129
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 +35 -3
- package/mail/relay.mjs +1 -1
- 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) {
|
|
@@ -1845,10 +1845,24 @@ export class TeamsMachine extends MachineCore {
|
|
|
1845
1845
|
/** Whether the machine's stable supercode serves `command` in TypeScript (its front door's own list), asked once: a dev
|
|
1846
1846
|
* or cargo build's native, run with no front door, serves no hook verb. */
|
|
1847
1847
|
#frontDoorServes(command) {
|
|
1848
|
+
// asked once, kept while it answered; an ask that found nothing is asked again a minute on, never kept for the
|
|
1849
|
+
// daemon's life (one slow or failed start of the front door refused every relay send until a restart)
|
|
1850
|
+
if (this.frontDoorServed !== undefined && !this.frontDoorServed.size && Date.now() - (this.frontDoorAskedAt ?? 0) > 60_000) this.frontDoorServed = undefined;
|
|
1848
1851
|
if (this.frontDoorServed === undefined) {
|
|
1849
1852
|
this.frontDoorServed = new Set();
|
|
1853
|
+
this.frontDoorAskedAt = Date.now();
|
|
1850
1854
|
try {
|
|
1851
|
-
const
|
|
1855
|
+
const stable = this.#stableSupercode();
|
|
1856
|
+
// On Windows the stable supercode is a `.cmd` shim, which Node starts only through a shell (EINVAL otherwise,
|
|
1857
|
+
// since Node 20.12): asked plainly it answered nothing, so every relay was refused as "no front door here" and
|
|
1858
|
+
// no mail reached a Claude session on the machine. The shim is supercode's own one line (claude_relay.rs:
|
|
1859
|
+
// `@"<node>" "<front door>" %*`), so what it runs is run directly: no shell, nothing of its path read by one
|
|
1860
|
+
// (#1270 review: cmd.exe /s cut a home path with a space at the space). A shim of another form goes through
|
|
1861
|
+
// cmd.exe, the whole line in an outer pair of quotes, so /s takes off only those.
|
|
1862
|
+
const direct = /\.(cmd|bat)$/i.test(stable) ? shimTarget(stable) : [stable];
|
|
1863
|
+
const answer = direct
|
|
1864
|
+
? spawnSync(direct[0], [...direct.slice(1), 'front-door', '--json'], { env: this.env, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true })
|
|
1865
|
+
: spawnSync(this.env.ComSpec || process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', `""${stable}" front-door --json"`], { env: this.env, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, windowsVerbatimArguments: true });
|
|
1852
1866
|
if (answer.status === 0) for (const row of JSON.parse(answer.stdout).typescript ?? []) this.frontDoorServed.add(row.command);
|
|
1853
1867
|
} catch { /* none served */ }
|
|
1854
1868
|
}
|
|
@@ -1864,6 +1878,14 @@ export class TeamsMachine extends MachineCore {
|
|
|
1864
1878
|
const path = answer.status === 0 ? answer.stdout.trim() : '';
|
|
1865
1879
|
if (path && isAbsolute(path) && existsSync(path)) this.stableSupercodePath = path;
|
|
1866
1880
|
} catch { /* this daemon's own supercode */ }
|
|
1881
|
+
// On Windows the daemon runs this release's per-version copy (service\bin\<version>, so an upgrade can replace the
|
|
1882
|
+
// npm one), and `__stable-program` refuses it as not the machine's own supercode: the daemon fell back to that
|
|
1883
|
+
// native, which serves no front door, so every relay was refused and no mail reached a Claude session here. The
|
|
1884
|
+
// shim the machine's own supercode keeps in <SUPERCODE_HOME>\bin is the machine's stable supercode.
|
|
1885
|
+
if (this.stableSupercodePath === null && process.platform === 'win32') {
|
|
1886
|
+
const shim = join(supercodeHome(this.env), 'bin', 'supercode.cmd');
|
|
1887
|
+
if (existsSync(shim)) this.stableSupercodePath = shim;
|
|
1888
|
+
}
|
|
1867
1889
|
return this.stableSupercodePath ?? this.supercodeBin;
|
|
1868
1890
|
}
|
|
1869
1891
|
|
|
@@ -4329,3 +4351,13 @@ function healthEntry(env) {
|
|
|
4329
4351
|
const sibling = fileURLToPath(new URL('../health/bin/health.mjs', import.meta.url));
|
|
4330
4352
|
return existsSync(sibling) ? sibling : null;
|
|
4331
4353
|
}
|
|
4354
|
+
|
|
4355
|
+
/** What supercode's Windows shim runs, from its one line (`@"<node>" "<front door>" %*`, or `@"<native>" %*`;
|
|
4356
|
+
* claude_relay.rs writes it): `[program, ...args]`, or null for a shim of another form. */
|
|
4357
|
+
function shimTarget(path) {
|
|
4358
|
+
let text;
|
|
4359
|
+
try { text = readFileSync(path, 'utf8'); } catch { return null; }
|
|
4360
|
+
const line = text.split(/\r?\n/).map((row) => row.trim()).find((row) => row.startsWith('@"'));
|
|
4361
|
+
const m = line ? /^@"([^"]+)"(?: "([^"]+)")? %\*$/.exec(line) : null;
|
|
4362
|
+
return m ? [m[1], ...(m[2] ? [m[2]] : [])] : null;
|
|
4363
|
+
}
|
package/mail/relay.mjs
CHANGED
|
@@ -270,7 +270,7 @@ export class Relays {
|
|
|
270
270
|
async #ensure(spec, step = () => {}, { stale = false } = {}) {
|
|
271
271
|
// a relay's hooks are the front door's verbs (mail step 1b): a stable supercode with no front door (a dev or cargo
|
|
272
272
|
// build's native) would answer none of them, and every mail the relay is handed would be blocked unfiled
|
|
273
|
-
if (!this.hooksServed()) throw new Error('relays run their hooks through the machine\'s stable supercode,
|
|
273
|
+
if (!this.hooksServed()) throw new Error('relays run their hooks through the machine\'s stable supercode, and none here serves them (no stable supercode was found, or it is a dev or cargo build); install supercode from npm to send to Claude sessions by relay');
|
|
274
274
|
const endpoint = await this.endpoint();
|
|
275
275
|
step('relay endpoint');
|
|
276
276
|
if (!endpoint) throw new Error('the relay endpoint is not served by this machine\'s daemon (its log says why); a Claude session can be sent mail once the daemon starts again');
|
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.129",
|
|
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",
|