@livedesk/client 0.1.120 → 0.1.122
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/THIRD_PARTY_NOTICES.md +0 -1
- package/bin/livedesk-client-node.js +261 -1
- package/fast/linux-x64/livedesk-client-fast.dll +0 -0
- package/fast/linux-x64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-arm64/livedesk-client-fast.dll +0 -0
- package/fast/osx-arm64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-x64/livedesk-client-fast.dll +0 -0
- package/fast/osx-x64/livedesk-client-fast.pdb +0 -0
- package/fast/win-x64/livedesk-client-fast.dll +0 -0
- package/fast/win-x64/livedesk-client-fast.pdb +0 -0
- package/package.json +1 -1
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -5,6 +5,7 @@ import os from 'os';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import { promises as fs, statfsSync } from 'fs';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
8
9
|
|
|
9
10
|
const AGENT_VERSION = '0.1.24-livedesk.1';
|
|
10
11
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
@@ -18,6 +19,7 @@ const MAX_FRAME_BASE64_CHARS = 3 * 1024 * 1024;
|
|
|
18
19
|
const MAX_AI_OUTPUT_CHARS = 6000;
|
|
19
20
|
const MAX_FILE_TRANSFER_FILES = 24;
|
|
20
21
|
const MAX_FILE_TRANSFER_BYTES = 24 * 1024 * 1024;
|
|
22
|
+
const MAX_AGENT_OUTPUT_CHARS = 32000;
|
|
21
23
|
|
|
22
24
|
function printHelp() {
|
|
23
25
|
console.log(`
|
|
@@ -868,6 +870,233 @@ function startLiveStream(socket, options, message, nextFrameSeq, activeStreams)
|
|
|
868
870
|
return { streamId, fps, intervalMs };
|
|
869
871
|
}
|
|
870
872
|
|
|
873
|
+
function isSensitiveAgentPath(value) {
|
|
874
|
+
const normalized = String(value || '').replaceAll('\\', '/').toLowerCase();
|
|
875
|
+
return normalized.includes('/.codex/') || normalized.endsWith('/auth.json') || normalized.includes('/.ssh/') || normalized.includes('/id_rsa') || normalized.endsWith('.pem') || normalized.endsWith('.key') || normalized.endsWith('/.env') || normalized.includes('/credential') || normalized.includes('/secret') || normalized.includes('/password');
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function resolveAgentPath(options, value, permissionMode, rejectSensitive = true) {
|
|
879
|
+
const base = normalizeDirectoryPath(options.filesDir || undefined);
|
|
880
|
+
const text = String(value || '').replace(/\0/g, '').trim();
|
|
881
|
+
if (!text || text.length > 600) throw new Error('path is invalid');
|
|
882
|
+
const resolved = path.resolve(path.isAbsolute(text) ? text : path.join(base, text));
|
|
883
|
+
const relative = path.relative(base, resolved);
|
|
884
|
+
if (permissionMode !== 'full-access' && (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))) throw new Error('path is outside the LiveDesk files directory for this permission mode');
|
|
885
|
+
if (rejectSensitive && isSensitiveAgentPath(resolved)) throw new Error('credential and secret paths are not available');
|
|
886
|
+
return resolved;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function runNodeAgentProcess(executable, args, timeoutMs = 15000, cwd = undefined) {
|
|
890
|
+
return new Promise((resolve, reject) => {
|
|
891
|
+
const child = spawn(executable, args, { cwd, windowsHide: true, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
892
|
+
let output = '';
|
|
893
|
+
let timedOut = false;
|
|
894
|
+
const append = chunk => { output = `${output}${String(chunk || '')}`.replace(/[\0\r]/g, ' ').slice(0, MAX_AGENT_OUTPUT_CHARS); };
|
|
895
|
+
child.stdout.on('data', append);
|
|
896
|
+
child.stderr.on('data', append);
|
|
897
|
+
const timer = setTimeout(() => {
|
|
898
|
+
timedOut = true;
|
|
899
|
+
child.kill('SIGTERM');
|
|
900
|
+
setTimeout(() => child.kill('SIGKILL'), 1000).unref?.();
|
|
901
|
+
}, Math.max(1000, Math.min(30000, Number(timeoutMs) || 15000)));
|
|
902
|
+
child.once('error', error => { clearTimeout(timer); reject(error); });
|
|
903
|
+
child.once('close', code => { clearTimeout(timer); resolve({ output: redactAgentOutput(`${output}${timedOut ? '\n[timeout]' : ''}`.trim()), exitCode: timedOut ? -1 : code ?? -1, timedOut }); });
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function redactAgentOutput(value) {
|
|
908
|
+
let output = String(value || '').replace(/[\0\r]/g, ' ');
|
|
909
|
+
output = output.replace(/\b(token|secret|password|api[-_]?key|authorization|private[-_]?key|connection[-_]?string|access[-_]?key|client[-_]?secret)\b\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1=[redacted]');
|
|
910
|
+
output = output.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]');
|
|
911
|
+
output = output.replace(/^(\s*(?:set\s+)?(?:[A-Z_][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|KEY|AUTH|CREDENTIAL|CONNECTION)[A-Z0-9_]*)\s*=\s*).+$/gim, '$1[redacted]');
|
|
912
|
+
return output.slice(0, MAX_AGENT_OUTPUT_CHARS);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
async function findNodeProcessExecutable(processName) {
|
|
916
|
+
const name = String(processName || '').trim();
|
|
917
|
+
if (!name) return '';
|
|
918
|
+
if (process.platform === 'win32') {
|
|
919
|
+
const result = await runNodeAgentProcess('powershell.exe', [
|
|
920
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
921
|
+
'$name=$args[0]; $p=Get-CimInstance Win32_Process | Where-Object { $_.Name -ieq $name } | Select-Object -First 1 -ExpandProperty ExecutablePath; if($p){[Console]::Out.Write($p)}',
|
|
922
|
+
name
|
|
923
|
+
], 10000);
|
|
924
|
+
return result.exitCode === 0 ? String(result.output || '').trim().split(/\r?\n/)[0] : '';
|
|
925
|
+
}
|
|
926
|
+
if (process.platform === 'linux') {
|
|
927
|
+
const result = await runNodeAgentProcess('/bin/sh', ['-lc', 'pid=$(pgrep -xo -- "$1" || true); if [ -n "$pid" ] && [ -e "/proc/$pid/exe" ]; then readlink -f "/proc/$pid/exe"; fi', 'livedesk-process-path', name], 10000);
|
|
928
|
+
return result.exitCode === 0 ? String(result.output || '').trim().split(/\r?\n/)[0] : '';
|
|
929
|
+
}
|
|
930
|
+
return '';
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async function persistNodeEnvironmentVariable(name, value) {
|
|
934
|
+
const variableName = String(name || '').trim();
|
|
935
|
+
const variableValue = String(value ?? '');
|
|
936
|
+
if (process.platform === 'win32') {
|
|
937
|
+
const result = await runNodeAgentProcess('setx.exe', [variableName, variableValue], 15000);
|
|
938
|
+
if (result.exitCode !== 0 || result.timedOut) throw new Error(`Persistent environment update failed (exit ${result.exitCode}).`);
|
|
939
|
+
process.env[variableName] = variableValue;
|
|
940
|
+
return { persisted: true, scope: 'user', output: result.output };
|
|
941
|
+
}
|
|
942
|
+
const profilePath = path.join(os.homedir(), '.profile');
|
|
943
|
+
const marker = `# LiveDesk managed environment: ${variableName}`;
|
|
944
|
+
const quoted = `'${variableValue.replaceAll("'", "'\\\"'\\\"'")}'`;
|
|
945
|
+
let profile = '';
|
|
946
|
+
try { profile = await fs.readFile(profilePath, 'utf8'); } catch { /* create on demand */ }
|
|
947
|
+
const lines = profile.split(/\r?\n/).filter(line => !line.includes(marker));
|
|
948
|
+
while (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
949
|
+
lines.push(marker, `export ${variableName}=${quoted}`);
|
|
950
|
+
await fs.writeFile(profilePath, `${lines.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
951
|
+
process.env[variableName] = variableValue;
|
|
952
|
+
return { persisted: true, scope: 'user-profile', path: profilePath };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
async function executeNodeAgentOperation(options, operation, payload = {}) {
|
|
956
|
+
const permissionMode = String(payload.permissionMode || 'ask');
|
|
957
|
+
const args = payload.toolArguments && typeof payload.toolArguments === 'object' ? payload.toolArguments : payload;
|
|
958
|
+
if (operation === 'file.read') {
|
|
959
|
+
const filePath = resolveAgentPath(options, args.path, permissionMode);
|
|
960
|
+
const info = await fs.stat(filePath);
|
|
961
|
+
const maxBytes = Math.max(1, Math.min(65536, Number(args.maxBytes) || 65536));
|
|
962
|
+
const content = (await fs.readFile(filePath)).subarray(0, maxBytes).toString('utf8');
|
|
963
|
+
return { summary: `Read ${Buffer.byteLength(content)} bytes from ${path.basename(filePath)}.`, data: { path: filePath, sizeBytes: info.size, returnedBytes: Buffer.byteLength(content), truncated: info.size > Buffer.byteLength(content), content } };
|
|
964
|
+
}
|
|
965
|
+
if (operation === 'file.write') {
|
|
966
|
+
const filePath = resolveAgentPath(options, args.path, permissionMode);
|
|
967
|
+
const content = String(args.content || '');
|
|
968
|
+
if (Buffer.byteLength(content) > 1048576) throw new Error('file content exceeds the 1 MiB limit');
|
|
969
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
970
|
+
if (args.append === true) await fs.appendFile(filePath, content, 'utf8');
|
|
971
|
+
else await fs.writeFile(filePath, content, 'utf8');
|
|
972
|
+
return { summary: `Wrote ${Buffer.byteLength(content)} bytes to ${path.basename(filePath)}.`, data: { path: filePath, sizeBytes: (await fs.stat(filePath)).size, append: args.append === true } };
|
|
973
|
+
}
|
|
974
|
+
if (operation === 'file.delete') {
|
|
975
|
+
const filePath = resolveAgentPath(options, args.path, permissionMode);
|
|
976
|
+
if (args.recursive === true) await fs.rm(filePath, { recursive: true, force: false });
|
|
977
|
+
else await fs.unlink(filePath);
|
|
978
|
+
return { summary: `Deleted ${path.basename(filePath)}.`, data: { path: filePath, recursive: args.recursive === true } };
|
|
979
|
+
}
|
|
980
|
+
if (operation === 'file.list') {
|
|
981
|
+
const directory = resolveAgentPath(options, args.path, permissionMode);
|
|
982
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
983
|
+
const maxEntries = Math.max(1, Math.min(500, Number(args.maxEntries) || 200));
|
|
984
|
+
const data = [];
|
|
985
|
+
for (const entry of entries.slice(0, maxEntries)) {
|
|
986
|
+
const entryPath = path.join(directory, entry.name);
|
|
987
|
+
if (isSensitiveAgentPath(entryPath)) continue;
|
|
988
|
+
data.push({ name: entry.name, path: entryPath, type: entry.isDirectory() ? 'directory' : 'file', sizeBytes: entry.isDirectory() ? 0 : (await fs.stat(entryPath)).size });
|
|
989
|
+
}
|
|
990
|
+
return { summary: `Listed ${data.length} entries.`, data: { path: directory, entries: data, truncated: entries.length > maxEntries } };
|
|
991
|
+
}
|
|
992
|
+
if (operation === 'application.launch') {
|
|
993
|
+
const executable = String(args.executable || '').trim();
|
|
994
|
+
if (!executable || executable.length > 400 || /[\0\r\n]/.test(executable)) throw new Error('executable is invalid');
|
|
995
|
+
const child = spawn(executable, Array.isArray(args.args) ? args.args.slice(0, 32).map(String) : [], { cwd: args.workingDirectory ? resolveAgentPath(options, args.workingDirectory, permissionMode, false) : undefined, detached: true, windowsHide: true, stdio: 'ignore' });
|
|
996
|
+
child.unref();
|
|
997
|
+
return { summary: `Started ${path.basename(executable)}.`, data: { executable, pid: child.pid } };
|
|
998
|
+
}
|
|
999
|
+
if (operation === 'process.control') {
|
|
1000
|
+
const action = String(args.action || 'stop');
|
|
1001
|
+
const processName = String(args.processName || '').trim();
|
|
1002
|
+
if (action === 'restart') {
|
|
1003
|
+
const executable = await findNodeProcessExecutable(processName);
|
|
1004
|
+
if (!executable) throw new Error(`Restart is not supported because the executable path for ${processName} could not be resolved.`);
|
|
1005
|
+
const stop = await runNodeAgentProcess(process.platform === 'win32' ? 'taskkill' : 'pkill', process.platform === 'win32' ? ['/IM', processName, '/T', '/F'] : ['-TERM', processName]);
|
|
1006
|
+
if (stop.exitCode !== 0 && !stop.timedOut) throw new Error(`Process stop failed before restart (exit ${stop.exitCode}).`);
|
|
1007
|
+
const child = spawn(executable, [], { detached: true, windowsHide: true, stdio: 'ignore' });
|
|
1008
|
+
child.unref();
|
|
1009
|
+
return { summary: `Restarted ${processName}.`, data: { action, processName, stop, restarted: true, pid: child.pid, executable } };
|
|
1010
|
+
}
|
|
1011
|
+
const result = await runNodeAgentProcess(process.platform === 'win32' ? 'taskkill' : 'pkill', process.platform === 'win32' ? ['/IM', processName, '/T'] : ['-TERM', processName]);
|
|
1012
|
+
return { summary: `${action} requested for ${processName}.`, data: { action, processName, ...result } };
|
|
1013
|
+
}
|
|
1014
|
+
if (operation === 'application.close') return executeNodeAgentOperation(options, 'process.control', { ...payload, toolArguments: { ...args, action: 'stop' } });
|
|
1015
|
+
if (operation === 'service.control') {
|
|
1016
|
+
const service = String(args.serviceName || '');
|
|
1017
|
+
const action = String(args.action || 'status');
|
|
1018
|
+
const executable = process.platform === 'win32' ? 'sc.exe' : 'systemctl';
|
|
1019
|
+
const actions = action === 'restart' ? ['stop', 'start'] : [action];
|
|
1020
|
+
const results = [];
|
|
1021
|
+
for (const step of actions) {
|
|
1022
|
+
const result = await runNodeAgentProcess(executable, [step, service]);
|
|
1023
|
+
results.push({ action: step, ...result });
|
|
1024
|
+
if (result.exitCode !== 0 || result.timedOut) break;
|
|
1025
|
+
}
|
|
1026
|
+
const ok = results.length === actions.length && results.every(result => result.exitCode === 0 && !result.timedOut);
|
|
1027
|
+
if (!ok) throw new Error(`Service ${action} failed for ${service}.`);
|
|
1028
|
+
return { summary: `Service ${action} completed for ${service}.`, data: { service, action, results } };
|
|
1029
|
+
}
|
|
1030
|
+
if (operation === 'command.run') {
|
|
1031
|
+
const command = String(args.command || '');
|
|
1032
|
+
const executable = process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
|
|
1033
|
+
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', command] : ['-lc', command];
|
|
1034
|
+
const result = await runNodeAgentProcess(executable, commandArgs, args.timeoutMs, args.workingDirectory ? resolveAgentPath(options, args.workingDirectory, permissionMode, false) : undefined);
|
|
1035
|
+
return { summary: result.timedOut ? 'Command timed out.' : `Command exited with code ${result.exitCode}.`, data: { ...result, output: redactAgentOutput(result.output) } };
|
|
1036
|
+
}
|
|
1037
|
+
if (operation === 'script.run') {
|
|
1038
|
+
const scriptPath = resolveAgentPath(options, args.path, permissionMode);
|
|
1039
|
+
const executable = process.platform === 'win32' ? 'powershell.exe' : '/bin/sh';
|
|
1040
|
+
const scriptArgs = process.platform === 'win32' ? ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...(Array.isArray(args.args) ? args.args.slice(0, 32).map(String) : [])] : [scriptPath, ...(Array.isArray(args.args) ? args.args.slice(0, 32).map(String) : [])];
|
|
1041
|
+
const result = await runNodeAgentProcess(executable, scriptArgs, args.timeoutMs);
|
|
1042
|
+
return { summary: result.timedOut ? 'Script timed out.' : `Script exited with code ${result.exitCode}.`, data: { ...result, output: redactAgentOutput(result.output) } };
|
|
1043
|
+
}
|
|
1044
|
+
if (operation === 'software.install') {
|
|
1045
|
+
const manager = String(args.manager || '').toLowerCase();
|
|
1046
|
+
const packageName = String(args.packageName || '');
|
|
1047
|
+
if (!/^[A-Za-z0-9._@:+/-]{1,200}$/.test(packageName)) throw new Error('packageName contains unsupported characters');
|
|
1048
|
+
const commands = { winget: ['winget.exe', ['install', '--id', packageName, '--silent', '--accept-source-agreements', '--accept-package-agreements']], brew: ['brew', ['install', packageName]], apt: ['apt-get', ['install', '-y', packageName]], npm: ['npm', ['install', '--global', args.version ? `${packageName}@${args.version}` : packageName]] };
|
|
1049
|
+
if (!commands[manager]) throw new Error('unsupported package manager');
|
|
1050
|
+
const [executable, commandArgs] = commands[manager];
|
|
1051
|
+
const result = await runNodeAgentProcess(executable, commandArgs, 30000);
|
|
1052
|
+
return { summary: `Package install exited with code ${result.exitCode}.`, data: { manager, packageName, ...result } };
|
|
1053
|
+
}
|
|
1054
|
+
if (operation === 'network.status') return { summary: 'Network status collected.', data: Object.fromEntries(Object.entries(os.networkInterfaces()).map(([name, values]) => [name, (values || []).map(value => ({ address: value.address, family: value.family, internal: value.internal }))])) };
|
|
1055
|
+
if (operation === 'system.power') {
|
|
1056
|
+
const action = String(args.action || '');
|
|
1057
|
+
const delaySec = String(Math.max(0, Math.min(3600, Number(args.delaySec) || 0)));
|
|
1058
|
+
let executable;
|
|
1059
|
+
let powerArgs;
|
|
1060
|
+
if (process.platform === 'win32') {
|
|
1061
|
+
({ executable, args: powerArgs } = {
|
|
1062
|
+
lock: { executable: 'rundll32.exe', args: ['user32.dll,LockWorkStation'] },
|
|
1063
|
+
sleep: { executable: 'rundll32.exe', args: ['powrprof.dll,SetSuspendState', '0,1,0'] },
|
|
1064
|
+
logoff: { executable: 'shutdown.exe', args: ['/l'] },
|
|
1065
|
+
restart: { executable: 'shutdown.exe', args: ['/r', '/t', delaySec] },
|
|
1066
|
+
shutdown: { executable: 'shutdown.exe', args: ['/s', '/t', delaySec] }
|
|
1067
|
+
}[action] || {});
|
|
1068
|
+
} else {
|
|
1069
|
+
({ executable, args: powerArgs } = {
|
|
1070
|
+
lock: { executable: 'loginctl', args: ['lock-session'] },
|
|
1071
|
+
sleep: { executable: 'systemctl', args: ['suspend'] },
|
|
1072
|
+
logoff: { executable: 'loginctl', args: ['terminate-user', os.userInfo().username] },
|
|
1073
|
+
restart: { executable: 'systemctl', args: ['reboot'] },
|
|
1074
|
+
shutdown: { executable: 'systemctl', args: ['poweroff'] }
|
|
1075
|
+
}[action] || {});
|
|
1076
|
+
}
|
|
1077
|
+
if (!executable) throw new Error(`Unsupported power action: ${action}.`);
|
|
1078
|
+
const result = await runNodeAgentProcess(executable, powerArgs, 15000);
|
|
1079
|
+
return { summary: `Power action ${action} requested.`, data: result };
|
|
1080
|
+
}
|
|
1081
|
+
if (operation === 'system.configure') {
|
|
1082
|
+
if (args.action === 'set-environment-variable') {
|
|
1083
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,119}$/.test(String(args.name || '')) || /^(PATH|PATHEXT|SYSTEMROOT|WINDIR|COMSPEC)$/i.test(String(args.name))) throw new Error('environment variable name is not allowed');
|
|
1084
|
+
const persistence = await persistNodeEnvironmentVariable(String(args.name), String(args.value ?? ''));
|
|
1085
|
+
return { summary: `Environment variable ${args.name} was persisted.`, data: { action: args.action, name: args.name, changed: true, ...persistence } };
|
|
1086
|
+
}
|
|
1087
|
+
const result = await runNodeAgentProcess(process.platform === 'win32' ? 'tzutil.exe' : 'timedatectl', process.platform === 'win32' ? ['/s', String(args.value || '')] : ['set-timezone', String(args.value || '')]);
|
|
1088
|
+
return { summary: `Timezone update exited with code ${result.exitCode}.`, data: result };
|
|
1089
|
+
}
|
|
1090
|
+
if (operation === 'logs.collect') {
|
|
1091
|
+
const maxLines = Math.max(1, Math.min(500, Number(args.maxLines) || 100));
|
|
1092
|
+
const result = await runNodeAgentProcess(process.platform === 'win32' ? 'powershell.exe' : 'journalctl', process.platform === 'win32' ? ['-NoProfile', '-NonInteractive', '-Command', `Get-WinEvent -LogName System -MaxEvents ${maxLines} | Format-List`] : ['-n', String(maxLines), '--no-pager', '-o', 'short']);
|
|
1093
|
+
return { summary: 'Recent logs collected.', data: { source: args.source || 'system', ...result, output: result.output.replace(/(token|password|secret|api[-_]?key|authorization)\s*[:=]\s*[^\s]+/gi, '$1=[redacted]') } };
|
|
1094
|
+
}
|
|
1095
|
+
throw new Error(`Unsupported command: ${operation}`);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const NODE_AGENT_OPERATIONS = new Set(['process.control', 'service.control', 'application.launch', 'application.close', 'file.read', 'file.write', 'file.delete', 'file.list', 'command.run', 'script.run', 'software.install', 'network.status', 'system.power', 'system.configure', 'logs.collect']);
|
|
1099
|
+
|
|
871
1100
|
async function handleRemoteCommand(socket, options, message, nextFrameSeq, activeStreams) {
|
|
872
1101
|
const command = String(message.command || '');
|
|
873
1102
|
if (command === 'ping') {
|
|
@@ -882,6 +1111,33 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
882
1111
|
return;
|
|
883
1112
|
}
|
|
884
1113
|
|
|
1114
|
+
if (NODE_AGENT_OPERATIONS.has(command)) {
|
|
1115
|
+
if (!options.taskEnabled) {
|
|
1116
|
+
writeJsonLine(socket, { type: 'command.result', commandId: message.commandId, error: 'remote task capability is disabled' });
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const result = await executeNodeAgentOperation(options, command, message.payload || {});
|
|
1121
|
+
writeJsonLine(socket, {
|
|
1122
|
+
type: 'command.result',
|
|
1123
|
+
commandId: message.commandId,
|
|
1124
|
+
result: {
|
|
1125
|
+
kind: command,
|
|
1126
|
+
mode: String(message.payload?.permissionMode || 'ask'),
|
|
1127
|
+
taskId: String(message.payload?.taskId || '').slice(0, 128),
|
|
1128
|
+
status: 'completed',
|
|
1129
|
+
summary: result.summary,
|
|
1130
|
+
data: result.data,
|
|
1131
|
+
sideEffects: ['file.read', 'file.list', 'network.status', 'logs.collect'].includes(command) ? 'none' : 'audited',
|
|
1132
|
+
completedAt: new Date().toISOString()
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
writeJsonLine(socket, { type: 'command.result', commandId: message.commandId, error: error?.message || String(error) });
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
885
1141
|
if (command === 'agent.task') {
|
|
886
1142
|
if (!options.taskEnabled) {
|
|
887
1143
|
writeJsonLine(socket, {
|
|
@@ -1178,10 +1434,14 @@ function connectOnce(options, deviceId) {
|
|
|
1178
1434
|
fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
|
|
1179
1435
|
computerAgent: options.taskEnabled,
|
|
1180
1436
|
taskDispatch: options.taskEnabled,
|
|
1437
|
+
agentApproval: options.taskEnabled,
|
|
1438
|
+
agentAudit: options.taskEnabled,
|
|
1439
|
+
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
1440
|
+
elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
|
|
1181
1441
|
aiAssist: options.taskEnabled && options.aiEnabled && (options.fakeAi || !!options.openAiApiKey),
|
|
1182
1442
|
aiModel: options.aiModel,
|
|
1183
1443
|
aiProvider: options.fakeAi ? 'fake' : (options.openAiApiKey ? 'openai' : ''),
|
|
1184
|
-
externalEffects:
|
|
1444
|
+
externalEffects: options.taskEnabled
|
|
1185
1445
|
}
|
|
1186
1446
|
});
|
|
1187
1447
|
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|