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
|
@@ -3,17 +3,12 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
export const AGENT_PROVIDER_CODEX = 'codex';
|
|
6
|
-
export const AGENT_PROVIDER_OPENCODE_GO = 'opencode-go';
|
|
7
6
|
export const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_CODEX;
|
|
8
|
-
export const DEFAULT_AGENT_BASE_URL = 'https://opencode.ai/zen/go/v1';
|
|
9
|
-
export const DEFAULT_CODEX_MODEL_ID = '';
|
|
10
7
|
export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
|
|
11
8
|
export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
12
|
-
settingsVersion:
|
|
9
|
+
settingsVersion: 4,
|
|
13
10
|
enabled: true,
|
|
14
11
|
provider: DEFAULT_AGENT_PROVIDER,
|
|
15
|
-
codexModelId: DEFAULT_CODEX_MODEL_ID,
|
|
16
|
-
codexReasoningEffort: 'medium',
|
|
17
12
|
codexSandboxMode: 'read-only',
|
|
18
13
|
codexApprovalPolicy: 'never',
|
|
19
14
|
codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
|
|
@@ -24,44 +19,9 @@ export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
|
24
19
|
codexShowDetailedEvents: true,
|
|
25
20
|
codexNetworkAccessEnabled: false,
|
|
26
21
|
codexWebSearchMode: 'disabled',
|
|
27
|
-
|
|
28
|
-
generateSummary: true,
|
|
29
|
-
fallbackSummary: true,
|
|
30
|
-
includeRawDeviceDetails: false,
|
|
31
|
-
maxConcurrentRequests: 2,
|
|
32
|
-
baseUrl: DEFAULT_AGENT_BASE_URL,
|
|
33
|
-
defaultModelId: 'deepseek-v4-flash',
|
|
34
|
-
planModelId: '',
|
|
35
|
-
summaryModelId: '',
|
|
36
|
-
reasoningLevel: 'auto',
|
|
37
|
-
planTemperature: 0.1,
|
|
38
|
-
summaryTemperature: 0.2,
|
|
39
|
-
topP: 1,
|
|
40
|
-
planMaxOutputTokens: 1024,
|
|
41
|
-
summaryMaxOutputTokens: 1024,
|
|
42
|
-
planTimeoutMs: 60000,
|
|
43
|
-
summaryTimeoutMs: 60000,
|
|
44
|
-
retryCount: 1,
|
|
45
|
-
retryDelayMs: 1000,
|
|
46
|
-
streamSummary: false
|
|
22
|
+
maxConcurrentRequests: 2
|
|
47
23
|
});
|
|
48
24
|
|
|
49
|
-
const PROVIDERS = new Set([AGENT_PROVIDER_CODEX, AGENT_PROVIDER_OPENCODE_GO]);
|
|
50
|
-
const REASONING_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
51
|
-
const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
|
|
52
|
-
const APPROVAL_POLICIES = new Set(['never', 'on-request', 'on-failure', 'untrusted']);
|
|
53
|
-
const WEB_SEARCH_MODES = new Set(['disabled', 'cached', 'live']);
|
|
54
|
-
const LEGACY_REASONING_LEVELS = new Set(['auto', 'low', 'medium', 'high']);
|
|
55
|
-
|
|
56
|
-
export class AgentSettingsError extends Error {
|
|
57
|
-
constructor(code, message) {
|
|
58
|
-
super(message);
|
|
59
|
-
this.name = 'AgentSettingsError';
|
|
60
|
-
this.code = code;
|
|
61
|
-
this.status = 400;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
25
|
function booleanValue(value, fallback) {
|
|
66
26
|
return typeof value === 'boolean' ? value : fallback;
|
|
67
27
|
}
|
|
@@ -73,31 +33,7 @@ function numberValue(value, min, max, fallback, integer = false) {
|
|
|
73
33
|
return integer ? Math.round(clamped) : clamped;
|
|
74
34
|
}
|
|
75
35
|
|
|
76
|
-
function
|
|
77
|
-
if (typeof value !== 'string') return fallback;
|
|
78
|
-
return value.trim().slice(0, maxLength);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function enumValue(value, allowed, fallback, code) {
|
|
82
|
-
const normalized = stringValue(value, fallback, 80).toLowerCase();
|
|
83
|
-
if (!allowed.has(normalized)) throw new AgentSettingsError(code, `Invalid agent setting: ${normalized}.`);
|
|
84
|
-
return normalized;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function normalizeBaseUrl(value) {
|
|
88
|
-
const candidate = stringValue(value, DEFAULT_AGENT_SETTINGS.baseUrl, 500).replace(/\/+$/, '');
|
|
89
|
-
try {
|
|
90
|
-
const parsed = new URL(candidate);
|
|
91
|
-
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) throw new Error('invalid protocol');
|
|
92
|
-
parsed.hash = '';
|
|
93
|
-
parsed.search = '';
|
|
94
|
-
return parsed.toString().replace(/\/+$/, '');
|
|
95
|
-
} catch {
|
|
96
|
-
throw new AgentSettingsError('agent-invalid-base-url', 'Base URL must be a valid HTTP or HTTPS URL.');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function normalizeWorkingDirectory(value) {
|
|
36
|
+
function normalizeWorkingDirectory() {
|
|
101
37
|
// The agent runtime owns this directory. Do not allow a browser/API caller
|
|
102
38
|
// to move Codex into a user project or an arbitrary filesystem root.
|
|
103
39
|
return DEFAULT_AGENT_WORKSPACE;
|
|
@@ -105,25 +41,17 @@ function normalizeWorkingDirectory(value) {
|
|
|
105
41
|
|
|
106
42
|
export function normalizeAgentSettings(value = {}) {
|
|
107
43
|
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
108
|
-
// The pre-Codex settings file was written by LiveDesk itself and had no
|
|
109
|
-
// migration marker. Keep the legacy values but move that first-run config
|
|
110
|
-
// to the new Codex default.
|
|
111
|
-
const migratingLegacy = Number(source.settingsVersion || 0) < 2 && source.provider === AGENT_PROVIDER_OPENCODE_GO;
|
|
112
|
-
const provider = migratingLegacy
|
|
113
|
-
? DEFAULT_AGENT_PROVIDER
|
|
114
|
-
: enumValue(source.provider, PROVIDERS, DEFAULT_AGENT_PROVIDER, 'agent-unsupported-provider');
|
|
115
|
-
const reasoningLevel = enumValue(source.reasoningLevel, LEGACY_REASONING_LEVELS, DEFAULT_AGENT_SETTINGS.reasoningLevel, 'agent-invalid-reasoning-level');
|
|
116
44
|
return {
|
|
117
|
-
settingsVersion:
|
|
45
|
+
settingsVersion: 4,
|
|
118
46
|
enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
|
|
119
|
-
provider
|
|
120
|
-
|
|
121
|
-
|
|
47
|
+
// LiveDesk has one Agent runtime. Old provider values are deliberately
|
|
48
|
+
// normalized to Codex so an upgrade cannot reactivate a retired provider.
|
|
49
|
+
provider: AGENT_PROVIDER_CODEX,
|
|
122
50
|
// These are safety settings, not user-controlled execution toggles. They
|
|
123
51
|
// are accepted for compatibility but always normalized to the safe floor.
|
|
124
52
|
codexSandboxMode: 'read-only',
|
|
125
53
|
codexApprovalPolicy: 'never',
|
|
126
|
-
codexWorkingDirectory: normalizeWorkingDirectory(
|
|
54
|
+
codexWorkingDirectory: normalizeWorkingDirectory(),
|
|
127
55
|
codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
|
|
128
56
|
codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
|
|
129
57
|
codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),
|
|
@@ -131,26 +59,7 @@ export function normalizeAgentSettings(value = {}) {
|
|
|
131
59
|
codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
|
|
132
60
|
codexNetworkAccessEnabled: false,
|
|
133
61
|
codexWebSearchMode: 'disabled',
|
|
134
|
-
|
|
135
|
-
generateSummary: booleanValue(source.generateSummary, DEFAULT_AGENT_SETTINGS.generateSummary),
|
|
136
|
-
fallbackSummary: booleanValue(source.fallbackSummary, DEFAULT_AGENT_SETTINGS.fallbackSummary),
|
|
137
|
-
includeRawDeviceDetails: booleanValue(source.includeRawDeviceDetails, DEFAULT_AGENT_SETTINGS.includeRawDeviceDetails),
|
|
138
|
-
maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true),
|
|
139
|
-
baseUrl: normalizeBaseUrl(source.baseUrl ?? DEFAULT_AGENT_SETTINGS.baseUrl),
|
|
140
|
-
defaultModelId: stringValue(source.defaultModelId, DEFAULT_AGENT_SETTINGS.defaultModelId, 160),
|
|
141
|
-
planModelId: stringValue(source.planModelId, DEFAULT_AGENT_SETTINGS.planModelId, 160),
|
|
142
|
-
summaryModelId: stringValue(source.summaryModelId, DEFAULT_AGENT_SETTINGS.summaryModelId, 160),
|
|
143
|
-
reasoningLevel,
|
|
144
|
-
planTemperature: numberValue(source.planTemperature, 0, 2, DEFAULT_AGENT_SETTINGS.planTemperature),
|
|
145
|
-
summaryTemperature: numberValue(source.summaryTemperature, 0, 2, DEFAULT_AGENT_SETTINGS.summaryTemperature),
|
|
146
|
-
topP: numberValue(source.topP, 0, 1, DEFAULT_AGENT_SETTINGS.topP),
|
|
147
|
-
planMaxOutputTokens: numberValue(source.planMaxOutputTokens, 128, 16384, DEFAULT_AGENT_SETTINGS.planMaxOutputTokens, true),
|
|
148
|
-
summaryMaxOutputTokens: numberValue(source.summaryMaxOutputTokens, 128, 16384, DEFAULT_AGENT_SETTINGS.summaryMaxOutputTokens, true),
|
|
149
|
-
planTimeoutMs: numberValue(source.planTimeoutMs, 5000, 180000, DEFAULT_AGENT_SETTINGS.planTimeoutMs, true),
|
|
150
|
-
summaryTimeoutMs: numberValue(source.summaryTimeoutMs, 5000, 180000, DEFAULT_AGENT_SETTINGS.summaryTimeoutMs, true),
|
|
151
|
-
retryCount: numberValue(source.retryCount, 0, 4, DEFAULT_AGENT_SETTINGS.retryCount, true),
|
|
152
|
-
retryDelayMs: numberValue(source.retryDelayMs, 100, 10000, DEFAULT_AGENT_SETTINGS.retryDelayMs, true),
|
|
153
|
-
streamSummary: booleanValue(source.streamSummary, DEFAULT_AGENT_SETTINGS.streamSummary)
|
|
62
|
+
maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true)
|
|
154
63
|
};
|
|
155
64
|
}
|
|
156
65
|
|
|
@@ -79,7 +79,7 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
name: 'livedesk.list_processes',
|
|
82
|
-
description: 'Read process status from the selected Clients. Use processName
|
|
82
|
+
description: 'Read process status from the selected Clients. Use processName when the request names a specific process.',
|
|
83
83
|
category: 'read',
|
|
84
84
|
readOnly: true,
|
|
85
85
|
mutating: false,
|
|
@@ -66,15 +66,13 @@ function normalizeCodexError(error) {
|
|
|
66
66
|
return new AgentProviderError('codex-run-failed', message, { status: 502, retryable: true });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function codexThreadOptions(
|
|
70
|
-
return {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
modelReasoningEffort: settings.codexReasoningEffort,
|
|
77
|
-
networkAccessEnabled: false,
|
|
69
|
+
function codexThreadOptions(workspace) {
|
|
70
|
+
return {
|
|
71
|
+
sandboxMode: 'read-only',
|
|
72
|
+
approvalPolicy: 'never',
|
|
73
|
+
workingDirectory: workspace,
|
|
74
|
+
skipGitRepoCheck: true,
|
|
75
|
+
networkAccessEnabled: false,
|
|
78
76
|
webSearchMode: 'disabled',
|
|
79
77
|
webSearchEnabled: false,
|
|
80
78
|
additionalDirectories: []
|
|
@@ -417,10 +415,10 @@ export function createCodexAgentRuntime({
|
|
|
417
415
|
const controller = new AbortController();
|
|
418
416
|
const timer = setTimeout(() => controller.abort(), Math.min(30000, settings.codexTaskTimeoutMs));
|
|
419
417
|
try {
|
|
420
|
-
const thread = codex.startThread(codexThreadOptions(
|
|
421
|
-
const turn = await thread.run('Reply with the single word READY. Do not use tools.', { signal: controller.signal });
|
|
422
|
-
if (!String(turn.finalResponse || '').trim()) throw new AgentProviderError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
|
|
423
|
-
return { ok: true,
|
|
418
|
+
const thread = codex.startThread(codexThreadOptions(workspace));
|
|
419
|
+
const turn = await thread.run('Reply with the single word READY. Do not use tools.', { signal: controller.signal });
|
|
420
|
+
if (!String(turn.finalResponse || '').trim()) throw new AgentProviderError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
|
|
421
|
+
return { ok: true, latencyMs: Date.now() - startedAt, threadId: thread.id || '' };
|
|
424
422
|
} catch (error) {
|
|
425
423
|
throw normalizeCodexError(error);
|
|
426
424
|
} finally {
|
|
@@ -472,7 +470,7 @@ export function createCodexAgentRuntime({
|
|
|
472
470
|
'Never use arbitrary MCP servers, change permissions, forge approvals, request credentials, or invent a tool result.',
|
|
473
471
|
`The Hub has fixed this run to permission mode ${permissionPolicy?.mode || 'ask'} and enforces the policy independently of your instructions.`,
|
|
474
472
|
'Use only the selected connected device IDs below. If the Hub asks for user approval, wait for that approval result and do not work around it.',
|
|
475
|
-
'You may perform multiple safe read-only checks when the request requires a sequence. For example, find Clients
|
|
473
|
+
'You may perform multiple safe read-only checks when the request requires a sequence. For example, find Clients missing a named process, then check a related service only on those Clients.',
|
|
476
474
|
`Selected device IDs: ${JSON.stringify(deviceIds)}`,
|
|
477
475
|
'Return a concise Korean or English summary grounded only in tool results. Do not invent results.',
|
|
478
476
|
`User request: ${safeText(instruction, 4000)}`
|
|
@@ -531,7 +529,7 @@ export function createCodexAgentRuntime({
|
|
|
531
529
|
if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
|
|
532
530
|
throw Object.assign(new Error('The Codex task was cancelled.'), { name: 'AbortError' });
|
|
533
531
|
}
|
|
534
|
-
const threadOptions = codexThreadOptions(
|
|
532
|
+
const threadOptions = codexThreadOptions(workspace);
|
|
535
533
|
run.status = 'running';
|
|
536
534
|
run.updatedAt = new Date().toISOString();
|
|
537
535
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -649,7 +647,7 @@ export function createCodexAgentRuntime({
|
|
|
649
647
|
const deviceIds = safeAgentDeviceIds(input.deviceIds);
|
|
650
648
|
if (deviceIds.length === 0) throw new AgentProviderError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
|
|
651
649
|
const settings = await settingsStore.get();
|
|
652
|
-
if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Agent
|
|
650
|
+
if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
|
|
653
651
|
assertSecurityConfiguration();
|
|
654
652
|
pruneRuns();
|
|
655
653
|
if (runs.size >= MAX_RUNS) throw new AgentProviderError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -644,15 +644,27 @@ function safeTaskData(value) {
|
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
function normalizeApprovalLevel(value) {
|
|
648
|
-
const level = safeString(value, 80).toLowerCase();
|
|
649
|
-
if (level === 'read-only') {
|
|
650
|
-
return 'read-only';
|
|
651
|
-
}
|
|
652
|
-
return
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const
|
|
647
|
+
function normalizeApprovalLevel(value) {
|
|
648
|
+
const level = safeString(value, 80).toLowerCase();
|
|
649
|
+
if (level === 'read-only') {
|
|
650
|
+
return 'read-only';
|
|
651
|
+
}
|
|
652
|
+
return 'task-only';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const RETIRED_AGENT_TASK_ERROR = 'agent-ai-assist-retired';
|
|
656
|
+
|
|
657
|
+
function getRetiredAgentTaskError(options = {}) {
|
|
658
|
+
const approvalLevel = safeString(options?.approvalLevel, 80).toLowerCase();
|
|
659
|
+
const hasModelSelection = options
|
|
660
|
+
&& typeof options === 'object'
|
|
661
|
+
&& Object.prototype.hasOwnProperty.call(options, 'model');
|
|
662
|
+
return approvalLevel === 'ai-assist' || hasModelSelection
|
|
663
|
+
? RETIRED_AGENT_TASK_ERROR
|
|
664
|
+
: '';
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const SUPPORTED_AGENT_OPERATIONS = new Set([
|
|
656
668
|
'system.health',
|
|
657
669
|
'gpu.status',
|
|
658
670
|
'disk.status',
|
|
@@ -2586,13 +2598,12 @@ export function createRemoteHub(options = {}) {
|
|
|
2586
2598
|
const count = clampNumber(options.count, 1, MAX_SYNTHETIC_DEVICES, 250);
|
|
2587
2599
|
const connectedRatio = Math.max(0, Math.min(1, Number(options.connectedRatio ?? 0.88)));
|
|
2588
2600
|
const thumbnailRatio = Math.max(0, Math.min(1, Number(options.thumbnailRatio ?? 0.7)));
|
|
2589
|
-
const
|
|
2590
|
-
const liveCount = clampNumber(options.liveCount, 0, Math.min(count, 24), Math.min(6, count));
|
|
2601
|
+
const liveCount = clampNumber(options.liveCount, 0, Math.min(count, 24), Math.min(6, count));
|
|
2591
2602
|
const replace = options.replace !== false;
|
|
2592
2603
|
const now = new Date();
|
|
2593
2604
|
const platforms = ['win32', 'linux', 'darwin'];
|
|
2594
2605
|
const machineKinds = ['Mac mini', 'Mini PC', 'Client', 'Render node', 'Dev box'];
|
|
2595
|
-
const workloadRoles = ['
|
|
2606
|
+
const workloadRoles = ['Office computer', 'Media workstation', 'Development machine', 'Idle machine', 'Build machine'];
|
|
2596
2607
|
const gpuNames = ['Apple M-series GPU', 'NVIDIA RTX local', 'Radeon Pro', 'Intel Arc', 'Integrated GPU'];
|
|
2597
2608
|
const npuNames = ['Apple Neural Engine', 'Ryzen AI NPU', 'Intel AI Boost', 'Qualcomm Hexagon', 'NPU not present'];
|
|
2598
2609
|
|
|
@@ -2601,15 +2612,13 @@ export function createRemoteHub(options = {}) {
|
|
|
2601
2612
|
}
|
|
2602
2613
|
|
|
2603
2614
|
let connected = 0;
|
|
2604
|
-
let
|
|
2605
|
-
let live = 0;
|
|
2615
|
+
let live = 0;
|
|
2606
2616
|
for (let index = 0; index < count; index += 1) {
|
|
2607
2617
|
const ordinal = index + 1;
|
|
2608
2618
|
const deviceId = `synthetic-${String(ordinal).padStart(4, '0')}`;
|
|
2609
2619
|
const isConnected = index / count < connectedRatio;
|
|
2610
2620
|
const hasThumbnail = index / count < thumbnailRatio;
|
|
2611
|
-
const
|
|
2612
|
-
const isLive = isConnected && index < liveCount;
|
|
2621
|
+
const isLive = isConnected && index < liveCount;
|
|
2613
2622
|
const seenAt = new Date(now.getTime() - index * 1350).toISOString();
|
|
2614
2623
|
const connectedAt = new Date(now.getTime() - (index + 8) * 60000).toISOString();
|
|
2615
2624
|
const platform = platforms[index % platforms.length];
|
|
@@ -2631,9 +2640,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2631
2640
|
const npuTops = index % 5 === 4 ? 0 : platform === 'darwin' ? 38 : index % 2 === 0 ? 45 : 16;
|
|
2632
2641
|
const machineKind = machineKinds[index % machineKinds.length];
|
|
2633
2642
|
const workloadRole = workloadRoles[index % workloadRoles.length];
|
|
2634
|
-
const
|
|
2635
|
-
const toolOnline = index % 4 !== 3;
|
|
2636
|
-
const taskStatus = index % 17 === 0
|
|
2643
|
+
const taskStatus = index % 17 === 0
|
|
2637
2644
|
? 'failed'
|
|
2638
2645
|
: index % 5 === 0
|
|
2639
2646
|
? 'completed'
|
|
@@ -2647,18 +2654,16 @@ export function createRemoteHub(options = {}) {
|
|
|
2647
2654
|
title: taskStatus === 'failed' ? 'Synthetic issue check' : 'Synthetic status task',
|
|
2648
2655
|
instructionPreview: 'Synthetic fleet scale validation task.',
|
|
2649
2656
|
status: taskStatus,
|
|
2650
|
-
approvalLevel:
|
|
2657
|
+
approvalLevel: 'task-only',
|
|
2651
2658
|
requestedAt: seenAt,
|
|
2652
2659
|
sentAt: seenAt,
|
|
2653
2660
|
updatedAt: seenAt,
|
|
2654
|
-
completedAt: taskStatus === 'completed' || taskStatus === 'failed' ? seenAt : '',
|
|
2655
|
-
error: taskStatus === 'failed' ? 'Synthetic task failure sample.' : '',
|
|
2656
|
-
resultKind: 'synthetic-agent-task',
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
? 'Synthetic task reported a sample issue.'
|
|
2661
|
-
: 'Synthetic task completed for scale validation.'
|
|
2661
|
+
completedAt: taskStatus === 'completed' || taskStatus === 'failed' ? seenAt : '',
|
|
2662
|
+
error: taskStatus === 'failed' ? 'Synthetic task failure sample.' : '',
|
|
2663
|
+
resultKind: 'synthetic-agent-task',
|
|
2664
|
+
resultSummary: taskStatus === 'failed'
|
|
2665
|
+
? 'Synthetic task reported a sample issue.'
|
|
2666
|
+
: 'Synthetic task completed for scale validation.'
|
|
2662
2667
|
}
|
|
2663
2668
|
: null;
|
|
2664
2669
|
const device = {
|
|
@@ -2677,11 +2682,8 @@ export function createRemoteHub(options = {}) {
|
|
|
2677
2682
|
thumbnail: hasThumbnail,
|
|
2678
2683
|
control: false,
|
|
2679
2684
|
liveStream: true,
|
|
2680
|
-
computerAgent: true,
|
|
2681
|
-
taskDispatch: true
|
|
2682
|
-
aiAssist: hasAiAssist,
|
|
2683
|
-
aiModel: hasAiAssist ? 'synthetic-ai' : '',
|
|
2684
|
-
aiProvider: hasAiAssist ? 'synthetic' : ''
|
|
2685
|
+
computerAgent: true,
|
|
2686
|
+
taskDispatch: true
|
|
2685
2687
|
},
|
|
2686
2688
|
connected: isConnected,
|
|
2687
2689
|
connectedAt: isConnected ? connectedAt : '',
|
|
@@ -2740,31 +2742,12 @@ export function createRemoteHub(options = {}) {
|
|
|
2740
2742
|
txMbps: 12 + ((index * 17) % 220),
|
|
2741
2743
|
usageRatio: Number(Math.min(0.94, ((index * 9) % 81) / 100).toFixed(2))
|
|
2742
2744
|
},
|
|
2743
|
-
workload: {
|
|
2744
|
-
role: workloadRole,
|
|
2745
|
-
status: taskStatus ||
|
|
2746
|
-
title: taskStatus ? 'Fleet validation task' :
|
|
2747
|
-
summary: taskStatus ? 'Synthetic status for fleet-scale validation.' : ''
|
|
2748
|
-
}
|
|
2749
|
-
tools: {
|
|
2750
|
-
ollama: {
|
|
2751
|
-
status: toolOnline ? (toolBusy ? 'busy' : 'online') : 'offline',
|
|
2752
|
-
port: 11434,
|
|
2753
|
-
version: toolOnline ? 'local' : ''
|
|
2754
|
-
},
|
|
2755
|
-
lmStudio: {
|
|
2756
|
-
status: index % 5 === 0 ? 'online' : 'offline',
|
|
2757
|
-
port: 1234
|
|
2758
|
-
},
|
|
2759
|
-
comfyui: {
|
|
2760
|
-
status: index % 3 === 0 ? (toolBusy ? 'busy' : 'online') : 'offline',
|
|
2761
|
-
port: 8188
|
|
2762
|
-
},
|
|
2763
|
-
stableDiffusion: {
|
|
2764
|
-
status: index % 7 === 0 ? 'online' : 'offline',
|
|
2765
|
-
port: 7860
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2745
|
+
workload: {
|
|
2746
|
+
role: workloadRole,
|
|
2747
|
+
status: taskStatus || 'idle',
|
|
2748
|
+
title: taskStatus ? 'Fleet validation task' : 'idle',
|
|
2749
|
+
summary: taskStatus ? 'Synthetic status for fleet-scale validation.' : ''
|
|
2750
|
+
}
|
|
2768
2751
|
},
|
|
2769
2752
|
latestThumbnail: null,
|
|
2770
2753
|
latestLiveFrame: null,
|
|
@@ -2839,25 +2822,20 @@ export function createRemoteHub(options = {}) {
|
|
|
2839
2822
|
if (isConnected) {
|
|
2840
2823
|
connected += 1;
|
|
2841
2824
|
}
|
|
2842
|
-
|
|
2843
|
-
aiAssist += 1;
|
|
2844
|
-
}
|
|
2845
|
-
devices.set(deviceId, device);
|
|
2825
|
+
devices.set(deviceId, device);
|
|
2846
2826
|
}
|
|
2847
2827
|
|
|
2848
2828
|
emitRemoteEvent('RemoteSyntheticFleetSeeded', null, {
|
|
2849
|
-
count,
|
|
2850
|
-
connected,
|
|
2851
|
-
|
|
2852
|
-
live
|
|
2829
|
+
count,
|
|
2830
|
+
connected,
|
|
2831
|
+
live
|
|
2853
2832
|
});
|
|
2854
2833
|
return {
|
|
2855
2834
|
ok: true,
|
|
2856
2835
|
synthetic: true,
|
|
2857
|
-
seeded: count,
|
|
2858
|
-
connected,
|
|
2859
|
-
|
|
2860
|
-
live,
|
|
2836
|
+
seeded: count,
|
|
2837
|
+
connected,
|
|
2838
|
+
live,
|
|
2861
2839
|
total: devices.size,
|
|
2862
2840
|
max: MAX_SYNTHETIC_DEVICES
|
|
2863
2841
|
};
|
|
@@ -3754,12 +3732,10 @@ export function createRemoteHub(options = {}) {
|
|
|
3754
3732
|
task.updatedAt = now;
|
|
3755
3733
|
task.completedAt = safeString(result?.completedAt, 80) || now;
|
|
3756
3734
|
task.error = resultError;
|
|
3757
|
-
task.resultSummary = resultError || summarizeTaskResult(result);
|
|
3758
|
-
task.resultData = safeTaskData(result?.data);
|
|
3759
|
-
task.resultKind = safeString(result?.kind || result?.mode || 'agent-task', 80);
|
|
3760
|
-
|
|
3761
|
-
task.resultResponseId = safeString(result?.responseId || result?.id || '', 160);
|
|
3762
|
-
device.latestTask = task;
|
|
3735
|
+
task.resultSummary = resultError || summarizeTaskResult(result);
|
|
3736
|
+
task.resultData = safeTaskData(result?.data);
|
|
3737
|
+
task.resultKind = safeString(result?.kind || result?.mode || 'agent-task', 80);
|
|
3738
|
+
device.latestTask = task;
|
|
3763
3739
|
device.pendingTaskCommands.delete(commandId);
|
|
3764
3740
|
device.counters.taskResultsReceived += 1;
|
|
3765
3741
|
if (status === 'failed') {
|
|
@@ -5862,9 +5838,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5862
5838
|
}
|
|
5863
5839
|
|
|
5864
5840
|
function requestAgentTask(deviceId, options = {}) {
|
|
5865
|
-
const
|
|
5866
|
-
|
|
5867
|
-
|
|
5841
|
+
const retiredRequestError = getRetiredAgentTaskError(options);
|
|
5842
|
+
if (retiredRequestError) {
|
|
5843
|
+
return { ok: false, error: retiredRequestError };
|
|
5844
|
+
}
|
|
5845
|
+
const device = devices.get(String(deviceId || ''));
|
|
5846
|
+
const requestedOperation = safeString(options.operation, 80);
|
|
5847
|
+
const operation = normalizeAgentOperation(requestedOperation);
|
|
5868
5848
|
if (requestedOperation && !operation) {
|
|
5869
5849
|
return { ok: false, error: 'unsupported-agent-operation' };
|
|
5870
5850
|
}
|
|
@@ -5882,10 +5862,6 @@ export function createRemoteHub(options = {}) {
|
|
|
5882
5862
|
|
|
5883
5863
|
const now = new Date().toISOString();
|
|
5884
5864
|
const approvalLevel = normalizeApprovalLevel(options.approvalLevel);
|
|
5885
|
-
if (approvalLevel === 'ai-assist' && !readCapabilityFlag(device.capabilities, 'aiAssist')) {
|
|
5886
|
-
return { ok: false, error: 'device-ai-assist-unavailable' };
|
|
5887
|
-
}
|
|
5888
|
-
|
|
5889
5865
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
5890
5866
|
const taskId = safeString(options.taskId, 128) || crypto.randomUUID();
|
|
5891
5867
|
const title = safeString(options.title, 120)
|
|
@@ -5907,24 +5883,12 @@ export function createRemoteHub(options = {}) {
|
|
|
5907
5883
|
requestedAt: now,
|
|
5908
5884
|
sentAt: now,
|
|
5909
5885
|
updatedAt: now,
|
|
5910
|
-
completedAt: now,
|
|
5911
|
-
error: '',
|
|
5912
|
-
resultKind:
|
|
5913
|
-
|
|
5914
|
-
?
|
|
5915
|
-
:
|
|
5916
|
-
resultResponseId: approvalLevel === 'ai-assist'
|
|
5917
|
-
? `synthetic-response-${taskId}`
|
|
5918
|
-
: '',
|
|
5919
|
-
resultSummary: operation === 'process.list'
|
|
5920
|
-
? (safeString(options.targetQuery, 160).toLowerCase() === 'comfyui'
|
|
5921
|
-
? (device.status?.tools?.comfyui?.status === 'online'
|
|
5922
|
-
? 'ComfyUI is running.'
|
|
5923
|
-
: 'ComfyUI is not running.')
|
|
5924
|
-
: 'Process list collected.')
|
|
5925
|
-
: approvalLevel === 'ai-assist'
|
|
5926
|
-
? `Synthetic AI assist completed for ${device.deviceName}.`
|
|
5927
|
-
: `Synthetic task accepted by ${device.deviceName}.`,
|
|
5886
|
+
completedAt: now,
|
|
5887
|
+
error: '',
|
|
5888
|
+
resultKind: 'synthetic-agent-task',
|
|
5889
|
+
resultSummary: operation === 'process.list'
|
|
5890
|
+
? 'Process list collected.'
|
|
5891
|
+
: `Synthetic task accepted by ${device.deviceName}.`,
|
|
5928
5892
|
resultData: undefined
|
|
5929
5893
|
};
|
|
5930
5894
|
|
|
@@ -5964,10 +5928,6 @@ export function createRemoteHub(options = {}) {
|
|
|
5964
5928
|
|
|
5965
5929
|
const now = new Date().toISOString();
|
|
5966
5930
|
const approvalLevel = normalizeApprovalLevel(options.approvalLevel);
|
|
5967
|
-
if (approvalLevel === 'ai-assist' && !readCapabilityFlag(device.capabilities, 'aiAssist')) {
|
|
5968
|
-
return { ok: false, error: 'device-ai-assist-unavailable' };
|
|
5969
|
-
}
|
|
5970
|
-
|
|
5971
5931
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
5972
5932
|
const taskId = safeString(options.taskId, 128) || crypto.randomUUID();
|
|
5973
5933
|
const title = safeString(options.title, 120)
|
|
@@ -5989,14 +5949,12 @@ export function createRemoteHub(options = {}) {
|
|
|
5989
5949
|
requestedAt: now,
|
|
5990
5950
|
sentAt: now,
|
|
5991
5951
|
updatedAt: now,
|
|
5992
|
-
completedAt: '',
|
|
5993
|
-
error: '',
|
|
5994
|
-
resultKind: '',
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
resultData: undefined
|
|
5999
|
-
};
|
|
5952
|
+
completedAt: '',
|
|
5953
|
+
error: '',
|
|
5954
|
+
resultKind: '',
|
|
5955
|
+
resultSummary: '',
|
|
5956
|
+
resultData: undefined
|
|
5957
|
+
};
|
|
6000
5958
|
|
|
6001
5959
|
const commandName = operation || 'agent.task';
|
|
6002
5960
|
const sent = writeJsonLine(device.socket, {
|
|
@@ -6012,8 +5970,7 @@ export function createRemoteHub(options = {}) {
|
|
|
6012
5970
|
toolArguments: options.toolArguments && typeof options.toolArguments === 'object' ? options.toolArguments : {},
|
|
6013
5971
|
permissionMode: safeString(options.permissionMode, 40) || 'ask',
|
|
6014
5972
|
approvalLevel,
|
|
6015
|
-
|
|
6016
|
-
requestedAt: now
|
|
5973
|
+
requestedAt: now
|
|
6017
5974
|
},
|
|
6018
5975
|
issuedAt: now
|
|
6019
5976
|
});
|
|
@@ -6035,15 +5992,26 @@ export function createRemoteHub(options = {}) {
|
|
|
6035
5992
|
approvalLevel
|
|
6036
5993
|
});
|
|
6037
5994
|
return { ok: true, commandId, taskId, approvalLevel };
|
|
6038
|
-
}
|
|
6039
|
-
|
|
6040
|
-
function requestAgentTaskBatch(deviceIds, options = {}) {
|
|
6041
|
-
const targets = [...new Set((deviceIds || [])
|
|
6042
|
-
.map(deviceId => safeString(deviceId, 128))
|
|
6043
|
-
.filter(Boolean))]
|
|
6044
|
-
.slice(0, 500);
|
|
6045
|
-
|
|
6046
|
-
if (
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
function requestAgentTaskBatch(deviceIds, options = {}) {
|
|
5998
|
+
const targets = [...new Set((deviceIds || [])
|
|
5999
|
+
.map(deviceId => safeString(deviceId, 128))
|
|
6000
|
+
.filter(Boolean))]
|
|
6001
|
+
.slice(0, 500);
|
|
6002
|
+
const retiredRequestError = getRetiredAgentTaskError(options);
|
|
6003
|
+
if (retiredRequestError) {
|
|
6004
|
+
return {
|
|
6005
|
+
ok: false,
|
|
6006
|
+
error: retiredRequestError,
|
|
6007
|
+
total: targets.length,
|
|
6008
|
+
queued: 0,
|
|
6009
|
+
approvalLevel: normalizeApprovalLevel(options.approvalLevel),
|
|
6010
|
+
results: []
|
|
6011
|
+
};
|
|
6012
|
+
}
|
|
6013
|
+
|
|
6014
|
+
if (targets.length === 0) {
|
|
6047
6015
|
return {
|
|
6048
6016
|
ok: false,
|
|
6049
6017
|
error: 'no-target-devices',
|