@volter/supercode-teams 0.3.114 → 0.3.115

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.
Files changed (2) hide show
  1. package/machine.mjs +40 -3
  2. 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
@@ -2367,18 +2374,37 @@ export class TeamsMachine extends MachineCore {
2367
2374
  const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2368
2375
  // Styled, so the composer's dim placeholder ("Ask Codex to do anything") reads as empty.
2369
2376
  const composer = async () => terminal.composerText?.('codex', await this.host.capture(session.id, { preserveEscapes: true })) ?? null;
2370
- if (await composer() !== '') return { delivered: false, reason: 'composer-not-empty' };
2377
+ // A composer that reads ready can still be mid-layout: a fresh Codex redraws its screen for about a second more
2378
+ // (measured on volter-desktop: ready from the first frame, redrawn at ~1.1 s and ~1.3 s), and a paste sent into
2379
+ // that is shown late or not at all. Paste only into an empty composer on a screen that has stopped changing.
2380
+ let settled = false;
2381
+ for (const until = Date.now() + CODEX_PASTE_SETTLE_DEADLINE_MS; !settled && !signal.aborted && Date.now() < until;) {
2382
+ const capture = await this.host.capture(session.id, { preserveEscapes: true });
2383
+ settled = terminal.composerText?.('codex', capture) === '' &&
2384
+ this.#screenUnchangedMs(session.id, capture, Date.now()) >= CODEX_PASTE_SETTLE_MS;
2385
+ if (!settled) await pause(150);
2386
+ }
2387
+ if (signal.aborted) return { delivered: false, reason: 'aborted' };
2388
+ if (!settled) return { delivered: false, reason: await composer() === '' ? 'screen-unsettled' : 'composer-not-empty' };
2371
2389
  for (const paste of codexWindowsPastes(body)) {
2372
2390
  if (signal.aborted) return { delivered: false, reason: 'aborted' };
2373
2391
  await this.host.sendInput(session.id, paste, { submitMode: 'none' });
2374
2392
  await pause(300);
2375
2393
  }
2376
2394
  let held = false;
2377
- for (let attempt = 0; attempt < 20 && !held && !signal.aborted; attempt += 1) {
2395
+ for (const until = Date.now() + CODEX_PASTE_SHOWN_MS; !held && !signal.aborted && Date.now() < until;) {
2378
2396
  held = Boolean(await composer());
2379
2397
  if (!held) await pause(150);
2380
2398
  }
2381
- if (!held) return { delivered: false, reason: 'paste-unconfirmed' };
2399
+ if (!held) {
2400
+ // A paste shown after this gave up would sit in the composer unsent: watch a while longer and clear it, so the
2401
+ // pane is left as it was found (the launch records the input as undelivered either way).
2402
+ for (const until = Date.now() + CODEX_PASTE_LATE_MS; !signal.aborted && Date.now() < until;) {
2403
+ if (await composer()) { await this.#clearCodexComposer(session, composer, pause); break; }
2404
+ await pause(250);
2405
+ }
2406
+ return { delivered: false, reason: 'paste-unconfirmed' };
2407
+ }
2382
2408
  await this.host.sendInput(session.id, '\r', { submitMode: 'single' });
2383
2409
  return { delivered: true };
2384
2410
  } catch (error) {
@@ -2388,6 +2414,17 @@ export class TeamsMachine extends MachineCore {
2388
2414
  }
2389
2415
  }
2390
2416
 
2417
+ /** Empty a Windows Codex composer this launch pasted into: line clear, then deletes until it reads empty (a paste
2418
+ * placeholder goes with one delete). Never Enter, Escape or Ctrl-C. */
2419
+ async #clearCodexComposer(session, composer, pause) {
2420
+ await this.host.sendInput(session.id, '\u0015', { submitMode: 'none' });
2421
+ await pause(300);
2422
+ for (let round = 0; round < 40 && await composer(); round += 1) {
2423
+ await this.host.sendInput(session.id, '\u007f'.repeat(200), { submitMode: 'none' });
2424
+ await pause(200);
2425
+ }
2426
+ }
2427
+
2391
2428
  async #deliverPaneText(session, contextKey, text, options = {}) {
2392
2429
  const kind = this.#paneKind(contextKey, session);
2393
2430
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.114",
3
+ "version": "0.3.115",
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",