livedesk 0.1.208 → 0.1.210
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 +93 -0
- package/hub/src/agents/agent-manager.js +2 -1
- package/hub/src/agents/agent-permission-store.js +78 -0
- package/hub/src/agents/agent-permissions.js +200 -0
- package/hub/src/agents/agent-tool-registry.js +322 -0
- package/hub/src/agents/codex-agent-runtime.js +57 -17
- package/hub/src/agents/codex-mcp-server.js +6 -9
- package/hub/src/remote-hub.js +50 -13
- package/hub/src/server.js +369 -28
- package/package.json +2 -2
- package/web/dist/assets/icons-B4rAeIsB.js +1 -0
- package/web/dist/assets/{index-CtAsVGFt.css → index-ClhUQLYr.css} +1 -1
- package/web/dist/assets/index-DWh-qMz_.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-BIAVgQv7.js +0 -15
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, writeFile } 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_FILE_RECORDS = 2250;
|
|
8
|
+
const MAX_AUDIT_FIELD = 4000;
|
|
9
|
+
|
|
10
|
+
function safeText(value, max = MAX_AUDIT_FIELD) {
|
|
11
|
+
return String(value ?? '').replace(/[\0\r\n]/g, ' ').trim().slice(0, max);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function redact(value, depth = 0) {
|
|
15
|
+
if (depth > 4) return '[truncated]';
|
|
16
|
+
if (Array.isArray(value)) return value.slice(0, 100).map(item => redact(item, depth + 1));
|
|
17
|
+
if (!value || typeof value !== 'object') return typeof value === 'string' ? safeText(value) : value;
|
|
18
|
+
return Object.fromEntries(Object.entries(value).slice(0, 100).map(([key, child]) => [
|
|
19
|
+
key,
|
|
20
|
+
/content|command|script|token|secret|password|credential|api[-_]?key|authorization/i.test(key) ? '[redacted]' : redact(child, depth + 1)
|
|
21
|
+
]));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createAgentAuditStore({ dataDir = path.join(os.homedir(), '.livedesk') } = {}) {
|
|
25
|
+
const filePath = path.join(dataDir, 'agent-audit.jsonl');
|
|
26
|
+
let records = null;
|
|
27
|
+
let rewriteNeeded = false;
|
|
28
|
+
let fileRecordCount = 0;
|
|
29
|
+
let writeQueue = Promise.resolve();
|
|
30
|
+
|
|
31
|
+
async function load() {
|
|
32
|
+
if (records) return records;
|
|
33
|
+
try {
|
|
34
|
+
const allLines = (await readFile(filePath, 'utf8')).split(/\r?\n/).filter(Boolean);
|
|
35
|
+
fileRecordCount = allLines.length;
|
|
36
|
+
rewriteNeeded = allLines.length > MAX_AUDIT_FILE_RECORDS;
|
|
37
|
+
records = allLines.slice(-MAX_AUDIT_RECORDS).map(line => JSON.parse(line)).filter(item => item && typeof item === 'object');
|
|
38
|
+
} catch {
|
|
39
|
+
records = [];
|
|
40
|
+
}
|
|
41
|
+
return records;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalize(event = {}) {
|
|
45
|
+
return {
|
|
46
|
+
auditId: safeText(event.auditId || crypto.randomUUID(), 100),
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
runId: safeText(event.runId, 100),
|
|
49
|
+
deviceIds: Array.isArray(event.deviceIds) ? [...new Set(event.deviceIds.map(item => safeText(item, 128)).filter(Boolean))].slice(0, 500) : [],
|
|
50
|
+
event: safeText(event.event, 100),
|
|
51
|
+
toolName: safeText(event.toolName, 120),
|
|
52
|
+
category: safeText(event.category, 80),
|
|
53
|
+
decision: safeText(event.decision, 40),
|
|
54
|
+
status: safeText(event.status, 40),
|
|
55
|
+
permissionMode: safeText(event.permissionMode, 40),
|
|
56
|
+
policyHash: safeText(event.policyHash, 100),
|
|
57
|
+
argumentsHash: safeText(event.argumentsHash, 100),
|
|
58
|
+
details: redact(event.details || {})
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
async record(event) {
|
|
64
|
+
const record = normalize(event);
|
|
65
|
+
writeQueue = writeQueue.then(async () => {
|
|
66
|
+
const current = await load();
|
|
67
|
+
current.push(record);
|
|
68
|
+
fileRecordCount += 1;
|
|
69
|
+
if (current.length > MAX_AUDIT_RECORDS) {
|
|
70
|
+
current.splice(0, current.length - MAX_AUDIT_RECORDS);
|
|
71
|
+
}
|
|
72
|
+
if (fileRecordCount > MAX_AUDIT_FILE_RECORDS) rewriteNeeded = true;
|
|
73
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
74
|
+
if (rewriteNeeded) {
|
|
75
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
76
|
+
await writeFile(tempPath, `${current.map(item => JSON.stringify(item)).join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
77
|
+
await rename(tempPath, filePath);
|
|
78
|
+
fileRecordCount = current.length;
|
|
79
|
+
rewriteNeeded = false;
|
|
80
|
+
} else {
|
|
81
|
+
await appendFile(filePath, `${JSON.stringify(record)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
await writeQueue;
|
|
85
|
+
return record;
|
|
86
|
+
},
|
|
87
|
+
async list({ runId = '', limit = 200 } = {}) {
|
|
88
|
+
const current = await load();
|
|
89
|
+
const normalizedLimit = Math.max(1, Math.min(500, Number(limit) || 200));
|
|
90
|
+
return current.filter(item => !runId || item.runId === String(runId)).slice(-normalizedLimit).reverse();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -169,6 +169,7 @@ export function createAgentManager({
|
|
|
169
169
|
return runtime.start(input);
|
|
170
170
|
},
|
|
171
171
|
getRun(runId) { return runtime?.get(runId) || null; },
|
|
172
|
-
cancelRun(runId) { return runtime?.cancel(runId) || null; }
|
|
172
|
+
cancelRun(runId) { return runtime?.cancel(runId) || null; },
|
|
173
|
+
cancelAllRuns() { return runtime?.cancelAll?.() || 0; }
|
|
173
174
|
};
|
|
174
175
|
}
|
|
@@ -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,200 @@
|
|
|
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: 'allow',
|
|
48
|
+
serviceControl: 'allow',
|
|
49
|
+
applicationControl: 'allow',
|
|
50
|
+
fileRead: 'allow',
|
|
51
|
+
fileWrite: 'allow',
|
|
52
|
+
fileDelete: 'ask',
|
|
53
|
+
shell: 'ask',
|
|
54
|
+
script: 'allow',
|
|
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
|
+
function isSafeAutoRelativePath(value) {
|
|
155
|
+
const text = String(value || '').trim();
|
|
156
|
+
if (!text || text.length > 600 || text.startsWith('/') || /^[A-Za-z]:[\\/]/.test(text) || text.startsWith('\\\\')) return false;
|
|
157
|
+
const segments = text.replaceAll('\\', '/').split('/').filter(Boolean);
|
|
158
|
+
return segments.length > 0 && !segments.includes('..');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function safeAutoConstraint(tool, args, mode) {
|
|
162
|
+
if (mode !== 'safe-auto') return '';
|
|
163
|
+
if (tool.category === 'fileWrite' && !isSafeAutoRelativePath(args?.path)) {
|
|
164
|
+
return 'Safe Auto file writes are limited to relative paths inside the LiveDesk files directory.';
|
|
165
|
+
}
|
|
166
|
+
if (tool.category === 'script') {
|
|
167
|
+
const scriptPath = String(args?.path || '').trim().toLowerCase();
|
|
168
|
+
if (!isSafeAutoRelativePath(scriptPath) || !scriptPath.replaceAll('\\', '/').startsWith('scripts/') || !/\.(ps1|psm1|sh|bash|py|js|mjs|cmd|bat)$/.test(scriptPath)) {
|
|
169
|
+
return 'Safe Auto scripts must be registered relative script files inside the LiveDesk files directory.';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function evaluateAgentToolPermission({ policy, toolName, arguments: args = {}, deviceIds = [] } = {}) {
|
|
176
|
+
const tool = getAgentToolDefinition(toolName);
|
|
177
|
+
if (!tool) return { decision: 'deny', category: 'unknown', risk: 'critical', reason: 'Unknown or unregistered Agent tool.' };
|
|
178
|
+
const allowed = new Set(normalizeIds(policy?.deviceIds));
|
|
179
|
+
const requested = targetIdsFromArgs(args, deviceIds);
|
|
180
|
+
if (requested.some(deviceId => !allowed.has(deviceId))) {
|
|
181
|
+
return { decision: 'deny', category: tool.category, risk: tool.risk, reason: 'The tool requested a device outside the selected Agent scope.' };
|
|
182
|
+
}
|
|
183
|
+
if (policy?.expiresAt && Date.parse(policy.expiresAt) <= Date.now()) {
|
|
184
|
+
return { decision: 'deny', category: tool.category, risk: 'high', reason: 'The Agent permission policy has expired.' };
|
|
185
|
+
}
|
|
186
|
+
const category = TOOL_CATEGORY_ALIASES[tool.name.replace(/^livedesk\./, '')] || tool.category;
|
|
187
|
+
const constraintError = safeAutoConstraint(tool, args, policy?.mode);
|
|
188
|
+
if (constraintError) {
|
|
189
|
+
return { decision: 'deny', category, risk: tool.risk, reason: constraintError };
|
|
190
|
+
}
|
|
191
|
+
const decision = normalizeDecision(policy?.categories?.[category], 'deny');
|
|
192
|
+
return {
|
|
193
|
+
decision,
|
|
194
|
+
category,
|
|
195
|
+
risk: tool.risk,
|
|
196
|
+
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.'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES = Object.freeze({ ...DEFAULT_CUSTOM_CATEGORIES });
|