@electerm/electerm-react 3.12.0 → 3.15.28
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/client/common/default-setting.js +1 -0
- package/client/common/is-absolute-path.js +1 -1
- package/client/common/normalize-remote-path.js +20 -0
- package/client/common/resolve.js +16 -0
- package/client/components/ai/agent-tools.js +121 -1
- package/client/components/ai/ai-chat-history-item.jsx +7 -2
- package/client/components/ai/ai-chat.jsx +1 -0
- package/client/components/ai/ai-config-props.js +1 -0
- package/client/components/ai/ai-config.jsx +13 -2
- package/client/components/ai/ai-output.jsx +6 -2
- package/client/components/main/main.jsx +1 -1
- package/client/components/sftp/address-bar.jsx +22 -6
- package/client/components/sftp/file-item.jsx +43 -2
- package/client/components/sftp/file-read.js +11 -2
- package/client/components/sftp/sftp-entry.jsx +38 -3
- package/client/components/sys-menu/icons-map.jsx +10 -2
- package/client/components/terminal/osc52-addon.js +147 -0
- package/client/components/terminal/terminal-apis.js +9 -0
- package/client/components/terminal/terminal.jsx +119 -3
- package/client/components/terminal/xmodem-client.js +62 -0
- package/client/components/terminal-info/base.jsx +41 -38
- package/client/components/terminal-info/log-path-edit.jsx +3 -2
- package/client/store/common.js +1 -1
- package/client/store/load-data.js +14 -0
- package/client/store/mcp-handler.js +231 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import uid from '../common/uid'
|
|
7
7
|
import { settingMap } from '../common/constants'
|
|
8
8
|
import { refs, refsTabs } from '../components/common/ref'
|
|
9
|
+
import { runCmd } from '../components/terminal/terminal-apis'
|
|
9
10
|
import deepCopy from 'json-deep-copy'
|
|
10
11
|
import {
|
|
11
12
|
getLocalFileInfo,
|
|
@@ -118,6 +119,26 @@ export default Store => {
|
|
|
118
119
|
case 'wait_for_terminal_idle':
|
|
119
120
|
result = await store.mcpWaitForTerminalIdle(args)
|
|
120
121
|
break
|
|
122
|
+
case 'get_terminal_status':
|
|
123
|
+
result = store.mcpGetTerminalStatus(args)
|
|
124
|
+
break
|
|
125
|
+
case 'cancel_terminal_command':
|
|
126
|
+
result = store.mcpCancelTerminalCommand(args)
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
// Background task operations
|
|
130
|
+
case 'run_background_command':
|
|
131
|
+
result = store.mcpRunBackgroundCommand(args)
|
|
132
|
+
break
|
|
133
|
+
case 'get_background_task_status':
|
|
134
|
+
result = await store.mcpGetBackgroundTaskStatus(args)
|
|
135
|
+
break
|
|
136
|
+
case 'get_background_task_log':
|
|
137
|
+
result = await store.mcpGetBackgroundTaskLog(args)
|
|
138
|
+
break
|
|
139
|
+
case 'cancel_background_task':
|
|
140
|
+
result = await store.mcpCancelBackgroundTask(args)
|
|
141
|
+
break
|
|
121
142
|
|
|
122
143
|
// SFTP operations
|
|
123
144
|
case 'sftp_list':
|
|
@@ -652,6 +673,216 @@ export default Store => {
|
|
|
652
673
|
}
|
|
653
674
|
}
|
|
654
675
|
|
|
676
|
+
// ==================== Terminal Status & Cancel ====================
|
|
677
|
+
|
|
678
|
+
Store.prototype.mcpGetTerminalStatus = function (args) {
|
|
679
|
+
const { store } = window
|
|
680
|
+
const tabId = args.tabId || store.activeTabId
|
|
681
|
+
if (!tabId) {
|
|
682
|
+
throw new Error('No active terminal')
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const tabRef = refsTabs.get('tab-' + tabId)
|
|
686
|
+
const onData = tabRef?.state.terminalOnData || ''
|
|
687
|
+
const term = refs.get('term-' + tabId)
|
|
688
|
+
|
|
689
|
+
let output = ''
|
|
690
|
+
let lineCount = 0
|
|
691
|
+
if (term && term.term) {
|
|
692
|
+
const buffer = term.term.buffer.active
|
|
693
|
+
if (buffer) {
|
|
694
|
+
const lines = []
|
|
695
|
+
const cursorY = buffer.cursorY || 0
|
|
696
|
+
const baseY = buffer.baseY || 0
|
|
697
|
+
const totalLines = buffer.length || 0
|
|
698
|
+
const end = baseY + cursorY + 1
|
|
699
|
+
const start = Math.max(0, end - 20)
|
|
700
|
+
for (let i = start; i < Math.min(totalLines, end); i++) {
|
|
701
|
+
const line = buffer.getLine(i)
|
|
702
|
+
if (line) {
|
|
703
|
+
lines.push(line.translateToString(true))
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
output = lines.join('\n')
|
|
707
|
+
lineCount = lines.length
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return {
|
|
712
|
+
tabId,
|
|
713
|
+
isRunning: onData === 'feed',
|
|
714
|
+
hasPasswordPrompt: onData === 'password',
|
|
715
|
+
isIdle: !onData,
|
|
716
|
+
output,
|
|
717
|
+
lineCount
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
Store.prototype.mcpCancelTerminalCommand = function (args) {
|
|
722
|
+
const { store } = window
|
|
723
|
+
const tabId = args.tabId || store.activeTabId
|
|
724
|
+
if (!tabId) {
|
|
725
|
+
throw new Error('No active terminal')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const term = refs.get('term-' + tabId)
|
|
729
|
+
if (!term || !term.attachAddon) {
|
|
730
|
+
throw new Error('Terminal not found')
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
term.attachAddon._sendData('\x03')
|
|
734
|
+
return {
|
|
735
|
+
success: true,
|
|
736
|
+
message: 'Sent Ctrl+C to terminal',
|
|
737
|
+
tabId
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ==================== Background Task Management ====================
|
|
742
|
+
|
|
743
|
+
const backgroundTasks = new Map()
|
|
744
|
+
let bgTaskCounter = 0
|
|
745
|
+
|
|
746
|
+
async function runMonitorCmd (tabId, cmd) {
|
|
747
|
+
try {
|
|
748
|
+
const result = await runCmd(tabId, cmd)
|
|
749
|
+
return result
|
|
750
|
+
} catch (e) {
|
|
751
|
+
// Fallback: send via terminal and wait for idle
|
|
752
|
+
const { store } = window
|
|
753
|
+
store.mcpSendTerminalCommand({ command: cmd, tabId })
|
|
754
|
+
const idle = await store.mcpWaitForTerminalIdle({
|
|
755
|
+
tabId, timeout: 10000, lines: 10, minWait: 500
|
|
756
|
+
})
|
|
757
|
+
return idle.output || ''
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
Store.prototype.mcpRunBackgroundCommand = function (args) {
|
|
762
|
+
const { store } = window
|
|
763
|
+
const tabId = args.tabId || store.activeTabId
|
|
764
|
+
if (!tabId) {
|
|
765
|
+
throw new Error('No active terminal')
|
|
766
|
+
}
|
|
767
|
+
if (!args.command) {
|
|
768
|
+
throw new Error('No command provided')
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const taskId = `bg-${Date.now()}-${++bgTaskCounter}`
|
|
772
|
+
const logFile = `/tmp/electerm-${taskId}.log`
|
|
773
|
+
const pidFile = `/tmp/electerm-${taskId}.pid`
|
|
774
|
+
const exitFile = `/tmp/electerm-${taskId}.exit`
|
|
775
|
+
|
|
776
|
+
// Encode command as base64 to avoid all quote-escaping issues.
|
|
777
|
+
// The subshell runs the user's command, captures its exit code, then cleans up the PID file.
|
|
778
|
+
const b64 = btoa(args.command)
|
|
779
|
+
const inner = `eval "$(echo ${b64} | base64 --decode)" > ${logFile} 2>&1; e=$?; echo $e > ${exitFile}; rm -f ${pidFile}`
|
|
780
|
+
const wrapped = `nohup bash -c '${inner}' & echo $! > ${pidFile}; disown`
|
|
781
|
+
|
|
782
|
+
store.mcpSendTerminalCommand({ command: wrapped, tabId, inputOnly: false })
|
|
783
|
+
|
|
784
|
+
const task = {
|
|
785
|
+
id: taskId,
|
|
786
|
+
command: args.command,
|
|
787
|
+
tabId,
|
|
788
|
+
startTime: Date.now(),
|
|
789
|
+
logFile,
|
|
790
|
+
pidFile,
|
|
791
|
+
exitFile,
|
|
792
|
+
status: 'started'
|
|
793
|
+
}
|
|
794
|
+
backgroundTasks.set(taskId, task)
|
|
795
|
+
|
|
796
|
+
return {
|
|
797
|
+
taskId,
|
|
798
|
+
tabId,
|
|
799
|
+
logFile,
|
|
800
|
+
pidFile,
|
|
801
|
+
exitFile,
|
|
802
|
+
message: 'Command started in background. Use get_background_task_status to check.'
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
Store.prototype.mcpGetBackgroundTaskStatus = async function (args) {
|
|
807
|
+
const task = backgroundTasks.get(args.taskId)
|
|
808
|
+
if (!task) {
|
|
809
|
+
throw new Error(`Task ${args.taskId} not found`)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const pidOutput = await runMonitorCmd(task.tabId,
|
|
813
|
+
`cat ${task.pidFile} 2>/dev/null`)
|
|
814
|
+
const pid = pidOutput.trim()
|
|
815
|
+
|
|
816
|
+
if (!pid) {
|
|
817
|
+
return { ...task, status: 'unknown', message: 'PID file not found' }
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const aliveCheck = await runMonitorCmd(task.tabId,
|
|
821
|
+
`kill -0 ${pid} 2>/dev/null && echo alive || echo dead`)
|
|
822
|
+
|
|
823
|
+
if (aliveCheck.trim() === 'alive') {
|
|
824
|
+
task.status = 'running'
|
|
825
|
+
return { ...task, pid, status: 'running' }
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Process exited — read exit code
|
|
829
|
+
const exitOutput = await runMonitorCmd(task.tabId,
|
|
830
|
+
`cat ${task.exitFile} 2>/dev/null`)
|
|
831
|
+
const exitCode = exitOutput.trim()
|
|
832
|
+
|
|
833
|
+
task.status = 'completed'
|
|
834
|
+
task.exitCode = exitCode !== '' ? parseInt(exitCode, 10) : null
|
|
835
|
+
task.endTime = Date.now()
|
|
836
|
+
return { ...task, pid, status: 'completed', exitCode: task.exitCode }
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
Store.prototype.mcpGetBackgroundTaskLog = async function (args) {
|
|
840
|
+
const task = backgroundTasks.get(args.taskId)
|
|
841
|
+
if (!task) {
|
|
842
|
+
throw new Error(`Task ${args.taskId} not found`)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const lines = args.lines || 100
|
|
846
|
+
const output = await runMonitorCmd(task.tabId,
|
|
847
|
+
`tail -n ${lines} ${task.logFile} 2>/dev/null || echo '(no output yet)'`)
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
taskId: task.id,
|
|
851
|
+
output: output.trim(),
|
|
852
|
+
lines
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
Store.prototype.mcpCancelBackgroundTask = async function (args) {
|
|
857
|
+
const task = backgroundTasks.get(args.taskId)
|
|
858
|
+
if (!task) {
|
|
859
|
+
throw new Error(`Task ${args.taskId} not found`)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const pidOutput = await runMonitorCmd(task.tabId,
|
|
863
|
+
`cat ${task.pidFile} 2>/dev/null`)
|
|
864
|
+
const pid = pidOutput.trim()
|
|
865
|
+
|
|
866
|
+
if (pid) {
|
|
867
|
+
await runMonitorCmd(task.tabId,
|
|
868
|
+
`kill ${pid} 2>/dev/null; echo $? > ${task.exitFile}`)
|
|
869
|
+
task.status = 'cancelled'
|
|
870
|
+
task.endTime = Date.now()
|
|
871
|
+
return {
|
|
872
|
+
taskId: task.id,
|
|
873
|
+
pid,
|
|
874
|
+
status: 'cancelled',
|
|
875
|
+
message: 'Process killed'
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return {
|
|
880
|
+
taskId: task.id,
|
|
881
|
+
status: 'unknown',
|
|
882
|
+
message: 'PID not found, task may have already finished'
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
655
886
|
// ==================== Settings APIs ====================
|
|
656
887
|
|
|
657
888
|
Store.prototype.mcpGetSettings = function () {
|