pinokiod 3.270.0 → 3.272.0
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/kernel/ansi_stream_tracker.js +115 -0
- package/kernel/api/app/index.js +422 -0
- package/kernel/api/htmlmodal/index.js +94 -0
- package/kernel/app_launcher/index.js +115 -0
- package/kernel/app_launcher/platform/base.js +276 -0
- package/kernel/app_launcher/platform/linux.js +229 -0
- package/kernel/app_launcher/platform/macos.js +163 -0
- package/kernel/app_launcher/platform/unsupported.js +34 -0
- package/kernel/app_launcher/platform/windows.js +247 -0
- package/kernel/bin/conda-meta.js +93 -0
- package/kernel/bin/conda.js +2 -4
- package/kernel/bin/index.js +2 -4
- package/kernel/index.js +9 -2
- package/kernel/peer.js +186 -19
- package/kernel/shell.js +212 -1
- package/package.json +1 -1
- package/server/index.js +491 -6
- package/server/public/common.js +224 -741
- package/server/public/create-launcher.js +754 -0
- package/server/public/htmlmodal.js +292 -0
- package/server/public/logs.js +715 -0
- package/server/public/resizeSync.js +117 -0
- package/server/public/style.css +651 -6
- package/server/public/tab-idle-notifier.js +34 -59
- package/server/public/tab-link-popover.js +7 -10
- package/server/public/terminal-settings.js +723 -9
- package/server/public/terminal_input_utils.js +72 -0
- package/server/public/terminal_key_caption.js +187 -0
- package/server/public/urldropdown.css +120 -3
- package/server/public/xterm-inline-bridge.js +116 -0
- package/server/socket.js +29 -0
- package/server/views/agents.ejs +1 -2
- package/server/views/app.ejs +55 -28
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -0
- package/server/views/connect.ejs +1 -2
- package/server/views/create.ejs +63 -0
- package/server/views/editor.ejs +36 -4
- package/server/views/index.ejs +1 -2
- package/server/views/index2.ejs +1 -2
- package/server/views/init/index.ejs +36 -28
- package/server/views/install.ejs +20 -22
- package/server/views/layout.ejs +2 -8
- package/server/views/logs.ejs +155 -0
- package/server/views/mini.ejs +0 -18
- package/server/views/net.ejs +2 -2
- package/server/views/network.ejs +1 -2
- package/server/views/network2.ejs +1 -2
- package/server/views/old_network.ejs +1 -2
- package/server/views/pro.ejs +26 -23
- package/server/views/prototype/index.ejs +30 -34
- package/server/views/screenshots.ejs +1 -2
- package/server/views/settings.ejs +1 -20
- package/server/views/shell.ejs +59 -66
- package/server/views/terminal.ejs +118 -73
- package/server/views/tools.ejs +1 -2
package/kernel/peer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const dgram = require('dgram');
|
|
2
2
|
const axios = require('axios');
|
|
3
3
|
const os = require('os')
|
|
4
|
+
const systeminformation = require('systeminformation')
|
|
4
5
|
const Environment = require("./environment")
|
|
5
6
|
class PeerDiscovery {
|
|
6
7
|
constructor(kernel, port = 41234, message = 'ping', interval = 1000) {
|
|
@@ -12,9 +13,8 @@ class PeerDiscovery {
|
|
|
12
13
|
this.peers = new Set();
|
|
13
14
|
this.interface_addresses = []
|
|
14
15
|
this.host_candidates = []
|
|
15
|
-
this.host =
|
|
16
|
+
this.host = null
|
|
16
17
|
this.default_port = 42000
|
|
17
|
-
this.peers.add(this.host)
|
|
18
18
|
this.router_info_cache = {}
|
|
19
19
|
// this.start();
|
|
20
20
|
}
|
|
@@ -85,6 +85,13 @@ class PeerDiscovery {
|
|
|
85
85
|
this.active = false
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
// Prepare host/peer state before the rest of the kernel bootstraps
|
|
89
|
+
async initialize(kernel) {
|
|
90
|
+
await this.refreshLocalAddress()
|
|
91
|
+
if (kernel) {
|
|
92
|
+
await this.check(kernel)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
88
95
|
async start(kernel) {
|
|
89
96
|
let env = await Environment.get(kernel.homedir, kernel)
|
|
90
97
|
|
|
@@ -576,23 +583,70 @@ class PeerDiscovery {
|
|
|
576
583
|
_isLocalLAN(ip) {
|
|
577
584
|
return this.isRFC1918(ip)
|
|
578
585
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
+
// Refresh LAN/IP selection; keeps peers set in sync with the active address
|
|
587
|
+
async refreshLocalAddress() {
|
|
588
|
+
try {
|
|
589
|
+
const { host, host_candidates, interface_addresses } = await this._getLocalIPAddress()
|
|
590
|
+
this.interface_addresses = interface_addresses
|
|
591
|
+
this.host_candidates = host_candidates
|
|
592
|
+
if (host && this.host !== host) {
|
|
593
|
+
if (this.host) {
|
|
594
|
+
this.peers.delete(this.host)
|
|
595
|
+
}
|
|
596
|
+
this.host = host
|
|
597
|
+
} else if (!this.host) {
|
|
598
|
+
this.host = host
|
|
599
|
+
}
|
|
600
|
+
if (this.host) {
|
|
601
|
+
this.peers.add(this.host)
|
|
602
|
+
}
|
|
603
|
+
return this.host
|
|
604
|
+
} catch (err) {
|
|
605
|
+
console.error('peer refreshLocalAddress error', err)
|
|
606
|
+
if (!this.host) {
|
|
607
|
+
this.host = null
|
|
608
|
+
}
|
|
609
|
+
return this.host
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async _getLocalIPAddress() {
|
|
613
|
+
const interface_addresses = await this._collectInterfaceAddresses()
|
|
614
|
+
const shareable = interface_addresses.filter((entry) => entry.shareable)
|
|
615
|
+
const host_candidates = shareable.map((entry) => ({
|
|
616
|
+
address: entry.address,
|
|
617
|
+
netmask: entry.netmask,
|
|
618
|
+
interface: entry.interface,
|
|
619
|
+
scope: entry.scope,
|
|
620
|
+
shareable: entry.shareable,
|
|
621
|
+
type: entry.type || null,
|
|
622
|
+
operstate: entry.operstate || null,
|
|
623
|
+
virtual: entry.virtual || false,
|
|
624
|
+
default: entry.default || false,
|
|
625
|
+
prefixLength: entry.prefixLength,
|
|
626
|
+
mac: entry.mac || null,
|
|
627
|
+
score: this._scoreCandidate(entry)
|
|
628
|
+
}))
|
|
629
|
+
let selectedHost = null
|
|
630
|
+
let bestScore = -Infinity
|
|
631
|
+
host_candidates.forEach((candidate, index) => {
|
|
632
|
+
const score = typeof candidate.score === 'number' ? candidate.score : -Infinity
|
|
633
|
+
if (score > bestScore) {
|
|
634
|
+
bestScore = score
|
|
635
|
+
selectedHost = candidate.address
|
|
636
|
+
} else if (score === bestScore && selectedHost === null) {
|
|
637
|
+
selectedHost = candidate.address
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
if (!selectedHost && shareable.length > 0) {
|
|
641
|
+
selectedHost = shareable[0].address
|
|
586
642
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return cgnatCandidate.address
|
|
643
|
+
if (!selectedHost && interface_addresses.length > 0) {
|
|
644
|
+
selectedHost = interface_addresses[0].address
|
|
590
645
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
return publicCandidate.address
|
|
646
|
+
if (!selectedHost) {
|
|
647
|
+
selectedHost = '127.0.0.1'
|
|
594
648
|
}
|
|
595
|
-
return
|
|
649
|
+
return { host: selectedHost, host_candidates, interface_addresses }
|
|
596
650
|
}
|
|
597
651
|
isPrivateOrCGNAT(ip) {
|
|
598
652
|
return this.isRFC1918(ip) || this.isCGNAT(ip)
|
|
@@ -626,7 +680,7 @@ class PeerDiscovery {
|
|
|
626
680
|
}
|
|
627
681
|
return secondOctet >= 16 && secondOctet <= 31
|
|
628
682
|
}
|
|
629
|
-
|
|
683
|
+
_collectInterfaceAddressesSync() {
|
|
630
684
|
const interfaces = os.networkInterfaces()
|
|
631
685
|
const results = []
|
|
632
686
|
const seen = new Set()
|
|
@@ -653,12 +707,51 @@ class PeerDiscovery {
|
|
|
653
707
|
interface: ifaceName,
|
|
654
708
|
internal: Boolean(iface.internal),
|
|
655
709
|
scope: classification.scope,
|
|
656
|
-
shareable: classification.shareable
|
|
710
|
+
shareable: classification.shareable,
|
|
711
|
+
mac: typeof iface.mac === 'string' ? iface.mac : null
|
|
657
712
|
})
|
|
658
713
|
}
|
|
659
714
|
}
|
|
660
715
|
return results
|
|
661
716
|
}
|
|
717
|
+
async _collectInterfaceAddresses() {
|
|
718
|
+
const baseEntries = this._collectInterfaceAddressesSync()
|
|
719
|
+
let metadata = []
|
|
720
|
+
try {
|
|
721
|
+
metadata = await systeminformation.networkInterfaces()
|
|
722
|
+
} catch (err) {
|
|
723
|
+
metadata = []
|
|
724
|
+
}
|
|
725
|
+
const metadataMap = new Map()
|
|
726
|
+
if (Array.isArray(metadata)) {
|
|
727
|
+
metadata.forEach((entry) => {
|
|
728
|
+
if (entry && typeof entry.iface === 'string') {
|
|
729
|
+
metadataMap.set(this._normalizeInterfaceName(entry.iface), entry)
|
|
730
|
+
}
|
|
731
|
+
})
|
|
732
|
+
}
|
|
733
|
+
return baseEntries.map((entry) => {
|
|
734
|
+
const key = this._normalizeInterfaceName(entry.interface)
|
|
735
|
+
const meta = key ? metadataMap.get(key) : null
|
|
736
|
+
const prefixLength = this._prefixLengthFromNetmask(entry.netmask)
|
|
737
|
+
return {
|
|
738
|
+
...entry,
|
|
739
|
+
prefixLength,
|
|
740
|
+
type: meta && meta.type ? meta.type : null,
|
|
741
|
+
operstate: meta && meta.operstate ? meta.operstate : null,
|
|
742
|
+
speed: typeof meta?.speed === 'number' ? meta.speed : null,
|
|
743
|
+
virtual: Boolean(meta && meta.virtual),
|
|
744
|
+
default: Boolean(meta && meta.default),
|
|
745
|
+
mac: entry.mac || (meta && meta.mac) || null
|
|
746
|
+
}
|
|
747
|
+
})
|
|
748
|
+
}
|
|
749
|
+
_normalizeInterfaceName(name) {
|
|
750
|
+
if (!name || typeof name !== 'string') {
|
|
751
|
+
return ''
|
|
752
|
+
}
|
|
753
|
+
return name.trim().toLowerCase()
|
|
754
|
+
}
|
|
662
755
|
classifyAddress(address, isInternal = false) {
|
|
663
756
|
if (!address || typeof address !== 'string') {
|
|
664
757
|
return { scope: 'unknown', shareable: false }
|
|
@@ -684,6 +777,80 @@ class PeerDiscovery {
|
|
|
684
777
|
}
|
|
685
778
|
return { scope: 'public', shareable: true }
|
|
686
779
|
}
|
|
780
|
+
_prefixLengthFromNetmask(netmask) {
|
|
781
|
+
if (!netmask || typeof netmask !== 'string') {
|
|
782
|
+
return null
|
|
783
|
+
}
|
|
784
|
+
const octets = this._parseIPv4(netmask)
|
|
785
|
+
if (!octets) {
|
|
786
|
+
return null
|
|
787
|
+
}
|
|
788
|
+
let bits = 0
|
|
789
|
+
for (const octet of octets) {
|
|
790
|
+
bits += this._countBits(octet)
|
|
791
|
+
}
|
|
792
|
+
return bits
|
|
793
|
+
}
|
|
794
|
+
_countBits(value) {
|
|
795
|
+
let count = 0
|
|
796
|
+
let v = value & 255
|
|
797
|
+
while (v) {
|
|
798
|
+
count += v & 1
|
|
799
|
+
v >>= 1
|
|
800
|
+
}
|
|
801
|
+
return count
|
|
802
|
+
}
|
|
803
|
+
// Heuristically rank interface candidates so physical LAN adapters win over VPN/tunnels
|
|
804
|
+
_scoreCandidate(entry) {
|
|
805
|
+
if (!entry || !entry.shareable) {
|
|
806
|
+
return -Infinity
|
|
807
|
+
}
|
|
808
|
+
let score = 0
|
|
809
|
+
switch (entry.scope) {
|
|
810
|
+
case 'lan':
|
|
811
|
+
score += 100
|
|
812
|
+
break
|
|
813
|
+
case 'cgnat':
|
|
814
|
+
score += 60
|
|
815
|
+
break
|
|
816
|
+
case 'public':
|
|
817
|
+
score += 40
|
|
818
|
+
break
|
|
819
|
+
default:
|
|
820
|
+
score -= 50
|
|
821
|
+
break
|
|
822
|
+
}
|
|
823
|
+
if (entry.default) {
|
|
824
|
+
score += 20
|
|
825
|
+
}
|
|
826
|
+
const type = entry.type ? entry.type.toLowerCase() : ''
|
|
827
|
+
if (type === 'wired') {
|
|
828
|
+
score += 25
|
|
829
|
+
} else if (type === 'wireless') {
|
|
830
|
+
score += 18
|
|
831
|
+
} else if (type === 'vpn') {
|
|
832
|
+
score -= 40
|
|
833
|
+
} else if (type === 'cellular') {
|
|
834
|
+
score += 5
|
|
835
|
+
}
|
|
836
|
+
if (entry.virtual) {
|
|
837
|
+
score -= 25
|
|
838
|
+
}
|
|
839
|
+
if (entry.operstate && entry.operstate.toLowerCase() === 'up') {
|
|
840
|
+
score += 5
|
|
841
|
+
} else if (entry.operstate) {
|
|
842
|
+
score -= 10
|
|
843
|
+
}
|
|
844
|
+
if (typeof entry.prefixLength === 'number') {
|
|
845
|
+
if (entry.prefixLength <= 24) {
|
|
846
|
+
score += 5
|
|
847
|
+
}
|
|
848
|
+
if (entry.prefixLength >= 30) {
|
|
849
|
+
score -= 20
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return score
|
|
853
|
+
}
|
|
687
854
|
_buildExternalHostEntries(externalPort) {
|
|
688
855
|
if (!externalPort && externalPort !== 0) {
|
|
689
856
|
return []
|
|
@@ -741,7 +908,7 @@ class PeerDiscovery {
|
|
|
741
908
|
return entries
|
|
742
909
|
}
|
|
743
910
|
_broadcastTargets() {
|
|
744
|
-
const addresses = this.
|
|
911
|
+
const addresses = this._collectInterfaceAddressesSync()
|
|
745
912
|
this.interface_addresses = addresses
|
|
746
913
|
const targets = new Set()
|
|
747
914
|
for (const entry of addresses) {
|
package/kernel/shell.js
CHANGED
|
@@ -17,6 +17,7 @@ const unparse = require('yargs-unparser-custom-flag');
|
|
|
17
17
|
const Util = require('./util')
|
|
18
18
|
const Environment = require('./environment')
|
|
19
19
|
const ShellParser = require('./shell_parser')
|
|
20
|
+
const AnsiStreamTracker = require('./ansi_stream_tracker')
|
|
20
21
|
const home = os.homedir()
|
|
21
22
|
|
|
22
23
|
// xterm.js currently ignores DECSYNCTERM (CSI ? 2026 h/l) and renders it as text on Windows.
|
|
@@ -48,6 +49,19 @@ class Shell {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
this.decsyncBuffer = ''
|
|
52
|
+
this.nudgeRestoreTimer = null
|
|
53
|
+
this.nudging = false
|
|
54
|
+
this.nudgeReleaseTimer = null
|
|
55
|
+
this.lastInputAt = 0
|
|
56
|
+
this.canNudge = true
|
|
57
|
+
this.awaitingIdleNudge = false
|
|
58
|
+
this.idleNudgeTimer = null
|
|
59
|
+
this.idleNudgeDelay = 100
|
|
60
|
+
this.ignoreNudgeOutput = false
|
|
61
|
+
this.userActive = false
|
|
62
|
+
this.userActiveTimer = null
|
|
63
|
+
this.userActiveTimeout = 1000
|
|
64
|
+
this.ansiTracker = new AnsiStreamTracker()
|
|
51
65
|
|
|
52
66
|
// Windows: /D => ignore AutoRun Registry Key
|
|
53
67
|
// Others: --noprofile => ignore .bash_profile, --norc => ignore .bashrc
|
|
@@ -229,6 +243,25 @@ class Shell {
|
|
|
229
243
|
}
|
|
230
244
|
async start(params, ondata) {
|
|
231
245
|
this.ondata = ondata
|
|
246
|
+
if (this.nudgeRestoreTimer) {
|
|
247
|
+
clearTimeout(this.nudgeRestoreTimer)
|
|
248
|
+
this.nudgeRestoreTimer = null
|
|
249
|
+
}
|
|
250
|
+
if (this.nudgeReleaseTimer) {
|
|
251
|
+
clearTimeout(this.nudgeReleaseTimer)
|
|
252
|
+
this.nudgeReleaseTimer = null
|
|
253
|
+
}
|
|
254
|
+
this.cancelIdleNudge()
|
|
255
|
+
this.nudging = false
|
|
256
|
+
this.lastInputAt = 0
|
|
257
|
+
this.canNudge = true
|
|
258
|
+
this.ignoreNudgeOutput = false
|
|
259
|
+
if (this.userActiveTimer) {
|
|
260
|
+
clearTimeout(this.userActiveTimer)
|
|
261
|
+
this.userActiveTimer = null
|
|
262
|
+
}
|
|
263
|
+
this.userActive = false
|
|
264
|
+
this.decsyncBuffer = ''
|
|
232
265
|
|
|
233
266
|
/*
|
|
234
267
|
params := {
|
|
@@ -383,6 +416,9 @@ class Shell {
|
|
|
383
416
|
// return this.id
|
|
384
417
|
}
|
|
385
418
|
resize({ cols, rows }) {
|
|
419
|
+
// console.log("RESIZE", { cols, rows })
|
|
420
|
+
this.cols = cols
|
|
421
|
+
this.rows = rows
|
|
386
422
|
this.ptyProcess.resize(cols, rows)
|
|
387
423
|
this.vt.resize(cols, rows)
|
|
388
424
|
}
|
|
@@ -400,6 +436,8 @@ class Shell {
|
|
|
400
436
|
chunk_size = 1024
|
|
401
437
|
}
|
|
402
438
|
// console.log({ interactive: this.params.interactive, chunk_size })
|
|
439
|
+
this.canNudge = true
|
|
440
|
+
this.cancelIdleNudge()
|
|
403
441
|
for(let i=0; i<message.length; i+=chunk_size) {
|
|
404
442
|
let chunk = message.slice(i, i+chunk_size)
|
|
405
443
|
// console.log("write chunk", { i, chunk })
|
|
@@ -417,9 +455,15 @@ class Shell {
|
|
|
417
455
|
if (this.input) {
|
|
418
456
|
if (this.ptyProcess) {
|
|
419
457
|
if (message.length > 1024) {
|
|
458
|
+
this.lastInputAt = Date.now()
|
|
459
|
+
this.canNudge = true
|
|
460
|
+
this.cancelIdleNudge()
|
|
420
461
|
this.emit2(message)
|
|
421
462
|
} else {
|
|
422
463
|
this.ptyProcess.write(message)
|
|
464
|
+
this.lastInputAt = Date.now()
|
|
465
|
+
this.canNudge = true
|
|
466
|
+
this.cancelIdleNudge()
|
|
423
467
|
}
|
|
424
468
|
}
|
|
425
469
|
}
|
|
@@ -433,15 +477,27 @@ class Shell {
|
|
|
433
477
|
for(let m of message) {
|
|
434
478
|
this.cmd = this.build({ message: m })
|
|
435
479
|
this.ptyProcess.write(this.cmd)
|
|
480
|
+
this.lastInputAt = Date.now()
|
|
481
|
+
this.canNudge = true
|
|
482
|
+
this.cancelIdleNudge()
|
|
436
483
|
if (newline) {
|
|
437
484
|
this.ptyProcess.write(this.EOL)
|
|
485
|
+
this.lastInputAt = Date.now()
|
|
486
|
+
this.canNudge = true
|
|
487
|
+
this.cancelIdleNudge()
|
|
438
488
|
}
|
|
439
489
|
}
|
|
440
490
|
} else {
|
|
441
491
|
this.cmd = this.build({ message })
|
|
442
492
|
this.ptyProcess.write(this.cmd)
|
|
493
|
+
this.lastInputAt = Date.now()
|
|
494
|
+
this.canNudge = true
|
|
495
|
+
this.cancelIdleNudge()
|
|
443
496
|
if (newline) {
|
|
444
497
|
this.ptyProcess.write(this.EOL)
|
|
498
|
+
this.lastInputAt = Date.now()
|
|
499
|
+
this.canNudge = true
|
|
500
|
+
this.cancelIdleNudge()
|
|
445
501
|
}
|
|
446
502
|
}
|
|
447
503
|
})
|
|
@@ -457,11 +513,17 @@ class Shell {
|
|
|
457
513
|
this.cmd = this.build({ message: m })
|
|
458
514
|
this.ptyProcess.write(this.cmd)
|
|
459
515
|
this.ptyProcess.write(this.EOL)
|
|
516
|
+
this.lastInputAt = Date.now()
|
|
517
|
+
this.canNudge = true
|
|
518
|
+
this.cancelIdleNudge()
|
|
460
519
|
}
|
|
461
520
|
} else {
|
|
462
521
|
this.cmd = this.build({ message })
|
|
463
522
|
this.ptyProcess.write(this.cmd)
|
|
464
523
|
this.ptyProcess.write(this.EOL)
|
|
524
|
+
this.lastInputAt = Date.now()
|
|
525
|
+
this.canNudge = true
|
|
526
|
+
this.cancelIdleNudge()
|
|
465
527
|
}
|
|
466
528
|
})
|
|
467
529
|
}
|
|
@@ -473,6 +535,9 @@ class Shell {
|
|
|
473
535
|
this.resolve = resolve
|
|
474
536
|
this.cmd = this.build({ message })
|
|
475
537
|
this.ptyProcess.write(this.cmd)
|
|
538
|
+
this.lastInputAt = Date.now()
|
|
539
|
+
this.canNudge = true
|
|
540
|
+
this.cancelIdleNudge()
|
|
476
541
|
})
|
|
477
542
|
}
|
|
478
543
|
}
|
|
@@ -1113,7 +1178,12 @@ class Shell {
|
|
|
1113
1178
|
data = data.replace(/\x1b\[6n/g, ''); // remove the code
|
|
1114
1179
|
}
|
|
1115
1180
|
|
|
1116
|
-
this.
|
|
1181
|
+
const filtered = this.filterDecsync(data)
|
|
1182
|
+
if (this.awaitingIdleNudge) {
|
|
1183
|
+
this.scheduleIdleNudge()
|
|
1184
|
+
}
|
|
1185
|
+
this.maybeNudgeForSequences(filtered)
|
|
1186
|
+
this.queue.push(filtered)
|
|
1117
1187
|
}
|
|
1118
1188
|
});
|
|
1119
1189
|
}
|
|
@@ -1143,6 +1213,24 @@ class Shell {
|
|
|
1143
1213
|
|
|
1144
1214
|
this.done = true
|
|
1145
1215
|
this.ready = false
|
|
1216
|
+
if (this.nudgeRestoreTimer) {
|
|
1217
|
+
clearTimeout(this.nudgeRestoreTimer)
|
|
1218
|
+
this.nudgeRestoreTimer = null
|
|
1219
|
+
}
|
|
1220
|
+
if (this.nudgeReleaseTimer) {
|
|
1221
|
+
clearTimeout(this.nudgeReleaseTimer)
|
|
1222
|
+
this.nudgeReleaseTimer = null
|
|
1223
|
+
}
|
|
1224
|
+
this.cancelIdleNudge()
|
|
1225
|
+
this.nudging = false
|
|
1226
|
+
this.lastInputAt = 0
|
|
1227
|
+
this.canNudge = true
|
|
1228
|
+
this.ignoreNudgeOutput = false
|
|
1229
|
+
if (this.userActiveTimer) {
|
|
1230
|
+
clearTimeout(this.userActiveTimer)
|
|
1231
|
+
this.userActiveTimer = null
|
|
1232
|
+
}
|
|
1233
|
+
this.userActive = false
|
|
1146
1234
|
|
|
1147
1235
|
let buf = this.vts.serialize()
|
|
1148
1236
|
let cleaned = this.stripAnsi(buf)
|
|
@@ -1260,6 +1348,129 @@ class Shell {
|
|
|
1260
1348
|
|
|
1261
1349
|
return result
|
|
1262
1350
|
}
|
|
1351
|
+
setUserActive(active, ttl) {
|
|
1352
|
+
const clearTimer = () => {
|
|
1353
|
+
if (this.userActiveTimer) {
|
|
1354
|
+
clearTimeout(this.userActiveTimer)
|
|
1355
|
+
this.userActiveTimer = null
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (active) {
|
|
1359
|
+
this.userActive = true
|
|
1360
|
+
this.lastInputAt = Date.now()
|
|
1361
|
+
const parsedTtl = Number(ttl)
|
|
1362
|
+
const timeout = Number.isFinite(parsedTtl) ? parsedTtl : this.userActiveTimeout
|
|
1363
|
+
clearTimer()
|
|
1364
|
+
if (timeout > 0) {
|
|
1365
|
+
this.userActiveTimer = setTimeout(() => {
|
|
1366
|
+
this.userActive = false
|
|
1367
|
+
this.userActiveTimer = null
|
|
1368
|
+
}, timeout)
|
|
1369
|
+
}
|
|
1370
|
+
this.cancelIdleNudge()
|
|
1371
|
+
} else {
|
|
1372
|
+
clearTimer()
|
|
1373
|
+
this.userActive = false
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
maybeNudgeForSequences(chunk = '') {
|
|
1377
|
+
if (!chunk || typeof chunk !== 'string') {
|
|
1378
|
+
return
|
|
1379
|
+
}
|
|
1380
|
+
const detection = this.ansiTracker.push(chunk)
|
|
1381
|
+
if (!detection) {
|
|
1382
|
+
return
|
|
1383
|
+
}
|
|
1384
|
+
if (this.ignoreNudgeOutput || this.userActive) {
|
|
1385
|
+
return
|
|
1386
|
+
}
|
|
1387
|
+
if (this.nudging || !this.canNudge) {
|
|
1388
|
+
// console.log('[nudge] guard: nudging/canNudge', { nudging: this.nudging, canNudge: this.canNudge, reason: detection.reason })
|
|
1389
|
+
return
|
|
1390
|
+
}
|
|
1391
|
+
const sinceInput = Date.now() - this.lastInputAt
|
|
1392
|
+
if (sinceInput < 200) {
|
|
1393
|
+
// console.log('[nudge] guard: recent input', { sinceInput, reason: detection.reason })
|
|
1394
|
+
return
|
|
1395
|
+
}
|
|
1396
|
+
// console.log('[nudge] scheduling idle nudge', {
|
|
1397
|
+
// reason: detection.reason,
|
|
1398
|
+
// sinceInput,
|
|
1399
|
+
// preview: chunk.slice(0, 160)
|
|
1400
|
+
// })
|
|
1401
|
+
this.requestIdleNudge()
|
|
1402
|
+
}
|
|
1403
|
+
cancelIdleNudge() {
|
|
1404
|
+
if (this.idleNudgeTimer) {
|
|
1405
|
+
clearTimeout(this.idleNudgeTimer)
|
|
1406
|
+
this.idleNudgeTimer = null
|
|
1407
|
+
}
|
|
1408
|
+
this.awaitingIdleNudge = false
|
|
1409
|
+
}
|
|
1410
|
+
requestIdleNudge() {
|
|
1411
|
+
if (this.awaitingIdleNudge || this.nudging || !this.canNudge) {
|
|
1412
|
+
return
|
|
1413
|
+
}
|
|
1414
|
+
this.awaitingIdleNudge = true
|
|
1415
|
+
this.scheduleIdleNudge()
|
|
1416
|
+
}
|
|
1417
|
+
scheduleIdleNudge() {
|
|
1418
|
+
if (!this.awaitingIdleNudge) {
|
|
1419
|
+
return
|
|
1420
|
+
}
|
|
1421
|
+
const delay = this.idleNudgeDelay || 500
|
|
1422
|
+
if (this.idleNudgeTimer) {
|
|
1423
|
+
clearTimeout(this.idleNudgeTimer)
|
|
1424
|
+
}
|
|
1425
|
+
this.idleNudgeTimer = setTimeout(() => {
|
|
1426
|
+
if (this.nudging || !this.canNudge) {
|
|
1427
|
+
this.cancelIdleNudge()
|
|
1428
|
+
return
|
|
1429
|
+
}
|
|
1430
|
+
this.idleNudgeTimer = null
|
|
1431
|
+
this.awaitingIdleNudge = false
|
|
1432
|
+
this.canNudge = false
|
|
1433
|
+
// console.log('[nudge] idle window elapsed')
|
|
1434
|
+
this.forceTerminalNudge()
|
|
1435
|
+
}, delay)
|
|
1436
|
+
}
|
|
1437
|
+
forceTerminalNudge() {
|
|
1438
|
+
if (!this.ptyProcess || this.nudging) {
|
|
1439
|
+
// console.log('[nudge] force skipped', { hasPty: !!this.ptyProcess, nudging: this.nudging })
|
|
1440
|
+
return
|
|
1441
|
+
}
|
|
1442
|
+
this.cancelIdleNudge()
|
|
1443
|
+
const baseCols = Number.isFinite(this.cols) ? this.cols : (this.vt && Number.isFinite(this.vt.cols) ? this.vt.cols : 80)
|
|
1444
|
+
const baseRows = Number.isFinite(this.rows) ? this.rows : (this.vt && Number.isFinite(this.vt.rows) ? this.vt.rows : 24)
|
|
1445
|
+
const cols = Math.max(2, Math.floor(baseCols))
|
|
1446
|
+
const rows = Math.max(2, Math.floor(baseRows))
|
|
1447
|
+
if (cols <= 2) {
|
|
1448
|
+
return
|
|
1449
|
+
}
|
|
1450
|
+
this.ignoreNudgeOutput = true
|
|
1451
|
+
this.nudging = true
|
|
1452
|
+
// console.log('[nudge] shrink start', { cols: cols - 1, rows })
|
|
1453
|
+
this.resize({ cols: cols - 1, rows })
|
|
1454
|
+
if (this.nudgeRestoreTimer) {
|
|
1455
|
+
clearTimeout(this.nudgeRestoreTimer)
|
|
1456
|
+
}
|
|
1457
|
+
this.nudgeRestoreTimer = setTimeout(() => {
|
|
1458
|
+
// console.log('[nudge] restore', { cols, rows })
|
|
1459
|
+
// console.log('[nudge] restore start', { cols, rows })
|
|
1460
|
+
this.resize({ cols, rows })
|
|
1461
|
+
if (this.nudgeReleaseTimer) {
|
|
1462
|
+
clearTimeout(this.nudgeReleaseTimer)
|
|
1463
|
+
}
|
|
1464
|
+
this.nudgeReleaseTimer = setTimeout(() => {
|
|
1465
|
+
this.nudging = false
|
|
1466
|
+
this.canNudge = true
|
|
1467
|
+
this.ignoreNudgeOutput = false
|
|
1468
|
+
// console.log('[nudge] complete')
|
|
1469
|
+
this.nudgeReleaseTimer = null
|
|
1470
|
+
}, 100)
|
|
1471
|
+
this.nudgeRestoreTimer = null
|
|
1472
|
+
}, 100)
|
|
1473
|
+
}
|
|
1263
1474
|
_log(buf, cleaned) {
|
|
1264
1475
|
|
|
1265
1476
|
|