agent-gear 2.3.0 → 2.5.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.
Files changed (93) hide show
  1. package/AGENTS.md +74 -3
  2. package/dist/cloud-host/supervisor.d.ts +92 -0
  3. package/dist/cloud-host/supervisor.d.ts.map +1 -0
  4. package/dist/cloud-host/supervisor.js +216 -0
  5. package/dist/cloud-host/supervisor.js.map +1 -0
  6. package/dist/cloud-host/supervisor.test.d.ts +2 -0
  7. package/dist/cloud-host/supervisor.test.d.ts.map +1 -0
  8. package/dist/cloud-host/supervisor.test.js +296 -0
  9. package/dist/cloud-host/supervisor.test.js.map +1 -0
  10. package/dist/commands/harness.d.ts +17 -0
  11. package/dist/commands/harness.d.ts.map +1 -0
  12. package/dist/commands/harness.js +35 -0
  13. package/dist/commands/harness.js.map +1 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +10 -3
  16. package/dist/config.js.map +1 -1
  17. package/dist/harness/env.d.ts +36 -0
  18. package/dist/harness/env.d.ts.map +1 -0
  19. package/dist/harness/env.js +78 -0
  20. package/dist/harness/env.js.map +1 -0
  21. package/dist/harness/env.test.d.ts +2 -0
  22. package/dist/harness/env.test.d.ts.map +1 -0
  23. package/dist/harness/env.test.js +70 -0
  24. package/dist/harness/env.test.js.map +1 -0
  25. package/dist/harness/opencode-cli.d.ts +24 -0
  26. package/dist/harness/opencode-cli.d.ts.map +1 -0
  27. package/dist/harness/opencode-cli.js +74 -0
  28. package/dist/harness/opencode-cli.js.map +1 -0
  29. package/dist/harness/opencode-cli.test.d.ts +2 -0
  30. package/dist/harness/opencode-cli.test.d.ts.map +1 -0
  31. package/dist/harness/opencode-cli.test.js +62 -0
  32. package/dist/harness/opencode-cli.test.js.map +1 -0
  33. package/dist/harness/opencode-config.atomic-failure.test.d.ts +2 -0
  34. package/dist/harness/opencode-config.atomic-failure.test.d.ts.map +1 -0
  35. package/dist/harness/opencode-config.atomic-failure.test.js +35 -0
  36. package/dist/harness/opencode-config.atomic-failure.test.js.map +1 -0
  37. package/dist/harness/opencode-config.d.ts +54 -0
  38. package/dist/harness/opencode-config.d.ts.map +1 -0
  39. package/dist/harness/opencode-config.js +130 -0
  40. package/dist/harness/opencode-config.js.map +1 -0
  41. package/dist/harness/opencode-config.test.d.ts +2 -0
  42. package/dist/harness/opencode-config.test.d.ts.map +1 -0
  43. package/dist/harness/opencode-config.test.js +183 -0
  44. package/dist/harness/opencode-config.test.js.map +1 -0
  45. package/dist/harness/prepare.d.ts +34 -0
  46. package/dist/harness/prepare.d.ts.map +1 -0
  47. package/dist/harness/prepare.js +46 -0
  48. package/dist/harness/prepare.js.map +1 -0
  49. package/dist/harness/prepare.test.d.ts +2 -0
  50. package/dist/harness/prepare.test.d.ts.map +1 -0
  51. package/dist/harness/prepare.test.js +96 -0
  52. package/dist/harness/prepare.test.js.map +1 -0
  53. package/dist/heartbeat.d.ts.map +1 -1
  54. package/dist/heartbeat.js +2 -0
  55. package/dist/heartbeat.js.map +1 -1
  56. package/dist/index.js +21 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/socketio-client.d.ts +62 -3
  59. package/dist/socketio-client.d.ts.map +1 -1
  60. package/dist/socketio-client.js +155 -16
  61. package/dist/socketio-client.js.map +1 -1
  62. package/dist/socketio-client.test.d.ts +2 -0
  63. package/dist/socketio-client.test.d.ts.map +1 -0
  64. package/dist/socketio-client.test.js +132 -0
  65. package/dist/socketio-client.test.js.map +1 -0
  66. package/dist/workload-executor.d.ts +31 -2
  67. package/dist/workload-executor.d.ts.map +1 -1
  68. package/dist/workload-executor.js +89 -16
  69. package/dist/workload-executor.js.map +1 -1
  70. package/dist/workload-executor.test.js +180 -1
  71. package/dist/workload-executor.test.js.map +1 -1
  72. package/package.json +2 -1
  73. package/src/cloud-host/supervisor.test.ts +367 -0
  74. package/src/cloud-host/supervisor.ts +289 -0
  75. package/src/commands/harness.ts +44 -0
  76. package/src/config.ts +9 -3
  77. package/src/harness/env.test.ts +79 -0
  78. package/src/harness/env.ts +89 -0
  79. package/src/harness/opencode-cli.test.ts +82 -0
  80. package/src/harness/opencode-cli.ts +99 -0
  81. package/src/harness/opencode-config.atomic-failure.test.ts +51 -0
  82. package/src/harness/opencode-config.test.ts +238 -0
  83. package/src/harness/opencode-config.ts +176 -0
  84. package/src/harness/prepare.test.ts +117 -0
  85. package/src/harness/prepare.ts +69 -0
  86. package/src/heartbeat.ts +2 -0
  87. package/src/index.ts +22 -0
  88. package/src/socketio-client.test.ts +155 -0
  89. package/src/socketio-client.ts +204 -18
  90. package/src/workload-executor.test.ts +237 -0
  91. package/src/workload-executor.ts +115 -17
  92. package/tests/commands/harness.test.ts +90 -0
  93. package/tests/socketio-client.test.ts +140 -2
