@volter/supercode-teams 0.2.123 → 0.2.125

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 CHANGED
@@ -701,7 +701,14 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
701
701
  if (!serviceBin) unsupported('--install requires invocation through the native supercode CLI');
702
702
  const serviceEnv = { ...process.env, SUPERCODE_TEAMS_ENTRY: fileURLToPath(new URL('./teams.mjs', import.meta.url)) };
703
703
  // Refuse source-backed installs before Windows stops the running connector.
704
- execFileSync(serviceBin, ['__teams-connector-service', 'validate', contextName, path, context.server_id, context.team_id], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: serviceEnv });
704
+ try {
705
+ execFileSync(serviceBin, ['__teams-connector-service', 'validate', contextName, path, context.server_id, context.team_id], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: serviceEnv });
706
+ } catch (error) {
707
+ // In particular, an older native CLI may not have the validation door.
708
+ // Its support-report child must not keep a refused Windows install alive.
709
+ await core.close();
710
+ throw error;
711
+ }
705
712
  // Ending a Windows task ends only the console host it started, never the connector under it, so the task
706
713
  // would refuse its new run while the old connector went on: the running one is asked to stop first.
707
714
  if (process.platform === 'win32') await stopRunningConnector(saved);
package/machine.mjs CHANGED
@@ -1485,17 +1485,44 @@ export class TeamsMachine extends MachineCore {
1485
1485
  const screen = await this.host.capture(session.id, { lines: 200, preserveEscapes: true });
1486
1486
  return /\besc to (?:interrupt|cancel)\b/iu.test(screen) ? null : terminal.composerText(kind, screen);
1487
1487
  };
1488
+ const painted = await read();
1489
+ await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1490
+ if (painted === null || painted !== await read()) return { cleared: false, reason: 'composer-not-stable' };
1491
+ // Native stderr can paint over the composer without entering its editor.
1492
+ // Repaint first so that a stale error line is not mistaken for a draft.
1493
+ await this.host.sendInput(session.id, '\u000c', { submitMode: 'none' });
1494
+ await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1488
1495
  const before = await read();
1489
1496
  await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1490
1497
  if (before === null || before !== await read()) return { cleared: false, reason: 'composer-not-stable' };
1491
- if (before === '') return { cleared: true, alreadyEmpty: true };
1492
- // Explicit deletion, never submit. Readline end/kill-left clears the observed line;
1493
- // a multiline or changed editor is reported unconfirmed, never sent as a turn.
1494
- await this.host.sendInput(session.id, '\u0005\u0015', { submitMode: 'single' });
1495
- await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1496
- const after = await read(), cleared = after === '';
1498
+ if (before === '') {
1499
+ act('control', `pane:${params.pane}`, 'draft-cleared');
1500
+ return { cleared: true, alreadyEmpty: painted === '', repainted: painted !== '' };
1501
+ }
1502
+ if (before !== painted) return { cleared: false, reason: 'composer-changed' };
1503
+ // Clear whole drafts, including lines before an empty final line. Each deletion
1504
+ // must leave only text from the previous reading; a concurrent edit stops us.
1505
+ let remaining = before, cleared = false, reason = 'composer-not-empty';
1506
+ for (let line = 0; line < before.split('\n').length + 2; line += 1) {
1507
+ if (remaining !== await read()) { reason = 'composer-changed'; break; }
1508
+ await this.host.sendInput(session.id, '\u0005\u0015', { submitMode: 'none' });
1509
+ await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1510
+ let after = await read();
1511
+ if (after === '') { cleared = true; break; }
1512
+ if (after === null || !remaining.includes(after)) { reason = 'composer-changed'; break; }
1513
+ // Kill-left left the cursor at the line's start. Backspace joins the
1514
+ // preceding line; it never submits or interrupts the session.
1515
+ remaining = after;
1516
+ if (remaining !== await read()) { reason = 'composer-changed'; break; }
1517
+ await this.host.sendInput(session.id, '\u007f', { submitMode: 'none' });
1518
+ await new Promise(resolve => setTimeout(resolve, COMPOSER_RECHECK_MS));
1519
+ after = await read();
1520
+ if (after === '') { cleared = true; break; }
1521
+ if (after === null || !remaining.replace(/\n/g, '').includes(after.replace(/\n/g, ''))) { reason = 'composer-changed'; break; }
1522
+ remaining = after;
1523
+ }
1497
1524
  act('control', `pane:${params.pane}`, cleared ? 'draft-cleared' : 'draft-clear-unconfirmed');
1498
- return { cleared, reason: cleared ? null : 'composer-not-empty', beforeHash: createHash('sha256').update(before).digest('hex') };
1525
+ return { cleared, reason: cleared ? null : reason, beforeHash: createHash('sha256').update(before).digest('hex') };
1499
1526
  } finally { this.inputInFlight.delete(session.id); }
1500
1527
  }
1501
1528
  case 'panes.turn': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.2.123",
3
+ "version": "0.2.125",
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",