mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-07 13:07:53 +08:00
85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
import { randomUUID } from 'node:crypto'
|
|
|
|
// 长任务由终端侧的命令边界协议判断完成;这里仅防止浏览器或会话失联后
|
|
// 无限悬挂。持续输出的 docker pull / 构建任务可正常运行一小时。
|
|
const TIMEOUT_MS = 60 * 60 * 1000
|
|
const MAX_RESULT_CHARS = 256 * 1024
|
|
const pending = new Map()
|
|
|
|
export function requestTerminalDispatch({ sessionId, hostId, command, explanation, toolCallId, emit, signal }) {
|
|
const requestId = randomUUID()
|
|
|
|
return new Promise((resolve) => {
|
|
const settle = (result) => {
|
|
const entry = pending.get(requestId)
|
|
if (!entry) return
|
|
clearTimeout(entry.timer)
|
|
pending.delete(requestId)
|
|
if (signal && entry.onAbort) signal.removeEventListener('abort', entry.onAbort)
|
|
resolve(result)
|
|
}
|
|
|
|
// 终端命令已经通过另一个 socket 写进 PTY。这里不能只结束 agent
|
|
// 的等待,否则 UI 虽显示“已停止”,远端进程仍会继续跑。
|
|
const cancel = (reason) => {
|
|
emit({ type: 'terminal_command_cancel', requestId, toolCallId, reason })
|
|
settle({ ok: false, error: reason })
|
|
}
|
|
|
|
const timer = setTimeout(() => {
|
|
emit({ type: 'terminal_command_timeout', requestId, toolCallId })
|
|
cancel('终端命令等待超时(60 分钟),已请求中断远端命令')
|
|
}, TIMEOUT_MS)
|
|
const onAbort = () => cancel('对话已停止,已请求中断远端命令')
|
|
|
|
pending.set(requestId, { sessionId, toolCallId, emit, timer, onAbort, settle })
|
|
if (signal) {
|
|
if (signal.aborted) return onAbort()
|
|
signal.addEventListener('abort', onAbort, { once: true })
|
|
}
|
|
|
|
emit({ type: 'terminal_command_request', requestId, hostId, command, explanation })
|
|
})
|
|
}
|
|
|
|
export function resolveTerminalDispatch(requestId, payload = {}) {
|
|
const entry = pending.get(requestId)
|
|
if (!entry) return { ok: false, error: '终端命令请求已失效' }
|
|
entry.settle({
|
|
ok: Boolean(payload.ok),
|
|
error: payload.error,
|
|
output: typeof payload.output === 'string' ? payload.output.slice(-MAX_RESULT_CHARS) : '',
|
|
capturedAt: Number.isFinite(payload.capturedAt) ? payload.capturedAt : undefined,
|
|
durationMs: Number.isFinite(payload.durationMs) ? payload.durationMs : undefined,
|
|
exitCode: Number.isFinite(payload.exitCode) ? payload.exitCode : null
|
|
})
|
|
return { ok: true }
|
|
}
|
|
|
|
export function reportTerminalDispatchProgress(requestId, payload = {}) {
|
|
const entry = pending.get(requestId)
|
|
if (!entry) return { ok: false, error: '终端命令请求已失效' }
|
|
entry.emit({
|
|
type: 'terminal_command_progress',
|
|
requestId,
|
|
toolCallId: entry.toolCallId,
|
|
output: typeof payload.output === 'string' ? payload.output.slice(-6 * 1024) : '',
|
|
capturedAt: Number.isFinite(payload.capturedAt) ? payload.capturedAt : undefined,
|
|
durationMs: Number.isFinite(payload.durationMs) ? payload.durationMs : undefined
|
|
})
|
|
return { ok: true }
|
|
}
|
|
|
|
export function clearTerminalDispatchBySession(sessionId) {
|
|
for (const [requestId, entry] of pending.entries()) {
|
|
if (entry.sessionId !== sessionId) continue
|
|
entry.emit({
|
|
type: 'terminal_command_cancel',
|
|
requestId,
|
|
toolCallId: entry.toolCallId,
|
|
reason: '会话已结束,已请求中断远端命令'
|
|
})
|
|
entry.settle({ ok: false, error: '会话已结束,已请求中断远端命令' })
|
|
}
|
|
}
|