@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/headless/index.mjs +46 -1
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +45 -0
- package/package.json +1 -1
- package/src/main/orchestrator.ts +45 -0
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.
|
|
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": {
|
package/src/main/orchestrator.ts
CHANGED
|
@@ -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) {
|