neoagent 3.0.0 → 3.0.1-beta.0
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/flutter_app/lib/main_controller.dart +34 -0
- package/flutter_app/lib/main_security.dart +19 -0
- package/lib/schema_migrations.js +224 -0
- package/package.json +1 -1
- package/server/admin/admin.css +29 -0
- package/server/admin/admin.js +81 -1
- package/server/admin/index.html +97 -9
- package/server/db/database.js +57 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +32788 -32752
- package/server/routes/security.js +19 -0
- package/server/routes/settings.js +1 -0
- package/server/routes/skills.js +9 -5
- package/server/services/ai/hooks.js +3 -2
- package/server/services/ai/learning.js +8 -3
- package/server/services/ai/loop/agent_engine_core.js +67 -0
- package/server/services/ai/loop/conversation_loop.js +49 -13
- package/server/services/ai/settings.js +8 -0
- package/server/services/ai/systemPrompt.js +18 -0
- package/server/services/ai/toolEvidence.js +24 -0
- package/server/services/ai/toolRunner.js +262 -55
- package/server/services/ai/tools.js +5 -5
- package/server/services/cli/shell_worker_pool.js +87 -5
- package/server/services/manager.js +16 -0
- package/server/services/memory/manager.js +39 -24
- package/server/services/runtime/manager.js +6 -2
- package/server/services/security/approval_gate_service.js +108 -5
- package/server/services/security/tool_categories.js +113 -4
- package/server/services/security/tool_security_hook.js +7 -2
- package/server/services/skills/runtime.js +2 -0
|
@@ -309,6 +309,34 @@ class MemoryManager {
|
|
|
309
309
|
return resolveAgentId(userId, options?.agentId || options?.agent_id || null);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
_findExactMemory(userId, agentId, memoryHash, scope) {
|
|
313
|
+
return db.prepare(
|
|
314
|
+
`SELECT id FROM memories
|
|
315
|
+
WHERE user_id = ? AND agent_id = ? AND archived = 0
|
|
316
|
+
AND memory_hash = ?
|
|
317
|
+
AND COALESCE(scope_type, 'agent') = ?
|
|
318
|
+
AND COALESCE(scope_id, '') = COALESCE(?, '')
|
|
319
|
+
LIMIT 1`
|
|
320
|
+
).get(userId, agentId, memoryHash, scope.scopeType, scope.scopeId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_reinforceExactMemory(memoryId, importance) {
|
|
324
|
+
db.prepare(
|
|
325
|
+
`UPDATE memories
|
|
326
|
+
SET importance = MAX(importance, ?),
|
|
327
|
+
confidence = MIN(1, confidence + 0.03),
|
|
328
|
+
updated_at = datetime('now')
|
|
329
|
+
WHERE id = ?`
|
|
330
|
+
).run(importance, memoryId);
|
|
331
|
+
db.prepare(
|
|
332
|
+
`UPDATE memory_facts
|
|
333
|
+
SET confidence = MIN(1, confidence + 0.03),
|
|
334
|
+
updated_at = datetime('now')
|
|
335
|
+
WHERE memory_id = ? AND status = 'active'`
|
|
336
|
+
).run(memoryId);
|
|
337
|
+
return memoryId;
|
|
338
|
+
}
|
|
339
|
+
|
|
312
340
|
_backfillMemoryIntelligence(limit = 1000) {
|
|
313
341
|
try {
|
|
314
342
|
const rows = db.prepare(
|
|
@@ -1602,29 +1630,9 @@ class MemoryManager {
|
|
|
1602
1630
|
);
|
|
1603
1631
|
const embedding = embeddingResult?.vector || null;
|
|
1604
1632
|
|
|
1605
|
-
const exact =
|
|
1606
|
-
`SELECT id FROM memories
|
|
1607
|
-
WHERE user_id = ? AND agent_id = ? AND archived = 0
|
|
1608
|
-
AND memory_hash = ?
|
|
1609
|
-
AND COALESCE(scope_type, 'agent') = ?
|
|
1610
|
-
AND COALESCE(scope_id, '') = COALESCE(?, '')
|
|
1611
|
-
LIMIT 1`
|
|
1612
|
-
).get(userId, agentId, memoryHash, scope.scopeType, scope.scopeId);
|
|
1633
|
+
const exact = this._findExactMemory(userId, agentId, memoryHash, scope);
|
|
1613
1634
|
if (exact?.id) {
|
|
1614
|
-
|
|
1615
|
-
`UPDATE memories
|
|
1616
|
-
SET importance = MAX(importance, ?),
|
|
1617
|
-
confidence = MIN(1, confidence + 0.03),
|
|
1618
|
-
updated_at = datetime('now')
|
|
1619
|
-
WHERE id = ?`
|
|
1620
|
-
).run(importance, exact.id);
|
|
1621
|
-
db.prepare(
|
|
1622
|
-
`UPDATE memory_facts
|
|
1623
|
-
SET confidence = MIN(1, confidence + 0.03),
|
|
1624
|
-
updated_at = datetime('now')
|
|
1625
|
-
WHERE memory_id = ? AND status = 'active'`
|
|
1626
|
-
).run(exact.id);
|
|
1627
|
-
return exact.id;
|
|
1635
|
+
return this._reinforceExactMemory(exact.id, importance);
|
|
1628
1636
|
}
|
|
1629
1637
|
|
|
1630
1638
|
const duplicateCandidateIds = embedding
|
|
@@ -1717,8 +1725,8 @@ class MemoryManager {
|
|
|
1717
1725
|
|
|
1718
1726
|
// Save new
|
|
1719
1727
|
const id = uuidv4();
|
|
1720
|
-
db.prepare(
|
|
1721
|
-
`INSERT INTO memories (
|
|
1728
|
+
const insertResult = db.prepare(
|
|
1729
|
+
`INSERT OR IGNORE INTO memories (
|
|
1722
1730
|
id, user_id, agent_id, category, scope_type, scope_id, source_type, source_id, source_label,
|
|
1723
1731
|
stale_after_days, metadata_json, content, summary, importance, confidence, memory_hash, embedding,
|
|
1724
1732
|
embedding_provider, embedding_model, embedding_dimensions, embedded_at
|
|
@@ -1747,6 +1755,13 @@ class MemoryManager {
|
|
|
1747
1755
|
embeddingResult?.dimensions || null,
|
|
1748
1756
|
embedding ? new Date().toISOString() : null,
|
|
1749
1757
|
);
|
|
1758
|
+
if (insertResult.changes === 0) {
|
|
1759
|
+
const existingExact = this._findExactMemory(userId, agentId, memoryHash, scope);
|
|
1760
|
+
if (existingExact?.id) {
|
|
1761
|
+
return this._reinforceExactMemory(existingExact.id, importance);
|
|
1762
|
+
}
|
|
1763
|
+
throw new Error('Memory insert was ignored but no matching memory row was found');
|
|
1764
|
+
}
|
|
1750
1765
|
|
|
1751
1766
|
this._upsertMemoryIntelligence(userId, agentId, id, {
|
|
1752
1767
|
content,
|
|
@@ -161,9 +161,13 @@ class RuntimeManager {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
async shutdown() {
|
|
164
|
-
|
|
164
|
+
const tasks = [
|
|
165
165
|
this.browserBackend?.shutdown?.(),
|
|
166
|
-
]
|
|
166
|
+
];
|
|
167
|
+
if (typeof this.shellWorkerPool?.shutdown === 'function') {
|
|
168
|
+
tasks.push(Promise.resolve().then(() => this.shellWorkerPool.shutdown()));
|
|
169
|
+
}
|
|
170
|
+
await Promise.allSettled(tasks);
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
|
|
@@ -5,28 +5,120 @@ const db = require('../../db/database');
|
|
|
5
5
|
const { getCategoryForTool } = require('./tool_categories');
|
|
6
6
|
|
|
7
7
|
const APPROVAL_TIMEOUT_MS = 30_000;
|
|
8
|
+
const SESSION_GRANT_TTL_MS = 12 * 60 * 60 * 1000;
|
|
8
9
|
|
|
9
10
|
class ApprovalGateService {
|
|
10
11
|
constructor({ io }) {
|
|
11
12
|
this._io = io;
|
|
12
|
-
/** @type {Map<string, { resolve: Function, timer: NodeJS.Timeout }>} */
|
|
13
|
+
/** @type {Map<string, { resolve: Function, timer: NodeJS.Timeout, userId: number|string, runId: string|null, toolName: string, toolArgs: object }>} */
|
|
13
14
|
this._pending = new Map();
|
|
14
15
|
/** @type {Set<string>} key = `${userId}:${runId}:${toolName}` */
|
|
15
16
|
this._sessionGrants = new Set();
|
|
17
|
+
this._loadSessionGrants();
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
hasSessionGrant(userId, runId, toolName) {
|
|
19
21
|
return this._sessionGrants.has(`${userId}:${runId}:${toolName}`);
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
_grantKey(userId, runId, toolName) {
|
|
25
|
+
return `${userId}:${runId}:${toolName}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_loadSessionGrants() {
|
|
29
|
+
try {
|
|
30
|
+
db.prepare(
|
|
31
|
+
`DELETE FROM approval_session_grants
|
|
32
|
+
WHERE expires_at <= datetime('now')`
|
|
33
|
+
).run();
|
|
34
|
+
const rows = db.prepare(
|
|
35
|
+
`SELECT user_id, run_id, tool_name
|
|
36
|
+
FROM approval_session_grants
|
|
37
|
+
WHERE expires_at > datetime('now')`
|
|
38
|
+
).all();
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
this._sessionGrants.add(this._grantKey(row.user_id, row.run_id, row.tool_name));
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.warn('[ApprovalGate] Failed to load session grants:', err.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_persistSessionGrant(userId, runId, toolName) {
|
|
48
|
+
const expiresAt = new Date(Date.now() + SESSION_GRANT_TTL_MS).toISOString();
|
|
49
|
+
db.prepare(
|
|
50
|
+
`INSERT INTO approval_session_grants (
|
|
51
|
+
user_id, run_id, tool_name, expires_at, updated_at
|
|
52
|
+
) VALUES (?, ?, ?, ?, datetime('now'))
|
|
53
|
+
ON CONFLICT(user_id, run_id, tool_name) DO UPDATE SET
|
|
54
|
+
expires_at = excluded.expires_at,
|
|
55
|
+
updated_at = excluded.updated_at`
|
|
56
|
+
).run(userId, runId, toolName, expiresAt);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_persistPendingApproval(approvalId, userId, runId, toolName, toolArgs, category, expiresAt) {
|
|
60
|
+
db.prepare(
|
|
61
|
+
`INSERT INTO pending_approvals (
|
|
62
|
+
id, user_id, run_id, tool_name, tool_args_json, category, status, expires_at, updated_at
|
|
63
|
+
) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, datetime('now'))`
|
|
64
|
+
).run(
|
|
65
|
+
approvalId,
|
|
66
|
+
userId,
|
|
67
|
+
runId,
|
|
68
|
+
toolName,
|
|
69
|
+
JSON.stringify(toolArgs ?? {}),
|
|
70
|
+
category,
|
|
71
|
+
expiresAt,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_updatePendingApprovalStatus(approvalId, status, scope = null) {
|
|
76
|
+
db.prepare(
|
|
77
|
+
`UPDATE pending_approvals
|
|
78
|
+
SET status = ?,
|
|
79
|
+
scope = COALESCE(?, scope),
|
|
80
|
+
decided_at = CASE WHEN ? = 'pending' THEN decided_at ELSE COALESCE(decided_at, datetime('now')) END,
|
|
81
|
+
updated_at = datetime('now')
|
|
82
|
+
WHERE id = ?`
|
|
83
|
+
).run(status, scope, status, approvalId);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getStoredApproval(approvalId, userId) {
|
|
87
|
+
try {
|
|
88
|
+
return db.prepare(
|
|
89
|
+
`SELECT id, run_id, tool_name, status, scope, expires_at, decided_at
|
|
90
|
+
FROM pending_approvals
|
|
91
|
+
WHERE id = ? AND user_id = ?`
|
|
92
|
+
).get(approvalId, userId);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
shutdown(reason = 'Approval expired because the server restarted.') {
|
|
99
|
+
for (const [approvalId, entry] of this._pending.entries()) {
|
|
100
|
+
clearTimeout(entry.timer);
|
|
101
|
+
this._pending.delete(approvalId);
|
|
102
|
+
this._updatePendingApprovalStatus(approvalId, 'expired', 'once');
|
|
103
|
+
this._io.to(`user:${entry.userId}`).emit('tool:approval_resolved', {
|
|
104
|
+
approvalId,
|
|
105
|
+
decision: 'expired',
|
|
106
|
+
reason,
|
|
107
|
+
});
|
|
108
|
+
this._logDecision(entry.userId, entry.runId, entry.toolName, entry.toolArgs, 'timeout', 'once');
|
|
109
|
+
entry.resolve('expired');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
22
113
|
/**
|
|
23
114
|
* Emits tool:approval_required and waits for a decision.
|
|
24
|
-
* Resolves to 'approved', 'denied', or '
|
|
115
|
+
* Resolves to 'approved', 'denied', 'timeout', or 'expired'.
|
|
25
116
|
*/
|
|
26
117
|
requestApproval(userId, runId, toolName, toolArgs) {
|
|
27
118
|
const approvalId = randomUUID();
|
|
28
119
|
const expiresAt = new Date(Date.now() + APPROVAL_TIMEOUT_MS).toISOString();
|
|
29
120
|
const category = getCategoryForTool(toolName, toolArgs) ?? 'unknown';
|
|
121
|
+
this._persistPendingApproval(approvalId, userId, runId, toolName, toolArgs, category, expiresAt);
|
|
30
122
|
|
|
31
123
|
const payload = { approvalId, runId, toolName, toolArgs, category, expiresAt };
|
|
32
124
|
this._io.to(`user:${userId}`).emit('tool:approval_required', payload);
|
|
@@ -35,6 +127,7 @@ class ApprovalGateService {
|
|
|
35
127
|
const timer = setTimeout(() => {
|
|
36
128
|
if (this._pending.has(approvalId)) {
|
|
37
129
|
this._pending.delete(approvalId);
|
|
130
|
+
this._updatePendingApprovalStatus(approvalId, 'timeout', 'once');
|
|
38
131
|
this._io.to(`user:${userId}`).emit('tool:approval_resolved', {
|
|
39
132
|
approvalId, decision: 'timeout',
|
|
40
133
|
});
|
|
@@ -43,7 +136,14 @@ class ApprovalGateService {
|
|
|
43
136
|
}
|
|
44
137
|
}, APPROVAL_TIMEOUT_MS);
|
|
45
138
|
|
|
46
|
-
this._pending.set(approvalId, {
|
|
139
|
+
this._pending.set(approvalId, {
|
|
140
|
+
resolve,
|
|
141
|
+
timer,
|
|
142
|
+
userId,
|
|
143
|
+
runId,
|
|
144
|
+
toolName,
|
|
145
|
+
toolArgs: toolArgs ?? {},
|
|
146
|
+
});
|
|
47
147
|
});
|
|
48
148
|
}
|
|
49
149
|
|
|
@@ -63,14 +163,17 @@ class ApprovalGateService {
|
|
|
63
163
|
const normalizedScope = ['once', 'session', 'always'].includes(scope) ? scope : 'once';
|
|
64
164
|
|
|
65
165
|
if (normalizedDecision === 'approved' && normalizedScope === 'session') {
|
|
66
|
-
this._sessionGrants.add(
|
|
166
|
+
this._sessionGrants.add(this._grantKey(userId, runId, toolName));
|
|
167
|
+
this._persistSessionGrant(userId, runId, toolName);
|
|
67
168
|
}
|
|
68
169
|
// 'always' scope is handled by the route (sets policy to 'allow') and
|
|
69
170
|
// also acts as a session grant for the current run.
|
|
70
171
|
if (normalizedDecision === 'approved' && normalizedScope === 'always') {
|
|
71
|
-
this._sessionGrants.add(
|
|
172
|
+
this._sessionGrants.add(this._grantKey(userId, runId, toolName));
|
|
173
|
+
this._persistSessionGrant(userId, runId, toolName);
|
|
72
174
|
}
|
|
73
175
|
|
|
176
|
+
this._updatePendingApprovalStatus(approvalId, normalizedDecision, normalizedScope);
|
|
74
177
|
const logScope = normalizedScope === 'always' ? 'session' : normalizedScope;
|
|
75
178
|
this._logDecision(userId, runId, toolName, toolArgs, normalizedDecision, logScope);
|
|
76
179
|
this._io.to(`user:${userId}`).emit('tool:approval_resolved', { approvalId, decision: normalizedDecision });
|
|
@@ -27,6 +27,7 @@ const TOOL_CATEGORIES = {
|
|
|
27
27
|
'update_ai_widget',
|
|
28
28
|
'delete_ai_widget',
|
|
29
29
|
],
|
|
30
|
+
external: [],
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
// Tools that bypass all policy checks — read-only or always safe
|
|
@@ -71,6 +72,98 @@ const SAFE_TOOLS = new Set([
|
|
|
71
72
|
'recordings_get',
|
|
72
73
|
]);
|
|
73
74
|
|
|
75
|
+
const BUILT_IN_TOOLS = new Set([
|
|
76
|
+
'execute_command',
|
|
77
|
+
'browser_navigate',
|
|
78
|
+
'browser_click',
|
|
79
|
+
'browser_type',
|
|
80
|
+
'browser_extract',
|
|
81
|
+
'browser_screenshot',
|
|
82
|
+
'browser_evaluate',
|
|
83
|
+
'desktop_list_devices',
|
|
84
|
+
'desktop_select_device',
|
|
85
|
+
'desktop_observe',
|
|
86
|
+
'desktop_click',
|
|
87
|
+
'desktop_drag',
|
|
88
|
+
'desktop_scroll',
|
|
89
|
+
'desktop_type',
|
|
90
|
+
'desktop_press_key',
|
|
91
|
+
'desktop_launch_app',
|
|
92
|
+
'desktop_get_tree',
|
|
93
|
+
'android_start_emulator',
|
|
94
|
+
'android_stop_emulator',
|
|
95
|
+
'android_list_devices',
|
|
96
|
+
'android_open_app',
|
|
97
|
+
'android_open_intent',
|
|
98
|
+
'android_tap',
|
|
99
|
+
'android_long_press',
|
|
100
|
+
'android_type',
|
|
101
|
+
'android_swipe',
|
|
102
|
+
'android_press_key',
|
|
103
|
+
'android_wait_for',
|
|
104
|
+
'android_observe',
|
|
105
|
+
'android_dump_ui',
|
|
106
|
+
'android_screenshot',
|
|
107
|
+
'android_list_apps',
|
|
108
|
+
'android_install_apk',
|
|
109
|
+
'android_shell',
|
|
110
|
+
'web_search',
|
|
111
|
+
'memory_save',
|
|
112
|
+
'memory_recall',
|
|
113
|
+
'session_search',
|
|
114
|
+
'memory_update_core',
|
|
115
|
+
'memory_write',
|
|
116
|
+
'memory_read',
|
|
117
|
+
'make_call',
|
|
118
|
+
'send_message',
|
|
119
|
+
'read_file',
|
|
120
|
+
'read_files',
|
|
121
|
+
'write_file',
|
|
122
|
+
'edit_file',
|
|
123
|
+
'replace_file_range',
|
|
124
|
+
'list_directory',
|
|
125
|
+
'search_files',
|
|
126
|
+
'code_navigate',
|
|
127
|
+
'query_structured_data',
|
|
128
|
+
'http_request',
|
|
129
|
+
'create_skill',
|
|
130
|
+
'list_skills',
|
|
131
|
+
'update_skill',
|
|
132
|
+
'delete_skill',
|
|
133
|
+
'think',
|
|
134
|
+
'activate_tools',
|
|
135
|
+
'spawn_subagent',
|
|
136
|
+
'delegate_to_agent',
|
|
137
|
+
'list_subagents',
|
|
138
|
+
'wait_subagent',
|
|
139
|
+
'cancel_subagent',
|
|
140
|
+
'notify_user',
|
|
141
|
+
'create_task',
|
|
142
|
+
'list_tasks',
|
|
143
|
+
'delete_task',
|
|
144
|
+
'update_task',
|
|
145
|
+
'create_ai_widget',
|
|
146
|
+
'list_ai_widgets',
|
|
147
|
+
'update_ai_widget',
|
|
148
|
+
'delete_ai_widget',
|
|
149
|
+
'mcp_add_server',
|
|
150
|
+
'mcp_list_servers',
|
|
151
|
+
'mcp_remove_server',
|
|
152
|
+
'generate_image',
|
|
153
|
+
'generate_table',
|
|
154
|
+
'generate_graph',
|
|
155
|
+
'analyze_image',
|
|
156
|
+
'ocr_extract',
|
|
157
|
+
'read_health_data',
|
|
158
|
+
'recordings_list',
|
|
159
|
+
'recordings_get',
|
|
160
|
+
'recordings_search',
|
|
161
|
+
'social_video_extract',
|
|
162
|
+
'task_complete',
|
|
163
|
+
'send_interim_update',
|
|
164
|
+
'save_widget_snapshot',
|
|
165
|
+
]);
|
|
166
|
+
|
|
74
167
|
// category → policy for users with no DB row yet
|
|
75
168
|
const DEFAULT_POLICY = {
|
|
76
169
|
shell: 'require_approval',
|
|
@@ -80,6 +173,7 @@ const DEFAULT_POLICY = {
|
|
|
80
173
|
browser_privileged: 'require_approval',
|
|
81
174
|
network_write: 'require_approval',
|
|
82
175
|
skill_mutation: 'deny',
|
|
176
|
+
external: 'require_approval',
|
|
83
177
|
};
|
|
84
178
|
|
|
85
179
|
// Reverse map: tool → category, built once
|
|
@@ -91,15 +185,30 @@ for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {
|
|
|
91
185
|
}
|
|
92
186
|
|
|
93
187
|
/**
|
|
94
|
-
* Returns the category for a tool
|
|
95
|
-
*
|
|
188
|
+
* Returns the category for a tool.
|
|
189
|
+
* - Known built-in tools may return null when they are read-only or otherwise
|
|
190
|
+
* intentionally left uncategorized.
|
|
191
|
+
* - Unknown tool names are treated as external capabilities and must never
|
|
192
|
+
* silently bypass policy checks.
|
|
96
193
|
*/
|
|
97
194
|
function getCategoryForTool(toolName, toolArgs = {}) {
|
|
98
195
|
if (toolName === 'http_request') {
|
|
99
196
|
const method = (toolArgs.method || 'GET').toUpperCase();
|
|
100
197
|
return ['GET', 'HEAD', 'OPTIONS'].includes(method) ? null : 'network_write';
|
|
101
198
|
}
|
|
102
|
-
|
|
199
|
+
if (_toolToCategory[toolName]) {
|
|
200
|
+
return _toolToCategory[toolName];
|
|
201
|
+
}
|
|
202
|
+
if (BUILT_IN_TOOLS.has(toolName) || SAFE_TOOLS.has(toolName)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return 'external';
|
|
103
206
|
}
|
|
104
207
|
|
|
105
|
-
module.exports = {
|
|
208
|
+
module.exports = {
|
|
209
|
+
TOOL_CATEGORIES,
|
|
210
|
+
SAFE_TOOLS,
|
|
211
|
+
DEFAULT_POLICY,
|
|
212
|
+
BUILT_IN_TOOLS,
|
|
213
|
+
getCategoryForTool,
|
|
214
|
+
};
|
|
@@ -63,10 +63,15 @@ function registerToolSecurityHooks(toolPolicyService, approvalGateService) {
|
|
|
63
63
|
if (decision === 'approved') return;
|
|
64
64
|
|
|
65
65
|
const isTimeout = decision === 'timeout';
|
|
66
|
+
const isExpired = decision === 'expired';
|
|
66
67
|
return {
|
|
67
68
|
block: true,
|
|
68
|
-
blocked_by:
|
|
69
|
-
|
|
69
|
+
blocked_by: isExpired
|
|
70
|
+
? 'approval_expired'
|
|
71
|
+
: (isTimeout ? 'approval_timeout' : 'user_denied'),
|
|
72
|
+
reason: isExpired
|
|
73
|
+
? `Approval for "${toolName}" expired because the server restarted or the run was interrupted. Do not retry unless the user explicitly asks you to try again.`
|
|
74
|
+
: isTimeout
|
|
70
75
|
? `Approval for "${toolName}" timed out — the user did not respond within 30 seconds. ` +
|
|
71
76
|
`Do not retry unless the user explicitly asks you to try again.`
|
|
72
77
|
: `The user denied the use of "${toolName}". Do not retry this tool call in this run.`,
|
|
@@ -21,6 +21,8 @@ function serializeInstalledSkill(skill) {
|
|
|
21
21
|
autoCreated: skill.metadata?.auto_created === true,
|
|
22
22
|
filePath: skill.filePath,
|
|
23
23
|
storeId: skill.metadata?.store_id || '',
|
|
24
|
+
readOnly: skill.readOnly === true,
|
|
25
|
+
ownerType: skill.ownerType || 'global',
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|