@zhin.js/agent 0.0.17 → 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 (61) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +14 -8
  3. package/lib/builtin-tools.d.ts +5 -137
  4. package/lib/builtin-tools.d.ts.map +1 -1
  5. package/lib/builtin-tools.js +321 -732
  6. package/lib/builtin-tools.js.map +1 -1
  7. package/lib/discover-agents.d.ts +28 -0
  8. package/lib/discover-agents.d.ts.map +1 -0
  9. package/lib/discover-agents.js +116 -0
  10. package/lib/discover-agents.js.map +1 -0
  11. package/lib/discover-skills.d.ts +49 -0
  12. package/lib/discover-skills.d.ts.map +1 -0
  13. package/lib/discover-skills.js +297 -0
  14. package/lib/discover-skills.js.map +1 -0
  15. package/lib/discover-tools.d.ts +56 -0
  16. package/lib/discover-tools.d.ts.map +1 -0
  17. package/lib/discover-tools.js +263 -0
  18. package/lib/discover-tools.js.map +1 -0
  19. package/lib/discovery-utils.d.ts +27 -0
  20. package/lib/discovery-utils.d.ts.map +1 -0
  21. package/lib/discovery-utils.js +96 -0
  22. package/lib/discovery-utils.js.map +1 -0
  23. package/lib/file-policy.d.ts +41 -4
  24. package/lib/file-policy.d.ts.map +1 -1
  25. package/lib/file-policy.js +126 -4
  26. package/lib/file-policy.js.map +1 -1
  27. package/lib/index.d.ts +1 -1
  28. package/lib/index.d.ts.map +1 -1
  29. package/lib/index.js +1 -1
  30. package/lib/index.js.map +1 -1
  31. package/lib/init/create-zhin-agent.d.ts.map +1 -1
  32. package/lib/init/create-zhin-agent.js +3 -1
  33. package/lib/init/create-zhin-agent.js.map +1 -1
  34. package/lib/init/register-builtin-tools.d.ts.map +1 -1
  35. package/lib/init/register-builtin-tools.js +51 -54
  36. package/lib/init/register-builtin-tools.js.map +1 -1
  37. package/lib/zhin-agent/config.js +1 -1
  38. package/lib/zhin-agent/config.js.map +1 -1
  39. package/lib/zhin-agent/exec-policy.d.ts +48 -2
  40. package/lib/zhin-agent/exec-policy.d.ts.map +1 -1
  41. package/lib/zhin-agent/exec-policy.js +184 -23
  42. package/lib/zhin-agent/exec-policy.js.map +1 -1
  43. package/lib/zhin-agent/prompt.d.ts +14 -0
  44. package/lib/zhin-agent/prompt.d.ts.map +1 -1
  45. package/lib/zhin-agent/prompt.js +192 -45
  46. package/lib/zhin-agent/prompt.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/builtin-tools.ts +333 -835
  49. package/src/discover-agents.ts +138 -0
  50. package/src/discover-skills.ts +325 -0
  51. package/src/discover-tools.ts +302 -0
  52. package/src/discovery-utils.ts +96 -0
  53. package/src/file-policy.ts +152 -4
  54. package/src/index.ts +5 -1
  55. package/src/init/create-zhin-agent.ts +3 -1
  56. package/src/init/register-builtin-tools.ts +51 -62
  57. package/src/zhin-agent/config.ts +1 -1
  58. package/src/zhin-agent/exec-policy.ts +229 -24
  59. package/src/zhin-agent/prompt.ts +209 -47
  60. package/tests/exec-policy.test.ts +355 -0
  61. 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
  });