livedesk 0.1.447 → 0.1.449
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/bin/livedesk.js +150 -17
- package/bootstrap/legacy-client-update.js +1 -7
- package/client/README.md +3 -3
- package/client/bin/livedesk-client-node.js +70 -190
- package/client/bin/livedesk-client.js +71 -31
- package/client/package.json +5 -6
- package/hub/package.json +1 -1
- package/hub/src/agents/agent-manager.js +123 -175
- package/hub/src/agents/agent-settings.js +9 -100
- package/hub/src/agents/agent-tool-registry.js +1 -1
- package/hub/src/agents/codex-agent-runtime.js +14 -16
- package/hub/src/remote-hub.js +92 -124
- package/hub/src/server.js +58 -46
- package/package.json +8 -9
- package/web/dist/assets/icons-CCS0Avce.js +1 -0
- package/web/dist/assets/index-CDA-kp_V.js +25 -0
- package/web/dist/assets/{react-C7a9r614.js → react-Cg5haagh.js} +1 -1
- package/web/dist/index.html +3 -3
- package/hub/src/agents/opencode-go-provider.js +0 -260
- package/hub/src/agents/secret-store.js +0 -165
- package/web/dist/assets/icons-BIyRF-ou.js +0 -1
- package/web/dist/assets/index-5opXQY8X.js +0 -25
package/bin/livedesk.js
CHANGED
|
@@ -1422,30 +1422,163 @@ async function collectWindowsPortRecords(ports) {
|
|
|
1422
1422
|
return records;
|
|
1423
1423
|
}
|
|
1424
1424
|
|
|
1425
|
-
async function
|
|
1426
|
-
const
|
|
1427
|
-
const
|
|
1428
|
-
for (const record of records) {
|
|
1425
|
+
async function terminateExactWindowsPortOwners(records) {
|
|
1426
|
+
const roots = [];
|
|
1427
|
+
const seenRoots = new Set();
|
|
1428
|
+
for (const record of Array.isArray(records) ? records : []) {
|
|
1429
1429
|
const pid = Number(record?.Pid || 0);
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
||
|
|
1434
|
-
||
|
|
1430
|
+
const startOrder = String(record?.CreationUtcTicks || '').trim();
|
|
1431
|
+
if (record?.KnownLiveDesk !== true
|
|
1432
|
+
|| !Number.isInteger(pid)
|
|
1433
|
+
|| pid <= 1
|
|
1434
|
+
|| !/^\d+$/.test(startOrder)
|
|
1435
|
+
|| seenRoots.has(`${pid}:${startOrder}`)) {
|
|
1435
1436
|
continue;
|
|
1436
1437
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1438
|
+
seenRoots.add(`${pid}:${startOrder}`);
|
|
1439
|
+
roots.push({ pid, startOrder });
|
|
1440
|
+
}
|
|
1441
|
+
if (roots.length === 0) return;
|
|
1442
|
+
|
|
1443
|
+
const encodedRoots = Buffer.from(JSON.stringify(roots), 'utf8').toString('base64');
|
|
1444
|
+
const script = [
|
|
1445
|
+
'$ErrorActionPreference = "Stop";',
|
|
1446
|
+
`$rootsJson = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("${encodedRoots}"));`,
|
|
1447
|
+
'$requestedRoots = @(ConvertFrom-Json -InputObject $rootsJson);',
|
|
1448
|
+
'$validatedRoots = [System.Collections.Generic.List[object]]::new();',
|
|
1449
|
+
'foreach ($item in $requestedRoots) {',
|
|
1450
|
+
' $targetPid = [int]$item.pid;',
|
|
1451
|
+
' $expectedStartTicks = [string]$item.startOrder;',
|
|
1452
|
+
' $proc = Get-CimInstance Win32_Process -Filter "ProcessId = $targetPid" -Property ProcessId,ParentProcessId,Name,CommandLine,CreationDate -ErrorAction SilentlyContinue | Select-Object -First 1;',
|
|
1453
|
+
' if (-not $proc) { continue };',
|
|
1454
|
+
' $actualStartTicks = if ($proc.CreationDate) { [string]$proc.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1455
|
+
' $processName = [string]$proc.Name;',
|
|
1456
|
+
' $commandLine = [string]$proc.CommandLine;',
|
|
1457
|
+
' $isNodeProcess = $processName -match "(?i)^node(?:\\.exe)?$";',
|
|
1458
|
+
' $hasKnownLiveDeskHubPath = $commandLine -match "(?i)(?:packages[\\\\/](?:livedesk[\\\\/])?hub|node_modules[\\\\/]@?livedesk[\\\\/]hub)[\\\\/]src[\\\\/]server\\.js";',
|
|
1459
|
+
' $hasUnifiedLauncherPath = $commandLine -match "(?i)(?:node_modules[\\\\/]livedesk|packages[\\\\/]livedesk)[\\\\/]bin[\\\\/]livedesk\\.js";',
|
|
1460
|
+
' $hasLegacyClientPath = $commandLine -match "(?i)(?:node_modules[\\\\/]@livedesk[\\\\/]client|packages[\\\\/]client|packages[\\\\/]livedesk[\\\\/]client)[\\\\/]bin[\\\\/]livedesk-client\\.js";',
|
|
1461
|
+
' if ($actualStartTicks -ne $expectedStartTicks -or -not $isNodeProcess -or -not ($hasKnownLiveDeskHubPath -or $hasUnifiedLauncherPath -or $hasLegacyClientPath)) { continue };',
|
|
1462
|
+
' $validatedRoots.Add([pscustomobject]@{ ProcessId = [int]$proc.ProcessId; ParentProcessId = [int]$proc.ParentProcessId; CreationUtcTicks = $actualStartTicks; Depth = 0 });',
|
|
1463
|
+
'}',
|
|
1464
|
+
'if ($validatedRoots.Count -eq 0) { exit 0 };',
|
|
1465
|
+
'$snapshot = @(Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate | ForEach-Object {',
|
|
1466
|
+
' $ticks = if ($_.CreationDate) { [string]$_.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1467
|
+
' if ($ticks) { [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; CreationUtcTicks = $ticks } }',
|
|
1468
|
+
'});',
|
|
1469
|
+
'$targets = @{};',
|
|
1470
|
+
'foreach ($root in $validatedRoots) {',
|
|
1471
|
+
' $currentRoot = @($snapshot | Where-Object { [int]$_.ProcessId -eq [int]$root.ProcessId }) | Select-Object -First 1;',
|
|
1472
|
+
' if (-not $currentRoot -or [string]$currentRoot.CreationUtcTicks -ne [string]$root.CreationUtcTicks) { continue };',
|
|
1473
|
+
' $queue = [System.Collections.Generic.Queue[object]]::new();',
|
|
1474
|
+
' $queue.Enqueue([pscustomobject]@{ Record = $currentRoot; Depth = 0 });',
|
|
1475
|
+
' $seen = [System.Collections.Generic.HashSet[int]]::new();',
|
|
1476
|
+
' while ($queue.Count -gt 0) {',
|
|
1477
|
+
' $current = $queue.Dequeue();',
|
|
1478
|
+
' $currentRecord = $current.Record;',
|
|
1479
|
+
' $currentPid = [int]$currentRecord.ProcessId;',
|
|
1480
|
+
' if (-not $seen.Add($currentPid)) { continue };',
|
|
1481
|
+
' $key = "$currentPid`:$([string]$currentRecord.CreationUtcTicks)";',
|
|
1482
|
+
' $targets[$key] = [pscustomobject]@{ ProcessId = $currentPid; CreationUtcTicks = [string]$currentRecord.CreationUtcTicks; Depth = [int]$current.Depth };',
|
|
1483
|
+
' foreach ($child in @($snapshot | Where-Object { [int]$_.ParentProcessId -eq $currentPid })) {',
|
|
1484
|
+
' try { if ([System.Numerics.BigInteger]::Parse([string]$child.CreationUtcTicks) -lt [System.Numerics.BigInteger]::Parse([string]$currentRecord.CreationUtcTicks)) { continue } } catch { continue };',
|
|
1485
|
+
' $queue.Enqueue([pscustomobject]@{ Record = $child; Depth = [int]$current.Depth + 1 });',
|
|
1486
|
+
' }',
|
|
1487
|
+
' }',
|
|
1488
|
+
'}',
|
|
1489
|
+
'$failures = [System.Collections.Generic.List[string]]::new();',
|
|
1490
|
+
'$exitTicks = @{};',
|
|
1491
|
+
'$orderedTargets = @($targets.Values | Sort-Object @{ Expression = { -[int]$_.Depth } });',
|
|
1492
|
+
'foreach ($targetRecord in $orderedTargets) {',
|
|
1493
|
+
' $targetPid = [int]$targetRecord.ProcessId;',
|
|
1494
|
+
' $expectedStartTicks = [string]$targetRecord.CreationUtcTicks;',
|
|
1495
|
+
' $targetKey = "$targetPid`:$expectedStartTicks";',
|
|
1496
|
+
' try {',
|
|
1497
|
+
' $current = Get-CimInstance Win32_Process -Filter "ProcessId = $targetPid" -Property ProcessId,CreationDate -ErrorAction SilentlyContinue | Select-Object -First 1;',
|
|
1498
|
+
' if (-not $current) { continue };',
|
|
1499
|
+
' $currentStartTicks = if ($current.CreationDate) { [string]$current.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1500
|
+
' if ($currentStartTicks -ne $expectedStartTicks) { continue };',
|
|
1501
|
+
' Stop-Process -Id $targetPid -Force -ErrorAction Stop;',
|
|
1502
|
+
' $exitTicks[$targetKey] = [string][DateTime]::UtcNow.Ticks;',
|
|
1503
|
+
' } catch {',
|
|
1504
|
+
' $failures.Add("pid=$targetPid $($_.Exception.Message)");',
|
|
1505
|
+
' }',
|
|
1506
|
+
'}',
|
|
1507
|
+
'Start-Sleep -Milliseconds 75;',
|
|
1508
|
+
'$lateSnapshot = @(Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate | ForEach-Object {',
|
|
1509
|
+
' $ticks = if ($_.CreationDate) { [string]$_.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1510
|
+
' if ($ticks) { [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; CreationUtcTicks = $ticks } }',
|
|
1511
|
+
'});',
|
|
1512
|
+
'$lateTargets = @{};',
|
|
1513
|
+
'$lateQueue = [System.Collections.Generic.Queue[object]]::new();',
|
|
1514
|
+
'foreach ($tracked in $targets.Values) {',
|
|
1515
|
+
' $trackedKey = "$([int]$tracked.ProcessId)`:$([string]$tracked.CreationUtcTicks)";',
|
|
1516
|
+
' if (-not $exitTicks.ContainsKey($trackedKey)) { $exitTicks[$trackedKey] = [string][DateTime]::UtcNow.Ticks };',
|
|
1517
|
+
' $lateQueue.Enqueue([pscustomobject]@{ Record = $tracked; Depth = [int]$tracked.Depth; ExitTicks = [string]$exitTicks[$trackedKey] });',
|
|
1518
|
+
'}',
|
|
1519
|
+
'$lateSeen = [System.Collections.Generic.HashSet[string]]::new();',
|
|
1520
|
+
'while ($lateQueue.Count -gt 0) {',
|
|
1521
|
+
' $parent = $lateQueue.Dequeue();',
|
|
1522
|
+
' $parentRecord = $parent.Record;',
|
|
1523
|
+
' $parentKey = "$([int]$parentRecord.ProcessId)`:$([string]$parentRecord.CreationUtcTicks)";',
|
|
1524
|
+
' if (-not $lateSeen.Add($parentKey)) { continue };',
|
|
1525
|
+
' foreach ($child in @($lateSnapshot | Where-Object { [int]$_.ParentProcessId -eq [int]$parentRecord.ProcessId })) {',
|
|
1526
|
+
' try {',
|
|
1527
|
+
' $childStart = [System.Numerics.BigInteger]::Parse([string]$child.CreationUtcTicks);',
|
|
1528
|
+
' if ($childStart -lt [System.Numerics.BigInteger]::Parse([string]$parentRecord.CreationUtcTicks)) { continue };',
|
|
1529
|
+
' if ($parent.ExitTicks -and $childStart -gt [System.Numerics.BigInteger]::Parse([string]$parent.ExitTicks)) { continue };',
|
|
1530
|
+
' } catch { continue };',
|
|
1531
|
+
' $childKey = "$([int]$child.ProcessId)`:$([string]$child.CreationUtcTicks)";',
|
|
1532
|
+
' $childDepth = [int]$parent.Depth + 1;',
|
|
1533
|
+
' $lateTargets[$childKey] = [pscustomobject]@{ ProcessId = [int]$child.ProcessId; CreationUtcTicks = [string]$child.CreationUtcTicks; Depth = $childDepth };',
|
|
1534
|
+
' $lateQueue.Enqueue([pscustomobject]@{ Record = $child; Depth = $childDepth; ExitTicks = "" });',
|
|
1535
|
+
' }',
|
|
1536
|
+
'}',
|
|
1537
|
+
'$orderedLateTargets = @($lateTargets.Values | Sort-Object @{ Expression = { -[int]$_.Depth } });',
|
|
1538
|
+
'foreach ($targetRecord in $orderedLateTargets) {',
|
|
1539
|
+
' $targetPid = [int]$targetRecord.ProcessId;',
|
|
1540
|
+
' $expectedStartTicks = [string]$targetRecord.CreationUtcTicks;',
|
|
1541
|
+
' try {',
|
|
1542
|
+
' $current = Get-CimInstance Win32_Process -Filter "ProcessId = $targetPid" -Property ProcessId,CreationDate -ErrorAction SilentlyContinue | Select-Object -First 1;',
|
|
1543
|
+
' if (-not $current) { continue };',
|
|
1544
|
+
' $currentStartTicks = if ($current.CreationDate) { [string]$current.CreationDate.ToUniversalTime().Ticks } else { "" };',
|
|
1545
|
+
' if ($currentStartTicks -ne $expectedStartTicks) { continue };',
|
|
1546
|
+
' Stop-Process -Id $targetPid -Force -ErrorAction Stop;',
|
|
1547
|
+
' } catch {',
|
|
1548
|
+
' $failures.Add("late pid=$targetPid $($_.Exception.Message)");',
|
|
1549
|
+
' }',
|
|
1550
|
+
'}',
|
|
1551
|
+
'if ($failures.Count -gt 0) { [Console]::Error.WriteLine(($failures -join "; ")); exit 1 };',
|
|
1552
|
+
'exit 0;'
|
|
1553
|
+
].join(' ');
|
|
1554
|
+
const result = await runQuiet(
|
|
1555
|
+
'powershell.exe',
|
|
1556
|
+
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script],
|
|
1557
|
+
{ timeout: 15000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 }
|
|
1558
|
+
);
|
|
1559
|
+
if (!result.ok) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
`Could not stop exact LiveDesk Windows port owner tree: `
|
|
1562
|
+
+ `${result.stderr || result.error?.message || 'PowerShell failed'}`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
async function stopWindowsProcessesOnPorts(ports) {
|
|
1568
|
+
const records = await collectWindowsPortRecords(ports);
|
|
1569
|
+
const killable = records.filter(record => record?.KnownLiveDesk === true);
|
|
1570
|
+
if (killable.length > 0) {
|
|
1571
|
+
await terminateExactWindowsPortOwners(killable);
|
|
1572
|
+
for (const record of killable) {
|
|
1573
|
+
record.Action = 'stopped';
|
|
1574
|
+
record.KillAttempted = true;
|
|
1444
1575
|
}
|
|
1445
1576
|
}
|
|
1446
1577
|
|
|
1447
1578
|
const probes = await Promise.all(ports.map(port => waitForPortBindable(port)));
|
|
1448
|
-
const finalRecords =
|
|
1579
|
+
const finalRecords = probes.every(probe => probe.ok)
|
|
1580
|
+
? []
|
|
1581
|
+
: await collectWindowsPortRecords(ports);
|
|
1449
1582
|
const portStates = ports.map((port, index) => {
|
|
1450
1583
|
const busy = finalRecords.some(record => Number(record?.Port) === Number(port));
|
|
1451
1584
|
return {
|
|
@@ -47,9 +47,6 @@ const PRESERVED_SINGLE_FLAGS = new Set([
|
|
|
47
47
|
'--no-audio',
|
|
48
48
|
'--tasks',
|
|
49
49
|
'--no-tasks',
|
|
50
|
-
'--ai',
|
|
51
|
-
'--no-ai',
|
|
52
|
-
'--fake-ai',
|
|
53
50
|
'--fake-thumbnail',
|
|
54
51
|
'--trace-frames'
|
|
55
52
|
]);
|
|
@@ -57,7 +54,6 @@ const PRESERVED_VALUE_FLAGS = new Set([
|
|
|
57
54
|
'--name',
|
|
58
55
|
'--heartbeat',
|
|
59
56
|
'--files-dir',
|
|
60
|
-
'--ai-model',
|
|
61
57
|
'--transport'
|
|
62
58
|
]);
|
|
63
59
|
const JOINED_VALUE_FLAGS = new Set(['--name', '--files-dir']);
|
|
@@ -71,9 +67,7 @@ const SETTING_GROUPS = new Map([
|
|
|
71
67
|
['--audio', 'audio'],
|
|
72
68
|
['--no-audio', 'audio'],
|
|
73
69
|
['--tasks', 'tasks'],
|
|
74
|
-
['--no-tasks', 'tasks']
|
|
75
|
-
['--ai', 'ai'],
|
|
76
|
-
['--no-ai', 'ai']
|
|
70
|
+
['--no-tasks', 'tasks']
|
|
77
71
|
]);
|
|
78
72
|
|
|
79
73
|
function cleanVersion(value) {
|
package/client/README.md
CHANGED
|
@@ -62,9 +62,9 @@ System Settings path when access is missing; after granting access, restart the
|
|
|
62
62
|
client. When file transfer is enabled by the Hub, received files are saved
|
|
63
63
|
to `Desktop/LiveDeskFiles` unless the Hub sets another destination folder.
|
|
64
64
|
|
|
65
|
-
By default, the launcher uses the packaged C# RemoteFast engine when supported.
|
|
66
|
-
It falls back to the Node engine
|
|
67
|
-
|
|
65
|
+
By default, the launcher uses the packaged C# RemoteFast engine when supported.
|
|
66
|
+
It falls back to the Node engine only when a compatible RemoteFast runtime is
|
|
67
|
+
unavailable. Windows, macOS, and Linux all try RemoteFast first so
|
|
68
68
|
the Hub can request the Mode 3 hardware video path when it is available.
|
|
69
69
|
RemoteFast is shipped as a platform-specific self-contained .NET executable,
|
|
70
70
|
so client computers do not need a separate .NET installation.
|
|
@@ -19,15 +19,13 @@ const AGENT_VERSION = (() => {
|
|
|
19
19
|
const PRODUCT_VERSION = String(process.env.LIVEDESK_NPM_LAUNCHER_VERSION || AGENT_VERSION).trim() || AGENT_VERSION;
|
|
20
20
|
const DEFAULT_MANAGER = '127.0.0.1:5197';
|
|
21
21
|
const DEFAULT_HEARTBEAT_MS = 5000;
|
|
22
|
-
const DEFAULT_RECONNECT_MS = 5000;
|
|
23
|
-
const EXIT_INVALID_PAIR_TOKEN = 23;
|
|
24
|
-
const EXIT_CLIENT_UPDATE = 42;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const MAX_AI_OUTPUT_CHARS = 6000;
|
|
30
|
-
const MAX_FILE_TRANSFER_FILES = 24;
|
|
22
|
+
const DEFAULT_RECONNECT_MS = 5000;
|
|
23
|
+
const EXIT_INVALID_PAIR_TOKEN = 23;
|
|
24
|
+
const EXIT_CLIENT_UPDATE = 42;
|
|
25
|
+
const DEFAULT_LIVE_FPS = 30;
|
|
26
|
+
const MAX_LIVE_FPS = 30;
|
|
27
|
+
const MAX_FRAME_BASE64_CHARS = 3 * 1024 * 1024;
|
|
28
|
+
const MAX_FILE_TRANSFER_FILES = 24;
|
|
31
29
|
const MAX_FILE_TRANSFER_BYTES = 24 * 1024 * 1024;
|
|
32
30
|
const MAX_AGENT_OUTPUT_CHARS = 32000;
|
|
33
31
|
const launchedProcessRegistry = new Map();
|
|
@@ -281,14 +279,10 @@ Options:
|
|
|
281
279
|
--no-thumbnail Disable thumbnail capture capability.
|
|
282
280
|
--live Enable focused live screen streaming. Default on.
|
|
283
281
|
--no-live Disable focused live screen streaming.
|
|
284
|
-
--tasks Enable safe remote task inbox. Default on.
|
|
285
|
-
--no-tasks Disable remote task dispatch capability.
|
|
286
|
-
--files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
|
|
287
|
-
--
|
|
288
|
-
--no-ai Disable OpenAI-backed remote AI assist tasks.
|
|
289
|
-
--ai-model <model> OpenAI model for AI assist. Default: ${DEFAULT_AI_MODEL}
|
|
290
|
-
--fake-ai Use deterministic AI assist responses for smoke tests.
|
|
291
|
-
--fake-thumbnail Use generated thumbnail frames for smoke tests.
|
|
282
|
+
--tasks Enable safe remote task inbox. Default on.
|
|
283
|
+
--no-tasks Disable remote task dispatch capability.
|
|
284
|
+
--files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
|
|
285
|
+
--fake-thumbnail Use generated thumbnail frames for smoke tests.
|
|
292
286
|
--exit-on-disconnect Exit after Hub disconnect. Useful for tests.
|
|
293
287
|
--exit-on-invalid-pair Exit when the Hub rejects the pair token.
|
|
294
288
|
--once Connect, send one status packet, and exit after welcome.
|
|
@@ -328,14 +322,10 @@ function parseArgs(argv) {
|
|
|
328
322
|
heartbeatMs: DEFAULT_HEARTBEAT_MS,
|
|
329
323
|
deviceId: '',
|
|
330
324
|
thumbnailEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_THUMBNAIL ?? process.env.MINDEXEC_REMOTE_THUMBNAIL),
|
|
331
|
-
liveEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_LIVE ?? process.env.MINDEXEC_REMOTE_LIVE),
|
|
332
|
-
taskEnabled: !isFalsy(process.env.LIVEDESK_CLIENT_TASKS ?? process.env.MINDEXEC_REMOTE_TASKS),
|
|
333
|
-
filesDir: process.env.LIVEDESK_CLIENT_FILES_DIR || process.env.MINDEXEC_REMOTE_FILES_DIR || '',
|
|
334
|
-
|
|
335
|
-
aiModel: process.env.LIVEDESK_CLIENT_AI_MODEL || process.env.MINDEXEC_REMOTE_AI_MODEL || process.env.OPENAI_MODEL || DEFAULT_AI_MODEL,
|
|
336
|
-
openAiApiKey: process.env.OPENAI_API_KEY || '',
|
|
337
|
-
fakeAi: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_AI || process.env.MINDEXEC_REMOTE_FAKE_AI),
|
|
338
|
-
fakeThumbnail: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_THUMBNAIL || process.env.MINDEXEC_REMOTE_FAKE_THUMBNAIL),
|
|
325
|
+
liveEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_LIVE ?? process.env.MINDEXEC_REMOTE_LIVE),
|
|
326
|
+
taskEnabled: !isFalsy(process.env.LIVEDESK_CLIENT_TASKS ?? process.env.MINDEXEC_REMOTE_TASKS),
|
|
327
|
+
filesDir: process.env.LIVEDESK_CLIENT_FILES_DIR || process.env.MINDEXEC_REMOTE_FILES_DIR || '',
|
|
328
|
+
fakeThumbnail: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_THUMBNAIL || process.env.MINDEXEC_REMOTE_FAKE_THUMBNAIL),
|
|
339
329
|
exitOnDisconnect: false,
|
|
340
330
|
exitOnInvalidPair: false,
|
|
341
331
|
once: false,
|
|
@@ -387,26 +377,11 @@ function parseArgs(argv) {
|
|
|
387
377
|
case '--no-tasks':
|
|
388
378
|
result.taskEnabled = false;
|
|
389
379
|
break;
|
|
390
|
-
case '--files-dir':
|
|
391
|
-
result.filesDir = args[++index] || result.filesDir;
|
|
392
|
-
break;
|
|
393
|
-
case '--
|
|
394
|
-
result.
|
|
395
|
-
result.taskEnabled = true;
|
|
396
|
-
break;
|
|
397
|
-
case '--no-ai':
|
|
398
|
-
result.aiEnabled = false;
|
|
399
|
-
break;
|
|
400
|
-
case '--ai-model':
|
|
401
|
-
result.aiModel = args[++index] || result.aiModel;
|
|
402
|
-
break;
|
|
403
|
-
case '--fake-ai':
|
|
404
|
-
result.fakeAi = true;
|
|
405
|
-
result.aiEnabled = true;
|
|
406
|
-
result.taskEnabled = true;
|
|
407
|
-
break;
|
|
408
|
-
case '--fake-thumbnail':
|
|
409
|
-
result.fakeThumbnail = true;
|
|
380
|
+
case '--files-dir':
|
|
381
|
+
result.filesDir = args[++index] || result.filesDir;
|
|
382
|
+
break;
|
|
383
|
+
case '--fake-thumbnail':
|
|
384
|
+
result.fakeThumbnail = true;
|
|
410
385
|
result.thumbnailEnabled = true;
|
|
411
386
|
break;
|
|
412
387
|
case '--exit-on-disconnect':
|
|
@@ -586,7 +561,7 @@ function getStatus(options = {}) {
|
|
|
586
561
|
usedMemRatio,
|
|
587
562
|
platform: os.platform(),
|
|
588
563
|
release: os.release(),
|
|
589
|
-
role: options.
|
|
564
|
+
role: options.taskEnabled ? 'Command-ready Client' : 'Local machine',
|
|
590
565
|
cpu: {
|
|
591
566
|
cores: cpuCores,
|
|
592
567
|
model: cpus[0]?.model || '',
|
|
@@ -609,9 +584,9 @@ function getStatus(options = {}) {
|
|
|
609
584
|
gpu: acceleratorStatus.gpu,
|
|
610
585
|
npu: acceleratorStatus.npu,
|
|
611
586
|
network: getNetworkStatus(),
|
|
612
|
-
workload: {
|
|
613
|
-
role: options.
|
|
614
|
-
status: 'idle',
|
|
587
|
+
workload: {
|
|
588
|
+
role: options.taskEnabled ? 'Command-ready Client' : 'Local machine',
|
|
589
|
+
status: 'idle',
|
|
615
590
|
title: 'idle'
|
|
616
591
|
},
|
|
617
592
|
timestamp: new Date().toISOString()
|
|
@@ -941,95 +916,17 @@ async function captureThumbnailFrame(options, payload, frameSeq) {
|
|
|
941
916
|
return await captureScreenFrame(options, payload, frameSeq, 'thumbnail');
|
|
942
917
|
}
|
|
943
918
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
return
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
'If the task requires external side effects, explain what would be needed and stop.',
|
|
956
|
-
'',
|
|
957
|
-
`Device: ${options.name} (${os.hostname()}, ${os.platform()} ${os.release()}, ${os.arch()})`,
|
|
958
|
-
`Task title: ${title}`,
|
|
959
|
-
'',
|
|
960
|
-
'Hub instruction:',
|
|
961
|
-
instruction
|
|
962
|
-
].join('\n');
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
function extractResponseText(response) {
|
|
966
|
-
if (typeof response?.output_text === 'string' && response.output_text.trim()) {
|
|
967
|
-
return response.output_text.trim();
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const parts = [];
|
|
971
|
-
if (Array.isArray(response?.output)) {
|
|
972
|
-
for (const item of response.output) {
|
|
973
|
-
if (!Array.isArray(item?.content)) {
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
for (const content of item.content) {
|
|
978
|
-
if (typeof content?.text === 'string') {
|
|
979
|
-
parts.push(content.text);
|
|
980
|
-
}
|
|
981
|
-
if (typeof content?.output_text === 'string') {
|
|
982
|
-
parts.push(content.output_text);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
return parts.join('\n').trim();
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
async function runAiAssistTask(options, payload, instruction) {
|
|
992
|
-
if (!options.aiEnabled) {
|
|
993
|
-
throw new Error('AI assist capability is disabled. Start the agent with --ai to enable it.');
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
if (options.fakeAi) {
|
|
997
|
-
return {
|
|
998
|
-
text: [
|
|
999
|
-
`Fake AI assist completed on ${options.name}.`,
|
|
1000
|
-
'',
|
|
1001
|
-
`Instruction: ${instruction.slice(0, 500)}`,
|
|
1002
|
-
'',
|
|
1003
|
-
'No shell, file, input, browser, or persistent side effects were performed.'
|
|
1004
|
-
].join('\n'),
|
|
1005
|
-
model: 'fake-ai',
|
|
1006
|
-
responseId: `fake-ai-${String(payload?.taskId || crypto.randomUUID()).slice(0, 96)}`
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
if (!options.openAiApiKey) {
|
|
1011
|
-
throw new Error('OPENAI_API_KEY is required for --ai remote tasks.');
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
const openaiModule = await import('openai');
|
|
1015
|
-
const OpenAI = openaiModule.default || openaiModule.OpenAI;
|
|
1016
|
-
const client = new OpenAI({ apiKey: options.openAiApiKey });
|
|
1017
|
-
const model = String(payload?.model || options.aiModel || DEFAULT_AI_MODEL).trim() || DEFAULT_AI_MODEL;
|
|
1018
|
-
const response = await client.responses.create({
|
|
1019
|
-
model,
|
|
1020
|
-
input: buildAiPrompt(options, payload, instruction)
|
|
1021
|
-
});
|
|
1022
|
-
const text = extractResponseText(response);
|
|
1023
|
-
if (!text) {
|
|
1024
|
-
throw new Error('AI assist returned no text output.');
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
return {
|
|
1028
|
-
text: text.slice(0, MAX_AI_OUTPUT_CHARS),
|
|
1029
|
-
model,
|
|
1030
|
-
responseId: response?.id || ''
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
919
|
+
const RETIRED_CLIENT_AGENT_TASK_ERROR = 'agent-ai-assist-retired';
|
|
920
|
+
|
|
921
|
+
function getRetiredClientAgentTaskError(payload = {}) {
|
|
922
|
+
const source = payload && typeof payload === 'object' ? payload : {};
|
|
923
|
+
const approvalLevel = String(source.approvalLevel || '').trim().toLowerCase();
|
|
924
|
+
const hasModelSelection = ['model', 'aiModel', 'aiProvider', 'provider']
|
|
925
|
+
.some(key => Object.prototype.hasOwnProperty.call(source, key));
|
|
926
|
+
return approvalLevel === 'ai-assist' || hasModelSelection
|
|
927
|
+
? RETIRED_CLIENT_AGENT_TASK_ERROR
|
|
928
|
+
: '';
|
|
929
|
+
}
|
|
1033
930
|
|
|
1034
931
|
function clampInteger(value, min, max, fallback) {
|
|
1035
932
|
const number = Number(value);
|
|
@@ -1571,15 +1468,16 @@ function buildClientUpdateBootstrapScript() {
|
|
|
1571
1468
|
'const isGroupAlive = pid => { try { process.kill(-pid, 0); return true; } catch (error) { return error?.code === "EPERM"; } };',
|
|
1572
1469
|
'const runWindowsPowerShell = script => new Promise((resolve, reject) => { execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 15000, killSignal: "SIGKILL", maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => { if (error) { reject(new Error(String(stderr || error.message || "PowerShell failed").trim())); return; } resolve(String(stdout || "").trim()); }); });',
|
|
1573
1470
|
'const queryWindowsProcessTable = async () => { const stdout = await runWindowsPowerShell(`$ErrorActionPreference = "Stop"; $records = @(Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate | ForEach-Object { $ticks = if ($_.CreationDate) { [string]$_.CreationDate.ToUniversalTime().Ticks } else { "" }; [pscustomobject]@{ ProcessId = [int]$_.ProcessId; ParentProcessId = [int]$_.ParentProcessId; CreationUtcTicks = $ticks } }); [pscustomobject]@{ Records = $records } | ConvertTo-Json -Compress -Depth 4`); if (!stdout) throw new Error("Windows process snapshot returned no data"); const parsed = JSON.parse(stdout); const values = parsed?.Records == null ? [] : (Array.isArray(parsed.Records) ? parsed.Records : [parsed.Records]); return values.map(item => ({ pid: Number(item?.ProcessId || 0), parentPid: Number(item?.ParentProcessId || 0), startOrder: String(item?.CreationUtcTicks || "") })).filter(item => Number.isInteger(item.pid) && item.pid > 1 && /^\\d+$/.test(item.startOrder)); };',
|
|
1471
|
+
'const queryTrackedWindowsProcessTable = child => { if (child.livedeskWindowsSnapshotPromise) return child.livedeskWindowsSnapshotPromise; const pending = queryWindowsProcessTable(); child.livedeskWindowsSnapshotPromise = pending; const clear = () => { if (child.livedeskWindowsSnapshotPromise === pending) child.livedeskWindowsSnapshotPromise = null; }; pending.then(clear, clear); return pending; };',
|
|
1574
1472
|
'const sameWindowsIdentity = (left, right) => Number(left?.pid || 0) === Number(right?.pid || 0) && String(left?.startOrder || "") === String(right?.startOrder || "");',
|
|
1575
|
-
'const captureWindowsIdentity = async
|
|
1473
|
+
'const captureWindowsIdentity = async child => { const processId = Number(child?.pid || 0); const deadline = Date.now() + 2500; let lastError = null; do { try { const snapshot = await queryTrackedWindowsProcessTable(child); const identity = snapshot.find(item => item.pid === processId) || null; if (identity) return identity; } catch (error) { lastError = error; } if (Date.now() < deadline) await sleep(50); } while (Date.now() < deadline); throw new Error(`Could not capture immutable Windows CreationDate for pid=${processId}: ${lastError?.message || "process not visible"}`); };',
|
|
1576
1474
|
'const windowsTicksAtMilliseconds = value => BigInt(Math.trunc(Number(value) || Date.now())) * 10000n + 621355968000000000n;',
|
|
1577
1475
|
'const setWindowsRootLifetimeEnd = (child, ticks) => { const candidate = BigInt(ticks); const previous = child.livedeskWindowsRootLifetimeEndTicks; if (!previous || candidate < BigInt(previous)) child.livedeskWindowsRootLifetimeEndTicks = String(candidate); return BigInt(child.livedeskWindowsRootLifetimeEndTicks); };',
|
|
1578
1476
|
'const mergeRootAbsentWindowsTree = (child, rootPid, snapshot) => { const tracked = child.livedeskWindowsTrackedRecords || (child.livedeskWindowsTrackedRecords = new Map()); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const exactCurrentByPid = new Map(); for (const record of tracked.values()) { const current = currentByPid.get(record.pid); if (sameWindowsIdentity(current, record)) exactCurrentByPid.set(record.pid, record); } const lowerBound = windowsTicksAtMilliseconds(Number(child.livedeskWindowsSpawnedAtMs || Date.now()) - 2000); const upperBound = child.livedeskWindowsRootLifetimeEndTicks ? BigInt(child.livedeskWindowsRootLifetimeEndTicks) : null; let changed = true; while (changed) { changed = false; for (const record of snapshot) { const key = `${record.pid}:${record.startOrder}`; if (record.pid === rootPid || tracked.has(key)) continue; let recordStart; try { recordStart = BigInt(record.startOrder); if (recordStart < lowerBound || (upperBound && recordStart > upperBound)) continue; } catch { continue; } const parent = record.parentPid === rootPid ? { depth: 0 } : exactCurrentByPid.get(record.parentPid); if (!parent) continue; const descendant = { ...record, depth: Number(parent.depth || 0) + 1 }; tracked.set(key, descendant); exactCurrentByPid.set(descendant.pid, descendant); changed = true; } } return tracked; };',
|
|
1579
1477
|
'const mergeExactWindowsTree = (child, root, snapshot) => { const tracked = child.livedeskWindowsTrackedRecords || (child.livedeskWindowsTrackedRecords = new Map()); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const currentRoot = currentByPid.get(root.pid) || null; const rootIsExact = sameWindowsIdentity(currentRoot, root); const rootWasReused = Boolean(currentRoot && !rootIsExact); if (rootWasReused) setWindowsRootLifetimeEnd(child, BigInt(currentRoot.startOrder) - 1n); else if (!currentRoot && child.livedeskWindowsRootExitedAtMs) setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); const rootLifetimeClosed = Boolean(child.livedeskWindowsRootLifetimeEndTicks); if (rootIsExact) tracked.set(`${root.pid}:${root.startOrder}`, { ...root, depth: 0 }); const exactCurrentByPid = new Map(); if (rootIsExact) exactCurrentByPid.set(root.pid, { ...root, depth: 0 }); for (const record of tracked.values()) { const current = currentByPid.get(record.pid); if (sameWindowsIdentity(current, record)) exactCurrentByPid.set(record.pid, record); } let changed = true; while (changed) { changed = false; for (const record of snapshot) { const key = `${record.pid}:${record.startOrder}`; if (record.pid === root.pid || tracked.has(key)) continue; let recordStart; try { recordStart = BigInt(record.startOrder); if (recordStart < BigInt(root.startOrder)) continue; if (rootLifetimeClosed && recordStart > BigInt(child.livedeskWindowsRootLifetimeEndTicks)) continue; } catch { continue; } let parent = exactCurrentByPid.get(record.parentPid) || null; if (!parent && !currentRoot && rootLifetimeClosed && record.parentPid === root.pid) parent = { ...root, depth: 0 }; if (!parent) continue; const descendant = { ...record, depth: Number(parent.depth || 0) + 1 }; tracked.set(key, descendant); exactCurrentByPid.set(descendant.pid, descendant); changed = true; } } return tracked; };',
|
|
1580
|
-
'const refreshTrackedWindowsTree = async (child, root = child.livedeskWindowsIdentity || null) => { const snapshot = await
|
|
1478
|
+
'const refreshTrackedWindowsTree = async (child, root = child.livedeskWindowsIdentity || null) => { const snapshot = await queryTrackedWindowsProcessTable(child); if (root) mergeExactWindowsTree(child, root, snapshot); else mergeRootAbsentWindowsTree(child, Number(child.pid || 0), snapshot); return snapshot; };',
|
|
1581
1479
|
'const stopExactWindowsRecords = async records => { if (!Array.isArray(records) || records.length === 0) return; const encoded = Buffer.from(JSON.stringify(records.map(record => ({ pid: record.pid, startOrder: record.startOrder }))), "utf8").toString("base64"); const script = `$ErrorActionPreference = "Stop"; $targetsJson = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("${encoded}")); $targets = ConvertFrom-Json -InputObject $targetsJson; $failures = [System.Collections.Generic.List[string]]::new(); foreach ($item in $targets) { $targetPid = [int]$item.pid; $expectedStartTicks = [string]$item.startOrder; try { $target = Get-CimInstance Win32_Process -Filter "ProcessId = $targetPid" -Property ProcessId,CreationDate -ErrorAction SilentlyContinue | Select-Object -First 1; if (-not $target) { continue }; $targetStartTicks = if ($target.CreationDate) { [string]$target.CreationDate.ToUniversalTime().Ticks } else { "" }; if ($targetStartTicks -ne $expectedStartTicks) { continue }; Stop-Process -Id $targetPid -Force -ErrorAction Stop } catch { $failures.Add("pid=$targetPid $($_.Exception.Message)") } }; if ($failures.Count -gt 0) { [Console]::Error.WriteLine(($failures -join "; ")); exit 1 }`; await runWindowsPowerShell(script); };',
|
|
1582
|
-
'const trackWindowsChild = child => { if (process.platform !== "win32" || Number(child?.pid || 0) <= 1 || child.livedeskWindowsIdentityPromise) return; child.livedeskWindowsTrackedRecords = new Map(); child.livedeskWindowsSpawnedAtMs = Date.now(); child.livedeskWindowsRootExitedAtMs = null; child.livedeskWindowsTrackingStopped = false; child.once("exit", () => { if (!child.livedeskWindowsRootExitedAtMs) child.livedeskWindowsRootExitedAtMs = Date.now(); setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); }); const refresh = () => { void refreshTrackedWindowsTree(child).then(() => { child.livedeskWindowsTrackingError = null; }).catch(error => { child.livedeskWindowsTrackingError = error; }); }; refresh(); child.livedeskWindowsTrackingTimer = setInterval(refresh, 250); child.livedeskWindowsTrackingTimer.unref?.(); child.livedeskWindowsIdentityPromise = (async () => { while (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) { try { const identity = await captureWindowsIdentity(child
|
|
1480
|
+
'const trackWindowsChild = child => { if (process.platform !== "win32" || Number(child?.pid || 0) <= 1 || child.livedeskWindowsIdentityPromise) return; child.livedeskWindowsTrackedRecords = new Map(); child.livedeskWindowsSpawnedAtMs = Date.now(); child.livedeskWindowsRootExitedAtMs = null; child.livedeskWindowsTrackingStopped = false; child.once("exit", () => { if (!child.livedeskWindowsRootExitedAtMs) child.livedeskWindowsRootExitedAtMs = Date.now(); setWindowsRootLifetimeEnd(child, windowsTicksAtMilliseconds(child.livedeskWindowsRootExitedAtMs)); }); const refresh = () => { void refreshTrackedWindowsTree(child).then(() => { child.livedeskWindowsTrackingError = null; }).catch(error => { child.livedeskWindowsTrackingError = error; }); }; refresh(); child.livedeskWindowsTrackingTimer = setInterval(refresh, 250); child.livedeskWindowsTrackingTimer.unref?.(); child.livedeskWindowsIdentityPromise = (async () => { while (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) { try { const identity = await captureWindowsIdentity(child); child.livedeskWindowsIdentity = identity; refresh(); return identity; } catch (error) { child.livedeskWindowsIdentityError = error; if (!child.livedeskWindowsTrackingStopped && !child.livedeskWindowsRootExitedAtMs) await sleep(250); } } return null; })(); };',
|
|
1583
1481
|
'const stopWindowsTracking = child => { child.livedeskWindowsTrackingStopped = true; if (child?.livedeskWindowsTrackingTimer) clearInterval(child.livedeskWindowsTrackingTimer); child.livedeskWindowsTrackingTimer = null; };',
|
|
1584
1482
|
'const terminateExactWindowsTrackedTree = (child, root) => { if (child.livedeskWindowsDrainPromise) return child.livedeskWindowsDrainPromise; child.livedeskWindowsDrainPromise = (async () => { let attempt = 0; let emptyPasses = 0; for (;;) { attempt += 1; try { const snapshot = await refreshTrackedWindowsTree(child, root); const currentByPid = new Map(snapshot.map(item => [item.pid, item])); const remaining = [...child.livedeskWindowsTrackedRecords.values()].filter(target => sameWindowsIdentity(currentByPid.get(target.pid), target)).sort((left, right) => Number(right.depth || 0) - Number(left.depth || 0)); if (remaining.length === 0) { emptyPasses += 1; if (emptyPasses >= 2) { stopWindowsTracking(child); return; } } else { emptyPasses = 0; await stopExactWindowsRecords(remaining); } child.livedeskWindowsTrackingError = null; } catch (error) { emptyPasses = 0; child.livedeskWindowsTrackingError = error; if (attempt === 1 || attempt % 10 === 0) process.stderr.write(`LiveDesk update cleanup is retrying exact Windows process-tree drain: ${error?.message || error}\\n`); } await sleep(Math.min(1000, 100 + (attempt * 50))); } })(); return child.livedeskWindowsDrainPromise; };',
|
|
1585
1483
|
'const stateLockPath = statePath + ".lock"; const lockWaitBuffer = new Int32Array(new SharedArrayBuffer(4));',
|
|
@@ -1845,10 +1743,25 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
1845
1743
|
error: 'remote task capability is disabled'
|
|
1846
1744
|
});
|
|
1847
1745
|
return;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
const payload = message.payload || {};
|
|
1851
|
-
const
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
const payload = message.payload || {};
|
|
1749
|
+
const retiredTaskError = getRetiredClientAgentTaskError(payload);
|
|
1750
|
+
if (retiredTaskError) {
|
|
1751
|
+
writeJsonLine(socket, {
|
|
1752
|
+
type: 'command.result',
|
|
1753
|
+
commandId: message.commandId,
|
|
1754
|
+
error: retiredTaskError,
|
|
1755
|
+
result: {
|
|
1756
|
+
ok: false,
|
|
1757
|
+
status: 'failed',
|
|
1758
|
+
error: retiredTaskError,
|
|
1759
|
+
completedAt: new Date().toISOString()
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
const instruction = String(payload.instruction || '').replace(/\0/g, '').trim().slice(0, 4000);
|
|
1852
1765
|
if (!instruction) {
|
|
1853
1766
|
writeJsonLine(socket, {
|
|
1854
1767
|
type: 'command.result',
|
|
@@ -1858,43 +1771,13 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
1858
1771
|
return;
|
|
1859
1772
|
}
|
|
1860
1773
|
|
|
1861
|
-
const
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1864
|
-
.
|
|
1865
|
-
.
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
try {
|
|
1869
|
-
const aiResult = await runAiAssistTask(options, payload, instruction);
|
|
1870
|
-
writeJsonLine(socket, {
|
|
1871
|
-
type: 'command.result',
|
|
1872
|
-
commandId: message.commandId,
|
|
1873
|
-
result: {
|
|
1874
|
-
kind: 'agent.task',
|
|
1875
|
-
mode: 'ai-assist',
|
|
1876
|
-
taskId: String(payload.taskId || '').slice(0, 128),
|
|
1877
|
-
title,
|
|
1878
|
-
status: 'completed',
|
|
1879
|
-
summary: aiResult.text,
|
|
1880
|
-
sideEffects: 'none',
|
|
1881
|
-
model: aiResult.model,
|
|
1882
|
-
responseId: aiResult.responseId,
|
|
1883
|
-
instructionPreview: instruction.slice(0, 320),
|
|
1884
|
-
completedAt: new Date().toISOString()
|
|
1885
|
-
}
|
|
1886
|
-
});
|
|
1887
|
-
} catch (error) {
|
|
1888
|
-
writeJsonLine(socket, {
|
|
1889
|
-
type: 'command.result',
|
|
1890
|
-
commandId: message.commandId,
|
|
1891
|
-
error: error?.message || String(error)
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
writeJsonLine(socket, {
|
|
1774
|
+
const completedAt = new Date().toISOString();
|
|
1775
|
+
const title = String(payload.title || instruction.split(/\r?\n/)[0] || 'Remote task')
|
|
1776
|
+
.replace(/[\r\n\t]/g, ' ')
|
|
1777
|
+
.trim()
|
|
1778
|
+
.slice(0, 120);
|
|
1779
|
+
|
|
1780
|
+
writeJsonLine(socket, {
|
|
1898
1781
|
type: 'command.result',
|
|
1899
1782
|
commandId: message.commandId,
|
|
1900
1783
|
result: {
|
|
@@ -2139,14 +2022,11 @@ function connectOnce(options, deviceId) {
|
|
|
2139
2022
|
taskDispatch: options.taskEnabled,
|
|
2140
2023
|
clientUpdate: true,
|
|
2141
2024
|
productVersion: PRODUCT_VERSION,
|
|
2142
|
-
agentApproval: options.taskEnabled,
|
|
2143
|
-
agentAudit: options.taskEnabled,
|
|
2144
|
-
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
2145
|
-
elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
|
|
2146
|
-
|
|
2147
|
-
aiModel: options.aiModel,
|
|
2148
|
-
aiProvider: options.fakeAi ? 'fake' : (options.openAiApiKey ? 'openai' : ''),
|
|
2149
|
-
externalEffects: options.taskEnabled
|
|
2025
|
+
agentApproval: options.taskEnabled,
|
|
2026
|
+
agentAudit: options.taskEnabled,
|
|
2027
|
+
agentTools: [...NODE_AGENT_OPERATIONS],
|
|
2028
|
+
elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
|
|
2029
|
+
externalEffects: options.taskEnabled
|
|
2150
2030
|
}
|
|
2151
2031
|
});
|
|
2152
2032
|
});
|