@volter/supercode-teams 0.2.159 → 0.2.161
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/bin/workspace.mjs +4 -3
- package/codex-startup.mjs +50 -1
- package/machine.mjs +19 -10
- package/mail-history.mjs +29 -4
- package/package.json +1 -1
- package/terminal-mail.mjs +8 -0
package/bin/workspace.mjs
CHANGED
|
@@ -680,7 +680,8 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
|
|
|
680
680
|
}
|
|
681
681
|
if (verb === 'connect') {
|
|
682
682
|
let saved = state.read();
|
|
683
|
-
const path = realpathSync(resolve(flags.cwd ?? '.'));
|
|
683
|
+
const path = realpathSync(resolve(flags.cwd ?? flags.root ?? '.'));
|
|
684
|
+
if (flags.cwd && flags.root && path !== realpathSync(resolve(flags.root))) usage('teams connect: --cwd and --root must name the same folder');
|
|
684
685
|
// A broad root is refused before enrolling anything. The service reruns connect without a terminal, and
|
|
685
686
|
// without --force, for the root accepted here; a broad root enrolled before rules covered subfolders keeps
|
|
686
687
|
// its exact-folder sync there.
|
|
@@ -707,7 +708,7 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
|
|
|
707
708
|
if (flags.install || (!flags.foreground && process.stdin.isTTY && process.stdout.isTTY)) {
|
|
708
709
|
const serviceBin = process.env.SUPERCODE_BIN;
|
|
709
710
|
if (!serviceBin) unsupported('--install requires invocation through the native supercode CLI');
|
|
710
|
-
const serviceEnv = { ...process.env, SUPERCODE_TEAMS_ENTRY: fileURLToPath(new URL('./teams.mjs', import.meta.url)) };
|
|
711
|
+
const serviceEnv = { ...process.env, SUPERCODE_POPUPS: process.env.SUPERCODE_POPUPS ?? '0', SUPERCODE_TEAMS_ENTRY: fileURLToPath(new URL('./teams.mjs', import.meta.url)) };
|
|
711
712
|
// Refuse source-backed installs before Windows stops the running connector.
|
|
712
713
|
try {
|
|
713
714
|
execFileSync(serviceBin, ['__teams-connector-service', 'validate', contextName, path, context.server_id, context.team_id], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: serviceEnv });
|
|
@@ -754,7 +755,7 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
|
|
|
754
755
|
const connector = new TeamsConnector({ server: context.origin, teamId: context.team_id, machineId: saved.machine.id, sourceId: saved.source.id, credential: saved.credential.token, stateDir: state.directory, core, largeCore, slowCore, liveCore, hostKeyId });
|
|
755
756
|
// A connected machine is a person's: each pane it launches opens a window on its screen (SUPERCODE_POPUPS=0: none).
|
|
756
757
|
const machineEvents = new MachineEvents({home:daemonHome,server:context.origin,teamId:context.team_id,credential:saved.credential.token,machineName:saved.machine.name,binary});
|
|
757
|
-
const machine = new TeamsMachine({ home: daemonHome, supercode: binary, popups: process.env.SUPERCODE_POPUPS !== '0', ...(flags.tmux_socket ? { tmuxSocket: String(flags.tmux_socket) } : {}), link: { server: context.origin, teamId: context.team_id, machineId: saved.machine.id, credential: saved.credential.token, incarnationId: connector.state.incarnation_id, configuration: () => connector.configuration() }, onTurn: (turn) => connector.noteTurn(turn), onTurnWithdrawn: (turn) => connector.withdrawTurn(turn) });
|
|
758
|
+
const machine = new TeamsMachine({ home: daemonHome, supercode: binary, popups: (process.env.SUPERCODE_POPUPS ?? '0') !== '0', ...(flags.tmux_socket ? { tmuxSocket: String(flags.tmux_socket) } : {}), link: { server: context.origin, teamId: context.team_id, machineId: saved.machine.id, credential: saved.credential.token, incarnationId: connector.state.incarnation_id, configuration: () => connector.configuration() }, onTurn: (turn) => connector.noteTurn(turn), onTurnWithdrawn: (turn) => connector.withdrawTurn(turn) });
|
|
758
759
|
machine.machineEvents = machineEvents;
|
|
759
760
|
machine.on('link', (event) => { if (event.event === 'replaced') process.stderr.write(`supercode teams: ${event.error}. Two connectors were running for this machine; stop one of them (\`supercode teams disconnect\` where it should not run).\n`); });
|
|
760
761
|
// The link's state for the watch after start: down until it is up, down again (since when) when it drops. A
|
package/codex-startup.mjs
CHANGED
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
// Codex can still show hook review on resume despite its invocation trust flag.
|
|
2
2
|
// Act only on this exact startup menu, and never submit without reading selection.
|
|
3
3
|
import { composerText } from '@volter/supercode-terminal';
|
|
4
|
-
import { realpathSync } from 'node:fs';
|
|
4
|
+
import { realpathSync, accessSync, constants, statSync } from 'node:fs';
|
|
5
|
+
import { join, isAbsolute } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
5
8
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
9
|
|
|
10
|
+
export function codexProgram(env = process.env) {
|
|
11
|
+
const program = env.SUPERCODE_CODEX_BIN?.trim() || join(homedir(), '.local', 'bin', process.platform === 'win32' ? 'codex.cmd' : 'codex');
|
|
12
|
+
if (!isAbsolute(program)) throw new Error('SUPERCODE_CODEX_BIN must name an absolute executable path');
|
|
13
|
+
try {
|
|
14
|
+
accessSync(program, constants.X_OK);
|
|
15
|
+
if (!statSync(program).isFile()) throw new Error('not a file');
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(`consolidated Codex executable is unavailable at ${program}; install it there or set SUPERCODE_CODEX_BIN on this machine`);
|
|
18
|
+
}
|
|
19
|
+
return program;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function codexVersion(program, env) {
|
|
23
|
+
const command = process.platform === 'win32' && /\.cmd$/i.test(program)
|
|
24
|
+
? { program: 'powershell.exe', args: ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(`& '${program.replaceAll("'", "''")}' --version`, 'utf16le').toString('base64')] }
|
|
25
|
+
: { program, args: ['--version'] };
|
|
26
|
+
const observed = spawnSync(command.program, command.args, { encoding: 'utf8', timeout: 3000, env, maxBuffer: 4096 });
|
|
27
|
+
if (observed.error || observed.status !== 0 || !/^codex(?:-cli)?\s+\S+/.test(observed.stdout?.trim() ?? '')) throw new Error(`consolidated Codex at ${program} did not report its CLI version`);
|
|
28
|
+
return observed.stdout.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function codexStartupArguments(args = []) {
|
|
32
|
+
const normalized = [];
|
|
33
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
34
|
+
const argument = args[index];
|
|
35
|
+
if (argument === '--') { normalized.push(...args.slice(index)); break; }
|
|
36
|
+
if (argument === '-c' || argument === '--config') {
|
|
37
|
+
const value = args[index + 1];
|
|
38
|
+
if (value?.split('=')[0].trim() === 'check_for_update_on_startup') { index += 1; continue; }
|
|
39
|
+
} else if (/^(?:--config=|-c=?)/.test(argument)) {
|
|
40
|
+
const value = argument.replace(/^(?:--config=|-c=?)/, '');
|
|
41
|
+
if (value.split('=')[0].trim() === 'check_for_update_on_startup') continue;
|
|
42
|
+
}
|
|
43
|
+
normalized.push(argument);
|
|
44
|
+
}
|
|
45
|
+
return ['-c', 'check_for_update_on_startup=false', ...normalized];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function codexUpdatePrompt(screen) {
|
|
49
|
+
const text = String(screen).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
50
|
+
const start = text.toLowerCase().lastIndexOf('update available');
|
|
51
|
+
if (start < 0) return false;
|
|
52
|
+
const menu = text.slice(start);
|
|
53
|
+
return /1\.\s*Update now/i.test(menu) && /2\.\s*Skip/i.test(menu) && !/(?:\? for shortcuts|context left|esc to interrupt)/i.test(menu);
|
|
54
|
+
}
|
|
55
|
+
|
|
7
56
|
// CLI and portable SDK callers can name a directory through a filesystem alias.
|
|
8
57
|
// Resolve only on the executing machine, before the native seed/server is built.
|
|
9
58
|
export function canonicalCodexFolderTrust(cwd, args = []) {
|
package/machine.mjs
CHANGED
|
@@ -52,7 +52,7 @@ import { popupTerminal, openPopup } from './popup-terminals.mjs';
|
|
|
52
52
|
import { JobOwner, executionCapabilities, streamProgram, serveWorkspace } from './execution.mjs';
|
|
53
53
|
import { TerminalMailbox, retainedTerminalMail } from './terminal-mail.mjs';
|
|
54
54
|
import { confirmNativeInitialInput } from './native-initial-input.mjs';
|
|
55
|
-
import { canonicalCodexFolderTrust, settleCodexHookReview } from './codex-startup.mjs';
|
|
55
|
+
import { canonicalCodexFolderTrust, codexProgram, codexStartupArguments, codexUpdatePrompt, codexVersion, settleCodexHookReview } from './codex-startup.mjs';
|
|
56
56
|
|
|
57
57
|
/** The preload every launched pane carries: it announces the ports a pane's node processes listen on. */
|
|
58
58
|
const PORTHOLE_HOOK = fileURLToPath(new URL('./porthole-hook.cjs', import.meta.url));
|
|
@@ -869,13 +869,13 @@ export class TeamsMachine extends MachineCore {
|
|
|
869
869
|
* Never rejects. The only caller is a fire-and-forget from the request path,
|
|
870
870
|
* where an unhandled rejection would take the daemon down over one pane.
|
|
871
871
|
*/
|
|
872
|
-
#deliverInitialInput({ key, contextKey, session, kind, by, input, act, nativeInput }) {
|
|
872
|
+
#deliverInitialInput({ key, contextKey, session, kind, by, input, act, nativeInput, executable = {} }) {
|
|
873
873
|
const abort = new AbortController();
|
|
874
874
|
const task = (async () => {
|
|
875
875
|
if (nativeInput) {
|
|
876
876
|
const result = await confirmNativeInitialInput(nativeInput, { signal: abort.signal,
|
|
877
877
|
deadlineMs: Date.now() + INITIAL_INPUT_READY_DEADLINE_MS, live: async () => Boolean(await this.#paneByContext(contextKey)) });
|
|
878
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: result.delivered,
|
|
878
|
+
this.launches.record({ key, pane: contextKey, kind, by, ...executable, status: 'ready', inputDelivered: result.delivered,
|
|
879
879
|
inputReadyBy: 'native-prompt', ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }) });
|
|
880
880
|
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : 'failed',
|
|
881
881
|
result.delivered ? 'first input confirmed by native user message' : `native first input unconfirmed: ${result.reason}`);
|
|
@@ -884,19 +884,19 @@ export class TeamsMachine extends MachineCore {
|
|
|
884
884
|
const readyBy = await this.#awaitInputReady(session, contextKey, kind, abort.signal);
|
|
885
885
|
if (readyBy === 'aborted') return;
|
|
886
886
|
if (readyBy === 'pane-gone' || readyBy === 'deadline') {
|
|
887
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: false, inputReadyBy: readyBy });
|
|
887
|
+
this.launches.record({ key, pane: contextKey, kind, by, ...executable, status: 'ready', inputDelivered: false, inputReadyBy: readyBy });
|
|
888
888
|
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${readyBy}`);
|
|
889
889
|
return;
|
|
890
890
|
}
|
|
891
891
|
const result = await this.#deliverPaneText(session, contextKey, input, { signal: abort.signal });
|
|
892
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: result.delivered,
|
|
892
|
+
this.launches.record({ key, pane: contextKey, kind, by, ...executable, status: 'ready', inputDelivered: result.delivered,
|
|
893
893
|
inputReadyBy: readyBy, ...(!result.delivered ? { inputError: result.reason } : {}) });
|
|
894
894
|
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : 'failed',
|
|
895
895
|
result.delivered ? `first input delivered (${readyBy})` : `first input unconfirmed: ${result.reason}`);
|
|
896
896
|
})().catch((error) => {
|
|
897
897
|
// Nothing above is expected to throw; if something does, the launch record
|
|
898
898
|
// is where it is answerable, not a crashed daemon.
|
|
899
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: false, inputError: error?.message ?? String(error) });
|
|
899
|
+
this.launches.record({ key, pane: contextKey, kind, by, ...executable, status: 'ready', inputDelivered: false, inputError: error?.message ?? String(error) });
|
|
900
900
|
}).finally(() => {
|
|
901
901
|
if (this.pendingInitialInputs.get(key)?.task === task) this.pendingInitialInputs.delete(key);
|
|
902
902
|
});
|
|
@@ -937,6 +937,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
937
937
|
|
|
938
938
|
async #deliverPaneText(session, contextKey, text, options = {}) {
|
|
939
939
|
const kind = this.#paneKind(contextKey, session);
|
|
940
|
+
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' };
|
|
940
941
|
if (['codex', 'grok', 'gemini', 'goose', 'opencode', 'pi', 'hermes', 'openclaw'].includes(kind)) return this.#terminalMail(session, contextKey, kind, submission(text, false), options);
|
|
941
942
|
if(this.inputRepairs[contextKey])return {delivered:false,reason:'input-cleanup-pending',cleanup_required:true};
|
|
942
943
|
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
@@ -1435,6 +1436,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
1435
1436
|
case 'launch': {
|
|
1436
1437
|
// A pane launch. Operators only; the body may carry secrets and is never logged.
|
|
1437
1438
|
if (!operator) throw Object.assign(new Error('launch is for operators'), { code: 403 });
|
|
1439
|
+
const programName = String(params.program).split(/[\\/]/).pop().replace(/\.(?:exe|cmd)$/i, '');
|
|
1440
|
+
if (programName === 'codex') params = { ...params, kind: 'codex' };
|
|
1438
1441
|
const key = params.key ?? `lk_${randomBytes(9).toString('base64url')}`;
|
|
1439
1442
|
const existing = this.launches.find(key);
|
|
1440
1443
|
// A key whose launch is in flight, or whose pane still lives, is reused; a failed
|
|
@@ -1453,7 +1456,12 @@ export class TeamsMachine extends MachineCore {
|
|
|
1453
1456
|
if (params.kind === 'claude-code' && params.cwd && (params.arguments ?? []).includes('--dangerously-skip-permissions')) trustClaudeFolder(params.cwd);
|
|
1454
1457
|
let session;
|
|
1455
1458
|
let nativeInput;
|
|
1459
|
+
let executable = {};
|
|
1456
1460
|
try {
|
|
1461
|
+
if (params.kind === 'codex') {
|
|
1462
|
+
params = { ...params, program: codexProgram(this.env), arguments: codexStartupArguments(params.arguments) };
|
|
1463
|
+
executable = { program: params.program, cliVersion: codexVersion(params.program, this.env) };
|
|
1464
|
+
}
|
|
1457
1465
|
if (process.platform !== 'win32' && !String(params.program).includes('/')) {
|
|
1458
1466
|
const search = params.env?.PATH ?? this.env.PATH ?? '';
|
|
1459
1467
|
const program = search.split(':').map(dir => resolve(dir || params.cwd || process.cwd(), params.program)).find(path => {
|
|
@@ -1474,11 +1482,11 @@ export class TeamsMachine extends MachineCore {
|
|
|
1474
1482
|
name: contextKey,
|
|
1475
1483
|
});
|
|
1476
1484
|
} catch (error) {
|
|
1477
|
-
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, status: 'failed', error: error.message });
|
|
1485
|
+
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...executable, status: 'failed', error: error.message });
|
|
1478
1486
|
act('launch', `pane:${contextKey}`, 'failed', error.code ?? error.message);
|
|
1479
1487
|
throw error;
|
|
1480
1488
|
}
|
|
1481
|
-
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, status: 'ready' });
|
|
1489
|
+
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...executable, status: 'ready' });
|
|
1482
1490
|
if (params.kind === 'codex' && (params.arguments ?? []).includes('--dangerously-bypass-hook-trust')) this.#settleCodexStartup(key, contextKey, session, act);
|
|
1483
1491
|
act('launch', `pane:${contextKey}`, 'ok', params.kind ?? params.program);
|
|
1484
1492
|
if (typeof params.name === 'string' && params.name.trim()) {
|
|
@@ -1495,7 +1503,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1495
1503
|
// outcome is recorded against the launch key, so `launches.get|list`
|
|
1496
1504
|
// answers "did my message go in, and was it late?" without anyone
|
|
1497
1505
|
// reading the screen.
|
|
1498
|
-
if (params.initialInput) this.#deliverInitialInput({ key, contextKey, session, kind: params.kind ?? null, by, input: params.initialInput, act, nativeInput });
|
|
1506
|
+
if (params.initialInput) this.#deliverInitialInput({ key, contextKey, session, kind: params.kind ?? null, by, input: params.initialInput, act, nativeInput, executable });
|
|
1499
1507
|
// A program that ends at once (not found, not runnable, failing on its first line) takes its pane with it:
|
|
1500
1508
|
// the caller is told, with its exit status, instead of being handed a pane that is already gone.
|
|
1501
1509
|
await new Promise((resolve) => setTimeout(resolve, LAUNCH_SETTLE_MS));
|
|
@@ -1504,7 +1512,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1504
1512
|
// Finished (it ran to a clean end) is an answer; exited (it failed at once) is the caller's error.
|
|
1505
1513
|
const exit = settled?.dead ? deadPaneExit(settled) : paneExit(contextKey);
|
|
1506
1514
|
const status = exit && !exit.failed ? 'finished' : 'exited';
|
|
1507
|
-
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, status, error: exit?.failed ? exit.how : undefined });
|
|
1515
|
+
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...executable, status, error: exit?.failed ? exit.how : undefined });
|
|
1508
1516
|
return { key, pane: contextKey, reused: false, status, exit: exit?.how ?? null };
|
|
1509
1517
|
}
|
|
1510
1518
|
if (this.popups && params.popup !== false) this.#popupWindow(contextKey, session);
|
|
@@ -1585,6 +1593,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1585
1593
|
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
1586
1594
|
let result;
|
|
1587
1595
|
if (params.keys || params.submit === false) {
|
|
1596
|
+
if (!params.keys && this.#paneKind(params.pane, session) === 'codex' && codexUpdatePrompt(await this.host.capture(session.id, { lines: 120 }))) return { delivered: false, reason: 'codex-update-prompt: delivery paste refused' };
|
|
1588
1597
|
if (!params.keys && [...this.terminalMailboxes.values()].some(queue => queue.session?.id === session.id && (queue.state.active || queue.state.pending.length))) return { delivered: false, reason: 'terminal mailbox has an unconfirmed paste' };
|
|
1589
1598
|
this.inputInFlight.add(session.id);
|
|
1590
1599
|
try {
|
package/mail-history.mjs
CHANGED
|
@@ -22,10 +22,11 @@ function sessionDirectory(binary, env, signal) {
|
|
|
22
22
|
|
|
23
23
|
/** Resolve from supercode's existing session directory and explicit voice
|
|
24
24
|
* backing, never by interpreting an operator mailbox's generated name. */
|
|
25
|
-
function mailActors(rows, sessions, self) {
|
|
25
|
+
function mailActors(rows, sessions, self, answered = []) {
|
|
26
26
|
const directory = new Map(sessions.map(session => [session.address, session]));
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const identities = [...answered, ...rows];
|
|
28
|
+
const voices = new Map(identities.filter(row => row.envelope.voice_for).map(row => [row.envelope.from, row.envelope.voice_for]));
|
|
29
|
+
const names = new Map(identities.map(row => [row.envelope.from, row.envelope.from_name]));
|
|
29
30
|
const actor = (address) => {
|
|
30
31
|
const backing = voices.get(address) || address;
|
|
31
32
|
const native = /^sc:([^:]+):([^:]+):(.+)$/.exec(backing);
|
|
@@ -45,6 +46,29 @@ function mailActors(rows, sessions, self) {
|
|
|
45
46
|
}));
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
async function answeredMail(rows, binary, env, signal) {
|
|
50
|
+
const known = new Set(rows.filter(row => row.envelope.voice_for).map(row => row.envelope.from));
|
|
51
|
+
const replies = rows.filter(row => row.direction === 'out' && row.envelope.in_reply_to && !known.has(row.session) && /^sc:[^:]+:operator:/.test(row.session));
|
|
52
|
+
const ids = [...new Set(replies.map(row => row.envelope.in_reply_to))];
|
|
53
|
+
if (!ids.length) return [];
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn(binary, ['message', 'show', ...ids, '--json'], {
|
|
56
|
+
env, stdio: ['ignore', 'pipe', 'pipe'], signal: AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(20_000)]),
|
|
57
|
+
});
|
|
58
|
+
let output = '', detail = '';
|
|
59
|
+
child.stdout.on('data', chunk => { output += chunk; if (output.length > 8_000_000) child.kill(); });
|
|
60
|
+
child.stderr.on('data', chunk => { detail = (detail + chunk).slice(-1000); });
|
|
61
|
+
child.on('error', reject);
|
|
62
|
+
child.on('close', code => {
|
|
63
|
+
try {
|
|
64
|
+
if (code !== 0 && code !== 3) throw new Error(detail || 'Answered messages could not be read');
|
|
65
|
+
const parents = JSON.parse(output).filter(row => row.found === true).map(row => row.message);
|
|
66
|
+
resolve(parents.filter(parent => parent.envelope?.voice_for && replies.some(reply => reply.envelope.in_reply_to === parent.envelope.id && reply.session === parent.envelope.from && reply.envelope.from === parent.session)));
|
|
67
|
+
} catch (error) { reject(error); }
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
// Match the CLI's local_machine_name: the native mailbox belongs to its
|
|
49
73
|
// enrolled context, which can differ from a viewer connector's --home.
|
|
50
74
|
function machineName(env) {
|
|
@@ -81,7 +105,8 @@ export function readMailHistory({ binary, env, harness, session, signal }) {
|
|
|
81
105
|
if (code !== 0) return reject(new Error(error || 'Mailbox could not be read'));
|
|
82
106
|
try {
|
|
83
107
|
const rows = JSON.parse(out).filter(row => ['peer', 'channel'].includes(row.envelope?.kind));
|
|
84
|
-
|
|
108
|
+
const [sessions, answered] = await Promise.all([sessionDirectory(binary, env, signal), answeredMail(rows, binary, env, signal)]);
|
|
109
|
+
resolve(mailActors(rows, sessions, address, answered));
|
|
85
110
|
} catch (error) { reject(error); }
|
|
86
111
|
});
|
|
87
112
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.161",
|
|
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
|
@@ -4,6 +4,7 @@ import { mkdirSync, readFileSync, writeFileSync, renameSync, appendFileSync, sta
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { StringDecoder } from 'node:string_decoder';
|
|
7
|
+
import { codexUpdatePrompt } from './codex-startup.mjs';
|
|
7
8
|
|
|
8
9
|
const hash = value => createHash('sha256').update(value).digest('hex');
|
|
9
10
|
const textOf = content => typeof content === 'string' ? content : Array.isArray(content) && content.every(p => ['text', 'input_text'].includes(p.type)) ? content.map(p => p.text).join('') : null;
|
|
@@ -116,6 +117,10 @@ export class TerminalMailbox {
|
|
|
116
117
|
this.state.receipts.push(receipt);
|
|
117
118
|
this.state.active = null; this.save(); this.log(receipt); return;
|
|
118
119
|
}
|
|
120
|
+
if (batch.kind === 'codex' && codexUpdatePrompt(await this.host.capture(this.session.id, { lines: 120 }))) {
|
|
121
|
+
batch.error = 'codex-update-prompt: delivery retained; restart Codex with check_for_update_on_startup=false';
|
|
122
|
+
this.save(); this.log({ delivered: false, queued: true, reason: batch.error, message_ids: batch.message_ids }); return;
|
|
123
|
+
}
|
|
119
124
|
if (batch.phase === 'prepared') {
|
|
120
125
|
// Persist uncertainty before I/O. A daemon restart checks the transcript;
|
|
121
126
|
// it never guesses that an unacknowledged paste did not happen.
|
|
@@ -129,6 +134,9 @@ export class TerminalMailbox {
|
|
|
129
134
|
batch.error = 'paste outcome uncertain; native transcript confirmation pending'; this.save(); return;
|
|
130
135
|
}
|
|
131
136
|
if (batch.enters < 3 && Date.now() - batch.last_enter >= 5000) {
|
|
137
|
+
if (batch.kind === 'codex' && codexUpdatePrompt(await this.host.capture(this.session.id, { lines: 120 }))) {
|
|
138
|
+
batch.error = 'codex-update-prompt: Enter refused'; this.save(); return;
|
|
139
|
+
}
|
|
132
140
|
batch.enters += 1; batch.last_enter = Date.now(); this.save();
|
|
133
141
|
await this.host.sendInput(this.session.id, '\r', { submitMode: 'single' });
|
|
134
142
|
const attempt = { delivered: false, queued: true, batch: batch.id, message_ids: batch.message_ids, attempt: batch.enters, at: Date.now() };
|