@phenx-inc/ctlsurf 0.1.7 → 0.1.9

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.
@@ -3647,8 +3647,8 @@ var ConversationBridge = class {
3647
3647
  inputBuffer = "";
3648
3648
  // Virtual terminal for processing escape sequences into rendered text
3649
3649
  terminal;
3650
- lastSnapshotLines = 0;
3651
- // how many lines we've already sent
3650
+ lastSnapshot = "";
3651
+ pendingWrites = 0;
3652
3652
  constructor() {
3653
3653
  this.terminal = new import_headless.Terminal({ cols: 120, rows: 50, scrollback: 1e4 });
3654
3654
  }
@@ -3657,19 +3657,26 @@ var ConversationBridge = class {
3657
3657
  }
3658
3658
  startSession() {
3659
3659
  this.terminal.reset();
3660
- this.lastSnapshotLines = 0;
3660
+ this.lastSnapshot = "";
3661
+ this.pendingWrites = 0;
3661
3662
  this.inputBuffer = "";
3662
3663
  this.sessionActive = true;
3663
3664
  console.log("[bridge] Session started");
3664
3665
  }
3665
3666
  /**
3666
3667
  * Feed terminal output data into the bridge.
3667
- * The headless terminal processes all escape sequences (cursor moves,
3668
- * line clears, color codes) so the buffer contains clean rendered text.
3668
+ * xterm.write() is async we track pending writes and only flush
3669
+ * when all writes have been processed into the buffer.
3669
3670
  */
3670
3671
  feedOutput(data) {
3671
3672
  if (!this.sessionActive) return;
3672
- this.terminal.write(data);
3673
+ this.pendingWrites++;
3674
+ this.terminal.write(data, () => {
3675
+ this.pendingWrites--;
3676
+ this.scheduleFlush();
3677
+ });
3678
+ }
3679
+ scheduleFlush() {
3673
3680
  if (this.flushTimer) {
3674
3681
  clearTimeout(this.flushTimer);
3675
3682
  }
@@ -3686,29 +3693,36 @@ var ConversationBridge = class {
3686
3693
  this.inputBuffer = "";
3687
3694
  }
3688
3695
  }
3689
- /**
3690
- * Resize the virtual terminal to match the actual PTY dimensions.
3691
- */
3692
3696
  resize(cols, rows) {
3693
3697
  this.terminal.resize(cols, rows);
3694
3698
  }
3695
3699
  /**
3696
3700
  * Read new content from the terminal buffer since the last flush.
3701
+ * Only flushes when all pending writes have completed.
3697
3702
  */
3698
3703
  flush() {
3704
+ if (this.pendingWrites > 0) {
3705
+ this.scheduleFlush();
3706
+ return;
3707
+ }
3699
3708
  const buf = this.terminal.buffer.active;
3700
- const totalLines = buf.baseY + buf.cursorY + 1;
3701
- if (totalLines <= this.lastSnapshotLines) return;
3702
- const newLines = [];
3703
- for (let i = this.lastSnapshotLines; i < totalLines; i++) {
3709
+ const totalLines = buf.baseY + this.terminal.rows;
3710
+ const allLines = [];
3711
+ for (let i = 0; i < totalLines; i++) {
3704
3712
  const line = buf.getLine(i);
3705
3713
  if (line) {
3706
- newLines.push(line.translateToString(true));
3714
+ allLines.push(line.translateToString(true));
3707
3715
  }
3708
3716
  }
3709
- this.lastSnapshotLines = totalLines;
3710
- const content = newLines.join("\n");
3711
- const cleaned = content.replace(/\n{3,}/g, "\n\n").trim();
3717
+ const currentSnapshot = allLines.join("\n");
3718
+ let newContent;
3719
+ if (currentSnapshot.startsWith(this.lastSnapshot)) {
3720
+ newContent = currentSnapshot.slice(this.lastSnapshot.length);
3721
+ } else {
3722
+ newContent = currentSnapshot;
3723
+ }
3724
+ this.lastSnapshot = currentSnapshot;
3725
+ const cleaned = newContent.replace(/\n{3,}/g, "\n\n").trim();
3712
3726
  if (cleaned.length === 0) return;
3713
3727
  this.sendEntry("terminal_output", cleaned);
3714
3728
  }
@@ -3720,8 +3734,12 @@ var ConversationBridge = class {
3720
3734
  content
3721
3735
  });
3722
3736
  }
3723
- endSession() {
3737
+ async endSession() {
3724
3738
  if (!this.sessionActive) return;
3739
+ if (this.pendingWrites > 0) {
3740
+ await new Promise((resolve) => setTimeout(resolve, 500));
3741
+ }
3742
+ this.pendingWrites = 0;
3725
3743
  this.flush();
3726
3744
  if (this.flushTimer) {
3727
3745
  clearTimeout(this.flushTimer);
@@ -4223,7 +4241,7 @@ var Orchestrator = class {
4223
4241
  // ─── PTY & Agent ────────────────────────────────
4224
4242
  async spawnAgent(agent, cwd) {
4225
4243
  if (this.ptyManager) {
4226
- this.bridge.endSession();
4244
+ await this.bridge.endSession();
4227
4245
  this.ptyManager.kill();
4228
4246
  }
4229
4247
  this.currentAgent = agent;
@@ -4239,9 +4257,9 @@ var Orchestrator = class {
4239
4257
  this.streamTerminalData(data);
4240
4258
  });
4241
4259
  const thisPtyManager = this.ptyManager;
4242
- this.ptyManager.onExit((exitCode) => {
4260
+ this.ptyManager.onExit(async (exitCode) => {
4243
4261
  this.events.onPtyExit(exitCode);
4244
- this.bridge.endSession();
4262
+ await this.bridge.endSession();
4245
4263
  if (thisPtyManager === this.ptyManager && this.currentAgent && isCodingAgent(this.currentAgent)) {
4246
4264
  this.workerWs.disconnect();
4247
4265
  }
@@ -4264,7 +4282,7 @@ var Orchestrator = class {
4264
4282
  this.workerWs.sendTerminalResize(cols, rows);
4265
4283
  }
4266
4284
  async killAgent() {
4267
- this.bridge.endSession();
4285
+ await this.bridge.endSession();
4268
4286
  this.ptyManager?.kill();
4269
4287
  this.ptyManager = null;
4270
4288
  if (this.currentAgent && isCodingAgent(this.currentAgent)) {
@@ -4313,7 +4331,7 @@ var Orchestrator = class {
4313
4331
  }
4314
4332
  // ─── Shutdown ───────────────────────────────────
4315
4333
  async shutdown() {
4316
- this.bridge.endSession();
4334
+ await this.bridge.endSession();
4317
4335
  this.ptyManager?.kill();
4318
4336
  this.ptyManager = null;
4319
4337
  this.workerWs.disconnect();