@phenx-inc/ctlsurf 0.3.8 → 0.3.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.
package/out/main/index.js CHANGED
@@ -10465,6 +10465,7 @@ const DEFAULT_PROFILES = {
10465
10465
  }
10466
10466
  };
10467
10467
  const TERM_STREAM_INTERVAL_MS = 50;
10468
+ const NO_PROJECT_POLL_MS = 5e3;
10468
10469
  class Orchestrator {
10469
10470
  settingsDir;
10470
10471
  events;
@@ -10483,6 +10484,8 @@ class Orchestrator {
10483
10484
  profiles: { ...DEFAULT_PROFILES },
10484
10485
  logChat: false
10485
10486
  };
10487
+ noProjectPollTimer = null;
10488
+ noProjectPollCwd = null;
10486
10489
  constructor(settingsDir, events) {
10487
10490
  this.settingsDir = settingsDir;
10488
10491
  this.events = events;
@@ -10508,6 +10511,11 @@ class Orchestrator {
10508
10511
  events.onWorkerRegistered(data);
10509
10512
  if (!data.folder_id) {
10510
10513
  events.onWorkerStatus("no_project");
10514
+ if (this.currentCwd && data.status !== "pending_approval") {
10515
+ this.startNoProjectPolling(this.currentCwd);
10516
+ }
10517
+ } else {
10518
+ this.stopNoProjectPolling();
10511
10519
  }
10512
10520
  },
10513
10521
  onTerminalInput: (data) => {
@@ -10747,6 +10755,7 @@ class Orchestrator {
10747
10755
  if (isCodingAgent(agent)) {
10748
10756
  this.connectWorkerWs(agent, cwd);
10749
10757
  } else {
10758
+ this.stopNoProjectPolling();
10750
10759
  this.workerWs.disconnect();
10751
10760
  this.checkProjectStatus(cwd);
10752
10761
  }
@@ -10821,12 +10830,47 @@ class Orchestrator {
10821
10830
  log$1("[worker-ws] No API key, skipping WS connect");
10822
10831
  return;
10823
10832
  }
10833
+ this.stopNoProjectPolling();
10824
10834
  this.workerWs.connect({
10825
10835
  machine: os.hostname(),
10826
10836
  cwd,
10827
10837
  agent: agent.name
10828
10838
  });
10829
10839
  }
10840
+ startNoProjectPolling(cwd) {
10841
+ if (this.noProjectPollTimer && this.noProjectPollCwd === cwd) return;
10842
+ this.stopNoProjectPolling();
10843
+ this.noProjectPollCwd = cwd;
10844
+ log$1(`[worker-ws] Polling for project folder at ${cwd}`);
10845
+ this.noProjectPollTimer = setInterval(() => {
10846
+ void this.checkForProjectFolder(cwd);
10847
+ }, NO_PROJECT_POLL_MS);
10848
+ }
10849
+ stopNoProjectPolling() {
10850
+ if (this.noProjectPollTimer) {
10851
+ clearInterval(this.noProjectPollTimer);
10852
+ this.noProjectPollTimer = null;
10853
+ this.noProjectPollCwd = null;
10854
+ }
10855
+ }
10856
+ async checkForProjectFolder(cwd) {
10857
+ if (this.currentCwd !== cwd || !this.currentAgent) {
10858
+ this.stopNoProjectPolling();
10859
+ return;
10860
+ }
10861
+ if (!this.ctlsurfApi.getApiKey()) return;
10862
+ try {
10863
+ const folder = await this.ctlsurfApi.findFolderByPath(cwd);
10864
+ if (folder?.id && this.currentCwd === cwd && this.currentAgent) {
10865
+ log$1(`[worker-ws] Project folder appeared (${folder.id}); reconnecting`);
10866
+ const agent = this.currentAgent;
10867
+ this.stopNoProjectPolling();
10868
+ this.workerWs.disconnect();
10869
+ this.connectWorkerWs(agent, cwd);
10870
+ }
10871
+ } catch {
10872
+ }
10873
+ }
10830
10874
  async checkProjectStatus(cwd) {
10831
10875
  if (!this.ctlsurfApi.getApiKey()) {
10832
10876
  this.events.onWorkerStatus("no_project");
@@ -10857,6 +10901,7 @@ class Orchestrator {
10857
10901
  }
10858
10902
  // ─── Shutdown ───────────────────────────────────
10859
10903
  async shutdown() {
10904
+ this.stopNoProjectPolling();
10860
10905
  this.bridge.endSession();
10861
10906
  await this.timeTracker.endAll();
10862
10907
  for (const [, tab] of this.tabs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
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": {
@@ -66,6 +66,7 @@ const DEFAULT_PROFILES: Record<string, Profile> = {
66
66
  }
67
67
 
68
68
  const TERM_STREAM_INTERVAL_MS = 50
69
+ const NO_PROJECT_POLL_MS = 5_000
69
70
 
70
71
  export class Orchestrator {
71
72
  private settingsDir: string
@@ -87,6 +88,8 @@ export class Orchestrator {
87
88
  profiles: { ...DEFAULT_PROFILES },
88
89
  logChat: false,
89
90
  }
91
+ private noProjectPollTimer: ReturnType<typeof setInterval> | null = null
92
+ private noProjectPollCwd: string | null = null
90
93
 
91
94
  constructor(settingsDir: string, events: OrchestratorEvents) {
92
95
  this.settingsDir = settingsDir
@@ -115,6 +118,11 @@ export class Orchestrator {
115
118
  events.onWorkerRegistered(data)
116
119
  if (!data.folder_id) {
117
120
  events.onWorkerStatus('no_project')
121
+ if (this.currentCwd && data.status !== 'pending_approval') {
122
+ this.startNoProjectPolling(this.currentCwd)
123
+ }
124
+ } else {
125
+ this.stopNoProjectPolling()
118
126
  }
119
127
  },
120
128
  onTerminalInput: (data: string) => {
@@ -398,6 +406,7 @@ export class Orchestrator {
398
406
  if (isCodingAgent(agent)) {
399
407
  this.connectWorkerWs(agent, cwd)
400
408
  } else {
409
+ this.stopNoProjectPolling()
401
410
  this.workerWs.disconnect()
402
411
  this.checkProjectStatus(cwd)
403
412
  }
@@ -484,6 +493,7 @@ export class Orchestrator {
484
493
  return
485
494
  }
486
495
 
496
+ this.stopNoProjectPolling()
487
497
  this.workerWs.connect({
488
498
  machine: os.hostname(),
489
499
  cwd,
@@ -491,6 +501,40 @@ export class Orchestrator {
491
501
  })
492
502
  }
493
503
 
504
+ private startNoProjectPolling(cwd: string): void {
505
+ if (this.noProjectPollTimer && this.noProjectPollCwd === cwd) return
506
+ this.stopNoProjectPolling()
507
+ this.noProjectPollCwd = cwd
508
+ log(`[worker-ws] Polling for project folder at ${cwd}`)
509
+ this.noProjectPollTimer = setInterval(() => { void this.checkForProjectFolder(cwd) }, NO_PROJECT_POLL_MS)
510
+ }
511
+
512
+ private stopNoProjectPolling(): void {
513
+ if (this.noProjectPollTimer) {
514
+ clearInterval(this.noProjectPollTimer)
515
+ this.noProjectPollTimer = null
516
+ this.noProjectPollCwd = null
517
+ }
518
+ }
519
+
520
+ private async checkForProjectFolder(cwd: string): Promise<void> {
521
+ if (this.currentCwd !== cwd || !this.currentAgent) {
522
+ this.stopNoProjectPolling()
523
+ return
524
+ }
525
+ if (!this.ctlsurfApi.getApiKey()) return
526
+ try {
527
+ const folder = await this.ctlsurfApi.findFolderByPath(cwd)
528
+ if (folder?.id && this.currentCwd === cwd && this.currentAgent) {
529
+ log(`[worker-ws] Project folder appeared (${folder.id}); reconnecting`)
530
+ const agent = this.currentAgent
531
+ this.stopNoProjectPolling()
532
+ this.workerWs.disconnect()
533
+ this.connectWorkerWs(agent, cwd)
534
+ }
535
+ } catch { /* ignore — retry on next tick */ }
536
+ }
537
+
494
538
  private async checkProjectStatus(cwd: string): Promise<void> {
495
539
  if (!this.ctlsurfApi.getApiKey()) {
496
540
  this.events.onWorkerStatus('no_project')
@@ -524,6 +568,7 @@ export class Orchestrator {
524
568
  // ─── Shutdown ───────────────────────────────────
525
569
 
526
570
  async shutdown(): Promise<void> {
571
+ this.stopNoProjectPolling()
527
572
  this.bridge.endSession()
528
573
  await this.timeTracker.endAll()
529
574
  for (const [, tab] of this.tabs) {