@zhin.js/agent 0.0.18 → 0.0.19

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +14 -8
  3. package/lib/builtin-tools.d.ts +4 -0
  4. package/lib/builtin-tools.d.ts.map +1 -1
  5. package/lib/builtin-tools.js +337 -29
  6. package/lib/builtin-tools.js.map +1 -1
  7. package/lib/file-policy.d.ts +41 -4
  8. package/lib/file-policy.d.ts.map +1 -1
  9. package/lib/file-policy.js +126 -4
  10. package/lib/file-policy.js.map +1 -1
  11. package/lib/index.d.ts +1 -1
  12. package/lib/index.d.ts.map +1 -1
  13. package/lib/index.js +1 -1
  14. package/lib/index.js.map +1 -1
  15. package/lib/init/create-zhin-agent.d.ts.map +1 -1
  16. package/lib/init/create-zhin-agent.js +1 -0
  17. package/lib/init/create-zhin-agent.js.map +1 -1
  18. package/lib/init/register-builtin-tools.d.ts.map +1 -1
  19. package/lib/init/register-builtin-tools.js +1 -0
  20. package/lib/init/register-builtin-tools.js.map +1 -1
  21. package/lib/zhin-agent/config.js +1 -1
  22. package/lib/zhin-agent/config.js.map +1 -1
  23. package/lib/zhin-agent/exec-policy.d.ts +48 -2
  24. package/lib/zhin-agent/exec-policy.d.ts.map +1 -1
  25. package/lib/zhin-agent/exec-policy.js +184 -23
  26. package/lib/zhin-agent/exec-policy.js.map +1 -1
  27. package/lib/zhin-agent/prompt.d.ts +14 -0
  28. package/lib/zhin-agent/prompt.d.ts.map +1 -1
  29. package/lib/zhin-agent/prompt.js +192 -45
  30. package/lib/zhin-agent/prompt.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/builtin-tools.ts +351 -30
  33. package/src/file-policy.ts +152 -4
  34. package/src/index.ts +5 -1
  35. package/src/init/create-zhin-agent.ts +1 -0
  36. package/src/init/register-builtin-tools.ts +1 -0
  37. package/src/zhin-agent/config.ts +1 -1
  38. package/src/zhin-agent/exec-policy.ts +229 -24
  39. package/src/zhin-agent/prompt.ts +209 -47
  40. package/tests/exec-policy.test.ts +355 -0
  41. package/tests/file-policy.test.ts +189 -1
@@ -1,5 +1,9 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { checkFileAccess, assertFileAccess, checkBashCommandSafety, shellEscape } from '../src/file-policy.js';
2
+ import {
3
+ checkFileAccess, assertFileAccess, checkBashCommandSafety, shellEscape,
4
+ isBlockedDevicePath, classifyBashCommand, isFileStale,
5
+ MAX_READ_FILE_SIZE, MAX_EDIT_FILE_SIZE,
6
+ } from '../src/file-policy.js';
3
7
 
4
8
  describe('file-policy', () => {
5
9
  // ── checkFileAccess ──
@@ -214,4 +218,188 @@ describe('file-policy', () => {
214
218
  expect(shellEscape('`whoami`')).toBe("'`whoami`'");
215
219
  });
216
220
  });
