@volter/supercode-teams 0.3.103 → 0.3.105

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 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;
@@ -2,9 +2,9 @@
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, appendFileSync } from 'node:fs';
5
+ import { readFileSync, writeFileSync, renameSync, mkdtempSync, existsSync, rmSync, appendFileSync, mkdirSync } from 'node:fs';
6
6
  import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
7
+ 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'));
@@ -45,9 +45,11 @@ if(writableRoots.length)config.push('-c',`sandbox_workspace_write.writable_roots
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
46
  // on the TUI's composer row, which the mail wake then reads as a person's draft and never types over (yuerans,
47
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.
48
+ // maintainer off for three hours). It is kept in a folder beside the bindings' (never among them: the carrier reads
49
+ // every file there on each poll, mail_question.rs `poll`), at most LOG_BYTES of it.
49
50
  const LOG_BYTES = 8 * 1024 * 1024;
50
- const serverLog = `${path.replace(/\.json$/, '')}.app-server.log`;
51
+ const serverLog = join(dirname(path), '..', 'native-launch-logs', `${basename(path).replace(/\.json$/, '')}.app-server.log`);
52
+ try { mkdirSync(dirname(serverLog), { recursive: true, mode: 0o700 }); } catch { /* its log is best kept */ }
51
53
  let logged = 0;
52
54
  const server = spawn(seed.program, [...config, 'app-server', '--listen', endpoint], { stdio: ['ignore', 'ignore', 'pipe'] });
53
55
  server.stderr.on('data', (chunk) => {
@@ -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
- export async function confirmNativeInitialInput({ binding, kind, text, startedAt }, { signal, deadlineMs, live }) {
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 (kind === 'claude-code') {
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.103",
3
+ "version": "0.3.105",
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",