pinokiod 3.208.0 → 3.210.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/api/terminal/index.js +106 -4
- package/kernel/bracketed_paste_detector.js +99 -0
- package/kernel/environment.js +2 -2
- package/kernel/shell.js +26 -0
- package/kernel/shells.js +60 -3
- package/package.json +1 -1
- package/server/index.js +3 -1
- package/server/views/shell.ejs +13 -3
- package/server/views/terminal.ejs +16 -4
|
@@ -136,14 +136,23 @@ class Terminal {
|
|
|
136
136
|
return { files: saved, errors: failures }
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const marker =
|
|
139
|
+
const marker = this.buildAttachmentPayload(saved, {
|
|
140
|
+
shellInstance,
|
|
141
|
+
params
|
|
142
|
+
})
|
|
143
|
+
let shellEmitAttempted = false
|
|
144
|
+
let shellEmitSucceeded = false
|
|
140
145
|
if (params && params.id && kernel && kernel.shell && typeof kernel.shell.emit === 'function') {
|
|
141
146
|
try {
|
|
142
|
-
|
|
147
|
+
shellEmitAttempted = true
|
|
148
|
+
shellEmitSucceeded = kernel.shell.emit({
|
|
143
149
|
id: params.id,
|
|
144
150
|
emit: marker,
|
|
145
151
|
paste: true
|
|
146
|
-
})
|
|
152
|
+
}) === true
|
|
153
|
+
if (!shellEmitSucceeded && ondata) {
|
|
154
|
+
ondata({ raw: marker })
|
|
155
|
+
}
|
|
147
156
|
} catch (error) {
|
|
148
157
|
if (ondata) {
|
|
149
158
|
ondata({ raw: marker })
|
|
@@ -164,7 +173,100 @@ class Terminal {
|
|
|
164
173
|
req.params.buffers = {}
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
return { files: saved, errors: failures }
|
|
176
|
+
return { files: saved, errors: failures, shellEmit: shellEmitSucceeded, shellEmitAttempted }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
buildAttachmentPayload(files, context = {}) {
|
|
180
|
+
const markerPrefix = ''
|
|
181
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
182
|
+
return markerPrefix
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const shellName = this.resolveShellName(context)
|
|
186
|
+
const escapedPaths = files
|
|
187
|
+
.map((file) => {
|
|
188
|
+
if (!file || typeof file !== 'object') {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
const rawPath = this.pickBestPath(file)
|
|
192
|
+
if (!rawPath) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
return this.escapePathForShell(shellName, rawPath)
|
|
196
|
+
})
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
|
|
199
|
+
if (escapedPaths.length === 0) {
|
|
200
|
+
return markerPrefix
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return escapedPaths.join(' ')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
resolveShellName(context) {
|
|
207
|
+
if (!context) {
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
210
|
+
const { shellInstance, params } = context
|
|
211
|
+
if (shellInstance && typeof shellInstance.shell === 'string' && shellInstance.shell.trim()) {
|
|
212
|
+
return shellInstance.shell.trim()
|
|
213
|
+
}
|
|
214
|
+
if (params && typeof params.shell === 'string' && params.shell.trim()) {
|
|
215
|
+
return params.shell.trim()
|
|
216
|
+
}
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
escapePathForShell(shellName, rawPath) {
|
|
221
|
+
if (!rawPath || typeof rawPath !== 'string') {
|
|
222
|
+
return ''
|
|
223
|
+
}
|
|
224
|
+
const normalizedPath = rawPath
|
|
225
|
+
if (!shellName) {
|
|
226
|
+
return this.escapePosixPath(normalizedPath)
|
|
227
|
+
}
|
|
228
|
+
const lowerShell = shellName.toLowerCase()
|
|
229
|
+
if (lowerShell.includes('powershell') || lowerShell.includes('pwsh')) {
|
|
230
|
+
return this.escapePowershellPath(normalizedPath)
|
|
231
|
+
}
|
|
232
|
+
if (lowerShell.includes('cmd.exe') || lowerShell === 'cmd') {
|
|
233
|
+
return this.escapeCmdPath(normalizedPath)
|
|
234
|
+
}
|
|
235
|
+
return this.escapePosixPath(normalizedPath)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
pickBestPath(file) {
|
|
239
|
+
const candidates = [
|
|
240
|
+
file.cliRelativePath,
|
|
241
|
+
file.cliPath,
|
|
242
|
+
file.path,
|
|
243
|
+
file.displayPath,
|
|
244
|
+
file.homeRelativePath,
|
|
245
|
+
file.storedAs,
|
|
246
|
+
file.originalName,
|
|
247
|
+
typeof file.name === 'string' ? file.name : null
|
|
248
|
+
]
|
|
249
|
+
for (const candidate of candidates) {
|
|
250
|
+
if (typeof candidate === 'string') {
|
|
251
|
+
const trimmed = candidate.trim()
|
|
252
|
+
if (trimmed.length > 0) {
|
|
253
|
+
return trimmed
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
escapePosixPath(input) {
|
|
261
|
+
return `'${input.split("'").join("'\\''")}'`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
escapePowershellPath(input) {
|
|
265
|
+
return `'${input.split("'").join("''")}'`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
escapeCmdPath(input) {
|
|
269
|
+
return `"${input.replace(/(["^%])/g, '^$1')}"`
|
|
168
270
|
}
|
|
169
271
|
|
|
170
272
|
resolveShellInstance(params, kernel) {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const { spawn } = require('child_process')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
|
|
4
|
+
const cache = new Map()
|
|
5
|
+
|
|
6
|
+
const OK_MARKER = '__PINOKIO_BP_OK__'
|
|
7
|
+
const NO_MARKER = '__PINOKIO_BP_NO__'
|
|
8
|
+
|
|
9
|
+
function normalizeKey(shell, platform) {
|
|
10
|
+
return `${platform || os.platform()}::${(shell || '').toLowerCase()}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isWindowsShell(shell) {
|
|
14
|
+
const name = (shell || '').toLowerCase()
|
|
15
|
+
return name.includes('cmd.exe') || name === 'cmd' || name.includes('powershell') || name.includes('pwsh')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isBash(shell) {
|
|
19
|
+
return (shell || '').toLowerCase().includes('bash')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isZsh(shell) {
|
|
23
|
+
return (shell || '').toLowerCase().includes('zsh')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runProbe(shell, args, script) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let stdout = ''
|
|
29
|
+
const child = spawn(shell, [...args, script], {
|
|
30
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
31
|
+
})
|
|
32
|
+
child.stdout.setEncoding('utf8')
|
|
33
|
+
child.stdout.on('data', (chunk) => {
|
|
34
|
+
stdout += chunk
|
|
35
|
+
})
|
|
36
|
+
child.on('error', () => resolve(null))
|
|
37
|
+
child.on('close', () => resolve(stdout))
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function detect(shell, platform = os.platform()) {
|
|
42
|
+
const key = normalizeKey(shell, platform)
|
|
43
|
+
if (cache.has(key)) {
|
|
44
|
+
return cache.get(key)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let result
|
|
48
|
+
try {
|
|
49
|
+
result = await runDetection(shell, platform)
|
|
50
|
+
} catch (_) {
|
|
51
|
+
result = null
|
|
52
|
+
}
|
|
53
|
+
if (result === null) {
|
|
54
|
+
result = true
|
|
55
|
+
}
|
|
56
|
+
cache.set(key, result)
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runDetection(shell, platform) {
|
|
61
|
+
if (!shell) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
if (isWindowsShell(shell)) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isBash(shell)) {
|
|
69
|
+
const script = `if bind -v 2>/dev/null | grep -q 'enable-bracketed-paste'; then bind 'set enable-bracketed-paste on' >/dev/null 2>&1; printf '${OK_MARKER}\n'; else printf '${NO_MARKER}\n'; fi`
|
|
70
|
+
const output = await runProbe(shell, ['--noprofile', '--norc', '-ic'], script)
|
|
71
|
+
return parseProbeOutput(output)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isZsh(shell)) {
|
|
75
|
+
const script = `if setopt -q bracketed-paste 2>/dev/null; then printf '${OK_MARKER}\n'; exit 0; fi; if setopt bracketed-paste 2>/dev/null; then printf '${OK_MARKER}\n'; exit 0; fi; printf '${NO_MARKER}\n'`
|
|
76
|
+
const output = await runProbe(shell, ['-f', '-c'], script)
|
|
77
|
+
return parseProbeOutput(output)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// For other shells assume supported unless clearly Windows-only.
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseProbeOutput(output) {
|
|
85
|
+
if (typeof output !== 'string') {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
if (output.includes(OK_MARKER)) {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
if (output.includes(NO_MARKER)) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
detect
|
|
99
|
+
}
|
package/kernel/environment.js
CHANGED
package/kernel/shell.js
CHANGED
|
@@ -40,6 +40,13 @@ class Shell {
|
|
|
40
40
|
this.platform = os.platform()
|
|
41
41
|
this.logs = {}
|
|
42
42
|
this.shell = this.platform === 'win32' ? 'cmd.exe' : 'bash';
|
|
43
|
+
this.supportsBracketedPaste = this.computeBracketedPasteSupport(this.shell)
|
|
44
|
+
if (this.kernel && this.kernel.bracketedPasteSupport) {
|
|
45
|
+
const cached = this.kernel.bracketedPasteSupport[(this.shell || '').toLowerCase()]
|
|
46
|
+
if (typeof cached === 'boolean') {
|
|
47
|
+
this.supportsBracketedPaste = cached
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
this.decsyncBuffer = ''
|
|
44
51
|
|
|
45
52
|
// Windows: /D => ignore AutoRun Registry Key
|
|
@@ -276,6 +283,12 @@ class Shell {
|
|
|
276
283
|
this.EOL = os.EOL
|
|
277
284
|
if (this.params.shell) {
|
|
278
285
|
this.shell = this.params.shell
|
|
286
|
+
const normalizedShell = (this.shell || '').toLowerCase()
|
|
287
|
+
if (this.kernel && this.kernel.bracketedPasteSupport && typeof this.kernel.bracketedPasteSupport[normalizedShell] === 'boolean') {
|
|
288
|
+
this.supportsBracketedPaste = this.kernel.bracketedPasteSupport[normalizedShell]
|
|
289
|
+
} else {
|
|
290
|
+
this.supportsBracketedPaste = this.computeBracketedPasteSupport(this.shell)
|
|
291
|
+
}
|
|
279
292
|
if (/bash/i.test(this.shell)) {
|
|
280
293
|
this.args = ["--noprofile", "--norc"]
|
|
281
294
|
//this.args = [ "--login", "-i"]
|
|
@@ -579,6 +592,19 @@ class Shell {
|
|
|
579
592
|
const regex = new RegExp(pattern, 'gi')
|
|
580
593
|
return str.replaceAll(regex, '');
|
|
581
594
|
}
|
|
595
|
+
computeBracketedPasteSupport(shellName) {
|
|
596
|
+
const name = (shellName || '').toLowerCase()
|
|
597
|
+
if (!name) {
|
|
598
|
+
return true
|
|
599
|
+
}
|
|
600
|
+
if (name.includes('cmd.exe') || name === 'cmd') {
|
|
601
|
+
return false
|
|
602
|
+
}
|
|
603
|
+
if (name.includes('powershell') || name.includes('pwsh')) {
|
|
604
|
+
return false
|
|
605
|
+
}
|
|
606
|
+
return true
|
|
607
|
+
}
|
|
582
608
|
exists(abspath) {
|
|
583
609
|
return new Promise(r=>fs.access(abspath, fs.constants.F_OK, e => r(!e)))
|
|
584
610
|
}
|
package/kernel/shells.js
CHANGED
|
@@ -9,10 +9,12 @@ const {
|
|
|
9
9
|
|
|
10
10
|
const path = require('path')
|
|
11
11
|
const Shell = require("./shell")
|
|
12
|
+
const { detect: detectBracketedPasteSupport } = require('./bracketed_paste_detector')
|
|
12
13
|
class Shells {
|
|
13
14
|
constructor(kernel) {
|
|
14
15
|
this.kernel = kernel
|
|
15
16
|
this.shells = []
|
|
17
|
+
this.bracketedPasteDetections = new Map()
|
|
16
18
|
|
|
17
19
|
}
|
|
18
20
|
/*
|
|
@@ -40,6 +42,14 @@ class Shells {
|
|
|
40
42
|
env: this.kernel.bin.envs({}),
|
|
41
43
|
})
|
|
42
44
|
|
|
45
|
+
await this.ensureBracketedPasteSupport(sh.shell)
|
|
46
|
+
if (this.kernel.bracketedPasteSupport) {
|
|
47
|
+
const cached = this.kernel.bracketedPasteSupport[(sh.shell || '').toLowerCase()]
|
|
48
|
+
if (typeof cached === 'boolean') {
|
|
49
|
+
sh.supportsBracketedPaste = cached
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
this.kernel.envs = sh.env
|
|
44
54
|
// also set the uppercase variables if they're not set
|
|
45
55
|
for(let key in sh.env) {
|
|
@@ -67,6 +77,44 @@ class Shells {
|
|
|
67
77
|
}
|
|
68
78
|
})
|
|
69
79
|
}
|
|
80
|
+
async ensureBracketedPasteSupport(shellName) {
|
|
81
|
+
if (!shellName) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const lower = (shellName || '').toLowerCase()
|
|
85
|
+
if (!lower) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
if (!this.kernel.bracketedPasteSupport) {
|
|
89
|
+
this.kernel.bracketedPasteSupport = {}
|
|
90
|
+
}
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(this.kernel.bracketedPasteSupport, lower)) {
|
|
92
|
+
return this.kernel.bracketedPasteSupport[lower]
|
|
93
|
+
}
|
|
94
|
+
if (this.bracketedPasteDetections.has(lower)) {
|
|
95
|
+
return this.bracketedPasteDetections.get(lower)
|
|
96
|
+
}
|
|
97
|
+
const fallback = !(lower.includes('cmd.exe') || lower === 'cmd' || lower.includes('powershell') || lower.includes('pwsh'))
|
|
98
|
+
const detectionPromise = detectBracketedPasteSupport(shellName, this.kernel.platform || os.platform())
|
|
99
|
+
.then((support) => {
|
|
100
|
+
const value = typeof support === 'boolean' ? support : fallback
|
|
101
|
+
this.kernel.bracketedPasteSupport[lower] = value
|
|
102
|
+
return value
|
|
103
|
+
})
|
|
104
|
+
.catch((error) => {
|
|
105
|
+
console.warn('[shells.ensureBracketedPasteSupport] detection failed', {
|
|
106
|
+
shell: shellName,
|
|
107
|
+
error: error && error.message ? error.message : error
|
|
108
|
+
})
|
|
109
|
+
this.kernel.bracketedPasteSupport[lower] = fallback
|
|
110
|
+
return fallback
|
|
111
|
+
})
|
|
112
|
+
.finally(() => {
|
|
113
|
+
this.bracketedPasteDetections.delete(lower)
|
|
114
|
+
})
|
|
115
|
+
this.bracketedPasteDetections.set(lower, detectionPromise)
|
|
116
|
+
return detectionPromise
|
|
117
|
+
}
|
|
70
118
|
async launch(params, options, ondata) {
|
|
71
119
|
// if array, duplicate the action
|
|
72
120
|
if (Array.isArray(params.message)) {
|
|
@@ -112,6 +160,9 @@ class Shells {
|
|
|
112
160
|
let exec_path = (params.path ? params.path : ".") // use the current path if not specified
|
|
113
161
|
let cwd = (options && options.cwd ? options.cwd : this.kernel.homedir) // if cwd exists, use it. Otherwise the cwd is pinokio home folder (~/pinokio)
|
|
114
162
|
params.path = this.kernel.api.resolvePath(cwd, exec_path)
|
|
163
|
+
|
|
164
|
+
const plannedShell = params.shell || (this.kernel.platform === 'win32' ? 'cmd.exe' : 'bash')
|
|
165
|
+
await this.ensureBracketedPasteSupport(plannedShell)
|
|
115
166
|
let sh = new Shell(this.kernel)
|
|
116
167
|
if (options) {
|
|
117
168
|
params.group = options.group // set group
|
|
@@ -387,18 +438,24 @@ class Shells {
|
|
|
387
438
|
let session = this.get(params.id)
|
|
388
439
|
if (session) {
|
|
389
440
|
if (params.paste) {
|
|
390
|
-
|
|
391
|
-
session.
|
|
392
|
-
|
|
441
|
+
const payload = params.emit != null ? String(params.emit) : ''
|
|
442
|
+
if (session.supportsBracketedPaste !== false) {
|
|
443
|
+
//session.emit("\x1b[?2004h\x1b[200~" + params.emit+ "\x1b[201~")
|
|
444
|
+
session.emit("\x1b[200~" + payload + "\x1b[201~")
|
|
445
|
+
} else {
|
|
446
|
+
session.emit(payload)
|
|
447
|
+
}
|
|
393
448
|
} else {
|
|
394
449
|
session.emit(params.emit)
|
|
395
450
|
}
|
|
451
|
+
return true
|
|
396
452
|
} else {
|
|
397
453
|
const preview = typeof params.emit === 'string' ? params.emit.slice(0, 64) : ''
|
|
398
454
|
console.warn('[shell.emit] missing session for id', params.id, {
|
|
399
455
|
paste: !!params.paste,
|
|
400
456
|
preview
|
|
401
457
|
})
|
|
458
|
+
return false
|
|
402
459
|
}
|
|
403
460
|
}
|
|
404
461
|
async send(params, ondata, enter) {
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -4868,7 +4868,9 @@ class Server {
|
|
|
4868
4868
|
const result = await terminalApi.upload(requestPayload, () => {}, this.kernel)
|
|
4869
4869
|
const files = result && Array.isArray(result.files) ? result.files : []
|
|
4870
4870
|
const errors = result && Array.isArray(result.errors) ? result.errors : []
|
|
4871
|
-
|
|
4871
|
+
const shellEmit = result && typeof result.shellEmit === 'boolean' ? result.shellEmit : false
|
|
4872
|
+
const shellEmitAttempted = result && typeof result.shellEmitAttempted === 'boolean' ? result.shellEmitAttempted : false
|
|
4873
|
+
res.json({ files, errors, shellEmit, shellEmitAttempted })
|
|
4872
4874
|
} catch (error) {
|
|
4873
4875
|
res.status(500).json({ error: error && error.message ? error.message : 'remote upload failed' })
|
|
4874
4876
|
}
|
package/server/views/shell.ejs
CHANGED
|
@@ -207,7 +207,7 @@ body.dark #status-window b {
|
|
|
207
207
|
<script>
|
|
208
208
|
let shell_id
|
|
209
209
|
let lastNotifiedShellId = null
|
|
210
|
-
const formatUploadNotification = (files, sessionId) => {
|
|
210
|
+
const formatUploadNotification = (files, sessionId, shellEmit, shellEmitAttempted) => {
|
|
211
211
|
if (!Array.isArray(files) || files.length === 0) {
|
|
212
212
|
return 'Upload complete (no files reported)'
|
|
213
213
|
}
|
|
@@ -219,7 +219,15 @@ const formatUploadNotification = (files, sessionId) => {
|
|
|
219
219
|
const summary = target ? `${name} → ${target}` : name
|
|
220
220
|
return summary.length > 180 ? `${summary.slice(0, 177)}…` : summary
|
|
221
221
|
})
|
|
222
|
-
|
|
222
|
+
let emitStatus
|
|
223
|
+
if (shellEmitAttempted) {
|
|
224
|
+
emitStatus = shellEmit ? 'Shell emit: delivered to terminal session' : 'Shell emit: failed – fallback used'
|
|
225
|
+
} else if (shellEmitAttempted === false) {
|
|
226
|
+
emitStatus = 'Shell emit: not attempted'
|
|
227
|
+
} else {
|
|
228
|
+
emitStatus = 'Shell emit: status unknown'
|
|
229
|
+
}
|
|
230
|
+
return `${countLabel}<br>Session: ${sessionLabel}<br>${emitStatus}<br>${summaries.join('<br>')}`
|
|
223
231
|
}
|
|
224
232
|
const postMessageToAncestors = (payload) => {
|
|
225
233
|
if (!payload || typeof payload !== "object") {
|
|
@@ -946,8 +954,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
946
954
|
reloadMemory()
|
|
947
955
|
}
|
|
948
956
|
} catch (_) {}
|
|
957
|
+
const shellEmit = typeof result.shellEmit === 'boolean' ? result.shellEmit : undefined
|
|
958
|
+
const shellEmitAttempted = typeof result.shellEmitAttempted === 'boolean' ? result.shellEmitAttempted : undefined
|
|
949
959
|
n.Noty({
|
|
950
|
-
text: formatUploadNotification(files, shell_id),
|
|
960
|
+
text: formatUploadNotification(files, shell_id, shellEmit, shellEmitAttempted),
|
|
951
961
|
timeout: 6000
|
|
952
962
|
})
|
|
953
963
|
}
|
|
@@ -182,7 +182,7 @@ body.frozen {
|
|
|
182
182
|
<link href="/terminal.css" rel="stylesheet"/>
|
|
183
183
|
<script>
|
|
184
184
|
let shell_id
|
|
185
|
-
const formatUploadNotification = (files, sessionId) => {
|
|
185
|
+
const formatUploadNotification = (files, sessionId, shellEmit, shellEmitAttempted) => {
|
|
186
186
|
if (!Array.isArray(files) || files.length === 0) {
|
|
187
187
|
return 'Upload complete (no files reported)'
|
|
188
188
|
}
|
|
@@ -194,7 +194,15 @@ const formatUploadNotification = (files, sessionId) => {
|
|
|
194
194
|
const summary = target ? `${name} → ${target}` : name
|
|
195
195
|
return summary.length > 180 ? `${summary.slice(0, 177)}…` : summary
|
|
196
196
|
})
|
|
197
|
-
|
|
197
|
+
let emitStatus
|
|
198
|
+
if (shellEmitAttempted) {
|
|
199
|
+
emitStatus = shellEmit ? 'Shell emit: delivered to terminal session' : 'Shell emit: failed – fallback used'
|
|
200
|
+
} else if (shellEmitAttempted === false) {
|
|
201
|
+
emitStatus = 'Shell emit: not attempted'
|
|
202
|
+
} else {
|
|
203
|
+
emitStatus = 'Shell emit: status unknown'
|
|
204
|
+
}
|
|
205
|
+
return `${countLabel}<br>Session: ${sessionLabel}<br>${emitStatus}<br>${summaries.join('<br>')}`
|
|
198
206
|
}
|
|
199
207
|
Dropzone.autoDiscover = false;
|
|
200
208
|
function postMessageToAncestors(payload) {
|
|
@@ -799,8 +807,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
799
807
|
type: "terminal.upload",
|
|
800
808
|
files: mappedFiles
|
|
801
809
|
})
|
|
810
|
+
const shellEmit = packet.data && typeof packet.data.shellEmit === 'boolean' ? packet.data.shellEmit : undefined
|
|
811
|
+
const shellEmitAttempted = packet.data && typeof packet.data.shellEmitAttempted === 'boolean' ? packet.data.shellEmitAttempted : undefined
|
|
802
812
|
n.Noty({
|
|
803
|
-
text: formatUploadNotification(mappedFiles, shell_id),
|
|
813
|
+
text: formatUploadNotification(mappedFiles, shell_id, shellEmit, shellEmitAttempted),
|
|
804
814
|
timeout: 6000
|
|
805
815
|
})
|
|
806
816
|
} else {
|
|
@@ -1043,8 +1053,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1043
1053
|
reloadMemory()
|
|
1044
1054
|
}
|
|
1045
1055
|
} catch (_) {}
|
|
1056
|
+
const shellEmit = typeof result.shellEmit === 'boolean' ? result.shellEmit : undefined
|
|
1057
|
+
const shellEmitAttempted = typeof result.shellEmitAttempted === 'boolean' ? result.shellEmitAttempted : undefined
|
|
1046
1058
|
n.Noty({
|
|
1047
|
-
text: formatUploadNotification(mappedFiles, shell_id),
|
|
1059
|
+
text: formatUploadNotification(mappedFiles, shell_id, shellEmit, shellEmitAttempted),
|
|
1048
1060
|
timeout: 6000
|
|
1049
1061
|
})
|
|
1050
1062
|
}
|