@phenx-inc/ctlsurf 0.1.9 → 0.1.10

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/out/main/index.js CHANGED
@@ -3625,14 +3625,15 @@ function requireXtermHeadless() {
3625
3625
  var xtermHeadlessExports = requireXtermHeadless();
3626
3626
  class ConversationBridge {
3627
3627
  wsClient = null;
3628
- flushTimer = null;
3629
- flushIntervalMs = 3e3;
3630
3628
  sessionActive = false;
3631
3629
  inputBuffer = "";
3630
+ bytesAccumulated = 0;
3632
3631
  // Virtual terminal for processing escape sequences into rendered text
3633
3632
  terminal;
3634
3633
  lastSnapshot = "";
3635
- pendingWrites = 0;
3634
+ // Flush after accumulating enough data or on a timer
3635
+ FLUSH_BYTES = 2e3;
3636
+ flushTimer = null;
3636
3637
  constructor() {
3637
3638
  this.terminal = new xtermHeadlessExports.Terminal({ cols: 120, rows: 50, scrollback: 1e4 });
3638
3639
  }
@@ -3642,9 +3643,11 @@ class ConversationBridge {
3642
3643
  startSession() {
3643
3644
  this.terminal.reset();
3644
3645
  this.lastSnapshot = "";
3645
- this.pendingWrites = 0;
3646
+ this.bytesAccumulated = 0;
3646
3647
  this.inputBuffer = "";
3647
3648
  this.sessionActive = true;
3649
+ if (this.flushTimer) clearInterval(this.flushTimer);
3650
+ this.flushTimer = setInterval(() => this.flush(), 5e3);
3648
3651
  console.log("[bridge] Session started");
3649
3652
  }
3650
3653
  /**
@@ -3654,17 +3657,11 @@ class ConversationBridge {
3654
3657
  */
3655
3658
  feedOutput(data) {
3656
3659
  if (!this.sessionActive) return;
3657
- this.pendingWrites++;
3658
- this.terminal.write(data, () => {
3659
- this.pendingWrites--;
3660
- this.scheduleFlush();
3661
- });
3662
- }
3663
- scheduleFlush() {
3664
- if (this.flushTimer) {
3665
- clearTimeout(this.flushTimer);
3660
+ this.terminal.write(data);
3661
+ this.bytesAccumulated += data.length;
3662
+ if (this.bytesAccumulated >= this.FLUSH_BYTES) {
3663
+ this.flush();
3666
3664
  }
3667
- this.flushTimer = setTimeout(() => this.flush(), this.flushIntervalMs);
3668
3665
  }
3669
3666
  feedInput(data) {
3670
3667
  if (!this.sessionActive) return;
@@ -3685,10 +3682,6 @@ class ConversationBridge {
3685
3682
  * Only flushes when all pending writes have completed.
3686
3683
  */
3687
3684
  flush() {
3688
- if (this.pendingWrites > 0) {
3689
- this.scheduleFlush();
3690
- return;
3691
- }
3692
3685
  const buf = this.terminal.buffer.active;
3693
3686
  const totalLines = buf.baseY + this.terminal.rows;
3694
3687
  const allLines = [];
@@ -3707,6 +3700,7 @@ class ConversationBridge {
3707
3700
  }
3708
3701
  this.lastSnapshot = currentSnapshot;
3709
3702
  const cleaned = newContent.replace(/\n{3,}/g, "\n\n").trim();
3703
+ this.bytesAccumulated = 0;
3710
3704
  if (cleaned.length === 0) return;
3711
3705
  this.sendEntry("terminal_output", cleaned);
3712
3706
  }
@@ -3718,15 +3712,11 @@ class ConversationBridge {
3718
3712
  content
3719
3713
  });
3720
3714
  }
3721
- async endSession() {
3715
+ endSession() {
3722
3716
  if (!this.sessionActive) return;
3723
- if (this.pendingWrites > 0) {
3724
- await new Promise((resolve) => setTimeout(resolve, 500));
3725
- }
3726
- this.pendingWrites = 0;
3727
3717
  this.flush();
3728
3718
  if (this.flushTimer) {
3729
- clearTimeout(this.flushTimer);
3719
+ clearInterval(this.flushTimer);
3730
3720
  this.flushTimer = null;
3731
3721
  }
3732
3722
  this.sessionActive = false;
@@ -7855,7 +7845,7 @@ class Orchestrator {
7855
7845
  // ─── PTY & Agent ────────────────────────────────
7856
7846
  async spawnAgent(agent, cwd) {
7857
7847
  if (this.ptyManager) {
7858
- await this.bridge.endSession();
7848
+ this.bridge.endSession();
7859
7849
  this.ptyManager.kill();
7860
7850
  }
7861
7851
  this.currentAgent = agent;
@@ -7873,7 +7863,7 @@ class Orchestrator {
7873
7863
  const thisPtyManager = this.ptyManager;
7874
7864
  this.ptyManager.onExit(async (exitCode) => {
7875
7865
  this.events.onPtyExit(exitCode);
7876
- await this.bridge.endSession();
7866
+ this.bridge.endSession();
7877
7867
  if (thisPtyManager === this.ptyManager && this.currentAgent && isCodingAgent(this.currentAgent)) {
7878
7868
  this.workerWs.disconnect();
7879
7869
  }
@@ -7896,7 +7886,7 @@ class Orchestrator {
7896
7886
  this.workerWs.sendTerminalResize(cols, rows);
7897
7887
  }
7898
7888
  async killAgent() {
7899
- await this.bridge.endSession();
7889
+ this.bridge.endSession();
7900
7890
  this.ptyManager?.kill();
7901
7891
  this.ptyManager = null;
7902
7892
  if (this.currentAgent && isCodingAgent(this.currentAgent)) {
@@ -7945,7 +7935,7 @@ class Orchestrator {
7945
7935
  }
7946
7936
  // ─── Shutdown ───────────────────────────────────
7947
7937
  async shutdown() {
7948
- await this.bridge.endSession();
7938
+ this.bridge.endSession();
7949
7939
  this.ptyManager?.kill();
7950
7940
  this.ptyManager = null;
7951
7941
  this.workerWs.disconnect();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -11,15 +11,17 @@ import { WorkerWsClient } from './workerWs'
11
11
  */
12
12
  export class ConversationBridge {
13
13
  private wsClient: WorkerWsClient | null = null
14
- private flushTimer: ReturnType<typeof setTimeout> | null = null
15
- private flushIntervalMs: number = 3000
16
14
  private sessionActive: boolean = false
17
15
  private inputBuffer: string = ''
16
+ private bytesAccumulated: number = 0
18
17
 
19
18
  // Virtual terminal for processing escape sequences into rendered text
20
19
  private terminal: Terminal
21
20
  private lastSnapshot: string = ''
22
- private pendingWrites: number = 0
21
+
22
+ // Flush after accumulating enough data or on a timer
23
+ private readonly FLUSH_BYTES = 2000
24
+ private flushTimer: ReturnType<typeof setInterval> | null = null
23
25
 
24
26
  constructor() {
25
27
  this.terminal = new Terminal({ cols: 120, rows: 50, scrollback: 10000 })
@@ -32,9 +34,14 @@ export class ConversationBridge {
32
34
  startSession(): void {
33
35
  this.terminal.reset()
34
36
  this.lastSnapshot = ''
35
- this.pendingWrites = 0
37
+ this.bytesAccumulated = 0
36
38
  this.inputBuffer = ''
37
39
  this.sessionActive = true
40
+
41
+ // Periodic flush via setInterval (setTimeout may not fire under raw-mode TUI)
42
+ if (this.flushTimer) clearInterval(this.flushTimer)
43
+ this.flushTimer = setInterval(() => this.flush(), 5000)
44
+
38
45
  console.log('[bridge] Session started')
39
46
  }
40
47
 
@@ -46,18 +53,13 @@ export class ConversationBridge {
46
53
  feedOutput(data: string): void {
47
54
  if (!this.sessionActive) return
48
55
 
49
- this.pendingWrites++
50
- this.terminal.write(data, () => {
51
- this.pendingWrites--
52
- this.scheduleFlush()
53
- })
54
- }
56
+ this.terminal.write(data)
57
+ this.bytesAccumulated += data.length
55
58
 
56
- private scheduleFlush(): void {
57
- if (this.flushTimer) {
58
- clearTimeout(this.flushTimer)
59
+ // Flush when enough data accumulated
60
+ if (this.bytesAccumulated >= this.FLUSH_BYTES) {
61
+ this.flush()
59
62
  }
60
- this.flushTimer = setTimeout(() => this.flush(), this.flushIntervalMs)
61
63
  }
62
64
 
63
65
  feedInput(data: string): void {
@@ -82,11 +84,6 @@ export class ConversationBridge {
82
84
  * Only flushes when all pending writes have completed.
83
85
  */
84
86
  private flush(): void {
85
- if (this.pendingWrites > 0) {
86
- this.scheduleFlush()
87
- return
88
- }
89
-
90
87
  const buf = this.terminal.buffer.active
91
88
  // Read all lines: scrollback (baseY) + visible area (rows)
92
89
  const totalLines = buf.baseY + this.terminal.rows
@@ -114,6 +111,9 @@ export class ConversationBridge {
114
111
  this.lastSnapshot = currentSnapshot
115
112
 
116
113
  const cleaned = newContent.replace(/\n{3,}/g, '\n\n').trim()
114
+
115
+ this.bytesAccumulated = 0
116
+
117
117
  if (cleaned.length === 0) return
118
118
 
119
119
  this.sendEntry('terminal_output', cleaned)
@@ -128,19 +128,14 @@ export class ConversationBridge {
128
128
  })
129
129
  }
130
130
 
131
- async endSession(): Promise<void> {
131
+ endSession(): void {
132
132
  if (!this.sessionActive) return
133
133
 
134
- // If writes are pending, give xterm a moment to process them
135
- if (this.pendingWrites > 0) {
136
- await new Promise<void>(resolve => setTimeout(resolve, 500))
137
- }
138
-
139
- this.pendingWrites = 0
134
+ // Give xterm a moment to process any remaining writes
140
135
  this.flush()
141
136
 
142
137
  if (this.flushTimer) {
143
- clearTimeout(this.flushTimer)
138
+ clearInterval(this.flushTimer)
144
139
  this.flushTimer = null
145
140
  }
146
141
 
@@ -284,7 +284,7 @@ export class Orchestrator {
284
284
 
285
285
  async spawnAgent(agent: AgentConfig, cwd: string): Promise<void> {
286
286
  if (this.ptyManager) {
287
- await this.bridge.endSession()
287
+ this.bridge.endSession()
288
288
  this.ptyManager.kill()
289
289
  }
290
290
 
@@ -307,7 +307,7 @@ export class Orchestrator {
307
307
 
308
308
  this.ptyManager.onExit(async (exitCode: number) => {
309
309
  this.events.onPtyExit(exitCode)
310
- await this.bridge.endSession()
310
+ this.bridge.endSession()
311
311
  if (thisPtyManager === this.ptyManager && this.currentAgent && isCodingAgent(this.currentAgent)) {
312
312
  this.workerWs.disconnect()
313
313
  }
@@ -335,7 +335,7 @@ export class Orchestrator {
335
335
  }
336
336
 
337
337
  async killAgent(): Promise<void> {
338
- await this.bridge.endSession()
338
+ this.bridge.endSession()
339
339
  this.ptyManager?.kill()
340
340
  this.ptyManager = null
341
341
  if (this.currentAgent && isCodingAgent(this.currentAgent)) {
@@ -391,7 +391,7 @@ export class Orchestrator {
391
391
  // ─── Shutdown ───────────────────────────────────
392
392
 
393
393
  async shutdown(): Promise<void> {
394
- await this.bridge.endSession()
394
+ this.bridge.endSession()
395
395
  this.ptyManager?.kill()
396
396
  this.ptyManager = null
397
397
  this.workerWs.disconnect()