@livedesk/hub 0.1.3 → 0.1.5
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/README.md +15 -15
- package/package.json +2 -1
- package/src/agents/agent-audit-store.js +93 -0
- package/src/agents/agent-device-scope.js +29 -0
- package/src/agents/agent-manager.js +175 -0
- package/src/agents/agent-permission-store.js +78 -0
- package/src/agents/agent-permissions.js +209 -0
- package/src/agents/agent-settings.js +195 -0
- package/src/agents/agent-tool-registry.js +322 -0
- package/src/agents/codex-agent-runtime.js +720 -0
- package/src/agents/codex-mcp-server.js +100 -0
- package/src/agents/opencode-go-provider.js +260 -0
- package/src/agents/provider-errors.js +12 -0
- package/src/agents/secret-store.js +165 -0
- package/src/captures/capture-store.js +252 -0
- package/src/http/hub-role-guard.js +8 -0
- package/src/live-desk-update.js +457 -0
- package/src/mode4-atlas.js +7 -2
- package/src/remote-hub.js +5648 -5059
- package/src/runtime/hub-runtime-status.js +10 -0
- package/src/runtime/hub-runtime.js +14 -0
- package/src/server.js +3706 -1632
- package/src/settings/effective-device-policy.js +16 -0
- package/src/settings/settings-schema.js +251 -0
- package/src/settings/settings-store.js +77 -0
- package/src/transport/udp-hub-transport.js +281 -0
- package/src/transport/udp-protocol.js +216 -0
- package/src/transport/udp-rendezvous.js +78 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const AGENT_PROVIDER_CODEX = 'codex';
|
|
6
|
+
export const AGENT_PROVIDER_OPENCODE_GO = 'opencode-go';
|
|
7
|
+
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
|
+
export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
|
|
11
|
+
export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
12
|
+
settingsVersion: 2,
|
|
13
|
+
enabled: true,
|
|
14
|
+
provider: DEFAULT_AGENT_PROVIDER,
|
|
15
|
+
codexModelId: DEFAULT_CODEX_MODEL_ID,
|
|
16
|
+
codexReasoningEffort: 'medium',
|
|
17
|
+
codexSandboxMode: 'read-only',
|
|
18
|
+
codexApprovalPolicy: 'never',
|
|
19
|
+
codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
|
|
20
|
+
codexTaskTimeoutMs: 600000,
|
|
21
|
+
codexMaxTurns: 12,
|
|
22
|
+
codexMaxToolCalls: 20,
|
|
23
|
+
codexResumeSessions: true,
|
|
24
|
+
codexShowDetailedEvents: true,
|
|
25
|
+
codexNetworkAccessEnabled: false,
|
|
26
|
+
codexWebSearchMode: 'disabled',
|
|
27
|
+
deterministicParserFirst: true,
|
|
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
|
|
47
|
+
});
|
|
48
|
+
|
|
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
|
+
function booleanValue(value, fallback) {
|
|
66
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function numberValue(value, min, max, fallback, integer = false) {
|
|
70
|
+
const number = Number(value);
|
|
71
|
+
if (!Number.isFinite(number)) return fallback;
|
|
72
|
+
const clamped = Math.max(min, Math.min(max, number));
|
|
73
|
+
return integer ? Math.round(clamped) : clamped;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function stringValue(value, fallback, maxLength = 240) {
|
|
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) {
|
|
101
|
+
// The agent runtime owns this directory. Do not allow a browser/API caller
|
|
102
|
+
// to move Codex into a user project or an arbitrary filesystem root.
|
|
103
|
+
return DEFAULT_AGENT_WORKSPACE;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeAgentSettings(value = {}) {
|
|
107
|
+
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
|
+
return {
|
|
117
|
+
settingsVersion: 2,
|
|
118
|
+
enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
|
|
119
|
+
provider,
|
|
120
|
+
codexModelId: stringValue(source.codexModelId, DEFAULT_AGENT_SETTINGS.codexModelId, 160),
|
|
121
|
+
codexReasoningEffort: enumValue(source.codexReasoningEffort, REASONING_EFFORTS, DEFAULT_AGENT_SETTINGS.codexReasoningEffort, 'agent-invalid-reasoning-effort'),
|
|
122
|
+
// These are safety settings, not user-controlled execution toggles. They
|
|
123
|
+
// are accepted for compatibility but always normalized to the safe floor.
|
|
124
|
+
codexSandboxMode: 'read-only',
|
|
125
|
+
codexApprovalPolicy: 'never',
|
|
126
|
+
codexWorkingDirectory: normalizeWorkingDirectory(source.codexWorkingDirectory),
|
|
127
|
+
codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
|
|
128
|
+
codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
|
|
129
|
+
codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),
|
|
130
|
+
codexResumeSessions: booleanValue(source.codexResumeSessions, DEFAULT_AGENT_SETTINGS.codexResumeSessions),
|
|
131
|
+
codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
|
|
132
|
+
codexNetworkAccessEnabled: false,
|
|
133
|
+
codexWebSearchMode: 'disabled',
|
|
134
|
+
deterministicParserFirst: booleanValue(source.deterministicParserFirst, DEFAULT_AGENT_SETTINGS.deterministicParserFirst),
|
|
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)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function writeJsonAtomic(filePath, value) {
|
|
158
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
159
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
160
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
161
|
+
await rename(temporaryPath, filePath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class AgentSettingsStore {
|
|
165
|
+
constructor({ dataDir = path.join(os.homedir(), '.livedesk') } = {}) {
|
|
166
|
+
this.filePath = path.join(dataDir, 'agent-settings.json');
|
|
167
|
+
this.settings = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async get() {
|
|
171
|
+
if (this.settings) return { ...this.settings };
|
|
172
|
+
try {
|
|
173
|
+
const raw = await readFile(this.filePath, 'utf8');
|
|
174
|
+
this.settings = normalizeAgentSettings(JSON.parse(raw));
|
|
175
|
+
} catch {
|
|
176
|
+
this.settings = normalizeAgentSettings(DEFAULT_AGENT_SETTINGS);
|
|
177
|
+
}
|
|
178
|
+
return { ...this.settings };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async update(patch = {}) {
|
|
182
|
+
const current = await this.get();
|
|
183
|
+
const next = normalizeAgentSettings({ ...current, ...patch });
|
|
184
|
+
await writeJsonAtomic(this.filePath, next);
|
|
185
|
+
this.settings = next;
|
|
186
|
+
return { ...next };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async reset() {
|
|
190
|
+
const next = normalizeAgentSettings(DEFAULT_AGENT_SETTINGS);
|
|
191
|
+
await writeJsonAtomic(this.filePath, next);
|
|
192
|
+
this.settings = next;
|
|
193
|
+
return { ...next };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const readOnlyInput = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
properties: {
|
|
4
|
+
deviceIds: { type: 'array', items: { type: 'string' }, maxItems: 500 }
|
|
5
|
+
},
|
|
6
|
+
additionalProperties: false
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function withQueryInput(properties) {
|
|
10
|
+
return {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: { ...properties, deviceIds: readOnlyInput.properties.deviceIds },
|
|
13
|
+
additionalProperties: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function withRequiredInput(properties, required = []) {
|
|
18
|
+
return {
|
|
19
|
+
...withQueryInput(properties),
|
|
20
|
+
required
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const taskPermission = { ask: 'ask', safeAuto: 'ask', fullAccess: 'allow' };
|
|
25
|
+
const readTaskPermission = { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' };
|
|
26
|
+
|
|
27
|
+
export const AGENT_TOOL_DEFINITIONS = Object.freeze([
|
|
28
|
+
{
|
|
29
|
+
name: 'livedesk.list_devices',
|
|
30
|
+
description: 'List connected LiveDesk workstations. This is read-only.',
|
|
31
|
+
category: 'read',
|
|
32
|
+
readOnly: true,
|
|
33
|
+
mutating: false,
|
|
34
|
+
risk: 'low',
|
|
35
|
+
reversible: true,
|
|
36
|
+
supportsBatch: true,
|
|
37
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
38
|
+
inputSchema: readOnlyInput,
|
|
39
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'livedesk.get_system_health',
|
|
43
|
+
description: 'Read system health from the selected workstations.',
|
|
44
|
+
category: 'read',
|
|
45
|
+
readOnly: true,
|
|
46
|
+
mutating: false,
|
|
47
|
+
risk: 'low',
|
|
48
|
+
reversible: true,
|
|
49
|
+
supportsBatch: true,
|
|
50
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
51
|
+
inputSchema: readOnlyInput,
|
|
52
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'livedesk.get_gpu_status',
|
|
56
|
+
description: 'Read GPU status from the selected workstations.',
|
|
57
|
+
category: 'read',
|
|
58
|
+
readOnly: true,
|
|
59
|
+
mutating: false,
|
|
60
|
+
risk: 'low',
|
|
61
|
+
reversible: true,
|
|
62
|
+
supportsBatch: true,
|
|
63
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
64
|
+
inputSchema: readOnlyInput,
|
|
65
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'livedesk.get_disk_status',
|
|
69
|
+
description: 'Read disk status from the selected workstations.',
|
|
70
|
+
category: 'read',
|
|
71
|
+
readOnly: true,
|
|
72
|
+
mutating: false,
|
|
73
|
+
risk: 'low',
|
|
74
|
+
reversible: true,
|
|
75
|
+
supportsBatch: true,
|
|
76
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
77
|
+
inputSchema: readOnlyInput,
|
|
78
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'livedesk.list_processes',
|
|
82
|
+
description: 'Read process status from the selected workstations. Use processName for a named process such as ComfyUI.',
|
|
83
|
+
category: 'read',
|
|
84
|
+
readOnly: true,
|
|
85
|
+
mutating: false,
|
|
86
|
+
risk: 'low',
|
|
87
|
+
reversible: true,
|
|
88
|
+
supportsBatch: true,
|
|
89
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
90
|
+
inputSchema: withQueryInput({ processName: { type: 'string', maxLength: 120 } }),
|
|
91
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'livedesk.get_service_status',
|
|
95
|
+
description: 'Read service status from the selected workstations. Use serviceName when the user names a service.',
|
|
96
|
+
category: 'read',
|
|
97
|
+
readOnly: true,
|
|
98
|
+
mutating: false,
|
|
99
|
+
risk: 'low',
|
|
100
|
+
reversible: true,
|
|
101
|
+
supportsBatch: true,
|
|
102
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
103
|
+
inputSchema: withQueryInput({ serviceName: { type: 'string', maxLength: 120 } }),
|
|
104
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'livedesk.collect_diagnostics',
|
|
108
|
+
description: 'Collect the existing safe LiveDesk diagnostics payload from the selected workstations.',
|
|
109
|
+
category: 'read',
|
|
110
|
+
readOnly: true,
|
|
111
|
+
mutating: false,
|
|
112
|
+
risk: 'low',
|
|
113
|
+
reversible: true,
|
|
114
|
+
supportsBatch: true,
|
|
115
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
116
|
+
inputSchema: readOnlyInput,
|
|
117
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'livedesk.control_process',
|
|
121
|
+
description: 'Stop or restart a named process on the selected workstations. This changes process state.',
|
|
122
|
+
category: 'processControl',
|
|
123
|
+
readOnly: false,
|
|
124
|
+
mutating: true,
|
|
125
|
+
risk: 'high',
|
|
126
|
+
reversible: true,
|
|
127
|
+
supportsBatch: true,
|
|
128
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
129
|
+
inputSchema: withRequiredInput({ processName: { type: 'string', minLength: 1, maxLength: 120 }, action: { type: 'string', enum: ['stop', 'restart'] } }, ['processName', 'action']),
|
|
130
|
+
defaultPermission: taskPermission
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'livedesk.control_service',
|
|
134
|
+
description: 'Start, stop, or restart a named service on the selected workstations.',
|
|
135
|
+
category: 'serviceControl',
|
|
136
|
+
readOnly: false,
|
|
137
|
+
mutating: true,
|
|
138
|
+
risk: 'high',
|
|
139
|
+
reversible: true,
|
|
140
|
+
supportsBatch: true,
|
|
141
|
+
supportedPlatforms: ['windows', 'linux'],
|
|
142
|
+
inputSchema: withRequiredInput({ serviceName: { type: 'string', minLength: 1, maxLength: 120 }, action: { type: 'string', enum: ['start', 'stop', 'restart'] } }, ['serviceName', 'action']),
|
|
143
|
+
defaultPermission: taskPermission
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'livedesk.launch_application',
|
|
147
|
+
description: 'Launch a specific application without using a shell command.',
|
|
148
|
+
category: 'applicationControl',
|
|
149
|
+
readOnly: false,
|
|
150
|
+
mutating: true,
|
|
151
|
+
risk: 'medium',
|
|
152
|
+
reversible: true,
|
|
153
|
+
supportsBatch: true,
|
|
154
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
155
|
+
inputSchema: withRequiredInput({ executable: { type: 'string', minLength: 1, maxLength: 400 }, args: { type: 'array', items: { type: 'string', maxLength: 400 }, maxItems: 32 }, workingDirectory: { type: 'string', maxLength: 600 } }, ['executable']),
|
|
156
|
+
defaultPermission: taskPermission
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'livedesk.close_application',
|
|
160
|
+
description: 'Close a named application process on the selected workstations.',
|
|
161
|
+
category: 'applicationControl',
|
|
162
|
+
readOnly: false,
|
|
163
|
+
mutating: true,
|
|
164
|
+
risk: 'medium',
|
|
165
|
+
reversible: true,
|
|
166
|
+
supportsBatch: true,
|
|
167
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
168
|
+
inputSchema: withRequiredInput({ processName: { type: 'string', minLength: 1, maxLength: 120 }, force: { type: 'boolean' } }, ['processName']),
|
|
169
|
+
defaultPermission: taskPermission
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'livedesk.read_file',
|
|
173
|
+
description: 'Read a bounded UTF-8 text file from the selected workstations, with sensitive credential paths rejected.',
|
|
174
|
+
category: 'fileRead',
|
|
175
|
+
readOnly: true,
|
|
176
|
+
mutating: false,
|
|
177
|
+
risk: 'medium',
|
|
178
|
+
reversible: true,
|
|
179
|
+
supportsBatch: true,
|
|
180
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
181
|
+
inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, maxBytes: { type: 'integer', minimum: 1, maximum: 65536 } }, ['path']),
|
|
182
|
+
defaultPermission: { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow' }
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'livedesk.write_file',
|
|
186
|
+
description: 'Write bounded text content to a file on the selected workstations.',
|
|
187
|
+
category: 'fileWrite',
|
|
188
|
+
readOnly: false,
|
|
189
|
+
mutating: true,
|
|
190
|
+
risk: 'high',
|
|
191
|
+
reversible: true,
|
|
192
|
+
supportsBatch: true,
|
|
193
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
194
|
+
inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, content: { type: 'string', maxLength: 1048576 }, append: { type: 'boolean' } }, ['path', 'content']),
|
|
195
|
+
defaultPermission: taskPermission
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'livedesk.delete_file',
|
|
199
|
+
description: 'Delete a selected file or explicitly requested directory on the selected workstations.',
|
|
200
|
+
category: 'fileDelete',
|
|
201
|
+
readOnly: false,
|
|
202
|
+
mutating: true,
|
|
203
|
+
risk: 'critical',
|
|
204
|
+
reversible: false,
|
|
205
|
+
supportsBatch: true,
|
|
206
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
207
|
+
inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, recursive: { type: 'boolean' } }, ['path']),
|
|
208
|
+
defaultPermission: taskPermission
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'livedesk.list_directory',
|
|
212
|
+
description: 'List bounded file metadata from a directory on the selected workstations.',
|
|
213
|
+
category: 'fileRead',
|
|
214
|
+
readOnly: true,
|
|
215
|
+
mutating: false,
|
|
216
|
+
risk: 'low',
|
|
217
|
+
reversible: true,
|
|
218
|
+
supportsBatch: true,
|
|
219
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
220
|
+
inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, recursive: { type: 'boolean' }, maxEntries: { type: 'integer', minimum: 1, maximum: 500 } }, ['path']),
|
|
221
|
+
defaultPermission: readTaskPermission
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'livedesk.run_command',
|
|
225
|
+
description: 'Run a bounded command through the selected workstation command interpreter. This is an audited high-risk action.',
|
|
226
|
+
category: 'shell',
|
|
227
|
+
readOnly: false,
|
|
228
|
+
mutating: true,
|
|
229
|
+
risk: 'critical',
|
|
230
|
+
reversible: false,
|
|
231
|
+
supportsBatch: false,
|
|
232
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
233
|
+
inputSchema: withRequiredInput({ command: { type: 'string', minLength: 1, maxLength: 4000 }, workingDirectory: { type: 'string', maxLength: 600 }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 30000 } }, ['command']),
|
|
234
|
+
defaultPermission: taskPermission
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'livedesk.run_script',
|
|
238
|
+
description: 'Run a bounded script file using the workstation platform interpreter.',
|
|
239
|
+
category: 'script',
|
|
240
|
+
readOnly: false,
|
|
241
|
+
mutating: true,
|
|
242
|
+
risk: 'high',
|
|
243
|
+
reversible: false,
|
|
244
|
+
supportsBatch: false,
|
|
245
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
246
|
+
inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, args: { type: 'array', items: { type: 'string', maxLength: 400 }, maxItems: 32 }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 30000 } }, ['path']),
|
|
247
|
+
defaultPermission: taskPermission
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'livedesk.install_software',
|
|
251
|
+
description: 'Install a named package through an explicitly selected package manager.',
|
|
252
|
+
category: 'softwareInstall',
|
|
253
|
+
readOnly: false,
|
|
254
|
+
mutating: true,
|
|
255
|
+
risk: 'high',
|
|
256
|
+
reversible: false,
|
|
257
|
+
supportsBatch: false,
|
|
258
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
259
|
+
inputSchema: withRequiredInput({ manager: { type: 'string', enum: ['winget', 'brew', 'apt', 'npm'] }, packageName: { type: 'string', minLength: 1, maxLength: 200 }, version: { type: 'string', maxLength: 80 } }, ['manager', 'packageName']),
|
|
260
|
+
defaultPermission: taskPermission
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'livedesk.get_network_status',
|
|
264
|
+
description: 'Read bounded network adapter and connectivity status from the selected workstations.',
|
|
265
|
+
category: 'network',
|
|
266
|
+
readOnly: true,
|
|
267
|
+
mutating: false,
|
|
268
|
+
risk: 'low',
|
|
269
|
+
reversible: true,
|
|
270
|
+
supportsBatch: true,
|
|
271
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
272
|
+
inputSchema: readOnlyInput,
|
|
273
|
+
defaultPermission: readTaskPermission
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'livedesk.power_action',
|
|
277
|
+
description: 'Request an explicit power action on the selected workstations.',
|
|
278
|
+
category: 'systemPower',
|
|
279
|
+
readOnly: false,
|
|
280
|
+
mutating: true,
|
|
281
|
+
risk: 'critical',
|
|
282
|
+
reversible: false,
|
|
283
|
+
supportsBatch: true,
|
|
284
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
285
|
+
inputSchema: withRequiredInput({ action: { type: 'string', enum: ['lock', 'logoff', 'sleep', 'restart', 'shutdown'] }, delaySec: { type: 'integer', minimum: 0, maximum: 3600 } }, ['action']),
|
|
286
|
+
defaultPermission: taskPermission
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'livedesk.system_configuration',
|
|
290
|
+
description: 'Apply one explicitly named system configuration action supported by the workstation agent.',
|
|
291
|
+
category: 'systemConfiguration',
|
|
292
|
+
readOnly: false,
|
|
293
|
+
mutating: true,
|
|
294
|
+
risk: 'critical',
|
|
295
|
+
reversible: false,
|
|
296
|
+
supportsBatch: false,
|
|
297
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
298
|
+
inputSchema: withRequiredInput({ action: { type: 'string', enum: ['set-timezone', 'set-environment-variable'] }, name: { type: 'string', minLength: 1, maxLength: 120 }, value: { type: 'string', maxLength: 400 } }, ['action', 'name', 'value']),
|
|
299
|
+
defaultPermission: taskPermission
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'livedesk.collect_logs',
|
|
303
|
+
description: 'Collect bounded recent application and system log lines without reading credential stores.',
|
|
304
|
+
category: 'read',
|
|
305
|
+
readOnly: true,
|
|
306
|
+
mutating: false,
|
|
307
|
+
risk: 'medium',
|
|
308
|
+
reversible: true,
|
|
309
|
+
supportsBatch: true,
|
|
310
|
+
supportedPlatforms: ['windows', 'macos', 'linux'],
|
|
311
|
+
inputSchema: withQueryInput({ source: { type: 'string', maxLength: 120 }, maxLines: { type: 'integer', minimum: 1, maximum: 500 } }),
|
|
312
|
+
defaultPermission: readTaskPermission
|
|
313
|
+
}
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
export const AGENT_TOOL_NAMES = Object.freeze(AGENT_TOOL_DEFINITIONS.map(tool => tool.name));
|
|
317
|
+
|
|
318
|
+
const toolByName = new Map(AGENT_TOOL_DEFINITIONS.map(tool => [tool.name, tool]));
|
|
319
|
+
|
|
320
|
+
export function getAgentToolDefinition(name) {
|
|
321
|
+
return toolByName.get(String(name || '').trim()) || null;
|
|
322
|
+
}
|