@volter/supercode-teams 0.3.114 → 0.3.116
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 +72 -3
- package/mail/relay.mjs +6 -4
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -126,6 +126,13 @@ export function claudeUsageLimitWait(screen) {
|
|
|
126
126
|
/** The settle floor for a pane whose kind has no composer pattern: the screen
|
|
127
127
|
* must have stopped changing for this long before the pane counts as ready. */
|
|
128
128
|
const INITIAL_INPUT_SETTLE_MS = 1_000;
|
|
129
|
+
/** A Windows Codex's typed first input waits, before pasting, for an empty composer on a screen unchanged this long
|
|
130
|
+
* (CODEX_PASTE_SETTLE_DEADLINE_MS at most); gives the paste CODEX_PASTE_SHOWN_MS to show, and watches
|
|
131
|
+
* CODEX_PASTE_LATE_MS more to clear one shown after that. */
|
|
132
|
+
const CODEX_PASTE_SETTLE_MS = 1_500;
|
|
133
|
+
const CODEX_PASTE_SETTLE_DEADLINE_MS = 30_000;
|
|
134
|
+
const CODEX_PASTE_SHOWN_MS = 5_000;
|
|
135
|
+
const CODEX_PASTE_LATE_MS = 15_000;
|
|
129
136
|
|
|
130
137
|
/**
|
|
131
138
|
* Claude asks whether to trust a folder it has not opened before, whatever its permission mode, and has no flag that
|
|
@@ -500,6 +507,17 @@ export class TeamsMachine extends MachineCore {
|
|
|
500
507
|
// Set by a service-run connector: how `machine.restart` ends this daemon so its service manager starts it again.
|
|
501
508
|
this.restartHandler = null;
|
|
502
509
|
this.launches = new Launches(this.p.launches);
|
|
510
|
+
// The release this daemon runs, in its machine record (`supercode teams machine status`): its Teams package and its
|
|
511
|
+
// native. A daemon an upgrade did not restart runs the release it started with, whatever is installed, and a
|
|
512
|
+
// client a release behind or ahead of it is refused, so status says which it is (2026-10-06: version skew between
|
|
513
|
+
// daemons and their installs behind much of the day's trouble).
|
|
514
|
+
this.once('listening', () => {
|
|
515
|
+
try {
|
|
516
|
+
const record = readJson(this.p.machine, null);
|
|
517
|
+
if (record?.pid !== process.pid) return;
|
|
518
|
+
writeJson(this.p.machine, { ...record, release: this.#release() });
|
|
519
|
+
} catch { /* its record says no release */ }
|
|
520
|
+
});
|
|
503
521
|
// the panes' stable porthole hook and the machine's stable supercode brought up to this release's at start, under
|
|
504
522
|
// every pane already running
|
|
505
523
|
this.#portholeHook();
|
|
@@ -1805,6 +1823,17 @@ export class TeamsMachine extends MachineCore {
|
|
|
1805
1823
|
return this.stableSupercodePath ?? this.supercodeBin;
|
|
1806
1824
|
}
|
|
1807
1825
|
|
|
1826
|
+
/** This daemon's release: its Teams package's version, and its native's (`--version`), as it started. */
|
|
1827
|
+
#release() {
|
|
1828
|
+
let teams = null, native = null;
|
|
1829
|
+
try { teams = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version ?? null; } catch { /* unread */ }
|
|
1830
|
+
try {
|
|
1831
|
+
const answer = spawnSync(this.supercodeBin, ['--version'], { env: this.env, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1832
|
+
native = answer.status === 0 ? answer.stdout.trim() || null : null;
|
|
1833
|
+
} catch { /* unread */ }
|
|
1834
|
+
return { teams, native };
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1808
1837
|
#portholeHook() {
|
|
1809
1838
|
if (this.portholeHookPath) return this.portholeHookPath;
|
|
1810
1839
|
const stable = join(supercodeHome(this.env), 'porthole-hook.cjs');
|
|
@@ -2367,18 +2396,47 @@ export class TeamsMachine extends MachineCore {
|
|
|
2367
2396
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2368
2397
|
// Styled, so the composer's dim placeholder ("Ask Codex to do anything") reads as empty.
|
|
2369
2398
|
const composer = async () => terminal.composerText?.('codex', await this.host.capture(session.id, { preserveEscapes: true })) ?? null;
|
|
2370
|
-
|
|
2399
|
+
// A composer that reads ready can still be mid-layout: a fresh Codex redraws its screen for about a second more
|
|
2400
|
+
// (measured on volter-desktop: ready from the first frame, redrawn at ~1.1 s and ~1.3 s), and a paste sent into
|
|
2401
|
+
// that is shown late or not at all. Paste only into an empty composer on a screen that has stopped changing.
|
|
2402
|
+
let settled = false;
|
|
2403
|
+
for (const until = Date.now() + CODEX_PASTE_SETTLE_DEADLINE_MS; !settled && !signal.aborted && Date.now() < until;) {
|
|
2404
|
+
const capture = await this.host.capture(session.id, { preserveEscapes: true });
|
|
2405
|
+
settled = terminal.composerText?.('codex', capture) === '' &&
|
|
2406
|
+
this.#screenUnchangedMs(session.id, capture, Date.now()) >= CODEX_PASTE_SETTLE_MS;
|
|
2407
|
+
if (!settled) await pause(150);
|
|
2408
|
+
}
|
|
2409
|
+
if (signal.aborted) return { delivered: false, reason: 'aborted' };
|
|
2410
|
+
if (!settled) return { delivered: false, reason: await composer() === '' ? 'screen-unsettled' : 'composer-not-empty' };
|
|
2371
2411
|
for (const paste of codexWindowsPastes(body)) {
|
|
2372
2412
|
if (signal.aborted) return { delivered: false, reason: 'aborted' };
|
|
2373
2413
|
await this.host.sendInput(session.id, paste, { submitMode: 'none' });
|
|
2374
2414
|
await pause(300);
|
|
2375
2415
|
}
|
|
2376
2416
|
let held = false;
|
|
2377
|
-
for (
|
|
2417
|
+
for (const until = Date.now() + CODEX_PASTE_SHOWN_MS; !held && !signal.aborted && Date.now() < until;) {
|
|
2378
2418
|
held = Boolean(await composer());
|
|
2379
2419
|
if (!held) await pause(150);
|
|
2380
2420
|
}
|
|
2381
|
-
if (!held)
|
|
2421
|
+
if (!held) {
|
|
2422
|
+
// A paste shown after this gave up would sit in the composer unsent: watch a while longer and clear it, so the
|
|
2423
|
+
// pane is left as it was found (the launch records the input as undelivered either way).
|
|
2424
|
+
for (const until = Date.now() + CODEX_PASTE_LATE_MS; !signal.aborted && Date.now() < until;) {
|
|
2425
|
+
const shown = await composer();
|
|
2426
|
+
if (!shown) { await pause(250); continue; }
|
|
2427
|
+
// Only this launch's paste is cleared: Codex shows a large paste as its placeholder, a small one as its
|
|
2428
|
+
// text. Anything else (a person typing meanwhile) is theirs and stays.
|
|
2429
|
+
// Compared with whitespace removed on both sides: the composer's text joins its screen rows with line breaks,
|
|
2430
|
+
// so a first line wider than one row never matches as it was sent.
|
|
2431
|
+
const squash = (value) => value.replace(/\s+/gu, '');
|
|
2432
|
+
const firstLine = squash(body.split('\n')[0]);
|
|
2433
|
+
if (/^\[Pasted Content \d+ chars\]/u.test(shown) || (firstLine && squash(shown).startsWith(firstLine))) {
|
|
2434
|
+
await this.#clearCodexComposer(session, composer, pause);
|
|
2435
|
+
}
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
return { delivered: false, reason: 'paste-unconfirmed' };
|
|
2439
|
+
}
|
|
2382
2440
|
await this.host.sendInput(session.id, '\r', { submitMode: 'single' });
|
|
2383
2441
|
return { delivered: true };
|
|
2384
2442
|
} catch (error) {
|
|
@@ -2388,6 +2446,17 @@ export class TeamsMachine extends MachineCore {
|
|
|
2388
2446
|
}
|
|
2389
2447
|
}
|
|
2390
2448
|
|
|
2449
|
+
/** Empty a Windows Codex composer this launch pasted into: line clear, then deletes until it reads empty (a paste
|
|
2450
|
+
* placeholder goes with one delete). Never Enter, Escape or Ctrl-C. */
|
|
2451
|
+
async #clearCodexComposer(session, composer, pause) {
|
|
2452
|
+
await this.host.sendInput(session.id, '\u0015', { submitMode: 'none' });
|
|
2453
|
+
await pause(300);
|
|
2454
|
+
for (let round = 0; round < 40 && await composer(); round += 1) {
|
|
2455
|
+
await this.host.sendInput(session.id, '\u007f'.repeat(200), { submitMode: 'none' });
|
|
2456
|
+
await pause(200);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2391
2460
|
async #deliverPaneText(session, contextKey, text, options = {}) {
|
|
2392
2461
|
const kind = this.#paneKind(contextKey, session);
|
|
2393
2462
|
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/mail/relay.mjs
CHANGED
|
@@ -283,11 +283,13 @@ export class Relays {
|
|
|
283
283
|
}
|
|
284
284
|
// why it is not reused, said in the timing line: a relay that should have been warm and was not shows here
|
|
285
285
|
step(`runtime not reused (${stale ? 'it held a stale turn' : !runtime ? 'no live receipt' : !alive(this.#pid(spec, runtime)) ? 'its process ended' : !record.connection ? 'no connection' : 'endpoint moved'})`);
|
|
286
|
-
// closed through the owner's door even after its receipt went. One whose receipt is gone, or whose process
|
|
287
|
-
// ended, is only serve's record of it: let go without holding this send (yuerans, 2026-10-06: 2.3-18 s
|
|
288
|
-
// send under load)
|
|
286
|
+
// closed through the owner's door even after its receipt went. One whose receipt is gone, or whose process is
|
|
287
|
+
// known and ended, is only serve's record of it: let go without holding this send (yuerans, 2026-10-06: 2.3-18 s
|
|
288
|
+
// of every send under load). One that may still run, its process unknown (no child pid, a record of another
|
|
289
|
+
// runtime) or alive, is closed before its successor starts in the same folder, as before.
|
|
289
290
|
const close = this.serve('harness.v1.runtimes.close', { connection: record.connection, runtime_id: record.runtime_id }, CLOSE_MS);
|
|
290
|
-
|
|
291
|
+
const pid = runtime ? this.#pid(spec, runtime) : null;
|
|
292
|
+
if (runtime && !(Number.isInteger(pid) && !alive(pid))) {
|
|
291
293
|
try { await close; }
|
|
292
294
|
catch (error) { if (this.runtimeOf('claude-code', record.runtime_id)) throw new Error(`could not retire the old relay: ${error.message}`); }
|
|
293
295
|
step('old runtime closed (runtimes.close)');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.116",
|
|
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",
|