221
+
222
+ // ── isBlockedDevicePath(设备路径拦截)──
223
+
224
+ describe('isBlockedDevicePath', () => {
225
+ describe('应阻止危险设备路径', () => {
226
+ const blocked = [
227
+ '/dev/zero',
228
+ '/dev/random',
229
+ '/dev/urandom',
230
+ '/dev/full',
231
+ '/dev/stdin',
232
+ '/dev/tty',
233
+ '/dev/console',
234
+ '/dev/stdout',
235
+ '/dev/stderr',
236
+ '/dev/fd/0',
237
+ '/dev/fd/1',
238
+ '/dev/fd/2',
239
+ ];
240
+ for (const p of blocked) {
241
+ it(`阻止 ${p}`, () => {
242
+ expect(isBlockedDevicePath(p)).toBe(true);
243
+ });
244
+ }
245
+ });
246
+
247
+ describe('应阻止 Linux /proc/ fd 别名', () => {
248
+ it('阻止 /proc/self/fd/0', () => {
249
+ expect(isBlockedDevicePath('/proc/self/fd/0')).toBe(true);
250
+ });
251
+ it('阻止 /proc/1234/fd/1', () => {
252
+ expect(isBlockedDevicePath('/proc/1234/fd/1')).toBe(true);
253
+ });
254
+ it('阻止 /proc/self/fd/2', () => {
255
+ expect(isBlockedDevicePath('/proc/self/fd/2')).toBe(true);
256
+ });
257
+ });
258
+
259
+ describe('应允许安全设备路径', () => {
260
+ it('允许 /dev/null', () => {
261
+ expect(isBlockedDevicePath('/dev/null')).toBe(false);
262
+ });
263
+ it('允许普通文件', () => {
264
+ expect(isBlockedDevicePath('/home/user/file.txt')).toBe(false);
265
+ });
266
+ it('允许 /proc/self/fd/3', () => {
267
+ expect(isBlockedDevicePath('/proc/self/fd/3')).toBe(false);
268
+ });
269
+ });
270
+ });
271
+
272
+ // ── classifyBashCommand(命令读写分类)──
273
+
274
+ describe('classifyBashCommand', () => {
275
+ describe('只读搜索命令', () => {
276
+ it('grep 是搜索', () => {
277
+ const r = classifyBashCommand('grep -rn pattern src/');
278
+ expect(r.isSearch).toBe(true);
279
+ expect(r.isReadOnly).toBe(true);
280
+ });
281
+
282
+ it('rg 是搜索', () => {
283
+ const r = classifyBashCommand('rg "hello" .');
284
+ expect(r.isSearch).toBe(true);
285
+ expect(r.isReadOnly).toBe(true);
286
+ });
287
+
288
+ it('find 是搜索', () => {
289
+ const r = classifyBashCommand('find . -name "*.ts"');
290
+ expect(r.isSearch).toBe(true);
291
+ expect(r.isReadOnly).toBe(true);
292
+ });
293
+ });
294
+
295
+ describe('只读读取命令', () => {
296
+ it('cat 是读取', () => {
297
+ const r = classifyBashCommand('cat README.md');
298
+ expect(r.isRead).toBe(true);
299
+ expect(r.isReadOnly).toBe(true);
300
+ });
301
+
302
+ it('head 是读取', () => {
303
+ const r = classifyBashCommand('head -50 file.ts');
304
+ expect(r.isRead).toBe(true);
305
+ expect(r.isReadOnly).toBe(true);
306
+ });
307
+
308
+ it('wc -l 是读取', () => {
309
+ expect(classifyBashCommand('wc -l file.txt').isRead).toBe(true);
310
+ });
311
+
312
+ it('jq 是读取', () => {
313
+ expect(classifyBashCommand('jq .name package.json').isRead).toBe(true);
314
+ });
315
+ });
316
+
317
+ describe('只读列出命令', () => {
318
+ it('ls 是列出', () => {
319
+ const r = classifyBashCommand('ls -la');
320
+ expect(r.isList).toBe(true);
321
+ expect(r.isReadOnly).toBe(true);
322
+ });
323
+
324
+ it('tree 是列出', () => {
325
+ expect(classifyBashCommand('tree .').isList).toBe(true);
326
+ });
327
+ });
328
+
329
+ describe('管道组合', () => {
330
+ it('cat | grep 是只读', () => {
331
+ const r = classifyBashCommand('cat file.txt | grep pattern');
332
+ expect(r.isReadOnly).toBe(true);
333
+ });
334
+
335
+ it('cat file && echo done 是只读(echo 是中性命令)', () => {
336
+ const r = classifyBashCommand('cat file && echo done');
337
+ expect(r.isReadOnly).toBe(true);
338
+ });
339
+
340
+ it('cat | sort | uniq 是只读', () => {
341
+ expect(classifyBashCommand('cat file | sort | uniq').isReadOnly).toBe(true);
342
+ });
343
+ });
344
+
345
+ describe('写/执行命令', () => {
346
+ it('rm 不是只读', () => {
347
+ expect(classifyBashCommand('rm file.txt').isReadOnly).toBe(false);
348
+ });
349
+
350
+ it('npm install 不是只读', () => {
351
+ expect(classifyBashCommand('npm install').isReadOnly).toBe(false);
352
+ });
353
+
354
+ it('git push 不是只读', () => {
355
+ expect(classifyBashCommand('git push').isReadOnly).toBe(false);
356
+ });
357
+
358
+ it('混合管道 cat | xargs rm 不是只读', () => {
359
+ expect(classifyBashCommand('cat files.txt | xargs rm').isReadOnly).toBe(false);
360
+ });
361
+ });
362
+
363
+ describe('纯中性命令', () => {
364
+ it('echo "hello" 是只读', () => {
365
+ expect(classifyBashCommand('echo "hello"').isReadOnly).toBe(true);
366
+ });
367
+
368
+ it(': (noop) 是只读', () => {
369
+ expect(classifyBashCommand(':').isReadOnly).toBe(true);
370
+ });
371
+ });
372
+ });
373
+
374
+ // ── isFileStale ──
375
+
376
+ describe('isFileStale', () => {
377
+ it('相同 mtime 不是 stale', () => {
378
+ expect(isFileStale(1000, 1000)).toBe(false);
379
+ });
380
+
381
+ it('1ms 误差内不是 stale', () => {
382
+ expect(isFileStale(1000, 1000.5)).toBe(false);
383
+ });
384
+
385
+ it('超过 1ms 差异是 stale', () => {
386
+ expect(isFileStale(1000, 1002)).toBe(true);
387
+ });
388
+
389
+ it('mtime 变小也是 stale', () => {
390
+ expect(isFileStale(1000, 997)).toBe(true);
391
+ });
392
+ });
393
+
394
+ // ── 常量导出 ──
395
+
396
+ describe('常量', () => {
397
+ it('MAX_READ_FILE_SIZE 为 256 MiB', () => {
398
+ expect(MAX_READ_FILE_SIZE).toBe(256 * 1024 * 1024);
399
+ });
400
+
401
+ it('MAX_EDIT_FILE_SIZE 为 1 GiB', () => {
402
+ expect(MAX_EDIT_FILE_SIZE).toBe(1024 * 1024 * 1024);
403
+ });
404
+ });
217
405
  });