sentinel-agentos 0.1.3 → 0.2.1
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 +667 -0
- package/dist/cli.js +102 -0
- package/dist/cli.js.map +1 -1
- package/dist/guard/sandbox.d.ts.map +1 -1
- package/dist/guard/sandbox.js +2 -2
- package/dist/guard/sandbox.js.map +1 -1
- package/dist/guard/snapshot-verify.d.ts +2 -2
- package/dist/guard/snapshot-verify.d.ts.map +1 -1
- package/dist/guard/snapshot-verify.js +20 -27
- package/dist/guard/snapshot-verify.js.map +1 -1
- package/dist/memory/semantic.d.ts +7 -0
- package/dist/memory/semantic.d.ts.map +1 -1
- package/dist/memory/semantic.js +30 -1
- package/dist/memory/semantic.js.map +1 -1
- package/package.json +8 -3
- package/scripts/sentinel-light.js +222 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sentinel-agentos",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Sentinel AgentOS — 确定性 Guard 层 + 分层记忆 + 自动评估,让任何 Agent 变得可靠、可审计、可改进",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist/",
|
|
28
|
+
"scripts/sentinel-light.js",
|
|
28
29
|
"README.md",
|
|
29
30
|
"LICENSE"
|
|
30
31
|
],
|
|
@@ -49,16 +50,20 @@
|
|
|
49
50
|
"@typescript-eslint/parser": "^7.0.0",
|
|
50
51
|
"eslint": "^8.0.0",
|
|
51
52
|
"eslint-config-prettier": "^9.0.0",
|
|
53
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
52
54
|
"jest": "^29.0.0",
|
|
53
55
|
"prettier": "^3.0.0",
|
|
54
56
|
"ts-jest": "^29.0.0",
|
|
55
|
-
"typescript": "^5.4.0"
|
|
57
|
+
"typescript": "^5.4.0",
|
|
58
|
+
"videoshow": "^0.1.12"
|
|
56
59
|
},
|
|
57
60
|
"engines": {
|
|
58
61
|
"node": ">=18"
|
|
59
62
|
},
|
|
60
63
|
"dependencies": {
|
|
61
64
|
"cors": "^2.8.6",
|
|
62
|
-
"express": "^5.2.1"
|
|
65
|
+
"express": "^5.2.1",
|
|
66
|
+
"puppeteer": "^25.1.0",
|
|
67
|
+
"ws": "^8.21.0"
|
|
63
68
|
}
|
|
64
69
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel AgentOS Full Guard — 全功能版
|
|
3
|
+
*
|
|
4
|
+
* preCheck: 轻量拦截(4.4μs)
|
|
5
|
+
* postCheck: 完整审计 + 三层记忆 + 三阶段评估 + 隐性反馈
|
|
6
|
+
*
|
|
7
|
+
* 模块初始化时自动注入语义记忆上下文到 session。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { AgentOS } = require('sentinel-agentos');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const AUDIT_DIR = path.join(__dirname, '..', '.sentinel-audit');
|
|
15
|
+
|
|
16
|
+
// 全局单例
|
|
17
|
+
if (!global.__sentinel_aos) {
|
|
18
|
+
const aos = new AgentOS({
|
|
19
|
+
workspaceRoot: process.cwd(),
|
|
20
|
+
maxWorkingTokens: 50000,
|
|
21
|
+
maxEpisodicSizeKb: 500,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 注册全套 Schema 规则
|
|
25
|
+
aos.guard.schema.registerRules([
|
|
26
|
+
{ tool: 'exec', required: ['command'] },
|
|
27
|
+
{
|
|
28
|
+
tool: 'write', required: ['path', 'content'],
|
|
29
|
+
pathDeny: { path: ['.env', '*.key', '*.pem', '.git/**', '**/credentials/**'] },
|
|
30
|
+
maxSize: { content: 1048576 }, secrets: ['content'],
|
|
31
|
+
},
|
|
32
|
+
{ tool: 'read', required: ['path'], pathDeny: { path: ['.env', '*.key'] } },
|
|
33
|
+
{ tool: 'edit', required: ['path'], pathDeny: { path: ['.env', '*.key', '.git/**'] } },
|
|
34
|
+
{
|
|
35
|
+
tool: 'delete', required: ['path'],
|
|
36
|
+
pathDeny: { path: ['.env', '*.key', '*.pem', '.git/**', 'node_modules/**', 'package.json'] },
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// 从磁盘恢复审计
|
|
41
|
+
const auditFile = path.join(AUDIT_DIR, 'audit.jsonl');
|
|
42
|
+
if (fs.existsSync(auditFile)) {
|
|
43
|
+
try {
|
|
44
|
+
fs.readFileSync(auditFile, 'utf-8').trim().split('\n').filter(Boolean).forEach(line => {
|
|
45
|
+
aos.guard.audit.entries.push(JSON.parse(line));
|
|
46
|
+
});
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 注入默认语义记忆
|
|
51
|
+
aos.memory.semantic.setPreference('user-name', '老板');
|
|
52
|
+
aos.memory.semantic.setPreference('language', 'zh-CN');
|
|
53
|
+
aos.memory.semantic.setPreference('direct-communication', true);
|
|
54
|
+
aos.memory.semantic.addFact('老板是中国用户,偏好直接、不说废话');
|
|
55
|
+
aos.memory.semantic.addFact('项目 coderev 是 AI 代码审查 CLI 工具');
|
|
56
|
+
aos.memory.semantic.addFact('项目 sentinel-agentos 是 AI Agent 操作系统');
|
|
57
|
+
aos.memory.semantic.learnRule('高风险操作前必须 preCheck', 'sentinel_init');
|
|
58
|
+
aos.memory.semantic.learnRule('操作完成后必须 postCheck 审计', 'sentinel_init');
|
|
59
|
+
aos.memory.semantic.learnRule('npm publish 前必须确认版本号', 'sentinel_init');
|
|
60
|
+
|
|
61
|
+
// 记录首次启动事件
|
|
62
|
+
aos.memory.episodic.record('milestone',
|
|
63
|
+
'Sentinel AgentOS 全功能启用:Guard + Memory + Evaluator',
|
|
64
|
+
['init', 'milestone'], ['sentinel-agentos']);
|
|
65
|
+
|
|
66
|
+
global.__sentinel_aos = aos;
|
|
67
|
+
global.__sentinel_session_id = 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const aos = global.__sentinel_aos;
|
|
71
|
+
let opCounter = 0;
|
|
72
|
+
|
|
73
|
+
// ── 确定性规则(零 LLM)──
|
|
74
|
+
const DANGEROUS = [
|
|
75
|
+
[/rm\s+-rf\s+\//, 'rm -rf / — 删除整个系统'],
|
|
76
|
+
[/rm\s+-rf\s+~/, 'rm -rf ~ — 删除用户目录'],
|
|
77
|
+
[/sudo\s+rm/, 'sudo rm — 超级用户删除'],
|
|
78
|
+
[/mkfs\./, 'mkfs — 格式化磁盘'],
|
|
79
|
+
[/dd\s+if=/, 'dd — 可能覆盖分区'],
|
|
80
|
+
[/fork\s*bomb|:\(\)/, 'fork bomb — 系统崩溃'],
|
|
81
|
+
[/chmod\s+777\s+-R\s*\//, 'chmod 777 -R / — 权限全开'],
|
|
82
|
+
[/del\s+\/F\s+\/S\s+[A-Z]:\\/, 'del /F /S — 全盘删除'],
|
|
83
|
+
[/>\s*\/dev\/sd[a-z]/, '写入磁盘设备'],
|
|
84
|
+
];
|
|
85
|
+
const WARNING = [
|
|
86
|
+
[/git\s+push\s+--force/, 'git push --force — 强制覆盖'],
|
|
87
|
+
[/git\s+reset\s+--hard/, 'git reset --hard — 不可逆'],
|
|
88
|
+
[/npm\s+publish\b/, 'npm publish — 发布公共包'],
|
|
89
|
+
[/npm\s+unpublish\b/, 'npm unpublish — 从 npm 删除'],
|
|
90
|
+
[/DROP\s+(TABLE|DATABASE)/i, 'DROP — 删除数据库'],
|
|
91
|
+
[/TRUNCATE\s+(TABLE\s+)?/i, 'TRUNCATE — 清空表'],
|
|
92
|
+
];
|
|
93
|
+
const SENSITIVE = [
|
|
94
|
+
'.env', '.env.*', '*.key', '*.pem', '*.p12', '*.pfx', '*.jks', '*.keystore',
|
|
95
|
+
'.git/**', '**/credentials/**', '**/secrets/**', '**/SECRETS/**',
|
|
96
|
+
'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'Cargo.lock',
|
|
97
|
+
];
|
|
98
|
+
const PROTECTED = [
|
|
99
|
+
'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
100
|
+
'.gitignore', '.gitattributes', 'Cargo.toml', 'Cargo.lock', 'tsconfig.json',
|
|
101
|
+
'AGENTS.md', 'SOUL.md', 'MEMORY.md', 'USER.md',
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function globMatch(pattern, p) {
|
|
105
|
+
p = (p || '').replace(/\\/g, '/');
|
|
106
|
+
if (!pattern.includes('*')) return p === pattern || p.endsWith('/' + pattern);
|
|
107
|
+
const re = '^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*\*\//g, '(.*/)?').replace(/\*/g, '[^/]*') + '$';
|
|
108
|
+
return new RegExp(re).test(p);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
// ── 执行前拦截 ──
|
|
113
|
+
preCheck(toolName, params) {
|
|
114
|
+
if (toolName === 'exec' && params.command) {
|
|
115
|
+
const cmd = String(params.command);
|
|
116
|
+
for (const [re, desc] of DANGEROUS) {
|
|
117
|
+
if (re.test(cmd)) return { passed: false, block: true, risk: 'DENY', reason: `🚫 危险命令: ${desc}` };
|
|
118
|
+
}
|
|
119
|
+
for (const [re, desc] of WARNING) {
|
|
120
|
+
if (re.test(cmd)) return { passed: false, block: true, risk: 'CONFIRM', reason: `⚠️ 需要确认: ${desc}`, needsConfirmation: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const p = params.path || params.file;
|
|
124
|
+
if (p && ['write', 'edit', 'delete', 'read'].includes(toolName)) {
|
|
125
|
+
for (const ptn of SENSITIVE) {
|
|
126
|
+
if (globMatch(ptn, p)) return { passed: false, block: true, risk: 'DENY', reason: `🚫 敏感文件: "${p}" → "${ptn}"` };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (toolName === 'delete' && p) {
|
|
130
|
+
for (const pf of PROTECTED) {
|
|
131
|
+
if (String(p) === pf || String(p).endsWith('/' + pf) || String(p).endsWith('\\' + pf))
|
|
132
|
+
return { passed: false, block: true, risk: 'DENY', reason: `🚫 保护文件: "${pf}"` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { passed: true, risk: 'auto' };
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// ── 执行后审计(异步 AgentOS,不阻塞回复)──
|
|
139
|
+
postCheck(toolName, params, result) {
|
|
140
|
+
// 轻量审计(纯内存 + 5ms I/O,不调 git)
|
|
141
|
+
const entry = {
|
|
142
|
+
id: `${++opCounter}`,
|
|
143
|
+
ts: new Date().toISOString(),
|
|
144
|
+
sessionId: `s${global.__sentinel_session_id}`,
|
|
145
|
+
tool: toolName,
|
|
146
|
+
params: typeof params === 'string' ? params.slice(0, 200) : JSON.stringify(params || {}).slice(0, 200),
|
|
147
|
+
result: String(result || '').slice(0, 100),
|
|
148
|
+
};
|
|
149
|
+
aos.memory.working.addMessage('tool', `${toolName}: ${entry.params}`);
|
|
150
|
+
try {
|
|
151
|
+
if (!fs.existsSync(AUDIT_DIR)) fs.mkdirSync(AUDIT_DIR, { recursive: true });
|
|
152
|
+
fs.appendFileSync(path.join(AUDIT_DIR, 'audit.jsonl'), JSON.stringify(entry) + '\n');
|
|
153
|
+
} catch {}
|
|
154
|
+
|
|
155
|
+
// AgentOS 完整审计放到 next tick,不阻塞回复(230ms git → 后台)
|
|
156
|
+
setImmediate(() => {
|
|
157
|
+
try {
|
|
158
|
+
const sid = `s${global.__sentinel_session_id}_op${opCounter}`;
|
|
159
|
+
const { preExec, snapshot } = aos.executePipeline({
|
|
160
|
+
sessionId: sid, agentId: 'openclaw', toolName, parameters: params || {},
|
|
161
|
+
});
|
|
162
|
+
aos.completeExecution({
|
|
163
|
+
sessionId: sid, agentId: 'openclaw', toolName,
|
|
164
|
+
toolParameters: params || {}, toolResult: result ?? null,
|
|
165
|
+
snapshot, startTime: Date.now() - 500, endTime: Date.now(),
|
|
166
|
+
retryCount: 0, wasSelfCorrected: false, hadTimeout: false,
|
|
167
|
+
userAccepted: true, userProvidedEdit: false, resultWasUsed: true,
|
|
168
|
+
});
|
|
169
|
+
if (toolName === 'exec' && params?.command) {
|
|
170
|
+
aos.memory.episodic.record('tool_call', String(params.command), ['exec'], []);
|
|
171
|
+
}
|
|
172
|
+
} catch {}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return { auditId: entry.id, verify: 'QUEUED' };
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// ── 查看审计 ──
|
|
179
|
+
audit(limit = 10) {
|
|
180
|
+
return aos.guard.audit.query({ limit });
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// ── 完整状态报告 ──
|
|
184
|
+
status() {
|
|
185
|
+
return aos.statusReport();
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// ── 注入 Memory 上下文(session 启动时调用)─
|
|
189
|
+
injectContext() {
|
|
190
|
+
return aos.injectContext();
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// ── 记录反馈 ──
|
|
194
|
+
feedback(signal) {
|
|
195
|
+
aos.recordFeedback(signal, `s${global.__sentinel_session_id}`);
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// ── 结束 Session ──
|
|
199
|
+
endSession() {
|
|
200
|
+
const sid = `s${global.__sentinel_session_id}`;
|
|
201
|
+
aos.endSession(sid);
|
|
202
|
+
global.__sentinel_session_id++;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// ── 获取完整状态快照 ──
|
|
206
|
+
fullStatus() {
|
|
207
|
+
return {
|
|
208
|
+
sessionId: `s${global.__sentinel_session_id}`,
|
|
209
|
+
opCount: opCounter,
|
|
210
|
+
audit: aos.guard.audit.stats(),
|
|
211
|
+
profile: aos.getProfile(),
|
|
212
|
+
satisfaction: aos.evaluator.feedback.getSatisfactionScore(),
|
|
213
|
+
workingMemory: {
|
|
214
|
+
messages: aos.memory.working.recentMessages.length,
|
|
215
|
+
budget: aos.memory.working.budget,
|
|
216
|
+
},
|
|
217
|
+
episodicEvents: aos.memory.episodic.count,
|
|
218
|
+
semanticRules: aos.memory.semantic.getAllRules().length,
|
|
219
|
+
preferences: aos.memory.semantic.getPreference('language'),
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
};
|