@volter/supercode-teams 0.3.106 → 0.3.108
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/codex-startup.mjs +27 -0
- package/machine.mjs +133 -10
- package/native-initial-input.mjs +7 -1
- package/package.json +1 -1
- package/terminal-mail.mjs +9 -3
package/codex-startup.mjs
CHANGED
|
@@ -131,3 +131,30 @@ export async function settleCodexHookReview(host, sessionId, { signal, deadlineM
|
|
|
131
131
|
}
|
|
132
132
|
return signal.aborted ? 'aborted' : 'deadline';
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/** The most one bracketed paste carries into a Windows Codex: ConPTY and Codex drop a whole paste past about 8190
|
|
136
|
+
* characters (measured 2026-10-06, codex-cli 0.160.1: 8180 arrived, 8200 did not), while consecutive pastes join
|
|
137
|
+
* into one composer entry. */
|
|
138
|
+
export const CODEX_WINDOWS_PASTE_CHARS = 8000;
|
|
139
|
+
|
|
140
|
+
/** One character as a Windows Codex reads it from its console. Codex drops some BMP characters that arrive as plain
|
|
141
|
+
* text through ConPTY (measured: `—`, `…`, `✓` vanish while `é`, `日`, and astral `🙂` arrive), so every BMP
|
|
142
|
+
* character past ASCII goes as a win32-input-mode key event carrying its UTF-16 unit (VK_PACKET, 231), which ConPTY
|
|
143
|
+
* hands Codex as exactly that character. ASCII and astral characters go as they are. */
|
|
144
|
+
function codexWindowsCharacter(character) {
|
|
145
|
+
const unit = character.codePointAt(0);
|
|
146
|
+
if (unit < 0x80 || unit > 0xffff) return character;
|
|
147
|
+
return `\u001b[231;0;${unit};1;0;1_\u001b[231;0;${unit};0;0;1_`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Text as the raw writes that paste it into a Windows Codex composer: bracketed pastes of at most
|
|
151
|
+
* CODEX_WINDOWS_PASTE_CHARS characters each, non-ASCII BMP characters as key events. Written as they are (a terminal
|
|
152
|
+
* host turns `\n` into `\r`, which Codex keeps as a line break inside a paste); never submitted here. */
|
|
153
|
+
export function codexWindowsPastes(text) {
|
|
154
|
+
const characters = Array.from(String(text));
|
|
155
|
+
const pastes = [];
|
|
156
|
+
for (let at = 0; at < characters.length; at += CODEX_WINDOWS_PASTE_CHARS) {
|
|
157
|
+
pastes.push(`\u001b[200~${characters.slice(at, at + CODEX_WINDOWS_PASTE_CHARS).map(codexWindowsCharacter).join('')}\u001b[201~`);
|
|
158
|
+
}
|
|
159
|
+
return pastes;
|
|
160
|
+
}
|
package/machine.mjs
CHANGED
|
@@ -93,7 +93,7 @@ import { JobOwner, executionCapabilities, streamProgram, serveWorkspace } from '
|
|
|
93
93
|
import { TerminalMailbox, retainedTerminalMail } from './terminal-mail.mjs';
|
|
94
94
|
import { confirmNativeInitialInput } from './native-initial-input.mjs';
|
|
95
95
|
import { sessionPeople } from './session-people.mjs';
|
|
96
|
-
import { canonicalCodexFolderTrust, codexProgram, codexStartupArguments, codexUpdatePrompt, codexVersion, settleCodexHookReview } from './codex-startup.mjs';
|
|
96
|
+
import { canonicalCodexFolderTrust, codexProgram, codexStartupArguments, codexUpdatePrompt, codexVersion, codexWindowsPastes, settleCodexHookReview } from './codex-startup.mjs';
|
|
97
97
|
import { sessionNoticeTarget } from './session-notice-target.mjs';
|
|
98
98
|
import { serveAppDoor } from './app-door.mjs';
|
|
99
99
|
|
|
@@ -203,6 +203,26 @@ const TURN_CONFIRM_MS = 60_000;
|
|
|
203
203
|
* as the program (`open --new "npm run build"`), that line through this machine's own shell, so a caller on
|
|
204
204
|
* another machine need not know which shell or platform this one has.
|
|
205
205
|
*/
|
|
206
|
+
/** How long the Windows pane host's command line for `program args` is, quoted as it quotes a batch program's
|
|
207
|
+
* arguments (sdk/terminal `batchArgument`: `"` doubled, a `\` run before a `"` doubled, `%` as `%%cd:~,%`), plus
|
|
208
|
+
* the `cmd /e:ON /v:OFF /d /s /c "…"` around it. An upper bound for a program cmd does not run. */
|
|
209
|
+
function windowsCommandLineLength(program, args) {
|
|
210
|
+
const quoted = (value) => {
|
|
211
|
+
let length = 2;
|
|
212
|
+
let backslashes = 0;
|
|
213
|
+
for (const character of String(value)) {
|
|
214
|
+
if (character === '\\') backslashes += 1;
|
|
215
|
+
else {
|
|
216
|
+
if (character === '"') length += backslashes + 1;
|
|
217
|
+
backslashes = 0;
|
|
218
|
+
}
|
|
219
|
+
length += character === '%' ? 8 : 1;
|
|
220
|
+
}
|
|
221
|
+
return length + backslashes;
|
|
222
|
+
};
|
|
223
|
+
return [program, ...args].reduce((total, value) => total + quoted(value) + 1, 0) + 32;
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
function launchCommand(program, args) {
|
|
207
227
|
const line = typeof program === 'string' ? program.trim() : '';
|
|
208
228
|
if (!args.length && /\s/.test(line) && !existsSync(line)) {
|
|
@@ -478,6 +498,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
478
498
|
// Set by a service-run connector: how `machine.restart` ends this daemon so its service manager starts it again.
|
|
479
499
|
this.restartHandler = null;
|
|
480
500
|
this.launches = new Launches(this.p.launches);
|
|
501
|
+
// the panes' stable porthole hook and the machine's stable supercode brought up to this release's at start, under
|
|
502
|
+
// every pane already running
|
|
503
|
+
this.#portholeHook();
|
|
504
|
+
this.#stableSupercode();
|
|
481
505
|
// Windows has no tmux: its panes live in the machine's pane server, a process of their own that outlives this
|
|
482
506
|
// daemon (a restart or an upgrade never ends a pane), as tmux panes live in tmux's server.
|
|
483
507
|
if (process.platform === 'win32' && !terminal.PaneServerHost) throw new Error('panes on Windows need a newer @volter/supercode-terminal (one with PaneServerHost)');
|
|
@@ -1754,6 +1778,49 @@ export class TeamsMachine extends MachineCore {
|
|
|
1754
1778
|
}
|
|
1755
1779
|
}
|
|
1756
1780
|
|
|
1781
|
+
/**
|
|
1782
|
+
* The porthole hook as a pane names it: one stable path in the machine's home, never this package's own. A pane keeps
|
|
1783
|
+
* the NODE_OPTIONS it was launched with for as long as it runs, and `--require` of a missing file kills every node
|
|
1784
|
+
* program in it; a path inside a Teams package goes missing when the daemon moves to another package and the old one
|
|
1785
|
+
* is removed (yuerans, 2026-10-06: the daemon moved from ~/.local to /opt/homebrew, ~/.local's packages were
|
|
1786
|
+
* removed, and every node program in the older panes, supercode itself among them, died with MODULE_NOT_FOUND). The
|
|
1787
|
+
* package's hook is copied there whenever it differs (replaced whole, by rename), so every pane, old or new, runs
|
|
1788
|
+
* the daemon's current hook; a home that cannot hold it falls back to the package's path.
|
|
1789
|
+
*/
|
|
1790
|
+
/**
|
|
1791
|
+
* The machine's stable supercode (`<SUPERCODE_HOME>/bin/supercode`, claude_relay.rs `stable_program_path`), pointed at
|
|
1792
|
+
* this daemon's native once per daemon (`__stable-program`), for a command a pane records; this daemon's own
|
|
1793
|
+
* supercode when its native has no such verb or the shim cannot be written.
|
|
1794
|
+
*/
|
|
1795
|
+
#stableSupercode() {
|
|
1796
|
+
if (this.stableSupercodePath !== undefined) return this.stableSupercodePath ?? this.supercodeBin;
|
|
1797
|
+
this.stableSupercodePath = null;
|
|
1798
|
+
try {
|
|
1799
|
+
const answer = spawnSync(this.supercodeBin, ['__stable-program'], { env: this.env, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1800
|
+
const path = answer.status === 0 ? answer.stdout.trim() : '';
|
|
1801
|
+
if (path && isAbsolute(path) && existsSync(path)) this.stableSupercodePath = path;
|
|
1802
|
+
} catch { /* this daemon's own supercode */ }
|
|
1803
|
+
return this.stableSupercodePath ?? this.supercodeBin;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
#portholeHook() {
|
|
1807
|
+
if (this.portholeHookPath) return this.portholeHookPath;
|
|
1808
|
+
const stable = join(supercodeHome(this.env), 'porthole-hook.cjs');
|
|
1809
|
+
try {
|
|
1810
|
+
const hook = readFileSync(PORTHOLE_HOOK);
|
|
1811
|
+
let current = null;
|
|
1812
|
+
try { current = readFileSync(stable); } catch { /* not there yet */ }
|
|
1813
|
+
if (!current?.equals(hook)) {
|
|
1814
|
+
mkdirSync(dirname(stable), { recursive: true });
|
|
1815
|
+
const staged = `${stable}.${process.pid}.tmp`;
|
|
1816
|
+
writeFileSync(staged, hook, { mode: 0o644 });
|
|
1817
|
+
renameSync(staged, stable);
|
|
1818
|
+
}
|
|
1819
|
+
this.portholeHookPath = stable;
|
|
1820
|
+
} catch { this.portholeHookPath = PORTHOLE_HOOK; }
|
|
1821
|
+
return this.portholeHookPath;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1757
1824
|
/**
|
|
1758
1825
|
* The environment every launched pane carries: the porthole hook preloaded into
|
|
1759
1826
|
* each node process the pane runs, the announce directory it writes into, and the pane's
|
|
@@ -1766,9 +1833,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
1766
1833
|
env.PATH ??= this.env.PATH;
|
|
1767
1834
|
// Its CLI reads this machine's contexts and sockets, not whatever home that server started with.
|
|
1768
1835
|
if (this.env.SUPERCODE_HOME) env.SUPERCODE_HOME ??= this.env.SUPERCODE_HOME;
|
|
1769
|
-
|
|
1836
|
+
// an inherited porthole preload (a daemon started from a pane, or an older daemon's path) is replaced, never stacked
|
|
1837
|
+
const inherited = String(env.NODE_OPTIONS ?? this.env.NODE_OPTIONS ?? '').replace(/\s*--require[= ]("[^"]*porthole-hook\.cjs"|\S*porthole-hook\.cjs)/g, '');
|
|
1770
1838
|
// Quoted: NODE_OPTIONS splits on spaces, and a home path may hold one (Windows user folders often do).
|
|
1771
|
-
env.NODE_OPTIONS = `${inherited} --require ${JSON.stringify(
|
|
1839
|
+
env.NODE_OPTIONS = `${inherited} --require ${JSON.stringify(this.#portholeHook())}`.trim();
|
|
1772
1840
|
env.SUPERCODE_TEAMS_PORTS_DIR = this.portsDir;
|
|
1773
1841
|
env.SUPERCODE_TEAMS_PANE = contextKey;
|
|
1774
1842
|
return env;
|
|
@@ -1782,20 +1850,24 @@ export class TeamsMachine extends MachineCore {
|
|
|
1782
1850
|
const name = String(params.program ?? '').split(/[\\/]/).pop();
|
|
1783
1851
|
// Windows has no Unix endpoint for a pane's own native server, so a fresh Codex there starts with no session to
|
|
1784
1852
|
// type into: its rollout appears only with its first turn. Its first input is therefore Codex's own argv prompt,
|
|
1785
|
-
// as it is on the other platforms, and the receipt is the rollout that records it under CODEX_HOME.
|
|
1786
|
-
// command line cannot carry a line break
|
|
1853
|
+
// as it is on the other platforms, and the receipt is the rollout that records it under CODEX_HOME. Input a
|
|
1854
|
+
// command line cannot carry — a line break (a batch line ends there), or past cmd's 8191 characters
|
|
1855
|
+
// (CreateProcess's 32767 for an .exe) — is pasted into the composer once it is up instead
|
|
1856
|
+
// (#typeCodexFirstInput), under the same receipt.
|
|
1787
1857
|
if (process.platform === 'win32' && params.kind === 'codex' && /^codex(?:\.cmd|\.exe)?$/i.test(name)) {
|
|
1788
|
-
const initialInput = params.initialInput &&
|
|
1858
|
+
const initialInput = params.initialInput && !args.includes('--') &&
|
|
1789
1859
|
!args.some((arg) => arg === '--remote' || arg.startsWith('--remote=')) ? params.initialInput : null;
|
|
1790
1860
|
if (!initialInput) return launchCommand(params.program, args);
|
|
1861
|
+
const typed = /[\r\n\0]/.test(initialInput) || windowsCommandLineLength(params.program, [...args, '--', initialInput]) >
|
|
1862
|
+
(/\.(cmd|bat)$/i.test(params.program) ? 8000 : 32000);
|
|
1791
1863
|
const codexHome = params.env?.CODEX_HOME || this.env.CODEX_HOME || join(homedir(), '.codex');
|
|
1792
1864
|
// A resume appends to the rollout it names (`resume <id>`; `--last` names none). A fork writes a new
|
|
1793
1865
|
// rollout, so its receipt is found as a fresh session's is.
|
|
1794
1866
|
const verb = args.indexOf('resume');
|
|
1795
1867
|
const named = verb >= 0 ? args.slice(verb + 1).find((arg) => /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(arg)) : null;
|
|
1796
1868
|
nativeInput({ kind: params.kind, text: initialInput, startedAt: Date.now(), codexSessions: join(codexHome, 'sessions'),
|
|
1797
|
-
cwd: params.cwd || null, continuing: verb >= 0, resumeId: named ?? null });
|
|
1798
|
-
return launchCommand(params.program, [...args, '--', initialInput]);
|
|
1869
|
+
cwd: params.cwd || null, continuing: verb >= 0, resumeId: named ?? null, typed });
|
|
1870
|
+
return launchCommand(params.program, typed ? args : [...args, '--', initialInput]);
|
|
1799
1871
|
}
|
|
1800
1872
|
if (process.platform === 'win32' || !((params.kind === 'codex' && name === 'codex') ||
|
|
1801
1873
|
(params.kind === 'claude-code' && name === 'claude'))) return launchCommand(params.program, args);
|
|
@@ -1817,7 +1889,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
1817
1889
|
if (initialInput) nativeInput({ binding, kind: params.kind, text: initialInput, startedAt: Date.now() });
|
|
1818
1890
|
const claudeCommand = (next) => launchCommand(params.program, initialInput ? [...next, '--', initialInput] : next);
|
|
1819
1891
|
const quote = (value) => `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
1820
|
-
|
|
1892
|
+
// the machine's stable supercode: the session outlives this daemon's install path (an upgrade or a move)
|
|
1893
|
+
const command = `env SUPERCODE_HOME=${quote(mailHome)} ${quote(this.#stableSupercode())} message native-question-hook ${quote(binding)}`;
|
|
1821
1894
|
const handler = { type: 'command', command, timeout: 3600 };
|
|
1822
1895
|
if (params.kind === 'claude-code') {
|
|
1823
1896
|
const hooks = { SessionStart: [{ hooks: [handler] }], PreToolUse: [{ matcher: 'AskUserQuestion', hooks: [handler] }], PostToolUse: [{ matcher: 'AskUserQuestion', hooks: [handler] }] };
|
|
@@ -2093,11 +2166,26 @@ export class TeamsMachine extends MachineCore {
|
|
|
2093
2166
|
#deliverInitialInput({ key, contextKey, session, kind, by, input, act, nativeInput, executable = {}, resumed = {} }) {
|
|
2094
2167
|
const abort = new AbortController();
|
|
2095
2168
|
const task = (async () => {
|
|
2169
|
+
let nativeReadyBy = 'native-prompt';
|
|
2170
|
+
if (nativeInput?.typed) {
|
|
2171
|
+
// A Windows Codex input its command line could not carry: pasted once the composer is up, and confirmed by
|
|
2172
|
+
// the same rollout receipt as an argv prompt. Its pane has no native session to mail yet.
|
|
2173
|
+
nativeReadyBy = await this.#awaitInputReady(session, contextKey, kind, abort.signal);
|
|
2174
|
+
if (nativeReadyBy === 'aborted') return;
|
|
2175
|
+
const typed = nativeReadyBy === 'pane-gone' || nativeReadyBy === 'deadline' ? { delivered: false, reason: nativeReadyBy }
|
|
2176
|
+
: await this.#typeCodexFirstInput(session, input, abort.signal);
|
|
2177
|
+
if (!typed.delivered) {
|
|
2178
|
+
this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: false,
|
|
2179
|
+
inputReadyBy: nativeReadyBy, inputError: typed.reason });
|
|
2180
|
+
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${typed.reason}`);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2096
2184
|
if (nativeInput) {
|
|
2097
2185
|
const result = await confirmNativeInitialInput(nativeInput, { signal: abort.signal,
|
|
2098
2186
|
deadlineMs: Date.now() + INITIAL_INPUT_READY_DEADLINE_MS, live: async () => Boolean(await this.#paneByContext(contextKey)) });
|
|
2099
2187
|
this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: result.delivered,
|
|
2100
|
-
inputReadyBy:
|
|
2188
|
+
inputReadyBy: nativeReadyBy, ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }),
|
|
2101
2189
|
...(result.native_session ? { native_session: result.native_session } : {}) });
|
|
2102
2190
|
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : result.queued ? 'input-pending' : 'failed',
|
|
2103
2191
|
result.delivered ? 'first input confirmed by native user message' : `native first input unconfirmed: ${result.reason}`);
|
|
@@ -2239,6 +2327,41 @@ export class TeamsMachine extends MachineCore {
|
|
|
2239
2327
|
return filed?.code === 0 ? null : `the line was typed but its sender was not recorded: ${filed?.text ?? 'the filing door answered nothing'}`;
|
|
2240
2328
|
}
|
|
2241
2329
|
|
|
2330
|
+
/**
|
|
2331
|
+
* A fresh Windows Codex's first input, pasted into its empty composer and submitted once: the input its command
|
|
2332
|
+
* line could not carry. Each bracketed paste stays under CODEX_WINDOWS_PASTE_CHARS, and Codex joins consecutive
|
|
2333
|
+
* ones into one entry. Delivery is not claimed here — the caller's rollout receipt confirms what Codex took.
|
|
2334
|
+
*/
|
|
2335
|
+
async #typeCodexFirstInput(session, text, signal) {
|
|
2336
|
+
const body = String(text).replace(/\r\n?/g, '\n').replace(/\n+$/u, '');
|
|
2337
|
+
if (!body.trim() || /[\u0000-\u0008\u000b-\u001f\u007f]/u.test(body)) return { delivered: false, reason: 'unsupported-text' };
|
|
2338
|
+
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
2339
|
+
this.inputInFlight.add(session.id);
|
|
2340
|
+
try {
|
|
2341
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2342
|
+
// Styled, so the composer's dim placeholder ("Ask Codex to do anything") reads as empty.
|
|
2343
|
+
const composer = async () => terminal.composerText?.('codex', await this.host.capture(session.id, { preserveEscapes: true })) ?? null;
|
|
2344
|
+
if (await composer() !== '') return { delivered: false, reason: 'composer-not-empty' };
|
|
2345
|
+
for (const paste of codexWindowsPastes(body)) {
|
|
2346
|
+
if (signal.aborted) return { delivered: false, reason: 'aborted' };
|
|
2347
|
+
await this.host.sendInput(session.id, paste, { submitMode: 'none' });
|
|
2348
|
+
await pause(300);
|
|
2349
|
+
}
|
|
2350
|
+
let held = false;
|
|
2351
|
+
for (let attempt = 0; attempt < 20 && !held && !signal.aborted; attempt += 1) {
|
|
2352
|
+
held = Boolean(await composer());
|
|
2353
|
+
if (!held) await pause(150);
|
|
2354
|
+
}
|
|
2355
|
+
if (!held) return { delivered: false, reason: 'paste-unconfirmed' };
|
|
2356
|
+
await this.host.sendInput(session.id, '\r', { submitMode: 'single' });
|
|
2357
|
+
return { delivered: true };
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
return { delivered: false, reason: error?.message ?? String(error) };
|
|
2360
|
+
} finally {
|
|
2361
|
+
this.inputInFlight.delete(session.id);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2242
2365
|
async #deliverPaneText(session, contextKey, text, options = {}) {
|
|
2243
2366
|
const kind = this.#paneKind(contextKey, session);
|
|
2244
2367
|
if (kind === 'codex' && codexUpdatePrompt(await this.host.capture(session.id, { lines: 120 }))) return { delivered: false, reason: 'codex-update-prompt: delivery refused; restart Codex with check_for_update_on_startup=false' };
|
package/native-initial-input.mjs
CHANGED
|
@@ -7,6 +7,9 @@ import { codexQuestions } from './native-questions.mjs';
|
|
|
7
7
|
|
|
8
8
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
9
|
|
|
10
|
+
/** Input as a composer takes it: line ends as `\n`, without the outer whitespace a submit trims. */
|
|
11
|
+
const normalizedInput = (text) => String(text).replace(/\r\n?/g, '\n').trim();
|
|
12
|
+
|
|
10
13
|
/** Rollouts one daemon's launches have already taken as their receipt, with when: a launch that began before a
|
|
11
14
|
* claim does not answer from that transcript, while a later launch (a resume of the same session) still can, since
|
|
12
15
|
* its receipt must be newer than it anyway. This orders launches; it does not prove ownership — two panes in one
|
|
@@ -100,7 +103,10 @@ export async function confirmNativeInitialInput({ binding, kind, text, startedAt
|
|
|
100
103
|
content.every((part) => ['text', 'input_text'].includes(part.type)) ? content.map((part) => part.text).join('') : null;
|
|
101
104
|
// The transcript that confirms the input also names the session it belongs to: the launch records it,
|
|
102
105
|
// so a session can be told apart as one an agent launched (Claude Code's sessionId; Codex's rollout id).
|
|
103
|
-
|
|
106
|
+
// A Windows Codex input may have been pasted: its line ends normalized and trailing newlines dropped.
|
|
107
|
+
const same = codexSessions && typeof value === 'string'
|
|
108
|
+
? normalizedInput(value) === normalizedInput(text) : value === text;
|
|
109
|
+
if (same) {
|
|
104
110
|
const native = kind === 'claude-code' ? record.sessionId : /([0-9a-f]{8}-[0-9a-f-]{27})\.jsonl$/.exec(entry.path)?.[1];
|
|
105
111
|
if (codexSessions) claimedRollouts.set(entry.path, Date.now());
|
|
106
112
|
return { delivered: true, receipt: 'native-user-message', ...(typeof native === 'string' && native ? { native_session: native } : {}) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.108",
|
|
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",
|
package/terminal-mail.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { StringDecoder } from 'node:string_decoder';
|
|
8
|
-
import { codexUpdatePrompt } from './codex-startup.mjs';
|
|
8
|
+
import { codexUpdatePrompt, codexWindowsPastes } from './codex-startup.mjs';
|
|
9
9
|
|
|
10
10
|
// When process `pid` started (ms), from `ps -o etime=` ([[dd-]hh:]mm:ss); null when it cannot be read.
|
|
11
11
|
function startedAt(pid) {
|
|
@@ -154,8 +154,14 @@ export class TerminalMailbox {
|
|
|
154
154
|
// it never guesses that an unacknowledged paste did not happen.
|
|
155
155
|
Object.assign(batch, { phase: 'paste_started', terminal: this.session.id, pid: this.session.rootPid ?? null, pasted_at: Date.now() }); this.save();
|
|
156
156
|
// The terminal host's final newline selects one bracketed paste;
|
|
157
|
-
// submitMode:none holds the separate Enter until the paste completes.
|
|
158
|
-
|
|
157
|
+
// submitMode:none holds the separate Enter until the paste completes. A Windows Codex takes its own pastes:
|
|
158
|
+
// at most 8000 characters each, non-ASCII as key events (codex-startup.mjs codexWindowsPastes).
|
|
159
|
+
if (process.platform === 'win32' && batch.kind === 'codex') {
|
|
160
|
+
for (const paste of codexWindowsPastes(batch.text)) {
|
|
161
|
+
await this.host.sendInput(this.session.id, paste, { submitMode: 'none' });
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
163
|
+
}
|
|
164
|
+
} else await this.host.sendInput(this.session.id, `${batch.text}\n`, { submitMode: 'none' });
|
|
159
165
|
batch.phase = 'pasted'; this.save();
|
|
160
166
|
}
|
|
161
167
|
if (batch.phase === 'paste_started') {
|