@@ -229,7 +229,7 @@ describe('SocketIO Client', () => {
229
229
  expect(mockSocket.emit).toHaveBeenCalledWith('register', {
230
230
  key: 'zag_k_1_abc123',
231
231
  hostname: 'my-machine',
232
- version: '2.3.0',
232
+ version: '2.5.0',
233
233
  resumeToken: undefined,
234
234
  homeDir: '/home/user',
235
235
  platform: 'linux',
@@ -247,7 +247,7 @@ describe('SocketIO Client', () => {
247
247
  expect(mockSocket.emit).toHaveBeenCalledWith('register', {
248
248
  key: 'zag_k_1_abc123',
249
249
  hostname: 'test-machine',
250
- version: '2.3.0',
250
+ version: '2.5.0',
251
251
  resumeToken: undefined,
252
252
  homeDir: '/home/user',
253
253
  platform: 'linux',
@@ -328,6 +328,144 @@ describe('SocketIO Client', () => {
328
328
 
329
329
  expect(console.error).toHaveBeenCalledWith('Connection error: connection failed');
330
330
  });
331
+
332
+ it('T-AG16: uses an explicit handshake token instead of the key (controller-api standard connection)', () => {
333
+ const mockSocket = createMockSocket();
334
+ vi.mocked(io).mockReturnValue(mockSocket as never);
335
+
336
+ connectToServer('wss://controller.example.com', 'zag_k_1_host_key', {
337
+ handshakeToken: 'zag_k_1_host_key',
338
+ });
339
+
340
+ expect(io).toHaveBeenCalledWith('wss://controller.example.com', expect.objectContaining({
341
+ auth: { token: 'zag_k_1_host_key' },
342
+ }));
343
+ });
344
+
345
+ it('T-AG16b: never puts the host key into the URL/query and supports handshakeToken=false', () => {
346
+ const mockSocket = createMockSocket();
347
+ vi.mocked(io).mockReturnValue(mockSocket as never);
348
+
349
+ connectToServer('wss://controller.example.com?token=leaked', 'zag_k_1_host_key', {
350
+ handshakeToken: false,
351
+ });
352
+
353
+ expect(io).toHaveBeenCalledWith(expect.not.stringContaining('token='), expect.objectContaining({ auth: {} }));
354
+ });
355
+
356
+ it('T-AG3: forwards the full registered payload (including initializing) to onRegistered', () => {
357
+ const mockSocket = createMockSocket();
358
+ vi.mocked(io).mockReturnValue(mockSocket as never);
359
+ const onRegistered = vi.fn();
360
+
361
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', { onRegistered });
362
+ mockSocket._trigger('registered', { controllerId: 'ctrl-1', initializing: true });
363
+
364
+ expect(onRegistered).toHaveBeenCalledWith({ controllerId: 'ctrl-1', initializing: true });
365
+ });
366
+
367
+ it('T-AG4: invokes onWorkloadBatchComplete after an init batch ends', async () => {
368
+ const mockSocket = createMockSocket();
369
+ vi.mocked(io).mockReturnValue(mockSocket as never);
370
+ const onWorkloadBatchComplete = vi.fn();
371
+
372
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', { onWorkloadBatchComplete });
373
+ mockSocket._trigger('workload:run', {
374
+ phase: 'init',
375
+ // 空脚本立即产生失败结果(不需要真实 spawn),用于锚定「批次结束即回调且摘要反映失败」
376
+ items: [{ logId: 'init-1', workloadName: 'Init', script: '', timeoutMs: 5000 }],
377
+ });
378
+
379
+ await vi.waitFor(() => {
380
+ expect(mockSocket.emit).toHaveBeenCalledWith('workload:result', expect.objectContaining({ logId: 'init-1' }));
381
+ expect(onWorkloadBatchComplete).toHaveBeenCalledWith({
382
+ phase: 'init',
383
+ itemCount: 1,
384
+ status: 'failed',
385
+ cancelled: false,
386
+ failedCount: 1,
387
+ });
388
+ });
389
+ });
390
+
391
+ it('T-AG4b: invokes onWorkloadBatchComplete even when a required init item fails', async () => {
392
+ const mockSocket = createMockSocket();
393
+ vi.mocked(io).mockReturnValue(mockSocket as never);
394
+ const onWorkloadBatchComplete = vi.fn();
395
+
396
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', { onWorkloadBatchComplete });
397
+ mockSocket._trigger('workload:run', {
398
+ phase: 'init',
399
+ items: [{ logId: 'init-fail', workloadName: 'Fail', script: '', timeoutMs: 5000, required: true }],
400
+ });
401
+
402
+ await vi.waitFor(() => {
403
+ expect(onWorkloadBatchComplete).toHaveBeenCalledWith({
404
+ phase: 'init',
405
+ itemCount: 1,
406
+ status: 'failed',
407
+ cancelled: false,
408
+ failedCount: 1,
409
+ });
410
+ });
411
+ });
412
+
413
+ it('T-AG18: skips the local MCP server when skipLocalMcpStart is set (dual connection singleton)', () => {
414
+ const mockSocket = createMockSocket();
415
+ vi.mocked(io).mockReturnValue(mockSocket as never);
416
+
417
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', { skipLocalMcpStart: true });
418
+
419
+ expect(localServerInstances).toHaveLength(0);
420
+ });
421
+
422
+ it('T-AG17: delegates shutdown to onShutdown without clearing local config or exiting', () => {
423
+ const mockSocket = createMockSocket();
424
+ vi.mocked(io).mockReturnValue(mockSocket as never);
425
+ const onShutdown = vi.fn();
426
+
427
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', { onShutdown });
428
+ mockSocket._trigger('shutdown', { reason: 'container destroyed' });
429
+
430
+ expect(onShutdown).toHaveBeenCalledWith({ reason: 'container destroyed' });
431
+ expect(clearConfigKey).not.toHaveBeenCalled();
432
+ expect(process.exit).not.toHaveBeenCalled();
433
+ });
434
+
435
+ it('T-AG16c: does not handle workload events when handleWorkload=false', () => {
436
+ const mockSocket = createMockSocket();
437
+ vi.mocked(io).mockReturnValue(mockSocket as never);
438
+ const onWorkloadBatchComplete = vi.fn();
439
+
440
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123', {
441
+ handleWorkload: false,
442
+ onWorkloadBatchComplete,
443
+ });
444
+ mockSocket._trigger('connect');
445
+ mockSocket._trigger('workload:run', {
446
+ phase: 'init',
447
+ items: [{ logId: 'x', workloadName: 'X', script: 'echo nope', timeoutMs: 5000 }],
448
+ });
449
+
450
+ expect(onWorkloadBatchComplete).not.toHaveBeenCalled();
451
+ expect(mockSocket.emit).not.toHaveBeenCalledWith('workload:result', expect.anything());
452
+ });
453
+
454
+ it('T-AG13: redacts secret values from invalid workload payload logs', () => {
455
+ const mockSocket = createMockSocket();
456
+ vi.mocked(io).mockReturnValue(mockSocket as never);
457
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
458
+
459
+ connectToServer('http://localhost:4001', 'zag_k_1_abc123');
460
+ mockSocket._trigger('workload:run', {
461
+ phase: 'init',
462
+ items: 'invalid',
463
+ env: { ZAG_MODEL_API_KEY: 'zag_sk_super_secret' },
464
+ });
465
+
466
+ expect(warnSpy).toHaveBeenCalledWith(expect.not.stringContaining('zag_sk_super_secret'));
467
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('***'));
468
+ });
331
469
  });
332
470
 
333
471
  describe('unregister', () => {