@volter/supercode-teams 0.3.102 → 0.3.104
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/machine.mjs +17 -0
- package/native-codex-pane.mjs +22 -3
- package/native-initial-input.mjs +51 -3
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -1752,6 +1752,23 @@ export class TeamsMachine extends MachineCore {
|
|
|
1752
1752
|
if (params.env?.SUPERCODE_BOARD_PROCESS_GUARD === '1' && process.platform === 'win32') throw new Error('board process guard is unavailable on Windows; no unguarded session was launched');
|
|
1753
1753
|
const args = boardGuardArguments(params.kind,params.arguments ?? [],{cwd:params.cwd,enabled:params.env?.SUPERCODE_BOARD_PROCESS_GUARD === '1'});
|
|
1754
1754
|
const name = String(params.program ?? '').split(/[\\/]/).pop();
|
|
1755
|
+
// Windows has no Unix endpoint for a pane's own native server, so a fresh Codex there starts with no session to
|
|
1756
|
+
// type into: its rollout appears only with its first turn. Its first input is therefore Codex's own argv prompt,
|
|
1757
|
+
// as it is on the other platforms, and the receipt is the rollout that records it under CODEX_HOME. A batch
|
|
1758
|
+
// command line cannot carry a line break, so multi-line input keeps the pane and is typed as before.
|
|
1759
|
+
if (process.platform === 'win32' && params.kind === 'codex' && /^codex(?:\.cmd|\.exe)?$/i.test(name)) {
|
|
1760
|
+
const initialInput = params.initialInput && !/[\r\n\0]/.test(params.initialInput) && !args.includes('--') &&
|
|
1761
|
+
!args.some((arg) => arg === '--remote' || arg.startsWith('--remote=')) ? params.initialInput : null;
|
|
1762
|
+
if (!initialInput) return launchCommand(params.program, args);
|
|
1763
|
+
const codexHome = params.env?.CODEX_HOME || this.env.CODEX_HOME || join(homedir(), '.codex');
|
|
1764
|
+
// A resume appends to the rollout it names (`resume <id>`; `--last` names none). A fork writes a new
|
|
1765
|
+
// rollout, so its receipt is found as a fresh session's is.
|
|
1766
|
+
const verb = args.indexOf('resume');
|
|
1767
|
+
const named = verb >= 0 ? args.slice(verb + 1).find((arg) => /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(arg)) : null;
|
|
1768
|
+
nativeInput({ kind: params.kind, text: initialInput, startedAt: Date.now(), codexSessions: join(codexHome, 'sessions'),
|
|
1769
|
+
cwd: params.cwd || null, continuing: verb >= 0, resumeId: named ?? null });
|
|
1770
|
+
return launchCommand(params.program, [...args, '--', initialInput]);
|
|
1771
|
+
}
|
|
1755
1772
|
if (process.platform === 'win32' || !((params.kind === 'codex' && name === 'codex') ||
|
|
1756
1773
|
(params.kind === 'claude-code' && name === 'claude'))) return launchCommand(params.program, args);
|
|
1757
1774
|
this.nativeQuestionHooksSupported ??= spawnSync(this.supercodeBin, ['message', 'native-question-hook', '--help'], { env: this.env, timeout: 2000, stdio: 'ignore' }).status === 0;
|
package/native-codex-pane.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Supercode hosts neither the agent nor its run loop; it can attach to native
|
|
3
3
|
// question requests while the stock TUI remains the session's UI.
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { readFileSync, writeFileSync, renameSync, mkdtempSync, existsSync, rmSync } from 'node:fs';
|
|
5
|
+
import { readFileSync, writeFileSync, renameSync, mkdtempSync, existsSync, rmSync, appendFileSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
|
|
@@ -42,7 +42,23 @@ for (let index = 0; index < seed.arguments.length; index += 1) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
if(writableRoots.length)config.push('-c',`sandbox_workspace_write.writable_roots=${JSON.stringify(writableRoots)}`);
|
|
45
|
-
|
|
45
|
+
// The app-server's stderr is never the pane's: the pane is the TUI's screen, and a record the server writes there lands
|
|
46
|
+
// on the TUI's composer row, which the mail wake then reads as a person's draft and never types over (yuerans,
|
|
47
|
+
// 2026-10-06: `ERROR codex_models_manager::manager: failed to refresh available models` held every wake of the box
|
|
48
|
+
// maintainer off for three hours). It is kept beside the pane's binding instead, at most LOG_BYTES of it.
|
|
49
|
+
const LOG_BYTES = 8 * 1024 * 1024;
|
|
50
|
+
const serverLog = `${path.replace(/\.json$/, '')}.app-server.log`;
|
|
51
|
+
let logged = 0;
|
|
52
|
+
const server = spawn(seed.program, [...config, 'app-server', '--listen', endpoint], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
53
|
+
server.stderr.on('data', (chunk) => {
|
|
54
|
+
if (logged >= LOG_BYTES) return;
|
|
55
|
+
const kept = chunk.subarray(0, LOG_BYTES - logged);
|
|
56
|
+
logged += kept.length;
|
|
57
|
+
try {
|
|
58
|
+
appendFileSync(serverLog, kept, { mode: 0o600 });
|
|
59
|
+
if (logged >= LOG_BYTES) appendFileSync(serverLog, `\nsupercode: the app-server's stderr past ${LOG_BYTES} bytes is not kept\n`);
|
|
60
|
+
} catch { /* its log is best kept; the pane never gets it */ }
|
|
61
|
+
});
|
|
46
62
|
let tui;
|
|
47
63
|
let ended = false;
|
|
48
64
|
let serverError;
|
|
@@ -67,7 +83,10 @@ try {
|
|
|
67
83
|
tui = spawn(seed.program, args, { stdio: 'inherit' });
|
|
68
84
|
process.exitCode = await new Promise((resolve, reject) => { tui.once('exit', (code) => resolve(code ?? 1)); tui.once('error', reject); });
|
|
69
85
|
} catch (error) {
|
|
70
|
-
|
|
86
|
+
// a server that failed says why in its log, which the pane shows only now, as the pane's own failure
|
|
87
|
+
let why = '';
|
|
88
|
+
try { why = readFileSync(serverLog, 'utf8').slice(-2000).trim(); } catch { /* it wrote nothing */ }
|
|
89
|
+
process.stderr.write(`supercode: ${error.message}\n${why ? `its app-server's stderr (${serverLog}):\n${why}\n` : ''}`);
|
|
71
90
|
process.exitCode = 1;
|
|
72
91
|
} finally {
|
|
73
92
|
clearInterval(guard);
|
package/native-initial-input.mjs
CHANGED
|
@@ -1,12 +1,55 @@
|
|
|
1
1
|
// Native CLIs accept an initial prompt before their TUI starts. Their own
|
|
2
2
|
// transcript, not a collapsed paste badge or an empty editor, confirms receipt.
|
|
3
|
-
import { open, readFile } from 'node:fs/promises';
|
|
3
|
+
import { open, readdir, readFile, stat } from 'node:fs/promises';
|
|
4
|
+
import { join, win32 } from 'node:path';
|
|
4
5
|
import { StringDecoder } from 'node:string_decoder';
|
|
5
6
|
import { codexQuestions } from './native-questions.mjs';
|
|
6
7
|
|
|
7
8
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/** Rollouts one daemon's launches have already taken as their receipt, with when: a launch that began before a
|
|
11
|
+
* claim does not answer from that transcript, while a later launch (a resume of the same session) still can, since
|
|
12
|
+
* its receipt must be newer than it anyway. This orders launches; it does not prove ownership — two panes in one
|
|
13
|
+
* directory sent the same words within a second can still read each other's rollout, as the first to read wins. */
|
|
14
|
+
const claimedRollouts = new Map();
|
|
15
|
+
|
|
16
|
+
/** Rollouts Codex wrote since a launch, under `<CODEX_HOME>/sessions/YYYY/MM/DD` (Codex names the day in local
|
|
17
|
+
* time). A fresh session's rollout is new: the day it started and the day after, so a launch just before midnight
|
|
18
|
+
* still finds it (a fork writes a new one too). A resumed one appends to a rollout of any day, so the whole tree is read.
|
|
19
|
+
* Where a pane has no native server to ask (Windows), this is how its first turn's transcript is found. */
|
|
20
|
+
async function codexRolloutsSince(root, startedAt, continuing, resumeId) {
|
|
21
|
+
const dirs = continuing ? [root] : [...new Set([startedAt, startedAt + 86_400_000].map((t) => {
|
|
22
|
+
const d = new Date(t);
|
|
23
|
+
return join(root, String(d.getFullYear()), String(d.getMonth() + 1).padStart(2, '0'), String(d.getDate()).padStart(2, '0'));
|
|
24
|
+
}))];
|
|
25
|
+
const found = [];
|
|
26
|
+
for (const dir of dirs) {
|
|
27
|
+
let names;
|
|
28
|
+
try { names = await readdir(dir, { recursive: continuing }); } catch { continue; }
|
|
29
|
+
for (const name of names.filter((n) => /(?:^|[\\/])rollout-[^\\/]*\.jsonl$/.test(n))) {
|
|
30
|
+
const path = join(dir, name);
|
|
31
|
+
if ((claimedRollouts.get(path) ?? -Infinity) >= startedAt) continue;
|
|
32
|
+
if (resumeId && !path.endsWith(`-${resumeId}.jsonl`)) continue;
|
|
33
|
+
try { if ((await stat(path)).mtimeMs >= startedAt - 1000) found.push({ path, offset: 0 }); } catch { /* Gone since the listing. */ }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return found;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Whether a rollout's `session_meta` is this launch's session. A fresh one was begun after the launch, in the
|
|
40
|
+
* launch's directory; another Codex sent the same words elsewhere is not this pane's receipt. A continued one keeps
|
|
41
|
+
* the directory it was first made in, so it is named by its id instead (`resumeId`, in the scan). */
|
|
42
|
+
function codexRolloutIsLaunch(meta, { cwd, startedAt, continuing }) {
|
|
43
|
+
if (!meta) return false;
|
|
44
|
+
if (continuing) return true;
|
|
45
|
+
// A Git Bash path (`/c/Users/…`) names the same directory as Codex's `C:\Users\…`.
|
|
46
|
+
const resolved = (path) => win32.resolve(path.replace(/^\/([a-z])(?=\/|$)/i, '$1:'))
|
|
47
|
+
.replace(/[\\/]+$/, '').toLowerCase();
|
|
48
|
+
if (cwd && (typeof meta.cwd !== 'string' || resolved(meta.cwd) !== resolved(cwd))) return false;
|
|
49
|
+
return Date.parse(meta.timestamp) >= startedAt - 1000;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function confirmNativeInitialInput({ binding, kind, text, startedAt, codexSessions, cwd, continuing = false, resumeId = null }, { signal, deadlineMs, live }) {
|
|
10
53
|
const readers = new Map();
|
|
11
54
|
const expired = () => signal.aborted || Date.now() >= deadlineMs;
|
|
12
55
|
try {
|
|
@@ -14,7 +57,9 @@ export async function confirmNativeInitialInput({ binding, kind, text, startedAt
|
|
|
14
57
|
if (!await live()) return { delivered: false, reason: 'pane-gone' };
|
|
15
58
|
let paths = [];
|
|
16
59
|
try {
|
|
17
|
-
if (
|
|
60
|
+
if (codexSessions) {
|
|
61
|
+
paths = await codexRolloutsSince(codexSessions, startedAt, continuing, resumeId);
|
|
62
|
+
} else if (kind === 'claude-code') {
|
|
18
63
|
paths = [JSON.parse(await readFile(binding.replace(/\.json$/, '.session.json'), 'utf8'))];
|
|
19
64
|
} else {
|
|
20
65
|
const seed = JSON.parse(await readFile(binding, 'utf8'));
|
|
@@ -45,6 +90,8 @@ export async function confirmNativeInitialInput({ binding, kind, text, startedAt
|
|
|
45
90
|
const line = reader.buffered.slice(0, newline); reader.buffered = reader.buffered.slice(newline + 1);
|
|
46
91
|
let record;
|
|
47
92
|
try { record = JSON.parse(line); } catch { continue; }
|
|
93
|
+
if (codexSessions && record.type === 'session_meta' && !reader.meta) reader.meta = record.payload ?? {};
|
|
94
|
+
if (codexSessions && !codexRolloutIsLaunch(reader.meta, { cwd, startedAt, continuing })) continue;
|
|
48
95
|
let content;
|
|
49
96
|
if (kind === 'claude-code' && record.type === 'user' && !record.isMeta) content = record.message?.content;
|
|
50
97
|
if (kind === 'codex' && record.type === 'response_item' && record.payload?.type === 'message' &&
|
|
@@ -55,6 +102,7 @@ export async function confirmNativeInitialInput({ binding, kind, text, startedAt
|
|
|
55
102
|
// so a session can be told apart as one an agent launched (Claude Code's sessionId; Codex's rollout id).
|
|
56
103
|
if (value === text) {
|
|
57
104
|
const native = kind === 'claude-code' ? record.sessionId : /([0-9a-f]{8}-[0-9a-f-]{27})\.jsonl$/.exec(entry.path)?.[1];
|
|
105
|
+
if (codexSessions) claimedRollouts.set(entry.path, Date.now());
|
|
58
106
|
return { delivered: true, receipt: 'native-user-message', ...(typeof native === 'string' && native ? { native_session: native } : {}) };
|
|
59
107
|
}
|
|
60
108
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.104",
|
|
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",
|