livedesk 0.1.207 → 0.1.209
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/hub/src/agents/agent-audit-store.js +76 -0
- package/hub/src/agents/agent-manager.js +3 -1
- package/hub/src/agents/agent-permission-store.js +78 -0
- package/hub/src/agents/agent-permissions.js +175 -0
- package/hub/src/agents/agent-tool-registry.js +322 -0
- package/hub/src/agents/codex-agent-runtime.js +64 -19
- package/hub/src/agents/codex-mcp-server.js +6 -9
- package/hub/src/remote-hub.js +50 -13
- package/hub/src/server.js +342 -27
- package/package.json +1 -1
- package/web/dist/assets/icons-B4rAeIsB.js +1 -0
- package/web/dist/assets/{index-DIA3wv8F.css → index-CJSXSfSl.css} +1 -1
- package/web/dist/assets/index-D8Z7w2jS.js +15 -0
- package/web/dist/assets/{react-iKD0ILd2.js → react-BlCG6J-q.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/dist/assets/icons-IUb7hANW.js +0 -1
- package/web/dist/assets/index-CHVI5IbK.js +0 -15
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const MAX_AUDIT_RECORDS = 2000;
|
|
7
|
+
const MAX_AUDIT_FIELD = 4000;
|
|
8
|
+
|
|
9
|
+
function safeText(value, max = MAX_AUDIT_FIELD) {
|
|
10
|
+
return String(value ?? '').replace(/[\0\r\n]/g, ' ').trim().slice(0, max);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function redact(value, depth = 0) {
|
|
14
|
+
if (depth > 4) return '[truncated]';
|
|
15
|
+
if (Array.isArray(value)) return value.slice(0, 100).map(item => redact(item, depth + 1));
|
|
16
|
+
if (!value || typeof value !== 'object') return typeof value === 'string' ? safeText(value) : value;
|
|
17
|
+
return Object.fromEntries(Object.entries(value).slice(0, 100).map(([key, child]) => [
|
|
18
|
+
key,
|
|
19
|
+
/content|command|script|token|secret|password|credential|api[-_]?key|authorization/i.test(key) ? '[redacted]' : redact(child, depth + 1)
|
|
20
|
+
]));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createAgentAuditStore({ dataDir = path.join(os.homedir(), '.livedesk') } = {}) {
|
|
24
|
+
const filePath = path.join(dataDir, 'agent-audit.jsonl');
|
|
25
|
+
let records = null;
|
|
26
|
+
let writeQueue = Promise.resolve();
|
|
27
|
+
|
|
28
|
+
async function load() {
|
|
29
|
+
if (records) return records;
|
|
30
|
+
try {
|
|
31
|
+
const lines = (await readFile(filePath, 'utf8')).split(/\r?\n/).filter(Boolean).slice(-MAX_AUDIT_RECORDS);
|
|
32
|
+
records = lines.map(line => JSON.parse(line)).filter(item => item && typeof item === 'object');
|
|
33
|
+
} catch {
|
|
34
|
+
records = [];
|
|
35
|
+
}
|
|
36
|
+
return records;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalize(event = {}) {
|
|
40
|
+
return {
|
|
41
|
+
auditId: safeText(event.auditId || crypto.randomUUID(), 100),
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
runId: safeText(event.runId, 100),
|
|
44
|
+
deviceIds: Array.isArray(event.deviceIds) ? [...new Set(event.deviceIds.map(item => safeText(item, 128)).filter(Boolean))].slice(0, 500) : [],
|
|
45
|
+
event: safeText(event.event, 100),
|
|
46
|
+
toolName: safeText(event.toolName, 120),
|
|
47
|
+
category: safeText(event.category, 80),
|
|
48
|
+
decision: safeText(event.decision, 40),
|
|
49
|
+
status: safeText(event.status, 40),
|
|
50
|
+
permissionMode: safeText(event.permissionMode, 40),
|
|
51
|
+
policyHash: safeText(event.policyHash, 100),
|
|
52
|
+
argumentsHash: safeText(event.argumentsHash, 100),
|
|
53
|
+
details: redact(event.details || {})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
async record(event) {
|
|
59
|
+
const record = normalize(event);
|
|
60
|
+
const current = await load();
|
|
61
|
+
current.push(record);
|
|
62
|
+
if (current.length > MAX_AUDIT_RECORDS) current.splice(0, current.length - MAX_AUDIT_RECORDS);
|
|
63
|
+
writeQueue = writeQueue.then(async () => {
|
|
64
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
65
|
+
await appendFile(filePath, `${JSON.stringify(record)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
66
|
+
});
|
|
67
|
+
await writeQueue;
|
|
68
|
+
return record;
|
|
69
|
+
},
|
|
70
|
+
async list({ runId = '', limit = 200 } = {}) {
|
|
71
|
+
const current = await load();
|
|
72
|
+
const normalizedLimit = Math.max(1, Math.min(500, Number(limit) || 200));
|
|
73
|
+
return current.filter(item => !runId || item.runId === String(runId)).slice(-normalizedLimit).reverse();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -68,7 +68,9 @@ export function createAgentManager({
|
|
|
68
68
|
restrictedEnvironment: false,
|
|
69
69
|
livedeskMcpOnly: false,
|
|
70
70
|
selectedDeviceScopeEnforced: false,
|
|
71
|
-
failClosed: true
|
|
71
|
+
failClosed: true,
|
|
72
|
+
verified: false,
|
|
73
|
+
state: 'unavailable'
|
|
72
74
|
};
|
|
73
75
|
return {
|
|
74
76
|
...exposedSettings,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES, normalizeAgentPermissionCategories } from './agent-permissions.js';
|
|
6
|
+
|
|
7
|
+
function safePolicyId(value) {
|
|
8
|
+
const id = String(value || '').trim().replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 80);
|
|
9
|
+
return id || 'custom-default';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function publicPolicy(policy) {
|
|
13
|
+
return {
|
|
14
|
+
id: policy.id,
|
|
15
|
+
name: policy.name,
|
|
16
|
+
categories: { ...policy.categories },
|
|
17
|
+
createdAt: policy.createdAt,
|
|
18
|
+
updatedAt: policy.updatedAt
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createAgentPermissionStore({ dataDir = path.join(os.homedir(), '.livedesk') } = {}) {
|
|
23
|
+
const filePath = path.join(dataDir, 'agent-permission-policies.json');
|
|
24
|
+
let policies;
|
|
25
|
+
|
|
26
|
+
async function load() {
|
|
27
|
+
if (policies) return policies;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(await readFile(filePath, 'utf8'));
|
|
30
|
+
policies = new Map(Object.entries(parsed && typeof parsed === 'object' ? parsed : {}).map(([id, value]) => [safePolicyId(id), {
|
|
31
|
+
id: safePolicyId(id),
|
|
32
|
+
name: String(value?.name || id).slice(0, 120),
|
|
33
|
+
categories: normalizeAgentPermissionCategories(value?.categories, DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES),
|
|
34
|
+
createdAt: String(value?.createdAt || new Date().toISOString()),
|
|
35
|
+
updatedAt: String(value?.updatedAt || new Date().toISOString())
|
|
36
|
+
}]));
|
|
37
|
+
} catch {
|
|
38
|
+
policies = new Map();
|
|
39
|
+
}
|
|
40
|
+
if (!policies.has('custom-default')) {
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
policies.set('custom-default', { id: 'custom-default', name: 'Custom default', categories: { ...DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES }, createdAt: now, updatedAt: now });
|
|
43
|
+
}
|
|
44
|
+
return policies;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function save() {
|
|
48
|
+
const current = await load();
|
|
49
|
+
const value = Object.fromEntries([...current].map(([id, policy]) => [id, policy]));
|
|
50
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
51
|
+
const temporaryPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
52
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
53
|
+
await rename(temporaryPath, filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
async list() { return [...await load()].map(([, policy]) => publicPolicy(policy)); },
|
|
58
|
+
async get(id = 'custom-default') {
|
|
59
|
+
const current = await load();
|
|
60
|
+
return publicPolicy(current.get(safePolicyId(id)) || current.get('custom-default'));
|
|
61
|
+
},
|
|
62
|
+
async upsert({ id = 'custom-default', name = 'Custom default', categories = {} } = {}) {
|
|
63
|
+
const current = await load();
|
|
64
|
+
const policyId = safePolicyId(id);
|
|
65
|
+
const previous = current.get(policyId);
|
|
66
|
+
const now = new Date().toISOString();
|
|
67
|
+
current.set(policyId, {
|
|
68
|
+
id: policyId,
|
|
69
|
+
name: String(name || policyId).slice(0, 120),
|
|
70
|
+
categories: normalizeAgentPermissionCategories(categories, previous?.categories || DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES),
|
|
71
|
+
createdAt: previous?.createdAt || now,
|
|
72
|
+
updatedAt: now
|
|
73
|
+
});
|
|
74
|
+
await save();
|
|
75
|
+
return publicPolicy(current.get(policyId));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getAgentToolDefinition } from './agent-tool-registry.js';
|
|
3
|
+
|
|
4
|
+
export const AGENT_PERMISSION_MODES = Object.freeze(['ask', 'safe-auto', 'full-access', 'custom']);
|
|
5
|
+
export const AGENT_PERMISSION_DECISIONS = Object.freeze(['allow', 'ask', 'deny']);
|
|
6
|
+
export const AGENT_PERMISSION_CATEGORIES = Object.freeze([
|
|
7
|
+
'read',
|
|
8
|
+
'processControl',
|
|
9
|
+
'serviceControl',
|
|
10
|
+
'applicationControl',
|
|
11
|
+
'fileRead',
|
|
12
|
+
'fileWrite',
|
|
13
|
+
'fileDelete',
|
|
14
|
+
'shell',
|
|
15
|
+
'script',
|
|
16
|
+
'softwareInstall',
|
|
17
|
+
'network',
|
|
18
|
+
'systemPower',
|
|
19
|
+
'systemConfiguration',
|
|
20
|
+
'userAccount'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const DEFAULT_CUSTOM_CATEGORIES = Object.freeze({
|
|
24
|
+
read: 'allow',
|
|
25
|
+
processControl: 'ask',
|
|
26
|
+
serviceControl: 'ask',
|
|
27
|
+
applicationControl: 'ask',
|
|
28
|
+
fileRead: 'ask',
|
|
29
|
+
fileWrite: 'ask',
|
|
30
|
+
fileDelete: 'ask',
|
|
31
|
+
shell: 'ask',
|
|
32
|
+
script: 'ask',
|
|
33
|
+
softwareInstall: 'ask',
|
|
34
|
+
network: 'ask',
|
|
35
|
+
systemPower: 'ask',
|
|
36
|
+
systemConfiguration: 'ask',
|
|
37
|
+
userAccount: 'deny'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const PRESET_CATEGORIES = Object.freeze({
|
|
41
|
+
ask: {
|
|
42
|
+
...DEFAULT_CUSTOM_CATEGORIES,
|
|
43
|
+
userAccount: 'ask'
|
|
44
|
+
},
|
|
45
|
+
'safe-auto': {
|
|
46
|
+
read: 'allow',
|
|
47
|
+
processControl: 'ask',
|
|
48
|
+
serviceControl: 'ask',
|
|
49
|
+
applicationControl: 'ask',
|
|
50
|
+
fileRead: 'allow',
|
|
51
|
+
fileWrite: 'ask',
|
|
52
|
+
fileDelete: 'deny',
|
|
53
|
+
shell: 'ask',
|
|
54
|
+
script: 'ask',
|
|
55
|
+
softwareInstall: 'ask',
|
|
56
|
+
network: 'allow',
|
|
57
|
+
systemPower: 'ask',
|
|
58
|
+
systemConfiguration: 'ask',
|
|
59
|
+
userAccount: 'deny'
|
|
60
|
+
},
|
|
61
|
+
'full-access': {
|
|
62
|
+
read: 'allow',
|
|
63
|
+
processControl: 'allow',
|
|
64
|
+
serviceControl: 'allow',
|
|
65
|
+
applicationControl: 'allow',
|
|
66
|
+
fileRead: 'allow',
|
|
67
|
+
fileWrite: 'allow',
|
|
68
|
+
fileDelete: 'allow',
|
|
69
|
+
shell: 'allow',
|
|
70
|
+
script: 'allow',
|
|
71
|
+
softwareInstall: 'allow',
|
|
72
|
+
network: 'allow',
|
|
73
|
+
systemPower: 'allow',
|
|
74
|
+
systemConfiguration: 'allow',
|
|
75
|
+
userAccount: 'ask'
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const TOOL_CATEGORY_ALIASES = Object.freeze({
|
|
80
|
+
'process.list': 'read',
|
|
81
|
+
'service.status': 'read',
|
|
82
|
+
'system.health': 'read',
|
|
83
|
+
'gpu.status': 'read',
|
|
84
|
+
'disk.status': 'read',
|
|
85
|
+
'diagnostics.collect': 'read'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function normalizeIds(value) {
|
|
89
|
+
return [...new Set((Array.isArray(value) ? value : [])
|
|
90
|
+
.map(item => String(item || '').trim())
|
|
91
|
+
.filter(Boolean))].sort().slice(0, 500);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeDecision(value, fallback = 'ask') {
|
|
95
|
+
return AGENT_PERMISSION_DECISIONS.includes(String(value || '').toLowerCase())
|
|
96
|
+
? String(value).toLowerCase()
|
|
97
|
+
: fallback;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function normalizeAgentPermissionCategories(value, fallback = DEFAULT_CUSTOM_CATEGORIES) {
|
|
101
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
102
|
+
return Object.fromEntries(AGENT_PERMISSION_CATEGORIES.map(category => [
|
|
103
|
+
category,
|
|
104
|
+
normalizeDecision(source[category], fallback[category] || 'ask')
|
|
105
|
+
]));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getPresetAgentPermissionCategories(mode, customCategories) {
|
|
109
|
+
const normalizedMode = AGENT_PERMISSION_MODES.includes(mode) ? mode : 'ask';
|
|
110
|
+
if (normalizedMode === 'custom') return normalizeAgentPermissionCategories(customCategories);
|
|
111
|
+
return normalizeAgentPermissionCategories(PRESET_CATEGORIES[normalizedMode] || PRESET_CATEGORIES.ask);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createAgentPermissionPolicy({ mode = 'ask', deviceIds = [], customCategories, maxToolCalls, now = new Date() } = {}) {
|
|
115
|
+
const normalizedMode = AGENT_PERMISSION_MODES.includes(String(mode)) ? String(mode) : 'ask';
|
|
116
|
+
const categories = getPresetAgentPermissionCategories(normalizedMode, customCategories);
|
|
117
|
+
const defaultMax = normalizedMode === 'ask' ? 30 : normalizedMode === 'safe-auto' ? 50 : normalizedMode === 'full-access' ? 100 : 50;
|
|
118
|
+
const requestedMax = Number(maxToolCalls);
|
|
119
|
+
const boundedMax = Number.isFinite(requestedMax) ? Math.max(1, Math.min(100, Math.floor(requestedMax))) : defaultMax;
|
|
120
|
+
const createdAt = new Date(now).toISOString();
|
|
121
|
+
const expiresAt = new Date(new Date(now).getTime() + 60 * 60 * 1000).toISOString();
|
|
122
|
+
return {
|
|
123
|
+
policyVersion: 1,
|
|
124
|
+
mode: normalizedMode,
|
|
125
|
+
deviceIds: normalizeIds(deviceIds),
|
|
126
|
+
categories,
|
|
127
|
+
maxToolCalls: boundedMax,
|
|
128
|
+
expiresAt,
|
|
129
|
+
createdAt
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function hashAgentPermissionPolicy(policy) {
|
|
134
|
+
const canonical = JSON.stringify({
|
|
135
|
+
policyVersion: policy?.policyVersion,
|
|
136
|
+
mode: policy?.mode,
|
|
137
|
+
deviceIds: normalizeIds(policy?.deviceIds),
|
|
138
|
+
categories: normalizeAgentPermissionCategories(policy?.categories),
|
|
139
|
+
allowedAppIds: normalizeIds(policy?.allowedAppIds),
|
|
140
|
+
allowedServiceNames: normalizeIds(policy?.allowedServiceNames),
|
|
141
|
+
allowedFileRoots: normalizeIds(policy?.allowedFileRoots),
|
|
142
|
+
allowedDomains: normalizeIds(policy?.allowedDomains),
|
|
143
|
+
allowedExecutables: normalizeIds(policy?.allowedExecutables),
|
|
144
|
+
maxToolCalls: Number(policy?.maxToolCalls) || 0,
|
|
145
|
+
expiresAt: String(policy?.expiresAt || '')
|
|
146
|
+
});
|
|
147
|
+
return crypto.createHash('sha256').update(canonical).digest('hex');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function targetIdsFromArgs(args, fallback = []) {
|
|
151
|
+
return normalizeIds(args?.deviceIds?.length ? args.deviceIds : fallback);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function evaluateAgentToolPermission({ policy, toolName, arguments: args = {}, deviceIds = [] } = {}) {
|
|
155
|
+
const tool = getAgentToolDefinition(toolName);
|
|
156
|
+
if (!tool) return { decision: 'deny', category: 'unknown', risk: 'critical', reason: 'Unknown or unregistered Agent tool.' };
|
|
157
|
+
const allowed = new Set(normalizeIds(policy?.deviceIds));
|
|
158
|
+
const requested = targetIdsFromArgs(args, deviceIds);
|
|
159
|
+
if (requested.some(deviceId => !allowed.has(deviceId))) {
|
|
160
|
+
return { decision: 'deny', category: tool.category, risk: tool.risk, reason: 'The tool requested a device outside the selected Agent scope.' };
|
|
161
|
+
}
|
|
162
|
+
if (policy?.expiresAt && Date.parse(policy.expiresAt) <= Date.now()) {
|
|
163
|
+
return { decision: 'deny', category: tool.category, risk: 'high', reason: 'The Agent permission policy has expired.' };
|
|
164
|
+
}
|
|
165
|
+
const category = TOOL_CATEGORY_ALIASES[tool.name.replace(/^livedesk\./, '')] || tool.category;
|
|
166
|
+
const decision = normalizeDecision(policy?.categories?.[category], 'deny');
|
|
167
|
+
return {
|
|
168
|
+
decision,
|
|
169
|
+
category,
|
|
170
|
+
risk: tool.risk,
|
|
171
|
+
reason: decision === 'allow' ? `Allowed by ${policy?.mode || 'current'} policy.` : decision === 'ask' ? 'This operation requires approval in the current policy.' : 'This operation is denied by the current policy.'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES = Object.freeze({ ...DEFAULT_CUSTOM_CATEGORIES });
|
|
@@ -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
|
+
}
|